@@ -10,6 +10,7 @@ mod common;
10
10
use common:: {
11
11
do_channel_full_cycle, expect_channel_pending_event, expect_channel_ready_event, expect_event,
12
12
expect_payment_received_event, expect_payment_successful_event, generate_blocks_and_wait,
13
+ get_transaction,
13
14
logging:: { init_log_logger, validate_log_entry, TestLogWriter } ,
14
15
open_channel, premine_and_distribute_funds, random_config, random_listening_addresses,
15
16
setup_bitcoind_and_electrsd, setup_builder, setup_node, setup_two_nodes, wait_for_tx,
@@ -32,15 +33,18 @@ use lightning_invoice::{Bolt11InvoiceDescription, Description};
32
33
use lightning_types:: payment:: PaymentPreimage ;
33
34
34
35
use bitcoin:: address:: NetworkUnchecked ;
36
+ use bitcoin:: hashes:: hex:: FromHex ;
35
37
use bitcoin:: hashes:: sha256:: Hash as Sha256Hash ;
36
38
use bitcoin:: hashes:: Hash ;
37
- use bitcoin:: Address ;
38
- use bitcoin:: Amount ;
39
+ use bitcoin:: { Address , Amount , ScriptBuf , Sequence , Transaction , Witness } ;
39
40
use log:: LevelFilter ;
40
41
42
+ use std:: collections:: HashSet ;
41
43
use std:: str:: FromStr ;
42
44
use std:: sync:: Arc ;
43
45
46
+ use crate :: common:: { distribute_funds_unconfirmed, premine_blocks} ;
47
+
44
48
#[ test]
45
49
fn channel_full_cycle ( ) {
46
50
let ( bitcoind, electrsd) = setup_bitcoind_and_electrsd ( ) ;
@@ -669,6 +673,219 @@ fn onchain_wallet_recovery() {
669
673
) ;
670
674
}
671
675
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
+
672
889
#[ test]
673
890
fn sign_verify_msg ( ) {
674
891
let ( _bitcoind, electrsd) = setup_bitcoind_and_electrsd ( ) ;
0 commit comments