diff --git a/crates/chain/src/indexed_tx_graph.rs b/crates/chain/src/indexed_tx_graph.rs new file mode 100644 index 00000000..b0d547c7 --- /dev/null +++ b/crates/chain/src/indexed_tx_graph.rs @@ -0,0 +1,248 @@ +use core::convert::Infallible; + +use alloc::collections::BTreeSet; +use bitcoin::{OutPoint, Transaction, TxOut}; + +use crate::{ + sparse_chain::ChainPosition, + tx_graph::{Additions, TxGraph, TxInGraph}, + BlockAnchor, ChainOracle, FullTxOut, ObservedIn, TxIndex, TxIndexAdditions, +}; + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct TxInChain<'a, T, A> { + pub observed_in: ObservedIn<&'a A>, + pub tx: TxInGraph<'a, T, A>, +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct TxOutInChain<'a, I, A> { + pub spk_index: &'a I, + pub txout: FullTxOut>, +} + +pub struct IndexedAdditions { + pub graph_additions: Additions, + pub index_delta: D, +} + +impl Default for IndexedAdditions { + fn default() -> Self { + Self { + graph_additions: Default::default(), + index_delta: Default::default(), + } + } +} + +impl TxIndexAdditions for IndexedAdditions { + fn append_additions(&mut self, other: Self) { + let Self { + graph_additions, + index_delta, + } = other; + self.graph_additions.append(graph_additions); + self.index_delta.append_additions(index_delta); + } +} + +pub struct IndexedTxGraph { + graph: TxGraph, + index: I, +} + +impl Default for IndexedTxGraph { + fn default() -> Self { + Self { + graph: Default::default(), + index: Default::default(), + } + } +} + +impl IndexedTxGraph { + /// Get a reference of the internal transaction graph. + pub fn graph(&self) -> &TxGraph { + &self.graph + } + + /// Get a reference of the internal transaction index. + pub fn index(&self) -> &I { + &self.index + } + + /// Insert a `txout` that exists in `outpoint` with the given `observation`. + pub fn insert_txout( + &mut self, + outpoint: OutPoint, + txout: &TxOut, + observation: ObservedIn, + ) -> IndexedAdditions { + IndexedAdditions { + graph_additions: { + let mut graph_additions = self.graph.insert_txout(outpoint, txout.clone()); + graph_additions.append(match observation { + ObservedIn::Block(anchor) => self.graph.insert_anchor(outpoint.txid, anchor), + ObservedIn::Mempool(seen_at) => { + self.graph.insert_seen_at(outpoint.txid, seen_at) + } + }); + graph_additions + }, + index_delta: ::index_txout(&mut self.index, outpoint, txout), + } + } + + pub fn insert_tx( + &mut self, + tx: &Transaction, + observation: ObservedIn, + ) -> IndexedAdditions { + let txid = tx.txid(); + IndexedAdditions { + graph_additions: { + let mut graph_additions = self.graph.insert_tx(tx.clone()); + graph_additions.append(match observation { + ObservedIn::Block(anchor) => self.graph.insert_anchor(txid, anchor), + ObservedIn::Mempool(seen_at) => self.graph.insert_seen_at(txid, seen_at), + }); + graph_additions + }, + index_delta: ::index_tx(&mut self.index, tx), + } + } + + pub fn filter_and_insert_txs<'t, T>( + &mut self, + txs: T, + observation: ObservedIn, + ) -> IndexedAdditions + where + T: Iterator, + { + txs.filter_map(|tx| { + if self.index.is_tx_relevant(tx) { + Some(self.insert_tx(tx, observation.clone())) + } else { + None + } + }) + .fold(IndexedAdditions::default(), |mut acc, other| { + acc.append_additions(other); + acc + }) + } + + pub fn relevant_heights(&self) -> BTreeSet { + self.graph.relevant_heights() + } + + pub fn try_list_chain_txs<'a, C>( + &'a self, + chain: C, + ) -> impl Iterator, C::Error>> + where + C: ChainOracle + 'a, + { + self.graph + .full_transactions() + .filter(|tx| self.index.is_tx_relevant(tx)) + .filter_map(move |tx| { + self.graph + .try_get_chain_position(&chain, tx.txid) + .map(|v| v.map(|observed_in| TxInChain { observed_in, tx })) + .transpose() + }) + } + + pub fn list_chain_txs<'a, C>( + &'a self, + chain: C, + ) -> impl Iterator> + where + C: ChainOracle + 'a, + { + self.try_list_chain_txs(chain) + .map(|r| r.expect("error is infallible")) + } + + pub fn try_list_chain_txouts<'a, C>( + &'a self, + chain: C, + ) -> impl Iterator, C::Error>> + where + C: ChainOracle + 'a, + ObservedIn: ChainPosition, + { + self.index.relevant_txouts().iter().filter_map( + move |(op, (spk_i, txout))| -> Option> { + 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, op.txid) { + Ok(Some(observed_at)) => observed_at, + Ok(None) => return None, + Err(err) => return Some(Err(err)), + }; + + let spent_by = match self.graph.try_get_spend_in_chain(&chain, *op) { + Ok(spent_by) => spent_by, + Err(err) => return Some(Err(err)), + }; + + let full_txout = FullTxOut { + outpoint: *op, + txout: txout.clone(), + chain_position, + spent_by, + is_on_coinbase, + }; + + let txout_in_chain = TxOutInChain { + spk_index: spk_i, + txout: full_txout, + }; + + Some(Ok(txout_in_chain)) + }, + ) + } + + pub fn list_chain_txouts<'a, C>( + &'a self, + chain: C, + ) -> impl Iterator> + where + C: ChainOracle + 'a, + ObservedIn: ChainPosition, + { + self.try_list_chain_txouts(chain) + .map(|r| r.expect("error in infallible")) + } + + /// Return relevant unspents. + pub fn try_list_chain_utxos<'a, C>( + &'a self, + chain: C, + ) -> impl Iterator, C::Error>> + where + C: ChainOracle + 'a, + ObservedIn: ChainPosition, + { + self.try_list_chain_txouts(chain) + .filter(|r| !matches!(r, Ok(txo) if txo.txout.spent_by.is_none())) + } + + pub fn list_chain_utxos<'a, C>( + &'a self, + chain: C, + ) -> impl Iterator> + where + C: ChainOracle + 'a, + ObservedIn: ChainPosition, + { + self.try_list_chain_utxos(chain) + .map(|r| r.expect("error is infallible")) + } +} diff --git a/crates/chain/src/lib.rs b/crates/chain/src/lib.rs index 4e49e34e..52844097 100644 --- a/crates/chain/src/lib.rs +++ b/crates/chain/src/lib.rs @@ -24,6 +24,7 @@ mod spk_txout_index; pub use spk_txout_index::*; mod chain_data; pub use chain_data::*; +pub mod indexed_tx_graph; pub mod keychain; pub mod sparse_chain; mod tx_data_traits; diff --git a/crates/chain/src/tx_graph.rs b/crates/chain/src/tx_graph.rs index 63b75324..ddeb5e13 100644 --- a/crates/chain/src/tx_graph.rs +++ b/crates/chain/src/tx_graph.rs @@ -55,10 +55,7 @@ //! assert!(additions.is_empty()); //! ``` -use crate::{ - collections::*, sparse_chain::ChainPosition, BlockAnchor, BlockId, ChainOracle, ForEachTxOut, - FullTxOut, ObservedIn, TxIndex, TxIndexAdditions, -}; +use crate::{collections::*, BlockAnchor, BlockId, ChainOracle, ForEachTxOut, ObservedIn}; use alloc::vec::Vec; use bitcoin::{OutPoint, Transaction, TxOut, Txid}; use core::{ @@ -129,18 +126,6 @@ impl<'a, A> TxInGraph<'a, Transaction, A> { } } -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct TxInChain<'a, T, A> { - pub observed_in: ObservedIn<&'a A>, - pub tx: TxInGraph<'a, T, A>, -} - -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct TxOutInChain<'a, I, A> { - pub spk_index: &'a I, - pub txout: FullTxOut>, -} - /// 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 @@ -692,232 +677,6 @@ impl TxGraph { } } -pub struct IndexedAdditions { - pub graph_additions: Additions, - pub index_delta: D, -} - -impl Default for IndexedAdditions { - fn default() -> Self { - Self { - graph_additions: Default::default(), - index_delta: Default::default(), - } - } -} - -impl TxIndexAdditions for IndexedAdditions { - fn append_additions(&mut self, other: Self) { - let Self { - graph_additions, - index_delta, - } = other; - self.graph_additions.append(graph_additions); - self.index_delta.append_additions(index_delta); - } -} - -pub struct IndexedTxGraph { - graph: TxGraph, - index: I, -} - -impl Default for IndexedTxGraph { - fn default() -> Self { - Self { - graph: Default::default(), - index: Default::default(), - } - } -} - -impl IndexedTxGraph { - /// Get a reference of the internal transaction graph. - pub fn graph(&self) -> &TxGraph { - &self.graph - } - - /// Get a reference of the internal transaction index. - pub fn index(&self) -> &I { - &self.index - } - - /// Insert a `txout` that exists in `outpoint` with the given `observation`. - pub fn insert_txout( - &mut self, - outpoint: OutPoint, - txout: &TxOut, - observation: ObservedIn, - ) -> IndexedAdditions { - IndexedAdditions { - graph_additions: { - let mut graph_additions = self.graph.insert_txout(outpoint, txout.clone()); - graph_additions.append(match observation { - ObservedIn::Block(anchor) => self.graph.insert_anchor(outpoint.txid, anchor), - ObservedIn::Mempool(seen_at) => { - self.graph.insert_seen_at(outpoint.txid, seen_at) - } - }); - graph_additions - }, - index_delta: ::index_txout(&mut self.index, outpoint, txout), - } - } - - pub fn insert_tx( - &mut self, - tx: &Transaction, - observation: ObservedIn, - ) -> IndexedAdditions { - let txid = tx.txid(); - IndexedAdditions { - graph_additions: { - let mut graph_additions = self.graph.insert_tx(tx.clone()); - graph_additions.append(match observation { - ObservedIn::Block(anchor) => self.graph.insert_anchor(txid, anchor), - ObservedIn::Mempool(seen_at) => self.graph.insert_seen_at(txid, seen_at), - }); - graph_additions - }, - index_delta: ::index_tx(&mut self.index, tx), - } - } - - pub fn filter_and_insert_txs<'t, T>( - &mut self, - txs: T, - observation: ObservedIn, - ) -> IndexedAdditions - where - T: Iterator, - { - txs.filter_map(|tx| { - if self.index.is_tx_relevant(tx) { - Some(self.insert_tx(tx, observation.clone())) - } else { - None - } - }) - .fold(IndexedAdditions::default(), |mut acc, other| { - acc.append_additions(other); - acc - }) - } - - pub fn relevant_heights(&self) -> BTreeSet { - self.graph.relevant_heights() - } - - pub fn try_list_chain_txs<'a, C>( - &'a self, - chain: C, - ) -> impl Iterator, C::Error>> - where - C: ChainOracle + 'a, - { - self.graph - .full_transactions() - .filter(|tx| self.index.is_tx_relevant(tx)) - .filter_map(move |tx| { - self.graph - .try_get_chain_position(&chain, tx.txid) - .map(|v| v.map(|observed_in| TxInChain { observed_in, tx })) - .transpose() - }) - } - - pub fn list_chain_txs<'a, C>( - &'a self, - chain: C, - ) -> impl Iterator> - where - C: ChainOracle + 'a, - { - self.try_list_chain_txs(chain) - .map(|r| r.expect("error is infallible")) - } - - pub fn try_list_chain_txouts<'a, C>( - &'a self, - chain: C, - ) -> impl Iterator, C::Error>> - where - C: ChainOracle + 'a, - ObservedIn: ChainPosition, - { - self.index.relevant_txouts().iter().filter_map( - move |(op, (spk_i, txout))| -> Option> { - 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, op.txid) { - Ok(Some(observed_at)) => observed_at, - Ok(None) => return None, - Err(err) => return Some(Err(err)), - }; - - let spent_by = match self.graph.try_get_spend_in_chain(&chain, *op) { - Ok(spent_by) => spent_by, - Err(err) => return Some(Err(err)), - }; - - let full_txout = FullTxOut { - outpoint: *op, - txout: txout.clone(), - chain_position, - spent_by, - is_on_coinbase, - }; - - let txout_in_chain = TxOutInChain { - spk_index: spk_i, - txout: full_txout, - }; - - Some(Ok(txout_in_chain)) - }, - ) - } - - pub fn list_chain_txouts<'a, C>( - &'a self, - chain: C, - ) -> impl Iterator> - where - C: ChainOracle + 'a, - ObservedIn: ChainPosition, - { - self.try_list_chain_txouts(chain) - .map(|r| r.expect("error in infallible")) - } - - /// Return relevant unspents. - pub fn try_list_chain_utxos<'a, C>( - &'a self, - chain: C, - ) -> impl Iterator, C::Error>> - where - C: ChainOracle + 'a, - ObservedIn: ChainPosition, - { - self.try_list_chain_txouts(chain) - .filter(|r| !matches!(r, Ok(txo) if txo.txout.spent_by.is_none())) - } - - pub fn list_chain_utxos<'a, C>( - &'a self, - chain: C, - ) -> impl Iterator> - where - C: ChainOracle + 'a, - ObservedIn: ChainPosition, - { - self.try_list_chain_utxos(chain) - .map(|r| r.expect("error is infallible")) - } -} - /// A structure that represents changes to a [`TxGraph`]. /// /// It is named "additions" because [`TxGraph`] is monotone, so transactions can only be added and