Skip to content

Commit

Permalink
Merge 717bcbd into 1dadce7
Browse files Browse the repository at this point in the history
  • Loading branch information
carlaKC committed Jan 26, 2020
2 parents 1dadce7 + 717bcbd commit 2b14ab2
Show file tree
Hide file tree
Showing 22 changed files with 1,646 additions and 433 deletions.
64 changes: 50 additions & 14 deletions contractcourt/htlc_incoming_contest_resolver.go
Expand Up @@ -4,6 +4,7 @@ import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"io"

"github.com/btcsuite/btcutil"
Expand Down Expand Up @@ -172,21 +173,37 @@ 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,
h.htlcExpiry, currentHeight)

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.
Expand All @@ -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)
}

// If we settled the htlc, we can resolve it.
case *invoices.HtlcSettleResolution:
return processHtlcResolution(resolution)

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
Expand Down Expand Up @@ -255,7 +287,11 @@ func (h *htlcIncomingContestResolver) Resolve() (ContractResolver, error) {
return &h.htlcSuccessResolver, nil

case hodlItem := <-hodlChan:
htlcResolution := hodlItem.(invoices.HtlcResolution)
htlcResolution, ok := hodlItem.(invoices.HtlcResolution)
if !ok {
return nil, fmt.Errorf("expected htlc "+
"resolution, got: %T", hodlItem)
}

return processHtlcResolution(htlcResolution)

Expand Down
4 changes: 2 additions & 2 deletions contractcourt/htlc_incoming_resolver_test.go
Expand Up @@ -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,
)
Expand Down Expand Up @@ -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.NewFailureResolution(
testResCircuitKey, testAcceptHeight, invoices.ResultCanceled,
)

Expand Down
2 changes: 1 addition & 1 deletion contractcourt/interfaces.go
Expand Up @@ -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{})
Expand Down
4 changes: 2 additions & 2 deletions contractcourt/mock_registry_test.go
Expand Up @@ -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,
Expand Down
5 changes: 3 additions & 2 deletions htlcswitch/failure.go
Expand Up @@ -72,11 +72,12 @@ func (l *LinkError) WireMessage() lnwire.FailureMessage {
func (l *LinkError) Error() string {
// If the link error has no failure detail, return the wire message's
// error.
if l.FailureDetail == FailureDetailNone {
if l.FailureDetail == nil {
return l.msg.Error()
}

return fmt.Sprintf("%v: %v", l.msg.Error(), l.FailureDetail)
return fmt.Sprintf("%v: %v", l.msg.Error(),
l.FailureDetail.FailureString())
}

// ForwardingError wraps an lnwire.FailureMessage in a struct that also
Expand Down
97 changes: 72 additions & 25 deletions htlcswitch/failure_detail.go
@@ -1,58 +1,105 @@
package htlcswitch

// FailureDetail is an enum which is used to enrich failures with
// additional information.
type FailureDetail int
import "github.com/lightningnetwork/lnd/invoices"

// FailureDetail is an interface implemented by failures that occur on
// our incoming or outgoing link, or within the switch itself.
type FailureDetail interface {
// FailureString returns the string representation of a failure
// detail.
FailureString() string
}

// OutgoingFailure is an enum which is used to enrich failures which occur in
// the switch or on our outgoing link with additional metadata.
type OutgoingFailure int

const (
// FailureDetailNone is returned when the wire message contains
// OutgoingFailureNone is returned when the wire message contains
// sufficient information.
FailureDetailNone = iota
OutgoingFailureNone OutgoingFailure = iota

// FailureDetailOnionDecode indicates that we could not decode an
// OutgoingFailureOnionDecode indicates that we could not decode an
// onion error.
FailureDetailOnionDecode
OutgoingFailureOnionDecode

// FailureDetailLinkNotEligible indicates that a routing attempt was
// OutgoingFailureLinkNotEligible indicates that a routing attempt was
// made over a link that is not eligible for routing.
FailureDetailLinkNotEligible
OutgoingFailureLinkNotEligible

// FailureDetailOnChainTimeout indicates that a payment had to be timed
// out on chain before it got past the first hop by us or the remote
// party.
FailureDetailOnChainTimeout
// OutgoingFailureOnChainTimeout indicates that a payment had to be
// timed out on chain before it got past the first hop by us or the
// remote party.
OutgoingFailureOnChainTimeout

// FailureDetailHTLCExceedsMax is returned when a htlc exceeds our
// OutgoingFailureHTLCExceedsMax is returned when a htlc exceeds our
// policy's maximum htlc amount.
FailureDetailHTLCExceedsMax
OutgoingFailureHTLCExceedsMax

// FailureDetailInsufficientBalance is returned when we cannot route a
// OutgoingFailureInsufficientBalance is returned when we cannot route a
// htlc due to insufficient outgoing capacity.
FailureDetailInsufficientBalance
OutgoingFailureInsufficientBalance

// OutgoingFailureIncompleteForward is returned when we cancel an
// incomplete forward
OutgoingFailureIncompleteForward

// OutgoingFailureDownstreamHtlcAdd is returned when we fail to add a
// downstream htlc to our outgoing link.
OutgoingFailureDownstreamHtlcAdd
)

// String returns the string representation of a failure detail.
func (fd FailureDetail) String() string {
// FailureString returns the string representation of a failure detail.
//
// Note: it is part of the FailureDetail interface.
func (fd OutgoingFailure) FailureString() string {
switch fd {
case FailureDetailNone:
case OutgoingFailureNone:
return "no failure detail"

case FailureDetailOnionDecode:
case OutgoingFailureOnionDecode:
return "could not decode onion"

case FailureDetailLinkNotEligible:
case OutgoingFailureLinkNotEligible:
return "link not eligible"

case FailureDetailOnChainTimeout:
case OutgoingFailureOnChainTimeout:
return "payment was resolved on-chain, then canceled back"

case FailureDetailHTLCExceedsMax:
case OutgoingFailureHTLCExceedsMax:
return "htlc exceeds maximum policy amount"

case FailureDetailInsufficientBalance:
case OutgoingFailureInsufficientBalance:
return "insufficient bandwidth to route htlc"

case OutgoingFailureIncompleteForward:
return "incomplete forward"

case OutgoingFailureDownstreamHtlcAdd:
return "could not add downstream htlc"

default:
return "unknown failure detail"
}
}

// invoiceFailure is an implementation of the failure detail interface
// for failures related to our own invoices. It wraps a failure resolution
// result, since the reasons for failure are already enumerated in the
// invoices package.
type invoiceFailure struct {
failureResolution invoices.FailureResolutionResult
}

// FailureString returns the string representation of a failure detail.
//
// Note: it is part of the FailureDetail interface.
func (i *invoiceFailure) FailureString() string {
return i.failureResolution.ResultString()
}

// newInvoiceFailure returns an invoice failure which wraps the
// failure resolution provided for use as a failure detail.
func newInvoiceFailure(result invoices.FailureResolutionResult) *invoiceFailure {
return &invoiceFailure{failureResolution: result}
}

0 comments on commit 2b14ab2

Please sign in to comment.