From 35003bd87c854664fab27936d348d0b0ed8be79b Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Tue, 19 Mar 2019 15:07:05 +0100 Subject: [PATCH 01/10] routing/test: sort nodes for channel graph This commit ensures that channel endpoints in EdgeInfo are ordered node1 < node2 to not violate assumptions being made by dependent code. --- routing/pathfind_test.go | 42 +++++++++++++++++++++++++--------------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/routing/pathfind_test.go b/routing/pathfind_test.go index 7ce114c29..fb5ca0a57 100644 --- a/routing/pathfind_test.go +++ b/routing/pathfind_test.go @@ -477,6 +477,16 @@ func createTestGraphFromChannels(testChannels []*testChannel) (*testGraphInstanc Index: 0, } + // Sort nodes + node1 := testChannel.Node1 + node2 := testChannel.Node2 + node1Vertex := aliasMap[node1.Alias] + node2Vertex := aliasMap[node2.Alias] + if bytes.Compare(node1Vertex[:], node2Vertex[:]) == 1 { + node1, node2 = node2, node1 + node1Vertex, node2Vertex = node2Vertex, node1Vertex + } + // We first insert the existence of the edge between the two // nodes. edgeInfo := channeldb.ChannelEdgeInfo{ @@ -485,10 +495,10 @@ func createTestGraphFromChannels(testChannels []*testChannel) (*testGraphInstanc ChannelPoint: *fundingPoint, Capacity: testChannel.Capacity, - NodeKey1Bytes: aliasMap[testChannel.Node1.Alias], - BitcoinKey1Bytes: aliasMap[testChannel.Node1.Alias], - NodeKey2Bytes: aliasMap[testChannel.Node2.Alias], - BitcoinKey2Bytes: aliasMap[testChannel.Node2.Alias], + NodeKey1Bytes: node1Vertex, + BitcoinKey1Bytes: node1Vertex, + NodeKey2Bytes: node2Vertex, + BitcoinKey2Bytes: node2Vertex, } err = graph.AddChannelEdge(&edgeInfo) @@ -510,12 +520,12 @@ func createTestGraphFromChannels(testChannels []*testChannel) (*testGraphInstanc MessageFlags: msgFlags, ChannelFlags: channelFlags, ChannelID: channelID, - LastUpdate: testChannel.Node1.LastUpdate, - TimeLockDelta: testChannel.Node1.Expiry, - MinHTLC: testChannel.Node1.MinHTLC, - MaxHTLC: testChannel.Node1.MaxHTLC, - FeeBaseMSat: testChannel.Node1.FeeBaseMsat, - FeeProportionalMillionths: testChannel.Node1.FeeRate, + LastUpdate: node1.LastUpdate, + TimeLockDelta: node1.Expiry, + MinHTLC: node1.MinHTLC, + MaxHTLC: node1.MaxHTLC, + FeeBaseMSat: node1.FeeBaseMsat, + FeeProportionalMillionths: node1.FeeRate, } if err := graph.UpdateEdgePolicy(edgePolicy); err != nil { return nil, err @@ -536,12 +546,12 @@ func createTestGraphFromChannels(testChannels []*testChannel) (*testGraphInstanc MessageFlags: msgFlags, ChannelFlags: channelFlags, ChannelID: channelID, - LastUpdate: testChannel.Node2.LastUpdate, - TimeLockDelta: testChannel.Node2.Expiry, - MinHTLC: testChannel.Node2.MinHTLC, - MaxHTLC: testChannel.Node2.MaxHTLC, - FeeBaseMSat: testChannel.Node2.FeeBaseMsat, - FeeProportionalMillionths: testChannel.Node2.FeeRate, + LastUpdate: node2.LastUpdate, + TimeLockDelta: node2.Expiry, + MinHTLC: node2.MinHTLC, + MaxHTLC: node2.MaxHTLC, + FeeBaseMSat: node2.FeeBaseMsat, + FeeProportionalMillionths: node2.FeeRate, } if err := graph.UpdateEdgePolicy(edgePolicy); err != nil { return nil, err From 3db48dc53f989324eac8c01f31b76caceda42ec5 Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Fri, 25 Jan 2019 10:44:21 +0100 Subject: [PATCH 02/10] routing: Route String() method --- routing/route/route.go | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/routing/route/route.go b/routing/route/route.go index 2cd046849..ad639b539 100644 --- a/routing/route/route.go +++ b/routing/route/route.go @@ -3,6 +3,8 @@ package route import ( "encoding/binary" "fmt" + "strconv" + "strings" "github.com/btcsuite/btcd/btcec" sphinx "github.com/lightningnetwork/lightning-onion" @@ -180,3 +182,20 @@ func (r *Route) ToSphinxPath() (*sphinx.PaymentPath, error) { return &path, nil } + +// String returns a human readable representation of the route. +func (r *Route) String() string { + var b strings.Builder + + for i, hop := range r.Hops { + if i > 0 { + b.WriteString(",") + } + b.WriteString(strconv.FormatUint(hop.ChannelID, 10)) + } + + return fmt.Sprintf("amt=%v, fees=%v, tl=%v, chans=%v", + r.TotalAmount-r.TotalFees(), r.TotalFees(), r.TotalTimeLock, + b.String(), + ) +} From 68f2a04f42174e9873fe0b84aa5dc0542be4652c Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Fri, 10 May 2019 18:00:15 +0200 Subject: [PATCH 03/10] routerrpc: expose mission control reset rpc --- lnrpc/routerrpc/router.pb.go | 262 +++++++++++++++++++++--------- lnrpc/routerrpc/router.proto | 9 + lnrpc/routerrpc/router_backend.go | 2 + lnrpc/routerrpc/router_server.go | 14 ++ routing/router.go | 4 +- rpcserver.go | 3 +- server.go | 6 +- 7 files changed, 218 insertions(+), 82 deletions(-) diff --git a/lnrpc/routerrpc/router.pb.go b/lnrpc/routerrpc/router.pb.go index f9d0f4336..0476353b0 100644 --- a/lnrpc/routerrpc/router.pb.go +++ b/lnrpc/routerrpc/router.pb.go @@ -737,6 +737,68 @@ func (m *ChannelUpdate) GetExtraOpaqueData() []byte { return nil } +type ResetMissionControlRequest struct { + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ResetMissionControlRequest) Reset() { *m = ResetMissionControlRequest{} } +func (m *ResetMissionControlRequest) String() string { return proto.CompactTextString(m) } +func (*ResetMissionControlRequest) ProtoMessage() {} +func (*ResetMissionControlRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_7a0613f69d37b0a5, []int{8} +} + +func (m *ResetMissionControlRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ResetMissionControlRequest.Unmarshal(m, b) +} +func (m *ResetMissionControlRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ResetMissionControlRequest.Marshal(b, m, deterministic) +} +func (m *ResetMissionControlRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_ResetMissionControlRequest.Merge(m, src) +} +func (m *ResetMissionControlRequest) XXX_Size() int { + return xxx_messageInfo_ResetMissionControlRequest.Size(m) +} +func (m *ResetMissionControlRequest) XXX_DiscardUnknown() { + xxx_messageInfo_ResetMissionControlRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_ResetMissionControlRequest proto.InternalMessageInfo + +type ResetMissionControlResponse struct { + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ResetMissionControlResponse) Reset() { *m = ResetMissionControlResponse{} } +func (m *ResetMissionControlResponse) String() string { return proto.CompactTextString(m) } +func (*ResetMissionControlResponse) ProtoMessage() {} +func (*ResetMissionControlResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_7a0613f69d37b0a5, []int{9} +} + +func (m *ResetMissionControlResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ResetMissionControlResponse.Unmarshal(m, b) +} +func (m *ResetMissionControlResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ResetMissionControlResponse.Marshal(b, m, deterministic) +} +func (m *ResetMissionControlResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_ResetMissionControlResponse.Merge(m, src) +} +func (m *ResetMissionControlResponse) XXX_Size() int { + return xxx_messageInfo_ResetMissionControlResponse.Size(m) +} +func (m *ResetMissionControlResponse) XXX_DiscardUnknown() { + xxx_messageInfo_ResetMissionControlResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_ResetMissionControlResponse proto.InternalMessageInfo + func init() { proto.RegisterEnum("routerrpc.Failure_FailureCode", Failure_FailureCode_name, Failure_FailureCode_value) proto.RegisterType((*PaymentRequest)(nil), "routerrpc.PaymentRequest") @@ -747,89 +809,94 @@ func init() { proto.RegisterType((*SendToRouteResponse)(nil), "routerrpc.SendToRouteResponse") proto.RegisterType((*Failure)(nil), "routerrpc.Failure") proto.RegisterType((*ChannelUpdate)(nil), "routerrpc.ChannelUpdate") + proto.RegisterType((*ResetMissionControlRequest)(nil), "routerrpc.ResetMissionControlRequest") + proto.RegisterType((*ResetMissionControlResponse)(nil), "routerrpc.ResetMissionControlResponse") } func init() { proto.RegisterFile("routerrpc/router.proto", fileDescriptor_7a0613f69d37b0a5) } var fileDescriptor_7a0613f69d37b0a5 = []byte{ - // 1224 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x74, 0x56, 0xef, 0x72, 0x1a, 0x37, - 0x10, 0x2f, 0xb1, 0xcd, 0x9f, 0x05, 0xcc, 0x59, 0xb6, 0x13, 0x4c, 0xe2, 0xc4, 0xa1, 0x9d, 0xd6, - 0x93, 0xe9, 0xd8, 0x53, 0x3a, 0xc9, 0xc7, 0x76, 0x08, 0x88, 0xfa, 0x26, 0x70, 0x47, 0x75, 0xe0, - 0xc4, 0xed, 0x07, 0x8d, 0xcc, 0xc9, 0x70, 0x35, 0xf7, 0xc7, 0x77, 0xa2, 0xb5, 0x5f, 0xa0, 0xaf, - 0xd3, 0xa7, 0xe8, 0x43, 0xf4, 0x11, 0xfa, 0x16, 0x1d, 0x49, 0x77, 0x80, 0x1d, 0xf7, 0x13, 0xa7, - 0xdf, 0xef, 0xa7, 0x5d, 0xed, 0x6a, 0x77, 0x05, 0x3c, 0x8d, 0xc3, 0x85, 0xe0, 0x71, 0x1c, 0x4d, - 0x4e, 0xf5, 0xd7, 0x49, 0x14, 0x87, 0x22, 0x44, 0xa5, 0x25, 0xde, 0x28, 0xc5, 0xd1, 0x44, 0xa3, - 0xcd, 0xbf, 0x73, 0xb0, 0x3d, 0x64, 0x77, 0x3e, 0x0f, 0x04, 0xe1, 0x37, 0x0b, 0x9e, 0x08, 0xf4, - 0x0c, 0x0a, 0x11, 0xbb, 0xa3, 0x31, 0xbf, 0xa9, 0xe7, 0x8e, 0x72, 0xc7, 0x25, 0x92, 0x8f, 0xd8, - 0x1d, 0xe1, 0x37, 0xa8, 0x09, 0xd5, 0x2b, 0xce, 0xe9, 0xdc, 0xf3, 0x3d, 0x41, 0x13, 0x26, 0xea, - 0x4f, 0x8e, 0x72, 0xc7, 0x1b, 0xa4, 0x7c, 0xc5, 0x79, 0x5f, 0x62, 0x0e, 0x13, 0xe8, 0x10, 0x60, - 0x32, 0x17, 0xbf, 0x6b, 0x51, 0x7d, 0xe3, 0x28, 0x77, 0xbc, 0x45, 0x4a, 0x12, 0x51, 0x0a, 0xf4, - 0x0d, 0xd4, 0x84, 0xe7, 0xf3, 0x70, 0x21, 0x68, 0xc2, 0x27, 0x61, 0xe0, 0x26, 0xf5, 0x4d, 0xa5, - 0xd9, 0x4e, 0x61, 0x47, 0xa3, 0xe8, 0x04, 0x76, 0xc3, 0x85, 0x98, 0x86, 0x5e, 0x30, 0xa5, 0x93, - 0x19, 0x0b, 0x02, 0x3e, 0xa7, 0x9e, 0x5b, 0xdf, 0x52, 0x1e, 0x77, 0x32, 0xaa, 0xa3, 0x19, 0xd3, - 0x6d, 0xfe, 0x06, 0xb5, 0x65, 0x18, 0x49, 0x14, 0x06, 0x09, 0x47, 0x07, 0x50, 0x94, 0x71, 0xcc, - 0x58, 0x32, 0x53, 0x81, 0x54, 0x88, 0x8c, 0xeb, 0x8c, 0x25, 0x33, 0xf4, 0x1c, 0x4a, 0x51, 0xcc, - 0xa9, 0xe7, 0xb3, 0x29, 0x57, 0x51, 0x54, 0x48, 0x31, 0x8a, 0xb9, 0x29, 0xd7, 0xe8, 0x15, 0x94, - 0x23, 0x6d, 0x8a, 0xf2, 0x38, 0x56, 0x31, 0x94, 0x08, 0xa4, 0x10, 0x8e, 0xe3, 0xe6, 0x0f, 0x50, - 0x23, 0x32, 0x97, 0x3d, 0xce, 0xb3, 0x9c, 0x21, 0xd8, 0x74, 0x79, 0x22, 0x52, 0x3f, 0xea, 0x5b, - 0xe6, 0x91, 0xf9, 0xeb, 0x89, 0xca, 0x33, 0x5f, 0xe6, 0xa8, 0xe9, 0x82, 0xb1, 0xda, 0x9f, 0x1e, - 0xf6, 0x18, 0x0c, 0x79, 0x3f, 0x32, 0x5c, 0x99, 0x63, 0x5f, 0xee, 0xca, 0xa9, 0x5d, 0xdb, 0x29, - 0xde, 0xe3, 0x7c, 0x90, 0x30, 0x81, 0xbe, 0xd6, 0x29, 0xa4, 0xf3, 0x70, 0x72, 0x4d, 0x5d, 0x3e, - 0x67, 0x77, 0xa9, 0xf9, 0xaa, 0x84, 0xfb, 0xe1, 0xe4, 0xba, 0x2b, 0xc1, 0xe6, 0xaf, 0x80, 0x1c, - 0x1e, 0xb8, 0xa3, 0x50, 0xf9, 0xca, 0x0e, 0xfa, 0x1a, 0x2a, 0x59, 0x70, 0x6b, 0x89, 0xc9, 0x02, - 0x56, 0xc9, 0x69, 0xc2, 0x96, 0x2a, 0x15, 0x65, 0xb6, 0xdc, 0xaa, 0x9c, 0xcc, 0x03, 0x59, 0x2f, - 0xda, 0x8c, 0xa6, 0x9a, 0x14, 0x76, 0xef, 0x19, 0x4f, 0xa3, 0x68, 0x80, 0x4c, 0xa3, 0x4e, 0x6b, - 0x6e, 0x99, 0x56, 0xb5, 0x46, 0xdf, 0x42, 0xe1, 0x8a, 0x79, 0xf3, 0x45, 0x9c, 0x19, 0x46, 0x27, - 0xcb, 0x8a, 0x3c, 0xe9, 0x69, 0x86, 0x64, 0x92, 0xe6, 0x9f, 0x05, 0x28, 0xa4, 0x20, 0x6a, 0xc1, - 0xe6, 0x24, 0x74, 0xb5, 0xc5, 0xed, 0xd6, 0xcb, 0xcf, 0xb7, 0x65, 0xbf, 0x9d, 0xd0, 0xe5, 0x44, - 0x69, 0x51, 0x0b, 0xf6, 0x53, 0x53, 0x34, 0x09, 0x17, 0xf1, 0x84, 0xd3, 0x68, 0x71, 0x79, 0xcd, - 0xef, 0xd2, 0xdb, 0xde, 0x4d, 0x49, 0x47, 0x71, 0x43, 0x45, 0xa1, 0x1f, 0x61, 0x3b, 0x2b, 0xb5, - 0x45, 0xe4, 0x32, 0xc1, 0xd5, 0xdd, 0x97, 0x5b, 0xf5, 0x35, 0x8f, 0x69, 0xc5, 0x8d, 0x15, 0x4f, - 0xaa, 0x93, 0xf5, 0xa5, 0x2c, 0xab, 0x99, 0x98, 0x4f, 0xf4, 0xed, 0xc9, 0xba, 0xde, 0x24, 0x45, - 0x09, 0xa8, 0x7b, 0x6b, 0x42, 0x35, 0x0c, 0xbc, 0x30, 0xa0, 0xc9, 0x8c, 0xd1, 0xd6, 0xdb, 0x77, - 0xaa, 0x96, 0x2b, 0xa4, 0xac, 0x40, 0x67, 0xc6, 0x5a, 0x6f, 0xdf, 0xc9, 0xd2, 0x53, 0xdd, 0xc3, - 0x6f, 0x23, 0x2f, 0xbe, 0xab, 0xe7, 0x8f, 0x72, 0xc7, 0x55, 0xa2, 0x1a, 0x0a, 0x2b, 0x04, 0xed, - 0xc1, 0xd6, 0xd5, 0x9c, 0x4d, 0x93, 0x7a, 0x41, 0x51, 0x7a, 0xd1, 0xfc, 0x67, 0x13, 0xca, 0x6b, - 0x29, 0x40, 0x15, 0x28, 0x12, 0xec, 0x60, 0x72, 0x8e, 0xbb, 0xc6, 0x17, 0xa8, 0x0e, 0x7b, 0x63, - 0xeb, 0x83, 0x65, 0x7f, 0xb4, 0xe8, 0xb0, 0x7d, 0x31, 0xc0, 0xd6, 0x88, 0x9e, 0xb5, 0x9d, 0x33, - 0x23, 0x87, 0x5e, 0x40, 0xdd, 0xb4, 0x3a, 0x36, 0x21, 0xb8, 0x33, 0x5a, 0x72, 0xed, 0x81, 0x3d, - 0xb6, 0x46, 0xc6, 0x13, 0xf4, 0x0a, 0x9e, 0xf7, 0x4c, 0xab, 0xdd, 0xa7, 0x2b, 0x4d, 0xa7, 0x3f, - 0x3a, 0xa7, 0xf8, 0xd3, 0xd0, 0x24, 0x17, 0xc6, 0xc6, 0x63, 0x82, 0xb3, 0x51, 0xbf, 0x93, 0x59, - 0xd8, 0x44, 0x07, 0xb0, 0xaf, 0x05, 0x7a, 0x0b, 0x1d, 0xd9, 0x36, 0x75, 0x6c, 0xdb, 0x32, 0xb6, - 0xd0, 0x0e, 0x54, 0x4d, 0xeb, 0xbc, 0xdd, 0x37, 0xbb, 0x94, 0xe0, 0x76, 0x7f, 0x60, 0xe4, 0xd1, - 0x2e, 0xd4, 0x1e, 0xea, 0x0a, 0xd2, 0x44, 0xa6, 0xb3, 0x2d, 0xd3, 0xb6, 0xe8, 0x39, 0x26, 0x8e, - 0x69, 0x5b, 0x46, 0x11, 0x3d, 0x05, 0x74, 0x9f, 0x3a, 0x1b, 0xb4, 0x3b, 0x46, 0x09, 0xed, 0xc3, - 0xce, 0x7d, 0xfc, 0x03, 0xbe, 0x30, 0x40, 0xa6, 0x41, 0x1f, 0x8c, 0xbe, 0xc7, 0x7d, 0xfb, 0x23, - 0x1d, 0x98, 0x96, 0x39, 0x18, 0x0f, 0x8c, 0x32, 0xda, 0x03, 0xa3, 0x87, 0x31, 0x35, 0x2d, 0x67, - 0xdc, 0xeb, 0x99, 0x1d, 0x13, 0x5b, 0x23, 0xa3, 0xa2, 0x3d, 0x3f, 0x16, 0x78, 0x55, 0x6e, 0xe8, - 0x9c, 0xb5, 0x2d, 0x0b, 0xf7, 0x69, 0xd7, 0x74, 0xda, 0xef, 0xfb, 0xb8, 0x6b, 0x6c, 0xa3, 0x43, - 0x38, 0x18, 0xe1, 0xc1, 0xd0, 0x26, 0x6d, 0x72, 0x41, 0x33, 0xbe, 0xd7, 0x36, 0xfb, 0x63, 0x82, - 0x8d, 0x1a, 0x7a, 0x0d, 0x87, 0x04, 0xff, 0x3c, 0x36, 0x09, 0xee, 0x52, 0xcb, 0xee, 0x62, 0xda, - 0xc3, 0xed, 0xd1, 0x98, 0x60, 0x3a, 0x30, 0x1d, 0xc7, 0xb4, 0x7e, 0x32, 0x0c, 0xf4, 0x15, 0x1c, - 0x2d, 0x25, 0x4b, 0x03, 0x0f, 0x54, 0x3b, 0x32, 0xbe, 0xec, 0x3e, 0x2d, 0xfc, 0x69, 0x44, 0x87, - 0x18, 0x13, 0x03, 0xa1, 0x06, 0x3c, 0x5d, 0xb9, 0xd7, 0x0e, 0x52, 0xdf, 0xbb, 0x92, 0x1b, 0x62, - 0x32, 0x68, 0x5b, 0xf2, 0x82, 0xef, 0x71, 0x7b, 0xf2, 0xd8, 0x2b, 0xee, 0xe1, 0xb1, 0xf7, 0x9b, - 0x7f, 0x6d, 0x40, 0xf5, 0x5e, 0xd1, 0xa3, 0x17, 0x50, 0x4a, 0xbc, 0x69, 0xc0, 0x84, 0x6c, 0x65, - 0xdd, 0xe5, 0x2b, 0x40, 0x3d, 0x00, 0x33, 0xe6, 0x05, 0x7a, 0xbc, 0xe8, 0x6e, 0x2b, 0x29, 0x44, - 0x0d, 0x97, 0x67, 0x50, 0x90, 0x3d, 0x23, 0x67, 0xf9, 0x86, 0x6a, 0x90, 0xbc, 0x5c, 0x9a, 0xae, - 0xb4, 0x2a, 0xe7, 0x57, 0x22, 0x98, 0x1f, 0xa9, 0xde, 0xa9, 0x92, 0x15, 0x80, 0xbe, 0x84, 0xaa, - 0xcf, 0x93, 0x84, 0x4d, 0x39, 0xd5, 0xf5, 0x0f, 0x4a, 0x51, 0x49, 0xc1, 0x9e, 0xc4, 0xa4, 0x28, - 0xeb, 0x5f, 0x2d, 0xda, 0xd2, 0xa2, 0x14, 0xd4, 0xa2, 0x87, 0xe3, 0x53, 0xb0, 0xb4, 0xcd, 0xd6, - 0xc7, 0xa7, 0x60, 0xe8, 0x0d, 0xec, 0xe8, 0x5e, 0xf6, 0x02, 0xcf, 0x5f, 0xf8, 0xba, 0xa7, 0x0b, - 0xea, 0xc8, 0x35, 0xd5, 0xd3, 0x1a, 0x57, 0xad, 0x7d, 0x00, 0xc5, 0x4b, 0x96, 0x70, 0x39, 0xb9, - 0xeb, 0x45, 0x65, 0xac, 0x20, 0xd7, 0x3d, 0xae, 0x1e, 0x21, 0x39, 0xcf, 0x63, 0x39, 0x4d, 0x4a, - 0x9a, 0xba, 0xe2, 0x9c, 0xc8, 0x3c, 0x2e, 0x3d, 0xb0, 0xdb, 0x95, 0x87, 0xf2, 0x9a, 0x07, 0x8d, - 0x2b, 0x0f, 0x6f, 0x60, 0x87, 0xdf, 0x8a, 0x98, 0xd1, 0x30, 0x62, 0x37, 0x0b, 0x4e, 0x5d, 0x26, - 0x58, 0xbd, 0xa2, 0x92, 0x5b, 0x53, 0x84, 0xad, 0xf0, 0x2e, 0x13, 0xac, 0xf5, 0x6f, 0x0e, 0xf2, - 0x6a, 0x2c, 0xc7, 0xa8, 0x0b, 0x65, 0x39, 0xa6, 0xd3, 0x97, 0x11, 0x1d, 0xac, 0x0d, 0xb2, 0xfb, - 0x8f, 0x7e, 0xa3, 0xf1, 0x18, 0x95, 0x4e, 0xf5, 0x0f, 0x60, 0xe0, 0x44, 0x78, 0xbe, 0x9c, 0x78, - 0xe9, 0xbb, 0x85, 0xd6, 0xf5, 0x0f, 0x1e, 0xc3, 0xc6, 0xf3, 0x47, 0xb9, 0xd4, 0x58, 0x5f, 0x1f, - 0x29, 0x7d, 0x39, 0xd0, 0xe1, 0x9a, 0xf6, 0xf3, 0xe7, 0xaa, 0xf1, 0xf2, 0xff, 0x68, 0x6d, 0xed, - 0xfd, 0x77, 0xbf, 0x9c, 0x4e, 0x3d, 0x31, 0x5b, 0x5c, 0x9e, 0x4c, 0x42, 0xff, 0x74, 0xee, 0x4d, - 0x67, 0x22, 0xf0, 0x82, 0x69, 0xc0, 0xc5, 0x1f, 0x61, 0x7c, 0x7d, 0x3a, 0x0f, 0xdc, 0x53, 0xf5, - 0x7a, 0x9d, 0x2e, 0xcd, 0x5c, 0xe6, 0xd5, 0x1f, 0x9f, 0xef, 0xff, 0x0b, 0x00, 0x00, 0xff, 0xff, - 0x89, 0x21, 0x7b, 0xbd, 0x28, 0x09, 0x00, 0x00, + // 1271 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x56, 0xdd, 0x72, 0x1a, 0x37, + 0x14, 0x2e, 0xf1, 0x0f, 0x70, 0x00, 0xb3, 0x96, 0xed, 0x04, 0xe3, 0x38, 0x71, 0xb6, 0x6d, 0xea, + 0xc9, 0x74, 0xec, 0x29, 0x9d, 0xe4, 0xb2, 0x1d, 0x02, 0xa2, 0xde, 0x09, 0xec, 0x52, 0x01, 0x4e, + 0xdc, 0x5e, 0x68, 0x64, 0x56, 0x86, 0xad, 0xd9, 0x1f, 0xef, 0x8a, 0x36, 0x7e, 0x81, 0xbe, 0x4e, + 0x9f, 0xa2, 0xd7, 0xbd, 0xee, 0xdb, 0x74, 0x24, 0xed, 0x02, 0x76, 0x9c, 0xe9, 0x15, 0xe8, 0xfb, + 0x3e, 0x9d, 0xa3, 0x73, 0x74, 0xce, 0xd1, 0xc2, 0xe3, 0x38, 0x9c, 0x0b, 0x1e, 0xc7, 0xd1, 0xf8, + 0x54, 0xff, 0x3b, 0x89, 0xe2, 0x50, 0x84, 0xa8, 0xb8, 0xc0, 0xeb, 0xc5, 0x38, 0x1a, 0x6b, 0xd4, + 0xfc, 0x3b, 0x07, 0x5b, 0x7d, 0x76, 0xeb, 0xf3, 0x40, 0x10, 0x7e, 0x33, 0xe7, 0x89, 0x40, 0x4f, + 0x20, 0x1f, 0xb1, 0x5b, 0x1a, 0xf3, 0x9b, 0x5a, 0xee, 0x28, 0x77, 0x5c, 0x24, 0x9b, 0x11, 0xbb, + 0x25, 0xfc, 0x06, 0x99, 0x50, 0xb9, 0xe2, 0x9c, 0xce, 0x3c, 0xdf, 0x13, 0x34, 0x61, 0xa2, 0xf6, + 0xe8, 0x28, 0x77, 0xbc, 0x46, 0x4a, 0x57, 0x9c, 0x77, 0x25, 0x36, 0x60, 0x02, 0x1d, 0x02, 0x8c, + 0x67, 0xe2, 0x77, 0x2d, 0xaa, 0xad, 0x1d, 0xe5, 0x8e, 0x37, 0x48, 0x51, 0x22, 0x4a, 0x81, 0xbe, + 0x81, 0xaa, 0xf0, 0x7c, 0x1e, 0xce, 0x05, 0x4d, 0xf8, 0x38, 0x0c, 0xdc, 0xa4, 0xb6, 0xae, 0x34, + 0x5b, 0x29, 0x3c, 0xd0, 0x28, 0x3a, 0x81, 0x9d, 0x70, 0x2e, 0x26, 0xa1, 0x17, 0x4c, 0xe8, 0x78, + 0xca, 0x82, 0x80, 0xcf, 0xa8, 0xe7, 0xd6, 0x36, 0x94, 0xc7, 0xed, 0x8c, 0x6a, 0x69, 0xc6, 0x72, + 0xcd, 0xdf, 0xa0, 0xba, 0x08, 0x23, 0x89, 0xc2, 0x20, 0xe1, 0x68, 0x1f, 0x0a, 0x32, 0x8e, 0x29, + 0x4b, 0xa6, 0x2a, 0x90, 0x32, 0x91, 0x71, 0x9d, 0xb1, 0x64, 0x8a, 0x0e, 0xa0, 0x18, 0xc5, 0x9c, + 0x7a, 0x3e, 0x9b, 0x70, 0x15, 0x45, 0x99, 0x14, 0xa2, 0x98, 0x5b, 0x72, 0x8d, 0x9e, 0x43, 0x29, + 0xd2, 0xa6, 0x28, 0x8f, 0x63, 0x15, 0x43, 0x91, 0x40, 0x0a, 0xe1, 0x38, 0x36, 0x7f, 0x80, 0x2a, + 0x91, 0xb9, 0xec, 0x70, 0x9e, 0xe5, 0x0c, 0xc1, 0xba, 0xcb, 0x13, 0x91, 0xfa, 0x51, 0xff, 0x65, + 0x1e, 0x99, 0xbf, 0x9a, 0xa8, 0x4d, 0xe6, 0xcb, 0x1c, 0x99, 0x2e, 0x18, 0xcb, 0xfd, 0xe9, 0x61, + 0x8f, 0xc1, 0x90, 0xf7, 0x23, 0xc3, 0x95, 0x39, 0xf6, 0xe5, 0xae, 0x9c, 0xda, 0xb5, 0x95, 0xe2, + 0x1d, 0xce, 0x7b, 0x09, 0x13, 0xe8, 0xa5, 0x4e, 0x21, 0x9d, 0x85, 0xe3, 0x6b, 0xea, 0xf2, 0x19, + 0xbb, 0x4d, 0xcd, 0x57, 0x24, 0xdc, 0x0d, 0xc7, 0xd7, 0x6d, 0x09, 0x9a, 0xbf, 0x02, 0x1a, 0xf0, + 0xc0, 0x1d, 0x86, 0xca, 0x57, 0x76, 0xd0, 0x17, 0x50, 0xce, 0x82, 0x5b, 0x49, 0x4c, 0x16, 0xb0, + 0x4a, 0x8e, 0x09, 0x1b, 0xaa, 0x54, 0x94, 0xd9, 0x52, 0xa3, 0x7c, 0x32, 0x0b, 0x64, 0xbd, 0x68, + 0x33, 0x9a, 0x32, 0x29, 0xec, 0xdc, 0x31, 0x9e, 0x46, 0x51, 0x07, 0x99, 0x46, 0x9d, 0xd6, 0xdc, + 0x22, 0xad, 0x6a, 0x8d, 0xbe, 0x85, 0xfc, 0x15, 0xf3, 0x66, 0xf3, 0x38, 0x33, 0x8c, 0x4e, 0x16, + 0x15, 0x79, 0xd2, 0xd1, 0x0c, 0xc9, 0x24, 0xe6, 0x9f, 0x79, 0xc8, 0xa7, 0x20, 0x6a, 0xc0, 0xfa, + 0x38, 0x74, 0xb5, 0xc5, 0xad, 0xc6, 0xb3, 0x4f, 0xb7, 0x65, 0xbf, 0xad, 0xd0, 0xe5, 0x44, 0x69, + 0x51, 0x03, 0xf6, 0x52, 0x53, 0x34, 0x09, 0xe7, 0xf1, 0x98, 0xd3, 0x68, 0x7e, 0x79, 0xcd, 0x6f, + 0xd3, 0xdb, 0xde, 0x49, 0xc9, 0x81, 0xe2, 0xfa, 0x8a, 0x42, 0x3f, 0xc2, 0x56, 0x56, 0x6a, 0xf3, + 0xc8, 0x65, 0x82, 0xab, 0xbb, 0x2f, 0x35, 0x6a, 0x2b, 0x1e, 0xd3, 0x8a, 0x1b, 0x29, 0x9e, 0x54, + 0xc6, 0xab, 0x4b, 0x59, 0x56, 0x53, 0x31, 0x1b, 0xeb, 0xdb, 0x93, 0x75, 0xbd, 0x4e, 0x0a, 0x12, + 0x50, 0xf7, 0x66, 0x42, 0x25, 0x0c, 0xbc, 0x30, 0xa0, 0xc9, 0x94, 0xd1, 0xc6, 0xeb, 0x37, 0xaa, + 0x96, 0xcb, 0xa4, 0xa4, 0xc0, 0xc1, 0x94, 0x35, 0x5e, 0xbf, 0x91, 0xa5, 0xa7, 0xba, 0x87, 0x7f, + 0x8c, 0xbc, 0xf8, 0xb6, 0xb6, 0x79, 0x94, 0x3b, 0xae, 0x10, 0xd5, 0x50, 0x58, 0x21, 0x68, 0x17, + 0x36, 0xae, 0x66, 0x6c, 0x92, 0xd4, 0xf2, 0x8a, 0xd2, 0x0b, 0xf3, 0xdf, 0x75, 0x28, 0xad, 0xa4, + 0x00, 0x95, 0xa1, 0x40, 0xf0, 0x00, 0x93, 0x73, 0xdc, 0x36, 0xbe, 0x40, 0x35, 0xd8, 0x1d, 0xd9, + 0xef, 0x6c, 0xe7, 0xbd, 0x4d, 0xfb, 0xcd, 0x8b, 0x1e, 0xb6, 0x87, 0xf4, 0xac, 0x39, 0x38, 0x33, + 0x72, 0xe8, 0x29, 0xd4, 0x2c, 0xbb, 0xe5, 0x10, 0x82, 0x5b, 0xc3, 0x05, 0xd7, 0xec, 0x39, 0x23, + 0x7b, 0x68, 0x3c, 0x42, 0xcf, 0xe1, 0xa0, 0x63, 0xd9, 0xcd, 0x2e, 0x5d, 0x6a, 0x5a, 0xdd, 0xe1, + 0x39, 0xc5, 0x1f, 0xfa, 0x16, 0xb9, 0x30, 0xd6, 0x1e, 0x12, 0x9c, 0x0d, 0xbb, 0xad, 0xcc, 0xc2, + 0x3a, 0xda, 0x87, 0x3d, 0x2d, 0xd0, 0x5b, 0xe8, 0xd0, 0x71, 0xe8, 0xc0, 0x71, 0x6c, 0x63, 0x03, + 0x6d, 0x43, 0xc5, 0xb2, 0xcf, 0x9b, 0x5d, 0xab, 0x4d, 0x09, 0x6e, 0x76, 0x7b, 0xc6, 0x26, 0xda, + 0x81, 0xea, 0x7d, 0x5d, 0x5e, 0x9a, 0xc8, 0x74, 0x8e, 0x6d, 0x39, 0x36, 0x3d, 0xc7, 0x64, 0x60, + 0x39, 0xb6, 0x51, 0x40, 0x8f, 0x01, 0xdd, 0xa5, 0xce, 0x7a, 0xcd, 0x96, 0x51, 0x44, 0x7b, 0xb0, + 0x7d, 0x17, 0x7f, 0x87, 0x2f, 0x0c, 0x90, 0x69, 0xd0, 0x07, 0xa3, 0x6f, 0x71, 0xd7, 0x79, 0x4f, + 0x7b, 0x96, 0x6d, 0xf5, 0x46, 0x3d, 0xa3, 0x84, 0x76, 0xc1, 0xe8, 0x60, 0x4c, 0x2d, 0x7b, 0x30, + 0xea, 0x74, 0xac, 0x96, 0x85, 0xed, 0xa1, 0x51, 0xd6, 0x9e, 0x1f, 0x0a, 0xbc, 0x22, 0x37, 0xb4, + 0xce, 0x9a, 0xb6, 0x8d, 0xbb, 0xb4, 0x6d, 0x0d, 0x9a, 0x6f, 0xbb, 0xb8, 0x6d, 0x6c, 0xa1, 0x43, + 0xd8, 0x1f, 0xe2, 0x5e, 0xdf, 0x21, 0x4d, 0x72, 0x41, 0x33, 0xbe, 0xd3, 0xb4, 0xba, 0x23, 0x82, + 0x8d, 0x2a, 0x7a, 0x01, 0x87, 0x04, 0xff, 0x3c, 0xb2, 0x08, 0x6e, 0x53, 0xdb, 0x69, 0x63, 0xda, + 0xc1, 0xcd, 0xe1, 0x88, 0x60, 0xda, 0xb3, 0x06, 0x03, 0xcb, 0xfe, 0xc9, 0x30, 0xd0, 0x57, 0x70, + 0xb4, 0x90, 0x2c, 0x0c, 0xdc, 0x53, 0x6d, 0xcb, 0xf8, 0xb2, 0xfb, 0xb4, 0xf1, 0x87, 0x21, 0xed, + 0x63, 0x4c, 0x0c, 0x84, 0xea, 0xf0, 0x78, 0xe9, 0x5e, 0x3b, 0x48, 0x7d, 0xef, 0x48, 0xae, 0x8f, + 0x49, 0xaf, 0x69, 0xcb, 0x0b, 0xbe, 0xc3, 0xed, 0xca, 0x63, 0x2f, 0xb9, 0xfb, 0xc7, 0xde, 0x33, + 0xff, 0x5a, 0x83, 0xca, 0x9d, 0xa2, 0x47, 0x4f, 0xa1, 0x98, 0x78, 0x93, 0x80, 0x09, 0xd9, 0xca, + 0xba, 0xcb, 0x97, 0x80, 0x7a, 0x00, 0xa6, 0xcc, 0x0b, 0xf4, 0x78, 0xd1, 0xdd, 0x56, 0x54, 0x88, + 0x1a, 0x2e, 0x4f, 0x20, 0x2f, 0x7b, 0x46, 0xce, 0xf2, 0x35, 0xd5, 0x20, 0x9b, 0x72, 0x69, 0xb9, + 0xd2, 0xaa, 0x9c, 0x5f, 0x89, 0x60, 0x7e, 0xa4, 0x7a, 0xa7, 0x42, 0x96, 0x00, 0xfa, 0x12, 0x2a, + 0x3e, 0x4f, 0x12, 0x36, 0xe1, 0x54, 0xd7, 0x3f, 0x28, 0x45, 0x39, 0x05, 0x3b, 0x12, 0x93, 0xa2, + 0xac, 0x7f, 0xb5, 0x68, 0x43, 0x8b, 0x52, 0x50, 0x8b, 0xee, 0x8f, 0x4f, 0xc1, 0xd2, 0x36, 0x5b, + 0x1d, 0x9f, 0x82, 0xa1, 0x57, 0xb0, 0xad, 0x7b, 0xd9, 0x0b, 0x3c, 0x7f, 0xee, 0xeb, 0x9e, 0xce, + 0xab, 0x23, 0x57, 0x55, 0x4f, 0x6b, 0x5c, 0xb5, 0xf6, 0x3e, 0x14, 0x2e, 0x59, 0xc2, 0xe5, 0xe4, + 0xae, 0x15, 0x94, 0xb1, 0xbc, 0x5c, 0x77, 0xb8, 0x7a, 0x84, 0xe4, 0x3c, 0x8f, 0xe5, 0x34, 0x29, + 0x6a, 0xea, 0x8a, 0x73, 0x22, 0xf3, 0xb8, 0xf0, 0xc0, 0x3e, 0x2e, 0x3d, 0x94, 0x56, 0x3c, 0x68, + 0x5c, 0x79, 0x78, 0x05, 0xdb, 0xfc, 0xa3, 0x88, 0x19, 0x0d, 0x23, 0x76, 0x33, 0xe7, 0xd4, 0x65, + 0x82, 0xd5, 0xca, 0x2a, 0xb9, 0x55, 0x45, 0x38, 0x0a, 0x6f, 0x33, 0xc1, 0xcc, 0xa7, 0x50, 0x27, + 0x3c, 0xe1, 0xa2, 0xe7, 0x25, 0x89, 0x17, 0x06, 0xad, 0x30, 0x10, 0x71, 0x38, 0x4b, 0x1f, 0x00, + 0xf3, 0x10, 0x0e, 0x1e, 0x64, 0xf5, 0x04, 0x6f, 0xfc, 0xf3, 0x08, 0x36, 0xd5, 0x4c, 0x8f, 0x51, + 0x1b, 0x4a, 0x72, 0xc6, 0xa7, 0xcf, 0x2a, 0xda, 0x5f, 0x99, 0x82, 0x77, 0xbf, 0x18, 0xea, 0xf5, + 0x87, 0xa8, 0xf4, 0x49, 0x78, 0x07, 0x06, 0x4e, 0x84, 0xe7, 0xcb, 0x71, 0x99, 0x3e, 0x7a, 0x68, + 0x55, 0x7f, 0xef, 0x25, 0xad, 0x1f, 0x3c, 0xc8, 0xa5, 0xc6, 0xba, 0xfa, 0x48, 0xe9, 0xb3, 0x83, + 0x0e, 0x57, 0xb4, 0x9f, 0xbe, 0x75, 0xf5, 0x67, 0x9f, 0xa3, 0x53, 0x6b, 0x2e, 0xec, 0x3c, 0x90, + 0x0a, 0xf4, 0xf5, 0xea, 0x09, 0x3e, 0x9b, 0xc8, 0xfa, 0xcb, 0xff, 0x93, 0x69, 0x2f, 0x6f, 0xbf, + 0xfb, 0xe5, 0x74, 0xe2, 0x89, 0xe9, 0xfc, 0xf2, 0x64, 0x1c, 0xfa, 0xa7, 0x33, 0x6f, 0x32, 0x15, + 0x81, 0x17, 0x4c, 0x02, 0x2e, 0xfe, 0x08, 0xe3, 0xeb, 0xd3, 0x59, 0xe0, 0x9e, 0xaa, 0x07, 0xf6, + 0x74, 0x61, 0xee, 0x72, 0x53, 0x7d, 0x9b, 0x7d, 0xff, 0x5f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x01, + 0xde, 0xa4, 0x83, 0xcb, 0x09, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -860,6 +927,10 @@ type RouterClient interface { //differs from SendPayment in that it allows users to specify a full route //manually. This can be used for things like rebalancing, and atomic swaps. SendToRoute(ctx context.Context, in *SendToRouteRequest, opts ...grpc.CallOption) (*SendToRouteResponse, error) + //* + //ResetMissionControl clears all mission control state and starts with a clean + //slate. + ResetMissionControl(ctx context.Context, in *ResetMissionControlRequest, opts ...grpc.CallOption) (*ResetMissionControlResponse, error) } type routerClient struct { @@ -897,6 +968,15 @@ func (c *routerClient) SendToRoute(ctx context.Context, in *SendToRouteRequest, return out, nil } +func (c *routerClient) ResetMissionControl(ctx context.Context, in *ResetMissionControlRequest, opts ...grpc.CallOption) (*ResetMissionControlResponse, error) { + out := new(ResetMissionControlResponse) + err := c.cc.Invoke(ctx, "/routerrpc.Router/ResetMissionControl", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // RouterServer is the server API for Router service. type RouterServer interface { //* @@ -915,6 +995,10 @@ type RouterServer interface { //differs from SendPayment in that it allows users to specify a full route //manually. This can be used for things like rebalancing, and atomic swaps. SendToRoute(context.Context, *SendToRouteRequest) (*SendToRouteResponse, error) + //* + //ResetMissionControl clears all mission control state and starts with a clean + //slate. + ResetMissionControl(context.Context, *ResetMissionControlRequest) (*ResetMissionControlResponse, error) } func RegisterRouterServer(s *grpc.Server, srv RouterServer) { @@ -975,6 +1059,24 @@ func _Router_SendToRoute_Handler(srv interface{}, ctx context.Context, dec func( return interceptor(ctx, in, info, handler) } +func _Router_ResetMissionControl_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ResetMissionControlRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(RouterServer).ResetMissionControl(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/routerrpc.Router/ResetMissionControl", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(RouterServer).ResetMissionControl(ctx, req.(*ResetMissionControlRequest)) + } + return interceptor(ctx, in, info, handler) +} + var _Router_serviceDesc = grpc.ServiceDesc{ ServiceName: "routerrpc.Router", HandlerType: (*RouterServer)(nil), @@ -991,6 +1093,10 @@ var _Router_serviceDesc = grpc.ServiceDesc{ MethodName: "SendToRoute", Handler: _Router_SendToRoute_Handler, }, + { + MethodName: "ResetMissionControl", + Handler: _Router_ResetMissionControl_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "routerrpc/router.proto", diff --git a/lnrpc/routerrpc/router.proto b/lnrpc/routerrpc/router.proto index dc875d680..852170546 100644 --- a/lnrpc/routerrpc/router.proto +++ b/lnrpc/routerrpc/router.proto @@ -245,6 +245,9 @@ message ChannelUpdate { */ bytes extra_opaque_data = 12; } +message ResetMissionControlRequest{} + +message ResetMissionControlResponse{} service Router { /** @@ -268,4 +271,10 @@ service Router { manually. This can be used for things like rebalancing, and atomic swaps. */ rpc SendToRoute(SendToRouteRequest) returns (SendToRouteResponse); + + /** + ResetMissionControl clears all mission control state and starts with a clean + slate. + */ + rpc ResetMissionControl(ResetMissionControlRequest) returns (ResetMissionControlResponse); } diff --git a/lnrpc/routerrpc/router_backend.go b/lnrpc/routerrpc/router_backend.go index 2b66f1f7c..97d4d88bd 100644 --- a/lnrpc/routerrpc/router_backend.go +++ b/lnrpc/routerrpc/router_backend.go @@ -36,6 +36,8 @@ type RouterBackend struct { FindRoute func(source, target route.Vertex, amt lnwire.MilliSatoshi, restrictions *routing.RestrictParams, finalExpiry ...uint16) (*route.Route, error) + + MissionControl *routing.MissionControl } // QueryRoutes attempts to query the daemons' Channel Router for a possible diff --git a/lnrpc/routerrpc/router_server.go b/lnrpc/routerrpc/router_server.go index 9f08237ad..d47dbe69d 100644 --- a/lnrpc/routerrpc/router_server.go +++ b/lnrpc/routerrpc/router_server.go @@ -59,6 +59,10 @@ var ( Entity: "offchain", Action: "read", }}, + "/routerrpc.Router/ResetMissionControl": {{ + Entity: "offchain", + Action: "write", + }}, } // DefaultRouterMacFilename is the default name of the router macaroon @@ -439,3 +443,13 @@ func marshallChannelUpdate(update *lnwire.ChannelUpdate) *ChannelUpdate { ExtraOpaqueData: update.ExtraOpaqueData, } } + +// ResetMissionControl clears all mission control state and starts with a clean +// slate. +func (s *Server) ResetMissionControl(ctx context.Context, + req *ResetMissionControlRequest) (*ResetMissionControlResponse, error) { + + s.cfg.RouterBackend.MissionControl.ResetHistory() + + return &ResetMissionControlResponse{}, nil +} diff --git a/routing/router.go b/routing/router.go index 37443b2df..1e4ec7063 100644 --- a/routing/router.go +++ b/routing/router.go @@ -1598,7 +1598,9 @@ type LightningPayment struct { // will be returned which describes the path the successful payment traversed // within the network to reach the destination. Additionally, the payment // preimage will also be returned. -func (r *ChannelRouter) SendPayment(payment *LightningPayment) ([32]byte, *route.Route, error) { +func (r *ChannelRouter) SendPayment(payment *LightningPayment) ([32]byte, + *route.Route, error) { + // Before starting the HTLC routing attempt, we'll create a fresh // payment session which will report our errors back to mission // control. diff --git a/rpcserver.go b/rpcserver.go index eebed0d26..e2381987b 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -476,7 +476,8 @@ func newRPCServer(s *server, macService *macaroons.Service, return info.NodeKey1Bytes, info.NodeKey2Bytes, nil }, - FindRoute: s.chanRouter.FindRoute, + FindRoute: s.chanRouter.FindRoute, + MissionControl: s.missionControl, } var ( diff --git a/server.go b/server.go index 8c5fafe06..d11fede01 100644 --- a/server.go +++ b/server.go @@ -187,6 +187,8 @@ type server struct { breachArbiter *breachArbiter + missionControl *routing.MissionControl + chanRouter *routing.ChannelRouter authGossiper *discovery.AuthenticatedGossiper @@ -646,7 +648,7 @@ func newServer(listenAddrs []net.Addr, chanDB *channeldb.DB, cc *chainControl, return link.Bandwidth() } - missionControl := routing.NewMissionControl( + s.missionControl = routing.NewMissionControl( chanGraph, selfNode, queryBandwidth, ) @@ -656,7 +658,7 @@ func newServer(listenAddrs []net.Addr, chanDB *channeldb.DB, cc *chainControl, ChainView: cc.chainView, Payer: s.htlcSwitch, Control: channeldb.NewPaymentControl(chanDB), - MissionControl: missionControl, + MissionControl: s.missionControl, ChannelPruneExpiry: routing.DefaultChannelPruneExpiry, GraphPruneInterval: time.Duration(time.Hour), QueryBandwidth: queryBandwidth, From b6102ad191f1a033a81fc846953298341e3cb73a Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Tue, 21 May 2019 10:04:57 +0200 Subject: [PATCH 04/10] routing: remove querybandwidth self node check This function is only ever called for channels connected to self. --- server.go | 9 --------- 1 file changed, 9 deletions(-) diff --git a/server.go b/server.go index d11fede01..cac4125b0 100644 --- a/server.go +++ b/server.go @@ -619,15 +619,6 @@ func newServer(listenAddrs []net.Addr, chanDB *channeldb.DB, cc *chainControl, } queryBandwidth := func(edge *channeldb.ChannelEdgeInfo) lnwire.MilliSatoshi { - // If we aren't on either side of this edge, then we'll - // just thread through the capacity of the edge as we - // know it. - if !bytes.Equal(edge.NodeKey1Bytes[:], selfNode.PubKeyBytes[:]) && - !bytes.Equal(edge.NodeKey2Bytes[:], selfNode.PubKeyBytes[:]) { - - return lnwire.NewMSatFromSatoshis(edge.Capacity) - } - cid := lnwire.NewChanIDFromOutPoint(&edge.ChannelPoint) link, err := s.htlcSwitch.GetLink(cid) if err != nil { From 6b70791c2db768292965325d77179d364982088b Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Tue, 19 Mar 2019 11:45:10 +0100 Subject: [PATCH 05/10] routing: use probability source in path finding This PR replaces the previously used edge and node ignore lists in path finding by a probability based system. It modifies path finding so that it not only compares routes on fee and time lock, but also takes route success probability into account. Allowing routes to be compared based on success probability is achieved by introducing a 'virtual' cost of a payment attempt and using that to translate probability into another cost factor. --- lnrpc/routerrpc/router_backend.go | 18 +- lnrpc/routerrpc/router_backend_test.go | 29 ++-- routing/heap.go | 10 +- routing/pathfind.go | 139 ++++++++++++---- routing/pathfind_test.go | 218 ++++++++++++++++++------- routing/payment_session.go | 24 ++- routing/router_test.go | 7 +- 7 files changed, 327 insertions(+), 118 deletions(-) diff --git a/lnrpc/routerrpc/router_backend.go b/lnrpc/routerrpc/router_backend.go index 97d4d88bd..dfda2109a 100644 --- a/lnrpc/routerrpc/router_backend.go +++ b/lnrpc/routerrpc/router_backend.go @@ -123,9 +123,21 @@ func (r *RouterBackend) QueryRoutes(ctx context.Context, } restrictions := &routing.RestrictParams{ - FeeLimit: feeLimit, - IgnoredNodes: ignoredNodes, - IgnoredEdges: ignoredEdges, + FeeLimit: feeLimit, + ProbabilitySource: func(node route.Vertex, + edge routing.EdgeLocator) float64 { + + if _, ok := ignoredNodes[node]; ok { + return 0 + } + + if _, ok := ignoredEdges[edge]; ok { + return 0 + } + + return 1 + }, + PaymentAttemptPenalty: routing.DefaultPaymentAttemptPenalty, } // Query the channel router for a possible path to the destination that diff --git a/lnrpc/routerrpc/router_backend_test.go b/lnrpc/routerrpc/router_backend_test.go index bfe7a488c..fee5fb4b3 100644 --- a/lnrpc/routerrpc/router_backend_test.go +++ b/lnrpc/routerrpc/router_backend_test.go @@ -39,6 +39,11 @@ func TestQueryRoutes(t *testing.T) { t.Fatal(err) } + ignoredEdge := routing.EdgeLocator{ + ChannelID: 555, + Direction: 1, + } + request := &lnrpc.QueryRoutesRequest{ PubKey: destKey, Amt: 100000, @@ -75,22 +80,22 @@ func TestQueryRoutes(t *testing.T) { t.Fatal("unexpected fee limit") } - if len(restrictions.IgnoredEdges) != 1 { - t.Fatal("unexpected ignored edges map size") + if restrictions.ProbabilitySource(route.Vertex{}, + ignoredEdge, + ) != 0 { + t.Fatal("expecting 0% probability for ignored edge") } - if _, ok := restrictions.IgnoredEdges[routing.EdgeLocator{ - ChannelID: 555, Direction: 1, - }]; !ok { - t.Fatal("unexpected ignored edge") + if restrictions.ProbabilitySource(ignoreNodeVertex, + routing.EdgeLocator{}, + ) != 0 { + t.Fatal("expecting 0% probability for ignored node") } - if len(restrictions.IgnoredNodes) != 1 { - t.Fatal("unexpected ignored nodes map size") - } - - if _, ok := restrictions.IgnoredNodes[ignoreNodeVertex]; !ok { - t.Fatal("unexpected ignored node") + if restrictions.ProbabilitySource(route.Vertex{}, + routing.EdgeLocator{}, + ) != 1 { + t.Fatal("expecting 100% probability") } hops := []*route.Hop{{}} diff --git a/routing/heap.go b/routing/heap.go index 80336fd0c..be7acaf0a 100644 --- a/routing/heap.go +++ b/routing/heap.go @@ -25,8 +25,14 @@ type nodeWithDist struct { // node. This value does not include the final cltv. incomingCltv uint32 - // fee is the fee that this node is charging for forwarding. - fee lnwire.MilliSatoshi + // probability is the probability that from this node onward the route + // is successful. + probability float64 + + // weight is the cost of the route from this node to the destination. + // Includes the routing fees and a virtual cost factor to account for + // time locks. + weight int64 } // distanceHeap is a min-distance heap that's used within our path finding diff --git a/routing/pathfind.go b/routing/pathfind.go index 222479854..4115bcd40 100644 --- a/routing/pathfind.go +++ b/routing/pathfind.go @@ -40,6 +40,14 @@ type pathFinder = func(g *graphParams, r *RestrictParams, source, target route.Vertex, amt lnwire.MilliSatoshi) ( []*channeldb.ChannelEdgePolicy, error) +var ( + // DefaultPaymentAttemptPenalty is the virtual cost in path finding weight + // units of executing a payment attempt that fails. It is used to trade + // off potentially better routes against their probability of + // succeeding. + DefaultPaymentAttemptPenalty = lnwire.NewMSatFromSatoshis(100) +) + // edgePolicyWithSource is a helper struct to keep track of the source node // of a channel edge. ChannelEdgePolicy only contains to destination node // of the edge. @@ -228,13 +236,9 @@ type graphParams struct { // RestrictParams wraps the set of restrictions passed to findPath that the // found path must adhere to. type RestrictParams struct { - // IgnoredNodes is an optional set of nodes that should be ignored if - // encountered during path finding. - IgnoredNodes map[route.Vertex]struct{} - - // IgnoredEdges is an optional set of edges that should be ignored if - // encountered during path finding. - IgnoredEdges map[EdgeLocator]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) float64 // FeeLimit is a maximum fee amount allowed to be used on the path from // the source to the target. @@ -248,6 +252,12 @@ type RestrictParams struct { // ctlv. After path finding is complete, the caller needs to increase // all cltv expiry heights with the required final cltv delta. CltvLimit *uint32 + + // PaymentAttemptPenalty is the virtual cost in path finding weight + // units of executing a payment attempt that fails. It is used to trade + // off potentially better routes against their probability of + // succeeding. + PaymentAttemptPenalty lnwire.MilliSatoshi } // findPath attempts to find a path from the source node within the @@ -331,10 +341,11 @@ func findPath(g *graphParams, r *RestrictParams, source, target route.Vertex, targetNode := &channeldb.LightningNode{PubKeyBytes: target} distance[target] = nodeWithDist{ dist: 0, + weight: 0, node: targetNode, amountToReceive: amt, - fee: 0, incomingCltv: 0, + probability: 1, } // We'll use this map as a series of "next" hop pointers. So to get @@ -342,15 +353,6 @@ func findPath(g *graphParams, r *RestrictParams, source, target route.Vertex, // mapped to within `next`. next := make(map[route.Vertex]*channeldb.ChannelEdgePolicy) - ignoredEdges := r.IgnoredEdges - if ignoredEdges == nil { - ignoredEdges = make(map[EdgeLocator]struct{}) - } - ignoredNodes := r.IgnoredNodes - if ignoredNodes == nil { - ignoredNodes = make(map[route.Vertex]struct{}) - } - // processEdge is a helper closure that will be used to make sure edges // satisfy our specific requirements. processEdge := func(fromNode *channeldb.LightningNode, @@ -380,21 +382,26 @@ func findPath(g *graphParams, r *RestrictParams, source, target route.Vertex, return } - // If this vertex or edge has been black listed, then we'll - // skip exploring this edge. - if _, ok := ignoredNodes[fromVertex]; ok { - return - } - - locator := newEdgeLocator(edge) - if _, ok := ignoredEdges[*locator]; ok { - return - } - + // Calculate amount that the candidate node would have to sent + // out. toNodeDist := distance[toNode] - amountToSend := toNodeDist.amountToReceive + // Request the success probability for this edge. + locator := newEdgeLocator(edge) + edgeProbability := r.ProbabilitySource( + fromVertex, *locator, + ) + + log.Tracef("path finding probability: fromnode=%v, chanid=%v, "+ + "probability=%v", fromVertex, locator.ChannelID, + edgeProbability) + + // If the probability is zero, there is no point in trying. + if edgeProbability == 0 { + return + } + // If the estimated bandwidth of the channel edge is not able // to carry the amount that needs to be send, return. if bandwidth < amountToSend { @@ -453,19 +460,32 @@ func findPath(g *graphParams, r *RestrictParams, source, target route.Vertex, return } + // Calculate total probability of successfully reaching target + // by multiplying the probabilities. Both this edge and the rest + // of the route must succeed. + probability := toNodeDist.probability * edgeProbability + // By adding fromNode in the route, there will be an extra // weight composed of the fee that this node will charge and // the amount that will be locked for timeLockDelta blocks in // the HTLC that is handed out to fromNode. weight := edgeWeight(amountToReceive, fee, timeLockDelta) - // Compute the tentative distance to this new channel/edge - // which is the distance from our toNode to the target node + // Compute the tentative weight to this new channel/edge + // which is the weight from our toNode to the target node // plus the weight of this edge. - tempDist := toNodeDist.dist + weight + tempWeight := toNodeDist.weight + weight - // If this new tentative distance is not better than the current - // best known distance to this node, return. + // Add an extra factor to the weight to take into account the + // probability. + tempDist := getProbabilityBasedDist( + tempWeight, probability, int64(r.PaymentAttemptPenalty), + ) + + // If the current best route is better than this candidate + // route, return. It is important to also return if the distance + // is equal, because otherwise the algorithm could run into an + // endless loop. if tempDist >= distance[fromVertex].dist { return } @@ -483,10 +503,11 @@ func findPath(g *graphParams, r *RestrictParams, source, target route.Vertex, // map is populated with this edge. distance[fromVertex] = nodeWithDist{ dist: tempDist, + weight: tempWeight, node: fromNode, amountToReceive: amountToReceive, - fee: fee, incomingCltv: incomingCltv, + probability: probability, } next[fromVertex] = edge @@ -614,5 +635,53 @@ func findPath(g *graphParams, r *RestrictParams, source, target route.Vertex, "too many hops") } + log.Debugf("Found route: probability=%v, hops=%v, fee=%v\n", + distance[source].probability, numEdges, + distance[source].amountToReceive-amt) + return pathEdges, nil } + +// getProbabilityBasedDist converts a weight into a distance that takes into +// account the success probability and the (virtual) cost of a failed payment +// attempt. +// +// Derivation: +// +// Suppose there are two routes A and B with fees Fa and Fb and success +// probabilities Pa and Pb. +// +// Is the expected cost of trying route A first and then B lower than trying the +// other way around? +// +// The expected cost of A-then-B is: Pa*Fa + (1-Pa)*Pb*(c+Fb) +// +// The expected cost of B-then-A is: Pb*Fb + (1-Pb)*Pa*(c+Fa) +// +// In these equations, the term representing the case where both A and B fail is +// left out because its value would be the same in both cases. +// +// Pa*Fa + (1-Pa)*Pb*(c+Fb) < Pb*Fb + (1-Pb)*Pa*(c+Fa) +// +// Pa*Fa + Pb*c + Pb*Fb - Pa*Pb*c - Pa*Pb*Fb < Pb*Fb + Pa*c + Pa*Fa - Pa*Pb*c - Pa*Pb*Fa +// +// Removing terms that cancel out: +// Pb*c - Pa*Pb*Fb < Pa*c - Pa*Pb*Fa +// +// Divide by Pa*Pb: +// c/Pa - Fb < c/Pb - Fa +// +// Move terms around: +// Fa + c/Pa < Fb + c/Pb +// +// So the value of F + c/P can be used to compare routes. +func getProbabilityBasedDist(weight int64, probability float64, penalty int64) int64 { + // Clamp probability to prevent overflow. + const minProbability = 0.00001 + + if probability < minProbability { + return infinity + } + + return weight + int64(float64(penalty)/probability) +} diff --git a/routing/pathfind_test.go b/routing/pathfind_test.go index fb5ca0a57..280dd64a3 100644 --- a/routing/pathfind_test.go +++ b/routing/pathfind_test.go @@ -52,7 +52,8 @@ const ( var ( noRestrictions = &RestrictParams{ - FeeLimit: noFeeLimit, + FeeLimit: noFeeLimit, + ProbabilitySource: noProbabilitySource, } ) @@ -72,6 +73,12 @@ var ( } ) +// noProbabilitySource is used in testing to return the same probability 1 for +// all edges. +func noProbabilitySource(route.Vertex, EdgeLocator) float64 { + return 1 +} + // testGraph is the struct which corresponds to the JSON format used to encode // graphs within the files in the testdata directory. // @@ -635,9 +642,7 @@ func TestFindLowestFeePath(t *testing.T) { &graphParams{ graph: testGraphInstance.graph, }, - &RestrictParams{ - FeeLimit: noFeeLimit, - }, + noRestrictions, sourceNode.PubKeyBytes, target, paymentAmt, ) if err != nil { @@ -776,7 +781,8 @@ func testBasicGraphPathFindingCase(t *testing.T, graphInstance *testGraphInstanc graph: graphInstance.graph, }, &RestrictParams{ - FeeLimit: test.feeLimit, + FeeLimit: test.feeLimit, + ProbabilitySource: noProbabilitySource, }, sourceNode.PubKeyBytes, target, paymentAmt, ) @@ -944,9 +950,7 @@ func TestPathFindingWithAdditionalEdges(t *testing.T) { graph: graph.graph, additionalEdges: additionalEdges, }, - &RestrictParams{ - FeeLimit: noFeeLimit, - }, + noRestrictions, sourceNode.PubKeyBytes, doge.PubKeyBytes, paymentAmt, ) if err != nil { @@ -1200,9 +1204,7 @@ func TestNewRoutePathTooLong(t *testing.T) { &graphParams{ graph: graph.graph, }, - &RestrictParams{ - FeeLimit: noFeeLimit, - }, + noRestrictions, sourceNode.PubKeyBytes, target, paymentAmt, ) if err != nil { @@ -1216,9 +1218,7 @@ func TestNewRoutePathTooLong(t *testing.T) { &graphParams{ graph: graph.graph, }, - &RestrictParams{ - FeeLimit: noFeeLimit, - }, + noRestrictions, sourceNode.PubKeyBytes, target, paymentAmt, ) if err == nil { @@ -1258,9 +1258,7 @@ func TestPathNotAvailable(t *testing.T) { &graphParams{ graph: graph.graph, }, - &RestrictParams{ - FeeLimit: noFeeLimit, - }, + noRestrictions, sourceNode.PubKeyBytes, unknownNode, 100, ) if !IsError(err, ErrNoPathFound) { @@ -1297,9 +1295,7 @@ func TestPathInsufficientCapacity(t *testing.T) { &graphParams{ graph: graph.graph, }, - &RestrictParams{ - FeeLimit: noFeeLimit, - }, + noRestrictions, sourceNode.PubKeyBytes, target, payAmt, ) if !IsError(err, ErrNoPathFound) { @@ -1332,9 +1328,7 @@ func TestRouteFailMinHTLC(t *testing.T) { &graphParams{ graph: graph.graph, }, - &RestrictParams{ - FeeLimit: noFeeLimit, - }, + noRestrictions, sourceNode.PubKeyBytes, target, payAmt, ) if !IsError(err, ErrNoPathFound) { @@ -1392,9 +1386,7 @@ func TestRouteFailMaxHTLC(t *testing.T) { &graphParams{ graph: graph.graph, }, - &RestrictParams{ - FeeLimit: noFeeLimit, - }, + noRestrictions, sourceNode.PubKeyBytes, target, payAmt, ) if err != nil { @@ -1416,9 +1408,7 @@ func TestRouteFailMaxHTLC(t *testing.T) { &graphParams{ graph: graph.graph, }, - &RestrictParams{ - FeeLimit: noFeeLimit, - }, + noRestrictions, sourceNode.PubKeyBytes, target, payAmt, ) if !IsError(err, ErrNoPathFound) { @@ -1453,9 +1443,7 @@ func TestRouteFailDisabledEdge(t *testing.T) { &graphParams{ graph: graph.graph, }, - &RestrictParams{ - FeeLimit: noFeeLimit, - }, + noRestrictions, sourceNode.PubKeyBytes, target, payAmt, ) if err != nil { @@ -1483,9 +1471,7 @@ func TestRouteFailDisabledEdge(t *testing.T) { &graphParams{ graph: graph.graph, }, - &RestrictParams{ - FeeLimit: noFeeLimit, - }, + noRestrictions, sourceNode.PubKeyBytes, target, payAmt, ) if err != nil { @@ -1510,9 +1496,7 @@ func TestRouteFailDisabledEdge(t *testing.T) { &graphParams{ graph: graph.graph, }, - &RestrictParams{ - FeeLimit: noFeeLimit, - }, + noRestrictions, sourceNode.PubKeyBytes, target, payAmt, ) if !IsError(err, ErrNoPathFound) { @@ -1546,9 +1530,7 @@ func TestPathSourceEdgesBandwidth(t *testing.T) { &graphParams{ graph: graph.graph, }, - &RestrictParams{ - FeeLimit: noFeeLimit, - }, + noRestrictions, sourceNode.PubKeyBytes, target, payAmt, ) if err != nil { @@ -1572,9 +1554,7 @@ func TestPathSourceEdgesBandwidth(t *testing.T) { graph: graph.graph, bandwidthHints: bandwidths, }, - &RestrictParams{ - FeeLimit: noFeeLimit, - }, + noRestrictions, sourceNode.PubKeyBytes, target, payAmt, ) if !IsError(err, ErrNoPathFound) { @@ -1592,9 +1572,7 @@ func TestPathSourceEdgesBandwidth(t *testing.T) { graph: graph.graph, bandwidthHints: bandwidths, }, - &RestrictParams{ - FeeLimit: noFeeLimit, - }, + noRestrictions, sourceNode.PubKeyBytes, target, payAmt, ) if err != nil { @@ -1625,9 +1603,7 @@ func TestPathSourceEdgesBandwidth(t *testing.T) { graph: graph.graph, bandwidthHints: bandwidths, }, - &RestrictParams{ - FeeLimit: noFeeLimit, - }, + noRestrictions, sourceNode.PubKeyBytes, target, payAmt, ) if err != nil { @@ -1916,6 +1892,7 @@ func TestRestrictOutgoingChannel(t *testing.T) { &RestrictParams{ FeeLimit: noFeeLimit, OutgoingChannelID: &outgoingChannelID, + ProbabilitySource: noProbabilitySource, }, sourceVertex, target, paymentAmt, ) @@ -1992,9 +1969,6 @@ func testCltvLimit(t *testing.T, limit uint32, expectedChannel uint64) { } sourceVertex := route.Vertex(sourceNode.PubKeyBytes) - ignoredEdges := make(map[EdgeLocator]struct{}) - ignoredVertexes := make(map[route.Vertex]struct{}) - paymentAmt := lnwire.NewMSatFromSatoshis(100) target := testGraphInstance.aliasMap["target"] @@ -2009,10 +1983,9 @@ func testCltvLimit(t *testing.T, limit uint32, expectedChannel uint64) { graph: testGraphInstance.graph, }, &RestrictParams{ - IgnoredNodes: ignoredVertexes, - IgnoredEdges: ignoredEdges, - FeeLimit: noFeeLimit, - CltvLimit: cltvLimit, + FeeLimit: noFeeLimit, + CltvLimit: cltvLimit, + ProbabilitySource: noProbabilitySource, }, sourceVertex, target, paymentAmt, ) @@ -2045,3 +2018,134 @@ func testCltvLimit(t *testing.T, limit uint32, expectedChannel uint64) { route.Hops[0].ChannelID) } } + +// TestProbabilityRouting asserts that path finding not only takes into account +// fees but also success probability. +func TestProbabilityRouting(t *testing.T) { + t.Parallel() + + testCases := []struct { + name string + p10, p11, p20 float64 + expectedChan uint64 + }{ + // Test two variations with probabilities that should multiply + // to the same total route probability. In both cases the three + // hop route should be the best route. The three hop route has a + // probability of 0.5 * 0.8 = 0.4. The fee is 5 (chan 10) + 8 + // (chan 11) = 13. Path finding distance should work out to: 13 + // + 10 (attempt penalty) / 0.4 = 38. The two hop route is 25 + + // 10 / 0.7 = 39. + { + name: "three hop 1", + p10: 0.8, p11: 0.5, p20: 0.7, + expectedChan: 10, + }, + { + name: "three hop 2", + p10: 0.5, p11: 0.8, p20: 0.7, + expectedChan: 10, + }, + + // If the probability of the two hop route is increased, its + // distance becomes 25 + 10 / 0.85 = 37. This is less than the + // three hop route with its distance 38. So with an attempt + // penalty of 10, the higher fee route is chosen because of the + // compensation for success probability. + { + name: "two hop higher cost", + p10: 0.5, p11: 0.8, p20: 0.85, + expectedChan: 20, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + testProbabilityRouting( + t, tc.p10, tc.p11, tc.p20, tc.expectedChan, + ) + }) + } +} + +func testProbabilityRouting(t *testing.T, p10, p11, p20 float64, + expectedChan uint64) { + + t.Parallel() + + // Set up a test graph with two possible paths to the target: a three + // hop path (via channels 10 and 11) and a two hop path (via channel + // 20). + testChannels := []*testChannel{ + symmetricTestChannel("roasbeef", "a1", 100000, &testChannelPolicy{}), + symmetricTestChannel("roasbeef", "b", 100000, &testChannelPolicy{}), + symmetricTestChannel("roasbeef", "c", 100000, &testChannelPolicy{}), + symmetricTestChannel("a1", "a2", 100000, &testChannelPolicy{ + Expiry: 144, + FeeBaseMsat: lnwire.NewMSatFromSatoshis(5), + MinHTLC: 1, + }, 10), + symmetricTestChannel("a2", "target", 100000, &testChannelPolicy{ + Expiry: 144, + FeeBaseMsat: lnwire.NewMSatFromSatoshis(8), + MinHTLC: 1, + }, 11), + symmetricTestChannel("b", "target", 100000, &testChannelPolicy{ + Expiry: 100, + FeeBaseMsat: lnwire.NewMSatFromSatoshis(25), + MinHTLC: 1, + }, 20), + } + + testGraphInstance, err := createTestGraphFromChannels(testChannels) + if err != nil { + t.Fatalf("unable to create graph: %v", err) + } + defer testGraphInstance.cleanUp() + + sourceNode, err := testGraphInstance.graph.SourceNode() + if err != nil { + t.Fatalf("unable to fetch source node: %v", err) + } + sourceVertex := route.Vertex(sourceNode.PubKeyBytes) + + paymentAmt := lnwire.NewMSatFromSatoshis(100) + target := testGraphInstance.aliasMap["target"] + + // Configure a probability source with the test parameters. + probabilitySource := func(node route.Vertex, edge EdgeLocator) float64 { + + switch edge.ChannelID { + case 10: + return p10 + case 11: + return p11 + case 20: + return p20 + default: + return 1 + } + } + + path, err := findPath( + &graphParams{ + graph: testGraphInstance.graph, + }, + &RestrictParams{ + FeeLimit: noFeeLimit, + ProbabilitySource: probabilitySource, + PaymentAttemptPenalty: lnwire.NewMSatFromSatoshis(10), + }, + sourceVertex, target, paymentAmt, + ) + if err != nil { + t.Fatal(err) + } + + // Assert that the route passes through the expected channel. + if path[1].ChannelID != expectedChan { + t.Fatalf("expected route to pass through channel %v, "+ + "but channel %v was selected instead", expectedChan, + path[1].ChannelID) + } +} diff --git a/routing/payment_session.go b/routing/payment_session.go index 28047f537..a75d11789 100644 --- a/routing/payment_session.go +++ b/routing/payment_session.go @@ -143,6 +143,20 @@ func (p *paymentSession) ReportEdgePolicyFailure( p.errFailedPolicyChans[*failedEdge] = struct{}{} } +func (p *paymentSession) getEdgeProbability(node route.Vertex, + edge EdgeLocator) float64 { + + if _, ok := p.pruneViewSnapshot.vertexes[node]; ok { + return 0 + } + + if _, ok := p.pruneViewSnapshot.edges[edge]; ok { + return 0 + } + + return 1 +} + // RequestRoute returns a route which is likely to be capable for successfully // routing the specified HTLC payment to the target node. Initially the first // set of paths returned from this method may encounter routing failure along @@ -200,11 +214,11 @@ func (p *paymentSession) RequestRoute(payment *LightningPayment, bandwidthHints: p.bandwidthHints, }, &RestrictParams{ - IgnoredNodes: pruneView.vertexes, - IgnoredEdges: pruneView.edges, - FeeLimit: payment.FeeLimit, - OutgoingChannelID: payment.OutgoingChannelID, - CltvLimit: cltvLimit, + ProbabilitySource: p.getEdgeProbability, + FeeLimit: payment.FeeLimit, + OutgoingChannelID: payment.OutgoingChannelID, + CltvLimit: cltvLimit, + PaymentAttemptPenalty: DefaultPaymentAttemptPenalty, }, p.mc.selfNode.PubKeyBytes, payment.Target, payment.Amount, diff --git a/routing/router_test.go b/routing/router_test.go index f90dbd8c5..70cd128bd 100644 --- a/routing/router_test.go +++ b/routing/router_test.go @@ -201,7 +201,8 @@ func TestFindRoutesWithFeeLimit(t *testing.T) { target := ctx.aliases["sophon"] paymentAmt := lnwire.NewMSatFromSatoshis(100) restrictions := &RestrictParams{ - FeeLimit: lnwire.NewMSatFromSatoshis(10), + FeeLimit: lnwire.NewMSatFromSatoshis(10), + ProbabilitySource: noProbabilitySource, } route, err := ctx.router.FindRoute( @@ -2198,9 +2199,7 @@ func TestFindPathFeeWeighting(t *testing.T) { &graphParams{ graph: ctx.graph, }, - &RestrictParams{ - FeeLimit: noFeeLimit, - }, + noRestrictions, sourceNode.PubKeyBytes, target, amt, ) if err != nil { From 3349d517aade07a621ad28d8a9de5a27917b5d50 Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Mon, 13 May 2019 17:00:35 +0200 Subject: [PATCH 06/10] routing: add min probability to path finding This commit adds a new restriction to pathfinding that allows returning only routes with a minimum success probability. --- routing/pathfind.go | 15 ++++++++++++ routing/pathfind_test.go | 49 ++++++++++++++++++++++++++++++++-------- 2 files changed, 55 insertions(+), 9 deletions(-) diff --git a/routing/pathfind.go b/routing/pathfind.go index 4115bcd40..4feb7265f 100644 --- a/routing/pathfind.go +++ b/routing/pathfind.go @@ -46,6 +46,10 @@ var ( // off potentially better routes against their probability of // succeeding. DefaultPaymentAttemptPenalty = lnwire.NewMSatFromSatoshis(100) + + // DefaultMinProbability is the default minimum probability for routes + // returned from findPath. + DefaultMinProbability = float64(0.01) ) // edgePolicyWithSource is a helper struct to keep track of the source node @@ -258,6 +262,10 @@ type RestrictParams struct { // off potentially better routes against their probability of // succeeding. PaymentAttemptPenalty lnwire.MilliSatoshi + + // MinProbability defines the minimum success probability of the + // returned route. + MinProbability float64 } // findPath attempts to find a path from the source node within the @@ -465,6 +473,13 @@ func findPath(g *graphParams, r *RestrictParams, source, target route.Vertex, // of the route must succeed. probability := toNodeDist.probability * edgeProbability + // If the probability is below the specified lower bound, we can + // abandon this direction. Adding further nodes can only lower + // the probability more. + if probability < r.MinProbability { + return + } + // By adding fromNode in the route, there will be an extra // weight composed of the fee that this node will charge and // the amount that will be locked for timeLockDelta blocks in diff --git a/routing/pathfind_test.go b/routing/pathfind_test.go index 280dd64a3..433265b83 100644 --- a/routing/pathfind_test.go +++ b/routing/pathfind_test.go @@ -2025,9 +2025,10 @@ func TestProbabilityRouting(t *testing.T) { t.Parallel() testCases := []struct { - name string - p10, p11, p20 float64 - expectedChan uint64 + name string + p10, p11, p20 float64 + minProbability float64 + expectedChan uint64 }{ // Test two variations with probabilities that should multiply // to the same total route probability. In both cases the three @@ -2039,12 +2040,14 @@ func TestProbabilityRouting(t *testing.T) { { name: "three hop 1", p10: 0.8, p11: 0.5, p20: 0.7, - expectedChan: 10, + minProbability: 0.1, + expectedChan: 10, }, { name: "three hop 2", p10: 0.5, p11: 0.8, p20: 0.7, - expectedChan: 10, + minProbability: 0.1, + expectedChan: 10, }, // If the probability of the two hop route is increased, its @@ -2055,20 +2058,42 @@ func TestProbabilityRouting(t *testing.T) { { name: "two hop higher cost", p10: 0.5, p11: 0.8, p20: 0.85, - expectedChan: 20, + minProbability: 0.1, + expectedChan: 20, + }, + + // If the same probabilities are used with a probability lower bound of + // 0.5, we expect the three hop route with probability 0.4 to be + // excluded and the two hop route to be picked. + { + name: "probability limit", + p10: 0.8, p11: 0.5, p20: 0.7, + minProbability: 0.5, + expectedChan: 20, + }, + + // With a probability limit above the probability of both routes, we + // expect no route to be returned. This expectation is signaled by using + // expected channel 0. + { + name: "probability limit no routes", + p10: 0.8, p11: 0.5, p20: 0.7, + minProbability: 0.8, + expectedChan: 0, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { testProbabilityRouting( - t, tc.p10, tc.p11, tc.p20, tc.expectedChan, + t, tc.p10, tc.p11, tc.p20, + tc.minProbability, tc.expectedChan, ) }) } } -func testProbabilityRouting(t *testing.T, p10, p11, p20 float64, +func testProbabilityRouting(t *testing.T, p10, p11, p20, minProbability float64, expectedChan uint64) { t.Parallel() @@ -2079,7 +2104,6 @@ func testProbabilityRouting(t *testing.T, p10, p11, p20 float64, testChannels := []*testChannel{ symmetricTestChannel("roasbeef", "a1", 100000, &testChannelPolicy{}), symmetricTestChannel("roasbeef", "b", 100000, &testChannelPolicy{}), - symmetricTestChannel("roasbeef", "c", 100000, &testChannelPolicy{}), symmetricTestChannel("a1", "a2", 100000, &testChannelPolicy{ Expiry: 144, FeeBaseMsat: lnwire.NewMSatFromSatoshis(5), @@ -2135,9 +2159,16 @@ func testProbabilityRouting(t *testing.T, p10, p11, p20 float64, FeeLimit: noFeeLimit, ProbabilitySource: probabilitySource, PaymentAttemptPenalty: lnwire.NewMSatFromSatoshis(10), + MinProbability: minProbability, }, sourceVertex, target, paymentAmt, ) + if expectedChan == 0 { + if err == nil || !IsError(err, ErrNoPathFound) { + t.Fatalf("expected no path found, but got %v", err) + } + return + } if err != nil { t.Fatal(err) } From 7133f37bb8ff78d3ee5c0638bce447b5efafb376 Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Tue, 19 Mar 2019 17:09:27 +0100 Subject: [PATCH 07/10] routing: global probability based mission control Previously every payment had its own local mission control state which was in effect only for that payment. In this commit most of the local state is removed and payments all tap into the global mission control probability estimator. Furthermore the decay time of pruned edges and nodes is extended, so that observations about the network can better benefit future payment processes. Last, the probability function is transformed from a binary output to a gradual curve, allowing for a better trade off between candidate routes. --- lnrpc/routerrpc/router_backend.go | 3 +- lnrpc/routerrpc/router_backend_test.go | 6 +- lntest/itest/lnd_test.go | 11 +- lntest/node.go | 6 + routing/missioncontrol.go | 294 +++++++++++++++---------- routing/missioncontrol_test.go | 67 ++++++ routing/mock_test.go | 5 +- routing/pathfind.go | 5 +- routing/pathfind_test.go | 9 +- routing/payment_session.go | 91 +++----- routing/payment_session_test.go | 3 +- routing/router.go | 58 ++--- 12 files changed, 338 insertions(+), 220 deletions(-) create mode 100644 routing/missioncontrol_test.go diff --git a/lnrpc/routerrpc/router_backend.go b/lnrpc/routerrpc/router_backend.go index dfda2109a..9e399e8fc 100644 --- a/lnrpc/routerrpc/router_backend.go +++ b/lnrpc/routerrpc/router_backend.go @@ -125,7 +125,8 @@ func (r *RouterBackend) QueryRoutes(ctx context.Context, restrictions := &routing.RestrictParams{ FeeLimit: feeLimit, ProbabilitySource: func(node route.Vertex, - edge routing.EdgeLocator) float64 { + edge routing.EdgeLocator, + amt lnwire.MilliSatoshi) float64 { if _, ok := ignoredNodes[node]; ok { return 0 diff --git a/lnrpc/routerrpc/router_backend_test.go b/lnrpc/routerrpc/router_backend_test.go index fee5fb4b3..49af5387c 100644 --- a/lnrpc/routerrpc/router_backend_test.go +++ b/lnrpc/routerrpc/router_backend_test.go @@ -81,19 +81,19 @@ func TestQueryRoutes(t *testing.T) { } if restrictions.ProbabilitySource(route.Vertex{}, - ignoredEdge, + ignoredEdge, 0, ) != 0 { t.Fatal("expecting 0% probability for ignored edge") } if restrictions.ProbabilitySource(ignoreNodeVertex, - routing.EdgeLocator{}, + routing.EdgeLocator{}, 0, ) != 0 { t.Fatal("expecting 0% probability for ignored node") } if restrictions.ProbabilitySource(route.Vertex{}, - routing.EdgeLocator{}, + routing.EdgeLocator{}, 0, ) != 1 { t.Fatal("expecting 100% probability") } diff --git a/lntest/itest/lnd_test.go b/lntest/itest/lnd_test.go index 943585369..6381c24f0 100644 --- a/lntest/itest/lnd_test.go +++ b/lntest/itest/lnd_test.go @@ -32,6 +32,7 @@ import ( "github.com/lightningnetwork/lnd" "github.com/lightningnetwork/lnd/chanbackup" "github.com/lightningnetwork/lnd/lnrpc" + "github.com/lightningnetwork/lnd/lnrpc/routerrpc" "github.com/lightningnetwork/lnd/lntest" "github.com/lightningnetwork/lnd/lnwire" "golang.org/x/net/context" @@ -8382,8 +8383,14 @@ out: // failed payment. shutdownAndAssert(net, t, carol) - // TODO(roasbeef): mission control - time.Sleep(time.Second * 5) + // Reset mission control to forget the temporary channel failure above. + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + _, err = net.Alice.RouterClient.ResetMissionControl( + ctxt, &routerrpc.ResetMissionControlRequest{}, + ) + if err != nil { + t.Fatalf("unable to reset mission control: %v", err) + } sendReq = &lnrpc.SendRequest{ PaymentRequest: carolInvoice.PaymentRequest, diff --git a/lntest/node.go b/lntest/node.go index 622a0e324..fd63afb5b 100644 --- a/lntest/node.go +++ b/lntest/node.go @@ -28,6 +28,7 @@ import ( "github.com/lightningnetwork/lnd/chanbackup" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/invoicesrpc" + "github.com/lightningnetwork/lnd/lnrpc/routerrpc" "github.com/lightningnetwork/lnd/macaroons" ) @@ -248,6 +249,10 @@ type HarnessNode struct { lnrpc.WalletUnlockerClient invoicesrpc.InvoicesClient + + // RouterClient cannot be embedded, because a name collision would occur + // on the main rpc SendPayment. + RouterClient routerrpc.RouterClient } // Assert *HarnessNode implements the lnrpc.LightningClient interface. @@ -497,6 +502,7 @@ func (hn *HarnessNode) initLightningClient(conn *grpc.ClientConn) error { // HarnessNode directly for normal rpc operations. hn.LightningClient = lnrpc.NewLightningClient(conn) hn.InvoicesClient = invoicesrpc.NewInvoicesClient(conn) + hn.RouterClient = routerrpc.NewRouterClient(conn) // Set the harness node's pubkey to what the node claims in GetInfo. err := hn.FetchNodeInfo() diff --git a/routing/missioncontrol.go b/routing/missioncontrol.go index 9cbaa9115..727858e7f 100644 --- a/routing/missioncontrol.go +++ b/routing/missioncontrol.go @@ -1,6 +1,7 @@ package routing import ( + "math" "sync" "time" @@ -13,48 +14,27 @@ import ( ) const ( - // vertexDecay is the decay period of colored vertexes added to - // MissionControl. Once vertexDecay passes after an entry has been - // added to the prune view, it is garbage collected. This value is - // larger than edgeDecay as an edge failure typical indicates an - // unbalanced channel, while a vertex failure indicates a node is not - // online and active. - vertexDecay = time.Duration(time.Minute * 5) + // defaultPenaltyHalfLife is the default half-life duration. The + // half-life duration defines after how much time a penalized node or + // channel is back at 50% probability. + defaultPenaltyHalfLife = time.Hour - // edgeDecay is the decay period of colored edges added to - // MissionControl. Once edgeDecay passed after an entry has been added, - // it is garbage collected. This value is smaller than vertexDecay as - // an edge related failure during payment sending typically indicates - // that a channel was unbalanced, a condition which may quickly change. - // - // TODO(roasbeef): instead use random delay on each? - edgeDecay = time.Duration(time.Second * 5) + // aprioriHopProbability is the assumed success probability of a hop in + // a route when no other information is available. + aprioriHopProbability = 1 ) // MissionControl contains state which summarizes the past attempts of HTLC -// routing by external callers when sending payments throughout the network. -// MissionControl remembers the outcome of these past routing attempts (success -// and failure), and is able to provide hints/guidance to future HTLC routing -// attempts. MissionControl maintains a decaying network view of the -// edges/vertexes that should be marked as "pruned" during path finding. This -// graph view acts as a shared memory during HTLC payment routing attempts. -// With each execution, if an error is encountered, based on the type of error -// and the location of the error within the route, an edge or vertex is added -// to the view. Later sending attempts will then query the view for all the -// vertexes/edges that should be ignored. Items in the view decay after a set -// period of time, allowing the view to be dynamic w.r.t network changes. +// 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 +// payment attempt success rate. +// +// Failed payment attempts are reported to mission control. These reports are +// used to track the time of the last node or channel level failure. The time +// 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 { - // failedEdges maps a short channel ID to be pruned, to the time that - // it was added to the prune view. Edges are added to this map if a - // caller reports to MissionControl a failure localized to that edge - // when sending a payment. - failedEdges map[EdgeLocator]time.Time - - // failedVertexes maps a node's public key that should be pruned, to - // the time that it was added to the prune view. Vertexes are added to - // this map if a caller reports to MissionControl a failure localized - // to that particular vertex. - failedVertexes map[route.Vertex]time.Time + history map[route.Vertex]*nodeHistory graph *channeldb.ChannelGraph @@ -62,6 +42,14 @@ type MissionControl struct { queryBandwidth func(*channeldb.ChannelEdgeInfo) lnwire.MilliSatoshi + // now is expected to return the current time. It is supplied as an + // external function to enable deterministic unit tests. + now func() time.Time + + // penaltyHalfLife defines after how much time a penalized node or + // channel is back at 50% probability. + penaltyHalfLife time.Duration + sync.Mutex // TODO(roasbeef): further counters, if vertex continually unavailable, @@ -74,83 +62,41 @@ type MissionControl struct { // PaymentSessionSource interface. var _ PaymentSessionSource = (*MissionControl)(nil) -// NewMissionControl returns a new instance of MissionControl. +// 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 + + // minPenalizeAmt is the minimum amount for which to take this failure + // into account. + minPenalizeAmt lnwire.MilliSatoshi +} + +// NewMissionControl returns a new instance of missionControl. // // TODO(roasbeef): persist memory func NewMissionControl(g *channeldb.ChannelGraph, selfNode *channeldb.LightningNode, qb func(*channeldb.ChannelEdgeInfo) lnwire.MilliSatoshi) *MissionControl { return &MissionControl{ - failedEdges: make(map[EdgeLocator]time.Time), - failedVertexes: make(map[route.Vertex]time.Time), - selfNode: selfNode, - queryBandwidth: qb, - graph: g, - } -} - -// graphPruneView is a filter of sorts that path finding routines should -// consult during the execution. Any edges or vertexes within the view should -// be ignored during path finding. The contents of the view reflect the current -// state of the wider network from the PoV of mission control compiled via HTLC -// routing attempts in the past. -type graphPruneView struct { - edges map[EdgeLocator]struct{} - - vertexes map[route.Vertex]struct{} -} - -// graphPruneView returns a new graphPruneView instance which is to be -// consulted during path finding. If a vertex/edge is found within the returned -// prune view, it is to be ignored as a goroutine has had issues routing -// through it successfully. Within this method the main view of the -// MissionControl is garbage collected as entries are detected to be "stale". -func (m *MissionControl) graphPruneView() graphPruneView { - // First, we'll grab the current time, this value will be used to - // determine if an entry is stale or not. - now := time.Now() - - m.Lock() - - // For each of the vertexes that have been added to the prune view, if - // it is now "stale", then we'll ignore it and avoid adding it to the - // view we'll return. - vertexes := make(map[route.Vertex]struct{}) - for vertex, pruneTime := range m.failedVertexes { - if now.Sub(pruneTime) >= vertexDecay { - log.Tracef("Pruning decayed failure report for vertex %v "+ - "from Mission Control", vertex) - - delete(m.failedVertexes, vertex) - continue - } - - vertexes[vertex] = struct{}{} - } - - // We'll also do the same for edges, but use the edgeDecay this time - // rather than the decay for vertexes. - edges := make(map[EdgeLocator]struct{}) - for edge, pruneTime := range m.failedEdges { - if now.Sub(pruneTime) >= edgeDecay { - log.Tracef("Pruning decayed failure report for edge %v "+ - "from Mission Control", edge) - - delete(m.failedEdges, edge) - continue - } - - edges[edge] = struct{}{} - } - - m.Unlock() - - log.Debugf("Mission Control returning prune view of %v edges, %v "+ - "vertexes", len(edges), len(vertexes)) - - return graphPruneView{ - edges: edges, - vertexes: vertexes, + history: make(map[route.Vertex]*nodeHistory), + selfNode: selfNode, + queryBandwidth: qb, + graph: g, + now: time.Now, + penaltyHalfLife: defaultPenaltyHalfLife, } } @@ -161,8 +107,6 @@ func (m *MissionControl) graphPruneView() graphPruneView { func (m *MissionControl) NewPaymentSession(routeHints [][]zpay32.HopHint, target route.Vertex) (PaymentSession, error) { - viewSnapshot := m.graphPruneView() - edges := make(map[route.Vertex][]*channeldb.ChannelEdgePolicy) // Traverse through all of the available hop hints and include them in @@ -226,10 +170,9 @@ func (m *MissionControl) NewPaymentSession(routeHints [][]zpay32.HopHint, } return &paymentSession{ - pruneViewSnapshot: viewSnapshot, additionalEdges: edges, bandwidthHints: bandwidthHints, - errFailedPolicyChans: make(map[EdgeLocator]struct{}), + errFailedPolicyChans: make(map[nodeChannel]struct{}), mc: m, pathFinder: findPath, }, nil @@ -239,8 +182,7 @@ func (m *MissionControl) NewPaymentSession(routeHints [][]zpay32.HopHint, // used for failure reporting to missioncontrol. func (m *MissionControl) NewPaymentSessionForRoute(preBuiltRoute *route.Route) PaymentSession { return &paymentSession{ - pruneViewSnapshot: m.graphPruneView(), - errFailedPolicyChans: make(map[EdgeLocator]struct{}), + errFailedPolicyChans: make(map[nodeChannel]struct{}), mc: m, preBuiltRoute: preBuiltRoute, } @@ -251,8 +193,7 @@ func (m *MissionControl) NewPaymentSessionForRoute(preBuiltRoute *route.Route) P // missioncontrol for resumed payment we don't want to make more attempts for. func (m *MissionControl) NewPaymentSessionEmpty() PaymentSession { return &paymentSession{ - pruneViewSnapshot: m.graphPruneView(), - errFailedPolicyChans: make(map[EdgeLocator]struct{}), + errFailedPolicyChans: make(map[nodeChannel]struct{}), mc: m, preBuiltRoute: &route.Route{}, preBuiltRouteTried: true, @@ -298,7 +239,122 @@ func generateBandwidthHints(sourceNode *channeldb.LightningNode, // if no payment attempts have been made. func (m *MissionControl) ResetHistory() { m.Lock() - m.failedEdges = make(map[EdgeLocator]time.Time) - m.failedVertexes = make(map[route.Vertex]time.Time) - m.Unlock() + defer m.Unlock() + + m.history = make(map[route.Vertex]*nodeHistory) + + log.Debugf("Mission control history cleared") +} + +// 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 { + + 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 aprioriHopProbability + } + + return m.getEdgeProbabilityForNode(nodeHistory, edge.ChannelID, 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 { + return aprioriHopProbability + } + + 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.penaltyHalfLife.Hours() + probability := aprioriHopProbability * (1 - math.Pow(2, exp)) + + return probability +} + +// 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(v route.Vertex) { + log.Debugf("Reporting vertex %v failure to Mission Control", v) + + now := m.now() + + m.Lock() + defer m.Unlock() + + history := m.createHistoryIfNotExists(v) + history.lastFail = &now +} + +// reportEdgeFailure reports a channel level failure. +// +// TODO(roasbeef): also add value attempted to send and capacity of channel +func (m *MissionControl) reportEdgeFailure(failedEdge edge, + minPenalizeAmt lnwire.MilliSatoshi) { + + log.Debugf("Reporting channel %v failure to Mission Control", + failedEdge.channel) + + now := m.now() + + m.Lock() + defer m.Unlock() + + history := m.createHistoryIfNotExists(failedEdge.from) + history.channelLastFail[failedEdge.channel] = &channelHistory{ + lastFail: now, + minPenalizeAmt: minPenalizeAmt, + } } diff --git a/routing/missioncontrol_test.go b/routing/missioncontrol_test.go new file mode 100644 index 000000000..39ebb5bdd --- /dev/null +++ b/routing/missioncontrol_test.go @@ -0,0 +1,67 @@ +package routing + +import ( + "testing" + "time" + + "github.com/lightningnetwork/lnd/lnwire" + "github.com/lightningnetwork/lnd/routing/route" +) + +// TestMissionControl tests mission control probability estimation. +func TestMissionControl(t *testing.T) { + now := testTime + + mc := NewMissionControl(nil, nil, nil) + mc.now = func() time.Time { return now } + mc.penaltyHalfLife = 30 * time.Minute + + testTime := time.Date(2018, time.January, 9, 14, 00, 00, 0, time.UTC) + + testNode := route.Vertex{} + testEdge := edge{ + channel: 123, + } + + expectP := func(amt lnwire.MilliSatoshi, expected float64) { + t.Helper() + + p := mc.getEdgeProbability( + testNode, EdgeLocator{ChannelID: testEdge.channel}, + amt, + ) + if p != expected { + t.Fatalf("unexpected probability %v", p) + } + } + + // Initial probability is expected to be 1. + expectP(1000, 1) + + // Expect probability to be zero after reporting the edge as failed. + mc.reportEdgeFailure(testEdge, 1000) + expectP(1000, 0) + + // As we reported with a min penalization amt, a lower amt than reported + // should be unaffected. + expectP(500, 1) + + // Edge decay started. + now = testTime.Add(30 * time.Minute) + expectP(1000, 0.5) + + // Edge fails again, this time without a min penalization amt. The edge + // should be penalized regardless of amount. + mc.reportEdgeFailure(testEdge, 0) + expectP(1000, 0) + expectP(500, 0) + + // Edge decay started. + now = testTime.Add(60 * time.Minute) + expectP(1000, 0.5) + + // A node level failure should bring probability of every channel back + // to zero. + mc.reportVertexFailure(testNode) + expectP(1000, 0) +} diff --git a/routing/mock_test.go b/routing/mock_test.go index 6d98dd6ef..c5314b830 100644 --- a/routing/mock_test.go +++ b/routing/mock_test.go @@ -112,10 +112,9 @@ func (m *mockPaymentSession) RequestRoute(payment *LightningPayment, func (m *mockPaymentSession) ReportVertexFailure(v route.Vertex) {} -func (m *mockPaymentSession) ReportEdgeFailure(e *EdgeLocator) {} +func (m *mockPaymentSession) ReportEdgeFailure(failedEdge edge, minPenalizeAmt lnwire.MilliSatoshi) {} -func (m *mockPaymentSession) ReportEdgePolicyFailure(errSource route.Vertex, failedEdge *EdgeLocator) { -} +func (m *mockPaymentSession) ReportEdgePolicyFailure(failedEdge edge) {} type mockPayer struct { sendResult chan error diff --git a/routing/pathfind.go b/routing/pathfind.go index 4feb7265f..3312a66e3 100644 --- a/routing/pathfind.go +++ b/routing/pathfind.go @@ -242,7 +242,8 @@ 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) float64 + ProbabilitySource func(route.Vertex, EdgeLocator, + lnwire.MilliSatoshi) float64 // FeeLimit is a maximum fee amount allowed to be used on the path from // the source to the target. @@ -398,7 +399,7 @@ func findPath(g *graphParams, r *RestrictParams, source, target route.Vertex, // Request the success probability for this edge. locator := newEdgeLocator(edge) edgeProbability := r.ProbabilitySource( - fromVertex, *locator, + fromVertex, *locator, amountToSend, ) log.Tracef("path finding probability: fromnode=%v, chanid=%v, "+ diff --git a/routing/pathfind_test.go b/routing/pathfind_test.go index 433265b83..8a5cf0687 100644 --- a/routing/pathfind_test.go +++ b/routing/pathfind_test.go @@ -75,7 +75,7 @@ var ( // noProbabilitySource is used in testing to return the same probability 1 for // all edges. -func noProbabilitySource(route.Vertex, EdgeLocator) float64 { +func noProbabilitySource(route.Vertex, EdgeLocator, lnwire.MilliSatoshi) float64 { return 1 } @@ -2137,7 +2137,12 @@ 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) float64 { + probabilitySource := func(node route.Vertex, edge EdgeLocator, + amt lnwire.MilliSatoshi) float64 { + + if amt == 0 { + t.Fatal("expected non-zero amount") + } switch edge.ChannelID { case 10: diff --git a/routing/payment_session.go b/routing/payment_session.go index a75d11789..6865d1b38 100644 --- a/routing/payment_session.go +++ b/routing/payment_session.go @@ -2,7 +2,6 @@ package routing import ( "fmt" - "time" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/lnwire" @@ -24,11 +23,12 @@ type PaymentSession interface { // route. ReportVertexFailure(v route.Vertex) - // ReportEdgeFailure reports to the PaymentSession that the passed - // channel failed to route the previous payment attempt. The - // PaymentSession will use this information to produce a better next - // route. - ReportEdgeFailure(e *EdgeLocator) + // ReportEdgeFailure reports to the PaymentSession that the passed edge + // failed to route the previous payment attempt. A minimum penalization + // amount is included to attenuate the failure. This is set to a + // non-zero value for channel balance failures. The PaymentSession will + // use this information to produce a better next route. + ReportEdgeFailure(failedEdge edge, minPenalizeAmt lnwire.MilliSatoshi) // ReportEdgePolicyFailure reports to the PaymentSession that we // received a failure message that relates to a channel policy. For @@ -36,7 +36,7 @@ type PaymentSession interface { // keep the edge included in the next attempted route. The // PaymentSession will use this information to produce a better next // route. - ReportEdgePolicyFailure(errSource route.Vertex, failedEdge *EdgeLocator) + ReportEdgePolicyFailure(failedEdge edge) } // paymentSession is used during an HTLC routings session to prune the local @@ -48,8 +48,6 @@ type PaymentSession interface { // loop if payment attempts take long enough. An additional set of edges can // also be provided to assist in reaching the payment's destination. type paymentSession struct { - pruneViewSnapshot graphPruneView - additionalEdges map[route.Vertex][]*channeldb.ChannelEdgePolicy bandwidthHints map[uint64]lnwire.MilliSatoshi @@ -58,7 +56,7 @@ type paymentSession struct { // source of policy related routing failures during this payment attempt. // We'll use this map to prune out channels when the first error may not // require pruning, but any subsequent ones do. - errFailedPolicyChans map[EdgeLocator]struct{} + errFailedPolicyChans map[nodeChannel]struct{} mc *MissionControl @@ -80,17 +78,7 @@ var _ PaymentSession = (*paymentSession)(nil) // // NOTE: Part of the PaymentSession interface. func (p *paymentSession) ReportVertexFailure(v route.Vertex) { - log.Debugf("Reporting vertex %v failure to Mission Control", v) - - // First, we'll add the failed vertex to our local prune view snapshot. - p.pruneViewSnapshot.vertexes[v] = struct{}{} - - // With the vertex added, we'll now report back to the global prune - // view, with this new piece of information so it can be utilized for - // new payment sessions. - p.mc.Lock() - p.mc.failedVertexes[v] = time.Now() - p.mc.Unlock() + p.mc.reportVertexFailure(v) } // ReportEdgeFailure adds a channel to the graph prune view. The time the @@ -102,18 +90,10 @@ func (p *paymentSession) ReportVertexFailure(v route.Vertex) { // TODO(roasbeef): also add value attempted to send and capacity of channel // // NOTE: Part of the PaymentSession interface. -func (p *paymentSession) ReportEdgeFailure(e *EdgeLocator) { - log.Debugf("Reporting edge %v failure to Mission Control", e) +func (p *paymentSession) ReportEdgeFailure(failedEdge edge, + minPenalizeAmt lnwire.MilliSatoshi) { - // First, we'll add the failed edge to our local prune view snapshot. - p.pruneViewSnapshot.edges[*e] = struct{}{} - - // With the edge added, we'll now report back to the global prune view, - // with this new piece of information so it can be utilized for new - // payment sessions. - p.mc.Lock() - p.mc.failedEdges[*e] = time.Now() - p.mc.Unlock() + p.mc.reportEdgeFailure(failedEdge, minPenalizeAmt) } // ReportEdgePolicyFailure handles a failure message that relates to a @@ -124,37 +104,28 @@ func (p *paymentSession) ReportEdgeFailure(e *EdgeLocator) { // new channel updates. // // NOTE: Part of the PaymentSession interface. -func (p *paymentSession) ReportEdgePolicyFailure( - errSource route.Vertex, failedEdge *EdgeLocator) { +// +// TODO(joostjager): Move this logic into global mission control. +func (p *paymentSession) ReportEdgePolicyFailure(failedEdge edge) { + key := nodeChannel{ + node: failedEdge.from, + channel: failedEdge.channel, + } // Check to see if we've already reported a policy related failure for // this channel. If so, then we'll prune out the vertex. - _, ok := p.errFailedPolicyChans[*failedEdge] + _, ok := p.errFailedPolicyChans[key] if ok { // TODO(joostjager): is this aggressive pruning still necessary? // Just pruning edges may also work unless there is a huge // number of failing channels from that node? - p.ReportVertexFailure(errSource) + p.ReportVertexFailure(key.node) return } // Finally, we'll record a policy failure from this node and move on. - p.errFailedPolicyChans[*failedEdge] = struct{}{} -} - -func (p *paymentSession) getEdgeProbability(node route.Vertex, - edge EdgeLocator) float64 { - - if _, ok := p.pruneViewSnapshot.vertexes[node]; ok { - return 0 - } - - if _, ok := p.pruneViewSnapshot.edges[edge]; ok { - return 0 - } - - return 1 + p.errFailedPolicyChans[key] = struct{}{} } // RequestRoute returns a route which is likely to be capable for successfully @@ -183,15 +154,6 @@ func (p *paymentSession) RequestRoute(payment *LightningPayment, return nil, fmt.Errorf("pre-built route already tried") } - // Otherwise we actually need to perform path finding, so we'll obtain - // our current prune view snapshot. This view will only ever grow - // during the duration of this payment session, never shrinking. - pruneView := p.pruneViewSnapshot - - log.Debugf("Mission Control session using prune view of %v "+ - "edges, %v vertexes", len(pruneView.edges), - len(pruneView.vertexes)) - // If a route cltv limit was specified, we need to subtract the final // delta before passing it into path finding. The optimal path is // independent of the final cltv delta and the path finding algorithm is @@ -214,11 +176,12 @@ func (p *paymentSession) RequestRoute(payment *LightningPayment, bandwidthHints: p.bandwidthHints, }, &RestrictParams{ - ProbabilitySource: p.getEdgeProbability, + ProbabilitySource: p.mc.getEdgeProbability, FeeLimit: payment.FeeLimit, OutgoingChannelID: payment.OutgoingChannelID, CltvLimit: cltvLimit, PaymentAttemptPenalty: DefaultPaymentAttemptPenalty, + MinProbability: DefaultMinProbability, }, p.mc.selfNode.PubKeyBytes, payment.Target, payment.Amount, @@ -241,3 +204,9 @@ func (p *paymentSession) RequestRoute(payment *LightningPayment, return route, err } + +// nodeChannel is a combination of the node pubkey and one of its channels. +type nodeChannel struct { + node route.Vertex + channel uint64 +} diff --git a/routing/payment_session_test.go b/routing/payment_session_test.go index f730bc576..3ac01f2a9 100644 --- a/routing/payment_session_test.go +++ b/routing/payment_session_test.go @@ -36,8 +36,7 @@ func TestRequestRoute(t *testing.T) { mc: &MissionControl{ selfNode: &channeldb.LightningNode{}, }, - pruneViewSnapshot: graphPruneView{}, - pathFinder: findPath, + pathFinder: findPath, } cltvLimit := uint32(30) diff --git a/routing/router.go b/routing/router.go index 1e4ec7063..a78791ea2 100644 --- a/routing/router.go +++ b/routing/router.go @@ -319,6 +319,13 @@ func (e *EdgeLocator) String() string { return fmt.Sprintf("%v:%v", e.ChannelID, e.Direction) } +// edge is a combination of a channel and the node pubkeys of both of its +// endpoints. +type edge struct { + from, to route.Vertex + channel uint64 +} + // ChannelRouter is the layer 3 router within the Lightning stack. Below the // ChannelRouter is the HtlcSwitch, and below that is the Bitcoin blockchain // itself. The primary role of the ChannelRouter is to respond to queries for @@ -1769,7 +1776,9 @@ func (r *ChannelRouter) processSendError(paySession PaymentSession, // Always determine chan id ourselves, because a channel // update with id may not be available. - failedEdge, err := getFailedEdge(rt, route.Vertex(errVertex)) + failedEdge, failedAmt, err := getFailedEdge( + rt, route.Vertex(errVertex), + ) if err != nil { return true } @@ -1801,13 +1810,11 @@ func (r *ChannelRouter) processSendError(paySession PaymentSession, // update to fail? if !updateOk { paySession.ReportEdgeFailure( - failedEdge, + failedEdge, 0, ) } - paySession.ReportEdgePolicyFailure( - route.NewVertex(errSource), failedEdge, - ) + paySession.ReportEdgePolicyFailure(failedEdge) } switch onionErr := fErr.FailureMessage.(type) { @@ -1898,7 +1905,7 @@ func (r *ChannelRouter) processSendError(paySession PaymentSession, // the update and continue. case *lnwire.FailChannelDisabled: r.applyChannelUpdate(&onionErr.Update, errSource) - paySession.ReportEdgeFailure(failedEdge) + paySession.ReportEdgeFailure(failedEdge, 0) return false // It's likely that the outgoing channel didn't have @@ -1906,7 +1913,7 @@ func (r *ChannelRouter) processSendError(paySession PaymentSession, // now, and continue onwards with our path finding. case *lnwire.FailTemporaryChannelFailure: r.applyChannelUpdate(onionErr.Update, errSource) - paySession.ReportEdgeFailure(failedEdge) + paySession.ReportEdgeFailure(failedEdge, failedAmt) return false // If the send fail due to a node not having the @@ -1931,7 +1938,7 @@ func (r *ChannelRouter) processSendError(paySession PaymentSession, // returning errors in order to attempt to black list // another node. case *lnwire.FailUnknownNextPeer: - paySession.ReportEdgeFailure(failedEdge) + paySession.ReportEdgeFailure(failedEdge, 0) return false // If the node wasn't able to forward for which ever @@ -1962,14 +1969,12 @@ func (r *ChannelRouter) processSendError(paySession PaymentSession, // we'll prune the channel in both directions and // continue with the rest of the routes. case *lnwire.FailPermanentChannelFailure: - paySession.ReportEdgeFailure(&EdgeLocator{ - ChannelID: failedEdge.ChannelID, - Direction: 0, - }) - paySession.ReportEdgeFailure(&EdgeLocator{ - ChannelID: failedEdge.ChannelID, - Direction: 1, - }) + paySession.ReportEdgeFailure(failedEdge, 0) + paySession.ReportEdgeFailure(edge{ + from: failedEdge.to, + to: failedEdge.from, + channel: failedEdge.channel, + }, 0) return false default: @@ -1979,12 +1984,14 @@ func (r *ChannelRouter) processSendError(paySession PaymentSession, // getFailedEdge tries to locate the failing channel given a route and the // pubkey of the node that sent the error. It will assume that the error is -// associated with the outgoing channel of the error node. -func getFailedEdge(route *route.Route, errSource route.Vertex) ( - *EdgeLocator, error) { +// associated with the outgoing channel of the error node. As a second result, +// it returns the amount sent over the edge. +func getFailedEdge(route *route.Route, errSource route.Vertex) (edge, + lnwire.MilliSatoshi, error) { hopCount := len(route.Hops) fromNode := route.SourcePubKey + amt := route.TotalAmount for i, hop := range route.Hops { toNode := hop.PubKeyBytes @@ -2003,17 +2010,18 @@ func getFailedEdge(route *route.Route, errSource route.Vertex) ( // If the errSource is the final hop, we assume that the failing // channel is the incoming channel. if errSource == fromNode || finalHopFailing { - return newEdgeLocatorByPubkeys( - hop.ChannelID, - &fromNode, - &toNode, - ), nil + return edge{ + from: fromNode, + to: toNode, + channel: hop.ChannelID, + }, amt, nil } fromNode = toNode + amt = hop.AmtToForward } - return nil, fmt.Errorf("cannot find error source node in route") + return edge{}, 0, fmt.Errorf("cannot find error source node in route") } // applyChannelUpdate validates a channel update and if valid, applies it to the From f7982f0f4c4bf6766b721f5cf07b1e3e3c91599e Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Fri, 10 May 2019 10:38:31 +0200 Subject: [PATCH 08/10] routing+routerrpc: expose mission control state over rpc This commit exposes mission control state for rpc for development purposes. --- lnrpc/routerrpc/router.pb.go | 423 +++++++++++++++++++++++++------ lnrpc/routerrpc/router.proto | 43 ++++ lnrpc/routerrpc/router_server.go | 47 ++++ routing/missioncontrol.go | 94 +++++++ routing/missioncontrol_test.go | 10 + 5 files changed, 536 insertions(+), 81 deletions(-) diff --git a/lnrpc/routerrpc/router.pb.go b/lnrpc/routerrpc/router.pb.go index 0476353b0..63c436446 100644 --- a/lnrpc/routerrpc/router.pb.go +++ b/lnrpc/routerrpc/router.pb.go @@ -799,6 +799,213 @@ func (m *ResetMissionControlResponse) XXX_DiscardUnknown() { var xxx_messageInfo_ResetMissionControlResponse proto.InternalMessageInfo +type QueryMissionControlRequest struct { + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *QueryMissionControlRequest) Reset() { *m = QueryMissionControlRequest{} } +func (m *QueryMissionControlRequest) String() string { return proto.CompactTextString(m) } +func (*QueryMissionControlRequest) ProtoMessage() {} +func (*QueryMissionControlRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_7a0613f69d37b0a5, []int{10} +} + +func (m *QueryMissionControlRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_QueryMissionControlRequest.Unmarshal(m, b) +} +func (m *QueryMissionControlRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_QueryMissionControlRequest.Marshal(b, m, deterministic) +} +func (m *QueryMissionControlRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryMissionControlRequest.Merge(m, src) +} +func (m *QueryMissionControlRequest) XXX_Size() int { + return xxx_messageInfo_QueryMissionControlRequest.Size(m) +} +func (m *QueryMissionControlRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryMissionControlRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryMissionControlRequest proto.InternalMessageInfo + +/// QueryMissionControlResponse contains mission control state per node. +type QueryMissionControlResponse struct { + Nodes []*NodeHistory `protobuf:"bytes,1,rep,name=nodes,proto3" json:"nodes,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *QueryMissionControlResponse) Reset() { *m = QueryMissionControlResponse{} } +func (m *QueryMissionControlResponse) String() string { return proto.CompactTextString(m) } +func (*QueryMissionControlResponse) ProtoMessage() {} +func (*QueryMissionControlResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_7a0613f69d37b0a5, []int{11} +} + +func (m *QueryMissionControlResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_QueryMissionControlResponse.Unmarshal(m, b) +} +func (m *QueryMissionControlResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_QueryMissionControlResponse.Marshal(b, m, deterministic) +} +func (m *QueryMissionControlResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryMissionControlResponse.Merge(m, src) +} +func (m *QueryMissionControlResponse) XXX_Size() int { + return xxx_messageInfo_QueryMissionControlResponse.Size(m) +} +func (m *QueryMissionControlResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryMissionControlResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryMissionControlResponse proto.InternalMessageInfo + +func (m *QueryMissionControlResponse) GetNodes() []*NodeHistory { + if m != nil { + return m.Nodes + } + 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:"-"` +} + +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{12} +} + +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) GetOtherChanSuccessProb() float32 { + if m != nil { + return m.OtherChanSuccessProb + } + 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"` + /// Time stamp of last failure. + LastFailTime int64 `protobuf:"varint,2,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"` + 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) { + return fileDescriptor_7a0613f69d37b0a5, []int{13} +} + +func (m *ChannelHistory) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ChannelHistory.Unmarshal(m, b) +} +func (m *ChannelHistory) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ChannelHistory.Marshal(b, m, deterministic) +} +func (m *ChannelHistory) XXX_Merge(src proto.Message) { + xxx_messageInfo_ChannelHistory.Merge(m, src) +} +func (m *ChannelHistory) XXX_Size() int { + return xxx_messageInfo_ChannelHistory.Size(m) +} +func (m *ChannelHistory) XXX_DiscardUnknown() { + xxx_messageInfo_ChannelHistory.DiscardUnknown(m) +} + +var xxx_messageInfo_ChannelHistory proto.InternalMessageInfo + +func (m *ChannelHistory) GetChannelId() uint64 { + if m != nil { + return m.ChannelId + } + return 0 +} + +func (m *ChannelHistory) GetLastFailTime() int64 { + if m != nil { + return m.LastFailTime + } + return 0 +} + +func (m *ChannelHistory) GetMinPenalizeAmtSat() int64 { + if m != nil { + return m.MinPenalizeAmtSat + } + return 0 +} + +func (m *ChannelHistory) GetSuccessProb() float32 { + if m != nil { + return m.SuccessProb + } + return 0 +} + func init() { proto.RegisterEnum("routerrpc.Failure_FailureCode", Failure_FailureCode_name, Failure_FailureCode_value) proto.RegisterType((*PaymentRequest)(nil), "routerrpc.PaymentRequest") @@ -811,92 +1018,107 @@ func init() { proto.RegisterType((*ChannelUpdate)(nil), "routerrpc.ChannelUpdate") proto.RegisterType((*ResetMissionControlRequest)(nil), "routerrpc.ResetMissionControlRequest") 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((*ChannelHistory)(nil), "routerrpc.ChannelHistory") } func init() { proto.RegisterFile("routerrpc/router.proto", fileDescriptor_7a0613f69d37b0a5) } var fileDescriptor_7a0613f69d37b0a5 = []byte{ - // 1271 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x56, 0xdd, 0x72, 0x1a, 0x37, - 0x14, 0x2e, 0xf1, 0x0f, 0x70, 0x00, 0xb3, 0x96, 0xed, 0x04, 0xe3, 0x38, 0x71, 0xb6, 0x6d, 0xea, - 0xc9, 0x74, 0xec, 0x29, 0x9d, 0xe4, 0xb2, 0x1d, 0x02, 0xa2, 0xde, 0x09, 0xec, 0x52, 0x01, 0x4e, - 0xdc, 0x5e, 0x68, 0x64, 0x56, 0x86, 0xad, 0xd9, 0x1f, 0xef, 0x8a, 0x36, 0x7e, 0x81, 0xbe, 0x4e, - 0x9f, 0xa2, 0xd7, 0xbd, 0xee, 0xdb, 0x74, 0x24, 0xed, 0x02, 0x76, 0x9c, 0xe9, 0x15, 0xe8, 0xfb, - 0x3e, 0x9d, 0xa3, 0x73, 0x74, 0xce, 0xd1, 0xc2, 0xe3, 0x38, 0x9c, 0x0b, 0x1e, 0xc7, 0xd1, 0xf8, - 0x54, 0xff, 0x3b, 0x89, 0xe2, 0x50, 0x84, 0xa8, 0xb8, 0xc0, 0xeb, 0xc5, 0x38, 0x1a, 0x6b, 0xd4, - 0xfc, 0x3b, 0x07, 0x5b, 0x7d, 0x76, 0xeb, 0xf3, 0x40, 0x10, 0x7e, 0x33, 0xe7, 0x89, 0x40, 0x4f, - 0x20, 0x1f, 0xb1, 0x5b, 0x1a, 0xf3, 0x9b, 0x5a, 0xee, 0x28, 0x77, 0x5c, 0x24, 0x9b, 0x11, 0xbb, - 0x25, 0xfc, 0x06, 0x99, 0x50, 0xb9, 0xe2, 0x9c, 0xce, 0x3c, 0xdf, 0x13, 0x34, 0x61, 0xa2, 0xf6, - 0xe8, 0x28, 0x77, 0xbc, 0x46, 0x4a, 0x57, 0x9c, 0x77, 0x25, 0x36, 0x60, 0x02, 0x1d, 0x02, 0x8c, - 0x67, 0xe2, 0x77, 0x2d, 0xaa, 0xad, 0x1d, 0xe5, 0x8e, 0x37, 0x48, 0x51, 0x22, 0x4a, 0x81, 0xbe, - 0x81, 0xaa, 0xf0, 0x7c, 0x1e, 0xce, 0x05, 0x4d, 0xf8, 0x38, 0x0c, 0xdc, 0xa4, 0xb6, 0xae, 0x34, - 0x5b, 0x29, 0x3c, 0xd0, 0x28, 0x3a, 0x81, 0x9d, 0x70, 0x2e, 0x26, 0xa1, 0x17, 0x4c, 0xe8, 0x78, - 0xca, 0x82, 0x80, 0xcf, 0xa8, 0xe7, 0xd6, 0x36, 0x94, 0xc7, 0xed, 0x8c, 0x6a, 0x69, 0xc6, 0x72, - 0xcd, 0xdf, 0xa0, 0xba, 0x08, 0x23, 0x89, 0xc2, 0x20, 0xe1, 0x68, 0x1f, 0x0a, 0x32, 0x8e, 0x29, - 0x4b, 0xa6, 0x2a, 0x90, 0x32, 0x91, 0x71, 0x9d, 0xb1, 0x64, 0x8a, 0x0e, 0xa0, 0x18, 0xc5, 0x9c, - 0x7a, 0x3e, 0x9b, 0x70, 0x15, 0x45, 0x99, 0x14, 0xa2, 0x98, 0x5b, 0x72, 0x8d, 0x9e, 0x43, 0x29, - 0xd2, 0xa6, 0x28, 0x8f, 0x63, 0x15, 0x43, 0x91, 0x40, 0x0a, 0xe1, 0x38, 0x36, 0x7f, 0x80, 0x2a, - 0x91, 0xb9, 0xec, 0x70, 0x9e, 0xe5, 0x0c, 0xc1, 0xba, 0xcb, 0x13, 0x91, 0xfa, 0x51, 0xff, 0x65, - 0x1e, 0x99, 0xbf, 0x9a, 0xa8, 0x4d, 0xe6, 0xcb, 0x1c, 0x99, 0x2e, 0x18, 0xcb, 0xfd, 0xe9, 0x61, - 0x8f, 0xc1, 0x90, 0xf7, 0x23, 0xc3, 0x95, 0x39, 0xf6, 0xe5, 0xae, 0x9c, 0xda, 0xb5, 0x95, 0xe2, - 0x1d, 0xce, 0x7b, 0x09, 0x13, 0xe8, 0xa5, 0x4e, 0x21, 0x9d, 0x85, 0xe3, 0x6b, 0xea, 0xf2, 0x19, - 0xbb, 0x4d, 0xcd, 0x57, 0x24, 0xdc, 0x0d, 0xc7, 0xd7, 0x6d, 0x09, 0x9a, 0xbf, 0x02, 0x1a, 0xf0, - 0xc0, 0x1d, 0x86, 0xca, 0x57, 0x76, 0xd0, 0x17, 0x50, 0xce, 0x82, 0x5b, 0x49, 0x4c, 0x16, 0xb0, - 0x4a, 0x8e, 0x09, 0x1b, 0xaa, 0x54, 0x94, 0xd9, 0x52, 0xa3, 0x7c, 0x32, 0x0b, 0x64, 0xbd, 0x68, - 0x33, 0x9a, 0x32, 0x29, 0xec, 0xdc, 0x31, 0x9e, 0x46, 0x51, 0x07, 0x99, 0x46, 0x9d, 0xd6, 0xdc, - 0x22, 0xad, 0x6a, 0x8d, 0xbe, 0x85, 0xfc, 0x15, 0xf3, 0x66, 0xf3, 0x38, 0x33, 0x8c, 0x4e, 0x16, - 0x15, 0x79, 0xd2, 0xd1, 0x0c, 0xc9, 0x24, 0xe6, 0x9f, 0x79, 0xc8, 0xa7, 0x20, 0x6a, 0xc0, 0xfa, - 0x38, 0x74, 0xb5, 0xc5, 0xad, 0xc6, 0xb3, 0x4f, 0xb7, 0x65, 0xbf, 0xad, 0xd0, 0xe5, 0x44, 0x69, - 0x51, 0x03, 0xf6, 0x52, 0x53, 0x34, 0x09, 0xe7, 0xf1, 0x98, 0xd3, 0x68, 0x7e, 0x79, 0xcd, 0x6f, - 0xd3, 0xdb, 0xde, 0x49, 0xc9, 0x81, 0xe2, 0xfa, 0x8a, 0x42, 0x3f, 0xc2, 0x56, 0x56, 0x6a, 0xf3, - 0xc8, 0x65, 0x82, 0xab, 0xbb, 0x2f, 0x35, 0x6a, 0x2b, 0x1e, 0xd3, 0x8a, 0x1b, 0x29, 0x9e, 0x54, - 0xc6, 0xab, 0x4b, 0x59, 0x56, 0x53, 0x31, 0x1b, 0xeb, 0xdb, 0x93, 0x75, 0xbd, 0x4e, 0x0a, 0x12, - 0x50, 0xf7, 0x66, 0x42, 0x25, 0x0c, 0xbc, 0x30, 0xa0, 0xc9, 0x94, 0xd1, 0xc6, 0xeb, 0x37, 0xaa, - 0x96, 0xcb, 0xa4, 0xa4, 0xc0, 0xc1, 0x94, 0x35, 0x5e, 0xbf, 0x91, 0xa5, 0xa7, 0xba, 0x87, 0x7f, - 0x8c, 0xbc, 0xf8, 0xb6, 0xb6, 0x79, 0x94, 0x3b, 0xae, 0x10, 0xd5, 0x50, 0x58, 0x21, 0x68, 0x17, - 0x36, 0xae, 0x66, 0x6c, 0x92, 0xd4, 0xf2, 0x8a, 0xd2, 0x0b, 0xf3, 0xdf, 0x75, 0x28, 0xad, 0xa4, - 0x00, 0x95, 0xa1, 0x40, 0xf0, 0x00, 0x93, 0x73, 0xdc, 0x36, 0xbe, 0x40, 0x35, 0xd8, 0x1d, 0xd9, - 0xef, 0x6c, 0xe7, 0xbd, 0x4d, 0xfb, 0xcd, 0x8b, 0x1e, 0xb6, 0x87, 0xf4, 0xac, 0x39, 0x38, 0x33, - 0x72, 0xe8, 0x29, 0xd4, 0x2c, 0xbb, 0xe5, 0x10, 0x82, 0x5b, 0xc3, 0x05, 0xd7, 0xec, 0x39, 0x23, - 0x7b, 0x68, 0x3c, 0x42, 0xcf, 0xe1, 0xa0, 0x63, 0xd9, 0xcd, 0x2e, 0x5d, 0x6a, 0x5a, 0xdd, 0xe1, - 0x39, 0xc5, 0x1f, 0xfa, 0x16, 0xb9, 0x30, 0xd6, 0x1e, 0x12, 0x9c, 0x0d, 0xbb, 0xad, 0xcc, 0xc2, - 0x3a, 0xda, 0x87, 0x3d, 0x2d, 0xd0, 0x5b, 0xe8, 0xd0, 0x71, 0xe8, 0xc0, 0x71, 0x6c, 0x63, 0x03, - 0x6d, 0x43, 0xc5, 0xb2, 0xcf, 0x9b, 0x5d, 0xab, 0x4d, 0x09, 0x6e, 0x76, 0x7b, 0xc6, 0x26, 0xda, - 0x81, 0xea, 0x7d, 0x5d, 0x5e, 0x9a, 0xc8, 0x74, 0x8e, 0x6d, 0x39, 0x36, 0x3d, 0xc7, 0x64, 0x60, - 0x39, 0xb6, 0x51, 0x40, 0x8f, 0x01, 0xdd, 0xa5, 0xce, 0x7a, 0xcd, 0x96, 0x51, 0x44, 0x7b, 0xb0, - 0x7d, 0x17, 0x7f, 0x87, 0x2f, 0x0c, 0x90, 0x69, 0xd0, 0x07, 0xa3, 0x6f, 0x71, 0xd7, 0x79, 0x4f, - 0x7b, 0x96, 0x6d, 0xf5, 0x46, 0x3d, 0xa3, 0x84, 0x76, 0xc1, 0xe8, 0x60, 0x4c, 0x2d, 0x7b, 0x30, - 0xea, 0x74, 0xac, 0x96, 0x85, 0xed, 0xa1, 0x51, 0xd6, 0x9e, 0x1f, 0x0a, 0xbc, 0x22, 0x37, 0xb4, - 0xce, 0x9a, 0xb6, 0x8d, 0xbb, 0xb4, 0x6d, 0x0d, 0x9a, 0x6f, 0xbb, 0xb8, 0x6d, 0x6c, 0xa1, 0x43, - 0xd8, 0x1f, 0xe2, 0x5e, 0xdf, 0x21, 0x4d, 0x72, 0x41, 0x33, 0xbe, 0xd3, 0xb4, 0xba, 0x23, 0x82, - 0x8d, 0x2a, 0x7a, 0x01, 0x87, 0x04, 0xff, 0x3c, 0xb2, 0x08, 0x6e, 0x53, 0xdb, 0x69, 0x63, 0xda, - 0xc1, 0xcd, 0xe1, 0x88, 0x60, 0xda, 0xb3, 0x06, 0x03, 0xcb, 0xfe, 0xc9, 0x30, 0xd0, 0x57, 0x70, - 0xb4, 0x90, 0x2c, 0x0c, 0xdc, 0x53, 0x6d, 0xcb, 0xf8, 0xb2, 0xfb, 0xb4, 0xf1, 0x87, 0x21, 0xed, - 0x63, 0x4c, 0x0c, 0x84, 0xea, 0xf0, 0x78, 0xe9, 0x5e, 0x3b, 0x48, 0x7d, 0xef, 0x48, 0xae, 0x8f, - 0x49, 0xaf, 0x69, 0xcb, 0x0b, 0xbe, 0xc3, 0xed, 0xca, 0x63, 0x2f, 0xb9, 0xfb, 0xc7, 0xde, 0x33, - 0xff, 0x5a, 0x83, 0xca, 0x9d, 0xa2, 0x47, 0x4f, 0xa1, 0x98, 0x78, 0x93, 0x80, 0x09, 0xd9, 0xca, - 0xba, 0xcb, 0x97, 0x80, 0x7a, 0x00, 0xa6, 0xcc, 0x0b, 0xf4, 0x78, 0xd1, 0xdd, 0x56, 0x54, 0x88, - 0x1a, 0x2e, 0x4f, 0x20, 0x2f, 0x7b, 0x46, 0xce, 0xf2, 0x35, 0xd5, 0x20, 0x9b, 0x72, 0x69, 0xb9, - 0xd2, 0xaa, 0x9c, 0x5f, 0x89, 0x60, 0x7e, 0xa4, 0x7a, 0xa7, 0x42, 0x96, 0x00, 0xfa, 0x12, 0x2a, - 0x3e, 0x4f, 0x12, 0x36, 0xe1, 0x54, 0xd7, 0x3f, 0x28, 0x45, 0x39, 0x05, 0x3b, 0x12, 0x93, 0xa2, - 0xac, 0x7f, 0xb5, 0x68, 0x43, 0x8b, 0x52, 0x50, 0x8b, 0xee, 0x8f, 0x4f, 0xc1, 0xd2, 0x36, 0x5b, - 0x1d, 0x9f, 0x82, 0xa1, 0x57, 0xb0, 0xad, 0x7b, 0xd9, 0x0b, 0x3c, 0x7f, 0xee, 0xeb, 0x9e, 0xce, - 0xab, 0x23, 0x57, 0x55, 0x4f, 0x6b, 0x5c, 0xb5, 0xf6, 0x3e, 0x14, 0x2e, 0x59, 0xc2, 0xe5, 0xe4, - 0xae, 0x15, 0x94, 0xb1, 0xbc, 0x5c, 0x77, 0xb8, 0x7a, 0x84, 0xe4, 0x3c, 0x8f, 0xe5, 0x34, 0x29, - 0x6a, 0xea, 0x8a, 0x73, 0x22, 0xf3, 0xb8, 0xf0, 0xc0, 0x3e, 0x2e, 0x3d, 0x94, 0x56, 0x3c, 0x68, - 0x5c, 0x79, 0x78, 0x05, 0xdb, 0xfc, 0xa3, 0x88, 0x19, 0x0d, 0x23, 0x76, 0x33, 0xe7, 0xd4, 0x65, - 0x82, 0xd5, 0xca, 0x2a, 0xb9, 0x55, 0x45, 0x38, 0x0a, 0x6f, 0x33, 0xc1, 0xcc, 0xa7, 0x50, 0x27, - 0x3c, 0xe1, 0xa2, 0xe7, 0x25, 0x89, 0x17, 0x06, 0xad, 0x30, 0x10, 0x71, 0x38, 0x4b, 0x1f, 0x00, - 0xf3, 0x10, 0x0e, 0x1e, 0x64, 0xf5, 0x04, 0x6f, 0xfc, 0xf3, 0x08, 0x36, 0xd5, 0x4c, 0x8f, 0x51, - 0x1b, 0x4a, 0x72, 0xc6, 0xa7, 0xcf, 0x2a, 0xda, 0x5f, 0x99, 0x82, 0x77, 0xbf, 0x18, 0xea, 0xf5, - 0x87, 0xa8, 0xf4, 0x49, 0x78, 0x07, 0x06, 0x4e, 0x84, 0xe7, 0xcb, 0x71, 0x99, 0x3e, 0x7a, 0x68, - 0x55, 0x7f, 0xef, 0x25, 0xad, 0x1f, 0x3c, 0xc8, 0xa5, 0xc6, 0xba, 0xfa, 0x48, 0xe9, 0xb3, 0x83, - 0x0e, 0x57, 0xb4, 0x9f, 0xbe, 0x75, 0xf5, 0x67, 0x9f, 0xa3, 0x53, 0x6b, 0x2e, 0xec, 0x3c, 0x90, - 0x0a, 0xf4, 0xf5, 0xea, 0x09, 0x3e, 0x9b, 0xc8, 0xfa, 0xcb, 0xff, 0x93, 0x69, 0x2f, 0x6f, 0xbf, - 0xfb, 0xe5, 0x74, 0xe2, 0x89, 0xe9, 0xfc, 0xf2, 0x64, 0x1c, 0xfa, 0xa7, 0x33, 0x6f, 0x32, 0x15, - 0x81, 0x17, 0x4c, 0x02, 0x2e, 0xfe, 0x08, 0xe3, 0xeb, 0xd3, 0x59, 0xe0, 0x9e, 0xaa, 0x07, 0xf6, - 0x74, 0x61, 0xee, 0x72, 0x53, 0x7d, 0x9b, 0x7d, 0xff, 0x5f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x01, - 0xde, 0xa4, 0x83, 0xcb, 0x09, 0x00, 0x00, + // 1456 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x56, 0xdb, 0x72, 0x1a, 0x47, + 0x13, 0xfe, 0x31, 0x12, 0x87, 0xe6, 0xa0, 0xd5, 0xe8, 0x60, 0x84, 0x2c, 0x5b, 0xde, 0xff, 0xff, + 0x1d, 0x95, 0xcb, 0x25, 0x55, 0x48, 0xd9, 0x95, 0xab, 0xa4, 0x30, 0x2c, 0xd1, 0x96, 0x60, 0xc1, + 0x03, 0xc8, 0x56, 0x72, 0x31, 0x35, 0x62, 0x47, 0xb0, 0x11, 0xec, 0xae, 0x76, 0x87, 0xc4, 0xe4, + 0x01, 0xf2, 0x3a, 0xc9, 0x4d, 0x6e, 0x73, 0x97, 0x87, 0xc8, 0xdb, 0xa4, 0x66, 0x66, 0x97, 0x83, + 0x8c, 0x92, 0x5c, 0xc1, 0x7e, 0xdf, 0x37, 0xdd, 0xd3, 0x3d, 0xdd, 0x3d, 0x03, 0xfb, 0x81, 0x37, + 0xe5, 0x2c, 0x08, 0xfc, 0xc1, 0x99, 0xfa, 0x77, 0xea, 0x07, 0x1e, 0xf7, 0x50, 0x76, 0x8e, 0x97, + 0xb3, 0x81, 0x3f, 0x50, 0xa8, 0xfe, 0x47, 0x02, 0x8a, 0x1d, 0x3a, 0x9b, 0x30, 0x97, 0x63, 0x76, + 0x37, 0x65, 0x21, 0x47, 0x8f, 0x21, 0xed, 0xd3, 0x19, 0x09, 0xd8, 0x5d, 0x29, 0x71, 0x9c, 0x38, + 0xc9, 0xe2, 0x94, 0x4f, 0x67, 0x98, 0xdd, 0x21, 0x1d, 0x0a, 0x37, 0x8c, 0x91, 0xb1, 0x33, 0x71, + 0x38, 0x09, 0x29, 0x2f, 0x3d, 0x3a, 0x4e, 0x9c, 0x24, 0x71, 0xee, 0x86, 0xb1, 0xa6, 0xc0, 0xba, + 0x94, 0xa3, 0x23, 0x80, 0xc1, 0x98, 0xff, 0xa0, 0x44, 0xa5, 0xe4, 0x71, 0xe2, 0x64, 0x13, 0x67, + 0x05, 0x22, 0x15, 0xe8, 0x33, 0xd8, 0xe2, 0xce, 0x84, 0x79, 0x53, 0x4e, 0x42, 0x36, 0xf0, 0x5c, + 0x3b, 0x2c, 0x6d, 0x48, 0x4d, 0x31, 0x82, 0xbb, 0x0a, 0x45, 0xa7, 0xb0, 0xe3, 0x4d, 0xf9, 0xd0, + 0x73, 0xdc, 0x21, 0x19, 0x8c, 0xa8, 0xeb, 0xb2, 0x31, 0x71, 0xec, 0xd2, 0xa6, 0xf4, 0xb8, 0x1d, + 0x53, 0x35, 0xc5, 0x98, 0xb6, 0xfe, 0x3d, 0x6c, 0xcd, 0xc3, 0x08, 0x7d, 0xcf, 0x0d, 0x19, 0x3a, + 0x80, 0x8c, 0x88, 0x63, 0x44, 0xc3, 0x91, 0x0c, 0x24, 0x8f, 0x45, 0x5c, 0xe7, 0x34, 0x1c, 0xa1, + 0x43, 0xc8, 0xfa, 0x01, 0x23, 0xce, 0x84, 0x0e, 0x99, 0x8c, 0x22, 0x8f, 0x33, 0x7e, 0xc0, 0x4c, + 0xf1, 0x8d, 0x9e, 0x41, 0xce, 0x57, 0xa6, 0x08, 0x0b, 0x02, 0x19, 0x43, 0x16, 0x43, 0x04, 0x19, + 0x41, 0xa0, 0x7f, 0x05, 0x5b, 0x58, 0xe4, 0xb2, 0xc1, 0x58, 0x9c, 0x33, 0x04, 0x1b, 0x36, 0x0b, + 0x79, 0xe4, 0x47, 0xfe, 0x17, 0x79, 0xa4, 0x93, 0xe5, 0x44, 0xa5, 0xe8, 0x44, 0xe4, 0x48, 0xb7, + 0x41, 0x5b, 0xac, 0x8f, 0x36, 0x7b, 0x02, 0x9a, 0x38, 0x1f, 0x11, 0xae, 0xc8, 0xf1, 0x44, 0xac, + 0x4a, 0xc8, 0x55, 0xc5, 0x08, 0x6f, 0x30, 0xd6, 0x0a, 0x29, 0x47, 0x2f, 0x54, 0x0a, 0xc9, 0xd8, + 0x1b, 0xdc, 0x12, 0x9b, 0x8d, 0xe9, 0x2c, 0x32, 0x5f, 0x10, 0x70, 0xd3, 0x1b, 0xdc, 0xd6, 0x05, + 0xa8, 0x7f, 0x07, 0xa8, 0xcb, 0x5c, 0xbb, 0xe7, 0x49, 0x5f, 0xf1, 0x46, 0x9f, 0x43, 0x3e, 0x0e, + 0x6e, 0x29, 0x31, 0x71, 0xc0, 0x32, 0x39, 0x3a, 0x6c, 0xca, 0x52, 0x91, 0x66, 0x73, 0x95, 0xfc, + 0xe9, 0xd8, 0x15, 0xf5, 0xa2, 0xcc, 0x28, 0x4a, 0x27, 0xb0, 0xb3, 0x62, 0x3c, 0x8a, 0xa2, 0x0c, + 0x22, 0x8d, 0x2a, 0xad, 0x89, 0x79, 0x5a, 0xe5, 0x37, 0x7a, 0x05, 0xe9, 0x1b, 0xea, 0x8c, 0xa7, + 0x41, 0x6c, 0x18, 0x9d, 0xce, 0x2b, 0xf2, 0xb4, 0xa1, 0x18, 0x1c, 0x4b, 0xf4, 0x9f, 0xd3, 0x90, + 0x8e, 0x40, 0x54, 0x81, 0x8d, 0x81, 0x67, 0x2b, 0x8b, 0xc5, 0xca, 0xd3, 0x4f, 0x97, 0xc5, 0xbf, + 0x35, 0xcf, 0x66, 0x58, 0x6a, 0x51, 0x05, 0xf6, 0x22, 0x53, 0x24, 0xf4, 0xa6, 0xc1, 0x80, 0x11, + 0x7f, 0x7a, 0x7d, 0xcb, 0x66, 0xd1, 0x69, 0xef, 0x44, 0x64, 0x57, 0x72, 0x1d, 0x49, 0xa1, 0xaf, + 0xa1, 0x18, 0x97, 0xda, 0xd4, 0xb7, 0x29, 0x67, 0xf2, 0xec, 0x73, 0x95, 0xd2, 0x92, 0xc7, 0xa8, + 0xe2, 0xfa, 0x92, 0xc7, 0x85, 0xc1, 0xf2, 0xa7, 0x28, 0xab, 0x11, 0x1f, 0x0f, 0xd4, 0xe9, 0x89, + 0xba, 0xde, 0xc0, 0x19, 0x01, 0xc8, 0x73, 0xd3, 0xa1, 0xe0, 0xb9, 0x8e, 0xe7, 0x92, 0x70, 0x44, + 0x49, 0xe5, 0xf5, 0x1b, 0x59, 0xcb, 0x79, 0x9c, 0x93, 0x60, 0x77, 0x44, 0x2b, 0xaf, 0xdf, 0x88, + 0xd2, 0x93, 0xdd, 0xc3, 0x3e, 0xfa, 0x4e, 0x30, 0x2b, 0xa5, 0x8e, 0x13, 0x27, 0x05, 0x2c, 0x1b, + 0xca, 0x90, 0x08, 0xda, 0x85, 0xcd, 0x9b, 0x31, 0x1d, 0x86, 0xa5, 0xb4, 0xa4, 0xd4, 0x87, 0xfe, + 0xe7, 0x06, 0xe4, 0x96, 0x52, 0x80, 0xf2, 0x90, 0xc1, 0x46, 0xd7, 0xc0, 0x97, 0x46, 0x5d, 0xfb, + 0x0f, 0x2a, 0xc1, 0x6e, 0xdf, 0xba, 0xb0, 0xda, 0xef, 0x2d, 0xd2, 0xa9, 0x5e, 0xb5, 0x0c, 0xab, + 0x47, 0xce, 0xab, 0xdd, 0x73, 0x2d, 0x81, 0x9e, 0x40, 0xc9, 0xb4, 0x6a, 0x6d, 0x8c, 0x8d, 0x5a, + 0x6f, 0xce, 0x55, 0x5b, 0xed, 0xbe, 0xd5, 0xd3, 0x1e, 0xa1, 0x67, 0x70, 0xd8, 0x30, 0xad, 0x6a, + 0x93, 0x2c, 0x34, 0xb5, 0x66, 0xef, 0x92, 0x18, 0x1f, 0x3a, 0x26, 0xbe, 0xd2, 0x92, 0xeb, 0x04, + 0xe7, 0xbd, 0x66, 0x2d, 0xb6, 0xb0, 0x81, 0x0e, 0x60, 0x4f, 0x09, 0xd4, 0x12, 0xd2, 0x6b, 0xb7, + 0x49, 0xb7, 0xdd, 0xb6, 0xb4, 0x4d, 0xb4, 0x0d, 0x05, 0xd3, 0xba, 0xac, 0x36, 0xcd, 0x3a, 0xc1, + 0x46, 0xb5, 0xd9, 0xd2, 0x52, 0x68, 0x07, 0xb6, 0xee, 0xeb, 0xd2, 0xc2, 0x44, 0xac, 0x6b, 0x5b, + 0x66, 0xdb, 0x22, 0x97, 0x06, 0xee, 0x9a, 0x6d, 0x4b, 0xcb, 0xa0, 0x7d, 0x40, 0xab, 0xd4, 0x79, + 0xab, 0x5a, 0xd3, 0xb2, 0x68, 0x0f, 0xb6, 0x57, 0xf1, 0x0b, 0xe3, 0x4a, 0x03, 0x91, 0x06, 0xb5, + 0x31, 0xf2, 0xd6, 0x68, 0xb6, 0xdf, 0x93, 0x96, 0x69, 0x99, 0xad, 0x7e, 0x4b, 0xcb, 0xa1, 0x5d, + 0xd0, 0x1a, 0x86, 0x41, 0x4c, 0xab, 0xdb, 0x6f, 0x34, 0xcc, 0x9a, 0x69, 0x58, 0x3d, 0x2d, 0xaf, + 0x3c, 0xaf, 0x0b, 0xbc, 0x20, 0x16, 0xd4, 0xce, 0xab, 0x96, 0x65, 0x34, 0x49, 0xdd, 0xec, 0x56, + 0xdf, 0x36, 0x8d, 0xba, 0x56, 0x44, 0x47, 0x70, 0xd0, 0x33, 0x5a, 0x9d, 0x36, 0xae, 0xe2, 0x2b, + 0x12, 0xf3, 0x8d, 0xaa, 0xd9, 0xec, 0x63, 0x43, 0xdb, 0x42, 0xcf, 0xe1, 0x08, 0x1b, 0xef, 0xfa, + 0x26, 0x36, 0xea, 0xc4, 0x6a, 0xd7, 0x0d, 0xd2, 0x30, 0xaa, 0xbd, 0x3e, 0x36, 0x48, 0xcb, 0xec, + 0x76, 0x4d, 0xeb, 0x1b, 0x4d, 0x43, 0xff, 0x83, 0xe3, 0xb9, 0x64, 0x6e, 0xe0, 0x9e, 0x6a, 0x5b, + 0xc4, 0x17, 0x9f, 0xa7, 0x65, 0x7c, 0xe8, 0x91, 0x8e, 0x61, 0x60, 0x0d, 0xa1, 0x32, 0xec, 0x2f, + 0xdc, 0x2b, 0x07, 0x91, 0xef, 0x1d, 0xc1, 0x75, 0x0c, 0xdc, 0xaa, 0x5a, 0xe2, 0x80, 0x57, 0xb8, + 0x5d, 0xb1, 0xed, 0x05, 0x77, 0x7f, 0xdb, 0x7b, 0xfa, 0x2f, 0x49, 0x28, 0xac, 0x14, 0x3d, 0x7a, + 0x02, 0xd9, 0xd0, 0x19, 0xba, 0x94, 0x8b, 0x56, 0x56, 0x5d, 0xbe, 0x00, 0xe4, 0x05, 0x30, 0xa2, + 0x8e, 0xab, 0xc6, 0x8b, 0xea, 0xb6, 0xac, 0x44, 0xe4, 0x70, 0x79, 0x0c, 0x69, 0xd1, 0x33, 0x62, + 0x96, 0x27, 0x65, 0x83, 0xa4, 0xc4, 0xa7, 0x69, 0x0b, 0xab, 0x62, 0x7e, 0x85, 0x9c, 0x4e, 0x7c, + 0xd9, 0x3b, 0x05, 0xbc, 0x00, 0xd0, 0x7f, 0xa1, 0x30, 0x61, 0x61, 0x48, 0x87, 0x8c, 0xa8, 0xfa, + 0x07, 0xa9, 0xc8, 0x47, 0x60, 0x43, 0x60, 0x42, 0x14, 0xf7, 0xaf, 0x12, 0x6d, 0x2a, 0x51, 0x04, + 0x2a, 0xd1, 0xfd, 0xf1, 0xc9, 0x69, 0xd4, 0x66, 0xcb, 0xe3, 0x93, 0x53, 0xf4, 0x12, 0xb6, 0x55, + 0x2f, 0x3b, 0xae, 0x33, 0x99, 0x4e, 0x54, 0x4f, 0xa7, 0xe5, 0x96, 0xb7, 0x64, 0x4f, 0x2b, 0x5c, + 0xb6, 0xf6, 0x01, 0x64, 0xae, 0x69, 0xc8, 0xc4, 0xe4, 0x2e, 0x65, 0xa4, 0xb1, 0xb4, 0xf8, 0x6e, + 0x30, 0x79, 0x09, 0x89, 0x79, 0x1e, 0x88, 0x69, 0x92, 0x55, 0xd4, 0x0d, 0x63, 0x58, 0xe4, 0x71, + 0xee, 0x81, 0x7e, 0x5c, 0x78, 0xc8, 0x2d, 0x79, 0x50, 0xb8, 0xf4, 0xf0, 0x12, 0xb6, 0xd9, 0x47, + 0x1e, 0x50, 0xe2, 0xf9, 0xf4, 0x6e, 0xca, 0x88, 0x4d, 0x39, 0x2d, 0xe5, 0x65, 0x72, 0xb7, 0x24, + 0xd1, 0x96, 0x78, 0x9d, 0x72, 0xaa, 0x3f, 0x81, 0x32, 0x66, 0x21, 0xe3, 0x2d, 0x27, 0x0c, 0x1d, + 0xcf, 0xad, 0x79, 0x2e, 0x0f, 0xbc, 0x71, 0x74, 0x01, 0xe8, 0x47, 0x70, 0xb8, 0x96, 0x55, 0x13, + 0x5c, 0x2c, 0x7e, 0x37, 0x65, 0xc1, 0x6c, 0xfd, 0xe2, 0x0b, 0x38, 0x5c, 0xcb, 0x46, 0xe3, 0xff, + 0x15, 0x6c, 0xba, 0x9e, 0xcd, 0xc2, 0x52, 0xe2, 0x38, 0x79, 0x92, 0xab, 0xec, 0x2f, 0xcd, 0x4d, + 0xcb, 0xb3, 0xd9, 0xb9, 0x13, 0x72, 0x2f, 0x98, 0x61, 0x25, 0xd2, 0x7f, 0x4f, 0x40, 0x6e, 0x09, + 0x46, 0xfb, 0x90, 0x8a, 0x66, 0xb4, 0x2a, 0xaa, 0xe8, 0x0b, 0xbd, 0x80, 0xe2, 0x98, 0x86, 0x9c, + 0x88, 0x91, 0x4d, 0xc4, 0x21, 0x45, 0xf7, 0xdd, 0x3d, 0x14, 0x7d, 0x09, 0x8f, 0x3d, 0x3e, 0x62, + 0x81, 0x7c, 0x2f, 0x90, 0x70, 0x3a, 0x18, 0xb0, 0x30, 0x24, 0x7e, 0xe0, 0x5d, 0xcb, 0x52, 0x7b, + 0x84, 0x1f, 0xa2, 0xd1, 0x6b, 0xc8, 0x44, 0x35, 0x22, 0x9e, 0x23, 0x62, 0xeb, 0x07, 0x9f, 0x8e, + 0xfc, 0x78, 0xf7, 0x73, 0xa9, 0xfe, 0x6b, 0x02, 0x8a, 0xab, 0x24, 0x7a, 0x2a, 0xab, 0x3f, 0x7e, + 0xad, 0x24, 0xe4, 0x61, 0x2e, 0x21, 0xff, 0x3a, 0x96, 0x0a, 0xec, 0x4e, 0x1c, 0x97, 0xf8, 0xcc, + 0xa5, 0x63, 0xe7, 0x27, 0x46, 0xe2, 0x87, 0x44, 0x52, 0xaa, 0xd7, 0x72, 0x48, 0x87, 0xfc, 0x4a, + 0xd0, 0x1b, 0x32, 0xe8, 0x15, 0xac, 0xf2, 0x5b, 0x12, 0x52, 0xf2, 0xca, 0x0e, 0x50, 0x1d, 0x72, + 0xe2, 0x0a, 0x8f, 0x5e, 0x4d, 0x68, 0x39, 0xe2, 0xd5, 0x07, 0x61, 0xb9, 0xbc, 0x8e, 0x8a, 0x8e, + 0xfc, 0x02, 0x34, 0x23, 0xe4, 0xce, 0x44, 0xdc, 0x86, 0xd1, 0x9b, 0x06, 0x2d, 0xeb, 0xef, 0x3d, + 0x94, 0xca, 0x87, 0x6b, 0xb9, 0xc8, 0x58, 0x53, 0x6d, 0x29, 0x7a, 0x55, 0xa0, 0xa3, 0x25, 0xed, + 0xa7, 0x4f, 0x99, 0xf2, 0xd3, 0x87, 0xe8, 0xc8, 0x9a, 0x0d, 0x3b, 0x6b, 0x2a, 0x1d, 0xfd, 0x7f, + 0x79, 0x07, 0x0f, 0xf6, 0x49, 0xf9, 0xc5, 0x3f, 0xc9, 0x16, 0x5e, 0xd6, 0xb4, 0xc4, 0x8a, 0x97, + 0x87, 0x1b, 0x6a, 0xc5, 0xcb, 0xdf, 0x74, 0xd6, 0xdb, 0xcf, 0xbf, 0x3d, 0x1b, 0x3a, 0x7c, 0x34, + 0xbd, 0x3e, 0x1d, 0x78, 0x93, 0xb3, 0xb1, 0x33, 0x1c, 0x71, 0xd7, 0x71, 0x87, 0x2e, 0xe3, 0x3f, + 0x7a, 0xc1, 0xed, 0xd9, 0xd8, 0xb5, 0xcf, 0xe4, 0x2b, 0xed, 0x6c, 0x6e, 0xee, 0x3a, 0x25, 0x1f, + 0xf8, 0x5f, 0xfc, 0x15, 0x00, 0x00, 0xff, 0xff, 0xe1, 0x3e, 0x2a, 0xde, 0x10, 0x0c, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -931,6 +1153,10 @@ type RouterClient interface { //ResetMissionControl clears all mission control state and starts with a clean //slate. ResetMissionControl(ctx context.Context, in *ResetMissionControlRequest, opts ...grpc.CallOption) (*ResetMissionControlResponse, error) + //* + //QueryMissionControl exposes the internal mission control state to callers. + //It is a development feature. + QueryMissionControl(ctx context.Context, in *QueryMissionControlRequest, opts ...grpc.CallOption) (*QueryMissionControlResponse, error) } type routerClient struct { @@ -977,6 +1203,15 @@ func (c *routerClient) ResetMissionControl(ctx context.Context, in *ResetMission return out, nil } +func (c *routerClient) QueryMissionControl(ctx context.Context, in *QueryMissionControlRequest, opts ...grpc.CallOption) (*QueryMissionControlResponse, error) { + out := new(QueryMissionControlResponse) + err := c.cc.Invoke(ctx, "/routerrpc.Router/QueryMissionControl", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // RouterServer is the server API for Router service. type RouterServer interface { //* @@ -999,6 +1234,10 @@ type RouterServer interface { //ResetMissionControl clears all mission control state and starts with a clean //slate. ResetMissionControl(context.Context, *ResetMissionControlRequest) (*ResetMissionControlResponse, error) + //* + //QueryMissionControl exposes the internal mission control state to callers. + //It is a development feature. + QueryMissionControl(context.Context, *QueryMissionControlRequest) (*QueryMissionControlResponse, error) } func RegisterRouterServer(s *grpc.Server, srv RouterServer) { @@ -1077,6 +1316,24 @@ func _Router_ResetMissionControl_Handler(srv interface{}, ctx context.Context, d return interceptor(ctx, in, info, handler) } +func _Router_QueryMissionControl_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryMissionControlRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(RouterServer).QueryMissionControl(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/routerrpc.Router/QueryMissionControl", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(RouterServer).QueryMissionControl(ctx, req.(*QueryMissionControlRequest)) + } + return interceptor(ctx, in, info, handler) +} + var _Router_serviceDesc = grpc.ServiceDesc{ ServiceName: "routerrpc.Router", HandlerType: (*RouterServer)(nil), @@ -1097,6 +1354,10 @@ var _Router_serviceDesc = grpc.ServiceDesc{ MethodName: "ResetMissionControl", Handler: _Router_ResetMissionControl_Handler, }, + { + MethodName: "QueryMissionControl", + Handler: _Router_QueryMissionControl_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "routerrpc/router.proto", diff --git a/lnrpc/routerrpc/router.proto b/lnrpc/routerrpc/router.proto index 852170546..f65a8c11e 100644 --- a/lnrpc/routerrpc/router.proto +++ b/lnrpc/routerrpc/router.proto @@ -249,6 +249,43 @@ message ResetMissionControlRequest{} message ResetMissionControlResponse{} +message QueryMissionControlRequest {} + +/// QueryMissionControlResponse contains mission control state per node. +message QueryMissionControlResponse { + repeated NodeHistory nodes = 1; +} + +/// 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 for channels not in the channel array. + float other_chan_success_prob = 3 [json_name = "other_chan_success_prob"]; + + /// Historical information of particular channels. + repeated ChannelHistory channels = 4 [json_name = "channels"]; +} + +/// NodeHistory contains the mission control state for a particular channel. +message ChannelHistory { + /// Short channel id + uint64 channel_id = 1 [json_name = "channel_id"]; + + /// Time stamp of last failure. + int64 last_fail_time = 2 [json_name = "last_fail_time"]; + + /// Minimum penalization amount. + int64 min_penalize_amt_sat = 3 [json_name = "min_penalize_amt_sat"]; + + /// Estimation of success probability for this channel. + float success_prob = 4 [json_name = "success_prob"]; +} + service Router { /** SendPayment attempts to route a payment described by the passed @@ -277,4 +314,10 @@ service Router { slate. */ rpc ResetMissionControl(ResetMissionControlRequest) returns (ResetMissionControlResponse); + + /** + QueryMissionControl exposes the internal mission control state to callers. + It is a development feature. + */ + rpc QueryMissionControl(QueryMissionControlRequest) returns (QueryMissionControlResponse); } diff --git a/lnrpc/routerrpc/router_server.go b/lnrpc/routerrpc/router_server.go index d47dbe69d..9f1ca2085 100644 --- a/lnrpc/routerrpc/router_server.go +++ b/lnrpc/routerrpc/router_server.go @@ -59,6 +59,10 @@ var ( Entity: "offchain", Action: "read", }}, + "/routerrpc.Router/QueryMissionControl": {{ + Entity: "offchain", + Action: "read", + }}, "/routerrpc.Router/ResetMissionControl": {{ Entity: "offchain", Action: "write", @@ -453,3 +457,46 @@ func (s *Server) ResetMissionControl(ctx context.Context, return &ResetMissionControlResponse{}, nil } + +// QueryMissionControl exposes the internal mission control state to callers. It +// is a development feature. +func (s *Server) QueryMissionControl(ctx context.Context, + req *QueryMissionControlRequest) (*QueryMissionControlResponse, error) { + + snapshot := s.cfg.RouterBackend.MissionControl.GetHistorySnapshot() + + rpcNodes := make([]*NodeHistory, len(snapshot.Nodes)) + for i, node := range snapshot.Nodes { + 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), + } + } + + var lastFail int64 + if node.LastFail != nil { + lastFail = node.LastFail.Unix() + } + + rpcNodes[i] = &NodeHistory{ + Pubkey: node.Node[:], + LastFailTime: lastFail, + OtherChanSuccessProb: float32( + node.OtherChanSuccessProb, + ), + Channels: channels, + } + } + + response := QueryMissionControlResponse{ + Nodes: rpcNodes, + } + + return &response, nil +} diff --git a/routing/missioncontrol.go b/routing/missioncontrol.go index 727858e7f..1a98e174f 100644 --- a/routing/missioncontrol.go +++ b/routing/missioncontrol.go @@ -84,6 +84,48 @@ type channelHistory struct { minPenalizeAmt lnwire.MilliSatoshi } +// 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 +} + +// 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, if any. + LastFail *time.Time + + // Channels is a list of channels for which specific information is + // logged. + Channels []MissionControlChannelSnapshot + + // OtherChanSuccessProb is the success probability for channels not in + // the Channels slice. + OtherChanSuccessProb float64 +} + +// MissionControlChannelSnapshot contains a snapshot of the current channel +// state in mission control. +type MissionControlChannelSnapshot struct { + // ChannelID is the short channel id of the snapshot. + ChannelID uint64 + + // LastFail is the time of last failure. + LastFail time.Time + + // MinPenalizeAmt is the minimum amount for which the channel will be + // penalized. + MinPenalizeAmt lnwire.MilliSatoshi + + // SuccessProb is the success probability estimation for this channel. + SuccessProb float64 +} + // NewMissionControl returns a new instance of missionControl. // // TODO(roasbeef): persist memory @@ -358,3 +400,55 @@ func (m *MissionControl) reportEdgeFailure(failedEdge edge, minPenalizeAmt: minPenalizeAmt, } } + +// GetHistorySnapshot takes a snapshot from the current mission control state +// and actual probability estimates. +func (m *MissionControl) GetHistorySnapshot() *MissionControlSnapshot { + m.Lock() + defer m.Unlock() + + log.Debugf("Requesting history snapshot from mission control: "+ + "node_count=%v", len(m.history)) + + nodes := make([]MissionControlNodeSnapshot, 0, len(m.history)) + + for v, h := range m.history { + channelSnapshot := make([]MissionControlChannelSnapshot, 0, + len(h.channelLastFail), + ) + + for id, lastFail := range h.channelLastFail { + // Show probability assuming amount meets min + // penalization amount. + prob := m.getEdgeProbabilityForNode( + h, id, lastFail.minPenalizeAmt, + ) + + channelSnapshot = append(channelSnapshot, + MissionControlChannelSnapshot{ + ChannelID: id, + LastFail: lastFail.lastFail, + MinPenalizeAmt: lastFail.minPenalizeAmt, + SuccessProb: prob, + }, + ) + } + + otherProb := m.getEdgeProbabilityForNode(h, 0, 0) + + nodes = append(nodes, + MissionControlNodeSnapshot{ + Node: v, + LastFail: h.lastFail, + OtherChanSuccessProb: otherProb, + Channels: channelSnapshot, + }, + ) + } + + snapshot := MissionControlSnapshot{ + Nodes: nodes, + } + + return &snapshot +} diff --git a/routing/missioncontrol_test.go b/routing/missioncontrol_test.go index 39ebb5bdd..ed79c5f20 100644 --- a/routing/missioncontrol_test.go +++ b/routing/missioncontrol_test.go @@ -64,4 +64,14 @@ func TestMissionControl(t *testing.T) { // to zero. mc.reportVertexFailure(testNode) expectP(1000, 0) + + // Check whether history snapshot looks sane. + history := mc.GetHistorySnapshot() + if len(history.Nodes) != 1 { + t.Fatal("unexpected number of nodes") + } + + if len(history.Nodes[0].Channels) != 1 { + t.Fatal("unexpected number of channels") + } } From 9b71d90a6e1800266cf34dcdda993ff4ce78f15c Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Fri, 10 May 2019 10:39:16 +0200 Subject: [PATCH 09/10] lncli: add querymc command Adds querymc command to lncli to dump mission control state. --- cmd/lncli/cmd_query_mission_control.go | 59 ++++++++++++++++++++++++++ cmd/lncli/main.go | 1 + cmd/lncli/routerrpc_active.go | 10 +++++ cmd/lncli/routerrpc_default.go | 10 +++++ 4 files changed, 80 insertions(+) create mode 100644 cmd/lncli/cmd_query_mission_control.go create mode 100644 cmd/lncli/routerrpc_active.go create mode 100644 cmd/lncli/routerrpc_default.go diff --git a/cmd/lncli/cmd_query_mission_control.go b/cmd/lncli/cmd_query_mission_control.go new file mode 100644 index 000000000..b8764d378 --- /dev/null +++ b/cmd/lncli/cmd_query_mission_control.go @@ -0,0 +1,59 @@ +// +build routerrpc + +package main + +import ( + "context" + "encoding/hex" + + "github.com/lightningnetwork/lnd/lnrpc/routerrpc" + + "github.com/urfave/cli" +) + +var queryMissionControlCommand = cli.Command{ + Name: "querymc", + Category: "Payments", + Action: actionDecorator(queryMissionControl), +} + +func queryMissionControl(ctx *cli.Context) error { + conn := getClientConn(ctx, false) + defer conn.Close() + + client := routerrpc.NewRouterClient(conn) + + req := &routerrpc.QueryMissionControlRequest{} + rpcCtx := context.Background() + snapshot, err := client.QueryMissionControl(rpcCtx, req) + if err != nil { + return err + } + + type displayNodeHistory struct { + Pubkey string + LastFailTime int64 + OtherChanSuccessProb float32 + Channels []*routerrpc.ChannelHistory + } + + displayResp := struct { + Nodes []displayNodeHistory + }{} + + 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, + }, + ) + } + + printJSON(displayResp) + + return nil +} diff --git a/cmd/lncli/main.go b/cmd/lncli/main.go index f04d45689..73a6ce757 100644 --- a/cmd/lncli/main.go +++ b/cmd/lncli/main.go @@ -303,6 +303,7 @@ func main() { // Add any extra autopilot commands determined by build flags. app.Commands = append(app.Commands, autopilotCommands()...) app.Commands = append(app.Commands, invoicesCommands()...) + app.Commands = append(app.Commands, routerCommands()...) if err := app.Run(os.Args); err != nil { fatal(err) diff --git a/cmd/lncli/routerrpc_active.go b/cmd/lncli/routerrpc_active.go new file mode 100644 index 000000000..4a34d6b1f --- /dev/null +++ b/cmd/lncli/routerrpc_active.go @@ -0,0 +1,10 @@ +// +build routerrpc + +package main + +import "github.com/urfave/cli" + +// routerCommands will return nil for non-routerrpc builds. +func routerCommands() []cli.Command { + return []cli.Command{queryMissionControlCommand} +} diff --git a/cmd/lncli/routerrpc_default.go b/cmd/lncli/routerrpc_default.go new file mode 100644 index 000000000..c2a5fe7be --- /dev/null +++ b/cmd/lncli/routerrpc_default.go @@ -0,0 +1,10 @@ +// +build !routerrpc + +package main + +import "github.com/urfave/cli" + +// routerCommands will return nil for non-routerrpc builds. +func routerCommands() []cli.Command { + return nil +} From 054e42f6805fe0318b2fdfc45931302149383c4f Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Wed, 22 May 2019 11:56:04 +0200 Subject: [PATCH 10/10] routing+routerrpc: expose mission control parameters in lnd config This commit exposes the three main parameters that influence mission control and path finding to the user as command line or config file flags. It allows for fine-tuning for optimal results. --- config.go | 4 +- lnrpc/routerrpc/config_active.go | 47 +++++++++++++++++++++++ lnrpc/routerrpc/config_default.go | 21 +++++++++- routing/missioncontrol.go | 64 +++++++++++++++++++++---------- routing/missioncontrol_test.go | 16 +++++--- routing/pathfind.go | 8 +++- routing/payment_session.go | 4 +- routing/payment_session_test.go | 1 + routing/router_test.go | 6 +++ server.go | 6 +++ 10 files changed, 145 insertions(+), 32 deletions(-) diff --git a/config.go b/config.go index a7ba5c6b9..c6e4c875c 100644 --- a/config.go +++ b/config.go @@ -27,6 +27,7 @@ import ( "github.com/lightningnetwork/lnd/discovery" "github.com/lightningnetwork/lnd/htlcswitch/hodl" "github.com/lightningnetwork/lnd/lncfg" + "github.com/lightningnetwork/lnd/lnrpc/routerrpc" "github.com/lightningnetwork/lnd/lnrpc/signrpc" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/routing" @@ -369,7 +370,8 @@ func loadConfig() (*config, error) { MinBackoff: defaultMinBackoff, MaxBackoff: defaultMaxBackoff, SubRPCServers: &subRPCServerConfigs{ - SignRPC: &signrpc.Config{}, + SignRPC: &signrpc.Config{}, + RouterRPC: routerrpc.DefaultConfig(), }, Autopilot: &autoPilotConfig{ MaxChannels: 5, diff --git a/lnrpc/routerrpc/config_active.go b/lnrpc/routerrpc/config_active.go index edf980355..c2ee3a234 100644 --- a/lnrpc/routerrpc/config_active.go +++ b/lnrpc/routerrpc/config_active.go @@ -3,7 +3,12 @@ package routerrpc import ( + "time" + + "github.com/lightningnetwork/lnd/lnwire" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/macaroons" "github.com/lightningnetwork/lnd/routing" ) @@ -19,6 +24,23 @@ type Config struct { // directory, named DefaultRouterMacFilename. RouterMacPath string `long:"routermacaroonpath" description:"Path to the router macaroon"` + // MinProbability is the minimum required route success probability to + // attempt the payment. + MinRouteProbability float64 `long:"minrtprob" description:"Minimum required route success probability to attempt the payment"` + + // AprioriHopProbability is the assumed success probability of a hop in + // 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."` + + // 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"` + + // AttemptCost is the virtual cost in path finding weight units of + // executing a payment attempt that fails. It is used to trade off + // potentially better routes against their probability of succeeding. + AttemptCost int64 `long:"attemptcost" description:"The (virtual) cost in sats of a failed payment attempt"` + // NetworkDir is the main network directory wherein the router rpc // server will find the macaroon named DefaultRouterMacFilename. NetworkDir string @@ -45,3 +67,28 @@ type Config struct { // main rpc server. RouterBackend *RouterBackend } + +// DefaultConfig defines the config defaults. +func DefaultConfig() *Config { + return &Config{ + AprioriHopProbability: routing.DefaultAprioriHopProbability, + MinRouteProbability: routing.DefaultMinRouteProbability, + PenaltyHalfLife: routing.DefaultPenaltyHalfLife, + AttemptCost: int64( + routing.DefaultPaymentAttemptPenalty.ToSatoshis(), + ), + } +} + +// GetMissionControlConfig returns the mission control config based on this sub +// server config. +func GetMissionControlConfig(cfg *Config) *routing.MissionControlConfig { + return &routing.MissionControlConfig{ + AprioriHopProbability: cfg.AprioriHopProbability, + MinRouteProbability: cfg.MinRouteProbability, + PaymentAttemptPenalty: lnwire.NewMSatFromSatoshis( + btcutil.Amount(cfg.AttemptCost), + ), + PenaltyHalfLife: cfg.PenaltyHalfLife, + } +} diff --git a/lnrpc/routerrpc/config_default.go b/lnrpc/routerrpc/config_default.go index 9ca27faa4..81c4a577e 100644 --- a/lnrpc/routerrpc/config_default.go +++ b/lnrpc/routerrpc/config_default.go @@ -2,6 +2,25 @@ package routerrpc -// Config is the default config for the package. When the build tag isn't +import "github.com/lightningnetwork/lnd/routing" + +// Config is the default config struct for the package. When the build tag isn't // specified, then we output a blank config. type Config struct{} + +// DefaultConfig defines the config defaults. Without the sub server enabled, +// there are no defaults to set. +func DefaultConfig() *Config { + return &Config{} +} + +// GetMissionControlConfig returns the mission control config based on this sub +// server config. +func GetMissionControlConfig(cfg *Config) *routing.MissionControlConfig { + return &routing.MissionControlConfig{ + AprioriHopProbability: routing.DefaultAprioriHopProbability, + MinRouteProbability: routing.DefaultMinRouteProbability, + PaymentAttemptPenalty: routing.DefaultPaymentAttemptPenalty, + PenaltyHalfLife: routing.DefaultPenaltyHalfLife, + } +} diff --git a/routing/missioncontrol.go b/routing/missioncontrol.go index 1a98e174f..17575a721 100644 --- a/routing/missioncontrol.go +++ b/routing/missioncontrol.go @@ -14,14 +14,10 @@ import ( ) const ( - // defaultPenaltyHalfLife is the default half-life duration. The + // DefaultPenaltyHalfLife is the default half-life duration. The // half-life duration defines after how much time a penalized node or // channel is back at 50% probability. - defaultPenaltyHalfLife = time.Hour - - // aprioriHopProbability is the assumed success probability of a hop in - // a route when no other information is available. - aprioriHopProbability = 1 + DefaultPenaltyHalfLife = time.Hour ) // MissionControl contains state which summarizes the past attempts of HTLC @@ -46,9 +42,7 @@ type MissionControl struct { // external function to enable deterministic unit tests. now func() time.Time - // penaltyHalfLife defines after how much time a penalized node or - // channel is back at 50% probability. - penaltyHalfLife time.Duration + cfg *MissionControlConfig sync.Mutex @@ -62,6 +56,28 @@ type MissionControl struct { // PaymentSessionSource interface. var _ PaymentSessionSource = (*MissionControl)(nil) +// MissionControlConfig defines parameters that control mission control +// behaviour. +type MissionControlConfig struct { + // PenaltyHalfLife defines after how much time a penalized node or + // channel is back at 50% probability. + PenaltyHalfLife time.Duration + + // PaymentAttemptPenalty is the virtual cost in path finding weight + // units of executing a payment attempt that fails. It is used to trade + // off potentially better routes against their probability of + // succeeding. + PaymentAttemptPenalty lnwire.MilliSatoshi + + // MinProbability defines the minimum success probability of the + // returned route. + MinRouteProbability float64 + + // AprioriHopProbability is the assumed success probability of a hop in + // a route when no other information is available. + AprioriHopProbability float64 +} + // nodeHistory contains a summary of payment attempt outcomes involving a // particular node. type nodeHistory struct { @@ -130,15 +146,23 @@ type MissionControlChannelSnapshot struct { // // TODO(roasbeef): persist memory func NewMissionControl(g *channeldb.ChannelGraph, selfNode *channeldb.LightningNode, - qb func(*channeldb.ChannelEdgeInfo) lnwire.MilliSatoshi) *MissionControl { + qb func(*channeldb.ChannelEdgeInfo) lnwire.MilliSatoshi, + cfg *MissionControlConfig) *MissionControl { + + log.Debugf("Instantiating mission control with config: "+ + "PenaltyHalfLife=%v, PaymentAttemptPenalty=%v, "+ + "MinRouteProbability=%v, AprioriHopProbability=%v", + cfg.PenaltyHalfLife, + int64(cfg.PaymentAttemptPenalty.ToSatoshis()), + cfg.MinRouteProbability, cfg.AprioriHopProbability) return &MissionControl{ - history: make(map[route.Vertex]*nodeHistory), - selfNode: selfNode, - queryBandwidth: qb, - graph: g, - now: time.Now, - penaltyHalfLife: defaultPenaltyHalfLife, + history: make(map[route.Vertex]*nodeHistory), + selfNode: selfNode, + queryBandwidth: qb, + graph: g, + now: time.Now, + cfg: cfg, } } @@ -302,7 +326,7 @@ func (m *MissionControl) getEdgeProbability(fromNode route.Vertex, // adjust this probability. nodeHistory, ok := m.history[fromNode] if !ok { - return aprioriHopProbability + return m.cfg.AprioriHopProbability } return m.getEdgeProbabilityForNode(nodeHistory, edge.ChannelID, amt) @@ -337,7 +361,7 @@ func (m *MissionControl) getEdgeProbabilityForNode(nodeHistory *nodeHistory, } if lastFailure == nil { - return aprioriHopProbability + return m.cfg.AprioriHopProbability } timeSinceLastFailure := m.now().Sub(*lastFailure) @@ -346,8 +370,8 @@ func (m *MissionControl) getEdgeProbabilityForNode(nodeHistory *nodeHistory, // 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.penaltyHalfLife.Hours() - probability := aprioriHopProbability * (1 - math.Pow(2, exp)) + exp := -timeSinceLastFailure.Hours() / m.cfg.PenaltyHalfLife.Hours() + probability := m.cfg.AprioriHopProbability * (1 - math.Pow(2, exp)) return probability } diff --git a/routing/missioncontrol_test.go b/routing/missioncontrol_test.go index ed79c5f20..19a288286 100644 --- a/routing/missioncontrol_test.go +++ b/routing/missioncontrol_test.go @@ -12,9 +12,13 @@ import ( func TestMissionControl(t *testing.T) { now := testTime - mc := NewMissionControl(nil, nil, nil) + mc := NewMissionControl( + nil, nil, nil, &MissionControlConfig{ + PenaltyHalfLife: 30 * time.Minute, + AprioriHopProbability: 0.8, + }, + ) mc.now = func() time.Time { return now } - mc.penaltyHalfLife = 30 * time.Minute testTime := time.Date(2018, time.January, 9, 14, 00, 00, 0, time.UTC) @@ -36,7 +40,7 @@ func TestMissionControl(t *testing.T) { } // Initial probability is expected to be 1. - expectP(1000, 1) + expectP(1000, 0.8) // Expect probability to be zero after reporting the edge as failed. mc.reportEdgeFailure(testEdge, 1000) @@ -44,11 +48,11 @@ func TestMissionControl(t *testing.T) { // As we reported with a min penalization amt, a lower amt than reported // should be unaffected. - expectP(500, 1) + expectP(500, 0.8) // Edge decay started. now = testTime.Add(30 * time.Minute) - expectP(1000, 0.5) + expectP(1000, 0.4) // Edge fails again, this time without a min penalization amt. The edge // should be penalized regardless of amount. @@ -58,7 +62,7 @@ func TestMissionControl(t *testing.T) { // Edge decay started. now = testTime.Add(60 * time.Minute) - expectP(1000, 0.5) + expectP(1000, 0.4) // A node level failure should bring probability of every channel back // to zero. diff --git a/routing/pathfind.go b/routing/pathfind.go index 3312a66e3..1e3676bc3 100644 --- a/routing/pathfind.go +++ b/routing/pathfind.go @@ -47,9 +47,13 @@ var ( // succeeding. DefaultPaymentAttemptPenalty = lnwire.NewMSatFromSatoshis(100) - // DefaultMinProbability is the default minimum probability for routes + // DefaultMinRouteProbability is the default minimum probability for routes // returned from findPath. - DefaultMinProbability = float64(0.01) + DefaultMinRouteProbability = float64(0.01) + + // DefaultAprioriHopProbability is the default a priori probability for + // a hop. + DefaultAprioriHopProbability = float64(0.95) ) // edgePolicyWithSource is a helper struct to keep track of the source node diff --git a/routing/payment_session.go b/routing/payment_session.go index 6865d1b38..7508e30a3 100644 --- a/routing/payment_session.go +++ b/routing/payment_session.go @@ -180,8 +180,8 @@ func (p *paymentSession) RequestRoute(payment *LightningPayment, FeeLimit: payment.FeeLimit, OutgoingChannelID: payment.OutgoingChannelID, CltvLimit: cltvLimit, - PaymentAttemptPenalty: DefaultPaymentAttemptPenalty, - MinProbability: DefaultMinProbability, + PaymentAttemptPenalty: p.mc.cfg.PaymentAttemptPenalty, + MinProbability: p.mc.cfg.MinRouteProbability, }, p.mc.selfNode.PubKeyBytes, payment.Target, payment.Amount, diff --git a/routing/payment_session_test.go b/routing/payment_session_test.go index 3ac01f2a9..a8748a624 100644 --- a/routing/payment_session_test.go +++ b/routing/payment_session_test.go @@ -35,6 +35,7 @@ func TestRequestRoute(t *testing.T) { session := &paymentSession{ mc: &MissionControl{ selfNode: &channeldb.LightningNode{}, + cfg: &MissionControlConfig{}, }, pathFinder: findPath, } diff --git a/routing/router_test.go b/routing/router_test.go index 70cd128bd..4c2b2be97 100644 --- a/routing/router_test.go +++ b/routing/router_test.go @@ -95,6 +95,12 @@ func createTestCtxFromGraphInstance(startingHeight uint32, graphInstance *testGr func(e *channeldb.ChannelEdgeInfo) lnwire.MilliSatoshi { return lnwire.NewMSatFromSatoshis(e.Capacity) }, + &MissionControlConfig{ + MinRouteProbability: 0.01, + PaymentAttemptPenalty: 100, + PenaltyHalfLife: time.Hour, + AprioriHopProbability: 0.9, + }, ) router, err := New(Config{ Graph: graphInstance.graph, diff --git a/server.go b/server.go index cac4125b0..64293b3e0 100644 --- a/server.go +++ b/server.go @@ -38,6 +38,7 @@ import ( "github.com/lightningnetwork/lnd/lncfg" "github.com/lightningnetwork/lnd/lnpeer" "github.com/lightningnetwork/lnd/lnrpc" + "github.com/lightningnetwork/lnd/lnrpc/routerrpc" "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/nat" @@ -639,8 +640,13 @@ func newServer(listenAddrs []net.Addr, chanDB *channeldb.DB, cc *chainControl, return link.Bandwidth() } + // Instantiate mission control with config from the sub server. + // + // TODO(joostjager): When we are further in the process of moving to sub + // servers, the mission control instance itself can be moved there too. s.missionControl = routing.NewMissionControl( chanGraph, selfNode, queryBandwidth, + routerrpc.GetMissionControlConfig(cfg.SubRPCServers.RouterRPC), ) s.chanRouter, err = routing.New(routing.Config{