Browse Source

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.
master
carla 3 years ago
parent
commit
e10e8f11de
No known key found for this signature in database
GPG Key ID: 4CA7FE54A6213C91
  1. 8
      routing/integrated_routing_context_test.go
  2. 57
      routing/missioncontrol.go
  3. 8
      routing/missioncontrol_test.go
  4. 67
      routing/probability_estimator.go
  5. 8
      routing/probability_estimator_test.go
  6. 8
      routing/router_test.go
  7. 10
      server.go

8
routing/integrated_routing_context_test.go

@ -58,9 +58,11 @@ func newIntegratedRoutingContext(t *testing.T) *integratedRoutingContext {
finalExpiry: 40,
mcCfg: MissionControlConfig{
PenaltyHalfLife: 30 * time.Minute,
AprioriHopProbability: 0.6,
AprioriWeight: 0.5,
ProbabilityEstimatorCfg: ProbabilityEstimatorCfg{
PenaltyHalfLife: 30 * time.Minute,
AprioriHopProbability: 0.6,
AprioriWeight: 0.5,
},
},
pathFindingCfg: PathFindingConfig{

57
routing/missioncontrol.go

@ -1,6 +1,7 @@
package routing
import (
"errors"
"fmt"
"sync"
"time"
@ -58,6 +59,17 @@ const (
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.
type NodeResults map[route.Vertex]TimedPairResult
@ -99,33 +111,36 @@ type MissionControl struct {
// 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
// AprioriHopProbability is the assumed success probability of a hop in
// a route when no other information is available.
AprioriHopProbability float64
// ProbabilityEstimatorConfig is the config we will use for probability
// calculations.
ProbabilityEstimatorCfg
// MaxMcHistory defines the maximum number of payment results that are
// held on disk.
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
// since the previously recorded failure before the failure amount may
// be raised.
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.
func (c *MissionControlConfig) String() string {
return fmt.Sprintf("Penalty Half Life: %v, Apriori Hop "+
@ -190,16 +205,18 @@ func NewMissionControl(db kvdb.Backend, self route.Vertex,
log.Debugf("Instantiating mission control with config: %v", cfg)
if err := cfg.validate(); err != nil {
return nil, err
}
store, err := newMissionControlStore(db, cfg.MaxMcHistory)
if err != nil {
return nil, err
}
estimator := &probabilityEstimator{
aprioriHopProbability: cfg.AprioriHopProbability,
aprioriWeight: cfg.AprioriWeight,
penaltyHalfLife: cfg.PenaltyHalfLife,
prevSuccessProbability: prevSuccessProbability,
ProbabilityEstimatorCfg: cfg.ProbabilityEstimatorCfg,
prevSuccessProbability: prevSuccessProbability,
}
mc := &MissionControl{

8
routing/missioncontrol_test.go

@ -80,9 +80,11 @@ func (ctx *mcTestContext) restartMc() {
mc, err := NewMissionControl(
ctx.db, mcTestSelf,
&MissionControlConfig{
PenaltyHalfLife: testPenaltyHalfLife,
AprioriHopProbability: testAprioriHopProbability,
AprioriWeight: testAprioriWeight,
ProbabilityEstimatorCfg: ProbabilityEstimatorCfg{
PenaltyHalfLife: testPenaltyHalfLife,
AprioriHopProbability: testAprioriHopProbability,
AprioriWeight: testAprioriWeight,
},
},
)
if err != nil {

67
routing/probability_estimator.go

@ -1,6 +1,7 @@
package routing
import (
"errors"
"math"
"time"
@ -8,25 +9,61 @@ import (
"github.com/lightningnetwork/lnd/routing/route"
)
// probabilityEstimator returns node and pair probabilities based on historical
// payment results.
type probabilityEstimator struct {
// penaltyHalfLife defines after how much time a penalized node or
var (
// ErrInvalidHalflife is returned when we get an invalid half life.
ErrInvalidHalflife = errors.New("penalty half life must be >= 0")
// 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.
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.
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
// 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
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
// 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
// early here with the configured a priori probability.
if p.aprioriWeight == 1 {
return p.aprioriHopProbability
if p.AprioriWeight == 1 {
return p.AprioriHopProbability
}
// If there is no channel history, our best estimate is still the a
// priori probability.
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
@ -58,7 +95,7 @@ func (p *probabilityEstimator) getNodeProbability(now time.Time,
// the weighted average calculation below. When the apriori weight
// approaches 1, the apriori factor goes to infinity. It will heavily
// 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
// 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
// aggressive way in which we can penalize nodes and unlikely to yield
// good results in a real network.
probabilitiesTotal := p.aprioriHopProbability * aprioriFactor
probabilitiesTotal := p.AprioriHopProbability * aprioriFactor
totalWeight := aprioriFactor
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
// which this happens is controlled by the penaltyHalfLife parameter.
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)
}

8
routing/probability_estimator_test.go

@ -40,9 +40,11 @@ func newEstimatorTestContext(t *testing.T) *estimatorTestContext {
return &estimatorTestContext{
t: t,
estimator: &probabilityEstimator{
aprioriHopProbability: aprioriHopProb,
aprioriWeight: aprioriWeight,
penaltyHalfLife: time.Hour,
ProbabilityEstimatorCfg: ProbabilityEstimatorCfg{
AprioriHopProbability: aprioriHopProb,
AprioriWeight: aprioriWeight,
PenaltyHalfLife: time.Hour,
},
prevSuccessProbability: aprioriPrevSucProb,
},
}

8
routing/router_test.go

@ -85,9 +85,11 @@ func createTestCtxFromGraphInstance(startingHeight uint32, graphInstance *testGr
}
mcConfig := &MissionControlConfig{
PenaltyHalfLife: time.Hour,
AprioriHopProbability: 0.9,
AprioriWeight: 0.5,
ProbabilityEstimatorCfg: ProbabilityEstimatorCfg{
PenaltyHalfLife: time.Hour,
AprioriHopProbability: 0.9,
AprioriWeight: 0.5,
},
}
mc, err := NewMissionControl(

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.
routingConfig := routerrpc.GetRoutingConfig(cfg.SubRPCServers.RouterRPC)
estimatorCfg := routing.ProbabilityEstimatorCfg{
AprioriHopProbability: routingConfig.AprioriHopProbability,
PenaltyHalfLife: routingConfig.PenaltyHalfLife,
AprioriWeight: routingConfig.AprioriWeight,
}
s.missionControl, err = routing.NewMissionControl(
remoteChanDB, selfNode.PubKeyBytes,
&routing.MissionControlConfig{
AprioriHopProbability: routingConfig.AprioriHopProbability,
PenaltyHalfLife: routingConfig.PenaltyHalfLife,
ProbabilityEstimatorCfg: estimatorCfg,
MaxMcHistory: routingConfig.MaxMcHistory,
AprioriWeight: routingConfig.AprioriWeight,
MinFailureRelaxInterval: routing.DefaultMinFailureRelaxInterval,
},
)

Loading…
Cancel
Save