lnd+routing+rpc: switch mc to (external) estimator

We use a more general `Estimator` interface for probability estimation
in missioncontrol.

The estimator is created outside of `NewMissionControl`, passed in as a
`MissionControlConfig` field, to facilitate usage of externally supplied
estimators.
This commit is contained in:
bitromortac
2023-01-20 11:25:53 +01:00
parent 686816d784
commit 16986ee5c7
8 changed files with 96 additions and 64 deletions

View File

@@ -465,6 +465,9 @@ type Config struct {
// ActiveNetParams contains parameters of the target chain. // ActiveNetParams contains parameters of the target chain.
ActiveNetParams chainreg.BitcoinNetParams ActiveNetParams chainreg.BitcoinNetParams
// Estimator is used to estimate routing probabilities.
Estimator routing.Estimator
} }
// DefaultConfig returns all default values for the Config struct. // DefaultConfig returns all default values for the Config struct.

View File

@@ -455,13 +455,23 @@ func (s *Server) GetMissionControlConfig(ctx context.Context,
error) { error) {
cfg := s.cfg.RouterBackend.MissionControl.GetConfig() cfg := s.cfg.RouterBackend.MissionControl.GetConfig()
eCfg, ok := cfg.Estimator.Config().(*routing.AprioriConfig)
if !ok {
return nil, fmt.Errorf("unknown estimator config type")
}
return &GetMissionControlConfigResponse{ return &GetMissionControlConfigResponse{
Config: &MissionControlConfig{ Config: &MissionControlConfig{
HalfLifeSeconds: uint64(cfg.PenaltyHalfLife.Seconds()), HalfLifeSeconds: uint64(
HopProbability: float32(cfg.AprioriHopProbability), eCfg.PenaltyHalfLife.Seconds()),
Weight: float32(cfg.AprioriWeight), HopProbability: float32(
MaximumPaymentResults: uint32(cfg.MaxMcHistory), eCfg.AprioriHopProbability,
MinimumFailureRelaxInterval: uint64(cfg.MinFailureRelaxInterval.Seconds()), ),
Weight: float32(eCfg.AprioriWeight),
MaximumPaymentResults: uint32(cfg.MaxMcHistory),
MinimumFailureRelaxInterval: uint64(
cfg.MinFailureRelaxInterval.Seconds(),
),
}, },
}, nil }, nil
} }
@@ -471,14 +481,20 @@ func (s *Server) SetMissionControlConfig(ctx context.Context,
req *SetMissionControlConfigRequest) (*SetMissionControlConfigResponse, req *SetMissionControlConfigRequest) (*SetMissionControlConfigResponse,
error) { error) {
aCfg := routing.AprioriConfig{
PenaltyHalfLife: time.Duration(
req.Config.HalfLifeSeconds,
) * time.Second,
AprioriHopProbability: float64(req.Config.HopProbability),
AprioriWeight: float64(req.Config.Weight),
}
estimator, err := routing.NewAprioriEstimator(aCfg)
if err != nil {
return nil, err
}
cfg := &routing.MissionControlConfig{ cfg := &routing.MissionControlConfig{
AprioriConfig: routing.AprioriConfig{ Estimator: estimator,
PenaltyHalfLife: time.Duration(
req.Config.HalfLifeSeconds,
) * time.Second,
AprioriHopProbability: float64(req.Config.HopProbability),
AprioriWeight: float64(req.Config.Weight),
},
MaxMcHistory: int(req.Config.MaximumPaymentResults), MaxMcHistory: int(req.Config.MaximumPaymentResults),
MinFailureRelaxInterval: time.Duration( MinFailureRelaxInterval: time.Duration(
req.Config.MinimumFailureRelaxInterval, req.Config.MinimumFailureRelaxInterval,

View File

@@ -11,6 +11,7 @@ import (
"github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing/route" "github.com/lightningnetwork/lnd/routing/route"
"github.com/lightningnetwork/lnd/zpay32" "github.com/lightningnetwork/lnd/zpay32"
"github.com/stretchr/testify/require"
) )
const ( const (
@@ -68,6 +69,14 @@ func newIntegratedRoutingContext(t *testing.T) *integratedRoutingContext {
// defaults would break the unit tests. The actual values picked aren't // defaults would break the unit tests. The actual values picked aren't
// critical to excite certain behavior, but do need to be aligned with // critical to excite certain behavior, but do need to be aligned with
// the test case assertions. // the test case assertions.
aCfg := AprioriConfig{
PenaltyHalfLife: 30 * time.Minute,
AprioriHopProbability: 0.6,
AprioriWeight: 0.5,
}
estimator, err := NewAprioriEstimator(aCfg)
require.NoError(t, err)
ctx := integratedRoutingContext{ ctx := integratedRoutingContext{
t: t, t: t,
graph: graph, graph: graph,
@@ -75,11 +84,7 @@ func newIntegratedRoutingContext(t *testing.T) *integratedRoutingContext {
finalExpiry: 40, finalExpiry: 40,
mcCfg: MissionControlConfig{ mcCfg: MissionControlConfig{
AprioriConfig: AprioriConfig{ Estimator: estimator,
PenaltyHalfLife: 30 * time.Minute,
AprioriHopProbability: 0.6,
AprioriWeight: 0.5,
},
}, },
pathFindingCfg: PathFindingConfig{ pathFindingCfg: PathFindingConfig{

View File

@@ -67,7 +67,10 @@ func TestProbabilityExtrapolation(t *testing.T) {
// If we use a static value for the node probability (no extrapolation // If we use a static value for the node probability (no extrapolation
// of data from other channels), all ten bad channels will be tried // of data from other channels), all ten bad channels will be tried
// first before switching to the paid channel. // first before switching to the paid channel.
ctx.mcCfg.AprioriWeight = 1 estimator, ok := ctx.mcCfg.Estimator.(*AprioriEstimator)
if ok {
estimator.AprioriWeight = 1
}
attempts, err = ctx.testPayment(1) attempts, err = ctx.testPayment(1)
require.NoError(t, err, "payment failed") require.NoError(t, err, "payment failed")
if len(attempts) != 11 { if len(attempts) != 11 {

View File

@@ -103,7 +103,7 @@ type MissionControl struct {
// estimator is the probability estimator that is used with the payment // estimator is the probability estimator that is used with the payment
// results that mission control collects. // results that mission control collects.
estimator *AprioriEstimator estimator Estimator
sync.Mutex sync.Mutex
@@ -116,8 +116,8 @@ 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 {
// AprioriConfig is the config we will use for probability calculations. // Estimator gives probability estimates for node pairs.
AprioriConfig Estimator Estimator
// 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.
@@ -134,10 +134,6 @@ type MissionControlConfig struct {
} }
func (c *MissionControlConfig) validate() error { func (c *MissionControlConfig) validate() error {
if err := c.AprioriConfig.validate(); err != nil {
return err
}
if c.MaxMcHistory < 0 { if c.MaxMcHistory < 0 {
return ErrInvalidMcHistory return ErrInvalidMcHistory
} }
@@ -151,11 +147,8 @@ func (c *MissionControlConfig) validate() error {
// 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("maximum history: %v, minimum failure relax "+
"Probablity: %v, Maximum History: %v, Apriori Weight: %v, "+ "interval: %v", c.MaxMcHistory, c.MinFailureRelaxInterval)
"Minimum Failure Relax Interval: %v", c.PenaltyHalfLife,
c.AprioriHopProbability, c.MaxMcHistory, c.AprioriWeight,
c.MinFailureRelaxInterval)
} }
// TimedPairResult describes a timestamped pair result. // TimedPairResult describes a timestamped pair result.
@@ -211,7 +204,8 @@ type paymentResult struct {
func NewMissionControl(db kvdb.Backend, self route.Vertex, func NewMissionControl(db kvdb.Backend, self route.Vertex,
cfg *MissionControlConfig) (*MissionControl, error) { cfg *MissionControlConfig) (*MissionControl, error) {
log.Debugf("Instantiating mission control with config: %v", cfg) log.Debugf("Instantiating mission control with config: %v, %v", cfg,
cfg.Estimator)
if err := cfg.validate(); err != nil { if err := cfg.validate(); err != nil {
return nil, err return nil, err
@@ -224,17 +218,12 @@ func NewMissionControl(db kvdb.Backend, self route.Vertex,
return nil, err return nil, err
} }
estimator := &AprioriEstimator{
AprioriConfig: cfg.AprioriConfig,
prevSuccessProbability: prevSuccessProbability,
}
mc := &MissionControl{ mc := &MissionControl{
state: newMissionControlState(cfg.MinFailureRelaxInterval), state: newMissionControlState(cfg.MinFailureRelaxInterval),
now: time.Now, now: time.Now,
selfNode: self, selfNode: self,
store: store, store: store,
estimator: estimator, estimator: cfg.Estimator,
} }
if err := mc.init(); err != nil { if err := mc.init(); err != nil {
@@ -283,7 +272,7 @@ func (m *MissionControl) GetConfig() *MissionControlConfig {
defer m.Unlock() defer m.Unlock()
return &MissionControlConfig{ return &MissionControlConfig{
AprioriConfig: m.estimator.AprioriConfig, Estimator: m.estimator,
MaxMcHistory: m.store.maxRecords, MaxMcHistory: m.store.maxRecords,
McFlushInterval: m.store.flushInterval, McFlushInterval: m.store.flushInterval,
MinFailureRelaxInterval: m.state.minFailureRelaxInterval, MinFailureRelaxInterval: m.state.minFailureRelaxInterval,
@@ -304,11 +293,12 @@ func (m *MissionControl) SetConfig(cfg *MissionControlConfig) error {
m.Lock() m.Lock()
defer m.Unlock() defer m.Unlock()
log.Infof("Updating mission control cfg: %v", cfg) log.Infof("Active mission control cfg: %v, estimator: %v", cfg,
cfg.Estimator)
m.store.maxRecords = cfg.MaxMcHistory m.store.maxRecords = cfg.MaxMcHistory
m.state.minFailureRelaxInterval = cfg.MinFailureRelaxInterval m.state.minFailureRelaxInterval = cfg.MinFailureRelaxInterval
m.estimator.AprioriConfig = cfg.AprioriConfig m.estimator = cfg.Estimator
return nil return nil
} }

View File

@@ -90,15 +90,17 @@ func (ctx *mcTestContext) restartMc() {
require.NoError(ctx.t, ctx.mc.store.storeResults()) require.NoError(ctx.t, ctx.mc.store.storeResults())
} }
aCfg := AprioriConfig{
PenaltyHalfLife: testPenaltyHalfLife,
AprioriHopProbability: testAprioriHopProbability,
AprioriWeight: testAprioriWeight,
}
estimator, err := NewAprioriEstimator(aCfg)
require.NoError(ctx.t, err)
mc, err := NewMissionControl( mc, err := NewMissionControl(
ctx.db, mcTestSelf, ctx.db, mcTestSelf,
&MissionControlConfig{ &MissionControlConfig{Estimator: estimator},
AprioriConfig: AprioriConfig{
PenaltyHalfLife: testPenaltyHalfLife,
AprioriHopProbability: testAprioriHopProbability,
AprioriWeight: testAprioriWeight,
},
},
) )
if err != nil { if err != nil {
ctx.t.Fatal(err) ctx.t.Fatal(err)

View File

@@ -119,13 +119,15 @@ func createTestCtxFromGraphInstanceAssumeValid(t *testing.T,
AttemptCost: 100, AttemptCost: 100,
} }
mcConfig := &MissionControlConfig{ aCfg := AprioriConfig{
AprioriConfig: AprioriConfig{ PenaltyHalfLife: time.Hour,
PenaltyHalfLife: time.Hour, AprioriHopProbability: 0.9,
AprioriHopProbability: 0.9, AprioriWeight: 0.5,
AprioriWeight: 0.5,
},
} }
estimator, err := NewAprioriEstimator(aCfg)
require.NoError(t, err)
mcConfig := &MissionControlConfig{Estimator: estimator}
mc, err := NewMissionControl( mc, err := NewMissionControl(
graphInstance.graphBackend, route.Vertex{}, mcConfig, graphInstance.graphBackend, route.Vertex{}, mcConfig,

View File

@@ -868,20 +868,31 @@ 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.AprioriConfig{ // We only initialize a probability estimator if there's no custom one.
AprioriHopProbability: routingConfig.AprioriHopProbability, var estimator routing.Estimator
PenaltyHalfLife: routingConfig.PenaltyHalfLife, if cfg.Estimator != nil {
AprioriWeight: routingConfig.AprioriWeight, estimator = cfg.Estimator
} else {
aCfg := routing.AprioriConfig{
AprioriHopProbability: routingConfig.
AprioriHopProbability,
PenaltyHalfLife: routingConfig.PenaltyHalfLife,
AprioriWeight: routingConfig.AprioriWeight,
}
estimator, err = routing.NewAprioriEstimator(aCfg)
if err != nil {
return nil, err
}
} }
mcCfg := &routing.MissionControlConfig{
Estimator: estimator,
MaxMcHistory: routingConfig.MaxMcHistory,
McFlushInterval: routingConfig.McFlushInterval,
MinFailureRelaxInterval: routing.DefaultMinFailureRelaxInterval,
}
s.missionControl, err = routing.NewMissionControl( s.missionControl, err = routing.NewMissionControl(
dbs.ChanStateDB, selfNode.PubKeyBytes, dbs.ChanStateDB, selfNode.PubKeyBytes, mcCfg,
&routing.MissionControlConfig{
AprioriConfig: estimatorCfg,
MaxMcHistory: routingConfig.MaxMcHistory,
McFlushInterval: routingConfig.McFlushInterval,
MinFailureRelaxInterval: routing.DefaultMinFailureRelaxInterval,
},
) )
if err != nil { if err != nil {
return nil, fmt.Errorf("can't create mission control: %v", err) return nil, fmt.Errorf("can't create mission control: %v", err)