diff --git a/crates/bdk/src/wallet/mod.rs b/crates/bdk/src/wallet/mod.rs index ce152cb5..2177a88c 100644 --- a/crates/bdk/src/wallet/mod.rs +++ b/crates/bdk/src/wallet/mod.rs @@ -262,6 +262,11 @@ where /// Infallibly return a derived address using the external descriptor, see [`AddressIndex`] for /// available address index selection strategies. If none of the keys in the descriptor are derivable /// (i.e. does not end with /*) then the same address will always be returned for any [`AddressIndex`]. + /// + /// # Panics + /// + /// This panics when the caller requests for an address of derivation index greater than the + /// BIP32 max index. pub fn get_address(&mut self, address_index: AddressIndex) -> AddressInfo { self.try_get_address(address_index).unwrap() } @@ -273,6 +278,11 @@ where /// see [`AddressIndex`] for available address index selection strategies. If none of the keys /// in the descriptor are derivable (i.e. does not end with /*) then the same address will always /// be returned for any [`AddressIndex`]. + /// + /// # Panics + /// + /// This panics when the caller requests for an address of derivation index greater than the + /// BIP32 max index. pub fn get_internal_address(&mut self, address_index: AddressIndex) -> AddressInfo { self.try_get_internal_address(address_index).unwrap() } @@ -649,6 +659,11 @@ impl Wallet { /// /// A `PersistBackend::WriteError` will result if unable to persist the new address /// to the `PersistBackend`. + /// + /// # Panics + /// + /// This panics when the caller requests for an address of derivation index greater than the + /// BIP32 max index. pub fn try_get_address( &mut self, address_index: AddressIndex, @@ -669,6 +684,11 @@ impl Wallet { /// see [`AddressIndex`] for available address index selection strategies. If none of the keys /// in the descriptor are derivable (i.e. does not end with /*) then the same address will always /// be returned for any [`AddressIndex`]. + /// + /// # Panics + /// + /// This panics when the caller requests for an address of derivation index greater than the + /// BIP32 max index. pub fn try_get_internal_address( &mut self, address_index: AddressIndex, @@ -691,6 +711,11 @@ impl Wallet { /// See [`AddressIndex`] for available address index selection strategies. If none of the keys /// in the descriptor are derivable (i.e. does not end with /*) then the same address will /// always be returned for any [`AddressIndex`]. + /// + /// # Panics + /// + /// This panics when the caller requests for an address of derivation index greater than the + /// BIP32 max index. fn _get_address( &mut self, keychain: KeychainKind, @@ -710,12 +735,14 @@ impl Wallet { let ((index, spk), index_changeset) = txout_index.next_unused_spk(&keychain); (index, spk.into(), Some(index_changeset)) } - AddressIndex::Peek(index) => { - let (index, spk) = txout_index - .spks_of_keychain(&keychain) - .take(index as usize + 1) - .last() - .unwrap(); + AddressIndex::Peek(mut peek_index) => { + let mut spk_iter = txout_index.unbounded_spk_iter(&keychain); + if !spk_iter.descriptor().has_wildcard() { + peek_index = 0; + } + let (index, spk) = spk_iter + .nth(peek_index as usize) + .expect("derivation index is out of bounds"); (index, spk, None) } }; @@ -745,7 +772,7 @@ impl Wallet { /// /// Will only return `Some(_)` if the wallet has given out the spk. pub fn derivation_of_spk(&self, spk: &Script) -> Option<(KeychainKind, u32)> { - self.indexed_graph.index.index_of_spk(spk).copied() + self.indexed_graph.index.index_of_spk(spk) } /// Return the list of unspent outputs of this wallet @@ -784,7 +811,7 @@ impl Wallet { self.chain.tip() } - /// Returns a iterators of all the script pubkeys for the `Internal` and External` variants in `KeychainKind`. + /// Get unbounded script pubkey iterators for both `Internal` and `External` keychains. /// /// This is intended to be used when doing a full scan of your addresses (e.g. after restoring /// from seed words). You pass the `BTreeMap` of iterators to a blockchain data source (e.g. @@ -792,36 +819,36 @@ impl Wallet { /// /// Note carefully that iterators go over **all** script pubkeys on the keychains (not what /// script pubkeys the wallet is storing internally). - pub fn spks_of_all_keychains( + pub fn all_unbounded_spk_iters( &self, ) -> BTreeMap + Clone> { - self.indexed_graph.index.spks_of_all_keychains() + self.indexed_graph.index.all_unbounded_spk_iters() } - /// Gets an iterator over all the script pubkeys in a single keychain. + /// Get an unbounded script pubkey iterator for the given `keychain`. /// - /// See [`spks_of_all_keychains`] for more documentation + /// See [`all_unbounded_spk_iters`] for more documentation /// - /// [`spks_of_all_keychains`]: Self::spks_of_all_keychains - pub fn spks_of_keychain( + /// [`all_unbounded_spk_iters`]: Self::all_unbounded_spk_iters + pub fn unbounded_spk_iter( &self, keychain: KeychainKind, ) -> impl Iterator + Clone { - self.indexed_graph.index.spks_of_keychain(&keychain) + self.indexed_graph.index.unbounded_spk_iter(&keychain) } /// Returns the utxo owned by this wallet corresponding to `outpoint` if it exists in the /// wallet's database. pub fn get_utxo(&self, op: OutPoint) -> Option { - let (&spk_i, _) = self.indexed_graph.index.txout(op)?; + let (keychain, index, _) = self.indexed_graph.index.txout(op)?; self.indexed_graph .graph() .filter_chain_unspents( &self.chain, self.chain.tip().block_id(), - core::iter::once((spk_i, op)), + core::iter::once(((), op)), ) - .map(|((k, i), full_txo)| new_local_utxo(k, i, full_txo)) + .map(|(_, full_txo)| new_local_utxo(keychain, index, full_txo)) .next() } @@ -1459,7 +1486,7 @@ impl Wallet { let ((index, spk), index_changeset) = self.indexed_graph.index.next_unused_spk(&change_keychain); let spk = spk.into(); - self.indexed_graph.index.mark_used(&change_keychain, index); + self.indexed_graph.index.mark_used(change_keychain, index); self.persist .stage(ChangeSet::from(indexed_tx_graph::ChangeSet::from( index_changeset, @@ -1640,7 +1667,7 @@ impl Wallet { .into(); let weighted_utxo = match txout_index.index_of_spk(&txout.script_pubkey) { - Some(&(keychain, derivation_index)) => { + Some((keychain, derivation_index)) => { #[allow(deprecated)] let satisfaction_weight = self .get_descriptor_for_keychain(keychain) @@ -1684,7 +1711,7 @@ impl Wallet { for (index, txout) in tx.output.iter().enumerate() { let change_type = self.map_keychain(KeychainKind::Internal); match txout_index.index_of_spk(&txout.script_pubkey) { - Some(&(keychain, _)) if keychain == change_type => change_index = Some(index), + Some((keychain, _)) if keychain == change_type => change_index = Some(index), _ => {} } } @@ -1939,10 +1966,10 @@ impl Wallet { pub fn cancel_tx(&mut self, tx: &Transaction) { let txout_index = &mut self.indexed_graph.index; for txout in &tx.output { - if let Some(&(keychain, index)) = txout_index.index_of_spk(&txout.script_pubkey) { + if let Some((keychain, index)) = txout_index.index_of_spk(&txout.script_pubkey) { // NOTE: unmark_used will **not** make something unused if it has actually been used // by a tx in the tracker. It only removes the superficial marking. - txout_index.unmark_used(&keychain, index); + txout_index.unmark_used(keychain, index); } } } @@ -1958,7 +1985,7 @@ impl Wallet { } fn get_descriptor_for_txout(&self, txout: &TxOut) -> Option { - let &(keychain, child) = self + let (keychain, child) = self .indexed_graph .index .index_of_spk(&txout.script_pubkey)?; @@ -2172,7 +2199,7 @@ impl Wallet { { // Try to find the prev_script in our db to figure out if this is internal or external, // and the derivation index - let &(keychain, child) = self + let (keychain, child) = self .indexed_graph .index .index_of_spk(&utxo.txout.script_pubkey) @@ -2226,7 +2253,7 @@ impl Wallet { // Try to figure out the keychain and derivation for every input and output for (is_input, index, out) in utxos.into_iter() { - if let Some(&(keychain, child)) = + if let Some((keychain, child)) = self.indexed_graph.index.index_of_spk(&out.script_pubkey) { let desc = self.get_descriptor_for_keychain(keychain); diff --git a/crates/chain/src/keychain/txout_index.rs b/crates/chain/src/keychain/txout_index.rs index a8955b8a..11ea76bc 100644 --- a/crates/chain/src/keychain/txout_index.rs +++ b/crates/chain/src/keychain/txout_index.rs @@ -5,24 +5,56 @@ use crate::{ spk_iter::BIP32_MAX_INDEX, SpkIterator, SpkTxOutIndex, }; -use bitcoin::{OutPoint, Script, TxOut}; -use core::{fmt::Debug, ops::Deref}; +use bitcoin::{OutPoint, Script, Transaction, TxOut, Txid}; +use core::{ + fmt::Debug, + ops::{Bound, RangeBounds}, +}; use crate::Append; const DEFAULT_LOOKAHEAD: u32 = 1_000; -/// A convenient wrapper around [`SpkTxOutIndex`] that relates script pubkeys to miniscript public -/// [`Descriptor`]s. +/// [`KeychainTxOutIndex`] controls how script pubkeys are revealed for multiple keychains, and +/// indexes [`TxOut`]s with them. /// -/// Descriptors are referenced by the provided keychain generic (`K`). +/// A single keychain is a chain of script pubkeys derived from a single [`Descriptor`]. Keychains +/// are identified using the `K` generic. Script pubkeys are identified by the keychain that they +/// are derived from `K`, as well as the derivation index `u32`. /// -/// Script pubkeys for a descriptor are revealed chronologically from index 0. I.e., If the last -/// revealed index of a descriptor is 5; scripts of indices 0 to 4 are guaranteed to be already -/// revealed. In addition to revealed scripts, we have a `lookahead` parameter for each keychain, -/// which defines the number of script pubkeys to store ahead of the last revealed index. +/// # Revealed script pubkeys /// -/// Methods that could update the last revealed index will return [`super::ChangeSet`] to report +/// Tracking how script pubkeys are revealed is useful for collecting chain data. For example, if +/// the user has requested 5 script pubkeys (to receive money with), we only need to use those +/// script pubkeys to scan for chain data. +/// +/// Call [`reveal_to_target`] or [`reveal_next_spk`] to reveal more script pubkeys. +/// Call [`revealed_keychain_spks`] or [`revealed_spks`] to iterate through revealed script pubkeys. +/// +/// # Lookahead script pubkeys +/// +/// When an user first recovers a wallet (i.e. from a recovery phrase and/or descriptor), we will +/// NOT have knowledge of which script pubkeys are revealed. So when we index a transaction or +/// txout (using [`index_tx`]/[`index_txout`]) we scan the txouts against script pubkeys derived +/// above the last revealed index. These additionally-derived script pubkeys are called the +/// lookahead. +/// +/// The [`KeychainTxOutIndex`] is constructed with the `lookahead` and cannot be altered. The +/// default `lookahead` count is 1000. Use [`new`] to set a custom `lookahead`. +/// +/// # Unbounded script pubkey iterator +/// +/// For script-pubkey-based chain sources (such as Electrum/Esplora), an initial scan is best done +/// by iterating though derived script pubkeys one by one and requesting transaction histories for +/// each script pubkey. We will stop after x-number of script pubkeys have empty histories. An +/// unbounded script pubkey iterator is useful to pass to such a chain source. +/// +/// Call [`unbounded_spk_iter`] to get an unbounded script pubkey iterator for a given keychain. +/// Call [`all_unbounded_spk_iters`] to get unbounded script pubkey iterators for all keychains. +/// +/// # Change sets +/// +/// Methods that can update the last revealed index will return [`super::ChangeSet`] to report /// these changes. This can be persisted for future recovery. /// /// ## Synopsis @@ -58,6 +90,15 @@ const DEFAULT_LOOKAHEAD: u32 = 1_000; /// [`Ord`]: core::cmp::Ord /// [`SpkTxOutIndex`]: crate::spk_txout_index::SpkTxOutIndex /// [`Descriptor`]: crate::miniscript::Descriptor +/// [`reveal_to_target`]: KeychainTxOutIndex::reveal_to_target +/// [`reveal_next_spk`]: KeychainTxOutIndex::reveal_next_spk +/// [`revealed_keychain_spks`]: KeychainTxOutIndex::revealed_keychain_spks +/// [`revealed_spks`]: KeychainTxOutIndex::revealed_spks +/// [`index_tx`]: KeychainTxOutIndex::index_tx +/// [`index_txout`]: KeychainTxOutIndex::index_txout +/// [`new`]: KeychainTxOutIndex::new +/// [`unbounded_spk_iter`]: KeychainTxOutIndex::unbounded_spk_iter +/// [`all_unbounded_spk_iters`]: KeychainTxOutIndex::all_unbounded_spk_iters #[derive(Clone, Debug)] pub struct KeychainTxOutIndex { inner: SpkTxOutIndex<(K, u32)>, @@ -75,14 +116,6 @@ impl Default for KeychainTxOutIndex { } } -impl Deref for KeychainTxOutIndex { - type Target = SpkTxOutIndex<(K, u32)>; - - fn deref(&self) -> &Self::Target { - &self.inner - } -} - impl Indexer for KeychainTxOutIndex { type ChangeSet = super::ChangeSet; @@ -110,7 +143,7 @@ impl Indexer for KeychainTxOutIndex { } fn is_tx_relevant(&self, tx: &bitcoin::Transaction) -> bool { - self.is_relevant(tx) + self.inner.is_relevant(tx) } } @@ -123,6 +156,8 @@ impl KeychainTxOutIndex { /// beyond the last revealed index. In certain situations, such as when performing an initial /// scan of the blockchain during wallet import, it may be uncertain or unknown what the index /// of the last revealed script pubkey actually is. + /// + /// Refer to [struct-level docs](KeychainTxOutIndex) for more about `lookahead`. pub fn new(lookahead: u32) -> Self { Self { inner: SpkTxOutIndex::default(), @@ -133,8 +168,12 @@ impl KeychainTxOutIndex { } } +/// Methods that are *re-exposed* from the internal [`SpkTxOutIndex`]. impl KeychainTxOutIndex { /// Return a reference to the internal [`SpkTxOutIndex`]. + /// + /// **WARNING:** The internal index will contain lookahead spks. Refer to + /// [struct-level docs](KeychainTxOutIndex) for more about `lookahead`. pub fn inner(&self) -> &SpkTxOutIndex<(K, u32)> { &self.inner } @@ -144,7 +183,116 @@ impl KeychainTxOutIndex { self.inner.outpoints() } - /// Return a reference to the internal map of the keychain to descriptors. + /// Iterate over known txouts that spend to tracked script pubkeys. + pub fn txouts( + &self, + ) -> impl DoubleEndedIterator + ExactSizeIterator { + self.inner + .txouts() + .map(|((k, i), op, txo)| (k.clone(), *i, op, txo)) + } + + /// Finds all txouts on a transaction that has previously been scanned and indexed. + pub fn txouts_in_tx( + &self, + txid: Txid, + ) -> impl DoubleEndedIterator { + self.inner + .txouts_in_tx(txid) + .map(|((k, i), op, txo)| (k.clone(), *i, op, txo)) + } + + /// Return the [`TxOut`] of `outpoint` if it has been indexed. + /// + /// The associated keychain and keychain index of the txout's spk is also returned. + /// + /// This calls [`SpkTxOutIndex::txout`] internally. + pub fn txout(&self, outpoint: OutPoint) -> Option<(K, u32, &TxOut)> { + self.inner + .txout(outpoint) + .map(|((k, i), txo)| (k.clone(), *i, txo)) + } + + /// Return the script that exists under the given `keychain`'s `index`. + /// + /// This calls [`SpkTxOutIndex::spk_at_index`] internally. + pub fn spk_at_index(&self, keychain: K, index: u32) -> Option<&Script> { + self.inner.spk_at_index(&(keychain, index)) + } + + /// Returns the keychain and keychain index associated with the spk. + /// + /// This calls [`SpkTxOutIndex::index_of_spk`] internally. + pub fn index_of_spk(&self, script: &Script) -> Option<(K, u32)> { + self.inner.index_of_spk(script).cloned() + } + + /// Returns whether the spk under the `keychain`'s `index` has been used. + /// + /// Here, "unused" means that after the script pubkey was stored in the index, the index has + /// never scanned a transaction output with it. + /// + /// This calls [`SpkTxOutIndex::is_used`] internally. + pub fn is_used(&self, keychain: K, index: u32) -> bool { + self.inner.is_used(&(keychain, index)) + } + + /// Marks the script pubkey at `index` as used even though the tracker hasn't seen an output + /// with it. + /// + /// This only has an effect when the `index` had been added to `self` already and was unused. + /// + /// Returns whether the `index` was initially present as `unused`. + /// + /// This is useful when you want to reserve a script pubkey for something but don't want to add + /// the transaction output using it to the index yet. Other callers will consider `index` on + /// `keychain` used until you call [`unmark_used`]. + /// + /// This calls [`SpkTxOutIndex::mark_used`] internally. + /// + /// [`unmark_used`]: Self::unmark_used + pub fn mark_used(&mut self, keychain: K, index: u32) -> bool { + self.inner.mark_used(&(keychain, index)) + } + + /// Undoes the effect of [`mark_used`]. Returns whether the `index` is inserted back into + /// `unused`. + /// + /// Note that if `self` has scanned an output with this script pubkey, then this will have no + /// effect. + /// + /// This calls [`SpkTxOutIndex::unmark_used`] internally. + /// + /// [`mark_used`]: Self::mark_used + pub fn unmark_used(&mut self, keychain: K, index: u32) -> bool { + self.inner.unmark_used(&(keychain, index)) + } + + /// Computes total input value going from script pubkeys in the index (sent) and the total output + /// value going to script pubkeys in the index (received) in `tx`. For the `sent` to be computed + /// correctly, the output being spent must have already been scanned by the index. Calculating + /// received just uses the [`Transaction`] outputs directly, so it will be correct even if it has + /// not been scanned. + /// + /// This calls [`SpkTxOutIndex::sent_and_received`] internally. + pub fn sent_and_received(&self, tx: &Transaction) -> (u64, u64) { + self.inner.sent_and_received(tx) + } + + /// Computes the net value that this transaction gives to the script pubkeys in the index and + /// *takes* from the transaction outputs in the index. Shorthand for calling + /// [`sent_and_received`] and subtracting sent from received. + /// + /// This calls [`SpkTxOutIndex::net_value`] internally. + /// + /// [`sent_and_received`]: Self::sent_and_received + pub fn net_value(&self, tx: &Transaction) -> i64 { + self.inner.net_value(tx) + } +} + +impl KeychainTxOutIndex { + /// Return a reference to the internal map of keychain to descriptors. pub fn keychains(&self) -> &BTreeMap> { &self.keychains } @@ -213,59 +361,67 @@ impl KeychainTxOutIndex { .map_or(0, |((_, index), _)| *index + 1) } - /// Generates script pubkey iterators for every `keychain`. The iterators iterate over all - /// derivable script pubkeys. - pub fn spks_of_all_keychains( + /// Get an unbounded spk iterator over a given `keychain`. + /// + /// # Panics + /// + /// This will panic if the given `keychain`'s descriptor does not exist. + pub fn unbounded_spk_iter(&self, keychain: &K) -> SpkIterator> { + SpkIterator::new( + self.keychains + .get(keychain) + .expect("keychain does not exist") + .clone(), + ) + } + + /// Get unbounded spk iterators for all keychains. + pub fn all_unbounded_spk_iters( &self, ) -> BTreeMap>> { self.keychains .iter() - .map(|(keychain, descriptor)| { - ( - keychain.clone(), - SpkIterator::new_with_range(descriptor.clone(), 0..), - ) - }) + .map(|(k, descriptor)| (k.clone(), SpkIterator::new(descriptor.clone()))) .collect() } - /// Generates a script pubkey iterator for the given `keychain`'s descriptor (if it exists). The - /// iterator iterates over all derivable scripts of the keychain's descriptor. - /// - /// # Panics - /// - /// This will panic if the `keychain` does not exist. - pub fn spks_of_keychain(&self, keychain: &K) -> SpkIterator> { - let descriptor = self - .keychains - .get(keychain) - .expect("keychain must exist") - .clone(); - SpkIterator::new_with_range(descriptor, 0..) + /// Iterate over revealed spks of all keychains. + pub fn revealed_spks(&self) -> impl DoubleEndedIterator + Clone { + self.keychains.keys().flat_map(|keychain| { + self.revealed_keychain_spks(keychain) + .map(|(i, spk)| (keychain.clone(), i, spk)) + }) } - /// Convenience method to get [`revealed_spks_of_keychain`] of all keychains. - /// - /// [`revealed_spks_of_keychain`]: Self::revealed_spks_of_keychain - pub fn revealed_spks_of_all_keychains( - &self, - ) -> BTreeMap + Clone> { - self.keychains - .keys() - .map(|keychain| (keychain.clone(), self.revealed_spks_of_keychain(keychain))) - .collect() - } - - /// Iterates over the script pubkeys revealed by this index under `keychain`. - pub fn revealed_spks_of_keychain( + /// Iterate over revealed spks of the given `keychain`. + pub fn revealed_keychain_spks( &self, keychain: &K, ) -> impl DoubleEndedIterator + Clone { - let next_index = self.last_revealed.get(keychain).map_or(0, |v| *v + 1); + let next_i = self.last_revealed.get(keychain).map_or(0, |&i| i + 1); self.inner .all_spks() - .range((keychain.clone(), u32::MIN)..(keychain.clone(), next_index)) - .map(|((_, derivation_index), spk)| (*derivation_index, spk.as_script())) + .range((keychain.clone(), u32::MIN)..(keychain.clone(), next_i)) + .map(|((_, i), spk)| (*i, spk.as_script())) + } + + /// Iterate over revealed, but unused, spks of all keychains. + pub fn unused_spks(&self) -> impl DoubleEndedIterator + Clone { + self.keychains.keys().flat_map(|keychain| { + self.unused_keychain_spks(keychain) + .map(|(i, spk)| (keychain.clone(), i, spk)) + }) + } + + /// Iterate over revealed, but unused, spks of the given `keychain`. + pub fn unused_keychain_spks( + &self, + keychain: &K, + ) -> impl DoubleEndedIterator + Clone { + let next_i = self.last_revealed.get(keychain).map_or(0, |&i| i + 1); + self.inner + .unused_spks((keychain.clone(), u32::MIN)..(keychain.clone(), next_i)) + .map(|((_, i), spk)| (*i, spk)) } /// Get the next derivation index for `keychain`. The next index is the index after the last revealed @@ -442,13 +598,13 @@ impl KeychainTxOutIndex { /// /// Panics if `keychain` has never been added to the index pub fn next_unused_spk(&mut self, keychain: &K) -> ((u32, &Script), super::ChangeSet) { - let need_new = self.unused_spks_of_keychain(keychain).next().is_none(); + let need_new = self.unused_keychain_spks(keychain).next().is_none(); // this rather strange branch is needed because of some lifetime issues if need_new { self.reveal_next_spk(keychain) } else { ( - self.unused_spks_of_keychain(keychain) + self.unused_keychain_spks(keychain) .next() .expect("we already know next exists"), super::ChangeSet::default(), @@ -456,58 +612,44 @@ impl KeychainTxOutIndex { } } - /// Marks the script pubkey at `index` as used even though the tracker hasn't seen an output with it. - /// This only has an effect when the `index` had been added to `self` already and was unused. - /// - /// Returns whether the `index` was initially present as `unused`. - /// - /// This is useful when you want to reserve a script pubkey for something but don't want to add - /// the transaction output using it to the index yet. Other callers will consider `index` on - /// `keychain` used until you call [`unmark_used`]. - /// - /// [`unmark_used`]: Self::unmark_used - pub fn mark_used(&mut self, keychain: &K, index: u32) -> bool { - self.inner.mark_used(&(keychain.clone(), index)) - } - - /// Undoes the effect of [`mark_used`]. Returns whether the `index` is inserted back into - /// `unused`. - /// - /// Note that if `self` has scanned an output with this script pubkey, then this will have no - /// effect. - /// - /// [`mark_used`]: Self::mark_used - pub fn unmark_used(&mut self, keychain: &K, index: u32) -> bool { - self.inner.unmark_used(&(keychain.clone(), index)) - } - - /// Iterates over all unused script pubkeys for a `keychain` stored in the index. - pub fn unused_spks_of_keychain( - &self, - keychain: &K, - ) -> impl DoubleEndedIterator { - let next_index = self.last_revealed.get(keychain).map_or(0, |&v| v + 1); - let range = (keychain.clone(), u32::MIN)..(keychain.clone(), next_index); - self.inner - .unused_spks(range) - .map(|((_, i), script)| (*i, script)) - } - - /// Iterates over all the [`OutPoint`] that have a `TxOut` with a script pubkey derived from + /// Iterate over all [`OutPoint`]s that point to `TxOut`s with script pubkeys derived from /// `keychain`. - pub fn txouts_of_keychain( + /// + /// Use [`keychain_outpoints_in_range`](KeychainTxOutIndex::keychain_outpoints_in_range) to + /// iterate over a specific derivation range. + pub fn keychain_outpoints( &self, keychain: &K, ) -> impl DoubleEndedIterator + '_ { + self.keychain_outpoints_in_range(keychain, ..) + } + + /// Iterate over [`OutPoint`]s that point to `TxOut`s with script pubkeys derived from + /// `keychain` in a given derivation `range`. + pub fn keychain_outpoints_in_range( + &self, + keychain: &K, + range: impl RangeBounds, + ) -> impl DoubleEndedIterator + '_ { + let start = match range.start_bound() { + Bound::Included(i) => Bound::Included((keychain.clone(), *i)), + Bound::Excluded(i) => Bound::Excluded((keychain.clone(), *i)), + Bound::Unbounded => Bound::Unbounded, + }; + let end = match range.end_bound() { + Bound::Included(i) => Bound::Included((keychain.clone(), *i)), + Bound::Excluded(i) => Bound::Excluded((keychain.clone(), *i)), + Bound::Unbounded => Bound::Unbounded, + }; self.inner - .outputs_in_range((keychain.clone(), u32::MIN)..(keychain.clone(), u32::MAX)) + .outputs_in_range((start, end)) .map(|((_, i), op)| (*i, op)) } /// Returns the highest derivation index of the `keychain` where [`KeychainTxOutIndex`] has /// found a [`TxOut`] with it's script pubkey. pub fn last_used_index(&self, keychain: &K) -> Option { - self.txouts_of_keychain(keychain).last().map(|(i, _)| i) + self.keychain_outpoints(keychain).last().map(|(i, _)| i) } /// Returns the highest derivation index of each keychain that [`KeychainTxOutIndex`] has found diff --git a/crates/chain/src/spk_iter.rs b/crates/chain/src/spk_iter.rs index a28944f1..2772dcf7 100644 --- a/crates/chain/src/spk_iter.rs +++ b/crates/chain/src/spk_iter.rs @@ -87,6 +87,11 @@ where secp: Secp256k1::verification_only(), } } + + /// Get a reference to the internal descriptor. + pub fn descriptor(&self) -> &D { + &self.descriptor + } } impl Iterator for SpkIterator diff --git a/crates/chain/src/spk_txout_index.rs b/crates/chain/src/spk_txout_index.rs index 8df1b119..90ee7dca 100644 --- a/crates/chain/src/spk_txout_index.rs +++ b/crates/chain/src/spk_txout_index.rs @@ -215,7 +215,7 @@ impl SpkTxOutIndex { /// let unused_change_spks = /// txout_index.unused_spks((change_index, u32::MIN)..(change_index, u32::MAX)); /// ``` - pub fn unused_spks(&self, range: R) -> impl DoubleEndedIterator + pub fn unused_spks(&self, range: R) -> impl DoubleEndedIterator + Clone where R: RangeBounds, { diff --git a/crates/chain/tests/test_keychain_txout_index.rs b/crates/chain/tests/test_keychain_txout_index.rs index e1f55dc3..0bbc3739 100644 --- a/crates/chain/tests/test_keychain_txout_index.rs +++ b/crates/chain/tests/test_keychain_txout_index.rs @@ -95,25 +95,25 @@ fn test_lookahead() { ); assert_eq!( txout_index - .revealed_spks_of_keychain(&TestKeychain::External) + .revealed_keychain_spks(&TestKeychain::External) .count(), index as usize + 1, ); assert_eq!( txout_index - .revealed_spks_of_keychain(&TestKeychain::Internal) + .revealed_keychain_spks(&TestKeychain::Internal) .count(), 0, ); assert_eq!( txout_index - .unused_spks_of_keychain(&TestKeychain::External) + .unused_keychain_spks(&TestKeychain::External) .count(), index as usize + 1, ); assert_eq!( txout_index - .unused_spks_of_keychain(&TestKeychain::Internal) + .unused_keychain_spks(&TestKeychain::Internal) .count(), 0, ); @@ -147,7 +147,7 @@ fn test_lookahead() { ); assert_eq!( txout_index - .revealed_spks_of_keychain(&TestKeychain::Internal) + .revealed_keychain_spks(&TestKeychain::Internal) .count(), 25, ); @@ -199,13 +199,13 @@ fn test_lookahead() { ); assert_eq!( txout_index - .revealed_spks_of_keychain(&TestKeychain::External) + .revealed_keychain_spks(&TestKeychain::External) .count(), last_external_index as usize + 1, ); assert_eq!( txout_index - .revealed_spks_of_keychain(&TestKeychain::Internal) + .revealed_keychain_spks(&TestKeychain::Internal) .count(), last_internal_index as usize + 1, ); @@ -305,7 +305,7 @@ fn test_wildcard_derivations() { (0..=15) .chain([17, 20, 23]) - .for_each(|index| assert!(txout_index.mark_used(&TestKeychain::External, index))); + .for_each(|index| assert!(txout_index.mark_used(TestKeychain::External, index))); assert_eq!(txout_index.next_index(&TestKeychain::External), (26, true)); @@ -321,7 +321,7 @@ fn test_wildcard_derivations() { // - Use all the derived till 26. // - next_unused() = ((27, ), keychain::ChangeSet) (0..=26).for_each(|index| { - txout_index.mark_used(&TestKeychain::External, index); + txout_index.mark_used(TestKeychain::External, index); }); let (spk, changeset) = txout_index.next_unused_spk(&TestKeychain::External); @@ -364,7 +364,7 @@ fn test_non_wildcard_derivations() { // - derive new and next unused should return the old script // - store_up_to should not panic and return empty changeset assert_eq!(txout_index.next_index(&TestKeychain::External), (0, false)); - txout_index.mark_used(&TestKeychain::External, 0); + txout_index.mark_used(TestKeychain::External, 0); let (spk, changeset) = txout_index.reveal_next_spk(&TestKeychain::External); assert_eq!(spk, (0, external_spk.as_script())); @@ -381,7 +381,7 @@ fn test_non_wildcard_derivations() { // we check that spks_of_keychain returns a SpkIterator with just one element assert_eq!( txout_index - .spks_of_keychain(&TestKeychain::External) + .revealed_keychain_spks(&TestKeychain::External) .count(), 1, ); diff --git a/crates/electrum/src/electrum_ext.rs b/crates/electrum/src/electrum_ext.rs index 1c9ce989..1f41aaf9 100644 --- a/crates/electrum/src/electrum_ext.rs +++ b/crates/electrum/src/electrum_ext.rs @@ -187,7 +187,7 @@ impl ElectrumExt for Client { ) -> Result<(ElectrumUpdate, BTreeMap), Error> { let mut request_spks = keychain_spks .into_iter() - .map(|(k, s)| (k, s.into_iter())) + .map(|(k, s)| (k.clone(), s.into_iter())) .collect::>(); let mut scanned_spks = BTreeMap::<(K, u32), (ScriptBuf, bool)>::new(); diff --git a/example-crates/example_cli/src/lib.rs b/example-crates/example_cli/src/lib.rs index 39b4f312..46231946 100644 --- a/example-crates/example_cli/src/lib.rs +++ b/example-crates/example_cli/src/lib.rs @@ -478,14 +478,14 @@ where true => Keychain::Internal, false => Keychain::External, }; - for (spk_i, spk) in index.revealed_spks_of_keychain(&target_keychain) { + for (spk_i, spk) in index.revealed_keychain_spks(&target_keychain) { let address = Address::from_script(spk, network) .expect("should always be able to derive address"); println!( "{:?} {} used:{}", spk_i, address, - index.is_used(&(target_keychain, spk_i)) + index.is_used(target_keychain, spk_i) ); } Ok(()) @@ -611,7 +611,7 @@ where // We don't want other callers/threads to use this address while we're using it // but we also don't want to scan the tx we just created because it's not // technically in the blockchain yet. - graph.index.mark_used(&change_keychain, index); + graph.index.mark_used(change_keychain, index); (tx, Some((change_keychain, index))) } else { (tx, None) @@ -636,7 +636,7 @@ where Err(e) => { if let Some((keychain, index)) = change_index { // We failed to broadcast, so allow our change address to be used in the future - graph.lock().unwrap().index.unmark_used(&keychain, index); + graph.lock().unwrap().index.unmark_used(keychain, index); } Err(e) } diff --git a/example-crates/example_electrum/src/main.rs b/example-crates/example_electrum/src/main.rs index 8d82fded..e7545f05 100644 --- a/example-crates/example_electrum/src/main.rs +++ b/example-crates/example_electrum/src/main.rs @@ -5,7 +5,7 @@ use std::{ }; use bdk_chain::{ - bitcoin::{constants::genesis_block, Address, Network, OutPoint, ScriptBuf, Txid}, + bitcoin::{constants::genesis_block, Address, Network, OutPoint, Txid}, indexed_tx_graph::{self, IndexedTxGraph}, keychain, local_chain::{self, LocalChain}, @@ -155,7 +155,7 @@ fn main() -> anyhow::Result<()> { let keychain_spks = graph .index - .spks_of_all_keychains() + .all_unbounded_spk_iters() .into_iter() .map(|(keychain, iter)| { let mut first = true; @@ -206,29 +206,28 @@ fn main() -> anyhow::Result<()> { if all_spks { let all_spks = graph .index - .all_spks() - .iter() - .map(|(k, v)| (*k, v.clone())) + .revealed_spks() + .map(|(k, i, spk)| (k, i, spk.to_owned())) .collect::>(); - spks = Box::new(spks.chain(all_spks.into_iter().map(|(index, script)| { - eprintln!("scanning {:?}", index); - script + spks = Box::new(spks.chain(all_spks.into_iter().map(|(k, i, spk)| { + eprintln!("scanning {}:{}", k, i); + spk }))); } if unused_spks { let unused_spks = graph .index - .unused_spks(..) - .map(|(k, v)| (*k, ScriptBuf::from(v))) + .unused_spks() + .map(|(k, i, spk)| (k, i, spk.to_owned())) .collect::>(); - spks = Box::new(spks.chain(unused_spks.into_iter().map(|(index, script)| { + spks = Box::new(spks.chain(unused_spks.into_iter().map(|(k, i, spk)| { eprintln!( - "Checking if address {} {:?} has been used", - Address::from_script(&script, args.network).unwrap(), - index + "Checking if address {} {}:{} has been used", + Address::from_script(&spk, args.network).unwrap(), + k, + i, ); - - script + spk }))); } diff --git a/example-crates/example_esplora/src/main.rs b/example-crates/example_esplora/src/main.rs index 101fd58b..298532cb 100644 --- a/example-crates/example_esplora/src/main.rs +++ b/example-crates/example_esplora/src/main.rs @@ -165,7 +165,7 @@ fn main() -> anyhow::Result<()> { .lock() .expect("mutex must not be poisoned") .index - .spks_of_all_keychains() + .all_unbounded_spk_iters() .into_iter() // This `map` is purely for logging. .map(|(keychain, iter)| { @@ -235,32 +235,32 @@ fn main() -> anyhow::Result<()> { if *all_spks { let all_spks = graph .index - .all_spks() - .iter() - .map(|(k, v)| (*k, v.clone())) + .revealed_spks() + .map(|(k, i, spk)| (k, i, spk.to_owned())) .collect::>(); - spks = Box::new(spks.chain(all_spks.into_iter().map(|(index, script)| { - eprintln!("scanning {:?}", index); + spks = Box::new(spks.chain(all_spks.into_iter().map(|(k, i, spk)| { + eprintln!("scanning {}:{}", k, i); // Flush early to ensure we print at every iteration. let _ = io::stderr().flush(); - script + spk }))); } if unused_spks { let unused_spks = graph .index - .unused_spks(..) - .map(|(k, v)| (*k, v.to_owned())) + .unused_spks() + .map(|(k, i, spk)| (k, i, spk.to_owned())) .collect::>(); - spks = Box::new(spks.chain(unused_spks.into_iter().map(|(index, script)| { + spks = Box::new(spks.chain(unused_spks.into_iter().map(|(k, i, spk)| { eprintln!( - "Checking if address {} {:?} has been used", - Address::from_script(&script, args.network).unwrap(), - index + "Checking if address {} {}:{} has been used", + Address::from_script(&spk, args.network).unwrap(), + k, + i, ); // Flush early to ensure we print at every iteration. let _ = io::stderr().flush(); - script + spk }))); } if utxos { diff --git a/example-crates/wallet_electrum/src/main.rs b/example-crates/wallet_electrum/src/main.rs index e5c6b146..4f8aba9f 100644 --- a/example-crates/wallet_electrum/src/main.rs +++ b/example-crates/wallet_electrum/src/main.rs @@ -40,7 +40,7 @@ fn main() -> Result<(), anyhow::Error> { let prev_tip = wallet.latest_checkpoint(); let keychain_spks = wallet - .spks_of_all_keychains() + .all_unbounded_spk_iters() .into_iter() .map(|(k, k_spks)| { let mut once = Some(()); diff --git a/example-crates/wallet_esplora_async/src/main.rs b/example-crates/wallet_esplora_async/src/main.rs index 755b3900..690cd87e 100644 --- a/example-crates/wallet_esplora_async/src/main.rs +++ b/example-crates/wallet_esplora_async/src/main.rs @@ -39,7 +39,7 @@ async fn main() -> Result<(), anyhow::Error> { let prev_tip = wallet.latest_checkpoint(); let keychain_spks = wallet - .spks_of_all_keychains() + .all_unbounded_spk_iters() .into_iter() .map(|(k, k_spks)| { let mut once = Some(()); diff --git a/example-crates/wallet_esplora_blocking/src/main.rs b/example-crates/wallet_esplora_blocking/src/main.rs index d0f35bea..73bfdd55 100644 --- a/example-crates/wallet_esplora_blocking/src/main.rs +++ b/example-crates/wallet_esplora_blocking/src/main.rs @@ -38,7 +38,7 @@ fn main() -> Result<(), anyhow::Error> { let prev_tip = wallet.latest_checkpoint(); let keychain_spks = wallet - .spks_of_all_keychains() + .all_unbounded_spk_iters() .into_iter() .map(|(k, k_spks)| { let mut once = Some(());