Skip to content

Commit c386008

Browse files
committed
accounts: use UpsertAccountPayment for TrackPayment
Instead of useing UpdateAccount.
1 parent 340dc5d commit c386008

File tree

5 files changed

+125
-41
lines changed

5 files changed

+125
-41
lines changed

accounts/errors.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,9 @@ var (
88
ErrLabelAlreadyExists = errors.New(
99
"account label uniqueness constraint violation",
1010
)
11+
12+
// ErrAlreadySucceeded is returned by the UpsertAccountPayment method
13+
// if the WithErrAlreadySucceeded option is used and the payment has
14+
// already succeeded.
15+
ErrAlreadySucceeded = errors.New("payment has already succeeded")
1116
)

accounts/interface.go

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -237,11 +237,14 @@ type Store interface {
237237

238238
// UpsertAccountPayment updates or inserts a payment entry for the given
239239
// account. Various functional options can be passed to modify the
240-
// behavior of the method.
240+
// behavior of the method. The returned boolean is true if the payment
241+
// was already known before the update. This is to be treated as a
242+
// best-effort indication if an error is also returned since the method
243+
// may error before the boolean can be set correctly.
241244
UpsertAccountPayment(_ context.Context, id AccountID,
242245
paymentHash lntypes.Hash, fullAmount lnwire.MilliSatoshi,
243246
status lnrpc.Payment_PaymentStatus,
244-
options ...UpsertPaymentOption) error
247+
options ...UpsertPaymentOption) (bool, error)
245248

246249
// RemoveAccount finds an account by its ID and removes it from the¨
247250
// store.
@@ -332,16 +335,20 @@ type UpsertPaymentOption func(*upsertAcctPaymentOption)
332335
// upsertAcctPaymentOption is a struct that holds optional parameters for the
333336
// UpsertAccountPayment method.
334337
type upsertAcctPaymentOption struct {
335-
debitAccount bool
336-
errIfAlreadyPending bool
338+
debitAccount bool
339+
errIfAlreadyPending bool
340+
usePendingAmount bool
341+
errIfAlreadySucceeded bool
337342
}
338343

339344
// newUpsertPaymentOption creates a new upsertAcctPaymentOption with default
340345
// values.
341346
func newUpsertPaymentOption() *upsertAcctPaymentOption {
342347
return &upsertAcctPaymentOption{
343-
debitAccount: false,
344-
errIfAlreadyPending: false,
348+
debitAccount: false,
349+
errIfAlreadyPending: false,
350+
usePendingAmount: false,
351+
errIfAlreadySucceeded: false,
345352
}
346353
}
347354

@@ -362,3 +369,22 @@ func WithErrIfAlreadyPending() UpsertPaymentOption {
362369
o.errIfAlreadyPending = true
363370
}
364371
}
372+
373+
// WithErrIfAlreadySucceeded is a functional option that can be passed to the
374+
// UpsertAccountPayment method to indicate that the ErrAlreadySucceeded error
375+
// should be returned if the payment is already in a succeeded state.
376+
func WithErrIfAlreadySucceeded() UpsertPaymentOption {
377+
return func(o *upsertAcctPaymentOption) {
378+
o.errIfAlreadySucceeded = true
379+
}
380+
}
381+
382+
// WithPendingAmount is a functional option that can be passed to the
383+
// UpsertAccountPayment method to indicate that if the payment already exists,
384+
// then the known payment amount should be used instead of the new value passed
385+
// to the method.
386+
func WithPendingAmount() UpsertPaymentOption {
387+
return func(o *upsertAcctPaymentOption) {
388+
o.usePendingAmount = true
389+
}
390+
}

accounts/service.go

Lines changed: 19 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -510,10 +510,12 @@ func (s *InterceptorService) AssociatePayment(ctx context.Context, id AccountID,
510510
// If the payment is already associated with the account but not in
511511
// flight, we update the payment amount in case we have a zero-amount
512512
// invoice that is retried.
513-
return s.store.UpsertAccountPayment(
513+
_, err := s.store.UpsertAccountPayment(
514514
ctx, id, paymentHash, fullAmt, lnrpc.Payment_UNKNOWN,
515515
WithErrIfAlreadyPending(),
516516
)
517+
518+
return err
517519
}
518520

519521
// invoiceUpdate credits the account an invoice was registered with, in case the
@@ -607,34 +609,30 @@ func (s *InterceptorService) TrackPayment(ctx context.Context, id AccountID,
607609
return nil
608610
}
609611

610-
// Similarly, if we've already processed the payment in the past, there
611-
// is a reference in the account with the given state.
612-
account, err := s.store.Account(ctx, id)
613-
if err != nil {
614-
return fmt.Errorf("error fetching account: %w", err)
615-
}
616-
617612
// If the account already stored a terminal state, we also don't need to
618-
// track the payment again.
619-
entry, ok := account.Payments[hash]
620-
if ok && successState(entry.Status) {
621-
return nil
613+
// track the payment again. So we add the WithErrIfAlreadySucceeded
614+
// option to ensure that we return an error if the payment has already
615+
// succeeded. We can then match on the ErrAlreadySucceeded error and
616+
// exit early if it is returned.
617+
opts := []UpsertPaymentOption{
618+
WithErrIfAlreadySucceeded(),
622619
}
623620

624621
// There is a case where the passed in fullAmt is zero but the pending
625622
// amount is not. In that case, we should not overwrite the pending
626623
// amount.
627624
if fullAmt == 0 {
628-
fullAmt = entry.FullAmount
625+
opts = append(opts, WithPendingAmount())
629626
}
630627

631-
account.Payments[hash] = &PaymentEntry{
632-
Status: lnrpc.Payment_UNKNOWN,
633-
FullAmount: fullAmt,
634-
}
635-
636-
if err := s.store.UpdateAccount(ctx, account); err != nil {
637-
if !ok {
628+
known, err := s.store.UpsertAccountPayment(
629+
ctx, id, hash, fullAmt, lnrpc.Payment_UNKNOWN, opts...,
630+
)
631+
if err != nil {
632+
if errors.Is(err, ErrAlreadySucceeded) {
633+
return nil
634+
}
635+
if !known {
638636
// In the rare case that the payment isn't associated
639637
// with an account yet, and we fail to update the
640638
// account we will not be tracking the payment, even if
@@ -813,7 +811,7 @@ func (s *InterceptorService) paymentUpdate(ctx context.Context,
813811
fullAmount := status.Value + status.Fee
814812

815813
// Update the persisted account.
816-
err := s.store.UpsertAccountPayment(
814+
_, err := s.store.UpsertAccountPayment(
817815
ctx, pendingPayment.accountID, hash, fullAmount,
818816
lnrpc.Payment_SUCCEEDED, WithDebitAccount(),
819817
)

accounts/store_kvdb.go

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -258,22 +258,33 @@ func (s *BoltStore) IncreaseAccountBalance(_ context.Context, id AccountID,
258258

259259
// UpsertAccountPayment updates or inserts a payment entry for the given
260260
// account. Various functional options can be passed to modify the behavior of
261-
// the method.
261+
// the method. The returned boolean is true if the payment was already known
262+
// before the update. This is to be treated as a best-effort indication if an
263+
// error is also returned since the method may error before the boolean can be
264+
// set correctly.
262265
//
263266
// NOTE: This is part of the Store interface.
264267
func (s *BoltStore) UpsertAccountPayment(_ context.Context, id AccountID,
265268
paymentHash lntypes.Hash, fullAmount lnwire.MilliSatoshi,
266269
status lnrpc.Payment_PaymentStatus,
267-
options ...UpsertPaymentOption) error {
270+
options ...UpsertPaymentOption) (bool, error) {
268271

269272
opts := newUpsertPaymentOption()
270273
for _, o := range options {
271274
o(opts)
272275
}
273276

277+
var known bool
274278
update := func(account *OffChainBalanceAccount) error {
275-
entry, ok := account.Payments[paymentHash]
276-
if ok {
279+
var entry *PaymentEntry
280+
entry, known = account.Payments[paymentHash]
281+
if known {
282+
if opts.errIfAlreadySucceeded &&
283+
successState(entry.Status) {
284+
285+
return ErrAlreadySucceeded
286+
}
287+
277288
// If the errIfAlreadyPending option is set, we return
278289
// an error if the payment is already in-flight or
279290
// succeeded.
@@ -285,6 +296,10 @@ func (s *BoltStore) UpsertAccountPayment(_ context.Context, id AccountID,
285296
"(status %v)", paymentHash,
286297
account.Payments[paymentHash].Status)
287298
}
299+
300+
if opts.usePendingAmount {
301+
fullAmount = entry.FullAmount
302+
}
288303
}
289304

290305
account.Payments[paymentHash] = &PaymentEntry{
@@ -299,7 +314,7 @@ func (s *BoltStore) UpsertAccountPayment(_ context.Context, id AccountID,
299314
return nil
300315
}
301316

302-
return s.updateAccount(id, update)
317+
return known, s.updateAccount(id, update)
303318
}
304319

305320
func (s *BoltStore) updateAccount(id AccountID,

accounts/store_test.go

Lines changed: 49 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -284,22 +284,24 @@ func TestAccountUpdateMethods(t *testing.T) {
284284

285285
// Assert that calling the method for a non-existent account
286286
// errors out.
287-
err = store.UpsertAccountPayment(
287+
_, err = store.UpsertAccountPayment(
288288
ctx, AccountID{}, lntypes.Hash{}, 0,
289289
lnrpc.Payment_UNKNOWN,
290290
)
291291
require.ErrorIs(t, err, ErrAccNotFound)
292292

293293
// Add a payment to the account but don't update the balance.
294-
// We do add a WithErrIfAlreadyPending option here just to show
295-
// that no error is returned since the payment does not exist
296-
// yet.
294+
// We do add a WithErrIfAlreadyPending and
295+
// WithErrIfAlreadySucceeded option. here just to show that no
296+
// error is returned since the payment does not exist yet.
297297
hash1 := lntypes.Hash{1, 2, 3, 4}
298-
err = store.UpsertAccountPayment(
298+
known, err := store.UpsertAccountPayment(
299299
ctx, acct.ID, hash1, 600, lnrpc.Payment_UNKNOWN,
300300
WithErrIfAlreadyPending(),
301+
WithErrIfAlreadySucceeded(),
301302
)
302303
require.NoError(t, err)
304+
require.False(t, known)
303305

304306
assertBalanceAndPayments(1000, AccountPayments{
305307
hash1: &PaymentEntry{
@@ -311,10 +313,11 @@ func TestAccountUpdateMethods(t *testing.T) {
311313
// Add a second payment to the account and again don't update
312314
// the balance.
313315
hash2 := lntypes.Hash{5, 6, 7, 8}
314-
err = store.UpsertAccountPayment(
316+
known, err = store.UpsertAccountPayment(
315317
ctx, acct.ID, hash2, 100, lnrpc.Payment_UNKNOWN,
316318
)
317319
require.NoError(t, err)
320+
require.False(t, known)
318321

319322
assertBalanceAndPayments(1000, AccountPayments{
320323
hash1: &PaymentEntry{
@@ -329,11 +332,12 @@ func TestAccountUpdateMethods(t *testing.T) {
329332

330333
// Now, update the first payment to have a new status and this
331334
// time, debit the account.
332-
err = store.UpsertAccountPayment(
335+
known, err = store.UpsertAccountPayment(
333336
ctx, acct.ID, hash1, 600, lnrpc.Payment_SUCCEEDED,
334337
WithDebitAccount(),
335338
)
336339
require.NoError(t, err)
340+
require.True(t, known)
337341

338342
// The account should now have a balance of 400 and the first
339343
// payment should have a status of succeeded.
@@ -350,10 +354,11 @@ func TestAccountUpdateMethods(t *testing.T) {
350354

351355
// Calling the same method again with the same payment hash
352356
// should have no effect by default.
353-
err = store.UpsertAccountPayment(
357+
known, err = store.UpsertAccountPayment(
354358
ctx, acct.ID, hash1, 600, lnrpc.Payment_SUCCEEDED,
355359
)
356360
require.NoError(t, err)
361+
require.True(t, known)
357362

358363
assertBalanceAndPayments(400, AccountPayments{
359364
hash1: &PaymentEntry{
@@ -368,11 +373,46 @@ func TestAccountUpdateMethods(t *testing.T) {
368373

369374
// But, if we use the WithErrIfAlreadyPending option, we should
370375
// get an error since the payment already exists.
371-
err = store.UpsertAccountPayment(
376+
known, err = store.UpsertAccountPayment(
372377
ctx, acct.ID, hash1, 600, lnrpc.Payment_SUCCEEDED,
373378
WithErrIfAlreadyPending(),
374379
)
375380
require.ErrorContains(t, err, "is already in flight")
381+
require.True(t, known)
382+
383+
// Do the above call again but this time, use the
384+
// WithErrIfAlreadySucceeded option. This should return the
385+
// ErrAlreadySucceeded error since the payment has already
386+
// succeeded.
387+
known, err = store.UpsertAccountPayment(
388+
ctx, acct.ID, hash1, 600, lnrpc.Payment_SUCCEEDED,
389+
WithErrIfAlreadySucceeded(),
390+
)
391+
require.ErrorIs(t, err, ErrAlreadySucceeded)
392+
require.True(t, known)
393+
394+
// We now call the method again for hash 2 and update its status
395+
// to SUCCEEDED. This time, we will use the WithPendingAmount
396+
// option which means that whatever `fullAmount` is passed in
397+
// should be ignored and the pending amount should be used
398+
// instead.
399+
known, err = store.UpsertAccountPayment(
400+
ctx, acct.ID, hash2, 0, lnrpc.Payment_SUCCEEDED,
401+
WithPendingAmount(),
402+
)
403+
require.NoError(t, err)
404+
require.True(t, known)
405+
406+
assertBalanceAndPayments(400, AccountPayments{
407+
hash1: &PaymentEntry{
408+
Status: lnrpc.Payment_SUCCEEDED,
409+
FullAmount: 600,
410+
},
411+
hash2: &PaymentEntry{
412+
Status: lnrpc.Payment_SUCCEEDED,
413+
FullAmount: 100,
414+
},
415+
})
376416
})
377417
}
378418

0 commit comments

Comments
 (0)