diff --git a/crates/bdk/src/wallet/mod.rs b/crates/bdk/src/wallet/mod.rs index 84040d5f..67032cd3 100644 --- a/crates/bdk/src/wallet/mod.rs +++ b/crates/bdk/src/wallet/mod.rs @@ -23,9 +23,7 @@ pub use bdk_chain::keychain::Balance; use bdk_chain::{ chain_graph, keychain::{persist, KeychainChangeSet, KeychainScan, KeychainTracker}, - sparse_chain, - tx_graph::TxInGraph, - BlockId, ConfirmationTime, + sparse_chain, BlockId, ConfirmationTime, }; use bitcoin::consensus::encode::serialize; use bitcoin::secp256k1::Secp256k1; @@ -455,11 +453,7 @@ impl Wallet { let fee = inputs.map(|inputs| inputs.saturating_sub(outputs)); Some(TransactionDetails { - transaction: if include_raw { - Some(tx.tx.clone()) - } else { - None - }, + transaction: if include_raw { Some(tx.clone()) } else { None }, txid, received, sent, @@ -524,8 +518,7 @@ impl Wallet { /// unconfirmed transactions last. pub fn transactions( &self, - ) -> impl DoubleEndedIterator)> + '_ - { + ) -> impl DoubleEndedIterator + '_ { self.keychain_tracker .chain_graph() .transactions_in_chain() @@ -1034,7 +1027,7 @@ impl Wallet { Some((ConfirmationTime::Confirmed { .. }, _tx)) => { return Err(Error::TransactionConfirmed) } - Some((_, tx)) => tx.tx.clone(), + Some((_, tx)) => tx.clone(), }; if !tx @@ -1092,7 +1085,7 @@ impl Wallet { outpoint: txin.previous_output, psbt_input: Box::new(psbt::Input { witness_utxo: Some(txout.clone()), - non_witness_utxo: Some(prev_tx.tx.clone()), + non_witness_utxo: Some(prev_tx.clone()), ..Default::default() }), }, @@ -1620,7 +1613,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.tx.clone()); + psbt_input.non_witness_utxo = Some(prev_tx.clone()); } } Ok(psbt_input) diff --git a/crates/chain/src/chain_graph.rs b/crates/chain/src/chain_graph.rs index 85a4c956..8c954f8d 100644 --- a/crates/chain/src/chain_graph.rs +++ b/crates/chain/src/chain_graph.rs @@ -2,7 +2,7 @@ use crate::{ collections::HashSet, sparse_chain::{self, ChainPosition, SparseChain}, - tx_graph::{self, TxGraph, TxInGraph}, + tx_graph::{self, TxGraph}, BlockId, ForEachTxOut, FullTxOut, TxHeight, }; use alloc::{string::ToString, vec::Vec}; @@ -84,11 +84,9 @@ where pub fn new(chain: SparseChain

, graph: TxGraph) -> Result> { let mut missing = HashSet::default(); for (pos, txid) in chain.txids() { - if let Some(graphed_tx) = graph.get_tx(*txid) { + if let Some(tx) = graph.get_tx(*txid) { let conflict = graph - .walk_conflicts(graphed_tx.tx, |_, txid| { - Some((chain.tx_position(txid)?.clone(), txid)) - }) + .walk_conflicts(tx, |_, txid| Some((chain.tx_position(txid)?.clone(), txid))) .next(); if let Some((conflict_pos, conflict)) = conflict { return Err(NewError::Conflict { @@ -145,7 +143,7 @@ where match self.chain.tx_position(*txid) { Some(original_pos) => { if original_pos != pos { - let graphed_tx = self + let tx = self .graph .get_tx(*txid) .expect("tx must exist as it is referenced in sparsechain") @@ -153,7 +151,7 @@ where let _ = inflated_chain .insert_tx(*txid, pos.clone()) .expect("must insert since this was already in update"); - let _ = inflated_graph.insert_tx(graphed_tx.tx.clone()); + let _ = inflated_graph.insert_tx(tx.clone()); } } None => { @@ -212,10 +210,10 @@ where /// /// This does not necessarily mean that it is *confirmed* in the blockchain; it might just be in /// the unconfirmed transaction list within the [`SparseChain`]. - pub fn get_tx_in_chain(&self, txid: Txid) -> Option<(&P, TxInGraph<'_, Transaction, ()>)> { + pub fn get_tx_in_chain(&self, txid: Txid) -> Option<(&P, &Transaction)> { let position = self.chain.tx_position(txid)?; - let graphed_tx = self.graph.get_tx(txid).expect("must exist"); - Some((position, graphed_tx)) + let tx = self.graph.get_tx(txid).expect("must exist"); + Some((position, tx)) } /// Determines the changes required to insert a transaction into the inner [`ChainGraph`] and @@ -348,7 +346,7 @@ where None => continue, }; - let mut full_tx = self.graph.get_tx(txid).map(|tx| tx.tx); + let mut full_tx = self.graph.get_tx(txid); if full_tx.is_none() { full_tx = changeset.graph.tx.iter().find(|tx| tx.txid() == txid) @@ -428,9 +426,7 @@ where /// Iterate over the full transactions and their position in the chain ordered by their position /// in ascending order. - pub fn transactions_in_chain( - &self, - ) -> impl DoubleEndedIterator)> { + pub fn transactions_in_chain(&self) -> impl DoubleEndedIterator { self.chain .txids() .map(move |(pos, txid)| (pos, self.graph.get_tx(*txid).expect("must exist"))) diff --git a/crates/chain/src/indexed_tx_graph.rs b/crates/chain/src/indexed_tx_graph.rs index 21852150..2e0315d8 100644 --- a/crates/chain/src/indexed_tx_graph.rs +++ b/crates/chain/src/indexed_tx_graph.rs @@ -5,7 +5,7 @@ use bitcoin::{OutPoint, Transaction, TxOut}; use crate::{ keychain::Balance, sparse_chain::ChainPosition, - tx_graph::{Additions, TxGraph, TxInGraph}, + tx_graph::{Additions, TxGraph, TxNode}, BlockAnchor, ChainOracle, FullTxOut, ObservedIn, TxIndex, TxIndexAdditions, }; @@ -15,7 +15,7 @@ pub struct TxInChain<'a, T, A> { /// Where the transaction is observed (in a block or in mempool). pub observed_in: ObservedIn<&'a A>, /// The transaction with anchors and last seen timestamp. - pub tx: TxInGraph<'a, T, A>, + pub tx: TxNode<'a, T, A>, } /// An outwards-facing view of a relevant txout that is part of the *best chain*'s history. diff --git a/crates/chain/src/tx_graph.rs b/crates/chain/src/tx_graph.rs index 2236822b..3aca6a6f 100644 --- a/crates/chain/src/tx_graph.rs +++ b/crates/chain/src/tx_graph.rs @@ -71,7 +71,7 @@ use core::{ #[derive(Clone, Debug, PartialEq)] pub struct TxGraph { // all transactions that the graph is aware of in format: `(tx_node, tx_anchors, tx_last_seen)` - txs: HashMap, u64)>, + txs: HashMap, u64)>, spends: BTreeMap>, anchors: BTreeSet<(A, Txid)>, @@ -94,9 +94,9 @@ impl Default for TxGraph { // pub type InChainTx<'a, T, A> = (ObservedIn<&'a A>, TxInGraph<'a, T, A>); // pub type InChainTxOut<'a, I, A> = (&'a I, FullTxOut>); -/// An outward-facing view of a transaction that resides in a [`TxGraph`]. +/// An outward-facing view of a transaction node that resides in a [`TxGraph`]. #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct TxInGraph<'a, T, A> { +pub struct TxNode<'a, T, A> { /// Txid of the transaction. pub txid: Txid, /// A partial or full representation of the transaction. @@ -107,7 +107,7 @@ pub struct TxInGraph<'a, T, A> { pub last_seen: u64, } -impl<'a, T, A> Deref for TxInGraph<'a, T, A> { +impl<'a, T, A> Deref for TxNode<'a, T, A> { type Target = T; fn deref(&self) -> &Self::Target { @@ -115,7 +115,7 @@ impl<'a, T, A> Deref for TxInGraph<'a, T, A> { } } -impl<'a, A> TxInGraph<'a, Transaction, A> { +impl<'a, A> TxNode<'a, Transaction, A> { pub fn from_tx(tx: &'a Transaction, anchors: &'a BTreeSet) -> Self { Self { txid: tx.txid(), @@ -131,12 +131,12 @@ impl<'a, A> TxInGraph<'a, Transaction, A> { /// This can either be a whole transaction, or a partial transaction (where we only have select /// outputs). #[derive(Clone, Debug, PartialEq)] -enum TxNode { +enum TxNodeInternal { Whole(Transaction), Partial(BTreeMap), } -impl Default for TxNode { +impl Default for TxNodeInternal { fn default() -> Self { Self::Partial(BTreeMap::new()) } @@ -146,13 +146,13 @@ impl TxGraph { /// Iterate over all tx outputs known by [`TxGraph`]. pub fn all_txouts(&self) -> impl Iterator { self.txs.iter().flat_map(|(txid, (tx, _, _))| match tx { - TxNode::Whole(tx) => tx + TxNodeInternal::Whole(tx) => tx .output .iter() .enumerate() .map(|(vout, txout)| (OutPoint::new(*txid, vout as _), txout)) .collect::>(), - TxNode::Partial(txouts) => txouts + TxNodeInternal::Partial(txouts) => txouts .iter() .map(|(vout, txout)| (OutPoint::new(*txid, *vout as _), txout)) .collect::>(), @@ -160,17 +160,17 @@ impl TxGraph { } /// Iterate over all full transactions in the graph. - pub fn full_transactions(&self) -> impl Iterator> { + pub fn full_transactions(&self) -> impl Iterator> { self.txs .iter() .filter_map(|(&txid, (tx, anchors, last_seen))| match tx { - TxNode::Whole(tx) => Some(TxInGraph { + TxNodeInternal::Whole(tx) => Some(TxNode { txid, tx, anchors, last_seen: *last_seen, }), - TxNode::Partial(_) => None, + TxNodeInternal::Partial(_) => None, }) } @@ -179,9 +179,14 @@ impl TxGraph { /// Refer to [`get_txout`] for getting a specific [`TxOut`]. /// /// [`get_txout`]: Self::get_txout - pub fn get_tx(&self, txid: Txid) -> Option> { + pub fn get_tx(&self, txid: Txid) -> Option<&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> { match &self.txs.get(&txid)? { - (TxNode::Whole(tx), anchors, last_seen) => Some(TxInGraph { + (TxNodeInternal::Whole(tx), anchors, last_seen) => Some(TxNode { txid, tx, anchors, @@ -194,21 +199,21 @@ 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 { - TxNode::Whole(tx) => tx.output.get(outpoint.vout as usize), - TxNode::Partial(txouts) => txouts.get(&outpoint.vout), + TxNodeInternal::Whole(tx) => tx.output.get(outpoint.vout as usize), + TxNodeInternal::Partial(txouts) => txouts.get(&outpoint.vout), } } /// Returns a [`BTreeMap`] of vout to output of the provided `txid`. pub fn txouts(&self, txid: Txid) -> Option> { Some(match &self.txs.get(&txid)?.0 { - TxNode::Whole(tx) => tx + TxNodeInternal::Whole(tx) => tx .output .iter() .enumerate() .map(|(vout, txout)| (vout as u32, txout)) .collect::>(), - TxNode::Partial(txouts) => txouts + TxNodeInternal::Partial(txouts) => txouts .iter() .map(|(vout, txout)| (*vout, txout)) .collect::>(), @@ -276,12 +281,12 @@ impl TxGraph { /// Iterate over all partial transactions (outputs only) in the graph. pub fn partial_transactions( &self, - ) -> impl Iterator, A>> { + ) -> impl Iterator, A>> { self.txs .iter() .filter_map(|(&txid, (tx, anchors, last_seen))| match tx { - TxNode::Whole(_) => None, - TxNode::Partial(partial) => Some(TxInGraph { + TxNodeInternal::Whole(_) => None, + TxNodeInternal::Partial(partial) => Some(TxNode { txid, tx: partial, anchors, @@ -368,7 +373,7 @@ impl TxGraph { update.txs.insert( outpoint.txid, ( - TxNode::Partial([(outpoint.vout, txout)].into()), + TxNodeInternal::Partial([(outpoint.vout, txout)].into()), BTreeSet::new(), 0, ), @@ -394,7 +399,7 @@ impl TxGraph { let mut update = Self::default(); update .txs - .insert(tx.txid(), (TxNode::Whole(tx), BTreeSet::new(), 0)); + .insert(tx.txid(), (TxNodeInternal::Whole(tx), BTreeSet::new(), 0)); self.determine_additions(&update) } @@ -478,10 +483,10 @@ impl TxGraph { }); match self.txs.get_mut(&txid) { - Some((tx_node @ TxNode::Partial(_), _, _)) => { - *tx_node = TxNode::Whole(tx); + Some((tx_node @ TxNodeInternal::Partial(_), _, _)) => { + *tx_node = TxNodeInternal::Whole(tx); } - Some((TxNode::Whole(tx), _, _)) => { + Some((TxNodeInternal::Whole(tx), _, _)) => { debug_assert_eq!( tx.txid(), txid, @@ -490,7 +495,7 @@ impl TxGraph { } None => { self.txs - .insert(txid, (TxNode::Whole(tx), BTreeSet::new(), 0)); + .insert(txid, (TxNodeInternal::Whole(tx), BTreeSet::new(), 0)); } } } @@ -502,8 +507,9 @@ impl TxGraph { .or_insert_with(Default::default); match tx_entry { - (TxNode::Whole(_), _, _) => { /* do nothing since we already have full tx */ } - (TxNode::Partial(txouts), _, _) => { + (TxNodeInternal::Whole(_), _, _) => { /* do nothing since we already have full tx */ + } + (TxNodeInternal::Partial(txouts), _, _) => { txouts.insert(outpoint.vout, txout); } } @@ -533,11 +539,11 @@ impl TxGraph { for (&txid, (update_tx_node, _, update_last_seen)) in &update.txs { let prev_last_seen: u64 = match (self.txs.get(&txid), update_tx_node) { - (None, TxNode::Whole(update_tx)) => { + (None, TxNodeInternal::Whole(update_tx)) => { additions.tx.insert(update_tx.clone()); 0 } - (None, TxNode::Partial(update_txos)) => { + (None, TxNodeInternal::Partial(update_txos)) => { additions.txout.extend( update_txos .iter() @@ -545,12 +551,18 @@ impl TxGraph { ); 0 } - (Some((TxNode::Whole(_), _, last_seen)), _) => *last_seen, - (Some((TxNode::Partial(_), _, last_seen)), TxNode::Whole(update_tx)) => { + (Some((TxNodeInternal::Whole(_), _, last_seen)), _) => *last_seen, + ( + Some((TxNodeInternal::Partial(_), _, last_seen)), + TxNodeInternal::Whole(update_tx), + ) => { additions.tx.insert(update_tx.clone()); *last_seen } - (Some((TxNode::Partial(txos), _, last_seen)), TxNode::Partial(update_txos)) => { + ( + Some((TxNodeInternal::Partial(txos), _, last_seen)), + TxNodeInternal::Partial(update_txos), + ) => { additions.txout.extend( update_txos .iter() @@ -608,8 +620,8 @@ impl TxGraph { // The tx is not anchored to a block which is in the best chain, let's check whether we can // ignore it by checking conflicts! let tx = match tx_node { - TxNode::Whole(tx) => tx, - TxNode::Partial(_) => { + TxNodeInternal::Whole(tx) => tx, + TxNodeInternal::Partial(_) => { // [TODO] Unfortunately, we can't iterate over conflicts of partial txs right now! // [TODO] So we just assume the partial tx does not exist in the best chain :/ return Ok(None); @@ -618,7 +630,7 @@ impl TxGraph { // [TODO] Is this logic correct? I do not think so, but it should be good enough for now! let mut latest_last_seen = 0_u64; - for conflicting_tx in self.walk_conflicts(tx, |_, txid| self.get_tx(txid)) { + for conflicting_tx in self.walk_conflicts(tx, |_, txid| self.get_tx_node(txid)) { for block_id in conflicting_tx.anchors.iter().map(A::anchor_block) { if chain.is_block_in_best_chain(block_id)? { // conflicting tx is in best chain, so the current tx cannot be in best chain! diff --git a/crates/chain/tests/test_chain_graph.rs b/crates/chain/tests/test_chain_graph.rs index 0514acc9..b5cbf5b9 100644 --- a/crates/chain/tests/test_chain_graph.rs +++ b/crates/chain/tests/test_chain_graph.rs @@ -1,13 +1,11 @@ #[macro_use] mod common; -use std::collections::BTreeSet; - use bdk_chain::{ chain_graph::*, collections::HashSet, sparse_chain, - tx_graph::{self, TxGraph, TxInGraph}, + tx_graph::{self, TxGraph}, BlockId, TxHeight, }; use bitcoin::{OutPoint, PackedLockTime, Script, Sequence, Transaction, TxIn, TxOut, Witness}; @@ -367,15 +365,7 @@ fn test_get_tx_in_chain() { let _ = cg.insert_tx(tx.clone(), TxHeight::Unconfirmed).unwrap(); assert_eq!( cg.get_tx_in_chain(tx.txid()), - Some(( - &TxHeight::Unconfirmed, - TxInGraph { - txid: tx.txid(), - tx: &tx, - anchors: &BTreeSet::new(), - last_seen: 0 - } - )) + Some((&TxHeight::Unconfirmed, &tx,)) ); } @@ -407,18 +397,9 @@ fn test_iterate_transactions() { assert_eq!( cg.transactions_in_chain().collect::>(), vec![ - ( - &TxHeight::Confirmed(0), - TxInGraph::from_tx(&txs[2], &BTreeSet::new()) - ), - ( - &TxHeight::Confirmed(1), - TxInGraph::from_tx(&txs[0], &BTreeSet::new()) - ), - ( - &TxHeight::Unconfirmed, - TxInGraph::from_tx(&txs[1], &BTreeSet::new()) - ), + (&TxHeight::Confirmed(0), &txs[2],), + (&TxHeight::Confirmed(1), &txs[0],), + (&TxHeight::Unconfirmed, &txs[1],), ] ); } diff --git a/crates/chain/tests/test_keychain_tracker.rs b/crates/chain/tests/test_keychain_tracker.rs index c3fee347..bd8c6e03 100644 --- a/crates/chain/tests/test_keychain_tracker.rs +++ b/crates/chain/tests/test_keychain_tracker.rs @@ -1,7 +1,6 @@ #![cfg(feature = "miniscript")] #[macro_use] mod common; -use std::collections::BTreeSet; use bdk_chain::{ keychain::{Balance, KeychainTracker}, @@ -9,7 +8,6 @@ use bdk_chain::{ bitcoin::{secp256k1::Secp256k1, OutPoint, PackedLockTime, Transaction, TxOut}, Descriptor, }, - tx_graph::TxInGraph, BlockId, ConfirmationTime, TxHeight, }; use bitcoin::TxIn; @@ -43,10 +41,7 @@ fn test_insert_tx() { .chain_graph() .transactions_in_chain() .collect::>(), - vec![( - &ConfirmationTime::Unconfirmed, - TxInGraph::from_tx(&tx, &BTreeSet::new()) - )] + vec![(&ConfirmationTime::Unconfirmed, &tx,)] ); assert_eq!( diff --git a/crates/chain/tests/test_tx_graph.rs b/crates/chain/tests/test_tx_graph.rs index 107e106d..279ddb74 100644 --- a/crates/chain/tests/test_tx_graph.rs +++ b/crates/chain/tests/test_tx_graph.rs @@ -2,12 +2,9 @@ mod common; use bdk_chain::{ collections::*, - tx_graph::{Additions, TxGraph, TxInGraph}, - BlockId, -}; -use bitcoin::{ - hashes::Hash, BlockHash, OutPoint, PackedLockTime, Script, Transaction, TxIn, TxOut, Txid, + tx_graph::{Additions, TxGraph}, }; +use bitcoin::{hashes::Hash, OutPoint, PackedLockTime, Script, Transaction, TxIn, TxOut, Txid}; use core::iter; #[test] @@ -38,7 +35,7 @@ fn insert_txouts() { )]; let mut graph = { - let mut graph = TxGraph::<(u32, BlockHash)>::default(); + let mut graph = TxGraph::<()>::default(); for (outpoint, txout) in &original_ops { assert_eq!( graph.insert_txout(*outpoint, txout.clone()), @@ -94,7 +91,7 @@ fn insert_tx_graph_doesnt_count_coinbase_as_spent() { output: vec![], }; - let mut graph = TxGraph::<(u32, BlockHash)>::default(); + let mut graph = TxGraph::<()>::default(); let _ = graph.insert_tx(tx); assert!(graph.outspends(OutPoint::null()).is_empty()); assert!(graph.tx_outspends(Txid::all_zeros()).next().is_none()); @@ -124,8 +121,8 @@ fn insert_tx_graph_keeps_track_of_spend() { output: vec![], }; - let mut graph1 = TxGraph::<(u32, BlockHash)>::default(); - let mut graph2 = TxGraph::<(u32, BlockHash)>::default(); + let mut graph1 = TxGraph::<()>::default(); + let mut graph2 = TxGraph::<()>::default(); // insert in different order let _ = graph1.insert_tx(tx1.clone()); @@ -153,17 +150,14 @@ fn insert_tx_can_retrieve_full_tx_from_graph() { output: vec![TxOut::default()], }; - let mut graph = TxGraph::::default(); + let mut graph = TxGraph::<()>::default(); let _ = graph.insert_tx(tx.clone()); - assert_eq!( - graph.get_tx(tx.txid()), - Some(TxInGraph::from_tx(&tx, &BTreeSet::new())) - ); + assert_eq!(graph.get_tx(tx.txid()), Some(&tx)); } #[test] fn insert_tx_displaces_txouts() { - let mut tx_graph = TxGraph::<(u32, BlockHash)>::default(); + let mut tx_graph = TxGraph::<()>::default(); let tx = Transaction { version: 0x01, lock_time: PackedLockTime(0), @@ -219,7 +213,7 @@ fn insert_tx_displaces_txouts() { #[test] fn insert_txout_does_not_displace_tx() { - let mut tx_graph = TxGraph::<(u32, BlockHash)>::default(); + let mut tx_graph = TxGraph::<()>::default(); let tx = Transaction { version: 0x01, lock_time: PackedLockTime(0), @@ -275,7 +269,7 @@ fn insert_txout_does_not_displace_tx() { #[test] fn test_calculate_fee() { - let mut graph = TxGraph::<(u32, BlockHash)>::default(); + let mut graph = TxGraph::<()>::default(); let intx1 = Transaction { version: 0x01, lock_time: PackedLockTime(0), @@ -369,7 +363,7 @@ fn test_calculate_fee_on_coinbase() { output: vec![TxOut::default()], }; - let graph = TxGraph::<(u32, BlockHash)>::default(); + let graph = TxGraph::<()>::default(); assert_eq!(graph.calculate_fee(&tx), Some(0)); } @@ -411,7 +405,7 @@ fn test_conflicting_descendants() { let txid_a = tx_a.txid(); let txid_b = tx_b.txid(); - let mut graph = TxGraph::<(u32, BlockHash)>::default(); + let mut graph = TxGraph::<()>::default(); let _ = graph.insert_tx(tx_a); let _ = graph.insert_tx(tx_b); @@ -487,7 +481,7 @@ fn test_descendants_no_repeat() { }) .collect::>(); - let mut graph = TxGraph::<(u32, BlockHash)>::default(); + let mut graph = TxGraph::<()>::default(); let mut expected_txids = BTreeSet::new(); // these are NOT descendants of `tx_a`