diff --git a/crates/bdk/src/wallet/mod.rs b/crates/bdk/src/wallet/mod.rs index 6e4adf74..84040d5f 100644 --- a/crates/bdk/src/wallet/mod.rs +++ b/crates/bdk/src/wallet/mod.rs @@ -524,7 +524,7 @@ impl Wallet { /// unconfirmed transactions last. pub fn transactions( &self, - ) -> impl DoubleEndedIterator)> + '_ + ) -> impl DoubleEndedIterator)> + '_ { self.keychain_tracker .chain_graph() diff --git a/crates/chain/src/chain_graph.rs b/crates/chain/src/chain_graph.rs index 3c841965..85a4c956 100644 --- a/crates/chain/src/chain_graph.rs +++ b/crates/chain/src/chain_graph.rs @@ -27,7 +27,7 @@ use core::fmt::Debug; #[derive(Clone, Debug, PartialEq)] pub struct ChainGraph

{ chain: SparseChain

, - graph: TxGraph, + graph: TxGraph, } impl

Default for ChainGraph

{ @@ -45,8 +45,8 @@ impl

AsRef> for ChainGraph

{ } } -impl

AsRef> for ChainGraph

{ - fn as_ref(&self) -> &TxGraph { +impl

AsRef for ChainGraph

{ + fn as_ref(&self) -> &TxGraph { &self.graph } } @@ -64,7 +64,7 @@ impl

ChainGraph

{ } /// Returns a reference to the internal [`TxGraph`]. - pub fn graph(&self) -> &TxGraph { + pub fn graph(&self) -> &TxGraph { &self.graph } } @@ -81,7 +81,7 @@ where /// transaction in `graph`. /// 2. The `chain` has two transactions that are allegedly in it, but they conflict in the `graph` /// (so could not possibly be in the same chain). - pub fn new(chain: SparseChain

, graph: TxGraph) -> Result> { + 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) { @@ -212,7 +212,7 @@ 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, BlockId>)> { + pub fn get_tx_in_chain(&self, txid: Txid) -> Option<(&P, TxInGraph<'_, Transaction, ()>)> { let position = self.chain.tx_position(txid)?; let graphed_tx = self.graph.get_tx(txid).expect("must exist"); Some((position, graphed_tx)) @@ -430,7 +430,7 @@ where /// in ascending order. pub fn transactions_in_chain( &self, - ) -> impl DoubleEndedIterator)> { + ) -> impl DoubleEndedIterator)> { self.chain .txids() .map(move |(pos, txid)| (pos, self.graph.get_tx(*txid).expect("must exist"))) @@ -469,7 +469,7 @@ where #[must_use] pub struct ChangeSet

{ pub chain: sparse_chain::ChangeSet

, - pub graph: tx_graph::Additions, + pub graph: tx_graph::Additions<()>, } impl

ChangeSet

{ diff --git a/crates/chain/src/tx_graph.rs b/crates/chain/src/tx_graph.rs index ddeb5e13..2236822b 100644 --- a/crates/chain/src/tx_graph.rs +++ b/crates/chain/src/tx_graph.rs @@ -55,7 +55,7 @@ //! assert!(additions.is_empty()); //! ``` -use crate::{collections::*, BlockAnchor, BlockId, ChainOracle, ForEachTxOut, ObservedIn}; +use crate::{collections::*, BlockAnchor, ChainOracle, ForEachTxOut, ObservedIn}; use alloc::vec::Vec; use bitcoin::{OutPoint, Transaction, TxOut, Txid}; use core::{ @@ -69,7 +69,7 @@ use core::{ /// /// [module-level documentation]: crate::tx_graph #[derive(Clone, Debug, PartialEq)] -pub struct TxGraph { +pub struct TxGraph { // all transactions that the graph is aware of in format: `(tx_node, tx_anchors, tx_last_seen)` txs: HashMap, u64)>, spends: BTreeMap>, @@ -244,9 +244,111 @@ impl TxGraph { Some(inputs_sum - outputs_sum) } + + /// The transactions spending from this output. + /// + /// `TxGraph` allows conflicting transactions within the graph. Obviously the transactions in + /// the returned set will never be in the same active-chain. + pub fn outspends(&self, outpoint: OutPoint) -> &HashSet { + self.spends.get(&outpoint).unwrap_or(&self.empty_outspends) + } + + /// Iterates over the transactions spending from `txid`. + /// + /// The iterator item is a union of `(vout, txid-set)` where: + /// + /// - `vout` is the provided `txid`'s outpoint that is being spent + /// - `txid-set` is the set of txids spending the `vout`. + pub fn tx_outspends( + &self, + txid: Txid, + ) -> impl DoubleEndedIterator)> + '_ { + let start = OutPoint { txid, vout: 0 }; + let end = OutPoint { + txid, + vout: u32::MAX, + }; + self.spends + .range(start..=end) + .map(|(outpoint, spends)| (outpoint.vout, spends)) + } + + /// Iterate over all partial transactions (outputs only) in the graph. + pub fn partial_transactions( + &self, + ) -> impl Iterator, A>> { + self.txs + .iter() + .filter_map(|(&txid, (tx, anchors, last_seen))| match tx { + TxNode::Whole(_) => None, + TxNode::Partial(partial) => Some(TxInGraph { + txid, + tx: partial, + anchors, + last_seen: *last_seen, + }), + }) + } + + /// Creates an iterator that filters and maps descendants from the starting `txid`. + /// + /// The supplied closure takes in two inputs `(depth, descendant_txid)`: + /// + /// * `depth` is the distance between the starting `txid` and the `descendant_txid`. I.e., if the + /// descendant is spending an output of the starting `txid`; the `depth` will be 1. + /// * `descendant_txid` is the descendant's txid which we are considering to walk. + /// + /// The supplied closure returns an `Option`, allowing the caller to map each node it vists + /// and decide whether to visit descendants. + pub fn walk_descendants<'g, F, O>(&'g self, txid: Txid, walk_map: F) -> TxDescendants + where + F: FnMut(usize, Txid) -> Option + 'g, + { + TxDescendants::new_exclude_root(self, txid, walk_map) + } + + /// Creates an iterator that both filters and maps conflicting transactions (this includes + /// descendants of directly-conflicting transactions, which are also considered conflicts). + /// + /// Refer to [`Self::walk_descendants`] for `walk_map` usage. + pub fn walk_conflicts<'g, F, O>( + &'g self, + tx: &'g Transaction, + walk_map: F, + ) -> TxDescendants + where + F: FnMut(usize, Txid) -> Option + 'g, + { + let txids = self.direct_conflicts_of_tx(tx).map(|(_, txid)| txid); + TxDescendants::from_multiple_include_root(self, txids, walk_map) + } + + /// Given a transaction, return an iterator of txids that directly conflict with the given + /// transaction's inputs (spends). The conflicting txids are returned with the given + /// transaction's vin (in which it conflicts). + /// + /// Note that this only returns directly conflicting txids and does not include descendants of + /// those txids (which are technically also conflicting). + pub fn direct_conflicts_of_tx<'g>( + &'g self, + tx: &'g Transaction, + ) -> impl Iterator + '_ { + let txid = tx.txid(); + tx.input + .iter() + .enumerate() + .filter_map(move |(vin, txin)| self.spends.get(&txin.previous_output).zip(Some(vin))) + .flat_map(|(spends, vin)| core::iter::repeat(vin).zip(spends.iter().cloned())) + .filter(move |(_, conflicting_txid)| *conflicting_txid != txid) + } + + /// Whether the graph has any transactions or outputs in it. + pub fn is_empty(&self) -> bool { + self.txs.is_empty() + } } -impl TxGraph { +impl TxGraph { /// Construct a new [`TxGraph`] from a list of transactions. pub fn new(txs: impl IntoIterator) -> Self { let mut new = Self::default(); @@ -256,6 +358,24 @@ impl TxGraph { new } + /// Returns the resultant [`Additions`] if the given `txout` is inserted at `outpoint`. Does not + /// mutate `self`. + /// + /// The [`Additions`] result will be empty if the `outpoint` (or a full transaction containing + /// the `outpoint`) already existed in `self`. + pub fn insert_txout_preview(&self, outpoint: OutPoint, txout: TxOut) -> Additions { + let mut update = Self::default(); + update.txs.insert( + outpoint.txid, + ( + TxNode::Partial([(outpoint.vout, txout)].into()), + BTreeSet::new(), + 0, + ), + ); + self.determine_additions(&update) + } + /// Inserts the given [`TxOut`] at [`OutPoint`]. /// /// Note this will ignore the action if we already have the full transaction that the txout is @@ -266,6 +386,18 @@ impl TxGraph { additions } + /// Returns the resultant [`Additions`] if the given transaction is inserted. Does not actually + /// mutate [`Self`]. + /// + /// The [`Additions`] result will be empty if `tx` already exists in `self`. + pub fn insert_tx_preview(&self, tx: Transaction) -> Additions { + let mut update = Self::default(); + update + .txs + .insert(tx.txid(), (TxNode::Whole(tx), BTreeSet::new(), 0)); + self.determine_additions(&update) + } + /// Inserts the given transaction into [`TxGraph`]. /// /// The [`Additions`] returned will be empty if `tx` already exists. @@ -275,6 +407,13 @@ impl TxGraph { additions } + /// Returns the resultant [`Additions`] if the `txid` is set in `anchor`. + pub fn insert_anchor_preview(&self, txid: Txid, anchor: A) -> Additions { + let mut update = Self::default(); + update.anchors.insert((anchor, txid)); + self.determine_additions(&update) + } + /// Inserts the given `anchor` into [`TxGraph`]. /// /// This is equivalent to calling [`insert_anchor_preview`] and [`apply_additions`] in sequence. @@ -289,6 +428,16 @@ impl TxGraph { additions } + /// Returns the resultant [`Additions`] if the `txid` is set to `seen_at`. + /// + /// Note that [`TxGraph`] only keeps track of the lastest `seen_at`. + pub fn insert_seen_at_preview(&self, txid: Txid, seen_at: u64) -> Additions { + let mut update = Self::default(); + let (_, _, update_last_seen) = update.txs.entry(txid).or_default(); + *update_last_seen = seen_at; + self.determine_additions(&update) + } + /// Inserts the given `seen_at` into [`TxGraph`]. /// /// This is equivalent to calling [`insert_seen_at_preview`] and [`apply_additions`] in @@ -421,54 +570,9 @@ impl TxGraph { additions } +} - /// Returns the resultant [`Additions`] if the given transaction is inserted. Does not actually - /// mutate [`Self`]. - /// - /// The [`Additions`] result will be empty if `tx` already exists in `self`. - pub fn insert_tx_preview(&self, tx: Transaction) -> Additions { - let mut update = Self::default(); - update - .txs - .insert(tx.txid(), (TxNode::Whole(tx), BTreeSet::new(), 0)); - self.determine_additions(&update) - } - - /// Returns the resultant [`Additions`] if the given `txout` is inserted at `outpoint`. Does not - /// mutate `self`. - /// - /// The [`Additions`] result will be empty if the `outpoint` (or a full transaction containing - /// the `outpoint`) already existed in `self`. - pub fn insert_txout_preview(&self, outpoint: OutPoint, txout: TxOut) -> Additions { - let mut update = Self::default(); - update.txs.insert( - outpoint.txid, - ( - TxNode::Partial([(outpoint.vout, txout)].into()), - BTreeSet::new(), - 0, - ), - ); - self.determine_additions(&update) - } - - /// Returns the resultant [`Additions`] if the `txid` is set in `anchor`. - pub fn insert_anchor_preview(&self, txid: Txid, anchor: A) -> Additions { - let mut update = Self::default(); - update.anchors.insert((anchor, txid)); - self.determine_additions(&update) - } - - /// Returns the resultant [`Additions`] if the `txid` is set to `seen_at`. - /// - /// Note that [`TxGraph`] only keeps track of the lastest `seen_at`. - pub fn insert_seen_at_preview(&self, txid: Txid, seen_at: u64) -> Additions { - let mut update = Self::default(); - let (_, _, update_last_seen) = update.txs.entry(txid).or_default(); - *update_last_seen = seen_at; - self.determine_additions(&update) - } - +impl TxGraph { /// Get all heights that are relevant to the graph. pub fn relevant_heights(&self) -> BTreeSet { self.anchors @@ -573,110 +677,6 @@ impl TxGraph { } } -impl TxGraph { - /// The transactions spending from this output. - /// - /// `TxGraph` allows conflicting transactions within the graph. Obviously the transactions in - /// the returned set will never be in the same active-chain. - pub fn outspends(&self, outpoint: OutPoint) -> &HashSet { - self.spends.get(&outpoint).unwrap_or(&self.empty_outspends) - } - - /// Iterates over the transactions spending from `txid`. - /// - /// The iterator item is a union of `(vout, txid-set)` where: - /// - /// - `vout` is the provided `txid`'s outpoint that is being spent - /// - `txid-set` is the set of txids spending the `vout`. - pub fn tx_outspends( - &self, - txid: Txid, - ) -> impl DoubleEndedIterator)> + '_ { - let start = OutPoint { txid, vout: 0 }; - let end = OutPoint { - txid, - vout: u32::MAX, - }; - self.spends - .range(start..=end) - .map(|(outpoint, spends)| (outpoint.vout, spends)) - } - - /// Iterate over all partial transactions (outputs only) in the graph. - pub fn partial_transactions( - &self, - ) -> impl Iterator, A>> { - self.txs - .iter() - .filter_map(|(&txid, (tx, anchors, last_seen))| match tx { - TxNode::Whole(_) => None, - TxNode::Partial(partial) => Some(TxInGraph { - txid, - tx: partial, - anchors, - last_seen: *last_seen, - }), - }) - } - - /// Creates an iterator that filters and maps descendants from the starting `txid`. - /// - /// The supplied closure takes in two inputs `(depth, descendant_txid)`: - /// - /// * `depth` is the distance between the starting `txid` and the `descendant_txid`. I.e., if the - /// descendant is spending an output of the starting `txid`; the `depth` will be 1. - /// * `descendant_txid` is the descendant's txid which we are considering to walk. - /// - /// The supplied closure returns an `Option`, allowing the caller to map each node it vists - /// and decide whether to visit descendants. - pub fn walk_descendants<'g, F, O>(&'g self, txid: Txid, walk_map: F) -> TxDescendants - where - F: FnMut(usize, Txid) -> Option + 'g, - { - TxDescendants::new_exclude_root(self, txid, walk_map) - } - - /// Creates an iterator that both filters and maps conflicting transactions (this includes - /// descendants of directly-conflicting transactions, which are also considered conflicts). - /// - /// Refer to [`Self::walk_descendants`] for `walk_map` usage. - pub fn walk_conflicts<'g, F, O>( - &'g self, - tx: &'g Transaction, - walk_map: F, - ) -> TxDescendants - where - F: FnMut(usize, Txid) -> Option + 'g, - { - let txids = self.direct_conflicts_of_tx(tx).map(|(_, txid)| txid); - TxDescendants::from_multiple_include_root(self, txids, walk_map) - } - - /// Given a transaction, return an iterator of txids that directly conflict with the given - /// transaction's inputs (spends). The conflicting txids are returned with the given - /// transaction's vin (in which it conflicts). - /// - /// Note that this only returns directly conflicting txids and does not include descendants of - /// those txids (which are technically also conflicting). - pub fn direct_conflicts_of_tx<'g>( - &'g self, - tx: &'g Transaction, - ) -> impl Iterator + '_ { - let txid = tx.txid(); - tx.input - .iter() - .enumerate() - .filter_map(move |(vin, txin)| self.spends.get(&txin.previous_output).zip(Some(vin))) - .flat_map(|(spends, vin)| core::iter::repeat(vin).zip(spends.iter().cloned())) - .filter(move |(_, conflicting_txid)| *conflicting_txid != txid) - } - - /// Whether the graph has any transactions or outputs in it. - pub fn is_empty(&self) -> bool { - self.txs.is_empty() - } -} - /// A structure that represents changes to a [`TxGraph`]. /// /// It is named "additions" because [`TxGraph`] is monotone, so transactions can only be added and @@ -698,7 +698,7 @@ impl TxGraph { ) )] #[must_use] -pub struct Additions { +pub struct Additions { pub tx: BTreeSet, pub txout: BTreeMap, pub anchors: BTreeSet<(A, Txid)>,