]> Untitled Git - bdk/commitdiff
test(chain): Add comprehensive tests for min_confirmations parameter
author志宇 <hello@evanlinjin.me>
Thu, 11 Sep 2025 03:48:27 +0000 (03:48 +0000)
committer志宇 <hello@evanlinjin.me>
Wed, 17 Sep 2025 23:46:29 +0000 (23:46 +0000)
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 <noreply@anthropic.com>
crates/chain/tests/test_canonical_view.rs [new file with mode: 0644]

diff --git a/crates/chain/tests/test_canonical_view.rs b/crates/chain/tests/test_canonical_view.rs
new file mode 100644 (file)
index 0000000..5d6740a
--- /dev/null
@@ -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);
+}