From 252ebdea5b74026ec84bbbc85ce757510600f6c6 Mon Sep 17 00:00:00 2001 From: ziggie Date: Fri, 1 Aug 2025 16:55:40 +0200 Subject: [PATCH 1/8] channeldb: rename payment_control files We rename the file to payment_kv_store to highlight that this is the kv implementation of the payment db backend. --- channeldb/{payment_control.go => payments_kv_store.go} | 0 channeldb/{payment_control_test.go => payments_kv_store_test.go} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename channeldb/{payment_control.go => payments_kv_store.go} (100%) rename channeldb/{payment_control_test.go => payments_kv_store_test.go} (100%) diff --git a/channeldb/payment_control.go b/channeldb/payments_kv_store.go similarity index 100% rename from channeldb/payment_control.go rename to channeldb/payments_kv_store.go diff --git a/channeldb/payment_control_test.go b/channeldb/payments_kv_store_test.go similarity index 100% rename from channeldb/payment_control_test.go rename to channeldb/payments_kv_store_test.go From 2d07d44d1a0b34a6bd1d3000a3b633107d8e2092 Mon Sep 17 00:00:00 2001 From: ziggie Date: Fri, 1 Aug 2025 17:01:19 +0200 Subject: [PATCH 2/8] channeldb: rename PaymentControl struct In the following commits we will gradually unify the current payment db operations into an interface to later down the road support both backends (sql+kv). --- channeldb/payments_kv_store.go | 34 +++++++-------- channeldb/payments_kv_store_test.go | 66 ++++++++++++++--------------- channeldb/payments_test.go | 4 +- routing/control_tower.go | 4 +- routing/control_tower_test.go | 38 ++++++++--------- server.go | 2 +- 6 files changed, 74 insertions(+), 74 deletions(-) diff --git a/channeldb/payments_kv_store.go b/channeldb/payments_kv_store.go index 61739e92d..24124ba7a 100644 --- a/channeldb/payments_kv_store.go +++ b/channeldb/payments_kv_store.go @@ -130,17 +130,17 @@ var ( "does not exist") ) -// PaymentControl implements persistence for payments and payment attempts. -type PaymentControl struct { +// KVPaymentsDB implements persistence for payments and payment attempts. +type KVPaymentsDB struct { paymentSeqMx sync.Mutex currPaymentSeq uint64 storedPaymentSeq uint64 db *DB } -// NewPaymentControl creates a new instance of the PaymentControl. -func NewPaymentControl(db *DB) *PaymentControl { - return &PaymentControl{ +// NewKVPaymentsDB creates a new instance of the KVPaymentsDB. +func NewKVPaymentsDB(db *DB) *KVPaymentsDB { + return &KVPaymentsDB{ db: db, } } @@ -149,7 +149,7 @@ func NewPaymentControl(db *DB) *PaymentControl { // making sure it does not already exist as an in-flight payment. When this // method returns successfully, the payment is guaranteed to be in the InFlight // state. -func (p *PaymentControl) InitPayment(paymentHash lntypes.Hash, +func (p *KVPaymentsDB) InitPayment(paymentHash lntypes.Hash, info *PaymentCreationInfo) error { // Obtain a new sequence number for this payment. This is used @@ -252,8 +252,8 @@ func (p *PaymentControl) InitPayment(paymentHash lntypes.Hash, } // DeleteFailedAttempts deletes all failed htlcs for a payment if configured -// by the PaymentControl db. -func (p *PaymentControl) DeleteFailedAttempts(hash lntypes.Hash) error { +// by the KVPaymentsDB db. +func (p *KVPaymentsDB) DeleteFailedAttempts(hash lntypes.Hash) error { if !p.db.keepFailedPaymentAttempts { const failedHtlcsOnly = true err := p.db.DeletePayment(hash, failedHtlcsOnly) @@ -318,7 +318,7 @@ func deserializePaymentIndex(r io.Reader) (lntypes.Hash, error) { // RegisterAttempt atomically records the provided HTLCAttemptInfo to the // DB. -func (p *PaymentControl) RegisterAttempt(paymentHash lntypes.Hash, +func (p *KVPaymentsDB) RegisterAttempt(paymentHash lntypes.Hash, attempt *HTLCAttemptInfo) (*MPPayment, error) { // Serialize the information before opening the db transaction. @@ -467,7 +467,7 @@ func (p *PaymentControl) RegisterAttempt(paymentHash lntypes.Hash, // After invoking this method, InitPayment should always return an error to // prevent us from making duplicate payments to the same payment hash. The // provided preimage is atomically saved to the DB for record keeping. -func (p *PaymentControl) SettleAttempt(hash lntypes.Hash, +func (p *KVPaymentsDB) SettleAttempt(hash lntypes.Hash, attemptID uint64, settleInfo *HTLCSettleInfo) (*MPPayment, error) { var b bytes.Buffer @@ -480,7 +480,7 @@ func (p *PaymentControl) SettleAttempt(hash lntypes.Hash, } // FailAttempt marks the given payment attempt failed. -func (p *PaymentControl) FailAttempt(hash lntypes.Hash, +func (p *KVPaymentsDB) FailAttempt(hash lntypes.Hash, attemptID uint64, failInfo *HTLCFailInfo) (*MPPayment, error) { var b bytes.Buffer @@ -493,7 +493,7 @@ func (p *PaymentControl) FailAttempt(hash lntypes.Hash, } // updateHtlcKey updates a database key for the specified htlc. -func (p *PaymentControl) updateHtlcKey(paymentHash lntypes.Hash, +func (p *KVPaymentsDB) updateHtlcKey(paymentHash lntypes.Hash, attemptID uint64, key, value []byte) (*MPPayment, error) { aid := make([]byte, 8) @@ -561,7 +561,7 @@ func (p *PaymentControl) updateHtlcKey(paymentHash lntypes.Hash, // payment failed. After invoking this method, InitPayment should return nil on // its next call for this payment hash, allowing the switch to make a // subsequent payment. -func (p *PaymentControl) Fail(paymentHash lntypes.Hash, +func (p *KVPaymentsDB) Fail(paymentHash lntypes.Hash, reason FailureReason) (*MPPayment, error) { var ( @@ -585,7 +585,7 @@ func (p *PaymentControl) Fail(paymentHash lntypes.Hash, // We mark the payment as failed as long as it is known. This // lets the last attempt to fail with a terminal write its - // failure to the PaymentControl without synchronizing with + // failure to the KVPaymentsDB without synchronizing with // other attempts. _, err = fetchPaymentStatus(bucket) if errors.Is(err, ErrPaymentNotInitiated) { @@ -618,7 +618,7 @@ func (p *PaymentControl) Fail(paymentHash lntypes.Hash, } // FetchPayment returns information about a payment from the database. -func (p *PaymentControl) FetchPayment(paymentHash lntypes.Hash) ( +func (p *KVPaymentsDB) FetchPayment(paymentHash lntypes.Hash) ( *MPPayment, error) { var payment *MPPayment @@ -714,7 +714,7 @@ func fetchPaymentBucketUpdate(tx kvdb.RwTx, paymentHash lntypes.Hash) ( // nextPaymentSequence returns the next sequence number to store for a new // payment. -func (p *PaymentControl) nextPaymentSequence() ([]byte, error) { +func (p *KVPaymentsDB) nextPaymentSequence() ([]byte, error) { p.paymentSeqMx.Lock() defer p.paymentSeqMx.Unlock() @@ -774,7 +774,7 @@ func fetchPaymentStatus(bucket kvdb.RBucket) (PaymentStatus, error) { } // FetchInFlightPayments returns all payments with status InFlight. -func (p *PaymentControl) FetchInFlightPayments() ([]*MPPayment, error) { +func (p *KVPaymentsDB) FetchInFlightPayments() ([]*MPPayment, error) { var ( inFlights []*MPPayment start = time.Now() diff --git a/channeldb/payments_kv_store_test.go b/channeldb/payments_kv_store_test.go index 9bc486a83..c7e7edcd6 100644 --- a/channeldb/payments_kv_store_test.go +++ b/channeldb/payments_kv_store_test.go @@ -54,16 +54,16 @@ func genInfo(t *testing.T) (*PaymentCreationInfo, *HTLCAttemptInfo, }, &attempt.HTLCAttemptInfo, preimage, nil } -// TestPaymentControlSwitchFail checks that payment status returns to Failed +// TestKVPaymentsDBSwitchFail checks that payment status returns to Failed // status after failing, and that InitPayment allows another HTLC for the // same payment hash. -func TestPaymentControlSwitchFail(t *testing.T) { +func TestKVPaymentsDBSwitchFail(t *testing.T) { t.Parallel() db, err := MakeTestDB(t) require.NoError(t, err, "unable to init db") - pControl := NewPaymentControl(db) + pControl := NewKVPaymentsDB(db) info, attempt, preimg, err := genInfo(t) require.NoError(t, err, "unable to generate htlc message") @@ -191,15 +191,15 @@ func TestPaymentControlSwitchFail(t *testing.T) { } } -// TestPaymentControlSwitchDoubleSend checks the ability of payment control to +// TestKVPaymentsDBSwitchDoubleSend checks the ability of payment control to // prevent double sending of htlc message, when message is in StatusInFlight. -func TestPaymentControlSwitchDoubleSend(t *testing.T) { +func TestKVPaymentsDBSwitchDoubleSend(t *testing.T) { t.Parallel() db, err := MakeTestDB(t) require.NoError(t, err, "unable to init db") - pControl := NewPaymentControl(db) + pControl := NewKVPaymentsDB(db) info, attempt, preimg, err := genInfo(t) require.NoError(t, err, "unable to generate htlc message") @@ -261,15 +261,15 @@ func TestPaymentControlSwitchDoubleSend(t *testing.T) { } } -// TestPaymentControlSuccessesWithoutInFlight checks that the payment +// TestKVPaymentsDBSuccessesWithoutInFlight checks that the payment // control will disallow calls to Success when no payment is in flight. -func TestPaymentControlSuccessesWithoutInFlight(t *testing.T) { +func TestKVPaymentsDBSuccessesWithoutInFlight(t *testing.T) { t.Parallel() db, err := MakeTestDB(t) require.NoError(t, err, "unable to init db") - pControl := NewPaymentControl(db) + pControl := NewKVPaymentsDB(db) info, _, preimg, err := genInfo(t) require.NoError(t, err, "unable to generate htlc message") @@ -286,15 +286,15 @@ func TestPaymentControlSuccessesWithoutInFlight(t *testing.T) { } } -// TestPaymentControlFailsWithoutInFlight checks that a strict payment +// TestKVPaymentsDBFailsWithoutInFlight checks that a strict payment // control will disallow calls to Fail when no payment is in flight. -func TestPaymentControlFailsWithoutInFlight(t *testing.T) { +func TestKVPaymentsDBFailsWithoutInFlight(t *testing.T) { t.Parallel() db, err := MakeTestDB(t) require.NoError(t, err, "unable to init db") - pControl := NewPaymentControl(db) + pControl := NewKVPaymentsDB(db) info, _, _, err := genInfo(t) require.NoError(t, err, "unable to generate htlc message") @@ -306,9 +306,9 @@ func TestPaymentControlFailsWithoutInFlight(t *testing.T) { } } -// TestPaymentControlDeleteNonInFlight checks that calling DeletePayments only +// TestKVPaymentsDBDeleteNonInFlight checks that calling DeletePayments only // deletes payments from the database that are not in-flight. -func TestPaymentControlDeleteNonInFlight(t *testing.T) { +func TestKVPaymentsDBDeleteNonInFlight(t *testing.T) { t.Parallel() db, err := MakeTestDB(t) @@ -319,7 +319,7 @@ func TestPaymentControlDeleteNonInFlight(t *testing.T) { // start at 1, so 9999 is a safe bet for this test. var duplicateSeqNr = 9999 - pControl := NewPaymentControl(db) + pControl := NewKVPaymentsDB(db) payments := []struct { failed bool @@ -513,15 +513,15 @@ func TestPaymentControlDeleteNonInFlight(t *testing.T) { require.Equal(t, 1, indexCount) } -// TestPaymentControlDeletePayments tests that DeletePayments correctly deletes +// TestKVPaymentsDBDeletePayments tests that DeletePayments correctly deletes // information about completed payments from the database. -func TestPaymentControlDeletePayments(t *testing.T) { +func TestKVPaymentsDBDeletePayments(t *testing.T) { t.Parallel() db, err := MakeTestDB(t) require.NoError(t, err, "unable to init db") - pControl := NewPaymentControl(db) + pControl := NewKVPaymentsDB(db) // Register three payments: // 1. A payment with two failed attempts. @@ -574,15 +574,15 @@ func TestPaymentControlDeletePayments(t *testing.T) { assertPayments(t, db, payments[2:]) } -// TestPaymentControlDeleteSinglePayment tests that DeletePayment correctly +// TestKVPaymentsDBDeleteSinglePayment tests that DeletePayment correctly // deletes information about a completed payment from the database. -func TestPaymentControlDeleteSinglePayment(t *testing.T) { +func TestKVPaymentsDBDeleteSinglePayment(t *testing.T) { t.Parallel() db, err := MakeTestDB(t) require.NoError(t, err, "unable to init db") - pControl := NewPaymentControl(db) + pControl := NewKVPaymentsDB(db) // Register four payments: // All payments will have one failed HTLC attempt and one HTLC attempt @@ -660,9 +660,9 @@ func TestPaymentControlDeleteSinglePayment(t *testing.T) { assertPayments(t, db, payments[3:]) } -// TestPaymentControlMultiShard checks the ability of payment control to +// TestKVPaymentsDBMultiShard checks the ability of payment control to // have multiple in-flight HTLCs for a single payment. -func TestPaymentControlMultiShard(t *testing.T) { +func TestKVPaymentsDBMultiShard(t *testing.T) { t.Parallel() // We will register three HTLC attempts, and always fail the second @@ -687,7 +687,7 @@ func TestPaymentControlMultiShard(t *testing.T) { t.Fatalf("unable to init db: %v", err) } - pControl := NewPaymentControl(db) + pControl := NewKVPaymentsDB(db) info, attempt, preimg, err := genInfo(t) if err != nil { @@ -945,13 +945,13 @@ func TestPaymentControlMultiShard(t *testing.T) { } } -func TestPaymentControlMPPRecordValidation(t *testing.T) { +func TestKVPaymentsDBMPPRecordValidation(t *testing.T) { t.Parallel() db, err := MakeTestDB(t) require.NoError(t, err, "unable to init db") - pControl := NewPaymentControl(db) + pControl := NewKVPaymentsDB(db) info, attempt, _, err := genInfo(t) require.NoError(t, err, "unable to generate htlc message") @@ -1044,7 +1044,7 @@ func testDeleteFailedAttempts(t *testing.T, keepFailedPaymentAttempts bool) { require.NoError(t, err, "unable to init db") db.keepFailedPaymentAttempts = keepFailedPaymentAttempts - pControl := NewPaymentControl(db) + pControl := NewKVPaymentsDB(db) // Register three payments: // All payments will have one failed HTLC attempt and one HTLC attempt @@ -1116,7 +1116,7 @@ func testDeleteFailedAttempts(t *testing.T, keepFailedPaymentAttempts bool) { // assertPaymentStatus retrieves the status of the payment referred to by hash // and compares it with the expected state. -func assertPaymentStatus(t *testing.T, p *PaymentControl, +func assertPaymentStatus(t *testing.T, p *KVPaymentsDB, hash lntypes.Hash, expStatus PaymentStatus) { t.Helper() @@ -1143,7 +1143,7 @@ type htlcStatus struct { // assertPaymentInfo retrieves the payment referred to by hash and verifies the // expected values. -func assertPaymentInfo(t *testing.T, p *PaymentControl, hash lntypes.Hash, +func assertPaymentInfo(t *testing.T, p *KVPaymentsDB, hash lntypes.Hash, c *PaymentCreationInfo, f *FailureReason, a *htlcStatus) { t.Helper() @@ -1210,7 +1210,7 @@ func assertPaymentInfo(t *testing.T, p *PaymentControl, hash lntypes.Hash, // fetchPaymentIndexEntry gets the payment hash for the sequence number provided // from our payment indexes bucket. -func fetchPaymentIndexEntry(_ *testing.T, p *PaymentControl, +func fetchPaymentIndexEntry(_ *testing.T, p *KVPaymentsDB, sequenceNumber uint64) (*lntypes.Hash, error) { var hash lntypes.Hash @@ -1241,7 +1241,7 @@ func fetchPaymentIndexEntry(_ *testing.T, p *PaymentControl, // assertPaymentIndex looks up the index for a payment in the db and checks // that its payment hash matches the expected hash passed in. -func assertPaymentIndex(t *testing.T, p *PaymentControl, +func assertPaymentIndex(t *testing.T, p *KVPaymentsDB, expectedHash lntypes.Hash) { // Lookup the payment so that we have its sequence number and check @@ -1256,7 +1256,7 @@ func assertPaymentIndex(t *testing.T, p *PaymentControl, // assertNoIndex checks that an index for the sequence number provided does not // exist. -func assertNoIndex(t *testing.T, p *PaymentControl, seqNr uint64) { +func assertNoIndex(t *testing.T, p *KVPaymentsDB, seqNr uint64) { _, err := fetchPaymentIndexEntry(t, p, seqNr) require.Equal(t, errNoSequenceNrIndex, err) } @@ -1272,7 +1272,7 @@ type payment struct { // createTestPayments registers payments depending on the provided statuses in // the payments slice. Each payment will receive one failed HTLC and another // HTLC depending on the final status of the payment provided. -func createTestPayments(t *testing.T, p *PaymentControl, payments []*payment) { +func createTestPayments(t *testing.T, p *KVPaymentsDB, payments []*payment) { attemptID := uint64(0) for i := 0; i < len(payments); i++ { diff --git a/channeldb/payments_test.go b/channeldb/payments_test.go index b2a0292a4..7a2312d03 100644 --- a/channeldb/payments_test.go +++ b/channeldb/payments_test.go @@ -516,7 +516,7 @@ func TestQueryPayments(t *testing.T) { // where we have duplicates in the nested duplicates // bucket. nonDuplicatePayments := 6 - pControl := NewPaymentControl(db) + pControl := NewKVPaymentsDB(db) for i := 0; i < nonDuplicatePayments; i++ { // Generate a test payment. @@ -618,7 +618,7 @@ func TestFetchPaymentWithSequenceNumber(t *testing.T) { db, err := MakeTestDB(t) require.NoError(t, err) - pControl := NewPaymentControl(db) + pControl := NewKVPaymentsDB(db) // Generate a test payment which does not have duplicates. noDuplicates, _, _, err := genInfo(t) diff --git a/routing/control_tower.go b/routing/control_tower.go index 61ab33bd2..b2e4b7bd5 100644 --- a/routing/control_tower.go +++ b/routing/control_tower.go @@ -151,7 +151,7 @@ func (s *controlTowerSubscriberImpl) Updates() <-chan interface{} { // controlTower is persistent implementation of ControlTower to restrict // double payment sending. type controlTower struct { - db *channeldb.PaymentControl + db *channeldb.KVPaymentsDB // subscriberIndex is used to provide a unique id for each subscriber // to all payments. This is used to easily remove the subscriber when @@ -168,7 +168,7 @@ type controlTower struct { } // NewControlTower creates a new instance of the controlTower. -func NewControlTower(db *channeldb.PaymentControl) ControlTower { +func NewControlTower(db *channeldb.KVPaymentsDB) ControlTower { return &controlTower{ db: db, subscribersAllPayments: make( diff --git a/routing/control_tower_test.go b/routing/control_tower_test.go index 532e639d6..697fa74d5 100644 --- a/routing/control_tower_test.go +++ b/routing/control_tower_test.go @@ -49,7 +49,7 @@ func TestControlTowerSubscribeUnknown(t *testing.T) { db := initDB(t, false) - pControl := NewControlTower(channeldb.NewPaymentControl(db)) + pControl := NewControlTower(channeldb.NewKVPaymentsDB(db)) // Subscription should fail when the payment is not known. _, err := pControl.SubscribePayment(lntypes.Hash{1}) @@ -63,7 +63,7 @@ func TestControlTowerSubscribeSuccess(t *testing.T) { db := initDB(t, false) - pControl := NewControlTower(channeldb.NewPaymentControl(db)) + pControl := NewControlTower(channeldb.NewKVPaymentsDB(db)) // Initiate a payment. info, attempt, preimg, err := genInfo() @@ -156,33 +156,33 @@ func TestControlTowerSubscribeSuccess(t *testing.T) { } } -// TestPaymentControlSubscribeFail tests that payment updates for a +// TestKVPaymentsDBSubscribeFail tests that payment updates for a // failed payment are properly sent to subscribers. -func TestPaymentControlSubscribeFail(t *testing.T) { +func TestKVPaymentsDBSubscribeFail(t *testing.T) { t.Parallel() t.Run("register attempt, keep failed payments", func(t *testing.T) { - testPaymentControlSubscribeFail(t, true, true) + testKVPaymentsDBSubscribeFail(t, true, true) }) t.Run("register attempt, delete failed payments", func(t *testing.T) { - testPaymentControlSubscribeFail(t, true, false) + testKVPaymentsDBSubscribeFail(t, true, false) }) t.Run("no register attempt, keep failed payments", func(t *testing.T) { - testPaymentControlSubscribeFail(t, false, true) + testKVPaymentsDBSubscribeFail(t, false, true) }) t.Run("no register attempt, delete failed payments", func(t *testing.T) { - testPaymentControlSubscribeFail(t, false, false) + testKVPaymentsDBSubscribeFail(t, false, false) }) } -// TestPaymentControlSubscribeAllSuccess tests that multiple payments are +// TestKVPaymentsDBSubscribeAllSuccess tests that multiple payments are // properly sent to subscribers of TrackPayments. -func TestPaymentControlSubscribeAllSuccess(t *testing.T) { +func TestKVPaymentsDBSubscribeAllSuccess(t *testing.T) { t.Parallel() db := initDB(t, true) - pControl := NewControlTower(channeldb.NewPaymentControl(db)) + pControl := NewControlTower(channeldb.NewKVPaymentsDB(db)) // Initiate a payment. info1, attempt1, preimg1, err := genInfo() @@ -288,14 +288,14 @@ func TestPaymentControlSubscribeAllSuccess(t *testing.T) { require.Equal(t, attempt2.Route, htlc2.Route, "unexpected htlc route.") } -// TestPaymentControlSubscribeAllImmediate tests whether already inflight +// TestKVPaymentsDBSubscribeAllImmediate tests whether already inflight // payments are reported at the start of the SubscribeAllPayments subscription. -func TestPaymentControlSubscribeAllImmediate(t *testing.T) { +func TestKVPaymentsDBSubscribeAllImmediate(t *testing.T) { t.Parallel() db := initDB(t, true) - pControl := NewControlTower(channeldb.NewPaymentControl(db)) + pControl := NewControlTower(channeldb.NewKVPaymentsDB(db)) // Initiate a payment. info, attempt, _, err := genInfo() @@ -325,14 +325,14 @@ func TestPaymentControlSubscribeAllImmediate(t *testing.T) { } } -// TestPaymentControlUnsubscribeSuccess tests that when unsubscribed, there are +// TestKVPaymentsDBUnsubscribeSuccess tests that when unsubscribed, there are // no more notifications to that specific subscription. -func TestPaymentControlUnsubscribeSuccess(t *testing.T) { +func TestKVPaymentsDBUnsubscribeSuccess(t *testing.T) { t.Parallel() db := initDB(t, true) - pControl := NewControlTower(channeldb.NewPaymentControl(db)) + pControl := NewControlTower(channeldb.NewKVPaymentsDB(db)) subscription1, err := pControl.SubscribeAllPayments() require.NoError(t, err, "expected subscribe to succeed, but got: %v") @@ -396,12 +396,12 @@ func TestPaymentControlUnsubscribeSuccess(t *testing.T) { require.Len(t, subscription2.Updates(), 0) } -func testPaymentControlSubscribeFail(t *testing.T, registerAttempt, +func testKVPaymentsDBSubscribeFail(t *testing.T, registerAttempt, keepFailedPaymentAttempts bool) { db := initDB(t, keepFailedPaymentAttempts) - pControl := NewControlTower(channeldb.NewPaymentControl(db)) + pControl := NewControlTower(channeldb.NewKVPaymentsDB(db)) // Initiate a payment. info, attempt, _, err := genInfo() diff --git a/server.go b/server.go index 7eb8c583d..ebc513fdd 100644 --- a/server.go +++ b/server.go @@ -1127,7 +1127,7 @@ func newServer(ctx context.Context, cfg *Config, listenAddrs []net.Addr, PathFindingConfig: pathFindingConfig, } - paymentControl := channeldb.NewPaymentControl(dbs.ChanStateDB) + paymentControl := channeldb.NewKVPaymentsDB(dbs.ChanStateDB) s.controlTower = routing.NewControlTower(paymentControl) From 8bd4744ee8bc38650cfca64892ad5b1682f26e5e Mon Sep 17 00:00:00 2001 From: ziggie Date: Fri, 1 Aug 2025 17:03:11 +0200 Subject: [PATCH 3/8] channeldb: rename pControl to paymentDB in tests --- channeldb/payments_kv_store_test.go | 208 ++++++++++++++-------------- channeldb/payments_test.go | 20 +-- 2 files changed, 114 insertions(+), 114 deletions(-) diff --git a/channeldb/payments_kv_store_test.go b/channeldb/payments_kv_store_test.go index c7e7edcd6..41bb380b6 100644 --- a/channeldb/payments_kv_store_test.go +++ b/channeldb/payments_kv_store_test.go @@ -63,64 +63,64 @@ func TestKVPaymentsDBSwitchFail(t *testing.T) { db, err := MakeTestDB(t) require.NoError(t, err, "unable to init db") - pControl := NewKVPaymentsDB(db) + paymentDB := NewKVPaymentsDB(db) info, attempt, preimg, err := genInfo(t) require.NoError(t, err, "unable to generate htlc message") // Sends base htlc message which initiate StatusInFlight. - err = pControl.InitPayment(info.PaymentIdentifier, info) + err = paymentDB.InitPayment(info.PaymentIdentifier, info) require.NoError(t, err, "unable to send htlc message") - assertPaymentIndex(t, pControl, info.PaymentIdentifier) + assertPaymentIndex(t, paymentDB, info.PaymentIdentifier) assertPaymentStatus( - t, pControl, info.PaymentIdentifier, StatusInitiated, + t, paymentDB, info.PaymentIdentifier, StatusInitiated, ) assertPaymentInfo( - t, pControl, info.PaymentIdentifier, info, nil, nil, + t, paymentDB, info.PaymentIdentifier, info, nil, nil, ) // Fail the payment, which should moved it to Failed. failReason := FailureReasonNoRoute - _, err = pControl.Fail(info.PaymentIdentifier, failReason) + _, err = paymentDB.Fail(info.PaymentIdentifier, failReason) require.NoError(t, err, "unable to fail payment hash") // Verify the status is indeed Failed. - assertPaymentStatus(t, pControl, info.PaymentIdentifier, StatusFailed) + assertPaymentStatus(t, paymentDB, info.PaymentIdentifier, StatusFailed) assertPaymentInfo( - t, pControl, info.PaymentIdentifier, info, &failReason, nil, + t, paymentDB, info.PaymentIdentifier, info, &failReason, nil, ) // Lookup the payment so we can get its old sequence number before it is // overwritten. - payment, err := pControl.FetchPayment(info.PaymentIdentifier) + payment, err := paymentDB.FetchPayment(info.PaymentIdentifier) require.NoError(t, err) // Sends the htlc again, which should succeed since the prior payment // failed. - err = pControl.InitPayment(info.PaymentIdentifier, info) + err = paymentDB.InitPayment(info.PaymentIdentifier, info) require.NoError(t, err, "unable to send htlc message") // Check that our index has been updated, and the old index has been // removed. - assertPaymentIndex(t, pControl, info.PaymentIdentifier) - assertNoIndex(t, pControl, payment.SequenceNum) + assertPaymentIndex(t, paymentDB, info.PaymentIdentifier) + assertNoIndex(t, paymentDB, payment.SequenceNum) assertPaymentStatus( - t, pControl, info.PaymentIdentifier, StatusInitiated, + t, paymentDB, info.PaymentIdentifier, StatusInitiated, ) assertPaymentInfo( - t, pControl, info.PaymentIdentifier, info, nil, nil, + t, paymentDB, info.PaymentIdentifier, info, nil, nil, ) // Record a new attempt. In this test scenario, the attempt fails. // However, this is not communicated to control tower in the current // implementation. It only registers the initiation of the attempt. - _, err = pControl.RegisterAttempt(info.PaymentIdentifier, attempt) + _, err = paymentDB.RegisterAttempt(info.PaymentIdentifier, attempt) require.NoError(t, err, "unable to register attempt") htlcReason := HTLCFailUnreadable - _, err = pControl.FailAttempt( + _, err = paymentDB.FailAttempt( info.PaymentIdentifier, attempt.AttemptID, &HTLCFailInfo{ Reason: htlcReason, @@ -130,7 +130,7 @@ func TestKVPaymentsDBSwitchFail(t *testing.T) { t.Fatal(err) } assertPaymentStatus( - t, pControl, info.PaymentIdentifier, StatusInFlight, + t, paymentDB, info.PaymentIdentifier, StatusInFlight, ) htlc := &htlcStatus{ @@ -138,25 +138,25 @@ func TestKVPaymentsDBSwitchFail(t *testing.T) { failure: &htlcReason, } - assertPaymentInfo(t, pControl, info.PaymentIdentifier, info, nil, htlc) + assertPaymentInfo(t, paymentDB, info.PaymentIdentifier, info, nil, htlc) // Record another attempt. attempt.AttemptID = 1 - _, err = pControl.RegisterAttempt(info.PaymentIdentifier, attempt) + _, err = paymentDB.RegisterAttempt(info.PaymentIdentifier, attempt) require.NoError(t, err, "unable to send htlc message") - assertPaymentStatus(t, pControl, info.PaymentIdentifier, StatusInFlight) + assertPaymentStatus(t, paymentDB, info.PaymentIdentifier, StatusInFlight) htlc = &htlcStatus{ HTLCAttemptInfo: attempt, } assertPaymentInfo( - t, pControl, info.PaymentIdentifier, info, nil, htlc, + t, paymentDB, info.PaymentIdentifier, info, nil, htlc, ) // Settle the attempt and verify that status was changed to // StatusSucceeded. - payment, err = pControl.SettleAttempt( + payment, err = paymentDB.SettleAttempt( info.PaymentIdentifier, attempt.AttemptID, &HTLCSettleInfo{ Preimage: preimg, @@ -176,16 +176,16 @@ func TestKVPaymentsDBSwitchFail(t *testing.T) { spew.Sdump(payment.HTLCs[0].Route), err) } - assertPaymentStatus(t, pControl, info.PaymentIdentifier, StatusSucceeded) + assertPaymentStatus(t, paymentDB, info.PaymentIdentifier, StatusSucceeded) htlc.settle = &preimg assertPaymentInfo( - t, pControl, info.PaymentIdentifier, info, nil, htlc, + t, paymentDB, info.PaymentIdentifier, info, nil, htlc, ) // Attempt a final payment, which should now fail since the prior // payment succeed. - err = pControl.InitPayment(info.PaymentIdentifier, info) + err = paymentDB.InitPayment(info.PaymentIdentifier, info) if !errors.Is(err, ErrAlreadyPaid) { t.Fatalf("unable to send htlc message: %v", err) } @@ -199,63 +199,63 @@ func TestKVPaymentsDBSwitchDoubleSend(t *testing.T) { db, err := MakeTestDB(t) require.NoError(t, err, "unable to init db") - pControl := NewKVPaymentsDB(db) + paymentDB := NewKVPaymentsDB(db) info, attempt, preimg, err := genInfo(t) require.NoError(t, err, "unable to generate htlc message") // Sends base htlc message which initiate base status and move it to // StatusInFlight and verifies that it was changed. - err = pControl.InitPayment(info.PaymentIdentifier, info) + err = paymentDB.InitPayment(info.PaymentIdentifier, info) require.NoError(t, err, "unable to send htlc message") - assertPaymentIndex(t, pControl, info.PaymentIdentifier) + assertPaymentIndex(t, paymentDB, info.PaymentIdentifier) assertPaymentStatus( - t, pControl, info.PaymentIdentifier, StatusInitiated, + t, paymentDB, info.PaymentIdentifier, StatusInitiated, ) assertPaymentInfo( - t, pControl, info.PaymentIdentifier, info, nil, nil, + t, paymentDB, info.PaymentIdentifier, info, nil, nil, ) // Try to initiate double sending of htlc message with the same // payment hash, should result in error indicating that payment has // already been sent. - err = pControl.InitPayment(info.PaymentIdentifier, info) + err = paymentDB.InitPayment(info.PaymentIdentifier, info) require.ErrorIs(t, err, ErrPaymentExists) // Record an attempt. - _, err = pControl.RegisterAttempt(info.PaymentIdentifier, attempt) + _, err = paymentDB.RegisterAttempt(info.PaymentIdentifier, attempt) require.NoError(t, err, "unable to send htlc message") - assertPaymentStatus(t, pControl, info.PaymentIdentifier, StatusInFlight) + assertPaymentStatus(t, paymentDB, info.PaymentIdentifier, StatusInFlight) htlc := &htlcStatus{ HTLCAttemptInfo: attempt, } assertPaymentInfo( - t, pControl, info.PaymentIdentifier, info, nil, htlc, + t, paymentDB, info.PaymentIdentifier, info, nil, htlc, ) // Sends base htlc message which initiate StatusInFlight. - err = pControl.InitPayment(info.PaymentIdentifier, info) + err = paymentDB.InitPayment(info.PaymentIdentifier, info) if !errors.Is(err, ErrPaymentInFlight) { t.Fatalf("payment control wrong behaviour: " + "double sending must trigger ErrPaymentInFlight error") } // After settling, the error should be ErrAlreadyPaid. - _, err = pControl.SettleAttempt( + _, err = paymentDB.SettleAttempt( info.PaymentIdentifier, attempt.AttemptID, &HTLCSettleInfo{ Preimage: preimg, }, ) require.NoError(t, err, "error shouldn't have been received, got") - assertPaymentStatus(t, pControl, info.PaymentIdentifier, StatusSucceeded) + assertPaymentStatus(t, paymentDB, info.PaymentIdentifier, StatusSucceeded) htlc.settle = &preimg - assertPaymentInfo(t, pControl, info.PaymentIdentifier, info, nil, htlc) + assertPaymentInfo(t, paymentDB, info.PaymentIdentifier, info, nil, htlc) - err = pControl.InitPayment(info.PaymentIdentifier, info) + err = paymentDB.InitPayment(info.PaymentIdentifier, info) if !errors.Is(err, ErrAlreadyPaid) { t.Fatalf("unable to send htlc message: %v", err) } @@ -269,13 +269,13 @@ func TestKVPaymentsDBSuccessesWithoutInFlight(t *testing.T) { db, err := MakeTestDB(t) require.NoError(t, err, "unable to init db") - pControl := NewKVPaymentsDB(db) + paymentDB := NewKVPaymentsDB(db) info, _, preimg, err := genInfo(t) require.NoError(t, err, "unable to generate htlc message") // Attempt to complete the payment should fail. - _, err = pControl.SettleAttempt( + _, err = paymentDB.SettleAttempt( info.PaymentIdentifier, 0, &HTLCSettleInfo{ Preimage: preimg, @@ -294,13 +294,13 @@ func TestKVPaymentsDBFailsWithoutInFlight(t *testing.T) { db, err := MakeTestDB(t) require.NoError(t, err, "unable to init db") - pControl := NewKVPaymentsDB(db) + paymentDB := NewKVPaymentsDB(db) info, _, _, err := genInfo(t) require.NoError(t, err, "unable to generate htlc message") // Calling Fail should return an error. - _, err = pControl.Fail(info.PaymentIdentifier, FailureReasonNoRoute) + _, err = paymentDB.Fail(info.PaymentIdentifier, FailureReasonNoRoute) if err != ErrPaymentNotInitiated { t.Fatalf("expected ErrPaymentNotInitiated, got %v", err) } @@ -319,7 +319,7 @@ func TestKVPaymentsDBDeleteNonInFlight(t *testing.T) { // start at 1, so 9999 is a safe bet for this test. var duplicateSeqNr = 9999 - pControl := NewKVPaymentsDB(db) + paymentDB := NewKVPaymentsDB(db) payments := []struct { failed bool @@ -357,11 +357,11 @@ func TestKVPaymentsDBDeleteNonInFlight(t *testing.T) { } // Sends base htlc message which initiate StatusInFlight. - err = pControl.InitPayment(info.PaymentIdentifier, info) + err = paymentDB.InitPayment(info.PaymentIdentifier, info) if err != nil { t.Fatalf("unable to send htlc message: %v", err) } - _, err = pControl.RegisterAttempt(info.PaymentIdentifier, attempt) + _, err = paymentDB.RegisterAttempt(info.PaymentIdentifier, attempt) if err != nil { t.Fatalf("unable to send htlc message: %v", err) } @@ -373,7 +373,7 @@ func TestKVPaymentsDBDeleteNonInFlight(t *testing.T) { if p.failed { // Fail the payment attempt. htlcFailure := HTLCFailUnreadable - _, err := pControl.FailAttempt( + _, err := paymentDB.FailAttempt( info.PaymentIdentifier, attempt.AttemptID, &HTLCFailInfo{ Reason: htlcFailure, @@ -385,22 +385,22 @@ func TestKVPaymentsDBDeleteNonInFlight(t *testing.T) { // Fail the payment, which should moved it to Failed. failReason := FailureReasonNoRoute - _, err = pControl.Fail(info.PaymentIdentifier, failReason) + _, err = paymentDB.Fail(info.PaymentIdentifier, failReason) if err != nil { t.Fatalf("unable to fail payment hash: %v", err) } // Verify the status is indeed Failed. - assertPaymentStatus(t, pControl, info.PaymentIdentifier, StatusFailed) + assertPaymentStatus(t, paymentDB, info.PaymentIdentifier, StatusFailed) htlc.failure = &htlcFailure assertPaymentInfo( - t, pControl, info.PaymentIdentifier, info, + t, paymentDB, info.PaymentIdentifier, info, &failReason, htlc, ) } else if p.success { // Verifies that status was changed to StatusSucceeded. - _, err := pControl.SettleAttempt( + _, err := paymentDB.SettleAttempt( info.PaymentIdentifier, attempt.AttemptID, &HTLCSettleInfo{ Preimage: preimg, @@ -410,18 +410,18 @@ func TestKVPaymentsDBDeleteNonInFlight(t *testing.T) { t.Fatalf("error shouldn't have been received, got: %v", err) } - assertPaymentStatus(t, pControl, info.PaymentIdentifier, StatusSucceeded) + assertPaymentStatus(t, paymentDB, info.PaymentIdentifier, StatusSucceeded) htlc.settle = &preimg assertPaymentInfo( - t, pControl, info.PaymentIdentifier, info, nil, htlc, + t, paymentDB, info.PaymentIdentifier, info, nil, htlc, ) numSuccess++ } else { - assertPaymentStatus(t, pControl, info.PaymentIdentifier, StatusInFlight) + assertPaymentStatus(t, paymentDB, info.PaymentIdentifier, StatusInFlight) assertPaymentInfo( - t, pControl, info.PaymentIdentifier, info, nil, htlc, + t, paymentDB, info.PaymentIdentifier, info, nil, htlc, ) numInflight++ @@ -431,7 +431,7 @@ func TestKVPaymentsDBDeleteNonInFlight(t *testing.T) { // add one. if p.hasDuplicate { appendDuplicatePayment( - t, pControl.db, info.PaymentIdentifier, + t, paymentDB.db, info.PaymentIdentifier, uint64(duplicateSeqNr), preimg, ) duplicateSeqNr++ @@ -521,7 +521,7 @@ func TestKVPaymentsDBDeletePayments(t *testing.T) { db, err := MakeTestDB(t) require.NoError(t, err, "unable to init db") - pControl := NewKVPaymentsDB(db) + paymentDB := NewKVPaymentsDB(db) // Register three payments: // 1. A payment with two failed attempts. @@ -535,7 +535,7 @@ func TestKVPaymentsDBDeletePayments(t *testing.T) { // Use helper function to register the test payments in the data and // populate the data to the payments slice. - createTestPayments(t, pControl, payments) + createTestPayments(t, paymentDB, payments) // Check that all payments are there as we added them. assertPayments(t, db, payments) @@ -582,7 +582,7 @@ func TestKVPaymentsDBDeleteSinglePayment(t *testing.T) { db, err := MakeTestDB(t) require.NoError(t, err, "unable to init db") - pControl := NewKVPaymentsDB(db) + paymentDB := NewKVPaymentsDB(db) // Register four payments: // All payments will have one failed HTLC attempt and one HTLC attempt @@ -607,7 +607,7 @@ func TestKVPaymentsDBDeleteSinglePayment(t *testing.T) { // Use helper function to register the test payments in the data and // populate the data to the payments slice. - createTestPayments(t, pControl, payments) + createTestPayments(t, paymentDB, payments) // Check that all payments are there as we added them. assertPayments(t, db, payments) @@ -687,7 +687,7 @@ func TestKVPaymentsDBMultiShard(t *testing.T) { t.Fatalf("unable to init db: %v", err) } - pControl := NewKVPaymentsDB(db) + paymentDB := NewKVPaymentsDB(db) info, attempt, preimg, err := genInfo(t) if err != nil { @@ -695,17 +695,17 @@ func TestKVPaymentsDBMultiShard(t *testing.T) { } // Init the payment, moving it to the StatusInFlight state. - err = pControl.InitPayment(info.PaymentIdentifier, info) + err = paymentDB.InitPayment(info.PaymentIdentifier, info) if err != nil { t.Fatalf("unable to send htlc message: %v", err) } - assertPaymentIndex(t, pControl, info.PaymentIdentifier) + assertPaymentIndex(t, paymentDB, info.PaymentIdentifier) assertPaymentStatus( - t, pControl, info.PaymentIdentifier, StatusInitiated, + t, paymentDB, info.PaymentIdentifier, StatusInitiated, ) assertPaymentInfo( - t, pControl, info.PaymentIdentifier, info, nil, nil, + t, paymentDB, info.PaymentIdentifier, info, nil, nil, ) // Create three unique attempts we'll use for the test, and @@ -724,12 +724,12 @@ func TestKVPaymentsDBMultiShard(t *testing.T) { a.AttemptID = i attempts = append(attempts, &a) - _, err = pControl.RegisterAttempt(info.PaymentIdentifier, &a) + _, err = paymentDB.RegisterAttempt(info.PaymentIdentifier, &a) if err != nil { t.Fatalf("unable to send htlc message: %v", err) } assertPaymentStatus( - t, pControl, info.PaymentIdentifier, + t, paymentDB, info.PaymentIdentifier, StatusInFlight, ) @@ -737,7 +737,7 @@ func TestKVPaymentsDBMultiShard(t *testing.T) { HTLCAttemptInfo: &a, } assertPaymentInfo( - t, pControl, info.PaymentIdentifier, info, nil, htlc, + t, paymentDB, info.PaymentIdentifier, info, nil, htlc, ) } @@ -746,13 +746,13 @@ func TestKVPaymentsDBMultiShard(t *testing.T) { // will be too large. b := *attempt b.AttemptID = 3 - _, err = pControl.RegisterAttempt(info.PaymentIdentifier, &b) + _, err = paymentDB.RegisterAttempt(info.PaymentIdentifier, &b) require.ErrorIs(t, err, ErrValueExceedsAmt) // Fail the second attempt. a := attempts[1] htlcFail := HTLCFailUnreadable - _, err = pControl.FailAttempt( + _, err = paymentDB.FailAttempt( info.PaymentIdentifier, a.AttemptID, &HTLCFailInfo{ Reason: htlcFail, @@ -767,11 +767,11 @@ func TestKVPaymentsDBMultiShard(t *testing.T) { failure: &htlcFail, } assertPaymentInfo( - t, pControl, info.PaymentIdentifier, info, nil, htlc, + t, paymentDB, info.PaymentIdentifier, info, nil, htlc, ) // Payment should still be in-flight. - assertPaymentStatus(t, pControl, info.PaymentIdentifier, StatusInFlight) + assertPaymentStatus(t, paymentDB, info.PaymentIdentifier, StatusInFlight) // Depending on the test case, settle or fail the first attempt. a = attempts[0] @@ -781,7 +781,7 @@ func TestKVPaymentsDBMultiShard(t *testing.T) { var firstFailReason *FailureReason if test.settleFirst { - _, err := pControl.SettleAttempt( + _, err := paymentDB.SettleAttempt( info.PaymentIdentifier, a.AttemptID, &HTLCSettleInfo{ Preimage: preimg, @@ -795,10 +795,10 @@ func TestKVPaymentsDBMultiShard(t *testing.T) { // Assert that the HTLC has had the preimage recorded. htlc.settle = &preimg assertPaymentInfo( - t, pControl, info.PaymentIdentifier, info, nil, htlc, + t, paymentDB, info.PaymentIdentifier, info, nil, htlc, ) } else { - _, err := pControl.FailAttempt( + _, err := paymentDB.FailAttempt( info.PaymentIdentifier, a.AttemptID, &HTLCFailInfo{ Reason: htlcFail, @@ -812,13 +812,13 @@ func TestKVPaymentsDBMultiShard(t *testing.T) { // Assert the failure was recorded. htlc.failure = &htlcFail assertPaymentInfo( - t, pControl, info.PaymentIdentifier, info, nil, htlc, + t, paymentDB, info.PaymentIdentifier, info, nil, htlc, ) // We also record a payment level fail, to move it into // a terminal state. failReason := FailureReasonNoRoute - _, err = pControl.Fail(info.PaymentIdentifier, failReason) + _, err = paymentDB.Fail(info.PaymentIdentifier, failReason) if err != nil { t.Fatalf("unable to fail payment hash: %v", err) } @@ -830,7 +830,7 @@ func TestKVPaymentsDBMultiShard(t *testing.T) { // The payment is now considered pending fail, since // there is still an active HTLC. assertPaymentStatus( - t, pControl, info.PaymentIdentifier, + t, paymentDB, info.PaymentIdentifier, StatusInFlight, ) } @@ -839,14 +839,14 @@ func TestKVPaymentsDBMultiShard(t *testing.T) { // that the payment has reached a terminal condition. b = *attempt b.AttemptID = 3 - _, err = pControl.RegisterAttempt(info.PaymentIdentifier, &b) + _, err = paymentDB.RegisterAttempt(info.PaymentIdentifier, &b) if test.settleFirst { require.ErrorIs(t, err, ErrPaymentPendingSettled) } else { require.ErrorIs(t, err, ErrPaymentPendingFailed) } - assertPaymentStatus(t, pControl, info.PaymentIdentifier, StatusInFlight) + assertPaymentStatus(t, paymentDB, info.PaymentIdentifier, StatusInFlight) // Settle or fail the remaining attempt based on the testcase. a = attempts[2] @@ -855,7 +855,7 @@ func TestKVPaymentsDBMultiShard(t *testing.T) { } if test.settleLast { // Settle the last outstanding attempt. - _, err = pControl.SettleAttempt( + _, err = paymentDB.SettleAttempt( info.PaymentIdentifier, a.AttemptID, &HTLCSettleInfo{ Preimage: preimg, @@ -865,12 +865,12 @@ func TestKVPaymentsDBMultiShard(t *testing.T) { htlc.settle = &preimg assertPaymentInfo( - t, pControl, info.PaymentIdentifier, + t, paymentDB, info.PaymentIdentifier, info, firstFailReason, htlc, ) } else { // Fail the attempt. - _, err := pControl.FailAttempt( + _, err := paymentDB.FailAttempt( info.PaymentIdentifier, a.AttemptID, &HTLCFailInfo{ Reason: htlcFail, @@ -884,7 +884,7 @@ func TestKVPaymentsDBMultiShard(t *testing.T) { // Assert the failure was recorded. htlc.failure = &htlcFail assertPaymentInfo( - t, pControl, info.PaymentIdentifier, info, + t, paymentDB, info.PaymentIdentifier, info, firstFailReason, htlc, ) @@ -893,7 +893,7 @@ func TestKVPaymentsDBMultiShard(t *testing.T) { // write a terminal failure to the database without // syncing. failReason := FailureReasonPaymentDetails - _, err = pControl.Fail(info.PaymentIdentifier, failReason) + _, err = paymentDB.Fail(info.PaymentIdentifier, failReason) require.NoError(t, err, "unable to fail") } @@ -926,11 +926,11 @@ func TestKVPaymentsDBMultiShard(t *testing.T) { } assertPaymentStatus( - t, pControl, info.PaymentIdentifier, finalStatus, + t, paymentDB, info.PaymentIdentifier, finalStatus, ) // Finally assert we cannot register more attempts. - _, err = pControl.RegisterAttempt(info.PaymentIdentifier, &b) + _, err = paymentDB.RegisterAttempt(info.PaymentIdentifier, &b) require.Equal(t, registerErr, err) } @@ -951,13 +951,13 @@ func TestKVPaymentsDBMPPRecordValidation(t *testing.T) { db, err := MakeTestDB(t) require.NoError(t, err, "unable to init db") - pControl := NewKVPaymentsDB(db) + paymentDB := NewKVPaymentsDB(db) info, attempt, _, err := genInfo(t) require.NoError(t, err, "unable to generate htlc message") // Init the payment. - err = pControl.InitPayment(info.PaymentIdentifier, info) + err = paymentDB.InitPayment(info.PaymentIdentifier, info) require.NoError(t, err, "unable to send htlc message") // Create three unique attempts we'll use for the test, and @@ -970,14 +970,14 @@ func TestKVPaymentsDBMPPRecordValidation(t *testing.T) { info.Value, [32]byte{1}, ) - _, err = pControl.RegisterAttempt(info.PaymentIdentifier, attempt) + _, err = paymentDB.RegisterAttempt(info.PaymentIdentifier, attempt) require.NoError(t, err, "unable to send htlc message") // Now try to register a non-MPP attempt, which should fail. b := *attempt b.AttemptID = 1 b.Route.FinalHop().MPP = nil - _, err = pControl.RegisterAttempt(info.PaymentIdentifier, &b) + _, err = paymentDB.RegisterAttempt(info.PaymentIdentifier, &b) if err != ErrMPPayment { t.Fatalf("expected ErrMPPayment, got: %v", err) } @@ -986,7 +986,7 @@ func TestKVPaymentsDBMPPRecordValidation(t *testing.T) { b.Route.FinalHop().MPP = record.NewMPP( info.Value, [32]byte{2}, ) - _, err = pControl.RegisterAttempt(info.PaymentIdentifier, &b) + _, err = paymentDB.RegisterAttempt(info.PaymentIdentifier, &b) if err != ErrMPPPaymentAddrMismatch { t.Fatalf("expected ErrMPPPaymentAddrMismatch, got: %v", err) } @@ -995,7 +995,7 @@ func TestKVPaymentsDBMPPRecordValidation(t *testing.T) { b.Route.FinalHop().MPP = record.NewMPP( info.Value/2, [32]byte{1}, ) - _, err = pControl.RegisterAttempt(info.PaymentIdentifier, &b) + _, err = paymentDB.RegisterAttempt(info.PaymentIdentifier, &b) if err != ErrMPPTotalAmountMismatch { t.Fatalf("expected ErrMPPTotalAmountMismatch, got: %v", err) } @@ -1005,11 +1005,11 @@ func TestKVPaymentsDBMPPRecordValidation(t *testing.T) { info, attempt, _, err = genInfo(t) require.NoError(t, err, "unable to generate htlc message") - err = pControl.InitPayment(info.PaymentIdentifier, info) + err = paymentDB.InitPayment(info.PaymentIdentifier, info) require.NoError(t, err, "unable to send htlc message") attempt.Route.FinalHop().MPP = nil - _, err = pControl.RegisterAttempt(info.PaymentIdentifier, attempt) + _, err = paymentDB.RegisterAttempt(info.PaymentIdentifier, attempt) require.NoError(t, err, "unable to send htlc message") // Attempt to register an MPP attempt, which should fail. @@ -1019,7 +1019,7 @@ func TestKVPaymentsDBMPPRecordValidation(t *testing.T) { info.Value, [32]byte{1}, ) - _, err = pControl.RegisterAttempt(info.PaymentIdentifier, &b) + _, err = paymentDB.RegisterAttempt(info.PaymentIdentifier, &b) if err != ErrNonMPPayment { t.Fatalf("expected ErrNonMPPayment, got: %v", err) } @@ -1044,7 +1044,7 @@ func testDeleteFailedAttempts(t *testing.T, keepFailedPaymentAttempts bool) { require.NoError(t, err, "unable to init db") db.keepFailedPaymentAttempts = keepFailedPaymentAttempts - pControl := NewKVPaymentsDB(db) + paymentDB := NewKVPaymentsDB(db) // Register three payments: // All payments will have one failed HTLC attempt and one HTLC attempt @@ -1067,14 +1067,14 @@ func testDeleteFailedAttempts(t *testing.T, keepFailedPaymentAttempts bool) { // Use helper function to register the test payments in the data and // populate the data to the payments slice. - createTestPayments(t, pControl, payments) + createTestPayments(t, paymentDB, 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)) + require.NoError(t, paymentDB.DeleteFailedAttempts(payments[0].id)) // Expect all HTLCs to be deleted if the config is set to delete them. if !keepFailedPaymentAttempts { @@ -1085,9 +1085,9 @@ func testDeleteFailedAttempts(t *testing.T, keepFailedPaymentAttempts bool) { // Calling DeleteFailedAttempts on an in-flight payment should return // an error. if keepFailedPaymentAttempts { - require.NoError(t, pControl.DeleteFailedAttempts(payments[1].id)) + require.NoError(t, paymentDB.DeleteFailedAttempts(payments[1].id)) } else { - require.Error(t, pControl.DeleteFailedAttempts(payments[1].id)) + require.Error(t, paymentDB.DeleteFailedAttempts(payments[1].id)) } // Since DeleteFailedAttempts returned an error, we should expect the @@ -1095,7 +1095,7 @@ func testDeleteFailedAttempts(t *testing.T, keepFailedPaymentAttempts bool) { assertPayments(t, db, payments) // Cleaning up a successful payment should remove failed htlcs. - require.NoError(t, pControl.DeleteFailedAttempts(payments[2].id)) + require.NoError(t, paymentDB.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 { @@ -1107,10 +1107,10 @@ func testDeleteFailedAttempts(t *testing.T, keepFailedPaymentAttempts bool) { // 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)) + require.NoError(t, paymentDB.DeleteFailedAttempts(lntypes.ZeroHash)) } else { // Attempting to cleanup a non-existent payment returns an error. - require.Error(t, pControl.DeleteFailedAttempts(lntypes.ZeroHash)) + require.Error(t, paymentDB.DeleteFailedAttempts(lntypes.ZeroHash)) } } diff --git a/channeldb/payments_test.go b/channeldb/payments_test.go index 7a2312d03..9514edab6 100644 --- a/channeldb/payments_test.go +++ b/channeldb/payments_test.go @@ -516,7 +516,7 @@ func TestQueryPayments(t *testing.T) { // where we have duplicates in the nested duplicates // bucket. nonDuplicatePayments := 6 - pControl := NewKVPaymentsDB(db) + paymentDB := NewKVPaymentsDB(db) for i := 0; i < nonDuplicatePayments; i++ { // Generate a test payment. @@ -530,7 +530,7 @@ func TestQueryPayments(t *testing.T) { info.CreationTime = time.Unix(int64(i+1), 0) // Create a new payment entry in the database. - err = pControl.InitPayment(info.PaymentIdentifier, info) + err = paymentDB.InitPayment(info.PaymentIdentifier, info) if err != nil { t.Fatalf("unable to initialize "+ "payment in database: %v", err) @@ -538,7 +538,7 @@ func TestQueryPayments(t *testing.T) { // Immediately delete the payment with index 2. if i == 1 { - pmt, err := pControl.FetchPayment( + pmt, err := paymentDB.FetchPayment( info.PaymentIdentifier, ) require.NoError(t, err) @@ -552,13 +552,13 @@ func TestQueryPayments(t *testing.T) { // to the parent payment + 1. Note that // duplicate payments will always be succeeded. if i == (nonDuplicatePayments - 1) { - pmt, err := pControl.FetchPayment( + pmt, err := paymentDB.FetchPayment( info.PaymentIdentifier, ) require.NoError(t, err) appendDuplicatePayment( - t, pControl.db, + t, paymentDB.db, info.PaymentIdentifier, pmt.SequenceNum+1, preimg, @@ -618,18 +618,18 @@ func TestFetchPaymentWithSequenceNumber(t *testing.T) { db, err := MakeTestDB(t) require.NoError(t, err) - pControl := NewKVPaymentsDB(db) + paymentDB := NewKVPaymentsDB(db) // Generate a test payment which does not have duplicates. noDuplicates, _, _, err := genInfo(t) require.NoError(t, err) // Create a new payment entry in the database. - err = pControl.InitPayment(noDuplicates.PaymentIdentifier, noDuplicates) + err = paymentDB.InitPayment(noDuplicates.PaymentIdentifier, noDuplicates) require.NoError(t, err) // Fetch the payment so we can get its sequence nr. - noDuplicatesPayment, err := pControl.FetchPayment( + noDuplicatesPayment, err := paymentDB.FetchPayment( noDuplicates.PaymentIdentifier, ) require.NoError(t, err) @@ -639,11 +639,11 @@ func TestFetchPaymentWithSequenceNumber(t *testing.T) { require.NoError(t, err) // Create a new payment entry in the database. - err = pControl.InitPayment(hasDuplicates.PaymentIdentifier, hasDuplicates) + err = paymentDB.InitPayment(hasDuplicates.PaymentIdentifier, hasDuplicates) require.NoError(t, err) // Fetch the payment so we can get its sequence nr. - hasDuplicatesPayment, err := pControl.FetchPayment( + hasDuplicatesPayment, err := paymentDB.FetchPayment( hasDuplicates.PaymentIdentifier, ) require.NoError(t, err) From 84b2a94da2efab79608e2b6a7e0e34c325649214 Mon Sep 17 00:00:00 2001 From: ziggie Date: Fri, 1 Aug 2025 17:46:48 +0200 Subject: [PATCH 4/8] multi: fix linter issues --- channeldb/payments_kv_store_test.go | 100 ++++++++++++++++++++-------- channeldb/payments_test.go | 12 +++- 2 files changed, 83 insertions(+), 29 deletions(-) diff --git a/channeldb/payments_kv_store_test.go b/channeldb/payments_kv_store_test.go index 41bb380b6..6ed39f605 100644 --- a/channeldb/payments_kv_store_test.go +++ b/channeldb/payments_kv_store_test.go @@ -144,7 +144,9 @@ func TestKVPaymentsDBSwitchFail(t *testing.T) { attempt.AttemptID = 1 _, err = paymentDB.RegisterAttempt(info.PaymentIdentifier, attempt) require.NoError(t, err, "unable to send htlc message") - assertPaymentStatus(t, paymentDB, info.PaymentIdentifier, StatusInFlight) + assertPaymentStatus( + t, paymentDB, info.PaymentIdentifier, StatusInFlight, + ) htlc = &htlcStatus{ HTLCAttemptInfo: attempt, @@ -176,7 +178,9 @@ func TestKVPaymentsDBSwitchFail(t *testing.T) { spew.Sdump(payment.HTLCs[0].Route), err) } - assertPaymentStatus(t, paymentDB, info.PaymentIdentifier, StatusSucceeded) + assertPaymentStatus( + t, paymentDB, info.PaymentIdentifier, StatusSucceeded, + ) htlc.settle = &preimg assertPaymentInfo( @@ -226,7 +230,9 @@ func TestKVPaymentsDBSwitchDoubleSend(t *testing.T) { // Record an attempt. _, err = paymentDB.RegisterAttempt(info.PaymentIdentifier, attempt) require.NoError(t, err, "unable to send htlc message") - assertPaymentStatus(t, paymentDB, info.PaymentIdentifier, StatusInFlight) + assertPaymentStatus( + t, paymentDB, info.PaymentIdentifier, StatusInFlight, + ) htlc := &htlcStatus{ HTLCAttemptInfo: attempt, @@ -250,10 +256,14 @@ func TestKVPaymentsDBSwitchDoubleSend(t *testing.T) { }, ) require.NoError(t, err, "error shouldn't have been received, got") - assertPaymentStatus(t, paymentDB, info.PaymentIdentifier, StatusSucceeded) + assertPaymentStatus( + t, paymentDB, info.PaymentIdentifier, StatusSucceeded, + ) htlc.settle = &preimg - assertPaymentInfo(t, paymentDB, info.PaymentIdentifier, info, nil, htlc) + assertPaymentInfo( + t, paymentDB, info.PaymentIdentifier, info, nil, htlc, + ) err = paymentDB.InitPayment(info.PaymentIdentifier, info) if !errors.Is(err, ErrAlreadyPaid) { @@ -361,7 +371,9 @@ func TestKVPaymentsDBDeleteNonInFlight(t *testing.T) { if err != nil { t.Fatalf("unable to send htlc message: %v", err) } - _, err = paymentDB.RegisterAttempt(info.PaymentIdentifier, attempt) + _, err = paymentDB.RegisterAttempt( + info.PaymentIdentifier, attempt, + ) if err != nil { t.Fatalf("unable to send htlc message: %v", err) } @@ -385,13 +397,18 @@ func TestKVPaymentsDBDeleteNonInFlight(t *testing.T) { // Fail the payment, which should moved it to Failed. failReason := FailureReasonNoRoute - _, err = paymentDB.Fail(info.PaymentIdentifier, failReason) + _, err = paymentDB.Fail( + info.PaymentIdentifier, failReason, + ) if err != nil { t.Fatalf("unable to fail payment hash: %v", err) } // Verify the status is indeed Failed. - assertPaymentStatus(t, paymentDB, info.PaymentIdentifier, StatusFailed) + assertPaymentStatus( + t, paymentDB, info.PaymentIdentifier, + StatusFailed, + ) htlc.failure = &htlcFailure assertPaymentInfo( @@ -407,21 +424,30 @@ func TestKVPaymentsDBDeleteNonInFlight(t *testing.T) { }, ) if err != nil { - t.Fatalf("error shouldn't have been received, got: %v", err) + t.Fatalf("error shouldn't have been received,"+ + " got: %v", err) } - assertPaymentStatus(t, paymentDB, info.PaymentIdentifier, StatusSucceeded) + assertPaymentStatus( + t, paymentDB, info.PaymentIdentifier, + StatusSucceeded, + ) htlc.settle = &preimg assertPaymentInfo( - t, paymentDB, info.PaymentIdentifier, info, nil, htlc, + t, paymentDB, info.PaymentIdentifier, info, nil, + htlc, ) numSuccess++ } else { - assertPaymentStatus(t, paymentDB, info.PaymentIdentifier, StatusInFlight) + assertPaymentStatus( + t, paymentDB, info.PaymentIdentifier, + StatusInFlight, + ) assertPaymentInfo( - t, paymentDB, info.PaymentIdentifier, info, nil, htlc, + t, paymentDB, info.PaymentIdentifier, info, nil, + htlc, ) numInflight++ @@ -635,7 +661,8 @@ func TestKVPaymentsDBDeleteSinglePayment(t *testing.T) { // Delete failed HTLC attempts for the third payment. require.NoError(t, db.DeletePayment(payments[2].id, true)) - // Only the successful HTLC attempt should be left for the third payment. + // Only the successful HTLC attempt should be left for the third + // payment. payments[2].htlcs = 1 assertPayments(t, db, payments[2:]) @@ -724,7 +751,9 @@ func TestKVPaymentsDBMultiShard(t *testing.T) { a.AttemptID = i attempts = append(attempts, &a) - _, err = paymentDB.RegisterAttempt(info.PaymentIdentifier, &a) + _, err = paymentDB.RegisterAttempt( + info.PaymentIdentifier, &a, + ) if err != nil { t.Fatalf("unable to send htlc message: %v", err) } @@ -737,7 +766,8 @@ func TestKVPaymentsDBMultiShard(t *testing.T) { HTLCAttemptInfo: &a, } assertPaymentInfo( - t, paymentDB, info.PaymentIdentifier, info, nil, htlc, + t, paymentDB, info.PaymentIdentifier, info, nil, + htlc, ) } @@ -771,7 +801,9 @@ func TestKVPaymentsDBMultiShard(t *testing.T) { ) // Payment should still be in-flight. - assertPaymentStatus(t, paymentDB, info.PaymentIdentifier, StatusInFlight) + assertPaymentStatus( + t, paymentDB, info.PaymentIdentifier, StatusInFlight, + ) // Depending on the test case, settle or fail the first attempt. a = attempts[0] @@ -795,7 +827,8 @@ func TestKVPaymentsDBMultiShard(t *testing.T) { // Assert that the HTLC has had the preimage recorded. htlc.settle = &preimg assertPaymentInfo( - t, paymentDB, info.PaymentIdentifier, info, nil, htlc, + t, paymentDB, info.PaymentIdentifier, info, nil, + htlc, ) } else { _, err := paymentDB.FailAttempt( @@ -812,13 +845,16 @@ func TestKVPaymentsDBMultiShard(t *testing.T) { // Assert the failure was recorded. htlc.failure = &htlcFail assertPaymentInfo( - t, paymentDB, info.PaymentIdentifier, info, nil, htlc, + t, paymentDB, info.PaymentIdentifier, info, nil, + htlc, ) // We also record a payment level fail, to move it into // a terminal state. failReason := FailureReasonNoRoute - _, err = paymentDB.Fail(info.PaymentIdentifier, failReason) + _, err = paymentDB.Fail( + info.PaymentIdentifier, failReason, + ) if err != nil { t.Fatalf("unable to fail payment hash: %v", err) } @@ -846,7 +882,9 @@ func TestKVPaymentsDBMultiShard(t *testing.T) { require.ErrorIs(t, err, ErrPaymentPendingFailed) } - assertPaymentStatus(t, paymentDB, info.PaymentIdentifier, StatusInFlight) + assertPaymentStatus( + t, paymentDB, info.PaymentIdentifier, StatusInFlight, + ) // Settle or fail the remaining attempt based on the testcase. a = attempts[2] @@ -893,7 +931,9 @@ func TestKVPaymentsDBMultiShard(t *testing.T) { // write a terminal failure to the database without // syncing. failReason := FailureReasonPaymentDetails - _, err = paymentDB.Fail(info.PaymentIdentifier, failReason) + _, err = paymentDB.Fail( + info.PaymentIdentifier, failReason, + ) require.NoError(t, err, "unable to fail") } @@ -1085,7 +1125,9 @@ func testDeleteFailedAttempts(t *testing.T, keepFailedPaymentAttempts bool) { // Calling DeleteFailedAttempts on an in-flight payment should return // an error. if keepFailedPaymentAttempts { - require.NoError(t, paymentDB.DeleteFailedAttempts(payments[1].id)) + require.NoError( + t, paymentDB.DeleteFailedAttempts(payments[1].id), + ) } else { require.Error(t, paymentDB.DeleteFailedAttempts(payments[1].id)) } @@ -1107,10 +1149,14 @@ func testDeleteFailedAttempts(t *testing.T, keepFailedPaymentAttempts bool) { // DeleteFailedAttempts is ignored, even for non-existent // payments, if the control tower is configured to keep failed // HTLCs. - require.NoError(t, paymentDB.DeleteFailedAttempts(lntypes.ZeroHash)) + require.NoError( + t, paymentDB.DeleteFailedAttempts(lntypes.ZeroHash), + ) } else { // Attempting to cleanup a non-existent payment returns an error. - require.Error(t, paymentDB.DeleteFailedAttempts(lntypes.ZeroHash)) + require.Error( + t, paymentDB.DeleteFailedAttempts(lntypes.ZeroHash), + ) } } @@ -1364,7 +1410,9 @@ func assertPayments(t *testing.T, db *DB, payments []*payment) { // Make sure that the number of fetched payments is the same // as expected. - require.Len(t, dbPayments, len(payments), "unexpected number of payments") + require.Len( + t, dbPayments, len(payments), "unexpected number of payments", + ) // Convert fetched payments of type MPPayment to our helper structure. p := make([]*payment, len(dbPayments)) diff --git a/channeldb/payments_test.go b/channeldb/payments_test.go index 9514edab6..fdc68fbf9 100644 --- a/channeldb/payments_test.go +++ b/channeldb/payments_test.go @@ -530,7 +530,9 @@ func TestQueryPayments(t *testing.T) { info.CreationTime = time.Unix(int64(i+1), 0) // Create a new payment entry in the database. - err = paymentDB.InitPayment(info.PaymentIdentifier, info) + err = paymentDB.InitPayment( + info.PaymentIdentifier, info, + ) if err != nil { t.Fatalf("unable to initialize "+ "payment in database: %v", err) @@ -625,7 +627,9 @@ func TestFetchPaymentWithSequenceNumber(t *testing.T) { require.NoError(t, err) // Create a new payment entry in the database. - err = paymentDB.InitPayment(noDuplicates.PaymentIdentifier, noDuplicates) + err = paymentDB.InitPayment( + noDuplicates.PaymentIdentifier, noDuplicates, + ) require.NoError(t, err) // Fetch the payment so we can get its sequence nr. @@ -639,7 +643,9 @@ func TestFetchPaymentWithSequenceNumber(t *testing.T) { require.NoError(t, err) // Create a new payment entry in the database. - err = paymentDB.InitPayment(hasDuplicates.PaymentIdentifier, hasDuplicates) + err = paymentDB.InitPayment( + hasDuplicates.PaymentIdentifier, hasDuplicates, + ) require.NoError(t, err) // Fetch the payment so we can get its sequence nr. From e120110470f5eb132da44f0fcd724e1e17a982e1 Mon Sep 17 00:00:00 2001 From: ziggie Date: Fri, 1 Aug 2025 18:07:41 +0200 Subject: [PATCH 5/8] lnd: introduce separate key-value payment db We also addd this new db on the server level to use it in the following commit to do all the payment related queries of the rpcserver. We add a new payment db instance on the server level. Which we will you for the payment related queries in a following commit. --- config_builder.go | 12 ++++++++++++ server.go | 11 ++++++++--- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/config_builder.go b/config_builder.go index 3c8b122a8..35a44e2c6 100644 --- a/config_builder.go +++ b/config_builder.go @@ -924,6 +924,10 @@ type DatabaseInstances struct { // InvoiceDB is the database that stores information about invoices. InvoiceDB invoices.InvoiceDB + // KVPaymentsDB is the database that stores all payment related + // information. + KVPaymentsDB *channeldb.KVPaymentsDB + // MacaroonDB is the database that stores macaroon root keys. MacaroonDB kvdb.Backend @@ -1215,6 +1219,14 @@ func (d *DefaultDatabaseBuilder) BuildDatabase( return nil, nil, err } + // Mount the payments DB which is only KV for now. + // + // TODO(ziggie): Add support for SQL payments DB. + kvPaymentsDB := channeldb.NewKVPaymentsDB( + dbs.ChanStateDB, + ) + dbs.KVPaymentsDB = kvPaymentsDB + // Wrap the watchtower client DB and make sure we clean up. if cfg.WtClient.Active { dbs.TowerClientDB, err = wtdb.OpenClientDB( diff --git a/server.go b/server.go index ebc513fdd..a84642f1f 100644 --- a/server.go +++ b/server.go @@ -335,6 +335,12 @@ type server struct { invoicesDB invoices.InvoiceDB + // kvPaymentsDB is the DB that contains all functions for managing + // payments. + // + // TODO(ziggie): Replace with interface. + kvPaymentsDB *channeldb.KVPaymentsDB + aliasMgr *aliasmgr.Manager htlcSwitch *htlcswitch.Switch @@ -678,6 +684,7 @@ func newServer(ctx context.Context, cfg *Config, listenAddrs []net.Addr, addrSource: addrSource, miscDB: dbs.ChanStateDB, invoicesDB: dbs.InvoiceDB, + kvPaymentsDB: dbs.KVPaymentsDB, cc: cc, sigPool: lnwallet.NewSigPool(cfg.Workers.Sig, cc.Signer), writePool: writePool, @@ -1127,9 +1134,7 @@ func newServer(ctx context.Context, cfg *Config, listenAddrs []net.Addr, PathFindingConfig: pathFindingConfig, } - paymentControl := channeldb.NewKVPaymentsDB(dbs.ChanStateDB) - - s.controlTower = routing.NewControlTower(paymentControl) + s.controlTower = routing.NewControlTower(dbs.KVPaymentsDB) strictPruning := cfg.Bitcoin.Node == "neutrino" || cfg.Routing.StrictZombiePruning From aaa1ee0b746e2832dcc79ee095e55c344c6bc1c9 Mon Sep 17 00:00:00 2001 From: ziggie Date: Fri, 1 Aug 2025 20:19:33 +0200 Subject: [PATCH 6/8] channeldb: move kv related code We move all kv particular code which was in the payments.go file to the kv related file. Code which will be backend independant will remain in the payments.go file although only the kv backend is currently supported. --- channeldb/payments.go | 1328 --------------------------- channeldb/payments_kv_store.go | 1324 ++++++++++++++++++++++++++ channeldb/payments_kv_store_test.go | 355 +++++++ channeldb/payments_test.go | 358 -------- 4 files changed, 1679 insertions(+), 1686 deletions(-) diff --git a/channeldb/payments.go b/channeldb/payments.go index 51457671a..a23f891d0 100644 --- a/channeldb/payments.go +++ b/channeldb/payments.go @@ -1,126 +1,11 @@ package channeldb import ( - "bytes" - "encoding/binary" - "errors" "fmt" - "io" - "sort" "time" - "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/wire" - "github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lnwire" - "github.com/lightningnetwork/lnd/record" - "github.com/lightningnetwork/lnd/routing/route" - "github.com/lightningnetwork/lnd/tlv" -) - -var ( - // paymentsRootBucket is the name of the top-level bucket within the - // database that stores all data related to payments. Within this - // bucket, each payment hash its own sub-bucket keyed by its payment - // hash. - // - // Bucket hierarchy: - // - // root-bucket - // | - // |-- - // | |--sequence-key: - // | |--creation-info-key: - // | |--fail-info-key: <(optional) fail info> - // | | - // | |--payment-htlcs-bucket (shard-bucket) - // | | | - // | | |-- ai: - // | | |-- si: <(optional) settle info> - // | | |-- fi: <(optional) fail info> - // | | | - // | | ... - // | | - // | | - // | |--duplicate-bucket (only for old, completed payments) - // | | - // | |-- - // | | |--sequence-key: - // | | |--creation-info-key: - // | | |--ai: - // | | |--si: - // | | |--fi: - // | | - // | |-- - // | | | - // | ... ... - // | - // |-- - // | | - // | ... - // ... - // - paymentsRootBucket = []byte("payments-root-bucket") - - // paymentSequenceKey is a key used in the payment's sub-bucket to - // store the sequence number of the payment. - paymentSequenceKey = []byte("payment-sequence-key") - - // paymentCreationInfoKey is a key used in the payment's sub-bucket to - // store the creation info of the payment. - paymentCreationInfoKey = []byte("payment-creation-info") - - // paymentHtlcsBucket is a bucket where we'll store the information - // about the HTLCs that were attempted for a payment. - paymentHtlcsBucket = []byte("payment-htlcs-bucket") - - // htlcAttemptInfoKey is the key used as the prefix of an HTLC attempt - // to store the info about the attempt that was done for the HTLC in - // question. The HTLC attempt ID is concatenated at the end. - htlcAttemptInfoKey = []byte("ai") - - // htlcSettleInfoKey is the key used as the prefix of an HTLC attempt - // settle info, if any. The HTLC attempt ID is concatenated at the end. - htlcSettleInfoKey = []byte("si") - - // htlcFailInfoKey is the key used as the prefix of an HTLC attempt - // failure information, if any.The HTLC attempt ID is concatenated at - // the end. - htlcFailInfoKey = []byte("fi") - - // paymentFailInfoKey is a key used in the payment's sub-bucket to - // store information about the reason a payment failed. - paymentFailInfoKey = []byte("payment-fail-info") - - // paymentsIndexBucket is the name of the top-level bucket within the - // database that stores an index of payment sequence numbers to its - // payment hash. - // payments-sequence-index-bucket - // |--: - // |--... - // |--: - paymentsIndexBucket = []byte("payments-index-bucket") -) - -var ( - // ErrNoSequenceNumber is returned if we look up a payment which does - // not have a sequence number. - ErrNoSequenceNumber = errors.New("sequence number not found") - - // ErrDuplicateNotFound is returned when we lookup a payment by its - // index and cannot find a payment with a matching sequence number. - ErrDuplicateNotFound = errors.New("duplicate payment not found") - - // ErrNoDuplicateBucket is returned when we expect to find duplicates - // when looking up a payment from its index, but the payment does not - // have any. - ErrNoDuplicateBucket = errors.New("expected duplicate bucket") - - // ErrNoDuplicateNestedBucket is returned if we do not find duplicate - // payments in their own sub-bucket. - ErrNoDuplicateNestedBucket = errors.New("nested duplicate bucket not " + - "found") ) // FailureReason encodes the reason a payment ultimately failed. @@ -208,253 +93,6 @@ func (p *PaymentCreationInfo) String() string { p.PaymentIdentifier, p.Value, p.CreationTime) } -// htlcBucketKey creates a composite key from prefix and id where the result is -// simply the two concatenated. -func htlcBucketKey(prefix, id []byte) []byte { - key := make([]byte, len(prefix)+len(id)) - copy(key, prefix) - copy(key[len(prefix):], id) - return key -} - -// FetchPayments returns all sent payments found in the DB. -// -// nolint: dupl -func (d *DB) FetchPayments() ([]*MPPayment, error) { - var payments []*MPPayment - - err := kvdb.View(d, func(tx kvdb.RTx) error { - paymentsBucket := tx.ReadBucket(paymentsRootBucket) - if paymentsBucket == nil { - return nil - } - - return paymentsBucket.ForEach(func(k, v []byte) error { - bucket := paymentsBucket.NestedReadBucket(k) - if bucket == nil { - // We only expect sub-buckets to be found in - // this top-level bucket. - return fmt.Errorf("non bucket element in " + - "payments bucket") - } - - p, err := fetchPayment(bucket) - if err != nil { - return err - } - - payments = append(payments, p) - - // For older versions of lnd, duplicate payments to a - // payment has was possible. These will be found in a - // sub-bucket indexed by their sequence number if - // available. - duplicatePayments, err := fetchDuplicatePayments(bucket) - if err != nil { - return err - } - - payments = append(payments, duplicatePayments...) - return nil - }) - }, func() { - payments = nil - }) - if err != nil { - return nil, err - } - - // Before returning, sort the payments by their sequence number. - sort.Slice(payments, func(i, j int) bool { - return payments[i].SequenceNum < payments[j].SequenceNum - }) - - return payments, nil -} - -func fetchCreationInfo(bucket kvdb.RBucket) (*PaymentCreationInfo, error) { - b := bucket.Get(paymentCreationInfoKey) - if b == nil { - return nil, fmt.Errorf("creation info not found") - } - - r := bytes.NewReader(b) - return deserializePaymentCreationInfo(r) -} - -func fetchPayment(bucket kvdb.RBucket) (*MPPayment, error) { - seqBytes := bucket.Get(paymentSequenceKey) - if seqBytes == nil { - return nil, fmt.Errorf("sequence number not found") - } - - sequenceNum := binary.BigEndian.Uint64(seqBytes) - - // Get the PaymentCreationInfo. - creationInfo, err := fetchCreationInfo(bucket) - if err != nil { - return nil, err - } - - var htlcs []HTLCAttempt - htlcsBucket := bucket.NestedReadBucket(paymentHtlcsBucket) - if htlcsBucket != nil { - // Get the payment attempts. This can be empty. - htlcs, err = fetchHtlcAttempts(htlcsBucket) - if err != nil { - return nil, err - } - } - - // Get failure reason if available. - var failureReason *FailureReason - b := bucket.Get(paymentFailInfoKey) - if b != nil { - reason := FailureReason(b[0]) - failureReason = &reason - } - - // Create a new payment. - payment := &MPPayment{ - SequenceNum: sequenceNum, - Info: creationInfo, - HTLCs: htlcs, - FailureReason: failureReason, - } - - // Set its state and status. - if err := payment.setState(); err != nil { - return nil, err - } - - return payment, nil -} - -// fetchHtlcAttempts retrieves all htlc attempts made for the payment found in -// the given bucket. -func fetchHtlcAttempts(bucket kvdb.RBucket) ([]HTLCAttempt, error) { - htlcsMap := make(map[uint64]*HTLCAttempt) - - attemptInfoCount := 0 - err := bucket.ForEach(func(k, v []byte) error { - aid := byteOrder.Uint64(k[len(k)-8:]) - - if _, ok := htlcsMap[aid]; !ok { - htlcsMap[aid] = &HTLCAttempt{} - } - - var err error - switch { - case bytes.HasPrefix(k, htlcAttemptInfoKey): - attemptInfo, err := readHtlcAttemptInfo(v) - if err != nil { - return err - } - - attemptInfo.AttemptID = aid - htlcsMap[aid].HTLCAttemptInfo = *attemptInfo - attemptInfoCount++ - - case bytes.HasPrefix(k, htlcSettleInfoKey): - htlcsMap[aid].Settle, err = readHtlcSettleInfo(v) - if err != nil { - return err - } - - case bytes.HasPrefix(k, htlcFailInfoKey): - htlcsMap[aid].Failure, err = readHtlcFailInfo(v) - if err != nil { - return err - } - - default: - return fmt.Errorf("unknown htlc attempt key") - } - - return nil - }) - if err != nil { - return nil, err - } - - // Sanity check that all htlcs have an attempt info. - if attemptInfoCount != len(htlcsMap) { - return nil, errNoAttemptInfo - } - - keys := make([]uint64, len(htlcsMap)) - i := 0 - for k := range htlcsMap { - keys[i] = k - i++ - } - - // Sort HTLC attempts by their attempt ID. This is needed because in the - // DB we store the attempts with keys prefixed by their status which - // changes order (groups them together by status). - sort.Slice(keys, func(i, j int) bool { - return keys[i] < keys[j] - }) - - htlcs := make([]HTLCAttempt, len(htlcsMap)) - for i, key := range keys { - htlcs[i] = *htlcsMap[key] - } - - return htlcs, nil -} - -// readHtlcAttemptInfo reads the payment attempt info for this htlc. -func readHtlcAttemptInfo(b []byte) (*HTLCAttemptInfo, error) { - r := bytes.NewReader(b) - return deserializeHTLCAttemptInfo(r) -} - -// readHtlcSettleInfo reads the settle info for the htlc. If the htlc isn't -// settled, nil is returned. -func readHtlcSettleInfo(b []byte) (*HTLCSettleInfo, error) { - r := bytes.NewReader(b) - return deserializeHTLCSettleInfo(r) -} - -// readHtlcFailInfo reads the failure info for the htlc. If the htlc hasn't -// failed, nil is returned. -func readHtlcFailInfo(b []byte) (*HTLCFailInfo, error) { - r := bytes.NewReader(b) - return deserializeHTLCFailInfo(r) -} - -// fetchFailedHtlcKeys retrieves the bucket keys of all failed HTLCs of a -// payment bucket. -func fetchFailedHtlcKeys(bucket kvdb.RBucket) ([][]byte, error) { - htlcsBucket := bucket.NestedReadBucket(paymentHtlcsBucket) - - var htlcs []HTLCAttempt - var err error - if htlcsBucket != nil { - htlcs, err = fetchHtlcAttempts(htlcsBucket) - if err != nil { - return nil, err - } - } - - // Now iterate though them and save the bucket keys for the failed - // HTLCs. - var htlcKeys [][]byte - for _, h := range htlcs { - if h.Failure == nil { - continue - } - - htlcKeyBytes := make([]byte, 8) - binary.BigEndian.PutUint64(htlcKeyBytes, h.AttemptID) - - htlcKeys = append(htlcKeys, htlcKeyBytes) - } - - return htlcKeys, nil -} - // PaymentsQuery represents a query to the payments database starting or ending // at a certain offset index. The number of retrieved records can be limited. type PaymentsQuery struct { @@ -522,969 +160,3 @@ type PaymentsResponse struct { // CountTotal field in the query was set to true. TotalCount uint64 } - -// QueryPayments is a query to the payments database which is restricted -// to a subset of payments by the payments query, containing an offset -// index and a maximum number of returned payments. -func (d *DB) QueryPayments(query PaymentsQuery) (PaymentsResponse, error) { - var resp PaymentsResponse - - if err := kvdb.View(d, func(tx kvdb.RTx) error { - // Get the root payments bucket. - paymentsBucket := tx.ReadBucket(paymentsRootBucket) - if paymentsBucket == nil { - return nil - } - - // Get the index bucket which maps sequence number -> payment - // hash and duplicate bool. If we have a payments bucket, we - // should have an indexes bucket as well. - indexes := tx.ReadBucket(paymentsIndexBucket) - if indexes == nil { - return fmt.Errorf("index bucket does not exist") - } - - // accumulatePayments gets payments with the sequence number - // and hash provided and adds them to our list of payments if - // they meet the criteria of our query. It returns the number - // of payments that were added. - accumulatePayments := func(sequenceKey, hash []byte) (bool, - error) { - - r := bytes.NewReader(hash) - paymentHash, err := deserializePaymentIndex(r) - if err != nil { - return false, err - } - - payment, err := fetchPaymentWithSequenceNumber( - tx, paymentHash, sequenceKey, - ) - if err != nil { - return false, err - } - - // To keep compatibility with the old API, we only - // return non-succeeded payments if requested. - if payment.Status != StatusSucceeded && - !query.IncludeIncomplete { - - return false, err - } - - // Get the creation time in Unix seconds, this always - // rounds down the nanoseconds to full seconds. - createTime := payment.Info.CreationTime.Unix() - - // Skip any payments that were created before the - // specified time. - if createTime < query.CreationDateStart { - return false, nil - } - - // Skip any payments that were created after the - // specified time. - if query.CreationDateEnd != 0 && - createTime > query.CreationDateEnd { - - return false, nil - } - - // At this point, we've exhausted the offset, so we'll - // begin collecting invoices found within the range. - resp.Payments = append(resp.Payments, payment) - return true, nil - } - - // Create a paginator which reads from our sequence index bucket - // with the parameters provided by the payments query. - paginator := newPaginator( - indexes.ReadCursor(), query.Reversed, query.IndexOffset, - query.MaxPayments, - ) - - // Run a paginated query, adding payments to our response. - if err := paginator.query(accumulatePayments); err != nil { - return err - } - - // Counting the total number of payments is expensive, since we - // literally have to traverse the cursor linearly, which can - // take quite a while. So it's an optional query parameter. - if query.CountTotal { - var ( - totalPayments uint64 - err error - ) - countFn := func(_, _ []byte) error { - totalPayments++ - - return nil - } - - // In non-boltdb database backends, there's a faster - // ForAll query that allows for batch fetching items. - if fastBucket, ok := indexes.(kvdb.ExtendedRBucket); ok { - err = fastBucket.ForAll(countFn) - } else { - err = indexes.ForEach(countFn) - } - if err != nil { - return fmt.Errorf("error counting payments: %w", - err) - } - - resp.TotalCount = totalPayments - } - - return nil - }, func() { - resp = PaymentsResponse{} - }); err != nil { - return resp, err - } - - // Need to swap the payments slice order if reversed order. - if query.Reversed { - for l, r := 0, len(resp.Payments)-1; l < r; l, r = l+1, r-1 { - resp.Payments[l], resp.Payments[r] = - resp.Payments[r], resp.Payments[l] - } - } - - // Set the first and last index of the returned payments so that the - // caller can resume from this point later on. - if len(resp.Payments) > 0 { - resp.FirstIndexOffset = resp.Payments[0].SequenceNum - resp.LastIndexOffset = - resp.Payments[len(resp.Payments)-1].SequenceNum - } - - return resp, nil -} - -// fetchPaymentWithSequenceNumber get the payment which matches the payment hash -// *and* sequence number provided from the database. This is required because -// we previously had more than one payment per hash, so we have multiple indexes -// pointing to a single payment; we want to retrieve the correct one. -func fetchPaymentWithSequenceNumber(tx kvdb.RTx, paymentHash lntypes.Hash, - sequenceNumber []byte) (*MPPayment, error) { - - // We can now lookup the payment keyed by its hash in - // the payments root bucket. - bucket, err := fetchPaymentBucket(tx, paymentHash) - if err != nil { - return nil, err - } - - // A single payment hash can have multiple payments associated with it. - // We lookup our sequence number first, to determine whether this is - // the payment we are actually looking for. - seqBytes := bucket.Get(paymentSequenceKey) - if seqBytes == nil { - return nil, ErrNoSequenceNumber - } - - // If this top level payment has the sequence number we are looking for, - // return it. - if bytes.Equal(seqBytes, sequenceNumber) { - return fetchPayment(bucket) - } - - // If we were not looking for the top level payment, we are looking for - // one of our duplicate payments. We need to iterate through the seq - // numbers in this bucket to find the correct payments. If we do not - // find a duplicate payments bucket here, something is wrong. - dup := bucket.NestedReadBucket(duplicatePaymentsBucket) - if dup == nil { - return nil, ErrNoDuplicateBucket - } - - var duplicatePayment *MPPayment - err = dup.ForEach(func(k, v []byte) error { - subBucket := dup.NestedReadBucket(k) - if subBucket == nil { - // We one bucket for each duplicate to be found. - return ErrNoDuplicateNestedBucket - } - - seqBytes := subBucket.Get(duplicatePaymentSequenceKey) - if seqBytes == nil { - return err - } - - // If this duplicate payment is not the sequence number we are - // looking for, we can continue. - if !bytes.Equal(seqBytes, sequenceNumber) { - return nil - } - - duplicatePayment, err = fetchDuplicatePayment(subBucket) - if err != nil { - return err - } - - return nil - }) - if err != nil { - return nil, err - } - - // If none of the duplicate payments matched our sequence number, we - // failed to find the payment with this sequence number; something is - // wrong. - if duplicatePayment == nil { - return nil, ErrDuplicateNotFound - } - - return duplicatePayment, nil -} - -// DeletePayment deletes a payment from the DB given its payment hash. If -// failedHtlcsOnly is set, only failed HTLC attempts of the payment will be -// deleted. -func (d *DB) DeletePayment(paymentHash lntypes.Hash, - failedHtlcsOnly bool) error { - - return kvdb.Update(d, func(tx kvdb.RwTx) error { - payments := tx.ReadWriteBucket(paymentsRootBucket) - if payments == nil { - return nil - } - - bucket := payments.NestedReadWriteBucket(paymentHash[:]) - if bucket == nil { - return fmt.Errorf("non bucket element in payments " + - "bucket") - } - - // If the status is InFlight, we cannot safely delete - // the payment information, so we return early. - paymentStatus, err := fetchPaymentStatus(bucket) - if err != nil { - return err - } - - // If the payment has inflight HTLCs, we cannot safely delete - // the payment information, so we return an error. - if err := paymentStatus.removable(); err != nil { - return fmt.Errorf("payment '%v' has inflight HTLCs"+ - "and therefore cannot be deleted: %w", - paymentHash.String(), err) - } - - // Delete the failed HTLC attempts we found. - if failedHtlcsOnly { - toDelete, err := fetchFailedHtlcKeys(bucket) - if err != nil { - return err - } - - htlcsBucket := bucket.NestedReadWriteBucket( - paymentHtlcsBucket, - ) - - for _, htlcID := range toDelete { - err = htlcsBucket.Delete( - htlcBucketKey(htlcAttemptInfoKey, htlcID), - ) - if err != nil { - return err - } - - err = htlcsBucket.Delete( - htlcBucketKey(htlcFailInfoKey, htlcID), - ) - if err != nil { - return err - } - - err = htlcsBucket.Delete( - htlcBucketKey(htlcSettleInfoKey, htlcID), - ) - if err != nil { - return err - } - } - - return nil - } - - seqNrs, err := fetchSequenceNumbers(bucket) - if err != nil { - return err - } - - if err := payments.DeleteNestedBucket(paymentHash[:]); err != nil { - return err - } - - indexBucket := tx.ReadWriteBucket(paymentsIndexBucket) - for _, k := range seqNrs { - if err := indexBucket.Delete(k); err != nil { - return err - } - } - - return nil - }, func() {}) -} - -// DeletePayments deletes all completed and failed payments from the DB. If -// failedOnly is set, only failed payments will be considered for deletion. If -// failedHtlcsOnly is set, the payment itself won't be deleted, only failed HTLC -// attempts. The method returns the number of deleted payments, which is always -// 0 if failedHtlcsOnly is set. -func (d *DB) DeletePayments(failedOnly, failedHtlcsOnly bool) (int, error) { - var numPayments int - err := kvdb.Update(d, func(tx kvdb.RwTx) error { - payments := tx.ReadWriteBucket(paymentsRootBucket) - if payments == nil { - return nil - } - - var ( - // deleteBuckets is the set of payment buckets we need - // to delete. - deleteBuckets [][]byte - - // deleteIndexes is the set of indexes pointing to these - // payments that need to be deleted. - deleteIndexes [][]byte - - // deleteHtlcs maps a payment hash to the HTLC IDs we - // want to delete for that payment. - deleteHtlcs = make(map[lntypes.Hash][][]byte) - ) - err := payments.ForEach(func(k, _ []byte) error { - bucket := payments.NestedReadBucket(k) - if bucket == nil { - // We only expect sub-buckets to be found in - // this top-level bucket. - return fmt.Errorf("non bucket element in " + - "payments bucket") - } - - // If the status is InFlight, we cannot safely delete - // the payment information, so we return early. - paymentStatus, err := fetchPaymentStatus(bucket) - if err != nil { - return err - } - - // If the payment has inflight HTLCs, we cannot safely - // delete the payment information, so we return an nil - // to skip it. - if err := paymentStatus.removable(); err != nil { - return nil - } - - // If we requested to only delete failed payments, we - // can return if this one is not. - if failedOnly && paymentStatus != StatusFailed { - return nil - } - - // If we are only deleting failed HTLCs, fetch them. - if failedHtlcsOnly { - toDelete, err := fetchFailedHtlcKeys(bucket) - if err != nil { - return err - } - - hash, err := lntypes.MakeHash(k) - if err != nil { - return err - } - - deleteHtlcs[hash] = toDelete - - // We return, we are only deleting attempts. - return nil - } - - // Add the bucket to the set of buckets we can delete. - deleteBuckets = append(deleteBuckets, k) - - // Get all the sequence number associated with the - // payment, including duplicates. - seqNrs, err := fetchSequenceNumbers(bucket) - if err != nil { - return err - } - - deleteIndexes = append(deleteIndexes, seqNrs...) - numPayments++ - return nil - }) - if err != nil { - return err - } - - // Delete the failed HTLC attempts we found. - for hash, htlcIDs := range deleteHtlcs { - bucket := payments.NestedReadWriteBucket(hash[:]) - htlcsBucket := bucket.NestedReadWriteBucket( - paymentHtlcsBucket, - ) - - for _, aid := range htlcIDs { - if err := htlcsBucket.Delete( - htlcBucketKey(htlcAttemptInfoKey, aid), - ); err != nil { - return err - } - - if err := htlcsBucket.Delete( - htlcBucketKey(htlcFailInfoKey, aid), - ); err != nil { - return err - } - - if err := htlcsBucket.Delete( - htlcBucketKey(htlcSettleInfoKey, aid), - ); err != nil { - return err - } - } - } - - for _, k := range deleteBuckets { - if err := payments.DeleteNestedBucket(k); err != nil { - return err - } - } - - // Get our index bucket and delete all indexes pointing to the - // payments we are deleting. - indexBucket := tx.ReadWriteBucket(paymentsIndexBucket) - for _, k := range deleteIndexes { - if err := indexBucket.Delete(k); err != nil { - return err - } - } - - return nil - }, func() { - numPayments = 0 - }) - if err != nil { - return 0, err - } - - return numPayments, nil -} - -// fetchSequenceNumbers fetches all the sequence numbers associated with a -// payment, including those belonging to any duplicate payments. -func fetchSequenceNumbers(paymentBucket kvdb.RBucket) ([][]byte, error) { - seqNum := paymentBucket.Get(paymentSequenceKey) - if seqNum == nil { - return nil, errors.New("expected sequence number") - } - - sequenceNumbers := [][]byte{seqNum} - - // Get the duplicate payments bucket, if it has no duplicates, just - // return early with the payment sequence number. - duplicates := paymentBucket.NestedReadBucket(duplicatePaymentsBucket) - if duplicates == nil { - return sequenceNumbers, nil - } - - // If we do have duplicated, they are keyed by sequence number, so we - // iterate through the duplicates bucket and add them to our set of - // sequence numbers. - if err := duplicates.ForEach(func(k, v []byte) error { - sequenceNumbers = append(sequenceNumbers, k) - return nil - }); err != nil { - return nil, err - } - - return sequenceNumbers, nil -} - -// nolint: dupl -func serializePaymentCreationInfo(w io.Writer, c *PaymentCreationInfo) error { - var scratch [8]byte - - if _, err := w.Write(c.PaymentIdentifier[:]); err != nil { - return err - } - - byteOrder.PutUint64(scratch[:], uint64(c.Value)) - if _, err := w.Write(scratch[:]); err != nil { - return err - } - - if err := serializeTime(w, c.CreationTime); err != nil { - return err - } - - byteOrder.PutUint32(scratch[:4], uint32(len(c.PaymentRequest))) - if _, err := w.Write(scratch[:4]); err != nil { - return err - } - - if _, err := w.Write(c.PaymentRequest[:]); err != nil { - return err - } - - // Any remaining bytes are TLV encoded records. Currently, these are - // only the custom records provided by the user to be sent to the first - // hop. But this can easily be extended with further records by merging - // the records into a single TLV stream. - err := c.FirstHopCustomRecords.SerializeTo(w) - if err != nil { - return err - } - - return nil -} - -func deserializePaymentCreationInfo(r io.Reader) (*PaymentCreationInfo, - error) { - - var scratch [8]byte - - c := &PaymentCreationInfo{} - - if _, err := io.ReadFull(r, c.PaymentIdentifier[:]); err != nil { - return nil, err - } - - if _, err := io.ReadFull(r, scratch[:]); err != nil { - return nil, err - } - c.Value = lnwire.MilliSatoshi(byteOrder.Uint64(scratch[:])) - - creationTime, err := deserializeTime(r) - if err != nil { - return nil, err - } - c.CreationTime = creationTime - - if _, err := io.ReadFull(r, scratch[:4]); err != nil { - return nil, err - } - - reqLen := uint32(byteOrder.Uint32(scratch[:4])) - payReq := make([]byte, reqLen) - if reqLen > 0 { - if _, err := io.ReadFull(r, payReq); err != nil { - return nil, err - } - } - c.PaymentRequest = payReq - - // Any remaining bytes are TLV encoded records. Currently, these are - // only the custom records provided by the user to be sent to the first - // hop. But this can easily be extended with further records by merging - // the records into a single TLV stream. - c.FirstHopCustomRecords, err = lnwire.ParseCustomRecordsFrom(r) - if err != nil { - return nil, err - } - - return c, nil -} - -func serializeHTLCAttemptInfo(w io.Writer, a *HTLCAttemptInfo) error { - if err := WriteElements(w, a.sessionKey); err != nil { - return err - } - - if err := SerializeRoute(w, a.Route); err != nil { - return err - } - - if err := serializeTime(w, a.AttemptTime); err != nil { - return err - } - - // If the hash is nil we can just return. - if a.Hash == nil { - return nil - } - - if _, err := w.Write(a.Hash[:]); err != nil { - return err - } - - // Merge the fixed/known records together with the custom records to - // serialize them as a single blob. We can't do this in SerializeRoute - // because we're in the middle of the byte stream there. We can only do - // TLV serialization at the end of the stream, since EOF is allowed for - // a stream if no more data is expected. - producers := []tlv.RecordProducer{ - &a.Route.FirstHopAmount, - } - tlvData, err := lnwire.MergeAndEncode( - producers, nil, a.Route.FirstHopWireCustomRecords, - ) - if err != nil { - return err - } - - if _, err := w.Write(tlvData); err != nil { - return err - } - - return nil -} - -func deserializeHTLCAttemptInfo(r io.Reader) (*HTLCAttemptInfo, error) { - a := &HTLCAttemptInfo{} - err := ReadElements(r, &a.sessionKey) - if err != nil { - return nil, err - } - - a.Route, err = DeserializeRoute(r) - if err != nil { - return nil, err - } - - a.AttemptTime, err = deserializeTime(r) - if err != nil { - return nil, err - } - - hash := lntypes.Hash{} - _, err = io.ReadFull(r, hash[:]) - - switch { - // Older payment attempts wouldn't have the hash set, in which case we - // can just return. - case err == io.EOF, err == io.ErrUnexpectedEOF: - return a, nil - - case err != nil: - return nil, err - - default: - } - - a.Hash = &hash - - // Read any remaining data (if any) and parse it into the known records - // and custom records. - extraData, err := io.ReadAll(r) - if err != nil { - return nil, err - } - - customRecords, _, _, err := lnwire.ParseAndExtractCustomRecords( - extraData, &a.Route.FirstHopAmount, - ) - if err != nil { - return nil, err - } - - a.Route.FirstHopWireCustomRecords = customRecords - - return a, nil -} - -func serializeHop(w io.Writer, h *route.Hop) error { - if err := WriteElements(w, - h.PubKeyBytes[:], - h.ChannelID, - h.OutgoingTimeLock, - h.AmtToForward, - ); err != nil { - return err - } - - if err := binary.Write(w, byteOrder, h.LegacyPayload); err != nil { - return err - } - - // For legacy payloads, we don't need to write any TLV records, so - // we'll write a zero indicating the our serialized TLV map has no - // records. - if h.LegacyPayload { - return WriteElements(w, uint32(0)) - } - - // Gather all non-primitive TLV records so that they can be serialized - // as a single blob. - // - // TODO(conner): add migration to unify all fields in a single TLV - // blobs. The split approach will cause headaches down the road as more - // fields are added, which we can avoid by having a single TLV stream - // for all payload fields. - var records []tlv.Record - if h.MPP != nil { - records = append(records, h.MPP.Record()) - } - - // Add blinding point and encrypted data if present. - if h.EncryptedData != nil { - records = append(records, record.NewEncryptedDataRecord( - &h.EncryptedData, - )) - } - - if h.BlindingPoint != nil { - records = append(records, record.NewBlindingPointRecord( - &h.BlindingPoint, - )) - } - - if h.AMP != nil { - records = append(records, h.AMP.Record()) - } - - if h.Metadata != nil { - records = append(records, record.NewMetadataRecord(&h.Metadata)) - } - - if h.TotalAmtMsat != 0 { - totalMsatInt := uint64(h.TotalAmtMsat) - records = append( - records, record.NewTotalAmtMsatBlinded(&totalMsatInt), - ) - } - - // Final sanity check to absolutely rule out custom records that are not - // custom and write into the standard range. - if err := h.CustomRecords.Validate(); err != nil { - return err - } - - // Convert custom records to tlv and add to the record list. - // MapToRecords sorts the list, so adding it here will keep the list - // canonical. - tlvRecords := tlv.MapToRecords(h.CustomRecords) - records = append(records, tlvRecords...) - - // Otherwise, we'll transform our slice of records into a map of the - // raw bytes, then serialize them in-line with a length (number of - // elements) prefix. - mapRecords, err := tlv.RecordsToMap(records) - if err != nil { - return err - } - - numRecords := uint32(len(mapRecords)) - if err := WriteElements(w, numRecords); err != nil { - return err - } - - for recordType, rawBytes := range mapRecords { - if err := WriteElements(w, recordType); err != nil { - return err - } - - if err := wire.WriteVarBytes(w, 0, rawBytes); err != nil { - return err - } - } - - return nil -} - -// maxOnionPayloadSize is the largest Sphinx payload possible, so we don't need -// to read/write a TLV stream larger than this. -const maxOnionPayloadSize = 1300 - -func deserializeHop(r io.Reader) (*route.Hop, error) { - h := &route.Hop{} - - var pub []byte - if err := ReadElements(r, &pub); err != nil { - return nil, err - } - copy(h.PubKeyBytes[:], pub) - - if err := ReadElements(r, - &h.ChannelID, &h.OutgoingTimeLock, &h.AmtToForward, - ); err != nil { - return nil, err - } - - // TODO(roasbeef): change field to allow LegacyPayload false to be the - // legacy default? - err := binary.Read(r, byteOrder, &h.LegacyPayload) - if err != nil { - return nil, err - } - - var numElements uint32 - if err := ReadElements(r, &numElements); err != nil { - return nil, err - } - - // If there're no elements, then we can return early. - if numElements == 0 { - return h, nil - } - - tlvMap := make(map[uint64][]byte) - for i := uint32(0); i < numElements; i++ { - var tlvType uint64 - if err := ReadElements(r, &tlvType); err != nil { - return nil, err - } - - rawRecordBytes, err := wire.ReadVarBytes( - r, 0, maxOnionPayloadSize, "tlv", - ) - if err != nil { - return nil, err - } - - tlvMap[tlvType] = rawRecordBytes - } - - // If the MPP type is present, remove it from the generic TLV map and - // parse it back into a proper MPP struct. - // - // TODO(conner): add migration to unify all fields in a single TLV - // blobs. The split approach will cause headaches down the road as more - // fields are added, which we can avoid by having a single TLV stream - // for all payload fields. - mppType := uint64(record.MPPOnionType) - if mppBytes, ok := tlvMap[mppType]; ok { - delete(tlvMap, mppType) - - var ( - mpp = &record.MPP{} - mppRec = mpp.Record() - r = bytes.NewReader(mppBytes) - ) - err := mppRec.Decode(r, uint64(len(mppBytes))) - if err != nil { - return nil, err - } - h.MPP = mpp - } - - // If encrypted data or blinding key are present, remove them from - // the TLV map and parse into proper types. - encryptedDataType := uint64(record.EncryptedDataOnionType) - if data, ok := tlvMap[encryptedDataType]; ok { - delete(tlvMap, encryptedDataType) - h.EncryptedData = data - } - - blindingType := uint64(record.BlindingPointOnionType) - if blindingPoint, ok := tlvMap[blindingType]; ok { - delete(tlvMap, blindingType) - - h.BlindingPoint, err = btcec.ParsePubKey(blindingPoint) - if err != nil { - return nil, fmt.Errorf("invalid blinding point: %w", - err) - } - } - - ampType := uint64(record.AMPOnionType) - if ampBytes, ok := tlvMap[ampType]; ok { - delete(tlvMap, ampType) - - var ( - amp = &record.AMP{} - ampRec = amp.Record() - r = bytes.NewReader(ampBytes) - ) - err := ampRec.Decode(r, uint64(len(ampBytes))) - if err != nil { - return nil, err - } - h.AMP = amp - } - - // If the metadata type is present, remove it from the tlv map and - // populate directly on the hop. - metadataType := uint64(record.MetadataOnionType) - if metadata, ok := tlvMap[metadataType]; ok { - delete(tlvMap, metadataType) - - h.Metadata = metadata - } - - totalAmtMsatType := uint64(record.TotalAmtMsatBlindedType) - if totalAmtMsat, ok := tlvMap[totalAmtMsatType]; ok { - delete(tlvMap, totalAmtMsatType) - - var ( - totalAmtMsatInt uint64 - buf [8]byte - ) - if err := tlv.DTUint64( - bytes.NewReader(totalAmtMsat), - &totalAmtMsatInt, - &buf, - uint64(len(totalAmtMsat)), - ); err != nil { - return nil, err - } - - h.TotalAmtMsat = lnwire.MilliSatoshi(totalAmtMsatInt) - } - - h.CustomRecords = tlvMap - - return h, nil -} - -// SerializeRoute serializes a route. -func SerializeRoute(w io.Writer, r route.Route) error { - if err := WriteElements(w, - r.TotalTimeLock, r.TotalAmount, r.SourcePubKey[:], - ); err != nil { - return err - } - - if err := WriteElements(w, uint32(len(r.Hops))); err != nil { - return err - } - - for _, h := range r.Hops { - if err := serializeHop(w, h); err != nil { - return err - } - } - - // Any new/extra TLV data is encoded in serializeHTLCAttemptInfo! - - return nil -} - -// DeserializeRoute deserializes a route. -func DeserializeRoute(r io.Reader) (route.Route, error) { - rt := route.Route{} - if err := ReadElements(r, - &rt.TotalTimeLock, &rt.TotalAmount, - ); err != nil { - return rt, err - } - - var pub []byte - if err := ReadElements(r, &pub); err != nil { - return rt, err - } - copy(rt.SourcePubKey[:], pub) - - var numHops uint32 - if err := ReadElements(r, &numHops); err != nil { - return rt, err - } - - var hops []*route.Hop - for i := uint32(0); i < numHops; i++ { - hop, err := deserializeHop(r) - if err != nil { - return rt, err - } - hops = append(hops, hop) - } - rt.Hops = hops - - // Any new/extra TLV data is decoded in deserializeHTLCAttemptInfo! - - return rt, nil -} diff --git a/channeldb/payments_kv_store.go b/channeldb/payments_kv_store.go index 24124ba7a..198267bbc 100644 --- a/channeldb/payments_kv_store.go +++ b/channeldb/payments_kv_store.go @@ -6,11 +6,18 @@ import ( "errors" "fmt" "io" + "sort" "sync" "time" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/lntypes" + "github.com/lightningnetwork/lnd/lnwire" + "github.com/lightningnetwork/lnd/record" + "github.com/lightningnetwork/lnd/routing/route" + "github.com/lightningnetwork/lnd/tlv" ) const ( @@ -130,6 +137,110 @@ var ( "does not exist") ) +var ( + // paymentsRootBucket is the name of the top-level bucket within the + // database that stores all data related to payments. Within this + // bucket, each payment hash its own sub-bucket keyed by its payment + // hash. + // + // Bucket hierarchy: + // + // root-bucket + // | + // |-- + // | |--sequence-key: + // | |--creation-info-key: + // | |--fail-info-key: <(optional) fail info> + // | | + // | |--payment-htlcs-bucket (shard-bucket) + // | | | + // | | |-- ai: + // | | |-- si: <(optional) settle info> + // | | |-- fi: <(optional) fail info> + // | | | + // | | ... + // | | + // | | + // | |--duplicate-bucket (only for old, completed payments) + // | | + // | |-- + // | | |--sequence-key: + // | | |--creation-info-key: + // | | |--ai: + // | | |--si: + // | | |--fi: + // | | + // | |-- + // | | | + // | ... ... + // | + // |-- + // | | + // | ... + // ... + // + paymentsRootBucket = []byte("payments-root-bucket") + + // paymentSequenceKey is a key used in the payment's sub-bucket to + // store the sequence number of the payment. + paymentSequenceKey = []byte("payment-sequence-key") + + // paymentCreationInfoKey is a key used in the payment's sub-bucket to + // store the creation info of the payment. + paymentCreationInfoKey = []byte("payment-creation-info") + + // paymentHtlcsBucket is a bucket where we'll store the information + // about the HTLCs that were attempted for a payment. + paymentHtlcsBucket = []byte("payment-htlcs-bucket") + + // htlcAttemptInfoKey is the key used as the prefix of an HTLC attempt + // to store the info about the attempt that was done for the HTLC in + // question. The HTLC attempt ID is concatenated at the end. + htlcAttemptInfoKey = []byte("ai") + + // htlcSettleInfoKey is the key used as the prefix of an HTLC attempt + // settle info, if any. The HTLC attempt ID is concatenated at the end. + htlcSettleInfoKey = []byte("si") + + // htlcFailInfoKey is the key used as the prefix of an HTLC attempt + // failure information, if any.The HTLC attempt ID is concatenated at + // the end. + htlcFailInfoKey = []byte("fi") + + // paymentFailInfoKey is a key used in the payment's sub-bucket to + // store information about the reason a payment failed. + paymentFailInfoKey = []byte("payment-fail-info") + + // paymentsIndexBucket is the name of the top-level bucket within the + // database that stores an index of payment sequence numbers to its + // payment hash. + // payments-sequence-index-bucket + // |--: + // |--... + // |--: + paymentsIndexBucket = []byte("payments-index-bucket") +) + +var ( + // ErrNoSequenceNumber is returned if we look up a payment which does + // not have a sequence number. + ErrNoSequenceNumber = errors.New("sequence number not found") + + // ErrDuplicateNotFound is returned when we lookup a payment by its + // index and cannot find a payment with a matching sequence number. + ErrDuplicateNotFound = errors.New("duplicate payment not found") + + // ErrNoDuplicateBucket is returned when we expect to find duplicates + // when looking up a payment from its index, but the payment does not + // have any. + ErrNoDuplicateBucket = errors.New("expected duplicate bucket") + + // ErrNoDuplicateNestedBucket is returned if we do not find duplicate + // payments in their own sub-bucket. + ErrNoDuplicateNestedBucket = errors.New("nested duplicate bucket not " + + "found") +) + // KVPaymentsDB implements persistence for payments and payment attempts. type KVPaymentsDB struct { paymentSeqMx sync.Mutex @@ -834,3 +945,1216 @@ func (p *KVPaymentsDB) FetchInFlightPayments() ([]*MPPayment, error) { return inFlights, nil } + +// htlcBucketKey creates a composite key from prefix and id where the result is +// simply the two concatenated. +func htlcBucketKey(prefix, id []byte) []byte { + key := make([]byte, len(prefix)+len(id)) + copy(key, prefix) + copy(key[len(prefix):], id) + return key +} + +// FetchPayments returns all sent payments found in the DB. +// +// nolint: dupl +func (d *DB) FetchPayments() ([]*MPPayment, error) { + var payments []*MPPayment + + err := kvdb.View(d, func(tx kvdb.RTx) error { + paymentsBucket := tx.ReadBucket(paymentsRootBucket) + if paymentsBucket == nil { + return nil + } + + return paymentsBucket.ForEach(func(k, v []byte) error { + bucket := paymentsBucket.NestedReadBucket(k) + if bucket == nil { + // We only expect sub-buckets to be found in + // this top-level bucket. + return fmt.Errorf("non bucket element in " + + "payments bucket") + } + + p, err := fetchPayment(bucket) + if err != nil { + return err + } + + payments = append(payments, p) + + // For older versions of lnd, duplicate payments to a + // payment has was possible. These will be found in a + // sub-bucket indexed by their sequence number if + // available. + duplicatePayments, err := fetchDuplicatePayments(bucket) + if err != nil { + return err + } + + payments = append(payments, duplicatePayments...) + return nil + }) + }, func() { + payments = nil + }) + if err != nil { + return nil, err + } + + // Before returning, sort the payments by their sequence number. + sort.Slice(payments, func(i, j int) bool { + return payments[i].SequenceNum < payments[j].SequenceNum + }) + + return payments, nil +} + +func fetchCreationInfo(bucket kvdb.RBucket) (*PaymentCreationInfo, error) { + b := bucket.Get(paymentCreationInfoKey) + if b == nil { + return nil, fmt.Errorf("creation info not found") + } + + r := bytes.NewReader(b) + return deserializePaymentCreationInfo(r) +} + +func fetchPayment(bucket kvdb.RBucket) (*MPPayment, error) { + seqBytes := bucket.Get(paymentSequenceKey) + if seqBytes == nil { + return nil, fmt.Errorf("sequence number not found") + } + + sequenceNum := binary.BigEndian.Uint64(seqBytes) + + // Get the PaymentCreationInfo. + creationInfo, err := fetchCreationInfo(bucket) + if err != nil { + return nil, err + } + + var htlcs []HTLCAttempt + htlcsBucket := bucket.NestedReadBucket(paymentHtlcsBucket) + if htlcsBucket != nil { + // Get the payment attempts. This can be empty. + htlcs, err = fetchHtlcAttempts(htlcsBucket) + if err != nil { + return nil, err + } + } + + // Get failure reason if available. + var failureReason *FailureReason + b := bucket.Get(paymentFailInfoKey) + if b != nil { + reason := FailureReason(b[0]) + failureReason = &reason + } + + // Create a new payment. + payment := &MPPayment{ + SequenceNum: sequenceNum, + Info: creationInfo, + HTLCs: htlcs, + FailureReason: failureReason, + } + + // Set its state and status. + if err := payment.setState(); err != nil { + return nil, err + } + + return payment, nil +} + +// fetchHtlcAttempts retrieves all htlc attempts made for the payment found in +// the given bucket. +func fetchHtlcAttempts(bucket kvdb.RBucket) ([]HTLCAttempt, error) { + htlcsMap := make(map[uint64]*HTLCAttempt) + + attemptInfoCount := 0 + err := bucket.ForEach(func(k, v []byte) error { + aid := byteOrder.Uint64(k[len(k)-8:]) + + if _, ok := htlcsMap[aid]; !ok { + htlcsMap[aid] = &HTLCAttempt{} + } + + var err error + switch { + case bytes.HasPrefix(k, htlcAttemptInfoKey): + attemptInfo, err := readHtlcAttemptInfo(v) + if err != nil { + return err + } + + attemptInfo.AttemptID = aid + htlcsMap[aid].HTLCAttemptInfo = *attemptInfo + attemptInfoCount++ + + case bytes.HasPrefix(k, htlcSettleInfoKey): + htlcsMap[aid].Settle, err = readHtlcSettleInfo(v) + if err != nil { + return err + } + + case bytes.HasPrefix(k, htlcFailInfoKey): + htlcsMap[aid].Failure, err = readHtlcFailInfo(v) + if err != nil { + return err + } + + default: + return fmt.Errorf("unknown htlc attempt key") + } + + return nil + }) + if err != nil { + return nil, err + } + + // Sanity check that all htlcs have an attempt info. + if attemptInfoCount != len(htlcsMap) { + return nil, errNoAttemptInfo + } + + keys := make([]uint64, len(htlcsMap)) + i := 0 + for k := range htlcsMap { + keys[i] = k + i++ + } + + // Sort HTLC attempts by their attempt ID. This is needed because in the + // DB we store the attempts with keys prefixed by their status which + // changes order (groups them together by status). + sort.Slice(keys, func(i, j int) bool { + return keys[i] < keys[j] + }) + + htlcs := make([]HTLCAttempt, len(htlcsMap)) + for i, key := range keys { + htlcs[i] = *htlcsMap[key] + } + + return htlcs, nil +} + +// readHtlcAttemptInfo reads the payment attempt info for this htlc. +func readHtlcAttemptInfo(b []byte) (*HTLCAttemptInfo, error) { + r := bytes.NewReader(b) + return deserializeHTLCAttemptInfo(r) +} + +// readHtlcSettleInfo reads the settle info for the htlc. If the htlc isn't +// settled, nil is returned. +func readHtlcSettleInfo(b []byte) (*HTLCSettleInfo, error) { + r := bytes.NewReader(b) + return deserializeHTLCSettleInfo(r) +} + +// readHtlcFailInfo reads the failure info for the htlc. If the htlc hasn't +// failed, nil is returned. +func readHtlcFailInfo(b []byte) (*HTLCFailInfo, error) { + r := bytes.NewReader(b) + return deserializeHTLCFailInfo(r) +} + +// fetchFailedHtlcKeys retrieves the bucket keys of all failed HTLCs of a +// payment bucket. +func fetchFailedHtlcKeys(bucket kvdb.RBucket) ([][]byte, error) { + htlcsBucket := bucket.NestedReadBucket(paymentHtlcsBucket) + + var htlcs []HTLCAttempt + var err error + if htlcsBucket != nil { + htlcs, err = fetchHtlcAttempts(htlcsBucket) + if err != nil { + return nil, err + } + } + + // Now iterate though them and save the bucket keys for the failed + // HTLCs. + var htlcKeys [][]byte + for _, h := range htlcs { + if h.Failure == nil { + continue + } + + htlcKeyBytes := make([]byte, 8) + binary.BigEndian.PutUint64(htlcKeyBytes, h.AttemptID) + + htlcKeys = append(htlcKeys, htlcKeyBytes) + } + + return htlcKeys, nil +} + +// QueryPayments is a query to the payments database which is restricted +// to a subset of payments by the payments query, containing an offset +// index and a maximum number of returned payments. +func (d *DB) QueryPayments(query PaymentsQuery) (PaymentsResponse, error) { + var resp PaymentsResponse + + if err := kvdb.View(d, func(tx kvdb.RTx) error { + // Get the root payments bucket. + paymentsBucket := tx.ReadBucket(paymentsRootBucket) + if paymentsBucket == nil { + return nil + } + + // Get the index bucket which maps sequence number -> payment + // hash and duplicate bool. If we have a payments bucket, we + // should have an indexes bucket as well. + indexes := tx.ReadBucket(paymentsIndexBucket) + if indexes == nil { + return fmt.Errorf("index bucket does not exist") + } + + // accumulatePayments gets payments with the sequence number + // and hash provided and adds them to our list of payments if + // they meet the criteria of our query. It returns the number + // of payments that were added. + accumulatePayments := func(sequenceKey, hash []byte) (bool, + error) { + + r := bytes.NewReader(hash) + paymentHash, err := deserializePaymentIndex(r) + if err != nil { + return false, err + } + + payment, err := fetchPaymentWithSequenceNumber( + tx, paymentHash, sequenceKey, + ) + if err != nil { + return false, err + } + + // To keep compatibility with the old API, we only + // return non-succeeded payments if requested. + if payment.Status != StatusSucceeded && + !query.IncludeIncomplete { + + return false, err + } + + // Get the creation time in Unix seconds, this always + // rounds down the nanoseconds to full seconds. + createTime := payment.Info.CreationTime.Unix() + + // Skip any payments that were created before the + // specified time. + if createTime < query.CreationDateStart { + return false, nil + } + + // Skip any payments that were created after the + // specified time. + if query.CreationDateEnd != 0 && + createTime > query.CreationDateEnd { + + return false, nil + } + + // At this point, we've exhausted the offset, so we'll + // begin collecting invoices found within the range. + resp.Payments = append(resp.Payments, payment) + return true, nil + } + + // Create a paginator which reads from our sequence index bucket + // with the parameters provided by the payments query. + paginator := newPaginator( + indexes.ReadCursor(), query.Reversed, query.IndexOffset, + query.MaxPayments, + ) + + // Run a paginated query, adding payments to our response. + if err := paginator.query(accumulatePayments); err != nil { + return err + } + + // Counting the total number of payments is expensive, since we + // literally have to traverse the cursor linearly, which can + // take quite a while. So it's an optional query parameter. + if query.CountTotal { + var ( + totalPayments uint64 + err error + ) + countFn := func(_, _ []byte) error { + totalPayments++ + + return nil + } + + // In non-boltdb database backends, there's a faster + // ForAll query that allows for batch fetching items. + if fastBucket, ok := indexes.(kvdb.ExtendedRBucket); ok { + err = fastBucket.ForAll(countFn) + } else { + err = indexes.ForEach(countFn) + } + if err != nil { + return fmt.Errorf("error counting payments: %w", + err) + } + + resp.TotalCount = totalPayments + } + + return nil + }, func() { + resp = PaymentsResponse{} + }); err != nil { + return resp, err + } + + // Need to swap the payments slice order if reversed order. + if query.Reversed { + for l, r := 0, len(resp.Payments)-1; l < r; l, r = l+1, r-1 { + resp.Payments[l], resp.Payments[r] = + resp.Payments[r], resp.Payments[l] + } + } + + // Set the first and last index of the returned payments so that the + // caller can resume from this point later on. + if len(resp.Payments) > 0 { + resp.FirstIndexOffset = resp.Payments[0].SequenceNum + resp.LastIndexOffset = + resp.Payments[len(resp.Payments)-1].SequenceNum + } + + return resp, nil +} + +// fetchPaymentWithSequenceNumber get the payment which matches the payment hash +// *and* sequence number provided from the database. This is required because +// we previously had more than one payment per hash, so we have multiple indexes +// pointing to a single payment; we want to retrieve the correct one. +func fetchPaymentWithSequenceNumber(tx kvdb.RTx, paymentHash lntypes.Hash, + sequenceNumber []byte) (*MPPayment, error) { + + // We can now lookup the payment keyed by its hash in + // the payments root bucket. + bucket, err := fetchPaymentBucket(tx, paymentHash) + if err != nil { + return nil, err + } + + // A single payment hash can have multiple payments associated with it. + // We lookup our sequence number first, to determine whether this is + // the payment we are actually looking for. + seqBytes := bucket.Get(paymentSequenceKey) + if seqBytes == nil { + return nil, ErrNoSequenceNumber + } + + // If this top level payment has the sequence number we are looking for, + // return it. + if bytes.Equal(seqBytes, sequenceNumber) { + return fetchPayment(bucket) + } + + // If we were not looking for the top level payment, we are looking for + // one of our duplicate payments. We need to iterate through the seq + // numbers in this bucket to find the correct payments. If we do not + // find a duplicate payments bucket here, something is wrong. + dup := bucket.NestedReadBucket(duplicatePaymentsBucket) + if dup == nil { + return nil, ErrNoDuplicateBucket + } + + var duplicatePayment *MPPayment + err = dup.ForEach(func(k, v []byte) error { + subBucket := dup.NestedReadBucket(k) + if subBucket == nil { + // We one bucket for each duplicate to be found. + return ErrNoDuplicateNestedBucket + } + + seqBytes := subBucket.Get(duplicatePaymentSequenceKey) + if seqBytes == nil { + return err + } + + // If this duplicate payment is not the sequence number we are + // looking for, we can continue. + if !bytes.Equal(seqBytes, sequenceNumber) { + return nil + } + + duplicatePayment, err = fetchDuplicatePayment(subBucket) + if err != nil { + return err + } + + return nil + }) + if err != nil { + return nil, err + } + + // If none of the duplicate payments matched our sequence number, we + // failed to find the payment with this sequence number; something is + // wrong. + if duplicatePayment == nil { + return nil, ErrDuplicateNotFound + } + + return duplicatePayment, nil +} + +// DeletePayment deletes a payment from the DB given its payment hash. If +// failedHtlcsOnly is set, only failed HTLC attempts of the payment will be +// deleted. +func (d *DB) DeletePayment(paymentHash lntypes.Hash, + failedHtlcsOnly bool) error { + + return kvdb.Update(d, func(tx kvdb.RwTx) error { + payments := tx.ReadWriteBucket(paymentsRootBucket) + if payments == nil { + return nil + } + + bucket := payments.NestedReadWriteBucket(paymentHash[:]) + if bucket == nil { + return fmt.Errorf("non bucket element in payments " + + "bucket") + } + + // If the status is InFlight, we cannot safely delete + // the payment information, so we return early. + paymentStatus, err := fetchPaymentStatus(bucket) + if err != nil { + return err + } + + // If the payment has inflight HTLCs, we cannot safely delete + // the payment information, so we return an error. + if err := paymentStatus.removable(); err != nil { + return fmt.Errorf("payment '%v' has inflight HTLCs"+ + "and therefore cannot be deleted: %w", + paymentHash.String(), err) + } + + // Delete the failed HTLC attempts we found. + if failedHtlcsOnly { + toDelete, err := fetchFailedHtlcKeys(bucket) + if err != nil { + return err + } + + htlcsBucket := bucket.NestedReadWriteBucket( + paymentHtlcsBucket, + ) + + for _, htlcID := range toDelete { + err = htlcsBucket.Delete( + htlcBucketKey(htlcAttemptInfoKey, htlcID), + ) + if err != nil { + return err + } + + err = htlcsBucket.Delete( + htlcBucketKey(htlcFailInfoKey, htlcID), + ) + if err != nil { + return err + } + + err = htlcsBucket.Delete( + htlcBucketKey(htlcSettleInfoKey, htlcID), + ) + if err != nil { + return err + } + } + + return nil + } + + seqNrs, err := fetchSequenceNumbers(bucket) + if err != nil { + return err + } + + if err := payments.DeleteNestedBucket(paymentHash[:]); err != nil { + return err + } + + indexBucket := tx.ReadWriteBucket(paymentsIndexBucket) + for _, k := range seqNrs { + if err := indexBucket.Delete(k); err != nil { + return err + } + } + + return nil + }, func() {}) +} + +// DeletePayments deletes all completed and failed payments from the DB. If +// failedOnly is set, only failed payments will be considered for deletion. If +// failedHtlcsOnly is set, the payment itself won't be deleted, only failed HTLC +// attempts. The method returns the number of deleted payments, which is always +// 0 if failedHtlcsOnly is set. +func (d *DB) DeletePayments(failedOnly, failedHtlcsOnly bool) (int, error) { + var numPayments int + err := kvdb.Update(d, func(tx kvdb.RwTx) error { + payments := tx.ReadWriteBucket(paymentsRootBucket) + if payments == nil { + return nil + } + + var ( + // deleteBuckets is the set of payment buckets we need + // to delete. + deleteBuckets [][]byte + + // deleteIndexes is the set of indexes pointing to these + // payments that need to be deleted. + deleteIndexes [][]byte + + // deleteHtlcs maps a payment hash to the HTLC IDs we + // want to delete for that payment. + deleteHtlcs = make(map[lntypes.Hash][][]byte) + ) + err := payments.ForEach(func(k, _ []byte) error { + bucket := payments.NestedReadBucket(k) + if bucket == nil { + // We only expect sub-buckets to be found in + // this top-level bucket. + return fmt.Errorf("non bucket element in " + + "payments bucket") + } + + // If the status is InFlight, we cannot safely delete + // the payment information, so we return early. + paymentStatus, err := fetchPaymentStatus(bucket) + if err != nil { + return err + } + + // If the payment has inflight HTLCs, we cannot safely + // delete the payment information, so we return an nil + // to skip it. + if err := paymentStatus.removable(); err != nil { + return nil + } + + // If we requested to only delete failed payments, we + // can return if this one is not. + if failedOnly && paymentStatus != StatusFailed { + return nil + } + + // If we are only deleting failed HTLCs, fetch them. + if failedHtlcsOnly { + toDelete, err := fetchFailedHtlcKeys(bucket) + if err != nil { + return err + } + + hash, err := lntypes.MakeHash(k) + if err != nil { + return err + } + + deleteHtlcs[hash] = toDelete + + // We return, we are only deleting attempts. + return nil + } + + // Add the bucket to the set of buckets we can delete. + deleteBuckets = append(deleteBuckets, k) + + // Get all the sequence number associated with the + // payment, including duplicates. + seqNrs, err := fetchSequenceNumbers(bucket) + if err != nil { + return err + } + + deleteIndexes = append(deleteIndexes, seqNrs...) + numPayments++ + return nil + }) + if err != nil { + return err + } + + // Delete the failed HTLC attempts we found. + for hash, htlcIDs := range deleteHtlcs { + bucket := payments.NestedReadWriteBucket(hash[:]) + htlcsBucket := bucket.NestedReadWriteBucket( + paymentHtlcsBucket, + ) + + for _, aid := range htlcIDs { + if err := htlcsBucket.Delete( + htlcBucketKey(htlcAttemptInfoKey, aid), + ); err != nil { + return err + } + + if err := htlcsBucket.Delete( + htlcBucketKey(htlcFailInfoKey, aid), + ); err != nil { + return err + } + + if err := htlcsBucket.Delete( + htlcBucketKey(htlcSettleInfoKey, aid), + ); err != nil { + return err + } + } + } + + for _, k := range deleteBuckets { + if err := payments.DeleteNestedBucket(k); err != nil { + return err + } + } + + // Get our index bucket and delete all indexes pointing to the + // payments we are deleting. + indexBucket := tx.ReadWriteBucket(paymentsIndexBucket) + for _, k := range deleteIndexes { + if err := indexBucket.Delete(k); err != nil { + return err + } + } + + return nil + }, func() { + numPayments = 0 + }) + if err != nil { + return 0, err + } + + return numPayments, nil +} + +// fetchSequenceNumbers fetches all the sequence numbers associated with a +// payment, including those belonging to any duplicate payments. +func fetchSequenceNumbers(paymentBucket kvdb.RBucket) ([][]byte, error) { + seqNum := paymentBucket.Get(paymentSequenceKey) + if seqNum == nil { + return nil, errors.New("expected sequence number") + } + + sequenceNumbers := [][]byte{seqNum} + + // Get the duplicate payments bucket, if it has no duplicates, just + // return early with the payment sequence number. + duplicates := paymentBucket.NestedReadBucket(duplicatePaymentsBucket) + if duplicates == nil { + return sequenceNumbers, nil + } + + // If we do have duplicated, they are keyed by sequence number, so we + // iterate through the duplicates bucket and add them to our set of + // sequence numbers. + if err := duplicates.ForEach(func(k, v []byte) error { + sequenceNumbers = append(sequenceNumbers, k) + return nil + }); err != nil { + return nil, err + } + + return sequenceNumbers, nil +} + +// nolint: dupl +func serializePaymentCreationInfo(w io.Writer, c *PaymentCreationInfo) error { + var scratch [8]byte + + if _, err := w.Write(c.PaymentIdentifier[:]); err != nil { + return err + } + + byteOrder.PutUint64(scratch[:], uint64(c.Value)) + if _, err := w.Write(scratch[:]); err != nil { + return err + } + + if err := serializeTime(w, c.CreationTime); err != nil { + return err + } + + byteOrder.PutUint32(scratch[:4], uint32(len(c.PaymentRequest))) + if _, err := w.Write(scratch[:4]); err != nil { + return err + } + + if _, err := w.Write(c.PaymentRequest[:]); err != nil { + return err + } + + // Any remaining bytes are TLV encoded records. Currently, these are + // only the custom records provided by the user to be sent to the first + // hop. But this can easily be extended with further records by merging + // the records into a single TLV stream. + err := c.FirstHopCustomRecords.SerializeTo(w) + if err != nil { + return err + } + + return nil +} + +func deserializePaymentCreationInfo(r io.Reader) (*PaymentCreationInfo, + error) { + + var scratch [8]byte + + c := &PaymentCreationInfo{} + + if _, err := io.ReadFull(r, c.PaymentIdentifier[:]); err != nil { + return nil, err + } + + if _, err := io.ReadFull(r, scratch[:]); err != nil { + return nil, err + } + c.Value = lnwire.MilliSatoshi(byteOrder.Uint64(scratch[:])) + + creationTime, err := deserializeTime(r) + if err != nil { + return nil, err + } + c.CreationTime = creationTime + + if _, err := io.ReadFull(r, scratch[:4]); err != nil { + return nil, err + } + + reqLen := uint32(byteOrder.Uint32(scratch[:4])) + payReq := make([]byte, reqLen) + if reqLen > 0 { + if _, err := io.ReadFull(r, payReq); err != nil { + return nil, err + } + } + c.PaymentRequest = payReq + + // Any remaining bytes are TLV encoded records. Currently, these are + // only the custom records provided by the user to be sent to the first + // hop. But this can easily be extended with further records by merging + // the records into a single TLV stream. + c.FirstHopCustomRecords, err = lnwire.ParseCustomRecordsFrom(r) + if err != nil { + return nil, err + } + + return c, nil +} + +func serializeHTLCAttemptInfo(w io.Writer, a *HTLCAttemptInfo) error { + if err := WriteElements(w, a.sessionKey); err != nil { + return err + } + + if err := SerializeRoute(w, a.Route); err != nil { + return err + } + + if err := serializeTime(w, a.AttemptTime); err != nil { + return err + } + + // If the hash is nil we can just return. + if a.Hash == nil { + return nil + } + + if _, err := w.Write(a.Hash[:]); err != nil { + return err + } + + // Merge the fixed/known records together with the custom records to + // serialize them as a single blob. We can't do this in SerializeRoute + // because we're in the middle of the byte stream there. We can only do + // TLV serialization at the end of the stream, since EOF is allowed for + // a stream if no more data is expected. + producers := []tlv.RecordProducer{ + &a.Route.FirstHopAmount, + } + tlvData, err := lnwire.MergeAndEncode( + producers, nil, a.Route.FirstHopWireCustomRecords, + ) + if err != nil { + return err + } + + if _, err := w.Write(tlvData); err != nil { + return err + } + + return nil +} + +func deserializeHTLCAttemptInfo(r io.Reader) (*HTLCAttemptInfo, error) { + a := &HTLCAttemptInfo{} + err := ReadElements(r, &a.sessionKey) + if err != nil { + return nil, err + } + + a.Route, err = DeserializeRoute(r) + if err != nil { + return nil, err + } + + a.AttemptTime, err = deserializeTime(r) + if err != nil { + return nil, err + } + + hash := lntypes.Hash{} + _, err = io.ReadFull(r, hash[:]) + + switch { + // Older payment attempts wouldn't have the hash set, in which case we + // can just return. + case err == io.EOF, err == io.ErrUnexpectedEOF: + return a, nil + + case err != nil: + return nil, err + + default: + } + + a.Hash = &hash + + // Read any remaining data (if any) and parse it into the known records + // and custom records. + extraData, err := io.ReadAll(r) + if err != nil { + return nil, err + } + + customRecords, _, _, err := lnwire.ParseAndExtractCustomRecords( + extraData, &a.Route.FirstHopAmount, + ) + if err != nil { + return nil, err + } + + a.Route.FirstHopWireCustomRecords = customRecords + + return a, nil +} + +func serializeHop(w io.Writer, h *route.Hop) error { + if err := WriteElements(w, + h.PubKeyBytes[:], + h.ChannelID, + h.OutgoingTimeLock, + h.AmtToForward, + ); err != nil { + return err + } + + if err := binary.Write(w, byteOrder, h.LegacyPayload); err != nil { + return err + } + + // For legacy payloads, we don't need to write any TLV records, so + // we'll write a zero indicating the our serialized TLV map has no + // records. + if h.LegacyPayload { + return WriteElements(w, uint32(0)) + } + + // Gather all non-primitive TLV records so that they can be serialized + // as a single blob. + // + // TODO(conner): add migration to unify all fields in a single TLV + // blobs. The split approach will cause headaches down the road as more + // fields are added, which we can avoid by having a single TLV stream + // for all payload fields. + var records []tlv.Record + if h.MPP != nil { + records = append(records, h.MPP.Record()) + } + + // Add blinding point and encrypted data if present. + if h.EncryptedData != nil { + records = append(records, record.NewEncryptedDataRecord( + &h.EncryptedData, + )) + } + + if h.BlindingPoint != nil { + records = append(records, record.NewBlindingPointRecord( + &h.BlindingPoint, + )) + } + + if h.AMP != nil { + records = append(records, h.AMP.Record()) + } + + if h.Metadata != nil { + records = append(records, record.NewMetadataRecord(&h.Metadata)) + } + + if h.TotalAmtMsat != 0 { + totalMsatInt := uint64(h.TotalAmtMsat) + records = append( + records, record.NewTotalAmtMsatBlinded(&totalMsatInt), + ) + } + + // Final sanity check to absolutely rule out custom records that are not + // custom and write into the standard range. + if err := h.CustomRecords.Validate(); err != nil { + return err + } + + // Convert custom records to tlv and add to the record list. + // MapToRecords sorts the list, so adding it here will keep the list + // canonical. + tlvRecords := tlv.MapToRecords(h.CustomRecords) + records = append(records, tlvRecords...) + + // Otherwise, we'll transform our slice of records into a map of the + // raw bytes, then serialize them in-line with a length (number of + // elements) prefix. + mapRecords, err := tlv.RecordsToMap(records) + if err != nil { + return err + } + + numRecords := uint32(len(mapRecords)) + if err := WriteElements(w, numRecords); err != nil { + return err + } + + for recordType, rawBytes := range mapRecords { + if err := WriteElements(w, recordType); err != nil { + return err + } + + if err := wire.WriteVarBytes(w, 0, rawBytes); err != nil { + return err + } + } + + return nil +} + +// maxOnionPayloadSize is the largest Sphinx payload possible, so we don't need +// to read/write a TLV stream larger than this. +const maxOnionPayloadSize = 1300 + +func deserializeHop(r io.Reader) (*route.Hop, error) { + h := &route.Hop{} + + var pub []byte + if err := ReadElements(r, &pub); err != nil { + return nil, err + } + copy(h.PubKeyBytes[:], pub) + + if err := ReadElements(r, + &h.ChannelID, &h.OutgoingTimeLock, &h.AmtToForward, + ); err != nil { + return nil, err + } + + // TODO(roasbeef): change field to allow LegacyPayload false to be the + // legacy default? + err := binary.Read(r, byteOrder, &h.LegacyPayload) + if err != nil { + return nil, err + } + + var numElements uint32 + if err := ReadElements(r, &numElements); err != nil { + return nil, err + } + + // If there're no elements, then we can return early. + if numElements == 0 { + return h, nil + } + + tlvMap := make(map[uint64][]byte) + for i := uint32(0); i < numElements; i++ { + var tlvType uint64 + if err := ReadElements(r, &tlvType); err != nil { + return nil, err + } + + rawRecordBytes, err := wire.ReadVarBytes( + r, 0, maxOnionPayloadSize, "tlv", + ) + if err != nil { + return nil, err + } + + tlvMap[tlvType] = rawRecordBytes + } + + // If the MPP type is present, remove it from the generic TLV map and + // parse it back into a proper MPP struct. + // + // TODO(conner): add migration to unify all fields in a single TLV + // blobs. The split approach will cause headaches down the road as more + // fields are added, which we can avoid by having a single TLV stream + // for all payload fields. + mppType := uint64(record.MPPOnionType) + if mppBytes, ok := tlvMap[mppType]; ok { + delete(tlvMap, mppType) + + var ( + mpp = &record.MPP{} + mppRec = mpp.Record() + r = bytes.NewReader(mppBytes) + ) + err := mppRec.Decode(r, uint64(len(mppBytes))) + if err != nil { + return nil, err + } + h.MPP = mpp + } + + // If encrypted data or blinding key are present, remove them from + // the TLV map and parse into proper types. + encryptedDataType := uint64(record.EncryptedDataOnionType) + if data, ok := tlvMap[encryptedDataType]; ok { + delete(tlvMap, encryptedDataType) + h.EncryptedData = data + } + + blindingType := uint64(record.BlindingPointOnionType) + if blindingPoint, ok := tlvMap[blindingType]; ok { + delete(tlvMap, blindingType) + + h.BlindingPoint, err = btcec.ParsePubKey(blindingPoint) + if err != nil { + return nil, fmt.Errorf("invalid blinding point: %w", + err) + } + } + + ampType := uint64(record.AMPOnionType) + if ampBytes, ok := tlvMap[ampType]; ok { + delete(tlvMap, ampType) + + var ( + amp = &record.AMP{} + ampRec = amp.Record() + r = bytes.NewReader(ampBytes) + ) + err := ampRec.Decode(r, uint64(len(ampBytes))) + if err != nil { + return nil, err + } + h.AMP = amp + } + + // If the metadata type is present, remove it from the tlv map and + // populate directly on the hop. + metadataType := uint64(record.MetadataOnionType) + if metadata, ok := tlvMap[metadataType]; ok { + delete(tlvMap, metadataType) + + h.Metadata = metadata + } + + totalAmtMsatType := uint64(record.TotalAmtMsatBlindedType) + if totalAmtMsat, ok := tlvMap[totalAmtMsatType]; ok { + delete(tlvMap, totalAmtMsatType) + + var ( + totalAmtMsatInt uint64 + buf [8]byte + ) + if err := tlv.DTUint64( + bytes.NewReader(totalAmtMsat), + &totalAmtMsatInt, + &buf, + uint64(len(totalAmtMsat)), + ); err != nil { + return nil, err + } + + h.TotalAmtMsat = lnwire.MilliSatoshi(totalAmtMsatInt) + } + + h.CustomRecords = tlvMap + + return h, nil +} + +// SerializeRoute serializes a route. +func SerializeRoute(w io.Writer, r route.Route) error { + if err := WriteElements(w, + r.TotalTimeLock, r.TotalAmount, r.SourcePubKey[:], + ); err != nil { + return err + } + + if err := WriteElements(w, uint32(len(r.Hops))); err != nil { + return err + } + + for _, h := range r.Hops { + if err := serializeHop(w, h); err != nil { + return err + } + } + + // Any new/extra TLV data is encoded in serializeHTLCAttemptInfo! + + return nil +} + +// DeserializeRoute deserializes a route. +func DeserializeRoute(r io.Reader) (route.Route, error) { + rt := route.Route{} + if err := ReadElements(r, + &rt.TotalTimeLock, &rt.TotalAmount, + ); err != nil { + return rt, err + } + + var pub []byte + if err := ReadElements(r, &pub); err != nil { + return rt, err + } + copy(rt.SourcePubKey[:], pub) + + var numHops uint32 + if err := ReadElements(r, &numHops); err != nil { + return rt, err + } + + var hops []*route.Hop + for i := uint32(0); i < numHops; i++ { + hop, err := deserializeHop(r) + if err != nil { + return rt, err + } + hops = append(hops, hop) + } + rt.Hops = hops + + // Any new/extra TLV data is decoded in deserializeHTLCAttemptInfo! + + return rt, nil +} diff --git a/channeldb/payments_kv_store_test.go b/channeldb/payments_kv_store_test.go index 6ed39f605..dfe36bce2 100644 --- a/channeldb/payments_kv_store_test.go +++ b/channeldb/payments_kv_store_test.go @@ -15,7 +15,10 @@ import ( "github.com/davecgh/go-spew/spew" "github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/lntypes" + "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/record" + "github.com/lightningnetwork/lnd/routing/route" + "github.com/lightningnetwork/lnd/tlv" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -1427,3 +1430,355 @@ func assertPayments(t *testing.T, db *DB, payments []*payment) { // Check that each payment we want to assert exists in the database. require.Equal(t, payments, p) } + +func makeFakeInfo(t *testing.T) (*PaymentCreationInfo, *HTLCAttemptInfo) { + var preimg lntypes.Preimage + copy(preimg[:], rev[:]) + + hash := preimg.Hash() + + c := &PaymentCreationInfo{ + PaymentIdentifier: hash, + Value: 1000, + // Use single second precision to avoid false positive test + // failures due to the monotonic time component. + CreationTime: time.Unix(time.Now().Unix(), 0), + PaymentRequest: []byte("test"), + } + + a, err := NewHtlcAttempt( + 44, priv, testRoute, time.Unix(100, 0), &hash, + ) + require.NoError(t, err) + + return c, &a.HTLCAttemptInfo +} + +func TestSentPaymentSerialization(t *testing.T) { + t.Parallel() + + c, s := makeFakeInfo(t) + + var b bytes.Buffer + require.NoError(t, serializePaymentCreationInfo(&b, c), "serialize") + + // Assert the length of the serialized creation info is as expected, + // without any custom records. + baseLength := 32 + 8 + 8 + 4 + len(c.PaymentRequest) + require.Len(t, b.Bytes(), baseLength) + + newCreationInfo, err := deserializePaymentCreationInfo(&b) + require.NoError(t, err, "deserialize") + require.Equal(t, c, newCreationInfo) + + b.Reset() + + // Now we add some custom records to the creation info and serialize it + // again. + c.FirstHopCustomRecords = lnwire.CustomRecords{ + lnwire.MinCustomRecordsTlvType: []byte{1, 2, 3}, + } + require.NoError(t, serializePaymentCreationInfo(&b, c), "serialize") + + newCreationInfo, err = deserializePaymentCreationInfo(&b) + require.NoError(t, err, "deserialize") + require.Equal(t, c, newCreationInfo) + + b.Reset() + require.NoError(t, serializeHTLCAttemptInfo(&b, s), "serialize") + + newWireInfo, err := deserializeHTLCAttemptInfo(&b) + require.NoError(t, err, "deserialize") + + // First we verify all the records match up properly. + require.Equal(t, s.Route, newWireInfo.Route) + + // We now add the new fields and custom records to the route and + // serialize it again. + b.Reset() + s.Route.FirstHopAmount = tlv.NewRecordT[tlv.TlvType0]( + tlv.NewBigSizeT(lnwire.MilliSatoshi(1234)), + ) + s.Route.FirstHopWireCustomRecords = lnwire.CustomRecords{ + lnwire.MinCustomRecordsTlvType + 3: []byte{4, 5, 6}, + } + require.NoError(t, serializeHTLCAttemptInfo(&b, s), "serialize") + + newWireInfo, err = deserializeHTLCAttemptInfo(&b) + require.NoError(t, err, "deserialize") + require.Equal(t, s.Route, newWireInfo.Route) + + err = newWireInfo.attachOnionBlobAndCircuit() + require.NoError(t, err) + + // Clear routes to allow DeepEqual to compare the remaining fields. + newWireInfo.Route = route.Route{} + s.Route = route.Route{} + newWireInfo.AttemptID = s.AttemptID + + // Call session key method to set our cached session key so we can use + // DeepEqual, and assert that our key equals the original key. + require.Equal(t, s.cachedSessionKey, newWireInfo.SessionKey()) + + require.Equal(t, s, newWireInfo) +} + +// TestRouteSerialization tests serialization of a regular and blinded route. +func TestRouteSerialization(t *testing.T) { + t.Parallel() + + testSerializeRoute(t, testRoute) + testSerializeRoute(t, testBlindedRoute) +} + +func testSerializeRoute(t *testing.T, route route.Route) { + var b bytes.Buffer + err := SerializeRoute(&b, route) + require.NoError(t, err) + + r := bytes.NewReader(b.Bytes()) + route2, err := DeserializeRoute(r) + require.NoError(t, err) + + reflect.DeepEqual(route, route2) +} + +// deletePayment removes a payment with paymentHash from the payments database. +func deletePayment(t *testing.T, db *DB, paymentHash lntypes.Hash, seqNr uint64) { + t.Helper() + + err := kvdb.Update(db, func(tx kvdb.RwTx) error { + payments := tx.ReadWriteBucket(paymentsRootBucket) + + // Delete the payment bucket. + err := payments.DeleteNestedBucket(paymentHash[:]) + if err != nil { + return err + } + + key := make([]byte, 8) + byteOrder.PutUint64(key, seqNr) + + // Delete the index that references this payment. + indexes := tx.ReadWriteBucket(paymentsIndexBucket) + return indexes.Delete(key) + }, func() {}) + + if err != nil { + t.Fatalf("could not delete "+ + "payment: %v", err) + } +} + +// TestFetchPaymentWithSequenceNumber tests lookup of payments with their +// sequence number. It sets up one payment with no duplicates, and another with +// two duplicates in its duplicates bucket then uses these payments to test the +// case where a specific duplicate is not found and the duplicates bucket is not +// present when we expect it to be. +func TestFetchPaymentWithSequenceNumber(t *testing.T) { + db, err := MakeTestDB(t) + require.NoError(t, err) + + paymentDB := NewKVPaymentsDB(db) + + // Generate a test payment which does not have duplicates. + noDuplicates, _, _, err := genInfo(t) + require.NoError(t, err) + + // Create a new payment entry in the database. + err = paymentDB.InitPayment( + noDuplicates.PaymentIdentifier, noDuplicates, + ) + require.NoError(t, err) + + // Fetch the payment so we can get its sequence nr. + noDuplicatesPayment, err := paymentDB.FetchPayment( + noDuplicates.PaymentIdentifier, + ) + require.NoError(t, err) + + // Generate a test payment which we will add duplicates to. + hasDuplicates, _, preimg, err := genInfo(t) + require.NoError(t, err) + + // Create a new payment entry in the database. + err = paymentDB.InitPayment( + hasDuplicates.PaymentIdentifier, hasDuplicates, + ) + require.NoError(t, err) + + // Fetch the payment so we can get its sequence nr. + hasDuplicatesPayment, err := paymentDB.FetchPayment( + hasDuplicates.PaymentIdentifier, + ) + require.NoError(t, err) + + // We declare the sequence numbers used here so that we can reference + // them in tests. + var ( + duplicateOneSeqNr = hasDuplicatesPayment.SequenceNum + 1 + duplicateTwoSeqNr = hasDuplicatesPayment.SequenceNum + 2 + ) + + // Add two duplicates to our second payment. + appendDuplicatePayment( + t, db, hasDuplicates.PaymentIdentifier, duplicateOneSeqNr, preimg, + ) + appendDuplicatePayment( + t, db, hasDuplicates.PaymentIdentifier, duplicateTwoSeqNr, preimg, + ) + + tests := []struct { + name string + paymentHash lntypes.Hash + sequenceNumber uint64 + expectedErr error + }{ + { + name: "lookup payment without duplicates", + paymentHash: noDuplicates.PaymentIdentifier, + sequenceNumber: noDuplicatesPayment.SequenceNum, + expectedErr: nil, + }, + { + name: "lookup payment with duplicates", + paymentHash: hasDuplicates.PaymentIdentifier, + sequenceNumber: hasDuplicatesPayment.SequenceNum, + expectedErr: nil, + }, + { + name: "lookup first duplicate", + paymentHash: hasDuplicates.PaymentIdentifier, + sequenceNumber: duplicateOneSeqNr, + expectedErr: nil, + }, + { + name: "lookup second duplicate", + paymentHash: hasDuplicates.PaymentIdentifier, + sequenceNumber: duplicateTwoSeqNr, + expectedErr: nil, + }, + { + name: "lookup non-existent duplicate", + paymentHash: hasDuplicates.PaymentIdentifier, + sequenceNumber: 999999, + expectedErr: ErrDuplicateNotFound, + }, + { + name: "lookup duplicate, no duplicates bucket", + paymentHash: noDuplicates.PaymentIdentifier, + sequenceNumber: duplicateTwoSeqNr, + expectedErr: ErrNoDuplicateBucket, + }, + } + + for _, test := range tests { + test := test + + t.Run(test.name, func(t *testing.T) { + err := kvdb.Update( + db, func(tx walletdb.ReadWriteTx) error { + var seqNrBytes [8]byte + byteOrder.PutUint64( + seqNrBytes[:], test.sequenceNumber, + ) + + _, err := fetchPaymentWithSequenceNumber( + tx, test.paymentHash, seqNrBytes[:], + ) + return err + }, func() {}, + ) + require.Equal(t, test.expectedErr, err) + }) + } +} + +// appendDuplicatePayment adds a duplicate payment to an existing payment. Note +// that this function requires a unique sequence number. +// +// This code is *only* intended to replicate legacy duplicate payments in lnd, +// our current schema does not allow duplicates. +func appendDuplicatePayment(t *testing.T, db *DB, paymentHash lntypes.Hash, + seqNr uint64, preImg lntypes.Preimage) { + + err := kvdb.Update(db, func(tx walletdb.ReadWriteTx) error { + bucket, err := fetchPaymentBucketUpdate( + tx, paymentHash, + ) + if err != nil { + return err + } + + // Create the duplicates bucket if it is not + // present. + dup, err := bucket.CreateBucketIfNotExists( + duplicatePaymentsBucket, + ) + if err != nil { + return err + } + + var sequenceKey [8]byte + byteOrder.PutUint64(sequenceKey[:], seqNr) + + // Create duplicate payments for the two dup + // sequence numbers we've setup. + putDuplicatePayment(t, dup, sequenceKey[:], paymentHash, preImg) + + // Finally, once we have created our entry we add an index for + // it. + err = createPaymentIndexEntry(tx, sequenceKey[:], paymentHash) + require.NoError(t, err) + + return nil + }, func() {}) + require.NoError(t, err, "could not create payment") +} + +// putDuplicatePayment creates a duplicate payment in the duplicates bucket +// provided with the minimal information required for successful reading. +func putDuplicatePayment(t *testing.T, duplicateBucket kvdb.RwBucket, + sequenceKey []byte, paymentHash lntypes.Hash, + preImg lntypes.Preimage) { + + paymentBucket, err := duplicateBucket.CreateBucketIfNotExists( + sequenceKey, + ) + require.NoError(t, err) + + err = paymentBucket.Put(duplicatePaymentSequenceKey, sequenceKey) + require.NoError(t, err) + + // Generate fake information for the duplicate payment. + info, _, _, err := genInfo(t) + require.NoError(t, err) + + // Write the payment info to disk under the creation info key. This code + // is copied rather than using serializePaymentCreationInfo to ensure + // we always write in the legacy format used by duplicate payments. + var b bytes.Buffer + var scratch [8]byte + _, err = b.Write(paymentHash[:]) + require.NoError(t, err) + + byteOrder.PutUint64(scratch[:], uint64(info.Value)) + _, err = b.Write(scratch[:]) + require.NoError(t, err) + + err = serializeTime(&b, info.CreationTime) + require.NoError(t, err) + + byteOrder.PutUint32(scratch[:4], 0) + _, err = b.Write(scratch[:4]) + require.NoError(t, err) + + // Get the PaymentCreationInfo. + err = paymentBucket.Put(duplicatePaymentCreationInfoKey, b.Bytes()) + require.NoError(t, err) + + // Duolicate payments are only stored for successes, so add the + // preimage. + err = paymentBucket.Put(duplicatePaymentSettleInfoKey, preImg[:]) + require.NoError(t, err) +} diff --git a/channeldb/payments_test.go b/channeldb/payments_test.go index fdc68fbf9..83f85ba99 100644 --- a/channeldb/payments_test.go +++ b/channeldb/payments_test.go @@ -1,7 +1,6 @@ package channeldb import ( - "bytes" "fmt" "math" "reflect" @@ -9,14 +8,9 @@ import ( "time" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcwallet/walletdb" "github.com/davecgh/go-spew/spew" - "github.com/lightningnetwork/lnd/kvdb" - "github.com/lightningnetwork/lnd/lntypes" - "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/record" "github.com/lightningnetwork/lnd/routing/route" - "github.com/lightningnetwork/lnd/tlv" "github.com/stretchr/testify/require" ) @@ -97,98 +91,6 @@ var ( } ) -func makeFakeInfo(t *testing.T) (*PaymentCreationInfo, *HTLCAttemptInfo) { - var preimg lntypes.Preimage - copy(preimg[:], rev[:]) - - hash := preimg.Hash() - - c := &PaymentCreationInfo{ - PaymentIdentifier: hash, - Value: 1000, - // Use single second precision to avoid false positive test - // failures due to the monotonic time component. - CreationTime: time.Unix(time.Now().Unix(), 0), - PaymentRequest: []byte("test"), - } - - a, err := NewHtlcAttempt( - 44, priv, testRoute, time.Unix(100, 0), &hash, - ) - require.NoError(t, err) - - return c, &a.HTLCAttemptInfo -} - -func TestSentPaymentSerialization(t *testing.T) { - t.Parallel() - - c, s := makeFakeInfo(t) - - var b bytes.Buffer - require.NoError(t, serializePaymentCreationInfo(&b, c), "serialize") - - // Assert the length of the serialized creation info is as expected, - // without any custom records. - baseLength := 32 + 8 + 8 + 4 + len(c.PaymentRequest) - require.Len(t, b.Bytes(), baseLength) - - newCreationInfo, err := deserializePaymentCreationInfo(&b) - require.NoError(t, err, "deserialize") - require.Equal(t, c, newCreationInfo) - - b.Reset() - - // Now we add some custom records to the creation info and serialize it - // again. - c.FirstHopCustomRecords = lnwire.CustomRecords{ - lnwire.MinCustomRecordsTlvType: []byte{1, 2, 3}, - } - require.NoError(t, serializePaymentCreationInfo(&b, c), "serialize") - - newCreationInfo, err = deserializePaymentCreationInfo(&b) - require.NoError(t, err, "deserialize") - require.Equal(t, c, newCreationInfo) - - b.Reset() - require.NoError(t, serializeHTLCAttemptInfo(&b, s), "serialize") - - newWireInfo, err := deserializeHTLCAttemptInfo(&b) - require.NoError(t, err, "deserialize") - - // First we verify all the records match up properly. - require.Equal(t, s.Route, newWireInfo.Route) - - // We now add the new fields and custom records to the route and - // serialize it again. - b.Reset() - s.Route.FirstHopAmount = tlv.NewRecordT[tlv.TlvType0]( - tlv.NewBigSizeT(lnwire.MilliSatoshi(1234)), - ) - s.Route.FirstHopWireCustomRecords = lnwire.CustomRecords{ - lnwire.MinCustomRecordsTlvType + 3: []byte{4, 5, 6}, - } - require.NoError(t, serializeHTLCAttemptInfo(&b, s), "serialize") - - newWireInfo, err = deserializeHTLCAttemptInfo(&b) - require.NoError(t, err, "deserialize") - require.Equal(t, s.Route, newWireInfo.Route) - - err = newWireInfo.attachOnionBlobAndCircuit() - require.NoError(t, err) - - // Clear routes to allow DeepEqual to compare the remaining fields. - newWireInfo.Route = route.Route{} - s.Route = route.Route{} - newWireInfo.AttemptID = s.AttemptID - - // Call session key method to set our cached session key so we can use - // DeepEqual, and assert that our key equals the original key. - require.Equal(t, s.cachedSessionKey, newWireInfo.SessionKey()) - - require.Equal(t, s, newWireInfo) -} - // assertRouteEquals compares to routes for equality and returns an error if // they are not equal. func assertRouteEqual(a, b *route.Route) error { @@ -200,53 +102,6 @@ func assertRouteEqual(a, b *route.Route) error { return nil } -// TestRouteSerialization tests serialization of a regular and blinded route. -func TestRouteSerialization(t *testing.T) { - t.Parallel() - - testSerializeRoute(t, testRoute) - testSerializeRoute(t, testBlindedRoute) -} - -func testSerializeRoute(t *testing.T, route route.Route) { - var b bytes.Buffer - err := SerializeRoute(&b, route) - require.NoError(t, err) - - r := bytes.NewReader(b.Bytes()) - route2, err := DeserializeRoute(r) - require.NoError(t, err) - - reflect.DeepEqual(route, route2) -} - -// deletePayment removes a payment with paymentHash from the payments database. -func deletePayment(t *testing.T, db *DB, paymentHash lntypes.Hash, seqNr uint64) { - t.Helper() - - err := kvdb.Update(db, func(tx kvdb.RwTx) error { - payments := tx.ReadWriteBucket(paymentsRootBucket) - - // Delete the payment bucket. - err := payments.DeleteNestedBucket(paymentHash[:]) - if err != nil { - return err - } - - key := make([]byte, 8) - byteOrder.PutUint64(key, seqNr) - - // Delete the index that references this payment. - indexes := tx.ReadWriteBucket(paymentsIndexBucket) - return indexes.Delete(key) - }, func() {}) - - if err != nil { - t.Fatalf("could not delete "+ - "payment: %v", err) - } -} - // TestQueryPayments tests retrieval of payments with forwards and reversed // queries. func TestQueryPayments(t *testing.T) { @@ -610,216 +465,3 @@ func TestQueryPayments(t *testing.T) { }) } } - -// TestFetchPaymentWithSequenceNumber tests lookup of payments with their -// sequence number. It sets up one payment with no duplicates, and another with -// two duplicates in its duplicates bucket then uses these payments to test the -// case where a specific duplicate is not found and the duplicates bucket is not -// present when we expect it to be. -func TestFetchPaymentWithSequenceNumber(t *testing.T) { - db, err := MakeTestDB(t) - require.NoError(t, err) - - paymentDB := NewKVPaymentsDB(db) - - // Generate a test payment which does not have duplicates. - noDuplicates, _, _, err := genInfo(t) - require.NoError(t, err) - - // Create a new payment entry in the database. - err = paymentDB.InitPayment( - noDuplicates.PaymentIdentifier, noDuplicates, - ) - require.NoError(t, err) - - // Fetch the payment so we can get its sequence nr. - noDuplicatesPayment, err := paymentDB.FetchPayment( - noDuplicates.PaymentIdentifier, - ) - require.NoError(t, err) - - // Generate a test payment which we will add duplicates to. - hasDuplicates, _, preimg, err := genInfo(t) - require.NoError(t, err) - - // Create a new payment entry in the database. - err = paymentDB.InitPayment( - hasDuplicates.PaymentIdentifier, hasDuplicates, - ) - require.NoError(t, err) - - // Fetch the payment so we can get its sequence nr. - hasDuplicatesPayment, err := paymentDB.FetchPayment( - hasDuplicates.PaymentIdentifier, - ) - require.NoError(t, err) - - // We declare the sequence numbers used here so that we can reference - // them in tests. - var ( - duplicateOneSeqNr = hasDuplicatesPayment.SequenceNum + 1 - duplicateTwoSeqNr = hasDuplicatesPayment.SequenceNum + 2 - ) - - // Add two duplicates to our second payment. - appendDuplicatePayment( - t, db, hasDuplicates.PaymentIdentifier, duplicateOneSeqNr, preimg, - ) - appendDuplicatePayment( - t, db, hasDuplicates.PaymentIdentifier, duplicateTwoSeqNr, preimg, - ) - - tests := []struct { - name string - paymentHash lntypes.Hash - sequenceNumber uint64 - expectedErr error - }{ - { - name: "lookup payment without duplicates", - paymentHash: noDuplicates.PaymentIdentifier, - sequenceNumber: noDuplicatesPayment.SequenceNum, - expectedErr: nil, - }, - { - name: "lookup payment with duplicates", - paymentHash: hasDuplicates.PaymentIdentifier, - sequenceNumber: hasDuplicatesPayment.SequenceNum, - expectedErr: nil, - }, - { - name: "lookup first duplicate", - paymentHash: hasDuplicates.PaymentIdentifier, - sequenceNumber: duplicateOneSeqNr, - expectedErr: nil, - }, - { - name: "lookup second duplicate", - paymentHash: hasDuplicates.PaymentIdentifier, - sequenceNumber: duplicateTwoSeqNr, - expectedErr: nil, - }, - { - name: "lookup non-existent duplicate", - paymentHash: hasDuplicates.PaymentIdentifier, - sequenceNumber: 999999, - expectedErr: ErrDuplicateNotFound, - }, - { - name: "lookup duplicate, no duplicates bucket", - paymentHash: noDuplicates.PaymentIdentifier, - sequenceNumber: duplicateTwoSeqNr, - expectedErr: ErrNoDuplicateBucket, - }, - } - - for _, test := range tests { - test := test - - t.Run(test.name, func(t *testing.T) { - err := kvdb.Update( - db, func(tx walletdb.ReadWriteTx) error { - var seqNrBytes [8]byte - byteOrder.PutUint64( - seqNrBytes[:], test.sequenceNumber, - ) - - _, err := fetchPaymentWithSequenceNumber( - tx, test.paymentHash, seqNrBytes[:], - ) - return err - }, func() {}, - ) - require.Equal(t, test.expectedErr, err) - }) - } -} - -// appendDuplicatePayment adds a duplicate payment to an existing payment. Note -// that this function requires a unique sequence number. -// -// This code is *only* intended to replicate legacy duplicate payments in lnd, -// our current schema does not allow duplicates. -func appendDuplicatePayment(t *testing.T, db *DB, paymentHash lntypes.Hash, - seqNr uint64, preImg lntypes.Preimage) { - - err := kvdb.Update(db, func(tx walletdb.ReadWriteTx) error { - bucket, err := fetchPaymentBucketUpdate( - tx, paymentHash, - ) - if err != nil { - return err - } - - // Create the duplicates bucket if it is not - // present. - dup, err := bucket.CreateBucketIfNotExists( - duplicatePaymentsBucket, - ) - if err != nil { - return err - } - - var sequenceKey [8]byte - byteOrder.PutUint64(sequenceKey[:], seqNr) - - // Create duplicate payments for the two dup - // sequence numbers we've setup. - putDuplicatePayment(t, dup, sequenceKey[:], paymentHash, preImg) - - // Finally, once we have created our entry we add an index for - // it. - err = createPaymentIndexEntry(tx, sequenceKey[:], paymentHash) - require.NoError(t, err) - - return nil - }, func() {}) - require.NoError(t, err, "could not create payment") -} - -// putDuplicatePayment creates a duplicate payment in the duplicates bucket -// provided with the minimal information required for successful reading. -func putDuplicatePayment(t *testing.T, duplicateBucket kvdb.RwBucket, - sequenceKey []byte, paymentHash lntypes.Hash, - preImg lntypes.Preimage) { - - paymentBucket, err := duplicateBucket.CreateBucketIfNotExists( - sequenceKey, - ) - require.NoError(t, err) - - err = paymentBucket.Put(duplicatePaymentSequenceKey, sequenceKey) - require.NoError(t, err) - - // Generate fake information for the duplicate payment. - info, _, _, err := genInfo(t) - require.NoError(t, err) - - // Write the payment info to disk under the creation info key. This code - // is copied rather than using serializePaymentCreationInfo to ensure - // we always write in the legacy format used by duplicate payments. - var b bytes.Buffer - var scratch [8]byte - _, err = b.Write(paymentHash[:]) - require.NoError(t, err) - - byteOrder.PutUint64(scratch[:], uint64(info.Value)) - _, err = b.Write(scratch[:]) - require.NoError(t, err) - - err = serializeTime(&b, info.CreationTime) - require.NoError(t, err) - - byteOrder.PutUint32(scratch[:4], 0) - _, err = b.Write(scratch[:4]) - require.NoError(t, err) - - // Get the PaymentCreationInfo. - err = paymentBucket.Put(duplicatePaymentCreationInfoKey, b.Bytes()) - require.NoError(t, err) - - // Duolicate payments are only stored for successes, so add the - // preimage. - err = paymentBucket.Put(duplicatePaymentSettleInfoKey, preImg[:]) - require.NoError(t, err) -} From 0aef5fa44c41cb8f0bd74447fddc1f39fd7990e8 Mon Sep 17 00:00:00 2001 From: ziggie Date: Sat, 2 Aug 2025 10:19:45 +0200 Subject: [PATCH 7/8] multi: unify all payment related kv data All interactions related to the payment db are now part of the kvPaymentDB struct. --- channeldb/payments_kv_store.go | 18 ++++---- channeldb/payments_kv_store_test.go | 68 ++++++++++++++--------------- channeldb/payments_test.go | 9 ++-- rpcserver.go | 6 +-- 4 files changed, 51 insertions(+), 50 deletions(-) diff --git a/channeldb/payments_kv_store.go b/channeldb/payments_kv_store.go index 198267bbc..5dac3d45d 100644 --- a/channeldb/payments_kv_store.go +++ b/channeldb/payments_kv_store.go @@ -367,7 +367,7 @@ func (p *KVPaymentsDB) InitPayment(paymentHash lntypes.Hash, func (p *KVPaymentsDB) DeleteFailedAttempts(hash lntypes.Hash) error { if !p.db.keepFailedPaymentAttempts { const failedHtlcsOnly = true - err := p.db.DeletePayment(hash, failedHtlcsOnly) + err := p.DeletePayment(hash, failedHtlcsOnly) if err != nil { return err } @@ -958,10 +958,10 @@ func htlcBucketKey(prefix, id []byte) []byte { // FetchPayments returns all sent payments found in the DB. // // nolint: dupl -func (d *DB) FetchPayments() ([]*MPPayment, error) { +func (p *KVPaymentsDB) FetchPayments() ([]*MPPayment, error) { var payments []*MPPayment - err := kvdb.View(d, func(tx kvdb.RTx) error { + err := kvdb.View(p.db, func(tx kvdb.RTx) error { paymentsBucket := tx.ReadBucket(paymentsRootBucket) if paymentsBucket == nil { return nil @@ -1196,10 +1196,10 @@ func fetchFailedHtlcKeys(bucket kvdb.RBucket) ([][]byte, error) { // QueryPayments is a query to the payments database which is restricted // to a subset of payments by the payments query, containing an offset // index and a maximum number of returned payments. -func (d *DB) QueryPayments(query PaymentsQuery) (PaymentsResponse, error) { +func (p *KVPaymentsDB) QueryPayments(query PaymentsQuery) (PaymentsResponse, error) { var resp PaymentsResponse - if err := kvdb.View(d, func(tx kvdb.RTx) error { + if err := kvdb.View(p.db, func(tx kvdb.RTx) error { // Get the root payments bucket. paymentsBucket := tx.ReadBucket(paymentsRootBucket) if paymentsBucket == nil { @@ -1413,10 +1413,10 @@ func fetchPaymentWithSequenceNumber(tx kvdb.RTx, paymentHash lntypes.Hash, // DeletePayment deletes a payment from the DB given its payment hash. If // failedHtlcsOnly is set, only failed HTLC attempts of the payment will be // deleted. -func (d *DB) DeletePayment(paymentHash lntypes.Hash, +func (p *KVPaymentsDB) DeletePayment(paymentHash lntypes.Hash, failedHtlcsOnly bool) error { - return kvdb.Update(d, func(tx kvdb.RwTx) error { + return kvdb.Update(p.db, func(tx kvdb.RwTx) error { payments := tx.ReadWriteBucket(paymentsRootBucket) if payments == nil { return nil @@ -1505,9 +1505,9 @@ func (d *DB) DeletePayment(paymentHash lntypes.Hash, // failedHtlcsOnly is set, the payment itself won't be deleted, only failed HTLC // attempts. The method returns the number of deleted payments, which is always // 0 if failedHtlcsOnly is set. -func (d *DB) DeletePayments(failedOnly, failedHtlcsOnly bool) (int, error) { +func (p *KVPaymentsDB) DeletePayments(failedOnly, failedHtlcsOnly bool) (int, error) { var numPayments int - err := kvdb.Update(d, func(tx kvdb.RwTx) error { + err := kvdb.Update(p.db, func(tx kvdb.RwTx) error { payments := tx.ReadWriteBucket(paymentsRootBucket) if payments == nil { return nil diff --git a/channeldb/payments_kv_store_test.go b/channeldb/payments_kv_store_test.go index dfe36bce2..926894f18 100644 --- a/channeldb/payments_kv_store_test.go +++ b/channeldb/payments_kv_store_test.go @@ -469,12 +469,12 @@ func TestKVPaymentsDBDeleteNonInFlight(t *testing.T) { } // Delete all failed payments. - numPayments, err := db.DeletePayments(true, false) + numPayments, err := paymentDB.DeletePayments(true, false) require.NoError(t, err) require.EqualValues(t, 1, numPayments) // This should leave the succeeded and in-flight payments. - dbPayments, err := db.FetchPayments() + dbPayments, err := paymentDB.FetchPayments() if err != nil { t.Fatal(err) } @@ -505,12 +505,12 @@ func TestKVPaymentsDBDeleteNonInFlight(t *testing.T) { } // Now delete all payments except in-flight. - numPayments, err = db.DeletePayments(false, false) + numPayments, err = paymentDB.DeletePayments(false, false) require.NoError(t, err) require.EqualValues(t, 2, numPayments) // This should leave the in-flight payment. - dbPayments, err = db.FetchPayments() + dbPayments, err = paymentDB.FetchPayments() if err != nil { t.Fatal(err) } @@ -567,40 +567,40 @@ func TestKVPaymentsDBDeletePayments(t *testing.T) { createTestPayments(t, paymentDB, payments) // Check that all payments are there as we added them. - assertPayments(t, db, payments) + assertPayments(t, paymentDB, payments) // Delete HTLC attempts for failed payments only. - numPayments, err := db.DeletePayments(true, true) + numPayments, err := paymentDB.DeletePayments(true, true) require.NoError(t, err) require.EqualValues(t, 0, numPayments) // The failed payment is the only altered one. payments[0].htlcs = 0 - assertPayments(t, db, payments) + assertPayments(t, paymentDB, payments) // Delete failed attempts for all payments. - numPayments, err = db.DeletePayments(false, true) + numPayments, err = paymentDB.DeletePayments(false, true) require.NoError(t, err) require.EqualValues(t, 0, numPayments) // The failed attempts should be deleted, except for the in-flight // payment, that shouldn't be altered until it has completed. payments[1].htlcs = 1 - assertPayments(t, db, payments) + assertPayments(t, paymentDB, payments) // Now delete all failed payments. - numPayments, err = db.DeletePayments(true, false) + numPayments, err = paymentDB.DeletePayments(true, false) require.NoError(t, err) require.EqualValues(t, 1, numPayments) - assertPayments(t, db, payments[1:]) + assertPayments(t, paymentDB, payments[1:]) // Finally delete all completed payments. - numPayments, err = db.DeletePayments(false, false) + numPayments, err = paymentDB.DeletePayments(false, false) require.NoError(t, err) require.EqualValues(t, 1, numPayments) - assertPayments(t, db, payments[2:]) + assertPayments(t, paymentDB, payments[2:]) } // TestKVPaymentsDBDeleteSinglePayment tests that DeletePayment correctly @@ -639,55 +639,55 @@ func TestKVPaymentsDBDeleteSinglePayment(t *testing.T) { createTestPayments(t, paymentDB, payments) // Check that all payments are there as we added them. - assertPayments(t, db, payments) + assertPayments(t, paymentDB, payments) // Delete HTLC attempts for first payment only. - require.NoError(t, db.DeletePayment(payments[0].id, true)) + require.NoError(t, paymentDB.DeletePayment(payments[0].id, true)) // The first payment is the only altered one as its failed HTLC should // have been removed but is still present as payment. payments[0].htlcs = 0 - assertPayments(t, db, payments) + assertPayments(t, paymentDB, payments) // Delete the first payment completely. - require.NoError(t, db.DeletePayment(payments[0].id, false)) + require.NoError(t, paymentDB.DeletePayment(payments[0].id, false)) // The first payment should have been deleted. - assertPayments(t, db, payments[1:]) + assertPayments(t, paymentDB, payments[1:]) // Now delete the second payment completely. - require.NoError(t, db.DeletePayment(payments[1].id, false)) + require.NoError(t, paymentDB.DeletePayment(payments[1].id, false)) // The Second payment should have been deleted. - assertPayments(t, db, payments[2:]) + assertPayments(t, paymentDB, payments[2:]) // Delete failed HTLC attempts for the third payment. - require.NoError(t, db.DeletePayment(payments[2].id, true)) + require.NoError(t, paymentDB.DeletePayment(payments[2].id, true)) // Only the successful HTLC attempt should be left for the third // payment. payments[2].htlcs = 1 - assertPayments(t, db, payments[2:]) + assertPayments(t, paymentDB, payments[2:]) // Now delete the third payment completely. - require.NoError(t, db.DeletePayment(payments[2].id, false)) + require.NoError(t, paymentDB.DeletePayment(payments[2].id, false)) // Only the last payment should be left. - assertPayments(t, db, payments[3:]) + assertPayments(t, paymentDB, payments[3:]) // Deleting HTLC attempts from InFlight payments should not work and an // error returned. - require.Error(t, db.DeletePayment(payments[3].id, true)) + require.Error(t, paymentDB.DeletePayment(payments[3].id, true)) // The payment is InFlight and therefore should not have been altered. - assertPayments(t, db, payments[3:]) + assertPayments(t, paymentDB, payments[3:]) // Finally deleting the InFlight payment should also not work and an // error returned. - require.Error(t, db.DeletePayment(payments[3].id, false)) + require.Error(t, paymentDB.DeletePayment(payments[3].id, false)) // The payment is InFlight and therefore should not have been altered. - assertPayments(t, db, payments[3:]) + assertPayments(t, paymentDB, payments[3:]) } // TestKVPaymentsDBMultiShard checks the ability of payment control to @@ -1113,7 +1113,7 @@ func testDeleteFailedAttempts(t *testing.T, keepFailedPaymentAttempts bool) { createTestPayments(t, paymentDB, payments) // Check that all payments are there as we added them. - assertPayments(t, db, payments) + assertPayments(t, paymentDB, payments) // Calling DeleteFailedAttempts on a failed payment should delete all // HTLCs. @@ -1123,7 +1123,7 @@ func testDeleteFailedAttempts(t *testing.T, keepFailedPaymentAttempts bool) { if !keepFailedPaymentAttempts { payments[0].htlcs = 0 } - assertPayments(t, db, payments) + assertPayments(t, paymentDB, payments) // Calling DeleteFailedAttempts on an in-flight payment should return // an error. @@ -1137,7 +1137,7 @@ func testDeleteFailedAttempts(t *testing.T, keepFailedPaymentAttempts bool) { // Since DeleteFailedAttempts returned an error, we should expect the // payment to be unchanged. - assertPayments(t, db, payments) + assertPayments(t, paymentDB, payments) // Cleaning up a successful payment should remove failed htlcs. require.NoError(t, paymentDB.DeleteFailedAttempts(payments[2].id)) @@ -1146,7 +1146,7 @@ func testDeleteFailedAttempts(t *testing.T, keepFailedPaymentAttempts bool) { if !keepFailedPaymentAttempts { payments[2].htlcs = 1 } - assertPayments(t, db, payments) + assertPayments(t, paymentDB, payments) if keepFailedPaymentAttempts { // DeleteFailedAttempts is ignored, even for non-existent @@ -1405,10 +1405,10 @@ func createTestPayments(t *testing.T, p *KVPaymentsDB, payments []*payment) { // indices for the slice asserts that exactly the same payments in the // slice for the provided indices exist when fetching payments from the // database. -func assertPayments(t *testing.T, db *DB, payments []*payment) { +func assertPayments(t *testing.T, paymentDB *KVPaymentsDB, payments []*payment) { t.Helper() - dbPayments, err := db.FetchPayments() + dbPayments, err := paymentDB.FetchPayments() require.NoError(t, err, "could not fetch payments from db") // Make sure that the number of fetched payments is the same diff --git a/channeldb/payments_test.go b/channeldb/payments_test.go index 83f85ba99..a5762c110 100644 --- a/channeldb/payments_test.go +++ b/channeldb/payments_test.go @@ -357,9 +357,11 @@ func TestQueryPayments(t *testing.T) { t.Fatalf("unable to init db: %v", err) } + paymentDB := NewKVPaymentsDB(db) + // Make a preliminary query to make sure it's ok to // query when we have no payments. - resp, err := db.QueryPayments(tt.query) + resp, err := paymentDB.QueryPayments(tt.query) require.NoError(t, err) require.Len(t, resp.Payments, 0) @@ -371,7 +373,6 @@ func TestQueryPayments(t *testing.T) { // where we have duplicates in the nested duplicates // bucket. nonDuplicatePayments := 6 - paymentDB := NewKVPaymentsDB(db) for i := 0; i < nonDuplicatePayments; i++ { // Generate a test payment. @@ -424,7 +425,7 @@ func TestQueryPayments(t *testing.T) { } // Fetch all payments in the database. - allPayments, err := db.FetchPayments() + allPayments, err := paymentDB.FetchPayments() if err != nil { t.Fatalf("payments could not be fetched from "+ "database: %v", err) @@ -436,7 +437,7 @@ func TestQueryPayments(t *testing.T) { len(allPayments), 6) } - querySlice, err := db.QueryPayments(tt.query) + querySlice, err := paymentDB.QueryPayments(tt.query) if err != nil { t.Fatalf("unexpected error: %v", err) } diff --git a/rpcserver.go b/rpcserver.go index ac4e98e27..9b8bedf69 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -7527,7 +7527,7 @@ func (r *rpcServer) ListPayments(ctx context.Context, query.MaxPayments = math.MaxUint64 } - paymentsQuerySlice, err := r.server.miscDB.QueryPayments(query) + paymentsQuerySlice, err := r.server.kvPaymentsDB.QueryPayments(query) if err != nil { return nil, err } @@ -7608,7 +7608,7 @@ func (r *rpcServer) DeletePayment(ctx context.Context, rpcsLog.Infof("[DeletePayment] payment_identifier=%v, "+ "failed_htlcs_only=%v", hash, req.FailedHtlcsOnly) - err = r.server.miscDB.DeletePayment(hash, req.FailedHtlcsOnly) + err = r.server.kvPaymentsDB.DeletePayment(hash, req.FailedHtlcsOnly) if err != nil { return nil, err } @@ -7648,7 +7648,7 @@ func (r *rpcServer) DeleteAllPayments(ctx context.Context, "failed_htlcs_only=%v", req.FailedPaymentsOnly, req.FailedHtlcsOnly) - numDeletedPayments, err := r.server.miscDB.DeletePayments( + numDeletedPayments, err := r.server.kvPaymentsDB.DeletePayments( req.FailedPaymentsOnly, req.FailedHtlcsOnly, ) if err != nil { From 2b3b27f5a96b814014c6899ffb0fb3c875dc1f65 Mon Sep 17 00:00:00 2001 From: ziggie Date: Sat, 2 Aug 2025 11:07:12 +0200 Subject: [PATCH 8/8] multi: fix linter issues --- channeldb/payments_kv_store.go | 57 ++++++++++++++++++++--------- channeldb/payments_kv_store_test.go | 23 +++++++++--- channeldb/payments_test.go | 17 +++++---- 3 files changed, 66 insertions(+), 31 deletions(-) diff --git a/channeldb/payments_kv_store.go b/channeldb/payments_kv_store.go index 5dac3d45d..584dc0f73 100644 --- a/channeldb/payments_kv_store.go +++ b/channeldb/payments_kv_store.go @@ -137,6 +137,7 @@ var ( "does not exist") ) +//nolint:ll var ( // paymentsRootBucket is the name of the top-level bucket within the // database that stores all data related to payments. Within this @@ -347,7 +348,7 @@ func (p *KVPaymentsDB) InitPayment(paymentHash lntypes.Hash, // are initializing a payment that was attempted earlier, but // left in a state where we could retry. err = bucket.DeleteNestedBucket(paymentHtlcsBucket) - if err != nil && err != kvdb.ErrBucketNotFound { + if err != nil && !errors.Is(err, kvdb.ErrBucketNotFound) { return err } @@ -372,6 +373,7 @@ func (p *KVPaymentsDB) DeleteFailedAttempts(hash lntypes.Hash) error { return err } } + return nil } @@ -395,6 +397,7 @@ func createPaymentIndexEntry(tx kvdb.RwTx, sequenceNumber []byte, } indexes := tx.ReadWriteBucket(paymentsIndexBucket) + return indexes.Put(sequenceNumber, b.Bytes()) } @@ -562,6 +565,7 @@ func (p *KVPaymentsDB) RegisterAttempt(paymentHash lntypes.Hash, // Retrieve attempt info for the notification. payment, err = fetchPayment(bucket) + return err }) if err != nil { @@ -637,17 +641,20 @@ func (p *KVPaymentsDB) updateHtlcKey(paymentHash lntypes.Hash, return fmt.Errorf("htlcs bucket not found") } - if htlcsBucket.Get(htlcBucketKey(htlcAttemptInfoKey, aid)) == nil { + attemptKey := htlcBucketKey(htlcAttemptInfoKey, aid) + if htlcsBucket.Get(attemptKey) == nil { return fmt.Errorf("HTLC with ID %v not registered", attemptID) } // Make sure the shard is not already failed or settled. - if htlcsBucket.Get(htlcBucketKey(htlcFailInfoKey, aid)) != nil { + failKey := htlcBucketKey(htlcFailInfoKey, aid) + if htlcsBucket.Get(failKey) != nil { return ErrAttemptAlreadyFailed } - if htlcsBucket.Get(htlcBucketKey(htlcSettleInfoKey, aid)) != nil { + settleKey := htlcBucketKey(htlcSettleInfoKey, aid) + if htlcsBucket.Get(settleKey) != nil { return ErrAttemptAlreadySettled } @@ -659,6 +666,7 @@ func (p *KVPaymentsDB) updateHtlcKey(paymentHash lntypes.Hash, // Retrieve attempt info for the notification. payment, err = fetchPayment(bucket) + return err }) if err != nil { @@ -687,7 +695,7 @@ func (p *KVPaymentsDB) Fail(paymentHash lntypes.Hash, prefetchPayment(tx, paymentHash) bucket, err := fetchPaymentBucketUpdate(tx, paymentHash) - if err == ErrPaymentNotInitiated { + if errors.Is(err, ErrPaymentNotInitiated) { updateErr = ErrPaymentNotInitiated return nil } else if err != nil { @@ -802,7 +810,6 @@ func fetchPaymentBucket(tx kvdb.RTx, paymentHash lntypes.Hash) ( } return bucket, nil - } // fetchPaymentBucketUpdate is identical to fetchPaymentBucket, but it returns a @@ -843,6 +850,7 @@ func (p *KVPaymentsDB) nextPaymentSequence() ([]byte, error) { currPaymentSeq = paymentsBucket.Sequence() newUpperBound = currPaymentSeq + paymentSeqBlockSize + return paymentsBucket.SetSequence(newUpperBound) }, func() {}); err != nil { return nil, err @@ -928,6 +936,7 @@ func (p *KVPaymentsDB) FetchInFlightPayments() ([]*MPPayment, error) { } inFlights = append(inFlights, p) + return nil }) }, func() { @@ -952,12 +961,11 @@ func htlcBucketKey(prefix, id []byte) []byte { key := make([]byte, len(prefix)+len(id)) copy(key, prefix) copy(key[len(prefix):], id) + return key } // FetchPayments returns all sent payments found in the DB. -// -// nolint: dupl func (p *KVPaymentsDB) FetchPayments() ([]*MPPayment, error) { var payments []*MPPayment @@ -993,6 +1001,7 @@ func (p *KVPaymentsDB) FetchPayments() ([]*MPPayment, error) { } payments = append(payments, duplicatePayments...) + return nil }) }, func() { @@ -1017,6 +1026,7 @@ func fetchCreationInfo(bucket kvdb.RBucket) (*PaymentCreationInfo, error) { } r := bytes.NewReader(b) + return deserializePaymentCreationInfo(r) } @@ -1196,7 +1206,9 @@ func fetchFailedHtlcKeys(bucket kvdb.RBucket) ([][]byte, error) { // QueryPayments is a query to the payments database which is restricted // to a subset of payments by the payments query, containing an offset // index and a maximum number of returned payments. -func (p *KVPaymentsDB) QueryPayments(query PaymentsQuery) (PaymentsResponse, error) { +func (p *KVPaymentsDB) QueryPayments(query PaymentsQuery) (PaymentsResponse, + error) { + var resp PaymentsResponse if err := kvdb.View(p.db, func(tx kvdb.RTx) error { @@ -1263,6 +1275,7 @@ func (p *KVPaymentsDB) QueryPayments(query PaymentsQuery) (PaymentsResponse, err // At this point, we've exhausted the offset, so we'll // begin collecting invoices found within the range. resp.Payments = append(resp.Payments, payment) + return true, nil } @@ -1294,7 +1307,8 @@ func (p *KVPaymentsDB) QueryPayments(query PaymentsQuery) (PaymentsResponse, err // In non-boltdb database backends, there's a faster // ForAll query that allows for batch fetching items. - if fastBucket, ok := indexes.(kvdb.ExtendedRBucket); ok { + fastBucket, ok := indexes.(kvdb.ExtendedRBucket) + if ok { err = fastBucket.ForAll(countFn) } else { err = indexes.ForEach(countFn) @@ -1456,7 +1470,9 @@ func (p *KVPaymentsDB) DeletePayment(paymentHash lntypes.Hash, for _, htlcID := range toDelete { err = htlcsBucket.Delete( - htlcBucketKey(htlcAttemptInfoKey, htlcID), + htlcBucketKey( + htlcAttemptInfoKey, htlcID, + ), ) if err != nil { return err @@ -1470,7 +1486,9 @@ func (p *KVPaymentsDB) DeletePayment(paymentHash lntypes.Hash, } err = htlcsBucket.Delete( - htlcBucketKey(htlcSettleInfoKey, htlcID), + htlcBucketKey( + htlcSettleInfoKey, htlcID, + ), ) if err != nil { return err @@ -1485,7 +1503,8 @@ func (p *KVPaymentsDB) DeletePayment(paymentHash lntypes.Hash, return err } - if err := payments.DeleteNestedBucket(paymentHash[:]); err != nil { + err = payments.DeleteNestedBucket(paymentHash[:]) + if err != nil { return err } @@ -1505,7 +1524,9 @@ func (p *KVPaymentsDB) DeletePayment(paymentHash lntypes.Hash, // failedHtlcsOnly is set, the payment itself won't be deleted, only failed HTLC // attempts. The method returns the number of deleted payments, which is always // 0 if failedHtlcsOnly is set. -func (p *KVPaymentsDB) DeletePayments(failedOnly, failedHtlcsOnly bool) (int, error) { +func (p *KVPaymentsDB) DeletePayments(failedOnly, + failedHtlcsOnly bool) (int, error) { + var numPayments int err := kvdb.Update(p.db, func(tx kvdb.RwTx) error { payments := tx.ReadWriteBucket(paymentsRootBucket) @@ -1585,6 +1606,7 @@ func (p *KVPaymentsDB) DeletePayments(failedOnly, failedHtlcsOnly bool) (int, er deleteIndexes = append(deleteIndexes, seqNrs...) numPayments++ + return nil }) if err != nil { @@ -1675,7 +1697,6 @@ func fetchSequenceNumbers(paymentBucket kvdb.RBucket) ([][]byte, error) { return sequenceNumbers, nil } -// nolint: dupl func serializePaymentCreationInfo(w io.Writer, c *PaymentCreationInfo) error { var scratch [8]byte @@ -1697,7 +1718,7 @@ func serializePaymentCreationInfo(w io.Writer, c *PaymentCreationInfo) error { return err } - if _, err := w.Write(c.PaymentRequest[:]); err != nil { + if _, err := w.Write(c.PaymentRequest); err != nil { return err } @@ -1739,7 +1760,7 @@ func deserializePaymentCreationInfo(r io.Reader) (*PaymentCreationInfo, return nil, err } - reqLen := uint32(byteOrder.Uint32(scratch[:4])) + reqLen := byteOrder.Uint32(scratch[:4]) payReq := make([]byte, reqLen) if reqLen > 0 { if _, err := io.ReadFull(r, payReq); err != nil { @@ -1827,7 +1848,7 @@ func deserializeHTLCAttemptInfo(r io.Reader) (*HTLCAttemptInfo, error) { switch { // Older payment attempts wouldn't have the hash set, in which case we // can just return. - case err == io.EOF, err == io.ErrUnexpectedEOF: + case errors.Is(err, io.EOF), errors.Is(err, io.ErrUnexpectedEOF): return a, nil case err != nil: diff --git a/channeldb/payments_kv_store_test.go b/channeldb/payments_kv_store_test.go index 926894f18..3e825597d 100644 --- a/channeldb/payments_kv_store_test.go +++ b/channeldb/payments_kv_store_test.go @@ -1405,7 +1405,9 @@ func createTestPayments(t *testing.T, p *KVPaymentsDB, payments []*payment) { // indices for the slice asserts that exactly the same payments in the // slice for the provided indices exist when fetching payments from the // database. -func assertPayments(t *testing.T, paymentDB *KVPaymentsDB, payments []*payment) { +func assertPayments(t *testing.T, paymentDB *KVPaymentsDB, + payments []*payment) { + t.Helper() dbPayments, err := paymentDB.FetchPayments() @@ -1544,7 +1546,9 @@ func testSerializeRoute(t *testing.T, route route.Route) { } // deletePayment removes a payment with paymentHash from the payments database. -func deletePayment(t *testing.T, db *DB, paymentHash lntypes.Hash, seqNr uint64) { +func deletePayment(t *testing.T, db *DB, paymentHash lntypes.Hash, + seqNr uint64) { + t.Helper() err := kvdb.Update(db, func(tx kvdb.RwTx) error { @@ -1561,6 +1565,7 @@ func deletePayment(t *testing.T, db *DB, paymentHash lntypes.Hash, seqNr uint64) // Delete the index that references this payment. indexes := tx.ReadWriteBucket(paymentsIndexBucket) + return indexes.Delete(key) }, func() {}) @@ -1622,10 +1627,12 @@ func TestFetchPaymentWithSequenceNumber(t *testing.T) { // Add two duplicates to our second payment. appendDuplicatePayment( - t, db, hasDuplicates.PaymentIdentifier, duplicateOneSeqNr, preimg, + t, db, hasDuplicates.PaymentIdentifier, duplicateOneSeqNr, + preimg, ) appendDuplicatePayment( - t, db, hasDuplicates.PaymentIdentifier, duplicateTwoSeqNr, preimg, + t, db, hasDuplicates.PaymentIdentifier, duplicateTwoSeqNr, + preimg, ) tests := []struct { @@ -1665,7 +1672,8 @@ func TestFetchPaymentWithSequenceNumber(t *testing.T) { expectedErr: ErrDuplicateNotFound, }, { - name: "lookup duplicate, no duplicates bucket", + name: "lookup duplicate, no duplicates " + + "bucket", paymentHash: noDuplicates.PaymentIdentifier, sequenceNumber: duplicateTwoSeqNr, expectedErr: ErrNoDuplicateBucket, @@ -1680,12 +1688,15 @@ func TestFetchPaymentWithSequenceNumber(t *testing.T) { db, func(tx walletdb.ReadWriteTx) error { var seqNrBytes [8]byte byteOrder.PutUint64( - seqNrBytes[:], test.sequenceNumber, + seqNrBytes[:], + test.sequenceNumber, ) + //nolint:ll _, err := fetchPaymentWithSequenceNumber( tx, test.paymentHash, seqNrBytes[:], ) + return err }, func() {}, ) diff --git a/channeldb/payments_test.go b/channeldb/payments_test.go index a5762c110..1d5e81465 100644 --- a/channeldb/payments_test.go +++ b/channeldb/payments_test.go @@ -432,9 +432,9 @@ func TestQueryPayments(t *testing.T) { } if len(allPayments) != 6 { - t.Fatalf("Number of payments received does not "+ - "match expected one. Got %v, want %v.", - len(allPayments), 6) + t.Fatalf("Number of payments received does "+ + "not match expected one. Got %v, "+ + "want %v.", len(allPayments), 6) } querySlice, err := paymentDB.QueryPayments(tt.query) @@ -445,7 +445,8 @@ func TestQueryPayments(t *testing.T) { tt.lastIndex != querySlice.LastIndexOffset { t.Errorf("First or last index does not match "+ - "expected index. Want (%d, %d), got (%d, %d).", + "expected index. Want (%d, %d), "+ + "got (%d, %d).", tt.firstIndex, tt.lastIndex, querySlice.FirstIndexOffset, querySlice.LastIndexOffset) @@ -453,14 +454,16 @@ func TestQueryPayments(t *testing.T) { if len(querySlice.Payments) != len(tt.expectedSeqNrs) { t.Errorf("expected: %v payments, got: %v", - len(tt.expectedSeqNrs), len(querySlice.Payments)) + len(tt.expectedSeqNrs), + len(querySlice.Payments)) } for i, seqNr := range tt.expectedSeqNrs { q := querySlice.Payments[i] if seqNr != q.SequenceNum { - t.Errorf("sequence numbers do not match, "+ - "got %v, want %v", q.SequenceNum, seqNr) + t.Errorf("sequence numbers do not "+ + "match, got %v, want %v", + q.SequenceNum, seqNr) } } })