From: 志宇 Date: Thu, 11 Sep 2025 03:48:27 +0000 (+0000) Subject: test(chain): Add comprehensive tests for min_confirmations parameter X-Git-Url: http://internal-gitweb-vhost/script/%22https:/database/struct.CommandStringError.html?a=commitdiff_plain;h=54409bc4aac05fb25d02909398008204eee70634;p=bdk 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 --- diff --git a/crates/chain/tests/test_canonical_view.rs b/crates/chain/tests/test_canonical_view.rs new file mode 100644 index 00000000..5d6740ac --- /dev/null +++ b/crates/chain/tests/test_canonical_view.rs @@ -0,0 +1,304 @@ +#![cfg(feature = "miniscript")] + +use bdk_chain::{local_chain::LocalChain, CanonicalizationParams, ConfirmationBlockTime, TxGraph}; +use bdk_testenv::{hash, utils::new_tx}; +use bitcoin::{Amount, OutPoint, ScriptBuf, Transaction, TxIn, TxOut}; + +#[test] +fn test_min_confirmations_parameter() { + // Create a local chain with several blocks + let chain = LocalChain::from_blocks( + [ + (0, hash!("block0")), + (1, hash!("block1")), + (2, hash!("block2")), + (3, hash!("block3")), + (4, hash!("block4")), + (5, hash!("block5")), + (6, hash!("block6")), + (7, hash!("block7")), + (8, hash!("block8")), + (9, hash!("block9")), + (10, hash!("block10")), + ] + .into(), + ) + .unwrap(); + + let mut tx_graph = TxGraph::default(); + + // Create a non-coinbase transaction + let tx = Transaction { + input: vec![TxIn { + previous_output: OutPoint::new(hash!("parent"), 0), + ..Default::default() + }], + output: vec![TxOut { + value: Amount::from_sat(50_000), + script_pubkey: ScriptBuf::new(), + }], + ..new_tx(1) + }; + let txid = tx.compute_txid(); + let outpoint = OutPoint::new(txid, 0); + + // Insert transaction into graph + let _ = tx_graph.insert_tx(tx.clone()); + + // Test 1: Transaction confirmed at height 5, tip at height 10 (6 confirmations) + let anchor_height_5 = ConfirmationBlockTime { + block_id: chain.get(5).unwrap().block_id(), + confirmation_time: 123456, + }; + let _ = tx_graph.insert_anchor(txid, anchor_height_5); + + let chain_tip = chain.tip().block_id(); + let canonical_view = + tx_graph.canonical_view(&chain, chain_tip, CanonicalizationParams::default()); + + // Test min_confirmations = 1: Should be confirmed (has 6 confirmations) + let balance_1_conf = canonical_view.balance( + [((), outpoint)], + |_, _| true, // trust all + 1, + ); + + assert_eq!(balance_1_conf.confirmed, Amount::from_sat(50_000)); + assert_eq!(balance_1_conf.trusted_pending, Amount::ZERO); + + // Test min_confirmations = 6: Should be confirmed (has exactly 6 confirmations) + let balance_6_conf = canonical_view.balance( + [((), outpoint)], + |_, _| true, // trust all + 6, + ); + assert_eq!(balance_6_conf.confirmed, Amount::from_sat(50_000)); + assert_eq!(balance_6_conf.trusted_pending, Amount::ZERO); + + // Test min_confirmations = 7: Should be trusted pending (only has 6 confirmations) + let balance_7_conf = canonical_view.balance( + [((), outpoint)], + |_, _| true, // trust all + 7, + ); + assert_eq!(balance_7_conf.confirmed, Amount::ZERO); + assert_eq!(balance_7_conf.trusted_pending, Amount::from_sat(50_000)); + + // Test min_confirmations = 0: Should behave same as 1 (confirmed) + let balance_0_conf = canonical_view.balance( + [((), outpoint)], + |_, _| true, // trust all + 0, + ); + assert_eq!(balance_0_conf.confirmed, Amount::from_sat(50_000)); + assert_eq!(balance_0_conf.trusted_pending, Amount::ZERO); + assert_eq!(balance_0_conf, balance_1_conf); +} + +#[test] +fn test_min_confirmations_with_untrusted_tx() { + // Create a local chain + let chain = LocalChain::from_blocks( + [ + (0, hash!("genesis")), + (1, hash!("b1")), + (2, hash!("b2")), + (3, hash!("b3")), + (4, hash!("b4")), + (5, hash!("b5")), + (6, hash!("b6")), + (7, hash!("b7")), + (8, hash!("b8")), + (9, hash!("b9")), + (10, hash!("tip")), + ] + .into(), + ) + .unwrap(); + + let mut tx_graph = TxGraph::default(); + + // Create a transaction + let tx = Transaction { + input: vec![TxIn { + previous_output: OutPoint::new(hash!("parent"), 0), + ..Default::default() + }], + output: vec![TxOut { + value: Amount::from_sat(25_000), + script_pubkey: ScriptBuf::new(), + }], + ..new_tx(1) + }; + let txid = tx.compute_txid(); + let outpoint = OutPoint::new(txid, 0); + + let _ = tx_graph.insert_tx(tx.clone()); + + // Anchor at height 8, tip at height 10 (3 confirmations) + let anchor = ConfirmationBlockTime { + block_id: chain.get(8).unwrap().block_id(), + confirmation_time: 123456, + }; + let _ = tx_graph.insert_anchor(txid, anchor); + + let canonical_view = tx_graph.canonical_view( + &chain, + chain.tip().block_id(), + CanonicalizationParams::default(), + ); + + // Test with min_confirmations = 5 and untrusted predicate + let balance = canonical_view.balance( + [((), outpoint)], + |_, _| false, // don't trust + 5, + ); + + // Should be untrusted pending (not enough confirmations and not trusted) + assert_eq!(balance.confirmed, Amount::ZERO); + assert_eq!(balance.trusted_pending, Amount::ZERO); + assert_eq!(balance.untrusted_pending, Amount::from_sat(25_000)); +} + +#[test] +fn test_min_confirmations_multiple_transactions() { + // Create a local chain + let chain = LocalChain::from_blocks( + [ + (0, hash!("genesis")), + (1, hash!("b1")), + (2, hash!("b2")), + (3, hash!("b3")), + (4, hash!("b4")), + (5, hash!("b5")), + (6, hash!("b6")), + (7, hash!("b7")), + (8, hash!("b8")), + (9, hash!("b9")), + (10, hash!("b10")), + (11, hash!("b11")), + (12, hash!("b12")), + (13, hash!("b13")), + (14, hash!("b14")), + (15, hash!("tip")), + ] + .into(), + ) + .unwrap(); + + let mut tx_graph = TxGraph::default(); + + // Create multiple transactions at different heights + let mut outpoints = vec![]; + + // Transaction 0: anchored at height 5, has 11 confirmations (tip-5+1 = 15-5+1 = 11) + let tx0 = Transaction { + input: vec![TxIn { + previous_output: OutPoint::new(hash!("parent0"), 0), + ..Default::default() + }], + output: vec![TxOut { + value: Amount::from_sat(10_000), + script_pubkey: ScriptBuf::new(), + }], + ..new_tx(1) + }; + let txid0 = tx0.compute_txid(); + let outpoint0 = OutPoint::new(txid0, 0); + let _ = tx_graph.insert_tx(tx0); + let _ = tx_graph.insert_anchor( + txid0, + ConfirmationBlockTime { + block_id: chain.get(5).unwrap().block_id(), + confirmation_time: 123456, + }, + ); + outpoints.push(((), outpoint0)); + + // Transaction 1: anchored at height 10, has 6 confirmations (15-10+1 = 6) + let tx1 = Transaction { + input: vec![TxIn { + previous_output: OutPoint::new(hash!("parent1"), 0), + ..Default::default() + }], + output: vec![TxOut { + value: Amount::from_sat(20_000), + script_pubkey: ScriptBuf::new(), + }], + ..new_tx(2) + }; + let txid1 = tx1.compute_txid(); + let outpoint1 = OutPoint::new(txid1, 0); + let _ = tx_graph.insert_tx(tx1); + let _ = tx_graph.insert_anchor( + txid1, + ConfirmationBlockTime { + block_id: chain.get(10).unwrap().block_id(), + confirmation_time: 123457, + }, + ); + outpoints.push(((), outpoint1)); + + // Transaction 2: anchored at height 13, has 3 confirmations (15-13+1 = 3) + let tx2 = Transaction { + input: vec![TxIn { + previous_output: OutPoint::new(hash!("parent2"), 0), + ..Default::default() + }], + output: vec![TxOut { + value: Amount::from_sat(30_000), + script_pubkey: ScriptBuf::new(), + }], + ..new_tx(3) + }; + let txid2 = tx2.compute_txid(); + let outpoint2 = OutPoint::new(txid2, 0); + let _ = tx_graph.insert_tx(tx2); + let _ = tx_graph.insert_anchor( + txid2, + ConfirmationBlockTime { + block_id: chain.get(13).unwrap().block_id(), + confirmation_time: 123458, + }, + ); + outpoints.push(((), outpoint2)); + + let canonical_view = tx_graph.canonical_view( + &chain, + chain.tip().block_id(), + CanonicalizationParams::default(), + ); + + // Test with min_confirmations = 5 + // tx0: 11 confirmations -> confirmed + // tx1: 6 confirmations -> confirmed + // tx2: 3 confirmations -> trusted pending + let balance = canonical_view.balance(outpoints.clone(), |_, _| true, 5); + + assert_eq!( + balance.confirmed, + Amount::from_sat(10_000 + 20_000) // tx0 + tx1 + ); + assert_eq!( + balance.trusted_pending, + Amount::from_sat(30_000) // tx2 + ); + assert_eq!(balance.untrusted_pending, Amount::ZERO); + + // Test with min_confirmations = 10 + // tx0: 11 confirmations -> confirmed + // tx1: 6 confirmations -> trusted pending + // tx2: 3 confirmations -> trusted pending + let balance_high = canonical_view.balance(outpoints, |_, _| true, 10); + + assert_eq!( + balance_high.confirmed, + Amount::from_sat(10_000) // only tx0 + ); + assert_eq!( + balance_high.trusted_pending, + Amount::from_sat(20_000 + 30_000) // tx1 + tx2 + ); + assert_eq!(balance_high.untrusted_pending, Amount::ZERO); +}