@@ -32,10 +32,12 @@ type mockLnd struct {
3232 invoiceReq chan lndclient.InvoiceSubscriptionRequest
3333 paymentReq chan lntypes.Hash
3434
35- callErr error
36- errChan chan error
37- invoiceChan chan * lndclient.Invoice
38- paymentChans map [lntypes.Hash ]chan lndclient.PaymentStatus
35+ invoiceSubscriptionErr error
36+ trackPaymentErr error
37+ invoiceErrChan chan error
38+ paymentErrChan chan error
39+ invoiceChan chan * lndclient.Invoice
40+ paymentChans map [lntypes.Hash ]chan lndclient.PaymentStatus
3941}
4042
4143func newMockLnd () * mockLnd {
@@ -44,9 +46,10 @@ func newMockLnd() *mockLnd {
4446 invoiceReq : make (
4547 chan lndclient.InvoiceSubscriptionRequest , 10 ,
4648 ),
47- paymentReq : make (chan lntypes.Hash , 10 ),
48- errChan : make (chan error , 10 ),
49- invoiceChan : make (chan * lndclient.Invoice ),
49+ paymentReq : make (chan lntypes.Hash , 10 ),
50+ invoiceErrChan : make (chan error , 10 ),
51+ paymentErrChan : make (chan error , 10 ),
52+ invoiceChan : make (chan * lndclient.Invoice ),
5053 paymentChans : make (
5154 map [lntypes.Hash ]chan lndclient.PaymentStatus ,
5255 ),
@@ -72,6 +75,16 @@ func (m *mockLnd) assertMainErr(t *testing.T, expectedErr error) {
7275 }
7376}
7477
78+ func (m * mockLnd ) assertMainErrContains (t * testing.T , expectedStr string ) {
79+ select {
80+ case err := <- m .mainErrChan :
81+ require .ErrorContains (t , err , expectedStr )
82+
83+ case <- time .After (testTimeout ):
84+ t .Fatalf ("Did not get expected main err before timeout" )
85+ }
86+ }
87+
7588func (m * mockLnd ) assertNoInvoiceRequest (t * testing.T ) {
7689 select {
7790 case req := <- m .invoiceReq :
@@ -132,28 +145,28 @@ func (m *mockLnd) SubscribeInvoices(_ context.Context,
132145 req lndclient.InvoiceSubscriptionRequest ) (<- chan * lndclient.Invoice ,
133146 <- chan error , error ) {
134147
135- if m .callErr != nil {
136- return nil , nil , m .callErr
148+ if m .invoiceSubscriptionErr != nil {
149+ return nil , nil , m .invoiceSubscriptionErr
137150 }
138151
139152 m .invoiceReq <- req
140153
141- return m .invoiceChan , m .errChan , nil
154+ return m .invoiceChan , m .invoiceErrChan , nil
142155}
143156
144157// TrackPayment picks up a previously started payment and returns a payment
145158// update stream and an error stream.
146159func (m * mockLnd ) TrackPayment (_ context.Context ,
147160 hash lntypes.Hash ) (chan lndclient.PaymentStatus , chan error , error ) {
148161
149- if m .callErr != nil {
150- return nil , nil , m .callErr
162+ if m .trackPaymentErr != nil {
163+ return nil , nil , m .trackPaymentErr
151164 }
152165
153166 m .paymentReq <- hash
154167 m .paymentChans [hash ] = make (chan lndclient.PaymentStatus , 1 )
155168
156- return m .paymentChans [hash ], m .errChan , nil
169+ return m .paymentChans [hash ], m .paymentErrChan , nil
157170}
158171
159172// TestAccountService tests that the account service can track payments and
@@ -169,15 +182,92 @@ func TestAccountService(t *testing.T) {
169182 validate func (t * testing.T , lnd * mockLnd ,
170183 s * InterceptorService )
171184 }{{
172- name : "startup err on tracking payment " ,
185+ name : "startup err on invoice subscription " ,
173186 setup : func (t * testing.T , lnd * mockLnd , s * InterceptorService ) {
174- lnd .callErr = testErr
187+ lnd .invoiceSubscriptionErr = testErr
175188 },
176189 startupErr : testErr .Error (),
177190 validate : func (t * testing.T , lnd * mockLnd ,
178191 s * InterceptorService ) {
179192
180193 lnd .assertNoInvoiceRequest (t )
194+ require .False (t , s .IsRunning ())
195+ },
196+ }, {
197+ name : "err on invoice update" ,
198+ setup : func (t * testing.T , lnd * mockLnd , s * InterceptorService ) {
199+ acct := & OffChainBalanceAccount {
200+ ID : testID ,
201+ Type : TypeInitialBalance ,
202+ CurrentBalance : 1234 ,
203+ Invoices : AccountInvoices {
204+ testHash : {},
205+ },
206+ }
207+
208+ err := s .store .UpdateAccount (acct )
209+ require .NoError (t , err )
210+ },
211+ validate : func (t * testing.T , lnd * mockLnd ,
212+ s * InterceptorService ) {
213+
214+ // Start by closing the store. This should cause an
215+ // error once we make an invoice update, as the service
216+ // will fail when persisting the invoice update.
217+ s .store .Close ()
218+
219+ // Ensure that the service was started successfully and
220+ // still running though, despite the closing of the
221+ // db store.
222+ require .True (t , s .IsRunning ())
223+
224+ // Now let's send the invoice update, which should fail.
225+ lnd .invoiceChan <- & lndclient.Invoice {
226+ AddIndex : 12 ,
227+ SettleIndex : 12 ,
228+ Hash : testHash ,
229+ AmountPaid : 777 ,
230+ State : invpkg .ContractSettled ,
231+ }
232+
233+ // Ensure that the service was eventually disabled.
234+ assertEventually (t , func () bool {
235+ isRunning := s .IsRunning ()
236+ return isRunning == false
237+ })
238+ lnd .assertMainErrContains (t , "database not open" )
239+ },
240+ }, {
241+ name : "err in invoice err channel" ,
242+ setup : func (t * testing.T , lnd * mockLnd , s * InterceptorService ) {
243+ acct := & OffChainBalanceAccount {
244+ ID : testID ,
245+ Type : TypeInitialBalance ,
246+ CurrentBalance : 1234 ,
247+ Invoices : AccountInvoices {
248+ testHash : {},
249+ },
250+ }
251+
252+ err := s .store .UpdateAccount (acct )
253+ require .NoError (t , err )
254+ },
255+ validate : func (t * testing.T , lnd * mockLnd ,
256+ s * InterceptorService ) {
257+ // Ensure that the service was started successfully.
258+ require .True (t , s .IsRunning ())
259+
260+ // Now let's send an error over the invoice error
261+ // channel. This should disable the service.
262+ lnd .invoiceErrChan <- testErr
263+
264+ // Ensure that the service was eventually disabled.
265+ assertEventually (t , func () bool {
266+ isRunning := s .IsRunning ()
267+ return isRunning == false
268+ })
269+
270+ lnd .assertMainErrContains (t , testErr .Error ())
181271 },
182272 }, {
183273 name : "goroutine err sent on main err chan" ,
@@ -195,7 +285,7 @@ func TestAccountService(t *testing.T) {
195285 err := s .store .UpdateAccount (acct )
196286 require .NoError (t , err )
197287
198- lnd .errChan <- testErr
288+ lnd .mainErrChan <- testErr
199289 },
200290 validate : func (t * testing.T , lnd * mockLnd ,
201291 s * InterceptorService ) {
@@ -227,6 +317,135 @@ func TestAccountService(t *testing.T) {
227317 lnd .assertNoPaymentRequest (t )
228318 lnd .assertInvoiceRequest (t , 0 , 0 )
229319 lnd .assertNoMainErr (t )
320+ require .True (t , s .IsRunning ())
321+ },
322+ }, {
323+ name : "startup err on payment tracking" ,
324+ setup : func (t * testing.T , lnd * mockLnd , s * InterceptorService ) {
325+ acct := & OffChainBalanceAccount {
326+ ID : testID ,
327+ Type : TypeInitialBalance ,
328+ CurrentBalance : 1234 ,
329+ Invoices : AccountInvoices {
330+ testHash : {},
331+ },
332+ Payments : AccountPayments {
333+ testHash : {
334+ Status : lnrpc .Payment_IN_FLIGHT ,
335+ FullAmount : 1234 ,
336+ },
337+ },
338+ }
339+
340+ err := s .store .UpdateAccount (acct )
341+ require .NoError (t , err )
342+
343+ lnd .trackPaymentErr = testErr
344+ },
345+ validate : func (t * testing.T , lnd * mockLnd ,
346+ s * InterceptorService ) {
347+
348+ // Assert that the invoice subscription succeeded.
349+ require .Contains (t , s .invoiceToAccount , testHash )
350+
351+ // But setting up the payment tracking should have failed.
352+ require .False (t , s .IsRunning ())
353+
354+ // Finally let's assert that we didn't successfully add the
355+ // payment to pending payment, and that lnd isn't awaiting
356+ // the payment request.
357+ require .NotContains (t , s .pendingPayments , testHash )
358+ lnd .assertNoPaymentRequest (t )
359+ },
360+ }, {
361+ name : "err on payment update" ,
362+ setup : func (t * testing.T , lnd * mockLnd , s * InterceptorService ) {
363+ acct := & OffChainBalanceAccount {
364+ ID : testID ,
365+ Type : TypeInitialBalance ,
366+ CurrentBalance : 1234 ,
367+ Payments : AccountPayments {
368+ testHash : {
369+ Status : lnrpc .Payment_IN_FLIGHT ,
370+ FullAmount : 1234 ,
371+ },
372+ },
373+ }
374+
375+ err := s .store .UpdateAccount (acct )
376+ require .NoError (t , err )
377+ },
378+ validate : func (t * testing.T , lnd * mockLnd ,
379+ s * InterceptorService ) {
380+ // Ensure that the service was started successfully,
381+ // and lnd contains the payment request.
382+ require .True (t , s .IsRunning ())
383+ lnd .assertPaymentRequests (t , map [lntypes.Hash ]struct {}{
384+ testHash : {},
385+ })
386+
387+ // Now let's wipe the service's pending payments.
388+ // This will cause an error send an update over
389+ // the payment channel, which should disable the
390+ // service.
391+ s .pendingPayments = make (map [lntypes.Hash ]* trackedPayment )
392+
393+ // Send an invalid payment over the payment chan
394+ // which should error and disable the service
395+ lnd .paymentChans [testHash ] <- lndclient.PaymentStatus {
396+ State : lnrpc .Payment_SUCCEEDED ,
397+ Fee : 234 ,
398+ Value : 1000 ,
399+ }
400+
401+ // Ensure that the service was eventually disabled.
402+ assertEventually (t , func () bool {
403+ isRunning := s .IsRunning ()
404+ return isRunning == false
405+ })
406+ lnd .assertMainErrContains (
407+ t , "not mapped to any account" ,
408+ )
409+
410+ },
411+ }, {
412+ name : "err in payment update chan" ,
413+ setup : func (t * testing.T , lnd * mockLnd , s * InterceptorService ) {
414+ acct := & OffChainBalanceAccount {
415+ ID : testID ,
416+ Type : TypeInitialBalance ,
417+ CurrentBalance : 1234 ,
418+ Payments : AccountPayments {
419+ testHash : {
420+ Status : lnrpc .Payment_IN_FLIGHT ,
421+ FullAmount : 1234 ,
422+ },
423+ },
424+ }
425+
426+ err := s .store .UpdateAccount (acct )
427+ require .NoError (t , err )
428+ },
429+ validate : func (t * testing.T , lnd * mockLnd ,
430+ s * InterceptorService ) {
431+ // Ensure that the service was started successfully,
432+ // and lnd contains the payment request.
433+ require .True (t , s .IsRunning ())
434+ lnd .assertPaymentRequests (t , map [lntypes.Hash ]struct {}{
435+ testHash : {},
436+ })
437+
438+ // Now let's send an error over the payment error
439+ // channel. This should disable the service.
440+ lnd .paymentErrChan <- testErr
441+
442+ // Ensure that the service was eventually disabled.
443+ assertEventually (t , func () bool {
444+ isRunning := s .IsRunning ()
445+ return isRunning == false
446+ })
447+
448+ lnd .assertMainErrContains (t , testErr .Error ())
230449 },
231450 }, {
232451 name : "startup track in-flight payments" ,
0 commit comments