Skip to content

Commit ac53fbd

Browse files
committed
staticaddr: surface invoice subscription errors
- propagate invoice subscription errors in MonitorInvoiceAndHtlcTx via HandleError instead of silently logging - add regression tests covering HTLC conf re-registration and invoice subscription error propagation
1 parent 5f2151a commit ac53fbd

File tree

2 files changed

+83
-0
lines changed

2 files changed

+83
-0
lines changed

staticaddr/loopin/actions.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -695,6 +695,8 @@ func (f *FSM) MonitorInvoiceAndHtlcTxAction(ctx context.Context,
695695
case err = <-invoiceErrChan:
696696
f.Errorf("invoice subscription error: %v", err)
697697

698+
return f.HandleError(err)
699+
698700
case <-ctx.Done():
699701
return f.HandleError(ctx.Err())
700702
}

staticaddr/loopin/actions_test.go

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,87 @@ func TestMonitorInvoiceAndHtlcTxReRegistersOnConfErr(t *testing.T) {
123123
}
124124
}
125125

126+
// TestMonitorInvoiceAndHtlcTxInvoiceErr asserts that invoice subscription
127+
// errors surface through the FSM as OnError so we don't silently hang.
128+
func TestMonitorInvoiceAndHtlcTxInvoiceErr(t *testing.T) {
129+
ctx, cancel := context.WithCancel(t.Context())
130+
defer cancel()
131+
132+
mockLnd := test.NewMockLnd()
133+
134+
clientKey, err := btcec.NewPrivateKey()
135+
require.NoError(t, err)
136+
serverKey, err := btcec.NewPrivateKey()
137+
require.NoError(t, err)
138+
139+
swapHash := lntypes.Hash{9, 9, 9}
140+
141+
loopIn := &StaticAddressLoopIn{
142+
SwapHash: swapHash,
143+
HtlcCltvExpiry: 3_000,
144+
InitiationHeight: uint32(mockLnd.Height),
145+
InitiationTime: time.Now(),
146+
ProtocolVersion: version.ProtocolVersion_V0,
147+
ClientPubkey: clientKey.PubKey(),
148+
ServerPubkey: serverKey.PubKey(),
149+
PaymentTimeoutSeconds: 3_600,
150+
}
151+
loopIn.SetState(MonitorInvoiceAndHtlcTx)
152+
153+
mockLnd.Invoices[swapHash] = &lndclient.Invoice{
154+
Hash: swapHash,
155+
State: invoices.ContractOpen,
156+
}
157+
158+
cfg := &Config{
159+
AddressManager: &mockAddressManager{
160+
params: &address.Parameters{
161+
ClientPubkey: clientKey.PubKey(),
162+
ServerPubkey: serverKey.PubKey(),
163+
ProtocolVersion: version.ProtocolVersion_V0,
164+
},
165+
},
166+
ChainNotifier: mockLnd.ChainNotifier,
167+
DepositManager: &noopDepositManager{},
168+
InvoicesClient: mockLnd.LndServices.Invoices,
169+
LndClient: mockLnd.Client,
170+
ChainParams: mockLnd.ChainParams,
171+
}
172+
173+
f, err := NewFSM(ctx, loopIn, cfg, false)
174+
require.NoError(t, err)
175+
176+
resultChan := make(chan fsm.EventType, 1)
177+
go func() {
178+
resultChan <- f.MonitorInvoiceAndHtlcTxAction(ctx, nil)
179+
}()
180+
181+
var invSub *test.SingleInvoiceSubscription
182+
select {
183+
case invSub = <-mockLnd.SingleInvoiceSubcribeChannel:
184+
case <-time.After(time.Second):
185+
t.Fatalf("invoice subscription not registered")
186+
}
187+
188+
// Drain the initial HTLC confirmation registration so it doesn't block.
189+
select {
190+
case <-mockLnd.RegisterConfChannel:
191+
case <-time.After(time.Second):
192+
t.Fatalf("htlc conf registration not received")
193+
}
194+
195+
// Inject an invoice subscription error and expect the FSM to surface an
196+
// error event instead of silently logging it.
197+
invSub.Err <- errors.New("test invoice sub error")
198+
199+
select {
200+
case event := <-resultChan:
201+
require.Equal(t, fsm.OnError, event)
202+
case <-time.After(time.Second):
203+
t.Fatalf("expected invoice error to surface, got timeout")
204+
}
205+
}
206+
126207
// mockAddressManager is a minimal AddressManager implementation used by the
127208
// test FSM setup.
128209
type mockAddressManager struct {

0 commit comments

Comments
 (0)