Skip to content

Commit a18dac2

Browse files
jkczyzclaude
andcommitted
f - Use max of flat increment and spec's 25/24 rule
The flat +25 sat/kwu increment falls below the spec's 25/24 multiplicative rule for feerates above 600 sat/kwu, which would cause our RBF attempts to be rejected by spec-compliant counterparties. Take the max of both to satisfy BIP125 relay requirements at low feerates and the spec rule at higher feerates. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 9b9c114 commit a18dac2

2 files changed

Lines changed: 71 additions & 4 deletions

File tree

lightning/src/ln/channel.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6487,11 +6487,14 @@ fn get_v2_channel_reserve_satoshis(channel_value_satoshis: u64, dust_limit_satos
64876487
/// Returns the minimum feerate for our own RBF attempts given a previous feerate.
64886488
///
64896489
/// The spec (tx_init_rbf) requires the new feerate to be >= 25/24 of the previous feerate.
6490-
/// However, that multiplier doesn't always satisfy BIP125's relay requirement of an absolute fee
6491-
/// increase, so for our own RBFs we use a flat +25 sat/kwu (0.1 sat/vB) increment instead. We
6492-
/// still accept the 25/24 rule from counterparties in [`FundedChannel::validate_tx_init_rbf`].
6490+
/// However, at low feerates that multiplier doesn't always satisfy BIP125's relay requirement of
6491+
/// an absolute fee increase, so we take the max of a flat +25 sat/kwu (0.1 sat/vB) increment
6492+
/// and the spec's multiplicative rule. We still accept the bare 25/24 rule from counterparties
6493+
/// in [`FundedChannel::validate_tx_init_rbf`].
64936494
fn min_rbf_feerate(prev_feerate: u32) -> FeeRate {
6494-
FeeRate::from_sat_per_kwu((prev_feerate as u64).saturating_add(25))
6495+
let flat_increment = (prev_feerate as u64).saturating_add(25);
6496+
let spec_increment = ((prev_feerate as u64) * 25).div_ceil(24);
6497+
FeeRate::from_sat_per_kwu(cmp::max(flat_increment, spec_increment))
64956498
}
64966499

64976500
/// Context for negotiating channels (dual-funded V2 open, splicing)

lightning/src/ln/splicing_tests.rs

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4357,6 +4357,70 @@ fn test_splice_rbf_acceptor_basic() {
43574357
);
43584358
}
43594359

4360+
#[test]
4361+
fn test_splice_rbf_at_high_feerate() {
4362+
// Test that min_rbf_feerate satisfies the spec's 25/24 rule at high feerates (above 600
4363+
// sat/kwu, where a flat +25 increment alone would be insufficient).
4364+
let chanmon_cfgs = create_chanmon_cfgs(2);
4365+
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
4366+
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
4367+
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
4368+
4369+
let node_id_0 = nodes[0].node.get_our_node_id();
4370+
let node_id_1 = nodes[1].node.get_our_node_id();
4371+
4372+
let initial_channel_value_sat = 100_000;
4373+
let (_, _, channel_id, _) =
4374+
create_announced_chan_between_nodes_with_value(&nodes, 0, 1, initial_channel_value_sat, 0);
4375+
4376+
let added_value = Amount::from_sat(50_000);
4377+
provide_utxo_reserves(&nodes, 2, added_value * 2);
4378+
4379+
// Step 1: Complete a splice-in at floor feerate.
4380+
let funding_contribution = do_initiate_splice_in(&nodes[0], &nodes[1], channel_id, added_value);
4381+
let (_first_splice_tx, new_funding_script) =
4382+
splice_channel(&nodes[0], &nodes[1], channel_id, funding_contribution);
4383+
4384+
// Step 2: RBF to a high feerate (1000 sat/kwu, well above the 600 crossover point).
4385+
provide_utxo_reserves(&nodes, 2, added_value * 2);
4386+
let high_feerate = FeeRate::from_sat_per_kwu(1000);
4387+
let contribution =
4388+
do_initiate_rbf_splice_in(&nodes[0], &nodes[1], channel_id, added_value, high_feerate);
4389+
complete_rbf_handshake(&nodes[0], &nodes[1]);
4390+
complete_interactive_funding_negotiation(
4391+
&nodes[0],
4392+
&nodes[1],
4393+
channel_id,
4394+
contribution,
4395+
new_funding_script.clone(),
4396+
);
4397+
let (_, splice_locked) = sign_interactive_funding_tx(&nodes[0], &nodes[1], false);
4398+
assert!(splice_locked.is_none());
4399+
expect_splice_pending_event(&nodes[0], &node_id_1);
4400+
expect_splice_pending_event(&nodes[1], &node_id_0);
4401+
4402+
// Step 3: RBF again using the template's min_rbf_feerate. The counterparty must accept it.
4403+
provide_utxo_reserves(&nodes, 2, added_value * 2);
4404+
let rbf_feerate = {
4405+
let funding_template = nodes[0].node.splice_channel(&channel_id, &node_id_1).unwrap();
4406+
funding_template.min_rbf_feerate().unwrap()
4407+
};
4408+
let contribution =
4409+
do_initiate_rbf_splice_in(&nodes[0], &nodes[1], channel_id, added_value, rbf_feerate);
4410+
complete_rbf_handshake(&nodes[0], &nodes[1]);
4411+
complete_interactive_funding_negotiation(
4412+
&nodes[0],
4413+
&nodes[1],
4414+
channel_id,
4415+
contribution,
4416+
new_funding_script,
4417+
);
4418+
let (_, splice_locked) = sign_interactive_funding_tx(&nodes[0], &nodes[1], false);
4419+
assert!(splice_locked.is_none());
4420+
expect_splice_pending_event(&nodes[0], &node_id_1);
4421+
expect_splice_pending_event(&nodes[1], &node_id_0);
4422+
}
4423+
43604424
#[test]
43614425
fn test_splice_rbf_insufficient_feerate() {
43624426
// Test that splice_in_sync rejects a feerate that doesn't satisfy the +25 sat/kwu rule, and that the

0 commit comments

Comments
 (0)