+++ /dev/null
-use crate::collections::{HashMap, HashSet, VecDeque};
-use crate::tx_graph::{TxAncestors, TxDescendants};
-use crate::{Anchor, CanonicalReason, CanonicalizationParams, ChainOracle, ObservedIn, TxGraph};
-use alloc::boxed::Box;
-use alloc::collections::BTreeSet;
-use alloc::sync::Arc;
-use alloc::vec::Vec;
-use bdk_core::BlockId;
-use bitcoin::{Transaction, Txid};
-
-type CanonicalMap<A> = HashMap<Txid, (Arc<Transaction>, CanonicalReason<A>)>;
-type NotCanonicalSet = HashSet<Txid>;
-
-/// Iterates over canonical txs.
-pub struct CanonicalIter<'g, A, C> {
- tx_graph: &'g TxGraph<A>,
- chain: &'g C,
- chain_tip: BlockId,
-
- unprocessed_assumed_txs: Box<dyn Iterator<Item = (Txid, Arc<Transaction>)> + 'g>,
- unprocessed_anchored_txs:
- Box<dyn Iterator<Item = (Txid, Arc<Transaction>, &'g BTreeSet<A>)> + 'g>,
- unprocessed_seen_txs: Box<dyn Iterator<Item = (Txid, Arc<Transaction>, u64)> + 'g>,
- unprocessed_leftover_txs: VecDeque<(Txid, Arc<Transaction>, u32)>,
-
- canonical: CanonicalMap<A>,
- not_canonical: NotCanonicalSet,
-
- queue: VecDeque<Txid>,
-}
-
-impl<'g, A: Anchor, C: ChainOracle> CanonicalIter<'g, A, C> {
- /// Constructs [`CanonicalIter`].
- pub fn new(
- tx_graph: &'g TxGraph<A>,
- chain: &'g C,
- chain_tip: BlockId,
- params: CanonicalizationParams,
- ) -> Self {
- let anchors = tx_graph.all_anchors();
- let unprocessed_assumed_txs = Box::new(
- params
- .assume_canonical
- .into_iter()
- .rev()
- .filter_map(|txid| Some((txid, tx_graph.get_tx(txid)?))),
- );
- let unprocessed_anchored_txs = Box::new(
- tx_graph
- .txids_by_descending_anchor_height()
- .filter_map(|(_, txid)| Some((txid, tx_graph.get_tx(txid)?, anchors.get(&txid)?))),
- );
- let unprocessed_seen_txs = Box::new(
- tx_graph
- .txids_by_descending_last_seen()
- .filter_map(|(last_seen, txid)| Some((txid, tx_graph.get_tx(txid)?, last_seen))),
- );
- Self {
- tx_graph,
- chain,
- chain_tip,
- unprocessed_assumed_txs,
- unprocessed_anchored_txs,
- unprocessed_seen_txs,
- unprocessed_leftover_txs: VecDeque::new(),
- canonical: HashMap::new(),
- not_canonical: HashSet::new(),
- queue: VecDeque::new(),
- }
- }
-
- /// Whether this transaction is already canonicalized.
- fn is_canonicalized(&self, txid: Txid) -> bool {
- self.canonical.contains_key(&txid) || self.not_canonical.contains(&txid)
- }
-
- /// Mark transaction as canonical if it is anchored in the best chain.
- fn scan_anchors(
- &mut self,
- txid: Txid,
- tx: Arc<Transaction>,
- anchors: &BTreeSet<A>,
- ) -> Result<(), C::Error> {
- for anchor in anchors {
- let in_chain_opt = self
- .chain
- .is_block_in_chain(anchor.anchor_block(), self.chain_tip)?;
- if in_chain_opt == Some(true) {
- self.mark_canonical(txid, tx, CanonicalReason::from_anchor(anchor.clone()));
- return Ok(());
- }
- }
- // cannot determine
- self.unprocessed_leftover_txs.push_back((
- txid,
- tx,
- anchors
- .iter()
- .last()
- .expect(
- "tx taken from `unprocessed_txs_with_anchors` so it must atleast have an anchor",
- )
- .confirmation_height_upper_bound(),
- ));
- Ok(())
- }
-
- /// Marks `tx` and it's ancestors as canonical and mark all conflicts of these as
- /// `not_canonical`.
- ///
- /// The exception is when it is discovered that `tx` double spends itself (i.e. two of it's
- /// inputs conflict with each other), then no changes will be made.
- ///
- /// The logic works by having two loops where one is nested in another.
- /// * The outer loop iterates through ancestors of `tx` (including `tx`). We can transitively
- /// assume that all ancestors of `tx` are also canonical.
- /// * The inner loop loops through conflicts of ancestors of `tx`. Any descendants of conflicts
- /// are also conflicts and are transitively considered non-canonical.
- ///
- /// If the inner loop ends up marking `tx` as non-canonical, then we know that it double spends
- /// itself.
- fn mark_canonical(&mut self, txid: Txid, tx: Arc<Transaction>, reason: CanonicalReason<A>) {
- let starting_txid = txid;
- let mut is_starting_tx = true;
-
- // We keep track of changes made so far so that we can undo it later in case we detect that
- // `tx` double spends itself.
- let mut detected_self_double_spend = false;
- let mut undo_not_canonical = Vec::<Txid>::new();
-
- // `staged_queue` doubles as the `undo_canonical` data.
- let staged_queue = TxAncestors::new_include_root(
- self.tx_graph,
- tx,
- |_: usize, tx: Arc<Transaction>| -> Option<Txid> {
- let this_txid = tx.compute_txid();
- let this_reason = if is_starting_tx {
- is_starting_tx = false;
- reason.clone()
- } else {
- reason.to_transitive(starting_txid)
- };
-
- use crate::collections::hash_map::Entry;
- let canonical_entry = match self.canonical.entry(this_txid) {
- // Already visited tx before, exit early.
- Entry::Occupied(_) => return None,
- Entry::Vacant(entry) => entry,
- };
-
- // Any conflicts with a canonical tx can be added to `not_canonical`. Descendants
- // of `not_canonical` txs can also be added to `not_canonical`.
- for (_, conflict_txid) in self.tx_graph.direct_conflicts(&tx) {
- TxDescendants::new_include_root(
- self.tx_graph,
- conflict_txid,
- |_: usize, txid: Txid| -> Option<()> {
- if self.not_canonical.insert(txid) {
- undo_not_canonical.push(txid);
- Some(())
- } else {
- None
- }
- },
- )
- .run_until_finished()
- }
-
- if self.not_canonical.contains(&this_txid) {
- // Early exit if self-double-spend is detected.
- detected_self_double_spend = true;
- return None;
- }
- canonical_entry.insert((tx, this_reason));
- Some(this_txid)
- },
- )
- .collect::<Vec<Txid>>();
-
- if detected_self_double_spend {
- for txid in staged_queue {
- self.canonical.remove(&txid);
- }
- for txid in undo_not_canonical {
- self.not_canonical.remove(&txid);
- }
- } else {
- self.queue.extend(staged_queue);
- }
- }
-}
-
-impl<A: Anchor, C: ChainOracle> Iterator for CanonicalIter<'_, A, C> {
- type Item = Result<(Txid, Arc<Transaction>, CanonicalReason<A>), C::Error>;
-
- fn next(&mut self) -> Option<Self::Item> {
- loop {
- if let Some(txid) = self.queue.pop_front() {
- let (tx, reason) = self
- .canonical
- .get(&txid)
- .cloned()
- .expect("reason must exist");
- return Some(Ok((txid, tx, reason)));
- }
-
- if let Some((txid, tx)) = self.unprocessed_assumed_txs.next() {
- if !self.is_canonicalized(txid) {
- self.mark_canonical(txid, tx, CanonicalReason::assumed());
- }
- }
-
- if let Some((txid, tx, anchors)) = self.unprocessed_anchored_txs.next() {
- if !self.is_canonicalized(txid) {
- if let Err(err) = self.scan_anchors(txid, tx, anchors) {
- return Some(Err(err));
- }
- }
- continue;
- }
-
- if let Some((txid, tx, last_seen)) = self.unprocessed_seen_txs.next() {
- debug_assert!(
- !tx.is_coinbase(),
- "Coinbase txs must not have `last_seen` (in mempool) value"
- );
- if !self.is_canonicalized(txid) {
- let observed_in = ObservedIn::Mempool(last_seen);
- self.mark_canonical(txid, tx, CanonicalReason::from_observed_in(observed_in));
- }
- continue;
- }
-
- if let Some((txid, tx, height)) = self.unprocessed_leftover_txs.pop_front() {
- if !self.is_canonicalized(txid) && !tx.is_coinbase() {
- let observed_in = ObservedIn::Block(height);
- self.mark_canonical(txid, tx, CanonicalReason::from_observed_in(observed_in));
- }
- continue;
- }
-
- return None;
- }
- }
-}
}
}
- CanonicalView::from_parts(self.chain_tip, view_order, view_txs, view_spends)
+ CanonicalView::new(self.chain_tip, view_order, view_txs, view_spends)
}
}
//! ## Example
//!
//! ```
-//! # use bdk_chain::{CanonicalView, TxGraph, CanonicalizationParams, local_chain::LocalChain};
+//! # use bdk_chain::{TxGraph, CanonicalizationParams, CanonicalizationTask, local_chain::LocalChain};
//! # use bdk_core::BlockId;
//! # use bitcoin::hashes::Hash;
//! # let tx_graph = TxGraph::<BlockId>::default();
//! # let chain = LocalChain::from_blocks([(0, bitcoin::BlockHash::all_zeros())].into_iter().collect()).unwrap();
-//! # let chain_tip = chain.tip().block_id();
+//! let chain_tip = chain.tip().block_id();
//! let params = CanonicalizationParams::default();
-//! let view = CanonicalView::new(&tx_graph, &chain, chain_tip, params).unwrap();
+//! let task = CanonicalizationTask::new(&tx_graph, chain_tip, params);
+//! let view = chain.canonicalize(task);
//!
//! // Iterate over canonical transactions
//! for tx in view.txs() {
use bdk_core::BlockId;
use bitcoin::{Amount, OutPoint, ScriptBuf, Transaction, Txid};
-use crate::{
- spk_txout::SpkTxOutIndex, tx_graph::TxNode, Anchor, Balance, CanonicalIter, CanonicalReason,
- CanonicalizationParams, ChainOracle, ChainPosition, FullTxOut, ObservedIn, TxGraph,
-};
+use crate::{spk_txout::SpkTxOutIndex, Anchor, Balance, ChainPosition, FullTxOut};
/// A single canonical transaction with its chain position.
///
/// after completing the canonicalization process. It takes the processed transaction
/// data including the canonical ordering, transaction map with chain positions, and
/// spend information.
- pub(crate) fn from_parts(
+ pub(crate) fn new(
tip: BlockId,
order: Vec<Txid>,
txs: HashMap<Txid, (Arc<Transaction>, ChainPosition<A>)>,
}
}
- /// Create a new canonical view from a transaction graph.
- ///
- /// This constructor analyzes the given [`TxGraph`] and creates a canonical view of all
- /// transactions, resolving conflicts and ordering them according to their chain position.
- ///
- /// # Returns
- ///
- /// Returns `Ok(CanonicalView)` on success, or an error if the chain oracle fails.
- pub fn new<'g, C>(
- tx_graph: &'g TxGraph<A>,
- chain: &'g C,
- chain_tip: BlockId,
- params: CanonicalizationParams,
- ) -> Result<Self, C::Error>
- where
- C: ChainOracle,
- {
- fn find_direct_anchor<A: Anchor, C: ChainOracle>(
- tx_node: &TxNode<'_, Arc<Transaction>, A>,
- chain: &C,
- chain_tip: BlockId,
- ) -> Result<Option<A>, C::Error> {
- tx_node
- .anchors
- .iter()
- .find_map(|a| -> Option<Result<A, C::Error>> {
- match chain.is_block_in_chain(a.anchor_block(), chain_tip) {
- Ok(Some(true)) => Some(Ok(a.clone())),
- Ok(Some(false)) | Ok(None) => None,
- Err(err) => Some(Err(err)),
- }
- })
- .transpose()
- }
-
- let mut view = Self {
- tip: chain_tip,
- order: vec![],
- txs: HashMap::new(),
- spends: HashMap::new(),
- };
-
- for r in CanonicalIter::new(tx_graph, chain, chain_tip, params) {
- let (txid, tx, why) = r?;
-
- let tx_node = match tx_graph.get_tx_node(txid) {
- Some(tx_node) => tx_node,
- None => {
- // TODO: Have the `CanonicalIter` return `TxNode`s.
- debug_assert!(false, "tx node must exist!");
- continue;
- }
- };
-
- view.order.push(txid);
-
- if !tx.is_coinbase() {
- view.spends
- .extend(tx.input.iter().map(|txin| (txin.previous_output, txid)));
- }
-
- let pos = match why {
- CanonicalReason::Assumed { descendant } => match descendant {
- Some(_) => match find_direct_anchor(&tx_node, chain, chain_tip)? {
- Some(anchor) => ChainPosition::Confirmed {
- anchor,
- transitively: None,
- },
- None => ChainPosition::Unconfirmed {
- first_seen: tx_node.first_seen,
- last_seen: tx_node.last_seen,
- },
- },
- None => ChainPosition::Unconfirmed {
- first_seen: tx_node.first_seen,
- last_seen: tx_node.last_seen,
- },
- },
- CanonicalReason::Anchor { anchor, descendant } => match descendant {
- Some(_) => match find_direct_anchor(&tx_node, chain, chain_tip)? {
- Some(anchor) => ChainPosition::Confirmed {
- anchor,
- transitively: None,
- },
- None => ChainPosition::Confirmed {
- anchor,
- transitively: descendant,
- },
- },
- None => ChainPosition::Confirmed {
- anchor,
- transitively: None,
- },
- },
- CanonicalReason::ObservedIn { observed_in, .. } => match observed_in {
- ObservedIn::Mempool(last_seen) => ChainPosition::Unconfirmed {
- first_seen: tx_node.first_seen,
- last_seen: Some(last_seen),
- },
- ObservedIn::Block(_) => ChainPosition::Unconfirmed {
- first_seen: tx_node.first_seen,
- last_seen: None,
- },
- },
- };
- view.txs.insert(txid, (tx_node.tx, pos));
- }
-
- Ok(view)
- }
-
/// Get a single canonical transaction by its transaction ID.
///
/// Returns `Some(CanonicalViewTx)` if the transaction exists in the canonical view,
/// # Example
///
/// ```
- /// # use bdk_chain::{CanonicalView, TxGraph, local_chain::LocalChain};
+ /// # use bdk_chain::{TxGraph, CanonicalizationTask, local_chain::LocalChain};
/// # use bdk_core::BlockId;
/// # use bitcoin::hashes::Hash;
/// # let tx_graph = TxGraph::<BlockId>::default();
/// # let chain = LocalChain::from_blocks([(0, bitcoin::BlockHash::all_zeros())].into_iter().collect()).unwrap();
- /// # let view = CanonicalView::new(&tx_graph, &chain, chain.tip().block_id(), Default::default()).unwrap();
+ /// # let chain_tip = chain.tip().block_id();
+ /// # let task = CanonicalizationTask::new(&tx_graph, chain_tip, Default::default());
+ /// # let view = chain.canonicalize(task);
/// // Iterate over all canonical transactions
/// for tx in view.txs() {
/// println!("TX {}: {:?}", tx.txid, tx.pos);
/// # Example
///
/// ```
- /// # use bdk_chain::{CanonicalView, TxGraph, local_chain::LocalChain, keychain_txout::KeychainTxOutIndex};
+ /// # use bdk_chain::{TxGraph, CanonicalizationTask, local_chain::LocalChain, keychain_txout::KeychainTxOutIndex};
/// # use bdk_core::BlockId;
/// # use bitcoin::hashes::Hash;
/// # let tx_graph = TxGraph::<BlockId>::default();
/// # let chain = LocalChain::from_blocks([(0, bitcoin::BlockHash::all_zeros())].into_iter().collect()).unwrap();
- /// # let view = CanonicalView::new(&tx_graph, &chain, chain.tip().block_id(), Default::default()).unwrap();
+ /// # let chain_tip = chain.tip().block_id();
+ /// # let task = CanonicalizationTask::new(&tx_graph, chain_tip, Default::default());
+ /// # let view = chain.canonicalize(task);
/// # let indexer = KeychainTxOutIndex::<&str>::default();
/// // Get all outputs from an indexer
/// for (keychain, txout) in view.filter_outpoints(indexer.outpoints().clone()) {
/// # Example
///
/// ```
- /// # use bdk_chain::{CanonicalView, TxGraph, local_chain::LocalChain, keychain_txout::KeychainTxOutIndex};
+ /// # use bdk_chain::{TxGraph, CanonicalizationTask, local_chain::LocalChain, keychain_txout::KeychainTxOutIndex};
/// # use bdk_core::BlockId;
/// # use bitcoin::hashes::Hash;
/// # let tx_graph = TxGraph::<BlockId>::default();
/// # let chain = LocalChain::from_blocks([(0, bitcoin::BlockHash::all_zeros())].into_iter().collect()).unwrap();
- /// # let view = CanonicalView::new(&tx_graph, &chain, chain.tip().block_id(), Default::default()).unwrap();
+ /// # let chain_tip = chain.tip().block_id();
+ /// # let task = CanonicalizationTask::new(&tx_graph, chain_tip, Default::default());
+ /// # let view = chain.canonicalize(task);
/// # let indexer = KeychainTxOutIndex::<&str>::default();
/// // Get unspent outputs (UTXOs) from an indexer
/// for (keychain, utxo) in view.filter_unspent_outpoints(indexer.outpoints().clone()) {
/// # Example
///
/// ```
- /// # use bdk_chain::{CanonicalView, TxGraph, local_chain::LocalChain, keychain_txout::KeychainTxOutIndex};
+ /// # use bdk_chain::{CanonicalizationTask, TxGraph, local_chain::LocalChain, keychain_txout::KeychainTxOutIndex};
/// # use bdk_core::BlockId;
/// # use bitcoin::hashes::Hash;
/// # let tx_graph = TxGraph::<BlockId>::default();
/// # let chain = LocalChain::from_blocks([(0, bitcoin::BlockHash::all_zeros())].into_iter().collect()).unwrap();
- /// # let view = CanonicalView::new(&tx_graph, &chain, chain.tip().block_id(), Default::default()).unwrap();
+ /// # let chain_tip = chain.tip().block_id();
+ /// # let task = CanonicalizationTask::new(&tx_graph, chain_tip, Default::default());
+ /// # let view = chain.canonicalize(task);
/// # let indexer = KeychainTxOutIndex::<&str>::default();
/// // Calculate balance with 6 confirmations, trusting all outputs
/// let balance = view.balance(
//! Contains the [`IndexedTxGraph`] and associated types. Refer to the
//! [`IndexedTxGraph`] documentation for more.
-use core::{convert::Infallible, fmt::Debug};
+use core::fmt::Debug;
use alloc::{sync::Arc, vec::Vec};
use bitcoin::{Block, OutPoint, Transaction, TxOut, Txid};
use crate::{
tx_graph::{self, TxGraph},
- Anchor, BlockId, CanonicalView, CanonicalizationParams, CanonicalizationTask, ChainOracle,
- Indexer, Merge, TxPosInBlock,
+ Anchor, BlockId, CanonicalizationParams, CanonicalizationTask, Indexer, Merge, TxPosInBlock,
};
/// A [`TxGraph<A>`] paired with an indexer `I`, enforcing that every insertion into the graph is
}
}
+impl<A, I> AsRef<TxGraph<A>> for IndexedTxGraph<A, I> {
+ fn as_ref(&self) -> &TxGraph<A> {
+ &self.graph
+ }
+}
+
impl<A, X> IndexedTxGraph<A, X>
where
A: Anchor,
{
- /// Returns a [`CanonicalView`].
- pub fn try_canonical_view<'a, C: ChainOracle>(
- &'a self,
- chain: &'a C,
- chain_tip: BlockId,
- params: CanonicalizationParams,
- ) -> Result<CanonicalView<A>, C::Error> {
- self.graph.try_canonical_view(chain, chain_tip, params)
- }
-
- /// Returns a [`CanonicalView`].
- ///
- /// This is the infallible version of [`try_canonical_view`](Self::try_canonical_view).
- pub fn canonical_view<'a, C: ChainOracle<Error = Infallible>>(
- &'a self,
- chain: &'a C,
- chain_tip: BlockId,
- params: CanonicalizationParams,
- ) -> CanonicalView<A> {
- self.graph.canonical_view(chain, chain_tip, params)
- }
-
/// Creates a [`CanonicalizationTask`] to determine the [`CanonicalView`] of transactions.
///
/// This method delegates to the underlying [`TxGraph`] to create a [`CanonicalizationTask`]
}
}
-impl<A, I> AsRef<TxGraph<A>> for IndexedTxGraph<A, I> {
- fn as_ref(&self) -> &TxGraph<A> {
- &self.graph
- }
-}
-
/// Represents changes to an [`IndexedTxGraph`].
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(
pub use tx_graph::TxGraph;
mod chain_oracle;
pub use chain_oracle::*;
-mod canonical_iter;
-pub use canonical_iter::*;
mod canonical_task;
pub use canonical_task::*;
mod canonical_view;
//! [`insert_txout`]: TxGraph::insert_txout
use crate::collections::*;
-use crate::BlockId;
-use crate::CanonicalIter;
-use crate::CanonicalView;
use crate::CanonicalizationParams;
use crate::CanonicalizationTask;
-use crate::{Anchor, ChainOracle, Merge};
+use crate::{Anchor, BlockId, Merge};
use alloc::collections::vec_deque::VecDeque;
use alloc::sync::Arc;
use alloc::vec::Vec;
pub use bdk_core::TxUpdate;
use bitcoin::{Amount, OutPoint, SignedAmount, Transaction, TxOut, Txid};
use core::fmt::{self, Formatter};
-use core::{
- convert::Infallible,
- ops::{Deref, RangeInclusive},
-};
+use core::ops::{Deref, RangeInclusive};
impl<A: Ord> From<TxGraph<A>> for TxUpdate<A> {
fn from(graph: TxGraph<A>) -> Self {
})
}
- /// Returns a [`CanonicalIter`].
- pub fn canonical_iter<'a, C: ChainOracle>(
- &'a self,
- chain: &'a C,
- chain_tip: BlockId,
- params: CanonicalizationParams,
- ) -> CanonicalIter<'a, A, C> {
- CanonicalIter::new(self, chain, chain_tip, params)
- }
-
- /// Returns a [`CanonicalView`].
- pub fn try_canonical_view<'a, C: ChainOracle>(
- &'a self,
- chain: &'a C,
- chain_tip: BlockId,
- params: CanonicalizationParams,
- ) -> Result<CanonicalView<A>, C::Error> {
- CanonicalView::new(self, chain, chain_tip, params)
- }
-
- /// Returns a [`CanonicalView`].
- pub fn canonical_view<'a, C: ChainOracle<Error = Infallible>>(
- &'a self,
- chain: &'a C,
- chain_tip: BlockId,
- params: CanonicalizationParams,
- ) -> CanonicalView<A> {
- CanonicalView::new(self, chain, chain_tip, params).expect("infallible")
- }
-
/// Construct a `TxGraph` from a `changeset`.
pub fn from_changeset(changeset: ChangeSet<A>) -> Self {
let mut graph = Self::default();