diff --git a/crates/bdk/src/wallet/mod.rs b/crates/bdk/src/wallet/mod.rs index bd33a9047..4eb686eb4 100644 --- a/crates/bdk/src/wallet/mod.rs +++ b/crates/bdk/src/wallet/mod.rs @@ -942,7 +942,7 @@ impl Wallet { /// # let mut wallet: Wallet<()> = todo!(); /// # let txid:Txid = todo!(); /// let tx = wallet.get_tx(txid).expect("transaction").tx_node.tx; - /// let fee = wallet.calculate_fee(tx).expect("fee"); + /// let fee = wallet.calculate_fee(&tx).expect("fee"); /// ``` /// /// ```rust, no_run @@ -973,7 +973,7 @@ impl Wallet { /// # let mut wallet: Wallet<()> = todo!(); /// # let txid:Txid = todo!(); /// let tx = wallet.get_tx(txid).expect("transaction").tx_node.tx; - /// let fee_rate = wallet.calculate_fee_rate(tx).expect("fee rate"); + /// let fee_rate = wallet.calculate_fee_rate(&tx).expect("fee rate"); /// ``` /// /// ```rust, no_run @@ -981,8 +981,8 @@ impl Wallet { /// # use bdk::Wallet; /// # let mut wallet: Wallet<()> = todo!(); /// # let mut psbt: PartiallySignedTransaction = todo!(); - /// let tx = &psbt.clone().extract_tx(); - /// let fee_rate = wallet.calculate_fee_rate(tx).expect("fee rate"); + /// let tx = psbt.clone().extract_tx(); + /// let fee_rate = wallet.calculate_fee_rate(&tx).expect("fee rate"); /// ``` /// [`insert_txout`]: Self::insert_txout pub fn calculate_fee_rate(&self, tx: &Transaction) -> Result { @@ -1003,8 +1003,8 @@ impl Wallet { /// # use bdk::Wallet; /// # let mut wallet: Wallet<()> = todo!(); /// # let txid:Txid = todo!(); - /// let tx = wallet.get_tx(txid).expect("transaction").tx_node.tx; - /// let (sent, received) = wallet.sent_and_received(tx); + /// let tx = wallet.get_tx(txid).expect("tx exists").tx_node.tx; + /// let (sent, received) = wallet.sent_and_received(&tx); /// ``` /// /// ```rust, no_run @@ -1065,7 +1065,7 @@ impl Wallet { pub fn get_tx( &self, txid: Txid, - ) -> Option> { + ) -> Option, ConfirmationTimeHeightAnchor>> { let graph = self.indexed_graph.graph(); Some(CanonicalTx { @@ -1167,7 +1167,8 @@ impl Wallet { /// Iterate over the transactions in the wallet. pub fn transactions( &self, - ) -> impl Iterator> + '_ { + ) -> impl Iterator, ConfirmationTimeHeightAnchor>> + '_ + { self.indexed_graph .graph() .list_chain_txs(&self.chain, self.chain.tip().block_id()) @@ -1670,6 +1671,7 @@ impl Wallet { let mut tx = graph .get_tx(txid) .ok_or(BuildFeeBumpError::TransactionNotFound(txid))? + .as_ref() .clone(); let pos = graph @@ -1739,7 +1741,7 @@ impl Wallet { sequence: Some(txin.sequence), psbt_input: Box::new(psbt::Input { witness_utxo: Some(txout.clone()), - non_witness_utxo: Some(prev_tx.clone()), + non_witness_utxo: Some(prev_tx.as_ref().clone()), ..Default::default() }), }, @@ -2295,7 +2297,7 @@ impl Wallet { psbt_input.witness_utxo = Some(prev_tx.output[prev_output.vout as usize].clone()); } if !desc.is_taproot() && (!desc.is_witness() || !only_witness_utxo) { - psbt_input.non_witness_utxo = Some(prev_tx.clone()); + psbt_input.non_witness_utxo = Some(prev_tx.as_ref().clone()); } } Ok(psbt_input) diff --git a/crates/bdk/tests/wallet.rs b/crates/bdk/tests/wallet.rs index e367b0bb5..b31b44bb2 100644 --- a/crates/bdk/tests/wallet.rs +++ b/crates/bdk/tests/wallet.rs @@ -208,12 +208,12 @@ fn test_get_funded_wallet_sent_and_received() { let mut tx_amounts: Vec<(Txid, (u64, u64))> = wallet .transactions() - .map(|ct| (ct.tx_node.txid, wallet.sent_and_received(ct.tx_node.tx))) + .map(|ct| (ct.tx_node.txid, wallet.sent_and_received(&ct.tx_node))) .collect(); tx_amounts.sort_by(|a1, a2| a1.0.cmp(&a2.0)); let tx = wallet.get_tx(txid).expect("transaction").tx_node.tx; - let (sent, received) = wallet.sent_and_received(tx); + let (sent, received) = wallet.sent_and_received(&tx); // The funded wallet contains a tx with a 76_000 sats input and two outputs, one spending 25_000 // to a foreign address and one returning 50_000 back to the wallet as change. The remaining 1000 @@ -227,7 +227,7 @@ fn test_get_funded_wallet_tx_fees() { let (wallet, txid) = get_funded_wallet(get_test_wpkh()); let tx = wallet.get_tx(txid).expect("transaction").tx_node.tx; - let tx_fee = wallet.calculate_fee(tx).expect("transaction fee"); + let tx_fee = wallet.calculate_fee(&tx).expect("transaction fee"); // The funded wallet contains a tx with a 76_000 sats input and two outputs, one spending 25_000 // to a foreign address and one returning 50_000 back to the wallet as change. The remaining 1000 @@ -240,7 +240,9 @@ fn test_get_funded_wallet_tx_fee_rate() { let (wallet, txid) = get_funded_wallet(get_test_wpkh()); let tx = wallet.get_tx(txid).expect("transaction").tx_node.tx; - let tx_fee_rate = wallet.calculate_fee_rate(tx).expect("transaction fee rate"); + let tx_fee_rate = wallet + .calculate_fee_rate(&tx) + .expect("transaction fee rate"); // The funded wallet contains a tx with a 76_000 sats input and two outputs, one spending 25_000 // to a foreign address and one returning 50_000 back to the wallet as change. The remaining 1000 @@ -1307,7 +1309,7 @@ fn test_add_foreign_utxo_where_outpoint_doesnt_match_psbt_input() { .add_foreign_utxo( utxo2.outpoint, psbt::Input { - non_witness_utxo: Some(tx1), + non_witness_utxo: Some(tx1.as_ref().clone()), ..Default::default() }, satisfaction_weight @@ -1320,7 +1322,7 @@ fn test_add_foreign_utxo_where_outpoint_doesnt_match_psbt_input() { .add_foreign_utxo( utxo2.outpoint, psbt::Input { - non_witness_utxo: Some(tx2), + non_witness_utxo: Some(tx2.as_ref().clone()), ..Default::default() }, satisfaction_weight @@ -1384,7 +1386,7 @@ fn test_add_foreign_utxo_only_witness_utxo() { let mut builder = builder.clone(); let tx2 = wallet2.get_tx(txid2).unwrap().tx_node.tx; let psbt_input = psbt::Input { - non_witness_utxo: Some(tx2.clone()), + non_witness_utxo: Some(tx2.as_ref().clone()), ..Default::default() }; builder @@ -3050,7 +3052,8 @@ fn test_taproot_sign_using_non_witness_utxo() { let mut psbt = builder.finish().unwrap(); psbt.inputs[0].witness_utxo = None; - psbt.inputs[0].non_witness_utxo = Some(wallet.get_tx(prev_txid).unwrap().tx_node.tx.clone()); + psbt.inputs[0].non_witness_utxo = + Some(wallet.get_tx(prev_txid).unwrap().tx_node.as_ref().clone()); assert!( psbt.inputs[0].non_witness_utxo.is_some(), "Previous tx should be present in the database" diff --git a/crates/chain/Cargo.toml b/crates/chain/Cargo.toml index 0a77708a1..6c5a59915 100644 --- a/crates/chain/Cargo.toml +++ b/crates/chain/Cargo.toml @@ -15,7 +15,7 @@ readme = "README.md" [dependencies] # For no-std, remember to enable the bitcoin/no-std feature bitcoin = { version = "0.30.0", default-features = false } -serde_crate = { package = "serde", version = "1", optional = true, features = ["derive"] } +serde_crate = { package = "serde", version = "1", optional = true, features = ["derive", "rc"] } # Use hashbrown as a feature flag to have HashSet and HashMap from it. hashbrown = { version = "0.9.1", optional = true, features = ["serde"] } diff --git a/crates/chain/src/tx_graph.rs b/crates/chain/src/tx_graph.rs index 34cbccf5c..30d020ecb 100644 --- a/crates/chain/src/tx_graph.rs +++ b/crates/chain/src/tx_graph.rs @@ -1,26 +1,27 @@ //! 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 doesn't 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. +//! [`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. //! -//! 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 [`insert_txout`] method.). +//! 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 +//! [`insert_txout`] method.). //! -//! The graph contains transactions in the form of [`TxNode`]s. Each node contains the -//! txid, the transaction (whole or partial), the blocks it's anchored in (see the [`Anchor`] -//! documentation for more details), and the timestamp of the last time we saw -//! the transaction as unconfirmed. +//! The graph contains transactions in the form of [`TxNode`]s. Each node contains the txid, the +//! transaction (whole or partial), the blocks that it is anchored to (see the [`Anchor`] +//! documentation for more details), and the timestamp of the last time we saw the transaction as +//! unconfirmed. //! //! Conflicting transactions are allowed to coexist within a [`TxGraph`]. This is useful for //! identifying and traversing conflicts and descendants of a given transaction. Some [`TxGraph`] -//! methods only consider "canonical" (i.e., in the best chain or in mempool) transactions, -//! we decide which transactions are canonical based on anchors `last_seen_unconfirmed`; -//! see the [`try_get_chain_position`] documentation for more details. +//! methods only consider transactions that are "canonical" (i.e., in the best chain or in mempool). +//! We decide which transactions are canonical based on the transaction's anchors and the +//! `last_seen` (as unconfirmed) timestamp; see the [`try_get_chain_position`] documentation for +//! more details. //! //! The [`ChangeSet`] reports changes made to a [`TxGraph`]; it can be used to either save to //! persistent storage, or to be applied to another [`TxGraph`]. @@ -30,10 +31,22 @@ //! //! # Applying changes //! -//! Methods that apply changes to [`TxGraph`] will return [`ChangeSet`]. -//! [`ChangeSet`] can be applied back to a [`TxGraph`] or be used to inform persistent storage +//! Methods that change the state of [`TxGraph`] will return [`ChangeSet`]s. +//! [`ChangeSet`]s can be applied back to a [`TxGraph`] or be used to inform persistent storage //! of the changes to [`TxGraph`]. //! +//! # Generics +//! +//! Anchors are represented as generics within `TxGraph`. To make use of all functionality of the +//! `TxGraph`, anchors (`A`) should implement [`Anchor`]. +//! +//! Anchors are made generic so that different types of data can be stored with how a transaction is +//! *anchored* to a given block. An example of this is storing a merkle proof of the transaction to +//! the confirmation block - this can be done with a custom [`Anchor`] type. The minimal [`Anchor`] +//! type would just be a [`BlockId`] which just represents the height and hash of the block which +//! the transaction is contained in. Note that a transaction can be contained in multiple +//! conflicting blocks (by nature of the Bitcoin network). +//! //! ``` //! # use bdk_chain::BlockId; //! # use bdk_chain::tx_graph::TxGraph; @@ -80,6 +93,7 @@ use crate::{ ChainOracle, ChainPosition, FullTxOut, }; use alloc::collections::vec_deque::VecDeque; +use alloc::sync::Arc; use alloc::vec::Vec; use bitcoin::{OutPoint, Script, Transaction, TxOut, Txid}; use core::fmt::{self, Formatter}; @@ -122,7 +136,7 @@ pub struct TxNode<'a, T, A> { /// Txid of the transaction. pub txid: Txid, /// A partial or full representation of the transaction. - pub tx: &'a T, + pub tx: T, /// The blocks that the transaction is "anchored" in. pub anchors: &'a BTreeSet, /// The last-seen unix timestamp of the transaction as unconfirmed. @@ -133,7 +147,7 @@ impl<'a, T, A> Deref for TxNode<'a, T, A> { type Target = T; fn deref(&self) -> &Self::Target { - self.tx + &self.tx } } @@ -143,7 +157,7 @@ impl<'a, T, A> Deref for TxNode<'a, T, A> { /// outputs). #[derive(Clone, Debug, PartialEq)] enum TxNodeInternal { - Whole(Transaction), + Whole(Arc), Partial(BTreeMap), } @@ -198,6 +212,7 @@ impl TxGraph { pub fn all_txouts(&self) -> impl Iterator { self.txs.iter().flat_map(|(txid, (tx, _, _))| match tx { TxNodeInternal::Whole(tx) => tx + .as_ref() .output .iter() .enumerate() @@ -229,13 +244,13 @@ impl TxGraph { } /// Iterate over all full transactions in the graph. - pub fn full_txs(&self) -> impl Iterator> { + pub fn full_txs(&self) -> impl Iterator, A>> { self.txs .iter() .filter_map(|(&txid, (tx, anchors, last_seen))| match tx { TxNodeInternal::Whole(tx) => Some(TxNode { txid, - tx, + tx: tx.clone(), anchors, last_seen_unconfirmed: *last_seen, }), @@ -248,16 +263,16 @@ impl TxGraph { /// Refer to [`get_txout`] for getting a specific [`TxOut`]. /// /// [`get_txout`]: Self::get_txout - pub fn get_tx(&self, txid: Txid) -> Option<&Transaction> { + pub fn get_tx(&self, txid: Txid) -> Option> { self.get_tx_node(txid).map(|n| n.tx) } /// Get a transaction node by txid. This only returns `Some` for full transactions. - pub fn get_tx_node(&self, txid: Txid) -> Option> { + pub fn get_tx_node(&self, txid: Txid) -> Option, A>> { match &self.txs.get(&txid)? { (TxNodeInternal::Whole(tx), anchors, last_seen) => Some(TxNode { txid, - tx, + tx: tx.clone(), anchors, last_seen_unconfirmed: *last_seen, }), @@ -268,7 +283,7 @@ impl TxGraph { /// Obtains a single tx output (if any) at the specified outpoint. pub fn get_txout(&self, outpoint: OutPoint) -> Option<&TxOut> { match &self.txs.get(&outpoint.txid)?.0 { - TxNodeInternal::Whole(tx) => tx.output.get(outpoint.vout as usize), + TxNodeInternal::Whole(tx) => tx.as_ref().output.get(outpoint.vout as usize), TxNodeInternal::Partial(txouts) => txouts.get(&outpoint.vout), } } @@ -279,6 +294,7 @@ impl TxGraph { pub fn tx_outputs(&self, txid: Txid) -> Option> { Some(match &self.txs.get(&txid)?.0 { TxNodeInternal::Whole(tx) => tx + .as_ref() .output .iter() .enumerate() @@ -356,16 +372,15 @@ impl TxGraph { &self, txid: Txid, ) -> impl DoubleEndedIterator)> + '_ { - let start = OutPoint { txid, vout: 0 }; - let end = OutPoint { - txid, - vout: u32::MAX, - }; + let start = OutPoint::new(txid, 0); + let end = OutPoint::new(txid, u32::MAX); self.spends .range(start..=end) .map(|(outpoint, spends)| (outpoint.vout, spends)) } +} +impl TxGraph { /// Creates an iterator that filters and maps ancestor transactions. /// /// The iterator starts with the ancestors of the supplied `tx` (ancestor transactions of `tx` @@ -379,13 +394,10 @@ 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, F, O>( - &'g self, - tx: &'g Transaction, - walk_map: F, - ) -> TxAncestors<'g, A, F> + pub fn walk_ancestors<'g, T, F, O>(&'g self, tx: T, walk_map: F) -> TxAncestors<'g, A, F> where - F: FnMut(usize, &'g Transaction) -> Option + 'g, + T: Into>, + F: FnMut(usize, Arc) -> Option + 'g, { TxAncestors::new_exclude_root(self, tx, walk_map) } @@ -406,7 +418,9 @@ impl TxGraph { { TxDescendants::new_exclude_root(self, txid, walk_map) } +} +impl TxGraph { /// Creates an iterator that both filters and maps conflicting transactions (this includes /// descendants of directly-conflicting transactions, which are also considered conflicts). /// @@ -419,7 +433,7 @@ impl TxGraph { where F: FnMut(usize, Txid) -> Option + 'g, { - let txids = self.direct_conflitcs(tx).map(|(_, txid)| txid); + let txids = self.direct_conflicts(tx).map(|(_, txid)| txid); TxDescendants::from_multiple_include_root(self, txids, walk_map) } @@ -430,7 +444,7 @@ impl TxGraph { /// Note that this only returns directly conflicting txids and won't include: /// - descendants of conflicting transactions (which are technically also conflicting) /// - transactions conflicting with the given transaction's ancestors - pub fn direct_conflitcs<'g>( + pub fn direct_conflicts<'g>( &'g self, tx: &'g Transaction, ) -> impl Iterator + '_ { @@ -467,9 +481,7 @@ impl TxGraph { new_graph.apply_changeset(self.initial_changeset().map_anchors(f)); new_graph } -} -impl TxGraph { /// Construct a new [`TxGraph`] from a list of transactions. pub fn new(txs: impl IntoIterator) -> Self { let mut new = Self::default(); @@ -506,9 +518,10 @@ impl TxGraph { /// The [`ChangeSet`] returned will be empty if `tx` already exists. pub fn insert_tx(&mut self, tx: Transaction) -> ChangeSet { let mut update = Self::default(); - update - .txs - .insert(tx.txid(), (TxNodeInternal::Whole(tx), BTreeSet::new(), 0)); + update.txs.insert( + tx.txid(), + (TxNodeInternal::Whole(tx.into()), BTreeSet::new(), 0), + ); self.apply_update(update) } @@ -567,7 +580,8 @@ impl TxGraph { /// Applies [`ChangeSet`] to [`TxGraph`]. pub fn apply_changeset(&mut self, changeset: ChangeSet) { - for tx in changeset.txs { + for wrapped_tx in changeset.txs { + let tx = wrapped_tx.as_ref(); let txid = tx.txid(); tx.input @@ -582,18 +596,20 @@ impl TxGraph { match self.txs.get_mut(&txid) { Some((tx_node @ TxNodeInternal::Partial(_), _, _)) => { - *tx_node = TxNodeInternal::Whole(tx); + *tx_node = TxNodeInternal::Whole(wrapped_tx.clone()); } Some((TxNodeInternal::Whole(tx), _, _)) => { debug_assert_eq!( - tx.txid(), + tx.as_ref().txid(), txid, "tx should produce txid that is same as key" ); } None => { - self.txs - .insert(txid, (TxNodeInternal::Whole(tx), BTreeSet::new(), 0)); + self.txs.insert( + txid, + (TxNodeInternal::Whole(wrapped_tx), BTreeSet::new(), 0), + ); } } } @@ -630,7 +646,7 @@ impl TxGraph { /// The [`ChangeSet`] would be the set difference between `update` and `self` (transactions that /// exist in `update` but not in `self`). pub(crate) fn determine_changeset(&self, update: TxGraph) -> ChangeSet { - let mut changeset = ChangeSet::default(); + let mut changeset = ChangeSet::::default(); for (&txid, (update_tx_node, _, update_last_seen)) in &update.txs { let prev_last_seen: u64 = match (self.txs.get(&txid), update_tx_node) { @@ -791,10 +807,10 @@ impl TxGraph { TxNodeInternal::Whole(tx) => { // A coinbase tx that is not anchored in the best chain cannot be unconfirmed and // should always be filtered out. - if tx.is_coin_base() { + if tx.as_ref().is_coin_base() { return Ok(None); } - tx + tx.clone() } TxNodeInternal::Partial(_) => { // Partial transactions (outputs only) cannot have conflicts. @@ -811,8 +827,8 @@ impl TxGraph { // First of all, we retrieve all our ancestors. Since we're using `new_include_root`, the // resulting array will also include `tx` let unconfirmed_ancestor_txs = - TxAncestors::new_include_root(self, tx, |_, ancestor_tx: &Transaction| { - let tx_node = self.get_tx_node(ancestor_tx.txid())?; + TxAncestors::new_include_root(self, tx.clone(), |_, ancestor_tx: Arc| { + let tx_node = self.get_tx_node(ancestor_tx.as_ref().txid())?; // We're filtering the ancestors to keep only the unconfirmed ones (= no anchors in // the best chain) for block in tx_node.anchors { @@ -828,8 +844,10 @@ impl TxGraph { // We determine our tx's last seen, which is the max between our last seen, // and our unconf descendants' last seen. - let unconfirmed_descendants_txs = - TxDescendants::new_include_root(self, tx.txid(), |_, descendant_txid: Txid| { + let unconfirmed_descendants_txs = TxDescendants::new_include_root( + self, + tx.as_ref().txid(), + |_, descendant_txid: Txid| { let tx_node = self.get_tx_node(descendant_txid)?; // We're filtering the ancestors to keep only the unconfirmed ones (= no anchors in // the best chain) @@ -841,8 +859,9 @@ impl TxGraph { } } Some(Ok(tx_node)) - }) - .collect::, C::Error>>()?; + }, + ) + .collect::, C::Error>>()?; let tx_last_seen = unconfirmed_descendants_txs .iter() @@ -853,7 +872,8 @@ impl TxGraph { // Now we traverse our ancestors and consider all their conflicts for tx_node in unconfirmed_ancestor_txs { // We retrieve all the transactions conflicting with this specific ancestor - let conflicting_txs = self.walk_conflicts(tx_node.tx, |_, txid| self.get_tx_node(txid)); + let conflicting_txs = + self.walk_conflicts(tx_node.tx.as_ref(), |_, txid| self.get_tx_node(txid)); // If a conflicting tx is in the best chain, or has `last_seen` higher than this ancestor, then // this tx cannot exist in the best chain @@ -867,7 +887,7 @@ impl TxGraph { return Ok(None); } if conflicting_tx.last_seen_unconfirmed == *last_seen - && conflicting_tx.txid() > tx.txid() + && conflicting_tx.as_ref().txid() > tx.as_ref().txid() { // Conflicting tx has priority if txid of conflicting tx > txid of original tx return Ok(None); @@ -960,7 +980,7 @@ impl TxGraph { &'a self, chain: &'a C, chain_tip: BlockId, - ) -> impl Iterator, C::Error>> { + ) -> impl Iterator, A>, C::Error>> { self.full_txs().filter_map(move |tx| { self.try_get_chain_position(chain, chain_tip, tx.txid) .map(|v| { @@ -982,7 +1002,7 @@ impl TxGraph { &'a self, chain: &'a C, chain_tip: BlockId, - ) -> impl Iterator> { + ) -> impl Iterator, A>> { self.try_list_chain_txs(chain, chain_tip) .map(|r| r.expect("oracle is infallible")) } @@ -1021,7 +1041,7 @@ impl TxGraph { None => return Ok(None), }; - let txout = match tx_node.tx.output.get(op.vout as usize) { + let txout = match tx_node.tx.as_ref().output.get(op.vout as usize) { Some(txout) => txout.clone(), None => return Ok(None), }; @@ -1043,7 +1063,7 @@ impl TxGraph { txout, chain_position, spent_by, - is_on_coinbase: tx_node.tx.is_coin_base(), + is_on_coinbase: tx_node.tx.as_ref().is_coin_base(), }, ))) }, @@ -1209,7 +1229,7 @@ impl TxGraph { #[must_use] pub struct ChangeSet { /// Added transactions. - pub txs: BTreeSet, + pub txs: BTreeSet>, /// Added txouts. pub txouts: BTreeMap, /// Added anchors. @@ -1345,7 +1365,7 @@ impl AsRef> for TxGraph { pub struct TxAncestors<'g, A, F> { graph: &'g TxGraph, visited: HashSet, - queue: VecDeque<(usize, &'g Transaction)>, + queue: VecDeque<(usize, Arc)>, filter_map: F, } @@ -1353,13 +1373,13 @@ impl<'g, A, F> TxAncestors<'g, A, F> { /// Creates a `TxAncestors` that includes the starting `Transaction` when iterating. pub(crate) fn new_include_root( graph: &'g TxGraph, - tx: &'g Transaction, + tx: impl Into>, filter_map: F, ) -> Self { Self { graph, visited: Default::default(), - queue: [(0, tx)].into(), + queue: [(0, tx.into())].into(), filter_map, } } @@ -1367,7 +1387,7 @@ impl<'g, A, F> TxAncestors<'g, A, F> { /// Creates a `TxAncestors` that excludes the starting `Transaction` when iterating. pub(crate) fn new_exclude_root( graph: &'g TxGraph, - tx: &'g Transaction, + tx: impl Into>, filter_map: F, ) -> Self { let mut ancestors = Self { @@ -1376,7 +1396,7 @@ impl<'g, A, F> TxAncestors<'g, A, F> { queue: Default::default(), filter_map, }; - ancestors.populate_queue(1, tx); + ancestors.populate_queue(1, tx.into()); ancestors } @@ -1389,12 +1409,13 @@ impl<'g, A, F> TxAncestors<'g, A, F> { filter_map: F, ) -> Self where - I: IntoIterator, + I: IntoIterator, + I::Item: Into>, { Self { graph, visited: Default::default(), - queue: txs.into_iter().map(|tx| (0, tx)).collect(), + queue: txs.into_iter().map(|tx| (0, tx.into())).collect(), filter_map, } } @@ -1408,7 +1429,8 @@ impl<'g, A, F> TxAncestors<'g, A, F> { filter_map: F, ) -> Self where - I: IntoIterator, + I: IntoIterator, + I::Item: Into>, { let mut ancestors = Self { graph, @@ -1417,12 +1439,12 @@ impl<'g, A, F> TxAncestors<'g, A, F> { filter_map, }; for tx in txs { - ancestors.populate_queue(1, tx); + ancestors.populate_queue(1, tx.into()); } ancestors } - fn populate_queue(&mut self, depth: usize, tx: &'g Transaction) { + fn populate_queue(&mut self, depth: usize, tx: Arc) { let ancestors = tx .input .iter() @@ -1436,7 +1458,7 @@ impl<'g, A, F> TxAncestors<'g, A, F> { impl<'g, A, F, O> Iterator for TxAncestors<'g, A, F> where - F: FnMut(usize, &'g Transaction) -> Option, + F: FnMut(usize, Arc) -> Option, { type Item = O; @@ -1445,7 +1467,7 @@ where // we have exhausted all paths when queue is empty let (ancestor_depth, tx) = self.queue.pop_front()?; // ignore paths when user filters them out - let item = match (self.filter_map)(ancestor_depth, tx) { + let item = match (self.filter_map)(ancestor_depth, tx.clone()) { Some(item) => item, None => continue, }; diff --git a/crates/chain/tests/test_indexed_tx_graph.rs b/crates/chain/tests/test_indexed_tx_graph.rs index 41b1d4d3e..3fcaf2d19 100644 --- a/crates/chain/tests/test_indexed_tx_graph.rs +++ b/crates/chain/tests/test_indexed_tx_graph.rs @@ -1,7 +1,7 @@ #[macro_use] mod common; -use std::collections::BTreeSet; +use std::{collections::BTreeSet, sync::Arc}; use bdk_chain::{ indexed_tx_graph::{self, IndexedTxGraph}, @@ -66,7 +66,7 @@ fn insert_relevant_txs() { let changeset = indexed_tx_graph::ChangeSet { graph: tx_graph::ChangeSet { - txs: txs.clone().into(), + txs: txs.iter().cloned().map(Arc::new).collect(), ..Default::default() }, indexer: keychain::ChangeSet([((), 9_u32)].into()), @@ -80,7 +80,6 @@ fn insert_relevant_txs() { assert_eq!(graph.initial_changeset(), changeset,); } -#[test] /// Ensure consistency IndexedTxGraph list_* and balance methods. These methods lists /// relevant txouts and utxos from the information fetched from a ChainOracle (here a LocalChain). /// @@ -108,7 +107,7 @@ fn insert_relevant_txs() { /// /// Finally Add more blocks to local chain until tx1 coinbase maturity hits. /// Assert maturity at coinbase maturity inflection height. Block height 98 and 99. - +#[test] fn test_list_owned_txouts() { // Create Local chains let local_chain = LocalChain::from_blocks((0..150).map(|i| (i as u32, h!("random"))).collect()) diff --git a/crates/chain/tests/test_tx_graph.rs b/crates/chain/tests/test_tx_graph.rs index 37e8c7192..8b4674485 100644 --- a/crates/chain/tests/test_tx_graph.rs +++ b/crates/chain/tests/test_tx_graph.rs @@ -13,6 +13,7 @@ use bitcoin::{ use common::*; use core::iter; use rand::RngCore; +use std::sync::Arc; use std::vec; #[test] @@ -119,7 +120,7 @@ fn insert_txouts() { assert_eq!( graph.insert_tx(update_txs.clone()), ChangeSet { - txs: [update_txs.clone()].into(), + txs: [Arc::new(update_txs.clone())].into(), ..Default::default() } ); @@ -143,7 +144,7 @@ fn insert_txouts() { assert_eq!( changeset, ChangeSet { - txs: [update_txs.clone()].into(), + txs: [Arc::new(update_txs.clone())].into(), txouts: update_ops.clone().into(), anchors: [(conf_anchor, update_txs.txid()), (unconf_anchor, h!("tx2"))].into(), last_seen: [(h!("tx2"), 1000000)].into() @@ -194,7 +195,7 @@ fn insert_txouts() { assert_eq!( graph.initial_changeset(), ChangeSet { - txs: [update_txs.clone()].into(), + txs: [Arc::new(update_txs.clone())].into(), txouts: update_ops.into_iter().chain(original_ops).collect(), anchors: [(conf_anchor, update_txs.txid()), (unconf_anchor, h!("tx2"))].into(), last_seen: [(h!("tx2"), 1000000)].into() @@ -276,7 +277,10 @@ fn insert_tx_can_retrieve_full_tx_from_graph() { let mut graph = TxGraph::<()>::default(); let _ = graph.insert_tx(tx.clone()); - assert_eq!(graph.get_tx(tx.txid()), Some(&tx)); + assert_eq!( + graph.get_tx(tx.txid()).map(|tx| tx.as_ref().clone()), + Some(tx) + ); } #[test] @@ -643,7 +647,7 @@ fn test_walk_ancestors() { ..common::new_tx(0) }; - let mut graph = TxGraph::::new(vec![ + let mut graph = TxGraph::::new([ tx_a0.clone(), tx_b0.clone(), tx_b1.clone(), @@ -664,17 +668,17 @@ fn test_walk_ancestors() { let ancestors = [ graph - .walk_ancestors(&tx_c0, |depth, tx| Some((depth, tx))) + .walk_ancestors(tx_c0.clone(), |depth, tx| Some((depth, tx))) .collect::>(), graph - .walk_ancestors(&tx_d0, |depth, tx| Some((depth, tx))) + .walk_ancestors(tx_d0.clone(), |depth, tx| Some((depth, tx))) .collect::>(), graph - .walk_ancestors(&tx_e0, |depth, tx| Some((depth, tx))) + .walk_ancestors(tx_e0.clone(), |depth, tx| Some((depth, tx))) .collect::>(), // Only traverse unconfirmed ancestors of tx_e0 this time graph - .walk_ancestors(&tx_e0, |depth, tx| { + .walk_ancestors(tx_e0.clone(), |depth, tx| { let tx_node = graph.get_tx_node(tx.txid())?; for block in tx_node.anchors { match local_chain.is_block_in_chain(block.anchor_block(), tip.block_id()) { @@ -701,8 +705,14 @@ fn test_walk_ancestors() { vec![(1, &tx_d1), (2, &tx_c2), (2, &tx_c3), (3, &tx_b2)], ]; - for (txids, expected_txids) in ancestors.iter().zip(expected_ancestors.iter()) { - assert_eq!(txids, expected_txids); + for (txids, expected_txids) in ancestors.into_iter().zip(expected_ancestors) { + assert_eq!( + txids, + expected_txids + .into_iter() + .map(|(i, tx)| (i, Arc::new(tx.clone()))) + .collect::>() + ); } } diff --git a/crates/esplora/tests/async_ext.rs b/crates/esplora/tests/async_ext.rs index e08d98a3c..c71c214e9 100644 --- a/crates/esplora/tests/async_ext.rs +++ b/crates/esplora/tests/async_ext.rs @@ -66,7 +66,7 @@ pub async fn test_update_tx_graph_without_keychain() -> anyhow::Result<()> { for tx in graph_update.full_txs() { // Retrieve the calculated fee from `TxGraph`, which will panic if we do not have the // floating txouts available from the transactions' previous outputs. - let fee = graph_update.calculate_fee(tx.tx).expect("Fee must exist"); + let fee = graph_update.calculate_fee(&tx.tx).expect("Fee must exist"); // Retrieve the fee in the transaction data from `bitcoind`. let tx_fee = env diff --git a/crates/esplora/tests/blocking_ext.rs b/crates/esplora/tests/blocking_ext.rs index d3795ed36..9e39a93c9 100644 --- a/crates/esplora/tests/blocking_ext.rs +++ b/crates/esplora/tests/blocking_ext.rs @@ -80,7 +80,7 @@ pub fn test_update_tx_graph_without_keychain() -> anyhow::Result<()> { for tx in graph_update.full_txs() { // Retrieve the calculated fee from `TxGraph`, which will panic if we do not have the // floating txouts available from the transactions' previous outputs. - let fee = graph_update.calculate_fee(tx.tx).expect("Fee must exist"); + let fee = graph_update.calculate_fee(&tx.tx).expect("Fee must exist"); // Retrieve the fee in the transaction data from `bitcoind`. let tx_fee = env