mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-08-27 14:11:04 +02:00
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.
This commit is contained in:
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -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)
|
||||
}
|
||||
|
@@ -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)
|
||||
}
|
||||
|
Reference in New Issue
Block a user