@@ -6304,3 +6304,153 @@ fn test_splice_revalidation_at_quiescence() {
63046304
63056305 expect_splice_failed_events ( & nodes[ 0 ] , & channel_id, contribution) ;
63066306}
6307+
6308+ #[ test]
6309+ fn test_splice_rbf_rejects_low_feerate_after_several_attempts ( ) {
6310+ // After several RBF attempts, the counterparty's RBF feerate must be high enough to
6311+ // confirm (per the fee estimator). Early attempts at low feerates are accepted, but
6312+ // once the threshold is crossed and the fee estimator expects a higher feerate, the
6313+ // attempt is rejected.
6314+ let chanmon_cfgs = create_chanmon_cfgs ( 2 ) ;
6315+ let node_cfgs = create_node_cfgs ( 2 , & chanmon_cfgs) ;
6316+ let node_chanmgrs = create_node_chanmgrs ( 2 , & node_cfgs, & [ None , None ] ) ;
6317+ let nodes = create_network ( 2 , & node_cfgs, & node_chanmgrs) ;
6318+
6319+ let node_id_0 = nodes[ 0 ] . node . get_our_node_id ( ) ;
6320+ let node_id_1 = nodes[ 1 ] . node . get_our_node_id ( ) ;
6321+
6322+ let initial_channel_value_sat = 100_000 ;
6323+ let ( _, _, channel_id, _) =
6324+ create_announced_chan_between_nodes_with_value ( & nodes, 0 , 1 , initial_channel_value_sat, 0 ) ;
6325+
6326+ let added_value = Amount :: from_sat ( 50_000 ) ;
6327+ provide_utxo_reserves ( & nodes, 2 , added_value * 2 ) ;
6328+
6329+ // Round 0: Initial splice-in at floor feerate (253).
6330+ let funding_contribution = do_initiate_splice_in ( & nodes[ 0 ] , & nodes[ 1 ] , channel_id, added_value) ;
6331+ let ( _, new_funding_script) =
6332+ splice_channel ( & nodes[ 0 ] , & nodes[ 1 ] , channel_id, funding_contribution) ;
6333+
6334+ // Feerate progression: 253 → 264 → 275 → 287 → 300
6335+ let feerate_1 = ( FEERATE_FLOOR_SATS_PER_KW as u64 * 25 ) . div_ceil ( 24 ) ;
6336+ let feerate_2 = ( feerate_1 * 25 ) . div_ceil ( 24 ) ;
6337+ let feerate_3 = ( feerate_2 * 25 ) . div_ceil ( 24 ) ;
6338+ let feerate_4 = ( feerate_3 * 25 ) . div_ceil ( 24 ) ;
6339+
6340+ // Rounds 1-3: RBF at minimum bump. Accepted (at or below threshold).
6341+ for feerate in [ feerate_1, feerate_2, feerate_3] {
6342+ provide_utxo_reserves ( & nodes, 2 , added_value * 2 ) ;
6343+ let rbf_feerate = FeeRate :: from_sat_per_kwu ( feerate) ;
6344+ let contribution =
6345+ do_initiate_rbf_splice_in ( & nodes[ 0 ] , & nodes[ 1 ] , channel_id, added_value, rbf_feerate) ;
6346+ complete_rbf_handshake ( & nodes[ 0 ] , & nodes[ 1 ] ) ;
6347+ complete_interactive_funding_negotiation (
6348+ & nodes[ 0 ] ,
6349+ & nodes[ 1 ] ,
6350+ channel_id,
6351+ contribution,
6352+ new_funding_script. clone ( ) ,
6353+ ) ;
6354+ let ( _, splice_locked) = sign_interactive_funding_tx ( & nodes[ 0 ] , & nodes[ 1 ] , false ) ;
6355+ assert ! ( splice_locked. is_none( ) ) ;
6356+ expect_splice_pending_event ( & nodes[ 0 ] , & node_id_1) ;
6357+ expect_splice_pending_event ( & nodes[ 1 ] , & node_id_0) ;
6358+ }
6359+
6360+ // Now 4 negotiated candidates (round 0 + rounds 1-3). Bump the fee estimator on node 1
6361+ // (the RBF receiver) so the next minimum RBF feerate (300) is below it.
6362+ let high_feerate = 1000 ;
6363+ * chanmon_cfgs[ 1 ] . fee_estimator . sat_per_kw . lock ( ) . unwrap ( ) = high_feerate;
6364+
6365+ // Round 4: RBF at minimum bump (300). Should be rejected because 300 < 1000.
6366+ provide_utxo_reserves ( & nodes, 2 , added_value * 2 ) ;
6367+ let rbf_feerate_3 = FeeRate :: from_sat_per_kwu ( feerate_4) ;
6368+ let _contribution =
6369+ do_initiate_rbf_splice_in ( & nodes[ 0 ] , & nodes[ 1 ] , channel_id, added_value, rbf_feerate_3) ;
6370+ let stfu_0 = get_event_msg ! ( nodes[ 0 ] , MessageSendEvent :: SendStfu , node_id_1) ;
6371+ nodes[ 1 ] . node . handle_stfu ( node_id_0, & stfu_0) ;
6372+ let stfu_1 = get_event_msg ! ( nodes[ 1 ] , MessageSendEvent :: SendStfu , node_id_0) ;
6373+ nodes[ 0 ] . node . handle_stfu ( node_id_1, & stfu_1) ;
6374+
6375+ // Node 0 sends tx_init_rbf. Node 1 rejects the low feerate after the threshold.
6376+ let tx_init_rbf = get_event_msg ! ( nodes[ 0 ] , MessageSendEvent :: SendTxInitRbf , node_id_1) ;
6377+ nodes[ 1 ] . node . handle_tx_init_rbf ( node_id_0, & tx_init_rbf) ;
6378+ get_event_msg ! ( nodes[ 1 ] , MessageSendEvent :: SendTxAbort , node_id_0) ;
6379+ }
6380+
6381+ #[ test]
6382+ fn test_splice_rbf_rejects_own_low_feerate_after_several_attempts ( ) {
6383+ // Same as test_splice_rbf_rejects_low_feerate_after_several_attempts, but for our own
6384+ // initiated RBF. The spec requires: "MUST set a high enough feerate to ensure quick
6385+ // confirmation." After several attempts, funding_contributed should reject our contribution
6386+ // if the feerate is below the fee estimator's target.
6387+ let chanmon_cfgs = create_chanmon_cfgs ( 2 ) ;
6388+ let node_cfgs = create_node_cfgs ( 2 , & chanmon_cfgs) ;
6389+ let node_chanmgrs = create_node_chanmgrs ( 2 , & node_cfgs, & [ None , None ] ) ;
6390+ let nodes = create_network ( 2 , & node_cfgs, & node_chanmgrs) ;
6391+
6392+ let node_id_0 = nodes[ 0 ] . node . get_our_node_id ( ) ;
6393+ let node_id_1 = nodes[ 1 ] . node . get_our_node_id ( ) ;
6394+
6395+ let initial_channel_value_sat = 100_000 ;
6396+ let ( _, _, channel_id, _) =
6397+ create_announced_chan_between_nodes_with_value ( & nodes, 0 , 1 , initial_channel_value_sat, 0 ) ;
6398+
6399+ let added_value = Amount :: from_sat ( 50_000 ) ;
6400+ provide_utxo_reserves ( & nodes, 2 , added_value * 2 ) ;
6401+
6402+ // Round 0: Initial splice-in at floor feerate (253).
6403+ let funding_contribution = do_initiate_splice_in ( & nodes[ 0 ] , & nodes[ 1 ] , channel_id, added_value) ;
6404+ let ( _, new_funding_script) =
6405+ splice_channel ( & nodes[ 0 ] , & nodes[ 1 ] , channel_id, funding_contribution) ;
6406+
6407+ // Feerate progression: 253 → 264 → 275 → 287 → 300
6408+ let feerate_1 = ( FEERATE_FLOOR_SATS_PER_KW as u64 * 25 ) . div_ceil ( 24 ) ;
6409+ let feerate_2 = ( feerate_1 * 25 ) . div_ceil ( 24 ) ;
6410+ let feerate_3 = ( feerate_2 * 25 ) . div_ceil ( 24 ) ;
6411+ let feerate_4 = ( feerate_3 * 25 ) . div_ceil ( 24 ) ;
6412+
6413+ // Rounds 1-3: RBF at minimum bump. Accepted.
6414+ for feerate in [ feerate_1, feerate_2, feerate_3] {
6415+ provide_utxo_reserves ( & nodes, 2 , added_value * 2 ) ;
6416+ let rbf_feerate = FeeRate :: from_sat_per_kwu ( feerate) ;
6417+ let contribution =
6418+ do_initiate_rbf_splice_in ( & nodes[ 0 ] , & nodes[ 1 ] , channel_id, added_value, rbf_feerate) ;
6419+ complete_rbf_handshake ( & nodes[ 0 ] , & nodes[ 1 ] ) ;
6420+ complete_interactive_funding_negotiation (
6421+ & nodes[ 0 ] ,
6422+ & nodes[ 1 ] ,
6423+ channel_id,
6424+ contribution,
6425+ new_funding_script. clone ( ) ,
6426+ ) ;
6427+ let ( _, splice_locked) = sign_interactive_funding_tx ( & nodes[ 0 ] , & nodes[ 1 ] , false ) ;
6428+ assert ! ( splice_locked. is_none( ) ) ;
6429+ expect_splice_pending_event ( & nodes[ 0 ] , & node_id_1) ;
6430+ expect_splice_pending_event ( & nodes[ 1 ] , & node_id_0) ;
6431+ }
6432+
6433+ // Bump node 0's fee estimator so the next minimum RBF feerate (300) is below it.
6434+ let high_feerate = 1000 ;
6435+ * chanmon_cfgs[ 0 ] . fee_estimator . sat_per_kw . lock ( ) . unwrap ( ) = high_feerate;
6436+
6437+ // Round 4: Our own RBF at minimum bump (300). funding_contributed should reject it.
6438+ provide_utxo_reserves ( & nodes, 2 , added_value * 2 ) ;
6439+ let rbf_feerate = FeeRate :: from_sat_per_kwu ( feerate_4) ;
6440+ let funding_template = nodes[ 0 ] . node . splice_channel ( & channel_id, & node_id_1) . unwrap ( ) ;
6441+ let wallet = WalletSync :: new ( Arc :: clone ( & nodes[ 0 ] . wallet_source ) , nodes[ 0 ] . logger ) ;
6442+ let contribution =
6443+ funding_template. splice_in_sync ( added_value, rbf_feerate, FeeRate :: MAX , & wallet) . unwrap ( ) ;
6444+
6445+ let result = nodes[ 0 ] . node . funding_contributed ( & channel_id, & node_id_1, contribution, None ) ;
6446+ assert ! ( result. is_err( ) , "Expected rejection for low feerate: {:?}" , result) ;
6447+
6448+ // SpliceFailed is emitted. DiscardFunding is not emitted because all inputs/outputs
6449+ // are filtered out (same UTXOs reused for RBF, still committed to the prior splice tx).
6450+ let events = nodes[ 0 ] . node . get_and_clear_pending_events ( ) ;
6451+ assert_eq ! ( events. len( ) , 1 , "{events:?}" ) ;
6452+ match & events[ 0 ] {
6453+ Event :: SpliceFailed { channel_id : cid, .. } => assert_eq ! ( * cid, channel_id) ,
6454+ other => panic ! ( "Expected SpliceFailed, got {:?}" , other) ,
6455+ }
6456+ }
0 commit comments