Skip to content

Commit 4667dca

Browse files
doc: Improve TxGraph & co docs
1 parent 0a7b60f commit 4667dca

File tree

7 files changed

+92
-28
lines changed

7 files changed

+92
-28
lines changed

crates/chain/src/chain_data.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,8 @@ impl From<(&u32, &BlockHash)> for BlockId {
147147

148148
/// An [`Anchor`] implementation that also records the exact confirmation height of the transaction.
149149
///
150+
/// Note that the confirmation block and the anchor block can be different here.
151+
///
150152
/// Refer to [`Anchor`] for more details.
151153
#[derive(Debug, Default, Clone, PartialEq, Eq, Copy, PartialOrd, Ord, core::hash::Hash)]
152154
#[cfg_attr(
@@ -186,6 +188,8 @@ impl AnchorFromBlockPosition for ConfirmationHeightAnchor {
186188
/// An [`Anchor`] implementation that also records the exact confirmation time and height of the
187189
/// transaction.
188190
///
191+
/// Note that the confirmation block and the anchor block can be different here.
192+
///
189193
/// Refer to [`Anchor`] for more details.
190194
#[derive(Debug, Default, Clone, PartialEq, Eq, Copy, PartialOrd, Ord, core::hash::Hash)]
191195
#[cfg_attr(

crates/chain/src/chain_oracle.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use crate::BlockId;
33
/// Represents a service that tracks the blockchain.
44
///
55
/// The main method is [`is_block_in_chain`] which determines whether a given block of [`BlockId`]
6-
/// is an ancestor of another "static block".
6+
/// is an ancestor of the `chain_tip`.
77
///
88
/// [`is_block_in_chain`]: Self::is_block_in_chain
99
pub trait ChainOracle {

crates/chain/src/indexed_tx_graph.rs

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
1-
//! Contains the [`IndexedTxGraph`] structure and associated types.
2-
//!
3-
//! This is essentially a [`TxGraph`] combined with an indexer.
4-
1+
//! Contains the [`IndexedTxGraph`] structure and associated types. Refer to the
2+
//! [`IndexedTxGraph`] documentation for more.
53
use alloc::vec::Vec;
64
use bitcoin::{Block, OutPoint, Transaction, TxOut, Txid};
75

@@ -160,7 +158,7 @@ where
160158
/// Batch insert unconfirmed transactions, filtering out those that are irrelevant.
161159
///
162160
/// Relevancy is determined by the internal [`Indexer::is_tx_relevant`] implementation of `I`.
163-
/// Irrelevant tansactions in `txs` will be ignored.
161+
/// Irrelevant transactions in `txs` will be ignored.
164162
///
165163
/// Items of `txs` are tuples containing the transaction and a *last seen* timestamp. The
166164
/// *last seen* communicates when the transaction is last seen in the mempool which is used for
@@ -223,7 +221,7 @@ where
223221
/// [`AnchorFromBlockPosition::from_block_position`].
224222
///
225223
/// Relevancy is determined by the internal [`Indexer::is_tx_relevant`] implementation of `I`.
226-
/// Irrelevant tansactions in `txs` will be ignored.
224+
/// Irrelevant transactions in `txs` will be ignored.
227225
pub fn apply_block_relevant(
228226
&mut self,
229227
block: Block,

crates/chain/src/lib.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,8 @@
1212
//! you do it synchronously or asynchronously. If you know a fact about the blockchain, you can just
1313
//! tell `bdk_chain`'s APIs about it, and that information will be integrated, if it can be done
1414
//! consistently.
15-
//! 2. Error-free APIs.
16-
//! 3. Data persistence agnostic -- `bdk_chain` does not care where you cache on-chain data, what you
17-
//! cache or how you fetch it.
15+
//! 2. Data persistence agnostic -- `bdk_chain` does not care where you cache on-chain data, what you
16+
//! cache or how you retrieve it from persistent storage.
1817
//!
1918
//! [Bitcoin Dev Kit]: https://bitcoindevkit.org/
2019

crates/chain/src/tx_data_traits.rs

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,25 @@ use alloc::vec::Vec;
55

66
/// Trait that "anchors" blockchain data to a specific block of height and hash.
77
///
8-
/// [`Anchor`] implementations must be [`Ord`] by the anchor block's [`BlockId`] first.
9-
///
10-
/// I.e. If transaction A is anchored in block B, then if block B is in the best chain, we can
8+
/// If transaction A is anchored in block B, and block B is in the best chain, we can
119
/// assume that transaction A is also confirmed in the best chain. This does not necessarily mean
1210
/// that transaction A is confirmed in block B. It could also mean transaction A is confirmed in a
1311
/// parent block of B.
1412
///
13+
/// Every [`Anchor`] implementation must contain a [`BlockId`] parameter, and must implement
14+
/// [`Ord`]. When implementing [`Ord`], the anchors' [`BlockId`]s should take precedence
15+
/// over other elements inside the [`Anchor`]s for comparison purposes, i.e., you should first
16+
/// compare the anchors' [`BlockId`]s and then care about the rest.
17+
///
18+
/// The example shows different types of anchors:
1519
/// ```
1620
/// # use bdk_chain::local_chain::LocalChain;
1721
/// # use bdk_chain::tx_graph::TxGraph;
1822
/// # use bdk_chain::BlockId;
1923
/// # use bdk_chain::ConfirmationHeightAnchor;
24+
/// # use bdk_chain::ConfirmationTimeAnchor;
2025
/// # use bdk_chain::example_utils::*;
2126
/// # use bitcoin::hashes::Hash;
22-
///
2327
/// // Initialize the local chain with two blocks.
2428
/// let chain = LocalChain::from_blocks(
2529
/// [
@@ -47,6 +51,7 @@ use alloc::vec::Vec;
4751
/// );
4852
///
4953
/// // Insert `tx` into a `TxGraph` that uses `ConfirmationHeightAnchor` as the anchor type.
54+
/// // This anchor records the anchor block and the confirmation height of the transaction.
5055
/// // When a transaction is anchored with `ConfirmationHeightAnchor`, the anchor block and
5156
/// // confirmation block can be different. However, the confirmation block cannot be higher than
5257
/// // 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;
6267
/// confirmation_height: 1,
6368
/// },
6469
/// );
70+
///
71+
/// // Insert `tx` into a `TxGraph` that uses `ConfirmationTimeAnchor` as the anchor type.
72+
/// // This anchor records the anchor block, the confirmation height and time of the transaction.
73+
/// // When a transaction is anchored with `ConfirmationTimeAnchor`, the anchor block and
74+
/// // confirmation block can be different. However, the confirmation block cannot be higher than
75+
/// // the anchor block and both blocks must be in the same chain for the anchor to be valid.
76+
/// let mut graph_c = TxGraph::<ConfirmationTimeAnchor>::default();
77+
/// let _ = graph_c.insert_tx(tx.clone());
78+
/// graph_c.insert_anchor(
79+
/// tx.txid(),
80+
/// ConfirmationTimeAnchor {
81+
/// anchor_block: BlockId {
82+
/// height: 2,
83+
/// hash: Hash::hash("third".as_bytes()),
84+
/// },
85+
/// confirmation_height: 1,
86+
/// confirmation_time: 123,
87+
/// },
88+
/// );
6589
/// ```
6690
pub trait Anchor: core::fmt::Debug + Clone + Eq + PartialOrd + Ord + core::hash::Hash {
6791
/// Returns the [`BlockId`] that the associated blockchain data is "anchored" in.

crates/chain/src/tx_graph.rs

Lines changed: 52 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,32 @@
11
//! Module for structures that store and traverse transactions.
22
//!
3-
//! [`TxGraph`] is a monotone structure that inserts transactions and indexes the spends. The
4-
//! [`ChangeSet`] structure reports changes of [`TxGraph`] but can also be applied to a
5-
//! [`TxGraph`] as well. Lastly, [`TxDescendants`] is an [`Iterator`] that traverses descendants of
6-
//! a given transaction.
3+
//! [`TxGraph`] contains transactions and indexes them so you can easily traverse the graph of those transactions.
4+
//! `TxGraph` is *monotone* in that you can always insert a transaction -- it doesn't care whether that
5+
//! transaction is in the current best chain or whether it conflicts with any of the
6+
//! existing transactions or what order you insert the transactions. This means that you can always
7+
//! combine two [`TxGraph`]s together, without resulting in inconsistencies.
8+
//! Furthermore, there is currently no way to delete a transaction.
9+
//!
10+
//! Transactions can be either whole or partial (i.e., transactions for which we only
11+
//! know some outputs, which we usually call "floating outputs"; these are usually inserted
12+
//! using the [`insert_txout`] method.).
13+
//!
14+
//! The graph contains transactions in the form of [`TxNode`]s. Each node contains the
15+
//! txid, the transaction (whole or partial), the blocks it's anchored in (see the [`Anchor`]
16+
//! documentation for more details), and the timestamp of the last time we saw
17+
//! the transaction as unconfirmed.
718
//!
819
//! Conflicting transactions are allowed to coexist within a [`TxGraph`]. This is useful for
9-
//! identifying and traversing conflicts and descendants of a given transaction.
20+
//! identifying and traversing conflicts and descendants of a given transaction. Some [`TxGraph`]
21+
//! methods only consider "canonical" (i.e., in the best chain or in mempool) transactions,
22+
//! we decide which transactions are canonical based on anchors `last_seen_unconfirmed`;
23+
//! see the [`try_get_chain_position`] documentation for more details.
24+
//!
25+
//! The [`ChangeSet`] reports changes made to a [`TxGraph`]; it can be used to either save to
26+
//! persistent storage, or to be applied to another [`TxGraph`].
27+
//!
28+
//! Lastly, you can use [`TxAncestors`]/[`TxDescendants`] to traverse ancestors and descendants of
29+
//! a given transaction, respectively.
1030
//!
1131
//! # Applying changes
1232
//!
@@ -49,6 +69,8 @@
4969
//! let changeset = graph.apply_update(update);
5070
//! assert!(changeset.is_empty());
5171
//! ```
72+
//! [`try_get_chain_position`]: TxGraph::try_get_chain_position
73+
//! [`insert_txout`]: TxGraph::insert_txout
5274
5375
use crate::{
5476
collections::*, keychain::Balance, local_chain::LocalChain, Anchor, Append, BlockId,
@@ -90,7 +112,7 @@ impl<A> Default for TxGraph<A> {
90112
}
91113
}
92114

93-
/// An outward-facing view of a (transaction) node in the [`TxGraph`].
115+
/// A transaction node in the [`TxGraph`].
94116
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
95117
pub struct TxNode<'a, T, A> {
96118
/// Txid of the transaction.
@@ -127,7 +149,7 @@ impl Default for TxNodeInternal {
127149
}
128150
}
129151

130-
/// An outwards-facing view of a transaction that is part of the *best chain*'s history.
152+
/// A transaction that is included in the chain, or is still in mempool.
131153
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
132154
pub struct CanonicalTx<'a, T, A> {
133155
/// How the transaction is observed as (confirmed or unconfirmed).
@@ -454,7 +476,7 @@ impl<A: Clone + Ord> TxGraph<A> {
454476
/// Batch insert unconfirmed transactions.
455477
///
456478
/// Items of `txs` are tuples containing the transaction and a *last seen* timestamp. The
457-
/// *last seen* communicates when the transaction is last seen in the mempool which is used for
479+
/// *last seen* communicates when the transaction is last seen in mempool which is used for
458480
/// conflict-resolution (refer to [`TxGraph::insert_seen_at`] for details).
459481
pub fn batch_insert_unconfirmed(
460482
&mut self,
@@ -480,7 +502,7 @@ impl<A: Clone + Ord> TxGraph<A> {
480502

481503
/// Inserts the given `seen_at` for `txid` into [`TxGraph`].
482504
///
483-
/// Note that [`TxGraph`] only keeps track of the lastest `seen_at`.
505+
/// Note that [`TxGraph`] only keeps track of the latest `seen_at`.
484506
pub fn insert_seen_at(&mut self, txid: Txid, seen_at: u64) -> ChangeSet<A> {
485507
let mut update = Self::default();
486508
let (_, _, update_last_seen) = update.txs.entry(txid).or_default();
@@ -687,8 +709,23 @@ impl<A: Anchor> TxGraph<A> {
687709

688710
/// Get the position of the transaction in `chain` with tip `chain_tip`.
689711
///
690-
/// If the given transaction of `txid` does not exist in the chain of `chain_tip`, `None` is
691-
/// returned.
712+
/// If the given `txid` does not exist in the chain of `chain_tip`, and
713+
/// we believe that the transaction is not in mempool anymore, `None` is returned.
714+
///
715+
/// There are two factors that we use to approximate whether an unconfirmed
716+
/// transaction currently exists in mempool:
717+
/// 1. Conflicts with confirmed txs. If the transaction `tx` or any of its unconfirmed
718+
/// ancestors conflict with a confirmed transaction, then `tx` can't be in mempool.
719+
/// 2. The `last_seen_unconfirmed` parameter. Given two conflicting transactions, we say
720+
/// that the one with a higher `last_seen_unconfirmed` (i.e., we saw it later) has a higher
721+
/// chance of still being in mempool. When trying to figure out if `tx`
722+
/// is still in mempool, we first of all calculate `tx`'s `max_last_seen_unconfirmed`,
723+
/// which is the max `last_seen_unconfirmed` between `tx` and all its descendants.
724+
/// We then look at all the conflicts of `tx`, and if for all of them
725+
/// `last_seen_unconfirmed` < `max_last_seen_unconfirmed` holds,
726+
/// then we consider `tx` to be still in mempool.
727+
/// 3. In case two transactions have the same `last_seen_unconfirmed` parameter, we compare
728+
/// their txids by lexicographical order, and arbitrarily pick the one with the higher txid.
692729
///
693730
/// # Error
694731
///
@@ -714,7 +751,7 @@ impl<A: Anchor> TxGraph<A> {
714751
}
715752
}
716753

717-
// The tx is not anchored to a block which is in the best chain, which means that it
754+
// The tx is not anchored to a block in the best chain, which means that it
718755
// might be in mempool, or it might have been dropped already.
719756
// Let's check conflicts to find out!
720757
let tx = match tx_node {
@@ -917,7 +954,8 @@ impl<A: Anchor> TxGraph<A> {
917954
/// (`OI`) for convenience. If `OI` is not necessary, the caller can use `()`, or
918955
/// [`Iterator::enumerate`] over a list of [`OutPoint`]s.
919956
///
920-
/// Floating outputs are ignored.
957+
/// Floating outputs (i.e., outputs for which we don't have the full transaction in the graph)
958+
/// are ignored.
921959
///
922960
/// # Error
923961
///
@@ -1244,7 +1282,7 @@ impl<A> AsRef<TxGraph<A>> for TxGraph<A> {
12441282
///
12451283
/// The iterator excludes partial transactions.
12461284
///
1247-
/// This `struct` is created by the [`walk_ancestors`] method of [`TxGraph`].
1285+
/// This struct is created by the [`walk_ancestors`] method of [`TxGraph`].
12481286
///
12491287
/// [`walk_ancestors`]: TxGraph::walk_ancestors
12501288
pub struct TxAncestors<'g, A, F> {

crates/chain/tests/test_tx_graph_conflicts.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ fn test_tx_conflict_handling() {
7070
..Default::default()
7171
},
7272
],
73+
// the txgraph is going to pick tx_conflict_2 because of higher lexicographical txid
7374
exp_chain_txs: HashSet::from(["tx1", "tx_conflict_2"]),
7475
exp_chain_txouts: HashSet::from([("tx1", 0), ("tx_conflict_2", 0)]),
7576
exp_unspents: HashSet::from([("tx_conflict_2", 0)]),

0 commit comments

Comments
 (0)