channeldb: store hold invoice

This commit is contained in:
Joost Jager 2018-10-05 10:14:56 +02:00
parent 8392f6d28f
commit 19f79613df
No known key found for this signature in database
GPG Key ID: A61B9D4C393C59C7
7 changed files with 111 additions and 43 deletions

@ -2,7 +2,6 @@ package channeldb
import ( import (
"crypto/rand" "crypto/rand"
"crypto/sha256"
"reflect" "reflect"
"testing" "testing"
"time" "time"
@ -67,17 +66,18 @@ func TestInvoiceWorkflow(t *testing.T) {
copy(fakeInvoice.Terms.PaymentPreimage[:], rev[:]) copy(fakeInvoice.Terms.PaymentPreimage[:], rev[:])
fakeInvoice.Terms.Value = lnwire.NewMSatFromSatoshis(10000) fakeInvoice.Terms.Value = lnwire.NewMSatFromSatoshis(10000)
paymentHash := fakeInvoice.Terms.PaymentPreimage.Hash()
// Add the invoice to the database, this should succeed as there aren't // Add the invoice to the database, this should succeed as there aren't
// any existing invoices within the database with the same payment // any existing invoices within the database with the same payment
// hash. // hash.
if _, err := db.AddInvoice(fakeInvoice); err != nil { if _, err := db.AddInvoice(fakeInvoice, paymentHash); err != nil {
t.Fatalf("unable to find invoice: %v", err) t.Fatalf("unable to find invoice: %v", err)
} }
// Attempt to retrieve the invoice which was just added to the // Attempt to retrieve the invoice which was just added to the
// database. It should be found, and the invoice returned should be // database. It should be found, and the invoice returned should be
// identical to the one created above. // identical to the one created above.
paymentHash := sha256.Sum256(fakeInvoice.Terms.PaymentPreimage[:])
dbInvoice, err := db.LookupInvoice(paymentHash) dbInvoice, err := db.LookupInvoice(paymentHash)
if err != nil { if err != nil {
t.Fatalf("unable to find invoice: %v", err) t.Fatalf("unable to find invoice: %v", err)
@ -126,7 +126,7 @@ func TestInvoiceWorkflow(t *testing.T) {
// Attempt to insert generated above again, this should fail as // Attempt to insert generated above again, this should fail as
// duplicates are rejected by the processing logic. // duplicates are rejected by the processing logic.
if _, err := db.AddInvoice(fakeInvoice); err != ErrDuplicateInvoice { if _, err := db.AddInvoice(fakeInvoice, paymentHash); err != ErrDuplicateInvoice {
t.Fatalf("invoice insertion should fail due to duplication, "+ t.Fatalf("invoice insertion should fail due to duplication, "+
"instead %v", err) "instead %v", err)
} }
@ -149,7 +149,8 @@ func TestInvoiceWorkflow(t *testing.T) {
t.Fatalf("unable to create invoice: %v", err) t.Fatalf("unable to create invoice: %v", err)
} }
if _, err := db.AddInvoice(invoice); err != nil { hash := invoice.Terms.PaymentPreimage.Hash()
if _, err := db.AddInvoice(invoice, hash); err != nil {
t.Fatalf("unable to add invoice %v", err) t.Fatalf("unable to add invoice %v", err)
} }
@ -198,7 +199,9 @@ func TestInvoiceAddTimeSeries(t *testing.T) {
t.Fatalf("unable to create invoice: %v", err) t.Fatalf("unable to create invoice: %v", err)
} }
if _, err := db.AddInvoice(invoice); err != nil { paymentHash := invoice.Terms.PaymentPreimage.Hash()
if _, err := db.AddInvoice(invoice, paymentHash); err != nil {
t.Fatalf("unable to add invoice %v", err) t.Fatalf("unable to add invoice %v", err)
} }
@ -256,9 +259,7 @@ func TestInvoiceAddTimeSeries(t *testing.T) {
for i := 10; i < len(invoices); i++ { for i := 10; i < len(invoices); i++ {
invoice := &invoices[i] invoice := &invoices[i]
paymentHash := sha256.Sum256( paymentHash := invoice.Terms.PaymentPreimage.Hash()
invoice.Terms.PaymentPreimage[:],
)
_, err := db.SettleInvoice(paymentHash, 0) _, err := db.SettleInvoice(paymentHash, 0)
if err != nil { if err != nil {
@ -334,12 +335,13 @@ func TestDuplicateSettleInvoice(t *testing.T) {
t.Fatalf("unable to create invoice: %v", err) t.Fatalf("unable to create invoice: %v", err)
} }
if _, err := db.AddInvoice(invoice); err != nil { payHash := invoice.Terms.PaymentPreimage.Hash()
if _, err := db.AddInvoice(invoice, payHash); err != nil {
t.Fatalf("unable to add invoice %v", err) t.Fatalf("unable to add invoice %v", err)
} }
// With the invoice in the DB, we'll now attempt to settle the invoice. // With the invoice in the DB, we'll now attempt to settle the invoice.
payHash := sha256.Sum256(invoice.Terms.PaymentPreimage[:])
dbInvoice, err := db.SettleInvoice(payHash, amt) dbInvoice, err := db.SettleInvoice(payHash, amt)
if err != nil { if err != nil {
t.Fatalf("unable to settle invoice: %v", err) t.Fatalf("unable to settle invoice: %v", err)
@ -397,13 +399,14 @@ func TestQueryInvoices(t *testing.T) {
t.Fatalf("unable to create invoice: %v", err) t.Fatalf("unable to create invoice: %v", err)
} }
if _, err := db.AddInvoice(invoice); err != nil { paymentHash := invoice.Terms.PaymentPreimage.Hash()
if _, err := db.AddInvoice(invoice, paymentHash); err != nil {
t.Fatalf("unable to add invoice: %v", err) t.Fatalf("unable to add invoice: %v", err)
} }
// We'll only settle half of all invoices created. // We'll only settle half of all invoices created.
if i%2 == 0 { if i%2 == 0 {
paymentHash := sha256.Sum256(invoice.Terms.PaymentPreimage[:])
if _, err := db.SettleInvoice(paymentHash, i); err != nil { if _, err := db.SettleInvoice(paymentHash, i); err != nil {
t.Fatalf("unable to settle invoice: %v", err) t.Fatalf("unable to settle invoice: %v", err)
} }

@ -2,7 +2,6 @@ package channeldb
import ( import (
"bytes" "bytes"
"crypto/sha256"
"encoding/binary" "encoding/binary"
"errors" "errors"
"fmt" "fmt"
@ -16,6 +15,10 @@ import (
) )
var ( var (
// UnknownPreimage is an all-zeroes preimage that indicates that the
// preimage for this invoice is not yet known.
UnknownPreimage lntypes.Preimage
// invoiceBucket is the name of the bucket within the database that // invoiceBucket is the name of the bucket within the database that
// stores all data related to invoices no matter their final state. // stores all data related to invoices no matter their final state.
// Within the invoice bucket, each invoice is keyed by its invoice ID // Within the invoice bucket, each invoice is keyed by its invoice ID
@ -218,7 +221,9 @@ func validateInvoice(i *Invoice) error {
// insertion will be aborted and rejected due to the strict policy banning any // insertion will be aborted and rejected due to the strict policy banning any
// duplicate payment hashes. A side effect of this function is that it sets // duplicate payment hashes. A side effect of this function is that it sets
// AddIndex on newInvoice. // AddIndex on newInvoice.
func (d *DB) AddInvoice(newInvoice *Invoice) (uint64, error) { func (d *DB) AddInvoice(newInvoice *Invoice, paymentHash lntypes.Hash) (
uint64, error) {
if err := validateInvoice(newInvoice); err != nil { if err := validateInvoice(newInvoice); err != nil {
return 0, err return 0, err
} }
@ -245,9 +250,6 @@ func (d *DB) AddInvoice(newInvoice *Invoice) (uint64, error) {
// Ensure that an invoice an identical payment hash doesn't // Ensure that an invoice an identical payment hash doesn't
// already exist within the index. // already exist within the index.
paymentHash := sha256.Sum256(
newInvoice.Terms.PaymentPreimage[:],
)
if invoiceIndex.Get(paymentHash[:]) != nil { if invoiceIndex.Get(paymentHash[:]) != nil {
return ErrDuplicateInvoice return ErrDuplicateInvoice
} }
@ -269,6 +271,7 @@ func (d *DB) AddInvoice(newInvoice *Invoice) (uint64, error) {
newIndex, err := putInvoice( newIndex, err := putInvoice(
invoices, invoiceIndex, addIndex, newInvoice, invoiceNum, invoices, invoiceIndex, addIndex, newInvoice, invoiceNum,
paymentHash,
) )
if err != nil { if err != nil {
return err return err
@ -744,7 +747,8 @@ func (d *DB) InvoicesSettledSince(sinceSettleIndex uint64) ([]Invoice, error) {
} }
func putInvoice(invoices, invoiceIndex, addIndex *bbolt.Bucket, func putInvoice(invoices, invoiceIndex, addIndex *bbolt.Bucket,
i *Invoice, invoiceNum uint32) (uint64, error) { i *Invoice, invoiceNum uint32, paymentHash lntypes.Hash) (
uint64, error) {
// Create the invoice key which is just the big-endian representation // Create the invoice key which is just the big-endian representation
// of the invoice number. // of the invoice number.
@ -763,7 +767,6 @@ func putInvoice(invoices, invoiceIndex, addIndex *bbolt.Bucket,
// Add the payment hash to the invoice index. This will let us quickly // Add the payment hash to the invoice index. This will let us quickly
// identify if we can settle an incoming payment, and also to possibly // identify if we can settle an incoming payment, and also to possibly
// allow a single invoice to have multiple payment installations. // allow a single invoice to have multiple payment installations.
paymentHash := sha256.Sum256(i.Terms.PaymentPreimage[:])
err := invoiceIndex.Put(paymentHash[:], invoiceKey[:]) err := invoiceIndex.Put(paymentHash[:], invoiceKey[:])
if err != nil { if err != nil {
return 0, err return 0, err

@ -2569,6 +2569,19 @@ func (l *channelLink) processExitHop(pd *lnwallet.PaymentDescriptor,
return true, nil return true, nil
} }
// Reject invoices with unknown preimages.
if invoice.Terms.PaymentPreimage == channeldb.UnknownPreimage {
log.Errorf("rejecting htlc because preimage is unknown")
failure := lnwire.NewFailUnknownPaymentHash(pd.Amount)
l.sendHTLCError(
pd.HtlcIndex, failure, obfuscator,
pd.SourceRef,
)
return true, nil
}
// If the invoice is already settled, we choose to accept the payment to // If the invoice is already settled, we choose to accept the payment to
// simplify failure recovery. // simplify failure recovery.
// //

@ -392,7 +392,7 @@ func (i *InvoiceRegistry) AddInvoice(invoice *channeldb.Invoice,
return spew.Sdump(invoice) return spew.Sdump(invoice)
})) }))
addIndex, err := i.cdb.AddInvoice(invoice) addIndex, err := i.cdb.AddInvoice(invoice, paymentHash)
if err != nil { if err != nil {
return 0, err return 0, err
} }

@ -4,19 +4,20 @@ import (
"bytes" "bytes"
"context" "context"
"crypto/rand" "crypto/rand"
"errors"
"fmt" "fmt"
"math" "math"
"time" "time"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/netann"
"github.com/lightningnetwork/lnd/zpay32"
"github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcutil" "github.com/btcsuite/btcutil"
"github.com/davecgh/go-spew/spew" "github.com/davecgh/go-spew/spew"
"github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/netann"
"github.com/lightningnetwork/lnd/zpay32"
) )
// AddInvoiceConfig contains dependencies for invoice creation. // AddInvoiceConfig contains dependencies for invoice creation.
@ -61,9 +62,15 @@ type AddInvoiceData struct {
Receipt []byte Receipt []byte
// The preimage which will allow settling an incoming HTLC payable to // The preimage which will allow settling an incoming HTLC payable to
// this preimage. // this preimage. If Preimage is set, Hash should be nil. If both
// Preimage and Hash are nil, a random preimage is generated.
Preimage *lntypes.Preimage Preimage *lntypes.Preimage
// The hash of the preimage. If Hash is set, Preimage should be nil.
// This condition indicates that we have a 'hold invoice' for which the
// htlc will be accepted and held until the preimage becomes known.
Hash *lntypes.Hash
// The value of this invoice in satoshis. // The value of this invoice in satoshis.
Value btcutil.Amount Value btcutil.Amount
@ -87,19 +94,58 @@ type AddInvoiceData struct {
} }
// AddInvoice attempts to add a new invoice to the invoice database. Any // AddInvoice attempts to add a new invoice to the invoice database. Any
// duplicated invoices are rejected, therefore all invoices *must* have a unique // duplicated invoices are rejected, therefore all invoices *must* have a
// payment preimage. AddInvoice returns the payment hash and the invoice // unique payment preimage.
// structure as stored in the database.
func AddInvoice(ctx context.Context, cfg *AddInvoiceConfig, func AddInvoice(ctx context.Context, cfg *AddInvoiceConfig,
invoice *AddInvoiceData) (*lntypes.Hash, *channeldb.Invoice, error) { invoice *AddInvoiceData) (*lntypes.Hash, *channeldb.Invoice, error) {
var paymentPreimage lntypes.Preimage var (
if invoice.Preimage == nil { paymentPreimage lntypes.Preimage
paymentHash lntypes.Hash
)
switch {
// Only either preimage or hash can be set.
case invoice.Preimage != nil && invoice.Hash != nil:
return nil, nil,
errors.New("preimage and hash both set")
// Prevent the unknown preimage magic value from being used for a
// regular invoice. This would cause the invoice the be handled as if it
// was a hold invoice.
case invoice.Preimage != nil &&
*invoice.Preimage == channeldb.UnknownPreimage:
return nil, nil,
fmt.Errorf("cannot use all zeroes as a preimage")
// Prevent the hash of the unknown preimage magic value to be used for a
// hold invoice. This would make it impossible to settle the invoice,
// because it would still be interpreted as not having a preimage.
case invoice.Hash != nil &&
*invoice.Hash == channeldb.UnknownPreimage.Hash():
return nil, nil,
fmt.Errorf("cannot use hash of all zeroes preimage")
// If no hash or preimage is given, generate a random preimage.
case invoice.Preimage == nil && invoice.Hash == nil:
if _, err := rand.Read(paymentPreimage[:]); err != nil { if _, err := rand.Read(paymentPreimage[:]); err != nil {
return nil, nil, err return nil, nil, err
} }
} else { paymentHash = paymentPreimage.Hash()
// If just a hash is given, we create a hold invoice by setting the
// preimage to unknown.
case invoice.Preimage == nil && invoice.Hash != nil:
paymentPreimage = channeldb.UnknownPreimage
paymentHash = *invoice.Hash
// A specific preimage was supplied. Use that for the invoice.
case invoice.Preimage != nil && invoice.Hash == nil:
paymentPreimage = *invoice.Preimage paymentPreimage = *invoice.Preimage
paymentHash = invoice.Preimage.Hash()
} }
// The size of the memo, receipt and description hash attached must not // The size of the memo, receipt and description hash attached must not
@ -134,10 +180,6 @@ func AddInvoice(ctx context.Context, cfg *AddInvoiceConfig,
) )
} }
// Next, generate the payment hash itself from the preimage. This will
// be used by clients to query for the state of a particular invoice.
rHash := paymentPreimage.Hash()
// We also create an encoded payment request which allows the // We also create an encoded payment request which allows the
// caller to compactly send the invoice to the payer. We'll create a // caller to compactly send the invoice to the payer. We'll create a
// list of options to be added to the encoded payment request. For now // list of options to be added to the encoded payment request. For now
@ -332,7 +374,7 @@ func AddInvoice(ctx context.Context, cfg *AddInvoiceConfig,
// Create and encode the payment request as a bech32 (zpay32) string. // Create and encode the payment request as a bech32 (zpay32) string.
creationDate := time.Now() creationDate := time.Now()
payReq, err := zpay32.NewInvoice( payReq, err := zpay32.NewInvoice(
cfg.ChainParams, rHash, creationDate, options..., cfg.ChainParams, paymentHash, creationDate, options...,
) )
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
@ -365,10 +407,10 @@ func AddInvoice(ctx context.Context, cfg *AddInvoiceConfig,
) )
// With all sanity checks passed, write the invoice to the database. // With all sanity checks passed, write the invoice to the database.
_, err = cfg.AddInvoice(newInvoice, rHash) _, err = cfg.AddInvoice(newInvoice, paymentHash)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
return &rHash, newInvoice, nil return &paymentHash, newInvoice, nil
} }

@ -65,11 +65,10 @@ func CreateRPCInvoice(invoice *channeldb.Invoice,
invoice.Terms.State) invoice.Terms.State)
} }
return &lnrpc.Invoice{ rpcInvoice := &lnrpc.Invoice{
Memo: string(invoice.Memo[:]), Memo: string(invoice.Memo[:]),
Receipt: invoice.Receipt[:], Receipt: invoice.Receipt[:],
RHash: decoded.PaymentHash[:], RHash: decoded.PaymentHash[:],
RPreimage: preimage[:],
Value: int64(satAmt), Value: int64(satAmt),
CreationDate: invoice.CreationDate.Unix(), CreationDate: invoice.CreationDate.Unix(),
SettleDate: settleDate, SettleDate: settleDate,
@ -87,7 +86,13 @@ func CreateRPCInvoice(invoice *channeldb.Invoice,
AmtPaidMsat: int64(invoice.AmtPaid), AmtPaidMsat: int64(invoice.AmtPaid),
AmtPaid: int64(invoice.AmtPaid), AmtPaid: int64(invoice.AmtPaid),
State: state, State: state,
}, nil }
if preimage != channeldb.UnknownPreimage {
rpcInvoice.RPreimage = preimage[:]
}
return rpcInvoice, nil
} }
// CreateRPCRouteHints takes in the decoded form of an invoice's route hints // CreateRPCRouteHints takes in the decoded form of an invoice's route hints

@ -85,7 +85,9 @@ func (p *preimageBeacon) LookupPreimage(
// If we've found the invoice, then we can return the preimage // If we've found the invoice, then we can return the preimage
// directly. // directly.
if err != channeldb.ErrInvoiceNotFound { if err != channeldb.ErrInvoiceNotFound &&
invoice.Terms.PaymentPreimage != channeldb.UnknownPreimage {
return invoice.Terms.PaymentPreimage, true return invoice.Terms.PaymentPreimage, true
} }