Skip to content

Commit

Permalink
multi: add explicit hodl invoice flag to invoice
Browse files Browse the repository at this point in the history
Previously it wasn't possible to store a preimage in the invoice
database and signal that a payment should not be settled right away. The
only way to hold a payment was to insert the magic UnknownPreimage value
in the invoice database. This commit introduces a distinct flag to
signal that an invoice is a hold invoice and thereby allows the preimage
to be present in the database already.

Preparation for (key send) hodl invoices for which we already know the
preimage.
  • Loading branch information
joostjager committed Jun 2, 2020
1 parent bdc0d87 commit d416ed5
Show file tree
Hide file tree
Showing 11 changed files with 153 additions and 93 deletions.
31 changes: 22 additions & 9 deletions channeldb/invoice_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@ var (
)

func randInvoice(value lnwire.MilliSatoshi) (*Invoice, error) {
var pre, payAddr [32]byte
var (
pre lntypes.Preimage
payAddr [32]byte
)
if _, err := rand.Read(pre[:]); err != nil {
return nil, err
}
Expand All @@ -32,7 +35,7 @@ func randInvoice(value lnwire.MilliSatoshi) (*Invoice, error) {
CreationDate: testNow,
Terms: ContractTerm{
Expiry: 4000,
PaymentPreimage: pre,
PaymentPreimage: &pre,
PaymentAddr: payAddr,
Value: value,
Features: emptyFeatures,
Expand Down Expand Up @@ -360,13 +363,18 @@ func TestInvoiceCancelSingleHtlc(t *testing.T) {
t.Fatalf("unable to make test db: %v", err)
}

preimage := lntypes.Preimage{1}
paymentHash := preimage.Hash()

testInvoice := &Invoice{
Htlcs: map[CircuitKey]*InvoiceHTLC{},
Terms: ContractTerm{
Value: lnwire.NewMSatFromSatoshis(10000),
Features: emptyFeatures,
PaymentPreimage: &preimage,
},
}
testInvoice.Terms.Value = lnwire.NewMSatFromSatoshis(10000)
testInvoice.Terms.Features = emptyFeatures

var paymentHash lntypes.Hash
if _, err := db.AddInvoice(testInvoice, paymentHash); err != nil {
t.Fatalf("unable to find invoice: %v", err)
}
Expand Down Expand Up @@ -1059,15 +1067,20 @@ func TestCustomRecords(t *testing.T) {
t.Fatalf("unable to make test db: %v", err)
}

preimage := lntypes.Preimage{1}
paymentHash := preimage.Hash()

testInvoice := &Invoice{
Htlcs: map[CircuitKey]*InvoiceHTLC{},
Terms: ContractTerm{
Value: lnwire.NewMSatFromSatoshis(10000),
Features: emptyFeatures,
PaymentPreimage: &preimage,
},
}
testInvoice.Terms.Value = lnwire.NewMSatFromSatoshis(10000)
testInvoice.Terms.Features = emptyFeatures

var paymentHash lntypes.Hash
if _, err := db.AddInvoice(testInvoice, paymentHash); err != nil {
t.Fatalf("unable to find invoice: %v", err)
t.Fatalf("unable to add invoice: %v", err)
}

// Accept an htlc with custom records on this invoice.
Expand Down
88 changes: 69 additions & 19 deletions channeldb/invoices.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ import (
)

var (
// UnknownPreimage is an all-zeroes preimage that indicates that the
// unknownPreimage is an all-zeroes preimage that indicates that the
// preimage for this invoice is not yet known.
UnknownPreimage lntypes.Preimage
unknownPreimage lntypes.Preimage

// invoiceBucket is the name of the bucket within the database that
// stores all data related to invoices no matter their final state.
Expand Down Expand Up @@ -150,6 +150,7 @@ const (
featuresType tlv.Type = 11
invStateType tlv.Type = 12
amtPaidType tlv.Type = 13
hodlInvoiceType tlv.Type = 14
)

// InvoiceRef is a composite identifier for invoices. Invoices can be referenced
Expand Down Expand Up @@ -261,8 +262,8 @@ type ContractTerm struct {

// PaymentPreimage is the preimage which is to be revealed in the
// occasion that an HTLC paying to the hash of this preimage is
// extended.
PaymentPreimage lntypes.Preimage
// extended. Set to nil if the preimage isn't known yet.
PaymentPreimage *lntypes.Preimage

// Value is the expected amount of milli-satoshis to be paid to an HTLC
// which can be satisfied by the above preimage.
Expand Down Expand Up @@ -346,6 +347,10 @@ type Invoice struct {
// Htlcs records all htlcs that paid to this invoice. Some of these
// htlcs may have been marked as canceled.
Htlcs map[CircuitKey]*InvoiceHTLC

// HodlInvoice indicates whether the invoice should be held in the
// Accepted state or be settled right away.
HodlInvoice bool
}

// HtlcState defines the states an htlc paying to an invoice can be in.
Expand Down Expand Up @@ -439,14 +444,19 @@ type InvoiceStateUpdateDesc struct {
NewState ContractState

// Preimage must be set to the preimage when NewState is settled.
Preimage lntypes.Preimage
Preimage *lntypes.Preimage
}

// InvoiceUpdateCallback is a callback used in the db transaction to update the
// invoice.
type InvoiceUpdateCallback = func(invoice *Invoice) (*InvoiceUpdateDesc, error)

func validateInvoice(i *Invoice) error {
func validateInvoice(i *Invoice, paymentHash lntypes.Hash) error {
// Avoid conflicts with all-zeroes magic value in the database.
if paymentHash == unknownPreimage.Hash() {
return fmt.Errorf("cannot use hash of all-zeroes preimage")
}

if len(i.Memo) > MaxMemoSize {
return fmt.Errorf("max length a memo is %v, and invoice "+
"of length %v was provided", MaxMemoSize, len(i.Memo))
Expand All @@ -459,6 +469,10 @@ func validateInvoice(i *Invoice) error {
if i.Terms.Features == nil {
return errors.New("invoice must have a feature vector")
}

if i.Terms.PaymentPreimage == nil && !i.HodlInvoice {
return errors.New("non-hodl invoices must have a preimage")
}
return nil
}

Expand All @@ -475,7 +489,7 @@ func (i *Invoice) IsPending() bool {
func (d *DB) AddInvoice(newInvoice *Invoice, paymentHash lntypes.Hash) (
uint64, error) {

if err := validateInvoice(newInvoice); err != nil {
if err := validateInvoice(newInvoice, paymentHash); err != nil {
return 0, err
}

Expand Down Expand Up @@ -1131,14 +1145,25 @@ func serializeInvoice(w io.Writer, i *Invoice) error {
}
featureBytes := fb.Bytes()

preimage := [32]byte(i.Terms.PaymentPreimage)
preimage := [32]byte(unknownPreimage)
if i.Terms.PaymentPreimage != nil {
preimage = *i.Terms.PaymentPreimage
if preimage == unknownPreimage {
return errors.New("cannot use all-zeroes preimage")
}
}
value := uint64(i.Terms.Value)
cltvDelta := uint32(i.Terms.FinalCltvDelta)
expiry := uint64(i.Terms.Expiry)

amtPaid := uint64(i.AmtPaid)
state := uint8(i.State)

var hodlInvoice uint8
if i.HodlInvoice {
hodlInvoice = 1
}

tlvStream, err := tlv.NewStream(
// Memo and payreq.
tlv.MakePrimitiveRecord(memoType, &i.Memo),
Expand All @@ -1161,6 +1186,8 @@ func serializeInvoice(w io.Writer, i *Invoice) error {
// Invoice state.
tlv.MakePrimitiveRecord(invStateType, &state),
tlv.MakePrimitiveRecord(amtPaidType, &amtPaid),

tlv.MakePrimitiveRecord(hodlInvoiceType, &hodlInvoice),
)
if err != nil {
return err
Expand Down Expand Up @@ -1256,12 +1283,13 @@ func fetchInvoice(invoiceNum []byte, invoices kvdb.RBucket) (Invoice, error) {

func deserializeInvoice(r io.Reader) (Invoice, error) {
var (
preimage [32]byte
value uint64
cltvDelta uint32
expiry uint64
amtPaid uint64
state uint8
preimageBytes [32]byte
value uint64
cltvDelta uint32
expiry uint64
amtPaid uint64
state uint8
hodlInvoice uint8

creationDateBytes []byte
settleDateBytes []byte
Expand All @@ -1281,7 +1309,7 @@ func deserializeInvoice(r io.Reader) (Invoice, error) {
tlv.MakePrimitiveRecord(settleIndexType, &i.SettleIndex),

// Terms.
tlv.MakePrimitiveRecord(preimageType, &preimage),
tlv.MakePrimitiveRecord(preimageType, &preimageBytes),
tlv.MakePrimitiveRecord(valueType, &value),
tlv.MakePrimitiveRecord(cltvDeltaType, &cltvDelta),
tlv.MakePrimitiveRecord(expiryType, &expiry),
Expand All @@ -1291,6 +1319,8 @@ func deserializeInvoice(r io.Reader) (Invoice, error) {
// Invoice state.
tlv.MakePrimitiveRecord(invStateType, &state),
tlv.MakePrimitiveRecord(amtPaidType, &amtPaid),

tlv.MakePrimitiveRecord(hodlInvoiceType, &hodlInvoice),
)
if err != nil {
return i, err
Expand All @@ -1307,13 +1337,21 @@ func deserializeInvoice(r io.Reader) (Invoice, error) {
return i, err
}

i.Terms.PaymentPreimage = lntypes.Preimage(preimage)
preimage := lntypes.Preimage(preimageBytes)
if preimage != unknownPreimage {
i.Terms.PaymentPreimage = &preimage
}

i.Terms.Value = lnwire.MilliSatoshi(value)
i.Terms.FinalCltvDelta = int32(cltvDelta)
i.Terms.Expiry = time.Duration(expiry)
i.AmtPaid = lnwire.MilliSatoshi(amtPaid)
i.State = ContractState(state)

if hodlInvoice != 0 {
i.HodlInvoice = true
}

err = i.CreationDate.UnmarshalBinary(creationDateBytes)
if err != nil {
return i, err
Expand Down Expand Up @@ -1443,10 +1481,16 @@ func copyInvoice(src *Invoice) *Invoice {
Htlcs: make(
map[CircuitKey]*InvoiceHTLC, len(src.Htlcs),
),
HodlInvoice: src.HodlInvoice,
}

dest.Terms.Features = src.Terms.Features.Clone()

if src.Terms.PaymentPreimage != nil {
preimage := *src.Terms.PaymentPreimage
dest.Terms.PaymentPreimage = &preimage
}

for k, v := range src.Htlcs {
dest.Htlcs[k] = copyInvoiceHTLC(v)
}
Expand Down Expand Up @@ -1619,10 +1663,16 @@ func updateInvoiceState(invoice *Invoice, hash lntypes.Hash,
case ContractOpen:
if update.NewState == ContractSettled {
// Validate preimage.
if update.Preimage.Hash() != hash {
return ErrInvoicePreimageMismatch
switch {
case update.Preimage != nil:
if update.Preimage.Hash() != hash {
return ErrInvoicePreimageMismatch
}
invoice.Terms.PaymentPreimage = update.Preimage

case invoice.Terms.PaymentPreimage == nil:
return errors.New("unknown preimage")
}
invoice.Terms.PaymentPreimage = update.Preimage
}

// Once settled, we are in a terminal state.
Expand Down
3 changes: 1 addition & 2 deletions contractcourt/channel_arbitrator.go
Original file line number Diff line number Diff line change
Expand Up @@ -1457,8 +1457,7 @@ func (c *ChannelArbitrator) isPreimageAvailable(hash lntypes.Hash) (bool,
return false, err
}

preimageAvailable = invoice.Terms.PaymentPreimage !=
channeldb.UnknownPreimage
preimageAvailable = invoice.Terms.PaymentPreimage != nil

return preimageAvailable, nil
}
Expand Down
Loading

0 comments on commit d416ed5

Please sign in to comment.