Repository navigation
FundingBuilder adjusts a prior contribution against its old max_feerate rather than the caller's #4762
Description
When a FundingBuilder seeded from a template's prior contribution is built at a higher feerate (an RBF), it adjusts the prior contribution in place and only then stores the caller's max_feerate on the result. The adjustment itself still runs against the prior contribution's max_feerate, so a bump above the original ceiling fails or falls back even though the caller supplied a new ceiling at or above the requested feerate.
Observed at d7a9806.
Where
FundingBuilderInner::build_from_prior_contribution(lightning/src/ln/funding.rs, around line 1475):contribution.for_initiator_at_feerate(self.feerate, ..)runs first;adjusted.max_feerate = self.max_feerateis applied in themapafterwards.FundingContribution::amend_without_coin_selection(around line 1074):new_contribution_at_current_feerate.at_feerate(target_feerate, .., true)runs first;max_feerateis overwritten on the next line.- Both go through
compute_feerate_adjustment(around line 1173), which returnsFeeRateAdjustmentError::FeeRateTooHighwhentarget_feerate > self.max_feerateand the fee at the target exceeds the contribution's current estimate. For an initiator adjusting the same inputs and outputs to a higher feerate, the fee always exceeds the current estimate, so the error is certain once the target passes the old maximum.
max_feerate is documented as the highest feerate we tolerate if we end up as the acceptor (FundingTemplate::with_prior_contribution docs, FundingContribution::max_feerate), and the comment on the check in compute_feerate_adjustment describes the acceptor case. The channel's own call to for_initiator_at_feerate in maybe_adjust_for_rbf is an adjustment the user did not request, where that ceiling makes sense. The builder's RBF path is different: the caller asked for feerate and supplied max_feerate >= feerate, which validate_contribution_parameters has already checked before the adjustment runs.
Effect by contribution type
- Manually selected inputs:
buildreturnsManuallySelectedInputsInsufficient(there is no fallback for this mode), although the inputs could pay the higher fee by adding less to the channel, which is exactly what the in-place adjustment would do. In ldk-node,splice_in_with_allnow builds such a contribution, andbump_channel_funding_feeat a target above 1.5x the original estimate failed with this error (Buildsplice_in_with_allfrom manually selected inputs ldk-node#1176). ldk-node works around it by rebuilding viawithout_prior_contribution(lightningdevkit/ldk-node@c875410), which loses the record of inherited inputs. - Coin-selected inputs: the error maps to
MissingCoinSelectionSourceand the async/sync builders fall back to coin selection, re-selecting inputs and creating a new change output even when the existing change could absorb the fee. - Splice-out with no inputs: the same fallback "changes the funding source: wallet inputs are selected to cover the outputs and fees instead of deducting them from the channel balance" (
rbf_prior_contributiondocs). A bump above the original ceiling silently starts spending the wallet.
Reproduction
With ldk-node at the PR above, before its workaround commit: splice_in_with_all at 1000 sat/kwu, then bump_channel_funding_fee at 2000 sat/kwu returns ChannelSplicingFailed, and the node log shows "Failed to RBF channel: The request cannot be satisfied using the manually selected inputs". A unit test in funding.rs would be: build a manual-input contribution with max_feerate 1.5x its feerate, put it in a template as the prior with a min_rbf_feerate, then with_prior_contribution(2x, 3x).build(); it returns ManuallySelectedInputsInsufficient instead of a contribution at the higher feerate.
Proposed fix
Set the caller's max_feerate on the prior contribution (or on new_contribution_at_current_feerate) before calling the initiator adjustment, in both places. validate_contribution_parameters guarantees feerate <= max_feerate, so the ceiling check then passes for the initiator path while the fee-buffer and spliceable-balance checks still apply. Alternatively, skip the ceiling check when is_initiator is set and the call comes from the builder, leaving maybe_adjust_for_rbf's behavior unchanged.
Activity
jkczyz commentedon Oct 19, 2026
https://git.rust-bitcoin.org/lightningdevkit/rust-lightning/issues/5282