diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 817d0edc5..0b2140a70 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -14,18 +14,26 @@ use std::boxed::Box; use std::collections::{HashMap, HashSet}; use std::env; use std::future::Future; +use std::iter; use std::path::PathBuf; use std::pin::Pin; +use std::str::FromStr; use std::sync::{Arc, RwLock}; -use std::time::Duration; +use std::time::{Duration, UNIX_EPOCH}; -use bitcoin::hashes::hex::FromHex; -use bitcoin::hashes::sha256::Hash as Sha256; -use bitcoin::hashes::Hash; +use bitcoin::absolute::LockTime; +use bitcoin::block::{Header, Version as BlockVersion}; +use bitcoin::hashes::{hex::FromHex, sha256::Hash as Sha256, sha256d::Hash as Sha256d, Hash}; +use bitcoin::merkle_tree::calculate_root; +use bitcoin::script::Builder as BuilderScriptBitcoin; use bitcoin::{ - Address, Amount, Network, OutPoint, ScriptBuf, Sequence, Transaction, Txid, Witness, + opcodes::all::OP_RETURN, transaction::Version, Address, Amount, Block, BlockHash, + CompactTarget, Network, OutPoint, ScriptBuf, Sequence, Transaction, TxMerkleNode, Txid, + Witness, Wtxid, +}; +use electrsd::corepc_node::{ + Client as BitcoindClient, Node as BitcoinD, TemplateRequest, TemplateRules, }; -use electrsd::corepc_node::{Client as BitcoindClient, Node as BitcoinD}; use electrsd::{corepc_node, ElectrsD}; use electrum_client::ElectrumApi; use ldk_node::config::{AsyncPaymentsRole, Config, ElectrumSyncConfig, EsploraSyncConfig}; @@ -285,7 +293,7 @@ pub(crate) fn setup_two_nodes( ) -> (TestNode, TestNode) { println!("== Node A =="); let config_a = random_config(anchor_channels); - let node_a = setup_node(chain_source, config_a, None); + let node_a = setup_node_from_config(chain_source, config_a, None); println!("\n== Node B =="); let mut config_b = random_config(anchor_channels); @@ -301,11 +309,16 @@ pub(crate) fn setup_two_nodes( .trusted_peers_no_reserve .push(node_a.node_id()); } - let node_b = setup_node(chain_source, config_b, None); + let node_b = setup_node_from_config(chain_source, config_b, None); (node_a, node_b) } -pub(crate) fn setup_node( +pub(crate) fn setup_node(chain_source: &TestChainSource, anchor_channels: bool) -> TestNode { + let config = random_config(anchor_channels); + setup_node_from_config(chain_source, config, None) +} + +pub(crate) fn setup_node_from_config( chain_source: &TestChainSource, config: TestConfig, seed_bytes: Option>, ) -> TestNode { setup_node_for_async_payments(chain_source, config, seed_bytes, None) @@ -389,6 +402,116 @@ pub(crate) fn setup_node_for_async_payments( node } +pub(crate) fn generate_block_and_insert_transactions( + bitcoind: &BitcoindClient, electrs: &E, txs: &[Transaction], +) { + let _ = bitcoind.create_wallet("ldk_node_test"); + let _ = bitcoind.load_wallet("ldk_node_test"); + let blockchain_info = bitcoind.get_blockchain_info().expect("failed to get blockchain info"); + let cur_height = blockchain_info.blocks; + let address = bitcoind.new_address().expect("failed to get new address"); + + let request_block_template = TemplateRequest { rules: vec![TemplateRules::Segwit] }; + let bt = + bitcoind.get_block_template(&request_block_template).expect("failed to get block template"); + + // === BIP 141: Witness Commitment Calculation === + let witness_root = calculate_root( + iter::once(Wtxid::all_zeros()).chain(txs.iter().map(|tx| tx.compute_wtxid())), + ) + .map(|root| TxMerkleNode::from_byte_array(root.to_byte_array())) + .unwrap(); + + // BIP 141: Witness reserved value (32 zero bytes) + let witness_reserved_value = [0u8; 32]; + + // === Coinbase Transaction Construction === + // BIP 141: Coinbase witness contains the witness reserved value + let coinbase_witness = Witness::from(vec![witness_reserved_value.to_vec()]); + + // BIP 141: Calculate commitment hash = Double-SHA256(witness root || witness reserved value) + let commitment_hash = + Sha256d::hash(&[witness_root.to_byte_array(), witness_reserved_value].concat()); + + // Format: OP_RETURN + OP_PUSHBYTES_36 + 0xaa21a9ed + 32-byte commitment hash + let witness_commitment_script = BuilderScriptBitcoin::new() + .push_opcode(OP_RETURN) + .push_slice(&{ + let mut data = [0u8; 36]; + data[..4].copy_from_slice(&[0xaa, 0x21, 0xa9, 0xed]); + data[4..].copy_from_slice(&commitment_hash.to_byte_array()); + data + }) + .into_script(); + + // BIP 34: Block height in coinbase input script + let block_height = bt.height; + let height_script = BuilderScriptBitcoin::new() + .push_int(block_height as i64) // BIP 34: block height as first item + .push_int(rand::random()) // Random nonce for uniqueness + .into_script(); + + // Do not use the coinbase value from the block template. + // The template may include transactions not actually mined, so fees may be incorrect. + let coinbase_output_value = 1_250_000_000; + + let coinbase_tx = Transaction { + version: Version::ONE, + lock_time: LockTime::from_height(0).unwrap(), + input: vec![bitcoin::TxIn { + previous_output: OutPoint::default(), // Null outpoint for coinbase + script_sig: height_script, // BIP 34: height + random data + sequence: Sequence::default(), + witness: coinbase_witness, // BIP 141: witness reserved value + }], + output: vec![ + // Coinbase reward output + bitcoin::TxOut { + value: Amount::from_sat(coinbase_output_value), + script_pubkey: address.script_pubkey(), + }, + // BIP 141: Witness commitment output (must be last output) + bitcoin::TxOut { value: Amount::ZERO, script_pubkey: witness_commitment_script }, + ], + }; + + // === Block Construction === + let bits: [u8; 4] = Vec::from_hex(&bt.bits).unwrap().try_into().expect("bits must be 4 bytes"); + let prev_hash_block = BlockHash::from_str(&bt.previous_block_hash).expect("invalid prev hash"); + + let txdata = [coinbase_tx].into_iter().chain(txs.iter().cloned()).collect::>(); + let mut block = Block { + header: Header { + version: BlockVersion::default(), + prev_blockhash: prev_hash_block, + merkle_root: TxMerkleNode::all_zeros(), // Will be calculated below + time: Ord::max(bt.min_time, UNIX_EPOCH.elapsed().unwrap().as_secs() as u32), + bits: CompactTarget::from_consensus(u32::from_be_bytes(bits)), + nonce: 0, + }, + txdata, + }; + + block.header.merkle_root = block.compute_merkle_root().expect("must compute"); + + for nonce in 0..=u32::MAX { + block.header.nonce = nonce; + if block.header.target().is_met_by(block.block_hash()) { + break; + } + } + + match bitcoind.submit_block(&block) { + Ok(_) => {}, + Err(e) => panic!("Failed to submit block: {:?}", e), + } + wait_for_block(electrs, cur_height as usize + 1); + + txs.iter().for_each(|tx| { + wait_for_tx(electrs, tx.compute_txid()); + }); +} + pub(crate) fn generate_blocks_and_wait( bitcoind: &BitcoindClient, electrs: &E, num: usize, ) { @@ -570,11 +693,13 @@ pub(crate) fn bump_fee_and_broadcast( let tx_bytes = Vec::::from_hex(&signed_result.hex).unwrap(); tx = bitcoin::consensus::encode::deserialize::(&tx_bytes).unwrap(); + if is_insert_block { + generate_block_and_insert_transactions(bitcoind, electrs, &[tx.clone()]); + return tx; + } + match bitcoind.send_raw_transaction(&tx) { Ok(res) => { - if is_insert_block { - generate_blocks_and_wait(bitcoind, electrs, 1); - } let new_txid: Txid = res.0.parse().unwrap(); wait_for_tx(electrs, new_txid); return tx; diff --git a/tests/integration_tests_rust.rs b/tests/integration_tests_rust.rs index 64a78e11b..912fb3257 100644 --- a/tests/integration_tests_rust.rs +++ b/tests/integration_tests_rust.rs @@ -20,10 +20,11 @@ use common::{ bump_fee_and_broadcast, distribute_funds_unconfirmed, do_channel_full_cycle, expect_channel_pending_event, expect_channel_ready_event, expect_event, expect_payment_claimable_event, expect_payment_received_event, expect_payment_successful_event, - generate_blocks_and_wait, open_channel, open_channel_push_amt, premine_and_distribute_funds, - premine_blocks, prepare_rbf, random_config, random_listening_addresses, - setup_bitcoind_and_electrsd, setup_builder, setup_node, setup_node_for_async_payments, - setup_two_nodes, wait_for_tx, TestChainSource, TestSyncStore, + generate_block_and_insert_transactions, generate_blocks_and_wait, open_channel, + open_channel_push_amt, premine_and_distribute_funds, premine_blocks, prepare_rbf, + random_config, random_listening_addresses, setup_bitcoind_and_electrsd, setup_builder, + setup_node, setup_node_for_async_payments, setup_node_from_config, setup_two_nodes, + wait_for_tx, TestChainSource, TestSyncStore, }; use ldk_node::config::{AsyncPaymentsRole, EsploraSyncConfig}; use ldk_node::liquidity::LSPS2ServiceConfig; @@ -596,7 +597,8 @@ fn onchain_wallet_recovery() { let seed_bytes = vec![42u8; 64]; let original_config = random_config(true); - let original_node = setup_node(&chain_source, original_config, Some(seed_bytes.clone())); + let original_node = + setup_node_from_config(&chain_source, original_config, Some(seed_bytes.clone())); let premine_amount_sat = 100_000; @@ -635,7 +637,7 @@ fn onchain_wallet_recovery() { // Now we start from scratch, only the seed remains the same. let recovered_config = random_config(true); - let recovered_node = setup_node(&chain_source, recovered_config, Some(seed_bytes)); + let recovered_node = setup_node_from_config(&chain_source, recovered_config, Some(seed_bytes)); recovered_node.sync_wallets().unwrap(); assert_eq!( @@ -668,36 +670,34 @@ fn onchain_wallet_recovery() { } #[test] -fn test_rbf_via_mempool() { - run_rbf_test(false); +fn test_rbf_only_in_mempool() { + run_rbf_test(false, false); } #[test] -fn test_rbf_via_direct_block_insertion() { - run_rbf_test(true); +fn test_rbf_direct_block_insertion_rbf_tx() { + run_rbf_test(true, false); +} + +#[test] +fn test_rbf_direct_block_insertion_original_tx() { + run_rbf_test(false, true); } // `is_insert_block`: // - `true`: transaction is mined immediately (no mempool), testing confirmed-Tx handling. // - `false`: transaction stays in mempool until confirmation, testing unconfirmed-Tx handling. -fn run_rbf_test(is_insert_block: bool) { +fn run_rbf_test(is_insert_block: bool, is_insertion_original_tx: bool) { let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); let chain_source_bitcoind = TestChainSource::BitcoindRpcSync(&bitcoind); let chain_source_electrsd = TestChainSource::Electrum(&electrsd); let chain_source_esplora = TestChainSource::Esplora(&electrsd); - macro_rules! config_node { - ($chain_source: expr, $anchor_channels: expr) => {{ - let config_a = random_config($anchor_channels); - let node = setup_node(&$chain_source, config_a, None); - node - }}; - } let anchor_channels = false; let nodes = vec![ - config_node!(chain_source_electrsd, anchor_channels), - config_node!(chain_source_bitcoind, anchor_channels), - config_node!(chain_source_esplora, anchor_channels), + setup_node(&chain_source_bitcoind, anchor_channels), + setup_node(&chain_source_electrsd, anchor_channels), + setup_node(&chain_source_esplora, anchor_channels), ]; let (bitcoind, electrs) = (&bitcoind.client, &electrsd.client); @@ -730,15 +730,29 @@ fn run_rbf_test(is_insert_block: bool) { }; } + macro_rules! validate_total_onchain_balance { + ($expected_balance_sat: expr) => { + for node in &nodes { + node.sync_wallets().unwrap(); + let balances = node.list_balances(); + assert_eq!(balances.total_onchain_balance_sats, $expected_balance_sat); + } + }; + } + let scripts_buf: HashSet = all_addrs.iter().map(|addr| addr.script_pubkey()).collect(); let mut tx; let mut fee_output_index; - // Modify the output to the nodes + let mut final_amount_sat = 0; + let mut original_tx; + + // Step 1: Bump fee and change output address distribute_funds_all_nodes!(); validate_balances!(amount_sat, false); (tx, fee_output_index) = prepare_rbf(electrs, txid, &scripts_buf); + original_tx = tx.clone(); tx.output.iter_mut().for_each(|output| { if scripts_buf.contains(&output.script_pubkey) { let new_addr = bitcoind.new_address().unwrap(); @@ -746,42 +760,68 @@ fn run_rbf_test(is_insert_block: bool) { } }); bump_fee_and_broadcast(bitcoind, electrs, tx, fee_output_index, is_insert_block); - validate_balances!(0, is_insert_block); + if is_insertion_original_tx { + generate_block_and_insert_transactions(bitcoind, electrs, &[original_tx.clone()]); + } + if is_insertion_original_tx { + final_amount_sat += amount_sat; + } + validate_balances!(final_amount_sat, is_insert_block || is_insertion_original_tx); - // Not modifying the output scripts, but still bumping the fee. + // Step 2: Bump fee only distribute_funds_all_nodes!(); - validate_balances!(amount_sat, false); + validate_total_onchain_balance!(amount_sat + final_amount_sat); (tx, fee_output_index) = prepare_rbf(electrs, txid, &scripts_buf); + original_tx = tx.clone(); bump_fee_and_broadcast(bitcoind, electrs, tx, fee_output_index, is_insert_block); - validate_balances!(amount_sat, is_insert_block); + if is_insertion_original_tx { + generate_block_and_insert_transactions(bitcoind, electrs, &[original_tx.clone()]); + } + final_amount_sat += amount_sat; + validate_balances!(final_amount_sat, is_insert_block || is_insertion_original_tx); - let mut final_amount_sat = amount_sat * 2; + // Step 3: Increase output value let value_sat = 21_000; - - // Increase the value of the nodes' outputs distribute_funds_all_nodes!(); + validate_total_onchain_balance!(amount_sat + final_amount_sat); (tx, fee_output_index) = prepare_rbf(electrs, txid, &scripts_buf); + original_tx = tx.clone(); tx.output.iter_mut().for_each(|output| { if scripts_buf.contains(&output.script_pubkey) { output.value = Amount::from_sat(output.value.to_sat() + value_sat); } }); + tx.output[fee_output_index].value -= Amount::from_sat(scripts_buf.len() as u64 * value_sat); bump_fee_and_broadcast(bitcoind, electrs, tx, fee_output_index, is_insert_block); - final_amount_sat += value_sat; - validate_balances!(final_amount_sat, is_insert_block); + if is_insertion_original_tx { + generate_block_and_insert_transactions(bitcoind, electrs, &[original_tx.clone()]); + } + final_amount_sat += amount_sat; + if !is_insertion_original_tx { + final_amount_sat += value_sat; + } + validate_balances!(final_amount_sat, is_insert_block || is_insertion_original_tx); - // Decreases the value of the nodes' outputs + // Step 4: Decrease output value distribute_funds_all_nodes!(); - final_amount_sat += amount_sat; + validate_total_onchain_balance!(amount_sat + final_amount_sat); (tx, fee_output_index) = prepare_rbf(electrs, txid, &scripts_buf); + original_tx = tx.clone(); tx.output.iter_mut().for_each(|output| { if scripts_buf.contains(&output.script_pubkey) { output.value = Amount::from_sat(output.value.to_sat() - value_sat); } }); + tx.output[fee_output_index].value += Amount::from_sat(scripts_buf.len() as u64 * value_sat); bump_fee_and_broadcast(bitcoind, electrs, tx, fee_output_index, is_insert_block); - final_amount_sat -= value_sat; - validate_balances!(final_amount_sat, is_insert_block); + if is_insertion_original_tx { + generate_block_and_insert_transactions(bitcoind, electrs, &[original_tx.clone()]); + } + final_amount_sat += amount_sat; + if !is_insertion_original_tx { + final_amount_sat -= value_sat; + } + validate_balances!(final_amount_sat, is_insert_block || is_insertion_original_tx); if !is_insert_block { generate_blocks_and_wait(bitcoind, electrs, 1); @@ -807,7 +847,7 @@ fn sign_verify_msg() { let (_bitcoind, electrsd) = setup_bitcoind_and_electrsd(); let config = random_config(true); let chain_source = TestChainSource::Esplora(&electrsd); - let node = setup_node(&chain_source, config, None); + let node = setup_node_from_config(&chain_source, config, None); // Tests arbitrary message signing and later verification let msg = "OK computer".as_bytes(); @@ -1174,7 +1214,7 @@ fn async_payment() { config_receiver.node_config.node_alias = None; config_receiver.log_writer = TestLogWriter::Custom(Arc::new(MultiNodeLogger::new("receiver ".to_string()))); - let node_receiver = setup_node(&chain_source, config_receiver, None); + let node_receiver = setup_node_from_config(&chain_source, config_receiver, None); let address_sender = node_sender.onchain_payment().new_address().unwrap(); let address_sender_lsp = node_sender_lsp.onchain_payment().new_address().unwrap(); @@ -1296,8 +1336,8 @@ fn test_node_announcement_propagation() { config_b.node_config.listening_addresses = Some(node_b_listening_addresses.clone()); config_b.node_config.announcement_addresses = None; - let node_a = setup_node(&chain_source, config_a, None); - let node_b = setup_node(&chain_source, config_b, None); + let node_a = setup_node_from_config(&chain_source, config_a, None); + let node_b = setup_node_from_config(&chain_source, config_b, None); let address_a = node_a.onchain_payment().new_address().unwrap(); let premine_amount_sat = 5_000_000; @@ -1753,7 +1793,7 @@ fn facade_logging() { config.log_writer = TestLogWriter::LogFacade; println!("== Facade logging starts =="); - let _node = setup_node(&chain_source, config, None); + let _node = setup_node_from_config(&chain_source, config, None); assert!(!logger.retrieve_logs().is_empty()); for (_, entry) in logger.retrieve_logs().iter().enumerate() { @@ -1834,6 +1874,6 @@ async fn drop_in_async_context() { let seed_bytes = vec![42u8; 64]; let config = random_config(true); - let node = setup_node(&chain_source, config, Some(seed_bytes)); + let node = setup_node_from_config(&chain_source, config, Some(seed_bytes)); node.stop().unwrap(); } diff --git a/tests/reorg_test.rs b/tests/reorg_test.rs index 03ace908f..28bd43a02 100644 --- a/tests/reorg_test.rs +++ b/tests/reorg_test.rs @@ -1,20 +1,22 @@ mod common; -use std::collections::HashMap; - -use bitcoin::Amount; +use bitcoin::{Amount, ScriptBuf}; use ldk_node::payment::{PaymentDirection, PaymentKind}; use ldk_node::{Event, LightningBalance, PendingSweepBalance}; -use proptest::prelude::prop; -use proptest::proptest; +use proptest::strategy::Strategy; +use proptest::strategy::ValueTree; +use proptest::{prelude::prop, proptest}; +use std::collections::{HashMap, HashSet}; use crate::common::{ - expect_event, generate_blocks_and_wait, invalidate_blocks, open_channel, - premine_and_distribute_funds, random_config, setup_bitcoind_and_electrsd, setup_node, - wait_for_outpoint_spend, TestChainSource, + bump_fee_and_broadcast, distribute_funds_unconfirmed, expect_event, + generate_block_and_insert_transactions, generate_blocks_and_wait, invalidate_blocks, + open_channel, premine_and_distribute_funds, premine_blocks, prepare_rbf, + setup_bitcoind_and_electrsd, setup_node, wait_for_outpoint_spend, TestChainSource, }; proptest! { #![proptest_config(proptest::test_runner::Config::with_cases(5))] + #[test] fn reorg_test(reorg_depth in 1..=6usize, force_close in prop::bool::ANY) { let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); @@ -23,18 +25,11 @@ proptest! { let chain_source_electrsd = TestChainSource::Electrum(&electrsd); let chain_source_esplora = TestChainSource::Esplora(&electrsd); - macro_rules! config_node { - ($chain_source: expr, $anchor_channels: expr) => {{ - let config_a = random_config($anchor_channels); - let node = setup_node(&$chain_source, config_a, None); - node - }}; - } let anchor_channels = true; let nodes = vec![ - config_node!(chain_source_electrsd, anchor_channels), - config_node!(chain_source_bitcoind, anchor_channels), - config_node!(chain_source_esplora, anchor_channels), + setup_node(&chain_source_electrsd, anchor_channels), + setup_node(&chain_source_bitcoind, anchor_channels), + setup_node(&chain_source_esplora, anchor_channels), ]; let (bitcoind, electrs) = (&bitcoind.client, &electrsd.client); @@ -192,4 +187,98 @@ proptest! { assert_eq!(node.next_event(), None); }); } + + #[test] + fn test_reorg_rbf( + reorg_depth in 2..=5usize, + quantity_rbf in 2..=6usize, + ) { + let mut runner = proptest::test_runner::TestRunner::default(); + let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); + + let chain_source_bitcoind = TestChainSource::BitcoindRpcSync(&bitcoind); + let chain_source_electrsd = TestChainSource::Electrum(&electrsd); + let chain_source_esplora = TestChainSource::Esplora(&electrsd); + + let anchor_channels = true; + let nodes = vec![ + setup_node(&chain_source_bitcoind, anchor_channels), + setup_node(&chain_source_electrsd, anchor_channels), + setup_node(&chain_source_esplora, anchor_channels), + ]; + + let (bitcoind, electrs) = (&bitcoind.client, &electrsd.client); + + let mut amount_sat = 2_100_000; + let all_addrs = + nodes.iter().map(|node| node.onchain_payment().new_address().unwrap()).collect::>(); + let scripts_buf: HashSet = + all_addrs.iter().map(|addr| addr.script_pubkey()).collect(); + + premine_blocks(bitcoind, electrs); + generate_blocks_and_wait(bitcoind, electrs, reorg_depth); + let txid = distribute_funds_unconfirmed(bitcoind, electrs, all_addrs, Amount::from_sat(amount_sat)); + + let mut is_spendable = false; + macro_rules! verify_wallet_balances_and_transactions { + ($expected_balance_sat: expr, $expected_size_list_payments: expr) => { + let spend_balance = if is_spendable { $expected_balance_sat } else { 0 }; + for node in &nodes { + node.sync_wallets().unwrap(); + let balances = node.list_balances(); + assert_eq!(balances.total_onchain_balance_sats, $expected_balance_sat); + assert_eq!(balances.spendable_onchain_balance_sats, spend_balance); + } + }; + } + + let mut tx_to_amount = HashMap::new(); + let (mut tx, fee_output_index) = prepare_rbf(electrs, txid, &scripts_buf); + tx_to_amount.insert(tx.clone(), amount_sat); + generate_block_and_insert_transactions(bitcoind, electrs, &[]); + verify_wallet_balances_and_transactions!(amount_sat, expected_size_list_payments); + for _ in 0..quantity_rbf { + let is_alterable_value = prop::bool::ANY.new_tree(&mut runner).unwrap().current(); + if is_alterable_value { + let value_sat = (5000..20000u64).new_tree(&mut runner).unwrap().current(); + let is_acrent_value = prop::bool::ANY.new_tree(&mut runner).unwrap().current(); + amount_sat = if is_acrent_value {amount_sat + value_sat} else {amount_sat - value_sat}; + for output in &mut tx.output { + if scripts_buf.contains(&output.script_pubkey) { + output.value = Amount::from_sat(amount_sat); + } + } + let fee_sat = Amount::from_sat(scripts_buf.len() as u64 * value_sat); + if is_acrent_value { + tx.output[fee_output_index].value -= fee_sat; + } else { + tx.output[fee_output_index].value += fee_sat; + } + + } + + tx = bump_fee_and_broadcast(bitcoind, electrs, tx, fee_output_index, is_spendable); + tx_to_amount.insert(tx.clone(), amount_sat); + + verify_wallet_balances_and_transactions!(amount_sat, expected_size_list_payments); + } + + is_spendable = true; + let index_tx_confirm = (0..tx_to_amount.len() - 1).new_tree(&mut runner).unwrap().current(); + let tx_to_confirm = tx_to_amount.iter().nth(index_tx_confirm).unwrap(); + generate_block_and_insert_transactions(bitcoind, electrs, &[tx_to_confirm.0.clone()]); + generate_blocks_and_wait(bitcoind, electrs, reorg_depth - 1); + amount_sat = *tx_to_confirm.1; + verify_wallet_balances_and_transactions!(amount_sat, expected_size_list_payments); + + invalidate_blocks(bitcoind, reorg_depth); + generate_block_and_insert_transactions(bitcoind, electrs, &[]); + + let index_tx_confirm = (0..tx_to_amount.len() - 1).new_tree(&mut runner).unwrap().current(); + let tx_to_confirm = tx_to_amount.iter().nth(index_tx_confirm).unwrap(); + generate_block_and_insert_transactions(bitcoind, electrs, &[tx_to_confirm.0.clone()]); + amount_sat = *tx_to_confirm.1; + generate_blocks_and_wait(bitcoind, electrs, 5); + verify_wallet_balances_and_transactions!(amount_sat, expected_size_list_payments); + } }