bdk_chain/indexer/
keychain_txout.rs

1//! [`KeychainTxOutIndex`] controls how script pubkeys are revealed for multiple keychains and
2//! indexes [`TxOut`]s with them.
3
4use crate::{
5    alloc::boxed::Box,
6    collections::*,
7    miniscript::{Descriptor, DescriptorPublicKey},
8    spk_client::{FullScanRequestBuilder, SyncRequestBuilder},
9    spk_iter::BIP32_MAX_INDEX,
10    spk_txout::SpkTxOutIndex,
11    DescriptorExt, DescriptorId, Indexed, Indexer, KeychainIndexed, SpkIterator,
12};
13use alloc::{borrow::ToOwned, vec::Vec};
14use bitcoin::{
15    key::Secp256k1, Amount, OutPoint, ScriptBuf, SignedAmount, Transaction, TxOut, Txid,
16};
17use core::{
18    fmt::Debug,
19    ops::{Bound, RangeBounds},
20};
21
22use crate::Merge;
23
24/// The default lookahead for a [`KeychainTxOutIndex`]
25pub const DEFAULT_LOOKAHEAD: u32 = 25;
26
27/// [`KeychainTxOutIndex`] controls how script pubkeys are revealed for multiple keychains, and
28/// indexes [`TxOut`]s with them.
29///
30/// A single keychain is a chain of script pubkeys derived from a single [`Descriptor`]. Keychains
31/// are identified using the `K` generic. Script pubkeys are identified by the keychain that they
32/// are derived from `K`, as well as the derivation index `u32`.
33///
34/// There is a strict 1-to-1 relationship between descriptors and keychains. Each keychain has one
35/// and only one descriptor and each descriptor has one and only one keychain. The
36/// [`insert_descriptor`] method will return an error if you try and violate this invariant. This
37/// rule is a proxy for a stronger rule: no two descriptors should produce the same script pubkey.
38/// Having two descriptors produce the same script pubkey should cause whichever keychain derives
39/// the script pubkey first to be the effective owner of it but you should not rely on this
40/// behaviour. ⚠ It is up you, the developer, not to violate this invariant.
41///
42/// # Revealed script pubkeys
43///
44/// Tracking how script pubkeys are revealed is useful for collecting chain data. For example, if
45/// the user has requested 5 script pubkeys (to receive money with), we only need to use those
46/// script pubkeys to scan for chain data.
47///
48/// Call [`reveal_to_target`] or [`reveal_next_spk`] to reveal more script pubkeys.
49/// Call [`revealed_keychain_spks`] or [`revealed_spks`] to iterate through revealed script pubkeys.
50///
51/// # Lookahead script pubkeys
52///
53/// When an user first recovers a wallet (i.e. from a recovery phrase and/or descriptor), we will
54/// NOT have knowledge of which script pubkeys are revealed. So when we index a transaction or
55/// txout (using [`index_tx`]/[`index_txout`]) we scan the txouts against script pubkeys derived
56/// above the last revealed index. These additionally-derived script pubkeys are called the
57/// lookahead.
58///
59/// The [`KeychainTxOutIndex`] is constructed with the `lookahead` and cannot be altered. See
60/// [`DEFAULT_LOOKAHEAD`] for the value used in the `Default` implementation. Use [`new`] to set a
61/// custom `lookahead`.
62///
63/// # Unbounded script pubkey iterator
64///
65/// For script-pubkey-based chain sources (such as Electrum/Esplora), an initial scan is best done
66/// by iterating though derived script pubkeys one by one and requesting transaction histories for
67/// each script pubkey. We will stop after x-number of script pubkeys have empty histories. An
68/// unbounded script pubkey iterator is useful to pass to such a chain source because it doesn't
69/// require holding a reference to the index.
70///
71/// Call [`unbounded_spk_iter`] to get an unbounded script pubkey iterator for a given keychain.
72/// Call [`all_unbounded_spk_iters`] to get unbounded script pubkey iterators for all keychains.
73///
74/// # Change sets
75///
76/// Methods that can update the last revealed index or add keychains will return [`ChangeSet`] to
77/// report these changes. This should be persisted for future recovery.
78///
79/// ## Synopsis
80///
81/// ```
82/// use bdk_chain::indexer::keychain_txout::KeychainTxOutIndex;
83/// # use bdk_chain::{ miniscript::{Descriptor, DescriptorPublicKey} };
84/// # use core::str::FromStr;
85///
86/// // imagine our service has internal and external addresses but also addresses for users
87/// #[derive(Clone, Debug, PartialEq, Eq, Ord, PartialOrd)]
88/// enum MyKeychain {
89///     External,
90///     Internal,
91///     MyAppUser {
92///         user_id: u32
93///     }
94/// }
95///
96/// // Construct index with lookahead of 21 and enable spk caching.
97/// let mut txout_index = KeychainTxOutIndex::<MyKeychain>::new(21, true);
98///
99/// # let secp = bdk_chain::bitcoin::secp256k1::Secp256k1::signing_only();
100/// # let (external_descriptor,_) = Descriptor::<DescriptorPublicKey>::parse_descriptor(&secp, "tr([73c5da0a/86'/0'/0']xprv9xgqHN7yz9MwCkxsBPN5qetuNdQSUttZNKw1dcYTV4mkaAFiBVGQziHs3NRSWMkCzvgjEe3n9xV8oYywvM8at9yRqyaZVz6TYYhX98VjsUk/0/*)").unwrap();
101/// # let (internal_descriptor,_) = Descriptor::<DescriptorPublicKey>::parse_descriptor(&secp, "tr([73c5da0a/86'/0'/0']xprv9xgqHN7yz9MwCkxsBPN5qetuNdQSUttZNKw1dcYTV4mkaAFiBVGQziHs3NRSWMkCzvgjEe3n9xV8oYywvM8at9yRqyaZVz6TYYhX98VjsUk/1/*)").unwrap();
102/// # let (descriptor_42, _) = Descriptor::<DescriptorPublicKey>::parse_descriptor(&secp, "tr([73c5da0a/86'/0'/0']xprv9xgqHN7yz9MwCkxsBPN5qetuNdQSUttZNKw1dcYTV4mkaAFiBVGQziHs3NRSWMkCzvgjEe3n9xV8oYywvM8at9yRqyaZVz6TYYhX98VjsUk/2/*)").unwrap();
103/// let _ = txout_index.insert_descriptor(MyKeychain::External, external_descriptor)?;
104/// let _ = txout_index.insert_descriptor(MyKeychain::Internal, internal_descriptor)?;
105/// let _ = txout_index.insert_descriptor(MyKeychain::MyAppUser { user_id: 42 }, descriptor_42)?;
106///
107/// let new_spk_for_user = txout_index.reveal_next_spk(MyKeychain::MyAppUser{ user_id: 42 });
108/// # Ok::<_, bdk_chain::indexer::keychain_txout::InsertDescriptorError<_>>(())
109/// ```
110///
111/// [`Ord`]: core::cmp::Ord
112/// [`SpkTxOutIndex`]: crate::spk_txout::SpkTxOutIndex
113/// [`Descriptor`]: crate::miniscript::Descriptor
114/// [`reveal_to_target`]: Self::reveal_to_target
115/// [`reveal_next_spk`]: Self::reveal_next_spk
116/// [`revealed_keychain_spks`]: Self::revealed_keychain_spks
117/// [`revealed_spks`]: Self::revealed_spks
118/// [`index_tx`]: Self::index_tx
119/// [`index_txout`]: Self::index_txout
120/// [`new`]: Self::new
121/// [`unbounded_spk_iter`]: Self::unbounded_spk_iter
122/// [`all_unbounded_spk_iters`]: Self::all_unbounded_spk_iters
123/// [`outpoints`]: Self::outpoints
124/// [`txouts`]: Self::txouts
125/// [`unused_spks`]: Self::unused_spks
126/// [`insert_descriptor`]: Self::insert_descriptor
127#[derive(Clone, Debug)]
128pub struct KeychainTxOutIndex<K> {
129    inner: SpkTxOutIndex<(K, u32)>,
130    keychain_to_descriptor_id: BTreeMap<K, DescriptorId>,
131    descriptor_id_to_keychain: HashMap<DescriptorId, K>,
132    descriptors: HashMap<DescriptorId, Descriptor<DescriptorPublicKey>>,
133    last_revealed: HashMap<DescriptorId, u32>,
134    lookahead: u32,
135
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.
140    spk_cache: BTreeMap<DescriptorId, HashMap<u32, ScriptBuf>>,
141    /// Staged script pubkeys waiting to be written out in the next ChangeSet.
142    spk_cache_stage: BTreeMap<DescriptorId, Vec<(u32, ScriptBuf)>>,
143}
144
145impl<K> Default for KeychainTxOutIndex<K> {
146    fn default() -> Self {
147        Self::new(DEFAULT_LOOKAHEAD, false)
148    }
149}
150
151impl<K> AsRef<SpkTxOutIndex<(K, u32)>> for KeychainTxOutIndex<K> {
152    fn as_ref(&self) -> &SpkTxOutIndex<(K, u32)> {
153        &self.inner
154    }
155}
156
157impl<K: Clone + Ord + Debug> Indexer for KeychainTxOutIndex<K> {
158    type ChangeSet = ChangeSet;
159
160    fn index_txout(&mut self, outpoint: OutPoint, txout: &TxOut) -> Self::ChangeSet {
161        let mut changeset = ChangeSet::default();
162        self._index_txout(&mut changeset, outpoint, txout);
163        self._empty_stage_into_changeset(&mut changeset);
164        changeset
165    }
166
167    fn index_tx(&mut self, tx: &bitcoin::Transaction) -> Self::ChangeSet {
168        let mut changeset = ChangeSet::default();
169        let txid = tx.compute_txid();
170        for (vout, txout) in tx.output.iter().enumerate() {
171            self._index_txout(&mut changeset, OutPoint::new(txid, vout as u32), txout);
172        }
173        self._empty_stage_into_changeset(&mut changeset);
174        changeset
175    }
176
177    fn initial_changeset(&self) -> Self::ChangeSet {
178        ChangeSet {
179            last_revealed: self.last_revealed.clone().into_iter().collect(),
180            spk_cache: self
181                .spk_cache
182                .iter()
183                .map(|(desc, spks)| {
184                    (
185                        *desc,
186                        spks.iter().map(|(i, spk)| (*i, spk.clone())).collect(),
187                    )
188                })
189                .collect(),
190        }
191    }
192
193    fn apply_changeset(&mut self, changeset: Self::ChangeSet) {
194        self.apply_changeset(changeset)
195    }
196
197    fn is_tx_relevant(&self, tx: &bitcoin::Transaction) -> bool {
198        self.inner.is_relevant(tx)
199    }
200}
201
202impl<K> KeychainTxOutIndex<K> {
203    /// Construct a [`KeychainTxOutIndex`] with the given `lookahead` and `persist_spks` boolean.
204    ///
205    /// # Lookahead
206    ///
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.
211    ///
212    /// Refer to [struct-level docs](KeychainTxOutIndex) for more about `lookahead`.
213    ///
214    /// # Script pubkey persistence
215    ///
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::keychain_txout::KeychainTxOutIndex;
224    /// // Derive 20 future addresses per keychain and persist + reload script pubkeys via ChangeSets:
225    /// let idx = KeychainTxOutIndex::<&'static str>::new(20, true);
226    ///
227    /// // Derive 10 future addresses per keychain without persistence:
228    /// let idx = KeychainTxOutIndex::<&'static str>::new(10, false);
229    /// ```
230    pub fn new(lookahead: u32, persist_spks: bool) -> Self {
231        Self {
232            inner: SpkTxOutIndex::default(),
233            keychain_to_descriptor_id: Default::default(),
234            descriptors: Default::default(),
235            descriptor_id_to_keychain: Default::default(),
236            last_revealed: Default::default(),
237            lookahead,
238            persist_spks,
239            spk_cache: Default::default(),
240            spk_cache_stage: Default::default(),
241        }
242    }
243
244    /// Get a reference to the internal [`SpkTxOutIndex`].
245    pub fn inner(&self) -> &SpkTxOutIndex<(K, u32)> {
246        &self.inner
247    }
248}
249
250/// Methods that are *re-exposed* from the internal [`SpkTxOutIndex`].
251impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
252    /// Construct `KeychainTxOutIndex<K>` from the given `changeset`.
253    ///
254    /// Shorthand for calling [`new`] and then [`apply_changeset`].
255    ///
256    /// [`new`]: Self::new
257    /// [`apply_changeset`]: Self::apply_changeset
258    pub fn from_changeset(lookahead: u32, use_spk_cache: bool, changeset: ChangeSet) -> Self {
259        let mut out = Self::new(lookahead, use_spk_cache);
260        out.apply_changeset(changeset);
261        out
262    }
263
264    fn _index_txout(&mut self, changeset: &mut ChangeSet, outpoint: OutPoint, txout: &TxOut) {
265        if let Some((keychain, index)) = self.inner.scan_txout(outpoint, txout).cloned() {
266            let did = self
267                .keychain_to_descriptor_id
268                .get(&keychain)
269                .expect("invariant");
270            let index_updated = match self.last_revealed.entry(*did) {
271                hash_map::Entry::Occupied(mut e) if e.get() < &index => {
272                    e.insert(index);
273                    true
274                }
275                hash_map::Entry::Vacant(e) => {
276                    e.insert(index);
277                    true
278                }
279                _ => false,
280            };
281            if index_updated {
282                changeset.last_revealed.insert(*did, index);
283                self.replenish_inner_index(*did, &keychain, self.lookahead);
284            }
285        }
286    }
287
288    fn _empty_stage_into_changeset(&mut self, changeset: &mut ChangeSet) {
289        if !self.persist_spks {
290            return;
291        }
292        for (did, spks) in core::mem::take(&mut self.spk_cache_stage) {
293            debug_assert!(
294                {
295                    let desc = self.descriptors.get(&did).expect("invariant");
296                    spks.iter().all(|(i, spk)| {
297                        let exp_spk = desc
298                            .at_derivation_index(*i)
299                            .expect("must derive")
300                            .script_pubkey();
301                        &exp_spk == spk
302                    })
303                },
304                "all staged spks must be correct"
305            );
306            changeset.spk_cache.entry(did).or_default().extend(spks);
307        }
308    }
309
310    /// Get the set of indexed outpoints, corresponding to tracked keychains.
311    pub fn outpoints(&self) -> &BTreeSet<KeychainIndexed<K, OutPoint>> {
312        self.inner.outpoints()
313    }
314
315    /// Iterate over known txouts that spend to tracked script pubkeys.
316    pub fn txouts(
317        &self,
318    ) -> impl DoubleEndedIterator<Item = KeychainIndexed<K, (OutPoint, &TxOut)>> + ExactSizeIterator
319    {
320        self.inner
321            .txouts()
322            .map(|(index, op, txout)| (index.clone(), (op, txout)))
323    }
324
325    /// Finds all txouts on a transaction that has previously been scanned and indexed.
326    pub fn txouts_in_tx(
327        &self,
328        txid: Txid,
329    ) -> impl DoubleEndedIterator<Item = KeychainIndexed<K, (OutPoint, &TxOut)>> {
330        self.inner
331            .txouts_in_tx(txid)
332            .map(|(index, op, txout)| (index.clone(), (op, txout)))
333    }
334
335    /// Return the [`TxOut`] of `outpoint` if it has been indexed, and if it corresponds to a
336    /// tracked keychain.
337    ///
338    /// The associated keychain and keychain index of the txout's spk is also returned.
339    ///
340    /// This calls [`SpkTxOutIndex::txout`] internally.
341    pub fn txout(&self, outpoint: OutPoint) -> Option<KeychainIndexed<K, &TxOut>> {
342        self.inner
343            .txout(outpoint)
344            .map(|(index, txout)| (index.clone(), txout))
345    }
346
347    /// Return the script that exists under the given `keychain`'s `index`.
348    ///
349    /// This calls [`SpkTxOutIndex::spk_at_index`] internally.
350    pub fn spk_at_index(&self, keychain: K, index: u32) -> Option<ScriptBuf> {
351        self.inner.spk_at_index(&(keychain.clone(), index))
352    }
353
354    /// Returns the keychain and keychain index associated with the spk.
355    ///
356    /// This calls [`SpkTxOutIndex::index_of_spk`] internally.
357    pub fn index_of_spk(&self, script: ScriptBuf) -> Option<&(K, u32)> {
358        self.inner.index_of_spk(script)
359    }
360
361    /// Returns whether the spk under the `keychain`'s `index` has been used.
362    ///
363    /// Here, "unused" means that after the script pubkey was stored in the index, the index has
364    /// never scanned a transaction output with it.
365    ///
366    /// This calls [`SpkTxOutIndex::is_used`] internally.
367    pub fn is_used(&self, keychain: K, index: u32) -> bool {
368        self.inner.is_used(&(keychain, index))
369    }
370
371    /// Marks the script pubkey at `index` as used even though the tracker hasn't seen an output
372    /// with it.
373    ///
374    /// This only has an effect when the `index` had been added to `self` already and was unused.
375    ///
376    /// Returns whether the spk under the given `keychain` and `index` is successfully
377    /// marked as used. Returns false either when there is no descriptor under the given
378    /// keychain, or when the spk is already marked as used.
379    ///
380    /// This is useful when you want to reserve a script pubkey for something but don't want to add
381    /// the transaction output using it to the index yet. Other callers will consider `index` on
382    /// `keychain` used until you call [`unmark_used`].
383    ///
384    /// This calls [`SpkTxOutIndex::mark_used`] internally.
385    ///
386    /// [`unmark_used`]: Self::unmark_used
387    pub fn mark_used(&mut self, keychain: K, index: u32) -> bool {
388        self.inner.mark_used(&(keychain, index))
389    }
390
391    /// Undoes the effect of [`mark_used`]. Returns whether the `index` is inserted back into
392    /// `unused`.
393    ///
394    /// Note that if `self` has scanned an output with this script pubkey, then this will have no
395    /// effect.
396    ///
397    /// This calls [`SpkTxOutIndex::unmark_used`] internally.
398    ///
399    /// [`mark_used`]: Self::mark_used
400    pub fn unmark_used(&mut self, keychain: K, index: u32) -> bool {
401        self.inner.unmark_used(&(keychain, index))
402    }
403
404    /// Computes the total value transfer effect `tx` has on the script pubkeys belonging to the
405    /// keychains in `range`. Value is *sent* when a script pubkey in the `range` is on an input and
406    /// *received* when it is on an output. For `sent` to be computed correctly, the output being
407    /// spent must have already been scanned by the index. Calculating received just uses the
408    /// [`Transaction`] outputs directly, so it will be correct even if it has not been scanned.
409    pub fn sent_and_received(
410        &self,
411        tx: &Transaction,
412        range: impl RangeBounds<K>,
413    ) -> (Amount, Amount) {
414        self.inner
415            .sent_and_received(tx, self.map_to_inner_bounds(range))
416    }
417
418    /// Computes the net value that this transaction gives to the script pubkeys in the index and
419    /// *takes* from the transaction outputs in the index. Shorthand for calling
420    /// [`sent_and_received`] and subtracting sent from received.
421    ///
422    /// This calls [`SpkTxOutIndex::net_value`] internally.
423    ///
424    /// [`sent_and_received`]: Self::sent_and_received
425    pub fn net_value(&self, tx: &Transaction, range: impl RangeBounds<K>) -> SignedAmount {
426        self.inner.net_value(tx, self.map_to_inner_bounds(range))
427    }
428}
429
430impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
431    /// Return all keychains and their corresponding descriptors.
432    pub fn keychains(
433        &self,
434    ) -> impl DoubleEndedIterator<Item = (K, &Descriptor<DescriptorPublicKey>)> + ExactSizeIterator + '_
435    {
436        self.keychain_to_descriptor_id
437            .iter()
438            .map(|(k, did)| (k.clone(), self.descriptors.get(did).expect("invariant")))
439    }
440
441    /// Insert a descriptor with a keychain associated to it.
442    ///
443    /// Adding a descriptor means you will be able to derive new script pubkeys under it and the
444    /// txout index will discover transaction outputs with those script pubkeys (once they've been
445    /// derived and added to the index).
446    ///
447    /// keychain <-> descriptor is a one-to-one mapping that cannot be changed. Attempting to do so
448    /// will return a [`InsertDescriptorError<K>`].
449    ///
450    /// [`KeychainTxOutIndex`] will prevent you from inserting two descriptors which derive the same
451    /// script pubkey at index 0, but it's up to you to ensure that descriptors don't collide at
452    /// other indices. If they do nothing catastrophic happens at the `KeychainTxOutIndex` level
453    /// (one keychain just becomes the defacto owner of that spk arbitrarily) but this may have
454    /// subtle implications up the application stack like one UTXO being missing from one keychain
455    /// because it has been assigned to another which produces the same script pubkey.
456    pub fn insert_descriptor(
457        &mut self,
458        keychain: K,
459        descriptor: Descriptor<DescriptorPublicKey>,
460    ) -> Result<bool, InsertDescriptorError<K>> {
461        let did = descriptor.descriptor_id();
462        if !self.keychain_to_descriptor_id.contains_key(&keychain)
463            && !self.descriptor_id_to_keychain.contains_key(&did)
464        {
465            self.descriptors.insert(did, descriptor.clone());
466            self.keychain_to_descriptor_id.insert(keychain.clone(), did);
467            self.descriptor_id_to_keychain.insert(did, keychain.clone());
468            self.replenish_inner_index(did, &keychain, self.lookahead);
469            return Ok(true);
470        }
471
472        if let Some(existing_desc_id) = self.keychain_to_descriptor_id.get(&keychain) {
473            let descriptor = self.descriptors.get(existing_desc_id).expect("invariant");
474            if *existing_desc_id != did {
475                return Err(InsertDescriptorError::KeychainAlreadyAssigned {
476                    existing_assignment: Box::new(descriptor.clone()),
477                    keychain,
478                });
479            }
480        }
481
482        if let Some(existing_keychain) = self.descriptor_id_to_keychain.get(&did) {
483            let descriptor = self.descriptors.get(&did).expect("invariant").clone();
484
485            if *existing_keychain != keychain {
486                return Err(InsertDescriptorError::DescriptorAlreadyAssigned {
487                    existing_assignment: existing_keychain.clone(),
488                    descriptor: Box::new(descriptor),
489                });
490            }
491        }
492
493        Ok(false)
494    }
495
496    /// Gets the descriptor associated with the keychain. Returns `None` if the keychain doesn't
497    /// have a descriptor associated with it.
498    pub fn get_descriptor(&self, keychain: K) -> Option<&Descriptor<DescriptorPublicKey>> {
499        let did = self.keychain_to_descriptor_id.get(&keychain)?;
500        self.descriptors.get(did)
501    }
502
503    /// Get the lookahead setting.
504    ///
505    /// Refer to [`new`] for more information on the `lookahead`.
506    ///
507    /// [`new`]: Self::new
508    pub fn lookahead(&self) -> u32 {
509        self.lookahead
510    }
511
512    /// Store lookahead scripts until `target_index` (inclusive).
513    ///
514    /// This does not change the global `lookahead` setting.
515    pub fn lookahead_to_target(&mut self, keychain: K, target_index: u32) -> ChangeSet {
516        let mut changeset = ChangeSet::default();
517        if let Some((next_index, _)) = self.next_index(keychain.clone()) {
518            let temp_lookahead = (target_index + 1)
519                .checked_sub(next_index)
520                .filter(|&index| index > 0);
521
522            if let Some(temp_lookahead) = temp_lookahead {
523                self.replenish_inner_index_keychain(keychain, temp_lookahead);
524            }
525        }
526        self._empty_stage_into_changeset(&mut changeset);
527        changeset
528    }
529
530    fn replenish_inner_index_did(&mut self, did: DescriptorId, lookahead: u32) {
531        if let Some(keychain) = self.descriptor_id_to_keychain.get(&did).cloned() {
532            self.replenish_inner_index(did, &keychain, lookahead);
533        }
534    }
535
536    fn replenish_inner_index_keychain(&mut self, keychain: K, lookahead: u32) {
537        if let Some(did) = self.keychain_to_descriptor_id.get(&keychain) {
538            self.replenish_inner_index(*did, &keychain, lookahead);
539        }
540    }
541
542    /// Syncs the state of the inner spk index after changes to a keychain
543    fn replenish_inner_index(&mut self, did: DescriptorId, keychain: &K, lookahead: u32) {
544        let descriptor = self.descriptors.get(&did).expect("invariant");
545
546        let mut next_index = self
547            .inner
548            .all_spks()
549            .range(&(keychain.clone(), u32::MIN)..=&(keychain.clone(), u32::MAX))
550            .last()
551            .map_or(0, |((_, index), _)| *index + 1);
552
553        // Exclusive: index to stop at.
554        let stop_index = if descriptor.has_wildcard() {
555            let next_reveal_index = self.last_revealed.get(&did).map_or(0, |v| *v + 1);
556            (next_reveal_index + lookahead).min(BIP32_MAX_INDEX)
557        } else {
558            1
559        };
560
561        if self.persist_spks {
562            let derive_spk = {
563                let secp = Secp256k1::verification_only();
564                let _desc = &descriptor;
565                move |spk_i: u32| -> ScriptBuf {
566                    _desc
567                        .derived_descriptor(&secp, spk_i)
568                        .expect("The descriptor cannot have hardened derivation")
569                        .script_pubkey()
570                }
571            };
572            let cached_spk_iter = core::iter::from_fn({
573                let spk_cache = self.spk_cache.entry(did).or_default();
574                let spk_stage = self.spk_cache_stage.entry(did).or_default();
575                let _i = &mut next_index;
576                move || -> Option<Indexed<ScriptBuf>> {
577                    if *_i >= stop_index {
578                        return None;
579                    }
580                    let spk_i = *_i;
581                    *_i = spk_i.saturating_add(1);
582
583                    if let Some(spk) = spk_cache.get(&spk_i) {
584                        debug_assert_eq!(spk, &derive_spk(spk_i), "cached spk must equal derived");
585                        return Some((spk_i, spk.clone()));
586                    }
587                    let spk = derive_spk(spk_i);
588                    spk_stage.push((spk_i, spk.clone()));
589                    spk_cache.insert(spk_i, spk.clone());
590                    Some((spk_i, spk))
591                }
592            });
593            for (new_index, new_spk) in cached_spk_iter {
594                let _inserted = self
595                    .inner
596                    .insert_spk((keychain.clone(), new_index), new_spk);
597                debug_assert!(_inserted, "replenish lookahead: must not have existing spk: keychain={keychain:?}, lookahead={lookahead}, next_index={next_index}");
598            }
599        } else {
600            let spk_iter = SpkIterator::new_with_range(descriptor, next_index..stop_index);
601            for (new_index, new_spk) in spk_iter {
602                let _inserted = self
603                    .inner
604                    .insert_spk((keychain.clone(), new_index), new_spk);
605                debug_assert!(_inserted, "replenish lookahead: must not have existing spk: keychain={keychain:?}, lookahead={lookahead}, next_index={next_index}");
606            }
607        }
608    }
609
610    /// Get an unbounded spk iterator over a given `keychain`. Returns `None` if the provided
611    /// keychain doesn't exist
612    pub fn unbounded_spk_iter(
613        &self,
614        keychain: K,
615    ) -> Option<SpkIterator<Descriptor<DescriptorPublicKey>>> {
616        let descriptor = self.get_descriptor(keychain)?.clone();
617        Some(SpkIterator::new(descriptor))
618    }
619
620    /// Get unbounded spk iterators for all keychains.
621    pub fn all_unbounded_spk_iters(
622        &self,
623    ) -> BTreeMap<K, SpkIterator<Descriptor<DescriptorPublicKey>>> {
624        self.keychain_to_descriptor_id
625            .iter()
626            .map(|(k, did)| {
627                (
628                    k.clone(),
629                    SpkIterator::new(self.descriptors.get(did).expect("invariant").clone()),
630                )
631            })
632            .collect()
633    }
634
635    /// Iterate over revealed spks of keychains in `range`
636    pub fn revealed_spks(
637        &self,
638        range: impl RangeBounds<K>,
639    ) -> impl Iterator<Item = KeychainIndexed<K, ScriptBuf>> + '_ {
640        let start = range.start_bound();
641        let end = range.end_bound();
642        let mut iter_last_revealed = self
643            .keychain_to_descriptor_id
644            .range((start, end))
645            .map(|(k, did)| (k, self.last_revealed.get(did).cloned()));
646        let mut iter_spks = self
647            .inner
648            .all_spks()
649            .range(self.map_to_inner_bounds((start, end)));
650        let mut current_keychain = iter_last_revealed.next();
651        // The reason we need a tricky algorithm is because of the "lookahead" feature which means
652        // that some of the spks in the SpkTxoutIndex will not have been revealed yet. So we need to
653        // filter out those spks that are above the last_revealed for that keychain. To do this we
654        // iterate through the last_revealed for each keychain and the spks for each keychain in
655        // tandem. This minimizes BTreeMap queries.
656        core::iter::from_fn(move || loop {
657            let ((keychain, index), spk) = iter_spks.next()?;
658            // We need to find the last revealed that matches the current spk we are considering so
659            // we skip ahead.
660            while current_keychain?.0 < keychain {
661                current_keychain = iter_last_revealed.next();
662            }
663            let (current_keychain, last_revealed) = current_keychain?;
664
665            if current_keychain == keychain && Some(*index) <= last_revealed {
666                break Some(((keychain.clone(), *index), spk.clone()));
667            }
668        })
669    }
670
671    /// Iterate over revealed spks of the given `keychain` with ascending indices.
672    ///
673    /// This is a double ended iterator so you can easily reverse it to get an iterator where
674    /// the script pubkeys that were most recently revealed are first.
675    pub fn revealed_keychain_spks(
676        &self,
677        keychain: K,
678    ) -> impl DoubleEndedIterator<Item = Indexed<ScriptBuf>> + '_ {
679        let end = self
680            .last_revealed_index(keychain.clone())
681            .map(|v| v + 1)
682            .unwrap_or(0);
683        self.inner
684            .all_spks()
685            .range((keychain.clone(), 0)..(keychain.clone(), end))
686            .map(|((_, index), spk)| (*index, spk.clone()))
687    }
688
689    /// Iterate over revealed, but unused, spks of all keychains.
690    pub fn unused_spks(
691        &self,
692    ) -> impl DoubleEndedIterator<Item = KeychainIndexed<K, ScriptBuf>> + Clone + '_ {
693        self.keychain_to_descriptor_id.keys().flat_map(|keychain| {
694            self.unused_keychain_spks(keychain.clone())
695                .map(|(i, spk)| ((keychain.clone(), i), spk.clone()))
696        })
697    }
698
699    /// Iterate over revealed, but unused, spks of the given `keychain`.
700    /// Returns an empty iterator if the provided keychain doesn't exist.
701    pub fn unused_keychain_spks(
702        &self,
703        keychain: K,
704    ) -> impl DoubleEndedIterator<Item = Indexed<ScriptBuf>> + Clone + '_ {
705        let end = match self.keychain_to_descriptor_id.get(&keychain) {
706            Some(did) => self.last_revealed.get(did).map(|v| *v + 1).unwrap_or(0),
707            None => 0,
708        };
709
710        self.inner
711            .unused_spks((keychain.clone(), 0)..(keychain.clone(), end))
712            .map(|((_, i), spk)| (*i, spk))
713    }
714
715    /// Get the next derivation index for `keychain`. The next index is the index after the last
716    /// revealed derivation index.
717    ///
718    /// The second field in the returned tuple represents whether the next derivation index is new.
719    /// There are two scenarios where the next derivation index is reused (not new):
720    ///
721    /// 1. The keychain's descriptor has no wildcard, and a script has already been revealed.
722    /// 2. The number of revealed scripts has already reached 2^31 (refer to BIP-32).
723    ///
724    /// Not checking the second field of the tuple may result in address reuse.
725    ///
726    /// Returns None if the provided `keychain` doesn't exist.
727    pub fn next_index(&self, keychain: K) -> Option<(u32, bool)> {
728        let did = self.keychain_to_descriptor_id.get(&keychain)?;
729        let last_index = self.last_revealed.get(did).cloned();
730        let descriptor = self.descriptors.get(did).expect("invariant");
731
732        // we can only get the next index if the wildcard exists.
733        let has_wildcard = descriptor.has_wildcard();
734
735        Some(match last_index {
736            // if there is no index, next_index is always 0.
737            None => (0, true),
738            // descriptors without wildcards can only have one index.
739            Some(_) if !has_wildcard => (0, false),
740            // derivation index must be < 2^31 (BIP-32).
741            Some(index) if index > BIP32_MAX_INDEX => {
742                unreachable!("index is out of bounds")
743            }
744            Some(index) if index == BIP32_MAX_INDEX => (index, false),
745            // get the next derivation index.
746            Some(index) => (index + 1, true),
747        })
748    }
749
750    /// Get the last derivation index that is revealed for each keychain.
751    ///
752    /// Keychains with no revealed indices will not be included in the returned [`BTreeMap`].
753    pub fn last_revealed_indices(&self) -> BTreeMap<K, u32> {
754        self.last_revealed
755            .iter()
756            .filter_map(|(desc_id, index)| {
757                let keychain = self.descriptor_id_to_keychain.get(desc_id)?;
758                Some((keychain.clone(), *index))
759            })
760            .collect()
761    }
762
763    /// Get the last derivation index revealed for `keychain`. Returns None if the keychain doesn't
764    /// exist, or if the keychain doesn't have any revealed scripts.
765    pub fn last_revealed_index(&self, keychain: K) -> Option<u32> {
766        let descriptor_id = self.keychain_to_descriptor_id.get(&keychain)?;
767        self.last_revealed.get(descriptor_id).cloned()
768    }
769
770    /// Convenience method to call [`Self::reveal_to_target`] on multiple keychains.
771    pub fn reveal_to_target_multi(&mut self, keychains: &BTreeMap<K, u32>) -> ChangeSet {
772        let mut changeset = ChangeSet::default();
773
774        for (keychain, &index) in keychains {
775            self._reveal_to_target(&mut changeset, keychain.clone(), index);
776        }
777
778        self._empty_stage_into_changeset(&mut changeset);
779        changeset
780    }
781
782    /// Reveals script pubkeys of the `keychain`'s descriptor **up to and including** the
783    /// `target_index`.
784    ///
785    /// If the `target_index` cannot be reached (due to the descriptor having no wildcard and/or
786    /// the `target_index` is in the hardened index range), this method will make a best-effort and
787    /// reveal up to the last possible index.
788    ///
789    /// This returns list of newly revealed indices (alongside their scripts) and a
790    /// [`ChangeSet`], which reports updates to the latest revealed index. If no new script
791    /// pubkeys are revealed, then both of these will be empty.
792    ///
793    /// Returns None if the provided `keychain` doesn't exist.
794    #[must_use]
795    pub fn reveal_to_target(
796        &mut self,
797        keychain: K,
798        target_index: u32,
799    ) -> Option<(Vec<Indexed<ScriptBuf>>, ChangeSet)> {
800        let mut changeset = ChangeSet::default();
801        let revealed_spks = self._reveal_to_target(&mut changeset, keychain, target_index)?;
802        self._empty_stage_into_changeset(&mut changeset);
803        Some((revealed_spks, changeset))
804    }
805    fn _reveal_to_target(
806        &mut self,
807        changeset: &mut ChangeSet,
808        keychain: K,
809        target_index: u32,
810    ) -> Option<Vec<Indexed<ScriptBuf>>> {
811        let mut spks: Vec<Indexed<ScriptBuf>> = vec![];
812        loop {
813            let (i, new) = self.next_index(keychain.clone())?;
814            if !new || i > target_index {
815                break;
816            }
817            match self._reveal_next_spk(changeset, keychain.clone()) {
818                Some(indexed_spk) => spks.push(indexed_spk),
819                None => break,
820            }
821        }
822        Some(spks)
823    }
824
825    /// Attempts to reveal the next script pubkey for `keychain`.
826    ///
827    /// Returns the derivation index of the revealed script pubkey, the revealed script pubkey and a
828    /// [`ChangeSet`] which represents changes in the last revealed index (if any).
829    /// Returns None if the provided keychain doesn't exist.
830    ///
831    /// When a new script cannot be revealed, we return the last revealed script and an empty
832    /// [`ChangeSet`]. There are two scenarios when a new script pubkey cannot be derived:
833    ///
834    ///  1. The descriptor has no wildcard and already has one script revealed.
835    ///  2. The descriptor has already revealed scripts up to the numeric bound.
836    ///  3. There is no descriptor associated with the given keychain.
837    pub fn reveal_next_spk(&mut self, keychain: K) -> Option<(Indexed<ScriptBuf>, ChangeSet)> {
838        let mut changeset = ChangeSet::default();
839        let indexed_spk = self._reveal_next_spk(&mut changeset, keychain)?;
840        self._empty_stage_into_changeset(&mut changeset);
841        Some((indexed_spk, changeset))
842    }
843    fn _reveal_next_spk(
844        &mut self,
845        changeset: &mut ChangeSet,
846        keychain: K,
847    ) -> Option<Indexed<ScriptBuf>> {
848        let (next_index, new) = self.next_index(keychain.clone())?;
849        if new {
850            let did = self.keychain_to_descriptor_id.get(&keychain)?;
851            self.last_revealed.insert(*did, next_index);
852            changeset.last_revealed.insert(*did, next_index);
853            self.replenish_inner_index(*did, &keychain, self.lookahead);
854        }
855        let script = self
856            .inner
857            .spk_at_index(&(keychain.clone(), next_index))
858            .expect("we just inserted it");
859        Some((next_index, script))
860    }
861
862    /// Gets the next unused script pubkey in the keychain. I.e., the script pubkey with the lowest
863    /// index that has not been used yet.
864    ///
865    /// This will derive and reveal a new script pubkey if no more unused script pubkeys exist.
866    ///
867    /// If the descriptor has no wildcard and already has a used script pubkey or if a descriptor
868    /// has used all scripts up to the derivation bounds, then the last derived script pubkey will
869    /// be returned.
870    ///
871    /// Returns `None` if there are no script pubkeys that have been used and no new script pubkey
872    /// could be revealed (see [`reveal_next_spk`] for when this happens).
873    ///
874    /// [`reveal_next_spk`]: Self::reveal_next_spk
875    pub fn next_unused_spk(&mut self, keychain: K) -> Option<(Indexed<ScriptBuf>, ChangeSet)> {
876        let mut changeset = ChangeSet::default();
877        let next_unused = self
878            .unused_keychain_spks(keychain.clone())
879            .next()
880            .map(|(i, spk)| (i, spk.to_owned()));
881        let spk = next_unused.or_else(|| self._reveal_next_spk(&mut changeset, keychain))?;
882        self._empty_stage_into_changeset(&mut changeset);
883        Some((spk, changeset))
884    }
885
886    /// Iterate over all [`OutPoint`]s that have `TxOut`s with script pubkeys derived from
887    /// `keychain`.
888    pub fn keychain_outpoints(
889        &self,
890        keychain: K,
891    ) -> impl DoubleEndedIterator<Item = Indexed<OutPoint>> + '_ {
892        self.keychain_outpoints_in_range(keychain.clone()..=keychain)
893            .map(|((_, i), op)| (i, op))
894    }
895
896    /// Iterate over [`OutPoint`]s that have script pubkeys derived from keychains in `range`.
897    pub fn keychain_outpoints_in_range<'a>(
898        &'a self,
899        range: impl RangeBounds<K> + 'a,
900    ) -> impl DoubleEndedIterator<Item = KeychainIndexed<K, OutPoint>> + 'a {
901        self.inner
902            .outputs_in_range(self.map_to_inner_bounds(range))
903            .map(|((k, i), op)| ((k.clone(), *i), op))
904    }
905
906    fn map_to_inner_bounds(&self, bound: impl RangeBounds<K>) -> impl RangeBounds<(K, u32)> {
907        let start = match bound.start_bound() {
908            Bound::Included(keychain) => Bound::Included((keychain.clone(), u32::MIN)),
909            Bound::Excluded(keychain) => Bound::Excluded((keychain.clone(), u32::MAX)),
910            Bound::Unbounded => Bound::Unbounded,
911        };
912        let end = match bound.end_bound() {
913            Bound::Included(keychain) => Bound::Included((keychain.clone(), u32::MAX)),
914            Bound::Excluded(keychain) => Bound::Excluded((keychain.clone(), u32::MIN)),
915            Bound::Unbounded => Bound::Unbounded,
916        };
917
918        (start, end)
919    }
920
921    /// Returns the highest derivation index of the `keychain` where [`KeychainTxOutIndex`] has
922    /// found a [`TxOut`] with it's script pubkey.
923    pub fn last_used_index(&self, keychain: K) -> Option<u32> {
924        self.keychain_outpoints(keychain).last().map(|(i, _)| i)
925    }
926
927    /// Returns the highest derivation index of each keychain that [`KeychainTxOutIndex`] has found
928    /// a [`TxOut`] with it's script pubkey.
929    pub fn last_used_indices(&self) -> BTreeMap<K, u32> {
930        self.keychain_to_descriptor_id
931            .iter()
932            .filter_map(|(keychain, _)| {
933                self.last_used_index(keychain.clone())
934                    .map(|index| (keychain.clone(), index))
935            })
936            .collect()
937    }
938
939    /// Applies the `ChangeSet<K>` to the [`KeychainTxOutIndex<K>`]
940    pub fn apply_changeset(&mut self, changeset: ChangeSet) {
941        if self.persist_spks {
942            for (did, spks) in changeset.spk_cache {
943                self.spk_cache.entry(did).or_default().extend(spks);
944            }
945        }
946        for (did, index) in changeset.last_revealed {
947            let v = self.last_revealed.entry(did).or_default();
948            *v = index.max(*v);
949            self.replenish_inner_index_did(did, self.lookahead);
950        }
951    }
952}
953
954#[derive(Clone, Debug, PartialEq)]
955/// Error returned from [`KeychainTxOutIndex::insert_descriptor`]
956pub enum InsertDescriptorError<K> {
957    /// The descriptor has already been assigned to a keychain so you can't assign it to another
958    DescriptorAlreadyAssigned {
959        /// The descriptor you have attempted to reassign
960        descriptor: Box<Descriptor<DescriptorPublicKey>>,
961        /// The keychain that the descriptor is already assigned to
962        existing_assignment: K,
963    },
964    /// The keychain is already assigned to a descriptor so you can't reassign it
965    KeychainAlreadyAssigned {
966        /// The keychain that you have attempted to reassign
967        keychain: K,
968        /// The descriptor that the keychain is already assigned to
969        existing_assignment: Box<Descriptor<DescriptorPublicKey>>,
970    },
971}
972
973impl<K: core::fmt::Debug> core::fmt::Display for InsertDescriptorError<K> {
974    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
975        match self {
976            InsertDescriptorError::DescriptorAlreadyAssigned {
977                existing_assignment: existing,
978                descriptor,
979            } => {
980                write!(
981                    f,
982                    "attempt to re-assign descriptor {descriptor:?} already assigned to {existing:?}"
983                )
984            }
985            InsertDescriptorError::KeychainAlreadyAssigned {
986                existing_assignment: existing,
987                keychain,
988            } => {
989                write!(
990                    f,
991                    "attempt to re-assign keychain {keychain:?} already assigned to {existing:?}"
992                )
993            }
994        }
995    }
996}
997
998#[cfg(feature = "std")]
999impl<K: core::fmt::Debug> std::error::Error for InsertDescriptorError<K> {}
1000
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. `spk_cache`: 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`].
1009///
1010/// # Monotonicity
1011///
1012/// - `last_revealed` is monotonic: merging retains the maximum index for each descriptor and never
1013///   decreases.
1014/// - `spk_cache` 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.
1017///
1018/// [`KeychainTxOutIndex`]: crate::keychain_txout::KeychainTxOutIndex
1019/// [`apply_changeset`]: crate::keychain_txout::KeychainTxOutIndex::apply_changeset
1020/// [`merge`]: Self::merge
1021#[derive(Clone, Debug, Default, PartialEq)]
1022#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
1023#[must_use]
1024pub struct ChangeSet {
1025    /// Maps each `DescriptorId` to its last revealed derivation index.
1026    pub last_revealed: BTreeMap<DescriptorId, u32>,
1027
1028    /// Cache of derived script pubkeys to persist, keyed by descriptor ID and derivation index
1029    /// (`u32`).
1030    #[cfg_attr(feature = "serde", serde(default))]
1031    pub spk_cache: BTreeMap<DescriptorId, BTreeMap<u32, ScriptBuf>>,
1032}
1033
1034impl Merge for ChangeSet {
1035    /// Merge another [`ChangeSet`] into self.
1036    fn merge(&mut self, other: Self) {
1037        // for `last_revealed`, entries of `other` will take precedence ONLY if it is greater than
1038        // what was originally in `self`.
1039        for (desc_id, index) in other.last_revealed {
1040            use crate::collections::btree_map::Entry;
1041            match self.last_revealed.entry(desc_id) {
1042                Entry::Vacant(entry) => {
1043                    entry.insert(index);
1044                }
1045                Entry::Occupied(mut entry) => {
1046                    if *entry.get() < index {
1047                        entry.insert(index);
1048                    }
1049                }
1050            }
1051        }
1052
1053        for (did, spks) in other.spk_cache {
1054            let orig_spks = self.spk_cache.entry(did).or_default();
1055            debug_assert!(
1056                orig_spks
1057                    .iter()
1058                    .all(|(i, orig_spk)| spks.get(i).map_or(true, |spk| spk == orig_spk)),
1059                "spk of the same descriptor-id and derivation index must not be different"
1060            );
1061            orig_spks.extend(spks);
1062        }
1063    }
1064
1065    /// Returns whether the changeset are empty.
1066    fn is_empty(&self) -> bool {
1067        self.last_revealed.is_empty() && self.spk_cache.is_empty()
1068    }
1069}
1070
1071/// Trait to extend [`SyncRequestBuilder`].
1072pub trait SyncRequestBuilderExt<K> {
1073    /// Add [`Script`](bitcoin::Script)s that are revealed by the `indexer` of the given `spk_range`
1074    /// that will be synced against.
1075    fn revealed_spks_from_indexer<R>(self, indexer: &KeychainTxOutIndex<K>, spk_range: R) -> Self
1076    where
1077        R: core::ops::RangeBounds<K>;
1078
1079    /// Add [`Script`](bitcoin::Script)s that are revealed by the `indexer` but currently unused.
1080    fn unused_spks_from_indexer(self, indexer: &KeychainTxOutIndex<K>) -> Self;
1081}
1082
1083impl<K: Clone + Ord + core::fmt::Debug> SyncRequestBuilderExt<K> for SyncRequestBuilder<(K, u32)> {
1084    fn revealed_spks_from_indexer<R>(self, indexer: &KeychainTxOutIndex<K>, spk_range: R) -> Self
1085    where
1086        R: core::ops::RangeBounds<K>,
1087    {
1088        self.spks_with_indexes(indexer.revealed_spks(spk_range))
1089    }
1090
1091    fn unused_spks_from_indexer(self, indexer: &KeychainTxOutIndex<K>) -> Self {
1092        self.spks_with_indexes(indexer.unused_spks())
1093    }
1094}
1095
1096/// Trait to extend [`FullScanRequestBuilder`].
1097pub trait FullScanRequestBuilderExt<K> {
1098    /// Add spk iterators for each keychain tracked in `indexer`.
1099    fn spks_from_indexer(self, indexer: &KeychainTxOutIndex<K>) -> Self;
1100}
1101
1102impl<K: Clone + Ord + core::fmt::Debug> FullScanRequestBuilderExt<K> for FullScanRequestBuilder<K> {
1103    fn spks_from_indexer(mut self, indexer: &KeychainTxOutIndex<K>) -> Self {
1104        for (keychain, spks) in indexer.all_unbounded_spk_iters() {
1105            self = self.spks_for_keychain(keychain, spks);
1106        }
1107        self
1108    }
1109}
1110
1111#[cfg(test)]
1112mod test {
1113    use super::*;
1114
1115    use bdk_testenv::utils::DESCRIPTORS;
1116    use bitcoin::secp256k1::Secp256k1;
1117    use miniscript::Descriptor;
1118
1119    // Test that `KeychainTxOutIndex` uses the spk cache.
1120    // And the indexed spks are as expected.
1121    #[test]
1122    fn test_spk_cache() {
1123        let lookahead = 10;
1124        let use_cache = true;
1125        let mut index = KeychainTxOutIndex::new(lookahead, use_cache);
1126        let s = DESCRIPTORS[0];
1127
1128        let desc = Descriptor::parse_descriptor(&Secp256k1::new(), s)
1129            .unwrap()
1130            .0;
1131
1132        let did = desc.descriptor_id();
1133
1134        let reveal_to = 2;
1135        let end_index = reveal_to + lookahead;
1136
1137        let _ = index.insert_descriptor(0i32, desc.clone());
1138        assert_eq!(index.spk_cache.get(&did).unwrap().len() as u32, lookahead);
1139        assert_eq!(index.next_index(0), Some((0, true)));
1140
1141        // Now reveal some scripts
1142        for _ in 0..=reveal_to {
1143            let _ = index.reveal_next_spk(0).unwrap();
1144        }
1145        assert_eq!(index.last_revealed_index(0), Some(reveal_to));
1146
1147        let spk_cache = &index.spk_cache;
1148        assert!(!spk_cache.is_empty());
1149
1150        for (&did, cached_spks) in spk_cache {
1151            assert_eq!(did, desc.descriptor_id());
1152            for (&i, cached_spk) in cached_spks {
1153                // Cached spk matches derived
1154                let exp_spk = desc.at_derivation_index(i).unwrap().script_pubkey();
1155                assert_eq!(&exp_spk, cached_spk);
1156                // Also matches the inner index
1157                assert_eq!(index.spk_at_index(0, i), Some(cached_spk.clone()));
1158            }
1159        }
1160
1161        let init_cs = index.initial_changeset();
1162        assert_eq!(
1163            init_cs.spk_cache.get(&did).unwrap().len() as u32,
1164            end_index + 1
1165        );
1166
1167        // Now test load from changeset
1168        let recovered =
1169            KeychainTxOutIndex::<&str>::from_changeset(lookahead, use_cache, init_cs.clone());
1170        assert_eq!(&recovered.spk_cache, spk_cache);
1171
1172        // The cache is optional at load time
1173        let index = KeychainTxOutIndex::<i32>::from_changeset(lookahead, false, init_cs);
1174        assert!(index.spk_cache.is_empty());
1175    }
1176}