Skip to content

Commit 9b9c114

Browse files
jkczyzclaude
andcommitted
Use +25 sat/kwu increment for our minimum RBF feerate
The spec's 25/24 multiplier doesn't always satisfy BIP125's relay requirement of an absolute fee increase. Use a flat +25 sat/kwu (0.1 sat/vB) increment for our own RBFs instead, while still accepting the 25/24 rule from counterparties. Extract a `min_rbf_feerate` helper to consolidate the two call sites and add a test that a counterparty feerate satisfying 25/24 (but not +25) is accepted. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 8d00139 commit 9b9c114

3 files changed

Lines changed: 72 additions & 57 deletions

File tree

lightning/src/ln/channel.rs

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6484,6 +6484,16 @@ fn get_v2_channel_reserve_satoshis(channel_value_satoshis: u64, dust_limit_satos
64846484
cmp::min(channel_value_satoshis, cmp::max(q, dust_limit_satoshis))
64856485
}
64866486

6487+
/// Returns the minimum feerate for our own RBF attempts given a previous feerate.
6488+
///
6489+
/// 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`].
6493+
fn min_rbf_feerate(prev_feerate: u32) -> FeeRate {
6494+
FeeRate::from_sat_per_kwu((prev_feerate as u64).saturating_add(25))
6495+
}
6496+
64876497
/// Context for negotiating channels (dual-funded V2 open, splicing)
64886498
#[derive(Debug)]
64896499
pub(super) struct FundingNegotiationContext {
@@ -12019,10 +12029,7 @@ where
1201912029
prev_feerate.is_some(),
1202012030
"pending_splice should have last_funding_feerate or funding_negotiation",
1202112031
);
12022-
let min_rbf_feerate = prev_feerate.map(|f| {
12023-
let min_feerate_kwu = ((f as u64) * 25).div_ceil(24);
12024-
FeeRate::from_sat_per_kwu(min_feerate_kwu)
12025-
});
12032+
let min_rbf_feerate = prev_feerate.map(min_rbf_feerate);
1202612033
let prior = if pending_splice.last_funding_feerate_sat_per_1000_weight.is_some() {
1202712034
self.build_prior_contribution()
1202812035
} else {
@@ -12114,10 +12121,7 @@ where
1211412121
}
1211512122

1211612123
match pending_splice.last_funding_feerate_sat_per_1000_weight {
12117-
Some(prev_feerate) => {
12118-
let min_feerate_kwu = ((prev_feerate as u64) * 25).div_ceil(24);
12119-
Ok(FeeRate::from_sat_per_kwu(min_feerate_kwu))
12120-
},
12124+
Some(prev_feerate) => Ok(min_rbf_feerate(prev_feerate)),
1212112125
None => Err(format!(
1212212126
"Channel {} has no prior feerate to compute RBF minimum",
1212312127
self.context.channel_id(),

lightning/src/ln/funding.rs

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -218,8 +218,8 @@ 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 (25/24 of
222-
/// the previous feerate). Use [`FundingTemplate::prior_contribution`] to inspect the prior
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
223223
/// contribution's parameters (e.g., [`FundingContribution::value_added`],
224224
/// [`FundingContribution::outputs`]) before deciding whether to reuse it via the RBF methods
225225
/// or build a fresh contribution with different parameters using the splice methods above.
@@ -232,7 +232,7 @@ pub struct FundingTemplate {
232232
/// transaction.
233233
shared_input: Option<Input>,
234234

235-
/// The minimum RBF feerate (25/24 of the previous feerate), if this template is for an
235+
/// The minimum RBF feerate (previous feerate + 25 sat/kwu), if this template is for an
236236
/// RBF attempt. `None` for fresh splices with no pending splice candidates.
237237
min_rbf_feerate: Option<FeeRate>,
238238

@@ -2262,8 +2262,8 @@ mod tests {
22622262
// When the caller's max_feerate is below the minimum RBF feerate, rbf_sync should
22632263
// return Err(()).
22642264
let prior_feerate = FeeRate::from_sat_per_kwu(2000);
2265-
let min_rbf_feerate = FeeRate::from_sat_per_kwu(5000);
2266-
let max_feerate = FeeRate::from_sat_per_kwu(3000);
2265+
let min_rbf_feerate = FeeRate::from_sat_per_kwu(2025);
2266+
let max_feerate = FeeRate::from_sat_per_kwu(2020);
22672267

22682268
let prior = FundingContribution {
22692269
value_added: Amount::from_sat(50_000),
@@ -2276,7 +2276,7 @@ mod tests {
22762276
is_splice: true,
22772277
};
22782278

2279-
// max_feerate (3000) < min_rbf_feerate (5000).
2279+
// max_feerate (2020) < min_rbf_feerate (2025).
22802280
let template = FundingTemplate::new(
22812281
None,
22822282
Some(min_rbf_feerate),
@@ -2359,8 +2359,8 @@ mod tests {
23592359
// When the prior contribution's feerate is below the minimum RBF feerate and no
23602360
// holder balance is available, rbf_sync should run coin selection to add inputs that
23612361
// cover the higher RBF fee.
2362-
let min_rbf_feerate = FeeRate::from_sat_per_kwu(5000);
23632362
let prior_feerate = FeeRate::from_sat_per_kwu(2000);
2363+
let min_rbf_feerate = FeeRate::from_sat_per_kwu(2025);
23642364
let withdrawal = funding_output_sats(20_000);
23652365

23662366
let prior = FundingContribution {
@@ -2397,7 +2397,7 @@ mod tests {
23972397
fn test_rbf_sync_no_prior_fee_bump_only_runs_coin_selection() {
23982398
// When there is no prior contribution (e.g., acceptor), rbf_sync should run coin
23992399
// selection to add inputs for a fee-bump-only contribution.
2400-
let min_rbf_feerate = FeeRate::from_sat_per_kwu(5000);
2400+
let min_rbf_feerate = FeeRate::from_sat_per_kwu(2025);
24012401

24022402
let template =
24032403
FundingTemplate::new(Some(shared_input(100_000)), Some(min_rbf_feerate), None);
@@ -2419,7 +2419,7 @@ mod tests {
24192419
// When the prior contribution's feerate is below the minimum RBF feerate and no
24202420
// holder balance is available, rbf_sync should use the caller's max_feerate (not the
24212421
// prior's) for the resulting contribution.
2422-
let min_rbf_feerate = FeeRate::from_sat_per_kwu(5000);
2422+
let min_rbf_feerate = FeeRate::from_sat_per_kwu(2025);
24232423
let prior_max_feerate = FeeRate::from_sat_per_kwu(50_000);
24242424
let callers_max_feerate = FeeRate::from_sat_per_kwu(10_000);
24252425
let withdrawal = funding_output_sats(20_000);
@@ -2458,8 +2458,8 @@ mod tests {
24582458
// When splice_out_sync is called on a template with min_rbf_feerate set (user
24592459
// choosing a fresh splice-out instead of rbf_sync), coin selection should NOT run.
24602460
// Fees come from the channel balance.
2461-
let min_rbf_feerate = FeeRate::from_sat_per_kwu(5000);
2462-
let feerate = FeeRate::from_sat_per_kwu(5000);
2461+
let min_rbf_feerate = FeeRate::from_sat_per_kwu(2025);
2462+
let feerate = FeeRate::from_sat_per_kwu(2025);
24632463
let withdrawal = funding_output_sats(20_000);
24642464

24652465
let template =

0 commit comments

Comments
 (0)