From 054e42f6805fe0318b2fdfc45931302149383c4f Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Wed, 22 May 2019 11:56:04 +0200 Subject: [PATCH] routing+routerrpc: expose mission control parameters in lnd config This commit exposes the three main parameters that influence mission control and path finding to the user as command line or config file flags. It allows for fine-tuning for optimal results. --- config.go | 4 +- lnrpc/routerrpc/config_active.go | 47 +++++++++++++++++++++++ lnrpc/routerrpc/config_default.go | 21 +++++++++- routing/missioncontrol.go | 64 +++++++++++++++++++++---------- routing/missioncontrol_test.go | 16 +++++--- routing/pathfind.go | 8 +++- routing/payment_session.go | 4 +- routing/payment_session_test.go | 1 + routing/router_test.go | 6 +++ server.go | 6 +++ 10 files changed, 145 insertions(+), 32 deletions(-) diff --git a/config.go b/config.go index a7ba5c6b97..c6e4c875c3 100644 --- a/config.go +++ b/config.go @@ -27,6 +27,7 @@ import ( "github.com/lightningnetwork/lnd/discovery" "github.com/lightningnetwork/lnd/htlcswitch/hodl" "github.com/lightningnetwork/lnd/lncfg" + "github.com/lightningnetwork/lnd/lnrpc/routerrpc" "github.com/lightningnetwork/lnd/lnrpc/signrpc" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/routing" @@ -369,7 +370,8 @@ func loadConfig() (*config, error) { MinBackoff: defaultMinBackoff, MaxBackoff: defaultMaxBackoff, SubRPCServers: &subRPCServerConfigs{ - SignRPC: &signrpc.Config{}, + SignRPC: &signrpc.Config{}, + RouterRPC: routerrpc.DefaultConfig(), }, Autopilot: &autoPilotConfig{ MaxChannels: 5, diff --git a/lnrpc/routerrpc/config_active.go b/lnrpc/routerrpc/config_active.go index edf9803555..c2ee3a2341 100644 --- a/lnrpc/routerrpc/config_active.go +++ b/lnrpc/routerrpc/config_active.go @@ -3,7 +3,12 @@ package routerrpc import ( + "time" + + "github.com/lightningnetwork/lnd/lnwire" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/macaroons" "github.com/lightningnetwork/lnd/routing" ) @@ -19,6 +24,23 @@ type Config struct { // directory, named DefaultRouterMacFilename. RouterMacPath string `long:"routermacaroonpath" description:"Path to the router macaroon"` + // MinProbability is the minimum required route success probability to + // attempt the payment. + MinRouteProbability float64 `long:"minrtprob" description:"Minimum required route success probability to attempt the payment"` + + // AprioriHopProbability is the assumed success probability of a hop in + // a route when no other information is available. + AprioriHopProbability float64 `long:"apriorihopprob" description:"Assumed success probability of a hop in a route when no other information is available."` + + // PenaltyHalfLife defines after how much time a penalized node or + // channel is back at 50% probability. + PenaltyHalfLife time.Duration `long:"penaltyhalflife" description:"Defines the duration after which a penalized node or channel is back at 50% probability"` + + // AttemptCost is the virtual cost in path finding weight units of + // executing a payment attempt that fails. It is used to trade off + // potentially better routes against their probability of succeeding. + AttemptCost int64 `long:"attemptcost" description:"The (virtual) cost in sats of a failed payment attempt"` + // NetworkDir is the main network directory wherein the router rpc // server will find the macaroon named DefaultRouterMacFilename. NetworkDir string @@ -45,3 +67,28 @@ type Config struct { // main rpc server. RouterBackend *RouterBackend } + +// DefaultConfig defines the config defaults. +func DefaultConfig() *Config { + return &Config{ + AprioriHopProbability: routing.DefaultAprioriHopProbability, + MinRouteProbability: routing.DefaultMinRouteProbability, + PenaltyHalfLife: routing.DefaultPenaltyHalfLife, + AttemptCost: int64( + routing.DefaultPaymentAttemptPenalty.ToSatoshis(), + ), + } +} + +// GetMissionControlConfig returns the mission control config based on this sub +// server config. +func GetMissionControlConfig(cfg *Config) *routing.MissionControlConfig { + return &routing.MissionControlConfig{ + AprioriHopProbability: cfg.AprioriHopProbability, + MinRouteProbability: cfg.MinRouteProbability, + PaymentAttemptPenalty: lnwire.NewMSatFromSatoshis( + btcutil.Amount(cfg.AttemptCost), + ), + PenaltyHalfLife: cfg.PenaltyHalfLife, + } +} diff --git a/lnrpc/routerrpc/config_default.go b/lnrpc/routerrpc/config_default.go index 9ca27faa48..81c4a577e3 100644 --- a/lnrpc/routerrpc/config_default.go +++ b/lnrpc/routerrpc/config_default.go @@ -2,6 +2,25 @@ package routerrpc -// Config is the default config for the package. When the build tag isn't +import "github.com/lightningnetwork/lnd/routing" + +// Config is the default config struct for the package. When the build tag isn't // specified, then we output a blank config. type Config struct{} + +// DefaultConfig defines the config defaults. Without the sub server enabled, +// there are no defaults to set. +func DefaultConfig() *Config { + return &Config{} +} + +// GetMissionControlConfig returns the mission control config based on this sub +// server config. +func GetMissionControlConfig(cfg *Config) *routing.MissionControlConfig { + return &routing.MissionControlConfig{ + AprioriHopProbability: routing.DefaultAprioriHopProbability, + MinRouteProbability: routing.DefaultMinRouteProbability, + PaymentAttemptPenalty: routing.DefaultPaymentAttemptPenalty, + PenaltyHalfLife: routing.DefaultPenaltyHalfLife, + } +} diff --git a/routing/missioncontrol.go b/routing/missioncontrol.go index 1a98e174f6..17575a7218 100644 --- a/routing/missioncontrol.go +++ b/routing/missioncontrol.go @@ -14,14 +14,10 @@ import ( ) const ( - // defaultPenaltyHalfLife is the default half-life duration. The + // DefaultPenaltyHalfLife is the default half-life duration. The // half-life duration defines after how much time a penalized node or // channel is back at 50% probability. - defaultPenaltyHalfLife = time.Hour - - // aprioriHopProbability is the assumed success probability of a hop in - // a route when no other information is available. - aprioriHopProbability = 1 + DefaultPenaltyHalfLife = time.Hour ) // MissionControl contains state which summarizes the past attempts of HTLC @@ -46,9 +42,7 @@ type MissionControl struct { // external function to enable deterministic unit tests. now func() time.Time - // penaltyHalfLife defines after how much time a penalized node or - // channel is back at 50% probability. - penaltyHalfLife time.Duration + cfg *MissionControlConfig sync.Mutex @@ -62,6 +56,28 @@ type MissionControl struct { // PaymentSessionSource interface. var _ PaymentSessionSource = (*MissionControl)(nil) +// MissionControlConfig defines parameters that control mission control +// behaviour. +type MissionControlConfig struct { + // PenaltyHalfLife defines after how much time a penalized node or + // channel is back at 50% probability. + PenaltyHalfLife time.Duration + + // PaymentAttemptPenalty is the virtual cost in path finding weight + // units of executing a payment attempt that fails. It is used to trade + // off potentially better routes against their probability of + // succeeding. + PaymentAttemptPenalty lnwire.MilliSatoshi + + // MinProbability defines the minimum success probability of the + // returned route. + MinRouteProbability float64 + + // AprioriHopProbability is the assumed success probability of a hop in + // a route when no other information is available. + AprioriHopProbability float64 +} + // nodeHistory contains a summary of payment attempt outcomes involving a // particular node. type nodeHistory struct { @@ -130,15 +146,23 @@ type MissionControlChannelSnapshot struct { // // TODO(roasbeef): persist memory func NewMissionControl(g *channeldb.ChannelGraph, selfNode *channeldb.LightningNode, - qb func(*channeldb.ChannelEdgeInfo) lnwire.MilliSatoshi) *MissionControl { + qb func(*channeldb.ChannelEdgeInfo) lnwire.MilliSatoshi, + cfg *MissionControlConfig) *MissionControl { + + log.Debugf("Instantiating mission control with config: "+ + "PenaltyHalfLife=%v, PaymentAttemptPenalty=%v, "+ + "MinRouteProbability=%v, AprioriHopProbability=%v", + cfg.PenaltyHalfLife, + int64(cfg.PaymentAttemptPenalty.ToSatoshis()), + cfg.MinRouteProbability, cfg.AprioriHopProbability) return &MissionControl{ - history: make(map[route.Vertex]*nodeHistory), - selfNode: selfNode, - queryBandwidth: qb, - graph: g, - now: time.Now, - penaltyHalfLife: defaultPenaltyHalfLife, + history: make(map[route.Vertex]*nodeHistory), + selfNode: selfNode, + queryBandwidth: qb, + graph: g, + now: time.Now, + cfg: cfg, } } @@ -302,7 +326,7 @@ func (m *MissionControl) getEdgeProbability(fromNode route.Vertex, // adjust this probability. nodeHistory, ok := m.history[fromNode] if !ok { - return aprioriHopProbability + return m.cfg.AprioriHopProbability } return m.getEdgeProbabilityForNode(nodeHistory, edge.ChannelID, amt) @@ -337,7 +361,7 @@ func (m *MissionControl) getEdgeProbabilityForNode(nodeHistory *nodeHistory, } if lastFailure == nil { - return aprioriHopProbability + return m.cfg.AprioriHopProbability } timeSinceLastFailure := m.now().Sub(*lastFailure) @@ -346,8 +370,8 @@ func (m *MissionControl) getEdgeProbabilityForNode(nodeHistory *nodeHistory, // the probability down to zero when a failure occurs. From there it // recovers asymptotically back to the a priori probability. The rate at // which this happens is controlled by the penaltyHalfLife parameter. - exp := -timeSinceLastFailure.Hours() / m.penaltyHalfLife.Hours() - probability := aprioriHopProbability * (1 - math.Pow(2, exp)) + exp := -timeSinceLastFailure.Hours() / m.cfg.PenaltyHalfLife.Hours() + probability := m.cfg.AprioriHopProbability * (1 - math.Pow(2, exp)) return probability } diff --git a/routing/missioncontrol_test.go b/routing/missioncontrol_test.go index ed79c5f203..19a288286d 100644 --- a/routing/missioncontrol_test.go +++ b/routing/missioncontrol_test.go @@ -12,9 +12,13 @@ import ( func TestMissionControl(t *testing.T) { now := testTime - mc := NewMissionControl(nil, nil, nil) + mc := NewMissionControl( + nil, nil, nil, &MissionControlConfig{ + PenaltyHalfLife: 30 * time.Minute, + AprioriHopProbability: 0.8, + }, + ) mc.now = func() time.Time { return now } - mc.penaltyHalfLife = 30 * time.Minute testTime := time.Date(2018, time.January, 9, 14, 00, 00, 0, time.UTC) @@ -36,7 +40,7 @@ func TestMissionControl(t *testing.T) { } // Initial probability is expected to be 1. - expectP(1000, 1) + expectP(1000, 0.8) // Expect probability to be zero after reporting the edge as failed. mc.reportEdgeFailure(testEdge, 1000) @@ -44,11 +48,11 @@ func TestMissionControl(t *testing.T) { // As we reported with a min penalization amt, a lower amt than reported // should be unaffected. - expectP(500, 1) + expectP(500, 0.8) // Edge decay started. now = testTime.Add(30 * time.Minute) - expectP(1000, 0.5) + expectP(1000, 0.4) // Edge fails again, this time without a min penalization amt. The edge // should be penalized regardless of amount. @@ -58,7 +62,7 @@ func TestMissionControl(t *testing.T) { // Edge decay started. now = testTime.Add(60 * time.Minute) - expectP(1000, 0.5) + expectP(1000, 0.4) // A node level failure should bring probability of every channel back // to zero. diff --git a/routing/pathfind.go b/routing/pathfind.go index 3312a66e3a..1e3676bc3f 100644 --- a/routing/pathfind.go +++ b/routing/pathfind.go @@ -47,9 +47,13 @@ var ( // succeeding. DefaultPaymentAttemptPenalty = lnwire.NewMSatFromSatoshis(100) - // DefaultMinProbability is the default minimum probability for routes + // DefaultMinRouteProbability is the default minimum probability for routes // returned from findPath. - DefaultMinProbability = float64(0.01) + DefaultMinRouteProbability = float64(0.01) + + // DefaultAprioriHopProbability is the default a priori probability for + // a hop. + DefaultAprioriHopProbability = float64(0.95) ) // edgePolicyWithSource is a helper struct to keep track of the source node diff --git a/routing/payment_session.go b/routing/payment_session.go index 6865d1b386..7508e30a34 100644 --- a/routing/payment_session.go +++ b/routing/payment_session.go @@ -180,8 +180,8 @@ func (p *paymentSession) RequestRoute(payment *LightningPayment, FeeLimit: payment.FeeLimit, OutgoingChannelID: payment.OutgoingChannelID, CltvLimit: cltvLimit, - PaymentAttemptPenalty: DefaultPaymentAttemptPenalty, - MinProbability: DefaultMinProbability, + PaymentAttemptPenalty: p.mc.cfg.PaymentAttemptPenalty, + MinProbability: p.mc.cfg.MinRouteProbability, }, p.mc.selfNode.PubKeyBytes, payment.Target, payment.Amount, diff --git a/routing/payment_session_test.go b/routing/payment_session_test.go index 3ac01f2a98..a8748a624f 100644 --- a/routing/payment_session_test.go +++ b/routing/payment_session_test.go @@ -35,6 +35,7 @@ func TestRequestRoute(t *testing.T) { session := &paymentSession{ mc: &MissionControl{ selfNode: &channeldb.LightningNode{}, + cfg: &MissionControlConfig{}, }, pathFinder: findPath, } diff --git a/routing/router_test.go b/routing/router_test.go index 70cd128bd0..4c2b2be976 100644 --- a/routing/router_test.go +++ b/routing/router_test.go @@ -95,6 +95,12 @@ func createTestCtxFromGraphInstance(startingHeight uint32, graphInstance *testGr func(e *channeldb.ChannelEdgeInfo) lnwire.MilliSatoshi { return lnwire.NewMSatFromSatoshis(e.Capacity) }, + &MissionControlConfig{ + MinRouteProbability: 0.01, + PaymentAttemptPenalty: 100, + PenaltyHalfLife: time.Hour, + AprioriHopProbability: 0.9, + }, ) router, err := New(Config{ Graph: graphInstance.graph, diff --git a/server.go b/server.go index cac4125b09..64293b3e02 100644 --- a/server.go +++ b/server.go @@ -38,6 +38,7 @@ import ( "github.com/lightningnetwork/lnd/lncfg" "github.com/lightningnetwork/lnd/lnpeer" "github.com/lightningnetwork/lnd/lnrpc" + "github.com/lightningnetwork/lnd/lnrpc/routerrpc" "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/nat" @@ -639,8 +640,13 @@ func newServer(listenAddrs []net.Addr, chanDB *channeldb.DB, cc *chainControl, return link.Bandwidth() } + // Instantiate mission control with config from the sub server. + // + // TODO(joostjager): When we are further in the process of moving to sub + // servers, the mission control instance itself can be moved there too. s.missionControl = routing.NewMissionControl( chanGraph, selfNode, queryBandwidth, + routerrpc.GetMissionControlConfig(cfg.SubRPCServers.RouterRPC), ) s.chanRouter, err = routing.New(routing.Config{