bdk/crates/chain/src/chain_data.rs

284 lines
7.6 KiB
Rust
Raw Normal View History

use bitcoin::{hashes::Hash, BlockHash, OutPoint, TxOut, Txid};
use crate::{
sparse_chain::{self, ChainPosition},
BlockAnchor, COINBASE_MATURITY,
};
/// Represents an observation of some chain data.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, core::hash::Hash)]
pub enum ObservedIn<A> {
/// The chain data is seen in a block identified by `A`.
Block(A),
/// The chain data is seen in mempool at this given timestamp.
Mempool(u64),
}
impl<A: Clone> ObservedIn<&A> {
pub fn into_owned(self) -> ObservedIn<A> {
match self {
ObservedIn::Block(a) => ObservedIn::Block(a.clone()),
ObservedIn::Mempool(last_seen) => ObservedIn::Mempool(last_seen),
}
}
}
impl ChainPosition for ObservedIn<BlockId> {
fn height(&self) -> TxHeight {
match self {
ObservedIn::Block(block_id) => TxHeight::Confirmed(block_id.height),
ObservedIn::Mempool(_) => TxHeight::Unconfirmed,
}
}
fn max_ord_of_height(height: TxHeight) -> Self {
match height {
TxHeight::Confirmed(height) => ObservedIn::Block(BlockId {
height,
hash: Hash::from_inner([u8::MAX; 32]),
}),
TxHeight::Unconfirmed => Self::Mempool(u64::MAX),
}
}
fn min_ord_of_height(height: TxHeight) -> Self {
match height {
TxHeight::Confirmed(height) => ObservedIn::Block(BlockId {
height,
hash: Hash::from_inner([u8::MIN; 32]),
}),
TxHeight::Unconfirmed => Self::Mempool(u64::MIN),
}
}
}
2023-03-10 23:23:29 +05:30
/// 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(_))
}
}
2023-03-10 23:23:29 +05:30
/// 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 { .. })
}
}
2023-03-10 23:23:29 +05:30
/// 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 {
2023-03-10 23:23:29 +05:30
/// The height of the block.
pub height: u32,
2023-03-10 23:23:29 +05:30
/// 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 BlockAnchor 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,
}
}
}
2023-03-10 23:23:29 +05:30
/// A `TxOut` with as much data as we can retrieve about it
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct FullTxOut<I> {
2023-03-10 23:23:29 +05:30
/// The location of the `TxOut`.
pub outpoint: OutPoint,
2023-03-10 23:23:29 +05:30
/// The `TxOut`.
pub txout: TxOut,
/// The position of the transaction in `outpoint` in the overall chain.
pub chain_position: I,
/// The txid and chain position of the transaction (if any) that has spent this output.
pub spent_by: Option<(I, Txid)>,
2023-03-10 23:23:29 +05:30
/// Whether this output is on a coinbase transaction.
pub is_on_coinbase: bool,
}
impl<I: ChainPosition> FullTxOut<I> {
/// 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
2023-03-10 23:23:29 +05:30
/// 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: Clone> FullTxOut<ObservedIn<&A>> {
pub fn into_owned(self) -> FullTxOut<ObservedIn<A>> {
FullTxOut {
outpoint: self.outpoint,
txout: self.txout,
chain_position: self.chain_position.into_owned(),
spent_by: self.spent_by.map(|(o, txid)| (o.into_owned(), txid)),
is_on_coinbase: self.is_on_coinbase,
}
}
}
2023-03-10 23:23:29 +05:30
// TODO: make test