Skip to content

Fix stale pre-splice announcement_signatures during reestablish#4577

Open
jkczyz wants to merge 2 commits intolightningdevkit:mainfrom
jkczyz:2026-04-splice-interop-fixes
Open

Fix stale pre-splice announcement_signatures during reestablish#4577
jkczyz wants to merge 2 commits intolightningdevkit:mainfrom
jkczyz:2026-04-splice-interop-fixes

Conversation

@jkczyz
Copy link
Copy Markdown
Contributor

@jkczyz jkczyz commented Apr 23, 2026

Two related issues surfaced during interop testing against Eclair's splicing implementation, both around stale announcement_signatures when splice_locked is implicit — inferred from my_current_funding_locked in channel_reestablish. Either could force-close an otherwise healthy channel.

  • Send side: the reestablish handler called get_announcement_sigs before processing the inferred splice_locked, so if announcement_sigs_state == NotSent it emitted sigs over the pre-splice short_channel_id. The peer, having already promoted, would fail verification and force-close.

  • Receive side: the announcement_signatures handler treated a mismatched short_channel_id as a signature verification failure and force-closed. BOLT7 does not require closing; a peer can legitimately retransmit pre-splice sigs around promotion.

jkczyz and others added 2 commits April 23, 2026 14:54
When a splice transaction confirms on both sides while peers are
disconnected, each peer's `channel_reestablish` carries
`my_current_funding_locked` with the splice txid. In the reestablish
handler, `get_announcement_sigs` was called before the inferred
`splice_locked` was processed and the splice was promoted, so
`self.funding` still pointed to the pre-splice scope. If
`announcement_sigs_state` was `NotSent`, the generated
`announcement_signatures` carried the pre-splice `short_channel_id`
and bitcoin key — which the peer (having already promoted via its own
inferred `splice_locked`) would verify against the post-splice
`UnsignedChannelAnnouncement`, failing the signature check and
force-closing.

Skip the pre-promotion call when `my_current_funding_locked` refers to
a pending splice funding. `maybe_promote_splice_funding` emits correct
post-splice signatures after the inferred `splice_locked` is
processed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
A peer may transmit `announcement_signatures` signed over a stale
`short_channel_id` — most plausibly a retransmission or a peer
implementation whose view hasn't caught up to our post-splice
promotion. Verifying such sigs against the current
`UnsignedChannelAnnouncement` (built from `self.funding`) always fails
the hash check, which previously produced a force-close.

BOLT lightningdevkit#7 does not require closing in this situation; the mismatch is
expected across splice handoffs. Short-circuit with
`ChannelError::Ignore` when `msg.short_channel_id` doesn't match the
current funding's scid, leaving the genuine invalid-signature paths
in place for sigs that actually target our current scid.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@ldk-reviews-bot
Copy link
Copy Markdown

ldk-reviews-bot commented Apr 23, 2026

👋 Thanks for assigning @wpaulino 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.

@jkczyz jkczyz self-assigned this Apr 23, 2026
@jkczyz jkczyz requested review from TheBlueMatt and wpaulino April 23, 2026 20:48
@ldk-claude-review-bot
Copy link
Copy Markdown
Collaborator

I've thoroughly reviewed the entire PR diff. The two code changes and two regression tests are logically sound and handle edge cases correctly:

  1. Send-side fix (lines 10328-10345): The splice_promotion_pending check correctly skips announcement_sigs when the counterparty's my_current_funding_locked refers to a pending splice. The inferred_splice_locked (computed later at lines 10530-10544) triggers maybe_promote_splice_funding, which generates correct post-splice sigs.

  2. Receive-side fix (lines 12159-12168): Using ChannelError::Ignore for SCID mismatches is correct — convert_channel_err_internal returns (false, from_chan_no_close(...)), so no force-close occurs.

  3. None SCID safety: When self.funding.get_short_channel_id() is None, the check causes all announcement_signatures to be ignored. This is safe because the caller already gates on is_usable() (line 13178), and a usable channel should always have a confirmed funding with a known SCID.

  4. Tests: Both regression tests exercise the intended scenarios and verify the channel remains operational afterward.

No issues found.

@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 23, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 87.09%. Comparing base (2313bd5) to head (97e9bc2).

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #4577   +/-   ##
=======================================
  Coverage   87.08%   87.09%           
=======================================
  Files         161      161           
  Lines      109255   109272   +17     
  Branches   109255   109272   +17     
=======================================
+ Hits        95147    95169   +22     
+ Misses      11627    11619    -8     
- Partials     2481     2484    +3     
Flag Coverage Δ
fuzzing-fake-hashes 30.89% <0.00%> (-0.09%) ⬇️
fuzzing-real-hashes 22.60% <55.55%> (-0.06%) ⬇️
tests 86.17% <100.00%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

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

Labels

None yet

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

3 participants