multi: replace htlcResolution with an interface

This commit repalces the htlcResolution struct with an interface.
This interface is implemeted by failure, settle and accept resolution
structs. Only settles and fails are exported because the existing
code that handles htlc resolutions uses a nil resolution to indicate
that a htlc was accepted. The accept resolution is used internally
to report on the resolution result of the accepted htlc, but a nil
resolution is surfaced. Further refactoring of all the functions
that call NotifyExitHopHtlc to handle a htlc accept case (rather than
having a nil check) is required.
This commit is contained in:
carla 2020-02-06 19:35:10 +02:00
parent 2cd26d7556
commit 2569b4d08a
No known key found for this signature in database
GPG Key ID: 4CA7FE54A6213C91
11 changed files with 477 additions and 257 deletions

View File

@ -4,6 +4,7 @@ import (
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"errors" "errors"
"fmt"
"io" "io"
"github.com/btcsuite/btcutil" "github.com/btcsuite/btcutil"
@ -172,7 +173,23 @@ func (h *htlcIncomingContestResolver) Resolve() (ContractResolver, error) {
processHtlcResolution := func(e invoices.HtlcResolution) ( processHtlcResolution := func(e invoices.HtlcResolution) (
ContractResolver, error) { ContractResolver, error) {
if e.Preimage == nil { // Take action based on the type of resolution we have
// received.
switch resolution := e.(type) {
// If the htlc resolution was a settle, apply the
// preimage and return a success resolver.
case *invoices.HtlcSettleResolution:
err := applyPreimage(resolution.Preimage)
if err != nil {
return nil, err
}
return &h.htlcSuccessResolver, nil
// If the htlc was failed, mark the htlc as
// resolved.
case *invoices.HtlcFailResolution:
log.Infof("%T(%v): Exit hop HTLC canceled "+ log.Infof("%T(%v): Exit hop HTLC canceled "+
"(expiry=%v, height=%v), abandoning", h, "(expiry=%v, height=%v), abandoning", h,
h.htlcResolution.ClaimOutpoint, h.htlcResolution.ClaimOutpoint,
@ -180,13 +197,13 @@ func (h *htlcIncomingContestResolver) Resolve() (ContractResolver, error) {
h.resolved = true h.resolved = true
return nil, h.Checkpoint(h) return nil, h.Checkpoint(h)
}
if err := applyPreimage(*e.Preimage); err != nil { // Error if the resolution type is unknown, we are only
return nil, err // expecting settles and fails.
default:
return nil, fmt.Errorf("unknown resolution"+
" type: %v", e)
} }
return &h.htlcSuccessResolver, nil
} }
// Create a buffered hodl chan to prevent deadlock. // Create a buffered hodl chan to prevent deadlock.
@ -211,14 +228,29 @@ func (h *htlcIncomingContestResolver) Resolve() (ContractResolver, error) {
defer h.Registry.HodlUnsubscribeAll(hodlChan) defer h.Registry.HodlUnsubscribeAll(hodlChan)
// If the resolution is non-nil (indicating that a settle or cancel has // Take action based on the resolution we received. If the htlc was
// occurred), and the invoice is known to the registry (indicating that // settled, or a htlc for a known invoice failed we can resolve it
// the htlc is paying one of our invoices and is not a forward), try to // directly. If the resolution is nil, the htlc was neither accepted
// resolve it directly. // nor failed, so we cannot take action yet.
if resolution != nil && switch res := resolution.(type) {
resolution.Outcome != invoices.ResultInvoiceNotFound { case *invoices.HtlcFailResolution:
// In the case where the htlc failed, but the invoice was known
// to the registry, we can directly resolve the htlc.
if res.Outcome != invoices.ResultInvoiceNotFound {
return processHtlcResolution(resolution)
}
return processHtlcResolution(*resolution) // If we settled the htlc, we can resolve it.
case *invoices.HtlcSettleResolution:
return processHtlcResolution(resolution)
// If the resolution is nil, the htlc was neither settled nor failed so
// we cannot take action at present.
case nil:
default:
return nil, fmt.Errorf("unknown htlc resolution type: %T",
resolution)
} }
// With the epochs and preimage subscriptions initialized, we'll query // With the epochs and preimage subscriptions initialized, we'll query
@ -256,7 +288,6 @@ func (h *htlcIncomingContestResolver) Resolve() (ContractResolver, error) {
case hodlItem := <-hodlChan: case hodlItem := <-hodlChan:
htlcResolution := hodlItem.(invoices.HtlcResolution) htlcResolution := hodlItem.(invoices.HtlcResolution)
return processHtlcResolution(htlcResolution) return processHtlcResolution(htlcResolution)
case newBlock, ok := <-blockEpochs.Epochs: case newBlock, ok := <-blockEpochs.Epochs:

View File

@ -35,7 +35,7 @@ func TestHtlcIncomingResolverFwdPreimageKnown(t *testing.T) {
defer timeout(t)() defer timeout(t)()
ctx := newIncomingResolverTestContext(t) ctx := newIncomingResolverTestContext(t)
ctx.registry.notifyResolution = invoices.NewFailureResolution( ctx.registry.notifyResolution = invoices.NewFailResolution(
testResCircuitKey, testHtlcExpiry, testResCircuitKey, testHtlcExpiry,
invoices.ResultInvoiceNotFound, invoices.ResultInvoiceNotFound,
) )
@ -52,7 +52,7 @@ func TestHtlcIncomingResolverFwdContestedSuccess(t *testing.T) {
defer timeout(t)() defer timeout(t)()
ctx := newIncomingResolverTestContext(t) ctx := newIncomingResolverTestContext(t)
ctx.registry.notifyResolution = invoices.NewFailureResolution( ctx.registry.notifyResolution = invoices.NewFailResolution(
testResCircuitKey, testHtlcExpiry, testResCircuitKey, testHtlcExpiry,
invoices.ResultInvoiceNotFound, invoices.ResultInvoiceNotFound,
) )
@ -72,7 +72,7 @@ func TestHtlcIncomingResolverFwdContestedTimeout(t *testing.T) {
defer timeout(t)() defer timeout(t)()
ctx := newIncomingResolverTestContext(t) ctx := newIncomingResolverTestContext(t)
ctx.registry.notifyResolution = invoices.NewFailureResolution( ctx.registry.notifyResolution = invoices.NewFailResolution(
testResCircuitKey, testHtlcExpiry, testResCircuitKey, testHtlcExpiry,
invoices.ResultInvoiceNotFound, invoices.ResultInvoiceNotFound,
) )
@ -91,7 +91,7 @@ func TestHtlcIncomingResolverFwdTimeout(t *testing.T) {
defer timeout(t)() defer timeout(t)()
ctx := newIncomingResolverTestContext(t) ctx := newIncomingResolverTestContext(t)
ctx.registry.notifyResolution = invoices.NewFailureResolution( ctx.registry.notifyResolution = invoices.NewFailResolution(
testResCircuitKey, testHtlcExpiry, testResCircuitKey, testHtlcExpiry,
invoices.ResultInvoiceNotFound, invoices.ResultInvoiceNotFound,
) )
@ -139,7 +139,7 @@ func TestHtlcIncomingResolverExitCancel(t *testing.T) {
defer timeout(t)() defer timeout(t)()
ctx := newIncomingResolverTestContext(t) ctx := newIncomingResolverTestContext(t)
ctx.registry.notifyResolution = invoices.NewFailureResolution( ctx.registry.notifyResolution = invoices.NewFailResolution(
testResCircuitKey, testAcceptHeight, testResCircuitKey, testAcceptHeight,
invoices.ResultInvoiceAlreadyCanceled, invoices.ResultInvoiceAlreadyCanceled,
) )
@ -158,7 +158,7 @@ func TestHtlcIncomingResolverExitSettleHodl(t *testing.T) {
ctx.resolve() ctx.resolve()
notifyData := <-ctx.registry.notifyChan notifyData := <-ctx.registry.notifyChan
notifyData.hodlChan <- *invoices.NewSettleResolution( notifyData.hodlChan <- invoices.NewSettleResolution(
testResPreimage, testResCircuitKey, testAcceptHeight, testResPreimage, testResCircuitKey, testAcceptHeight,
invoices.ResultSettled, invoices.ResultSettled,
) )
@ -187,7 +187,7 @@ func TestHtlcIncomingResolverExitCancelHodl(t *testing.T) {
ctx := newIncomingResolverTestContext(t) ctx := newIncomingResolverTestContext(t)
ctx.resolve() ctx.resolve()
notifyData := <-ctx.registry.notifyChan notifyData := <-ctx.registry.notifyChan
notifyData.hodlChan <- *invoices.NewFailureResolution( notifyData.hodlChan <- invoices.NewFailResolution(
testResCircuitKey, testAcceptHeight, invoices.ResultCanceled, testResCircuitKey, testAcceptHeight, invoices.ResultCanceled,
) )

View File

@ -27,7 +27,7 @@ type Registry interface {
NotifyExitHopHtlc(payHash lntypes.Hash, paidAmount lnwire.MilliSatoshi, NotifyExitHopHtlc(payHash lntypes.Hash, paidAmount lnwire.MilliSatoshi,
expiry uint32, currentHeight int32, expiry uint32, currentHeight int32,
circuitKey channeldb.CircuitKey, hodlChan chan<- interface{}, circuitKey channeldb.CircuitKey, hodlChan chan<- interface{},
payload invoices.Payload) (*invoices.HtlcResolution, error) payload invoices.Payload) (invoices.HtlcResolution, error)
// HodlUnsubscribeAll unsubscribes from all htlc resolutions. // HodlUnsubscribeAll unsubscribes from all htlc resolutions.
HodlUnsubscribeAll(subscriber chan<- interface{}) HodlUnsubscribeAll(subscriber chan<- interface{})

View File

@ -18,13 +18,13 @@ type notifyExitHopData struct {
type mockRegistry struct { type mockRegistry struct {
notifyChan chan notifyExitHopData notifyChan chan notifyExitHopData
notifyErr error notifyErr error
notifyResolution *invoices.HtlcResolution notifyResolution invoices.HtlcResolution
} }
func (r *mockRegistry) NotifyExitHopHtlc(payHash lntypes.Hash, func (r *mockRegistry) NotifyExitHopHtlc(payHash lntypes.Hash,
paidAmount lnwire.MilliSatoshi, expiry uint32, currentHeight int32, paidAmount lnwire.MilliSatoshi, expiry uint32, currentHeight int32,
circuitKey channeldb.CircuitKey, hodlChan chan<- interface{}, circuitKey channeldb.CircuitKey, hodlChan chan<- interface{},
payload invoices.Payload) (*invoices.HtlcResolution, error) { payload invoices.Payload) (invoices.HtlcResolution, error) {
r.notifyChan <- notifyExitHopData{ r.notifyChan <- notifyExitHopData{
hodlChan: hodlChan, hodlChan: hodlChan,

View File

@ -27,7 +27,7 @@ type InvoiceDatabase interface {
NotifyExitHopHtlc(payHash lntypes.Hash, paidAmount lnwire.MilliSatoshi, NotifyExitHopHtlc(payHash lntypes.Hash, paidAmount lnwire.MilliSatoshi,
expiry uint32, currentHeight int32, expiry uint32, currentHeight int32,
circuitKey channeldb.CircuitKey, hodlChan chan<- interface{}, circuitKey channeldb.CircuitKey, hodlChan chan<- interface{},
payload invoices.Payload) (*invoices.HtlcResolution, error) payload invoices.Payload) (invoices.HtlcResolution, error)
// CancelInvoice attempts to cancel the invoice corresponding to the // CancelInvoice attempts to cancel the invoice corresponding to the
// passed payment hash. // passed payment hash.

View File

@ -1158,7 +1158,7 @@ loop:
for { for {
// Lookup all hodl htlcs that can be failed or settled with this event. // Lookup all hodl htlcs that can be failed or settled with this event.
// The hodl htlc must be present in the map. // The hodl htlc must be present in the map.
circuitKey := htlcResolution.CircuitKey circuitKey := htlcResolution.CircuitKey()
hodlHtlc, ok := l.hodlMap[circuitKey] hodlHtlc, ok := l.hodlMap[circuitKey]
if !ok { if !ok {
return fmt.Errorf("hodl htlc not found: %v", circuitKey) return fmt.Errorf("hodl htlc not found: %v", circuitKey)
@ -1193,46 +1193,61 @@ loop:
func (l *channelLink) processHtlcResolution(resolution invoices.HtlcResolution, func (l *channelLink) processHtlcResolution(resolution invoices.HtlcResolution,
htlc hodlHtlc) error { htlc hodlHtlc) error {
circuitKey := resolution.CircuitKey circuitKey := resolution.CircuitKey()
// Determine required action for the resolution. If the event's preimage is // Determine required action for the resolution based on the type of
// non-nil, the htlc must be settled. Otherwise, it should be canceled. // resolution we have received.
if resolution.Preimage != nil { switch res := resolution.(type) {
l.log.Debugf("received settle resolution for %v", circuitKey) // Settle htlcs that returned a settle resolution using the preimage
// in the resolution.
case *invoices.HtlcSettleResolution:
l.log.Debugf("received settle resolution for %v"+
"with outcome: %v", circuitKey, res.Outcome)
return l.settleHTLC( return l.settleHTLC(
*resolution.Preimage, htlc.pd.HtlcIndex, res.Preimage, htlc.pd.HtlcIndex,
htlc.pd.SourceRef, htlc.pd.SourceRef,
) )
// For htlc failures, we get the relevant failure message based
// on the failure resolution and then fail the htlc.
case *invoices.HtlcFailResolution:
l.log.Debugf("received cancel resolution for "+
"%v with outcome: %v", circuitKey, res.Outcome)
// Get the lnwire failure message based on the resolution
// result.
failure := getResolutionFailure(res, htlc.pd.Amount)
l.sendHTLCError(
htlc.pd.HtlcIndex, failure, htlc.obfuscator,
htlc.pd.SourceRef,
)
return nil
// Fail if we do not get a settle of fail resolution, since we
// are only expecting to handle settles and fails.
default:
return fmt.Errorf("unknown htlc resolution type: %T",
resolution)
} }
l.log.Debugf("received cancel resolution for %v with outcome: %v",
circuitKey, resolution.Outcome)
// Get the lnwire failure message based on the resolution result.
failure := getResolutionFailure(resolution, htlc.pd.Amount)
l.sendHTLCError(
htlc.pd.HtlcIndex, failure, htlc.obfuscator,
htlc.pd.SourceRef,
)
return nil
} }
// getResolutionFailure returns the wire message that a htlc resolution should // getResolutionFailure returns the wire message that a htlc resolution should
// be failed with. // be failed with.
func getResolutionFailure(resolution invoices.HtlcResolution, func getResolutionFailure(resolution *invoices.HtlcFailResolution,
amount lnwire.MilliSatoshi) lnwire.FailureMessage { amount lnwire.MilliSatoshi) lnwire.FailureMessage {
// If the resolution has been resolved as part of a MPP timeout, we need // If the resolution has been resolved as part of a MPP timeout,
// to fail the htlc with lnwire.FailMppTimeout. // we need to fail the htlc with lnwire.FailMppTimeout.
if resolution.Outcome == invoices.ResultMppTimeout { if resolution.Outcome == invoices.ResultMppTimeout {
return &lnwire.FailMPPTimeout{} return &lnwire.FailMPPTimeout{}
} }
// If the htlc is not a MPP timeout, we fail it with FailIncorrectDetails // If the htlc is not a MPP timeout, we fail it with
// This covers hodl cancels (which return it to avoid leaking information // FailIncorrectDetails. This error is sent for invoice payment
// and other invoice failures such as underpayment or expiry too soon. // failures such as underpayment/ expiry too soon and hodl invoices
// (which return FailIncorrectDetails to avoid leaking information).
return lnwire.NewFailIncorrectDetails( return lnwire.NewFailIncorrectDetails(
amount, uint32(resolution.AcceptHeight), amount, uint32(resolution.AcceptHeight),
) )
@ -2863,7 +2878,7 @@ func (l *channelLink) processExitHop(pd *lnwallet.PaymentDescriptor,
} }
// Process the received resolution. // Process the received resolution.
return l.processHtlcResolution(*event, htlc) return l.processHtlcResolution(event, htlc)
} }
// settleHTLC settles the HTLC on the channel. // settleHTLC settles the HTLC on the channel.

View File

@ -816,7 +816,7 @@ func (i *mockInvoiceRegistry) SettleHodlInvoice(preimage lntypes.Preimage) error
func (i *mockInvoiceRegistry) NotifyExitHopHtlc(rhash lntypes.Hash, func (i *mockInvoiceRegistry) NotifyExitHopHtlc(rhash lntypes.Hash,
amt lnwire.MilliSatoshi, expiry uint32, currentHeight int32, amt lnwire.MilliSatoshi, expiry uint32, currentHeight int32,
circuitKey channeldb.CircuitKey, hodlChan chan<- interface{}, circuitKey channeldb.CircuitKey, hodlChan chan<- interface{},
payload invoices.Payload) (*invoices.HtlcResolution, error) { payload invoices.Payload) (invoices.HtlcResolution, error) {
event, err := i.registry.NotifyExitHopHtlc( event, err := i.registry.NotifyExitHopHtlc(
rhash, amt, expiry, currentHeight, circuitKey, hodlChan, rhash, amt, expiry, currentHeight, circuitKey, hodlChan,

View File

@ -36,48 +36,6 @@ const (
DefaultHtlcHoldDuration = 120 * time.Second DefaultHtlcHoldDuration = 120 * time.Second
) )
// HtlcResolution describes how an htlc should be resolved. If the preimage
// field is set, the event indicates a settle event. If Preimage is nil, it is
// a cancel event.
type HtlcResolution struct {
// Preimage is the htlc preimage. Its value is nil in case of a cancel.
Preimage *lntypes.Preimage
// CircuitKey is the key of the htlc for which we have a resolution
// decision.
CircuitKey channeldb.CircuitKey
// AcceptHeight is the original height at which the htlc was accepted.
AcceptHeight int32
// Outcome indicates the outcome of the invoice registry update.
Outcome ResolutionResult
}
// NewFailureResolution returns a htlc failure resolution.
func NewFailureResolution(key channeldb.CircuitKey,
acceptHeight int32, outcome ResolutionResult) *HtlcResolution {
return &HtlcResolution{
CircuitKey: key,
AcceptHeight: acceptHeight,
Outcome: outcome,
}
}
// NewSettleResolution returns a htlc resolution which is associated with a
// settle.
func NewSettleResolution(preimage lntypes.Preimage, key channeldb.CircuitKey,
acceptHeight int32, outcome ResolutionResult) *HtlcResolution {
return &HtlcResolution{
Preimage: &preimage,
CircuitKey: key,
AcceptHeight: acceptHeight,
Outcome: outcome,
}
}
// RegistryConfig contains the configuration parameters for invoice registry. // RegistryConfig contains the configuration parameters for invoice registry.
type RegistryConfig struct { type RegistryConfig struct {
// FinalCltvRejectDelta defines the number of blocks before the expiry // FinalCltvRejectDelta defines the number of blocks before the expiry
@ -683,7 +641,7 @@ func (i *InvoiceRegistry) cancelSingleHtlc(hash lntypes.Hash,
return fmt.Errorf("htlc %v not found", key) return fmt.Errorf("htlc %v not found", key)
} }
if htlc.State == channeldb.HtlcStateCanceled { if htlc.State == channeldb.HtlcStateCanceled {
resolution := *NewFailureResolution( resolution := NewFailResolution(
key, int32(htlc.AcceptHeight), result, key, int32(htlc.AcceptHeight), result,
) )
@ -777,7 +735,7 @@ func (i *InvoiceRegistry) processKeySend(ctx invoiceUpdateCtx) error {
func (i *InvoiceRegistry) NotifyExitHopHtlc(rHash lntypes.Hash, func (i *InvoiceRegistry) NotifyExitHopHtlc(rHash lntypes.Hash,
amtPaid lnwire.MilliSatoshi, expiry uint32, currentHeight int32, amtPaid lnwire.MilliSatoshi, expiry uint32, currentHeight int32,
circuitKey channeldb.CircuitKey, hodlChan chan<- interface{}, circuitKey channeldb.CircuitKey, hodlChan chan<- interface{},
payload Payload) (*HtlcResolution, error) { payload Payload) (HtlcResolution, error) {
mpp := payload.MultiPath() mpp := payload.MultiPath()
@ -802,7 +760,7 @@ func (i *InvoiceRegistry) NotifyExitHopHtlc(rHash lntypes.Hash,
if err != nil { if err != nil {
updateCtx.log(fmt.Sprintf("keysend error: %v", err)) updateCtx.log(fmt.Sprintf("keysend error: %v", err))
return NewFailureResolution( return NewFailResolution(
circuitKey, currentHeight, ResultKeySendError, circuitKey, currentHeight, ResultKeySendError,
), nil ), nil
} }
@ -817,15 +775,10 @@ func (i *InvoiceRegistry) NotifyExitHopHtlc(rHash lntypes.Hash,
} }
switch r := resolution.(type) { switch r := resolution.(type) {
// A direct resolution was received for this htlc.
case *HtlcResolution:
return r, nil
// The htlc is held. Start a timer outside the lock if the htlc should // The htlc is held. Start a timer outside the lock if the htlc should
// be auto-released, because otherwise a deadlock may happen with the // be auto-released, because otherwise a deadlock may happen with the
// main event loop. // main event loop.
case *acceptResolution: case *htlcAcceptResolution:
if r.autoRelease { if r.autoRelease {
err := i.startHtlcTimer(rHash, circuitKey, r.acceptTime) err := i.startHtlcTimer(rHash, circuitKey, r.acceptTime)
if err != nil { if err != nil {
@ -833,33 +786,31 @@ func (i *InvoiceRegistry) NotifyExitHopHtlc(rHash lntypes.Hash,
} }
} }
// We return a nil resolution because htlc acceptances are
// represented as nil resolutions externally.
// TODO(carla) update calling code to handle accept resolutions.
return nil, nil return nil, nil
// A direct resolution was received for this htlc.
case HtlcResolution:
return r, nil
// Fail if an unknown resolution type was received.
default: default:
return nil, errors.New("invalid resolution type") return nil, errors.New("invalid resolution type")
} }
} }
// acceptResolution is returned when the htlc should be held.
type acceptResolution struct {
// autoRelease signals that the htlc should be automatically released
// after a timeout.
autoRelease bool
// acceptTime is the time at which this htlc was accepted.
acceptTime time.Time
}
// notifyExitHopHtlcLocked is the internal implementation of NotifyExitHopHtlc // notifyExitHopHtlcLocked is the internal implementation of NotifyExitHopHtlc
// that should be executed inside the registry lock. // that should be executed inside the registry lock.
func (i *InvoiceRegistry) notifyExitHopHtlcLocked( func (i *InvoiceRegistry) notifyExitHopHtlcLocked(
ctx *invoiceUpdateCtx, hodlChan chan<- interface{}) ( ctx *invoiceUpdateCtx, hodlChan chan<- interface{}) (
interface{}, error) { HtlcResolution, error) {
// We'll attempt to settle an invoice matching this rHash on disk (if // We'll attempt to settle an invoice matching this rHash on disk (if
// one exists). The callback will update the invoice state and/or htlcs. // one exists). The callback will update the invoice state and/or htlcs.
var ( var (
result ResolutionResult resolution HtlcResolution
updateSubscribers bool updateSubscribers bool
) )
invoice, err := i.cdb.UpdateInvoice( invoice, err := i.cdb.UpdateInvoice(
@ -876,8 +827,8 @@ func (i *InvoiceRegistry) notifyExitHopHtlcLocked(
updateSubscribers = updateDesc != nil && updateSubscribers = updateDesc != nil &&
updateDesc.State != nil updateDesc.State != nil
// Assign result to outer scope variable. // Assign resolution to outer scope variable.
result = res resolution = res
return updateDesc, nil return updateDesc, nil
}, },
@ -886,7 +837,7 @@ func (i *InvoiceRegistry) notifyExitHopHtlcLocked(
case channeldb.ErrInvoiceNotFound: case channeldb.ErrInvoiceNotFound:
// If the invoice was not found, return a failure resolution // If the invoice was not found, return a failure resolution
// with an invoice not found result. // with an invoice not found result.
return NewFailureResolution( return NewFailResolution(
ctx.circuitKey, ctx.currentHeight, ctx.circuitKey, ctx.currentHeight,
ResultInvoiceNotFound, ResultInvoiceNotFound,
), nil ), nil
@ -898,38 +849,40 @@ func (i *InvoiceRegistry) notifyExitHopHtlcLocked(
return nil, err return nil, err
} }
ctx.log(result.String())
if updateSubscribers { if updateSubscribers {
i.notifyClients(ctx.hash, invoice, invoice.State) i.notifyClients(ctx.hash, invoice, invoice.State)
} }
// Inspect latest htlc state on the invoice. switch res := resolution.(type) {
invoiceHtlc, ok := invoice.Htlcs[ctx.circuitKey] case *HtlcFailResolution:
// Inspect latest htlc state on the invoice. If it is found,
// we will update the accept height as it was recorded in the
// invoice database (which occurs in the case where the htlc
// reached the database in a previous call). If the htlc was
// not found on the invoice, it was immediately failed so we
// send the failure resolution as is, which has the current
// height set as the accept height.
invoiceHtlc, ok := invoice.Htlcs[ctx.circuitKey]
if ok {
res.AcceptHeight = int32(invoiceHtlc.AcceptHeight)
}
// If it isn't recorded, cancel htlc. ctx.log(fmt.Sprintf("failure resolution result "+
if !ok { "outcome: %v, at accept height: %v",
return NewFailureResolution( res.Outcome, res.AcceptHeight))
ctx.circuitKey, ctx.currentHeight, result,
), nil
}
// Determine accepted height of this htlc. If the htlc reached the return res, nil
// invoice database (possibly in a previous call to the invoice
// registry), we'll take the original accepted height as it was recorded
// in the database.
acceptHeight := int32(invoiceHtlc.AcceptHeight)
switch invoiceHtlc.State { // If the htlc was settled, we will settle any previously accepted
case channeldb.HtlcStateCanceled: // htlcs and notify our peer to settle them.
return NewFailureResolution( case *HtlcSettleResolution:
ctx.circuitKey, acceptHeight, result, ctx.log(fmt.Sprintf("settle resolution result "+
), nil "outcome: %v, at accept height: %v",
res.Outcome, res.AcceptHeight))
case channeldb.HtlcStateSettled: // Also settle any previously accepted htlcs. If a htlc is
// Also settle any previously accepted htlcs. The invoice state // marked as settled, we should follow now and settle the htlc
// is leading. If an htlc is marked as settled, we should follow // with our peer.
// now and settle the htlc with our peer.
for key, htlc := range invoice.Htlcs { for key, htlc := range invoice.Htlcs {
if htlc.State != channeldb.HtlcStateSettled { if htlc.State != channeldb.HtlcStateSettled {
continue continue
@ -940,34 +893,49 @@ func (i *InvoiceRegistry) notifyExitHopHtlcLocked(
// resolution is set based on the outcome of the single // resolution is set based on the outcome of the single
// htlc that we just settled, so may not be accurate // htlc that we just settled, so may not be accurate
// for all htlcs. // for all htlcs.
resolution := *NewSettleResolution( htlcSettleResolution := NewSettleResolution(
invoice.Terms.PaymentPreimage, key, res.Preimage, key,
acceptHeight, result, int32(htlc.AcceptHeight), res.Outcome,
) )
i.notifyHodlSubscribers(resolution) // Notify subscribers that the htlc should be settled
// with our peer.
i.notifyHodlSubscribers(htlcSettleResolution)
} }
resolution := NewSettleResolution(
invoice.Terms.PaymentPreimage, ctx.circuitKey,
acceptHeight, result,
)
return resolution, nil return resolution, nil
case channeldb.HtlcStateAccepted: // If we accepted the htlc, subscribe to the hodl invoice and return
var resolution acceptResolution // an accept resolution with the htlc's accept time on it.
case *htlcAcceptResolution:
invoiceHtlc, ok := invoice.Htlcs[ctx.circuitKey]
if !ok {
return nil, fmt.Errorf("accepted htlc: %v not"+
" present on invoice: %x", ctx.circuitKey,
ctx.hash[:])
}
// Determine accepted height of this htlc. If the htlc reached
// the invoice database (possibly in a previous call to the
// invoice registry), we'll take the original accepted height
// as it was recorded in the database.
acceptHeight := int32(invoiceHtlc.AcceptHeight)
ctx.log(fmt.Sprintf("accept resolution result "+
"outcome: %v, at accept height: %v",
res.outcome, acceptHeight))
// Auto-release the htlc if the invoice is still open. It can // Auto-release the htlc if the invoice is still open. It can
// only happen for mpp payments that there are htlcs in state // only happen for mpp payments that there are htlcs in state
// Accepted while the invoice is Open. // Accepted while the invoice is Open.
if invoice.State == channeldb.ContractOpen { if invoice.State == channeldb.ContractOpen {
resolution.acceptTime = invoiceHtlc.AcceptTime res.acceptTime = invoiceHtlc.AcceptTime
resolution.autoRelease = true res.autoRelease = true
} }
i.hodlSubscribe(hodlChan, ctx.circuitKey) i.hodlSubscribe(hodlChan, ctx.circuitKey)
return &resolution, nil return res, nil
default: default:
panic("unknown action") panic("unknown action")
@ -1022,7 +990,7 @@ func (i *InvoiceRegistry) SettleHodlInvoice(preimage lntypes.Preimage) error {
continue continue
} }
resolution := *NewSettleResolution( resolution := NewSettleResolution(
preimage, key, int32(htlc.AcceptHeight), ResultSettled, preimage, key, int32(htlc.AcceptHeight), ResultSettled,
) )
@ -1102,7 +1070,7 @@ func (i *InvoiceRegistry) cancelInvoiceImpl(payHash lntypes.Hash,
} }
i.notifyHodlSubscribers( i.notifyHodlSubscribers(
*NewFailureResolution( NewFailResolution(
key, int32(htlc.AcceptHeight), ResultCanceled, key, int32(htlc.AcceptHeight), ResultCanceled,
), ),
) )
@ -1374,7 +1342,7 @@ func (i *InvoiceRegistry) SubscribeSingleInvoice(
// notifyHodlSubscribers sends out the htlc resolution to all current // notifyHodlSubscribers sends out the htlc resolution to all current
// subscribers. // subscribers.
func (i *InvoiceRegistry) notifyHodlSubscribers(htlcResolution HtlcResolution) { func (i *InvoiceRegistry) notifyHodlSubscribers(htlcResolution HtlcResolution) {
subscribers, ok := i.hodlSubscriptions[htlcResolution.CircuitKey] subscribers, ok := i.hodlSubscriptions[htlcResolution.CircuitKey()]
if !ok { if !ok {
return return
} }
@ -1391,11 +1359,11 @@ func (i *InvoiceRegistry) notifyHodlSubscribers(htlcResolution HtlcResolution) {
delete( delete(
i.hodlReverseSubscriptions[subscriber], i.hodlReverseSubscriptions[subscriber],
htlcResolution.CircuitKey, htlcResolution.CircuitKey(),
) )
} }
delete(i.hodlSubscriptions, htlcResolution.CircuitKey) delete(i.hodlSubscriptions, htlcResolution.CircuitKey())
} }
// hodlSubscribe adds a new invoice subscription. // hodlSubscribe adds a new invoice subscription.

View File

@ -74,29 +74,38 @@ func TestSettleInvoice(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if resolution.Preimage != nil { failResolution, ok := resolution.(*HtlcFailResolution)
t.Fatal("expected cancel resolution") if !ok {
t.Fatalf("expected fail resolution, got: %T",
resolution)
} }
if resolution.AcceptHeight != testCurrentHeight { if failResolution.AcceptHeight != testCurrentHeight {
t.Fatalf("expected acceptHeight %v, but got %v", t.Fatalf("expected acceptHeight %v, but got %v",
testCurrentHeight, resolution.AcceptHeight) testCurrentHeight, failResolution.AcceptHeight)
} }
if resolution.Outcome != ResultExpiryTooSoon { if failResolution.Outcome != ResultExpiryTooSoon {
t.Fatalf("expected expiry too soon, got: %v", t.Fatalf("expected expiry too soon, got: %v",
resolution.Outcome) failResolution.Outcome)
} }
// Settle invoice with a slightly higher amount. // Settle invoice with a slightly higher amount.
amtPaid := lnwire.MilliSatoshi(100500) amtPaid := lnwire.MilliSatoshi(100500)
resolution, err = ctx.registry.NotifyExitHopHtlc( resolution, err = ctx.registry.NotifyExitHopHtlc(
testInvoicePaymentHash, amtPaid, testHtlcExpiry, testCurrentHeight, testInvoicePaymentHash, amtPaid, testHtlcExpiry,
getCircuitKey(0), hodlChan, testPayload, testCurrentHeight, getCircuitKey(0), hodlChan,
testPayload,
) )
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if resolution.Outcome != ResultSettled { settleResolution, ok := resolution.(*HtlcSettleResolution)
t.Fatalf("expected settled, got: %v", resolution.Outcome) if !ok {
t.Fatalf("expected settle resolution, got: %T",
resolution)
}
if settleResolution.Outcome != ResultSettled {
t.Fatalf("expected settled, got: %v",
settleResolution.Outcome)
} }
// We expect the settled state to be sent to the single invoice // We expect the settled state to be sent to the single invoice
@ -134,12 +143,14 @@ func TestSettleInvoice(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("unexpected NotifyExitHopHtlc error: %v", err) t.Fatalf("unexpected NotifyExitHopHtlc error: %v", err)
} }
if resolution.Preimage == nil { settleResolution, ok = resolution.(*HtlcSettleResolution)
t.Fatal("expected settle resolution") if !ok {
t.Fatalf("expected settle resolution, got: %T",
resolution)
} }
if resolution.Outcome != ResultReplayToSettled { if settleResolution.Outcome != ResultReplayToSettled {
t.Fatalf("expected replay settled, got: %v", t.Fatalf("expected replay settled, got: %v",
resolution.Outcome) settleResolution.Outcome)
} }
// Try to settle again with a new higher-valued htlc. This payment // Try to settle again with a new higher-valued htlc. This payment
@ -152,12 +163,14 @@ func TestSettleInvoice(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("unexpected NotifyExitHopHtlc error: %v", err) t.Fatalf("unexpected NotifyExitHopHtlc error: %v", err)
} }
if resolution.Preimage == nil { settleResolution, ok = resolution.(*HtlcSettleResolution)
t.Fatal("expected settle resolution") if !ok {
t.Fatalf("expected settle resolution, got: %T",
resolution)
} }
if resolution.Outcome != ResultDuplicateToSettled { if settleResolution.Outcome != ResultDuplicateToSettled {
t.Fatalf("expected duplicate settled, got: %v", t.Fatalf("expected duplicate settled, got: %v",
resolution.Outcome) settleResolution.Outcome)
} }
// Try to settle again with a lower amount. This should fail just as it // Try to settle again with a lower amount. This should fail just as it
@ -169,12 +182,14 @@ func TestSettleInvoice(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("unexpected NotifyExitHopHtlc error: %v", err) t.Fatalf("unexpected NotifyExitHopHtlc error: %v", err)
} }
if resolution.Preimage != nil { failResolution, ok = resolution.(*HtlcFailResolution)
t.Fatal("expected cancel resolution") if !ok {
t.Fatalf("expected fail resolution, got: %T",
resolution)
} }
if resolution.Outcome != ResultAmountTooLow { if failResolution.Outcome != ResultAmountTooLow {
t.Fatalf("expected amount too low, got: %v", t.Fatalf("expected amount too low, got: %v",
resolution.Outcome) failResolution.Outcome)
} }
// Check that settled amount is equal to the sum of values of the htlcs // Check that settled amount is equal to the sum of values of the htlcs
@ -298,17 +313,18 @@ func TestCancelInvoice(t *testing.T) {
if err != nil { if err != nil {
t.Fatal("expected settlement of a canceled invoice to succeed") t.Fatal("expected settlement of a canceled invoice to succeed")
} }
failResolution, ok := resolution.(*HtlcFailResolution)
if resolution.Preimage != nil { if !ok {
t.Fatal("expected cancel htlc resolution") t.Fatalf("expected fail resolution, got: %T",
resolution)
} }
if resolution.AcceptHeight != testCurrentHeight { if failResolution.AcceptHeight != testCurrentHeight {
t.Fatalf("expected acceptHeight %v, but got %v", t.Fatalf("expected acceptHeight %v, but got %v",
testCurrentHeight, resolution.AcceptHeight) testCurrentHeight, failResolution.AcceptHeight)
} }
if resolution.Outcome != ResultInvoiceAlreadyCanceled { if failResolution.Outcome != ResultInvoiceAlreadyCanceled {
t.Fatalf("expected invoice already canceled, got: %v", t.Fatalf("expected expiry too soon, got: %v",
resolution.Outcome) failResolution.Outcome)
} }
} }
@ -422,12 +438,14 @@ func TestSettleHoldInvoice(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("expected settle to succeed but got %v", err) t.Fatalf("expected settle to succeed but got %v", err)
} }
if resolution == nil || resolution.Preimage != nil { failResolution, ok := resolution.(*HtlcFailResolution)
t.Fatalf("expected htlc to be canceled") if !ok {
t.Fatalf("expected fail resolution, got: %T",
resolution)
} }
if resolution.Outcome != ResultExpiryTooSoon { if failResolution.Outcome != ResultExpiryTooSoon {
t.Fatalf("expected expiry too soon, got: %v", t.Fatalf("expected expiry too soon, got: %v",
resolution.Outcome) failResolution.Outcome)
} }
// We expect the accepted state to be sent to the single invoice // We expect the accepted state to be sent to the single invoice
@ -449,16 +467,21 @@ func TestSettleHoldInvoice(t *testing.T) {
} }
htlcResolution := (<-hodlChan).(HtlcResolution) htlcResolution := (<-hodlChan).(HtlcResolution)
if *htlcResolution.Preimage != testInvoicePreimage { settleResolution, ok := htlcResolution.(*HtlcSettleResolution)
if !ok {
t.Fatalf("expected settle resolution, got: %T",
htlcResolution)
}
if settleResolution.Preimage != testInvoicePreimage {
t.Fatal("unexpected preimage in hodl resolution") t.Fatal("unexpected preimage in hodl resolution")
} }
if htlcResolution.AcceptHeight != testCurrentHeight { if settleResolution.AcceptHeight != testCurrentHeight {
t.Fatalf("expected acceptHeight %v, but got %v", t.Fatalf("expected acceptHeight %v, but got %v",
testCurrentHeight, resolution.AcceptHeight) testCurrentHeight, settleResolution.AcceptHeight)
} }
if htlcResolution.Outcome != ResultSettled { if settleResolution.Outcome != ResultSettled {
t.Fatalf("expected result settled, got: %v", t.Fatalf("expected result settled, got: %v",
htlcResolution.Outcome) settleResolution.Outcome)
} }
// We expect a settled notification to be sent out for both all and // We expect a settled notification to be sent out for both all and
@ -545,8 +568,10 @@ func TestCancelHoldInvoice(t *testing.T) {
} }
htlcResolution := (<-hodlChan).(HtlcResolution) htlcResolution := (<-hodlChan).(HtlcResolution)
if htlcResolution.Preimage != nil { _, ok := htlcResolution.(*HtlcFailResolution)
t.Fatal("expected cancel htlc resolution") if !ok {
t.Fatalf("expected fail resolution, got: %T",
htlcResolution)
} }
// Offering the same htlc again at a higher height should still result // Offering the same htlc again at a higher height should still result
@ -559,16 +584,18 @@ func TestCancelHoldInvoice(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("expected settle to succeed but got %v", err) t.Fatalf("expected settle to succeed but got %v", err)
} }
if resolution.Preimage != nil { failResolution, ok := resolution.(*HtlcFailResolution)
t.Fatalf("expected htlc to be canceled") if !ok {
t.Fatalf("expected fail resolution, got: %T",
resolution)
} }
if resolution.AcceptHeight != testCurrentHeight { if failResolution.AcceptHeight != testCurrentHeight {
t.Fatalf("expected acceptHeight %v, but got %v", t.Fatalf("expected acceptHeight %v, but got %v",
testCurrentHeight, resolution.AcceptHeight) testCurrentHeight, failResolution.AcceptHeight)
} }
if resolution.Outcome != ResultReplayToCanceled { if failResolution.Outcome != ResultReplayToCanceled {
t.Fatalf("expected replay to canceled, got %v", t.Fatalf("expected replay to canceled, got %v",
resolution.Outcome) failResolution.Outcome)
} }
} }
@ -585,16 +612,21 @@ func TestUnknownInvoice(t *testing.T) {
// succeed. // succeed.
hodlChan := make(chan interface{}) hodlChan := make(chan interface{})
amt := lnwire.MilliSatoshi(100000) amt := lnwire.MilliSatoshi(100000)
result, err := ctx.registry.NotifyExitHopHtlc( resolution, err := ctx.registry.NotifyExitHopHtlc(
testInvoicePaymentHash, amt, testHtlcExpiry, testCurrentHeight, testInvoicePaymentHash, amt, testHtlcExpiry, testCurrentHeight,
getCircuitKey(0), hodlChan, testPayload, getCircuitKey(0), hodlChan, testPayload,
) )
if err != nil { if err != nil {
t.Fatal("unexpected error") t.Fatal("unexpected error")
} }
if result.Outcome != ResultInvoiceNotFound { failResolution, ok := resolution.(*HtlcFailResolution)
if !ok {
t.Fatalf("expected fail resolution, got: %T",
resolution)
}
if failResolution.Outcome != ResultInvoiceNotFound {
t.Fatalf("expected ResultInvoiceNotFound, got: %v", t.Fatalf("expected ResultInvoiceNotFound, got: %v",
result.Outcome) failResolution.Outcome)
} }
} }
@ -646,16 +678,17 @@ func testKeySend(t *testing.T, keySendEnabled bool) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
failResolution, ok := resolution.(*HtlcFailResolution)
// Expect a cancel resolution with the correct outcome. if !ok {
if resolution.Preimage != nil { t.Fatalf("expected fail resolution, got: %T",
t.Fatal("expected cancel resolution") resolution)
} }
switch { switch {
case !keySendEnabled && resolution.Outcome != ResultInvoiceNotFound: case !keySendEnabled && failResolution.Outcome != ResultInvoiceNotFound:
t.Fatal("expected invoice not found outcome") t.Fatal("expected invoice not found outcome")
case keySendEnabled && resolution.Outcome != ResultKeySendError: case keySendEnabled && failResolution.Outcome != ResultKeySendError:
t.Fatal("expected keysend error") t.Fatal("expected keysend error")
} }
@ -676,15 +709,25 @@ func testKeySend(t *testing.T, keySendEnabled bool) {
// Expect a cancel resolution if keysend is disabled. // Expect a cancel resolution if keysend is disabled.
if !keySendEnabled { if !keySendEnabled {
if resolution.Outcome != ResultInvoiceNotFound { failResolution, ok = resolution.(*HtlcFailResolution)
if !ok {
t.Fatalf("expected fail resolution, got: %T",
resolution)
}
if failResolution.Outcome != ResultInvoiceNotFound {
t.Fatal("expected keysend payment not to be accepted") t.Fatal("expected keysend payment not to be accepted")
} }
return return
} }
// Otherwise we expect no error and a settle resolution for the htlc. // Otherwise we expect no error and a settle resolution for the htlc.
if resolution.Preimage == nil || *resolution.Preimage != preimage { settleResolution, ok := resolution.(*HtlcSettleResolution)
t.Fatal("expected valid settle event") if !ok {
t.Fatalf("expected settle resolution, got: %T",
resolution)
}
if settleResolution.Preimage != preimage {
t.Fatalf("expected settle with matching preimage")
} }
// We expect a new invoice notification to be sent out. // We expect a new invoice notification to be sent out.
@ -739,12 +782,14 @@ func TestMppPayment(t *testing.T) {
ctx.clock.SetTime(testTime.Add(30 * time.Second)) ctx.clock.SetTime(testTime.Add(30 * time.Second))
htlcResolution := (<-hodlChan1).(HtlcResolution) htlcResolution := (<-hodlChan1).(HtlcResolution)
if htlcResolution.Preimage != nil { failResolution, ok := htlcResolution.(*HtlcFailResolution)
t.Fatal("expected cancel resolution") if !ok {
t.Fatalf("expected fail resolution, got: %T",
resolution)
} }
if htlcResolution.Outcome != ResultMppTimeout { if failResolution.Outcome != ResultMppTimeout {
t.Fatalf("expected mpp timeout, got: %v", t.Fatalf("expected mpp timeout, got: %v",
htlcResolution.Outcome) failResolution.Outcome)
} }
// Send htlc 2. // Send htlc 2.
@ -771,12 +816,14 @@ func TestMppPayment(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if resolution == nil { settleResolution, ok := resolution.(*HtlcSettleResolution)
t.Fatal("expected a settle resolution") if !ok {
t.Fatalf("expected settle resolution, got: %T",
htlcResolution)
} }
if resolution.Outcome != ResultSettled { if settleResolution.Outcome != ResultSettled {
t.Fatalf("expected result settled, got: %v", t.Fatalf("expected result settled, got: %v",
resolution.Outcome) settleResolution.Outcome)
} }
// Check that settled amount is equal to the sum of values of the htlcs // Check that settled amount is equal to the sum of values of the htlcs

124
invoices/resolution.go Normal file
View File

@ -0,0 +1,124 @@
package invoices
import (
"time"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/lntypes"
)
// HtlcResolution describes how an htlc should be resolved.
type HtlcResolution interface {
// CircuitKey returns the circuit key for the htlc that we have a
// resolution for.
CircuitKey() channeldb.CircuitKey
}
// HtlcFailResolution is an implementation of the HtlcResolution interface
// which is returned when a htlc is failed.
type HtlcFailResolution struct {
// circuitKey is the key of the htlc for which we have a resolution.
circuitKey channeldb.CircuitKey
// AcceptHeight is the original height at which the htlc was accepted.
AcceptHeight int32
// outcome indicates the outcome of the invoice registry update.
Outcome ResolutionResult
}
// NewFailResolution returns a htlc failure resolution.
func NewFailResolution(key channeldb.CircuitKey,
acceptHeight int32, outcome ResolutionResult) *HtlcFailResolution {
return &HtlcFailResolution{
circuitKey: key,
AcceptHeight: acceptHeight,
Outcome: outcome,
}
}
// CircuitKey returns the circuit key for the htlc that we have a
// resolution for.
//
// Note: it is part of the HtlcResolution interface.
func (f *HtlcFailResolution) CircuitKey() channeldb.CircuitKey {
return f.circuitKey
}
// HtlcSettleResolution is an implementation of the HtlcResolution interface
// which is returned when a htlc is settled.
type HtlcSettleResolution struct {
// Preimage is the htlc preimage. Its value is nil in case of a cancel.
Preimage lntypes.Preimage
// circuitKey is the key of the htlc for which we have a resolution.
circuitKey channeldb.CircuitKey
// acceptHeight is the original height at which the htlc was accepted.
AcceptHeight int32
// Outcome indicates the outcome of the invoice registry update.
Outcome ResolutionResult
}
// NewSettleResolution returns a htlc resolution which is associated with a
// settle.
func NewSettleResolution(preimage lntypes.Preimage, key channeldb.CircuitKey,
acceptHeight int32, outcome ResolutionResult) *HtlcSettleResolution {
return &HtlcSettleResolution{
Preimage: preimage,
circuitKey: key,
AcceptHeight: acceptHeight,
Outcome: outcome,
}
}
// CircuitKey returns the circuit key for the htlc that we have a
// resolution for.
//
// Note: it is part of the HtlcResolution interface.
func (s *HtlcSettleResolution) CircuitKey() channeldb.CircuitKey {
return s.circuitKey
}
// htlcAcceptResolution is an implementation of the HtlcResolution interface
// which is returned when a htlc is accepted. This struct is not exported
// because the codebase uses a nil resolution to indicate that a htlc was
// accepted. This struct is used internally in the invoice registry to
// surface accept resolution results. When an invoice update returns an
// acceptResolution, a nil resolution should be surfaced.
type htlcAcceptResolution struct {
// circuitKey is the key of the htlc for which we have a resolution.
circuitKey channeldb.CircuitKey
// autoRelease signals that the htlc should be automatically released
// after a timeout.
autoRelease bool
// acceptTime is the time at which this htlc was accepted.
acceptTime time.Time
// outcome indicates the outcome of the invoice registry update.
outcome ResolutionResult
}
// newAcceptResolution returns a htlc resolution which is associated with a
// htlc accept.
func newAcceptResolution(key channeldb.CircuitKey,
outcome ResolutionResult) *htlcAcceptResolution {
return &htlcAcceptResolution{
circuitKey: key,
outcome: outcome,
}
}
// CircuitKey returns the circuit key for the htlc that we have a
// resolution for.
//
// Note: it is part of the HtlcResolution interface.
func (a *htlcAcceptResolution) CircuitKey() channeldb.CircuitKey {
return a.circuitKey
}

View File

@ -186,26 +186,53 @@ func (i *invoiceUpdateCtx) log(s string) {
i.hash[:], s, i.amtPaid, i.expiry, i.circuitKey, i.mpp) i.hash[:], s, i.amtPaid, i.expiry, i.circuitKey, i.mpp)
} }
// failRes is a helper function which creates a failure resolution with
// the information contained in the invoiceUpdateCtx and the outcome provided.
func (i invoiceUpdateCtx) failRes(outcome ResolutionResult) *HtlcFailResolution {
return NewFailResolution(i.circuitKey, i.currentHeight, outcome)
}
// settleRes is a helper function which creates a settle resolution with
// the information contained in the invoiceUpdateCtx and the preimage and
// outcome provided.
func (i invoiceUpdateCtx) settleRes(preimage lntypes.Preimage,
outcome ResolutionResult) *HtlcSettleResolution {
return NewSettleResolution(
preimage, i.circuitKey, i.currentHeight, outcome,
)
}
// acceptRes is a helper function which creates an accept resolution with
// the information contained in the invoiceUpdateCtx and the outcome provided.
func (i invoiceUpdateCtx) acceptRes(outcome ResolutionResult) *htlcAcceptResolution {
return newAcceptResolution(i.circuitKey, outcome)
}
// updateInvoice is a callback for DB.UpdateInvoice that contains the invoice // updateInvoice is a callback for DB.UpdateInvoice that contains the invoice
// settlement logic. // settlement logic. It returns a hltc resolution that indicates what the
// outcome of the update was.
func updateInvoice(ctx *invoiceUpdateCtx, inv *channeldb.Invoice) ( func updateInvoice(ctx *invoiceUpdateCtx, inv *channeldb.Invoice) (
*channeldb.InvoiceUpdateDesc, ResolutionResult, error) { *channeldb.InvoiceUpdateDesc, HtlcResolution, error) {
// Don't update the invoice when this is a replayed htlc. // Don't update the invoice when this is a replayed htlc.
htlc, ok := inv.Htlcs[ctx.circuitKey] htlc, ok := inv.Htlcs[ctx.circuitKey]
if ok { if ok {
switch htlc.State { switch htlc.State {
case channeldb.HtlcStateCanceled: case channeldb.HtlcStateCanceled:
return nil, ResultReplayToCanceled, nil return nil, ctx.failRes(ResultReplayToCanceled), nil
case channeldb.HtlcStateAccepted: case channeldb.HtlcStateAccepted:
return nil, ResultReplayToAccepted, nil return nil, ctx.acceptRes(ResultReplayToAccepted), nil
case channeldb.HtlcStateSettled: case channeldb.HtlcStateSettled:
return nil, ResultReplayToSettled, nil return nil, ctx.settleRes(
inv.Terms.PaymentPreimage,
ResultReplayToSettled,
), nil
default: default:
return nil, 0, errors.New("unknown htlc state") return nil, nil, errors.New("unknown htlc state")
} }
} }
@ -218,8 +245,9 @@ func updateInvoice(ctx *invoiceUpdateCtx, inv *channeldb.Invoice) (
// updateMpp is a callback for DB.UpdateInvoice that contains the invoice // updateMpp is a callback for DB.UpdateInvoice that contains the invoice
// settlement logic for mpp payments. // settlement logic for mpp payments.
func updateMpp(ctx *invoiceUpdateCtx, inv *channeldb.Invoice) ( func updateMpp(ctx *invoiceUpdateCtx,
*channeldb.InvoiceUpdateDesc, ResolutionResult, error) { inv *channeldb.Invoice) (*channeldb.InvoiceUpdateDesc,
HtlcResolution, error) {
// Start building the accept descriptor. // Start building the accept descriptor.
acceptDesc := &channeldb.HtlcAcceptDesc{ acceptDesc := &channeldb.HtlcAcceptDesc{
@ -235,23 +263,23 @@ func updateMpp(ctx *invoiceUpdateCtx, inv *channeldb.Invoice) (
// Because non-mpp payments don't have a payment address, this is needed // Because non-mpp payments don't have a payment address, this is needed
// to thwart probing. // to thwart probing.
if inv.State != channeldb.ContractOpen { if inv.State != channeldb.ContractOpen {
return nil, ResultInvoiceNotOpen, nil return nil, ctx.failRes(ResultInvoiceNotOpen), nil
} }
// Check the payment address that authorizes the payment. // Check the payment address that authorizes the payment.
if ctx.mpp.PaymentAddr() != inv.Terms.PaymentAddr { if ctx.mpp.PaymentAddr() != inv.Terms.PaymentAddr {
return nil, ResultAddressMismatch, nil return nil, ctx.failRes(ResultAddressMismatch), nil
} }
// Don't accept zero-valued sets. // Don't accept zero-valued sets.
if ctx.mpp.TotalMsat() == 0 { if ctx.mpp.TotalMsat() == 0 {
return nil, ResultHtlcSetTotalTooLow, nil return nil, ctx.failRes(ResultHtlcSetTotalTooLow), nil
} }
// Check that the total amt of the htlc set is high enough. In case this // Check that the total amt of the htlc set is high enough. In case this
// is a zero-valued invoice, it will always be enough. // is a zero-valued invoice, it will always be enough.
if ctx.mpp.TotalMsat() < inv.Terms.Value { if ctx.mpp.TotalMsat() < inv.Terms.Value {
return nil, ResultHtlcSetTotalTooLow, nil return nil, ctx.failRes(ResultHtlcSetTotalTooLow), nil
} }
// Check whether total amt matches other htlcs in the set. // Check whether total amt matches other htlcs in the set.
@ -265,7 +293,7 @@ func updateMpp(ctx *invoiceUpdateCtx, inv *channeldb.Invoice) (
} }
if ctx.mpp.TotalMsat() != htlc.MppTotalAmt { if ctx.mpp.TotalMsat() != htlc.MppTotalAmt {
return nil, ResultHtlcSetTotalMismatch, nil return nil, ctx.failRes(ResultHtlcSetTotalMismatch), nil
} }
newSetTotal += htlc.Amt newSetTotal += htlc.Amt
@ -276,16 +304,16 @@ func updateMpp(ctx *invoiceUpdateCtx, inv *channeldb.Invoice) (
// Make sure the communicated set total isn't overpaid. // Make sure the communicated set total isn't overpaid.
if newSetTotal > ctx.mpp.TotalMsat() { if newSetTotal > ctx.mpp.TotalMsat() {
return nil, ResultHtlcSetOverpayment, nil return nil, ctx.failRes(ResultHtlcSetOverpayment), nil
} }
// The invoice is still open. Check the expiry. // The invoice is still open. Check the expiry.
if ctx.expiry < uint32(ctx.currentHeight+ctx.finalCltvRejectDelta) { if ctx.expiry < uint32(ctx.currentHeight+ctx.finalCltvRejectDelta) {
return nil, ResultExpiryTooSoon, nil return nil, ctx.failRes(ResultExpiryTooSoon), nil
} }
if ctx.expiry < uint32(ctx.currentHeight+inv.Terms.FinalCltvDelta) { if ctx.expiry < uint32(ctx.currentHeight+inv.Terms.FinalCltvDelta) {
return nil, ResultExpiryTooSoon, nil return nil, ctx.failRes(ResultExpiryTooSoon), nil
} }
// Record HTLC in the invoice database. // Record HTLC in the invoice database.
@ -300,7 +328,7 @@ func updateMpp(ctx *invoiceUpdateCtx, inv *channeldb.Invoice) (
// If the invoice cannot be settled yet, only record the htlc. // If the invoice cannot be settled yet, only record the htlc.
setComplete := newSetTotal == ctx.mpp.TotalMsat() setComplete := newSetTotal == ctx.mpp.TotalMsat()
if !setComplete { if !setComplete {
return &update, ResultPartialAccepted, nil return &update, ctx.acceptRes(ResultPartialAccepted), nil
} }
// Check to see if we can settle or this is an hold invoice and // Check to see if we can settle or this is an hold invoice and
@ -310,7 +338,7 @@ func updateMpp(ctx *invoiceUpdateCtx, inv *channeldb.Invoice) (
update.State = &channeldb.InvoiceStateUpdateDesc{ update.State = &channeldb.InvoiceStateUpdateDesc{
NewState: channeldb.ContractAccepted, NewState: channeldb.ContractAccepted,
} }
return &update, ResultAccepted, nil return &update, ctx.acceptRes(ResultAccepted), nil
} }
update.State = &channeldb.InvoiceStateUpdateDesc{ update.State = &channeldb.InvoiceStateUpdateDesc{
@ -318,18 +346,20 @@ func updateMpp(ctx *invoiceUpdateCtx, inv *channeldb.Invoice) (
Preimage: inv.Terms.PaymentPreimage, Preimage: inv.Terms.PaymentPreimage,
} }
return &update, ResultSettled, nil return &update, ctx.settleRes(
inv.Terms.PaymentPreimage, ResultSettled,
), nil
} }
// updateLegacy is a callback for DB.UpdateInvoice that contains the invoice // updateLegacy is a callback for DB.UpdateInvoice that contains the invoice
// settlement logic for legacy payments. // settlement logic for legacy payments.
func updateLegacy(ctx *invoiceUpdateCtx, inv *channeldb.Invoice) ( func updateLegacy(ctx *invoiceUpdateCtx,
*channeldb.InvoiceUpdateDesc, ResolutionResult, error) { inv *channeldb.Invoice) (*channeldb.InvoiceUpdateDesc, HtlcResolution, error) {
// If the invoice is already canceled, there is no further // If the invoice is already canceled, there is no further
// checking to do. // checking to do.
if inv.State == channeldb.ContractCanceled { if inv.State == channeldb.ContractCanceled {
return nil, ResultInvoiceAlreadyCanceled, nil return nil, ctx.failRes(ResultInvoiceAlreadyCanceled), nil
} }
// If an invoice amount is specified, check that enough is paid. Also // If an invoice amount is specified, check that enough is paid. Also
@ -337,7 +367,7 @@ func updateLegacy(ctx *invoiceUpdateCtx, inv *channeldb.Invoice) (
// or accepted. In case this is a zero-valued invoice, it will always be // or accepted. In case this is a zero-valued invoice, it will always be
// enough. // enough.
if ctx.amtPaid < inv.Terms.Value { if ctx.amtPaid < inv.Terms.Value {
return nil, ResultAmountTooLow, nil return nil, ctx.failRes(ResultAmountTooLow), nil
} }
// TODO(joostjager): Check invoice mpp required feature // TODO(joostjager): Check invoice mpp required feature
@ -350,17 +380,17 @@ func updateLegacy(ctx *invoiceUpdateCtx, inv *channeldb.Invoice) (
if htlc.State == channeldb.HtlcStateAccepted && if htlc.State == channeldb.HtlcStateAccepted &&
htlc.MppTotalAmt > 0 { htlc.MppTotalAmt > 0 {
return nil, ResultMppInProgress, nil return nil, ctx.failRes(ResultMppInProgress), nil
} }
} }
// The invoice is still open. Check the expiry. // The invoice is still open. Check the expiry.
if ctx.expiry < uint32(ctx.currentHeight+ctx.finalCltvRejectDelta) { if ctx.expiry < uint32(ctx.currentHeight+ctx.finalCltvRejectDelta) {
return nil, ResultExpiryTooSoon, nil return nil, ctx.failRes(ResultExpiryTooSoon), nil
} }
if ctx.expiry < uint32(ctx.currentHeight+inv.Terms.FinalCltvDelta) { if ctx.expiry < uint32(ctx.currentHeight+inv.Terms.FinalCltvDelta) {
return nil, ResultExpiryTooSoon, nil return nil, ctx.failRes(ResultExpiryTooSoon), nil
} }
// Record HTLC in the invoice database. // Record HTLC in the invoice database.
@ -381,10 +411,12 @@ func updateLegacy(ctx *invoiceUpdateCtx, inv *channeldb.Invoice) (
// We do accept or settle the HTLC. // We do accept or settle the HTLC.
switch inv.State { switch inv.State {
case channeldb.ContractAccepted: case channeldb.ContractAccepted:
return &update, ResultDuplicateToAccepted, nil return &update, ctx.acceptRes(ResultDuplicateToAccepted), nil
case channeldb.ContractSettled: case channeldb.ContractSettled:
return &update, ResultDuplicateToSettled, nil return &update, ctx.settleRes(
inv.Terms.PaymentPreimage, ResultDuplicateToSettled,
), nil
} }
// Check to see if we can settle or this is an hold invoice and we need // Check to see if we can settle or this is an hold invoice and we need
@ -394,7 +426,8 @@ func updateLegacy(ctx *invoiceUpdateCtx, inv *channeldb.Invoice) (
update.State = &channeldb.InvoiceStateUpdateDesc{ update.State = &channeldb.InvoiceStateUpdateDesc{
NewState: channeldb.ContractAccepted, NewState: channeldb.ContractAccepted,
} }
return &update, ResultAccepted, nil
return &update, ctx.acceptRes(ResultAccepted), nil
} }
update.State = &channeldb.InvoiceStateUpdateDesc{ update.State = &channeldb.InvoiceStateUpdateDesc{
@ -402,5 +435,7 @@ func updateLegacy(ctx *invoiceUpdateCtx, inv *channeldb.Invoice) (
Preimage: inv.Terms.PaymentPreimage, Preimage: inv.Terms.PaymentPreimage,
} }
return &update, ResultSettled, nil return &update, ctx.settleRes(
inv.Terms.PaymentPreimage, ResultSettled,
), nil
} }