]> Untitled Git - bdk/commitdiff
ref(chain)!: create module `indexer`
authorvalued mammal <valuedmammal@protonmail.com>
Fri, 28 Jun 2024 13:07:36 +0000 (09:07 -0400)
committervalued mammal <valuedmammal@protonmail.com>
Fri, 5 Jul 2024 16:22:28 +0000 (12:22 -0400)
and replace keychain module with `balance.rs`

28 files changed:
crates/bitcoind_rpc/tests/test_emitter.rs
crates/chain/src/balance.rs [new file with mode: 0644]
crates/chain/src/changeset.rs
crates/chain/src/indexed_tx_graph.rs
crates/chain/src/indexer.rs [new file with mode: 0644]
crates/chain/src/indexer/keychain_txout.rs [new file with mode: 0644]
crates/chain/src/indexer/spk_txout.rs [new file with mode: 0644]
crates/chain/src/keychain.rs [deleted file]
crates/chain/src/keychain/txout_index.rs [deleted file]
crates/chain/src/lib.rs
crates/chain/src/spk_client.rs
crates/chain/src/spk_iter.rs
crates/chain/src/spk_txout_index.rs [deleted file]
crates/chain/src/tx_graph.rs
crates/chain/tests/test_indexed_tx_graph.rs
crates/chain/tests/test_keychain_txout_index.rs
crates/chain/tests/test_spk_txout_index.rs
crates/chain/tests/test_tx_graph_conflicts.rs
crates/electrum/tests/test_electrum.rs
crates/esplora/src/async_ext.rs
crates/esplora/src/blocking_ext.rs
crates/sqlite/src/store.rs
crates/wallet/src/wallet/mod.rs
crates/wallet/tests/common.rs
example-crates/example_bitcoind_rpc_polling/src/main.rs
example-crates/example_cli/src/lib.rs
example-crates/example_electrum/src/main.rs
example-crates/example_esplora/src/main.rs

index 2c8b1e11a71a99fb4b8071f54c20619efc6fe710..2c2c863d64341cb6c8a928c9457e56b4fd69f1bd 100644 (file)
@@ -3,9 +3,8 @@ use std::collections::{BTreeMap, BTreeSet};
 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};
diff --git a/crates/chain/src/balance.rs b/crates/chain/src/balance.rs
new file mode 100644 (file)
index 0000000..569755a
--- /dev/null
@@ -0,0 +1,57 @@
+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,
+        }
+    }
+}
index d84228409bf82f6fa0fabccd4d374194ba35945c..3dabc5a40204e22cb0f27a8481ddd3dd0eacea05 100644 (file)
@@ -16,7 +16,8 @@ pub struct CombinedChangeSet<K, A> {
     /// 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>,
 }
@@ -62,11 +63,14 @@ impl<K, A> From<crate::local_chain::ChangeSet> for CombinedChangeSet<K, A> {
 }
 
 #[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,
@@ -76,8 +80,8 @@ impl<K, A> From<crate::indexed_tx_graph::ChangeSet<A, crate::keychain::ChangeSet
 }
 
 #[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,
index 3848ba64b1715b9b683fd83b9acbed7708d6a906..41392c02aa9148ab744c4cf4ee23b8b4a2d56462 100644 (file)
@@ -5,7 +5,7 @@ use bitcoin::{Block, OutPoint, Transaction, TxOut, Txid};
 
 use crate::{
     tx_graph::{self, TxGraph},
-    Anchor, AnchorFromBlockPosition, Append, BlockId,
+    Anchor, AnchorFromBlockPosition, Append, BlockId, Indexer,
 };
 
 /// The [`IndexedTxGraph`] combines a [`TxGraph`] and an [`Indexer`] implementation.
@@ -320,8 +320,10 @@ impl<A, IA: Default> From<tx_graph::ChangeSet<A>> for ChangeSet<A, IA> {
 }
 
 #[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,
@@ -329,30 +331,6 @@ impl<A, K> From<crate::keychain::ChangeSet<K>> for ChangeSet<A, crate::keychain:
     }
 }
 
-/// 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
diff --git a/crates/chain/src/indexer.rs b/crates/chain/src/indexer.rs
new file mode 100644 (file)
index 0000000..22e8398
--- /dev/null
@@ -0,0 +1,33 @@
+//! [`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;
+}
diff --git a/crates/chain/src/indexer/keychain_txout.rs b/crates/chain/src/indexer/keychain_txout.rs
new file mode 100644 (file)
index 0000000..1d9f41f
--- /dev/null
@@ -0,0 +1,946 @@
+//! [`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)
+    }
+}
diff --git a/crates/chain/src/indexer/spk_txout.rs b/crates/chain/src/indexer/spk_txout.rs
new file mode 100644 (file)
index 0000000..ead446a
--- /dev/null
@@ -0,0 +1,334 @@
+//! [`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
+    }
+}
diff --git a/crates/chain/src/keychain.rs b/crates/chain/src/keychain.rs
deleted file mode 100644 (file)
index 2a0500a..0000000
+++ /dev/null
@@ -1,78 +0,0 @@
-//! 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,
-        }
-    }
-}
diff --git a/crates/chain/src/keychain/txout_index.rs b/crates/chain/src/keychain/txout_index.rs
deleted file mode 100644 (file)
index 758f16c..0000000
+++ /dev/null
@@ -1,946 +0,0 @@
-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)
-    }
-}
index 94f81441eabd0735d5e6de56f69015f9b06cf3f9..ec0c61a35db3cf2c4c64f956f02e52c6e369e192 100644 (file)
 #![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;
@@ -98,3 +99,8 @@ pub mod collections {
 
 /// 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);
index 24bd9b4a7fa0b3971fe48d516c2340fdd5cbca10..1ddf7a6d17c02dea59134daaff1a7312f4f42272 100644 (file)
@@ -1,8 +1,7 @@
 //! 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};
@@ -160,7 +159,7 @@ impl SyncRequest {
     #[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;
@@ -216,12 +215,12 @@ impl<K: Ord + Clone> FullScanRequest<K> {
     /// [`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,
index dd4c0a8f56294f4b937728b7d1eb2d796e4312e1..7228b71942cab8e9a27d0122db286fbdb80d71d4 100644 (file)
@@ -1,7 +1,7 @@
 use crate::{
     bitcoin::{secp256k1::Secp256k1, ScriptBuf},
-    keychain::Indexed,
     miniscript::{Descriptor, DescriptorPublicKey},
+    Indexed,
 };
 use core::{borrow::Borrow, ops::Bound, ops::RangeBounds};
 
@@ -137,7 +137,7 @@ where
 mod test {
     use crate::{
         bitcoin::secp256k1::Secp256k1,
-        keychain::KeychainTxOutIndex,
+        indexer::keychain_txout::KeychainTxOutIndex,
         miniscript::{Descriptor, DescriptorPublicKey},
         spk_iter::{SpkIterator, BIP32_MAX_INDEX},
     };
diff --git a/crates/chain/src/spk_txout_index.rs b/crates/chain/src/spk_txout_index.rs
deleted file mode 100644 (file)
index f664af8..0000000
+++ /dev/null
@@ -1,332 +0,0 @@
-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
-    }
-}
index b79aa434ec6109bc2e77f82dcd960af4ed914005..1532d13a0910c65396b9886aaf774f3104b577e2 100644 (file)
@@ -89,8 +89,7 @@
 //! [`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;
index dbd7fd302b293e6a92e95b78ea516a846308811a..57f9837404205fd33ccc40443ce9c3908ed9ea4d 100644 (file)
@@ -8,9 +8,9 @@ use std::{collections::BTreeSet, 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,
@@ -26,6 +26,7 @@ use miniscript::Descriptor;
 /// 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();
@@ -76,7 +77,7 @@ fn insert_relevant_txs() {
             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(),
         },
@@ -90,7 +91,7 @@ fn insert_relevant_txs() {
     // 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(),
         },
index 2c1c34e705ba0951686232fc959b13d8cd805489..751786dd221dd965c64f1deafe8cbc5b81772ffb 100644 (file)
@@ -4,9 +4,8 @@
 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};
@@ -31,8 +30,8 @@ fn init_txout_index(
     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)
@@ -146,8 +145,6 @@ fn when_apply_contradictory_changesets_they_are_ignored() {
 
 #[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 =
@@ -169,7 +166,7 @@ fn test_set_all_derivation_indices() {
     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);
@@ -304,7 +301,7 @@ fn test_lookahead() {
             ],
             ..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)
@@ -643,14 +640,14 @@ fn insert_descriptor_no_change() {
     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",
     );
 }
index 8be252703c0011f8d663aed45720e4123a99806b..ccad2af04f75f0041172cd7b22093a41cb9d9073 100644 (file)
@@ -1,4 +1,4 @@
-use bdk_chain::{indexed_tx_graph::Indexer, SpkTxOutIndex};
+use bdk_chain::{Indexer, SpkTxOutIndex};
 use bitcoin::{
     absolute, transaction, Amount, OutPoint, ScriptBuf, SignedAmount, Transaction, TxIn, TxOut,
 };
index 512b076d68ba000eb65e0c99125718e6b412e6cf..802ba5c7e4a06bd37e6265c4347633586b9a7078 100644 (file)
@@ -5,7 +5,7 @@ mod common;
 
 use std::collections::{BTreeSet, HashSet};
 
-use bdk_chain::{keychain::Balance, BlockId};
+use bdk_chain::{Balance, BlockId};
 use bitcoin::{Amount, OutPoint, Script};
 use common::*;
 
index 9788b251424b7ce167cf0c77b4988cf5254a46e9..1befb326c8d9ec02a6844d25fb1157f23aaa0dc3 100644 (file)
@@ -1,9 +1,8 @@
 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};
index dff52fdd7726cd371edf6684f67f32a4164d0ff9..939c43a2f68d23b18127cfe4f29ade60fbc29f84 100644 (file)
@@ -231,7 +231,7 @@ async fn chain_update<A: Anchor>(
 }
 
 /// 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<
index 5093b4ef55ffcdabd095c6af4b02f7b9fa6ac87f..adad25c2e5ece6a72c12b2b4566b4cd111ba53e6 100644 (file)
@@ -213,7 +213,7 @@ fn chain_update<A: Anchor>(
 }
 
 /// 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>>>,
index 4cd81f50b28d5a05b2904e3b70ef164790a31e49..82b25b5d5945f4e989bec6d03b97b5b4ca2d0389 100644 (file)
@@ -14,7 +14,8 @@ use std::sync::{Arc, Mutex};
 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.
@@ -187,7 +188,7 @@ where
     /// 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() {
@@ -206,7 +207,7 @@ where
     /// 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() {
@@ -279,7 +280,7 @@ impl<K, A> Store<K, A> {
     /// 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
@@ -343,7 +344,7 @@ impl<K, A> Store<K, A> {
     /// 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
@@ -393,7 +394,7 @@ impl<K, A> Store<K, A> {
     /// 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
@@ -418,7 +419,7 @@ where
     /// 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() {
@@ -514,12 +515,12 @@ where
             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() {
@@ -547,7 +548,7 @@ mod test {
     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;
@@ -684,12 +685,12 @@ mod test {
             .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,
@@ -712,10 +713,10 @@ mod test {
             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 {
@@ -732,10 +733,10 @@ mod test {
             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 {
index 23d7eb114d0ecef60a088e351a1dfc2053b6aa8b..be158a06e6e42585b265ed5a727b5da9148559eb 100644 (file)
@@ -19,10 +19,10 @@ use alloc::{
     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,
     },
@@ -112,7 +112,7 @@ pub struct Wallet {
 
 /// 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
@@ -2451,7 +2451,7 @@ fn create_signers<E: IntoWalletDescriptor>(
     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
index 0a3e479b80c1585b21abf5f32b48316ea949be44..c124ba21343beeba48205710aa2464baa329454f 100644 (file)
@@ -1,13 +1,12 @@
 #![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;
 
index 38e796474c8a17103a35cff1f3acd3decc89f577..084b9381279c704d5c559ade15cc8e75d0ed7ffb 100644 (file)
@@ -13,7 +13,8 @@ use bdk_bitcoind_rpc::{
 };
 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,
 };
@@ -37,7 +38,7 @@ const DB_COMMIT_DELAY: Duration = Duration::from_secs(60);
 
 type ChangeSet = (
     local_chain::ChangeSet,
-    indexed_tx_graph::ChangeSet<ConfirmationTimeHeightAnchor, keychain::ChangeSet<Keychain>>,
+    indexed_tx_graph::ChangeSet<ConfirmationTimeHeightAnchor, keychain_txout::ChangeSet<Keychain>>,
 );
 
 #[derive(Debug)]
index b1862fb8193336276fbddb6653707cae880339f7..bd2e3b8c968727fc3a2a99738ee304deb1646624 100644 (file)
@@ -14,7 +14,7 @@ use bdk_chain::{
         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},
@@ -30,7 +30,7 @@ use clap::{Parser, Subcommand};
 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)]
@@ -191,7 +191,7 @@ impl core::fmt::Display for Keychain {
 }
 
 pub struct CreateTxChange {
-    pub index_changeset: keychain::ChangeSet<Keychain>,
+    pub index_changeset: keychain_txout::ChangeSet<Keychain>,
     pub change_keychain: Keychain,
     pub index: u32,
 }
@@ -207,7 +207,7 @@ pub fn create_tx<A: Anchor, O: ChainOracle>(
 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(),
index 79c6f79e77006197da10f411b333f166148ce774..2ea4d86f02af6193be855a71e7538a0065dc9c3e 100644 (file)
@@ -7,7 +7,7 @@ use bdk_chain::{
     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,
@@ -100,7 +100,7 @@ pub struct ScanOptions {
 
 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<()> {
index 60cf1ef3c38339e09f62936317943a9fbfbc0e4f..49b3167d6c4fefc66954adf2a7bc659d901e32b1 100644 (file)
@@ -7,7 +7,7 @@ use std::{
 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,
@@ -26,7 +26,7 @@ const DB_PATH: &str = ".bdk_esplora_example.db";
 
 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)]