feat(chain)!: wrap TxGraph txs with Arc

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.
This commit is contained in:
志宇 2024-03-14 14:20:51 +08:00
parent 80e190b3e7
commit 8ab58af093
No known key found for this signature in database
8 changed files with 150 additions and 114 deletions

View File

@ -942,7 +942,7 @@ impl<D> Wallet<D> {
/// # let mut wallet: Wallet<()> = todo!(); /// # let mut wallet: Wallet<()> = todo!();
/// # let txid:Txid = todo!(); /// # let txid:Txid = todo!();
/// let tx = wallet.get_tx(txid).expect("transaction").tx_node.tx; /// 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 /// ```rust, no_run
@ -973,7 +973,7 @@ impl<D> Wallet<D> {
/// # let mut wallet: Wallet<()> = todo!(); /// # let mut wallet: Wallet<()> = todo!();
/// # let txid:Txid = todo!(); /// # let txid:Txid = todo!();
/// let tx = wallet.get_tx(txid).expect("transaction").tx_node.tx; /// 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 /// ```rust, no_run
@ -981,8 +981,8 @@ impl<D> Wallet<D> {
/// # use bdk::Wallet; /// # use bdk::Wallet;
/// # let mut wallet: Wallet<()> = todo!(); /// # let mut wallet: Wallet<()> = todo!();
/// # let mut psbt: PartiallySignedTransaction = todo!(); /// # let mut psbt: PartiallySignedTransaction = todo!();
/// let tx = &psbt.clone().extract_tx(); /// let tx = psbt.clone().extract_tx();
/// let fee_rate = wallet.calculate_fee_rate(tx).expect("fee rate"); /// let fee_rate = wallet.calculate_fee_rate(&tx).expect("fee rate");
/// ``` /// ```
/// [`insert_txout`]: Self::insert_txout /// [`insert_txout`]: Self::insert_txout
pub fn calculate_fee_rate(&self, tx: &Transaction) -> Result<FeeRate, CalculateFeeError> { pub fn calculate_fee_rate(&self, tx: &Transaction) -> Result<FeeRate, CalculateFeeError> {
@ -1003,8 +1003,8 @@ impl<D> Wallet<D> {
/// # use bdk::Wallet; /// # use bdk::Wallet;
/// # let mut wallet: Wallet<()> = todo!(); /// # let mut wallet: Wallet<()> = todo!();
/// # let txid:Txid = todo!(); /// # let txid:Txid = todo!();
/// let tx = wallet.get_tx(txid).expect("transaction").tx_node.tx; /// let tx = wallet.get_tx(txid).expect("tx exists").tx_node.tx;
/// let (sent, received) = wallet.sent_and_received(tx); /// let (sent, received) = wallet.sent_and_received(&tx);
/// ``` /// ```
/// ///
/// ```rust, no_run /// ```rust, no_run
@ -1065,7 +1065,7 @@ impl<D> Wallet<D> {
pub fn get_tx( pub fn get_tx(
&self, &self,
txid: Txid, txid: Txid,
) -> Option<CanonicalTx<'_, Transaction, ConfirmationTimeHeightAnchor>> { ) -> Option<CanonicalTx<'_, Arc<Transaction>, ConfirmationTimeHeightAnchor>> {
let graph = self.indexed_graph.graph(); let graph = self.indexed_graph.graph();
Some(CanonicalTx { Some(CanonicalTx {
@ -1167,7 +1167,8 @@ impl<D> Wallet<D> {
/// Iterate over the transactions in the wallet. /// Iterate over the transactions in the wallet.
pub fn transactions( pub fn transactions(
&self, &self,
) -> impl Iterator<Item = CanonicalTx<'_, Transaction, ConfirmationTimeHeightAnchor>> + '_ { ) -> impl Iterator<Item = CanonicalTx<'_, Arc<Transaction>, ConfirmationTimeHeightAnchor>> + '_
{
self.indexed_graph self.indexed_graph
.graph() .graph()
.list_chain_txs(&self.chain, self.chain.tip().block_id()) .list_chain_txs(&self.chain, self.chain.tip().block_id())
@ -1670,6 +1671,7 @@ impl<D> Wallet<D> {
let mut tx = graph let mut tx = graph
.get_tx(txid) .get_tx(txid)
.ok_or(BuildFeeBumpError::TransactionNotFound(txid))? .ok_or(BuildFeeBumpError::TransactionNotFound(txid))?
.as_ref()
.clone(); .clone();
let pos = graph let pos = graph
@ -1739,7 +1741,7 @@ impl<D> Wallet<D> {
sequence: Some(txin.sequence), sequence: Some(txin.sequence),
psbt_input: Box::new(psbt::Input { psbt_input: Box::new(psbt::Input {
witness_utxo: Some(txout.clone()), witness_utxo: Some(txout.clone()),
non_witness_utxo: Some(prev_tx.clone()), non_witness_utxo: Some(prev_tx.as_ref().clone()),
..Default::default() ..Default::default()
}), }),
}, },
@ -2295,7 +2297,7 @@ impl<D> Wallet<D> {
psbt_input.witness_utxo = Some(prev_tx.output[prev_output.vout as usize].clone()); psbt_input.witness_utxo = Some(prev_tx.output[prev_output.vout as usize].clone());
} }
if !desc.is_taproot() && (!desc.is_witness() || !only_witness_utxo) { 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) Ok(psbt_input)

View File

@ -208,12 +208,12 @@ fn test_get_funded_wallet_sent_and_received() {
let mut tx_amounts: Vec<(Txid, (u64, u64))> = wallet let mut tx_amounts: Vec<(Txid, (u64, u64))> = wallet
.transactions() .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(); .collect();
tx_amounts.sort_by(|a1, a2| a1.0.cmp(&a2.0)); tx_amounts.sort_by(|a1, a2| a1.0.cmp(&a2.0));
let tx = wallet.get_tx(txid).expect("transaction").tx_node.tx; 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 // 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 // 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 (wallet, txid) = get_funded_wallet(get_test_wpkh());
let tx = wallet.get_tx(txid).expect("transaction").tx_node.tx; 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 // 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 // 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 (wallet, txid) = get_funded_wallet(get_test_wpkh());
let tx = wallet.get_tx(txid).expect("transaction").tx_node.tx; 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 // 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 // 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( .add_foreign_utxo(
utxo2.outpoint, utxo2.outpoint,
psbt::Input { psbt::Input {
non_witness_utxo: Some(tx1), non_witness_utxo: Some(tx1.as_ref().clone()),
..Default::default() ..Default::default()
}, },
satisfaction_weight satisfaction_weight
@ -1320,7 +1322,7 @@ fn test_add_foreign_utxo_where_outpoint_doesnt_match_psbt_input() {
.add_foreign_utxo( .add_foreign_utxo(
utxo2.outpoint, utxo2.outpoint,
psbt::Input { psbt::Input {
non_witness_utxo: Some(tx2), non_witness_utxo: Some(tx2.as_ref().clone()),
..Default::default() ..Default::default()
}, },
satisfaction_weight satisfaction_weight
@ -1384,7 +1386,7 @@ fn test_add_foreign_utxo_only_witness_utxo() {
let mut builder = builder.clone(); let mut builder = builder.clone();
let tx2 = wallet2.get_tx(txid2).unwrap().tx_node.tx; let tx2 = wallet2.get_tx(txid2).unwrap().tx_node.tx;
let psbt_input = psbt::Input { let psbt_input = psbt::Input {
non_witness_utxo: Some(tx2.clone()), non_witness_utxo: Some(tx2.as_ref().clone()),
..Default::default() ..Default::default()
}; };
builder builder
@ -3050,7 +3052,8 @@ fn test_taproot_sign_using_non_witness_utxo() {
let mut psbt = builder.finish().unwrap(); let mut psbt = builder.finish().unwrap();
psbt.inputs[0].witness_utxo = None; 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!( assert!(
psbt.inputs[0].non_witness_utxo.is_some(), psbt.inputs[0].non_witness_utxo.is_some(),
"Previous tx should be present in the database" "Previous tx should be present in the database"

View File

@ -15,7 +15,7 @@ readme = "README.md"
[dependencies] [dependencies]
# For no-std, remember to enable the bitcoin/no-std feature # For no-std, remember to enable the bitcoin/no-std feature
bitcoin = { version = "0.30.0", default-features = false } 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. # Use hashbrown as a feature flag to have HashSet and HashMap from it.
hashbrown = { version = "0.9.1", optional = true, features = ["serde"] } hashbrown = { version = "0.9.1", optional = true, features = ["serde"] }

View File

@ -1,26 +1,27 @@
//! Module for structures that store and traverse transactions. //! 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`] contains transactions and indexes them so you can easily traverse the graph of
//! `TxGraph` is *monotone* in that you can always insert a transaction -- it doesn't care whether that //! those transactions. `TxGraph` is *monotone* in that you can always insert a transaction -- it
//! transaction is in the current best chain or whether it conflicts with any of the //! does not care whether that transaction is in the current best chain or whether it conflicts with
//! existing transactions or what order you insert the transactions. This means that you can always //! any of the existing transactions or what order you insert the transactions. This means that you
//! combine two [`TxGraph`]s together, without resulting in inconsistencies. //! can always combine two [`TxGraph`]s together, without resulting in inconsistencies. Furthermore,
//! Furthermore, there is currently no way to delete a transaction. //! there is currently no way to delete a transaction.
//! //!
//! Transactions can be either whole or partial (i.e., transactions for which we only //! Transactions can be either whole or partial (i.e., transactions for which we only know some
//! know some outputs, which we usually call "floating outputs"; these are usually inserted //! outputs, which we usually call "floating outputs"; these are usually inserted using the
//! using the [`insert_txout`] method.). //! [`insert_txout`] method.).
//! //!
//! The graph contains transactions in the form of [`TxNode`]s. Each node contains the //! The graph contains transactions in the form of [`TxNode`]s. Each node contains the txid, the
//! txid, the transaction (whole or partial), the blocks it's anchored in (see the [`Anchor`] //! 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 //! documentation for more details), and the timestamp of the last time we saw the transaction as
//! the transaction as unconfirmed. //! unconfirmed.
//! //!
//! Conflicting transactions are allowed to coexist within a [`TxGraph`]. This is useful for //! 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`] //! 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, //! methods only consider transactions that are "canonical" (i.e., in the best chain or in mempool).
//! we decide which transactions are canonical based on anchors `last_seen_unconfirmed`; //! We decide which transactions are canonical based on the transaction's anchors and the
//! see the [`try_get_chain_position`] documentation for more details. //! `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 //! 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`]. //! persistent storage, or to be applied to another [`TxGraph`].
@ -30,10 +31,22 @@
//! //!
//! # Applying changes //! # Applying changes
//! //!
//! Methods that apply changes to [`TxGraph`] will return [`ChangeSet`]. //! Methods that change the state of [`TxGraph`] will return [`ChangeSet`]s.
//! [`ChangeSet`] can be applied back to a [`TxGraph`] or be used to inform persistent storage //! [`ChangeSet`]s can be applied back to a [`TxGraph`] or be used to inform persistent storage
//! of the changes to [`TxGraph`]. //! 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::BlockId;
//! # use bdk_chain::tx_graph::TxGraph; //! # use bdk_chain::tx_graph::TxGraph;
@ -80,6 +93,7 @@ use crate::{
ChainOracle, ChainPosition, FullTxOut, ChainOracle, ChainPosition, FullTxOut,
}; };
use alloc::collections::vec_deque::VecDeque; use alloc::collections::vec_deque::VecDeque;
use alloc::sync::Arc;
use alloc::vec::Vec; use alloc::vec::Vec;
use bitcoin::{OutPoint, Script, Transaction, TxOut, Txid}; use bitcoin::{OutPoint, Script, Transaction, TxOut, Txid};
use core::fmt::{self, Formatter}; use core::fmt::{self, Formatter};
@ -122,7 +136,7 @@ pub struct TxNode<'a, T, A> {
/// Txid of the transaction. /// Txid of the transaction.
pub txid: Txid, pub txid: Txid,
/// A partial or full representation of the transaction. /// A partial or full representation of the transaction.
pub tx: &'a T, pub tx: T,
/// The blocks that the transaction is "anchored" in. /// The blocks that the transaction is "anchored" in.
pub anchors: &'a BTreeSet<A>, pub anchors: &'a BTreeSet<A>,
/// The last-seen unix timestamp of the transaction as unconfirmed. /// 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; type Target = T;
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
self.tx &self.tx
} }
} }
@ -143,7 +157,7 @@ impl<'a, T, A> Deref for TxNode<'a, T, A> {
/// outputs). /// outputs).
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
enum TxNodeInternal { enum TxNodeInternal {
Whole(Transaction), Whole(Arc<Transaction>),
Partial(BTreeMap<u32, TxOut>), Partial(BTreeMap<u32, TxOut>),
} }
@ -198,6 +212,7 @@ impl<A> TxGraph<A> {
pub fn all_txouts(&self) -> impl Iterator<Item = (OutPoint, &TxOut)> { pub fn all_txouts(&self) -> impl Iterator<Item = (OutPoint, &TxOut)> {
self.txs.iter().flat_map(|(txid, (tx, _, _))| match tx { self.txs.iter().flat_map(|(txid, (tx, _, _))| match tx {
TxNodeInternal::Whole(tx) => tx TxNodeInternal::Whole(tx) => tx
.as_ref()
.output .output
.iter() .iter()
.enumerate() .enumerate()
@ -229,13 +244,13 @@ impl<A> TxGraph<A> {
} }
/// Iterate over all full transactions in the graph. /// 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 self.txs
.iter() .iter()
.filter_map(|(&txid, (tx, anchors, last_seen))| match tx { .filter_map(|(&txid, (tx, anchors, last_seen))| match tx {
TxNodeInternal::Whole(tx) => Some(TxNode { TxNodeInternal::Whole(tx) => Some(TxNode {
txid, txid,
tx, tx: tx.clone(),
anchors, anchors,
last_seen_unconfirmed: *last_seen, last_seen_unconfirmed: *last_seen,
}), }),
@ -248,16 +263,16 @@ impl<A> TxGraph<A> {
/// Refer to [`get_txout`] for getting a specific [`TxOut`]. /// Refer to [`get_txout`] for getting a specific [`TxOut`].
/// ///
/// [`get_txout`]: Self::get_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) self.get_tx_node(txid).map(|n| n.tx)
} }
/// Get a transaction node by txid. This only returns `Some` for full transactions. /// 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)? { match &self.txs.get(&txid)? {
(TxNodeInternal::Whole(tx), anchors, last_seen) => Some(TxNode { (TxNodeInternal::Whole(tx), anchors, last_seen) => Some(TxNode {
txid, txid,
tx, tx: tx.clone(),
anchors, anchors,
last_seen_unconfirmed: *last_seen, last_seen_unconfirmed: *last_seen,
}), }),
@ -268,7 +283,7 @@ impl<A> TxGraph<A> {
/// Obtains a single tx output (if any) at the specified outpoint. /// Obtains a single tx output (if any) at the specified outpoint.
pub fn get_txout(&self, outpoint: OutPoint) -> Option<&TxOut> { pub fn get_txout(&self, outpoint: OutPoint) -> Option<&TxOut> {
match &self.txs.get(&outpoint.txid)?.0 { 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), 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>> { pub fn tx_outputs(&self, txid: Txid) -> Option<BTreeMap<u32, &TxOut>> {
Some(match &self.txs.get(&txid)?.0 { Some(match &self.txs.get(&txid)?.0 {
TxNodeInternal::Whole(tx) => tx TxNodeInternal::Whole(tx) => tx
.as_ref()
.output .output
.iter() .iter()
.enumerate() .enumerate()
@ -356,16 +372,15 @@ impl<A> TxGraph<A> {
&self, &self,
txid: Txid, txid: Txid,
) -> impl DoubleEndedIterator<Item = (u32, &HashSet<Txid>)> + '_ { ) -> impl DoubleEndedIterator<Item = (u32, &HashSet<Txid>)> + '_ {
let start = OutPoint { txid, vout: 0 }; let start = OutPoint::new(txid, 0);
let end = OutPoint { let end = OutPoint::new(txid, u32::MAX);
txid,
vout: u32::MAX,
};
self.spends self.spends
.range(start..=end) .range(start..=end)
.map(|(outpoint, spends)| (outpoint.vout, spends)) .map(|(outpoint, spends)| (outpoint.vout, spends))
} }
}
impl<A: Clone + Ord> TxGraph<A> {
/// Creates an iterator that filters and maps ancestor transactions. /// Creates an iterator that filters and maps ancestor transactions.
/// ///
/// The iterator starts with the ancestors of the supplied `tx` (ancestor transactions of `tx` /// 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` /// The supplied closure returns an `Option<T>`, allowing the caller to map each `Transaction`
/// it visits and decide whether to visit ancestors. /// it visits and decide whether to visit ancestors.
pub fn walk_ancestors<'g, F, O>( pub fn walk_ancestors<'g, T, F, O>(&'g self, tx: T, walk_map: F) -> TxAncestors<'g, A, F>
&'g self,
tx: &'g Transaction,
walk_map: F,
) -> TxAncestors<'g, A, F>
where 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) TxAncestors::new_exclude_root(self, tx, walk_map)
} }
@ -406,7 +418,9 @@ impl<A> TxGraph<A> {
{ {
TxDescendants::new_exclude_root(self, txid, walk_map) TxDescendants::new_exclude_root(self, txid, walk_map)
} }
}
impl<A> TxGraph<A> {
/// Creates an iterator that both filters and maps conflicting transactions (this includes /// Creates an iterator that both filters and maps conflicting transactions (this includes
/// descendants of directly-conflicting transactions, which are also considered conflicts). /// descendants of directly-conflicting transactions, which are also considered conflicts).
/// ///
@ -419,7 +433,7 @@ impl<A> TxGraph<A> {
where where
F: FnMut(usize, Txid) -> Option<O> + 'g, 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) 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: /// Note that this only returns directly conflicting txids and won't include:
/// - descendants of conflicting transactions (which are technically also conflicting) /// - descendants of conflicting transactions (which are technically also conflicting)
/// - transactions conflicting with the given transaction's ancestors /// - transactions conflicting with the given transaction's ancestors
pub fn direct_conflitcs<'g>( pub fn direct_conflicts<'g>(
&'g self, &'g self,
tx: &'g Transaction, tx: &'g Transaction,
) -> impl Iterator<Item = (usize, Txid)> + '_ { ) -> 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.apply_changeset(self.initial_changeset().map_anchors(f));
new_graph new_graph
} }
}
impl<A: Clone + Ord> TxGraph<A> {
/// Construct a new [`TxGraph`] from a list of transactions. /// Construct a new [`TxGraph`] from a list of transactions.
pub fn new(txs: impl IntoIterator<Item = Transaction>) -> Self { pub fn new(txs: impl IntoIterator<Item = Transaction>) -> Self {
let mut new = Self::default(); 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. /// The [`ChangeSet`] returned will be empty if `tx` already exists.
pub fn insert_tx(&mut self, tx: Transaction) -> ChangeSet<A> { pub fn insert_tx(&mut self, tx: Transaction) -> ChangeSet<A> {
let mut update = Self::default(); let mut update = Self::default();
update update.txs.insert(
.txs tx.txid(),
.insert(tx.txid(), (TxNodeInternal::Whole(tx), BTreeSet::new(), 0)); (TxNodeInternal::Whole(tx.into()), BTreeSet::new(), 0),
);
self.apply_update(update) self.apply_update(update)
} }
@ -567,7 +580,8 @@ impl<A: Clone + Ord> TxGraph<A> {
/// Applies [`ChangeSet`] to [`TxGraph`]. /// Applies [`ChangeSet`] to [`TxGraph`].
pub fn apply_changeset(&mut self, changeset: ChangeSet<A>) { 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(); let txid = tx.txid();
tx.input tx.input
@ -582,18 +596,20 @@ impl<A: Clone + Ord> TxGraph<A> {
match self.txs.get_mut(&txid) { match self.txs.get_mut(&txid) {
Some((tx_node @ TxNodeInternal::Partial(_), _, _)) => { Some((tx_node @ TxNodeInternal::Partial(_), _, _)) => {
*tx_node = TxNodeInternal::Whole(tx); *tx_node = TxNodeInternal::Whole(wrapped_tx.clone());
} }
Some((TxNodeInternal::Whole(tx), _, _)) => { Some((TxNodeInternal::Whole(tx), _, _)) => {
debug_assert_eq!( debug_assert_eq!(
tx.txid(), tx.as_ref().txid(),
txid, txid,
"tx should produce txid that is same as key" "tx should produce txid that is same as key"
); );
} }
None => { None => {
self.txs self.txs.insert(
.insert(txid, (TxNodeInternal::Whole(tx), BTreeSet::new(), 0)); 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 /// The [`ChangeSet`] would be the set difference between `update` and `self` (transactions that
/// exist in `update` but not in `self`). /// exist in `update` but not in `self`).
pub(crate) fn determine_changeset(&self, update: TxGraph<A>) -> ChangeSet<A> { 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 { for (&txid, (update_tx_node, _, update_last_seen)) in &update.txs {
let prev_last_seen: u64 = match (self.txs.get(&txid), update_tx_node) { 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) => { TxNodeInternal::Whole(tx) => {
// A coinbase tx that is not anchored in the best chain cannot be unconfirmed and // A coinbase tx that is not anchored in the best chain cannot be unconfirmed and
// should always be filtered out. // should always be filtered out.
if tx.is_coin_base() { if tx.as_ref().is_coin_base() {
return Ok(None); return Ok(None);
} }
tx tx.clone()
} }
TxNodeInternal::Partial(_) => { TxNodeInternal::Partial(_) => {
// Partial transactions (outputs only) cannot have conflicts. // 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 // First of all, we retrieve all our ancestors. Since we're using `new_include_root`, the
// resulting array will also include `tx` // resulting array will also include `tx`
let unconfirmed_ancestor_txs = let unconfirmed_ancestor_txs =
TxAncestors::new_include_root(self, tx, |_, ancestor_tx: &Transaction| { TxAncestors::new_include_root(self, tx.clone(), |_, ancestor_tx: Arc<Transaction>| {
let tx_node = self.get_tx_node(ancestor_tx.txid())?; 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 // We're filtering the ancestors to keep only the unconfirmed ones (= no anchors in
// the best chain) // the best chain)
for block in tx_node.anchors { 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, // We determine our tx's last seen, which is the max between our last seen,
// and our unconf descendants' last seen. // and our unconf descendants' last seen.
let unconfirmed_descendants_txs = let unconfirmed_descendants_txs = TxDescendants::new_include_root(
TxDescendants::new_include_root(self, tx.txid(), |_, descendant_txid: Txid| { self,
tx.as_ref().txid(),
|_, descendant_txid: Txid| {
let tx_node = self.get_tx_node(descendant_txid)?; let tx_node = self.get_tx_node(descendant_txid)?;
// We're filtering the ancestors to keep only the unconfirmed ones (= no anchors in // We're filtering the ancestors to keep only the unconfirmed ones (= no anchors in
// the best chain) // the best chain)
@ -841,8 +859,9 @@ impl<A: Anchor> TxGraph<A> {
} }
} }
Some(Ok(tx_node)) Some(Ok(tx_node))
}) },
.collect::<Result<Vec<_>, C::Error>>()?; )
.collect::<Result<Vec<_>, C::Error>>()?;
let tx_last_seen = unconfirmed_descendants_txs let tx_last_seen = unconfirmed_descendants_txs
.iter() .iter()
@ -853,7 +872,8 @@ impl<A: Anchor> TxGraph<A> {
// Now we traverse our ancestors and consider all their conflicts // Now we traverse our ancestors and consider all their conflicts
for tx_node in unconfirmed_ancestor_txs { for tx_node in unconfirmed_ancestor_txs {
// We retrieve all the transactions conflicting with this specific ancestor // 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 // 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 // this tx cannot exist in the best chain
@ -867,7 +887,7 @@ impl<A: Anchor> TxGraph<A> {
return Ok(None); return Ok(None);
} }
if conflicting_tx.last_seen_unconfirmed == *last_seen 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 // Conflicting tx has priority if txid of conflicting tx > txid of original tx
return Ok(None); return Ok(None);
@ -960,7 +980,7 @@ impl<A: Anchor> TxGraph<A> {
&'a self, &'a self,
chain: &'a C, chain: &'a C,
chain_tip: BlockId, 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.full_txs().filter_map(move |tx| {
self.try_get_chain_position(chain, chain_tip, tx.txid) self.try_get_chain_position(chain, chain_tip, tx.txid)
.map(|v| { .map(|v| {
@ -982,7 +1002,7 @@ impl<A: Anchor> TxGraph<A> {
&'a self, &'a self,
chain: &'a C, chain: &'a C,
chain_tip: BlockId, 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) self.try_list_chain_txs(chain, chain_tip)
.map(|r| r.expect("oracle is infallible")) .map(|r| r.expect("oracle is infallible"))
} }
@ -1021,7 +1041,7 @@ impl<A: Anchor> TxGraph<A> {
None => return Ok(None), 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(), Some(txout) => txout.clone(),
None => return Ok(None), None => return Ok(None),
}; };
@ -1043,7 +1063,7 @@ impl<A: Anchor> TxGraph<A> {
txout, txout,
chain_position, chain_position,
spent_by, 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] #[must_use]
pub struct ChangeSet<A = ()> { pub struct ChangeSet<A = ()> {
/// Added transactions. /// Added transactions.
pub txs: BTreeSet<Transaction>, pub txs: BTreeSet<Arc<Transaction>>,
/// Added txouts. /// Added txouts.
pub txouts: BTreeMap<OutPoint, TxOut>, pub txouts: BTreeMap<OutPoint, TxOut>,
/// Added anchors. /// Added anchors.
@ -1345,7 +1365,7 @@ impl<A> AsRef<TxGraph<A>> for TxGraph<A> {
pub struct TxAncestors<'g, A, F> { pub struct TxAncestors<'g, A, F> {
graph: &'g TxGraph<A>, graph: &'g TxGraph<A>,
visited: HashSet<Txid>, visited: HashSet<Txid>,
queue: VecDeque<(usize, &'g Transaction)>, queue: VecDeque<(usize, Arc<Transaction>)>,
filter_map: F, 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. /// Creates a `TxAncestors` that includes the starting `Transaction` when iterating.
pub(crate) fn new_include_root( pub(crate) fn new_include_root(
graph: &'g TxGraph<A>, graph: &'g TxGraph<A>,
tx: &'g Transaction, tx: impl Into<Arc<Transaction>>,
filter_map: F, filter_map: F,
) -> Self { ) -> Self {
Self { Self {
graph, graph,
visited: Default::default(), visited: Default::default(),
queue: [(0, tx)].into(), queue: [(0, tx.into())].into(),
filter_map, filter_map,
} }
} }
@ -1367,7 +1387,7 @@ impl<'g, A, F> TxAncestors<'g, A, F> {
/// Creates a `TxAncestors` that excludes the starting `Transaction` when iterating. /// Creates a `TxAncestors` that excludes the starting `Transaction` when iterating.
pub(crate) fn new_exclude_root( pub(crate) fn new_exclude_root(
graph: &'g TxGraph<A>, graph: &'g TxGraph<A>,
tx: &'g Transaction, tx: impl Into<Arc<Transaction>>,
filter_map: F, filter_map: F,
) -> Self { ) -> Self {
let mut ancestors = Self { let mut ancestors = Self {
@ -1376,7 +1396,7 @@ impl<'g, A, F> TxAncestors<'g, A, F> {
queue: Default::default(), queue: Default::default(),
filter_map, filter_map,
}; };
ancestors.populate_queue(1, tx); ancestors.populate_queue(1, tx.into());
ancestors ancestors
} }
@ -1389,12 +1409,13 @@ impl<'g, A, F> TxAncestors<'g, A, F> {
filter_map: F, filter_map: F,
) -> Self ) -> Self
where where
I: IntoIterator<Item = &'g Transaction>, I: IntoIterator,
I::Item: Into<Arc<Transaction>>,
{ {
Self { Self {
graph, graph,
visited: Default::default(), visited: Default::default(),
queue: txs.into_iter().map(|tx| (0, tx)).collect(), queue: txs.into_iter().map(|tx| (0, tx.into())).collect(),
filter_map, filter_map,
} }
} }
@ -1408,7 +1429,8 @@ impl<'g, A, F> TxAncestors<'g, A, F> {
filter_map: F, filter_map: F,
) -> Self ) -> Self
where where
I: IntoIterator<Item = &'g Transaction>, I: IntoIterator,
I::Item: Into<Arc<Transaction>>,
{ {
let mut ancestors = Self { let mut ancestors = Self {
graph, graph,
@ -1417,12 +1439,12 @@ impl<'g, A, F> TxAncestors<'g, A, F> {
filter_map, filter_map,
}; };
for tx in txs { for tx in txs {
ancestors.populate_queue(1, tx); ancestors.populate_queue(1, tx.into());
} }
ancestors ancestors
} }
fn populate_queue(&mut self, depth: usize, tx: &'g Transaction) { fn populate_queue(&mut self, depth: usize, tx: Arc<Transaction>) {
let ancestors = tx let ancestors = tx
.input .input
.iter() .iter()
@ -1436,7 +1458,7 @@ impl<'g, A, F> TxAncestors<'g, A, F> {
impl<'g, A, F, O> Iterator for TxAncestors<'g, A, F> impl<'g, A, F, O> Iterator for TxAncestors<'g, A, F>
where where
F: FnMut(usize, &'g Transaction) -> Option<O>, F: FnMut(usize, Arc<Transaction>) -> Option<O>,
{ {
type Item = O; type Item = O;
@ -1445,7 +1467,7 @@ where
// we have exhausted all paths when queue is empty // we have exhausted all paths when queue is empty
let (ancestor_depth, tx) = self.queue.pop_front()?; let (ancestor_depth, tx) = self.queue.pop_front()?;
// ignore paths when user filters them out // 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, Some(item) => item,
None => continue, None => continue,
}; };

View File

@ -1,7 +1,7 @@
#[macro_use] #[macro_use]
mod common; mod common;
use std::collections::BTreeSet; use std::{collections::BTreeSet, sync::Arc};
use bdk_chain::{ use bdk_chain::{
indexed_tx_graph::{self, IndexedTxGraph}, indexed_tx_graph::{self, IndexedTxGraph},
@ -66,7 +66,7 @@ fn insert_relevant_txs() {
let changeset = indexed_tx_graph::ChangeSet { let changeset = indexed_tx_graph::ChangeSet {
graph: tx_graph::ChangeSet { graph: tx_graph::ChangeSet {
txs: txs.clone().into(), txs: txs.iter().cloned().map(Arc::new).collect(),
..Default::default() ..Default::default()
}, },
indexer: keychain::ChangeSet([((), 9_u32)].into()), indexer: keychain::ChangeSet([((), 9_u32)].into()),
@ -80,7 +80,6 @@ fn insert_relevant_txs() {
assert_eq!(graph.initial_changeset(), changeset,); assert_eq!(graph.initial_changeset(), changeset,);
} }
#[test]
/// Ensure consistency IndexedTxGraph list_* and balance methods. These methods lists /// Ensure consistency IndexedTxGraph list_* and balance methods. These methods lists
/// relevant txouts and utxos from the information fetched from a ChainOracle (here a LocalChain). /// 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. /// Finally Add more blocks to local chain until tx1 coinbase maturity hits.
/// Assert maturity at coinbase maturity inflection height. Block height 98 and 99. /// Assert maturity at coinbase maturity inflection height. Block height 98 and 99.
#[test]
fn test_list_owned_txouts() { fn test_list_owned_txouts() {
// Create Local chains // Create Local chains
let local_chain = LocalChain::from_blocks((0..150).map(|i| (i as u32, h!("random"))).collect()) let local_chain = LocalChain::from_blocks((0..150).map(|i| (i as u32, h!("random"))).collect())

View File

@ -13,6 +13,7 @@ use bitcoin::{
use common::*; use common::*;
use core::iter; use core::iter;
use rand::RngCore; use rand::RngCore;
use std::sync::Arc;
use std::vec; use std::vec;
#[test] #[test]
@ -119,7 +120,7 @@ fn insert_txouts() {
assert_eq!( assert_eq!(
graph.insert_tx(update_txs.clone()), graph.insert_tx(update_txs.clone()),
ChangeSet { ChangeSet {
txs: [update_txs.clone()].into(), txs: [Arc::new(update_txs.clone())].into(),
..Default::default() ..Default::default()
} }
); );
@ -143,7 +144,7 @@ fn insert_txouts() {
assert_eq!( assert_eq!(
changeset, changeset,
ChangeSet { ChangeSet {
txs: [update_txs.clone()].into(), txs: [Arc::new(update_txs.clone())].into(),
txouts: update_ops.clone().into(), txouts: update_ops.clone().into(),
anchors: [(conf_anchor, update_txs.txid()), (unconf_anchor, h!("tx2"))].into(), anchors: [(conf_anchor, update_txs.txid()), (unconf_anchor, h!("tx2"))].into(),
last_seen: [(h!("tx2"), 1000000)].into() last_seen: [(h!("tx2"), 1000000)].into()
@ -194,7 +195,7 @@ fn insert_txouts() {
assert_eq!( assert_eq!(
graph.initial_changeset(), graph.initial_changeset(),
ChangeSet { ChangeSet {
txs: [update_txs.clone()].into(), txs: [Arc::new(update_txs.clone())].into(),
txouts: update_ops.into_iter().chain(original_ops).collect(), txouts: update_ops.into_iter().chain(original_ops).collect(),
anchors: [(conf_anchor, update_txs.txid()), (unconf_anchor, h!("tx2"))].into(), anchors: [(conf_anchor, update_txs.txid()), (unconf_anchor, h!("tx2"))].into(),
last_seen: [(h!("tx2"), 1000000)].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 mut graph = TxGraph::<()>::default();
let _ = graph.insert_tx(tx.clone()); 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] #[test]
@ -643,7 +647,7 @@ fn test_walk_ancestors() {
..common::new_tx(0) ..common::new_tx(0)
}; };
let mut graph = TxGraph::<BlockId>::new(vec![ let mut graph = TxGraph::<BlockId>::new([
tx_a0.clone(), tx_a0.clone(),
tx_b0.clone(), tx_b0.clone(),
tx_b1.clone(), tx_b1.clone(),
@ -664,17 +668,17 @@ fn test_walk_ancestors() {
let ancestors = [ let ancestors = [
graph graph
.walk_ancestors(&tx_c0, |depth, tx| Some((depth, tx))) .walk_ancestors(tx_c0.clone(), |depth, tx| Some((depth, tx)))
.collect::<Vec<_>>(), .collect::<Vec<_>>(),
graph graph
.walk_ancestors(&tx_d0, |depth, tx| Some((depth, tx))) .walk_ancestors(tx_d0.clone(), |depth, tx| Some((depth, tx)))
.collect::<Vec<_>>(), .collect::<Vec<_>>(),
graph graph
.walk_ancestors(&tx_e0, |depth, tx| Some((depth, tx))) .walk_ancestors(tx_e0.clone(), |depth, tx| Some((depth, tx)))
.collect::<Vec<_>>(), .collect::<Vec<_>>(),
// Only traverse unconfirmed ancestors of tx_e0 this time // Only traverse unconfirmed ancestors of tx_e0 this time
graph graph
.walk_ancestors(&tx_e0, |depth, tx| { .walk_ancestors(tx_e0.clone(), |depth, tx| {
let tx_node = graph.get_tx_node(tx.txid())?; let tx_node = graph.get_tx_node(tx.txid())?;
for block in tx_node.anchors { for block in tx_node.anchors {
match local_chain.is_block_in_chain(block.anchor_block(), tip.block_id()) { 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)], vec![(1, &tx_d1), (2, &tx_c2), (2, &tx_c3), (3, &tx_b2)],
]; ];
for (txids, expected_txids) in ancestors.iter().zip(expected_ancestors.iter()) { for (txids, expected_txids) in ancestors.into_iter().zip(expected_ancestors) {
assert_eq!(txids, expected_txids); assert_eq!(
txids,
expected_txids
.into_iter()
.map(|(i, tx)| (i, Arc::new(tx.clone())))
.collect::<Vec<_>>()
);
} }
} }

View File

@ -66,7 +66,7 @@ pub async fn test_update_tx_graph_without_keychain() -> anyhow::Result<()> {
for tx in graph_update.full_txs() { for tx in graph_update.full_txs() {
// Retrieve the calculated fee from `TxGraph`, which will panic if we do not have the // Retrieve the calculated fee from `TxGraph`, which will panic if we do not have the
// floating txouts available from the transactions' previous outputs. // 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`. // Retrieve the fee in the transaction data from `bitcoind`.
let tx_fee = env let tx_fee = env

View File

@ -80,7 +80,7 @@ pub fn test_update_tx_graph_without_keychain() -> anyhow::Result<()> {
for tx in graph_update.full_txs() { for tx in graph_update.full_txs() {
// Retrieve the calculated fee from `TxGraph`, which will panic if we do not have the // Retrieve the calculated fee from `TxGraph`, which will panic if we do not have the
// floating txouts available from the transactions' previous outputs. // 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`. // Retrieve the fee in the transaction data from `bitcoind`.
let tx_fee = env let tx_fee = env