impl<A: Anchor> CanonicalTxOut<ChainPosition<A>> {
/// Whether the `txout` is considered mature.
///
- /// Depending on the implementation of [`confirmation_height_upper_bound`] in [`Anchor`], this
- /// method may return false-negatives. In other words, interpreted confirmation count may be
- /// less than the actual value.
+ /// A coinbase output is mature once a transaction spending it can be included in the block
+ /// after `tip`. Consensus requires the spending block to be at least [`COINBASE_MATURITY`]
+ /// blocks above the confirming block, so the output is mature once `tip` is at least
+ /// `COINBASE_MATURITY - 1` blocks above the confirming block. Non-coinbase outputs are always
+ /// mature.
///
- /// [`confirmation_height_upper_bound`]: Anchor::confirmation_height_upper_bound
+ /// If the position is confirmed [`transitively`](ChainPosition::Confirmed::transitively), the
+ /// confirmation height may be an upper bound (see [`confirmation_height_upper_bound`]), so
+ /// this method may return false-negatives.
+ ///
+ /// [`confirmation_height_upper_bound`]: ChainPosition::confirmation_height_upper_bound
pub fn is_mature(&self, tip: u32) -> bool {
if self.is_on_coinbase {
let conf_height = match self.pos.confirmation_height_upper_bound() {
///
/// This method does not take into account the lock time.
///
- /// Depending on the implementation of [`confirmation_height_upper_bound`] in [`Anchor`], this
- /// method may return false-negatives. In other words, interpreted confirmation count may be
- /// less than the actual value.
+ /// If the position is confirmed [`transitively`](ChainPosition::Confirmed::transitively), the
+ /// confirmation height may be an upper bound (see [`confirmation_height_upper_bound`]), so
+ /// this method may return false-negatives.
///
- /// [`confirmation_height_upper_bound`]: Anchor::confirmation_height_upper_bound
+ /// [`confirmation_height_upper_bound`]: ChainPosition::confirmation_height_upper_bound
pub fn is_confirmed_and_spendable(&self, tip: u32) -> bool {
if !self.is_mature(tip) {
return false;
.expect(
"tx taken from `unprocessed_anchored_txs` so it must have at least one anchor",
)
- .confirmation_height_upper_bound(),
+ .confirmation_height(),
))
}
}
impl<A: Anchor> ChainPosition<A> {
/// Determines the upper bound of the confirmation height.
+ ///
+ /// This is the height of the anchor block. It is exact for a direct anchor. If the position is
+ /// confirmed [`transitively`](ChainPosition::Confirmed::transitively), the anchor belongs to a
+ /// descendant, so the chain data may be confirmed in an earlier block.
pub fn confirmation_height_upper_bound(&self) -> Option<u32> {
match self {
- ChainPosition::Confirmed { anchor, .. } => {
- Some(anchor.confirmation_height_upper_bound())
- }
+ ChainPosition::Confirmed { anchor, .. } => Some(anchor.confirmation_height()),
ChainPosition::Unconfirmed { .. } => None,
}
}
- /// Number of confirmations of this position, given `tip`.
+ /// Lower bound of the number of confirmations of this position, given `tip`.
+ ///
+ /// This is derived from [`confirmation_height_upper_bound`], so it is exact unless the position
+ /// is confirmed [`transitively`](ChainPosition::Confirmed::transitively).
///
/// Returns `0` if unconfirmed, or if the confirmation height is above `tip`.
+ ///
+ /// [`confirmation_height_upper_bound`]: Self::confirmation_height_upper_bound
pub fn confirmations_lower_bound(&self, tip: u32) -> u32 {
let Some(height) = self.confirmation_height_upper_bound() else {
return 0;
/// Returns the [`BlockId`] that the associated blockchain data is "anchored" in.
fn anchor_block(&self) -> BlockId;
- /// Get the upper bound of the chain data's confirmation height.
+ /// Get the height of the block that confirmed the associated chain data.
///
- /// The default definition gives a pessimistic answer. This can be overridden by the `Anchor`
- /// implementation for a more accurate value.
- fn confirmation_height_upper_bound(&self) -> u32 {
+ /// This is the height of [`anchor_block`](Anchor::anchor_block).
+ fn confirmation_height(&self) -> u32 {
self.anchor_block().height
}
}
<A as Anchor>::anchor_block(self)
}
- fn confirmation_height_upper_bound(&self) -> u32 {
- <A as Anchor>::confirmation_height_upper_bound(self)
+ fn confirmation_height(&self) -> u32 {
+ <A as Anchor>::confirmation_height(self)
}
}
fn anchor_block(&self) -> BlockId {
self.block_id
}
-
- fn confirmation_height_upper_bound(&self) -> u32 {
- self.block_id.height
- }
}
/// Set of parameters sufficient to construct an [`Anchor`].
// `txs_by_highest_conf_heights`.
// We want to remove `(old_top_h?, txid)` and insert `(new_top_h?, txid)`.
let mut old_top_h = None;
- let mut new_top_h = anchor.confirmation_height_upper_bound();
+ let mut new_top_h = anchor.confirmation_height();
let is_changed = match self.anchors.entry(txid) {
hash_map::Entry::Occupied(mut e) => {
- old_top_h = e
- .get()
- .iter()
- .last()
- .map(Anchor::confirmation_height_upper_bound);
+ old_top_h = e.get().iter().last().map(Anchor::confirmation_height);
if let Some(old_top_h) = old_top_h {
if old_top_h > new_top_h {
new_top_h = old_top_h;
#[macro_use]
mod common;
-use bdk_chain::{local_chain::LocalChain, Balance, BlockId};
+use bdk_chain::{local_chain::LocalChain, Balance, BlockId, ChainPosition, TxGraph};
use bdk_testenv::{block_id, hash, local_chain};
use bitcoin::{Amount, BlockHash, OutPoint};
use common::*;
);
}
}
+
+/// A tx without its own anchor borrows the anchor of a confirmed descendant, so its confirmation
+/// height is only an upper bound until its own anchor is known.
+#[test]
+fn transitively_confirmed_tx_reports_descendant_anchor_height() {
+ let chain = local_chain![
+ (0, hash!("genesis")),
+ (1, hash!("parent_block")),
+ (5, hash!("child_block"))
+ ];
+ let tip = chain.tip().block_id();
+ let parent_anchor = block_id!(1, "parent_block");
+ let child_anchor = block_id!(5, "child_block");
+
+ let templates = [
+ TxTemplate {
+ tx_name: "parent",
+ inputs: &[TxInTemplate::Bogus],
+ outputs: &[TxOutTemplate::new(100_000, Some(0))],
+ // Seen in the mempool, but its confirmation has not been fetched yet.
+ last_seen: Some(1),
+ ..Default::default()
+ },
+ TxTemplate {
+ tx_name: "child",
+ inputs: &[TxInTemplate::PrevTx("parent", 0)],
+ outputs: &[TxOutTemplate::new(90_000, Some(0))],
+ anchors: &[child_anchor],
+ ..Default::default()
+ },
+ ];
+ let mut env = init_graph(&templates);
+ let parent_txid = env.txid_to_name["parent"];
+ let child_txid = env.txid_to_name["child"];
+
+ let parent_position = |tx_graph: &TxGraph<BlockId>| {
+ chain
+ .canonical_view(tx_graph, tip, Default::default())
+ .txs()
+ .find(|tx| tx.txid == parent_txid)
+ .expect("parent must be canonical")
+ .pos
+ };
+
+ // Really confirmed in block 1, but the position can only report the child's block 5.
+ let pos = parent_position(&env.tx_graph);
+ assert_eq!(
+ pos,
+ ChainPosition::Confirmed {
+ anchor: child_anchor,
+ transitively: Some(child_txid),
+ }
+ );
+ assert_eq!(pos.confirmation_height_upper_bound(), Some(5));
+ assert_eq!(pos.confirmations_lower_bound(tip.height), 1);
+
+ // Once the parent's own anchor is known, the position becomes exact.
+ let _ = env.tx_graph.insert_anchor(parent_txid, parent_anchor);
+ let pos = parent_position(&env.tx_graph);
+ assert_eq!(
+ pos,
+ ChainPosition::Confirmed {
+ anchor: parent_anchor,
+ transitively: None,
+ }
+ );
+ assert_eq!(pos.confirmation_height_upper_bound(), Some(1));
+ assert_eq!(pos.confirmations_lower_bound(tip.height), 5);
+}