fix: intercept self hold payments based on invoice rather than payment hash (#2027)

* fix: intercept self hold payments based on invoice rather than payment hash

* fix: generate test invoices with long expiry

* fix: add timeout seconds for standard lnd payments
This commit is contained in:
Roland 2026-02-27 11:52:24 +07:00 committed by GitHub
parent 778f83237a
commit b49818de07
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 252 additions and 50 deletions

View file

@ -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{

View file

@ -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
}

View file

@ -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)
}
}

View file

@ -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)

View file

@ -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 <amount> --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()
}

View file

@ -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