diff --git a/lnclient/lnd/lnd.go b/lnclient/lnd/lnd.go index 884f0b941..d5706dda7 100644 --- a/lnclient/lnd/lnd.go +++ b/lnclient/lnd/lnd.go @@ -35,6 +35,8 @@ import ( "github.com/lightningnetwork/lnd/lnrpc/routerrpc" ) +const SEND_PAYMENT_TIMEOUT = 50 + type LNDService struct { client *wrapper.LNDWrapper nodeInfo *lnclient.NodeInfo @@ -449,6 +451,7 @@ func (svc *LNDService) SendPaymentSync(payReq string, amount *uint64) (*lnclient PaymentRequest: payReq, MaxParts: MAX_PARTIAL_PAYMENTS, FeeLimitMsat: int64(transactions.CalculateFeeReserveMsat(paymentAmountMsat)), + TimeoutSeconds: SEND_PAYMENT_TIMEOUT, } if amount != nil { @@ -527,7 +530,6 @@ func (svc *LNDService) SendKeysend(amount uint64, destination string, custom_rec destCustomRecords[record.Type] = decodedValue } const MAX_PARTIAL_PAYMENTS = 16 - const SEND_PAYMENT_TIMEOUT = 50 const KEYSEND_CUSTOM_RECORD = 5482373484 destCustomRecords[KEYSEND_CUSTOM_RECORD] = preImageBytes sendPaymentRequest := &routerrpc.SendPaymentRequest{ diff --git a/tests/mock_ln_client.go b/tests/mock_ln_client.go index ea211f6c1..05be89ff5 100644 --- a/tests/mock_ln_client.go +++ b/tests/mock_ln_client.go @@ -77,6 +77,8 @@ var MockLNClientHoldTransaction = &lnclient.Transaction{ } type MockLn struct { + MakeInvoiceResponses []*lnclient.Transaction + MakeInvoiceErrors []error PayInvoiceResponses []*lnclient.PayInvoiceResponse PayInvoiceErrors []error PaymentDelay *time.Duration @@ -117,10 +119,24 @@ func (mln *MockLn) GetInfo(ctx context.Context) (info *lnclient.NodeInfo, err er } func (mln *MockLn) MakeInvoice(ctx context.Context, amount int64, description string, descriptionHash string, expiry int64, throughNodePubkey *string) (transaction *lnclient.Transaction, err error) { + if len(mln.MakeInvoiceResponses) > 0 { + response := mln.MakeInvoiceResponses[0] + err := mln.MakeInvoiceErrors[0] + mln.MakeInvoiceResponses = mln.MakeInvoiceResponses[1:] + mln.MakeInvoiceErrors = mln.MakeInvoiceErrors[1:] + return response, err + } return MockLNClientTransaction, nil } func (mln *MockLn) MakeHoldInvoice(ctx context.Context, amount int64, description string, descriptionHash string, expiry int64, paymentHash string) (transaction *lnclient.Transaction, err error) { + if len(mln.MakeInvoiceResponses) > 0 { + response := mln.MakeInvoiceResponses[0] + err := mln.MakeInvoiceErrors[0] + mln.MakeInvoiceResponses = mln.MakeInvoiceResponses[1:] + mln.MakeInvoiceErrors = mln.MakeInvoiceErrors[1:] + return response, err + } return MockLNClientHoldTransaction, nil } diff --git a/transactions/hold_invoice_self_payment_consumer.go b/transactions/hold_invoice_self_payment_consumer.go index eab5a0069..6a50da0b9 100644 --- a/transactions/hold_invoice_self_payment_consumer.go +++ b/transactions/hold_invoice_self_payment_consumer.go @@ -8,24 +8,24 @@ import ( ) type holdInvoiceUpdatedConsumer struct { - paymentHash string + paymentRequest string settledChannel chan<- *db.Transaction canceledChannel chan<- *db.Transaction } -func newHoldInvoiceUpdatedConsumer(paymentHash string, settledChannel chan<- *db.Transaction, canceledChannel chan<- *db.Transaction) *holdInvoiceUpdatedConsumer { +func newHoldInvoiceUpdatedConsumer(paymentRequest string, settledChannel chan<- *db.Transaction, canceledChannel chan<- *db.Transaction) *holdInvoiceUpdatedConsumer { return &holdInvoiceUpdatedConsumer{ - paymentHash: paymentHash, + paymentRequest: paymentRequest, settledChannel: settledChannel, canceledChannel: canceledChannel, } } func (consumer *holdInvoiceUpdatedConsumer) ConsumeEvent(ctx context.Context, event *events.Event, globalProperties map[string]interface{}) { - if event.Event == "nwc_payment_received" && event.Properties.(*db.Transaction).PaymentHash == consumer.paymentHash { + if event.Event == "nwc_payment_received" && event.Properties.(*db.Transaction).PaymentRequest == consumer.paymentRequest { consumer.settledChannel <- event.Properties.(*db.Transaction) } - if event.Event == "nwc_hold_invoice_canceled" && event.Properties.(*db.Transaction).PaymentHash == consumer.paymentHash { + if event.Event == "nwc_hold_invoice_canceled" && event.Properties.(*db.Transaction).PaymentRequest == consumer.paymentRequest { consumer.canceledChannel <- event.Properties.(*db.Transaction) } } diff --git a/transactions/payments_test.go b/transactions/payments_test.go index 71a83866a..9628c019b 100644 --- a/transactions/payments_test.go +++ b/transactions/payments_test.go @@ -111,10 +111,11 @@ func TestSendPaymentSync_Duplicate_AlreadyPaid(t *testing.T) { defer svc.Remove() svc.DB.Create(&db.Transaction{ - State: constants.TRANSACTION_STATE_SETTLED, - Type: constants.TRANSACTION_TYPE_OUTGOING, - PaymentHash: tests.MockLNClientTransaction.PaymentHash, - AmountMsat: 123000, + State: constants.TRANSACTION_STATE_SETTLED, + Type: constants.TRANSACTION_TYPE_OUTGOING, + PaymentRequest: tests.MockLNClientTransaction.Invoice, + PaymentHash: tests.MockLNClientTransaction.PaymentHash, + AmountMsat: 123000, }) transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher) @@ -131,10 +132,11 @@ func TestSendPaymentSync_Duplicate_Pending(t *testing.T) { defer svc.Remove() svc.DB.Create(&db.Transaction{ - State: constants.TRANSACTION_STATE_PENDING, - Type: constants.TRANSACTION_TYPE_OUTGOING, - PaymentHash: tests.MockLNClientTransaction.PaymentHash, - AmountMsat: 123000, + State: constants.TRANSACTION_STATE_PENDING, + Type: constants.TRANSACTION_TYPE_OUTGOING, + PaymentHash: tests.MockLNClientTransaction.PaymentHash, + PaymentRequest: tests.MockLNClientTransaction.Invoice, + AmountMsat: 123000, }) transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher) diff --git a/transactions/self_hold_payments_test.go b/transactions/self_hold_payments_test.go index c3041b8d4..4936fb3a0 100644 --- a/transactions/self_hold_payments_test.go +++ b/transactions/self_hold_payments_test.go @@ -9,7 +9,9 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/getAlby/hub/config" "github.com/getAlby/hub/constants" + "github.com/getAlby/hub/db" "github.com/getAlby/hub/lnclient" "github.com/getAlby/hub/tests" ) @@ -99,3 +101,176 @@ func TestSelfHoldPaymentCanceled(t *testing.T) { assert.Equal(t, true, updatedHoldTransaction.Hold) wg.Wait() } + +func TestWrappedInvoice(t *testing.T) { + ctx := context.TODO() + + svc, err := tests.CreateTestService(t) + svc.Cfg.SetUpdate("LNBackendType", config.LDKBackendType, "") + require.NoError(t, err) + defer svc.Remove() + + // invoices were created with long expiry in LND (Polar) + /* + lnd@grace:/$ lncli addinvoice --amt --preimage fcf200c74d9900dc77af17eb1f57c02eec0f94b5b169d3eee23df9a216a3411b --expiry 31536000 + bash: amount: No such file or directory + lnd@grace:/$ lncli addinvoice --amt 1000 --preimage fcf200c74d9900dc77af17eb1f57c02eec0f94b5b169d3eee23df9a21 + 6a3411b --expiry 31536000 + { + "r_hash": "8f3cfa1cdcf19de4b6fdb9ec339b7470e724d0c755b7c7568164d5df1b70ea6e", + "payment_request": "lnbcrt10u1p5cammypp53u7058xu7xw7fdhah8kr8xm5wrnjf5x82kmuw45pvn2a7xmsafhqdqqcqzzsxq97zvuqsp5k9qse5srd9mfaxlgucr0p2gzf9464xte5xtqgjxyxj7794jxav9q9qxpqysgqnvwx0j9qxkx6k9efetdr0vdkrnp4vn23ud4gwpm0k28vf3v0rcmzfnued907r7ju6d86wr25ypt366szd0f7s28nzrvwmp4rck4358spfr9vx9", + "add_index": "1", + "payment_addr": "b1410cd20369769e9be8e606f0a902496baa9979a1960448c434bde2d646eb0a" + } + lnd@grace:/$ lncli addholdinvoice 8f3cfa1cdcf19de4b6fdb9ec339b7470e724d0c755b7c7568164d5df1b70ea6e --amt 1100 + --expiry 31536000 + [lncli] rpc error: code = Unknown desc = invoice with payment hash already exists + lnd@grace:/$ lncli cancelinvoice 8f3cfa1cdcf19de4b6fdb9ec339b7470e724d0c755b7c7568164d5df1b70ea6e + {} + lnd@grace:/$ lncli deletecanceledinvoice 8f3cfa1cdcf19de4b6fdb9ec339b7470e724d0c755b7c7568164d5df1b70ea6e + { + "status": "canceled invoice deleted successfully: invoice hash 8f3cfa1cdcf19de4b6fdb9ec339b7470e724d0c755b7c7568164d5df1b70ea6e" + } + lnd@grace:/$ lncli addholdinvoice 8f3cfa1cdcf19de4b6fdb9ec339b7470e724d0c755b7c7568164d5df1b70ea6e --amt 1100 --expiry 31536000 + { + "payment_request": "lnbcrt11u1p5cama7pp53u7058xu7xw7fdhah8kr8xm5wrnjf5x82kmuw45pvn2a7xmsafhqdqqcqzzsxq97zvuqsp5mtvgejxel3yjyk55e58dgjg9v2mnxva9xn83yg05mducwz3ujhrq9qxpqysgqp48sr0r7x6hj5vcefn3wtj7g8r33agyp4aqfasyr2fptxp066wzppmr7rw9my3frezy65hw7u5l0tnqh7393x2km7tf2tk3efdl0c7qptan9m9", + "add_index": "2", + "payment_addr": "dad88cc8d9fc49225a94cd0ed4490562b73333a534cf1221f4db79870a3c95c6" + } + */ + + // use the pubkey from Bob's invoice to activate self payments + svc.LNClient.(*tests.MockLn).Pubkey = "03a53c23a3e12cb3b16dc23c8a6d18e5930b480443c5f46860f553b33d74731342" + + transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher) + + // Charlie creates invoice with payment hash + // Bob also creates invoice with payment hash, but it's a HOLD invoice one. + + // Create 3 isolated apps: Charlie (invoice creator), Bob (wrapper), Alice (payer) + charlieApp, _, err := svc.AppsService.CreateApp("Charlie", "", 0, "", nil, []string{constants.MAKE_INVOICE_SCOPE}, true, nil) + require.NoError(t, err) + require.NotNil(t, charlieApp) + + bobApp, _, err := svc.AppsService.CreateApp("Bob", "", 0, "", nil, []string{constants.MAKE_INVOICE_SCOPE, constants.PAY_INVOICE_SCOPE}, true, nil) + require.NoError(t, err) + require.NotNil(t, bobApp) + + aliceApp, _, err := svc.AppsService.CreateApp("Alice", "", 0, "", nil, []string{constants.PAY_INVOICE_SCOPE}, true, nil) + require.NoError(t, err) + require.NotNil(t, aliceApp) + + // created with sandbox.albylabs.com + // Charlie's 1000 sat invoice + mockCharlieInvoice := &lnclient.Transaction{ + Type: "incoming", + Invoice: "lnbcrt10u1p5cammypp53u7058xu7xw7fdhah8kr8xm5wrnjf5x82kmuw45pvn2a7xmsafhqdqqcqzzsxq97zvuqsp5k9qse5srd9mfaxlgucr0p2gzf9464xte5xtqgjxyxj7794jxav9q9qxpqysgqnvwx0j9qxkx6k9efetdr0vdkrnp4vn23ud4gwpm0k28vf3v0rcmzfnued907r7ju6d86wr25ypt366szd0f7s28nzrvwmp4rck4358spfr9vx9", + Description: "mock hold invoice", + DescriptionHash: "", + Preimage: "fcf200c74d9900dc77af17eb1f57c02eec0f94b5b169d3eee23df9a216a3411b", + PaymentHash: "8f3cfa1cdcf19de4b6fdb9ec339b7470e724d0c755b7c7568164d5df1b70ea6e", + Amount: 1000_000, + } + + // Bob's 1100 sat invoice + // (same payment hash) + + mockBobHoldInvoice := &lnclient.Transaction{ + Type: "incoming", + Invoice: "lnbcrt11u1p5cama7pp53u7058xu7xw7fdhah8kr8xm5wrnjf5x82kmuw45pvn2a7xmsafhqdqqcqzzsxq97zvuqsp5mtvgejxel3yjyk55e58dgjg9v2mnxva9xn83yg05mducwz3ujhrq9qxpqysgqp48sr0r7x6hj5vcefn3wtj7g8r33agyp4aqfasyr2fptxp066wzppmr7rw9my3frezy65hw7u5l0tnqh7393x2km7tf2tk3efdl0c7qptan9m9", + Description: "mock hold invoice", + DescriptionHash: "", + Preimage: "", + PaymentHash: "8f3cfa1cdcf19de4b6fdb9ec339b7470e724d0c755b7c7568164d5df1b70ea6e", + Amount: 1100_000, + } + + svc.LNClient.(*tests.MockLn).MakeInvoiceResponses = []*lnclient.Transaction{ + mockCharlieInvoice, + mockBobHoldInvoice, + } + svc.LNClient.(*tests.MockLn).MakeInvoiceErrors = []error{nil, nil} + + var preimages = []string{tests.MockLNClientHoldTransaction.Preimage, tests.MockLNClientHoldTransaction.Preimage} + svc.LNClient.(*tests.MockLn).PayInvoiceResponses = []*lnclient.PayInvoiceResponse{{ + Preimage: preimages[0], + }, { + Preimage: preimages[1], + }} + svc.LNClient.(*tests.MockLn).PayInvoiceErrors = []error{nil, nil} + + // Charlie creates a standard invoice for 1000 sats + charlieInvoice, err := transactionsService.MakeInvoice(ctx, 1000, "Charlie invoice", "", 0, nil, svc.LNClient, &charlieApp.ID, nil, nil) + require.NoError(t, err) + require.False(t, charlieInvoice.Hold) + require.Equal(t, mockCharlieInvoice.Invoice, charlieInvoice.PaymentRequest) + + // Bob creates a wrapped invoice with the same payment hash but higher amount (1100 sats) + // Bob acts as an intermediary, adding a fee of 100 sats + bobWrappedInvoice, err := transactionsService.MakeHoldInvoice(ctx, 1100, "Bob wrapped invoice", "", 0, charlieInvoice.PaymentHash, nil, svc.LNClient, &bobApp.ID, nil) + require.NoError(t, err) + require.True(t, bobWrappedInvoice.Hold) + require.Equal(t, mockBobHoldInvoice.Invoice, bobWrappedInvoice.PaymentRequest) + + // Top up Alice's wallet + aliceFundingTx := db.Transaction{ + AppId: &aliceApp.ID, + RequestEventId: nil, + Type: constants.TRANSACTION_TYPE_INCOMING, + State: constants.TRANSACTION_STATE_SETTLED, + FeeReserveMsat: uint64(0), + AmountMsat: 10000_000, + PaymentRequest: "lnbc100u1p5hkvrndpz2pshjmt9de6zqmmxyqcnqvpsxqs8xct5wvnp4qwmtpr4p72ms7gnq3pkfk2876y2msvl33s3840dlp6xsv2w59dpscpp5hpd6h7t023cf3q8d06y9slqcnkydffgzun9th5vjm62nsw8wssgqsp5dfddw9ezn93u7g9xmzh4q74kmwxlf0gxgx8c5e4cuu2ce3eapmgq9qyysgqcqzp2xqyz5vqp0t02p3882uhqsz0qf56jgy6mrf2523tudqnf5d2f6f83ud3krd9tu4zkd4yzwyc74acprnvz2853yf9lc89n90sy3r0lvckyy59racq40428t", + PaymentHash: "b85babf96f54709880ed7e88587c189d88d4a502e4cabbd192de953838ee8410", + SelfPayment: true, + } + svc.DB.Save(&aliceFundingTx) + + // Top up Bob's wallet + bobFundingTx := db.Transaction{ + AppId: &bobApp.ID, + RequestEventId: nil, + Type: constants.TRANSACTION_TYPE_INCOMING, + State: constants.TRANSACTION_STATE_SETTLED, + FeeReserveMsat: uint64(0), + AmountMsat: 10000_000, + PaymentRequest: "lnbc100u1p5hkvy7dpz2pshjmt9de6zqmmxyqcnqvpsxqs8xct5wvnp4qwmtpr4p72ms7gnq3pkfk2876y2msvl33s3840dlp6xsv2w59dpscpp526ulrmuxlgr9zmn56etnkr8eear5xyt2f3h5vecyfe9fwwmjgakqsp5ld7hwc4m6dy3zhy94lr24gklnqyuh7c2xe3wue7qjnlqf59l36rs9qyysgqcqzp2xqyz5vqpj7xg854g2wvdqxcdvt5pjucw0ljxckey3cm82tpx4nlgr8lgs6qv6jwvmnamurguxvwyxt3eft653zeqv7s2gq3gzag925mr796yhqqfaczhq", + PaymentHash: "56b9f1ef86fa06516e74d6573b0cf9cf4743116a4c6f4667044e4a973b72476c", + SelfPayment: true, + } + svc.DB.Save(&bobFundingTx) + + // Alice pays Bob's wrapped invoice + var wg sync.WaitGroup + wg.Add(1) + go func() { + defer wg.Done() + result, err := transactionsService.SendPaymentSync(bobWrappedInvoice.PaymentRequest, nil, nil, svc.LNClient, &aliceApp.ID, nil) + assert.NoError(t, err) + require.NotNil(t, result) + assert.Equal(t, constants.TRANSACTION_STATE_SETTLED, result.State) + }() + + // TODO: rather than wait, listen for the event + // Wait for Alice's payment to be accepted + time.Sleep(10 * time.Millisecond) + + // Bob pays Charlie's invoice to get the preimage + result, err := transactionsService.SendPaymentSync(charlieInvoice.PaymentRequest, nil, nil, svc.LNClient, &bobApp.ID, nil) + assert.NoError(t, err) + require.NotNil(t, result) + assert.Equal(t, constants.TRANSACTION_STATE_SETTLED, result.State) + + // TODO: expect Alice's payment is still held here + + // Bob settles Alice's invoice using the preimage from Charlie + // TODO: allow passing a payment request + settledAliceInvoice, err := transactionsService.SettleHoldInvoice(ctx, *result.Preimage, svc.LNClient) + assert.NoError(t, err) + require.NotNil(t, settledAliceInvoice) + assert.Equal(t, constants.TRANSACTION_STATE_SETTLED, settledAliceInvoice.State) + assert.Equal(t, true, settledAliceInvoice.SelfPayment) + assert.Equal(t, true, settledAliceInvoice.Hold) + + wg.Wait() +} diff --git a/transactions/transactions_service.go b/transactions/transactions_service.go index 6daef4145..a4cd08b40 100644 --- a/transactions/transactions_service.go +++ b/transactions/transactions_service.go @@ -304,8 +304,8 @@ func (svc *transactionsService) SendPaymentSync(payReq string, amountMsat *uint6 if paymentRequest.Payee != "" && paymentRequest.Payee == lnClient.GetPubkey() { var incomingTransaction db.Transaction result := svc.db.Limit(1).Find(&incomingTransaction, &db.Transaction{ - Type: constants.TRANSACTION_TYPE_INCOMING, - PaymentHash: paymentRequest.PaymentHash, + Type: constants.TRANSACTION_TYPE_INCOMING, + PaymentRequest: payReq, }) if result.Error == nil && result.RowsAffected > 0 { selfPayment = true @@ -325,19 +325,19 @@ func (svc *transactionsService) SendPaymentSync(payReq string, amountMsat *uint6 return svc.db.Transaction(func(tx *gorm.DB) error { var existingSettledTransaction db.Transaction if tx.Limit(1).Find(&existingSettledTransaction, &db.Transaction{ - Type: constants.TRANSACTION_TYPE_OUTGOING, - PaymentHash: paymentRequest.PaymentHash, - State: constants.TRANSACTION_STATE_SETTLED, + Type: constants.TRANSACTION_TYPE_OUTGOING, + PaymentRequest: payReq, + State: constants.TRANSACTION_STATE_SETTLED, }).RowsAffected > 0 { - logger.Logger.WithField("payment_hash", dbTransaction.PaymentHash).Debug("this invoice has already been paid") + logger.Logger.WithField("payment_request", dbTransaction.PaymentRequest).Debug("this invoice has already been paid") return errors.New("this invoice has already been paid") } if tx.Limit(1).Find(&existingSettledTransaction, &db.Transaction{ - Type: constants.TRANSACTION_TYPE_OUTGOING, - PaymentHash: paymentRequest.PaymentHash, - State: constants.TRANSACTION_STATE_PENDING, + Type: constants.TRANSACTION_TYPE_OUTGOING, + PaymentRequest: payReq, + State: constants.TRANSACTION_STATE_PENDING, }).RowsAffected > 0 { - logger.Logger.WithField("payment_hash", dbTransaction.PaymentHash).Debug("this invoice is already being paid") + logger.Logger.WithField("payment_request", dbTransaction.PaymentRequest).Debug("this invoice is already being paid") return errors.New("there is already a payment pending for this invoice") } @@ -391,7 +391,7 @@ func (svc *transactionsService) SendPaymentSync(payReq string, amountMsat *uint6 var response *lnclient.PayInvoiceResponse if selfPayment { - response, err = svc.interceptSelfPayment(paymentRequest.PaymentHash, lnClient) + response, err = svc.interceptSelfPayment(payReq, paymentRequest.PaymentHash, lnClient) } else { response, err = lnClient.SendPaymentSync(payReq, amountMsat) } @@ -520,7 +520,7 @@ func (svc *transactionsService) SendKeysend(amount uint64, destination string, c return nil, err } - _, err = svc.interceptSelfPayment(paymentHash, lnClient) + _, err = svc.interceptSelfPayment("", paymentHash, lnClient) if err == nil { payKeysendResponse = &lnclient.PayKeysendResponse{ Fee: 0, @@ -818,7 +818,7 @@ func (svc *transactionsService) ConsumeEvent(ctx context.Context, event *events. logger.Logger.WithField("event", event).Error("Transaction has no settle deadline") return } - svc.markHoldInvoiceAccepted(lnClientTransaction.PaymentHash, *lnClientTransaction.SettleDeadline, false) + svc.markHoldInvoiceAccepted(lnClientTransaction.Invoice, *lnClientTransaction.SettleDeadline, false) case "nwc_lnclient_payment_sent": lnClientTransaction, ok := event.Properties.(*lnclient.Transaction) @@ -898,23 +898,23 @@ func (svc *transactionsService) ConsumeEvent(ctx context.Context, event *events. } } -func (svc *transactionsService) markHoldInvoiceAccepted(paymentHash string, settleDeadline uint32, selfPayment bool) { +func (svc *transactionsService) markHoldInvoiceAccepted(paymentRequest string, settleDeadline uint32, selfPayment bool) { logger.Logger.WithFields(logrus.Fields{ - "paymentHash": paymentHash, - "self_payment": selfPayment, + "payment_request": paymentRequest, + "self_payment": selfPayment, }).Info("Processing hold invoice accepted event") var dbTransaction db.Transaction err := svc.db.Transaction(func(tx *gorm.DB) error { - result := tx.Where("payment_hash = ? AND type = ? AND state = ?", paymentHash, constants.TRANSACTION_TYPE_INCOMING, constants.TRANSACTION_STATE_PENDING).First(&dbTransaction) + result := tx.Where("payment_request = ? AND type = ? AND state = ?", paymentRequest, constants.TRANSACTION_TYPE_INCOMING, constants.TRANSACTION_STATE_PENDING).First(&dbTransaction) if result.Error != nil { if errors.Is(result.Error, gorm.ErrRecordNotFound) { logger.Logger.WithFields(logrus.Fields{ - "paymentHash": paymentHash, + "payment_request": paymentRequest, }).Warn("No corresponding pending incoming transaction found in DB for accepted hold invoice") } logger.Logger.WithFields(logrus.Fields{ - "paymentHash": paymentHash, + "payment_request": paymentRequest, }).WithError(result.Error).Error("Failed to query DB for accepted hold invoice") return result.Error } @@ -926,22 +926,23 @@ func (svc *transactionsService) markHoldInvoiceAccepted(paymentHash string, sett }).Error if err != nil { logger.Logger.WithFields(logrus.Fields{ - "paymentHash": paymentHash, - "dbTxID": dbTransaction.ID, + "payment_request": paymentRequest, + "id": dbTransaction.ID, }).WithError(err).Error("Failed to update hold invoice state to accepted in DB") return err } logger.Logger.WithFields(logrus.Fields{ - "paymentHash": paymentHash, - "dbTxID": dbTransaction.ID, + "payment_request": paymentRequest, + "id": dbTransaction.ID, }).Info("Updated hold invoice state to accepted in DB") return nil }) if err != nil { logger.Logger.WithFields(logrus.Fields{ - "paymentHash": paymentHash, + "payment_request": paymentRequest, + "id": dbTransaction.ID, }).WithError(err).Error("Failed DB transaction for hold invoice accepted event") } else { svc.eventPublisher.Publish(&events.Event{ @@ -951,13 +952,18 @@ func (svc *transactionsService) markHoldInvoiceAccepted(paymentHash string, sett } } -func (svc *transactionsService) interceptSelfPayment(paymentHash string, lnClient lnclient.LNClient) (*lnclient.PayInvoiceResponse, error) { - logger.Logger.WithField("payment_hash", paymentHash).Debug("Intercepting self payment") +func (svc *transactionsService) interceptSelfPayment(paymentRequest string, paymentHash string, lnClient lnclient.LNClient) (*lnclient.PayInvoiceResponse, error) { + logger.Logger.WithFields(logrus.Fields{ + "payment_request": paymentRequest, + "payment_hash": paymentHash, + }).Debug("Intercepting self payment") incomingTransaction := db.Transaction{} result := svc.db.Limit(1).Find(&incomingTransaction, &db.Transaction{ - Type: constants.TRANSACTION_TYPE_INCOMING, - State: constants.TRANSACTION_STATE_PENDING, - PaymentHash: paymentHash, + Type: constants.TRANSACTION_TYPE_INCOMING, + State: constants.TRANSACTION_STATE_PENDING, + // NOTE: for keysend, payment request will be "" + PaymentRequest: paymentRequest, + PaymentHash: paymentHash, }) if result.Error != nil { return nil, result.Error @@ -968,7 +974,7 @@ func (svc *transactionsService) interceptSelfPayment(paymentHash string, lnClien } if incomingTransaction.Hold { - return svc.interceptSelfHoldPayment(paymentHash, lnClient) + return svc.interceptSelfHoldPayment(paymentRequest, lnClient) } if incomingTransaction.Preimage == nil { @@ -990,11 +996,11 @@ func (svc *transactionsService) interceptSelfPayment(paymentHash string, lnClien }, nil } -func (svc *transactionsService) interceptSelfHoldPayment(paymentHash string, lnClient lnclient.LNClient) (*lnclient.PayInvoiceResponse, error) { +func (svc *transactionsService) interceptSelfHoldPayment(paymentRequest string, lnClient lnclient.LNClient) (*lnclient.PayInvoiceResponse, error) { settledChannel := make(chan *db.Transaction) canceledChannel := make(chan *db.Transaction) - holdInvoiceUpdatedConsumer := newHoldInvoiceUpdatedConsumer(paymentHash, settledChannel, canceledChannel) + holdInvoiceUpdatedConsumer := newHoldInvoiceUpdatedConsumer(paymentRequest, settledChannel, canceledChannel) svc.eventPublisher.RegisterSubscriber(holdInvoiceUpdatedConsumer) defer svc.eventPublisher.RemoveSubscriber(holdInvoiceUpdatedConsumer) @@ -1009,7 +1015,7 @@ func (svc *transactionsService) interceptSelfHoldPayment(paymentHash string, lnC fakeSettleDeadline := clientInfo.BlockHeight + 24 - svc.markHoldInvoiceAccepted(paymentHash, fakeSettleDeadline, true) + svc.markHoldInvoiceAccepted(paymentRequest, fakeSettleDeadline, true) select { case settledTransaction := <-settledChannel: @@ -1353,9 +1359,10 @@ func (svc *transactionsService) markTransactionSettled(tx *gorm.DB, dbTransactio var existingSettledTransaction db.Transaction if tx.Limit(1).Find(&existingSettledTransaction, &db.Transaction{ - Type: dbTransaction.Type, - PaymentHash: dbTransaction.PaymentHash, - State: constants.TRANSACTION_STATE_SETTLED, + Type: dbTransaction.Type, + PaymentRequest: dbTransaction.PaymentRequest, + PaymentHash: dbTransaction.PaymentHash, + State: constants.TRANSACTION_STATE_SETTLED, }).RowsAffected > 0 { logger.Logger.WithField("payment_hash", dbTransaction.PaymentHash).Debug("payment already marked as sent") return &existingSettledTransaction, nil