2023-03-01 11:09:08 +01:00
|
|
|
use bitcoin::{hashes::Hash, BlockHash, OutPoint, TxOut, Txid};
|
|
|
|
|
|
|
|
use crate::{
|
|
|
|
sparse_chain::{self, ChainPosition},
|
2023-04-21 13:29:44 +08:00
|
|
|
Anchor, COINBASE_MATURITY,
|
2023-03-01 11:09:08 +01:00
|
|
|
};
|
|
|
|
|
2023-03-24 15:47:39 +08:00
|
|
|
/// Represents an observation of some chain data.
|
2023-04-05 16:39:54 +08:00
|
|
|
///
|
2023-04-17 23:25:57 +08:00
|
|
|
/// The generic `A` should be a [`Anchor`] implementation.
|
2023-03-26 11:24:30 +08:00
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, core::hash::Hash)]
|
2023-04-05 16:39:54 +08:00
|
|
|
pub enum ObservedAs<A> {
|
|
|
|
/// The chain data is seen as confirmed, and in anchored by `A`.
|
|
|
|
Confirmed(A),
|
2023-03-26 11:24:30 +08:00
|
|
|
/// The chain data is seen in mempool at this given timestamp.
|
2023-04-05 16:39:54 +08:00
|
|
|
Unconfirmed(u64),
|
2023-03-26 11:24:30 +08:00
|
|
|
}
|
|
|
|
|
2023-04-05 16:39:54 +08:00
|
|
|
impl<A: Clone> ObservedAs<&A> {
|
|
|
|
pub fn cloned(self) -> ObservedAs<A> {
|
2023-03-27 19:55:57 +08:00
|
|
|
match self {
|
2023-04-05 16:39:54 +08:00
|
|
|
ObservedAs::Confirmed(a) => ObservedAs::Confirmed(a.clone()),
|
|
|
|
ObservedAs::Unconfirmed(last_seen) => ObservedAs::Unconfirmed(last_seen),
|
2023-03-26 11:24:30 +08:00
|
|
|
}
|
|
|
|
}
|
2023-03-24 15:47:39 +08:00
|
|
|
}
|
|
|
|
|
2023-03-10 23:23:29 +05:30
|
|
|
/// Represents the height at which a transaction is confirmed.
|
2023-03-01 11:09:08 +01:00
|
|
|
#[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.
|
2023-03-01 11:09:08 +01:00
|
|
|
#[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.
|
2023-03-24 09:23:36 +08:00
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Copy, PartialOrd, Ord, core::hash::Hash)]
|
2023-03-01 11:09:08 +01:00
|
|
|
#[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.
|
2023-03-01 11:09:08 +01:00
|
|
|
pub height: u32,
|
2023-03-10 23:23:29 +05:30
|
|
|
/// The hash of the block.
|
2023-03-01 11:09:08 +01:00
|
|
|
pub hash: BlockHash,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for BlockId {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self {
|
|
|
|
height: Default::default(),
|
|
|
|
hash: BlockHash::from_inner([0u8; 32]),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-17 23:25:57 +08:00
|
|
|
impl Anchor for BlockId {
|
2023-03-24 09:23:36 +08:00
|
|
|
fn anchor_block(&self) -> BlockId {
|
|
|
|
*self
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-01 11:09:08 +01:00
|
|
|
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
|
2023-03-26 11:24:30 +08:00
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
2023-04-05 16:39:54 +08:00
|
|
|
pub struct FullTxOut<P> {
|
2023-03-10 23:23:29 +05:30
|
|
|
/// The location of the `TxOut`.
|
2023-03-01 11:09:08 +01:00
|
|
|
pub outpoint: OutPoint,
|
2023-03-10 23:23:29 +05:30
|
|
|
/// The `TxOut`.
|
2023-03-01 11:09:08 +01:00
|
|
|
pub txout: TxOut,
|
|
|
|
/// The position of the transaction in `outpoint` in the overall chain.
|
2023-04-05 16:39:54 +08:00
|
|
|
pub chain_position: P,
|
2023-03-01 11:09:08 +01:00
|
|
|
/// The txid and chain position of the transaction (if any) that has spent this output.
|
2023-04-05 16:39:54 +08:00
|
|
|
pub spent_by: Option<(P, Txid)>,
|
2023-03-10 23:23:29 +05:30
|
|
|
/// Whether this output is on a coinbase transaction.
|
2023-03-01 11:09:08 +01:00
|
|
|
pub is_on_coinbase: bool,
|
|
|
|
}
|
|
|
|
|
2023-04-05 16:39:54 +08:00
|
|
|
impl<P: ChainPosition> FullTxOut<P> {
|
2023-03-01 11:09:08 +01:00
|
|
|
/// 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.
|
2023-03-01 11:09:08 +01:00
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-21 13:29:44 +08:00
|
|
|
impl<A: Anchor> FullTxOut<ObservedAs<A>> {
|
2023-04-05 16:39:54 +08:00
|
|
|
/// Whether the `txout` is considered mature.
|
|
|
|
///
|
|
|
|
/// This is the alternative version of [`is_mature`] which depends on `chain_position` being a
|
2023-04-17 23:25:57 +08:00
|
|
|
/// [`ObservedAs<A>`] where `A` implements [`Anchor`].
|
2023-04-05 16:39:54 +08:00
|
|
|
///
|
2023-04-22 23:39:49 +08:00
|
|
|
/// Depending on the implementation of [`confirmation_height_upper_bound`] in [`Anchor`], this
|
|
|
|
/// method may return false-negatives. In other words, interpretted confirmation count may be
|
|
|
|
/// less than the actual value.
|
|
|
|
///
|
2023-04-05 16:39:54 +08:00
|
|
|
/// [`is_mature`]: Self::is_mature
|
2023-04-22 23:39:49 +08:00
|
|
|
/// [`confirmation_height_upper_bound`]: Anchor::confirmation_height_upper_bound
|
2023-04-21 13:29:44 +08:00
|
|
|
pub fn is_mature(&self, tip: u32) -> bool {
|
2023-04-22 23:39:49 +08:00
|
|
|
if self.is_on_coinbase {
|
|
|
|
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 {
|
2023-04-05 16:39:54 +08:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
true
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Whether the utxo is/was/will be spendable with chain `tip`.
|
|
|
|
///
|
2023-04-21 13:29:44 +08:00
|
|
|
/// This method does not take into account the locktime.
|
2023-04-10 16:23:10 +08:00
|
|
|
///
|
2023-04-05 16:39:54 +08:00
|
|
|
/// This is the alternative version of [`is_spendable_at`] which depends on `chain_position`
|
2023-04-17 23:25:57 +08:00
|
|
|
/// being a [`ObservedAs<A>`] where `A` implements [`Anchor`].
|
2023-04-05 16:39:54 +08:00
|
|
|
///
|
2023-04-22 23:39:49 +08:00
|
|
|
/// Depending on the implementation of [`confirmation_height_upper_bound`] in [`Anchor`], this
|
|
|
|
/// method may return false-negatives. In other words, interpretted confirmation count may be
|
|
|
|
/// less than the actual value.
|
|
|
|
///
|
2023-04-05 16:39:54 +08:00
|
|
|
/// [`is_spendable_at`]: Self::is_spendable_at
|
2023-04-22 23:39:49 +08:00
|
|
|
/// [`confirmation_height_upper_bound`]: Anchor::confirmation_height_upper_bound
|
2023-04-21 13:29:44 +08:00
|
|
|
pub fn is_confirmed_and_spendable(&self, tip: u32) -> bool {
|
|
|
|
if !self.is_mature(tip) {
|
2023-04-05 16:39:54 +08:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2023-04-17 23:25:57 +08:00
|
|
|
let confirmation_height = match &self.chain_position {
|
2023-04-21 13:29:44 +08:00
|
|
|
ObservedAs::Confirmed(anchor) => anchor.confirmation_height_upper_bound(),
|
2023-04-05 16:39:54 +08:00
|
|
|
ObservedAs::Unconfirmed(_) => return false,
|
|
|
|
};
|
2023-04-17 23:25:57 +08:00
|
|
|
if confirmation_height > tip {
|
|
|
|
return false;
|
|
|
|
}
|
2023-04-05 16:39:54 +08:00
|
|
|
|
|
|
|
// 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
|
2023-03-27 19:55:57 +08:00
|
|
|
}
|
|
|
|
}
|