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 17, 2024
1 parent d99b3ef commit 6afb3a2
Show file tree
Hide file tree
Showing 25 changed files with 768 additions and 904 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::<BlockTime, _>::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::<BlockTime, _>::new({
let mut recv_index = SpkTxOutIndex::default();
recv_index.insert_spk((), spk_to_track.clone());
recv_index
Expand Down
142 changes: 45 additions & 97 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),
pub enum ChainPosition<AM> {
/// The chain data is seen as confirmed, and in anchored by `Anchor`.
Confirmed((Anchor, AM)),
/// The chain data is not confirmed and last seen in the mempool at this timestamp.
Unconfirmed(u64),
}

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

impl<A: Clone> ChainPosition<&A> {
/// Maps a [`ChainPosition<&A>`] into a [`ChainPosition<A>`] by cloning the contents.
pub fn cloned(self) -> ChainPosition<A> {
impl<AM: Clone> ChainPosition<AM> {
/// Maps a [`ChainPosition`] into a [`ChainPosition`] by cloning the contents.
pub fn cloned(self) -> ChainPosition<AM> {
match self {
ChainPosition::Confirmed(a) => ChainPosition::Confirmed(a.clone()),
ChainPosition::Confirmed(a) => ChainPosition::Confirmed(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,57 +121,22 @@ 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> {
pub struct FullTxOut<AM> {
/// The position of the transaction in `outpoint` in the overall chain.
pub chain_position: ChainPosition<A>,
pub chain_position: ChainPosition<AM>,
/// The location of the `TxOut`.
pub outpoint: OutPoint,
/// The `TxOut`.
pub txout: TxOut,
/// The txid and chain position of the transaction (if any) that has spent this output.
pub spent_by: Option<(ChainPosition<A>, Txid)>,
pub spent_by: Option<(ChainPosition<AM>, Txid)>,
/// Whether this output is on a coinbase transaction.
pub is_on_coinbase: bool,
}

impl<A: Anchor> FullTxOut<A> {
impl<AM> FullTxOut<AM> {
/// Whether the `txout` is considered mature.
///
/// Depending on the implementation of [`confirmation_height_upper_bound`] in [`Anchor`], this
Expand All @@ -206,7 +147,7 @@ impl<A: Anchor> FullTxOut<A> {
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 Down Expand Up @@ -236,16 +177,16 @@ impl<A: Anchor> FullTxOut<A> {
}

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 +198,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
26 changes: 15 additions & 11 deletions crates/chain/src/changeset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,23 @@
serde(
crate = "crate::serde",
bound(
deserialize = "A: Ord + crate::serde::Deserialize<'de>, K: Ord + crate::serde::Deserialize<'de>",
serialize = "A: Ord + crate::serde::Serialize, K: Ord + crate::serde::Serialize",
deserialize = "AM: Ord + crate::serde::Deserialize<'de>, K: Ord + crate::serde::Deserialize<'de>",
serialize = "AM: Ord + crate::serde::Serialize, K: Ord + crate::serde::Serialize",
),
)
)]
pub struct CombinedChangeSet<K, A> {
pub struct CombinedChangeSet<K, AM> {
/// Changes to the [`LocalChain`](crate::local_chain::LocalChain).
pub chain: crate::local_chain::ChangeSet,
/// Changes to [`IndexedTxGraph`](crate::indexed_tx_graph::IndexedTxGraph).
pub indexed_tx_graph:
crate::indexed_tx_graph::ChangeSet<A, crate::indexer::keychain_txout::ChangeSet<K>>,
crate::indexed_tx_graph::ChangeSet<AM, crate::indexer::keychain_txout::ChangeSet<K>>,
/// Stores the network type of the transaction data.
pub network: Option<bitcoin::Network>,
}

#[cfg(feature = "miniscript")]
impl<K, A> core::default::Default for CombinedChangeSet<K, A> {
impl<K, AM> core::default::Default for CombinedChangeSet<K, AM> {
fn default() -> Self {
Self {
chain: core::default::Default::default(),
Expand All @@ -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, AM> crate::Merge for CombinedChangeSet<K, AM>
where
AM: 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 All @@ -53,7 +56,7 @@ impl<K: Ord, A: crate::Anchor> crate::Merge for CombinedChangeSet<K, A> {
}

#[cfg(feature = "miniscript")]
impl<K, A> From<crate::local_chain::ChangeSet> for CombinedChangeSet<K, A> {
impl<K, AM> From<crate::local_chain::ChangeSet> for CombinedChangeSet<K, AM> {
fn from(chain: crate::local_chain::ChangeSet) -> Self {
Self {
chain,
Expand All @@ -63,12 +66,13 @@ impl<K, A> From<crate::local_chain::ChangeSet> for CombinedChangeSet<K, A> {
}

#[cfg(feature = "miniscript")]
impl<K, A> From<crate::indexed_tx_graph::ChangeSet<A, crate::indexer::keychain_txout::ChangeSet<K>>>
for CombinedChangeSet<K, A>
impl<K, AM>
From<crate::indexed_tx_graph::ChangeSet<AM, crate::indexer::keychain_txout::ChangeSet<K>>>
for CombinedChangeSet<K, AM>
{
fn from(
indexed_tx_graph: crate::indexed_tx_graph::ChangeSet<
A,
AM,
crate::indexer::keychain_txout::ChangeSet<K>,
>,
) -> Self {
Expand All @@ -80,7 +84,7 @@ impl<K, A> From<crate::indexed_tx_graph::ChangeSet<A, crate::indexer::keychain_t
}

#[cfg(feature = "miniscript")]
impl<K, A> From<crate::indexer::keychain_txout::ChangeSet<K>> for CombinedChangeSet<K, A> {
impl<K, AM> From<crate::indexer::keychain_txout::ChangeSet<K>> for CombinedChangeSet<K, AM> {
fn from(indexer: crate::indexer::keychain_txout::ChangeSet<K>) -> Self {
Self {
indexed_tx_graph: crate::indexed_tx_graph::ChangeSet {
Expand Down
Loading

0 comments on commit 6afb3a2

Please sign in to comment.