Merge bitcoindevkit/bdk#1269: Revamp KeychainTxOutIndex
API to be safer
71fff1613d1c167d180636857063e181f6acedf1 feat(chain): add txout methods to `KeychainTxOutIndex` (志宇) 83e7b7ec402bef27c3f83876f346ec2abd23eff1 docs(chain): improve `KeychainTxOutIndex` docs (志宇) 9294e30943f3f79bb36915d69652d9086323131f docs(wallet): improve docs for unbounded spk iterator methods (志宇) b74c2e262255a39e9dc904aad469e307bf7d0151 fix(wallet): use efficient peek address logic (志宇) 81aeaba48a980a7e7add76ac2d24e896bedaa1d2 feat(chain): add `SpkIterator::descriptor` method (志宇) c7b47af72f278ba73abea58db49cff1245428f97 refactor(chain)!: revamp `KeychainTxOutIndex` API (志宇) 705690ee8fddba8517d907183b7ddfeafb633609 feat(chain): make output of `SpkTxOutIndex::unused_spks` cloneable (志宇) Pull request description: Closes #1268 ### Description Previously `SpkTxOutIndex` methods can be called from `KeychainTxOutIndex` due to the `DeRef` implementation. However, the internal `SpkTxOut` will also contain lookahead spks resulting in an error-prone API. `SpkTxOutIndex` methods are now not directly callable from `KeychainTxOutIndex`. Methods of `KeychainTxOutIndex` are renamed for clarity. I.e. methods that return an unbounded spk iter are prefixed with `unbounded`. In addition to this, I also optimized the peek-address logic of `bdk::Wallet` using the optimized `<SpkIterator as Iterator>::nth` implementation. ### Notes to the reviewers This is mostly refactoring, but can also be considered a bug-fix (as the API before was very problematic). ### Changelog notice Changed * Wallet's peek-address logic is optimized by making use of `<SpkIterator as Iterator>::nth`. * `KeychainTxOutIndex` API is refactored to better differentiate between methods that return unbounded vs stored spks. * `KeychainTxOutIndex` no longer directly exposes `SpkTxOutIndex` methods via `DeRef`. This was problematic because `SpkTxOutIndex` also contains lookahead spks which we want to hide. Added * `SpkIterator::descriptor` method which gets a reference to the internal descriptor. ### Checklists #### All Submissions: * [x] I've signed all my commits * [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk/blob/master/CONTRIBUTING.md) * [x] I ran `cargo fmt` and `cargo clippy` before committing #### New Features: ~* [ ] I've added tests for the new feature~ * [x] I've added docs for the new feature #### Bugfixes: * [x] This pull request breaks the existing API * [ ] I've added tests to reproduce the issue which are now passing * [x] I'm linking the issue being fixed by this PR ACKs for top commit: danielabrozzoni: ACK 71fff1613d1c167d180636857063e181f6acedf1 Tree-SHA512: f29c7d2311d0e81c4fe29b8f57c219c24db958194fad5de82bb6d42d562d37fd5d152be7ee03a3f00843be5760569ad29b848250267a548d7d15320fd5292a8f
This commit is contained in:
commit
60abd87a32
@ -262,6 +262,11 @@ where
|
|||||||
/// Infallibly return a derived address using the external descriptor, see [`AddressIndex`] for
|
/// 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
|
/// 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`].
|
/// (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 {
|
pub fn get_address(&mut self, address_index: AddressIndex) -> AddressInfo {
|
||||||
self.try_get_address(address_index).unwrap()
|
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
|
/// 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
|
/// in the descriptor are derivable (i.e. does not end with /*) then the same address will always
|
||||||
/// be returned for any [`AddressIndex`].
|
/// 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 {
|
pub fn get_internal_address(&mut self, address_index: AddressIndex) -> AddressInfo {
|
||||||
self.try_get_internal_address(address_index).unwrap()
|
self.try_get_internal_address(address_index).unwrap()
|
||||||
}
|
}
|
||||||
@ -649,6 +659,11 @@ impl<D> Wallet<D> {
|
|||||||
///
|
///
|
||||||
/// A `PersistBackend<ChangeSet>::WriteError` will result if unable to persist the new address
|
/// A `PersistBackend<ChangeSet>::WriteError` will result if unable to persist the new address
|
||||||
/// to the `PersistBackend`.
|
/// 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(
|
pub fn try_get_address(
|
||||||
&mut self,
|
&mut self,
|
||||||
address_index: AddressIndex,
|
address_index: AddressIndex,
|
||||||
@ -669,6 +684,11 @@ impl<D> Wallet<D> {
|
|||||||
/// see [`AddressIndex`] for available address index selection strategies. If none of the keys
|
/// 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
|
/// in the descriptor are derivable (i.e. does not end with /*) then the same address will always
|
||||||
/// be returned for any [`AddressIndex`].
|
/// 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(
|
pub fn try_get_internal_address(
|
||||||
&mut self,
|
&mut self,
|
||||||
address_index: AddressIndex,
|
address_index: AddressIndex,
|
||||||
@ -691,6 +711,11 @@ impl<D> Wallet<D> {
|
|||||||
/// See [`AddressIndex`] for available address index selection strategies. If none of the keys
|
/// 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
|
/// in the descriptor are derivable (i.e. does not end with /*) then the same address will
|
||||||
/// always be returned for any [`AddressIndex`].
|
/// 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(
|
fn _get_address(
|
||||||
&mut self,
|
&mut self,
|
||||||
keychain: KeychainKind,
|
keychain: KeychainKind,
|
||||||
@ -710,12 +735,14 @@ impl<D> Wallet<D> {
|
|||||||
let ((index, spk), index_changeset) = txout_index.next_unused_spk(&keychain);
|
let ((index, spk), index_changeset) = txout_index.next_unused_spk(&keychain);
|
||||||
(index, spk.into(), Some(index_changeset))
|
(index, spk.into(), Some(index_changeset))
|
||||||
}
|
}
|
||||||
AddressIndex::Peek(index) => {
|
AddressIndex::Peek(mut peek_index) => {
|
||||||
let (index, spk) = txout_index
|
let mut spk_iter = txout_index.unbounded_spk_iter(&keychain);
|
||||||
.spks_of_keychain(&keychain)
|
if !spk_iter.descriptor().has_wildcard() {
|
||||||
.take(index as usize + 1)
|
peek_index = 0;
|
||||||
.last()
|
}
|
||||||
.unwrap();
|
let (index, spk) = spk_iter
|
||||||
|
.nth(peek_index as usize)
|
||||||
|
.expect("derivation index is out of bounds");
|
||||||
(index, spk, None)
|
(index, spk, None)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -745,7 +772,7 @@ impl<D> Wallet<D> {
|
|||||||
///
|
///
|
||||||
/// Will only return `Some(_)` if the wallet has given out the spk.
|
/// Will only return `Some(_)` if the wallet has given out the spk.
|
||||||
pub fn derivation_of_spk(&self, spk: &Script) -> Option<(KeychainKind, u32)> {
|
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
|
/// Return the list of unspent outputs of this wallet
|
||||||
@ -784,7 +811,7 @@ impl<D> Wallet<D> {
|
|||||||
self.chain.tip()
|
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
|
/// 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.
|
/// from seed words). You pass the `BTreeMap` of iterators to a blockchain data source (e.g.
|
||||||
@ -792,36 +819,36 @@ impl<D> Wallet<D> {
|
|||||||
///
|
///
|
||||||
/// Note carefully that iterators go over **all** script pubkeys on the keychains (not what
|
/// Note carefully that iterators go over **all** script pubkeys on the keychains (not what
|
||||||
/// script pubkeys the wallet is storing internally).
|
/// script pubkeys the wallet is storing internally).
|
||||||
pub fn spks_of_all_keychains(
|
pub fn all_unbounded_spk_iters(
|
||||||
&self,
|
&self,
|
||||||
) -> BTreeMap<KeychainKind, impl Iterator<Item = (u32, ScriptBuf)> + Clone> {
|
) -> BTreeMap<KeychainKind, impl Iterator<Item = (u32, ScriptBuf)> + 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
|
/// [`all_unbounded_spk_iters`]: Self::all_unbounded_spk_iters
|
||||||
pub fn spks_of_keychain(
|
pub fn unbounded_spk_iter(
|
||||||
&self,
|
&self,
|
||||||
keychain: KeychainKind,
|
keychain: KeychainKind,
|
||||||
) -> impl Iterator<Item = (u32, ScriptBuf)> + Clone {
|
) -> impl Iterator<Item = (u32, ScriptBuf)> + 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
|
/// Returns the utxo owned by this wallet corresponding to `outpoint` if it exists in the
|
||||||
/// wallet's database.
|
/// wallet's database.
|
||||||
pub fn get_utxo(&self, op: OutPoint) -> Option<LocalOutput> {
|
pub fn get_utxo(&self, op: OutPoint) -> Option<LocalOutput> {
|
||||||
let (&spk_i, _) = self.indexed_graph.index.txout(op)?;
|
let (keychain, index, _) = self.indexed_graph.index.txout(op)?;
|
||||||
self.indexed_graph
|
self.indexed_graph
|
||||||
.graph()
|
.graph()
|
||||||
.filter_chain_unspents(
|
.filter_chain_unspents(
|
||||||
&self.chain,
|
&self.chain,
|
||||||
self.chain.tip().block_id(),
|
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()
|
.next()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1459,7 +1486,7 @@ impl<D> Wallet<D> {
|
|||||||
let ((index, spk), index_changeset) =
|
let ((index, spk), index_changeset) =
|
||||||
self.indexed_graph.index.next_unused_spk(&change_keychain);
|
self.indexed_graph.index.next_unused_spk(&change_keychain);
|
||||||
let spk = spk.into();
|
let spk = spk.into();
|
||||||
self.indexed_graph.index.mark_used(&change_keychain, index);
|
self.indexed_graph.index.mark_used(change_keychain, index);
|
||||||
self.persist
|
self.persist
|
||||||
.stage(ChangeSet::from(indexed_tx_graph::ChangeSet::from(
|
.stage(ChangeSet::from(indexed_tx_graph::ChangeSet::from(
|
||||||
index_changeset,
|
index_changeset,
|
||||||
@ -1640,7 +1667,7 @@ impl<D> Wallet<D> {
|
|||||||
.into();
|
.into();
|
||||||
|
|
||||||
let weighted_utxo = match txout_index.index_of_spk(&txout.script_pubkey) {
|
let weighted_utxo = match txout_index.index_of_spk(&txout.script_pubkey) {
|
||||||
Some(&(keychain, derivation_index)) => {
|
Some((keychain, derivation_index)) => {
|
||||||
#[allow(deprecated)]
|
#[allow(deprecated)]
|
||||||
let satisfaction_weight = self
|
let satisfaction_weight = self
|
||||||
.get_descriptor_for_keychain(keychain)
|
.get_descriptor_for_keychain(keychain)
|
||||||
@ -1684,7 +1711,7 @@ impl<D> Wallet<D> {
|
|||||||
for (index, txout) in tx.output.iter().enumerate() {
|
for (index, txout) in tx.output.iter().enumerate() {
|
||||||
let change_type = self.map_keychain(KeychainKind::Internal);
|
let change_type = self.map_keychain(KeychainKind::Internal);
|
||||||
match txout_index.index_of_spk(&txout.script_pubkey) {
|
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<D> Wallet<D> {
|
|||||||
pub fn cancel_tx(&mut self, tx: &Transaction) {
|
pub fn cancel_tx(&mut self, tx: &Transaction) {
|
||||||
let txout_index = &mut self.indexed_graph.index;
|
let txout_index = &mut self.indexed_graph.index;
|
||||||
for txout in &tx.output {
|
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
|
// 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.
|
// 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<D> Wallet<D> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn get_descriptor_for_txout(&self, txout: &TxOut) -> Option<DerivedDescriptor> {
|
fn get_descriptor_for_txout(&self, txout: &TxOut) -> Option<DerivedDescriptor> {
|
||||||
let &(keychain, child) = self
|
let (keychain, child) = self
|
||||||
.indexed_graph
|
.indexed_graph
|
||||||
.index
|
.index
|
||||||
.index_of_spk(&txout.script_pubkey)?;
|
.index_of_spk(&txout.script_pubkey)?;
|
||||||
@ -2172,7 +2199,7 @@ impl<D> Wallet<D> {
|
|||||||
{
|
{
|
||||||
// Try to find the prev_script in our db to figure out if this is internal or external,
|
// Try to find the prev_script in our db to figure out if this is internal or external,
|
||||||
// and the derivation index
|
// and the derivation index
|
||||||
let &(keychain, child) = self
|
let (keychain, child) = self
|
||||||
.indexed_graph
|
.indexed_graph
|
||||||
.index
|
.index
|
||||||
.index_of_spk(&utxo.txout.script_pubkey)
|
.index_of_spk(&utxo.txout.script_pubkey)
|
||||||
@ -2226,7 +2253,7 @@ impl<D> Wallet<D> {
|
|||||||
|
|
||||||
// Try to figure out the keychain and derivation for every input and output
|
// Try to figure out the keychain and derivation for every input and output
|
||||||
for (is_input, index, out) in utxos.into_iter() {
|
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)
|
self.indexed_graph.index.index_of_spk(&out.script_pubkey)
|
||||||
{
|
{
|
||||||
let desc = self.get_descriptor_for_keychain(keychain);
|
let desc = self.get_descriptor_for_keychain(keychain);
|
||||||
|
@ -5,24 +5,56 @@ use crate::{
|
|||||||
spk_iter::BIP32_MAX_INDEX,
|
spk_iter::BIP32_MAX_INDEX,
|
||||||
SpkIterator, SpkTxOutIndex,
|
SpkIterator, SpkTxOutIndex,
|
||||||
};
|
};
|
||||||
use bitcoin::{OutPoint, Script, TxOut};
|
use bitcoin::{OutPoint, Script, Transaction, TxOut, Txid};
|
||||||
use core::{fmt::Debug, ops::Deref};
|
use core::{
|
||||||
|
fmt::Debug,
|
||||||
|
ops::{Bound, RangeBounds},
|
||||||
|
};
|
||||||
|
|
||||||
use crate::Append;
|
use crate::Append;
|
||||||
|
|
||||||
const DEFAULT_LOOKAHEAD: u32 = 1_000;
|
const DEFAULT_LOOKAHEAD: u32 = 1_000;
|
||||||
|
|
||||||
/// A convenient wrapper around [`SpkTxOutIndex`] that relates script pubkeys to miniscript public
|
/// [`KeychainTxOutIndex`] controls how script pubkeys are revealed for multiple keychains, and
|
||||||
/// [`Descriptor`]s.
|
/// 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 script pubkeys
|
||||||
/// 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.
|
|
||||||
///
|
///
|
||||||
/// 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.
|
/// these changes. This can be persisted for future recovery.
|
||||||
///
|
///
|
||||||
/// ## Synopsis
|
/// ## Synopsis
|
||||||
@ -58,6 +90,15 @@ const DEFAULT_LOOKAHEAD: u32 = 1_000;
|
|||||||
/// [`Ord`]: core::cmp::Ord
|
/// [`Ord`]: core::cmp::Ord
|
||||||
/// [`SpkTxOutIndex`]: crate::spk_txout_index::SpkTxOutIndex
|
/// [`SpkTxOutIndex`]: crate::spk_txout_index::SpkTxOutIndex
|
||||||
/// [`Descriptor`]: crate::miniscript::Descriptor
|
/// [`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)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct KeychainTxOutIndex<K> {
|
pub struct KeychainTxOutIndex<K> {
|
||||||
inner: SpkTxOutIndex<(K, u32)>,
|
inner: SpkTxOutIndex<(K, u32)>,
|
||||||
@ -75,14 +116,6 @@ impl<K> Default for KeychainTxOutIndex<K> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<K> Deref for KeychainTxOutIndex<K> {
|
|
||||||
type Target = SpkTxOutIndex<(K, u32)>;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.inner
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<K: Clone + Ord + Debug> Indexer for KeychainTxOutIndex<K> {
|
impl<K: Clone + Ord + Debug> Indexer for KeychainTxOutIndex<K> {
|
||||||
type ChangeSet = super::ChangeSet<K>;
|
type ChangeSet = super::ChangeSet<K>;
|
||||||
|
|
||||||
@ -110,7 +143,7 @@ impl<K: Clone + Ord + Debug> Indexer for KeychainTxOutIndex<K> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn is_tx_relevant(&self, tx: &bitcoin::Transaction) -> bool {
|
fn is_tx_relevant(&self, tx: &bitcoin::Transaction) -> bool {
|
||||||
self.is_relevant(tx)
|
self.inner.is_relevant(tx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,6 +156,8 @@ impl<K> KeychainTxOutIndex<K> {
|
|||||||
/// beyond the last revealed index. In certain situations, such as when performing an initial
|
/// 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
|
/// scan of the blockchain during wallet import, it may be uncertain or unknown what the index
|
||||||
/// of the last revealed script pubkey actually is.
|
/// of the last revealed script pubkey actually is.
|
||||||
|
///
|
||||||
|
/// Refer to [struct-level docs](KeychainTxOutIndex) for more about `lookahead`.
|
||||||
pub fn new(lookahead: u32) -> Self {
|
pub fn new(lookahead: u32) -> Self {
|
||||||
Self {
|
Self {
|
||||||
inner: SpkTxOutIndex::default(),
|
inner: SpkTxOutIndex::default(),
|
||||||
@ -133,8 +168,12 @@ impl<K> KeychainTxOutIndex<K> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Methods that are *re-exposed* from the internal [`SpkTxOutIndex`].
|
||||||
impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
|
impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
|
||||||
/// Return a reference to the internal [`SpkTxOutIndex`].
|
/// 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)> {
|
pub fn inner(&self) -> &SpkTxOutIndex<(K, u32)> {
|
||||||
&self.inner
|
&self.inner
|
||||||
}
|
}
|
||||||
@ -144,7 +183,116 @@ impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
|
|||||||
self.inner.outpoints()
|
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<Item = (K, u32, OutPoint, &TxOut)> + 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<Item = (K, u32, OutPoint, &TxOut)> {
|
||||||
|
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<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
|
||||||
|
/// Return a reference to the internal map of keychain to descriptors.
|
||||||
pub fn keychains(&self) -> &BTreeMap<K, Descriptor<DescriptorPublicKey>> {
|
pub fn keychains(&self) -> &BTreeMap<K, Descriptor<DescriptorPublicKey>> {
|
||||||
&self.keychains
|
&self.keychains
|
||||||
}
|
}
|
||||||
@ -213,59 +361,67 @@ impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
|
|||||||
.map_or(0, |((_, index), _)| *index + 1)
|
.map_or(0, |((_, index), _)| *index + 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generates script pubkey iterators for every `keychain`. The iterators iterate over all
|
/// Get an unbounded spk iterator over a given `keychain`.
|
||||||
/// derivable script pubkeys.
|
///
|
||||||
pub fn spks_of_all_keychains(
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// This will panic if the given `keychain`'s descriptor does not exist.
|
||||||
|
pub fn unbounded_spk_iter(&self, keychain: &K) -> SpkIterator<Descriptor<DescriptorPublicKey>> {
|
||||||
|
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,
|
&self,
|
||||||
) -> BTreeMap<K, SpkIterator<Descriptor<DescriptorPublicKey>>> {
|
) -> BTreeMap<K, SpkIterator<Descriptor<DescriptorPublicKey>>> {
|
||||||
self.keychains
|
self.keychains
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(keychain, descriptor)| {
|
.map(|(k, descriptor)| (k.clone(), SpkIterator::new(descriptor.clone())))
|
||||||
(
|
.collect()
|
||||||
keychain.clone(),
|
}
|
||||||
SpkIterator::new_with_range(descriptor.clone(), 0..),
|
|
||||||
)
|
/// Iterate over revealed spks of all keychains.
|
||||||
|
pub fn revealed_spks(&self) -> impl DoubleEndedIterator<Item = (K, u32, &Script)> + Clone {
|
||||||
|
self.keychains.keys().flat_map(|keychain| {
|
||||||
|
self.revealed_keychain_spks(keychain)
|
||||||
|
.map(|(i, spk)| (keychain.clone(), i, spk))
|
||||||
})
|
})
|
||||||
.collect()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generates a script pubkey iterator for the given `keychain`'s descriptor (if it exists). The
|
/// Iterate over revealed spks of the given `keychain`.
|
||||||
/// iterator iterates over all derivable scripts of the keychain's descriptor.
|
pub fn revealed_keychain_spks(
|
||||||
///
|
|
||||||
/// # Panics
|
|
||||||
///
|
|
||||||
/// This will panic if the `keychain` does not exist.
|
|
||||||
pub fn spks_of_keychain(&self, keychain: &K) -> SpkIterator<Descriptor<DescriptorPublicKey>> {
|
|
||||||
let descriptor = self
|
|
||||||
.keychains
|
|
||||||
.get(keychain)
|
|
||||||
.expect("keychain must exist")
|
|
||||||
.clone();
|
|
||||||
SpkIterator::new_with_range(descriptor, 0..)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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<K, impl Iterator<Item = (u32, &Script)> + 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(
|
|
||||||
&self,
|
&self,
|
||||||
keychain: &K,
|
keychain: &K,
|
||||||
) -> impl DoubleEndedIterator<Item = (u32, &Script)> + Clone {
|
) -> impl DoubleEndedIterator<Item = (u32, &Script)> + 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
|
self.inner
|
||||||
.all_spks()
|
.all_spks()
|
||||||
.range((keychain.clone(), u32::MIN)..(keychain.clone(), next_index))
|
.range((keychain.clone(), u32::MIN)..(keychain.clone(), next_i))
|
||||||
.map(|((_, derivation_index), spk)| (*derivation_index, spk.as_script()))
|
.map(|((_, i), spk)| (*i, spk.as_script()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterate over revealed, but unused, spks of all keychains.
|
||||||
|
pub fn unused_spks(&self) -> impl DoubleEndedIterator<Item = (K, u32, &Script)> + 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<Item = (u32, &Script)> + 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
|
/// Get the next derivation index for `keychain`. The next index is the index after the last revealed
|
||||||
@ -442,13 +598,13 @@ impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
|
|||||||
///
|
///
|
||||||
/// Panics if `keychain` has never been added to the index
|
/// Panics if `keychain` has never been added to the index
|
||||||
pub fn next_unused_spk(&mut self, keychain: &K) -> ((u32, &Script), super::ChangeSet<K>) {
|
pub fn next_unused_spk(&mut self, keychain: &K) -> ((u32, &Script), super::ChangeSet<K>) {
|
||||||
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
|
// this rather strange branch is needed because of some lifetime issues
|
||||||
if need_new {
|
if need_new {
|
||||||
self.reveal_next_spk(keychain)
|
self.reveal_next_spk(keychain)
|
||||||
} else {
|
} else {
|
||||||
(
|
(
|
||||||
self.unused_spks_of_keychain(keychain)
|
self.unused_keychain_spks(keychain)
|
||||||
.next()
|
.next()
|
||||||
.expect("we already know next exists"),
|
.expect("we already know next exists"),
|
||||||
super::ChangeSet::default(),
|
super::ChangeSet::default(),
|
||||||
@ -456,58 +612,44 @@ impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Marks the script pubkey at `index` as used even though the tracker hasn't seen an output with it.
|
/// Iterate over all [`OutPoint`]s that point to `TxOut`s with script pubkeys derived from
|
||||||
/// 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<Item = (u32, &Script)> {
|
|
||||||
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
|
|
||||||
/// `keychain`.
|
/// `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,
|
&self,
|
||||||
keychain: &K,
|
keychain: &K,
|
||||||
) -> impl DoubleEndedIterator<Item = (u32, OutPoint)> + '_ {
|
) -> impl DoubleEndedIterator<Item = (u32, OutPoint)> + '_ {
|
||||||
|
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<u32>,
|
||||||
|
) -> impl DoubleEndedIterator<Item = (u32, OutPoint)> + '_ {
|
||||||
|
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
|
self.inner
|
||||||
.outputs_in_range((keychain.clone(), u32::MIN)..(keychain.clone(), u32::MAX))
|
.outputs_in_range((start, end))
|
||||||
.map(|((_, i), op)| (*i, op))
|
.map(|((_, i), op)| (*i, op))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the highest derivation index of the `keychain` where [`KeychainTxOutIndex`] has
|
/// Returns the highest derivation index of the `keychain` where [`KeychainTxOutIndex`] has
|
||||||
/// found a [`TxOut`] with it's script pubkey.
|
/// found a [`TxOut`] with it's script pubkey.
|
||||||
pub fn last_used_index(&self, keychain: &K) -> Option<u32> {
|
pub fn last_used_index(&self, keychain: &K) -> Option<u32> {
|
||||||
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
|
/// Returns the highest derivation index of each keychain that [`KeychainTxOutIndex`] has found
|
||||||
|
@ -87,6 +87,11 @@ where
|
|||||||
secp: Secp256k1::verification_only(),
|
secp: Secp256k1::verification_only(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get a reference to the internal descriptor.
|
||||||
|
pub fn descriptor(&self) -> &D {
|
||||||
|
&self.descriptor
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<D> Iterator for SpkIterator<D>
|
impl<D> Iterator for SpkIterator<D>
|
||||||
|
@ -215,7 +215,7 @@ impl<I: Clone + Ord> SpkTxOutIndex<I> {
|
|||||||
/// let unused_change_spks =
|
/// let unused_change_spks =
|
||||||
/// txout_index.unused_spks((change_index, u32::MIN)..(change_index, u32::MAX));
|
/// txout_index.unused_spks((change_index, u32::MIN)..(change_index, u32::MAX));
|
||||||
/// ```
|
/// ```
|
||||||
pub fn unused_spks<R>(&self, range: R) -> impl DoubleEndedIterator<Item = (&I, &Script)>
|
pub fn unused_spks<R>(&self, range: R) -> impl DoubleEndedIterator<Item = (&I, &Script)> + Clone
|
||||||
where
|
where
|
||||||
R: RangeBounds<I>,
|
R: RangeBounds<I>,
|
||||||
{
|
{
|
||||||
|
@ -95,25 +95,25 @@ fn test_lookahead() {
|
|||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
txout_index
|
txout_index
|
||||||
.revealed_spks_of_keychain(&TestKeychain::External)
|
.revealed_keychain_spks(&TestKeychain::External)
|
||||||
.count(),
|
.count(),
|
||||||
index as usize + 1,
|
index as usize + 1,
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
txout_index
|
txout_index
|
||||||
.revealed_spks_of_keychain(&TestKeychain::Internal)
|
.revealed_keychain_spks(&TestKeychain::Internal)
|
||||||
.count(),
|
.count(),
|
||||||
0,
|
0,
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
txout_index
|
txout_index
|
||||||
.unused_spks_of_keychain(&TestKeychain::External)
|
.unused_keychain_spks(&TestKeychain::External)
|
||||||
.count(),
|
.count(),
|
||||||
index as usize + 1,
|
index as usize + 1,
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
txout_index
|
txout_index
|
||||||
.unused_spks_of_keychain(&TestKeychain::Internal)
|
.unused_keychain_spks(&TestKeychain::Internal)
|
||||||
.count(),
|
.count(),
|
||||||
0,
|
0,
|
||||||
);
|
);
|
||||||
@ -147,7 +147,7 @@ fn test_lookahead() {
|
|||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
txout_index
|
txout_index
|
||||||
.revealed_spks_of_keychain(&TestKeychain::Internal)
|
.revealed_keychain_spks(&TestKeychain::Internal)
|
||||||
.count(),
|
.count(),
|
||||||
25,
|
25,
|
||||||
);
|
);
|
||||||
@ -199,13 +199,13 @@ fn test_lookahead() {
|
|||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
txout_index
|
txout_index
|
||||||
.revealed_spks_of_keychain(&TestKeychain::External)
|
.revealed_keychain_spks(&TestKeychain::External)
|
||||||
.count(),
|
.count(),
|
||||||
last_external_index as usize + 1,
|
last_external_index as usize + 1,
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
txout_index
|
txout_index
|
||||||
.revealed_spks_of_keychain(&TestKeychain::Internal)
|
.revealed_keychain_spks(&TestKeychain::Internal)
|
||||||
.count(),
|
.count(),
|
||||||
last_internal_index as usize + 1,
|
last_internal_index as usize + 1,
|
||||||
);
|
);
|
||||||
@ -305,7 +305,7 @@ fn test_wildcard_derivations() {
|
|||||||
|
|
||||||
(0..=15)
|
(0..=15)
|
||||||
.chain([17, 20, 23])
|
.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));
|
assert_eq!(txout_index.next_index(&TestKeychain::External), (26, true));
|
||||||
|
|
||||||
@ -321,7 +321,7 @@ fn test_wildcard_derivations() {
|
|||||||
// - Use all the derived till 26.
|
// - Use all the derived till 26.
|
||||||
// - next_unused() = ((27, <spk>), keychain::ChangeSet)
|
// - next_unused() = ((27, <spk>), keychain::ChangeSet)
|
||||||
(0..=26).for_each(|index| {
|
(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);
|
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
|
// - derive new and next unused should return the old script
|
||||||
// - store_up_to should not panic and return empty changeset
|
// - store_up_to should not panic and return empty changeset
|
||||||
assert_eq!(txout_index.next_index(&TestKeychain::External), (0, false));
|
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);
|
let (spk, changeset) = txout_index.reveal_next_spk(&TestKeychain::External);
|
||||||
assert_eq!(spk, (0, external_spk.as_script()));
|
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
|
// we check that spks_of_keychain returns a SpkIterator with just one element
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
txout_index
|
txout_index
|
||||||
.spks_of_keychain(&TestKeychain::External)
|
.revealed_keychain_spks(&TestKeychain::External)
|
||||||
.count(),
|
.count(),
|
||||||
1,
|
1,
|
||||||
);
|
);
|
||||||
|
@ -187,7 +187,7 @@ impl ElectrumExt for Client {
|
|||||||
) -> Result<(ElectrumUpdate, BTreeMap<K, u32>), Error> {
|
) -> Result<(ElectrumUpdate, BTreeMap<K, u32>), Error> {
|
||||||
let mut request_spks = keychain_spks
|
let mut request_spks = keychain_spks
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(k, s)| (k, s.into_iter()))
|
.map(|(k, s)| (k.clone(), s.into_iter()))
|
||||||
.collect::<BTreeMap<K, _>>();
|
.collect::<BTreeMap<K, _>>();
|
||||||
let mut scanned_spks = BTreeMap::<(K, u32), (ScriptBuf, bool)>::new();
|
let mut scanned_spks = BTreeMap::<(K, u32), (ScriptBuf, bool)>::new();
|
||||||
|
|
||||||
|
@ -478,14 +478,14 @@ where
|
|||||||
true => Keychain::Internal,
|
true => Keychain::Internal,
|
||||||
false => Keychain::External,
|
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)
|
let address = Address::from_script(spk, network)
|
||||||
.expect("should always be able to derive address");
|
.expect("should always be able to derive address");
|
||||||
println!(
|
println!(
|
||||||
"{:?} {} used:{}",
|
"{:?} {} used:{}",
|
||||||
spk_i,
|
spk_i,
|
||||||
address,
|
address,
|
||||||
index.is_used(&(target_keychain, spk_i))
|
index.is_used(target_keychain, spk_i)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -611,7 +611,7 @@ where
|
|||||||
// We don't want other callers/threads to use this address while we're using it
|
// 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
|
// but we also don't want to scan the tx we just created because it's not
|
||||||
// technically in the blockchain yet.
|
// technically in the blockchain yet.
|
||||||
graph.index.mark_used(&change_keychain, index);
|
graph.index.mark_used(change_keychain, index);
|
||||||
(tx, Some((change_keychain, index)))
|
(tx, Some((change_keychain, index)))
|
||||||
} else {
|
} else {
|
||||||
(tx, None)
|
(tx, None)
|
||||||
@ -636,7 +636,7 @@ where
|
|||||||
Err(e) => {
|
Err(e) => {
|
||||||
if let Some((keychain, index)) = change_index {
|
if let Some((keychain, index)) = change_index {
|
||||||
// We failed to broadcast, so allow our change address to be used in the future
|
// 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)
|
Err(e)
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ use std::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use bdk_chain::{
|
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},
|
indexed_tx_graph::{self, IndexedTxGraph},
|
||||||
keychain,
|
keychain,
|
||||||
local_chain::{self, LocalChain},
|
local_chain::{self, LocalChain},
|
||||||
@ -155,7 +155,7 @@ fn main() -> anyhow::Result<()> {
|
|||||||
|
|
||||||
let keychain_spks = graph
|
let keychain_spks = graph
|
||||||
.index
|
.index
|
||||||
.spks_of_all_keychains()
|
.all_unbounded_spk_iters()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(keychain, iter)| {
|
.map(|(keychain, iter)| {
|
||||||
let mut first = true;
|
let mut first = true;
|
||||||
@ -206,29 +206,28 @@ fn main() -> anyhow::Result<()> {
|
|||||||
if all_spks {
|
if all_spks {
|
||||||
let all_spks = graph
|
let all_spks = graph
|
||||||
.index
|
.index
|
||||||
.all_spks()
|
.revealed_spks()
|
||||||
.iter()
|
.map(|(k, i, spk)| (k, i, spk.to_owned()))
|
||||||
.map(|(k, v)| (*k, v.clone()))
|
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
spks = Box::new(spks.chain(all_spks.into_iter().map(|(index, script)| {
|
spks = Box::new(spks.chain(all_spks.into_iter().map(|(k, i, spk)| {
|
||||||
eprintln!("scanning {:?}", index);
|
eprintln!("scanning {}:{}", k, i);
|
||||||
script
|
spk
|
||||||
})));
|
})));
|
||||||
}
|
}
|
||||||
if unused_spks {
|
if unused_spks {
|
||||||
let unused_spks = graph
|
let unused_spks = graph
|
||||||
.index
|
.index
|
||||||
.unused_spks(..)
|
.unused_spks()
|
||||||
.map(|(k, v)| (*k, ScriptBuf::from(v)))
|
.map(|(k, i, spk)| (k, i, spk.to_owned()))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
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!(
|
eprintln!(
|
||||||
"Checking if address {} {:?} has been used",
|
"Checking if address {} {}:{} has been used",
|
||||||
Address::from_script(&script, args.network).unwrap(),
|
Address::from_script(&spk, args.network).unwrap(),
|
||||||
index
|
k,
|
||||||
|
i,
|
||||||
);
|
);
|
||||||
|
spk
|
||||||
script
|
|
||||||
})));
|
})));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -165,7 +165,7 @@ fn main() -> anyhow::Result<()> {
|
|||||||
.lock()
|
.lock()
|
||||||
.expect("mutex must not be poisoned")
|
.expect("mutex must not be poisoned")
|
||||||
.index
|
.index
|
||||||
.spks_of_all_keychains()
|
.all_unbounded_spk_iters()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
// This `map` is purely for logging.
|
// This `map` is purely for logging.
|
||||||
.map(|(keychain, iter)| {
|
.map(|(keychain, iter)| {
|
||||||
@ -235,32 +235,32 @@ fn main() -> anyhow::Result<()> {
|
|||||||
if *all_spks {
|
if *all_spks {
|
||||||
let all_spks = graph
|
let all_spks = graph
|
||||||
.index
|
.index
|
||||||
.all_spks()
|
.revealed_spks()
|
||||||
.iter()
|
.map(|(k, i, spk)| (k, i, spk.to_owned()))
|
||||||
.map(|(k, v)| (*k, v.clone()))
|
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
spks = Box::new(spks.chain(all_spks.into_iter().map(|(index, script)| {
|
spks = Box::new(spks.chain(all_spks.into_iter().map(|(k, i, spk)| {
|
||||||
eprintln!("scanning {:?}", index);
|
eprintln!("scanning {}:{}", k, i);
|
||||||
// Flush early to ensure we print at every iteration.
|
// Flush early to ensure we print at every iteration.
|
||||||
let _ = io::stderr().flush();
|
let _ = io::stderr().flush();
|
||||||
script
|
spk
|
||||||
})));
|
})));
|
||||||
}
|
}
|
||||||
if unused_spks {
|
if unused_spks {
|
||||||
let unused_spks = graph
|
let unused_spks = graph
|
||||||
.index
|
.index
|
||||||
.unused_spks(..)
|
.unused_spks()
|
||||||
.map(|(k, v)| (*k, v.to_owned()))
|
.map(|(k, i, spk)| (k, i, spk.to_owned()))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
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!(
|
eprintln!(
|
||||||
"Checking if address {} {:?} has been used",
|
"Checking if address {} {}:{} has been used",
|
||||||
Address::from_script(&script, args.network).unwrap(),
|
Address::from_script(&spk, args.network).unwrap(),
|
||||||
index
|
k,
|
||||||
|
i,
|
||||||
);
|
);
|
||||||
// Flush early to ensure we print at every iteration.
|
// Flush early to ensure we print at every iteration.
|
||||||
let _ = io::stderr().flush();
|
let _ = io::stderr().flush();
|
||||||
script
|
spk
|
||||||
})));
|
})));
|
||||||
}
|
}
|
||||||
if utxos {
|
if utxos {
|
||||||
|
@ -40,7 +40,7 @@ fn main() -> Result<(), anyhow::Error> {
|
|||||||
|
|
||||||
let prev_tip = wallet.latest_checkpoint();
|
let prev_tip = wallet.latest_checkpoint();
|
||||||
let keychain_spks = wallet
|
let keychain_spks = wallet
|
||||||
.spks_of_all_keychains()
|
.all_unbounded_spk_iters()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(k, k_spks)| {
|
.map(|(k, k_spks)| {
|
||||||
let mut once = Some(());
|
let mut once = Some(());
|
||||||
|
@ -39,7 +39,7 @@ async fn main() -> Result<(), anyhow::Error> {
|
|||||||
|
|
||||||
let prev_tip = wallet.latest_checkpoint();
|
let prev_tip = wallet.latest_checkpoint();
|
||||||
let keychain_spks = wallet
|
let keychain_spks = wallet
|
||||||
.spks_of_all_keychains()
|
.all_unbounded_spk_iters()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(k, k_spks)| {
|
.map(|(k, k_spks)| {
|
||||||
let mut once = Some(());
|
let mut once = Some(());
|
||||||
|
@ -38,7 +38,7 @@ fn main() -> Result<(), anyhow::Error> {
|
|||||||
|
|
||||||
let prev_tip = wallet.latest_checkpoint();
|
let prev_tip = wallet.latest_checkpoint();
|
||||||
let keychain_spks = wallet
|
let keychain_spks = wallet
|
||||||
.spks_of_all_keychains()
|
.all_unbounded_spk_iters()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(k, k_spks)| {
|
.map(|(k, k_spks)| {
|
||||||
let mut once = Some(());
|
let mut once = Some(());
|
||||||
|
Loading…
x
Reference in New Issue
Block a user