diff --git a/cmd/lncli/cmd_query_mission_control.go b/cmd/lncli/cmd_query_mission_control.go index f1514beb098..3ccbd42bf13 100644 --- a/cmd/lncli/cmd_query_mission_control.go +++ b/cmd/lncli/cmd_query_mission_control.go @@ -35,11 +35,19 @@ func queryMissionControl(ctx *cli.Context) error { Pubkey string LastFailTime int64 OtherChanSuccessProb float32 - Channels []*routerrpc.ChannelHistory + } + + type displayPairHistory struct { + NodeA, NodeB string + Timestamp int64 + SuccessProb float32 + Amt int64 + Result string } displayResp := struct { Nodes []displayNodeHistory + Pairs []displayPairHistory }{} for _, n := range snapshot.Nodes { @@ -49,7 +57,20 @@ func queryMissionControl(ctx *cli.Context) error { Pubkey: hex.EncodeToString(n.Pubkey), LastFailTime: n.LastFailTime, OtherChanSuccessProb: n.OtherChanSuccessProb, - Channels: n.Channels, + }, + ) + } + + for _, n := range snapshot.Pairs { + displayResp.Pairs = append( + displayResp.Pairs, + displayPairHistory{ + NodeA: hex.EncodeToString(n.NodeA), + NodeB: hex.EncodeToString(n.NodeB), + Timestamp: n.Timestamp, + SuccessProb: n.SuccessProb, + Amt: n.Amt, + Result: n.Result.String(), }, ) } diff --git a/lnrpc/routerrpc/router.pb.go b/lnrpc/routerrpc/router.pb.go index cba7914d7f1..ed728d624fb 100644 --- a/lnrpc/routerrpc/router.pb.go +++ b/lnrpc/routerrpc/router.pb.go @@ -74,6 +74,34 @@ func (PaymentState) EnumDescriptor() ([]byte, []int) { return fileDescriptor_7a0613f69d37b0a5, []int{0} } +type PairResult int32 + +const ( + PairResult_SUCCESS PairResult = 0 + PairResult_FAIL PairResult = 1 + PairResult_FAIL_BALANCE PairResult = 2 +) + +var PairResult_name = map[int32]string{ + 0: "SUCCESS", + 1: "FAIL", + 2: "FAIL_BALANCE", +} + +var PairResult_value = map[string]int32{ + "SUCCESS": 0, + "FAIL": 1, + "FAIL_BALANCE": 2, +} + +func (x PairResult) String() string { + return proto.EnumName(PairResult_name, int32(x)) +} + +func (PairResult) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_7a0613f69d37b0a5, []int{1} +} + type Failure_FailureCode int32 const ( @@ -982,6 +1010,7 @@ var xxx_messageInfo_QueryMissionControlRequest proto.InternalMessageInfo /// QueryMissionControlResponse contains mission control state per node. type QueryMissionControlResponse struct { Nodes []*NodeHistory `protobuf:"bytes,1,rep,name=nodes,proto3" json:"nodes,omitempty"` + Pairs []*PairHistory `protobuf:"bytes,2,rep,name=pairs,proto3" json:"pairs,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` @@ -1019,6 +1048,13 @@ func (m *QueryMissionControlResponse) GetNodes() []*NodeHistory { return nil } +func (m *QueryMissionControlResponse) GetPairs() []*PairHistory { + if m != nil { + return m.Pairs + } + return nil +} + /// NodeHistory contains the mission control state for a particular node. type NodeHistory struct { /// Node pubkey @@ -1026,12 +1062,10 @@ type NodeHistory struct { /// Time stamp of last failure. Set to zero if no failure happened yet. LastFailTime int64 `protobuf:"varint,2,opt,name=last_fail_time,proto3" json:"last_fail_time,omitempty"` /// Estimation of success probability for channels not in the channel array. - OtherChanSuccessProb float32 `protobuf:"fixed32,3,opt,name=other_chan_success_prob,proto3" json:"other_chan_success_prob,omitempty"` - /// Historical information of particular channels. - Channels []*ChannelHistory `protobuf:"bytes,4,rep,name=channels,proto3" json:"channels,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + OtherChanSuccessProb float32 `protobuf:"fixed32,3,opt,name=other_chan_success_prob,proto3" json:"other_chan_success_prob,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } func (m *NodeHistory) Reset() { *m = NodeHistory{} } @@ -1080,83 +1114,92 @@ func (m *NodeHistory) GetOtherChanSuccessProb() float32 { return 0 } -func (m *NodeHistory) GetChannels() []*ChannelHistory { - if m != nil { - return m.Channels - } - return nil -} - /// NodeHistory contains the mission control state for a particular channel. -type ChannelHistory struct { - /// Short channel id - ChannelId uint64 `protobuf:"varint,1,opt,name=channel_id,proto3" json:"channel_id,omitempty"` +type PairHistory struct { + NodeA []byte `protobuf:"bytes,1,opt,name=node_a,json=nodeA,proto3" json:"node_a,omitempty"` + NodeB []byte `protobuf:"bytes,2,opt,name=node_b,json=nodeB,proto3" json:"node_b,omitempty"` /// Time stamp of last failure. - LastFailTime int64 `protobuf:"varint,2,opt,name=last_fail_time,proto3" json:"last_fail_time,omitempty"` + Timestamp int64 `protobuf:"varint,3,opt,name=timestamp,proto3" json:"timestamp,omitempty"` /// Minimum penalization amount. - MinPenalizeAmtSat int64 `protobuf:"varint,3,opt,name=min_penalize_amt_sat,proto3" json:"min_penalize_amt_sat,omitempty"` + Amt int64 `protobuf:"varint,4,opt,name=amt,proto3" json:"amt,omitempty"` /// Estimation of success probability for this channel. - SuccessProb float32 `protobuf:"fixed32,4,opt,name=success_prob,proto3" json:"success_prob,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + SuccessProb float32 `protobuf:"fixed32,5,opt,name=success_prob,proto3" json:"success_prob,omitempty"` + Result PairResult `protobuf:"varint,6,opt,name=result,proto3,enum=routerrpc.PairResult" json:"result,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (m *ChannelHistory) Reset() { *m = ChannelHistory{} } -func (m *ChannelHistory) String() string { return proto.CompactTextString(m) } -func (*ChannelHistory) ProtoMessage() {} -func (*ChannelHistory) Descriptor() ([]byte, []int) { +func (m *PairHistory) Reset() { *m = PairHistory{} } +func (m *PairHistory) String() string { return proto.CompactTextString(m) } +func (*PairHistory) ProtoMessage() {} +func (*PairHistory) Descriptor() ([]byte, []int) { return fileDescriptor_7a0613f69d37b0a5, []int{14} } -func (m *ChannelHistory) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_ChannelHistory.Unmarshal(m, b) +func (m *PairHistory) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_PairHistory.Unmarshal(m, b) } -func (m *ChannelHistory) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_ChannelHistory.Marshal(b, m, deterministic) +func (m *PairHistory) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_PairHistory.Marshal(b, m, deterministic) } -func (m *ChannelHistory) XXX_Merge(src proto.Message) { - xxx_messageInfo_ChannelHistory.Merge(m, src) +func (m *PairHistory) XXX_Merge(src proto.Message) { + xxx_messageInfo_PairHistory.Merge(m, src) } -func (m *ChannelHistory) XXX_Size() int { - return xxx_messageInfo_ChannelHistory.Size(m) +func (m *PairHistory) XXX_Size() int { + return xxx_messageInfo_PairHistory.Size(m) } -func (m *ChannelHistory) XXX_DiscardUnknown() { - xxx_messageInfo_ChannelHistory.DiscardUnknown(m) +func (m *PairHistory) XXX_DiscardUnknown() { + xxx_messageInfo_PairHistory.DiscardUnknown(m) } -var xxx_messageInfo_ChannelHistory proto.InternalMessageInfo +var xxx_messageInfo_PairHistory proto.InternalMessageInfo -func (m *ChannelHistory) GetChannelId() uint64 { +func (m *PairHistory) GetNodeA() []byte { if m != nil { - return m.ChannelId + return m.NodeA } - return 0 + return nil } -func (m *ChannelHistory) GetLastFailTime() int64 { +func (m *PairHistory) GetNodeB() []byte { if m != nil { - return m.LastFailTime + return m.NodeB + } + return nil +} + +func (m *PairHistory) GetTimestamp() int64 { + if m != nil { + return m.Timestamp } return 0 } -func (m *ChannelHistory) GetMinPenalizeAmtSat() int64 { +func (m *PairHistory) GetAmt() int64 { if m != nil { - return m.MinPenalizeAmtSat + return m.Amt } return 0 } -func (m *ChannelHistory) GetSuccessProb() float32 { +func (m *PairHistory) GetSuccessProb() float32 { if m != nil { return m.SuccessProb } return 0 } +func (m *PairHistory) GetResult() PairResult { + if m != nil { + return m.Result + } + return PairResult_SUCCESS +} + func init() { proto.RegisterEnum("routerrpc.PaymentState", PaymentState_name, PaymentState_value) + proto.RegisterEnum("routerrpc.PairResult", PairResult_name, PairResult_value) proto.RegisterEnum("routerrpc.Failure_FailureCode", Failure_FailureCode_name, Failure_FailureCode_value) proto.RegisterType((*SendPaymentRequest)(nil), "routerrpc.SendPaymentRequest") proto.RegisterType((*TrackPaymentRequest)(nil), "routerrpc.TrackPaymentRequest") @@ -1172,117 +1215,120 @@ func init() { proto.RegisterType((*QueryMissionControlRequest)(nil), "routerrpc.QueryMissionControlRequest") proto.RegisterType((*QueryMissionControlResponse)(nil), "routerrpc.QueryMissionControlResponse") proto.RegisterType((*NodeHistory)(nil), "routerrpc.NodeHistory") - proto.RegisterType((*ChannelHistory)(nil), "routerrpc.ChannelHistory") + proto.RegisterType((*PairHistory)(nil), "routerrpc.PairHistory") } func init() { proto.RegisterFile("routerrpc/router.proto", fileDescriptor_7a0613f69d37b0a5) } var fileDescriptor_7a0613f69d37b0a5 = []byte{ - // 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, + // 1702 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x57, 0x4f, 0x77, 0x22, 0xc7, + 0x11, 0x5f, 0x04, 0x08, 0x51, 0xfc, 0xd1, 0xa8, 0xf5, 0x8f, 0x95, 0x56, 0xb6, 0x8c, 0x93, 0xb5, + 0xde, 0x3e, 0x5b, 0x72, 0xc8, 0x5b, 0x3f, 0x9f, 0x92, 0x37, 0x82, 0xc6, 0x8c, 0x17, 0x66, 0xe4, + 0x1e, 0x58, 0x7b, 0x93, 0x43, 0xbf, 0x16, 0xb4, 0x60, 0x9e, 0x60, 0x06, 0xcf, 0x34, 0x9b, 0xd5, + 0x25, 0xb7, 0xbc, 0xe4, 0x92, 0xcf, 0x92, 0x73, 0x2e, 0xf9, 0x38, 0xc9, 0x77, 0xc8, 0x29, 0xaf, + 0xbb, 0x07, 0x18, 0x10, 0xda, 0xf8, 0xc4, 0xf4, 0xaf, 0x7e, 0x5d, 0x55, 0x5d, 0xd5, 0x55, 0x5d, + 0xc0, 0x51, 0x18, 0xcc, 0x04, 0x0f, 0xc3, 0x69, 0xff, 0x4a, 0x7f, 0x5d, 0x4e, 0xc3, 0x40, 0x04, + 0x28, 0xbf, 0xc0, 0x4f, 0xf2, 0xe1, 0xb4, 0xaf, 0xd1, 0xea, 0x7f, 0xb7, 0x00, 0xb9, 0xdc, 0x1f, + 0xdc, 0xb0, 0x87, 0x09, 0xf7, 0x05, 0xe1, 0x3f, 0xcf, 0x78, 0x24, 0x10, 0x82, 0xcc, 0x80, 0x47, + 0xa2, 0x92, 0x3a, 0x4f, 0x5d, 0x14, 0x89, 0xfa, 0x46, 0x06, 0xa4, 0xd9, 0x44, 0x54, 0xb6, 0xce, + 0x53, 0x17, 0x69, 0x22, 0x3f, 0xd1, 0x67, 0x50, 0x9c, 0xea, 0x7d, 0x74, 0xc4, 0xa2, 0x51, 0x25, + 0xad, 0xd8, 0x85, 0x18, 0x6b, 0xb1, 0x68, 0x84, 0x2e, 0xc0, 0xb8, 0xf3, 0x7c, 0x36, 0xa6, 0xfd, + 0xb1, 0x78, 0x4f, 0x07, 0x7c, 0x2c, 0x58, 0x25, 0x73, 0x9e, 0xba, 0xc8, 0x92, 0xb2, 0xc2, 0xeb, + 0x63, 0xf1, 0xbe, 0x21, 0x51, 0xf4, 0x05, 0xec, 0xce, 0x95, 0x85, 0xda, 0x8b, 0x4a, 0xf6, 0x3c, + 0x75, 0x91, 0x27, 0xe5, 0xe9, 0xaa, 0x6f, 0x5f, 0xc0, 0xae, 0xf0, 0x26, 0x3c, 0x98, 0x09, 0x1a, + 0xf1, 0x7e, 0xe0, 0x0f, 0xa2, 0xca, 0xb6, 0xd6, 0x18, 0xc3, 0xae, 0x46, 0x51, 0x15, 0x4a, 0x77, + 0x9c, 0xd3, 0xb1, 0x37, 0xf1, 0x04, 0x8d, 0x98, 0xa8, 0xe4, 0x94, 0xeb, 0x85, 0x3b, 0xce, 0xdb, + 0x12, 0x73, 0x99, 0x90, 0xfe, 0x05, 0x33, 0x31, 0x0c, 0x3c, 0x7f, 0x48, 0xfb, 0x23, 0xe6, 0x53, + 0x6f, 0x50, 0xd9, 0x39, 0x4f, 0x5d, 0x64, 0x48, 0x79, 0x8e, 0xd7, 0x47, 0xcc, 0xb7, 0x06, 0xe8, + 0x0c, 0x40, 0x9d, 0x41, 0xa9, 0xab, 0xe4, 0x95, 0xc5, 0xbc, 0x44, 0x94, 0x2e, 0x54, 0x83, 0x82, + 0x0a, 0x30, 0x1d, 0x79, 0xbe, 0x88, 0x2a, 0x70, 0x9e, 0xbe, 0x28, 0xd4, 0x8c, 0xcb, 0xb1, 0x2f, + 0x63, 0x4d, 0xa4, 0xa4, 0xe5, 0xf9, 0x82, 0x24, 0x49, 0xd5, 0x6f, 0x61, 0xbf, 0x1b, 0xb2, 0xfe, + 0xfd, 0x5a, 0xf0, 0xd7, 0xc3, 0x9a, 0x7a, 0x14, 0xd6, 0xea, 0x9f, 0xa1, 0x14, 0x6f, 0x72, 0x05, + 0x13, 0xb3, 0x08, 0x7d, 0x05, 0xd9, 0x48, 0x30, 0xc1, 0x15, 0xb9, 0x5c, 0x3b, 0xbe, 0x5c, 0x64, + 0xfb, 0x32, 0x41, 0xe4, 0x44, 0xb3, 0xd0, 0x09, 0xec, 0x4c, 0x43, 0xee, 0x4d, 0xd8, 0x90, 0xab, + 0x84, 0x16, 0xc9, 0x62, 0x8d, 0xaa, 0x90, 0x55, 0x9b, 0x55, 0x3a, 0x0b, 0xb5, 0x62, 0xf2, 0x0c, + 0x44, 0x8b, 0xaa, 0xbf, 0x83, 0x5d, 0xb5, 0x6e, 0x72, 0xfe, 0xb1, 0x2b, 0x73, 0x0c, 0x39, 0x36, + 0xd1, 0xb1, 0xd7, 0xd7, 0x66, 0x9b, 0x4d, 0x64, 0xd8, 0xab, 0x03, 0x30, 0x96, 0xfb, 0xa3, 0x69, + 0xe0, 0x47, 0x5c, 0xa6, 0x42, 0x2a, 0x97, 0x99, 0x90, 0x69, 0x9b, 0xc8, 0x5d, 0x29, 0xb5, 0xab, + 0x1c, 0xe3, 0x4d, 0xce, 0x3b, 0x11, 0x13, 0xe8, 0xa5, 0xbe, 0x01, 0x74, 0x1c, 0xf4, 0xef, 0xe5, + 0x9d, 0x62, 0x0f, 0xb1, 0xfa, 0x92, 0x84, 0xdb, 0x41, 0xff, 0xbe, 0x21, 0xc1, 0xea, 0x1f, 0xf5, + 0xdd, 0xee, 0x06, 0xda, 0xf7, 0x5f, 0x1c, 0xde, 0x65, 0x08, 0xb6, 0x9e, 0x0e, 0x01, 0x85, 0xfd, + 0x15, 0xe5, 0xf1, 0x29, 0x92, 0x91, 0x4d, 0xad, 0x45, 0xf6, 0x4b, 0xc8, 0xdd, 0x31, 0x6f, 0x3c, + 0x0b, 0xe7, 0x8a, 0x51, 0x22, 0x4d, 0x4d, 0x2d, 0x21, 0x73, 0x4a, 0xf5, 0x9f, 0x39, 0xc8, 0xc5, + 0x20, 0xaa, 0x41, 0xa6, 0x1f, 0x0c, 0xe6, 0xd9, 0xfd, 0xe4, 0xf1, 0xb6, 0xf9, 0x6f, 0x3d, 0x18, + 0x70, 0xa2, 0xb8, 0xe8, 0xf7, 0x50, 0x96, 0x37, 0xda, 0xe7, 0x63, 0x3a, 0x9b, 0x0e, 0xd8, 0x22, + 0xa1, 0x95, 0xc4, 0xee, 0xba, 0x26, 0xf4, 0x94, 0x9c, 0x94, 0xfa, 0xc9, 0x25, 0x3a, 0x85, 0xfc, + 0x48, 0x8c, 0xfb, 0x3a, 0x13, 0x19, 0x55, 0x14, 0x3b, 0x12, 0x50, 0x39, 0xa8, 0x42, 0x29, 0xf0, + 0xbd, 0xc0, 0xa7, 0xd1, 0x88, 0xd1, 0xda, 0xeb, 0x6f, 0x54, 0xb1, 0x16, 0x49, 0x41, 0x81, 0xee, + 0x88, 0xd5, 0x5e, 0x7f, 0x83, 0x3e, 0x85, 0x82, 0x2a, 0x19, 0xfe, 0x61, 0xea, 0x85, 0x0f, 0xaa, + 0x4a, 0x4b, 0x44, 0x55, 0x11, 0x56, 0x08, 0x3a, 0x80, 0xec, 0xdd, 0x98, 0x0d, 0x23, 0x55, 0x99, + 0x25, 0xa2, 0x17, 0xe8, 0x6b, 0x38, 0x88, 0x63, 0x40, 0xa3, 0x60, 0x16, 0xf6, 0x39, 0xf5, 0xfc, + 0x01, 0xff, 0xa0, 0xea, 0xb2, 0x44, 0x50, 0x2c, 0x73, 0x95, 0xc8, 0x92, 0x92, 0xea, 0xdf, 0xb2, + 0x50, 0x48, 0x04, 0x00, 0x15, 0x61, 0x87, 0x60, 0x17, 0x93, 0xb7, 0xb8, 0x61, 0x3c, 0x43, 0x15, + 0x38, 0xe8, 0xd9, 0x6f, 0x6c, 0xe7, 0x47, 0x9b, 0xde, 0x98, 0xef, 0x3a, 0xd8, 0xee, 0xd2, 0x96, + 0xe9, 0xb6, 0x8c, 0x14, 0x7a, 0x01, 0x15, 0xcb, 0xae, 0x3b, 0x84, 0xe0, 0x7a, 0x77, 0x21, 0x33, + 0x3b, 0x4e, 0xcf, 0xee, 0x1a, 0x5b, 0xe8, 0x53, 0x38, 0x6d, 0x5a, 0xb6, 0xd9, 0xa6, 0x4b, 0x4e, + 0xbd, 0xdd, 0x7d, 0x4b, 0xf1, 0x4f, 0x37, 0x16, 0x79, 0x67, 0xa4, 0x37, 0x11, 0x5a, 0xdd, 0x76, + 0x7d, 0xae, 0x21, 0x83, 0x9e, 0xc3, 0xa1, 0x26, 0xe8, 0x2d, 0xb4, 0xeb, 0x38, 0xd4, 0x75, 0x1c, + 0xdb, 0xc8, 0xa2, 0x3d, 0x28, 0x59, 0xf6, 0x5b, 0xb3, 0x6d, 0x35, 0x28, 0xc1, 0x66, 0xbb, 0x63, + 0x6c, 0xa3, 0x7d, 0xd8, 0x5d, 0xe7, 0xe5, 0xa4, 0x8a, 0x39, 0xcf, 0xb1, 0x2d, 0xc7, 0xa6, 0x6f, + 0x31, 0x71, 0x2d, 0xc7, 0x36, 0x76, 0xd0, 0x11, 0xa0, 0x55, 0x51, 0xab, 0x63, 0xd6, 0x8d, 0x3c, + 0x3a, 0x84, 0xbd, 0x55, 0xfc, 0x0d, 0x7e, 0x67, 0x80, 0x0c, 0x83, 0x76, 0x8c, 0x5e, 0xe3, 0xb6, + 0xf3, 0x23, 0xed, 0x58, 0xb6, 0xd5, 0xe9, 0x75, 0x8c, 0x02, 0x3a, 0x00, 0xa3, 0x89, 0x31, 0xb5, + 0x6c, 0xb7, 0xd7, 0x6c, 0x5a, 0x75, 0x0b, 0xdb, 0x5d, 0xa3, 0xa8, 0x2d, 0x6f, 0x3a, 0x78, 0x49, + 0x6e, 0xa8, 0xb7, 0x4c, 0xdb, 0xc6, 0x6d, 0xda, 0xb0, 0x5c, 0xf3, 0xba, 0x8d, 0x1b, 0x46, 0x19, + 0x9d, 0xc1, 0xf3, 0x2e, 0xee, 0xdc, 0x38, 0xc4, 0x24, 0xef, 0xe8, 0x5c, 0xde, 0x34, 0xad, 0x76, + 0x8f, 0x60, 0x63, 0x17, 0x7d, 0x06, 0x67, 0x04, 0xff, 0xd0, 0xb3, 0x08, 0x6e, 0x50, 0xdb, 0x69, + 0x60, 0xda, 0xc4, 0x66, 0xb7, 0x47, 0x30, 0xed, 0x58, 0xae, 0x6b, 0xd9, 0xdf, 0x19, 0x06, 0xfa, + 0x15, 0x9c, 0x2f, 0x28, 0x0b, 0x05, 0x6b, 0xac, 0x3d, 0x79, 0xbe, 0x79, 0x3e, 0x6d, 0xfc, 0x53, + 0x97, 0xde, 0x60, 0x4c, 0x0c, 0x84, 0x4e, 0xe0, 0x68, 0x69, 0x5e, 0x1b, 0x88, 0x6d, 0xef, 0x4b, + 0xd9, 0x0d, 0x26, 0x1d, 0xd3, 0x96, 0x09, 0x5e, 0x91, 0x1d, 0x48, 0xb7, 0x97, 0xb2, 0x75, 0xb7, + 0x0f, 0xd1, 0x01, 0xec, 0xce, 0xad, 0xcd, 0xc1, 0x7f, 0xe7, 0xd0, 0x31, 0xa0, 0x9e, 0x4d, 0xb0, + 0xd9, 0x90, 0x87, 0x5f, 0x08, 0xfe, 0x93, 0xfb, 0x3e, 0xb3, 0xb3, 0x65, 0xa4, 0xab, 0xff, 0x48, + 0x43, 0x69, 0xa5, 0xb6, 0xd0, 0x0b, 0xc8, 0x47, 0xde, 0xd0, 0x67, 0x42, 0x56, 0xbf, 0x6e, 0x0c, + 0x4b, 0x40, 0x3d, 0x2e, 0x23, 0xe6, 0xf9, 0xba, 0x23, 0xe9, 0x8e, 0x9c, 0x57, 0x88, 0xea, 0x47, + 0xc7, 0x90, 0x9b, 0x3f, 0x4e, 0x69, 0x55, 0x87, 0xdb, 0x7d, 0xfd, 0x28, 0xbd, 0x80, 0xbc, 0x6c, + 0x79, 0x91, 0x60, 0x93, 0xa9, 0x2a, 0xd1, 0x12, 0x59, 0x02, 0xe8, 0x73, 0x28, 0x4d, 0x78, 0x14, + 0xb1, 0x21, 0xa7, 0xba, 0xcc, 0x40, 0x31, 0x8a, 0x31, 0xd8, 0x54, 0xd5, 0xf6, 0x39, 0xcc, 0xcb, + 0x3e, 0x26, 0x65, 0x35, 0x29, 0x06, 0x35, 0x69, 0xbd, 0xe3, 0x0a, 0x16, 0x57, 0x73, 0xb2, 0xe3, + 0x0a, 0x86, 0x5e, 0xc1, 0x9e, 0x6e, 0x19, 0x9e, 0xef, 0x4d, 0x66, 0x13, 0xdd, 0x3a, 0x72, 0xca, + 0xe5, 0x5d, 0xd5, 0x3a, 0x34, 0xae, 0x3a, 0xc8, 0x73, 0xd8, 0xb9, 0x65, 0x11, 0x97, 0xcd, 0x3e, + 0x2e, 0xed, 0x9c, 0x5c, 0x37, 0x39, 0x97, 0x22, 0xf9, 0x04, 0x84, 0xb2, 0x69, 0xe5, 0xb5, 0xe8, + 0x8e, 0x73, 0x22, 0xe3, 0xb8, 0xb0, 0xc0, 0x3e, 0x2c, 0x2d, 0x14, 0x12, 0x16, 0x34, 0xae, 0x2c, + 0xbc, 0x82, 0x3d, 0xfe, 0x41, 0x84, 0x8c, 0x06, 0x53, 0xf6, 0xf3, 0x8c, 0xd3, 0x01, 0x13, 0xac, + 0x52, 0x54, 0xc1, 0xdd, 0x55, 0x02, 0x47, 0xe1, 0x0d, 0x26, 0x58, 0xf5, 0x05, 0x9c, 0x10, 0x1e, + 0x71, 0xd1, 0xf1, 0xa2, 0xc8, 0x0b, 0xfc, 0x7a, 0xe0, 0x8b, 0x30, 0x18, 0xc7, 0x6f, 0x46, 0xf5, + 0x0c, 0x4e, 0x37, 0x4a, 0x75, 0xd3, 0x97, 0x9b, 0x7f, 0x98, 0xf1, 0xf0, 0x61, 0xf3, 0xe6, 0x07, + 0x38, 0xdd, 0x28, 0x8d, 0x5f, 0x8c, 0x2f, 0x21, 0xeb, 0x07, 0x03, 0x1e, 0x55, 0x52, 0x6a, 0x66, + 0x38, 0x4a, 0xb4, 0x67, 0x3b, 0x18, 0xf0, 0x96, 0x17, 0x89, 0x20, 0x7c, 0x20, 0x9a, 0x24, 0xd9, + 0x53, 0xe6, 0x85, 0x51, 0x65, 0xeb, 0x11, 0xfb, 0x86, 0x79, 0xe1, 0x82, 0xad, 0x48, 0xd5, 0xbf, + 0xa6, 0xa0, 0x90, 0x50, 0x82, 0x8e, 0x60, 0x7b, 0x3a, 0xbb, 0xbd, 0xe7, 0x0f, 0xf1, 0x15, 0x8c, + 0x57, 0xe8, 0x25, 0x94, 0xc7, 0x2c, 0x12, 0x54, 0xf6, 0x56, 0x2a, 0x53, 0x1a, 0x3f, 0xa8, 0x6b, + 0x28, 0xfa, 0x16, 0x8e, 0x03, 0x31, 0xe2, 0xa1, 0x9e, 0x95, 0xa2, 0x59, 0xbf, 0xcf, 0xa3, 0x88, + 0x4e, 0xc3, 0xe0, 0x56, 0x5d, 0xcc, 0x2d, 0xf2, 0x94, 0xb8, 0xfa, 0xaf, 0x14, 0x14, 0x12, 0x0e, + 0xa2, 0x43, 0xd8, 0x96, 0x07, 0xa2, 0x2c, 0xf6, 0x44, 0x1d, 0xcf, 0x5c, 0xc0, 0xb7, 0x71, 0x11, + 0x28, 0xf8, 0x7a, 0xf5, 0x9e, 0xa7, 0x95, 0x6b, 0x89, 0x7b, 0x1e, 0x4f, 0xa6, 0x99, 0xe5, 0x64, + 0x5a, 0x85, 0xe2, 0x8a, 0x73, 0x59, 0xe5, 0xdc, 0x0a, 0x86, 0xbe, 0x82, 0xed, 0x90, 0x47, 0xb3, + 0xb1, 0x50, 0x57, 0xb9, 0x5c, 0x3b, 0x5c, 0x0b, 0x25, 0x51, 0x42, 0x12, 0x93, 0x5e, 0xfd, 0x3d, + 0x05, 0xc5, 0xe4, 0x28, 0x85, 0x4a, 0x90, 0xb7, 0x6c, 0xda, 0x6c, 0x5b, 0xdf, 0xb5, 0xba, 0xc6, + 0x33, 0xb9, 0x74, 0x7b, 0xf5, 0x3a, 0xc6, 0x0d, 0xdc, 0x30, 0x52, 0x08, 0x41, 0x59, 0x76, 0x05, + 0xdc, 0xa0, 0x5d, 0xab, 0x83, 0x9d, 0x9e, 0x7c, 0x50, 0xf6, 0x61, 0x37, 0xc6, 0x6c, 0x87, 0x12, + 0xa7, 0xd7, 0xc5, 0x46, 0x1a, 0x19, 0x50, 0x8c, 0x41, 0x4c, 0x88, 0x43, 0x8c, 0x8c, 0xec, 0x82, + 0x31, 0xf2, 0xf8, 0x71, 0x6a, 0xe0, 0xae, 0x69, 0xb5, 0x5d, 0x23, 0xfb, 0xea, 0x35, 0xc0, 0xd2, + 0x4b, 0x54, 0x80, 0x9c, 0xb2, 0xee, 0xba, 0xc6, 0x33, 0xb4, 0x03, 0x19, 0xa9, 0xc0, 0x48, 0xcd, + 0x95, 0xd3, 0x6b, 0xb3, 0x6d, 0xda, 0x75, 0x6c, 0x6c, 0xd5, 0xfe, 0x92, 0x81, 0x6d, 0x35, 0xb1, + 0x84, 0xa8, 0x05, 0x85, 0xc4, 0xe8, 0x8f, 0xce, 0x12, 0xe7, 0x7f, 0xfc, 0x97, 0xe0, 0xa4, 0xb2, + 0x79, 0xa4, 0x9c, 0x45, 0x5f, 0xa7, 0xd0, 0xf7, 0x50, 0x4c, 0x0e, 0xb2, 0x28, 0x39, 0xa0, 0x6c, + 0x98, 0x70, 0x3f, 0xaa, 0xeb, 0x0d, 0x18, 0x38, 0x12, 0xde, 0x44, 0x0e, 0x24, 0xf1, 0x88, 0x88, + 0x4e, 0x12, 0xfc, 0xb5, 0xb9, 0xf3, 0xe4, 0x74, 0xa3, 0x2c, 0xae, 0xad, 0xb6, 0x3e, 0x62, 0x3c, + 0xa4, 0x3d, 0x3a, 0xe2, 0xea, 0x64, 0x78, 0xf2, 0xc9, 0x53, 0xe2, 0x58, 0xdb, 0x00, 0xf6, 0x37, + 0x74, 0x01, 0xf4, 0xeb, 0xa4, 0x07, 0x4f, 0xf6, 0x90, 0x93, 0x97, 0xff, 0x8f, 0xb6, 0xb4, 0xb2, + 0xa1, 0x5d, 0xac, 0x58, 0x79, 0xba, 0xd9, 0xac, 0x58, 0xf9, 0x48, 0xd7, 0xb9, 0xfe, 0xcd, 0x1f, + 0xae, 0x86, 0x9e, 0x18, 0xcd, 0x6e, 0x2f, 0xfb, 0xc1, 0xe4, 0x6a, 0xec, 0x0d, 0x47, 0xc2, 0xf7, + 0xfc, 0xa1, 0xcf, 0xc5, 0x9f, 0x82, 0xf0, 0xfe, 0x6a, 0xec, 0x0f, 0xae, 0xd4, 0xd0, 0x7b, 0xb5, + 0x50, 0x77, 0xbb, 0xad, 0xfe, 0x32, 0xfe, 0xf6, 0x7f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x28, 0x55, + 0x66, 0x91, 0x62, 0x0e, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. diff --git a/lnrpc/routerrpc/router.proto b/lnrpc/routerrpc/router.proto index 18f08444a7d..e092682176a 100644 --- a/lnrpc/routerrpc/router.proto +++ b/lnrpc/routerrpc/router.proto @@ -326,6 +326,8 @@ message QueryMissionControlRequest {} /// QueryMissionControlResponse contains mission control state per node. message QueryMissionControlResponse { repeated NodeHistory nodes = 1; + + repeated PairHistory pairs = 2; } /// NodeHistory contains the mission control state for a particular node. @@ -338,24 +340,32 @@ message NodeHistory { /// Estimation of success probability for channels not in the channel array. float other_chan_success_prob = 3 [json_name = "other_chan_success_prob"]; +} + +enum PairResult { + SUCCESS = 0; + + FAIL = 1; - /// Historical information of particular channels. - repeated ChannelHistory channels = 4 [json_name = "channels"]; + FAIL_BALANCE = 2; } /// NodeHistory contains the mission control state for a particular channel. -message ChannelHistory { - /// Short channel id - uint64 channel_id = 1 [json_name = "channel_id"]; +message PairHistory { + bytes node_a = 1; + + bytes node_b = 2; /// Time stamp of last failure. - int64 last_fail_time = 2 [json_name = "last_fail_time"]; + int64 timestamp = 3 [json_name = "timestamp"]; /// Minimum penalization amount. - int64 min_penalize_amt_sat = 3 [json_name = "min_penalize_amt_sat"]; + int64 amt = 4 [json_name = "amt"]; /// Estimation of success probability for this channel. - float success_prob = 4 [json_name = "success_prob"]; + float success_prob = 5 [json_name = "success_prob"]; + + PairResult result = 6; } service Router { diff --git a/lnrpc/routerrpc/router_backend.go b/lnrpc/routerrpc/router_backend.go index d4d1d8e274d..21b53a12b0f 100644 --- a/lnrpc/routerrpc/router_backend.go +++ b/lnrpc/routerrpc/router_backend.go @@ -59,8 +59,8 @@ type RouterBackend struct { type MissionControl interface { // GetEdgeProbability is expected to return the success probability of a payment // from fromNode along edge. - GetEdgeProbability(fromNode route.Vertex, - edge routing.EdgeLocator, amt lnwire.MilliSatoshi) float64 + GetEdgeProbability(fromNode, toNode route.Vertex, + amt lnwire.MilliSatoshi) float64 // ResetHistory resets the history of MissionControl returning it to a state as // if no payment attempts have been made. @@ -142,28 +142,37 @@ func (r *RouterBackend) QueryRoutes(ctx context.Context, ignoredNodes[ignoreVertex] = struct{}{} } - ignoredEdges := make(map[routing.EdgeLocator]struct{}) + ignoredEdges := make(map[routing.DirectedNodePair]struct{}) for _, ignoredEdge := range in.IgnoredEdges { - locator := routing.EdgeLocator{ - ChannelID: ignoredEdge.ChannelId, + a, b, err := r.FetchChannelEndpoints(ignoredEdge.ChannelId) + if err != nil { + log.Warnf("Channel %v unknown", ignoredEdge.ChannelId) + continue } + + var pair routing.DirectedNodePair if ignoredEdge.DirectionReverse { - locator.Direction = 1 + pair.From, pair.To = b, a + } else { + pair.From, pair.To = a, b } - ignoredEdges[locator] = struct{}{} + ignoredEdges[pair] = struct{}{} } restrictions := &routing.RestrictParams{ FeeLimit: feeLimit, - ProbabilitySource: func(node route.Vertex, - edge routing.EdgeLocator, + ProbabilitySource: func(fromNode, toNode route.Vertex, amt lnwire.MilliSatoshi) float64 { - if _, ok := ignoredNodes[node]; ok { + if _, ok := ignoredNodes[fromNode]; ok { return 0 } - if _, ok := ignoredEdges[edge]; ok { + pair := routing.DirectedNodePair{ + From: fromNode, + To: toNode, + } + if _, ok := ignoredEdges[pair]; ok { return 0 } @@ -172,7 +181,7 @@ func (r *RouterBackend) QueryRoutes(ctx context.Context, } return r.MissionControl.GetEdgeProbability( - node, edge, amt, + fromNode, toNode, amt, ) }, } diff --git a/lnrpc/routerrpc/router_backend_test.go b/lnrpc/routerrpc/router_backend_test.go index 1a0ce3ee982..9fdc6bcbdf1 100644 --- a/lnrpc/routerrpc/router_backend_test.go +++ b/lnrpc/routerrpc/router_backend_test.go @@ -50,11 +50,6 @@ func testQueryRoutes(t *testing.T, useMissionControl bool) { t.Fatal(err) } - ignoredEdge := routing.EdgeLocator{ - ChannelID: 555, - Direction: 1, - } - request := &lnrpc.QueryRoutesRequest{ PubKey: destKey, Amt: 100000, @@ -92,14 +87,14 @@ func testQueryRoutes(t *testing.T, useMissionControl bool) { t.Fatal("unexpected fee limit") } - if restrictions.ProbabilitySource(route.Vertex{}, - ignoredEdge, 0, + if restrictions.ProbabilitySource(route.Vertex{2}, + route.Vertex{1}, 0, ) != 0 { t.Fatal("expecting 0% probability for ignored edge") } if restrictions.ProbabilitySource(ignoreNodeVertex, - routing.EdgeLocator{}, 0, + route.Vertex{6}, 0, ) != 0 { t.Fatal("expecting 0% probability for ignored node") } @@ -108,8 +103,8 @@ func testQueryRoutes(t *testing.T, useMissionControl bool) { if useMissionControl { expectedProb = testMissionControlProb } - if restrictions.ProbabilitySource(route.Vertex{}, - routing.EdgeLocator{}, 0, + if restrictions.ProbabilitySource(route.Vertex{4}, + route.Vertex{5}, 0, ) != expectedProb { t.Fatal("expecting 100% probability") } @@ -128,6 +123,14 @@ func testQueryRoutes(t *testing.T, useMissionControl bool) { return 1, nil }, MissionControl: &mockMissionControl{}, + FetchChannelEndpoints: func(chanID uint64) (route.Vertex, + route.Vertex, error) { + + if chanID != 555 { + t.Fatal() + } + return route.Vertex{1}, route.Vertex{2}, nil + }, } resp, err := backend.QueryRoutes(context.Background(), request) @@ -142,8 +145,9 @@ func testQueryRoutes(t *testing.T, useMissionControl bool) { type mockMissionControl struct { } -func (m *mockMissionControl) GetEdgeProbability(fromNode route.Vertex, - edge routing.EdgeLocator, amt lnwire.MilliSatoshi) float64 { +func (m *mockMissionControl) GetEdgeProbability(fromNode, toNode route.Vertex, + amt lnwire.MilliSatoshi) float64 { + return testMissionControlProb } diff --git a/lnrpc/routerrpc/router_server.go b/lnrpc/routerrpc/router_server.go index 64b88f620d6..8f30da58a6f 100644 --- a/lnrpc/routerrpc/router_server.go +++ b/lnrpc/routerrpc/router_server.go @@ -451,40 +451,56 @@ func (s *Server) QueryMissionControl(ctx context.Context, snapshot := s.cfg.RouterBackend.MissionControl.GetHistorySnapshot() - rpcNodes := make([]*NodeHistory, len(snapshot.Nodes)) - for i, n := range snapshot.Nodes { + rpcNodes := make([]*NodeHistory, 0, len(snapshot.Nodes)) + for _, n := range snapshot.Nodes { // Copy node struct to prevent loop variable binding bugs. node := n - channels := make([]*ChannelHistory, len(node.Channels)) - for j, channel := range node.Channels { - channels[j] = &ChannelHistory{ - ChannelId: channel.ChannelID, - LastFailTime: channel.LastFail.Unix(), - MinPenalizeAmtSat: int64( - channel.MinPenalizeAmt.ToSatoshis(), - ), - SuccessProb: float32(channel.SuccessProb), - } + rpcNode := NodeHistory{ + Pubkey: node.Node[:], + LastFailTime: node.LastFail.Unix(), + OtherChanSuccessProb: float32( + node.OtherChanSuccessProb, + ), } - var lastFail int64 - if node.LastFail != nil { - lastFail = node.LastFail.Unix() + rpcNodes = append(rpcNodes, &rpcNode) + } + + rpcPairs := make([]*PairHistory, 0, len(snapshot.Pairs)) + for _, p := range snapshot.Pairs { + // Prevent binding to loop variable + pair := p + + var result PairResult + switch pair.ResultType { + case routing.ChannelResultFail: + result = PairResult_FAIL + case routing.ChannelResultFailBalance: + result = PairResult_FAIL_BALANCE + case routing.ChannelResultSuccess: + result = PairResult_SUCCESS + default: + return nil, errors.New("unknown result") } - rpcNodes[i] = &NodeHistory{ - Pubkey: node.Node[:], - LastFailTime: lastFail, - OtherChanSuccessProb: float32( - node.OtherChanSuccessProb, + rpcPair := PairHistory{ + NodeA: pair.NodeA[:], + NodeB: pair.NodeB[:], + Timestamp: pair.Timestamp.Unix(), + Amt: int64( + pair.Amount.ToSatoshis(), ), - Channels: channels, + SuccessProb: float32(pair.SuccessProb), + Result: result, } + + rpcPairs = append(rpcPairs, &rpcPair) } response := QueryMissionControlResponse{ Nodes: rpcNodes, + Pairs: rpcPairs, } return &response, nil diff --git a/routing/missioncontrol.go b/routing/missioncontrol.go index e85fb19af62..4f7724ef5a5 100644 --- a/routing/missioncontrol.go +++ b/routing/missioncontrol.go @@ -1,6 +1,7 @@ package routing import ( + "fmt" "math" "sync" "time" @@ -43,6 +44,10 @@ const ( // DefaultMaxMcHistory is the default maximum history size. DefaultMaxMcHistory = 1000 + + // prevSuccessProbability is the assumed probability for node pairs that + // successfully relayed the previous attempt. + prevSuccessProbability = 1.0 ) // MissionControl contains state which summarizes the past attempts of HTLC @@ -55,12 +60,20 @@ const ( // since the last failure is used to estimate a success probability that is fed // into the path finding process for subsequent payment attempts. type MissionControl struct { - history map[route.Vertex]*nodeHistory + // lastPairResult tracks the last payment outcome per node pair. + lastPairResult map[NodePair]pairHistory + + // lastNodeFailure tracks the last node level failure per node. + lastNodeFailure map[route.Vertex]time.Time // lastSecondChance tracks the last time a second chance was granted for // a directed node pair. lastSecondChance map[DirectedNodePair]time.Time + // paymentAttempts stores the payment initiation data. This data is + // combined with the result when it comes in. + paymentAttempts map[uint64]*paymentInitiate + // now is expected to return the current time. It is supplied as an // external function to enable deterministic unit tests. now func() time.Time @@ -93,26 +106,11 @@ type MissionControlConfig struct { MaxMcHistory int } -// nodeHistory contains a summary of payment attempt outcomes involving a -// particular node. -type nodeHistory struct { - // lastFail is the last time a node level failure occurred, if any. - lastFail *time.Time +// pairHistory contains the last payment result for a node pair. +type pairHistory struct { + timestamp time.Time - // channelLastFail tracks history per channel, if available for that - // channel. - channelLastFail map[uint64]*channelHistory -} - -// channelHistory contains a summary of payment attempt outcomes involving a -// particular channel. -type channelHistory struct { - // lastFail is the last time a channel level failure occurred. - lastFail time.Time - - // minPenalizeAmt is the minimum amount for which to take this failure - // into account. - minPenalizeAmt lnwire.MilliSatoshi + pairResult } // MissionControlSnapshot contains a snapshot of the current state of mission @@ -120,6 +118,10 @@ type channelHistory struct { type MissionControlSnapshot struct { // Nodes contains the per node information of this snapshot. Nodes []MissionControlNodeSnapshot + + // Pairs is a list of channels for which specific information is + // logged. + Pairs []MissionControlChannelSnapshot } // MissionControlNodeSnapshot contains a snapshot of the current node state in @@ -129,11 +131,7 @@ type MissionControlNodeSnapshot struct { Node route.Vertex // Lastfail is the time of last failure, if any. - LastFail *time.Time - - // Channels is a list of channels for which specific information is - // logged. - Channels []MissionControlChannelSnapshot + LastFail time.Time // OtherChanSuccessProb is the success probability for channels not in // the Channels slice. @@ -143,20 +141,28 @@ type MissionControlNodeSnapshot struct { // MissionControlChannelSnapshot contains a snapshot of the current channel // state in mission control. type MissionControlChannelSnapshot struct { - // ChannelID is the short channel id of the snapshot. - ChannelID uint64 + NodeA, NodeB route.Vertex - // LastFail is the time of last failure. - LastFail time.Time + // Timestamp is the time of last outcome. + Timestamp time.Time - // MinPenalizeAmt is the minimum amount for which the channel will be + // Amount is the minimum amount for which the channel will be // penalized. - MinPenalizeAmt lnwire.MilliSatoshi + Amount lnwire.MilliSatoshi + + ResultType ChannelResultType // SuccessProb is the success probability estimation for this channel. SuccessProb float64 } +// paymentInitiate contains information that is available when a payment attempt +// is initiated. +type paymentInitiate struct { + timestamp time.Time + route *route.Route +} + // paymentResult is the information that becomes available when a payment // attempt completes. type paymentResult struct { @@ -182,8 +188,10 @@ func NewMissionControl(db *bbolt.DB, cfg *MissionControlConfig) ( } mc := &MissionControl{ - history: make(map[route.Vertex]*nodeHistory), + lastPairResult: make(map[NodePair]pairHistory), + lastNodeFailure: make(map[route.Vertex]time.Time), lastSecondChance: make(map[DirectedNodePair]time.Time), + paymentAttempts: make(map[uint64]*paymentInitiate), now: time.Now, cfg: cfg, store: store, @@ -227,7 +235,8 @@ func (m *MissionControl) ResetHistory() error { return err } - m.history = make(map[route.Vertex]*nodeHistory) + m.lastPairResult = make(map[NodePair]pairHistory) + m.lastNodeFailure = make(map[route.Vertex]time.Time) m.lastSecondChance = make(map[DirectedNodePair]time.Time) log.Debugf("Mission control history cleared") @@ -237,57 +246,22 @@ func (m *MissionControl) ResetHistory() error { // GetEdgeProbability is expected to return the success probability of a payment // from fromNode along edge. -func (m *MissionControl) GetEdgeProbability(fromNode route.Vertex, - edge EdgeLocator, amt lnwire.MilliSatoshi) float64 { +func (m *MissionControl) GetEdgeProbability(fromNode, toNode route.Vertex, + amt lnwire.MilliSatoshi) float64 { m.Lock() defer m.Unlock() - // Get the history for this node. If there is no history available, - // assume that it's success probability is a constant a priori - // probability. After the attempt new information becomes available to - // adjust this probability. - nodeHistory, ok := m.history[fromNode] - if !ok { - return m.cfg.AprioriHopProbability - } - - return m.getEdgeProbabilityForNode(nodeHistory, edge.ChannelID, amt) + return m.getEdgeProbabilityForNode(fromNode, toNode, amt) } -// getEdgeProbabilityForNode estimates the probability of successfully -// traversing a channel based on the node history. -func (m *MissionControl) getEdgeProbabilityForNode(nodeHistory *nodeHistory, - channelID uint64, amt lnwire.MilliSatoshi) float64 { - - // Calculate the last failure of the given edge. A node failure is - // considered a failure that would have affected every edge. Therefore - // we insert a node level failure into the history of every channel. - lastFailure := nodeHistory.lastFail - - // Take into account a minimum penalize amount. For balance errors, a - // failure may be reported with such a minimum to prevent too aggresive - // penalization. We only take into account a previous failure if the - // amount that we currently get the probability for is greater or equal - // than the minPenalizeAmt of the previous failure. - channelHistory, ok := nodeHistory.channelLastFail[channelID] - if ok && channelHistory.minPenalizeAmt <= amt { - - // If there is both a node level failure recorded and a channel - // level failure is applicable too, we take the most recent of - // the two. - if lastFailure == nil || - channelHistory.lastFail.After(*lastFailure) { - - lastFailure = &channelHistory.lastFail - } - } - - if lastFailure == nil { +// getProbAfterFail returns a probability estimate based on a last failure time. +func (m *MissionControl) getProbAfterFail(lastFailure time.Time) float64 { + if lastFailure.IsZero() { return m.cfg.AprioriHopProbability } - timeSinceLastFailure := m.now().Sub(*lastFailure) + timeSinceLastFailure := m.now().Sub(lastFailure) // Calculate success probability. It is an exponential curve that brings // the probability down to zero when a failure occurs. From there it @@ -299,6 +273,42 @@ func (m *MissionControl) getEdgeProbabilityForNode(nodeHistory *nodeHistory, return probability } +// getEdgeProbabilityForNode estimates the probability of successfully +// traversing a channel based on the node history. +func (m *MissionControl) getEdgeProbabilityForNode(fromNode, + toNode route.Vertex, amt lnwire.MilliSatoshi) float64 { + + // Start by getting the last node level failure. If there is none, + // lastFail will be zero. + lastFail := m.lastNodeFailure[fromNode] + + // Retrieve the last pair outcome. + pair := newNodePair(fromNode, toNode) + lastPairResult, lastPairResultExists := m.lastPairResult[pair] + + // If there is none or it happened before the last node level failure, + // the node level failure is the most recent and thus returned. + if lastPairResultExists && lastPairResult.timestamp.After(lastFail) { + switch lastPairResult.resultType { + case ChannelResultSuccess: + return prevSuccessProbability + + // If the last pair outcome is a balance failure and the current + // amount is less than the failed amount, ignore this as a + // failure. + case ChannelResultFailBalance: + if amt >= lastPairResult.amount { + lastFail = lastPairResult.timestamp + } + + case ChannelResultFail: + lastFail = lastPairResult.timestamp + } + } + + return m.getProbAfterFail(lastFail) +} + // requestSecondChance checks whether the node fromNode can have a second chance // at providing a channel update for its channel with toNode. func (m *MissionControl) requestSecondChance(timestamp time.Time, @@ -330,123 +340,78 @@ func (m *MissionControl) requestSecondChance(timestamp time.Time, return false } -// createHistoryIfNotExists returns the history for the given node. If the node -// is yet unknown, it will create an empty history structure. -func (m *MissionControl) createHistoryIfNotExists(vertex route.Vertex) *nodeHistory { - if node, ok := m.history[vertex]; ok { - return node - } +// GetHistorySnapshot takes a snapshot from the current mission control state +// and actual probability estimates. +func (m *MissionControl) GetHistorySnapshot() *MissionControlSnapshot { + m.Lock() + defer m.Unlock() - node := &nodeHistory{ - channelLastFail: make(map[uint64]*channelHistory), - } - m.history[vertex] = node + log.Debugf("Requesting history snapshot from mission control: "+ + "node_count=%v", len(m.lastPairResult)) - return node -} + nodes := make([]MissionControlNodeSnapshot, 0, len(m.lastNodeFailure)) + for v, h := range m.lastNodeFailure { + otherProb := m.getEdgeProbabilityForNode(v, route.Vertex{}, 0) -// reportVertexFailure reports a node level failure. -func (m *MissionControl) reportVertexFailure(timestamp time.Time, - v route.Vertex) { + nodes = append(nodes, MissionControlNodeSnapshot{ + Node: v, + LastFail: h, + OtherChanSuccessProb: otherProb, + }) + } - log.Debugf("Reporting vertex %v failure to Mission Control", v) + pairs := make([]MissionControlChannelSnapshot, 0, len(m.lastPairResult)) - m.Lock() - defer m.Unlock() - - history := m.createHistoryIfNotExists(v) - history.lastFail = ×tamp -} + for v, h := range m.lastPairResult { + // Show probability assuming amount meets min + // penalization amount. + prob := m.getEdgeProbabilityForNode( + v.A, v.B, h.amount, + ) -// reportEdgePolicyFailure reports a policy related failure. -func (m *MissionControl) reportEdgePolicyFailure(timestamp time.Time, - failedEdge edge) { + pair := MissionControlChannelSnapshot{ + NodeA: v.A, + NodeB: v.B, + Amount: h.amount, + Timestamp: h.timestamp, + ResultType: h.resultType, + SuccessProb: prob, + } - m.Lock() - defer m.Unlock() + pairs = append(pairs, pair) + } - // We may have an out of date graph. Therefore we don't always penalize - // immediately. If some time has passed since the last policy failure, - // we grant the node a second chance at forwarding the payment. - if m.requestSecondChance( - timestamp, failedEdge.from, failedEdge.to, - ) { - return + snapshot := MissionControlSnapshot{ + Nodes: nodes, + Pairs: pairs, } - history := m.createHistoryIfNotExists(failedEdge.from) - history.lastFail = ×tamp + return &snapshot } -// reportEdgeFailure reports a channel level failure. -// -// TODO(roasbeef): also add value attempted to send and capacity of channel -func (m *MissionControl) reportEdgeFailure(timestamp time.Time, failedEdge edge, - minPenalizeAmt lnwire.MilliSatoshi) { - - log.Debugf("Reporting channel %v failure to Mission Control", - failedEdge.channel) +// ReportPaymentInitiate reports a payment attempt initiation to mission +// control. +func (m *MissionControl) ReportPaymentInitiate(paymentID uint64, + rt *route.Route) error { - m.Lock() - defer m.Unlock() + timestamp := m.now() - history := m.createHistoryIfNotExists(failedEdge.from) - history.channelLastFail[failedEdge.channel] = &channelHistory{ - lastFail: timestamp, - minPenalizeAmt: minPenalizeAmt, + initiate := paymentInitiate{ + route: rt, + timestamp: timestamp, } -} -// GetHistorySnapshot takes a snapshot from the current mission control state -// and actual probability estimates. -func (m *MissionControl) GetHistorySnapshot() *MissionControlSnapshot { m.Lock() defer m.Unlock() - log.Debugf("Requesting history snapshot from mission control: "+ - "node_count=%v", len(m.history)) - - nodes := make([]MissionControlNodeSnapshot, 0, len(m.history)) - - for v, h := range m.history { - channelSnapshot := make([]MissionControlChannelSnapshot, 0, - len(h.channelLastFail), - ) - - for id, lastFail := range h.channelLastFail { - // Show probability assuming amount meets min - // penalization amount. - prob := m.getEdgeProbabilityForNode( - h, id, lastFail.minPenalizeAmt, - ) - - channelSnapshot = append(channelSnapshot, - MissionControlChannelSnapshot{ - ChannelID: id, - LastFail: lastFail.lastFail, - MinPenalizeAmt: lastFail.minPenalizeAmt, - SuccessProb: prob, - }, - ) - } - - otherProb := m.getEdgeProbabilityForNode(h, 0, 0) - - nodes = append(nodes, - MissionControlNodeSnapshot{ - Node: v, - LastFail: h.lastFail, - OtherChanSuccessProb: otherProb, - Channels: channelSnapshot, - }, - ) + if _, exists := m.paymentAttempts[paymentID]; exists { + return fmt.Errorf("payment attempt %v already exists", + paymentID) } - snapshot := MissionControlSnapshot{ - Nodes: nodes, - } + m.paymentAttempts[paymentID] = &initiate - return &snapshot + return nil } // ReportPaymentFail reports a failed payment to mission control as input for @@ -455,23 +420,52 @@ func (m *MissionControl) GetHistorySnapshot() *MissionControlSnapshot { // returns a bool indicating whether this error is a final error. If it is // final, a failure reason is returned and no further payment attempts need to // be made. -func (m *MissionControl) ReportPaymentFail(paymentID uint64, rt *route.Route, +func (m *MissionControl) ReportPaymentFail(paymentID uint64, failureSourceIdx *int, failure lnwire.FailureMessage) (bool, channeldb.FailureReason, error) { timestamp := m.now() - // TODO(joostjager): Use actual payment initiation time for timeFwd. result := &paymentResult{ - success: false, - timeFwd: timestamp, timeReply: timestamp, id: paymentID, + success: false, failureSourceIdx: failureSourceIdx, failure: failure, - route: rt, } + return m.processPaymentResult(result) +} + +// ReportPaymentSuccess reports a successful payment to mission control as input +// for future probability estimates. +func (m *MissionControl) ReportPaymentSuccess(paymentID uint64) error { + timestamp := m.now() + + result := &paymentResult{ + timeReply: timestamp, + id: paymentID, + success: true, + } + + _, _, err := m.processPaymentResult(result) + return err +} + +func (m *MissionControl) processPaymentResult(result *paymentResult) (bool, + channeldb.FailureReason, error) { + + // Retrieve payment initiation data. + initiate, ok := m.paymentAttempts[result.id] + if !ok { + return false, 0, fmt.Errorf("initiate not found for payment %v", + result.id) + } + + // Supplement with initiation data. + result.route = initiate.route + result.timeFwd = initiate.timestamp + // Store complete result in database. if err := m.store.AddResult(result); err != nil { return false, 0, err @@ -489,245 +483,37 @@ func (m *MissionControl) ReportPaymentFail(paymentID uint64, rt *route.Route, func (m *MissionControl) applyPaymentResult(result *paymentResult) ( bool, channeldb.FailureReason) { - var ( - failureSourceIdxInt int - failure lnwire.FailureMessage + // Interpret result. + i := newInterpretedResult( + result.route, result.success, result.failureSourceIdx, + result.failure, ) - if result.failureSourceIdx == nil { - // If the failure message could not be decrypted, attribute the - // failure to our own outgoing channel. - // - // TODO(joostager): Penalize all channels in the route. - failureSourceIdxInt = 0 - failure = lnwire.NewTemporaryChannelFailure(nil) - } else { - failureSourceIdxInt = *result.failureSourceIdx - failure = result.failure - } - - var failureVertex route.Vertex + log.Debugf("Interpretation: %v", i) - if failureSourceIdxInt > 0 { - failureVertex = result.route.Hops[failureSourceIdxInt-1].PubKeyBytes - } else { - failureVertex = result.route.SourcePubKey - } - log.Tracef("Node %x (index %v) reported failure when sending htlc", - failureVertex, result.failureSourceIdx) - - // Always determine chan id ourselves, because a channel update with id - // may not be available. - failedEdge, failedAmt := getFailedEdge( - result.route, failureSourceIdxInt, - ) + // Update mission control state using the interpretation. + m.Lock() + defer m.Unlock() - switch failure.(type) { - - // If the end destination didn't know the payment - // hash or we sent the wrong payment amount to the - // destination, then we'll terminate immediately. - case *lnwire.FailUnknownPaymentHash: - // 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 - // we'll exit early. - case *lnwire.FailIncorrectPaymentAmount: - return true, channeldb.FailureReasonIncorrectPaymentDetails - - // If the time-lock that was extended to the final node - // was incorrect, then we can't proceed. - case *lnwire.FailFinalIncorrectCltvExpiry: - // 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 - // node, then we'll exit early. - case *lnwire.FailFinalIncorrectHtlcAmount: - // 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 - // the final hop expires too soon, then will fail the - // payment. - // - // TODO(roasbeef): can happen to to race condition, try - // again with recent block height - case *lnwire.FailFinalExpiryTooSoon: - // 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, - // then we'll cancel the payment. - case *lnwire.FailInvalidRealm: - return true, channeldb.FailureReasonError - - // If we get a notice that the expiry was too soon for - // an intermediate node, then we'll prune out the node - // that sent us this error, as it doesn't now what the - // correct block height is. - case *lnwire.FailExpiryTooSoon: - m.reportVertexFailure(result.timeReply, failureVertex) - return false, 0 - - // If we hit an instance of onion payload corruption or an invalid - // version, then we'll exit early as this shouldn't happen in the - // 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: - return true, channeldb.FailureReasonError - case *lnwire.FailInvalidOnionHmac: - return true, channeldb.FailureReasonError - case *lnwire.FailInvalidOnionKey: - return true, channeldb.FailureReasonError - - // If we get a failure due to violating the minimum - // amount, we'll apply the new minimum amount and retry - // routing. - case *lnwire.FailAmountBelowMinimum: - m.reportEdgePolicyFailure(result.timeReply, failedEdge) - return false, 0 - - // If we get a failure due to a fee, we'll apply the - // new fee update, and retry our attempt using the - // newly updated fees. - case *lnwire.FailFeeInsufficient: - m.reportEdgePolicyFailure(result.timeReply, failedEdge) - return false, 0 - - // If we get the failure for an intermediate node that - // disagrees with our time lock values, then we'll - // apply the new delta value and try it once more. - case *lnwire.FailIncorrectCltvExpiry: - m.reportEdgePolicyFailure(result.timeReply, failedEdge) - return false, 0 - - // The outgoing channel that this node was meant to - // forward one is currently disabled, so we'll apply - // the update and continue. - case *lnwire.FailChannelDisabled: - m.reportEdgeFailure(result.timeReply, failedEdge, 0) - return false, 0 - - // It's likely that the outgoing channel didn't have - // sufficient capacity, so we'll prune this edge for - // now, and continue onwards with our path finding. - case *lnwire.FailTemporaryChannelFailure: - m.reportEdgeFailure(result.timeReply, failedEdge, failedAmt) - 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.FailRequiredNodeFeatureMissing: - m.reportVertexFailure(result.timeReply, 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: - m.reportVertexFailure(result.timeReply, failureVertex) - return false, 0 - - // If the next hop in the route wasn't known or - // offline, we'll only the channel which we attempted - // to route over. This is conservative, and it can - // handle faulty channels between nodes properly. - // Additionally, this guards against routing nodes - // returning errors in order to attempt to black list - // another node. - case *lnwire.FailUnknownNextPeer: - m.reportEdgeFailure(result.timeReply, failedEdge, 0) - return false, 0 - - // If the node wasn't able to forward for which ever - // reason, then we'll note this and continue with the - // routes. - case *lnwire.FailTemporaryNodeFailure: - m.reportVertexFailure(result.timeReply, failureVertex) - return false, 0 - - case *lnwire.FailPermanentNodeFailure: - m.reportVertexFailure(result.timeReply, failureVertex) - return false, 0 - - // If we crafted a route that contains a too long time - // lock for an intermediate node, we'll prune the node. - // As there currently is no way of knowing that node's - // maximum acceptable cltv, we cannot take this - // constraint into account during routing. - // - // TODO(joostjager): Record the rejected cltv and use - // that as a hint during future path finding through - // that node. - case *lnwire.FailExpiryTooFar: - m.reportVertexFailure(result.timeReply, failureVertex) - return false, 0 - - // If we get a permanent channel or node failure, then - // we'll prune the channel in both directions and - // continue with the rest of the routes. - case *lnwire.FailPermanentChannelFailure: - m.reportEdgeFailure(result.timeReply, failedEdge, 0) - m.reportEdgeFailure(result.timeReply, edge{ - from: failedEdge.to, - to: failedEdge.from, - channel: failedEdge.channel, - }, 0) - return false, 0 - - // Any other failure or an empty failure will get the node pruned. - default: - m.reportVertexFailure(result.timeReply, failureVertex) - return false, 0 + if i.policyFailure != nil { + if m.requestSecondChance( + result.timeReply, + i.policyFailure.From, i.policyFailure.To, + ) { + return false, 0 + } } -} -// getFailedEdge tries to locate the failing channel given a route and the -// 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, failureSource int) (edge, - lnwire.MilliSatoshi) { - - // Determine if we have a failure from the final hop. If it is, we - // assume that the failing channel is the incoming channel. - // - // 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-- + for node := range i.nodeFailures { + m.lastNodeFailure[node] = result.timeReply } - // 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 + for pair, pairResult := range i.pairResults { + m.lastPairResult[pair] = pairHistory{ + pairResult: pairResult, + timestamp: result.timeReply, + } } - return edge{ - from: route.Hops[failureSource-1].PubKeyBytes, - to: route.Hops[failureSource].PubKeyBytes, - channel: route.Hops[failureSource].ChannelID, - }, route.Hops[failureSource-1].AmtToForward + return i.finalFailure, i.failureReason } diff --git a/routing/missioncontrol_store.go b/routing/missioncontrol_store.go index 329d819f756..7df22565d64 100644 --- a/routing/missioncontrol_store.go +++ b/routing/missioncontrol_store.go @@ -90,15 +90,17 @@ func (b *missionControlStore) fetchAll() ([]*paymentResult, error) { err := b.db.View(func(tx *bbolt.Tx) error { resultBucket := tx.Bucket(resultsKey) - results = make([]*paymentResult, 0) + results = make([]*paymentResult, resultBucket.Stats().KeyN) + i := 0 return resultBucket.ForEach(func(k, v []byte) error { result, err := deserializeResult(k, v) if err != nil { return err } - results = append(results, result) + results[i] = result + i++ return nil }) diff --git a/routing/missioncontrol_test.go b/routing/missioncontrol_test.go index 798161c9255..f8c5e62e41a 100644 --- a/routing/missioncontrol_test.go +++ b/routing/missioncontrol_test.go @@ -12,10 +12,6 @@ import ( ) var ( - mcTestEdge = EdgeLocator{ - ChannelID: 2, - } - mcTestRoute = &route.Route{ SourcePubKey: route.Vertex{10}, Hops: []*route.Hop{ @@ -94,14 +90,13 @@ func (ctx *mcTestContext) cleanup() { } // Assert that mission control returns a probability for an edge. -func (ctx *mcTestContext) expectP(amt lnwire.MilliSatoshi, - expected float64) { +func (ctx *mcTestContext) expectP(amt lnwire.MilliSatoshi, expected float64) { ctx.t.Helper() - p := ctx.mc.GetEdgeProbability(mcTestNode1, mcTestEdge, amt) + p := ctx.mc.GetEdgeProbability(mcTestNode1, mcTestNode2, amt) if p != expected { - ctx.t.Fatalf("unexpected probability %v", p) + ctx.t.Fatalf("expected probability %v but got %v", expected, p) } } @@ -111,10 +106,35 @@ func (ctx *mcTestContext) reportFailure(t time.Time, mcTestRoute.Hops[0].AmtToForward = amt + err := ctx.mc.ReportPaymentInitiate(ctx.pid, mcTestRoute) + if err != nil { + ctx.t.Fatal(err) + } + errorSourceIdx := 1 - ctx.mc.ReportPaymentFail( - ctx.pid, mcTestRoute, &errorSourceIdx, failure, + _, _, err = ctx.mc.ReportPaymentFail( + ctx.pid, &errorSourceIdx, failure, ) + if err != nil { + ctx.t.Fatal(err) + } + + ctx.pid++ +} + +// reportSuccess reports a success by using a test route. +func (ctx *mcTestContext) reportSuccess(t time.Time) { + err := ctx.mc.ReportPaymentInitiate(ctx.pid, mcTestRoute) + if err != nil { + ctx.t.Fatal(err) + } + + err = ctx.mc.ReportPaymentSuccess(ctx.pid) + if err != nil { + ctx.t.Fatal(err) + } + + ctx.pid++ } // TestMissionControl tests mission control probability estimation. @@ -175,16 +195,25 @@ func TestMissionControl(t *testing.T) { t.Fatal("unexpected number of nodes") } - if len(history.Nodes[0].Channels) != 1 { + if len(history.Pairs) != 1 { t.Fatal("unexpected number of channels") } + + // Test reporting an unknown failure. + ctx.reportFailure( + ctx.now, 0, nil, + ) + + // Test reporting a success. + ctx.reportSuccess( + ctx.now, + ) } // TestMissionControlChannelUpdate tests that the first channel update is not // penalizing the channel yet. func TestMissionControlChannelUpdate(t *testing.T) { ctx := createMcTestContext(t) - defer ctx.cleanup() // Report a policy related failure. Because it is the first, we don't // expect a penalty. diff --git a/routing/mock_test.go b/routing/mock_test.go index 54a8011df01..b4f37d65874 100644 --- a/routing/mock_test.go +++ b/routing/mock_test.go @@ -99,21 +99,23 @@ type mockMissionControl struct { var _ MissionController = (*mockMissionControl)(nil) func (m *mockMissionControl) ReportPaymentFail(paymentID uint64, - rt *route.Route, failureSourceIdx *int, failure lnwire.FailureMessage) ( - bool, channeldb.FailureReason, error) { + errorSourceIndex *int, failure lnwire.FailureMessage) (bool, + channeldb.FailureReason, error) { return false, 0, nil } -func (m *mockMissionControl) ReportEdgeFailure(failedEdge edge, - minPenalizeAmt lnwire.MilliSatoshi) { +func (m *mockMissionControl) ReportPaymentSuccess(paymentID uint64) error { + return nil } -func (m *mockMissionControl) ReportEdgePolicyFailure(failedEdge edge) {} +func (m *mockMissionControl) ReportPaymentInitiate(paymentID uint64, + rt *route.Route) error { -func (m *mockMissionControl) ReportVertexFailure(v route.Vertex) {} + return nil +} -func (m *mockMissionControl) GetEdgeProbability(fromNode route.Vertex, edge EdgeLocator, +func (m *mockMissionControl) GetEdgeProbability(fromNode, toNode route.Vertex, amt lnwire.MilliSatoshi) float64 { return 0 @@ -138,12 +140,6 @@ func (m *mockPaymentSession) RequestRoute(payment *LightningPayment, return r, nil } -func (m *mockPaymentSession) ReportVertexFailure(v route.Vertex) {} - -func (m *mockPaymentSession) ReportEdgeFailure(failedEdge edge, minPenalizeAmt lnwire.MilliSatoshi) {} - -func (m *mockPaymentSession) ReportEdgePolicyFailure(failedEdge edge) {} - type mockPayer struct { sendResult chan error paymentResultErr chan error diff --git a/routing/nodepair.go b/routing/nodepair.go index edec8e02b0b..f0a4d6905df 100644 --- a/routing/nodepair.go +++ b/routing/nodepair.go @@ -1,6 +1,8 @@ package routing import ( + "bytes" + "github.com/lightningnetwork/lnd/routing/route" ) @@ -8,3 +10,25 @@ import ( type DirectedNodePair struct { From, To route.Vertex } + +// NodePair stores an undirected pair of nodes. +type NodePair struct { + A, B route.Vertex +} + +// newNodePair instantiates a new nodePair struct. It makes sure that node a is +// the node with the lower pubkey. A second return parameters indicates whether +// the given node ordering is reversed or not. +func newNodePair(a, b route.Vertex) NodePair { + if bytes.Compare(a[:], b[:]) == 1 { + return NodePair{ + A: b, + B: a, + } + } + + return NodePair{ + A: a, + B: b, + } +} diff --git a/routing/pathfind.go b/routing/pathfind.go index 91b40371fe8..dacd41717cb 100644 --- a/routing/pathfind.go +++ b/routing/pathfind.go @@ -53,7 +53,7 @@ var ( // DefaultAprioriHopProbability is the default a priori probability for // a hop. - DefaultAprioriHopProbability = float64(0.95) + DefaultAprioriHopProbability = float64(0.6) ) // edgePolicyWithSource is a helper struct to keep track of the source node @@ -246,7 +246,7 @@ type graphParams struct { type RestrictParams struct { // ProbabilitySource is a callback that is expected to return the // success probability of traversing the channel from the node. - ProbabilitySource func(route.Vertex, EdgeLocator, + ProbabilitySource func(route.Vertex, route.Vertex, lnwire.MilliSatoshi) float64 // FeeLimit is a maximum fee amount allowed to be used on the path from @@ -401,14 +401,12 @@ func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig, amountToSend := toNodeDist.amountToReceive // Request the success probability for this edge. - locator := newEdgeLocator(edge) edgeProbability := r.ProbabilitySource( - fromVertex, *locator, amountToSend, + fromVertex, toNode, amountToSend, ) - log.Tracef("path finding probability: fromnode=%v, chanid=%v, "+ - "probability=%v", fromVertex, locator.ChannelID, - edgeProbability) + log.Tracef("path finding probability: fromnode=%v, tonode=%v, "+ + "probability=%v", fromVertex, toNode, edgeProbability) // If the probability is zero, there is no point in trying. if edgeProbability == 0 { diff --git a/routing/pathfind_test.go b/routing/pathfind_test.go index 352c06faa75..64676c83811 100644 --- a/routing/pathfind_test.go +++ b/routing/pathfind_test.go @@ -77,7 +77,7 @@ var ( // noProbabilitySource is used in testing to return the same probability 1 for // all edges. -func noProbabilitySource(route.Vertex, EdgeLocator, lnwire.MilliSatoshi) float64 { +func noProbabilitySource(route.Vertex, route.Vertex, lnwire.MilliSatoshi) float64 { return 1 } @@ -2156,6 +2156,8 @@ func testProbabilityRouting(t *testing.T, p10, p11, p20, minProbability float64, } defer testGraphInstance.cleanUp() + alias := testGraphInstance.aliasMap + sourceNode, err := testGraphInstance.graph.SourceNode() if err != nil { t.Fatalf("unable to fetch source node: %v", err) @@ -2166,19 +2168,19 @@ func testProbabilityRouting(t *testing.T, p10, p11, p20, minProbability float64, target := testGraphInstance.aliasMap["target"] // Configure a probability source with the test parameters. - probabilitySource := func(node route.Vertex, edge EdgeLocator, + probabilitySource := func(fromNode, toNode route.Vertex, amt lnwire.MilliSatoshi) float64 { if amt == 0 { t.Fatal("expected non-zero amount") } - switch edge.ChannelID { - case 10: + switch { + case fromNode == alias["a1"] && toNode == alias["a2"]: return p10 - case 11: + case fromNode == alias["a2"] && toNode == alias["target"]: return p11 - case 20: + case fromNode == alias["b"] && toNode == alias["target"]: return p20 default: return 1 diff --git a/routing/payment_lifecycle.go b/routing/payment_lifecycle.go index db9b9803b66..86ee2f8bdb3 100644 --- a/routing/payment_lifecycle.go +++ b/routing/payment_lifecycle.go @@ -83,6 +83,20 @@ func (p *paymentLifecycle) resumePayment() ([32]byte, *route.Route, error) { return [32]byte{}, nil, err } p.circuit = c + + // Report payment initiation to mission control. Mission + // control is not persisting this event and relies on it + // to be resupplied after restart. + // + // TODO(joostjager): Store payment attempt initiation + // time in payment control and pass in here. + err = p.router.cfg.MissionControl.ReportPaymentInitiate( + p.attempt.PaymentID, &p.attempt.Route, + ) + if err != nil { + log.Errorf("Error reporting resumed payment "+ + "initiate to mc: %v", err) + } } // Using the created circuit, initialize the error decrypter so we can @@ -161,6 +175,15 @@ func (p *paymentLifecycle) resumePayment() ([32]byte, *route.Route, error) { log.Debugf("Payment %x succeeded with pid=%v", p.payment.PaymentHash, p.attempt.PaymentID) + // Report success to mission control. + err = p.router.cfg.MissionControl.ReportPaymentSuccess( + p.attempt.PaymentID, + ) + if err != nil { + log.Errorf("Error reporting payment success to mc: %v", + err) + } + // In case of success we atomically store the db payment and // move the payment to the success state. err = p.router.cfg.Control.Success(p.payment.PaymentHash, result.Preimage) @@ -318,11 +341,18 @@ func (p *paymentLifecycle) sendPaymentAttempt(firstHop lnwire.ShortChannelID, }), ) + err := p.router.cfg.MissionControl.ReportPaymentInitiate( + p.attempt.PaymentID, &p.attempt.Route, + ) + if err != nil { + log.Errorf("Error reporting payment initiate to mc: %v", err) + } + // Send it to the Switch. When this method returns we assume // the Switch successfully has persisted the payment attempt, // such that we can resume waiting for the result after a // restart. - err := p.router.cfg.Payer.SendHTLC( + err = p.router.cfg.Payer.SendHTLC( firstHop, p.attempt.PaymentID, htlcAdd, ) if err != nil { diff --git a/routing/result_interpretation.go b/routing/result_interpretation.go new file mode 100644 index 00000000000..bdaa81d5f0e --- /dev/null +++ b/routing/result_interpretation.go @@ -0,0 +1,412 @@ +package routing + +import ( + "fmt" + "strings" + + "github.com/lightningnetwork/lnd/channeldb" + + "github.com/lightningnetwork/lnd/lnwire" + "github.com/lightningnetwork/lnd/routing/route" +) + +type ChannelResultType byte + +const ( + ChannelResultSuccess ChannelResultType = iota + + ChannelResultFail + + ChannelResultFailBalance +) + +type pairResult struct { + // amount is the amount that was sent across the channel. + amount lnwire.MilliSatoshi + + // resultType is the type of channel result that collected from the + // payment attempt. + resultType ChannelResultType +} + +func (r ChannelResultType) String() string { + switch r { + case ChannelResultFail: + return "Fail" + case ChannelResultFailBalance: + return "FailBalance" + case ChannelResultSuccess: + return "Success" + default: + return "Unknown" + } +} + +func (p pairResult) String() string { + return fmt.Sprintf("amt=%v, type=%v", + p.amount, p.resultType) +} + +type interpretedResult struct { + nodeFailures map[route.Vertex]struct{} + pairResults map[NodePair]pairResult + finalFailure bool + failureReason channeldb.FailureReason + policyFailure *DirectedNodePair +} + +func newInterpretedResult(rt *route.Route, success bool, failureSrcIdx *int, + failure lnwire.FailureMessage) *interpretedResult { + + i := &interpretedResult{ + nodeFailures: make(map[route.Vertex]struct{}), + pairResults: make(map[NodePair]pairResult), + } + + if success { + i.processSuccess(rt) + } else { + i.processFail(rt, failureSrcIdx, failure) + } + return i +} + +func (i *interpretedResult) processSuccess(route *route.Route) { + i.reportChannelRangeResult( + route, 1, len(route.Hops)-1, ChannelResultSuccess, + ) +} + +func (i *interpretedResult) processFail( + route *route.Route, errSourceIdx *int, + failure lnwire.FailureMessage) { + + if errSourceIdx == nil { + i.processPaymentOutcomeUnknown(route) + return + } + + switch *errSourceIdx { + + // We don't keep a reputation for ourselves, as information about + // channels should be available directly in links. Just retry with local + // info that should now be updated. + case 0: + log.Warnf("Routing error for local channel %v occurred", + route.Hops[0].ChannelID) + + // An error from the final hop was received. + case len(route.Hops): + i.processPaymentOutcomeFinal( + route, failure, + ) + + // An intermediate hop failed. Interpret the outcome, update reputation + // and try again. + default: + i.processPaymentOutcomeIntermediate( + route, *errSourceIdx, failure, + ) + } +} + +func (i *interpretedResult) failNode(rt *route.Route, idx int) { + i.nodeFailures[rt.Hops[idx].PubKeyBytes] = struct{}{} +} + +func (i *interpretedResult) setFailure(final bool, + reason channeldb.FailureReason) { + + i.finalFailure = final + i.failureReason = reason +} + +// processPaymentOutcomeFinal handles failures sent by the final hop. +func (i *interpretedResult) processPaymentOutcomeFinal( + route *route.Route, failure lnwire.FailureMessage) { + + n := len(route.Hops) + + // If a failure from the final node is received, we will fail the + // payment in almost all cases. Only when the penultimate node sends an + // incorrect htlc, we want to retry via another route. Invalid onion + // failures are not expected, because the final node wouldn't be able to + // encrypt that failure. + switch failure.(type) { + + // Expiry or amount of the HTLC doesn't match the onion, try another + // route. + case *lnwire.FailFinalIncorrectCltvExpiry, + *lnwire.FailFinalIncorrectHtlcAmount: + + // We trust ourselves. If this is a direct payment, we penalize + // the final node and fail the payment. + if n == 1 { + i.failNode(route, n-1) + i.setFailure(true, channeldb.FailureReasonError) + + return + } + + // Otherwise penalize the last channel of the route and retry. + i.reportChannelResult( + route, n-1, ChannelResultFail, + ) + + // The other hops relayed corectly, so assign those pairs a + // success result. + if n > 2 { + i.reportChannelRangeResult( + route, 1, n-2, ChannelResultSuccess, + ) + } + + // We are using wrong payment hash or amount, fail the payment. + case *lnwire.FailIncorrectPaymentAmount, + *lnwire.FailUnknownPaymentHash: + + // Assign all pairs a success result, as the payment reached the + // destination correctly. + i.reportChannelRangeResult(route, 1, n-1, ChannelResultSuccess) + + i.setFailure( + true, channeldb.FailureReasonIncorrectPaymentDetails, + ) + + // The HTLC that was extended to the final hop expires too soon. Fail + // the payment, because we may be using the wrong final cltv delta. + case *lnwire.FailFinalExpiryTooSoon: + // TODO(roasbeef): can happen to to race condition, try again + // with recent block height + + // TODO(joostjager): can also happen because a node delayed + // deliberately. What to penalize? + i.setFailure( + true, channeldb.FailureReasonIncorrectPaymentDetails, + ) + + default: + // All other errors are considered terminal if coming from the + // final hop. They indicate that something is wrong at the + // recipient, so we do apply a penalty. + i.failNode(route, n-1) + + // Other channels in the route forwarded correctly. + if n > 2 { + i.reportChannelRangeResult( + route, 1, n-2, ChannelResultSuccess, + ) + } + + i.setFailure(true, channeldb.FailureReasonError) + } +} + +// processPaymentOutcomeIntermediate handles failures sent by an intermediate +// hop. +func (i *interpretedResult) processPaymentOutcomeIntermediate( + route *route.Route, errorSourceIdx int, + failure lnwire.FailureMessage) { + + reportOutgoing := func(t ChannelResultType) { + i.reportChannelResult( + route, errorSourceIdx, t, + ) + + if errorSourceIdx > 1 { + i.reportChannelRangeResult( + route, 1, errorSourceIdx-1, ChannelResultSuccess, + ) + } + } + + reportIncoming := func() { + // We trust ourselves. If the error comes from the first hop, we + // can penalize the whole node. In that case there is no + // uncertainty as to which node to blame. + if errorSourceIdx == 1 { + i.failNode(route, errorSourceIdx-1) + return + } + + // Otherwise report the incoming channel. + i.reportChannelResult( + route, errorSourceIdx-1, ChannelResultFail, + ) + + if errorSourceIdx > 2 { + i.reportChannelRangeResult( + route, 1, errorSourceIdx-2, ChannelResultSuccess, + ) + } + } + + reportAll := func() { + // We trust ourselves. If the error comes from the first hop, we + // can penalize the whole node. In that case there is no + // uncertainty as to which node to blame. + if errorSourceIdx == 1 { + i.failNode(route, errorSourceIdx-1) + return + } + + // Otherwise report all channels up to the error source. + i.reportChannelRangeResult( + route, 1, errorSourceIdx-1, ChannelResultFail, + ) + } + + switch failure.(type) { + + // If a hop reports onion payload corruption or an invalid version, we + // will report the outgoing channel of that node. It may be either their + // or the next node's fault. + case *lnwire.FailInvalidOnionVersion, + *lnwire.FailInvalidOnionHmac, + *lnwire.FailInvalidOnionKey: + + reportOutgoing(ChannelResultFail) + + // If the next hop in the route wasn't known or offline, we'll only + // penalize the channel which we attempted to route over. This is + // conservative, and it can handle faulty channels between nodes + // properly. Additionally, this guards against routing nodes returning + // errors in order to attempt to black list another node. + case *lnwire.FailUnknownNextPeer: + reportOutgoing(ChannelResultFail) + + // If we get a permanent channel or node failure, then + // we'll prune the channel in both directions and + // continue with the rest of the routes. + case *lnwire.FailPermanentChannelFailure: + reportOutgoing(ChannelResultFail) + + // If we get a failure due to violating the channel policy, we request a + // second chance because our graph may be out of date. An attached + // channel update should have been applied by now. If the second chance + // is granted, we try again. Otherwise either the error source or its + // predecessor sending an incorrect htlc is to blame. + case *lnwire.FailAmountBelowMinimum, + *lnwire.FailFeeInsufficient, + *lnwire.FailIncorrectCltvExpiry, + *lnwire.FailChannelDisabled: + + // Set the node pair that is responsible for this failure. The + // second chance logic uses the policyFailure field. + i.policyFailure = &DirectedNodePair{ + From: route.Hops[errorSourceIdx-1].PubKeyBytes, + To: route.Hops[errorSourceIdx].PubKeyBytes, + } + + // Assuming no second chance, we report incoming channel. + reportIncoming() + + // If the outgoing channel doesn't have enough capacity, we penalize. + // But we penalize only in a single direction and only for amounts + // greater than the attempted amount. + case *lnwire.FailTemporaryChannelFailure: + reportOutgoing(ChannelResultFailBalance) + + // If FailExpiryTooSoon is received, there must have been some delay + // along the path. We can't know which node is causing the delay, so we + // penalize all of them up to the error source. + case *lnwire.FailExpiryTooSoon: + reportAll() + + // In all other cases, we report the whole node. These are all failures + // that should not happen. + default: + i.failNode(route, errorSourceIdx-1) + } +} + +// processPaymentOutcomeUnknown processes a payment outcome for which no failure +// message or source is available. +func (i *interpretedResult) processPaymentOutcomeUnknown(route *route.Route) { + + n := len(route.Hops) + + // If this is a direct payment, the destination must be at fault. + if n == 1 { + i.failNode(route, n-1) + i.setFailure( + true, channeldb.FailureReasonError, + ) + return + } + + // Penalize all channels in the route to make sure the responsible node + // is at least hit too. Start at one to not penalize our own channel. + i.reportChannelRangeResult(route, 1, n-1, ChannelResultFail) +} + +func (i *interpretedResult) reportChannelRangeResult( + rt *route.Route, fromIdx, toIdx int, + resultType ChannelResultType) { + + // Start at one because we don't penalize our own channels. + for idx := fromIdx; idx <= toIdx; idx++ { + i.reportChannelResult(rt, idx, resultType) + } +} + +// reportChannelFailure reports a bidirectional failure of a channel. +func (i *interpretedResult) reportChannelResult( + rt *route.Route, channelIdx int, + resultType ChannelResultType) { + + channelID := rt.Hops[channelIdx].ChannelID + log.Debugf("Reporting channel failure to Mission Control: "+ + "chan=%v", channelID) + + nodeB := rt.Hops[channelIdx].PubKeyBytes + var ( + nodeA route.Vertex + amt lnwire.MilliSatoshi + ) + + if channelIdx == 0 { + nodeA = rt.SourcePubKey + amt = rt.TotalAmount + } else { + nodeA = rt.Hops[channelIdx-1].PubKeyBytes + amt = rt.Hops[channelIdx-1].AmtToForward + } + + pair := newNodePair(nodeA, nodeB) + + i.pairResults[pair] = pairResult{ + amount: amt, + resultType: resultType, + } +} + +func (i interpretedResult) String() string { + var b strings.Builder + + first := true + + for n := range i.nodeFailures { + if !first { + b.WriteString(",") + } else { + first = false + } + b.WriteString(n.String()) + } + + for p, r := range i.pairResults { + if !first { + b.WriteString(",") + } else { + first = false + } + b.WriteString(fmt.Sprintf( + "(%x-%x,%v,%v)", + p.A[:6], p.B[:6], + r.amount.ToSatoshis(), r.resultType, + )) + } + + return b.String() +} diff --git a/routing/result_interpretation_test.go b/routing/result_interpretation_test.go new file mode 100644 index 00000000000..1b5cbcab089 --- /dev/null +++ b/routing/result_interpretation_test.go @@ -0,0 +1,118 @@ +package routing + +import ( + "testing" + + "github.com/lightningnetwork/lnd/lnwire" + + "github.com/lightningnetwork/lnd/routing/route" +) + +var ( + hops = []route.Vertex{ + route.Vertex{1}, route.Vertex{2}, route.Vertex{3}, + route.Vertex{4}, + } + + routeOneHop = route.Route{ + Hops: []*route.Hop{ + &route.Hop{PubKeyBytes: hops[0]}, + }, + } + + routeTwoHop = route.Route{ + Hops: []*route.Hop{ + &route.Hop{PubKeyBytes: hops[0]}, + &route.Hop{PubKeyBytes: hops[1]}, + }, + } + + routeThreeHop = route.Route{ + Hops: []*route.Hop{ + &route.Hop{PubKeyBytes: hops[0]}, + &route.Hop{PubKeyBytes: hops[1]}, + &route.Hop{PubKeyBytes: hops[2]}, + }, + } + + routeFourHop = route.Route{ + Hops: []*route.Hop{ + &route.Hop{PubKeyBytes: hops[0]}, + &route.Hop{PubKeyBytes: hops[1]}, + &route.Hop{PubKeyBytes: hops[2]}, + &route.Hop{PubKeyBytes: hops[3]}, + }, + } +) + +func TestResultInterpretationSuccess(t *testing.T) { + i := newInterpretedResult(&routeTwoHop, true, nil, nil) + + if len(i.pairResults) != 1 { + t.Fatal("expected one pair result") + } + + if i.pairResults[newNodePair(hops[0], hops[1])].resultType != + ChannelResultSuccess { + + t.Fatal("wrong pair result") + } +} + +func TestResultInterpretationSuccessDirect(t *testing.T) { + i := newInterpretedResult(&routeOneHop, true, nil, nil) + + if len(i.pairResults) != 0 { + t.Fatal("expected no results") + } +} + +func TestResultInterpretationFail(t *testing.T) { + failureSrcIdx := 1 + i := newInterpretedResult( + &routeTwoHop, false, &failureSrcIdx, + lnwire.NewTemporaryChannelFailure(nil), + ) + + if len(i.pairResults) != 1 { + t.Fatal("expected one pair result") + } + + if i.pairResults[newNodePair(hops[0], hops[1])].resultType != + ChannelResultFailBalance { + + t.Fatal("wrong pair result") + } + + if i.finalFailure { + t.Fatal("expected attempt to be non-final") + } +} + +func TestResultInterpretationFailExpiryTooSoon(t *testing.T) { + failureSrcIdx := 3 + i := newInterpretedResult( + &routeFourHop, false, &failureSrcIdx, + lnwire.NewExpiryTooSoon(lnwire.ChannelUpdate{}), + ) + + if len(i.pairResults) != 2 { + t.Fatal("expected two pair results") + } + + if i.pairResults[newNodePair(hops[0], hops[1])].resultType != + ChannelResultFail { + + t.Fatal("wrong pair result") + } + + if i.pairResults[newNodePair(hops[1], hops[2])].resultType != + ChannelResultFail { + + t.Fatal("wrong pair result") + } + + if i.finalFailure { + t.Fatal("expected attempt to be non-final") + } +} diff --git a/routing/router.go b/routing/router.go index 146e06cabd0..e15dc62b7b9 100644 --- a/routing/router.go +++ b/routing/router.go @@ -178,13 +178,20 @@ type MissionController interface { // input for future probability estimates. It returns a bool indicating // whether this error is a final error and no further payment attempts // need to be made. - ReportPaymentFail(paymentID uint64, rt *route.Route, + ReportPaymentFail(paymentID uint64, failureSourceIdx *int, failure lnwire.FailureMessage) (bool, channeldb.FailureReason, error) + // ReportPaymentSuccess reports a successful payment to mission control + // as input for future probability estimates. + ReportPaymentSuccess(paymentID uint64) error + + // ReportPaymentAttempt reports a payment attempt to mission control. + ReportPaymentInitiate(paymentID uint64, rt *route.Route) error + // GetEdgeProbability is expected to return the success probability of a // payment from fromNode along edge. - GetEdgeProbability(fromNode route.Vertex, edge EdgeLocator, + GetEdgeProbability(fromNode, toNode route.Vertex, amt lnwire.MilliSatoshi) float64 } @@ -1901,7 +1908,7 @@ func (r *ChannelRouter) processSendError(paymentID uint64, rt *route.Route, // Report outcome to mission control. final, reason, err := r.cfg.MissionControl.ReportPaymentFail( - paymentID, rt, srcIdx, msg, + paymentID, srcIdx, msg, ) if err != nil { log.Errorf("Error reporting payment result to mc: %v", diff --git a/routing/router_test.go b/routing/router_test.go index 28ab1692744..a1fa141ff25 100644 --- a/routing/router_test.go +++ b/routing/router_test.go @@ -274,7 +274,7 @@ func TestSendPaymentRouteFailureFallback(t *testing.T) { var payHash [32]byte paymentAmt := lnwire.NewMSatFromSatoshis(1000) payment := LightningPayment{ - Target: ctx.aliases["luoji"], + Target: ctx.aliases["sophon"], Amount: paymentAmt, FeeLimit: noFeeLimit, PaymentHash: payHash, @@ -284,16 +284,16 @@ func TestSendPaymentRouteFailureFallback(t *testing.T) { copy(preImage[:], bytes.Repeat([]byte{9}, 32)) // 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 + // router's configuration to ignore the path that has son goku as the // first hop. This should force the router to instead take the - // available two hop path (through satoshi). + // the more costly path (through pham nuwen). ctx.router.cfg.Payer.(*mockPaymentAttemptDispatcher).setPaymentResult( func(firstHop lnwire.ShortChannelID) ([32]byte, error) { - roasbeefLuoji := lnwire.NewShortChanIDFromInt(689530843) - if firstHop == roasbeefLuoji { + roasbeefSongoku := lnwire.NewShortChanIDFromInt(12345) + if firstHop == roasbeefSongoku { return [32]byte{}, &htlcswitch.ForwardingError{ - FailureSourceIdx: 0, + FailureSourceIdx: 1, // TODO(roasbeef): temp node failure should be? FailureMessage: &lnwire.FailTemporaryChannelFailure{}, } @@ -302,7 +302,7 @@ func TestSendPaymentRouteFailureFallback(t *testing.T) { return preImage, nil }) - // Send off the payment request to the router, route through satoshi + // Send off the payment request to the router, route through pham nuwen // should've been selected as a fall back and succeeded correctly. paymentPreImage, route, err := ctx.router.SendPayment(&payment) if err != nil { @@ -321,10 +321,10 @@ func TestSendPaymentRouteFailureFallback(t *testing.T) { preImage[:], paymentPreImage[:]) } - // The route should have satoshi as the first hop. - if route.Hops[0].PubKeyBytes != ctx.aliases["satoshi"] { + // The route should have pham nuwen as the first hop. + if route.Hops[0].PubKeyBytes != ctx.aliases["phamnuwen"] { - t.Fatalf("route should go through satoshi as first hop, "+ + t.Fatalf("route should go through phamnuwen as first hop, "+ "instead passes through: %v", getAliasFromPubKey(route.Hops[0].PubKeyBytes, ctx.aliases)) @@ -743,7 +743,7 @@ func TestSendPaymentErrorPathPruning(t *testing.T) { var payHash [32]byte paymentAmt := lnwire.NewMSatFromSatoshis(1000) payment := LightningPayment{ - Target: ctx.aliases["luoji"], + Target: ctx.aliases["sophon"], Amount: paymentAmt, FeeLimit: noFeeLimit, PaymentHash: payHash, @@ -752,32 +752,29 @@ func TestSendPaymentErrorPathPruning(t *testing.T) { var preImage [32]byte copy(preImage[:], bytes.Repeat([]byte{9}, 32)) - roasbeefLuoji := lnwire.NewShortChanIDFromInt(689530843) + roasbeefSongoku := lnwire.NewShortChanIDFromInt(12345) + roasbeefPhanNuwen := lnwire.NewShortChanIDFromInt(999991) // First, we'll modify the SendToSwitch method to return an error - // indicating that the channel from roasbeef to luoji is not operable + // indicating that the channel from roasbeef to son goku is not operable // with an UnknownNextPeer. - // - // TODO(roasbeef): filtering should be intelligent enough so just not - // go through satoshi at all at this point. ctx.router.cfg.Payer.(*mockPaymentAttemptDispatcher).setPaymentResult( func(firstHop lnwire.ShortChannelID) ([32]byte, error) { - if firstHop == roasbeefLuoji { + if firstHop == roasbeefSongoku { // We'll first simulate an error from the first - // outgoing link to simulate the channel from luo ji to - // roasbeef not having enough capacity. + // hop to simulate the channel from songoku to + // sophon not having enough capacity. return [32]byte{}, &htlcswitch.ForwardingError{ - FailureSourceIdx: 0, + FailureSourceIdx: 1, FailureMessage: &lnwire.FailTemporaryChannelFailure{}, } } - // Next, we'll create an error from satoshi to indicate - // that the luoji node is not longer online, which should - // prune out the rest of the routes. - roasbeefSatoshi := lnwire.NewShortChanIDFromInt(2340213491) - if firstHop == roasbeefSatoshi { + // Next, we'll create an error from phan nuwen to + // indicate that the sophon node is not longer online, + // which should prune out the rest of the routes. + if firstHop == roasbeefPhanNuwen { return [32]byte{}, &htlcswitch.ForwardingError{ FailureSourceIdx: 1, FailureMessage: &lnwire.FailUnknownNextPeer{}, @@ -804,15 +801,14 @@ func TestSendPaymentErrorPathPruning(t *testing.T) { ctx.router.cfg.MissionControl.(*MissionControl).ResetHistory() - // Next, we'll modify the SendToSwitch method to indicate that luo ji - // wasn't originally online. This should also halt the send all - // together as all paths contain luoji and he can't be reached. + // Next, we'll modify the SendToSwitch method to indicate that the + // connection between songoku and isn't up. ctx.router.cfg.Payer.(*mockPaymentAttemptDispatcher).setPaymentResult( func(firstHop lnwire.ShortChannelID) ([32]byte, error) { - if firstHop == roasbeefLuoji { + if firstHop == roasbeefSongoku { return [32]byte{}, &htlcswitch.ForwardingError{ - FailureSourceIdx: 0, + FailureSourceIdx: 1, FailureMessage: &lnwire.FailUnknownNextPeer{}, } } @@ -821,14 +817,14 @@ func TestSendPaymentErrorPathPruning(t *testing.T) { }) // This shouldn't return an error, as we'll make a payment attempt via - // the satoshi channel based on the assumption that there might be an - // intermittent issue with the roasbeef <-> lioji channel. + // the pham nuwen channel based on the assumption that there might be an + // intermittent issue with the songoku <-> sophon channel. paymentPreImage, rt, err := ctx.router.SendPayment(&payment) if err != nil { t.Fatalf("unable send payment: %v", err) } - // This path should go: roasbeef -> satoshi -> luoji + // This path should go: roasbeef -> pham nuwen -> sophon if len(rt.Hops) != 2 { t.Fatalf("incorrect route length: expected %v got %v", 2, len(rt.Hops)) @@ -837,9 +833,9 @@ func TestSendPaymentErrorPathPruning(t *testing.T) { t.Fatalf("incorrect preimage used: expected %x got %x", preImage[:], paymentPreImage[:]) } - if rt.Hops[0].PubKeyBytes != ctx.aliases["satoshi"] { + if rt.Hops[0].PubKeyBytes != ctx.aliases["phamnuwen"] { - t.Fatalf("route should go through satoshi as first hop, "+ + t.Fatalf("route should go through phamnuwen as first hop, "+ "instead passes through: %v", getAliasFromPubKey(rt.Hops[0].PubKeyBytes, ctx.aliases)) @@ -853,12 +849,12 @@ func TestSendPaymentErrorPathPruning(t *testing.T) { ctx.router.cfg.Payer.(*mockPaymentAttemptDispatcher).setPaymentResult( func(firstHop lnwire.ShortChannelID) ([32]byte, error) { - if firstHop == roasbeefLuoji { + if firstHop == roasbeefSongoku { // We'll first simulate an error from the first // outgoing link to simulate the channel from luo ji to // roasbeef not having enough capacity. return [32]byte{}, &htlcswitch.ForwardingError{ - FailureSourceIdx: 0, + FailureSourceIdx: 1, FailureMessage: &lnwire.FailTemporaryChannelFailure{}, } } @@ -886,9 +882,9 @@ func TestSendPaymentErrorPathPruning(t *testing.T) { } // The route should have satoshi as the first hop. - if rt.Hops[0].PubKeyBytes != ctx.aliases["satoshi"] { + if rt.Hops[0].PubKeyBytes != ctx.aliases["phamnuwen"] { - t.Fatalf("route should go through satoshi as first hop, "+ + t.Fatalf("route should go through phamnuwen as first hop, "+ "instead passes through: %v", getAliasFromPubKey(rt.Hops[0].PubKeyBytes, ctx.aliases)) @@ -2960,6 +2956,7 @@ func TestRouterPaymentStateMachine(t *testing.T) { ChainView: chainView, Control: control, SessionSource: &mockPaymentSessionSource{}, + MissionControl: &mockMissionControl{}, Payer: payer, ChannelPruneExpiry: time.Hour * 24, GraphPruneInterval: time.Hour * 2,