mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-05-07 18:30:21 +02:00
htlcswitch: hodl invoice
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.
This commit is contained in:
parent
1f41a2abce
commit
32f2b047e8
@ -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)
|
||||
@ -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)
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
@ -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")
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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 (
|
||||
@ -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.
|
||||
@ -111,6 +122,8 @@ func (c ContractState) String() string {
|
||||
return "Settled"
|
||||
case ContractCanceled:
|
||||
return "Canceled"
|
||||
case ContractAccepted:
|
||||
return "Accepted"
|
||||
}
|
||||
|
||||
return "Unknown"
|
||||
@ -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
|
||||
@ -644,7 +660,7 @@ func (d *DB) SettleInvoice(paymentHash [32]byte,
|
||||
return ErrInvoiceNotFound
|
||||
}
|
||||
|
||||
settledInvoice, err = settleInvoice(
|
||||
settledInvoice, err = acceptOrSettleInvoice(
|
||||
invoices, settleIndex, invoiceNum, amtPaid,
|
||||
)
|
||||
|
||||
@ -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) {
|
||||
@ -932,7 +988,7 @@ 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)
|
||||
@ -940,32 +996,90 @@ func settleInvoice(invoices, settleIndex *bbolt.Bucket, invoiceNum []byte,
|
||||
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
|
||||
@ -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
|
||||
|
@ -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"
|
||||
)
|
||||
@ -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,
|
||||
@ -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
|
||||
@ -112,6 +123,26 @@ 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)
|
||||
@ -119,26 +150,35 @@ func (h *htlcIncomingContestResolver) Resolve() (ContractResolver, error) {
|
||||
// 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:
|
||||
|
@ -180,8 +180,11 @@ func (h *htlcSuccessResolver) Resolve() (ContractResolver, error) {
|
||||
// With the HTLC claimed, we can attempt to settle its
|
||||
// corresponding invoice if we were the original destination. As
|
||||
// the htlc is already settled at this point, we don't need to
|
||||
// process the result.
|
||||
_, err = h.Registry.NotifyExitHopHtlc(h.payHash, h.htlcAmt)
|
||||
// read on the hodl channel.
|
||||
hodlChan := make(chan interface{}, 1)
|
||||
_, err = h.Registry.NotifyExitHopHtlc(
|
||||
h.payHash, h.htlcAmt, hodlChan,
|
||||
)
|
||||
if err != nil && err != channeldb.ErrInvoiceNotFound {
|
||||
log.Errorf("Unable to settle invoice with payment "+
|
||||
"hash %x: %v", h.payHash, err)
|
||||
@ -254,8 +257,10 @@ func (h *htlcSuccessResolver) Resolve() (ContractResolver, error) {
|
||||
|
||||
// With the HTLC claimed, we can attempt to settle its corresponding
|
||||
// invoice if we were the original destination. As the htlc is already
|
||||
// settled at this point, we don't need to read the result.
|
||||
_, err = h.Registry.NotifyExitHopHtlc(h.payHash, h.htlcAmt)
|
||||
// settled at this point, we don't need to read on the hodl
|
||||
// channel.
|
||||
hodlChan := make(chan interface{}, 1)
|
||||
_, err = h.Registry.NotifyExitHopHtlc(h.payHash, h.htlcAmt, hodlChan)
|
||||
if err != nil && err != channeldb.ErrInvoiceNotFound {
|
||||
log.Errorf("Unable to settle invoice with payment "+
|
||||
"hash %x: %v", h.payHash, err)
|
||||
|
@ -21,13 +21,20 @@ type InvoiceDatabase interface {
|
||||
// NotifyExitHopHtlc attempts to mark an invoice as settled. If the
|
||||
// invoice is a debug invoice, then this method is a noop as debug
|
||||
// invoices are never fully settled. The return value describes how the
|
||||
// htlc should be resolved.
|
||||
NotifyExitHopHtlc(rhash lntypes.Hash, amt lnwire.MilliSatoshi) (
|
||||
*invoices.HodlEvent, error)
|
||||
// htlc should be resolved. If the htlc cannot be resolved immediately,
|
||||
// the resolution is sent on the passed in hodlChan later.
|
||||
NotifyExitHopHtlc(payHash lntypes.Hash, paidAmount lnwire.MilliSatoshi,
|
||||
hodlChan chan<- interface{}) (*invoices.HodlEvent, error)
|
||||
|
||||
// CancelInvoice attempts to cancel the invoice corresponding to the
|
||||
// passed payment hash.
|
||||
CancelInvoice(payHash lntypes.Hash) error
|
||||
|
||||
// SettleHodlInvoice settles a hold invoice.
|
||||
SettleHodlInvoice(preimage lntypes.Preimage) error
|
||||
|
||||
// HodlUnsubscribeAll unsubscribes from all hodl events.
|
||||
HodlUnsubscribeAll(subscriber chan<- interface{})
|
||||
}
|
||||
|
||||
// ChannelLink is an interface which represents the subsystem for managing the
|
||||
|
@ -21,6 +21,7 @@ import (
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
"github.com/lightningnetwork/lnd/lnwallet"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/lightningnetwork/lnd/queue"
|
||||
"github.com/lightningnetwork/lnd/ticker"
|
||||
)
|
||||
|
||||
@ -345,6 +346,14 @@ type channelLink struct {
|
||||
|
||||
sync.RWMutex
|
||||
|
||||
// hodlQueue is used to receive exit hop htlc resolutions from invoice
|
||||
// registry.
|
||||
hodlQueue *queue.ConcurrentQueue
|
||||
|
||||
// hodlMap stores a list of htlc data structs per hash. It allows
|
||||
// resolving those htlcs when we receive a message on hodlQueue.
|
||||
hodlMap map[lntypes.Hash][]hodlHtlc
|
||||
|
||||
wg sync.WaitGroup
|
||||
quit chan struct{}
|
||||
}
|
||||
@ -368,6 +377,8 @@ func NewChannelLink(cfg ChannelLinkConfig,
|
||||
logCommitTimer: time.NewTimer(300 * time.Millisecond),
|
||||
overflowQueue: newPacketQueue(input.MaxHTLCNumber / 2),
|
||||
htlcUpdates: make(chan []channeldb.HTLC),
|
||||
hodlMap: make(map[lntypes.Hash][]hodlHtlc),
|
||||
hodlQueue: queue.NewConcurrentQueue(10),
|
||||
quit: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
@ -391,6 +402,7 @@ func (l *channelLink) Start() error {
|
||||
|
||||
l.mailBox.ResetMessages()
|
||||
l.overflowQueue.Start()
|
||||
l.hodlQueue.Start()
|
||||
|
||||
// Before launching the htlcManager messages, revert any circuits that
|
||||
// were marked open in the switch's circuit map, but did not make it
|
||||
@ -456,12 +468,17 @@ func (l *channelLink) Stop() {
|
||||
|
||||
log.Infof("ChannelLink(%v) is stopping", l)
|
||||
|
||||
// As the link is stopping, we are no longer interested in hodl events
|
||||
// coming from the invoice registry.
|
||||
l.cfg.Registry.HodlUnsubscribeAll(l.hodlQueue.ChanIn())
|
||||
|
||||
if l.cfg.ChainEvents.Cancel != nil {
|
||||
l.cfg.ChainEvents.Cancel()
|
||||
}
|
||||
|
||||
l.updateFeeTimer.Stop()
|
||||
l.overflowQueue.Stop()
|
||||
l.hodlQueue.Stop()
|
||||
|
||||
close(l.quit)
|
||||
l.wg.Wait()
|
||||
@ -1065,12 +1082,75 @@ out:
|
||||
case msg := <-l.upstream:
|
||||
l.handleUpstreamMsg(msg)
|
||||
|
||||
// A hodl event is received. This means that we now have a
|
||||
// resolution for a previously accepted htlc.
|
||||
case hodlItem := <-l.hodlQueue.ChanOut():
|
||||
hodlEvent := hodlItem.(invoices.HodlEvent)
|
||||
err := l.processHodlQueue(hodlEvent)
|
||||
if err != nil {
|
||||
l.fail(LinkFailureError{code: ErrInternalError},
|
||||
fmt.Sprintf("process hodl queue: %v",
|
||||
err.Error()),
|
||||
)
|
||||
break out
|
||||
}
|
||||
|
||||
case <-l.quit:
|
||||
break out
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// processHodlQueue processes a received hodl event and continues reading from
|
||||
// the hodl queue until no more events remain. When this function returns
|
||||
// without an error, the commit tx should be updated.
|
||||
func (l *channelLink) processHodlQueue(firstHodlEvent invoices.HodlEvent) error {
|
||||
// Try to read all waiting resolution messages, so that they can all be
|
||||
// processed in a single commitment tx update.
|
||||
hodlEvent := firstHodlEvent
|
||||
loop:
|
||||
for {
|
||||
if err := l.processHodlMapEvent(hodlEvent); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
select {
|
||||
case item := <-l.hodlQueue.ChanOut():
|
||||
hodlEvent = item.(invoices.HodlEvent)
|
||||
default:
|
||||
break loop
|
||||
}
|
||||
}
|
||||
|
||||
// Update the commitment tx.
|
||||
if err := l.updateCommitTx(); err != nil {
|
||||
return fmt.Errorf("unable to update commitment: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// processHodlMapEvent resolves stored hodl htlcs based using the information in
|
||||
// hodlEvent.
|
||||
func (l *channelLink) processHodlMapEvent(hodlEvent invoices.HodlEvent) error {
|
||||
// Lookup all hodl htlcs that can be failed or settled with this event.
|
||||
// The hodl htlc must be present in the map.
|
||||
hash := hodlEvent.Hash
|
||||
hodlHtlcs, ok := l.hodlMap[hash]
|
||||
if !ok {
|
||||
return fmt.Errorf("hodl htlc not found: %v", hash)
|
||||
}
|
||||
|
||||
if err := l.processHodlEvent(hodlEvent, hodlHtlcs...); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Clean up hodl map.
|
||||
delete(l.hodlMap, hash)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// processHodlEvent applies a received hodl event to the provided htlc. When
|
||||
// this function returns without an error, the commit tx should be updated.
|
||||
func (l *channelLink) processHodlEvent(hodlEvent invoices.HodlEvent,
|
||||
@ -2620,19 +2700,6 @@ func (l *channelLink) processExitHop(pd *lnwallet.PaymentDescriptor,
|
||||
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
|
||||
// simplify failure recovery.
|
||||
//
|
||||
@ -2729,22 +2796,31 @@ func (l *channelLink) processExitHop(pd *lnwallet.PaymentDescriptor,
|
||||
// after this, this code will be re-executed after restart. We will
|
||||
// receive back a resolution event.
|
||||
event, err := l.cfg.Registry.NotifyExitHopHtlc(
|
||||
invoiceHash, pd.Amount,
|
||||
invoiceHash, pd.Amount, l.hodlQueue.ChanIn(),
|
||||
)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// Process the received resolution.
|
||||
// Create a hodlHtlc struct and decide either resolved now or later.
|
||||
htlc := hodlHtlc{
|
||||
pd: pd,
|
||||
obfuscator: obfuscator,
|
||||
}
|
||||
|
||||
if event == nil {
|
||||
// Save payment descriptor for future reference.
|
||||
hodlHtlcs := l.hodlMap[invoiceHash]
|
||||
l.hodlMap[invoiceHash] = append(hodlHtlcs, htlc)
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Process the received resolution.
|
||||
err = l.processHodlEvent(*event, htlc)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
|
@ -759,18 +759,23 @@ func (i *mockInvoiceRegistry) LookupInvoice(rHash lntypes.Hash) (channeldb.Invoi
|
||||
return i.registry.LookupInvoice(rHash)
|
||||
}
|
||||
|
||||
func (i *mockInvoiceRegistry) NotifyExitHopHtlc(rhash lntypes.Hash,
|
||||
amt lnwire.MilliSatoshi) (*invoices.HodlEvent, error) {
|
||||
func (i *mockInvoiceRegistry) SettleHodlInvoice(preimage lntypes.Preimage) error {
|
||||
return i.registry.SettleHodlInvoice(preimage)
|
||||
}
|
||||
|
||||
event, err := i.registry.NotifyExitHopHtlc(rhash, amt)
|
||||
func (i *mockInvoiceRegistry) NotifyExitHopHtlc(rhash lntypes.Hash,
|
||||
amt lnwire.MilliSatoshi, hodlChan chan<- interface{}) (
|
||||
*invoices.HodlEvent, error) {
|
||||
|
||||
event, err := i.registry.NotifyExitHopHtlc(rhash, amt, hodlChan)
|
||||
if err != nil {
|
||||
return event, err
|
||||
return nil, err
|
||||
}
|
||||
if i.settleChan != nil {
|
||||
i.settleChan <- rhash
|
||||
}
|
||||
|
||||
return event, err
|
||||
return event, nil
|
||||
}
|
||||
|
||||
func (i *mockInvoiceRegistry) CancelInvoice(payHash lntypes.Hash) error {
|
||||
@ -784,6 +789,10 @@ func (i *mockInvoiceRegistry) AddInvoice(invoice channeldb.Invoice,
|
||||
return err
|
||||
}
|
||||
|
||||
func (i *mockInvoiceRegistry) HodlUnsubscribeAll(subscriber chan<- interface{}) {
|
||||
i.registry.HodlUnsubscribeAll(subscriber)
|
||||
}
|
||||
|
||||
var _ InvoiceDatabase = (*mockInvoiceRegistry)(nil)
|
||||
|
||||
type mockSigner struct {
|
||||
|
@ -62,6 +62,14 @@ type InvoiceRegistry struct {
|
||||
// value from the payment request.
|
||||
decodeFinalCltvExpiry func(invoice string) (uint32, error)
|
||||
|
||||
// subscriptions is a map from a payment hash to a list of subscribers.
|
||||
// It is used for efficient notification of links.
|
||||
hodlSubscriptions map[lntypes.Hash]map[chan<- interface{}]struct{}
|
||||
|
||||
// reverseSubscriptions tracks hashes subscribed to per subscriber. This
|
||||
// is used to unsubscribe from all hashes efficiently.
|
||||
hodlReverseSubscriptions map[chan<- interface{}]map[lntypes.Hash]struct{}
|
||||
|
||||
wg sync.WaitGroup
|
||||
quit chan struct{}
|
||||
}
|
||||
@ -82,6 +90,8 @@ func NewRegistry(cdb *channeldb.DB, decodeFinalCltvExpiry func(invoice string) (
|
||||
newSingleSubscriptions: make(chan *SingleInvoiceSubscription),
|
||||
subscriptionCancels: make(chan uint32),
|
||||
invoiceEvents: make(chan *invoiceEvent, 100),
|
||||
hodlSubscriptions: make(map[lntypes.Hash]map[chan<- interface{}]struct{}),
|
||||
hodlReverseSubscriptions: make(map[chan<- interface{}]map[lntypes.Hash]struct{}),
|
||||
decodeFinalCltvExpiry: decodeFinalCltvExpiry,
|
||||
quit: make(chan struct{}),
|
||||
}
|
||||
@ -171,8 +181,10 @@ func (i *InvoiceRegistry) invoiceEventNotifier() {
|
||||
// dispatch notifications to all registered clients.
|
||||
case event := <-i.invoiceEvents:
|
||||
// For backwards compatibility, do not notify all
|
||||
// invoice subscribers of cancel events.
|
||||
if event.state != channeldb.ContractCanceled {
|
||||
// invoice subscribers of cancel and accept events.
|
||||
if event.state != channeldb.ContractCanceled &&
|
||||
event.state != channeldb.ContractAccepted {
|
||||
|
||||
i.dispatchToClients(event)
|
||||
}
|
||||
i.dispatchToSingleClients(event)
|
||||
@ -449,8 +461,17 @@ func (i *InvoiceRegistry) LookupInvoice(rHash lntypes.Hash) (channeldb.Invoice,
|
||||
// NotifyExitHopHtlc attempts to mark an invoice as settled. If the invoice is a
|
||||
// debug invoice, then this method is a noop as debug invoices are never fully
|
||||
// settled. The return value describes how the htlc should be resolved.
|
||||
//
|
||||
// When the preimage of the invoice is not yet known (hodl invoice), this
|
||||
// function moves the invoice to the accepted state. When SettleHoldInvoice is
|
||||
// called later, a resolution message will be send back to the caller via the
|
||||
// provided hodlChan. Invoice registry sends on this channel what action needs
|
||||
// to be taken on the htlc (settle or cancel). The caller needs to ensure that
|
||||
// the channel is either buffered or received on from another goroutine to
|
||||
// prevent deadlock.
|
||||
func (i *InvoiceRegistry) NotifyExitHopHtlc(rHash lntypes.Hash,
|
||||
amtPaid lnwire.MilliSatoshi) (*HodlEvent, error) {
|
||||
amtPaid lnwire.MilliSatoshi, hodlChan chan<- interface{}) (
|
||||
*HodlEvent, error) {
|
||||
|
||||
i.Lock()
|
||||
defer i.Unlock()
|
||||
@ -474,7 +495,7 @@ func (i *InvoiceRegistry) NotifyExitHopHtlc(rHash lntypes.Hash,
|
||||
|
||||
// If this isn't a debug invoice, then we'll attempt to settle an
|
||||
// invoice matching this rHash on disk (if one exists).
|
||||
invoice, err := i.cdb.SettleInvoice(rHash, amtPaid)
|
||||
invoice, err := i.cdb.AcceptOrSettleInvoice(rHash, amtPaid)
|
||||
switch err {
|
||||
|
||||
// If invoice is already settled, settle htlc. This means we accept more
|
||||
@ -486,14 +507,55 @@ func (i *InvoiceRegistry) NotifyExitHopHtlc(rHash lntypes.Hash,
|
||||
case channeldb.ErrInvoiceAlreadyCanceled:
|
||||
return createEvent(nil), nil
|
||||
|
||||
// If this call settled the invoice, settle the htlc.
|
||||
// If invoice is already accepted, add this htlc to the list of
|
||||
// subscribers.
|
||||
case channeldb.ErrInvoiceAlreadyAccepted:
|
||||
i.hodlSubscribe(hodlChan, rHash)
|
||||
return nil, nil
|
||||
|
||||
// If this call settled the invoice, settle the htlc. Otherwise
|
||||
// subscribe for a future hodl event.
|
||||
case nil:
|
||||
i.notifyClients(rHash, invoice, invoice.Terms.State)
|
||||
return createEvent(&invoice.Terms.PaymentPreimage), nil
|
||||
switch invoice.Terms.State {
|
||||
case channeldb.ContractSettled:
|
||||
return createEvent(&invoice.Terms.PaymentPreimage), nil
|
||||
case channeldb.ContractAccepted:
|
||||
// Subscribe to updates to this invoice.
|
||||
i.hodlSubscribe(hodlChan, rHash)
|
||||
return nil, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unexpected invoice state %v",
|
||||
invoice.Terms.State)
|
||||
}
|
||||
|
||||
default:
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// SettleHodlInvoice sets the preimage of a hodl invoice.
|
||||
func (i *InvoiceRegistry) SettleHodlInvoice(preimage lntypes.Preimage) error {
|
||||
i.Lock()
|
||||
defer i.Unlock()
|
||||
|
||||
invoice, err := i.cdb.SettleHoldInvoice(preimage)
|
||||
if err != nil {
|
||||
log.Errorf("Invoice SetPreimage %v: %v", preimage, err)
|
||||
return err
|
||||
}
|
||||
|
||||
// If another error occurred, return that.
|
||||
return nil, err
|
||||
hash := preimage.Hash()
|
||||
log.Infof("Notifying clients of set preimage to %v",
|
||||
invoice.Terms.PaymentPreimage)
|
||||
|
||||
i.notifyHodlSubscribers(HodlEvent{
|
||||
Hash: hash,
|
||||
Preimage: &preimage,
|
||||
})
|
||||
i.notifyClients(hash, invoice, invoice.Terms.State)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CancelInvoice attempts to cancel the invoice corresponding to the passed
|
||||
@ -512,13 +574,14 @@ func (i *InvoiceRegistry) CancelInvoice(payHash lntypes.Hash) error {
|
||||
log.Debugf("Invoice %v already canceled", payHash)
|
||||
return nil
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Infof("Invoice %v canceled", payHash)
|
||||
|
||||
i.notifyHodlSubscribers(HodlEvent{
|
||||
Hash: payHash,
|
||||
})
|
||||
i.notifyClients(payHash, invoice, channeldb.ContractCanceled)
|
||||
|
||||
return nil
|
||||
@ -770,3 +833,60 @@ func (i *InvoiceRegistry) SubscribeSingleInvoice(
|
||||
|
||||
return client
|
||||
}
|
||||
|
||||
// notifyHodlSubscribers sends out the hodl event to all current subscribers.
|
||||
func (i *InvoiceRegistry) notifyHodlSubscribers(hodlEvent HodlEvent) {
|
||||
subscribers, ok := i.hodlSubscriptions[hodlEvent.Hash]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
// Notify all interested subscribers and remove subscription from both
|
||||
// maps. The subscription can be removed as there only ever will be a
|
||||
// single resolution for each hash.
|
||||
for subscriber := range subscribers {
|
||||
select {
|
||||
case subscriber <- hodlEvent:
|
||||
case <-i.quit:
|
||||
return
|
||||
}
|
||||
|
||||
delete(i.hodlReverseSubscriptions[subscriber], hodlEvent.Hash)
|
||||
}
|
||||
|
||||
delete(i.hodlSubscriptions, hodlEvent.Hash)
|
||||
}
|
||||
|
||||
// hodlSubscribe adds a new invoice subscription.
|
||||
func (i *InvoiceRegistry) hodlSubscribe(subscriber chan<- interface{},
|
||||
hash lntypes.Hash) {
|
||||
|
||||
log.Debugf("Hodl subscribe for %v", hash)
|
||||
|
||||
subscriptions, ok := i.hodlSubscriptions[hash]
|
||||
if !ok {
|
||||
subscriptions = make(map[chan<- interface{}]struct{})
|
||||
i.hodlSubscriptions[hash] = subscriptions
|
||||
}
|
||||
subscriptions[subscriber] = struct{}{}
|
||||
|
||||
reverseSubscriptions, ok := i.hodlReverseSubscriptions[subscriber]
|
||||
if !ok {
|
||||
reverseSubscriptions = make(map[lntypes.Hash]struct{})
|
||||
i.hodlReverseSubscriptions[subscriber] = reverseSubscriptions
|
||||
}
|
||||
reverseSubscriptions[hash] = struct{}{}
|
||||
}
|
||||
|
||||
// HodlUnsubscribeAll cancels the subscription.
|
||||
func (i *InvoiceRegistry) HodlUnsubscribeAll(subscriber chan<- interface{}) {
|
||||
i.Lock()
|
||||
defer i.Unlock()
|
||||
|
||||
hashes := i.hodlReverseSubscriptions[subscriber]
|
||||
for hash := range hashes {
|
||||
delete(i.hodlSubscriptions[hash], subscriber)
|
||||
}
|
||||
|
||||
delete(i.hodlReverseSubscriptions, subscriber)
|
||||
}
|
||||
|
@ -117,9 +117,11 @@ func TestSettleInvoice(t *testing.T) {
|
||||
t.Fatal("no update received")
|
||||
}
|
||||
|
||||
hodlChan := make(chan interface{}, 1)
|
||||
|
||||
// Settle invoice with a slightly higher amount.
|
||||
amtPaid := lnwire.MilliSatoshi(100500)
|
||||
_, err = registry.NotifyExitHopHtlc(hash, amtPaid)
|
||||
_, err = registry.NotifyExitHopHtlc(hash, amtPaid, hodlChan)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -151,13 +153,13 @@ func TestSettleInvoice(t *testing.T) {
|
||||
}
|
||||
|
||||
// Try to settle again.
|
||||
_, err = registry.NotifyExitHopHtlc(hash, amtPaid)
|
||||
_, err = registry.NotifyExitHopHtlc(hash, amtPaid, hodlChan)
|
||||
if err != nil {
|
||||
t.Fatal("expected duplicate settle to succeed")
|
||||
}
|
||||
|
||||
// Try to settle again with a different amount.
|
||||
_, err = registry.NotifyExitHopHtlc(hash, amtPaid+600)
|
||||
_, err = registry.NotifyExitHopHtlc(hash, amtPaid+600, hodlChan)
|
||||
if err != nil {
|
||||
t.Fatal("expected duplicate settle to succeed")
|
||||
}
|
||||
@ -176,6 +178,13 @@ func TestSettleInvoice(t *testing.T) {
|
||||
if err != channeldb.ErrInvoiceAlreadySettled {
|
||||
t.Fatal("expected cancelation of a settled invoice to fail")
|
||||
}
|
||||
|
||||
// As this is a direct sette, we expect nothing on the hodl chan.
|
||||
select {
|
||||
case <-hodlChan:
|
||||
t.Fatal("unexpected event")
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
// TestCancelInvoice tests cancelation of an invoice and related notifications.
|
||||
@ -264,7 +273,8 @@ func TestCancelInvoice(t *testing.T) {
|
||||
|
||||
// Notify arrival of a new htlc paying to this invoice. This should
|
||||
// succeed.
|
||||
event, err := registry.NotifyExitHopHtlc(hash, amt)
|
||||
hodlChan := make(chan interface{})
|
||||
event, err := registry.NotifyExitHopHtlc(hash, amt, hodlChan)
|
||||
if err != nil {
|
||||
t.Fatal("expected settlement of a canceled invoice to succeed")
|
||||
}
|
||||
@ -274,6 +284,134 @@ func TestCancelInvoice(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestHoldInvoice tests settling of a hold invoice and related notifications.
|
||||
func TestHoldInvoice(t *testing.T) {
|
||||
defer timeout(t)()
|
||||
|
||||
cdb, cleanup, err := newDB()
|
||||
defer cleanup()
|
||||
|
||||
// Instantiate and start the invoice registry.
|
||||
registry := NewRegistry(cdb, decodeExpiry)
|
||||
|
||||
err = registry.Start()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer registry.Stop()
|
||||
|
||||
allSubscriptions := registry.SubscribeNotifications(0, 0)
|
||||
defer allSubscriptions.Cancel()
|
||||
|
||||
// Subscribe to the not yet existing invoice.
|
||||
subscription := registry.SubscribeSingleInvoice(hash)
|
||||
defer subscription.Cancel()
|
||||
|
||||
if subscription.hash != hash {
|
||||
t.Fatalf("expected subscription for provided hash")
|
||||
}
|
||||
|
||||
// Add the invoice.
|
||||
invoice := &channeldb.Invoice{
|
||||
Terms: channeldb.ContractTerm{
|
||||
PaymentPreimage: channeldb.UnknownPreimage,
|
||||
Value: lnwire.MilliSatoshi(100000),
|
||||
},
|
||||
}
|
||||
|
||||
_, err = registry.AddInvoice(invoice, hash)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// We expect the open state to be sent to the single invoice subscriber.
|
||||
update := <-subscription.Updates
|
||||
if update.Terms.State != channeldb.ContractOpen {
|
||||
t.Fatalf("expected state ContractOpen, but got %v",
|
||||
update.Terms.State)
|
||||
}
|
||||
|
||||
// We expect a new invoice notification to be sent out.
|
||||
newInvoice := <-allSubscriptions.NewInvoices
|
||||
if newInvoice.Terms.State != channeldb.ContractOpen {
|
||||
t.Fatalf("expected state ContractOpen, but got %v",
|
||||
newInvoice.Terms.State)
|
||||
}
|
||||
|
||||
// Use slightly higher amount for accept/settle.
|
||||
amtPaid := lnwire.MilliSatoshi(100500)
|
||||
|
||||
hodlChan := make(chan interface{}, 1)
|
||||
|
||||
// NotifyExitHopHtlc without a preimage present in the invoice registry
|
||||
// should be possible.
|
||||
event, err := registry.NotifyExitHopHtlc(hash, amtPaid, hodlChan)
|
||||
if err != nil {
|
||||
t.Fatalf("expected settle to succeed but got %v", err)
|
||||
}
|
||||
if event != nil {
|
||||
t.Fatalf("unexpect direct settle")
|
||||
}
|
||||
|
||||
// Test idempotency.
|
||||
event, err = registry.NotifyExitHopHtlc(hash, amtPaid, hodlChan)
|
||||
if err != nil {
|
||||
t.Fatalf("expected settle to succeed but got %v", err)
|
||||
}
|
||||
if event != nil {
|
||||
t.Fatalf("unexpect direct settle")
|
||||
}
|
||||
|
||||
// We expect the accepted state to be sent to the single invoice
|
||||
// subscriber. For all invoice subscribers, we don't expect an update.
|
||||
// Those only get notified on settle.
|
||||
update = <-subscription.Updates
|
||||
if update.Terms.State != channeldb.ContractAccepted {
|
||||
t.Fatalf("expected state ContractAccepted, but got %v",
|
||||
update.Terms.State)
|
||||
}
|
||||
if update.AmtPaid != amtPaid {
|
||||
t.Fatal("invoice AmtPaid incorrect")
|
||||
}
|
||||
|
||||
// Settling with preimage should succeed.
|
||||
err = registry.SettleHodlInvoice(preimage)
|
||||
if err != nil {
|
||||
t.Fatal("expected set preimage to succeed")
|
||||
}
|
||||
|
||||
hodlEvent := (<-hodlChan).(HodlEvent)
|
||||
if *hodlEvent.Preimage != preimage {
|
||||
t.Fatal("unexpected preimage in hodl event")
|
||||
}
|
||||
|
||||
// We expect a settled notification to be sent out for both all and
|
||||
// single invoice subscribers.
|
||||
settledInvoice := <-allSubscriptions.SettledInvoices
|
||||
if settledInvoice.Terms.State != channeldb.ContractSettled {
|
||||
t.Fatalf("expected state ContractSettled, but got %v",
|
||||
settledInvoice.Terms.State)
|
||||
}
|
||||
|
||||
update = <-subscription.Updates
|
||||
if update.Terms.State != channeldb.ContractSettled {
|
||||
t.Fatalf("expected state ContractSettled, but got %v",
|
||||
update.Terms.State)
|
||||
}
|
||||
|
||||
// Idempotency.
|
||||
err = registry.SettleHodlInvoice(preimage)
|
||||
if err != channeldb.ErrInvoiceAlreadySettled {
|
||||
t.Fatalf("expected ErrInvoiceAlreadySettled but got %v", err)
|
||||
}
|
||||
|
||||
// Try to cancel.
|
||||
err = registry.CancelInvoice(hash)
|
||||
if err == nil {
|
||||
t.Fatal("expected cancelation of a settled invoice to fail")
|
||||
}
|
||||
}
|
||||
|
||||
func newDB() (*channeldb.DB, func(), error) {
|
||||
// First, create a temporary directory to be used for the duration of
|
||||
// this test.
|
||||
|
26
invoices/utils_test.go
Normal file
26
invoices/utils_test.go
Normal file
@ -0,0 +1,26 @@
|
||||
package invoices
|
||||
|
||||
import (
|
||||
"os"
|
||||
"runtime/pprof"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// timeout implements a test level timeout.
|
||||
func timeout(t *testing.T) func() {
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
select {
|
||||
case <-time.After(5 * time.Second):
|
||||
pprof.Lookup("goroutine").WriteTo(os.Stdout, 1)
|
||||
|
||||
panic("test timeout")
|
||||
case <-done:
|
||||
}
|
||||
}()
|
||||
|
||||
return func() {
|
||||
close(done)
|
||||
}
|
||||
}
|
@ -60,6 +60,8 @@ func CreateRPCInvoice(invoice *channeldb.Invoice,
|
||||
state = lnrpc.Invoice_SETTLED
|
||||
case channeldb.ContractCanceled:
|
||||
state = lnrpc.Invoice_CANCELED
|
||||
case channeldb.ContractAccepted:
|
||||
state = lnrpc.Invoice_ACCEPTED
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown invoice state %v",
|
||||
invoice.Terms.State)
|
||||
|
1144
lnrpc/rpc.pb.go
1144
lnrpc/rpc.pb.go
File diff suppressed because it is too large
Load Diff
@ -1892,6 +1892,7 @@ message Invoice {
|
||||
OPEN = 0;
|
||||
SETTLED = 1;
|
||||
CANCELED = 2;
|
||||
ACCEPTED = 3;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1162,7 +1162,8 @@
|
||||
"enum": [
|
||||
"OPEN",
|
||||
"SETTLED",
|
||||
"CANCELED"
|
||||
"CANCELED",
|
||||
"ACCEPTED"
|
||||
],
|
||||
"default": "OPEN"
|
||||
},
|
||||
|
Loading…
x
Reference in New Issue
Block a user