Skip to content

Commit 7245631

Browse files
evanlinjinclaude
andcommitted
refactor(chain)!: Rename min_confirmations to additional_confirmations
BREAKING CHANGE: `CanonicalView::balance()` now takes `additional_confirmations` instead of `min_confirmations`. The new parameter represents confirmations beyond the first (e.g., 5 means 6 total confirmations required). - Rename `why` to `reason` in CanonicalView for clarity - Update docs to clarify topological-spending order - Simplify docs by removing redundant conflict mentions 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent 3f9eec5 commit 7245631

File tree

7 files changed

+96
-101
lines changed

7 files changed

+96
-101
lines changed

crates/bitcoind_rpc/tests/test_emitter.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,7 @@ fn get_balance(
312312
let outpoints = recv_graph.index.outpoints().clone();
313313
let balance = recv_graph
314314
.canonical_view(recv_chain, chain_tip, CanonicalizationParams::default())
315-
.balance(outpoints, |_, _| true, 1);
315+
.balance(outpoints, |_, _| true, 0);
316316
Ok(balance)
317317
}
318318

crates/chain/src/canonical_view.rs

Lines changed: 19 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -60,12 +60,12 @@ pub struct CanonicalTx<A> {
6060
/// provides methods to query transaction data, unspent outputs, and balances.
6161
///
6262
/// The view maintains:
63-
/// - An ordered list of canonical transactions (WIP)
63+
/// - An ordered list of canonical transactions in topological-spending order
6464
/// - A mapping of outpoints to the transactions that spend them
6565
/// - The chain tip used for canonicalization
6666
#[derive(Debug)]
6767
pub struct CanonicalView<A> {
68-
/// Ordered list of transaction IDs in canonical order.
68+
/// Ordered list of transaction IDs in topological-spending order.
6969
order: Vec<Txid>,
7070
/// Map of transaction IDs to their transaction data and chain position.
7171
txs: HashMap<Txid, (Arc<Transaction>, ChainPosition<A>)>,
@@ -119,7 +119,7 @@ impl<A: Anchor> CanonicalView<A> {
119119
};
120120

121121
for r in CanonicalIter::new(tx_graph, chain, chain_tip, params) {
122-
let (txid, tx, why) = r?;
122+
let (txid, tx, reason) = r?;
123123

124124
let tx_node = match tx_graph.get_tx_node(txid) {
125125
Some(tx_node) => tx_node,
@@ -137,7 +137,7 @@ impl<A: Anchor> CanonicalView<A> {
137137
.extend(tx.input.iter().map(|txin| (txin.previous_output, txid)));
138138
}
139139

140-
let pos = match why {
140+
let pos = match reason {
141141
CanonicalReason::Assumed { descendant } => match descendant {
142142
Some(_) => match find_direct_anchor(&tx_node, chain, chain_tip)? {
143143
Some(anchor) => ChainPosition::Confirmed {
@@ -189,8 +189,8 @@ impl<A: Anchor> CanonicalView<A> {
189189

190190
/// Get a single canonical transaction by its transaction ID.
191191
///
192-
/// Returns `Some(CanonicalViewTx)` if the transaction exists in the canonical view,
193-
/// or `None` if the transaction doesn't exist or was excluded due to conflicts.
192+
/// Returns `Some(CanonicalTx)` if the transaction exists in the canonical view,
193+
/// or `None` if it doesn't exist.
194194
pub fn tx(&self, txid: Txid) -> Option<CanonicalTx<A>> {
195195
self.txs
196196
.get(&txid)
@@ -206,7 +206,6 @@ impl<A: Anchor> CanonicalView<A> {
206206
/// Returns `None` if:
207207
/// - The transaction doesn't exist in the canonical view
208208
/// - The output index is out of bounds
209-
/// - The transaction was excluded due to conflicts
210209
pub fn txout(&self, op: OutPoint) -> Option<FullTxOut<A>> {
211210
let (tx, pos) = self.txs.get(&op.txid)?;
212211
let vout: usize = op.vout.try_into().ok()?;
@@ -226,8 +225,9 @@ impl<A: Anchor> CanonicalView<A> {
226225

227226
/// Get an iterator over all canonical transactions in order.
228227
///
229-
/// Transactions are returned in canonical order, with confirmed transactions ordered by
230-
/// block height and position, followed by unconfirmed transactions.
228+
/// Transactions are returned in topological-spending order, where ancestors appear before
229+
/// their descendants (i.e., transactions that spend outputs come after the transactions
230+
/// that create those outputs).
231231
///
232232
/// # Example
233233
///
@@ -326,16 +326,14 @@ impl<A: Anchor> CanonicalView<A> {
326326
/// * `trust_predicate` - Function that returns `true` for trusted scripts. Trusted outputs
327327
/// count toward `trusted_pending` balance, while untrusted ones count toward
328328
/// `untrusted_pending`
329-
/// * `min_confirmations` - Minimum confirmations required for an output to be considered
330-
/// confirmed. Outputs with fewer confirmations are treated as pending.
329+
/// * `additional_confirmations` - Additional confirmations required beyond the first one.
330+
/// Outputs with fewer than (1 + additional_confirmations) are treated as pending.
331331
///
332-
/// # Minimum Confirmations
332+
/// # Additional Confirmations
333333
///
334-
/// The `min_confirmations` parameter controls when outputs are considered confirmed. A
335-
/// `min_confirmations` value of `0` is equivalent to `1` (require at least 1 confirmation).
336-
///
337-
/// Outputs with fewer than `min_confirmations` are categorized as pending (trusted or
338-
/// untrusted based on the trust predicate).
334+
/// The `additional_confirmations` parameter specifies how many confirmations beyond the
335+
/// first one are required. For example, `additional_confirmations = 5` means 6 total
336+
/// confirmations are required (1 + 5).
339337
///
340338
/// # Example
341339
///
@@ -351,14 +349,14 @@ impl<A: Anchor> CanonicalView<A> {
351349
/// let balance = view.balance(
352350
/// indexer.outpoints().into_iter().map(|(k, op)| (k.clone(), *op)),
353351
/// |_keychain, _script| true, // Trust all outputs
354-
/// 6, // Require 6 confirmations
352+
/// 5, // Require 6 confirmations (1 + 5)
355353
/// );
356354
/// ```
357355
pub fn balance<'v, O: Clone + 'v>(
358356
&'v self,
359357
outpoints: impl IntoIterator<Item = (O, OutPoint)> + 'v,
360358
mut trust_predicate: impl FnMut(&O, ScriptBuf) -> bool,
361-
min_confirmations: u32,
359+
additional_confirmations: u32,
362360
) -> Balance {
363361
let mut immature = Amount::ZERO;
364362
let mut trusted_pending = Amount::ZERO;
@@ -374,9 +372,9 @@ impl<A: Anchor> CanonicalView<A> {
374372
.height
375373
.saturating_sub(confirmation_height)
376374
.saturating_add(1);
377-
let min_confirmations = min_confirmations.max(1); // 0 and 1 behave identically
375+
let required_confirmations = 1 + additional_confirmations;
378376

379-
if confirmations < min_confirmations {
377+
if confirmations < required_confirmations {
380378
// Not enough confirmations, treat as trusted/untrusted pending
381379
if trust_predicate(&spk_i, txout.txout.script_pubkey) {
382380
trusted_pending += txout.txout.value;

crates/chain/tests/test_canonical_view.rs

Lines changed: 71 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,28 @@
22

33
use bdk_chain::{local_chain::LocalChain, CanonicalizationParams, ConfirmationBlockTime, TxGraph};
44
use bdk_testenv::{hash, utils::new_tx};
5-
use bitcoin::{Amount, OutPoint, ScriptBuf, Transaction, TxIn, TxOut};
5+
use bitcoin::{Amount, BlockHash, OutPoint, ScriptBuf, Transaction, TxIn, TxOut};
6+
use std::collections::BTreeMap;
67

78
#[test]
8-
fn test_min_confirmations_parameter() {
9+
fn test_additional_confirmations_parameter() {
910
// Create a local chain with several blocks
10-
let chain = LocalChain::from_blocks(
11-
[
12-
(0, hash!("block0")),
13-
(1, hash!("block1")),
14-
(2, hash!("block2")),
15-
(3, hash!("block3")),
16-
(4, hash!("block4")),
17-
(5, hash!("block5")),
18-
(6, hash!("block6")),
19-
(7, hash!("block7")),
20-
(8, hash!("block8")),
21-
(9, hash!("block9")),
22-
(10, hash!("block10")),
23-
]
24-
.into(),
25-
)
26-
.unwrap();
11+
let blocks: BTreeMap<u32, BlockHash> = [
12+
(0, hash!("block0")),
13+
(1, hash!("block1")),
14+
(2, hash!("block2")),
15+
(3, hash!("block3")),
16+
(4, hash!("block4")),
17+
(5, hash!("block5")),
18+
(6, hash!("block6")),
19+
(7, hash!("block7")),
20+
(8, hash!("block8")),
21+
(9, hash!("block9")),
22+
(10, hash!("block10")),
23+
]
24+
.into_iter()
25+
.collect();
26+
let chain = LocalChain::from_blocks(blocks).unwrap();
2727

2828
let mut tx_graph = TxGraph::default();
2929

@@ -56,65 +56,63 @@ fn test_min_confirmations_parameter() {
5656
let canonical_view =
5757
tx_graph.canonical_view(&chain, chain_tip, CanonicalizationParams::default());
5858

59-
// Test min_confirmations = 1: Should be confirmed (has 6 confirmations)
59+
// Test additional_confirmations = 0: Should be confirmed (has 6 confirmations, needs 1)
6060
let balance_1_conf = canonical_view.balance(
6161
[((), outpoint)],
6262
|_, _| true, // trust all
63-
1,
63+
0,
6464
);
6565

6666
assert_eq!(balance_1_conf.confirmed, Amount::from_sat(50_000));
6767
assert_eq!(balance_1_conf.trusted_pending, Amount::ZERO);
6868

69-
// Test min_confirmations = 6: Should be confirmed (has exactly 6 confirmations)
69+
// Test additional_confirmations = 5: Should be confirmed (has 6 confirmations, needs 6)
7070
let balance_6_conf = canonical_view.balance(
7171
[((), outpoint)],
7272
|_, _| true, // trust all
73-
6,
73+
5,
7474
);
7575
assert_eq!(balance_6_conf.confirmed, Amount::from_sat(50_000));
7676
assert_eq!(balance_6_conf.trusted_pending, Amount::ZERO);
7777

78-
// Test min_confirmations = 7: Should be trusted pending (only has 6 confirmations)
78+
// Test additional_confirmations = 6: Should be trusted pending (has 6 confirmations, needs 7)
7979
let balance_7_conf = canonical_view.balance(
8080
[((), outpoint)],
8181
|_, _| true, // trust all
82-
7,
82+
6,
8383
);
8484
assert_eq!(balance_7_conf.confirmed, Amount::ZERO);
8585
assert_eq!(balance_7_conf.trusted_pending, Amount::from_sat(50_000));
8686

87-
// Test min_confirmations = 0: Should behave same as 1 (confirmed)
87+
// Test additional_confirmations = 0: Should be confirmed
8888
let balance_0_conf = canonical_view.balance(
8989
[((), outpoint)],
9090
|_, _| true, // trust all
9191
0,
9292
);
9393
assert_eq!(balance_0_conf.confirmed, Amount::from_sat(50_000));
9494
assert_eq!(balance_0_conf.trusted_pending, Amount::ZERO);
95-
assert_eq!(balance_0_conf, balance_1_conf);
9695
}
9796

9897
#[test]
99-
fn test_min_confirmations_with_untrusted_tx() {
98+
fn test_additional_confirmations_with_untrusted_tx() {
10099
// Create a local chain
101-
let chain = LocalChain::from_blocks(
102-
[
103-
(0, hash!("genesis")),
104-
(1, hash!("b1")),
105-
(2, hash!("b2")),
106-
(3, hash!("b3")),
107-
(4, hash!("b4")),
108-
(5, hash!("b5")),
109-
(6, hash!("b6")),
110-
(7, hash!("b7")),
111-
(8, hash!("b8")),
112-
(9, hash!("b9")),
113-
(10, hash!("tip")),
114-
]
115-
.into(),
116-
)
117-
.unwrap();
100+
let blocks: BTreeMap<u32, BlockHash> = [
101+
(0, hash!("genesis")),
102+
(1, hash!("b1")),
103+
(2, hash!("b2")),
104+
(3, hash!("b3")),
105+
(4, hash!("b4")),
106+
(5, hash!("b5")),
107+
(6, hash!("b6")),
108+
(7, hash!("b7")),
109+
(8, hash!("b8")),
110+
(9, hash!("b9")),
111+
(10, hash!("tip")),
112+
]
113+
.into_iter()
114+
.collect();
115+
let chain = LocalChain::from_blocks(blocks).unwrap();
118116

119117
let mut tx_graph = TxGraph::default();
120118

@@ -148,11 +146,11 @@ fn test_min_confirmations_with_untrusted_tx() {
148146
CanonicalizationParams::default(),
149147
);
150148

151-
// Test with min_confirmations = 5 and untrusted predicate
149+
// Test with additional_confirmations = 4 and untrusted predicate (requires 5 total)
152150
let balance = canonical_view.balance(
153151
[((), outpoint)],
154152
|_, _| false, // don't trust
155-
5,
153+
4,
156154
);
157155

158156
// Should be untrusted pending (not enough confirmations and not trusted)
@@ -162,30 +160,29 @@ fn test_min_confirmations_with_untrusted_tx() {
162160
}
163161

164162
#[test]
165-
fn test_min_confirmations_multiple_transactions() {
163+
fn test_additional_confirmations_multiple_transactions() {
166164
// Create a local chain
167-
let chain = LocalChain::from_blocks(
168-
[
169-
(0, hash!("genesis")),
170-
(1, hash!("b1")),
171-
(2, hash!("b2")),
172-
(3, hash!("b3")),
173-
(4, hash!("b4")),
174-
(5, hash!("b5")),
175-
(6, hash!("b6")),
176-
(7, hash!("b7")),
177-
(8, hash!("b8")),
178-
(9, hash!("b9")),
179-
(10, hash!("b10")),
180-
(11, hash!("b11")),
181-
(12, hash!("b12")),
182-
(13, hash!("b13")),
183-
(14, hash!("b14")),
184-
(15, hash!("tip")),
185-
]
186-
.into(),
187-
)
188-
.unwrap();
165+
let blocks: BTreeMap<u32, BlockHash> = [
166+
(0, hash!("genesis")),
167+
(1, hash!("b1")),
168+
(2, hash!("b2")),
169+
(3, hash!("b3")),
170+
(4, hash!("b4")),
171+
(5, hash!("b5")),
172+
(6, hash!("b6")),
173+
(7, hash!("b7")),
174+
(8, hash!("b8")),
175+
(9, hash!("b9")),
176+
(10, hash!("b10")),
177+
(11, hash!("b11")),
178+
(12, hash!("b12")),
179+
(13, hash!("b13")),
180+
(14, hash!("b14")),
181+
(15, hash!("tip")),
182+
]
183+
.into_iter()
184+
.collect();
185+
let chain = LocalChain::from_blocks(blocks).unwrap();
189186

190187
let mut tx_graph = TxGraph::default();
191188

@@ -270,11 +267,11 @@ fn test_min_confirmations_multiple_transactions() {
270267
CanonicalizationParams::default(),
271268
);
272269

273-
// Test with min_confirmations = 5
270+
// Test with additional_confirmations = 4 (requires 5 total)
274271
// tx0: 11 confirmations -> confirmed
275272
// tx1: 6 confirmations -> confirmed
276273
// tx2: 3 confirmations -> trusted pending
277-
let balance = canonical_view.balance(outpoints.clone(), |_, _| true, 5);
274+
let balance = canonical_view.balance(outpoints.clone(), |_, _| true, 4);
278275

279276
assert_eq!(
280277
balance.confirmed,
@@ -286,11 +283,11 @@ fn test_min_confirmations_multiple_transactions() {
286283
);
287284
assert_eq!(balance.untrusted_pending, Amount::ZERO);
288285

289-
// Test with min_confirmations = 10
286+
// Test with additional_confirmations = 9 (requires 10 total)
290287
// tx0: 11 confirmations -> confirmed
291288
// tx1: 6 confirmations -> trusted pending
292289
// tx2: 3 confirmations -> trusted pending
293-
let balance_high = canonical_view.balance(outpoints, |_, _| true, 10);
290+
let balance_high = canonical_view.balance(outpoints, |_, _| true, 9);
294291

295292
assert_eq!(
296293
balance_high.confirmed,

crates/chain/tests/test_indexed_tx_graph.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -474,7 +474,7 @@ fn test_list_owned_txouts() {
474474
.balance(
475475
graph.index.outpoints().iter().cloned(),
476476
|_, spk: ScriptBuf| trusted_spks.contains(&spk),
477-
1,
477+
0,
478478
);
479479

480480
let confirmed_txouts_txid = txouts

crates/electrum/tests/test_electrum.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ fn get_balance(
4242
let outpoints = recv_graph.index.outpoints().clone();
4343
let balance = recv_graph
4444
.canonical_view(recv_chain, chain_tip, CanonicalizationParams::default())
45-
.balance(outpoints, |_, _| true, 1);
45+
.balance(outpoints, |_, _| true, 0);
4646
Ok(balance)
4747
}
4848

examples/example_bitcoind_rpc_polling/src/main.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@ fn main() -> anyhow::Result<()> {
205205
.balance(
206206
graph.index.outpoints().iter().cloned(),
207207
|(k, _), _| k == &Keychain::Internal,
208-
1,
208+
0,
209209
)
210210
};
211211
println!(
@@ -365,7 +365,7 @@ fn main() -> anyhow::Result<()> {
365365
.balance(
366366
graph.index.outpoints().iter().cloned(),
367367
|(k, _), _| k == &Keychain::Internal,
368-
1,
368+
0,
369369
)
370370
};
371371
println!(

0 commit comments

Comments
 (0)