Skip to content

Commit 023fdf0

Browse files
committed
Merge bitcoindevkit#2038: refactor(chain,core)!: replace CanonicalIter with sans-IO CanonicalTask + ChainQuery trait
555f774 fix(chain): position resolution for assumed txs (Leonardo Lima) 4f10cde chore(chain,example)!: remove `ChainOracle`, update docs (志宇) a3c73e4 refactor(chain)!: split `canonical_view` into `canonical`,`canonical_view_task` (志宇) 22b04c3 refactor(chain)!: split canonicalization into two tasks with generic `Canonical<A, P>` (志宇) a9f5ca4 refactor(chain)!: remove `CanonicalIter` APIs (Leonardo Lima) bb83da8 chore(workspace): use new `LocalChain::canonical_view` API (Leonardo Lima) 987da73 feat(core,chain): introduce `CanonicalizationTask` and `ChainQuery` (Leonardo Lima) Pull request description: fixes bitcoindevkit#1816 ### Description Replaces the iterator-based `CanonicalIter` with a two-phase sans-IO canonicalization pipeline, and introduces a generic `ChainQuery` trait in `bdk_core` to decouple canonicalization from chain sources. **Old API:** ```rust // Direct coupling between canonicalization logic and ChainOracle let view = tx_graph.canonical_view(&chain, chain_tip, params)?; ``` **New API:** ```rust // Option A: Two-phase (full control) let canonical_txs = chain.canonicalize(tx_graph.canonical_task(tip, params)); let view = chain.canonicalize(canonical_txs.view_task(&tx_graph)); // Option B: Convenience method let view = chain.canonical_view(&tx_graph, tip, params); ``` #### Phase 1: `CanonicalTask` Determines which transactions are canonical by processing them in stages: 1. **Assumed txs** — transactions assumed canonical via `CanonicalParams` 2. **Anchored txs** — transactions anchored in the best chain (descending height) 3. **Seen txs** — unconfirmed transactions by descending last-seen time 4. **Remaining txs** — leftover anchored transactions not in the best chain Produces a `CanonicalTxs<A>` containing each canonical transaction with its `CanonicalReason`. #### Phase 2: `CanonicalViewTask` Resolves `CanonicalReason`s into concrete `ChainPosition`s (confirmed height or unconfirmed with last-seen), producing the final `CanonicalView<A>`. Both phases implement the `ChainQuery` trait, so any chain source can drive them via the same `next_query`/`resolve_query` loop. #### Key structural changes - **`ChainQuery` trait** added to `bdk_core` — a generic sans-IO interface (`next_query` → `resolve_query` → `finish`) for any algorithm that needs to verify blocks against a chain source. - **`ChainOracle` trait removed** — replaced by `ChainQuery`. `LocalChain::canonicalize()` now drives any `ChainQuery` implementor. - **`Canonical<A, P>` generic container** — `CanonicalTxs<A>` (phase 1 output) and `CanonicalView<A>` (phase 2 output) are type aliases over `Canonical<A, P>`. - **Module split** — `canonical_view.rs` split into `canonical.rs` (types: `Canonical`, `CanonicalTx`, `CanonicalTxOut`) and `canonical_view_task.rs` (phase 2 task). `canonical_iter.rs` replaced by `canonical_task.rs`. ### Notes to the reviewers The changes are split into multiple commits for easier review. Also depends on bitcoindevkit#2029. ### Changelog notice ``` ### Added - `bdk_core::ChainQuery` trait — generic sans-IO interface for chain verification queries - `bdk_core::ChainRequest` / `ChainResponse` type aliases - `CanonicalTask` — phase 1 sans-IO canonicalization (determines canonical txs) - `CanonicalViewTask` — phase 2 sans-IO canonicalization (resolves chain positions) - `Canonical<A, P>` generic container with `CanonicalTxs<A>` and `CanonicalView<A>` aliases - `LocalChain::canonicalize()` — drives any `ChainQuery` implementor - `LocalChain::canonical_view()` — convenience method for full two-phase canonicalization ### Changed - **Breaking:** Replace `TxGraph::canonical_iter()` / `TxGraph::canonical_view()` with `TxGraph::canonical_task()` - **Breaking:** Canonicalization now uses a two-phase sans-IO process via `ChainQuery` - **Breaking:** `ChainQuery`, `ChainRequest`, `ChainResponse` have no generics (use `BlockId` directly) - **Breaking:** Chain tip moved from `ChainRequest` to `ChainQuery::tip()` ### Removed - **Breaking:** `ChainOracle` trait and all implementations - **Breaking:** `CanonicalIter` type and `canonical_iter` module - **Breaking:** `TxGraph::try_canonical_view()` and `TxGraph::canonical_view()` methods - **Breaking:** `CanonicalView::new()` public constructor ``` ### Checklists #### All Submissions: * [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk/blob/master/CONTRIBUTING.md) #### New Features: * [x] I've added tests for the new feature * [x] I've added docs for the new feature ACKs for top commit: evanlinjin: ACK 555f774 Tree-SHA512: ce466a623a1f8df20dedd3e4e68460892bd369567db44e6ba391c3d9ae1d10bca6204ebdcdb7b1376a9c97f3155b89e991846af4122e200d1fa15d998deb7830
2 parents 47556ab + 555f774 commit 023fdf0

25 files changed

Lines changed: 1432 additions & 885 deletions

crates/bitcoind_rpc/examples/filter_iter.rs

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

72-
let canonical_view = graph.canonical_view(&chain, chain.tip().block_id(), Default::default());
72+
let canonical_view =
73+
chain.canonical_view(graph.graph(), chain.tip().block_id(), Default::default());
7374

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

crates/bitcoind_rpc/tests/test_emitter.rs

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use bdk_chain::{
55
bitcoin::{Address, Amount, Txid},
66
local_chain::{CheckPoint, LocalChain},
77
spk_txout::SpkTxOutIndex,
8-
Balance, BlockId, CanonicalizationParams, IndexedTxGraph, Merge,
8+
Balance, BlockId, IndexedTxGraph, Merge,
99
};
1010
use bdk_testenv::{
1111
anyhow,
@@ -318,11 +318,14 @@ fn get_balance(
318318
recv_chain: &LocalChain,
319319
recv_graph: &IndexedTxGraph<BlockId, SpkTxOutIndex<()>>,
320320
) -> anyhow::Result<Balance> {
321-
let chain_tip = recv_chain.tip().block_id();
322321
let outpoints = recv_graph.index.outpoints().clone();
323-
let balance = recv_graph
324-
.canonical_view(recv_chain, chain_tip, CanonicalizationParams::default())
325-
.balance(outpoints, |_, _| true, 1);
322+
let balance = recv_chain
323+
.canonical_view(
324+
recv_graph.graph(),
325+
recv_chain.tip().block_id(),
326+
Default::default(),
327+
)
328+
.balance(outpoints, |_, _| true, 0);
326329
Ok(balance)
327330
}
328331

@@ -631,8 +634,8 @@ fn test_expect_tx_evicted() -> anyhow::Result<()> {
631634
let _txid_2 = core.send_raw_transaction(&tx1b)?;
632635

633636
// Retrieve the expected unconfirmed txids and spks from the graph.
634-
let exp_spk_txids = graph
635-
.canonical_view(&chain, chain_tip, Default::default())
637+
let exp_spk_txids = chain
638+
.canonical_view(graph.graph(), chain_tip, Default::default())
636639
.list_expected_spk_txids(&graph.index, ..)
637640
.collect::<Vec<_>>();
638641
assert_eq!(exp_spk_txids, vec![(spk, txid_1)]);
@@ -647,8 +650,8 @@ fn test_expect_tx_evicted() -> anyhow::Result<()> {
647650
// Update graph with evicted tx.
648651
let _ = graph.batch_insert_relevant_evicted_at(mempool_event.evicted);
649652

650-
let canonical_txids = graph
651-
.canonical_view(&chain, chain_tip, CanonicalizationParams::default())
653+
let canonical_txids = chain
654+
.canonical_view(graph.graph(), chain_tip, Default::default())
652655
.txs()
653656
.map(|tx| tx.txid)
654657
.collect::<Vec<_>>();

crates/chain/benches/canonicalization.rs

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
use bdk_chain::CanonicalizationParams;
21
use bdk_chain::{keychain_txout::KeychainTxOutIndex, local_chain::LocalChain, IndexedTxGraph};
32
use bdk_core::{BlockId, CheckPoint};
43
use bdk_core::{ConfirmationBlockTime, TxUpdate};
@@ -95,31 +94,19 @@ fn setup<F: Fn(&mut KeychainTxGraph, &LocalChain)>(f: F) -> (KeychainTxGraph, Lo
9594
}
9695

9796
fn run_list_canonical_txs(tx_graph: &KeychainTxGraph, chain: &LocalChain, exp_txs: usize) {
98-
let view = tx_graph.canonical_view(
99-
chain,
100-
chain.tip().block_id(),
101-
CanonicalizationParams::default(),
102-
);
97+
let view = chain.canonical_view(tx_graph.graph(), chain.tip().block_id(), Default::default());
10398
let txs = view.txs();
10499
assert_eq!(txs.count(), exp_txs);
105100
}
106101

107102
fn run_filter_chain_txouts(tx_graph: &KeychainTxGraph, chain: &LocalChain, exp_txos: usize) {
108-
let view = tx_graph.canonical_view(
109-
chain,
110-
chain.tip().block_id(),
111-
CanonicalizationParams::default(),
112-
);
103+
let view = chain.canonical_view(tx_graph.graph(), chain.tip().block_id(), Default::default());
113104
let utxos = view.filter_outpoints(tx_graph.index.outpoints().clone());
114105
assert_eq!(utxos.count(), exp_txos);
115106
}
116107

117108
fn run_filter_chain_unspents(tx_graph: &KeychainTxGraph, chain: &LocalChain, exp_utxos: usize) {
118-
let view = tx_graph.canonical_view(
119-
chain,
120-
chain.tip().block_id(),
121-
CanonicalizationParams::default(),
122-
);
109+
let view = chain.canonical_view(tx_graph.graph(), chain.tip().block_id(), Default::default());
123110
let utxos = view.filter_unspent_outpoints(tx_graph.index.outpoints().clone());
124111
assert_eq!(utxos.count(), exp_utxos);
125112
}

crates/chain/benches/indexer.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use bdk_chain::{
22
keychain_txout::{InsertDescriptorError, KeychainTxOutIndex},
33
local_chain::LocalChain,
4-
CanonicalizationParams, IndexedTxGraph,
4+
IndexedTxGraph,
55
};
66
use bdk_core::{BlockId, CheckPoint, ConfirmationBlockTime, TxUpdate};
77
use bitcoin::{
@@ -82,10 +82,9 @@ fn do_bench(indexed_tx_graph: &KeychainTxGraph, chain: &LocalChain) {
8282
.unwrap();
8383

8484
// Check balance
85-
let chain_tip = chain.tip().block_id();
8685
let op = graph.index.outpoints().clone();
87-
let bal = graph
88-
.canonical_view(chain, chain_tip, CanonicalizationParams::default())
86+
let bal = chain
87+
.canonical_view(graph.graph(), chain.tip().block_id(), Default::default())
8988
.balance(op, |_, _| false, 1);
9089
assert_eq!(bal.total(), AMOUNT * TX_CT as u64);
9190
}

0 commit comments

Comments
 (0)