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}