//! 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;
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};
/// 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.
type Target = T;
fn deref(&self) -> &Self::Target {
- self.tx
+ &self.tx
}
}
/// outputs).
#[derive(Clone, Debug, PartialEq)]
enum TxNodeInternal {
- Whole(Transaction),
+ Whole(Arc<Transaction>),
Partial(BTreeMap<u32, TxOut>),
}
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()
}
/// 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,
}),
/// 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,
}),
/// 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),
}
}
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()
&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`
///
/// 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)
}
{
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).
///
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)
}
/// 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)> + '_ {
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();
/// 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)
}
/// 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
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),
+ );
}
}
}
/// 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) {
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.
// 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 {
// 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)
}
}
Some(Ok(tx_node))
- })
- .collect::<Result<Vec<_>, C::Error>>()?;
+ },
+ )
+ .collect::<Result<Vec<_>, C::Error>>()?;
let tx_last_seen = unconfirmed_descendants_txs
.iter()
// 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
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);
&'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| {
&'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"))
}
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),
};
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(),
},
)))
},
#[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.
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,
}
/// 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,
}
}
/// 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 {
queue: Default::default(),
filter_map,
};
- ancestors.populate_queue(1, tx);
+ ancestors.populate_queue(1, tx.into());
ancestors
}
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,
}
}
filter_map: F,
) -> Self
where
- I: IntoIterator<Item = &'g Transaction>,
+ I: IntoIterator,
+ I::Item: Into<Arc<Transaction>>,
{
let mut ancestors = Self {
graph,
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()
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;
// 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,
};