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{