Skip to content

Commit

Permalink
refactor(chain)!: Remove Anchor trait
Browse files Browse the repository at this point in the history
The `Anchor` trait has been removed and anchors are now unique to
(`Txid`, `BlockId`).
  • Loading branch information
LagginTimes committed Jul 18, 2024
1 parent d99b3ef commit 8605283
Show file tree
Hide file tree
Showing 25 changed files with 628 additions and 791 deletions.
22 changes: 12 additions & 10 deletions crates/bitcoind_rpc/tests/test_emitter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use bdk_bitcoind_rpc::Emitter;
use bdk_chain::{
bitcoin::{Address, Amount, Txid},
local_chain::{CheckPoint, LocalChain},
Balance, BlockId, IndexedTxGraph, Merge, SpkTxOutIndex,
Balance, BlockId, BlockTime, IndexedTxGraph, Merge, SpkTxOutIndex,
};
use bdk_testenv::{anyhow, TestEnv};
use bitcoin::{hashes::Hash, Block, OutPoint, ScriptBuf, WScriptHash};
Expand Down Expand Up @@ -149,7 +149,7 @@ fn test_into_tx_graph() -> anyhow::Result<()> {
println!("mined blocks!");

let (mut chain, _) = LocalChain::from_genesis_hash(env.rpc_client().get_block_hash(0)?);
let mut indexed_tx_graph = IndexedTxGraph::<BlockId, _>::new({
let mut indexed_tx_graph = IndexedTxGraph::new({
let mut index = SpkTxOutIndex::<usize>::default();
index.insert_spk(0, addr_0.script_pubkey());
index.insert_spk(1, addr_1.script_pubkey());
Expand Down Expand Up @@ -206,17 +206,19 @@ fn test_into_tx_graph() -> anyhow::Result<()> {

// mine a block that confirms the 3 txs
let exp_block_hash = env.mine_blocks(1, None)?[0];
let exp_block_height = env.rpc_client().get_block_info(&exp_block_hash)?.height as u32;
let exp_block = env.rpc_client().get_block_info(&exp_block_hash)?;
let exp_block_height = exp_block.height as u32;
let exp_block_time = exp_block.time as u32;
let exp_anchors = exp_txids
.iter()
.map({
let anchor = BlockId {
let blockid = BlockId {
height: exp_block_height,
hash: exp_block_hash,
};
move |&txid| (anchor, txid)
move |&txid| ((txid, blockid), BlockTime::new(exp_block_time))
})
.collect::<BTreeSet<_>>();
.collect::<BTreeMap<_, _>>();

// must receive mined block which will confirm the transactions.
{
Expand Down Expand Up @@ -277,7 +279,7 @@ fn ensure_block_emitted_after_reorg_is_at_reorg_height() -> anyhow::Result<()> {

fn process_block(
recv_chain: &mut LocalChain,
recv_graph: &mut IndexedTxGraph<BlockId, SpkTxOutIndex<()>>,
recv_graph: &mut IndexedTxGraph<BlockTime, SpkTxOutIndex<()>>,
block: Block,
block_height: u32,
) -> anyhow::Result<()> {
Expand All @@ -288,7 +290,7 @@ fn process_block(

fn sync_from_emitter<C>(
recv_chain: &mut LocalChain,
recv_graph: &mut IndexedTxGraph<BlockId, SpkTxOutIndex<()>>,
recv_graph: &mut IndexedTxGraph<BlockTime, SpkTxOutIndex<()>>,
emitter: &mut Emitter<C>,
) -> anyhow::Result<()>
where
Expand All @@ -303,7 +305,7 @@ where

fn get_balance(
recv_chain: &LocalChain,
recv_graph: &IndexedTxGraph<BlockId, SpkTxOutIndex<()>>,
recv_graph: &IndexedTxGraph<BlockTime, SpkTxOutIndex<()>>,
) -> anyhow::Result<Balance> {
let chain_tip = recv_chain.tip().block_id();
let outpoints = recv_graph.index.outpoints().clone();
Expand Down Expand Up @@ -341,7 +343,7 @@ fn tx_can_become_unconfirmed_after_reorg() -> anyhow::Result<()> {

// setup receiver
let (mut recv_chain, _) = LocalChain::from_genesis_hash(env.rpc_client().get_block_hash(0)?);
let mut recv_graph = IndexedTxGraph::<BlockId, _>::new({
let mut recv_graph = IndexedTxGraph::new({
let mut recv_index = SpkTxOutIndex::default();
recv_index.insert_spk((), spk_to_track.clone());
recv_index
Expand Down
144 changes: 40 additions & 104 deletions crates/chain/src/chain_data.rs
Original file line number Diff line number Diff line change
@@ -1,45 +1,33 @@
use bitcoin::{hashes::Hash, BlockHash, OutPoint, TxOut, Txid};

use crate::{Anchor, AnchorFromBlockPosition, COINBASE_MATURITY};
use crate::{Anchor, BlockTime, COINBASE_MATURITY};

/// Represents the observed position 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 ChainPosition<A> {
/// The chain data is seen as confirmed, and in anchored by `A`.
Confirmed(A),
/// The chain data is seen as confirmed, and in anchored by `Anchor`.
Confirmed(Anchor, A),
/// The chain data is not confirmed and last seen in the mempool at this timestamp.
Unconfirmed(u64),
}

impl<A> ChainPosition<A> {
/// Returns whether [`ChainPosition`] is confirmed or not.
pub fn is_confirmed(&self) -> bool {
matches!(self, Self::Confirmed(_))
matches!(self, Self::Confirmed(_, _))
}
}

impl<A: Clone> ChainPosition<&A> {
/// Maps a [`ChainPosition<&A>`] into a [`ChainPosition<A>`] by cloning the contents.
impl<A: Clone> ChainPosition<A> {
/// Maps a [`ChainPosition`] into a [`ChainPosition`] by cloning the contents.
pub fn cloned(self) -> ChainPosition<A> {
match self {
ChainPosition::Confirmed(a) => ChainPosition::Confirmed(a.clone()),
ChainPosition::Confirmed(anchor, a) => ChainPosition::Confirmed(anchor, a),
ChainPosition::Unconfirmed(last_seen) => ChainPosition::Unconfirmed(last_seen),
}
}
}

impl<A: Anchor> ChainPosition<A> {
/// Determines the upper bound of the confirmation height.
pub fn confirmation_height_upper_bound(&self) -> Option<u32> {
match self {
ChainPosition::Confirmed(a) => Some(a.confirmation_height_upper_bound()),
ChainPosition::Unconfirmed(_) => None,
}
}
}

/// Block height and timestamp at which a transaction is confirmed.
#[derive(Debug, Clone, PartialEq, Eq, Copy, PartialOrd, Ord, core::hash::Hash)]
#[cfg_attr(
Expand Down Expand Up @@ -74,12 +62,12 @@ impl ConfirmationTime {
}
}

impl From<ChainPosition<ConfirmationBlockTime>> for ConfirmationTime {
fn from(observed_as: ChainPosition<ConfirmationBlockTime>) -> Self {
impl From<ChainPosition<BlockTime>> for ConfirmationTime {
fn from(observed_as: ChainPosition<BlockTime>) -> Self {
match observed_as {
ChainPosition::Confirmed(a) => Self::Confirmed {
height: a.block_id.height,
time: a.confirmation_time,
ChainPosition::Confirmed((_txid, blockid), anchor_meta) => Self::Confirmed {
height: blockid.height,
time: *anchor_meta.as_ref() as u64,
},
ChainPosition::Unconfirmed(last_seen) => Self::Unconfirmed { last_seen },
}
Expand All @@ -103,18 +91,6 @@ pub struct BlockId {
pub hash: BlockHash,
}

impl Anchor for BlockId {
fn anchor_block(&self) -> Self {
*self
}
}

impl AnchorFromBlockPosition for BlockId {
fn from_block_position(_block: &bitcoin::Block, block_id: BlockId, _tx_pos: usize) -> Self {
block_id
}
}

impl Default for BlockId {
fn default() -> Self {
Self {
Expand Down Expand Up @@ -145,41 +121,6 @@ impl From<(&u32, &BlockHash)> for BlockId {
}
}

/// An [`Anchor`] implementation that also records the exact confirmation time of the transaction.
///
/// Refer to [`Anchor`] for more details.
#[derive(Debug, Default, Clone, PartialEq, Eq, Copy, PartialOrd, Ord, core::hash::Hash)]
#[cfg_attr(
feature = "serde",
derive(serde::Deserialize, serde::Serialize),
serde(crate = "serde_crate")
)]
pub struct ConfirmationBlockTime {
/// The anchor block.
pub block_id: BlockId,
/// The confirmation time of the transaction being anchored.
pub confirmation_time: u64,
}

impl Anchor for ConfirmationBlockTime {
fn anchor_block(&self) -> BlockId {
self.block_id
}

fn confirmation_height_upper_bound(&self) -> u32 {
self.block_id.height
}
}

impl AnchorFromBlockPosition for ConfirmationBlockTime {
fn from_block_position(block: &bitcoin::Block, block_id: BlockId, _tx_pos: usize) -> Self {
Self {
block_id,
confirmation_time: block.header.time as _,
}
}
}

/// A `TxOut` with as much data as we can retrieve about it
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct FullTxOut<A> {
Expand All @@ -195,18 +136,12 @@ pub struct FullTxOut<A> {
pub is_on_coinbase: bool,
}

impl<A: Anchor> FullTxOut<A> {
impl<A> FullTxOut<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
pub fn is_mature(&self, tip: u32) -> bool {
if self.is_on_coinbase {
let tx_height = match &self.chain_position {
ChainPosition::Confirmed(anchor) => anchor.confirmation_height_upper_bound(),
ChainPosition::Confirmed((_, blockid), _) => blockid.height,
ChainPosition::Unconfirmed(_) => {
debug_assert!(false, "coinbase tx can never be unconfirmed");
return false;
Expand All @@ -224,28 +159,22 @@ impl<A: Anchor> FullTxOut<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 confirmation_height = match &self.chain_position {
ChainPosition::Confirmed(anchor) => anchor.confirmation_height_upper_bound(),
ChainPosition::Confirmed((_, blockid), _) => blockid.height,
ChainPosition::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((ChainPosition::Confirmed(spending_anchor), _)) = &self.spent_by {
if spending_anchor.anchor_block().height <= tip {
if let Some((ChainPosition::Confirmed((_, spending_blockid), _), _)) = &self.spent_by {
if spending_blockid.height <= tip {
return false;
}
}
Expand All @@ -257,25 +186,32 @@ impl<A: Anchor> FullTxOut<A> {
#[cfg(test)]
mod test {
use super::*;
use crate::BlockTime;

#[test]
fn chain_position_ord() {
let unconf1 = ChainPosition::<ConfirmationBlockTime>::Unconfirmed(10);
let unconf2 = ChainPosition::<ConfirmationBlockTime>::Unconfirmed(20);
let conf1 = ChainPosition::Confirmed(ConfirmationBlockTime {
confirmation_time: 20,
block_id: BlockId {
height: 9,
..Default::default()
},
});
let conf2 = ChainPosition::Confirmed(ConfirmationBlockTime {
confirmation_time: 15,
block_id: BlockId {
height: 12,
..Default::default()
},
});
let unconf1 = ChainPosition::Unconfirmed(10);
let unconf2 = ChainPosition::Unconfirmed(20);
let conf1 = ChainPosition::Confirmed(
(
Txid::all_zeros(),
BlockId {
height: 9,
..Default::default()
},
),
BlockTime::new(20),
);
let conf2 = ChainPosition::Confirmed(
(
Txid::all_zeros(),
BlockId {
height: 12,
..Default::default()
},
),
BlockTime::new(15),
);

assert!(unconf2 > unconf1, "higher last_seen means higher ord");
assert!(unconf1 > conf1, "unconfirmed is higher ord than confirmed");
Expand Down
5 changes: 4 additions & 1 deletion crates/chain/src/changeset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,10 @@ impl<K, A> core::default::Default for CombinedChangeSet<K, A> {
}

#[cfg(feature = "miniscript")]
impl<K: Ord, A: crate::Anchor> crate::Merge for CombinedChangeSet<K, A> {
impl<K: Ord, A> crate::Merge for CombinedChangeSet<K, A>
where
A: Ord + Clone,
{
fn merge(&mut self, other: Self) {
crate::Merge::merge(&mut self.chain, other.chain);
crate::Merge::merge(&mut self.indexed_tx_graph, other.indexed_tx_graph);
Expand Down
Loading

0 comments on commit 8605283

Please sign in to comment.