@@ -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.
128209type mockAddressManager struct {
0 commit comments