use bdk_bitcoind_rpc::Emitter;
use bdk_chain::{
bitcoin::{Address, Amount, Txid},
- keychain::Balance,
local_chain::{CheckPoint, LocalChain},
- Append, BlockId, IndexedTxGraph, SpkTxOutIndex,
+ Append, Balance, BlockId, IndexedTxGraph, SpkTxOutIndex,
};
use bdk_testenv::{anyhow, TestEnv};
use bitcoin::{hashes::Hash, Block, OutPoint, ScriptBuf, WScriptHash};
--- /dev/null
+use bitcoin::Amount;
+
+/// Balance, differentiated into various categories.
+#[derive(Debug, PartialEq, Eq, Clone, Default)]
+#[cfg_attr(
+ feature = "serde",
+ derive(serde::Deserialize, serde::Serialize),
+ serde(crate = "serde_crate",)
+)]
+pub struct Balance {
+ /// All coinbase outputs not yet matured
+ pub immature: Amount,
+ /// Unconfirmed UTXOs generated by a wallet tx
+ pub trusted_pending: Amount,
+ /// Unconfirmed UTXOs received from an external wallet
+ pub untrusted_pending: Amount,
+ /// Confirmed and immediately spendable balance
+ pub confirmed: Amount,
+}
+
+impl Balance {
+ /// Get sum of trusted_pending and confirmed coins.
+ ///
+ /// This is the balance you can spend right now that shouldn't get cancelled via another party
+ /// double spending it.
+ pub fn trusted_spendable(&self) -> Amount {
+ self.confirmed + self.trusted_pending
+ }
+
+ /// Get the whole balance visible to the wallet.
+ pub fn total(&self) -> Amount {
+ self.confirmed + self.trusted_pending + self.untrusted_pending + self.immature
+ }
+}
+
+impl core::fmt::Display for Balance {
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+ write!(
+ f,
+ "{{ immature: {}, trusted_pending: {}, untrusted_pending: {}, confirmed: {} }}",
+ self.immature, self.trusted_pending, self.untrusted_pending, self.confirmed
+ )
+ }
+}
+
+impl core::ops::Add for Balance {
+ type Output = Self;
+
+ fn add(self, other: Self) -> Self {
+ Self {
+ immature: self.immature + other.immature,
+ trusted_pending: self.trusted_pending + other.trusted_pending,
+ untrusted_pending: self.untrusted_pending + other.untrusted_pending,
+ confirmed: self.confirmed + other.confirmed,
+ }
+ }
+}
/// Changes to the [`LocalChain`](crate::local_chain::LocalChain).
pub chain: crate::local_chain::ChangeSet,
/// Changes to [`IndexedTxGraph`](crate::indexed_tx_graph::IndexedTxGraph).
- pub indexed_tx_graph: crate::indexed_tx_graph::ChangeSet<A, crate::keychain::ChangeSet<K>>,
+ pub indexed_tx_graph:
+ crate::indexed_tx_graph::ChangeSet<A, crate::indexer::keychain_txout::ChangeSet<K>>,
/// Stores the network type of the transaction data.
pub network: Option<bitcoin::Network>,
}
}
#[cfg(feature = "miniscript")]
-impl<K, A> From<crate::indexed_tx_graph::ChangeSet<A, crate::keychain::ChangeSet<K>>>
+impl<K, A> From<crate::indexed_tx_graph::ChangeSet<A, crate::indexer::keychain_txout::ChangeSet<K>>>
for CombinedChangeSet<K, A>
{
fn from(
- indexed_tx_graph: crate::indexed_tx_graph::ChangeSet<A, crate::keychain::ChangeSet<K>>,
+ indexed_tx_graph: crate::indexed_tx_graph::ChangeSet<
+ A,
+ crate::indexer::keychain_txout::ChangeSet<K>,
+ >,
) -> Self {
Self {
indexed_tx_graph,
}
#[cfg(feature = "miniscript")]
-impl<K, A> From<crate::keychain::ChangeSet<K>> for CombinedChangeSet<K, A> {
- fn from(indexer: crate::keychain::ChangeSet<K>) -> Self {
+impl<K, A> From<crate::indexer::keychain_txout::ChangeSet<K>> for CombinedChangeSet<K, A> {
+ fn from(indexer: crate::indexer::keychain_txout::ChangeSet<K>) -> Self {
Self {
indexed_tx_graph: crate::indexed_tx_graph::ChangeSet {
indexer,
use crate::{
tx_graph::{self, TxGraph},
- Anchor, AnchorFromBlockPosition, Append, BlockId,
+ Anchor, AnchorFromBlockPosition, Append, BlockId, Indexer,
};
/// The [`IndexedTxGraph`] combines a [`TxGraph`] and an [`Indexer`] implementation.
}
#[cfg(feature = "miniscript")]
-impl<A, K> From<crate::keychain::ChangeSet<K>> for ChangeSet<A, crate::keychain::ChangeSet<K>> {
- fn from(indexer: crate::keychain::ChangeSet<K>) -> Self {
+impl<A, K> From<crate::indexer::keychain_txout::ChangeSet<K>>
+ for ChangeSet<A, crate::indexer::keychain_txout::ChangeSet<K>>
+{
+ fn from(indexer: crate::indexer::keychain_txout::ChangeSet<K>) -> Self {
Self {
graph: Default::default(),
indexer,
}
}
-/// Utilities for indexing transaction data.
-///
-/// Types which implement this trait can be used to construct an [`IndexedTxGraph`].
-/// This trait's methods should rarely be called directly.
-pub trait Indexer {
- /// The resultant "changeset" when new transaction data is indexed.
- type ChangeSet;
-
- /// Scan and index the given `outpoint` and `txout`.
- fn index_txout(&mut self, outpoint: OutPoint, txout: &TxOut) -> Self::ChangeSet;
-
- /// Scans a transaction for relevant outpoints, which are stored and indexed internally.
- fn index_tx(&mut self, tx: &Transaction) -> Self::ChangeSet;
-
- /// Apply changeset to itself.
- fn apply_changeset(&mut self, changeset: Self::ChangeSet);
-
- /// Determines the [`ChangeSet`] between `self` and an empty [`Indexer`].
- fn initial_changeset(&self) -> Self::ChangeSet;
-
- /// Determines whether the transaction should be included in the index.
- fn is_tx_relevant(&self, tx: &Transaction) -> bool;
-}
-
impl<A, I> AsRef<TxGraph<A>> for IndexedTxGraph<A, I> {
fn as_ref(&self) -> &TxGraph<A> {
&self.graph
--- /dev/null
+//! [`Indexer`] provides utilities for indexing transaction data.
+
+use bitcoin::{OutPoint, Transaction, TxOut};
+
+#[cfg(feature = "miniscript")]
+pub mod keychain_txout;
+pub mod spk_txout;
+
+/// Utilities for indexing transaction data.
+///
+/// Types which implement this trait can be used to construct an [`IndexedTxGraph`].
+/// This trait's methods should rarely be called directly.
+///
+/// [`IndexedTxGraph`]: crate::IndexedTxGraph
+pub trait Indexer {
+ /// The resultant "changeset" when new transaction data is indexed.
+ type ChangeSet;
+
+ /// Scan and index the given `outpoint` and `txout`.
+ fn index_txout(&mut self, outpoint: OutPoint, txout: &TxOut) -> Self::ChangeSet;
+
+ /// Scans a transaction for relevant outpoints, which are stored and indexed internally.
+ fn index_tx(&mut self, tx: &Transaction) -> Self::ChangeSet;
+
+ /// Apply changeset to itself.
+ fn apply_changeset(&mut self, changeset: Self::ChangeSet);
+
+ /// Determines the [`ChangeSet`](Indexer::ChangeSet) between `self` and an empty [`Indexer`].
+ fn initial_changeset(&self) -> Self::ChangeSet;
+
+ /// Determines whether the transaction should be included in the index.
+ fn is_tx_relevant(&self, tx: &Transaction) -> bool;
+}
--- /dev/null
+//! [`KeychainTxOutIndex`] controls how script pubkeys are revealed for multiple keychains and
+//! indexes [`TxOut`]s with them.
+
+use crate::{
+ collections::*,
+ miniscript::{Descriptor, DescriptorPublicKey},
+ spk_iter::BIP32_MAX_INDEX,
+ DescriptorExt, DescriptorId, Indexed, Indexer, KeychainIndexed, SpkIterator, SpkTxOutIndex,
+};
+use alloc::{borrow::ToOwned, vec::Vec};
+use bitcoin::{Amount, OutPoint, Script, ScriptBuf, SignedAmount, Transaction, TxOut, Txid};
+use core::{
+ fmt::Debug,
+ ops::{Bound, RangeBounds},
+};
+
+use crate::Append;
+
+/// The default lookahead for a [`KeychainTxOutIndex`]
+pub const DEFAULT_LOOKAHEAD: u32 = 25;
+
+/// [`KeychainTxOutIndex`] controls how script pubkeys are revealed for multiple keychains, and
+/// indexes [`TxOut`]s with them.
+///
+/// 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`.
+///
+/// There is a strict 1-to-1 relationship between descriptors and keychains. Each keychain has one
+/// and only one descriptor and each descriptor has one and only one keychain. The
+/// [`insert_descriptor`] method will return an error if you try and violate this invariant. This
+/// rule is a proxy for a stronger rule: no two descriptors should produce the same script pubkey.
+/// Having two descriptors produce the same script pubkey should cause whichever keychain derives
+/// the script pubkey first to be the effective owner of it but you should not rely on this
+/// behaviour. âš It is up you, the developer, not to violate this invariant.
+///
+/// # Revealed script pubkeys
+///
+/// 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. See
+/// [`DEFAULT_LOOKAHEAD`] for the value used in the `Default` implementation. 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 because it doesn't
+/// require holding a reference to the index.
+///
+/// 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 or add keychains will return [`ChangeSet`] to report
+/// these changes. This should be persisted for future recovery.
+///
+/// ## Synopsis
+///
+/// ```
+/// use bdk_chain::indexer::keychain_txout::KeychainTxOutIndex;
+/// # use bdk_chain::{ miniscript::{Descriptor, DescriptorPublicKey} };
+/// # use core::str::FromStr;
+///
+/// // imagine our service has internal and external addresses but also addresses for users
+/// #[derive(Clone, Debug, PartialEq, Eq, Ord, PartialOrd)]
+/// enum MyKeychain {
+/// External,
+/// Internal,
+/// MyAppUser {
+/// user_id: u32
+/// }
+/// }
+///
+/// let mut txout_index = KeychainTxOutIndex::<MyKeychain>::default();
+///
+/// # let secp = bdk_chain::bitcoin::secp256k1::Secp256k1::signing_only();
+/// # let (external_descriptor,_) = Descriptor::<DescriptorPublicKey>::parse_descriptor(&secp, "tr([73c5da0a/86'/0'/0']xprv9xgqHN7yz9MwCkxsBPN5qetuNdQSUttZNKw1dcYTV4mkaAFiBVGQziHs3NRSWMkCzvgjEe3n9xV8oYywvM8at9yRqyaZVz6TYYhX98VjsUk/0/*)").unwrap();
+/// # let (internal_descriptor,_) = Descriptor::<DescriptorPublicKey>::parse_descriptor(&secp, "tr([73c5da0a/86'/0'/0']xprv9xgqHN7yz9MwCkxsBPN5qetuNdQSUttZNKw1dcYTV4mkaAFiBVGQziHs3NRSWMkCzvgjEe3n9xV8oYywvM8at9yRqyaZVz6TYYhX98VjsUk/1/*)").unwrap();
+/// # let (descriptor_42, _) = Descriptor::<DescriptorPublicKey>::parse_descriptor(&secp, "tr([73c5da0a/86'/0'/0']xprv9xgqHN7yz9MwCkxsBPN5qetuNdQSUttZNKw1dcYTV4mkaAFiBVGQziHs3NRSWMkCzvgjEe3n9xV8oYywvM8at9yRqyaZVz6TYYhX98VjsUk/2/*)").unwrap();
+/// let _ = txout_index.insert_descriptor(MyKeychain::External, external_descriptor)?;
+/// let _ = txout_index.insert_descriptor(MyKeychain::Internal, internal_descriptor)?;
+/// let _ = txout_index.insert_descriptor(MyKeychain::MyAppUser { user_id: 42 }, descriptor_42)?;
+///
+/// let new_spk_for_user = txout_index.reveal_next_spk(&MyKeychain::MyAppUser{ user_id: 42 });
+/// # Ok::<_, bdk_chain::indexer::keychain_txout::InsertDescriptorError<_>>(())
+/// ```
+///
+/// [`Ord`]: core::cmp::Ord
+/// [`SpkTxOutIndex`]: crate::spk_txout_index::SpkTxOutIndex
+/// [`Descriptor`]: crate::miniscript::Descriptor
+/// [`reveal_to_target`]: Self::reveal_to_target
+/// [`reveal_next_spk`]: Self::reveal_next_spk
+/// [`revealed_keychain_spks`]: Self::revealed_keychain_spks
+/// [`revealed_spks`]: Self::revealed_spks
+/// [`index_tx`]: Self::index_tx
+/// [`index_txout`]: Self::index_txout
+/// [`new`]: Self::new
+/// [`unbounded_spk_iter`]: Self::unbounded_spk_iter
+/// [`all_unbounded_spk_iters`]: Self::all_unbounded_spk_iters
+/// [`outpoints`]: Self::outpoints
+/// [`txouts`]: Self::txouts
+/// [`unused_spks`]: Self::unused_spks
+/// [`insert_descriptor`]: Self::insert_descriptor
+#[derive(Clone, Debug)]
+pub struct KeychainTxOutIndex<K> {
+ inner: SpkTxOutIndex<(K, u32)>,
+ keychain_to_descriptor_id: BTreeMap<K, DescriptorId>,
+ descriptor_id_to_keychain: HashMap<DescriptorId, K>,
+ descriptors: HashMap<DescriptorId, Descriptor<DescriptorPublicKey>>,
+ last_revealed: HashMap<DescriptorId, u32>,
+ lookahead: u32,
+}
+
+impl<K> Default for KeychainTxOutIndex<K> {
+ fn default() -> Self {
+ Self::new(DEFAULT_LOOKAHEAD)
+ }
+}
+
+impl<K: Clone + Ord + Debug> Indexer for KeychainTxOutIndex<K> {
+ type ChangeSet = ChangeSet<K>;
+
+ fn index_txout(&mut self, outpoint: OutPoint, txout: &TxOut) -> Self::ChangeSet {
+ let mut changeset = ChangeSet::default();
+ if let Some((keychain, index)) = self.inner.scan_txout(outpoint, txout).cloned() {
+ let did = self
+ .keychain_to_descriptor_id
+ .get(&keychain)
+ .expect("invariant");
+ if self.last_revealed.get(did) < Some(&index) {
+ self.last_revealed.insert(*did, index);
+ changeset.last_revealed.insert(*did, index);
+ self.replenish_inner_index(*did, &keychain, self.lookahead);
+ }
+ }
+ changeset
+ }
+
+ fn index_tx(&mut self, tx: &bitcoin::Transaction) -> Self::ChangeSet {
+ let mut changeset = ChangeSet::<K>::default();
+ let txid = tx.compute_txid();
+ for (op, txout) in tx.output.iter().enumerate() {
+ changeset.append(self.index_txout(OutPoint::new(txid, op as u32), txout));
+ }
+ changeset
+ }
+
+ fn initial_changeset(&self) -> Self::ChangeSet {
+ ChangeSet {
+ keychains_added: self
+ .keychains()
+ .map(|(k, v)| (k.clone(), v.clone()))
+ .collect(),
+ last_revealed: self.last_revealed.clone().into_iter().collect(),
+ }
+ }
+
+ fn apply_changeset(&mut self, changeset: Self::ChangeSet) {
+ self.apply_changeset(changeset)
+ }
+
+ fn is_tx_relevant(&self, tx: &bitcoin::Transaction) -> bool {
+ self.inner.is_relevant(tx)
+ }
+}
+
+impl<K> KeychainTxOutIndex<K> {
+ /// Construct a [`KeychainTxOutIndex`] with the given `lookahead`.
+ ///
+ /// The `lookahead` is the number of script pubkeys to derive and cache from the internal
+ /// descriptors over and above the last revealed script index. Without a lookahead the index
+ /// will miss outputs you own when processing transactions whose output script pubkeys lie
+ /// 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(),
+ keychain_to_descriptor_id: Default::default(),
+ descriptors: Default::default(),
+ descriptor_id_to_keychain: Default::default(),
+ last_revealed: Default::default(),
+ lookahead,
+ }
+ }
+}
+
+/// Methods that are *re-exposed* from the internal [`SpkTxOutIndex`].
+impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
+ /// 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
+ }
+
+ /// Get the set of indexed outpoints, corresponding to tracked keychains.
+ pub fn outpoints(&self) -> &BTreeSet<KeychainIndexed<K, OutPoint>> {
+ self.inner.outpoints()
+ }
+
+ /// Iterate over known txouts that spend to tracked script pubkeys.
+ pub fn txouts(
+ &self,
+ ) -> impl DoubleEndedIterator<Item = KeychainIndexed<K, (OutPoint, &TxOut)>> + ExactSizeIterator
+ {
+ self.inner
+ .txouts()
+ .map(|(index, op, txout)| (index.clone(), (op, txout)))
+ }
+
+ /// Finds all txouts on a transaction that has previously been scanned and indexed.
+ pub fn txouts_in_tx(
+ &self,
+ txid: Txid,
+ ) -> impl DoubleEndedIterator<Item = KeychainIndexed<K, (OutPoint, &TxOut)>> {
+ self.inner
+ .txouts_in_tx(txid)
+ .map(|(index, op, txout)| (index.clone(), (op, txout)))
+ }
+
+ /// Return the [`TxOut`] of `outpoint` if it has been indexed, and if it corresponds to a
+ /// tracked keychain.
+ ///
+ /// 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<KeychainIndexed<K, &TxOut>> {
+ self.inner
+ .txout(outpoint)
+ .map(|(index, txout)| (index.clone(), txout))
+ }
+
+ /// 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.clone(), 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)
+ }
+
+ /// 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 spk under the given `keychain` and `index` is successfully
+ /// marked as used. Returns false either when there is no descriptor under the given
+ /// keychain, or when the spk is already marked as used.
+ ///
+ /// 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 the total value transfer effect `tx` has on the script pubkeys belonging to the
+ /// keychains in `range`. Value is *sent* when a script pubkey in the `range` is on an input and
+ /// *received* when it is on an output. For `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.
+ pub fn sent_and_received(
+ &self,
+ tx: &Transaction,
+ range: impl RangeBounds<K>,
+ ) -> (Amount, Amount) {
+ self.inner
+ .sent_and_received(tx, self.map_to_inner_bounds(range))
+ }
+
+ /// 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, range: impl RangeBounds<K>) -> SignedAmount {
+ self.inner.net_value(tx, self.map_to_inner_bounds(range))
+ }
+}
+
+impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
+ /// Return all keychains and their corresponding descriptors.
+ pub fn keychains(
+ &self,
+ ) -> impl DoubleEndedIterator<Item = (&K, &Descriptor<DescriptorPublicKey>)> + ExactSizeIterator + '_
+ {
+ self.keychain_to_descriptor_id
+ .iter()
+ .map(|(k, did)| (k, self.descriptors.get(did).expect("invariant")))
+ }
+
+ /// Insert a descriptor with a keychain associated to it.
+ ///
+ /// Adding a descriptor means you will be able to derive new script pubkeys under it and the
+ /// txout index will discover transaction outputs with those script pubkeys (once they've been
+ /// derived and added to the index).
+ ///
+ /// keychain <-> descriptor is a one-to-one mapping that cannot be changed. Attempting to do so
+ /// will return a [`InsertDescriptorError<K>`].
+ ///
+ /// `[KeychainTxOutIndex]` will prevent you from inserting two descriptors which derive the same
+ /// script pubkey at index 0, but it's up to you to ensure that descriptors don't collide at
+ /// other indices. If they do nothing catastrophic happens at the `KeychainTxOutIndex` level
+ /// (one keychain just becomes the defacto owner of that spk arbitrarily) but this may have
+ /// subtle implications up the application stack like one UTXO being missing from one keychain
+ /// because it has been assigned to another which produces the same script pubkey.
+ pub fn insert_descriptor(
+ &mut self,
+ keychain: K,
+ descriptor: Descriptor<DescriptorPublicKey>,
+ ) -> Result<ChangeSet<K>, InsertDescriptorError<K>> {
+ let mut changeset = ChangeSet::<K>::default();
+ let did = descriptor.descriptor_id();
+ if !self.keychain_to_descriptor_id.contains_key(&keychain)
+ && !self.descriptor_id_to_keychain.contains_key(&did)
+ {
+ self.descriptors.insert(did, descriptor.clone());
+ self.keychain_to_descriptor_id.insert(keychain.clone(), did);
+ self.descriptor_id_to_keychain.insert(did, keychain.clone());
+ self.replenish_inner_index(did, &keychain, self.lookahead);
+ changeset
+ .keychains_added
+ .insert(keychain.clone(), descriptor);
+ } else {
+ if let Some(existing_desc_id) = self.keychain_to_descriptor_id.get(&keychain) {
+ let descriptor = self.descriptors.get(existing_desc_id).expect("invariant");
+ if *existing_desc_id != did {
+ return Err(InsertDescriptorError::KeychainAlreadyAssigned {
+ existing_assignment: descriptor.clone(),
+ keychain,
+ });
+ }
+ }
+
+ if let Some(existing_keychain) = self.descriptor_id_to_keychain.get(&did) {
+ let descriptor = self.descriptors.get(&did).expect("invariant").clone();
+
+ if *existing_keychain != keychain {
+ return Err(InsertDescriptorError::DescriptorAlreadyAssigned {
+ existing_assignment: existing_keychain.clone(),
+ descriptor,
+ });
+ }
+ }
+ }
+
+ Ok(changeset)
+ }
+
+ /// Gets the descriptor associated with the keychain. Returns `None` if the keychain doesn't
+ /// have a descriptor associated with it.
+ pub fn get_descriptor(&self, keychain: &K) -> Option<&Descriptor<DescriptorPublicKey>> {
+ let did = self.keychain_to_descriptor_id.get(keychain)?;
+ self.descriptors.get(did)
+ }
+
+ /// Get the lookahead setting.
+ ///
+ /// Refer to [`new`] for more information on the `lookahead`.
+ ///
+ /// [`new`]: Self::new
+ pub fn lookahead(&self) -> u32 {
+ self.lookahead
+ }
+
+ /// Store lookahead scripts until `target_index` (inclusive).
+ ///
+ /// This does not change the global `lookahead` setting.
+ pub fn lookahead_to_target(&mut self, keychain: &K, target_index: u32) {
+ if let Some((next_index, _)) = self.next_index(keychain) {
+ let temp_lookahead = (target_index + 1)
+ .checked_sub(next_index)
+ .filter(|&index| index > 0);
+
+ if let Some(temp_lookahead) = temp_lookahead {
+ self.replenish_inner_index_keychain(keychain, temp_lookahead);
+ }
+ }
+ }
+
+ fn replenish_inner_index_did(&mut self, did: DescriptorId, lookahead: u32) {
+ if let Some(keychain) = self.descriptor_id_to_keychain.get(&did).cloned() {
+ self.replenish_inner_index(did, &keychain, lookahead);
+ }
+ }
+
+ fn replenish_inner_index_keychain(&mut self, keychain: &K, lookahead: u32) {
+ if let Some(did) = self.keychain_to_descriptor_id.get(keychain) {
+ self.replenish_inner_index(*did, keychain, lookahead);
+ }
+ }
+
+ /// Syncs the state of the inner spk index after changes to a keychain
+ fn replenish_inner_index(&mut self, did: DescriptorId, keychain: &K, lookahead: u32) {
+ let descriptor = self.descriptors.get(&did).expect("invariant");
+ let next_store_index = self
+ .inner
+ .all_spks()
+ .range(&(keychain.clone(), u32::MIN)..=&(keychain.clone(), u32::MAX))
+ .last()
+ .map_or(0, |((_, index), _)| *index + 1);
+ let next_reveal_index = self.last_revealed.get(&did).map_or(0, |v| *v + 1);
+ for (new_index, new_spk) in
+ SpkIterator::new_with_range(descriptor, next_store_index..next_reveal_index + lookahead)
+ {
+ let _inserted = self
+ .inner
+ .insert_spk((keychain.clone(), new_index), new_spk);
+ debug_assert!(_inserted, "replenish lookahead: must not have existing spk: keychain={:?}, lookahead={}, next_store_index={}, next_reveal_index={}", keychain, lookahead, next_store_index, next_reveal_index);
+ }
+ }
+
+ /// Get an unbounded spk iterator over a given `keychain`. Returns `None` if the provided
+ /// keychain doesn't exist
+ pub fn unbounded_spk_iter(
+ &self,
+ keychain: &K,
+ ) -> Option<SpkIterator<Descriptor<DescriptorPublicKey>>> {
+ let descriptor = self.get_descriptor(keychain)?.clone();
+ Some(SpkIterator::new(descriptor))
+ }
+
+ /// Get unbounded spk iterators for all keychains.
+ pub fn all_unbounded_spk_iters(
+ &self,
+ ) -> BTreeMap<K, SpkIterator<Descriptor<DescriptorPublicKey>>> {
+ self.keychain_to_descriptor_id
+ .iter()
+ .map(|(k, did)| {
+ (
+ k.clone(),
+ SpkIterator::new(self.descriptors.get(did).expect("invariant").clone()),
+ )
+ })
+ .collect()
+ }
+
+ /// Iterate over revealed spks of keychains in `range`
+ pub fn revealed_spks(
+ &self,
+ range: impl RangeBounds<K>,
+ ) -> impl Iterator<Item = KeychainIndexed<K, &Script>> {
+ let start = range.start_bound();
+ let end = range.end_bound();
+ let mut iter_last_revealed = self
+ .keychain_to_descriptor_id
+ .range((start, end))
+ .map(|(k, did)| (k, self.last_revealed.get(did).cloned()));
+ let mut iter_spks = self
+ .inner
+ .all_spks()
+ .range(self.map_to_inner_bounds((start, end)));
+ let mut current_keychain = iter_last_revealed.next();
+ // The reason we need a tricky algorithm is because of the "lookahead" feature which means
+ // that some of the spks in the SpkTxoutIndex will not have been revealed yet. So we need to
+ // filter out those spks that are above the last_revealed for that keychain. To do this we
+ // iterate through the last_revealed for each keychain and the spks for each keychain in
+ // tandem. This minimizes BTreeMap queries.
+ core::iter::from_fn(move || loop {
+ let ((keychain, index), spk) = iter_spks.next()?;
+ // We need to find the last revealed that matches the current spk we are considering so
+ // we skip ahead.
+ while current_keychain?.0 < keychain {
+ current_keychain = iter_last_revealed.next();
+ }
+ let (current_keychain, last_revealed) = current_keychain?;
+
+ if current_keychain == keychain && Some(*index) <= last_revealed {
+ break Some(((keychain.clone(), *index), spk.as_script()));
+ }
+ })
+ }
+
+ /// Iterate over revealed spks of the given `keychain` with ascending indices.
+ ///
+ /// This is a double ended iterator so you can easily reverse it to get an iterator where
+ /// the script pubkeys that were most recently revealed are first.
+ pub fn revealed_keychain_spks<'a>(
+ &'a self,
+ keychain: &'a K,
+ ) -> impl DoubleEndedIterator<Item = Indexed<&Script>> + 'a {
+ let end = self
+ .last_revealed_index(keychain)
+ .map(|v| v + 1)
+ .unwrap_or(0);
+ self.inner
+ .all_spks()
+ .range((keychain.clone(), 0)..(keychain.clone(), end))
+ .map(|((_, index), spk)| (*index, spk.as_script()))
+ }
+
+ /// Iterate over revealed, but unused, spks of all keychains.
+ pub fn unused_spks(
+ &self,
+ ) -> impl DoubleEndedIterator<Item = KeychainIndexed<K, &Script>> + Clone {
+ self.keychain_to_descriptor_id.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`.
+ /// Returns an empty iterator if the provided keychain doesn't exist.
+ pub fn unused_keychain_spks(
+ &self,
+ keychain: &K,
+ ) -> impl DoubleEndedIterator<Item = Indexed<&Script>> + Clone {
+ let end = match self.keychain_to_descriptor_id.get(keychain) {
+ Some(did) => self.last_revealed.get(did).map(|v| *v + 1).unwrap_or(0),
+ None => 0,
+ };
+
+ self.inner
+ .unused_spks((keychain.clone(), 0)..(keychain.clone(), end))
+ .map(|((_, i), spk)| (*i, spk))
+ }
+
+ /// Get the next derivation index for `keychain`. The next index is the index after the last revealed
+ /// derivation index.
+ ///
+ /// The second field in the returned tuple represents whether the next derivation index is new.
+ /// There are two scenarios where the next derivation index is reused (not new):
+ ///
+ /// 1. The keychain's descriptor has no wildcard, and a script has already been revealed.
+ /// 2. The number of revealed scripts has already reached 2^31 (refer to BIP-32).
+ ///
+ /// Not checking the second field of the tuple may result in address reuse.
+ ///
+ /// Returns None if the provided `keychain` doesn't exist.
+ pub fn next_index(&self, keychain: &K) -> Option<(u32, bool)> {
+ let did = self.keychain_to_descriptor_id.get(keychain)?;
+ let last_index = self.last_revealed.get(did).cloned();
+ let descriptor = self.descriptors.get(did).expect("invariant");
+
+ // we can only get the next index if the wildcard exists.
+ let has_wildcard = descriptor.has_wildcard();
+
+ Some(match last_index {
+ // if there is no index, next_index is always 0.
+ None => (0, true),
+ // descriptors without wildcards can only have one index.
+ Some(_) if !has_wildcard => (0, false),
+ // derivation index must be < 2^31 (BIP-32).
+ Some(index) if index > BIP32_MAX_INDEX => {
+ unreachable!("index is out of bounds")
+ }
+ Some(index) if index == BIP32_MAX_INDEX => (index, false),
+ // get the next derivation index.
+ Some(index) => (index + 1, true),
+ })
+ }
+
+ /// Get the last derivation index that is revealed for each keychain.
+ ///
+ /// Keychains with no revealed indices will not be included in the returned [`BTreeMap`].
+ pub fn last_revealed_indices(&self) -> BTreeMap<K, u32> {
+ self.last_revealed
+ .iter()
+ .filter_map(|(desc_id, index)| {
+ let keychain = self.descriptor_id_to_keychain.get(desc_id)?;
+ Some((keychain.clone(), *index))
+ })
+ .collect()
+ }
+
+ /// Get the last derivation index revealed for `keychain`. Returns None if the keychain doesn't
+ /// exist, or if the keychain doesn't have any revealed scripts.
+ pub fn last_revealed_index(&self, keychain: &K) -> Option<u32> {
+ let descriptor_id = self.keychain_to_descriptor_id.get(keychain)?;
+ self.last_revealed.get(descriptor_id).cloned()
+ }
+
+ /// Convenience method to call [`Self::reveal_to_target`] on multiple keychains.
+ pub fn reveal_to_target_multi(&mut self, keychains: &BTreeMap<K, u32>) -> ChangeSet<K> {
+ let mut changeset = ChangeSet::default();
+
+ for (keychain, &index) in keychains {
+ if let Some((_, new_changeset)) = self.reveal_to_target(keychain, index) {
+ changeset.append(new_changeset);
+ }
+ }
+
+ changeset
+ }
+
+ /// Reveals script pubkeys of the `keychain`'s descriptor **up to and including** the
+ /// `target_index`.
+ ///
+ /// If the `target_index` cannot be reached (due to the descriptor having no wildcard and/or
+ /// the `target_index` is in the hardened index range), this method will make a best-effort and
+ /// reveal up to the last possible index.
+ ///
+ /// This returns list of newly revealed indices (alongside their scripts) and a
+ /// [`ChangeSet`], which reports updates to the latest revealed index. If no new script
+ /// pubkeys are revealed, then both of these will be empty.
+ ///
+ /// Returns None if the provided `keychain` doesn't exist.
+ #[must_use]
+ pub fn reveal_to_target(
+ &mut self,
+ keychain: &K,
+ target_index: u32,
+ ) -> Option<(Vec<Indexed<ScriptBuf>>, ChangeSet<K>)> {
+ let mut changeset = ChangeSet::default();
+ let mut spks: Vec<Indexed<ScriptBuf>> = vec![];
+ while let Some((i, new)) = self.next_index(keychain) {
+ if !new || i > target_index {
+ break;
+ }
+ match self.reveal_next_spk(keychain) {
+ Some(((i, spk), change)) => {
+ spks.push((i, spk));
+ changeset.append(change);
+ }
+ None => break,
+ }
+ }
+
+ Some((spks, changeset))
+ }
+
+ /// Attempts to reveal the next script pubkey for `keychain`.
+ ///
+ /// Returns the derivation index of the revealed script pubkey, the revealed script pubkey and a
+ /// [`ChangeSet`] which represents changes in the last revealed index (if any).
+ /// Returns None if the provided keychain doesn't exist.
+ ///
+ /// When a new script cannot be revealed, we return the last revealed script and an empty
+ /// [`ChangeSet`]. There are two scenarios when a new script pubkey cannot be derived:
+ ///
+ /// 1. The descriptor has no wildcard and already has one script revealed.
+ /// 2. The descriptor has already revealed scripts up to the numeric bound.
+ /// 3. There is no descriptor associated with the given keychain.
+ pub fn reveal_next_spk(&mut self, keychain: &K) -> Option<(Indexed<ScriptBuf>, ChangeSet<K>)> {
+ let (next_index, new) = self.next_index(keychain)?;
+ let mut changeset = ChangeSet::default();
+
+ if new {
+ let did = self.keychain_to_descriptor_id.get(keychain)?;
+ self.last_revealed.insert(*did, next_index);
+ changeset.last_revealed.insert(*did, next_index);
+ self.replenish_inner_index(*did, keychain, self.lookahead);
+ }
+ let script = self
+ .inner
+ .spk_at_index(&(keychain.clone(), next_index))
+ .expect("we just inserted it");
+ Some(((next_index, script.into()), changeset))
+ }
+
+ /// Gets the next unused script pubkey in the keychain. I.e., the script pubkey with the lowest
+ /// index that has not been used yet.
+ ///
+ /// This will derive and reveal a new script pubkey if no more unused script pubkeys exist.
+ ///
+ /// If the descriptor has no wildcard and already has a used script pubkey or if a descriptor
+ /// has used all scripts up to the derivation bounds, then the last derived script pubkey will be
+ /// returned.
+ ///
+ /// Returns `None` if there are no script pubkeys that have been used and no new script pubkey
+ /// could be revealed (see [`reveal_next_spk`] for when this happens).
+ ///
+ /// [`reveal_next_spk`]: Self::reveal_next_spk
+ pub fn next_unused_spk(&mut self, keychain: &K) -> Option<(Indexed<ScriptBuf>, ChangeSet<K>)> {
+ let next_unused = self
+ .unused_keychain_spks(keychain)
+ .next()
+ .map(|(i, spk)| ((i, spk.to_owned()), ChangeSet::default()));
+
+ next_unused.or_else(|| self.reveal_next_spk(keychain))
+ }
+
+ /// Iterate over all [`OutPoint`]s that have `TxOut`s with script pubkeys derived from
+ /// `keychain`.
+ pub fn keychain_outpoints<'a>(
+ &'a self,
+ keychain: &'a K,
+ ) -> impl DoubleEndedIterator<Item = Indexed<OutPoint>> + 'a {
+ self.keychain_outpoints_in_range(keychain..=keychain)
+ .map(|((_, i), op)| (i, op))
+ }
+
+ /// Iterate over [`OutPoint`]s that have script pubkeys derived from keychains in `range`.
+ pub fn keychain_outpoints_in_range<'a>(
+ &'a self,
+ range: impl RangeBounds<K> + 'a,
+ ) -> impl DoubleEndedIterator<Item = KeychainIndexed<K, OutPoint>> + 'a {
+ self.inner
+ .outputs_in_range(self.map_to_inner_bounds(range))
+ .map(|((k, i), op)| ((k.clone(), *i), op))
+ }
+
+ fn map_to_inner_bounds(&self, bound: impl RangeBounds<K>) -> impl RangeBounds<(K, u32)> {
+ let start = match bound.start_bound() {
+ Bound::Included(keychain) => Bound::Included((keychain.clone(), u32::MIN)),
+ Bound::Excluded(keychain) => Bound::Excluded((keychain.clone(), u32::MAX)),
+ Bound::Unbounded => Bound::Unbounded,
+ };
+ let end = match bound.end_bound() {
+ Bound::Included(keychain) => Bound::Included((keychain.clone(), u32::MAX)),
+ Bound::Excluded(keychain) => Bound::Excluded((keychain.clone(), u32::MIN)),
+ Bound::Unbounded => Bound::Unbounded,
+ };
+
+ (start, end)
+ }
+
+ /// 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<u32> {
+ self.keychain_outpoints(keychain).last().map(|(i, _)| i)
+ }
+
+ /// Returns the highest derivation index of each keychain that [`KeychainTxOutIndex`] has found
+ /// a [`TxOut`] with it's script pubkey.
+ pub fn last_used_indices(&self) -> BTreeMap<K, u32> {
+ self.keychain_to_descriptor_id
+ .iter()
+ .filter_map(|(keychain, _)| {
+ self.last_used_index(keychain)
+ .map(|index| (keychain.clone(), index))
+ })
+ .collect()
+ }
+
+ /// Applies the `ChangeSet<K>` to the [`KeychainTxOutIndex<K>`]
+ ///
+ /// Keychains added by the `keychains_added` field of `ChangeSet<K>` respect the one-to-one
+ /// keychain <-> descriptor invariant by silently ignoring attempts to violate it (but will
+ /// panic if `debug_assertions` are enabled).
+ pub fn apply_changeset(&mut self, changeset: ChangeSet<K>) {
+ let ChangeSet {
+ keychains_added,
+ last_revealed,
+ } = changeset;
+ for (keychain, descriptor) in keychains_added {
+ let _ignore_invariant_violation = self.insert_descriptor(keychain, descriptor);
+ }
+
+ for (&desc_id, &index) in &last_revealed {
+ let v = self.last_revealed.entry(desc_id).or_default();
+ *v = index.max(*v);
+ }
+
+ for did in last_revealed.keys() {
+ self.replenish_inner_index_did(*did, self.lookahead);
+ }
+ }
+}
+
+#[derive(Clone, Debug, PartialEq)]
+/// Error returned from [`KeychainTxOutIndex::insert_descriptor`]
+pub enum InsertDescriptorError<K> {
+ /// The descriptor has already been assigned to a keychain so you can't assign it to another
+ DescriptorAlreadyAssigned {
+ /// The descriptor you have attempted to reassign
+ descriptor: Descriptor<DescriptorPublicKey>,
+ /// The keychain that the descriptor is already assigned to
+ existing_assignment: K,
+ },
+ /// The keychain is already assigned to a descriptor so you can't reassign it
+ KeychainAlreadyAssigned {
+ /// The keychain that you have attempted to reassign
+ keychain: K,
+ /// The descriptor that the keychain is already assigned to
+ existing_assignment: Descriptor<DescriptorPublicKey>,
+ },
+}
+
+impl<K: core::fmt::Debug> core::fmt::Display for InsertDescriptorError<K> {
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+ match self {
+ InsertDescriptorError::DescriptorAlreadyAssigned {
+ existing_assignment: existing,
+ descriptor,
+ } => {
+ write!(
+ f,
+ "attempt to re-assign descriptor {descriptor:?} already assigned to {existing:?}"
+ )
+ }
+ InsertDescriptorError::KeychainAlreadyAssigned {
+ existing_assignment: existing,
+ keychain,
+ } => {
+ write!(
+ f,
+ "attempt to re-assign keychain {keychain:?} already assigned to {existing:?}"
+ )
+ }
+ }
+ }
+}
+
+#[cfg(feature = "std")]
+impl<K: core::fmt::Debug> std::error::Error for InsertDescriptorError<K> {}
+
+/// Represents updates to the derivation index of a [`KeychainTxOutIndex`].
+/// It maps each keychain `K` to a descriptor and its last revealed index.
+///
+/// It can be applied to [`KeychainTxOutIndex`] with [`apply_changeset`].
+///
+/// The `last_revealed` field is monotone in that [`append`] will never decrease it.
+/// `keychains_added` is *not* monotone, once it is set any attempt to change it is subject to the
+/// same *one-to-one* keychain <-> descriptor mapping invariant as [`KeychainTxOutIndex`] itself.
+///
+/// [`apply_changeset`]: KeychainTxOutIndex::apply_changeset
+/// [`append`]: Self::append
+#[derive(Clone, Debug, PartialEq)]
+#[cfg_attr(
+ feature = "serde",
+ derive(serde::Deserialize, serde::Serialize),
+ serde(
+ crate = "serde_crate",
+ bound(
+ deserialize = "K: Ord + serde::Deserialize<'de>",
+ serialize = "K: Ord + serde::Serialize"
+ )
+ )
+)]
+#[must_use]
+pub struct ChangeSet<K> {
+ /// Contains the keychains that have been added and their respective descriptor
+ pub keychains_added: BTreeMap<K, Descriptor<DescriptorPublicKey>>,
+ /// Contains for each descriptor_id the last revealed index of derivation
+ pub last_revealed: BTreeMap<DescriptorId, u32>,
+}
+
+impl<K: Ord> Append for ChangeSet<K> {
+ /// Merge another [`ChangeSet<K>`] into self.
+ ///
+ /// For the `keychains_added` field this method respects the invariants of
+ /// [`insert_descriptor`]. `last_revealed` always becomes the larger of the two.
+ ///
+ /// [`insert_descriptor`]: KeychainTxOutIndex::insert_descriptor
+ fn append(&mut self, other: Self) {
+ for (new_keychain, new_descriptor) in other.keychains_added {
+ // enforce 1-to-1 invariance
+ if !self.keychains_added.contains_key(&new_keychain)
+ // FIXME: very inefficient
+ && self
+ .keychains_added
+ .values()
+ .all(|descriptor| descriptor != &new_descriptor)
+ {
+ self.keychains_added.insert(new_keychain, new_descriptor);
+ }
+ }
+
+ // for `last_revealed`, entries of `other` will take precedence ONLY if it is greater than
+ // what was originally in `self`.
+ for (desc_id, index) in other.last_revealed {
+ use crate::collections::btree_map::Entry;
+ match self.last_revealed.entry(desc_id) {
+ Entry::Vacant(entry) => {
+ entry.insert(index);
+ }
+ Entry::Occupied(mut entry) => {
+ if *entry.get() < index {
+ entry.insert(index);
+ }
+ }
+ }
+ }
+ }
+
+ /// Returns whether the changeset are empty.
+ fn is_empty(&self) -> bool {
+ self.last_revealed.is_empty() && self.keychains_added.is_empty()
+ }
+}
+
+impl<K> Default for ChangeSet<K> {
+ fn default() -> Self {
+ Self {
+ last_revealed: BTreeMap::default(),
+ keychains_added: BTreeMap::default(),
+ }
+ }
+}
+
+#[derive(Clone, Debug, Eq, PartialEq)]
+/// The keychain doesn't exist. Most likley hasn't been inserted with [`KeychainTxOutIndex::insert_descriptor`].
+pub struct NoSuchKeychain<K>(K);
+
+impl<K: Debug> core::fmt::Display for NoSuchKeychain<K> {
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+ write!(f, "no such keychain {:?} exists", &self.0)
+ }
+}
--- /dev/null
+//! [`SpkTxOutIndex`] is an index storing [`TxOut`]s that have a script pubkey that matches those in a list.
+
+use core::ops::RangeBounds;
+
+use crate::{
+ collections::{hash_map::Entry, BTreeMap, BTreeSet, HashMap},
+ Indexer,
+};
+use bitcoin::{Amount, OutPoint, Script, ScriptBuf, SignedAmount, Transaction, TxOut, Txid};
+
+/// An index storing [`TxOut`]s that have a script pubkey that matches those in a list.
+///
+/// The basic idea is that you insert script pubkeys you care about into the index with
+/// [`insert_spk`] and then when you call [`Indexer::index_tx`] or [`Indexer::index_txout`], the
+/// index will look at any txouts you pass in and store and index any txouts matching one of its
+/// script pubkeys.
+///
+/// Each script pubkey is associated with an application-defined index script index `I`, which must be
+/// [`Ord`]. Usually, this is used to associate the derivation index of the script pubkey or even a
+/// combination of `(keychain, derivation_index)`.
+///
+/// Note there is no harm in scanning transactions that disappear from the blockchain or were never
+/// in there in the first place. `SpkTxOutIndex` is intentionally *monotone* -- you cannot delete or
+/// modify txouts that have been indexed. To find out which txouts from the index are actually in the
+/// chain or unspent, you must use other sources of information like a [`TxGraph`].
+///
+/// [`TxOut`]: bitcoin::TxOut
+/// [`insert_spk`]: Self::insert_spk
+/// [`Ord`]: core::cmp::Ord
+/// [`TxGraph`]: crate::tx_graph::TxGraph
+#[derive(Clone, Debug)]
+pub struct SpkTxOutIndex<I> {
+ /// script pubkeys ordered by index
+ spks: BTreeMap<I, ScriptBuf>,
+ /// A reverse lookup from spk to spk index
+ spk_indices: HashMap<ScriptBuf, I>,
+ /// The set of unused indexes.
+ unused: BTreeSet<I>,
+ /// Lookup index and txout by outpoint.
+ txouts: BTreeMap<OutPoint, (I, TxOut)>,
+ /// Lookup from spk index to outpoints that had that spk
+ spk_txouts: BTreeSet<(I, OutPoint)>,
+}
+
+impl<I> Default for SpkTxOutIndex<I> {
+ fn default() -> Self {
+ Self {
+ txouts: Default::default(),
+ spks: Default::default(),
+ spk_indices: Default::default(),
+ spk_txouts: Default::default(),
+ unused: Default::default(),
+ }
+ }
+}
+
+impl<I: Clone + Ord + core::fmt::Debug> Indexer for SpkTxOutIndex<I> {
+ type ChangeSet = ();
+
+ fn index_txout(&mut self, outpoint: OutPoint, txout: &TxOut) -> Self::ChangeSet {
+ self.scan_txout(outpoint, txout);
+ Default::default()
+ }
+
+ fn index_tx(&mut self, tx: &Transaction) -> Self::ChangeSet {
+ self.scan(tx);
+ Default::default()
+ }
+
+ fn initial_changeset(&self) -> Self::ChangeSet {}
+
+ fn apply_changeset(&mut self, _changeset: Self::ChangeSet) {
+ // This applies nothing.
+ }
+
+ fn is_tx_relevant(&self, tx: &Transaction) -> bool {
+ self.is_relevant(tx)
+ }
+}
+
+impl<I: Clone + Ord + core::fmt::Debug> SpkTxOutIndex<I> {
+ /// Scans a transaction's outputs for matching script pubkeys.
+ ///
+ /// Typically, this is used in two situations:
+ ///
+ /// 1. After loading transaction data from the disk, you may scan over all the txouts to restore all
+ /// your txouts.
+ /// 2. When getting new data from the chain, you usually scan it before incorporating it into your chain state.
+ pub fn scan(&mut self, tx: &Transaction) -> BTreeSet<I> {
+ let mut scanned_indices = BTreeSet::new();
+ let txid = tx.compute_txid();
+ for (i, txout) in tx.output.iter().enumerate() {
+ let op = OutPoint::new(txid, i as u32);
+ if let Some(spk_i) = self.scan_txout(op, txout) {
+ scanned_indices.insert(spk_i.clone());
+ }
+ }
+
+ scanned_indices
+ }
+
+ /// Scan a single `TxOut` for a matching script pubkey and returns the index that matches the
+ /// script pubkey (if any).
+ pub fn scan_txout(&mut self, op: OutPoint, txout: &TxOut) -> Option<&I> {
+ let spk_i = self.spk_indices.get(&txout.script_pubkey);
+ if let Some(spk_i) = spk_i {
+ self.txouts.insert(op, (spk_i.clone(), txout.clone()));
+ self.spk_txouts.insert((spk_i.clone(), op));
+ self.unused.remove(spk_i);
+ }
+ spk_i
+ }
+
+ /// Get a reference to the set of indexed outpoints.
+ pub fn outpoints(&self) -> &BTreeSet<(I, OutPoint)> {
+ &self.spk_txouts
+ }
+
+ /// Iterate over all known txouts that spend to tracked script pubkeys.
+ pub fn txouts(
+ &self,
+ ) -> impl DoubleEndedIterator<Item = (&I, OutPoint, &TxOut)> + ExactSizeIterator {
+ self.txouts
+ .iter()
+ .map(|(op, (index, txout))| (index, *op, txout))
+ }
+
+ /// Finds all txouts on a transaction that has previously been scanned and indexed.
+ pub fn txouts_in_tx(
+ &self,
+ txid: Txid,
+ ) -> impl DoubleEndedIterator<Item = (&I, OutPoint, &TxOut)> {
+ self.txouts
+ .range(OutPoint::new(txid, u32::MIN)..=OutPoint::new(txid, u32::MAX))
+ .map(|(op, (index, txout))| (index, *op, txout))
+ }
+
+ /// Iterates over all the outputs with script pubkeys in an index range.
+ pub fn outputs_in_range(
+ &self,
+ range: impl RangeBounds<I>,
+ ) -> impl DoubleEndedIterator<Item = (&I, OutPoint)> {
+ use bitcoin::hashes::Hash;
+ use core::ops::Bound::*;
+ let min_op = OutPoint {
+ txid: Txid::all_zeros(),
+ vout: u32::MIN,
+ };
+ let max_op = OutPoint {
+ txid: Txid::from_byte_array([0xff; Txid::LEN]),
+ vout: u32::MAX,
+ };
+
+ let start = match range.start_bound() {
+ Included(index) => Included((index.clone(), min_op)),
+ Excluded(index) => Excluded((index.clone(), max_op)),
+ Unbounded => Unbounded,
+ };
+
+ let end = match range.end_bound() {
+ Included(index) => Included((index.clone(), max_op)),
+ Excluded(index) => Excluded((index.clone(), min_op)),
+ Unbounded => Unbounded,
+ };
+
+ self.spk_txouts.range((start, end)).map(|(i, op)| (i, *op))
+ }
+
+ /// Returns the txout and script pubkey index of the `TxOut` at `OutPoint`.
+ ///
+ /// Returns `None` if the `TxOut` hasn't been scanned or if nothing matching was found there.
+ pub fn txout(&self, outpoint: OutPoint) -> Option<(&I, &TxOut)> {
+ self.txouts.get(&outpoint).map(|v| (&v.0, &v.1))
+ }
+
+ /// Returns the script that has been inserted at the `index`.
+ ///
+ /// If that index hasn't been inserted yet, it will return `None`.
+ pub fn spk_at_index(&self, index: &I) -> Option<&Script> {
+ self.spks.get(index).map(|s| s.as_script())
+ }
+
+ /// The script pubkeys that are being tracked by the index.
+ pub fn all_spks(&self) -> &BTreeMap<I, ScriptBuf> {
+ &self.spks
+ }
+
+ /// Adds a script pubkey to scan for. Returns `false` and does nothing if spk already exists in the map
+ ///
+ /// the index will look for outputs spending to this spk whenever it scans new data.
+ pub fn insert_spk(&mut self, index: I, spk: ScriptBuf) -> bool {
+ match self.spk_indices.entry(spk.clone()) {
+ Entry::Vacant(value) => {
+ value.insert(index.clone());
+ self.spks.insert(index.clone(), spk);
+ self.unused.insert(index);
+ true
+ }
+ Entry::Occupied(_) => false,
+ }
+ }
+
+ /// Iterates over all unused script pubkeys in an index range.
+ ///
+ /// Here, "unused" means that after the script pubkey was stored in the index, the index has
+ /// never scanned a transaction output with it.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// # use bdk_chain::SpkTxOutIndex;
+ ///
+ /// // imagine our spks are indexed like (keychain, derivation_index).
+ /// let txout_index = SpkTxOutIndex::<(u32, u32)>::default();
+ /// let all_unused_spks = txout_index.unused_spks(..);
+ /// let change_index = 1;
+ /// let unused_change_spks =
+ /// 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)> + Clone
+ where
+ R: RangeBounds<I>,
+ {
+ self.unused
+ .range(range)
+ .map(move |index| (index, self.spk_at_index(index).expect("must exist")))
+ }
+
+ /// Returns whether the script pubkey at `index` has been used or not.
+ ///
+ /// Here, "unused" means that after the script pubkey was stored in the index, the index has
+ /// never scanned a transaction output with it.
+ pub fn is_used(&self, index: &I) -> bool {
+ !self.unused.contains(index)
+ }
+
+ /// Marks the script pubkey at `index` as used even though it hasn't seen an output spending to it.
+ /// This only affects when the `index` had already been added to `self` 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 the `index` used
+ /// until you call [`unmark_used`].
+ ///
+ /// [`unmark_used`]: Self::unmark_used
+ pub fn mark_used(&mut self, index: &I) -> bool {
+ self.unused.remove(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, index: &I) -> bool {
+ // we cannot set the index as unused when it does not exist
+ if !self.spks.contains_key(index) {
+ return false;
+ }
+ // we cannot set the index as unused when txouts are indexed under it
+ if self.outputs_in_range(index..=index).next().is_some() {
+ return false;
+ }
+ self.unused.insert(index.clone())
+ }
+
+ /// Returns the index associated with the script pubkey.
+ pub fn index_of_spk(&self, script: &Script) -> Option<&I> {
+ self.spk_indices.get(script)
+ }
+
+ /// Computes the total value transfer effect `tx` has on the script pubkeys in `range`. Value is
+ /// *sent* when a script pubkey in the `range` is on an input and *received* when it is on an
+ /// output. For `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.
+ pub fn sent_and_received(
+ &self,
+ tx: &Transaction,
+ range: impl RangeBounds<I>,
+ ) -> (Amount, Amount) {
+ let mut sent = Amount::ZERO;
+ let mut received = Amount::ZERO;
+
+ for txin in &tx.input {
+ if let Some((index, txout)) = self.txout(txin.previous_output) {
+ if range.contains(index) {
+ sent += txout.value;
+ }
+ }
+ }
+ for txout in &tx.output {
+ if let Some(index) = self.index_of_spk(&txout.script_pubkey) {
+ if range.contains(index) {
+ received += txout.value;
+ }
+ }
+ }
+
+ (sent, received)
+ }
+
+ /// Computes the net value transfer effect of `tx` on the script pubkeys in `range`. Shorthand
+ /// for calling [`sent_and_received`] and subtracting sent from received.
+ ///
+ /// [`sent_and_received`]: Self::sent_and_received
+ pub fn net_value(&self, tx: &Transaction, range: impl RangeBounds<I>) -> SignedAmount {
+ let (sent, received) = self.sent_and_received(tx, range);
+ received.to_signed().expect("valid `SignedAmount`")
+ - sent.to_signed().expect("valid `SignedAmount`")
+ }
+
+ /// Whether any of the inputs of this transaction spend a txout tracked or whether any output
+ /// matches one of our script pubkeys.
+ ///
+ /// It is easily possible to misuse this method and get false negatives by calling it before you
+ /// have scanned the `TxOut`s the transaction is spending. For example, if you want to filter out
+ /// all the transactions in a block that are irrelevant, you **must first scan all the
+ /// transactions in the block** and only then use this method.
+ pub fn is_relevant(&self, tx: &Transaction) -> bool {
+ let input_matches = tx
+ .input
+ .iter()
+ .any(|input| self.txouts.contains_key(&input.previous_output));
+ let output_matches = tx
+ .output
+ .iter()
+ .any(|output| self.spk_indices.contains_key(&output.script_pubkey));
+ input_matches || output_matches
+ }
+}
+++ /dev/null
-//! Module for keychain related structures.
-//!
-//! A keychain here is a set of application-defined indexes for a miniscript descriptor where we can
-//! derive script pubkeys at a particular derivation index. The application's index is simply
-//! anything that implements `Ord`.
-//!
-//! [`KeychainTxOutIndex`] indexes script pubkeys of keychains and scans in relevant outpoints (that
-//! has a `txout` containing an indexed script pubkey). Internally, this uses [`SpkTxOutIndex`], but
-//! also maintains "revealed" and "lookahead" index counts per keychain.
-//!
-//! [`SpkTxOutIndex`]: crate::SpkTxOutIndex
-
-#[cfg(feature = "miniscript")]
-mod txout_index;
-use bitcoin::{Amount, ScriptBuf};
-#[cfg(feature = "miniscript")]
-pub use txout_index::*;
-
-/// Balance, differentiated into various categories.
-#[derive(Debug, PartialEq, Eq, Clone, Default)]
-#[cfg_attr(
- feature = "serde",
- derive(serde::Deserialize, serde::Serialize),
- serde(crate = "serde_crate",)
-)]
-pub struct Balance {
- /// All coinbase outputs not yet matured
- pub immature: Amount,
- /// Unconfirmed UTXOs generated by a wallet tx
- pub trusted_pending: Amount,
- /// Unconfirmed UTXOs received from an external wallet
- pub untrusted_pending: Amount,
- /// Confirmed and immediately spendable balance
- pub confirmed: Amount,
-}
-
-impl Balance {
- /// Get sum of trusted_pending and confirmed coins.
- ///
- /// This is the balance you can spend right now that shouldn't get cancelled via another party
- /// double spending it.
- pub fn trusted_spendable(&self) -> Amount {
- self.confirmed + self.trusted_pending
- }
-
- /// Get the whole balance visible to the wallet.
- pub fn total(&self) -> Amount {
- self.confirmed + self.trusted_pending + self.untrusted_pending + self.immature
- }
-}
-
-/// A tuple of keychain index and `T` representing the indexed value.
-pub type Indexed<T> = (u32, T);
-/// A tuple of keychain `K`, derivation index (`u32`) and a `T` associated with them.
-pub type KeychainIndexed<K, T> = ((K, u32), T);
-
-impl core::fmt::Display for Balance {
- fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
- write!(
- f,
- "{{ immature: {}, trusted_pending: {}, untrusted_pending: {}, confirmed: {} }}",
- self.immature, self.trusted_pending, self.untrusted_pending, self.confirmed
- )
- }
-}
-
-impl core::ops::Add for Balance {
- type Output = Self;
-
- fn add(self, other: Self) -> Self {
- Self {
- immature: self.immature + other.immature,
- trusted_pending: self.trusted_pending + other.trusted_pending,
- untrusted_pending: self.untrusted_pending + other.untrusted_pending,
- confirmed: self.confirmed + other.confirmed,
- }
- }
-}
+++ /dev/null
-use crate::{
- collections::*,
- indexed_tx_graph::Indexer,
- miniscript::{Descriptor, DescriptorPublicKey},
- spk_iter::BIP32_MAX_INDEX,
- DescriptorExt, DescriptorId, SpkIterator, SpkTxOutIndex,
-};
-use alloc::{borrow::ToOwned, vec::Vec};
-use bitcoin::{Amount, OutPoint, Script, SignedAmount, Transaction, TxOut, Txid};
-use core::{
- fmt::Debug,
- ops::{Bound, RangeBounds},
-};
-
-use super::*;
-use crate::Append;
-
-/// The default lookahead for a [`KeychainTxOutIndex`]
-pub const DEFAULT_LOOKAHEAD: u32 = 25;
-
-/// [`KeychainTxOutIndex`] controls how script pubkeys are revealed for multiple keychains, and
-/// indexes [`TxOut`]s with them.
-///
-/// 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`.
-///
-/// There is a strict 1-to-1 relationship between descriptors and keychains. Each keychain has one
-/// and only one descriptor and each descriptor has one and only one keychain. The
-/// [`insert_descriptor`] method will return an error if you try and violate this invariant. This
-/// rule is a proxy for a stronger rule: no two descriptors should produce the same script pubkey.
-/// Having two descriptors produce the same script pubkey should cause whichever keychain derives
-/// the script pubkey first to be the effective owner of it but you should not rely on this
-/// behaviour. âš It is up you, the developer, not to violate this invariant.
-///
-/// # Revealed script pubkeys
-///
-/// 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. See
-/// [`DEFAULT_LOOKAHEAD`] for the value used in the `Default` implementation. 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 because it doesn't
-/// require holding a reference to the index.
-///
-/// 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 or add keychains will return [`ChangeSet`] to report
-/// these changes. This should be persisted for future recovery.
-///
-/// ## Synopsis
-///
-/// ```
-/// use bdk_chain::keychain::KeychainTxOutIndex;
-/// # use bdk_chain::{ miniscript::{Descriptor, DescriptorPublicKey} };
-/// # use core::str::FromStr;
-///
-/// // imagine our service has internal and external addresses but also addresses for users
-/// #[derive(Clone, Debug, PartialEq, Eq, Ord, PartialOrd)]
-/// enum MyKeychain {
-/// External,
-/// Internal,
-/// MyAppUser {
-/// user_id: u32
-/// }
-/// }
-///
-/// let mut txout_index = KeychainTxOutIndex::<MyKeychain>::default();
-///
-/// # let secp = bdk_chain::bitcoin::secp256k1::Secp256k1::signing_only();
-/// # let (external_descriptor,_) = Descriptor::<DescriptorPublicKey>::parse_descriptor(&secp, "tr([73c5da0a/86'/0'/0']xprv9xgqHN7yz9MwCkxsBPN5qetuNdQSUttZNKw1dcYTV4mkaAFiBVGQziHs3NRSWMkCzvgjEe3n9xV8oYywvM8at9yRqyaZVz6TYYhX98VjsUk/0/*)").unwrap();
-/// # let (internal_descriptor,_) = Descriptor::<DescriptorPublicKey>::parse_descriptor(&secp, "tr([73c5da0a/86'/0'/0']xprv9xgqHN7yz9MwCkxsBPN5qetuNdQSUttZNKw1dcYTV4mkaAFiBVGQziHs3NRSWMkCzvgjEe3n9xV8oYywvM8at9yRqyaZVz6TYYhX98VjsUk/1/*)").unwrap();
-/// # let (descriptor_42, _) = Descriptor::<DescriptorPublicKey>::parse_descriptor(&secp, "tr([73c5da0a/86'/0'/0']xprv9xgqHN7yz9MwCkxsBPN5qetuNdQSUttZNKw1dcYTV4mkaAFiBVGQziHs3NRSWMkCzvgjEe3n9xV8oYywvM8at9yRqyaZVz6TYYhX98VjsUk/2/*)").unwrap();
-/// let _ = txout_index.insert_descriptor(MyKeychain::External, external_descriptor)?;
-/// let _ = txout_index.insert_descriptor(MyKeychain::Internal, internal_descriptor)?;
-/// let _ = txout_index.insert_descriptor(MyKeychain::MyAppUser { user_id: 42 }, descriptor_42)?;
-///
-/// let new_spk_for_user = txout_index.reveal_next_spk(&MyKeychain::MyAppUser{ user_id: 42 });
-/// # Ok::<_, bdk_chain::keychain::InsertDescriptorError<_>>(())
-/// ```
-///
-/// [`Ord`]: core::cmp::Ord
-/// [`SpkTxOutIndex`]: crate::spk_txout_index::SpkTxOutIndex
-/// [`Descriptor`]: crate::miniscript::Descriptor
-/// [`reveal_to_target`]: Self::reveal_to_target
-/// [`reveal_next_spk`]: Self::reveal_next_spk
-/// [`revealed_keychain_spks`]: Self::revealed_keychain_spks
-/// [`revealed_spks`]: Self::revealed_spks
-/// [`index_tx`]: Self::index_tx
-/// [`index_txout`]: Self::index_txout
-/// [`new`]: Self::new
-/// [`unbounded_spk_iter`]: Self::unbounded_spk_iter
-/// [`all_unbounded_spk_iters`]: Self::all_unbounded_spk_iters
-/// [`outpoints`]: Self::outpoints
-/// [`txouts`]: Self::txouts
-/// [`unused_spks`]: Self::unused_spks
-/// [`insert_descriptor`]: Self::insert_descriptor
-#[derive(Clone, Debug)]
-pub struct KeychainTxOutIndex<K> {
- inner: SpkTxOutIndex<(K, u32)>,
- keychain_to_descriptor_id: BTreeMap<K, DescriptorId>,
- descriptor_id_to_keychain: HashMap<DescriptorId, K>,
- descriptors: HashMap<DescriptorId, Descriptor<DescriptorPublicKey>>,
- last_revealed: HashMap<DescriptorId, u32>,
- lookahead: u32,
-}
-
-impl<K> Default for KeychainTxOutIndex<K> {
- fn default() -> Self {
- Self::new(DEFAULT_LOOKAHEAD)
- }
-}
-
-impl<K: Clone + Ord + Debug> Indexer for KeychainTxOutIndex<K> {
- type ChangeSet = ChangeSet<K>;
-
- fn index_txout(&mut self, outpoint: OutPoint, txout: &TxOut) -> Self::ChangeSet {
- let mut changeset = ChangeSet::default();
- if let Some((keychain, index)) = self.inner.scan_txout(outpoint, txout).cloned() {
- let did = self
- .keychain_to_descriptor_id
- .get(&keychain)
- .expect("invariant");
- if self.last_revealed.get(did) < Some(&index) {
- self.last_revealed.insert(*did, index);
- changeset.last_revealed.insert(*did, index);
- self.replenish_inner_index(*did, &keychain, self.lookahead);
- }
- }
- changeset
- }
-
- fn index_tx(&mut self, tx: &bitcoin::Transaction) -> Self::ChangeSet {
- let mut changeset = ChangeSet::<K>::default();
- let txid = tx.compute_txid();
- for (op, txout) in tx.output.iter().enumerate() {
- changeset.append(self.index_txout(OutPoint::new(txid, op as u32), txout));
- }
- changeset
- }
-
- fn initial_changeset(&self) -> Self::ChangeSet {
- ChangeSet {
- keychains_added: self
- .keychains()
- .map(|(k, v)| (k.clone(), v.clone()))
- .collect(),
- last_revealed: self.last_revealed.clone().into_iter().collect(),
- }
- }
-
- fn apply_changeset(&mut self, changeset: Self::ChangeSet) {
- self.apply_changeset(changeset)
- }
-
- fn is_tx_relevant(&self, tx: &bitcoin::Transaction) -> bool {
- self.inner.is_relevant(tx)
- }
-}
-
-impl<K> KeychainTxOutIndex<K> {
- /// Construct a [`KeychainTxOutIndex`] with the given `lookahead`.
- ///
- /// The `lookahead` is the number of script pubkeys to derive and cache from the internal
- /// descriptors over and above the last revealed script index. Without a lookahead the index
- /// will miss outputs you own when processing transactions whose output script pubkeys lie
- /// 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(),
- keychain_to_descriptor_id: Default::default(),
- descriptors: Default::default(),
- descriptor_id_to_keychain: Default::default(),
- last_revealed: Default::default(),
- lookahead,
- }
- }
-}
-
-/// Methods that are *re-exposed* from the internal [`SpkTxOutIndex`].
-impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
- /// 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
- }
-
- /// Get the set of indexed outpoints, corresponding to tracked keychains.
- pub fn outpoints(&self) -> &BTreeSet<KeychainIndexed<K, OutPoint>> {
- self.inner.outpoints()
- }
-
- /// Iterate over known txouts that spend to tracked script pubkeys.
- pub fn txouts(
- &self,
- ) -> impl DoubleEndedIterator<Item = KeychainIndexed<K, (OutPoint, &TxOut)>> + ExactSizeIterator
- {
- self.inner
- .txouts()
- .map(|(index, op, txout)| (index.clone(), (op, txout)))
- }
-
- /// Finds all txouts on a transaction that has previously been scanned and indexed.
- pub fn txouts_in_tx(
- &self,
- txid: Txid,
- ) -> impl DoubleEndedIterator<Item = KeychainIndexed<K, (OutPoint, &TxOut)>> {
- self.inner
- .txouts_in_tx(txid)
- .map(|(index, op, txout)| (index.clone(), (op, txout)))
- }
-
- /// Return the [`TxOut`] of `outpoint` if it has been indexed, and if it corresponds to a
- /// tracked keychain.
- ///
- /// 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<KeychainIndexed<K, &TxOut>> {
- self.inner
- .txout(outpoint)
- .map(|(index, txout)| (index.clone(), txout))
- }
-
- /// 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.clone(), 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)
- }
-
- /// 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 spk under the given `keychain` and `index` is successfully
- /// marked as used. Returns false either when there is no descriptor under the given
- /// keychain, or when the spk is already marked as used.
- ///
- /// 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 the total value transfer effect `tx` has on the script pubkeys belonging to the
- /// keychains in `range`. Value is *sent* when a script pubkey in the `range` is on an input and
- /// *received* when it is on an output. For `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.
- pub fn sent_and_received(
- &self,
- tx: &Transaction,
- range: impl RangeBounds<K>,
- ) -> (Amount, Amount) {
- self.inner
- .sent_and_received(tx, self.map_to_inner_bounds(range))
- }
-
- /// 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, range: impl RangeBounds<K>) -> SignedAmount {
- self.inner.net_value(tx, self.map_to_inner_bounds(range))
- }
-}
-
-impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
- /// Return all keychains and their corresponding descriptors.
- pub fn keychains(
- &self,
- ) -> impl DoubleEndedIterator<Item = (&K, &Descriptor<DescriptorPublicKey>)> + ExactSizeIterator + '_
- {
- self.keychain_to_descriptor_id
- .iter()
- .map(|(k, did)| (k, self.descriptors.get(did).expect("invariant")))
- }
-
- /// Insert a descriptor with a keychain associated to it.
- ///
- /// Adding a descriptor means you will be able to derive new script pubkeys under it and the
- /// txout index will discover transaction outputs with those script pubkeys (once they've been
- /// derived and added to the index).
- ///
- /// keychain <-> descriptor is a one-to-one mapping that cannot be changed. Attempting to do so
- /// will return a [`InsertDescriptorError<K>`].
- ///
- /// `[KeychainTxOutIndex]` will prevent you from inserting two descriptors which derive the same
- /// script pubkey at index 0, but it's up to you to ensure that descriptors don't collide at
- /// other indices. If they do nothing catastrophic happens at the `KeychainTxOutIndex` level
- /// (one keychain just becomes the defacto owner of that spk arbitrarily) but this may have
- /// subtle implications up the application stack like one UTXO being missing from one keychain
- /// because it has been assigned to another which produces the same script pubkey.
- pub fn insert_descriptor(
- &mut self,
- keychain: K,
- descriptor: Descriptor<DescriptorPublicKey>,
- ) -> Result<ChangeSet<K>, InsertDescriptorError<K>> {
- let mut changeset = ChangeSet::<K>::default();
- let did = descriptor.descriptor_id();
- if !self.keychain_to_descriptor_id.contains_key(&keychain)
- && !self.descriptor_id_to_keychain.contains_key(&did)
- {
- self.descriptors.insert(did, descriptor.clone());
- self.keychain_to_descriptor_id.insert(keychain.clone(), did);
- self.descriptor_id_to_keychain.insert(did, keychain.clone());
- self.replenish_inner_index(did, &keychain, self.lookahead);
- changeset
- .keychains_added
- .insert(keychain.clone(), descriptor);
- } else {
- if let Some(existing_desc_id) = self.keychain_to_descriptor_id.get(&keychain) {
- let descriptor = self.descriptors.get(existing_desc_id).expect("invariant");
- if *existing_desc_id != did {
- return Err(InsertDescriptorError::KeychainAlreadyAssigned {
- existing_assignment: descriptor.clone(),
- keychain,
- });
- }
- }
-
- if let Some(existing_keychain) = self.descriptor_id_to_keychain.get(&did) {
- let descriptor = self.descriptors.get(&did).expect("invariant").clone();
-
- if *existing_keychain != keychain {
- return Err(InsertDescriptorError::DescriptorAlreadyAssigned {
- existing_assignment: existing_keychain.clone(),
- descriptor,
- });
- }
- }
- }
-
- Ok(changeset)
- }
-
- /// Gets the descriptor associated with the keychain. Returns `None` if the keychain doesn't
- /// have a descriptor associated with it.
- pub fn get_descriptor(&self, keychain: &K) -> Option<&Descriptor<DescriptorPublicKey>> {
- let did = self.keychain_to_descriptor_id.get(keychain)?;
- self.descriptors.get(did)
- }
-
- /// Get the lookahead setting.
- ///
- /// Refer to [`new`] for more information on the `lookahead`.
- ///
- /// [`new`]: Self::new
- pub fn lookahead(&self) -> u32 {
- self.lookahead
- }
-
- /// Store lookahead scripts until `target_index` (inclusive).
- ///
- /// This does not change the global `lookahead` setting.
- pub fn lookahead_to_target(&mut self, keychain: &K, target_index: u32) {
- if let Some((next_index, _)) = self.next_index(keychain) {
- let temp_lookahead = (target_index + 1)
- .checked_sub(next_index)
- .filter(|&index| index > 0);
-
- if let Some(temp_lookahead) = temp_lookahead {
- self.replenish_inner_index_keychain(keychain, temp_lookahead);
- }
- }
- }
-
- fn replenish_inner_index_did(&mut self, did: DescriptorId, lookahead: u32) {
- if let Some(keychain) = self.descriptor_id_to_keychain.get(&did).cloned() {
- self.replenish_inner_index(did, &keychain, lookahead);
- }
- }
-
- fn replenish_inner_index_keychain(&mut self, keychain: &K, lookahead: u32) {
- if let Some(did) = self.keychain_to_descriptor_id.get(keychain) {
- self.replenish_inner_index(*did, keychain, lookahead);
- }
- }
-
- /// Syncs the state of the inner spk index after changes to a keychain
- fn replenish_inner_index(&mut self, did: DescriptorId, keychain: &K, lookahead: u32) {
- let descriptor = self.descriptors.get(&did).expect("invariant");
- let next_store_index = self
- .inner
- .all_spks()
- .range(&(keychain.clone(), u32::MIN)..=&(keychain.clone(), u32::MAX))
- .last()
- .map_or(0, |((_, index), _)| *index + 1);
- let next_reveal_index = self.last_revealed.get(&did).map_or(0, |v| *v + 1);
- for (new_index, new_spk) in
- SpkIterator::new_with_range(descriptor, next_store_index..next_reveal_index + lookahead)
- {
- let _inserted = self
- .inner
- .insert_spk((keychain.clone(), new_index), new_spk);
- debug_assert!(_inserted, "replenish lookahead: must not have existing spk: keychain={:?}, lookahead={}, next_store_index={}, next_reveal_index={}", keychain, lookahead, next_store_index, next_reveal_index);
- }
- }
-
- /// Get an unbounded spk iterator over a given `keychain`. Returns `None` if the provided
- /// keychain doesn't exist
- pub fn unbounded_spk_iter(
- &self,
- keychain: &K,
- ) -> Option<SpkIterator<Descriptor<DescriptorPublicKey>>> {
- let descriptor = self.get_descriptor(keychain)?.clone();
- Some(SpkIterator::new(descriptor))
- }
-
- /// Get unbounded spk iterators for all keychains.
- pub fn all_unbounded_spk_iters(
- &self,
- ) -> BTreeMap<K, SpkIterator<Descriptor<DescriptorPublicKey>>> {
- self.keychain_to_descriptor_id
- .iter()
- .map(|(k, did)| {
- (
- k.clone(),
- SpkIterator::new(self.descriptors.get(did).expect("invariant").clone()),
- )
- })
- .collect()
- }
-
- /// Iterate over revealed spks of keychains in `range`
- pub fn revealed_spks(
- &self,
- range: impl RangeBounds<K>,
- ) -> impl Iterator<Item = KeychainIndexed<K, &Script>> {
- let start = range.start_bound();
- let end = range.end_bound();
- let mut iter_last_revealed = self
- .keychain_to_descriptor_id
- .range((start, end))
- .map(|(k, did)| (k, self.last_revealed.get(did).cloned()));
- let mut iter_spks = self
- .inner
- .all_spks()
- .range(self.map_to_inner_bounds((start, end)));
- let mut current_keychain = iter_last_revealed.next();
- // The reason we need a tricky algorithm is because of the "lookahead" feature which means
- // that some of the spks in the SpkTxoutIndex will not have been revealed yet. So we need to
- // filter out those spks that are above the last_revealed for that keychain. To do this we
- // iterate through the last_revealed for each keychain and the spks for each keychain in
- // tandem. This minimizes BTreeMap queries.
- core::iter::from_fn(move || loop {
- let ((keychain, index), spk) = iter_spks.next()?;
- // We need to find the last revealed that matches the current spk we are considering so
- // we skip ahead.
- while current_keychain?.0 < keychain {
- current_keychain = iter_last_revealed.next();
- }
- let (current_keychain, last_revealed) = current_keychain?;
-
- if current_keychain == keychain && Some(*index) <= last_revealed {
- break Some(((keychain.clone(), *index), spk.as_script()));
- }
- })
- }
-
- /// Iterate over revealed spks of the given `keychain` with ascending indices.
- ///
- /// This is a double ended iterator so you can easily reverse it to get an iterator where
- /// the script pubkeys that were most recently revealed are first.
- pub fn revealed_keychain_spks<'a>(
- &'a self,
- keychain: &'a K,
- ) -> impl DoubleEndedIterator<Item = Indexed<&Script>> + 'a {
- let end = self
- .last_revealed_index(keychain)
- .map(|v| v + 1)
- .unwrap_or(0);
- self.inner
- .all_spks()
- .range((keychain.clone(), 0)..(keychain.clone(), end))
- .map(|((_, index), spk)| (*index, spk.as_script()))
- }
-
- /// Iterate over revealed, but unused, spks of all keychains.
- pub fn unused_spks(
- &self,
- ) -> impl DoubleEndedIterator<Item = KeychainIndexed<K, &Script>> + Clone {
- self.keychain_to_descriptor_id.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`.
- /// Returns an empty iterator if the provided keychain doesn't exist.
- pub fn unused_keychain_spks(
- &self,
- keychain: &K,
- ) -> impl DoubleEndedIterator<Item = Indexed<&Script>> + Clone {
- let end = match self.keychain_to_descriptor_id.get(keychain) {
- Some(did) => self.last_revealed.get(did).map(|v| *v + 1).unwrap_or(0),
- None => 0,
- };
-
- self.inner
- .unused_spks((keychain.clone(), 0)..(keychain.clone(), end))
- .map(|((_, i), spk)| (*i, spk))
- }
-
- /// Get the next derivation index for `keychain`. The next index is the index after the last revealed
- /// derivation index.
- ///
- /// The second field in the returned tuple represents whether the next derivation index is new.
- /// There are two scenarios where the next derivation index is reused (not new):
- ///
- /// 1. The keychain's descriptor has no wildcard, and a script has already been revealed.
- /// 2. The number of revealed scripts has already reached 2^31 (refer to BIP-32).
- ///
- /// Not checking the second field of the tuple may result in address reuse.
- ///
- /// Returns None if the provided `keychain` doesn't exist.
- pub fn next_index(&self, keychain: &K) -> Option<(u32, bool)> {
- let did = self.keychain_to_descriptor_id.get(keychain)?;
- let last_index = self.last_revealed.get(did).cloned();
- let descriptor = self.descriptors.get(did).expect("invariant");
-
- // we can only get the next index if the wildcard exists.
- let has_wildcard = descriptor.has_wildcard();
-
- Some(match last_index {
- // if there is no index, next_index is always 0.
- None => (0, true),
- // descriptors without wildcards can only have one index.
- Some(_) if !has_wildcard => (0, false),
- // derivation index must be < 2^31 (BIP-32).
- Some(index) if index > BIP32_MAX_INDEX => {
- unreachable!("index is out of bounds")
- }
- Some(index) if index == BIP32_MAX_INDEX => (index, false),
- // get the next derivation index.
- Some(index) => (index + 1, true),
- })
- }
-
- /// Get the last derivation index that is revealed for each keychain.
- ///
- /// Keychains with no revealed indices will not be included in the returned [`BTreeMap`].
- pub fn last_revealed_indices(&self) -> BTreeMap<K, u32> {
- self.last_revealed
- .iter()
- .filter_map(|(desc_id, index)| {
- let keychain = self.descriptor_id_to_keychain.get(desc_id)?;
- Some((keychain.clone(), *index))
- })
- .collect()
- }
-
- /// Get the last derivation index revealed for `keychain`. Returns None if the keychain doesn't
- /// exist, or if the keychain doesn't have any revealed scripts.
- pub fn last_revealed_index(&self, keychain: &K) -> Option<u32> {
- let descriptor_id = self.keychain_to_descriptor_id.get(keychain)?;
- self.last_revealed.get(descriptor_id).cloned()
- }
-
- /// Convenience method to call [`Self::reveal_to_target`] on multiple keychains.
- pub fn reveal_to_target_multi(&mut self, keychains: &BTreeMap<K, u32>) -> ChangeSet<K> {
- let mut changeset = ChangeSet::default();
-
- for (keychain, &index) in keychains {
- if let Some((_, new_changeset)) = self.reveal_to_target(keychain, index) {
- changeset.append(new_changeset);
- }
- }
-
- changeset
- }
-
- /// Reveals script pubkeys of the `keychain`'s descriptor **up to and including** the
- /// `target_index`.
- ///
- /// If the `target_index` cannot be reached (due to the descriptor having no wildcard and/or
- /// the `target_index` is in the hardened index range), this method will make a best-effort and
- /// reveal up to the last possible index.
- ///
- /// This returns list of newly revealed indices (alongside their scripts) and a
- /// [`ChangeSet`], which reports updates to the latest revealed index. If no new script
- /// pubkeys are revealed, then both of these will be empty.
- ///
- /// Returns None if the provided `keychain` doesn't exist.
- #[must_use]
- pub fn reveal_to_target(
- &mut self,
- keychain: &K,
- target_index: u32,
- ) -> Option<(Vec<Indexed<ScriptBuf>>, ChangeSet<K>)> {
- let mut changeset = ChangeSet::default();
- let mut spks: Vec<Indexed<ScriptBuf>> = vec![];
- while let Some((i, new)) = self.next_index(keychain) {
- if !new || i > target_index {
- break;
- }
- match self.reveal_next_spk(keychain) {
- Some(((i, spk), change)) => {
- spks.push((i, spk));
- changeset.append(change);
- }
- None => break,
- }
- }
-
- Some((spks, changeset))
- }
-
- /// Attempts to reveal the next script pubkey for `keychain`.
- ///
- /// Returns the derivation index of the revealed script pubkey, the revealed script pubkey and a
- /// [`ChangeSet`] which represents changes in the last revealed index (if any).
- /// Returns None if the provided keychain doesn't exist.
- ///
- /// When a new script cannot be revealed, we return the last revealed script and an empty
- /// [`ChangeSet`]. There are two scenarios when a new script pubkey cannot be derived:
- ///
- /// 1. The descriptor has no wildcard and already has one script revealed.
- /// 2. The descriptor has already revealed scripts up to the numeric bound.
- /// 3. There is no descriptor associated with the given keychain.
- pub fn reveal_next_spk(&mut self, keychain: &K) -> Option<(Indexed<ScriptBuf>, ChangeSet<K>)> {
- let (next_index, new) = self.next_index(keychain)?;
- let mut changeset = ChangeSet::default();
-
- if new {
- let did = self.keychain_to_descriptor_id.get(keychain)?;
- self.last_revealed.insert(*did, next_index);
- changeset.last_revealed.insert(*did, next_index);
- self.replenish_inner_index(*did, keychain, self.lookahead);
- }
- let script = self
- .inner
- .spk_at_index(&(keychain.clone(), next_index))
- .expect("we just inserted it");
- Some(((next_index, script.into()), changeset))
- }
-
- /// Gets the next unused script pubkey in the keychain. I.e., the script pubkey with the lowest
- /// index that has not been used yet.
- ///
- /// This will derive and reveal a new script pubkey if no more unused script pubkeys exist.
- ///
- /// If the descriptor has no wildcard and already has a used script pubkey or if a descriptor
- /// has used all scripts up to the derivation bounds, then the last derived script pubkey will be
- /// returned.
- ///
- /// Returns `None` if there are no script pubkeys that have been used and no new script pubkey
- /// could be revealed (see [`reveal_next_spk`] for when this happens).
- ///
- /// [`reveal_next_spk`]: Self::reveal_next_spk
- pub fn next_unused_spk(&mut self, keychain: &K) -> Option<(Indexed<ScriptBuf>, ChangeSet<K>)> {
- let next_unused = self
- .unused_keychain_spks(keychain)
- .next()
- .map(|(i, spk)| ((i, spk.to_owned()), ChangeSet::default()));
-
- next_unused.or_else(|| self.reveal_next_spk(keychain))
- }
-
- /// Iterate over all [`OutPoint`]s that have `TxOut`s with script pubkeys derived from
- /// `keychain`.
- pub fn keychain_outpoints<'a>(
- &'a self,
- keychain: &'a K,
- ) -> impl DoubleEndedIterator<Item = Indexed<OutPoint>> + 'a {
- self.keychain_outpoints_in_range(keychain..=keychain)
- .map(|((_, i), op)| (i, op))
- }
-
- /// Iterate over [`OutPoint`]s that have script pubkeys derived from keychains in `range`.
- pub fn keychain_outpoints_in_range<'a>(
- &'a self,
- range: impl RangeBounds<K> + 'a,
- ) -> impl DoubleEndedIterator<Item = KeychainIndexed<K, OutPoint>> + 'a {
- self.inner
- .outputs_in_range(self.map_to_inner_bounds(range))
- .map(|((k, i), op)| ((k.clone(), *i), op))
- }
-
- fn map_to_inner_bounds(&self, bound: impl RangeBounds<K>) -> impl RangeBounds<(K, u32)> {
- let start = match bound.start_bound() {
- Bound::Included(keychain) => Bound::Included((keychain.clone(), u32::MIN)),
- Bound::Excluded(keychain) => Bound::Excluded((keychain.clone(), u32::MAX)),
- Bound::Unbounded => Bound::Unbounded,
- };
- let end = match bound.end_bound() {
- Bound::Included(keychain) => Bound::Included((keychain.clone(), u32::MAX)),
- Bound::Excluded(keychain) => Bound::Excluded((keychain.clone(), u32::MIN)),
- Bound::Unbounded => Bound::Unbounded,
- };
-
- (start, end)
- }
-
- /// 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<u32> {
- self.keychain_outpoints(keychain).last().map(|(i, _)| i)
- }
-
- /// Returns the highest derivation index of each keychain that [`KeychainTxOutIndex`] has found
- /// a [`TxOut`] with it's script pubkey.
- pub fn last_used_indices(&self) -> BTreeMap<K, u32> {
- self.keychain_to_descriptor_id
- .iter()
- .filter_map(|(keychain, _)| {
- self.last_used_index(keychain)
- .map(|index| (keychain.clone(), index))
- })
- .collect()
- }
-
- /// Applies the `ChangeSet<K>` to the [`KeychainTxOutIndex<K>`]
- ///
- /// Keychains added by the `keychains_added` field of `ChangeSet<K>` respect the one-to-one
- /// keychain <-> descriptor invariant by silently ignoring attempts to violate it (but will
- /// panic if `debug_assertions` are enabled).
- pub fn apply_changeset(&mut self, changeset: ChangeSet<K>) {
- let ChangeSet {
- keychains_added,
- last_revealed,
- } = changeset;
- for (keychain, descriptor) in keychains_added {
- let _ignore_invariant_violation = self.insert_descriptor(keychain, descriptor);
- }
-
- for (&desc_id, &index) in &last_revealed {
- let v = self.last_revealed.entry(desc_id).or_default();
- *v = index.max(*v);
- }
-
- for did in last_revealed.keys() {
- self.replenish_inner_index_did(*did, self.lookahead);
- }
- }
-}
-
-#[derive(Clone, Debug, PartialEq)]
-/// Error returned from [`KeychainTxOutIndex::insert_descriptor`]
-pub enum InsertDescriptorError<K> {
- /// The descriptor has already been assigned to a keychain so you can't assign it to another
- DescriptorAlreadyAssigned {
- /// The descriptor you have attempted to reassign
- descriptor: Descriptor<DescriptorPublicKey>,
- /// The keychain that the descriptor is already assigned to
- existing_assignment: K,
- },
- /// The keychain is already assigned to a descriptor so you can't reassign it
- KeychainAlreadyAssigned {
- /// The keychain that you have attempted to reassign
- keychain: K,
- /// The descriptor that the keychain is already assigned to
- existing_assignment: Descriptor<DescriptorPublicKey>,
- },
-}
-
-impl<K: core::fmt::Debug> core::fmt::Display for InsertDescriptorError<K> {
- fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
- match self {
- InsertDescriptorError::DescriptorAlreadyAssigned {
- existing_assignment: existing,
- descriptor,
- } => {
- write!(
- f,
- "attempt to re-assign descriptor {descriptor:?} already assigned to {existing:?}"
- )
- }
- InsertDescriptorError::KeychainAlreadyAssigned {
- existing_assignment: existing,
- keychain,
- } => {
- write!(
- f,
- "attempt to re-assign keychain {keychain:?} already assigned to {existing:?}"
- )
- }
- }
- }
-}
-
-#[cfg(feature = "std")]
-impl<K: core::fmt::Debug> std::error::Error for InsertDescriptorError<K> {}
-
-/// Represents updates to the derivation index of a [`KeychainTxOutIndex`].
-/// It maps each keychain `K` to a descriptor and its last revealed index.
-///
-/// It can be applied to [`KeychainTxOutIndex`] with [`apply_changeset`].
-///
-/// The `last_revealed` field is monotone in that [`append`] will never decrease it.
-/// `keychains_added` is *not* monotone, once it is set any attempt to change it is subject to the
-/// same *one-to-one* keychain <-> descriptor mapping invariant as [`KeychainTxOutIndex`] itself.
-///
-/// [`KeychainTxOutIndex`]: crate::keychain::KeychainTxOutIndex
-/// [`apply_changeset`]: crate::keychain::KeychainTxOutIndex::apply_changeset
-/// [`append`]: Self::append
-#[derive(Clone, Debug, PartialEq)]
-#[cfg_attr(
- feature = "serde",
- derive(serde::Deserialize, serde::Serialize),
- serde(
- crate = "serde_crate",
- bound(
- deserialize = "K: Ord + serde::Deserialize<'de>",
- serialize = "K: Ord + serde::Serialize"
- )
- )
-)]
-#[must_use]
-pub struct ChangeSet<K> {
- /// Contains the keychains that have been added and their respective descriptor
- pub keychains_added: BTreeMap<K, Descriptor<DescriptorPublicKey>>,
- /// Contains for each descriptor_id the last revealed index of derivation
- pub last_revealed: BTreeMap<DescriptorId, u32>,
-}
-
-impl<K: Ord> Append for ChangeSet<K> {
- /// Merge another [`ChangeSet<K>`] into self.
- ///
- /// For the `keychains_added` field this method respects the invariants of
- /// [`insert_descriptor`]. `last_revealed` always becomes the larger of the two.
- ///
- /// [`insert_descriptor`]: KeychainTxOutIndex::insert_descriptor
- fn append(&mut self, other: Self) {
- for (new_keychain, new_descriptor) in other.keychains_added {
- // enforce 1-to-1 invariance
- if !self.keychains_added.contains_key(&new_keychain)
- // FIXME: very inefficient
- && self
- .keychains_added
- .values()
- .all(|descriptor| descriptor != &new_descriptor)
- {
- self.keychains_added.insert(new_keychain, new_descriptor);
- }
- }
-
- // for `last_revealed`, entries of `other` will take precedence ONLY if it is greater than
- // what was originally in `self`.
- for (desc_id, index) in other.last_revealed {
- use crate::collections::btree_map::Entry;
- match self.last_revealed.entry(desc_id) {
- Entry::Vacant(entry) => {
- entry.insert(index);
- }
- Entry::Occupied(mut entry) => {
- if *entry.get() < index {
- entry.insert(index);
- }
- }
- }
- }
- }
-
- /// Returns whether the changeset are empty.
- fn is_empty(&self) -> bool {
- self.last_revealed.is_empty() && self.keychains_added.is_empty()
- }
-}
-
-impl<K> Default for ChangeSet<K> {
- fn default() -> Self {
- Self {
- last_revealed: BTreeMap::default(),
- keychains_added: BTreeMap::default(),
- }
- }
-}
-
-#[derive(Clone, Debug, Eq, PartialEq)]
-/// The keychain doesn't exist. Most likley hasn't been inserted with [`KeychainTxOutIndex::insert_descriptor`].
-pub struct NoSuchKeychain<K>(K);
-
-impl<K: Debug> core::fmt::Display for NoSuchKeychain<K> {
- fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
- write!(f, "no such keychain {:?} exists", &self.0)
- }
-}
#![warn(missing_docs)]
pub use bitcoin;
-mod spk_txout_index;
-pub use spk_txout_index::*;
+mod balance;
+pub use balance::*;
mod chain_data;
pub use chain_data::*;
pub mod indexed_tx_graph;
pub use indexed_tx_graph::IndexedTxGraph;
-pub mod keychain;
-pub use keychain::{Indexed, KeychainIndexed};
+pub mod indexer;
+pub use indexer::spk_txout::*;
+pub use indexer::Indexer;
pub mod local_chain;
mod tx_data_traits;
pub mod tx_graph;
/// How many confirmations are needed f or a coinbase output to be spent.
pub const COINBASE_MATURITY: u32 = 100;
+
+/// A tuple of keychain index and `T` representing the indexed value.
+pub type Indexed<T> = (u32, T);
+/// A tuple of keychain `K`, derivation index (`u32`) and a `T` associated with them.
+pub type KeychainIndexed<K, T> = ((K, u32), T);
//! Helper types for spk-based blockchain clients.
use crate::{
- collections::BTreeMap, keychain::Indexed, local_chain::CheckPoint,
- ConfirmationTimeHeightAnchor, TxGraph,
+ collections::BTreeMap, local_chain::CheckPoint, ConfirmationTimeHeightAnchor, Indexed, TxGraph,
};
use alloc::boxed::Box;
use bitcoin::{OutPoint, Script, ScriptBuf, Txid};
#[must_use]
pub fn populate_with_revealed_spks<K: Clone + Ord + core::fmt::Debug + Send + Sync>(
self,
- index: &crate::keychain::KeychainTxOutIndex<K>,
+ index: &crate::indexer::keychain_txout::KeychainTxOutIndex<K>,
spk_range: impl core::ops::RangeBounds<K>,
) -> Self {
use alloc::borrow::ToOwned;
/// [`KeychainTxOutIndex::all_unbounded_spk_iters`] and is used to populate the
/// [`FullScanRequest`].
///
- /// [`KeychainTxOutIndex::all_unbounded_spk_iters`]: crate::keychain::KeychainTxOutIndex::all_unbounded_spk_iters
+ /// [`KeychainTxOutIndex::all_unbounded_spk_iters`]: crate::indexer::keychain_txout::KeychainTxOutIndex::all_unbounded_spk_iters
#[cfg(feature = "miniscript")]
#[must_use]
pub fn from_keychain_txout_index(
chain_tip: CheckPoint,
- index: &crate::keychain::KeychainTxOutIndex<K>,
+ index: &crate::indexer::keychain_txout::KeychainTxOutIndex<K>,
) -> Self
where
K: core::fmt::Debug,
use crate::{
bitcoin::{secp256k1::Secp256k1, ScriptBuf},
- keychain::Indexed,
miniscript::{Descriptor, DescriptorPublicKey},
+ Indexed,
};
use core::{borrow::Borrow, ops::Bound, ops::RangeBounds};
mod test {
use crate::{
bitcoin::secp256k1::Secp256k1,
- keychain::KeychainTxOutIndex,
+ indexer::keychain_txout::KeychainTxOutIndex,
miniscript::{Descriptor, DescriptorPublicKey},
spk_iter::{SpkIterator, BIP32_MAX_INDEX},
};
+++ /dev/null
-use core::ops::RangeBounds;
-
-use crate::{
- collections::{hash_map::Entry, BTreeMap, BTreeSet, HashMap},
- indexed_tx_graph::Indexer,
-};
-use bitcoin::{Amount, OutPoint, Script, ScriptBuf, SignedAmount, Transaction, TxOut, Txid};
-
-/// An index storing [`TxOut`]s that have a script pubkey that matches those in a list.
-///
-/// The basic idea is that you insert script pubkeys you care about into the index with
-/// [`insert_spk`] and then when you call [`Indexer::index_tx`] or [`Indexer::index_txout`], the
-/// index will look at any txouts you pass in and store and index any txouts matching one of its
-/// script pubkeys.
-///
-/// Each script pubkey is associated with an application-defined index script index `I`, which must be
-/// [`Ord`]. Usually, this is used to associate the derivation index of the script pubkey or even a
-/// combination of `(keychain, derivation_index)`.
-///
-/// Note there is no harm in scanning transactions that disappear from the blockchain or were never
-/// in there in the first place. `SpkTxOutIndex` is intentionally *monotone* -- you cannot delete or
-/// modify txouts that have been indexed. To find out which txouts from the index are actually in the
-/// chain or unspent, you must use other sources of information like a [`TxGraph`].
-///
-/// [`TxOut`]: bitcoin::TxOut
-/// [`insert_spk`]: Self::insert_spk
-/// [`Ord`]: core::cmp::Ord
-/// [`TxGraph`]: crate::tx_graph::TxGraph
-#[derive(Clone, Debug)]
-pub struct SpkTxOutIndex<I> {
- /// script pubkeys ordered by index
- spks: BTreeMap<I, ScriptBuf>,
- /// A reverse lookup from spk to spk index
- spk_indices: HashMap<ScriptBuf, I>,
- /// The set of unused indexes.
- unused: BTreeSet<I>,
- /// Lookup index and txout by outpoint.
- txouts: BTreeMap<OutPoint, (I, TxOut)>,
- /// Lookup from spk index to outpoints that had that spk
- spk_txouts: BTreeSet<(I, OutPoint)>,
-}
-
-impl<I> Default for SpkTxOutIndex<I> {
- fn default() -> Self {
- Self {
- txouts: Default::default(),
- spks: Default::default(),
- spk_indices: Default::default(),
- spk_txouts: Default::default(),
- unused: Default::default(),
- }
- }
-}
-
-impl<I: Clone + Ord + core::fmt::Debug> Indexer for SpkTxOutIndex<I> {
- type ChangeSet = ();
-
- fn index_txout(&mut self, outpoint: OutPoint, txout: &TxOut) -> Self::ChangeSet {
- self.scan_txout(outpoint, txout);
- Default::default()
- }
-
- fn index_tx(&mut self, tx: &Transaction) -> Self::ChangeSet {
- self.scan(tx);
- Default::default()
- }
-
- fn initial_changeset(&self) -> Self::ChangeSet {}
-
- fn apply_changeset(&mut self, _changeset: Self::ChangeSet) {
- // This applies nothing.
- }
-
- fn is_tx_relevant(&self, tx: &Transaction) -> bool {
- self.is_relevant(tx)
- }
-}
-
-impl<I: Clone + Ord + core::fmt::Debug> SpkTxOutIndex<I> {
- /// Scans a transaction's outputs for matching script pubkeys.
- ///
- /// Typically, this is used in two situations:
- ///
- /// 1. After loading transaction data from the disk, you may scan over all the txouts to restore all
- /// your txouts.
- /// 2. When getting new data from the chain, you usually scan it before incorporating it into your chain state.
- pub fn scan(&mut self, tx: &Transaction) -> BTreeSet<I> {
- let mut scanned_indices = BTreeSet::new();
- let txid = tx.compute_txid();
- for (i, txout) in tx.output.iter().enumerate() {
- let op = OutPoint::new(txid, i as u32);
- if let Some(spk_i) = self.scan_txout(op, txout) {
- scanned_indices.insert(spk_i.clone());
- }
- }
-
- scanned_indices
- }
-
- /// Scan a single `TxOut` for a matching script pubkey and returns the index that matches the
- /// script pubkey (if any).
- pub fn scan_txout(&mut self, op: OutPoint, txout: &TxOut) -> Option<&I> {
- let spk_i = self.spk_indices.get(&txout.script_pubkey);
- if let Some(spk_i) = spk_i {
- self.txouts.insert(op, (spk_i.clone(), txout.clone()));
- self.spk_txouts.insert((spk_i.clone(), op));
- self.unused.remove(spk_i);
- }
- spk_i
- }
-
- /// Get a reference to the set of indexed outpoints.
- pub fn outpoints(&self) -> &BTreeSet<(I, OutPoint)> {
- &self.spk_txouts
- }
-
- /// Iterate over all known txouts that spend to tracked script pubkeys.
- pub fn txouts(
- &self,
- ) -> impl DoubleEndedIterator<Item = (&I, OutPoint, &TxOut)> + ExactSizeIterator {
- self.txouts
- .iter()
- .map(|(op, (index, txout))| (index, *op, txout))
- }
-
- /// Finds all txouts on a transaction that has previously been scanned and indexed.
- pub fn txouts_in_tx(
- &self,
- txid: Txid,
- ) -> impl DoubleEndedIterator<Item = (&I, OutPoint, &TxOut)> {
- self.txouts
- .range(OutPoint::new(txid, u32::MIN)..=OutPoint::new(txid, u32::MAX))
- .map(|(op, (index, txout))| (index, *op, txout))
- }
-
- /// Iterates over all the outputs with script pubkeys in an index range.
- pub fn outputs_in_range(
- &self,
- range: impl RangeBounds<I>,
- ) -> impl DoubleEndedIterator<Item = (&I, OutPoint)> {
- use bitcoin::hashes::Hash;
- use core::ops::Bound::*;
- let min_op = OutPoint {
- txid: Txid::all_zeros(),
- vout: u32::MIN,
- };
- let max_op = OutPoint {
- txid: Txid::from_byte_array([0xff; Txid::LEN]),
- vout: u32::MAX,
- };
-
- let start = match range.start_bound() {
- Included(index) => Included((index.clone(), min_op)),
- Excluded(index) => Excluded((index.clone(), max_op)),
- Unbounded => Unbounded,
- };
-
- let end = match range.end_bound() {
- Included(index) => Included((index.clone(), max_op)),
- Excluded(index) => Excluded((index.clone(), min_op)),
- Unbounded => Unbounded,
- };
-
- self.spk_txouts.range((start, end)).map(|(i, op)| (i, *op))
- }
-
- /// Returns the txout and script pubkey index of the `TxOut` at `OutPoint`.
- ///
- /// Returns `None` if the `TxOut` hasn't been scanned or if nothing matching was found there.
- pub fn txout(&self, outpoint: OutPoint) -> Option<(&I, &TxOut)> {
- self.txouts.get(&outpoint).map(|v| (&v.0, &v.1))
- }
-
- /// Returns the script that has been inserted at the `index`.
- ///
- /// If that index hasn't been inserted yet, it will return `None`.
- pub fn spk_at_index(&self, index: &I) -> Option<&Script> {
- self.spks.get(index).map(|s| s.as_script())
- }
-
- /// The script pubkeys that are being tracked by the index.
- pub fn all_spks(&self) -> &BTreeMap<I, ScriptBuf> {
- &self.spks
- }
-
- /// Adds a script pubkey to scan for. Returns `false` and does nothing if spk already exists in the map
- ///
- /// the index will look for outputs spending to this spk whenever it scans new data.
- pub fn insert_spk(&mut self, index: I, spk: ScriptBuf) -> bool {
- match self.spk_indices.entry(spk.clone()) {
- Entry::Vacant(value) => {
- value.insert(index.clone());
- self.spks.insert(index.clone(), spk);
- self.unused.insert(index);
- true
- }
- Entry::Occupied(_) => false,
- }
- }
-
- /// Iterates over all unused script pubkeys in an index range.
- ///
- /// Here, "unused" means that after the script pubkey was stored in the index, the index has
- /// never scanned a transaction output with it.
- ///
- /// # Example
- ///
- /// ```rust
- /// # use bdk_chain::SpkTxOutIndex;
- ///
- /// // imagine our spks are indexed like (keychain, derivation_index).
- /// let txout_index = SpkTxOutIndex::<(u32, u32)>::default();
- /// let all_unused_spks = txout_index.unused_spks(..);
- /// let change_index = 1;
- /// let unused_change_spks =
- /// 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)> + Clone
- where
- R: RangeBounds<I>,
- {
- self.unused
- .range(range)
- .map(move |index| (index, self.spk_at_index(index).expect("must exist")))
- }
-
- /// Returns whether the script pubkey at `index` has been used or not.
- ///
- /// Here, "unused" means that after the script pubkey was stored in the index, the index has
- /// never scanned a transaction output with it.
- pub fn is_used(&self, index: &I) -> bool {
- !self.unused.contains(index)
- }
-
- /// Marks the script pubkey at `index` as used even though it hasn't seen an output spending to it.
- /// This only affects when the `index` had already been added to `self` 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 the `index` used
- /// until you call [`unmark_used`].
- ///
- /// [`unmark_used`]: Self::unmark_used
- pub fn mark_used(&mut self, index: &I) -> bool {
- self.unused.remove(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, index: &I) -> bool {
- // we cannot set the index as unused when it does not exist
- if !self.spks.contains_key(index) {
- return false;
- }
- // we cannot set the index as unused when txouts are indexed under it
- if self.outputs_in_range(index..=index).next().is_some() {
- return false;
- }
- self.unused.insert(index.clone())
- }
-
- /// Returns the index associated with the script pubkey.
- pub fn index_of_spk(&self, script: &Script) -> Option<&I> {
- self.spk_indices.get(script)
- }
-
- /// Computes the total value transfer effect `tx` has on the script pubkeys in `range`. Value is
- /// *sent* when a script pubkey in the `range` is on an input and *received* when it is on an
- /// output. For `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.
- pub fn sent_and_received(
- &self,
- tx: &Transaction,
- range: impl RangeBounds<I>,
- ) -> (Amount, Amount) {
- let mut sent = Amount::ZERO;
- let mut received = Amount::ZERO;
-
- for txin in &tx.input {
- if let Some((index, txout)) = self.txout(txin.previous_output) {
- if range.contains(index) {
- sent += txout.value;
- }
- }
- }
- for txout in &tx.output {
- if let Some(index) = self.index_of_spk(&txout.script_pubkey) {
- if range.contains(index) {
- received += txout.value;
- }
- }
- }
-
- (sent, received)
- }
-
- /// Computes the net value transfer effect of `tx` on the script pubkeys in `range`. Shorthand
- /// for calling [`sent_and_received`] and subtracting sent from received.
- ///
- /// [`sent_and_received`]: Self::sent_and_received
- pub fn net_value(&self, tx: &Transaction, range: impl RangeBounds<I>) -> SignedAmount {
- let (sent, received) = self.sent_and_received(tx, range);
- received.to_signed().expect("valid `SignedAmount`")
- - sent.to_signed().expect("valid `SignedAmount`")
- }
-
- /// Whether any of the inputs of this transaction spend a txout tracked or whether any output
- /// matches one of our script pubkeys.
- ///
- /// It is easily possible to misuse this method and get false negatives by calling it before you
- /// have scanned the `TxOut`s the transaction is spending. For example, if you want to filter out
- /// all the transactions in a block that are irrelevant, you **must first scan all the
- /// transactions in the block** and only then use this method.
- pub fn is_relevant(&self, tx: &Transaction) -> bool {
- let input_matches = tx
- .input
- .iter()
- .any(|input| self.txouts.contains_key(&input.previous_output));
- let output_matches = tx
- .output
- .iter()
- .any(|output| self.spk_indices.contains_key(&output.script_pubkey));
- input_matches || output_matches
- }
-}
//! [`insert_txout`]: TxGraph::insert_txout
use crate::{
- collections::*, keychain::Balance, Anchor, Append, BlockId, ChainOracle, ChainPosition,
- FullTxOut,
+ collections::*, Anchor, Append, Balance, BlockId, ChainOracle, ChainPosition, FullTxOut,
};
use alloc::collections::vec_deque::VecDeque;
use alloc::sync::Arc;
use crate::common::DESCRIPTORS;
use bdk_chain::{
indexed_tx_graph::{self, IndexedTxGraph},
- keychain::{self, Balance, KeychainTxOutIndex},
+ indexer::keychain_txout::KeychainTxOutIndex,
local_chain::LocalChain,
- tx_graph, Append, ChainPosition, ConfirmationHeightAnchor, DescriptorExt,
+ tx_graph, Append, Balance, ChainPosition, ConfirmationHeightAnchor, DescriptorExt,
};
use bitcoin::{
secp256k1::Secp256k1, Amount, OutPoint, Script, ScriptBuf, Transaction, TxIn, TxOut,
/// agnostic.
#[test]
fn insert_relevant_txs() {
+ use bdk_chain::indexer::keychain_txout;
let (descriptor, _) = Descriptor::parse_descriptor(&Secp256k1::signing_only(), DESCRIPTORS[0])
.expect("must be valid");
let spk_0 = descriptor.at_derivation_index(0).unwrap().script_pubkey();
txs: txs.iter().cloned().map(Arc::new).collect(),
..Default::default()
},
- indexer: keychain::ChangeSet {
+ indexer: keychain_txout::ChangeSet {
last_revealed: [(descriptor.descriptor_id(), 9_u32)].into(),
keychains_added: [].into(),
},
// The initial changeset will also contain info about the keychain we added
let initial_changeset = indexed_tx_graph::ChangeSet {
graph: changeset.graph,
- indexer: keychain::ChangeSet {
+ indexer: keychain_txout::ChangeSet {
last_revealed: changeset.indexer.last_revealed,
keychains_added: [((), descriptor)].into(),
},
mod common;
use bdk_chain::{
collections::BTreeMap,
- indexed_tx_graph::Indexer,
- keychain::{self, ChangeSet, KeychainTxOutIndex},
- Append, DescriptorExt, DescriptorId,
+ indexer::keychain_txout::{ChangeSet, KeychainTxOutIndex},
+ Append, DescriptorExt, DescriptorId, Indexer,
};
use bitcoin::{secp256k1::Secp256k1, Amount, OutPoint, ScriptBuf, Transaction, TxOut};
external_descriptor: Descriptor<DescriptorPublicKey>,
internal_descriptor: Descriptor<DescriptorPublicKey>,
lookahead: u32,
-) -> bdk_chain::keychain::KeychainTxOutIndex<TestKeychain> {
- let mut txout_index = bdk_chain::keychain::KeychainTxOutIndex::<TestKeychain>::new(lookahead);
+) -> KeychainTxOutIndex<TestKeychain> {
+ let mut txout_index = KeychainTxOutIndex::<TestKeychain>::new(lookahead);
let _ = txout_index
.insert_descriptor(TestKeychain::External, external_descriptor)
#[test]
fn test_set_all_derivation_indices() {
- use bdk_chain::indexed_tx_graph::Indexer;
-
let external_descriptor = parse_descriptor(DESCRIPTORS[0]);
let internal_descriptor = parse_descriptor(DESCRIPTORS[1]);
let mut txout_index =
assert_eq!(txout_index.last_revealed_indices(), derive_to);
assert_eq!(
txout_index.reveal_to_target_multi(&derive_to),
- keychain::ChangeSet::default(),
+ ChangeSet::default(),
"no changes if we set to the same thing"
);
assert_eq!(txout_index.initial_changeset().last_revealed, last_revealed);
],
..common::new_tx(external_index)
};
- assert_eq!(txout_index.index_tx(&tx), keychain::ChangeSet::default());
+ assert_eq!(txout_index.index_tx(&tx), ChangeSet::default());
assert_eq!(
txout_index.last_revealed_index(&TestKeychain::External),
Some(last_external_index)
let mut txout_index = KeychainTxOutIndex::<()>::default();
assert_eq!(
txout_index.insert_descriptor((), desc.clone()),
- Ok(keychain::ChangeSet {
+ Ok(ChangeSet {
keychains_added: [((), desc.clone())].into(),
last_revealed: Default::default()
}),
);
assert_eq!(
txout_index.insert_descriptor((), desc.clone()),
- Ok(keychain::ChangeSet::default()),
+ Ok(ChangeSet::default()),
"inserting the same descriptor for keychain should return an empty changeset",
);
}
-use bdk_chain::{indexed_tx_graph::Indexer, SpkTxOutIndex};
+use bdk_chain::{Indexer, SpkTxOutIndex};
use bitcoin::{
absolute, transaction, Amount, OutPoint, ScriptBuf, SignedAmount, Transaction, TxIn, TxOut,
};
use std::collections::{BTreeSet, HashSet};
-use bdk_chain::{keychain::Balance, BlockId};
+use bdk_chain::{Balance, BlockId};
use bitcoin::{Amount, OutPoint, Script};
use common::*;
use bdk_chain::{
bitcoin::{hashes::Hash, Address, Amount, ScriptBuf, WScriptHash},
- keychain::Balance,
local_chain::LocalChain,
spk_client::SyncRequest,
- ConfirmationTimeHeightAnchor, IndexedTxGraph, SpkTxOutIndex,
+ Balance, ConfirmationTimeHeightAnchor, IndexedTxGraph, SpkTxOutIndex,
};
use bdk_electrum::BdkElectrumClient;
use bdk_testenv::{anyhow, bitcoincore_rpc::RpcApi, TestEnv};
}
/// This performs a full scan to get an update for the [`TxGraph`] and
-/// [`KeychainTxOutIndex`](bdk_chain::keychain::KeychainTxOutIndex).
+/// [`KeychainTxOutIndex`](bdk_chain::indexer::keychain_txout::KeychainTxOutIndex).
async fn full_scan_for_index_and_graph<K: Ord + Clone + Send>(
client: &esplora_client::AsyncClient,
keychain_spks: BTreeMap<
}
/// This performs a full scan to get an update for the [`TxGraph`] and
-/// [`KeychainTxOutIndex`](bdk_chain::keychain::KeychainTxOutIndex).
+/// [`KeychainTxOutIndex`](bdk_chain::indexer::keychain_txout::KeychainTxOutIndex).
fn full_scan_for_index_and_graph_blocking<K: Ord + Clone>(
client: &esplora_client::BlockingClient,
keychain_spks: BTreeMap<K, impl IntoIterator<Item = Indexed<ScriptBuf>>>,
use crate::Error;
use bdk_chain::CombinedChangeSet;
use bdk_chain::{
- indexed_tx_graph, keychain, local_chain, tx_graph, Anchor, Append, DescriptorExt, DescriptorId,
+ indexed_tx_graph, indexer::keychain_txout, local_chain, tx_graph, Anchor, Append,
+ DescriptorExt, DescriptorId,
};
/// Persists data in to a relational schema based [SQLite] database file.
/// If keychain exists only update last active index.
fn insert_keychains(
db_transaction: &rusqlite::Transaction,
- tx_graph_changeset: &indexed_tx_graph::ChangeSet<A, keychain::ChangeSet<K>>,
+ tx_graph_changeset: &indexed_tx_graph::ChangeSet<A, keychain_txout::ChangeSet<K>>,
) -> Result<(), Error> {
let keychain_changeset = &tx_graph_changeset.indexer;
for (keychain, descriptor) in keychain_changeset.keychains_added.iter() {
/// Update descriptor last revealed index.
fn update_last_revealed(
db_transaction: &rusqlite::Transaction,
- tx_graph_changeset: &indexed_tx_graph::ChangeSet<A, keychain::ChangeSet<K>>,
+ tx_graph_changeset: &indexed_tx_graph::ChangeSet<A, keychain_txout::ChangeSet<K>>,
) -> Result<(), Error> {
let keychain_changeset = &tx_graph_changeset.indexer;
for (descriptor_id, last_revealed) in keychain_changeset.last_revealed.iter() {
/// Error if trying to insert existing txid.
fn insert_txs(
db_transaction: &rusqlite::Transaction,
- tx_graph_changeset: &indexed_tx_graph::ChangeSet<A, keychain::ChangeSet<K>>,
+ tx_graph_changeset: &indexed_tx_graph::ChangeSet<A, keychain_txout::ChangeSet<K>>,
) -> Result<(), Error> {
for tx in tx_graph_changeset.graph.txs.iter() {
let insert_tx_stmt = &mut db_transaction
/// Error if trying to insert existing outpoint.
fn insert_txouts(
db_transaction: &rusqlite::Transaction,
- tx_graph_changeset: &indexed_tx_graph::ChangeSet<A, keychain::ChangeSet<K>>,
+ tx_graph_changeset: &indexed_tx_graph::ChangeSet<A, keychain_txout::ChangeSet<K>>,
) -> Result<(), Error> {
for txout in tx_graph_changeset.graph.txouts.iter() {
let insert_txout_stmt = &mut db_transaction
/// Update transaction last seen times.
fn update_last_seen(
db_transaction: &rusqlite::Transaction,
- tx_graph_changeset: &indexed_tx_graph::ChangeSet<A, keychain::ChangeSet<K>>,
+ tx_graph_changeset: &indexed_tx_graph::ChangeSet<A, keychain_txout::ChangeSet<K>>,
) -> Result<(), Error> {
for tx_last_seen in tx_graph_changeset.graph.last_seen.iter() {
let insert_or_update_tx_stmt = &mut db_transaction
/// Insert anchors.
fn insert_anchors(
db_transaction: &rusqlite::Transaction,
- tx_graph_changeset: &indexed_tx_graph::ChangeSet<A, keychain::ChangeSet<K>>,
+ tx_graph_changeset: &indexed_tx_graph::ChangeSet<A, keychain_txout::ChangeSet<K>>,
) -> Result<(), Error> {
// serde_json::to_string
for anchor in tx_graph_changeset.graph.anchors.iter() {
last_seen,
};
- let indexer: keychain::ChangeSet<K> = keychain::ChangeSet {
+ let indexer = keychain_txout::ChangeSet {
keychains_added,
last_revealed,
};
- let indexed_tx_graph: indexed_tx_graph::ChangeSet<A, keychain::ChangeSet<K>> =
+ let indexed_tx_graph: indexed_tx_graph::ChangeSet<A, keychain_txout::ChangeSet<K>> =
indexed_tx_graph::ChangeSet { graph, indexer };
if network.is_none() && chain.is_empty() && indexed_tx_graph.is_empty() {
use bdk_chain::miniscript::Descriptor;
use bdk_chain::CombinedChangeSet;
use bdk_chain::{
- indexed_tx_graph, keychain, tx_graph, BlockId, ConfirmationHeightAnchor,
+ indexed_tx_graph, tx_graph, BlockId, ConfirmationHeightAnchor,
ConfirmationTimeHeightAnchor, DescriptorExt,
};
use std::str::FromStr;
.into(),
};
- let keychain_changeset = keychain::ChangeSet {
+ let keychain_changeset = keychain_txout::ChangeSet {
keychains_added: [(ext_keychain, ext_desc), (int_keychain, int_desc)].into(),
last_revealed: [(ext_desc_id, 124), (int_desc_id, 421)].into(),
};
- let graph_changeset: indexed_tx_graph::ChangeSet<A, keychain::ChangeSet<Keychain>> =
+ let graph_changeset: indexed_tx_graph::ChangeSet<A, keychain_txout::ChangeSet<Keychain>> =
indexed_tx_graph::ChangeSet {
graph: tx_graph_changeset,
indexer: keychain_changeset,
last_seen: [(tx2.compute_txid(), 1708919121)].into(),
};
- let graph_changeset2: indexed_tx_graph::ChangeSet<A, keychain::ChangeSet<Keychain>> =
+ let graph_changeset2: indexed_tx_graph::ChangeSet<A, keychain_txout::ChangeSet<Keychain>> =
indexed_tx_graph::ChangeSet {
graph: tx_graph_changeset2,
- indexer: keychain::ChangeSet::default(),
+ indexer: keychain_txout::ChangeSet::default(),
};
changesets.push(CombinedChangeSet {
last_seen: BTreeMap::default(),
};
- let graph_changeset3: indexed_tx_graph::ChangeSet<A, keychain::ChangeSet<Keychain>> =
+ let graph_changeset3: indexed_tx_graph::ChangeSet<A, keychain_txout::ChangeSet<Keychain>> =
indexed_tx_graph::ChangeSet {
graph: tx_graph_changeset3,
- indexer: keychain::ChangeSet::default(),
+ indexer: keychain_txout::ChangeSet::default(),
};
changesets.push(CombinedChangeSet {
sync::Arc,
vec::Vec,
};
-pub use bdk_chain::keychain::Balance;
+pub use bdk_chain::Balance;
use bdk_chain::{
indexed_tx_graph,
- keychain::KeychainTxOutIndex,
+ indexer::keychain_txout::KeychainTxOutIndex,
local_chain::{
self, ApplyHeaderError, CannotConnectError, CheckPoint, CheckPointIter, LocalChain,
},
/// An update to [`Wallet`].
///
-/// It updates [`bdk_chain::keychain::KeychainTxOutIndex`], [`bdk_chain::TxGraph`] and [`local_chain::LocalChain`] atomically.
+/// It updates [`KeychainTxOutIndex`], [`bdk_chain::TxGraph`] and [`local_chain::LocalChain`] atomically.
#[derive(Debug, Clone, Default)]
pub struct Update {
/// Contains the last active derivation indices per keychain (`K`), which is used to update the
let _ = index
.insert_descriptor(KeychainKind::Internal, descriptor)
.map_err(|e| {
- use bdk_chain::keychain::InsertDescriptorError;
+ use bdk_chain::indexer::keychain_txout::InsertDescriptorError;
match e {
InsertDescriptorError::DescriptorAlreadyAssigned { .. } => {
crate::descriptor::error::Error::ExternalAndInternalAreTheSame
#![allow(unused)]
-
-use bdk_chain::indexed_tx_graph::Indexer;
use bdk_chain::{BlockId, ConfirmationTime, ConfirmationTimeHeightAnchor, TxGraph};
-use bdk_wallet::wallet::Update;
-use bdk_wallet::{KeychainKind, LocalOutput, Wallet};
-use bitcoin::hashes::Hash;
+use bdk_wallet::{
+ wallet::{Update, Wallet},
+ KeychainKind, LocalOutput,
+};
use bitcoin::{
- transaction, Address, Amount, BlockHash, FeeRate, Network, OutPoint, Transaction, TxIn, TxOut,
- Txid,
+ hashes::Hash, transaction, Address, Amount, BlockHash, FeeRate, Network, OutPoint, Transaction,
+ TxIn, TxOut, Txid,
};
use std::str::FromStr;
};
use bdk_chain::{
bitcoin::{constants::genesis_block, Block, Transaction},
- indexed_tx_graph, keychain,
+ indexed_tx_graph,
+ indexer::keychain_txout,
local_chain::{self, LocalChain},
Append, ConfirmationTimeHeightAnchor, IndexedTxGraph,
};
type ChangeSet = (
local_chain::ChangeSet,
- indexed_tx_graph::ChangeSet<ConfirmationTimeHeightAnchor, keychain::ChangeSet<Keychain>>,
+ indexed_tx_graph::ChangeSet<ConfirmationTimeHeightAnchor, keychain_txout::ChangeSet<Keychain>>,
);
#[derive(Debug)]
transaction, Address, Amount, Network, Sequence, Transaction, TxIn, TxOut,
},
indexed_tx_graph::{self, IndexedTxGraph},
- keychain::{self, KeychainTxOutIndex},
+ indexer::keychain_txout::{self, KeychainTxOutIndex},
local_chain,
miniscript::{
descriptor::{DescriptorSecretKey, KeyMap},
pub type KeychainTxGraph<A> = IndexedTxGraph<A, KeychainTxOutIndex<Keychain>>;
pub type KeychainChangeSet<A> = (
local_chain::ChangeSet,
- indexed_tx_graph::ChangeSet<A, keychain::ChangeSet<Keychain>>,
+ indexed_tx_graph::ChangeSet<A, keychain_txout::ChangeSet<Keychain>>,
);
#[derive(Parser)]
}
pub struct CreateTxChange {
- pub index_changeset: keychain::ChangeSet<Keychain>,
+ pub index_changeset: keychain_txout::ChangeSet<Keychain>,
pub change_keychain: Keychain,
pub index: u32,
}
where
O::Error: std::error::Error + Send + Sync + 'static,
{
- let mut changeset = keychain::ChangeSet::default();
+ let mut changeset = keychain_txout::ChangeSet::default();
let assets = bdk_tmp_plan::Assets {
keys: keymap.iter().map(|(pk, _)| pk.clone()).collect(),
bitcoin::{constants::genesis_block, Address, Network, Txid},
collections::BTreeSet,
indexed_tx_graph::{self, IndexedTxGraph},
- keychain,
+ indexer::keychain_txout,
local_chain::{self, LocalChain},
spk_client::{FullScanRequest, SyncRequest},
Append, ConfirmationHeightAnchor,
type ChangeSet = (
local_chain::ChangeSet,
- indexed_tx_graph::ChangeSet<ConfirmationHeightAnchor, keychain::ChangeSet<Keychain>>,
+ indexed_tx_graph::ChangeSet<ConfirmationHeightAnchor, keychain_txout::ChangeSet<Keychain>>,
);
fn main() -> anyhow::Result<()> {
use bdk_chain::{
bitcoin::{constants::genesis_block, Address, Network, Txid},
indexed_tx_graph::{self, IndexedTxGraph},
- keychain,
+ indexer::keychain_txout,
local_chain::{self, LocalChain},
spk_client::{FullScanRequest, SyncRequest},
Append, ConfirmationTimeHeightAnchor,
type ChangeSet = (
local_chain::ChangeSet,
- indexed_tx_graph::ChangeSet<ConfirmationTimeHeightAnchor, keychain::ChangeSet<Keychain>>,
+ indexed_tx_graph::ChangeSet<ConfirmationTimeHeightAnchor, keychain_txout::ChangeSet<Keychain>>,
);
#[derive(Subcommand, Debug, Clone)]