@@ -10,6 +10,7 @@ mod common;
1010use common:: {
1111 do_channel_full_cycle, expect_channel_pending_event, expect_channel_ready_event, expect_event,
1212 expect_payment_received_event, expect_payment_successful_event, generate_blocks_and_wait,
13+ get_transaction,
1314 logging:: { init_log_logger, validate_log_entry, TestLogWriter } ,
1415 open_channel, premine_and_distribute_funds, random_config, random_listening_addresses,
1516 setup_bitcoind_and_electrsd, setup_builder, setup_node, setup_two_nodes, wait_for_tx,
@@ -32,15 +33,18 @@ use lightning_invoice::{Bolt11InvoiceDescription, Description};
3233use lightning_types:: payment:: PaymentPreimage ;
3334
3435use bitcoin:: address:: NetworkUnchecked ;
36+ use bitcoin:: hashes:: hex:: FromHex ;
3537use bitcoin:: hashes:: sha256:: Hash as Sha256Hash ;
3638use bitcoin:: hashes:: Hash ;
37- use bitcoin:: Address ;
38- use bitcoin:: Amount ;
39+ use bitcoin:: { Address , Amount , ScriptBuf , Sequence , Transaction , Witness } ;
3940use log:: LevelFilter ;
4041
42+ use std:: collections:: HashSet ;
4143use std:: str:: FromStr ;
4244use std:: sync:: Arc ;
4345
46+ use crate :: common:: { distribute_funds_unconfirmed, premine_blocks} ;
47+
4448#[ test]
4549fn channel_full_cycle ( ) {
4650 let ( bitcoind, electrsd) = setup_bitcoind_and_electrsd ( ) ;
@@ -669,6 +673,219 @@ fn onchain_wallet_recovery() {
669673 ) ;
670674}
671675
676+ #[ test]
677+ fn test_rbf_via_mempool ( ) {
678+ run_rbf_test ( false ) ;
679+ }
680+
681+ #[ test]
682+ fn test_rbf_via_direct_block_insertion ( ) {
683+ run_rbf_test ( true ) ;
684+ }
685+
686+ // `is_insert_block`:
687+ // - `true`: transaction is mined immediately (no mempool), testing confirmed-Tx handling.
688+ // - `false`: transaction stays in mempool until confirmation, testing unconfirmed-Tx handling.
689+ fn run_rbf_test ( is_insert_block : bool ) {
690+ let ( bitcoind, electrsd) = setup_bitcoind_and_electrsd ( ) ;
691+ let chain_source_bitcoind = TestChainSource :: BitcoindRpcSync ( & bitcoind) ;
692+ let chain_source_electrsd = TestChainSource :: Electrum ( & electrsd) ;
693+ let chain_source_esplora = TestChainSource :: Esplora ( & electrsd) ;
694+
695+ macro_rules! config_node {
696+ ( $chain_source: expr, $anchor_channels: expr) => { {
697+ let config_a = random_config( $anchor_channels) ;
698+ let node = setup_node( & $chain_source, config_a, None ) ;
699+ node
700+ } } ;
701+ }
702+ let anchor_channels = false ;
703+ let nodes = vec ! [
704+ config_node!( chain_source_electrsd, anchor_channels) ,
705+ config_node!( chain_source_bitcoind, anchor_channels) ,
706+ config_node!( chain_source_esplora, anchor_channels) ,
707+ ] ;
708+
709+ let ( bitcoind, electrs) = ( & bitcoind. client , & electrsd. client ) ;
710+ premine_blocks ( bitcoind, electrs) ;
711+
712+ // Helpers declaration before starting the test
713+ let all_addrs =
714+ nodes. iter ( ) . map ( |node| node. onchain_payment ( ) . new_address ( ) . unwrap ( ) ) . collect :: < Vec < _ > > ( ) ;
715+ let amount_sat = 2_100_000 ;
716+ let mut txid;
717+ macro_rules! distribute_funds_all_nodes {
718+ ( ) => {
719+ txid = distribute_funds_unconfirmed(
720+ bitcoind,
721+ electrs,
722+ all_addrs. clone( ) ,
723+ Amount :: from_sat( amount_sat) ,
724+ ) ;
725+ } ;
726+ }
727+
728+ let mut tx;
729+ let scripts_buf: HashSet < ScriptBuf > =
730+ all_addrs. iter ( ) . map ( |addr| addr. script_pubkey ( ) ) . collect ( ) ;
731+ let mut fee_output_index;
732+ macro_rules! prepare_rbf {
733+ ( ) => {
734+ tx = get_transaction( electrs, txid) ;
735+
736+ let mut option_fee_output_index = None ;
737+ for ( index, output) in tx. output. iter( ) . enumerate( ) {
738+ if !scripts_buf. contains( & output. script_pubkey) {
739+ option_fee_output_index = Some ( index) ;
740+ break ;
741+ }
742+ }
743+ fee_output_index = option_fee_output_index. expect(
744+ "No output available for fee pumping. Need at least one output not being modified." ,
745+ ) ;
746+ } ;
747+ }
748+
749+ let mut bump_fee_amount_sat;
750+ macro_rules! bump_fee_rbf_and_public_transaction {
751+ ( ) => {
752+ bump_fee_amount_sat = tx. vsize( ) as u64 ;
753+ let attempts = 5 ;
754+ for _attempt in 0 ..attempts {
755+ bump_fee!( ) ;
756+ println!( "Bumping fee to {} sats" , bump_fee_amount_sat) ;
757+ println!( "Transaction ID: {}" , tx. compute_txid( ) ) ;
758+ match bitcoind. send_raw_transaction( & tx) {
759+ Ok ( res) => {
760+ // Mine a block immediately so the transaction is confirmed
761+ // before any node identifies it as a transaction that was in the mempool.
762+ if is_insert_block {
763+ generate_blocks_and_wait( bitcoind, electrs, 1 ) ;
764+ }
765+ let new_txid = res. 0 . parse( ) . unwrap( ) ;
766+ wait_for_tx( electrs, new_txid) ;
767+ break ;
768+ } ,
769+ Err ( _) => {
770+ if _attempt == attempts - 1 {
771+ panic!( "Failed to pump fee after {} attempts" , attempts) ;
772+ }
773+
774+ bump_fee_amount_sat += bump_fee_amount_sat * 5 ;
775+ if tx. output[ fee_output_index] . value. to_sat( ) < bump_fee_amount_sat {
776+ panic!( "Insufficient funds to increase fee" ) ;
777+ }
778+ } ,
779+ }
780+ }
781+ } ;
782+ }
783+
784+ macro_rules! bump_fee {
785+ ( ) => {
786+ let fee_output = & mut tx. output[ fee_output_index] ;
787+ let new_fee_value = fee_output. value. to_sat( ) . saturating_sub( bump_fee_amount_sat) ;
788+ fee_output. value = Amount :: from_sat( new_fee_value) ;
789+ println!( "New fee value: {} sats" , new_fee_value) ;
790+
791+ // dust limit
792+ if new_fee_value < 546 {
793+ panic!( "Warning: Fee output approaching dust limit ({} sats)" , new_fee_value) ;
794+ }
795+
796+ for input in & mut tx. input {
797+ input. sequence = Sequence :: ENABLE_RBF_NO_LOCKTIME ;
798+ input. script_sig = ScriptBuf :: new( ) ;
799+ input. witness = Witness :: new( ) ;
800+ }
801+
802+ let signed_result = bitcoind. sign_raw_transaction_with_wallet( & tx) . unwrap( ) ;
803+ assert!( signed_result. complete, "Failed to sign RBF transaction" ) ;
804+
805+ let tx_bytes = Vec :: <u8 >:: from_hex( & signed_result. hex) . unwrap( ) ;
806+ tx = bitcoin:: consensus:: encode:: deserialize:: <Transaction >( & tx_bytes) . unwrap( ) ;
807+ } ;
808+ }
809+
810+ macro_rules! validate_balances {
811+ ( $expected_balance_sat: expr, $is_spendable: expr) => {
812+ let spend_balance = if $is_spendable { $expected_balance_sat } else { 0 } ;
813+ for node in & nodes {
814+ node. sync_wallets( ) . unwrap( ) ;
815+ let balances = node. list_balances( ) ;
816+ assert_eq!( balances. spendable_onchain_balance_sats, spend_balance) ;
817+ assert_eq!( balances. total_onchain_balance_sats, $expected_balance_sat) ;
818+ }
819+ } ;
820+ }
821+
822+ // Modify the output to the nodes
823+ distribute_funds_all_nodes ! ( ) ;
824+ validate_balances ! ( amount_sat, false ) ;
825+ prepare_rbf ! ( ) ;
826+ tx. output . iter_mut ( ) . for_each ( |output| {
827+ if scripts_buf. contains ( & output. script_pubkey ) {
828+ let new_addr = bitcoind. new_address ( ) . unwrap ( ) ;
829+ output. script_pubkey = new_addr. script_pubkey ( ) ;
830+ }
831+ } ) ;
832+ bump_fee_rbf_and_public_transaction ! ( ) ;
833+ validate_balances ! ( 0 , is_insert_block) ;
834+
835+ // Not modifying the output scripts, but still bumping the fee.
836+ distribute_funds_all_nodes ! ( ) ;
837+ validate_balances ! ( amount_sat, false ) ;
838+ prepare_rbf ! ( ) ;
839+ bump_fee_rbf_and_public_transaction ! ( ) ;
840+ validate_balances ! ( amount_sat, is_insert_block) ;
841+
842+ let mut final_amount_sat = amount_sat * 2 ;
843+ let value_sat = 21_000 ;
844+
845+ // Increase the value of the nodes' outputs
846+ distribute_funds_all_nodes ! ( ) ;
847+ prepare_rbf ! ( ) ;
848+ tx. output . iter_mut ( ) . for_each ( |output| {
849+ if scripts_buf. contains ( & output. script_pubkey ) {
850+ output. value = Amount :: from_sat ( output. value . to_sat ( ) + value_sat) ;
851+ }
852+ } ) ;
853+ bump_fee_rbf_and_public_transaction ! ( ) ;
854+ final_amount_sat += value_sat;
855+ validate_balances ! ( final_amount_sat, is_insert_block) ;
856+
857+ // Decreases the value of the nodes' outputs
858+ distribute_funds_all_nodes ! ( ) ;
859+ final_amount_sat += amount_sat;
860+ prepare_rbf ! ( ) ;
861+ tx. output . iter_mut ( ) . for_each ( |output| {
862+ if scripts_buf. contains ( & output. script_pubkey ) {
863+ output. value = Amount :: from_sat ( output. value . to_sat ( ) - value_sat) ;
864+ }
865+ } ) ;
866+ bump_fee_rbf_and_public_transaction ! ( ) ;
867+ final_amount_sat -= value_sat;
868+ validate_balances ! ( final_amount_sat, is_insert_block) ;
869+
870+ if !is_insert_block {
871+ generate_blocks_and_wait ( bitcoind, electrs, 1 ) ;
872+ validate_balances ! ( final_amount_sat, true ) ;
873+ }
874+
875+ // Check if it is possible to send all funds from the node
876+ let mut txids = Vec :: new ( ) ;
877+ let addr = bitcoind. new_address ( ) . unwrap ( ) ;
878+ nodes. iter ( ) . for_each ( |node| {
879+ let txid = node. onchain_payment ( ) . send_all_to_address ( & addr, true , None ) . unwrap ( ) ;
880+ txids. push ( txid) ;
881+ } ) ;
882+ txids. iter ( ) . for_each ( |txid| {
883+ wait_for_tx ( electrs, * txid) ;
884+ } ) ;
885+ generate_blocks_and_wait ( bitcoind, electrs, 6 ) ;
886+ validate_balances ! ( 0 , true ) ;
887+ }
888+
672889#[ test]
673890fn sign_verify_msg ( ) {
674891 let ( _bitcoind, electrsd) = setup_bitcoind_and_electrsd ( ) ;
0 commit comments