diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs
index 3cc6a6b0d86..79d012ba686 100644
--- a/lightning/src/ln/channel.rs
+++ b/lightning/src/ln/channel.rs
@@ -6484,6 +6484,19 @@ fn get_v2_channel_reserve_satoshis(channel_value_satoshis: u64, dust_limit_satos
cmp::min(channel_value_satoshis, cmp::max(q, dust_limit_satoshis))
}
+/// Returns the minimum feerate for our own RBF attempts given a previous feerate.
+///
+/// The spec (tx_init_rbf) requires the new feerate to be >= 25/24 of the previous feerate.
+/// However, at low feerates that multiplier doesn't always satisfy BIP125's relay requirement of
+/// an absolute fee increase, so we take the max of a flat +25 sat/kwu (0.1 sat/vB) increment
+/// and the spec's multiplicative rule. We still accept the bare 25/24 rule from counterparties
+/// in [`FundedChannel::validate_tx_init_rbf`].
+fn min_rbf_feerate(prev_feerate: u32) -> FeeRate {
+ let flat_increment = (prev_feerate as u64).saturating_add(25);
+ let spec_increment = ((prev_feerate as u64) * 25).div_ceil(24);
+ FeeRate::from_sat_per_kwu(cmp::max(flat_increment, spec_increment))
+}
+
/// Context for negotiating channels (dual-funded V2 open, splicing)
#[derive(Debug)]
pub(super) struct FundingNegotiationContext {
@@ -12019,10 +12032,7 @@ where
prev_feerate.is_some(),
"pending_splice should have last_funding_feerate or funding_negotiation",
);
- let min_rbf_feerate = prev_feerate.map(|f| {
- let min_feerate_kwu = ((f as u64) * 25).div_ceil(24);
- FeeRate::from_sat_per_kwu(min_feerate_kwu)
- });
+ let min_rbf_feerate = prev_feerate.map(min_rbf_feerate);
let prior = if pending_splice.last_funding_feerate_sat_per_1000_weight.is_some() {
self.build_prior_contribution()
} else {
@@ -12114,10 +12124,7 @@ where
}
match pending_splice.last_funding_feerate_sat_per_1000_weight {
- Some(prev_feerate) => {
- let min_feerate_kwu = ((prev_feerate as u64) * 25).div_ceil(24);
- Ok(FeeRate::from_sat_per_kwu(min_feerate_kwu))
- },
+ Some(prev_feerate) => Ok(min_rbf_feerate(prev_feerate)),
None => Err(format!(
"Channel {} has no prior feerate to compute RBF minimum",
self.context.channel_id(),
diff --git a/lightning/src/ln/funding.rs b/lightning/src/ln/funding.rs
index 0ba4ed188e6..c94b2806d60 100644
--- a/lightning/src/ln/funding.rs
+++ b/lightning/src/ln/funding.rs
@@ -218,8 +218,9 @@ impl PriorContribution {
/// prior contribution logic internally — reusing an adjusted prior when possible, re-running
/// coin selection when needed, or creating a fee-bump-only contribution.
///
-/// Check [`FundingTemplate::min_rbf_feerate`] for the minimum feerate required (25/24 of
-/// the previous feerate). Use [`FundingTemplate::prior_contribution`] to inspect the prior
+/// Check [`FundingTemplate::min_rbf_feerate`] for the minimum feerate required (the greater of
+/// the previous feerate + 25 sat/kwu and the spec's 25/24 rule). Use
+/// [`FundingTemplate::prior_contribution`] to inspect the prior
/// contribution's parameters (e.g., [`FundingContribution::value_added`],
/// [`FundingContribution::outputs`]) before deciding whether to reuse it via the RBF methods
/// or build a fresh contribution with different parameters using the splice methods above.
@@ -232,8 +233,9 @@ pub struct FundingTemplate {
/// transaction.
shared_input: Option,
- /// The minimum RBF feerate (25/24 of the previous feerate), if this template is for an
- /// RBF attempt. `None` for fresh splices with no pending splice candidates.
+ /// The minimum RBF feerate (the greater of previous feerate + 25 sat/kwu and the spec's
+ /// 25/24 rule), if this template is for an RBF attempt. `None` for fresh splices with no
+ /// pending splice candidates.
min_rbf_feerate: Option,
/// The user's prior contribution from a previous splice negotiation, if available.
@@ -2262,8 +2264,8 @@ mod tests {
// When the caller's max_feerate is below the minimum RBF feerate, rbf_sync should
// return Err(()).
let prior_feerate = FeeRate::from_sat_per_kwu(2000);
- let min_rbf_feerate = FeeRate::from_sat_per_kwu(5000);
- let max_feerate = FeeRate::from_sat_per_kwu(3000);
+ let min_rbf_feerate = FeeRate::from_sat_per_kwu(2025);
+ let max_feerate = FeeRate::from_sat_per_kwu(2020);
let prior = FundingContribution {
value_added: Amount::from_sat(50_000),
@@ -2276,7 +2278,7 @@ mod tests {
is_splice: true,
};
- // max_feerate (3000) < min_rbf_feerate (5000).
+ // max_feerate (2020) < min_rbf_feerate (2025).
let template = FundingTemplate::new(
None,
Some(min_rbf_feerate),
@@ -2359,8 +2361,8 @@ mod tests {
// When the prior contribution's feerate is below the minimum RBF feerate and no
// holder balance is available, rbf_sync should run coin selection to add inputs that
// cover the higher RBF fee.
- let min_rbf_feerate = FeeRate::from_sat_per_kwu(5000);
let prior_feerate = FeeRate::from_sat_per_kwu(2000);
+ let min_rbf_feerate = FeeRate::from_sat_per_kwu(2025);
let withdrawal = funding_output_sats(20_000);
let prior = FundingContribution {
@@ -2397,7 +2399,7 @@ mod tests {
fn test_rbf_sync_no_prior_fee_bump_only_runs_coin_selection() {
// When there is no prior contribution (e.g., acceptor), rbf_sync should run coin
// selection to add inputs for a fee-bump-only contribution.
- let min_rbf_feerate = FeeRate::from_sat_per_kwu(5000);
+ let min_rbf_feerate = FeeRate::from_sat_per_kwu(2025);
let template =
FundingTemplate::new(Some(shared_input(100_000)), Some(min_rbf_feerate), None);
@@ -2419,7 +2421,7 @@ mod tests {
// When the prior contribution's feerate is below the minimum RBF feerate and no
// holder balance is available, rbf_sync should use the caller's max_feerate (not the
// prior's) for the resulting contribution.
- let min_rbf_feerate = FeeRate::from_sat_per_kwu(5000);
+ let min_rbf_feerate = FeeRate::from_sat_per_kwu(2025);
let prior_max_feerate = FeeRate::from_sat_per_kwu(50_000);
let callers_max_feerate = FeeRate::from_sat_per_kwu(10_000);
let withdrawal = funding_output_sats(20_000);
@@ -2458,8 +2460,8 @@ mod tests {
// When splice_out_sync is called on a template with min_rbf_feerate set (user
// choosing a fresh splice-out instead of rbf_sync), coin selection should NOT run.
// Fees come from the channel balance.
- let min_rbf_feerate = FeeRate::from_sat_per_kwu(5000);
- let feerate = FeeRate::from_sat_per_kwu(5000);
+ let min_rbf_feerate = FeeRate::from_sat_per_kwu(2025);
+ let feerate = FeeRate::from_sat_per_kwu(2025);
let withdrawal = funding_output_sats(20_000);
let template =
diff --git a/lightning/src/ln/splicing_tests.rs b/lightning/src/ln/splicing_tests.rs
index 20339e445bf..da5b79b6017 100644
--- a/lightning/src/ln/splicing_tests.rs
+++ b/lightning/src/ln/splicing_tests.rs
@@ -4322,8 +4322,8 @@ fn test_splice_rbf_acceptor_basic() {
provide_utxo_reserves(&nodes, 2, added_value * 2);
// Step 3: Use splice_channel API to initiate the RBF.
- // Original feerate was FEERATE_FLOOR_SATS_PER_KW (253). 253 * 25 / 24 = 263.54, so 264 works.
- let rbf_feerate_sat_per_kwu = (FEERATE_FLOOR_SATS_PER_KW as u64 * 25).div_ceil(24);
+ // Original feerate was FEERATE_FLOOR_SATS_PER_KW (253). 253 + 25 = 278.
+ let rbf_feerate_sat_per_kwu = FEERATE_FLOOR_SATS_PER_KW as u64 + 25;
let rbf_feerate = FeeRate::from_sat_per_kwu(rbf_feerate_sat_per_kwu);
let funding_contribution =
do_initiate_rbf_splice_in(&nodes[0], &nodes[1], channel_id, added_value, rbf_feerate);
@@ -4357,9 +4357,73 @@ fn test_splice_rbf_acceptor_basic() {
);
}
+#[test]
+fn test_splice_rbf_at_high_feerate() {
+ // Test that min_rbf_feerate satisfies the spec's 25/24 rule at high feerates (above 600
+ // sat/kwu, where a flat +25 increment alone would be insufficient).
+ let chanmon_cfgs = create_chanmon_cfgs(2);
+ let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
+ let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
+ let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
+
+ let node_id_0 = nodes[0].node.get_our_node_id();
+ let node_id_1 = nodes[1].node.get_our_node_id();
+
+ let initial_channel_value_sat = 100_000;
+ let (_, _, channel_id, _) =
+ create_announced_chan_between_nodes_with_value(&nodes, 0, 1, initial_channel_value_sat, 0);
+
+ let added_value = Amount::from_sat(50_000);
+ provide_utxo_reserves(&nodes, 2, added_value * 2);
+
+ // Step 1: Complete a splice-in at floor feerate.
+ let funding_contribution = do_initiate_splice_in(&nodes[0], &nodes[1], channel_id, added_value);
+ let (_first_splice_tx, new_funding_script) =
+ splice_channel(&nodes[0], &nodes[1], channel_id, funding_contribution);
+
+ // Step 2: RBF to a high feerate (1000 sat/kwu, well above the 600 crossover point).
+ provide_utxo_reserves(&nodes, 2, added_value * 2);
+ let high_feerate = FeeRate::from_sat_per_kwu(1000);
+ let contribution =
+ do_initiate_rbf_splice_in(&nodes[0], &nodes[1], channel_id, added_value, high_feerate);
+ complete_rbf_handshake(&nodes[0], &nodes[1]);
+ complete_interactive_funding_negotiation(
+ &nodes[0],
+ &nodes[1],
+ channel_id,
+ contribution,
+ new_funding_script.clone(),
+ );
+ let (_, splice_locked) = sign_interactive_funding_tx(&nodes[0], &nodes[1], false);
+ assert!(splice_locked.is_none());
+ expect_splice_pending_event(&nodes[0], &node_id_1);
+ expect_splice_pending_event(&nodes[1], &node_id_0);
+
+ // Step 3: RBF again using the template's min_rbf_feerate. The counterparty must accept it.
+ provide_utxo_reserves(&nodes, 2, added_value * 2);
+ let rbf_feerate = {
+ let funding_template = nodes[0].node.splice_channel(&channel_id, &node_id_1).unwrap();
+ funding_template.min_rbf_feerate().unwrap()
+ };
+ let contribution =
+ do_initiate_rbf_splice_in(&nodes[0], &nodes[1], channel_id, added_value, rbf_feerate);
+ complete_rbf_handshake(&nodes[0], &nodes[1]);
+ complete_interactive_funding_negotiation(
+ &nodes[0],
+ &nodes[1],
+ channel_id,
+ contribution,
+ new_funding_script,
+ );
+ let (_, splice_locked) = sign_interactive_funding_tx(&nodes[0], &nodes[1], false);
+ assert!(splice_locked.is_none());
+ expect_splice_pending_event(&nodes[0], &node_id_1);
+ expect_splice_pending_event(&nodes[1], &node_id_0);
+}
+
#[test]
fn test_splice_rbf_insufficient_feerate() {
- // Test that splice_in_sync rejects a feerate that doesn't satisfy the 25/24 rule, and that the
+ // Test that splice_in_sync rejects a feerate that doesn't satisfy the +25 sat/kwu rule, and that the
// acceptor also rejects tx_init_rbf with an insufficient feerate from a misbehaving peer.
let chanmon_cfgs = create_chanmon_cfgs(2);
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
@@ -4388,8 +4452,7 @@ fn test_splice_rbf_insufficient_feerate() {
// Verify that the template exposes the RBF floor.
let min_rbf_feerate = funding_template.min_rbf_feerate().unwrap();
- let expected_floor =
- FeeRate::from_sat_per_kwu(((FEERATE_FLOOR_SATS_PER_KW as u64) * 25).div_ceil(24));
+ let expected_floor = FeeRate::from_sat_per_kwu(FEERATE_FLOOR_SATS_PER_KW as u64 + 25);
assert_eq!(min_rbf_feerate, expected_floor);
let wallet = WalletSync::new(Arc::clone(&nodes[0].wallet_source), nodes[0].logger);
@@ -4417,6 +4480,22 @@ fn test_splice_rbf_insufficient_feerate() {
let tx_abort = get_event_msg!(nodes[1], MessageSendEvent::SendTxAbort, node_id_0);
assert_eq!(tx_abort.channel_id, channel_id);
+
+ // Acceptor-side: a counterparty feerate that satisfies the spec's 25/24 rule (264) is
+ // accepted, even though our own RBF floor (+25 sat/kwu = 278) is higher.
+ // After tx_abort the channel remains quiescent, so no need to re-enter quiescence.
+ nodes[0].node.handle_tx_abort(node_id_1, &tx_abort);
+
+ let rbf_feerate_25_24 = ((FEERATE_FLOOR_SATS_PER_KW as u64) * 25).div_ceil(24) as u32;
+ let tx_init_rbf = msgs::TxInitRbf {
+ channel_id,
+ locktime: 0,
+ feerate_sat_per_1000_weight: rbf_feerate_25_24,
+ funding_output_contribution: Some(added_value.to_sat() as i64),
+ };
+
+ nodes[1].node.handle_tx_init_rbf(node_id_0, &tx_init_rbf);
+ let _tx_ack_rbf = get_event_msg!(nodes[1], MessageSendEvent::SendTxAckRbf, node_id_0);
}
#[test]
@@ -4695,7 +4774,7 @@ fn test_splice_rbf_not_quiescence_initiator() {
provide_utxo_reserves(&nodes, 2, added_value * 2);
// Initiate RBF from node 0 (quiescence initiator).
- let rbf_feerate_sat_per_kwu = (FEERATE_FLOOR_SATS_PER_KW as u64 * 25).div_ceil(24);
+ let rbf_feerate_sat_per_kwu = FEERATE_FLOOR_SATS_PER_KW as u64 + 25;
let rbf_feerate = FeeRate::from_sat_per_kwu(rbf_feerate_sat_per_kwu);
let _funding_contribution =
do_initiate_rbf_splice_in(&nodes[0], &nodes[1], channel_id, added_value, rbf_feerate);
@@ -4725,7 +4804,7 @@ fn test_splice_rbf_not_quiescence_initiator() {
#[test]
fn test_splice_rbf_both_contribute_tiebreak() {
- let min_rbf_feerate = (FEERATE_FLOOR_SATS_PER_KW as u64 * 25).div_ceil(24);
+ let min_rbf_feerate = FEERATE_FLOOR_SATS_PER_KW as u64 + 25;
let feerate = FeeRate::from_sat_per_kwu(min_rbf_feerate);
let added_value = Amount::from_sat(50_000);
do_test_splice_rbf_tiebreak(feerate, feerate, added_value, true);
@@ -4735,7 +4814,7 @@ fn test_splice_rbf_both_contribute_tiebreak() {
fn test_splice_rbf_tiebreak_higher_feerate() {
// Node 0 (winner) uses a higher feerate than node 1 (loser). Node 1's change output is
// adjusted (reduced) to accommodate the higher feerate. Negotiation succeeds.
- let min_rbf_feerate = (FEERATE_FLOOR_SATS_PER_KW as u64 * 25).div_ceil(24);
+ let min_rbf_feerate = FEERATE_FLOOR_SATS_PER_KW as u64 + 25;
do_test_splice_rbf_tiebreak(
FeeRate::from_sat_per_kwu(min_rbf_feerate * 3),
FeeRate::from_sat_per_kwu(min_rbf_feerate),
@@ -4749,7 +4828,7 @@ fn test_splice_rbf_tiebreak_lower_feerate() {
// Node 0 (winner) uses a lower feerate than node 1 (loser). Since the initiator's feerate
// is below node 1's minimum, node 1 proceeds without contribution and will retry via a new
// splice at its preferred feerate after the RBF locks.
- let min_rbf_feerate = (FEERATE_FLOOR_SATS_PER_KW as u64 * 25).div_ceil(24);
+ let min_rbf_feerate = FEERATE_FLOOR_SATS_PER_KW as u64 + 25;
do_test_splice_rbf_tiebreak(
FeeRate::from_sat_per_kwu(min_rbf_feerate),
FeeRate::from_sat_per_kwu(min_rbf_feerate * 3),
@@ -4763,7 +4842,7 @@ fn test_splice_rbf_tiebreak_feerate_too_high() {
// Node 0 (winner) uses a feerate high enough that node 1's (loser) contribution cannot
// cover the fees. Node 1 proceeds without its contribution (QuiescentAction is preserved
// for a future splice). The RBF completes with only node 0's inputs/outputs.
- let min_rbf_feerate = (FEERATE_FLOOR_SATS_PER_KW as u64 * 25).div_ceil(24);
+ let min_rbf_feerate = FEERATE_FLOOR_SATS_PER_KW as u64 + 25;
do_test_splice_rbf_tiebreak(
FeeRate::from_sat_per_kwu(20_000),
FeeRate::from_sat_per_kwu(min_rbf_feerate),
@@ -5064,7 +5143,7 @@ fn test_splice_rbf_tiebreak_feerate_too_high_rejected() {
// The target (100k) far exceeds node 1's max (3k), and the fair fee at 100k exceeds
// node 1's budget, triggering TooHigh.
let high_feerate = FeeRate::from_sat_per_kwu(100_000);
- let min_rbf_feerate_sat_per_kwu = (FEERATE_FLOOR_SATS_PER_KW as u64 * 25).div_ceil(24);
+ let min_rbf_feerate_sat_per_kwu = FEERATE_FLOOR_SATS_PER_KW as u64 + 25;
let min_rbf_feerate = FeeRate::from_sat_per_kwu(min_rbf_feerate_sat_per_kwu);
let node_1_max_feerate = FeeRate::from_sat_per_kwu(3_000);
@@ -5194,7 +5273,7 @@ fn test_splice_rbf_acceptor_recontributes() {
provide_utxo_reserves(&nodes, 2, added_value * 2);
// Step 5: Only node 0 calls splice_channel + funding_contributed.
- let rbf_feerate_sat_per_kwu = (FEERATE_FLOOR_SATS_PER_KW as u64 * 25).div_ceil(24);
+ let rbf_feerate_sat_per_kwu = FEERATE_FLOOR_SATS_PER_KW as u64 + 25;
let rbf_feerate = FeeRate::from_sat_per_kwu(rbf_feerate_sat_per_kwu);
let rbf_funding_contribution =
do_initiate_rbf_splice_in(&nodes[0], &nodes[1], channel_id, added_value, rbf_feerate);
@@ -5318,8 +5397,7 @@ fn test_splice_rbf_after_counterparty_rbf_aborted() {
// is adjusted to the RBF feerate via for_acceptor_at_feerate.
provide_utxo_reserves(&nodes, 2, added_value * 2);
- let rbf_feerate =
- FeeRate::from_sat_per_kwu((FEERATE_FLOOR_SATS_PER_KW as u64 * 25).div_ceil(24));
+ let rbf_feerate = FeeRate::from_sat_per_kwu(FEERATE_FLOOR_SATS_PER_KW as u64 + 25);
let _rbf_funding_contribution =
do_initiate_rbf_splice_in(&nodes[0], &nodes[1], channel_id, added_value, rbf_feerate);
@@ -5482,7 +5560,7 @@ fn test_splice_rbf_sequential() {
// Three consecutive RBF rounds on the same splice (initial → RBF #1 → RBF #2).
// Node 0 is the quiescence initiator; node 1 is the acceptor with no contribution.
// Verifies:
- // - Each round satisfies the 25/24 feerate rule
+ // - Each round satisfies the +25 sat/kwu feerate rule
// - DiscardFunding events reference the correct txids from previous rounds
// - The final RBF can be mined and splice_locked successfully
let chanmon_cfgs = create_chanmon_cfgs(2);
@@ -5505,11 +5583,11 @@ fn test_splice_rbf_sequential() {
let (splice_tx_0, new_funding_script) =
splice_channel(&nodes[0], &nodes[1], channel_id, funding_contribution);
- // Feerate progression: 253 → ceil(253*25/24) = 264 → ceil(264*25/24) = 275
- let feerate_1_sat_per_kwu = (FEERATE_FLOOR_SATS_PER_KW as u64 * 25).div_ceil(24); // 264
- let feerate_2_sat_per_kwu = (feerate_1_sat_per_kwu * 25).div_ceil(24);
+ // Feerate progression: 253 → 253+25 = 278 → 278+25 = 303
+ let feerate_1_sat_per_kwu = FEERATE_FLOOR_SATS_PER_KW as u64 + 25; // 278
+ let feerate_2_sat_per_kwu = feerate_1_sat_per_kwu + 25;
- // --- Round 1: RBF #1 at feerate 264. ---
+ // --- Round 1: RBF #1 at feerate 278. ---
provide_utxo_reserves(&nodes, 2, added_value * 2);
let rbf_feerate_1 = FeeRate::from_sat_per_kwu(feerate_1_sat_per_kwu);
@@ -5529,7 +5607,7 @@ fn test_splice_rbf_sequential() {
expect_splice_pending_event(&nodes[0], &node_id_1);
expect_splice_pending_event(&nodes[1], &node_id_0);
- // --- Round 2: RBF #2 at feerate 275. ---
+ // --- Round 2: RBF #2 at feerate 303. ---
provide_utxo_reserves(&nodes, 2, added_value * 2);
let rbf_feerate_2 = FeeRate::from_sat_per_kwu(feerate_2_sat_per_kwu);
@@ -5625,7 +5703,7 @@ fn test_splice_rbf_acceptor_contributes_then_disconnects() {
// --- Round 1: Node 0 initiates RBF; node 1 re-contributes via prior. ---
provide_utxo_reserves(&nodes, 2, added_value * 2);
- let rbf_feerate_sat_per_kwu = (FEERATE_FLOOR_SATS_PER_KW as u64 * 25).div_ceil(24);
+ let rbf_feerate_sat_per_kwu = FEERATE_FLOOR_SATS_PER_KW as u64 + 25;
let rbf_feerate = FeeRate::from_sat_per_kwu(rbf_feerate_sat_per_kwu);
let _rbf_funding_contribution =
do_initiate_rbf_splice_in(&nodes[0], &nodes[1], channel_id, added_value, rbf_feerate);
@@ -5697,7 +5775,7 @@ fn test_splice_rbf_disconnect_filters_prior_contributions() {
// Include a splice-out output with a different script_pubkey so the test can verify
// selective filtering: the change output (same script_pubkey as round 0) is filtered,
// while the splice-out output (different script_pubkey) survives.
- let feerate_1_sat_per_kwu = (FEERATE_FLOOR_SATS_PER_KW as u64 * 25).div_ceil(24);
+ let feerate_1_sat_per_kwu = FEERATE_FLOOR_SATS_PER_KW as u64 + 25;
let rbf_feerate = FeeRate::from_sat_per_kwu(feerate_1_sat_per_kwu);
let splice_out_output = TxOut {
value: Amount::from_sat(1_000),
@@ -5747,9 +5825,9 @@ fn test_splice_rbf_disconnect_filters_prior_contributions() {
reconnect_args.send_announcement_sigs = (true, true);
reconnect_nodes(reconnect_args);
- // --- Round 2: RBF at the same feerate as the failed round 1 (264). ---
+ // --- Round 2: RBF at the same feerate as the failed round 1 (278). ---
// This should succeed because the failed round never updated the feerate floor, which
- // remains at round 0's rate (253), and 264 >= ceil(253 * 25/24).
+ // remains at round 0's rate (253), and 278 >= 253 + 25.
provide_utxo_reserves(&nodes, 1, added_value * 2);
let rbf_feerate_2 = FeeRate::from_sat_per_kwu(feerate_1_sat_per_kwu);
@@ -5809,8 +5887,7 @@ fn test_splice_channel_with_pending_splice_includes_rbf_floor() {
// Call splice_channel again — the pending splice should cause min_rbf_feerate to be set
// and the prior contribution to be available.
let funding_template = nodes[0].node.splice_channel(&channel_id, &node_id_1).unwrap();
- let expected_floor =
- FeeRate::from_sat_per_kwu(((FEERATE_FLOOR_SATS_PER_KW as u64) * 25).div_ceil(24));
+ let expected_floor = FeeRate::from_sat_per_kwu(FEERATE_FLOOR_SATS_PER_KW as u64 + 25);
assert_eq!(funding_template.min_rbf_feerate(), Some(expected_floor));
assert!(funding_template.prior_contribution().is_some());
@@ -5859,7 +5936,7 @@ fn test_funding_contributed_adjusts_feerate_for_rbf() {
splice_channel(&nodes[1], &nodes[0], channel_id, node_1_contribution);
// Node 0 calls funding_contributed. The contribution's feerate (floor) is below the RBF
- // floor (25/24 of floor), but funding_contributed adjusts it upward.
+ // floor (floor + 25 sat/kwu), but funding_contributed adjusts it upward.
nodes[0].node.funding_contributed(&channel_id, &node_id_1, contribution.clone(), None).unwrap();
// STFU should be sent immediately (the adjusted feerate satisfies the RBF check).
@@ -5871,8 +5948,7 @@ fn test_funding_contributed_adjusts_feerate_for_rbf() {
// Verify the RBF handshake proceeds.
let tx_init_rbf = get_event_msg!(nodes[0], MessageSendEvent::SendTxInitRbf, node_id_1);
let rbf_feerate = FeeRate::from_sat_per_kwu(tx_init_rbf.feerate_sat_per_1000_weight as u64);
- let expected_floor =
- FeeRate::from_sat_per_kwu((FEERATE_FLOOR_SATS_PER_KW as u64 * 25).div_ceil(24));
+ let expected_floor = FeeRate::from_sat_per_kwu(FEERATE_FLOOR_SATS_PER_KW as u64 + 25);
assert!(rbf_feerate >= expected_floor);
}
@@ -5897,7 +5973,7 @@ fn test_funding_contributed_rbf_adjustment_exceeds_max_feerate() {
provide_utxo_reserves(&nodes, 4, added_value * 2);
// Node 0 calls splice_channel and builds contribution with max_feerate = floor_feerate.
- // This means the minimum RBF feerate (25/24 of floor) will exceed max_feerate, preventing adjustment.
+ // This means the minimum RBF feerate (floor + 25 sat/kwu) will exceed max_feerate, preventing adjustment.
let floor_feerate = FeeRate::from_sat_per_kwu(FEERATE_FLOOR_SATS_PER_KW as u64);
let funding_template = nodes[0].node.splice_channel(&channel_id, &node_id_1).unwrap();
let wallet = WalletSync::new(Arc::clone(&nodes[0].wallet_source), nodes[0].logger);
@@ -5972,7 +6048,8 @@ fn test_funding_contributed_rbf_adjustment_insufficient_budget() {
funding_template.splice_in_sync(added_value, floor_feerate, FeeRate::MAX, &wallet).unwrap();
// Node 1 initiates a splice at a HIGH feerate (10,000 sat/kwu). The minimum RBF feerate will be
- // 25/24 of 10,000 = 10,417 sat/kwu — far above what node 0's tight budget can handle.
+ // max(10,000 + 25, ceil(10,000 * 25/24)) = 10,417 sat/kwu — far above what node 0's tight
+ // budget can handle.
let high_feerate = FeeRate::from_sat_per_kwu(10_000);
let node_1_template = nodes[1].node.splice_channel(&channel_id, &node_id_0).unwrap();
let node_1_wallet = WalletSync::new(Arc::clone(&nodes[1].wallet_source), nodes[1].logger);
@@ -6047,7 +6124,7 @@ fn test_prior_contribution_unadjusted_when_max_feerate_too_low() {
.unwrap();
let (_splice_tx, _) = splice_channel(&nodes[0], &nodes[1], channel_id, funding_contribution);
- // Call splice_channel again — the minimum RBF feerate (25/24 of floor) exceeds the prior
+ // Call splice_channel again — the minimum RBF feerate (floor + 25 sat/kwu) exceeds the prior
// contribution's max_feerate (floor), so adjustment fails. rbf_sync re-runs coin selection
// with the caller's max_feerate.
let funding_template = nodes[0].node.splice_channel(&channel_id, &node_id_1).unwrap();
@@ -6092,8 +6169,7 @@ fn test_splice_channel_during_negotiation_includes_rbf_feerate() {
// Node 0 (acceptor) calls splice_channel while the negotiation is in progress.
// min_rbf_feerate should be derived from the in-progress negotiation's feerate.
let template = nodes[0].node.splice_channel(&channel_id, &node_id_1).unwrap();
- let expected_floor =
- FeeRate::from_sat_per_kwu(((FEERATE_FLOOR_SATS_PER_KW as u64) * 25).div_ceil(24));
+ let expected_floor = FeeRate::from_sat_per_kwu(FEERATE_FLOOR_SATS_PER_KW as u64 + 25);
assert_eq!(template.min_rbf_feerate(), Some(expected_floor));
// No prior contribution since there are no negotiated candidates yet. rbf_sync runs
@@ -6339,7 +6415,7 @@ fn test_splice_rbf_rejects_low_feerate_after_several_attempts() {
// Rounds 1-10: RBF at minimum bump. Accepted (at or below threshold).
let mut prev_feerate = FEERATE_FLOOR_SATS_PER_KW as u64;
for _ in 0..10 {
- let feerate = (prev_feerate * 25).div_ceil(24);
+ let feerate = prev_feerate + 25;
provide_utxo_reserves(&nodes, 2, added_value * 2);
let rbf_feerate = FeeRate::from_sat_per_kwu(feerate);
let contribution =
@@ -6360,7 +6436,7 @@ fn test_splice_rbf_rejects_low_feerate_after_several_attempts() {
}
// Round 11: RBF at minimum bump. Should be rejected because feerate < fee estimator.
- let next_feerate = (prev_feerate * 25).div_ceil(24);
+ let next_feerate = prev_feerate + 25;
provide_utxo_reserves(&nodes, 2, added_value * 2);
let rbf_feerate = FeeRate::from_sat_per_kwu(next_feerate);
let _contribution =
@@ -6410,7 +6486,7 @@ fn test_splice_rbf_rejects_own_low_feerate_after_several_attempts() {
// Rounds 1-10: RBF at minimum bump. Accepted (at or below threshold).
let mut prev_feerate = FEERATE_FLOOR_SATS_PER_KW as u64;
for _ in 0..10 {
- let feerate = (prev_feerate * 25).div_ceil(24);
+ let feerate = prev_feerate + 25;
provide_utxo_reserves(&nodes, 2, added_value * 2);
let rbf_feerate = FeeRate::from_sat_per_kwu(feerate);
let contribution =
@@ -6431,7 +6507,7 @@ fn test_splice_rbf_rejects_own_low_feerate_after_several_attempts() {
}
// Round 11: Our own RBF at minimum bump. funding_contributed should reject it.
- let next_feerate = (prev_feerate * 25).div_ceil(24);
+ let next_feerate = prev_feerate + 25;
provide_utxo_reserves(&nodes, 2, added_value * 2);
let rbf_feerate = FeeRate::from_sat_per_kwu(next_feerate);
let funding_template = nodes[0].node.splice_channel(&channel_id, &node_id_1).unwrap();