From 16986ee5c7c5c8d08c08e61fa1f46da296b92d43 Mon Sep 17 00:00:00 2001 From: bitromortac Date: Fri, 20 Jan 2023 11:25:53 +0100 Subject: [PATCH] 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. --- config.go | 3 ++ lnrpc/routerrpc/router_server.go | 40 +++++++++++++++------- routing/integrated_routing_context_test.go | 15 +++++--- routing/integrated_routing_test.go | 5 ++- routing/missioncontrol.go | 34 +++++++----------- routing/missioncontrol_test.go | 16 +++++---- routing/router_test.go | 14 ++++---- server.go | 33 ++++++++++++------ 8 files changed, 96 insertions(+), 64 deletions(-) diff --git a/config.go b/config.go index 6e8b5eb9c..dca79551f 100644 --- a/config.go +++ b/config.go @@ -465,6 +465,9 @@ type Config struct { // ActiveNetParams contains parameters of the target chain. ActiveNetParams chainreg.BitcoinNetParams + + // Estimator is used to estimate routing probabilities. + Estimator routing.Estimator } // DefaultConfig returns all default values for the Config struct. diff --git a/lnrpc/routerrpc/router_server.go b/lnrpc/routerrpc/router_server.go index b0bc4bcf6..8f5cdf1a4 100644 --- a/lnrpc/routerrpc/router_server.go +++ b/lnrpc/routerrpc/router_server.go @@ -455,13 +455,23 @@ func (s *Server) GetMissionControlConfig(ctx context.Context, error) { 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{ Config: &MissionControlConfig{ - HalfLifeSeconds: uint64(cfg.PenaltyHalfLife.Seconds()), - HopProbability: float32(cfg.AprioriHopProbability), - Weight: float32(cfg.AprioriWeight), - MaximumPaymentResults: uint32(cfg.MaxMcHistory), - MinimumFailureRelaxInterval: uint64(cfg.MinFailureRelaxInterval.Seconds()), + HalfLifeSeconds: uint64( + eCfg.PenaltyHalfLife.Seconds()), + HopProbability: float32( + eCfg.AprioriHopProbability, + ), + Weight: float32(eCfg.AprioriWeight), + MaximumPaymentResults: uint32(cfg.MaxMcHistory), + MinimumFailureRelaxInterval: uint64( + cfg.MinFailureRelaxInterval.Seconds(), + ), }, }, nil } @@ -471,14 +481,20 @@ func (s *Server) SetMissionControlConfig(ctx context.Context, req *SetMissionControlConfigRequest) (*SetMissionControlConfigResponse, 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{ - AprioriConfig: routing.AprioriConfig{ - PenaltyHalfLife: time.Duration( - req.Config.HalfLifeSeconds, - ) * time.Second, - AprioriHopProbability: float64(req.Config.HopProbability), - AprioriWeight: float64(req.Config.Weight), - }, + Estimator: estimator, MaxMcHistory: int(req.Config.MaximumPaymentResults), MinFailureRelaxInterval: time.Duration( req.Config.MinimumFailureRelaxInterval, diff --git a/routing/integrated_routing_context_test.go b/routing/integrated_routing_context_test.go index 7f5210b43..0ab0c1b01 100644 --- a/routing/integrated_routing_context_test.go +++ b/routing/integrated_routing_context_test.go @@ -11,6 +11,7 @@ import ( "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/routing/route" "github.com/lightningnetwork/lnd/zpay32" + "github.com/stretchr/testify/require" ) const ( @@ -68,6 +69,14 @@ func newIntegratedRoutingContext(t *testing.T) *integratedRoutingContext { // defaults would break the unit tests. The actual values picked aren't // critical to excite certain behavior, but do need to be aligned with // 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{ t: t, graph: graph, @@ -75,11 +84,7 @@ func newIntegratedRoutingContext(t *testing.T) *integratedRoutingContext { finalExpiry: 40, mcCfg: MissionControlConfig{ - AprioriConfig: AprioriConfig{ - PenaltyHalfLife: 30 * time.Minute, - AprioriHopProbability: 0.6, - AprioriWeight: 0.5, - }, + Estimator: estimator, }, pathFindingCfg: PathFindingConfig{ diff --git a/routing/integrated_routing_test.go b/routing/integrated_routing_test.go index 2dd2cf632..f4c99a9a1 100644 --- a/routing/integrated_routing_test.go +++ b/routing/integrated_routing_test.go @@ -67,7 +67,10 @@ func TestProbabilityExtrapolation(t *testing.T) { // If we use a static value for the node probability (no extrapolation // of data from other channels), all ten bad channels will be tried // 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) require.NoError(t, err, "payment failed") if len(attempts) != 11 { diff --git a/routing/missioncontrol.go b/routing/missioncontrol.go index f800cac64..201777af3 100644 --- a/routing/missioncontrol.go +++ b/routing/missioncontrol.go @@ -103,7 +103,7 @@ type MissionControl struct { // estimator is the probability estimator that is used with the payment // results that mission control collects. - estimator *AprioriEstimator + estimator Estimator sync.Mutex @@ -116,8 +116,8 @@ type MissionControl struct { // MissionControlConfig defines parameters that control mission control // behaviour. type MissionControlConfig struct { - // AprioriConfig is the config we will use for probability calculations. - AprioriConfig + // Estimator gives probability estimates for node pairs. + Estimator Estimator // MaxMcHistory defines the maximum number of payment results that are // held on disk. @@ -134,10 +134,6 @@ type MissionControlConfig struct { } func (c *MissionControlConfig) validate() error { - if err := c.AprioriConfig.validate(); err != nil { - return err - } - if c.MaxMcHistory < 0 { return ErrInvalidMcHistory } @@ -151,11 +147,8 @@ func (c *MissionControlConfig) validate() error { // String returns a string representation of a mission control config. func (c *MissionControlConfig) String() string { - return fmt.Sprintf("Penalty Half Life: %v, Apriori Hop "+ - "Probablity: %v, Maximum History: %v, Apriori Weight: %v, "+ - "Minimum Failure Relax Interval: %v", c.PenaltyHalfLife, - c.AprioriHopProbability, c.MaxMcHistory, c.AprioriWeight, - c.MinFailureRelaxInterval) + return fmt.Sprintf("maximum history: %v, minimum failure relax "+ + "interval: %v", c.MaxMcHistory, c.MinFailureRelaxInterval) } // TimedPairResult describes a timestamped pair result. @@ -211,7 +204,8 @@ type paymentResult struct { func NewMissionControl(db kvdb.Backend, self route.Vertex, 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 { return nil, err @@ -224,17 +218,12 @@ func NewMissionControl(db kvdb.Backend, self route.Vertex, return nil, err } - estimator := &AprioriEstimator{ - AprioriConfig: cfg.AprioriConfig, - prevSuccessProbability: prevSuccessProbability, - } - mc := &MissionControl{ state: newMissionControlState(cfg.MinFailureRelaxInterval), now: time.Now, selfNode: self, store: store, - estimator: estimator, + estimator: cfg.Estimator, } if err := mc.init(); err != nil { @@ -283,7 +272,7 @@ func (m *MissionControl) GetConfig() *MissionControlConfig { defer m.Unlock() return &MissionControlConfig{ - AprioriConfig: m.estimator.AprioriConfig, + Estimator: m.estimator, MaxMcHistory: m.store.maxRecords, McFlushInterval: m.store.flushInterval, MinFailureRelaxInterval: m.state.minFailureRelaxInterval, @@ -304,11 +293,12 @@ func (m *MissionControl) SetConfig(cfg *MissionControlConfig) error { m.Lock() 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.state.minFailureRelaxInterval = cfg.MinFailureRelaxInterval - m.estimator.AprioriConfig = cfg.AprioriConfig + m.estimator = cfg.Estimator return nil } diff --git a/routing/missioncontrol_test.go b/routing/missioncontrol_test.go index 1b4007260..91cc8eaa0 100644 --- a/routing/missioncontrol_test.go +++ b/routing/missioncontrol_test.go @@ -90,15 +90,17 @@ func (ctx *mcTestContext) restartMc() { 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( ctx.db, mcTestSelf, - &MissionControlConfig{ - AprioriConfig: AprioriConfig{ - PenaltyHalfLife: testPenaltyHalfLife, - AprioriHopProbability: testAprioriHopProbability, - AprioriWeight: testAprioriWeight, - }, - }, + &MissionControlConfig{Estimator: estimator}, ) if err != nil { ctx.t.Fatal(err) diff --git a/routing/router_test.go b/routing/router_test.go index 5922b71af..8701d8b28 100644 --- a/routing/router_test.go +++ b/routing/router_test.go @@ -119,13 +119,15 @@ func createTestCtxFromGraphInstanceAssumeValid(t *testing.T, AttemptCost: 100, } - mcConfig := &MissionControlConfig{ - AprioriConfig: AprioriConfig{ - PenaltyHalfLife: time.Hour, - AprioriHopProbability: 0.9, - AprioriWeight: 0.5, - }, + aCfg := AprioriConfig{ + PenaltyHalfLife: time.Hour, + AprioriHopProbability: 0.9, + AprioriWeight: 0.5, } + estimator, err := NewAprioriEstimator(aCfg) + require.NoError(t, err) + + mcConfig := &MissionControlConfig{Estimator: estimator} mc, err := NewMissionControl( graphInstance.graphBackend, route.Vertex{}, mcConfig, diff --git a/server.go b/server.go index d23a532bc..592e061bb 100644 --- a/server.go +++ b/server.go @@ -868,20 +868,31 @@ 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.AprioriConfig{ - AprioriHopProbability: routingConfig.AprioriHopProbability, - PenaltyHalfLife: routingConfig.PenaltyHalfLife, - AprioriWeight: routingConfig.AprioriWeight, + // We only initialize a probability estimator if there's no custom one. + var estimator routing.Estimator + if cfg.Estimator != nil { + 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( - dbs.ChanStateDB, selfNode.PubKeyBytes, - &routing.MissionControlConfig{ - AprioriConfig: estimatorCfg, - MaxMcHistory: routingConfig.MaxMcHistory, - McFlushInterval: routingConfig.McFlushInterval, - MinFailureRelaxInterval: routing.DefaultMinFailureRelaxInterval, - }, + dbs.ChanStateDB, selfNode.PubKeyBytes, mcCfg, ) if err != nil { return nil, fmt.Errorf("can't create mission control: %v", err)