diff --git a/crates/bitcoind_rpc/examples/filter_iter.rs b/crates/bitcoind_rpc/examples/filter_iter.rs index 55c3325d7..7dc88b60e 100644 --- a/crates/bitcoind_rpc/examples/filter_iter.rs +++ b/crates/bitcoind_rpc/examples/filter_iter.rs @@ -7,7 +7,7 @@ use bdk_chain::bitcoin::{constants::genesis_block, secp256k1::Secp256k1, Network use bdk_chain::indexer::keychain_txout::KeychainTxOutIndex; use bdk_chain::local_chain::LocalChain; use bdk_chain::miniscript::Descriptor; -use bdk_chain::{BlockId, ConfirmationBlockTime, IndexedTxGraph, SpkIterator}; +use bdk_chain::{BlockId, ConfirmationBlockTime, SpkIterator, TxGraph}; use bdk_testenv::anyhow; use bitcoin::Address; @@ -31,7 +31,7 @@ fn main() -> anyhow::Result<()> { let (descriptor, _) = Descriptor::parse_descriptor(&secp, EXTERNAL)?; let (change_descriptor, _) = Descriptor::parse_descriptor(&secp, INTERNAL)?; let (mut chain, _) = LocalChain::from_genesis_hash(genesis_block(NETWORK).block_hash()); - let mut graph = IndexedTxGraph::>::new({ + let mut graph = TxGraph::>::new({ let mut index = KeychainTxOutIndex::default(); index.insert_descriptor("external", descriptor.clone())?; index.insert_descriptor("internal", change_descriptor.clone())?; @@ -88,7 +88,6 @@ fn main() -> anyhow::Result<()> { println!("\ntook: {}s", start.elapsed().as_secs()); println!("Local tip: {}", chain.tip().height()); let unspent: Vec<_> = graph - .graph() .filter_chain_unspents( &chain, chain.tip().block_id(), diff --git a/crates/bitcoind_rpc/tests/test_emitter.rs b/crates/bitcoind_rpc/tests/test_emitter.rs index 14b0c9212..3185e6b34 100644 --- a/crates/bitcoind_rpc/tests/test_emitter.rs +++ b/crates/bitcoind_rpc/tests/test_emitter.rs @@ -5,7 +5,7 @@ use bdk_chain::{ bitcoin::{Address, Amount, Txid}, local_chain::{CheckPoint, LocalChain}, spk_txout::SpkTxOutIndex, - Balance, BlockId, IndexedTxGraph, Merge, + Balance, BlockId, Merge, TxGraph, }; use bdk_testenv::{anyhow, TestEnv}; use bitcoin::{hashes::Hash, Block, OutPoint, ScriptBuf, WScriptHash}; @@ -148,7 +148,7 @@ fn test_into_tx_graph() -> anyhow::Result<()> { env.mine_blocks(101, None)?; let (mut chain, _) = LocalChain::from_genesis_hash(env.rpc_client().get_block_hash(0)?); - let mut indexed_tx_graph = IndexedTxGraph::::new({ + let mut tx_graph = TxGraph::::new({ let mut index = SpkTxOutIndex::::default(); index.insert_spk(0, addr_0.script_pubkey()); index.insert_spk(1, addr_1.script_pubkey()); @@ -161,8 +161,8 @@ fn test_into_tx_graph() -> anyhow::Result<()> { while let Some(emission) = emitter.next_block()? { let height = emission.block_height(); let _ = chain.apply_update(emission.checkpoint)?; - let indexed_additions = indexed_tx_graph.apply_block_relevant(&emission.block, height); - assert!(indexed_additions.is_empty()); + let tx_graph_changeset = tx_graph.apply_block_relevant(&emission.block, height); + assert!(tx_graph_changeset.is_empty()); } // send 3 txs to a tracked address, these txs will be in the mempool @@ -189,10 +189,9 @@ fn test_into_tx_graph() -> anyhow::Result<()> { assert!(emitter.next_block()?.is_none()); let mempool_txs = emitter.mempool()?; - let indexed_additions = indexed_tx_graph.batch_insert_unconfirmed(mempool_txs); + let tx_graph_changeset = tx_graph.batch_insert_unconfirmed(mempool_txs); assert_eq!( - indexed_additions - .tx_graph + tx_graph_changeset .txs .iter() .map(|tx| tx.compute_txid()) @@ -200,7 +199,7 @@ fn test_into_tx_graph() -> anyhow::Result<()> { exp_txids, "changeset should have the 3 mempool transactions", ); - assert!(indexed_additions.tx_graph.anchors.is_empty()); + assert!(tx_graph_changeset.anchors.is_empty()); } // mine a block that confirms the 3 txs @@ -222,10 +221,10 @@ fn test_into_tx_graph() -> anyhow::Result<()> { let emission = emitter.next_block()?.expect("must get mined block"); let height = emission.block_height(); let _ = chain.apply_update(emission.checkpoint)?; - let indexed_additions = indexed_tx_graph.apply_block_relevant(&emission.block, height); - assert!(indexed_additions.tx_graph.txs.is_empty()); - assert!(indexed_additions.tx_graph.txouts.is_empty()); - assert_eq!(indexed_additions.tx_graph.anchors, exp_anchors); + let tx_graph_changeset = tx_graph.apply_block_relevant(&emission.block, height); + assert!(tx_graph_changeset.txs.is_empty()); + assert!(tx_graph_changeset.txouts.is_empty()); + assert_eq!(tx_graph_changeset.anchors, exp_anchors); } Ok(()) @@ -276,7 +275,7 @@ fn ensure_block_emitted_after_reorg_is_at_reorg_height() -> anyhow::Result<()> { fn process_block( recv_chain: &mut LocalChain, - recv_graph: &mut IndexedTxGraph>, + recv_graph: &mut TxGraph>, block: Block, block_height: u32, ) -> anyhow::Result<()> { @@ -287,7 +286,7 @@ fn process_block( fn sync_from_emitter( recv_chain: &mut LocalChain, - recv_graph: &mut IndexedTxGraph>, + recv_graph: &mut TxGraph>, emitter: &mut Emitter, ) -> anyhow::Result<()> where @@ -302,13 +301,11 @@ where fn get_balance( recv_chain: &LocalChain, - recv_graph: &IndexedTxGraph>, + recv_graph: &TxGraph>, ) -> anyhow::Result { let chain_tip = recv_chain.tip().block_id(); let outpoints = recv_graph.index.outpoints().clone(); - let balance = recv_graph - .graph() - .balance(recv_chain, chain_tip, outpoints, |_, _| true); + let balance = recv_graph.balance(recv_chain, chain_tip, outpoints, |_, _| true); Ok(balance) } @@ -340,7 +337,7 @@ fn tx_can_become_unconfirmed_after_reorg() -> anyhow::Result<()> { // setup receiver let (mut recv_chain, _) = LocalChain::from_genesis_hash(env.rpc_client().get_block_hash(0)?); - let mut recv_graph = IndexedTxGraph::::new({ + let mut recv_graph = TxGraph::::new({ let mut recv_index = SpkTxOutIndex::default(); recv_index.insert_spk((), spk_to_track.clone()); recv_index diff --git a/crates/chain/benches/canonicalization.rs b/crates/chain/benches/canonicalization.rs index 3002a7ca3..f28199be9 100644 --- a/crates/chain/benches/canonicalization.rs +++ b/crates/chain/benches/canonicalization.rs @@ -1,4 +1,4 @@ -use bdk_chain::{keychain_txout::KeychainTxOutIndex, local_chain::LocalChain, IndexedTxGraph}; +use bdk_chain::{keychain_txout::KeychainTxOutIndex, local_chain::LocalChain, TxGraph}; use bdk_core::{BlockId, CheckPoint}; use bdk_core::{ConfirmationBlockTime, TxUpdate}; use bdk_testenv::hash; @@ -11,7 +11,7 @@ use miniscript::{Descriptor, DescriptorPublicKey}; use std::sync::Arc; type Keychain = (); -type KeychainTxGraph = IndexedTxGraph>; +type KeychainTxGraph = TxGraph>; /// New tx guaranteed to have at least one output fn new_tx(lt: u32) -> Transaction { @@ -90,14 +90,12 @@ fn setup(f: F) -> (KeychainTxGraph, Lo } fn run_list_canonical_txs(tx_graph: &KeychainTxGraph, chain: &LocalChain, exp_txs: usize) { - let txs = tx_graph - .graph() - .list_canonical_txs(chain, chain.tip().block_id()); + let txs = tx_graph.list_canonical_txs(chain, chain.tip().block_id()); assert_eq!(txs.count(), exp_txs); } fn run_filter_chain_txouts(tx_graph: &KeychainTxGraph, chain: &LocalChain, exp_txos: usize) { - let utxos = tx_graph.graph().filter_chain_txouts( + let utxos = tx_graph.filter_chain_txouts( chain, chain.tip().block_id(), tx_graph.index.outpoints().clone(), @@ -106,7 +104,7 @@ fn run_filter_chain_txouts(tx_graph: &KeychainTxGraph, chain: &LocalChain, exp_t } fn run_filter_chain_unspents(tx_graph: &KeychainTxGraph, chain: &LocalChain, exp_utxos: usize) { - let utxos = tx_graph.graph().filter_chain_unspents( + let utxos = tx_graph.filter_chain_unspents( chain, chain.tip().block_id(), tx_graph.index.outpoints().clone(), diff --git a/crates/chain/src/canonical_iter.rs b/crates/chain/src/canonical_iter.rs index 99550ab7f..8f2b4d0d8 100644 --- a/crates/chain/src/canonical_iter.rs +++ b/crates/chain/src/canonical_iter.rs @@ -8,8 +8,8 @@ use bdk_core::BlockId; use bitcoin::{Transaction, Txid}; /// Iterates over canonical txs. -pub struct CanonicalIter<'g, A, C> { - tx_graph: &'g TxGraph, +pub struct CanonicalIter<'g, A, X, C> { + tx_graph: &'g TxGraph, chain: &'g C, chain_tip: BlockId, @@ -24,9 +24,9 @@ pub struct CanonicalIter<'g, A, C> { queue: VecDeque, } -impl<'g, A: Anchor, C: ChainOracle> CanonicalIter<'g, A, C> { +impl<'g, A: Anchor, X, C: ChainOracle> CanonicalIter<'g, A, X, C> { /// Constructs [`CanonicalIter`]. - pub fn new(tx_graph: &'g TxGraph, chain: &'g C, chain_tip: BlockId) -> Self { + pub fn new(tx_graph: &'g TxGraph, chain: &'g C, chain_tip: BlockId) -> Self { let anchors = tx_graph.all_anchors(); let pending_anchored = Box::new( tx_graph @@ -133,7 +133,7 @@ impl<'g, A: Anchor, C: ChainOracle> CanonicalIter<'g, A, C> { } } -impl Iterator for CanonicalIter<'_, A, C> { +impl Iterator for CanonicalIter<'_, A, X, C> { type Item = Result<(Txid, Arc, CanonicalReason), C::Error>; fn next(&mut self) -> Option { diff --git a/crates/chain/src/indexed_tx_graph.rs b/crates/chain/src/indexed_tx_graph.rs deleted file mode 100644 index 039924c92..000000000 --- a/crates/chain/src/indexed_tx_graph.rs +++ /dev/null @@ -1,390 +0,0 @@ -//! Contains the [`IndexedTxGraph`] and associated types. Refer to the -//! [`IndexedTxGraph`] documentation for more. -use core::fmt::Debug; - -use alloc::{sync::Arc, vec::Vec}; -use bitcoin::{Block, OutPoint, Transaction, TxOut, Txid}; - -use crate::{ - tx_graph::{self, TxGraph}, - Anchor, BlockId, Indexer, Merge, TxPosInBlock, -}; - -/// The [`IndexedTxGraph`] combines a [`TxGraph`] and an [`Indexer`] implementation. -/// -/// It ensures that [`TxGraph`] and [`Indexer`] are updated atomically. -#[derive(Debug, Clone)] -pub struct IndexedTxGraph { - /// Transaction index. - pub index: I, - graph: TxGraph, -} - -impl Default for IndexedTxGraph { - fn default() -> Self { - Self { - graph: Default::default(), - index: Default::default(), - } - } -} - -impl IndexedTxGraph { - /// Construct a new [`IndexedTxGraph`] with a given `index`. - pub fn new(index: I) -> Self { - Self { - index, - graph: TxGraph::default(), - } - } - - /// Get a reference of the internal transaction graph. - pub fn graph(&self) -> &TxGraph { - &self.graph - } -} - -impl IndexedTxGraph { - /// Applies the [`ChangeSet`] to the [`IndexedTxGraph`]. - pub fn apply_changeset(&mut self, changeset: ChangeSet) { - self.index.apply_changeset(changeset.indexer); - - for tx in &changeset.tx_graph.txs { - self.index.index_tx(tx); - } - for (&outpoint, txout) in &changeset.tx_graph.txouts { - self.index.index_txout(outpoint, txout); - } - - self.graph.apply_changeset(changeset.tx_graph); - } - - /// Determines the [`ChangeSet`] between `self` and an empty [`IndexedTxGraph`]. - pub fn initial_changeset(&self) -> ChangeSet { - let graph = self.graph.initial_changeset(); - let indexer = self.index.initial_changeset(); - ChangeSet { - tx_graph: graph, - indexer, - } - } -} - -impl IndexedTxGraph -where - I::ChangeSet: Default + Merge, -{ - fn index_tx_graph_changeset( - &mut self, - tx_graph_changeset: &tx_graph::ChangeSet, - ) -> I::ChangeSet { - let mut changeset = I::ChangeSet::default(); - for added_tx in &tx_graph_changeset.txs { - changeset.merge(self.index.index_tx(added_tx)); - } - for (&added_outpoint, added_txout) in &tx_graph_changeset.txouts { - changeset.merge(self.index.index_txout(added_outpoint, added_txout)); - } - changeset - } - - /// Apply an `update` directly. - /// - /// `update` is a [`tx_graph::TxUpdate`] and the resultant changes is returned as [`ChangeSet`]. - #[cfg(feature = "std")] - #[cfg_attr(docsrs, doc(cfg(feature = "std")))] - pub fn apply_update(&mut self, update: tx_graph::TxUpdate) -> ChangeSet { - let tx_graph = self.graph.apply_update(update); - let indexer = self.index_tx_graph_changeset(&tx_graph); - ChangeSet { tx_graph, indexer } - } - - /// Apply the given `update` with an optional `seen_at` timestamp. - /// - /// `seen_at` represents when the update is seen (in unix seconds). It is used to determine the - /// `last_seen`s for all transactions in the update which have no corresponding anchor(s). The - /// `last_seen` value is used internally to determine precedence of conflicting unconfirmed - /// transactions (where the transaction with the lower `last_seen` value is omitted from the - /// canonical history). - /// - /// Not setting a `seen_at` value means unconfirmed transactions introduced by this update will - /// not be part of the canonical history of transactions. - /// - /// Use [`apply_update`](IndexedTxGraph::apply_update) to have the `seen_at` value automatically - /// set to the current time. - pub fn apply_update_at( - &mut self, - update: tx_graph::TxUpdate, - seen_at: Option, - ) -> ChangeSet { - let tx_graph = self.graph.apply_update_at(update, seen_at); - let indexer = self.index_tx_graph_changeset(&tx_graph); - ChangeSet { tx_graph, indexer } - } - - /// Insert a floating `txout` of given `outpoint`. - pub fn insert_txout(&mut self, outpoint: OutPoint, txout: TxOut) -> ChangeSet { - let graph = self.graph.insert_txout(outpoint, txout); - let indexer = self.index_tx_graph_changeset(&graph); - ChangeSet { - tx_graph: graph, - indexer, - } - } - - /// Insert and index a transaction into the graph. - pub fn insert_tx>>(&mut self, tx: T) -> ChangeSet { - let tx_graph = self.graph.insert_tx(tx); - let indexer = self.index_tx_graph_changeset(&tx_graph); - ChangeSet { tx_graph, indexer } - } - - /// Insert an `anchor` for a given transaction. - pub fn insert_anchor(&mut self, txid: Txid, anchor: A) -> ChangeSet { - self.graph.insert_anchor(txid, anchor).into() - } - - /// Insert a unix timestamp of when a transaction is seen in the mempool. - /// - /// This is used for transaction conflict resolution in [`TxGraph`] where the transaction with - /// the later last-seen is prioritized. - pub fn insert_seen_at(&mut self, txid: Txid, seen_at: u64) -> ChangeSet { - self.graph.insert_seen_at(txid, seen_at).into() - } - - /// Batch insert transactions, filtering out those that are irrelevant. - /// - /// Relevancy is determined by the [`Indexer::is_tx_relevant`] implementation of `I`. Irrelevant - /// transactions in `txs` will be ignored. `txs` do not need to be in topological order. - pub fn batch_insert_relevant>>( - &mut self, - txs: impl IntoIterator)>, - ) -> ChangeSet { - // The algorithm below allows for non-topologically ordered transactions by using two loops. - // This is achieved by: - // 1. insert all txs into the index. If they are irrelevant then that's fine it will just - // not store anything about them. - // 2. decide whether to insert them into the graph depending on whether `is_tx_relevant` - // returns true or not. (in a second loop). - let txs = txs - .into_iter() - .map(|(tx, anchors)| (>>::into(tx), anchors)) - .collect::>(); - - let mut indexer = I::ChangeSet::default(); - for (tx, _) in &txs { - indexer.merge(self.index.index_tx(tx)); - } - - let mut tx_graph = tx_graph::ChangeSet::default(); - for (tx, anchors) in txs { - if self.index.is_tx_relevant(&tx) { - let txid = tx.compute_txid(); - tx_graph.merge(self.graph.insert_tx(tx.clone())); - for anchor in anchors { - tx_graph.merge(self.graph.insert_anchor(txid, anchor)); - } - } - } - - ChangeSet { tx_graph, indexer } - } - - /// Batch insert unconfirmed transactions, filtering out those that are irrelevant. - /// - /// Relevancy is determined by the internal [`Indexer::is_tx_relevant`] implementation of `I`. - /// Irrelevant transactions in `txs` will be ignored. - /// - /// Items of `txs` are tuples containing the transaction and a *last seen* timestamp. The - /// *last seen* communicates when the transaction is last seen in the mempool which is used for - /// conflict-resolution in [`TxGraph`] (refer to [`TxGraph::insert_seen_at`] for details). - pub fn batch_insert_relevant_unconfirmed>>( - &mut self, - unconfirmed_txs: impl IntoIterator, - ) -> ChangeSet { - // The algorithm below allows for non-topologically ordered transactions by using two loops. - // This is achieved by: - // 1. insert all txs into the index. If they are irrelevant then that's fine it will just - // not store anything about them. - // 2. decide whether to insert them into the graph depending on whether `is_tx_relevant` - // returns true or not. (in a second loop). - let txs = unconfirmed_txs - .into_iter() - .map(|(tx, last_seen)| (>>::into(tx), last_seen)) - .collect::>(); - - let mut indexer = I::ChangeSet::default(); - for (tx, _) in &txs { - indexer.merge(self.index.index_tx(tx)); - } - - let graph = self.graph.batch_insert_unconfirmed( - txs.into_iter() - .filter(|(tx, _)| self.index.is_tx_relevant(tx)) - .map(|(tx, seen_at)| (tx.clone(), seen_at)), - ); - - ChangeSet { - tx_graph: graph, - indexer, - } - } - - /// Batch insert unconfirmed transactions. - /// - /// Items of `txs` are tuples containing the transaction and a *last seen* timestamp. The - /// *last seen* communicates when the transaction is last seen in the mempool which is used for - /// conflict-resolution in [`TxGraph`] (refer to [`TxGraph::insert_seen_at`] for details). - /// - /// To filter out irrelevant transactions, use [`batch_insert_relevant_unconfirmed`] instead. - /// - /// [`batch_insert_relevant_unconfirmed`]: IndexedTxGraph::batch_insert_relevant_unconfirmed - pub fn batch_insert_unconfirmed>>( - &mut self, - txs: impl IntoIterator, - ) -> ChangeSet { - let graph = self.graph.batch_insert_unconfirmed(txs); - let indexer = self.index_tx_graph_changeset(&graph); - ChangeSet { - tx_graph: graph, - indexer, - } - } -} - -/// Methods are available if the anchor (`A`) can be created from [`TxPosInBlock`]. -impl IndexedTxGraph -where - I::ChangeSet: Default + Merge, - for<'b> A: Anchor + From>, - I: Indexer, -{ - /// Batch insert all transactions of the given `block` of `height`, filtering out those that are - /// irrelevant. - /// - /// Each inserted transaction's anchor will be constructed using [`TxPosInBlock`]. - /// - /// Relevancy is determined by the internal [`Indexer::is_tx_relevant`] implementation of `I`. - /// Irrelevant transactions in `txs` will be ignored. - pub fn apply_block_relevant( - &mut self, - block: &Block, - height: u32, - ) -> ChangeSet { - let block_id = BlockId { - hash: block.block_hash(), - height, - }; - let mut changeset = ChangeSet::::default(); - for (tx_pos, tx) in block.txdata.iter().enumerate() { - changeset.indexer.merge(self.index.index_tx(tx)); - if self.index.is_tx_relevant(tx) { - let txid = tx.compute_txid(); - let anchor = TxPosInBlock { - block, - block_id, - tx_pos, - } - .into(); - changeset.tx_graph.merge(self.graph.insert_tx(tx.clone())); - changeset - .tx_graph - .merge(self.graph.insert_anchor(txid, anchor)); - } - } - changeset - } - - /// Batch insert all transactions of the given `block` of `height`. - /// - /// Each inserted transaction's anchor will be constructed using [`TxPosInBlock`]. - /// - /// To only insert relevant transactions, use [`apply_block_relevant`] instead. - /// - /// [`apply_block_relevant`]: IndexedTxGraph::apply_block_relevant - pub fn apply_block(&mut self, block: Block, height: u32) -> ChangeSet { - let block_id = BlockId { - hash: block.block_hash(), - height, - }; - let mut graph = tx_graph::ChangeSet::default(); - for (tx_pos, tx) in block.txdata.iter().enumerate() { - let anchor = TxPosInBlock { - block: &block, - block_id, - tx_pos, - } - .into(); - graph.merge(self.graph.insert_anchor(tx.compute_txid(), anchor)); - graph.merge(self.graph.insert_tx(tx.clone())); - } - let indexer = self.index_tx_graph_changeset(&graph); - ChangeSet { - tx_graph: graph, - indexer, - } - } -} - -impl AsRef> for IndexedTxGraph { - fn as_ref(&self) -> &TxGraph { - &self.graph - } -} - -/// Represents changes to an [`IndexedTxGraph`]. -#[derive(Clone, Debug, PartialEq)] -#[cfg_attr( - feature = "serde", - derive(serde::Deserialize, serde::Serialize), - serde(bound( - deserialize = "A: Ord + serde::Deserialize<'de>, IA: serde::Deserialize<'de>", - serialize = "A: Ord + serde::Serialize, IA: serde::Serialize" - )) -)] -#[must_use] -pub struct ChangeSet { - /// [`TxGraph`] changeset. - pub tx_graph: tx_graph::ChangeSet, - /// [`Indexer`] changeset. - pub indexer: IA, -} - -impl Default for ChangeSet { - fn default() -> Self { - Self { - tx_graph: Default::default(), - indexer: Default::default(), - } - } -} - -impl Merge for ChangeSet { - fn merge(&mut self, other: Self) { - self.tx_graph.merge(other.tx_graph); - self.indexer.merge(other.indexer); - } - - fn is_empty(&self) -> bool { - self.tx_graph.is_empty() && self.indexer.is_empty() - } -} - -impl From> for ChangeSet { - fn from(graph: tx_graph::ChangeSet) -> Self { - Self { - tx_graph: graph, - ..Default::default() - } - } -} - -#[cfg(feature = "miniscript")] -impl From for ChangeSet { - fn from(indexer: crate::keychain_txout::ChangeSet) -> Self { - Self { - tx_graph: Default::default(), - indexer, - } - } -} diff --git a/crates/chain/src/indexer.rs b/crates/chain/src/indexer.rs index 22e839815..bc2dbc815 100644 --- a/crates/chain/src/indexer.rs +++ b/crates/chain/src/indexer.rs @@ -8,13 +8,14 @@ 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. +/// An `Indexer` is the second type parameter of a [`TxGraph`]. The `TxGraph` calls the +/// indexer whenever new transaction data is inserted into it, allowing the indexer to look at the +/// new data and mutate its state. /// -/// [`IndexedTxGraph`]: crate::IndexedTxGraph +/// [`TxGraph`]: crate::TxGraph pub trait Indexer { /// The resultant "changeset" when new transaction data is indexed. - type ChangeSet; + type ChangeSet: Default + crate::Merge; /// Scan and index the given `outpoint` and `txout`. fn index_txout(&mut self, outpoint: OutPoint, txout: &TxOut) -> Self::ChangeSet; @@ -31,3 +32,19 @@ pub trait Indexer { /// Determines whether the transaction should be included in the index. fn is_tx_relevant(&self, tx: &Transaction) -> bool; } + +impl Indexer for () { + type ChangeSet = (); + + fn index_txout(&mut self, _outpoint: OutPoint, _txout: &TxOut) -> Self::ChangeSet {} + + fn index_tx(&mut self, _tx: &Transaction) -> Self::ChangeSet {} + + fn apply_changeset(&mut self, _changeset: Self::ChangeSet) {} + + fn initial_changeset(&self) -> Self::ChangeSet {} + + fn is_tx_relevant(&self, _tx: &Transaction) -> bool { + false + } +} diff --git a/crates/chain/src/lib.rs b/crates/chain/src/lib.rs index 92a6d5c4e..7a873d09f 100644 --- a/crates/chain/src/lib.rs +++ b/crates/chain/src/lib.rs @@ -31,8 +31,6 @@ 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 indexer; pub use indexer::spk_txout; pub use indexer::Indexer; diff --git a/crates/chain/src/tx_graph.rs b/crates/chain/src/tx_graph.rs index 2d512cfea..b890369c6 100644 --- a/crates/chain/src/tx_graph.rs +++ b/crates/chain/src/tx_graph.rs @@ -1,11 +1,9 @@ -//! Module for structures that store and traverse transactions. -//! -//! [`TxGraph`] contains transactions and indexes them so you can easily traverse the graph of -//! those transactions. `TxGraph` is *monotone* in that you can always insert a transaction -- it -//! does not care whether that transaction is in the current best chain or whether it conflicts with -//! any of the existing transactions or what order you insert the transactions. This means that you -//! can always combine two [`TxGraph`]s together, without resulting in inconsistencies. Furthermore, -//! there is currently no way to delete a transaction. +//! The [`TxGraph`] stores and indexes transactions so you can easily traverse the resulting graph. The +//! nodes are transactions and the edges are spends. `TxGraph` is *monotone* in that you can always +//! insert a transaction -- it does not care whether that transaction is in the current best chain +//! or whether it conflicts with any of the existing transactions or what order you insert the +//! transactions. This means that you can always combine two [`TxGraph`]s together, without +//! resulting in inconsistencies. Furthermore, there is currently no way to delete a transaction. //! //! Transactions can be either whole or partial (i.e., transactions for which we only know some //! outputs, which we usually call "floating outputs"; these are usually inserted using the @@ -36,6 +34,8 @@ //! //! # Generics //! +//! ## Anchors +//! //! Anchors are represented as generics within `TxGraph`. To make use of all functionality of the //! `TxGraph`, anchors (`A`) should implement [`Anchor`]. //! @@ -46,6 +46,14 @@ //! the transaction is contained in. Note that a transaction can be contained in multiple //! conflicting blocks (by nature of the Bitcoin network). //! +//! ## Indexer +//! +//! You can add your own [`Indexer`] to the transaction graph which will be notified whenever new +//! transaction data is attached to the graph. Usually this is instantiated with indexers like +//! [`KeychainTxOutIndex`] to keep track of transaction outputs owned by an output descriptor. +//! +//! # Examples +//! //! ``` //! # use bdk_chain::BlockId; //! # use bdk_chain::tx_graph::TxGraph; @@ -75,41 +83,44 @@ //! # use std::sync::Arc; //! # let tx_a = tx_from_hex(RAW_TX_1); //! # let tx_b = tx_from_hex(RAW_TX_2); -//! let mut graph: TxGraph = TxGraph::default(); +//! let mut tx_graph: TxGraph = TxGraph::default(); //! //! let mut update = tx_graph::TxUpdate::default(); //! update.txs.push(Arc::new(tx_a)); //! update.txs.push(Arc::new(tx_b)); //! //! // apply the update graph -//! let changeset = graph.apply_update(update.clone()); +//! let changeset = tx_graph.apply_update(update.clone()); //! //! // if we apply it again, the resulting changeset will be empty -//! let changeset = graph.apply_update(update); +//! let changeset = tx_graph.apply_update(update); //! assert!(changeset.is_empty()); //! ``` //! [`insert_txout`]: TxGraph::insert_txout +//! [`KeychainTxOutIndex`]: crate::indexer::keychain_txout::KeychainTxOutIndex use crate::collections::*; use crate::BlockId; use crate::CanonicalIter; use crate::CanonicalReason; +use crate::Indexer; use crate::ObservedIn; +use crate::TxPosInBlock; use crate::{Anchor, Balance, ChainOracle, ChainPosition, FullTxOut, Merge}; use alloc::collections::vec_deque::VecDeque; use alloc::sync::Arc; use alloc::vec::Vec; use bdk_core::ConfirmationBlockTime; pub use bdk_core::TxUpdate; -use bitcoin::{Amount, OutPoint, ScriptBuf, SignedAmount, Transaction, TxOut, Txid}; +use bitcoin::{Amount, Block, OutPoint, ScriptBuf, SignedAmount, Transaction, TxOut, Txid}; use core::fmt::{self, Formatter}; use core::{ convert::Infallible, ops::{Deref, RangeInclusive}, }; -impl From> for TxUpdate { - fn from(graph: TxGraph) -> Self { +impl From> for TxUpdate { + fn from(graph: TxGraph) -> Self { Self { txs: graph.full_txs().map(|tx_node| tx_node.tx).collect(), txouts: graph @@ -140,7 +151,7 @@ impl From> for TxGraph { /// /// [module-level documentation]: crate::tx_graph #[derive(Clone, Debug, PartialEq)] -pub struct TxGraph { +pub struct TxGraph { txs: HashMap, spends: BTreeMap>, anchors: HashMap>, @@ -153,9 +164,16 @@ pub struct TxGraph { // FIXME: This can be removed once `HashSet::new` and `BTreeSet::new` are const fns. empty_outspends: HashSet, empty_anchors: BTreeSet, + + /// Indexer. This is called to index all transactions and outpoints added to the transaction + /// graph. + pub index: X, } -impl Default for TxGraph { +impl Default for TxGraph +where + X: Default, +{ fn default() -> Self { Self { txs: Default::default(), @@ -166,6 +184,7 @@ impl Default for TxGraph { txs_by_last_seen: Default::default(), empty_outspends: Default::default(), empty_anchors: Default::default(), + index: Default::default(), } } } @@ -201,6 +220,19 @@ enum TxNodeInternal { Partial(BTreeMap), } +impl TxNodeInternal { + /// Iterate over outpoint and txouts of a partial tx node. The caller is + /// responsible for matching the given `partial` with the correct `txid`. + fn iter_txouts( + txid: Txid, + partial: &BTreeMap, + ) -> impl Iterator { + partial + .iter() + .map(move |(&vout, txout)| (OutPoint { txid, vout }, txout)) + } +} + impl Default for TxNodeInternal { fn default() -> Self { Self::Partial(BTreeMap::new()) @@ -245,23 +277,22 @@ impl fmt::Display for CalculateFeeError { #[cfg(feature = "std")] impl std::error::Error for CalculateFeeError {} -impl TxGraph { +impl TxGraph { /// Iterate over all tx outputs known by [`TxGraph`]. /// /// This includes txouts of both full transactions as well as floating transactions. pub fn all_txouts(&self) -> impl Iterator { - self.txs.iter().flat_map(|(txid, tx)| match tx { + self.txs.iter().flat_map(|(&txid, tx)| match tx { TxNodeInternal::Whole(tx) => tx .as_ref() .output .iter() .enumerate() - .map(|(vout, txout)| (OutPoint::new(*txid, vout as _), txout)) - .collect::>(), - TxNodeInternal::Partial(txouts) => txouts - .iter() - .map(|(vout, txout)| (OutPoint::new(*txid, *vout as _), txout)) + .map(|(vout, txout)| (OutPoint::new(txid, vout as _), txout)) .collect::>(), + TxNodeInternal::Partial(partial) => { + TxNodeInternal::iter_txouts(txid, partial).collect::>() + } }) } @@ -272,13 +303,11 @@ impl TxGraph { pub fn floating_txouts(&self) -> impl Iterator { self.txs .iter() - .filter_map(|(txid, tx_node)| match tx_node { + .filter_map(|(&txid, tx_node)| match tx_node { TxNodeInternal::Whole(_) => None, - TxNodeInternal::Partial(txouts) => Some( - txouts - .iter() - .map(|(&vout, txout)| (OutPoint::new(*txid, vout), txout)), - ), + TxNodeInternal::Partial(partial) => { + Some(TxNodeInternal::iter_txouts(txid, partial)) + } }) .flatten() } @@ -289,7 +318,7 @@ impl TxGraph { TxNodeInternal::Whole(tx) => Some(TxNode { txid, tx: tx.clone(), - anchors: self.anchors.get(&txid).unwrap_or(&self.empty_anchors), + anchors: self.get_anchors(txid), last_seen_unconfirmed: self.last_seen.get(&txid).copied(), }), TxNodeInternal::Partial(_) => None, @@ -324,7 +353,7 @@ impl TxGraph { TxNodeInternal::Whole(tx) => Some(TxNode { txid, tx: tx.clone(), - anchors: self.anchors.get(&txid).unwrap_or(&self.empty_anchors), + anchors: self.get_anchors(txid), last_seen_unconfirmed: self.last_seen.get(&txid).copied(), }), _ => None, @@ -339,6 +368,11 @@ impl TxGraph { } } + /// Get the anchors associated with a transaction. + pub fn get_anchors(&self, txid: Txid) -> &BTreeSet { + self.anchors.get(&txid).unwrap_or(&self.empty_anchors) + } + /// Returns known outputs of a given `txid`. /// /// Returns a [`BTreeMap`] of vout to output of the provided `txid`. @@ -428,7 +462,15 @@ impl TxGraph { } } -impl TxGraph { +impl TxGraph { + /// Return the anchors as elements of a reverse map + fn rev_anchors(&self) -> BTreeSet<(A, Txid)> { + self.anchors + .iter() + .flat_map(|(&txid, anchors)| anchors.iter().cloned().map(move |a| (a, txid))) + .collect() + } + /// Creates an iterator that filters and maps ancestor transactions. /// /// The iterator starts with the ancestors of the supplied `tx` (ancestor transactions of `tx` @@ -442,7 +484,7 @@ impl TxGraph { /// /// The supplied closure returns an `Option`, allowing the caller to map each `Transaction` /// it visits and decide whether to visit ancestors. - pub fn walk_ancestors<'g, T, F, O>(&'g self, tx: T, walk_map: F) -> TxAncestors<'g, A, F, O> + pub fn walk_ancestors<'g, T, F, O>(&'g self, tx: T, walk_map: F) -> TxAncestors<'g, A, X, F, O> where T: Into>, F: FnMut(usize, Arc) -> Option + 'g, @@ -464,7 +506,7 @@ impl TxGraph { &'g self, txid: Txid, walk_map: F, - ) -> TxDescendants<'g, A, F, O> + ) -> TxDescendants<'g, A, X, F, O> where F: FnMut(usize, Txid) -> Option + 'g, { @@ -472,7 +514,7 @@ impl TxGraph { } } -impl TxGraph { +impl TxGraph { /// Creates an iterator that both filters and maps conflicting transactions (this includes /// descendants of directly-conflicting transactions, which are also considered conflicts). /// @@ -481,7 +523,7 @@ impl TxGraph { &'g self, tx: &'g Transaction, walk_map: F, - ) -> TxDescendants<'g, A, F, O> + ) -> TxDescendants<'g, A, X, F, O> where F: FnMut(usize, Txid) -> Option + 'g, { @@ -520,27 +562,45 @@ impl TxGraph { } } -impl TxGraph { +impl TxGraph { /// Transform the [`TxGraph`] to have [`Anchor`]s of another type. /// /// This takes in a closure of signature `FnMut(A) -> A2` which is called for each [`Anchor`] to /// transform it. - pub fn map_anchors(self, f: F) -> TxGraph + pub fn map_anchors(self, mut f: F) -> TxGraph where F: FnMut(A) -> A2, { - let mut new_graph = TxGraph::::default(); - new_graph.apply_changeset(self.initial_changeset().map_anchors(f)); + let new_anchors = self.rev_anchors().into_iter().map(|(a, txid)| (f(a), txid)); + + let mut new_graph: TxGraph = TxGraph { + txs: self.txs, + spends: self.spends, + anchors: Default::default(), + last_seen: self.last_seen, + txs_by_highest_conf_heights: self.txs_by_highest_conf_heights, + txs_by_last_seen: self.txs_by_last_seen, + empty_anchors: Default::default(), + empty_outspends: Default::default(), + index: self.index, + }; + + for (new_anchor, txid) in new_anchors { + let _ = new_graph.insert_anchor(txid, new_anchor); + } + new_graph } - /// Construct a new [`TxGraph`] from a list of transactions. - pub fn new(txs: impl IntoIterator) -> Self { - let mut new = Self::default(); - for tx in txs.into_iter() { - let _ = new.insert_tx(tx); + /// Construct a new [`TxGraph`] from [`Indexer`]. + pub fn new(indexer: X) -> Self + where + X: Default, + { + Self { + index: indexer, + ..Default::default() } - new } /// Inserts the given [`TxOut`] at [`OutPoint`]. @@ -552,8 +612,8 @@ impl TxGraph { /// the `outpoint`) already existed in `self`. /// /// [`apply_changeset`]: Self::apply_changeset - pub fn insert_txout(&mut self, outpoint: OutPoint, txout: TxOut) -> ChangeSet { - let mut changeset = ChangeSet::::default(); + pub fn insert_txout(&mut self, outpoint: OutPoint, txout: TxOut) -> ChangeSet { + let mut changeset = ChangeSet::default(); let tx_node = self.txs.entry(outpoint.txid).or_default(); match tx_node { TxNodeInternal::Whole(_) => { @@ -576,16 +636,17 @@ impl TxGraph { } } } + self.index_changeset(&mut changeset); changeset } /// Inserts the given transaction into [`TxGraph`]. /// /// The [`ChangeSet`] returned will be empty if `tx` already exists. - pub fn insert_tx>>(&mut self, tx: T) -> ChangeSet { + pub fn insert_tx>>(&mut self, tx: T) -> ChangeSet { let tx: Arc = tx.into(); let txid = tx.compute_txid(); - let mut changeset = ChangeSet::::default(); + let mut changeset = ChangeSet::default(); let tx_node = self.txs.entry(txid).or_default(); match tx_node { @@ -612,6 +673,20 @@ impl TxGraph { } } + self.index_changeset(&mut changeset); + + changeset + } + + /// Inserts a batch of transactions at once and returns the [`ChangeSet`]. + pub fn batch_insert_tx>>( + &mut self, + txs: impl IntoIterator, + ) -> ChangeSet { + let mut changeset = ChangeSet::default(); + for tx in txs { + changeset.merge(self.insert_tx(tx)); + } changeset } @@ -623,8 +698,8 @@ impl TxGraph { pub fn batch_insert_unconfirmed>>( &mut self, txs: impl IntoIterator, - ) -> ChangeSet { - let mut changeset = ChangeSet::::default(); + ) -> ChangeSet { + let mut changeset = ChangeSet::default(); for (tx, seen_at) in txs { let tx: Arc = tx.into(); changeset.merge(self.insert_seen_at(tx.compute_txid(), seen_at)); @@ -633,11 +708,88 @@ impl TxGraph { changeset } + /// Batch insert transactions, filtering out those that are irrelevant. + /// + /// Relevancy is determined by the [`Indexer::is_tx_relevant`] implementation of `I`. Irrelevant + /// transactions in `txs` will be ignored. `txs` do not need to be in topological order. + /// + /// The second item in the input tuple is a list of *anchors* for the transaction. This is + /// usually a single item or none at all. Therefore using an `Option` as the type is the most + /// ergonomic approach. + pub fn batch_insert_relevant>>( + &mut self, + txs: impl IntoIterator)>, + ) -> ChangeSet { + // The algorithm below allows for non-topologically ordered transactions by using two loops. + // This is achieved by: + // 1. insert all txs into the index. If they are irrelevant then that's fine it will just + // not store anything about them. + // 2. decide whether to insert them into the graph depending on whether `is_tx_relevant` + // returns true or not. (in a second loop). + let txs = txs + .into_iter() + .map(|(tx, anchors)| (>>::into(tx), anchors)) + .collect::>(); + + let mut changeset = ChangeSet::default(); + + for (tx, _) in &txs { + changeset.merge(self.index.index_tx(tx).into()); + } + + for (tx, anchors) in txs { + if self.index.is_tx_relevant(&tx) { + let txid = tx.compute_txid(); + changeset.merge(self.insert_tx(tx.clone())); + for anchor in anchors { + changeset.merge(self.insert_anchor(txid, anchor)); + } + } + } + + changeset + } + + /// Batch insert unconfirmed transactions, filtering out those that are irrelevant. + /// + /// Relevancy is determined by the internal [`Indexer::is_tx_relevant`] implementation of `I`. + /// Irrelevant transactions in `txs` will be ignored. + /// + /// Items of `txs` are tuples containing the transaction and a *last seen* timestamp. The + /// *last seen* communicates when the transaction is last seen in the mempool which is used for + /// conflict-resolution in [`TxGraph`] (refer to [`TxGraph::insert_seen_at`] for details). + pub fn batch_insert_relevant_unconfirmed>>( + &mut self, + unconfirmed_txs: impl IntoIterator, + ) -> ChangeSet { + // The algorithm below allows for non-topologically ordered transactions by using two loops. + // This is achieved by: + // 1. insert all txs into the index. If they are irrelevant then that's fine it will just + // not store anything about them. + // 2. decide whether to insert them into the graph depending on whether `is_tx_relevant` + // returns true or not. (in a second loop). + let mut txs = unconfirmed_txs + .into_iter() + .map(|(tx, last_seen)| (>>::into(tx), last_seen)) + .collect::>(); + + let mut changeset = ChangeSet::::default(); + for (tx, _) in &txs { + changeset.indexer.merge(self.index.index_tx(tx)); + } + + txs.retain(|(tx, _)| self.index.is_tx_relevant(tx)); + + changeset.merge(self.batch_insert_unconfirmed(txs)); + + changeset + } + /// Inserts the given `anchor` into [`TxGraph`]. /// /// The [`ChangeSet`] returned will be empty if graph already knows that `txid` exists in /// `anchor`. - pub fn insert_anchor(&mut self, txid: Txid, anchor: A) -> ChangeSet { + pub fn insert_anchor(&mut self, txid: Txid, anchor: A) -> ChangeSet { // These two variables are used to determine how to modify the `txid`'s entry in // `txs_by_highest_conf_heights`. // We want to remove `(old_top_h?, txid)` and insert `(new_top_h?, txid)`. @@ -665,7 +817,7 @@ impl TxGraph { } }; - let mut changeset = ChangeSet::::default(); + let mut changeset = ChangeSet::default(); if is_changed { let new_top_is_changed = match old_top_h { None => true, @@ -686,7 +838,7 @@ impl TxGraph { /// Inserts the given `seen_at` for `txid` into [`TxGraph`]. /// /// Note that [`TxGraph`] only keeps track of the latest `seen_at`. - pub fn insert_seen_at(&mut self, txid: Txid, seen_at: u64) -> ChangeSet { + pub fn insert_seen_at(&mut self, txid: Txid, seen_at: u64) -> ChangeSet { let mut old_last_seen = None; let is_changed = match self.last_seen.entry(txid) { hash_map::Entry::Occupied(mut e) => { @@ -704,7 +856,7 @@ impl TxGraph { } }; - let mut changeset = ChangeSet::::default(); + let mut changeset = ChangeSet::default(); if is_changed { if let Some(old_last_seen) = old_last_seen { self.txs_by_last_seen.remove(&(old_last_seen, txid)); @@ -721,7 +873,7 @@ impl TxGraph { /// exist in `update` but not in `self`). #[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] - pub fn apply_update(&mut self, update: TxUpdate) -> ChangeSet { + pub fn apply_update(&mut self, update: TxUpdate) -> ChangeSet { use std::time::*; let now = SystemTime::now() .duration_since(UNIX_EPOCH) @@ -742,8 +894,12 @@ impl TxGraph { /// /// Use [`apply_update`](TxGraph::apply_update) to have the `seen_at` value automatically set /// to the current time. - pub fn apply_update_at(&mut self, update: TxUpdate, seen_at: Option) -> ChangeSet { - let mut changeset = ChangeSet::::default(); + pub fn apply_update_at( + &mut self, + update: TxUpdate, + seen_at: Option, + ) -> ChangeSet { + let mut changeset = ChangeSet::default(); let mut unanchored_txs = HashSet::::new(); for tx in update.txs { if unanchored_txs.insert(tx.compute_txid()) { @@ -765,28 +921,91 @@ impl TxGraph { changeset.merge(self.insert_seen_at(txid, seen_at)); } } + self.index_changeset(&mut changeset); + changeset + } + + /// Changes the [`Indexer`] for a `TxGraph`. **This doesn't re-index the transactions in the + /// graph**. To do that that call [`reindex`]. + /// + /// Returns the new `TxGraph` and the old indexer. + /// + /// [`reindex`]: Self::reindex + pub fn swap_indexer(self, indexer: X2) -> (TxGraph, X) { + ( + TxGraph { + txs: self.txs, + spends: self.spends, + anchors: self.anchors, + last_seen: self.last_seen, + txs_by_highest_conf_heights: self.txs_by_highest_conf_heights, + txs_by_last_seen: self.txs_by_last_seen, + empty_outspends: self.empty_outspends, + empty_anchors: self.empty_anchors, + index: indexer, + }, + self.index, + ) + } + + /// Reindexes the transaction graph by calling the [`Indexer`] on every transaction. + /// + /// The returned changeset will only be non-empty in the `indexer` field. + pub fn reindex(&mut self) -> ChangeSet { + let mut changeset = ChangeSet::::default(); + for (&txid, node) in &self.txs { + match node { + TxNodeInternal::Whole(tx) => { + changeset.merge(self.index.index_tx(tx.as_ref()).into()) + } + TxNodeInternal::Partial(txouts) => { + for (op, txout) in TxNodeInternal::iter_txouts(txid, txouts) { + changeset.merge(self.index.index_txout(op, txout).into()); + } + } + }; + } changeset } /// Determines the [`ChangeSet`] between `self` and an empty [`TxGraph`]. - pub fn initial_changeset(&self) -> ChangeSet { + pub fn initial_changeset(&self) -> ChangeSet { ChangeSet { txs: self.full_txs().map(|tx_node| tx_node.tx).collect(), txouts: self .floating_txouts() .map(|(op, txout)| (op, txout.clone())) .collect(), - anchors: self - .anchors - .iter() - .flat_map(|(txid, anchors)| anchors.iter().map(|a| (a.clone(), *txid))) - .collect(), - last_seen: self.last_seen.iter().map(|(&k, &v)| (k, v)).collect(), + anchors: self.rev_anchors(), + last_seen: self.last_seen.clone().into_iter().collect(), + indexer: self.index.initial_changeset(), + } + } + + /// Indexes the txs and txouts of `changeset` and merges the resulting changes. + fn index_changeset(&mut self, changeset: &mut ChangeSet) { + let indexer = &mut changeset.indexer; + for tx in &changeset.txs { + indexer.merge(self.index.index_tx(tx)); + } + for (&outpoint, txout) in &changeset.txouts { + indexer.merge(self.index.index_txout(outpoint, txout)); } } /// Applies [`ChangeSet`] to [`TxGraph`]. - pub fn apply_changeset(&mut self, changeset: ChangeSet) { + pub fn apply_changeset(&mut self, changeset: ChangeSet) { + // It's possible that the changeset contains some state for the indexer to help it index the + // transactions so we do that first. + self.index.apply_changeset(changeset.indexer); + + for tx in &changeset.txs { + self.index.index_tx(tx); + } + for (&outpoint, txout) in &changeset.txouts { + self.index.index_txout(outpoint, txout); + } + for tx in changeset.txs { let _ = self.insert_tx(tx); } @@ -802,7 +1021,7 @@ impl TxGraph { } } -impl TxGraph { +impl TxGraph { /// List graph transactions that are in `chain` with `chain_tip`. /// /// Each transaction is represented as a [`CanonicalTx`] that contains where the transaction is @@ -979,7 +1198,7 @@ impl TxGraph { &'a self, chain: &'a C, chain_tip: BlockId, - ) -> CanonicalIter<'a, A, C> { + ) -> CanonicalIter<'a, A, X, C> { CanonicalIter::new(self, chain, chain_tip) } @@ -1112,25 +1331,89 @@ impl TxGraph { } } +/// Methods are available if the anchor (`A`) can be created from [`TxPosInBlock`]. +impl TxGraph +where + for<'b> A: Anchor + From>, + X: Indexer, + X::ChangeSet: Default + Merge, +{ + /// Batch insert all transactions of the given `block` of `height`, filtering out those that are + /// irrelevant. + /// + /// Each inserted transaction's anchor will be constructed using [`TxPosInBlock`]. + /// + /// Relevancy is determined by the internal [`Indexer::is_tx_relevant`] implementation of `X`. + /// Irrelevant transactions in `txs` will be ignored. + pub fn apply_block_relevant( + &mut self, + block: &Block, + height: u32, + ) -> ChangeSet { + let block_id = BlockId { + hash: block.block_hash(), + height, + }; + let mut changeset = ChangeSet::default(); + for (tx_pos, tx) in block.txdata.iter().enumerate() { + changeset.merge(self.index.index_tx(tx).into()); + if self.index.is_tx_relevant(tx) { + let anchor = TxPosInBlock { + block, + block_id, + tx_pos, + }; + changeset.merge(self.insert_tx(tx.clone())); + changeset.merge(self.insert_anchor(tx.compute_txid(), anchor.into())); + } + } + changeset + } + + /// Batch insert all transactions of the given `block` of `height`. + /// + /// Each inserted transaction's anchor will be constructed using [`TxPosInBlock`]. + /// + /// To only insert relevant transactions, use [`apply_block_relevant`] instead. + /// + /// [`apply_block_relevant`]: Self::apply_block_relevant + pub fn apply_block(&mut self, block: Block, height: u32) -> ChangeSet { + let block_id = BlockId { + hash: block.block_hash(), + height, + }; + let mut changeset = ChangeSet::default(); + for (tx_pos, tx) in block.txdata.iter().enumerate() { + changeset.merge(self.index.index_tx(tx).into()); + let anchor = TxPosInBlock { + block: &block, + block_id, + tx_pos, + }; + changeset.merge(self.insert_tx(tx.clone())); + changeset.merge(self.insert_anchor(tx.compute_txid(), anchor.into())); + } + changeset + } +} + /// The [`ChangeSet`] represents changes to a [`TxGraph`]. /// /// Since [`TxGraph`] is monotone, the "changeset" can only contain transactions to be added and /// not removed. /// -/// Refer to [module-level documentation] for more. -/// -/// [module-level documentation]: crate::tx_graph +/// Refer to [module-level documentation](self) for more. #[derive(Debug, Clone, PartialEq)] #[cfg_attr( feature = "serde", derive(serde::Deserialize, serde::Serialize), serde(bound( - deserialize = "A: Ord + serde::Deserialize<'de>", - serialize = "A: Ord + serde::Serialize", + deserialize = "A: Ord + serde::Deserialize<'de>, X: serde::Deserialize<'de>", + serialize = "A: Ord + serde::Serialize, X: serde::Serialize", )) )] #[must_use] -pub struct ChangeSet { +pub struct ChangeSet { /// Added transactions. pub txs: BTreeSet>, /// Added txouts. @@ -1139,20 +1422,35 @@ pub struct ChangeSet { pub anchors: BTreeSet<(A, Txid)>, /// Added last-seen unix timestamps of transactions. pub last_seen: BTreeMap, + /// [`Indexer`] changes + pub indexer: X, } -impl Default for ChangeSet { +impl Default for ChangeSet { fn default() -> Self { Self { txs: Default::default(), txouts: Default::default(), anchors: Default::default(), last_seen: Default::default(), + indexer: Default::default(), + } + } +} + +impl From for ChangeSet { + fn from(indexer: X) -> Self { + Self { + indexer, + txs: Default::default(), + txouts: Default::default(), + anchors: Default::default(), + last_seen: Default::default(), } } } -impl ChangeSet { +impl ChangeSet { /// Iterates over all outpoints contained within [`ChangeSet`]. pub fn txouts(&self) -> impl Iterator { self.txs @@ -1186,7 +1484,7 @@ impl ChangeSet { } } -impl Merge for ChangeSet { +impl Merge for ChangeSet { fn merge(&mut self, other: Self) { // We use `extend` instead of `BTreeMap::append` due to performance issues with `append`. // Refer to https://github.com/rust-lang/rust/issues/34666#issuecomment-675658420 @@ -1202,6 +1500,7 @@ impl Merge for ChangeSet { .filter(|(txid, update_ls)| self.last_seen.get(txid) < Some(update_ls)) .collect::>(), ); + self.indexer.merge(other.indexer); } fn is_empty(&self) -> bool { @@ -1209,15 +1508,16 @@ impl Merge for ChangeSet { && self.txouts.is_empty() && self.anchors.is_empty() && self.last_seen.is_empty() + && self.indexer.is_empty() } } -impl ChangeSet { +impl ChangeSet { /// Transform the [`ChangeSet`] to have [`Anchor`]s of another type. /// /// This takes in a closure of signature `FnMut(A) -> A2` which is called for each [`Anchor`] to /// transform it. - pub fn map_anchors(self, mut f: F) -> ChangeSet + pub fn map_anchors(self, mut f: F) -> ChangeSet where F: FnMut(A) -> A2, { @@ -1228,12 +1528,13 @@ impl ChangeSet { self.anchors.into_iter().map(|(a, txid)| (f(a), txid)), ), last_seen: self.last_seen, + indexer: self.indexer, } } } -impl AsRef> for TxGraph { - fn as_ref(&self) -> &TxGraph { +impl AsRef> for TxGraph { + fn as_ref(&self) -> &TxGraph { self } } @@ -1245,23 +1546,23 @@ impl AsRef> for TxGraph { /// Returned by the [`walk_ancestors`] method of [`TxGraph`]. /// /// [`walk_ancestors`]: TxGraph::walk_ancestors -pub struct TxAncestors<'g, A, F, O> +pub struct TxAncestors<'g, A, X, F, O> where F: FnMut(usize, Arc) -> Option, { - graph: &'g TxGraph, + graph: &'g TxGraph, visited: HashSet, queue: VecDeque<(usize, Arc)>, filter_map: F, } -impl<'g, A, F, O> TxAncestors<'g, A, F, O> +impl<'g, A, X, F, O> TxAncestors<'g, A, X, F, O> where F: FnMut(usize, Arc) -> Option, { /// Creates a `TxAncestors` that includes the starting `Transaction` when iterating. pub(crate) fn new_include_root( - graph: &'g TxGraph, + graph: &'g TxGraph, tx: impl Into>, filter_map: F, ) -> Self { @@ -1275,7 +1576,7 @@ where /// Creates a `TxAncestors` that excludes the starting `Transaction` when iterating. pub(crate) fn new_exclude_root( - graph: &'g TxGraph, + graph: &'g TxGraph, tx: impl Into>, filter_map: F, ) -> Self { @@ -1293,7 +1594,7 @@ where /// `Transaction`s when iterating. #[allow(unused)] pub(crate) fn from_multiple_include_root( - graph: &'g TxGraph, + graph: &'g TxGraph, txs: I, filter_map: F, ) -> Self @@ -1313,7 +1614,7 @@ where /// `Transaction`s when iterating. #[allow(unused)] pub(crate) fn from_multiple_exclude_root( - graph: &'g TxGraph, + graph: &'g TxGraph, txs: I, filter_map: F, ) -> Self @@ -1350,7 +1651,7 @@ where } } -impl Iterator for TxAncestors<'_, A, F, O> +impl Iterator for TxAncestors<'_, A, X, F, O> where F: FnMut(usize, Arc) -> Option, { @@ -1376,23 +1677,23 @@ where /// Returned by the [`walk_descendants`] method of [`TxGraph`]. /// /// [`walk_descendants`]: TxGraph::walk_descendants -pub struct TxDescendants<'g, A, F, O> +pub struct TxDescendants<'g, A, X, F, O> where F: FnMut(usize, Txid) -> Option, { - graph: &'g TxGraph, + graph: &'g TxGraph, visited: HashSet, queue: VecDeque<(usize, Txid)>, filter_map: F, } -impl<'g, A, F, O> TxDescendants<'g, A, F, O> +impl<'g, A, X, F, O> TxDescendants<'g, A, X, F, O> where F: FnMut(usize, Txid) -> Option, { /// Creates a `TxDescendants` that includes the starting `txid` when iterating. #[allow(unused)] - pub(crate) fn new_include_root(graph: &'g TxGraph, txid: Txid, filter_map: F) -> Self { + pub(crate) fn new_include_root(graph: &'g TxGraph, txid: Txid, filter_map: F) -> Self { Self { graph, visited: Default::default(), @@ -1402,7 +1703,7 @@ where } /// Creates a `TxDescendants` that excludes the starting `txid` when iterating. - pub(crate) fn new_exclude_root(graph: &'g TxGraph, txid: Txid, filter_map: F) -> Self { + pub(crate) fn new_exclude_root(graph: &'g TxGraph, txid: Txid, filter_map: F) -> Self { let mut descendants = Self { graph, visited: Default::default(), @@ -1416,7 +1717,7 @@ where /// Creates a `TxDescendants` from multiple starting transactions that includes the starting /// `txid`s when iterating. pub(crate) fn from_multiple_include_root( - graph: &'g TxGraph, + graph: &'g TxGraph, txids: I, filter_map: F, ) -> Self @@ -1435,7 +1736,7 @@ where /// `txid`s when iterating. #[allow(unused)] pub(crate) fn from_multiple_exclude_root( - graph: &'g TxGraph, + graph: &'g TxGraph, txids: I, filter_map: F, ) -> Self @@ -1470,7 +1771,7 @@ where } } -impl Iterator for TxDescendants<'_, A, F, O> +impl Iterator for TxDescendants<'_, A, X, F, O> where F: FnMut(usize, Txid) -> Option, { diff --git a/crates/chain/tests/test_indexed_tx_graph.rs b/crates/chain/tests/test_indexed_tx_graph.rs index 1e28eb6a2..bce83b9d8 100644 --- a/crates/chain/tests/test_indexed_tx_graph.rs +++ b/crates/chain/tests/test_indexed_tx_graph.rs @@ -6,10 +6,8 @@ mod common; use std::{collections::BTreeSet, sync::Arc}; use bdk_chain::{ - indexed_tx_graph::{self, IndexedTxGraph}, - indexer::keychain_txout::KeychainTxOutIndex, - local_chain::LocalChain, - tx_graph, Balance, ChainPosition, ConfirmationBlockTime, DescriptorExt, + indexer::keychain_txout::KeychainTxOutIndex, local_chain::LocalChain, tx_graph, Balance, + ChainPosition, ConfirmationBlockTime, DescriptorExt, TxGraph, }; use bdk_testenv::{ block_id, hash, @@ -18,7 +16,7 @@ use bdk_testenv::{ use bitcoin::{secp256k1::Secp256k1, Amount, OutPoint, ScriptBuf, Transaction, TxIn, TxOut}; use miniscript::Descriptor; -/// Ensure [`IndexedTxGraph::insert_relevant_txs`] can successfully index transactions NOT presented +/// Ensure [`TxGraph::batch_insert_relevant`] can successfully index transactions NOT presented /// in topological order. /// /// Given 3 transactions (A, B, C), where A has 2 owned outputs. B and C spends an output each of A. @@ -33,9 +31,8 @@ fn insert_relevant_txs() { let spk_0 = descriptor.at_derivation_index(0).unwrap().script_pubkey(); let spk_1 = descriptor.at_derivation_index(9).unwrap().script_pubkey(); - let mut graph = IndexedTxGraph::>::new( - KeychainTxOutIndex::new(10), - ); + let mut graph = + TxGraph::>::new(KeychainTxOutIndex::new(10)); let _ = graph .index .insert_descriptor((), descriptor.clone()) @@ -73,14 +70,12 @@ fn insert_relevant_txs() { let txs = [tx_c, tx_b, tx_a]; - let changeset = indexed_tx_graph::ChangeSet { - tx_graph: tx_graph::ChangeSet { - txs: txs.iter().cloned().map(Arc::new).collect(), - ..Default::default() - }, + let changeset = tx_graph::ChangeSet { + txs: txs.iter().cloned().map(Arc::new).collect(), indexer: keychain_txout::ChangeSet { last_revealed: [(descriptor.descriptor_id(), 9_u32)].into(), }, + ..Default::default() }; assert_eq!( @@ -89,17 +84,18 @@ fn insert_relevant_txs() { ); // The initial changeset will also contain info about the keychain we added - let initial_changeset = indexed_tx_graph::ChangeSet { - tx_graph: changeset.tx_graph, + let initial_changeset = tx_graph::ChangeSet { + txs: changeset.txs, indexer: keychain_txout::ChangeSet { last_revealed: changeset.indexer.last_revealed, }, + ..Default::default() }; assert_eq!(graph.initial_changeset(), initial_changeset); } -/// Ensure consistency IndexedTxGraph list_* and balance methods. These methods lists +/// Ensure consistency TxGraph list_* and balance methods. These methods lists /// relevant txouts and utxos from the information fetched from a ChainOracle (here a LocalChain). /// /// Test Setup: @@ -133,14 +129,14 @@ fn test_list_owned_txouts() { LocalChain::from_blocks((0..150).map(|i| (i as u32, hash!("random"))).collect()) .expect("must have genesis hash"); - // Initiate IndexedTxGraph + // Initiate TxGraph let (desc_1, _) = Descriptor::parse_descriptor(&Secp256k1::signing_only(), DESCRIPTORS[2]).unwrap(); let (desc_2, _) = Descriptor::parse_descriptor(&Secp256k1::signing_only(), DESCRIPTORS[3]).unwrap(); - let mut graph = IndexedTxGraph::>::new( + let mut graph = TxGraph::>::new( KeychainTxOutIndex::new(10), ); @@ -261,13 +257,12 @@ fn test_list_owned_txouts() { // A helper lambda to extract and filter data from the graph. let fetch = - |height: u32, graph: &IndexedTxGraph>| { + |height: u32, graph: &TxGraph>| { let chain_tip = local_chain .get(height) .map(|cp| cp.block_id()) .unwrap_or_else(|| panic!("block must exist at {}", height)); let txouts = graph - .graph() .filter_chain_txouts( &local_chain, chain_tip, @@ -276,7 +271,6 @@ fn test_list_owned_txouts() { .collect::>(); let utxos = graph - .graph() .filter_chain_unspents( &local_chain, chain_tip, @@ -284,7 +278,7 @@ fn test_list_owned_txouts() { ) .collect::>(); - let balance = graph.graph().balance( + let balance = graph.balance( &local_chain, chain_tip, graph.index.outpoints().iter().cloned(), @@ -446,6 +440,7 @@ fn test_list_owned_txouts() { ); // tx3 also gets into confirmed utxo set + // but tx2 is no longer unspent because tx3 spent it assert_eq!( confirmed_utxos_txid, [tx1.compute_txid(), tx3.compute_txid()].into() @@ -524,7 +519,7 @@ fn test_list_owned_txouts() { } } -/// Given a `LocalChain`, `IndexedTxGraph`, and a `Transaction`, when we insert some anchor +/// Given a `LocalChain`, `TxGraph`, and a `Transaction`, when we insert some anchor /// (possibly non-canonical) and/or a last-seen timestamp into the graph, we check the canonical /// position of the tx: /// @@ -549,7 +544,7 @@ fn test_get_chain_position() { // addr: bcrt1qc6fweuf4xjvz4x3gx3t9e0fh4hvqyu2qw4wvxm let spk = ScriptBuf::from_hex("0014c692ecf13534982a9a2834565cbd37add8027140").unwrap(); - let mut graph = IndexedTxGraph::new({ + let mut graph = TxGraph::new({ let mut index = SpkTxOutIndex::default(); let _ = index.insert_spk(0u32, spk.clone()); index @@ -561,11 +556,11 @@ fn test_get_chain_position() { let cp = CheckPoint::from_block_ids(blocks.clone()).unwrap(); let chain = LocalChain::from_tip(cp).unwrap(); - // The test will insert a transaction into the indexed tx graph along with any anchors and + // The test will insert a transaction into the tx graph along with any anchors and // timestamps, then check the tx's canonical position is expected. fn run( chain: &LocalChain, - graph: &mut IndexedTxGraph>, + graph: &mut TxGraph>, test: TestCase, ) { let TestCase { @@ -588,7 +583,6 @@ fn test_get_chain_position() { // check chain position let chain_pos = graph - .graph() .list_canonical_txs(chain, chain.tip().block_id()) .find_map(|canon_tx| { if canon_tx.tx_node.txid == txid { diff --git a/crates/chain/tests/test_tx_graph.rs b/crates/chain/tests/test_tx_graph.rs index ef57ac15b..788e1eb19 100644 --- a/crates/chain/tests/test_tx_graph.rs +++ b/crates/chain/tests/test_tx_graph.rs @@ -115,7 +115,8 @@ fn insert_txouts() { txs: [Arc::new(update_tx.clone())].into(), txouts: update_ops.clone().into(), anchors: [(conf_anchor, update_tx.compute_txid()),].into(), - last_seen: [(hash!("tx2"), 1000000)].into() + last_seen: [(hash!("tx2"), 1000000)].into(), + ..Default::default() } ); @@ -168,7 +169,8 @@ fn insert_txouts() { txs: [Arc::new(update_tx.clone())].into(), txouts: update_ops.into_iter().chain(original_ops).collect(), anchors: [(conf_anchor, update_tx.compute_txid()),].into(), - last_seen: [(hash!("tx2"), 1000000)].into() + last_seen: [(hash!("tx2"), 1000000)].into(), + ..Default::default() } ); } @@ -588,7 +590,8 @@ fn test_walk_ancestors() { ..new_tx(0) }; - let mut graph = TxGraph::::new([ + let mut graph = TxGraph::::default(); + let _ = graph.batch_insert_tx([ tx_a0.clone(), tx_b0.clone(), tx_b1.clone(), @@ -1049,7 +1052,8 @@ fn transactions_inserted_into_tx_graph_are_not_canonical_until_they_have_an_anch let txids: Vec = txs.iter().map(Transaction::compute_txid).collect(); // graph - let mut graph = TxGraph::::new(txs); + let mut graph = TxGraph::::default(); + let _ = graph.batch_insert_tx(txs); let full_txs: Vec<_> = graph.full_txs().collect(); assert_eq!(full_txs.len(), 2); let unseen_txs: Vec<_> = graph.txs_with_no_anchor_or_last_seen().collect(); diff --git a/crates/electrum/tests/test_electrum.rs b/crates/electrum/tests/test_electrum.rs index 7794589a6..55b977ea3 100644 --- a/crates/electrum/tests/test_electrum.rs +++ b/crates/electrum/tests/test_electrum.rs @@ -3,7 +3,7 @@ use bdk_chain::{ local_chain::LocalChain, spk_client::{FullScanRequest, SyncRequest, SyncResponse}, spk_txout::SpkTxOutIndex, - Balance, ConfirmationBlockTime, IndexedTxGraph, Indexer, Merge, TxGraph, + Balance, ConfirmationBlockTime, Indexer, Merge, TxGraph, }; use bdk_electrum::BdkElectrumClient; use bdk_testenv::{anyhow, bitcoincore_rpc::RpcApi, TestEnv}; @@ -16,13 +16,11 @@ const BATCH_SIZE: usize = 5; fn get_balance( recv_chain: &LocalChain, - recv_graph: &IndexedTxGraph>, + recv_graph: &TxGraph>, ) -> anyhow::Result { let chain_tip = recv_chain.tip().block_id(); let outpoints = recv_graph.index.outpoints().clone(); - let balance = recv_graph - .graph() - .balance(recv_chain, chain_tip, outpoints, |_, _| true); + let balance = recv_graph.balance(recv_chain, chain_tip, outpoints, |_, _| true); Ok(balance) } @@ -30,7 +28,7 @@ fn sync_with_electrum( client: &BdkElectrumClient, spks: Spks, chain: &mut LocalChain, - graph: &mut IndexedTxGraph, + graph: &mut TxGraph, ) -> anyhow::Result where I: Indexer, @@ -308,7 +306,7 @@ fn test_sync() -> anyhow::Result<()> { // Setup receiver. let (mut recv_chain, _) = LocalChain::from_genesis_hash(env.bitcoind.client.get_block_hash(0)?); - let mut recv_graph = IndexedTxGraph::::new({ + let mut recv_graph = TxGraph::::new({ let mut recv_index = SpkTxOutIndex::default(); recv_index.insert_spk((), spk_to_track.clone()); recv_index @@ -398,13 +396,10 @@ fn test_sync() -> anyhow::Result<()> { // Check to see if we have the floating txouts available from our transactions' previous outputs // in order to calculate transaction fees. - for tx in recv_graph.graph().full_txs() { + for tx in recv_graph.full_txs() { // Retrieve the calculated fee from `TxGraph`, which will panic if we do not have the // floating txouts available from the transaction's previous outputs. - let fee = recv_graph - .graph() - .calculate_fee(&tx.tx) - .expect("fee must exist"); + let fee = recv_graph.calculate_fee(&tx.tx).expect("fee must exist"); // Retrieve the fee in the transaction data from `bitcoind`. let tx_fee = env @@ -451,7 +446,7 @@ fn tx_can_become_unconfirmed_after_reorg() -> anyhow::Result<()> { // Setup receiver. let (mut recv_chain, _) = LocalChain::from_genesis_hash(env.bitcoind.client.get_block_hash(0)?); - let mut recv_graph = IndexedTxGraph::::new({ + let mut recv_graph = TxGraph::::new({ let mut recv_index = SpkTxOutIndex::default(); recv_index.insert_spk((), spk_to_track.clone()); recv_index @@ -538,7 +533,7 @@ fn test_sync_with_coinbase() -> anyhow::Result<()> { // Setup receiver. let (mut recv_chain, _) = LocalChain::from_genesis_hash(env.bitcoind.client.get_block_hash(0)?); - let mut recv_graph = IndexedTxGraph::::new({ + let mut recv_graph = TxGraph::::new({ let mut recv_index = SpkTxOutIndex::default(); recv_index.insert_spk((), spk_to_track.clone()); recv_index @@ -573,7 +568,7 @@ fn test_check_fee_calculation() -> anyhow::Result<()> { // Setup receiver. let (mut recv_chain, _) = LocalChain::from_genesis_hash(env.bitcoind.client.get_block_hash(0)?); - let mut recv_graph = IndexedTxGraph::::new({ + let mut recv_graph = TxGraph::::new({ let mut recv_index = SpkTxOutIndex::default(); recv_index.insert_spk((), spk_to_track.clone()); recv_index @@ -634,7 +629,6 @@ fn test_check_fee_calculation() -> anyhow::Result<()> { // Check the graph update contains the right floating txout let graph_txout = recv_graph - .graph() .all_txouts() .find(|(_op, txout)| txout.value == prev_amt) .unwrap(); @@ -649,13 +643,10 @@ fn test_check_fee_calculation() -> anyhow::Result<()> { }, ); - for tx in recv_graph.graph().full_txs() { + for tx in recv_graph.full_txs() { // Retrieve the calculated fee from `TxGraph`, which will panic if we do not have the // floating txouts available from the transaction's previous outputs. - let fee = recv_graph - .graph() - .calculate_fee(&tx.tx) - .expect("fee must exist"); + let fee = recv_graph.calculate_fee(&tx.tx).expect("fee must exist"); // Check the fee calculated fee matches the initial fee amount assert_eq!(fee, FEE_AMOUNT); diff --git a/crates/wallet/src/wallet/changeset.rs b/crates/wallet/src/wallet/changeset.rs index 94dba2b7d..3e0972b0c 100644 --- a/crates/wallet/src/wallet/changeset.rs +++ b/crates/wallet/src/wallet/changeset.rs @@ -1,11 +1,6 @@ -use bdk_chain::{ - indexed_tx_graph, keychain_txout, local_chain, tx_graph, ConfirmationBlockTime, Merge, -}; +use bdk_chain::{keychain_txout, local_chain, tx_graph, ConfirmationBlockTime, Merge}; use miniscript::{Descriptor, DescriptorPublicKey}; -type IndexedTxGraphChangeSet = - indexed_tx_graph::ChangeSet; - /// A changeset for [`Wallet`](crate::Wallet). #[derive(Default, Debug, Clone, PartialEq, serde::Deserialize, serde::Serialize)] pub struct ChangeSet { @@ -18,6 +13,9 @@ pub struct ChangeSet { /// Changes to the [`LocalChain`](local_chain::LocalChain). pub local_chain: local_chain::ChangeSet, /// Changes to [`TxGraph`](tx_graph::TxGraph). + /// + /// The `indexer` field of this changeset can be ignored, as it is always the unit + /// type. The actual [`Self::indexer`] changeset is managed separately. pub tx_graph: tx_graph::ChangeSet, /// Changes to [`KeychainTxOutIndex`](keychain_txout::KeychainTxOutIndex). pub indexer: keychain_txout::ChangeSet, @@ -191,11 +189,19 @@ impl From for ChangeSet { } } -impl From for ChangeSet { - fn from(indexed_tx_graph: IndexedTxGraphChangeSet) -> Self { +impl From> for ChangeSet { + fn from( + tx_graph: tx_graph::ChangeSet, + ) -> Self { Self { - tx_graph: indexed_tx_graph.tx_graph, - indexer: indexed_tx_graph.indexer, + tx_graph: tx_graph::ChangeSet { + txs: tx_graph.txs, + txouts: tx_graph.txouts, + anchors: tx_graph.anchors, + last_seen: tx_graph.last_seen, + ..Default::default() + }, + indexer: tx_graph.indexer, ..Default::default() } } diff --git a/crates/wallet/src/wallet/mod.rs b/crates/wallet/src/wallet/mod.rs index 3d60aa2ec..370b3dbad 100644 --- a/crates/wallet/src/wallet/mod.rs +++ b/crates/wallet/src/wallet/mod.rs @@ -22,16 +22,15 @@ use alloc::{ use core::{cmp::Ordering, fmt, mem, ops::Deref}; use bdk_chain::{ - indexed_tx_graph, indexer::keychain_txout::KeychainTxOutIndex, local_chain::{ApplyHeaderError, CannotConnectError, CheckPoint, CheckPointIter, LocalChain}, spk_client::{ FullScanRequest, FullScanRequestBuilder, FullScanResponse, SyncRequest, SyncRequestBuilder, SyncResponse, }, - tx_graph::{CalculateFeeError, CanonicalTx, TxGraph, TxUpdate}, - BlockId, ChainPosition, ConfirmationBlockTime, DescriptorExt, FullTxOut, Indexed, - IndexedTxGraph, Indexer, Merge, + tx_graph::{self, CalculateFeeError, CanonicalTx, TxGraph, TxUpdate}, + BlockId, ChainPosition, ConfirmationBlockTime, DescriptorExt, FullTxOut, Indexed, Indexer, + Merge, }; use bitcoin::{ absolute, @@ -104,7 +103,7 @@ pub struct Wallet { signers: Arc, change_signers: Arc, chain: LocalChain, - indexed_graph: IndexedTxGraph>, + tx_graph: TxGraph>, stage: ChangeSet, network: Network, secp: SecpCtx, @@ -403,16 +402,14 @@ impl Wallet { let descriptor = index.get_descriptor(KeychainKind::External).cloned(); let change_descriptor = index.get_descriptor(KeychainKind::Internal).cloned(); - let indexed_graph = IndexedTxGraph::new(index); - let indexed_graph_changeset = indexed_graph.initial_changeset(); + let tx_graph = TxGraph::new(index); let stage = ChangeSet { descriptor, change_descriptor, local_chain: chain_changeset, - tx_graph: indexed_graph_changeset.tx_graph, - indexer: indexed_graph_changeset.indexer, network: Some(network), + ..Default::default() }; Ok(Wallet { @@ -420,7 +417,7 @@ impl Wallet { change_signers, network, chain, - indexed_graph, + tx_graph, stage, secp, }) @@ -598,12 +595,14 @@ impl Wallet { None => Arc::new(SignersContainer::new()), }; - let index = create_indexer(descriptor, change_descriptor, params.lookahead) + let mut index = create_indexer(descriptor, change_descriptor, params.lookahead) .map_err(LoadError::Descriptor)?; + index.apply_changeset(changeset.indexer); - let mut indexed_graph = IndexedTxGraph::new(index); - indexed_graph.apply_changeset(changeset.indexer.into()); - indexed_graph.apply_changeset(changeset.tx_graph.into()); + let mut graph = TxGraph::new(()); + graph.apply_changeset(changeset.tx_graph); + let mut indexed_graph = graph.swap_indexer(index).0; + let _ = indexed_graph.reindex(); let stage = ChangeSet::default(); @@ -611,7 +610,7 @@ impl Wallet { signers, change_signers, chain, - indexed_graph, + tx_graph: indexed_graph, stage, network, secp, @@ -625,7 +624,7 @@ impl Wallet { /// Iterator over all keychains in this wallet pub fn keychains(&self) -> impl Iterator { - self.indexed_graph.index.keychains() + self.tx_graph.index.keychains() } /// Peek an address of the given `keychain` at `index` without revealing it. @@ -639,7 +638,7 @@ impl Wallet { pub fn peek_address(&self, keychain: KeychainKind, mut index: u32) -> AddressInfo { let keychain = self.map_keychain(keychain); let mut spk_iter = self - .indexed_graph + .tx_graph .index .unbounded_spk_iter(keychain) .expect("keychain must exist"); @@ -684,7 +683,7 @@ impl Wallet { /// ``` pub fn reveal_next_address(&mut self, keychain: KeychainKind) -> AddressInfo { let keychain = self.map_keychain(keychain); - let index = &mut self.indexed_graph.index; + let index = &mut self.tx_graph.index; let stage = &mut self.stage; let ((index, spk), index_changeset) = index @@ -717,7 +716,7 @@ impl Wallet { ) -> impl Iterator + '_ { let keychain = self.map_keychain(keychain); let (spks, index_changeset) = self - .indexed_graph + .tx_graph .index .reveal_to_target(keychain, index) .expect("keychain must exist"); @@ -741,14 +740,13 @@ impl Wallet { /// calls to this method before closing the wallet. See [`Wallet::reveal_next_address`]. pub fn next_unused_address(&mut self, keychain: KeychainKind) -> AddressInfo { let keychain = self.map_keychain(keychain); - let index = &mut self.indexed_graph.index; + let indexer = &mut self.tx_graph.index; - let ((index, spk), index_changeset) = index + let ((index, spk), index_changeset) = indexer .next_unused_spk(keychain) .expect("keychain must exist"); - self.stage - .merge(indexed_tx_graph::ChangeSet::from(index_changeset).into()); + self.stage.merge(index_changeset.into()); AddressInfo { index, @@ -762,7 +760,7 @@ impl Wallet { /// /// Returns whether the given index was present and then removed from the unused set. pub fn mark_used(&mut self, keychain: KeychainKind, index: u32) -> bool { - self.indexed_graph.index.mark_used(keychain, index) + self.tx_graph.index.mark_used(keychain, index) } /// Undoes the effect of [`mark_used`] and returns whether the `index` was inserted @@ -774,7 +772,7 @@ impl Wallet { /// /// [`mark_used`]: Self::mark_used pub fn unmark_used(&mut self, keychain: KeychainKind, index: u32) -> bool { - self.indexed_graph.index.unmark_used(keychain, index) + self.tx_graph.index.unmark_used(keychain, index) } /// List addresses that are revealed but unused. @@ -786,7 +784,7 @@ impl Wallet { &self, keychain: KeychainKind, ) -> impl DoubleEndedIterator + '_ { - self.indexed_graph + self.tx_graph .index .unused_keychain_spks(self.map_keychain(keychain)) .map(move |(index, spk)| AddressInfo { @@ -799,24 +797,23 @@ impl Wallet { /// Return whether or not a `script` is part of this wallet (either internal or external) pub fn is_mine(&self, script: ScriptBuf) -> bool { - self.indexed_graph.index.index_of_spk(script).is_some() + self.tx_graph.index.index_of_spk(script).is_some() } /// Finds how the wallet derived the script pubkey `spk`. /// /// Will only return `Some(_)` if the wallet has given out the spk. pub fn derivation_of_spk(&self, spk: ScriptBuf) -> Option<(KeychainKind, u32)> { - self.indexed_graph.index.index_of_spk(spk).cloned() + self.tx_graph.index.index_of_spk(spk).cloned() } /// Return the list of unspent outputs of this wallet pub fn list_unspent(&self) -> impl Iterator + '_ { - self.indexed_graph - .graph() + self.tx_graph .filter_chain_unspents( &self.chain, self.chain.tip().block_id(), - self.indexed_graph.index.outpoints().iter().cloned(), + self.tx_graph.index.outpoints().iter().cloned(), ) .map(|((k, i), full_txo)| new_local_utxo(k, i, full_txo)) } @@ -825,12 +822,11 @@ impl Wallet { /// /// To list only unspent outputs (UTXOs), use [`Wallet::list_unspent`] instead. pub fn list_output(&self) -> impl Iterator + '_ { - self.indexed_graph - .graph() + self.tx_graph .filter_chain_txouts( &self.chain, self.chain.tip().block_id(), - self.indexed_graph.index.outpoints().iter().cloned(), + self.tx_graph.index.outpoints().iter().cloned(), ) .map(|((k, i), full_txo)| new_local_utxo(k, i, full_txo)) } @@ -856,7 +852,7 @@ impl Wallet { pub fn all_unbounded_spk_iters( &self, ) -> BTreeMap> + Clone> { - self.indexed_graph.index.all_unbounded_spk_iters() + self.tx_graph.index.all_unbounded_spk_iters() } /// Get an unbounded script pubkey iterator for the given `keychain`. @@ -868,7 +864,7 @@ impl Wallet { &self, keychain: KeychainKind, ) -> impl Iterator> + Clone { - self.indexed_graph + self.tx_graph .index .unbounded_spk_iter(self.map_keychain(keychain)) .expect("keychain must exist") @@ -877,9 +873,8 @@ impl Wallet { /// Returns the utxo owned by this wallet corresponding to `outpoint` if it exists in the /// wallet's database. pub fn get_utxo(&self, op: OutPoint) -> Option { - let ((keychain, index), _) = self.indexed_graph.index.txout(op)?; - self.indexed_graph - .graph() + let ((keychain, index), _) = self.tx_graph.index.txout(op)?; + self.tx_graph .filter_chain_unspents( &self.chain, self.chain.tip().block_id(), @@ -907,7 +902,7 @@ impl Wallet { /// [`list_unspent`]: Self::list_unspent /// [`list_output`]: Self::list_output pub fn insert_txout(&mut self, outpoint: OutPoint, txout: TxOut) { - let additions = self.indexed_graph.insert_txout(outpoint, txout); + let additions = self.tx_graph.insert_txout(outpoint, txout); self.stage.merge(additions.into()); } @@ -939,7 +934,7 @@ impl Wallet { /// ``` /// [`insert_txout`]: Self::insert_txout pub fn calculate_fee(&self, tx: &Transaction) -> Result { - self.indexed_graph.graph().calculate_fee(tx) + self.tx_graph.calculate_fee(tx) } /// Calculate the [`FeeRate`] for a given transaction. @@ -999,7 +994,7 @@ impl Wallet { /// let (sent, received) = wallet.sent_and_received(tx); /// ``` pub fn sent_and_received(&self, tx: &Transaction) -> (Amount, Amount) { - self.indexed_graph.index.sent_and_received(tx, ..) + self.tx_graph.index.sent_and_received(tx, ..) } /// Get a single transaction from the wallet as a [`WalletTx`] (if the transaction exists). @@ -1056,8 +1051,7 @@ impl Wallet { /// /// [`Anchor`]: bdk_chain::Anchor pub fn get_tx(&self, txid: Txid) -> Option { - let graph = self.indexed_graph.graph(); - graph + self.tx_graph .list_canonical_txs(&self.chain, self.chain.tip().block_id()) .find(|tx| tx.tx_node.txid == txid) } @@ -1074,8 +1068,8 @@ impl Wallet { /// To iterate over all canonical transactions, including those that are irrelevant, use /// [`TxGraph::list_canonical_txs`]. pub fn transactions(&self) -> impl Iterator + '_ { - let tx_graph = self.indexed_graph.graph(); - let tx_index = &self.indexed_graph.index; + let tx_graph = &self.tx_graph; + let tx_index = &self.tx_graph.index; tx_graph .list_canonical_txs(&self.chain, self.chain.tip().block_id()) .filter(|c_tx| tx_index.is_tx_relevant(&c_tx.tx_node.tx)) @@ -1109,10 +1103,10 @@ impl Wallet { /// Return the balance, separated into available, trusted-pending, untrusted-pending and immature /// values. pub fn balance(&self) -> Balance { - self.indexed_graph.graph().balance( + self.tx_graph.balance( &self.chain, self.chain.tip().block_id(), - self.indexed_graph.index.outpoints().iter().cloned(), + self.tx_graph.index.outpoints().iter().cloned(), |&(k, _), _| k == KeychainKind::Internal, ) } @@ -1143,7 +1137,7 @@ impl Wallet { KeychainKind::External => Arc::make_mut(&mut self.signers), KeychainKind::Internal => Arc::make_mut(&mut self.change_signers), }; - if let Some(descriptor) = self.indexed_graph.index.get_descriptor(keychain) { + if let Some(descriptor) = self.tx_graph.index.get_descriptor(keychain) { *wallet_signers = SignersContainer::build(keymap, descriptor, &self.secp) } } @@ -1223,7 +1217,7 @@ impl Wallet { params: TxParams, rng: &mut impl RngCore, ) -> Result { - let keychains: BTreeMap<_, _> = self.indexed_graph.index.keychains().collect(); + let keychains: BTreeMap<_, _> = self.tx_graph.index.keychains().collect(); let external_descriptor = keychains.get(&KeychainKind::External).expect("must exist"); let internal_descriptor = keychains.get(&KeychainKind::Internal); @@ -1427,13 +1421,13 @@ impl Wallet { None => { let change_keychain = self.map_keychain(KeychainKind::Internal); let (index, spk) = self - .indexed_graph + .tx_graph .index .unused_keychain_spks(change_keychain) .next() .unwrap_or_else(|| { let (next_index, _) = self - .indexed_graph + .tx_graph .index .next_index(change_keychain) .expect("keychain must exist"); @@ -1522,7 +1516,7 @@ impl Wallet { // recording changes to the change keychain if let (Excess::Change { .. }, Some((keychain, index))) = (excess, drain_index) { let (_, index_changeset) = self - .indexed_graph + .tx_graph .index .reveal_to_target(keychain, index) .expect("must not be None"); @@ -1578,8 +1572,8 @@ impl Wallet { &mut self, txid: Txid, ) -> Result, BuildFeeBumpError> { - let graph = self.indexed_graph.graph(); - let txout_index = &self.indexed_graph.index; + let graph = &self.tx_graph; + let txout_index = &self.tx_graph.index; let chain_tip = self.chain.tip().block_id(); let chain_positions = graph .list_canonical_txs(&self.chain, chain_tip) @@ -1809,7 +1803,7 @@ impl Wallet { /// the same structure but with the all secret keys replaced by their corresponding public key. /// This can be used to build a watch-only version of a wallet. pub fn public_descriptor(&self, keychain: KeychainKind) -> &ExtendedDescriptor { - self.indexed_graph + self.tx_graph .index .get_descriptor(self.map_keychain(keychain)) .expect("keychain must exist") @@ -1837,8 +1831,7 @@ impl Wallet { .map(|txin| txin.previous_output.txid) .collect::>(); let confirmation_heights = self - .indexed_graph - .graph() + .tx_graph .list_canonical_txs(&self.chain, chain_tip) .filter(|canon_tx| prev_txids.contains(&canon_tx.tx_node.txid)) // This is for a small performance gain. Although `.filter` filters out excess txs, it @@ -1881,7 +1874,7 @@ impl Wallet { .get_utxo_for(n) .and_then(|txout| self.get_descriptor_for_txout(&txout)) .or_else(|| { - self.indexed_graph.index.keychains().find_map(|(_, desc)| { + self.tx_graph.index.keychains().find_map(|(_, desc)| { desc.derive_from_psbt_input(psbt_input, psbt.get_utxo_for(n), &self.secp) }) }); @@ -1940,12 +1933,12 @@ impl Wallet { /// The derivation index of this wallet. It will return `None` if it has not derived any addresses. /// Otherwise, it will return the index of the highest address it has derived. pub fn derivation_index(&self, keychain: KeychainKind) -> Option { - self.indexed_graph.index.last_revealed_index(keychain) + self.tx_graph.index.last_revealed_index(keychain) } /// The index of the next address that you would get if you were to ask the wallet for a new address pub fn next_derivation_index(&self, keychain: KeychainKind) -> u32 { - self.indexed_graph + self.tx_graph .index .next_index(self.map_keychain(keychain)) .expect("keychain must exist") @@ -1957,7 +1950,7 @@ impl Wallet { /// This frees up the change address used when creating the tx for use in future transactions. // TODO: Make this free up reserved utxos when that's implemented pub fn cancel_tx(&mut self, tx: &Transaction) { - let txout_index = &mut self.indexed_graph.index; + let txout_index = &mut self.tx_graph.index; for txout in &tx.output { if let Some((keychain, index)) = txout_index.index_of_spk(txout.script_pubkey.clone()) { // NOTE: unmark_used will **not** make something unused if it has actually been used @@ -1969,7 +1962,7 @@ impl Wallet { fn get_descriptor_for_txout(&self, txout: &TxOut) -> Option { let &(keychain, child) = self - .indexed_graph + .tx_graph .index .index_of_spk(txout.script_pubkey.clone())?; let descriptor = self.public_descriptor(keychain); @@ -2032,7 +2025,7 @@ impl Wallet { .iter() .map(|u| -> bool { let txid = u.0.outpoint.txid; - let tx = match self.indexed_graph.graph().get_tx(txid) { + let tx = match self.tx_graph.get_tx(txid) { Some(tx) => tx, None => return false, }; @@ -2177,7 +2170,7 @@ impl Wallet { // Try to find the prev_script in our db to figure out if this is internal or external, // and the derivation index let &(keychain, child) = self - .indexed_graph + .tx_graph .index .index_of_spk(utxo.txout.script_pubkey) .ok_or(CreateTxError::UnknownUtxo)?; @@ -2197,7 +2190,7 @@ impl Wallet { .map_err(MiniscriptPsbtError::Conversion)?; let prev_output = utxo.outpoint; - if let Some(prev_tx) = self.indexed_graph.graph().get_tx(prev_output.txid) { + if let Some(prev_tx) = self.tx_graph.get_tx(prev_output.txid) { if desc.is_witness() || desc.is_taproot() { psbt_input.witness_utxo = Some(prev_tx.output[prev_output.vout as usize].clone()); } @@ -2224,9 +2217,7 @@ impl Wallet { // Try to figure out the keychain and derivation for every input and output for (is_input, index, out) in utxos.into_iter() { - if let Some(&(keychain, child)) = - self.indexed_graph.index.index_of_spk(out.script_pubkey) - { + if let Some(&(keychain, child)) = self.tx_graph.index.index_of_spk(out.script_pubkey) { let desc = self.public_descriptor(keychain); let desc = desc .at_derivation_index(child) @@ -2296,12 +2287,12 @@ impl Wallet { }; let index_changeset = self - .indexed_graph + .tx_graph .index .reveal_to_target_multi(&update.last_active_indices); changeset.merge(index_changeset.into()); changeset.merge( - self.indexed_graph + self.tx_graph .apply_update_at(update.tx_update, Some(seen_at)) .into(), ); @@ -2333,13 +2324,13 @@ impl Wallet { } /// Get a reference to the inner [`TxGraph`]. - pub fn tx_graph(&self) -> &TxGraph { - self.indexed_graph.graph() + pub fn tx_graph(&self) -> &TxGraph> { + &self.tx_graph } /// Get a reference to the inner [`KeychainTxOutIndex`]. pub fn spk_index(&self) -> &KeychainTxOutIndex { - &self.indexed_graph.index + &self.tx_graph.index } /// Get a reference to the inner [`LocalChain`]. @@ -2396,11 +2387,7 @@ impl Wallet { .apply_header_connected_to(&block.header, height, connected_to)? .into(), ); - changeset.merge( - self.indexed_graph - .apply_block_relevant(block, height) - .into(), - ); + changeset.merge(self.tx_graph.apply_block_relevant(block, height).into()); self.stage.merge(changeset); Ok(()) } @@ -2422,7 +2409,7 @@ impl Wallet { unconfirmed_txs: impl IntoIterator, ) { let indexed_graph_changeset = self - .indexed_graph + .tx_graph .batch_insert_relevant_unconfirmed(unconfirmed_txs); self.stage.merge(indexed_graph_changeset.into()); } @@ -2451,7 +2438,7 @@ impl Wallet { use bdk_chain::keychain_txout::SyncRequestBuilderExt; SyncRequest::builder() .chain_tip(self.chain.tip()) - .revealed_spks_from_indexer(&self.indexed_graph.index, ..) + .revealed_spks_from_indexer(&self.tx_graph.index, ..) } /// Create a [`FullScanRequest] for this wallet. @@ -2466,13 +2453,15 @@ impl Wallet { use bdk_chain::keychain_txout::FullScanRequestBuilderExt; FullScanRequest::builder() .chain_tip(self.chain.tip()) - .spks_from_indexer(&self.indexed_graph.index) + .spks_from_indexer(&self.tx_graph.index) } } -impl AsRef> for Wallet { - fn as_ref(&self) -> &bdk_chain::tx_graph::TxGraph { - self.indexed_graph.graph() +impl AsRef>> for Wallet { + fn as_ref( + &self, + ) -> &tx_graph::TxGraph> { + &self.tx_graph } } diff --git a/example-crates/example_bitcoind_rpc_polling/src/main.rs b/example-crates/example_bitcoind_rpc_polling/src/main.rs index 95c547967..8f123fa06 100644 --- a/example-crates/example_bitcoind_rpc_polling/src/main.rs +++ b/example-crates/example_bitcoind_rpc_polling/src/main.rs @@ -159,8 +159,7 @@ fn main() -> anyhow::Result<()> { let graph_changeset = graph.apply_block_relevant(&emission.block, height); db_stage.merge(ChangeSet { local_chain: chain_changeset, - tx_graph: graph_changeset.tx_graph, - indexer: graph_changeset.indexer, + tx_graph: graph_changeset, ..Default::default() }); @@ -183,7 +182,7 @@ fn main() -> anyhow::Result<()> { last_print = Instant::now(); let synced_to = chain.tip(); let balance = { - graph.graph().balance( + graph.balance( &*chain, synced_to.block_id(), graph.index.outpoints().iter().cloned(), @@ -208,8 +207,7 @@ fn main() -> anyhow::Result<()> { { let db = &mut *db.lock().unwrap(); db_stage.merge(ChangeSet { - tx_graph: graph_changeset.tx_graph, - indexer: graph_changeset.indexer, + tx_graph: graph_changeset, ..Default::default() }); if let Some(changeset) = db_stage.take() { @@ -298,8 +296,7 @@ fn main() -> anyhow::Result<()> { db_stage.merge(ChangeSet { local_chain: chain_changeset, - tx_graph: graph_changeset.tx_graph, - indexer: graph_changeset.indexer, + tx_graph: graph_changeset, ..Default::default() }); @@ -320,7 +317,7 @@ fn main() -> anyhow::Result<()> { last_print = Some(Instant::now()); let synced_to = chain.tip(); let balance = { - graph.graph().balance( + graph.balance( &*chain, synced_to.block_id(), graph.index.outpoints().iter().cloned(), diff --git a/example-crates/example_cli/src/lib.rs b/example-crates/example_cli/src/lib.rs index 3a700db3a..99d73daba 100644 --- a/example-crates/example_cli/src/lib.rs +++ b/example-crates/example_cli/src/lib.rs @@ -21,10 +21,9 @@ use bdk_chain::miniscript::{ }; use bdk_chain::ConfirmationBlockTime; use bdk_chain::{ - indexed_tx_graph, indexer::keychain_txout::{self, KeychainTxOutIndex}, local_chain::{self, LocalChain}, - tx_graph, ChainOracle, DescriptorExt, FullTxOut, IndexedTxGraph, Merge, + tx_graph, ChainOracle, DescriptorExt, FullTxOut, Merge, TxGraph, }; use bdk_coin_select::{ metrics::LowestFee, Candidate, ChangePolicy, CoinSelector, DrainWeights, FeeRate, Target, @@ -37,8 +36,8 @@ use rand::prelude::*; pub use anyhow; pub use clap; -/// Alias for a `IndexedTxGraph` with specific `Anchor` and `Indexer`. -pub type KeychainTxGraph = IndexedTxGraph>; +/// Alias for a `TxGraph` with specific `Anchor` and `Indexer`. +pub type KeychainTxGraph = TxGraph>; /// ChangeSet #[derive(Default, Debug, Clone, PartialEq, serde::Deserialize, serde::Serialize)] @@ -51,10 +50,8 @@ pub struct ChangeSet { pub network: Option, /// Changes to the [`LocalChain`]. pub local_chain: local_chain::ChangeSet, - /// Changes to [`TxGraph`](tx_graph::TxGraph). - pub tx_graph: tx_graph::ChangeSet, - /// Changes to [`KeychainTxOutIndex`]. - pub indexer: keychain_txout::ChangeSet, + /// Changes to [`TxGraph`]. + pub tx_graph: tx_graph::ChangeSet, } #[derive(Parser)] @@ -420,7 +417,6 @@ pub fn planned_utxos( let chain_tip = chain.get_chain_tip()?; let outpoints = graph.index.outpoints(); graph - .graph() .try_filter_chain_unspents(chain, chain_tip, outpoints.iter().cloned())? .filter_map(|((k, i), full_txo)| -> Option> { let desc = graph @@ -467,7 +463,7 @@ pub fn handle_commands( spk_chooser(index, Keychain::External).expect("Must exist"); let db = &mut *db.lock().unwrap(); db.append_changeset(&ChangeSet { - indexer: index_changeset, + tx_graph: index_changeset.into(), ..Default::default() })?; let addr = Address::from_script(spk.as_script(), network)?; @@ -512,7 +508,7 @@ pub fn handle_commands( } } - let balance = graph.graph().try_balance( + let balance = graph.try_balance( chain, chain.get_chain_tip()?, graph.index.outpoints().iter().cloned(), @@ -555,7 +551,6 @@ pub fn handle_commands( unconfirmed, } => { let txouts = graph - .graph() .try_filter_chain_txouts(chain, chain_tip, outpoints.iter().cloned())? .filter(|(_, full_txo)| match (spent, unspent) { (true, false) => full_txo.spent_by.is_some(), @@ -630,7 +625,7 @@ pub fn handle_commands( { let db = &mut *db.lock().unwrap(); db.append_changeset(&ChangeSet { - indexer, + tx_graph: indexer.into(), ..Default::default() })?; } @@ -720,8 +715,7 @@ pub fn handle_commands( // it's not a big deal since we can always find it again from the // blockchain. db.lock().unwrap().append_changeset(&ChangeSet { - tx_graph: changeset.tx_graph, - indexer: changeset.indexer, + tx_graph: changeset, ..Default::default() })?; } @@ -812,10 +806,7 @@ pub fn init_or_load( index.insert_descriptor(Keychain::Internal, change_desc)?; } let mut graph = KeychainTxGraph::new(index); - graph.apply_changeset(indexed_tx_graph::ChangeSet { - tx_graph: changeset.tx_graph, - indexer: changeset.indexer, - }); + graph.apply_changeset(changeset.tx_graph); graph }); @@ -924,7 +915,6 @@ impl Merge for ChangeSet { } Merge::merge(&mut self.local_chain, other.local_chain); Merge::merge(&mut self.tx_graph, other.tx_graph); - Merge::merge(&mut self.indexer, other.indexer); } fn is_empty(&self) -> bool { @@ -933,6 +923,5 @@ impl Merge for ChangeSet { && self.network.is_none() && self.local_chain.is_empty() && self.tx_graph.is_empty() - && self.indexer.is_empty() } } diff --git a/example-crates/example_electrum/src/main.rs b/example-crates/example_electrum/src/main.rs index 9c705a3df..8c214cf83 100644 --- a/example-crates/example_electrum/src/main.rs +++ b/example-crates/example_electrum/src/main.rs @@ -3,9 +3,8 @@ use std::io::{self, Write}; use bdk_chain::{ bitcoin::Network, collections::BTreeSet, - indexed_tx_graph, spk_client::{FullScanRequest, SyncRequest}, - ConfirmationBlockTime, Merge, + tx_graph, Merge, }; use bdk_electrum::{ electrum_client::{self, Client, ElectrumApi}, @@ -127,14 +126,7 @@ fn main() -> anyhow::Result<()> { let client = BdkElectrumClient::new(electrum_cmd.electrum_args().client(network)?); // Tell the electrum client about the txs we've already got locally so it doesn't re-download them - client.populate_tx_cache( - graph - .lock() - .unwrap() - .graph() - .full_txs() - .map(|tx_node| tx_node.tx), - ); + client.populate_tx_cache(graph.lock().unwrap().full_txs().map(|tx_node| tx_node.tx)); let (chain_update, tx_update, keychain_update) = match electrum_cmd.clone() { ElectrumCommands::Scan { @@ -177,13 +169,13 @@ fn main() -> anyhow::Result<()> { }) }; - let res = client + let resp = client .full_scan::<_>(request, stop_gap, scan_options.batch_size, false) .context("scanning the blockchain")?; ( - res.chain_update, - res.tx_update, - Some(res.last_active_indices), + resp.chain_update, + resp.tx_update, + Some(resp.last_active_indices), ) } ElectrumCommands::Sync { @@ -225,7 +217,6 @@ fn main() -> anyhow::Result<()> { let init_outpoints = graph.index.outpoints(); request = request.outpoints( graph - .graph() .filter_chain_unspents( &*chain, chain_tip.block_id(), @@ -237,21 +228,20 @@ fn main() -> anyhow::Result<()> { if unconfirmed { request = request.txids( graph - .graph() .list_canonical_txs(&*chain, chain_tip.block_id()) .filter(|canonical_tx| !canonical_tx.chain_position.is_confirmed()) .map(|canonical_tx| canonical_tx.tx_node.txid), ); } - let res = client + let resp = client .sync(request, scan_options.batch_size, false) .context("scanning the blockchain")?; // drop lock on graph and chain drop((graph, chain)); - (res.chain_update, res.tx_update, None) + (resp.chain_update, resp.tx_update, None) } }; @@ -261,18 +251,15 @@ fn main() -> anyhow::Result<()> { let chain_changeset = chain.apply_update(chain_update.expect("request has chain tip"))?; - let mut indexed_tx_graph_changeset = - indexed_tx_graph::ChangeSet::::default(); + let mut tx_graph_changeset = tx_graph::ChangeSet::default(); if let Some(keychain_update) = keychain_update { - let keychain_changeset = graph.index.reveal_to_target_multi(&keychain_update); - indexed_tx_graph_changeset.merge(keychain_changeset.into()); + tx_graph_changeset.merge(graph.index.reveal_to_target_multi(&keychain_update).into()); } - indexed_tx_graph_changeset.merge(graph.apply_update(tx_update)); + tx_graph_changeset.merge(graph.apply_update(tx_update)); ChangeSet { local_chain: chain_changeset, - tx_graph: indexed_tx_graph_changeset.tx_graph, - indexer: indexed_tx_graph_changeset.indexer, + tx_graph: tx_graph_changeset, ..Default::default() } }; diff --git a/example-crates/example_esplora/src/main.rs b/example-crates/example_esplora/src/main.rs index cba86b862..f836af839 100644 --- a/example-crates/example_esplora/src/main.rs +++ b/example-crates/example_esplora/src/main.rs @@ -126,7 +126,7 @@ fn main() -> anyhow::Result<()> { }; let client = esplora_cmd.esplora_args().client(network)?; - // Prepare the `IndexedTxGraph` and `LocalChain` updates based on whether we are scanning or + // Prepare the `TxGraph` and `LocalChain` updates based on whether we are scanning or // syncing. // // Scanning: We are iterating through spks of all keychains and scanning for transactions for @@ -137,7 +137,7 @@ fn main() -> anyhow::Result<()> { // // Syncing: We only check for specified spks, utxos and txids to update their confirmation // status or fetch missing transactions. - let (local_chain_changeset, indexed_tx_graph_changeset) = match &esplora_cmd { + let (local_chain_changeset, tx_graph_changeset) = match &esplora_cmd { EsploraCommands::Scan { stop_gap, scan_options, @@ -239,7 +239,6 @@ fn main() -> anyhow::Result<()> { let init_outpoints = graph.index.outpoints(); request = request.outpoints( graph - .graph() .filter_chain_unspents( &*chain, local_tip.block_id(), @@ -254,7 +253,6 @@ fn main() -> anyhow::Result<()> { // `EsploraExt::update_tx_graph_without_keychain`. request = request.txids( graph - .graph() .list_canonical_txs(&*chain, local_tip.block_id()) .filter(|canonical_tx| !canonical_tx.chain_position.is_confirmed()) .map(|canonical_tx| canonical_tx.tx_node.txid), @@ -280,8 +278,7 @@ fn main() -> anyhow::Result<()> { let mut db = db.lock().unwrap(); db.append_changeset(&ChangeSet { local_chain: local_chain_changeset, - tx_graph: indexed_tx_graph_changeset.tx_graph, - indexer: indexed_tx_graph_changeset.indexer, + tx_graph: tx_graph_changeset, ..Default::default() })?; Ok(())