use crate::{
sparse_chain::{self, ChainPosition},
- BlockAnchor, COINBASE_MATURITY,
+ Anchor, ConfirmationHeight, COINBASE_MATURITY,
};
/// Represents an observation of some chain data.
///
-/// The generic `A` should be a [`BlockAnchor`] implementation.
+/// The generic `A` should be a [`Anchor`] implementation.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, core::hash::Hash)]
pub enum ObservedAs<A> {
/// The chain data is seen as confirmed, and in anchored by `A`.
Confirmed(A),
+ /// The chain data is assumed to be confirmed, because a transaction that spends it is anchored
+ /// by `A`.
+ ConfirmedImplicit(A),
/// The chain data is seen in mempool at this given timestamp.
Unconfirmed(u64),
}
pub fn cloned(self) -> ObservedAs<A> {
match self {
ObservedAs::Confirmed(a) => ObservedAs::Confirmed(a.clone()),
+ ObservedAs::ConfirmedImplicit(a) => ObservedAs::ConfirmedImplicit(a.clone()),
ObservedAs::Unconfirmed(last_seen) => ObservedAs::Unconfirmed(last_seen),
}
}
}
}
-impl BlockAnchor for BlockId {
+impl Anchor for BlockId {
fn anchor_block(&self) -> BlockId {
*self
}
}
}
-impl<A: BlockAnchor> FullTxOut<ObservedAs<A>> {
+impl<A: Anchor + ConfirmationHeight> FullTxOut<ObservedAs<A>> {
/// Whether the `txout` is considered mature.
///
/// This is the alternative version of [`is_mature`] which depends on `chain_position` being a
- /// [`ObservedAs<A>`] where `A` implements [`BlockAnchor`].
+ /// [`ObservedAs<A>`] where `A` implements [`Anchor`].
///
/// [`is_mature`]: Self::is_mature
- pub fn is_observed_as_confirmed_and_mature(&self, tip: u32) -> bool {
+ pub fn is_observed_as_mature(&self, tip: u32) -> bool {
if !self.is_on_coinbase {
return false;
}
let tx_height = match &self.chain_position {
- ObservedAs::Confirmed(anchor) => anchor.anchor_block().height,
+ ObservedAs::Confirmed(anchor) => anchor.confirmation_height(),
+ // although we do not know the exact confirm height, the returned height here is the
+ // "upper bound" so only false-negatives are possible
+ ObservedAs::ConfirmedImplicit(anchor) => anchor.confirmation_height(),
ObservedAs::Unconfirmed(_) => {
debug_assert!(false, "coinbase tx can never be unconfirmed");
return false;
/// Currently this method does not take into account the locktime.
///
/// This is the alternative version of [`is_spendable_at`] which depends on `chain_position`
- /// being a [`ObservedAs<A>`] where `A` implements [`BlockAnchor`].
+ /// being a [`ObservedAs<A>`] where `A` implements [`Anchor`].
///
/// [`is_spendable_at`]: Self::is_spendable_at
pub fn is_observed_as_confirmed_and_spendable(&self, tip: u32) -> bool {
- if !self.is_observed_as_confirmed_and_mature(tip) {
+ if !self.is_observed_as_mature(tip) {
return false;
}
- match &self.chain_position {
- ObservedAs::Confirmed(anchor) => {
- if anchor.anchor_block().height > tip {
- return false;
- }
- }
+ let confirmation_height = match &self.chain_position {
+ ObservedAs::Confirmed(anchor) => anchor.confirmation_height(),
+ // although we do not know the exact confirm height, the returned height here is the
+ // "upper bound" so only false-negatives are possible
+ ObservedAs::ConfirmedImplicit(anchor) => anchor.confirmation_height(),
ObservedAs::Unconfirmed(_) => return false,
};
+ if confirmation_height > tip {
+ return false;
+ }
// if the spending tx is confirmed within tip height, the txout is no longer spendable
if let Some((ObservedAs::Confirmed(spending_anchor), _)) = &self.spent_by {
use core::convert::Infallible;
-use bitcoin::{OutPoint, Script, Transaction, TxOut};
+use bitcoin::{OutPoint, Script, Transaction, TxOut, Txid};
use crate::{
keychain::Balance,
- tx_graph::{Additions, TxGraph, TxNode},
- Append, BlockAnchor, BlockId, ChainOracle, FullTxOut, ObservedAs,
+ tx_graph::{Additions, CanonicalTx, TxGraph},
+ Anchor, Append, BlockId, ChainOracle, ConfirmationHeight, FullTxOut, ObservedAs,
};
+/// A struct that combines [`TxGraph`] and an [`Indexer`] implementation.
+///
+/// This structure ensures that [`TxGraph`] and [`Indexer`] are updated atomically.
pub struct IndexedTxGraph<A, I> {
/// Transaction index.
pub index: I,
}
}
-impl<A: BlockAnchor, I: TxIndex> IndexedTxGraph<A, I> {
+impl<A: Anchor, I: Indexer> IndexedTxGraph<A, I> {
/// Get a reference of the internal transaction graph.
pub fn graph(&self) -> &TxGraph<A> {
&self.graph
self.graph.apply_additions(graph_additions);
}
+}
+
+impl<A: Anchor, I: Indexer> IndexedTxGraph<A, I>
+where
+ I::Additions: Default + Append,
+{
+ /// Apply an `update` directly.
+ ///
+ /// `update` is a [`TxGraph<A>`] and the resultant changes is returned as [`IndexedAdditions`].
+ pub fn apply_update(&mut self, update: TxGraph<A>) -> IndexedAdditions<A, I::Additions> {
+ let graph_additions = self.graph.apply_update(update);
+
+ let mut index_additions = I::Additions::default();
+ for added_tx in &graph_additions.tx {
+ index_additions.append(self.index.index_tx(added_tx));
+ }
+ for (&added_outpoint, added_txout) in &graph_additions.txout {
+ index_additions.append(self.index.index_txout(added_outpoint, added_txout));
+ }
+
+ IndexedAdditions {
+ graph_additions,
+ index_additions,
+ }
+ }
- /// Insert a `txout` that exists in `outpoint` with the given `observation`.
+ /// Insert a floating `txout` of given `outpoint`.
pub fn insert_txout(
&mut self,
outpoint: OutPoint,
txout: &TxOut,
- observation: ObservedAs<A>,
) -> IndexedAdditions<A, I::Additions> {
- IndexedAdditions {
- graph_additions: {
- let mut graph_additions = self.graph.insert_txout(outpoint, txout.clone());
- graph_additions.append(match observation {
- ObservedAs::Confirmed(anchor) => {
- self.graph.insert_anchor(outpoint.txid, anchor)
- }
- ObservedAs::Unconfirmed(seen_at) => {
- self.graph.insert_seen_at(outpoint.txid, seen_at)
- }
- });
- graph_additions
- },
- index_additions: <I as TxIndex>::index_txout(&mut self.index, outpoint, txout),
- }
+ let mut update = TxGraph::<A>::default();
+ let _ = update.insert_txout(outpoint, txout.clone());
+ self.apply_update(update)
}
+ /// Insert and index a transaction into the graph.
+ ///
+ /// `anchors` can be provided to anchor the transaction to various blocks. `seen_at` is a
+ /// unix timestamp of when the transaction is last seen.
pub fn insert_tx(
&mut self,
tx: &Transaction,
- observation: ObservedAs<A>,
+ anchors: impl IntoIterator<Item = A>,
+ seen_at: Option<u64>,
) -> IndexedAdditions<A, I::Additions> {
let txid = tx.txid();
- IndexedAdditions {
- graph_additions: {
- let mut graph_additions = self.graph.insert_tx(tx.clone());
- graph_additions.append(match observation {
- ObservedAs::Confirmed(anchor) => self.graph.insert_anchor(txid, anchor),
- ObservedAs::Unconfirmed(seen_at) => self.graph.insert_seen_at(txid, seen_at),
- });
- graph_additions
- },
- index_additions: <I as TxIndex>::index_tx(&mut self.index, tx),
+ let mut update = TxGraph::<A>::default();
+ if self.graph.get_tx(txid).is_none() {
+ let _ = update.insert_tx(tx.clone());
}
+ for anchor in anchors.into_iter() {
+ let _ = update.insert_anchor(txid, anchor);
+ }
+ if let Some(seen_at) = seen_at {
+ let _ = update.insert_seen_at(txid, seen_at);
+ }
+
+ self.apply_update(update)
}
+ /// Insert relevant transactions from the given `txs` iterator.
+ ///
+ /// Relevancy is determined by the [`Indexer::is_tx_relevant`] implementation of `I`. Irrelevant
+ /// transactions in `txs` will be ignored.
+ ///
+ /// `anchors` can be provided to anchor the transactions to blocks. `seen_at` is a unix
+ /// timestamp of when the transactions are last seen.
pub fn insert_relevant_txs<'t, T>(
&mut self,
txs: T,
- observation: ObservedAs<A>,
+ anchors: impl IntoIterator<Item = A> + Clone,
+ seen_at: Option<u64>,
) -> IndexedAdditions<A, I::Additions>
where
T: Iterator<Item = &'t Transaction>,
- I::Additions: Default + Append,
{
- txs.filter_map(|tx| {
- if self.index.is_tx_relevant(tx) {
- Some(self.insert_tx(tx, observation.clone()))
- } else {
- None
- }
+ txs.filter_map(|tx| match self.index.is_tx_relevant(tx) {
+ true => Some(self.insert_tx(tx, anchors.clone(), seen_at)),
+ false => None,
})
- .fold(IndexedAdditions::default(), |mut acc, other| {
+ .fold(Default::default(), |mut acc, other| {
acc.append(other);
acc
})
}
+}
- // [TODO] Have to methods, one for relevant-only, and one for any. Have one in `TxGraph`.
- pub fn try_list_chain_txs<'a, C>(
+impl<A: Anchor, I: OwnedIndexer> IndexedTxGraph<A, I> {
+ pub fn try_list_owned_txs<'a, C>(
&'a self,
chain: &'a C,
- static_block: BlockId,
+ chain_tip: BlockId,
) -> impl Iterator<Item = Result<CanonicalTx<'a, Transaction, A>, C::Error>>
where
C: ChainOracle + 'a,
{
self.graph
- .full_transactions()
- .filter(|tx| self.index.is_tx_relevant(tx))
- .filter_map(move |tx| {
+ .full_txs()
+ .filter(|node| tx_alters_owned_utxo_set(&self.graph, &self.index, node.txid, node.tx))
+ .filter_map(move |tx_node| {
self.graph
- .try_get_chain_position(chain, static_block, tx.txid)
+ .try_get_chain_position(chain, chain_tip, tx_node.txid)
.map(|v| {
- v.map(|observed_in| CanonicalTx {
- observed_as: observed_in,
- tx,
+ v.map(|observed_as| CanonicalTx {
+ observed_as,
+ node: tx_node,
})
})
.transpose()
})
}
- pub fn list_chain_txs<'a, C>(
+ pub fn list_owned_txs<'a, C>(
&'a self,
chain: &'a C,
- static_block: BlockId,
+ chain_tip: BlockId,
) -> impl Iterator<Item = CanonicalTx<'a, Transaction, A>>
where
C: ChainOracle<Error = Infallible> + 'a,
{
- self.try_list_chain_txs(chain, static_block)
- .map(|r| r.expect("error is infallible"))
+ self.try_list_owned_txs(chain, chain_tip)
+ .map(|r| r.expect("chain oracle is infallible"))
}
- pub fn try_list_chain_txouts<'a, C>(
+ pub fn try_list_owned_txouts<'a, C>(
&'a self,
chain: &'a C,
- static_block: BlockId,
+ chain_tip: BlockId,
) -> impl Iterator<Item = Result<FullTxOut<ObservedAs<A>>, C::Error>> + 'a
where
C: ChainOracle + 'a,
{
- self.graph
- .all_txouts()
- .filter(|&(op, txo)| self.index.is_txout_relevant(op, txo))
- .filter_map(move |(op, txout)| -> Option<Result<_, C::Error>> {
- let graph_tx = self.graph.get_tx(op.txid)?;
-
- let is_on_coinbase = graph_tx.is_coin_base();
-
- let chain_position =
- match self
- .graph
- .try_get_chain_position(chain, static_block, op.txid)
- {
- Ok(Some(observed_at)) => observed_at.cloned(),
- Ok(None) => return None,
- Err(err) => return Some(Err(err)),
- };
-
- let spent_by = match self.graph.try_get_spend_in_chain(chain, static_block, op) {
- Ok(Some((obs, txid))) => Some((obs.cloned(), txid)),
- Ok(None) => None,
- Err(err) => return Some(Err(err)),
- };
-
- let full_txout = FullTxOut {
- outpoint: op,
- txout: txout.clone(),
- chain_position,
- spent_by,
- is_on_coinbase,
- };
-
- Some(Ok(full_txout))
+ self.graph()
+ .try_list_chain_txouts(chain, chain_tip, |_, txout| {
+ self.index.is_spk_owned(&txout.script_pubkey)
})
}
- pub fn list_chain_txouts<'a, C>(
+ pub fn list_owned_txouts<'a, C>(
&'a self,
chain: &'a C,
- static_block: BlockId,
+ chain_tip: BlockId,
) -> impl Iterator<Item = FullTxOut<ObservedAs<A>>> + 'a
where
- C: ChainOracle<Error = Infallible> + 'a,
+ C: ChainOracle + 'a,
{
- self.try_list_chain_txouts(chain, static_block)
- .map(|r| r.expect("error in infallible"))
+ self.try_list_owned_txouts(chain, chain_tip)
+ .map(|r| r.expect("oracle is infallible"))
}
- /// Return relevant unspents.
- pub fn try_list_chain_utxos<'a, C>(
+ pub fn try_list_owned_unspents<'a, C>(
&'a self,
chain: &'a C,
- static_block: BlockId,
+ chain_tip: BlockId,
) -> impl Iterator<Item = Result<FullTxOut<ObservedAs<A>>, C::Error>> + 'a
where
C: ChainOracle + 'a,
{
- self.try_list_chain_txouts(chain, static_block)
- .filter(|r| !matches!(r, Ok(txo) if txo.spent_by.is_none()))
+ self.graph()
+ .try_list_chain_unspents(chain, chain_tip, |_, txout| {
+ self.index.is_spk_owned(&txout.script_pubkey)
+ })
}
- pub fn list_chain_utxos<'a, C>(
+ pub fn list_owned_unspents<'a, C>(
&'a self,
chain: &'a C,
- static_block: BlockId,
+ chain_tip: BlockId,
) -> impl Iterator<Item = FullTxOut<ObservedAs<A>>> + 'a
where
- C: ChainOracle<Error = Infallible> + 'a,
+ C: ChainOracle + 'a,
{
- self.try_list_chain_utxos(chain, static_block)
- .map(|r| r.expect("error is infallible"))
+ self.try_list_owned_unspents(chain, chain_tip)
+ .map(|r| r.expect("oracle is infallible"))
}
+}
+impl<A: Anchor + ConfirmationHeight, I: OwnedIndexer> IndexedTxGraph<A, I> {
pub fn try_balance<C, F>(
&self,
chain: &C,
- static_block: BlockId,
+ chain_tip: BlockId,
tip: u32,
mut should_trust: F,
) -> Result<Balance, C::Error>
let mut untrusted_pending = 0;
let mut confirmed = 0;
- for res in self.try_list_chain_txouts(chain, static_block) {
+ for res in self.try_list_owned_txouts(chain, chain_tip) {
let txout = res?;
match &txout.chain_position {
- ObservedAs::Confirmed(_) => {
+ ObservedAs::Confirmed(_) | ObservedAs::ConfirmedImplicit(_) => {
if txout.is_on_coinbase {
- if txout.is_observed_as_confirmed_and_mature(tip) {
+ if txout.is_observed_as_mature(tip) {
confirmed += txout.txout.value;
} else {
immature += txout.txout.value;
C: ChainOracle,
{
let mut sum = 0;
- for txo_res in self.try_list_chain_txouts(chain, static_block) {
+ for txo_res in self
+ .graph()
+ .try_list_chain_txouts(chain, static_block, |_, _| true)
+ {
let txo = txo_res?;
if txo.is_observed_as_confirmed_and_spendable(height) {
sum += txo.txout.value;
pub struct IndexedAdditions<A, IA> {
/// [`TxGraph`] additions.
pub graph_additions: Additions<A>,
- /// [`TxIndex`] additions.
+ /// [`Indexer`] additions.
pub index_additions: IA,
}
}
}
-impl<A: BlockAnchor, IA: Append> Append for IndexedAdditions<A, IA> {
+impl<A: Anchor, IA: Append> Append for IndexedAdditions<A, IA> {
fn append(&mut self, other: Self) {
self.graph_additions.append(other.graph_additions);
self.index_additions.append(other.index_additions);
}
}
-/// An outwards-facing view of a transaction that is part of the *best chain*'s history.
-#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
-pub struct CanonicalTx<'a, T, A> {
- /// Where the transaction is observed (in a block or in mempool).
- pub observed_as: ObservedAs<&'a A>,
- /// The transaction with anchors and last seen timestamp.
- pub tx: TxNode<'a, T, A>,
-}
-
-/// Represents an index of transaction data.
-pub trait TxIndex {
+/// Represents a structure that can index transaction data.
+pub trait Indexer {
/// The resultant "additions" when new transaction data is indexed.
type Additions;
/// Apply additions to itself.
fn apply_additions(&mut self, additions: Self::Additions);
- /// Returns whether the txout is marked as relevant in the index.
- fn is_txout_relevant(&self, outpoint: OutPoint, txout: &TxOut) -> bool;
-
- /// Returns whether the transaction is marked as relevant in the index.
+ /// Determines whether the transaction should be included in the index.
fn is_tx_relevant(&self, tx: &Transaction) -> bool;
}
-pub trait SpkIndex: TxIndex {}
+/// A trait that extends [`Indexer`] to also index "owned" script pubkeys.
+pub trait OwnedIndexer: Indexer {
+ /// Determines whether a given script pubkey (`spk`) is owned.
+ fn is_spk_owned(&self, spk: &Script) -> bool;
+}
+
+fn tx_alters_owned_utxo_set<A, I>(
+ graph: &TxGraph<A>,
+ index: &I,
+ txid: Txid,
+ tx: &Transaction,
+) -> bool
+where
+ A: Anchor,
+ I: OwnedIndexer,
+{
+ let prev_spends = (0..tx.input.len() as u32)
+ .map(|vout| OutPoint { txid, vout })
+ .filter_map(|op| graph.get_txout(op));
+ prev_spends
+ .chain(&tx.output)
+ .any(|txout| index.is_spk_owned(&txout.script_pubkey))
+}
use crate::{
collections::*,
- indexed_tx_graph::TxIndex,
+ indexed_tx_graph::Indexer,
miniscript::{Descriptor, DescriptorPublicKey},
ForEachTxOut, SpkTxOutIndex,
};
}
}
-impl<K: Clone + Ord + Debug + 'static> TxIndex for KeychainTxOutIndex<K> {
+impl<K: Clone + Ord + Debug + 'static> Indexer for KeychainTxOutIndex<K> {
type Additions = DerivationAdditions<K>;
fn index_txout(&mut self, outpoint: OutPoint, txout: &TxOut) -> Self::Additions {
self.apply_additions(additions)
}
- fn is_txout_relevant(&self, _outpoint: OutPoint, txout: &TxOut) -> bool {
- self.index_of_spk(&txout.script_pubkey).is_some()
- }
-
fn is_tx_relevant(&self, tx: &bitcoin::Transaction) -> bool {
self.is_relevant(tx)
}
/// `chain` for this to return `Some`.
pub fn spent_by<A>(&self, graph: &TxGraph<A>, outpoint: OutPoint) -> Option<(&P, Txid)> {
graph
- .outspends(outpoint)
+ .output_spends(outpoint)
.iter()
.find_map(|&txid| Some((self.tx_position(txid)?, txid)))
}
use crate::{
collections::{hash_map::Entry, BTreeMap, BTreeSet, HashMap},
- indexed_tx_graph::TxIndex,
+ indexed_tx_graph::Indexer,
ForEachTxOut,
};
use bitcoin::{self, OutPoint, Script, Transaction, TxOut, Txid};
}
}
-impl<I: Clone + Ord + 'static> TxIndex for SpkTxOutIndex<I> {
+impl<I: Clone + Ord + 'static> Indexer for SpkTxOutIndex<I> {
type Additions = ();
fn index_txout(&mut self, outpoint: OutPoint, txout: &TxOut) -> Self::Additions {
// This applies nothing.
}
- fn is_txout_relevant(&self, _outpoint: OutPoint, txout: &TxOut) -> bool {
- self.index_of_spk(&txout.script_pubkey).is_some()
- }
-
fn is_tx_relevant(&self, tx: &Transaction) -> bool {
self.is_relevant(tx)
}
/// assume that transaction A is also confirmed in the best chain. This does not necessarily mean
/// that transaction A is confirmed in block B. It could also mean transaction A is confirmed in a
/// parent block of B.
-pub trait BlockAnchor:
+pub trait Anchor:
core::fmt::Debug + Clone + Eq + PartialOrd + Ord + core::hash::Hash + Send + Sync + 'static
{
/// Returns the [`BlockId`] that the associated blockchain data is "anchored" in.
fn anchor_block(&self) -> BlockId;
}
-impl<A: BlockAnchor> BlockAnchor for &'static A {
+impl<A: Anchor> Anchor for &'static A {
fn anchor_block(&self) -> BlockId {
- <A as BlockAnchor>::anchor_block(self)
+ <A as Anchor>::anchor_block(self)
}
}
-impl BlockAnchor for (u32, BlockHash) {
+impl Anchor for (u32, BlockHash) {
fn anchor_block(&self) -> BlockId {
(*self).into()
}
}
+/// A trait that returns a confirmation height.
+///
+/// This is typically used to provide an [`Anchor`] implementation the exact confirmation height of
+/// the data being anchored.
+pub trait ConfirmationHeight {
+ /// Returns the confirmation height.
+ fn confirmation_height(&self) -> u32;
+}
+
/// Trait that makes an object appendable.
pub trait Append {
/// Append another object of the same type onto `self`.
//! assert!(additions.is_empty());
//! ```
-use crate::{collections::*, BlockAnchor, BlockId, ChainOracle, ForEachTxOut, ObservedAs};
+use crate::{collections::*, Anchor, BlockId, ChainOracle, ForEachTxOut, FullTxOut, ObservedAs};
use alloc::vec::Vec;
use bitcoin::{OutPoint, Transaction, TxOut, Txid};
use core::{
}
}
-/// An outward-facing representation of a (transaction) node in the [`TxGraph`].
+/// An outward-facing view of a (transaction) node in the [`TxGraph`].
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct TxNode<'a, T, A> {
/// Txid of the transaction.
}
}
+/// An outwards-facing view of a transaction that is part of the *best chain*'s history.
+#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
+pub struct CanonicalTx<'a, T, A> {
+ /// How the transaction is observed as (confirmed or unconfirmed).
+ pub observed_as: ObservedAs<&'a A>,
+ /// The transaction node (as part of the graph).
+ pub node: TxNode<'a, T, A>,
+}
+
impl<A> TxGraph<A> {
/// 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<Item = (OutPoint, &TxOut)> {
self.txs.iter().flat_map(|(txid, (tx, _, _))| match tx {
TxNodeInternal::Whole(tx) => tx
})
}
+ /// 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<Item = (OutPoint, &TxOut)> {
+ 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_transactions(&self) -> impl Iterator<Item = TxNode<'_, Transaction, A>> {
+ pub fn full_txs(&self) -> impl Iterator<Item = TxNode<'_, Transaction, A>> {
self.txs
.iter()
.filter_map(|(&txid, (tx, anchors, last_seen))| match tx {
}
}
+ /// Returns known outputs of a given `txid`.
+ ///
/// Returns a [`BTreeMap`] of vout to output of the provided `txid`.
- pub fn txouts(&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 {
TxNodeInternal::Whole(tx) => tx
.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<Txid> {
+ pub fn output_spends(&self, outpoint: OutPoint) -> &HashSet<Txid> {
self.spends.get(&outpoint).unwrap_or(&self.empty_outspends)
}
///
/// - `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(
+ pub fn tx_spends(
&self,
txid: Txid,
) -> impl DoubleEndedIterator<Item = (u32, &HashSet<Txid>)> + '_ {
.map(|(outpoint, spends)| (outpoint.vout, spends))
}
- /// Iterate over all partial transactions (outputs only) in the graph.
- pub fn partial_transactions(
- &self,
- ) -> impl Iterator<Item = TxNode<'_, BTreeMap<u32, TxOut>, A>> {
- self.txs
- .iter()
- .filter_map(|(&txid, (tx, anchors, last_seen))| match tx {
- TxNodeInternal::Whole(_) => None,
- TxNodeInternal::Partial(partial) => Some(TxNode {
- txid,
- tx: partial,
- anchors,
- last_seen_unconfirmed: *last_seen,
- }),
- })
- }
-
/// Creates an iterator that filters and maps descendants from the starting `txid`.
///
/// The supplied closure takes in two inputs `(depth, descendant_txid)`:
/// Returns the resultant [`Additions`] if the given `txout` is inserted at `outpoint`. Does not
/// mutate `self`.
///
+ /// Inserting floating txouts are useful for determining fee/feerate of transactions we care
+ /// about.
+ ///
/// 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<A> {
/// Inserts the given [`TxOut`] at [`OutPoint`].
///
- /// Note this will ignore the action if we already have the full transaction that the txout is
- /// alleged to be on (even if it doesn't match it!).
+ /// This is equivalent to calling [`insert_txout_preview`] and [`apply_additions`] in sequence.
+ ///
+ /// [`insert_txout_preview`]: Self::insert_txout_preview
+ /// [`apply_additions`]: Self::apply_additions
pub fn insert_txout(&mut self, outpoint: OutPoint, txout: TxOut) -> Additions<A> {
let additions = self.insert_txout_preview(outpoint, txout);
self.apply_additions(additions.clone());
}
}
-impl<A: BlockAnchor> TxGraph<A> {
+impl<A: Anchor> TxGraph<A> {
/// Get all heights that are relevant to the graph.
pub fn relevant_heights(&self) -> impl Iterator<Item = u32> + '_ {
let mut visited = HashSet::new();
.filter(move |&h| visited.insert(h))
}
- /// Determines whether a transaction of `txid` is in the best chain.
+ /// Get the position of the transaction in `chain` with tip `chain_tip`.
+ ///
+ /// If the given transaction of `txid` does not exist in the chain of `chain_tip`, `None` is
+ /// returned.
+ ///
+ /// # Error
+ ///
+ /// An error will occur if the [`ChainOracle`] implementation (`chain`) fails. If the
+ /// [`ChainOracle`] is infallible, [`get_chain_position`] can be used instead.
///
- /// TODO: Also return conflicting tx list, ordered by last_seen.
+ /// [`get_chain_position`]: Self::get_chain_position
pub fn try_get_chain_position<C>(
&self,
chain: &C,
- static_block: BlockId,
+ chain_tip: BlockId,
txid: Txid,
) -> Result<Option<ObservedAs<&A>>, C::Error>
where
};
for anchor in anchors {
- match chain.is_block_in_chain(anchor.anchor_block(), static_block)? {
+ match chain.is_block_in_chain(anchor.anchor_block(), chain_tip)? {
Some(true) => return Ok(Some(ObservedAs::Confirmed(anchor))),
- Some(false) => continue,
- // if we cannot determine whether block is in the best chain, we can check whether
- // a spending transaction is confirmed in best chain, and if so, it is guaranteed
- // that the tx being spent (this tx) is in the best chain
- None => {
- let spending_anchors = self
- .spends
- .range(OutPoint::new(txid, u32::MIN)..=OutPoint::new(txid, u32::MAX))
- .flat_map(|(_, spending_txids)| spending_txids)
- .filter_map(|spending_txid| self.txs.get(spending_txid))
- .flat_map(|(_, spending_anchors, _)| spending_anchors);
- for spending_anchor in spending_anchors {
- match chain
- .is_block_in_chain(spending_anchor.anchor_block(), static_block)?
- {
- Some(true) => return Ok(Some(ObservedAs::Confirmed(anchor))),
- _ => continue,
- }
- }
- }
+ _ => continue,
+ }
+ }
+
+ // If we cannot determine whether tx is in best chain, we can check whether a spending tx is
+ // confirmed and in best chain, and if so, it is guaranteed that this tx is in the best
+ // chain.
+ //
+ // [TODO] This logic is incomplete as we do not check spends of spends.
+ let spending_anchors = self
+ .spends
+ .range(OutPoint::new(txid, u32::MIN)..=OutPoint::new(txid, u32::MAX))
+ .flat_map(|(_, spending_txids)| spending_txids)
+ .filter_map(|spending_txid| self.txs.get(spending_txid))
+ .flat_map(|(_, spending_anchors, _)| spending_anchors);
+ for spending_anchor in spending_anchors {
+ match chain.is_block_in_chain(spending_anchor.anchor_block(), chain_tip)? {
+ Some(true) => return Ok(Some(ObservedAs::ConfirmedImplicit(spending_anchor))),
+ _ => continue,
}
}
// this tx cannot exist in the best chain
for conflicting_tx in self.walk_conflicts(tx, |_, txid| self.get_tx_node(txid)) {
for block in conflicting_tx.anchors.iter().map(A::anchor_block) {
- if chain.is_block_in_chain(block, static_block)? == Some(true) {
+ if chain.is_block_in_chain(block, chain_tip)? == Some(true) {
// conflicting tx is in best chain, so the current tx cannot be in best chain!
return Ok(None);
}
Ok(Some(ObservedAs::Unconfirmed(last_seen)))
}
+ /// Get the position of the transaction in `chain` with tip `chain_tip`.
+ ///
+ /// This is the infallible version of [`try_get_chain_position`].
+ ///
+ /// [`try_get_chain_position`]: Self::try_get_chain_position
pub fn get_chain_position<C>(
&self,
chain: &C,
- static_block: BlockId,
+ chain_tip: BlockId,
txid: Txid,
) -> Option<ObservedAs<&A>>
where
C: ChainOracle<Error = Infallible>,
{
- self.try_get_chain_position(chain, static_block, txid)
+ self.try_get_chain_position(chain, chain_tip, txid)
.expect("error is infallible")
}
- pub fn try_get_spend_in_chain<C>(
+ /// Get the txid of the spending transaction and where the spending transaction is observed in
+ /// the `chain` of `chain_tip`.
+ ///
+ /// If no in-chain transaction spends `outpoint`, `None` will be returned.
+ ///
+ /// # Error
+ ///
+ /// An error will occur only if the [`ChainOracle`] implementation (`chain`) fails.
+ ///
+ /// If the [`ChainOracle`] is infallible, [`get_chain_spend`] can be used instead.
+ ///
+ /// [`get_chain_spend`]: Self::get_chain_spend
+ pub fn try_get_chain_spend<C>(
&self,
chain: &C,
- static_block: BlockId,
+ chain_tip: BlockId,
outpoint: OutPoint,
) -> Result<Option<(ObservedAs<&A>, Txid)>, C::Error>
where
C: ChainOracle,
{
if self
- .try_get_chain_position(chain, static_block, outpoint.txid)?
+ .try_get_chain_position(chain, chain_tip, outpoint.txid)?
.is_none()
{
return Ok(None);
}
if let Some(spends) = self.spends.get(&outpoint) {
for &txid in spends {
- if let Some(observed_at) = self.try_get_chain_position(chain, static_block, txid)? {
+ if let Some(observed_at) = self.try_get_chain_position(chain, chain_tip, txid)? {
return Ok(Some((observed_at, txid)));
}
}
Ok(None)
}
+ /// Get the txid of the spending transaction and where the spending transaction is observed in
+ /// the `chain` of `chain_tip`.
+ ///
+ /// This is the infallible version of [`try_get_chain_spend`]
+ ///
+ /// [`try_get_chain_spend`]: Self::try_get_chain_spend
pub fn get_chain_spend<C>(
&self,
chain: &C,
where
C: ChainOracle<Error = Infallible>,
{
- self.try_get_spend_in_chain(chain, static_block, outpoint)
+ self.try_get_chain_spend(chain, static_block, outpoint)
.expect("error is infallible")
}
+
+ /// List graph transactions that are in `chain` with `chain_tip`.
+ ///
+ /// Each transaction is represented as a [`CanonicalTx`] that contains where the transaction is
+ /// observed in-chain, and the [`TxNode`].
+ ///
+ /// # Error
+ ///
+ /// If the [`ChainOracle`] implementation (`chain`) fails, an error will be returned with the
+ /// returned item.
+ ///
+ /// If the [`ChainOracle`] is infallible, [`list_chain_txs`] can be used instead.
+ ///
+ /// [`list_chain_txs`]: Self::list_chain_txs
+ pub fn try_list_chain_txs<'a, C>(
+ &'a self,
+ chain: &'a C,
+ chain_tip: BlockId,
+ ) -> impl Iterator<Item = Result<CanonicalTx<'a, Transaction, A>, C::Error>>
+ where
+ C: ChainOracle + 'a,
+ {
+ self.full_txs().filter_map(move |tx| {
+ self.try_get_chain_position(chain, chain_tip, tx.txid)
+ .map(|v| {
+ v.map(|observed_in| CanonicalTx {
+ observed_as: observed_in,
+ node: tx,
+ })
+ })
+ .transpose()
+ })
+ }
+
+ /// List graph transactions that are in `chain` with `chain_tip`.
+ ///
+ /// This is the infallible version of [`try_list_chain_txs`].
+ ///
+ /// [`try_list_chain_txs`]: Self::try_list_chain_txs
+ pub fn list_chain_txs<'a, C>(
+ &'a self,
+ chain: &'a C,
+ chain_tip: BlockId,
+ ) -> impl Iterator<Item = CanonicalTx<'a, Transaction, A>>
+ where
+ C: ChainOracle + 'a,
+ {
+ self.try_list_chain_txs(chain, chain_tip)
+ .map(|r| r.expect("oracle is infallible"))
+ }
+
+ /// List outputs that are in `chain` with `chain_tip`.
+ ///
+ /// Floating ouputs are not iterated over.
+ ///
+ /// The `filter_predicate` should return true for outputs that we wish to iterate over.
+ ///
+ /// # Error
+ ///
+ /// A returned item can error if the [`ChainOracle`] implementation (`chain`) fails.
+ ///
+ /// If the [`ChainOracle`] is infallible, [`list_chain_txouts`] can be used instead.
+ ///
+ /// [`list_chain_txouts`]: Self::list_chain_txouts
+ pub fn try_list_chain_txouts<'a, C, P>(
+ &'a self,
+ chain: &'a C,
+ chain_tip: BlockId,
+ mut filter_predicate: P,
+ ) -> impl Iterator<Item = Result<FullTxOut<ObservedAs<A>>, C::Error>> + 'a
+ where
+ C: ChainOracle + 'a,
+ P: FnMut(OutPoint, &TxOut) -> bool + 'a,
+ {
+ self.try_list_chain_txs(chain, chain_tip)
+ .flat_map(move |tx_res| match tx_res {
+ Ok(canonical_tx) => canonical_tx
+ .node
+ .output
+ .iter()
+ .enumerate()
+ .filter_map(|(vout, txout)| {
+ let outpoint = OutPoint::new(canonical_tx.node.txid, vout as _);
+ if filter_predicate(outpoint, txout) {
+ Some(Ok((outpoint, txout.clone(), canonical_tx.clone())))
+ } else {
+ None
+ }
+ })
+ .collect::<Vec<_>>(),
+ Err(err) => vec![Err(err)],
+ })
+ .map(move |res| -> Result<_, C::Error> {
+ let (
+ outpoint,
+ txout,
+ CanonicalTx {
+ observed_as,
+ node: tx_node,
+ },
+ ) = res?;
+ let chain_position = observed_as.cloned();
+ let spent_by = self
+ .try_get_chain_spend(chain, chain_tip, outpoint)?
+ .map(|(obs_as, txid)| (obs_as.cloned(), txid));
+ let is_on_coinbase = tx_node.tx.is_coin_base();
+ Ok(FullTxOut {
+ outpoint,
+ txout,
+ chain_position,
+ spent_by,
+ is_on_coinbase,
+ })
+ })
+ }
+
+ /// List outputs that are in `chain` with `chain_tip`.
+ ///
+ /// This is the infallible version of [`try_list_chain_txouts`].
+ ///
+ /// [`try_list_chain_txouts`]: Self::try_list_chain_txouts
+ pub fn list_chain_txouts<'a, C, P>(
+ &'a self,
+ chain: &'a C,
+ chain_tip: BlockId,
+ filter_predicate: P,
+ ) -> impl Iterator<Item = FullTxOut<ObservedAs<A>>> + 'a
+ where
+ C: ChainOracle<Error = Infallible> + 'a,
+ P: FnMut(OutPoint, &TxOut) -> bool + 'a,
+ {
+ self.try_list_chain_txouts(chain, chain_tip, filter_predicate)
+ .map(|r| r.expect("error in infallible"))
+ }
+
+ /// List unspent outputs (UTXOs) that are in `chain` with `chain_tip`.
+ ///
+ /// Floating outputs are not iterated over.
+ ///
+ /// # Error
+ ///
+ /// An item can be an error if the [`ChainOracle`] implementation fails. If the oracle is
+ /// infallible, [`list_chain_unspents`] can be used instead.
+ ///
+ /// [`list_chain_unspents`]: Self::list_chain_unspents
+ pub fn try_list_chain_unspents<'a, C, P>(
+ &'a self,
+ chain: &'a C,
+ chain_tip: BlockId,
+ filter_txout: P,
+ ) -> impl Iterator<Item = Result<FullTxOut<ObservedAs<A>>, C::Error>> + 'a
+ where
+ C: ChainOracle + 'a,
+ P: FnMut(OutPoint, &TxOut) -> bool + 'a,
+ {
+ self.try_list_chain_txouts(chain, chain_tip, filter_txout)
+ .filter(|r| !matches!(r, Ok(txo) if txo.spent_by.is_none()))
+ }
+
+ /// List unspent outputs (UTXOs) that are in `chain` with `chain_tip`.
+ ///
+ /// This is the infallible version of [`try_list_chain_unspents`].
+ ///
+ /// [`try_list_chain_unspents`]: Self::try_list_chain_unspents
+ pub fn list_chain_unspents<'a, C, P>(
+ &'a self,
+ chain: &'a C,
+ static_block: BlockId,
+ filter_txout: P,
+ ) -> impl Iterator<Item = FullTxOut<ObservedAs<A>>> + 'a
+ where
+ C: ChainOracle<Error = Infallible> + 'a,
+ P: FnMut(OutPoint, &TxOut) -> bool + 'a,
+ {
+ self.try_list_chain_unspents(chain, static_block, filter_txout)
+ .map(|r| r.expect("error is infallible"))
+ }
}
/// A structure that represents changes to a [`TxGraph`].
// Apply addition and check the new graph counts.
graph.apply_additions(additions);
assert_eq!(graph.all_txouts().count(), 4);
- assert_eq!(graph.full_transactions().count(), 1);
- assert_eq!(graph.partial_transactions().count(), 2);
+ assert_eq!(graph.full_txs().count(), 1);
+ assert_eq!(graph.floating_txouts().count(), 3);
// Check TxOuts are fetched correctly from the graph.
assert_eq!(
- graph.txouts(h!("tx1")).expect("should exists"),
+ graph.tx_outputs(h!("tx1")).expect("should exists"),
[
(
1u32,
);
assert_eq!(
- graph.txouts(update_txs.txid()).expect("should exists"),
+ graph.tx_outputs(update_txs.txid()).expect("should exists"),
[(
0u32,
&TxOut {
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());
+ assert!(graph.output_spends(OutPoint::null()).is_empty());
+ assert!(graph.tx_spends(Txid::all_zeros()).next().is_none());
}
#[test]
let _ = graph2.insert_tx(tx1);
assert_eq!(
- graph1.outspends(op),
+ graph1.output_spends(op),
&iter::once(tx2.txid()).collect::<HashSet<_>>()
);
- assert_eq!(graph2.outspends(op), graph1.outspends(op));
+ assert_eq!(graph2.output_spends(op), graph1.output_spends(op));
}
#[test]