Skip to content

Commit

Permalink
htlcswitch: hodl invoice
Browse files Browse the repository at this point in the history
This commit modifies the invoice registry to handle invoices for which
the preimage is not known yet (hodl invoices). In that case, the
resolution channel passed in from links and resolvers is stored until we
either learn the preimage or want to cancel the htlc.
  • Loading branch information
joostjager committed Mar 15, 2019
1 parent 1f41a2a commit 32f2b04
Show file tree
Hide file tree
Showing 14 changed files with 1,191 additions and 645 deletions.
10 changes: 5 additions & 5 deletions channeldb/invoice_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ func TestInvoiceWorkflow(t *testing.T) {
// now have the settled bit toggle to true and a non-default
// SettledDate
payAmt := fakeInvoice.Terms.Value * 2
if _, err := db.SettleInvoice(paymentHash, payAmt); err != nil {
if _, err := db.AcceptOrSettleInvoice(paymentHash, payAmt); err != nil {
t.Fatalf("unable to settle invoice: %v", err)
}
dbInvoice2, err := db.LookupInvoice(paymentHash)
Expand Down Expand Up @@ -261,7 +261,7 @@ func TestInvoiceAddTimeSeries(t *testing.T) {

paymentHash := invoice.Terms.PaymentPreimage.Hash()

_, err := db.SettleInvoice(paymentHash, 0)
_, err := db.AcceptOrSettleInvoice(paymentHash, 0)
if err != nil {
t.Fatalf("unable to settle invoice: %v", err)
}
Expand Down Expand Up @@ -342,7 +342,7 @@ func TestDuplicateSettleInvoice(t *testing.T) {
}

// With the invoice in the DB, we'll now attempt to settle the invoice.
dbInvoice, err := db.SettleInvoice(payHash, amt)
dbInvoice, err := db.AcceptOrSettleInvoice(payHash, amt)
if err != nil {
t.Fatalf("unable to settle invoice: %v", err)
}
Expand All @@ -362,7 +362,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.SettleInvoice(payHash, amt)
dbInvoice, err = db.AcceptOrSettleInvoice(payHash, amt)
if err != ErrInvoiceAlreadySettled {
t.Fatalf("expected ErrInvoiceAlreadySettled")
}
Expand Down Expand Up @@ -407,7 +407,7 @@ func TestQueryInvoices(t *testing.T) {

// We'll only settle half of all invoices created.
if i%2 == 0 {
if _, err := db.SettleInvoice(paymentHash, i); err != nil {
if _, err := db.AcceptOrSettleInvoice(paymentHash, i); err != nil {
t.Fatalf("unable to settle invoice: %v", err)
}
}
Expand Down
143 changes: 130 additions & 13 deletions channeldb/invoices.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,13 @@ var (
// ErrInvoiceAlreadyCanceled is returned when the invoice is already
// canceled.
ErrInvoiceAlreadyCanceled = errors.New("invoice already canceled")

// ErrInvoiceAlreadyAccepted is returned when the invoice is already
// accepted.
ErrInvoiceAlreadyAccepted = errors.New("invoice already accepted")

// ErrInvoiceStillOpen is returned when the invoice is still open.
ErrInvoiceStillOpen = errors.New("invoice still open")
)

const (
Expand Down Expand Up @@ -100,6 +107,10 @@ const (

// ContractCanceled means the invoice has been canceled.
ContractCanceled ContractState = 2

// ContractAccepted means the HTLC has been accepted but not settled
// yet.
ContractAccepted ContractState = 3
)

// String returns a human readable identifier for the ContractState type.
Expand All @@ -111,6 +122,8 @@ func (c ContractState) String() string {
return "Settled"
case ContractCanceled:
return "Canceled"
case ContractAccepted:
return "Accepted"
}

return "Unknown"
Expand Down Expand Up @@ -611,11 +624,14 @@ func (d *DB) QueryInvoices(q InvoiceQuery) (InvoiceSlice, error) {
return resp, nil
}

// SettleInvoice attempts to mark an invoice corresponding to the passed
// payment hash as fully settled. If an invoice matching the passed payment
// hash doesn't existing within the database, then the action will fail with a
// "not found" error.
func (d *DB) SettleInvoice(paymentHash [32]byte,
// AcceptOrSettleInvoice attempts to mark an invoice corresponding to the passed
// payment hash as settled. If an invoice matching the passed payment hash
// doesn't existing within the database, then the action will fail with a "not
// found" error.
//
// When the preimage for the invoice is unknown (hold invoice), the invoice is
// marked as accepted.
func (d *DB) AcceptOrSettleInvoice(paymentHash [32]byte,
amtPaid lnwire.MilliSatoshi) (*Invoice, error) {

var settledInvoice *Invoice
Expand Down Expand Up @@ -644,7 +660,7 @@ func (d *DB) SettleInvoice(paymentHash [32]byte,
return ErrInvoiceNotFound
}

settledInvoice, err = settleInvoice(
settledInvoice, err = acceptOrSettleInvoice(
invoices, settleIndex, invoiceNum, amtPaid,
)

Expand All @@ -654,6 +670,46 @@ func (d *DB) SettleInvoice(paymentHash [32]byte,
return settledInvoice, err
}

// SettleHoldInvoice sets the preimage of a hodl invoice and marks the invoice
// as settled.
func (d *DB) SettleHoldInvoice(preimage lntypes.Preimage) (*Invoice, error) {
var updatedInvoice *Invoice
hash := preimage.Hash()
err := d.Update(func(tx *bbolt.Tx) error {
invoices, err := tx.CreateBucketIfNotExists(invoiceBucket)
if err != nil {
return err
}
invoiceIndex, err := invoices.CreateBucketIfNotExists(
invoiceIndexBucket,
)
if err != nil {
return err
}
settleIndex, err := invoices.CreateBucketIfNotExists(
settleIndexBucket,
)
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(hash[:])
if invoiceNum == nil {
return ErrInvoiceNotFound
}

updatedInvoice, err = settleHoldInvoice(
invoices, settleIndex, invoiceNum, preimage,
)

return err
})

return updatedInvoice, err
}

// CancelInvoice attempts to cancel the invoice corresponding to the passed
// payment hash.
func (d *DB) CancelInvoice(paymentHash lntypes.Hash) (*Invoice, error) {
Expand Down Expand Up @@ -932,40 +988,98 @@ func deserializeInvoice(r io.Reader) (Invoice, error) {
return invoice, nil
}

func settleInvoice(invoices, settleIndex *bbolt.Bucket, invoiceNum []byte,
func acceptOrSettleInvoice(invoices, settleIndex *bbolt.Bucket, invoiceNum []byte,
amtPaid lnwire.MilliSatoshi) (*Invoice, error) {

invoice, err := fetchInvoice(invoiceNum, invoices)
if err != nil {
return nil, err
}

switch invoice.Terms.State {
case ContractSettled:
state := invoice.Terms.State

switch {
case state == ContractAccepted:
return &invoice, ErrInvoiceAlreadyAccepted
case state == ContractSettled:
return &invoice, ErrInvoiceAlreadySettled
case ContractCanceled:
case state == ContractCanceled:
return &invoice, ErrInvoiceAlreadyCanceled
}

holdInvoice := invoice.Terms.PaymentPreimage == UnknownPreimage
if holdInvoice {
invoice.Terms.State = ContractAccepted
} else {
err := setSettleFields(settleIndex, invoiceNum, &invoice)
if err != nil {
return nil, err
}
}

invoice.AmtPaid = amtPaid

var buf bytes.Buffer
if err := serializeInvoice(&buf, &invoice); err != nil {
return nil, err
}

if err := invoices.Put(invoiceNum[:], buf.Bytes()); err != nil {
return nil, err
}

return &invoice, nil
}

func setSettleFields(settleIndex *bbolt.Bucket, invoiceNum []byte,
invoice *Invoice) error {

// Now that we know the invoice hasn't already been settled, we'll
// update the settle index so we can place this settle event in the
// proper location within our time series.
nextSettleSeqNo, err := settleIndex.NextSequence()
if err != nil {
return nil, err
return err
}

var seqNoBytes [8]byte
byteOrder.PutUint64(seqNoBytes[:], nextSettleSeqNo)
if err := settleIndex.Put(seqNoBytes[:], invoiceNum); err != nil {
return nil, err
return err
}

invoice.AmtPaid = amtPaid
invoice.Terms.State = ContractSettled
invoice.SettleDate = time.Now()
invoice.SettleIndex = nextSettleSeqNo

return nil
}

func settleHoldInvoice(invoices, settleIndex *bbolt.Bucket,
invoiceNum []byte, preimage lntypes.Preimage) (*Invoice,
error) {

invoice, err := fetchInvoice(invoiceNum, invoices)
if err != nil {
return nil, err
}

switch invoice.Terms.State {
case ContractOpen:
return &invoice, ErrInvoiceStillOpen
case ContractCanceled:
return &invoice, ErrInvoiceAlreadyCanceled
case ContractSettled:
return &invoice, ErrInvoiceAlreadySettled
}

invoice.Terms.PaymentPreimage = preimage

err = setSettleFields(settleIndex, invoiceNum, &invoice)
if err != nil {
return nil, err
}

var buf bytes.Buffer
if err := serializeInvoice(&buf, &invoice); err != nil {
return nil, err
Expand Down Expand Up @@ -995,6 +1109,9 @@ func cancelInvoice(invoices *bbolt.Bucket, invoiceNum []byte) (

invoice.Terms.State = ContractCanceled

// Set AmtPaid back to 0, in case the invoice was already accepted.
invoice.AmtPaid = 0

var buf bytes.Buffer
if err := serializeInvoice(&buf, &invoice); err != nil {
return nil, err
Expand Down
70 changes: 55 additions & 15 deletions contractcourt/htlc_incoming_contest_resolver.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package contractcourt

import (
"bytes"
"encoding/binary"
"fmt"
"io"

"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/invoices"

"github.com/btcsuite/btcutil"
"github.com/lightningnetwork/lnd/lntypes"
)
Expand Down Expand Up @@ -70,11 +72,18 @@ func (h *htlcIncomingContestResolver) Resolve() (ContractResolver, error) {
return nil, h.Checkpoint(h)
}

// applyPreimage is a helper function that will populate our internal
// tryApplyPreimage is a helper function that will populate our internal
// resolver with the preimage we learn of. This should be called once
// the preimage is revealed so the inner resolver can properly complete
// its duties.
applyPreimage := func(preimage lntypes.Preimage) {
// its duties. The boolean return value indicates whether the preimage
// was properly applied.
tryApplyPreimage := func(preimage lntypes.Preimage) bool {
// Check to see if this preimage matches our htlc.
if !preimage.Matches(h.payHash) {
return false
}

// Update htlcResolution with the matching preimage.
h.htlcResolution.Preimage = preimage

log.Infof("%T(%v): extracted preimage=%v from beacon!", h,
Expand All @@ -93,6 +102,8 @@ func (h *htlcIncomingContestResolver) Resolve() (ContractResolver, error) {
// preimage.
h.htlcResolution.SignedSuccessTx.TxIn[0].Witness[3] = preimage[:]
}

return true
}

// If the HTLC hasn't expired yet, then we may still be able to claim
Expand All @@ -112,33 +123,62 @@ func (h *htlcIncomingContestResolver) Resolve() (ContractResolver, error) {
blockEpochs.Cancel()
}()

// Create a buffered hodl chan to prevent deadlock.
hodlChan := make(chan interface{}, 1)

// Notify registry that we are potentially settling as exit hop
// on-chain, so that we will get a hodl event when a corresponding hodl
// invoice is settled.
event, err := h.Registry.NotifyExitHopHtlc(h.payHash, h.htlcAmt, hodlChan)
if err != nil && err != channeldb.ErrInvoiceNotFound {
return nil, err
}
defer h.Registry.HodlUnsubscribeAll(hodlChan)

// If the htlc can be settled directly, we can progress to the inner
// resolver immediately.
if event != nil && event.Preimage != nil {
if tryApplyPreimage(*event.Preimage) {
return &h.htlcSuccessResolver, nil
}
}

// With the epochs and preimage subscriptions initialized, we'll query
// to see if we already know the preimage.
preimage, ok := h.PreimageDB.LookupPreimage(h.payHash)
if ok {
// If we do, then this means we can claim the HTLC! However,
// we don't know how to ourselves, so we'll return our inner
// resolver which has the knowledge to do so.
applyPreimage(preimage)
return &h.htlcSuccessResolver, nil
if tryApplyPreimage(preimage) {
return &h.htlcSuccessResolver, nil
}
}

for {

select {
case preimage := <-preimageSubscription.WitnessUpdates:
// If this isn't our preimage, then we'll continue
// onwards.
hash := preimage.Hash()
preimageMatches := bytes.Equal(hash[:], h.payHash[:])
if !preimageMatches {
if !tryApplyPreimage(preimage) {
continue
}

// Otherwise, we've learned of the preimage! We'll add
// this information to our inner resolver, then return
// it so it can continue contract resolution.
applyPreimage(preimage)
// We've learned of the preimage and this information
// has been added to our inner resolver. We return it so
// it can continue contract resolution.
return &h.htlcSuccessResolver, nil

case hodlItem := <-hodlChan:
hodlEvent := hodlItem.(invoices.HodlEvent)

// Only process settle events.
if hodlEvent.Preimage == nil {
continue
}

if !tryApplyPreimage(*hodlEvent.Preimage) {
continue
}
return &h.htlcSuccessResolver, nil

case newBlock, ok := <-blockEpochs.Epochs:
Expand Down
Loading

0 comments on commit 32f2b04

Please sign in to comment.