mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-09-05 17:05:50 +02:00
channeldb+routing: htlcs are pruned on settle
This commit is contained in:
@@ -223,6 +223,19 @@ func (p *PaymentControl) InitPayment(paymentHash lntypes.Hash,
|
|||||||
return updateErr
|
return updateErr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeleteFailedAttempts deletes all failed htlcs for a payment if configured
|
||||||
|
// by the PaymentControl db.
|
||||||
|
func (p *PaymentControl) DeleteFailedAttempts(hash lntypes.Hash) error {
|
||||||
|
if !p.db.keepFailedPaymentAttempts {
|
||||||
|
const failedHtlcsOnly = true
|
||||||
|
err := p.db.DeletePayment(hash, failedHtlcsOnly)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// paymentIndexTypeHash is a payment index type which indicates that we have
|
// paymentIndexTypeHash is a payment index type which indicates that we have
|
||||||
// created an index of payment sequence number to payment hash.
|
// created an index of payment sequence number to payment hash.
|
||||||
type paymentIndexType uint8
|
type paymentIndexType uint8
|
||||||
|
@@ -308,7 +308,7 @@ func TestPaymentControlFailsWithoutInFlight(t *testing.T) {
|
|||||||
|
|
||||||
// TestPaymentControlDeleteNonInFlight checks that calling DeletePayments only
|
// TestPaymentControlDeleteNonInFlight checks that calling DeletePayments only
|
||||||
// deletes payments from the database that are not in-flight.
|
// deletes payments from the database that are not in-flight.
|
||||||
func TestPaymentControlDeleteNonInFligt(t *testing.T) {
|
func TestPaymentControlDeleteNonInFlight(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
db, cleanup, err := MakeTestDB()
|
db, cleanup, err := MakeTestDB()
|
||||||
@@ -528,7 +528,7 @@ func TestPaymentControlDeletePayments(t *testing.T) {
|
|||||||
|
|
||||||
// Register three payments:
|
// Register three payments:
|
||||||
// 1. A payment with two failed attempts.
|
// 1. A payment with two failed attempts.
|
||||||
// 2. A Payment with one failed and one settled attempt.
|
// 2. A payment with one failed and one settled attempt.
|
||||||
// 3. A payment with one failed and one in-flight attempt.
|
// 3. A payment with one failed and one in-flight attempt.
|
||||||
payments := []*payment{
|
payments := []*payment{
|
||||||
{status: StatusFailed},
|
{status: StatusFailed},
|
||||||
@@ -585,7 +585,7 @@ func TestPaymentControlDeleteSinglePayment(t *testing.T) {
|
|||||||
// according to its final status.
|
// according to its final status.
|
||||||
// 1. A payment with two failed attempts.
|
// 1. A payment with two failed attempts.
|
||||||
// 2. Another payment with two failed attempts.
|
// 2. Another payment with two failed attempts.
|
||||||
// 3. A Payment with one failed and one settled attempt.
|
// 3. A payment with one failed and one settled attempt.
|
||||||
// 4. A payment with one failed and one in-flight attempt.
|
// 4. A payment with one failed and one in-flight attempt.
|
||||||
|
|
||||||
// Initiate payments, which is a slice of payment that is used as
|
// Initiate payments, which is a slice of payment that is used as
|
||||||
@@ -1003,6 +1003,96 @@ func TestPaymentControlMPPRecordValidation(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestDeleteFailedAttempts checks that DeleteFailedAttempts properly removes
|
||||||
|
// failed HTLCs from finished payments.
|
||||||
|
func TestDeleteFailedAttempts(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
t.Run("keep failed payment attempts", func(t *testing.T) {
|
||||||
|
testDeleteFailedAttempts(t, true)
|
||||||
|
})
|
||||||
|
t.Run("remove failed payment attempts", func(t *testing.T) {
|
||||||
|
testDeleteFailedAttempts(t, false)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func testDeleteFailedAttempts(t *testing.T, keepFailedPaymentAttempts bool) {
|
||||||
|
db, cleanup, err := MakeTestDB()
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
require.NoError(t, err, "unable to init db")
|
||||||
|
db.keepFailedPaymentAttempts = keepFailedPaymentAttempts
|
||||||
|
|
||||||
|
pControl := NewPaymentControl(db)
|
||||||
|
|
||||||
|
// Register three payments:
|
||||||
|
// All payments will have one failed HTLC attempt and one HTLC attempt
|
||||||
|
// according to its final status.
|
||||||
|
// 1. A payment with two failed attempts.
|
||||||
|
// 2. A payment with one failed and one in-flight attempt.
|
||||||
|
// 3. A payment with one failed and one settled attempt.
|
||||||
|
|
||||||
|
// Initiate payments, which is a slice of payment that is used as
|
||||||
|
// template to create the corresponding test payments in the database.
|
||||||
|
//
|
||||||
|
// Note: The payment id and number of htlc attempts of each payment will
|
||||||
|
// be added to this slice when creating the payments below.
|
||||||
|
// This allows the slice to be used directly for testing purposes.
|
||||||
|
payments := []*payment{
|
||||||
|
{status: StatusFailed},
|
||||||
|
{status: StatusInFlight},
|
||||||
|
{status: StatusSucceeded},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use helper function to register the test payments in the data and
|
||||||
|
// populate the data to the payments slice.
|
||||||
|
createTestPayments(t, pControl, payments)
|
||||||
|
|
||||||
|
// Check that all payments are there as we added them.
|
||||||
|
assertPayments(t, db, payments)
|
||||||
|
|
||||||
|
// Calling DeleteFailedAttempts on a failed payment should delete all
|
||||||
|
// HTLCs.
|
||||||
|
require.NoError(t, pControl.DeleteFailedAttempts(payments[0].id))
|
||||||
|
|
||||||
|
// Expect all HTLCs to be deleted if the config is set to delete them.
|
||||||
|
if !keepFailedPaymentAttempts {
|
||||||
|
payments[0].htlcs = 0
|
||||||
|
}
|
||||||
|
assertPayments(t, db, payments)
|
||||||
|
|
||||||
|
// Calling DeleteFailedAttempts on an in-flight payment should return
|
||||||
|
// an error.
|
||||||
|
if keepFailedPaymentAttempts {
|
||||||
|
require.NoError(t, pControl.DeleteFailedAttempts(payments[1].id))
|
||||||
|
} else {
|
||||||
|
require.Error(t, pControl.DeleteFailedAttempts(payments[1].id))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since DeleteFailedAttempts returned an error, we should expect the
|
||||||
|
// payment to be unchanged.
|
||||||
|
assertPayments(t, db, payments)
|
||||||
|
|
||||||
|
// Cleaning up a successful payment should remove failed htlcs.
|
||||||
|
require.NoError(t, pControl.DeleteFailedAttempts(payments[2].id))
|
||||||
|
// Expect all HTLCs except for the settled one to be deleted if the
|
||||||
|
// config is set to delete them.
|
||||||
|
if !keepFailedPaymentAttempts {
|
||||||
|
payments[2].htlcs = 1
|
||||||
|
}
|
||||||
|
assertPayments(t, db, payments)
|
||||||
|
|
||||||
|
if keepFailedPaymentAttempts {
|
||||||
|
// DeleteFailedAttempts is ignored, even for non-existent
|
||||||
|
// payments, if the control tower is configured to keep failed
|
||||||
|
// HTLCs.
|
||||||
|
require.NoError(t, pControl.DeleteFailedAttempts(lntypes.ZeroHash))
|
||||||
|
} else {
|
||||||
|
// Attempting to cleanup a non-existent payment returns an error.
|
||||||
|
require.Error(t, pControl.DeleteFailedAttempts(lntypes.ZeroHash))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// assertPaymentStatus retrieves the status of the payment referred to by hash
|
// assertPaymentStatus retrieves the status of the payment referred to by hash
|
||||||
// and compares it with the expected state.
|
// and compares it with the expected state.
|
||||||
func assertPaymentStatus(t *testing.T, p *PaymentControl,
|
func assertPaymentStatus(t *testing.T, p *PaymentControl,
|
||||||
|
@@ -19,6 +19,11 @@ type ControlTower interface {
|
|||||||
// hash.
|
// hash.
|
||||||
InitPayment(lntypes.Hash, *channeldb.PaymentCreationInfo) error
|
InitPayment(lntypes.Hash, *channeldb.PaymentCreationInfo) error
|
||||||
|
|
||||||
|
// DeleteFailedAttempts removes all failed HTLCs from the db. It should
|
||||||
|
// be called for a given payment whenever all inflight htlcs are
|
||||||
|
// completed, and the payment has reached a final settled state.
|
||||||
|
DeleteFailedAttempts(lntypes.Hash) error
|
||||||
|
|
||||||
// RegisterAttempt atomically records the provided HTLCAttemptInfo.
|
// RegisterAttempt atomically records the provided HTLCAttemptInfo.
|
||||||
RegisterAttempt(lntypes.Hash, *channeldb.HTLCAttemptInfo) error
|
RegisterAttempt(lntypes.Hash, *channeldb.HTLCAttemptInfo) error
|
||||||
|
|
||||||
@@ -125,6 +130,12 @@ func (p *controlTower) InitPayment(paymentHash lntypes.Hash,
|
|||||||
return p.db.InitPayment(paymentHash, info)
|
return p.db.InitPayment(paymentHash, info)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeleteFailedAttempts deletes all failed htlcs if the payment was
|
||||||
|
// successfully settled.
|
||||||
|
func (p *controlTower) DeleteFailedAttempts(paymentHash lntypes.Hash) error {
|
||||||
|
return p.db.DeleteFailedAttempts(paymentHash)
|
||||||
|
}
|
||||||
|
|
||||||
// RegisterAttempt atomically records the provided HTLCAttemptInfo to the
|
// RegisterAttempt atomically records the provided HTLCAttemptInfo to the
|
||||||
// DB.
|
// DB.
|
||||||
func (p *controlTower) RegisterAttempt(paymentHash lntypes.Hash,
|
func (p *controlTower) RegisterAttempt(paymentHash lntypes.Hash,
|
||||||
|
@@ -309,6 +309,32 @@ func (m *mockControlTowerOld) InitPayment(phash lntypes.Hash,
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *mockControlTowerOld) DeleteFailedAttempts(phash lntypes.Hash) error {
|
||||||
|
p, ok := m.payments[phash]
|
||||||
|
if !ok {
|
||||||
|
return channeldb.ErrPaymentNotInitiated
|
||||||
|
}
|
||||||
|
|
||||||
|
var inFlight bool
|
||||||
|
for _, a := range p.attempts {
|
||||||
|
if a.Settle != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if a.Failure != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
inFlight = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if inFlight {
|
||||||
|
return channeldb.ErrPaymentInFlight
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (m *mockControlTowerOld) RegisterAttempt(phash lntypes.Hash,
|
func (m *mockControlTowerOld) RegisterAttempt(phash lntypes.Hash,
|
||||||
a *channeldb.HTLCAttemptInfo) error {
|
a *channeldb.HTLCAttemptInfo) error {
|
||||||
|
|
||||||
@@ -665,6 +691,11 @@ func (m *mockControlTower) InitPayment(phash lntypes.Hash,
|
|||||||
return args.Error(0)
|
return args.Error(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *mockControlTower) DeleteFailedAttempts(phash lntypes.Hash) error {
|
||||||
|
args := m.Called(phash)
|
||||||
|
return args.Error(0)
|
||||||
|
}
|
||||||
|
|
||||||
func (m *mockControlTower) RegisterAttempt(phash lntypes.Hash,
|
func (m *mockControlTower) RegisterAttempt(phash lntypes.Hash,
|
||||||
a *channeldb.HTLCAttemptInfo) error {
|
a *channeldb.HTLCAttemptInfo) error {
|
||||||
|
|
||||||
|
@@ -186,9 +186,21 @@ lifecycle:
|
|||||||
// Find the first successful shard and return
|
// Find the first successful shard and return
|
||||||
// the preimage and route.
|
// the preimage and route.
|
||||||
for _, a := range payment.HTLCs {
|
for _, a := range payment.HTLCs {
|
||||||
if a.Settle != nil {
|
if a.Settle == nil {
|
||||||
return a.Settle.Preimage, &a.Route, nil
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err := p.router.cfg.Control.
|
||||||
|
DeleteFailedAttempts(
|
||||||
|
p.identifier)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Error deleting failed "+
|
||||||
|
"payment attempts for "+
|
||||||
|
"payment %v: %v", p.identifier,
|
||||||
|
err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return a.Settle.Preimage, &a.Route, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Payment failed.
|
// Payment failed.
|
||||||
|
@@ -3571,6 +3571,7 @@ func TestSendMPPaymentSucceed(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
controlTower.On("DeleteFailedAttempts", identifier).Return(nil)
|
||||||
|
|
||||||
// Call the actual method SendPayment on router. This is place inside a
|
// Call the actual method SendPayment on router. This is place inside a
|
||||||
// goroutine so we can set a timeout for the whole test, in case
|
// goroutine so we can set a timeout for the whole test, in case
|
||||||
@@ -3782,6 +3783,7 @@ func TestSendMPPaymentSucceedOnExtraShards(t *testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
controlTower.On("DeleteFailedAttempts", identifier).Return(nil)
|
||||||
|
|
||||||
// Call the actual method SendPayment on router. This is place inside a
|
// Call the actual method SendPayment on router. This is place inside a
|
||||||
// goroutine so we can set a timeout for the whole test, in case
|
// goroutine so we can set a timeout for the whole test, in case
|
||||||
|
Reference in New Issue
Block a user