Skip to content

Commit b46dbf8

Browse files
evanlinjinclaude
authored andcommitted
test(chain): Add comprehensive tests for min_confirmations parameter
Add test file `tests/test_canonical_view.rs` with three comprehensive test cases: 1. `test_min_confirmations_parameter`: Tests basic min_confirmations functionality - Verifies min_confirmations = 0 and 1 behave identically - Tests edge case where transaction has exactly required confirmations - Tests case where transaction has insufficient confirmations 2. `test_min_confirmations_with_untrusted_tx`: Tests trust predicate interaction - Verifies insufficient confirmations + untrusted predicate = untrusted_pending - Ensures trust predicate is respected when confirmations are insufficient 3. `test_min_confirmations_multiple_transactions`: Tests complex scenarios - Multiple transactions with different confirmation counts - Verifies correct categorization based on min_confirmations threshold - Tests both min_confirmations = 5 and min_confirmations = 10 scenarios These tests validate that the min_confirmations parameter correctly controls when transactions are treated as confirmed vs trusted/untrusted pending. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent 7ac59b9 commit b46dbf8

File tree

1 file changed

+304
-0
lines changed

1 file changed

+304
-0
lines changed
Lines changed: 304 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,304 @@
1+
#![cfg(feature = "miniscript")]
2+
3+
use bdk_chain::{local_chain::LocalChain, CanonicalizationParams, ConfirmationBlockTime, TxGraph};
4+
use bdk_testenv::{hash, utils::new_tx};
5+
use bitcoin::{Amount, OutPoint, ScriptBuf, Transaction, TxIn, TxOut};
6+
7+
#[test]
8+
fn test_min_confirmations_parameter() {
9+
// 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();
27+
28+
let mut tx_graph = TxGraph::default();
29+
30+
// Create a non-coinbase transaction
31+
let tx = Transaction {
32+
input: vec![TxIn {
33+
previous_output: OutPoint::new(hash!("parent"), 0),
34+
..Default::default()
35+
}],
36+
output: vec![TxOut {
37+
value: Amount::from_sat(50_000),
38+
script_pubkey: ScriptBuf::new(),
39+
}],
40+
..new_tx(1)
41+
};
42+
let txid = tx.compute_txid();
43+
let outpoint = OutPoint::new(txid, 0);
44+
45+
// Insert transaction into graph
46+
let _ = tx_graph.insert_tx(tx.clone());
47+
48+
// Test 1: Transaction confirmed at height 5, tip at height 10 (6 confirmations)
49+
let anchor_height_5 = ConfirmationBlockTime {
50+
block_id: chain.get(5).unwrap().block_id(),
51+
confirmation_time: 123456,
52+
};
53+
let _ = tx_graph.insert_anchor(txid, anchor_height_5);
54+
55+
let chain_tip = chain.tip().block_id();
56+
let canonical_view =
57+
tx_graph.canonical_view(&chain, chain_tip, CanonicalizationParams::default());
58+
59+
// Test min_confirmations = 1: Should be confirmed (has 6 confirmations)
60+
let balance_1_conf = canonical_view.balance(
61+
[((), outpoint)],
62+
|_, _| true, // trust all
63+
1,
64+
);
65+
66+
assert_eq!(balance_1_conf.confirmed, Amount::from_sat(50_000));
67+
assert_eq!(balance_1_conf.trusted_pending, Amount::ZERO);
68+
69+
// Test min_confirmations = 6: Should be confirmed (has exactly 6 confirmations)
70+
let balance_6_conf = canonical_view.balance(
71+
[((), outpoint)],
72+
|_, _| true, // trust all
73+
6,
74+
);
75+
assert_eq!(balance_6_conf.confirmed, Amount::from_sat(50_000));
76+
assert_eq!(balance_6_conf.trusted_pending, Amount::ZERO);
77+
78+
// Test min_confirmations = 7: Should be trusted pending (only has 6 confirmations)
79+
let balance_7_conf = canonical_view.balance(
80+
[((), outpoint)],
81+
|_, _| true, // trust all
82+
7,
83+
);
84+
assert_eq!(balance_7_conf.confirmed, Amount::ZERO);
85+
assert_eq!(balance_7_conf.trusted_pending, Amount::from_sat(50_000));
86+
87+
// Test min_confirmations = 0: Should behave same as 1 (confirmed)
88+
let balance_0_conf = canonical_view.balance(
89+
[((), outpoint)],
90+
|_, _| true, // trust all
91+
0,
92+
);
93+
assert_eq!(balance_0_conf.confirmed, Amount::from_sat(50_000));
94+
assert_eq!(balance_0_conf.trusted_pending, Amount::ZERO);
95+
assert_eq!(balance_0_conf, balance_1_conf);
96+
}
97+
98+
#[test]
99+
fn test_min_confirmations_with_untrusted_tx() {
100+
// 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();
118+
119+
let mut tx_graph = TxGraph::default();
120+
121+
// Create a transaction
122+
let tx = Transaction {
123+
input: vec![TxIn {
124+
previous_output: OutPoint::new(hash!("parent"), 0),
125+
..Default::default()
126+
}],
127+
output: vec![TxOut {
128+
value: Amount::from_sat(25_000),
129+
script_pubkey: ScriptBuf::new(),
130+
}],
131+
..new_tx(1)
132+
};
133+
let txid = tx.compute_txid();
134+
let outpoint = OutPoint::new(txid, 0);
135+
136+
let _ = tx_graph.insert_tx(tx.clone());
137+
138+
// Anchor at height 8, tip at height 10 (3 confirmations)
139+
let anchor = ConfirmationBlockTime {
140+
block_id: chain.get(8).unwrap().block_id(),
141+
confirmation_time: 123456,
142+
};
143+
let _ = tx_graph.insert_anchor(txid, anchor);
144+
145+
let canonical_view = tx_graph.canonical_view(
146+
&chain,
147+
chain.tip().block_id(),
148+
CanonicalizationParams::default(),
149+
);
150+
151+
// Test with min_confirmations = 5 and untrusted predicate
152+
let balance = canonical_view.balance(
153+
[((), outpoint)],
154+
|_, _| false, // don't trust
155+
5,
156+
);
157+
158+
// Should be untrusted pending (not enough confirmations and not trusted)
159+
assert_eq!(balance.confirmed, Amount::ZERO);
160+
assert_eq!(balance.trusted_pending, Amount::ZERO);
161+
assert_eq!(balance.untrusted_pending, Amount::from_sat(25_000));
162+
}
163+
164+
#[test]
165+
fn test_min_confirmations_multiple_transactions() {
166+
// 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();
189+
190+
let mut tx_graph = TxGraph::default();
191+
192+
// Create multiple transactions at different heights
193+
let mut outpoints = vec![];
194+
195+
// Transaction 0: anchored at height 5, has 11 confirmations (tip-5+1 = 15-5+1 = 11)
196+
let tx0 = Transaction {
197+
input: vec![TxIn {
198+
previous_output: OutPoint::new(hash!("parent0"), 0),
199+
..Default::default()
200+
}],
201+
output: vec![TxOut {
202+
value: Amount::from_sat(10_000),
203+
script_pubkey: ScriptBuf::new(),
204+
}],
205+
..new_tx(1)
206+
};
207+
let txid0 = tx0.compute_txid();
208+
let outpoint0 = OutPoint::new(txid0, 0);
209+
let _ = tx_graph.insert_tx(tx0);
210+
let _ = tx_graph.insert_anchor(
211+
txid0,
212+
ConfirmationBlockTime {
213+
block_id: chain.get(5).unwrap().block_id(),
214+
confirmation_time: 123456,
215+
},
216+
);
217+
outpoints.push(((), outpoint0));
218+
219+
// Transaction 1: anchored at height 10, has 6 confirmations (15-10+1 = 6)
220+
let tx1 = Transaction {
221+
input: vec![TxIn {
222+
previous_output: OutPoint::new(hash!("parent1"), 0),
223+
..Default::default()
224+
}],
225+
output: vec![TxOut {
226+
value: Amount::from_sat(20_000),
227+
script_pubkey: ScriptBuf::new(),
228+
}],
229+
..new_tx(2)
230+
};
231+
let txid1 = tx1.compute_txid();
232+
let outpoint1 = OutPoint::new(txid1, 0);
233+
let _ = tx_graph.insert_tx(tx1);
234+
let _ = tx_graph.insert_anchor(
235+
txid1,
236+
ConfirmationBlockTime {
237+
block_id: chain.get(10).unwrap().block_id(),
238+
confirmation_time: 123457,
239+
},
240+
);
241+
outpoints.push(((), outpoint1));
242+
243+
// Transaction 2: anchored at height 13, has 3 confirmations (15-13+1 = 3)
244+
let tx2 = Transaction {
245+
input: vec![TxIn {
246+
previous_output: OutPoint::new(hash!("parent2"), 0),
247+
..Default::default()
248+
}],
249+
output: vec![TxOut {
250+
value: Amount::from_sat(30_000),
251+
script_pubkey: ScriptBuf::new(),
252+
}],
253+
..new_tx(3)
254+
};
255+
let txid2 = tx2.compute_txid();
256+
let outpoint2 = OutPoint::new(txid2, 0);
257+
let _ = tx_graph.insert_tx(tx2);
258+
let _ = tx_graph.insert_anchor(
259+
txid2,
260+
ConfirmationBlockTime {
261+
block_id: chain.get(13).unwrap().block_id(),
262+
confirmation_time: 123458,
263+
},
264+
);
265+
outpoints.push(((), outpoint2));
266+
267+
let canonical_view = tx_graph.canonical_view(
268+
&chain,
269+
chain.tip().block_id(),
270+
CanonicalizationParams::default(),
271+
);
272+
273+
// Test with min_confirmations = 5
274+
// tx0: 11 confirmations -> confirmed
275+
// tx1: 6 confirmations -> confirmed
276+
// tx2: 3 confirmations -> trusted pending
277+
let balance = canonical_view.balance(outpoints.clone(), |_, _| true, 5);
278+
279+
assert_eq!(
280+
balance.confirmed,
281+
Amount::from_sat(10_000 + 20_000) // tx0 + tx1
282+
);
283+
assert_eq!(
284+
balance.trusted_pending,
285+
Amount::from_sat(30_000) // tx2
286+
);
287+
assert_eq!(balance.untrusted_pending, Amount::ZERO);
288+
289+
// Test with min_confirmations = 10
290+
// tx0: 11 confirmations -> confirmed
291+
// tx1: 6 confirmations -> trusted pending
292+
// tx2: 3 confirmations -> trusted pending
293+
let balance_high = canonical_view.balance(outpoints, |_, _| true, 10);
294+
295+
assert_eq!(
296+
balance_high.confirmed,
297+
Amount::from_sat(10_000) // only tx0
298+
);
299+
assert_eq!(
300+
balance_high.trusted_pending,
301+
Amount::from_sat(20_000 + 30_000) // tx1 + tx2
302+
);
303+
assert_eq!(balance_high.untrusted_pending, Amount::ZERO);
304+
}

0 commit comments

Comments
 (0)