diff --git a/staticaddr/openchannel/manager.go b/staticaddr/openchannel/manager.go index cb276cfc..2274ce50 100644 --- a/staticaddr/openchannel/manager.go +++ b/staticaddr/openchannel/manager.go @@ -458,9 +458,7 @@ func (m *Manager) openChannelPsbt(ctx context.Context, log.Infof("Starting PSBT funding flow with pending channel ID %x.\n", pendingChanID) - // maybeCancelShim is a helper function that cancels the funding shim - // with the RPC server in case we end up aborting early. - maybeCancelShim := func() { + defer func() { shimMu.Lock() defer shimMu.Unlock() @@ -478,15 +476,14 @@ func (m *Manager) openChannelPsbt(ctx context.Context, }, } _, err := m.cfg.LightningClient.FundingStateStep( - ctx, cancelMsg, + context.WithoutCancel(ctx), cancelMsg, ) if err != nil { log.Errorf("Error canceling shim: %v\n", err) } shimPending = false } - } - defer maybeCancelShim() + }() // Create the PSBT funding shim that will tell the funding manager we // want to use a PSBT. diff --git a/staticaddr/openchannel/manager_test.go b/staticaddr/openchannel/manager_test.go index ccb30237..2a6edb86 100644 --- a/staticaddr/openchannel/manager_test.go +++ b/staticaddr/openchannel/manager_test.go @@ -608,9 +608,10 @@ type mockLndClient struct { rawClient lnrpc.LightningClient - mu sync.Mutex - fundingStepIdx int - fundingStepErr error + mu sync.Mutex + fundingStepIdx int + fundingStepErr error + fundingStepCtxErrs []error } func (m *mockLndClient) RawClientWithMacAuth( @@ -620,13 +621,14 @@ func (m *mockLndClient) RawClientWithMacAuth( return ctx, 0, m.rawClient } -func (m *mockLndClient) FundingStateStep(_ context.Context, +func (m *mockLndClient) FundingStateStep(ctx context.Context, _ *lnrpc.FundingTransitionMsg) (*lnrpc.FundingStateStepResp, error) { m.mu.Lock() defer m.mu.Unlock() m.fundingStepIdx++ + m.fundingStepCtxErrs = append(m.fundingStepCtxErrs, ctx.Err()) return &lnrpc.FundingStateStepResp{}, m.fundingStepErr } @@ -746,6 +748,43 @@ func TestStreamOpenError(t *testing.T) { // Verify that the shim was canceled via FundingStateStep. lnClient.mu.Lock() require.Equal(t, 1, lnClient.fundingStepIdx) + require.Len(t, lnClient.fundingStepCtxErrs, 1) + require.NoError(t, lnClient.fundingStepCtxErrs[0]) + lnClient.mu.Unlock() +} + +// TestStreamOpenErrorWithCanceledContext verifies that deferred shim cleanup +// still uses a live context even if the caller canceled the original request. +func TestStreamOpenErrorWithCanceledContext(t *testing.T) { + t.Parallel() + + mockRaw := &mockRawLnrpcClient{ + openErr: errors.New("connection refused"), + } + lnClient := &mockLndClient{rawClient: mockRaw} + + manager := &Manager{ + cfg: &Config{ + LightningClient: lnClient, + ChainParams: &chaincfg.RegressionNetParams, + }, + } + + ctx, cancel := context.WithCancel(context.Background()) + cancel() + + req := &lnrpc.OpenChannelRequest{ + LocalFundingAmount: 100000, + MinConfs: defaultUtxoMinConf, + } + + _, err := manager.openChannelPsbt(ctx, req, nil, 0) + require.ErrorContains(t, err, "opening stream to server failed") + + lnClient.mu.Lock() + require.Equal(t, 1, lnClient.fundingStepIdx) + require.Len(t, lnClient.fundingStepCtxErrs, 1) + require.NoError(t, lnClient.fundingStepCtxErrs[0]) lnClient.mu.Unlock() }