|
| 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