mirror of
https://github.com/getAlby/hub.git
synced 2026-08-13 12:33:39 +02:00
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:
parent
778f83237a
commit
b49818de07
6 changed files with 252 additions and 50 deletions
|
|
@ -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{
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue