Skip to content

Commit 2ccc116

Browse files
committed
[chain_redesign] BlockId should not implement Anchor
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.
1 parent 4ae727a commit 2ccc116

File tree

4 files changed

+167
-180
lines changed

4 files changed

+167
-180
lines changed

crates/chain/src/chain_data.rs

Lines changed: 52 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -160,12 +160,6 @@ impl Default for BlockId {
160160
}
161161
}
162162

163-
impl Anchor for BlockId {
164-
fn anchor_block(&self) -> BlockId {
165-
*self
166-
}
167-
}
168-
169163
impl From<(u32, BlockHash)> for BlockId {
170164
fn from((height, hash): (u32, BlockHash)) -> Self {
171165
Self { height, hash }
@@ -187,6 +181,58 @@ impl From<(&u32, &BlockHash)> for BlockId {
187181
}
188182
}
189183

184+
/// An [`Anchor`] implementation that also records the exact confirmation height of the transaction.
185+
#[derive(Debug, Default, Clone, PartialEq, Eq, Copy, PartialOrd, Ord, core::hash::Hash)]
186+
#[cfg_attr(
187+
feature = "serde",
188+
derive(serde::Deserialize, serde::Serialize),
189+
serde(crate = "serde_crate")
190+
)]
191+
pub struct ConfirmationHeightAnchor {
192+
/// The anchor block.
193+
pub anchor_block: BlockId,
194+
195+
/// The exact confirmation height of the transaction.
196+
///
197+
/// It is assumed that this value is never larger than the height of the anchor block.
198+
pub confirmation_height: u32,
199+
}
200+
201+
impl Anchor for ConfirmationHeightAnchor {
202+
fn anchor_block(&self) -> BlockId {
203+
self.anchor_block
204+
}
205+
206+
fn confirmation_height_upper_bound(&self) -> u32 {
207+
self.confirmation_height
208+
}
209+
}
210+
211+
/// An [`Anchor`] implementation that also records the exact confirmation time and height of the
212+
/// transaction.
213+
#[derive(Debug, Default, Clone, PartialEq, Eq, Copy, PartialOrd, Ord, core::hash::Hash)]
214+
#[cfg_attr(
215+
feature = "serde",
216+
derive(serde::Deserialize, serde::Serialize),
217+
serde(crate = "serde_crate")
218+
)]
219+
pub struct ConfirmationTimeAnchor {
220+
/// The anchor block.
221+
pub anchor_block: BlockId,
222+
223+
pub confirmation_height: u32,
224+
pub confirmation_time: u64,
225+
}
226+
227+
impl Anchor for ConfirmationTimeAnchor {
228+
fn anchor_block(&self) -> BlockId {
229+
self.anchor_block
230+
}
231+
232+
fn confirmation_height_upper_bound(&self) -> u32 {
233+
self.confirmation_height
234+
}
235+
}
190236
/// A `TxOut` with as much data as we can retrieve about it
191237
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
192238
pub struct FullTxOut<P> {

crates/chain/src/tx_graph.rs

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,11 @@ impl<A> TxGraph<A> {
349349
.filter(move |(_, conflicting_txid)| *conflicting_txid != txid)
350350
}
351351

352+
/// Get all transaction anchors known by [`TxGraph`].
353+
pub fn all_anchors(&self) -> &BTreeSet<(A, Txid)> {
354+
&self.anchors
355+
}
356+
352357
/// Whether the graph has any transactions or outputs in it.
353358
pub fn is_empty(&self) -> bool {
354359
self.txs.is_empty()
@@ -592,21 +597,6 @@ impl<A: Clone + Ord> TxGraph<A> {
592597
}
593598

594599
impl<A: Anchor> TxGraph<A> {
595-
/// Get all heights that are relevant to the graph.
596-
pub fn relevant_heights(&self) -> impl Iterator<Item = u32> + '_ {
597-
let mut last_height = Option::<u32>::None;
598-
self.anchors
599-
.iter()
600-
.map(|(a, _)| a.anchor_block().height)
601-
.filter(move |&height| {
602-
let is_unique = Some(height) != last_height;
603-
if is_unique {
604-
last_height = Some(height);
605-
}
606-
is_unique
607-
})
608-
}
609-
610600
/// Get the position of the transaction in `chain` with tip `chain_tip`.
611601
///
612602
/// If the given transaction of `txid` does not exist in the chain of `chain_tip`, `None` is

crates/chain/tests/test_indexed_tx_graph.rs

Lines changed: 86 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use bdk_chain::{
88
keychain::{Balance, DerivationAdditions, KeychainTxOutIndex},
99
local_chain::LocalChain,
1010
tx_graph::Additions,
11-
BlockId, ObservedAs,
11+
ConfirmationHeightAnchor, ObservedAs,
1212
};
1313
use bitcoin::{secp256k1::Secp256k1, BlockHash, OutPoint, Script, Transaction, TxIn, TxOut};
1414
use miniscript::Descriptor;
@@ -28,7 +28,7 @@ fn insert_relevant_txs() {
2828
let spk_0 = descriptor.at_derivation_index(0).script_pubkey();
2929
let spk_1 = descriptor.at_derivation_index(9).script_pubkey();
3030

31-
let mut graph = IndexedTxGraph::<BlockId, KeychainTxOutIndex<()>>::default();
31+
let mut graph = IndexedTxGraph::<ConfirmationHeightAnchor, KeychainTxOutIndex<()>>::default();
3232
graph.index.add_keychain((), descriptor);
3333
graph.index.set_lookahead(&(), 10);
3434

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

121-
let mut graph = IndexedTxGraph::<BlockId, KeychainTxOutIndex<String>>::default();
121+
let mut graph =
122+
IndexedTxGraph::<ConfirmationHeightAnchor, KeychainTxOutIndex<String>>::default();
122123

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

208209
let _ = graph.insert_relevant_txs(
209-
[&tx1, &tx2, &tx3, &tx6]
210-
.iter()
211-
.enumerate()
212-
.map(|(i, tx)| (*tx, [local_chain.get_block(i as u32).unwrap()])),
210+
[&tx1, &tx2, &tx3, &tx6].iter().enumerate().map(|(i, tx)| {
211+
(
212+
*tx,
213+
local_chain
214+
.get_block(i as u32)
215+
.map(|anchor_block| ConfirmationHeightAnchor {
216+
anchor_block,
217+
confirmation_height: anchor_block.height,
218+
}),
219+
)
220+
}),
213221
None,
214222
);
215223

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

218226
// A helper lambda to extract and filter data from the graph.
219-
let fetch = |ht: u32, graph: &IndexedTxGraph<BlockId, KeychainTxOutIndex<String>>| {
220-
let txouts = graph
221-
.list_owned_txouts(&local_chain, local_chain.get_block(ht).unwrap())
222-
.collect::<Vec<_>>();
223-
224-
let utxos = graph
225-
.list_owned_unspents(&local_chain, local_chain.get_block(ht).unwrap())
226-
.collect::<Vec<_>>();
227-
228-
let balance = graph.balance(
229-
&local_chain,
230-
local_chain.get_block(ht).unwrap(),
231-
|spk: &Script| trusted_spks.contains(spk),
232-
);
233-
234-
assert_eq!(txouts.len(), 5);
235-
assert_eq!(utxos.len(), 4);
236-
237-
let confirmed_txouts_txid = txouts
238-
.iter()
239-
.filter_map(|full_txout| {
240-
if matches!(full_txout.chain_position, ObservedAs::Confirmed(_)) {
241-
Some(full_txout.outpoint.txid)
242-
} else {
243-
None
244-
}
245-
})
246-
.collect::<BTreeSet<_>>();
247-
248-
let unconfirmed_txouts_txid = txouts
249-
.iter()
250-
.filter_map(|full_txout| {
251-
if matches!(full_txout.chain_position, ObservedAs::Unconfirmed(_)) {
252-
Some(full_txout.outpoint.txid)
253-
} else {
254-
None
255-
}
256-
})
257-
.collect::<BTreeSet<_>>();
258-
259-
let confirmed_utxos_txid = utxos
260-
.iter()
261-
.filter_map(|full_txout| {
262-
if matches!(full_txout.chain_position, ObservedAs::Confirmed(_)) {
263-
Some(full_txout.outpoint.txid)
264-
} else {
265-
None
266-
}
267-
})
268-
.collect::<BTreeSet<_>>();
269-
270-
let unconfirmed_utxos_txid = utxos
271-
.iter()
272-
.filter_map(|full_txout| {
273-
if matches!(full_txout.chain_position, ObservedAs::Unconfirmed(_)) {
274-
Some(full_txout.outpoint.txid)
275-
} else {
276-
None
277-
}
278-
})
279-
.collect::<BTreeSet<_>>();
280-
281-
(
282-
confirmed_txouts_txid,
283-
unconfirmed_txouts_txid,
284-
confirmed_utxos_txid,
285-
unconfirmed_utxos_txid,
286-
balance,
287-
)
288-
};
227+
let fetch =
228+
|ht: u32, graph: &IndexedTxGraph<ConfirmationHeightAnchor, KeychainTxOutIndex<String>>| {
229+
let txouts = graph
230+
.list_owned_txouts(&local_chain, local_chain.get_block(ht).unwrap())
231+
.collect::<Vec<_>>();
232+
233+
let utxos = graph
234+
.list_owned_unspents(&local_chain, local_chain.get_block(ht).unwrap())
235+
.collect::<Vec<_>>();
236+
237+
let balance = graph.balance(
238+
&local_chain,
239+
local_chain.get_block(ht).unwrap(),
240+
|spk: &Script| trusted_spks.contains(spk),
241+
);
242+
243+
assert_eq!(txouts.len(), 5);
244+
assert_eq!(utxos.len(), 4);
245+
246+
let confirmed_txouts_txid = txouts
247+
.iter()
248+
.filter_map(|full_txout| {
249+
if matches!(full_txout.chain_position, ObservedAs::Confirmed(_)) {
250+
Some(full_txout.outpoint.txid)
251+
} else {
252+
None
253+
}
254+
})
255+
.collect::<BTreeSet<_>>();
256+
257+
let unconfirmed_txouts_txid = txouts
258+
.iter()
259+
.filter_map(|full_txout| {
260+
if matches!(full_txout.chain_position, ObservedAs::Unconfirmed(_)) {
261+
Some(full_txout.outpoint.txid)
262+
} else {
263+
None
264+
}
265+
})
266+
.collect::<BTreeSet<_>>();
267+
268+
let confirmed_utxos_txid = utxos
269+
.iter()
270+
.filter_map(|full_txout| {
271+
if matches!(full_txout.chain_position, ObservedAs::Confirmed(_)) {
272+
Some(full_txout.outpoint.txid)
273+
} else {
274+
None
275+
}
276+
})
277+
.collect::<BTreeSet<_>>();
278+
279+
let unconfirmed_utxos_txid = utxos
280+
.iter()
281+
.filter_map(|full_txout| {
282+
if matches!(full_txout.chain_position, ObservedAs::Unconfirmed(_)) {
283+
Some(full_txout.outpoint.txid)
284+
} else {
285+
None
286+
}
287+
})
288+
.collect::<BTreeSet<_>>();
289+
290+
(
291+
confirmed_txouts_txid,
292+
unconfirmed_txouts_txid,
293+
confirmed_utxos_txid,
294+
unconfirmed_utxos_txid,
295+
balance,
296+
)
297+
};
289298

290299
// ----- TEST BLOCK -----
291300

0 commit comments

Comments
 (0)