Skip to content

Commit 3351a17

Browse files
authored
Merge pull request #9543 from ziggie1984/fix-payment-inconsitency
multi: fix payment failure overwrite
2 parents 0053fda + b010e95 commit 3351a17

File tree

6 files changed

+139
-44
lines changed

6 files changed

+139
-44
lines changed

docs/release-notes/release-notes-0.19.0.md

+5
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,11 @@
9494

9595
* [Pass through](https://github.com/lightningnetwork/lnd/pull/9601) the unused
9696
`MaxPeers` configuration variable for neutrino mode.
97+
98+
* [Fixed a bug](https://github.com/lightningnetwork/lnd/pull/9543) where
99+
the payment might have been failed more than once and therefore overwriting
100+
the failure reason and notifying the `SubscribeAllPayments` subscribers more
101+
than once.
97102

98103
* [Fixed](https://github.com/lightningnetwork/lnd/pull/9609) a bug that may
99104
cause `listunspent` to give inaccurate wallet UTXOs.

itest/list_on_test.go

+4
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,10 @@ var allTestCases = []*lntest.TestCase{
378378
Name: "sendtoroute multi path payment",
379379
TestFunc: testSendToRouteMultiPath,
380380
},
381+
{
382+
Name: "send to route fail payment notification",
383+
TestFunc: testSendToRouteFailPaymentNotification,
384+
},
381385
{
382386
Name: "send multi path payment",
383387
TestFunc: testSendMultiPathPayment,

itest/lnd_trackpayments_test.go

+95
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@ package itest
22

33
import (
44
"encoding/hex"
5+
"time"
56

67
"github.com/btcsuite/btcd/btcutil"
8+
"github.com/lightningnetwork/lnd/chainreg"
79
"github.com/lightningnetwork/lnd/lnrpc"
810
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
911
"github.com/lightningnetwork/lnd/lntest"
@@ -148,3 +150,96 @@ func testTrackPaymentsCompatible(ht *lntest.HarnessTest) {
148150
require.NoError(ht, err, "unable to get payment update")
149151
require.Equal(ht, lnrpc.Payment_SUCCEEDED, payment3.Status)
150152
}
153+
154+
// testSendToRouteFailPaymentNotification tests that when we are failing a
155+
// payment via SendToRouteV2 we only receive one failure notification, also
156+
// making sure the failure reason is not overwritten in our db.
157+
func testSendToRouteFailPaymentNotification(ht *lntest.HarnessTest) {
158+
// Create a simple network with Alice->Bob.
159+
_, nodes := ht.CreateSimpleNetwork(
160+
[][]string{nil, nil},
161+
lntest.OpenChannelParams{Amt: 100_000},
162+
)
163+
alice, bob := nodes[0], nodes[1]
164+
165+
// Make Bob create an invoice for Alice to pay.
166+
_, rHashes, _ := ht.CreatePayReqs(bob, paymentAmt, 1)
167+
168+
rHash := rHashes[0]
169+
170+
// We create a dummy payment address so that the payment fails at
171+
// the first hop.
172+
payAddr := make([]byte, 32)
173+
174+
// Subscribe to all the payments because otherwise we would not be able
175+
// to verify if two failure notifications are received. The single
176+
// subscriber is deleted after receiving the first failure notification.
177+
tracker := alice.RPC.TrackPayments(&routerrpc.TrackPaymentsRequest{
178+
NoInflightUpdates: true,
179+
})
180+
181+
bobPubKey, err := hex.DecodeString(bob.PubKeyStr)
182+
require.NoError(ht, err, "unable to decode bob's pubkey")
183+
184+
// Build a route for the specified hops.
185+
r := alice.RPC.BuildRoute(&routerrpc.BuildRouteRequest{
186+
AmtMsat: int64(paymentAmt * 1000),
187+
FinalCltvDelta: chainreg.DefaultBitcoinTimeLockDelta,
188+
HopPubkeys: [][]byte{bobPubKey},
189+
}).Route
190+
191+
// Set the MPP records to indicate this is a payment shard.
192+
hop := r.Hops[len(r.Hops)-1]
193+
hop.MppRecord = &lnrpc.MPPRecord{
194+
PaymentAddr: payAddr,
195+
TotalAmtMsat: int64(paymentAmt * 1000),
196+
}
197+
198+
// Send the only shard.
199+
sendReq := &routerrpc.SendToRouteRequest{
200+
PaymentHash: rHash,
201+
Route: r,
202+
}
203+
204+
// We launch the payment and check the payment stream to verify that
205+
// we are receiving exactly one failure notification.
206+
respChan := make(chan *lnrpc.HTLCAttempt, 1)
207+
go func() {
208+
attempt := alice.RPC.SendToRouteV2(sendReq)
209+
respChan <- attempt
210+
}()
211+
212+
// We excpect the attempt and the payment to fail.
213+
select {
214+
case attempt := <-respChan:
215+
require.Equal(ht, attempt.Status, lnrpc.HTLCAttempt_FAILED)
216+
217+
case <-time.After(defaultTimeout):
218+
ht.Fatal("timeout waiting for SendToRouteV2 response")
219+
}
220+
221+
// Assert the first track payment update should be a failed update.
222+
update, err := tracker.Recv()
223+
require.NoError(ht, err, "unable to receive payment update")
224+
require.Equal(ht, lnrpc.Payment_FAILED, update.Status)
225+
226+
// Now check no other notifications come in.
227+
notifChan := make(chan *lnrpc.Payment, 1)
228+
go func() {
229+
notif, err := tracker.Recv()
230+
if err != nil {
231+
return
232+
}
233+
234+
notifChan <- notif
235+
}()
236+
237+
// We do not expect any other notifications.
238+
select {
239+
case unexpectedNotif := <-notifChan:
240+
ht.Fatalf("received unexpected notification: %v",
241+
unexpectedNotif)
242+
243+
case <-time.After(100 * time.Millisecond):
244+
}
245+
}

routing/control_tower.go

+3
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,9 @@ func (p *controlTower) FetchPayment(paymentHash lntypes.Hash) (
282282
// reason the payment failed. After invoking this method, InitPayment should
283283
// return nil on its next call for this payment hash, allowing the switch to
284284
// make a subsequent payment.
285+
//
286+
// NOTE: This method will overwrite the failure reason if the payment is already
287+
// failed.
285288
func (p *controlTower) FailPayment(paymentHash lntypes.Hash,
286289
reason channeldb.FailureReason) error {
287290

routing/router.go

+25-17
Original file line numberDiff line numberDiff line change
@@ -1048,6 +1048,29 @@ func (r *ChannelRouter) sendToRoute(htlcHash lntypes.Hash, rt *route.Route,
10481048
firstHopCustomRecords lnwire.CustomRecords) (*channeldb.HTLCAttempt,
10491049
error) {
10501050

1051+
// Helper function to fail a payment. It makes sure the payment is only
1052+
// failed once so that the failure reason is not overwritten.
1053+
failPayment := func(paymentIdentifier lntypes.Hash,
1054+
reason channeldb.FailureReason) error {
1055+
1056+
payment, fetchErr := r.cfg.Control.FetchPayment(
1057+
paymentIdentifier,
1058+
)
1059+
if fetchErr != nil {
1060+
return fetchErr
1061+
}
1062+
1063+
// NOTE: We cannot rely on the payment status to be failed here
1064+
// because it can still be in-flight although the payment is
1065+
// already failed.
1066+
_, failedReason := payment.TerminalInfo()
1067+
if failedReason != nil {
1068+
return nil
1069+
}
1070+
1071+
return r.cfg.Control.FailPayment(paymentIdentifier, reason)
1072+
}
1073+
10511074
log.Debugf("SendToRoute for payment %v with skipTempErr=%v",
10521075
htlcHash, skipTempErr)
10531076

@@ -1148,20 +1171,6 @@ func (r *ChannelRouter) sendToRoute(htlcHash lntypes.Hash, rt *route.Route,
11481171
return nil, err
11491172
}
11501173

1151-
// We now look up the payment to see if it's already failed.
1152-
payment, err := p.router.cfg.Control.FetchPayment(p.identifier)
1153-
if err != nil {
1154-
return result.attempt, err
1155-
}
1156-
1157-
// Exit if the above error has caused the payment to be failed, we also
1158-
// return the error from sending attempt to mimic the old behavior of
1159-
// this method.
1160-
_, failedReason := payment.TerminalInfo()
1161-
if failedReason != nil {
1162-
return result.attempt, result.err
1163-
}
1164-
11651174
// Since for SendToRoute we won't retry in case the shard fails, we'll
11661175
// mark the payment failed with the control tower immediately if the
11671176
// skipTempErr is false.
@@ -1175,8 +1184,7 @@ func (r *ChannelRouter) sendToRoute(htlcHash lntypes.Hash, rt *route.Route,
11751184
return result.attempt, result.err
11761185
}
11771186

1178-
// Otherwise we need to fail the payment.
1179-
err := r.cfg.Control.FailPayment(paymentIdentifier, reason)
1187+
err := failPayment(paymentIdentifier, reason)
11801188
if err != nil {
11811189
return nil, err
11821190
}
@@ -1199,7 +1207,7 @@ func (r *ChannelRouter) sendToRoute(htlcHash lntypes.Hash, rt *route.Route,
11991207
// An error returned from collecting the result, we'll mark the payment
12001208
// as failed if we don't skip temp error.
12011209
if !skipTempErr {
1202-
err := r.cfg.Control.FailPayment(paymentIdentifier, reason)
1210+
err := failPayment(paymentIdentifier, reason)
12031211
if err != nil {
12041212
return nil, err
12051213
}

routing/router_test.go

+7-27
Original file line numberDiff line numberDiff line change
@@ -2215,13 +2215,6 @@ func TestSendToRouteSkipTempErrSuccess(t *testing.T) {
22152215
mock.Anything, rt,
22162216
).Return(nil)
22172217

2218-
// Mock the control tower to return the mocked payment.
2219-
payment := &mockMPPayment{}
2220-
controlTower.On("FetchPayment", payHash).Return(payment, nil).Once()
2221-
2222-
// Mock the payment to return nil failure reason.
2223-
payment.On("TerminalInfo").Return(nil, nil)
2224-
22252218
// Expect a successful send to route.
22262219
attempt, err := router.SendToRouteSkipTempErr(payHash, rt, nil)
22272220
require.NoError(t, err)
@@ -2231,7 +2224,6 @@ func TestSendToRouteSkipTempErrSuccess(t *testing.T) {
22312224
controlTower.AssertExpectations(t)
22322225
payer.AssertExpectations(t)
22332226
missionControl.AssertExpectations(t)
2234-
payment.AssertExpectations(t)
22352227
}
22362228

22372229
// TestSendToRouteSkipTempErrNonMPP checks that an error is return when
@@ -2352,19 +2344,12 @@ func TestSendToRouteSkipTempErrTempFailure(t *testing.T) {
23522344
mock.Anything, mock.Anything, mock.Anything,
23532345
).Return(tempErr)
23542346

2355-
// Mock the control tower to return the mocked payment.
2356-
payment := &mockMPPayment{}
2357-
controlTower.On("FetchPayment", payHash).Return(payment, nil).Once()
2358-
23592347
// Mock the mission control to return a nil reason from reporting the
23602348
// attempt failure.
23612349
missionControl.On("ReportPaymentFail",
23622350
mock.Anything, rt, mock.Anything, mock.Anything,
23632351
).Return(nil, nil)
23642352

2365-
// Mock the payment to return nil failure reason.
2366-
payment.On("TerminalInfo").Return(nil, nil)
2367-
23682353
// Expect a failed send to route.
23692354
attempt, err := router.SendToRouteSkipTempErr(payHash, rt, nil)
23702355
require.Equal(t, tempErr, err)
@@ -2374,7 +2359,6 @@ func TestSendToRouteSkipTempErrTempFailure(t *testing.T) {
23742359
controlTower.AssertExpectations(t)
23752360
payer.AssertExpectations(t)
23762361
missionControl.AssertExpectations(t)
2377-
payment.AssertExpectations(t)
23782362
}
23792363

23802364
// TestSendToRouteSkipTempErrPermanentFailure validates a permanent failure
@@ -2436,7 +2420,9 @@ func TestSendToRouteSkipTempErrPermanentFailure(t *testing.T) {
24362420
).Return(testAttempt, nil)
24372421

24382422
// Expect the payment to be failed.
2439-
controlTower.On("FailPayment", payHash, mock.Anything).Return(nil)
2423+
controlTower.On(
2424+
"FailPayment", payHash, mock.Anything,
2425+
).Return(nil).Once()
24402426

24412427
// Mock an error to be returned from sending the htlc.
24422428
payer.On("SendHTLC",
@@ -2448,13 +2434,6 @@ func TestSendToRouteSkipTempErrPermanentFailure(t *testing.T) {
24482434
mock.Anything, rt, mock.Anything, mock.Anything,
24492435
).Return(&failureReason, nil)
24502436

2451-
// Mock the control tower to return the mocked payment.
2452-
payment := &mockMPPayment{}
2453-
controlTower.On("FetchPayment", payHash).Return(payment, nil).Once()
2454-
2455-
// Mock the payment to return a failure reason.
2456-
payment.On("TerminalInfo").Return(nil, &failureReason)
2457-
24582437
// Expect a failed send to route.
24592438
attempt, err := router.SendToRouteSkipTempErr(payHash, rt, nil)
24602439
require.Equal(t, permErr, err)
@@ -2464,7 +2443,6 @@ func TestSendToRouteSkipTempErrPermanentFailure(t *testing.T) {
24642443
controlTower.AssertExpectations(t)
24652444
payer.AssertExpectations(t)
24662445
missionControl.AssertExpectations(t)
2467-
payment.AssertExpectations(t)
24682446
}
24692447

24702448
// TestSendToRouteTempFailure validates a temporary failure will cause the
@@ -2525,7 +2503,9 @@ func TestSendToRouteTempFailure(t *testing.T) {
25252503
).Return(testAttempt, nil)
25262504

25272505
// Expect the payment to be failed.
2528-
controlTower.On("FailPayment", payHash, mock.Anything).Return(nil)
2506+
controlTower.On(
2507+
"FailPayment", payHash, mock.Anything,
2508+
).Return(nil).Once()
25292509

25302510
payer.On("SendHTLC",
25312511
mock.Anything, mock.Anything, mock.Anything,
@@ -2536,7 +2516,7 @@ func TestSendToRouteTempFailure(t *testing.T) {
25362516
controlTower.On("FetchPayment", payHash).Return(payment, nil).Once()
25372517

25382518
// Mock the payment to return nil failure reason.
2539-
payment.On("TerminalInfo").Return(nil, nil)
2519+
payment.On("TerminalInfo").Return(nil, nil).Once()
25402520

25412521
// Return a nil reason to mock a temporary failure.
25422522
missionControl.On("ReportPaymentFail",

0 commit comments

Comments
 (0)