mirror of
https://github.com/lightningnetwork/lnd.git
synced 2024-11-19 01:43:16 +01:00
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:
parent
2cd26d7556
commit
2569b4d08a
@ -4,6 +4,7 @@ import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/btcsuite/btcutil"
|
||||
@ -172,7 +173,23 @@ func (h *htlcIncomingContestResolver) Resolve() (ContractResolver, error) {
|
||||
processHtlcResolution := func(e invoices.HtlcResolution) (
|
||||
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 "+
|
||||
"(expiry=%v, height=%v), abandoning", h,
|
||||
h.htlcResolution.ClaimOutpoint,
|
||||
@ -180,13 +197,13 @@ func (h *htlcIncomingContestResolver) Resolve() (ContractResolver, error) {
|
||||
|
||||
h.resolved = true
|
||||
return nil, h.Checkpoint(h)
|
||||
}
|
||||
|
||||
if err := applyPreimage(*e.Preimage); err != nil {
|
||||
return nil, err
|
||||
// Error if the resolution type is unknown, we are only
|
||||
// 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.
|
||||
@ -211,14 +228,29 @@ func (h *htlcIncomingContestResolver) Resolve() (ContractResolver, error) {
|
||||
|
||||
defer h.Registry.HodlUnsubscribeAll(hodlChan)
|
||||
|
||||
// If the resolution is non-nil (indicating that a settle or cancel has
|
||||
// occurred), and the invoice is known to the registry (indicating that
|
||||
// the htlc is paying one of our invoices and is not a forward), try to
|
||||
// resolve it directly.
|
||||
if resolution != nil &&
|
||||
resolution.Outcome != invoices.ResultInvoiceNotFound {
|
||||
// Take action based on the resolution we received. If the htlc was
|
||||
// settled, or a htlc for a known invoice failed we can resolve it
|
||||
// directly. If the resolution is nil, the htlc was neither accepted
|
||||
// nor failed, so we cannot take action yet.
|
||||
switch res := resolution.(type) {
|
||||
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
|
||||
@ -256,7 +288,6 @@ func (h *htlcIncomingContestResolver) Resolve() (ContractResolver, error) {
|
||||
|
||||
case hodlItem := <-hodlChan:
|
||||
htlcResolution := hodlItem.(invoices.HtlcResolution)
|
||||
|
||||
return processHtlcResolution(htlcResolution)
|
||||
|
||||
case newBlock, ok := <-blockEpochs.Epochs:
|
||||
|
@ -35,7 +35,7 @@ func TestHtlcIncomingResolverFwdPreimageKnown(t *testing.T) {
|
||||
defer timeout(t)()
|
||||
|
||||
ctx := newIncomingResolverTestContext(t)
|
||||
ctx.registry.notifyResolution = invoices.NewFailureResolution(
|
||||
ctx.registry.notifyResolution = invoices.NewFailResolution(
|
||||
testResCircuitKey, testHtlcExpiry,
|
||||
invoices.ResultInvoiceNotFound,
|
||||
)
|
||||
@ -52,7 +52,7 @@ func TestHtlcIncomingResolverFwdContestedSuccess(t *testing.T) {
|
||||
defer timeout(t)()
|
||||
|
||||
ctx := newIncomingResolverTestContext(t)
|
||||
ctx.registry.notifyResolution = invoices.NewFailureResolution(
|
||||
ctx.registry.notifyResolution = invoices.NewFailResolution(
|
||||
testResCircuitKey, testHtlcExpiry,
|
||||
invoices.ResultInvoiceNotFound,
|
||||
)
|
||||
@ -72,7 +72,7 @@ func TestHtlcIncomingResolverFwdContestedTimeout(t *testing.T) {
|
||||
defer timeout(t)()
|
||||
|
||||
ctx := newIncomingResolverTestContext(t)
|
||||
ctx.registry.notifyResolution = invoices.NewFailureResolution(
|
||||
ctx.registry.notifyResolution = invoices.NewFailResolution(
|
||||
testResCircuitKey, testHtlcExpiry,
|
||||
invoices.ResultInvoiceNotFound,
|
||||
)
|
||||
@ -91,7 +91,7 @@ func TestHtlcIncomingResolverFwdTimeout(t *testing.T) {
|
||||
defer timeout(t)()
|
||||
|
||||
ctx := newIncomingResolverTestContext(t)
|
||||
ctx.registry.notifyResolution = invoices.NewFailureResolution(
|
||||
ctx.registry.notifyResolution = invoices.NewFailResolution(
|
||||
testResCircuitKey, testHtlcExpiry,
|
||||
invoices.ResultInvoiceNotFound,
|
||||
)
|
||||
@ -139,7 +139,7 @@ func TestHtlcIncomingResolverExitCancel(t *testing.T) {
|
||||
defer timeout(t)()
|
||||
|
||||
ctx := newIncomingResolverTestContext(t)
|
||||
ctx.registry.notifyResolution = invoices.NewFailureResolution(
|
||||
ctx.registry.notifyResolution = invoices.NewFailResolution(
|
||||
testResCircuitKey, testAcceptHeight,
|
||||
invoices.ResultInvoiceAlreadyCanceled,
|
||||
)
|
||||
@ -158,7 +158,7 @@ func TestHtlcIncomingResolverExitSettleHodl(t *testing.T) {
|
||||
ctx.resolve()
|
||||
|
||||
notifyData := <-ctx.registry.notifyChan
|
||||
notifyData.hodlChan <- *invoices.NewSettleResolution(
|
||||
notifyData.hodlChan <- invoices.NewSettleResolution(
|
||||
testResPreimage, testResCircuitKey, testAcceptHeight,
|
||||
invoices.ResultSettled,
|
||||
)
|
||||
@ -187,7 +187,7 @@ func TestHtlcIncomingResolverExitCancelHodl(t *testing.T) {
|
||||
ctx := newIncomingResolverTestContext(t)
|
||||
ctx.resolve()
|
||||
notifyData := <-ctx.registry.notifyChan
|
||||
notifyData.hodlChan <- *invoices.NewFailureResolution(
|
||||
notifyData.hodlChan <- invoices.NewFailResolution(
|
||||
testResCircuitKey, testAcceptHeight, invoices.ResultCanceled,
|
||||
)
|
||||
|
||||
|
@ -27,7 +27,7 @@ type Registry interface {
|
||||
NotifyExitHopHtlc(payHash lntypes.Hash, paidAmount lnwire.MilliSatoshi,
|
||||
expiry uint32, currentHeight int32,
|
||||
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(subscriber chan<- interface{})
|
||||
|
@ -18,13 +18,13 @@ type notifyExitHopData struct {
|
||||
type mockRegistry struct {
|
||||
notifyChan chan notifyExitHopData
|
||||
notifyErr error
|
||||
notifyResolution *invoices.HtlcResolution
|
||||
notifyResolution invoices.HtlcResolution
|
||||
}
|
||||
|
||||
func (r *mockRegistry) NotifyExitHopHtlc(payHash lntypes.Hash,
|
||||
paidAmount lnwire.MilliSatoshi, expiry uint32, currentHeight int32,
|
||||
circuitKey channeldb.CircuitKey, hodlChan chan<- interface{},
|
||||
payload invoices.Payload) (*invoices.HtlcResolution, error) {
|
||||
payload invoices.Payload) (invoices.HtlcResolution, error) {
|
||||
|
||||
r.notifyChan <- notifyExitHopData{
|
||||
hodlChan: hodlChan,
|
||||
|
@ -27,7 +27,7 @@ type InvoiceDatabase interface {
|
||||
NotifyExitHopHtlc(payHash lntypes.Hash, paidAmount lnwire.MilliSatoshi,
|
||||
expiry uint32, currentHeight int32,
|
||||
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
|
||||
// passed payment hash.
|
||||
|
@ -1158,7 +1158,7 @@ loop:
|
||||
for {
|
||||
// Lookup all hodl htlcs that can be failed or settled with this event.
|
||||
// The hodl htlc must be present in the map.
|
||||
circuitKey := htlcResolution.CircuitKey
|
||||
circuitKey := htlcResolution.CircuitKey()
|
||||
hodlHtlc, ok := l.hodlMap[circuitKey]
|
||||
if !ok {
|
||||
return fmt.Errorf("hodl htlc not found: %v", circuitKey)
|
||||
@ -1193,46 +1193,61 @@ loop:
|
||||
func (l *channelLink) processHtlcResolution(resolution invoices.HtlcResolution,
|
||||
htlc hodlHtlc) error {
|
||||
|
||||
circuitKey := resolution.CircuitKey
|
||||
circuitKey := resolution.CircuitKey()
|
||||
|
||||
// Determine required action for the resolution. If the event's preimage is
|
||||
// non-nil, the htlc must be settled. Otherwise, it should be canceled.
|
||||
if resolution.Preimage != nil {
|
||||
l.log.Debugf("received settle resolution for %v", circuitKey)
|
||||
// Determine required action for the resolution based on the type of
|
||||
// resolution we have received.
|
||||
switch res := resolution.(type) {
|
||||
// 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(
|
||||
*resolution.Preimage, htlc.pd.HtlcIndex,
|
||||
res.Preimage, htlc.pd.HtlcIndex,
|
||||
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
|
||||
// be failed with.
|
||||
func getResolutionFailure(resolution invoices.HtlcResolution,
|
||||
func getResolutionFailure(resolution *invoices.HtlcFailResolution,
|
||||
amount lnwire.MilliSatoshi) lnwire.FailureMessage {
|
||||
|
||||
// If the resolution has been resolved as part of a MPP timeout, we need
|
||||
// to fail the htlc with lnwire.FailMppTimeout.
|
||||
// If the resolution has been resolved as part of a MPP timeout,
|
||||
// we need to fail the htlc with lnwire.FailMppTimeout.
|
||||
if resolution.Outcome == invoices.ResultMppTimeout {
|
||||
return &lnwire.FailMPPTimeout{}
|
||||
}
|
||||
|
||||
// If the htlc is not a MPP timeout, we fail it with FailIncorrectDetails
|
||||
// This covers hodl cancels (which return it to avoid leaking information
|
||||
// and other invoice failures such as underpayment or expiry too soon.
|
||||
// If the htlc is not a MPP timeout, we fail it with
|
||||
// FailIncorrectDetails. This error is sent for invoice payment
|
||||
// failures such as underpayment/ expiry too soon and hodl invoices
|
||||
// (which return FailIncorrectDetails to avoid leaking information).
|
||||
return lnwire.NewFailIncorrectDetails(
|
||||
amount, uint32(resolution.AcceptHeight),
|
||||
)
|
||||
@ -2863,7 +2878,7 @@ func (l *channelLink) processExitHop(pd *lnwallet.PaymentDescriptor,
|
||||
}
|
||||
|
||||
// Process the received resolution.
|
||||
return l.processHtlcResolution(*event, htlc)
|
||||
return l.processHtlcResolution(event, htlc)
|
||||
}
|
||||
|
||||
// settleHTLC settles the HTLC on the channel.
|
||||
|
@ -816,7 +816,7 @@ func (i *mockInvoiceRegistry) SettleHodlInvoice(preimage lntypes.Preimage) error
|
||||
func (i *mockInvoiceRegistry) NotifyExitHopHtlc(rhash lntypes.Hash,
|
||||
amt lnwire.MilliSatoshi, expiry uint32, currentHeight int32,
|
||||
circuitKey channeldb.CircuitKey, hodlChan chan<- interface{},
|
||||
payload invoices.Payload) (*invoices.HtlcResolution, error) {
|
||||
payload invoices.Payload) (invoices.HtlcResolution, error) {
|
||||
|
||||
event, err := i.registry.NotifyExitHopHtlc(
|
||||
rhash, amt, expiry, currentHeight, circuitKey, hodlChan,
|
||||
|
@ -36,48 +36,6 @@ const (
|
||||
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.
|
||||
type RegistryConfig struct {
|
||||
// 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)
|
||||
}
|
||||
if htlc.State == channeldb.HtlcStateCanceled {
|
||||
resolution := *NewFailureResolution(
|
||||
resolution := NewFailResolution(
|
||||
key, int32(htlc.AcceptHeight), result,
|
||||
)
|
||||
|
||||
@ -777,7 +735,7 @@ func (i *InvoiceRegistry) processKeySend(ctx invoiceUpdateCtx) error {
|
||||
func (i *InvoiceRegistry) NotifyExitHopHtlc(rHash lntypes.Hash,
|
||||
amtPaid lnwire.MilliSatoshi, expiry uint32, currentHeight int32,
|
||||
circuitKey channeldb.CircuitKey, hodlChan chan<- interface{},
|
||||
payload Payload) (*HtlcResolution, error) {
|
||||
payload Payload) (HtlcResolution, error) {
|
||||
|
||||
mpp := payload.MultiPath()
|
||||
|
||||
@ -802,7 +760,7 @@ func (i *InvoiceRegistry) NotifyExitHopHtlc(rHash lntypes.Hash,
|
||||
if err != nil {
|
||||
updateCtx.log(fmt.Sprintf("keysend error: %v", err))
|
||||
|
||||
return NewFailureResolution(
|
||||
return NewFailResolution(
|
||||
circuitKey, currentHeight, ResultKeySendError,
|
||||
), nil
|
||||
}
|
||||
@ -817,15 +775,10 @@ func (i *InvoiceRegistry) NotifyExitHopHtlc(rHash lntypes.Hash,
|
||||
}
|
||||
|
||||
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
|
||||
// be auto-released, because otherwise a deadlock may happen with the
|
||||
// main event loop.
|
||||
case *acceptResolution:
|
||||
case *htlcAcceptResolution:
|
||||
if r.autoRelease {
|
||||
err := i.startHtlcTimer(rHash, circuitKey, r.acceptTime)
|
||||
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
|
||||
|
||||
// A direct resolution was received for this htlc.
|
||||
case HtlcResolution:
|
||||
return r, nil
|
||||
|
||||
// Fail if an unknown resolution type was received.
|
||||
default:
|
||||
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
|
||||
// that should be executed inside the registry lock.
|
||||
func (i *InvoiceRegistry) notifyExitHopHtlcLocked(
|
||||
ctx *invoiceUpdateCtx, hodlChan chan<- interface{}) (
|
||||
interface{}, error) {
|
||||
HtlcResolution, error) {
|
||||
|
||||
// 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.
|
||||
var (
|
||||
result ResolutionResult
|
||||
resolution HtlcResolution
|
||||
updateSubscribers bool
|
||||
)
|
||||
invoice, err := i.cdb.UpdateInvoice(
|
||||
@ -876,8 +827,8 @@ func (i *InvoiceRegistry) notifyExitHopHtlcLocked(
|
||||
updateSubscribers = updateDesc != nil &&
|
||||
updateDesc.State != nil
|
||||
|
||||
// Assign result to outer scope variable.
|
||||
result = res
|
||||
// Assign resolution to outer scope variable.
|
||||
resolution = res
|
||||
|
||||
return updateDesc, nil
|
||||
},
|
||||
@ -886,7 +837,7 @@ func (i *InvoiceRegistry) notifyExitHopHtlcLocked(
|
||||
case channeldb.ErrInvoiceNotFound:
|
||||
// If the invoice was not found, return a failure resolution
|
||||
// with an invoice not found result.
|
||||
return NewFailureResolution(
|
||||
return NewFailResolution(
|
||||
ctx.circuitKey, ctx.currentHeight,
|
||||
ResultInvoiceNotFound,
|
||||
), nil
|
||||
@ -898,38 +849,40 @@ func (i *InvoiceRegistry) notifyExitHopHtlcLocked(
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ctx.log(result.String())
|
||||
|
||||
if updateSubscribers {
|
||||
i.notifyClients(ctx.hash, invoice, invoice.State)
|
||||
}
|
||||
|
||||
// Inspect latest htlc state on the invoice.
|
||||
invoiceHtlc, ok := invoice.Htlcs[ctx.circuitKey]
|
||||
switch res := resolution.(type) {
|
||||
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.
|
||||
if !ok {
|
||||
return NewFailureResolution(
|
||||
ctx.circuitKey, ctx.currentHeight, result,
|
||||
), nil
|
||||
}
|
||||
ctx.log(fmt.Sprintf("failure resolution result "+
|
||||
"outcome: %v, at accept height: %v",
|
||||
res.Outcome, res.AcceptHeight))
|
||||
|
||||
// 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)
|
||||
return res, nil
|
||||
|
||||
switch invoiceHtlc.State {
|
||||
case channeldb.HtlcStateCanceled:
|
||||
return NewFailureResolution(
|
||||
ctx.circuitKey, acceptHeight, result,
|
||||
), nil
|
||||
// If the htlc was settled, we will settle any previously accepted
|
||||
// htlcs and notify our peer to settle them.
|
||||
case *HtlcSettleResolution:
|
||||
ctx.log(fmt.Sprintf("settle resolution result "+
|
||||
"outcome: %v, at accept height: %v",
|
||||
res.Outcome, res.AcceptHeight))
|
||||
|
||||
case channeldb.HtlcStateSettled:
|
||||
// Also settle any previously accepted htlcs. The invoice state
|
||||
// is leading. If an htlc is marked as settled, we should follow
|
||||
// now and settle the htlc with our peer.
|
||||
// Also settle any previously accepted htlcs. If a htlc is
|
||||
// marked as settled, we should follow now and settle the htlc
|
||||
// with our peer.
|
||||
for key, htlc := range invoice.Htlcs {
|
||||
if htlc.State != channeldb.HtlcStateSettled {
|
||||
continue
|
||||
@ -940,34 +893,49 @@ func (i *InvoiceRegistry) notifyExitHopHtlcLocked(
|
||||
// resolution is set based on the outcome of the single
|
||||
// htlc that we just settled, so may not be accurate
|
||||
// for all htlcs.
|
||||
resolution := *NewSettleResolution(
|
||||
invoice.Terms.PaymentPreimage, key,
|
||||
acceptHeight, result,
|
||||
htlcSettleResolution := NewSettleResolution(
|
||||
res.Preimage, key,
|
||||
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
|
||||
|
||||
case channeldb.HtlcStateAccepted:
|
||||
var resolution acceptResolution
|
||||
// If we accepted the htlc, subscribe to the hodl invoice and return
|
||||
// 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
|
||||
// only happen for mpp payments that there are htlcs in state
|
||||
// Accepted while the invoice is Open.
|
||||
if invoice.State == channeldb.ContractOpen {
|
||||
resolution.acceptTime = invoiceHtlc.AcceptTime
|
||||
resolution.autoRelease = true
|
||||
res.acceptTime = invoiceHtlc.AcceptTime
|
||||
res.autoRelease = true
|
||||
|
||||
}
|
||||
|
||||
i.hodlSubscribe(hodlChan, ctx.circuitKey)
|
||||
return &resolution, nil
|
||||
return res, nil
|
||||
|
||||
default:
|
||||
panic("unknown action")
|
||||
@ -1022,7 +990,7 @@ func (i *InvoiceRegistry) SettleHodlInvoice(preimage lntypes.Preimage) error {
|
||||
continue
|
||||
}
|
||||
|
||||
resolution := *NewSettleResolution(
|
||||
resolution := NewSettleResolution(
|
||||
preimage, key, int32(htlc.AcceptHeight), ResultSettled,
|
||||
)
|
||||
|
||||
@ -1102,7 +1070,7 @@ func (i *InvoiceRegistry) cancelInvoiceImpl(payHash lntypes.Hash,
|
||||
}
|
||||
|
||||
i.notifyHodlSubscribers(
|
||||
*NewFailureResolution(
|
||||
NewFailResolution(
|
||||
key, int32(htlc.AcceptHeight), ResultCanceled,
|
||||
),
|
||||
)
|
||||
@ -1374,7 +1342,7 @@ func (i *InvoiceRegistry) SubscribeSingleInvoice(
|
||||
// notifyHodlSubscribers sends out the htlc resolution to all current
|
||||
// subscribers.
|
||||
func (i *InvoiceRegistry) notifyHodlSubscribers(htlcResolution HtlcResolution) {
|
||||
subscribers, ok := i.hodlSubscriptions[htlcResolution.CircuitKey]
|
||||
subscribers, ok := i.hodlSubscriptions[htlcResolution.CircuitKey()]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
@ -1391,11 +1359,11 @@ func (i *InvoiceRegistry) notifyHodlSubscribers(htlcResolution HtlcResolution) {
|
||||
|
||||
delete(
|
||||
i.hodlReverseSubscriptions[subscriber],
|
||||
htlcResolution.CircuitKey,
|
||||
htlcResolution.CircuitKey(),
|
||||
)
|
||||
}
|
||||
|
||||
delete(i.hodlSubscriptions, htlcResolution.CircuitKey)
|
||||
delete(i.hodlSubscriptions, htlcResolution.CircuitKey())
|
||||
}
|
||||
|
||||
// hodlSubscribe adds a new invoice subscription.
|
||||
|
@ -74,29 +74,38 @@ func TestSettleInvoice(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if resolution.Preimage != nil {
|
||||
t.Fatal("expected cancel resolution")
|
||||
failResolution, ok := resolution.(*HtlcFailResolution)
|
||||
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",
|
||||
testCurrentHeight, resolution.AcceptHeight)
|
||||
testCurrentHeight, failResolution.AcceptHeight)
|
||||
}
|
||||
if resolution.Outcome != ResultExpiryTooSoon {
|
||||
if failResolution.Outcome != ResultExpiryTooSoon {
|
||||
t.Fatalf("expected expiry too soon, got: %v",
|
||||
resolution.Outcome)
|
||||
failResolution.Outcome)
|
||||
}
|
||||
|
||||
// Settle invoice with a slightly higher amount.
|
||||
amtPaid := lnwire.MilliSatoshi(100500)
|
||||
resolution, err = ctx.registry.NotifyExitHopHtlc(
|
||||
testInvoicePaymentHash, amtPaid, testHtlcExpiry, testCurrentHeight,
|
||||
getCircuitKey(0), hodlChan, testPayload,
|
||||
testInvoicePaymentHash, amtPaid, testHtlcExpiry,
|
||||
testCurrentHeight, getCircuitKey(0), hodlChan,
|
||||
testPayload,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if resolution.Outcome != ResultSettled {
|
||||
t.Fatalf("expected settled, got: %v", resolution.Outcome)
|
||||
settleResolution, ok := resolution.(*HtlcSettleResolution)
|
||||
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
|
||||
@ -134,12 +143,14 @@ func TestSettleInvoice(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected NotifyExitHopHtlc error: %v", err)
|
||||
}
|
||||
if resolution.Preimage == nil {
|
||||
t.Fatal("expected settle resolution")
|
||||
settleResolution, ok = resolution.(*HtlcSettleResolution)
|
||||
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",
|
||||
resolution.Outcome)
|
||||
settleResolution.Outcome)
|
||||
}
|
||||
|
||||
// Try to settle again with a new higher-valued htlc. This payment
|
||||
@ -152,12 +163,14 @@ func TestSettleInvoice(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected NotifyExitHopHtlc error: %v", err)
|
||||
}
|
||||
if resolution.Preimage == nil {
|
||||
t.Fatal("expected settle resolution")
|
||||
settleResolution, ok = resolution.(*HtlcSettleResolution)
|
||||
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",
|
||||
resolution.Outcome)
|
||||
settleResolution.Outcome)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
t.Fatalf("unexpected NotifyExitHopHtlc error: %v", err)
|
||||
}
|
||||
if resolution.Preimage != nil {
|
||||
t.Fatal("expected cancel resolution")
|
||||
failResolution, ok = resolution.(*HtlcFailResolution)
|
||||
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",
|
||||
resolution.Outcome)
|
||||
failResolution.Outcome)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
t.Fatal("expected settlement of a canceled invoice to succeed")
|
||||
}
|
||||
|
||||
if resolution.Preimage != nil {
|
||||
t.Fatal("expected cancel htlc resolution")
|
||||
failResolution, ok := resolution.(*HtlcFailResolution)
|
||||
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",
|
||||
testCurrentHeight, resolution.AcceptHeight)
|
||||
testCurrentHeight, failResolution.AcceptHeight)
|
||||
}
|
||||
if resolution.Outcome != ResultInvoiceAlreadyCanceled {
|
||||
t.Fatalf("expected invoice already canceled, got: %v",
|
||||
resolution.Outcome)
|
||||
if failResolution.Outcome != ResultInvoiceAlreadyCanceled {
|
||||
t.Fatalf("expected expiry too soon, got: %v",
|
||||
failResolution.Outcome)
|
||||
}
|
||||
}
|
||||
|
||||
@ -422,12 +438,14 @@ func TestSettleHoldInvoice(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("expected settle to succeed but got %v", err)
|
||||
}
|
||||
if resolution == nil || resolution.Preimage != nil {
|
||||
t.Fatalf("expected htlc to be canceled")
|
||||
failResolution, ok := resolution.(*HtlcFailResolution)
|
||||
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",
|
||||
resolution.Outcome)
|
||||
failResolution.Outcome)
|
||||
}
|
||||
|
||||
// We expect the accepted state to be sent to the single invoice
|
||||
@ -449,16 +467,21 @@ func TestSettleHoldInvoice(t *testing.T) {
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
if htlcResolution.AcceptHeight != testCurrentHeight {
|
||||
if settleResolution.AcceptHeight != testCurrentHeight {
|
||||
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",
|
||||
htlcResolution.Outcome)
|
||||
settleResolution.Outcome)
|
||||
}
|
||||
|
||||
// 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)
|
||||
if htlcResolution.Preimage != nil {
|
||||
t.Fatal("expected cancel htlc resolution")
|
||||
_, ok := htlcResolution.(*HtlcFailResolution)
|
||||
if !ok {
|
||||
t.Fatalf("expected fail resolution, got: %T",
|
||||
htlcResolution)
|
||||
}
|
||||
|
||||
// Offering the same htlc again at a higher height should still result
|
||||
@ -559,16 +584,18 @@ func TestCancelHoldInvoice(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("expected settle to succeed but got %v", err)
|
||||
}
|
||||
if resolution.Preimage != nil {
|
||||
t.Fatalf("expected htlc to be canceled")
|
||||
failResolution, ok := resolution.(*HtlcFailResolution)
|
||||
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",
|
||||
testCurrentHeight, resolution.AcceptHeight)
|
||||
testCurrentHeight, failResolution.AcceptHeight)
|
||||
}
|
||||
if resolution.Outcome != ResultReplayToCanceled {
|
||||
if failResolution.Outcome != ResultReplayToCanceled {
|
||||
t.Fatalf("expected replay to canceled, got %v",
|
||||
resolution.Outcome)
|
||||
failResolution.Outcome)
|
||||
}
|
||||
}
|
||||
|
||||
@ -585,16 +612,21 @@ func TestUnknownInvoice(t *testing.T) {
|
||||
// succeed.
|
||||
hodlChan := make(chan interface{})
|
||||
amt := lnwire.MilliSatoshi(100000)
|
||||
result, err := ctx.registry.NotifyExitHopHtlc(
|
||||
resolution, err := ctx.registry.NotifyExitHopHtlc(
|
||||
testInvoicePaymentHash, amt, testHtlcExpiry, testCurrentHeight,
|
||||
getCircuitKey(0), hodlChan, testPayload,
|
||||
)
|
||||
if err != nil {
|
||||
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",
|
||||
result.Outcome)
|
||||
failResolution.Outcome)
|
||||
}
|
||||
}
|
||||
|
||||
@ -646,16 +678,17 @@ func testKeySend(t *testing.T, keySendEnabled bool) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Expect a cancel resolution with the correct outcome.
|
||||
if resolution.Preimage != nil {
|
||||
t.Fatal("expected cancel resolution")
|
||||
failResolution, ok := resolution.(*HtlcFailResolution)
|
||||
if !ok {
|
||||
t.Fatalf("expected fail resolution, got: %T",
|
||||
resolution)
|
||||
}
|
||||
|
||||
switch {
|
||||
case !keySendEnabled && resolution.Outcome != ResultInvoiceNotFound:
|
||||
case !keySendEnabled && failResolution.Outcome != ResultInvoiceNotFound:
|
||||
t.Fatal("expected invoice not found outcome")
|
||||
|
||||
case keySendEnabled && resolution.Outcome != ResultKeySendError:
|
||||
case keySendEnabled && failResolution.Outcome != ResultKeySendError:
|
||||
t.Fatal("expected keysend error")
|
||||
}
|
||||
|
||||
@ -676,15 +709,25 @@ func testKeySend(t *testing.T, keySendEnabled bool) {
|
||||
|
||||
// Expect a cancel resolution if keysend is disabled.
|
||||
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")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Otherwise we expect no error and a settle resolution for the htlc.
|
||||
if resolution.Preimage == nil || *resolution.Preimage != preimage {
|
||||
t.Fatal("expected valid settle event")
|
||||
settleResolution, ok := resolution.(*HtlcSettleResolution)
|
||||
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.
|
||||
@ -739,12 +782,14 @@ func TestMppPayment(t *testing.T) {
|
||||
ctx.clock.SetTime(testTime.Add(30 * time.Second))
|
||||
|
||||
htlcResolution := (<-hodlChan1).(HtlcResolution)
|
||||
if htlcResolution.Preimage != nil {
|
||||
t.Fatal("expected cancel resolution")
|
||||
failResolution, ok := htlcResolution.(*HtlcFailResolution)
|
||||
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",
|
||||
htlcResolution.Outcome)
|
||||
failResolution.Outcome)
|
||||
}
|
||||
|
||||
// Send htlc 2.
|
||||
@ -771,12 +816,14 @@ func TestMppPayment(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if resolution == nil {
|
||||
t.Fatal("expected a settle resolution")
|
||||
settleResolution, ok := resolution.(*HtlcSettleResolution)
|
||||
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",
|
||||
resolution.Outcome)
|
||||
settleResolution.Outcome)
|
||||
}
|
||||
|
||||
// Check that settled amount is equal to the sum of values of the htlcs
|
||||
|
124
invoices/resolution.go
Normal file
124
invoices/resolution.go
Normal 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
|
||||
}
|
@ -186,26 +186,53 @@ func (i *invoiceUpdateCtx) log(s string) {
|
||||
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
|
||||
// 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) (
|
||||
*channeldb.InvoiceUpdateDesc, ResolutionResult, error) {
|
||||
*channeldb.InvoiceUpdateDesc, HtlcResolution, error) {
|
||||
|
||||
// Don't update the invoice when this is a replayed htlc.
|
||||
htlc, ok := inv.Htlcs[ctx.circuitKey]
|
||||
if ok {
|
||||
switch htlc.State {
|
||||
case channeldb.HtlcStateCanceled:
|
||||
return nil, ResultReplayToCanceled, nil
|
||||
return nil, ctx.failRes(ResultReplayToCanceled), nil
|
||||
|
||||
case channeldb.HtlcStateAccepted:
|
||||
return nil, ResultReplayToAccepted, nil
|
||||
return nil, ctx.acceptRes(ResultReplayToAccepted), nil
|
||||
|
||||
case channeldb.HtlcStateSettled:
|
||||
return nil, ResultReplayToSettled, nil
|
||||
return nil, ctx.settleRes(
|
||||
inv.Terms.PaymentPreimage,
|
||||
ResultReplayToSettled,
|
||||
), nil
|
||||
|
||||
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
|
||||
// settlement logic for mpp payments.
|
||||
func updateMpp(ctx *invoiceUpdateCtx, inv *channeldb.Invoice) (
|
||||
*channeldb.InvoiceUpdateDesc, ResolutionResult, error) {
|
||||
func updateMpp(ctx *invoiceUpdateCtx,
|
||||
inv *channeldb.Invoice) (*channeldb.InvoiceUpdateDesc,
|
||||
HtlcResolution, error) {
|
||||
|
||||
// Start building the accept descriptor.
|
||||
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
|
||||
// to thwart probing.
|
||||
if inv.State != channeldb.ContractOpen {
|
||||
return nil, ResultInvoiceNotOpen, nil
|
||||
return nil, ctx.failRes(ResultInvoiceNotOpen), nil
|
||||
}
|
||||
|
||||
// Check the payment address that authorizes the payment.
|
||||
if ctx.mpp.PaymentAddr() != inv.Terms.PaymentAddr {
|
||||
return nil, ResultAddressMismatch, nil
|
||||
return nil, ctx.failRes(ResultAddressMismatch), nil
|
||||
}
|
||||
|
||||
// Don't accept zero-valued sets.
|
||||
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
|
||||
// is a zero-valued invoice, it will always be enough.
|
||||
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.
|
||||
@ -265,7 +293,7 @@ func updateMpp(ctx *invoiceUpdateCtx, inv *channeldb.Invoice) (
|
||||
}
|
||||
|
||||
if ctx.mpp.TotalMsat() != htlc.MppTotalAmt {
|
||||
return nil, ResultHtlcSetTotalMismatch, nil
|
||||
return nil, ctx.failRes(ResultHtlcSetTotalMismatch), nil
|
||||
}
|
||||
|
||||
newSetTotal += htlc.Amt
|
||||
@ -276,16 +304,16 @@ func updateMpp(ctx *invoiceUpdateCtx, inv *channeldb.Invoice) (
|
||||
|
||||
// Make sure the communicated set total isn't overpaid.
|
||||
if newSetTotal > ctx.mpp.TotalMsat() {
|
||||
return nil, ResultHtlcSetOverpayment, nil
|
||||
return nil, ctx.failRes(ResultHtlcSetOverpayment), nil
|
||||
}
|
||||
|
||||
// The invoice is still open. Check the expiry.
|
||||
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) {
|
||||
return nil, ResultExpiryTooSoon, nil
|
||||
return nil, ctx.failRes(ResultExpiryTooSoon), nil
|
||||
}
|
||||
|
||||
// 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.
|
||||
setComplete := newSetTotal == ctx.mpp.TotalMsat()
|
||||
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
|
||||
@ -310,7 +338,7 @@ func updateMpp(ctx *invoiceUpdateCtx, inv *channeldb.Invoice) (
|
||||
update.State = &channeldb.InvoiceStateUpdateDesc{
|
||||
NewState: channeldb.ContractAccepted,
|
||||
}
|
||||
return &update, ResultAccepted, nil
|
||||
return &update, ctx.acceptRes(ResultAccepted), nil
|
||||
}
|
||||
|
||||
update.State = &channeldb.InvoiceStateUpdateDesc{
|
||||
@ -318,18 +346,20 @@ func updateMpp(ctx *invoiceUpdateCtx, inv *channeldb.Invoice) (
|
||||
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
|
||||
// settlement logic for legacy payments.
|
||||
func updateLegacy(ctx *invoiceUpdateCtx, inv *channeldb.Invoice) (
|
||||
*channeldb.InvoiceUpdateDesc, ResolutionResult, error) {
|
||||
func updateLegacy(ctx *invoiceUpdateCtx,
|
||||
inv *channeldb.Invoice) (*channeldb.InvoiceUpdateDesc, HtlcResolution, error) {
|
||||
|
||||
// If the invoice is already canceled, there is no further
|
||||
// checking to do.
|
||||
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
|
||||
@ -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
|
||||
// enough.
|
||||
if ctx.amtPaid < inv.Terms.Value {
|
||||
return nil, ResultAmountTooLow, nil
|
||||
return nil, ctx.failRes(ResultAmountTooLow), nil
|
||||
}
|
||||
|
||||
// TODO(joostjager): Check invoice mpp required feature
|
||||
@ -350,17 +380,17 @@ func updateLegacy(ctx *invoiceUpdateCtx, inv *channeldb.Invoice) (
|
||||
if htlc.State == channeldb.HtlcStateAccepted &&
|
||||
htlc.MppTotalAmt > 0 {
|
||||
|
||||
return nil, ResultMppInProgress, nil
|
||||
return nil, ctx.failRes(ResultMppInProgress), nil
|
||||
}
|
||||
}
|
||||
|
||||
// The invoice is still open. Check the expiry.
|
||||
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) {
|
||||
return nil, ResultExpiryTooSoon, nil
|
||||
return nil, ctx.failRes(ResultExpiryTooSoon), nil
|
||||
}
|
||||
|
||||
// Record HTLC in the invoice database.
|
||||
@ -381,10 +411,12 @@ func updateLegacy(ctx *invoiceUpdateCtx, inv *channeldb.Invoice) (
|
||||
// We do accept or settle the HTLC.
|
||||
switch inv.State {
|
||||
case channeldb.ContractAccepted:
|
||||
return &update, ResultDuplicateToAccepted, nil
|
||||
return &update, ctx.acceptRes(ResultDuplicateToAccepted), nil
|
||||
|
||||
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
|
||||
@ -394,7 +426,8 @@ func updateLegacy(ctx *invoiceUpdateCtx, inv *channeldb.Invoice) (
|
||||
update.State = &channeldb.InvoiceStateUpdateDesc{
|
||||
NewState: channeldb.ContractAccepted,
|
||||
}
|
||||
return &update, ResultAccepted, nil
|
||||
|
||||
return &update, ctx.acceptRes(ResultAccepted), nil
|
||||
}
|
||||
|
||||
update.State = &channeldb.InvoiceStateUpdateDesc{
|
||||
@ -402,5 +435,7 @@ func updateLegacy(ctx *invoiceUpdateCtx, inv *channeldb.Invoice) (
|
||||
Preimage: inv.Terms.PaymentPreimage,
|
||||
}
|
||||
|
||||
return &update, ResultSettled, nil
|
||||
return &update, ctx.settleRes(
|
||||
inv.Terms.PaymentPreimage, ResultSettled,
|
||||
), nil
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user