multi: move failed attempt cfg option to the router subsytem
Previously we had db and application logic mixed on the db level. We now move the config option KeepFailedPaymentAttempts to the ChannelRouter level and move it out of the db level.
This commit is contained in:
10 files changed
+224
-175
No files matched your search
@@ -50,10 +50,7 @@ func TestControlTowerSubscribeUnknown(t *testing.T) {
|
||||
|
||||
db := initDB(t)
|
||||
|
||||
paymentDB, err := paymentsdb.NewKVStore(
|
||||
db,
|
||||
paymentsdb.WithKeepFailedPaymentAttempts(true),
|
||||
)
|
||||
paymentDB, err := paymentsdb.NewKVStore(db)
|
||||
require.NoError(t, err)
|
||||
|
||||
pControl := NewControlTower(paymentDB)
|
||||
@@ -182,17 +179,11 @@ func TestControlTowerSubscribeSuccess(t *testing.T) {
|
||||
func TestKVStoreSubscribeFail(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("register attempt, keep failed payments", func(t *testing.T) {
|
||||
testKVStoreSubscribeFail(t, true, true)
|
||||
t.Run("register attempt", func(t *testing.T) {
|
||||
testKVStoreSubscribeFail(t, true)
|
||||
})
|
||||
t.Run("register attempt, delete failed payments", func(t *testing.T) {
|
||||
testKVStoreSubscribeFail(t, true, false)
|
||||
})
|
||||
t.Run("no register attempt, keep failed payments", func(t *testing.T) {
|
||||
testKVStoreSubscribeFail(t, false, true)
|
||||
})
|
||||
t.Run("no register attempt, delete failed payments", func(t *testing.T) {
|
||||
testKVStoreSubscribeFail(t, false, false)
|
||||
t.Run("no register attempt", func(t *testing.T) {
|
||||
testKVStoreSubscribeFail(t, false)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -203,10 +194,7 @@ func TestKVStoreSubscribeAllSuccess(t *testing.T) {
|
||||
|
||||
db := initDB(t)
|
||||
|
||||
paymentDB, err := paymentsdb.NewKVStore(
|
||||
db,
|
||||
paymentsdb.WithKeepFailedPaymentAttempts(true),
|
||||
)
|
||||
paymentDB, err := paymentsdb.NewKVStore(db)
|
||||
require.NoError(t, err)
|
||||
|
||||
pControl := NewControlTower(paymentDB)
|
||||
@@ -334,10 +322,7 @@ func TestKVStoreSubscribeAllImmediate(t *testing.T) {
|
||||
|
||||
db := initDB(t)
|
||||
|
||||
paymentDB, err := paymentsdb.NewKVStore(
|
||||
db,
|
||||
paymentsdb.WithKeepFailedPaymentAttempts(true),
|
||||
)
|
||||
paymentDB, err := paymentsdb.NewKVStore(db)
|
||||
require.NoError(t, err)
|
||||
|
||||
pControl := NewControlTower(paymentDB)
|
||||
@@ -385,10 +370,7 @@ func TestKVStoreUnsubscribeSuccess(t *testing.T) {
|
||||
|
||||
db := initDB(t)
|
||||
|
||||
paymentDB, err := paymentsdb.NewKVStore(
|
||||
db,
|
||||
paymentsdb.WithKeepFailedPaymentAttempts(true),
|
||||
)
|
||||
paymentDB, err := paymentsdb.NewKVStore(db)
|
||||
require.NoError(t, err)
|
||||
|
||||
pControl := NewControlTower(paymentDB)
|
||||
@@ -458,17 +440,10 @@ func TestKVStoreUnsubscribeSuccess(t *testing.T) {
|
||||
require.Len(t, subscription2.Updates(), 0)
|
||||
}
|
||||
|
||||
func testKVStoreSubscribeFail(t *testing.T, registerAttempt,
|
||||
keepFailedPaymentAttempts bool) {
|
||||
|
||||
func testKVStoreSubscribeFail(t *testing.T, registerAttempt bool) {
|
||||
db := initDB(t)
|
||||
|
||||
paymentDB, err := paymentsdb.NewKVStore(
|
||||
db,
|
||||
paymentsdb.WithKeepFailedPaymentAttempts(
|
||||
keepFailedPaymentAttempts,
|
||||
),
|
||||
)
|
||||
paymentDB, err := paymentsdb.NewKVStore(db)
|
||||
require.NoError(t, err)
|
||||
|
||||
pControl := NewControlTower(paymentDB)
|
||||
|
||||
@@ -338,15 +338,16 @@ lifecycle:
|
||||
// terminal condition. We either return the settled preimage or the
|
||||
// payment's failure reason.
|
||||
//
|
||||
// Optionally delete the failed attempts from the database. Depends on
|
||||
// the database options deleting attempts is not allowed so this will
|
||||
// just be a no-op.
|
||||
err = p.router.cfg.Control.DeleteFailedAttempts(
|
||||
cleanupCtx, p.identifier,
|
||||
)
|
||||
if err != nil {
|
||||
log.Errorf("Error deleting failed htlc attempts for payment "+
|
||||
"%v: %v", p.identifier, err)
|
||||
// Optionally delete the failed attempts from the database. If we are
|
||||
// configured to keep failed payment attempts, we skip deletion.
|
||||
if !p.router.cfg.KeepFailedPaymentAttempts {
|
||||
err = p.router.cfg.Control.DeleteFailedAttempts(
|
||||
cleanupCtx, p.identifier,
|
||||
)
|
||||
if err != nil {
|
||||
log.Errorf("Error deleting failed htlc attempts "+
|
||||
"for payment %v: %v", p.identifier, err)
|
||||
}
|
||||
}
|
||||
|
||||
htlc, failure := payment.TerminalInfo()
|
||||
|
||||
@@ -1280,6 +1280,156 @@ func TestResumePaymentSuccess(t *testing.T) {
|
||||
require.Equal(t, 1, m.collectResultsCount)
|
||||
}
|
||||
|
||||
// TestKeepFailedPaymentAttempts tests that DeleteFailedAttempts is
|
||||
// called or skipped based on the KeepFailedPaymentAttempts
|
||||
// configuration of the router.
|
||||
func TestKeepFailedPaymentAttempts(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
keepFailedPaymentAttempts bool
|
||||
expectDeleteCalled bool
|
||||
}{
|
||||
{
|
||||
name: "keep failed attempts - " +
|
||||
"delete not called",
|
||||
keepFailedPaymentAttempts: true,
|
||||
expectDeleteCalled: false,
|
||||
},
|
||||
{
|
||||
name: "delete failed attempts - " +
|
||||
"delete called",
|
||||
keepFailedPaymentAttempts: false,
|
||||
expectDeleteCalled: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Create a test paymentLifecycle with the initial two
|
||||
// calls mocked.
|
||||
p, m := setupTestPaymentLifecycle(t)
|
||||
|
||||
// Set the KeepFailedPaymentAttempts configuration.
|
||||
p.router.cfg.KeepFailedPaymentAttempts =
|
||||
tc.keepFailedPaymentAttempts
|
||||
|
||||
// Create a dummy route that will be returned by
|
||||
// `RequestRoute`.
|
||||
paymentAmt := lnwire.MilliSatoshi(10000)
|
||||
rt := createDummyRoute(t, paymentAmt)
|
||||
|
||||
// We now enter the payment lifecycle loop.
|
||||
//
|
||||
// 1.1. calls `FetchPayment` and return the payment.
|
||||
m.control.On("FetchPayment", p.identifier).
|
||||
Return(m.payment, nil).Once()
|
||||
|
||||
// 1.2. calls `GetState` and return the state.
|
||||
ps := &paymentsdb.MPPaymentState{
|
||||
RemainingAmt: paymentAmt,
|
||||
}
|
||||
m.payment.On("GetState").Return(ps).Once()
|
||||
|
||||
// NOTE: GetStatus is only used to populate the logs
|
||||
// which is not critical so we loosen the checks on how
|
||||
// many times it's been called.
|
||||
m.payment.On("GetStatus").
|
||||
Return(paymentsdb.StatusInFlight)
|
||||
|
||||
// 1.3. decideNextStep now returns stepProceed.
|
||||
m.payment.On("AllowMoreAttempts").
|
||||
Return(true, nil).Once()
|
||||
|
||||
// 1.4. mock requestRoute to return an route.
|
||||
m.paySession.On("RequestRoute",
|
||||
paymentAmt, p.feeLimit,
|
||||
uint32(ps.NumAttemptsInFlight),
|
||||
uint32(p.currentHeight), mock.Anything,
|
||||
).Return(rt, nil).Once()
|
||||
|
||||
// 1.5. mock `registerAttempt` to return an attempt.
|
||||
//
|
||||
// Mock NextPaymentID to always return the attemptID.
|
||||
attemptID := uint64(1)
|
||||
p.router.cfg.NextPaymentID = func() (uint64, error) {
|
||||
return attemptID, nil
|
||||
}
|
||||
|
||||
// Mock shardTracker to return the mock shard.
|
||||
m.shardTracker.On("NewShard",
|
||||
attemptID, true,
|
||||
).Return(m.shard, nil).Once()
|
||||
|
||||
// Mock the methods on the shard.
|
||||
m.shard.On("MPP").Return(&record.MPP{}).Twice().
|
||||
On("AMP").Return(nil).Once().
|
||||
On("Hash").Return(p.identifier).Once()
|
||||
|
||||
// Mock the time and expect it to be called.
|
||||
m.clock.On("Now").Return(time.Now())
|
||||
|
||||
// We now register attempt and return no error.
|
||||
m.control.On("RegisterAttempt",
|
||||
p.identifier, mock.Anything,
|
||||
).Return(nil).Once()
|
||||
|
||||
// 1.6. mock `sendAttempt` to succeed, which brings us
|
||||
// into the next iteration of the lifecycle.
|
||||
m.payer.On("SendHTLC",
|
||||
mock.Anything, attemptID, mock.Anything,
|
||||
).Return(nil).Once()
|
||||
|
||||
// We now enter the second iteration of the lifecycle
|
||||
// loop.
|
||||
//
|
||||
// 2.1. calls `FetchPayment` and return the payment.
|
||||
m.control.On("FetchPayment", p.identifier).
|
||||
Return(m.payment, nil).Once()
|
||||
|
||||
// 2.2. calls `GetState` and return the state.
|
||||
m.payment.On("GetState").Return(ps).
|
||||
Run(func(args mock.Arguments) {
|
||||
ps.RemainingAmt = 0
|
||||
}).Once()
|
||||
|
||||
// 2.3. decideNextStep now returns stepExit and exits
|
||||
// the loop.
|
||||
m.payment.On("AllowMoreAttempts").
|
||||
Return(false, nil).Once().
|
||||
On("NeedWaitAttempts").Return(false, nil).Once()
|
||||
|
||||
// Conditionally expect DeleteFailedAttempts to be
|
||||
// called based on the configuration.
|
||||
if tc.expectDeleteCalled {
|
||||
m.control.On("DeleteFailedAttempts",
|
||||
p.identifier).Return(nil).Once()
|
||||
}
|
||||
// If expectDeleteCalled is false, we don't set up the
|
||||
// expectation, which means the mock will fail if it's
|
||||
// called.
|
||||
|
||||
// Finally, mock the `TerminalInfo` to return the
|
||||
// settled attempt. Create a SettleAttempt.
|
||||
testPreimage := lntypes.Preimage{1, 2, 3}
|
||||
settledAttempt := makeSettledAttempt(
|
||||
t, int(paymentAmt), testPreimage,
|
||||
)
|
||||
m.payment.On("TerminalInfo").
|
||||
Return(settledAttempt, nil).Once()
|
||||
|
||||
// Send the payment and assert the preimage is matched.
|
||||
sendPaymentAndAssertSucceeded(t, p, testPreimage)
|
||||
|
||||
// Expected collectResultAsync to called.
|
||||
require.Equal(t, 1, m.collectResultsCount)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestResumePaymentSuccessWithTwoAttempts checks a successful payment flow
|
||||
// with two HTLC attempts.
|
||||
//
|
||||
|
||||
@@ -295,6 +295,10 @@ type Config struct {
|
||||
// TrafficShaper is an optional traffic shaper that can be used to
|
||||
// control the outgoing channel of a payment.
|
||||
TrafficShaper fn.Option[htlcswitch.AuxTrafficShaper]
|
||||
|
||||
// KeepFailedPaymentAttempts indicates whether to keep failed payment
|
||||
// attempts in the database.
|
||||
KeepFailedPaymentAttempts bool
|
||||
}
|
||||
|
||||
// EdgeLocator is a struct used to identify a specific edge.
|
||||
|
||||
Reference in new issue
Block a user