//! 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
//! 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.).
//!
//! 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 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`].
//!
//! Lastly, you can use [`TxAncestors`]/[`TxDescendants`] to traverse ancestors and descendants of
//! a given transaction, respectively.
//!
//! # Applying changes
//!
//! 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`. 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;
//! # use bdk_chain::example_utils::*;
//! # use bitcoin::Transaction;
//! # let tx_a = tx_from_hex(RAW_TX_1);
//! let mut tx_graph: TxGraph = TxGraph::default();
//!
//! // insert a transaction
//! let changeset = tx_graph.insert_tx(tx_a);
//!
//! // We can restore the state of the `tx_graph` by applying all
//! // the changesets obtained by mutating the original (the order doesn't matter).
//! let mut restored_tx_graph: TxGraph = TxGraph::default();
//! restored_tx_graph.apply_changeset(changeset);
//!
//! assert_eq!(tx_graph, restored_tx_graph);
//! ```
//!
//! A [`TxGraph`] can also be updated with another [`TxGraph`] which merges them together.
//!
//! ```
//! # use bdk_chain::{Append, BlockId};
//! # use bdk_chain::tx_graph::TxGraph;
//! # use bdk_chain::example_utils::*;
//! # use bitcoin::Transaction;
//! # let tx_a = tx_from_hex(RAW_TX_1);
//! # let tx_b = tx_from_hex(RAW_TX_2);
//! let mut graph: TxGraph = TxGraph::default();
//! let update = TxGraph::new(vec![tx_a, tx_b]);
//!
//! // apply the update graph
//! let changeset = graph.apply_update(update.clone());
//!
//! // if we apply it again, the resulting changeset will be empty
//! let changeset = graph.apply_update(update);
//! assert!(changeset.is_empty());
//! ```
//! [`try_get_chain_position`]: TxGraph::try_get_chain_position
//! [`insert_txout`]: TxGraph::insert_txout
use crate::{
collections::*, keychain::Balance, Anchor, Append, BlockId, ChainOracle, ChainPosition,
FullTxOut,
};
use alloc::collections::vec_deque::VecDeque;
use alloc::sync::Arc;
use alloc::vec::Vec;
use bitcoin::{Amount, OutPoint, Script, Transaction, TxOut, Txid};
use core::fmt::{self, Formatter};
use core::{
convert::Infallible,
ops::{Deref, RangeInclusive},
};
/// A graph of transactions and spends.
///
/// See the [module-level documentation] for more.
///
/// [module-level documentation]: crate::tx_graph
#[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)>,
spends: BTreeMap>,
anchors: BTreeSet<(A, Txid)>,
// This atrocity exists so that `TxGraph::outspends()` can return a reference.
// FIXME: This can be removed once `HashSet::new` is a const fn.
empty_outspends: HashSet,
}
impl Default for TxGraph {
fn default() -> Self {
Self {
txs: Default::default(),
spends: Default::default(),
anchors: Default::default(),
empty_outspends: Default::default(),
}
}
}
/// A transaction node in the [`TxGraph`].
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct TxNode<'a, T, A> {
/// Txid of the transaction.
pub txid: Txid,
/// A partial or full representation of the transaction.
pub tx: T,
/// The blocks that the transaction is "anchored" in.
pub anchors: &'a BTreeSet,
/// The last-seen unix timestamp of the transaction as unconfirmed.
pub last_seen_unconfirmed: u64,
}
impl<'a, T, A> Deref for TxNode<'a, T, A> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.tx
}
}
/// Internal representation of a transaction node of a [`TxGraph`].
///
/// This can either be a whole transaction, or a partial transaction (where we only have select
/// outputs).
#[derive(Clone, Debug, PartialEq)]
enum TxNodeInternal {
Whole(Arc),
Partial(BTreeMap),
}
impl Default for TxNodeInternal {
fn default() -> Self {
Self::Partial(BTreeMap::new())
}
}
/// A transaction that is included in the chain, or is still in mempool.
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct CanonicalTx<'a, T, A> {
/// How the transaction is observed as (confirmed or unconfirmed).
pub chain_position: ChainPosition<&'a A>,
/// The transaction node (as part of the graph).
pub tx_node: TxNode<'a, T, A>,
}
/// Errors returned by `TxGraph::calculate_fee`.
#[derive(Debug, PartialEq, Eq)]
pub enum CalculateFeeError {
/// Missing `TxOut` for one or more of the inputs of the tx
MissingTxOut(Vec),
/// When the transaction is invalid according to the graph it has a negative fee
NegativeFee(i64),
}
impl fmt::Display for CalculateFeeError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
CalculateFeeError::MissingTxOut(outpoints) => write!(
f,
"missing `TxOut` for one or more of the inputs of the tx: {:?}",
outpoints
),
CalculateFeeError::NegativeFee(fee) => write!(
f,
"transaction is invalid according to the graph and has negative fee: {}",
fee
),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for CalculateFeeError {}
impl TxGraph {
/// Iterate over all tx outputs known by [`TxGraph`].
///
/// This includes txouts of both full transactions as well as floating transactions.
pub fn all_txouts(&self) -> impl Iterator- {
self.txs.iter().flat_map(|(txid, (tx, _, _))| match tx {
TxNodeInternal::Whole(tx) => tx
.as_ref()
.output
.iter()
.enumerate()
.map(|(vout, txout)| (OutPoint::new(*txid, vout as _), txout))
.collect::>(),
TxNodeInternal::Partial(txouts) => txouts
.iter()
.map(|(vout, txout)| (OutPoint::new(*txid, *vout as _), txout))
.collect::>(),
})
}
/// Iterate over floating txouts known by [`TxGraph`].
///
/// Floating txouts are txouts that do not have the residing full transaction contained in the
/// graph.
pub fn floating_txouts(&self) -> impl Iterator
- {
self.txs
.iter()
.filter_map(|(txid, (tx_node, _, _))| match tx_node {
TxNodeInternal::Whole(_) => None,
TxNodeInternal::Partial(txouts) => Some(
txouts
.iter()
.map(|(&vout, txout)| (OutPoint::new(*txid, vout), txout)),
),
})
.flatten()
}
/// Iterate over all full transactions in the graph.
pub fn full_txs(&self) -> impl Iterator
- , A>> {
self.txs
.iter()
.filter_map(|(&txid, (tx, anchors, last_seen))| match tx {
TxNodeInternal::Whole(tx) => Some(TxNode {
txid,
tx: tx.clone(),
anchors,
last_seen_unconfirmed: *last_seen,
}),
TxNodeInternal::Partial(_) => None,
})
}
/// Get a transaction by txid. This only returns `Some` for full transactions.
///
/// Refer to [`get_txout`] for getting a specific [`TxOut`].
///
/// [`get_txout`]: Self::get_txout
pub fn get_tx(&self, txid: Txid) -> Option> {
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, A>> {
match &self.txs.get(&txid)? {
(TxNodeInternal::Whole(tx), anchors, last_seen) => Some(TxNode {
txid,
tx: tx.clone(),
anchors,
last_seen_unconfirmed: *last_seen,
}),
_ => None,
}
}
/// 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.as_ref().output.get(outpoint.vout as usize),
TxNodeInternal::Partial(txouts) => txouts.get(&outpoint.vout),
}
}
/// Returns known outputs of a given `txid`.
///
/// Returns a [`BTreeMap`] of vout to output of the provided `txid`.
pub fn tx_outputs(&self, txid: Txid) -> Option> {
Some(match &self.txs.get(&txid)?.0 {
TxNodeInternal::Whole(tx) => tx
.as_ref()
.output
.iter()
.enumerate()
.map(|(vout, txout)| (vout as u32, txout))
.collect::>(),
TxNodeInternal::Partial(txouts) => txouts
.iter()
.map(|(vout, txout)| (*vout, txout))
.collect::>(),
})
}
/// Calculates the fee of a given transaction. Returns 0 if `tx` is a coinbase transaction.
/// Returns `OK(_)` if we have all the [`TxOut`]s being spent by `tx` in the graph (either as
/// the full transactions or individual txouts).
///
/// To calculate the fee for a [`Transaction`] that depends on foreign [`TxOut`] values you must
/// first manually insert the foreign TxOuts into the tx graph using the [`insert_txout`] function.
/// Only insert TxOuts you trust the values for!
///
/// Note `tx` does not have to be in the graph for this to work.
///
/// [`insert_txout`]: Self::insert_txout
pub fn calculate_fee(&self, tx: &Transaction) -> Result {
if tx.is_coinbase() {
return Ok(0);
}
let (inputs_sum, missing_outputs) = tx.input.iter().fold(
(0_i64, Vec::new()),
|(mut sum, mut missing_outpoints), txin| match self.get_txout(txin.previous_output) {
None => {
missing_outpoints.push(txin.previous_output);
(sum, missing_outpoints)
}
Some(txout) => {
sum += txout.value.to_sat() as i64;
(sum, missing_outpoints)
}
},
);
if !missing_outputs.is_empty() {
return Err(CalculateFeeError::MissingTxOut(missing_outputs));
}
let outputs_sum = tx
.output
.iter()
.map(|txout| txout.value.to_sat() as i64)
.sum::();
let fee = inputs_sum - outputs_sum;
if fee < 0 {
Err(CalculateFeeError::NegativeFee(fee))
} else {
Ok(fee as u64)
}
}
/// 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_spends(
&self,
txid: Txid,
) -> impl DoubleEndedIterator
- )> + '_ {
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 TxGraph {
/// Creates an iterator that filters and maps ancestor transactions.
///
/// The iterator starts with the ancestors of the supplied `tx` (ancestor transactions of `tx`
/// are transactions spent by `tx`). The supplied transaction is excluded from the iterator.
///
/// The supplied closure takes in two inputs `(depth, ancestor_tx)`:
///
/// * `depth` is the distance between the starting `Transaction` and the `ancestor_tx`. I.e., if
/// the `Transaction` is spending an output of the `ancestor_tx` then `depth` will be 1.
/// * `ancestor_tx` is the `Transaction`'s ancestor which we are considering to walk.
///
/// The supplied closure returns an `Option`, allowing the caller to map each `Transaction`
/// it visits and decide whether to visit ancestors.
pub fn walk_ancestors<'g, T, F, O>(&'g self, tx: T, walk_map: F) -> TxAncestors<'g, A, F>
where
T: Into>,
F: FnMut(usize, Arc) -> Option + 'g,
{
TxAncestors::new_exclude_root(self, tx, walk_map)
}
/// 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` then `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 visits
/// 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)
}
}
impl TxGraph {
/// 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(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 won't include:
/// - descendants of conflicting transactions (which are technically also conflicting)
/// - transactions conflicting with the given transaction's ancestors
pub fn direct_conflicts<'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)
}
/// Get all transaction anchors known by [`TxGraph`].
pub fn all_anchors(&self) -> &BTreeSet<(A, Txid)> {
&self.anchors
}
/// Whether the graph has any transactions or outputs in it.
pub fn is_empty(&self) -> bool {
self.txs.is_empty()
}
}
impl TxGraph {
/// Transform the [`TxGraph`] to have [`Anchor`]s of another type.
///
/// This takes in a closure of signature `FnMut(A) -> A2` which is called for each [`Anchor`] to
/// transform it.
pub fn map_anchors(self, f: F) -> TxGraph
where
F: FnMut(A) -> A2,
{
let mut new_graph = TxGraph::::default();
new_graph.apply_changeset(self.initial_changeset().map_anchors(f));
new_graph
}
/// Construct a new [`TxGraph`] from a list of transactions.
pub fn new(txs: impl IntoIterator
- ) -> Self {
let mut new = Self::default();
for tx in txs.into_iter() {
let _ = new.insert_tx(tx);
}
new
}
/// Inserts the given [`TxOut`] at [`OutPoint`].
///
/// Inserting floating txouts are useful for determining fee/feerate of transactions we care
/// about.
///
/// The [`ChangeSet`] result will be empty if the `outpoint` (or a full transaction containing
/// the `outpoint`) already existed in `self`.
///
/// [`apply_changeset`]: Self::apply_changeset
pub fn insert_txout(&mut self, outpoint: OutPoint, txout: TxOut) -> ChangeSet {
let mut update = Self::default();
update.txs.insert(
outpoint.txid,
(
TxNodeInternal::Partial([(outpoint.vout, txout)].into()),
BTreeSet::new(),
0,
),
);
self.apply_update(update)
}
/// Inserts the given transaction into [`TxGraph`].
///
/// The [`ChangeSet`] returned will be empty if `tx` already exists.
pub fn insert_tx>>(&mut self, tx: T) -> ChangeSet {
let tx = tx.into();
let mut update = Self::default();
update
.txs
.insert(tx.txid(), (TxNodeInternal::Whole(tx), BTreeSet::new(), 0));
self.apply_update(update)
}
/// Batch insert unconfirmed transactions.
///
/// Items of `txs` are tuples containing the transaction and a *last seen* timestamp. The
/// *last seen* communicates when the transaction is last seen in mempool which is used for
/// conflict-resolution (refer to [`TxGraph::insert_seen_at`] for details).
pub fn batch_insert_unconfirmed(
&mut self,
txs: impl IntoIterator
- ,
) -> ChangeSet {
let mut changeset = ChangeSet::::default();
for (tx, seen_at) in txs {
changeset.append(self.insert_seen_at(tx.txid(), seen_at));
changeset.append(self.insert_tx(tx));
}
changeset
}
/// Inserts the given `anchor` into [`TxGraph`].
///
/// The [`ChangeSet`] returned will be empty if graph already knows that `txid` exists in
/// `anchor`.
pub fn insert_anchor(&mut self, txid: Txid, anchor: A) -> ChangeSet {
let mut update = Self::default();
update.anchors.insert((anchor, txid));
self.apply_update(update)
}
/// Inserts the given `seen_at` for `txid` into [`TxGraph`].
///
/// Note that [`TxGraph`] only keeps track of the latest `seen_at`. To batch
/// update all unconfirmed transactions with the latest `seen_at`, see
/// [`update_last_seen_unconfirmed`].
///
/// [`update_last_seen_unconfirmed`]: Self::update_last_seen_unconfirmed
pub fn insert_seen_at(&mut self, txid: Txid, seen_at: u64) -> ChangeSet {
let mut update = Self::default();
let (_, _, update_last_seen) = update.txs.entry(txid).or_default();
*update_last_seen = seen_at;
self.apply_update(update)
}
/// Update the last seen time for all unconfirmed transactions.
///
/// This method updates the last seen unconfirmed time for this [`TxGraph`] by inserting
/// the given `seen_at` for every transaction not yet anchored to a confirmed block,
/// and returns the [`ChangeSet`] after applying all updates to `self`.
///
/// This is useful for keeping track of the latest time a transaction was seen
/// unconfirmed, which is important for evaluating transaction conflicts in the same
/// [`TxGraph`]. For details of how [`TxGraph`] resolves conflicts, see the docs for
/// [`try_get_chain_position`].
///
/// A normal use of this method is to call it with the current system time. Although
/// block headers contain a timestamp, using the header time would be less effective
/// at tracking mempool transactions, because it can drift from actual clock time, plus
/// we may want to update a transaction's last seen time repeatedly between blocks.
///
/// # Example
///
/// ```rust
/// # use bdk_chain::example_utils::*;
/// # use std::time::UNIX_EPOCH;
/// # let tx = tx_from_hex(RAW_TX_1);
/// # let mut tx_graph = bdk_chain::TxGraph::<()>::new([tx]);
/// let now = std::time::SystemTime::now()
/// .duration_since(UNIX_EPOCH)
/// .expect("valid duration")
/// .as_secs();
/// let changeset = tx_graph.update_last_seen_unconfirmed(now);
/// assert!(!changeset.last_seen.is_empty());
/// ```
///
/// Note that [`TxGraph`] only keeps track of the latest `seen_at`, so the given time must
/// by strictly greater than what is currently stored for a transaction to have an effect.
/// To insert a last seen time for a single txid, see [`insert_seen_at`].
///
/// [`insert_seen_at`]: Self::insert_seen_at
/// [`try_get_chain_position`]: Self::try_get_chain_position
pub fn update_last_seen_unconfirmed(&mut self, seen_at: u64) -> ChangeSet {
let mut changeset = ChangeSet::default();
let unanchored_txs: Vec = self
.txs
.iter()
.filter_map(
|(&txid, (_, anchors, _))| {
if anchors.is_empty() {
Some(txid)
} else {
None
}
},
)
.collect();
for txid in unanchored_txs {
changeset.append(self.insert_seen_at(txid, seen_at));
}
changeset
}
/// Extends this graph with another so that `self` becomes the union of the two sets of
/// transactions.
///
/// The returned [`ChangeSet`] is the set difference between `update` and `self` (transactions that
/// exist in `update` but not in `self`).
pub fn apply_update(&mut self, update: TxGraph) -> ChangeSet {
let changeset = self.determine_changeset(update);
self.apply_changeset(changeset.clone());
changeset
}
/// Determines the [`ChangeSet`] between `self` and an empty [`TxGraph`].
pub fn initial_changeset(&self) -> ChangeSet {
Self::default().determine_changeset(self.clone())
}
/// Applies [`ChangeSet`] to [`TxGraph`].
pub fn apply_changeset(&mut self, changeset: ChangeSet) {
for wrapped_tx in changeset.txs {
let tx = wrapped_tx.as_ref();
let txid = tx.txid();
tx.input
.iter()
.map(|txin| txin.previous_output)
// coinbase spends are not to be counted
.filter(|outpoint| !outpoint.is_null())
// record spend as this tx has spent this outpoint
.for_each(|outpoint| {
self.spends.entry(outpoint).or_default().insert(txid);
});
match self.txs.get_mut(&txid) {
Some((tx_node @ TxNodeInternal::Partial(_), _, _)) => {
*tx_node = TxNodeInternal::Whole(wrapped_tx.clone());
}
Some((TxNodeInternal::Whole(tx), _, _)) => {
debug_assert_eq!(
tx.as_ref().txid(),
txid,
"tx should produce txid that is same as key"
);
}
None => {
self.txs.insert(
txid,
(TxNodeInternal::Whole(wrapped_tx), BTreeSet::new(), 0),
);
}
}
}
for (outpoint, txout) in changeset.txouts {
let tx_entry = self.txs.entry(outpoint.txid).or_default();
match tx_entry {
(TxNodeInternal::Whole(_), _, _) => { /* do nothing since we already have full tx */
}
(TxNodeInternal::Partial(txouts), _, _) => {
txouts.insert(outpoint.vout, txout);
}
}
}
for (anchor, txid) in changeset.anchors {
if self.anchors.insert((anchor.clone(), txid)) {
let (_, anchors, _) = self.txs.entry(txid).or_default();
anchors.insert(anchor);
}
}
for (txid, new_last_seen) in changeset.last_seen {
let (_, _, last_seen) = self.txs.entry(txid).or_default();
if new_last_seen > *last_seen {
*last_seen = new_last_seen;
}
}
}
/// Previews the resultant [`ChangeSet`] when [`Self`] is updated against the `update` graph.
///
/// 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) -> ChangeSet {
let mut changeset = ChangeSet::::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) {
(None, TxNodeInternal::Whole(update_tx)) => {
changeset.txs.insert(update_tx.clone());
0
}
(None, TxNodeInternal::Partial(update_txos)) => {
changeset.txouts.extend(
update_txos
.iter()
.map(|(&vout, txo)| (OutPoint::new(txid, vout), txo.clone())),
);
0
}
(Some((TxNodeInternal::Whole(_), _, last_seen)), _) => *last_seen,
(
Some((TxNodeInternal::Partial(_), _, last_seen)),
TxNodeInternal::Whole(update_tx),
) => {
changeset.txs.insert(update_tx.clone());
*last_seen
}
(
Some((TxNodeInternal::Partial(txos), _, last_seen)),
TxNodeInternal::Partial(update_txos),
) => {
changeset.txouts.extend(
update_txos
.iter()
.filter(|(vout, _)| !txos.contains_key(*vout))
.map(|(&vout, txo)| (OutPoint::new(txid, vout), txo.clone())),
);
*last_seen
}
};
if *update_last_seen > prev_last_seen {
changeset.last_seen.insert(txid, *update_last_seen);
}
}
changeset.anchors = update.anchors.difference(&self.anchors).cloned().collect();
changeset
}
}
impl TxGraph {
/// Get the position of the transaction in `chain` with tip `chain_tip`.
///
/// Chain data is fetched from `chain`, a [`ChainOracle`] implementation.
///
/// This method returns `Ok(None)` if the transaction is not found in the chain, and no longer
/// belongs in the mempool. The following factors are used to approximate whether an
/// unconfirmed transaction exists in the mempool (not evicted):
///
/// 1. Unconfirmed transactions that conflict with confirmed transactions are evicted.
/// 2. Unconfirmed transactions that spend from transactions that are evicted, are also
/// evicted.
/// 3. Given two conflicting unconfirmed transactions, the transaction with the lower
/// `last_seen_unconfirmed` parameter is evicted. A transaction's `last_seen_unconfirmed`
/// parameter is the max of all it's descendants' `last_seen_unconfirmed` parameters. If the
/// final `last_seen_unconfirmed`s are the same, the transaction with the lower `txid` (by
/// lexicographical order) is evicted.
///
/// # Error
///
/// An error will occur if the [`ChainOracle`] implementation (`chain`) fails. If the
/// [`ChainOracle`] is infallible, [`get_chain_position`] can be used instead.
///
/// [`get_chain_position`]: Self::get_chain_position
pub fn try_get_chain_position(
&self,
chain: &C,
chain_tip: BlockId,
txid: Txid,
) -> Result