From fe9a3e103b73d43e21a3b0406fc66b450712af3d Mon Sep 17 00:00:00 2001 From: ziggie Date: Wed, 10 Dec 2025 16:42:52 +0100 Subject: [PATCH] funding: fix race in itest for zero-conf funding Fix a race condition where forwarding through a public zero-conf channel could fail with UnknownNextPeer when using the confirmed SCID. The issue occurred because ReportShortChanID (which updates the switch's baseIndex to handle the confirmed SCID) was called AFTER addToGraph (which announces the confirmed SCID to the network). With slow backends like postgres, addToGraph takes significant time, creating a window where other nodes learn about the confirmed SCID from gossip and attempt to route through it, but the receiving node's switch hasn't been updated yet to handle forwards using the confirmed SCID. The fix reorders operations to call ReportShortChanID before addToGraph, ensuring the switch is ready to handle the confirmed SCID before it's announced to the network. Forwards using either the alias or confirmed SCID will work since getLinkByMapping uses baseIndex to map both to the same link in forwardingIndex. Fixes flaky test: zero_conf-channel_policy_update_public_zero_conf --- funding/manager.go | 29 ++++++++++++++++------------- funding/manager_test.go | 18 ++++++++++-------- 2 files changed, 26 insertions(+), 21 deletions(-) diff --git a/funding/manager.go b/funding/manager.go index 5711ed8e2..81b673f56 100644 --- a/funding/manager.go +++ b/funding/manager.go @@ -3957,6 +3957,22 @@ func (f *Manager) waitForZeroConfChannel(c *channeldb.OpenChannel) error { // Six confirmations have been reached. If this channel is public, // we'll delete some of the alias mappings the gossiper uses. + // + // Tell the Switch to refresh the relevant ChannelLink so that forwards + // under the confirmed SCID are possible. We do this BEFORE updating the + // graph to avoid a race where other nodes learn about the confirmed + // SCID from gossip before our switch is ready to handle forwards using + // it. This is especially important for integration tests. + err = f.cfg.ReportShortChanID(c.FundingOutpoint) + if err != nil { + // This should only fail if the link is not found in the + // Switch's linkIndex map. If this is the case, then the peer + // has gone offline and the next time the link is loaded, it + // will have a refreshed state. Just log an error here. + log.Errorf("unable to report scid for zero-conf channel "+ + "channel: %v", err) + } + isPublic := c.ChannelFlags&lnwire.FFAnnounceChannel != 0 if isPublic { err = f.cfg.AliasManager.DeleteSixConfs(c.ShortChannelID) @@ -3985,19 +4001,6 @@ func (f *Manager) waitForZeroConfChannel(c *channeldb.OpenChannel) error { } } - // Since we have now marked down the confirmed SCID, we'll also need to - // tell the Switch to refresh the relevant ChannelLink so that forwards - // under the confirmed SCID are possible if this is a public channel. - err = f.cfg.ReportShortChanID(c.FundingOutpoint) - if err != nil { - // This should only fail if the link is not found in the - // Switch's linkIndex map. If this is the case, then the peer - // has gone offline and the next time the link is loaded, it - // will have a refreshed state. Just log an error here. - log.Errorf("unable to report scid for zero-conf channel "+ - "channel: %v", err) - } - // Update the confirmed transaction's label. f.makeLabelForTx(c) diff --git a/funding/manager_test.go b/funding/manager_test.go index 45b847ffd..26563c8c4 100644 --- a/funding/manager_test.go +++ b/funding/manager_test.go @@ -4726,14 +4726,9 @@ func testZeroConf(t *testing.T, chanType *lnwire.ChannelType) { assertConfirmationHeight(t, alice, chanID, 1) assertConfirmationHeight(t, bob, chanID, 1) - // For taproot channels, we don't expect them to be announced atm. - if !isTaprootChanType(chanType) { - assertChannelAnnouncements( - t, alice, bob, fundingAmt, nil, nil, nil, nil, - ) - } - - // Both Alice and Bob should send on reportScidChan. + // Both Alice and Bob should call ReportShortChanID first (before + // sending announcements) to avoid a race where other nodes learn about + // the confirmed SCID before the switch is ready. select { case <-alice.reportScidChan: case <-time.After(time.Second * 5): @@ -4746,6 +4741,13 @@ func testZeroConf(t *testing.T, chanType *lnwire.ChannelType) { t.Fatalf("did not call ReportShortChanID in time") } + // For taproot channels, we don't expect them to be announced atm. + if !isTaprootChanType(chanType) { + assertChannelAnnouncements( + t, alice, bob, fundingAmt, nil, nil, nil, nil, + ) + } + // Send along the 6-confirmation channel so that announcement sigs can // be exchanged. alice.mockNotifier.sixConfChannel <- &chainntnfs.TxConfirmation{