Skip to content

Commit 0a9bee3

Browse files
Add RBF test for output value change and fee bump
1 parent 0b5dae7 commit 0a9bee3

File tree

2 files changed

+285
-5
lines changed

2 files changed

+285
-5
lines changed

tests/common/mod.rs

Lines changed: 195 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,12 @@ use lightning_types::payment::{PaymentHash, PaymentPreimage};
2929

3030
use lightning_persister::fs_store::FilesystemStore;
3131

32+
use bitcoin::hashes::hex::FromHex;
3233
use bitcoin::hashes::sha256::Hash as Sha256;
3334
use bitcoin::hashes::Hash;
34-
use bitcoin::{Address, Amount, Network, OutPoint, Txid};
35+
use bitcoin::{
36+
Address, Amount, Network, OutPoint, ScriptBuf, Sequence, Transaction, Txid, Witness,
37+
};
3538

3639
use electrsd::corepc_node::Client as BitcoindClient;
3740
use electrsd::corepc_node::Node as BitcoinD;
@@ -40,7 +43,9 @@ use electrum_client::ElectrumApi;
4043

4144
use rand::distributions::Alphanumeric;
4245
use rand::{thread_rng, Rng};
46+
use serde_json::{json, Value};
4347

48+
use std::collections::{HashMap, HashSet};
4449
use std::env;
4550
use std::path::PathBuf;
4651
use std::sync::{Arc, RwLock};
@@ -470,16 +475,47 @@ where
470475
pub(crate) fn premine_and_distribute_funds<E: ElectrumApi>(
471476
bitcoind: &BitcoindClient, electrs: &E, addrs: Vec<Address>, amount: Amount,
472477
) {
478+
premine_blocks(bitcoind, electrs);
479+
480+
distribute_funds(bitcoind, electrs, addrs, amount);
481+
}
482+
483+
pub(crate) fn premine_blocks<E: ElectrumApi>(bitcoind: &BitcoindClient, electrs: &E) {
473484
let _ = bitcoind.create_wallet("ldk_node_test");
474485
let _ = bitcoind.load_wallet("ldk_node_test");
475486
generate_blocks_and_wait(bitcoind, electrs, 101);
487+
}
476488

477-
for addr in addrs {
478-
let txid = bitcoind.send_to_address(&addr, amount).unwrap().0.parse().unwrap();
479-
wait_for_tx(electrs, txid);
489+
pub(crate) fn distribute_funds_unconfirmed<E: ElectrumApi>(
490+
bitcoind: &BitcoindClient, electrs: &E, addrs: Vec<Address>, amount: Amount,
491+
) -> Txid {
492+
let mut amounts = HashMap::<String, f64>::new();
493+
for addr in &addrs {
494+
amounts.insert(addr.to_string(), amount.to_btc());
480495
}
481496

497+
let empty_account = json!("");
498+
let amounts_json = json!(amounts);
499+
let txid = bitcoind
500+
.call::<Value>("sendmany", &[empty_account, amounts_json])
501+
.unwrap()
502+
.as_str()
503+
.unwrap()
504+
.parse()
505+
.unwrap();
506+
507+
wait_for_tx(electrs, txid);
508+
509+
txid
510+
}
511+
512+
pub(crate) fn distribute_funds<E: ElectrumApi>(
513+
bitcoind: &BitcoindClient, electrs: &E, addrs: Vec<Address>, amount: Amount,
514+
) -> Txid {
515+
let address_txid_map = distribute_funds_unconfirmed(bitcoind, electrs, addrs, amount);
482516
generate_blocks_and_wait(bitcoind, electrs, 1);
517+
518+
address_txid_map
483519
}
484520

485521
pub fn open_channel(
@@ -1074,6 +1110,161 @@ pub(crate) fn do_channel_full_cycle<E: ElectrumApi>(
10741110
println!("\nB stopped");
10751111
}
10761112

1113+
pub(crate) struct SetupRBF {
1114+
pub nodes_a: Vec<TestNode>,
1115+
pub nodes_b: Vec<TestNode>,
1116+
pub addrs_a: Vec<Address>,
1117+
pub addrs_b: Vec<Address>,
1118+
}
1119+
1120+
impl SetupRBF {
1121+
pub(crate) fn new(bitcoind: &BitcoinD, electrsd: &ElectrsD) -> Self {
1122+
let chain_source_bitcoind = TestChainSource::BitcoindRpcSync(bitcoind);
1123+
let chain_source_electrum = TestChainSource::Electrum(electrsd);
1124+
let chain_source_esplora = TestChainSource::Esplora(electrsd);
1125+
1126+
let (node_bitcoind_a, node_bitcoind_b) =
1127+
setup_two_nodes(&chain_source_bitcoind, false, false, false);
1128+
let (node_electrsd_a, node_electrsd_b) =
1129+
setup_two_nodes(&chain_source_electrum, false, false, false);
1130+
let (node_esplora_a, node_esplora_b) =
1131+
setup_two_nodes(&chain_source_esplora, false, false, false);
1132+
1133+
let nodes_a = vec![node_bitcoind_a, node_electrsd_a, node_esplora_a];
1134+
let nodes_b = vec![node_bitcoind_b, node_electrsd_b, node_esplora_b];
1135+
1136+
let addrs_a = nodes_a
1137+
.iter()
1138+
.map(|node| node.onchain_payment().new_address().unwrap())
1139+
.collect::<Vec<_>>();
1140+
let addrs_b = nodes_b
1141+
.iter()
1142+
.map(|node| node.onchain_payment().new_address().unwrap())
1143+
.collect::<Vec<_>>();
1144+
1145+
Self { nodes_a, nodes_b, addrs_a, addrs_b }
1146+
}
1147+
1148+
pub(crate) fn sync_wallets(&self) {
1149+
for node in &self.nodes_a {
1150+
node.sync_wallets().unwrap();
1151+
}
1152+
for node in &self.nodes_b {
1153+
node.sync_wallets().unwrap();
1154+
}
1155+
}
1156+
1157+
pub(crate) fn validate_balances(
1158+
&self, nodes: &[TestNode], expected_balance: u64, is_spendable: bool,
1159+
) {
1160+
let spend_balance = if is_spendable { expected_balance } else { 0 };
1161+
for node in nodes.iter() {
1162+
assert_eq!(node.list_balances().total_onchain_balance_sats, expected_balance);
1163+
assert_eq!(node.list_balances().spendable_onchain_balance_sats, spend_balance);
1164+
}
1165+
}
1166+
1167+
pub(crate) fn setup_initial_funding<E: ElectrumApi>(
1168+
&self, bitcoind: &BitcoindClient, electrs: &E, amount: u64,
1169+
) -> bitcoin::Txid {
1170+
premine_blocks(bitcoind, electrs);
1171+
let all_addrs = self.addrs_a.iter().chain(self.addrs_b.iter()).cloned().collect::<Vec<_>>();
1172+
distribute_funds_unconfirmed(bitcoind, electrs, all_addrs, Amount::from_sat(amount))
1173+
}
1174+
1175+
pub(crate) fn pump_fee_with_rbf_replacement<E: ElectrumApi>(
1176+
&self, bitcoind: &BitcoindClient, electrs: &E, original_tx: &mut Transaction,
1177+
fee_output_index: usize,
1178+
) -> Txid {
1179+
let mut pump_fee_amount = 1 * original_tx.vsize() as u64;
1180+
1181+
macro_rules! bump_fee {
1182+
() => {{
1183+
let fee_output = &mut original_tx.output[fee_output_index];
1184+
let new_fee_value = fee_output.value.to_sat().saturating_sub(pump_fee_amount);
1185+
fee_output.value = Amount::from_sat(new_fee_value);
1186+
1187+
if new_fee_value < 546 {
1188+
// dust limit
1189+
panic!("Warning: Fee output approaching dust limit ({} sats)", new_fee_value);
1190+
}
1191+
1192+
pump_fee_amount += pump_fee_amount * 5;
1193+
1194+
for input in &mut original_tx.input {
1195+
input.sequence = Sequence::ENABLE_RBF_NO_LOCKTIME;
1196+
input.script_sig = ScriptBuf::new();
1197+
input.witness = Witness::new();
1198+
}
1199+
1200+
let signed_result =
1201+
bitcoind.sign_raw_transaction_with_wallet(&original_tx).unwrap();
1202+
assert!(signed_result.complete, "Failed to sign RBF transaction");
1203+
1204+
let tx_bytes = Vec::<u8>::from_hex(&signed_result.hex).unwrap();
1205+
let tx = bitcoin::consensus::encode::deserialize::<Transaction>(&tx_bytes).unwrap();
1206+
1207+
tx
1208+
}};
1209+
}
1210+
1211+
for _attempt in 0..3 {
1212+
let tx = bump_fee!();
1213+
match bitcoind.send_raw_transaction(&tx) {
1214+
Ok(res) => {
1215+
let new_txid = res.0.parse().unwrap();
1216+
wait_for_tx(electrs, new_txid);
1217+
println!("New txid from the RBF: {}", new_txid);
1218+
return new_txid;
1219+
},
1220+
Err(_) => {
1221+
if original_tx.output[fee_output_index].value.to_sat() < pump_fee_amount {
1222+
panic!("Insufficient funds to increase fee");
1223+
}
1224+
},
1225+
}
1226+
}
1227+
1228+
panic!("Failed to pump fee after 3 attempts");
1229+
}
1230+
1231+
pub(crate) fn init_rbf<E: ElectrumApi>(
1232+
&self, electrs: &E, original_txid: Txid,
1233+
) -> (Transaction, HashSet<ScriptBuf>, HashSet<ScriptBuf>, usize) {
1234+
let original_tx: Transaction = electrs.transaction_get(&original_txid).unwrap();
1235+
1236+
let total_addresses_to_modify = &self.addrs_a.len() + &self.addrs_b.len();
1237+
if original_tx.output.len() <= total_addresses_to_modify {
1238+
panic!(
1239+
"Transaction must have more outputs ({}) than addresses to modify ({}) to allow fee pumping",
1240+
original_tx.output.len(),
1241+
total_addresses_to_modify
1242+
);
1243+
}
1244+
1245+
let scripts_a: HashSet<ScriptBuf> =
1246+
self.addrs_a.iter().map(|addr| addr.script_pubkey()).collect();
1247+
let scripts_b: HashSet<ScriptBuf> =
1248+
self.addrs_b.iter().map(|addr| addr.script_pubkey()).collect();
1249+
1250+
let mut fee_output_index: Option<usize> = None;
1251+
for (index, output) in original_tx.output.iter().enumerate() {
1252+
if !scripts_a.contains(&output.script_pubkey)
1253+
&& !scripts_b.contains(&output.script_pubkey)
1254+
{
1255+
fee_output_index = Some(index);
1256+
break;
1257+
}
1258+
}
1259+
1260+
let fee_output_index = fee_output_index.expect(
1261+
"No output available for fee pumping. Need at least one output not being modified.",
1262+
);
1263+
1264+
(original_tx, scripts_a, scripts_b, fee_output_index)
1265+
}
1266+
}
1267+
10771268
// A `KVStore` impl for testing purposes that wraps all our `KVStore`s and asserts their synchronicity.
10781269
pub(crate) struct TestSyncStore {
10791270
serializer: RwLock<()>,

tests/integration_tests_rust.rs

Lines changed: 90 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use common::{
1212
expect_payment_received_event, expect_payment_successful_event, generate_blocks_and_wait,
1313
logging::{init_log_logger, validate_log_entry, TestLogWriter},
1414
open_channel, premine_and_distribute_funds, random_config, random_listening_addresses,
15-
setup_bitcoind_and_electrsd, setup_builder, setup_node, setup_two_nodes, wait_for_tx,
15+
setup_bitcoind_and_electrsd, setup_builder, setup_node, setup_two_nodes, wait_for_tx, SetupRBF,
1616
TestChainSource, TestSyncStore,
1717
};
1818

@@ -1389,3 +1389,92 @@ fn facade_logging() {
13891389
validate_log_entry(entry);
13901390
}
13911391
}
1392+
1393+
#[test]
1394+
fn test_rbf_value() {
1395+
let (bitcoind, electrsd) = setup_bitcoind_and_electrsd();
1396+
let setup = SetupRBF::new(&bitcoind, &electrsd);
1397+
let amount = 2_125_000;
1398+
let value = 21_000;
1399+
1400+
let (bitcoind, electrs) = (&bitcoind.client, &electrsd.client);
1401+
let txid = setup.setup_initial_funding(bitcoind, electrs, amount);
1402+
1403+
setup.sync_wallets();
1404+
setup.validate_balances(&setup.nodes_a, amount, false);
1405+
setup.validate_balances(&setup.nodes_b, amount, false);
1406+
1407+
let (mut original_tx, add_scripts, subtract_scripts, fee_output_index) =
1408+
setup.init_rbf(electrs, txid);
1409+
original_tx.output.iter_mut().for_each(|output| {
1410+
if add_scripts.contains(&output.script_pubkey) {
1411+
output.value = Amount::from_sat(output.value.to_sat() + value);
1412+
} else if subtract_scripts.contains(&output.script_pubkey) {
1413+
output.value = Amount::from_sat(output.value.to_sat().saturating_sub(value));
1414+
}
1415+
});
1416+
1417+
setup.pump_fee_with_rbf_replacement(bitcoind, electrs, &mut original_tx, fee_output_index);
1418+
1419+
setup.sync_wallets();
1420+
setup.validate_balances(&setup.nodes_a, amount + value, false);
1421+
setup.validate_balances(&setup.nodes_b, amount - value, false);
1422+
1423+
generate_blocks_and_wait(bitcoind, electrs, 1);
1424+
1425+
setup.sync_wallets();
1426+
setup.validate_balances(&setup.nodes_a, amount + value, true);
1427+
setup.validate_balances(&setup.nodes_b, amount - value, true);
1428+
1429+
let funding = 1_500_00;
1430+
let should_announce = true;
1431+
for index_node in 0..setup.nodes_a.len() {
1432+
let node_add = &setup.nodes_a[index_node];
1433+
let node_subtract = &setup.nodes_b[index_node];
1434+
open_channel(node_add, node_subtract, funding, should_announce, &electrsd);
1435+
open_channel(node_subtract, node_add, funding, should_announce, &electrsd);
1436+
}
1437+
}
1438+
1439+
#[test]
1440+
fn test_rbf_output() {
1441+
let (bitcoind, electrsd) = setup_bitcoind_and_electrsd();
1442+
let setup = SetupRBF::new(&bitcoind, &electrsd);
1443+
let amount = 2_125_000;
1444+
1445+
let (bitcoind, electrs) = (&bitcoind.client, &electrsd.client);
1446+
let txid = setup.setup_initial_funding(bitcoind, electrs, amount);
1447+
println!("\n Transacao que deve ir embora: {}\n", txid);
1448+
1449+
setup.sync_wallets();
1450+
setup.validate_balances(&setup.nodes_a, amount, false);
1451+
setup.validate_balances(&setup.nodes_b, amount, false);
1452+
1453+
let (mut original_tx, _, modify_scripts, fee_output_index) = setup.init_rbf(electrs, txid);
1454+
original_tx.output.iter_mut().for_each(|output| {
1455+
if modify_scripts.contains(&output.script_pubkey) {
1456+
let new_addr = bitcoind.new_address().unwrap();
1457+
output.script_pubkey = new_addr.script_pubkey();
1458+
}
1459+
});
1460+
1461+
setup.pump_fee_with_rbf_replacement(bitcoind, electrs, &mut original_tx, fee_output_index);
1462+
1463+
setup.sync_wallets();
1464+
setup.validate_balances(&setup.nodes_a, amount, false);
1465+
setup.validate_balances(&setup.nodes_b, 0, false);
1466+
1467+
generate_blocks_and_wait(bitcoind, electrs, 1);
1468+
1469+
setup.sync_wallets();
1470+
setup.validate_balances(&setup.nodes_a, amount, true);
1471+
setup.validate_balances(&setup.nodes_b, 0, true);
1472+
1473+
let funding = 1_500_00;
1474+
let should_announce = true;
1475+
for index_node in 0..setup.nodes_a.len() {
1476+
let node_static = &setup.nodes_a[index_node];
1477+
let node_modify = &setup.nodes_b[index_node];
1478+
open_channel(node_static, node_modify, funding, should_announce, &electrsd);
1479+
}
1480+
}

0 commit comments

Comments
 (0)