]> Untitled Git - bdk/commitdiff
feat(chain)!: wrap `TxGraph` txs with `Arc`
author志宇 <hello@evanlinjin.me>
Thu, 14 Mar 2024 06:20:51 +0000 (14:20 +0800)
committer志宇 <hello@evanlinjin.me>
Mon, 25 Mar 2024 04:57:26 +0000 (12:57 +0800)
Wrapping transactions as `Arc<Transaction>` allows us to share
transactions cheaply between the chain-source and receiving structures.
Therefore the chain-source can keep already-fetched transactions (save
bandwidth) and have a shared pointer to the transactions (save memory).

This is better than the current way we do things, which is to refer back
to the receiving structures mid-sync.

Documentation for `TxGraph` is also updated.

crates/bdk/src/wallet/mod.rs
crates/bdk/tests/wallet.rs
crates/chain/Cargo.toml
crates/chain/src/tx_graph.rs
crates/chain/tests/test_indexed_tx_graph.rs
crates/chain/tests/test_tx_graph.rs
crates/esplora/tests/async_ext.rs
crates/esplora/tests/blocking_ext.rs

index bd33a9047a961861770bfabeb6a85abc49ae0aac..4eb686eb4973829c9b8a8162c06507ff0d7ea7e0 100644 (file)
@@ -942,7 +942,7 @@ impl<D> Wallet<D> {
     /// # 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<D> Wallet<D> {
     /// # 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<D> Wallet<D> {
     /// # 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<FeeRate, CalculateFeeError> {
@@ -1003,8 +1003,8 @@ impl<D> Wallet<D> {
     /// # 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<D> Wallet<D> {
     pub fn get_tx(
         &self,
         txid: Txid,
-    ) -> Option<CanonicalTx<'_, Transaction, ConfirmationTimeHeightAnchor>> {
+    ) -> Option<CanonicalTx<'_, Arc<Transaction>, ConfirmationTimeHeightAnchor>> {
         let graph = self.indexed_graph.graph();
 
         Some(CanonicalTx {
@@ -1167,7 +1167,8 @@ impl<D> Wallet<D> {
     /// Iterate over the transactions in the wallet.
     pub fn transactions(
         &self,
-    ) -> impl Iterator<Item = CanonicalTx<'_, Transaction, ConfirmationTimeHeightAnchor>> + '_ {
+    ) -> impl Iterator<Item = CanonicalTx<'_, Arc<Transaction>, ConfirmationTimeHeightAnchor>> + '_
+    {
         self.indexed_graph
             .graph()
             .list_chain_txs(&self.chain, self.chain.tip().block_id())
@@ -1670,6 +1671,7 @@ impl<D> Wallet<D> {
         let mut tx = graph
             .get_tx(txid)
             .ok_or(BuildFeeBumpError::TransactionNotFound(txid))?
+            .as_ref()
             .clone();
 
         let pos = graph
@@ -1739,7 +1741,7 @@ impl<D> Wallet<D> {
                                 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<D> Wallet<D> {
                 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)
index e367b0bb5fc4f1fcabe36e9e508243a94f5af9d9..b31b44bb220e3fd5fcedfb638d37711a30977bb4 100644 (file)
@@ -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"
index 0a77708a1f4d339e67f2f3bb99c33bccb4ec1380..6c5a59915d9a32ceea5f288486a1cae11c63432e 100644 (file)
@@ -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"] }
index 34cbccf5ce5e1c4022923b9e52d36c317ae13e2a..30d020ecb3f4e65f4a6182bcbb54b3cf085c5b7b 100644 (file)
@@ -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`].
 //!
 //! # 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<A>`. 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<A>,
     /// 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<Transaction>),
     Partial(BTreeMap<u32, TxOut>),
 }
 
@@ -198,6 +212,7 @@ impl<A> TxGraph<A> {
     pub fn all_txouts(&self) -> impl Iterator<Item = (OutPoint, &TxOut)> {
         self.txs.iter().flat_map(|(txid, (tx, _, _))| match tx {
             TxNodeInternal::Whole(tx) => tx
+                .as_ref()
                 .output
                 .iter()
                 .enumerate()
@@ -229,13 +244,13 @@ impl<A> TxGraph<A> {
     }
 
     /// Iterate over all full transactions in the graph.
-    pub fn full_txs(&self) -> impl Iterator<Item = TxNode<'_, Transaction, A>> {
+    pub fn full_txs(&self) -> impl Iterator<Item = TxNode<'_, Arc<Transaction>, 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<A> TxGraph<A> {
     /// 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<Arc<Transaction>> {
         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<TxNode<'_, Transaction, A>> {
+    pub fn get_tx_node(&self, txid: Txid) -> Option<TxNode<'_, Arc<Transaction>, 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<A> TxGraph<A> {
     /// 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<A> TxGraph<A> {
     pub fn tx_outputs(&self, txid: Txid) -> Option<BTreeMap<u32, &TxOut>> {
         Some(match &self.txs.get(&txid)?.0 {
             TxNodeInternal::Whole(tx) => tx
+                .as_ref()
                 .output
                 .iter()
                 .enumerate()
@@ -356,16 +372,15 @@ impl<A> TxGraph<A> {
         &self,
         txid: Txid,
     ) -> impl DoubleEndedIterator<Item = (u32, &HashSet<Txid>)> + '_ {
-        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<A: Clone + Ord> TxGraph<A> {
     /// 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<A> TxGraph<A> {
     ///
     /// The supplied closure returns an `Option<T>`, 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<O> + 'g,
+        T: Into<Arc<Transaction>>,
+        F: FnMut(usize, Arc<Transaction>) -> Option<O> + 'g,
     {
         TxAncestors::new_exclude_root(self, tx, walk_map)
     }
@@ -406,7 +418,9 @@ impl<A> TxGraph<A> {
     {
         TxDescendants::new_exclude_root(self, txid, walk_map)
     }
+}
 
+impl<A> TxGraph<A> {
     /// 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<A> TxGraph<A> {
     where
         F: FnMut(usize, Txid) -> Option<O> + '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<A> TxGraph<A> {
     /// 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<Item = (usize, Txid)> + '_ {
@@ -467,9 +481,7 @@ impl<A: Clone + Ord> TxGraph<A> {
         new_graph.apply_changeset(self.initial_changeset().map_anchors(f));
         new_graph
     }
-}
 
-impl<A: Clone + Ord> TxGraph<A> {
     /// Construct a new [`TxGraph`] from a list of transactions.
     pub fn new(txs: impl IntoIterator<Item = Transaction>) -> Self {
         let mut new = Self::default();
@@ -506,9 +518,10 @@ impl<A: Clone + Ord> TxGraph<A> {
     /// The [`ChangeSet`] returned will be empty if `tx` already exists.
     pub fn insert_tx(&mut self, tx: Transaction) -> ChangeSet<A> {
         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<A: Clone + Ord> TxGraph<A> {
 
     /// Applies [`ChangeSet`] to [`TxGraph`].
     pub fn apply_changeset(&mut self, changeset: ChangeSet<A>) {
-        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<A: Clone + Ord> TxGraph<A> {
 
             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<A: Clone + Ord> TxGraph<A> {
     /// 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<A>) -> ChangeSet<A> {
-        let mut changeset = ChangeSet::default();
+        let mut changeset = ChangeSet::<A>::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<A: Anchor> TxGraph<A> {
             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<A: Anchor> TxGraph<A> {
         // 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<Transaction>| {
+                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<A: Anchor> TxGraph<A> {
 
         // 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<A: Anchor> TxGraph<A> {
                     }
                 }
                 Some(Ok(tx_node))
-            })
-            .collect::<Result<Vec<_>, C::Error>>()?;
+            },
+        )
+        .collect::<Result<Vec<_>, C::Error>>()?;
 
         let tx_last_seen = unconfirmed_descendants_txs
             .iter()
@@ -853,7 +872,8 @@ impl<A: Anchor> TxGraph<A> {
         // 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<A: Anchor> TxGraph<A> {
                     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<A: Anchor> TxGraph<A> {
         &'a self,
         chain: &'a C,
         chain_tip: BlockId,
-    ) -> impl Iterator<Item = Result<CanonicalTx<'a, Transaction, A>, C::Error>> {
+    ) -> impl Iterator<Item = Result<CanonicalTx<'a, Arc<Transaction>, 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<A: Anchor> TxGraph<A> {
         &'a self,
         chain: &'a C,
         chain_tip: BlockId,
-    ) -> impl Iterator<Item = CanonicalTx<'a, Transaction, A>> {
+    ) -> impl Iterator<Item = CanonicalTx<'a, Arc<Transaction>, A>> {
         self.try_list_chain_txs(chain, chain_tip)
             .map(|r| r.expect("oracle is infallible"))
     }
@@ -1021,7 +1041,7 @@ impl<A: Anchor> TxGraph<A> {
                         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<A: Anchor> TxGraph<A> {
                             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<A: Anchor> TxGraph<A> {
 #[must_use]
 pub struct ChangeSet<A = ()> {
     /// Added transactions.
-    pub txs: BTreeSet<Transaction>,
+    pub txs: BTreeSet<Arc<Transaction>>,
     /// Added txouts.
     pub txouts: BTreeMap<OutPoint, TxOut>,
     /// Added anchors.
@@ -1345,7 +1365,7 @@ impl<A> AsRef<TxGraph<A>> for TxGraph<A> {
 pub struct TxAncestors<'g, A, F> {
     graph: &'g TxGraph<A>,
     visited: HashSet<Txid>,
-    queue: VecDeque<(usize, &'g Transaction)>,
+    queue: VecDeque<(usize, Arc<Transaction>)>,
     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<A>,
-        tx: &'g Transaction,
+        tx: impl Into<Arc<Transaction>>,
         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<A>,
-        tx: &'g Transaction,
+        tx: impl Into<Arc<Transaction>>,
         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<Item = &'g Transaction>,
+        I: IntoIterator,
+        I::Item: Into<Arc<Transaction>>,
     {
         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<Item = &'g Transaction>,
+        I: IntoIterator,
+        I::Item: Into<Arc<Transaction>>,
     {
         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<Transaction>) {
         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<O>,
+    F: FnMut(usize, Arc<Transaction>) -> Option<O>,
 {
     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,
             };
index 41b1d4d3efebfe961467da9c1d480dd7b2df359b..3fcaf2d192d16b262db7f48df701c313b5b59992 100644 (file)
@@ -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())
index 37e8c71925b4b36e4b2ffa11cbda132f0f9ac1f3..8b46744856287847c36caed4438a2e58742964eb 100644 (file)
@@ -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::<BlockId>::new(vec![
+    let mut graph = TxGraph::<BlockId>::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::<Vec<_>>(),
         graph
-            .walk_ancestors(&tx_d0, |depth, tx| Some((depth, tx)))
+            .walk_ancestors(tx_d0.clone(), |depth, tx| Some((depth, tx)))
             .collect::<Vec<_>>(),
         graph
-            .walk_ancestors(&tx_e0, |depth, tx| Some((depth, tx)))
+            .walk_ancestors(tx_e0.clone(), |depth, tx| Some((depth, tx)))
             .collect::<Vec<_>>(),
         // 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::<Vec<_>>()
+        );
     }
 }
 
index 6c3c9cf1f993dde6b90f206cb7b0bbc7cd890042..49bfea45c79729b0939b1dfd66797d225e961fe3 100644 (file)
@@ -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
index 6225a6a6ba73d56dcc51da2491b98f864c2d6258..d63705dcbcb291cf268749eef9155ec1c3b6630d 100644 (file)
@@ -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