Merge pull request #2802 from joostjager/probability
routing: probability based path finding
This commit is contained in:
commit
7453dbeacc
59
cmd/lncli/cmd_query_mission_control.go
Normal file
59
cmd/lncli/cmd_query_mission_control.go
Normal file
@ -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
|
||||
}
|
@ -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)
|
||||
|
10
cmd/lncli/routerrpc_active.go
Normal file
10
cmd/lncli/routerrpc_active.go
Normal file
@ -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}
|
||||
}
|
10
cmd/lncli/routerrpc_default.go
Normal file
10
cmd/lncli/routerrpc_default.go
Normal file
@ -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
|
||||
}
|
@ -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"
|
||||
@ -370,6 +371,7 @@ func loadConfig() (*config, error) {
|
||||
MaxBackoff: defaultMaxBackoff,
|
||||
SubRPCServers: &subRPCServerConfigs{
|
||||
SignRPC: &signrpc.Config{},
|
||||
RouterRPC: routerrpc.DefaultConfig(),
|
||||
},
|
||||
Autopilot: &autoPilotConfig{
|
||||
MaxChannels: 5,
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
@ -737,6 +737,275 @@ 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
|
||||
|
||||
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")
|
||||
@ -747,89 +1016,109 @@ 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")
|
||||
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{
|
||||
// 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,
|
||||
// 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.
|
||||
@ -860,6 +1149,14 @@ 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)
|
||||
//*
|
||||
//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 {
|
||||
@ -897,6 +1194,24 @@ 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
|
||||
}
|
||||
|
||||
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 {
|
||||
//*
|
||||
@ -915,6 +1230,14 @@ 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)
|
||||
//*
|
||||
//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) {
|
||||
@ -975,6 +1298,42 @@ 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)
|
||||
}
|
||||
|
||||
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),
|
||||
@ -991,6 +1350,14 @@ var _Router_serviceDesc = grpc.ServiceDesc{
|
||||
MethodName: "SendToRoute",
|
||||
Handler: _Router_SendToRoute_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "ResetMissionControl",
|
||||
Handler: _Router_ResetMissionControl_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "QueryMissionControl",
|
||||
Handler: _Router_QueryMissionControl_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{},
|
||||
Metadata: "routerrpc/router.proto",
|
||||
|
@ -245,6 +245,46 @@ message ChannelUpdate {
|
||||
*/
|
||||
bytes extra_opaque_data = 12;
|
||||
}
|
||||
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 {
|
||||
/**
|
||||
@ -268,4 +308,16 @@ 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);
|
||||
|
||||
/**
|
||||
QueryMissionControl exposes the internal mission control state to callers.
|
||||
It is a development feature.
|
||||
*/
|
||||
rpc QueryMissionControl(QueryMissionControlRequest) returns (QueryMissionControlResponse);
|
||||
}
|
||||
|
@ -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
|
||||
@ -122,8 +124,21 @@ func (r *RouterBackend) QueryRoutes(ctx context.Context,
|
||||
|
||||
restrictions := &routing.RestrictParams{
|
||||
FeeLimit: feeLimit,
|
||||
IgnoredNodes: ignoredNodes,
|
||||
IgnoredEdges: ignoredEdges,
|
||||
ProbabilitySource: func(node route.Vertex,
|
||||
edge routing.EdgeLocator,
|
||||
amt lnwire.MilliSatoshi) 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
|
||||
|
@ -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,
|
||||
) != 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,
|
||||
) != 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{}, 0,
|
||||
) != 1 {
|
||||
t.Fatal("expecting 100% probability")
|
||||
}
|
||||
|
||||
hops := []*route.Hop{{}}
|
||||
|
@ -59,6 +59,14 @@ var (
|
||||
Entity: "offchain",
|
||||
Action: "read",
|
||||
}},
|
||||
"/routerrpc.Router/QueryMissionControl": {{
|
||||
Entity: "offchain",
|
||||
Action: "read",
|
||||
}},
|
||||
"/routerrpc.Router/ResetMissionControl": {{
|
||||
Entity: "offchain",
|
||||
Action: "write",
|
||||
}},
|
||||
}
|
||||
|
||||
// DefaultRouterMacFilename is the default name of the router macaroon
|
||||
@ -439,3 +447,56 @@ 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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
@ -1,6 +1,7 @@
|
||||
package routing
|
||||
|
||||
import (
|
||||
"math"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@ -13,48 +14,23 @@ 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)
|
||||
|
||||
// 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)
|
||||
// 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
|
||||
)
|
||||
|
||||
// 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 +38,12 @@ 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
|
||||
|
||||
cfg *MissionControlConfig
|
||||
|
||||
sync.Mutex
|
||||
|
||||
// TODO(roasbeef): further counters, if vertex continually unavailable,
|
||||
@ -74,83 +56,113 @@ type MissionControl struct {
|
||||
// PaymentSessionSource interface.
|
||||
var _ PaymentSessionSource = (*MissionControl)(nil)
|
||||
|
||||
// NewMissionControl returns a new instance of MissionControl.
|
||||
// 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 {
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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
|
||||
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{
|
||||
failedEdges: make(map[EdgeLocator]time.Time),
|
||||
failedVertexes: make(map[route.Vertex]time.Time),
|
||||
history: make(map[route.Vertex]*nodeHistory),
|
||||
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,
|
||||
now: time.Now,
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
@ -161,8 +173,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 +236,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 +248,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 +259,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 +305,174 @@ 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 m.cfg.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 m.cfg.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.cfg.PenaltyHalfLife.Hours()
|
||||
probability := m.cfg.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,
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
81
routing/missioncontrol_test.go
Normal file
81
routing/missioncontrol_test.go
Normal file
@ -0,0 +1,81 @@
|
||||
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, &MissionControlConfig{
|
||||
PenaltyHalfLife: 30 * time.Minute,
|
||||
AprioriHopProbability: 0.8,
|
||||
},
|
||||
)
|
||||
mc.now = func() time.Time { return now }
|
||||
|
||||
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, 0.8)
|
||||
|
||||
// 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, 0.8)
|
||||
|
||||
// Edge decay started.
|
||||
now = testTime.Add(30 * time.Minute)
|
||||
expectP(1000, 0.4)
|
||||
|
||||
// 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.4)
|
||||
|
||||
// A node level failure should bring probability of every channel back
|
||||
// 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")
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -40,6 +40,22 @@ 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)
|
||||
|
||||
// DefaultMinRouteProbability is the default minimum probability for routes
|
||||
// returned from findPath.
|
||||
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
|
||||
// of a channel edge. ChannelEdgePolicy only contains to destination node
|
||||
// of the edge.
|
||||
@ -228,13 +244,10 @@ 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,
|
||||
lnwire.MilliSatoshi) float64
|
||||
|
||||
// FeeLimit is a maximum fee amount allowed to be used on the path from
|
||||
// the source to the target.
|
||||
@ -248,6 +261,16 @@ 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
|
||||
|
||||
// MinProbability defines the minimum success probability of the
|
||||
// returned route.
|
||||
MinProbability float64
|
||||
}
|
||||
|
||||
// findPath attempts to find a path from the source node within the
|
||||
@ -331,10 +354,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 +366,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 +395,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, amountToSend,
|
||||
)
|
||||
|
||||
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 +473,39 @@ 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
|
||||
|
||||
// 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
|
||||
// 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 +523,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 +655,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)
|
||||
}
|
||||
|
@ -53,6 +53,7 @@ const (
|
||||
var (
|
||||
noRestrictions = &RestrictParams{
|
||||
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, lnwire.MilliSatoshi) float64 {
|
||||
return 1
|
||||
}
|
||||
|
||||
// testGraph is the struct which corresponds to the JSON format used to encode
|
||||
// graphs within the files in the testdata directory.
|
||||
//
|
||||
@ -477,6 +484,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 +502,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 +527,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 +553,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
|
||||
@ -625,9 +642,7 @@ func TestFindLowestFeePath(t *testing.T) {
|
||||
&graphParams{
|
||||
graph: testGraphInstance.graph,
|
||||
},
|
||||
&RestrictParams{
|
||||
FeeLimit: noFeeLimit,
|
||||
},
|
||||
noRestrictions,
|
||||
sourceNode.PubKeyBytes, target, paymentAmt,
|
||||
)
|
||||
if err != nil {
|
||||
@ -767,6 +782,7 @@ func testBasicGraphPathFindingCase(t *testing.T, graphInstance *testGraphInstanc
|
||||
},
|
||||
&RestrictParams{
|
||||
FeeLimit: test.feeLimit,
|
||||
ProbabilitySource: noProbabilitySource,
|
||||
},
|
||||
sourceNode.PubKeyBytes, target, paymentAmt,
|
||||
)
|
||||
@ -934,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 {
|
||||
@ -1190,9 +1204,7 @@ func TestNewRoutePathTooLong(t *testing.T) {
|
||||
&graphParams{
|
||||
graph: graph.graph,
|
||||
},
|
||||
&RestrictParams{
|
||||
FeeLimit: noFeeLimit,
|
||||
},
|
||||
noRestrictions,
|
||||
sourceNode.PubKeyBytes, target, paymentAmt,
|
||||
)
|
||||
if err != nil {
|
||||
@ -1206,9 +1218,7 @@ func TestNewRoutePathTooLong(t *testing.T) {
|
||||
&graphParams{
|
||||
graph: graph.graph,
|
||||
},
|
||||
&RestrictParams{
|
||||
FeeLimit: noFeeLimit,
|
||||
},
|
||||
noRestrictions,
|
||||
sourceNode.PubKeyBytes, target, paymentAmt,
|
||||
)
|
||||
if err == nil {
|
||||
@ -1248,9 +1258,7 @@ func TestPathNotAvailable(t *testing.T) {
|
||||
&graphParams{
|
||||
graph: graph.graph,
|
||||
},
|
||||
&RestrictParams{
|
||||
FeeLimit: noFeeLimit,
|
||||
},
|
||||
noRestrictions,
|
||||
sourceNode.PubKeyBytes, unknownNode, 100,
|
||||
)
|
||||
if !IsError(err, ErrNoPathFound) {
|
||||
@ -1287,9 +1295,7 @@ func TestPathInsufficientCapacity(t *testing.T) {
|
||||
&graphParams{
|
||||
graph: graph.graph,
|
||||
},
|
||||
&RestrictParams{
|
||||
FeeLimit: noFeeLimit,
|
||||
},
|
||||
noRestrictions,
|
||||
sourceNode.PubKeyBytes, target, payAmt,
|
||||
)
|
||||
if !IsError(err, ErrNoPathFound) {
|
||||
@ -1322,9 +1328,7 @@ func TestRouteFailMinHTLC(t *testing.T) {
|
||||
&graphParams{
|
||||
graph: graph.graph,
|
||||
},
|
||||
&RestrictParams{
|
||||
FeeLimit: noFeeLimit,
|
||||
},
|
||||
noRestrictions,
|
||||
sourceNode.PubKeyBytes, target, payAmt,
|
||||
)
|
||||
if !IsError(err, ErrNoPathFound) {
|
||||
@ -1382,9 +1386,7 @@ func TestRouteFailMaxHTLC(t *testing.T) {
|
||||
&graphParams{
|
||||
graph: graph.graph,
|
||||
},
|
||||
&RestrictParams{
|
||||
FeeLimit: noFeeLimit,
|
||||
},
|
||||
noRestrictions,
|
||||
sourceNode.PubKeyBytes, target, payAmt,
|
||||
)
|
||||
if err != nil {
|
||||
@ -1406,9 +1408,7 @@ func TestRouteFailMaxHTLC(t *testing.T) {
|
||||
&graphParams{
|
||||
graph: graph.graph,
|
||||
},
|
||||
&RestrictParams{
|
||||
FeeLimit: noFeeLimit,
|
||||
},
|
||||
noRestrictions,
|
||||
sourceNode.PubKeyBytes, target, payAmt,
|
||||
)
|
||||
if !IsError(err, ErrNoPathFound) {
|
||||
@ -1443,9 +1443,7 @@ func TestRouteFailDisabledEdge(t *testing.T) {
|
||||
&graphParams{
|
||||
graph: graph.graph,
|
||||
},
|
||||
&RestrictParams{
|
||||
FeeLimit: noFeeLimit,
|
||||
},
|
||||
noRestrictions,
|
||||
sourceNode.PubKeyBytes, target, payAmt,
|
||||
)
|
||||
if err != nil {
|
||||
@ -1473,9 +1471,7 @@ func TestRouteFailDisabledEdge(t *testing.T) {
|
||||
&graphParams{
|
||||
graph: graph.graph,
|
||||
},
|
||||
&RestrictParams{
|
||||
FeeLimit: noFeeLimit,
|
||||
},
|
||||
noRestrictions,
|
||||
sourceNode.PubKeyBytes, target, payAmt,
|
||||
)
|
||||
if err != nil {
|
||||
@ -1500,9 +1496,7 @@ func TestRouteFailDisabledEdge(t *testing.T) {
|
||||
&graphParams{
|
||||
graph: graph.graph,
|
||||
},
|
||||
&RestrictParams{
|
||||
FeeLimit: noFeeLimit,
|
||||
},
|
||||
noRestrictions,
|
||||
sourceNode.PubKeyBytes, target, payAmt,
|
||||
)
|
||||
if !IsError(err, ErrNoPathFound) {
|
||||
@ -1536,9 +1530,7 @@ func TestPathSourceEdgesBandwidth(t *testing.T) {
|
||||
&graphParams{
|
||||
graph: graph.graph,
|
||||
},
|
||||
&RestrictParams{
|
||||
FeeLimit: noFeeLimit,
|
||||
},
|
||||
noRestrictions,
|
||||
sourceNode.PubKeyBytes, target, payAmt,
|
||||
)
|
||||
if err != nil {
|
||||
@ -1562,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) {
|
||||
@ -1582,9 +1572,7 @@ func TestPathSourceEdgesBandwidth(t *testing.T) {
|
||||
graph: graph.graph,
|
||||
bandwidthHints: bandwidths,
|
||||
},
|
||||
&RestrictParams{
|
||||
FeeLimit: noFeeLimit,
|
||||
},
|
||||
noRestrictions,
|
||||
sourceNode.PubKeyBytes, target, payAmt,
|
||||
)
|
||||
if err != nil {
|
||||
@ -1615,9 +1603,7 @@ func TestPathSourceEdgesBandwidth(t *testing.T) {
|
||||
graph: graph.graph,
|
||||
bandwidthHints: bandwidths,
|
||||
},
|
||||
&RestrictParams{
|
||||
FeeLimit: noFeeLimit,
|
||||
},
|
||||
noRestrictions,
|
||||
sourceNode.PubKeyBytes, target, payAmt,
|
||||
)
|
||||
if err != nil {
|
||||
@ -1906,6 +1892,7 @@ func TestRestrictOutgoingChannel(t *testing.T) {
|
||||
&RestrictParams{
|
||||
FeeLimit: noFeeLimit,
|
||||
OutgoingChannelID: &outgoingChannelID,
|
||||
ProbabilitySource: noProbabilitySource,
|
||||
},
|
||||
sourceVertex, target, paymentAmt,
|
||||
)
|
||||
@ -1982,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"]
|
||||
|
||||
@ -1999,10 +1983,9 @@ func testCltvLimit(t *testing.T, limit uint32, expectedChannel uint64) {
|
||||
graph: testGraphInstance.graph,
|
||||
},
|
||||
&RestrictParams{
|
||||
IgnoredNodes: ignoredVertexes,
|
||||
IgnoredEdges: ignoredEdges,
|
||||
FeeLimit: noFeeLimit,
|
||||
CltvLimit: cltvLimit,
|
||||
ProbabilitySource: noProbabilitySource,
|
||||
},
|
||||
sourceVertex, target, paymentAmt,
|
||||
)
|
||||
@ -2035,3 +2018,170 @@ 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
|
||||
minProbability 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,
|
||||
minProbability: 0.1,
|
||||
expectedChan: 10,
|
||||
},
|
||||
{
|
||||
name: "three hop 2",
|
||||
p10: 0.5, p11: 0.8, p20: 0.7,
|
||||
minProbability: 0.1,
|
||||
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,
|
||||
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.minProbability, tc.expectedChan,
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func testProbabilityRouting(t *testing.T, p10, p11, p20, minProbability 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("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,
|
||||
amt lnwire.MilliSatoshi) float64 {
|
||||
|
||||
if amt == 0 {
|
||||
t.Fatal("expected non-zero amount")
|
||||
}
|
||||
|
||||
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),
|
||||
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)
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
@ -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,23 +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{}{}
|
||||
p.errFailedPolicyChans[key] = struct{}{}
|
||||
}
|
||||
|
||||
// RequestRoute returns a route which is likely to be capable for successfully
|
||||
@ -169,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
|
||||
@ -200,11 +176,12 @@ func (p *paymentSession) RequestRoute(payment *LightningPayment,
|
||||
bandwidthHints: p.bandwidthHints,
|
||||
},
|
||||
&RestrictParams{
|
||||
IgnoredNodes: pruneView.vertexes,
|
||||
IgnoredEdges: pruneView.edges,
|
||||
ProbabilitySource: p.mc.getEdgeProbability,
|
||||
FeeLimit: payment.FeeLimit,
|
||||
OutgoingChannelID: payment.OutgoingChannelID,
|
||||
CltvLimit: cltvLimit,
|
||||
PaymentAttemptPenalty: p.mc.cfg.PaymentAttemptPenalty,
|
||||
MinProbability: p.mc.cfg.MinRouteProbability,
|
||||
},
|
||||
p.mc.selfNode.PubKeyBytes, payment.Target,
|
||||
payment.Amount,
|
||||
@ -227,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
|
||||
}
|
||||
|
@ -35,8 +35,8 @@ func TestRequestRoute(t *testing.T) {
|
||||
session := &paymentSession{
|
||||
mc: &MissionControl{
|
||||
selfNode: &channeldb.LightningNode{},
|
||||
cfg: &MissionControlConfig{},
|
||||
},
|
||||
pruneViewSnapshot: graphPruneView{},
|
||||
pathFinder: findPath,
|
||||
}
|
||||
|
||||
|
@ -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(),
|
||||
)
|
||||
}
|
||||
|
@ -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
|
||||
@ -1598,7 +1605,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.
|
||||
@ -1772,7 +1781,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
|
||||
}
|
||||
@ -1804,13 +1815,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) {
|
||||
@ -1901,7 +1910,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
|
||||
@ -1909,7 +1918,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
|
||||
@ -1934,7 +1943,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
|
||||
@ -1965,14 +1974,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:
|
||||
@ -1982,12 +1989,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
|
||||
|
||||
@ -2006,17 +2015,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
|
||||
|
@ -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,
|
||||
@ -202,6 +208,7 @@ func TestFindRoutesWithFeeLimit(t *testing.T) {
|
||||
paymentAmt := lnwire.NewMSatFromSatoshis(100)
|
||||
restrictions := &RestrictParams{
|
||||
FeeLimit: lnwire.NewMSatFromSatoshis(10),
|
||||
ProbabilitySource: noProbabilitySource,
|
||||
}
|
||||
|
||||
route, err := ctx.router.FindRoute(
|
||||
@ -2198,9 +2205,7 @@ func TestFindPathFeeWeighting(t *testing.T) {
|
||||
&graphParams{
|
||||
graph: ctx.graph,
|
||||
},
|
||||
&RestrictParams{
|
||||
FeeLimit: noFeeLimit,
|
||||
},
|
||||
noRestrictions,
|
||||
sourceNode.PubKeyBytes, target, amt,
|
||||
)
|
||||
if err != nil {
|
||||
|
@ -477,6 +477,7 @@ func newRPCServer(s *server, macService *macaroons.Service,
|
||||
return info.NodeKey1Bytes, info.NodeKey2Bytes, nil
|
||||
},
|
||||
FindRoute: s.chanRouter.FindRoute,
|
||||
MissionControl: s.missionControl,
|
||||
}
|
||||
|
||||
var (
|
||||
|
21
server.go
21
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"
|
||||
@ -187,6 +188,8 @@ type server struct {
|
||||
|
||||
breachArbiter *breachArbiter
|
||||
|
||||
missionControl *routing.MissionControl
|
||||
|
||||
chanRouter *routing.ChannelRouter
|
||||
|
||||
authGossiper *discovery.AuthenticatedGossiper
|
||||
@ -617,15 +620,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 {
|
||||
@ -646,8 +640,13 @@ func newServer(listenAddrs []net.Addr, chanDB *channeldb.DB, cc *chainControl,
|
||||
return link.Bandwidth()
|
||||
}
|
||||
|
||||
missionControl := routing.NewMissionControl(
|
||||
// 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{
|
||||
@ -656,7 +655,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,
|
||||
|
Loading…
Reference in New Issue
Block a user