@@ -93,7 +93,8 @@ pub const DEFAULT_LOOKAHEAD: u32 = 25;
93
93
/// }
94
94
/// }
95
95
///
96
- /// let mut txout_index = KeychainTxOutIndex::<MyKeychain>::default();
96
+ /// // Construct index with lookahead of 21 and enable spk caching.
97
+ /// let mut txout_index = KeychainTxOutIndex::<MyKeychain>::new(21, true);
97
98
///
98
99
/// # let secp = bdk_chain::bitcoin::secp256k1::Secp256k1::signing_only();
99
100
/// # let (external_descriptor,_) = Descriptor::<DescriptorPublicKey>::parse_descriptor(&secp, "tr([73c5da0a/86'/0'/0']xprv9xgqHN7yz9MwCkxsBPN5qetuNdQSUttZNKw1dcYTV4mkaAFiBVGQziHs3NRSWMkCzvgjEe3n9xV8oYywvM8at9yRqyaZVz6TYYhX98VjsUk/0/*)").unwrap();
@@ -132,8 +133,12 @@ pub struct KeychainTxOutIndex<K> {
132
133
last_revealed : HashMap < DescriptorId , u32 > ,
133
134
lookahead : u32 ,
134
135
135
- use_spk_cache : bool ,
136
+ /// If `true`, the script pubkeys are persisted across restarts to avoid re-derivation.
137
+ /// If `false`, `spk_cache` and `spk_cache_stage` will remain empty.
138
+ persist_spks : bool ,
139
+ /// Cache of derived spks.
136
140
spk_cache : BTreeMap < DescriptorId , HashMap < u32 , ScriptBuf > > ,
141
+ /// Staged script pubkeys waiting to be written out in the next ChangeSet.
137
142
spk_cache_stage : BTreeMap < DescriptorId , Vec < ( u32 , ScriptBuf ) > > ,
138
143
}
139
144
@@ -199,27 +204,38 @@ impl<K> KeychainTxOutIndex<K> {
199
204
///
200
205
/// # Lookahead
201
206
///
202
- /// The `lookahead` is the number of script pubkeys to derive and cache from the internal
203
- /// descriptors over and above the last revealed script index. Without a lookahead the index
204
- /// will miss outputs you own when processing transactions whose output script pubkeys lie
205
- /// beyond the last revealed index. In certain situations, such as when performing an initial
206
- /// scan of the blockchain during wallet import, it may be uncertain or unknown what the index
207
- /// of the last revealed script pubkey actually is.
207
+ /// The `lookahead` parameter controls how many script pubkeys to derive *beyond* the highest
208
+ /// revealed index for each keychain (external/internal). Without any lookahead, the index will
209
+ /// miss outputs sent to addresses you haven’t explicitly revealed yet. A nonzero `lookahead`
210
+ /// lets you catch outputs on those “future” addresses automatically.
208
211
///
209
212
/// Refer to [struct-level docs](KeychainTxOutIndex) for more about `lookahead`.
210
213
///
211
- /// # Script pubkey cache
214
+ /// # Script pubkey persistence
212
215
///
213
- /// The `use_spk_cache` parameter enables the internal script pubkey cache.
214
- pub fn new ( lookahead : u32 , use_spk_cache : bool ) -> Self {
216
+ /// Derived script pubkeys remain in memory. If `persist_spks` is `true`, they're saved and
217
+ /// reloaded via the `ChangeSet` on startup, avoiding re-derivation. Otherwise, they must be
218
+ /// re-derived on init, affecting startup only for very large or complex wallets.
219
+ ///
220
+ /// # Examples
221
+ ///
222
+ /// ```rust
223
+ /// # use bdk_chain::KeychainTxOutIndex;
224
+ /// // Derive 20 future addresses per chain and persist + reload script pubkeys via ChangeSets:
225
+ /// let idx = KeychainTxOutIndex::new(20, true);
226
+ ///
227
+ /// // Derive 10 future addresses per chain without persistence:
228
+ /// let idx = KeychainTxOutIndex::new(10, false);
229
+ /// ```
230
+ pub fn new ( lookahead : u32 , persist_spks : bool ) -> Self {
215
231
Self {
216
232
inner : SpkTxOutIndex :: default ( ) ,
217
233
keychain_to_descriptor_id : Default :: default ( ) ,
218
234
descriptors : Default :: default ( ) ,
219
235
descriptor_id_to_keychain : Default :: default ( ) ,
220
236
last_revealed : Default :: default ( ) ,
221
237
lookahead,
222
- use_spk_cache ,
238
+ persist_spks ,
223
239
spk_cache : Default :: default ( ) ,
224
240
spk_cache_stage : Default :: default ( ) ,
225
241
}
@@ -270,7 +286,7 @@ impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
270
286
}
271
287
272
288
fn _empty_stage_into_changeset ( & mut self , changeset : & mut ChangeSet ) {
273
- if !self . use_spk_cache {
289
+ if !self . persist_spks {
274
290
return ;
275
291
}
276
292
for ( did, spks) in core:: mem:: take ( & mut self . spk_cache_stage ) {
@@ -542,7 +558,7 @@ impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
542
558
1
543
559
} ;
544
560
545
- if self . use_spk_cache {
561
+ if self . persist_spks {
546
562
let derive_spk = {
547
563
let secp = Secp256k1 :: verification_only ( ) ;
548
564
let _desc = & descriptor;
@@ -927,7 +943,7 @@ impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
927
943
* v = index. max ( * v) ;
928
944
self . replenish_inner_index_did ( did, self . lookahead ) ;
929
945
}
930
- if self . use_spk_cache {
946
+ if self . persist_spks {
931
947
for ( did, spks) in changeset. spk_cache {
932
948
self . spk_cache . entry ( did) . or_default ( ) . extend ( spks) ;
933
949
}
@@ -982,14 +998,22 @@ impl<K: core::fmt::Debug> core::fmt::Display for InsertDescriptorError<K> {
982
998
#[ cfg( feature = "std" ) ]
983
999
impl < K : core:: fmt:: Debug > std:: error:: Error for InsertDescriptorError < K > { }
984
1000
985
- /// Represents updates to the derivation index of a [`KeychainTxOutIndex`].
986
- /// It maps each keychain `K` to a descriptor and its last revealed index.
1001
+ /// `ChangeSet` represents persistent updates to a [`KeychainTxOutIndex`].
1002
+ ///
1003
+ /// It tracks:
1004
+ /// 1. `last_revealed`: the highest derivation index revealed per descriptor.
1005
+ /// 2. `spks`: the cache of derived script pubkeys to persist across runs.
1006
+ ///
1007
+ /// You can apply a `ChangeSet` to a `KeychainTxOutIndex` via
1008
+ /// [`KeychainTxOutIndex::apply_changeset`], or merge two change sets with [`ChangeSet::merge`].
987
1009
///
988
- /// It can be applied to [`KeychainTxOutIndex`] with [`apply_changeset`].
1010
+ /// # Monotonicity
989
1011
///
990
- /// The `last_revealed` field is monotone in that [`merge`] will never decrease it.
991
- /// `keychains_added` is *not* monotone, once it is set any attempt to change it is subject to the
992
- /// same *one-to-one* keychain <-> descriptor mapping invariant as [`KeychainTxOutIndex`] itself.
1012
+ /// - `last_revealed` is monotonic: merging retains the maximum index for each descriptor and never
1013
+ /// decreases.
1014
+ /// - `spks` accumulates entries: once a script pubkey is persisted, it remains available for
1015
+ /// reload. If the same descriptor and index appear again with a new script pubkey, the latter
1016
+ /// value overrides the former.
993
1017
///
994
1018
/// [`KeychainTxOutIndex`]: crate::keychain_txout::KeychainTxOutIndex
995
1019
/// [`apply_changeset`]: crate::keychain_txout::KeychainTxOutIndex::apply_changeset
@@ -998,10 +1022,11 @@ impl<K: core::fmt::Debug> std::error::Error for InsertDescriptorError<K> {}
998
1022
#[ cfg_attr( feature = "serde" , derive( serde:: Deserialize , serde:: Serialize ) ) ]
999
1023
#[ must_use]
1000
1024
pub struct ChangeSet {
1001
- /// Contains for each descriptor_id the last revealed index of derivation
1025
+ /// Maps each `DescriptorId` to its last revealed derivation index.
1002
1026
pub last_revealed : BTreeMap < DescriptorId , u32 > ,
1003
1027
1004
- /// Cache of previously derived script pubkeys.
1028
+ /// Cache of derived script pubkeys to persist, keyed by descriptor ID and derivation index
1029
+ /// (`u32`).
1005
1030
#[ cfg_attr( feature = "serde" , serde( default ) ) ]
1006
1031
pub spk_cache : BTreeMap < DescriptorId , BTreeMap < u32 , ScriptBuf > > ,
1007
1032
}
0 commit comments