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.
This commit is contained in:
Joost Jager 2019-05-22 11:56:04 +02:00
parent 9b71d90a6e
commit 054e42f680
No known key found for this signature in database
GPG Key ID: A61B9D4C393C59C7
10 changed files with 145 additions and 32 deletions

@ -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,

@ -3,7 +3,12 @@
package routerrpc
import (
"time"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcutil"
"github.com/lightningnetwork/lnd/macaroons"
"github.com/lightningnetwork/lnd/routing"
)
@ -19,6 +24,23 @@ type Config struct {
// directory, named DefaultRouterMacFilename.
RouterMacPath string `long:"routermacaroonpath" description:"Path to the router macaroon"`
// MinProbability is the minimum required route success probability to
// attempt the payment.
MinRouteProbability float64 `long:"minrtprob" description:"Minimum required route success probability to attempt the payment"`
// AprioriHopProbability is the assumed success probability of a hop in
// a route when no other information is available.
AprioriHopProbability float64 `long:"apriorihopprob" description:"Assumed success probability of a hop in a route when no other information is available."`
// PenaltyHalfLife defines after how much time a penalized node or
// channel is back at 50% probability.
PenaltyHalfLife time.Duration `long:"penaltyhalflife" description:"Defines the duration after which a penalized node or channel is back at 50% probability"`
// AttemptCost is the virtual cost in path finding weight units of
// executing a payment attempt that fails. It is used to trade off
// potentially better routes against their probability of succeeding.
AttemptCost int64 `long:"attemptcost" description:"The (virtual) cost in sats of a failed payment attempt"`
// NetworkDir is the main network directory wherein the router rpc
// server will find the macaroon named DefaultRouterMacFilename.
NetworkDir string
@ -45,3 +67,28 @@ type Config struct {
// main rpc server.
RouterBackend *RouterBackend
}
// DefaultConfig defines the config defaults.
func DefaultConfig() *Config {
return &Config{
AprioriHopProbability: routing.DefaultAprioriHopProbability,
MinRouteProbability: routing.DefaultMinRouteProbability,
PenaltyHalfLife: routing.DefaultPenaltyHalfLife,
AttemptCost: int64(
routing.DefaultPaymentAttemptPenalty.ToSatoshis(),
),
}
}
// GetMissionControlConfig returns the mission control config based on this sub
// server config.
func GetMissionControlConfig(cfg *Config) *routing.MissionControlConfig {
return &routing.MissionControlConfig{
AprioriHopProbability: cfg.AprioriHopProbability,
MinRouteProbability: cfg.MinRouteProbability,
PaymentAttemptPenalty: lnwire.NewMSatFromSatoshis(
btcutil.Amount(cfg.AttemptCost),
),
PenaltyHalfLife: cfg.PenaltyHalfLife,
}
}

@ -2,6 +2,25 @@
package routerrpc
// Config is the default config for the package. When the build tag isn't
import "github.com/lightningnetwork/lnd/routing"
// Config is the default config struct for the package. When the build tag isn't
// specified, then we output a blank config.
type Config struct{}
// DefaultConfig defines the config defaults. Without the sub server enabled,
// there are no defaults to set.
func DefaultConfig() *Config {
return &Config{}
}
// GetMissionControlConfig returns the mission control config based on this sub
// server config.
func GetMissionControlConfig(cfg *Config) *routing.MissionControlConfig {
return &routing.MissionControlConfig{
AprioriHopProbability: routing.DefaultAprioriHopProbability,
MinRouteProbability: routing.DefaultMinRouteProbability,
PaymentAttemptPenalty: routing.DefaultPaymentAttemptPenalty,
PenaltyHalfLife: routing.DefaultPenaltyHalfLife,
}
}

@ -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
}

@ -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.

@ -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

@ -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,

@ -35,6 +35,7 @@ func TestRequestRoute(t *testing.T) {
session := &paymentSession{
mc: &MissionControl{
selfNode: &channeldb.LightningNode{},
cfg: &MissionControlConfig{},
},
pathFinder: findPath,
}

@ -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,

@ -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{