What changed, and why it matters
This commit fixes a small but real bug in LND's channel-funding code. When a funding request referred to a reservation that no longer existed, the code sent an error message but forgot to send the matching 'completion' signal. A caller waiting on both channels could hang or misbehave. The fix adds the missing completion signal and includes a regression test. There is no claim in the commit that this is a security issue, but incomplete error handling in wallet code can sometimes be abused to cause denial of service.
Treat as a routine bug fix with possible denial-of-service side effects. Review whether any callers rely on both channels being populated and whether the missing signal was reachable in production (e.g., via race or double-processing of funding messages). No immediate security response is indicated by the commit itself, but the fix should be included in the next release.
Security signals we found
Incomplete error-path signaling in wallet funding flow
Potential channel hang / goroutine leak on missing reservation
Regression test added for missing-reservation response
No explicit security framing by vendor
Evidence from the diff
In lnwallet/wallet.go, handleFundingCounterPartySigs() handles an addCounterPartySigsMsg. If the pending funding ID is not found in fundingLimbo, it previously wrote an error to msg.err but never wrote to msg.completeChan. The patch adds ‘msg.completeChan <- nil’ so both result channels are satisfied. A unit test verifies that both channels receive a value (nil on completeChan, an error on errChan) when the reservation is missing. This is a straightforward completion of error-path signaling.
Changed components
lnwallet/wallet.goLightningWallet.handleFundingCounterPartySigschannel funding reservation handlingInspect captured patch +25 / −0
### lnwallet/wallet.go
@@ -2257,6 +2257,7 @@ func (l *LightningWallet) handleFundingCounterPartySigs(msg *addCounterPartySigs
l.limboMtx.RUnlock()
if !ok {
msg.err <- fmt.Errorf("attempted to update non-existent funding state")
+ msg.completeChan <- nil
return
}
### lnwallet/wallet_test.go
@@ -3,9 +3,33 @@ package lnwallet
import (
"testing"
+ "github.com/lightningnetwork/lnd/chanstate"
"github.com/stretchr/testify/require"
)
+// TestHandleFundingCounterPartySigsMissingReservation tests the missing
+// reservation response.
+func TestHandleFundingCounterPartySigsMissingReservation(t *testing.T) {
+ t.Parallel()
+
+ wallet := &LightningWallet{
+ fundingLimbo: make(map[uint64]*ChannelReservation),
+ }
+ completeChan := make(chan *chanstate.OpenChannel, 1)
+ errChan := make(chan error, 1)
+
+ wallet.handleFundingCounterPartySigs(&addCounterPartySigsMsg{
+ pendingFundingID: 1,
+ completeChan: completeChan,
+ err: errChan,
+ })
+
+ require.Len(t, completeChan, 1)
+ require.Nil(t, <-completeChan)
+ require.Len(t, errChan, 1)
+ require.ErrorContains(t, <-errChan, "non-existent funding state")
+}
+
// TestRegisterFundingIntent checks RegisterFundingIntent behaves as expected.
func TestRegisterFundingIntent(t *testing.T) {
t.Parallel()Why this scored 32/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.