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.
|
// Add any extra autopilot commands determined by build flags.
|
||||||
app.Commands = append(app.Commands, autopilotCommands()...)
|
app.Commands = append(app.Commands, autopilotCommands()...)
|
||||||
app.Commands = append(app.Commands, invoicesCommands()...)
|
app.Commands = append(app.Commands, invoicesCommands()...)
|
||||||
|
app.Commands = append(app.Commands, routerCommands()...)
|
||||||
|
|
||||||
if err := app.Run(os.Args); err != nil {
|
if err := app.Run(os.Args); err != nil {
|
||||||
fatal(err)
|
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/discovery"
|
||||||
"github.com/lightningnetwork/lnd/htlcswitch/hodl"
|
"github.com/lightningnetwork/lnd/htlcswitch/hodl"
|
||||||
"github.com/lightningnetwork/lnd/lncfg"
|
"github.com/lightningnetwork/lnd/lncfg"
|
||||||
|
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
|
||||||
"github.com/lightningnetwork/lnd/lnrpc/signrpc"
|
"github.com/lightningnetwork/lnd/lnrpc/signrpc"
|
||||||
"github.com/lightningnetwork/lnd/lnwire"
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
"github.com/lightningnetwork/lnd/routing"
|
"github.com/lightningnetwork/lnd/routing"
|
||||||
@ -370,6 +371,7 @@ func loadConfig() (*config, error) {
|
|||||||
MaxBackoff: defaultMaxBackoff,
|
MaxBackoff: defaultMaxBackoff,
|
||||||
SubRPCServers: &subRPCServerConfigs{
|
SubRPCServers: &subRPCServerConfigs{
|
||||||
SignRPC: &signrpc.Config{},
|
SignRPC: &signrpc.Config{},
|
||||||
|
RouterRPC: routerrpc.DefaultConfig(),
|
||||||
},
|
},
|
||||||
Autopilot: &autoPilotConfig{
|
Autopilot: &autoPilotConfig{
|
||||||
MaxChannels: 5,
|
MaxChannels: 5,
|
||||||
|
@ -3,7 +3,12 @@
|
|||||||
package routerrpc
|
package routerrpc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
|
|
||||||
"github.com/btcsuite/btcd/chaincfg"
|
"github.com/btcsuite/btcd/chaincfg"
|
||||||
|
"github.com/btcsuite/btcutil"
|
||||||
"github.com/lightningnetwork/lnd/macaroons"
|
"github.com/lightningnetwork/lnd/macaroons"
|
||||||
"github.com/lightningnetwork/lnd/routing"
|
"github.com/lightningnetwork/lnd/routing"
|
||||||
)
|
)
|
||||||
@ -19,6 +24,23 @@ type Config struct {
|
|||||||
// directory, named DefaultRouterMacFilename.
|
// directory, named DefaultRouterMacFilename.
|
||||||
RouterMacPath string `long:"routermacaroonpath" description:"Path to the router macaroon"`
|
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
|
// NetworkDir is the main network directory wherein the router rpc
|
||||||
// server will find the macaroon named DefaultRouterMacFilename.
|
// server will find the macaroon named DefaultRouterMacFilename.
|
||||||
NetworkDir string
|
NetworkDir string
|
||||||
@ -45,3 +67,28 @@ type Config struct {
|
|||||||
// main rpc server.
|
// main rpc server.
|
||||||
RouterBackend *RouterBackend
|
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
|
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.
|
// specified, then we output a blank config.
|
||||||
type Config struct{}
|
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
|
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() {
|
func init() {
|
||||||
proto.RegisterEnum("routerrpc.Failure_FailureCode", Failure_FailureCode_name, Failure_FailureCode_value)
|
proto.RegisterEnum("routerrpc.Failure_FailureCode", Failure_FailureCode_name, Failure_FailureCode_value)
|
||||||
proto.RegisterType((*PaymentRequest)(nil), "routerrpc.PaymentRequest")
|
proto.RegisterType((*PaymentRequest)(nil), "routerrpc.PaymentRequest")
|
||||||
@ -747,89 +1016,109 @@ func init() {
|
|||||||
proto.RegisterType((*SendToRouteResponse)(nil), "routerrpc.SendToRouteResponse")
|
proto.RegisterType((*SendToRouteResponse)(nil), "routerrpc.SendToRouteResponse")
|
||||||
proto.RegisterType((*Failure)(nil), "routerrpc.Failure")
|
proto.RegisterType((*Failure)(nil), "routerrpc.Failure")
|
||||||
proto.RegisterType((*ChannelUpdate)(nil), "routerrpc.ChannelUpdate")
|
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) }
|
func init() { proto.RegisterFile("routerrpc/router.proto", fileDescriptor_7a0613f69d37b0a5) }
|
||||||
|
|
||||||
var fileDescriptor_7a0613f69d37b0a5 = []byte{
|
var fileDescriptor_7a0613f69d37b0a5 = []byte{
|
||||||
// 1224 bytes of a gzipped FileDescriptorProto
|
// 1456 bytes of a gzipped FileDescriptorProto
|
||||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x74, 0x56, 0xef, 0x72, 0x1a, 0x37,
|
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x56, 0xdb, 0x72, 0x1a, 0x47,
|
||||||
0x10, 0x2f, 0xb1, 0xcd, 0x9f, 0x05, 0xcc, 0x59, 0xb6, 0x13, 0x4c, 0xe2, 0xc4, 0xa1, 0x9d, 0xd6,
|
0x13, 0xfe, 0x31, 0x12, 0x87, 0xe6, 0xa0, 0xd5, 0xe8, 0x60, 0x84, 0x2c, 0x5b, 0xde, 0xff, 0xff,
|
||||||
0x93, 0xe9, 0xd8, 0x53, 0x3a, 0xc9, 0xc7, 0x76, 0x08, 0x88, 0xfa, 0x26, 0x70, 0x47, 0x75, 0xe0,
|
0x1d, 0x95, 0xcb, 0x25, 0x55, 0x48, 0xd9, 0x95, 0xab, 0xa4, 0x30, 0x2c, 0xd1, 0x96, 0x60, 0xc1,
|
||||||
0xc4, 0xed, 0x07, 0x8d, 0xcc, 0xc9, 0x70, 0x35, 0xf7, 0xc7, 0x77, 0xa2, 0xb5, 0x5f, 0xa0, 0xaf,
|
0x03, 0xc8, 0x56, 0x72, 0x31, 0x35, 0x62, 0x47, 0xb0, 0x11, 0xec, 0xae, 0x76, 0x87, 0xc4, 0xe4,
|
||||||
0xd3, 0xa7, 0xe8, 0x43, 0xf4, 0x11, 0xfa, 0x16, 0x1d, 0x49, 0x77, 0x80, 0x1d, 0xf7, 0x13, 0xa7,
|
0x01, 0xf2, 0x3a, 0xc9, 0x4d, 0x6e, 0x73, 0x97, 0x87, 0xc8, 0xdb, 0xa4, 0x66, 0x66, 0x97, 0x83,
|
||||||
0xdf, 0xef, 0xa7, 0x5d, 0xed, 0x6a, 0x77, 0x05, 0x3c, 0x8d, 0xc3, 0x85, 0xe0, 0x71, 0x1c, 0x4d,
|
0x8c, 0x92, 0x5c, 0xc1, 0x7e, 0xdf, 0x37, 0xdd, 0xd3, 0x3d, 0xdd, 0x3d, 0x03, 0xfb, 0x81, 0x37,
|
||||||
0x4e, 0xf5, 0xd7, 0x49, 0x14, 0x87, 0x22, 0x44, 0xa5, 0x25, 0xde, 0x28, 0xc5, 0xd1, 0x44, 0xa3,
|
0xe5, 0x2c, 0x08, 0xfc, 0xc1, 0x99, 0xfa, 0x77, 0xea, 0x07, 0x1e, 0xf7, 0x50, 0x76, 0x8e, 0x97,
|
||||||
0xcd, 0xbf, 0x73, 0xb0, 0x3d, 0x64, 0x77, 0x3e, 0x0f, 0x04, 0xe1, 0x37, 0x0b, 0x9e, 0x08, 0xf4,
|
0xb3, 0x81, 0x3f, 0x50, 0xa8, 0xfe, 0x47, 0x02, 0x8a, 0x1d, 0x3a, 0x9b, 0x30, 0x97, 0x63, 0x76,
|
||||||
0x0c, 0x0a, 0x11, 0xbb, 0xa3, 0x31, 0xbf, 0xa9, 0xe7, 0x8e, 0x72, 0xc7, 0x25, 0x92, 0x8f, 0xd8,
|
0x37, 0x65, 0x21, 0x47, 0x8f, 0x21, 0xed, 0xd3, 0x19, 0x09, 0xd8, 0x5d, 0x29, 0x71, 0x9c, 0x38,
|
||||||
0x1d, 0xe1, 0x37, 0xa8, 0x09, 0xd5, 0x2b, 0xce, 0xe9, 0xdc, 0xf3, 0x3d, 0x41, 0x13, 0x26, 0xea,
|
0xc9, 0xe2, 0x94, 0x4f, 0x67, 0x98, 0xdd, 0x21, 0x1d, 0x0a, 0x37, 0x8c, 0x91, 0xb1, 0x33, 0x71,
|
||||||
0x4f, 0x8e, 0x72, 0xc7, 0x1b, 0xa4, 0x7c, 0xc5, 0x79, 0x5f, 0x62, 0x0e, 0x13, 0xe8, 0x10, 0x60,
|
0x38, 0x09, 0x29, 0x2f, 0x3d, 0x3a, 0x4e, 0x9c, 0x24, 0x71, 0xee, 0x86, 0xb1, 0xa6, 0xc0, 0xba,
|
||||||
0x32, 0x17, 0xbf, 0x6b, 0x51, 0x7d, 0xe3, 0x28, 0x77, 0xbc, 0x45, 0x4a, 0x12, 0x51, 0x0a, 0xf4,
|
0x94, 0xa3, 0x23, 0x80, 0xc1, 0x98, 0xff, 0xa0, 0x44, 0xa5, 0xe4, 0x71, 0xe2, 0x64, 0x13, 0x67,
|
||||||
0x0d, 0xd4, 0x84, 0xe7, 0xf3, 0x70, 0x21, 0x68, 0xc2, 0x27, 0x61, 0xe0, 0x26, 0xf5, 0x4d, 0xa5,
|
0x05, 0x22, 0x15, 0xe8, 0x33, 0xd8, 0xe2, 0xce, 0x84, 0x79, 0x53, 0x4e, 0x42, 0x36, 0xf0, 0x5c,
|
||||||
0xd9, 0x4e, 0x61, 0x47, 0xa3, 0xe8, 0x04, 0x76, 0xc3, 0x85, 0x98, 0x86, 0x5e, 0x30, 0xa5, 0x93,
|
0x3b, 0x2c, 0x6d, 0x48, 0x4d, 0x31, 0x82, 0xbb, 0x0a, 0x45, 0xa7, 0xb0, 0xe3, 0x4d, 0xf9, 0xd0,
|
||||||
0x19, 0x0b, 0x02, 0x3e, 0xa7, 0x9e, 0x5b, 0xdf, 0x52, 0x1e, 0x77, 0x32, 0xaa, 0xa3, 0x19, 0xd3,
|
0x73, 0xdc, 0x21, 0x19, 0x8c, 0xa8, 0xeb, 0xb2, 0x31, 0x71, 0xec, 0xd2, 0xa6, 0xf4, 0xb8, 0x1d,
|
||||||
0x6d, 0xfe, 0x06, 0xb5, 0x65, 0x18, 0x49, 0x14, 0x06, 0x09, 0x47, 0x07, 0x50, 0x94, 0x71, 0xcc,
|
0x53, 0x35, 0xc5, 0x98, 0xb6, 0xfe, 0x3d, 0x6c, 0xcd, 0xc3, 0x08, 0x7d, 0xcf, 0x0d, 0x19, 0x3a,
|
||||||
0x58, 0x32, 0x53, 0x81, 0x54, 0x88, 0x8c, 0xeb, 0x8c, 0x25, 0x33, 0xf4, 0x1c, 0x4a, 0x51, 0xcc,
|
0x80, 0x8c, 0x88, 0x63, 0x44, 0xc3, 0x91, 0x0c, 0x24, 0x8f, 0x45, 0x5c, 0xe7, 0x34, 0x1c, 0xa1,
|
||||||
0xa9, 0xe7, 0xb3, 0x29, 0x57, 0x51, 0x54, 0x48, 0x31, 0x8a, 0xb9, 0x29, 0xd7, 0xe8, 0x15, 0x94,
|
0x43, 0xc8, 0xfa, 0x01, 0x23, 0xce, 0x84, 0x0e, 0x99, 0x8c, 0x22, 0x8f, 0x33, 0x7e, 0xc0, 0x4c,
|
||||||
0x23, 0x6d, 0x8a, 0xf2, 0x38, 0x56, 0x31, 0x94, 0x08, 0xa4, 0x10, 0x8e, 0xe3, 0xe6, 0x0f, 0x50,
|
0xf1, 0x8d, 0x9e, 0x41, 0xce, 0x57, 0xa6, 0x08, 0x0b, 0x02, 0x19, 0x43, 0x16, 0x43, 0x04, 0x19,
|
||||||
0x23, 0x32, 0x97, 0x3d, 0xce, 0xb3, 0x9c, 0x21, 0xd8, 0x74, 0x79, 0x22, 0x52, 0x3f, 0xea, 0x5b,
|
0x41, 0xa0, 0x7f, 0x05, 0x5b, 0x58, 0xe4, 0xb2, 0xc1, 0x58, 0x9c, 0x33, 0x04, 0x1b, 0x36, 0x0b,
|
||||||
0xe6, 0x91, 0xf9, 0xeb, 0x89, 0xca, 0x33, 0x5f, 0xe6, 0xa8, 0xe9, 0x82, 0xb1, 0xda, 0x9f, 0x1e,
|
0x79, 0xe4, 0x47, 0xfe, 0x17, 0x79, 0xa4, 0x93, 0xe5, 0x44, 0xa5, 0xe8, 0x44, 0xe4, 0x48, 0xb7,
|
||||||
0xf6, 0x18, 0x0c, 0x79, 0x3f, 0x32, 0x5c, 0x99, 0x63, 0x5f, 0xee, 0xca, 0xa9, 0x5d, 0xdb, 0x29,
|
0x41, 0x5b, 0xac, 0x8f, 0x36, 0x7b, 0x02, 0x9a, 0x38, 0x1f, 0x11, 0xae, 0xc8, 0xf1, 0x44, 0xac,
|
||||||
0xde, 0xe3, 0x7c, 0x90, 0x30, 0x81, 0xbe, 0xd6, 0x29, 0xa4, 0xf3, 0x70, 0x72, 0x4d, 0x5d, 0x3e,
|
0x4a, 0xc8, 0x55, 0xc5, 0x08, 0x6f, 0x30, 0xd6, 0x0a, 0x29, 0x47, 0x2f, 0x54, 0x0a, 0xc9, 0xd8,
|
||||||
0x67, 0x77, 0xa9, 0xf9, 0xaa, 0x84, 0xfb, 0xe1, 0xe4, 0xba, 0x2b, 0xc1, 0xe6, 0xaf, 0x80, 0x1c,
|
0x1b, 0xdc, 0x12, 0x9b, 0x8d, 0xe9, 0x2c, 0x32, 0x5f, 0x10, 0x70, 0xd3, 0x1b, 0xdc, 0xd6, 0x05,
|
||||||
0x1e, 0xb8, 0xa3, 0x50, 0xf9, 0xca, 0x0e, 0xfa, 0x1a, 0x2a, 0x59, 0x70, 0x6b, 0x89, 0xc9, 0x02,
|
0xa8, 0x7f, 0x07, 0xa8, 0xcb, 0x5c, 0xbb, 0xe7, 0x49, 0x5f, 0xf1, 0x46, 0x9f, 0x43, 0x3e, 0x0e,
|
||||||
0x56, 0xc9, 0x69, 0xc2, 0x96, 0x2a, 0x15, 0x65, 0xb6, 0xdc, 0xaa, 0x9c, 0xcc, 0x03, 0x59, 0x2f,
|
0x6e, 0x29, 0x31, 0x71, 0xc0, 0x32, 0x39, 0x3a, 0x6c, 0xca, 0x52, 0x91, 0x66, 0x73, 0x95, 0xfc,
|
||||||
0xda, 0x8c, 0xa6, 0x9a, 0x14, 0x76, 0xef, 0x19, 0x4f, 0xa3, 0x68, 0x80, 0x4c, 0xa3, 0x4e, 0x6b,
|
0xe9, 0xd8, 0x15, 0xf5, 0xa2, 0xcc, 0x28, 0x4a, 0x27, 0xb0, 0xb3, 0x62, 0x3c, 0x8a, 0xa2, 0x0c,
|
||||||
0x6e, 0x99, 0x56, 0xb5, 0x46, 0xdf, 0x42, 0xe1, 0x8a, 0x79, 0xf3, 0x45, 0x9c, 0x19, 0x46, 0x27,
|
0x22, 0x8d, 0x2a, 0xad, 0x89, 0x79, 0x5a, 0xe5, 0x37, 0x7a, 0x05, 0xe9, 0x1b, 0xea, 0x8c, 0xa7,
|
||||||
0xcb, 0x8a, 0x3c, 0xe9, 0x69, 0x86, 0x64, 0x92, 0xe6, 0x9f, 0x05, 0x28, 0xa4, 0x20, 0x6a, 0xc1,
|
0x41, 0x6c, 0x18, 0x9d, 0xce, 0x2b, 0xf2, 0xb4, 0xa1, 0x18, 0x1c, 0x4b, 0xf4, 0x9f, 0xd3, 0x90,
|
||||||
0xe6, 0x24, 0x74, 0xb5, 0xc5, 0xed, 0xd6, 0xcb, 0xcf, 0xb7, 0x65, 0xbf, 0x9d, 0xd0, 0xe5, 0x44,
|
0x8e, 0x40, 0x54, 0x81, 0x8d, 0x81, 0x67, 0x2b, 0x8b, 0xc5, 0xca, 0xd3, 0x4f, 0x97, 0xc5, 0xbf,
|
||||||
0x69, 0x51, 0x0b, 0xf6, 0x53, 0x53, 0x34, 0x09, 0x17, 0xf1, 0x84, 0xd3, 0x68, 0x71, 0x79, 0xcd,
|
0x35, 0xcf, 0x66, 0x58, 0x6a, 0x51, 0x05, 0xf6, 0x22, 0x53, 0x24, 0xf4, 0xa6, 0xc1, 0x80, 0x11,
|
||||||
0xef, 0xd2, 0xdb, 0xde, 0x4d, 0x49, 0x47, 0x71, 0x43, 0x45, 0xa1, 0x1f, 0x61, 0x3b, 0x2b, 0xb5,
|
0x7f, 0x7a, 0x7d, 0xcb, 0x66, 0xd1, 0x69, 0xef, 0x44, 0x64, 0x57, 0x72, 0x1d, 0x49, 0xa1, 0xaf,
|
||||||
0x45, 0xe4, 0x32, 0xc1, 0xd5, 0xdd, 0x97, 0x5b, 0xf5, 0x35, 0x8f, 0x69, 0xc5, 0x8d, 0x15, 0x4f,
|
0xa1, 0x18, 0x97, 0xda, 0xd4, 0xb7, 0x29, 0x67, 0xf2, 0xec, 0x73, 0x95, 0xd2, 0x92, 0xc7, 0xa8,
|
||||||
0xaa, 0x93, 0xf5, 0xa5, 0x2c, 0xab, 0x99, 0x98, 0x4f, 0xf4, 0xed, 0xc9, 0xba, 0xde, 0x24, 0x45,
|
0xe2, 0xfa, 0x92, 0xc7, 0x85, 0xc1, 0xf2, 0xa7, 0x28, 0xab, 0x11, 0x1f, 0x0f, 0xd4, 0xe9, 0x89,
|
||||||
0x09, 0xa8, 0x7b, 0x6b, 0x42, 0x35, 0x0c, 0xbc, 0x30, 0xa0, 0xc9, 0x8c, 0xd1, 0xd6, 0xdb, 0x77,
|
0xba, 0xde, 0xc0, 0x19, 0x01, 0xc8, 0x73, 0xd3, 0xa1, 0xe0, 0xb9, 0x8e, 0xe7, 0x92, 0x70, 0x44,
|
||||||
0xaa, 0x96, 0x2b, 0xa4, 0xac, 0x40, 0x67, 0xc6, 0x5a, 0x6f, 0xdf, 0xc9, 0xd2, 0x53, 0xdd, 0xc3,
|
0x49, 0xe5, 0xf5, 0x1b, 0x59, 0xcb, 0x79, 0x9c, 0x93, 0x60, 0x77, 0x44, 0x2b, 0xaf, 0xdf, 0x88,
|
||||||
0x6f, 0x23, 0x2f, 0xbe, 0xab, 0xe7, 0x8f, 0x72, 0xc7, 0x55, 0xa2, 0x1a, 0x0a, 0x2b, 0x04, 0xed,
|
0xd2, 0x93, 0xdd, 0xc3, 0x3e, 0xfa, 0x4e, 0x30, 0x2b, 0xa5, 0x8e, 0x13, 0x27, 0x05, 0x2c, 0x1b,
|
||||||
0xc1, 0xd6, 0xd5, 0x9c, 0x4d, 0x93, 0x7a, 0x41, 0x51, 0x7a, 0xd1, 0xfc, 0x67, 0x13, 0xca, 0x6b,
|
0xca, 0x90, 0x08, 0xda, 0x85, 0xcd, 0x9b, 0x31, 0x1d, 0x86, 0xa5, 0xb4, 0xa4, 0xd4, 0x87, 0xfe,
|
||||||
0x29, 0x40, 0x15, 0x28, 0x12, 0xec, 0x60, 0x72, 0x8e, 0xbb, 0xc6, 0x17, 0xa8, 0x0e, 0x7b, 0x63,
|
0xe7, 0x06, 0xe4, 0x96, 0x52, 0x80, 0xf2, 0x90, 0xc1, 0x46, 0xd7, 0xc0, 0x97, 0x46, 0x5d, 0xfb,
|
||||||
0xeb, 0x83, 0x65, 0x7f, 0xb4, 0xe8, 0xb0, 0x7d, 0x31, 0xc0, 0xd6, 0x88, 0x9e, 0xb5, 0x9d, 0x33,
|
0x0f, 0x2a, 0xc1, 0x6e, 0xdf, 0xba, 0xb0, 0xda, 0xef, 0x2d, 0xd2, 0xa9, 0x5e, 0xb5, 0x0c, 0xab,
|
||||||
0x23, 0x87, 0x5e, 0x40, 0xdd, 0xb4, 0x3a, 0x36, 0x21, 0xb8, 0x33, 0x5a, 0x72, 0xed, 0x81, 0x3d,
|
0x47, 0xce, 0xab, 0xdd, 0x73, 0x2d, 0x81, 0x9e, 0x40, 0xc9, 0xb4, 0x6a, 0x6d, 0x8c, 0x8d, 0x5a,
|
||||||
0xb6, 0x46, 0xc6, 0x13, 0xf4, 0x0a, 0x9e, 0xf7, 0x4c, 0xab, 0xdd, 0xa7, 0x2b, 0x4d, 0xa7, 0x3f,
|
0x6f, 0xce, 0x55, 0x5b, 0xed, 0xbe, 0xd5, 0xd3, 0x1e, 0xa1, 0x67, 0x70, 0xd8, 0x30, 0xad, 0x6a,
|
||||||
0x3a, 0xa7, 0xf8, 0xd3, 0xd0, 0x24, 0x17, 0xc6, 0xc6, 0x63, 0x82, 0xb3, 0x51, 0xbf, 0x93, 0x59,
|
0x93, 0x2c, 0x34, 0xb5, 0x66, 0xef, 0x92, 0x18, 0x1f, 0x3a, 0x26, 0xbe, 0xd2, 0x92, 0xeb, 0x04,
|
||||||
0xd8, 0x44, 0x07, 0xb0, 0xaf, 0x05, 0x7a, 0x0b, 0x1d, 0xd9, 0x36, 0x75, 0x6c, 0xdb, 0x32, 0xb6,
|
0xe7, 0xbd, 0x66, 0x2d, 0xb6, 0xb0, 0x81, 0x0e, 0x60, 0x4f, 0x09, 0xd4, 0x12, 0xd2, 0x6b, 0xb7,
|
||||||
0xd0, 0x0e, 0x54, 0x4d, 0xeb, 0xbc, 0xdd, 0x37, 0xbb, 0x94, 0xe0, 0x76, 0x7f, 0x60, 0xe4, 0xd1,
|
0x49, 0xb7, 0xdd, 0xb6, 0xb4, 0x4d, 0xb4, 0x0d, 0x05, 0xd3, 0xba, 0xac, 0x36, 0xcd, 0x3a, 0xc1,
|
||||||
0x2e, 0xd4, 0x1e, 0xea, 0x0a, 0xd2, 0x44, 0xa6, 0xb3, 0x2d, 0xd3, 0xb6, 0xe8, 0x39, 0x26, 0x8e,
|
0x46, 0xb5, 0xd9, 0xd2, 0x52, 0x68, 0x07, 0xb6, 0xee, 0xeb, 0xd2, 0xc2, 0x44, 0xac, 0x6b, 0x5b,
|
||||||
0x69, 0x5b, 0x46, 0x11, 0x3d, 0x05, 0x74, 0x9f, 0x3a, 0x1b, 0xb4, 0x3b, 0x46, 0x09, 0xed, 0xc3,
|
0x66, 0xdb, 0x22, 0x97, 0x06, 0xee, 0x9a, 0x6d, 0x4b, 0xcb, 0xa0, 0x7d, 0x40, 0xab, 0xd4, 0x79,
|
||||||
0xce, 0x7d, 0xfc, 0x03, 0xbe, 0x30, 0x40, 0xa6, 0x41, 0x1f, 0x8c, 0xbe, 0xc7, 0x7d, 0xfb, 0x23,
|
0xab, 0x5a, 0xd3, 0xb2, 0x68, 0x0f, 0xb6, 0x57, 0xf1, 0x0b, 0xe3, 0x4a, 0x03, 0x91, 0x06, 0xb5,
|
||||||
0x1d, 0x98, 0x96, 0x39, 0x18, 0x0f, 0x8c, 0x32, 0xda, 0x03, 0xa3, 0x87, 0x31, 0x35, 0x2d, 0x67,
|
0x31, 0xf2, 0xd6, 0x68, 0xb6, 0xdf, 0x93, 0x96, 0x69, 0x99, 0xad, 0x7e, 0x4b, 0xcb, 0xa1, 0x5d,
|
||||||
0xdc, 0xeb, 0x99, 0x1d, 0x13, 0x5b, 0x23, 0xa3, 0xa2, 0x3d, 0x3f, 0x16, 0x78, 0x55, 0x6e, 0xe8,
|
0xd0, 0x1a, 0x86, 0x41, 0x4c, 0xab, 0xdb, 0x6f, 0x34, 0xcc, 0x9a, 0x69, 0x58, 0x3d, 0x2d, 0xaf,
|
||||||
0x9c, 0xb5, 0x2d, 0x0b, 0xf7, 0x69, 0xd7, 0x74, 0xda, 0xef, 0xfb, 0xb8, 0x6b, 0x6c, 0xa3, 0x43,
|
0x3c, 0xaf, 0x0b, 0xbc, 0x20, 0x16, 0xd4, 0xce, 0xab, 0x96, 0x65, 0x34, 0x49, 0xdd, 0xec, 0x56,
|
||||||
0x38, 0x18, 0xe1, 0xc1, 0xd0, 0x26, 0x6d, 0x72, 0x41, 0x33, 0xbe, 0xd7, 0x36, 0xfb, 0x63, 0x82,
|
0xdf, 0x36, 0x8d, 0xba, 0x56, 0x44, 0x47, 0x70, 0xd0, 0x33, 0x5a, 0x9d, 0x36, 0xae, 0xe2, 0x2b,
|
||||||
0x8d, 0x1a, 0x7a, 0x0d, 0x87, 0x04, 0xff, 0x3c, 0x36, 0x09, 0xee, 0x52, 0xcb, 0xee, 0x62, 0xda,
|
0x12, 0xf3, 0x8d, 0xaa, 0xd9, 0xec, 0x63, 0x43, 0xdb, 0x42, 0xcf, 0xe1, 0x08, 0x1b, 0xef, 0xfa,
|
||||||
0xc3, 0xed, 0xd1, 0x98, 0x60, 0x3a, 0x30, 0x1d, 0xc7, 0xb4, 0x7e, 0x32, 0x0c, 0xf4, 0x15, 0x1c,
|
0x26, 0x36, 0xea, 0xc4, 0x6a, 0xd7, 0x0d, 0xd2, 0x30, 0xaa, 0xbd, 0x3e, 0x36, 0x48, 0xcb, 0xec,
|
||||||
0x2d, 0x25, 0x4b, 0x03, 0x0f, 0x54, 0x3b, 0x32, 0xbe, 0xec, 0x3e, 0x2d, 0xfc, 0x69, 0x44, 0x87,
|
0x76, 0x4d, 0xeb, 0x1b, 0x4d, 0x43, 0xff, 0x83, 0xe3, 0xb9, 0x64, 0x6e, 0xe0, 0x9e, 0x6a, 0x5b,
|
||||||
0x18, 0x13, 0x03, 0xa1, 0x06, 0x3c, 0x5d, 0xb9, 0xd7, 0x0e, 0x52, 0xdf, 0xbb, 0x92, 0x1b, 0x62,
|
0xc4, 0x17, 0x9f, 0xa7, 0x65, 0x7c, 0xe8, 0x91, 0x8e, 0x61, 0x60, 0x0d, 0xa1, 0x32, 0xec, 0x2f,
|
||||||
0x32, 0x68, 0x5b, 0xf2, 0x82, 0xef, 0x71, 0x7b, 0xf2, 0xd8, 0x2b, 0xee, 0xe1, 0xb1, 0xf7, 0x9b,
|
0xdc, 0x2b, 0x07, 0x91, 0xef, 0x1d, 0xc1, 0x75, 0x0c, 0xdc, 0xaa, 0x5a, 0xe2, 0x80, 0x57, 0xb8,
|
||||||
0x7f, 0x6d, 0x40, 0xf5, 0x5e, 0xd1, 0xa3, 0x17, 0x50, 0x4a, 0xbc, 0x69, 0xc0, 0x84, 0x6c, 0x65,
|
0x5d, 0xb1, 0xed, 0x05, 0x77, 0x7f, 0xdb, 0x7b, 0xfa, 0x2f, 0x49, 0x28, 0xac, 0x14, 0x3d, 0x7a,
|
||||||
0xdd, 0xe5, 0x2b, 0x40, 0x3d, 0x00, 0x33, 0xe6, 0x05, 0x7a, 0xbc, 0xe8, 0x6e, 0x2b, 0x29, 0x44,
|
0x02, 0xd9, 0xd0, 0x19, 0xba, 0x94, 0x8b, 0x56, 0x56, 0x5d, 0xbe, 0x00, 0xe4, 0x05, 0x30, 0xa2,
|
||||||
0x0d, 0x97, 0x67, 0x50, 0x90, 0x3d, 0x23, 0x67, 0xf9, 0x86, 0x6a, 0x90, 0xbc, 0x5c, 0x9a, 0xae,
|
0x8e, 0xab, 0xc6, 0x8b, 0xea, 0xb6, 0xac, 0x44, 0xe4, 0x70, 0x79, 0x0c, 0x69, 0xd1, 0x33, 0x62,
|
||||||
0xb4, 0x2a, 0xe7, 0x57, 0x22, 0x98, 0x1f, 0xa9, 0xde, 0xa9, 0x92, 0x15, 0x80, 0xbe, 0x84, 0xaa,
|
0x96, 0x27, 0x65, 0x83, 0xa4, 0xc4, 0xa7, 0x69, 0x0b, 0xab, 0x62, 0x7e, 0x85, 0x9c, 0x4e, 0x7c,
|
||||||
0xcf, 0x93, 0x84, 0x4d, 0x39, 0xd5, 0xf5, 0x0f, 0x4a, 0x51, 0x49, 0xc1, 0x9e, 0xc4, 0xa4, 0x28,
|
0xd9, 0x3b, 0x05, 0xbc, 0x00, 0xd0, 0x7f, 0xa1, 0x30, 0x61, 0x61, 0x48, 0x87, 0x8c, 0xa8, 0xfa,
|
||||||
0xeb, 0x5f, 0x2d, 0xda, 0xd2, 0xa2, 0x14, 0xd4, 0xa2, 0x87, 0xe3, 0x53, 0xb0, 0xb4, 0xcd, 0xd6,
|
0x07, 0xa9, 0xc8, 0x47, 0x60, 0x43, 0x60, 0x42, 0x14, 0xf7, 0xaf, 0x12, 0x6d, 0x2a, 0x51, 0x04,
|
||||||
0xc7, 0xa7, 0x60, 0xe8, 0x0d, 0xec, 0xe8, 0x5e, 0xf6, 0x02, 0xcf, 0x5f, 0xf8, 0xba, 0xa7, 0x0b,
|
0x2a, 0xd1, 0xfd, 0xf1, 0xc9, 0x69, 0xd4, 0x66, 0xcb, 0xe3, 0x93, 0x53, 0xf4, 0x12, 0xb6, 0x55,
|
||||||
0xea, 0xc8, 0x35, 0xd5, 0xd3, 0x1a, 0x57, 0xad, 0x7d, 0x00, 0xc5, 0x4b, 0x96, 0x70, 0x39, 0xb9,
|
0x2f, 0x3b, 0xae, 0x33, 0x99, 0x4e, 0x54, 0x4f, 0xa7, 0xe5, 0x96, 0xb7, 0x64, 0x4f, 0x2b, 0x5c,
|
||||||
0xeb, 0x45, 0x65, 0xac, 0x20, 0xd7, 0x3d, 0xae, 0x1e, 0x21, 0x39, 0xcf, 0x63, 0x39, 0x4d, 0x4a,
|
0xb6, 0xf6, 0x01, 0x64, 0xae, 0x69, 0xc8, 0xc4, 0xe4, 0x2e, 0x65, 0xa4, 0xb1, 0xb4, 0xf8, 0x6e,
|
||||||
0x9a, 0xba, 0xe2, 0x9c, 0xc8, 0x3c, 0x2e, 0x3d, 0xb0, 0xdb, 0x95, 0x87, 0xf2, 0x9a, 0x07, 0x8d,
|
0x30, 0x79, 0x09, 0x89, 0x79, 0x1e, 0x88, 0x69, 0x92, 0x55, 0xd4, 0x0d, 0x63, 0x58, 0xe4, 0x71,
|
||||||
0x2b, 0x0f, 0x6f, 0x60, 0x87, 0xdf, 0x8a, 0x98, 0xd1, 0x30, 0x62, 0x37, 0x0b, 0x4e, 0x5d, 0x26,
|
0xee, 0x81, 0x7e, 0x5c, 0x78, 0xc8, 0x2d, 0x79, 0x50, 0xb8, 0xf4, 0xf0, 0x12, 0xb6, 0xd9, 0x47,
|
||||||
0x58, 0xbd, 0xa2, 0x92, 0x5b, 0x53, 0x84, 0xad, 0xf0, 0x2e, 0x13, 0xac, 0xf5, 0x6f, 0x0e, 0xf2,
|
0x1e, 0x50, 0xe2, 0xf9, 0xf4, 0x6e, 0xca, 0x88, 0x4d, 0x39, 0x2d, 0xe5, 0x65, 0x72, 0xb7, 0x24,
|
||||||
0x6a, 0x2c, 0xc7, 0xa8, 0x0b, 0x65, 0x39, 0xa6, 0xd3, 0x97, 0x11, 0x1d, 0xac, 0x0d, 0xb2, 0xfb,
|
0xd1, 0x96, 0x78, 0x9d, 0x72, 0xaa, 0x3f, 0x81, 0x32, 0x66, 0x21, 0xe3, 0x2d, 0x27, 0x0c, 0x1d,
|
||||||
0x8f, 0x7e, 0xa3, 0xf1, 0x18, 0x95, 0x4e, 0xf5, 0x0f, 0x60, 0xe0, 0x44, 0x78, 0xbe, 0x9c, 0x78,
|
0xcf, 0xad, 0x79, 0x2e, 0x0f, 0xbc, 0x71, 0x74, 0x01, 0xe8, 0x47, 0x70, 0xb8, 0x96, 0x55, 0x13,
|
||||||
0xe9, 0xbb, 0x85, 0xd6, 0xf5, 0x0f, 0x1e, 0xc3, 0xc6, 0xf3, 0x47, 0xb9, 0xd4, 0x58, 0x5f, 0x1f,
|
0x5c, 0x2c, 0x7e, 0x37, 0x65, 0xc1, 0x6c, 0xfd, 0xe2, 0x0b, 0x38, 0x5c, 0xcb, 0x46, 0xe3, 0xff,
|
||||||
0x29, 0x7d, 0x39, 0xd0, 0xe1, 0x9a, 0xf6, 0xf3, 0xe7, 0xaa, 0xf1, 0xf2, 0xff, 0x68, 0x6d, 0xed,
|
0x15, 0x6c, 0xba, 0x9e, 0xcd, 0xc2, 0x52, 0xe2, 0x38, 0x79, 0x92, 0xab, 0xec, 0x2f, 0xcd, 0x4d,
|
||||||
0xfd, 0x77, 0xbf, 0x9c, 0x4e, 0x3d, 0x31, 0x5b, 0x5c, 0x9e, 0x4c, 0x42, 0xff, 0x74, 0xee, 0x4d,
|
0xcb, 0xb3, 0xd9, 0xb9, 0x13, 0x72, 0x2f, 0x98, 0x61, 0x25, 0xd2, 0x7f, 0x4f, 0x40, 0x6e, 0x09,
|
||||||
0x67, 0x22, 0xf0, 0x82, 0x69, 0xc0, 0xc5, 0x1f, 0x61, 0x7c, 0x7d, 0x3a, 0x0f, 0xdc, 0x53, 0xf5,
|
0x46, 0xfb, 0x90, 0x8a, 0x66, 0xb4, 0x2a, 0xaa, 0xe8, 0x0b, 0xbd, 0x80, 0xe2, 0x98, 0x86, 0x9c,
|
||||||
0x7a, 0x9d, 0x2e, 0xcd, 0x5c, 0xe6, 0xd5, 0x1f, 0x9f, 0xef, 0xff, 0x0b, 0x00, 0x00, 0xff, 0xff,
|
0x88, 0x91, 0x4d, 0xc4, 0x21, 0x45, 0xf7, 0xdd, 0x3d, 0x14, 0x7d, 0x09, 0x8f, 0x3d, 0x3e, 0x62,
|
||||||
0x89, 0x21, 0x7b, 0xbd, 0x28, 0x09, 0x00, 0x00,
|
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.
|
// 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
|
//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.
|
//manually. This can be used for things like rebalancing, and atomic swaps.
|
||||||
SendToRoute(ctx context.Context, in *SendToRouteRequest, opts ...grpc.CallOption) (*SendToRouteResponse, error)
|
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 {
|
type routerClient struct {
|
||||||
@ -897,6 +1194,24 @@ func (c *routerClient) SendToRoute(ctx context.Context, in *SendToRouteRequest,
|
|||||||
return out, nil
|
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.
|
// RouterServer is the server API for Router service.
|
||||||
type RouterServer interface {
|
type RouterServer interface {
|
||||||
//*
|
//*
|
||||||
@ -915,6 +1230,14 @@ type RouterServer interface {
|
|||||||
//differs from SendPayment in that it allows users to specify a full route
|
//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.
|
//manually. This can be used for things like rebalancing, and atomic swaps.
|
||||||
SendToRoute(context.Context, *SendToRouteRequest) (*SendToRouteResponse, error)
|
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) {
|
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)
|
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{
|
var _Router_serviceDesc = grpc.ServiceDesc{
|
||||||
ServiceName: "routerrpc.Router",
|
ServiceName: "routerrpc.Router",
|
||||||
HandlerType: (*RouterServer)(nil),
|
HandlerType: (*RouterServer)(nil),
|
||||||
@ -991,6 +1350,14 @@ var _Router_serviceDesc = grpc.ServiceDesc{
|
|||||||
MethodName: "SendToRoute",
|
MethodName: "SendToRoute",
|
||||||
Handler: _Router_SendToRoute_Handler,
|
Handler: _Router_SendToRoute_Handler,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
MethodName: "ResetMissionControl",
|
||||||
|
Handler: _Router_ResetMissionControl_Handler,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MethodName: "QueryMissionControl",
|
||||||
|
Handler: _Router_QueryMissionControl_Handler,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Streams: []grpc.StreamDesc{},
|
Streams: []grpc.StreamDesc{},
|
||||||
Metadata: "routerrpc/router.proto",
|
Metadata: "routerrpc/router.proto",
|
||||||
|
@ -245,6 +245,46 @@ message ChannelUpdate {
|
|||||||
*/
|
*/
|
||||||
bytes extra_opaque_data = 12;
|
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 {
|
service Router {
|
||||||
/**
|
/**
|
||||||
@ -268,4 +308,16 @@ service Router {
|
|||||||
manually. This can be used for things like rebalancing, and atomic swaps.
|
manually. This can be used for things like rebalancing, and atomic swaps.
|
||||||
*/
|
*/
|
||||||
rpc SendToRoute(SendToRouteRequest) returns (SendToRouteResponse);
|
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,
|
FindRoute func(source, target route.Vertex,
|
||||||
amt lnwire.MilliSatoshi, restrictions *routing.RestrictParams,
|
amt lnwire.MilliSatoshi, restrictions *routing.RestrictParams,
|
||||||
finalExpiry ...uint16) (*route.Route, error)
|
finalExpiry ...uint16) (*route.Route, error)
|
||||||
|
|
||||||
|
MissionControl *routing.MissionControl
|
||||||
}
|
}
|
||||||
|
|
||||||
// QueryRoutes attempts to query the daemons' Channel Router for a possible
|
// 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{
|
restrictions := &routing.RestrictParams{
|
||||||
FeeLimit: feeLimit,
|
FeeLimit: feeLimit,
|
||||||
IgnoredNodes: ignoredNodes,
|
ProbabilitySource: func(node route.Vertex,
|
||||||
IgnoredEdges: ignoredEdges,
|
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
|
// Query the channel router for a possible path to the destination that
|
||||||
|
@ -39,6 +39,11 @@ func TestQueryRoutes(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ignoredEdge := routing.EdgeLocator{
|
||||||
|
ChannelID: 555,
|
||||||
|
Direction: 1,
|
||||||
|
}
|
||||||
|
|
||||||
request := &lnrpc.QueryRoutesRequest{
|
request := &lnrpc.QueryRoutesRequest{
|
||||||
PubKey: destKey,
|
PubKey: destKey,
|
||||||
Amt: 100000,
|
Amt: 100000,
|
||||||
@ -75,22 +80,22 @@ func TestQueryRoutes(t *testing.T) {
|
|||||||
t.Fatal("unexpected fee limit")
|
t.Fatal("unexpected fee limit")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(restrictions.IgnoredEdges) != 1 {
|
if restrictions.ProbabilitySource(route.Vertex{},
|
||||||
t.Fatal("unexpected ignored edges map size")
|
ignoredEdge, 0,
|
||||||
|
) != 0 {
|
||||||
|
t.Fatal("expecting 0% probability for ignored edge")
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := restrictions.IgnoredEdges[routing.EdgeLocator{
|
if restrictions.ProbabilitySource(ignoreNodeVertex,
|
||||||
ChannelID: 555, Direction: 1,
|
routing.EdgeLocator{}, 0,
|
||||||
}]; !ok {
|
) != 0 {
|
||||||
t.Fatal("unexpected ignored edge")
|
t.Fatal("expecting 0% probability for ignored node")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(restrictions.IgnoredNodes) != 1 {
|
if restrictions.ProbabilitySource(route.Vertex{},
|
||||||
t.Fatal("unexpected ignored nodes map size")
|
routing.EdgeLocator{}, 0,
|
||||||
}
|
) != 1 {
|
||||||
|
t.Fatal("expecting 100% probability")
|
||||||
if _, ok := restrictions.IgnoredNodes[ignoreNodeVertex]; !ok {
|
|
||||||
t.Fatal("unexpected ignored node")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
hops := []*route.Hop{{}}
|
hops := []*route.Hop{{}}
|
||||||
|
@ -59,6 +59,14 @@ var (
|
|||||||
Entity: "offchain",
|
Entity: "offchain",
|
||||||
Action: "read",
|
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
|
// DefaultRouterMacFilename is the default name of the router macaroon
|
||||||
@ -439,3 +447,56 @@ func marshallChannelUpdate(update *lnwire.ChannelUpdate) *ChannelUpdate {
|
|||||||
ExtraOpaqueData: update.ExtraOpaqueData,
|
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"
|
||||||
"github.com/lightningnetwork/lnd/chanbackup"
|
"github.com/lightningnetwork/lnd/chanbackup"
|
||||||
"github.com/lightningnetwork/lnd/lnrpc"
|
"github.com/lightningnetwork/lnd/lnrpc"
|
||||||
|
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
|
||||||
"github.com/lightningnetwork/lnd/lntest"
|
"github.com/lightningnetwork/lnd/lntest"
|
||||||
"github.com/lightningnetwork/lnd/lnwire"
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
@ -8382,8 +8383,14 @@ out:
|
|||||||
// failed payment.
|
// failed payment.
|
||||||
shutdownAndAssert(net, t, carol)
|
shutdownAndAssert(net, t, carol)
|
||||||
|
|
||||||
// TODO(roasbeef): mission control
|
// Reset mission control to forget the temporary channel failure above.
|
||||||
time.Sleep(time.Second * 5)
|
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{
|
sendReq = &lnrpc.SendRequest{
|
||||||
PaymentRequest: carolInvoice.PaymentRequest,
|
PaymentRequest: carolInvoice.PaymentRequest,
|
||||||
|
@ -28,6 +28,7 @@ import (
|
|||||||
"github.com/lightningnetwork/lnd/chanbackup"
|
"github.com/lightningnetwork/lnd/chanbackup"
|
||||||
"github.com/lightningnetwork/lnd/lnrpc"
|
"github.com/lightningnetwork/lnd/lnrpc"
|
||||||
"github.com/lightningnetwork/lnd/lnrpc/invoicesrpc"
|
"github.com/lightningnetwork/lnd/lnrpc/invoicesrpc"
|
||||||
|
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
|
||||||
"github.com/lightningnetwork/lnd/macaroons"
|
"github.com/lightningnetwork/lnd/macaroons"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -248,6 +249,10 @@ type HarnessNode struct {
|
|||||||
lnrpc.WalletUnlockerClient
|
lnrpc.WalletUnlockerClient
|
||||||
|
|
||||||
invoicesrpc.InvoicesClient
|
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.
|
// 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.
|
// HarnessNode directly for normal rpc operations.
|
||||||
hn.LightningClient = lnrpc.NewLightningClient(conn)
|
hn.LightningClient = lnrpc.NewLightningClient(conn)
|
||||||
hn.InvoicesClient = invoicesrpc.NewInvoicesClient(conn)
|
hn.InvoicesClient = invoicesrpc.NewInvoicesClient(conn)
|
||||||
|
hn.RouterClient = routerrpc.NewRouterClient(conn)
|
||||||
|
|
||||||
// Set the harness node's pubkey to what the node claims in GetInfo.
|
// Set the harness node's pubkey to what the node claims in GetInfo.
|
||||||
err := hn.FetchNodeInfo()
|
err := hn.FetchNodeInfo()
|
||||||
|
@ -25,8 +25,14 @@ type nodeWithDist struct {
|
|||||||
// node. This value does not include the final cltv.
|
// node. This value does not include the final cltv.
|
||||||
incomingCltv uint32
|
incomingCltv uint32
|
||||||
|
|
||||||
// fee is the fee that this node is charging for forwarding.
|
// probability is the probability that from this node onward the route
|
||||||
fee lnwire.MilliSatoshi
|
// 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
|
// distanceHeap is a min-distance heap that's used within our path finding
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package routing
|
package routing
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"math"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -13,48 +14,23 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// vertexDecay is the decay period of colored vertexes added to
|
// DefaultPenaltyHalfLife is the default half-life duration. The
|
||||||
// MissionControl. Once vertexDecay passes after an entry has been
|
// half-life duration defines after how much time a penalized node or
|
||||||
// added to the prune view, it is garbage collected. This value is
|
// channel is back at 50% probability.
|
||||||
// larger than edgeDecay as an edge failure typical indicates an
|
DefaultPenaltyHalfLife = time.Hour
|
||||||
// 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)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// MissionControl contains state which summarizes the past attempts of HTLC
|
// MissionControl contains state which summarizes the past attempts of HTLC
|
||||||
// routing by external callers when sending payments throughout the network.
|
// routing by external callers when sending payments throughout the network. It
|
||||||
// MissionControl remembers the outcome of these past routing attempts (success
|
// acts as a shared memory during routing attempts with the goal to optimize the
|
||||||
// and failure), and is able to provide hints/guidance to future HTLC routing
|
// payment attempt success rate.
|
||||||
// attempts. MissionControl maintains a decaying network view of the
|
//
|
||||||
// edges/vertexes that should be marked as "pruned" during path finding. This
|
// Failed payment attempts are reported to mission control. These reports are
|
||||||
// graph view acts as a shared memory during HTLC payment routing attempts.
|
// used to track the time of the last node or channel level failure. The time
|
||||||
// With each execution, if an error is encountered, based on the type of error
|
// since the last failure is used to estimate a success probability that is fed
|
||||||
// and the location of the error within the route, an edge or vertex is added
|
// into the path finding process for subsequent payment attempts.
|
||||||
// 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.
|
|
||||||
type MissionControl struct {
|
type MissionControl struct {
|
||||||
// failedEdges maps a short channel ID to be pruned, to the time that
|
history map[route.Vertex]*nodeHistory
|
||||||
// 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
|
|
||||||
|
|
||||||
graph *channeldb.ChannelGraph
|
graph *channeldb.ChannelGraph
|
||||||
|
|
||||||
@ -62,6 +38,12 @@ type MissionControl struct {
|
|||||||
|
|
||||||
queryBandwidth func(*channeldb.ChannelEdgeInfo) lnwire.MilliSatoshi
|
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
|
sync.Mutex
|
||||||
|
|
||||||
// TODO(roasbeef): further counters, if vertex continually unavailable,
|
// TODO(roasbeef): further counters, if vertex continually unavailable,
|
||||||
@ -74,83 +56,113 @@ type MissionControl struct {
|
|||||||
// PaymentSessionSource interface.
|
// PaymentSessionSource interface.
|
||||||
var _ PaymentSessionSource = (*MissionControl)(nil)
|
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
|
// TODO(roasbeef): persist memory
|
||||||
func NewMissionControl(g *channeldb.ChannelGraph, selfNode *channeldb.LightningNode,
|
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{
|
return &MissionControl{
|
||||||
failedEdges: make(map[EdgeLocator]time.Time),
|
history: make(map[route.Vertex]*nodeHistory),
|
||||||
failedVertexes: make(map[route.Vertex]time.Time),
|
|
||||||
selfNode: selfNode,
|
selfNode: selfNode,
|
||||||
queryBandwidth: qb,
|
queryBandwidth: qb,
|
||||||
graph: g,
|
graph: g,
|
||||||
}
|
now: time.Now,
|
||||||
}
|
cfg: cfg,
|
||||||
|
|
||||||
// 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,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -161,8 +173,6 @@ func (m *MissionControl) graphPruneView() graphPruneView {
|
|||||||
func (m *MissionControl) NewPaymentSession(routeHints [][]zpay32.HopHint,
|
func (m *MissionControl) NewPaymentSession(routeHints [][]zpay32.HopHint,
|
||||||
target route.Vertex) (PaymentSession, error) {
|
target route.Vertex) (PaymentSession, error) {
|
||||||
|
|
||||||
viewSnapshot := m.graphPruneView()
|
|
||||||
|
|
||||||
edges := make(map[route.Vertex][]*channeldb.ChannelEdgePolicy)
|
edges := make(map[route.Vertex][]*channeldb.ChannelEdgePolicy)
|
||||||
|
|
||||||
// Traverse through all of the available hop hints and include them in
|
// 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{
|
return &paymentSession{
|
||||||
pruneViewSnapshot: viewSnapshot,
|
|
||||||
additionalEdges: edges,
|
additionalEdges: edges,
|
||||||
bandwidthHints: bandwidthHints,
|
bandwidthHints: bandwidthHints,
|
||||||
errFailedPolicyChans: make(map[EdgeLocator]struct{}),
|
errFailedPolicyChans: make(map[nodeChannel]struct{}),
|
||||||
mc: m,
|
mc: m,
|
||||||
pathFinder: findPath,
|
pathFinder: findPath,
|
||||||
}, nil
|
}, nil
|
||||||
@ -239,8 +248,7 @@ func (m *MissionControl) NewPaymentSession(routeHints [][]zpay32.HopHint,
|
|||||||
// used for failure reporting to missioncontrol.
|
// used for failure reporting to missioncontrol.
|
||||||
func (m *MissionControl) NewPaymentSessionForRoute(preBuiltRoute *route.Route) PaymentSession {
|
func (m *MissionControl) NewPaymentSessionForRoute(preBuiltRoute *route.Route) PaymentSession {
|
||||||
return &paymentSession{
|
return &paymentSession{
|
||||||
pruneViewSnapshot: m.graphPruneView(),
|
errFailedPolicyChans: make(map[nodeChannel]struct{}),
|
||||||
errFailedPolicyChans: make(map[EdgeLocator]struct{}),
|
|
||||||
mc: m,
|
mc: m,
|
||||||
preBuiltRoute: preBuiltRoute,
|
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.
|
// missioncontrol for resumed payment we don't want to make more attempts for.
|
||||||
func (m *MissionControl) NewPaymentSessionEmpty() PaymentSession {
|
func (m *MissionControl) NewPaymentSessionEmpty() PaymentSession {
|
||||||
return &paymentSession{
|
return &paymentSession{
|
||||||
pruneViewSnapshot: m.graphPruneView(),
|
errFailedPolicyChans: make(map[nodeChannel]struct{}),
|
||||||
errFailedPolicyChans: make(map[EdgeLocator]struct{}),
|
|
||||||
mc: m,
|
mc: m,
|
||||||
preBuiltRoute: &route.Route{},
|
preBuiltRoute: &route.Route{},
|
||||||
preBuiltRouteTried: true,
|
preBuiltRouteTried: true,
|
||||||
@ -298,7 +305,174 @@ func generateBandwidthHints(sourceNode *channeldb.LightningNode,
|
|||||||
// if no payment attempts have been made.
|
// if no payment attempts have been made.
|
||||||
func (m *MissionControl) ResetHistory() {
|
func (m *MissionControl) ResetHistory() {
|
||||||
m.Lock()
|
m.Lock()
|
||||||
m.failedEdges = make(map[EdgeLocator]time.Time)
|
defer m.Unlock()
|
||||||
m.failedVertexes = make(map[route.Vertex]time.Time)
|
|
||||||
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) 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 {
|
type mockPayer struct {
|
||||||
sendResult chan error
|
sendResult chan error
|
||||||
|
@ -40,6 +40,22 @@ type pathFinder = func(g *graphParams, r *RestrictParams,
|
|||||||
source, target route.Vertex, amt lnwire.MilliSatoshi) (
|
source, target route.Vertex, amt lnwire.MilliSatoshi) (
|
||||||
[]*channeldb.ChannelEdgePolicy, error)
|
[]*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
|
// edgePolicyWithSource is a helper struct to keep track of the source node
|
||||||
// of a channel edge. ChannelEdgePolicy only contains to destination node
|
// of a channel edge. ChannelEdgePolicy only contains to destination node
|
||||||
// of the edge.
|
// of the edge.
|
||||||
@ -228,13 +244,10 @@ type graphParams struct {
|
|||||||
// RestrictParams wraps the set of restrictions passed to findPath that the
|
// RestrictParams wraps the set of restrictions passed to findPath that the
|
||||||
// found path must adhere to.
|
// found path must adhere to.
|
||||||
type RestrictParams struct {
|
type RestrictParams struct {
|
||||||
// IgnoredNodes is an optional set of nodes that should be ignored if
|
// ProbabilitySource is a callback that is expected to return the
|
||||||
// encountered during path finding.
|
// success probability of traversing the channel from the node.
|
||||||
IgnoredNodes map[route.Vertex]struct{}
|
ProbabilitySource func(route.Vertex, EdgeLocator,
|
||||||
|
lnwire.MilliSatoshi) float64
|
||||||
// IgnoredEdges is an optional set of edges that should be ignored if
|
|
||||||
// encountered during path finding.
|
|
||||||
IgnoredEdges map[EdgeLocator]struct{}
|
|
||||||
|
|
||||||
// FeeLimit is a maximum fee amount allowed to be used on the path from
|
// FeeLimit is a maximum fee amount allowed to be used on the path from
|
||||||
// the source to the target.
|
// the source to the target.
|
||||||
@ -248,6 +261,16 @@ type RestrictParams struct {
|
|||||||
// ctlv. After path finding is complete, the caller needs to increase
|
// ctlv. After path finding is complete, the caller needs to increase
|
||||||
// all cltv expiry heights with the required final cltv delta.
|
// all cltv expiry heights with the required final cltv delta.
|
||||||
CltvLimit *uint32
|
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
|
// 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}
|
targetNode := &channeldb.LightningNode{PubKeyBytes: target}
|
||||||
distance[target] = nodeWithDist{
|
distance[target] = nodeWithDist{
|
||||||
dist: 0,
|
dist: 0,
|
||||||
|
weight: 0,
|
||||||
node: targetNode,
|
node: targetNode,
|
||||||
amountToReceive: amt,
|
amountToReceive: amt,
|
||||||
fee: 0,
|
|
||||||
incomingCltv: 0,
|
incomingCltv: 0,
|
||||||
|
probability: 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
// We'll use this map as a series of "next" hop pointers. So to get
|
// 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`.
|
// mapped to within `next`.
|
||||||
next := make(map[route.Vertex]*channeldb.ChannelEdgePolicy)
|
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
|
// processEdge is a helper closure that will be used to make sure edges
|
||||||
// satisfy our specific requirements.
|
// satisfy our specific requirements.
|
||||||
processEdge := func(fromNode *channeldb.LightningNode,
|
processEdge := func(fromNode *channeldb.LightningNode,
|
||||||
@ -380,21 +395,26 @@ func findPath(g *graphParams, r *RestrictParams, source, target route.Vertex,
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// If this vertex or edge has been black listed, then we'll
|
// Calculate amount that the candidate node would have to sent
|
||||||
// skip exploring this edge.
|
// out.
|
||||||
if _, ok := ignoredNodes[fromVertex]; ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
locator := newEdgeLocator(edge)
|
|
||||||
if _, ok := ignoredEdges[*locator]; ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
toNodeDist := distance[toNode]
|
toNodeDist := distance[toNode]
|
||||||
|
|
||||||
amountToSend := toNodeDist.amountToReceive
|
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
|
// If the estimated bandwidth of the channel edge is not able
|
||||||
// to carry the amount that needs to be send, return.
|
// to carry the amount that needs to be send, return.
|
||||||
if bandwidth < amountToSend {
|
if bandwidth < amountToSend {
|
||||||
@ -453,19 +473,39 @@ func findPath(g *graphParams, r *RestrictParams, source, target route.Vertex,
|
|||||||
return
|
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
|
// By adding fromNode in the route, there will be an extra
|
||||||
// weight composed of the fee that this node will charge and
|
// weight composed of the fee that this node will charge and
|
||||||
// the amount that will be locked for timeLockDelta blocks in
|
// the amount that will be locked for timeLockDelta blocks in
|
||||||
// the HTLC that is handed out to fromNode.
|
// the HTLC that is handed out to fromNode.
|
||||||
weight := edgeWeight(amountToReceive, fee, timeLockDelta)
|
weight := edgeWeight(amountToReceive, fee, timeLockDelta)
|
||||||
|
|
||||||
// Compute the tentative distance to this new channel/edge
|
// Compute the tentative weight to this new channel/edge
|
||||||
// which is the distance from our toNode to the target node
|
// which is the weight from our toNode to the target node
|
||||||
// plus the weight of this edge.
|
// 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
|
// Add an extra factor to the weight to take into account the
|
||||||
// best known distance to this node, return.
|
// 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 {
|
if tempDist >= distance[fromVertex].dist {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -483,10 +523,11 @@ func findPath(g *graphParams, r *RestrictParams, source, target route.Vertex,
|
|||||||
// map is populated with this edge.
|
// map is populated with this edge.
|
||||||
distance[fromVertex] = nodeWithDist{
|
distance[fromVertex] = nodeWithDist{
|
||||||
dist: tempDist,
|
dist: tempDist,
|
||||||
|
weight: tempWeight,
|
||||||
node: fromNode,
|
node: fromNode,
|
||||||
amountToReceive: amountToReceive,
|
amountToReceive: amountToReceive,
|
||||||
fee: fee,
|
|
||||||
incomingCltv: incomingCltv,
|
incomingCltv: incomingCltv,
|
||||||
|
probability: probability,
|
||||||
}
|
}
|
||||||
|
|
||||||
next[fromVertex] = edge
|
next[fromVertex] = edge
|
||||||
@ -614,5 +655,53 @@ func findPath(g *graphParams, r *RestrictParams, source, target route.Vertex,
|
|||||||
"too many hops")
|
"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
|
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 (
|
var (
|
||||||
noRestrictions = &RestrictParams{
|
noRestrictions = &RestrictParams{
|
||||||
FeeLimit: noFeeLimit,
|
FeeLimit: noFeeLimit,
|
||||||
|
ProbabilitySource: noProbabilitySource,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -72,6 +73,12 @@ var (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// noProbabilitySource is used in testing to return the same probability 1 for
|
||||||
|
// all edges.
|
||||||
|
func noProbabilitySource(route.Vertex, EdgeLocator, lnwire.MilliSatoshi) float64 {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
// testGraph is the struct which corresponds to the JSON format used to encode
|
// testGraph is the struct which corresponds to the JSON format used to encode
|
||||||
// graphs within the files in the testdata directory.
|
// graphs within the files in the testdata directory.
|
||||||
//
|
//
|
||||||
@ -477,6 +484,16 @@ func createTestGraphFromChannels(testChannels []*testChannel) (*testGraphInstanc
|
|||||||
Index: 0,
|
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
|
// We first insert the existence of the edge between the two
|
||||||
// nodes.
|
// nodes.
|
||||||
edgeInfo := channeldb.ChannelEdgeInfo{
|
edgeInfo := channeldb.ChannelEdgeInfo{
|
||||||
@ -485,10 +502,10 @@ func createTestGraphFromChannels(testChannels []*testChannel) (*testGraphInstanc
|
|||||||
ChannelPoint: *fundingPoint,
|
ChannelPoint: *fundingPoint,
|
||||||
Capacity: testChannel.Capacity,
|
Capacity: testChannel.Capacity,
|
||||||
|
|
||||||
NodeKey1Bytes: aliasMap[testChannel.Node1.Alias],
|
NodeKey1Bytes: node1Vertex,
|
||||||
BitcoinKey1Bytes: aliasMap[testChannel.Node1.Alias],
|
BitcoinKey1Bytes: node1Vertex,
|
||||||
NodeKey2Bytes: aliasMap[testChannel.Node2.Alias],
|
NodeKey2Bytes: node2Vertex,
|
||||||
BitcoinKey2Bytes: aliasMap[testChannel.Node2.Alias],
|
BitcoinKey2Bytes: node2Vertex,
|
||||||
}
|
}
|
||||||
|
|
||||||
err = graph.AddChannelEdge(&edgeInfo)
|
err = graph.AddChannelEdge(&edgeInfo)
|
||||||
@ -510,12 +527,12 @@ func createTestGraphFromChannels(testChannels []*testChannel) (*testGraphInstanc
|
|||||||
MessageFlags: msgFlags,
|
MessageFlags: msgFlags,
|
||||||
ChannelFlags: channelFlags,
|
ChannelFlags: channelFlags,
|
||||||
ChannelID: channelID,
|
ChannelID: channelID,
|
||||||
LastUpdate: testChannel.Node1.LastUpdate,
|
LastUpdate: node1.LastUpdate,
|
||||||
TimeLockDelta: testChannel.Node1.Expiry,
|
TimeLockDelta: node1.Expiry,
|
||||||
MinHTLC: testChannel.Node1.MinHTLC,
|
MinHTLC: node1.MinHTLC,
|
||||||
MaxHTLC: testChannel.Node1.MaxHTLC,
|
MaxHTLC: node1.MaxHTLC,
|
||||||
FeeBaseMSat: testChannel.Node1.FeeBaseMsat,
|
FeeBaseMSat: node1.FeeBaseMsat,
|
||||||
FeeProportionalMillionths: testChannel.Node1.FeeRate,
|
FeeProportionalMillionths: node1.FeeRate,
|
||||||
}
|
}
|
||||||
if err := graph.UpdateEdgePolicy(edgePolicy); err != nil {
|
if err := graph.UpdateEdgePolicy(edgePolicy); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -536,12 +553,12 @@ func createTestGraphFromChannels(testChannels []*testChannel) (*testGraphInstanc
|
|||||||
MessageFlags: msgFlags,
|
MessageFlags: msgFlags,
|
||||||
ChannelFlags: channelFlags,
|
ChannelFlags: channelFlags,
|
||||||
ChannelID: channelID,
|
ChannelID: channelID,
|
||||||
LastUpdate: testChannel.Node2.LastUpdate,
|
LastUpdate: node2.LastUpdate,
|
||||||
TimeLockDelta: testChannel.Node2.Expiry,
|
TimeLockDelta: node2.Expiry,
|
||||||
MinHTLC: testChannel.Node2.MinHTLC,
|
MinHTLC: node2.MinHTLC,
|
||||||
MaxHTLC: testChannel.Node2.MaxHTLC,
|
MaxHTLC: node2.MaxHTLC,
|
||||||
FeeBaseMSat: testChannel.Node2.FeeBaseMsat,
|
FeeBaseMSat: node2.FeeBaseMsat,
|
||||||
FeeProportionalMillionths: testChannel.Node2.FeeRate,
|
FeeProportionalMillionths: node2.FeeRate,
|
||||||
}
|
}
|
||||||
if err := graph.UpdateEdgePolicy(edgePolicy); err != nil {
|
if err := graph.UpdateEdgePolicy(edgePolicy); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -625,9 +642,7 @@ func TestFindLowestFeePath(t *testing.T) {
|
|||||||
&graphParams{
|
&graphParams{
|
||||||
graph: testGraphInstance.graph,
|
graph: testGraphInstance.graph,
|
||||||
},
|
},
|
||||||
&RestrictParams{
|
noRestrictions,
|
||||||
FeeLimit: noFeeLimit,
|
|
||||||
},
|
|
||||||
sourceNode.PubKeyBytes, target, paymentAmt,
|
sourceNode.PubKeyBytes, target, paymentAmt,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -767,6 +782,7 @@ func testBasicGraphPathFindingCase(t *testing.T, graphInstance *testGraphInstanc
|
|||||||
},
|
},
|
||||||
&RestrictParams{
|
&RestrictParams{
|
||||||
FeeLimit: test.feeLimit,
|
FeeLimit: test.feeLimit,
|
||||||
|
ProbabilitySource: noProbabilitySource,
|
||||||
},
|
},
|
||||||
sourceNode.PubKeyBytes, target, paymentAmt,
|
sourceNode.PubKeyBytes, target, paymentAmt,
|
||||||
)
|
)
|
||||||
@ -934,9 +950,7 @@ func TestPathFindingWithAdditionalEdges(t *testing.T) {
|
|||||||
graph: graph.graph,
|
graph: graph.graph,
|
||||||
additionalEdges: additionalEdges,
|
additionalEdges: additionalEdges,
|
||||||
},
|
},
|
||||||
&RestrictParams{
|
noRestrictions,
|
||||||
FeeLimit: noFeeLimit,
|
|
||||||
},
|
|
||||||
sourceNode.PubKeyBytes, doge.PubKeyBytes, paymentAmt,
|
sourceNode.PubKeyBytes, doge.PubKeyBytes, paymentAmt,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -1190,9 +1204,7 @@ func TestNewRoutePathTooLong(t *testing.T) {
|
|||||||
&graphParams{
|
&graphParams{
|
||||||
graph: graph.graph,
|
graph: graph.graph,
|
||||||
},
|
},
|
||||||
&RestrictParams{
|
noRestrictions,
|
||||||
FeeLimit: noFeeLimit,
|
|
||||||
},
|
|
||||||
sourceNode.PubKeyBytes, target, paymentAmt,
|
sourceNode.PubKeyBytes, target, paymentAmt,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -1206,9 +1218,7 @@ func TestNewRoutePathTooLong(t *testing.T) {
|
|||||||
&graphParams{
|
&graphParams{
|
||||||
graph: graph.graph,
|
graph: graph.graph,
|
||||||
},
|
},
|
||||||
&RestrictParams{
|
noRestrictions,
|
||||||
FeeLimit: noFeeLimit,
|
|
||||||
},
|
|
||||||
sourceNode.PubKeyBytes, target, paymentAmt,
|
sourceNode.PubKeyBytes, target, paymentAmt,
|
||||||
)
|
)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@ -1248,9 +1258,7 @@ func TestPathNotAvailable(t *testing.T) {
|
|||||||
&graphParams{
|
&graphParams{
|
||||||
graph: graph.graph,
|
graph: graph.graph,
|
||||||
},
|
},
|
||||||
&RestrictParams{
|
noRestrictions,
|
||||||
FeeLimit: noFeeLimit,
|
|
||||||
},
|
|
||||||
sourceNode.PubKeyBytes, unknownNode, 100,
|
sourceNode.PubKeyBytes, unknownNode, 100,
|
||||||
)
|
)
|
||||||
if !IsError(err, ErrNoPathFound) {
|
if !IsError(err, ErrNoPathFound) {
|
||||||
@ -1287,9 +1295,7 @@ func TestPathInsufficientCapacity(t *testing.T) {
|
|||||||
&graphParams{
|
&graphParams{
|
||||||
graph: graph.graph,
|
graph: graph.graph,
|
||||||
},
|
},
|
||||||
&RestrictParams{
|
noRestrictions,
|
||||||
FeeLimit: noFeeLimit,
|
|
||||||
},
|
|
||||||
sourceNode.PubKeyBytes, target, payAmt,
|
sourceNode.PubKeyBytes, target, payAmt,
|
||||||
)
|
)
|
||||||
if !IsError(err, ErrNoPathFound) {
|
if !IsError(err, ErrNoPathFound) {
|
||||||
@ -1322,9 +1328,7 @@ func TestRouteFailMinHTLC(t *testing.T) {
|
|||||||
&graphParams{
|
&graphParams{
|
||||||
graph: graph.graph,
|
graph: graph.graph,
|
||||||
},
|
},
|
||||||
&RestrictParams{
|
noRestrictions,
|
||||||
FeeLimit: noFeeLimit,
|
|
||||||
},
|
|
||||||
sourceNode.PubKeyBytes, target, payAmt,
|
sourceNode.PubKeyBytes, target, payAmt,
|
||||||
)
|
)
|
||||||
if !IsError(err, ErrNoPathFound) {
|
if !IsError(err, ErrNoPathFound) {
|
||||||
@ -1382,9 +1386,7 @@ func TestRouteFailMaxHTLC(t *testing.T) {
|
|||||||
&graphParams{
|
&graphParams{
|
||||||
graph: graph.graph,
|
graph: graph.graph,
|
||||||
},
|
},
|
||||||
&RestrictParams{
|
noRestrictions,
|
||||||
FeeLimit: noFeeLimit,
|
|
||||||
},
|
|
||||||
sourceNode.PubKeyBytes, target, payAmt,
|
sourceNode.PubKeyBytes, target, payAmt,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -1406,9 +1408,7 @@ func TestRouteFailMaxHTLC(t *testing.T) {
|
|||||||
&graphParams{
|
&graphParams{
|
||||||
graph: graph.graph,
|
graph: graph.graph,
|
||||||
},
|
},
|
||||||
&RestrictParams{
|
noRestrictions,
|
||||||
FeeLimit: noFeeLimit,
|
|
||||||
},
|
|
||||||
sourceNode.PubKeyBytes, target, payAmt,
|
sourceNode.PubKeyBytes, target, payAmt,
|
||||||
)
|
)
|
||||||
if !IsError(err, ErrNoPathFound) {
|
if !IsError(err, ErrNoPathFound) {
|
||||||
@ -1443,9 +1443,7 @@ func TestRouteFailDisabledEdge(t *testing.T) {
|
|||||||
&graphParams{
|
&graphParams{
|
||||||
graph: graph.graph,
|
graph: graph.graph,
|
||||||
},
|
},
|
||||||
&RestrictParams{
|
noRestrictions,
|
||||||
FeeLimit: noFeeLimit,
|
|
||||||
},
|
|
||||||
sourceNode.PubKeyBytes, target, payAmt,
|
sourceNode.PubKeyBytes, target, payAmt,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -1473,9 +1471,7 @@ func TestRouteFailDisabledEdge(t *testing.T) {
|
|||||||
&graphParams{
|
&graphParams{
|
||||||
graph: graph.graph,
|
graph: graph.graph,
|
||||||
},
|
},
|
||||||
&RestrictParams{
|
noRestrictions,
|
||||||
FeeLimit: noFeeLimit,
|
|
||||||
},
|
|
||||||
sourceNode.PubKeyBytes, target, payAmt,
|
sourceNode.PubKeyBytes, target, payAmt,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -1500,9 +1496,7 @@ func TestRouteFailDisabledEdge(t *testing.T) {
|
|||||||
&graphParams{
|
&graphParams{
|
||||||
graph: graph.graph,
|
graph: graph.graph,
|
||||||
},
|
},
|
||||||
&RestrictParams{
|
noRestrictions,
|
||||||
FeeLimit: noFeeLimit,
|
|
||||||
},
|
|
||||||
sourceNode.PubKeyBytes, target, payAmt,
|
sourceNode.PubKeyBytes, target, payAmt,
|
||||||
)
|
)
|
||||||
if !IsError(err, ErrNoPathFound) {
|
if !IsError(err, ErrNoPathFound) {
|
||||||
@ -1536,9 +1530,7 @@ func TestPathSourceEdgesBandwidth(t *testing.T) {
|
|||||||
&graphParams{
|
&graphParams{
|
||||||
graph: graph.graph,
|
graph: graph.graph,
|
||||||
},
|
},
|
||||||
&RestrictParams{
|
noRestrictions,
|
||||||
FeeLimit: noFeeLimit,
|
|
||||||
},
|
|
||||||
sourceNode.PubKeyBytes, target, payAmt,
|
sourceNode.PubKeyBytes, target, payAmt,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -1562,9 +1554,7 @@ func TestPathSourceEdgesBandwidth(t *testing.T) {
|
|||||||
graph: graph.graph,
|
graph: graph.graph,
|
||||||
bandwidthHints: bandwidths,
|
bandwidthHints: bandwidths,
|
||||||
},
|
},
|
||||||
&RestrictParams{
|
noRestrictions,
|
||||||
FeeLimit: noFeeLimit,
|
|
||||||
},
|
|
||||||
sourceNode.PubKeyBytes, target, payAmt,
|
sourceNode.PubKeyBytes, target, payAmt,
|
||||||
)
|
)
|
||||||
if !IsError(err, ErrNoPathFound) {
|
if !IsError(err, ErrNoPathFound) {
|
||||||
@ -1582,9 +1572,7 @@ func TestPathSourceEdgesBandwidth(t *testing.T) {
|
|||||||
graph: graph.graph,
|
graph: graph.graph,
|
||||||
bandwidthHints: bandwidths,
|
bandwidthHints: bandwidths,
|
||||||
},
|
},
|
||||||
&RestrictParams{
|
noRestrictions,
|
||||||
FeeLimit: noFeeLimit,
|
|
||||||
},
|
|
||||||
sourceNode.PubKeyBytes, target, payAmt,
|
sourceNode.PubKeyBytes, target, payAmt,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -1615,9 +1603,7 @@ func TestPathSourceEdgesBandwidth(t *testing.T) {
|
|||||||
graph: graph.graph,
|
graph: graph.graph,
|
||||||
bandwidthHints: bandwidths,
|
bandwidthHints: bandwidths,
|
||||||
},
|
},
|
||||||
&RestrictParams{
|
noRestrictions,
|
||||||
FeeLimit: noFeeLimit,
|
|
||||||
},
|
|
||||||
sourceNode.PubKeyBytes, target, payAmt,
|
sourceNode.PubKeyBytes, target, payAmt,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -1906,6 +1892,7 @@ func TestRestrictOutgoingChannel(t *testing.T) {
|
|||||||
&RestrictParams{
|
&RestrictParams{
|
||||||
FeeLimit: noFeeLimit,
|
FeeLimit: noFeeLimit,
|
||||||
OutgoingChannelID: &outgoingChannelID,
|
OutgoingChannelID: &outgoingChannelID,
|
||||||
|
ProbabilitySource: noProbabilitySource,
|
||||||
},
|
},
|
||||||
sourceVertex, target, paymentAmt,
|
sourceVertex, target, paymentAmt,
|
||||||
)
|
)
|
||||||
@ -1982,9 +1969,6 @@ func testCltvLimit(t *testing.T, limit uint32, expectedChannel uint64) {
|
|||||||
}
|
}
|
||||||
sourceVertex := route.Vertex(sourceNode.PubKeyBytes)
|
sourceVertex := route.Vertex(sourceNode.PubKeyBytes)
|
||||||
|
|
||||||
ignoredEdges := make(map[EdgeLocator]struct{})
|
|
||||||
ignoredVertexes := make(map[route.Vertex]struct{})
|
|
||||||
|
|
||||||
paymentAmt := lnwire.NewMSatFromSatoshis(100)
|
paymentAmt := lnwire.NewMSatFromSatoshis(100)
|
||||||
target := testGraphInstance.aliasMap["target"]
|
target := testGraphInstance.aliasMap["target"]
|
||||||
|
|
||||||
@ -1999,10 +1983,9 @@ func testCltvLimit(t *testing.T, limit uint32, expectedChannel uint64) {
|
|||||||
graph: testGraphInstance.graph,
|
graph: testGraphInstance.graph,
|
||||||
},
|
},
|
||||||
&RestrictParams{
|
&RestrictParams{
|
||||||
IgnoredNodes: ignoredVertexes,
|
|
||||||
IgnoredEdges: ignoredEdges,
|
|
||||||
FeeLimit: noFeeLimit,
|
FeeLimit: noFeeLimit,
|
||||||
CltvLimit: cltvLimit,
|
CltvLimit: cltvLimit,
|
||||||
|
ProbabilitySource: noProbabilitySource,
|
||||||
},
|
},
|
||||||
sourceVertex, target, paymentAmt,
|
sourceVertex, target, paymentAmt,
|
||||||
)
|
)
|
||||||
@ -2035,3 +2018,170 @@ func testCltvLimit(t *testing.T, limit uint32, expectedChannel uint64) {
|
|||||||
route.Hops[0].ChannelID)
|
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 (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/lightningnetwork/lnd/channeldb"
|
"github.com/lightningnetwork/lnd/channeldb"
|
||||||
"github.com/lightningnetwork/lnd/lnwire"
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
@ -24,11 +23,12 @@ type PaymentSession interface {
|
|||||||
// route.
|
// route.
|
||||||
ReportVertexFailure(v route.Vertex)
|
ReportVertexFailure(v route.Vertex)
|
||||||
|
|
||||||
// ReportEdgeFailure reports to the PaymentSession that the passed
|
// ReportEdgeFailure reports to the PaymentSession that the passed edge
|
||||||
// channel failed to route the previous payment attempt. The
|
// failed to route the previous payment attempt. A minimum penalization
|
||||||
// PaymentSession will use this information to produce a better next
|
// amount is included to attenuate the failure. This is set to a
|
||||||
// route.
|
// non-zero value for channel balance failures. The PaymentSession will
|
||||||
ReportEdgeFailure(e *EdgeLocator)
|
// use this information to produce a better next route.
|
||||||
|
ReportEdgeFailure(failedEdge edge, minPenalizeAmt lnwire.MilliSatoshi)
|
||||||
|
|
||||||
// ReportEdgePolicyFailure reports to the PaymentSession that we
|
// ReportEdgePolicyFailure reports to the PaymentSession that we
|
||||||
// received a failure message that relates to a channel policy. For
|
// 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
|
// keep the edge included in the next attempted route. The
|
||||||
// PaymentSession will use this information to produce a better next
|
// PaymentSession will use this information to produce a better next
|
||||||
// route.
|
// route.
|
||||||
ReportEdgePolicyFailure(errSource route.Vertex, failedEdge *EdgeLocator)
|
ReportEdgePolicyFailure(failedEdge edge)
|
||||||
}
|
}
|
||||||
|
|
||||||
// paymentSession is used during an HTLC routings session to prune the local
|
// 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
|
// loop if payment attempts take long enough. An additional set of edges can
|
||||||
// also be provided to assist in reaching the payment's destination.
|
// also be provided to assist in reaching the payment's destination.
|
||||||
type paymentSession struct {
|
type paymentSession struct {
|
||||||
pruneViewSnapshot graphPruneView
|
|
||||||
|
|
||||||
additionalEdges map[route.Vertex][]*channeldb.ChannelEdgePolicy
|
additionalEdges map[route.Vertex][]*channeldb.ChannelEdgePolicy
|
||||||
|
|
||||||
bandwidthHints map[uint64]lnwire.MilliSatoshi
|
bandwidthHints map[uint64]lnwire.MilliSatoshi
|
||||||
@ -58,7 +56,7 @@ type paymentSession struct {
|
|||||||
// source of policy related routing failures during this payment attempt.
|
// 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
|
// We'll use this map to prune out channels when the first error may not
|
||||||
// require pruning, but any subsequent ones do.
|
// require pruning, but any subsequent ones do.
|
||||||
errFailedPolicyChans map[EdgeLocator]struct{}
|
errFailedPolicyChans map[nodeChannel]struct{}
|
||||||
|
|
||||||
mc *MissionControl
|
mc *MissionControl
|
||||||
|
|
||||||
@ -80,17 +78,7 @@ var _ PaymentSession = (*paymentSession)(nil)
|
|||||||
//
|
//
|
||||||
// NOTE: Part of the PaymentSession interface.
|
// NOTE: Part of the PaymentSession interface.
|
||||||
func (p *paymentSession) ReportVertexFailure(v route.Vertex) {
|
func (p *paymentSession) ReportVertexFailure(v route.Vertex) {
|
||||||
log.Debugf("Reporting vertex %v failure to Mission Control", v)
|
p.mc.reportVertexFailure(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()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReportEdgeFailure adds a channel to the graph prune view. The time the
|
// 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
|
// TODO(roasbeef): also add value attempted to send and capacity of channel
|
||||||
//
|
//
|
||||||
// NOTE: Part of the PaymentSession interface.
|
// NOTE: Part of the PaymentSession interface.
|
||||||
func (p *paymentSession) ReportEdgeFailure(e *EdgeLocator) {
|
func (p *paymentSession) ReportEdgeFailure(failedEdge edge,
|
||||||
log.Debugf("Reporting edge %v failure to Mission Control", e)
|
minPenalizeAmt lnwire.MilliSatoshi) {
|
||||||
|
|
||||||
// First, we'll add the failed edge to our local prune view snapshot.
|
p.mc.reportEdgeFailure(failedEdge, minPenalizeAmt)
|
||||||
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()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReportEdgePolicyFailure handles a failure message that relates to a
|
// ReportEdgePolicyFailure handles a failure message that relates to a
|
||||||
@ -124,23 +104,28 @@ func (p *paymentSession) ReportEdgeFailure(e *EdgeLocator) {
|
|||||||
// new channel updates.
|
// new channel updates.
|
||||||
//
|
//
|
||||||
// NOTE: Part of the PaymentSession interface.
|
// 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
|
// Check to see if we've already reported a policy related failure for
|
||||||
// this channel. If so, then we'll prune out the vertex.
|
// this channel. If so, then we'll prune out the vertex.
|
||||||
_, ok := p.errFailedPolicyChans[*failedEdge]
|
_, ok := p.errFailedPolicyChans[key]
|
||||||
if ok {
|
if ok {
|
||||||
// TODO(joostjager): is this aggressive pruning still necessary?
|
// TODO(joostjager): is this aggressive pruning still necessary?
|
||||||
// Just pruning edges may also work unless there is a huge
|
// Just pruning edges may also work unless there is a huge
|
||||||
// number of failing channels from that node?
|
// number of failing channels from that node?
|
||||||
p.ReportVertexFailure(errSource)
|
p.ReportVertexFailure(key.node)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finally, we'll record a policy failure from this node and move on.
|
// 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
|
// 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")
|
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
|
// If a route cltv limit was specified, we need to subtract the final
|
||||||
// delta before passing it into path finding. The optimal path is
|
// delta before passing it into path finding. The optimal path is
|
||||||
// independent of the final cltv delta and the path finding algorithm 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,
|
bandwidthHints: p.bandwidthHints,
|
||||||
},
|
},
|
||||||
&RestrictParams{
|
&RestrictParams{
|
||||||
IgnoredNodes: pruneView.vertexes,
|
ProbabilitySource: p.mc.getEdgeProbability,
|
||||||
IgnoredEdges: pruneView.edges,
|
|
||||||
FeeLimit: payment.FeeLimit,
|
FeeLimit: payment.FeeLimit,
|
||||||
OutgoingChannelID: payment.OutgoingChannelID,
|
OutgoingChannelID: payment.OutgoingChannelID,
|
||||||
CltvLimit: cltvLimit,
|
CltvLimit: cltvLimit,
|
||||||
|
PaymentAttemptPenalty: p.mc.cfg.PaymentAttemptPenalty,
|
||||||
|
MinProbability: p.mc.cfg.MinRouteProbability,
|
||||||
},
|
},
|
||||||
p.mc.selfNode.PubKeyBytes, payment.Target,
|
p.mc.selfNode.PubKeyBytes, payment.Target,
|
||||||
payment.Amount,
|
payment.Amount,
|
||||||
@ -227,3 +204,9 @@ func (p *paymentSession) RequestRoute(payment *LightningPayment,
|
|||||||
|
|
||||||
return route, err
|
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{
|
session := &paymentSession{
|
||||||
mc: &MissionControl{
|
mc: &MissionControl{
|
||||||
selfNode: &channeldb.LightningNode{},
|
selfNode: &channeldb.LightningNode{},
|
||||||
|
cfg: &MissionControlConfig{},
|
||||||
},
|
},
|
||||||
pruneViewSnapshot: graphPruneView{},
|
|
||||||
pathFinder: findPath,
|
pathFinder: findPath,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,6 +3,8 @@ package route
|
|||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/btcsuite/btcd/btcec"
|
"github.com/btcsuite/btcd/btcec"
|
||||||
sphinx "github.com/lightningnetwork/lightning-onion"
|
sphinx "github.com/lightningnetwork/lightning-onion"
|
||||||
@ -180,3 +182,20 @@ func (r *Route) ToSphinxPath() (*sphinx.PaymentPath, error) {
|
|||||||
|
|
||||||
return &path, nil
|
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)
|
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 layer 3 router within the Lightning stack. Below the
|
||||||
// ChannelRouter is the HtlcSwitch, and below that is the Bitcoin blockchain
|
// ChannelRouter is the HtlcSwitch, and below that is the Bitcoin blockchain
|
||||||
// itself. The primary role of the ChannelRouter is to respond to queries for
|
// 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
|
// will be returned which describes the path the successful payment traversed
|
||||||
// within the network to reach the destination. Additionally, the payment
|
// within the network to reach the destination. Additionally, the payment
|
||||||
// preimage will also be returned.
|
// 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
|
// Before starting the HTLC routing attempt, we'll create a fresh
|
||||||
// payment session which will report our errors back to mission
|
// payment session which will report our errors back to mission
|
||||||
// control.
|
// control.
|
||||||
@ -1772,7 +1781,9 @@ func (r *ChannelRouter) processSendError(paySession PaymentSession,
|
|||||||
|
|
||||||
// Always determine chan id ourselves, because a channel
|
// Always determine chan id ourselves, because a channel
|
||||||
// update with id may not be available.
|
// 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 {
|
if err != nil {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@ -1804,13 +1815,11 @@ func (r *ChannelRouter) processSendError(paySession PaymentSession,
|
|||||||
// update to fail?
|
// update to fail?
|
||||||
if !updateOk {
|
if !updateOk {
|
||||||
paySession.ReportEdgeFailure(
|
paySession.ReportEdgeFailure(
|
||||||
failedEdge,
|
failedEdge, 0,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
paySession.ReportEdgePolicyFailure(
|
paySession.ReportEdgePolicyFailure(failedEdge)
|
||||||
route.NewVertex(errSource), failedEdge,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
switch onionErr := fErr.FailureMessage.(type) {
|
switch onionErr := fErr.FailureMessage.(type) {
|
||||||
@ -1901,7 +1910,7 @@ func (r *ChannelRouter) processSendError(paySession PaymentSession,
|
|||||||
// the update and continue.
|
// the update and continue.
|
||||||
case *lnwire.FailChannelDisabled:
|
case *lnwire.FailChannelDisabled:
|
||||||
r.applyChannelUpdate(&onionErr.Update, errSource)
|
r.applyChannelUpdate(&onionErr.Update, errSource)
|
||||||
paySession.ReportEdgeFailure(failedEdge)
|
paySession.ReportEdgeFailure(failedEdge, 0)
|
||||||
return false
|
return false
|
||||||
|
|
||||||
// It's likely that the outgoing channel didn't have
|
// 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.
|
// now, and continue onwards with our path finding.
|
||||||
case *lnwire.FailTemporaryChannelFailure:
|
case *lnwire.FailTemporaryChannelFailure:
|
||||||
r.applyChannelUpdate(onionErr.Update, errSource)
|
r.applyChannelUpdate(onionErr.Update, errSource)
|
||||||
paySession.ReportEdgeFailure(failedEdge)
|
paySession.ReportEdgeFailure(failedEdge, failedAmt)
|
||||||
return false
|
return false
|
||||||
|
|
||||||
// If the send fail due to a node not having the
|
// 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
|
// returning errors in order to attempt to black list
|
||||||
// another node.
|
// another node.
|
||||||
case *lnwire.FailUnknownNextPeer:
|
case *lnwire.FailUnknownNextPeer:
|
||||||
paySession.ReportEdgeFailure(failedEdge)
|
paySession.ReportEdgeFailure(failedEdge, 0)
|
||||||
return false
|
return false
|
||||||
|
|
||||||
// If the node wasn't able to forward for which ever
|
// 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
|
// we'll prune the channel in both directions and
|
||||||
// continue with the rest of the routes.
|
// continue with the rest of the routes.
|
||||||
case *lnwire.FailPermanentChannelFailure:
|
case *lnwire.FailPermanentChannelFailure:
|
||||||
paySession.ReportEdgeFailure(&EdgeLocator{
|
paySession.ReportEdgeFailure(failedEdge, 0)
|
||||||
ChannelID: failedEdge.ChannelID,
|
paySession.ReportEdgeFailure(edge{
|
||||||
Direction: 0,
|
from: failedEdge.to,
|
||||||
})
|
to: failedEdge.from,
|
||||||
paySession.ReportEdgeFailure(&EdgeLocator{
|
channel: failedEdge.channel,
|
||||||
ChannelID: failedEdge.ChannelID,
|
}, 0)
|
||||||
Direction: 1,
|
|
||||||
})
|
|
||||||
return false
|
return false
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@ -1982,12 +1989,14 @@ func (r *ChannelRouter) processSendError(paySession PaymentSession,
|
|||||||
|
|
||||||
// getFailedEdge tries to locate the failing channel given a route and the
|
// 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
|
// pubkey of the node that sent the error. It will assume that the error is
|
||||||
// associated with the outgoing channel of the error node.
|
// associated with the outgoing channel of the error node. As a second result,
|
||||||
func getFailedEdge(route *route.Route, errSource route.Vertex) (
|
// it returns the amount sent over the edge.
|
||||||
*EdgeLocator, error) {
|
func getFailedEdge(route *route.Route, errSource route.Vertex) (edge,
|
||||||
|
lnwire.MilliSatoshi, error) {
|
||||||
|
|
||||||
hopCount := len(route.Hops)
|
hopCount := len(route.Hops)
|
||||||
fromNode := route.SourcePubKey
|
fromNode := route.SourcePubKey
|
||||||
|
amt := route.TotalAmount
|
||||||
for i, hop := range route.Hops {
|
for i, hop := range route.Hops {
|
||||||
toNode := hop.PubKeyBytes
|
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
|
// If the errSource is the final hop, we assume that the failing
|
||||||
// channel is the incoming channel.
|
// channel is the incoming channel.
|
||||||
if errSource == fromNode || finalHopFailing {
|
if errSource == fromNode || finalHopFailing {
|
||||||
return newEdgeLocatorByPubkeys(
|
return edge{
|
||||||
hop.ChannelID,
|
from: fromNode,
|
||||||
&fromNode,
|
to: toNode,
|
||||||
&toNode,
|
channel: hop.ChannelID,
|
||||||
), nil
|
}, amt, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
fromNode = toNode
|
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
|
// 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 {
|
func(e *channeldb.ChannelEdgeInfo) lnwire.MilliSatoshi {
|
||||||
return lnwire.NewMSatFromSatoshis(e.Capacity)
|
return lnwire.NewMSatFromSatoshis(e.Capacity)
|
||||||
},
|
},
|
||||||
|
&MissionControlConfig{
|
||||||
|
MinRouteProbability: 0.01,
|
||||||
|
PaymentAttemptPenalty: 100,
|
||||||
|
PenaltyHalfLife: time.Hour,
|
||||||
|
AprioriHopProbability: 0.9,
|
||||||
|
},
|
||||||
)
|
)
|
||||||
router, err := New(Config{
|
router, err := New(Config{
|
||||||
Graph: graphInstance.graph,
|
Graph: graphInstance.graph,
|
||||||
@ -202,6 +208,7 @@ func TestFindRoutesWithFeeLimit(t *testing.T) {
|
|||||||
paymentAmt := lnwire.NewMSatFromSatoshis(100)
|
paymentAmt := lnwire.NewMSatFromSatoshis(100)
|
||||||
restrictions := &RestrictParams{
|
restrictions := &RestrictParams{
|
||||||
FeeLimit: lnwire.NewMSatFromSatoshis(10),
|
FeeLimit: lnwire.NewMSatFromSatoshis(10),
|
||||||
|
ProbabilitySource: noProbabilitySource,
|
||||||
}
|
}
|
||||||
|
|
||||||
route, err := ctx.router.FindRoute(
|
route, err := ctx.router.FindRoute(
|
||||||
@ -2198,9 +2205,7 @@ func TestFindPathFeeWeighting(t *testing.T) {
|
|||||||
&graphParams{
|
&graphParams{
|
||||||
graph: ctx.graph,
|
graph: ctx.graph,
|
||||||
},
|
},
|
||||||
&RestrictParams{
|
noRestrictions,
|
||||||
FeeLimit: noFeeLimit,
|
|
||||||
},
|
|
||||||
sourceNode.PubKeyBytes, target, amt,
|
sourceNode.PubKeyBytes, target, amt,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -477,6 +477,7 @@ func newRPCServer(s *server, macService *macaroons.Service,
|
|||||||
return info.NodeKey1Bytes, info.NodeKey2Bytes, nil
|
return info.NodeKey1Bytes, info.NodeKey2Bytes, nil
|
||||||
},
|
},
|
||||||
FindRoute: s.chanRouter.FindRoute,
|
FindRoute: s.chanRouter.FindRoute,
|
||||||
|
MissionControl: s.missionControl,
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
21
server.go
21
server.go
@ -38,6 +38,7 @@ import (
|
|||||||
"github.com/lightningnetwork/lnd/lncfg"
|
"github.com/lightningnetwork/lnd/lncfg"
|
||||||
"github.com/lightningnetwork/lnd/lnpeer"
|
"github.com/lightningnetwork/lnd/lnpeer"
|
||||||
"github.com/lightningnetwork/lnd/lnrpc"
|
"github.com/lightningnetwork/lnd/lnrpc"
|
||||||
|
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
|
||||||
"github.com/lightningnetwork/lnd/lnwallet"
|
"github.com/lightningnetwork/lnd/lnwallet"
|
||||||
"github.com/lightningnetwork/lnd/lnwire"
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
"github.com/lightningnetwork/lnd/nat"
|
"github.com/lightningnetwork/lnd/nat"
|
||||||
@ -187,6 +188,8 @@ type server struct {
|
|||||||
|
|
||||||
breachArbiter *breachArbiter
|
breachArbiter *breachArbiter
|
||||||
|
|
||||||
|
missionControl *routing.MissionControl
|
||||||
|
|
||||||
chanRouter *routing.ChannelRouter
|
chanRouter *routing.ChannelRouter
|
||||||
|
|
||||||
authGossiper *discovery.AuthenticatedGossiper
|
authGossiper *discovery.AuthenticatedGossiper
|
||||||
@ -617,15 +620,6 @@ func newServer(listenAddrs []net.Addr, chanDB *channeldb.DB, cc *chainControl,
|
|||||||
}
|
}
|
||||||
|
|
||||||
queryBandwidth := func(edge *channeldb.ChannelEdgeInfo) lnwire.MilliSatoshi {
|
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)
|
cid := lnwire.NewChanIDFromOutPoint(&edge.ChannelPoint)
|
||||||
link, err := s.htlcSwitch.GetLink(cid)
|
link, err := s.htlcSwitch.GetLink(cid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -646,8 +640,13 @@ func newServer(listenAddrs []net.Addr, chanDB *channeldb.DB, cc *chainControl,
|
|||||||
return link.Bandwidth()
|
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,
|
chanGraph, selfNode, queryBandwidth,
|
||||||
|
routerrpc.GetMissionControlConfig(cfg.SubRPCServers.RouterRPC),
|
||||||
)
|
)
|
||||||
|
|
||||||
s.chanRouter, err = routing.New(routing.Config{
|
s.chanRouter, err = routing.New(routing.Config{
|
||||||
@ -656,7 +655,7 @@ func newServer(listenAddrs []net.Addr, chanDB *channeldb.DB, cc *chainControl,
|
|||||||
ChainView: cc.chainView,
|
ChainView: cc.chainView,
|
||||||
Payer: s.htlcSwitch,
|
Payer: s.htlcSwitch,
|
||||||
Control: channeldb.NewPaymentControl(chanDB),
|
Control: channeldb.NewPaymentControl(chanDB),
|
||||||
MissionControl: missionControl,
|
MissionControl: s.missionControl,
|
||||||
ChannelPruneExpiry: routing.DefaultChannelPruneExpiry,
|
ChannelPruneExpiry: routing.DefaultChannelPruneExpiry,
|
||||||
GraphPruneInterval: time.Duration(time.Hour),
|
GraphPruneInterval: time.Duration(time.Hour),
|
||||||
QueryBandwidth: queryBandwidth,
|
QueryBandwidth: queryBandwidth,
|
||||||
|
Loading…
Reference in New Issue
Block a user