Skip to content

Commit 677e25a

Browse files
oleonardolimaclaude
andcommitted
refactor(chain)!: generalize ChainQuery trait with generic type
Make `ChainRequest`/`ChainResponse` generic over block identifier types to enable reuse beyond BlockId. Move `chain_tip` into `ChainRequest` for better encapsulation and simpler API. - Make `ChainRequest` and `ChainResponse` generic types with `BlockId` as default - Add `chain_tip` field to `ChainRequest` to make it self-contained - Change `ChainQuery` trait to use generic parameter `B` for block identifier type - Remove `chain_tip` parameter from `LocalChain::canonicalize()` method - Rename `ChainQuery::Result` to `ChainQuery::Output` for clarity BREAKING CHANGE: - `ChainRequest` now has a `chain_tip` field and is generic over block identifier type - `ChainResponse` is now generic with default type parameter `BlockId` - `ChainQuery` trait now takes a generic parameter `B = BlockId` - `LocalChain::canonicalize()` no longer takes a `chain_tip` parameter Co-authored-by: Claude <[email protected]>
1 parent cd06dac commit 677e25a

File tree

21 files changed

+215
-191
lines changed

21 files changed

+215
-191
lines changed

crates/bitcoind_rpc/examples/filter_iter.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,9 @@ fn main() -> anyhow::Result<()> {
6969
println!("\ntook: {}s", start.elapsed().as_secs());
7070
println!("Local tip: {}", chain.tip().height());
7171

72-
let task = graph.canonicalization_task(Default::default());
73-
let canonical_view = chain.canonicalize(task, Some(chain.tip().block_id()));
72+
let chain_tip = chain.tip().block_id();
73+
let task = graph.canonicalization_task(chain_tip, Default::default());
74+
let canonical_view = chain.canonicalize(task);
7475

7576
let unspent: Vec<_> = canonical_view
7677
.filter_unspent_outpoints(graph.index.outpoints().clone())

crates/bitcoind_rpc/tests/test_emitter.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -312,9 +312,9 @@ fn get_balance(
312312
let outpoints = recv_graph.index.outpoints().clone();
313313
let task = recv_graph
314314
.graph()
315-
.canonicalization_task(CanonicalizationParams::default());
315+
.canonicalization_task(chain_tip, CanonicalizationParams::default());
316316
let balance = recv_chain
317-
.canonicalize(task, Some(chain_tip))
317+
.canonicalize(task)
318318
.balance(outpoints, |_, _| true, 0);
319319
Ok(balance)
320320
}
@@ -619,9 +619,9 @@ fn test_expect_tx_evicted() -> anyhow::Result<()> {
619619
let _txid_2 = core.send_raw_transaction(&tx1b)?;
620620

621621
// Retrieve the expected unconfirmed txids and spks from the graph.
622-
let task = graph.canonicalization_task(Default::default());
622+
let task = graph.canonicalization_task(chain_tip, Default::default());
623623
let exp_spk_txids = chain
624-
.canonicalize(task, Some(chain_tip))
624+
.canonicalize(task)
625625
.list_expected_spk_txids(&graph.index, ..)
626626
.collect::<Vec<_>>();
627627
assert_eq!(exp_spk_txids, vec![(spk, txid_1)]);
@@ -638,9 +638,9 @@ fn test_expect_tx_evicted() -> anyhow::Result<()> {
638638

639639
let task = graph
640640
.graph()
641-
.canonicalization_task(CanonicalizationParams::default());
641+
.canonicalization_task(chain_tip, CanonicalizationParams::default());
642642
let canonical_txids = chain
643-
.canonicalize(task, Some(chain_tip))
643+
.canonicalize(task)
644644
.txs()
645645
.map(|tx| tx.txid)
646646
.collect::<Vec<_>>();

crates/chain/benches/canonicalization.rs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -95,28 +95,31 @@ fn setup<F: Fn(&mut KeychainTxGraph, &LocalChain)>(f: F) -> (KeychainTxGraph, Lo
9595
}
9696

9797
fn run_list_canonical_txs(tx_graph: &KeychainTxGraph, chain: &LocalChain, exp_txs: usize) {
98+
let chain_tip = chain.tip().block_id();
9899
let task = tx_graph
99100
.graph()
100-
.canonicalization_task(CanonicalizationParams::default());
101-
let view = chain.canonicalize(task, Some(chain.tip().block_id()));
101+
.canonicalization_task(chain_tip, CanonicalizationParams::default());
102+
let view = chain.canonicalize(task);
102103
let txs = view.txs();
103104
assert_eq!(txs.count(), exp_txs);
104105
}
105106

106107
fn run_filter_chain_txouts(tx_graph: &KeychainTxGraph, chain: &LocalChain, exp_txos: usize) {
108+
let chain_tip = chain.tip().block_id();
107109
let task = tx_graph
108110
.graph()
109-
.canonicalization_task(CanonicalizationParams::default());
110-
let view = chain.canonicalize(task, Some(chain.tip().block_id()));
111+
.canonicalization_task(chain_tip, CanonicalizationParams::default());
112+
let view = chain.canonicalize(task);
111113
let utxos = view.filter_outpoints(tx_graph.index.outpoints().clone());
112114
assert_eq!(utxos.count(), exp_txos);
113115
}
114116

115117
fn run_filter_chain_unspents(tx_graph: &KeychainTxGraph, chain: &LocalChain, exp_utxos: usize) {
118+
let chain_tip = chain.tip().block_id();
116119
let task = tx_graph
117120
.graph()
118-
.canonicalization_task(CanonicalizationParams::default());
119-
let view = chain.canonicalize(task, Some(chain.tip().block_id()));
121+
.canonicalization_task(chain_tip, CanonicalizationParams::default());
122+
let view = chain.canonicalize(task);
120123
let utxos = view.filter_unspent_outpoints(tx_graph.index.outpoints().clone());
121124
assert_eq!(utxos.count(), exp_utxos);
122125
}

crates/chain/benches/indexer.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -86,10 +86,8 @@ fn do_bench(indexed_tx_graph: &KeychainTxGraph, chain: &LocalChain) {
8686
let op = graph.index.outpoints().clone();
8787
let task = graph
8888
.graph()
89-
.canonicalization_task(CanonicalizationParams::default());
90-
let bal = chain
91-
.canonicalize(task, Some(chain_tip))
92-
.balance(op, |_, _| false, 1);
89+
.canonicalization_task(chain_tip, CanonicalizationParams::default());
90+
let bal = chain.canonicalize(task).balance(op, |_, _| false, 1);
9391
assert_eq!(bal.total(), AMOUNT * TX_CT as u64);
9492
}
9593

crates/chain/src/canonical_task.rs

Lines changed: 30 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use alloc::boxed::Box;
55
use alloc::collections::BTreeSet;
66
use alloc::sync::Arc;
77
use alloc::vec::Vec;
8-
use bdk_core::{BlockId, ChainQuery};
8+
use bdk_core::{BlockId, ChainQuery, ChainRequest, ChainResponse};
99
use bitcoin::{Transaction, Txid};
1010

1111
type CanonicalMap<A> = HashMap<Txid, (Arc<Transaction>, CanonicalReason<A>)>;
@@ -21,19 +21,10 @@ pub struct CanonicalizationParams {
2121
pub assume_canonical: Vec<Txid>,
2222
}
2323

24-
/// A request to check which anchors are confirmed in the chain.
25-
#[derive(Debug, Clone, PartialEq, Eq)]
26-
pub struct CanonicalizationRequest<A> {
27-
/// The anchors to check.
28-
pub anchors: Vec<A>,
29-
}
30-
31-
/// Response containing the best confirmed anchor, if any.
32-
pub type CanonicalizationResponse<A> = Option<A>;
33-
3424
/// Manages the canonicalization process without direct I/O operations.
3525
pub struct CanonicalizationTask<'g, A> {
3626
tx_graph: &'g TxGraph<A>,
27+
chain_tip: BlockId,
3728

3829
unprocessed_assumed_txs: Box<dyn Iterator<Item = (Txid, Arc<Transaction>)> + 'g>,
3930
unprocessed_anchored_txs:
@@ -54,26 +45,34 @@ pub struct CanonicalizationTask<'g, A> {
5445
}
5546

5647
impl<'g, A: Anchor> ChainQuery for CanonicalizationTask<'g, A> {
57-
type Request = CanonicalizationRequest<A>;
58-
type Response = CanonicalizationResponse<A>;
59-
type Context = BlockId;
60-
type Result = CanonicalView<A>;
48+
type Output = CanonicalView<A>;
6149

62-
fn next_query(&mut self) -> Option<Self::Request> {
50+
fn next_query(&mut self) -> Option<ChainRequest> {
6351
// Check if we have pending anchor checks
6452
if let Some((_, _, anchors)) = self.pending_anchor_checks.front() {
65-
return Some(CanonicalizationRequest {
66-
anchors: anchors.clone(),
53+
// Convert anchors to BlockIds for the ChainRequest
54+
let block_ids = anchors.iter().map(|anchor| anchor.anchor_block()).collect();
55+
return Some(ChainRequest {
56+
chain_tip: self.chain_tip,
57+
block_ids,
6758
});
6859
}
6960

7061
// Process more anchored transactions if available
7162
self.process_anchored_txs()
7263
}
7364

74-
fn resolve_query(&mut self, response: Self::Response) {
65+
fn resolve_query(&mut self, response: ChainResponse) {
7566
if let Some((txid, tx, anchors)) = self.pending_anchor_checks.pop_front() {
76-
match response {
67+
// Find the anchor that matches the confirmed BlockId
68+
let best_anchor = response.and_then(|block_id| {
69+
anchors
70+
.iter()
71+
.find(|anchor| anchor.anchor_block() == block_id)
72+
.cloned()
73+
});
74+
75+
match best_anchor {
7776
Some(best_anchor) => {
7877
self.confirmed_anchors.insert(txid, best_anchor.clone());
7978
if !self.is_canonicalized(txid) {
@@ -101,7 +100,7 @@ impl<'g, A: Anchor> ChainQuery for CanonicalizationTask<'g, A> {
101100
self.pending_anchor_checks.is_empty() && self.unprocessed_anchored_txs.size_hint().0 == 0
102101
}
103102

104-
fn finish(mut self, context: Self::Context) -> Self::Result {
103+
fn finish(mut self) -> Self::Output {
105104
// Process remaining transactions (seen and leftover)
106105
self.process_seen_txs();
107106
self.process_leftover_txs();
@@ -181,13 +180,17 @@ impl<'g, A: Anchor> ChainQuery for CanonicalizationTask<'g, A> {
181180
}
182181
}
183182

184-
CanonicalView::new(context, view_order, view_txs, view_spends)
183+
CanonicalView::new(self.chain_tip, view_order, view_txs, view_spends)
185184
}
186185
}
187186

188187
impl<'g, A: Anchor> CanonicalizationTask<'g, A> {
189188
/// Creates a new canonicalization task.
190-
pub fn new(tx_graph: &'g TxGraph<A>, params: CanonicalizationParams) -> Self {
189+
pub fn new(
190+
tx_graph: &'g TxGraph<A>,
191+
chain_tip: BlockId,
192+
params: CanonicalizationParams,
193+
) -> Self {
191194
let anchors = tx_graph.all_anchors();
192195
let unprocessed_assumed_txs = Box::new(
193196
params
@@ -209,6 +212,7 @@ impl<'g, A: Anchor> CanonicalizationTask<'g, A> {
209212

210213
let mut task = Self {
211214
tx_graph,
215+
chain_tip,
212216

213217
unprocessed_assumed_txs,
214218
unprocessed_anchored_txs,
@@ -242,7 +246,7 @@ impl<'g, A: Anchor> CanonicalizationTask<'g, A> {
242246
}
243247
}
244248

245-
fn process_anchored_txs(&mut self) -> Option<CanonicalizationRequest<A>> {
249+
fn process_anchored_txs(&mut self) -> Option<ChainRequest> {
246250
while let Some((txid, tx, anchors)) = self.unprocessed_anchored_txs.next() {
247251
if !self.is_canonicalized(txid) {
248252
self.pending_anchor_checks
@@ -512,8 +516,8 @@ mod tests {
512516

513517
// Create canonicalization task and canonicalize using the chain
514518
let params = CanonicalizationParams::default();
515-
let task = CanonicalizationTask::new(&tx_graph, params);
516-
let canonical_view = chain.canonicalize(task, Some(chain_tip));
519+
let task = CanonicalizationTask::new(&tx_graph, chain_tip, params);
520+
let canonical_view = chain.canonicalize(task);
517521

518522
// Should have one canonical transaction
519523
assert_eq!(canonical_view.txs().len(), 1);

crates/chain/src/canonical_view.rs

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,10 @@
1111
//! # use bitcoin::hashes::Hash;
1212
//! # let tx_graph = TxGraph::<BlockId>::default();
1313
//! # let chain = LocalChain::from_blocks([(0, bitcoin::BlockHash::all_zeros())].into_iter().collect()).unwrap();
14+
//! let chain_tip = chain.tip().block_id();
1415
//! let params = CanonicalizationParams::default();
15-
//! let task = CanonicalizationTask::new(&tx_graph, params);
16-
//! let view = chain.canonicalize(task, Some(chain.tip().block_id()));
16+
//! let task = CanonicalizationTask::new(&tx_graph, chain_tip, params);
17+
//! let view = chain.canonicalize(task);
1718
//!
1819
//! // Iterate over canonical transactions
1920
//! for tx in view.txs() {
@@ -143,8 +144,9 @@ impl<A: Anchor> CanonicalView<A> {
143144
/// # use bitcoin::hashes::Hash;
144145
/// # let tx_graph = TxGraph::<BlockId>::default();
145146
/// # let chain = LocalChain::from_blocks([(0, bitcoin::BlockHash::all_zeros())].into_iter().collect()).unwrap();
146-
/// # let task = CanonicalizationTask::new(&tx_graph, Default::default());
147-
/// # let view = chain.canonicalize(task, Some(chain.tip().block_id()));
147+
/// # let chain_tip = chain.tip().block_id();
148+
/// # let task = CanonicalizationTask::new(&tx_graph, chain_tip, Default::default());
149+
/// # let view = chain.canonicalize(task);
148150
/// // Iterate over all canonical transactions
149151
/// for tx in view.txs() {
150152
/// println!("TX {}: {:?}", tx.txid, tx.pos);
@@ -177,8 +179,9 @@ impl<A: Anchor> CanonicalView<A> {
177179
/// # use bitcoin::hashes::Hash;
178180
/// # let tx_graph = TxGraph::<BlockId>::default();
179181
/// # let chain = LocalChain::from_blocks([(0, bitcoin::BlockHash::all_zeros())].into_iter().collect()).unwrap();
180-
/// # let task = CanonicalizationTask::new(&tx_graph, Default::default());
181-
/// # let view = chain.canonicalize(task, Some(chain.tip().block_id()));
182+
/// # let chain_tip = chain.tip().block_id();
183+
/// # let task = CanonicalizationTask::new(&tx_graph, chain_tip, Default::default());
184+
/// # let view = chain.canonicalize(task);
182185
/// # let indexer = KeychainTxOutIndex::<&str>::default();
183186
/// // Get all outputs from an indexer
184187
/// for (keychain, txout) in view.filter_outpoints(indexer.outpoints().clone()) {
@@ -207,8 +210,9 @@ impl<A: Anchor> CanonicalView<A> {
207210
/// # use bitcoin::hashes::Hash;
208211
/// # let tx_graph = TxGraph::<BlockId>::default();
209212
/// # let chain = LocalChain::from_blocks([(0, bitcoin::BlockHash::all_zeros())].into_iter().collect()).unwrap();
210-
/// # let task = CanonicalizationTask::new(&tx_graph, Default::default());
211-
/// # let view = chain.canonicalize(task, Some(chain.tip().block_id()));
213+
/// # let chain_tip = chain.tip().block_id();
214+
/// # let task = CanonicalizationTask::new(&tx_graph, chain_tip, Default::default());
215+
/// # let view = chain.canonicalize(task);
212216
/// # let indexer = KeychainTxOutIndex::<&str>::default();
213217
/// // Get unspent outputs (UTXOs) from an indexer
214218
/// for (keychain, utxo) in view.filter_unspent_outpoints(indexer.outpoints().clone()) {
@@ -254,8 +258,9 @@ impl<A: Anchor> CanonicalView<A> {
254258
/// # use bitcoin::hashes::Hash;
255259
/// # let tx_graph = TxGraph::<BlockId>::default();
256260
/// # let chain = LocalChain::from_blocks([(0, bitcoin::BlockHash::all_zeros())].into_iter().collect()).unwrap();
257-
/// # let task = CanonicalizationTask::new(&tx_graph, Default::default());
258-
/// # let view = chain.canonicalize(task, Some(chain.tip().block_id()));
261+
/// # let chain_tip = chain.tip().block_id();
262+
/// # let task = CanonicalizationTask::new(&tx_graph, chain_tip, Default::default());
263+
/// # let view = chain.canonicalize(task);
259264
/// # let indexer = KeychainTxOutIndex::<&str>::default();
260265
/// // Calculate balance with 6 confirmations, trusting all outputs
261266
/// let balance = view.balance(

crates/chain/src/indexed_tx_graph.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -441,9 +441,10 @@ where
441441
/// for anchor verification requests.
442442
pub fn canonicalization_task(
443443
&'_ self,
444+
chain_tip: BlockId,
444445
params: CanonicalizationParams,
445446
) -> CanonicalizationTask<'_, A> {
446-
self.graph.canonicalization_task(params)
447+
self.graph.canonicalization_task(chain_tip, params)
447448
}
448449
}
449450

crates/chain/src/local_chain.rs

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -140,38 +140,35 @@ impl LocalChain<BlockHash> {
140140
/// # use bitcoin::hashes::Hash;
141141
/// # let tx_graph: TxGraph<BlockId> = TxGraph::default();
142142
/// # let chain = LocalChain::from_blocks([(0, bitcoin::BlockHash::all_zeros())].into_iter().collect()).unwrap();
143-
/// let task = CanonicalizationTask::new(&tx_graph, CanonicalizationParams::default());
144-
/// let view = chain.canonicalize(task, Some(chain.tip().block_id()));
143+
/// let chain_tip = chain.tip().block_id();
144+
/// let task = CanonicalizationTask::new(&tx_graph, chain_tip, CanonicalizationParams::default());
145+
/// let view = chain.canonicalize(task);
145146
/// ```
146147
pub fn canonicalize<A: Anchor>(
147148
&self,
148149
mut task: CanonicalizationTask<'_, A>,
149-
chain_tip: Option<BlockId>,
150150
) -> CanonicalView<A> {
151-
let chain_tip = match chain_tip {
152-
Some(chain_tip) => chain_tip,
153-
None => self.get_chain_tip().expect("infallible"),
154-
};
155-
156151
// Process all requests from the task
157152
while let Some(request) = task.next_query() {
158-
// Check each anchor and return the first confirmed one
159-
let mut best_anchor = None;
160-
for anchor in &request.anchors {
153+
let chain_tip = request.chain_tip;
154+
155+
// Check each block ID and return the first confirmed one
156+
let mut best_block_id = None;
157+
for block_id in &request.block_ids {
161158
if self
162-
.is_block_in_chain(anchor.anchor_block(), chain_tip)
159+
.is_block_in_chain(*block_id, chain_tip)
163160
.expect("infallible")
164161
== Some(true)
165162
{
166-
best_anchor = Some(anchor.clone());
163+
best_block_id = Some(*block_id);
167164
break;
168165
}
169166
}
170-
task.resolve_query(best_anchor);
167+
task.resolve_query(best_block_id);
171168
}
172169

173170
// Return the finished canonical view
174-
task.finish(chain_tip)
171+
task.finish()
175172
}
176173

177174
/// Update the chain with a given [`Header`] at `height` which you claim is connected to a

crates/chain/src/tx_graph.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
//! encapsulates the canonicalization logic without performing any I/O operations.
3030
//!
3131
//! 2. **Execute the task** with a chain oracle to obtain a [`CanonicalView`]: ```ignore let view =
32-
//! chain.canonicalize(task, Some(chain_tip)); ``` The chain oracle (such as
32+
//! chain.canonicalize(task); ``` The chain oracle (such as
3333
//! [`LocalChain`](crate::local_chain::LocalChain)) handles all anchor verification queries from
3434
//! the task.
3535
//!
@@ -129,7 +129,7 @@
129129
use crate::collections::*;
130130
use crate::CanonicalizationParams;
131131
use crate::CanonicalizationTask;
132-
use crate::{Anchor, Merge};
132+
use crate::{Anchor, BlockId, Merge};
133133
use alloc::collections::vec_deque::VecDeque;
134134
use alloc::sync::Arc;
135135
use alloc::vec::Vec;
@@ -964,9 +964,10 @@ impl<A: Anchor> TxGraph<A> {
964964
/// for anchor verification requests.
965965
pub fn canonicalization_task(
966966
&'_ self,
967+
chain_tip: BlockId,
967968
params: CanonicalizationParams,
968969
) -> CanonicalizationTask<'_, A> {
969-
CanonicalizationTask::new(self, params)
970+
CanonicalizationTask::new(self, chain_tip, params)
970971
}
971972
}
972973

0 commit comments

Comments
 (0)