routing: extract probability estimator cfg and add validation
In preparation for allowing live update of mc config, we extract our probability estimator cfg for easy update and add validation.
This commit is contained in:
parent
7b24b586a0
commit
e10e8f11de
@ -58,10 +58,12 @@ func newIntegratedRoutingContext(t *testing.T) *integratedRoutingContext {
|
|||||||
finalExpiry: 40,
|
finalExpiry: 40,
|
||||||
|
|
||||||
mcCfg: MissionControlConfig{
|
mcCfg: MissionControlConfig{
|
||||||
|
ProbabilityEstimatorCfg: ProbabilityEstimatorCfg{
|
||||||
PenaltyHalfLife: 30 * time.Minute,
|
PenaltyHalfLife: 30 * time.Minute,
|
||||||
AprioriHopProbability: 0.6,
|
AprioriHopProbability: 0.6,
|
||||||
AprioriWeight: 0.5,
|
AprioriWeight: 0.5,
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
|
||||||
pathFindingCfg: PathFindingConfig{
|
pathFindingCfg: PathFindingConfig{
|
||||||
AttemptCost: 1000,
|
AttemptCost: 1000,
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package routing
|
package routing
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@ -58,6 +59,17 @@ const (
|
|||||||
DefaultMinFailureRelaxInterval = time.Minute
|
DefaultMinFailureRelaxInterval = time.Minute
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrInvalidMcHistory is returned if we get a negative mission control
|
||||||
|
// history count.
|
||||||
|
ErrInvalidMcHistory = errors.New("mission control history must be " +
|
||||||
|
">= 0")
|
||||||
|
|
||||||
|
// ErrInvalidFailureInterval is returned if we get an invalid failure
|
||||||
|
// interval.
|
||||||
|
ErrInvalidFailureInterval = errors.New("failure interval must be >= 0")
|
||||||
|
)
|
||||||
|
|
||||||
// NodeResults contains previous results from a node to its peers.
|
// NodeResults contains previous results from a node to its peers.
|
||||||
type NodeResults map[route.Vertex]TimedPairResult
|
type NodeResults map[route.Vertex]TimedPairResult
|
||||||
|
|
||||||
@ -99,33 +111,36 @@ type MissionControl struct {
|
|||||||
// MissionControlConfig defines parameters that control mission control
|
// MissionControlConfig defines parameters that control mission control
|
||||||
// behaviour.
|
// behaviour.
|
||||||
type MissionControlConfig struct {
|
type MissionControlConfig struct {
|
||||||
// PenaltyHalfLife defines after how much time a penalized node or
|
// ProbabilityEstimatorConfig is the config we will use for probability
|
||||||
// channel is back at 50% probability.
|
// calculations.
|
||||||
PenaltyHalfLife time.Duration
|
ProbabilityEstimatorCfg
|
||||||
|
|
||||||
// AprioriHopProbability is the assumed success probability of a hop in
|
|
||||||
// a route when no other information is available.
|
|
||||||
AprioriHopProbability float64
|
|
||||||
|
|
||||||
// MaxMcHistory defines the maximum number of payment results that are
|
// MaxMcHistory defines the maximum number of payment results that are
|
||||||
// held on disk.
|
// held on disk.
|
||||||
MaxMcHistory int
|
MaxMcHistory int
|
||||||
|
|
||||||
// AprioriWeight is a value in the range [0, 1] that defines to what
|
|
||||||
// extent historical results should be extrapolated to untried
|
|
||||||
// connections. Setting it to one will completely ignore historical
|
|
||||||
// results and always assume the configured a priori probability for
|
|
||||||
// untried connections. A value of zero will ignore the a priori
|
|
||||||
// probability completely and only base the probability on historical
|
|
||||||
// results, unless there are none available.
|
|
||||||
AprioriWeight float64
|
|
||||||
|
|
||||||
// MinFailureRelaxInterval is the minimum time that must have passed
|
// MinFailureRelaxInterval is the minimum time that must have passed
|
||||||
// since the previously recorded failure before the failure amount may
|
// since the previously recorded failure before the failure amount may
|
||||||
// be raised.
|
// be raised.
|
||||||
MinFailureRelaxInterval time.Duration
|
MinFailureRelaxInterval time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *MissionControlConfig) validate() error {
|
||||||
|
if err := c.ProbabilityEstimatorCfg.validate(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.MaxMcHistory < 0 {
|
||||||
|
return ErrInvalidMcHistory
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.MinFailureRelaxInterval < 0 {
|
||||||
|
return ErrInvalidFailureInterval
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// String returns a string representation of a mission control config.
|
// String returns a string representation of a mission control config.
|
||||||
func (c *MissionControlConfig) String() string {
|
func (c *MissionControlConfig) String() string {
|
||||||
return fmt.Sprintf("Penalty Half Life: %v, Apriori Hop "+
|
return fmt.Sprintf("Penalty Half Life: %v, Apriori Hop "+
|
||||||
@ -190,15 +205,17 @@ func NewMissionControl(db kvdb.Backend, self route.Vertex,
|
|||||||
|
|
||||||
log.Debugf("Instantiating mission control with config: %v", cfg)
|
log.Debugf("Instantiating mission control with config: %v", cfg)
|
||||||
|
|
||||||
|
if err := cfg.validate(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
store, err := newMissionControlStore(db, cfg.MaxMcHistory)
|
store, err := newMissionControlStore(db, cfg.MaxMcHistory)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
estimator := &probabilityEstimator{
|
estimator := &probabilityEstimator{
|
||||||
aprioriHopProbability: cfg.AprioriHopProbability,
|
ProbabilityEstimatorCfg: cfg.ProbabilityEstimatorCfg,
|
||||||
aprioriWeight: cfg.AprioriWeight,
|
|
||||||
penaltyHalfLife: cfg.PenaltyHalfLife,
|
|
||||||
prevSuccessProbability: prevSuccessProbability,
|
prevSuccessProbability: prevSuccessProbability,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,10 +80,12 @@ func (ctx *mcTestContext) restartMc() {
|
|||||||
mc, err := NewMissionControl(
|
mc, err := NewMissionControl(
|
||||||
ctx.db, mcTestSelf,
|
ctx.db, mcTestSelf,
|
||||||
&MissionControlConfig{
|
&MissionControlConfig{
|
||||||
|
ProbabilityEstimatorCfg: ProbabilityEstimatorCfg{
|
||||||
PenaltyHalfLife: testPenaltyHalfLife,
|
PenaltyHalfLife: testPenaltyHalfLife,
|
||||||
AprioriHopProbability: testAprioriHopProbability,
|
AprioriHopProbability: testAprioriHopProbability,
|
||||||
AprioriWeight: testAprioriWeight,
|
AprioriWeight: testAprioriWeight,
|
||||||
},
|
},
|
||||||
|
},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.t.Fatal(err)
|
ctx.t.Fatal(err)
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package routing
|
package routing
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"math"
|
"math"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -8,25 +9,61 @@ import (
|
|||||||
"github.com/lightningnetwork/lnd/routing/route"
|
"github.com/lightningnetwork/lnd/routing/route"
|
||||||
)
|
)
|
||||||
|
|
||||||
// probabilityEstimator returns node and pair probabilities based on historical
|
var (
|
||||||
// payment results.
|
// ErrInvalidHalflife is returned when we get an invalid half life.
|
||||||
type probabilityEstimator struct {
|
ErrInvalidHalflife = errors.New("penalty half life must be >= 0")
|
||||||
// penaltyHalfLife defines after how much time a penalized node or
|
|
||||||
|
// ErrInvalidHopProbability is returned when we get an invalid hop
|
||||||
|
// probability.
|
||||||
|
ErrInvalidHopProbability = errors.New("hop probability must be in [0;1]")
|
||||||
|
|
||||||
|
// ErrInvalidAprioriWeight is returned when we get an apriori weight
|
||||||
|
// that is out of range.
|
||||||
|
ErrInvalidAprioriWeight = errors.New("apriori weight must be in [0;1]")
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProbabilityEstimatorCfg contains configuration for our probability estimator.
|
||||||
|
type ProbabilityEstimatorCfg struct {
|
||||||
|
// PenaltyHalfLife defines after how much time a penalized node or
|
||||||
// channel is back at 50% probability.
|
// channel is back at 50% probability.
|
||||||
penaltyHalfLife time.Duration
|
PenaltyHalfLife time.Duration
|
||||||
|
|
||||||
// aprioriHopProbability is the assumed success probability of a hop in
|
// AprioriHopProbability is the assumed success probability of a hop in
|
||||||
// a route when no other information is available.
|
// a route when no other information is available.
|
||||||
aprioriHopProbability float64
|
AprioriHopProbability float64
|
||||||
|
|
||||||
// aprioriWeight is a value in the range [0, 1] that defines to what
|
// AprioriWeight is a value in the range [0, 1] that defines to what
|
||||||
// extent historical results should be extrapolated to untried
|
// extent historical results should be extrapolated to untried
|
||||||
// connections. Setting it to one will completely ignore historical
|
// connections. Setting it to one will completely ignore historical
|
||||||
// results and always assume the configured a priori probability for
|
// results and always assume the configured a priori probability for
|
||||||
// untried connections. A value of zero will ignore the a priori
|
// untried connections. A value of zero will ignore the a priori
|
||||||
// probability completely and only base the probability on historical
|
// probability completely and only base the probability on historical
|
||||||
// results, unless there are none available.
|
// results, unless there are none available.
|
||||||
aprioriWeight float64
|
AprioriWeight float64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p ProbabilityEstimatorCfg) validate() error {
|
||||||
|
if p.PenaltyHalfLife < 0 {
|
||||||
|
return ErrInvalidHalflife
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.AprioriHopProbability < 0 || p.AprioriHopProbability > 1 {
|
||||||
|
return ErrInvalidHopProbability
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.AprioriWeight < 0 || p.AprioriWeight > 1 {
|
||||||
|
return ErrInvalidAprioriWeight
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// probabilityEstimator returns node and pair probabilities based on historical
|
||||||
|
// payment results.
|
||||||
|
type probabilityEstimator struct {
|
||||||
|
// ProbabilityEstimatorCfg contains configuration options for our
|
||||||
|
// estimator.
|
||||||
|
ProbabilityEstimatorCfg
|
||||||
|
|
||||||
// prevSuccessProbability is the assumed probability for node pairs that
|
// prevSuccessProbability is the assumed probability for node pairs that
|
||||||
// successfully relayed the previous attempt.
|
// successfully relayed the previous attempt.
|
||||||
@ -41,14 +78,14 @@ func (p *probabilityEstimator) getNodeProbability(now time.Time,
|
|||||||
|
|
||||||
// If the channel history is not to be taken into account, we can return
|
// If the channel history is not to be taken into account, we can return
|
||||||
// early here with the configured a priori probability.
|
// early here with the configured a priori probability.
|
||||||
if p.aprioriWeight == 1 {
|
if p.AprioriWeight == 1 {
|
||||||
return p.aprioriHopProbability
|
return p.AprioriHopProbability
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there is no channel history, our best estimate is still the a
|
// If there is no channel history, our best estimate is still the a
|
||||||
// priori probability.
|
// priori probability.
|
||||||
if len(results) == 0 {
|
if len(results) == 0 {
|
||||||
return p.aprioriHopProbability
|
return p.AprioriHopProbability
|
||||||
}
|
}
|
||||||
|
|
||||||
// The value of the apriori weight is in the range [0, 1]. Convert it to
|
// The value of the apriori weight is in the range [0, 1]. Convert it to
|
||||||
@ -58,7 +95,7 @@ func (p *probabilityEstimator) getNodeProbability(now time.Time,
|
|||||||
// the weighted average calculation below. When the apriori weight
|
// the weighted average calculation below. When the apriori weight
|
||||||
// approaches 1, the apriori factor goes to infinity. It will heavily
|
// approaches 1, the apriori factor goes to infinity. It will heavily
|
||||||
// outweigh any observations that have been collected.
|
// outweigh any observations that have been collected.
|
||||||
aprioriFactor := 1/(1-p.aprioriWeight) - 1
|
aprioriFactor := 1/(1-p.AprioriWeight) - 1
|
||||||
|
|
||||||
// Calculate a weighted average consisting of the apriori probability
|
// Calculate a weighted average consisting of the apriori probability
|
||||||
// and historical observations. This is the part that incentivizes nodes
|
// and historical observations. This is the part that incentivizes nodes
|
||||||
@ -76,7 +113,7 @@ func (p *probabilityEstimator) getNodeProbability(now time.Time,
|
|||||||
// effectively prunes all channels of the node forever. This is the most
|
// effectively prunes all channels of the node forever. This is the most
|
||||||
// aggressive way in which we can penalize nodes and unlikely to yield
|
// aggressive way in which we can penalize nodes and unlikely to yield
|
||||||
// good results in a real network.
|
// good results in a real network.
|
||||||
probabilitiesTotal := p.aprioriHopProbability * aprioriFactor
|
probabilitiesTotal := p.AprioriHopProbability * aprioriFactor
|
||||||
totalWeight := aprioriFactor
|
totalWeight := aprioriFactor
|
||||||
|
|
||||||
for _, result := range results {
|
for _, result := range results {
|
||||||
@ -106,7 +143,7 @@ func (p *probabilityEstimator) getNodeProbability(now time.Time,
|
|||||||
// the result is fresh and asymptotically approaches zero over time. The rate at
|
// the result is fresh and asymptotically approaches zero over time. The rate at
|
||||||
// which this happens is controlled by the penaltyHalfLife parameter.
|
// which this happens is controlled by the penaltyHalfLife parameter.
|
||||||
func (p *probabilityEstimator) getWeight(age time.Duration) float64 {
|
func (p *probabilityEstimator) getWeight(age time.Duration) float64 {
|
||||||
exp := -age.Hours() / p.penaltyHalfLife.Hours()
|
exp := -age.Hours() / p.PenaltyHalfLife.Hours()
|
||||||
return math.Pow(2, exp)
|
return math.Pow(2, exp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,9 +40,11 @@ func newEstimatorTestContext(t *testing.T) *estimatorTestContext {
|
|||||||
return &estimatorTestContext{
|
return &estimatorTestContext{
|
||||||
t: t,
|
t: t,
|
||||||
estimator: &probabilityEstimator{
|
estimator: &probabilityEstimator{
|
||||||
aprioriHopProbability: aprioriHopProb,
|
ProbabilityEstimatorCfg: ProbabilityEstimatorCfg{
|
||||||
aprioriWeight: aprioriWeight,
|
AprioriHopProbability: aprioriHopProb,
|
||||||
penaltyHalfLife: time.Hour,
|
AprioriWeight: aprioriWeight,
|
||||||
|
PenaltyHalfLife: time.Hour,
|
||||||
|
},
|
||||||
prevSuccessProbability: aprioriPrevSucProb,
|
prevSuccessProbability: aprioriPrevSucProb,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -85,9 +85,11 @@ func createTestCtxFromGraphInstance(startingHeight uint32, graphInstance *testGr
|
|||||||
}
|
}
|
||||||
|
|
||||||
mcConfig := &MissionControlConfig{
|
mcConfig := &MissionControlConfig{
|
||||||
|
ProbabilityEstimatorCfg: ProbabilityEstimatorCfg{
|
||||||
PenaltyHalfLife: time.Hour,
|
PenaltyHalfLife: time.Hour,
|
||||||
AprioriHopProbability: 0.9,
|
AprioriHopProbability: 0.9,
|
||||||
AprioriWeight: 0.5,
|
AprioriWeight: 0.5,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
mc, err := NewMissionControl(
|
mc, err := NewMissionControl(
|
||||||
|
10
server.go
10
server.go
@ -725,13 +725,17 @@ func newServer(cfg *Config, listenAddrs []net.Addr,
|
|||||||
// servers, the mission control instance itself can be moved there too.
|
// servers, the mission control instance itself can be moved there too.
|
||||||
routingConfig := routerrpc.GetRoutingConfig(cfg.SubRPCServers.RouterRPC)
|
routingConfig := routerrpc.GetRoutingConfig(cfg.SubRPCServers.RouterRPC)
|
||||||
|
|
||||||
|
estimatorCfg := routing.ProbabilityEstimatorCfg{
|
||||||
|
AprioriHopProbability: routingConfig.AprioriHopProbability,
|
||||||
|
PenaltyHalfLife: routingConfig.PenaltyHalfLife,
|
||||||
|
AprioriWeight: routingConfig.AprioriWeight,
|
||||||
|
}
|
||||||
|
|
||||||
s.missionControl, err = routing.NewMissionControl(
|
s.missionControl, err = routing.NewMissionControl(
|
||||||
remoteChanDB, selfNode.PubKeyBytes,
|
remoteChanDB, selfNode.PubKeyBytes,
|
||||||
&routing.MissionControlConfig{
|
&routing.MissionControlConfig{
|
||||||
AprioriHopProbability: routingConfig.AprioriHopProbability,
|
ProbabilityEstimatorCfg: estimatorCfg,
|
||||||
PenaltyHalfLife: routingConfig.PenaltyHalfLife,
|
|
||||||
MaxMcHistory: routingConfig.MaxMcHistory,
|
MaxMcHistory: routingConfig.MaxMcHistory,
|
||||||
AprioriWeight: routingConfig.AprioriWeight,
|
|
||||||
MinFailureRelaxInterval: routing.DefaultMinFailureRelaxInterval,
|
MinFailureRelaxInterval: routing.DefaultMinFailureRelaxInterval,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user