diff --git a/cmd/lncli/cmd_query_mission_control.go b/cmd/lncli/cmd_query_mission_control.go index 3c13a09b8..0122fb023 100644 --- a/cmd/lncli/cmd_query_mission_control.go +++ b/cmd/lncli/cmd_query_mission_control.go @@ -31,36 +31,17 @@ func queryMissionControl(ctx *cli.Context) error { return err } - type displayNodeHistory struct { - Pubkey string - LastFailTime int64 - OtherSuccessProb float32 - } - type displayPairHistory struct { NodeFrom, NodeTo string LastAttemptSuccessful bool Timestamp 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, - OtherSuccessProb: n.OtherSuccessProb, - }, - ) - } - for _, n := range snapshot.Pairs { displayResp.Pairs = append( displayResp.Pairs, @@ -69,7 +50,6 @@ func queryMissionControl(ctx *cli.Context) error { NodeTo: hex.EncodeToString(n.NodeTo), LastAttemptSuccessful: n.LastAttemptSuccessful, Timestamp: n.Timestamp, - SuccessProb: n.SuccessProb, MinPenalizeAmtSat: n.MinPenalizeAmtSat, }, ) diff --git a/lnrpc/routerrpc/config.go b/lnrpc/routerrpc/config.go index ba094c8f0..98b3594cf 100644 --- a/lnrpc/routerrpc/config.go +++ b/lnrpc/routerrpc/config.go @@ -16,6 +16,15 @@ type RoutingConfig struct { // a route when no other information is available. AprioriHopProbability float64 `long:"apriorihopprob" description:"Assumed success probability of a hop in a route when no other information is available."` + // AprioriWeight is a value in the range [0, 1] that defines to what + // extent historical results should be extrapolated to untried + // connections. Setting it to one will completely ignore historical + // results and always assume the configured a priori probability for + // untried connections. A value of zero will ignore the a priori + // probability completely and only base the probability on historical + // results, unless there are none available. + AprioriWeight float64 `long:"aprioriweight" description:"Weight of the a priori probability in success probability estimation. Valid values are in [0, 1]."` + // PenaltyHalfLife defines after how much time a penalized node or // channel is back at 50% probability. PenaltyHalfLife time.Duration `long:"penaltyhalflife" description:"Defines the duration after which a penalized node or channel is back at 50% probability"` diff --git a/lnrpc/routerrpc/config_active.go b/lnrpc/routerrpc/config_active.go index 0479211ca..a8393fc61 100644 --- a/lnrpc/routerrpc/config_active.go +++ b/lnrpc/routerrpc/config_active.go @@ -45,6 +45,7 @@ type Config struct { func DefaultConfig() *Config { defaultRoutingConfig := RoutingConfig{ AprioriHopProbability: routing.DefaultAprioriHopProbability, + AprioriWeight: routing.DefaultAprioriWeight, MinRouteProbability: routing.DefaultMinRouteProbability, PenaltyHalfLife: routing.DefaultPenaltyHalfLife, AttemptCost: routing.DefaultPaymentAttemptPenalty. @@ -61,6 +62,7 @@ func DefaultConfig() *Config { func GetRoutingConfig(cfg *Config) *RoutingConfig { return &RoutingConfig{ AprioriHopProbability: cfg.AprioriHopProbability, + AprioriWeight: cfg.AprioriWeight, MinRouteProbability: cfg.MinRouteProbability, AttemptCost: cfg.AttemptCost, PenaltyHalfLife: cfg.PenaltyHalfLife, diff --git a/lnrpc/routerrpc/config_default.go b/lnrpc/routerrpc/config_default.go index f8af98f34..6fcc74a77 100644 --- a/lnrpc/routerrpc/config_default.go +++ b/lnrpc/routerrpc/config_default.go @@ -18,6 +18,7 @@ func DefaultConfig() *Config { func GetRoutingConfig(cfg *Config) *RoutingConfig { return &RoutingConfig{ AprioriHopProbability: routing.DefaultAprioriHopProbability, + AprioriWeight: routing.DefaultAprioriWeight, MinRouteProbability: routing.DefaultMinRouteProbability, AttemptCost: routing.DefaultPaymentAttemptPenalty. ToSatoshis(), diff --git a/lnrpc/routerrpc/router.pb.go b/lnrpc/routerrpc/router.pb.go index 0fe976a4c..5463ee709 100644 --- a/lnrpc/routerrpc/router.pb.go +++ b/lnrpc/routerrpc/router.pb.go @@ -1006,8 +1006,6 @@ var xxx_messageInfo_QueryMissionControlRequest proto.InternalMessageInfo /// QueryMissionControlResponse contains mission control state. 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. Pairs []*PairHistory `protobuf:"bytes,2,rep,name=pairs,proto3" json:"pairs,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` @@ -1040,13 +1038,6 @@ func (m *QueryMissionControlResponse) XXX_DiscardUnknown() { var xxx_messageInfo_QueryMissionControlResponse proto.InternalMessageInfo -func (m *QueryMissionControlResponse) GetNodes() []*NodeHistory { - if m != nil { - return m.Nodes - } - return nil -} - func (m *QueryMissionControlResponse) GetPairs() []*PairHistory { if m != nil { return m.Pairs @@ -1054,67 +1045,6 @@ func (m *QueryMissionControlResponse) GetPairs() []*PairHistory { 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 of forwarding towards peers of this node - //for which no specific history is available. - 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{} } -func (m *NodeHistory) String() string { return proto.CompactTextString(m) } -func (*NodeHistory) ProtoMessage() {} -func (*NodeHistory) Descriptor() ([]byte, []int) { - return fileDescriptor_7a0613f69d37b0a5, []int{13} -} - -func (m *NodeHistory) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_NodeHistory.Unmarshal(m, b) -} -func (m *NodeHistory) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_NodeHistory.Marshal(b, m, deterministic) -} -func (m *NodeHistory) XXX_Merge(src proto.Message) { - xxx_messageInfo_NodeHistory.Merge(m, src) -} -func (m *NodeHistory) XXX_Size() int { - return xxx_messageInfo_NodeHistory.Size(m) -} -func (m *NodeHistory) XXX_DiscardUnknown() { - xxx_messageInfo_NodeHistory.DiscardUnknown(m) -} - -var xxx_messageInfo_NodeHistory proto.InternalMessageInfo - -func (m *NodeHistory) GetPubkey() []byte { - if m != nil { - return m.Pubkey - } - return nil -} - -func (m *NodeHistory) GetLastFailTime() int64 { - if m != nil { - return m.LastFailTime - } - return 0 -} - -func (m *NodeHistory) GetOtherSuccessProb() float32 { - if m != nil { - return m.OtherSuccessProb - } - return 0 -} - /// PairHistory contains the mission control state for a particular node pair. type PairHistory struct { /// The source node pubkey of the pair. @@ -1125,8 +1055,6 @@ type PairHistory struct { Timestamp int64 `protobuf:"varint,3,opt,name=timestamp,proto3" json:"timestamp,omitempty"` /// Minimum penalization amount (only applies to failed attempts). 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:"-"` @@ -1138,7 +1066,7 @@ 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} + return fileDescriptor_7a0613f69d37b0a5, []int{13} } func (m *PairHistory) XXX_Unmarshal(b []byte) error { @@ -1187,13 +1115,6 @@ func (m *PairHistory) GetMinPenalizeAmtSat() int64 { return 0 } -func (m *PairHistory) GetSuccessProb() float32 { - if m != nil { - return m.SuccessProb - } - return 0 -} - func (m *PairHistory) GetLastAttemptSuccessful() bool { if m != nil { return m.LastAttemptSuccessful @@ -1227,7 +1148,7 @@ func (m *BuildRouteRequest) Reset() { *m = BuildRouteRequest{} } func (m *BuildRouteRequest) String() string { return proto.CompactTextString(m) } func (*BuildRouteRequest) ProtoMessage() {} func (*BuildRouteRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_7a0613f69d37b0a5, []int{15} + return fileDescriptor_7a0613f69d37b0a5, []int{14} } func (m *BuildRouteRequest) XXX_Unmarshal(b []byte) error { @@ -1289,7 +1210,7 @@ func (m *BuildRouteResponse) Reset() { *m = BuildRouteResponse{} } func (m *BuildRouteResponse) String() string { return proto.CompactTextString(m) } func (*BuildRouteResponse) ProtoMessage() {} func (*BuildRouteResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_7a0613f69d37b0a5, []int{16} + return fileDescriptor_7a0613f69d37b0a5, []int{15} } func (m *BuildRouteResponse) XXX_Unmarshal(b []byte) error { @@ -1334,7 +1255,6 @@ func init() { proto.RegisterType((*ResetMissionControlResponse)(nil), "routerrpc.ResetMissionControlResponse") proto.RegisterType((*QueryMissionControlRequest)(nil), "routerrpc.QueryMissionControlRequest") proto.RegisterType((*QueryMissionControlResponse)(nil), "routerrpc.QueryMissionControlResponse") - proto.RegisterType((*NodeHistory)(nil), "routerrpc.NodeHistory") proto.RegisterType((*PairHistory)(nil), "routerrpc.PairHistory") proto.RegisterType((*BuildRouteRequest)(nil), "routerrpc.BuildRouteRequest") proto.RegisterType((*BuildRouteResponse)(nil), "routerrpc.BuildRouteResponse") @@ -1343,125 +1263,120 @@ func init() { func init() { proto.RegisterFile("routerrpc/router.proto", fileDescriptor_7a0613f69d37b0a5) } var fileDescriptor_7a0613f69d37b0a5 = []byte{ - // 1875 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x58, 0xd1, 0x72, 0x22, 0xb9, - 0xd5, 0xde, 0x36, 0x60, 0xe0, 0x00, 0x76, 0x5b, 0xf6, 0x78, 0x7a, 0xb0, 0xbd, 0xe3, 0x65, 0xf7, - 0x9f, 0x75, 0x4d, 0xcd, 0x6f, 0x4f, 0x9c, 0xda, 0xad, 0xa9, 0xbd, 0x48, 0x0a, 0x83, 0x58, 0xf7, - 0x0c, 0x34, 0x5e, 0x01, 0xb3, 0x3b, 0xc9, 0x85, 0x4a, 0x06, 0xd9, 0x74, 0xb9, 0xe9, 0x66, 0xbb, - 0x85, 0x33, 0xce, 0x45, 0xaa, 0x52, 0xb9, 0xce, 0x73, 0xe4, 0x26, 0x77, 0x79, 0x91, 0x3c, 0x45, - 0xf2, 0x04, 0x7b, 0x9f, 0x92, 0xd4, 0x0d, 0x8d, 0x8d, 0x27, 0xb9, 0x72, 0xeb, 0x3b, 0x9f, 0x8e, - 0xa4, 0x73, 0x8e, 0x3e, 0x1d, 0x0c, 0xbb, 0x61, 0x30, 0x13, 0x3c, 0x0c, 0xa7, 0xc3, 0x13, 0xfd, - 0x75, 0x3c, 0x0d, 0x03, 0x11, 0xa0, 0xe2, 0x1c, 0xaf, 0x16, 0xc3, 0xe9, 0x50, 0xa3, 0xb5, 0x3f, - 0x67, 0x01, 0xf5, 0xb8, 0x3f, 0xba, 0x60, 0x77, 0x13, 0xee, 0x0b, 0xc2, 0x7f, 0x9e, 0xf1, 0x48, - 0x20, 0x04, 0xd9, 0x11, 0x8f, 0x84, 0x65, 0x1c, 0x1a, 0x47, 0x65, 0xa2, 0xbe, 0x91, 0x09, 0x19, - 0x36, 0x11, 0xd6, 0xda, 0xa1, 0x71, 0x94, 0x21, 0xf2, 0x13, 0x7d, 0x01, 0xe5, 0xa9, 0x9e, 0x47, - 0xc7, 0x2c, 0x1a, 0x5b, 0x19, 0xc5, 0x2e, 0xc5, 0xd8, 0x39, 0x8b, 0xc6, 0xe8, 0x08, 0xcc, 0x2b, - 0xd7, 0x67, 0x1e, 0x1d, 0x7a, 0xe2, 0x96, 0x8e, 0xb8, 0x27, 0x98, 0x95, 0x3d, 0x34, 0x8e, 0x72, - 0x64, 0x43, 0xe1, 0x0d, 0x4f, 0xdc, 0x36, 0x25, 0x8a, 0xbe, 0x86, 0xcd, 0xc4, 0x59, 0xa8, 0x77, - 0x61, 0xe5, 0x0e, 0x8d, 0xa3, 0x22, 0xd9, 0x98, 0x2e, 0xef, 0xed, 0x6b, 0xd8, 0x14, 0xee, 0x84, - 0x07, 0x33, 0x41, 0x23, 0x3e, 0x0c, 0xfc, 0x51, 0x64, 0xad, 0x6b, 0x8f, 0x31, 0xdc, 0xd3, 0x28, - 0xaa, 0x41, 0xe5, 0x8a, 0x73, 0xea, 0xb9, 0x13, 0x57, 0xd0, 0x88, 0x09, 0x2b, 0xaf, 0xb6, 0x5e, - 0xba, 0xe2, 0xbc, 0x2d, 0xb1, 0x1e, 0x13, 0xe8, 0x15, 0x98, 0xc1, 0x4c, 0x5c, 0x07, 0xae, 0x7f, - 0x4d, 0x87, 0x63, 0xe6, 0x53, 0x77, 0x64, 0x15, 0x0e, 0x8d, 0xa3, 0xec, 0xd9, 0xda, 0x6b, 0x83, - 0x6c, 0x24, 0xb6, 0xc6, 0x98, 0xf9, 0xf6, 0x08, 0x1d, 0x00, 0xa8, 0x73, 0x28, 0x97, 0x56, 0x51, - 0xad, 0x5a, 0x94, 0x88, 0xf2, 0x87, 0x4e, 0xa1, 0xa4, 0x82, 0x4c, 0xc7, 0xae, 0x2f, 0x22, 0x0b, - 0x0e, 0x33, 0x47, 0xa5, 0x53, 0xf3, 0xd8, 0xf3, 0x65, 0xbc, 0x89, 0xb4, 0x9c, 0xbb, 0xbe, 0x20, - 0x69, 0x12, 0xc2, 0x50, 0x90, 0xd1, 0xa5, 0xc2, 0xbb, 0xb5, 0x4a, 0x6a, 0xc2, 0xcb, 0xe3, 0x79, - 0xa6, 0x8e, 0x1f, 0xa6, 0xe6, 0xb8, 0xc9, 0x23, 0xd1, 0xf7, 0x6e, 0xb1, 0x2f, 0xc2, 0x3b, 0x92, - 0x1f, 0xe9, 0x51, 0xf5, 0x3b, 0x28, 0xa7, 0x0d, 0x32, 0x59, 0x37, 0xfc, 0x4e, 0xe5, 0x2f, 0x4b, - 0xe4, 0x27, 0xda, 0x81, 0xdc, 0x2d, 0xf3, 0x66, 0x5c, 0x25, 0xb0, 0x4c, 0xf4, 0xe0, 0xbb, 0xb5, - 0x37, 0x46, 0xed, 0x0d, 0x6c, 0xf7, 0x43, 0x36, 0xbc, 0xb9, 0x57, 0x03, 0xf7, 0xb3, 0x6b, 0x3c, - 0xc8, 0x6e, 0xed, 0x4f, 0x50, 0x89, 0x27, 0xf5, 0x04, 0x13, 0xb3, 0x08, 0xfd, 0x3f, 0xe4, 0x22, - 0xc1, 0x04, 0x57, 0xe4, 0x8d, 0xd3, 0xa7, 0xa9, 0xa3, 0xa4, 0x88, 0x9c, 0x68, 0x16, 0xaa, 0x42, - 0x61, 0x1a, 0x72, 0x77, 0xc2, 0xae, 0x93, 0x6d, 0xcd, 0xc7, 0xa8, 0x06, 0x39, 0x35, 0x59, 0x55, - 0x55, 0xe9, 0xb4, 0x9c, 0x0e, 0x23, 0xd1, 0xa6, 0xda, 0x6f, 0x60, 0x53, 0x8d, 0x5b, 0x9c, 0x7f, - 0xaa, 0x72, 0x9f, 0x42, 0x9e, 0x4d, 0x74, 0x09, 0xe8, 0xea, 0x5d, 0x67, 0x13, 0x99, 0xfd, 0xda, - 0x08, 0xcc, 0xc5, 0xfc, 0x68, 0x1a, 0xf8, 0x11, 0x97, 0x15, 0x2b, 0x9d, 0xcb, 0x82, 0x90, 0xd5, - 0x33, 0x91, 0xb3, 0x0c, 0x35, 0x6b, 0x23, 0xc6, 0x5b, 0x9c, 0x77, 0x22, 0x26, 0xd0, 0x0b, 0x5d, - 0x88, 0xd4, 0x0b, 0x86, 0x37, 0xb2, 0xb4, 0xd9, 0x5d, 0xec, 0xbe, 0x22, 0xe1, 0x76, 0x30, 0xbc, - 0x69, 0x4a, 0xb0, 0xf6, 0x7b, 0x7d, 0xc5, 0xfa, 0x81, 0xde, 0xfb, 0xff, 0x1c, 0xde, 0x45, 0x08, - 0xd6, 0x1e, 0x0f, 0x01, 0x85, 0xed, 0x25, 0xe7, 0xf1, 0x29, 0xd2, 0x91, 0x35, 0xee, 0x45, 0xf6, - 0x15, 0xe4, 0xaf, 0x98, 0xeb, 0xcd, 0xc2, 0xc4, 0x31, 0x4a, 0xa5, 0xa9, 0xa5, 0x2d, 0x24, 0xa1, - 0xd4, 0x7e, 0xc9, 0x43, 0x3e, 0x06, 0xd1, 0x29, 0x64, 0x87, 0xc1, 0x28, 0xc9, 0xee, 0xe7, 0x0f, - 0xa7, 0x25, 0x7f, 0x1b, 0xc1, 0x88, 0x13, 0xc5, 0x45, 0xbf, 0x85, 0x0d, 0x79, 0xb1, 0x7c, 0xee, - 0xd1, 0xd9, 0x74, 0xc4, 0xe6, 0x09, 0xb5, 0x52, 0xb3, 0x1b, 0x9a, 0x30, 0x50, 0x76, 0x52, 0x19, - 0xa6, 0x87, 0x68, 0x0f, 0x8a, 0x63, 0xe1, 0x0d, 0x75, 0x26, 0xb2, 0xaa, 0xa0, 0x0b, 0x12, 0x50, - 0x39, 0xa8, 0x41, 0x25, 0xf0, 0xdd, 0xc0, 0xa7, 0xd1, 0x98, 0xd1, 0xd3, 0x6f, 0xbe, 0x55, 0x9a, - 0x51, 0x26, 0x25, 0x05, 0xf6, 0xc6, 0xec, 0xf4, 0x9b, 0x6f, 0xd1, 0x73, 0x28, 0xa9, 0x5b, 0xcb, - 0x3f, 0x4e, 0xdd, 0xf0, 0x4e, 0x89, 0x45, 0x85, 0xa8, 0x8b, 0x8c, 0x15, 0x22, 0xaf, 0xc6, 0x95, - 0xc7, 0xae, 0x23, 0x25, 0x10, 0x15, 0xa2, 0x07, 0xe8, 0x35, 0xec, 0xc4, 0x31, 0xa0, 0x51, 0x30, - 0x0b, 0x87, 0x9c, 0xba, 0xfe, 0x88, 0x7f, 0x54, 0xf2, 0x50, 0x21, 0x28, 0xb6, 0xf5, 0x94, 0xc9, - 0x96, 0x16, 0xb4, 0x0b, 0xeb, 0x63, 0xee, 0x5e, 0x8f, 0xb5, 0x34, 0x54, 0x48, 0x3c, 0xaa, 0xfd, - 0x3d, 0x07, 0xa5, 0x54, 0x60, 0x50, 0x19, 0x0a, 0x04, 0xf7, 0x30, 0x79, 0x8f, 0x9b, 0xe6, 0x67, - 0xe8, 0x08, 0xbe, 0xb2, 0x9d, 0x46, 0x97, 0x10, 0xdc, 0xe8, 0xd3, 0x2e, 0xa1, 0x03, 0xe7, 0x9d, - 0xd3, 0xfd, 0xd1, 0xa1, 0x17, 0xf5, 0x0f, 0x1d, 0xec, 0xf4, 0x69, 0x13, 0xf7, 0xeb, 0x76, 0xbb, - 0x67, 0x1a, 0x68, 0x1f, 0xac, 0x05, 0x33, 0x31, 0xd7, 0x3b, 0xdd, 0x81, 0xd3, 0x37, 0xd7, 0xd0, - 0x73, 0xd8, 0x6b, 0xd9, 0x4e, 0xbd, 0x4d, 0x17, 0x9c, 0x46, 0xbb, 0xff, 0x9e, 0xe2, 0x9f, 0x2e, - 0x6c, 0xf2, 0xc1, 0xcc, 0xac, 0x22, 0x9c, 0xf7, 0xdb, 0x8d, 0xc4, 0x43, 0x16, 0x3d, 0x83, 0x27, - 0x9a, 0xa0, 0xa7, 0xd0, 0x7e, 0xb7, 0x4b, 0x7b, 0xdd, 0xae, 0x63, 0xe6, 0xd0, 0x16, 0x54, 0x6c, - 0xe7, 0x7d, 0xbd, 0x6d, 0x37, 0x29, 0xc1, 0xf5, 0x76, 0xc7, 0x5c, 0x47, 0xdb, 0xb0, 0x79, 0x9f, - 0x97, 0x97, 0x2e, 0x12, 0x5e, 0xd7, 0xb1, 0xbb, 0x0e, 0x7d, 0x8f, 0x49, 0xcf, 0xee, 0x3a, 0x66, - 0x01, 0xed, 0x02, 0x5a, 0x36, 0x9d, 0x77, 0xea, 0x0d, 0xb3, 0x88, 0x9e, 0xc0, 0xd6, 0x32, 0xfe, - 0x0e, 0x7f, 0x30, 0x01, 0x59, 0xb0, 0xa3, 0x37, 0x46, 0xcf, 0x70, 0xbb, 0xfb, 0x23, 0xed, 0xd8, - 0x8e, 0xdd, 0x19, 0x74, 0xcc, 0x12, 0xda, 0x01, 0xb3, 0x85, 0x31, 0xb5, 0x9d, 0xde, 0xa0, 0xd5, - 0xb2, 0x1b, 0x36, 0x76, 0xfa, 0x66, 0x59, 0xaf, 0xbc, 0xea, 0xe0, 0x15, 0x39, 0xa1, 0x71, 0x5e, - 0x77, 0x1c, 0xdc, 0xa6, 0x4d, 0xbb, 0x57, 0x3f, 0x6b, 0xe3, 0xa6, 0xb9, 0x81, 0x0e, 0xe0, 0x59, - 0x1f, 0x77, 0x2e, 0xba, 0xa4, 0x4e, 0x3e, 0xd0, 0xc4, 0xde, 0xaa, 0xdb, 0xed, 0x01, 0xc1, 0xe6, - 0x26, 0xfa, 0x02, 0x0e, 0x08, 0xfe, 0x61, 0x60, 0x13, 0xdc, 0xa4, 0x4e, 0xb7, 0x89, 0x69, 0x0b, - 0xd7, 0xfb, 0x03, 0x82, 0x69, 0xc7, 0xee, 0xf5, 0x6c, 0xe7, 0x7b, 0xd3, 0x44, 0x5f, 0xc1, 0xe1, - 0x9c, 0x32, 0x77, 0x70, 0x8f, 0xb5, 0x25, 0xcf, 0x97, 0xa4, 0xd4, 0xc1, 0x3f, 0xf5, 0xe9, 0x05, - 0xc6, 0xc4, 0x44, 0xa8, 0x0a, 0xbb, 0x8b, 0xe5, 0xf5, 0x02, 0xf1, 0xda, 0xdb, 0xd2, 0x76, 0x81, - 0x49, 0xa7, 0xee, 0xc8, 0x04, 0x2f, 0xd9, 0x76, 0xe4, 0xb6, 0x17, 0xb6, 0xfb, 0xdb, 0x7e, 0x82, - 0x10, 0x6c, 0xa4, 0xb2, 0xd2, 0xaa, 0x13, 0x73, 0x17, 0xed, 0xc0, 0x66, 0xb2, 0x83, 0x84, 0xf8, - 0xaf, 0x3c, 0x7a, 0x0a, 0x68, 0xe0, 0x10, 0x5c, 0x6f, 0xca, 0x80, 0xcc, 0x0d, 0xff, 0xce, 0xbf, - 0xcd, 0x16, 0xd6, 0xcc, 0x4c, 0xed, 0x1f, 0x19, 0xa8, 0x2c, 0xdd, 0x4b, 0xb4, 0x0f, 0xc5, 0xc8, - 0xbd, 0xf6, 0x99, 0x90, 0xca, 0xa1, 0x45, 0x65, 0x01, 0xa8, 0xb7, 0x71, 0xcc, 0x5c, 0x5f, 0xab, - 0x99, 0x56, 0xf3, 0xa2, 0x42, 0x94, 0x96, 0xed, 0x41, 0x3e, 0x79, 0x5f, 0x33, 0xf3, 0xf7, 0x75, - 0x7d, 0xa8, 0xdf, 0xd5, 0x7d, 0x28, 0x4a, 0xc9, 0x8c, 0x04, 0x9b, 0x4c, 0xd5, 0x15, 0xaf, 0x90, - 0x05, 0x80, 0xbe, 0x84, 0xca, 0x84, 0x47, 0x11, 0xbb, 0xe6, 0x54, 0x5f, 0x53, 0x50, 0x8c, 0x72, - 0x0c, 0xb6, 0xd4, 0x6d, 0xfd, 0x12, 0x12, 0xd9, 0x88, 0x49, 0x39, 0x4d, 0x8a, 0x41, 0x4d, 0xba, - 0xaf, 0xd8, 0x82, 0xc5, 0x6a, 0x90, 0x56, 0x6c, 0xc1, 0xd0, 0x4b, 0xd8, 0xd2, 0x92, 0xe3, 0xfa, - 0xee, 0x64, 0x36, 0xd1, 0xd2, 0x93, 0x57, 0xd2, 0xb3, 0xa9, 0xa4, 0x47, 0xe3, 0x4a, 0x81, 0x9e, - 0x41, 0xe1, 0x92, 0x45, 0x5c, 0x3e, 0x16, 0xb1, 0x34, 0xe4, 0xe5, 0xb8, 0xc5, 0xb9, 0x34, 0xc9, - 0x27, 0x24, 0x94, 0xa2, 0xa7, 0x15, 0x21, 0x7f, 0xc5, 0x39, 0x91, 0xb1, 0x9c, 0xaf, 0xc0, 0x3e, - 0x2e, 0x56, 0x28, 0xa5, 0x56, 0xd0, 0xb8, 0x5a, 0xe1, 0x25, 0x6c, 0xf1, 0x8f, 0x22, 0x64, 0x34, - 0x98, 0xb2, 0x9f, 0x67, 0x9c, 0x8e, 0x98, 0x60, 0x56, 0x59, 0x05, 0x78, 0x53, 0x19, 0xba, 0x0a, - 0x6f, 0x32, 0xc1, 0x6a, 0xfb, 0x50, 0x25, 0x3c, 0xe2, 0xa2, 0xe3, 0x46, 0x91, 0x1b, 0xf8, 0x8d, - 0xc0, 0x17, 0x61, 0xe0, 0xc5, 0x6f, 0x4e, 0xed, 0x00, 0xf6, 0x56, 0x5a, 0xf5, 0xa3, 0x21, 0x27, - 0xff, 0x30, 0xe3, 0xe1, 0xdd, 0xea, 0xc9, 0x77, 0xb0, 0xb7, 0xd2, 0x1a, 0xbf, 0x38, 0xaf, 0x20, - 0xe7, 0x07, 0x23, 0x1e, 0x59, 0x86, 0xea, 0x62, 0x76, 0x53, 0xf2, 0xee, 0x04, 0x23, 0x7e, 0xee, - 0x46, 0x22, 0x08, 0xef, 0x88, 0x26, 0x49, 0xf6, 0x94, 0xb9, 0x61, 0x64, 0xad, 0x3d, 0x60, 0x5f, - 0x30, 0x37, 0x9c, 0xb3, 0x15, 0xa9, 0xf6, 0x17, 0x03, 0x4a, 0x29, 0x27, 0x52, 0x68, 0xa7, 0xb3, - 0xcb, 0xa4, 0xc1, 0x29, 0x93, 0x78, 0x84, 0x5e, 0xc0, 0x86, 0xc7, 0x22, 0x41, 0xa5, 0x36, 0x53, - 0x99, 0xd2, 0xf8, 0x41, 0xbe, 0x87, 0xa2, 0x63, 0x40, 0x81, 0x18, 0xf3, 0x90, 0x46, 0xb3, 0xe1, - 0x90, 0x47, 0x11, 0x9d, 0x86, 0xc1, 0xa5, 0xaa, 0xcb, 0x35, 0xb2, 0xc2, 0xf2, 0x36, 0x5b, 0xc8, - 0x9a, 0xb9, 0xda, 0x2f, 0x06, 0x94, 0x52, 0x9b, 0x93, 0x55, 0x2b, 0x0f, 0x43, 0xaf, 0xc2, 0x60, - 0x92, 0xdc, 0x87, 0x39, 0x80, 0x2c, 0xc8, 0xab, 0x81, 0x08, 0xe2, 0xcb, 0x90, 0x0c, 0x97, 0xab, - 0x3d, 0xa3, 0x36, 0x98, 0xaa, 0xf6, 0x53, 0xd8, 0x99, 0xb8, 0x3e, 0x9d, 0x72, 0x9f, 0x79, 0xee, - 0x1f, 0x39, 0x4d, 0x3a, 0x97, 0xac, 0x22, 0xae, 0xb4, 0xa1, 0x1a, 0x94, 0x97, 0x4e, 0x92, 0x53, - 0x27, 0x59, 0xc2, 0xd0, 0x1b, 0x78, 0xaa, 0xa2, 0xc0, 0x84, 0xe0, 0x93, 0xa9, 0x48, 0x0e, 0x78, - 0x35, 0xf3, 0xd4, 0x1d, 0x28, 0x90, 0xc7, 0xcc, 0xb5, 0xbf, 0x19, 0xb0, 0x75, 0x36, 0x73, 0xbd, - 0xd1, 0x52, 0xff, 0xf2, 0x0c, 0x0a, 0x72, 0xf9, 0x54, 0x7f, 0x24, 0x9b, 0x2c, 0x55, 0xb0, 0xab, - 0x9a, 0xfe, 0xb5, 0x95, 0x4d, 0xff, 0xaa, 0xf6, 0x3b, 0xf3, 0x68, 0xfb, 0xfd, 0x1c, 0x4a, 0xe3, - 0x60, 0x4a, 0x75, 0xb2, 0x23, 0x2b, 0x7b, 0x98, 0x39, 0x2a, 0x13, 0x18, 0x07, 0xd3, 0x0b, 0x8d, - 0xd4, 0xde, 0x00, 0x4a, 0x6f, 0x34, 0xae, 0xcc, 0x79, 0x1b, 0x65, 0x3c, 0xda, 0x46, 0xbd, 0xfc, - 0xab, 0x01, 0xe5, 0x74, 0x87, 0x8a, 0x2a, 0x50, 0xb4, 0x1d, 0xda, 0x6a, 0xdb, 0xdf, 0x9f, 0xf7, - 0xcd, 0xcf, 0xe4, 0xb0, 0x37, 0x68, 0x34, 0x30, 0x6e, 0xe2, 0xa6, 0x69, 0x48, 0x95, 0x95, 0x82, - 0x89, 0x9b, 0xb4, 0x6f, 0x77, 0x70, 0x77, 0x20, 0xdf, 0xdf, 0x6d, 0xd8, 0x8c, 0x31, 0xa7, 0x4b, - 0x49, 0x77, 0xd0, 0xc7, 0x66, 0x06, 0x99, 0x50, 0x8e, 0x41, 0x4c, 0x48, 0x97, 0x98, 0x59, 0xf9, - 0x68, 0xc4, 0xc8, 0xc3, 0xb7, 0x3c, 0x79, 0xea, 0x73, 0xa7, 0xff, 0xcc, 0xc2, 0xba, 0xda, 0x60, - 0x88, 0xce, 0xa1, 0x94, 0xfa, 0x19, 0x80, 0x0e, 0x3e, 0xf9, 0xf3, 0xa0, 0x6a, 0xad, 0x6e, 0xb9, - 0x67, 0xd1, 0x6b, 0x03, 0xbd, 0x85, 0x72, 0xba, 0xd1, 0x47, 0xe9, 0x06, 0x6e, 0xc5, 0x2f, 0x80, - 0x4f, 0xfa, 0x7a, 0x07, 0x26, 0x8e, 0x84, 0x3b, 0x91, 0x0d, 0x5b, 0xdc, 0x42, 0xa3, 0x6a, 0x8a, - 0x7f, 0xaf, 0x2f, 0xaf, 0xee, 0xad, 0xb4, 0xc5, 0x19, 0x6a, 0xeb, 0x23, 0xc6, 0x4d, 0xec, 0x83, - 0x23, 0x2e, 0x77, 0xce, 0xd5, 0xcf, 0x1f, 0x33, 0xc7, 0xde, 0x46, 0xb0, 0xbd, 0x42, 0xe5, 0xd0, - 0xff, 0xa5, 0x77, 0xf0, 0xa8, 0x46, 0x56, 0x5f, 0xfc, 0x37, 0xda, 0x62, 0x95, 0x15, 0x72, 0xb8, - 0xb4, 0xca, 0xe3, 0x62, 0xba, 0xb4, 0xca, 0xa7, 0x54, 0xd5, 0x06, 0x58, 0x54, 0x34, 0xda, 0x4f, - 0xcd, 0x7a, 0x70, 0x23, 0xab, 0x07, 0x8f, 0x58, 0xb5, 0xab, 0xb3, 0x5f, 0xfd, 0xee, 0xe4, 0xda, - 0x15, 0xe3, 0xd9, 0xe5, 0xf1, 0x30, 0x98, 0x9c, 0x78, 0xb2, 0x33, 0xf5, 0x5d, 0xff, 0xda, 0xe7, - 0xe2, 0x0f, 0x41, 0x78, 0x73, 0xe2, 0xf9, 0xa3, 0x13, 0x75, 0x31, 0x4e, 0xe6, 0x5e, 0x2e, 0xd7, - 0xd5, 0x3f, 0x09, 0x7e, 0xfd, 0x9f, 0x00, 0x00, 0x00, 0xff, 0xff, 0xe1, 0xb1, 0x0f, 0x8c, 0x54, - 0x10, 0x00, 0x00, + // 1805 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x57, 0x4f, 0x73, 0x1a, 0xc9, + 0x15, 0xdf, 0x11, 0x20, 0xe0, 0x01, 0xd2, 0xa8, 0x25, 0xcb, 0x63, 0x64, 0xad, 0xb5, 0xec, 0x66, + 0x57, 0xe5, 0x72, 0x24, 0x47, 0xa9, 0xdd, 0x72, 0xed, 0x21, 0x29, 0x0c, 0xcd, 0x6a, 0x6c, 0x98, + 0x91, 0x1b, 0xf0, 0xae, 0x93, 0x43, 0x57, 0x1b, 0x5a, 0x62, 0x4a, 0xc3, 0x0c, 0x3b, 0xd3, 0x38, + 0x56, 0x0e, 0xa9, 0xca, 0x07, 0xc8, 0xe7, 0xc8, 0x25, 0xb7, 0x7c, 0x91, 0xdc, 0xf2, 0x0d, 0x92, + 0x4f, 0x90, 0x7b, 0xaa, 0xbb, 0x67, 0x60, 0x90, 0x90, 0x93, 0x93, 0xe8, 0xdf, 0xfb, 0xd7, 0xf3, + 0xde, 0xeb, 0xdf, 0x7b, 0x82, 0xfd, 0x28, 0x9c, 0x0b, 0x1e, 0x45, 0xb3, 0xd1, 0xa9, 0xfe, 0x75, + 0x32, 0x8b, 0x42, 0x11, 0xa2, 0xf2, 0x02, 0xaf, 0x97, 0xa3, 0xd9, 0x48, 0xa3, 0x8d, 0x3f, 0xe7, + 0x01, 0xf5, 0x79, 0x30, 0xbe, 0x60, 0x37, 0x53, 0x1e, 0x08, 0xc2, 0x7f, 0x9e, 0xf3, 0x58, 0x20, + 0x04, 0xf9, 0x31, 0x8f, 0x85, 0x65, 0x1c, 0x19, 0xc7, 0x55, 0xa2, 0x7e, 0x23, 0x13, 0x72, 0x6c, + 0x2a, 0xac, 0x8d, 0x23, 0xe3, 0x38, 0x47, 0xe4, 0x4f, 0xf4, 0x05, 0x54, 0x67, 0xda, 0x8e, 0x4e, + 0x58, 0x3c, 0xb1, 0x72, 0x4a, 0xbb, 0x92, 0x60, 0xe7, 0x2c, 0x9e, 0xa0, 0x63, 0x30, 0x2f, 0xbd, + 0x80, 0xf9, 0x74, 0xe4, 0x8b, 0x0f, 0x74, 0xcc, 0x7d, 0xc1, 0xac, 0xfc, 0x91, 0x71, 0x5c, 0x20, + 0x5b, 0x0a, 0x6f, 0xf9, 0xe2, 0x43, 0x5b, 0xa2, 0xe8, 0x1b, 0xd8, 0x4e, 0x9d, 0x45, 0xfa, 0x16, + 0x56, 0xe1, 0xc8, 0x38, 0x2e, 0x93, 0xad, 0xd9, 0xea, 0xdd, 0xbe, 0x81, 0x6d, 0xe1, 0x4d, 0x79, + 0x38, 0x17, 0x34, 0xe6, 0xa3, 0x30, 0x18, 0xc7, 0xd6, 0xa6, 0xf6, 0x98, 0xc0, 0x7d, 0x8d, 0xa2, + 0x06, 0xd4, 0x2e, 0x39, 0xa7, 0xbe, 0x37, 0xf5, 0x04, 0x8d, 0x99, 0xb0, 0x8a, 0xea, 0xea, 0x95, + 0x4b, 0xce, 0xbb, 0x12, 0xeb, 0x33, 0x81, 0x9e, 0x81, 0x19, 0xce, 0xc5, 0x55, 0xe8, 0x05, 0x57, + 0x74, 0x34, 0x61, 0x01, 0xf5, 0xc6, 0x56, 0xe9, 0xc8, 0x38, 0xce, 0xbf, 0xdc, 0x78, 0x6e, 0x90, + 0xad, 0x54, 0xd6, 0x9a, 0xb0, 0xc0, 0x1e, 0xa3, 0x43, 0x00, 0xf5, 0x1d, 0xca, 0xa5, 0x55, 0x56, + 0x51, 0xcb, 0x12, 0x51, 0xfe, 0xd0, 0x19, 0x54, 0x54, 0x92, 0xe9, 0xc4, 0x0b, 0x44, 0x6c, 0xc1, + 0x51, 0xee, 0xb8, 0x72, 0x66, 0x9e, 0xf8, 0x81, 0xcc, 0x37, 0x91, 0x92, 0x73, 0x2f, 0x10, 0x24, + 0xab, 0x84, 0x30, 0x94, 0x64, 0x76, 0xa9, 0xf0, 0x3f, 0x58, 0x15, 0x65, 0xf0, 0xf4, 0x64, 0x51, + 0xa9, 0x93, 0xbb, 0xa5, 0x39, 0x69, 0xf3, 0x58, 0x0c, 0xfc, 0x0f, 0x38, 0x10, 0xd1, 0x0d, 0x29, + 0x8e, 0xf5, 0xa9, 0xfe, 0x3d, 0x54, 0xb3, 0x02, 0x59, 0xac, 0x6b, 0x7e, 0xa3, 0xea, 0x97, 0x27, + 0xf2, 0x27, 0xda, 0x83, 0xc2, 0x07, 0xe6, 0xcf, 0xb9, 0x2a, 0x60, 0x95, 0xe8, 0xc3, 0xf7, 0x1b, + 0x2f, 0x8c, 0xc6, 0x0b, 0xd8, 0x1d, 0x44, 0x6c, 0x74, 0x7d, 0xab, 0x07, 0x6e, 0x57, 0xd7, 0xb8, + 0x53, 0xdd, 0xc6, 0x9f, 0xa0, 0x96, 0x18, 0xf5, 0x05, 0x13, 0xf3, 0x18, 0xfd, 0x12, 0x0a, 0xb1, + 0x60, 0x82, 0x2b, 0xe5, 0xad, 0xb3, 0x87, 0x99, 0x4f, 0xc9, 0x28, 0x72, 0xa2, 0xb5, 0x50, 0x1d, + 0x4a, 0xb3, 0x88, 0x7b, 0x53, 0x76, 0x95, 0x5e, 0x6b, 0x71, 0x46, 0x0d, 0x28, 0x28, 0x63, 0xd5, + 0x55, 0x95, 0xb3, 0x6a, 0x36, 0x8d, 0x44, 0x8b, 0x1a, 0xbf, 0x81, 0x6d, 0x75, 0xee, 0x70, 0xfe, + 0xa9, 0xce, 0x7d, 0x08, 0x45, 0x36, 0xd5, 0x2d, 0xa0, 0xbb, 0x77, 0x93, 0x4d, 0x65, 0xf5, 0x1b, + 0x63, 0x30, 0x97, 0xf6, 0xf1, 0x2c, 0x0c, 0x62, 0x2e, 0x3b, 0x56, 0x3a, 0x97, 0x0d, 0x21, 0xbb, + 0x67, 0x2a, 0xad, 0x0c, 0x65, 0xb5, 0x95, 0xe0, 0x1d, 0xce, 0x7b, 0x31, 0x13, 0xe8, 0x6b, 0xdd, + 0x88, 0xd4, 0x0f, 0x47, 0xd7, 0xb2, 0xb5, 0xd9, 0x4d, 0xe2, 0xbe, 0x26, 0xe1, 0x6e, 0x38, 0xba, + 0x6e, 0x4b, 0xb0, 0xf1, 0x7b, 0xfd, 0xc4, 0x06, 0xa1, 0xbe, 0xfb, 0xff, 0x9d, 0xde, 0x65, 0x0a, + 0x36, 0xee, 0x4f, 0x01, 0x85, 0xdd, 0x15, 0xe7, 0xc9, 0x57, 0x64, 0x33, 0x6b, 0xdc, 0xca, 0xec, + 0x33, 0x28, 0x5e, 0x32, 0xcf, 0x9f, 0x47, 0xa9, 0x63, 0x94, 0x29, 0x53, 0x47, 0x4b, 0x48, 0xaa, + 0xd2, 0xf8, 0x4f, 0x11, 0x8a, 0x09, 0x88, 0xce, 0x20, 0x3f, 0x0a, 0xc7, 0x69, 0x75, 0x3f, 0xbf, + 0x6b, 0x96, 0xfe, 0x6d, 0x85, 0x63, 0x4e, 0x94, 0x2e, 0xfa, 0x2d, 0x6c, 0xc9, 0x87, 0x15, 0x70, + 0x9f, 0xce, 0x67, 0x63, 0xb6, 0x28, 0xa8, 0x95, 0xb1, 0x6e, 0x69, 0x85, 0xa1, 0x92, 0x93, 0xda, + 0x28, 0x7b, 0x44, 0x07, 0x50, 0x9e, 0x08, 0x7f, 0xa4, 0x2b, 0x91, 0x57, 0x0d, 0x5d, 0x92, 0x80, + 0xaa, 0x41, 0x03, 0x6a, 0x61, 0xe0, 0x85, 0x01, 0x8d, 0x27, 0x8c, 0x9e, 0x7d, 0xfb, 0x9d, 0xe2, + 0x8c, 0x2a, 0xa9, 0x28, 0xb0, 0x3f, 0x61, 0x67, 0xdf, 0x7e, 0x87, 0x9e, 0x40, 0x45, 0xbd, 0x5a, + 0xfe, 0x71, 0xe6, 0x45, 0x37, 0x8a, 0x2c, 0x6a, 0x44, 0x3d, 0x64, 0xac, 0x10, 0xf9, 0x34, 0x2e, + 0x7d, 0x76, 0x15, 0x2b, 0x82, 0xa8, 0x11, 0x7d, 0x40, 0xcf, 0x61, 0x2f, 0xc9, 0x01, 0x8d, 0xc3, + 0x79, 0x34, 0xe2, 0xd4, 0x0b, 0xc6, 0xfc, 0xa3, 0xa2, 0x87, 0x1a, 0x41, 0x89, 0xac, 0xaf, 0x44, + 0xb6, 0x94, 0xa0, 0x7d, 0xd8, 0x9c, 0x70, 0xef, 0x6a, 0xa2, 0xa9, 0xa1, 0x46, 0x92, 0x53, 0xe3, + 0x6f, 0x05, 0xa8, 0x64, 0x12, 0x83, 0xaa, 0x50, 0x22, 0xb8, 0x8f, 0xc9, 0x5b, 0xdc, 0x36, 0x3f, + 0x43, 0xc7, 0xf0, 0x95, 0xed, 0xb4, 0x5c, 0x42, 0x70, 0x6b, 0x40, 0x5d, 0x42, 0x87, 0xce, 0x6b, + 0xc7, 0xfd, 0xd1, 0xa1, 0x17, 0xcd, 0x77, 0x3d, 0xec, 0x0c, 0x68, 0x1b, 0x0f, 0x9a, 0x76, 0xb7, + 0x6f, 0x1a, 0xe8, 0x31, 0x58, 0x4b, 0xcd, 0x54, 0xdc, 0xec, 0xb9, 0x43, 0x67, 0x60, 0x6e, 0xa0, + 0x27, 0x70, 0xd0, 0xb1, 0x9d, 0x66, 0x97, 0x2e, 0x75, 0x5a, 0xdd, 0xc1, 0x5b, 0x8a, 0x7f, 0xba, + 0xb0, 0xc9, 0x3b, 0x33, 0xb7, 0x4e, 0xe1, 0x7c, 0xd0, 0x6d, 0xa5, 0x1e, 0xf2, 0xe8, 0x11, 0x3c, + 0xd0, 0x0a, 0xda, 0x84, 0x0e, 0x5c, 0x97, 0xf6, 0x5d, 0xd7, 0x31, 0x0b, 0x68, 0x07, 0x6a, 0xb6, + 0xf3, 0xb6, 0xd9, 0xb5, 0xdb, 0x94, 0xe0, 0x66, 0xb7, 0x67, 0x6e, 0xa2, 0x5d, 0xd8, 0xbe, 0xad, + 0x57, 0x94, 0x2e, 0x52, 0x3d, 0xd7, 0xb1, 0x5d, 0x87, 0xbe, 0xc5, 0xa4, 0x6f, 0xbb, 0x8e, 0x59, + 0x42, 0xfb, 0x80, 0x56, 0x45, 0xe7, 0xbd, 0x66, 0xcb, 0x2c, 0xa3, 0x07, 0xb0, 0xb3, 0x8a, 0xbf, + 0xc6, 0xef, 0x4c, 0x40, 0x16, 0xec, 0xe9, 0x8b, 0xd1, 0x97, 0xb8, 0xeb, 0xfe, 0x48, 0x7b, 0xb6, + 0x63, 0xf7, 0x86, 0x3d, 0xb3, 0x82, 0xf6, 0xc0, 0xec, 0x60, 0x4c, 0x6d, 0xa7, 0x3f, 0xec, 0x74, + 0xec, 0x96, 0x8d, 0x9d, 0x81, 0x59, 0xd5, 0x91, 0xd7, 0x7d, 0x78, 0x4d, 0x1a, 0xb4, 0xce, 0x9b, + 0x8e, 0x83, 0xbb, 0xb4, 0x6d, 0xf7, 0x9b, 0x2f, 0xbb, 0xb8, 0x6d, 0x6e, 0xa1, 0x43, 0x78, 0x34, + 0xc0, 0xbd, 0x0b, 0x97, 0x34, 0xc9, 0x3b, 0x9a, 0xca, 0x3b, 0x4d, 0xbb, 0x3b, 0x24, 0xd8, 0xdc, + 0x46, 0x5f, 0xc0, 0x21, 0xc1, 0x6f, 0x86, 0x36, 0xc1, 0x6d, 0xea, 0xb8, 0x6d, 0x4c, 0x3b, 0xb8, + 0x39, 0x18, 0x12, 0x4c, 0x7b, 0x76, 0xbf, 0x6f, 0x3b, 0x3f, 0x98, 0x26, 0xfa, 0x0a, 0x8e, 0x16, + 0x2a, 0x0b, 0x07, 0xb7, 0xb4, 0x76, 0xe4, 0xf7, 0xa5, 0x25, 0x75, 0xf0, 0x4f, 0x03, 0x7a, 0x81, + 0x31, 0x31, 0x11, 0xaa, 0xc3, 0xfe, 0x32, 0xbc, 0x0e, 0x90, 0xc4, 0xde, 0x95, 0xb2, 0x0b, 0x4c, + 0x7a, 0x4d, 0x47, 0x16, 0x78, 0x45, 0xb6, 0x27, 0xaf, 0xbd, 0x94, 0xdd, 0xbe, 0xf6, 0x03, 0x84, + 0x60, 0x2b, 0x53, 0x95, 0x4e, 0x93, 0x98, 0xfb, 0x68, 0x0f, 0xb6, 0xd3, 0x1b, 0xa4, 0x8a, 0xff, + 0x2a, 0xa2, 0x87, 0x80, 0x86, 0x0e, 0xc1, 0xcd, 0xb6, 0x4c, 0xc8, 0x42, 0xf0, 0xef, 0xe2, 0xab, + 0x7c, 0x69, 0xc3, 0xcc, 0x35, 0xfe, 0x9e, 0x83, 0xda, 0xca, 0xbb, 0x44, 0x8f, 0xa1, 0x1c, 0x7b, + 0x57, 0x01, 0x13, 0x92, 0x39, 0x34, 0xa9, 0x2c, 0x01, 0x35, 0x1b, 0x27, 0xcc, 0x0b, 0x34, 0x9b, + 0x69, 0x36, 0x2f, 0x2b, 0x44, 0x71, 0xd9, 0x01, 0x14, 0xd3, 0xf9, 0x9a, 0x5b, 0xcc, 0xd7, 0xcd, + 0x91, 0x9e, 0xab, 0x8f, 0xa1, 0x2c, 0x29, 0x33, 0x16, 0x6c, 0x3a, 0x53, 0x4f, 0xbc, 0x46, 0x96, + 0x00, 0xfa, 0x12, 0x6a, 0x53, 0x1e, 0xc7, 0xec, 0x8a, 0x53, 0xfd, 0x4c, 0x41, 0x69, 0x54, 0x13, + 0xb0, 0xa3, 0x5e, 0xeb, 0x97, 0x90, 0xd2, 0x46, 0xa2, 0x54, 0xd0, 0x4a, 0x09, 0xa8, 0x95, 0x6e, + 0x33, 0xb6, 0x60, 0x09, 0x1b, 0x64, 0x19, 0x5b, 0x30, 0xf4, 0x14, 0x76, 0x34, 0xe5, 0x78, 0x81, + 0x37, 0x9d, 0x4f, 0x35, 0xf5, 0x14, 0x15, 0xf5, 0x6c, 0x2b, 0xea, 0xd1, 0xb8, 0x62, 0xa0, 0x47, + 0x50, 0x7a, 0xcf, 0x62, 0x2e, 0x87, 0x45, 0x42, 0x0d, 0x45, 0x79, 0xee, 0x70, 0x2e, 0x45, 0x72, + 0x84, 0x44, 0x92, 0xf4, 0x34, 0x23, 0x14, 0x2f, 0x39, 0x27, 0x32, 0x97, 0x8b, 0x08, 0xec, 0xe3, + 0x32, 0x42, 0x25, 0x13, 0x41, 0xe3, 0x2a, 0xc2, 0x53, 0xd8, 0xe1, 0x1f, 0x45, 0xc4, 0x68, 0x38, + 0x63, 0x3f, 0xcf, 0x39, 0x1d, 0x33, 0xc1, 0xac, 0xaa, 0x4a, 0xf0, 0xb6, 0x12, 0xb8, 0x0a, 0x6f, + 0x33, 0xc1, 0x1a, 0x8f, 0xa1, 0x4e, 0x78, 0xcc, 0x45, 0xcf, 0x8b, 0x63, 0x2f, 0x0c, 0x5a, 0x61, + 0x20, 0xa2, 0xd0, 0x4f, 0x66, 0x4e, 0xe3, 0x10, 0x0e, 0xd6, 0x4a, 0xf5, 0xd0, 0x90, 0xc6, 0x6f, + 0xe6, 0x3c, 0xba, 0x59, 0x6f, 0xfc, 0x06, 0x0e, 0xd6, 0x4a, 0x93, 0x89, 0xf3, 0x0c, 0x0a, 0x33, + 0xe6, 0x45, 0xb1, 0xb5, 0xa1, 0xb6, 0x98, 0xfd, 0x95, 0xd1, 0xef, 0x45, 0xe7, 0x5e, 0x2c, 0xc2, + 0xe8, 0x86, 0x68, 0xa5, 0x57, 0xf9, 0x92, 0x61, 0x6e, 0x34, 0xfe, 0x69, 0x40, 0x25, 0x23, 0x94, + 0x7d, 0x10, 0x84, 0x63, 0x4e, 0x2f, 0xa3, 0x70, 0x9a, 0x76, 0xd8, 0x02, 0x40, 0x16, 0x14, 0xd5, + 0x41, 0x84, 0x49, 0x7b, 0xa5, 0xc7, 0xd5, 0xfe, 0xc9, 0xa9, 0x19, 0x9c, 0xe9, 0x9f, 0x33, 0xd8, + 0x9b, 0x7a, 0x01, 0x9d, 0xf1, 0x80, 0xf9, 0xde, 0x1f, 0x39, 0x4d, 0x77, 0x81, 0xbc, 0x52, 0x5c, + 0x2b, 0x43, 0x2f, 0xe0, 0xa1, 0xcf, 0x62, 0x41, 0x99, 0x10, 0x7c, 0x3a, 0x13, 0x34, 0x9e, 0x8f, + 0x46, 0x3c, 0x8e, 0x2f, 0xe7, 0xbe, 0xea, 0x98, 0x12, 0xb9, 0x4f, 0xfc, 0x2a, 0x5f, 0x2a, 0x98, + 0x9b, 0x8d, 0xbf, 0x1a, 0xb0, 0xf3, 0x72, 0xee, 0xf9, 0xe3, 0x95, 0x99, 0xff, 0x08, 0x4a, 0x32, + 0x40, 0x66, 0xa7, 0x90, 0x8b, 0x89, 0x2a, 0xf2, 0xba, 0x45, 0x79, 0x63, 0xed, 0xa2, 0xbc, 0x6e, + 0x65, 0xcd, 0xdd, 0xbb, 0xb2, 0x3e, 0x81, 0xca, 0x24, 0x9c, 0xd1, 0xd9, 0xfc, 0xfd, 0x35, 0xbf, + 0x89, 0xad, 0xfc, 0x51, 0xee, 0xb8, 0x4a, 0x60, 0x12, 0xce, 0x2e, 0x34, 0xd2, 0x78, 0x01, 0x28, + 0x7b, 0xd1, 0xa4, 0x9a, 0x8b, 0xd5, 0xc3, 0xb8, 0x77, 0xf5, 0x78, 0xfa, 0x17, 0x03, 0xaa, 0xd9, + 0xad, 0x0e, 0xd5, 0xa0, 0x6c, 0x3b, 0xb4, 0xd3, 0xb5, 0x7f, 0x38, 0x1f, 0x98, 0x9f, 0xc9, 0x63, + 0x7f, 0xd8, 0x6a, 0x61, 0xdc, 0xc6, 0x6d, 0xd3, 0x90, 0xcc, 0x24, 0x49, 0x06, 0xb7, 0xe9, 0xc0, + 0xee, 0x61, 0x77, 0x28, 0x67, 0xd6, 0x2e, 0x6c, 0x27, 0x98, 0xe3, 0x52, 0xe2, 0x0e, 0x07, 0xd8, + 0xcc, 0x21, 0x13, 0xaa, 0x09, 0x88, 0x09, 0x71, 0x89, 0x99, 0x97, 0x44, 0x9b, 0x20, 0x77, 0xe7, + 0x5f, 0x3a, 0x1e, 0x0b, 0x67, 0xff, 0xc8, 0xc3, 0xa6, 0xba, 0x60, 0x84, 0xce, 0xa1, 0x92, 0x59, + 0x9d, 0xd1, 0xe1, 0x27, 0x57, 0xea, 0xba, 0xb5, 0x7e, 0x4d, 0x9d, 0xc7, 0xcf, 0x0d, 0xf4, 0x0a, + 0xaa, 0xd9, 0xe5, 0x18, 0x65, 0x97, 0x9e, 0x35, 0x5b, 0xf3, 0x27, 0x7d, 0xbd, 0x06, 0x13, 0xc7, + 0xc2, 0x9b, 0xca, 0x25, 0x27, 0x59, 0x3b, 0x51, 0x3d, 0xa3, 0x7f, 0x6b, 0x97, 0xad, 0x1f, 0xac, + 0x95, 0x25, 0x15, 0xea, 0xea, 0x4f, 0x4c, 0x16, 0xbf, 0x3b, 0x9f, 0xb8, 0xba, 0x6d, 0xd6, 0x3f, + 0xbf, 0x4f, 0x9c, 0x78, 0x1b, 0xc3, 0xee, 0x1a, 0x66, 0x40, 0xbf, 0xc8, 0xde, 0xe0, 0x5e, 0x5e, + 0xa9, 0x7f, 0xfd, 0xbf, 0xd4, 0x96, 0x51, 0xd6, 0x50, 0xc8, 0x4a, 0x94, 0xfb, 0x09, 0x68, 0x25, + 0xca, 0xa7, 0x98, 0xc8, 0x06, 0x58, 0x76, 0x34, 0x7a, 0x9c, 0xb1, 0xba, 0xf3, 0x22, 0xeb, 0x87, + 0xf7, 0x48, 0xb5, 0xab, 0x97, 0xbf, 0xfa, 0xdd, 0xe9, 0x95, 0x27, 0x26, 0xf3, 0xf7, 0x27, 0xa3, + 0x70, 0x7a, 0xea, 0xcb, 0x6d, 0x2e, 0xf0, 0x82, 0xab, 0x80, 0x8b, 0x3f, 0x84, 0xd1, 0xf5, 0xa9, + 0x1f, 0x8c, 0x4f, 0xd5, 0xc3, 0x38, 0x5d, 0x78, 0x79, 0xbf, 0xa9, 0xfe, 0xb1, 0xfe, 0xf5, 0x7f, + 0x03, 0x00, 0x00, 0xff, 0xff, 0x3e, 0x15, 0x37, 0x36, 0x88, 0x0f, 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 612a374f0..906f25421 100644 --- a/lnrpc/routerrpc/router.proto +++ b/lnrpc/routerrpc/router.proto @@ -337,30 +337,12 @@ message QueryMissionControlRequest {} /// QueryMissionControlResponse contains mission control state. message QueryMissionControlResponse { - /// Node-level mission control state. - repeated NodeHistory nodes = 1 [json_name = "nodes"]; + reserved 1; /// Node pair-level mission control state. repeated PairHistory pairs = 2 [json_name = "pairs"]; } -/// NodeHistory contains the mission control state for a particular node. -message NodeHistory { - /// Node pubkey - bytes pubkey = 1 [json_name = "pubkey"]; - - /// 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 of forwarding towards peers of this node - for which no specific history is available. - **/ - float other_success_prob = 3 [json_name = "other_success_prob"]; - - reserved 4; -} - /// PairHistory contains the mission control state for a particular node pair. message PairHistory { /// The source node pubkey of the pair. @@ -375,8 +357,7 @@ message PairHistory { /// Minimum penalization amount (only applies to failed attempts). 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"]; + reserved 5; /// Whether the last payment attempt through this pair was successful. bool last_attempt_successful = 6 [json_name = "last_attempt_successful"]; diff --git a/lnrpc/routerrpc/router_server.go b/lnrpc/routerrpc/router_server.go index 37e68474e..77a701bbc 100644 --- a/lnrpc/routerrpc/router_server.go +++ b/lnrpc/routerrpc/router_server.go @@ -466,22 +466,6 @@ func (s *Server) QueryMissionControl(ctx context.Context, snapshot := s.cfg.RouterBackend.MissionControl.GetHistorySnapshot() - rpcNodes := make([]*NodeHistory, 0, len(snapshot.Nodes)) - for _, n := range snapshot.Nodes { - // Copy node struct to prevent loop variable binding bugs. - node := n - - rpcNode := NodeHistory{ - Pubkey: node.Node[:], - LastFailTime: node.LastFail.Unix(), - OtherSuccessProb: float32( - node.OtherSuccessProb, - ), - } - - rpcNodes = append(rpcNodes, &rpcNode) - } - rpcPairs := make([]*PairHistory, 0, len(snapshot.Pairs)) for _, p := range snapshot.Pairs { // Prevent binding to loop variable. @@ -494,7 +478,6 @@ func (s *Server) QueryMissionControl(ctx context.Context, MinPenalizeAmtSat: int64( pair.MinPenalizeAmt.ToSatoshis(), ), - SuccessProb: float32(pair.SuccessProb), LastAttemptSuccessful: pair.LastAttemptSuccessful, } @@ -502,7 +485,6 @@ func (s *Server) QueryMissionControl(ctx context.Context, } response := QueryMissionControlResponse{ - Nodes: rpcNodes, Pairs: rpcPairs, } diff --git a/routing/missioncontrol.go b/routing/missioncontrol.go index cebb7ea98..de93df0da 100644 --- a/routing/missioncontrol.go +++ b/routing/missioncontrol.go @@ -1,7 +1,6 @@ package routing import ( - "math" "sync" "time" @@ -47,8 +46,15 @@ const ( // prevSuccessProbability is the assumed probability for node pairs that // successfully relayed the previous attempt. prevSuccessProbability = 0.95 + + // DefaultAprioriWeight is the default a priori weight. See + // MissionControlConfig for further explanation. + DefaultAprioriWeight = 0.5 ) +// NodeResults contains previous results from a node to its peers. +type NodeResults map[route.Vertex]timedPairResult + // MissionControl contains state which summarizes the past attempts of HTLC // routing by external callers when sending payments throughout the network. It // acts as a shared memory during routing attempts with the goal to optimize the @@ -59,11 +65,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 { - // lastPairResult tracks the last payment result per node pair. - lastPairResult map[DirectedNodePair]timedPairResult - - // lastNodeFailure tracks the last node level failure per node. - lastNodeFailure map[route.Vertex]time.Time + // lastPairResult tracks the last payment result (on a pair basis) for + // each transited node. This is a multi-layer map that allows us to look + // up the failure history of all connected channels (node pairs) for a + // particular node. + lastPairResult map[route.Vertex]NodeResults // lastSecondChance tracks the last time a second chance was granted for // a directed node pair. @@ -77,6 +83,10 @@ type MissionControl struct { store *missionControlStore + // estimator is the probability estimator that is used with the payment + // results that mission control collects. + estimator *probabilityEstimator + sync.Mutex // TODO(roasbeef): further counters, if vertex continually unavailable, @@ -99,6 +109,15 @@ type MissionControlConfig struct { // MaxMcHistory defines the maximum number of payment results that are // held on disk. MaxMcHistory int + + // AprioriWeight is a value in the range [0, 1] that defines to what + // extent historical results should be extrapolated to untried + // connections. Setting it to one will completely ignore historical + // results and always assume the configured a priori probability for + // untried connections. A value of zero will ignore the a priori + // probability completely and only base the probability on historical + // results, unless there are none available. + AprioriWeight float64 } // timedPairResult describes a timestamped pair result. @@ -112,28 +131,11 @@ type timedPairResult struct { // MissionControlSnapshot contains a snapshot of the current state of mission // control. 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 -// mission control. -type MissionControlNodeSnapshot struct { - // Node pubkey. - Node route.Vertex - - // LastFail is the time of last failure. - LastFail time.Time - - // OtherSuccessProb is the success probability for pairs not in - // the Pairs slice. - OtherSuccessProb float64 -} - // MissionControlPairSnapshot contains a snapshot of the current node pair // state in mission control. type MissionControlPairSnapshot struct { @@ -147,9 +149,6 @@ type MissionControlPairSnapshot struct { // penalized. MinPenalizeAmt lnwire.MilliSatoshi - // 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 @@ -171,21 +170,29 @@ func NewMissionControl(db *bbolt.DB, cfg *MissionControlConfig) ( *MissionControl, error) { log.Debugf("Instantiating mission control with config: "+ - "PenaltyHalfLife=%v, AprioriHopProbability=%v", - cfg.PenaltyHalfLife, cfg.AprioriHopProbability) + "PenaltyHalfLife=%v, AprioriHopProbability=%v, "+ + "AprioriWeight=%v", cfg.PenaltyHalfLife, + cfg.AprioriHopProbability, cfg.AprioriWeight) store, err := newMissionControlStore(db, cfg.MaxMcHistory) if err != nil { return nil, err } + estimator := &probabilityEstimator{ + aprioriHopProbability: cfg.AprioriHopProbability, + aprioriWeight: cfg.AprioriWeight, + penaltyHalfLife: cfg.PenaltyHalfLife, + prevSuccessProbability: prevSuccessProbability, + } + mc := &MissionControl{ - lastPairResult: make(map[DirectedNodePair]timedPairResult), - lastNodeFailure: make(map[route.Vertex]time.Time), + lastPairResult: make(map[route.Vertex]NodeResults), lastSecondChance: make(map[DirectedNodePair]time.Time), now: time.Now, cfg: cfg, store: store, + estimator: estimator, } if err := mc.init(); err != nil { @@ -226,8 +233,7 @@ func (m *MissionControl) ResetHistory() error { return err } - m.lastPairResult = make(map[DirectedNodePair]timedPairResult) - m.lastNodeFailure = make(map[route.Vertex]time.Time) + m.lastPairResult = make(map[route.Vertex]NodeResults) m.lastSecondChance = make(map[DirectedNodePair]time.Time) log.Debugf("Mission control history cleared") @@ -243,62 +249,40 @@ func (m *MissionControl) GetProbability(fromNode, toNode route.Vertex, m.Lock() defer m.Unlock() - return m.getPairProbability(fromNode, toNode, amt) + now := m.now() + results := m.lastPairResult[fromNode] + + return m.estimator.getPairProbability(now, results, toNode, amt) } -// 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 +// setLastPairResult stores a result for a node pair. +func (m *MissionControl) setLastPairResult(fromNode, + toNode route.Vertex, result timedPairResult) { + + nodePairs, ok := m.lastPairResult[fromNode] + if !ok { + nodePairs = make(NodeResults) + m.lastPairResult[fromNode] = nodePairs } - 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 - // recovers asymptotically back to the a priori probability. The rate at - // which this happens is controlled by the penaltyHalfLife parameter. - exp := -timeSinceLastFailure.Hours() / m.cfg.PenaltyHalfLife.Hours() - probability := m.cfg.AprioriHopProbability * (1 - math.Pow(2, exp)) - - return probability + nodePairs[toNode] = result } -// getPairProbability estimates the probability of successfully -// traversing from fromNode to toNode based on historical payment outcomes. -func (m *MissionControl) getPairProbability(fromNode, - toNode route.Vertex, amt lnwire.MilliSatoshi) float64 { +// setAllFail stores a fail result for all known connection of the given node. +func (m *MissionControl) setAllFail(fromNode route.Vertex, + timestamp time.Time) { - // Start by getting the last node level failure. 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. If - // there is none, lastFail will be zero. - lastFail := m.lastNodeFailure[fromNode] - - // Retrieve the last pair outcome. - pair := NewDirectedNodePair(fromNode, toNode) - lastPairResult, ok := m.lastPairResult[pair] - - // Only look at the last pair outcome if it happened after the last node - // level failure. Otherwise the node level failure is the most recent - // and used as the basis for calculation of the probability. - if ok && lastPairResult.timestamp.After(lastFail) { - if lastPairResult.success { - return prevSuccessProbability - } - - // 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. - if amt >= lastPairResult.minPenalizeAmt { - lastFail = lastPairResult.timestamp - } + nodePairs, ok := m.lastPairResult[fromNode] + if !ok { + return } - return m.getProbAfterFail(lastFail) + for connection := range nodePairs { + nodePairs[connection] = timedPairResult{ + timestamp: timestamp, + pairResult: failPairResult(0), + } + } } // requestSecondChance checks whether the node fromNode can have a second chance @@ -339,40 +323,27 @@ 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.lastPairResult)) - - nodes := make([]MissionControlNodeSnapshot, 0, len(m.lastNodeFailure)) - for v, h := range m.lastNodeFailure { - otherProb := m.getPairProbability(v, route.Vertex{}, 0) - - nodes = append(nodes, MissionControlNodeSnapshot{ - Node: v, - LastFail: h, - OtherSuccessProb: otherProb, - }) - } + "pair_result_count=%v", len(m.lastPairResult)) pairs := make([]MissionControlPairSnapshot, 0, len(m.lastPairResult)) - for v, h := range m.lastPairResult { - // Show probability assuming amount meets min - // penalization amount. - prob := m.getPairProbability(v.From, v.To, h.minPenalizeAmt) + for fromNode, fromPairs := range m.lastPairResult { + for toNode, result := range fromPairs { - pair := MissionControlPairSnapshot{ - Pair: v, - MinPenalizeAmt: h.minPenalizeAmt, - Timestamp: h.timestamp, - SuccessProb: prob, - LastAttemptSuccessful: h.success, + pair := NewDirectedNodePair(fromNode, toNode) + + pairSnapshot := MissionControlPairSnapshot{ + Pair: pair, + MinPenalizeAmt: result.minPenalizeAmt, + Timestamp: result.timestamp, + LastAttemptSuccessful: result.success, + } + + pairs = append(pairs, pairSnapshot) } - - pairs = append(pairs, pair) } snapshot := MissionControlSnapshot{ - Nodes: nodes, Pairs: pairs, } @@ -463,11 +434,28 @@ func (m *MissionControl) applyPaymentResult( } } + // If there is a node-level failure, record a failure for every tried + // connection of that node. A node-level failure can be considered as a + // failure that would have occurred with any of the node's channels. + // + // Ideally we'd also record the failure for the untried connections of + // the node. Unfortunately this would require access to the graph and + // adding this dependency and db calls does not outweigh the benefits. + // + // Untried connections will fall back to the node probability. After the + // call to setAllPairResult below, the node probability will be equal to + // the probability of the tried channels except that the a priori + // probability is mixed in too. This effect is controlled by the + // aprioriWeight parameter. If that parameter isn't set to an extreme + // and there are a few known connections, there shouldn't be much of a + // difference. The largest difference occurs when aprioriWeight is 1. In + // that case, a node-level failure would not be applied to untried + // channels. if i.nodeFailure != nil { log.Debugf("Reporting node failure to Mission Control: "+ "node=%v", *i.nodeFailure) - m.lastNodeFailure[*i.nodeFailure] = result.timeReply + m.setAllFail(*i.nodeFailure, result.timeReply) } for pair, pairResult := range i.pairResults { @@ -480,10 +468,10 @@ func (m *MissionControl) applyPaymentResult( pair, pairResult.minPenalizeAmt) } - m.lastPairResult[pair] = timedPairResult{ + m.setLastPairResult(pair.From, pair.To, timedPairResult{ timestamp: result.timeReply, pairResult: pairResult, - } + }) } return i.finalFailureReason diff --git a/routing/missioncontrol_test.go b/routing/missioncontrol_test.go index df96e3140..bbef69c94 100644 --- a/routing/missioncontrol_test.go +++ b/routing/missioncontrol_test.go @@ -32,6 +32,10 @@ var ( mcTestTime = time.Date(2018, time.January, 9, 14, 00, 00, 0, time.UTC) mcTestNode1 = mcTestRoute.Hops[0].PubKeyBytes mcTestNode2 = mcTestRoute.Hops[1].PubKeyBytes + + testPenaltyHalfLife = 30 * time.Minute + testAprioriHopProbability = 0.9 + testAprioriWeight = 0.5 ) type mcTestContext struct { @@ -73,8 +77,9 @@ func (ctx *mcTestContext) restartMc() { mc, err := NewMissionControl( ctx.db, &MissionControlConfig{ - PenaltyHalfLife: 30 * time.Minute, - AprioriHopProbability: 0.8, + PenaltyHalfLife: testPenaltyHalfLife, + AprioriHopProbability: testAprioriHopProbability, + AprioriWeight: testAprioriWeight, }, ) if err != nil { @@ -133,20 +138,23 @@ func TestMissionControl(t *testing.T) { testTime := time.Date(2018, time.January, 9, 14, 00, 00, 0, time.UTC) - // Initial probability is expected to be 1. - ctx.expectP(1000, 0.8) + // Initial probability is expected to be the a priori. + ctx.expectP(1000, testAprioriHopProbability) // Expect probability to be zero after reporting the edge as failed. ctx.reportFailure(1000, lnwire.NewTemporaryChannelFailure(nil)) ctx.expectP(1000, 0) // As we reported with a min penalization amt, a lower amt than reported - // should be unaffected. - ctx.expectP(500, 0.8) + // should return the node probability, which is the a priori + // probability. + ctx.expectP(500, testAprioriHopProbability) - // Edge decay started. + // Edge decay started. The node probability weighted average should now + // have shifted from 1:1 to 1:0.5 -> 60%. The connection probability is + // half way through the recovery, so we expect 30% here. ctx.now = testTime.Add(30 * time.Minute) - ctx.expectP(1000, 0.4) + ctx.expectP(1000, 0.3) // Edge fails again, this time without a min penalization amt. The edge // should be penalized regardless of amount. @@ -156,26 +164,22 @@ func TestMissionControl(t *testing.T) { // Edge decay started. ctx.now = testTime.Add(60 * time.Minute) - ctx.expectP(1000, 0.4) + ctx.expectP(1000, 0.3) // Restart mission control to test persistence. ctx.restartMc() - ctx.expectP(1000, 0.4) + ctx.expectP(1000, 0.3) - // A node level failure should bring probability of every channel back - // to zero. + // A node level failure should bring probability of all known channels + // back to zero. ctx.reportFailure(0, lnwire.NewExpiryTooSoon(lnwire.ChannelUpdate{})) ctx.expectP(1000, 0) // Check whether history snapshot looks sane. history := ctx.mc.GetHistorySnapshot() - if len(history.Nodes) != 1 { - t.Fatalf("unexpected number of nodes: expected 1 got %v", - len(history.Nodes)) - } - if len(history.Pairs) != 2 { - t.Fatalf("expected 2 pairs, but got %v", len(history.Pairs)) + if len(history.Pairs) != 3 { + t.Fatalf("expected 3 pairs, but got %v", len(history.Pairs)) } // Test reporting a success. @@ -192,7 +196,7 @@ func TestMissionControlChannelUpdate(t *testing.T) { ctx.reportFailure( 0, lnwire.NewFeeInsufficient(0, lnwire.ChannelUpdate{}), ) - ctx.expectP(0, 0.8) + ctx.expectP(0, testAprioriHopProbability) // Report another failure for the same channel. We expect it to be // pruned. diff --git a/routing/probability_estimator.go b/routing/probability_estimator.go new file mode 100644 index 000000000..a1303dfec --- /dev/null +++ b/routing/probability_estimator.go @@ -0,0 +1,155 @@ +package routing + +import ( + "math" + "time" + + "github.com/lightningnetwork/lnd/lnwire" + "github.com/lightningnetwork/lnd/routing/route" +) + +// probabilityEstimator returns node and pair probabilities based on historical +// payment results. +type probabilityEstimator struct { + // penaltyHalfLife defines after how much time a penalized node or + // channel is back at 50% probability. + penaltyHalfLife time.Duration + + // aprioriHopProbability is the assumed success probability of a hop in + // a route when no other information is available. + aprioriHopProbability float64 + + // aprioriWeight is a value in the range [0, 1] that defines to what + // extent historical results should be extrapolated to untried + // connections. Setting it to one will completely ignore historical + // results and always assume the configured a priori probability for + // untried connections. A value of zero will ignore the a priori + // probability completely and only base the probability on historical + // results, unless there are none available. + aprioriWeight float64 + + // prevSuccessProbability is the assumed probability for node pairs that + // successfully relayed the previous attempt. + prevSuccessProbability float64 +} + +// getNodeProbability calculates the probability for connections from a node +// that have not been tried before. The results parameter is a list of last +// payment results for that node. +func (p *probabilityEstimator) getNodeProbability(now time.Time, + results NodeResults, amt lnwire.MilliSatoshi) float64 { + + // If the channel history is not to be taken into account, we can return + // early here with the configured a priori probability. + if p.aprioriWeight == 1 { + return p.aprioriHopProbability + } + + // If there is no channel history, our best estimate is still the a + // priori probability. + if len(results) == 0 { + return p.aprioriHopProbability + } + + // The value of the apriori weight is in the range [0, 1]. Convert it to + // a factor that properly expresses the intention of the weight in the + // following weight average calculation. When the apriori weight is 0, + // the apriori factor is also 0. This means it won't have any effect on + // the weighted average calculation below. When the apriori weight + // approaches 1, the apriori factor goes to infinity. It will heavily + // outweigh any observations that have been collected. + aprioriFactor := 1/(1-p.aprioriWeight) - 1 + + // Calculate a weighted average consisting of the apriori probability + // and historical observations. This is the part that incentivizes nodes + // to make sure that all (not just some) of their channels are in good + // shape. Senders will steer around nodes that have shown a few + // failures, even though there may be many channels still untried. + // + // If there is just a single observation and the apriori weight is 0, + // this single observation will totally determine the node probability. + // The node probability is returned for all other channels of the node. + // This means that one failure will lead to the success probability + // estimates for all other channels being 0 too. The probability for the + // channel that was tried will not even recover, because it is + // recovering to the node probability (which is zero). So one failure + // effectively prunes all channels of the node forever. This is the most + // aggressive way in which we can penalize nodes and unlikely to yield + // good results in a real network. + probabilitiesTotal := p.aprioriHopProbability * aprioriFactor + totalWeight := aprioriFactor + + for _, result := range results { + age := now.Sub(result.timestamp) + + switch { + // Weigh success with a constant high weight of 1. There is no + // decay. + case result.success: + totalWeight++ + probabilitiesTotal += p.prevSuccessProbability + + // Weigh failures in accordance with their age. The base + // probability of a failure is considered zero, so nothing needs + // to be added to probabilitiesTotal. + case amt >= result.minPenalizeAmt: + totalWeight += p.getWeight(age) + } + } + + return probabilitiesTotal / totalWeight +} + +// getWeight calculates a weight in the range [0, 1] that should be assigned to +// a payment result. Weight follows an exponential curve that starts at 1 when +// the result is fresh and asymptotically approaches zero over time. The rate at +// which this happens is controlled by the penaltyHalfLife parameter. +func (p *probabilityEstimator) getWeight(age time.Duration) float64 { + exp := -age.Hours() / p.penaltyHalfLife.Hours() + return math.Pow(2, exp) +} + +// getPairProbability estimates the probability of successfully traversing to +// toNode based on historical payment outcomes for the from node. Those outcomes +// are passed in via the results parameter. +func (p *probabilityEstimator) getPairProbability( + now time.Time, results NodeResults, + toNode route.Vertex, amt lnwire.MilliSatoshi) float64 { + + // Retrieve the last pair outcome. + lastPairResult, ok := results[toNode] + + // If there is no history for this pair, return the node probability + // that is a probability estimate for untried channel. + if !ok { + return p.getNodeProbability(now, results, amt) + } + + // For successes, we have a fixed (high) probability. Those pairs + // will be assumed good until proven otherwise. + if lastPairResult.success { + return p.prevSuccessProbability + } + + nodeProbability := p.getNodeProbability(now, results, amt) + + // Take into account a minimum penalize amount. For balance errors, a + // failure may be reported with such a minimum to prevent too aggressive + // penalization. If the current amount is smaller than the amount that + // previously triggered a failure, we act as if this is an untried + // channel. + if amt < lastPairResult.minPenalizeAmt { + return nodeProbability + } + + timeSinceLastFailure := now.Sub(lastPairResult.timestamp) + + // Calculate success probability based on the weight of the last + // failure. When the failure is fresh, its weight is 1 and we'll return + // probability 0. Over time the probability recovers to the node + // probability. It would be as if this channel was never tried before. + weight := p.getWeight(timeSinceLastFailure) + probability := nodeProbability * (1 - weight) + + return probability +} diff --git a/routing/probability_estimator_test.go b/routing/probability_estimator_test.go new file mode 100644 index 000000000..c9604241d --- /dev/null +++ b/routing/probability_estimator_test.go @@ -0,0 +1,163 @@ +package routing + +import ( + "testing" + "time" + + "github.com/lightningnetwork/lnd/lnwire" + "github.com/lightningnetwork/lnd/routing/route" +) + +const ( + // Define node identifiers + node1 = 1 + node2 = 2 + node3 = 3 + + // untriedNode is a node id for which we don't record any results in + // this test. This can be used to assert the probability for untried + // ndoes. + untriedNode = 255 + + // Define test estimator parameters. + aprioriHopProb = 0.6 + aprioriWeight = 0.75 + aprioriPrevSucProb = 0.95 +) + +type estimatorTestContext struct { + t *testing.T + estimator *probabilityEstimator + + // results contains a list of last results. Every element in the list + // corresponds to the last result towards a node. The list index equals + // the node id. So the first element in the list is the result towards + // node 0. + results map[int]timedPairResult +} + +func newEstimatorTestContext(t *testing.T) *estimatorTestContext { + return &estimatorTestContext{ + t: t, + estimator: &probabilityEstimator{ + aprioriHopProbability: aprioriHopProb, + aprioriWeight: aprioriWeight, + penaltyHalfLife: time.Hour, + prevSuccessProbability: aprioriPrevSucProb, + }, + } +} + +// assertPairProbability asserts that the calculated success probability is +// correct. +func (c *estimatorTestContext) assertPairProbability(now time.Time, + toNode byte, amt lnwire.MilliSatoshi, expectedProb float64) { + + c.t.Helper() + + results := make(NodeResults) + for i, r := range c.results { + results[route.Vertex{byte(i)}] = r + } + + const tolerance = 0.01 + + p := c.estimator.getPairProbability(now, results, route.Vertex{toNode}, amt) + diff := p - expectedProb + if diff > tolerance || diff < -tolerance { + c.t.Fatalf("expected probability %v for node %v, but got %v", + expectedProb, toNode, p) + } +} + +// TestProbabilityEstimatorNoResults tests the probability estimation when no +// results are available. +func TestProbabilityEstimatorNoResults(t *testing.T) { + ctx := newEstimatorTestContext(t) + + ctx.assertPairProbability(testTime, 0, 0, aprioriHopProb) +} + +// TestProbabilityEstimatorOneSuccess tests the probability estimation for nodes +// that have a single success result. +func TestProbabilityEstimatorOneSuccess(t *testing.T) { + ctx := newEstimatorTestContext(t) + + ctx.results = map[int]timedPairResult{ + node1: { + timestamp: testTime.Add(-time.Hour), + pairResult: successPairResult(), + }, + } + + // Because of the previous success, this channel keep reporting a high + // probability. + ctx.assertPairProbability( + testTime, node1, 100, aprioriPrevSucProb, + ) + + // Untried channels are also influenced by the success. With a + // aprioriWeight of 0.75, the a priori probability is assigned weight 3. + expectedP := (3*aprioriHopProb + 1*aprioriPrevSucProb) / 4 + ctx.assertPairProbability(testTime, untriedNode, 100, expectedP) +} + +// TestProbabilityEstimatorOneFailure tests the probability estimation for nodes +// that have a single failure. +func TestProbabilityEstimatorOneFailure(t *testing.T) { + ctx := newEstimatorTestContext(t) + + ctx.results = map[int]timedPairResult{ + node1: { + timestamp: testTime.Add(-time.Hour), + pairResult: failPairResult(0), + }, + } + + // For an untried node, we expected the node probability. The weight for + // the failure after one hour is 0.5. This makes the node probability + // 0.51: + expectedNodeProb := (3*aprioriHopProb + 0.5*0) / 3.5 + ctx.assertPairProbability(testTime, untriedNode, 100, expectedNodeProb) + + // The pair probability decays back to the node probability. With the + // weight at 0.5, we expected a pair probability of 0.5 * 0.51 = 0.25. + ctx.assertPairProbability(testTime, node1, 100, expectedNodeProb/2) +} + +// TestProbabilityEstimatorMix tests the probability estimation for nodes for +// which a mix of successes and failures is recorded. +func TestProbabilityEstimatorMix(t *testing.T) { + ctx := newEstimatorTestContext(t) + + ctx.results = map[int]timedPairResult{ + node1: { + timestamp: testTime.Add(-time.Hour), + pairResult: successPairResult(), + }, + node2: { + timestamp: testTime.Add(-2 * time.Hour), + pairResult: failPairResult(0), + }, + node3: { + timestamp: testTime.Add(-3 * time.Hour), + pairResult: failPairResult(0), + }, + } + + // We expect the probability for a previously successful channel to + // remain high. + ctx.assertPairProbability(testTime, node1, 100, prevSuccessProbability) + + // For an untried node, we expected the node probability to be returned. + // This is a weighted average of the results above and the a priori + // probability: 0.62. + expectedNodeProb := (3*aprioriHopProb + 1*prevSuccessProbability) / + (3 + 1 + 0.25 + 0.125) + + ctx.assertPairProbability(testTime, untriedNode, 100, expectedNodeProb) + + // For the previously failed connection with node 1, we expect 0.75 * + // the node probability = 0.47. + ctx.assertPairProbability(testTime, node2, 100, expectedNodeProb*0.75) +} diff --git a/routing/result_interpretation.go b/routing/result_interpretation.go index 74c740149..696d930c6 100644 --- a/routing/result_interpretation.go +++ b/routing/result_interpretation.go @@ -26,6 +26,20 @@ type pairResult struct { success bool } +// failPairResult creates a new result struct for a failure. +func failPairResult(minPenalizeAmt lnwire.MilliSatoshi) pairResult { + return pairResult{ + minPenalizeAmt: minPenalizeAmt, + } +} + +// successPairResult creates a new result struct for a success. +func successPairResult() pairResult { + return pairResult{ + success: true, + } +} + // String returns the human-readable representation of a pair result. func (p pairResult) String() string { if p.success { @@ -364,10 +378,30 @@ func (i *interpretedResult) processPaymentOutcomeUnknown(route *route.Route) { i.failPairRange(route, 0, n-1) } -// failNode marks the node indicated by idx in the route as failed. This -// function intentionally panics when the self node is failed. +// failNode marks the node indicated by idx in the route as failed. It also +// marks the incoming and outgoing channels of the node as failed. This function +// intentionally panics when the self node is failed. func (i *interpretedResult) failNode(rt *route.Route, idx int) { + // Mark the node as failing. i.nodeFailure = &rt.Hops[idx-1].PubKeyBytes + + // Mark the incoming connection as failed for the node. We intent to + // penalize as much as we can for a node level failure, including future + // outgoing traffic for this connection. The pair as it is returned by + // getPair is directed towards the failed node. Therefore we first + // reverse the pair. We don't want to affect the score of the node + // sending towards the failing node. + incomingChannelIdx := idx - 1 + inPair, _ := getPair(rt, incomingChannelIdx) + i.pairResults[inPair.Reverse()] = failPairResult(0) + + // If not the ultimate node, mark the outgoing connection as failed for + // the node. + if idx < len(rt.Hops) { + outgoingChannelIdx := idx + outPair, _ := getPair(rt, outgoingChannelIdx) + i.pairResults[outPair] = failPairResult(0) + } } // failPairRange marks the node pairs from node fromIdx to node toIdx as failed @@ -387,8 +421,8 @@ func (i *interpretedResult) failPair( pair, _ := getPair(rt, idx) // Report pair in both directions without a minimum penalization amount. - i.pairResults[pair] = pairResult{} - i.pairResults[pair.Reverse()] = pairResult{} + i.pairResults[pair] = failPairResult(0) + i.pairResults[pair.Reverse()] = failPairResult(0) } // failPairBalance marks a pair as failed with a minimum penalization amount. @@ -397,9 +431,7 @@ func (i *interpretedResult) failPairBalance( pair, amt := getPair(rt, channelIdx) - i.pairResults[pair] = pairResult{ - minPenalizeAmt: amt, - } + i.pairResults[pair] = failPairResult(amt) } // successPairRange marks the node pairs from node fromIdx to node toIdx as @@ -410,9 +442,7 @@ func (i *interpretedResult) successPairRange( for idx := fromIdx; idx <= toIdx; idx++ { pair, _ := getPair(rt, idx) - i.pairResults[pair] = pairResult{ - success: true, - } + i.pairResults[pair] = successPairResult() } } diff --git a/routing/result_interpretation_test.go b/routing/result_interpretation_test.go index 8c4498b06..78b6088ae 100644 --- a/routing/result_interpretation_test.go +++ b/routing/result_interpretation_test.go @@ -68,12 +68,8 @@ var resultTestCases = []resultTestCase{ expectedResult: &interpretedResult{ pairResults: map[DirectedNodePair]pairResult{ - getTestPair(0, 1): { - success: true, - }, - getTestPair(1, 2): { - minPenalizeAmt: 99, - }, + getTestPair(0, 1): successPairResult(), + getTestPair(1, 2): failPairResult(99), }, }, }, @@ -87,12 +83,12 @@ var resultTestCases = []resultTestCase{ expectedResult: &interpretedResult{ pairResults: map[DirectedNodePair]pairResult{ - getTestPair(0, 1): {}, - getTestPair(1, 0): {}, - getTestPair(1, 2): {}, - getTestPair(2, 1): {}, - getTestPair(2, 3): {}, - getTestPair(3, 2): {}, + getTestPair(0, 1): failPairResult(0), + getTestPair(1, 0): failPairResult(0), + getTestPair(1, 2): failPairResult(0), + getTestPair(2, 1): failPairResult(0), + getTestPair(2, 3): failPairResult(0), + getTestPair(3, 2): failPairResult(0), }, }, }, @@ -107,12 +103,8 @@ var resultTestCases = []resultTestCase{ expectedResult: &interpretedResult{ pairResults: map[DirectedNodePair]pairResult{ - getTestPair(0, 1): { - success: true, - }, - getTestPair(1, 2): { - success: true, - }, + getTestPair(0, 1): successPairResult(), + getTestPair(1, 2): successPairResult(), }, finalFailureReason: &reasonIncorrectDetails, }, @@ -126,9 +118,7 @@ var resultTestCases = []resultTestCase{ expectedResult: &interpretedResult{ pairResults: map[DirectedNodePair]pairResult{ - getTestPair(0, 1): { - success: true, - }, + getTestPair(0, 1): successPairResult(), }, }, }, @@ -141,12 +131,8 @@ var resultTestCases = []resultTestCase{ expectedResult: &interpretedResult{ pairResults: map[DirectedNodePair]pairResult{ - getTestPair(0, 1): { - success: true, - }, - getTestPair(1, 2): { - success: true, - }, + getTestPair(0, 1): successPairResult(), + getTestPair(1, 2): successPairResult(), }, }, }, @@ -160,6 +146,10 @@ var resultTestCases = []resultTestCase{ expectedResult: &interpretedResult{ nodeFailure: &hops[1], + pairResults: map[DirectedNodePair]pairResult{ + getTestPair(1, 0): failPairResult(0), + getTestPair(1, 2): failPairResult(0), + }, }, }, @@ -174,6 +164,9 @@ var resultTestCases = []resultTestCase{ expectedResult: &interpretedResult{ finalFailureReason: &reasonError, nodeFailure: &hops[1], + pairResults: map[DirectedNodePair]pairResult{ + getTestPair(1, 0): failPairResult(0), + }, }, }, } diff --git a/routing/router_test.go b/routing/router_test.go index 0aae52caf..fb73a0005 100644 --- a/routing/router_test.go +++ b/routing/router_test.go @@ -91,6 +91,7 @@ func createTestCtxFromGraphInstance(startingHeight uint32, graphInstance *testGr mcConfig := &MissionControlConfig{ PenaltyHalfLife: time.Hour, AprioriHopProbability: 0.9, + AprioriWeight: 0.5, } mc, err := NewMissionControl( diff --git a/server.go b/server.go index e6c64f7eb..b52a8f22e 100644 --- a/server.go +++ b/server.go @@ -660,6 +660,7 @@ func newServer(listenAddrs []net.Addr, chanDB *channeldb.DB, AprioriHopProbability: routingConfig.AprioriHopProbability, PenaltyHalfLife: routingConfig.PenaltyHalfLife, MaxMcHistory: routingConfig.MaxMcHistory, + AprioriWeight: routingConfig.AprioriWeight, }, ) if err != nil {