Skip to content

Commit 666f19c

Browse files
accounts: associate payments before sending them
1 parent 32b2a58 commit 666f19c

File tree

4 files changed

+80
-4
lines changed

4 files changed

+80
-4
lines changed

accounts/checkers.go

+13
Original file line numberDiff line numberDiff line change
@@ -522,6 +522,7 @@ func checkSend(ctx context.Context, chainParams *chaincfg.Params,
522522
}
523523

524524
// The invoice is optional.
525+
var paymentHash lntypes.Hash
525526
if len(invoice) > 0 {
526527
payReq, err := zpay32.Decode(invoice, chainParams)
527528
if err != nil {
@@ -531,6 +532,10 @@ func checkSend(ctx context.Context, chainParams *chaincfg.Params,
531532
if payReq.MilliSat != nil && *payReq.MilliSat > sendAmt {
532533
sendAmt = *payReq.MilliSat
533534
}
535+
536+
if payReq.PaymentHash != nil {
537+
paymentHash = *payReq.PaymentHash
538+
}
534539
}
535540

536541
// We also add the max fee to the amount to check. This might mean that
@@ -549,6 +554,14 @@ func checkSend(ctx context.Context, chainParams *chaincfg.Params,
549554
return fmt.Errorf("error validating account balance: %w", err)
550555
}
551556

557+
emptyHash := lntypes.Hash{}
558+
if paymentHash != emptyHash {
559+
err = service.AssociatePayment(acct.ID, paymentHash, sendAmt)
560+
if err != nil {
561+
return fmt.Errorf("error associating payment: %w", err)
562+
}
563+
}
564+
552565
return nil
553566
}
554567

accounts/checkers_test.go

+6
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,12 @@ func (m *mockService) AssociateInvoice(id AccountID, hash lntypes.Hash) error {
6868
return nil
6969
}
7070

71+
func (m *mockService) AssociatePayment(id AccountID, paymentHash lntypes.Hash,
72+
amt lnwire.MilliSatoshi) error {
73+
74+
return nil
75+
}
76+
7177
func (m *mockService) TrackPayment(_ AccountID, hash lntypes.Hash,
7278
amt lnwire.MilliSatoshi) error {
7379

accounts/interface.go

+5-2
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,9 @@ type Service interface {
247247
// so we never need to debit the amount from the account.
248248
RemovePayment(hash lntypes.Hash) error
249249

250-
// IsRunning returns true if the service can be used.
251-
IsRunning() bool
250+
// AssociatePayment associates a payment (hash) with the given account,
251+
// ensuring that the payment will be tracked for a user when LiT is
252+
// restarted.
253+
AssociatePayment(id AccountID, paymentHash lntypes.Hash,
254+
fullAmt lnwire.MilliSatoshi) error
252255
}

accounts/service.go

+56-2
Original file line numberDiff line numberDiff line change
@@ -416,6 +416,40 @@ func (s *InterceptorService) AssociateInvoice(id AccountID,
416416
return s.store.UpdateAccount(account)
417417
}
418418

419+
// AssociatePayment associates a payment (hash) with the given account,
420+
// ensuring that the payment will be tracked for a user when LiT is
421+
// restarted.
422+
func (s *InterceptorService) AssociatePayment(id AccountID,
423+
paymentHash lntypes.Hash, fullAmt lnwire.MilliSatoshi) error {
424+
425+
s.Lock()
426+
defer s.Unlock()
427+
428+
account, err := s.store.Account(id)
429+
if err != nil {
430+
return err
431+
}
432+
433+
// If the payment is already associated with the account, we don't need
434+
// to associate it again.
435+
_, ok := account.Payments[paymentHash]
436+
if ok {
437+
return nil
438+
}
439+
440+
// Associate the payment with the account and store it.
441+
account.Payments[paymentHash] = &PaymentEntry{
442+
Status: lnrpc.Payment_UNKNOWN,
443+
FullAmount: fullAmt,
444+
}
445+
446+
if err := s.store.UpdateAccount(account); err != nil {
447+
return fmt.Errorf("error updating account: %w", err)
448+
}
449+
450+
return nil
451+
}
452+
419453
// invoiceUpdate credits the account an invoice was registered with, in case the
420454
// invoice was settled.
421455
//
@@ -527,13 +561,33 @@ func (s *InterceptorService) TrackPayment(id AccountID, hash lntypes.Hash,
527561
return nil
528562
}
529563

530-
// Okay, we haven't tracked this payment before. So let's now associate
531-
// the account with it.
532564
account.Payments[hash] = &PaymentEntry{
533565
Status: lnrpc.Payment_UNKNOWN,
534566
FullAmount: fullAmt,
535567
}
568+
536569
if err := s.store.UpdateAccount(account); err != nil {
570+
if !ok {
571+
// In the rare case that the payment isn't associated
572+
// with an account yet, and we fail to update the
573+
// account we will not be tracking the payment, even if
574+
// track the service is restarted. Therefore the node
575+
// runner needs to manually check if the payment was
576+
// made and debit the account if that's the case.
577+
errStr := "critical error: failed to store the " +
578+
"payment with hash %v for user with account " +
579+
"id %v. Manual intervention required! " +
580+
"Verify if the payment was executed, and " +
581+
"manually update the user account balance by " +
582+
"subtracting the payment amount if it was"
583+
584+
mainChanErr := s.disableAndErrorfUnsafe(
585+
errStr, hash, id,
586+
)
587+
588+
s.mainErrCallback(mainChanErr)
589+
}
590+
537591
return fmt.Errorf("error updating account: %w", err)
538592
}
539593

0 commit comments

Comments
 (0)