@@ -5030,3 +5030,216 @@ fn test_splice_rbf_recontributes_feerate_too_high() {
50305030 other => panic ! ( "Expected HandleError/DisconnectPeerWithWarning, got {:?}" , other) ,
50315031 }
50325032}
5033+
5034+ #[ test]
5035+ fn test_splice_rbf_sequential ( ) {
5036+ // Three consecutive RBF rounds on the same splice (initial → RBF #1 → RBF #2).
5037+ // Node 0 is the quiescence initiator; node 1 is the acceptor with no contribution.
5038+ // Verifies:
5039+ // - Each round satisfies the 25/24 feerate rule
5040+ // - DiscardFunding events reference the correct txids from previous rounds
5041+ // - The final RBF can be mined and splice_locked successfully
5042+ let chanmon_cfgs = create_chanmon_cfgs ( 2 ) ;
5043+ let node_cfgs = create_node_cfgs ( 2 , & chanmon_cfgs) ;
5044+ let node_chanmgrs = create_node_chanmgrs ( 2 , & node_cfgs, & [ None , None ] ) ;
5045+ let nodes = create_network ( 2 , & node_cfgs, & node_chanmgrs) ;
5046+
5047+ let node_id_0 = nodes[ 0 ] . node . get_our_node_id ( ) ;
5048+ let node_id_1 = nodes[ 1 ] . node . get_our_node_id ( ) ;
5049+
5050+ let initial_channel_value_sat = 100_000 ;
5051+ let ( _, _, channel_id, _) =
5052+ create_announced_chan_between_nodes_with_value ( & nodes, 0 , 1 , initial_channel_value_sat, 0 ) ;
5053+
5054+ let added_value = Amount :: from_sat ( 50_000 ) ;
5055+ provide_utxo_reserves ( & nodes, 2 , added_value * 2 ) ;
5056+
5057+ // Save the pre-splice funding outpoint.
5058+ let original_funding_outpoint = nodes[ 0 ]
5059+ . chain_monitor
5060+ . chain_monitor
5061+ . get_monitor ( channel_id)
5062+ . map ( |monitor| ( monitor. get_funding_txo ( ) , monitor. get_funding_script ( ) ) )
5063+ . unwrap ( ) ;
5064+
5065+ // --- Round 0: Initial splice-in from node 0 at floor feerate (253). ---
5066+ let funding_contribution = do_initiate_splice_in ( & nodes[ 0 ] , & nodes[ 1 ] , channel_id, added_value) ;
5067+ let ( splice_tx_0, new_funding_script) =
5068+ splice_channel ( & nodes[ 0 ] , & nodes[ 1 ] , channel_id, funding_contribution) ;
5069+
5070+ // Feerate progression: 253 → ceil(253*25/24) = 264 → ceil(264*25/24) = 275
5071+ let feerate_1_sat_per_kwu = ( FEERATE_FLOOR_SATS_PER_KW as u64 * 25 + 23 ) / 24 ; // 264
5072+ let feerate_2_sat_per_kwu = ( feerate_1_sat_per_kwu * 25 + 23 ) / 24 ; // 275
5073+
5074+ // --- Round 1: RBF #1 at feerate 264. ---
5075+ provide_utxo_reserves ( & nodes, 2 , added_value * 2 ) ;
5076+
5077+ let rbf_feerate_1 = FeeRate :: from_sat_per_kwu ( feerate_1_sat_per_kwu) ;
5078+ let funding_template =
5079+ nodes[ 0 ] . node . rbf_channel ( & channel_id, & node_id_1, rbf_feerate_1) . unwrap ( ) ;
5080+ let wallet = WalletSync :: new ( Arc :: clone ( & nodes[ 0 ] . wallet_source ) , nodes[ 0 ] . logger ) ;
5081+ let funding_contribution_1 = funding_template. splice_in_sync ( added_value, & wallet) . unwrap ( ) ;
5082+ nodes[ 0 ]
5083+ . node
5084+ . funding_contributed ( & channel_id, & node_id_1, funding_contribution_1. clone ( ) , None )
5085+ . unwrap ( ) ;
5086+
5087+ let stfu_a = get_event_msg ! ( nodes[ 0 ] , MessageSendEvent :: SendStfu , node_id_1) ;
5088+ nodes[ 1 ] . node . handle_stfu ( node_id_0, & stfu_a) ;
5089+ let stfu_b = get_event_msg ! ( nodes[ 1 ] , MessageSendEvent :: SendStfu , node_id_0) ;
5090+ nodes[ 0 ] . node . handle_stfu ( node_id_1, & stfu_b) ;
5091+
5092+ let tx_init_rbf = get_event_msg ! ( nodes[ 0 ] , MessageSendEvent :: SendTxInitRbf , node_id_1) ;
5093+ assert_eq ! ( tx_init_rbf. feerate_sat_per_1000_weight, feerate_1_sat_per_kwu as u32 ) ;
5094+ nodes[ 1 ] . node . handle_tx_init_rbf ( node_id_0, & tx_init_rbf) ;
5095+ let tx_ack_rbf = get_event_msg ! ( nodes[ 1 ] , MessageSendEvent :: SendTxAckRbf , node_id_0) ;
5096+ nodes[ 0 ] . node . handle_tx_ack_rbf ( node_id_1, & tx_ack_rbf) ;
5097+
5098+ complete_interactive_funding_negotiation (
5099+ & nodes[ 0 ] ,
5100+ & nodes[ 1 ] ,
5101+ channel_id,
5102+ funding_contribution_1,
5103+ new_funding_script. clone ( ) ,
5104+ ) ;
5105+ let ( splice_tx_1, splice_locked) = sign_interactive_funding_tx ( & nodes[ 0 ] , & nodes[ 1 ] , false ) ;
5106+ assert ! ( splice_locked. is_none( ) ) ;
5107+ expect_splice_pending_event ( & nodes[ 0 ] , & node_id_1) ;
5108+ expect_splice_pending_event ( & nodes[ 1 ] , & node_id_0) ;
5109+
5110+ // --- Round 2: RBF #2 at feerate 275. ---
5111+ provide_utxo_reserves ( & nodes, 2 , added_value * 2 ) ;
5112+
5113+ let rbf_feerate_2 = FeeRate :: from_sat_per_kwu ( feerate_2_sat_per_kwu) ;
5114+ let funding_template =
5115+ nodes[ 0 ] . node . rbf_channel ( & channel_id, & node_id_1, rbf_feerate_2) . unwrap ( ) ;
5116+ let wallet = WalletSync :: new ( Arc :: clone ( & nodes[ 0 ] . wallet_source ) , nodes[ 0 ] . logger ) ;
5117+ let funding_contribution_2 = funding_template. splice_in_sync ( added_value, & wallet) . unwrap ( ) ;
5118+ nodes[ 0 ]
5119+ . node
5120+ . funding_contributed ( & channel_id, & node_id_1, funding_contribution_2. clone ( ) , None )
5121+ . unwrap ( ) ;
5122+
5123+ let stfu_a = get_event_msg ! ( nodes[ 0 ] , MessageSendEvent :: SendStfu , node_id_1) ;
5124+ nodes[ 1 ] . node . handle_stfu ( node_id_0, & stfu_a) ;
5125+ let stfu_b = get_event_msg ! ( nodes[ 1 ] , MessageSendEvent :: SendStfu , node_id_0) ;
5126+ nodes[ 0 ] . node . handle_stfu ( node_id_1, & stfu_b) ;
5127+
5128+ let tx_init_rbf = get_event_msg ! ( nodes[ 0 ] , MessageSendEvent :: SendTxInitRbf , node_id_1) ;
5129+ assert_eq ! ( tx_init_rbf. feerate_sat_per_1000_weight, feerate_2_sat_per_kwu as u32 ) ;
5130+ nodes[ 1 ] . node . handle_tx_init_rbf ( node_id_0, & tx_init_rbf) ;
5131+ let tx_ack_rbf = get_event_msg ! ( nodes[ 1 ] , MessageSendEvent :: SendTxAckRbf , node_id_0) ;
5132+ nodes[ 0 ] . node . handle_tx_ack_rbf ( node_id_1, & tx_ack_rbf) ;
5133+
5134+ complete_interactive_funding_negotiation (
5135+ & nodes[ 0 ] ,
5136+ & nodes[ 1 ] ,
5137+ channel_id,
5138+ funding_contribution_2,
5139+ new_funding_script. clone ( ) ,
5140+ ) ;
5141+ let ( rbf_tx_final, splice_locked) = sign_interactive_funding_tx ( & nodes[ 0 ] , & nodes[ 1 ] , false ) ;
5142+ assert ! ( splice_locked. is_none( ) ) ;
5143+ expect_splice_pending_event ( & nodes[ 0 ] , & node_id_1) ;
5144+ expect_splice_pending_event ( & nodes[ 1 ] , & node_id_0) ;
5145+
5146+ // --- Mine and lock the final RBF. ---
5147+ mine_transaction ( & nodes[ 0 ] , & rbf_tx_final) ;
5148+ mine_transaction ( & nodes[ 1 ] , & rbf_tx_final) ;
5149+
5150+ connect_blocks ( & nodes[ 0 ] , ANTI_REORG_DELAY - 1 ) ;
5151+ connect_blocks ( & nodes[ 1 ] , ANTI_REORG_DELAY - 1 ) ;
5152+
5153+ let splice_locked_b = get_event_msg ! ( nodes[ 0 ] , MessageSendEvent :: SendSpliceLocked , node_id_1) ;
5154+ nodes[ 1 ] . node . handle_splice_locked ( node_id_0, & splice_locked_b) ;
5155+
5156+ let mut msg_events = nodes[ 1 ] . node . get_and_clear_pending_msg_events ( ) ;
5157+ assert_eq ! ( msg_events. len( ) , 2 , "{msg_events:?}" ) ;
5158+ let splice_locked_a =
5159+ if let MessageSendEvent :: SendSpliceLocked { msg, .. } = msg_events. remove ( 0 ) {
5160+ msg
5161+ } else {
5162+ panic ! ( "Expected SendSpliceLocked, got {:?}" , msg_events[ 0 ] ) ;
5163+ } ;
5164+ let announcement_sigs_b =
5165+ if let MessageSendEvent :: SendAnnouncementSignatures { msg, .. } = msg_events. remove ( 0 ) {
5166+ msg
5167+ } else {
5168+ panic ! ( "Expected SendAnnouncementSignatures" ) ;
5169+ } ;
5170+ nodes[ 0 ] . node . handle_splice_locked ( node_id_1, & splice_locked_a) ;
5171+ nodes[ 0 ] . node . handle_announcement_signatures ( node_id_1, & announcement_sigs_b) ;
5172+
5173+ // --- Verify DiscardFunding events for both replaced candidates. ---
5174+ let splice_tx_0_txid = splice_tx_0. compute_txid ( ) ;
5175+ let splice_tx_1_txid = splice_tx_1. compute_txid ( ) ;
5176+
5177+ // Node 0 (initiator): ChannelReady + 2 DiscardFunding.
5178+ let events_a = nodes[ 0 ] . node . get_and_clear_pending_events ( ) ;
5179+ assert_eq ! ( events_a. len( ) , 3 , "{events_a:?}" ) ;
5180+ assert ! ( matches!( events_a[ 0 ] , Event :: ChannelReady { .. } ) ) ;
5181+ let discard_txids_a: Vec < _ > = events_a[ 1 ..]
5182+ . iter ( )
5183+ . map ( |e| match e {
5184+ Event :: DiscardFunding { funding_info : FundingInfo :: Tx { transaction } , .. } => {
5185+ transaction. compute_txid ( )
5186+ } ,
5187+ Event :: DiscardFunding { funding_info : FundingInfo :: OutPoint { outpoint } , .. } => {
5188+ outpoint. txid
5189+ } ,
5190+ other => panic ! ( "Expected DiscardFunding, got {:?}" , other) ,
5191+ } )
5192+ . collect ( ) ;
5193+ assert ! ( discard_txids_a. contains( & splice_tx_0_txid) , "Missing discard for initial splice" ) ;
5194+ assert ! ( discard_txids_a. contains( & splice_tx_1_txid) , "Missing discard for RBF #1" ) ;
5195+ check_added_monitors ( & nodes[ 0 ] , 1 ) ;
5196+
5197+ // Node 1 (acceptor): ChannelReady + 2 DiscardFunding.
5198+ let events_b = nodes[ 1 ] . node . get_and_clear_pending_events ( ) ;
5199+ assert_eq ! ( events_b. len( ) , 3 , "{events_b:?}" ) ;
5200+ assert ! ( matches!( events_b[ 0 ] , Event :: ChannelReady { .. } ) ) ;
5201+ let discard_txids_b: Vec < _ > = events_b[ 1 ..]
5202+ . iter ( )
5203+ . map ( |e| match e {
5204+ Event :: DiscardFunding { funding_info : FundingInfo :: Tx { transaction } , .. } => {
5205+ transaction. compute_txid ( )
5206+ } ,
5207+ Event :: DiscardFunding { funding_info : FundingInfo :: OutPoint { outpoint } , .. } => {
5208+ outpoint. txid
5209+ } ,
5210+ other => panic ! ( "Expected DiscardFunding, got {:?}" , other) ,
5211+ } )
5212+ . collect ( ) ;
5213+ assert ! ( discard_txids_b. contains( & splice_tx_0_txid) , "Missing discard for initial splice" ) ;
5214+ assert ! ( discard_txids_b. contains( & splice_tx_1_txid) , "Missing discard for RBF #1" ) ;
5215+ check_added_monitors ( & nodes[ 1 ] , 1 ) ;
5216+
5217+ // Complete the announcement exchange.
5218+ let mut msg_events = nodes[ 0 ] . node . get_and_clear_pending_msg_events ( ) ;
5219+ assert_eq ! ( msg_events. len( ) , 2 , "{msg_events:?}" ) ;
5220+ if let MessageSendEvent :: SendAnnouncementSignatures { msg, .. } = msg_events. remove ( 0 ) {
5221+ nodes[ 1 ] . node . handle_announcement_signatures ( node_id_0, & msg) ;
5222+ } else {
5223+ panic ! ( "Expected SendAnnouncementSignatures" ) ;
5224+ }
5225+ assert ! ( matches!( msg_events. remove( 0 ) , MessageSendEvent :: BroadcastChannelAnnouncement { .. } ) ) ;
5226+
5227+ let mut msg_events = nodes[ 1 ] . node . get_and_clear_pending_msg_events ( ) ;
5228+ assert_eq ! ( msg_events. len( ) , 1 , "{msg_events:?}" ) ;
5229+ assert ! ( matches!( msg_events. remove( 0 ) , MessageSendEvent :: BroadcastChannelAnnouncement { .. } ) ) ;
5230+
5231+ // Clean up old watched outpoints.
5232+ let ( orig_outpoint, orig_script) = original_funding_outpoint;
5233+ let splice_funding_idx = |tx : & Transaction | {
5234+ tx. output . iter ( ) . position ( |o| o. script_pubkey == new_funding_script) . unwrap ( )
5235+ } ;
5236+ let outpoint_0 =
5237+ OutPoint { txid : splice_tx_0_txid, index : splice_funding_idx ( & splice_tx_0) as u16 } ;
5238+ let outpoint_1 =
5239+ OutPoint { txid : splice_tx_1_txid, index : splice_funding_idx ( & splice_tx_1) as u16 } ;
5240+ for node in & nodes {
5241+ node. chain_source . remove_watched_txn_and_outputs ( orig_outpoint, orig_script. clone ( ) ) ;
5242+ node. chain_source . remove_watched_txn_and_outputs ( outpoint_0, new_funding_script. clone ( ) ) ;
5243+ node. chain_source . remove_watched_txn_and_outputs ( outpoint_1, new_funding_script. clone ( ) ) ;
5244+ }
5245+ }
0 commit comments