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:
commit
0f3ab775c7
@ -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)
|
||||
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 {
|
||||
for _, tlb := range topLevelBuckets {
|
||||
if _, err := tx.CreateTopLevelBucket(tlb); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := tx.CreateTopLevelBucket(closedChannelBucket); 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)
|
||||
})
|
||||
|
@ -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")
|
||||
|
@ -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,7 +398,8 @@ func TestInvoiceCancelSingleHtlc(t *testing.T) {
|
||||
}
|
||||
|
||||
// Cancel the htlc again.
|
||||
invoice, err = db.UpdateInvoice(paymentHash, func(invoice *Invoice) (*InvoiceUpdateDesc, error) {
|
||||
invoice, err = db.UpdateInvoice(ref,
|
||||
func(invoice *Invoice) (*InvoiceUpdateDesc, error) {
|
||||
return &InvoiceUpdateDesc{
|
||||
CancelHtlcs: map[CircuitKey]struct{}{
|
||||
key: {},
|
||||
@ -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())
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
channeldb/migration/create_tlb.go
Normal file
27
channeldb/migration/create_tlb.go
Normal 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
channeldb/migration/create_tlb_test.go
Normal file
57
channeldb/migration/create_tlb_test.go
Normal 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
channeldb/migration/log.go
Normal file
12
channeldb/migration/log.go
Normal 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
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,7 @@ package htlcswitch
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
crand "crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
@ -137,7 +137,7 @@ func generateRandomBytes(n int) ([]byte, error) {
|
||||
// TODO(roasbeef): should use counter in tests (atomic) rather than
|
||||
// this
|
||||
|
||||
_, err := rand.Read(b[:])
|
||||
_, err := crand.Read(b)
|
||||
// Note that Err == nil only if we read len(b) bytes.
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -547,7 +547,7 @@ func getChanID(msg lnwire.Message) (lnwire.ChannelID, error) {
|
||||
// invoice which should be added by destination peer.
|
||||
func generatePaymentWithPreimage(invoiceAmt, htlcAmt lnwire.MilliSatoshi,
|
||||
timelock uint32, blob [lnwire.OnionPacketSize]byte,
|
||||
preimage, rhash [32]byte) (*channeldb.Invoice, *lnwire.UpdateAddHTLC,
|
||||
preimage, rhash, payAddr [32]byte) (*channeldb.Invoice, *lnwire.UpdateAddHTLC,
|
||||
uint64, error) {
|
||||
|
||||
// Create the db invoice. Normally the payment requests needs to be set,
|
||||
@ -562,6 +562,7 @@ func generatePaymentWithPreimage(invoiceAmt, htlcAmt lnwire.MilliSatoshi,
|
||||
FinalCltvDelta: testInvoiceCltvExpiry,
|
||||
Value: invoiceAmt,
|
||||
PaymentPreimage: preimage,
|
||||
PaymentAddr: payAddr,
|
||||
Features: lnwire.NewFeatureVector(
|
||||
nil, lnwire.Features,
|
||||
),
|
||||
@ -598,8 +599,16 @@ func generatePayment(invoiceAmt, htlcAmt lnwire.MilliSatoshi, timelock uint32,
|
||||
copy(preimage[:], r)
|
||||
|
||||
rhash := sha256.Sum256(preimage[:])
|
||||
|
||||
var payAddr [sha256.Size]byte
|
||||
r, err = generateRandomBytes(sha256.Size)
|
||||
if err != nil {
|
||||
return nil, nil, 0, err
|
||||
}
|
||||
copy(payAddr[:], r)
|
||||
|
||||
return generatePaymentWithPreimage(
|
||||
invoiceAmt, htlcAmt, timelock, blob, preimage, rhash,
|
||||
invoiceAmt, htlcAmt, timelock, blob, preimage, rhash, payAddr,
|
||||
)
|
||||
}
|
||||
|
||||
@ -1328,10 +1337,15 @@ func (n *twoHopNetwork) makeHoldPayment(sendingPeer, receivingPeer lnpeer.Peer,
|
||||
|
||||
rhash := preimage.Hash()
|
||||
|
||||
var payAddr [32]byte
|
||||
if _, err := crand.Read(payAddr[:]); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Generate payment: invoice and htlc.
|
||||
invoice, htlc, pid, err := generatePaymentWithPreimage(
|
||||
invoiceAmt, htlcAmt, timelock, blob,
|
||||
channeldb.UnknownPreimage, rhash,
|
||||
channeldb.UnknownPreimage, rhash, payAddr,
|
||||
)
|
||||
if err != nil {
|
||||
paymentErr <- err
|
||||
|
@ -61,8 +61,8 @@ type RegistryConfig struct {
|
||||
// htlcReleaseEvent describes an htlc auto-release event. It is used to release
|
||||
// mpp htlcs for which the complete set didn't arrive in time.
|
||||
type htlcReleaseEvent struct {
|
||||
// hash is the payment hash of the htlc to release.
|
||||
hash lntypes.Hash
|
||||
// invoiceRef identifiers the invoice this htlc belongs to.
|
||||
invoiceRef channeldb.InvoiceRef
|
||||
|
||||
// key is the circuit key of the htlc to release.
|
||||
key channeldb.CircuitKey
|
||||
@ -280,7 +280,8 @@ func (i *InvoiceRegistry) invoiceEventLoop() {
|
||||
// the subscriber.
|
||||
case *SingleInvoiceSubscription:
|
||||
log.Infof("New single invoice subscription "+
|
||||
"client: id=%v, hash=%v", e.id, e.hash)
|
||||
"client: id=%v, ref=%v", e.id,
|
||||
e.invoiceRef)
|
||||
|
||||
i.singleNotificationClients[e.id] = e
|
||||
}
|
||||
@ -288,8 +289,8 @@ func (i *InvoiceRegistry) invoiceEventLoop() {
|
||||
// A new htlc came in for auto-release.
|
||||
case event := <-i.htlcAutoReleaseChan:
|
||||
log.Debugf("Scheduling auto-release for htlc: "+
|
||||
"hash=%v, key=%v at %v",
|
||||
event.hash, event.key, event.releaseTime)
|
||||
"ref=%v, key=%v at %v",
|
||||
event.invoiceRef, event.key, event.releaseTime)
|
||||
|
||||
// We use an independent timer for every htlc rather
|
||||
// than a set timer that is reset with every htlc coming
|
||||
@ -302,7 +303,7 @@ func (i *InvoiceRegistry) invoiceEventLoop() {
|
||||
case <-nextReleaseTick:
|
||||
event := autoReleaseHeap.Pop().(*htlcReleaseEvent)
|
||||
err := i.cancelSingleHtlc(
|
||||
event.hash, event.key, ResultMppTimeout,
|
||||
event.invoiceRef, event.key, ResultMppTimeout,
|
||||
)
|
||||
if err != nil {
|
||||
log.Errorf("HTLC timer: %v", err)
|
||||
@ -319,7 +320,7 @@ func (i *InvoiceRegistry) invoiceEventLoop() {
|
||||
func (i *InvoiceRegistry) dispatchToSingleClients(event *invoiceEvent) {
|
||||
// Dispatch to single invoice subscribers.
|
||||
for _, client := range i.singleNotificationClients {
|
||||
if client.hash != event.hash {
|
||||
if client.invoiceRef.PayHash() != event.hash {
|
||||
continue
|
||||
}
|
||||
|
||||
@ -453,7 +454,7 @@ func (i *InvoiceRegistry) deliverBacklogEvents(client *InvoiceSubscription) erro
|
||||
func (i *InvoiceRegistry) deliverSingleBacklogEvents(
|
||||
client *SingleInvoiceSubscription) error {
|
||||
|
||||
invoice, err := i.cdb.LookupInvoice(client.hash)
|
||||
invoice, err := i.cdb.LookupInvoice(client.invoiceRef)
|
||||
|
||||
// It is possible that the invoice does not exist yet, but the client is
|
||||
// already watching it in anticipation.
|
||||
@ -467,7 +468,7 @@ func (i *InvoiceRegistry) deliverSingleBacklogEvents(
|
||||
}
|
||||
|
||||
err = client.notify(&invoiceEvent{
|
||||
hash: client.hash,
|
||||
hash: client.invoiceRef.PayHash(),
|
||||
invoice: &invoice,
|
||||
})
|
||||
if err != nil {
|
||||
@ -490,8 +491,8 @@ func (i *InvoiceRegistry) AddInvoice(invoice *channeldb.Invoice,
|
||||
|
||||
i.Lock()
|
||||
|
||||
log.Debugf("Invoice(%v): added with terms %v", paymentHash,
|
||||
invoice.Terms)
|
||||
ref := channeldb.InvoiceRefByHash(paymentHash)
|
||||
log.Debugf("Invoice%v: added with terms %v", ref, invoice.Terms)
|
||||
|
||||
addIndex, err := i.cdb.AddInvoice(invoice, paymentHash)
|
||||
if err != nil {
|
||||
@ -521,17 +522,18 @@ func (i *InvoiceRegistry) LookupInvoice(rHash lntypes.Hash) (channeldb.Invoice,
|
||||
|
||||
// We'll check the database to see if there's an existing matching
|
||||
// invoice.
|
||||
return i.cdb.LookupInvoice(rHash)
|
||||
ref := channeldb.InvoiceRefByHash(rHash)
|
||||
return i.cdb.LookupInvoice(ref)
|
||||
}
|
||||
|
||||
// startHtlcTimer starts a new timer via the invoice registry main loop that
|
||||
// cancels a single htlc on an invoice when the htlc hold duration has passed.
|
||||
func (i *InvoiceRegistry) startHtlcTimer(hash lntypes.Hash,
|
||||
func (i *InvoiceRegistry) startHtlcTimer(invoiceRef channeldb.InvoiceRef,
|
||||
key channeldb.CircuitKey, acceptTime time.Time) error {
|
||||
|
||||
releaseTime := acceptTime.Add(i.cfg.HtlcHoldDuration)
|
||||
event := &htlcReleaseEvent{
|
||||
hash: hash,
|
||||
invoiceRef: invoiceRef,
|
||||
key: key,
|
||||
releaseTime: releaseTime,
|
||||
}
|
||||
@ -548,7 +550,7 @@ func (i *InvoiceRegistry) startHtlcTimer(hash lntypes.Hash,
|
||||
// cancelSingleHtlc cancels a single accepted htlc on an invoice. It takes
|
||||
// a resolution result which will be used to notify subscribed links and
|
||||
// resolvers of the details of the htlc cancellation.
|
||||
func (i *InvoiceRegistry) cancelSingleHtlc(hash lntypes.Hash,
|
||||
func (i *InvoiceRegistry) cancelSingleHtlc(invoiceRef channeldb.InvoiceRef,
|
||||
key channeldb.CircuitKey, result FailResolutionResult) error {
|
||||
|
||||
i.Lock()
|
||||
@ -560,7 +562,7 @@ func (i *InvoiceRegistry) cancelSingleHtlc(hash lntypes.Hash,
|
||||
// Only allow individual htlc cancelation on open invoices.
|
||||
if invoice.State != channeldb.ContractOpen {
|
||||
log.Debugf("cancelSingleHtlc: invoice %v no longer "+
|
||||
"open", hash)
|
||||
"open", invoiceRef)
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
@ -575,13 +577,13 @@ func (i *InvoiceRegistry) cancelSingleHtlc(hash lntypes.Hash,
|
||||
// resolved.
|
||||
if htlc.State != channeldb.HtlcStateAccepted {
|
||||
log.Debugf("cancelSingleHtlc: htlc %v on invoice %v "+
|
||||
"is already resolved", key, hash)
|
||||
"is already resolved", key, invoiceRef)
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
log.Debugf("cancelSingleHtlc: cancelling htlc %v on invoice %v",
|
||||
key, hash)
|
||||
key, invoiceRef)
|
||||
|
||||
// Return an update descriptor that cancels htlc and keeps
|
||||
// invoice open.
|
||||
@ -598,7 +600,7 @@ func (i *InvoiceRegistry) cancelSingleHtlc(hash lntypes.Hash,
|
||||
// Intercept the update descriptor to set the local updated variable. If
|
||||
// no invoice update is performed, we can return early.
|
||||
var updated bool
|
||||
invoice, err := i.cdb.UpdateInvoice(hash,
|
||||
invoice, err := i.cdb.UpdateInvoice(invoiceRef,
|
||||
func(invoice *channeldb.Invoice) (
|
||||
*channeldb.InvoiceUpdateDesc, error) {
|
||||
|
||||
@ -721,11 +723,9 @@ func (i *InvoiceRegistry) NotifyExitHopHtlc(rHash lntypes.Hash,
|
||||
circuitKey channeldb.CircuitKey, hodlChan chan<- interface{},
|
||||
payload Payload) (HtlcResolution, error) {
|
||||
|
||||
mpp := payload.MultiPath()
|
||||
|
||||
// Create the update context containing the relevant details of the
|
||||
// incoming htlc.
|
||||
updateCtx := invoiceUpdateCtx{
|
||||
ctx := invoiceUpdateCtx{
|
||||
hash: rHash,
|
||||
circuitKey: circuitKey,
|
||||
amtPaid: amtPaid,
|
||||
@ -733,16 +733,16 @@ func (i *InvoiceRegistry) NotifyExitHopHtlc(rHash lntypes.Hash,
|
||||
currentHeight: currentHeight,
|
||||
finalCltvRejectDelta: i.cfg.FinalCltvRejectDelta,
|
||||
customRecords: payload.CustomRecords(),
|
||||
mpp: mpp,
|
||||
mpp: payload.MultiPath(),
|
||||
}
|
||||
|
||||
// Process keysend if present. Do this outside of the lock, because
|
||||
// AddInvoice obtains its own lock. This is no problem, because the
|
||||
// operation is idempotent.
|
||||
if i.cfg.AcceptKeySend {
|
||||
err := i.processKeySend(updateCtx)
|
||||
err := i.processKeySend(ctx)
|
||||
if err != nil {
|
||||
updateCtx.log(fmt.Sprintf("keysend error: %v", err))
|
||||
ctx.log(fmt.Sprintf("keysend error: %v", err))
|
||||
|
||||
return NewFailResolution(
|
||||
circuitKey, currentHeight, ResultKeySendError,
|
||||
@ -752,7 +752,7 @@ func (i *InvoiceRegistry) NotifyExitHopHtlc(rHash lntypes.Hash,
|
||||
|
||||
// Execute locked notify exit hop logic.
|
||||
i.Lock()
|
||||
resolution, err := i.notifyExitHopHtlcLocked(&updateCtx, hodlChan)
|
||||
resolution, err := i.notifyExitHopHtlcLocked(&ctx, hodlChan)
|
||||
i.Unlock()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -764,7 +764,9 @@ func (i *InvoiceRegistry) NotifyExitHopHtlc(rHash lntypes.Hash,
|
||||
// main event loop.
|
||||
case *htlcAcceptResolution:
|
||||
if r.autoRelease {
|
||||
err := i.startHtlcTimer(rHash, circuitKey, r.acceptTime)
|
||||
err := i.startHtlcTimer(
|
||||
ctx.invoiceRef(), circuitKey, r.acceptTime,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -798,7 +800,7 @@ func (i *InvoiceRegistry) notifyExitHopHtlcLocked(
|
||||
updateSubscribers bool
|
||||
)
|
||||
invoice, err := i.cdb.UpdateInvoice(
|
||||
ctx.hash,
|
||||
ctx.invoiceRef(),
|
||||
func(inv *channeldb.Invoice) (
|
||||
*channeldb.InvoiceUpdateDesc, error) {
|
||||
|
||||
@ -952,7 +954,8 @@ func (i *InvoiceRegistry) SettleHodlInvoice(preimage lntypes.Preimage) error {
|
||||
}
|
||||
|
||||
hash := preimage.Hash()
|
||||
invoice, err := i.cdb.UpdateInvoice(hash, updateInvoice)
|
||||
invoiceRef := channeldb.InvoiceRefByHash(hash)
|
||||
invoice, err := i.cdb.UpdateInvoice(invoiceRef, updateInvoice)
|
||||
if err != nil {
|
||||
log.Errorf("SettleHodlInvoice with preimage %v: %v",
|
||||
preimage, err)
|
||||
@ -960,7 +963,7 @@ func (i *InvoiceRegistry) SettleHodlInvoice(preimage lntypes.Preimage) error {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debugf("Invoice(%v): settled with preimage %v", hash,
|
||||
log.Debugf("Invoice%v: settled with preimage %v", invoiceRef,
|
||||
invoice.Terms.PaymentPreimage)
|
||||
|
||||
// In the callback, we marked the invoice as settled. UpdateInvoice will
|
||||
@ -1001,7 +1004,8 @@ func (i *InvoiceRegistry) cancelInvoiceImpl(payHash lntypes.Hash,
|
||||
i.Lock()
|
||||
defer i.Unlock()
|
||||
|
||||
log.Debugf("Invoice(%v): canceling invoice", payHash)
|
||||
ref := channeldb.InvoiceRefByHash(payHash)
|
||||
log.Debugf("Invoice%v: canceling invoice", ref)
|
||||
|
||||
updateInvoice := func(invoice *channeldb.Invoice) (
|
||||
*channeldb.InvoiceUpdateDesc, error) {
|
||||
@ -1022,12 +1026,13 @@ func (i *InvoiceRegistry) cancelInvoiceImpl(payHash lntypes.Hash,
|
||||
}, nil
|
||||
}
|
||||
|
||||
invoice, err := i.cdb.UpdateInvoice(payHash, updateInvoice)
|
||||
invoiceRef := channeldb.InvoiceRefByHash(payHash)
|
||||
invoice, err := i.cdb.UpdateInvoice(invoiceRef, updateInvoice)
|
||||
|
||||
// Implement idempotency by returning success if the invoice was already
|
||||
// canceled.
|
||||
if err == channeldb.ErrInvoiceAlreadyCanceled {
|
||||
log.Debugf("Invoice(%v): already canceled", payHash)
|
||||
log.Debugf("Invoice%v: already canceled", ref)
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
@ -1036,12 +1041,12 @@ func (i *InvoiceRegistry) cancelInvoiceImpl(payHash lntypes.Hash,
|
||||
|
||||
// Return without cancellation if the invoice state is ContractAccepted.
|
||||
if invoice.State == channeldb.ContractAccepted {
|
||||
log.Debugf("Invoice(%v): remains accepted as cancel wasn't"+
|
||||
"explicitly requested.", payHash)
|
||||
log.Debugf("Invoice%v: remains accepted as cancel wasn't"+
|
||||
"explicitly requested.", ref)
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Debugf("Invoice(%v): canceled", payHash)
|
||||
log.Debugf("Invoice%v: canceled", ref)
|
||||
|
||||
// In the callback, some htlcs may have been moved to the canceled
|
||||
// state. We now go through all of these and notify links and resolvers
|
||||
@ -1130,7 +1135,7 @@ type InvoiceSubscription struct {
|
||||
type SingleInvoiceSubscription struct {
|
||||
invoiceSubscriptionKit
|
||||
|
||||
hash lntypes.Hash
|
||||
invoiceRef channeldb.InvoiceRef
|
||||
|
||||
// Updates is a channel that we'll use to send all invoice events for
|
||||
// the invoice that is subscribed to.
|
||||
@ -1272,7 +1277,7 @@ func (i *InvoiceRegistry) SubscribeSingleInvoice(
|
||||
ntfnQueue: queue.NewConcurrentQueue(20),
|
||||
cancelChan: make(chan struct{}),
|
||||
},
|
||||
hash: hash,
|
||||
invoiceRef: channeldb.InvoiceRefByHash(hash),
|
||||
}
|
||||
client.ntfnQueue.Start()
|
||||
|
||||
|
@ -28,7 +28,7 @@ func TestSettleInvoice(t *testing.T) {
|
||||
}
|
||||
defer subscription.Cancel()
|
||||
|
||||
if subscription.hash != testInvoicePaymentHash {
|
||||
if subscription.invoiceRef.PayHash() != testInvoicePaymentHash {
|
||||
t.Fatalf("expected subscription for provided hash")
|
||||
}
|
||||
|
||||
@ -240,7 +240,7 @@ func TestCancelInvoice(t *testing.T) {
|
||||
}
|
||||
defer subscription.Cancel()
|
||||
|
||||
if subscription.hash != testInvoicePaymentHash {
|
||||
if subscription.invoiceRef.PayHash() != testInvoicePaymentHash {
|
||||
t.Fatalf("expected subscription for provided hash")
|
||||
}
|
||||
|
||||
@ -366,7 +366,7 @@ func TestSettleHoldInvoice(t *testing.T) {
|
||||
}
|
||||
defer subscription.Cancel()
|
||||
|
||||
if subscription.hash != testInvoicePaymentHash {
|
||||
if subscription.invoiceRef.PayHash() != testInvoicePaymentHash {
|
||||
t.Fatalf("expected subscription for provided hash")
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
package invoices
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
@ -198,14 +199,20 @@ func newTestInvoice(t *testing.T, preimage lntypes.Preimage,
|
||||
expiry = time.Hour
|
||||
}
|
||||
|
||||
var payAddr [32]byte
|
||||
if _, err := rand.Read(payAddr[:]); err != nil {
|
||||
t.Fatalf("unable to generate payment addr: %v", err)
|
||||
}
|
||||
|
||||
rawInvoice, err := zpay32.NewInvoice(
|
||||
testNetParams,
|
||||
preimage.Hash(),
|
||||
timestamp,
|
||||
zpay32.Amount(testInvoiceAmount),
|
||||
zpay32.Description(testInvoiceDescription),
|
||||
zpay32.Expiry(expiry))
|
||||
|
||||
zpay32.Expiry(expiry),
|
||||
zpay32.PaymentAddr(payAddr),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("Error while creating new invoice: %v", err)
|
||||
}
|
||||
@ -219,6 +226,7 @@ func newTestInvoice(t *testing.T, preimage lntypes.Preimage,
|
||||
return &channeldb.Invoice{
|
||||
Terms: channeldb.ContractTerm{
|
||||
PaymentPreimage: preimage,
|
||||
PaymentAddr: payAddr,
|
||||
Value: testInvoiceAmount,
|
||||
Expiry: expiry,
|
||||
Features: testFeatures,
|
||||
|
@ -22,10 +22,20 @@ type invoiceUpdateCtx struct {
|
||||
mpp *record.MPP
|
||||
}
|
||||
|
||||
// invoiceRef returns an identifier that can be used to lookup or update the
|
||||
// invoice this HTLC is targeting.
|
||||
func (i *invoiceUpdateCtx) invoiceRef() channeldb.InvoiceRef {
|
||||
if i.mpp != nil {
|
||||
payAddr := i.mpp.PaymentAddr()
|
||||
return channeldb.InvoiceRefByHashAndAddr(i.hash, payAddr)
|
||||
}
|
||||
return channeldb.InvoiceRefByHash(i.hash)
|
||||
}
|
||||
|
||||
// log logs a message specific to this update context.
|
||||
func (i *invoiceUpdateCtx) log(s string) {
|
||||
log.Debugf("Invoice(%x): %v, amt=%v, expiry=%v, circuit=%v, mpp=%v",
|
||||
i.hash[:], s, i.amtPaid, i.expiry, i.circuitKey, i.mpp)
|
||||
log.Debugf("Invoice%v: %v, amt=%v, expiry=%v, circuit=%v, mpp=%v",
|
||||
i.invoiceRef, s, i.amtPaid, i.expiry, i.circuitKey, i.mpp)
|
||||
}
|
||||
|
||||
// failRes is a helper function which creates a failure resolution with
|
||||
|
@ -182,3 +182,6 @@
|
||||
<time> [ERR] UTXN: Notification chan closed, can't advance output <chan_point>
|
||||
<time> [ERR] RPCS: [/walletrpc.WalletKit/LabelTransaction]: cannot label transaction with empty label
|
||||
<time> [ERR] RPCS: [/walletrpc.WalletKit/LabelTransaction]: transaction already labelled
|
||||
<time> [ERR] NTFN: unable to get hash from block with height 790
|
||||
<time> [ERR] CRTR: Payment with hash <hex> failed: timeout
|
||||
<time> [ERR] RPCS: [/routerrpc.Route<time> [INF] LTND: Listening on the p2p interface is disabled!
|
||||
|
@ -7,7 +7,7 @@ import "time"
|
||||
const (
|
||||
// MinerMempoolTimeout is the max time we will wait for a transaction
|
||||
// to propagate to the mining node's mempool.
|
||||
MinerMempoolTimeout = time.Second * 30
|
||||
MinerMempoolTimeout = time.Minute
|
||||
|
||||
// ChannelOpenTimeout is the max time we will wait before a channel to
|
||||
// be considered opened.
|
||||
|
@ -7,7 +7,7 @@ import "time"
|
||||
const (
|
||||
// MinerMempoolTimeout is the max time we will wait for a transaction
|
||||
// to propagate to the mining node's mempool.
|
||||
MinerMempoolTimeout = time.Second * 30
|
||||
MinerMempoolTimeout = time.Minute
|
||||
|
||||
// ChannelOpenTimeout is the max time we will wait before a channel to
|
||||
// be considered opened.
|
||||
|
Loading…
Reference in New Issue
Block a user