Skip to content

Commit 72455fc

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 305e81e commit 72455fc

File tree

3 files changed

+67
-49
lines changed

3 files changed

+67
-49
lines changed

lightning/src/ln/channel.rs

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6462,6 +6462,16 @@ fn get_v2_channel_reserve_satoshis(channel_value_satoshis: u64, dust_limit_satos
64626462
cmp::min(channel_value_satoshis, cmp::max(q, dust_limit_satoshis))
64636463
}
64646464

6465+
/// Returns the minimum feerate for our own RBF attempts given a previous feerate.
6466+
///
6467+
/// The spec (tx_init_rbf) requires the new feerate to be >= 25/24 of the previous feerate.
6468+
/// However, that multiplier doesn't always satisfy BIP125's relay requirement of an absolute fee
6469+
/// increase, so for our own RBFs we use a flat +25 sat/kwu (0.1 sat/vB) increment instead. We
6470+
/// still accept the 25/24 rule from counterparties in [`FundedChannel::validate_tx_init_rbf`].
6471+
fn min_rbf_feerate(prev_feerate: u32) -> FeeRate {
6472+
FeeRate::from_sat_per_kwu((prev_feerate as u64).saturating_add(25))
6473+
}
6474+
64656475
/// Context for negotiating channels (dual-funded V2 open, splicing)
64666476
#[derive(Debug)]
64676477
pub(super) struct FundingNegotiationContext {
@@ -11972,8 +11982,7 @@ where
1197211982
// slightly higher feerate than necessary. Call splice_channel again after
1197311983
// receiving SpliceFailed to get a fresh template without the RBF constraint.
1197411984
let prev_feerate = negotiation.funding_feerate_sat_per_1000_weight();
11975-
let min_feerate_kwu = ((prev_feerate as u64) * 25).div_ceil(24);
11976-
(Some(FeeRate::from_sat_per_kwu(min_feerate_kwu)), None)
11985+
(Some(min_rbf_feerate(prev_feerate)), None)
1197711986
} else {
1197811987
// No RBF feerate to derive — either a fresh splice or a pending splice that
1197911988
// can't be RBF'd (e.g., splice_locked already exchanged).
@@ -12057,10 +12066,7 @@ where
1205712066
}
1205812067

1205912068
match pending_splice.last_funding_feerate_sat_per_1000_weight {
12060-
Some(prev_feerate) => {
12061-
let min_feerate_kwu = ((prev_feerate as u64) * 25).div_ceil(24);
12062-
Ok(FeeRate::from_sat_per_kwu(min_feerate_kwu))
12063-
},
12069+
Some(prev_feerate) => Ok(min_rbf_feerate(prev_feerate)),
1206412070
None => Err(format!(
1206512071
"Channel {} has no prior feerate to compute RBF minimum",
1206612072
self.context.channel_id(),

lightning/src/ln/funding.rs

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -153,8 +153,8 @@ impl PriorContribution {
153153
/// prior contribution logic internally — reusing an adjusted prior when possible, re-running
154154
/// coin selection when needed, or creating a fee-bump-only contribution.
155155
///
156-
/// Check [`FundingTemplate::min_rbf_feerate`] for the minimum feerate required (25/24 of
157-
/// the previous feerate). Use [`FundingTemplate::prior_contribution`] to inspect the prior
156+
/// Check [`FundingTemplate::min_rbf_feerate`] for the minimum feerate required (previous
157+
/// feerate + 25 sat/kwu). Use [`FundingTemplate::prior_contribution`] to inspect the prior
158158
/// contribution's parameters (e.g., [`FundingContribution::value_added`],
159159
/// [`FundingContribution::outputs`]) before deciding whether to reuse it via the RBF methods
160160
/// or build a fresh contribution with different parameters using the splice methods above.
@@ -167,7 +167,7 @@ pub struct FundingTemplate {
167167
/// transaction.
168168
shared_input: Option<Input>,
169169

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

@@ -2161,8 +2161,8 @@ mod tests {
21612161
// When the caller's max_feerate is below the minimum RBF feerate, rbf_sync should
21622162
// return Err(()).
21632163
let prior_feerate = FeeRate::from_sat_per_kwu(2000);
2164-
let min_rbf_feerate = FeeRate::from_sat_per_kwu(5000);
2165-
let max_feerate = FeeRate::from_sat_per_kwu(3000);
2164+
let min_rbf_feerate = FeeRate::from_sat_per_kwu(2025);
2165+
let max_feerate = FeeRate::from_sat_per_kwu(2020);
21662166

21672167
let prior = FundingContribution {
21682168
value_added: Amount::from_sat(50_000),
@@ -2175,7 +2175,7 @@ mod tests {
21752175
is_splice: true,
21762176
};
21772177

2178-
// max_feerate (3000) < min_rbf_feerate (5000).
2178+
// max_feerate (2020) < min_rbf_feerate (2025).
21792179
let template = FundingTemplate::new(
21802180
None,
21812181
Some(min_rbf_feerate),
@@ -2255,8 +2255,8 @@ mod tests {
22552255
// When the prior contribution's feerate is below the minimum RBF feerate and no
22562256
// holder balance is available, rbf_sync should run coin selection to add inputs that
22572257
// cover the higher RBF fee.
2258-
let min_rbf_feerate = FeeRate::from_sat_per_kwu(5000);
22592258
let prior_feerate = FeeRate::from_sat_per_kwu(2000);
2259+
let min_rbf_feerate = FeeRate::from_sat_per_kwu(2025);
22602260
let withdrawal = funding_output_sats(20_000);
22612261

22622262
let prior = FundingContribution {
@@ -2293,7 +2293,7 @@ mod tests {
22932293
fn test_rbf_sync_no_prior_fee_bump_only_runs_coin_selection() {
22942294
// When there is no prior contribution (e.g., acceptor), rbf_sync should run coin
22952295
// selection to add inputs for a fee-bump-only contribution.
2296-
let min_rbf_feerate = FeeRate::from_sat_per_kwu(5000);
2296+
let min_rbf_feerate = FeeRate::from_sat_per_kwu(2025);
22972297

22982298
let template =
22992299
FundingTemplate::new(Some(shared_input(100_000)), Some(min_rbf_feerate), None);
@@ -2315,7 +2315,7 @@ mod tests {
23152315
// When the prior contribution's feerate is below the minimum RBF feerate and no
23162316
// holder balance is available, rbf_sync should use the caller's max_feerate (not the
23172317
// prior's) for the resulting contribution.
2318-
let min_rbf_feerate = FeeRate::from_sat_per_kwu(5000);
2318+
let min_rbf_feerate = FeeRate::from_sat_per_kwu(2025);
23192319
let prior_max_feerate = FeeRate::from_sat_per_kwu(50_000);
23202320
let callers_max_feerate = FeeRate::from_sat_per_kwu(10_000);
23212321
let withdrawal = funding_output_sats(20_000);
@@ -2354,8 +2354,8 @@ mod tests {
23542354
// When splice_out_sync is called on a template with min_rbf_feerate set (user
23552355
// choosing a fresh splice-out instead of rbf_sync), coin selection should NOT run.
23562356
// Fees come from the channel balance.
2357-
let min_rbf_feerate = FeeRate::from_sat_per_kwu(5000);
2358-
let feerate = FeeRate::from_sat_per_kwu(5000);
2357+
let min_rbf_feerate = FeeRate::from_sat_per_kwu(2025);
2358+
let feerate = FeeRate::from_sat_per_kwu(2025);
23592359
let withdrawal = funding_output_sats(20_000);
23602360

23612361
let template =

0 commit comments

Comments
 (0)