Skip to content

Persist negotiated splice candidates on reload#4653

Open
wpaulino wants to merge 1 commit into
lightningdevkit:mainfrom
wpaulino:pending-splice-persist
Open

Persist negotiated splice candidates on reload#4653
wpaulino wants to merge 1 commit into
lightningdevkit:mainfrom
wpaulino:pending-splice-persist

Conversation

@wpaulino
Copy link
Copy Markdown
Contributor

@wpaulino wpaulino commented Jun 1, 2026

Prior to supporting RBF, we would avoid persisting FundedChannel::pending_splice when there was a pending funding negotiation that could not be resumed on channel reestablishment. With the addition of RBF support, this would cause our previously negotiated splices (but still pending) to be dropped unintentionally.

Fixes the following payloads from #4636:

21ff19ffa2ffa200192222c21821b5b1ff5db1
21ff19ffa2ffa200192222c2193b00261ba3a3201b201821b5b1ff5db1
21ff19ffa2ffa200192222c2193b00201b20b11b203a1821b5b1ff5db1
21ff19ffa2ffa200192222c2191821b5b1ff5db1
0873a1ffa1101810b8ff
21ffffa219ffa2001922221821b5b1ff5db1
0873a1ffa1101810b6ff

Prior to supporting RBF, we would avoid persisting
`FundedChannel::pending_splice` when there was a pending funding
negotiation that could not be resumed on channel reestablishment. With
the addition of RBF support, this would cause our previously negotiated
splices (but still pending) to be dropped unintentionally.
@wpaulino wpaulino added this to the 0.3 milestone Jun 1, 2026
@wpaulino wpaulino requested a review from jkczyz June 1, 2026 22:12
@wpaulino wpaulino self-assigned this Jun 1, 2026
@ldk-reviews-bot
Copy link
Copy Markdown

ldk-reviews-bot commented Jun 1, 2026

👋 Thanks for assigning @jkczyz as a reviewer!
I'll wait for their review and will help manage the review process.
Once they submit their review, I'll check if a second reviewer would be helpful.

@ldk-claude-review-bot
Copy link
Copy Markdown
Collaborator

No issues found.

I thoroughly reviewed every hunk of the diff, including:

  • Wire compatibility: The change from impl_ser_tlv_based! to the custom PendingFundingWriteable/manual Readable impl maintains wire compatibility. Writing contributions as option wrapping an Iterable produces identical bytes to the previous optional_vec encoding (both write elements sequentially without a count prefix, both skip the TLV entry when empty).
  • Contributions trimming logic: When reset_funding_negotiation is true and funding_negotiation is Some, saturating_sub(1) correctly drops the last contribution (the in-progress RBF round's), mirroring what reset_pending_splice_state does at runtime. The saturating_sub safely handles the edge case of an acceptor with no contributions.
  • Filter logic: The updated filter correctly preserves pending_splice when there are negotiated candidates even if the negotiation must be reset, which is the core fix of this PR.
  • Event generation: ChannelManager::write() generates SpliceNegotiationFailed events via maybe_splice_funding_failed() before serialization, correctly producing the event the test expects after reload.
  • Test coverage: The new test properly exercises the scenario: complete a splice, start an RBF handshake, reload, verify the RBF is aborted (event) but the original candidate is preserved (prior_contribution assertion).
  • Debug assertions: The assert at lines 3043-3049 is trivially satisfied when reset_funding_negotiation is true (since funding_negotiation is set to None), but remains meaningful when false. This is correct by design.

Copy link
Copy Markdown
Contributor

@jkczyz jkczyz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Comment on lines +3057 to +3069
let contributions = if contributions_len == 0 {
None
} else {
Some(Iterable(self.pending_funding.contributions.iter().take(contributions_len)))
};
write_tlv_fields!(writer, {
(1, funding_negotiation, upgradable_option),
(3, self.pending_funding.negotiated_candidates, required_vec),
(5, self.pending_funding.sent_funding_txid, option),
(7, self.pending_funding.received_funding_txid, option),
(8, self.pending_funding.last_funding_feerate_sat_per_1000_weight, option),
(10, contributions, option),
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we simplify this using a slice?

(10, self.pending_funding.contributions[..contributions_len], optional_vec),

.pending_splice
.as_ref()
.filter(|pending_splice| {
!reset_pending_splice_state || !pending_splice.negotiated_candidates.is_empty()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider using !self.pending_funding().is_empty() to mirror the logic from reset_pending_splice_state.

@ldk-reviews-bot
Copy link
Copy Markdown

👋 The first review has been submitted!

Do you think this PR is ready for a second reviewer? If so, click here to assign a second reviewer.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants