Skip to content

Commit 8fef7c7

Browse files
committed
docs(chain): Adds docs for persisting spks in KeychainTxOutIndex
1 parent 3126cd2 commit 8fef7c7

File tree

1 file changed

+48
-23
lines changed

1 file changed

+48
-23
lines changed

crates/chain/src/indexer/keychain_txout.rs

Lines changed: 48 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,8 @@ pub const DEFAULT_LOOKAHEAD: u32 = 25;
9393
/// }
9494
/// }
9595
///
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);
9798
///
9899
/// # let secp = bdk_chain::bitcoin::secp256k1::Secp256k1::signing_only();
99100
/// # let (external_descriptor,_) = Descriptor::<DescriptorPublicKey>::parse_descriptor(&secp, "tr([73c5da0a/86'/0'/0']xprv9xgqHN7yz9MwCkxsBPN5qetuNdQSUttZNKw1dcYTV4mkaAFiBVGQziHs3NRSWMkCzvgjEe3n9xV8oYywvM8at9yRqyaZVz6TYYhX98VjsUk/0/*)").unwrap();
@@ -132,8 +133,12 @@ pub struct KeychainTxOutIndex<K> {
132133
last_revealed: HashMap<DescriptorId, u32>,
133134
lookahead: u32,
134135

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.
136140
spk_cache: BTreeMap<DescriptorId, HashMap<u32, ScriptBuf>>,
141+
/// Staged script pubkeys waiting to be written out in the next ChangeSet.
137142
spk_cache_stage: BTreeMap<DescriptorId, Vec<(u32, ScriptBuf)>>,
138143
}
139144

@@ -199,27 +204,38 @@ impl<K> KeychainTxOutIndex<K> {
199204
///
200205
/// # Lookahead
201206
///
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.
208211
///
209212
/// Refer to [struct-level docs](KeychainTxOutIndex) for more about `lookahead`.
210213
///
211-
/// # Script pubkey cache
214+
/// # Script pubkey persistence
212215
///
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 {
215231
Self {
216232
inner: SpkTxOutIndex::default(),
217233
keychain_to_descriptor_id: Default::default(),
218234
descriptors: Default::default(),
219235
descriptor_id_to_keychain: Default::default(),
220236
last_revealed: Default::default(),
221237
lookahead,
222-
use_spk_cache,
238+
persist_spks,
223239
spk_cache: Default::default(),
224240
spk_cache_stage: Default::default(),
225241
}
@@ -270,7 +286,7 @@ impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
270286
}
271287

272288
fn _empty_stage_into_changeset(&mut self, changeset: &mut ChangeSet) {
273-
if !self.use_spk_cache {
289+
if !self.persist_spks {
274290
return;
275291
}
276292
for (did, spks) in core::mem::take(&mut self.spk_cache_stage) {
@@ -542,7 +558,7 @@ impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
542558
1
543559
};
544560

545-
if self.use_spk_cache {
561+
if self.persist_spks {
546562
let derive_spk = {
547563
let secp = Secp256k1::verification_only();
548564
let _desc = &descriptor;
@@ -927,7 +943,7 @@ impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
927943
*v = index.max(*v);
928944
self.replenish_inner_index_did(did, self.lookahead);
929945
}
930-
if self.use_spk_cache {
946+
if self.persist_spks {
931947
for (did, spks) in changeset.spk_cache {
932948
self.spk_cache.entry(did).or_default().extend(spks);
933949
}
@@ -982,14 +998,22 @@ impl<K: core::fmt::Debug> core::fmt::Display for InsertDescriptorError<K> {
982998
#[cfg(feature = "std")]
983999
impl<K: core::fmt::Debug> std::error::Error for InsertDescriptorError<K> {}
9841000

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`].
9871009
///
988-
/// It can be applied to [`KeychainTxOutIndex`] with [`apply_changeset`].
1010+
/// # Monotonicity
9891011
///
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.
9931017
///
9941018
/// [`KeychainTxOutIndex`]: crate::keychain_txout::KeychainTxOutIndex
9951019
/// [`apply_changeset`]: crate::keychain_txout::KeychainTxOutIndex::apply_changeset
@@ -998,10 +1022,11 @@ impl<K: core::fmt::Debug> std::error::Error for InsertDescriptorError<K> {}
9981022
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
9991023
#[must_use]
10001024
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.
10021026
pub last_revealed: BTreeMap<DescriptorId, u32>,
10031027

1004-
/// Cache of previously derived script pubkeys.
1028+
/// Cache of derived script pubkeys to persist, keyed by descriptor ID and derivation index
1029+
/// (`u32`).
10051030
#[cfg_attr(feature = "serde", serde(default))]
10061031
pub spk_cache: BTreeMap<DescriptorId, BTreeMap<u32, ScriptBuf>>,
10071032
}

0 commit comments

Comments
 (0)