Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 7 additions & 15 deletions crates/chain/src/canonical.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,14 +107,12 @@ impl<P: Ord> PartialOrd for CanonicalTxOut<P> {
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.
///
/// [`confirmation_height_upper_bound`]: Anchor::confirmation_height_upper_bound
/// A coinbase output is mature once [`COINBASE_MATURITY`] blocks (including the block that
/// confirmed it) have been mined up to and including `tip`. Non-coinbase outputs are always

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know the best way to word this, but a coin is mature if it's eligible to be included in the next block. The minimum such height is COINBASE_MATURITY - 1 , so this should say something like:

/// A coinbase output is mature once it can be included in the next block after
/// [`COINBASE_MATURITY`] blocks have been mined after its confirmation

/// mature.
pub fn is_mature(&self, tip: u32) -> bool {
if self.is_on_coinbase {
let conf_height = match self.pos.confirmation_height_upper_bound() {
let conf_height = match self.pos.confirmation_height() {
Some(height) => height,
None => {
debug_assert!(false, "coinbase tx can never be unconfirmed");
Expand All @@ -133,18 +131,12 @@ impl<A: Anchor> CanonicalTxOut<ChainPosition<A>> {
/// Whether the utxo is/was/will be spendable with chain `tip`.
///
/// 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.
///
/// [`confirmation_height_upper_bound`]: Anchor::confirmation_height_upper_bound
pub fn is_confirmed_and_spendable(&self, tip: u32) -> bool {
if !self.is_mature(tip) {
return false;
}

let conf_height = match self.pos.confirmation_height_upper_bound() {
let conf_height = match self.pos.confirmation_height() {
Some(height) => height,
None => return false,
};
Expand All @@ -156,7 +148,7 @@ impl<A: Anchor> CanonicalTxOut<ChainPosition<A>> {
if let Some(spend_height) = self
.spent_by
.as_ref()
.and_then(|(pos, _)| pos.confirmation_height_upper_bound())
.and_then(|(pos, _)| pos.confirmation_height())
{
if spend_height <= tip {
return false;
Expand Down Expand Up @@ -449,7 +441,7 @@ impl<A: Anchor> CanonicalView<A> {
for (spk_i, txout) in self.filter_unspent_outpoints(outpoints) {
match &txout.pos {
ChainPosition::Confirmed { anchor, .. } => {
let confirmation_height = anchor.confirmation_height_upper_bound();
let confirmation_height = anchor.confirmation_height();
let confirmations = self
.tip
.height
Expand Down
2 changes: 1 addition & 1 deletion crates/chain/src/canonical_task.rs
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ impl<'g, A: Anchor> ChainQuery for CanonicalTask<'g, A> {
.expect(
"tx taken from `unprocessed_anchored_txs` so it must have at least one anchor",
)
.confirmation_height_upper_bound(),
.confirmation_height(),
))
}
}
Expand Down
24 changes: 11 additions & 13 deletions crates/chain/src/chain_data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,21 +74,19 @@ impl<A: Clone> ChainPosition<&A> {
}

impl<A: Anchor> ChainPosition<A> {
/// Determines the upper bound of the confirmation height.
pub fn confirmation_height_upper_bound(&self) -> Option<u32> {
/// Height of the block that confirmed this position, if it is confirmed.
pub fn confirmation_height(&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`.
///
/// Returns `0` if unconfirmed, or if the confirmation height is above `tip`.
pub fn confirmations_lower_bound(&self, tip: u32) -> u32 {
let Some(height) = self.confirmation_height_upper_bound() else {
pub fn confirmations(&self, tip: u32) -> u32 {
let Some(height) = self.confirmation_height() else {
return 0;
};

Expand Down Expand Up @@ -352,7 +350,7 @@ mod test {
}

#[test]
fn test_confirmations_lower_bound() {
fn test_confirmations() {
let confirmed_at = |height: u32| ChainPosition::Confirmed {
anchor: ConfirmationBlockTime {
confirmation_time: 0,
Expand All @@ -364,15 +362,15 @@ mod test {
transitively: None,
};

assert_eq!(confirmed_at(100).confirmations_lower_bound(100), 1);
assert_eq!(confirmed_at(99).confirmations_lower_bound(100), 2);
assert_eq!(confirmed_at(90).confirmations_lower_bound(100), 11);
assert_eq!(confirmed_at(101).confirmations_lower_bound(100), 0);
assert_eq!(confirmed_at(100).confirmations(100), 1);
assert_eq!(confirmed_at(99).confirmations(100), 2);
assert_eq!(confirmed_at(90).confirmations(100), 11);
assert_eq!(confirmed_at(101).confirmations(100), 0);

let unconfirmed = ChainPosition::<ConfirmationBlockTime>::Unconfirmed {
first_seen: Some(1),
last_seen: Some(2),
};
assert_eq!(unconfirmed.confirmations_lower_bound(100), 0);
assert_eq!(unconfirmed.confirmations(100), 0);
}
}
21 changes: 7 additions & 14 deletions crates/chain/src/tx_data_traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@ use crate::{BlockId, ConfirmationBlockTime};

/// Trait that "anchors" blockchain data to a specific block of height and hash.
///
/// 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.
/// If transaction A is anchored in block B, then block B is the block that confirmed transaction
/// A. If block B is in the best chain, transaction A is confirmed in the best chain.
///
/// Every [`Anchor`] implementation must contain a [`BlockId`] parameter, and must implement
/// [`Ord`]. When implementing [`Ord`], the anchors' [`BlockId`]s should take precedence
Expand Down Expand Up @@ -68,11 +66,10 @@ pub trait Anchor: core::fmt::Debug + Clone + Eq + PartialOrd + Ord + core::hash:
/// 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
}
}
Expand All @@ -82,8 +79,8 @@ impl<A: Anchor> Anchor for &A {
<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)
}
}

Expand All @@ -97,10 +94,6 @@ impl Anchor for ConfirmationBlockTime {
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`].
Expand Down
8 changes: 2 additions & 6 deletions crates/chain/src/tx_graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -766,15 +766,11 @@ impl<A: Anchor> TxGraph<A> {
// `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;
Expand Down