Merge pull request #3188 from joostjager/error-source-idx

htlcswitch+routing+routerrpc: error source as index and expose decrypt failure
This commit is contained in:
Conner Fromknecht 2019-07-11 13:27:14 -07:00 committed by GitHub
commit a4f4ff0f83
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 716 additions and 438 deletions

2
go.mod

@ -30,7 +30,7 @@ require (
github.com/juju/version v0.0.0-20180108022336-b64dbd566305 // indirect
github.com/kkdai/bstream v0.0.0-20181106074824-b3251f7901ec
github.com/lightninglabs/neutrino v0.0.0-20190629001446-52dd89dd1aaa
github.com/lightningnetwork/lightning-onion v0.0.0-20190430041606-751fb4dd8b72
github.com/lightningnetwork/lightning-onion v0.0.0-20190703000913-ecc936dc56c9
github.com/lightningnetwork/lnd/queue v1.0.1
github.com/lightningnetwork/lnd/ticker v1.0.0
github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796

4
go.sum

@ -137,8 +137,8 @@ github.com/lightninglabs/neutrino v0.0.0-20190313035638-e1ad4c33fb18 h1:lxD7RgKY
github.com/lightninglabs/neutrino v0.0.0-20190313035638-e1ad4c33fb18/go.mod h1:v6tz6jbuAubTrRpX8ke2KH9sJxml8KlPQTKgo9mAp1Q=
github.com/lightninglabs/neutrino v0.0.0-20190629001446-52dd89dd1aaa h1:vLsdHrqHKNDShoSRkrFEM0FsNt5waIKS87IgP1YLz+g=
github.com/lightninglabs/neutrino v0.0.0-20190629001446-52dd89dd1aaa/go.mod h1:vzLU75ll8qbRJIzW5dvK/UXtR9c2FecJ6VNOM8chyVM=
github.com/lightningnetwork/lightning-onion v0.0.0-20190430041606-751fb4dd8b72 h1:KgmypyQfJnEf2vhwboKCtTp4mHxIcLeXPBPWDbPuzFQ=
github.com/lightningnetwork/lightning-onion v0.0.0-20190430041606-751fb4dd8b72/go.mod h1:Sooe/CoCqa85JxqHV+IBR2HW+6t2Cv+36awSmoccswM=
github.com/lightningnetwork/lightning-onion v0.0.0-20190703000913-ecc936dc56c9 h1:u6dbtgPtilk/HWg9GwA8GniHqzCW/7an3ZSpZARfHx4=
github.com/lightningnetwork/lightning-onion v0.0.0-20190703000913-ecc936dc56c9/go.mod h1:Sooe/CoCqa85JxqHV+IBR2HW+6t2Cv+36awSmoccswM=
github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796 h1:sjOGyegMIhvgfq5oaue6Td+hxZuf3tDC8lAPrFldqFw=
github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796/go.mod h1:3p7ZTf9V1sNPI5H8P3NkTFF4LuwMdPl2DodF60qAKqY=
github.com/ltcsuite/ltcutil v0.0.0-20181217130922-17f3b04680b6/go.mod h1:8Vg/LTOO0KYa/vlHWJ6XZAevPQThGH5sufO0Hrou/lA=

@ -6,17 +6,18 @@ import (
"io"
"github.com/btcsuite/btcd/btcec"
"github.com/lightningnetwork/lightning-onion"
sphinx "github.com/lightningnetwork/lightning-onion"
"github.com/lightningnetwork/lnd/lnwire"
)
// ForwardingError wraps an lnwire.FailureMessage in a struct that also
// includes the source of the error.
type ForwardingError struct {
// ErrorSource is the public key of the node that sent the error. With
// this information, the dispatcher of a payment can modify their set
// of candidate routes in response to the type of error extracted.
ErrorSource *btcec.PublicKey
// FailureSourceIdx is the index of the node that sent the failure. With
// this information, the dispatcher of a payment can modify their set of
// candidate routes in response to the type of failure extracted. Index
// zero is the self node.
FailureSourceIdx int
// ExtraMsg is an additional error message that callers can provide in
// order to provide context specific error details.
@ -30,10 +31,10 @@ type ForwardingError struct {
// returned.
func (f *ForwardingError) Error() string {
if f.ExtraMsg == "" {
return f.FailureMessage.Error()
return fmt.Sprintf("%v", f.FailureMessage)
}
return fmt.Sprintf("%v: %v", f.FailureMessage.Error(), f.ExtraMsg)
return fmt.Sprintf("%v: %v", f.FailureMessage, f.ExtraMsg)
}
// ErrorDecrypter is an interface that is used to decrypt the onion encrypted
@ -243,10 +244,21 @@ func (s *SphinxErrorEncrypter) Reextract(
// ErrorEncrypter interface.
var _ ErrorEncrypter = (*SphinxErrorEncrypter)(nil)
// OnionErrorDecrypter is the interface that provides onion level error
// decryption.
type OnionErrorDecrypter interface {
// DecryptError attempts to decrypt the passed encrypted error response.
// The onion failure is encrypted in backward manner, starting from the
// node where error have occurred. As a result, in order to decrypt the
// error we need get all shared secret and apply decryption in the
// reverse order.
DecryptError(encryptedData []byte) (*sphinx.DecryptedError, error)
}
// SphinxErrorDecrypter wraps the sphinx data SphinxErrorDecrypter and maps the
// returned errors to concrete lnwire.FailureMessage instances.
type SphinxErrorDecrypter struct {
*sphinx.OnionErrorDecrypter
OnionErrorDecrypter
}
// DecryptError peels off each layer of onion encryption from the first hop, to
@ -254,22 +266,27 @@ type SphinxErrorDecrypter struct {
// along with the source of the error.
//
// NOTE: Part of the ErrorDecrypter interface.
func (s *SphinxErrorDecrypter) DecryptError(reason lnwire.OpaqueReason) (*ForwardingError, error) {
func (s *SphinxErrorDecrypter) DecryptError(reason lnwire.OpaqueReason) (
*ForwardingError, error) {
source, failureData, err := s.OnionErrorDecrypter.DecryptError(reason)
failure, err := s.OnionErrorDecrypter.DecryptError(reason)
if err != nil {
return nil, err
}
r := bytes.NewReader(failureData)
// Decode the failure. If an error occurs, we leave the failure message
// field nil.
r := bytes.NewReader(failure.Message)
failureMsg, err := lnwire.DecodeFailure(r, 0)
if err != nil {
return nil, err
return &ForwardingError{
FailureSourceIdx: failure.SenderIdx,
}, nil
}
return &ForwardingError{
ErrorSource: source,
FailureMessage: failureMsg,
FailureSourceIdx: failure.SenderIdx,
FailureMessage: failureMsg,
}, nil
}

@ -1214,7 +1214,7 @@ func (l *channelLink) processHodlEvent(hodlEvent invoices.HodlEvent,
)
case invoices.CancelExpiryTooSoon:
failure = lnwire.FailFinalExpiryTooSoon{}
failure = &lnwire.FailFinalExpiryTooSoon{}
default:
return fmt.Errorf("unknown cancel reason: %v",

@ -163,10 +163,7 @@ func initSwitchWithDB(startingHeight uint32, db *channeldb.DB) (*Switch, error)
}
}
priv, _ := btcec.NewPrivateKey(btcec.S256())
pubkey := priv.PubKey()
cfg := Config{
SelfKey: pubkey,
DB: db,
SwitchPackager: channeldb.NewSwitchPackager(),
FwdingLog: &mockForwardingLog{
@ -393,12 +390,9 @@ func (o *mockDeobfuscator) DecryptError(reason lnwire.OpaqueReason) (*Forwarding
return nil, err
}
priv, _ := btcec.NewPrivateKey(btcec.S256())
pubkey := priv.PubKey()
return &ForwardingError{
ErrorSource: pubkey,
FailureMessage: failure,
FailureSourceIdx: 1,
FailureMessage: failure,
}, nil
}
@ -971,3 +965,18 @@ func (m *mockCircuitMap) NumPending() int {
func (m *mockCircuitMap) NumOpen() int {
return 0
}
type mockOnionErrorDecryptor struct {
sourceIdx int
message []byte
err error
}
func (m *mockOnionErrorDecryptor) DecryptError(encryptedData []byte) (
*sphinx.DecryptedError, error) {
return &sphinx.DecryptedError{
SenderIdx: m.sourceIdx,
Message: m.message,
}, m.err
}

@ -9,7 +9,6 @@ import (
"sync/atomic"
"time"
"github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
"github.com/coreos/bbolt"
@ -59,6 +58,10 @@ var (
// active links in the switch for a specific destination.
ErrNoLinksFound = errors.New("no channel links found")
// ErrUnreadableFailureMessage is returned when the failure message
// cannot be decrypted.
ErrUnreadableFailureMessage = errors.New("unreadable failure message")
// zeroPreimage is the empty preimage which is returned when we have
// some errors.
zeroPreimage [sha256.Size]byte
@ -112,11 +115,6 @@ type ChanClose struct {
// Config defines the configuration for the service. ALL elements within the
// configuration MUST be non-nil for the service to carry out its duties.
type Config struct {
// SelfKey is the key of the backing Lightning node. This key is used
// to properly craft failure messages, such that the Layer 3 router can
// properly route around link./vertex failures.
SelfKey *btcec.PublicKey
// FwdingLog is an interface that will be used by the switch to log
// forwarding events. A forwarding event happens each time a payment
// circuit is successfully completed. So when we forward an HTLC, and a
@ -776,8 +774,8 @@ func (s *Switch) handleLocalDispatch(pkt *htlcPacket) error {
if err != nil {
log.Errorf("Link %v not found", pkt.outgoingChanID)
return &ForwardingError{
ErrorSource: s.cfg.SelfKey,
FailureMessage: &lnwire.FailUnknownNextPeer{},
FailureSourceIdx: 0,
FailureMessage: &lnwire.FailUnknownNextPeer{},
}
}
@ -790,9 +788,9 @@ func (s *Switch) handleLocalDispatch(pkt *htlcPacket) error {
// will be returned back to the router.
htlcErr := lnwire.NewTemporaryChannelFailure(nil)
return &ForwardingError{
ErrorSource: s.cfg.SelfKey,
ExtraMsg: err.Error(),
FailureMessage: htlcErr,
FailureSourceIdx: 0,
ExtraMsg: err.Error(),
FailureMessage: htlcErr,
}
}
@ -808,8 +806,8 @@ func (s *Switch) handleLocalDispatch(pkt *htlcPacket) error {
"satisfied", pkt.outgoingChanID)
return &ForwardingError{
ErrorSource: s.cfg.SelfKey,
FailureMessage: htlcErr,
FailureSourceIdx: 0,
FailureMessage: htlcErr,
}
}
@ -823,9 +821,9 @@ func (s *Switch) handleLocalDispatch(pkt *htlcPacket) error {
// will be returned back to the router.
htlcErr := lnwire.NewTemporaryChannelFailure(nil)
return &ForwardingError{
ErrorSource: s.cfg.SelfKey,
ExtraMsg: err.Error(),
FailureMessage: htlcErr,
FailureSourceIdx: 0,
ExtraMsg: err.Error(),
FailureMessage: htlcErr,
}
}
@ -942,9 +940,7 @@ func (s *Switch) extractResult(deobfuscator ErrorDecrypter, n *networkResult,
// the payment deobfuscator.
func (s *Switch) parseFailedPayment(deobfuscator ErrorDecrypter,
paymentID uint64, paymentHash lntypes.Hash, unencrypted,
isResolution bool, htlc *lnwire.UpdateFailHTLC) *ForwardingError {
var failure *ForwardingError
isResolution bool, htlc *lnwire.UpdateFailHTLC) error {
switch {
@ -966,10 +962,11 @@ func (s *Switch) parseFailedPayment(deobfuscator ErrorDecrypter,
// router.
failureMsg = lnwire.NewTemporaryChannelFailure(nil)
}
failure = &ForwardingError{
ErrorSource: s.cfg.SelfKey,
ExtraMsg: userErr,
FailureMessage: failureMsg,
return &ForwardingError{
FailureSourceIdx: 0,
ExtraMsg: userErr,
FailureMessage: failureMsg,
}
// A payment had to be timed out on chain before it got past
@ -980,33 +977,29 @@ func (s *Switch) parseFailedPayment(deobfuscator ErrorDecrypter,
userErr := fmt.Sprintf("payment was resolved "+
"on-chain, then cancelled back (hash=%v, pid=%d)",
paymentHash, paymentID)
failure = &ForwardingError{
ErrorSource: s.cfg.SelfKey,
ExtraMsg: userErr,
FailureMessage: lnwire.FailPermanentChannelFailure{},
return &ForwardingError{
FailureSourceIdx: 0,
ExtraMsg: userErr,
FailureMessage: &lnwire.FailPermanentChannelFailure{},
}
// A regular multi-hop payment error that we'll need to
// decrypt.
default:
var err error
// We'll attempt to fully decrypt the onion encrypted
// error. If we're unable to then we'll bail early.
failure, err = deobfuscator.DecryptError(htlc.Reason)
failure, err := deobfuscator.DecryptError(htlc.Reason)
if err != nil {
userErr := fmt.Sprintf("unable to de-obfuscate "+
"onion failure (hash=%v, pid=%d): %v",
log.Errorf("unable to de-obfuscate onion failure "+
"(hash=%v, pid=%d): %v",
paymentHash, paymentID, err)
log.Error(userErr)
failure = &ForwardingError{
ErrorSource: s.cfg.SelfKey,
ExtraMsg: userErr,
FailureMessage: lnwire.NewTemporaryChannelFailure(nil),
}
}
}
return failure
return ErrUnreadableFailureMessage
}
return failure
}
}
// handlePacketForward is used in cases when we need forward the htlc update

@ -6,7 +6,6 @@ import (
"fmt"
"io"
"io/ioutil"
"strings"
"testing"
"time"
@ -1825,9 +1824,13 @@ func TestSwitchSendPayment(t *testing.T) {
select {
case err := <-errChan:
if !strings.Contains(err.Error(), lnwire.CodeUnknownPaymentHash.String()) {
t.Fatalf("expected %v got %v", err,
lnwire.CodeUnknownPaymentHash)
fErr, ok := err.(*ForwardingError)
if !ok {
t.Fatal("expected ForwardingError")
}
if _, ok := fErr.FailureMessage.(*lnwire.FailUnknownPaymentHash); !ok {
t.Fatalf("expected UnknownPaymentHash got %v", fErr)
}
case <-time.After(time.Second):
t.Fatal("err wasn't received")
@ -2238,3 +2241,140 @@ func TestSwitchGetPaymentResult(t *testing.T) {
t.Fatalf("result not received")
}
}
// TestInvalidFailure tests that the switch returns an unreadable failure error
// if the failure cannot be decrypted.
func TestInvalidFailure(t *testing.T) {
t.Parallel()
alicePeer, err := newMockServer(t, "alice", testStartingHeight, nil, 6)
if err != nil {
t.Fatalf("unable to create alice server: %v", err)
}
s, err := initSwitchWithDB(testStartingHeight, nil)
if err != nil {
t.Fatalf("unable to init switch: %v", err)
}
if err := s.Start(); err != nil {
t.Fatalf("unable to start switch: %v", err)
}
defer s.Stop()
chanID1, _, aliceChanID, _ := genIDs()
// Set up a mock channel link.
aliceChannelLink := newMockChannelLink(
s, chanID1, aliceChanID, alicePeer, true,
)
if err := s.AddLink(aliceChannelLink); err != nil {
t.Fatalf("unable to add link: %v", err)
}
// Create a request which should be forwarded to the mock channel link.
preimage, err := genPreimage()
if err != nil {
t.Fatalf("unable to generate preimage: %v", err)
}
rhash := fastsha256.Sum256(preimage[:])
update := &lnwire.UpdateAddHTLC{
PaymentHash: rhash,
Amount: 1,
}
paymentID := uint64(123)
// Send the request.
err = s.SendHTLC(
aliceChannelLink.ShortChanID(), paymentID, update,
)
if err != nil {
t.Fatalf("unable to send payment: %v", err)
}
// Catch the packet and complete the circuit so that the switch is ready
// for a response.
select {
case packet := <-aliceChannelLink.packets:
if err := aliceChannelLink.completeCircuit(packet); err != nil {
t.Fatalf("unable to complete payment circuit: %v", err)
}
case <-time.After(time.Second):
t.Fatal("request was not propagated to destination")
}
// Send response packet with an unreadable failure message to the
// switch. The reason failed is not relevant, because we mock the
// decryption.
packet := &htlcPacket{
outgoingChanID: aliceChannelLink.ShortChanID(),
outgoingHTLCID: 0,
amount: 1,
htlc: &lnwire.UpdateFailHTLC{
Reason: []byte{1, 2, 3},
},
}
if err := s.forward(packet); err != nil {
t.Fatalf("can't forward htlc packet: %v", err)
}
// Get payment result from switch. We expect an unreadable failure
// message error.
deobfuscator := SphinxErrorDecrypter{
OnionErrorDecrypter: &mockOnionErrorDecryptor{
err: ErrUnreadableFailureMessage,
},
}
resultChan, err := s.GetPaymentResult(
paymentID, rhash, &deobfuscator,
)
if err != nil {
t.Fatal(err)
}
select {
case result := <-resultChan:
if result.Error != ErrUnreadableFailureMessage {
t.Fatal("expected unreadable failure message")
}
case <-time.After(time.Second):
t.Fatal("err wasn't received")
}
// Modify the decryption to simulate that decryption went alright, but
// the failure cannot be decoded.
deobfuscator = SphinxErrorDecrypter{
OnionErrorDecrypter: &mockOnionErrorDecryptor{
sourceIdx: 2,
message: []byte{200},
},
}
resultChan, err = s.GetPaymentResult(
paymentID, rhash, &deobfuscator,
)
if err != nil {
t.Fatal(err)
}
select {
case result := <-resultChan:
fErr, ok := result.Error.(*ForwardingError)
if !ok {
t.Fatal("expected ForwardingError")
}
if fErr.FailureSourceIdx != 2 {
t.Fatal("unexpected error source index")
}
if fErr.FailureMessage != nil {
t.Fatal("expected empty failure message")
}
case <-time.After(time.Second):
t.Fatal("err wasn't received")
}
}

@ -103,31 +103,40 @@ const (
Failure_TEMPORARY_NODE_FAILURE Failure_FailureCode = 19
Failure_PERMANENT_NODE_FAILURE Failure_FailureCode = 20
Failure_PERMANENT_CHANNEL_FAILURE Failure_FailureCode = 21
//*
//The error source is known, but the failure itself couldn't be decoded.
Failure_UNKNOWN_FAILURE Failure_FailureCode = 998
//*
//An unreadable failure result is returned if the received failure message
//cannot be decrypted. In that case the error source is unknown.
Failure_UNREADABLE_FAILURE Failure_FailureCode = 999
)
var Failure_FailureCode_name = map[int32]string{
0: "RESERVED",
1: "UNKNOWN_PAYMENT_HASH",
2: "INCORRECT_PAYMENT_AMOUNT",
3: "FINAL_INCORRECT_CLTV_EXPIRY",
4: "FINAL_INCORRECT_HTLC_AMOUNT",
5: "FINAL_EXPIRY_TOO_SOON",
6: "INVALID_REALM",
7: "EXPIRY_TOO_SOON",
8: "INVALID_ONION_VERSION",
9: "INVALID_ONION_HMAC",
10: "INVALID_ONION_KEY",
11: "AMOUNT_BELOW_MINIMUM",
12: "FEE_INSUFFICIENT",
13: "INCORRECT_CLTV_EXPIRY",
14: "CHANNEL_DISABLED",
15: "TEMPORARY_CHANNEL_FAILURE",
16: "REQUIRED_NODE_FEATURE_MISSING",
17: "REQUIRED_CHANNEL_FEATURE_MISSING",
18: "UNKNOWN_NEXT_PEER",
19: "TEMPORARY_NODE_FAILURE",
20: "PERMANENT_NODE_FAILURE",
21: "PERMANENT_CHANNEL_FAILURE",
0: "RESERVED",
1: "UNKNOWN_PAYMENT_HASH",
2: "INCORRECT_PAYMENT_AMOUNT",
3: "FINAL_INCORRECT_CLTV_EXPIRY",
4: "FINAL_INCORRECT_HTLC_AMOUNT",
5: "FINAL_EXPIRY_TOO_SOON",
6: "INVALID_REALM",
7: "EXPIRY_TOO_SOON",
8: "INVALID_ONION_VERSION",
9: "INVALID_ONION_HMAC",
10: "INVALID_ONION_KEY",
11: "AMOUNT_BELOW_MINIMUM",
12: "FEE_INSUFFICIENT",
13: "INCORRECT_CLTV_EXPIRY",
14: "CHANNEL_DISABLED",
15: "TEMPORARY_CHANNEL_FAILURE",
16: "REQUIRED_NODE_FEATURE_MISSING",
17: "REQUIRED_CHANNEL_FEATURE_MISSING",
18: "UNKNOWN_NEXT_PEER",
19: "TEMPORARY_NODE_FAILURE",
20: "PERMANENT_NODE_FAILURE",
21: "PERMANENT_CHANNEL_FAILURE",
998: "UNKNOWN_FAILURE",
999: "UNREADABLE_FAILURE",
}
var Failure_FailureCode_value = map[string]int32{
@ -153,6 +162,8 @@ var Failure_FailureCode_value = map[string]int32{
"TEMPORARY_NODE_FAILURE": 19,
"PERMANENT_NODE_FAILURE": 20,
"PERMANENT_CHANNEL_FAILURE": 21,
"UNKNOWN_FAILURE": 998,
"UNREADABLE_FAILURE": 999,
}
func (x Failure_FailureCode) String() string {
@ -610,10 +621,6 @@ func (m *SendToRouteResponse) GetFailure() *Failure {
type Failure struct {
/// Failure code as defined in the Lightning spec
Code Failure_FailureCode `protobuf:"varint,1,opt,name=code,proto3,enum=routerrpc.Failure_FailureCode" json:"code,omitempty"`
//*
//The node pubkey of the intermediate or final node that generated the failure
//message.
FailureSourcePubkey []byte `protobuf:"bytes,2,opt,name=failure_source_pubkey,json=failureSourcePubkey,proto3" json:"failure_source_pubkey,omitempty"`
/// An optional channel update message.
ChannelUpdate *ChannelUpdate `protobuf:"bytes,3,opt,name=channel_update,json=channelUpdate,proto3" json:"channel_update,omitempty"`
/// A failure type-dependent htlc value.
@ -623,7 +630,11 @@ type Failure struct {
/// A failure type-dependent cltv expiry value.
CltvExpiry uint32 `protobuf:"varint,6,opt,name=cltv_expiry,json=cltvExpiry,proto3" json:"cltv_expiry,omitempty"`
/// A failure type-dependent flags value.
Flags uint32 `protobuf:"varint,7,opt,name=flags,proto3" json:"flags,omitempty"`
Flags uint32 `protobuf:"varint,7,opt,name=flags,proto3" json:"flags,omitempty"`
//*
//The position in the path of the intermediate or final node that generated
//the failure message. Position zero is the sender node.
FailureSourceIndex uint32 `protobuf:"varint,8,opt,name=failure_source_index,json=failureSourceIndex,proto3" json:"failure_source_index,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
@ -661,13 +672,6 @@ func (m *Failure) GetCode() Failure_FailureCode {
return Failure_RESERVED
}
func (m *Failure) GetFailureSourcePubkey() []byte {
if m != nil {
return m.FailureSourcePubkey
}
return nil
}
func (m *Failure) GetChannelUpdate() *ChannelUpdate {
if m != nil {
return m.ChannelUpdate
@ -703,6 +707,13 @@ func (m *Failure) GetFlags() uint32 {
return 0
}
func (m *Failure) GetFailureSourceIndex() uint32 {
if m != nil {
return m.FailureSourceIndex
}
return 0
}
type ChannelUpdate struct {
//*
//The signature that validates the announced data and proves the ownership
@ -1167,109 +1178,111 @@ func init() {
func init() { proto.RegisterFile("routerrpc/router.proto", fileDescriptor_7a0613f69d37b0a5) }
var fileDescriptor_7a0613f69d37b0a5 = []byte{
// 1623 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x57, 0xdd, 0x72, 0x1a, 0xc9,
0x15, 0x5e, 0x04, 0x08, 0x71, 0xf8, 0x1b, 0xb5, 0x64, 0x79, 0x8c, 0x2c, 0xaf, 0x76, 0x92, 0x78,
0x55, 0xae, 0x8d, 0x94, 0x90, 0xf2, 0xd6, 0x5e, 0x25, 0x85, 0xa1, 0x59, 0x26, 0x86, 0x19, 0x6d,
0x03, 0xde, 0x75, 0x72, 0xd1, 0xd5, 0x82, 0x16, 0x4c, 0x09, 0x66, 0xd8, 0x99, 0x66, 0x63, 0xe5,
0x22, 0x77, 0xc9, 0x5d, 0x9e, 0x25, 0x79, 0x82, 0xbc, 0x47, 0x5e, 0x25, 0x57, 0xa9, 0xee, 0x1e,
0x60, 0x40, 0xc8, 0xeb, 0x2b, 0x31, 0xdf, 0xf9, 0xfa, 0x9c, 0xd3, 0xe7, 0xaf, 0x8f, 0xe0, 0x24,
0x0c, 0x16, 0x82, 0x87, 0xe1, 0x7c, 0x78, 0xa5, 0x7f, 0x5d, 0xce, 0xc3, 0x40, 0x04, 0x28, 0xbf,
0xc2, 0xab, 0xf9, 0x70, 0x3e, 0xd4, 0xa8, 0xf5, 0xbf, 0x3d, 0x40, 0x3d, 0xee, 0x8f, 0xae, 0xd9,
0xfd, 0x8c, 0xfb, 0x82, 0xf0, 0x1f, 0x17, 0x3c, 0x12, 0x08, 0x41, 0x66, 0xc4, 0x23, 0x61, 0xa6,
0xce, 0x53, 0x17, 0x45, 0xa2, 0x7e, 0x23, 0x03, 0xd2, 0x6c, 0x26, 0xcc, 0xbd, 0xf3, 0xd4, 0x45,
0x9a, 0xc8, 0x9f, 0xe8, 0x0b, 0x28, 0xce, 0xf5, 0x39, 0x3a, 0x61, 0xd1, 0xc4, 0x4c, 0x2b, 0x76,
0x21, 0xc6, 0xda, 0x2c, 0x9a, 0xa0, 0x0b, 0x30, 0x6e, 0x3d, 0x9f, 0x4d, 0xe9, 0x70, 0x2a, 0x7e,
0xa2, 0x23, 0x3e, 0x15, 0xcc, 0xcc, 0x9c, 0xa7, 0x2e, 0xb2, 0xa4, 0xac, 0xf0, 0xc6, 0x54, 0xfc,
0xd4, 0x94, 0x28, 0xfa, 0x12, 0x2a, 0x4b, 0x65, 0xa1, 0xf6, 0xc2, 0xcc, 0x9e, 0xa7, 0x2e, 0xf2,
0xa4, 0x3c, 0xdf, 0xf4, 0xed, 0x4b, 0xa8, 0x08, 0x6f, 0xc6, 0x83, 0x85, 0xa0, 0x11, 0x1f, 0x06,
0xfe, 0x28, 0x32, 0xf7, 0xb5, 0xc6, 0x18, 0xee, 0x69, 0x14, 0x59, 0x50, 0xba, 0xe5, 0x9c, 0x4e,
0xbd, 0x99, 0x27, 0x68, 0xc4, 0x84, 0x99, 0x53, 0xae, 0x17, 0x6e, 0x39, 0xef, 0x48, 0xac, 0xc7,
0x84, 0xf4, 0x2f, 0x58, 0x88, 0x71, 0xe0, 0xf9, 0x63, 0x3a, 0x9c, 0x30, 0x9f, 0x7a, 0x23, 0xf3,
0xe0, 0x3c, 0x75, 0x91, 0x21, 0xe5, 0x25, 0xde, 0x98, 0x30, 0xdf, 0x1e, 0xa1, 0x33, 0x00, 0x75,
0x07, 0xa5, 0xce, 0xcc, 0x2b, 0x8b, 0x79, 0x89, 0x28, 0x5d, 0xa8, 0x06, 0x05, 0x15, 0x60, 0x3a,
0xf1, 0x7c, 0x11, 0x99, 0x70, 0x9e, 0xbe, 0x28, 0xd4, 0x8c, 0xcb, 0xa9, 0x2f, 0x63, 0x4d, 0xa4,
0xa4, 0xed, 0xf9, 0x82, 0x24, 0x49, 0xd6, 0x37, 0x70, 0xd4, 0x0f, 0xd9, 0xf0, 0x6e, 0x2b, 0xf8,
0xdb, 0x61, 0x4d, 0x3d, 0x08, 0xab, 0xf5, 0x37, 0x28, 0xc5, 0x87, 0x7a, 0x82, 0x89, 0x45, 0x84,
0x7e, 0x0d, 0xd9, 0x48, 0x30, 0xc1, 0x15, 0xb9, 0x5c, 0x7b, 0x7a, 0xb9, 0xca, 0xf6, 0x65, 0x82,
0xc8, 0x89, 0x66, 0xa1, 0x2a, 0x1c, 0xcc, 0x43, 0xee, 0xcd, 0xd8, 0x98, 0xab, 0x84, 0x16, 0xc9,
0xea, 0x1b, 0x59, 0x90, 0x55, 0x87, 0x55, 0x3a, 0x0b, 0xb5, 0x62, 0xf2, 0x0e, 0x44, 0x8b, 0xac,
0xdf, 0x43, 0x45, 0x7d, 0xb7, 0x38, 0xff, 0x58, 0xc9, 0x3c, 0x85, 0x1c, 0x9b, 0xe9, 0xd8, 0xeb,
0xb2, 0xd9, 0x67, 0x33, 0x19, 0x76, 0x6b, 0x04, 0xc6, 0xfa, 0x7c, 0x34, 0x0f, 0xfc, 0x88, 0xcb,
0x54, 0x48, 0xe5, 0x32, 0x13, 0x32, 0x6d, 0x33, 0x79, 0x2a, 0xa5, 0x4e, 0x95, 0x63, 0xbc, 0xc5,
0x79, 0x37, 0x62, 0x02, 0xbd, 0xd4, 0x15, 0x40, 0xa7, 0xc1, 0xf0, 0x4e, 0xd6, 0x14, 0xbb, 0x8f,
0xd5, 0x97, 0x24, 0xdc, 0x09, 0x86, 0x77, 0x4d, 0x09, 0x5a, 0x7f, 0xd6, 0xb5, 0xdd, 0x0f, 0xb4,
0xef, 0x9f, 0x1c, 0xde, 0x75, 0x08, 0xf6, 0x1e, 0x0f, 0x01, 0x85, 0xa3, 0x0d, 0xe5, 0xf1, 0x2d,
0x92, 0x91, 0x4d, 0x6d, 0x45, 0xf6, 0x2b, 0xc8, 0xdd, 0x32, 0x6f, 0xba, 0x08, 0x97, 0x8a, 0x51,
0x22, 0x4d, 0x2d, 0x2d, 0x21, 0x4b, 0x8a, 0xf5, 0x8f, 0x1c, 0xe4, 0x62, 0x10, 0xd5, 0x20, 0x33,
0x0c, 0x46, 0xcb, 0xec, 0xbe, 0x78, 0x78, 0x6c, 0xf9, 0xb7, 0x11, 0x8c, 0x38, 0x51, 0x5c, 0x54,
0x83, 0x27, 0xb1, 0x2a, 0x1a, 0x05, 0x8b, 0x70, 0xc8, 0xe9, 0x7c, 0x71, 0x73, 0xc7, 0xef, 0xe3,
0x84, 0x1f, 0xc5, 0xc2, 0x9e, 0x92, 0x5d, 0x2b, 0x11, 0xfa, 0x03, 0x94, 0x65, 0x17, 0xf8, 0x7c,
0x4a, 0x17, 0xf3, 0x11, 0x5b, 0x15, 0x81, 0x99, 0xb0, 0xd8, 0xd0, 0x84, 0x81, 0x92, 0x93, 0xd2,
0x30, 0xf9, 0x89, 0x4e, 0x21, 0x3f, 0x11, 0xd3, 0xa1, 0xce, 0x5e, 0x46, 0x35, 0xd2, 0x81, 0x04,
0x54, 0xde, 0x2c, 0x28, 0x05, 0xbe, 0x17, 0xf8, 0x34, 0x9a, 0x30, 0x5a, 0x7b, 0xfd, 0xb5, 0x6a,
0xf0, 0x22, 0x29, 0x28, 0xb0, 0x37, 0x61, 0xb5, 0xd7, 0x5f, 0xa3, 0xcf, 0xa1, 0xa0, 0xda, 0x8c,
0x7f, 0x98, 0x7b, 0xe1, 0xbd, 0xea, 0xec, 0x12, 0x51, 0x9d, 0x87, 0x15, 0x82, 0x8e, 0x21, 0x7b,
0x3b, 0x65, 0xe3, 0x48, 0x75, 0x73, 0x89, 0xe8, 0x0f, 0xeb, 0xbf, 0x19, 0x28, 0x24, 0x42, 0x80,
0x8a, 0x70, 0x40, 0x70, 0x0f, 0x93, 0x77, 0xb8, 0x69, 0x7c, 0x86, 0x4c, 0x38, 0x1e, 0x38, 0x6f,
0x1d, 0xf7, 0x7b, 0x87, 0x5e, 0xd7, 0xdf, 0x77, 0xb1, 0xd3, 0xa7, 0xed, 0x7a, 0xaf, 0x6d, 0xa4,
0xd0, 0x73, 0x30, 0x6d, 0xa7, 0xe1, 0x12, 0x82, 0x1b, 0xfd, 0x95, 0xac, 0xde, 0x75, 0x07, 0x4e,
0xdf, 0xd8, 0x43, 0x9f, 0xc3, 0x69, 0xcb, 0x76, 0xea, 0x1d, 0xba, 0xe6, 0x34, 0x3a, 0xfd, 0x77,
0x14, 0xff, 0x70, 0x6d, 0x93, 0xf7, 0x46, 0x7a, 0x17, 0xa1, 0xdd, 0xef, 0x34, 0x96, 0x1a, 0x32,
0xe8, 0x19, 0x3c, 0xd1, 0x04, 0x7d, 0x84, 0xf6, 0x5d, 0x97, 0xf6, 0x5c, 0xd7, 0x31, 0xb2, 0xe8,
0x10, 0x4a, 0xb6, 0xf3, 0xae, 0xde, 0xb1, 0x9b, 0x94, 0xe0, 0x7a, 0xa7, 0x6b, 0xec, 0xa3, 0x23,
0xa8, 0x6c, 0xf3, 0x72, 0x52, 0xc5, 0x92, 0xe7, 0x3a, 0xb6, 0xeb, 0xd0, 0x77, 0x98, 0xf4, 0x6c,
0xd7, 0x31, 0x0e, 0xd0, 0x09, 0xa0, 0x4d, 0x51, 0xbb, 0x5b, 0x6f, 0x18, 0x79, 0xf4, 0x04, 0x0e,
0x37, 0xf1, 0xb7, 0xf8, 0xbd, 0x01, 0x32, 0x0c, 0xda, 0x31, 0xfa, 0x06, 0x77, 0xdc, 0xef, 0x69,
0xd7, 0x76, 0xec, 0xee, 0xa0, 0x6b, 0x14, 0xd0, 0x31, 0x18, 0x2d, 0x8c, 0xa9, 0xed, 0xf4, 0x06,
0xad, 0x96, 0xdd, 0xb0, 0xb1, 0xd3, 0x37, 0x8a, 0xda, 0xf2, 0xae, 0x8b, 0x97, 0xe4, 0x81, 0x46,
0xbb, 0xee, 0x38, 0xb8, 0x43, 0x9b, 0x76, 0xaf, 0xfe, 0xa6, 0x83, 0x9b, 0x46, 0x19, 0x9d, 0xc1,
0xb3, 0x3e, 0xee, 0x5e, 0xbb, 0xa4, 0x4e, 0xde, 0xd3, 0xa5, 0xbc, 0x55, 0xb7, 0x3b, 0x03, 0x82,
0x8d, 0x0a, 0xfa, 0x02, 0xce, 0x08, 0xfe, 0x6e, 0x60, 0x13, 0xdc, 0xa4, 0x8e, 0xdb, 0xc4, 0xb4,
0x85, 0xeb, 0xfd, 0x01, 0xc1, 0xb4, 0x6b, 0xf7, 0x7a, 0xb6, 0xf3, 0xad, 0x61, 0xa0, 0x5f, 0xc2,
0xf9, 0x8a, 0xb2, 0x52, 0xb0, 0xc5, 0x3a, 0x94, 0xf7, 0x5b, 0xe6, 0xd3, 0xc1, 0x3f, 0xf4, 0xe9,
0x35, 0xc6, 0xc4, 0x40, 0xa8, 0x0a, 0x27, 0x6b, 0xf3, 0xda, 0x40, 0x6c, 0xfb, 0x48, 0xca, 0xae,
0x31, 0xe9, 0xd6, 0x1d, 0x99, 0xe0, 0x0d, 0xd9, 0xb1, 0x74, 0x7b, 0x2d, 0xdb, 0x76, 0xfb, 0x89,
0xf5, 0xaf, 0x34, 0x94, 0x36, 0x8a, 0x1e, 0x3d, 0x87, 0x7c, 0xe4, 0x8d, 0x7d, 0x26, 0x64, 0x2b,
0xeb, 0x2e, 0x5f, 0x03, 0xea, 0xa5, 0x98, 0x30, 0xcf, 0xd7, 0xe3, 0x45, 0x77, 0x5b, 0x5e, 0x21,
0x6a, 0xb8, 0x3c, 0x85, 0xdc, 0xf2, 0xa5, 0x49, 0xab, 0x06, 0xd9, 0x1f, 0xea, 0x17, 0xe6, 0x39,
0xe4, 0xe5, 0xfc, 0x8a, 0x04, 0x9b, 0xcd, 0x55, 0xef, 0x94, 0xc8, 0x1a, 0x40, 0xbf, 0x80, 0xd2,
0x8c, 0x47, 0x11, 0x1b, 0x73, 0xaa, 0xeb, 0x1f, 0x14, 0xa3, 0x18, 0x83, 0x2d, 0x89, 0x49, 0xd2,
0xb2, 0x7f, 0x35, 0x29, 0xab, 0x49, 0x31, 0xa8, 0x49, 0xdb, 0xe3, 0x53, 0xb0, 0xb8, 0xcd, 0x92,
0xe3, 0x53, 0x30, 0xf4, 0x0a, 0x0e, 0x75, 0x2f, 0x7b, 0xbe, 0x37, 0x5b, 0xcc, 0x74, 0x4f, 0xe7,
0x94, 0xcb, 0x15, 0xd5, 0xd3, 0x1a, 0x57, 0xad, 0xfd, 0x0c, 0x0e, 0x6e, 0x58, 0xc4, 0xe5, 0xe4,
0x56, 0xef, 0x67, 0x89, 0xe4, 0xe4, 0x77, 0x8b, 0x73, 0x29, 0x92, 0xf3, 0x3c, 0x94, 0xd3, 0x24,
0xaf, 0x45, 0xb7, 0x9c, 0x13, 0x19, 0xc7, 0x95, 0x05, 0xf6, 0x61, 0x6d, 0xa1, 0x90, 0xb0, 0xa0,
0x71, 0x65, 0xe1, 0x15, 0x1c, 0xf2, 0x0f, 0x22, 0x64, 0x34, 0x98, 0xb3, 0x1f, 0x17, 0x9c, 0x8e,
0x98, 0x60, 0x66, 0x51, 0x05, 0xb7, 0xa2, 0x04, 0xae, 0xc2, 0x9b, 0x4c, 0x30, 0xeb, 0x39, 0x54,
0x09, 0x8f, 0xb8, 0xe8, 0x7a, 0x51, 0xe4, 0x05, 0x7e, 0x23, 0xf0, 0x45, 0x18, 0x4c, 0xe3, 0x07,
0xc0, 0x3a, 0x83, 0xd3, 0x9d, 0x52, 0x3d, 0xc1, 0xe5, 0xe1, 0xef, 0x16, 0x3c, 0xbc, 0xdf, 0x7d,
0xf8, 0x2d, 0x9c, 0xee, 0x94, 0xc6, 0xe3, 0xff, 0x2b, 0xc8, 0xfa, 0xc1, 0x88, 0x47, 0x66, 0x4a,
0x2d, 0x00, 0x27, 0x89, 0xb9, 0xe9, 0x04, 0x23, 0xde, 0xf6, 0x22, 0x11, 0x84, 0xf7, 0x44, 0x93,
0xac, 0xff, 0xa4, 0xa0, 0x90, 0x80, 0xd1, 0x09, 0xec, 0xc7, 0x33, 0x5a, 0x17, 0x55, 0xfc, 0x85,
0x5e, 0x42, 0x79, 0xca, 0x22, 0x41, 0xe5, 0xc8, 0xa6, 0x32, 0x49, 0xf1, 0x7b, 0xb7, 0x85, 0xa2,
0x6f, 0xe0, 0x69, 0x20, 0x26, 0x3c, 0xd4, 0xab, 0x4c, 0xb4, 0x18, 0x0e, 0x79, 0x14, 0xd1, 0x79,
0x18, 0xdc, 0xa8, 0x52, 0xdb, 0x23, 0x8f, 0x89, 0xd1, 0x6b, 0x38, 0x88, 0x6b, 0x24, 0x32, 0x33,
0xca, 0xf5, 0x67, 0x0f, 0x47, 0xfe, 0xd2, 0xfb, 0x15, 0xd5, 0xfa, 0x77, 0x0a, 0xca, 0x9b, 0x42,
0xf4, 0x42, 0x55, 0xbf, 0x2a, 0x41, 0x6f, 0xa4, 0xee, 0x91, 0x21, 0x09, 0xe4, 0x93, 0xef, 0x52,
0x83, 0xe3, 0x99, 0xe7, 0xd3, 0x39, 0xf7, 0xd9, 0xd4, 0xfb, 0x2b, 0xa7, 0xcb, 0x45, 0x22, 0xad,
0xd8, 0x3b, 0x65, 0xc8, 0x82, 0xe2, 0xc6, 0xa5, 0x33, 0xea, 0xd2, 0x1b, 0xd8, 0xab, 0x7f, 0xa6,
0xa0, 0x98, 0x5c, 0x89, 0x50, 0x09, 0xf2, 0xb6, 0x43, 0x5b, 0x1d, 0xfb, 0xdb, 0x76, 0xdf, 0xf8,
0x4c, 0x7e, 0xf6, 0x06, 0x8d, 0x06, 0xc6, 0x4d, 0xdc, 0x34, 0x52, 0x08, 0x41, 0x59, 0x4e, 0x02,
0xdc, 0xa4, 0x7d, 0xbb, 0x8b, 0xdd, 0x81, 0x7c, 0x16, 0x8e, 0xa0, 0x12, 0x63, 0x8e, 0x4b, 0x89,
0x3b, 0xe8, 0x63, 0x23, 0x8d, 0x0c, 0x28, 0xc6, 0x20, 0x26, 0xc4, 0x25, 0x46, 0x46, 0xce, 0xb2,
0x18, 0x79, 0xf8, 0xc4, 0x34, 0x71, 0xbf, 0x6e, 0x77, 0x7a, 0x46, 0xb6, 0xf6, 0xf7, 0x0c, 0xec,
0xab, 0x15, 0x22, 0x44, 0x6d, 0x28, 0x24, 0x76, 0x71, 0x74, 0x96, 0xc8, 0xc0, 0xc3, 0x1d, 0xbd,
0x6a, 0xee, 0xde, 0xf1, 0x16, 0xd1, 0x6f, 0x52, 0xe8, 0x8f, 0x50, 0x4c, 0x6e, 0x96, 0x28, 0xb9,
0x31, 0xec, 0x58, 0x39, 0x3f, 0xaa, 0xeb, 0x2d, 0x18, 0x38, 0x12, 0xde, 0x4c, 0xbe, 0xf6, 0xf1,
0xce, 0x86, 0xaa, 0x09, 0xfe, 0xd6, 0x22, 0x58, 0x3d, 0xdd, 0x29, 0x8b, 0xfb, 0xa3, 0xa3, 0xaf,
0x18, 0x6f, 0x4d, 0x0f, 0xae, 0xb8, 0xb9, 0xaa, 0x55, 0x5f, 0x3c, 0x26, 0x8e, 0xb5, 0x8d, 0xe0,
0x68, 0x47, 0x27, 0xa3, 0x5f, 0x25, 0x3d, 0x78, 0x74, 0x0e, 0x54, 0x5f, 0xfe, 0x1c, 0x6d, 0x6d,
0x65, 0x47, 0xcb, 0x6f, 0x58, 0x79, 0x7c, 0x60, 0x6c, 0x58, 0xf9, 0xc8, 0xe4, 0x78, 0xf3, 0xdb,
0x3f, 0x5d, 0x8d, 0x3d, 0x31, 0x59, 0xdc, 0x5c, 0x0e, 0x83, 0xd9, 0xd5, 0xd4, 0x1b, 0x4f, 0x84,
0xef, 0xf9, 0x63, 0x9f, 0x8b, 0xbf, 0x04, 0xe1, 0xdd, 0xd5, 0xd4, 0x1f, 0x5d, 0xa9, 0x2d, 0xf4,
0x6a, 0xa5, 0xee, 0x66, 0x5f, 0xfd, 0x0f, 0xf7, 0xbb, 0xff, 0x07, 0x00, 0x00, 0xff, 0xff, 0xb7,
0x0c, 0x44, 0x60, 0xf3, 0x0d, 0x00, 0x00,
// 1659 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x57, 0x4f, 0x73, 0x22, 0xc7,
0x15, 0x37, 0x02, 0x84, 0x78, 0xfc, 0xd1, 0xa8, 0xa5, 0x95, 0x58, 0xb4, 0x5a, 0xcb, 0xe3, 0x64,
0xad, 0xda, 0x72, 0x24, 0x87, 0xd4, 0xba, 0x7c, 0x4a, 0x8a, 0x85, 0xc6, 0x8c, 0x17, 0x66, 0xe4,
0x06, 0xd6, 0xde, 0xe4, 0xd0, 0xd5, 0x62, 0x5a, 0x30, 0x25, 0x98, 0xc1, 0x33, 0x8d, 0xb3, 0xca,
0x21, 0xb7, 0x54, 0xe5, 0x92, 0xcf, 0x92, 0x5c, 0x73, 0xc9, 0xc7, 0x49, 0xbe, 0x43, 0x4e, 0xa9,
0xee, 0x1e, 0x60, 0x40, 0x68, 0xb3, 0x27, 0x31, 0xbf, 0xdf, 0xaf, 0xdf, 0xeb, 0x7e, 0xaf, 0xdf,
0xeb, 0x27, 0x38, 0x0e, 0x83, 0xb9, 0xe0, 0x61, 0x38, 0x1b, 0x5e, 0xe9, 0x5f, 0x97, 0xb3, 0x30,
0x10, 0x01, 0xca, 0x2f, 0xf1, 0x6a, 0x3e, 0x9c, 0x0d, 0x35, 0x6a, 0xfe, 0x77, 0x07, 0x50, 0x8f,
0xfb, 0xee, 0x35, 0xbb, 0x9f, 0x72, 0x5f, 0x10, 0xfe, 0xd3, 0x9c, 0x47, 0x02, 0x21, 0xc8, 0xb8,
0x3c, 0x12, 0x95, 0xd4, 0x79, 0xea, 0xa2, 0x48, 0xd4, 0x6f, 0x64, 0x40, 0x9a, 0x4d, 0x45, 0x65,
0xe7, 0x3c, 0x75, 0x91, 0x26, 0xf2, 0x27, 0xfa, 0x0c, 0x8a, 0x33, 0xbd, 0x8e, 0x8e, 0x59, 0x34,
0xae, 0xa4, 0x95, 0xba, 0x10, 0x63, 0x6d, 0x16, 0x8d, 0xd1, 0x05, 0x18, 0xb7, 0x9e, 0xcf, 0x26,
0x74, 0x38, 0x11, 0x3f, 0x53, 0x97, 0x4f, 0x04, 0xab, 0x64, 0xce, 0x53, 0x17, 0x59, 0x52, 0x56,
0x78, 0x63, 0x22, 0x7e, 0x6e, 0x4a, 0x14, 0x7d, 0x01, 0xfb, 0x0b, 0x63, 0xa1, 0xde, 0x45, 0x25,
0x7b, 0x9e, 0xba, 0xc8, 0x93, 0xf2, 0x6c, 0x7d, 0x6f, 0x5f, 0xc0, 0xbe, 0xf0, 0xa6, 0x3c, 0x98,
0x0b, 0x1a, 0xf1, 0x61, 0xe0, 0xbb, 0x51, 0x65, 0x57, 0x5b, 0x8c, 0xe1, 0x9e, 0x46, 0x91, 0x09,
0xa5, 0x5b, 0xce, 0xe9, 0xc4, 0x9b, 0x7a, 0x82, 0x46, 0x4c, 0x54, 0x72, 0x6a, 0xeb, 0x85, 0x5b,
0xce, 0x3b, 0x12, 0xeb, 0x31, 0x21, 0xf7, 0x17, 0xcc, 0xc5, 0x28, 0xf0, 0xfc, 0x11, 0x1d, 0x8e,
0x99, 0x4f, 0x3d, 0xb7, 0xb2, 0x77, 0x9e, 0xba, 0xc8, 0x90, 0xf2, 0x02, 0x6f, 0x8c, 0x99, 0x6f,
0xb9, 0xe8, 0x0c, 0x40, 0x9d, 0x41, 0x99, 0xab, 0xe4, 0x95, 0xc7, 0xbc, 0x44, 0x94, 0x2d, 0x54,
0x83, 0x82, 0x0a, 0x30, 0x1d, 0x7b, 0xbe, 0x88, 0x2a, 0x70, 0x9e, 0xbe, 0x28, 0xd4, 0x8c, 0xcb,
0x89, 0x2f, 0x63, 0x4d, 0x24, 0xd3, 0xf6, 0x7c, 0x41, 0x92, 0x22, 0xf3, 0x1b, 0x38, 0xec, 0x87,
0x6c, 0x78, 0xb7, 0x11, 0xfc, 0xcd, 0xb0, 0xa6, 0x1e, 0x84, 0xd5, 0xfc, 0x33, 0x94, 0xe2, 0x45,
0x3d, 0xc1, 0xc4, 0x3c, 0x42, 0xbf, 0x82, 0x6c, 0x24, 0x98, 0xe0, 0x4a, 0x5c, 0xae, 0x9d, 0x5c,
0x2e, 0xb3, 0x7d, 0x99, 0x10, 0x72, 0xa2, 0x55, 0xa8, 0x0a, 0x7b, 0xb3, 0x90, 0x7b, 0x53, 0x36,
0xe2, 0x2a, 0xa1, 0x45, 0xb2, 0xfc, 0x46, 0x26, 0x64, 0xd5, 0x62, 0x95, 0xce, 0x42, 0xad, 0x98,
0x3c, 0x03, 0xd1, 0x94, 0xf9, 0x5b, 0xd8, 0x57, 0xdf, 0x2d, 0xce, 0x3f, 0x74, 0x65, 0x4e, 0x20,
0xc7, 0xa6, 0x3a, 0xf6, 0xfa, 0xda, 0xec, 0xb2, 0xa9, 0x0c, 0xbb, 0xe9, 0x82, 0xb1, 0x5a, 0x1f,
0xcd, 0x02, 0x3f, 0xe2, 0x32, 0x15, 0xd2, 0xb8, 0xcc, 0x84, 0x4c, 0xdb, 0x54, 0xae, 0x4a, 0xa9,
0x55, 0xe5, 0x18, 0x6f, 0x71, 0xde, 0x8d, 0x98, 0x40, 0x2f, 0xf4, 0x0d, 0xa0, 0x93, 0x60, 0x78,
0x27, 0xef, 0x14, 0xbb, 0x8f, 0xcd, 0x97, 0x24, 0xdc, 0x09, 0x86, 0x77, 0x4d, 0x09, 0x9a, 0x7f,
0xd0, 0x77, 0xbb, 0x1f, 0xe8, 0xbd, 0x7f, 0x74, 0x78, 0x57, 0x21, 0xd8, 0x79, 0x3c, 0x04, 0x14,
0x0e, 0xd7, 0x8c, 0xc7, 0xa7, 0x48, 0x46, 0x36, 0xb5, 0x11, 0xd9, 0x2f, 0x21, 0x77, 0xcb, 0xbc,
0xc9, 0x3c, 0x5c, 0x18, 0x46, 0x89, 0x34, 0xb5, 0x34, 0x43, 0x16, 0x12, 0xf3, 0x9f, 0x39, 0xc8,
0xc5, 0x20, 0xaa, 0x41, 0x66, 0x18, 0xb8, 0x8b, 0xec, 0x3e, 0x7f, 0xb8, 0x6c, 0xf1, 0xb7, 0x11,
0xb8, 0x9c, 0x28, 0x2d, 0xfa, 0x1d, 0x94, 0xe5, 0x8d, 0xf6, 0xf9, 0x84, 0xce, 0x67, 0x2e, 0x5b,
0x26, 0xb4, 0x92, 0x58, 0xdd, 0xd0, 0x82, 0x81, 0xe2, 0x49, 0x69, 0x98, 0xfc, 0x44, 0xa7, 0x90,
0x1f, 0x8b, 0xc9, 0x50, 0x67, 0x22, 0xa3, 0x8a, 0x62, 0x4f, 0x02, 0x2a, 0x07, 0x26, 0x94, 0x02,
0xdf, 0x0b, 0x7c, 0x1a, 0x8d, 0x19, 0xad, 0xbd, 0xfa, 0x5a, 0x15, 0x6b, 0x91, 0x14, 0x14, 0xd8,
0x1b, 0xb3, 0xda, 0xab, 0xaf, 0xd1, 0xa7, 0x50, 0x50, 0x25, 0xc3, 0xdf, 0xcf, 0xbc, 0xf0, 0x5e,
0x55, 0x69, 0x89, 0xa8, 0x2a, 0xc2, 0x0a, 0x41, 0x47, 0x90, 0xbd, 0x9d, 0xb0, 0x51, 0xa4, 0x2a,
0xb3, 0x44, 0xf4, 0x07, 0xfa, 0x0a, 0x8e, 0xe2, 0x18, 0xd0, 0x28, 0x98, 0x87, 0x43, 0x4e, 0x3d,
0xdf, 0xe5, 0xef, 0x55, 0x5d, 0x96, 0x08, 0x8a, 0xb9, 0x9e, 0xa2, 0x2c, 0xc9, 0x98, 0x7f, 0xcd,
0x42, 0x21, 0x11, 0x00, 0x54, 0x84, 0x3d, 0x82, 0x7b, 0x98, 0xbc, 0xc5, 0x4d, 0xe3, 0x13, 0x54,
0x81, 0xa3, 0x81, 0xfd, 0xc6, 0x76, 0x7e, 0xb0, 0xe9, 0x75, 0xfd, 0x5d, 0x17, 0xdb, 0x7d, 0xda,
0xae, 0xf7, 0xda, 0x46, 0x0a, 0x3d, 0x83, 0x8a, 0x65, 0x37, 0x1c, 0x42, 0x70, 0xa3, 0xbf, 0xe4,
0xea, 0x5d, 0x67, 0x60, 0xf7, 0x8d, 0x1d, 0xf4, 0x29, 0x9c, 0xb6, 0x2c, 0xbb, 0xde, 0xa1, 0x2b,
0x4d, 0xa3, 0xd3, 0x7f, 0x4b, 0xf1, 0x8f, 0xd7, 0x16, 0x79, 0x67, 0xa4, 0xb7, 0x09, 0xda, 0xfd,
0x4e, 0x63, 0x61, 0x21, 0x83, 0x9e, 0xc2, 0x13, 0x2d, 0xd0, 0x4b, 0x68, 0xdf, 0x71, 0x68, 0xcf,
0x71, 0x6c, 0x23, 0x8b, 0x0e, 0xa0, 0x64, 0xd9, 0x6f, 0xeb, 0x1d, 0xab, 0x49, 0x09, 0xae, 0x77,
0xba, 0xc6, 0x2e, 0x3a, 0x84, 0xfd, 0x4d, 0x5d, 0x4e, 0x9a, 0x58, 0xe8, 0x1c, 0xdb, 0x72, 0x6c,
0xfa, 0x16, 0x93, 0x9e, 0xe5, 0xd8, 0xc6, 0x1e, 0x3a, 0x06, 0xb4, 0x4e, 0xb5, 0xbb, 0xf5, 0x86,
0x91, 0x47, 0x4f, 0xe0, 0x60, 0x1d, 0x7f, 0x83, 0xdf, 0x19, 0x20, 0xc3, 0xa0, 0x37, 0x46, 0x5f,
0xe3, 0x8e, 0xf3, 0x03, 0xed, 0x5a, 0xb6, 0xd5, 0x1d, 0x74, 0x8d, 0x02, 0x3a, 0x02, 0xa3, 0x85,
0x31, 0xb5, 0xec, 0xde, 0xa0, 0xd5, 0xb2, 0x1a, 0x16, 0xb6, 0xfb, 0x46, 0x51, 0x7b, 0xde, 0x76,
0xf0, 0x92, 0x5c, 0xd0, 0x68, 0xd7, 0x6d, 0x1b, 0x77, 0x68, 0xd3, 0xea, 0xd5, 0x5f, 0x77, 0x70,
0xd3, 0x28, 0xa3, 0x33, 0x78, 0xda, 0xc7, 0xdd, 0x6b, 0x87, 0xd4, 0xc9, 0x3b, 0xba, 0xe0, 0x5b,
0x75, 0xab, 0x33, 0x20, 0xd8, 0xd8, 0x47, 0x9f, 0xc1, 0x19, 0xc1, 0xdf, 0x0f, 0x2c, 0x82, 0x9b,
0xd4, 0x76, 0x9a, 0x98, 0xb6, 0x70, 0xbd, 0x3f, 0x20, 0x98, 0x76, 0xad, 0x5e, 0xcf, 0xb2, 0xbf,
0x35, 0x0c, 0xf4, 0x0b, 0x38, 0x5f, 0x4a, 0x96, 0x06, 0x36, 0x54, 0x07, 0xf2, 0x7c, 0x8b, 0x7c,
0xda, 0xf8, 0xc7, 0x3e, 0xbd, 0xc6, 0x98, 0x18, 0x08, 0x55, 0xe1, 0x78, 0xe5, 0x5e, 0x3b, 0x88,
0x7d, 0x1f, 0x4a, 0xee, 0x1a, 0x93, 0x6e, 0xdd, 0x96, 0x09, 0x5e, 0xe3, 0x8e, 0xe4, 0xb6, 0x57,
0xdc, 0xe6, 0xb6, 0x9f, 0xa0, 0x23, 0xd8, 0x5f, 0x78, 0x5b, 0x80, 0xff, 0xce, 0xa1, 0x13, 0x40,
0x03, 0x9b, 0xe0, 0x7a, 0x53, 0x1e, 0x7e, 0x49, 0xfc, 0x27, 0xf7, 0x5d, 0x66, 0x6f, 0xc7, 0x48,
0x9b, 0x7f, 0x4f, 0x43, 0x69, 0xad, 0xb6, 0xd0, 0x33, 0xc8, 0x47, 0xde, 0xc8, 0x67, 0x42, 0x56,
0xbf, 0x6e, 0x0c, 0x2b, 0x40, 0x3d, 0x2e, 0x63, 0xe6, 0xf9, 0xba, 0x23, 0xe9, 0x8e, 0x9c, 0x57,
0x88, 0xea, 0x47, 0x27, 0x90, 0x5b, 0x3c, 0x4e, 0x69, 0x55, 0x87, 0xbb, 0x43, 0xfd, 0x28, 0x3d,
0x83, 0xbc, 0x6c, 0x79, 0x91, 0x60, 0xd3, 0x99, 0x2a, 0xd1, 0x12, 0x59, 0x01, 0xe8, 0x73, 0x28,
0x4d, 0x79, 0x14, 0xb1, 0x11, 0xa7, 0xba, 0xcc, 0x40, 0x29, 0x8a, 0x31, 0xd8, 0x52, 0xd5, 0xf6,
0x39, 0x2c, 0xca, 0x3e, 0x16, 0x65, 0xb5, 0x28, 0x06, 0xb5, 0x68, 0xb3, 0xe3, 0x0a, 0x16, 0x57,
0x73, 0xb2, 0xe3, 0x0a, 0x86, 0x5e, 0xc2, 0x81, 0x6e, 0x19, 0x9e, 0xef, 0x4d, 0xe7, 0x53, 0xdd,
0x3a, 0x72, 0x6a, 0xcb, 0xfb, 0xaa, 0x75, 0x68, 0x5c, 0x75, 0x90, 0xa7, 0xb0, 0x77, 0xc3, 0x22,
0x2e, 0x9b, 0x7d, 0x5c, 0xda, 0x39, 0xf9, 0xdd, 0xe2, 0x5c, 0x52, 0xf2, 0x09, 0x08, 0x65, 0xd3,
0xca, 0x6b, 0xea, 0x96, 0x73, 0x22, 0xe3, 0xb8, 0xf4, 0xc0, 0xde, 0xaf, 0x3c, 0x14, 0x12, 0x1e,
0x34, 0xae, 0x3c, 0xbc, 0x84, 0x03, 0xfe, 0x5e, 0x84, 0x8c, 0x06, 0x33, 0xf6, 0xd3, 0x9c, 0x53,
0x97, 0x09, 0x56, 0x29, 0xaa, 0xe0, 0xee, 0x2b, 0xc2, 0x51, 0x78, 0x93, 0x09, 0x66, 0x3e, 0x83,
0x2a, 0xe1, 0x11, 0x17, 0x5d, 0x2f, 0x8a, 0xbc, 0xc0, 0x6f, 0x04, 0xbe, 0x08, 0x83, 0x49, 0xfc,
0x66, 0x98, 0x67, 0x70, 0xba, 0x95, 0xd5, 0x4d, 0x5f, 0x2e, 0xfe, 0x7e, 0xce, 0xc3, 0xfb, 0xed,
0x8b, 0xdf, 0xc0, 0xe9, 0x56, 0x36, 0x7e, 0x31, 0xbe, 0x84, 0xac, 0x1f, 0xb8, 0x3c, 0xaa, 0xa4,
0xd4, 0xcc, 0x70, 0x9c, 0x68, 0xcf, 0x76, 0xe0, 0xf2, 0xb6, 0x17, 0x89, 0x20, 0xbc, 0x27, 0x5a,
0x64, 0xfe, 0x2b, 0x05, 0x85, 0x04, 0x8c, 0x8e, 0x61, 0x77, 0x36, 0xbf, 0xb9, 0xe3, 0xf7, 0xf1,
0xa5, 0x8a, 0xbf, 0xd0, 0x0b, 0x28, 0x4f, 0x58, 0x24, 0xa8, 0xec, 0x96, 0x54, 0x26, 0x29, 0x7e,
0x22, 0x37, 0x50, 0xf4, 0x0d, 0x9c, 0x04, 0x62, 0xcc, 0x43, 0x3d, 0xfd, 0x44, 0xf3, 0xe1, 0x90,
0x47, 0x11, 0x9d, 0x85, 0xc1, 0x8d, 0xba, 0x6a, 0x3b, 0xe4, 0x31, 0x1a, 0xbd, 0x82, 0xbd, 0xf8,
0x8e, 0x44, 0x95, 0x8c, 0xda, 0xfa, 0xd3, 0x87, 0x2f, 0xcb, 0x62, 0xf7, 0x4b, 0xa9, 0xf9, 0x8f,
0x14, 0x94, 0xd7, 0x49, 0xf4, 0x5c, 0xdd, 0x7e, 0x75, 0x05, 0x3d, 0x57, 0x9d, 0x23, 0x43, 0x12,
0xc8, 0x47, 0x9f, 0xa5, 0x06, 0x47, 0x53, 0xcf, 0xa7, 0x33, 0xee, 0xb3, 0x89, 0xf7, 0x27, 0x4e,
0x17, 0xb3, 0x47, 0x5a, 0xa9, 0xb7, 0x72, 0xc8, 0x84, 0xe2, 0xda, 0xa1, 0x33, 0xea, 0xd0, 0x6b,
0xd8, 0xcb, 0xbf, 0xa5, 0xa0, 0x98, 0x9c, 0xa2, 0x50, 0x09, 0xf2, 0x96, 0x4d, 0x5b, 0x1d, 0xeb,
0xdb, 0x76, 0xdf, 0xf8, 0x44, 0x7e, 0xf6, 0x06, 0x8d, 0x06, 0xc6, 0x4d, 0xdc, 0x34, 0x52, 0x08,
0x41, 0x59, 0x36, 0x04, 0xdc, 0xa4, 0x7d, 0xab, 0x8b, 0x9d, 0x81, 0x7c, 0x4b, 0x0e, 0x61, 0x3f,
0xc6, 0x6c, 0x87, 0x12, 0x67, 0xd0, 0xc7, 0x46, 0x1a, 0x19, 0x50, 0x8c, 0x41, 0x4c, 0x88, 0x43,
0x8c, 0x8c, 0x6c, 0x80, 0x31, 0xf2, 0xf0, 0x5d, 0x6a, 0xe2, 0x7e, 0xdd, 0xea, 0xf4, 0x8c, 0x6c,
0xed, 0x2f, 0x19, 0xd8, 0x55, 0x53, 0x47, 0x88, 0xda, 0x50, 0x48, 0x8c, 0xef, 0xe8, 0x2c, 0x91,
0x81, 0x87, 0x63, 0x7d, 0xb5, 0xb2, 0x7d, 0x2c, 0x9c, 0x47, 0x5f, 0xa5, 0xd0, 0x77, 0x50, 0x4c,
0x0e, 0xa3, 0x28, 0x39, 0x64, 0x6c, 0x99, 0x52, 0x3f, 0x68, 0xeb, 0x0d, 0x18, 0x38, 0x12, 0xde,
0x54, 0x0e, 0x15, 0xf1, 0x98, 0x87, 0xaa, 0x09, 0xfd, 0xc6, 0xec, 0x58, 0x3d, 0xdd, 0xca, 0xc5,
0xf5, 0xd1, 0xd1, 0x47, 0x8c, 0x07, 0xad, 0x07, 0x47, 0x5c, 0x9f, 0xee, 0xaa, 0xcf, 0x1f, 0xa3,
0x63, 0x6b, 0x2e, 0x1c, 0x6e, 0xa9, 0x64, 0xf4, 0xcb, 0xe4, 0x0e, 0x1e, 0xed, 0x03, 0xd5, 0x17,
0xff, 0x4f, 0xb6, 0xf2, 0xb2, 0xa5, 0xe4, 0xd7, 0xbc, 0x3c, 0xde, 0x30, 0xd6, 0xbc, 0x7c, 0xa0,
0x73, 0xbc, 0xfe, 0xf5, 0xef, 0xaf, 0x46, 0x9e, 0x18, 0xcf, 0x6f, 0x2e, 0x87, 0xc1, 0xf4, 0x6a,
0xe2, 0x8d, 0xc6, 0xc2, 0xf7, 0xfc, 0x91, 0xcf, 0xc5, 0x1f, 0x83, 0xf0, 0xee, 0x6a, 0xe2, 0xbb,
0x57, 0x6a, 0x70, 0xbd, 0x5a, 0x9a, 0xbb, 0xd9, 0x55, 0xff, 0xf6, 0xfd, 0xe6, 0x7f, 0x01, 0x00,
0x00, 0xff, 0xff, 0xcc, 0x5a, 0xee, 0x77, 0x26, 0x0e, 0x00, 0x00,
}
// Reference imports to suppress errors if they are not otherwise used.

@ -194,16 +194,23 @@ message Failure {
TEMPORARY_NODE_FAILURE = 19;
PERMANENT_NODE_FAILURE = 20;
PERMANENT_CHANNEL_FAILURE = 21;
/**
The error source is known, but the failure itself couldn't be decoded.
*/
UNKNOWN_FAILURE = 998;
/**
An unreadable failure result is returned if the received failure message
cannot be decrypted. In that case the error source is unknown.
*/
UNREADABLE_FAILURE = 999;
}
/// Failure code as defined in the Lightning spec
FailureCode code = 1;
/**
The node pubkey of the intermediate or final node that generated the failure
message.
**/
bytes failure_source_pubkey = 2;
reserved 2;
/// An optional channel update message.
ChannelUpdate channel_update = 3;
@ -219,6 +226,12 @@ message Failure {
/// A failure type-dependent flags value.
uint32 flags = 7;
/**
The position in the path of the intermediate or final node that generated
the failure message. Position zero is the sender node.
**/
uint32 failure_source_index = 8;
}

@ -310,6 +310,11 @@ func (s *Server) SendToRoute(ctx context.Context,
func marshallError(sendError error) (*Failure, error) {
response := &Failure{}
if sendError == htlcswitch.ErrUnreadableFailureMessage {
response.Code = Failure_UNREADABLE_FAILURE
return response, nil
}
fErr, ok := sendError.(*htlcswitch.ForwardingError)
if !ok {
return nil, sendError
@ -394,12 +399,11 @@ func marshallError(sendError error) (*Failure, error) {
case *lnwire.FailPermanentChannelFailure:
response.Code = Failure_PERMANENT_CHANNEL_FAILURE
default:
return nil, errors.New("unknown wire error")
response.Code = Failure_UNKNOWN_FAILURE
}
response.FailureSourcePubkey = fErr.ErrorSource.SerializeCompressed()
response.FailureSourceIndex = uint32(fErr.FailureSourceIdx)
return response, nil
}

@ -162,14 +162,14 @@ type FailInvalidRealm struct{}
// Returns a human readable string describing the target FailureMessage.
//
// NOTE: Implements the error interface.
func (f FailInvalidRealm) Error() string {
func (f *FailInvalidRealm) Error() string {
return f.Code().String()
}
// Code returns the failure unique code.
//
// NOTE: Part of the FailureMessage interface.
func (f FailInvalidRealm) Code() FailCode {
func (f *FailInvalidRealm) Code() FailCode {
return CodeInvalidRealm
}
@ -181,14 +181,14 @@ type FailTemporaryNodeFailure struct{}
// Code returns the failure unique code.
// NOTE: Part of the FailureMessage interface.
func (f FailTemporaryNodeFailure) Code() FailCode {
func (f *FailTemporaryNodeFailure) Code() FailCode {
return CodeTemporaryNodeFailure
}
// Returns a human readable string describing the target FailureMessage.
//
// NOTE: Implements the error interface.
func (f FailTemporaryNodeFailure) Error() string {
func (f *FailTemporaryNodeFailure) Error() string {
return f.Code().String()
}
@ -201,14 +201,14 @@ type FailPermanentNodeFailure struct{}
// Code returns the failure unique code.
//
// NOTE: Part of the FailureMessage interface.
func (f FailPermanentNodeFailure) Code() FailCode {
func (f *FailPermanentNodeFailure) Code() FailCode {
return CodePermanentNodeFailure
}
// Returns a human readable string describing the target FailureMessage.
//
// NOTE: Implements the error interface.
func (f FailPermanentNodeFailure) Error() string {
func (f *FailPermanentNodeFailure) Error() string {
return f.Code().String()
}
@ -222,14 +222,14 @@ type FailRequiredNodeFeatureMissing struct{}
// Code returns the failure unique code.
//
// NOTE: Part of the FailureMessage interface.
func (f FailRequiredNodeFeatureMissing) Code() FailCode {
func (f *FailRequiredNodeFeatureMissing) Code() FailCode {
return CodeRequiredNodeFeatureMissing
}
// Returns a human readable string describing the target FailureMessage.
//
// NOTE: Implements the error interface.
func (f FailRequiredNodeFeatureMissing) Error() string {
func (f *FailRequiredNodeFeatureMissing) Error() string {
return f.Code().String()
}
@ -242,14 +242,14 @@ type FailPermanentChannelFailure struct{}
// Code returns the failure unique code.
//
// NOTE: Part of the FailureMessage interface.
func (f FailPermanentChannelFailure) Code() FailCode {
func (f *FailPermanentChannelFailure) Code() FailCode {
return CodePermanentChannelFailure
}
// Returns a human readable string describing the target FailureMessage.
//
// NOTE: Implements the error interface.
func (f FailPermanentChannelFailure) Error() string {
func (f *FailPermanentChannelFailure) Error() string {
return f.Code().String()
}
@ -263,14 +263,14 @@ type FailRequiredChannelFeatureMissing struct{}
// Code returns the failure unique code.
//
// NOTE: Part of the FailureMessage interface.
func (f FailRequiredChannelFeatureMissing) Code() FailCode {
func (f *FailRequiredChannelFeatureMissing) Code() FailCode {
return CodeRequiredChannelFeatureMissing
}
// Returns a human readable string describing the target FailureMessage.
//
// NOTE: Implements the error interface.
func (f FailRequiredChannelFeatureMissing) Error() string {
func (f *FailRequiredChannelFeatureMissing) Error() string {
return f.Code().String()
}
@ -283,14 +283,14 @@ type FailUnknownNextPeer struct{}
// Code returns the failure unique code.
//
// NOTE: Part of the FailureMessage interface.
func (f FailUnknownNextPeer) Code() FailCode {
func (f *FailUnknownNextPeer) Code() FailCode {
return CodeUnknownNextPeer
}
// Returns a human readable string describing the target FailureMessage.
//
// NOTE: Implements the error interface.
func (f FailUnknownNextPeer) Error() string {
func (f *FailUnknownNextPeer) Error() string {
return f.Code().String()
}
@ -306,14 +306,14 @@ type FailIncorrectPaymentAmount struct{}
// Code returns the failure unique code.
//
// NOTE: Part of the FailureMessage interface.
func (f FailIncorrectPaymentAmount) Code() FailCode {
func (f *FailIncorrectPaymentAmount) Code() FailCode {
return CodeIncorrectPaymentAmount
}
// Returns a human readable string describing the target FailureMessage.
//
// NOTE: Implements the error interface.
func (f FailIncorrectPaymentAmount) Error() string {
func (f *FailIncorrectPaymentAmount) Error() string {
return f.Code().String()
}
@ -344,21 +344,21 @@ func NewFailUnknownPaymentHash(amt MilliSatoshi) *FailUnknownPaymentHash {
}
// Amount is the value of the extended HTLC.
func (f FailUnknownPaymentHash) Amount() MilliSatoshi {
func (f *FailUnknownPaymentHash) Amount() MilliSatoshi {
return f.amount
}
// Code returns the failure unique code.
//
// NOTE: Part of the FailureMessage interface.
func (f FailUnknownPaymentHash) Code() FailCode {
func (f *FailUnknownPaymentHash) Code() FailCode {
return CodeUnknownPaymentHash
}
// Returns a human readable string describing the target FailureMessage.
//
// NOTE: Implements the error interface.
func (f FailUnknownPaymentHash) Error() string {
func (f *FailUnknownPaymentHash) Error() string {
return fmt.Sprintf("UnknownPaymentHash(amt=%v)", f.amount)
}
@ -398,14 +398,14 @@ type FailFinalExpiryTooSoon struct{}
// Code returns the failure unique code.
//
// NOTE: Part of the FailureMessage interface.
func (f FailFinalExpiryTooSoon) Code() FailCode {
func (f *FailFinalExpiryTooSoon) Code() FailCode {
return CodeFinalExpiryTooSoon
}
// Returns a human readable string describing the target FailureMessage.
//
// NOTE: Implements the error interface.
func (f FailFinalExpiryTooSoon) Error() string {
func (f *FailFinalExpiryTooSoon) Error() string {
return f.Code().String()
}
@ -425,7 +425,7 @@ type FailInvalidOnionVersion struct {
// Returns a human readable string describing the target FailureMessage.
//
// NOTE: Implements the error interface.
func (f FailInvalidOnionVersion) Error() string {
func (f *FailInvalidOnionVersion) Error() string {
return fmt.Sprintf("InvalidOnionVersion(onion_sha=%x)", f.OnionSHA256[:])
}
@ -492,7 +492,7 @@ func (f *FailInvalidOnionHmac) Encode(w io.Writer, pver uint32) error {
// Returns a human readable string describing the target FailureMessage.
//
// NOTE: Implements the error interface.
func (f FailInvalidOnionHmac) Error() string {
func (f *FailInvalidOnionHmac) Error() string {
return fmt.Sprintf("InvalidOnionHMAC(onion_sha=%x)", f.OnionSHA256[:])
}
@ -534,7 +534,7 @@ func (f *FailInvalidOnionKey) Encode(w io.Writer, pver uint32) error {
// Returns a human readable string describing the target FailureMessage.
//
// NOTE: Implements the error interface.
func (f FailInvalidOnionKey) Error() string {
func (f *FailInvalidOnionKey) Error() string {
return fmt.Sprintf("InvalidOnionKey(onion_sha=%x)", f.OnionSHA256[:])
}
@ -604,7 +604,7 @@ func (f *FailTemporaryChannelFailure) Code() FailCode {
// Returns a human readable string describing the target FailureMessage.
//
// NOTE: Implements the error interface.
func (f FailTemporaryChannelFailure) Error() string {
func (f *FailTemporaryChannelFailure) Error() string {
if f.Update == nil {
return f.Code().String()
}
@ -688,7 +688,7 @@ func (f *FailAmountBelowMinimum) Code() FailCode {
// Returns a human readable string describing the target FailureMessage.
//
// NOTE: Implements the error interface.
func (f FailAmountBelowMinimum) Error() string {
func (f *FailAmountBelowMinimum) Error() string {
return fmt.Sprintf("AmountBelowMinimum(amt=%v, update=%v", f.HtlcMsat,
spew.Sdump(f.Update))
}
@ -756,7 +756,7 @@ func (f *FailFeeInsufficient) Code() FailCode {
// Returns a human readable string describing the target FailureMessage.
//
// NOTE: Implements the error interface.
func (f FailFeeInsufficient) Error() string {
func (f *FailFeeInsufficient) Error() string {
return fmt.Sprintf("FeeInsufficient(htlc_amt==%v, update=%v", f.HtlcMsat,
spew.Sdump(f.Update))
}
@ -945,7 +945,7 @@ func (f *FailChannelDisabled) Code() FailCode {
// Returns a human readable string describing the target FailureMessage.
//
// NOTE: Implements the error interface.
func (f FailChannelDisabled) Error() string {
func (f *FailChannelDisabled) Error() string {
return fmt.Sprintf("ChannelDisabled(flags=%v, update=%v", f.Flags,
spew.Sdump(f.Update))
}
@ -993,7 +993,7 @@ type FailFinalIncorrectCltvExpiry struct {
// Returns a human readable string describing the target FailureMessage.
//
// NOTE: Implements the error interface.
func (f FailFinalIncorrectCltvExpiry) Error() string {
func (f *FailFinalIncorrectCltvExpiry) Error() string {
return fmt.Sprintf("FinalIncorrectCltvExpiry(expiry=%v)", f.CltvExpiry)
}
@ -1038,7 +1038,7 @@ type FailFinalIncorrectHtlcAmount struct {
// Returns a human readable string describing the target FailureMessage.
//
// NOTE: Implements the error interface.
func (f FailFinalIncorrectHtlcAmount) Error() string {
func (f *FailFinalIncorrectHtlcAmount) Error() string {
return fmt.Sprintf("FinalIncorrectHtlcAmount(amt=%v)",
f.IncomingHTLCAmount)
}
@ -1081,14 +1081,14 @@ type FailExpiryTooFar struct{}
// Code returns the failure unique code.
//
// NOTE: Part of the FailureMessage interface.
func (f FailExpiryTooFar) Code() FailCode {
func (f *FailExpiryTooFar) Code() FailCode {
return CodeExpiryTooFar
}
// Returns a human readable string describing the target FailureMessage.
//
// NOTE: Implements the error interface.
func (f FailExpiryTooFar) Error() string {
func (f *FailExpiryTooFar) Error() string {
return f.Code().String()
}

@ -177,7 +177,7 @@ func TestFailUnknownPaymentHashOptionalAmount(t *testing.T) {
// Creation an error that is a non-pointer will allow us to skip the
// type assertion for the Serializable interface. As a result, the
// amount body won't be written.
onionError := FailUnknownPaymentHash{}
onionError := &FailUnknownPaymentHash{}
var b bytes.Buffer
if err := EncodeFailure(&b, onionError, 0); err != nil {
@ -189,7 +189,7 @@ func TestFailUnknownPaymentHashOptionalAmount(t *testing.T) {
t.Fatalf("unable to decode error: %v", err)
}
if !reflect.DeepEqual(onionError, onionError) {
if !reflect.DeepEqual(onionError, onionError2) {
t.Fatalf("expected %v, got %v", spew.Sdump(onionError),
spew.Sdump(onionError2))
}

@ -386,7 +386,9 @@ type testGraphInstance struct {
// a deterministical way and added to the channel graph. A list of nodes is
// not required and derived from the channel data. The goal is to keep
// instantiating a test channel graph as light weight as possible.
func createTestGraphFromChannels(testChannels []*testChannel) (*testGraphInstance, error) {
func createTestGraphFromChannels(testChannels []*testChannel, source string) (
*testGraphInstance, error) {
// We'll use this fake address for the IP address of all the nodes in
// our tests. This value isn't needed for path finding so it doesn't
// need to be unique.
@ -444,13 +446,13 @@ func createTestGraphFromChannels(testChannels []*testChannel) (*testGraphInstanc
return dbNode, nil
}
var source *channeldb.LightningNode
if source, err = addNodeWithAlias("roasbeef"); err != nil {
// Add the source node.
dbNode, err := addNodeWithAlias(source)
if err != nil {
return nil, err
}
// Set the source node
if err := graph.SetSourceNode(source); err != nil {
if err = graph.SetSourceNode(dbNode); err != nil {
return nil, err
}
@ -464,7 +466,10 @@ func createTestGraphFromChannels(testChannels []*testChannel) (*testGraphInstanc
_, exists := aliasMap[alias]
if !exists {
addNodeWithAlias(alias)
_, err := addNodeWithAlias(alias)
if err != nil {
return nil, err
}
}
}
@ -619,7 +624,9 @@ func TestFindLowestFeePath(t *testing.T) {
}),
}
testGraphInstance, err := createTestGraphFromChannels(testChannels)
testGraphInstance, err := createTestGraphFromChannels(
testChannels, "roasbeef",
)
if err != nil {
t.Fatalf("unable to create graph: %v", err)
}
@ -1367,7 +1374,7 @@ func TestRouteFailMaxHTLC(t *testing.T) {
}),
}
graph, err := createTestGraphFromChannels(testChannels)
graph, err := createTestGraphFromChannels(testChannels, "roasbeef")
if err != nil {
t.Fatalf("unable to create graph: %v", err)
}
@ -1862,7 +1869,9 @@ func TestRestrictOutgoingChannel(t *testing.T) {
}),
}
testGraphInstance, err := createTestGraphFromChannels(testChannels)
testGraphInstance, err := createTestGraphFromChannels(
testChannels, "roasbeef",
)
if err != nil {
t.Fatalf("unable to create graph: %v", err)
}
@ -1957,7 +1966,9 @@ func testCltvLimit(t *testing.T, limit uint32, expectedChannel uint64) {
}),
}
testGraphInstance, err := createTestGraphFromChannels(testChannels)
testGraphInstance, err := createTestGraphFromChannels(
testChannels, "roasbeef",
)
if err != nil {
t.Fatalf("unable to create graph: %v", err)
}
@ -2121,7 +2132,9 @@ func testProbabilityRouting(t *testing.T, p10, p11, p20, minProbability float64,
}, 20),
}
testGraphInstance, err := createTestGraphFromChannels(testChannels)
testGraphInstance, err := createTestGraphFromChannels(
testChannels, "roasbeef",
)
if err != nil {
t.Fatalf("unable to create graph: %v", err)
}

@ -17,7 +17,7 @@ import (
type errNoRoute struct {
// lastError is the error encountered during the last payment attempt,
// if at least one attempt has been made.
lastError *htlcswitch.ForwardingError
lastError error
}
// Error returns a string representation of the error.
@ -37,7 +37,7 @@ type paymentLifecycle struct {
finalCLTVDelta uint16
attempt *channeldb.PaymentAttemptInfo
circuit *sphinx.Circuit
lastError *htlcswitch.ForwardingError
lastError error
}
// resumePayment resumes the paymentLifecycle from the current state.
@ -341,25 +341,16 @@ func (p *paymentLifecycle) sendPaymentAttempt(firstHop lnwire.ShortChannelID,
// handleSendError inspects the given error from the Switch and determines
// whether we should make another payment attempt.
func (p *paymentLifecycle) handleSendError(sendErr error) error {
var reason channeldb.FailureReason
// If an internal, non-forwarding error occurred, we can stop trying.
fErr, ok := sendErr.(*htlcswitch.ForwardingError)
if !ok {
reason = channeldb.FailureReasonError
} else {
var final bool
final, reason = p.router.processSendError(
p.paySession, &p.attempt.Route, fErr,
)
final, reason := p.router.processSendError(
p.paySession, &p.attempt.Route, sendErr,
)
if !final {
// Save the forwarding error so it can be returned if
// this turns out to be the last attempt.
p.lastError = sendErr
if !final {
// Save the forwarding error so it can be returned if
// this turns out to be the last attempt.
p.lastError = fErr
return nil
}
return nil
}
log.Debugf("Payment %x failed: final_outcome=%v, raw_err=%v",

@ -1812,22 +1812,58 @@ func (r *ChannelRouter) sendPayment(
// to continue with an alternative route. This is indicated by the boolean
// return value.
func (r *ChannelRouter) processSendError(paySession PaymentSession,
rt *route.Route, fErr *htlcswitch.ForwardingError) (
bool, channeldb.FailureReason) {
rt *route.Route, sendErr error) (bool, channeldb.FailureReason) {
errSource := fErr.ErrorSource
errVertex := route.NewVertex(errSource)
// If the failure message could not be decrypted, attribute the failure
// to our own outgoing channel.
//
// TODO(joostager): Penalize all channels in the route.
if sendErr == htlcswitch.ErrUnreadableFailureMessage {
sendErr = &htlcswitch.ForwardingError{
FailureSourceIdx: 0,
FailureMessage: lnwire.NewTemporaryChannelFailure(nil),
}
}
log.Tracef("node=%x reported failure when sending htlc", errVertex)
// If an internal, non-forwarding error occurred, we can stop trying.
fErr, ok := sendErr.(*htlcswitch.ForwardingError)
if !ok {
return true, channeldb.FailureReasonError
}
var (
failureSourceIdx = fErr.FailureSourceIdx
failureVertex route.Vertex
failureSource *btcec.PublicKey
err error
)
// For any non-self failure, look up the source pub key in the hops
// slice. Otherwise return the self node pubkey.
if failureSourceIdx > 0 {
failureVertex = rt.Hops[failureSourceIdx-1].PubKeyBytes
failureSource, err = btcec.ParsePubKey(failureVertex[:], btcec.S256())
if err != nil {
log.Errorf("Cannot parse pubkey %v: %v",
failureVertex, err)
return true, channeldb.FailureReasonError
}
} else {
failureVertex = r.selfNode.PubKeyBytes
failureSource, err = r.selfNode.PubKey()
if err != nil {
log.Errorf("Cannot parse self pubkey: %v", err)
return true, channeldb.FailureReasonError
}
}
log.Tracef("Node %x (index %v) reported failure when sending htlc",
failureVertex, failureSourceIdx)
// Always determine chan id ourselves, because a channel
// update with id may not be available.
failedEdge, failedAmt, err := getFailedEdge(
rt, route.Vertex(errVertex),
)
if err != nil {
return true, channeldb.FailureReasonError
}
failedEdge, failedAmt := getFailedEdge(rt, failureSourceIdx)
// processChannelUpdateAndRetry is a closure that
// handles a failure message containing a channel
@ -1919,8 +1955,8 @@ func (r *ChannelRouter) processSendError(paySession PaymentSession,
// that sent us this error, as it doesn't now what the
// correct block height is.
case *lnwire.FailExpiryTooSoon:
r.applyChannelUpdate(&onionErr.Update, errSource)
paySession.ReportVertexFailure(errVertex)
r.applyChannelUpdate(&onionErr.Update, failureSource)
paySession.ReportVertexFailure(failureVertex)
return false, 0
// If we hit an instance of onion payload corruption or an invalid
@ -1941,7 +1977,7 @@ func (r *ChannelRouter) processSendError(paySession PaymentSession,
// routing.
case *lnwire.FailAmountBelowMinimum:
processChannelUpdateAndRetry(
&onionErr.Update, errSource,
&onionErr.Update, failureSource,
)
return false, 0
@ -1950,7 +1986,7 @@ func (r *ChannelRouter) processSendError(paySession PaymentSession,
// newly updated fees.
case *lnwire.FailFeeInsufficient:
processChannelUpdateAndRetry(
&onionErr.Update, errSource,
&onionErr.Update, failureSource,
)
return false, 0
@ -1959,7 +1995,7 @@ func (r *ChannelRouter) processSendError(paySession PaymentSession,
// apply the new delta value and try it once more.
case *lnwire.FailIncorrectCltvExpiry:
processChannelUpdateAndRetry(
&onionErr.Update, errSource,
&onionErr.Update, failureSource,
)
return false, 0
@ -1967,7 +2003,7 @@ func (r *ChannelRouter) processSendError(paySession PaymentSession,
// forward one is currently disabled, so we'll apply
// the update and continue.
case *lnwire.FailChannelDisabled:
r.applyChannelUpdate(&onionErr.Update, errSource)
r.applyChannelUpdate(&onionErr.Update, failureSource)
paySession.ReportEdgeFailure(failedEdge, 0)
return false, 0
@ -1975,7 +2011,7 @@ func (r *ChannelRouter) processSendError(paySession PaymentSession,
// sufficient capacity, so we'll prune this edge for
// now, and continue onwards with our path finding.
case *lnwire.FailTemporaryChannelFailure:
r.applyChannelUpdate(onionErr.Update, errSource)
r.applyChannelUpdate(onionErr.Update, failureSource)
paySession.ReportEdgeFailure(failedEdge, failedAmt)
return false, 0
@ -1983,14 +2019,14 @@ func (r *ChannelRouter) processSendError(paySession PaymentSession,
// required features, then we'll note this error and
// continue.
case *lnwire.FailRequiredNodeFeatureMissing:
paySession.ReportVertexFailure(errVertex)
paySession.ReportVertexFailure(failureVertex)
return false, 0
// If the send fail due to a node not having the
// required features, then we'll note this error and
// continue.
case *lnwire.FailRequiredChannelFeatureMissing:
paySession.ReportVertexFailure(errVertex)
paySession.ReportVertexFailure(failureVertex)
return false, 0
// If the next hop in the route wasn't known or
@ -2008,11 +2044,11 @@ func (r *ChannelRouter) processSendError(paySession PaymentSession,
// reason, then we'll note this and continue with the
// routes.
case *lnwire.FailTemporaryNodeFailure:
paySession.ReportVertexFailure(errVertex)
paySession.ReportVertexFailure(failureVertex)
return false, 0
case *lnwire.FailPermanentNodeFailure:
paySession.ReportVertexFailure(errVertex)
paySession.ReportVertexFailure(failureVertex)
return false, 0
// If we crafted a route that contains a too long time
@ -2025,7 +2061,7 @@ func (r *ChannelRouter) processSendError(paySession PaymentSession,
// that as a hint during future path finding through
// that node.
case *lnwire.FailExpiryTooFar:
paySession.ReportVertexFailure(errVertex)
paySession.ReportVertexFailure(failureVertex)
return false, 0
// If we get a permanent channel or node failure, then
@ -2040,51 +2076,51 @@ func (r *ChannelRouter) processSendError(paySession PaymentSession,
}, 0)
return false, 0
// Any other failure or an empty failure will get the node pruned.
default:
return true, channeldb.FailureReasonError
paySession.ReportVertexFailure(failureVertex)
return false, 0
}
}
// getFailedEdge tries to locate the failing channel given a route and the
// pubkey of the node that sent the error. It will assume that the error is
// associated with the outgoing channel of the error node. As a second result,
// pubkey of the node that sent the failure. It will assume that the failure is
// associated with the outgoing channel of the failing node. As a second result,
// it returns the amount sent over the edge.
func getFailedEdge(route *route.Route, errSource route.Vertex) (edge,
lnwire.MilliSatoshi, error) {
func getFailedEdge(route *route.Route, failureSource int) (edge,
lnwire.MilliSatoshi) {
hopCount := len(route.Hops)
fromNode := route.SourcePubKey
amt := route.TotalAmount
for i, hop := range route.Hops {
toNode := hop.PubKeyBytes
// Determine if we have a failure from the final hop.
//
// TODO(joostjager): In this case, certain types of errors are
// not expected. For example FailUnknownNextPeer. This could be
// a reason to prune the node?
finalHopFailing := i == hopCount-1 && errSource == toNode
// As this error indicates that the target channel was unable to
// carry this HTLC (for w/e reason), we'll return the _outgoing_
// channel that the source of the error was meant to pass the
// HTLC along to.
//
// If the errSource is the final hop, we assume that the failing
// channel is the incoming channel.
if errSource == fromNode || finalHopFailing {
return edge{
from: fromNode,
to: toNode,
channel: hop.ChannelID,
}, amt, nil
}
fromNode = toNode
amt = hop.AmtToForward
// Determine if we have a failure from the final hop. If it is, we
// assume that the failing channel is the incoming channel. In this
// function the outgoing channel of the hop indicated by failureSource
// is returned, where index zero is the self node. By decrementing
// failureSource by one, the outgoing channel of the penultimate hop is
// returned, which is the same as the incoming channel of the final
// node.
//
// TODO(joostjager): In this case, certain types of failures are not
// expected. For example FailUnknownNextPeer. This could be a reason to
// prune the node?
if failureSource == len(route.Hops) {
failureSource--
}
return edge{}, 0, fmt.Errorf("cannot find error source node in route")
// As this failure indicates that the target channel was unable to carry
// this HTLC (for w/e reason), we'll return the _outgoing_ channel that
// the source of the failure was meant to pass the HTLC along to.
if failureSource == 0 {
return edge{
from: route.SourcePubKey,
to: route.Hops[0].PubKeyBytes,
channel: route.Hops[0].ChannelID,
}, route.TotalAmount
}
return edge{
from: route.Hops[failureSource-1].PubKeyBytes,
to: route.Hops[failureSource].PubKeyBytes,
channel: route.Hops[failureSource].ChannelID,
}, route.Hops[failureSource-1].AmtToForward
}
// applyChannelUpdate validates a channel update and if valid, applies it to the

@ -265,8 +265,6 @@ func TestSendPaymentRouteFailureFallback(t *testing.T) {
var preImage [32]byte
copy(preImage[:], bytes.Repeat([]byte{9}, 32))
sourceNode := ctx.router.selfNode
// We'll modify the SendToSwitch method that's been set within the
// router's configuration to ignore the path that has luo ji as the
// first hop. This should force the router to instead take the
@ -276,12 +274,8 @@ func TestSendPaymentRouteFailureFallback(t *testing.T) {
roasbeefLuoji := lnwire.NewShortChanIDFromInt(689530843)
if firstHop == roasbeefLuoji {
pub, err := sourceNode.PubKey()
if err != nil {
return preImage, err
}
return [32]byte{}, &htlcswitch.ForwardingError{
ErrorSource: pub,
FailureSourceIdx: 0,
// TODO(roasbeef): temp node failure should be?
FailureMessage: &lnwire.FailTemporaryChannelFailure{},
}
@ -342,7 +336,7 @@ func TestChannelUpdateValidation(t *testing.T) {
}, 2),
}
testGraph, err := createTestGraphFromChannels(testChannels)
testGraph, err := createTestGraphFromChannels(testChannels, "a")
defer testGraph.cleanUp()
if err != nil {
t.Fatalf("unable to create graph: %v", err)
@ -410,17 +404,8 @@ func TestChannelUpdateValidation(t *testing.T) {
// The unsigned channel update is attached to the failure message.
ctx.router.cfg.Payer.(*mockPaymentAttemptDispatcher).setPaymentResult(
func(firstHop lnwire.ShortChannelID) ([32]byte, error) {
v := ctx.aliases["b"]
source, err := btcec.ParsePubKey(
v[:], btcec.S256(),
)
if err != nil {
t.Fatal(err)
}
return [32]byte{}, &htlcswitch.ForwardingError{
ErrorSource: source,
FailureSourceIdx: 1,
FailureMessage: &lnwire.FailFeeInsufficient{
Update: errChanUpdate,
},
@ -533,9 +518,6 @@ func TestSendPaymentErrorRepeatedFeeInsufficient(t *testing.T) {
FeeRate: uint32(edgeUpdateToFail.FeeProportionalMillionths),
}
// The error will be returned by Son Goku.
sourceNode := ctx.aliases["songoku"]
// We'll now modify the SendToSwitch method to return an error for the
// outgoing channel to Son goku. This will be a fee related error, so
// it should only cause the edge to be pruned after the second attempt.
@ -544,15 +526,8 @@ func TestSendPaymentErrorRepeatedFeeInsufficient(t *testing.T) {
roasbeefSongoku := lnwire.NewShortChanIDFromInt(chanID)
if firstHop == roasbeefSongoku {
sourceKey, err := btcec.ParsePubKey(
sourceNode[:], btcec.S256(),
)
if err != nil {
t.Fatal(err)
}
return [32]byte{}, &htlcswitch.ForwardingError{
ErrorSource: sourceKey,
FailureSourceIdx: 1,
// Within our error, we'll add a channel update
// which is meant to reflect he new fee
@ -647,9 +622,6 @@ func TestSendPaymentErrorNonFinalTimeLockErrors(t *testing.T) {
FeeRate: uint32(edgeUpdateToFail.FeeProportionalMillionths),
}
// The error will be returned by Son Goku.
sourceNode := ctx.aliases["songoku"]
// We'll now modify the SendToSwitch method to return an error for the
// outgoing channel to son goku. Since this is a time lock related
// error, we should fail the payment flow all together, as Goku is the
@ -658,15 +630,8 @@ func TestSendPaymentErrorNonFinalTimeLockErrors(t *testing.T) {
func(firstHop lnwire.ShortChannelID) ([32]byte, error) {
if firstHop == roasbeefSongoku {
sourceKey, err := btcec.ParsePubKey(
sourceNode[:], btcec.S256(),
)
if err != nil {
t.Fatal(err)
}
return [32]byte{}, &htlcswitch.ForwardingError{
ErrorSource: sourceKey,
FailureSourceIdx: 1,
FailureMessage: &lnwire.FailExpiryTooSoon{
Update: errChanUpdate,
},
@ -719,15 +684,8 @@ func TestSendPaymentErrorNonFinalTimeLockErrors(t *testing.T) {
func(firstHop lnwire.ShortChannelID) ([32]byte, error) {
if firstHop == roasbeefSongoku {
sourceKey, err := btcec.ParsePubKey(
sourceNode[:], btcec.S256(),
)
if err != nil {
t.Fatal(err)
}
return [32]byte{}, &htlcswitch.ForwardingError{
ErrorSource: sourceKey,
FailureSourceIdx: 1,
FailureMessage: &lnwire.FailIncorrectCltvExpiry{
Update: errChanUpdate,
},
@ -776,16 +734,6 @@ func TestSendPaymentErrorPathPruning(t *testing.T) {
var preImage [32]byte
copy(preImage[:], bytes.Repeat([]byte{9}, 32))
sourceNode, err := ctx.graph.SourceNode()
if err != nil {
t.Fatalf("unable to fetch source node: %v", err)
}
sourcePub, err := sourceNode.PubKey()
if err != nil {
t.Fatalf("unable to fetch source node pub: %v", err)
}
roasbeefLuoji := lnwire.NewShortChanIDFromInt(689530843)
// First, we'll modify the SendToSwitch method to return an error
@ -802,8 +750,8 @@ func TestSendPaymentErrorPathPruning(t *testing.T) {
// outgoing link to simulate the channel from luo ji to
// roasbeef not having enough capacity.
return [32]byte{}, &htlcswitch.ForwardingError{
ErrorSource: sourcePub,
FailureMessage: &lnwire.FailTemporaryChannelFailure{},
FailureSourceIdx: 0,
FailureMessage: &lnwire.FailTemporaryChannelFailure{},
}
}
@ -812,17 +760,9 @@ func TestSendPaymentErrorPathPruning(t *testing.T) {
// prune out the rest of the routes.
roasbeefSatoshi := lnwire.NewShortChanIDFromInt(2340213491)
if firstHop == roasbeefSatoshi {
vertex := ctx.aliases["satoshi"]
key, err := btcec.ParsePubKey(
vertex[:], btcec.S256(),
)
if err != nil {
t.Fatal(err)
}
return [32]byte{}, &htlcswitch.ForwardingError{
ErrorSource: key,
FailureMessage: &lnwire.FailUnknownNextPeer{},
FailureSourceIdx: 1,
FailureMessage: &lnwire.FailUnknownNextPeer{},
}
}
@ -854,8 +794,8 @@ func TestSendPaymentErrorPathPruning(t *testing.T) {
if firstHop == roasbeefLuoji {
return [32]byte{}, &htlcswitch.ForwardingError{
ErrorSource: sourcePub,
FailureMessage: &lnwire.FailUnknownNextPeer{},
FailureSourceIdx: 0,
FailureMessage: &lnwire.FailUnknownNextPeer{},
}
}
@ -900,8 +840,8 @@ func TestSendPaymentErrorPathPruning(t *testing.T) {
// outgoing link to simulate the channel from luo ji to
// roasbeef not having enough capacity.
return [32]byte{}, &htlcswitch.ForwardingError{
ErrorSource: sourcePub,
FailureMessage: &lnwire.FailTemporaryChannelFailure{},
FailureSourceIdx: 0,
FailureMessage: &lnwire.FailTemporaryChannelFailure{},
}
}
return preImage, nil
@ -1039,7 +979,9 @@ func TestIgnoreChannelEdgePolicyForUnknownChannel(t *testing.T) {
// Setup an initially empty network.
testChannels := []*testChannel{}
testGraph, err := createTestGraphFromChannels(testChannels)
testGraph, err := createTestGraphFromChannels(
testChannels, "roasbeef",
)
if err != nil {
t.Fatalf("unable to create graph: %v", err)
}
@ -2027,7 +1969,7 @@ func TestPruneChannelGraphStaleEdges(t *testing.T) {
// We'll create our test graph and router backed with these test
// channels we've created.
testGraph, err := createTestGraphFromChannels(testChannels)
testGraph, err := createTestGraphFromChannels(testChannels, "a")
if err != nil {
t.Fatalf("unable to create test graph: %v", err)
}
@ -2064,6 +2006,14 @@ func TestPruneChannelGraphDoubleDisabled(t *testing.T) {
// according to that heuristic.
timestamp := time.Now()
testChannels := []*testChannel{
// Channel from self shouldn't be pruned.
symmetricTestChannel(
"self", "a", 100000, &testChannelPolicy{
LastUpdate: timestamp,
Disabled: true,
}, 99,
),
// No edges.
{
Node1: &testChannelEnd{Alias: "a"},
@ -2135,7 +2085,7 @@ func TestPruneChannelGraphDoubleDisabled(t *testing.T) {
// We'll create our test graph and router backed with these test
// channels we've created.
testGraph, err := createTestGraphFromChannels(testChannels)
testGraph, err := createTestGraphFromChannels(testChannels, "self")
if err != nil {
t.Fatalf("unable to create test graph: %v", err)
}
@ -2475,6 +2425,120 @@ func TestEmptyRoutesGenerateSphinxPacket(t *testing.T) {
}
}
// TestUnknownErrorSource tests that if the source of an error is unknown, all
// edges along the route will be pruned.
func TestUnknownErrorSource(t *testing.T) {
t.Parallel()
// Setup a network. It contains two paths to c: a->b->c and an
// alternative a->d->c.
chanCapSat := btcutil.Amount(100000)
testChannels := []*testChannel{
symmetricTestChannel("a", "b", chanCapSat, &testChannelPolicy{
Expiry: 144,
FeeRate: 400,
MinHTLC: 1,
MaxHTLC: lnwire.NewMSatFromSatoshis(chanCapSat),
}, 1),
symmetricTestChannel("b", "c", chanCapSat, &testChannelPolicy{
Expiry: 144,
FeeRate: 400,
MinHTLC: 1,
MaxHTLC: lnwire.NewMSatFromSatoshis(chanCapSat),
}, 3),
symmetricTestChannel("a", "d", chanCapSat, &testChannelPolicy{
Expiry: 144,
FeeRate: 400,
FeeBaseMsat: 100000,
MinHTLC: 1,
MaxHTLC: lnwire.NewMSatFromSatoshis(chanCapSat),
}, 2),
symmetricTestChannel("d", "c", chanCapSat, &testChannelPolicy{
Expiry: 144,
FeeRate: 400,
FeeBaseMsat: 100000,
MinHTLC: 1,
MaxHTLC: lnwire.NewMSatFromSatoshis(chanCapSat),
}, 4),
}
testGraph, err := createTestGraphFromChannels(testChannels, "a")
defer testGraph.cleanUp()
if err != nil {
t.Fatalf("unable to create graph: %v", err)
}
const startingBlockHeight = 101
ctx, cleanUp, err := createTestCtxFromGraphInstance(startingBlockHeight,
testGraph)
defer cleanUp()
if err != nil {
t.Fatalf("unable to create router: %v", err)
}
// Create a payment to node c.
payment := LightningPayment{
Target: ctx.aliases["c"],
Amount: lnwire.NewMSatFromSatoshis(1000),
FeeLimit: noFeeLimit,
PaymentHash: lntypes.Hash{},
}
// We'll modify the SendToSwitch method so that it simulates hop b as a
// node that returns an unparsable failure if approached via the a->b
// channel.
ctx.router.cfg.Payer.(*mockPaymentAttemptDispatcher).setPaymentResult(
func(firstHop lnwire.ShortChannelID) ([32]byte, error) {
// If channel a->b is used, return an error without
// source and message. The sender won't know the origin
// of the error.
if firstHop.ToUint64() == 1 {
return [32]byte{},
htlcswitch.ErrUnreadableFailureMessage
}
// Otherwise the payment succeeds.
return lntypes.Preimage{}, nil
})
// Send off the payment request to the router. The expectation is that
// the route a->b->c is tried first. An unreadable faiure is returned
// which should pruning the channel a->b. We expect the payment to
// succeed via a->d.
_, _, err = ctx.router.SendPayment(&payment)
if err != nil {
t.Fatalf("expected payment to succeed, but got: %v", err)
}
// Next we modify payment result to return an unknown failure.
ctx.router.cfg.Payer.(*mockPaymentAttemptDispatcher).setPaymentResult(
func(firstHop lnwire.ShortChannelID) ([32]byte, error) {
// If channel a->b is used, simulate that the failure
// couldn't be decoded (FailureMessage is nil).
if firstHop.ToUint64() == 2 {
return [32]byte{},
&htlcswitch.ForwardingError{
FailureSourceIdx: 1,
}
}
// Otherwise the payment succeeds.
return lntypes.Preimage{}, nil
})
// Send off the payment request to the router. We expect the payment to
// fail because both routes have been pruned.
payment.PaymentHash = lntypes.Hash{1}
_, _, err = ctx.router.SendPayment(&payment)
if err == nil {
t.Fatalf("expected payment to fail")
}
}
// assertChannelsPruned ensures that only the given channels are pruned from the
// graph out of the set of all channels.
func assertChannelsPruned(t *testing.T, graph *channeldb.ChannelGraph,
@ -2543,7 +2607,7 @@ func TestRouterPaymentStateMachine(t *testing.T) {
}, 2),
}
testGraph, err := createTestGraphFromChannels(testChannels)
testGraph, err := createTestGraphFromChannels(testChannels, "a")
if err != nil {
t.Fatalf("unable to create graph: %v", err)
}
@ -2938,11 +3002,6 @@ func TestRouterPaymentStateMachine(t *testing.T) {
PaymentHash: payHash,
}
errSource, err := btcec.ParsePubKey(hop1[:], btcec.S256())
if err != nil {
t.Fatalf("unable to fetch source node pub: %v", err)
}
copy(preImage[:], bytes.Repeat([]byte{9}, 32))
router.cfg.MissionControl = &mockPaymentSessionSource{
@ -3020,8 +3079,8 @@ func TestRouterPaymentStateMachine(t *testing.T) {
case sendToSwitchResultFailure:
select {
case sendResult <- &htlcswitch.ForwardingError{
ErrorSource: errSource,
FailureMessage: &lnwire.FailTemporaryChannelFailure{},
FailureSourceIdx: 1,
FailureMessage: &lnwire.FailTemporaryChannelFailure{},
}:
case <-time.After(1 * time.Second):
t.Fatalf("unable to send result")
@ -3046,8 +3105,8 @@ func TestRouterPaymentStateMachine(t *testing.T) {
select {
case getPaymentResult <- &htlcswitch.PaymentResult{
Error: &htlcswitch.ForwardingError{
ErrorSource: errSource,
FailureMessage: &lnwire.FailTemporaryChannelFailure{},
FailureSourceIdx: 1,
FailureMessage: &lnwire.FailTemporaryChannelFailure{},
},
}:
case <-time.After(1 * time.Second):
@ -3164,7 +3223,7 @@ func TestSendToRouteStructuredError(t *testing.T) {
}, 2),
}
testGraph, err := createTestGraphFromChannels(testChannels)
testGraph, err := createTestGraphFromChannels(testChannels, "a")
if err != nil {
t.Fatalf("unable to create graph: %v", err)
}
@ -3214,17 +3273,8 @@ func TestSendToRouteStructuredError(t *testing.T) {
// The unsigned channel update is attached to the failure message.
ctx.router.cfg.Payer.(*mockPaymentAttemptDispatcher).setPaymentResult(
func(firstHop lnwire.ShortChannelID) ([32]byte, error) {
v := ctx.aliases["b"]
source, err := btcec.ParsePubKey(
v[:], btcec.S256(),
)
if err != nil {
t.Fatal(err)
}
return [32]byte{}, &htlcswitch.ForwardingError{
ErrorSource: source,
FailureSourceIdx: 1,
FailureMessage: &lnwire.FailFeeInsufficient{
Update: lnwire.ChannelUpdate{},
},

@ -409,8 +409,7 @@ func newServer(listenAddrs []net.Addr, chanDB *channeldb.DB,
}
s.htlcSwitch, err = htlcswitch.New(htlcswitch.Config{
DB: chanDB,
SelfKey: s.identityPriv.PubKey(),
DB: chanDB,
LocalChannelClose: func(pubKey []byte,
request *htlcswitch.ChanClose) {