Merge pull request #4285 from cfromknecht/pay-addr-index

channeldb: index payments by payment addr, use payment hash as fallback
This commit is contained in:
Conner Fromknecht authored and GitHub committed 2020-05-27 17:36:30 -07:00
commit 0f3ab775c7
17 files changed
+561 -183

No files matched your search

+35 -73
View File
@@ -13,6 +13,7 @@ import (
"github.com/btcsuite/btcwallet/walletdb"
"github.com/go-errors/errors"
"github.com/lightningnetwork/lnd/channeldb/kvdb"
mig "github.com/lightningnetwork/lnd/channeldb/migration"
"github.com/lightningnetwork/lnd/channeldb/migration12"
"github.com/lightningnetwork/lnd/channeldb/migration13"
"github.com/lightningnetwork/lnd/channeldb/migration_01_to_11"
@@ -136,6 +137,13 @@ var (
number: 13,
migration: migration13.MigrateMPP,
},
{
// Initialize payment address index and begin using it
// as the default index, falling back to payment hash
// index.
number: 14,
migration: mig.CreateTLB(payAddrIndexBucket),
},
}
// Big endian is the preferred byte order, due to cursor scans over
@@ -242,48 +250,32 @@ func (d *DB) Path() string {
return d.dbPath
}
var topLevelBuckets = [][]byte{
openChannelBucket,
closedChannelBucket,
forwardingLogBucket,
fwdPackagesKey,
invoiceBucket,
payAddrIndexBucket,
nodeInfoBucket,
nodeBucket,
edgeBucket,
edgeIndexBucket,
graphMetaBucket,
metaBucket,
}
// Wipe completely deletes all saved state within all used buckets within the
// database. The deletion is done in a single transaction, therefore this
// operation is fully atomic.
func (d *DB) Wipe() error {
return kvdb.Update(d, func(tx kvdb.RwTx) error {
err := tx.DeleteTopLevelBucket(openChannelBucket)
if err != nil && err != kvdb.ErrBucketNotFound {
return err
for _, tlb := range topLevelBuckets {
err := tx.DeleteTopLevelBucket(tlb)
if err != nil && err != kvdb.ErrBucketNotFound {
return err
}
}
err = tx.DeleteTopLevelBucket(closedChannelBucket)
if err != nil && err != kvdb.ErrBucketNotFound {
return err
}
err = tx.DeleteTopLevelBucket(invoiceBucket)
if err != nil && err != kvdb.ErrBucketNotFound {
return err
}
err = tx.DeleteTopLevelBucket(nodeInfoBucket)
if err != nil && err != kvdb.ErrBucketNotFound {
return err
}
err = tx.DeleteTopLevelBucket(nodeBucket)
if err != nil && err != kvdb.ErrBucketNotFound {
return err
}
err = tx.DeleteTopLevelBucket(edgeBucket)
if err != nil && err != kvdb.ErrBucketNotFound {
return err
}
err = tx.DeleteTopLevelBucket(edgeIndexBucket)
if err != nil && err != kvdb.ErrBucketNotFound {
return err
}
err = tx.DeleteTopLevelBucket(graphMetaBucket)
if err != nil && err != kvdb.ErrBucketNotFound {
return err
}
return nil
})
}
@@ -301,33 +293,13 @@ func initChannelDB(db kvdb.Backend) error {
return nil
}
if _, err := tx.CreateTopLevelBucket(openChannelBucket); err != nil {
return err
}
if _, err := tx.CreateTopLevelBucket(closedChannelBucket); err != nil {
return err
for _, tlb := range topLevelBuckets {
if _, err := tx.CreateTopLevelBucket(tlb); err != nil {
return err
}
}
if _, err := tx.CreateTopLevelBucket(forwardingLogBucket); err != nil {
return err
}
if _, err := tx.CreateTopLevelBucket(fwdPackagesKey); err != nil {
return err
}
if _, err := tx.CreateTopLevelBucket(invoiceBucket); err != nil {
return err
}
if _, err := tx.CreateTopLevelBucket(nodeInfoBucket); err != nil {
return err
}
nodes, err := tx.CreateTopLevelBucket(nodeBucket)
if err != nil {
return err
}
nodes := tx.ReadWriteBucket(nodeBucket)
_, err = nodes.CreateBucket(aliasIndexBucket)
if err != nil {
return err
@@ -337,10 +309,7 @@ func initChannelDB(db kvdb.Backend) error {
return err
}
edges, err := tx.CreateTopLevelBucket(edgeBucket)
if err != nil {
return err
}
edges := tx.ReadWriteBucket(edgeBucket)
if _, err := edges.CreateBucket(edgeIndexBucket); err != nil {
return err
}
@@ -354,19 +323,12 @@ func initChannelDB(db kvdb.Backend) error {
return err
}
graphMeta, err := tx.CreateTopLevelBucket(graphMetaBucket)
if err != nil {
return err
}
graphMeta := tx.ReadWriteBucket(graphMetaBucket)
_, err = graphMeta.CreateBucket(pruneLogBucket)
if err != nil {
return err
}
if _, err := tx.CreateTopLevelBucket(metaBucket); err != nil {
return err
}
meta.DbVersionNumber = getLatestDBVersion(dbVersions)
return putMeta(meta, tx)
})
+8
View File
@@ -43,6 +43,14 @@ var (
// payment hash already exists.
ErrDuplicateInvoice = fmt.Errorf("invoice with payment hash already exists")
// ErrDuplicatePayAddr is returned when an invoice with the target
// payment addr already exists.
ErrDuplicatePayAddr = fmt.Errorf("invoice with payemnt addr already exists")
// ErrInvRefEquivocation is returned when an InvoiceRef targets
// multiple, distinct invoices.
ErrInvRefEquivocation = errors.New("inv ref matches multiple invoices")
// ErrNoPaymentsCreated is returned when bucket of payments hasn't been
// created.
ErrNoPaymentsCreated = fmt.Errorf("there are no existing payments")
+178 -38
View File
@@ -20,16 +20,20 @@ var (
)
func randInvoice(value lnwire.MilliSatoshi) (*Invoice, error) {
var pre [32]byte
var pre, payAddr [32]byte
if _, err := rand.Read(pre[:]); err != nil {
return nil, err
}
if _, err := rand.Read(payAddr[:]); err != nil {
return nil, err
}
i := &Invoice{
CreationDate: testNow,
Terms: ContractTerm{
Expiry: 4000,
PaymentPreimage: pre,
PaymentAddr: payAddr,
Value: value,
Features: emptyFeatures,
},
@@ -91,9 +95,45 @@ func TestInvoiceIsPending(t *testing.T) {
}
}
type invWorkflowTest struct {
name string
queryPayHash bool
queryPayAddr bool
}
var invWorkflowTests = []invWorkflowTest{
{
name: "unknown",
queryPayHash: false,
queryPayAddr: false,
},
{
name: "only payhash known",
queryPayHash: true,
queryPayAddr: false,
},
{
name: "payaddr and payhash known",
queryPayHash: true,
queryPayAddr: true,
},
}
// TestInvoiceWorkflow asserts the basic process of inserting, fetching, and
// updating an invoice. We assert that the flow is successful using when
// querying with various combinations of payment hash and payment address.
func TestInvoiceWorkflow(t *testing.T) {
t.Parallel()
for _, test := range invWorkflowTests {
test := test
t.Run(test.name, func(t *testing.T) {
testInvoiceWorkflow(t, test)
})
}
}
func testInvoiceWorkflow(t *testing.T, test invWorkflowTest) {
db, cleanUp, err := makeTestDB()
defer cleanUp()
if err != nil {
@@ -102,31 +142,45 @@ func TestInvoiceWorkflow(t *testing.T) {
// Create a fake invoice which we'll use several times in the tests
// below.
fakeInvoice := &Invoice{
CreationDate: testNow,
Htlcs: map[CircuitKey]*InvoiceHTLC{},
fakeInvoice, err := randInvoice(10000)
if err != nil {
t.Fatalf("unable to create invoice: %v", err)
}
fakeInvoice.Memo = []byte("memo")
fakeInvoice.PaymentRequest = []byte("")
copy(fakeInvoice.Terms.PaymentPreimage[:], rev[:])
fakeInvoice.Terms.Value = lnwire.NewMSatFromSatoshis(10000)
fakeInvoice.Terms.Features = emptyFeatures
invPayHash := fakeInvoice.Terms.PaymentPreimage.Hash()
paymentHash := fakeInvoice.Terms.PaymentPreimage.Hash()
// Select the payment hash and payment address we will use to lookup or
// update the invoice for the remainder of the test.
var (
payHash lntypes.Hash
payAddr *[32]byte
ref InvoiceRef
)
switch {
case test.queryPayHash && test.queryPayAddr:
payHash = invPayHash
payAddr = &fakeInvoice.Terms.PaymentAddr
ref = InvoiceRefByHashAndAddr(payHash, *payAddr)
case test.queryPayHash:
payHash = invPayHash
ref = InvoiceRefByHash(payHash)
}
// Add the invoice to the database, this should succeed as there aren't
// any existing invoices within the database with the same payment
// hash.
if _, err := db.AddInvoice(fakeInvoice, paymentHash); err != nil {
if _, err := db.AddInvoice(fakeInvoice, invPayHash); err != nil {
t.Fatalf("unable to find invoice: %v", err)
}
// Attempt to retrieve the invoice which was just added to the
// database. It should be found, and the invoice returned should be
// identical to the one created above.
dbInvoice, err := db.LookupInvoice(paymentHash)
if err != nil {
t.Fatalf("unable to find invoice: %v", err)
dbInvoice, err := db.LookupInvoice(ref)
if !test.queryPayAddr && !test.queryPayHash {
if err != ErrInvoiceNotFound {
t.Fatalf("invoice should not exist: %v", err)
}
return
}
if !reflect.DeepEqual(*fakeInvoice, dbInvoice) {
t.Fatalf("invoice fetched from db doesn't match original %v vs %v",
@@ -145,11 +199,11 @@ func TestInvoiceWorkflow(t *testing.T) {
// now have the settled bit toggle to true and a non-default
// SettledDate
payAmt := fakeInvoice.Terms.Value * 2
_, err = db.UpdateInvoice(paymentHash, getUpdateInvoice(payAmt))
_, err = db.UpdateInvoice(ref, getUpdateInvoice(payAmt))
if err != nil {
t.Fatalf("unable to settle invoice: %v", err)
}
dbInvoice2, err := db.LookupInvoice(paymentHash)
dbInvoice2, err := db.LookupInvoice(ref)
if err != nil {
t.Fatalf("unable to fetch invoice: %v", err)
}
@@ -173,7 +227,7 @@ func TestInvoiceWorkflow(t *testing.T) {
// Attempt to insert generated above again, this should fail as
// duplicates are rejected by the processing logic.
if _, err := db.AddInvoice(fakeInvoice, paymentHash); err != ErrDuplicateInvoice {
if _, err := db.AddInvoice(fakeInvoice, payHash); err != ErrDuplicateInvoice {
t.Fatalf("invoice insertion should fail due to duplication, "+
"instead %v", err)
}
@@ -181,7 +235,9 @@ func TestInvoiceWorkflow(t *testing.T) {
// Attempt to look up a non-existent invoice, this should also fail but
// with a "not found" error.
var fakeHash [32]byte
if _, err := db.LookupInvoice(fakeHash); err != ErrInvoiceNotFound {
fakeRef := InvoiceRefByHash(fakeHash)
_, err = db.LookupInvoice(fakeRef)
if err != ErrInvoiceNotFound {
t.Fatalf("lookup should have failed, instead %v", err)
}
@@ -229,6 +285,70 @@ func TestInvoiceWorkflow(t *testing.T) {
}
}
// TestAddDuplicatePayAddr asserts that the payment addresses of inserted
// invoices are unique.
func TestAddDuplicatePayAddr(t *testing.T) {
db, cleanUp, err := makeTestDB()
defer cleanUp()
assert.Nil(t, err)
// Create two invoices with the same payment addr.
invoice1, err := randInvoice(1000)
assert.Nil(t, err)
invoice2, err := randInvoice(20000)
assert.Nil(t, err)
invoice2.Terms.PaymentAddr = invoice1.Terms.PaymentAddr
// First insert should succeed.
inv1Hash := invoice1.Terms.PaymentPreimage.Hash()
_, err = db.AddInvoice(invoice1, inv1Hash)
assert.Nil(t, err)
// Second insert should fail with duplicate payment addr.
inv2Hash := invoice2.Terms.PaymentPreimage.Hash()
_, err = db.AddInvoice(invoice2, inv2Hash)
assert.Equal(t, ErrDuplicatePayAddr, err)
}
// TestInvRefEquivocation asserts that retrieving or updating an invoice using
// an equivocating InvoiceRef results in ErrInvRefEquivocation.
func TestInvRefEquivocation(t *testing.T) {
db, cleanUp, err := makeTestDB()
defer cleanUp()
assert.Nil(t, err)
// Add two random invoices.
invoice1, err := randInvoice(1000)
assert.Nil(t, err)
inv1Hash := invoice1.Terms.PaymentPreimage.Hash()
_, err = db.AddInvoice(invoice1, inv1Hash)
assert.Nil(t, err)
invoice2, err := randInvoice(2000)
assert.Nil(t, err)
inv2Hash := invoice2.Terms.PaymentPreimage.Hash()
_, err = db.AddInvoice(invoice2, inv2Hash)
assert.Nil(t, err)
// Now, query using invoice 1's payment address, but invoice 2's payment
// hash. We expect an error since the invref points to multiple
// invoices.
ref := InvoiceRefByHashAndAddr(inv2Hash, invoice1.Terms.PaymentAddr)
_, err = db.LookupInvoice(ref)
assert.Equal(t, ErrInvRefEquivocation, err)
// The same error should be returned when updating an equivocating
// reference.
nop := func(_ *Invoice) (*InvoiceUpdateDesc, error) {
return nil, nil
}
_, err = db.UpdateInvoice(ref, nop)
assert.Equal(t, ErrInvRefEquivocation, err)
}
// TestInvoiceCancelSingleHtlc tests that a single htlc can be canceled on the
// invoice.
func TestInvoiceCancelSingleHtlc(t *testing.T) {
@@ -257,7 +377,9 @@ func TestInvoiceCancelSingleHtlc(t *testing.T) {
Amt: 500,
CustomRecords: make(record.CustomSet),
}
invoice, err := db.UpdateInvoice(paymentHash,
ref := InvoiceRefByHash(paymentHash)
invoice, err := db.UpdateInvoice(ref,
func(invoice *Invoice) (*InvoiceUpdateDesc, error) {
return &InvoiceUpdateDesc{
AddHtlcs: map[CircuitKey]*HtlcAcceptDesc{
@@ -276,13 +398,14 @@ func TestInvoiceCancelSingleHtlc(t *testing.T) {
}
// Cancel the htlc again.
invoice, err = db.UpdateInvoice(paymentHash, func(invoice *Invoice) (*InvoiceUpdateDesc, error) {
return &InvoiceUpdateDesc{
CancelHtlcs: map[CircuitKey]struct{}{
key: {},
},
}, nil
})
invoice, err = db.UpdateInvoice(ref,
func(invoice *Invoice) (*InvoiceUpdateDesc, error) {
return &InvoiceUpdateDesc{
CancelHtlcs: map[CircuitKey]struct{}{
key: {},
},
}, nil
})
if err != nil {
t.Fatalf("unable to cancel htlc: %v", err)
}
@@ -387,8 +510,9 @@ func TestInvoiceAddTimeSeries(t *testing.T) {
paymentHash := invoice.Terms.PaymentPreimage.Hash()
ref := InvoiceRefByHash(paymentHash)
_, err := db.UpdateInvoice(
paymentHash, getUpdateInvoice(invoice.Terms.Value),
ref, getUpdateInvoice(invoice.Terms.Value),
)
if err != nil {
t.Fatalf("unable to settle invoice: %v", err)
@@ -577,9 +701,8 @@ func TestDuplicateSettleInvoice(t *testing.T) {
}
// With the invoice in the DB, we'll now attempt to settle the invoice.
dbInvoice, err := db.UpdateInvoice(
payHash, getUpdateInvoice(amt),
)
ref := InvoiceRefByHash(payHash)
dbInvoice, err := db.UpdateInvoice(ref, getUpdateInvoice(amt))
if err != nil {
t.Fatalf("unable to settle invoice: %v", err)
}
@@ -608,9 +731,7 @@ func TestDuplicateSettleInvoice(t *testing.T) {
// If we try to settle the invoice again, then we should get the very
// same invoice back, but with an error this time.
dbInvoice, err = db.UpdateInvoice(
payHash, getUpdateInvoice(amt),
)
dbInvoice, err = db.UpdateInvoice(ref, getUpdateInvoice(amt))
if err != ErrInvoiceAlreadySettled {
t.Fatalf("expected ErrInvoiceAlreadySettled")
}
@@ -660,9 +781,8 @@ func TestQueryInvoices(t *testing.T) {
// We'll only settle half of all invoices created.
if i%2 == 0 {
_, err := db.UpdateInvoice(
paymentHash, getUpdateInvoice(amt),
)
ref := InvoiceRefByHash(paymentHash)
_, err := db.UpdateInvoice(ref, getUpdateInvoice(amt))
if err != nil {
t.Fatalf("unable to settle invoice: %v", err)
}
@@ -958,7 +1078,8 @@ func TestCustomRecords(t *testing.T) {
100001: []byte{1, 2},
}
_, err = db.UpdateInvoice(paymentHash,
ref := InvoiceRefByHash(paymentHash)
_, err = db.UpdateInvoice(ref,
func(invoice *Invoice) (*InvoiceUpdateDesc, error) {
return &InvoiceUpdateDesc{
AddHtlcs: map[CircuitKey]*HtlcAcceptDesc{
@@ -976,7 +1097,7 @@ func TestCustomRecords(t *testing.T) {
// Retrieve the invoice from that database and verify that the custom
// records are present.
dbInvoice, err := db.LookupInvoice(paymentHash)
dbInvoice, err := db.LookupInvoice(ref)
if err != nil {
t.Fatalf("unable to lookup invoice: %v", err)
}
@@ -988,3 +1109,22 @@ func TestCustomRecords(t *testing.T) {
t.Fatalf("invalid custom records")
}
}
// TestInvoiceRef asserts that the proper identifiers are returned from an
// InvoiceRef depending on the constructor used.
func TestInvoiceRef(t *testing.T) {
payHash := lntypes.Hash{0x01}
payAddr := [32]byte{0x02}
// An InvoiceRef by hash should return the provided hash and a nil
// payment addr.
refByHash := InvoiceRefByHash(payHash)
assert.Equal(t, payHash, refByHash.PayHash())
assert.Equal(t, (*[32]byte)(nil), refByHash.PayAddr())
// An InvoiceRef by hash and addr should return the payment hash and
// payment addr passed to the constructor.
refByHashAndAddr := InvoiceRefByHashAndAddr(payHash, payAddr)
assert.Equal(t, payHash, refByHashAndAddr.PayHash())
assert.Equal(t, &payAddr, refByHashAndAddr.PayAddr())
}
+149 -19
View File
@@ -37,6 +37,16 @@ var (
// maps: payHash => invoiceKey
invoiceIndexBucket = []byte("paymenthashes")
// payAddrIndexBucket is the name of the top-level bucket that maps
// payment addresses to their invoice number. This can be used
// to efficiently query or update non-legacy invoices. Note that legacy
// invoices will not be included in this index since they all have the
// same, all-zero payment address, however all newly generated invoices
// will end up in this index.
//
// maps: payAddr => invoiceKey
payAddrIndexBucket = []byte("pay-addr-index")
// numInvoicesKey is the name of key which houses the auto-incrementing
// invoice ID which is essentially used as a primary key. With each
// invoice inserted, the primary key is incremented by one. This key is
@@ -142,6 +152,69 @@ const (
amtPaidType tlv.Type = 13
)
// InvoiceRef is a composite identifier for invoices. Invoices can be referenced
// by various combinations of payment hash and payment addr, in certain contexts
// only some of these are known. An InvoiceRef and its constructors thus
// encapsulate the valid combinations of query parameters that can be supplied
// to LookupInvoice and UpdateInvoice.
type InvoiceRef struct {
// payHash is the payment hash of the target invoice. All invoices are
// currently indexed by payment hash. This value will be used as a
// fallback when no payment address is known.
payHash lntypes.Hash
// payAddr is the payment addr of the target invoice. Newer invoices
// (0.11 and up) are indexed by payment address in addition to payment
// hash, but pre 0.8 invoices do not have one at all. When this value is
// known it will be used as the primary identifier, falling back to
// payHash if no value is known.
payAddr *[32]byte
}
// InvoiceRefByHash creates an InvoiceRef that queries for an invoice only by
// its payment hash.
func InvoiceRefByHash(payHash lntypes.Hash) InvoiceRef {
return InvoiceRef{
payHash: payHash,
}
}
// InvoiceRefByHashAndAddr creates an InvoiceRef that first queries for an
// invoice by the provided payment address, falling back to the payment hash if
// the payment address is unknown.
func InvoiceRefByHashAndAddr(payHash lntypes.Hash,
payAddr [32]byte) InvoiceRef {
return InvoiceRef{
payHash: payHash,
payAddr: &payAddr,
}
}
// PayHash returns the target invoice's payment hash.
func (r InvoiceRef) PayHash() lntypes.Hash {
return r.payHash
}
// PayAddr returns the optional payment address of the target invoice.
//
// NOTE: This value may be nil.
func (r InvoiceRef) PayAddr() *[32]byte {
if r.payAddr != nil {
addr := *r.payAddr
return &addr
}
return nil
}
// String returns a human-readable representation of an InvoiceRef.
func (r InvoiceRef) String() string {
if r.payAddr != nil {
return fmt.Sprintf("(pay_hash=%v, pay_addr=%x)", r.payHash, *r.payAddr)
}
return fmt.Sprintf("(pay_hash=%v)", r.payHash)
}
// ContractState describes the state the invoice is in.
type ContractState uint8
@@ -432,6 +505,11 @@ func (d *DB) AddInvoice(newInvoice *Invoice, paymentHash lntypes.Hash) (
return ErrDuplicateInvoice
}
payAddrIndex := tx.ReadWriteBucket(payAddrIndexBucket)
if payAddrIndex.Get(newInvoice.Terms.PaymentAddr[:]) != nil {
return ErrDuplicatePayAddr
}
// If the current running payment ID counter hasn't yet been
// created, then create it now.
var invoiceNum uint32
@@ -448,8 +526,8 @@ func (d *DB) AddInvoice(newInvoice *Invoice, paymentHash lntypes.Hash) (
}
newIndex, err := putInvoice(
invoices, invoiceIndex, addIndex, newInvoice, invoiceNum,
paymentHash,
invoices, invoiceIndex, payAddrIndex, addIndex,
newInvoice, invoiceNum, paymentHash,
)
if err != nil {
return err
@@ -533,7 +611,7 @@ func (d *DB) InvoicesAddedSince(sinceAddIndex uint64) ([]Invoice, error) {
// full invoice is returned. Before setting the incoming HTLC, the values
// SHOULD be checked to ensure the payer meets the agreed upon contractual
// terms of the payment.
func (d *DB) LookupInvoice(paymentHash [32]byte) (Invoice, error) {
func (d *DB) LookupInvoice(ref InvoiceRef) (Invoice, error) {
var invoice Invoice
err := kvdb.View(d, func(tx kvdb.RTx) error {
invoices := tx.ReadBucket(invoiceBucket)
@@ -544,16 +622,19 @@ func (d *DB) LookupInvoice(paymentHash [32]byte) (Invoice, error) {
if invoiceIndex == nil {
return ErrNoInvoicesCreated
}
payAddrIndex := tx.ReadBucket(payAddrIndexBucket)
// Check the invoice index to see if an invoice paying to this
// hash exists within the DB.
invoiceNum := invoiceIndex.Get(paymentHash[:])
if invoiceNum == nil {
return ErrInvoiceNotFound
// Retrieve the invoice number for this invoice using the
// provided invoice reference.
invoiceNum, err := fetchInvoiceNumByRef(
invoiceIndex, payAddrIndex, ref,
)
if err != nil {
return err
}
// An invoice matching the payment hash has been found, so
// retrieve the record of the invoice itself.
// An invoice was found, retrieve the remainder of the invoice
// body.
i, err := fetchInvoice(invoiceNum, invoices)
if err != nil {
return err
@@ -569,6 +650,47 @@ func (d *DB) LookupInvoice(paymentHash [32]byte) (Invoice, error) {
return invoice, nil
}
// fetchInvoiceNumByRef retrieve the invoice number for the provided invoice
// reference. The payment address will be treated as the primary key, falling
// back to the payment hash if nothing is found for the payment address. An
// error is returned if the invoice is not found.
func fetchInvoiceNumByRef(invoiceIndex, payAddrIndex kvdb.ReadBucket,
ref InvoiceRef) ([]byte, error) {
payHash := ref.PayHash()
payAddr := ref.PayAddr()
var (
invoiceNumByHash = invoiceIndex.Get(payHash[:])
invoiceNumByAddr []byte
)
if payAddr != nil {
invoiceNumByAddr = payAddrIndex.Get(payAddr[:])
}
switch {
// If payment address and payment hash both reference an existing
// invoice, ensure they reference the _same_ invoice.
case invoiceNumByAddr != nil && invoiceNumByHash != nil:
if !bytes.Equal(invoiceNumByAddr, invoiceNumByHash) {
return nil, ErrInvRefEquivocation
}
return invoiceNumByAddr, nil
// If we were only able to reference the invoice by hash, return the
// corresponding invoice number. This can happen when no payment address
// was provided, or if it didn't match anything in our records.
case invoiceNumByHash != nil:
return invoiceNumByHash, nil
// Otherwise we don't know of the target invoice.
default:
return nil, ErrInvoiceNotFound
}
}
// InvoiceWithPaymentHash is used to store an invoice and its corresponding
// payment hash. This struct is only used to store results of
// ChannelDB.FetchAllInvoicesWithPaymentHash() call.
@@ -819,7 +941,7 @@ func (d *DB) QueryInvoices(q InvoiceQuery) (InvoiceSlice, error) {
// The update is performed inside the same database transaction that fetches the
// invoice and is therefore atomic. The fields to update are controlled by the
// supplied callback.
func (d *DB) UpdateInvoice(paymentHash lntypes.Hash,
func (d *DB) UpdateInvoice(ref InvoiceRef,
callback InvoiceUpdateCallback) (*Invoice, error) {
var updatedInvoice *Invoice
@@ -840,16 +962,20 @@ func (d *DB) UpdateInvoice(paymentHash lntypes.Hash,
if err != nil {
return err
}
payAddrIndex := tx.ReadBucket(payAddrIndexBucket)
// Retrieve the invoice number for this invoice using the
// provided invoice reference.
invoiceNum, err := fetchInvoiceNumByRef(
invoiceIndex, payAddrIndex, ref,
)
if err != nil {
return err
// Check the invoice index to see if an invoice paying to this
// hash exists within the DB.
invoiceNum := invoiceIndex.Get(paymentHash[:])
if invoiceNum == nil {
return ErrInvoiceNotFound
}
payHash := ref.PayHash()
updatedInvoice, err = d.updateInvoice(
paymentHash, invoices, settleIndex, invoiceNum,
payHash, invoices, settleIndex, invoiceNum,
callback,
)
@@ -920,7 +1046,7 @@ func (d *DB) InvoicesSettledSince(sinceSettleIndex uint64) ([]Invoice, error) {
return settledInvoices, nil
}
func putInvoice(invoices, invoiceIndex, addIndex kvdb.RwBucket,
func putInvoice(invoices, invoiceIndex, payAddrIndex, addIndex kvdb.RwBucket,
i *Invoice, invoiceNum uint32, paymentHash lntypes.Hash) (
uint64, error) {
@@ -945,6 +1071,10 @@ func putInvoice(invoices, invoiceIndex, addIndex kvdb.RwBucket,
if err != nil {
return 0, err
}
err = payAddrIndex.Put(i.Terms.PaymentAddr[:], invoiceKey[:])
if err != nil {
return 0, err
}
// Next, we'll obtain the next add invoice index (sequence
// number), so we can properly place this invoice within this
+2
View File
@@ -3,6 +3,7 @@ package channeldb
import (
"github.com/btcsuite/btclog"
"github.com/lightningnetwork/lnd/build"
mig "github.com/lightningnetwork/lnd/channeldb/migration"
"github.com/lightningnetwork/lnd/channeldb/migration12"
"github.com/lightningnetwork/lnd/channeldb/migration13"
"github.com/lightningnetwork/lnd/channeldb/migration_01_to_11"
@@ -28,6 +29,7 @@ func DisableLog() {
// using btclog.
func UseLogger(logger btclog.Logger) {
log = logger
mig.UseLogger(logger)
migration_01_to_11.UseLogger(logger)
migration12.UseLogger(logger)
migration13.UseLogger(logger)
+27
View File
@@ -0,0 +1,27 @@
package migration
import (
"fmt"
"github.com/lightningnetwork/lnd/channeldb/kvdb"
)
// CreateTLB creates a new top-level bucket with the passed bucket identifier.
func CreateTLB(bucket []byte) func(kvdb.RwTx) error {
return func(tx kvdb.RwTx) error {
log.Infof("Creating top-level bucket: \"%s\" ...", bucket)
if tx.ReadBucket(bucket) != nil {
return fmt.Errorf("top-level bucket \"%s\" "+
"already exists", bucket)
}
_, err := tx.CreateTopLevelBucket(bucket)
if err != nil {
return err
}
log.Infof("Created top-level bucket: \"%s\"", bucket)
return nil
}
}
+57
View File
@@ -0,0 +1,57 @@
package migration_test
import (
"fmt"
"testing"
"github.com/lightningnetwork/lnd/channeldb/kvdb"
"github.com/lightningnetwork/lnd/channeldb/migration"
"github.com/lightningnetwork/lnd/channeldb/migtest"
)
// TestCreateTLB asserts that a CreateTLB properly initializes a new top-level
// bucket, and that it succeeds even if the bucket already exists. It would
// probably be better if the latter failed, but the kvdb abstraction doesn't
// support this.
func TestCreateTLB(t *testing.T) {
newBucket := []byte("hello")
tests := []struct {
name string
beforeMigration func(kvdb.RwTx) error
shouldFail bool
}{
{
name: "already exists",
beforeMigration: func(tx kvdb.RwTx) error {
_, err := tx.CreateTopLevelBucket(newBucket)
return err
},
shouldFail: true,
},
{
name: "does not exist",
beforeMigration: func(_ kvdb.RwTx) error { return nil },
shouldFail: false,
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
migtest.ApplyMigration(
t,
test.beforeMigration,
func(tx kvdb.RwTx) error {
if tx.ReadBucket(newBucket) != nil {
return nil
}
return fmt.Errorf("bucket \"%s\" not "+
"created", newBucket)
},
migration.CreateTLB(newBucket),
test.shouldFail,
)
})
}
}
+12
View File
@@ -0,0 +1,12 @@
package migration
import "github.com/btcsuite/btclog"
// log is a logger that is initialized as disabled. This means the package will
// not perform any logging by default until a logger is set.
var log = btclog.Disabled
// UseLogger uses a specified Logger to output package logging info.
func UseLogger(logger btclog.Logger) {
log = logger
}
+1 -1
View File
@@ -74,7 +74,7 @@ func ApplyMigration(t *testing.T,
// Apply migration.
err = kvdb.Update(cdb, migrationFunc)
if err != nil {
t.Fatal(err)
t.Logf("migration error: %v", err)
}
}