From 000177f5a1fd76196f476b99ed15bf61d6986fb4 Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Mon, 3 Jul 2023 14:19:48 +0200 Subject: [PATCH 01/29] add db migration, add entry types --- db/models/transactionentry.go | 10 ++++++++++ lib/service/invoices.go | 4 ++++ lib/service/invoicesubscription.go | 1 + 3 files changed, 15 insertions(+) diff --git a/db/models/transactionentry.go b/db/models/transactionentry.go index 931e99d4..803ce3e8 100644 --- a/db/models/transactionentry.go +++ b/db/models/transactionentry.go @@ -4,6 +4,15 @@ import ( "time" ) +const ( + EntryTypeIncoming = "incoming" + EntryTypeOutgoing = "outgoing" + EntryTypeFee = "fee" + EntryTypeFeeReserve = "fee_reserve" + EntryTypeFeeReserveReversal = "fee_reserve_reversal" + EntryTypeOutgoingReversal = "outgoing_reversal" +) + // TransactionEntry : Transaction Entries Model type TransactionEntry struct { ID int64 `bun:",pk,autoincrement"` @@ -19,4 +28,5 @@ type TransactionEntry struct { DebitAccount *Account `bun:"rel:belongs-to,join:debit_account_id=id"` Amount int64 `bun:",notnull"` CreatedAt time.Time `bun:",nullzero,notnull,default:current_timestamp"` + EntryType string } diff --git a/lib/service/invoices.go b/lib/service/invoices.go index ba3fc0b1..a0779f5a 100644 --- a/lib/service/invoices.go +++ b/lib/service/invoices.go @@ -82,6 +82,7 @@ func (svc *LndhubService) SendInternalPayment(ctx context.Context, invoice *mode CreditAccountID: recipientCreditAccount.ID, DebitAccountID: recipientDebitAccount.ID, Amount: invoice.Amount, + EntryType: models.EntryTypeIncoming, } _, err = svc.DB.NewInsert().Model(&recipientEntry).Exec(ctx) if err != nil { @@ -203,6 +204,7 @@ func (svc *LndhubService) PayInvoice(ctx context.Context, invoice *models.Invoic CreditAccountID: creditAccount.ID, DebitAccountID: debitAccount.ID, Amount: invoice.Amount, + EntryType: models.EntryTypeOutgoing, } // The DB constraints make sure the user actually has enough balance for the transaction @@ -258,6 +260,7 @@ func (svc *LndhubService) HandleFailedPayment(ctx context.Context, invoice *mode CreditAccountID: entryToRevert.DebitAccountID, DebitAccountID: entryToRevert.CreditAccountID, Amount: invoice.Amount, + EntryType: models.EntryTypeOutgoingReversal, } _, err = tx.NewInsert().Model(&entry).Exec(ctx) if err != nil { @@ -313,6 +316,7 @@ func (svc *LndhubService) HandleSuccessfulPayment(ctx context.Context, invoice * DebitAccountID: parentEntry.DebitAccountID, Amount: int64(invoice.Fee), ParentID: parentEntry.ID, + EntryType: models.EntryTypeFee, } _, err = svc.DB.NewInsert().Model(&entry).Exec(ctx) if err != nil { diff --git a/lib/service/invoicesubscription.go b/lib/service/invoicesubscription.go index 85ffbe4c..b9d24f80 100644 --- a/lib/service/invoicesubscription.go +++ b/lib/service/invoicesubscription.go @@ -163,6 +163,7 @@ func (svc *LndhubService) ProcessInvoiceUpdate(ctx context.Context, rawInvoice * CreditAccountID: creditAccount.ID, DebitAccountID: debitAccount.ID, Amount: rawInvoice.AmtPaidSat, + EntryType: models.EntryTypeIncoming, } // Save the transaction entry _, err = tx.NewInsert().Model(&entry).Exec(ctx) From 1313e089eb92a26538d662fd6caf10701cbe7e23 Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Mon, 3 Jul 2023 14:31:07 +0200 Subject: [PATCH 02/29] add db migration --- db/migrations/20230703120000_add_tx_entry_type.up.sql | 1 + 1 file changed, 1 insertion(+) create mode 100644 db/migrations/20230703120000_add_tx_entry_type.up.sql diff --git a/db/migrations/20230703120000_add_tx_entry_type.up.sql b/db/migrations/20230703120000_add_tx_entry_type.up.sql new file mode 100644 index 00000000..f27df153 --- /dev/null +++ b/db/migrations/20230703120000_add_tx_entry_type.up.sql @@ -0,0 +1 @@ +alter table transaction_entries add column entry_type character varying; \ No newline at end of file From dc54877da5af35600f6d73cd80590e88dcc848c2 Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Tue, 4 Jul 2023 10:57:38 +0200 Subject: [PATCH 03/29] fee reserves as seperate tx entries --- db/models/transactionentry.go | 2 + lib/service/invoices.go | 112 +++++++++++++++++++++++++++++----- 2 files changed, 100 insertions(+), 14 deletions(-) diff --git a/db/models/transactionentry.go b/db/models/transactionentry.go index 803ce3e8..95c75943 100644 --- a/db/models/transactionentry.go +++ b/db/models/transactionentry.go @@ -23,6 +23,8 @@ type TransactionEntry struct { ParentID int64 `bun:",nullzero"` Parent *TransactionEntry `bun:"rel:belongs-to"` CreditAccountID int64 `bun:",notnull"` + FeeReserveID int64 `bun:",nullzero"` + FeeReserve *TransactionEntry `bun:"rel:belongs-to"` CreditAccount *Account `bun:"rel:belongs-to,join:credit_account_id=id"` DebitAccountID int64 `bun:",notnull"` DebitAccount *Account `bun:"rel:belongs-to,join:debit_account_id=id"` diff --git a/lib/service/invoices.go b/lib/service/invoices.go index a0779f5a..0ddc1f95 100644 --- a/lib/service/invoices.go +++ b/lib/service/invoices.go @@ -197,21 +197,15 @@ func (svc *LndhubService) PayInvoice(ctx context.Context, invoice *models.Invoic svc.Logger.Errorf("Could not find outgoing account user_id:%v", userId) return nil, err } - - entry := models.TransactionEntry{ - UserID: userId, - InvoiceID: invoice.ID, - CreditAccountID: creditAccount.ID, - DebitAccountID: debitAccount.ID, - Amount: invoice.Amount, - EntryType: models.EntryTypeOutgoing, + feeAccount, err := svc.AccountFor(ctx, common.AccountTypeFees, userId) + if err != nil { + svc.Logger.Errorf("Could not find outgoing account user_id:%v", userId) + return nil, err } - // The DB constraints make sure the user actually has enough balance for the transaction - // If the user does not have enough balance this call fails - _, err = svc.DB.NewInsert().Model(&entry).Exec(ctx) + entry, err := svc.InsertTransactionEntry(ctx, invoice, creditAccount, debitAccount, feeAccount) if err != nil { - svc.Logger.Errorf("Could not insert transaction entry user_id:%v invoice_id:%v", userId, invoice.ID) + svc.Logger.Errorf("Could not insert transaction entries: %v", err) return nil, err } @@ -270,6 +264,13 @@ func (svc *LndhubService) HandleFailedPayment(ctx context.Context, invoice *mode return err } + err = svc.RevertFeeReserve(ctx, &entry, tx) + if err != nil { + sentry.CaptureException(err) + svc.Logger.Errorf("Could not revert fee reserve entry entry user_id:%v invoice_id:%v error %s", invoice.UserID, invoice.ID, err.Error()) + return err + } + invoice.State = common.InvoiceStateError if failedPaymentError != nil { invoice.ErrorMessage = failedPaymentError.Error() @@ -290,20 +291,96 @@ func (svc *LndhubService) HandleFailedPayment(ctx context.Context, invoice *mode return err } +func (svc *LndhubService) InsertTransactionEntry(ctx context.Context, invoice *models.Invoice, creditAccount, debitAccount, feeAccount models.Account) (entry models.TransactionEntry, err error) { + entry = models.TransactionEntry{ + UserID: invoice.UserID, + InvoiceID: invoice.ID, + CreditAccountID: creditAccount.ID, + DebitAccountID: debitAccount.ID, + Amount: invoice.Amount, + EntryType: models.EntryTypeOutgoing, + } + + tx, err := svc.DB.BeginTx(ctx, &sql.TxOptions{}) + if err != nil { + return entry, err + } + + // The DB constraints make sure the user actually has enough balance for the transaction + // If the user does not have enough balance this call fails + _, err = tx.NewInsert().Model(&entry).Exec(ctx) + if err != nil { + return entry, err + } + + //if external payment: add fee reserve to entry + feeLimit := svc.CalcFeeLimit(invoice.DestinationPubkeyHex, invoice.Amount) + if feeLimit != 0 { + feeReserveEntry := models.TransactionEntry{ + UserID: invoice.UserID, + InvoiceID: invoice.ID, + CreditAccountID: feeAccount.ID, + DebitAccountID: debitAccount.ID, + Amount: invoice.Amount, + EntryType: models.EntryTypeFeeReserve, + } + _, err = tx.NewInsert().Model(&feeReserveEntry).Exec(ctx) + if err != nil { + return entry, err + } + entry.FeeReserve = &feeReserveEntry + } + return entry, err +} + +func (svc *LndhubService) RevertFeeReserve(ctx context.Context, entry *models.TransactionEntry, tx bun.Tx) (err error) { + if entry.FeeReserve != nil { + entryToRevert := entry.FeeReserve + feeReserveRevert := models.TransactionEntry{ + UserID: entryToRevert.UserID, + InvoiceID: entryToRevert.ID, + CreditAccountID: entryToRevert.DebitAccountID, + DebitAccountID: entryToRevert.CreditAccountID, + Amount: entryToRevert.Amount, + EntryType: models.EntryTypeOutgoingReversal, + } + _, err = tx.NewInsert().Model(&feeReserveRevert).Exec(ctx) + return err + } + return nil +} + func (svc *LndhubService) HandleSuccessfulPayment(ctx context.Context, invoice *models.Invoice, parentEntry models.TransactionEntry) error { invoice.State = common.InvoiceStateSettled invoice.SettledAt = schema.NullTime{Time: time.Now()} - _, err := svc.DB.NewUpdate().Model(invoice).WherePK().Exec(ctx) + tx, err := svc.DB.BeginTx(ctx, &sql.TxOptions{}) if err != nil { + sentry.CaptureException(err) + svc.Logger.Errorf("Could not open tx entry for updating succesful payment:r_hash:%s %v", invoice.RHash, err) + return err + } + _, err = tx.NewUpdate().Model(invoice).WherePK().Exec(ctx) + if err != nil { + tx.Rollback() sentry.CaptureException(err) svc.Logger.Errorf("Could not update sucessful payment invoice user_id:%v invoice_id:%v, error %s", invoice.UserID, invoice.ID, err.Error()) return err } + //revert the fee reserve entry + err = svc.RevertFeeReserve(ctx, &parentEntry, tx) + if err != nil { + tx.Rollback() + sentry.CaptureException(err) + svc.Logger.Errorf("Could not revert fee reserve entry entry user_id:%v invoice_id:%v error %s", invoice.UserID, invoice.ID, err.Error()) + return err + } + // Get the user's fee account for the transaction entry, current account is already there in parent entry feeAccount, err := svc.AccountFor(ctx, common.AccountTypeFees, invoice.UserID) if err != nil { + tx.Rollback() svc.Logger.Errorf("Could not find fees account user_id:%v", invoice.UserID) return err } @@ -318,12 +395,19 @@ func (svc *LndhubService) HandleSuccessfulPayment(ctx context.Context, invoice * ParentID: parentEntry.ID, EntryType: models.EntryTypeFee, } - _, err = svc.DB.NewInsert().Model(&entry).Exec(ctx) + _, err = tx.NewInsert().Model(&entry).Exec(ctx) if err != nil { + tx.Rollback() sentry.CaptureException(err) svc.Logger.Errorf("Could not insert fee transaction entry user_id:%v invoice_id:%v error %s", invoice.UserID, invoice.ID, err.Error()) return err } + err = tx.Commit() + if err != nil { + sentry.CaptureException(err) + svc.Logger.Errorf("Failed to commit DB transaction user_id:%v invoice_id:%v %v", invoice.UserID, invoice.ID, err) + return err + } userBalance, err := svc.CurrentUserBalance(ctx, entry.UserID) if err != nil { From 3660db37a9b4d8d00dd576c59f84514060aca871 Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Tue, 4 Jul 2023 11:50:03 +0200 Subject: [PATCH 04/29] commit fee reserve tx entry --- db/migrations/20230703120000_add_tx_entry_type.up.sql | 4 +++- lib/service/invoices.go | 4 ++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/db/migrations/20230703120000_add_tx_entry_type.up.sql b/db/migrations/20230703120000_add_tx_entry_type.up.sql index f27df153..ef311358 100644 --- a/db/migrations/20230703120000_add_tx_entry_type.up.sql +++ b/db/migrations/20230703120000_add_tx_entry_type.up.sql @@ -1 +1,3 @@ -alter table transaction_entries add column entry_type character varying; \ No newline at end of file +alter table transaction_entries +add column entry_type character varying, +add column fee_reserve_id bigint; \ No newline at end of file diff --git a/lib/service/invoices.go b/lib/service/invoices.go index 0ddc1f95..cf3c2de6 100644 --- a/lib/service/invoices.go +++ b/lib/service/invoices.go @@ -330,6 +330,10 @@ func (svc *LndhubService) InsertTransactionEntry(ctx context.Context, invoice *m } entry.FeeReserve = &feeReserveEntry } + err = tx.Commit() + if err != nil { + return entry, err + } return entry, err } From 6b149af44f52ab9fc96f27a9820b2eccc5047ad4 Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Tue, 4 Jul 2023 14:47:40 +0200 Subject: [PATCH 05/29] fix cp error --- lib/service/invoices.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/service/invoices.go b/lib/service/invoices.go index cf3c2de6..e3b3e9d8 100644 --- a/lib/service/invoices.go +++ b/lib/service/invoices.go @@ -346,7 +346,7 @@ func (svc *LndhubService) RevertFeeReserve(ctx context.Context, entry *models.Tr CreditAccountID: entryToRevert.DebitAccountID, DebitAccountID: entryToRevert.CreditAccountID, Amount: entryToRevert.Amount, - EntryType: models.EntryTypeOutgoingReversal, + EntryType: models.EntryTypeFeeReserveReversal, } _, err = tx.NewInsert().Model(&feeReserveRevert).Exec(ctx) return err From f0237fa4433b9671c96a45c5c283a5b679730a03 Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Tue, 4 Jul 2023 14:47:50 +0200 Subject: [PATCH 06/29] update constraint --- db/migrations/20230703130000_replace_tx_entry_constraint.up.sql | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 db/migrations/20230703130000_replace_tx_entry_constraint.up.sql diff --git a/db/migrations/20230703130000_replace_tx_entry_constraint.up.sql b/db/migrations/20230703130000_replace_tx_entry_constraint.up.sql new file mode 100644 index 00000000..baa95c45 --- /dev/null +++ b/db/migrations/20230703130000_replace_tx_entry_constraint.up.sql @@ -0,0 +1,2 @@ +alter table transaction_entries drop constraint unique_tx_entry_tuple, +add constraint unique_tx_entry_tuple UNIQUE(user_id, invoice_id, debit_account_id, credit_account_id, entry_type); \ No newline at end of file From 1c841b4dd73e0c396b39000e08ea0c9a53daa9a3 Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Tue, 4 Jul 2023 15:52:59 +0200 Subject: [PATCH 07/29] also get fee reserve tx by invoice id --- lib/service/checkpayments.go | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/lib/service/checkpayments.go b/lib/service/checkpayments.go index 7805e36c..b3783739 100644 --- a/lib/service/checkpayments.go +++ b/lib/service/checkpayments.go @@ -13,15 +13,15 @@ import ( ) func (svc *LndhubService) GetAllPendingPayments(ctx context.Context) ([]models.Invoice, error) { - payments := []models.Invoice{} - err := svc.DB.NewSelect().Model(&payments).Where("state = 'initialized'").Where("type = 'outgoing'").Where("r_hash != ''").Where("created_at >= (now() - interval '2 weeks') ").Scan(ctx) - return payments, err + payments := []models.Invoice{} + err := svc.DB.NewSelect().Model(&payments).Where("state = 'initialized'").Where("type = 'outgoing'").Where("r_hash != ''").Where("created_at >= (now() - interval '2 weeks') ").Scan(ctx) + return payments, err } func (svc *LndhubService) CheckAllPendingOutgoingPayments(ctx context.Context) (err error) { - pendingPayments, err := svc.GetAllPendingPayments(ctx) - if err != nil { - return err - } + pendingPayments, err := svc.GetAllPendingPayments(ctx) + if err != nil { + return err + } svc.Logger.Infof("Found %d pending payments", len(pendingPayments)) //call trackoutgoingpaymentstatus for each one @@ -42,10 +42,19 @@ func (svc *LndhubService) CheckAllPendingOutgoingPayments(ctx context.Context) ( } func (svc *LndhubService) GetTransactionEntryByInvoiceId(ctx context.Context, id int64) (models.TransactionEntry, error) { - entry := models.TransactionEntry{} + entry := models.TransactionEntry{} + feeReserveEntry := models.TransactionEntry{} - err := svc.DB.NewSelect().Model(&entry).Where("invoice_id = ?", id).Limit(1).Scan(ctx) - return entry, err + err := svc.DB.NewSelect().Model(&entry).Where("invoice_id = ? and entry_type = ?", id, models.EntryTypeOutgoing).Limit(1).Scan(ctx) + if err != nil { + return entry, err + } + err = svc.DB.NewSelect().Model(&feeReserveEntry).Where("invoice_id = ? and entry_type = ?", id, models.EntryTypeFeeReserve).Limit(1).Scan(ctx) + if err != nil { + return entry, err + } + entry.FeeReserve = &feeReserveEntry + return entry, err } // Should be called in a goroutine as the tracking can potentially take a long time @@ -72,7 +81,7 @@ func (svc *LndhubService) TrackOutgoingPaymentstatus(ctx context.Context, invoic svc.Logger.Errorf("Error tracking payment with hash %s: %s", invoice.RHash, err.Error()) return } - entry, err := svc.GetTransactionEntryByInvoiceId(ctx, invoice.ID) + entry, err := svc.GetTransactionEntryByInvoiceId(ctx, invoice.ID) if err != nil { svc.Logger.Errorf("Error tracking payment %s: %s", invoice.RHash, err.Error()) return From 4bd261344fe7e9fff54920104373480578e876b2 Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Tue, 4 Jul 2023 16:30:58 +0200 Subject: [PATCH 08/29] fix bug --- lib/service/checkpayments.go | 1 + lib/service/invoices.go | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/service/checkpayments.go b/lib/service/checkpayments.go index b3783739..05cc2874 100644 --- a/lib/service/checkpayments.go +++ b/lib/service/checkpayments.go @@ -93,6 +93,7 @@ func (svc *LndhubService) TrackOutgoingPaymentstatus(ctx context.Context, invoic } if payment.Status == lnrpc.Payment_FAILED { svc.Logger.Infof("Failed payment detected: hash %s, reason %s", payment.PaymentHash, payment.FailureReason) + fmt.Println(entry.FeeReserve) err = svc.HandleFailedPayment(ctx, invoice, entry, fmt.Errorf(payment.FailureReason.String())) if err != nil { sentry.CaptureException(err) diff --git a/lib/service/invoices.go b/lib/service/invoices.go index e3b3e9d8..0b081926 100644 --- a/lib/service/invoices.go +++ b/lib/service/invoices.go @@ -264,7 +264,7 @@ func (svc *LndhubService) HandleFailedPayment(ctx context.Context, invoice *mode return err } - err = svc.RevertFeeReserve(ctx, &entry, tx) + err = svc.RevertFeeReserve(ctx, &entryToRevert, tx) if err != nil { sentry.CaptureException(err) svc.Logger.Errorf("Could not revert fee reserve entry entry user_id:%v invoice_id:%v error %s", invoice.UserID, invoice.ID, err.Error()) From 1677a97abe2c697dec6382059db21d849a73b767 Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Tue, 4 Jul 2023 17:03:01 +0200 Subject: [PATCH 09/29] remove debug --- lib/service/checkpayments.go | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/service/checkpayments.go b/lib/service/checkpayments.go index 05cc2874..b3783739 100644 --- a/lib/service/checkpayments.go +++ b/lib/service/checkpayments.go @@ -93,7 +93,6 @@ func (svc *LndhubService) TrackOutgoingPaymentstatus(ctx context.Context, invoic } if payment.Status == lnrpc.Payment_FAILED { svc.Logger.Infof("Failed payment detected: hash %s, reason %s", payment.PaymentHash, payment.FailureReason) - fmt.Println(entry.FeeReserve) err = svc.HandleFailedPayment(ctx, invoice, entry, fmt.Errorf(payment.FailureReason.String())) if err != nil { sentry.CaptureException(err) From 9ba016becd7b292f71630bf8e31d45469243a58c Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Tue, 4 Jul 2023 17:04:41 +0200 Subject: [PATCH 10/29] remove fee reserve id from tx entry --- db/migrations/20230703120000_add_tx_entry_type.up.sql | 3 +-- db/models/transactionentry.go | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/db/migrations/20230703120000_add_tx_entry_type.up.sql b/db/migrations/20230703120000_add_tx_entry_type.up.sql index ef311358..2a410a01 100644 --- a/db/migrations/20230703120000_add_tx_entry_type.up.sql +++ b/db/migrations/20230703120000_add_tx_entry_type.up.sql @@ -1,3 +1,2 @@ alter table transaction_entries -add column entry_type character varying, -add column fee_reserve_id bigint; \ No newline at end of file +add column entry_type character varying; \ No newline at end of file diff --git a/db/models/transactionentry.go b/db/models/transactionentry.go index 95c75943..2abbe522 100644 --- a/db/models/transactionentry.go +++ b/db/models/transactionentry.go @@ -23,7 +23,6 @@ type TransactionEntry struct { ParentID int64 `bun:",nullzero"` Parent *TransactionEntry `bun:"rel:belongs-to"` CreditAccountID int64 `bun:",notnull"` - FeeReserveID int64 `bun:",nullzero"` FeeReserve *TransactionEntry `bun:"rel:belongs-to"` CreditAccount *Account `bun:"rel:belongs-to,join:credit_account_id=id"` DebitAccountID int64 `bun:",notnull"` From c20f02ec75e6a07e7d41fd034795a8eb257f80fd Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Wed, 5 Jul 2023 11:15:12 +0200 Subject: [PATCH 11/29] add trick to avoid looking up the fee account again --- lib/service/invoices.go | 49 +++++++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 22 deletions(-) diff --git a/lib/service/invoices.go b/lib/service/invoices.go index 0b081926..52d5cd2b 100644 --- a/lib/service/invoices.go +++ b/lib/service/invoices.go @@ -354,6 +354,27 @@ func (svc *LndhubService) RevertFeeReserve(ctx context.Context, entry *models.Tr return nil } +func (svc *LndhubService) AddFeeEntry(ctx context.Context, entry *models.TransactionEntry, invoice *models.Invoice, tx bun.Tx) (err error) { + if entry.FeeReserve != nil { + // add transaction entry for fee + // if there was no fee reserve then this is an internal payment + // and no fee entry is needed + // if there is a fee reserve then we must use the same account id's + entry := models.TransactionEntry{ + UserID: invoice.UserID, + InvoiceID: invoice.ID, + CreditAccountID: entry.FeeReserve.CreditAccountID, + DebitAccountID: entry.FeeReserve.DebitAccountID, + Amount: int64(invoice.Fee), + ParentID: entry.ID, + EntryType: models.EntryTypeFee, + } + _, err = tx.NewInsert().Model(&entry).Exec(ctx) + return err + } + return nil +} + func (svc *LndhubService) HandleSuccessfulPayment(ctx context.Context, invoice *models.Invoice, parentEntry models.TransactionEntry) error { invoice.State = common.InvoiceStateSettled invoice.SettledAt = schema.NullTime{Time: time.Now()} @@ -381,31 +402,15 @@ func (svc *LndhubService) HandleSuccessfulPayment(ctx context.Context, invoice * return err } - // Get the user's fee account for the transaction entry, current account is already there in parent entry - feeAccount, err := svc.AccountFor(ctx, common.AccountTypeFees, invoice.UserID) - if err != nil { - tx.Rollback() - svc.Logger.Errorf("Could not find fees account user_id:%v", invoice.UserID) - return err - } - - // add transaction entry for fee - entry := models.TransactionEntry{ - UserID: invoice.UserID, - InvoiceID: invoice.ID, - CreditAccountID: feeAccount.ID, - DebitAccountID: parentEntry.DebitAccountID, - Amount: int64(invoice.Fee), - ParentID: parentEntry.ID, - EntryType: models.EntryTypeFee, - } - _, err = tx.NewInsert().Model(&entry).Exec(ctx) + //revert the fee reserve entry + err = svc.AddFeeEntry(ctx, &parentEntry, invoice, tx) if err != nil { tx.Rollback() sentry.CaptureException(err) - svc.Logger.Errorf("Could not insert fee transaction entry user_id:%v invoice_id:%v error %s", invoice.UserID, invoice.ID, err.Error()) + svc.Logger.Errorf("Could not add fee entry user_id:%v invoice_id:%v error %s", invoice.UserID, invoice.ID, err.Error()) return err } + err = tx.Commit() if err != nil { sentry.CaptureException(err) @@ -413,7 +418,7 @@ func (svc *LndhubService) HandleSuccessfulPayment(ctx context.Context, invoice * return err } - userBalance, err := svc.CurrentUserBalance(ctx, entry.UserID) + userBalance, err := svc.CurrentUserBalance(ctx, parentEntry.UserID) if err != nil { sentry.CaptureException(err) svc.Logger.Errorf("Could not fetch user balance user_id:%v invoice_id:%v error %s", invoice.UserID, invoice.ID, err.Error()) @@ -421,7 +426,7 @@ func (svc *LndhubService) HandleSuccessfulPayment(ctx context.Context, invoice * } if userBalance < 0 { - amountMsg := fmt.Sprintf("User balance is negative transaction_entry_id:%v user_id:%v amount:%v", entry.ID, entry.UserID, userBalance) + amountMsg := fmt.Sprintf("User balance is negative user_id:%v amount:%v", invoice.UserID, userBalance) svc.Logger.Info(amountMsg) sentry.CaptureMessage(amountMsg) } From a22ab04f011f3e7ffcd3ef5b9ffd09171e0d704f Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Wed, 5 Jul 2023 11:20:18 +0200 Subject: [PATCH 12/29] fix comment c/p --- lib/service/invoices.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/service/invoices.go b/lib/service/invoices.go index 52d5cd2b..bc822cbe 100644 --- a/lib/service/invoices.go +++ b/lib/service/invoices.go @@ -402,7 +402,7 @@ func (svc *LndhubService) HandleSuccessfulPayment(ctx context.Context, invoice * return err } - //revert the fee reserve entry + //add the real fee entry err = svc.AddFeeEntry(ctx, &parentEntry, invoice, tx) if err != nil { tx.Rollback() From c3bd3bc40f54e300f383ae4845aaf61f96c78fef Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Wed, 5 Jul 2023 11:42:52 +0200 Subject: [PATCH 13/29] fix bugs --- integration_tests/payment_failure_test.go | 19 ++++++++++++------ lib/service/invoices.go | 24 ++++++++++++----------- 2 files changed, 26 insertions(+), 17 deletions(-) diff --git a/integration_tests/payment_failure_test.go b/integration_tests/payment_failure_test.go index f4c81fb4..82e0285a 100644 --- a/integration_tests/payment_failure_test.go +++ b/integration_tests/payment_failure_test.go @@ -90,8 +90,8 @@ func (suite *PaymentTestErrorsSuite) TestExternalFailingInvoice() { //test an expired invoice externalInvoice := lnrpc.Invoice{ - Memo: "integration tests: external pay from alice", - Value: int64(externalSatRequested), + Memo: "integration tests: external pay from alice", + Value: int64(externalSatRequested), Expiry: 1, } invoice, err := suite.externalLND.AddInvoice(context.Background(), &externalInvoice) @@ -148,10 +148,17 @@ func (suite *PaymentTestErrorsSuite) TestExternalFailingInvoice() { fmt.Printf("Error when getting balance %v\n", err.Error()) } - // check if there are 3 transaction entries, with reversed credit and debit account ids - assert.Equal(suite.T(), 3, len(transactonEntries)) - assert.Equal(suite.T(), transactonEntries[1].CreditAccountID, transactonEntries[2].DebitAccountID) - assert.Equal(suite.T(), transactonEntries[1].DebitAccountID, transactonEntries[2].CreditAccountID) + // check if there are 5 transaction entries: + // - the incoming payment + // - the outgoing payment + // - the fee reserve + the fee reserve reversal + // - the outgoing payment reversal + // with reversed credit and debit account ids for payment 2/5 & payment 3/4 + assert.Equal(suite.T(), 5, len(transactonEntries)) + assert.Equal(suite.T(), transactonEntries[1].CreditAccountID, transactonEntries[4].DebitAccountID) + assert.Equal(suite.T(), transactonEntries[1].DebitAccountID, transactonEntries[4].CreditAccountID) + assert.Equal(suite.T(), transactonEntries[2].CreditAccountID, transactonEntries[3].DebitAccountID) + assert.Equal(suite.T(), transactonEntries[2].DebitAccountID, transactonEntries[3].CreditAccountID) assert.Equal(suite.T(), transactonEntries[1].Amount, int64(externalSatRequested)) assert.Equal(suite.T(), transactonEntries[2].Amount, int64(externalSatRequested)) // assert that balance is the same diff --git a/lib/service/invoices.go b/lib/service/invoices.go index bc822cbe..dbb572c2 100644 --- a/lib/service/invoices.go +++ b/lib/service/invoices.go @@ -247,7 +247,16 @@ func (svc *LndhubService) HandleFailedPayment(ctx context.Context, invoice *mode svc.Logger.Errorf("Could not open tx entry for updating failed payment:r_hash:%s %v", invoice.RHash, err) return err } - // add transaction entry with reverted credit/debit account id + + //revert the fee reserve if necessary + err = svc.RevertFeeReserve(ctx, &entryToRevert, invoice, tx) + if err != nil { + sentry.CaptureException(err) + svc.Logger.Errorf("Could not revert fee reserve entry entry user_id:%v invoice_id:%v error %s", invoice.UserID, invoice.ID, err.Error()) + return err + } + + //revert the payment if necessary entry := models.TransactionEntry{ UserID: invoice.UserID, InvoiceID: invoice.ID, @@ -264,13 +273,6 @@ func (svc *LndhubService) HandleFailedPayment(ctx context.Context, invoice *mode return err } - err = svc.RevertFeeReserve(ctx, &entryToRevert, tx) - if err != nil { - sentry.CaptureException(err) - svc.Logger.Errorf("Could not revert fee reserve entry entry user_id:%v invoice_id:%v error %s", invoice.UserID, invoice.ID, err.Error()) - return err - } - invoice.State = common.InvoiceStateError if failedPaymentError != nil { invoice.ErrorMessage = failedPaymentError.Error() @@ -337,12 +339,12 @@ func (svc *LndhubService) InsertTransactionEntry(ctx context.Context, invoice *m return entry, err } -func (svc *LndhubService) RevertFeeReserve(ctx context.Context, entry *models.TransactionEntry, tx bun.Tx) (err error) { +func (svc *LndhubService) RevertFeeReserve(ctx context.Context, entry *models.TransactionEntry, invoice *models.Invoice, tx bun.Tx) (err error) { if entry.FeeReserve != nil { entryToRevert := entry.FeeReserve feeReserveRevert := models.TransactionEntry{ UserID: entryToRevert.UserID, - InvoiceID: entryToRevert.ID, + InvoiceID: invoice.ID, CreditAccountID: entryToRevert.DebitAccountID, DebitAccountID: entryToRevert.CreditAccountID, Amount: entryToRevert.Amount, @@ -394,7 +396,7 @@ func (svc *LndhubService) HandleSuccessfulPayment(ctx context.Context, invoice * } //revert the fee reserve entry - err = svc.RevertFeeReserve(ctx, &parentEntry, tx) + err = svc.RevertFeeReserve(ctx, &parentEntry, invoice, tx) if err != nil { tx.Rollback() sentry.CaptureException(err) From da3912f11aaa4d6c54ab3e4c9041d265bcfef2de Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Wed, 5 Jul 2023 13:54:25 +0200 Subject: [PATCH 14/29] wtf who wrote this --- lib/service/invoices.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/service/invoices.go b/lib/service/invoices.go index dbb572c2..634cee2c 100644 --- a/lib/service/invoices.go +++ b/lib/service/invoices.go @@ -323,7 +323,7 @@ func (svc *LndhubService) InsertTransactionEntry(ctx context.Context, invoice *m InvoiceID: invoice.ID, CreditAccountID: feeAccount.ID, DebitAccountID: debitAccount.ID, - Amount: invoice.Amount, + Amount: feeLimit, EntryType: models.EntryTypeFeeReserve, } _, err = tx.NewInsert().Model(&feeReserveEntry).Exec(ctx) From c0c2f3e3a9cc06abf08df7ff101557ffda1d58b3 Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Wed, 5 Jul 2023 13:56:13 +0200 Subject: [PATCH 15/29] boy scouting --- integration_tests/hodl_invoice_test.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/integration_tests/hodl_invoice_test.go b/integration_tests/hodl_invoice_test.go index 7b73fa82..25b1fe2e 100644 --- a/integration_tests/hodl_invoice_test.go +++ b/integration_tests/hodl_invoice_test.go @@ -153,16 +153,16 @@ func (suite *HodlInvoiceSuite) TestHodlInvoice() { errorString := "FAILURE_REASON_INCORRECT_PAYMENT_DETAILS" assert.Equal(suite.T(), errorString, invoices[0].ErrorMessage) - transactonEntries, err := suite.service.TransactionEntriesFor(context.Background(), userId) + transactionEntries, err := suite.service.TransactionEntriesFor(context.Background(), userId) if err != nil { fmt.Printf("Error when getting transaction entries %v\n", err.Error()) } // check if there are 3 transaction entries, with reversed credit and debit account ids - assert.Equal(suite.T(), 3, len(transactonEntries)) - assert.Equal(suite.T(), transactonEntries[1].CreditAccountID, transactonEntries[2].DebitAccountID) - assert.Equal(suite.T(), transactonEntries[1].DebitAccountID, transactonEntries[2].CreditAccountID) - assert.Equal(suite.T(), transactonEntries[1].Amount, int64(externalSatRequested)) - assert.Equal(suite.T(), transactonEntries[2].Amount, int64(externalSatRequested)) + assert.Equal(suite.T(), 3, len(transactionEntries)) + assert.Equal(suite.T(), transactionEntries[1].CreditAccountID, transactionEntries[2].DebitAccountID) + assert.Equal(suite.T(), transactionEntries[1].DebitAccountID, transactionEntries[2].CreditAccountID) + assert.Equal(suite.T(), transactionEntries[1].Amount, int64(externalSatRequested)) + assert.Equal(suite.T(), transactionEntries[2].Amount, int64(externalSatRequested)) // create external invoice externalInvoice = lnrpc.Invoice{ From 809a6f97858a2b911195ce660c482ab76191de8c Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Wed, 5 Jul 2023 14:08:38 +0200 Subject: [PATCH 16/29] fix hodl invoice test --- integration_tests/hodl_invoice_test.go | 30 +++++++++++++++-------- integration_tests/payment_failure_test.go | 16 ++++++------ 2 files changed, 28 insertions(+), 18 deletions(-) diff --git a/integration_tests/hodl_invoice_test.go b/integration_tests/hodl_invoice_test.go index 25b1fe2e..1fdc50da 100644 --- a/integration_tests/hodl_invoice_test.go +++ b/integration_tests/hodl_invoice_test.go @@ -77,10 +77,10 @@ func (suite *HodlInvoiceSuite) SetupSuite() { } func (suite *HodlInvoiceSuite) TestHodlInvoice() { - userFundingSats := 1000 - externalSatRequested := 500 + userFundingSats := int64(1000) + externalSatRequested := int64(500) // fund user account - invoiceResponse := suite.createAddInvoiceReq(userFundingSats, "integration test external payment user", suite.userToken) + invoiceResponse := suite.createAddInvoiceReq(int(userFundingSats), "integration test external payment user", suite.userToken) err := suite.mlnd.mockPaidInvoice(invoiceResponse, 0, false, nil) assert.NoError(suite.T(), err) @@ -90,7 +90,7 @@ func (suite *HodlInvoiceSuite) TestHodlInvoice() { // create external invoice externalInvoice := lnrpc.Invoice{ Memo: "integration tests: external pay from user", - Value: int64(externalSatRequested), + Value: externalSatRequested, RPreimage: []byte("preimage1"), } invoice, err := suite.externalLND.AddInvoice(context.Background(), &externalInvoice) @@ -107,7 +107,10 @@ func (suite *HodlInvoiceSuite) TestHodlInvoice() { if err != nil { fmt.Printf("Error when getting balance %v\n", err.Error()) } - assert.Equal(suite.T(), int64(userFundingSats-externalSatRequested), userBalance) + + //also check that the fee reserve was reduced + feeReserve := suite.service.CalcFeeLimit(suite.externalLND.GetMainPubkey(), int64(externalSatRequested)) + assert.Equal(suite.T(), userFundingSats-externalSatRequested-feeReserve, userBalance) // check payment is pending inv, err := suite.service.FindInvoiceByPaymentHash(context.Background(), userId, hex.EncodeToString(invoice.RHash)) @@ -157,12 +160,19 @@ func (suite *HodlInvoiceSuite) TestHodlInvoice() { if err != nil { fmt.Printf("Error when getting transaction entries %v\n", err.Error()) } - // check if there are 3 transaction entries, with reversed credit and debit account ids - assert.Equal(suite.T(), 3, len(transactionEntries)) - assert.Equal(suite.T(), transactionEntries[1].CreditAccountID, transactionEntries[2].DebitAccountID) - assert.Equal(suite.T(), transactionEntries[1].DebitAccountID, transactionEntries[2].CreditAccountID) + // check if there are 5 transaction entries: + // - the incoming payment + // - the outgoing payment + // - the fee reserve + the fee reserve reversal + // - the outgoing payment reversal + // with reversed credit and debit account ids for payment 2/5 & payment 3/4 + assert.Equal(suite.T(), 5, len(transactionEntries)) + assert.Equal(suite.T(), transactionEntries[1].CreditAccountID, transactionEntries[4].DebitAccountID) + assert.Equal(suite.T(), transactionEntries[1].DebitAccountID, transactionEntries[4].CreditAccountID) + assert.Equal(suite.T(), transactionEntries[2].CreditAccountID, transactionEntries[3].DebitAccountID) + assert.Equal(suite.T(), transactionEntries[2].DebitAccountID, transactionEntries[3].CreditAccountID) assert.Equal(suite.T(), transactionEntries[1].Amount, int64(externalSatRequested)) - assert.Equal(suite.T(), transactionEntries[2].Amount, int64(externalSatRequested)) + assert.Equal(suite.T(), transactionEntries[4].Amount, int64(externalSatRequested)) // create external invoice externalInvoice = lnrpc.Invoice{ diff --git a/integration_tests/payment_failure_test.go b/integration_tests/payment_failure_test.go index 82e0285a..24da4825 100644 --- a/integration_tests/payment_failure_test.go +++ b/integration_tests/payment_failure_test.go @@ -138,7 +138,7 @@ func (suite *PaymentTestErrorsSuite) TestExternalFailingInvoice() { assert.Equal(suite.T(), common.InvoiceStateError, invoices[0].State) assert.Equal(suite.T(), SendPaymentMockError, invoices[0].ErrorMessage) - transactonEntries, err := suite.service.TransactionEntriesFor(context.Background(), userId) + transactionEntries, err := suite.service.TransactionEntriesFor(context.Background(), userId) if err != nil { fmt.Printf("Error when getting transaction entries %v\n", err.Error()) } @@ -154,13 +154,13 @@ func (suite *PaymentTestErrorsSuite) TestExternalFailingInvoice() { // - the fee reserve + the fee reserve reversal // - the outgoing payment reversal // with reversed credit and debit account ids for payment 2/5 & payment 3/4 - assert.Equal(suite.T(), 5, len(transactonEntries)) - assert.Equal(suite.T(), transactonEntries[1].CreditAccountID, transactonEntries[4].DebitAccountID) - assert.Equal(suite.T(), transactonEntries[1].DebitAccountID, transactonEntries[4].CreditAccountID) - assert.Equal(suite.T(), transactonEntries[2].CreditAccountID, transactonEntries[3].DebitAccountID) - assert.Equal(suite.T(), transactonEntries[2].DebitAccountID, transactonEntries[3].CreditAccountID) - assert.Equal(suite.T(), transactonEntries[1].Amount, int64(externalSatRequested)) - assert.Equal(suite.T(), transactonEntries[2].Amount, int64(externalSatRequested)) + assert.Equal(suite.T(), 5, len(transactionEntries)) + assert.Equal(suite.T(), transactionEntries[1].CreditAccountID, transactionEntries[4].DebitAccountID) + assert.Equal(suite.T(), transactionEntries[1].DebitAccountID, transactionEntries[4].CreditAccountID) + assert.Equal(suite.T(), transactionEntries[2].CreditAccountID, transactionEntries[3].DebitAccountID) + assert.Equal(suite.T(), transactionEntries[2].DebitAccountID, transactionEntries[3].CreditAccountID) + assert.Equal(suite.T(), transactionEntries[1].Amount, int64(externalSatRequested)) + assert.Equal(suite.T(), transactionEntries[2].Amount, int64(externalSatRequested)) // assert that balance is the same assert.Equal(suite.T(), int64(userFundingSats), userBalance) } From 61132a77b35d26e076864172ff30bde87117c583 Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Wed, 5 Jul 2023 14:16:27 +0200 Subject: [PATCH 17/29] no fee entry anymore for internal payments --- integration_tests/internal_payment_test.go | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/integration_tests/internal_payment_test.go b/integration_tests/internal_payment_test.go index 986eeb59..03132f84 100644 --- a/integration_tests/internal_payment_test.go +++ b/integration_tests/internal_payment_test.go @@ -171,13 +171,12 @@ func (suite *PaymentTestSuite) TestInternalPayment() { suite.echo.ServeHTTP(rec, req) assert.Equal(suite.T(), http.StatusBadRequest, rec.Code) - transactonEntriesAlice, _ := suite.service.TransactionEntriesFor(context.Background(), aliceId) + transactionEntriesAlice, _ := suite.service.TransactionEntriesFor(context.Background(), aliceId) aliceBalance, _ := suite.service.CurrentUserBalance(context.Background(), aliceId) - assert.Equal(suite.T(), 3, len(transactonEntriesAlice)) - assert.Equal(suite.T(), int64(aliceFundingSats), transactonEntriesAlice[0].Amount) - assert.Equal(suite.T(), int64(bobSatRequested), transactonEntriesAlice[1].Amount) - assert.Equal(suite.T(), int64(fee), transactonEntriesAlice[2].Amount) - assert.Equal(suite.T(), transactonEntriesAlice[1].ID, transactonEntriesAlice[2].ParentID) + assert.Equal(suite.T(), 2, len(transactionEntriesAlice)) + assert.Equal(suite.T(), int64(aliceFundingSats), transactionEntriesAlice[0].Amount) + assert.Equal(suite.T(), int64(bobSatRequested), transactionEntriesAlice[1].Amount) + assert.Equal(suite.T(), transactionEntriesAlice[1].ID, transactionEntriesAlice[2].ParentID) assert.Equal(suite.T(), int64(aliceFundingSats-bobSatRequested-fee), aliceBalance) bobBalance, _ := suite.service.CurrentUserBalance(context.Background(), bobId) From ef2f2675d4b30db02079a4d254e29c804574c32a Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Wed, 5 Jul 2023 14:20:59 +0200 Subject: [PATCH 18/29] fix internal payment test --- integration_tests/internal_payment_test.go | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/integration_tests/internal_payment_test.go b/integration_tests/internal_payment_test.go index 03132f84..d454552c 100644 --- a/integration_tests/internal_payment_test.go +++ b/integration_tests/internal_payment_test.go @@ -240,7 +240,7 @@ func (suite *PaymentTestSuite) TestInternalPaymentFail() { assert.Equal(suite.T(), 2, len(invoices)) assert.Equal(suite.T(), common.InvoiceStateError, invoices[0].State) assert.Equal(suite.T(), common.InvoiceStateSettled, invoices[1].State) - transactonEntries, err := suite.service.TransactionEntriesFor(context.Background(), userId) + transactionEntries, err := suite.service.TransactionEntriesFor(context.Background(), userId) if err != nil { fmt.Printf("Error when getting transaction entries %v\n", err.Error()) } @@ -250,15 +250,14 @@ func (suite *PaymentTestSuite) TestInternalPaymentFail() { fmt.Printf("Error when getting balance %v\n", err.Error()) } - // check if there are 5 transaction entries, with reversed credit and debit account ids for last 2 - assert.Equal(suite.T(), 5, len(transactonEntries)) - assert.Equal(suite.T(), int64(aliceFundingSats), transactonEntries[0].Amount) - assert.Equal(suite.T(), int64(bobSatRequested), transactonEntries[1].Amount) - assert.Equal(suite.T(), int64(fee), transactonEntries[2].Amount) - assert.Equal(suite.T(), transactonEntries[3].CreditAccountID, transactonEntries[4].DebitAccountID) - assert.Equal(suite.T(), transactonEntries[3].DebitAccountID, transactonEntries[4].CreditAccountID) - assert.Equal(suite.T(), transactonEntries[3].Amount, int64(bobSatRequested)) - assert.Equal(suite.T(), transactonEntries[4].Amount, int64(bobSatRequested)) + // check if there are 4 transaction entries, with reversed credit and debit account ids for last 2 + assert.Equal(suite.T(), 4, len(transactionEntries)) + assert.Equal(suite.T(), int64(aliceFundingSats), transactionEntries[0].Amount) + assert.Equal(suite.T(), int64(bobSatRequested), transactionEntries[1].Amount) + assert.Equal(suite.T(), transactionEntries[2].CreditAccountID, transactionEntries[3].DebitAccountID) + assert.Equal(suite.T(), transactionEntries[2].DebitAccountID, transactionEntries[3].CreditAccountID) + assert.Equal(suite.T(), transactionEntries[2].Amount, int64(bobSatRequested)) + assert.Equal(suite.T(), transactionEntries[3].Amount, int64(bobSatRequested)) // assert that balance was reduced only once assert.Equal(suite.T(), int64(aliceFundingSats)-int64(bobSatRequested+fee), int64(aliceBalance)) } From ec0493c1b1e3bdb1a3a55a5add1ed4fb328acf90 Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Wed, 5 Jul 2023 14:48:01 +0200 Subject: [PATCH 19/29] fix async payment test --- integration_tests/internal_payment_test.go | 1 - integration_tests/outgoing_payment_test.go | 40 +++++++++---------- .../payment_failure_async_test.go | 17 ++++---- 3 files changed, 29 insertions(+), 29 deletions(-) diff --git a/integration_tests/internal_payment_test.go b/integration_tests/internal_payment_test.go index d454552c..3d117fa9 100644 --- a/integration_tests/internal_payment_test.go +++ b/integration_tests/internal_payment_test.go @@ -176,7 +176,6 @@ func (suite *PaymentTestSuite) TestInternalPayment() { assert.Equal(suite.T(), 2, len(transactionEntriesAlice)) assert.Equal(suite.T(), int64(aliceFundingSats), transactionEntriesAlice[0].Amount) assert.Equal(suite.T(), int64(bobSatRequested), transactionEntriesAlice[1].Amount) - assert.Equal(suite.T(), transactionEntriesAlice[1].ID, transactionEntriesAlice[2].ParentID) assert.Equal(suite.T(), int64(aliceFundingSats-bobSatRequested-fee), aliceBalance) bobBalance, _ := suite.service.CurrentUserBalance(context.Background(), bobId) diff --git a/integration_tests/outgoing_payment_test.go b/integration_tests/outgoing_payment_test.go index 61afe510..f1fd8a60 100644 --- a/integration_tests/outgoing_payment_test.go +++ b/integration_tests/outgoing_payment_test.go @@ -63,7 +63,7 @@ func (suite *PaymentTestSuite) TestOutGoingPayment() { assert.Equal(suite.T(), 1, len(outgoingInvoices)) assert.Equal(suite.T(), 1, len(incomingInvoices)) - assert.Equal(suite.T(), 3, len(transactonEntries)) + assert.Equal(suite.T(), 5, len(transactonEntries)) assert.Equal(suite.T(), int64(aliceFundingSats), transactonEntries[0].Amount) assert.Equal(suite.T(), currentAccount.ID, transactonEntries[0].CreditAccountID) @@ -77,13 +77,13 @@ func (suite *PaymentTestSuite) TestOutGoingPayment() { assert.Equal(suite.T(), int64(0), transactonEntries[1].ParentID) assert.Equal(suite.T(), outgoingInvoices[0].ID, transactonEntries[1].InvoiceID) - assert.Equal(suite.T(), int64(suite.mlnd.fee), transactonEntries[2].Amount) + assert.Equal(suite.T(), int64(suite.mlnd.fee), transactonEntries[4].Amount) assert.Equal(suite.T(), feeAccount.ID, transactonEntries[2].CreditAccountID) assert.Equal(suite.T(), currentAccount.ID, transactonEntries[2].DebitAccountID) assert.Equal(suite.T(), outgoingInvoices[0].ID, transactonEntries[2].InvoiceID) // make sure fee entry parent id is previous entry - assert.Equal(suite.T(), transactonEntries[1].ID, transactonEntries[2].ParentID) + assert.Equal(suite.T(), transactonEntries[1].ID, transactonEntries[4].ParentID) //fetch transactions, make sure the fee is there // check invoices again @@ -134,7 +134,7 @@ func (suite *PaymentTestSuite) TestOutGoingPaymentWithNegativeBalance() { assert.Equal(suite.T(), int64(-1), aliceBalance) // check that no additional transaction entry was created - transactonEntries, err := suite.service.TransactionEntriesFor(context.Background(), userId) + transactionEntries, err := suite.service.TransactionEntriesFor(context.Background(), userId) if err != nil { fmt.Printf("Error when getting transaction entries %v\n", err.Error()) } @@ -149,27 +149,27 @@ func (suite *PaymentTestSuite) TestOutGoingPaymentWithNegativeBalance() { assert.Equal(suite.T(), 1, len(outgoingInvoices)) assert.Equal(suite.T(), 1, len(incomingInvoices)) - assert.Equal(suite.T(), 3, len(transactonEntries)) + assert.Equal(suite.T(), 5, len(transactionEntries)) - assert.Equal(suite.T(), int64(aliceFundingSats), transactonEntries[0].Amount) - assert.Equal(suite.T(), currentAccount.ID, transactonEntries[0].CreditAccountID) - assert.Equal(suite.T(), incomingAccount.ID, transactonEntries[0].DebitAccountID) - assert.Equal(suite.T(), int64(0), transactonEntries[0].ParentID) - assert.Equal(suite.T(), incomingInvoices[0].ID, transactonEntries[0].InvoiceID) + assert.Equal(suite.T(), int64(aliceFundingSats), transactionEntries[0].Amount) + assert.Equal(suite.T(), currentAccount.ID, transactionEntries[0].CreditAccountID) + assert.Equal(suite.T(), incomingAccount.ID, transactionEntries[0].DebitAccountID) + assert.Equal(suite.T(), int64(0), transactionEntries[0].ParentID) + assert.Equal(suite.T(), incomingInvoices[0].ID, transactionEntries[0].InvoiceID) - assert.Equal(suite.T(), int64(externalSatRequested), transactonEntries[1].Amount) - assert.Equal(suite.T(), outgoingAccount.ID, transactonEntries[1].CreditAccountID) - assert.Equal(suite.T(), currentAccount.ID, transactonEntries[1].DebitAccountID) - assert.Equal(suite.T(), int64(0), transactonEntries[1].ParentID) - assert.Equal(suite.T(), outgoingInvoices[0].ID, transactonEntries[1].InvoiceID) + assert.Equal(suite.T(), int64(externalSatRequested), transactionEntries[1].Amount) + assert.Equal(suite.T(), outgoingAccount.ID, transactionEntries[1].CreditAccountID) + assert.Equal(suite.T(), currentAccount.ID, transactionEntries[1].DebitAccountID) + assert.Equal(suite.T(), int64(0), transactionEntries[1].ParentID) + assert.Equal(suite.T(), outgoingInvoices[0].ID, transactionEntries[1].InvoiceID) - assert.Equal(suite.T(), int64(suite.mlnd.fee), transactonEntries[2].Amount) - assert.Equal(suite.T(), feeAccount.ID, transactonEntries[2].CreditAccountID) - assert.Equal(suite.T(), currentAccount.ID, transactonEntries[2].DebitAccountID) - assert.Equal(suite.T(), outgoingInvoices[0].ID, transactonEntries[2].InvoiceID) + assert.Equal(suite.T(), int64(suite.mlnd.fee), transactionEntries[4].Amount) + assert.Equal(suite.T(), feeAccount.ID, transactionEntries[2].CreditAccountID) + assert.Equal(suite.T(), currentAccount.ID, transactionEntries[2].DebitAccountID) + assert.Equal(suite.T(), outgoingInvoices[0].ID, transactionEntries[2].InvoiceID) // make sure fee entry parent id is previous entry - assert.Equal(suite.T(), transactonEntries[1].ID, transactonEntries[2].ParentID) + assert.Equal(suite.T(), transactionEntries[1].ID, transactionEntries[4].ParentID) } func (suite *PaymentTestSuite) TestZeroAmountInvoice() { diff --git a/integration_tests/payment_failure_async_test.go b/integration_tests/payment_failure_async_test.go index 7f29ae8a..22a68606 100644 --- a/integration_tests/payment_failure_async_test.go +++ b/integration_tests/payment_failure_async_test.go @@ -105,7 +105,8 @@ func (suite *PaymentTestAsyncErrorsSuite) TestExternalAsyncFailingInvoice() { if err != nil { fmt.Printf("Error when getting balance %v\n", err.Error()) } - assert.Equal(suite.T(), int64(userFundingSats-externalSatRequested), userBalance) + feeReserve := suite.service.CalcFeeLimit(suite.externalLND.GetMainPubkey(), int64(externalSatRequested)) + assert.Equal(suite.T(), int64(userFundingSats-externalSatRequested)-feeReserve, userBalance) // fail payment and wait a bit suite.serviceClient.FailPayment(SendPaymentMockError) @@ -126,16 +127,16 @@ func (suite *PaymentTestAsyncErrorsSuite) TestExternalAsyncFailingInvoice() { assert.Equal(suite.T(), common.InvoiceStateError, invoices[0].State) assert.Equal(suite.T(), SendPaymentMockError, invoices[0].ErrorMessage) - transactonEntries, err := suite.service.TransactionEntriesFor(context.Background(), userId) + transactionEntries, err := suite.service.TransactionEntriesFor(context.Background(), userId) if err != nil { fmt.Printf("Error when getting transaction entries %v\n", err.Error()) } - // check if there are 3 transaction entries, with reversed credit and debit account ids - assert.Equal(suite.T(), 3, len(transactonEntries)) - assert.Equal(suite.T(), transactonEntries[1].CreditAccountID, transactonEntries[2].DebitAccountID) - assert.Equal(suite.T(), transactonEntries[1].DebitAccountID, transactonEntries[2].CreditAccountID) - assert.Equal(suite.T(), transactonEntries[1].Amount, int64(externalSatRequested)) - assert.Equal(suite.T(), transactonEntries[2].Amount, int64(externalSatRequested)) + // check if there are 5 transaction entries, with reversed credit and debit account ids + assert.Equal(suite.T(), 5, len(transactionEntries)) + assert.Equal(suite.T(), transactionEntries[1].CreditAccountID, transactionEntries[2].DebitAccountID) + assert.Equal(suite.T(), transactionEntries[1].DebitAccountID, transactionEntries[2].CreditAccountID) + assert.Equal(suite.T(), transactionEntries[1].Amount, int64(externalSatRequested)) + assert.Equal(suite.T(), transactionEntries[2].Amount, int64(externalSatRequested)) } func (suite *PaymentTestAsyncErrorsSuite) TearDownSuite() { From 59ee273bc70f28216270db61e5caaa30aa574c7f Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Wed, 5 Jul 2023 15:14:17 +0200 Subject: [PATCH 20/29] fix async payment test --- integration_tests/payment_failure_async_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/integration_tests/payment_failure_async_test.go b/integration_tests/payment_failure_async_test.go index 22a68606..d6c4a305 100644 --- a/integration_tests/payment_failure_async_test.go +++ b/integration_tests/payment_failure_async_test.go @@ -133,10 +133,10 @@ func (suite *PaymentTestAsyncErrorsSuite) TestExternalAsyncFailingInvoice() { } // check if there are 5 transaction entries, with reversed credit and debit account ids assert.Equal(suite.T(), 5, len(transactionEntries)) - assert.Equal(suite.T(), transactionEntries[1].CreditAccountID, transactionEntries[2].DebitAccountID) - assert.Equal(suite.T(), transactionEntries[1].DebitAccountID, transactionEntries[2].CreditAccountID) + assert.Equal(suite.T(), transactionEntries[1].CreditAccountID, transactionEntries[3].DebitAccountID) + assert.Equal(suite.T(), transactionEntries[1].DebitAccountID, transactionEntries[3].CreditAccountID) assert.Equal(suite.T(), transactionEntries[1].Amount, int64(externalSatRequested)) - assert.Equal(suite.T(), transactionEntries[2].Amount, int64(externalSatRequested)) + assert.Equal(suite.T(), transactionEntries[3].Amount, int64(externalSatRequested)) } func (suite *PaymentTestAsyncErrorsSuite) TearDownSuite() { From c09554a75298ec3032a869e32b5a3d5e49f99529 Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Wed, 5 Jul 2023 15:21:48 +0200 Subject: [PATCH 21/29] fix async payment test --- integration_tests/payment_failure_async_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/integration_tests/payment_failure_async_test.go b/integration_tests/payment_failure_async_test.go index d6c4a305..edc8a1f5 100644 --- a/integration_tests/payment_failure_async_test.go +++ b/integration_tests/payment_failure_async_test.go @@ -133,10 +133,10 @@ func (suite *PaymentTestAsyncErrorsSuite) TestExternalAsyncFailingInvoice() { } // check if there are 5 transaction entries, with reversed credit and debit account ids assert.Equal(suite.T(), 5, len(transactionEntries)) - assert.Equal(suite.T(), transactionEntries[1].CreditAccountID, transactionEntries[3].DebitAccountID) - assert.Equal(suite.T(), transactionEntries[1].DebitAccountID, transactionEntries[3].CreditAccountID) + assert.Equal(suite.T(), transactionEntries[1].CreditAccountID, transactionEntries[4].DebitAccountID) + assert.Equal(suite.T(), transactionEntries[1].DebitAccountID, transactionEntries[4].CreditAccountID) assert.Equal(suite.T(), transactionEntries[1].Amount, int64(externalSatRequested)) - assert.Equal(suite.T(), transactionEntries[3].Amount, int64(externalSatRequested)) + assert.Equal(suite.T(), transactionEntries[4].Amount, int64(externalSatRequested)) } func (suite *PaymentTestAsyncErrorsSuite) TearDownSuite() { From 3527f44185a1407cefd780bc811e94e37560f4cf Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Wed, 5 Jul 2023 15:26:17 +0200 Subject: [PATCH 22/29] fix async payment test --- integration_tests/payment_failure_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration_tests/payment_failure_test.go b/integration_tests/payment_failure_test.go index 24da4825..0689cdb6 100644 --- a/integration_tests/payment_failure_test.go +++ b/integration_tests/payment_failure_test.go @@ -160,7 +160,7 @@ func (suite *PaymentTestErrorsSuite) TestExternalFailingInvoice() { assert.Equal(suite.T(), transactionEntries[2].CreditAccountID, transactionEntries[3].DebitAccountID) assert.Equal(suite.T(), transactionEntries[2].DebitAccountID, transactionEntries[3].CreditAccountID) assert.Equal(suite.T(), transactionEntries[1].Amount, int64(externalSatRequested)) - assert.Equal(suite.T(), transactionEntries[2].Amount, int64(externalSatRequested)) + assert.Equal(suite.T(), transactionEntries[3].Amount, int64(externalSatRequested)) // assert that balance is the same assert.Equal(suite.T(), int64(userFundingSats), userBalance) } From d8a9201290aa509c965a272bf5312aaf6705c06b Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Wed, 5 Jul 2023 15:32:58 +0200 Subject: [PATCH 23/29] fix async payment test --- integration_tests/payment_failure_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration_tests/payment_failure_test.go b/integration_tests/payment_failure_test.go index 0689cdb6..eff222a9 100644 --- a/integration_tests/payment_failure_test.go +++ b/integration_tests/payment_failure_test.go @@ -160,7 +160,7 @@ func (suite *PaymentTestErrorsSuite) TestExternalFailingInvoice() { assert.Equal(suite.T(), transactionEntries[2].CreditAccountID, transactionEntries[3].DebitAccountID) assert.Equal(suite.T(), transactionEntries[2].DebitAccountID, transactionEntries[3].CreditAccountID) assert.Equal(suite.T(), transactionEntries[1].Amount, int64(externalSatRequested)) - assert.Equal(suite.T(), transactionEntries[3].Amount, int64(externalSatRequested)) + assert.Equal(suite.T(), transactionEntries[4].Amount, int64(externalSatRequested)) // assert that balance is the same assert.Equal(suite.T(), int64(userFundingSats), userBalance) } From 9a6f9abe5ec6845eaa932c6f4609a3477c56fcb1 Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Wed, 5 Jul 2023 16:57:38 +0200 Subject: [PATCH 24/29] add test to go below 0 with hodl invoices --- integration_tests/hodl_invoice_test.go | 91 ++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) diff --git a/integration_tests/hodl_invoice_test.go b/integration_tests/hodl_invoice_test.go index 1fdc50da..b9385e36 100644 --- a/integration_tests/hodl_invoice_test.go +++ b/integration_tests/hodl_invoice_test.go @@ -228,6 +228,97 @@ func (suite *HodlInvoiceSuite) TestHodlInvoice() { clearTable(suite.service, "transaction_entries") clearTable(suite.service, "accounts") } +func (suite *HodlInvoiceSuite) TestNegativeBalanceWithHodl() { + //10M funding, 5M sat requested + userFundingSats := 10000000 + externalSatRequested := 5000000 + userId := getUserIdFromToken(suite.userToken) + // fund user account + invoiceResponse := suite.createAddInvoiceReq(userFundingSats, "integration test external payment user", suite.userToken) + err := suite.mlnd.mockPaidInvoice(invoiceResponse, 0, false, nil) + assert.NoError(suite.T(), err) + + // wait a bit for the callback event to hit + time.Sleep(10 * time.Millisecond) + + // create external invoice + externalInvoice := lnrpc.Invoice{ + Memo: "integration tests: external pay from user", + Value: int64(externalSatRequested), + RPreimage: []byte("preimage2"), + } + invoice, err := suite.externalLND.AddInvoice(context.Background(), &externalInvoice) + assert.NoError(suite.T(), err) + //the fee should be 1 %, so 50k sats (+1) + feeLimit := suite.service.CalcFeeLimit(suite.externalLND.GetMainPubkey(), int64(externalSatRequested)) + // pay external from user, req will be canceled after 2 sec + go suite.createPayInvoiceReqWithCancel(invoice.PaymentRequest, suite.userToken) + // wait for payment to be updated as pending in database + time.Sleep(3 * time.Second) + // check payment is pending + inv, err := suite.service.FindInvoiceByPaymentHash(context.Background(), userId, hex.EncodeToString(invoice.RHash)) + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), common.InvoiceStateInitialized, inv.State) + + //drain balance from account: at this point we have 5 M, so we can pay 4.95M + drainInv := lnrpc.Invoice{ + Memo: "integration tests: external pay from user", + Value: int64(4950000), + RPreimage: []byte("preimage3"), + } + drainInvoice, err := suite.externalLND.AddInvoice(context.Background(), &drainInv) + assert.NoError(suite.T(), err) + //pay drain invoice + go suite.createPayInvoiceReqWithCancel(drainInvoice.PaymentRequest, suite.userToken) + time.Sleep(3 * time.Second) + + //start payment checking loop + go func() { + err = suite.service.CheckAllPendingOutgoingPayments(context.Background()) + assert.NoError(suite.T(), err) + }() + //wait a bit for routine to start + time.Sleep(time.Second) + //now settle both invoices with the maximum fee + suite.hodlLND.SettlePayment(lnrpc.Payment{ + PaymentHash: hex.EncodeToString(drainInvoice.RHash), + Value: drainInv.Value, + CreationDate: 0, + FeeSat: feeLimit, + PaymentPreimage: "preimage3", + ValueSat: drainInv.Value, + ValueMsat: 0, + PaymentRequest: invoice.PaymentRequest, + Status: lnrpc.Payment_SUCCEEDED, + FailureReason: 0, + }) + //wait a bit for db update to happen + time.Sleep(time.Second) + //send settle invoice with lnrpc.payment + suite.hodlLND.SettlePayment(lnrpc.Payment{ + PaymentHash: hex.EncodeToString(invoice.RHash), + Value: externalInvoice.Value, + CreationDate: 0, + FeeSat: feeLimit, + PaymentPreimage: "preimage2", + ValueSat: externalInvoice.Value, + ValueMsat: 0, + PaymentRequest: invoice.PaymentRequest, + Status: lnrpc.Payment_SUCCEEDED, + FailureReason: 0, + }) + //wait a bit for db update to happen + time.Sleep(time.Second) + + if err != nil { + fmt.Printf("Error when getting balance %v\n", err.Error()) + } + //fetch user balance again + userBalance, err := suite.service.CurrentUserBalance(context.Background(), userId) + assert.NoError(suite.T(), err) + //assert the balance is negative + assert.True(suite.T(), userBalance < 0) +} func (suite *HodlInvoiceSuite) TearDownSuite() { suite.invoiceUpdateSubCancelFn() From 3497d53cc631c717872ab2a91960e9718891fe59 Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Wed, 5 Jul 2023 17:27:40 +0200 Subject: [PATCH 25/29] add test to show negative balances are no longer possible --- integration_tests/hodl_invoice_test.go | 10 ++-------- integration_tests/lnd_mock_hodl.go | 2 +- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/integration_tests/hodl_invoice_test.go b/integration_tests/hodl_invoice_test.go index b9385e36..85256349 100644 --- a/integration_tests/hodl_invoice_test.go +++ b/integration_tests/hodl_invoice_test.go @@ -34,7 +34,7 @@ type HodlInvoiceSuite struct { func (suite *HodlInvoiceSuite) SetupSuite() { mlnd := newDefaultMockLND() - externalLND, err := NewMockLND("1234567890abcdefabcd", 0, make(chan (*lnrpc.Invoice))) + externalLND, err := NewMockLND("1234567890abcdefabcd", 0, make(chan (*lnrpc.Invoice), 5)) if err != nil { log.Fatalf("Error initializing test service: %v", err) } @@ -307,17 +307,11 @@ func (suite *HodlInvoiceSuite) TestNegativeBalanceWithHodl() { Status: lnrpc.Payment_SUCCEEDED, FailureReason: 0, }) - //wait a bit for db update to happen - time.Sleep(time.Second) - - if err != nil { - fmt.Printf("Error when getting balance %v\n", err.Error()) - } //fetch user balance again userBalance, err := suite.service.CurrentUserBalance(context.Background(), userId) assert.NoError(suite.T(), err) //assert the balance is negative - assert.True(suite.T(), userBalance < 0) + assert.False(suite.T(), userBalance < 0) } func (suite *HodlInvoiceSuite) TearDownSuite() { diff --git a/integration_tests/lnd_mock_hodl.go b/integration_tests/lnd_mock_hodl.go index 01f7c5a3..9a087a2b 100644 --- a/integration_tests/lnd_mock_hodl.go +++ b/integration_tests/lnd_mock_hodl.go @@ -33,7 +33,7 @@ func (hps *HodlPaymentSubscriber) Recv() (*lnrpc.Payment, error) { func NewLNDMockHodlWrapperAsync(lnd lnd.LightningClientWrapper) (result *LNDMockHodlWrapperAsync, err error) { return &LNDMockHodlWrapperAsync{ hps: &HodlPaymentSubscriber{ - ch: make(chan lnrpc.Payment), + ch: make(chan lnrpc.Payment, 5), }, LightningClientWrapper: lnd, }, nil From 8831541cdca4bd5c1d69df5899285ea58d187b3a Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Thu, 6 Jul 2023 16:22:28 +0200 Subject: [PATCH 26/29] fix comment --- integration_tests/hodl_invoice_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration_tests/hodl_invoice_test.go b/integration_tests/hodl_invoice_test.go index 85256349..235f290e 100644 --- a/integration_tests/hodl_invoice_test.go +++ b/integration_tests/hodl_invoice_test.go @@ -310,7 +310,7 @@ func (suite *HodlInvoiceSuite) TestNegativeBalanceWithHodl() { //fetch user balance again userBalance, err := suite.service.CurrentUserBalance(context.Background(), userId) assert.NoError(suite.T(), err) - //assert the balance is negative + //assert the balance did not go below 0 assert.False(suite.T(), userBalance < 0) } From 1f2ffb90e0ad2e21e76cf821ea693f8f69cf5be9 Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Thu, 6 Jul 2023 16:46:29 +0200 Subject: [PATCH 27/29] clean up tables after 2 tests --- integration_tests/hodl_invoice_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/integration_tests/hodl_invoice_test.go b/integration_tests/hodl_invoice_test.go index 235f290e..d4911148 100644 --- a/integration_tests/hodl_invoice_test.go +++ b/integration_tests/hodl_invoice_test.go @@ -224,9 +224,6 @@ func (suite *HodlInvoiceSuite) TestHodlInvoice() { inv, err = suite.service.FindInvoiceByPaymentHash(context.Background(), userId, hex.EncodeToString(invoice.RHash)) assert.NoError(suite.T(), err) assert.Equal(suite.T(), common.InvoiceStateSettled, inv.State) - clearTable(suite.service, "invoices") - clearTable(suite.service, "transaction_entries") - clearTable(suite.service, "accounts") } func (suite *HodlInvoiceSuite) TestNegativeBalanceWithHodl() { //10M funding, 5M sat requested @@ -315,6 +312,9 @@ func (suite *HodlInvoiceSuite) TestNegativeBalanceWithHodl() { } func (suite *HodlInvoiceSuite) TearDownSuite() { + clearTable(suite.service, "invoices") + clearTable(suite.service, "transaction_entries") + clearTable(suite.service, "accounts") suite.invoiceUpdateSubCancelFn() } From 24428b3302b573d79489d82e90fd558821157196 Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Thu, 6 Jul 2023 17:13:50 +0200 Subject: [PATCH 28/29] add another user so they don't interfere --- integration_tests/hodl_invoice_test.go | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/integration_tests/hodl_invoice_test.go b/integration_tests/hodl_invoice_test.go index d4911148..8c7f3f87 100644 --- a/integration_tests/hodl_invoice_test.go +++ b/integration_tests/hodl_invoice_test.go @@ -28,6 +28,7 @@ type HodlInvoiceSuite struct { service *service.LndhubService userLogin ExpectedCreateUserResponseBody userToken string + userToken2 string invoiceUpdateSubCancelFn context.CancelFunc hodlLND *LNDMockHodlWrapperAsync } @@ -51,7 +52,7 @@ func (suite *HodlInvoiceSuite) SetupSuite() { if err != nil { log.Fatalf("Error initializing test service: %v", err) } - users, userTokens, err := createUsers(svc, 1) + users, userTokens, err := createUsers(svc, 2) if err != nil { log.Fatalf("Error creating test users: %v", err) } @@ -66,10 +67,11 @@ func (suite *HodlInvoiceSuite) SetupSuite() { e.HTTPErrorHandler = responses.HTTPErrorHandler e.Validator = &lib.CustomValidator{Validator: validator.New()} suite.echo = e - assert.Equal(suite.T(), 1, len(users)) - assert.Equal(suite.T(), 1, len(userTokens)) + assert.Equal(suite.T(), 2, len(users)) + assert.Equal(suite.T(), 2, len(userTokens)) suite.userLogin = users[0] suite.userToken = userTokens[0] + suite.userToken2 = userTokens[1] suite.echo.Use(tokens.Middleware([]byte(suite.service.Config.JWTSecret))) suite.echo.GET("/balance", controllers.NewBalanceController(suite.service).Balance) suite.echo.POST("/addinvoice", controllers.NewAddInvoiceController(suite.service).AddInvoice) @@ -229,9 +231,9 @@ func (suite *HodlInvoiceSuite) TestNegativeBalanceWithHodl() { //10M funding, 5M sat requested userFundingSats := 10000000 externalSatRequested := 5000000 - userId := getUserIdFromToken(suite.userToken) + userId := getUserIdFromToken(suite.userToken2) // fund user account - invoiceResponse := suite.createAddInvoiceReq(userFundingSats, "integration test external payment user", suite.userToken) + invoiceResponse := suite.createAddInvoiceReq(userFundingSats, "integration test external payment user", suite.userToken2) err := suite.mlnd.mockPaidInvoice(invoiceResponse, 0, false, nil) assert.NoError(suite.T(), err) @@ -242,14 +244,14 @@ func (suite *HodlInvoiceSuite) TestNegativeBalanceWithHodl() { externalInvoice := lnrpc.Invoice{ Memo: "integration tests: external pay from user", Value: int64(externalSatRequested), - RPreimage: []byte("preimage2"), + RPreimage: []byte("preimage3"), } invoice, err := suite.externalLND.AddInvoice(context.Background(), &externalInvoice) assert.NoError(suite.T(), err) //the fee should be 1 %, so 50k sats (+1) feeLimit := suite.service.CalcFeeLimit(suite.externalLND.GetMainPubkey(), int64(externalSatRequested)) // pay external from user, req will be canceled after 2 sec - go suite.createPayInvoiceReqWithCancel(invoice.PaymentRequest, suite.userToken) + go suite.createPayInvoiceReqWithCancel(invoice.PaymentRequest, suite.userToken2) // wait for payment to be updated as pending in database time.Sleep(3 * time.Second) // check payment is pending @@ -261,12 +263,12 @@ func (suite *HodlInvoiceSuite) TestNegativeBalanceWithHodl() { drainInv := lnrpc.Invoice{ Memo: "integration tests: external pay from user", Value: int64(4950000), - RPreimage: []byte("preimage3"), + RPreimage: []byte("preimage4"), } drainInvoice, err := suite.externalLND.AddInvoice(context.Background(), &drainInv) assert.NoError(suite.T(), err) //pay drain invoice - go suite.createPayInvoiceReqWithCancel(drainInvoice.PaymentRequest, suite.userToken) + go suite.createPayInvoiceReqWithCancel(drainInvoice.PaymentRequest, suite.userToken2) time.Sleep(3 * time.Second) //start payment checking loop @@ -297,7 +299,7 @@ func (suite *HodlInvoiceSuite) TestNegativeBalanceWithHodl() { Value: externalInvoice.Value, CreationDate: 0, FeeSat: feeLimit, - PaymentPreimage: "preimage2", + PaymentPreimage: "preimage4", ValueSat: externalInvoice.Value, ValueMsat: 0, PaymentRequest: invoice.PaymentRequest, From 5a264de92e596892eb12466fb148a99c84305999 Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Fri, 7 Jul 2023 10:50:09 +0200 Subject: [PATCH 29/29] fix migration issue --- lib/service/checkpayments.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lib/service/checkpayments.go b/lib/service/checkpayments.go index b3783739..e97125fc 100644 --- a/lib/service/checkpayments.go +++ b/lib/service/checkpayments.go @@ -2,7 +2,9 @@ package service import ( "context" + "database/sql" "encoding/hex" + "errors" "fmt" "sync" @@ -47,6 +49,16 @@ func (svc *LndhubService) GetTransactionEntryByInvoiceId(ctx context.Context, id err := svc.DB.NewSelect().Model(&entry).Where("invoice_id = ? and entry_type = ?", id, models.EntryTypeOutgoing).Limit(1).Scan(ctx) if err != nil { + //migration issue: pre-feereserve payment will cause a "no rows in result set" error. + //in this case, we also look for the entries without the outgoing check, and do not add the fee reserve + //we can remove this later when all relevant payments will have an entry_type and a fee_reserve tx + if errors.Is(err, sql.ErrNoRows) { + //check again with legacy query + err = svc.DB.NewSelect().Model(&entry).Where("invoice_id = ?", id).Limit(1).Scan(ctx) + if err == nil { + return entry, nil + } + } return entry, err } err = svc.DB.NewSelect().Model(&feeReserveEntry).Where("invoice_id = ? and entry_type = ?", id, models.EntryTypeFeeReserve).Limit(1).Scan(ctx)