Skip to content

Commit f56d3c8

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 f56d3c8

3 files changed

Lines changed: 79 additions & 9 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/funding.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -218,8 +218,9 @@ impl PriorContribution {
218218
/// prior contribution logic internally — reusing an adjusted prior when possible, re-running
219219
/// coin selection when needed, or creating a fee-bump-only contribution.
220220
///
221-
/// Check [`FundingTemplate::min_rbf_feerate`] for the minimum feerate required (previous
222-
/// feerate + 25 sat/kwu). Use [`FundingTemplate::prior_contribution`] to inspect the prior
221+
/// Check [`FundingTemplate::min_rbf_feerate`] for the minimum feerate required (the greater of
222+
/// the previous feerate + 25 sat/kwu and the spec's 25/24 rule). Use
223+
/// [`FundingTemplate::prior_contribution`] to inspect the prior
223224
/// contribution's parameters (e.g., [`FundingContribution::value_added`],
224225
/// [`FundingContribution::outputs`]) before deciding whether to reuse it via the RBF methods
225226
/// or build a fresh contribution with different parameters using the splice methods above.
@@ -232,8 +233,9 @@ pub struct FundingTemplate {
232233
/// transaction.
233234
shared_input: Option<Input>,
234235

235-
/// The minimum RBF feerate (previous feerate + 25 sat/kwu), if this template is for an
236-
/// RBF attempt. `None` for fresh splices with no pending splice candidates.
236+
/// The minimum RBF feerate (the greater of previous feerate + 25 sat/kwu and the spec's
237+
/// 25/24 rule), if this template is for an RBF attempt. `None` for fresh splices with no
238+
/// pending splice candidates.
237239
min_rbf_feerate: Option<FeeRate>,
238240

239241
/// The user's prior contribution from a previous splice negotiation, if available.

lightning/src/ln/splicing_tests.rs

Lines changed: 66 additions & 1 deletion
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
@@ -5984,7 +6048,8 @@ fn test_funding_contributed_rbf_adjustment_insufficient_budget() {
59846048
funding_template.splice_in_sync(added_value, floor_feerate, FeeRate::MAX, &wallet).unwrap();
59856049

59866050
// Node 1 initiates a splice at a HIGH feerate (10,000 sat/kwu). The minimum RBF feerate will be
5987-
// 10,000 + 25 = 10,025 sat/kwu — far above what node 0's tight budget can handle.
6051+
// max(10,000 + 25, ceil(10,000 * 25/24)) = 10,417 sat/kwu — far above what node 0's tight
6052+
// budget can handle.
59886053
let high_feerate = FeeRate::from_sat_per_kwu(10_000);
59896054
let node_1_template = nodes[1].node.splice_channel(&channel_id, &node_id_0).unwrap();
59906055
let node_1_wallet = WalletSync::new(Arc::clone(&nodes[1].wallet_source), nodes[1].logger);

0 commit comments

Comments
 (0)