Merge pull request #3843 from carlaKC/htlcnotifier-0-switcherrors

[htlcnotifier 1/4]: Introduce Internal Errors
This commit is contained in:
Carla Kirk-Cohen 2020-01-15 08:46:38 +02:00 committed by GitHub
commit 7d356458e3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 422 additions and 237 deletions

@ -9,6 +9,76 @@ import (
"github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/lnwire"
) )
// ClearTextError is an interface which is implemented by errors that occur
// when we know the underlying wire failure message. These errors are the
// opposite to opaque errors which are onion-encrypted blobs only understandable
// to the initiating node. ClearTextErrors are used when we fail a htlc at our
// node, or one of our initiated payments failed and we can decrypt the onion
// encrypted error fully.
type ClearTextError interface {
error
// WireMessage extracts a valid wire failure message from an internal
// error which may contain additional metadata (which should not be
// exposed to the network). This value may be nil in the case where
// an unknown wire error is returned by one of our peers.
WireMessage() lnwire.FailureMessage
}
// LinkError is an implementation of the ClearTextError interface which
// represents failures that occur on our incoming or outgoing link.
type LinkError struct {
// msg returns the wire failure associated with the error.
// This value should *not* be nil, because we should always
// know the failure type for failures which occur at our own
// node.
msg lnwire.FailureMessage
// FailureDetail enriches the wire error with additional information.
FailureDetail
}
// NewLinkError returns a LinkError with the failure message provided.
// The failure message provided should *not* be nil, because we should
// always know the failure type for failures which occur at our own node.
func NewLinkError(msg lnwire.FailureMessage) *LinkError {
return &LinkError{msg: msg}
}
// NewDetailedLinkError returns a link error that enriches a wire message with
// a failure detail.
func NewDetailedLinkError(msg lnwire.FailureMessage,
detail FailureDetail) *LinkError {
return &LinkError{
msg: msg,
FailureDetail: detail,
}
}
// WireMessage extracts a valid wire failure message from an internal
// error which may contain additional metadata (which should not be
// exposed to the network). This value should never be nil for LinkErrors,
// because we are the ones failing the htlc.
//
// Note this is part of the ClearTextError interface.
func (l *LinkError) WireMessage() lnwire.FailureMessage {
return l.msg
}
// Error returns the string representation of a link error.
//
// Note this is part of the ClearTextError interface.
func (l *LinkError) Error() string {
// If the link error has no failure detail, return the wire message's
// error.
if l.FailureDetail == FailureDetailNone {
return l.msg.Error()
}
return fmt.Sprintf("%v: %v", l.msg.Error(), l.FailureDetail)
}
// ForwardingError wraps an lnwire.FailureMessage in a struct that also // ForwardingError wraps an lnwire.FailureMessage in a struct that also
// includes the source of the error. // includes the source of the error.
type ForwardingError struct { type ForwardingError struct {
@ -18,28 +88,51 @@ type ForwardingError struct {
// zero is the self node. // zero is the self node.
FailureSourceIdx int FailureSourceIdx int
// ExtraMsg is an additional error message that callers can provide in // msg is the wire message associated with the error. This value may
// order to provide context specific error details. // be nil in the case where we fail to decode failure message sent by
ExtraMsg string // a peer.
msg lnwire.FailureMessage
}
lnwire.FailureMessage // WireMessage extracts a valid wire failure message from an internal
// error which may contain additional metadata (which should not be
// exposed to the network). This value may be nil in the case where
// an unknown wire error is returned by one of our peers.
//
// Note this is part of the ClearTextError interface.
func (f *ForwardingError) WireMessage() lnwire.FailureMessage {
return f.msg
} }
// Error implements the built-in error interface. We use this method to allow // Error implements the built-in error interface. We use this method to allow
// the switch or any callers to insert additional context to the error message // the switch or any callers to insert additional context to the error message
// returned. // returned.
func (f *ForwardingError) Error() string { func (f *ForwardingError) Error() string {
if f.ExtraMsg == "" {
return fmt.Sprintf(
"%v@%v", f.FailureMessage, f.FailureSourceIdx,
)
}
return fmt.Sprintf( return fmt.Sprintf(
"%v@%v: %v", f.FailureMessage, f.FailureSourceIdx, f.ExtraMsg, "%v@%v", f.msg, f.FailureSourceIdx,
) )
} }
// NewForwardingError creates a new payment error which wraps a wire error
// with additional metadata.
func NewForwardingError(failure lnwire.FailureMessage,
index int) *ForwardingError {
return &ForwardingError{
FailureSourceIdx: index,
msg: failure,
}
}
// NewUnknownForwardingError returns a forwarding error which has a nil failure
// message. This constructor should only be used in the case where we cannot
// decode the failure we have received from a peer.
func NewUnknownForwardingError(index int) *ForwardingError {
return &ForwardingError{
FailureSourceIdx: index,
}
}
// ErrorDecrypter is an interface that is used to decrypt the onion encrypted // ErrorDecrypter is an interface that is used to decrypt the onion encrypted
// failure reason an extra out a well formed error. // failure reason an extra out a well formed error.
type ErrorDecrypter interface { type ErrorDecrypter interface {
@ -94,15 +187,10 @@ func (s *SphinxErrorDecrypter) DecryptError(reason lnwire.OpaqueReason) (
r := bytes.NewReader(failure.Message) r := bytes.NewReader(failure.Message)
failureMsg, err := lnwire.DecodeFailure(r, 0) failureMsg, err := lnwire.DecodeFailure(r, 0)
if err != nil { if err != nil {
return &ForwardingError{ return NewUnknownForwardingError(failure.SenderIdx), nil
FailureSourceIdx: failure.SenderIdx,
}, nil
} }
return &ForwardingError{ return NewForwardingError(failureMsg, failure.SenderIdx), nil
FailureSourceIdx: failure.SenderIdx,
FailureMessage: failureMsg,
}, nil
} }
// A compile time check to ensure ErrorDecrypter implements the Deobfuscator // A compile time check to ensure ErrorDecrypter implements the Deobfuscator

@ -0,0 +1,58 @@
package htlcswitch
// FailureDetail is an enum which is used to enrich failures with
// additional information.
type FailureDetail int
const (
// FailureDetailNone is returned when the wire message contains
// sufficient information.
FailureDetailNone = iota
// FailureDetailOnionDecode indicates that we could not decode an
// onion error.
FailureDetailOnionDecode
// FailureDetailLinkNotEligible indicates that a routing attempt was
// made over a link that is not eligible for routing.
FailureDetailLinkNotEligible
// 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
// FailureDetailHTLCExceedsMax is returned when a htlc exceeds our
// policy's maximum htlc amount.
FailureDetailHTLCExceedsMax
// FailureDetailInsufficientBalance is returned when we cannot route a
// htlc due to insufficient outgoing capacity.
FailureDetailInsufficientBalance
)
// String returns the string representation of a failure detail.
func (fd FailureDetail) String() string {
switch fd {
case FailureDetailNone:
return "no failure detail"
case FailureDetailOnionDecode:
return "could not decode onion"
case FailureDetailLinkNotEligible:
return "link not eligible"
case FailureDetailOnChainTimeout:
return "payment was resolved on-chain, then canceled back"
case FailureDetailHTLCExceedsMax:
return "htlc exceeds maximum policy amount"
case FailureDetailInsufficientBalance:
return "insufficient bandwidth to route htlc"
default:
return "unknown failure detail"
}
}

@ -102,20 +102,21 @@ type ChannelLink interface {
// CheckHtlcForward should return a nil error if the passed HTLC details // CheckHtlcForward should return a nil error if the passed HTLC details
// satisfy the current forwarding policy fo the target link. Otherwise, // satisfy the current forwarding policy fo the target link. Otherwise,
// a valid protocol failure message should be returned in order to // a LinkError with a valid protocol failure message should be returned
// signal to the source of the HTLC, the policy consistency issue. // in order to signal to the source of the HTLC, the policy consistency
// issue.
CheckHtlcForward(payHash [32]byte, incomingAmt lnwire.MilliSatoshi, CheckHtlcForward(payHash [32]byte, incomingAmt lnwire.MilliSatoshi,
amtToForward lnwire.MilliSatoshi, amtToForward lnwire.MilliSatoshi,
incomingTimeout, outgoingTimeout uint32, incomingTimeout, outgoingTimeout uint32,
heightNow uint32) lnwire.FailureMessage heightNow uint32) *LinkError
// CheckHtlcTransit should return a nil error if the passed HTLC details // CheckHtlcTransit should return a nil error if the passed HTLC details
// satisfy the current channel policy. Otherwise, a valid protocol // satisfy the current channel policy. Otherwise, a LinkError with a
// failure message should be returned in order to signal the violation. // valid protocol failure message should be returned in order to signal
// This call is intended to be used for locally initiated payments for // the violation. This call is intended to be used for locally initiated
// which there is no corresponding incoming htlc. // payments for which there is no corresponding incoming htlc.
CheckHtlcTransit(payHash [32]byte, amt lnwire.MilliSatoshi, CheckHtlcTransit(payHash [32]byte, amt lnwire.MilliSatoshi,
timeout uint32, heightNow uint32) lnwire.FailureMessage timeout uint32, heightNow uint32) *LinkError
// Bandwidth returns the amount of milli-satoshis which current link // Bandwidth returns the amount of milli-satoshis which current link
// might pass through channel link. The value returned from this method // might pass through channel link. The value returned from this method

@ -2135,16 +2135,17 @@ func (l *channelLink) UpdateForwardingPolicy(newPolicy ForwardingPolicy) {
l.cfg.FwrdingPolicy = newPolicy l.cfg.FwrdingPolicy = newPolicy
} }
// CheckHtlcForward should return a nil error if the passed HTLC details satisfy // CheckHtlcForward should return a nil error if the passed HTLC details
// the current forwarding policy fo the target link. Otherwise, a valid // satisfy the current forwarding policy fo the target link. Otherwise,
// protocol failure message should be returned in order to signal to the source // a LinkError with a valid protocol failure message should be returned
// of the HTLC, the policy consistency issue. // in order to signal to the source of the HTLC, the policy consistency
// issue.
// //
// NOTE: Part of the ChannelLink interface. // NOTE: Part of the ChannelLink interface.
func (l *channelLink) CheckHtlcForward(payHash [32]byte, func (l *channelLink) CheckHtlcForward(payHash [32]byte,
incomingHtlcAmt, amtToForward lnwire.MilliSatoshi, incomingHtlcAmt, amtToForward lnwire.MilliSatoshi,
incomingTimeout, outgoingTimeout uint32, incomingTimeout, outgoingTimeout uint32,
heightNow uint32) lnwire.FailureMessage { heightNow uint32) *LinkError {
l.RLock() l.RLock()
policy := l.cfg.FwrdingPolicy policy := l.cfg.FwrdingPolicy
@ -2176,14 +2177,14 @@ func (l *channelLink) CheckHtlcForward(payHash [32]byte,
// As part of the returned error, we'll send our latest routing // As part of the returned error, we'll send our latest routing
// policy so the sending node obtains the most up to date data. // policy so the sending node obtains the most up to date data.
failure := l.createFailureWithUpdate(
return l.createFailureWithUpdate(
func(upd *lnwire.ChannelUpdate) lnwire.FailureMessage { func(upd *lnwire.ChannelUpdate) lnwire.FailureMessage {
return lnwire.NewFeeInsufficient( return lnwire.NewFeeInsufficient(
amtToForward, *upd, amtToForward, *upd,
) )
}, },
) )
return NewLinkError(failure)
} }
// Finally, we'll ensure that the time-lock on the outgoing HTLC meets // Finally, we'll ensure that the time-lock on the outgoing HTLC meets
@ -2198,26 +2199,27 @@ func (l *channelLink) CheckHtlcForward(payHash [32]byte,
// Grab the latest routing policy so the sending node is up to // Grab the latest routing policy so the sending node is up to
// date with our current policy. // date with our current policy.
return l.createFailureWithUpdate( failure := l.createFailureWithUpdate(
func(upd *lnwire.ChannelUpdate) lnwire.FailureMessage { func(upd *lnwire.ChannelUpdate) lnwire.FailureMessage {
return lnwire.NewIncorrectCltvExpiry( return lnwire.NewIncorrectCltvExpiry(
incomingTimeout, *upd, incomingTimeout, *upd,
) )
}, },
) )
return NewLinkError(failure)
} }
return nil return nil
} }
// CheckHtlcTransit should return a nil error if the passed HTLC details satisfy the // CheckHtlcTransit should return a nil error if the passed HTLC details
// current channel policy. Otherwise, a valid protocol failure message should // satisfy the current channel policy. Otherwise, a LinkError with a
// be returned in order to signal the violation. This call is intended to be // valid protocol failure message should be returned in order to signal
// used for locally initiated payments for which there is no corresponding // the violation. This call is intended to be used for locally initiated
// incoming htlc. // payments for which there is no corresponding incoming htlc.
func (l *channelLink) CheckHtlcTransit(payHash [32]byte, func (l *channelLink) CheckHtlcTransit(payHash [32]byte,
amt lnwire.MilliSatoshi, timeout uint32, amt lnwire.MilliSatoshi, timeout uint32,
heightNow uint32) lnwire.FailureMessage { heightNow uint32) *LinkError {
l.RLock() l.RLock()
policy := l.cfg.FwrdingPolicy policy := l.cfg.FwrdingPolicy
@ -2232,7 +2234,7 @@ func (l *channelLink) CheckHtlcTransit(payHash [32]byte,
// the channel's amount and time lock constraints. // the channel's amount and time lock constraints.
func (l *channelLink) canSendHtlc(policy ForwardingPolicy, func (l *channelLink) canSendHtlc(policy ForwardingPolicy,
payHash [32]byte, amt lnwire.MilliSatoshi, timeout uint32, payHash [32]byte, amt lnwire.MilliSatoshi, timeout uint32,
heightNow uint32) lnwire.FailureMessage { heightNow uint32) *LinkError {
// As our first sanity check, we'll ensure that the passed HTLC isn't // As our first sanity check, we'll ensure that the passed HTLC isn't
// too small for the next hop. If so, then we'll cancel the HTLC // too small for the next hop. If so, then we'll cancel the HTLC
@ -2244,13 +2246,14 @@ func (l *channelLink) canSendHtlc(policy ForwardingPolicy,
// As part of the returned error, we'll send our latest routing // As part of the returned error, we'll send our latest routing
// policy so the sending node obtains the most up to date data. // policy so the sending node obtains the most up to date data.
return l.createFailureWithUpdate( failure := l.createFailureWithUpdate(
func(upd *lnwire.ChannelUpdate) lnwire.FailureMessage { func(upd *lnwire.ChannelUpdate) lnwire.FailureMessage {
return lnwire.NewAmountBelowMinimum( return lnwire.NewAmountBelowMinimum(
amt, *upd, amt, *upd,
) )
}, },
) )
return NewLinkError(failure)
} }
// Next, ensure that the passed HTLC isn't too large. If so, we'll // Next, ensure that the passed HTLC isn't too large. If so, we'll
@ -2261,11 +2264,12 @@ func (l *channelLink) canSendHtlc(policy ForwardingPolicy,
// As part of the returned error, we'll send our latest routing // As part of the returned error, we'll send our latest routing
// policy so the sending node obtains the most up-to-date data. // policy so the sending node obtains the most up-to-date data.
return l.createFailureWithUpdate( failure := l.createFailureWithUpdate(
func(upd *lnwire.ChannelUpdate) lnwire.FailureMessage { func(upd *lnwire.ChannelUpdate) lnwire.FailureMessage {
return lnwire.NewTemporaryChannelFailure(upd) return lnwire.NewTemporaryChannelFailure(upd)
}, },
) )
return NewDetailedLinkError(failure, FailureDetailHTLCExceedsMax)
} }
// We want to avoid offering an HTLC which will expire in the near // We want to avoid offering an HTLC which will expire in the near
@ -2275,12 +2279,12 @@ func (l *channelLink) canSendHtlc(policy ForwardingPolicy,
l.log.Errorf("htlc(%x) has an expiry that's too soon: "+ l.log.Errorf("htlc(%x) has an expiry that's too soon: "+
"outgoing_expiry=%v, best_height=%v", payHash[:], "outgoing_expiry=%v, best_height=%v", payHash[:],
timeout, heightNow) timeout, heightNow)
failure := l.createFailureWithUpdate(
return l.createFailureWithUpdate(
func(upd *lnwire.ChannelUpdate) lnwire.FailureMessage { func(upd *lnwire.ChannelUpdate) lnwire.FailureMessage {
return lnwire.NewExpiryTooSoon(*upd) return lnwire.NewExpiryTooSoon(*upd)
}, },
) )
return NewLinkError(failure)
} }
// Check absolute max delta. // Check absolute max delta.
@ -2289,16 +2293,19 @@ func (l *channelLink) canSendHtlc(policy ForwardingPolicy,
"the future: got %v, but maximum is %v", payHash[:], "the future: got %v, but maximum is %v", payHash[:],
timeout-heightNow, l.cfg.MaxOutgoingCltvExpiry) timeout-heightNow, l.cfg.MaxOutgoingCltvExpiry)
return &lnwire.FailExpiryTooFar{} return NewLinkError(&lnwire.FailExpiryTooFar{})
} }
// Check to see if there is enough balance in this channel. // Check to see if there is enough balance in this channel.
if amt > l.Bandwidth() { if amt > l.Bandwidth() {
return l.createFailureWithUpdate( failure := l.createFailureWithUpdate(
func(upd *lnwire.ChannelUpdate) lnwire.FailureMessage { func(upd *lnwire.ChannelUpdate) lnwire.FailureMessage {
return lnwire.NewTemporaryChannelFailure(upd) return lnwire.NewTemporaryChannelFailure(upd)
}, },
) )
return NewDetailedLinkError(
failure, FailureDetailInsufficientBalance,
)
} }
return nil return nil

@ -574,12 +574,12 @@ func TestExitNodeTimelockPayloadMismatch(t *testing.T) {
t.Fatalf("payment should have failed but didn't") t.Fatalf("payment should have failed but didn't")
} }
ferr, ok := err.(*ForwardingError) rtErr, ok := err.(ClearTextError)
if !ok { if !ok {
t.Fatalf("expected a ForwardingError, instead got: %T", err) t.Fatalf("expected a ClearTextError, instead got: %T", err)
} }
switch ferr.FailureMessage.(type) { switch rtErr.WireMessage().(type) {
case *lnwire.FailFinalIncorrectCltvExpiry: case *lnwire.FailFinalIncorrectCltvExpiry:
default: default:
t.Fatalf("incorrect error, expected incorrect cltv expiry, "+ t.Fatalf("incorrect error, expected incorrect cltv expiry, "+
@ -674,12 +674,12 @@ func TestLinkForwardTimelockPolicyMismatch(t *testing.T) {
t.Fatalf("payment should have failed but didn't") t.Fatalf("payment should have failed but didn't")
} }
ferr, ok := err.(*ForwardingError) rtErr, ok := err.(ClearTextError)
if !ok { if !ok {
t.Fatalf("expected a ForwardingError, instead got: %T", err) t.Fatalf("expected a ClearTextError, instead got: %T", err)
} }
switch ferr.FailureMessage.(type) { switch rtErr.WireMessage().(type) {
case *lnwire.FailIncorrectCltvExpiry: case *lnwire.FailIncorrectCltvExpiry:
default: default:
t.Fatalf("incorrect error, expected incorrect cltv expiry, "+ t.Fatalf("incorrect error, expected incorrect cltv expiry, "+
@ -732,12 +732,12 @@ func TestLinkForwardFeePolicyMismatch(t *testing.T) {
t.Fatalf("payment should have failed but didn't") t.Fatalf("payment should have failed but didn't")
} }
ferr, ok := err.(*ForwardingError) rtErr, ok := err.(ClearTextError)
if !ok { if !ok {
t.Fatalf("expected a ForwardingError, instead got: %T", err) t.Fatalf("expected a ClearTextError, instead got: %T", err)
} }
switch ferr.FailureMessage.(type) { switch rtErr.WireMessage().(type) {
case *lnwire.FailFeeInsufficient: case *lnwire.FailFeeInsufficient:
default: default:
t.Fatalf("incorrect error, expected fee insufficient, "+ t.Fatalf("incorrect error, expected fee insufficient, "+
@ -790,12 +790,12 @@ func TestLinkForwardMinHTLCPolicyMismatch(t *testing.T) {
t.Fatalf("payment should have failed but didn't") t.Fatalf("payment should have failed but didn't")
} }
ferr, ok := err.(*ForwardingError) rtErr, ok := err.(ClearTextError)
if !ok { if !ok {
t.Fatalf("expected a ForwardingError, instead got: %T", err) t.Fatalf("expected a ClearTextError, instead got: %T", err)
} }
switch ferr.FailureMessage.(type) { switch rtErr.WireMessage().(type) {
case *lnwire.FailAmountBelowMinimum: case *lnwire.FailAmountBelowMinimum:
default: default:
t.Fatalf("incorrect error, expected amount below minimum, "+ t.Fatalf("incorrect error, expected amount below minimum, "+
@ -857,12 +857,12 @@ func TestLinkForwardMaxHTLCPolicyMismatch(t *testing.T) {
t.Fatalf("payment should have failed but didn't") t.Fatalf("payment should have failed but didn't")
} }
ferr, ok := err.(*ForwardingError) rtErr, ok := err.(ClearTextError)
if !ok { if !ok {
t.Fatalf("expected a ForwardingError, instead got: %T", err) t.Fatalf("expected a ClearTextError, instead got: %T", err)
} }
switch ferr.FailureMessage.(type) { switch rtErr.WireMessage().(type) {
case *lnwire.FailTemporaryChannelFailure: case *lnwire.FailTemporaryChannelFailure:
default: default:
t.Fatalf("incorrect error, expected temporary channel failure, "+ t.Fatalf("incorrect error, expected temporary channel failure, "+
@ -964,11 +964,12 @@ func TestUpdateForwardingPolicy(t *testing.T) {
t.Fatalf("payment should've been rejected") t.Fatalf("payment should've been rejected")
} }
ferr, ok := err.(*ForwardingError) rtErr, ok := err.(ClearTextError)
if !ok { if !ok {
t.Fatalf("expected a ForwardingError, instead got (%T): %v", err, err) t.Fatalf("expected a ClearTextError, instead got (%T): %v", err, err)
} }
switch ferr.FailureMessage.(type) {
switch rtErr.WireMessage().(type) {
case *lnwire.FailFeeInsufficient: case *lnwire.FailFeeInsufficient:
default: default:
t.Fatalf("expected FailFeeInsufficient instead got: %v", err) t.Fatalf("expected FailFeeInsufficient instead got: %v", err)
@ -1003,12 +1004,13 @@ func TestUpdateForwardingPolicy(t *testing.T) {
t.Fatalf("payment should've been rejected") t.Fatalf("payment should've been rejected")
} }
ferr, ok = err.(*ForwardingError) rtErr, ok = err.(ClearTextError)
if !ok { if !ok {
t.Fatalf("expected a ForwardingError, instead got (%T): %v", t.Fatalf("expected a ClearTextError, instead got (%T): %v",
err, err) err, err)
} }
switch ferr.FailureMessage.(type) {
switch rtErr.WireMessage().(type) {
case *lnwire.FailTemporaryChannelFailure: case *lnwire.FailTemporaryChannelFailure:
default: default:
t.Fatalf("expected TemporaryChannelFailure, instead got: %v", t.Fatalf("expected TemporaryChannelFailure, instead got: %v",
@ -1249,13 +1251,14 @@ func TestChannelLinkMultiHopUnknownNextHop(t *testing.T) {
if err == nil { if err == nil {
t.Fatal("error haven't been received") t.Fatal("error haven't been received")
} }
fErr, ok := err.(*ForwardingError) rtErr, ok := err.(ClearTextError)
if !ok { if !ok {
t.Fatalf("expected ForwardingError") t.Fatalf("expected ClearTextError")
} }
if _, ok = fErr.FailureMessage.(*lnwire.FailUnknownNextPeer); !ok {
if _, ok = rtErr.WireMessage().(*lnwire.FailUnknownNextPeer); !ok {
t.Fatalf("wrong error has been received: %T", t.Fatalf("wrong error has been received: %T",
fErr.FailureMessage) rtErr.WireMessage())
} }
// Wait for Alice to receive the revocation. // Wait for Alice to receive the revocation.
@ -1364,12 +1367,12 @@ func TestChannelLinkMultiHopDecodeError(t *testing.T) {
t.Fatal("error haven't been received") t.Fatal("error haven't been received")
} }
ferr, ok := err.(*ForwardingError) rtErr, ok := err.(ClearTextError)
if !ok { if !ok {
t.Fatalf("expected a ForwardingError, instead got: %T", err) t.Fatalf("expected a ClearTextError, instead got: %T", err)
} }
switch ferr.FailureMessage.(type) { switch rtErr.WireMessage().(type) {
case *lnwire.FailInvalidOnionVersion: case *lnwire.FailInvalidOnionVersion:
default: default:
t.Fatalf("wrong error have been received: %v", err) t.Fatalf("wrong error have been received: %v", err)
@ -1456,13 +1459,13 @@ func TestChannelLinkExpiryTooSoonExitNode(t *testing.T) {
"time lock value") "time lock value")
} }
ferr, ok := err.(*ForwardingError) rtErr, ok := err.(ClearTextError)
if !ok { if !ok {
t.Fatalf("expected a ForwardingError, instead got: %T %v", t.Fatalf("expected a ClearTextError, instead got: %T %v",
err, err) rtErr, err)
} }
switch ferr.FailureMessage.(type) { switch rtErr.WireMessage().(type) {
case *lnwire.FailIncorrectDetails: case *lnwire.FailIncorrectDetails:
default: default:
t.Fatalf("expected incorrect_or_unknown_payment_details, "+ t.Fatalf("expected incorrect_or_unknown_payment_details, "+
@ -1519,12 +1522,13 @@ func TestChannelLinkExpiryTooSoonMidNode(t *testing.T) {
"time lock value") "time lock value")
} }
ferr, ok := err.(*ForwardingError) rtErr, ok := err.(ClearTextError)
if !ok { if !ok {
t.Fatalf("expected a ForwardingError, instead got: %T: %v", err, err) t.Fatalf("expected a ClearTextError, instead got: %T: %v",
rtErr, err)
} }
switch ferr.FailureMessage.(type) { switch rtErr.WireMessage().(type) {
case *lnwire.FailExpiryTooSoon: case *lnwire.FailExpiryTooSoon:
default: default:
t.Fatalf("incorrect error, expected final time lock too "+ t.Fatalf("incorrect error, expected final time lock too "+
@ -5537,7 +5541,7 @@ func TestCheckHtlcForward(t *testing.T) {
t.Run("below minhtlc", func(t *testing.T) { t.Run("below minhtlc", func(t *testing.T) {
result := link.CheckHtlcForward(hash, 100, 50, result := link.CheckHtlcForward(hash, 100, 50,
200, 150, 0) 200, 150, 0)
if _, ok := result.(*lnwire.FailAmountBelowMinimum); !ok { if _, ok := result.WireMessage().(*lnwire.FailAmountBelowMinimum); !ok {
t.Fatalf("expected FailAmountBelowMinimum failure code") t.Fatalf("expected FailAmountBelowMinimum failure code")
} }
}) })
@ -5545,7 +5549,7 @@ func TestCheckHtlcForward(t *testing.T) {
t.Run("above maxhtlc", func(t *testing.T) { t.Run("above maxhtlc", func(t *testing.T) {
result := link.CheckHtlcForward(hash, 1500, 1200, result := link.CheckHtlcForward(hash, 1500, 1200,
200, 150, 0) 200, 150, 0)
if _, ok := result.(*lnwire.FailTemporaryChannelFailure); !ok { if _, ok := result.WireMessage().(*lnwire.FailTemporaryChannelFailure); !ok {
t.Fatalf("expected FailTemporaryChannelFailure failure code") t.Fatalf("expected FailTemporaryChannelFailure failure code")
} }
}) })
@ -5553,7 +5557,7 @@ func TestCheckHtlcForward(t *testing.T) {
t.Run("insufficient fee", func(t *testing.T) { t.Run("insufficient fee", func(t *testing.T) {
result := link.CheckHtlcForward(hash, 1005, 1000, result := link.CheckHtlcForward(hash, 1005, 1000,
200, 150, 0) 200, 150, 0)
if _, ok := result.(*lnwire.FailFeeInsufficient); !ok { if _, ok := result.WireMessage().(*lnwire.FailFeeInsufficient); !ok {
t.Fatalf("expected FailFeeInsufficient failure code") t.Fatalf("expected FailFeeInsufficient failure code")
} }
}) })
@ -5561,7 +5565,7 @@ func TestCheckHtlcForward(t *testing.T) {
t.Run("expiry too soon", func(t *testing.T) { t.Run("expiry too soon", func(t *testing.T) {
result := link.CheckHtlcForward(hash, 1500, 1000, result := link.CheckHtlcForward(hash, 1500, 1000,
200, 150, 190) 200, 150, 190)
if _, ok := result.(*lnwire.FailExpiryTooSoon); !ok { if _, ok := result.WireMessage().(*lnwire.FailExpiryTooSoon); !ok {
t.Fatalf("expected FailExpiryTooSoon failure code") t.Fatalf("expected FailExpiryTooSoon failure code")
} }
}) })
@ -5569,7 +5573,7 @@ func TestCheckHtlcForward(t *testing.T) {
t.Run("incorrect cltv expiry", func(t *testing.T) { t.Run("incorrect cltv expiry", func(t *testing.T) {
result := link.CheckHtlcForward(hash, 1500, 1000, result := link.CheckHtlcForward(hash, 1500, 1000,
200, 190, 0) 200, 190, 0)
if _, ok := result.(*lnwire.FailIncorrectCltvExpiry); !ok { if _, ok := result.WireMessage().(*lnwire.FailIncorrectCltvExpiry); !ok {
t.Fatalf("expected FailIncorrectCltvExpiry failure code") t.Fatalf("expected FailIncorrectCltvExpiry failure code")
} }
@ -5579,7 +5583,7 @@ func TestCheckHtlcForward(t *testing.T) {
// Check that expiry isn't too far in the future. // Check that expiry isn't too far in the future.
result := link.CheckHtlcForward(hash, 1500, 1000, result := link.CheckHtlcForward(hash, 1500, 1000,
10200, 10100, 0) 10200, 10100, 0)
if _, ok := result.(*lnwire.FailExpiryTooFar); !ok { if _, ok := result.WireMessage().(*lnwire.FailExpiryTooFar); !ok {
t.Fatalf("expected FailExpiryTooFar failure code") t.Fatalf("expected FailExpiryTooFar failure code")
} }
}) })
@ -5632,11 +5636,11 @@ func TestChannelLinkCanceledInvoice(t *testing.T) {
// Because the invoice is canceled, we expect an unknown payment hash // Because the invoice is canceled, we expect an unknown payment hash
// result. // result.
fErr, ok := err.(*ForwardingError) rtErr, ok := err.(ClearTextError)
if !ok { if !ok {
t.Fatalf("expected ForwardingError, but got %v", err) t.Fatalf("expected ClearTextError, but got %v", err)
} }
_, ok = fErr.FailureMessage.(*lnwire.FailIncorrectDetails) _, ok = rtErr.WireMessage().(*lnwire.FailIncorrectDetails)
if !ok { if !ok {
t.Fatalf("expected unknown payment hash, but got %v", err) t.Fatalf("expected unknown payment hash, but got %v", err)
} }
@ -6213,16 +6217,16 @@ func TestChannelLinkReceiveEmptySig(t *testing.T) {
aliceLink.Stop() aliceLink.Stop()
} }
// assertFailureCode asserts that an error is of type ForwardingError and that // assertFailureCode asserts that an error is of type ClearTextError and that
// the failure code is as expected. // the failure code is as expected.
func assertFailureCode(t *testing.T, err error, code lnwire.FailCode) { func assertFailureCode(t *testing.T, err error, code lnwire.FailCode) {
fErr, ok := err.(*ForwardingError) rtErr, ok := err.(ClearTextError)
if !ok { if !ok {
t.Fatalf("expected ForwardingError but got %T", err) t.Fatalf("expected ClearTextError but got %T", err)
} }
if fErr.FailureMessage.Code() != code { if rtErr.WireMessage().Code() != code {
t.Fatalf("expected %v but got %v", t.Fatalf("expected %v but got %v",
code, fErr.FailureMessage.Code()) code, rtErr.WireMessage().Code())
} }
} }

@ -400,10 +400,7 @@ func (o *mockDeobfuscator) DecryptError(reason lnwire.OpaqueReason) (*Forwarding
return nil, err return nil, err
} }
return &ForwardingError{ return NewForwardingError(failure, 1), nil
FailureSourceIdx: 1,
FailureMessage: failure,
}, nil
} }
var _ ErrorDecrypter = (*mockDeobfuscator)(nil) var _ ErrorDecrypter = (*mockDeobfuscator)(nil)
@ -648,9 +645,9 @@ type mockChannelLink struct {
htlcID uint64 htlcID uint64
checkHtlcTransitResult lnwire.FailureMessage checkHtlcTransitResult *LinkError
checkHtlcForwardResult lnwire.FailureMessage checkHtlcForwardResult *LinkError
} }
// completeCircuit is a helper method for adding the finalized payment circuit // completeCircuit is a helper method for adding the finalized payment circuit
@ -710,14 +707,14 @@ func (f *mockChannelLink) HandleChannelUpdate(lnwire.Message) {
func (f *mockChannelLink) UpdateForwardingPolicy(_ ForwardingPolicy) { func (f *mockChannelLink) UpdateForwardingPolicy(_ ForwardingPolicy) {
} }
func (f *mockChannelLink) CheckHtlcForward([32]byte, lnwire.MilliSatoshi, func (f *mockChannelLink) CheckHtlcForward([32]byte, lnwire.MilliSatoshi,
lnwire.MilliSatoshi, uint32, uint32, uint32) lnwire.FailureMessage { lnwire.MilliSatoshi, uint32, uint32, uint32) *LinkError {
return f.checkHtlcForwardResult return f.checkHtlcForwardResult
} }
func (f *mockChannelLink) CheckHtlcTransit(payHash [32]byte, func (f *mockChannelLink) CheckHtlcTransit(payHash [32]byte,
amt lnwire.MilliSatoshi, timeout uint32, amt lnwire.MilliSatoshi, timeout uint32,
heightNow uint32) lnwire.FailureMessage { heightNow uint32) *LinkError {
return f.checkHtlcTransitResult return f.checkHtlcTransitResult
} }

@ -756,25 +756,19 @@ func (s *Switch) handleLocalDispatch(pkt *htlcPacket) error {
s.indexMtx.RUnlock() s.indexMtx.RUnlock()
if err != nil { if err != nil {
log.Errorf("Link %v not found", pkt.outgoingChanID) log.Errorf("Link %v not found", pkt.outgoingChanID)
return &ForwardingError{ return NewLinkError(&lnwire.FailUnknownNextPeer{})
FailureSourceIdx: 0,
FailureMessage: &lnwire.FailUnknownNextPeer{},
}
} }
if !link.EligibleToForward() { if !link.EligibleToForward() {
err := fmt.Errorf("Link %v is not available to forward", log.Errorf("Link %v is not available to forward",
pkt.outgoingChanID) pkt.outgoingChanID)
log.Error(err)
// The update does not need to be populated as the error // The update does not need to be populated as the error
// will be returned back to the router. // will be returned back to the router.
htlcErr := lnwire.NewTemporaryChannelFailure(nil) return NewDetailedLinkError(
return &ForwardingError{ lnwire.NewTemporaryChannelFailure(nil),
FailureSourceIdx: 0, FailureDetailLinkNotEligible,
ExtraMsg: err.Error(), )
FailureMessage: htlcErr,
}
} }
// Ensure that the htlc satisfies the outgoing channel policy. // Ensure that the htlc satisfies the outgoing channel policy.
@ -787,11 +781,7 @@ func (s *Switch) handleLocalDispatch(pkt *htlcPacket) error {
if htlcErr != nil { if htlcErr != nil {
log.Errorf("Link %v policy for local forward not "+ log.Errorf("Link %v policy for local forward not "+
"satisfied", pkt.outgoingChanID) "satisfied", pkt.outgoingChanID)
return htlcErr
return &ForwardingError{
FailureSourceIdx: 0,
FailureMessage: htlcErr,
}
} }
return link.HandleSwitchPacket(pkt) return link.HandleSwitchPacket(pkt)
@ -915,41 +905,43 @@ func (s *Switch) parseFailedPayment(deobfuscator ErrorDecrypter,
// decrypt the error, simply decode it them report back to the // decrypt the error, simply decode it them report back to the
// user. // user.
case unencrypted: case unencrypted:
var userErr string
r := bytes.NewReader(htlc.Reason) r := bytes.NewReader(htlc.Reason)
failureMsg, err := lnwire.DecodeFailure(r, 0) failureMsg, err := lnwire.DecodeFailure(r, 0)
if err != nil { if err != nil {
userErr = fmt.Sprintf("unable to decode onion "+ // If we could not decode the failure reason, return a link
"failure (hash=%v, pid=%d): %v", // error indicating that we failed to decode the onion.
paymentHash, paymentID, err) linkError := NewDetailedLinkError(
log.Error(userErr) // As this didn't even clear the link, we don't
// need to apply an update here since it goes
// directly to the router.
lnwire.NewTemporaryChannelFailure(nil),
FailureDetailOnionDecode,
)
// As this didn't even clear the link, we don't need to log.Errorf("%v: (hash=%v, pid=%d): %v",
// apply an update here since it goes directly to the linkError.FailureDetail, paymentHash, paymentID,
// router. err)
failureMsg = lnwire.NewTemporaryChannelFailure(nil)
return linkError
} }
return &ForwardingError{ // If we successfully decoded the failure reason, return it.
FailureSourceIdx: 0, return NewLinkError(failureMsg)
ExtraMsg: userErr,
FailureMessage: failureMsg,
}
// A payment had to be timed out on chain before it got past // A payment had to be timed out on chain before it got past
// the first hop. In this case, we'll report a permanent // the first hop. In this case, we'll report a permanent
// channel failure as this means us, or the remote party had to // channel failure as this means us, or the remote party had to
// go on chain. // go on chain.
case isResolution && htlc.Reason == nil: case isResolution && htlc.Reason == nil:
userErr := fmt.Sprintf("payment was resolved "+ linkError := NewDetailedLinkError(
"on-chain, then canceled back (hash=%v, pid=%d)", &lnwire.FailPermanentChannelFailure{},
FailureDetailOnChainTimeout,
)
log.Info("%v: hash=%v, pid=%d", linkError.FailureDetail,
paymentHash, paymentID) paymentHash, paymentID)
return &ForwardingError{ return linkError
FailureSourceIdx: 0,
ExtraMsg: userErr,
FailureMessage: &lnwire.FailPermanentChannelFailure{},
}
// A regular multi-hop payment error that we'll need to // A regular multi-hop payment error that we'll need to
// decrypt. // decrypt.
@ -1016,18 +1008,21 @@ func (s *Switch) handlePacketForward(packet *htlcPacket) error {
// selection process. This way we can return the error for // selection process. This way we can return the error for
// precise link that the sender selected, while optimistically // precise link that the sender selected, while optimistically
// trying all links to utilize our available bandwidth. // trying all links to utilize our available bandwidth.
linkErrs := make(map[lnwire.ShortChannelID]lnwire.FailureMessage) linkErrs := make(map[lnwire.ShortChannelID]*LinkError)
// Try to find destination channel link with appropriate // Try to find destination channel link with appropriate
// bandwidth. // bandwidth.
var destination ChannelLink var destination ChannelLink
for _, link := range interfaceLinks { for _, link := range interfaceLinks {
var failure lnwire.FailureMessage var failure *LinkError
// We'll skip any links that aren't yet eligible for // We'll skip any links that aren't yet eligible for
// forwarding. // forwarding.
if !link.EligibleToForward() { if !link.EligibleToForward() {
failure = &lnwire.FailUnknownNextPeer{} failure = NewDetailedLinkError(
&lnwire.FailUnknownNextPeer{},
FailureDetailLinkNotEligible,
)
} else { } else {
// We'll ensure that the HTLC satisfies the // We'll ensure that the HTLC satisfies the
// current forwarding conditions of this target // current forwarding conditions of this target
@ -1062,7 +1057,9 @@ func (s *Switch) handlePacketForward(packet *htlcPacket) error {
// If we can't find the error of the source, // If we can't find the error of the source,
// then we'll return an unknown next peer, // then we'll return an unknown next peer,
// though this should never happen. // though this should never happen.
linkErr = &lnwire.FailUnknownNextPeer{} linkErr = NewLinkError(
&lnwire.FailUnknownNextPeer{},
)
log.Warnf("unable to find err source for "+ log.Warnf("unable to find err source for "+
"outgoing_link=%v, errors=%v", "outgoing_link=%v, errors=%v",
packet.outgoingChanID, newLogClosure(func() string { packet.outgoingChanID, newLogClosure(func() string {
@ -1075,7 +1072,7 @@ func (s *Switch) handlePacketForward(packet *htlcPacket) error {
htlc.PaymentHash[:], packet.outgoingChanID, htlc.PaymentHash[:], packet.outgoingChanID,
linkErr) linkErr)
return s.failAddPacket(packet, linkErr, addErr) return s.failAddPacket(packet, linkErr.WireMessage(), addErr)
} }
// Send the packet to the destination channel link which // Send the packet to the destination channel link which

@ -1290,7 +1290,7 @@ func TestSwitchForwardCircuitPersistence(t *testing.T) {
type multiHopFwdTest struct { type multiHopFwdTest struct {
name string name string
eligible1, eligible2 bool eligible1, eligible2 bool
failure1, failure2 lnwire.FailureMessage failure1, failure2 *LinkError
expectedReply lnwire.FailCode expectedReply lnwire.FailCode
} }
@ -1308,9 +1308,11 @@ func TestSkipIneligibleLinksMultiHopForward(t *testing.T) {
// Channel one has a policy failure and the other channel isn't // Channel one has a policy failure and the other channel isn't
// available. // available.
{ {
name: "policy fail", name: "policy fail",
eligible1: true, eligible1: true,
failure1: lnwire.NewFinalIncorrectCltvExpiry(0), failure1: NewLinkError(
lnwire.NewFinalIncorrectCltvExpiry(0),
),
expectedReply: lnwire.CodeFinalIncorrectCltvExpiry, expectedReply: lnwire.CodeFinalIncorrectCltvExpiry,
}, },
@ -1325,11 +1327,16 @@ func TestSkipIneligibleLinksMultiHopForward(t *testing.T) {
// The requested channel has insufficient bandwidth and the // The requested channel has insufficient bandwidth and the
// other channel's policy isn't satisfied. // other channel's policy isn't satisfied.
{ {
name: "non-strict policy fail", name: "non-strict policy fail",
eligible1: true, eligible1: true,
failure1: lnwire.NewTemporaryChannelFailure(nil), failure1: NewDetailedLinkError(
eligible2: true, lnwire.NewTemporaryChannelFailure(nil),
failure2: lnwire.NewFinalIncorrectCltvExpiry(0), FailureDetailInsufficientBalance,
),
eligible2: true,
failure2: NewLinkError(
lnwire.NewFinalIncorrectCltvExpiry(0),
),
expectedReply: lnwire.CodeTemporaryChannelFailure, expectedReply: lnwire.CodeTemporaryChannelFailure,
}, },
} }
@ -1482,7 +1489,9 @@ func testSkipLinkLocalForward(t *testing.T, eligible bool,
aliceChannelLink := newMockChannelLink( aliceChannelLink := newMockChannelLink(
s, chanID1, aliceChanID, alicePeer, eligible, s, chanID1, aliceChanID, alicePeer, eligible,
) )
aliceChannelLink.checkHtlcTransitResult = policyResult aliceChannelLink.checkHtlcTransitResult = NewLinkError(
policyResult,
)
if err := s.AddLink(aliceChannelLink); err != nil { if err := s.AddLink(aliceChannelLink); err != nil {
t.Fatalf("unable to add alice link: %v", err) t.Fatalf("unable to add alice link: %v", err)
} }
@ -2178,11 +2187,11 @@ func TestUpdateFailMalformedHTLCErrorConversion(t *testing.T) {
t.Fatalf("unable to send payment: %v", err) t.Fatalf("unable to send payment: %v", err)
} }
fwdingErr := err.(*ForwardingError) routingErr := err.(ClearTextError)
failureMsg := fwdingErr.FailureMessage failureMsg := routingErr.WireMessage()
if _, ok := failureMsg.(*lnwire.FailInvalidOnionKey); !ok { if _, ok := failureMsg.(*lnwire.FailInvalidOnionKey); !ok {
t.Fatalf("expected onion failure instead got: %v", t.Fatalf("expected onion failure instead got: %v",
fwdingErr.FailureMessage) routingErr.WireMessage())
} }
} }
@ -2441,14 +2450,18 @@ func TestInvalidFailure(t *testing.T) {
select { select {
case result := <-resultChan: case result := <-resultChan:
fErr, ok := result.Error.(*ForwardingError) rtErr, ok := result.Error.(ClearTextError)
if !ok { if !ok {
t.Fatal("expected ForwardingError") t.Fatal("expected ClearTextError")
} }
if fErr.FailureSourceIdx != 2 { source, ok := rtErr.(*ForwardingError)
if !ok {
t.Fatalf("expected forwarding error, got: %T", rtErr)
}
if source.FailureSourceIdx != 2 {
t.Fatal("unexpected error source index") t.Fatal("unexpected error source index")
} }
if fErr.FailureMessage != nil { if rtErr.WireMessage() != nil {
t.Fatal("expected empty failure message") t.Fatal("expected empty failure message")
} }

@ -326,12 +326,12 @@ func marshallError(sendError error) (*Failure, error) {
return response, nil return response, nil
} }
fErr, ok := sendError.(*htlcswitch.ForwardingError) rtErr, ok := sendError.(htlcswitch.ClearTextError)
if !ok { if !ok {
return nil, sendError return nil, sendError
} }
switch onionErr := fErr.FailureMessage.(type) { switch onionErr := rtErr.WireMessage().(type) {
case *lnwire.FailIncorrectDetails: case *lnwire.FailIncorrectDetails:
response.Code = Failure_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS response.Code = Failure_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS
@ -425,7 +425,16 @@ func marshallError(sendError error) (*Failure, error) {
return nil, fmt.Errorf("cannot marshall failure %T", onionErr) return nil, fmt.Errorf("cannot marshall failure %T", onionErr)
} }
response.FailureSourceIndex = uint32(fErr.FailureSourceIdx) // If the ClearTextError received is a ForwardingError, the error
// originated from a node along the route, not locally on our outgoing
// link. We set failureSourceIdx to the index of the node where the
// failure occurred. If the error is not a ForwardingError, the failure
// occurred at our node, so we leave the index as 0 to indicate that
// we failed locally.
fErr, ok := rtErr.(*htlcswitch.ForwardingError)
if ok {
response.FailureSourceIndex = uint32(fErr.FailureSourceIdx)
}
return response, nil return response, nil
} }

@ -35,12 +35,12 @@ func (m *mockPaymentAttemptDispatcher) SendHTLC(firstHop lnwire.ShortChannelID,
var result *htlcswitch.PaymentResult var result *htlcswitch.PaymentResult
preimage, err := m.onPayment(firstHop) preimage, err := m.onPayment(firstHop)
if err != nil { if err != nil {
fwdErr, ok := err.(*htlcswitch.ForwardingError) rtErr, ok := err.(htlcswitch.ClearTextError)
if !ok { if !ok {
return err return err
} }
result = &htlcswitch.PaymentResult{ result = &htlcswitch.PaymentResult{
Error: fwdErr, Error: rtErr,
} }
} else { } else {
result = &htlcswitch.PaymentResult{Preimage: preimage} result = &htlcswitch.PaymentResult{Preimage: preimage}

@ -1905,18 +1905,33 @@ func (r *ChannelRouter) processSendError(paymentID uint64, rt *route.Route,
return reportFail(nil, nil) return reportFail(nil, nil)
} }
// If an internal, non-forwarding error occurred, we can stop
// trying. // If the error is a ClearTextError, we have received a valid wire
fErr, ok := sendErr.(*htlcswitch.ForwardingError) // failure message, either from our own outgoing link or from a node
// down the route. If the error is not related to the propagation of
// our payment, we can stop trying because an internal error has
// occurred.
rtErr, ok := sendErr.(htlcswitch.ClearTextError)
if !ok { if !ok {
return &internalErrorReason return &internalErrorReason
} }
failureMessage := fErr.FailureMessage // failureSourceIdx is the index of the node that the failure occurred
failureSourceIdx := fErr.FailureSourceIdx // at. If the ClearTextError received is not a ForwardingError the
// payment error occurred at our node, so we leave this value as 0
// to indicate that the failure occurred locally. If the error is a
// ForwardingError, it did not originate at our node, so we set
// failureSourceIdx to the index of the node where the failure occurred.
failureSourceIdx := 0
source, ok := rtErr.(*htlcswitch.ForwardingError)
if ok {
failureSourceIdx = source.FailureSourceIdx
}
// Apply channel update if the error contains one. For unknown // Extract the wire failure and apply channel update if it contains one.
// failures, failureMessage is nil. // If we received an unknown failure message from a node along the
// route, the failure message will be nil.
failureMessage := rtErr.WireMessage()
if failureMessage != nil { if failureMessage != nil {
err := r.tryApplyChannelUpdate( err := r.tryApplyChannelUpdate(
rt, failureSourceIdx, failureMessage, rt, failureSourceIdx, failureMessage,

@ -288,11 +288,12 @@ func TestSendPaymentRouteFailureFallback(t *testing.T) {
roasbeefSongoku := lnwire.NewShortChanIDFromInt(12345) roasbeefSongoku := lnwire.NewShortChanIDFromInt(12345)
if firstHop == roasbeefSongoku { if firstHop == roasbeefSongoku {
return [32]byte{}, &htlcswitch.ForwardingError{ return [32]byte{}, htlcswitch.NewForwardingError(
FailureSourceIdx: 1, // TODO(roasbeef): temp node failure
// TODO(roasbeef): temp node failure should be? // should be?
FailureMessage: &lnwire.FailTemporaryChannelFailure{}, &lnwire.FailTemporaryChannelFailure{},
} 1,
)
} }
return preImage, nil return preImage, nil
@ -420,12 +421,12 @@ func TestChannelUpdateValidation(t *testing.T) {
// The unsigned channel update is attached to the failure message. // The unsigned channel update is attached to the failure message.
ctx.router.cfg.Payer.(*mockPaymentAttemptDispatcher).setPaymentResult( ctx.router.cfg.Payer.(*mockPaymentAttemptDispatcher).setPaymentResult(
func(firstHop lnwire.ShortChannelID) ([32]byte, error) { func(firstHop lnwire.ShortChannelID) ([32]byte, error) {
return [32]byte{}, &htlcswitch.ForwardingError{ return [32]byte{}, htlcswitch.NewForwardingError(
FailureSourceIdx: 1, &lnwire.FailFeeInsufficient{
FailureMessage: &lnwire.FailFeeInsufficient{
Update: errChanUpdate, Update: errChanUpdate,
}, },
} 1,
)
}) })
// The payment parameter is mostly redundant in SendToRoute. Can be left // The payment parameter is mostly redundant in SendToRoute. Can be left
@ -542,16 +543,15 @@ func TestSendPaymentErrorRepeatedFeeInsufficient(t *testing.T) {
roasbeefSongoku := lnwire.NewShortChanIDFromInt(chanID) roasbeefSongoku := lnwire.NewShortChanIDFromInt(chanID)
if firstHop == roasbeefSongoku { if firstHop == roasbeefSongoku {
return [32]byte{}, &htlcswitch.ForwardingError{ return [32]byte{}, htlcswitch.NewForwardingError(
FailureSourceIdx: 1, // Within our error, we'll add a
// channel update which is meant to
// Within our error, we'll add a channel update // reflect the new fee schedule for the
// which is meant to reflect he new fee // node/channel.
// schedule for the node/channel. &lnwire.FailFeeInsufficient{
FailureMessage: &lnwire.FailFeeInsufficient{
Update: errChanUpdate, Update: errChanUpdate,
}, }, 1,
} )
} }
return preImage, nil return preImage, nil
@ -646,12 +646,11 @@ func TestSendPaymentErrorNonFinalTimeLockErrors(t *testing.T) {
func(firstHop lnwire.ShortChannelID) ([32]byte, error) { func(firstHop lnwire.ShortChannelID) ([32]byte, error) {
if firstHop == roasbeefSongoku { if firstHop == roasbeefSongoku {
return [32]byte{}, &htlcswitch.ForwardingError{ return [32]byte{}, htlcswitch.NewForwardingError(
FailureSourceIdx: 1, &lnwire.FailExpiryTooSoon{
FailureMessage: &lnwire.FailExpiryTooSoon{
Update: errChanUpdate, Update: errChanUpdate,
}, }, 1,
} )
} }
return preImage, nil return preImage, nil
@ -700,12 +699,11 @@ func TestSendPaymentErrorNonFinalTimeLockErrors(t *testing.T) {
func(firstHop lnwire.ShortChannelID) ([32]byte, error) { func(firstHop lnwire.ShortChannelID) ([32]byte, error) {
if firstHop == roasbeefSongoku { if firstHop == roasbeefSongoku {
return [32]byte{}, &htlcswitch.ForwardingError{ return [32]byte{}, htlcswitch.NewForwardingError(
FailureSourceIdx: 1, &lnwire.FailIncorrectCltvExpiry{
FailureMessage: &lnwire.FailIncorrectCltvExpiry{
Update: errChanUpdate, Update: errChanUpdate,
}, }, 1,
} )
} }
return preImage, nil return preImage, nil
@ -763,20 +761,19 @@ func TestSendPaymentErrorPathPruning(t *testing.T) {
// We'll first simulate an error from the first // We'll first simulate an error from the first
// hop to simulate the channel from songoku to // hop to simulate the channel from songoku to
// sophon not having enough capacity. // sophon not having enough capacity.
return [32]byte{}, &htlcswitch.ForwardingError{ return [32]byte{}, htlcswitch.NewForwardingError(
FailureSourceIdx: 1, &lnwire.FailTemporaryChannelFailure{},
FailureMessage: &lnwire.FailTemporaryChannelFailure{}, 1,
} )
} }
// Next, we'll create an error from phan nuwen to // Next, we'll create an error from phan nuwen to
// indicate that the sophon node is not longer online, // indicate that the sophon node is not longer online,
// which should prune out the rest of the routes. // which should prune out the rest of the routes.
if firstHop == roasbeefPhanNuwen { if firstHop == roasbeefPhanNuwen {
return [32]byte{}, &htlcswitch.ForwardingError{ return [32]byte{}, htlcswitch.NewForwardingError(
FailureSourceIdx: 1, &lnwire.FailUnknownNextPeer{}, 1,
FailureMessage: &lnwire.FailUnknownNextPeer{}, )
}
} }
return preImage, nil return preImage, nil
@ -805,10 +802,10 @@ func TestSendPaymentErrorPathPruning(t *testing.T) {
func(firstHop lnwire.ShortChannelID) ([32]byte, error) { func(firstHop lnwire.ShortChannelID) ([32]byte, error) {
if firstHop == roasbeefSongoku { if firstHop == roasbeefSongoku {
return [32]byte{}, &htlcswitch.ForwardingError{ failure := htlcswitch.NewForwardingError(
FailureSourceIdx: 1, &lnwire.FailUnknownNextPeer{}, 1,
FailureMessage: &lnwire.FailUnknownNextPeer{}, )
} return [32]byte{}, failure
} }
return preImage, nil return preImage, nil
@ -851,10 +848,10 @@ func TestSendPaymentErrorPathPruning(t *testing.T) {
// We'll first simulate an error from the first // We'll first simulate an error from the first
// outgoing link to simulate the channel from luo ji to // outgoing link to simulate the channel from luo ji to
// roasbeef not having enough capacity. // roasbeef not having enough capacity.
return [32]byte{}, &htlcswitch.ForwardingError{ return [32]byte{}, htlcswitch.NewForwardingError(
FailureSourceIdx: 1, &lnwire.FailTemporaryChannelFailure{},
FailureMessage: &lnwire.FailTemporaryChannelFailure{}, 1,
} )
} }
return preImage, nil return preImage, nil
}) })
@ -2539,9 +2536,7 @@ func TestUnknownErrorSource(t *testing.T) {
// couldn't be decoded (FailureMessage is nil). // couldn't be decoded (FailureMessage is nil).
if firstHop.ToUint64() == 2 { if firstHop.ToUint64() == 2 {
return [32]byte{}, return [32]byte{},
&htlcswitch.ForwardingError{ htlcswitch.NewUnknownForwardingError(1)
FailureSourceIdx: 1,
}
} }
// Otherwise the payment succeeds. // Otherwise the payment succeeds.
@ -3105,10 +3100,10 @@ func TestRouterPaymentStateMachine(t *testing.T) {
// called, and we respond with a forwarding error // called, and we respond with a forwarding error
case sendToSwitchResultFailure: case sendToSwitchResultFailure:
select { select {
case sendResult <- &htlcswitch.ForwardingError{ case sendResult <- htlcswitch.NewForwardingError(
FailureSourceIdx: 1, &lnwire.FailTemporaryChannelFailure{},
FailureMessage: &lnwire.FailTemporaryChannelFailure{}, 1,
}: ):
case <-time.After(1 * time.Second): case <-time.After(1 * time.Second):
t.Fatalf("unable to send result") t.Fatalf("unable to send result")
} }
@ -3129,12 +3124,14 @@ func TestRouterPaymentStateMachine(t *testing.T) {
// to be called, and we respond with a forwarding // to be called, and we respond with a forwarding
// error, indicating that the router should retry. // error, indicating that the router should retry.
case getPaymentResultFailure: case getPaymentResultFailure:
failure := htlcswitch.NewForwardingError(
&lnwire.FailTemporaryChannelFailure{},
1,
)
select { select {
case getPaymentResult <- &htlcswitch.PaymentResult{ case getPaymentResult <- &htlcswitch.PaymentResult{
Error: &htlcswitch.ForwardingError{ Error: failure,
FailureSourceIdx: 1,
FailureMessage: &lnwire.FailTemporaryChannelFailure{},
},
}: }:
case <-time.After(1 * time.Second): case <-time.After(1 * time.Second):
t.Fatalf("unable to get result") t.Fatalf("unable to get result")
@ -3302,12 +3299,11 @@ func TestSendToRouteStructuredError(t *testing.T) {
// The unsigned channel update is attached to the failure message. // The unsigned channel update is attached to the failure message.
ctx.router.cfg.Payer.(*mockPaymentAttemptDispatcher).setPaymentResult( ctx.router.cfg.Payer.(*mockPaymentAttemptDispatcher).setPaymentResult(
func(firstHop lnwire.ShortChannelID) ([32]byte, error) { func(firstHop lnwire.ShortChannelID) ([32]byte, error) {
return [32]byte{}, &htlcswitch.ForwardingError{ return [32]byte{}, htlcswitch.NewForwardingError(
FailureSourceIdx: 1, &lnwire.FailFeeInsufficient{
FailureMessage: &lnwire.FailFeeInsufficient{
Update: lnwire.ChannelUpdate{}, Update: lnwire.ChannelUpdate{},
}, }, 1,
} )
}) })
// The payment parameter is mostly redundant in SendToRoute. Can be left // The payment parameter is mostly redundant in SendToRoute. Can be left
@ -3324,7 +3320,7 @@ func TestSendToRouteStructuredError(t *testing.T) {
t.Fatalf("expected forwarding error") t.Fatalf("expected forwarding error")
} }
if _, ok := fErr.FailureMessage.(*lnwire.FailFeeInsufficient); !ok { if _, ok := fErr.WireMessage().(*lnwire.FailFeeInsufficient); !ok {
t.Fatalf("expected fee insufficient error") t.Fatalf("expected fee insufficient error")
} }