bdk/crates/chain/src/chain_data.rs
志宇 f3e7b67bf1
[bdk_chain_redesign] Various tweaks and fixes
The `ConfirmationHeight` trait has been removed in favour of a second
method on the `Anchor` trait: `confirmation_height_upper_bound()`.

Methods `try_balance_at()` and `balance_at()` of `IndexedTxGraph` have
been removed as they do not provide additional functionality.

`IndexedTxGraph::insert_relevant_txs` now uses two loops, the first loop
indexes all transactions first. This is done as some indexes require
ancestor transactions to be indexed first and we cannot guarantee that
the input transactions are in topological order.
2023-04-21 13:29:44 +08:00

305 lines
8.4 KiB
Rust

use bitcoin::{hashes::Hash, BlockHash, OutPoint, TxOut, Txid};
use crate::{
sparse_chain::{self, ChainPosition},
Anchor, COINBASE_MATURITY,
};
/// Represents an observation of some chain data.
///
/// The generic `A` should be a [`Anchor`] implementation.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, core::hash::Hash)]
pub enum ObservedAs<A> {
/// The chain data is seen as confirmed, and in anchored by `A`.
Confirmed(A),
/// The chain data is seen in mempool at this given timestamp.
Unconfirmed(u64),
}
impl<A: Clone> ObservedAs<&A> {
pub fn cloned(self) -> ObservedAs<A> {
match self {
ObservedAs::Confirmed(a) => ObservedAs::Confirmed(a.clone()),
ObservedAs::Unconfirmed(last_seen) => ObservedAs::Unconfirmed(last_seen),
}
}
}
/// Represents the height at which a transaction is confirmed.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(
feature = "serde",
derive(serde::Deserialize, serde::Serialize),
serde(crate = "serde_crate")
)]
pub enum TxHeight {
Confirmed(u32),
Unconfirmed,
}
impl Default for TxHeight {
fn default() -> Self {
Self::Unconfirmed
}
}
impl core::fmt::Display for TxHeight {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::Confirmed(h) => core::write!(f, "confirmed_at({})", h),
Self::Unconfirmed => core::write!(f, "unconfirmed"),
}
}
}
impl From<Option<u32>> for TxHeight {
fn from(opt: Option<u32>) -> Self {
match opt {
Some(h) => Self::Confirmed(h),
None => Self::Unconfirmed,
}
}
}
impl From<TxHeight> for Option<u32> {
fn from(height: TxHeight) -> Self {
match height {
TxHeight::Confirmed(h) => Some(h),
TxHeight::Unconfirmed => None,
}
}
}
impl crate::sparse_chain::ChainPosition for TxHeight {
fn height(&self) -> TxHeight {
*self
}
fn max_ord_of_height(height: TxHeight) -> Self {
height
}
fn min_ord_of_height(height: TxHeight) -> Self {
height
}
}
impl TxHeight {
pub fn is_confirmed(&self) -> bool {
matches!(self, Self::Confirmed(_))
}
}
/// Block height and timestamp at which a transaction is confirmed.
#[derive(Debug, Clone, PartialEq, Eq, Copy, PartialOrd, Ord, core::hash::Hash)]
#[cfg_attr(
feature = "serde",
derive(serde::Deserialize, serde::Serialize),
serde(crate = "serde_crate")
)]
pub enum ConfirmationTime {
Confirmed { height: u32, time: u64 },
Unconfirmed,
}
impl sparse_chain::ChainPosition for ConfirmationTime {
fn height(&self) -> TxHeight {
match self {
ConfirmationTime::Confirmed { height, .. } => TxHeight::Confirmed(*height),
ConfirmationTime::Unconfirmed => TxHeight::Unconfirmed,
}
}
fn max_ord_of_height(height: TxHeight) -> Self {
match height {
TxHeight::Confirmed(height) => Self::Confirmed {
height,
time: u64::MAX,
},
TxHeight::Unconfirmed => Self::Unconfirmed,
}
}
fn min_ord_of_height(height: TxHeight) -> Self {
match height {
TxHeight::Confirmed(height) => Self::Confirmed {
height,
time: u64::MIN,
},
TxHeight::Unconfirmed => Self::Unconfirmed,
}
}
}
impl ConfirmationTime {
pub fn is_confirmed(&self) -> bool {
matches!(self, Self::Confirmed { .. })
}
}
/// A reference to a block in the canonical chain.
#[derive(Debug, Clone, PartialEq, Eq, Copy, PartialOrd, Ord, core::hash::Hash)]
#[cfg_attr(
feature = "serde",
derive(serde::Deserialize, serde::Serialize),
serde(crate = "serde_crate")
)]
pub struct BlockId {
/// The height of the block.
pub height: u32,
/// The hash of the block.
pub hash: BlockHash,
}
impl Default for BlockId {
fn default() -> Self {
Self {
height: Default::default(),
hash: BlockHash::from_inner([0u8; 32]),
}
}
}
impl Anchor for BlockId {
fn anchor_block(&self) -> BlockId {
*self
}
}
impl From<(u32, BlockHash)> for BlockId {
fn from((height, hash): (u32, BlockHash)) -> Self {
Self { height, hash }
}
}
impl From<BlockId> for (u32, BlockHash) {
fn from(block_id: BlockId) -> Self {
(block_id.height, block_id.hash)
}
}
impl From<(&u32, &BlockHash)> for BlockId {
fn from((height, hash): (&u32, &BlockHash)) -> Self {
Self {
height: *height,
hash: *hash,
}
}
}
/// A `TxOut` with as much data as we can retrieve about it
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct FullTxOut<P> {
/// The location of the `TxOut`.
pub outpoint: OutPoint,
/// The `TxOut`.
pub txout: TxOut,
/// The position of the transaction in `outpoint` in the overall chain.
pub chain_position: P,
/// The txid and chain position of the transaction (if any) that has spent this output.
pub spent_by: Option<(P, Txid)>,
/// Whether this output is on a coinbase transaction.
pub is_on_coinbase: bool,
}
impl<P: ChainPosition> FullTxOut<P> {
/// Whether the utxo is/was/will be spendable at `height`.
///
/// It is spendable if it is not an immature coinbase output and no spending tx has been
/// confirmed by that height.
pub fn is_spendable_at(&self, height: u32) -> bool {
if !self.is_mature(height) {
return false;
}
if self.chain_position.height() > TxHeight::Confirmed(height) {
return false;
}
match &self.spent_by {
Some((spending_height, _)) => spending_height.height() > TxHeight::Confirmed(height),
None => true,
}
}
pub fn is_mature(&self, height: u32) -> bool {
if self.is_on_coinbase {
let tx_height = match self.chain_position.height() {
TxHeight::Confirmed(tx_height) => tx_height,
TxHeight::Unconfirmed => {
debug_assert!(false, "coinbase tx can never be unconfirmed");
return false;
}
};
let age = height.saturating_sub(tx_height);
if age + 1 < COINBASE_MATURITY {
return false;
}
}
true
}
}
impl<A: Anchor> FullTxOut<ObservedAs<A>> {
/// Whether the `txout` is considered mature.
///
/// This is the alternative version of [`is_mature`] which depends on `chain_position` being a
/// [`ObservedAs<A>`] where `A` implements [`Anchor`].
///
/// [`is_mature`]: Self::is_mature
pub fn is_mature(&self, tip: u32) -> bool {
if !self.is_on_coinbase {
return true;
}
let tx_height = match &self.chain_position {
ObservedAs::Confirmed(anchor) => anchor.confirmation_height_upper_bound(),
ObservedAs::Unconfirmed(_) => {
debug_assert!(false, "coinbase tx can never be unconfirmed");
return false;
}
};
let age = tip.saturating_sub(tx_height);
if age + 1 < COINBASE_MATURITY {
return false;
}
true
}
/// Whether the utxo is/was/will be spendable with chain `tip`.
///
/// This method does not take into account the locktime.
///
/// This is the alternative version of [`is_spendable_at`] which depends on `chain_position`
/// being a [`ObservedAs<A>`] where `A` implements [`Anchor`].
///
/// [`is_spendable_at`]: Self::is_spendable_at
pub fn is_confirmed_and_spendable(&self, tip: u32) -> bool {
if !self.is_mature(tip) {
return false;
}
let confirmation_height = match &self.chain_position {
ObservedAs::Confirmed(anchor) => anchor.confirmation_height_upper_bound(),
ObservedAs::Unconfirmed(_) => return false,
};
if confirmation_height > tip {
return false;
}
// if the spending tx is confirmed within tip height, the txout is no longer spendable
if let Some((ObservedAs::Confirmed(spending_anchor), _)) = &self.spent_by {
if spending_anchor.anchor_block().height <= tip {
return false;
}
}
true
}
}
// TODO: make test