Skip to content

Commit 69959f7

Browse files
Add property-based reorg test for RBF transactions
This test simulates multiple RBF transactions, randomly confirms one, then simulates a reorg and confirms another at random. This ensures the wallet correctly handles cases where any RBF transaction may be confirmed after
1 parent 51db964 commit 69959f7

File tree

1 file changed

+103
-5
lines changed

1 file changed

+103
-5
lines changed

tests/reorg_test.rs

Lines changed: 103 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,22 @@
11
mod common;
2-
use bitcoin::Amount;
2+
use bitcoin::{Amount, ScriptBuf};
33
use ldk_node::payment::{PaymentDirection, PaymentKind};
44
use ldk_node::{Event, LightningBalance, PendingSweepBalance};
5+
use proptest::strategy::Strategy;
6+
use proptest::strategy::ValueTree;
57
use proptest::{prelude::prop, proptest};
6-
use std::collections::HashMap;
8+
use std::collections::{HashMap, HashSet};
79

810
use crate::common::{
9-
expect_event, generate_blocks_and_wait, invalidate_blocks, new_node, open_channel,
10-
premine_and_distribute_funds, setup_bitcoind_and_electrsd, wait_for_outpoint_spend,
11-
TestChainSource,
11+
bump_fee_and_broadcast, distribute_funds_unconfirmed, expect_event,
12+
generate_block_and_insert_transactions, generate_blocks_and_wait, invalidate_blocks, new_node,
13+
open_channel, premine_and_distribute_funds, premine_blocks, prepare_rbf,
14+
setup_bitcoind_and_electrsd, wait_for_outpoint_spend, TestChainSource,
1215
};
1316

1417
proptest! {
1518
#![proptest_config(proptest::test_runner::Config::with_cases(5))]
19+
1620
#[test]
1721
fn reorg_test(reorg_depth in 1..=6usize, force_close in prop::bool::ANY) {
1822
let (bitcoind, electrsd) = setup_bitcoind_and_electrsd();
@@ -183,4 +187,98 @@ proptest! {
183187
assert_eq!(node.next_event(), None);
184188
});
185189
}
190+
191+
#[test]
192+
fn test_reorg_rbf(
193+
reorg_depth in 2..=5usize,
194+
quantity_rbf in 2..=6usize,
195+
) {
196+
let mut runner = proptest::test_runner::TestRunner::default();
197+
let (bitcoind, electrsd) = setup_bitcoind_and_electrsd();
198+
199+
let chain_source_bitcoind = TestChainSource::BitcoindRpcSync(&bitcoind);
200+
let chain_source_electrsd = TestChainSource::Electrum(&electrsd);
201+
let chain_source_esplora = TestChainSource::Esplora(&electrsd);
202+
203+
let anchor_channels = true;
204+
let nodes = vec![
205+
new_node(&chain_source_bitcoind, anchor_channels),
206+
new_node(&chain_source_electrsd, anchor_channels),
207+
new_node(&chain_source_esplora, anchor_channels),
208+
];
209+
210+
let (bitcoind, electrs) = (&bitcoind.client, &electrsd.client);
211+
212+
let mut amount_sat = 2_100_000;
213+
let all_addrs =
214+
nodes.iter().map(|node| node.onchain_payment().new_address().unwrap()).collect::<Vec<_>>();
215+
let scripts_buf: HashSet<ScriptBuf> =
216+
all_addrs.iter().map(|addr| addr.script_pubkey()).collect();
217+
218+
premine_blocks(bitcoind, electrs);
219+
generate_blocks_and_wait(bitcoind, electrs, reorg_depth);
220+
let txid = distribute_funds_unconfirmed(bitcoind, electrs, all_addrs, Amount::from_sat(amount_sat));
221+
222+
let mut is_spendable = false;
223+
macro_rules! verify_wallet_balances_and_transactions {
224+
($expected_balance_sat: expr, $expected_size_list_payments: expr) => {
225+
let spend_balance = if is_spendable { $expected_balance_sat } else { 0 };
226+
for node in &nodes {
227+
node.sync_wallets().unwrap();
228+
let balances = node.list_balances();
229+
assert_eq!(balances.total_onchain_balance_sats, $expected_balance_sat);
230+
assert_eq!(balances.spendable_onchain_balance_sats, spend_balance);
231+
}
232+
};
233+
}
234+
235+
let mut tx_to_amount = HashMap::new();
236+
let (mut tx, fee_output_index) = prepare_rbf(electrs, txid, &scripts_buf);
237+
tx_to_amount.insert(tx.clone(), amount_sat);
238+
generate_block_and_insert_transactions(bitcoind, electrs, &[]);
239+
verify_wallet_balances_and_transactions!(amount_sat, expected_size_list_payments);
240+
for _ in 0..quantity_rbf {
241+
let is_alterable_value = prop::bool::ANY.new_tree(&mut runner).unwrap().current();
242+
if is_alterable_value {
243+
let value_sat = (5000..20000u64).new_tree(&mut runner).unwrap().current();
244+
let is_acrent_value = prop::bool::ANY.new_tree(&mut runner).unwrap().current();
245+
amount_sat = if is_acrent_value {amount_sat + value_sat} else {amount_sat - value_sat};
246+
for output in &mut tx.output {
247+
if scripts_buf.contains(&output.script_pubkey) {
248+
output.value = Amount::from_sat(amount_sat);
249+
}
250+
}
251+
let fee_sat = Amount::from_sat(scripts_buf.len() as u64 * value_sat);
252+
if is_acrent_value {
253+
tx.output[fee_output_index].value -= fee_sat;
254+
} else {
255+
tx.output[fee_output_index].value += fee_sat;
256+
}
257+
258+
}
259+
260+
tx = bump_fee_and_broadcast(bitcoind, electrs, tx, fee_output_index, is_spendable);
261+
tx_to_amount.insert(tx.clone(), amount_sat);
262+
263+
verify_wallet_balances_and_transactions!(amount_sat, expected_size_list_payments);
264+
}
265+
266+
is_spendable = true;
267+
let index_tx_confirm = (0..tx_to_amount.len() - 1).new_tree(&mut runner).unwrap().current();
268+
let tx_to_confirm = tx_to_amount.iter().nth(index_tx_confirm).unwrap();
269+
generate_block_and_insert_transactions(bitcoind, electrs, &[tx_to_confirm.0.clone()]);
270+
generate_blocks_and_wait(bitcoind, electrs, reorg_depth - 1);
271+
amount_sat = *tx_to_confirm.1;
272+
verify_wallet_balances_and_transactions!(amount_sat, expected_size_list_payments);
273+
274+
invalidate_blocks(bitcoind, reorg_depth);
275+
generate_block_and_insert_transactions(bitcoind, electrs, &[]);
276+
277+
let index_tx_confirm = (0..tx_to_amount.len() - 1).new_tree(&mut runner).unwrap().current();
278+
let tx_to_confirm = tx_to_amount.iter().nth(index_tx_confirm).unwrap();
279+
generate_block_and_insert_transactions(bitcoind, electrs, &[tx_to_confirm.0.clone()]);
280+
amount_sat = *tx_to_confirm.1;
281+
generate_blocks_and_wait(bitcoind, electrs, 5);
282+
verify_wallet_balances_and_transactions!(amount_sat, expected_size_list_payments);
283+
}
186284
}

0 commit comments

Comments
 (0)