Skip to content

Commit

Permalink
[chain_redesign] BlockId should not implement Anchor
Browse files Browse the repository at this point in the history
If `BlockId` implements `Anchor`, the meaning is ambiguous. We cannot
tell whether it means the tx is anchors at the block, or whether it also
means the tx is confirmed at that block.

Instead, `ConfirmationHeightAnchor` and `ConfirmationTimeAnchor` structs
are introduced as non-ambiguous `Anchor` implementations.

Additionally, `TxGraph::relevant_heights` is removed because it is also
ambiguous. What heights are deemed relevant? A simpler and more flexible
method `TxGraph::all_anchors` is introduced instead.
  • Loading branch information
evanlinjin committed May 3, 2023
1 parent 79e4e68 commit 8de421f
Show file tree
Hide file tree
Showing 4 changed files with 167 additions and 180 deletions.
58 changes: 52 additions & 6 deletions crates/chain/src/chain_data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,12 +160,6 @@ impl Default for BlockId {
}
}

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 }
Expand All @@ -187,6 +181,58 @@ impl From<(&u32, &BlockHash)> for BlockId {
}
}

/// An [`Anchor`] implementation that also records the exact confirmation height of the transaction.
#[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 ConfirmationHeightAnchor {
/// The anchor block.
pub anchor_block: BlockId,

/// The exact confirmation height of the transaction.
///
/// It is assumed that this value is never larger than the height of the anchor block.
pub confirmation_height: u32,
}

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

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

/// An [`Anchor`] implementation that also records the exact confirmation time and height of the
/// transaction.
#[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 ConfirmationTimeAnchor {
/// The anchor block.
pub anchor_block: BlockId,

pub confirmation_height: u32,
pub confirmation_time: u64,
}

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

fn confirmation_height_upper_bound(&self) -> u32 {
self.confirmation_height
}
}
/// A `TxOut` with as much data as we can retrieve about it
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct FullTxOut<P> {
Expand Down
20 changes: 5 additions & 15 deletions crates/chain/src/tx_graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,11 @@ impl<A> TxGraph<A> {
.filter(move |(_, conflicting_txid)| *conflicting_txid != txid)
}

/// Iterates over all transaction anchors known by [`TxGraph`].
pub fn all_anchors(&self) -> impl ExactSizeIterator<Item = &(A, Txid)> + DoubleEndedIterator {
self.anchors.iter()
}

/// Whether the graph has any transactions or outputs in it.
pub fn is_empty(&self) -> bool {
self.txs.is_empty()
Expand Down Expand Up @@ -592,21 +597,6 @@ impl<A: Clone + Ord> TxGraph<A> {
}

impl<A: Anchor> TxGraph<A> {
/// Get all heights that are relevant to the graph.
pub fn relevant_heights(&self) -> impl Iterator<Item = u32> + '_ {
let mut last_height = Option::<u32>::None;
self.anchors
.iter()
.map(|(a, _)| a.anchor_block().height)
.filter(move |&height| {
let is_unique = Some(height) != last_height;
if is_unique {
last_height = Some(height);
}
is_unique
})
}

/// Get the position of the transaction in `chain` with tip `chain_tip`.
///
/// If the given transaction of `txid` does not exist in the chain of `chain_tip`, `None` is
Expand Down
163 changes: 86 additions & 77 deletions crates/chain/tests/test_indexed_tx_graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use bdk_chain::{
keychain::{Balance, DerivationAdditions, KeychainTxOutIndex},
local_chain::LocalChain,
tx_graph::Additions,
BlockId, ObservedAs,
ConfirmationHeightAnchor, ObservedAs,
};
use bitcoin::{secp256k1::Secp256k1, BlockHash, OutPoint, Script, Transaction, TxIn, TxOut};
use miniscript::Descriptor;
Expand All @@ -28,7 +28,7 @@ fn insert_relevant_txs() {
let spk_0 = descriptor.at_derivation_index(0).script_pubkey();
let spk_1 = descriptor.at_derivation_index(9).script_pubkey();

let mut graph = IndexedTxGraph::<BlockId, KeychainTxOutIndex<()>>::default();
let mut graph = IndexedTxGraph::<ConfirmationHeightAnchor, KeychainTxOutIndex<()>>::default();
graph.index.add_keychain((), descriptor);
graph.index.set_lookahead(&(), 10);

Expand Down Expand Up @@ -118,7 +118,8 @@ fn test_list_owned_txouts() {
let (desc_1, _) = Descriptor::parse_descriptor(&Secp256k1::signing_only(), "tr(tprv8ZgxMBicQKsPd3krDUsBAmtnRsK3rb8u5yi1zhQgMhF1tR8MW7xfE4rnrbbsrbPR52e7rKapu6ztw1jXveJSCGHEriUGZV7mCe88duLp5pj/86'/1'/0'/0/*)").unwrap();
let (desc_2, _) = Descriptor::parse_descriptor(&Secp256k1::signing_only(), "tr(tprv8ZgxMBicQKsPd3krDUsBAmtnRsK3rb8u5yi1zhQgMhF1tR8MW7xfE4rnrbbsrbPR52e7rKapu6ztw1jXveJSCGHEriUGZV7mCe88duLp5pj/86'/1'/0'/1/*)").unwrap();

let mut graph = IndexedTxGraph::<BlockId, KeychainTxOutIndex<String>>::default();
let mut graph =
IndexedTxGraph::<ConfirmationHeightAnchor, KeychainTxOutIndex<String>>::default();

graph.index.add_keychain("keychain_1".into(), desc_1);
graph.index.add_keychain("keychain_2".into(), desc_2);
Expand Down Expand Up @@ -206,86 +207,94 @@ fn test_list_owned_txouts() {
// For unconfirmed txs we pass in `None`.

let _ = graph.insert_relevant_txs(
[&tx1, &tx2, &tx3, &tx6]
.iter()
.enumerate()
.map(|(i, tx)| (*tx, [local_chain.get_block(i as u32).unwrap()])),
[&tx1, &tx2, &tx3, &tx6].iter().enumerate().map(|(i, tx)| {
(
*tx,
local_chain
.get_block(i as u32)
.map(|anchor_block| ConfirmationHeightAnchor {
anchor_block,
confirmation_height: anchor_block.height,
}),
)
}),
None,
);

let _ = graph.insert_relevant_txs([&tx4, &tx5].iter().map(|tx| (*tx, None)), Some(100));

// A helper lambda to extract and filter data from the graph.
let fetch = |ht: u32, graph: &IndexedTxGraph<BlockId, KeychainTxOutIndex<String>>| {
let txouts = graph
.list_owned_txouts(&local_chain, local_chain.get_block(ht).unwrap())
.collect::<Vec<_>>();

let utxos = graph
.list_owned_unspents(&local_chain, local_chain.get_block(ht).unwrap())
.collect::<Vec<_>>();

let balance = graph.balance(
&local_chain,
local_chain.get_block(ht).unwrap(),
|spk: &Script| trusted_spks.contains(spk),
);

assert_eq!(txouts.len(), 5);
assert_eq!(utxos.len(), 4);

let confirmed_txouts_txid = txouts
.iter()
.filter_map(|full_txout| {
if matches!(full_txout.chain_position, ObservedAs::Confirmed(_)) {
Some(full_txout.outpoint.txid)
} else {
None
}
})
.collect::<BTreeSet<_>>();

let unconfirmed_txouts_txid = txouts
.iter()
.filter_map(|full_txout| {
if matches!(full_txout.chain_position, ObservedAs::Unconfirmed(_)) {
Some(full_txout.outpoint.txid)
} else {
None
}
})
.collect::<BTreeSet<_>>();

let confirmed_utxos_txid = utxos
.iter()
.filter_map(|full_txout| {
if matches!(full_txout.chain_position, ObservedAs::Confirmed(_)) {
Some(full_txout.outpoint.txid)
} else {
None
}
})
.collect::<BTreeSet<_>>();

let unconfirmed_utxos_txid = utxos
.iter()
.filter_map(|full_txout| {
if matches!(full_txout.chain_position, ObservedAs::Unconfirmed(_)) {
Some(full_txout.outpoint.txid)
} else {
None
}
})
.collect::<BTreeSet<_>>();

(
confirmed_txouts_txid,
unconfirmed_txouts_txid,
confirmed_utxos_txid,
unconfirmed_utxos_txid,
balance,
)
};
let fetch =
|ht: u32, graph: &IndexedTxGraph<ConfirmationHeightAnchor, KeychainTxOutIndex<String>>| {
let txouts = graph
.list_owned_txouts(&local_chain, local_chain.get_block(ht).unwrap())
.collect::<Vec<_>>();

let utxos = graph
.list_owned_unspents(&local_chain, local_chain.get_block(ht).unwrap())
.collect::<Vec<_>>();

let balance = graph.balance(
&local_chain,
local_chain.get_block(ht).unwrap(),
|spk: &Script| trusted_spks.contains(spk),
);

assert_eq!(txouts.len(), 5);
assert_eq!(utxos.len(), 4);

let confirmed_txouts_txid = txouts
.iter()
.filter_map(|full_txout| {
if matches!(full_txout.chain_position, ObservedAs::Confirmed(_)) {
Some(full_txout.outpoint.txid)
} else {
None
}
})
.collect::<BTreeSet<_>>();

let unconfirmed_txouts_txid = txouts
.iter()
.filter_map(|full_txout| {
if matches!(full_txout.chain_position, ObservedAs::Unconfirmed(_)) {
Some(full_txout.outpoint.txid)
} else {
None
}
})
.collect::<BTreeSet<_>>();

let confirmed_utxos_txid = utxos
.iter()
.filter_map(|full_txout| {
if matches!(full_txout.chain_position, ObservedAs::Confirmed(_)) {
Some(full_txout.outpoint.txid)
} else {
None
}
})
.collect::<BTreeSet<_>>();

let unconfirmed_utxos_txid = utxos
.iter()
.filter_map(|full_txout| {
if matches!(full_txout.chain_position, ObservedAs::Unconfirmed(_)) {
Some(full_txout.outpoint.txid)
} else {
None
}
})
.collect::<BTreeSet<_>>();

(
confirmed_txouts_txid,
unconfirmed_txouts_txid,
confirmed_utxos_txid,
unconfirmed_utxos_txid,
balance,
)
};

// ----- TEST BLOCK -----

Expand Down
Loading

0 comments on commit 8de421f

Please sign in to comment.