Skip to content

Commit

Permalink
routing+routerrpc: expose mission control parameters in lnd config
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
joostjager committed May 30, 2019
1 parent e7226c9 commit c08eee7
Show file tree
Hide file tree
Showing 10 changed files with 148 additions and 30 deletions.
4 changes: 3 additions & 1 deletion config.go
Expand Up @@ -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"
Expand Down Expand Up @@ -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,
Expand Down
47 changes: 47 additions & 0 deletions lnrpc/routerrpc/config_active.go
Expand Up @@ -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"
)
Expand All @@ -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
Expand All @@ -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,
}
}
21 changes: 20 additions & 1 deletion lnrpc/routerrpc/config_default.go
Expand Up @@ -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,
}
}
64 changes: 46 additions & 18 deletions routing/missioncontrol.go
Expand Up @@ -14,10 +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
DefaultPenaltyHalfLife = time.Hour
)

// MissionControl contains state which summarizes the past attempts of HTLC
Expand All @@ -42,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

Expand All @@ -58,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 {
Expand Down Expand Up @@ -126,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,
}
}

Expand Down Expand Up @@ -297,7 +325,7 @@ func (m *MissionControl) getEdgeProbability(fromNode route.Vertex,
// information becomes available to adjust this probability.
nodeHistory, ok := m.history[fromNode]
if !ok {
return 1
return m.cfg.AprioriHopProbability
}

return m.getEdgeProbabilityForNode(nodeHistory, edge.ChannelID, amt)
Expand Down Expand Up @@ -326,17 +354,17 @@ func (m *MissionControl) getEdgeProbabilityForNode(nodeHistory *nodeHistory,
}

if lastFailure == nil {
return 1
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 1. The rate at which this happens is
// controlled by the penaltyHalfLife parameter.
exp := -timeSinceLastFailure.Hours() / m.penaltyHalfLife.Hours()
probability := 1 - math.Pow(2, exp)
// 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
}
Expand Down
16 changes: 10 additions & 6 deletions routing/missioncontrol_test.go
Expand Up @@ -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)

Expand All @@ -36,19 +40,19 @@ 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)
expectP(1000, 0)

// 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.
Expand All @@ -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.
Expand Down
8 changes: 6 additions & 2 deletions routing/pathfind.go
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions routing/payment_session.go
Expand Up @@ -179,8 +179,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,
Expand Down
1 change: 1 addition & 0 deletions routing/payment_session_test.go
Expand Up @@ -35,6 +35,7 @@ func TestRequestRoute(t *testing.T) {
session := &paymentSession{
mc: &MissionControl{
selfNode: &channeldb.LightningNode{},
cfg: &MissionControlConfig{},
},
pathFinder: findPath,
}
Expand Down
6 changes: 6 additions & 0 deletions routing/router_test.go
Expand Up @@ -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,
Expand Down
7 changes: 7 additions & 0 deletions server.go
Expand Up @@ -17,6 +17,8 @@ import (
"sync/atomic"
"time"

"github.com/lightningnetwork/lnd/lnrpc/routerrpc"

"github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/connmgr"
Expand Down Expand Up @@ -639,8 +641,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{
Expand Down

0 comments on commit c08eee7

Please sign in to comment.