From 0adff9c35f109a15740212b4dce74c03950c85ed Mon Sep 17 00:00:00 2001 From: Daniela Brozzoni Date: Tue, 17 Oct 2023 11:00:05 +0200 Subject: [PATCH] doc: Improve TxGraph & co docs --- crates/bdk/src/wallet/mod.rs | 4 +- crates/bdk/src/wallet/signer.rs | 2 +- crates/bitcoind_rpc/src/lib.rs | 2 +- crates/chain/src/chain_data.rs | 4 ++ crates/chain/src/chain_oracle.rs | 2 +- crates/chain/src/indexed_tx_graph.rs | 12 ++-- crates/chain/src/lib.rs | 5 +- crates/chain/src/local_chain.rs | 6 +- crates/chain/src/tx_data_traits.rs | 32 +++++++-- crates/chain/src/tx_graph.rs | 67 ++++++++++++++----- crates/chain/tests/test_tx_graph_conflicts.rs | 1 + 11 files changed, 99 insertions(+), 38 deletions(-) diff --git a/crates/bdk/src/wallet/mod.rs b/crates/bdk/src/wallet/mod.rs index 4f0687690..e945484dd 100644 --- a/crates/bdk/src/wallet/mod.rs +++ b/crates/bdk/src/wallet/mod.rs @@ -11,7 +11,7 @@ //! Wallet //! -//! This module defines the [`Wallet`] structure. +//! This module defines the [`Wallet`]. use crate::collections::{BTreeMap, HashMap, HashSet}; use alloc::{ boxed::Box, @@ -77,7 +77,7 @@ const COINBASE_MATURITY: u32 = 100; /// A Bitcoin wallet /// -/// The `Wallet` struct acts as a way of coherently interfacing with output descriptors and related transactions. +/// The `Wallet` acts as a way of coherently interfacing with output descriptors and related transactions. /// Its main components are: /// /// 1. output *descriptors* from which it can derive addresses. diff --git a/crates/bdk/src/wallet/signer.rs b/crates/bdk/src/wallet/signer.rs index e1e003c61..da4940bf9 100644 --- a/crates/bdk/src/wallet/signer.rs +++ b/crates/bdk/src/wallet/signer.rs @@ -221,7 +221,7 @@ pub enum SignerContext { }, } -/// Wrapper structure to pair a signer with its context +/// Wrapper to pair a signer with its context #[derive(Debug, Clone)] pub struct SignerWrapper { signer: S, diff --git a/crates/bitcoind_rpc/src/lib.rs b/crates/bitcoind_rpc/src/lib.rs index 0fafbc767..e790b8a8e 100644 --- a/crates/bitcoind_rpc/src/lib.rs +++ b/crates/bitcoind_rpc/src/lib.rs @@ -14,7 +14,7 @@ use bitcoin::{block::Header, Block, BlockHash, Transaction}; pub use bitcoincore_rpc; use bitcoincore_rpc::bitcoincore_rpc_json; -/// A structure that emits data sourced from [`bitcoincore_rpc::Client`]. +/// The [`Emitter`] is used to emit data sourced from [`bitcoincore_rpc::Client`]. /// /// Refer to [module-level documentation] for more. /// diff --git a/crates/chain/src/chain_data.rs b/crates/chain/src/chain_data.rs index 4da644965..c32d3e1c9 100644 --- a/crates/chain/src/chain_data.rs +++ b/crates/chain/src/chain_data.rs @@ -147,6 +147,8 @@ impl From<(&u32, &BlockHash)> for BlockId { /// An [`Anchor`] implementation that also records the exact confirmation height of the transaction. /// +/// Note that the confirmation block and the anchor block can be different here. +/// /// Refer to [`Anchor`] for more details. #[derive(Debug, Default, Clone, PartialEq, Eq, Copy, PartialOrd, Ord, core::hash::Hash)] #[cfg_attr( @@ -186,6 +188,8 @@ impl AnchorFromBlockPosition for ConfirmationHeightAnchor { /// An [`Anchor`] implementation that also records the exact confirmation time and height of the /// transaction. /// +/// Note that the confirmation block and the anchor block can be different here. +/// /// Refer to [`Anchor`] for more details. #[derive(Debug, Default, Clone, PartialEq, Eq, Copy, PartialOrd, Ord, core::hash::Hash)] #[cfg_attr( diff --git a/crates/chain/src/chain_oracle.rs b/crates/chain/src/chain_oracle.rs index 038025619..08e697ed4 100644 --- a/crates/chain/src/chain_oracle.rs +++ b/crates/chain/src/chain_oracle.rs @@ -3,7 +3,7 @@ use crate::BlockId; /// Represents a service that tracks the blockchain. /// /// The main method is [`is_block_in_chain`] which determines whether a given block of [`BlockId`] -/// is an ancestor of another "static block". +/// is an ancestor of the `chain_tip`. /// /// [`is_block_in_chain`]: Self::is_block_in_chain pub trait ChainOracle { diff --git a/crates/chain/src/indexed_tx_graph.rs b/crates/chain/src/indexed_tx_graph.rs index 68e7846b6..777b5d978 100644 --- a/crates/chain/src/indexed_tx_graph.rs +++ b/crates/chain/src/indexed_tx_graph.rs @@ -1,7 +1,5 @@ -//! Contains the [`IndexedTxGraph`] structure and associated types. -//! -//! This is essentially a [`TxGraph`] combined with an indexer. - +//! Contains the [`IndexedTxGraph`] and associated types. Refer to the +//! [`IndexedTxGraph`] documentation for more. use alloc::vec::Vec; use bitcoin::{Block, OutPoint, Transaction, TxOut, Txid}; @@ -11,9 +9,9 @@ use crate::{ Anchor, AnchorFromBlockPosition, Append, BlockId, }; -/// A struct that combines [`TxGraph`] and an [`Indexer`] implementation. +/// The [`IndexedTxGraph`] combines a [`TxGraph`] and an [`Indexer`] implementation. /// -/// This structure ensures that [`TxGraph`] and [`Indexer`] are updated atomically. +/// It ensures that [`TxGraph`] and [`Indexer`] are updated atomically. #[derive(Debug)] pub struct IndexedTxGraph { /// Transaction index. @@ -266,7 +264,7 @@ where } } -/// A structure that represents changes to an [`IndexedTxGraph`]. +/// Represents changes to an [`IndexedTxGraph`]. #[derive(Clone, Debug, PartialEq)] #[cfg_attr( feature = "serde", diff --git a/crates/chain/src/lib.rs b/crates/chain/src/lib.rs index ed167ebf6..04ca62c4c 100644 --- a/crates/chain/src/lib.rs +++ b/crates/chain/src/lib.rs @@ -12,9 +12,8 @@ //! you do it synchronously or asynchronously. If you know a fact about the blockchain, you can just //! tell `bdk_chain`'s APIs about it, and that information will be integrated, if it can be done //! consistently. -//! 2. Error-free APIs. -//! 3. Data persistence agnostic -- `bdk_chain` does not care where you cache on-chain data, what you -//! cache or how you fetch it. +//! 2. Data persistence agnostic -- `bdk_chain` does not care where you cache on-chain data, what you +//! cache or how you retrieve it from persistent storage. //! //! [Bitcoin Dev Kit]: https://bitcoindevkit.org/ diff --git a/crates/chain/src/local_chain.rs b/crates/chain/src/local_chain.rs index 1538cdfda..bdd25d8e0 100644 --- a/crates/chain/src/local_chain.rs +++ b/crates/chain/src/local_chain.rs @@ -7,7 +7,7 @@ use crate::{BlockId, ChainOracle}; use alloc::sync::Arc; use bitcoin::BlockHash; -/// A structure that represents changes to [`LocalChain`]. +/// The [`ChangeSet`] represents changes to [`LocalChain`]. /// /// The key represents the block height, and the value either represents added a new [`CheckPoint`] /// (if [`Some`]), or removing a [`CheckPoint`] (if [`None`]). @@ -127,7 +127,7 @@ impl CheckPoint { } } -/// A structure that iterates over checkpoints backwards. +/// Iterates over checkpoints backwards. pub struct CheckPointIter { current: Option>, } @@ -153,7 +153,7 @@ impl IntoIterator for CheckPoint { } } -/// A struct to update [`LocalChain`]. +/// Used to update [`LocalChain`]. /// /// This is used as input for [`LocalChain::apply_update`]. It contains the update's chain `tip` and /// a flag `introduce_older_blocks` which signals whether this update intends to introduce missing diff --git a/crates/chain/src/tx_data_traits.rs b/crates/chain/src/tx_data_traits.rs index c957a3e57..5c854a956 100644 --- a/crates/chain/src/tx_data_traits.rs +++ b/crates/chain/src/tx_data_traits.rs @@ -5,21 +5,25 @@ use alloc::vec::Vec; /// Trait that "anchors" blockchain data to a specific block of height and hash. /// -/// [`Anchor`] implementations must be [`Ord`] by the anchor block's [`BlockId`] first. -/// -/// I.e. If transaction A is anchored in block B, then if block B is in the best chain, we can +/// If transaction A is anchored in block B, and block B is in the best chain, we can /// 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. /// +/// Every [`Anchor`] implementation must contain a [`BlockId`] parameter, and must implement +/// [`Ord`]. When implementing [`Ord`], the anchors' [`BlockId`]s should take precedence +/// over other elements inside the [`Anchor`]s for comparison purposes, i.e., you should first +/// compare the anchors' [`BlockId`]s and then care about the rest. +/// +/// The example shows different types of anchors: /// ``` /// # use bdk_chain::local_chain::LocalChain; /// # use bdk_chain::tx_graph::TxGraph; /// # use bdk_chain::BlockId; /// # use bdk_chain::ConfirmationHeightAnchor; +/// # use bdk_chain::ConfirmationTimeHeightAnchor; /// # use bdk_chain::example_utils::*; /// # use bitcoin::hashes::Hash; -/// /// // Initialize the local chain with two blocks. /// let chain = LocalChain::from_blocks( /// [ @@ -47,6 +51,7 @@ use alloc::vec::Vec; /// ); /// /// // Insert `tx` into a `TxGraph` that uses `ConfirmationHeightAnchor` as the anchor type. +/// // This anchor records the anchor block and the confirmation height of the transaction. /// // When a transaction is anchored with `ConfirmationHeightAnchor`, the anchor block and /// // confirmation block can be different. However, the confirmation block cannot be higher than /// // the anchor block and both blocks must be in the same chain for the anchor to be valid. @@ -62,6 +67,25 @@ use alloc::vec::Vec; /// confirmation_height: 1, /// }, /// ); +/// +/// // Insert `tx` into a `TxGraph` that uses `ConfirmationTimeHeightAnchor` as the anchor type. +/// // This anchor records the anchor block, the confirmation height and time of the transaction. +/// // When a transaction is anchored with `ConfirmationTimeHeightAnchor`, the anchor block and +/// // confirmation block can be different. However, the confirmation block cannot be higher than +/// // the anchor block and both blocks must be in the same chain for the anchor to be valid. +/// let mut graph_c = TxGraph::::default(); +/// let _ = graph_c.insert_tx(tx.clone()); +/// graph_c.insert_anchor( +/// tx.txid(), +/// ConfirmationTimeHeightAnchor { +/// anchor_block: BlockId { +/// height: 2, +/// hash: Hash::hash("third".as_bytes()), +/// }, +/// confirmation_height: 1, +/// confirmation_time: 123, +/// }, +/// ); /// ``` pub trait Anchor: core::fmt::Debug + Clone + Eq + PartialOrd + Ord + core::hash::Hash { /// Returns the [`BlockId`] that the associated blockchain data is "anchored" in. diff --git a/crates/chain/src/tx_graph.rs b/crates/chain/src/tx_graph.rs index f84c3a3dc..10cf10446 100644 --- a/crates/chain/src/tx_graph.rs +++ b/crates/chain/src/tx_graph.rs @@ -1,12 +1,32 @@ //! Module for structures that store and traverse transactions. //! -//! [`TxGraph`] is a monotone structure that inserts transactions and indexes the spends. The -//! [`ChangeSet`] structure reports changes of [`TxGraph`] but can also be applied to a -//! [`TxGraph`] as well. Lastly, [`TxDescendants`] is an [`Iterator`] that traverses descendants of -//! a given transaction. +//! [`TxGraph`] contains transactions and indexes them so you can easily traverse the graph of those transactions. +//! `TxGraph` is *monotone* in that you can always insert a transaction -- it doesn't care whether that +//! transaction is in the current best chain or whether it conflicts with any of the +//! existing transactions or what order you insert the transactions. This means that you can always +//! combine two [`TxGraph`]s together, without resulting in inconsistencies. +//! Furthermore, there is currently no way to delete a transaction. +//! +//! Transactions can be either whole or partial (i.e., transactions for which we only +//! know some outputs, which we usually call "floating outputs"; these are usually inserted +//! using the [`insert_txout`] method.). +//! +//! The graph contains transactions in the form of [`TxNode`]s. Each node contains the +//! txid, the transaction (whole or partial), the blocks it's anchored in (see the [`Anchor`] +//! documentation for more details), and the timestamp of the last time we saw +//! the transaction as unconfirmed. //! //! Conflicting transactions are allowed to coexist within a [`TxGraph`]. This is useful for -//! identifying and traversing conflicts and descendants of a given transaction. +//! identifying and traversing conflicts and descendants of a given transaction. Some [`TxGraph`] +//! methods only consider "canonical" (i.e., in the best chain or in mempool) transactions, +//! we decide which transactions are canonical based on anchors `last_seen_unconfirmed`; +//! see the [`try_get_chain_position`] documentation for more details. +//! +//! 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 //! @@ -49,6 +69,8 @@ //! 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, local_chain::LocalChain, Anchor, Append, BlockId, @@ -91,7 +113,7 @@ impl Default for TxGraph { } } -/// An outward-facing view of a (transaction) node in the [`TxGraph`]. +/// A transaction node in the [`TxGraph`]. #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct TxNode<'a, T, A> { /// Txid of the transaction. @@ -128,7 +150,7 @@ impl Default for TxNodeInternal { } } -/// An outwards-facing view of a transaction that is part of the *best chain*'s history. +/// 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). @@ -475,7 +497,7 @@ impl TxGraph { /// 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 the mempool which is used for + /// *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, @@ -708,8 +730,20 @@ impl TxGraph { /// 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. + /// 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 /// @@ -735,7 +769,7 @@ impl TxGraph { } } - // The tx is not anchored to a block which is in the best chain, which means that it + // The tx is not anchored to a block in the best chain, which means that it // might be in mempool, or it might have been dropped already. // Let's check conflicts to find out! let tx = match tx_node { @@ -945,7 +979,8 @@ impl TxGraph { /// (`OI`) for convenience. If `OI` is not necessary, the caller can use `()`, or /// [`Iterator::enumerate`] over a list of [`OutPoint`]s. /// - /// Floating outputs are ignored. + /// Floating outputs (i.e., outputs for which we don't have the full transaction in the graph) + /// are ignored. /// /// # Error /// @@ -1136,9 +1171,9 @@ impl TxGraph { } } -/// A structure that represents changes to a [`TxGraph`]. +/// The [`ChangeSet`] represents changes to a [`TxGraph`]. /// -/// Since [`TxGraph`] is monotone "changeset" can only contain transactions to be added and +/// Since [`TxGraph`] is monotone, the "changeset" can only contain transactions to be added and /// not removed. /// /// Refer to [module-level documentation] for more. @@ -1272,7 +1307,7 @@ impl AsRef> for TxGraph { /// /// The iterator excludes partial transactions. /// -/// This `struct` is created by the [`walk_ancestors`] method of [`TxGraph`]. +/// Returned by the [`walk_ancestors`] method of [`TxGraph`]. /// /// [`walk_ancestors`]: TxGraph::walk_ancestors pub struct TxAncestors<'g, A, F> { @@ -1390,7 +1425,7 @@ where /// An iterator that traverses transaction descendants. /// -/// This `struct` is created by the [`walk_descendants`] method of [`TxGraph`]. +/// Returned by the [`walk_descendants`] method of [`TxGraph`]. /// /// [`walk_descendants`]: TxGraph::walk_descendants pub struct TxDescendants<'g, A, F> { diff --git a/crates/chain/tests/test_tx_graph_conflicts.rs b/crates/chain/tests/test_tx_graph_conflicts.rs index f2a161b43..8ac440f3e 100644 --- a/crates/chain/tests/test_tx_graph_conflicts.rs +++ b/crates/chain/tests/test_tx_graph_conflicts.rs @@ -110,6 +110,7 @@ fn test_tx_conflict_handling() { ..Default::default() }, ], + // the txgraph is going to pick tx_conflict_2 because of higher lexicographical txid exp_chain_txs: HashSet::from(["tx1", "tx_conflict_2"]), exp_chain_txouts: HashSet::from([("tx1", 0), ("tx_conflict_2", 0)]), exp_unspents: HashSet::from([("tx_conflict_2", 0)]),