Merge pull request #3156 from joostjager/extended-fail

routerrpc: add more failure reasons and route hints
This commit is contained in:
Olaoluwa Osuntokun 2019-07-08 19:12:03 -07:00 committed by GitHub
commit ba5fbb3c27
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 397 additions and 199 deletions

@ -246,9 +246,12 @@ func (p *PaymentControl) Success(paymentHash lntypes.Hash,
// its next call for this payment hash, allowing the switch to make a // its next call for this payment hash, allowing the switch to make a
// subsequent payment. // subsequent payment.
func (p *PaymentControl) Fail(paymentHash lntypes.Hash, func (p *PaymentControl) Fail(paymentHash lntypes.Hash,
reason FailureReason) error { reason FailureReason) (*route.Route, error) {
var updateErr error var (
updateErr error
route *route.Route
)
err := p.db.Batch(func(tx *bbolt.Tx) error { err := p.db.Batch(func(tx *bbolt.Tx) error {
// Reset the update error, to avoid carrying over an error // Reset the update error, to avoid carrying over an error
// from a previous execution of the batched db transaction. // from a previous execution of the batched db transaction.
@ -270,13 +273,27 @@ func (p *PaymentControl) Fail(paymentHash lntypes.Hash,
// Put the failure reason in the bucket for record keeping. // Put the failure reason in the bucket for record keeping.
v := []byte{byte(reason)} v := []byte{byte(reason)}
return bucket.Put(paymentFailInfoKey, v) err = bucket.Put(paymentFailInfoKey, v)
if err != nil {
return err
}
// Retrieve attempt info for the notification, if available.
attempt, err := fetchPaymentAttempt(bucket)
if err != nil && err != errNoAttemptInfo {
return err
}
if err != errNoAttemptInfo {
route = &attempt.Route
}
return nil
}) })
if err != nil { if err != nil {
return err return nil, err
} }
return updateErr return route, updateErr
} }
// FetchPayment returns information about a payment from the database. // FetchPayment returns information about a payment from the database.

@ -94,7 +94,7 @@ func TestPaymentControlSwitchFail(t *testing.T) {
// Fail the payment, which should moved it to Failed. // Fail the payment, which should moved it to Failed.
failReason := FailureReasonNoRoute failReason := FailureReasonNoRoute
err = pControl.Fail(info.PaymentHash, failReason) _, err = pControl.Fail(info.PaymentHash, failReason)
if err != nil { if err != nil {
t.Fatalf("unable to fail payment hash: %v", err) t.Fatalf("unable to fail payment hash: %v", err)
} }
@ -270,7 +270,7 @@ func TestPaymentControlFailsWithoutInFlight(t *testing.T) {
} }
// Calling Fail should return an error. // Calling Fail should return an error.
err = pControl.Fail(info.PaymentHash, FailureReasonNoRoute) _, err = pControl.Fail(info.PaymentHash, FailureReasonNoRoute)
if err != ErrPaymentNotInitiated { if err != ErrPaymentNotInitiated {
t.Fatalf("expected ErrPaymentNotInitiated, got %v", err) t.Fatalf("expected ErrPaymentNotInitiated, got %v", err)
} }
@ -330,7 +330,7 @@ func TestPaymentControlDeleteNonInFligt(t *testing.T) {
if p.failed { if p.failed {
// Fail the payment, which should moved it to Failed. // Fail the payment, which should moved it to Failed.
failReason := FailureReasonNoRoute failReason := FailureReasonNoRoute
err = pControl.Fail(info.PaymentHash, failReason) _, err = pControl.Fail(info.PaymentHash, failReason)
if err != nil { if err != nil {
t.Fatalf("unable to fail payment hash: %v", err) t.Fatalf("unable to fail payment hash: %v", err)
} }

@ -94,10 +94,17 @@ const (
// destination was found during path finding. // destination was found during path finding.
FailureReasonNoRoute FailureReason = 1 FailureReasonNoRoute FailureReason = 1
// FailureReasonError indicates that an unexpected error happened during
// payment.
FailureReasonError FailureReason = 2
// FailureReasonIncorrectPaymentDetails indicates that either the hash
// is unknown or the final cltv delta or amount is incorrect.
FailureReasonIncorrectPaymentDetails FailureReason = 3
// TODO(halseth): cancel state. // TODO(halseth): cancel state.
// TODO(joostjager): Add failure reasons for: // TODO(joostjager): Add failure reasons for:
// UnknownPaymentHash, FinalInvalidAmt, FinalInvalidCltv
// LocalLiquidityInsufficient, RemoteCapacityInsufficient. // LocalLiquidityInsufficient, RemoteCapacityInsufficient.
) )
@ -108,6 +115,10 @@ func (r FailureReason) String() string {
return "timeout" return "timeout"
case FailureReasonNoRoute: case FailureReasonNoRoute:
return "no_route" return "no_route"
case FailureReasonError:
return "error"
case FailureReasonIncorrectPaymentDetails:
return "incorrect_payment_details"
} }
return "unknown" return "unknown"

@ -39,6 +39,13 @@ const (
//All possible routes were tried and failed permanently. Or were no //All possible routes were tried and failed permanently. Or were no
//routes to the destination at all. //routes to the destination at all.
PaymentState_FAILED_NO_ROUTE PaymentState = 3 PaymentState_FAILED_NO_ROUTE PaymentState = 3
//*
//A non-recoverable error has occured.
PaymentState_FAILED_ERROR PaymentState = 4
//*
//Payment details incorrect (unknown hash, invalid amt or
//invalid final cltv delta)
PaymentState_FAILED_INCORRECT_PAYMENT_DETAILS PaymentState = 5
) )
var PaymentState_name = map[int32]string{ var PaymentState_name = map[int32]string{
@ -46,13 +53,17 @@ var PaymentState_name = map[int32]string{
1: "SUCCEEDED", 1: "SUCCEEDED",
2: "FAILED_TIMEOUT", 2: "FAILED_TIMEOUT",
3: "FAILED_NO_ROUTE", 3: "FAILED_NO_ROUTE",
4: "FAILED_ERROR",
5: "FAILED_INCORRECT_PAYMENT_DETAILS",
} }
var PaymentState_value = map[string]int32{ var PaymentState_value = map[string]int32{
"IN_FLIGHT": 0, "IN_FLIGHT": 0,
"SUCCEEDED": 1, "SUCCEEDED": 1,
"FAILED_TIMEOUT": 2, "FAILED_TIMEOUT": 2,
"FAILED_NO_ROUTE": 3, "FAILED_NO_ROUTE": 3,
"FAILED_ERROR": 4,
"FAILED_INCORRECT_PAYMENT_DETAILS": 5,
} }
func (x PaymentState) String() string { func (x PaymentState) String() string {
@ -189,10 +200,13 @@ type SendPaymentRequest struct {
//* //*
//An optional maximum total time lock for the route. If zero, there is no //An optional maximum total time lock for the route. If zero, there is no
//maximum enforced. //maximum enforced.
CltvLimit int32 `protobuf:"varint,9,opt,name=cltv_limit,json=cltvLimit,proto3" json:"cltv_limit,omitempty"` CltvLimit int32 `protobuf:"varint,9,opt,name=cltv_limit,json=cltvLimit,proto3" json:"cltv_limit,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"` //*
XXX_unrecognized []byte `json:"-"` //Optional route hints to reach the destination through private channels.
XXX_sizecache int32 `json:"-"` RouteHints []*lnrpc.RouteHint `protobuf:"bytes,10,rep,name=route_hints,proto3" json:"route_hints,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
} }
func (m *SendPaymentRequest) Reset() { *m = SendPaymentRequest{} } func (m *SendPaymentRequest) Reset() { *m = SendPaymentRequest{} }
@ -283,6 +297,13 @@ func (m *SendPaymentRequest) GetCltvLimit() int32 {
return 0 return 0
} }
func (m *SendPaymentRequest) GetRouteHints() []*lnrpc.RouteHint {
if m != nil {
return m.RouteHints
}
return nil
}
type TrackPaymentRequest struct { type TrackPaymentRequest struct {
/// The hash of the payment to look up. /// The hash of the payment to look up.
PaymentHash []byte `protobuf:"bytes,1,opt,name=payment_hash,json=paymentHash,proto3" json:"payment_hash,omitempty"` PaymentHash []byte `protobuf:"bytes,1,opt,name=payment_hash,json=paymentHash,proto3" json:"payment_hash,omitempty"`
@ -1146,106 +1167,109 @@ func init() {
func init() { proto.RegisterFile("routerrpc/router.proto", fileDescriptor_7a0613f69d37b0a5) } func init() { proto.RegisterFile("routerrpc/router.proto", fileDescriptor_7a0613f69d37b0a5) }
var fileDescriptor_7a0613f69d37b0a5 = []byte{ var fileDescriptor_7a0613f69d37b0a5 = []byte{
// 1575 bytes of a gzipped FileDescriptorProto // 1623 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x57, 0xdd, 0x72, 0x22, 0xc7, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x57, 0xdd, 0x72, 0x1a, 0xc9,
0x15, 0x36, 0x02, 0x09, 0x71, 0xf8, 0xd1, 0xa8, 0xa5, 0xd5, 0xb2, 0x68, 0xb5, 0x96, 0x27, 0xc9, 0x15, 0x5e, 0x04, 0x08, 0x71, 0xf8, 0x1b, 0xb5, 0x64, 0x79, 0x8c, 0x2c, 0xaf, 0x76, 0x92, 0x78,
0x5a, 0xb5, 0xe5, 0x48, 0x09, 0xa9, 0x75, 0xf9, 0x2a, 0x29, 0x16, 0x1a, 0x33, 0x59, 0x98, 0x91, 0x55, 0xae, 0x8d, 0x94, 0x90, 0xf2, 0xd6, 0x5e, 0x25, 0x85, 0xa1, 0x59, 0x26, 0x86, 0x19, 0x6d,
0x1b, 0x58, 0x5b, 0xc9, 0x45, 0x57, 0x0b, 0x5a, 0x30, 0x25, 0x98, 0xc1, 0xd3, 0x8d, 0xb3, 0xca, 0x03, 0xde, 0x75, 0x72, 0xd1, 0xd5, 0x82, 0x16, 0x4c, 0x09, 0x66, 0xd8, 0x99, 0x66, 0x63, 0xe5,
0x45, 0xee, 0x92, 0xd7, 0x49, 0x9e, 0x20, 0x97, 0x79, 0x87, 0xbc, 0x4d, 0xaa, 0xbb, 0x07, 0x18, 0x22, 0x77, 0xc9, 0x5d, 0x9e, 0x25, 0x79, 0x82, 0xbc, 0x47, 0x5e, 0x25, 0x57, 0xa9, 0xee, 0x1e,
0x10, 0xda, 0xf8, 0x4a, 0xcc, 0x77, 0xbe, 0x3e, 0xe7, 0xf4, 0xf9, 0xeb, 0x23, 0x38, 0x89, 0xc2, 0x60, 0x40, 0xc8, 0xeb, 0x2b, 0x31, 0xdf, 0xf9, 0xfa, 0x9c, 0xd3, 0xe7, 0xaf, 0x8f, 0xe0, 0x24,
0xb9, 0xe4, 0x51, 0x34, 0x1b, 0x5c, 0x99, 0x5f, 0x97, 0xb3, 0x28, 0x94, 0x21, 0xca, 0x2d, 0xf1, 0x0c, 0x16, 0x82, 0x87, 0xe1, 0x7c, 0x78, 0xa5, 0x7f, 0x5d, 0xce, 0xc3, 0x40, 0x04, 0x28, 0xbf,
0x4a, 0x2e, 0x9a, 0x0d, 0x0c, 0x6a, 0xff, 0x67, 0x07, 0x50, 0x97, 0x07, 0xc3, 0x6b, 0xf6, 0x30, 0xc2, 0xab, 0xf9, 0x70, 0x3e, 0xd4, 0xa8, 0xf5, 0xbf, 0x3d, 0x40, 0x3d, 0xee, 0x8f, 0xae, 0xd9,
0xe5, 0x81, 0x24, 0xfc, 0xc7, 0x39, 0x17, 0x12, 0x21, 0xc8, 0x0c, 0xb9, 0x90, 0xe5, 0xd4, 0x79, 0xfd, 0x8c, 0xfb, 0x82, 0xf0, 0x1f, 0x17, 0x3c, 0x12, 0x08, 0x41, 0x66, 0xc4, 0x23, 0x61, 0xa6,
0xea, 0xa2, 0x40, 0xf4, 0x6f, 0x64, 0x41, 0x9a, 0x4d, 0x65, 0x79, 0xe7, 0x3c, 0x75, 0x91, 0x26, 0xce, 0x53, 0x17, 0x45, 0xa2, 0x7e, 0x23, 0x03, 0xd2, 0x6c, 0x26, 0xcc, 0xbd, 0xf3, 0xd4, 0x45,
0xea, 0x27, 0xfa, 0x02, 0x0a, 0x33, 0x73, 0x8e, 0x8e, 0x99, 0x18, 0x97, 0xd3, 0x9a, 0x9d, 0x8f, 0x9a, 0xc8, 0x9f, 0xe8, 0x0b, 0x28, 0xce, 0xf5, 0x39, 0x3a, 0x61, 0xd1, 0xc4, 0x4c, 0x2b, 0x76,
0xb1, 0x16, 0x13, 0x63, 0x74, 0x01, 0xd6, 0x9d, 0x1f, 0xb0, 0x09, 0x1d, 0x4c, 0xe4, 0x4f, 0x74, 0x21, 0xc6, 0xda, 0x2c, 0x9a, 0xa0, 0x0b, 0x30, 0x6e, 0x3d, 0x9f, 0x4d, 0xe9, 0x70, 0x2a, 0x7e,
0xc8, 0x27, 0x92, 0x95, 0x33, 0xe7, 0xa9, 0x8b, 0x5d, 0x52, 0xd2, 0x78, 0x7d, 0x22, 0x7f, 0x6a, 0xa2, 0x23, 0x3e, 0x15, 0xcc, 0xcc, 0x9c, 0xa7, 0x2e, 0xb2, 0xa4, 0xac, 0xf0, 0xc6, 0x54, 0xfc,
0x28, 0x14, 0x7d, 0x09, 0x07, 0x0b, 0x65, 0x91, 0xf1, 0xa2, 0xbc, 0x7b, 0x9e, 0xba, 0xc8, 0x91, 0xd4, 0x94, 0x28, 0xfa, 0x12, 0x2a, 0x4b, 0x65, 0xa1, 0xf6, 0xc2, 0xcc, 0x9e, 0xa7, 0x2e, 0xf2,
0xd2, 0x6c, 0xdd, 0xb7, 0x2f, 0xe1, 0x40, 0xfa, 0x53, 0x1e, 0xce, 0x25, 0x15, 0x7c, 0x10, 0x06, 0xa4, 0x3c, 0xdf, 0xf4, 0xed, 0x4b, 0xa8, 0x08, 0x6f, 0xc6, 0x83, 0x85, 0xa0, 0x11, 0x1f, 0x06,
0x43, 0x51, 0xde, 0x33, 0x1a, 0x63, 0xb8, 0x6b, 0x50, 0x64, 0x43, 0xf1, 0x8e, 0x73, 0x3a, 0xf1, 0xfe, 0x28, 0x32, 0xf7, 0xb5, 0xc6, 0x18, 0xee, 0x69, 0x14, 0x59, 0x50, 0xba, 0xe5, 0x9c, 0x4e,
0xa7, 0xbe, 0xa4, 0x82, 0xc9, 0x72, 0x56, 0xbb, 0x9e, 0xbf, 0xe3, 0xbc, 0xad, 0xb0, 0x2e, 0x93, 0xbd, 0x99, 0x27, 0x68, 0xc4, 0x84, 0x99, 0x53, 0xae, 0x17, 0x6e, 0x39, 0xef, 0x48, 0xac, 0xc7,
0xca, 0xbf, 0x70, 0x2e, 0x47, 0xa1, 0x1f, 0x8c, 0xe8, 0x60, 0xcc, 0x02, 0xea, 0x0f, 0xcb, 0xfb, 0x84, 0xf4, 0x2f, 0x58, 0x88, 0x71, 0xe0, 0xf9, 0x63, 0x3a, 0x9c, 0x30, 0x9f, 0x7a, 0x23, 0xf3,
0xe7, 0xa9, 0x8b, 0x0c, 0x29, 0x2d, 0xf0, 0xfa, 0x98, 0x05, 0xce, 0x10, 0x9d, 0x01, 0xe8, 0x3b, 0xe0, 0x3c, 0x75, 0x91, 0x21, 0xe5, 0x25, 0xde, 0x98, 0x30, 0xdf, 0x1e, 0xa1, 0x33, 0x00, 0x75,
0x68, 0x75, 0xe5, 0x9c, 0xb6, 0x98, 0x53, 0x88, 0xd6, 0x65, 0x7f, 0x03, 0x47, 0xbd, 0x88, 0x0d, 0x07, 0xa5, 0xce, 0xcc, 0x2b, 0x8b, 0x79, 0x89, 0x28, 0x5d, 0xa8, 0x06, 0x05, 0x15, 0x60, 0x3a,
0xee, 0x37, 0x02, 0xb9, 0x19, 0xa2, 0xd4, 0xa3, 0x10, 0xd9, 0x7f, 0x83, 0x62, 0x7c, 0xa8, 0x2b, 0xf1, 0x7c, 0x11, 0x99, 0x70, 0x9e, 0xbe, 0x28, 0xd4, 0x8c, 0xcb, 0xa9, 0x2f, 0x63, 0x4d, 0xa4,
0x99, 0x9c, 0x0b, 0xf4, 0x6b, 0xd8, 0x15, 0x92, 0x49, 0xae, 0xc9, 0xa5, 0xea, 0xf3, 0xcb, 0x65, 0xa4, 0xed, 0xf9, 0x82, 0x24, 0x49, 0xd6, 0x37, 0x70, 0xd4, 0x0f, 0xd9, 0xf0, 0x6e, 0x2b, 0xf8,
0xe6, 0x2e, 0x13, 0x44, 0x4e, 0x0c, 0x0b, 0x55, 0x60, 0x7f, 0x16, 0x71, 0x7f, 0xca, 0x46, 0x5c, 0xdb, 0x61, 0x4d, 0x3d, 0x08, 0xab, 0xf5, 0x37, 0x28, 0xc5, 0x87, 0x7a, 0x82, 0x89, 0x45, 0x84,
0x27, 0xa7, 0x40, 0x96, 0xdf, 0xc8, 0x86, 0x5d, 0x7d, 0x58, 0xa7, 0x26, 0x5f, 0x2d, 0x5c, 0x4e, 0x7e, 0x0d, 0xd9, 0x48, 0x30, 0xc1, 0x15, 0xb9, 0x5c, 0x7b, 0x7a, 0xb9, 0xca, 0xf6, 0x65, 0x82,
0x02, 0xa5, 0x86, 0x28, 0x8c, 0x18, 0x91, 0xfd, 0x7b, 0x38, 0xd0, 0xdf, 0x4d, 0xce, 0x3f, 0x95, 0xc8, 0x89, 0x66, 0xa1, 0x2a, 0x1c, 0xcc, 0x43, 0xee, 0xcd, 0xd8, 0x98, 0xab, 0x84, 0x16, 0xc9,
0xfe, 0xe7, 0x90, 0x65, 0x53, 0x13, 0x47, 0x53, 0x02, 0x7b, 0x6c, 0xaa, 0x42, 0x68, 0x0f, 0xc1, 0xea, 0x1b, 0x59, 0x90, 0x55, 0x87, 0x55, 0x3a, 0x0b, 0xb5, 0x62, 0xf2, 0x0e, 0x44, 0x8b, 0xac,
0x5a, 0x9d, 0x17, 0xb3, 0x30, 0x10, 0x5c, 0x85, 0x55, 0x29, 0x57, 0x51, 0x55, 0x29, 0x98, 0xaa, 0xdf, 0x43, 0x45, 0x7d, 0xb7, 0x38, 0xff, 0x58, 0xc9, 0x3c, 0x85, 0x1c, 0x9b, 0xe9, 0xd8, 0xeb,
0x53, 0x29, 0x7d, 0xaa, 0x14, 0xe3, 0x4d, 0xce, 0x3b, 0x82, 0x49, 0xf4, 0xda, 0x64, 0x93, 0x4e, 0xb2, 0xd9, 0x67, 0x33, 0x19, 0x76, 0x6b, 0x04, 0xc6, 0xfa, 0x7c, 0x34, 0x0f, 0xfc, 0x88, 0xcb,
0xc2, 0xc1, 0xbd, 0xaa, 0x0f, 0xf6, 0x10, 0xab, 0x2f, 0x2a, 0xb8, 0x1d, 0x0e, 0xee, 0x1b, 0x0a, 0x54, 0x48, 0xe5, 0x32, 0x13, 0x32, 0x6d, 0x33, 0x79, 0x2a, 0xa5, 0x4e, 0x95, 0x63, 0xbc, 0xc5,
0xb4, 0xff, 0x6c, 0xea, 0xb4, 0x17, 0x1a, 0xdf, 0x7f, 0x76, 0x78, 0x57, 0x21, 0xd8, 0x79, 0x3a, 0x79, 0x37, 0x62, 0x02, 0xbd, 0xd4, 0x15, 0x40, 0xa7, 0xc1, 0xf0, 0x4e, 0xd6, 0x14, 0xbb, 0x8f,
0x04, 0x14, 0x8e, 0xd6, 0x94, 0xc7, 0xb7, 0x48, 0x46, 0x36, 0xb5, 0x11, 0xd9, 0xaf, 0x20, 0x7b, 0xd5, 0x97, 0x24, 0xdc, 0x09, 0x86, 0x77, 0x4d, 0x09, 0x5a, 0x7f, 0xd6, 0xb5, 0xdd, 0x0f, 0xb4,
0xc7, 0xfc, 0xc9, 0x3c, 0x5a, 0x28, 0x46, 0x89, 0x34, 0x35, 0x8d, 0x84, 0x2c, 0x28, 0xf6, 0x3f, 0xef, 0x9f, 0x1c, 0xde, 0x75, 0x08, 0xf6, 0x1e, 0x0f, 0x01, 0x85, 0xa3, 0x0d, 0xe5, 0xf1, 0x2d,
0xb2, 0x90, 0x8d, 0x41, 0x54, 0x85, 0xcc, 0x20, 0x1c, 0x2e, 0xb2, 0xfb, 0xea, 0xf1, 0xb1, 0xc5, 0x92, 0x91, 0x4d, 0x6d, 0x45, 0xf6, 0x2b, 0xc8, 0xdd, 0x32, 0x6f, 0xba, 0x08, 0x97, 0x8a, 0x51,
0xdf, 0x7a, 0x38, 0xe4, 0x44, 0x73, 0x51, 0x15, 0x9e, 0xc5, 0xaa, 0xa8, 0x08, 0xe7, 0xd1, 0x80, 0x22, 0x4d, 0x2d, 0x2d, 0x21, 0x4b, 0x8a, 0xf5, 0x8f, 0x1c, 0xe4, 0x62, 0x10, 0xd5, 0x20, 0x33,
0xd3, 0xd9, 0xfc, 0xf6, 0x9e, 0x3f, 0xc4, 0x09, 0x3f, 0x8a, 0x85, 0x5d, 0x2d, 0xbb, 0xd6, 0x22, 0x0c, 0x46, 0xcb, 0xec, 0xbe, 0x78, 0x78, 0x6c, 0xf9, 0xb7, 0x11, 0x8c, 0x38, 0x51, 0x5c, 0x54,
0xf4, 0x07, 0x28, 0xa9, 0x8a, 0x0e, 0xf8, 0x84, 0xce, 0x67, 0x43, 0xb6, 0x2c, 0x82, 0x72, 0xc2, 0x83, 0x27, 0xb1, 0x2a, 0x1a, 0x05, 0x8b, 0x70, 0xc8, 0xe9, 0x7c, 0x71, 0x73, 0xc7, 0xef, 0xe3,
0x62, 0xdd, 0x10, 0xfa, 0x5a, 0x4e, 0x8a, 0x83, 0xe4, 0x27, 0x3a, 0x85, 0xdc, 0x58, 0x4e, 0x06, 0x84, 0x1f, 0xc5, 0xc2, 0x9e, 0x92, 0x5d, 0x2b, 0x11, 0xfa, 0x03, 0x94, 0x65, 0x17, 0xf8, 0x7c,
0x26, 0x7b, 0x19, 0xdd, 0x14, 0xfb, 0x0a, 0xd0, 0x79, 0xb3, 0xa1, 0x18, 0x06, 0x7e, 0x18, 0x50, 0x4a, 0x17, 0xf3, 0x11, 0x5b, 0x15, 0x81, 0x99, 0xb0, 0xd8, 0xd0, 0x84, 0x81, 0x92, 0x93, 0xd2,
0x31, 0x66, 0xb4, 0xfa, 0xf6, 0x6b, 0xdd, 0xac, 0x05, 0x92, 0xd7, 0x60, 0x77, 0xcc, 0xaa, 0x6f, 0x30, 0xf9, 0x89, 0x4e, 0x21, 0x3f, 0x11, 0xd3, 0xa1, 0xce, 0x5e, 0x46, 0x35, 0xd2, 0x81, 0x04,
0xbf, 0x46, 0x9f, 0x43, 0x5e, 0xb7, 0x0c, 0xff, 0x38, 0xf3, 0xa3, 0x07, 0xdd, 0xa5, 0x45, 0xa2, 0x54, 0xde, 0x2c, 0x28, 0x05, 0xbe, 0x17, 0xf8, 0x34, 0x9a, 0x30, 0x5a, 0x7b, 0xfd, 0xb5, 0x6a,
0xbb, 0x08, 0x6b, 0x04, 0x1d, 0xc3, 0xee, 0xdd, 0x84, 0x8d, 0x84, 0xee, 0xcc, 0x22, 0x31, 0x1f, 0xf0, 0x22, 0x29, 0x28, 0xb0, 0x37, 0x61, 0xb5, 0xd7, 0x5f, 0xa3, 0xcf, 0xa1, 0xa0, 0xda, 0x8c,
0xf6, 0x7f, 0x33, 0x90, 0x4f, 0x84, 0x00, 0x15, 0x60, 0x9f, 0xe0, 0x2e, 0x26, 0x1f, 0x70, 0xc3, 0x7f, 0x98, 0x7b, 0xe1, 0xbd, 0xea, 0xec, 0x12, 0x51, 0x9d, 0x87, 0x15, 0x82, 0x8e, 0x21, 0x7b,
0xfa, 0x0c, 0x95, 0xe1, 0xb8, 0xef, 0xbe, 0x77, 0xbd, 0xef, 0x5d, 0x7a, 0x5d, 0xbb, 0xe9, 0x60, 0x3b, 0x65, 0xe3, 0x48, 0x75, 0x73, 0x89, 0xe8, 0x0f, 0xeb, 0xbf, 0x19, 0x28, 0x24, 0x42, 0x80,
0xb7, 0x47, 0x5b, 0xb5, 0x6e, 0xcb, 0x4a, 0xa1, 0x97, 0x50, 0x76, 0xdc, 0xba, 0x47, 0x08, 0xae, 0x8a, 0x70, 0x40, 0x70, 0x0f, 0x93, 0x77, 0xb8, 0x69, 0x7c, 0x86, 0x4c, 0x38, 0x1e, 0x38, 0x6f,
0xf7, 0x96, 0xb2, 0x5a, 0xc7, 0xeb, 0xbb, 0x3d, 0x6b, 0x07, 0x7d, 0x0e, 0xa7, 0x4d, 0xc7, 0xad, 0x1d, 0xf7, 0x7b, 0x87, 0x5e, 0xd7, 0xdf, 0x77, 0xb1, 0xd3, 0xa7, 0xed, 0x7a, 0xaf, 0x6d, 0xa4,
0xb5, 0xe9, 0x8a, 0x53, 0x6f, 0xf7, 0x3e, 0x50, 0xfc, 0xc3, 0xb5, 0x43, 0x6e, 0xac, 0xf4, 0x36, 0xd0, 0x73, 0x30, 0x6d, 0xa7, 0xe1, 0x12, 0x82, 0x1b, 0xfd, 0x95, 0xac, 0xde, 0x75, 0x07, 0x4e,
0x42, 0xab, 0xd7, 0xae, 0x2f, 0x34, 0x64, 0xd0, 0x0b, 0x78, 0x66, 0x08, 0xe6, 0x08, 0xed, 0x79, 0xdf, 0xd8, 0x43, 0x9f, 0xc3, 0x69, 0xcb, 0x76, 0xea, 0x1d, 0xba, 0xe6, 0x34, 0x3a, 0xfd, 0x77,
0x1e, 0xed, 0x7a, 0x9e, 0x6b, 0xed, 0xa2, 0x43, 0x28, 0x3a, 0xee, 0x87, 0x5a, 0xdb, 0x69, 0x50, 0x14, 0xff, 0x70, 0x6d, 0x93, 0xf7, 0x46, 0x7a, 0x17, 0xa1, 0xdd, 0xef, 0x34, 0x96, 0x1a, 0x32,
0x82, 0x6b, 0xed, 0x8e, 0xb5, 0x87, 0x8e, 0xe0, 0x60, 0x93, 0x97, 0x55, 0x2a, 0x16, 0x3c, 0xcf, 0xe8, 0x19, 0x3c, 0xd1, 0x04, 0x7d, 0x84, 0xf6, 0x5d, 0x97, 0xf6, 0x5c, 0xd7, 0x31, 0xb2, 0xe8,
0x75, 0x3c, 0x97, 0x7e, 0xc0, 0xa4, 0xeb, 0x78, 0xae, 0xb5, 0x8f, 0x4e, 0x00, 0xad, 0x8b, 0x5a, 0x10, 0x4a, 0xb6, 0xf3, 0xae, 0xde, 0xb1, 0x9b, 0x94, 0xe0, 0x7a, 0xa7, 0x6b, 0xec, 0xa3, 0x23,
0x9d, 0x5a, 0xdd, 0xca, 0xa1, 0x67, 0x70, 0xb8, 0x8e, 0xbf, 0xc7, 0x37, 0x16, 0xa8, 0x30, 0x18, 0xa8, 0x6c, 0xf3, 0x72, 0x52, 0xc5, 0x92, 0xe7, 0x3a, 0xb6, 0xeb, 0xd0, 0x77, 0x98, 0xf4, 0x6c,
0xc7, 0xe8, 0x3b, 0xdc, 0xf6, 0xbe, 0xa7, 0x1d, 0xc7, 0x75, 0x3a, 0xfd, 0x8e, 0x95, 0x47, 0xc7, 0xd7, 0x31, 0x0e, 0xd0, 0x09, 0xa0, 0x4d, 0x51, 0xbb, 0x5b, 0x6f, 0x18, 0x79, 0xf4, 0x04, 0x0e,
0x60, 0x35, 0x31, 0xa6, 0x8e, 0xdb, 0xed, 0x37, 0x9b, 0x4e, 0xdd, 0xc1, 0x6e, 0xcf, 0x2a, 0x18, 0x37, 0xf1, 0xb7, 0xf8, 0xbd, 0x01, 0x32, 0x0c, 0xda, 0x31, 0xfa, 0x06, 0x77, 0xdc, 0xef, 0x69,
0xcb, 0xdb, 0x2e, 0x5e, 0x54, 0x07, 0xea, 0xad, 0x9a, 0xeb, 0xe2, 0x36, 0x6d, 0x38, 0xdd, 0xda, 0xd7, 0x76, 0xec, 0xee, 0xa0, 0x6b, 0x14, 0xd0, 0x31, 0x18, 0x2d, 0x8c, 0xa9, 0xed, 0xf4, 0x06,
0xbb, 0x36, 0x6e, 0x58, 0x25, 0x74, 0x06, 0x2f, 0x7a, 0xb8, 0x73, 0xed, 0x91, 0x1a, 0xb9, 0xa1, 0xad, 0x96, 0xdd, 0xb0, 0xb1, 0xd3, 0x37, 0x8a, 0xda, 0xf2, 0xae, 0x8b, 0x97, 0xe4, 0x81, 0x46,
0x0b, 0x79, 0xb3, 0xe6, 0xb4, 0xfb, 0x04, 0x5b, 0x07, 0xe8, 0x0b, 0x38, 0x23, 0xf8, 0xbb, 0xbe, 0xbb, 0xee, 0x38, 0xb8, 0x43, 0x9b, 0x76, 0xaf, 0xfe, 0xa6, 0x83, 0x9b, 0x46, 0x19, 0x9d, 0xc1,
0x43, 0x70, 0x83, 0xba, 0x5e, 0x03, 0xd3, 0x26, 0xae, 0xf5, 0xfa, 0x04, 0xd3, 0x8e, 0xd3, 0xed, 0xb3, 0x3e, 0xee, 0x5e, 0xbb, 0xa4, 0x4e, 0xde, 0xd3, 0xa5, 0xbc, 0x55, 0xb7, 0x3b, 0x03, 0x82,
0x3a, 0xee, 0xb7, 0x96, 0x85, 0x7e, 0x09, 0xe7, 0x4b, 0xca, 0x52, 0xc1, 0x06, 0xeb, 0x50, 0xdd, 0x8d, 0x0a, 0xfa, 0x02, 0xce, 0x08, 0xfe, 0x6e, 0x60, 0x13, 0xdc, 0xa4, 0x8e, 0xdb, 0xc4, 0xb4,
0x6f, 0x91, 0x4f, 0x17, 0xff, 0xd0, 0xa3, 0xd7, 0x18, 0x13, 0x0b, 0xa1, 0x0a, 0x9c, 0xac, 0xcc, 0x85, 0xeb, 0xfd, 0x01, 0xc1, 0xb4, 0x6b, 0xf7, 0x7a, 0xb6, 0xf3, 0xad, 0x61, 0xa0, 0x5f, 0xc2,
0x1b, 0x03, 0xb1, 0xed, 0x23, 0x25, 0xbb, 0xc6, 0xa4, 0x53, 0x73, 0x55, 0x82, 0xd7, 0x64, 0xc7, 0xf9, 0x8a, 0xb2, 0x52, 0xb0, 0xc5, 0x3a, 0x94, 0xf7, 0x5b, 0xe6, 0xd3, 0xc1, 0x3f, 0xf4, 0xe9,
0xca, 0xed, 0x95, 0x6c, 0xd3, 0xed, 0x67, 0xf6, 0x3f, 0xd3, 0x50, 0x5c, 0x2b, 0x7a, 0xf4, 0x12, 0x35, 0xc6, 0xc4, 0x40, 0xa8, 0x0a, 0x27, 0x6b, 0xf3, 0xda, 0x40, 0x6c, 0xfb, 0x48, 0xca, 0xae,
0x72, 0xc2, 0x1f, 0x05, 0x4c, 0xaa, 0x56, 0x36, 0x5d, 0xbe, 0x02, 0xf4, 0xd4, 0x1f, 0x33, 0x3f, 0x31, 0xe9, 0xd6, 0x1d, 0x99, 0xe0, 0x0d, 0xd9, 0xb1, 0x74, 0x7b, 0x2d, 0xdb, 0x76, 0xfb, 0x89,
0x30, 0xe3, 0xc5, 0x74, 0x5b, 0x4e, 0x23, 0x7a, 0xb8, 0x3c, 0x87, 0xec, 0xe2, 0xd5, 0x48, 0xeb, 0xf5, 0xaf, 0x34, 0x94, 0x36, 0x8a, 0x1e, 0x3d, 0x87, 0x7c, 0xe4, 0x8d, 0x7d, 0x26, 0x64, 0x2b,
0x06, 0xd9, 0x1b, 0x98, 0xd7, 0xe2, 0x25, 0xe4, 0xd4, 0xfc, 0x12, 0x92, 0x4d, 0x67, 0xba, 0x77, 0xeb, 0x2e, 0x5f, 0x03, 0xea, 0xa5, 0x98, 0x30, 0xcf, 0xd7, 0xe3, 0x45, 0x77, 0x5b, 0x5e, 0x21,
0x8a, 0x64, 0x05, 0xa0, 0x5f, 0x40, 0x71, 0xca, 0x85, 0x60, 0x23, 0x4e, 0x4d, 0xfd, 0x83, 0x66, 0x6a, 0xb8, 0x3c, 0x85, 0xdc, 0xf2, 0xa5, 0x49, 0xab, 0x06, 0xd9, 0x1f, 0xea, 0x17, 0xe6, 0x39,
0x14, 0x62, 0xb0, 0xa9, 0x30, 0x45, 0x5a, 0xf4, 0xaf, 0x21, 0xed, 0x1a, 0x52, 0x0c, 0x1a, 0xd2, 0xe4, 0xe5, 0xfc, 0x8a, 0x04, 0x9b, 0xcd, 0x55, 0xef, 0x94, 0xc8, 0x1a, 0x40, 0xbf, 0x80, 0xd2,
0xe6, 0xf8, 0x94, 0x2c, 0x6e, 0xb3, 0xe4, 0xf8, 0x94, 0x0c, 0xbd, 0x81, 0x43, 0xd3, 0xcb, 0x7e, 0x8c, 0x47, 0x11, 0x1b, 0x73, 0xaa, 0xeb, 0x1f, 0x14, 0xa3, 0x18, 0x83, 0x2d, 0x89, 0x49, 0xd2,
0xe0, 0x4f, 0xe7, 0x53, 0xd3, 0xd3, 0x59, 0xed, 0xf2, 0x81, 0xee, 0x69, 0x83, 0xeb, 0xd6, 0x7e, 0xb2, 0x7f, 0x35, 0x29, 0xab, 0x49, 0x31, 0xa8, 0x49, 0xdb, 0xe3, 0x53, 0xb0, 0xb8, 0xcd, 0x92,
0x01, 0xfb, 0xb7, 0x4c, 0x70, 0x35, 0xb9, 0xf5, 0x5b, 0x58, 0x24, 0x59, 0xf5, 0xdd, 0xe4, 0x5c, 0xe3, 0x53, 0x30, 0xf4, 0x0a, 0x0e, 0x75, 0x2f, 0x7b, 0xbe, 0x37, 0x5b, 0xcc, 0x74, 0x4f, 0xe7,
0x89, 0xd4, 0x3c, 0x8f, 0xd4, 0x34, 0xc9, 0x19, 0xd1, 0x1d, 0xe7, 0x44, 0xc5, 0x71, 0x69, 0x81, 0x94, 0xcb, 0x15, 0xd5, 0xd3, 0x1a, 0x57, 0xad, 0xfd, 0x0c, 0x0e, 0x6e, 0x58, 0xc4, 0xe5, 0xe4,
0x7d, 0x5c, 0x59, 0xc8, 0x27, 0x2c, 0x18, 0x5c, 0x5b, 0x78, 0x03, 0x87, 0xfc, 0xa3, 0x8c, 0x18, 0x56, 0xef, 0x67, 0x89, 0xe4, 0xe4, 0x77, 0x8b, 0x73, 0x29, 0x92, 0xf3, 0x3c, 0x94, 0xd3, 0x24,
0x0d, 0x67, 0xec, 0xc7, 0x39, 0xa7, 0x43, 0x26, 0x59, 0xb9, 0xa0, 0x83, 0x7b, 0xa0, 0x05, 0x9e, 0xaf, 0x45, 0xb7, 0x9c, 0x13, 0x19, 0xc7, 0x95, 0x05, 0xf6, 0x61, 0x6d, 0xa1, 0x90, 0xb0, 0xa0,
0xc6, 0x1b, 0x4c, 0x32, 0xfb, 0x25, 0x54, 0x08, 0x17, 0x5c, 0x76, 0x7c, 0x21, 0xfc, 0x30, 0xa8, 0x71, 0x65, 0xe1, 0x15, 0x1c, 0xf2, 0x0f, 0x22, 0x64, 0x34, 0x98, 0xb3, 0x1f, 0x17, 0x9c, 0x8e,
0x87, 0x81, 0x8c, 0xc2, 0x49, 0xfc, 0x00, 0xd8, 0x67, 0x70, 0xba, 0x55, 0x6a, 0x26, 0xb8, 0x3a, 0x98, 0x60, 0x66, 0x51, 0x05, 0xb7, 0xa2, 0x04, 0xae, 0xc2, 0x9b, 0x4c, 0x30, 0xeb, 0x39, 0x54,
0xfc, 0xdd, 0x9c, 0x47, 0x0f, 0xdb, 0x0f, 0xbf, 0x87, 0xd3, 0xad, 0xd2, 0x78, 0xfc, 0x7f, 0x05, 0x09, 0x8f, 0xb8, 0xe8, 0x7a, 0x51, 0xe4, 0x05, 0x7e, 0x23, 0xf0, 0x45, 0x18, 0x4c, 0xe3, 0x07,
0xbb, 0x41, 0x38, 0xe4, 0xa2, 0x9c, 0x3a, 0x4f, 0x5f, 0xe4, 0xab, 0x27, 0x89, 0xb9, 0xe9, 0x86, 0xc0, 0x3a, 0x83, 0xd3, 0x9d, 0x52, 0x3d, 0xc1, 0xe5, 0xe1, 0xef, 0x16, 0x3c, 0xbc, 0xdf, 0x7d,
0x43, 0xde, 0xf2, 0x85, 0x0c, 0xa3, 0x07, 0x62, 0x48, 0xf6, 0xbf, 0x53, 0x90, 0x4f, 0xc0, 0xe8, 0xf8, 0x2d, 0x9c, 0xee, 0x94, 0xc6, 0xe3, 0xff, 0x2b, 0xc8, 0xfa, 0xc1, 0x88, 0x47, 0x66, 0x4a,
0x04, 0xf6, 0xe2, 0x19, 0x6d, 0x8a, 0x2a, 0xfe, 0x42, 0xaf, 0xa1, 0x34, 0x61, 0x42, 0x52, 0x35, 0x2d, 0x00, 0x27, 0x89, 0xb9, 0xe9, 0x04, 0x23, 0xde, 0xf6, 0x22, 0x11, 0x84, 0xf7, 0x44, 0x93,
0xb2, 0xa9, 0x4a, 0x52, 0xfc, 0xde, 0x6d, 0xa0, 0xe8, 0x1b, 0x78, 0x1e, 0xca, 0x31, 0x8f, 0xcc, 0xac, 0xff, 0xa4, 0xa0, 0x90, 0x80, 0xd1, 0x09, 0xec, 0xc7, 0x33, 0x5a, 0x17, 0x55, 0xfc, 0x85,
0x5a, 0x22, 0xe6, 0x83, 0x01, 0x17, 0x82, 0xce, 0xa2, 0xf0, 0x56, 0x97, 0xda, 0x0e, 0x79, 0x4a, 0x5e, 0x42, 0x79, 0xca, 0x22, 0x41, 0xe5, 0xc8, 0xa6, 0x32, 0x49, 0xf1, 0x7b, 0xb7, 0x85, 0xa2,
0x8c, 0xde, 0xc2, 0x7e, 0x5c, 0x23, 0xa2, 0x9c, 0xd1, 0xae, 0xbf, 0x78, 0x3c, 0xf2, 0x17, 0xde, 0x6f, 0xe0, 0x69, 0x20, 0x26, 0x3c, 0xd4, 0xab, 0x4c, 0xb4, 0x18, 0x0e, 0x79, 0x14, 0xd1, 0x79,
0x2f, 0xa9, 0xf6, 0xbf, 0x52, 0x50, 0x5a, 0x17, 0xa2, 0x57, 0xba, 0xfa, 0x75, 0x09, 0xfa, 0x43, 0x18, 0xdc, 0xa8, 0x52, 0xdb, 0x23, 0x8f, 0x89, 0xd1, 0x6b, 0x38, 0x88, 0x6b, 0x24, 0x32, 0x33,
0x7d, 0x8f, 0x0c, 0x49, 0x20, 0x3f, 0xfb, 0x2e, 0x55, 0x38, 0x9e, 0xfa, 0x01, 0x9d, 0xf1, 0x80, 0xca, 0xf5, 0x67, 0x0f, 0x47, 0xfe, 0xd2, 0xfb, 0x15, 0xd5, 0xfa, 0x77, 0x0a, 0xca, 0x9b, 0x42,
0x4d, 0xfc, 0xbf, 0x72, 0xba, 0x58, 0x24, 0xd2, 0x9a, 0xbd, 0x55, 0x86, 0x6c, 0x28, 0xac, 0x5d, 0xf4, 0x42, 0x55, 0xbf, 0x2a, 0x41, 0x6f, 0xa4, 0xee, 0x91, 0x21, 0x09, 0xe4, 0x93, 0xef, 0x52,
0x3a, 0xa3, 0x2f, 0xbd, 0x86, 0xbd, 0xe9, 0x43, 0x21, 0xb9, 0x11, 0xa1, 0x22, 0xe4, 0x1c, 0x97, 0x83, 0xe3, 0x99, 0xe7, 0xd3, 0x39, 0xf7, 0xd9, 0xd4, 0xfb, 0x2b, 0xa7, 0xcb, 0x45, 0x22, 0xad,
0x36, 0xdb, 0xce, 0xb7, 0xad, 0x9e, 0xf5, 0x99, 0xfa, 0xec, 0xf6, 0xeb, 0x75, 0x8c, 0x1b, 0xb8, 0xd8, 0x3b, 0x65, 0xc8, 0x82, 0xe2, 0xc6, 0xa5, 0x33, 0xea, 0xd2, 0x1b, 0xd8, 0xab, 0x7f, 0xa6,
0x61, 0xa5, 0x10, 0x82, 0x92, 0x1a, 0x04, 0xb8, 0x41, 0x7b, 0x4e, 0x07, 0x7b, 0x7d, 0xf5, 0x2a, 0xa0, 0x98, 0x5c, 0x89, 0x50, 0x09, 0xf2, 0xb6, 0x43, 0x5b, 0x1d, 0xfb, 0xdb, 0x76, 0xdf, 0xf8,
0x1c, 0xc1, 0x41, 0x8c, 0xb9, 0x1e, 0x25, 0x5e, 0xbf, 0x87, 0xad, 0x74, 0xf5, 0xef, 0x19, 0xd8, 0x4c, 0x7e, 0xf6, 0x06, 0x8d, 0x06, 0xc6, 0x4d, 0xdc, 0x34, 0x52, 0x08, 0x41, 0x59, 0x4e, 0x02,
0xd3, 0x9b, 0x40, 0x84, 0x5a, 0x90, 0x4f, 0xac, 0xc7, 0xe8, 0x2c, 0x11, 0xc8, 0xc7, 0x6b, 0x73, 0xdc, 0xa4, 0x7d, 0xbb, 0x8b, 0xdd, 0x81, 0x7c, 0x16, 0x8e, 0xa0, 0x12, 0x63, 0x8e, 0x4b, 0x89,
0xa5, 0xbc, 0x7d, 0x55, 0x9b, 0x8b, 0xdf, 0xa4, 0xd0, 0x1f, 0xa1, 0x90, 0x5c, 0x10, 0x51, 0xf2, 0x3b, 0xe8, 0x63, 0x23, 0x8d, 0x0c, 0x28, 0xc6, 0x20, 0x26, 0xc4, 0x25, 0x46, 0x46, 0xce, 0xb2,
0xe1, 0xdf, 0xb2, 0x39, 0x7e, 0x52, 0xd7, 0x7b, 0xb0, 0xb0, 0x90, 0xfe, 0x54, 0x3d, 0xda, 0xf1, 0x18, 0x79, 0xf8, 0xc4, 0x34, 0x71, 0xbf, 0x6e, 0x77, 0x7a, 0x46, 0xb6, 0xf6, 0xf7, 0x0c, 0xec,
0xea, 0x85, 0x2a, 0x09, 0xfe, 0xc6, 0x3e, 0x57, 0x39, 0xdd, 0x2a, 0x8b, 0xcb, 0xbc, 0x6d, 0xae, 0xab, 0x15, 0x22, 0x44, 0x6d, 0x28, 0x24, 0x76, 0x71, 0x74, 0x96, 0xc8, 0xc0, 0xc3, 0x1d, 0xbd,
0x18, 0x2f, 0x3f, 0x8f, 0xae, 0xb8, 0xbe, 0x71, 0x55, 0x5e, 0x3d, 0x25, 0x8e, 0xb5, 0x0d, 0xe1, 0x6a, 0xee, 0xde, 0xf1, 0x16, 0xd1, 0x6f, 0x52, 0xe8, 0x8f, 0x50, 0x4c, 0x6e, 0x96, 0x28, 0xb9,
0x68, 0x4b, 0x43, 0xa2, 0x5f, 0x25, 0x3d, 0x78, 0xb2, 0x9d, 0x2b, 0xaf, 0xff, 0x1f, 0x6d, 0x65, 0x31, 0xec, 0x58, 0x39, 0x3f, 0xaa, 0xeb, 0x2d, 0x18, 0x38, 0x12, 0xde, 0x4c, 0xbe, 0xf6, 0xf1,
0x65, 0x4b, 0xe7, 0xae, 0x59, 0x79, 0xba, 0xef, 0xd7, 0xac, 0x7c, 0x62, 0x00, 0xbc, 0xfb, 0xed, 0xce, 0x86, 0xaa, 0x09, 0xfe, 0xd6, 0x22, 0x58, 0x3d, 0xdd, 0x29, 0x8b, 0xfb, 0xa3, 0xa3, 0xaf,
0x9f, 0xae, 0x46, 0xbe, 0x1c, 0xcf, 0x6f, 0x2f, 0x07, 0xe1, 0xf4, 0x6a, 0xe2, 0x8f, 0xc6, 0x32, 0x18, 0x6f, 0x4d, 0x0f, 0xae, 0xb8, 0xb9, 0xaa, 0x55, 0x5f, 0x3c, 0x26, 0x8e, 0xb5, 0x8d, 0xe0,
0xf0, 0x83, 0x51, 0xc0, 0xe5, 0x5f, 0xc2, 0xe8, 0xfe, 0x6a, 0x12, 0x0c, 0xaf, 0xf4, 0x32, 0x79, 0x68, 0x47, 0x27, 0xa3, 0x5f, 0x25, 0x3d, 0x78, 0x74, 0x0e, 0x54, 0x5f, 0xfe, 0x1c, 0x6d, 0x6d,
0xb5, 0x54, 0x77, 0xbb, 0xa7, 0xff, 0xad, 0xfa, 0xdd, 0xff, 0x02, 0x00, 0x00, 0xff, 0xff, 0x9a, 0x65, 0x47, 0xcb, 0x6f, 0x58, 0x79, 0x7c, 0x60, 0x6c, 0x58, 0xf9, 0xc8, 0xe4, 0x78, 0xf3, 0xdb,
0xf3, 0x15, 0x70, 0x86, 0x0d, 0x00, 0x00, 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,
} }
// Reference imports to suppress errors if they are not otherwise used. // Reference imports to suppress errors if they are not otherwise used.

@ -58,6 +58,11 @@ message SendPaymentRequest {
maximum enforced. maximum enforced.
*/ */
int32 cltv_limit = 9; int32 cltv_limit = 9;
/**
Optional route hints to reach the destination through private channels.
*/
repeated lnrpc.RouteHint route_hints = 10 [json_name = "route_hints"];
} }
message TrackPaymentRequest { message TrackPaymentRequest {
@ -86,6 +91,17 @@ enum PaymentState {
routes to the destination at all. routes to the destination at all.
*/ */
FAILED_NO_ROUTE = 3; FAILED_NO_ROUTE = 3;
/**
A non-recoverable error has occured.
*/
FAILED_ERROR = 4;
/**
Payment details incorrect (unknown hash, invalid amt or
invalid final cltv delta)
*/
FAILED_INCORRECT_PAYMENT_DETAILS = 5;
} }

@ -6,6 +6,8 @@ import (
"fmt" "fmt"
"time" "time"
"github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcutil" "github.com/btcsuite/btcutil"
"github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc"
@ -382,6 +384,15 @@ func (r *RouterBackend) extractIntentFromSendRequest(
payIntent.PayAttemptTimeout = time.Second * payIntent.PayAttemptTimeout = time.Second *
time.Duration(rpcPayReq.TimeoutSeconds) time.Duration(rpcPayReq.TimeoutSeconds)
// Route hints.
routeHints, err := unmarshallRouteHints(
rpcPayReq.RouteHints,
)
if err != nil {
return nil, err
}
payIntent.RouteHints = routeHints
// If the payment request field isn't blank, then the details of the // If the payment request field isn't blank, then the details of the
// invoice are encoded entirely within the encoded payReq. So we'll // invoice are encoded entirely within the encoded payReq. So we'll
// attempt to decode it, populating the payment accordingly. // attempt to decode it, populating the payment accordingly.
@ -443,7 +454,9 @@ func (r *RouterBackend) extractIntentFromSendRequest(
copy(payIntent.Target[:], destKey) copy(payIntent.Target[:], destKey)
payIntent.FinalCLTVDelta = uint16(payReq.MinFinalCLTVExpiry()) payIntent.FinalCLTVDelta = uint16(payReq.MinFinalCLTVExpiry())
payIntent.RouteHints = payReq.RouteHints payIntent.RouteHints = append(
payIntent.RouteHints, payReq.RouteHints...,
)
} else { } else {
// Otherwise, If the payment request field was not specified // Otherwise, If the payment request field was not specified
// (and a custom route wasn't specified), construct the payment // (and a custom route wasn't specified), construct the payment
@ -493,6 +506,50 @@ func (r *RouterBackend) extractIntentFromSendRequest(
return payIntent, nil return payIntent, nil
} }
// unmarshallRouteHints unmarshalls a list of route hints.
func unmarshallRouteHints(rpcRouteHints []*lnrpc.RouteHint) (
[][]zpay32.HopHint, error) {
routeHints := make([][]zpay32.HopHint, 0, len(rpcRouteHints))
for _, rpcRouteHint := range rpcRouteHints {
routeHint := make(
[]zpay32.HopHint, 0, len(rpcRouteHint.HopHints),
)
for _, rpcHint := range rpcRouteHint.HopHints {
hint, err := unmarshallHopHint(rpcHint)
if err != nil {
return nil, err
}
routeHint = append(routeHint, hint)
}
routeHints = append(routeHints, routeHint)
}
return routeHints, nil
}
// unmarshallHopHint unmarshalls a single hop hint.
func unmarshallHopHint(rpcHint *lnrpc.HopHint) (zpay32.HopHint, error) {
pubBytes, err := hex.DecodeString(rpcHint.NodeId)
if err != nil {
return zpay32.HopHint{}, err
}
pubkey, err := btcec.ParsePubKey(pubBytes, btcec.S256())
if err != nil {
return zpay32.HopHint{}, err
}
return zpay32.HopHint{
NodeID: pubkey,
ChannelID: rpcHint.ChanId,
FeeBaseMSat: rpcHint.FeeBaseMsat,
FeeProportionalMillionths: rpcHint.FeeProportionalMillionths,
CLTVExpiryDelta: uint16(rpcHint.CltvExpiryDelta),
}, nil
}
// ValidatePayReqExpiry checks if the passed payment request has expired. In // ValidatePayReqExpiry checks if the passed payment request has expired. In
// the case it has expired, an error will be returned. // the case it has expired, an error will be returned.
func ValidatePayReqExpiry(payReq *zpay32.Invoice) error { func ValidatePayReqExpiry(payReq *zpay32.Invoice) error {

@ -502,8 +502,10 @@ func (s *Server) TrackPayment(request *TrackPaymentRequest,
func (s *Server) trackPayment(paymentHash lntypes.Hash, func (s *Server) trackPayment(paymentHash lntypes.Hash,
stream Router_TrackPaymentServer) error { stream Router_TrackPaymentServer) error {
router := s.cfg.RouterBackend
// Subscribe to the outcome of this payment. // Subscribe to the outcome of this payment.
inFlight, resultChan, err := s.cfg.RouterBackend.Tower.SubscribePayment( inFlight, resultChan, err := router.Tower.SubscribePayment(
paymentHash, paymentHash,
) )
switch { switch {
@ -538,20 +540,21 @@ func (s *Server) trackPayment(paymentHash lntypes.Hash,
status.State = PaymentState_SUCCEEDED status.State = PaymentState_SUCCEEDED
status.Preimage = result.Preimage[:] status.Preimage = result.Preimage[:]
status.Route = s.cfg.RouterBackend.MarshallRoute( status.Route = router.MarshallRoute(
result.Route, result.Route,
) )
} else { } else {
switch result.FailureReason { state, err := marshallFailureReason(
result.FailureReason,
case channeldb.FailureReasonTimeout: )
status.State = PaymentState_FAILED_TIMEOUT if err != nil {
return err
case channeldb.FailureReasonNoRoute: }
status.State = PaymentState_FAILED_NO_ROUTE status.State = state
if result.Route != nil {
default: status.Route = router.MarshallRoute(
return errors.New("unknown failure reason") result.Route,
)
} }
} }
@ -568,3 +571,26 @@ func (s *Server) trackPayment(paymentHash lntypes.Hash,
return nil return nil
} }
// marshallFailureReason marshalls the failure reason to the corresponding rpc
// type.
func marshallFailureReason(reason channeldb.FailureReason) (
PaymentState, error) {
switch reason {
case channeldb.FailureReasonTimeout:
return PaymentState_FAILED_TIMEOUT, nil
case channeldb.FailureReasonNoRoute:
return PaymentState_FAILED_NO_ROUTE, nil
case channeldb.FailureReasonError:
return PaymentState_FAILED_ERROR, nil
case channeldb.FailureReasonIncorrectPaymentDetails:
return PaymentState_FAILED_INCORRECT_PAYMENT_DETAILS, nil
}
return 0, errors.New("unknown failure reason")
}

@ -13834,7 +13834,7 @@ func testHoldInvoicePersistence(net *lntest.NetworkHarness, t *harnessTest) {
status.State) status.State)
} }
} else { } else {
if status.State != routerrpc.PaymentState_FAILED_NO_ROUTE { if status.State != routerrpc.PaymentState_FAILED_INCORRECT_PAYMENT_DETAILS {
t.Fatalf("state not failed: %v", t.Fatalf("state not failed: %v",
status.State) status.State)
} }

@ -114,16 +114,41 @@ func (p *controlTower) Success(paymentHash lntypes.Hash,
// Notify subscribers of success event. // Notify subscribers of success event.
p.notifyFinalEvent( p.notifyFinalEvent(
paymentHash, PaymentResult{ paymentHash, createSuccessResult(route, preimage),
Success: true,
Preimage: preimage,
Route: route,
},
) )
return nil return nil
} }
// createSuccessResult creates a success result to send to subscribers.
func createSuccessResult(rt *route.Route,
preimage lntypes.Preimage) *PaymentResult {
return &PaymentResult{
Success: true,
Preimage: preimage,
Route: rt,
}
}
// createFailResult creates a failed result to send to subscribers.
func createFailedResult(rt *route.Route,
reason channeldb.FailureReason) *PaymentResult {
result := &PaymentResult{
Success: false,
FailureReason: reason,
}
// In case of incorrect payment details, set the route. This can be used
// for probing and to extract a fee estimate from the route.
if reason == channeldb.FailureReasonIncorrectPaymentDetails {
result.Route = rt
}
return result
}
// Fail transitions a payment into the Failed state, and records the reason the // Fail transitions a payment into the Failed state, and records the reason the
// payment failed. After invoking this method, InitPayment should return nil on // payment failed. After invoking this method, InitPayment should return nil on
// its next call for this payment hash, allowing the switch to make a // its next call for this payment hash, allowing the switch to make a
@ -131,17 +156,14 @@ func (p *controlTower) Success(paymentHash lntypes.Hash,
func (p *controlTower) Fail(paymentHash lntypes.Hash, func (p *controlTower) Fail(paymentHash lntypes.Hash,
reason channeldb.FailureReason) error { reason channeldb.FailureReason) error {
err := p.db.Fail(paymentHash, reason) route, err := p.db.Fail(paymentHash, reason)
if err != nil { if err != nil {
return err return err
} }
// Notify subscribers of fail event. // Notify subscribers of fail event.
p.notifyFinalEvent( p.notifyFinalEvent(
paymentHash, PaymentResult{ paymentHash, createFailedResult(route, reason),
Success: false,
FailureReason: reason,
},
) )
return nil return nil
@ -191,16 +213,21 @@ func (p *controlTower) SubscribePayment(paymentHash lntypes.Hash) (
// a subscriber, because we can send the result on the channel // a subscriber, because we can send the result on the channel
// immediately. // immediately.
case channeldb.StatusSucceeded: case channeldb.StatusSucceeded:
event.Success = true event = *createSuccessResult(
event.Preimage = *payment.PaymentPreimage &payment.Attempt.Route, *payment.PaymentPreimage,
event.Route = &payment.Attempt.Route )
// Payment already failed. It is not necessary to register as a // Payment already failed. It is not necessary to register as a
// subscriber, because we can send the result on the channel // subscriber, because we can send the result on the channel
// immediately. // immediately.
case channeldb.StatusFailed: case channeldb.StatusFailed:
event.Success = false var route *route.Route
event.FailureReason = *payment.Failure if payment.Attempt != nil {
route = &payment.Attempt.Route
}
event = *createFailedResult(
route, *payment.Failure,
)
default: default:
return false, nil, errors.New("unknown payment status") return false, nil, errors.New("unknown payment status")
@ -216,7 +243,7 @@ func (p *controlTower) SubscribePayment(paymentHash lntypes.Hash) (
// notifyFinalEvent sends a final payment event to all subscribers of this // notifyFinalEvent sends a final payment event to all subscribers of this
// payment. The channel will be closed after this. // payment. The channel will be closed after this.
func (p *controlTower) notifyFinalEvent(paymentHash lntypes.Hash, func (p *controlTower) notifyFinalEvent(paymentHash lntypes.Hash,
event PaymentResult) { event *PaymentResult) {
// Get all subscribers for this hash. As there is only a single outcome, // Get all subscribers for this hash. As there is only a single outcome,
// the subscriber list can be cleared. // the subscriber list can be cleared.
@ -232,7 +259,7 @@ func (p *controlTower) notifyFinalEvent(paymentHash lntypes.Hash,
// Notify all subscribers of the event. The subscriber channel is // Notify all subscribers of the event. The subscriber channel is
// buffered, so it cannot block here. // buffered, so it cannot block here.
for _, subscriber := range list { for _, subscriber := range list {
subscriber <- event subscriber <- *event
close(subscriber) close(subscriber)
} }
} }

@ -341,39 +341,41 @@ func (p *paymentLifecycle) sendPaymentAttempt(firstHop lnwire.ShortChannelID,
// handleSendError inspects the given error from the Switch and determines // handleSendError inspects the given error from the Switch and determines
// whether we should make another payment attempt. // whether we should make another payment attempt.
func (p *paymentLifecycle) handleSendError(sendErr error) error { func (p *paymentLifecycle) handleSendError(sendErr error) error {
var finalOutcome bool var reason channeldb.FailureReason
// If an internal, non-forwarding error occurred, we can stop trying. // If an internal, non-forwarding error occurred, we can stop trying.
fErr, ok := sendErr.(*htlcswitch.ForwardingError) fErr, ok := sendErr.(*htlcswitch.ForwardingError)
if !ok { if !ok {
finalOutcome = true reason = channeldb.FailureReasonError
} else { } else {
finalOutcome = p.router.processSendError( var final bool
final, reason = p.router.processSendError(
p.paySession, &p.attempt.Route, fErr, p.paySession, &p.attempt.Route, fErr,
) )
// Save the forwarding error so it can be returned if this turns if !final {
// out to be the last attempt. // Save the forwarding error so it can be returned if
p.lastError = fErr // this turns out to be the last attempt.
} p.lastError = fErr
if finalOutcome { return nil
log.Errorf("Payment %x failed with final outcome: %v",
p.payment.PaymentHash, sendErr)
// Mark the payment failed with no route.
// TODO(halseth): make payment codes for the actual reason we
// don't continue path finding.
err := p.router.cfg.Control.Fail(
p.payment.PaymentHash, channeldb.FailureReasonNoRoute,
)
if err != nil {
return err
} }
// Terminal state, return the error we encountered.
return sendErr
} }
return nil log.Debugf("Payment %x failed: final_outcome=%v, raw_err=%v",
p.payment.PaymentHash, reason, sendErr)
// Mark the payment failed with no route.
//
// TODO(halseth): make payment codes for the actual reason we don't
// continue path finding.
err := p.router.cfg.Control.Fail(
p.payment.PaymentHash, reason,
)
if err != nil {
return err
}
// Terminal state, return the error we encountered.
return sendErr
} }

@ -1812,7 +1812,8 @@ func (r *ChannelRouter) sendPayment(
// to continue with an alternative route. This is indicated by the boolean // to continue with an alternative route. This is indicated by the boolean
// return value. // return value.
func (r *ChannelRouter) processSendError(paySession PaymentSession, func (r *ChannelRouter) processSendError(paySession PaymentSession,
rt *route.Route, fErr *htlcswitch.ForwardingError) bool { rt *route.Route, fErr *htlcswitch.ForwardingError) (
bool, channeldb.FailureReason) {
errSource := fErr.ErrorSource errSource := fErr.ErrorSource
errVertex := route.NewVertex(errSource) errVertex := route.NewVertex(errSource)
@ -1825,7 +1826,7 @@ func (r *ChannelRouter) processSendError(paySession PaymentSession,
rt, route.Vertex(errVertex), rt, route.Vertex(errVertex),
) )
if err != nil { if err != nil {
return true return true, channeldb.FailureReasonError
} }
// processChannelUpdateAndRetry is a closure that // processChannelUpdateAndRetry is a closure that
@ -1868,22 +1869,33 @@ func (r *ChannelRouter) processSendError(paySession PaymentSession,
// hash or we sent the wrong payment amount to the // hash or we sent the wrong payment amount to the
// destination, then we'll terminate immediately. // destination, then we'll terminate immediately.
case *lnwire.FailUnknownPaymentHash: case *lnwire.FailUnknownPaymentHash:
return true // TODO(joostjager): Check onionErr.Amount() whether it matches
// what we expect. (Will it ever not match, because if not
// final_incorrect_htlc_amount would be returned?)
return true, channeldb.FailureReasonIncorrectPaymentDetails
// If we sent the wrong amount to the destination, then // If we sent the wrong amount to the destination, then
// we'll exit early. // we'll exit early.
case *lnwire.FailIncorrectPaymentAmount: case *lnwire.FailIncorrectPaymentAmount:
return true return true, channeldb.FailureReasonIncorrectPaymentDetails
// If the time-lock that was extended to the final node // If the time-lock that was extended to the final node
// was incorrect, then we can't proceed. // was incorrect, then we can't proceed.
case *lnwire.FailFinalIncorrectCltvExpiry: case *lnwire.FailFinalIncorrectCltvExpiry:
return true // TODO(joostjager): Take into account that second last hop may
// have deliberately handed out an htlc that expires too soon.
// In that case we should continue routing.
return true, channeldb.FailureReasonError
// If we crafted an invalid onion payload for the final // If we crafted an invalid onion payload for the final
// node, then we'll exit early. // node, then we'll exit early.
case *lnwire.FailFinalIncorrectHtlcAmount: case *lnwire.FailFinalIncorrectHtlcAmount:
return true // TODO(joostjager): Take into account that second last hop may
// have deliberately handed out an htlc with a too low value. In
// that case we should continue routing.
return true, channeldb.FailureReasonError
// Similarly, if the HTLC expiry that we extended to // Similarly, if the HTLC expiry that we extended to
// the final hop expires too soon, then will fail the // the final hop expires too soon, then will fail the
@ -1892,12 +1904,15 @@ func (r *ChannelRouter) processSendError(paySession PaymentSession,
// TODO(roasbeef): can happen to to race condition, try // TODO(roasbeef): can happen to to race condition, try
// again with recent block height // again with recent block height
case *lnwire.FailFinalExpiryTooSoon: case *lnwire.FailFinalExpiryTooSoon:
return true // TODO(joostjager): Take into account that any hop may have
// delayed. Ideally we should continue routing. Knowing the
// delaying node at this point would help.
return true, channeldb.FailureReasonIncorrectPaymentDetails
// If we erroneously attempted to cross a chain border, // If we erroneously attempted to cross a chain border,
// then we'll cancel the payment. // then we'll cancel the payment.
case *lnwire.FailInvalidRealm: case *lnwire.FailInvalidRealm:
return true return true, channeldb.FailureReasonError
// If we get a notice that the expiry was too soon for // If we get a notice that the expiry was too soon for
// an intermediate node, then we'll prune out the node // an intermediate node, then we'll prune out the node
@ -1906,17 +1921,20 @@ func (r *ChannelRouter) processSendError(paySession PaymentSession,
case *lnwire.FailExpiryTooSoon: case *lnwire.FailExpiryTooSoon:
r.applyChannelUpdate(&onionErr.Update, errSource) r.applyChannelUpdate(&onionErr.Update, errSource)
paySession.ReportVertexFailure(errVertex) paySession.ReportVertexFailure(errVertex)
return false return false, 0
// If we hit an instance of onion payload corruption or // If we hit an instance of onion payload corruption or an invalid
// an invalid version, then we'll exit early as this // version, then we'll exit early as this shouldn't happen in the
// shouldn't happen in the typical case. // typical case.
//
// TODO(joostjager): Take into account that the previous hop may have
// tampered with the onion. Routing should continue using other paths.
case *lnwire.FailInvalidOnionVersion: case *lnwire.FailInvalidOnionVersion:
return true return true, channeldb.FailureReasonError
case *lnwire.FailInvalidOnionHmac: case *lnwire.FailInvalidOnionHmac:
return true return true, channeldb.FailureReasonError
case *lnwire.FailInvalidOnionKey: case *lnwire.FailInvalidOnionKey:
return true return true, channeldb.FailureReasonError
// If we get a failure due to violating the minimum // If we get a failure due to violating the minimum
// amount, we'll apply the new minimum amount and retry // amount, we'll apply the new minimum amount and retry
@ -1925,7 +1943,7 @@ func (r *ChannelRouter) processSendError(paySession PaymentSession,
processChannelUpdateAndRetry( processChannelUpdateAndRetry(
&onionErr.Update, errSource, &onionErr.Update, errSource,
) )
return false return false, 0
// If we get a failure due to a fee, we'll apply the // If we get a failure due to a fee, we'll apply the
// new fee update, and retry our attempt using the // new fee update, and retry our attempt using the
@ -1934,7 +1952,7 @@ func (r *ChannelRouter) processSendError(paySession PaymentSession,
processChannelUpdateAndRetry( processChannelUpdateAndRetry(
&onionErr.Update, errSource, &onionErr.Update, errSource,
) )
return false return false, 0
// If we get the failure for an intermediate node that // If we get the failure for an intermediate node that
// disagrees with our time lock values, then we'll // disagrees with our time lock values, then we'll
@ -1943,7 +1961,7 @@ func (r *ChannelRouter) processSendError(paySession PaymentSession,
processChannelUpdateAndRetry( processChannelUpdateAndRetry(
&onionErr.Update, errSource, &onionErr.Update, errSource,
) )
return false return false, 0
// The outgoing channel that this node was meant to // The outgoing channel that this node was meant to
// forward one is currently disabled, so we'll apply // forward one is currently disabled, so we'll apply
@ -1951,7 +1969,7 @@ func (r *ChannelRouter) processSendError(paySession PaymentSession,
case *lnwire.FailChannelDisabled: case *lnwire.FailChannelDisabled:
r.applyChannelUpdate(&onionErr.Update, errSource) r.applyChannelUpdate(&onionErr.Update, errSource)
paySession.ReportEdgeFailure(failedEdge, 0) paySession.ReportEdgeFailure(failedEdge, 0)
return false return false, 0
// It's likely that the outgoing channel didn't have // It's likely that the outgoing channel didn't have
// sufficient capacity, so we'll prune this edge for // sufficient capacity, so we'll prune this edge for
@ -1959,21 +1977,21 @@ func (r *ChannelRouter) processSendError(paySession PaymentSession,
case *lnwire.FailTemporaryChannelFailure: case *lnwire.FailTemporaryChannelFailure:
r.applyChannelUpdate(onionErr.Update, errSource) r.applyChannelUpdate(onionErr.Update, errSource)
paySession.ReportEdgeFailure(failedEdge, failedAmt) paySession.ReportEdgeFailure(failedEdge, failedAmt)
return false return false, 0
// If the send fail due to a node not having the // If the send fail due to a node not having the
// required features, then we'll note this error and // required features, then we'll note this error and
// continue. // continue.
case *lnwire.FailRequiredNodeFeatureMissing: case *lnwire.FailRequiredNodeFeatureMissing:
paySession.ReportVertexFailure(errVertex) paySession.ReportVertexFailure(errVertex)
return false return false, 0
// If the send fail due to a node not having the // If the send fail due to a node not having the
// required features, then we'll note this error and // required features, then we'll note this error and
// continue. // continue.
case *lnwire.FailRequiredChannelFeatureMissing: case *lnwire.FailRequiredChannelFeatureMissing:
paySession.ReportVertexFailure(errVertex) paySession.ReportVertexFailure(errVertex)
return false return false, 0
// If the next hop in the route wasn't known or // If the next hop in the route wasn't known or
// offline, we'll only the channel which we attempted // offline, we'll only the channel which we attempted
@ -1984,18 +2002,18 @@ func (r *ChannelRouter) processSendError(paySession PaymentSession,
// another node. // another node.
case *lnwire.FailUnknownNextPeer: case *lnwire.FailUnknownNextPeer:
paySession.ReportEdgeFailure(failedEdge, 0) paySession.ReportEdgeFailure(failedEdge, 0)
return false return false, 0
// If the node wasn't able to forward for which ever // If the node wasn't able to forward for which ever
// reason, then we'll note this and continue with the // reason, then we'll note this and continue with the
// routes. // routes.
case *lnwire.FailTemporaryNodeFailure: case *lnwire.FailTemporaryNodeFailure:
paySession.ReportVertexFailure(errVertex) paySession.ReportVertexFailure(errVertex)
return false return false, 0
case *lnwire.FailPermanentNodeFailure: case *lnwire.FailPermanentNodeFailure:
paySession.ReportVertexFailure(errVertex) paySession.ReportVertexFailure(errVertex)
return false return false, 0
// If we crafted a route that contains a too long time // If we crafted a route that contains a too long time
// lock for an intermediate node, we'll prune the node. // lock for an intermediate node, we'll prune the node.
@ -2008,7 +2026,7 @@ func (r *ChannelRouter) processSendError(paySession PaymentSession,
// that node. // that node.
case *lnwire.FailExpiryTooFar: case *lnwire.FailExpiryTooFar:
paySession.ReportVertexFailure(errVertex) paySession.ReportVertexFailure(errVertex)
return false return false, 0
// If we get a permanent channel or node failure, then // If we get a permanent channel or node failure, then
// we'll prune the channel in both directions and // we'll prune the channel in both directions and
@ -2020,10 +2038,10 @@ func (r *ChannelRouter) processSendError(paySession PaymentSession,
to: failedEdge.from, to: failedEdge.from,
channel: failedEdge.channel, channel: failedEdge.channel,
}, 0) }, 0)
return false return false, 0
default: default:
return true return true, channeldb.FailureReasonError
} }
} }