Skip to content

funding: wait for commitment restoration before channel_ready - #11288

Open
Amirhosein wants to merge 2 commits into
lightningnetwork:masterfrom
Amirhosein:fix/funding-discovery-race-11259
Open

Amirhosein wants to merge 2 commits into
lightningnetwork:masterfrom
Amirhosein:fix/funding-discovery-race-11259

Conversation

@Amirhosein

@Amirhosein Amirhosein commented Oct 1, 2026 •

Copy link
Copy Markdown

Change Description

I would like to propose this fix for #11259, reordering the release of discoverySignal relative to commitment restoration during channel opening.

Background & Root Cause

During channel opening, if an incoming channel_ready is processed before the funding goroutine finishes calling lnwallet.NewLightningChannel(...), a link can be instantiated and process commitment updates (e.g. update_add_htlc + commitment_signed). This can write an unacknowledged remote commitment diff to disk.

When the concurrent funding goroutine runs restoreCommitState, it fetches the unacked remote commitment diff from the database via RemoteCommitChainTip(). However, the in-memory channel copy passed to NewLightningChannel still has RemoteNextRevocation == nil (since processChannelReady has not populated it yet). Dereferencing this nil revocation point during DeriveCommitmentKeys causes a latent nil-pointer panic.

Changes

  1. Deferred Discovery Signaling: Removed the premature signal closes in handleFundingConfirmation and advancePendingChannelState. I moved the release of discoverySignal to occur immediately after lnwallet.NewLightningChannel returns in advanceFundingState. This covers both regular confirmed channels and zero-confirmation channels in a unified location.
  2. Restart Barrier Restoration: In Manager.start(), I restored the barrier for channels where channel.IsPending || channel.RemoteNextRevocation == nil. If a node restarted after MarkAsOpen was persisted to the database but before commitment restoration or channel_ready processing completed, this ensures an early channel_ready from a reconnected peer cannot bypass the restoration barrier.
  3. Restoration Failure Handling: If lnwallet.NewLightningChannel returns an error, I ensure discoverySignal is closed (with an appropriate warning log) before returning, preventing queued peer messages from hanging indefinitely until daemon shutdown.
  4. Regression Tests: Added funding/manager_discovery_test.go with 8 test cases covering both confirmed and zero-conf channels, with and without restart, and across successful and failed restoration paths. The tests use a custom aux leaf-store hook to reliably pause restoration and verify that peer channel_ready messages remain queued and no link is started until restoration completes.
  5. Release Notes: Added a release note entry under # Bug Fixes in docs/release-notes/release-notes-0.22.0.md.

Fixes #11259.

Steps to Test

Reviewers can run the unit tests and targeted integration tests:

# Run the funding unit tests
make unit pkg=funding timeout=5m

# Run the new regression test under the race detector
go test -race -tags='dev nolog lowscrypt' ./funding -run '^TestFundingManagerDiscoverySignal$' -count=10 -timeout=2m

# Targeted integration tests for channel opening and immediate payments
make itest icase='(immediate_payment_after_channel_opened|zero_conf_channel_open)' timeout=10m

I have verified all 8 test cases locally with -race and 10 repetitions, and verified that without the fix the assertions fail as expected.

Pull Request Checklist

Testing

  • Your PR passes all CI checks.
  • Tests covering the positive and negative (error paths) are included.
  • Bug fixes contain tests triggering the bug to prevent regressions.

Code Style and Documentation

Thank you very much for taking the time to review this PR. I would welcome any feedback or suggestions on the approach!

I moved discovery signaling after NewLightningChannel returns so an early
channel_ready cannot start a link during commitment restoration. I also
restore the barrier after a restart when MarkAsOpen has been persisted but
channel_ready has not yet been processed.

I release the signal after restoration errors as well, since the funding
goroutine will no longer read the commitment chain. This lets queued peer
messages proceed rather than leaving them blocked until shutdown.

I added regression coverage for confirmed and zero-conf openings, both
with and without restart, and successful and failed restoration.

Fixes lightningnetwork#11259.
I added a release note for the channel-opening race fix, including restart handling and release of queued channel_ready messages after restoration errors.
@Amirhosein
Amirhosein marked this pull request as ready for review October 1, 2026 06:48
@github-actions github-actions Bot added the severity-critical Requires expert review - security/consensus critical label Oct 1, 2026
@github-actions

github-actions Bot commented Oct 1, 2026

Copy link
Copy Markdown

🔴 PR Severity: CRITICAL

gh pr view | 3 files | 353 lines changed

🔴 Critical (1 file)
  • funding/manager.go - channel funding workflow coordination; changes the ordering of discovery signaling and commitment restoration relative to channel_ready
🟢 Low (2 files)
  • funding/manager_discovery_test.go - new regression test file
  • docs/release-notes/release-notes-0.22.0.md - release notes entry

Analysis

The only non-test, non-doc file touched is funding/manager.go, which sits in the funding/* package (channel funding workflow coordination) and is classified CRITICAL per the severity rules. The change itself is subtle: it reorders when discoverySignal is released relative to commitment-state restoration from a channel backup, to avoid a race where an early channel_ready lets a link process a commitment update before RemoteNextRevocation is restored. This directly affects channel-open correctness and crash-safety on restart, so it warrants expert review of the funding manager's state machine and barrier/signal handling, despite the overall diff being modest in size (no severity bump triggered — single critical package, well under the file/line thresholds).


To override, add a severity-override-{critical,high,medium,low} label.

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

Labels

severity-critical Requires expert review - security/consensus critical

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Channel-open race with latent nil dereference

1 participant