From f4968def241acec4a2b03c8d002fd0e81800b9c0 Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Mon, 29 Jul 2019 15:10:58 +0200 Subject: [PATCH 1/6] routing: convert to node pair based Previously mission control tracked failures on a per node, per channel basis. This commit changes this to tracking on the level of directed node pairs. The goal of moving to this coarser-grained level is to reduce the number of required payment attempts without compromising payment reliability. --- cmd/lncli/cmd_query_mission_control.go | 35 ++- lnrpc/routerrpc/router.pb.go | 319 +++++++++++++------------ lnrpc/routerrpc/router.proto | 35 ++- lnrpc/routerrpc/router_backend.go | 54 +++-- lnrpc/routerrpc/router_backend_test.go | 28 ++- lnrpc/routerrpc/router_server.go | 47 ++-- lntest/itest/lnd_test.go | 11 + routing/missioncontrol.go | 210 +++++++--------- routing/missioncontrol_test.go | 14 +- routing/mock_test.go | 8 +- routing/nodepair.go | 8 + routing/pathfind.go | 10 +- routing/pathfind_test.go | 14 +- routing/router.go | 2 +- routing/router_test.go | 1 + 15 files changed, 419 insertions(+), 377 deletions(-) diff --git a/cmd/lncli/cmd_query_mission_control.go b/cmd/lncli/cmd_query_mission_control.go index f1514beb098..d073c5f0612 100644 --- a/cmd/lncli/cmd_query_mission_control.go +++ b/cmd/lncli/cmd_query_mission_control.go @@ -32,24 +32,43 @@ func queryMissionControl(ctx *cli.Context) error { } type displayNodeHistory struct { - Pubkey string - LastFailTime int64 - OtherChanSuccessProb float32 - Channels []*routerrpc.ChannelHistory + Pubkey string + LastFailTime int64 + OtherSuccessProb float32 + } + + type displayPairHistory struct { + NodeFrom, NodeTo string + LastFailTime int64 + SuccessProb float32 + MinPenalizeAmtSat int64 } displayResp := struct { Nodes []displayNodeHistory + Pairs []displayPairHistory }{} for _, n := range snapshot.Nodes { displayResp.Nodes = append( displayResp.Nodes, displayNodeHistory{ - Pubkey: hex.EncodeToString(n.Pubkey), - LastFailTime: n.LastFailTime, - OtherChanSuccessProb: n.OtherChanSuccessProb, - Channels: n.Channels, + Pubkey: hex.EncodeToString(n.Pubkey), + LastFailTime: n.LastFailTime, + OtherSuccessProb: n.OtherSuccessProb, + }, + ) + } + + for _, n := range snapshot.Pairs { + displayResp.Pairs = append( + displayResp.Pairs, + displayPairHistory{ + NodeFrom: hex.EncodeToString(n.NodeFrom), + NodeTo: hex.EncodeToString(n.NodeTo), + LastFailTime: n.LastFailTime, + SuccessProb: n.SuccessProb, + MinPenalizeAmtSat: n.MinPenalizeAmtSat, }, ) } diff --git a/lnrpc/routerrpc/router.pb.go b/lnrpc/routerrpc/router.pb.go index cba7914d7f1..f78b464e880 100644 --- a/lnrpc/routerrpc/router.pb.go +++ b/lnrpc/routerrpc/router.pb.go @@ -979,9 +979,12 @@ func (m *QueryMissionControlRequest) XXX_DiscardUnknown() { var xxx_messageInfo_QueryMissionControlRequest proto.InternalMessageInfo -/// QueryMissionControlResponse contains mission control state per node. +/// QueryMissionControlResponse contains mission control state. type QueryMissionControlResponse struct { - Nodes []*NodeHistory `protobuf:"bytes,1,rep,name=nodes,proto3" json:"nodes,omitempty"` + /// Node-level mission control state. + Nodes []*NodeHistory `protobuf:"bytes,1,rep,name=nodes,proto3" json:"nodes,omitempty"` + /// Node pair-level mission control state. + 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,19 +1022,26 @@ 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 Pubkey []byte `protobuf:"bytes,1,opt,name=pubkey,proto3" json:"pubkey,omitempty"` /// 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:"-"` + //* + //Estimation of success probability for node pairs that are not in the pairs + //list. + OtherSuccessProb float32 `protobuf:"fixed32,3,opt,name=other_success_prob,proto3" json:"other_success_prob,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } func (m *NodeHistory) Reset() { *m = NodeHistory{} } @@ -1073,82 +1083,84 @@ func (m *NodeHistory) GetLastFailTime() int64 { return 0 } -func (m *NodeHistory) GetOtherChanSuccessProb() float32 { +func (m *NodeHistory) GetOtherSuccessProb() float32 { if m != nil { - return m.OtherChanSuccessProb + return m.OtherSuccessProb } 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"` +/// PairHistory contains the mission control state for a particular node pair. +type PairHistory struct { + /// The source node pubkey of the pair. + NodeFrom []byte `protobuf:"bytes,1,opt,name=node_from,proto3" json:"node_from,omitempty"` + /// The destination node pubkey of the pair. + NodeTo []byte `protobuf:"bytes,2,opt,name=node_to,proto3" json:"node_to,omitempty"` /// Time stamp of last failure. - LastFailTime int64 `protobuf:"varint,2,opt,name=last_fail_time,proto3" json:"last_fail_time,omitempty"` + LastFailTime int64 `protobuf:"varint,3,opt,name=last_fail_time,proto3" json:"last_fail_time,omitempty"` /// Minimum penalization amount. - MinPenalizeAmtSat int64 `protobuf:"varint,3,opt,name=min_penalize_amt_sat,proto3" json:"min_penalize_amt_sat,omitempty"` - /// Estimation of success probability for this channel. - SuccessProb float32 `protobuf:"fixed32,4,opt,name=success_prob,proto3" json:"success_prob,omitempty"` + MinPenalizeAmtSat int64 `protobuf:"varint,4,opt,name=min_penalize_amt_sat,proto3" json:"min_penalize_amt_sat,omitempty"` + /// Estimation of success probability for this pair. + SuccessProb float32 `protobuf:"fixed32,5,opt,name=success_prob,proto3" json:"success_prob,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) GetNodeFrom() []byte { if m != nil { - return m.ChannelId + return m.NodeFrom } - return 0 + return nil +} + +func (m *PairHistory) GetNodeTo() []byte { + if m != nil { + return m.NodeTo + } + return nil } -func (m *ChannelHistory) GetLastFailTime() int64 { +func (m *PairHistory) GetLastFailTime() int64 { if m != nil { return m.LastFailTime } return 0 } -func (m *ChannelHistory) GetMinPenalizeAmtSat() int64 { +func (m *PairHistory) GetMinPenalizeAmtSat() int64 { if m != nil { return m.MinPenalizeAmtSat } return 0 } -func (m *ChannelHistory) GetSuccessProb() float32 { +func (m *PairHistory) GetSuccessProb() float32 { if m != nil { return m.SuccessProb } @@ -1172,117 +1184,118 @@ 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, + // 1672 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x57, 0x5f, 0x73, 0x22, 0xc7, + 0x11, 0x37, 0x02, 0x84, 0x68, 0xfe, 0xad, 0x46, 0x3a, 0xdd, 0x1e, 0x3a, 0xd9, 0x32, 0x4e, 0xce, + 0xaa, 0x2b, 0x47, 0x72, 0x48, 0xd9, 0x95, 0xa7, 0xa4, 0x38, 0x18, 0xcc, 0xde, 0xc1, 0xae, 0x3c, + 0xc0, 0xd9, 0x97, 0x3c, 0x4c, 0x8d, 0x60, 0x04, 0x5b, 0x82, 0x5d, 0xbc, 0x3b, 0x5c, 0x4e, 0x79, + 0xc8, 0x4b, 0x2a, 0x55, 0x79, 0xc9, 0x67, 0xc9, 0x73, 0x3e, 0x40, 0xbe, 0x4a, 0xf2, 0x1d, 0xf2, + 0x94, 0x9a, 0x99, 0x5d, 0x58, 0x10, 0xba, 0xe4, 0x89, 0x9d, 0x5f, 0xff, 0xa6, 0xbb, 0xa7, 0x7b, + 0xba, 0xa7, 0x81, 0x93, 0xc0, 0x5f, 0x0a, 0x1e, 0x04, 0x8b, 0xd1, 0x95, 0xfe, 0xba, 0x5c, 0x04, + 0xbe, 0xf0, 0x51, 0x7e, 0x85, 0x57, 0xf3, 0xc1, 0x62, 0xa4, 0xd1, 0xda, 0x7f, 0xf6, 0x00, 0xf5, + 0xb9, 0x37, 0xbe, 0x66, 0xf7, 0x73, 0xee, 0x09, 0xc2, 0x7f, 0x5a, 0xf2, 0x50, 0x20, 0x04, 0x99, + 0x31, 0x0f, 0x85, 0x99, 0x3a, 0x4f, 0x5d, 0x14, 0x89, 0xfa, 0x46, 0x06, 0xa4, 0xd9, 0x5c, 0x98, + 0x7b, 0xe7, 0xa9, 0x8b, 0x34, 0x91, 0x9f, 0xe8, 0x73, 0x28, 0x2e, 0xf4, 0x3e, 0x3a, 0x65, 0xe1, + 0xd4, 0x4c, 0x2b, 0x76, 0x21, 0xc2, 0x3a, 0x2c, 0x9c, 0xa2, 0x0b, 0x30, 0x6e, 0x5d, 0x8f, 0xcd, + 0xe8, 0x68, 0x26, 0xde, 0xd3, 0x31, 0x9f, 0x09, 0x66, 0x66, 0xce, 0x53, 0x17, 0x59, 0x52, 0x56, + 0x78, 0x73, 0x26, 0xde, 0xb7, 0x24, 0x8a, 0xbe, 0x84, 0x4a, 0xac, 0x2c, 0xd0, 0x5e, 0x98, 0xd9, + 0xf3, 0xd4, 0x45, 0x9e, 0x94, 0x17, 0x9b, 0xbe, 0x7d, 0x09, 0x15, 0xe1, 0xce, 0xb9, 0xbf, 0x14, + 0x34, 0xe4, 0x23, 0xdf, 0x1b, 0x87, 0xe6, 0xbe, 0xd6, 0x18, 0xc1, 0x7d, 0x8d, 0xa2, 0x1a, 0x94, + 0x6e, 0x39, 0xa7, 0x33, 0x77, 0xee, 0x0a, 0x1a, 0x32, 0x61, 0xe6, 0x94, 0xeb, 0x85, 0x5b, 0xce, + 0xbb, 0x12, 0xeb, 0x33, 0x21, 0xfd, 0xf3, 0x97, 0x62, 0xe2, 0xbb, 0xde, 0x84, 0x8e, 0xa6, 0xcc, + 0xa3, 0xee, 0xd8, 0x3c, 0x38, 0x4f, 0x5d, 0x64, 0x48, 0x39, 0xc6, 0x9b, 0x53, 0xe6, 0x59, 0x63, + 0x74, 0x06, 0xa0, 0xce, 0xa0, 0xd4, 0x99, 0x79, 0x65, 0x31, 0x2f, 0x11, 0xa5, 0x0b, 0xd5, 0xa1, + 0xa0, 0x02, 0x4c, 0xa7, 0xae, 0x27, 0x42, 0x13, 0xce, 0xd3, 0x17, 0x85, 0xba, 0x71, 0x39, 0xf3, + 0x64, 0xac, 0x89, 0x94, 0x74, 0x5c, 0x4f, 0x90, 0x24, 0xa9, 0xf6, 0x6b, 0x38, 0x1a, 0x04, 0x6c, + 0x74, 0xb7, 0x15, 0xfc, 0xed, 0xb0, 0xa6, 0x1e, 0x84, 0xb5, 0xf6, 0x27, 0x28, 0x45, 0x9b, 0xfa, + 0x82, 0x89, 0x65, 0x88, 0x7e, 0x01, 0xd9, 0x50, 0x30, 0xc1, 0x15, 0xb9, 0x5c, 0x7f, 0x7a, 0xb9, + 0xca, 0xf6, 0x65, 0x82, 0xc8, 0x89, 0x66, 0xa1, 0x2a, 0x1c, 0x2c, 0x02, 0xee, 0xce, 0xd9, 0x84, + 0xab, 0x84, 0x16, 0xc9, 0x6a, 0x8d, 0x6a, 0x90, 0x55, 0x9b, 0x55, 0x3a, 0x0b, 0xf5, 0x62, 0xf2, + 0x0c, 0x44, 0x8b, 0x6a, 0xbf, 0x81, 0x8a, 0x5a, 0xb7, 0x39, 0xff, 0xd8, 0x95, 0x79, 0x0a, 0x39, + 0x36, 0xd7, 0xb1, 0xd7, 0xd7, 0x66, 0x9f, 0xcd, 0x65, 0xd8, 0x6b, 0x63, 0x30, 0xd6, 0xfb, 0xc3, + 0x85, 0xef, 0x85, 0x5c, 0xa6, 0x42, 0x2a, 0x97, 0x99, 0x90, 0x69, 0x9b, 0xcb, 0x5d, 0x29, 0xb5, + 0xab, 0x1c, 0xe1, 0x6d, 0xce, 0x7b, 0x21, 0x13, 0xe8, 0x85, 0xbe, 0x01, 0x74, 0xe6, 0x8f, 0xee, + 0xe4, 0x9d, 0x62, 0xf7, 0x91, 0xfa, 0x92, 0x84, 0xbb, 0xfe, 0xe8, 0xae, 0x25, 0xc1, 0xda, 0xef, + 0xf5, 0xdd, 0x1e, 0xf8, 0xda, 0xf7, 0xff, 0x3b, 0xbc, 0xeb, 0x10, 0xec, 0x3d, 0x1e, 0x02, 0x0a, + 0x47, 0x1b, 0xca, 0xa3, 0x53, 0x24, 0x23, 0x9b, 0xda, 0x8a, 0xec, 0x57, 0x90, 0xbb, 0x65, 0xee, + 0x6c, 0x19, 0xc4, 0x8a, 0x51, 0x22, 0x4d, 0x6d, 0x2d, 0x21, 0x31, 0xa5, 0xf6, 0x8f, 0x1c, 0xe4, + 0x22, 0x10, 0xd5, 0x21, 0x33, 0xf2, 0xc7, 0x71, 0x76, 0x3f, 0x7d, 0xb8, 0x2d, 0xfe, 0x6d, 0xfa, + 0x63, 0x4e, 0x14, 0x17, 0xfd, 0x16, 0xca, 0xf2, 0x46, 0x7b, 0x7c, 0x46, 0x97, 0x8b, 0x31, 0x5b, + 0x25, 0xd4, 0x4c, 0xec, 0x6e, 0x6a, 0xc2, 0x50, 0xc9, 0x49, 0x69, 0x94, 0x5c, 0xa2, 0x53, 0xc8, + 0x4f, 0xc5, 0x6c, 0xa4, 0x33, 0x91, 0x51, 0x45, 0x71, 0x20, 0x01, 0x95, 0x83, 0x1a, 0x94, 0x7c, + 0xcf, 0xf5, 0x3d, 0x1a, 0x4e, 0x19, 0xad, 0x7f, 0xf3, 0xad, 0x2a, 0xd6, 0x22, 0x29, 0x28, 0xb0, + 0x3f, 0x65, 0xf5, 0x6f, 0xbe, 0x45, 0x9f, 0x41, 0x41, 0x95, 0x0c, 0xff, 0xb0, 0x70, 0x83, 0x7b, + 0x55, 0xa5, 0x25, 0xa2, 0xaa, 0x08, 0x2b, 0x04, 0x1d, 0x43, 0xf6, 0x76, 0xc6, 0x26, 0xa1, 0xaa, + 0xcc, 0x12, 0xd1, 0x0b, 0xf4, 0x35, 0x1c, 0x47, 0x31, 0xa0, 0xa1, 0xbf, 0x0c, 0x46, 0x9c, 0xba, + 0xde, 0x98, 0x7f, 0x50, 0x75, 0x59, 0x22, 0x28, 0x92, 0xf5, 0x95, 0xc8, 0x92, 0x92, 0xda, 0x5f, + 0xb3, 0x50, 0x48, 0x04, 0x00, 0x15, 0xe1, 0x80, 0xe0, 0x3e, 0x26, 0x6f, 0x71, 0xcb, 0xf8, 0x04, + 0x99, 0x70, 0x3c, 0xb4, 0xdf, 0xd8, 0xce, 0x0f, 0x36, 0xbd, 0x6e, 0xbc, 0xeb, 0x61, 0x7b, 0x40, + 0x3b, 0x8d, 0x7e, 0xc7, 0x48, 0xa1, 0xe7, 0x60, 0x5a, 0x76, 0xd3, 0x21, 0x04, 0x37, 0x07, 0x2b, + 0x59, 0xa3, 0xe7, 0x0c, 0xed, 0x81, 0xb1, 0x87, 0x3e, 0x83, 0xd3, 0xb6, 0x65, 0x37, 0xba, 0x74, + 0xcd, 0x69, 0x76, 0x07, 0x6f, 0x29, 0xfe, 0xf1, 0xda, 0x22, 0xef, 0x8c, 0xf4, 0x2e, 0x42, 0x67, + 0xd0, 0x6d, 0xc6, 0x1a, 0x32, 0xe8, 0x19, 0x3c, 0xd1, 0x04, 0xbd, 0x85, 0x0e, 0x1c, 0x87, 0xf6, + 0x1d, 0xc7, 0x36, 0xb2, 0xe8, 0x10, 0x4a, 0x96, 0xfd, 0xb6, 0xd1, 0xb5, 0x5a, 0x94, 0xe0, 0x46, + 0xb7, 0x67, 0xec, 0xa3, 0x23, 0xa8, 0x6c, 0xf3, 0x72, 0x52, 0x45, 0xcc, 0x73, 0x6c, 0xcb, 0xb1, + 0xe9, 0x5b, 0x4c, 0xfa, 0x96, 0x63, 0x1b, 0x07, 0xe8, 0x04, 0xd0, 0xa6, 0xa8, 0xd3, 0x6b, 0x34, + 0x8d, 0x3c, 0x7a, 0x02, 0x87, 0x9b, 0xf8, 0x1b, 0xfc, 0xce, 0x00, 0x19, 0x06, 0xed, 0x18, 0x7d, + 0x85, 0xbb, 0xce, 0x0f, 0xb4, 0x67, 0xd9, 0x56, 0x6f, 0xd8, 0x33, 0x0a, 0xe8, 0x18, 0x8c, 0x36, + 0xc6, 0xd4, 0xb2, 0xfb, 0xc3, 0x76, 0xdb, 0x6a, 0x5a, 0xd8, 0x1e, 0x18, 0x45, 0x6d, 0x79, 0xd7, + 0xc1, 0x4b, 0x72, 0x43, 0xb3, 0xd3, 0xb0, 0x6d, 0xdc, 0xa5, 0x2d, 0xab, 0xdf, 0x78, 0xd5, 0xc5, + 0x2d, 0xa3, 0x8c, 0xce, 0xe0, 0xd9, 0x00, 0xf7, 0xae, 0x1d, 0xd2, 0x20, 0xef, 0x68, 0x2c, 0x6f, + 0x37, 0xac, 0xee, 0x90, 0x60, 0xa3, 0x82, 0x3e, 0x87, 0x33, 0x82, 0xbf, 0x1f, 0x5a, 0x04, 0xb7, + 0xa8, 0xed, 0xb4, 0x30, 0x6d, 0xe3, 0xc6, 0x60, 0x48, 0x30, 0xed, 0x59, 0xfd, 0xbe, 0x65, 0x7f, + 0x67, 0x18, 0xe8, 0x67, 0x70, 0xbe, 0xa2, 0xac, 0x14, 0x6c, 0xb1, 0x0e, 0xe5, 0xf9, 0xe2, 0x7c, + 0xda, 0xf8, 0xc7, 0x01, 0xbd, 0xc6, 0x98, 0x18, 0x08, 0x55, 0xe1, 0x64, 0x6d, 0x5e, 0x1b, 0x88, + 0x6c, 0x1f, 0x49, 0xd9, 0x35, 0x26, 0xbd, 0x86, 0x2d, 0x13, 0xbc, 0x21, 0x3b, 0x96, 0x6e, 0xaf, + 0x65, 0xdb, 0x6e, 0x3f, 0x41, 0xc7, 0x50, 0x89, 0xad, 0xc5, 0xe0, 0xbf, 0x72, 0xe8, 0x29, 0xa0, + 0xa1, 0x4d, 0x70, 0xa3, 0x25, 0x0f, 0xbf, 0x12, 0xfc, 0x3b, 0xf7, 0x3a, 0x73, 0xb0, 0x67, 0xa4, + 0x6b, 0x7f, 0x4f, 0x43, 0x69, 0xa3, 0xb6, 0xd0, 0x73, 0xc8, 0x87, 0xee, 0xc4, 0x63, 0x42, 0x56, + 0xbf, 0x6e, 0x0c, 0x6b, 0x40, 0x3d, 0x2e, 0x53, 0xe6, 0x7a, 0xba, 0x23, 0xe9, 0x8e, 0x9c, 0x57, + 0x88, 0xea, 0x47, 0x4f, 0x21, 0x17, 0x3f, 0x4e, 0x69, 0x55, 0x87, 0xfb, 0x23, 0xfd, 0x28, 0x3d, + 0x87, 0xbc, 0x6c, 0x79, 0xa1, 0x60, 0xf3, 0x85, 0x2a, 0xd1, 0x12, 0x59, 0x03, 0xe8, 0x0b, 0x28, + 0xcd, 0x79, 0x18, 0xb2, 0x09, 0xa7, 0xba, 0xcc, 0x40, 0x31, 0x8a, 0x11, 0xd8, 0x56, 0xd5, 0xf6, + 0x05, 0xc4, 0x65, 0x1f, 0x91, 0xb2, 0x9a, 0x14, 0x81, 0x9a, 0xb4, 0xdd, 0x71, 0x05, 0x8b, 0xaa, + 0x39, 0xd9, 0x71, 0x05, 0x43, 0x2f, 0xe1, 0x50, 0xb7, 0x0c, 0xd7, 0x73, 0xe7, 0xcb, 0xb9, 0x6e, + 0x1d, 0x39, 0xe5, 0x72, 0x45, 0xb5, 0x0e, 0x8d, 0xab, 0x0e, 0xf2, 0x0c, 0x0e, 0x6e, 0x58, 0xc8, + 0x65, 0xb3, 0x8f, 0x4a, 0x3b, 0x27, 0xd7, 0x6d, 0xce, 0xa5, 0x48, 0x3e, 0x01, 0x81, 0x6c, 0x5a, + 0x79, 0x2d, 0xba, 0xe5, 0x9c, 0xc8, 0x38, 0xae, 0x2c, 0xb0, 0x0f, 0x6b, 0x0b, 0x85, 0x84, 0x05, + 0x8d, 0x2b, 0x0b, 0x2f, 0xe1, 0x90, 0x7f, 0x10, 0x01, 0xa3, 0xfe, 0x82, 0xfd, 0xb4, 0xe4, 0x74, + 0xcc, 0x04, 0x33, 0x8b, 0x2a, 0xb8, 0x15, 0x25, 0x70, 0x14, 0xde, 0x62, 0x82, 0xd5, 0x9e, 0x43, + 0x95, 0xf0, 0x90, 0x8b, 0x9e, 0x1b, 0x86, 0xae, 0xef, 0x35, 0x7d, 0x4f, 0x04, 0xfe, 0x2c, 0x7a, + 0x33, 0x6a, 0x67, 0x70, 0xba, 0x53, 0xaa, 0x9b, 0xbe, 0xdc, 0xfc, 0xfd, 0x92, 0x07, 0xf7, 0xbb, + 0x37, 0xdf, 0xc3, 0xe9, 0x4e, 0x69, 0xf4, 0x62, 0x7c, 0x05, 0x59, 0xcf, 0x1f, 0xf3, 0xd0, 0x4c, + 0xa9, 0x99, 0xe1, 0x24, 0xd1, 0x9e, 0x6d, 0x7f, 0xcc, 0x3b, 0x6e, 0x28, 0xfc, 0xe0, 0x9e, 0x68, + 0x92, 0x64, 0x2f, 0x98, 0x1b, 0x84, 0xe6, 0xde, 0x03, 0xf6, 0x35, 0x73, 0x83, 0x15, 0x5b, 0x91, + 0x6a, 0x7f, 0x4e, 0x41, 0x21, 0xa1, 0x04, 0x9d, 0xc0, 0xfe, 0x62, 0x79, 0x73, 0xc7, 0xef, 0xa3, + 0x2b, 0x18, 0xad, 0xd0, 0x0b, 0x28, 0xcf, 0x58, 0x28, 0xa8, 0xec, 0xad, 0x54, 0xa6, 0x34, 0x7a, + 0x50, 0xb7, 0x50, 0x74, 0x09, 0xc8, 0x17, 0x53, 0x1e, 0xd0, 0x70, 0x39, 0x1a, 0xf1, 0x30, 0xa4, + 0x8b, 0xc0, 0xbf, 0x51, 0x77, 0x72, 0x8f, 0xec, 0x90, 0xbc, 0xce, 0x1c, 0x64, 0x8c, 0x6c, 0xed, + 0x9f, 0x29, 0x28, 0x24, 0x9c, 0x93, 0xb7, 0x56, 0x1e, 0x86, 0xde, 0x06, 0xfe, 0x3c, 0xae, 0x85, + 0x15, 0x80, 0x4c, 0xc8, 0xa9, 0x85, 0xf0, 0xa3, 0x42, 0x88, 0x97, 0x3b, 0xbc, 0x4c, 0xef, 0xf4, + 0xb2, 0x0e, 0xc7, 0x73, 0xd7, 0xa3, 0x0b, 0xee, 0xb1, 0x99, 0xfb, 0x47, 0x4e, 0xe3, 0x19, 0x24, + 0xa3, 0xd8, 0x3b, 0x65, 0xa8, 0x06, 0xc5, 0x8d, 0x33, 0x65, 0xd5, 0x99, 0x36, 0xb0, 0x97, 0x7f, + 0x4b, 0x41, 0x31, 0x39, 0x4d, 0xa1, 0x12, 0xe4, 0x2d, 0x9b, 0xb6, 0xbb, 0xd6, 0x77, 0x9d, 0x81, + 0xf1, 0x89, 0x5c, 0xf6, 0x87, 0xcd, 0x26, 0xc6, 0x2d, 0xdc, 0x32, 0x52, 0x08, 0x41, 0x59, 0x36, + 0x06, 0xdc, 0xa2, 0x03, 0xab, 0x87, 0x9d, 0xa1, 0x7c, 0x53, 0x8e, 0xa0, 0x12, 0x61, 0xb6, 0x43, + 0x89, 0x33, 0x1c, 0x60, 0x23, 0x8d, 0x0c, 0x28, 0x46, 0x20, 0x26, 0xc4, 0x21, 0x46, 0x46, 0x36, + 0xc2, 0x08, 0x79, 0xf8, 0x3e, 0xb5, 0xf0, 0xa0, 0x61, 0x75, 0xfb, 0x46, 0xb6, 0xfe, 0x97, 0x0c, + 0xec, 0xab, 0xe9, 0x23, 0x40, 0x1d, 0x28, 0x24, 0xc6, 0x78, 0x74, 0x96, 0xb8, 0x16, 0x0f, 0xc7, + 0xfb, 0xaa, 0xb9, 0x7b, 0x3c, 0x5c, 0x86, 0x5f, 0xa7, 0xd0, 0x6b, 0x28, 0x26, 0x87, 0x52, 0x94, + 0x1c, 0x36, 0x76, 0x4c, 0xab, 0x1f, 0xd5, 0xf5, 0x06, 0x0c, 0x1c, 0x0a, 0x77, 0x2e, 0x87, 0x8b, + 0x68, 0xdc, 0x43, 0xd5, 0x04, 0x7f, 0x6b, 0x86, 0xac, 0x9e, 0xee, 0x94, 0x45, 0x75, 0xd2, 0xd5, + 0x47, 0x8c, 0x06, 0xae, 0x07, 0x47, 0xdc, 0x9c, 0xf2, 0xaa, 0x9f, 0x3e, 0x26, 0x8e, 0xb4, 0x8d, + 0xe1, 0x68, 0x47, 0x45, 0xa3, 0x9f, 0x27, 0x3d, 0x78, 0xb4, 0x1f, 0x54, 0x5f, 0xfc, 0x2f, 0xda, + 0xda, 0xca, 0x8e, 0xd2, 0xdf, 0xb0, 0xf2, 0x78, 0xe3, 0xd8, 0xb0, 0xf2, 0x91, 0x0e, 0xf2, 0xea, + 0x97, 0xbf, 0xbb, 0x9a, 0xb8, 0x62, 0xba, 0xbc, 0xb9, 0x1c, 0xf9, 0xf3, 0xab, 0x99, 0x3b, 0x99, + 0x0a, 0xcf, 0xf5, 0x26, 0x1e, 0x17, 0x7f, 0xf0, 0x83, 0xbb, 0xab, 0x99, 0x37, 0xbe, 0x52, 0x03, + 0xec, 0xd5, 0x4a, 0xdd, 0xcd, 0xbe, 0xfa, 0xfb, 0xf7, 0xab, 0xff, 0x06, 0x00, 0x00, 0xff, 0xff, + 0xba, 0x51, 0xff, 0x1a, 0x2e, 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..765ed1c73c2 100644 --- a/lnrpc/routerrpc/router.proto +++ b/lnrpc/routerrpc/router.proto @@ -323,9 +323,13 @@ message ResetMissionControlResponse{} message QueryMissionControlRequest {} -/// QueryMissionControlResponse contains mission control state per node. +/// QueryMissionControlResponse contains mission control state. message QueryMissionControlResponse { + /// Node-level mission control state. repeated NodeHistory nodes = 1; + + /// Node pair-level mission control state. + repeated PairHistory pairs = 2; } /// NodeHistory contains the mission control state for a particular node. @@ -336,26 +340,31 @@ message NodeHistory { /// Time stamp of last failure. Set to zero if no failure happened yet. int64 last_fail_time = 2 [json_name = "last_fail_time"]; - /// Estimation of success probability for channels not in the channel array. - float other_chan_success_prob = 3 [json_name = "other_chan_success_prob"]; + /** + Estimation of success probability for node pairs that are not in the pairs + list. + **/ + float other_success_prob = 3 [json_name = "other_success_prob"]; - /// Historical information of particular channels. - repeated ChannelHistory channels = 4 [json_name = "channels"]; + reserved 4; } -/// NodeHistory contains the mission control state for a particular channel. -message ChannelHistory { - /// Short channel id - uint64 channel_id = 1 [json_name = "channel_id"]; +/// PairHistory contains the mission control state for a particular node pair. +message PairHistory { + /// The source node pubkey of the pair. + bytes node_from = 1 [json_name="node_from"]; + + /// The destination node pubkey of the pair. + bytes node_to = 2 [json_name="node_to"]; /// Time stamp of last failure. - int64 last_fail_time = 2 [json_name = "last_fail_time"]; + int64 last_fail_time = 3 [json_name = "last_fail_time"]; /// Minimum penalization amount. - int64 min_penalize_amt_sat = 3 [json_name = "min_penalize_amt_sat"]; + int64 min_penalize_amt_sat = 4 [json_name = "min_penalize_amt_sat"]; - /// Estimation of success probability for this channel. - float success_prob = 4 [json_name = "success_prob"]; + /// Estimation of success probability for this pair. + float success_prob = 5 [json_name = "success_prob"]; } service Router { diff --git a/lnrpc/routerrpc/router_backend.go b/lnrpc/routerrpc/router_backend.go index d4d1d8e274d..faba3eea9eb 100644 --- a/lnrpc/routerrpc/router_backend.go +++ b/lnrpc/routerrpc/router_backend.go @@ -58,9 +58,9 @@ type RouterBackend struct { // MissionControl defines the mission control dependencies of routerrpc. 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 + // from fromNode to toNode. + 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,32 @@ 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, - } - if ignoredEdge.DirectionReverse { - locator.Direction = 1 + pair, err := r.rpcEdgeToPair(ignoredEdge) + if err != nil { + log.Warnf("Ignore channel %v skipped: %v", + ignoredEdge.ChannelId, err) + + continue } - 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 +176,7 @@ func (r *RouterBackend) QueryRoutes(ctx context.Context, } return r.MissionControl.GetEdgeProbability( - node, edge, amt, + fromNode, toNode, amt, ) }, } @@ -211,6 +215,26 @@ func (r *RouterBackend) QueryRoutes(ctx context.Context, return routeResp, nil } +// rpcEdgeToPair looks up the provided channel and returns the channel endpoints +// as a directed pair. +func (r *RouterBackend) rpcEdgeToPair(e *lnrpc.EdgeLocator) ( + routing.DirectedNodePair, error) { + + a, b, err := r.FetchChannelEndpoints(e.ChannelId) + if err != nil { + return routing.DirectedNodePair{}, err + } + + var pair routing.DirectedNodePair + if e.DirectionReverse { + pair.From, pair.To = b, a + } else { + pair.From, pair.To = a, b + } + + return pair, nil +} + // calculateFeeLimit returns the fee limit in millisatoshis. If a percentage // based fee limit has been requested, we'll factor in the ratio provided with // the amount of the payment. 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..3b48c8f2ba9 100644 --- a/lnrpc/routerrpc/router_server.go +++ b/lnrpc/routerrpc/router_server.go @@ -451,40 +451,43 @@ 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(), + OtherSuccessProb: float32( + node.OtherSuccessProb, + ), } - var lastFail int64 - if node.LastFail != nil { - lastFail = node.LastFail.Unix() - } + rpcNodes = append(rpcNodes, &rpcNode) + } - rpcNodes[i] = &NodeHistory{ - Pubkey: node.Node[:], - LastFailTime: lastFail, - OtherChanSuccessProb: float32( - node.OtherChanSuccessProb, + rpcPairs := make([]*PairHistory, 0, len(snapshot.Pairs)) + for _, p := range snapshot.Pairs { + // Prevent binding to loop variable + pair := p + + rpcPair := PairHistory{ + NodeFrom: pair.Pair.From[:], + NodeTo: pair.Pair.To[:], + LastFailTime: pair.LastFail.Unix(), + MinPenalizeAmtSat: int64( + pair.MinPenalizeAmt.ToSatoshis(), ), - Channels: channels, + SuccessProb: float32(pair.SuccessProb), } + + rpcPairs = append(rpcPairs, &rpcPair) } response := QueryMissionControlResponse{ Nodes: rpcNodes, + Pairs: rpcPairs, } return &response, nil diff --git a/lntest/itest/lnd_test.go b/lntest/itest/lnd_test.go index 53674d5a466..5e1d6680870 100644 --- a/lntest/itest/lnd_test.go +++ b/lntest/itest/lnd_test.go @@ -9543,6 +9543,17 @@ func testBidirectionalAsyncPayments(net *lntest.NetworkHarness, t *harnessTest) "timeout: %v", err) } + // Reset mission control to prevent previous payment results from + // interfering with this test. A new channel has been opened, but + // mission control operates on node pairs. + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + _, err = net.Alice.RouterClient.ResetMissionControl( + ctxt, &routerrpc.ResetMissionControlRequest{}, + ) + if err != nil { + t.Fatalf("unable to reset mc for alice: %v", err) + } + // Open up a payment streams to Alice and to Bob, that we'll use to // send payment between nodes. ctx, cancel := context.WithTimeout(ctxb, lntest.AsyncBenchmarkTimeout) diff --git a/routing/missioncontrol.go b/routing/missioncontrol.go index e85fb19af62..3a690bc9fdd 100644 --- a/routing/missioncontrol.go +++ b/routing/missioncontrol.go @@ -55,7 +55,11 @@ 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 + // lastPairFailure tracks the last payment failure per node pair. + lastPairFailure map[DirectedNodePair]pairFailure + + // 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. @@ -93,22 +97,10 @@ 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 - - // 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 +// pairFailure describes a payment failure for a node pair. +type pairFailure struct { + // timestamp is the time when this failure result was obtained. + timestamp time.Time // minPenalizeAmt is the minimum amount for which to take this failure // into account. @@ -120,6 +112,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 []MissionControlPairSnapshot } // MissionControlNodeSnapshot contains a snapshot of the current node state in @@ -129,22 +125,18 @@ 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 + // OtherSuccessProb is the success probability for channels not in // the Channels slice. - OtherChanSuccessProb float64 + OtherSuccessProb float64 } -// MissionControlChannelSnapshot contains a snapshot of the current channel +// MissionControlPairSnapshot contains a snapshot of the current node pair // state in mission control. -type MissionControlChannelSnapshot struct { - // ChannelID is the short channel id of the snapshot. - ChannelID uint64 +type MissionControlPairSnapshot struct { + // Pair is the node pair of which the state is described. + Pair DirectedNodePair // LastFail is the time of last failure. LastFail time.Time @@ -182,7 +174,8 @@ func NewMissionControl(db *bbolt.DB, cfg *MissionControlConfig) ( } mc := &MissionControl{ - history: make(map[route.Vertex]*nodeHistory), + lastPairFailure: make(map[DirectedNodePair]pairFailure), + lastNodeFailure: make(map[route.Vertex]time.Time), lastSecondChance: make(map[DirectedNodePair]time.Time), now: time.Now, cfg: cfg, @@ -227,7 +220,8 @@ func (m *MissionControl) ResetHistory() error { return err } - m.history = make(map[route.Vertex]*nodeHistory) + m.lastPairFailure = make(map[DirectedNodePair]pairFailure) + m.lastNodeFailure = make(map[route.Vertex]time.Time) m.lastSecondChance = make(map[DirectedNodePair]time.Time) log.Debugf("Mission control history cleared") @@ -237,57 +231,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 +258,30 @@ 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 := NewDirectedNodePair(fromNode, toNode) + lastPairResult, lastPairResultExists := m.lastPairFailure[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) { + if amt >= lastPairResult.minPenalizeAmt { + 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,21 +313,6 @@ 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 - } - - node := &nodeHistory{ - channelLastFail: make(map[uint64]*channelHistory), - } - m.history[vertex] = node - - return node -} - // reportVertexFailure reports a node level failure. func (m *MissionControl) reportVertexFailure(timestamp time.Time, v route.Vertex) { @@ -354,8 +322,7 @@ func (m *MissionControl) reportVertexFailure(timestamp time.Time, m.Lock() defer m.Unlock() - history := m.createHistoryIfNotExists(v) - history.lastFail = ×tamp + m.lastNodeFailure[v] = timestamp } // reportEdgePolicyFailure reports a policy related failure. @@ -374,8 +341,7 @@ func (m *MissionControl) reportEdgePolicyFailure(timestamp time.Time, return } - history := m.createHistoryIfNotExists(failedEdge.from) - history.lastFail = ×tamp + m.lastNodeFailure[failedEdge.from] = timestamp } // reportEdgeFailure reports a channel level failure. @@ -390,10 +356,10 @@ func (m *MissionControl) reportEdgeFailure(timestamp time.Time, failedEdge edge, m.Lock() defer m.Unlock() - history := m.createHistoryIfNotExists(failedEdge.from) - history.channelLastFail[failedEdge.channel] = &channelHistory{ - lastFail: timestamp, + pair := NewDirectedNodePair(failedEdge.from, failedEdge.to) + m.lastPairFailure[pair] = pairFailure{ minPenalizeAmt: minPenalizeAmt, + timestamp: timestamp, } } @@ -404,46 +370,42 @@ func (m *MissionControl) GetHistorySnapshot() *MissionControlSnapshot { defer m.Unlock() log.Debugf("Requesting history snapshot from mission control: "+ - "node_count=%v", len(m.history)) + "node_failure_count=%v, pair_result_count=%v", + len(m.lastNodeFailure), len(m.lastPairFailure)) + + nodes := make([]MissionControlNodeSnapshot, 0, len(m.lastNodeFailure)) + for v, h := range m.lastNodeFailure { + otherProb := m.getEdgeProbabilityForNode(v, route.Vertex{}, 0) + + nodes = append(nodes, MissionControlNodeSnapshot{ + Node: v, + LastFail: h, + OtherSuccessProb: otherProb, + }) + } - nodes := make([]MissionControlNodeSnapshot, 0, len(m.history)) + pairs := make([]MissionControlPairSnapshot, 0, len(m.lastPairFailure)) - for v, h := range m.history { - channelSnapshot := make([]MissionControlChannelSnapshot, 0, - len(h.channelLastFail), + for v, h := range m.lastPairFailure { + // Show probability assuming amount meets min + // penalization amount. + prob := m.getEdgeProbabilityForNode( + v.From, v.To, h.minPenalizeAmt, ) - 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, - }, - ) + pair := MissionControlPairSnapshot{ + Pair: v, + MinPenalizeAmt: h.minPenalizeAmt, + LastFail: h.timestamp, + SuccessProb: prob, } - otherProb := m.getEdgeProbabilityForNode(h, 0, 0) - - nodes = append(nodes, - MissionControlNodeSnapshot{ - Node: v, - LastFail: h.lastFail, - OtherChanSuccessProb: otherProb, - Channels: channelSnapshot, - }, - ) + pairs = append(pairs, pair) } snapshot := MissionControlSnapshot{ Nodes: nodes, + Pairs: pairs, } return &snapshot diff --git a/routing/missioncontrol_test.go b/routing/missioncontrol_test.go index 798161c9255..2133f288ab2 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) } } @@ -175,7 +170,7 @@ 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") } } @@ -184,7 +179,6 @@ func TestMissionControl(t *testing.T) { // 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..5fd1072421d 100644 --- a/routing/mock_test.go +++ b/routing/mock_test.go @@ -113,7 +113,7 @@ func (m *mockMissionControl) ReportEdgePolicyFailure(failedEdge edge) {} func (m *mockMissionControl) ReportVertexFailure(v route.Vertex) {} -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 +138,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..8a8cb3302fe 100644 --- a/routing/nodepair.go +++ b/routing/nodepair.go @@ -8,3 +8,11 @@ import ( type DirectedNodePair struct { From, To route.Vertex } + +// NewDirectedNodePair instantiates a new DirectedNodePair struct. +func NewDirectedNodePair(from, to route.Vertex) DirectedNodePair { + return DirectedNodePair{ + From: from, + To: to, + } +} diff --git a/routing/pathfind.go b/routing/pathfind.go index 91b40371fe8..b82ef4659f0 100644 --- a/routing/pathfind.go +++ b/routing/pathfind.go @@ -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/router.go b/routing/router.go index 146e06cabd0..cd28d3ec5df 100644 --- a/routing/router.go +++ b/routing/router.go @@ -184,7 +184,7 @@ type MissionController interface { // 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 } diff --git a/routing/router_test.go b/routing/router_test.go index 28ab1692744..e27914e92cb 100644 --- a/routing/router_test.go +++ b/routing/router_test.go @@ -2960,6 +2960,7 @@ func TestRouterPaymentStateMachine(t *testing.T) { ChainView: chainView, Control: control, SessionSource: &mockPaymentSessionSource{}, + MissionControl: &mockMissionControl{}, Payer: payer, ChannelPruneExpiry: time.Hour * 24, GraphPruneInterval: time.Hour * 2, From 1e160b8d13d967b89f6d99dbedc411c6de60a24e Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Tue, 18 Jun 2019 19:42:51 +0200 Subject: [PATCH 2/6] routing/test: do not test local channel mission control This commit updates existing tests to not rely on mission control for pruning of local channels. Information about local channels should already be up to date before path finding starts. If not, the problem should be fixed where bandwidth hints are set up. --- routing/router_test.go | 74 ++++++++++++++++++++---------------------- 1 file changed, 35 insertions(+), 39 deletions(-) diff --git a/routing/router_test.go b/routing/router_test.go index e27914e92cb..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)) From 85bd6eb3170f740bba3ee258d7dd6394f97a9f11 Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Tue, 25 Jun 2019 10:51:55 +0200 Subject: [PATCH 3/6] routing: improved payment result interpretation --- routing/missioncontrol.go | 306 ++--------------------- routing/nodepair.go | 5 + routing/result_interpretation.go | 347 ++++++++++++++++++++++++++ routing/result_interpretation_test.go | 91 +++++++ 4 files changed, 470 insertions(+), 279 deletions(-) create mode 100644 routing/result_interpretation.go create mode 100644 routing/result_interpretation_test.go diff --git a/routing/missioncontrol.go b/routing/missioncontrol.go index 3a690bc9fdd..01998323847 100644 --- a/routing/missioncontrol.go +++ b/routing/missioncontrol.go @@ -313,56 +313,6 @@ func (m *MissionControl) requestSecondChance(timestamp time.Time, return false } -// reportVertexFailure reports a node level failure. -func (m *MissionControl) reportVertexFailure(timestamp time.Time, - v route.Vertex) { - - log.Debugf("Reporting vertex %v failure to Mission Control", v) - - m.Lock() - defer m.Unlock() - - m.lastNodeFailure[v] = timestamp -} - -// reportEdgePolicyFailure reports a policy related failure. -func (m *MissionControl) reportEdgePolicyFailure(timestamp time.Time, - failedEdge edge) { - - m.Lock() - defer m.Unlock() - - // 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 - } - - m.lastNodeFailure[failedEdge.from] = timestamp -} - -// 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) - - m.Lock() - defer m.Unlock() - - pair := NewDirectedNodePair(failedEdge.from, failedEdge.to) - m.lastPairFailure[pair] = pairFailure{ - minPenalizeAmt: minPenalizeAmt, - timestamp: timestamp, - } -} - // GetHistorySnapshot takes a snapshot from the current mission control state // and actual probability estimates. func (m *MissionControl) GetHistorySnapshot() *MissionControlSnapshot { @@ -451,245 +401,43 @@ 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.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 - } + log.Debugf("Interpretation: %v", i) - var failureVertex route.Vertex + // Update mission control state using the interpretation. + m.Lock() + defer m.Unlock() - if failureSourceIdxInt > 0 { - failureVertex = result.route.Hops[failureSourceIdxInt-1].PubKeyBytes - } else { - failureVertex = result.route.SourcePubKey + if i.policyFailure != nil { + if m.requestSecondChance( + result.timeReply, + i.policyFailure.From, i.policyFailure.To, + ) { + return false, 0 + } } - 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, - ) + for node := range i.nodeFailures { + log.Debugf("Reporting node failure to Mission Control: "+ + "node=%v", node) - 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 + m.lastNodeFailure[node] = result.timeReply } -} -// 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) { + for pair, minPenalizeAmt := range i.pairResults { + log.Debugf("Reporting pair failure to Mission Control: "+ + "pair=%v, minPenalizeAmt=%v", pair, minPenalizeAmt) - // 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-- - } - - // 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 + m.lastPairFailure[pair] = pairFailure{ + minPenalizeAmt: minPenalizeAmt, + 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/nodepair.go b/routing/nodepair.go index 8a8cb3302fe..3ca1f828490 100644 --- a/routing/nodepair.go +++ b/routing/nodepair.go @@ -16,3 +16,8 @@ func NewDirectedNodePair(from, to route.Vertex) DirectedNodePair { To: to, } } + +// Reverse reverses the pair direction. +func (d DirectedNodePair) Reverse() DirectedNodePair { + return DirectedNodePair{From: d.To, To: d.From} +} diff --git a/routing/result_interpretation.go b/routing/result_interpretation.go new file mode 100644 index 00000000000..1c2d70c07fb --- /dev/null +++ b/routing/result_interpretation.go @@ -0,0 +1,347 @@ +package routing + +import ( + "fmt" + "strings" + + "github.com/lightningnetwork/lnd/channeldb" + + "github.com/lightningnetwork/lnd/lnwire" + "github.com/lightningnetwork/lnd/routing/route" +) + +type interpretedResult struct { + nodeFailures map[route.Vertex]struct{} + pairResults map[DirectedNodePair]lnwire.MilliSatoshi + finalFailure bool + failureReason channeldb.FailureReason + policyFailure *DirectedNodePair +} + +func newInterpretedResult(rt *route.Route, failureSrcIdx *int, + failure lnwire.FailureMessage) *interpretedResult { + + i := &interpretedResult{ + nodeFailures: make(map[route.Vertex]struct{}), + pairResults: make(map[DirectedNodePair]lnwire.MilliSatoshi), + } + + i.processFail(rt, failureSrcIdx, failure) + + return i +} + +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.failPair(route, n-1) + + // We are using wrong payment hash or amount, fail the payment. + case *lnwire.FailIncorrectPaymentAmount, + *lnwire.FailUnknownPaymentHash: + + 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) + + 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() { + i.failPair( + route, errorSourceIdx, + ) + } + + reportOutgoingBalance := func() { + i.failPairBalance( + route, errorSourceIdx, + ) + } + + 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.failPair( + route, errorSourceIdx-1, + ) + } + + 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.failPairRange( + route, 1, errorSourceIdx-1, + ) + } + + 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() + + // 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() + + // 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() + + // 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: + reportOutgoingBalance() + + // 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.failPairRange(route, 1, n-1) +} + +func (i *interpretedResult) failPairRange( + rt *route.Route, fromIdx, toIdx int) { + + // Start at one because we don't penalize our own channels. + for idx := fromIdx; idx <= toIdx; idx++ { + i.failPair(rt, idx) + } +} + +// reportChannelFailure reports a bidirectional failure of a channel. +func (i *interpretedResult) failPair( + rt *route.Route, channelIdx int) { + + pair, _ := getPair(rt, channelIdx) + + // Report pair in both directions without a minimum penalization amount. + i.pairResults[pair] = 0 + i.pairResults[pair.Reverse()] = 0 +} + +func (i *interpretedResult) failPairBalance( + rt *route.Route, channelIdx int) { + + pair, amt := getPair(rt, channelIdx) + + i.pairResults[pair] = amt +} + +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)", p.From[:6], p.To[:6], r.ToSatoshis(), + )) + } + + return b.String() +} + +// getPair returns a node pair from the route and the amount passed between that +// pair. +func getPair(rt *route.Route, channelIdx int) (DirectedNodePair, + lnwire.MilliSatoshi) { + + nodeTo := rt.Hops[channelIdx].PubKeyBytes + var ( + nodeFrom route.Vertex + amt lnwire.MilliSatoshi + ) + + if channelIdx == 0 { + nodeFrom = rt.SourcePubKey + amt = rt.TotalAmount + } else { + nodeFrom = rt.Hops[channelIdx-1].PubKeyBytes + amt = rt.Hops[channelIdx-1].AmtToForward + } + + pair := NewDirectedNodePair(nodeFrom, nodeTo) + + return pair, amt +} diff --git a/routing/result_interpretation_test.go b/routing/result_interpretation_test.go new file mode 100644 index 00000000000..27b32ec12ba --- /dev/null +++ b/routing/result_interpretation_test.go @@ -0,0 +1,91 @@ +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], AmtToForward: 100}, + &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 TestResultInterpretationFail(t *testing.T) { + failureSrcIdx := 1 + i := newInterpretedResult( + &routeTwoHop, &failureSrcIdx, + lnwire.NewTemporaryChannelFailure(nil), + ) + + if len(i.pairResults) != 1 { + t.Fatal("expected one pair result") + } + + if i.pairResults[NewDirectedNodePair(hops[0], hops[1])] != 100 { + 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, &failureSrcIdx, + lnwire.NewExpiryTooSoon(lnwire.ChannelUpdate{}), + ) + + if len(i.pairResults) != 4 { + t.Fatalf("expected 4 pair results, but got %v", + len(i.pairResults)) + } + + if i.pairResults[NewDirectedNodePair(hops[0], hops[1])] != 0 { + t.Fatal("wrong pair result") + } + + if i.pairResults[NewDirectedNodePair(hops[1], hops[2])] != 0 { + t.Fatal("wrong pair result") + } + + if i.finalFailure { + t.Fatal("expected attempt to be non-final") + } +} From 64dcce765c276ca39a313c2c964ad33275297cfb Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Mon, 29 Jul 2019 14:20:06 +0200 Subject: [PATCH 4/6] routing: process successes --- cmd/lncli/cmd_query_mission_control.go | 4 +- lnrpc/routerrpc/router.pb.go | 236 +++++++++++++------------ lnrpc/routerrpc/router.proto | 5 +- lnrpc/routerrpc/router_server.go | 9 +- routing/missioncontrol.go | 80 ++++++--- routing/missioncontrol_test.go | 20 +++ routing/mock_test.go | 10 +- routing/pathfind.go | 2 +- routing/payment_lifecycle.go | 9 + routing/result_interpretation.go | 79 ++++++++- routing/result_interpretation_test.go | 36 +++- routing/router.go | 2 + 12 files changed, 331 insertions(+), 161 deletions(-) diff --git a/cmd/lncli/cmd_query_mission_control.go b/cmd/lncli/cmd_query_mission_control.go index d073c5f0612..d7fe53cbb96 100644 --- a/cmd/lncli/cmd_query_mission_control.go +++ b/cmd/lncli/cmd_query_mission_control.go @@ -39,7 +39,7 @@ func queryMissionControl(ctx *cli.Context) error { type displayPairHistory struct { NodeFrom, NodeTo string - LastFailTime int64 + Timestamp int64 SuccessProb float32 MinPenalizeAmtSat int64 } @@ -66,7 +66,7 @@ func queryMissionControl(ctx *cli.Context) error { displayPairHistory{ NodeFrom: hex.EncodeToString(n.NodeFrom), NodeTo: hex.EncodeToString(n.NodeTo), - LastFailTime: n.LastFailTime, + Timestamp: n.Timestamp, SuccessProb: n.SuccessProb, MinPenalizeAmtSat: n.MinPenalizeAmtSat, }, diff --git a/lnrpc/routerrpc/router.pb.go b/lnrpc/routerrpc/router.pb.go index f78b464e880..9698a115a6f 100644 --- a/lnrpc/routerrpc/router.pb.go +++ b/lnrpc/routerrpc/router.pb.go @@ -1097,14 +1097,16 @@ type PairHistory struct { /// The destination node pubkey of the pair. NodeTo []byte `protobuf:"bytes,2,opt,name=node_to,proto3" json:"node_to,omitempty"` /// Time stamp of last failure. - LastFailTime int64 `protobuf:"varint,3,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,4,opt,name=min_penalize_amt_sat,proto3" json:"min_penalize_amt_sat,omitempty"` /// Estimation of success probability for this pair. - SuccessProb float32 `protobuf:"fixed32,5,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"` + /// Whether the last payment attempt through this pair was successful. + LastAttemptSuccessful bool `protobuf:"varint,6,opt,name=last_attempt_successful,proto3" json:"last_attempt_successful,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } func (m *PairHistory) Reset() { *m = PairHistory{} } @@ -1146,9 +1148,9 @@ func (m *PairHistory) GetNodeTo() []byte { return nil } -func (m *PairHistory) GetLastFailTime() int64 { +func (m *PairHistory) GetTimestamp() int64 { if m != nil { - return m.LastFailTime + return m.Timestamp } return 0 } @@ -1167,6 +1169,13 @@ func (m *PairHistory) GetSuccessProb() float32 { return 0 } +func (m *PairHistory) GetLastAttemptSuccessful() bool { + if m != nil { + return m.LastAttemptSuccessful + } + return false +} + func init() { proto.RegisterEnum("routerrpc.PaymentState", PaymentState_name, PaymentState_value) proto.RegisterEnum("routerrpc.Failure_FailureCode", Failure_FailureCode_name, Failure_FailureCode_value) @@ -1190,112 +1199,113 @@ func init() { func init() { proto.RegisterFile("routerrpc/router.proto", fileDescriptor_7a0613f69d37b0a5) } var fileDescriptor_7a0613f69d37b0a5 = []byte{ - // 1672 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x57, 0x5f, 0x73, 0x22, 0xc7, - 0x11, 0x37, 0x02, 0x84, 0x68, 0xfe, 0xad, 0x46, 0x3a, 0xdd, 0x1e, 0x3a, 0xd9, 0x32, 0x4e, 0xce, - 0xaa, 0x2b, 0x47, 0x72, 0x48, 0xd9, 0x95, 0xa7, 0xa4, 0x38, 0x18, 0xcc, 0xde, 0xc1, 0xae, 0x3c, - 0xc0, 0xd9, 0x97, 0x3c, 0x4c, 0x8d, 0x60, 0x04, 0x5b, 0x82, 0x5d, 0xbc, 0x3b, 0x5c, 0x4e, 0x79, - 0xc8, 0x4b, 0x2a, 0x55, 0x79, 0xc9, 0x67, 0xc9, 0x73, 0x3e, 0x40, 0xbe, 0x4a, 0xf2, 0x1d, 0xf2, - 0x94, 0x9a, 0x99, 0x5d, 0x58, 0x10, 0xba, 0xe4, 0x89, 0x9d, 0x5f, 0xff, 0xa6, 0xbb, 0xa7, 0x7b, - 0xba, 0xa7, 0x81, 0x93, 0xc0, 0x5f, 0x0a, 0x1e, 0x04, 0x8b, 0xd1, 0x95, 0xfe, 0xba, 0x5c, 0x04, - 0xbe, 0xf0, 0x51, 0x7e, 0x85, 0x57, 0xf3, 0xc1, 0x62, 0xa4, 0xd1, 0xda, 0x7f, 0xf6, 0x00, 0xf5, - 0xb9, 0x37, 0xbe, 0x66, 0xf7, 0x73, 0xee, 0x09, 0xc2, 0x7f, 0x5a, 0xf2, 0x50, 0x20, 0x04, 0x99, - 0x31, 0x0f, 0x85, 0x99, 0x3a, 0x4f, 0x5d, 0x14, 0x89, 0xfa, 0x46, 0x06, 0xa4, 0xd9, 0x5c, 0x98, - 0x7b, 0xe7, 0xa9, 0x8b, 0x34, 0x91, 0x9f, 0xe8, 0x73, 0x28, 0x2e, 0xf4, 0x3e, 0x3a, 0x65, 0xe1, - 0xd4, 0x4c, 0x2b, 0x76, 0x21, 0xc2, 0x3a, 0x2c, 0x9c, 0xa2, 0x0b, 0x30, 0x6e, 0x5d, 0x8f, 0xcd, - 0xe8, 0x68, 0x26, 0xde, 0xd3, 0x31, 0x9f, 0x09, 0x66, 0x66, 0xce, 0x53, 0x17, 0x59, 0x52, 0x56, - 0x78, 0x73, 0x26, 0xde, 0xb7, 0x24, 0x8a, 0xbe, 0x84, 0x4a, 0xac, 0x2c, 0xd0, 0x5e, 0x98, 0xd9, - 0xf3, 0xd4, 0x45, 0x9e, 0x94, 0x17, 0x9b, 0xbe, 0x7d, 0x09, 0x15, 0xe1, 0xce, 0xb9, 0xbf, 0x14, - 0x34, 0xe4, 0x23, 0xdf, 0x1b, 0x87, 0xe6, 0xbe, 0xd6, 0x18, 0xc1, 0x7d, 0x8d, 0xa2, 0x1a, 0x94, - 0x6e, 0x39, 0xa7, 0x33, 0x77, 0xee, 0x0a, 0x1a, 0x32, 0x61, 0xe6, 0x94, 0xeb, 0x85, 0x5b, 0xce, - 0xbb, 0x12, 0xeb, 0x33, 0x21, 0xfd, 0xf3, 0x97, 0x62, 0xe2, 0xbb, 0xde, 0x84, 0x8e, 0xa6, 0xcc, - 0xa3, 0xee, 0xd8, 0x3c, 0x38, 0x4f, 0x5d, 0x64, 0x48, 0x39, 0xc6, 0x9b, 0x53, 0xe6, 0x59, 0x63, - 0x74, 0x06, 0xa0, 0xce, 0xa0, 0xd4, 0x99, 0x79, 0x65, 0x31, 0x2f, 0x11, 0xa5, 0x0b, 0xd5, 0xa1, - 0xa0, 0x02, 0x4c, 0xa7, 0xae, 0x27, 0x42, 0x13, 0xce, 0xd3, 0x17, 0x85, 0xba, 0x71, 0x39, 0xf3, - 0x64, 0xac, 0x89, 0x94, 0x74, 0x5c, 0x4f, 0x90, 0x24, 0xa9, 0xf6, 0x6b, 0x38, 0x1a, 0x04, 0x6c, - 0x74, 0xb7, 0x15, 0xfc, 0xed, 0xb0, 0xa6, 0x1e, 0x84, 0xb5, 0xf6, 0x27, 0x28, 0x45, 0x9b, 0xfa, - 0x82, 0x89, 0x65, 0x88, 0x7e, 0x01, 0xd9, 0x50, 0x30, 0xc1, 0x15, 0xb9, 0x5c, 0x7f, 0x7a, 0xb9, - 0xca, 0xf6, 0x65, 0x82, 0xc8, 0x89, 0x66, 0xa1, 0x2a, 0x1c, 0x2c, 0x02, 0xee, 0xce, 0xd9, 0x84, - 0xab, 0x84, 0x16, 0xc9, 0x6a, 0x8d, 0x6a, 0x90, 0x55, 0x9b, 0x55, 0x3a, 0x0b, 0xf5, 0x62, 0xf2, - 0x0c, 0x44, 0x8b, 0x6a, 0xbf, 0x81, 0x8a, 0x5a, 0xb7, 0x39, 0xff, 0xd8, 0x95, 0x79, 0x0a, 0x39, - 0x36, 0xd7, 0xb1, 0xd7, 0xd7, 0x66, 0x9f, 0xcd, 0x65, 0xd8, 0x6b, 0x63, 0x30, 0xd6, 0xfb, 0xc3, - 0x85, 0xef, 0x85, 0x5c, 0xa6, 0x42, 0x2a, 0x97, 0x99, 0x90, 0x69, 0x9b, 0xcb, 0x5d, 0x29, 0xb5, - 0xab, 0x1c, 0xe1, 0x6d, 0xce, 0x7b, 0x21, 0x13, 0xe8, 0x85, 0xbe, 0x01, 0x74, 0xe6, 0x8f, 0xee, - 0xe4, 0x9d, 0x62, 0xf7, 0x91, 0xfa, 0x92, 0x84, 0xbb, 0xfe, 0xe8, 0xae, 0x25, 0xc1, 0xda, 0xef, - 0xf5, 0xdd, 0x1e, 0xf8, 0xda, 0xf7, 0xff, 0x3b, 0xbc, 0xeb, 0x10, 0xec, 0x3d, 0x1e, 0x02, 0x0a, - 0x47, 0x1b, 0xca, 0xa3, 0x53, 0x24, 0x23, 0x9b, 0xda, 0x8a, 0xec, 0x57, 0x90, 0xbb, 0x65, 0xee, - 0x6c, 0x19, 0xc4, 0x8a, 0x51, 0x22, 0x4d, 0x6d, 0x2d, 0x21, 0x31, 0xa5, 0xf6, 0x8f, 0x1c, 0xe4, - 0x22, 0x10, 0xd5, 0x21, 0x33, 0xf2, 0xc7, 0x71, 0x76, 0x3f, 0x7d, 0xb8, 0x2d, 0xfe, 0x6d, 0xfa, - 0x63, 0x4e, 0x14, 0x17, 0xfd, 0x16, 0xca, 0xf2, 0x46, 0x7b, 0x7c, 0x46, 0x97, 0x8b, 0x31, 0x5b, - 0x25, 0xd4, 0x4c, 0xec, 0x6e, 0x6a, 0xc2, 0x50, 0xc9, 0x49, 0x69, 0x94, 0x5c, 0xa2, 0x53, 0xc8, - 0x4f, 0xc5, 0x6c, 0xa4, 0x33, 0x91, 0x51, 0x45, 0x71, 0x20, 0x01, 0x95, 0x83, 0x1a, 0x94, 0x7c, - 0xcf, 0xf5, 0x3d, 0x1a, 0x4e, 0x19, 0xad, 0x7f, 0xf3, 0xad, 0x2a, 0xd6, 0x22, 0x29, 0x28, 0xb0, - 0x3f, 0x65, 0xf5, 0x6f, 0xbe, 0x45, 0x9f, 0x41, 0x41, 0x95, 0x0c, 0xff, 0xb0, 0x70, 0x83, 0x7b, - 0x55, 0xa5, 0x25, 0xa2, 0xaa, 0x08, 0x2b, 0x04, 0x1d, 0x43, 0xf6, 0x76, 0xc6, 0x26, 0xa1, 0xaa, - 0xcc, 0x12, 0xd1, 0x0b, 0xf4, 0x35, 0x1c, 0x47, 0x31, 0xa0, 0xa1, 0xbf, 0x0c, 0x46, 0x9c, 0xba, - 0xde, 0x98, 0x7f, 0x50, 0x75, 0x59, 0x22, 0x28, 0x92, 0xf5, 0x95, 0xc8, 0x92, 0x92, 0xda, 0x5f, - 0xb3, 0x50, 0x48, 0x04, 0x00, 0x15, 0xe1, 0x80, 0xe0, 0x3e, 0x26, 0x6f, 0x71, 0xcb, 0xf8, 0x04, - 0x99, 0x70, 0x3c, 0xb4, 0xdf, 0xd8, 0xce, 0x0f, 0x36, 0xbd, 0x6e, 0xbc, 0xeb, 0x61, 0x7b, 0x40, - 0x3b, 0x8d, 0x7e, 0xc7, 0x48, 0xa1, 0xe7, 0x60, 0x5a, 0x76, 0xd3, 0x21, 0x04, 0x37, 0x07, 0x2b, - 0x59, 0xa3, 0xe7, 0x0c, 0xed, 0x81, 0xb1, 0x87, 0x3e, 0x83, 0xd3, 0xb6, 0x65, 0x37, 0xba, 0x74, - 0xcd, 0x69, 0x76, 0x07, 0x6f, 0x29, 0xfe, 0xf1, 0xda, 0x22, 0xef, 0x8c, 0xf4, 0x2e, 0x42, 0x67, - 0xd0, 0x6d, 0xc6, 0x1a, 0x32, 0xe8, 0x19, 0x3c, 0xd1, 0x04, 0xbd, 0x85, 0x0e, 0x1c, 0x87, 0xf6, - 0x1d, 0xc7, 0x36, 0xb2, 0xe8, 0x10, 0x4a, 0x96, 0xfd, 0xb6, 0xd1, 0xb5, 0x5a, 0x94, 0xe0, 0x46, - 0xb7, 0x67, 0xec, 0xa3, 0x23, 0xa8, 0x6c, 0xf3, 0x72, 0x52, 0x45, 0xcc, 0x73, 0x6c, 0xcb, 0xb1, - 0xe9, 0x5b, 0x4c, 0xfa, 0x96, 0x63, 0x1b, 0x07, 0xe8, 0x04, 0xd0, 0xa6, 0xa8, 0xd3, 0x6b, 0x34, - 0x8d, 0x3c, 0x7a, 0x02, 0x87, 0x9b, 0xf8, 0x1b, 0xfc, 0xce, 0x00, 0x19, 0x06, 0xed, 0x18, 0x7d, - 0x85, 0xbb, 0xce, 0x0f, 0xb4, 0x67, 0xd9, 0x56, 0x6f, 0xd8, 0x33, 0x0a, 0xe8, 0x18, 0x8c, 0x36, - 0xc6, 0xd4, 0xb2, 0xfb, 0xc3, 0x76, 0xdb, 0x6a, 0x5a, 0xd8, 0x1e, 0x18, 0x45, 0x6d, 0x79, 0xd7, - 0xc1, 0x4b, 0x72, 0x43, 0xb3, 0xd3, 0xb0, 0x6d, 0xdc, 0xa5, 0x2d, 0xab, 0xdf, 0x78, 0xd5, 0xc5, - 0x2d, 0xa3, 0x8c, 0xce, 0xe0, 0xd9, 0x00, 0xf7, 0xae, 0x1d, 0xd2, 0x20, 0xef, 0x68, 0x2c, 0x6f, - 0x37, 0xac, 0xee, 0x90, 0x60, 0xa3, 0x82, 0x3e, 0x87, 0x33, 0x82, 0xbf, 0x1f, 0x5a, 0x04, 0xb7, - 0xa8, 0xed, 0xb4, 0x30, 0x6d, 0xe3, 0xc6, 0x60, 0x48, 0x30, 0xed, 0x59, 0xfd, 0xbe, 0x65, 0x7f, - 0x67, 0x18, 0xe8, 0x67, 0x70, 0xbe, 0xa2, 0xac, 0x14, 0x6c, 0xb1, 0x0e, 0xe5, 0xf9, 0xe2, 0x7c, - 0xda, 0xf8, 0xc7, 0x01, 0xbd, 0xc6, 0x98, 0x18, 0x08, 0x55, 0xe1, 0x64, 0x6d, 0x5e, 0x1b, 0x88, - 0x6c, 0x1f, 0x49, 0xd9, 0x35, 0x26, 0xbd, 0x86, 0x2d, 0x13, 0xbc, 0x21, 0x3b, 0x96, 0x6e, 0xaf, - 0x65, 0xdb, 0x6e, 0x3f, 0x41, 0xc7, 0x50, 0x89, 0xad, 0xc5, 0xe0, 0xbf, 0x72, 0xe8, 0x29, 0xa0, - 0xa1, 0x4d, 0x70, 0xa3, 0x25, 0x0f, 0xbf, 0x12, 0xfc, 0x3b, 0xf7, 0x3a, 0x73, 0xb0, 0x67, 0xa4, - 0x6b, 0x7f, 0x4f, 0x43, 0x69, 0xa3, 0xb6, 0xd0, 0x73, 0xc8, 0x87, 0xee, 0xc4, 0x63, 0x42, 0x56, - 0xbf, 0x6e, 0x0c, 0x6b, 0x40, 0x3d, 0x2e, 0x53, 0xe6, 0x7a, 0xba, 0x23, 0xe9, 0x8e, 0x9c, 0x57, - 0x88, 0xea, 0x47, 0x4f, 0x21, 0x17, 0x3f, 0x4e, 0x69, 0x55, 0x87, 0xfb, 0x23, 0xfd, 0x28, 0x3d, - 0x87, 0xbc, 0x6c, 0x79, 0xa1, 0x60, 0xf3, 0x85, 0x2a, 0xd1, 0x12, 0x59, 0x03, 0xe8, 0x0b, 0x28, - 0xcd, 0x79, 0x18, 0xb2, 0x09, 0xa7, 0xba, 0xcc, 0x40, 0x31, 0x8a, 0x11, 0xd8, 0x56, 0xd5, 0xf6, - 0x05, 0xc4, 0x65, 0x1f, 0x91, 0xb2, 0x9a, 0x14, 0x81, 0x9a, 0xb4, 0xdd, 0x71, 0x05, 0x8b, 0xaa, - 0x39, 0xd9, 0x71, 0x05, 0x43, 0x2f, 0xe1, 0x50, 0xb7, 0x0c, 0xd7, 0x73, 0xe7, 0xcb, 0xb9, 0x6e, - 0x1d, 0x39, 0xe5, 0x72, 0x45, 0xb5, 0x0e, 0x8d, 0xab, 0x0e, 0xf2, 0x0c, 0x0e, 0x6e, 0x58, 0xc8, - 0x65, 0xb3, 0x8f, 0x4a, 0x3b, 0x27, 0xd7, 0x6d, 0xce, 0xa5, 0x48, 0x3e, 0x01, 0x81, 0x6c, 0x5a, - 0x79, 0x2d, 0xba, 0xe5, 0x9c, 0xc8, 0x38, 0xae, 0x2c, 0xb0, 0x0f, 0x6b, 0x0b, 0x85, 0x84, 0x05, - 0x8d, 0x2b, 0x0b, 0x2f, 0xe1, 0x90, 0x7f, 0x10, 0x01, 0xa3, 0xfe, 0x82, 0xfd, 0xb4, 0xe4, 0x74, - 0xcc, 0x04, 0x33, 0x8b, 0x2a, 0xb8, 0x15, 0x25, 0x70, 0x14, 0xde, 0x62, 0x82, 0xd5, 0x9e, 0x43, - 0x95, 0xf0, 0x90, 0x8b, 0x9e, 0x1b, 0x86, 0xae, 0xef, 0x35, 0x7d, 0x4f, 0x04, 0xfe, 0x2c, 0x7a, - 0x33, 0x6a, 0x67, 0x70, 0xba, 0x53, 0xaa, 0x9b, 0xbe, 0xdc, 0xfc, 0xfd, 0x92, 0x07, 0xf7, 0xbb, - 0x37, 0xdf, 0xc3, 0xe9, 0x4e, 0x69, 0xf4, 0x62, 0x7c, 0x05, 0x59, 0xcf, 0x1f, 0xf3, 0xd0, 0x4c, - 0xa9, 0x99, 0xe1, 0x24, 0xd1, 0x9e, 0x6d, 0x7f, 0xcc, 0x3b, 0x6e, 0x28, 0xfc, 0xe0, 0x9e, 0x68, - 0x92, 0x64, 0x2f, 0x98, 0x1b, 0x84, 0xe6, 0xde, 0x03, 0xf6, 0x35, 0x73, 0x83, 0x15, 0x5b, 0x91, - 0x6a, 0x7f, 0x4e, 0x41, 0x21, 0xa1, 0x04, 0x9d, 0xc0, 0xfe, 0x62, 0x79, 0x73, 0xc7, 0xef, 0xa3, - 0x2b, 0x18, 0xad, 0xd0, 0x0b, 0x28, 0xcf, 0x58, 0x28, 0xa8, 0xec, 0xad, 0x54, 0xa6, 0x34, 0x7a, - 0x50, 0xb7, 0x50, 0x74, 0x09, 0xc8, 0x17, 0x53, 0x1e, 0xd0, 0x70, 0x39, 0x1a, 0xf1, 0x30, 0xa4, - 0x8b, 0xc0, 0xbf, 0x51, 0x77, 0x72, 0x8f, 0xec, 0x90, 0xbc, 0xce, 0x1c, 0x64, 0x8c, 0x6c, 0xed, - 0x9f, 0x29, 0x28, 0x24, 0x9c, 0x93, 0xb7, 0x56, 0x1e, 0x86, 0xde, 0x06, 0xfe, 0x3c, 0xae, 0x85, - 0x15, 0x80, 0x4c, 0xc8, 0xa9, 0x85, 0xf0, 0xa3, 0x42, 0x88, 0x97, 0x3b, 0xbc, 0x4c, 0xef, 0xf4, - 0xb2, 0x0e, 0xc7, 0x73, 0xd7, 0xa3, 0x0b, 0xee, 0xb1, 0x99, 0xfb, 0x47, 0x4e, 0xe3, 0x19, 0x24, - 0xa3, 0xd8, 0x3b, 0x65, 0xa8, 0x06, 0xc5, 0x8d, 0x33, 0x65, 0xd5, 0x99, 0x36, 0xb0, 0x97, 0x7f, - 0x4b, 0x41, 0x31, 0x39, 0x4d, 0xa1, 0x12, 0xe4, 0x2d, 0x9b, 0xb6, 0xbb, 0xd6, 0x77, 0x9d, 0x81, - 0xf1, 0x89, 0x5c, 0xf6, 0x87, 0xcd, 0x26, 0xc6, 0x2d, 0xdc, 0x32, 0x52, 0x08, 0x41, 0x59, 0x36, - 0x06, 0xdc, 0xa2, 0x03, 0xab, 0x87, 0x9d, 0xa1, 0x7c, 0x53, 0x8e, 0xa0, 0x12, 0x61, 0xb6, 0x43, - 0x89, 0x33, 0x1c, 0x60, 0x23, 0x8d, 0x0c, 0x28, 0x46, 0x20, 0x26, 0xc4, 0x21, 0x46, 0x46, 0x36, - 0xc2, 0x08, 0x79, 0xf8, 0x3e, 0xb5, 0xf0, 0xa0, 0x61, 0x75, 0xfb, 0x46, 0xb6, 0xfe, 0x97, 0x0c, - 0xec, 0xab, 0xe9, 0x23, 0x40, 0x1d, 0x28, 0x24, 0xc6, 0x78, 0x74, 0x96, 0xb8, 0x16, 0x0f, 0xc7, - 0xfb, 0xaa, 0xb9, 0x7b, 0x3c, 0x5c, 0x86, 0x5f, 0xa7, 0xd0, 0x6b, 0x28, 0x26, 0x87, 0x52, 0x94, - 0x1c, 0x36, 0x76, 0x4c, 0xab, 0x1f, 0xd5, 0xf5, 0x06, 0x0c, 0x1c, 0x0a, 0x77, 0x2e, 0x87, 0x8b, - 0x68, 0xdc, 0x43, 0xd5, 0x04, 0x7f, 0x6b, 0x86, 0xac, 0x9e, 0xee, 0x94, 0x45, 0x75, 0xd2, 0xd5, - 0x47, 0x8c, 0x06, 0xae, 0x07, 0x47, 0xdc, 0x9c, 0xf2, 0xaa, 0x9f, 0x3e, 0x26, 0x8e, 0xb4, 0x8d, - 0xe1, 0x68, 0x47, 0x45, 0xa3, 0x9f, 0x27, 0x3d, 0x78, 0xb4, 0x1f, 0x54, 0x5f, 0xfc, 0x2f, 0xda, - 0xda, 0xca, 0x8e, 0xd2, 0xdf, 0xb0, 0xf2, 0x78, 0xe3, 0xd8, 0xb0, 0xf2, 0x91, 0x0e, 0xf2, 0xea, - 0x97, 0xbf, 0xbb, 0x9a, 0xb8, 0x62, 0xba, 0xbc, 0xb9, 0x1c, 0xf9, 0xf3, 0xab, 0x99, 0x3b, 0x99, - 0x0a, 0xcf, 0xf5, 0x26, 0x1e, 0x17, 0x7f, 0xf0, 0x83, 0xbb, 0xab, 0x99, 0x37, 0xbe, 0x52, 0x03, - 0xec, 0xd5, 0x4a, 0xdd, 0xcd, 0xbe, 0xfa, 0xfb, 0xf7, 0xab, 0xff, 0x06, 0x00, 0x00, 0xff, 0xff, - 0xba, 0x51, 0xff, 0x1a, 0x2e, 0x0e, 0x00, 0x00, + // 1694 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, 0x9b, 0x6d, 0x69, 0xa5, 0x59, 0xb4, 0xb2, 0x65, 0x9c, 0xac, + 0x55, 0x5b, 0x8e, 0xe4, 0x90, 0xb2, 0xcb, 0xa7, 0xa4, 0x58, 0x68, 0xcc, 0xec, 0xc2, 0x8c, 0xdc, + 0xc0, 0xda, 0x9b, 0x1c, 0xba, 0x5a, 0xd0, 0x82, 0x29, 0xc1, 0x0c, 0x9e, 0x69, 0x9c, 0x55, 0x0e, + 0xb9, 0xa4, 0x52, 0x95, 0x4b, 0x3e, 0x4b, 0xce, 0xf9, 0x42, 0xc9, 0x27, 0xc8, 0x25, 0xa7, 0x54, + 0x77, 0xcf, 0xc0, 0x80, 0xd0, 0xc6, 0x27, 0xa6, 0x7f, 0xef, 0xd7, 0xef, 0x75, 0xbf, 0x7f, 0xfd, + 0x80, 0xe3, 0xc0, 0x5f, 0x0a, 0x1e, 0x04, 0x8b, 0xd1, 0x95, 0xfe, 0xba, 0x5c, 0x04, 0xbe, 0xf0, + 0x51, 0x7e, 0x85, 0x57, 0xf3, 0xc1, 0x62, 0xa4, 0xd1, 0xda, 0x7f, 0xf7, 0x00, 0xf5, 0xb9, 0x37, + 0xbe, 0x66, 0xf7, 0x73, 0xee, 0x09, 0xc2, 0x7f, 0x5c, 0xf2, 0x50, 0x20, 0x04, 0x99, 0x31, 0x0f, + 0x85, 0x99, 0x3a, 0x4f, 0x5d, 0x14, 0x89, 0xfa, 0x46, 0x06, 0xa4, 0xd9, 0x5c, 0x98, 0x7b, 0xe7, + 0xa9, 0x8b, 0x34, 0x91, 0x9f, 0xe8, 0x53, 0x28, 0x2e, 0xf4, 0x3e, 0x3a, 0x65, 0xe1, 0xd4, 0x4c, + 0x2b, 0x76, 0x21, 0xc2, 0x3a, 0x2c, 0x9c, 0xa2, 0x0b, 0x30, 0x6e, 0x5d, 0x8f, 0xcd, 0xe8, 0x68, + 0x26, 0x7e, 0xa2, 0x63, 0x3e, 0x13, 0xcc, 0xcc, 0x9c, 0xa7, 0x2e, 0xb2, 0xa4, 0xac, 0xf0, 0xe6, + 0x4c, 0xfc, 0xd4, 0x92, 0x28, 0xfa, 0x1c, 0x2a, 0xb1, 0xb2, 0x40, 0x9f, 0xc2, 0xcc, 0x9e, 0xa7, + 0x2e, 0xf2, 0xa4, 0xbc, 0xd8, 0x3c, 0xdb, 0xe7, 0x50, 0x11, 0xee, 0x9c, 0xfb, 0x4b, 0x41, 0x43, + 0x3e, 0xf2, 0xbd, 0x71, 0x68, 0xee, 0x6b, 0x8d, 0x11, 0xdc, 0xd7, 0x28, 0xaa, 0x41, 0xe9, 0x96, + 0x73, 0x3a, 0x73, 0xe7, 0xae, 0xa0, 0x21, 0x13, 0x66, 0x4e, 0x1d, 0xbd, 0x70, 0xcb, 0x79, 0x57, + 0x62, 0x7d, 0x26, 0xe4, 0xf9, 0xfc, 0xa5, 0x98, 0xf8, 0xae, 0x37, 0xa1, 0xa3, 0x29, 0xf3, 0xa8, + 0x3b, 0x36, 0x0f, 0xce, 0x53, 0x17, 0x19, 0x52, 0x8e, 0xf1, 0xe6, 0x94, 0x79, 0xd6, 0x18, 0x9d, + 0x01, 0xa8, 0x3b, 0x28, 0x75, 0x66, 0x5e, 0x59, 0xcc, 0x4b, 0x44, 0xe9, 0x42, 0x75, 0x28, 0x28, + 0x07, 0xd3, 0xa9, 0xeb, 0x89, 0xd0, 0x84, 0xf3, 0xf4, 0x45, 0xa1, 0x6e, 0x5c, 0xce, 0x3c, 0xe9, + 0x6b, 0x22, 0x25, 0x1d, 0xd7, 0x13, 0x24, 0x49, 0xaa, 0x7d, 0x03, 0x87, 0x83, 0x80, 0x8d, 0xee, + 0xb6, 0x9c, 0xbf, 0xed, 0xd6, 0xd4, 0x03, 0xb7, 0xd6, 0xfe, 0x0c, 0xa5, 0x68, 0x53, 0x5f, 0x30, + 0xb1, 0x0c, 0xd1, 0xaf, 0x20, 0x1b, 0x0a, 0x26, 0xb8, 0x22, 0x97, 0xeb, 0x27, 0x97, 0xab, 0x68, + 0x5f, 0x26, 0x88, 0x9c, 0x68, 0x16, 0xaa, 0xc2, 0xc1, 0x22, 0xe0, 0xee, 0x9c, 0x4d, 0xb8, 0x0a, + 0x68, 0x91, 0xac, 0xd6, 0xa8, 0x06, 0x59, 0xb5, 0x59, 0x85, 0xb3, 0x50, 0x2f, 0x26, 0xef, 0x40, + 0xb4, 0xa8, 0xf6, 0x5b, 0xa8, 0xa8, 0x75, 0x9b, 0xf3, 0x0f, 0xa5, 0xcc, 0x09, 0xe4, 0xd8, 0x5c, + 0xfb, 0x5e, 0xa7, 0xcd, 0x3e, 0x9b, 0x4b, 0xb7, 0xd7, 0xc6, 0x60, 0xac, 0xf7, 0x87, 0x0b, 0xdf, + 0x0b, 0xb9, 0x0c, 0x85, 0x54, 0x2e, 0x23, 0x21, 0xc3, 0x36, 0x97, 0xbb, 0x52, 0x6a, 0x57, 0x39, + 0xc2, 0xdb, 0x9c, 0xf7, 0x42, 0x26, 0xd0, 0x0b, 0x9d, 0x01, 0x74, 0xe6, 0x8f, 0xee, 0x64, 0x4e, + 0xb1, 0xfb, 0x48, 0x7d, 0x49, 0xc2, 0x5d, 0x7f, 0x74, 0xd7, 0x92, 0x60, 0xed, 0x0f, 0x3a, 0xb7, + 0x07, 0xbe, 0x3e, 0xfb, 0xcf, 0x76, 0xef, 0xda, 0x05, 0x7b, 0x8f, 0xbb, 0x80, 0xc2, 0xe1, 0x86, + 0xf2, 0xe8, 0x16, 0x49, 0xcf, 0xa6, 0xb6, 0x3c, 0xfb, 0x05, 0xe4, 0x6e, 0x99, 0x3b, 0x5b, 0x06, + 0xb1, 0x62, 0x94, 0x08, 0x53, 0x5b, 0x4b, 0x48, 0x4c, 0xa9, 0xfd, 0x33, 0x07, 0xb9, 0x08, 0x44, + 0x75, 0xc8, 0x8c, 0xfc, 0x71, 0x1c, 0xdd, 0x8f, 0x1f, 0x6e, 0x8b, 0x7f, 0x9b, 0xfe, 0x98, 0x13, + 0xc5, 0x45, 0xbf, 0x83, 0xb2, 0xcc, 0x68, 0x8f, 0xcf, 0xe8, 0x72, 0x31, 0x66, 0xab, 0x80, 0x9a, + 0x89, 0xdd, 0x4d, 0x4d, 0x18, 0x2a, 0x39, 0x29, 0x8d, 0x92, 0x4b, 0x74, 0x0a, 0xf9, 0xa9, 0x98, + 0x8d, 0x74, 0x24, 0x32, 0xaa, 0x28, 0x0e, 0x24, 0xa0, 0x62, 0x50, 0x83, 0x92, 0xef, 0xb9, 0xbe, + 0x47, 0xc3, 0x29, 0xa3, 0xf5, 0xaf, 0xbe, 0x56, 0xc5, 0x5a, 0x24, 0x05, 0x05, 0xf6, 0xa7, 0xac, + 0xfe, 0xd5, 0xd7, 0xe8, 0x13, 0x28, 0xa8, 0x92, 0xe1, 0xef, 0x17, 0x6e, 0x70, 0xaf, 0xaa, 0xb4, + 0x44, 0x54, 0x15, 0x61, 0x85, 0xa0, 0x23, 0xc8, 0xde, 0xce, 0xd8, 0x24, 0x54, 0x95, 0x59, 0x22, + 0x7a, 0x81, 0xbe, 0x84, 0xa3, 0xc8, 0x07, 0x34, 0xf4, 0x97, 0xc1, 0x88, 0x53, 0xd7, 0x1b, 0xf3, + 0xf7, 0xaa, 0x2e, 0x4b, 0x04, 0x45, 0xb2, 0xbe, 0x12, 0x59, 0x52, 0x52, 0xfb, 0x5b, 0x16, 0x0a, + 0x09, 0x07, 0xa0, 0x22, 0x1c, 0x10, 0xdc, 0xc7, 0xe4, 0x2d, 0x6e, 0x19, 0x1f, 0x21, 0x13, 0x8e, + 0x86, 0xf6, 0x1b, 0xdb, 0xf9, 0xde, 0xa6, 0xd7, 0x8d, 0x77, 0x3d, 0x6c, 0x0f, 0x68, 0xa7, 0xd1, + 0xef, 0x18, 0x29, 0xf4, 0x1c, 0x4c, 0xcb, 0x6e, 0x3a, 0x84, 0xe0, 0xe6, 0x60, 0x25, 0x6b, 0xf4, + 0x9c, 0xa1, 0x3d, 0x30, 0xf6, 0xd0, 0x27, 0x70, 0xda, 0xb6, 0xec, 0x46, 0x97, 0xae, 0x39, 0xcd, + 0xee, 0xe0, 0x2d, 0xc5, 0x3f, 0x5c, 0x5b, 0xe4, 0x9d, 0x91, 0xde, 0x45, 0xe8, 0x0c, 0xba, 0xcd, + 0x58, 0x43, 0x06, 0x3d, 0x83, 0xa7, 0x9a, 0xa0, 0xb7, 0xd0, 0x81, 0xe3, 0xd0, 0xbe, 0xe3, 0xd8, + 0x46, 0x16, 0x3d, 0x81, 0x92, 0x65, 0xbf, 0x6d, 0x74, 0xad, 0x16, 0x25, 0xb8, 0xd1, 0xed, 0x19, + 0xfb, 0xe8, 0x10, 0x2a, 0xdb, 0xbc, 0x9c, 0x54, 0x11, 0xf3, 0x1c, 0xdb, 0x72, 0x6c, 0xfa, 0x16, + 0x93, 0xbe, 0xe5, 0xd8, 0xc6, 0x01, 0x3a, 0x06, 0xb4, 0x29, 0xea, 0xf4, 0x1a, 0x4d, 0x23, 0x8f, + 0x9e, 0xc2, 0x93, 0x4d, 0xfc, 0x0d, 0x7e, 0x67, 0x80, 0x74, 0x83, 0x3e, 0x18, 0x7d, 0x85, 0xbb, + 0xce, 0xf7, 0xb4, 0x67, 0xd9, 0x56, 0x6f, 0xd8, 0x33, 0x0a, 0xe8, 0x08, 0x8c, 0x36, 0xc6, 0xd4, + 0xb2, 0xfb, 0xc3, 0x76, 0xdb, 0x6a, 0x5a, 0xd8, 0x1e, 0x18, 0x45, 0x6d, 0x79, 0xd7, 0xc5, 0x4b, + 0x72, 0x43, 0xb3, 0xd3, 0xb0, 0x6d, 0xdc, 0xa5, 0x2d, 0xab, 0xdf, 0x78, 0xd5, 0xc5, 0x2d, 0xa3, + 0x8c, 0xce, 0xe0, 0xd9, 0x00, 0xf7, 0xae, 0x1d, 0xd2, 0x20, 0xef, 0x68, 0x2c, 0x6f, 0x37, 0xac, + 0xee, 0x90, 0x60, 0xa3, 0x82, 0x3e, 0x85, 0x33, 0x82, 0xbf, 0x1b, 0x5a, 0x04, 0xb7, 0xa8, 0xed, + 0xb4, 0x30, 0x6d, 0xe3, 0xc6, 0x60, 0x48, 0x30, 0xed, 0x59, 0xfd, 0xbe, 0x65, 0x7f, 0x6b, 0x18, + 0xe8, 0x17, 0x70, 0xbe, 0xa2, 0xac, 0x14, 0x6c, 0xb1, 0x9e, 0xc8, 0xfb, 0xc5, 0xf1, 0xb4, 0xf1, + 0x0f, 0x03, 0x7a, 0x8d, 0x31, 0x31, 0x10, 0xaa, 0xc2, 0xf1, 0xda, 0xbc, 0x36, 0x10, 0xd9, 0x3e, + 0x94, 0xb2, 0x6b, 0x4c, 0x7a, 0x0d, 0x5b, 0x06, 0x78, 0x43, 0x76, 0x24, 0x8f, 0xbd, 0x96, 0x6d, + 0x1f, 0xfb, 0x29, 0x3a, 0x82, 0x4a, 0x6c, 0x2d, 0x06, 0xff, 0x95, 0x43, 0x27, 0x80, 0x86, 0x36, + 0xc1, 0x8d, 0x96, 0xbc, 0xfc, 0x4a, 0xf0, 0xef, 0xdc, 0xeb, 0xcc, 0xc1, 0x9e, 0x91, 0xae, 0xfd, + 0x23, 0x0d, 0xa5, 0x8d, 0xda, 0x42, 0xcf, 0x21, 0x1f, 0xba, 0x13, 0x8f, 0x09, 0x59, 0xfd, 0xba, + 0x31, 0xac, 0x01, 0xf5, 0xb8, 0x4c, 0x99, 0xeb, 0xe9, 0x8e, 0xa4, 0x3b, 0x72, 0x5e, 0x21, 0xaa, + 0x1f, 0x9d, 0x40, 0x2e, 0x7e, 0x9c, 0xd2, 0xaa, 0x0e, 0xf7, 0x47, 0xfa, 0x51, 0x7a, 0x0e, 0x79, + 0xd9, 0xf2, 0x42, 0xc1, 0xe6, 0x0b, 0x55, 0xa2, 0x25, 0xb2, 0x06, 0xd0, 0x67, 0x50, 0x9a, 0xf3, + 0x30, 0x64, 0x13, 0x4e, 0x75, 0x99, 0x81, 0x62, 0x14, 0x23, 0xb0, 0xad, 0xaa, 0xed, 0x33, 0x88, + 0xcb, 0x3e, 0x22, 0x65, 0x35, 0x29, 0x02, 0x35, 0x69, 0xbb, 0xe3, 0x0a, 0x16, 0x55, 0x73, 0xb2, + 0xe3, 0x0a, 0x86, 0x5e, 0xc2, 0x13, 0xdd, 0x32, 0x5c, 0xcf, 0x9d, 0x2f, 0xe7, 0xba, 0x75, 0xe4, + 0xd4, 0x91, 0x2b, 0xaa, 0x75, 0x68, 0x5c, 0x75, 0x90, 0x67, 0x70, 0x70, 0xc3, 0x42, 0x2e, 0x9b, + 0x7d, 0x54, 0xda, 0x39, 0xb9, 0x6e, 0x73, 0x2e, 0x45, 0xf2, 0x09, 0x08, 0x64, 0xd3, 0xca, 0x6b, + 0xd1, 0x2d, 0xe7, 0x44, 0xfa, 0x71, 0x65, 0x81, 0xbd, 0x5f, 0x5b, 0x28, 0x24, 0x2c, 0x68, 0x5c, + 0x59, 0x78, 0x09, 0x4f, 0xf8, 0x7b, 0x11, 0x30, 0xea, 0x2f, 0xd8, 0x8f, 0x4b, 0x4e, 0xc7, 0x4c, + 0x30, 0xb3, 0xa8, 0x9c, 0x5b, 0x51, 0x02, 0x47, 0xe1, 0x2d, 0x26, 0x58, 0xed, 0x39, 0x54, 0x09, + 0x0f, 0xb9, 0xe8, 0xb9, 0x61, 0xe8, 0xfa, 0x5e, 0xd3, 0xf7, 0x44, 0xe0, 0xcf, 0xa2, 0x37, 0xa3, + 0x76, 0x06, 0xa7, 0x3b, 0xa5, 0xba, 0xe9, 0xcb, 0xcd, 0xdf, 0x2d, 0x79, 0x70, 0xbf, 0x7b, 0xf3, + 0x3d, 0x9c, 0xee, 0x94, 0x46, 0x2f, 0xc6, 0x17, 0x90, 0xf5, 0xfc, 0x31, 0x0f, 0xcd, 0x94, 0x9a, + 0x19, 0x8e, 0x13, 0xed, 0xd9, 0xf6, 0xc7, 0xbc, 0xe3, 0x86, 0xc2, 0x0f, 0xee, 0x89, 0x26, 0x49, + 0xf6, 0x82, 0xb9, 0x41, 0x68, 0xee, 0x3d, 0x60, 0x5f, 0x33, 0x37, 0x58, 0xb1, 0x15, 0xa9, 0xf6, + 0x97, 0x14, 0x14, 0x12, 0x4a, 0xd0, 0x31, 0xec, 0x2f, 0x96, 0x37, 0x77, 0xfc, 0x3e, 0x4a, 0xc1, + 0x68, 0x85, 0x5e, 0x40, 0x79, 0xc6, 0x42, 0x41, 0x65, 0x6f, 0xa5, 0x32, 0xa4, 0xd1, 0x83, 0xba, + 0x85, 0xa2, 0x4b, 0x40, 0xbe, 0x98, 0xf2, 0x80, 0x86, 0xcb, 0xd1, 0x88, 0x87, 0x21, 0x5d, 0x04, + 0xfe, 0x8d, 0xca, 0xc9, 0x3d, 0xb2, 0x43, 0xf2, 0x3a, 0x73, 0x90, 0x31, 0xb2, 0xb5, 0xff, 0xa4, + 0xa0, 0x90, 0x38, 0x9c, 0xcc, 0x5a, 0x79, 0x19, 0x7a, 0x1b, 0xf8, 0xf3, 0xb8, 0x16, 0x56, 0x00, + 0x32, 0x21, 0xa7, 0x16, 0xc2, 0x8f, 0x0a, 0x21, 0x5e, 0x6e, 0x66, 0x7b, 0x5a, 0x1d, 0x30, 0x91, + 0xed, 0x75, 0x38, 0x9a, 0xbb, 0x1e, 0x5d, 0x70, 0x8f, 0xcd, 0xdc, 0x3f, 0x71, 0x1a, 0x4f, 0x1e, + 0x19, 0x45, 0xdc, 0x29, 0x43, 0x35, 0x28, 0x6e, 0xdc, 0x24, 0xab, 0x6e, 0xb2, 0x81, 0xa1, 0x6f, + 0xe0, 0x44, 0x79, 0x81, 0x09, 0xc1, 0xe7, 0x0b, 0x11, 0x5f, 0xf0, 0x76, 0x39, 0x53, 0x35, 0x70, + 0x40, 0x1e, 0x13, 0xbf, 0xfc, 0x7b, 0x0a, 0x8a, 0xc9, 0xe9, 0x0b, 0x95, 0x20, 0x6f, 0xd9, 0xb4, + 0xdd, 0xb5, 0xbe, 0xed, 0x0c, 0x8c, 0x8f, 0xe4, 0xb2, 0x3f, 0x6c, 0x36, 0x31, 0x6e, 0xe1, 0x96, + 0x91, 0x42, 0x08, 0xca, 0xb2, 0x91, 0xe0, 0x16, 0x1d, 0x58, 0x3d, 0xec, 0x0c, 0xe5, 0x1b, 0x74, + 0x08, 0x95, 0x08, 0xb3, 0x1d, 0x4a, 0x9c, 0xe1, 0x00, 0x1b, 0x69, 0x64, 0x40, 0x31, 0x02, 0x31, + 0x21, 0x0e, 0x31, 0x32, 0xb2, 0x71, 0x46, 0xc8, 0xc3, 0xf7, 0xac, 0x85, 0x07, 0x0d, 0xab, 0xdb, + 0x37, 0xb2, 0xf5, 0xbf, 0x66, 0x60, 0x5f, 0x4d, 0x2b, 0x01, 0xea, 0x40, 0x21, 0x31, 0xf6, 0xa3, + 0xb3, 0x44, 0x1a, 0x3d, 0xfc, 0x3b, 0x50, 0x35, 0x77, 0x8f, 0x93, 0xcb, 0xf0, 0xcb, 0x14, 0x7a, + 0x0d, 0xc5, 0xe4, 0x10, 0x8b, 0x92, 0xc3, 0xc9, 0x8e, 0xe9, 0xf6, 0x83, 0xba, 0xde, 0x80, 0x81, + 0x43, 0xe1, 0xce, 0xe5, 0x30, 0x12, 0x8d, 0x87, 0xa8, 0x9a, 0xe0, 0x6f, 0xcd, 0x9c, 0xd5, 0xd3, + 0x9d, 0xb2, 0xa8, 0xae, 0xba, 0xfa, 0x8a, 0xd1, 0x80, 0xf6, 0xe0, 0x8a, 0x9b, 0x53, 0x61, 0xf5, + 0xe3, 0xc7, 0xc4, 0x91, 0xb6, 0x31, 0x1c, 0xee, 0xe8, 0x00, 0xe8, 0x97, 0xc9, 0x13, 0x3c, 0xda, + 0x3f, 0xaa, 0x2f, 0xfe, 0x1f, 0x6d, 0x6d, 0x65, 0x47, 0xab, 0xd8, 0xb0, 0xf2, 0x78, 0xa3, 0xd9, + 0xb0, 0xf2, 0x81, 0x8e, 0xf3, 0xea, 0xd7, 0xbf, 0xbf, 0x9a, 0xb8, 0x62, 0xba, 0xbc, 0xb9, 0x1c, + 0xf9, 0xf3, 0xab, 0x99, 0x3b, 0x99, 0x0a, 0xcf, 0xf5, 0x26, 0x1e, 0x17, 0x7f, 0xf4, 0x83, 0xbb, + 0xab, 0x99, 0x37, 0xbe, 0x52, 0x03, 0xef, 0xd5, 0x4a, 0xdd, 0xcd, 0xbe, 0xfa, 0xbb, 0xf8, 0x9b, + 0xff, 0x05, 0x00, 0x00, 0xff, 0xff, 0xaf, 0x8b, 0x2a, 0x02, 0x5e, 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 765ed1c73c2..b7c58d34656 100644 --- a/lnrpc/routerrpc/router.proto +++ b/lnrpc/routerrpc/router.proto @@ -358,13 +358,16 @@ message PairHistory { bytes node_to = 2 [json_name="node_to"]; /// Time stamp of last failure. - int64 last_fail_time = 3 [json_name = "last_fail_time"]; + int64 timestamp = 3 [json_name = "timestamp"]; /// Minimum penalization amount. int64 min_penalize_amt_sat = 4 [json_name = "min_penalize_amt_sat"]; /// Estimation of success probability for this pair. float success_prob = 5 [json_name = "success_prob"]; + + /// Whether the last payment attempt through this pair was successful. + bool last_attempt_successful = 6 [json_name = "last_attempt_successful"]; } service Router { diff --git a/lnrpc/routerrpc/router_server.go b/lnrpc/routerrpc/router_server.go index 3b48c8f2ba9..6e81bea4035 100644 --- a/lnrpc/routerrpc/router_server.go +++ b/lnrpc/routerrpc/router_server.go @@ -473,13 +473,14 @@ func (s *Server) QueryMissionControl(ctx context.Context, pair := p rpcPair := PairHistory{ - NodeFrom: pair.Pair.From[:], - NodeTo: pair.Pair.To[:], - LastFailTime: pair.LastFail.Unix(), + NodeFrom: pair.Pair.From[:], + NodeTo: pair.Pair.To[:], + Timestamp: pair.Timestamp.Unix(), MinPenalizeAmtSat: int64( pair.MinPenalizeAmt.ToSatoshis(), ), - SuccessProb: float32(pair.SuccessProb), + SuccessProb: float32(pair.SuccessProb), + LastAttemptSuccessful: pair.LastAttemptSuccessful, } rpcPairs = append(rpcPairs, &rpcPair) diff --git a/routing/missioncontrol.go b/routing/missioncontrol.go index 01998323847..8c85e272c64 100644 --- a/routing/missioncontrol.go +++ b/routing/missioncontrol.go @@ -43,6 +43,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 @@ -56,7 +60,7 @@ const ( // into the path finding process for subsequent payment attempts. type MissionControl struct { // lastPairFailure tracks the last payment failure per node pair. - lastPairFailure map[DirectedNodePair]pairFailure + lastPairFailure map[DirectedNodePair]timedPairResult // lastNodeFailure tracks the last node level failure per node. lastNodeFailure map[route.Vertex]time.Time @@ -97,14 +101,12 @@ type MissionControlConfig struct { MaxMcHistory int } -// pairFailure describes a payment failure for a node pair. -type pairFailure struct { - // timestamp is the time when this failure result was obtained. +// timedPairResult describes a timestamped pair result. +type timedPairResult struct { + // timestamp is the time when this result was obtained. timestamp 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 @@ -138,8 +140,8 @@ type MissionControlPairSnapshot struct { // Pair is the node pair of which the state is described. Pair DirectedNodePair - // LastFail is the time of last failure. - LastFail time.Time + // Timestamp is the time of last result. + Timestamp time.Time // MinPenalizeAmt is the minimum amount for which the channel will be // penalized. @@ -147,6 +149,10 @@ type MissionControlPairSnapshot struct { // SuccessProb is the success probability estimation for this channel. SuccessProb float64 + + // LastAttemptSuccessful indicates whether the last payment attempt + // through this pair was successful. + LastAttemptSuccessful bool } // paymentResult is the information that becomes available when a payment @@ -174,7 +180,7 @@ func NewMissionControl(db *bbolt.DB, cfg *MissionControlConfig) ( } mc := &MissionControl{ - lastPairFailure: make(map[DirectedNodePair]pairFailure), + lastPairFailure: make(map[DirectedNodePair]timedPairResult), lastNodeFailure: make(map[route.Vertex]time.Time), lastSecondChance: make(map[DirectedNodePair]time.Time), now: time.Now, @@ -220,7 +226,7 @@ func (m *MissionControl) ResetHistory() error { return err } - m.lastPairFailure = make(map[DirectedNodePair]pairFailure) + m.lastPairFailure = make(map[DirectedNodePair]timedPairResult) m.lastNodeFailure = make(map[route.Vertex]time.Time) m.lastSecondChance = make(map[DirectedNodePair]time.Time) @@ -274,6 +280,10 @@ func (m *MissionControl) getEdgeProbabilityForNode(fromNode, // 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) { + if lastPairResult.success { + return prevSuccessProbability + } + if amt >= lastPairResult.minPenalizeAmt { lastFail = lastPairResult.timestamp } @@ -346,7 +356,7 @@ func (m *MissionControl) GetHistorySnapshot() *MissionControlSnapshot { pair := MissionControlPairSnapshot{ Pair: v, MinPenalizeAmt: h.minPenalizeAmt, - LastFail: h.timestamp, + Timestamp: h.timestamp, SuccessProb: prob, } @@ -373,7 +383,6 @@ func (m *MissionControl) ReportPaymentFail(paymentID uint64, rt *route.Route, timestamp := m.now() - // TODO(joostjager): Use actual payment initiation time for timeFwd. result := &paymentResult{ success: false, timeFwd: timestamp, @@ -384,6 +393,31 @@ func (m *MissionControl) ReportPaymentFail(paymentID uint64, rt *route.Route, 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, + rt *route.Route) error { + + timestamp := m.now() + + result := &paymentResult{ + timeFwd: timestamp, + timeReply: timestamp, + id: paymentID, + success: true, + route: rt, + } + + _, _, err := m.processPaymentResult(result) + return err +} + +func (m *MissionControl) processPaymentResult(result *paymentResult) (bool, + channeldb.FailureReason, error) { + // Store complete result in database. if err := m.store.AddResult(result); err != nil { return false, 0, err @@ -403,7 +437,7 @@ func (m *MissionControl) applyPaymentResult(result *paymentResult) ( // Interpret result. i := newInterpretedResult( - result.route, result.failureSourceIdx, + result.route, result.success, result.failureSourceIdx, result.failure, ) @@ -429,13 +463,19 @@ func (m *MissionControl) applyPaymentResult(result *paymentResult) ( m.lastNodeFailure[node] = result.timeReply } - for pair, minPenalizeAmt := range i.pairResults { - log.Debugf("Reporting pair failure to Mission Control: "+ - "pair=%v, minPenalizeAmt=%v", pair, minPenalizeAmt) + for pair, pairResult := range i.pairResults { + if pairResult.success { + log.Debugf("Reporting pair success to Mission "+ + "Control: pair=%v", pair) + } else { + log.Debugf("Reporting pair failure to Mission "+ + "Control: pair=%v, minPenalizeAmt=%v", + pair, pairResult.minPenalizeAmt) + } - m.lastPairFailure[pair] = pairFailure{ - minPenalizeAmt: minPenalizeAmt, - timestamp: result.timeReply, + m.lastPairFailure[pair] = timedPairResult{ + timestamp: result.timeReply, + pairResult: pairResult, } } diff --git a/routing/missioncontrol_test.go b/routing/missioncontrol_test.go index 2133f288ab2..8ad24670a57 100644 --- a/routing/missioncontrol_test.go +++ b/routing/missioncontrol_test.go @@ -112,6 +112,16 @@ func (ctx *mcTestContext) reportFailure(t time.Time, ) } +// reportSuccess reports a success by using a test route. +func (ctx *mcTestContext) reportSuccess(t time.Time) { + err := ctx.mc.ReportPaymentSuccess(ctx.pid, mcTestRoute) + if err != nil { + ctx.t.Fatal(err) + } + + ctx.pid++ +} + // TestMissionControl tests mission control probability estimation. func TestMissionControl(t *testing.T) { ctx := createMcTestContext(t) @@ -173,6 +183,16 @@ func TestMissionControl(t *testing.T) { 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 diff --git a/routing/mock_test.go b/routing/mock_test.go index 5fd1072421d..37a8db68ee7 100644 --- a/routing/mock_test.go +++ b/routing/mock_test.go @@ -105,13 +105,11 @@ func (m *mockMissionControl) ReportPaymentFail(paymentID uint64, return false, 0, nil } -func (m *mockMissionControl) ReportEdgeFailure(failedEdge edge, - minPenalizeAmt lnwire.MilliSatoshi) { -} - -func (m *mockMissionControl) ReportEdgePolicyFailure(failedEdge edge) {} +func (m *mockMissionControl) ReportPaymentSuccess(paymentID uint64, + rt *route.Route) error { -func (m *mockMissionControl) ReportVertexFailure(v route.Vertex) {} + return nil +} func (m *mockMissionControl) GetEdgeProbability(fromNode, toNode route.Vertex, amt lnwire.MilliSatoshi) float64 { diff --git a/routing/pathfind.go b/routing/pathfind.go index b82ef4659f0..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 diff --git a/routing/payment_lifecycle.go b/routing/payment_lifecycle.go index db9b9803b66..9bc3f33021e 100644 --- a/routing/payment_lifecycle.go +++ b/routing/payment_lifecycle.go @@ -161,6 +161,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, &p.attempt.Route, + ) + 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) diff --git a/routing/result_interpretation.go b/routing/result_interpretation.go index 1c2d70c07fb..915018e895e 100644 --- a/routing/result_interpretation.go +++ b/routing/result_interpretation.go @@ -10,27 +10,51 @@ import ( "github.com/lightningnetwork/lnd/routing/route" ) +type pairResult struct { + // minPenalizeAmt is the amount that was sent across the channel. + minPenalizeAmt lnwire.MilliSatoshi + + // success indicates whether the payment attempt was successful through + // this pair. + success bool +} + +func (p pairResult) String() string { + if p.success { + return "success" + } + + return fmt.Sprintf("failed (minPenalizeAmt=%v)", p.minPenalizeAmt) +} + type interpretedResult struct { nodeFailures map[route.Vertex]struct{} - pairResults map[DirectedNodePair]lnwire.MilliSatoshi + pairResults map[DirectedNodePair]pairResult finalFailure bool failureReason channeldb.FailureReason policyFailure *DirectedNodePair } -func newInterpretedResult(rt *route.Route, failureSrcIdx *int, +func newInterpretedResult(rt *route.Route, success bool, failureSrcIdx *int, failure lnwire.FailureMessage) *interpretedResult { i := &interpretedResult{ nodeFailures: make(map[route.Vertex]struct{}), - pairResults: make(map[DirectedNodePair]lnwire.MilliSatoshi), + pairResults: make(map[DirectedNodePair]pairResult), } - i.processFail(rt, failureSrcIdx, failure) - + if success { + i.processSuccess(rt) + } else { + i.processFail(rt, failureSrcIdx, failure) + } return i } +func (i *interpretedResult) processSuccess(route *route.Route) { + i.successPairRange(route, 1, len(route.Hops)-1) +} + func (i *interpretedResult) processFail( route *route.Route, errSourceIdx *int, failure lnwire.FailureMessage) { @@ -105,10 +129,20 @@ func (i *interpretedResult) processPaymentOutcomeFinal( // Otherwise penalize the last channel of the route and retry. i.failPair(route, n-1) + // The other hops relayed corectly, so assign those pairs a + // success result. + if n > 2 { + i.successPairRange(route, 1, n-2) + } + // 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.successPairRange(route, 1, n-1) + i.setFailure( true, channeldb.FailureReasonIncorrectPaymentDetails, ) @@ -131,6 +165,11 @@ func (i *interpretedResult) processPaymentOutcomeFinal( // recipient, so we do apply a penalty. i.failNode(route, n-1) + // Other channels in the route forwarded correctly. + if n > 2 { + i.successPairRange(route, 1, n-2) + } + i.setFailure(true, channeldb.FailureReasonError) } } @@ -151,6 +190,10 @@ func (i *interpretedResult) processPaymentOutcomeIntermediate( i.failPairBalance( route, errorSourceIdx, ) + + if errorSourceIdx > 1 { + i.successPairRange(route, 1, errorSourceIdx-1) + } } reportIncoming := func() { @@ -166,6 +209,10 @@ func (i *interpretedResult) processPaymentOutcomeIntermediate( i.failPair( route, errorSourceIdx-1, ) + + if errorSourceIdx > 2 { + i.successPairRange(route, 1, errorSourceIdx-2) + } } reportAll := func() { @@ -282,8 +329,8 @@ func (i *interpretedResult) failPair( pair, _ := getPair(rt, channelIdx) // Report pair in both directions without a minimum penalization amount. - i.pairResults[pair] = 0 - i.pairResults[pair.Reverse()] = 0 + i.pairResults[pair] = pairResult{} + i.pairResults[pair.Reverse()] = pairResult{} } func (i *interpretedResult) failPairBalance( @@ -291,7 +338,21 @@ func (i *interpretedResult) failPairBalance( pair, amt := getPair(rt, channelIdx) - i.pairResults[pair] = amt + i.pairResults[pair] = pairResult{ + minPenalizeAmt: amt, + } +} + +func (i *interpretedResult) successPairRange( + rt *route.Route, fromIdx, toIdx int) { + + for idx := fromIdx; idx <= toIdx; idx++ { + pair, _ := getPair(rt, idx) + + i.pairResults[pair] = pairResult{ + success: true, + } + } } func (i interpretedResult) String() string { @@ -315,7 +376,7 @@ func (i interpretedResult) String() string { first = false } b.WriteString(fmt.Sprintf( - "(%x-%x,%v)", p.From[:6], p.To[:6], r.ToSatoshis(), + "(%x-%x,%v)", p.From[:6], p.To[:6], r, )) } diff --git a/routing/result_interpretation_test.go b/routing/result_interpretation_test.go index 27b32ec12ba..f91e8d98bd8 100644 --- a/routing/result_interpretation_test.go +++ b/routing/result_interpretation_test.go @@ -45,10 +45,30 @@ var ( } ) +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[NewDirectedNodePair(hops[0], hops[1])].success { + 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, &failureSrcIdx, + &routeTwoHop, false, &failureSrcIdx, lnwire.NewTemporaryChannelFailure(nil), ) @@ -56,7 +76,9 @@ func TestResultInterpretationFail(t *testing.T) { t.Fatal("expected one pair result") } - if i.pairResults[NewDirectedNodePair(hops[0], hops[1])] != 100 { + if i.pairResults[NewDirectedNodePair(hops[0], hops[1])]. + minPenalizeAmt != 100 { + t.Fatal("wrong pair result") } @@ -68,7 +90,7 @@ func TestResultInterpretationFail(t *testing.T) { func TestResultInterpretationFailExpiryTooSoon(t *testing.T) { failureSrcIdx := 3 i := newInterpretedResult( - &routeFourHop, &failureSrcIdx, + &routeFourHop, false, &failureSrcIdx, lnwire.NewExpiryTooSoon(lnwire.ChannelUpdate{}), ) @@ -77,11 +99,15 @@ func TestResultInterpretationFailExpiryTooSoon(t *testing.T) { len(i.pairResults)) } - if i.pairResults[NewDirectedNodePair(hops[0], hops[1])] != 0 { + if i.pairResults[NewDirectedNodePair(hops[0], hops[1])]. + minPenalizeAmt != 0 { + t.Fatal("wrong pair result") } - if i.pairResults[NewDirectedNodePair(hops[1], hops[2])] != 0 { + if i.pairResults[NewDirectedNodePair(hops[1], hops[2])]. + minPenalizeAmt != 0 { + t.Fatal("wrong pair result") } diff --git a/routing/router.go b/routing/router.go index cd28d3ec5df..218a558f610 100644 --- a/routing/router.go +++ b/routing/router.go @@ -182,6 +182,8 @@ type MissionController interface { failureSourceIdx *int, failure lnwire.FailureMessage) (bool, channeldb.FailureReason, error) + ReportPaymentSuccess(paymentID uint64, rt *route.Route) error + // GetEdgeProbability is expected to return the success probability of a // payment from fromNode along edge. GetEdgeProbability(fromNode, toNode route.Vertex, From 5ef8dbc0d0803f9df23d34bfd4a0e61749c2e4cb Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Fri, 2 Aug 2019 15:14:48 +0200 Subject: [PATCH 5/6] routing: report payment initiation --- routing/missioncontrol.go | 59 +++++++++++++++++++++++++++++----- routing/missioncontrol_test.go | 21 ++++++++++-- routing/mock_test.go | 10 ++++-- routing/payment_lifecycle.go | 25 ++++++++++++-- routing/router.go | 11 +++++-- 5 files changed, 107 insertions(+), 19 deletions(-) diff --git a/routing/missioncontrol.go b/routing/missioncontrol.go index 8c85e272c64..0a36ac6bdaf 100644 --- a/routing/missioncontrol.go +++ b/routing/missioncontrol.go @@ -1,6 +1,7 @@ package routing import ( + "fmt" "math" "sync" "time" @@ -69,6 +70,10 @@ type MissionControl struct { // 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 @@ -155,6 +160,13 @@ type MissionControlPairSnapshot struct { LastAttemptSuccessful bool } +// 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 { @@ -183,6 +195,7 @@ func NewMissionControl(db *bbolt.DB, cfg *MissionControlConfig) ( lastPairFailure: make(map[DirectedNodePair]timedPairResult), 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, @@ -371,13 +384,38 @@ func (m *MissionControl) GetHistorySnapshot() *MissionControlSnapshot { return &snapshot } +// ReportPaymentInitiate reports a payment attempt initiation to mission +// control. +func (m *MissionControl) ReportPaymentInitiate(paymentID uint64, + rt *route.Route) error { + + timestamp := m.now() + + initiate := paymentInitiate{ + route: rt, + timestamp: timestamp, + } + + m.Lock() + defer m.Unlock() + + if _, exists := m.paymentAttempts[paymentID]; exists { + return fmt.Errorf("payment attempt %v already exists", + paymentID) + } + + m.paymentAttempts[paymentID] = &initiate + + return nil +} + // ReportPaymentFail reports a failed payment to mission control as input for // future probability estimates. The failureSourceIdx argument indicates the // failure source. If it is nil, the failure source is unknown. This function // 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) { @@ -385,12 +423,10 @@ func (m *MissionControl) ReportPaymentFail(paymentID uint64, rt *route.Route, result := &paymentResult{ success: false, - timeFwd: timestamp, timeReply: timestamp, id: paymentID, failureSourceIdx: failureSourceIdx, failure: failure, - route: rt, } return m.processPaymentResult(result) @@ -398,17 +434,13 @@ func (m *MissionControl) ReportPaymentFail(paymentID uint64, rt *route.Route, // ReportPaymentSuccess reports a successful payment to mission control as input // for future probability estimates. -func (m *MissionControl) ReportPaymentSuccess(paymentID uint64, - rt *route.Route) error { - +func (m *MissionControl) ReportPaymentSuccess(paymentID uint64) error { timestamp := m.now() result := &paymentResult{ - timeFwd: timestamp, timeReply: timestamp, id: paymentID, success: true, - route: rt, } _, _, err := m.processPaymentResult(result) @@ -418,6 +450,17 @@ func (m *MissionControl) ReportPaymentSuccess(paymentID uint64, 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 diff --git a/routing/missioncontrol_test.go b/routing/missioncontrol_test.go index 8ad24670a57..f8c5e62e41a 100644 --- a/routing/missioncontrol_test.go +++ b/routing/missioncontrol_test.go @@ -106,15 +106,30 @@ 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.ReportPaymentSuccess(ctx.pid, mcTestRoute) + 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) } diff --git a/routing/mock_test.go b/routing/mock_test.go index 37a8db68ee7..b4f37d65874 100644 --- a/routing/mock_test.go +++ b/routing/mock_test.go @@ -99,13 +99,17 @@ 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) ReportPaymentSuccess(paymentID uint64, +func (m *mockMissionControl) ReportPaymentSuccess(paymentID uint64) error { + return nil +} + +func (m *mockMissionControl) ReportPaymentInitiate(paymentID uint64, rt *route.Route) error { return nil diff --git a/routing/payment_lifecycle.go b/routing/payment_lifecycle.go index 9bc3f33021e..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 @@ -163,7 +177,7 @@ func (p *paymentLifecycle) resumePayment() ([32]byte, *route.Route, error) { // Report success to mission control. err = p.router.cfg.MissionControl.ReportPaymentSuccess( - p.attempt.PaymentID, &p.attempt.Route, + p.attempt.PaymentID, ) if err != nil { log.Errorf("Error reporting payment success to mc: %v", @@ -327,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/router.go b/routing/router.go index 218a558f610..e15dc62b7b9 100644 --- a/routing/router.go +++ b/routing/router.go @@ -178,11 +178,16 @@ 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(paymentID uint64, rt *route.Route) 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. @@ -1903,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", From 2172b17474ca4d21e40541308171a4bca094dde4 Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Fri, 2 Aug 2019 15:26:02 +0200 Subject: [PATCH 6/6] routing: undirected pairs [wip] --- cmd/lncli/cmd_query_mission_control.go | 32 +-- lnrpc/routerrpc/router.pb.go | 301 +++++++++++++------------ lnrpc/routerrpc/router.proto | 34 ++- lnrpc/routerrpc/router_backend.go | 35 +-- lnrpc/routerrpc/router_server.go | 28 ++- lntest/itest/lnd_test.go | 11 - routing/missioncontrol.go | 101 ++++----- routing/missioncontrol_store.go | 6 +- routing/nodepair.go | 29 ++- routing/result_interpretation.go | 178 ++++++++------- routing/result_interpretation_test.go | 23 +- 11 files changed, 399 insertions(+), 379 deletions(-) diff --git a/cmd/lncli/cmd_query_mission_control.go b/cmd/lncli/cmd_query_mission_control.go index d7fe53cbb96..3ccbd42bf13 100644 --- a/cmd/lncli/cmd_query_mission_control.go +++ b/cmd/lncli/cmd_query_mission_control.go @@ -32,16 +32,17 @@ func queryMissionControl(ctx *cli.Context) error { } type displayNodeHistory struct { - Pubkey string - LastFailTime int64 - OtherSuccessProb float32 + Pubkey string + LastFailTime int64 + OtherChanSuccessProb float32 } type displayPairHistory struct { - NodeFrom, NodeTo string - Timestamp int64 - SuccessProb float32 - MinPenalizeAmtSat int64 + NodeA, NodeB string + Timestamp int64 + SuccessProb float32 + Amt int64 + Result string } displayResp := struct { @@ -53,9 +54,9 @@ func queryMissionControl(ctx *cli.Context) error { displayResp.Nodes = append( displayResp.Nodes, displayNodeHistory{ - Pubkey: hex.EncodeToString(n.Pubkey), - LastFailTime: n.LastFailTime, - OtherSuccessProb: n.OtherSuccessProb, + Pubkey: hex.EncodeToString(n.Pubkey), + LastFailTime: n.LastFailTime, + OtherChanSuccessProb: n.OtherChanSuccessProb, }, ) } @@ -64,11 +65,12 @@ func queryMissionControl(ctx *cli.Context) error { displayResp.Pairs = append( displayResp.Pairs, displayPairHistory{ - NodeFrom: hex.EncodeToString(n.NodeFrom), - NodeTo: hex.EncodeToString(n.NodeTo), - Timestamp: n.Timestamp, - SuccessProb: n.SuccessProb, - MinPenalizeAmtSat: n.MinPenalizeAmtSat, + 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 9698a115a6f..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 ( @@ -979,11 +1007,9 @@ func (m *QueryMissionControlRequest) XXX_DiscardUnknown() { var xxx_messageInfo_QueryMissionControlRequest proto.InternalMessageInfo -/// QueryMissionControlResponse contains mission control state. +/// QueryMissionControlResponse contains mission control state per node. type QueryMissionControlResponse struct { - /// Node-level mission control state. - Nodes []*NodeHistory `protobuf:"bytes,1,rep,name=nodes,proto3" json:"nodes,omitempty"` - /// Node pair-level mission control state. + 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:"-"` @@ -1035,10 +1061,8 @@ type NodeHistory struct { Pubkey []byte `protobuf:"bytes,1,opt,name=pubkey,proto3" json:"pubkey,omitempty"` /// 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 node pairs that are not in the pairs - //list. - OtherSuccessProb float32 `protobuf:"fixed32,3,opt,name=other_success_prob,proto3" json:"other_success_prob,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"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` @@ -1083,30 +1107,27 @@ func (m *NodeHistory) GetLastFailTime() int64 { return 0 } -func (m *NodeHistory) GetOtherSuccessProb() float32 { +func (m *NodeHistory) GetOtherChanSuccessProb() float32 { if m != nil { - return m.OtherSuccessProb + return m.OtherChanSuccessProb } return 0 } -/// PairHistory contains the mission control state for a particular node pair. +/// NodeHistory contains the mission control state for a particular channel. type PairHistory struct { - /// The source node pubkey of the pair. - NodeFrom []byte `protobuf:"bytes,1,opt,name=node_from,proto3" json:"node_from,omitempty"` - /// The destination node pubkey of the pair. - NodeTo []byte `protobuf:"bytes,2,opt,name=node_to,proto3" json:"node_to,omitempty"` + 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. Timestamp int64 `protobuf:"varint,3,opt,name=timestamp,proto3" json:"timestamp,omitempty"` /// Minimum penalization amount. - MinPenalizeAmtSat int64 `protobuf:"varint,4,opt,name=min_penalize_amt_sat,proto3" json:"min_penalize_amt_sat,omitempty"` - /// Estimation of success probability for this pair. - SuccessProb float32 `protobuf:"fixed32,5,opt,name=success_prob,proto3" json:"success_prob,omitempty"` - /// Whether the last payment attempt through this pair was successful. - LastAttemptSuccessful bool `protobuf:"varint,6,opt,name=last_attempt_successful,proto3" json:"last_attempt_successful,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + Amt int64 `protobuf:"varint,4,opt,name=amt,proto3" json:"amt,omitempty"` + /// Estimation of success probability for this channel. + 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 *PairHistory) Reset() { *m = PairHistory{} } @@ -1134,16 +1155,16 @@ func (m *PairHistory) XXX_DiscardUnknown() { var xxx_messageInfo_PairHistory proto.InternalMessageInfo -func (m *PairHistory) GetNodeFrom() []byte { +func (m *PairHistory) GetNodeA() []byte { if m != nil { - return m.NodeFrom + return m.NodeA } return nil } -func (m *PairHistory) GetNodeTo() []byte { +func (m *PairHistory) GetNodeB() []byte { if m != nil { - return m.NodeTo + return m.NodeB } return nil } @@ -1155,9 +1176,9 @@ func (m *PairHistory) GetTimestamp() int64 { return 0 } -func (m *PairHistory) GetMinPenalizeAmtSat() int64 { +func (m *PairHistory) GetAmt() int64 { if m != nil { - return m.MinPenalizeAmtSat + return m.Amt } return 0 } @@ -1169,15 +1190,16 @@ func (m *PairHistory) GetSuccessProb() float32 { return 0 } -func (m *PairHistory) GetLastAttemptSuccessful() bool { +func (m *PairHistory) GetResult() PairResult { if m != nil { - return m.LastAttemptSuccessful + return m.Result } - return false + 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") @@ -1199,113 +1221,114 @@ func init() { func init() { proto.RegisterFile("routerrpc/router.proto", fileDescriptor_7a0613f69d37b0a5) } var fileDescriptor_7a0613f69d37b0a5 = []byte{ - // 1694 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, 0x9b, 0x6d, 0x69, 0xa5, 0x59, 0xb4, 0xb2, 0x65, 0x9c, 0xac, - 0x55, 0x5b, 0x8e, 0xe4, 0x90, 0xb2, 0xcb, 0xa7, 0xa4, 0x58, 0x68, 0xcc, 0xec, 0xc2, 0x8c, 0xdc, - 0xc0, 0xda, 0x9b, 0x1c, 0xba, 0x5a, 0xd0, 0x82, 0x29, 0xc1, 0x0c, 0x9e, 0x69, 0x9c, 0x55, 0x0e, - 0xb9, 0xa4, 0x52, 0x95, 0x4b, 0x3e, 0x4b, 0xce, 0xf9, 0x42, 0xc9, 0x27, 0xc8, 0x25, 0xa7, 0x54, - 0x77, 0xcf, 0xc0, 0x80, 0xd0, 0xc6, 0x27, 0xa6, 0x7f, 0xef, 0xd7, 0xef, 0x75, 0xbf, 0x7f, 0xfd, - 0x80, 0xe3, 0xc0, 0x5f, 0x0a, 0x1e, 0x04, 0x8b, 0xd1, 0x95, 0xfe, 0xba, 0x5c, 0x04, 0xbe, 0xf0, - 0x51, 0x7e, 0x85, 0x57, 0xf3, 0xc1, 0x62, 0xa4, 0xd1, 0xda, 0x7f, 0xf7, 0x00, 0xf5, 0xb9, 0x37, - 0xbe, 0x66, 0xf7, 0x73, 0xee, 0x09, 0xc2, 0x7f, 0x5c, 0xf2, 0x50, 0x20, 0x04, 0x99, 0x31, 0x0f, - 0x85, 0x99, 0x3a, 0x4f, 0x5d, 0x14, 0x89, 0xfa, 0x46, 0x06, 0xa4, 0xd9, 0x5c, 0x98, 0x7b, 0xe7, - 0xa9, 0x8b, 0x34, 0x91, 0x9f, 0xe8, 0x53, 0x28, 0x2e, 0xf4, 0x3e, 0x3a, 0x65, 0xe1, 0xd4, 0x4c, - 0x2b, 0x76, 0x21, 0xc2, 0x3a, 0x2c, 0x9c, 0xa2, 0x0b, 0x30, 0x6e, 0x5d, 0x8f, 0xcd, 0xe8, 0x68, - 0x26, 0x7e, 0xa2, 0x63, 0x3e, 0x13, 0xcc, 0xcc, 0x9c, 0xa7, 0x2e, 0xb2, 0xa4, 0xac, 0xf0, 0xe6, - 0x4c, 0xfc, 0xd4, 0x92, 0x28, 0xfa, 0x1c, 0x2a, 0xb1, 0xb2, 0x40, 0x9f, 0xc2, 0xcc, 0x9e, 0xa7, - 0x2e, 0xf2, 0xa4, 0xbc, 0xd8, 0x3c, 0xdb, 0xe7, 0x50, 0x11, 0xee, 0x9c, 0xfb, 0x4b, 0x41, 0x43, - 0x3e, 0xf2, 0xbd, 0x71, 0x68, 0xee, 0x6b, 0x8d, 0x11, 0xdc, 0xd7, 0x28, 0xaa, 0x41, 0xe9, 0x96, - 0x73, 0x3a, 0x73, 0xe7, 0xae, 0xa0, 0x21, 0x13, 0x66, 0x4e, 0x1d, 0xbd, 0x70, 0xcb, 0x79, 0x57, - 0x62, 0x7d, 0x26, 0xe4, 0xf9, 0xfc, 0xa5, 0x98, 0xf8, 0xae, 0x37, 0xa1, 0xa3, 0x29, 0xf3, 0xa8, - 0x3b, 0x36, 0x0f, 0xce, 0x53, 0x17, 0x19, 0x52, 0x8e, 0xf1, 0xe6, 0x94, 0x79, 0xd6, 0x18, 0x9d, - 0x01, 0xa8, 0x3b, 0x28, 0x75, 0x66, 0x5e, 0x59, 0xcc, 0x4b, 0x44, 0xe9, 0x42, 0x75, 0x28, 0x28, - 0x07, 0xd3, 0xa9, 0xeb, 0x89, 0xd0, 0x84, 0xf3, 0xf4, 0x45, 0xa1, 0x6e, 0x5c, 0xce, 0x3c, 0xe9, - 0x6b, 0x22, 0x25, 0x1d, 0xd7, 0x13, 0x24, 0x49, 0xaa, 0x7d, 0x03, 0x87, 0x83, 0x80, 0x8d, 0xee, - 0xb6, 0x9c, 0xbf, 0xed, 0xd6, 0xd4, 0x03, 0xb7, 0xd6, 0xfe, 0x0c, 0xa5, 0x68, 0x53, 0x5f, 0x30, - 0xb1, 0x0c, 0xd1, 0xaf, 0x20, 0x1b, 0x0a, 0x26, 0xb8, 0x22, 0x97, 0xeb, 0x27, 0x97, 0xab, 0x68, - 0x5f, 0x26, 0x88, 0x9c, 0x68, 0x16, 0xaa, 0xc2, 0xc1, 0x22, 0xe0, 0xee, 0x9c, 0x4d, 0xb8, 0x0a, - 0x68, 0x91, 0xac, 0xd6, 0xa8, 0x06, 0x59, 0xb5, 0x59, 0x85, 0xb3, 0x50, 0x2f, 0x26, 0xef, 0x40, - 0xb4, 0xa8, 0xf6, 0x5b, 0xa8, 0xa8, 0x75, 0x9b, 0xf3, 0x0f, 0xa5, 0xcc, 0x09, 0xe4, 0xd8, 0x5c, - 0xfb, 0x5e, 0xa7, 0xcd, 0x3e, 0x9b, 0x4b, 0xb7, 0xd7, 0xc6, 0x60, 0xac, 0xf7, 0x87, 0x0b, 0xdf, - 0x0b, 0xb9, 0x0c, 0x85, 0x54, 0x2e, 0x23, 0x21, 0xc3, 0x36, 0x97, 0xbb, 0x52, 0x6a, 0x57, 0x39, - 0xc2, 0xdb, 0x9c, 0xf7, 0x42, 0x26, 0xd0, 0x0b, 0x9d, 0x01, 0x74, 0xe6, 0x8f, 0xee, 0x64, 0x4e, - 0xb1, 0xfb, 0x48, 0x7d, 0x49, 0xc2, 0x5d, 0x7f, 0x74, 0xd7, 0x92, 0x60, 0xed, 0x0f, 0x3a, 0xb7, - 0x07, 0xbe, 0x3e, 0xfb, 0xcf, 0x76, 0xef, 0xda, 0x05, 0x7b, 0x8f, 0xbb, 0x80, 0xc2, 0xe1, 0x86, - 0xf2, 0xe8, 0x16, 0x49, 0xcf, 0xa6, 0xb6, 0x3c, 0xfb, 0x05, 0xe4, 0x6e, 0x99, 0x3b, 0x5b, 0x06, - 0xb1, 0x62, 0x94, 0x08, 0x53, 0x5b, 0x4b, 0x48, 0x4c, 0xa9, 0xfd, 0x33, 0x07, 0xb9, 0x08, 0x44, - 0x75, 0xc8, 0x8c, 0xfc, 0x71, 0x1c, 0xdd, 0x8f, 0x1f, 0x6e, 0x8b, 0x7f, 0x9b, 0xfe, 0x98, 0x13, - 0xc5, 0x45, 0xbf, 0x83, 0xb2, 0xcc, 0x68, 0x8f, 0xcf, 0xe8, 0x72, 0x31, 0x66, 0xab, 0x80, 0x9a, - 0x89, 0xdd, 0x4d, 0x4d, 0x18, 0x2a, 0x39, 0x29, 0x8d, 0x92, 0x4b, 0x74, 0x0a, 0xf9, 0xa9, 0x98, - 0x8d, 0x74, 0x24, 0x32, 0xaa, 0x28, 0x0e, 0x24, 0xa0, 0x62, 0x50, 0x83, 0x92, 0xef, 0xb9, 0xbe, - 0x47, 0xc3, 0x29, 0xa3, 0xf5, 0xaf, 0xbe, 0x56, 0xc5, 0x5a, 0x24, 0x05, 0x05, 0xf6, 0xa7, 0xac, - 0xfe, 0xd5, 0xd7, 0xe8, 0x13, 0x28, 0xa8, 0x92, 0xe1, 0xef, 0x17, 0x6e, 0x70, 0xaf, 0xaa, 0xb4, - 0x44, 0x54, 0x15, 0x61, 0x85, 0xa0, 0x23, 0xc8, 0xde, 0xce, 0xd8, 0x24, 0x54, 0x95, 0x59, 0x22, - 0x7a, 0x81, 0xbe, 0x84, 0xa3, 0xc8, 0x07, 0x34, 0xf4, 0x97, 0xc1, 0x88, 0x53, 0xd7, 0x1b, 0xf3, - 0xf7, 0xaa, 0x2e, 0x4b, 0x04, 0x45, 0xb2, 0xbe, 0x12, 0x59, 0x52, 0x52, 0xfb, 0x5b, 0x16, 0x0a, - 0x09, 0x07, 0xa0, 0x22, 0x1c, 0x10, 0xdc, 0xc7, 0xe4, 0x2d, 0x6e, 0x19, 0x1f, 0x21, 0x13, 0x8e, - 0x86, 0xf6, 0x1b, 0xdb, 0xf9, 0xde, 0xa6, 0xd7, 0x8d, 0x77, 0x3d, 0x6c, 0x0f, 0x68, 0xa7, 0xd1, - 0xef, 0x18, 0x29, 0xf4, 0x1c, 0x4c, 0xcb, 0x6e, 0x3a, 0x84, 0xe0, 0xe6, 0x60, 0x25, 0x6b, 0xf4, - 0x9c, 0xa1, 0x3d, 0x30, 0xf6, 0xd0, 0x27, 0x70, 0xda, 0xb6, 0xec, 0x46, 0x97, 0xae, 0x39, 0xcd, - 0xee, 0xe0, 0x2d, 0xc5, 0x3f, 0x5c, 0x5b, 0xe4, 0x9d, 0x91, 0xde, 0x45, 0xe8, 0x0c, 0xba, 0xcd, - 0x58, 0x43, 0x06, 0x3d, 0x83, 0xa7, 0x9a, 0xa0, 0xb7, 0xd0, 0x81, 0xe3, 0xd0, 0xbe, 0xe3, 0xd8, - 0x46, 0x16, 0x3d, 0x81, 0x92, 0x65, 0xbf, 0x6d, 0x74, 0xad, 0x16, 0x25, 0xb8, 0xd1, 0xed, 0x19, - 0xfb, 0xe8, 0x10, 0x2a, 0xdb, 0xbc, 0x9c, 0x54, 0x11, 0xf3, 0x1c, 0xdb, 0x72, 0x6c, 0xfa, 0x16, - 0x93, 0xbe, 0xe5, 0xd8, 0xc6, 0x01, 0x3a, 0x06, 0xb4, 0x29, 0xea, 0xf4, 0x1a, 0x4d, 0x23, 0x8f, - 0x9e, 0xc2, 0x93, 0x4d, 0xfc, 0x0d, 0x7e, 0x67, 0x80, 0x74, 0x83, 0x3e, 0x18, 0x7d, 0x85, 0xbb, - 0xce, 0xf7, 0xb4, 0x67, 0xd9, 0x56, 0x6f, 0xd8, 0x33, 0x0a, 0xe8, 0x08, 0x8c, 0x36, 0xc6, 0xd4, - 0xb2, 0xfb, 0xc3, 0x76, 0xdb, 0x6a, 0x5a, 0xd8, 0x1e, 0x18, 0x45, 0x6d, 0x79, 0xd7, 0xc5, 0x4b, - 0x72, 0x43, 0xb3, 0xd3, 0xb0, 0x6d, 0xdc, 0xa5, 0x2d, 0xab, 0xdf, 0x78, 0xd5, 0xc5, 0x2d, 0xa3, - 0x8c, 0xce, 0xe0, 0xd9, 0x00, 0xf7, 0xae, 0x1d, 0xd2, 0x20, 0xef, 0x68, 0x2c, 0x6f, 0x37, 0xac, - 0xee, 0x90, 0x60, 0xa3, 0x82, 0x3e, 0x85, 0x33, 0x82, 0xbf, 0x1b, 0x5a, 0x04, 0xb7, 0xa8, 0xed, - 0xb4, 0x30, 0x6d, 0xe3, 0xc6, 0x60, 0x48, 0x30, 0xed, 0x59, 0xfd, 0xbe, 0x65, 0x7f, 0x6b, 0x18, - 0xe8, 0x17, 0x70, 0xbe, 0xa2, 0xac, 0x14, 0x6c, 0xb1, 0x9e, 0xc8, 0xfb, 0xc5, 0xf1, 0xb4, 0xf1, - 0x0f, 0x03, 0x7a, 0x8d, 0x31, 0x31, 0x10, 0xaa, 0xc2, 0xf1, 0xda, 0xbc, 0x36, 0x10, 0xd9, 0x3e, - 0x94, 0xb2, 0x6b, 0x4c, 0x7a, 0x0d, 0x5b, 0x06, 0x78, 0x43, 0x76, 0x24, 0x8f, 0xbd, 0x96, 0x6d, - 0x1f, 0xfb, 0x29, 0x3a, 0x82, 0x4a, 0x6c, 0x2d, 0x06, 0xff, 0x95, 0x43, 0x27, 0x80, 0x86, 0x36, - 0xc1, 0x8d, 0x96, 0xbc, 0xfc, 0x4a, 0xf0, 0xef, 0xdc, 0xeb, 0xcc, 0xc1, 0x9e, 0x91, 0xae, 0xfd, - 0x23, 0x0d, 0xa5, 0x8d, 0xda, 0x42, 0xcf, 0x21, 0x1f, 0xba, 0x13, 0x8f, 0x09, 0x59, 0xfd, 0xba, - 0x31, 0xac, 0x01, 0xf5, 0xb8, 0x4c, 0x99, 0xeb, 0xe9, 0x8e, 0xa4, 0x3b, 0x72, 0x5e, 0x21, 0xaa, - 0x1f, 0x9d, 0x40, 0x2e, 0x7e, 0x9c, 0xd2, 0xaa, 0x0e, 0xf7, 0x47, 0xfa, 0x51, 0x7a, 0x0e, 0x79, - 0xd9, 0xf2, 0x42, 0xc1, 0xe6, 0x0b, 0x55, 0xa2, 0x25, 0xb2, 0x06, 0xd0, 0x67, 0x50, 0x9a, 0xf3, - 0x30, 0x64, 0x13, 0x4e, 0x75, 0x99, 0x81, 0x62, 0x14, 0x23, 0xb0, 0xad, 0xaa, 0xed, 0x33, 0x88, - 0xcb, 0x3e, 0x22, 0x65, 0x35, 0x29, 0x02, 0x35, 0x69, 0xbb, 0xe3, 0x0a, 0x16, 0x55, 0x73, 0xb2, - 0xe3, 0x0a, 0x86, 0x5e, 0xc2, 0x13, 0xdd, 0x32, 0x5c, 0xcf, 0x9d, 0x2f, 0xe7, 0xba, 0x75, 0xe4, - 0xd4, 0x91, 0x2b, 0xaa, 0x75, 0x68, 0x5c, 0x75, 0x90, 0x67, 0x70, 0x70, 0xc3, 0x42, 0x2e, 0x9b, - 0x7d, 0x54, 0xda, 0x39, 0xb9, 0x6e, 0x73, 0x2e, 0x45, 0xf2, 0x09, 0x08, 0x64, 0xd3, 0xca, 0x6b, - 0xd1, 0x2d, 0xe7, 0x44, 0xfa, 0x71, 0x65, 0x81, 0xbd, 0x5f, 0x5b, 0x28, 0x24, 0x2c, 0x68, 0x5c, - 0x59, 0x78, 0x09, 0x4f, 0xf8, 0x7b, 0x11, 0x30, 0xea, 0x2f, 0xd8, 0x8f, 0x4b, 0x4e, 0xc7, 0x4c, - 0x30, 0xb3, 0xa8, 0x9c, 0x5b, 0x51, 0x02, 0x47, 0xe1, 0x2d, 0x26, 0x58, 0xed, 0x39, 0x54, 0x09, - 0x0f, 0xb9, 0xe8, 0xb9, 0x61, 0xe8, 0xfa, 0x5e, 0xd3, 0xf7, 0x44, 0xe0, 0xcf, 0xa2, 0x37, 0xa3, - 0x76, 0x06, 0xa7, 0x3b, 0xa5, 0xba, 0xe9, 0xcb, 0xcd, 0xdf, 0x2d, 0x79, 0x70, 0xbf, 0x7b, 0xf3, - 0x3d, 0x9c, 0xee, 0x94, 0x46, 0x2f, 0xc6, 0x17, 0x90, 0xf5, 0xfc, 0x31, 0x0f, 0xcd, 0x94, 0x9a, - 0x19, 0x8e, 0x13, 0xed, 0xd9, 0xf6, 0xc7, 0xbc, 0xe3, 0x86, 0xc2, 0x0f, 0xee, 0x89, 0x26, 0x49, - 0xf6, 0x82, 0xb9, 0x41, 0x68, 0xee, 0x3d, 0x60, 0x5f, 0x33, 0x37, 0x58, 0xb1, 0x15, 0xa9, 0xf6, - 0x97, 0x14, 0x14, 0x12, 0x4a, 0xd0, 0x31, 0xec, 0x2f, 0x96, 0x37, 0x77, 0xfc, 0x3e, 0x4a, 0xc1, - 0x68, 0x85, 0x5e, 0x40, 0x79, 0xc6, 0x42, 0x41, 0x65, 0x6f, 0xa5, 0x32, 0xa4, 0xd1, 0x83, 0xba, - 0x85, 0xa2, 0x4b, 0x40, 0xbe, 0x98, 0xf2, 0x80, 0x86, 0xcb, 0xd1, 0x88, 0x87, 0x21, 0x5d, 0x04, - 0xfe, 0x8d, 0xca, 0xc9, 0x3d, 0xb2, 0x43, 0xf2, 0x3a, 0x73, 0x90, 0x31, 0xb2, 0xb5, 0xff, 0xa4, - 0xa0, 0x90, 0x38, 0x9c, 0xcc, 0x5a, 0x79, 0x19, 0x7a, 0x1b, 0xf8, 0xf3, 0xb8, 0x16, 0x56, 0x00, - 0x32, 0x21, 0xa7, 0x16, 0xc2, 0x8f, 0x0a, 0x21, 0x5e, 0x6e, 0x66, 0x7b, 0x5a, 0x1d, 0x30, 0x91, - 0xed, 0x75, 0x38, 0x9a, 0xbb, 0x1e, 0x5d, 0x70, 0x8f, 0xcd, 0xdc, 0x3f, 0x71, 0x1a, 0x4f, 0x1e, - 0x19, 0x45, 0xdc, 0x29, 0x43, 0x35, 0x28, 0x6e, 0xdc, 0x24, 0xab, 0x6e, 0xb2, 0x81, 0xa1, 0x6f, - 0xe0, 0x44, 0x79, 0x81, 0x09, 0xc1, 0xe7, 0x0b, 0x11, 0x5f, 0xf0, 0x76, 0x39, 0x53, 0x35, 0x70, - 0x40, 0x1e, 0x13, 0xbf, 0xfc, 0x7b, 0x0a, 0x8a, 0xc9, 0xe9, 0x0b, 0x95, 0x20, 0x6f, 0xd9, 0xb4, - 0xdd, 0xb5, 0xbe, 0xed, 0x0c, 0x8c, 0x8f, 0xe4, 0xb2, 0x3f, 0x6c, 0x36, 0x31, 0x6e, 0xe1, 0x96, - 0x91, 0x42, 0x08, 0xca, 0xb2, 0x91, 0xe0, 0x16, 0x1d, 0x58, 0x3d, 0xec, 0x0c, 0xe5, 0x1b, 0x74, - 0x08, 0x95, 0x08, 0xb3, 0x1d, 0x4a, 0x9c, 0xe1, 0x00, 0x1b, 0x69, 0x64, 0x40, 0x31, 0x02, 0x31, - 0x21, 0x0e, 0x31, 0x32, 0xb2, 0x71, 0x46, 0xc8, 0xc3, 0xf7, 0xac, 0x85, 0x07, 0x0d, 0xab, 0xdb, - 0x37, 0xb2, 0xf5, 0xbf, 0x66, 0x60, 0x5f, 0x4d, 0x2b, 0x01, 0xea, 0x40, 0x21, 0x31, 0xf6, 0xa3, - 0xb3, 0x44, 0x1a, 0x3d, 0xfc, 0x3b, 0x50, 0x35, 0x77, 0x8f, 0x93, 0xcb, 0xf0, 0xcb, 0x14, 0x7a, - 0x0d, 0xc5, 0xe4, 0x10, 0x8b, 0x92, 0xc3, 0xc9, 0x8e, 0xe9, 0xf6, 0x83, 0xba, 0xde, 0x80, 0x81, - 0x43, 0xe1, 0xce, 0xe5, 0x30, 0x12, 0x8d, 0x87, 0xa8, 0x9a, 0xe0, 0x6f, 0xcd, 0x9c, 0xd5, 0xd3, - 0x9d, 0xb2, 0xa8, 0xae, 0xba, 0xfa, 0x8a, 0xd1, 0x80, 0xf6, 0xe0, 0x8a, 0x9b, 0x53, 0x61, 0xf5, - 0xe3, 0xc7, 0xc4, 0x91, 0xb6, 0x31, 0x1c, 0xee, 0xe8, 0x00, 0xe8, 0x97, 0xc9, 0x13, 0x3c, 0xda, - 0x3f, 0xaa, 0x2f, 0xfe, 0x1f, 0x6d, 0x6d, 0x65, 0x47, 0xab, 0xd8, 0xb0, 0xf2, 0x78, 0xa3, 0xd9, - 0xb0, 0xf2, 0x81, 0x8e, 0xf3, 0xea, 0xd7, 0xbf, 0xbf, 0x9a, 0xb8, 0x62, 0xba, 0xbc, 0xb9, 0x1c, - 0xf9, 0xf3, 0xab, 0x99, 0x3b, 0x99, 0x0a, 0xcf, 0xf5, 0x26, 0x1e, 0x17, 0x7f, 0xf4, 0x83, 0xbb, - 0xab, 0x99, 0x37, 0xbe, 0x52, 0x03, 0xef, 0xd5, 0x4a, 0xdd, 0xcd, 0xbe, 0xfa, 0xbb, 0xf8, 0x9b, - 0xff, 0x05, 0x00, 0x00, 0xff, 0xff, 0xaf, 0x8b, 0x2a, 0x02, 0x5e, 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 b7c58d34656..e092682176a 100644 --- a/lnrpc/routerrpc/router.proto +++ b/lnrpc/routerrpc/router.proto @@ -323,12 +323,10 @@ message ResetMissionControlResponse{} message QueryMissionControlRequest {} -/// QueryMissionControlResponse contains mission control state. +/// QueryMissionControlResponse contains mission control state per node. message QueryMissionControlResponse { - /// Node-level mission control state. repeated NodeHistory nodes = 1; - /// Node pair-level mission control state. repeated PairHistory pairs = 2; } @@ -340,34 +338,34 @@ message NodeHistory { /// Time stamp of last failure. Set to zero if no failure happened yet. int64 last_fail_time = 2 [json_name = "last_fail_time"]; - /** - Estimation of success probability for node pairs that are not in the pairs - list. - **/ - float other_success_prob = 3 [json_name = "other_success_prob"]; + /// 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; - reserved 4; + FAIL_BALANCE = 2; } -/// PairHistory contains the mission control state for a particular node pair. +/// NodeHistory contains the mission control state for a particular channel. message PairHistory { - /// The source node pubkey of the pair. - bytes node_from = 1 [json_name="node_from"]; + bytes node_a = 1; - /// The destination node pubkey of the pair. - bytes node_to = 2 [json_name="node_to"]; + bytes node_b = 2; /// Time stamp of last failure. int64 timestamp = 3 [json_name = "timestamp"]; /// Minimum penalization amount. - int64 min_penalize_amt_sat = 4 [json_name = "min_penalize_amt_sat"]; + int64 amt = 4 [json_name = "amt"]; - /// Estimation of success probability for this pair. + /// Estimation of success probability for this channel. float success_prob = 5 [json_name = "success_prob"]; - /// Whether the last payment attempt through this pair was successful. - bool last_attempt_successful = 6 [json_name = "last_attempt_successful"]; + PairResult result = 6; } service Router { diff --git a/lnrpc/routerrpc/router_backend.go b/lnrpc/routerrpc/router_backend.go index faba3eea9eb..21b53a12b0f 100644 --- a/lnrpc/routerrpc/router_backend.go +++ b/lnrpc/routerrpc/router_backend.go @@ -58,7 +58,7 @@ type RouterBackend struct { // MissionControl defines the mission control dependencies of routerrpc. type MissionControl interface { // GetEdgeProbability is expected to return the success probability of a payment - // from fromNode to toNode. + // from fromNode along edge. GetEdgeProbability(fromNode, toNode route.Vertex, amt lnwire.MilliSatoshi) float64 @@ -144,13 +144,18 @@ func (r *RouterBackend) QueryRoutes(ctx context.Context, ignoredEdges := make(map[routing.DirectedNodePair]struct{}) for _, ignoredEdge := range in.IgnoredEdges { - pair, err := r.rpcEdgeToPair(ignoredEdge) + a, b, err := r.FetchChannelEndpoints(ignoredEdge.ChannelId) if err != nil { - log.Warnf("Ignore channel %v skipped: %v", - ignoredEdge.ChannelId, err) - + log.Warnf("Channel %v unknown", ignoredEdge.ChannelId) continue } + + var pair routing.DirectedNodePair + if ignoredEdge.DirectionReverse { + pair.From, pair.To = b, a + } else { + pair.From, pair.To = a, b + } ignoredEdges[pair] = struct{}{} } @@ -215,26 +220,6 @@ func (r *RouterBackend) QueryRoutes(ctx context.Context, return routeResp, nil } -// rpcEdgeToPair looks up the provided channel and returns the channel endpoints -// as a directed pair. -func (r *RouterBackend) rpcEdgeToPair(e *lnrpc.EdgeLocator) ( - routing.DirectedNodePair, error) { - - a, b, err := r.FetchChannelEndpoints(e.ChannelId) - if err != nil { - return routing.DirectedNodePair{}, err - } - - var pair routing.DirectedNodePair - if e.DirectionReverse { - pair.From, pair.To = b, a - } else { - pair.From, pair.To = a, b - } - - return pair, nil -} - // calculateFeeLimit returns the fee limit in millisatoshis. If a percentage // based fee limit has been requested, we'll factor in the ratio provided with // the amount of the payment. diff --git a/lnrpc/routerrpc/router_server.go b/lnrpc/routerrpc/router_server.go index 6e81bea4035..8f30da58a6f 100644 --- a/lnrpc/routerrpc/router_server.go +++ b/lnrpc/routerrpc/router_server.go @@ -459,8 +459,8 @@ func (s *Server) QueryMissionControl(ctx context.Context, rpcNode := NodeHistory{ Pubkey: node.Node[:], LastFailTime: node.LastFail.Unix(), - OtherSuccessProb: float32( - node.OtherSuccessProb, + OtherChanSuccessProb: float32( + node.OtherChanSuccessProb, ), } @@ -472,15 +472,27 @@ func (s *Server) QueryMissionControl(ctx context.Context, // 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") + } + rpcPair := PairHistory{ - NodeFrom: pair.Pair.From[:], - NodeTo: pair.Pair.To[:], + NodeA: pair.NodeA[:], + NodeB: pair.NodeB[:], Timestamp: pair.Timestamp.Unix(), - MinPenalizeAmtSat: int64( - pair.MinPenalizeAmt.ToSatoshis(), + Amt: int64( + pair.Amount.ToSatoshis(), ), - SuccessProb: float32(pair.SuccessProb), - LastAttemptSuccessful: pair.LastAttemptSuccessful, + SuccessProb: float32(pair.SuccessProb), + Result: result, } rpcPairs = append(rpcPairs, &rpcPair) diff --git a/lntest/itest/lnd_test.go b/lntest/itest/lnd_test.go index 5e1d6680870..53674d5a466 100644 --- a/lntest/itest/lnd_test.go +++ b/lntest/itest/lnd_test.go @@ -9543,17 +9543,6 @@ func testBidirectionalAsyncPayments(net *lntest.NetworkHarness, t *harnessTest) "timeout: %v", err) } - // Reset mission control to prevent previous payment results from - // interfering with this test. A new channel has been opened, but - // mission control operates on node pairs. - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - _, err = net.Alice.RouterClient.ResetMissionControl( - ctxt, &routerrpc.ResetMissionControlRequest{}, - ) - if err != nil { - t.Fatalf("unable to reset mc for alice: %v", err) - } - // Open up a payment streams to Alice and to Bob, that we'll use to // send payment between nodes. ctx, cancel := context.WithTimeout(ctxb, lntest.AsyncBenchmarkTimeout) diff --git a/routing/missioncontrol.go b/routing/missioncontrol.go index 0a36ac6bdaf..4f7724ef5a5 100644 --- a/routing/missioncontrol.go +++ b/routing/missioncontrol.go @@ -60,8 +60,8 @@ 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 { - // lastPairFailure tracks the last payment failure per node pair. - lastPairFailure map[DirectedNodePair]timedPairResult + // 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 @@ -106,9 +106,8 @@ type MissionControlConfig struct { MaxMcHistory int } -// timedPairResult describes a timestamped pair result. -type timedPairResult struct { - // timestamp is the time when this result was obtained. +// pairHistory contains the last payment result for a node pair. +type pairHistory struct { timestamp time.Time pairResult @@ -122,7 +121,7 @@ type MissionControlSnapshot struct { // Pairs is a list of channels for which specific information is // logged. - Pairs []MissionControlPairSnapshot + Pairs []MissionControlChannelSnapshot } // MissionControlNodeSnapshot contains a snapshot of the current node state in @@ -134,30 +133,27 @@ type MissionControlNodeSnapshot struct { // Lastfail is the time of last failure, if any. LastFail time.Time - // OtherSuccessProb is the success probability for channels not in + // OtherChanSuccessProb is the success probability for channels not in // the Channels slice. - OtherSuccessProb float64 + OtherChanSuccessProb float64 } -// MissionControlPairSnapshot contains a snapshot of the current node pair +// MissionControlChannelSnapshot contains a snapshot of the current channel // state in mission control. -type MissionControlPairSnapshot struct { - // Pair is the node pair of which the state is described. - Pair DirectedNodePair +type MissionControlChannelSnapshot struct { + NodeA, NodeB route.Vertex - // Timestamp is the time of last result. + // 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 - - // LastAttemptSuccessful indicates whether the last payment attempt - // through this pair was successful. - LastAttemptSuccessful bool } // paymentInitiate contains information that is available when a payment attempt @@ -192,7 +188,7 @@ func NewMissionControl(db *bbolt.DB, cfg *MissionControlConfig) ( } mc := &MissionControl{ - lastPairFailure: make(map[DirectedNodePair]timedPairResult), + lastPairResult: make(map[NodePair]pairHistory), lastNodeFailure: make(map[route.Vertex]time.Time), lastSecondChance: make(map[DirectedNodePair]time.Time), paymentAttempts: make(map[uint64]*paymentInitiate), @@ -239,7 +235,7 @@ func (m *MissionControl) ResetHistory() error { return err } - m.lastPairFailure = make(map[DirectedNodePair]timedPairResult) + m.lastPairResult = make(map[NodePair]pairHistory) m.lastNodeFailure = make(map[route.Vertex]time.Time) m.lastSecondChance = make(map[DirectedNodePair]time.Time) @@ -287,17 +283,25 @@ func (m *MissionControl) getEdgeProbabilityForNode(fromNode, lastFail := m.lastNodeFailure[fromNode] // Retrieve the last pair outcome. - pair := NewDirectedNodePair(fromNode, toNode) - lastPairResult, lastPairResultExists := m.lastPairFailure[pair] + 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) { - if lastPairResult.success { + switch lastPairResult.resultType { + case ChannelResultSuccess: return prevSuccessProbability - } - if amt >= lastPairResult.minPenalizeAmt { + // 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 } } @@ -343,34 +347,35 @@ func (m *MissionControl) GetHistorySnapshot() *MissionControlSnapshot { defer m.Unlock() log.Debugf("Requesting history snapshot from mission control: "+ - "node_failure_count=%v, pair_result_count=%v", - len(m.lastNodeFailure), len(m.lastPairFailure)) + "node_count=%v", len(m.lastPairResult)) nodes := make([]MissionControlNodeSnapshot, 0, len(m.lastNodeFailure)) for v, h := range m.lastNodeFailure { otherProb := m.getEdgeProbabilityForNode(v, route.Vertex{}, 0) nodes = append(nodes, MissionControlNodeSnapshot{ - Node: v, - LastFail: h, - OtherSuccessProb: otherProb, + Node: v, + LastFail: h, + OtherChanSuccessProb: otherProb, }) } - pairs := make([]MissionControlPairSnapshot, 0, len(m.lastPairFailure)) + pairs := make([]MissionControlChannelSnapshot, 0, len(m.lastPairResult)) - for v, h := range m.lastPairFailure { + for v, h := range m.lastPairResult { // Show probability assuming amount meets min // penalization amount. prob := m.getEdgeProbabilityForNode( - v.From, v.To, h.minPenalizeAmt, + v.A, v.B, h.amount, ) - pair := MissionControlPairSnapshot{ - Pair: v, - MinPenalizeAmt: h.minPenalizeAmt, - Timestamp: h.timestamp, - SuccessProb: prob, + pair := MissionControlChannelSnapshot{ + NodeA: v.A, + NodeB: v.B, + Amount: h.amount, + Timestamp: h.timestamp, + ResultType: h.resultType, + SuccessProb: prob, } pairs = append(pairs, pair) @@ -422,9 +427,9 @@ func (m *MissionControl) ReportPaymentFail(paymentID uint64, timestamp := m.now() result := &paymentResult{ - success: false, timeReply: timestamp, id: paymentID, + success: false, failureSourceIdx: failureSourceIdx, failure: failure, } @@ -500,25 +505,13 @@ func (m *MissionControl) applyPaymentResult(result *paymentResult) ( } for node := range i.nodeFailures { - log.Debugf("Reporting node failure to Mission Control: "+ - "node=%v", node) - m.lastNodeFailure[node] = result.timeReply } for pair, pairResult := range i.pairResults { - if pairResult.success { - log.Debugf("Reporting pair success to Mission "+ - "Control: pair=%v", pair) - } else { - log.Debugf("Reporting pair failure to Mission "+ - "Control: pair=%v, minPenalizeAmt=%v", - pair, pairResult.minPenalizeAmt) - } - - m.lastPairFailure[pair] = timedPairResult{ - timestamp: result.timeReply, + m.lastPairResult[pair] = pairHistory{ pairResult: pairResult, + timestamp: result.timeReply, } } 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/nodepair.go b/routing/nodepair.go index 3ca1f828490..f0a4d6905df 100644 --- a/routing/nodepair.go +++ b/routing/nodepair.go @@ -1,6 +1,8 @@ package routing import ( + "bytes" + "github.com/lightningnetwork/lnd/routing/route" ) @@ -9,15 +11,24 @@ type DirectedNodePair struct { From, To route.Vertex } -// NewDirectedNodePair instantiates a new DirectedNodePair struct. -func NewDirectedNodePair(from, to route.Vertex) DirectedNodePair { - return DirectedNodePair{ - From: from, - To: to, - } +// NodePair stores an undirected pair of nodes. +type NodePair struct { + A, B route.Vertex } -// Reverse reverses the pair direction. -func (d DirectedNodePair) Reverse() DirectedNodePair { - return DirectedNodePair{From: d.To, To: d.From} +// 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/result_interpretation.go b/routing/result_interpretation.go index 915018e895e..bdaa81d5f0e 100644 --- a/routing/result_interpretation.go +++ b/routing/result_interpretation.go @@ -10,26 +10,46 @@ import ( "github.com/lightningnetwork/lnd/routing/route" ) +type ChannelResultType byte + +const ( + ChannelResultSuccess ChannelResultType = iota + + ChannelResultFail + + ChannelResultFailBalance +) + type pairResult struct { - // minPenalizeAmt is the amount that was sent across the channel. - minPenalizeAmt lnwire.MilliSatoshi + // amount is the amount that was sent across the channel. + amount lnwire.MilliSatoshi - // success indicates whether the payment attempt was successful through - // this pair. - success bool + // resultType is the type of channel result that collected from the + // payment attempt. + resultType ChannelResultType } -func (p pairResult) String() string { - if p.success { - return "success" +func (r ChannelResultType) String() string { + switch r { + case ChannelResultFail: + return "Fail" + case ChannelResultFailBalance: + return "FailBalance" + case ChannelResultSuccess: + return "Success" + default: + return "Unknown" } +} - return fmt.Sprintf("failed (minPenalizeAmt=%v)", p.minPenalizeAmt) +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[DirectedNodePair]pairResult + pairResults map[NodePair]pairResult finalFailure bool failureReason channeldb.FailureReason policyFailure *DirectedNodePair @@ -40,7 +60,7 @@ func newInterpretedResult(rt *route.Route, success bool, failureSrcIdx *int, i := &interpretedResult{ nodeFailures: make(map[route.Vertex]struct{}), - pairResults: make(map[DirectedNodePair]pairResult), + pairResults: make(map[NodePair]pairResult), } if success { @@ -52,7 +72,9 @@ func newInterpretedResult(rt *route.Route, success bool, failureSrcIdx *int, } func (i *interpretedResult) processSuccess(route *route.Route) { - i.successPairRange(route, 1, len(route.Hops)-1) + i.reportChannelRangeResult( + route, 1, len(route.Hops)-1, ChannelResultSuccess, + ) } func (i *interpretedResult) processFail( @@ -127,12 +149,16 @@ func (i *interpretedResult) processPaymentOutcomeFinal( } // Otherwise penalize the last channel of the route and retry. - i.failPair(route, n-1) + i.reportChannelResult( + route, n-1, ChannelResultFail, + ) // The other hops relayed corectly, so assign those pairs a // success result. if n > 2 { - i.successPairRange(route, 1, n-2) + i.reportChannelRangeResult( + route, 1, n-2, ChannelResultSuccess, + ) } // We are using wrong payment hash or amount, fail the payment. @@ -141,7 +167,7 @@ func (i *interpretedResult) processPaymentOutcomeFinal( // Assign all pairs a success result, as the payment reached the // destination correctly. - i.successPairRange(route, 1, n-1) + i.reportChannelRangeResult(route, 1, n-1, ChannelResultSuccess) i.setFailure( true, channeldb.FailureReasonIncorrectPaymentDetails, @@ -167,7 +193,9 @@ func (i *interpretedResult) processPaymentOutcomeFinal( // Other channels in the route forwarded correctly. if n > 2 { - i.successPairRange(route, 1, n-2) + i.reportChannelRangeResult( + route, 1, n-2, ChannelResultSuccess, + ) } i.setFailure(true, channeldb.FailureReasonError) @@ -180,19 +208,15 @@ func (i *interpretedResult) processPaymentOutcomeIntermediate( route *route.Route, errorSourceIdx int, failure lnwire.FailureMessage) { - reportOutgoing := func() { - i.failPair( - route, errorSourceIdx, - ) - } - - reportOutgoingBalance := func() { - i.failPairBalance( - route, errorSourceIdx, + reportOutgoing := func(t ChannelResultType) { + i.reportChannelResult( + route, errorSourceIdx, t, ) if errorSourceIdx > 1 { - i.successPairRange(route, 1, errorSourceIdx-1) + i.reportChannelRangeResult( + route, 1, errorSourceIdx-1, ChannelResultSuccess, + ) } } @@ -206,12 +230,14 @@ func (i *interpretedResult) processPaymentOutcomeIntermediate( } // Otherwise report the incoming channel. - i.failPair( - route, errorSourceIdx-1, + i.reportChannelResult( + route, errorSourceIdx-1, ChannelResultFail, ) if errorSourceIdx > 2 { - i.successPairRange(route, 1, errorSourceIdx-2) + i.reportChannelRangeResult( + route, 1, errorSourceIdx-2, ChannelResultSuccess, + ) } } @@ -225,8 +251,8 @@ func (i *interpretedResult) processPaymentOutcomeIntermediate( } // Otherwise report all channels up to the error source. - i.failPairRange( - route, 1, errorSourceIdx-1, + i.reportChannelRangeResult( + route, 1, errorSourceIdx-1, ChannelResultFail, ) } @@ -239,7 +265,7 @@ func (i *interpretedResult) processPaymentOutcomeIntermediate( *lnwire.FailInvalidOnionHmac, *lnwire.FailInvalidOnionKey: - reportOutgoing() + 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 @@ -247,13 +273,13 @@ func (i *interpretedResult) processPaymentOutcomeIntermediate( // properly. Additionally, this guards against routing nodes returning // errors in order to attempt to black list another node. case *lnwire.FailUnknownNextPeer: - reportOutgoing() + 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() + 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 @@ -279,7 +305,7 @@ func (i *interpretedResult) processPaymentOutcomeIntermediate( // But we penalize only in a single direction and only for amounts // greater than the attempted amount. case *lnwire.FailTemporaryChannelFailure: - reportOutgoingBalance() + 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 @@ -297,6 +323,7 @@ func (i *interpretedResult) processPaymentOutcomeIntermediate( // 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. @@ -310,48 +337,47 @@ func (i *interpretedResult) processPaymentOutcomeUnknown(route *route.Route) { // 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.failPairRange(route, 1, n-1) + i.reportChannelRangeResult(route, 1, n-1, ChannelResultFail) } -func (i *interpretedResult) failPairRange( - rt *route.Route, fromIdx, toIdx int) { +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.failPair(rt, idx) + i.reportChannelResult(rt, idx, resultType) } } // reportChannelFailure reports a bidirectional failure of a channel. -func (i *interpretedResult) failPair( - rt *route.Route, channelIdx int) { - - pair, _ := getPair(rt, channelIdx) +func (i *interpretedResult) reportChannelResult( + rt *route.Route, channelIdx int, + resultType ChannelResultType) { - // Report pair in both directions without a minimum penalization amount. - i.pairResults[pair] = pairResult{} - i.pairResults[pair.Reverse()] = pairResult{} -} + channelID := rt.Hops[channelIdx].ChannelID + log.Debugf("Reporting channel failure to Mission Control: "+ + "chan=%v", channelID) -func (i *interpretedResult) failPairBalance( - rt *route.Route, channelIdx int) { - - pair, amt := getPair(rt, channelIdx) + nodeB := rt.Hops[channelIdx].PubKeyBytes + var ( + nodeA route.Vertex + amt lnwire.MilliSatoshi + ) - i.pairResults[pair] = pairResult{ - minPenalizeAmt: amt, + if channelIdx == 0 { + nodeA = rt.SourcePubKey + amt = rt.TotalAmount + } else { + nodeA = rt.Hops[channelIdx-1].PubKeyBytes + amt = rt.Hops[channelIdx-1].AmtToForward } -} - -func (i *interpretedResult) successPairRange( - rt *route.Route, fromIdx, toIdx int) { - for idx := fromIdx; idx <= toIdx; idx++ { - pair, _ := getPair(rt, idx) + pair := newNodePair(nodeA, nodeB) - i.pairResults[pair] = pairResult{ - success: true, - } + i.pairResults[pair] = pairResult{ + amount: amt, + resultType: resultType, } } @@ -376,33 +402,11 @@ func (i interpretedResult) String() string { first = false } b.WriteString(fmt.Sprintf( - "(%x-%x,%v)", p.From[:6], p.To[:6], r, + "(%x-%x,%v,%v)", + p.A[:6], p.B[:6], + r.amount.ToSatoshis(), r.resultType, )) } return b.String() } - -// getPair returns a node pair from the route and the amount passed between that -// pair. -func getPair(rt *route.Route, channelIdx int) (DirectedNodePair, - lnwire.MilliSatoshi) { - - nodeTo := rt.Hops[channelIdx].PubKeyBytes - var ( - nodeFrom route.Vertex - amt lnwire.MilliSatoshi - ) - - if channelIdx == 0 { - nodeFrom = rt.SourcePubKey - amt = rt.TotalAmount - } else { - nodeFrom = rt.Hops[channelIdx-1].PubKeyBytes - amt = rt.Hops[channelIdx-1].AmtToForward - } - - pair := NewDirectedNodePair(nodeFrom, nodeTo) - - return pair, amt -} diff --git a/routing/result_interpretation_test.go b/routing/result_interpretation_test.go index f91e8d98bd8..1b5cbcab089 100644 --- a/routing/result_interpretation_test.go +++ b/routing/result_interpretation_test.go @@ -22,7 +22,7 @@ var ( routeTwoHop = route.Route{ Hops: []*route.Hop{ - &route.Hop{PubKeyBytes: hops[0], AmtToForward: 100}, + &route.Hop{PubKeyBytes: hops[0]}, &route.Hop{PubKeyBytes: hops[1]}, }, } @@ -52,7 +52,9 @@ func TestResultInterpretationSuccess(t *testing.T) { t.Fatal("expected one pair result") } - if !i.pairResults[NewDirectedNodePair(hops[0], hops[1])].success { + if i.pairResults[newNodePair(hops[0], hops[1])].resultType != + ChannelResultSuccess { + t.Fatal("wrong pair result") } } @@ -76,8 +78,8 @@ func TestResultInterpretationFail(t *testing.T) { t.Fatal("expected one pair result") } - if i.pairResults[NewDirectedNodePair(hops[0], hops[1])]. - minPenalizeAmt != 100 { + if i.pairResults[newNodePair(hops[0], hops[1])].resultType != + ChannelResultFailBalance { t.Fatal("wrong pair result") } @@ -94,19 +96,18 @@ func TestResultInterpretationFailExpiryTooSoon(t *testing.T) { lnwire.NewExpiryTooSoon(lnwire.ChannelUpdate{}), ) - if len(i.pairResults) != 4 { - t.Fatalf("expected 4 pair results, but got %v", - len(i.pairResults)) + if len(i.pairResults) != 2 { + t.Fatal("expected two pair results") } - if i.pairResults[NewDirectedNodePair(hops[0], hops[1])]. - minPenalizeAmt != 0 { + if i.pairResults[newNodePair(hops[0], hops[1])].resultType != + ChannelResultFail { t.Fatal("wrong pair result") } - if i.pairResults[NewDirectedNodePair(hops[1], hops[2])]. - minPenalizeAmt != 0 { + if i.pairResults[newNodePair(hops[1], hops[2])].resultType != + ChannelResultFail { t.Fatal("wrong pair result") }