From b0a998af8da62895c7a0aff695a222d51564dd5a Mon Sep 17 00:00:00 2001 From: bitromortac Date: Fri, 18 Nov 2022 10:13:45 +0100 Subject: [PATCH 1/7] routing: rename apriori estimator * we rename the current probability estimator to be the "apriori" probability estimator to distinguish from a different implementation later * the AprioriEstimator is exported to later be able to type switch * getLocalPairProbability -> LocalPairProbabiltiy (later part of an exported interface) * getPairProbability -> getPairProbabiltiy (later part of an exported interface) --- lnrpc/routerrpc/router_server.go | 2 +- routing/integrated_routing_context_test.go | 2 +- routing/missioncontrol.go | 23 ++++++----- routing/missioncontrol_test.go | 2 +- ...ty_estimator.go => probability_apriori.go} | 38 +++++++++---------- ...or_test.go => probability_apriori_test.go} | 8 ++-- routing/router_test.go | 2 +- server.go | 4 +- 8 files changed, 40 insertions(+), 41 deletions(-) rename routing/{probability_estimator.go => probability_apriori.go} (91%) rename routing/{probability_estimator_test.go => probability_apriori_test.go} (97%) diff --git a/lnrpc/routerrpc/router_server.go b/lnrpc/routerrpc/router_server.go index 44335b84b..b0bc4bcf6 100644 --- a/lnrpc/routerrpc/router_server.go +++ b/lnrpc/routerrpc/router_server.go @@ -472,7 +472,7 @@ func (s *Server) SetMissionControlConfig(ctx context.Context, error) { cfg := &routing.MissionControlConfig{ - ProbabilityEstimatorCfg: routing.ProbabilityEstimatorCfg{ + AprioriConfig: routing.AprioriConfig{ PenaltyHalfLife: time.Duration( req.Config.HalfLifeSeconds, ) * time.Second, diff --git a/routing/integrated_routing_context_test.go b/routing/integrated_routing_context_test.go index 3c5b63c6f..7f5210b43 100644 --- a/routing/integrated_routing_context_test.go +++ b/routing/integrated_routing_context_test.go @@ -75,7 +75,7 @@ func newIntegratedRoutingContext(t *testing.T) *integratedRoutingContext { finalExpiry: 40, mcCfg: MissionControlConfig{ - ProbabilityEstimatorCfg: ProbabilityEstimatorCfg{ + AprioriConfig: AprioriConfig{ PenaltyHalfLife: 30 * time.Minute, AprioriHopProbability: 0.6, AprioriWeight: 0.5, diff --git a/routing/missioncontrol.go b/routing/missioncontrol.go index 86de4bc87..f800cac64 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 *probabilityEstimator + estimator *AprioriEstimator sync.Mutex @@ -116,9 +116,8 @@ type MissionControl struct { // MissionControlConfig defines parameters that control mission control // behaviour. type MissionControlConfig struct { - // ProbabilityEstimatorConfig is the config we will use for probability - // calculations. - ProbabilityEstimatorCfg + // AprioriConfig is the config we will use for probability calculations. + AprioriConfig // MaxMcHistory defines the maximum number of payment results that are // held on disk. @@ -135,7 +134,7 @@ type MissionControlConfig struct { } func (c *MissionControlConfig) validate() error { - if err := c.ProbabilityEstimatorCfg.validate(); err != nil { + if err := c.AprioriConfig.validate(); err != nil { return err } @@ -225,9 +224,9 @@ func NewMissionControl(db kvdb.Backend, self route.Vertex, return nil, err } - estimator := &probabilityEstimator{ - ProbabilityEstimatorCfg: cfg.ProbabilityEstimatorCfg, - prevSuccessProbability: prevSuccessProbability, + estimator := &AprioriEstimator{ + AprioriConfig: cfg.AprioriConfig, + prevSuccessProbability: prevSuccessProbability, } mc := &MissionControl{ @@ -284,7 +283,7 @@ func (m *MissionControl) GetConfig() *MissionControlConfig { defer m.Unlock() return &MissionControlConfig{ - ProbabilityEstimatorCfg: m.estimator.ProbabilityEstimatorCfg, + AprioriConfig: m.estimator.AprioriConfig, MaxMcHistory: m.store.maxRecords, McFlushInterval: m.store.flushInterval, MinFailureRelaxInterval: m.state.minFailureRelaxInterval, @@ -309,7 +308,7 @@ func (m *MissionControl) SetConfig(cfg *MissionControlConfig) error { m.store.maxRecords = cfg.MaxMcHistory m.state.minFailureRelaxInterval = cfg.MinFailureRelaxInterval - m.estimator.ProbabilityEstimatorCfg = cfg.ProbabilityEstimatorCfg + m.estimator.AprioriConfig = cfg.AprioriConfig return nil } @@ -344,10 +343,10 @@ func (m *MissionControl) GetProbability(fromNode, toNode route.Vertex, // Use a distinct probability estimation function for local channels. if fromNode == m.selfNode { - return m.estimator.getLocalPairProbability(now, results, toNode) + return m.estimator.LocalPairProbability(now, results, toNode) } - return m.estimator.getPairProbability( + return m.estimator.PairProbability( now, results, toNode, amt, capacity, ) } diff --git a/routing/missioncontrol_test.go b/routing/missioncontrol_test.go index 6bbe4aaf5..1b4007260 100644 --- a/routing/missioncontrol_test.go +++ b/routing/missioncontrol_test.go @@ -93,7 +93,7 @@ func (ctx *mcTestContext) restartMc() { mc, err := NewMissionControl( ctx.db, mcTestSelf, &MissionControlConfig{ - ProbabilityEstimatorCfg: ProbabilityEstimatorCfg{ + AprioriConfig: AprioriConfig{ PenaltyHalfLife: testPenaltyHalfLife, AprioriHopProbability: testAprioriHopProbability, AprioriWeight: testAprioriWeight, diff --git a/routing/probability_estimator.go b/routing/probability_apriori.go similarity index 91% rename from routing/probability_estimator.go rename to routing/probability_apriori.go index 320c16bda..93d11ef26 100644 --- a/routing/probability_estimator.go +++ b/routing/probability_apriori.go @@ -53,15 +53,16 @@ var ( // ErrInvalidHopProbability is returned when we get an invalid hop // probability. - ErrInvalidHopProbability = errors.New("hop probability must be in [0;1]") + 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]") + ErrInvalidAprioriWeight = errors.New("apriori weight must be in [0, 1]") ) -// ProbabilityEstimatorCfg contains configuration for our probability estimator. -type ProbabilityEstimatorCfg struct { +// AprioriConfig contains configuration for our probability estimator. +type AprioriConfig struct { // PenaltyHalfLife defines after how much time a penalized node or // channel is back at 50% probability. PenaltyHalfLife time.Duration @@ -80,7 +81,7 @@ type ProbabilityEstimatorCfg struct { AprioriWeight float64 } -func (p ProbabilityEstimatorCfg) validate() error { +func (p AprioriConfig) validate() error { if p.PenaltyHalfLife < 0 { return ErrInvalidHalflife } @@ -96,12 +97,11 @@ func (p ProbabilityEstimatorCfg) validate() error { return nil } -// probabilityEstimator returns node and pair probabilities based on historical +// AprioriEstimator returns node and pair probabilities based on historical // payment results. -type probabilityEstimator struct { - // ProbabilityEstimatorCfg contains configuration options for our - // estimator. - ProbabilityEstimatorCfg +type AprioriEstimator struct { + // AprioriConfig contains configuration options for our estimator. + AprioriConfig // prevSuccessProbability is the assumed probability for node pairs that // successfully relayed the previous attempt. @@ -111,7 +111,7 @@ type probabilityEstimator struct { // getNodeProbability calculates the probability for connections from a node // that have not been tried before. The results parameter is a list of last // payment results for that node. -func (p *probabilityEstimator) getNodeProbability(now time.Time, +func (p *AprioriEstimator) getNodeProbability(now time.Time, results NodeResults, amt lnwire.MilliSatoshi, capacity btcutil.Amount) float64 { @@ -184,7 +184,7 @@ func (p *probabilityEstimator) getNodeProbability(now time.Time, // a payment result. Weight follows an exponential curve that starts at 1 when // 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 { +func (p *AprioriEstimator) getWeight(age time.Duration) float64 { exp := -age.Hours() / p.PenaltyHalfLife.Hours() return math.Pow(2, exp) } @@ -219,12 +219,12 @@ func capacityFactor(amt lnwire.MilliSatoshi, capacity btcutil.Amount) float64 { return 1 - 1/denominator } -// getPairProbability estimates the probability of successfully traversing to +// PairProbability estimates the probability of successfully traversing to // toNode based on historical payment outcomes for the from node. Those outcomes // are passed in via the results parameter. -func (p *probabilityEstimator) getPairProbability( - now time.Time, results NodeResults, toNode route.Vertex, - amt lnwire.MilliSatoshi, capacity btcutil.Amount) float64 { +func (p *AprioriEstimator) PairProbability(now time.Time, + results NodeResults, toNode route.Vertex, amt lnwire.MilliSatoshi, + capacity btcutil.Amount) float64 { nodeProbability := p.getNodeProbability(now, results, amt, capacity) @@ -233,9 +233,9 @@ func (p *probabilityEstimator) getPairProbability( ) } -// getLocalPairProbability estimates the probability of successfully traversing +// LocalPairProbability estimates the probability of successfully traversing // our own local channels to toNode. -func (p *probabilityEstimator) getLocalPairProbability( +func (p *AprioriEstimator) LocalPairProbability( now time.Time, results NodeResults, toNode route.Vertex) float64 { // For local channels that have never been tried before, we assume them @@ -251,7 +251,7 @@ func (p *probabilityEstimator) getLocalPairProbability( // calculateProbability estimates the probability of successfully traversing to // toNode based on historical payment outcomes and a fall-back node probability. -func (p *probabilityEstimator) calculateProbability( +func (p *AprioriEstimator) calculateProbability( now time.Time, results NodeResults, nodeProbability float64, toNode route.Vertex, amt lnwire.MilliSatoshi) float64 { diff --git a/routing/probability_estimator_test.go b/routing/probability_apriori_test.go similarity index 97% rename from routing/probability_estimator_test.go rename to routing/probability_apriori_test.go index 2e683fc2d..db11aa12c 100644 --- a/routing/probability_estimator_test.go +++ b/routing/probability_apriori_test.go @@ -36,7 +36,7 @@ const ( type estimatorTestContext struct { t *testing.T - estimator *probabilityEstimator + estimator *AprioriEstimator // results contains a list of last results. Every element in the list // corresponds to the last result towards a node. The list index equals @@ -48,8 +48,8 @@ type estimatorTestContext struct { func newEstimatorTestContext(t *testing.T) *estimatorTestContext { return &estimatorTestContext{ t: t, - estimator: &probabilityEstimator{ - ProbabilityEstimatorCfg: ProbabilityEstimatorCfg{ + estimator: &AprioriEstimator{ + AprioriConfig: AprioriConfig{ AprioriHopProbability: aprioriHopProb, AprioriWeight: aprioriWeight, PenaltyHalfLife: time.Hour, @@ -74,7 +74,7 @@ func (c *estimatorTestContext) assertPairProbability(now time.Time, const tolerance = 0.01 - p := c.estimator.getPairProbability( + p := c.estimator.PairProbability( now, results, route.Vertex{toNode}, amt, capacity, ) diff := p - expectedProb diff --git a/routing/router_test.go b/routing/router_test.go index 0346721e3..5922b71af 100644 --- a/routing/router_test.go +++ b/routing/router_test.go @@ -120,7 +120,7 @@ func createTestCtxFromGraphInstanceAssumeValid(t *testing.T, } mcConfig := &MissionControlConfig{ - ProbabilityEstimatorCfg: ProbabilityEstimatorCfg{ + AprioriConfig: AprioriConfig{ PenaltyHalfLife: time.Hour, AprioriHopProbability: 0.9, AprioriWeight: 0.5, diff --git a/server.go b/server.go index 8a3d6fedc..d23a532bc 100644 --- a/server.go +++ b/server.go @@ -868,7 +868,7 @@ 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{ + estimatorCfg := routing.AprioriConfig{ AprioriHopProbability: routingConfig.AprioriHopProbability, PenaltyHalfLife: routingConfig.PenaltyHalfLife, AprioriWeight: routingConfig.AprioriWeight, @@ -877,7 +877,7 @@ func newServer(cfg *Config, listenAddrs []net.Addr, s.missionControl, err = routing.NewMissionControl( dbs.ChanStateDB, selfNode.PubKeyBytes, &routing.MissionControlConfig{ - ProbabilityEstimatorCfg: estimatorCfg, + AprioriConfig: estimatorCfg, MaxMcHistory: routingConfig.MaxMcHistory, McFlushInterval: routingConfig.McFlushInterval, MinFailureRelaxInterval: routing.DefaultMinFailureRelaxInterval, From b8c6227383e254085ab752af417e3c32fcc06757 Mon Sep 17 00:00:00 2001 From: bitromortac Date: Fri, 18 Nov 2022 09:59:26 +0100 Subject: [PATCH 2/7] routing: add probability estimator interface We introduce a probability `Estimator` interface which is implemented by the current apriori probability estimator. A second implementation, the bimodal probability estimator follows. --- routing/probability_apriori.go | 54 +++++++++++++++++++++++++++++++- routing/probability_estimator.go | 37 ++++++++++++++++++++++ 2 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 routing/probability_estimator.go diff --git a/routing/probability_apriori.go b/routing/probability_apriori.go index 93d11ef26..dabd47ff9 100644 --- a/routing/probability_apriori.go +++ b/routing/probability_apriori.go @@ -2,6 +2,7 @@ package routing import ( "errors" + "fmt" "math" "time" @@ -45,6 +46,10 @@ const ( // capacityCutoffFraction, but a smooth smearing such that some residual // probability is left when spending the whole amount, see above. capacitySmearingFraction = 0.1 + + // AprioriEstimatorName is used to identify the apriori probability + // estimator. + AprioriEstimatorName = "apriori" ) var ( @@ -81,6 +86,7 @@ type AprioriConfig struct { AprioriWeight float64 } +// validate checks the configuration of the estimator for allowed values. func (p AprioriConfig) validate() error { if p.PenaltyHalfLife < 0 { return ErrInvalidHalflife @@ -97,8 +103,24 @@ func (p AprioriConfig) validate() error { return nil } +// DefaultAprioriConfig returns the default configuration for the estimator. +func DefaultAprioriConfig() AprioriConfig { + return AprioriConfig{ + PenaltyHalfLife: DefaultPenaltyHalfLife, + AprioriHopProbability: DefaultAprioriHopProbability, + AprioriWeight: DefaultAprioriWeight, + } +} + // AprioriEstimator returns node and pair probabilities based on historical -// payment results. +// payment results. It uses a preconfigured success probability value for +// untried hops (AprioriHopProbability) and returns a high success probability +// for hops that could previously conduct a payment (prevSuccessProbability). +// Successful edges are retried until proven otherwise. Recently failed hops are +// penalized by an exponential time decay (PenaltyHalfLife), after which they +// are reconsidered for routing. If information was learned about a forwarding +// node, the information is taken into account to estimate a per node +// probability that mixes with the a priori probability (AprioriWeight). type AprioriEstimator struct { // AprioriConfig contains configuration options for our estimator. AprioriConfig @@ -108,6 +130,36 @@ type AprioriEstimator struct { prevSuccessProbability float64 } +// NewAprioriEstimator creates a new AprioriEstimator. +func NewAprioriEstimator(cfg AprioriConfig) (*AprioriEstimator, error) { + if err := cfg.validate(); err != nil { + return nil, err + } + + return &AprioriEstimator{ + AprioriConfig: cfg, + prevSuccessProbability: prevSuccessProbability, + }, nil +} + +// Compile-time checks that interfaces are implemented. +var _ Estimator = (*AprioriEstimator)(nil) +var _ estimatorConfig = (*AprioriConfig)(nil) + +// Config returns the estimator's configuration. +func (p *AprioriEstimator) Config() estimatorConfig { + return p.AprioriConfig +} + +// String returns the estimator's configuration as a string representation. +func (p *AprioriEstimator) String() string { + return fmt.Sprintf("estimator type: %v, penalty halflife time: %v, "+ + "apriori hop probability: %v, apriori weight: %v, previous "+ + "success probability: %v", AprioriEstimatorName, + p.PenaltyHalfLife, p.AprioriHopProbability, p.AprioriWeight, + p.prevSuccessProbability) +} + // getNodeProbability calculates the probability for connections from a node // that have not been tried before. The results parameter is a list of last // payment results for that node. diff --git a/routing/probability_estimator.go b/routing/probability_estimator.go new file mode 100644 index 000000000..c110b748d --- /dev/null +++ b/routing/probability_estimator.go @@ -0,0 +1,37 @@ +package routing + +import ( + "time" + + "github.com/btcsuite/btcd/btcutil" + "github.com/lightningnetwork/lnd/lnwire" + "github.com/lightningnetwork/lnd/routing/route" +) + +// Estimator estimates the probability to reach a node. +type Estimator interface { + // PairProbability estimates the probability of successfully traversing + // to toNode based on historical payment outcomes for the from node. + // Those outcomes are passed in via the results parameter. + PairProbability(now time.Time, results NodeResults, + toNode route.Vertex, amt lnwire.MilliSatoshi, + capacity btcutil.Amount) float64 + + // LocalPairProbability estimates the probability of successfully + // traversing our own local channels to toNode. + LocalPairProbability(now time.Time, results NodeResults, + toNode route.Vertex) float64 + + // Config returns the estimator's configuration. + Config() estimatorConfig + + // String returns the string representation of the estimator's + // configuration. + String() string +} + +// estimatorConfig represents a configuration for a probability estimator. +type estimatorConfig interface { + // validate checks that all configuration parameters are sane. + validate() error +} From 686816d7848fdd570a91d7a4519fda09b111fedf Mon Sep 17 00:00:00 2001 From: bitromortac Date: Fri, 27 May 2022 11:34:07 +0200 Subject: [PATCH 3/7] routing: implement bimodal probability estimator Implements a new probability estimator based on a probability theory framework. The computed probability consists of: * the direct channel probability, which is estimated based on a depleted liquidity distribution model, formulas and broader concept derived after Pickhardt et al. https://arxiv.org/abs/2103.08576 * an extension of the probability model to incorporate knowledge decay after time for previous successes and failures * a mixed node probability taking into account successes/failures on other channels of the node (similar to the apriori approach) --- docs/release-notes/release-notes-0.16.0.md | 11 + routing/probability_apriori_test.go | 8 + routing/probability_bimodal.go | 536 +++++++++++++++++ routing/probability_bimodal_test.go | 664 +++++++++++++++++++++ 4 files changed, 1219 insertions(+) create mode 100644 routing/probability_bimodal.go create mode 100644 routing/probability_bimodal_test.go diff --git a/docs/release-notes/release-notes-0.16.0.md b/docs/release-notes/release-notes-0.16.0.md index bee7566a9..519fba941 100644 --- a/docs/release-notes/release-notes-0.16.0.md +++ b/docs/release-notes/release-notes-0.16.0.md @@ -389,6 +389,16 @@ in the lnwire package](https://github.com/lightningnetwork/lnd/pull/7303) * [Pathfinding takes capacity of edges into account to improve success probability estimation.](https://github.com/lightningnetwork/lnd/pull/6857) +* [A new probability model ("bimodal") is added which models channel based + liquidities within a probability theory framework.]( + https://github.com/lightningnetwork/lnd/pull/6815) + +## Configuration +* Note that [this pathfinding change](https://github.com/lightningnetwork/lnd/pull/6815) + introduces a breaking change in lnd.conf apriori parameters under the routing + section, see sample-lnd.conf for an updated configuration. The behavior of + `lncli setmccfg/getmccfg` is altered as well. + ### Tooling and documentation @@ -445,6 +455,7 @@ refactor the itest for code health and maintenance. * Alyssa Hertig * andreihod * Antoni Spaanderman +* bitromortac * Carla Kirk-Cohen * Carsten Otto * Chris Geihsler diff --git a/routing/probability_apriori_test.go b/routing/probability_apriori_test.go index db11aa12c..a055d4ae2 100644 --- a/routing/probability_apriori_test.go +++ b/routing/probability_apriori_test.go @@ -87,6 +87,8 @@ func (c *estimatorTestContext) assertPairProbability(now time.Time, // TestProbabilityEstimatorNoResults tests the probability estimation when no // results are available. func TestProbabilityEstimatorNoResults(t *testing.T) { + t.Parallel() + ctx := newEstimatorTestContext(t) // A zero amount does not trigger capacity rescaling. @@ -104,6 +106,8 @@ func TestProbabilityEstimatorNoResults(t *testing.T) { // TestProbabilityEstimatorOneSuccess tests the probability estimation for nodes // that have a single success result. func TestProbabilityEstimatorOneSuccess(t *testing.T) { + t.Parallel() + ctx := newEstimatorTestContext(t) ctx.results = map[int]TimedPairResult{ @@ -144,6 +148,8 @@ func TestProbabilityEstimatorOneSuccess(t *testing.T) { // TestProbabilityEstimatorOneFailure tests the probability estimation for nodes // that have a single failure. func TestProbabilityEstimatorOneFailure(t *testing.T) { + t.Parallel() + ctx := newEstimatorTestContext(t) ctx.results = map[int]TimedPairResult{ @@ -171,6 +177,8 @@ func TestProbabilityEstimatorOneFailure(t *testing.T) { // TestProbabilityEstimatorMix tests the probability estimation for nodes for // which a mix of successes and failures is recorded. func TestProbabilityEstimatorMix(t *testing.T) { + t.Parallel() + ctx := newEstimatorTestContext(t) ctx.results = map[int]TimedPairResult{ diff --git a/routing/probability_bimodal.go b/routing/probability_bimodal.go new file mode 100644 index 000000000..daa086fa9 --- /dev/null +++ b/routing/probability_bimodal.go @@ -0,0 +1,536 @@ +package routing + +import ( + "fmt" + "math" + "time" + + "github.com/btcsuite/btcd/btcutil" + "github.com/go-errors/errors" + "github.com/lightningnetwork/lnd/lnwire" + "github.com/lightningnetwork/lnd/routing/route" +) + +const ( + // DefaultBimodalScaleMsat is the default value for BimodalScaleMsat in + // BimodalConfig. It describes the distribution of funds in the LN based + // on empirical findings. We assume an unbalanced network by default. + DefaultBimodalScaleMsat = lnwire.MilliSatoshi(300_000_000) + + // DefaultBimodalNodeWeight is the default value for the + // BimodalNodeWeight in BimodalConfig. It is chosen such that past + // forwardings on other channels of a router are only slightly taken + // into account. + DefaultBimodalNodeWeight = 0.2 + + // DefaultBimodalDecayTime is the default value for BimodalDecayTime. + // We will forget about previous learnings about channel liquidity on + // the timescale of about a week. + DefaultBimodalDecayTime = 7 * 24 * time.Hour + + // BimodalScaleMsatLimit is the maximum value for BimodalScaleMsat to + // avoid numerical issues. + BimodalScaleMsatMax = lnwire.MilliSatoshi(21e17) + + // BimodalEstimatorName is used to identify the bimodal estimator. + BimodalEstimatorName = "bimodal" +) + +var ( + // ErrInvalidScale is returned when we get a scale below or equal zero. + ErrInvalidScale = errors.New("scale must be >= 0 and sane") + + // ErrInvalidNodeWeight is returned when we get a node weight that is + // out of range. + ErrInvalidNodeWeight = errors.New("node weight must be in [0, 1]") + + // ErrInvalidDecayTime is returned when we get a decay time below zero. + ErrInvalidDecayTime = errors.New("decay time must be larger than zero") +) + +// BimodalConfig contains configuration for our probability estimator. +type BimodalConfig struct { + // BimodalNodeWeight defines how strongly other previous forwardings on + // channels of a router should be taken into account when computing a + // channel's probability to route. The allowed values are in the range + // [0, 1], where a value of 0 means that only direct information about a + // channel is taken into account. + BimodalNodeWeight float64 + + // BimodalScaleMsat describes the scale over which channels + // statistically have some liquidity left. The value determines how + // quickly the bimodal distribution drops off from the edges of a + // channel. A larger value (compared to typical channel capacities) + // means that the drop off is slow and that channel balances are + // distributed more uniformly. A small value leads to the assumption of + // very unbalanced channels. + BimodalScaleMsat lnwire.MilliSatoshi + + // BimodalDecayTime is the scale for the exponential information decay + // over time for previous successes or failures. + BimodalDecayTime time.Duration +} + +// validate checks the configuration of the estimator for allowed values. +func (p BimodalConfig) validate() error { + if p.BimodalDecayTime <= 0 { + return fmt.Errorf("%v: %w", BimodalEstimatorName, + ErrInvalidDecayTime) + } + + if p.BimodalNodeWeight < 0 || p.BimodalNodeWeight > 1 { + return fmt.Errorf("%v: %w", BimodalEstimatorName, + ErrInvalidNodeWeight) + } + + if p.BimodalScaleMsat == 0 || p.BimodalScaleMsat > BimodalScaleMsatMax { + return fmt.Errorf("%v: %w", BimodalEstimatorName, + ErrInvalidScale) + } + + return nil +} + +// DefaultBimodalConfig returns the default configuration for the estimator. +func DefaultBimodalConfig() BimodalConfig { + return BimodalConfig{ + BimodalNodeWeight: DefaultBimodalNodeWeight, + BimodalScaleMsat: DefaultBimodalScaleMsat, + BimodalDecayTime: DefaultBimodalDecayTime, + } +} + +// BimodalEstimator returns node and pair probabilities based on historical +// payment results based on a liquidity distribution model of the LN. The main +// function is to estimate the direct channel probability based on a depleted +// liquidity distribution model, with additional information decay over time. A +// per-node probability can be mixed with the direct probability, taking into +// account successes/failures on other channels of the forwarder. +type BimodalEstimator struct { + // BimodalConfig contains configuration options for our estimator. + BimodalConfig +} + +// NewBimodalEstimator creates a new BimodalEstimator. +func NewBimodalEstimator(cfg BimodalConfig) (*BimodalEstimator, error) { + if err := cfg.validate(); err != nil { + return nil, err + } + + return &BimodalEstimator{ + BimodalConfig: cfg, + }, nil +} + +// Compile-time checks that interfaces are implemented. +var _ Estimator = (*BimodalEstimator)(nil) +var _ estimatorConfig = (*BimodalConfig)(nil) + +// config returns the current configuration of the estimator. +func (p *BimodalEstimator) Config() estimatorConfig { + return p.BimodalConfig +} + +// String returns the estimator's configuration as a string representation. +func (p *BimodalEstimator) String() string { + return fmt.Sprintf("estimator type: %v, decay time: %v, liquidity "+ + "scale: %v, node weight: %v", BimodalEstimatorName, + p.BimodalDecayTime, p.BimodalScaleMsat, p.BimodalNodeWeight) +} + +// PairProbability estimates the probability of successfully traversing to +// toNode based on historical payment outcomes for the from node. Those outcomes +// are passed in via the results parameter. +func (p *BimodalEstimator) PairProbability(now time.Time, + results NodeResults, toNode route.Vertex, amt lnwire.MilliSatoshi, + capacity btcutil.Amount) float64 { + + // We first compute the probability for the desired hop taking into + // account previous knowledge. + directProbability := p.directProbability( + now, results, toNode, amt, lnwire.NewMSatFromSatoshis(capacity), + ) + + // The final probability is computed by taking into account other + // channels of the from node. + return p.calculateProbability(directProbability, now, results, toNode) +} + +// LocalPairProbability computes the probability to reach toNode given a set of +// previous learnings. +func (p *BimodalEstimator) LocalPairProbability(now time.Time, + results NodeResults, toNode route.Vertex) float64 { + + // For direct local probabilities we assume to know exactly how much we + // can send over a channel, which assumes that channels are active and + // have enough liquidity. + directProbability := 1.0 + + // If we had an unexpected failure for this node, we reduce the + // probability for some time to avoid infinite retries. + result, ok := results[toNode] + if ok && !result.FailTime.IsZero() { + timeAgo := now.Sub(result.FailTime) + + // We only expect results in the past to get a probability + // between 0 and 1. + if timeAgo < 0 { + timeAgo = 0 + } + exponent := -float64(timeAgo) / float64(p.BimodalDecayTime) + directProbability -= math.Exp(exponent) + } + + return directProbability +} + +// directProbability computes the probability to reach a node based on the +// liquidity distribution in the LN. +func (p *BimodalEstimator) directProbability(now time.Time, + results NodeResults, toNode route.Vertex, amt lnwire.MilliSatoshi, + capacity lnwire.MilliSatoshi) float64 { + + // We first determine the time-adjusted success and failure amounts to + // then compute a probability. We know that we can send a zero amount. + successAmount := lnwire.MilliSatoshi(0) + + // We know that we cannot send the full capacity. + failAmount := capacity + + // If we have information about past successes or failures, we modify + // them with a time decay. + result, ok := results[toNode] + if ok { + // Apply a time decay for the amount we cannot send. + if !result.FailTime.IsZero() { + failAmount = cannotSend( + result.FailAmt, capacity, now, result.FailTime, + p.BimodalDecayTime, + ) + } + + // Apply a time decay for the amount we can send. + if !result.SuccessTime.IsZero() { + successAmount = canSend( + result.SuccessAmt, now, result.SuccessTime, + p.BimodalDecayTime, + ) + } + } + + // Compute the direct channel probability. + probability, err := p.probabilityFormula( + capacity, successAmount, failAmount, amt, + ) + if err != nil { + log.Errorf("error computing probability: %v", err) + + return 0.0 + } + + return probability +} + +// calculateProbability computes the total hop probability combining the channel +// probability and historic forwarding data of other channels of the node we try +// to send from. +// +// Goals: +// * We want to incentivize good routing nodes: the more routable channels a +// node has, the more we want to incentivize (vice versa for failures). +// -> We reduce/increase the direct probability depending on past +// failures/successes for other channels of the node. +// +// * We want to be forgiving/give other nodes a chance as well: we want to +// forget about (non-)routable channels over time. +// -> We weight the successes/failures with a time decay such that they will not +// influence the total probability if a long time went by. +// +// * If we don't have other info, we want to solely rely on the direct +// probability. +// +// * We want to be able to specify how important the other channels are compared +// to the direct channel. +// -> Introduce a node weight factor that weights the direct probability against +// the node-wide average. The larger the node weight, the more important other +// channels of the node are. +// +// How do failures on low fee nodes redirect routing to higher fee nodes? +// Assumptions: +// * attemptCostPPM of 1000 PPM +// * constant direct channel probability of P0 (usually 0.5 for large amounts) +// * node weight w of 0.2 +// +// The question we want to answer is: +// How often would a zero-fee node be tried (even if there were failures for its +// other channels) over trying a high-fee node with 2000 PPM and no direct +// knowledge about the channel to send over? +// +// The probability of a route of length l is P(l) = l * P0. +// +// The total probability after n failures (with the implemented method here) is: +// P(l, n) = P(l-1) * P(n) +// = P(l-1) * (P0 + n*0) / (1 + n*w) +// = P(l) / (1 + n*w) +// +// Condition for a high-fee channel to overcome a low fee channel in the +// Dijkstra weight function (only looking at fee and probability PPM terms): +// highFeePPM + attemptCostPPM * 1/P(l) = 0PPM + attemptCostPPM * 1/P(l, n) +// highFeePPM/attemptCostPPM = 1/P(l, n) - 1/P(l) = +// = (1 + n*w)/P(l) - 1/P(l) = +// = n*w/P(l) +// +// Therefore: +// n = (highFeePPM/attemptCostPPM) * (P(l)/w) = +// = (2000/1000) * 0.5 * l / w = l/w +// +// For a one-hop route we get: +// n = 1/0.2 = 5 tolerated failures +// +// For a three-hop route we get: +// n = 3/0.2 = 15 tolerated failures +// +// For more details on the behavior see tests. +func (p *BimodalEstimator) calculateProbability(directProbability float64, + now time.Time, results NodeResults, toNode route.Vertex) float64 { + + // If we don't take other channels into account, we can return early. + if p.BimodalNodeWeight == 0.0 { + return directProbability + } + + // w is a parameter which determines how strongly the other channels of + // a node should be incorporated, the higher the stronger. + w := p.BimodalNodeWeight + + // dt determines the timeliness of the previous successes/failures + // to be taken into account. + dt := float64(p.BimodalDecayTime) + + // The direct channel probability is weighted fully, all other results + // are weighted according to how recent the information is. + totalProbabilities := directProbability + totalWeights := 1.0 + + for peer, result := range results { + // We don't include the direct hop probability here because it + // is already included in totalProbabilities. + if peer == toNode { + continue + } + + // We add probabilities weighted by how recent the info is. + var weight float64 + if result.SuccessAmt > 0 { + exponent := -float64(now.Sub(result.SuccessTime)) / dt + weight = math.Exp(exponent) + totalProbabilities += w * weight + totalWeights += w * weight + } + if result.FailAmt > 0 { + exponent := -float64(now.Sub(result.FailTime)) / dt + weight = math.Exp(exponent) + + // Failures don't add to total success probability. + totalWeights += w * weight + } + } + + return totalProbabilities / totalWeights +} + +// canSend returns the sendable amount over the channel, respecting time decay. +// canSend approaches zero, if we wait for a much longer time than the decay +// time. +func canSend(successAmount lnwire.MilliSatoshi, now, successTime time.Time, + decayConstant time.Duration) lnwire.MilliSatoshi { + + // The factor approaches 0 for successTime a long time in the past, + // is 1 when the successTime is now. + factor := math.Exp( + -float64(now.Sub(successTime)) / float64(decayConstant), + ) + + canSend := factor * float64(successAmount) + + return lnwire.MilliSatoshi(canSend) +} + +// cannotSend returns the not sendable amount over the channel, respecting time +// decay. cannotSend approaches the capacity, if we wait for a much longer time +// than the decay time. +func cannotSend(failAmount, capacity lnwire.MilliSatoshi, now, + failTime time.Time, decayConstant time.Duration) lnwire.MilliSatoshi { + + if failAmount > capacity { + failAmount = capacity + } + + // The factor approaches 0 for failTime a long time in the past and it + // is 1 when the failTime is now. + factor := math.Exp( + -float64(now.Sub(failTime)) / float64(decayConstant), + ) + + cannotSend := capacity - lnwire.MilliSatoshi( + factor*float64(capacity-failAmount), + ) + + return cannotSend +} + +// primitive computes the indefinite integral of our assumed (normalized) +// liquidity probability distribution. The distribution of liquidity x here is +// the function P(x) ~ exp(-x/s) + exp((x-c)/s), i.e., two exponentials residing +// at the ends of channels. This means that we expect liquidity to be at either +// side of the channel with capacity c. The s parameter (scale) defines how far +// the liquidity leaks into the channel. A very low scale assumes completely +// unbalanced channels, a very high scale assumes a random distribution. More +// details can be found in +// https://github.com/lightningnetwork/lnd/issues/5988#issuecomment-1131234858. +func (p *BimodalEstimator) primitive(c, x float64) float64 { + s := float64(p.BimodalScaleMsat) + + // The indefinite integral of P(x) is given by + // Int P(x) dx = H(x) = s * (-e(-x/s) + e((x-c)/s)), + // and its norm from 0 to c can be computed from it, + // norm = [H(x)]_0^c = s * (-e(-c/s) + 1 -(1 + e(-c/s))). + ecs := math.Exp(-c / s) + exs := math.Exp(-x / s) + + // It would be possible to split the next term and reuse the factors + // from before, but this can lead to numerical issues with large + // numbers. + excs := math.Exp((x - c) / s) + + // norm can only become zero, if c is zero, which we sorted out before + // calling this method. + norm := -2*ecs + 2 + + // We end up with the primitive function of the normalized P(x). + return (-exs + excs) / norm +} + +// integral computes the integral of our liquidity distribution from the lower +// to the upper value. +func (p *BimodalEstimator) integral(capacity, lower, upper float64) float64 { + if lower < 0 || lower > upper { + log.Errorf("probability integral limits nonsensical: capacity:"+ + "%v lower: %v upper: %v", capacity, lower, upper) + + return 0.0 + } + + return p.primitive(capacity, upper) - p.primitive(capacity, lower) +} + +// probabilityFormula computes the expected probability for a payment of +// amountMsat given prior learnings for a channel of certain capacity. +// successAmountMsat and failAmountMsat stand for the unsettled success and +// failure amounts, respectively. The formula is derived using the formalism +// presented in Pickhardt et al., https://arxiv.org/abs/2103.08576. +func (p *BimodalEstimator) probabilityFormula(capacityMsat, successAmountMsat, + failAmountMsat, amountMsat lnwire.MilliSatoshi) (float64, error) { + + // Convert to positive-valued floats. + capacity := float64(capacityMsat) + successAmount := float64(successAmountMsat) + failAmount := float64(failAmountMsat) + amount := float64(amountMsat) + + // Capacity being zero is a sentinel value to ignore the probability + // estimation, we'll return the full probability here. + if capacity == 0.0 { + return 1.0, nil + } + + // We cannot send more than the capacity. + if amount > capacity { + return 0.0, nil + } + + // Mission control may have some outdated values, we correct them here. + // TODO(bitromortac): there may be better decisions to make in these + // cases, e.g., resetting failAmount=cap and successAmount=0. + + // failAmount should be capacity at max. + if failAmount > capacity { + failAmount = capacity + } + + // successAmount should be capacity at max. + if successAmount > capacity { + successAmount = capacity + } + + // The next statement is a safety check against an illogical condition, + // otherwise the renormalization integral would become zero. This may + // happen if a large channel gets closed and smaller ones remain, but + // it should recover with the time decay. + if failAmount <= successAmount { + log.Tracef("fail amount (%v) is larger than or equal the "+ + "success amount (%v) for capacity (%v)", + failAmountMsat, successAmountMsat, capacityMsat) + + return 0.0, nil + } + + // We cannot send more than the fail amount. + if amount >= failAmount { + return 0.0, nil + } + + // The success probability for payment amount a is the integral over the + // prior distribution P(x), the probability to find liquidity between + // the amount a and channel capacity c (or failAmount a_f): + // P(X >= a | X < a_f) = Integral_{a}^{a_f} P(x) dx + prob := p.integral(capacity, amount, failAmount) + if math.IsNaN(prob) { + return 0.0, fmt.Errorf("non-normalized probability is NaN, "+ + "capacity: %v, amount: %v, fail amount: %v", + capacity, amount, failAmount) + } + + // If we have payment information, we need to adjust the prior + // distribution P(x) and get the posterior distribution by renormalizing + // the prior distribution in such a way that the probability mass lies + // between a_s and a_f. + reNorm := p.integral(capacity, successAmount, failAmount) + if math.IsNaN(reNorm) { + return 0.0, fmt.Errorf("normalization factor is NaN, "+ + "capacity: %v, success amount: %v, fail amount: %v", + capacity, successAmount, failAmount) + } + + // The normalization factor can only be zero if the success amount is + // equal or larger than the fail amount. This should not happen as we + // have checked this scenario above. + if reNorm == 0.0 { + return 0.0, fmt.Errorf("normalization factor is zero, "+ + "capacity: %v, success amount: %v, fail amount: %v", + capacity, successAmount, failAmount) + } + + prob /= reNorm + + // Note that for payment amounts smaller than successAmount, we can get + // a value larger than unity, which we cap here to get a proper + // probability. + if prob > 1.0 { + if amount > successAmount { + return 0.0, fmt.Errorf("unexpected large probability "+ + "(%v) capacity: %v, amount: %v, success "+ + "amount: %v, fail amount: %v", prob, capacity, + amount, successAmount, failAmount) + } + + return 1.0, nil + } else if prob < 0.0 { + return 0.0, fmt.Errorf("negative probability "+ + "(%v) capacity: %v, amount: %v, success "+ + "amount: %v, fail amount: %v", prob, capacity, + amount, successAmount, failAmount) + } + + return prob, nil +} diff --git a/routing/probability_bimodal_test.go b/routing/probability_bimodal_test.go new file mode 100644 index 000000000..53f8829c4 --- /dev/null +++ b/routing/probability_bimodal_test.go @@ -0,0 +1,664 @@ +package routing + +import ( + "math" + "testing" + "time" + + "github.com/lightningnetwork/lnd/lnwire" + "github.com/lightningnetwork/lnd/routing/route" + "github.com/stretchr/testify/require" +) + +const ( + smallAmount = lnwire.MilliSatoshi(400_000) + largeAmount = lnwire.MilliSatoshi(5_000_000) + capacity = lnwire.MilliSatoshi(10_000_000) + scale = lnwire.MilliSatoshi(400_000) +) + +// TestSuccessProbability tests that we get correct probability estimates for +// the direct channel probability. +func TestSuccessProbability(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + expectedProbability float64 + tolerance float64 + successAmount lnwire.MilliSatoshi + failAmount lnwire.MilliSatoshi + amount lnwire.MilliSatoshi + capacity lnwire.MilliSatoshi + }{ + // We can't send more than the capacity. + { + name: "no info, larger than capacity", + capacity: capacity, + successAmount: 0, + failAmount: capacity, + amount: capacity + 1, + expectedProbability: 0.0, + }, + // With the current model we don't prefer any channels if the + // send amount is large compared to the scale but small compared + // to the capacity. + { + name: "no info, large amount", + capacity: capacity, + successAmount: 0, + failAmount: capacity, + amount: largeAmount, + expectedProbability: 0.5, + }, + // We always expect to be able to "send" an amount of 0. + { + name: "no info, zero amount", + capacity: capacity, + successAmount: 0, + failAmount: capacity, + amount: 0, + expectedProbability: 1.0, + }, + // We can't send the whole capacity. + { + name: "no info, full capacity", + capacity: capacity, + successAmount: 0, + failAmount: capacity, + amount: capacity, + expectedProbability: 0.0, + }, + // Sending a small amount will have a higher probability to go + // through than a large amount. + { + name: "no info, small amount", + capacity: capacity, + successAmount: 0, + failAmount: capacity, + amount: smallAmount, + expectedProbability: 0.684, + tolerance: 0.001, + }, + // If we had an unsettled success, we are sure we can send a + // lower amount. + { + name: "previous success, lower amount", + capacity: capacity, + successAmount: largeAmount, + failAmount: capacity, + amount: smallAmount, + expectedProbability: 1.0, + }, + // If we had an unsettled success, we are sure we can send the + // same amount. + { + name: "previous success, success amount", + capacity: capacity, + successAmount: largeAmount, + failAmount: capacity, + amount: largeAmount, + expectedProbability: 1.0, + }, + // If we had an unsettled success with a small amount, we know + // with increased probability that we can send a comparable + // higher amount. + { + name: "previous success, larger amount", + capacity: capacity, + successAmount: smallAmount / 2, + failAmount: capacity, + amount: smallAmount, + expectedProbability: 0.851, + tolerance: 0.001, + }, + // If we had a large unsettled success before, we know we can + // send even larger payments with high probability. + { + name: "previous large success, larger " + + "amount", + capacity: capacity, + successAmount: largeAmount / 2, + failAmount: capacity, + amount: largeAmount, + expectedProbability: 0.998, + tolerance: 0.001, + }, + // If we had a failure before, we can't send with the fail + // amount. + { + name: "previous failure, fail amount", + capacity: capacity, + failAmount: largeAmount, + amount: largeAmount, + expectedProbability: 0.0, + }, + // We can't send a higher amount than the fail amount either. + { + name: "previous failure, larger fail " + + "amount", + capacity: capacity, + failAmount: largeAmount, + amount: largeAmount + smallAmount, + expectedProbability: 0.0, + }, + // We expect a diminished non-zero probability if we try to send + // an amount that's lower than the last fail amount. + { + name: "previous failure, lower than fail " + + "amount", + capacity: capacity, + failAmount: largeAmount, + amount: smallAmount, + expectedProbability: 0.368, + tolerance: 0.001, + }, + // From here on we deal with mixed previous successes and + // failures. + // We expect to be always able to send a tiny amount. + { + name: "previous f/s, very small amount", + capacity: capacity, + failAmount: largeAmount, + successAmount: smallAmount, + amount: 0, + expectedProbability: 1.0, + }, + // We expect to be able to send up to the previous success + // amount will full certainty. + { + name: "previous f/s, success amount", + capacity: capacity, + failAmount: largeAmount, + successAmount: smallAmount, + amount: smallAmount, + expectedProbability: 1.0, + }, + // This tests a random value between small amount and large + // amount. + { + name: "previous f/s, between f/s", + capacity: capacity, + failAmount: largeAmount, + successAmount: smallAmount, + amount: smallAmount + largeAmount/10, + expectedProbability: 0.287, + tolerance: 0.001, + }, + // We still can't send the fail amount. + { + name: "previous f/s, fail amount", + capacity: capacity, + failAmount: largeAmount, + successAmount: smallAmount, + amount: largeAmount, + expectedProbability: 0.0, + }, + // Same success and failure amounts (illogical). + { + name: "previous f/s, same", + capacity: capacity, + failAmount: largeAmount, + successAmount: largeAmount, + amount: largeAmount, + expectedProbability: 0.0, + }, + // Higher success than failure amount (illogical). + { + name: "previous f/s, higher success", + capacity: capacity, + failAmount: smallAmount, + successAmount: largeAmount, + expectedProbability: 0.0, + }, + } + + estimator := BimodalEstimator{ + BimodalConfig: BimodalConfig{BimodalScaleMsat: scale}, + } + + for _, test := range tests { + test := test + + t.Run(test.name, func(t *testing.T) { + t.Parallel() + + p, err := estimator.probabilityFormula( + test.capacity, test.successAmount, + test.failAmount, test.amount, + ) + require.InDelta(t, test.expectedProbability, p, + test.tolerance) + require.NoError(t, err) + }) + } +} + +// TestIntegral tests certain limits of the probability distribution integral. +func TestIntegral(t *testing.T) { + t.Parallel() + + defaultScale := lnwire.NewMSatFromSatoshis(300_000) + + tests := []struct { + name string + capacity float64 + lower float64 + upper float64 + scale lnwire.MilliSatoshi + expected float64 + }{ + { + name: "all zero", + expected: math.NaN(), + scale: defaultScale, + }, + { + name: "all same", + capacity: 1, + lower: 1, + upper: 1, + scale: defaultScale, + }, + { + name: "large numbers, low lower", + capacity: 21e17, + lower: 0, + upper: 21e17, + expected: 1, + scale: defaultScale, + }, + { + name: "large numbers, high lower", + capacity: 21e17, + lower: 21e17, + upper: 21e17, + scale: defaultScale, + }, + { + name: "same scale and capacity", + capacity: 21e17, + lower: 21e17, + upper: 21e17, + scale: 21e17, + }, + } + + for _, test := range tests { + test := test + + t.Run(test.name, func(t *testing.T) { + t.Parallel() + estimator := BimodalEstimator{ + BimodalConfig: BimodalConfig{ + BimodalScaleMsat: test.scale, + }, + } + + p := estimator.integral( + test.capacity, test.lower, test.upper, + ) + require.InDelta(t, test.expected, p, 0.001) + }) + } +} + +// TestCanSend tests that the success amount drops to zero over time. +func TestCanSend(t *testing.T) { + t.Parallel() + + successAmount := lnwire.MilliSatoshi(1_000_000) + successTime := time.Unix(1_000, 0) + now := time.Unix(2_000, 0) + decayTime := time.Duration(1_000) * time.Second + infinity := time.Unix(10_000_000_000, 0) + + // Test an immediate retry. + require.Equal(t, successAmount, canSend( + successAmount, successTime, successTime, decayTime, + )) + + // Test that after the decay time, the successAmount is 1/e of its + // value. + decayAmount := lnwire.MilliSatoshi(float64(successAmount) / math.E) + require.Equal(t, decayAmount, canSend( + successAmount, now, successTime, decayTime, + )) + + // After a long time, we want the amount to approach 0. + require.Equal(t, lnwire.MilliSatoshi(0), canSend( + successAmount, infinity, successTime, decayTime, + )) +} + +// TestCannotSend tests that the fail amount approaches the capacity over time. +func TestCannotSend(t *testing.T) { + t.Parallel() + + failAmount := lnwire.MilliSatoshi(1_000_000) + failTime := time.Unix(1_000, 0) + now := time.Unix(2_000, 0) + decayTime := time.Duration(1_000) * time.Second + infinity := time.Unix(10_000_000_000, 0) + capacity := lnwire.MilliSatoshi(3_000_000) + + // Test immediate retry. + require.EqualValues(t, failAmount, cannotSend( + failAmount, capacity, failTime, failTime, decayTime, + )) + + // After the decay time we want to be between the fail amount and + // the capacity. + summand := lnwire.MilliSatoshi(float64(capacity-failAmount) / math.E) + expected := capacity - summand + require.Equal(t, expected, cannotSend( + failAmount, capacity, now, failTime, decayTime, + )) + + // After a long time, we want the amount to approach the capacity. + require.Equal(t, capacity, cannotSend( + failAmount, capacity, infinity, failTime, decayTime, + )) +} + +// TestComputeProbability tests the inclusion of previous forwarding results of +// other channels of the node into the total probability. +func TestComputeProbability(t *testing.T) { + t.Parallel() + + nodeWeight := 1 / 5. + toNode := route.Vertex{10} + tolerance := 0.01 + decayTime := time.Duration(1) * time.Hour * 24 + + // makeNodeResults prepares forwarding data for the other channels of + // the node. + makeNodeResults := func(successes []bool, now time.Time) NodeResults { + results := make(NodeResults, len(successes)) + + for i, s := range successes { + vertex := route.Vertex{byte(i)} + + results[vertex] = TimedPairResult{ + FailTime: now, FailAmt: 1, + } + if s { + results[vertex] = TimedPairResult{ + SuccessTime: now, SuccessAmt: 1, + } + } + } + + return results + } + + tests := []struct { + name string + directProbability float64 + otherResults []bool + expectedProbability float64 + delay time.Duration + }{ + // If no other information is available, use the direct + // probability. + { + name: "unknown, only direct", + directProbability: 0.5, + expectedProbability: 0.5, + }, + // If there was a single success, expect increased success + // probability. + { + name: "unknown, single success", + directProbability: 0.5, + otherResults: []bool{true}, + expectedProbability: 0.583, + }, + // If there were many successes, expect even higher success + // probability. + { + name: "unknown, many successes", + directProbability: 0.5, + otherResults: []bool{ + true, true, true, true, true, + }, + expectedProbability: 0.75, + }, + // If there was a single failure, we expect a slightly decreased + // probability. + { + name: "unknown, single failure", + directProbability: 0.5, + otherResults: []bool{false}, + expectedProbability: 0.416, + }, + // If there were many failures, we expect a strongly decreased + // probability. + { + name: "unknown, many failures", + directProbability: 0.5, + otherResults: []bool{ + false, false, false, false, false, + }, + expectedProbability: 0.25, + }, + // A success and a failure neutralize themselves. + { + name: "unknown, mixed even", + directProbability: 0.5, + otherResults: []bool{true, false}, + expectedProbability: 0.5, + }, + // A mixed result history leads to increase/decrease of the most + // experienced successes/failures. + { + name: "unknown, mixed uneven", + directProbability: 0.5, + otherResults: []bool{ + true, true, false, false, false, + }, + expectedProbability: 0.45, + }, + // Many successes don't elevate the probability above 1. + { + name: "success, successes", + directProbability: 1.0, + otherResults: []bool{ + true, true, true, true, true, + }, + expectedProbability: 1.0, + }, + // Five failures on a very certain channel will lower its + // success probability to the unknown probability. + { + name: "success, failures", + directProbability: 1.0, + otherResults: []bool{ + false, false, false, false, false, + }, + expectedProbability: 0.5, + }, + // If we are sure that the channel can send, a single failure + // will not decrease the outcome significantly. + { + name: "success, single failure", + directProbability: 1.0, + otherResults: []bool{false}, + expectedProbability: 0.8333, + }, + { + name: "success, many failures", + directProbability: 1.0, + otherResults: []bool{ + false, false, false, false, false, false, false, + }, + expectedProbability: 0.416, + }, + // Failures won't decrease the probability below zero. + { + name: "fail, failures", + directProbability: 0.0, + otherResults: []bool{false, false, false}, + expectedProbability: 0.0, + }, + { + name: "fail, successes", + directProbability: 0.0, + otherResults: []bool{ + true, true, true, true, true, + }, + expectedProbability: 0.5, + }, + // We test forgetting information with the time decay. + // A past success won't alter the certain success probability. + { + name: "success, single success, decay " + + "time", + directProbability: 1.0, + otherResults: []bool{true}, + delay: decayTime, + expectedProbability: 1.00, + }, + // A failure that was experienced some time ago won't influence + // as much as a recent one. + { + name: "success, single fail, decay time", + directProbability: 1.0, + otherResults: []bool{false}, + delay: decayTime, + expectedProbability: 0.9314, + }, + // Information from a long time ago doesn't have any effect. + { + name: "success, single fail, long ago", + directProbability: 1.0, + otherResults: []bool{false}, + delay: 10 * decayTime, + expectedProbability: 1.0, + }, + { + name: "fail, successes decay time", + directProbability: 0.0, + otherResults: []bool{ + true, true, true, true, true, + }, + delay: decayTime, + expectedProbability: 0.269, + }, + // Very recent info approaches the case with no time decay. + { + name: "unknown, successes close", + directProbability: 0.5, + otherResults: []bool{ + true, true, true, true, true, + }, + delay: decayTime / 10, + expectedProbability: 0.741, + }, + } + + estimator := BimodalEstimator{ + BimodalConfig: BimodalConfig{ + BimodalScaleMsat: scale, BimodalNodeWeight: nodeWeight, + BimodalDecayTime: decayTime, + }, + } + + for _, test := range tests { + test := test + + t.Run(test.name, func(t *testing.T) { + t.Parallel() + + then := time.Unix(0, 0) + results := makeNodeResults(test.otherResults, then) + now := then.Add(test.delay) + + p := estimator.calculateProbability( + test.directProbability, now, results, toNode, + ) + + require.InDelta(t, test.expectedProbability, p, + tolerance) + }) + } +} + +// TestLocalPairProbability tests that we reduce probability for failed direct +// neighbors. +func TestLocalPairProbability(t *testing.T) { + t.Parallel() + + decayTime := time.Hour + now := time.Unix(1000000000, 0) + toNode := route.Vertex{1} + + createFailedResult := func(timeAgo time.Duration) NodeResults { + return NodeResults{ + toNode: TimedPairResult{ + FailTime: now.Add(-timeAgo), + }, + } + } + + tests := []struct { + name string + expectedProbability float64 + results NodeResults + }{ + { + name: "no results", + expectedProbability: 1.0, + }, + { + name: "recent failure", + results: createFailedResult(0), + expectedProbability: 0.0, + }, + { + name: "after decay time", + results: createFailedResult(decayTime), + expectedProbability: 1 - 1/math.E, + }, + { + name: "long ago", + results: createFailedResult(10 * decayTime), + expectedProbability: 1.0, + }, + } + + estimator := BimodalEstimator{ + BimodalConfig: BimodalConfig{BimodalDecayTime: decayTime}, + } + + for _, test := range tests { + test := test + + t.Run(test.name, func(t *testing.T) { + t.Parallel() + p := estimator.LocalPairProbability( + now, test.results, toNode, + ) + require.InDelta(t, test.expectedProbability, p, 0.001) + }) + } +} + +// FuzzProbability checks that we don't encounter errors related to NaNs. +func FuzzProbability(f *testing.F) { + estimator := BimodalEstimator{ + BimodalConfig: BimodalConfig{BimodalScaleMsat: scale}, + } + f.Add(uint64(0), uint64(0), uint64(0), uint64(0)) + + f.Fuzz(func(t *testing.T, capacity, successAmt, failAmt, amt uint64) { + _, err := estimator.probabilityFormula( + lnwire.MilliSatoshi(capacity), + lnwire.MilliSatoshi(successAmt), + lnwire.MilliSatoshi(failAmt), lnwire.MilliSatoshi(amt), + ) + + require.NoError(t, err, "c: %v s: %v f: %v a: %v", capacity, + successAmt, failAmt, amt) + }) +} From 16986ee5c7c5c8d08c08e61fa1f46da296b92d43 Mon Sep 17 00:00:00 2001 From: bitromortac Date: Fri, 20 Jan 2023 11:25:53 +0100 Subject: [PATCH 4/7] 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) From 58d5131e317ed5efa650a93ace12da40928d3cf3 Mon Sep 17 00:00:00 2001 From: bitromortac Date: Mon, 6 Feb 2023 14:29:02 +0100 Subject: [PATCH 5/7] lnd+routerrpc: configure server with new estimator We add new lnd.conf configuration options for both probability estimators. Note that this is a breaking change for the existing apriori parameters. --- lnrpc/routerrpc/config.go | 49 +++++++++++++++------- lnrpc/routerrpc/routing_config.go | 69 +++++++++++++++++++++++-------- routing/pathfind.go | 4 ++ sample-lnd.conf | 51 ++++++++++++++++------- server.go | 45 ++++++++++++++++---- 5 files changed, 160 insertions(+), 58 deletions(-) diff --git a/lnrpc/routerrpc/config.go b/lnrpc/routerrpc/config.go index ec036258f..9877556db 100644 --- a/lnrpc/routerrpc/config.go +++ b/lnrpc/routerrpc/config.go @@ -42,14 +42,23 @@ type Config struct { // DefaultConfig defines the config defaults. func DefaultConfig() *Config { defaultRoutingConfig := RoutingConfig{ - AprioriHopProbability: routing.DefaultAprioriHopProbability, - AprioriWeight: routing.DefaultAprioriWeight, - MinRouteProbability: routing.DefaultMinRouteProbability, - PenaltyHalfLife: routing.DefaultPenaltyHalfLife, - AttemptCost: routing.DefaultAttemptCost.ToSatoshis(), - AttemptCostPPM: routing.DefaultAttemptCostPPM, - MaxMcHistory: routing.DefaultMaxMcHistory, - McFlushInterval: routing.DefaultMcFlushInterval, + ProbabilityEstimatorType: routing.DefaultEstimator, + MinRouteProbability: routing.DefaultMinRouteProbability, + + AttemptCost: routing.DefaultAttemptCost.ToSatoshis(), + AttemptCostPPM: routing.DefaultAttemptCostPPM, + MaxMcHistory: routing.DefaultMaxMcHistory, + McFlushInterval: routing.DefaultMcFlushInterval, + AprioriConfig: &AprioriConfig{ + HopProbability: routing.DefaultAprioriHopProbability, + Weight: routing.DefaultAprioriWeight, + PenaltyHalfLife: routing.DefaultPenaltyHalfLife, + }, + BimodalConfig: &BimodalConfig{ + Scale: int64(routing.DefaultBimodalScaleMsat), + NodeWeight: routing.DefaultBimodalNodeWeight, + DecayTime: routing.DefaultBimodalDecayTime, + }, } return &Config{ @@ -60,13 +69,21 @@ func DefaultConfig() *Config { // GetRoutingConfig returns the routing config based on this sub server config. func GetRoutingConfig(cfg *Config) *RoutingConfig { return &RoutingConfig{ - AprioriHopProbability: cfg.AprioriHopProbability, - AprioriWeight: cfg.AprioriWeight, - MinRouteProbability: cfg.MinRouteProbability, - AttemptCost: cfg.AttemptCost, - AttemptCostPPM: cfg.AttemptCostPPM, - PenaltyHalfLife: cfg.PenaltyHalfLife, - MaxMcHistory: cfg.MaxMcHistory, - McFlushInterval: cfg.McFlushInterval, + ProbabilityEstimatorType: cfg.ProbabilityEstimatorType, + MinRouteProbability: cfg.MinRouteProbability, + AttemptCost: cfg.AttemptCost, + AttemptCostPPM: cfg.AttemptCostPPM, + MaxMcHistory: cfg.MaxMcHistory, + McFlushInterval: cfg.McFlushInterval, + AprioriConfig: &AprioriConfig{ + HopProbability: cfg.AprioriConfig.HopProbability, + Weight: cfg.AprioriConfig.Weight, + PenaltyHalfLife: cfg.AprioriConfig.PenaltyHalfLife, + }, + BimodalConfig: &BimodalConfig{ + Scale: cfg.BimodalConfig.Scale, + NodeWeight: cfg.BimodalConfig.NodeWeight, + DecayTime: cfg.BimodalConfig.DecayTime, + }, } } diff --git a/lnrpc/routerrpc/routing_config.go b/lnrpc/routerrpc/routing_config.go index 2a53ba2e4..a1bbad590 100644 --- a/lnrpc/routerrpc/routing_config.go +++ b/lnrpc/routerrpc/routing_config.go @@ -7,28 +7,16 @@ import ( ) // RoutingConfig contains the configurable parameters that control routing. +// +//nolint:lll type RoutingConfig struct { + // ProbabilityEstimatorType sets the estimator to use. + ProbabilityEstimatorType string `long:"estimator" choice:"apriori" choice:"bimodal" description:"Probability estimator used for pathfinding." ` + // MinRouteProbability 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."` - - // 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 `long:"aprioriweight" description:"Weight of the a priori probability in success probability estimation. Valid values are in [0, 1]."` - - // 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 fixed virtual cost in path finding of a failed // payment attempt. It is used to trade off potentially better routes // against their probability of succeeding. @@ -47,4 +35,51 @@ type RoutingConfig struct { // McFlushInterval defines the timer interval to use to flush mission // control state to the DB. McFlushInterval time.Duration `long:"mcflushinterval" description:"the timer interval to use to flush mission control state to the DB"` + + // AprioriConfig defines parameters for the apriori probability. + AprioriConfig *AprioriConfig `group:"apriori" namespace:"apriori" description:"configuration for the apriori pathfinding probability estimator"` + + // BimodalConfig defines parameters for the bimodal probability. + BimodalConfig *BimodalConfig `group:"bimodal" namespace:"bimodal" description:"configuration for the bimodal pathfinding probability estimator"` +} + +// AprioriConfig defines parameters for the apriori probability. +// +//nolint:lll +type AprioriConfig struct { + // HopProbability is the assumed success probability of a hop in a route + // when no other information is available. + HopProbability float64 `long:"hopprob" description:"Assumed success probability of a hop in a route when no other information is available."` + + // Weight 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. + Weight float64 `long:"weight" description:"Weight of the a priori probability in success probability estimation. Valid values are in [0, 1]."` + + // 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"` +} + +// BimodalConfig defines parameters for the bimodal probability. +// +//nolint:lll +type BimodalConfig struct { + // Scale describes the scale over which channels still have some + // liquidity left on both channel ends. A value of 0 means that we + // assume perfectly unbalanced channels, a very high value means + // randomly balanced channels. + Scale int64 `long:"scale" description:"Defines the unbalancedness assumed for the network, the amount defined in msat."` + + // NodeWeight defines how strongly non-routed channels should be taken + // into account for probability estimation. Valid values are in [0,1]. + NodeWeight float64 `long:"nodeweight" description:"Defines how strongly non-routed channels should be taken into account for probability estimation. Valid values are in [0, 1]."` + + // DecayTime is the scale for the exponential information decay over + // time for previous successes or failures. + DecayTime time.Duration `long:"decaytime" description:"Describes the information decay of knowledge about previous successes and failures in channels."` } diff --git a/routing/pathfind.go b/routing/pathfind.go index bffc46336..f204591b6 100644 --- a/routing/pathfind.go +++ b/routing/pathfind.go @@ -46,6 +46,10 @@ type pathFinder = func(g *graphParams, r *RestrictParams, []*channeldb.CachedEdgePolicy, float64, error) var ( + // DefaultEstimator is the default estimator used for computing + // probabilities in pathfinding. + DefaultEstimator = AprioriEstimatorName + // DefaultAttemptCost is the default fixed virtual cost in path finding // of a failed payment attempt. It is used to trade off potentially // better routes against their probability of succeeding. diff --git a/sample-lnd.conf b/sample-lnd.conf index 72b78c489..b8aadd874 100644 --- a/sample-lnd.conf +++ b/sample-lnd.conf @@ -1105,21 +1105,23 @@ litecoin.node=ltcd [routerrpc] +; Probability estimator used for pathfinding. (default: apriori) +; routerrpc.estimator=[apriori|bimodal] + ; Minimum required route success probability to attempt the payment (default: ; 0.01) ; routerrpc.minrtprob=1 -; Assumed success probability of a hop in a route when no other information is -; available. (default: 0.6) -; routerrpc.apriorihopprob=0.2 +; The maximum number of payment results that are held on disk by mission control +; (default: 1000) +; routerrpc.maxmchistory=900 -; Weight of the a priori probability in success probability estimation. Valid -; values are in [0, 1]. (default: 0.5) -; routerrpc.aprioriweight=0.3 +; The time interval with which the MC store state is flushed to the database +; (default: 1s) +; routerrpc.mcflushinterval=1m -; Defines the duration after which a penalized node or channel is back at 50% -; probability (default: 1h0m0s) -; routerrpc.penaltyhalflife=2h +; Path to the router macaroon +; routerrpc.routermacaroonpath=~/.lnd/data/chain/bitcoin/simnet/router.macaroon ; The (virtual) fixed cost in sats of a failed payment attempt (default: 100) ; routerrpc.attemptcost=90 @@ -1128,15 +1130,32 @@ litecoin.node=ltcd ; attempt (default: 1000) ; routerrpc.attemptcostppm=900 -; The maximum number of payment results that are held on disk by mission control -; (default: 1000) -; routerrpc.maxmchistory=900 +; Assumed success probability of a hop in a route when no other information is +; available. (default: 0.6) +; routerrpc.apriori.hopprob=0.2 -; The time interval with which the MC store state is flushed to the DB. -; routerrpc.mcflushinterval=1m +; Weight of the a priori probability in success probability estimation. Valid +; values are in [0, 1]. (default: 0.5) +; routerrpc.apriori.weight=0.3 -; Path to the router macaroon -; routerrpc.routermacaroonpath=~/.lnd/data/chain/bitcoin/simnet/router.macaroon +; Defines the duration after which a penalized node or channel is back at 50% +; probability (default: 1h0m0s) +; routerrpc.apriori.penaltyhalflife=2h + +; Describes the scale over which channels still have some liquidity left on +; both channel ends. A very low value (compared to typical channel capacities) +; means that we assume unbalanced channels, a very high value means randomly +; balanced channels. Value in msat. (default: 300000000) +; routerrpc.bimodal.scale=1000000000 + +; Defines how strongly non-routed channels of forwarders should be taken into +; account for probability estimation. A weight of zero disables this feature. +; Valid values are in [0, 1]. (default: 0.2) +; routerrpc.bimodal.nodeweight=0.3 + +; Defines the information decay of knowledge about previous successes and +; failures in channels. (default: 168h0m0s) +; routerrpc.bimodal.decaytime=72h [workers] diff --git a/server.go b/server.go index 592e061bb..d63d9644d 100644 --- a/server.go +++ b/server.go @@ -873,15 +873,42 @@ func newServer(cfg *Config, listenAddrs []net.Addr, 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 + switch routingConfig.ProbabilityEstimatorType { + case routing.AprioriEstimatorName: + aCfg := routingConfig.AprioriConfig + aprioriConfig := routing.AprioriConfig{ + AprioriHopProbability: aCfg.HopProbability, + PenaltyHalfLife: aCfg.PenaltyHalfLife, + AprioriWeight: aCfg.Weight, + } + + estimator, err = routing.NewAprioriEstimator( + aprioriConfig, + ) + if err != nil { + return nil, err + } + + case routing.BimodalEstimatorName: + bCfg := routingConfig.BimodalConfig + bimodalConfig := routing.BimodalConfig{ + BimodalNodeWeight: bCfg.NodeWeight, + BimodalScaleMsat: lnwire.MilliSatoshi( + bCfg.Scale, + ), + BimodalDecayTime: bCfg.DecayTime, + } + + estimator, err = routing.NewBimodalEstimator( + bimodalConfig, + ) + if err != nil { + return nil, err + } + + default: + return nil, fmt.Errorf("unknown estimator type %v", + routingConfig.ProbabilityEstimatorType) } } From 2ccdfb1151c060f483c95160406be9e15a47b111 Mon Sep 17 00:00:00 2001 From: bitromortac Date: Tue, 22 Nov 2022 09:18:41 +0100 Subject: [PATCH 6/7] lncli+routerrpc: adapt mc api to dynamic estimator The active probability estimator can be switched dynamically using the `Set/GetMissionControl` API, maintaining backward compatibility. The lncli commands `setmccfg` and `getmccfg` are updated around this functionality. Note that deprecated configuration parameters were removed from the commands. --- cmd/lncli/cmd_mission_control.go | 200 +++++-- lnrpc/routerrpc/router.pb.go | 812 +++++++++++++++++++--------- lnrpc/routerrpc/router.proto | 102 +++- lnrpc/routerrpc/router.swagger.json | 64 ++- lnrpc/routerrpc/router_server.go | 140 ++++- lntemp/rpc/router.go | 14 + lntest/itest/lnd_routing_test.go | 78 ++- 7 files changed, 1074 insertions(+), 336 deletions(-) diff --git a/cmd/lncli/cmd_mission_control.go b/cmd/lncli/cmd_mission_control.go index 9d8b5533b..76c76f992 100644 --- a/cmd/lncli/cmd_mission_control.go +++ b/cmd/lncli/cmd_mission_control.go @@ -7,6 +7,7 @@ import ( "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/lnrpc/routerrpc" "github.com/lightningnetwork/lnd/lnwire" + "github.com/lightningnetwork/lnd/routing" "github.com/lightningnetwork/lnd/routing/route" "github.com/urfave/cli" ) @@ -45,26 +46,12 @@ var setCfgCommand = cli.Command{ Category: "Mission Control", Usage: "Set mission control's config.", Description: ` - Update the config values being used by mission control to calculate - the probability that payment routes will succeed. - `, + Update the config values being used by mission control to calculate the + probability that payment routes will succeed. The estimator type must be + provided to set estimator-related parameters.`, Flags: []cli.Flag{ - cli.DurationFlag{ - Name: "halflife", - Usage: "the amount of time taken to restore a node " + - "or channel to 50% probability of success.", - }, - cli.Float64Flag{ - Name: "hopprob", - Usage: "the probability of success assigned " + - "to hops that we have no information about", - }, - cli.Float64Flag{ - Name: "weight", - Usage: "the degree to which mission control should " + - "rely on historical results, expressed as " + - "value in [0;1]", - }, cli.UintFlag{ + // General settings. + cli.UintFlag{ Name: "pmtnr", Usage: "the number of payments mission control " + "should store", @@ -74,6 +61,48 @@ var setCfgCommand = cli.Command{ Usage: "the amount of time to wait after a failure " + "before raising failure amount", }, + // Probability estimator. + cli.StringFlag{ + Name: "estimator", + Usage: "the probability estimator to use, choose " + + "between 'apriori' or 'bimodal'", + }, + // Apriori config. + cli.DurationFlag{ + Name: "apriorihalflife", + Usage: "the amount of time taken to restore a node " + + "or channel to 50% probability of success.", + }, + cli.Float64Flag{ + Name: "apriorihopprob", + Usage: "the probability of success assigned " + + "to hops that we have no information about", + }, + cli.Float64Flag{ + Name: "aprioriweight", + Usage: "the degree to which mission control should " + + "rely on historical results, expressed as " + + "value in [0, 1]", + }, + // Bimodal config. + cli.DurationFlag{ + Name: "bimodaldecaytime", + Usage: "the time span after which we phase out " + + "learnings from previous payment attempts", + }, + cli.Uint64Flag{ + Name: "bimodalscale", + Usage: "controls the assumed channel liquidity " + + "imbalance in the network, measured in msat. " + + "a low value (compared to typical channel " + + "capacity) anticipates unbalanced channels.", + }, + cli.Float64Flag{ + Name: "bimodalweight", + Usage: "controls the degree to which the probability " + + "estimator takes into account other channels " + + "of a router", + }, }, Action: actionDecorator(setCfg), } @@ -85,51 +114,140 @@ func setCfg(ctx *cli.Context) error { client := routerrpc.NewRouterClient(conn) - resp, err := client.GetMissionControlConfig( + // Fetch current mission control config which we update to create our + // response. + mcCfg, err := client.GetMissionControlConfig( ctxc, &routerrpc.GetMissionControlConfigRequest{}, ) if err != nil { return err } + // haveValue is a helper variable to determine if a flag has been set or + // the help should be displayed. var haveValue bool - if ctx.IsSet("halflife") { - haveValue = true - resp.Config.HalfLifeSeconds = uint64(ctx.Duration( - "halflife", - ).Seconds()) - } - - if ctx.IsSet("hopprob") { - haveValue = true - resp.Config.HopProbability = float32(ctx.Float64("hopprob")) - } - - if ctx.IsSet("weight") { - haveValue = true - resp.Config.Weight = float32(ctx.Float64("weight")) - } - + // Handle general mission control settings. if ctx.IsSet("pmtnr") { haveValue = true - resp.Config.MaximumPaymentResults = uint32(ctx.Int("pmtnr")) + mcCfg.Config.MaximumPaymentResults = uint32(ctx.Int("pmtnr")) } - if ctx.IsSet("failrelax") { haveValue = true - resp.Config.MinimumFailureRelaxInterval = uint64(ctx.Duration( + mcCfg.Config.MinimumFailureRelaxInterval = uint64(ctx.Duration( "failrelax", ).Seconds()) } + // We switch between estimators and set corresponding configs. If + // estimator is not set, we ignore the values. + if ctx.IsSet("estimator") { + switch ctx.String("estimator") { + case routing.AprioriEstimatorName: + haveValue = true + + // If we switch from another estimator, initialize with + // default values. + if mcCfg.Config.Model != + routerrpc.MissionControlConfig_APRIORI { + + dCfg := routing.DefaultAprioriConfig() + aParams := &routerrpc.AprioriParameters{ + HalfLifeSeconds: uint64( + dCfg.PenaltyHalfLife.Seconds(), + ), + HopProbability: dCfg.AprioriHopProbability, //nolint:lll + Weight: dCfg.AprioriWeight, + } + + // We make sure the correct config is set. + mcCfg.Config.EstimatorConfig = + &routerrpc.MissionControlConfig_Apriori{ + Apriori: aParams, + } + } + + // We update all values for the apriori estimator. + mcCfg.Config.Model = routerrpc. + MissionControlConfig_APRIORI + + aCfg := mcCfg.Config.GetApriori() + if ctx.IsSet("apriorihalflife") { + aCfg.HalfLifeSeconds = uint64(ctx.Duration( + "apriorihalflife", + ).Seconds()) + } + + if ctx.IsSet("apriorihopprob") { + aCfg.HopProbability = ctx.Float64( + "apriorihopprob", + ) + } + + if ctx.IsSet("aprioriweight") { + aCfg.Weight = ctx.Float64("aprioriweight") + } + + case routing.BimodalEstimatorName: + haveValue = true + + // If we switch from another estimator, initialize with + // default values. + if mcCfg.Config.Model != + routerrpc.MissionControlConfig_BIMODAL { + + dCfg := routing.DefaultBimodalConfig() + bParams := &routerrpc.BimodalParameters{ + DecayTime: uint64( + dCfg.BimodalDecayTime.Seconds(), + ), + ScaleMsat: uint64( + dCfg.BimodalScaleMsat, + ), + NodeWeight: dCfg.BimodalNodeWeight, + } + + // We make sure the correct config is set. + mcCfg.Config.EstimatorConfig = + &routerrpc.MissionControlConfig_Bimodal{ + Bimodal: bParams, + } + } + + // We update all values for the bimodal estimator. + mcCfg.Config.Model = routerrpc. + MissionControlConfig_BIMODAL + + bCfg := mcCfg.Config.GetBimodal() + if ctx.IsSet("bimodaldecaytime") { + bCfg.DecayTime = uint64(ctx.Duration( + "bimodaldecaytime", + ).Seconds()) + } + + if ctx.IsSet("bimodalscale") { + bCfg.ScaleMsat = ctx.Uint64("bimodalscale") + } + + if ctx.IsSet("bimodalweight") { + bCfg.NodeWeight = ctx.Float64( + "bimodalweight", + ) + } + + default: + return fmt.Errorf("unknown estimator %v", + ctx.String("estimator")) + } + } + if !haveValue { return cli.ShowCommandHelp(ctx, "setmccfg") } _, err = client.SetMissionControlConfig( ctxc, &routerrpc.SetMissionControlConfigRequest{ - Config: resp.Config, + Config: mcCfg.Config, }, ) return err diff --git a/lnrpc/routerrpc/router.pb.go b/lnrpc/routerrpc/router.pb.go index f19cb0024..e81f09549 100644 --- a/lnrpc/routerrpc/router.pb.go +++ b/lnrpc/routerrpc/router.pb.go @@ -298,6 +298,52 @@ func (ChanStatusAction) EnumDescriptor() ([]byte, []int) { return file_routerrpc_router_proto_rawDescGZIP(), []int{3} } +type MissionControlConfig_ProbabilityModel int32 + +const ( + MissionControlConfig_APRIORI MissionControlConfig_ProbabilityModel = 0 + MissionControlConfig_BIMODAL MissionControlConfig_ProbabilityModel = 1 +) + +// Enum value maps for MissionControlConfig_ProbabilityModel. +var ( + MissionControlConfig_ProbabilityModel_name = map[int32]string{ + 0: "APRIORI", + 1: "BIMODAL", + } + MissionControlConfig_ProbabilityModel_value = map[string]int32{ + "APRIORI": 0, + "BIMODAL": 1, + } +) + +func (x MissionControlConfig_ProbabilityModel) Enum() *MissionControlConfig_ProbabilityModel { + p := new(MissionControlConfig_ProbabilityModel) + *p = x + return p +} + +func (x MissionControlConfig_ProbabilityModel) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (MissionControlConfig_ProbabilityModel) Descriptor() protoreflect.EnumDescriptor { + return file_routerrpc_router_proto_enumTypes[4].Descriptor() +} + +func (MissionControlConfig_ProbabilityModel) Type() protoreflect.EnumType { + return &file_routerrpc_router_proto_enumTypes[4] +} + +func (x MissionControlConfig_ProbabilityModel) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use MissionControlConfig_ProbabilityModel.Descriptor instead. +func (MissionControlConfig_ProbabilityModel) EnumDescriptor() ([]byte, []int) { + return file_routerrpc_router_proto_rawDescGZIP(), []int{19, 0} +} + type HtlcEvent_EventType int32 const ( @@ -334,11 +380,11 @@ func (x HtlcEvent_EventType) String() string { } func (HtlcEvent_EventType) Descriptor() protoreflect.EnumDescriptor { - return file_routerrpc_router_proto_enumTypes[4].Descriptor() + return file_routerrpc_router_proto_enumTypes[5].Descriptor() } func (HtlcEvent_EventType) Type() protoreflect.EnumType { - return &file_routerrpc_router_proto_enumTypes[4] + return &file_routerrpc_router_proto_enumTypes[5] } func (x HtlcEvent_EventType) Number() protoreflect.EnumNumber { @@ -347,7 +393,7 @@ func (x HtlcEvent_EventType) Number() protoreflect.EnumNumber { // Deprecated: Use HtlcEvent_EventType.Descriptor instead. func (HtlcEvent_EventType) EnumDescriptor() ([]byte, []int) { - return file_routerrpc_router_proto_rawDescGZIP(), []int{25, 0} + return file_routerrpc_router_proto_rawDescGZIP(), []int{27, 0} } type SendPaymentRequest struct { @@ -1590,29 +1636,46 @@ type MissionControlConfig struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - // The amount of time mission control will take to restore a penalized node - // or channel back to 50% success probability, expressed in seconds. Setting - // this value to a higher value will penalize failures for longer, making - // mission control less likely to route through nodes and channels that we - // have previously recorded failures for. + // Deprecated, use AprioriParameters. The amount of time mission control will + // take to restore a penalized node or channel back to 50% success probability, + // expressed in seconds. Setting this value to a higher value will penalize + // failures for longer, making mission control less likely to route through + // nodes and channels that we have previously recorded failures for. + // + // Deprecated: Do not use. HalfLifeSeconds uint64 `protobuf:"varint,1,opt,name=half_life_seconds,json=halfLifeSeconds,proto3" json:"half_life_seconds,omitempty"` - // The probability of success mission control should assign to hop in a route - // where it has no other information available. Higher values will make mission - // control more willing to try hops that we have no information about, lower - // values will discourage trying these hops. + // Deprecated, use AprioriParameters. The probability of success mission + // control should assign to hop in a route where it has no other information + // available. Higher values will make mission control more willing to try hops + // that we have no information about, lower values will discourage trying these + // hops. + // + // Deprecated: Do not use. HopProbability float32 `protobuf:"fixed32,2,opt,name=hop_probability,json=hopProbability,proto3" json:"hop_probability,omitempty"` - // The importance that mission control should place on historical results, - // expressed as a value in [0;1]. Setting this value to 1 will ignore all - // historical payments and just use the hop probability to assess the - // probability of success for each hop. A zero value ignores hop probability - // completely and relies entirely on historical results, unless none are - // available. + // Deprecated, use AprioriParameters. The importance that mission control + // should place on historical results, expressed as a value in [0;1]. Setting + // this value to 1 will ignore all historical payments and just use the hop + // probability to assess the probability of success for each hop. A zero value + // ignores hop probability completely and relies entirely on historical + // results, unless none are available. + // + // Deprecated: Do not use. Weight float32 `protobuf:"fixed32,3,opt,name=weight,proto3" json:"weight,omitempty"` // The maximum number of payment results that mission control will store. MaximumPaymentResults uint32 `protobuf:"varint,4,opt,name=maximum_payment_results,json=maximumPaymentResults,proto3" json:"maximum_payment_results,omitempty"` // The minimum time that must have passed since the previously recorded failure // before we raise the failure amount. MinimumFailureRelaxInterval uint64 `protobuf:"varint,5,opt,name=minimum_failure_relax_interval,json=minimumFailureRelaxInterval,proto3" json:"minimum_failure_relax_interval,omitempty"` + // ProbabilityModel defines which probability estimator should be used in + // pathfinding. + Model MissionControlConfig_ProbabilityModel `protobuf:"varint,6,opt,name=Model,proto3,enum=routerrpc.MissionControlConfig_ProbabilityModel" json:"Model,omitempty"` + // EstimatorConfig is populated dependent on the estimator type. + // + // Types that are assignable to EstimatorConfig: + // + // *MissionControlConfig_Apriori + // *MissionControlConfig_Bimodal + EstimatorConfig isMissionControlConfig_EstimatorConfig `protobuf_oneof:"EstimatorConfig"` } func (x *MissionControlConfig) Reset() { @@ -1647,6 +1710,7 @@ func (*MissionControlConfig) Descriptor() ([]byte, []int) { return file_routerrpc_router_proto_rawDescGZIP(), []int{19} } +// Deprecated: Do not use. func (x *MissionControlConfig) GetHalfLifeSeconds() uint64 { if x != nil { return x.HalfLifeSeconds @@ -1654,6 +1718,7 @@ func (x *MissionControlConfig) GetHalfLifeSeconds() uint64 { return 0 } +// Deprecated: Do not use. func (x *MissionControlConfig) GetHopProbability() float32 { if x != nil { return x.HopProbability @@ -1661,6 +1726,7 @@ func (x *MissionControlConfig) GetHopProbability() float32 { return 0 } +// Deprecated: Do not use. func (x *MissionControlConfig) GetWeight() float32 { if x != nil { return x.Weight @@ -1682,6 +1748,204 @@ func (x *MissionControlConfig) GetMinimumFailureRelaxInterval() uint64 { return 0 } +func (x *MissionControlConfig) GetModel() MissionControlConfig_ProbabilityModel { + if x != nil { + return x.Model + } + return MissionControlConfig_APRIORI +} + +func (m *MissionControlConfig) GetEstimatorConfig() isMissionControlConfig_EstimatorConfig { + if m != nil { + return m.EstimatorConfig + } + return nil +} + +func (x *MissionControlConfig) GetApriori() *AprioriParameters { + if x, ok := x.GetEstimatorConfig().(*MissionControlConfig_Apriori); ok { + return x.Apriori + } + return nil +} + +func (x *MissionControlConfig) GetBimodal() *BimodalParameters { + if x, ok := x.GetEstimatorConfig().(*MissionControlConfig_Bimodal); ok { + return x.Bimodal + } + return nil +} + +type isMissionControlConfig_EstimatorConfig interface { + isMissionControlConfig_EstimatorConfig() +} + +type MissionControlConfig_Apriori struct { + Apriori *AprioriParameters `protobuf:"bytes,7,opt,name=apriori,proto3,oneof"` +} + +type MissionControlConfig_Bimodal struct { + Bimodal *BimodalParameters `protobuf:"bytes,8,opt,name=bimodal,proto3,oneof"` +} + +func (*MissionControlConfig_Apriori) isMissionControlConfig_EstimatorConfig() {} + +func (*MissionControlConfig_Bimodal) isMissionControlConfig_EstimatorConfig() {} + +type BimodalParameters struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // NodeWeight defines how strongly other previous forwardings on channels of a + // router should be taken into account when computing a channel's probability + // to route. The allowed values are in the range [0, 1], where a value of 0 + // means that only direct information about a channel is taken into account. + NodeWeight float64 `protobuf:"fixed64,1,opt,name=node_weight,json=nodeWeight,proto3" json:"node_weight,omitempty"` + // ScaleMsat describes the scale over which channels statistically have some + // liquidity left. The value determines how quickly the bimodal distribution + // drops off from the edges of a channel. A larger value (compared to typical + // channel capacities) means that the drop off is slow and that channel + // balances are distributed more uniformly. A small value leads to the + // assumption of very unbalanced channels. + ScaleMsat uint64 `protobuf:"varint,2,opt,name=scale_msat,json=scaleMsat,proto3" json:"scale_msat,omitempty"` + // DecayTime describes the information decay of knowledge about previous + // successes and failures in channels. The smaller the decay time, the quicker + // we forget about past forwardings. + DecayTime uint64 `protobuf:"varint,3,opt,name=decay_time,json=decayTime,proto3" json:"decay_time,omitempty"` +} + +func (x *BimodalParameters) Reset() { + *x = BimodalParameters{} + if protoimpl.UnsafeEnabled { + mi := &file_routerrpc_router_proto_msgTypes[20] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *BimodalParameters) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BimodalParameters) ProtoMessage() {} + +func (x *BimodalParameters) ProtoReflect() protoreflect.Message { + mi := &file_routerrpc_router_proto_msgTypes[20] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BimodalParameters.ProtoReflect.Descriptor instead. +func (*BimodalParameters) Descriptor() ([]byte, []int) { + return file_routerrpc_router_proto_rawDescGZIP(), []int{20} +} + +func (x *BimodalParameters) GetNodeWeight() float64 { + if x != nil { + return x.NodeWeight + } + return 0 +} + +func (x *BimodalParameters) GetScaleMsat() uint64 { + if x != nil { + return x.ScaleMsat + } + return 0 +} + +func (x *BimodalParameters) GetDecayTime() uint64 { + if x != nil { + return x.DecayTime + } + return 0 +} + +type AprioriParameters struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The amount of time mission control will take to restore a penalized node + // or channel back to 50% success probability, expressed in seconds. Setting + // this value to a higher value will penalize failures for longer, making + // mission control less likely to route through nodes and channels that we + // have previously recorded failures for. + HalfLifeSeconds uint64 `protobuf:"varint,1,opt,name=half_life_seconds,json=halfLifeSeconds,proto3" json:"half_life_seconds,omitempty"` + // The probability of success mission control should assign to hop in a route + // where it has no other information available. Higher values will make mission + // control more willing to try hops that we have no information about, lower + // values will discourage trying these hops. + HopProbability float64 `protobuf:"fixed64,2,opt,name=hop_probability,json=hopProbability,proto3" json:"hop_probability,omitempty"` + // The importance that mission control should place on historical results, + // expressed as a value in [0;1]. Setting this value to 1 will ignore all + // historical payments and just use the hop probability to assess the + // probability of success for each hop. A zero value ignores hop probability + // completely and relies entirely on historical results, unless none are + // available. + Weight float64 `protobuf:"fixed64,3,opt,name=weight,proto3" json:"weight,omitempty"` +} + +func (x *AprioriParameters) Reset() { + *x = AprioriParameters{} + if protoimpl.UnsafeEnabled { + mi := &file_routerrpc_router_proto_msgTypes[21] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *AprioriParameters) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AprioriParameters) ProtoMessage() {} + +func (x *AprioriParameters) ProtoReflect() protoreflect.Message { + mi := &file_routerrpc_router_proto_msgTypes[21] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AprioriParameters.ProtoReflect.Descriptor instead. +func (*AprioriParameters) Descriptor() ([]byte, []int) { + return file_routerrpc_router_proto_rawDescGZIP(), []int{21} +} + +func (x *AprioriParameters) GetHalfLifeSeconds() uint64 { + if x != nil { + return x.HalfLifeSeconds + } + return 0 +} + +func (x *AprioriParameters) GetHopProbability() float64 { + if x != nil { + return x.HopProbability + } + return 0 +} + +func (x *AprioriParameters) GetWeight() float64 { + if x != nil { + return x.Weight + } + return 0 +} + type QueryProbabilityRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -1698,7 +1962,7 @@ type QueryProbabilityRequest struct { func (x *QueryProbabilityRequest) Reset() { *x = QueryProbabilityRequest{} if protoimpl.UnsafeEnabled { - mi := &file_routerrpc_router_proto_msgTypes[20] + mi := &file_routerrpc_router_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1711,7 +1975,7 @@ func (x *QueryProbabilityRequest) String() string { func (*QueryProbabilityRequest) ProtoMessage() {} func (x *QueryProbabilityRequest) ProtoReflect() protoreflect.Message { - mi := &file_routerrpc_router_proto_msgTypes[20] + mi := &file_routerrpc_router_proto_msgTypes[22] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1724,7 +1988,7 @@ func (x *QueryProbabilityRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use QueryProbabilityRequest.ProtoReflect.Descriptor instead. func (*QueryProbabilityRequest) Descriptor() ([]byte, []int) { - return file_routerrpc_router_proto_rawDescGZIP(), []int{20} + return file_routerrpc_router_proto_rawDescGZIP(), []int{22} } func (x *QueryProbabilityRequest) GetFromNode() []byte { @@ -1762,7 +2026,7 @@ type QueryProbabilityResponse struct { func (x *QueryProbabilityResponse) Reset() { *x = QueryProbabilityResponse{} if protoimpl.UnsafeEnabled { - mi := &file_routerrpc_router_proto_msgTypes[21] + mi := &file_routerrpc_router_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1775,7 +2039,7 @@ func (x *QueryProbabilityResponse) String() string { func (*QueryProbabilityResponse) ProtoMessage() {} func (x *QueryProbabilityResponse) ProtoReflect() protoreflect.Message { - mi := &file_routerrpc_router_proto_msgTypes[21] + mi := &file_routerrpc_router_proto_msgTypes[23] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1788,7 +2052,7 @@ func (x *QueryProbabilityResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use QueryProbabilityResponse.ProtoReflect.Descriptor instead. func (*QueryProbabilityResponse) Descriptor() ([]byte, []int) { - return file_routerrpc_router_proto_rawDescGZIP(), []int{21} + return file_routerrpc_router_proto_rawDescGZIP(), []int{23} } func (x *QueryProbabilityResponse) GetProbability() float64 { @@ -1829,7 +2093,7 @@ type BuildRouteRequest struct { func (x *BuildRouteRequest) Reset() { *x = BuildRouteRequest{} if protoimpl.UnsafeEnabled { - mi := &file_routerrpc_router_proto_msgTypes[22] + mi := &file_routerrpc_router_proto_msgTypes[24] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1842,7 +2106,7 @@ func (x *BuildRouteRequest) String() string { func (*BuildRouteRequest) ProtoMessage() {} func (x *BuildRouteRequest) ProtoReflect() protoreflect.Message { - mi := &file_routerrpc_router_proto_msgTypes[22] + mi := &file_routerrpc_router_proto_msgTypes[24] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1855,7 +2119,7 @@ func (x *BuildRouteRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use BuildRouteRequest.ProtoReflect.Descriptor instead. func (*BuildRouteRequest) Descriptor() ([]byte, []int) { - return file_routerrpc_router_proto_rawDescGZIP(), []int{22} + return file_routerrpc_router_proto_rawDescGZIP(), []int{24} } func (x *BuildRouteRequest) GetAmtMsat() int64 { @@ -1905,7 +2169,7 @@ type BuildRouteResponse struct { func (x *BuildRouteResponse) Reset() { *x = BuildRouteResponse{} if protoimpl.UnsafeEnabled { - mi := &file_routerrpc_router_proto_msgTypes[23] + mi := &file_routerrpc_router_proto_msgTypes[25] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1918,7 +2182,7 @@ func (x *BuildRouteResponse) String() string { func (*BuildRouteResponse) ProtoMessage() {} func (x *BuildRouteResponse) ProtoReflect() protoreflect.Message { - mi := &file_routerrpc_router_proto_msgTypes[23] + mi := &file_routerrpc_router_proto_msgTypes[25] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1931,7 +2195,7 @@ func (x *BuildRouteResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use BuildRouteResponse.ProtoReflect.Descriptor instead. func (*BuildRouteResponse) Descriptor() ([]byte, []int) { - return file_routerrpc_router_proto_rawDescGZIP(), []int{23} + return file_routerrpc_router_proto_rawDescGZIP(), []int{25} } func (x *BuildRouteResponse) GetRoute() *lnrpc.Route { @@ -1950,7 +2214,7 @@ type SubscribeHtlcEventsRequest struct { func (x *SubscribeHtlcEventsRequest) Reset() { *x = SubscribeHtlcEventsRequest{} if protoimpl.UnsafeEnabled { - mi := &file_routerrpc_router_proto_msgTypes[24] + mi := &file_routerrpc_router_proto_msgTypes[26] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1963,7 +2227,7 @@ func (x *SubscribeHtlcEventsRequest) String() string { func (*SubscribeHtlcEventsRequest) ProtoMessage() {} func (x *SubscribeHtlcEventsRequest) ProtoReflect() protoreflect.Message { - mi := &file_routerrpc_router_proto_msgTypes[24] + mi := &file_routerrpc_router_proto_msgTypes[26] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1976,7 +2240,7 @@ func (x *SubscribeHtlcEventsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SubscribeHtlcEventsRequest.ProtoReflect.Descriptor instead. func (*SubscribeHtlcEventsRequest) Descriptor() ([]byte, []int) { - return file_routerrpc_router_proto_rawDescGZIP(), []int{24} + return file_routerrpc_router_proto_rawDescGZIP(), []int{26} } // HtlcEvent contains the htlc event that was processed. These are served on a @@ -2021,7 +2285,7 @@ type HtlcEvent struct { func (x *HtlcEvent) Reset() { *x = HtlcEvent{} if protoimpl.UnsafeEnabled { - mi := &file_routerrpc_router_proto_msgTypes[25] + mi := &file_routerrpc_router_proto_msgTypes[27] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2034,7 +2298,7 @@ func (x *HtlcEvent) String() string { func (*HtlcEvent) ProtoMessage() {} func (x *HtlcEvent) ProtoReflect() protoreflect.Message { - mi := &file_routerrpc_router_proto_msgTypes[25] + mi := &file_routerrpc_router_proto_msgTypes[27] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2047,7 +2311,7 @@ func (x *HtlcEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use HtlcEvent.ProtoReflect.Descriptor instead. func (*HtlcEvent) Descriptor() ([]byte, []int) { - return file_routerrpc_router_proto_rawDescGZIP(), []int{25} + return file_routerrpc_router_proto_rawDescGZIP(), []int{27} } func (x *HtlcEvent) GetIncomingChannelId() uint64 { @@ -2199,7 +2463,7 @@ type HtlcInfo struct { func (x *HtlcInfo) Reset() { *x = HtlcInfo{} if protoimpl.UnsafeEnabled { - mi := &file_routerrpc_router_proto_msgTypes[26] + mi := &file_routerrpc_router_proto_msgTypes[28] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2212,7 +2476,7 @@ func (x *HtlcInfo) String() string { func (*HtlcInfo) ProtoMessage() {} func (x *HtlcInfo) ProtoReflect() protoreflect.Message { - mi := &file_routerrpc_router_proto_msgTypes[26] + mi := &file_routerrpc_router_proto_msgTypes[28] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2225,7 +2489,7 @@ func (x *HtlcInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use HtlcInfo.ProtoReflect.Descriptor instead. func (*HtlcInfo) Descriptor() ([]byte, []int) { - return file_routerrpc_router_proto_rawDescGZIP(), []int{26} + return file_routerrpc_router_proto_rawDescGZIP(), []int{28} } func (x *HtlcInfo) GetIncomingTimelock() uint32 { @@ -2268,7 +2532,7 @@ type ForwardEvent struct { func (x *ForwardEvent) Reset() { *x = ForwardEvent{} if protoimpl.UnsafeEnabled { - mi := &file_routerrpc_router_proto_msgTypes[27] + mi := &file_routerrpc_router_proto_msgTypes[29] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2281,7 +2545,7 @@ func (x *ForwardEvent) String() string { func (*ForwardEvent) ProtoMessage() {} func (x *ForwardEvent) ProtoReflect() protoreflect.Message { - mi := &file_routerrpc_router_proto_msgTypes[27] + mi := &file_routerrpc_router_proto_msgTypes[29] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2294,7 +2558,7 @@ func (x *ForwardEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use ForwardEvent.ProtoReflect.Descriptor instead. func (*ForwardEvent) Descriptor() ([]byte, []int) { - return file_routerrpc_router_proto_rawDescGZIP(), []int{27} + return file_routerrpc_router_proto_rawDescGZIP(), []int{29} } func (x *ForwardEvent) GetInfo() *HtlcInfo { @@ -2313,7 +2577,7 @@ type ForwardFailEvent struct { func (x *ForwardFailEvent) Reset() { *x = ForwardFailEvent{} if protoimpl.UnsafeEnabled { - mi := &file_routerrpc_router_proto_msgTypes[28] + mi := &file_routerrpc_router_proto_msgTypes[30] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2326,7 +2590,7 @@ func (x *ForwardFailEvent) String() string { func (*ForwardFailEvent) ProtoMessage() {} func (x *ForwardFailEvent) ProtoReflect() protoreflect.Message { - mi := &file_routerrpc_router_proto_msgTypes[28] + mi := &file_routerrpc_router_proto_msgTypes[30] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2339,7 +2603,7 @@ func (x *ForwardFailEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use ForwardFailEvent.ProtoReflect.Descriptor instead. func (*ForwardFailEvent) Descriptor() ([]byte, []int) { - return file_routerrpc_router_proto_rawDescGZIP(), []int{28} + return file_routerrpc_router_proto_rawDescGZIP(), []int{30} } type SettleEvent struct { @@ -2354,7 +2618,7 @@ type SettleEvent struct { func (x *SettleEvent) Reset() { *x = SettleEvent{} if protoimpl.UnsafeEnabled { - mi := &file_routerrpc_router_proto_msgTypes[29] + mi := &file_routerrpc_router_proto_msgTypes[31] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2367,7 +2631,7 @@ func (x *SettleEvent) String() string { func (*SettleEvent) ProtoMessage() {} func (x *SettleEvent) ProtoReflect() protoreflect.Message { - mi := &file_routerrpc_router_proto_msgTypes[29] + mi := &file_routerrpc_router_proto_msgTypes[31] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2380,7 +2644,7 @@ func (x *SettleEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use SettleEvent.ProtoReflect.Descriptor instead. func (*SettleEvent) Descriptor() ([]byte, []int) { - return file_routerrpc_router_proto_rawDescGZIP(), []int{29} + return file_routerrpc_router_proto_rawDescGZIP(), []int{31} } func (x *SettleEvent) GetPreimage() []byte { @@ -2402,7 +2666,7 @@ type FinalHtlcEvent struct { func (x *FinalHtlcEvent) Reset() { *x = FinalHtlcEvent{} if protoimpl.UnsafeEnabled { - mi := &file_routerrpc_router_proto_msgTypes[30] + mi := &file_routerrpc_router_proto_msgTypes[32] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2415,7 +2679,7 @@ func (x *FinalHtlcEvent) String() string { func (*FinalHtlcEvent) ProtoMessage() {} func (x *FinalHtlcEvent) ProtoReflect() protoreflect.Message { - mi := &file_routerrpc_router_proto_msgTypes[30] + mi := &file_routerrpc_router_proto_msgTypes[32] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2428,7 +2692,7 @@ func (x *FinalHtlcEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use FinalHtlcEvent.ProtoReflect.Descriptor instead. func (*FinalHtlcEvent) Descriptor() ([]byte, []int) { - return file_routerrpc_router_proto_rawDescGZIP(), []int{30} + return file_routerrpc_router_proto_rawDescGZIP(), []int{32} } func (x *FinalHtlcEvent) GetSettled() bool { @@ -2454,7 +2718,7 @@ type SubscribedEvent struct { func (x *SubscribedEvent) Reset() { *x = SubscribedEvent{} if protoimpl.UnsafeEnabled { - mi := &file_routerrpc_router_proto_msgTypes[31] + mi := &file_routerrpc_router_proto_msgTypes[33] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2467,7 +2731,7 @@ func (x *SubscribedEvent) String() string { func (*SubscribedEvent) ProtoMessage() {} func (x *SubscribedEvent) ProtoReflect() protoreflect.Message { - mi := &file_routerrpc_router_proto_msgTypes[31] + mi := &file_routerrpc_router_proto_msgTypes[33] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2480,7 +2744,7 @@ func (x *SubscribedEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use SubscribedEvent.ProtoReflect.Descriptor instead. func (*SubscribedEvent) Descriptor() ([]byte, []int) { - return file_routerrpc_router_proto_rawDescGZIP(), []int{31} + return file_routerrpc_router_proto_rawDescGZIP(), []int{33} } type LinkFailEvent struct { @@ -2503,7 +2767,7 @@ type LinkFailEvent struct { func (x *LinkFailEvent) Reset() { *x = LinkFailEvent{} if protoimpl.UnsafeEnabled { - mi := &file_routerrpc_router_proto_msgTypes[32] + mi := &file_routerrpc_router_proto_msgTypes[34] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2516,7 +2780,7 @@ func (x *LinkFailEvent) String() string { func (*LinkFailEvent) ProtoMessage() {} func (x *LinkFailEvent) ProtoReflect() protoreflect.Message { - mi := &file_routerrpc_router_proto_msgTypes[32] + mi := &file_routerrpc_router_proto_msgTypes[34] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2529,7 +2793,7 @@ func (x *LinkFailEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use LinkFailEvent.ProtoReflect.Descriptor instead. func (*LinkFailEvent) Descriptor() ([]byte, []int) { - return file_routerrpc_router_proto_rawDescGZIP(), []int{32} + return file_routerrpc_router_proto_rawDescGZIP(), []int{34} } func (x *LinkFailEvent) GetInfo() *HtlcInfo { @@ -2576,7 +2840,7 @@ type PaymentStatus struct { func (x *PaymentStatus) Reset() { *x = PaymentStatus{} if protoimpl.UnsafeEnabled { - mi := &file_routerrpc_router_proto_msgTypes[33] + mi := &file_routerrpc_router_proto_msgTypes[35] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2589,7 +2853,7 @@ func (x *PaymentStatus) String() string { func (*PaymentStatus) ProtoMessage() {} func (x *PaymentStatus) ProtoReflect() protoreflect.Message { - mi := &file_routerrpc_router_proto_msgTypes[33] + mi := &file_routerrpc_router_proto_msgTypes[35] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2602,7 +2866,7 @@ func (x *PaymentStatus) ProtoReflect() protoreflect.Message { // Deprecated: Use PaymentStatus.ProtoReflect.Descriptor instead. func (*PaymentStatus) Descriptor() ([]byte, []int) { - return file_routerrpc_router_proto_rawDescGZIP(), []int{33} + return file_routerrpc_router_proto_rawDescGZIP(), []int{35} } func (x *PaymentStatus) GetState() PaymentState { @@ -2640,7 +2904,7 @@ type CircuitKey struct { func (x *CircuitKey) Reset() { *x = CircuitKey{} if protoimpl.UnsafeEnabled { - mi := &file_routerrpc_router_proto_msgTypes[34] + mi := &file_routerrpc_router_proto_msgTypes[36] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2653,7 +2917,7 @@ func (x *CircuitKey) String() string { func (*CircuitKey) ProtoMessage() {} func (x *CircuitKey) ProtoReflect() protoreflect.Message { - mi := &file_routerrpc_router_proto_msgTypes[34] + mi := &file_routerrpc_router_proto_msgTypes[36] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2666,7 +2930,7 @@ func (x *CircuitKey) ProtoReflect() protoreflect.Message { // Deprecated: Use CircuitKey.ProtoReflect.Descriptor instead. func (*CircuitKey) Descriptor() ([]byte, []int) { - return file_routerrpc_router_proto_rawDescGZIP(), []int{34} + return file_routerrpc_router_proto_rawDescGZIP(), []int{36} } func (x *CircuitKey) GetChanId() uint64 { @@ -2719,7 +2983,7 @@ type ForwardHtlcInterceptRequest struct { func (x *ForwardHtlcInterceptRequest) Reset() { *x = ForwardHtlcInterceptRequest{} if protoimpl.UnsafeEnabled { - mi := &file_routerrpc_router_proto_msgTypes[35] + mi := &file_routerrpc_router_proto_msgTypes[37] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2732,7 +2996,7 @@ func (x *ForwardHtlcInterceptRequest) String() string { func (*ForwardHtlcInterceptRequest) ProtoMessage() {} func (x *ForwardHtlcInterceptRequest) ProtoReflect() protoreflect.Message { - mi := &file_routerrpc_router_proto_msgTypes[35] + mi := &file_routerrpc_router_proto_msgTypes[37] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2745,7 +3009,7 @@ func (x *ForwardHtlcInterceptRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ForwardHtlcInterceptRequest.ProtoReflect.Descriptor instead. func (*ForwardHtlcInterceptRequest) Descriptor() ([]byte, []int) { - return file_routerrpc_router_proto_rawDescGZIP(), []int{35} + return file_routerrpc_router_proto_rawDescGZIP(), []int{37} } func (x *ForwardHtlcInterceptRequest) GetIncomingCircuitKey() *CircuitKey { @@ -2855,7 +3119,7 @@ type ForwardHtlcInterceptResponse struct { func (x *ForwardHtlcInterceptResponse) Reset() { *x = ForwardHtlcInterceptResponse{} if protoimpl.UnsafeEnabled { - mi := &file_routerrpc_router_proto_msgTypes[36] + mi := &file_routerrpc_router_proto_msgTypes[38] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2868,7 +3132,7 @@ func (x *ForwardHtlcInterceptResponse) String() string { func (*ForwardHtlcInterceptResponse) ProtoMessage() {} func (x *ForwardHtlcInterceptResponse) ProtoReflect() protoreflect.Message { - mi := &file_routerrpc_router_proto_msgTypes[36] + mi := &file_routerrpc_router_proto_msgTypes[38] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2881,7 +3145,7 @@ func (x *ForwardHtlcInterceptResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ForwardHtlcInterceptResponse.ProtoReflect.Descriptor instead. func (*ForwardHtlcInterceptResponse) Descriptor() ([]byte, []int) { - return file_routerrpc_router_proto_rawDescGZIP(), []int{36} + return file_routerrpc_router_proto_rawDescGZIP(), []int{38} } func (x *ForwardHtlcInterceptResponse) GetIncomingCircuitKey() *CircuitKey { @@ -2931,7 +3195,7 @@ type UpdateChanStatusRequest struct { func (x *UpdateChanStatusRequest) Reset() { *x = UpdateChanStatusRequest{} if protoimpl.UnsafeEnabled { - mi := &file_routerrpc_router_proto_msgTypes[37] + mi := &file_routerrpc_router_proto_msgTypes[39] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2944,7 +3208,7 @@ func (x *UpdateChanStatusRequest) String() string { func (*UpdateChanStatusRequest) ProtoMessage() {} func (x *UpdateChanStatusRequest) ProtoReflect() protoreflect.Message { - mi := &file_routerrpc_router_proto_msgTypes[37] + mi := &file_routerrpc_router_proto_msgTypes[39] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2957,7 +3221,7 @@ func (x *UpdateChanStatusRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateChanStatusRequest.ProtoReflect.Descriptor instead. func (*UpdateChanStatusRequest) Descriptor() ([]byte, []int) { - return file_routerrpc_router_proto_rawDescGZIP(), []int{37} + return file_routerrpc_router_proto_rawDescGZIP(), []int{39} } func (x *UpdateChanStatusRequest) GetChanPoint() *lnrpc.ChannelPoint { @@ -2983,7 +3247,7 @@ type UpdateChanStatusResponse struct { func (x *UpdateChanStatusResponse) Reset() { *x = UpdateChanStatusResponse{} if protoimpl.UnsafeEnabled { - mi := &file_routerrpc_router_proto_msgTypes[38] + mi := &file_routerrpc_router_proto_msgTypes[40] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2996,7 +3260,7 @@ func (x *UpdateChanStatusResponse) String() string { func (*UpdateChanStatusResponse) ProtoMessage() {} func (x *UpdateChanStatusResponse) ProtoReflect() protoreflect.Message { - mi := &file_routerrpc_router_proto_msgTypes[38] + mi := &file_routerrpc_router_proto_msgTypes[40] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3009,7 +3273,7 @@ func (x *UpdateChanStatusResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateChanStatusResponse.ProtoReflect.Descriptor instead. func (*UpdateChanStatusResponse) Descriptor() ([]byte, []int) { - return file_routerrpc_router_proto_rawDescGZIP(), []int{38} + return file_routerrpc_router_proto_rawDescGZIP(), []int{40} } var File_routerrpc_router_proto protoreflect.FileDescriptor @@ -3175,23 +3439,55 @@ var file_routerrpc_router_proto_rawDesc = []byte{ 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x21, 0x0a, 0x1f, 0x53, 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x80, 0x02, 0x0a, 0x14, 0x4d, 0x69, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x89, 0x04, 0x0a, 0x14, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x12, 0x2a, 0x0a, 0x11, 0x68, 0x61, 0x6c, 0x66, 0x5f, 0x6c, 0x69, 0x66, 0x65, 0x5f, 0x73, - 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x68, 0x61, - 0x6c, 0x66, 0x4c, 0x69, 0x66, 0x65, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x12, 0x27, 0x0a, - 0x0f, 0x68, 0x6f, 0x70, 0x5f, 0x70, 0x72, 0x6f, 0x62, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x02, 0x52, 0x0e, 0x68, 0x6f, 0x70, 0x50, 0x72, 0x6f, 0x62, 0x61, - 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x02, 0x52, 0x06, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x36, - 0x0a, 0x17, 0x6d, 0x61, 0x78, 0x69, 0x6d, 0x75, 0x6d, 0x5f, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, - 0x74, 0x5f, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, - 0x15, 0x6d, 0x61, 0x78, 0x69, 0x6d, 0x75, 0x6d, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, - 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x12, 0x43, 0x0a, 0x1e, 0x6d, 0x69, 0x6e, 0x69, 0x6d, 0x75, - 0x6d, 0x5f, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x78, 0x5f, - 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x1b, - 0x6d, 0x69, 0x6e, 0x69, 0x6d, 0x75, 0x6d, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x52, 0x65, - 0x6c, 0x61, 0x78, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x22, 0x6a, 0x0a, 0x17, 0x51, + 0x67, 0x12, 0x2e, 0x0a, 0x11, 0x68, 0x61, 0x6c, 0x66, 0x5f, 0x6c, 0x69, 0x66, 0x65, 0x5f, 0x73, + 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x18, 0x01, + 0x52, 0x0f, 0x68, 0x61, 0x6c, 0x66, 0x4c, 0x69, 0x66, 0x65, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, + 0x73, 0x12, 0x2b, 0x0a, 0x0f, 0x68, 0x6f, 0x70, 0x5f, 0x70, 0x72, 0x6f, 0x62, 0x61, 0x62, 0x69, + 0x6c, 0x69, 0x74, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x02, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0e, + 0x68, 0x6f, 0x70, 0x50, 0x72, 0x6f, 0x62, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x12, 0x1a, + 0x0a, 0x06, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x02, 0x42, 0x02, + 0x18, 0x01, 0x52, 0x06, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x36, 0x0a, 0x17, 0x6d, 0x61, + 0x78, 0x69, 0x6d, 0x75, 0x6d, 0x5f, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x72, 0x65, + 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x15, 0x6d, 0x61, 0x78, + 0x69, 0x6d, 0x75, 0x6d, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x75, 0x6c, + 0x74, 0x73, 0x12, 0x43, 0x0a, 0x1e, 0x6d, 0x69, 0x6e, 0x69, 0x6d, 0x75, 0x6d, 0x5f, 0x66, 0x61, + 0x69, 0x6c, 0x75, 0x72, 0x65, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x78, 0x5f, 0x69, 0x6e, 0x74, 0x65, + 0x72, 0x76, 0x61, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x1b, 0x6d, 0x69, 0x6e, 0x69, + 0x6d, 0x75, 0x6d, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x52, 0x65, 0x6c, 0x61, 0x78, 0x49, + 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, 0x46, 0x0a, 0x05, 0x4d, 0x6f, 0x64, 0x65, 0x6c, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x30, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, + 0x70, 0x63, 0x2e, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, + 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x50, 0x72, 0x6f, 0x62, 0x61, 0x62, 0x69, 0x6c, + 0x69, 0x74, 0x79, 0x4d, 0x6f, 0x64, 0x65, 0x6c, 0x52, 0x05, 0x4d, 0x6f, 0x64, 0x65, 0x6c, 0x12, + 0x38, 0x0a, 0x07, 0x61, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x1c, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x70, 0x72, + 0x69, 0x6f, 0x72, 0x69, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x48, 0x00, + 0x52, 0x07, 0x61, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x12, 0x38, 0x0a, 0x07, 0x62, 0x69, 0x6d, + 0x6f, 0x64, 0x61, 0x6c, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x72, 0x6f, 0x75, + 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x69, 0x6d, 0x6f, 0x64, 0x61, 0x6c, 0x50, 0x61, + 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x48, 0x00, 0x52, 0x07, 0x62, 0x69, 0x6d, 0x6f, + 0x64, 0x61, 0x6c, 0x22, 0x2c, 0x0a, 0x10, 0x50, 0x72, 0x6f, 0x62, 0x61, 0x62, 0x69, 0x6c, 0x69, + 0x74, 0x79, 0x4d, 0x6f, 0x64, 0x65, 0x6c, 0x12, 0x0b, 0x0a, 0x07, 0x41, 0x50, 0x52, 0x49, 0x4f, + 0x52, 0x49, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x42, 0x49, 0x4d, 0x4f, 0x44, 0x41, 0x4c, 0x10, + 0x01, 0x42, 0x11, 0x0a, 0x0f, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x6f, 0x72, 0x43, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x22, 0x72, 0x0a, 0x11, 0x42, 0x69, 0x6d, 0x6f, 0x64, 0x61, 0x6c, 0x50, + 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x6e, 0x6f, 0x64, + 0x65, 0x5f, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0a, + 0x6e, 0x6f, 0x64, 0x65, 0x57, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x63, + 0x61, 0x6c, 0x65, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, + 0x73, 0x63, 0x61, 0x6c, 0x65, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x64, 0x65, 0x63, + 0x61, 0x79, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x64, + 0x65, 0x63, 0x61, 0x79, 0x54, 0x69, 0x6d, 0x65, 0x22, 0x80, 0x01, 0x0a, 0x11, 0x41, 0x70, 0x72, + 0x69, 0x6f, 0x72, 0x69, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x12, 0x2a, + 0x0a, 0x11, 0x68, 0x61, 0x6c, 0x66, 0x5f, 0x6c, 0x69, 0x66, 0x65, 0x5f, 0x73, 0x65, 0x63, 0x6f, + 0x6e, 0x64, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x68, 0x61, 0x6c, 0x66, 0x4c, + 0x69, 0x66, 0x65, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x12, 0x27, 0x0a, 0x0f, 0x68, 0x6f, + 0x70, 0x5f, 0x70, 0x72, 0x6f, 0x62, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x01, 0x52, 0x0e, 0x68, 0x6f, 0x70, 0x50, 0x72, 0x6f, 0x62, 0x61, 0x62, 0x69, 0x6c, + 0x69, 0x74, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x01, 0x52, 0x06, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x22, 0x6a, 0x0a, 0x17, 0x51, 0x75, 0x65, 0x72, 0x79, 0x50, 0x72, 0x6f, 0x62, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x66, 0x72, 0x6f, 0x6d, 0x5f, 0x6e, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x66, 0x72, 0x6f, 0x6d, 0x4e, @@ -3560,138 +3856,144 @@ func file_routerrpc_router_proto_rawDescGZIP() []byte { return file_routerrpc_router_proto_rawDescData } -var file_routerrpc_router_proto_enumTypes = make([]protoimpl.EnumInfo, 5) -var file_routerrpc_router_proto_msgTypes = make([]protoimpl.MessageInfo, 41) +var file_routerrpc_router_proto_enumTypes = make([]protoimpl.EnumInfo, 6) +var file_routerrpc_router_proto_msgTypes = make([]protoimpl.MessageInfo, 43) var file_routerrpc_router_proto_goTypes = []interface{}{ - (FailureDetail)(0), // 0: routerrpc.FailureDetail - (PaymentState)(0), // 1: routerrpc.PaymentState - (ResolveHoldForwardAction)(0), // 2: routerrpc.ResolveHoldForwardAction - (ChanStatusAction)(0), // 3: routerrpc.ChanStatusAction - (HtlcEvent_EventType)(0), // 4: routerrpc.HtlcEvent.EventType - (*SendPaymentRequest)(nil), // 5: routerrpc.SendPaymentRequest - (*TrackPaymentRequest)(nil), // 6: routerrpc.TrackPaymentRequest - (*TrackPaymentsRequest)(nil), // 7: routerrpc.TrackPaymentsRequest - (*RouteFeeRequest)(nil), // 8: routerrpc.RouteFeeRequest - (*RouteFeeResponse)(nil), // 9: routerrpc.RouteFeeResponse - (*SendToRouteRequest)(nil), // 10: routerrpc.SendToRouteRequest - (*SendToRouteResponse)(nil), // 11: routerrpc.SendToRouteResponse - (*ResetMissionControlRequest)(nil), // 12: routerrpc.ResetMissionControlRequest - (*ResetMissionControlResponse)(nil), // 13: routerrpc.ResetMissionControlResponse - (*QueryMissionControlRequest)(nil), // 14: routerrpc.QueryMissionControlRequest - (*QueryMissionControlResponse)(nil), // 15: routerrpc.QueryMissionControlResponse - (*XImportMissionControlRequest)(nil), // 16: routerrpc.XImportMissionControlRequest - (*XImportMissionControlResponse)(nil), // 17: routerrpc.XImportMissionControlResponse - (*PairHistory)(nil), // 18: routerrpc.PairHistory - (*PairData)(nil), // 19: routerrpc.PairData - (*GetMissionControlConfigRequest)(nil), // 20: routerrpc.GetMissionControlConfigRequest - (*GetMissionControlConfigResponse)(nil), // 21: routerrpc.GetMissionControlConfigResponse - (*SetMissionControlConfigRequest)(nil), // 22: routerrpc.SetMissionControlConfigRequest - (*SetMissionControlConfigResponse)(nil), // 23: routerrpc.SetMissionControlConfigResponse - (*MissionControlConfig)(nil), // 24: routerrpc.MissionControlConfig - (*QueryProbabilityRequest)(nil), // 25: routerrpc.QueryProbabilityRequest - (*QueryProbabilityResponse)(nil), // 26: routerrpc.QueryProbabilityResponse - (*BuildRouteRequest)(nil), // 27: routerrpc.BuildRouteRequest - (*BuildRouteResponse)(nil), // 28: routerrpc.BuildRouteResponse - (*SubscribeHtlcEventsRequest)(nil), // 29: routerrpc.SubscribeHtlcEventsRequest - (*HtlcEvent)(nil), // 30: routerrpc.HtlcEvent - (*HtlcInfo)(nil), // 31: routerrpc.HtlcInfo - (*ForwardEvent)(nil), // 32: routerrpc.ForwardEvent - (*ForwardFailEvent)(nil), // 33: routerrpc.ForwardFailEvent - (*SettleEvent)(nil), // 34: routerrpc.SettleEvent - (*FinalHtlcEvent)(nil), // 35: routerrpc.FinalHtlcEvent - (*SubscribedEvent)(nil), // 36: routerrpc.SubscribedEvent - (*LinkFailEvent)(nil), // 37: routerrpc.LinkFailEvent - (*PaymentStatus)(nil), // 38: routerrpc.PaymentStatus - (*CircuitKey)(nil), // 39: routerrpc.CircuitKey - (*ForwardHtlcInterceptRequest)(nil), // 40: routerrpc.ForwardHtlcInterceptRequest - (*ForwardHtlcInterceptResponse)(nil), // 41: routerrpc.ForwardHtlcInterceptResponse - (*UpdateChanStatusRequest)(nil), // 42: routerrpc.UpdateChanStatusRequest - (*UpdateChanStatusResponse)(nil), // 43: routerrpc.UpdateChanStatusResponse - nil, // 44: routerrpc.SendPaymentRequest.DestCustomRecordsEntry - nil, // 45: routerrpc.ForwardHtlcInterceptRequest.CustomRecordsEntry - (*lnrpc.RouteHint)(nil), // 46: lnrpc.RouteHint - (lnrpc.FeatureBit)(0), // 47: lnrpc.FeatureBit - (*lnrpc.Route)(nil), // 48: lnrpc.Route - (*lnrpc.Failure)(nil), // 49: lnrpc.Failure - (lnrpc.Failure_FailureCode)(0), // 50: lnrpc.Failure.FailureCode - (*lnrpc.HTLCAttempt)(nil), // 51: lnrpc.HTLCAttempt - (*lnrpc.ChannelPoint)(nil), // 52: lnrpc.ChannelPoint - (*lnrpc.Payment)(nil), // 53: lnrpc.Payment + (FailureDetail)(0), // 0: routerrpc.FailureDetail + (PaymentState)(0), // 1: routerrpc.PaymentState + (ResolveHoldForwardAction)(0), // 2: routerrpc.ResolveHoldForwardAction + (ChanStatusAction)(0), // 3: routerrpc.ChanStatusAction + (MissionControlConfig_ProbabilityModel)(0), // 4: routerrpc.MissionControlConfig.ProbabilityModel + (HtlcEvent_EventType)(0), // 5: routerrpc.HtlcEvent.EventType + (*SendPaymentRequest)(nil), // 6: routerrpc.SendPaymentRequest + (*TrackPaymentRequest)(nil), // 7: routerrpc.TrackPaymentRequest + (*TrackPaymentsRequest)(nil), // 8: routerrpc.TrackPaymentsRequest + (*RouteFeeRequest)(nil), // 9: routerrpc.RouteFeeRequest + (*RouteFeeResponse)(nil), // 10: routerrpc.RouteFeeResponse + (*SendToRouteRequest)(nil), // 11: routerrpc.SendToRouteRequest + (*SendToRouteResponse)(nil), // 12: routerrpc.SendToRouteResponse + (*ResetMissionControlRequest)(nil), // 13: routerrpc.ResetMissionControlRequest + (*ResetMissionControlResponse)(nil), // 14: routerrpc.ResetMissionControlResponse + (*QueryMissionControlRequest)(nil), // 15: routerrpc.QueryMissionControlRequest + (*QueryMissionControlResponse)(nil), // 16: routerrpc.QueryMissionControlResponse + (*XImportMissionControlRequest)(nil), // 17: routerrpc.XImportMissionControlRequest + (*XImportMissionControlResponse)(nil), // 18: routerrpc.XImportMissionControlResponse + (*PairHistory)(nil), // 19: routerrpc.PairHistory + (*PairData)(nil), // 20: routerrpc.PairData + (*GetMissionControlConfigRequest)(nil), // 21: routerrpc.GetMissionControlConfigRequest + (*GetMissionControlConfigResponse)(nil), // 22: routerrpc.GetMissionControlConfigResponse + (*SetMissionControlConfigRequest)(nil), // 23: routerrpc.SetMissionControlConfigRequest + (*SetMissionControlConfigResponse)(nil), // 24: routerrpc.SetMissionControlConfigResponse + (*MissionControlConfig)(nil), // 25: routerrpc.MissionControlConfig + (*BimodalParameters)(nil), // 26: routerrpc.BimodalParameters + (*AprioriParameters)(nil), // 27: routerrpc.AprioriParameters + (*QueryProbabilityRequest)(nil), // 28: routerrpc.QueryProbabilityRequest + (*QueryProbabilityResponse)(nil), // 29: routerrpc.QueryProbabilityResponse + (*BuildRouteRequest)(nil), // 30: routerrpc.BuildRouteRequest + (*BuildRouteResponse)(nil), // 31: routerrpc.BuildRouteResponse + (*SubscribeHtlcEventsRequest)(nil), // 32: routerrpc.SubscribeHtlcEventsRequest + (*HtlcEvent)(nil), // 33: routerrpc.HtlcEvent + (*HtlcInfo)(nil), // 34: routerrpc.HtlcInfo + (*ForwardEvent)(nil), // 35: routerrpc.ForwardEvent + (*ForwardFailEvent)(nil), // 36: routerrpc.ForwardFailEvent + (*SettleEvent)(nil), // 37: routerrpc.SettleEvent + (*FinalHtlcEvent)(nil), // 38: routerrpc.FinalHtlcEvent + (*SubscribedEvent)(nil), // 39: routerrpc.SubscribedEvent + (*LinkFailEvent)(nil), // 40: routerrpc.LinkFailEvent + (*PaymentStatus)(nil), // 41: routerrpc.PaymentStatus + (*CircuitKey)(nil), // 42: routerrpc.CircuitKey + (*ForwardHtlcInterceptRequest)(nil), // 43: routerrpc.ForwardHtlcInterceptRequest + (*ForwardHtlcInterceptResponse)(nil), // 44: routerrpc.ForwardHtlcInterceptResponse + (*UpdateChanStatusRequest)(nil), // 45: routerrpc.UpdateChanStatusRequest + (*UpdateChanStatusResponse)(nil), // 46: routerrpc.UpdateChanStatusResponse + nil, // 47: routerrpc.SendPaymentRequest.DestCustomRecordsEntry + nil, // 48: routerrpc.ForwardHtlcInterceptRequest.CustomRecordsEntry + (*lnrpc.RouteHint)(nil), // 49: lnrpc.RouteHint + (lnrpc.FeatureBit)(0), // 50: lnrpc.FeatureBit + (*lnrpc.Route)(nil), // 51: lnrpc.Route + (*lnrpc.Failure)(nil), // 52: lnrpc.Failure + (lnrpc.Failure_FailureCode)(0), // 53: lnrpc.Failure.FailureCode + (*lnrpc.HTLCAttempt)(nil), // 54: lnrpc.HTLCAttempt + (*lnrpc.ChannelPoint)(nil), // 55: lnrpc.ChannelPoint + (*lnrpc.Payment)(nil), // 56: lnrpc.Payment } var file_routerrpc_router_proto_depIdxs = []int32{ - 46, // 0: routerrpc.SendPaymentRequest.route_hints:type_name -> lnrpc.RouteHint - 44, // 1: routerrpc.SendPaymentRequest.dest_custom_records:type_name -> routerrpc.SendPaymentRequest.DestCustomRecordsEntry - 47, // 2: routerrpc.SendPaymentRequest.dest_features:type_name -> lnrpc.FeatureBit - 48, // 3: routerrpc.SendToRouteRequest.route:type_name -> lnrpc.Route - 49, // 4: routerrpc.SendToRouteResponse.failure:type_name -> lnrpc.Failure - 18, // 5: routerrpc.QueryMissionControlResponse.pairs:type_name -> routerrpc.PairHistory - 18, // 6: routerrpc.XImportMissionControlRequest.pairs:type_name -> routerrpc.PairHistory - 19, // 7: routerrpc.PairHistory.history:type_name -> routerrpc.PairData - 24, // 8: routerrpc.GetMissionControlConfigResponse.config:type_name -> routerrpc.MissionControlConfig - 24, // 9: routerrpc.SetMissionControlConfigRequest.config:type_name -> routerrpc.MissionControlConfig - 19, // 10: routerrpc.QueryProbabilityResponse.history:type_name -> routerrpc.PairData - 48, // 11: routerrpc.BuildRouteResponse.route:type_name -> lnrpc.Route - 4, // 12: routerrpc.HtlcEvent.event_type:type_name -> routerrpc.HtlcEvent.EventType - 32, // 13: routerrpc.HtlcEvent.forward_event:type_name -> routerrpc.ForwardEvent - 33, // 14: routerrpc.HtlcEvent.forward_fail_event:type_name -> routerrpc.ForwardFailEvent - 34, // 15: routerrpc.HtlcEvent.settle_event:type_name -> routerrpc.SettleEvent - 37, // 16: routerrpc.HtlcEvent.link_fail_event:type_name -> routerrpc.LinkFailEvent - 36, // 17: routerrpc.HtlcEvent.subscribed_event:type_name -> routerrpc.SubscribedEvent - 35, // 18: routerrpc.HtlcEvent.final_htlc_event:type_name -> routerrpc.FinalHtlcEvent - 31, // 19: routerrpc.ForwardEvent.info:type_name -> routerrpc.HtlcInfo - 31, // 20: routerrpc.LinkFailEvent.info:type_name -> routerrpc.HtlcInfo - 50, // 21: routerrpc.LinkFailEvent.wire_failure:type_name -> lnrpc.Failure.FailureCode - 0, // 22: routerrpc.LinkFailEvent.failure_detail:type_name -> routerrpc.FailureDetail - 1, // 23: routerrpc.PaymentStatus.state:type_name -> routerrpc.PaymentState - 51, // 24: routerrpc.PaymentStatus.htlcs:type_name -> lnrpc.HTLCAttempt - 39, // 25: routerrpc.ForwardHtlcInterceptRequest.incoming_circuit_key:type_name -> routerrpc.CircuitKey - 45, // 26: routerrpc.ForwardHtlcInterceptRequest.custom_records:type_name -> routerrpc.ForwardHtlcInterceptRequest.CustomRecordsEntry - 39, // 27: routerrpc.ForwardHtlcInterceptResponse.incoming_circuit_key:type_name -> routerrpc.CircuitKey - 2, // 28: routerrpc.ForwardHtlcInterceptResponse.action:type_name -> routerrpc.ResolveHoldForwardAction - 50, // 29: routerrpc.ForwardHtlcInterceptResponse.failure_code:type_name -> lnrpc.Failure.FailureCode - 52, // 30: routerrpc.UpdateChanStatusRequest.chan_point:type_name -> lnrpc.ChannelPoint - 3, // 31: routerrpc.UpdateChanStatusRequest.action:type_name -> routerrpc.ChanStatusAction - 5, // 32: routerrpc.Router.SendPaymentV2:input_type -> routerrpc.SendPaymentRequest - 6, // 33: routerrpc.Router.TrackPaymentV2:input_type -> routerrpc.TrackPaymentRequest - 7, // 34: routerrpc.Router.TrackPayments:input_type -> routerrpc.TrackPaymentsRequest - 8, // 35: routerrpc.Router.EstimateRouteFee:input_type -> routerrpc.RouteFeeRequest - 10, // 36: routerrpc.Router.SendToRoute:input_type -> routerrpc.SendToRouteRequest - 10, // 37: routerrpc.Router.SendToRouteV2:input_type -> routerrpc.SendToRouteRequest - 12, // 38: routerrpc.Router.ResetMissionControl:input_type -> routerrpc.ResetMissionControlRequest - 14, // 39: routerrpc.Router.QueryMissionControl:input_type -> routerrpc.QueryMissionControlRequest - 16, // 40: routerrpc.Router.XImportMissionControl:input_type -> routerrpc.XImportMissionControlRequest - 20, // 41: routerrpc.Router.GetMissionControlConfig:input_type -> routerrpc.GetMissionControlConfigRequest - 22, // 42: routerrpc.Router.SetMissionControlConfig:input_type -> routerrpc.SetMissionControlConfigRequest - 25, // 43: routerrpc.Router.QueryProbability:input_type -> routerrpc.QueryProbabilityRequest - 27, // 44: routerrpc.Router.BuildRoute:input_type -> routerrpc.BuildRouteRequest - 29, // 45: routerrpc.Router.SubscribeHtlcEvents:input_type -> routerrpc.SubscribeHtlcEventsRequest - 5, // 46: routerrpc.Router.SendPayment:input_type -> routerrpc.SendPaymentRequest - 6, // 47: routerrpc.Router.TrackPayment:input_type -> routerrpc.TrackPaymentRequest - 41, // 48: routerrpc.Router.HtlcInterceptor:input_type -> routerrpc.ForwardHtlcInterceptResponse - 42, // 49: routerrpc.Router.UpdateChanStatus:input_type -> routerrpc.UpdateChanStatusRequest - 53, // 50: routerrpc.Router.SendPaymentV2:output_type -> lnrpc.Payment - 53, // 51: routerrpc.Router.TrackPaymentV2:output_type -> lnrpc.Payment - 53, // 52: routerrpc.Router.TrackPayments:output_type -> lnrpc.Payment - 9, // 53: routerrpc.Router.EstimateRouteFee:output_type -> routerrpc.RouteFeeResponse - 11, // 54: routerrpc.Router.SendToRoute:output_type -> routerrpc.SendToRouteResponse - 51, // 55: routerrpc.Router.SendToRouteV2:output_type -> lnrpc.HTLCAttempt - 13, // 56: routerrpc.Router.ResetMissionControl:output_type -> routerrpc.ResetMissionControlResponse - 15, // 57: routerrpc.Router.QueryMissionControl:output_type -> routerrpc.QueryMissionControlResponse - 17, // 58: routerrpc.Router.XImportMissionControl:output_type -> routerrpc.XImportMissionControlResponse - 21, // 59: routerrpc.Router.GetMissionControlConfig:output_type -> routerrpc.GetMissionControlConfigResponse - 23, // 60: routerrpc.Router.SetMissionControlConfig:output_type -> routerrpc.SetMissionControlConfigResponse - 26, // 61: routerrpc.Router.QueryProbability:output_type -> routerrpc.QueryProbabilityResponse - 28, // 62: routerrpc.Router.BuildRoute:output_type -> routerrpc.BuildRouteResponse - 30, // 63: routerrpc.Router.SubscribeHtlcEvents:output_type -> routerrpc.HtlcEvent - 38, // 64: routerrpc.Router.SendPayment:output_type -> routerrpc.PaymentStatus - 38, // 65: routerrpc.Router.TrackPayment:output_type -> routerrpc.PaymentStatus - 40, // 66: routerrpc.Router.HtlcInterceptor:output_type -> routerrpc.ForwardHtlcInterceptRequest - 43, // 67: routerrpc.Router.UpdateChanStatus:output_type -> routerrpc.UpdateChanStatusResponse - 50, // [50:68] is the sub-list for method output_type - 32, // [32:50] is the sub-list for method input_type - 32, // [32:32] is the sub-list for extension type_name - 32, // [32:32] is the sub-list for extension extendee - 0, // [0:32] is the sub-list for field type_name + 49, // 0: routerrpc.SendPaymentRequest.route_hints:type_name -> lnrpc.RouteHint + 47, // 1: routerrpc.SendPaymentRequest.dest_custom_records:type_name -> routerrpc.SendPaymentRequest.DestCustomRecordsEntry + 50, // 2: routerrpc.SendPaymentRequest.dest_features:type_name -> lnrpc.FeatureBit + 51, // 3: routerrpc.SendToRouteRequest.route:type_name -> lnrpc.Route + 52, // 4: routerrpc.SendToRouteResponse.failure:type_name -> lnrpc.Failure + 19, // 5: routerrpc.QueryMissionControlResponse.pairs:type_name -> routerrpc.PairHistory + 19, // 6: routerrpc.XImportMissionControlRequest.pairs:type_name -> routerrpc.PairHistory + 20, // 7: routerrpc.PairHistory.history:type_name -> routerrpc.PairData + 25, // 8: routerrpc.GetMissionControlConfigResponse.config:type_name -> routerrpc.MissionControlConfig + 25, // 9: routerrpc.SetMissionControlConfigRequest.config:type_name -> routerrpc.MissionControlConfig + 4, // 10: routerrpc.MissionControlConfig.Model:type_name -> routerrpc.MissionControlConfig.ProbabilityModel + 27, // 11: routerrpc.MissionControlConfig.apriori:type_name -> routerrpc.AprioriParameters + 26, // 12: routerrpc.MissionControlConfig.bimodal:type_name -> routerrpc.BimodalParameters + 20, // 13: routerrpc.QueryProbabilityResponse.history:type_name -> routerrpc.PairData + 51, // 14: routerrpc.BuildRouteResponse.route:type_name -> lnrpc.Route + 5, // 15: routerrpc.HtlcEvent.event_type:type_name -> routerrpc.HtlcEvent.EventType + 35, // 16: routerrpc.HtlcEvent.forward_event:type_name -> routerrpc.ForwardEvent + 36, // 17: routerrpc.HtlcEvent.forward_fail_event:type_name -> routerrpc.ForwardFailEvent + 37, // 18: routerrpc.HtlcEvent.settle_event:type_name -> routerrpc.SettleEvent + 40, // 19: routerrpc.HtlcEvent.link_fail_event:type_name -> routerrpc.LinkFailEvent + 39, // 20: routerrpc.HtlcEvent.subscribed_event:type_name -> routerrpc.SubscribedEvent + 38, // 21: routerrpc.HtlcEvent.final_htlc_event:type_name -> routerrpc.FinalHtlcEvent + 34, // 22: routerrpc.ForwardEvent.info:type_name -> routerrpc.HtlcInfo + 34, // 23: routerrpc.LinkFailEvent.info:type_name -> routerrpc.HtlcInfo + 53, // 24: routerrpc.LinkFailEvent.wire_failure:type_name -> lnrpc.Failure.FailureCode + 0, // 25: routerrpc.LinkFailEvent.failure_detail:type_name -> routerrpc.FailureDetail + 1, // 26: routerrpc.PaymentStatus.state:type_name -> routerrpc.PaymentState + 54, // 27: routerrpc.PaymentStatus.htlcs:type_name -> lnrpc.HTLCAttempt + 42, // 28: routerrpc.ForwardHtlcInterceptRequest.incoming_circuit_key:type_name -> routerrpc.CircuitKey + 48, // 29: routerrpc.ForwardHtlcInterceptRequest.custom_records:type_name -> routerrpc.ForwardHtlcInterceptRequest.CustomRecordsEntry + 42, // 30: routerrpc.ForwardHtlcInterceptResponse.incoming_circuit_key:type_name -> routerrpc.CircuitKey + 2, // 31: routerrpc.ForwardHtlcInterceptResponse.action:type_name -> routerrpc.ResolveHoldForwardAction + 53, // 32: routerrpc.ForwardHtlcInterceptResponse.failure_code:type_name -> lnrpc.Failure.FailureCode + 55, // 33: routerrpc.UpdateChanStatusRequest.chan_point:type_name -> lnrpc.ChannelPoint + 3, // 34: routerrpc.UpdateChanStatusRequest.action:type_name -> routerrpc.ChanStatusAction + 6, // 35: routerrpc.Router.SendPaymentV2:input_type -> routerrpc.SendPaymentRequest + 7, // 36: routerrpc.Router.TrackPaymentV2:input_type -> routerrpc.TrackPaymentRequest + 8, // 37: routerrpc.Router.TrackPayments:input_type -> routerrpc.TrackPaymentsRequest + 9, // 38: routerrpc.Router.EstimateRouteFee:input_type -> routerrpc.RouteFeeRequest + 11, // 39: routerrpc.Router.SendToRoute:input_type -> routerrpc.SendToRouteRequest + 11, // 40: routerrpc.Router.SendToRouteV2:input_type -> routerrpc.SendToRouteRequest + 13, // 41: routerrpc.Router.ResetMissionControl:input_type -> routerrpc.ResetMissionControlRequest + 15, // 42: routerrpc.Router.QueryMissionControl:input_type -> routerrpc.QueryMissionControlRequest + 17, // 43: routerrpc.Router.XImportMissionControl:input_type -> routerrpc.XImportMissionControlRequest + 21, // 44: routerrpc.Router.GetMissionControlConfig:input_type -> routerrpc.GetMissionControlConfigRequest + 23, // 45: routerrpc.Router.SetMissionControlConfig:input_type -> routerrpc.SetMissionControlConfigRequest + 28, // 46: routerrpc.Router.QueryProbability:input_type -> routerrpc.QueryProbabilityRequest + 30, // 47: routerrpc.Router.BuildRoute:input_type -> routerrpc.BuildRouteRequest + 32, // 48: routerrpc.Router.SubscribeHtlcEvents:input_type -> routerrpc.SubscribeHtlcEventsRequest + 6, // 49: routerrpc.Router.SendPayment:input_type -> routerrpc.SendPaymentRequest + 7, // 50: routerrpc.Router.TrackPayment:input_type -> routerrpc.TrackPaymentRequest + 44, // 51: routerrpc.Router.HtlcInterceptor:input_type -> routerrpc.ForwardHtlcInterceptResponse + 45, // 52: routerrpc.Router.UpdateChanStatus:input_type -> routerrpc.UpdateChanStatusRequest + 56, // 53: routerrpc.Router.SendPaymentV2:output_type -> lnrpc.Payment + 56, // 54: routerrpc.Router.TrackPaymentV2:output_type -> lnrpc.Payment + 56, // 55: routerrpc.Router.TrackPayments:output_type -> lnrpc.Payment + 10, // 56: routerrpc.Router.EstimateRouteFee:output_type -> routerrpc.RouteFeeResponse + 12, // 57: routerrpc.Router.SendToRoute:output_type -> routerrpc.SendToRouteResponse + 54, // 58: routerrpc.Router.SendToRouteV2:output_type -> lnrpc.HTLCAttempt + 14, // 59: routerrpc.Router.ResetMissionControl:output_type -> routerrpc.ResetMissionControlResponse + 16, // 60: routerrpc.Router.QueryMissionControl:output_type -> routerrpc.QueryMissionControlResponse + 18, // 61: routerrpc.Router.XImportMissionControl:output_type -> routerrpc.XImportMissionControlResponse + 22, // 62: routerrpc.Router.GetMissionControlConfig:output_type -> routerrpc.GetMissionControlConfigResponse + 24, // 63: routerrpc.Router.SetMissionControlConfig:output_type -> routerrpc.SetMissionControlConfigResponse + 29, // 64: routerrpc.Router.QueryProbability:output_type -> routerrpc.QueryProbabilityResponse + 31, // 65: routerrpc.Router.BuildRoute:output_type -> routerrpc.BuildRouteResponse + 33, // 66: routerrpc.Router.SubscribeHtlcEvents:output_type -> routerrpc.HtlcEvent + 41, // 67: routerrpc.Router.SendPayment:output_type -> routerrpc.PaymentStatus + 41, // 68: routerrpc.Router.TrackPayment:output_type -> routerrpc.PaymentStatus + 43, // 69: routerrpc.Router.HtlcInterceptor:output_type -> routerrpc.ForwardHtlcInterceptRequest + 46, // 70: routerrpc.Router.UpdateChanStatus:output_type -> routerrpc.UpdateChanStatusResponse + 53, // [53:71] is the sub-list for method output_type + 35, // [35:53] is the sub-list for method input_type + 35, // [35:35] is the sub-list for extension type_name + 35, // [35:35] is the sub-list for extension extendee + 0, // [0:35] is the sub-list for field type_name } func init() { file_routerrpc_router_proto_init() } @@ -3941,7 +4243,7 @@ func file_routerrpc_router_proto_init() { } } file_routerrpc_router_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*QueryProbabilityRequest); i { + switch v := v.(*BimodalParameters); i { case 0: return &v.state case 1: @@ -3953,7 +4255,7 @@ func file_routerrpc_router_proto_init() { } } file_routerrpc_router_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*QueryProbabilityResponse); i { + switch v := v.(*AprioriParameters); i { case 0: return &v.state case 1: @@ -3965,7 +4267,7 @@ func file_routerrpc_router_proto_init() { } } file_routerrpc_router_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*BuildRouteRequest); i { + switch v := v.(*QueryProbabilityRequest); i { case 0: return &v.state case 1: @@ -3977,7 +4279,7 @@ func file_routerrpc_router_proto_init() { } } file_routerrpc_router_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*BuildRouteResponse); i { + switch v := v.(*QueryProbabilityResponse); i { case 0: return &v.state case 1: @@ -3989,7 +4291,7 @@ func file_routerrpc_router_proto_init() { } } file_routerrpc_router_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SubscribeHtlcEventsRequest); i { + switch v := v.(*BuildRouteRequest); i { case 0: return &v.state case 1: @@ -4001,7 +4303,7 @@ func file_routerrpc_router_proto_init() { } } file_routerrpc_router_proto_msgTypes[25].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*HtlcEvent); i { + switch v := v.(*BuildRouteResponse); i { case 0: return &v.state case 1: @@ -4013,7 +4315,7 @@ func file_routerrpc_router_proto_init() { } } file_routerrpc_router_proto_msgTypes[26].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*HtlcInfo); i { + switch v := v.(*SubscribeHtlcEventsRequest); i { case 0: return &v.state case 1: @@ -4025,7 +4327,7 @@ func file_routerrpc_router_proto_init() { } } file_routerrpc_router_proto_msgTypes[27].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ForwardEvent); i { + switch v := v.(*HtlcEvent); i { case 0: return &v.state case 1: @@ -4037,7 +4339,7 @@ func file_routerrpc_router_proto_init() { } } file_routerrpc_router_proto_msgTypes[28].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ForwardFailEvent); i { + switch v := v.(*HtlcInfo); i { case 0: return &v.state case 1: @@ -4049,7 +4351,7 @@ func file_routerrpc_router_proto_init() { } } file_routerrpc_router_proto_msgTypes[29].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SettleEvent); i { + switch v := v.(*ForwardEvent); i { case 0: return &v.state case 1: @@ -4061,7 +4363,7 @@ func file_routerrpc_router_proto_init() { } } file_routerrpc_router_proto_msgTypes[30].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*FinalHtlcEvent); i { + switch v := v.(*ForwardFailEvent); i { case 0: return &v.state case 1: @@ -4073,7 +4375,7 @@ func file_routerrpc_router_proto_init() { } } file_routerrpc_router_proto_msgTypes[31].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SubscribedEvent); i { + switch v := v.(*SettleEvent); i { case 0: return &v.state case 1: @@ -4085,7 +4387,7 @@ func file_routerrpc_router_proto_init() { } } file_routerrpc_router_proto_msgTypes[32].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*LinkFailEvent); i { + switch v := v.(*FinalHtlcEvent); i { case 0: return &v.state case 1: @@ -4097,7 +4399,7 @@ func file_routerrpc_router_proto_init() { } } file_routerrpc_router_proto_msgTypes[33].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PaymentStatus); i { + switch v := v.(*SubscribedEvent); i { case 0: return &v.state case 1: @@ -4109,7 +4411,7 @@ func file_routerrpc_router_proto_init() { } } file_routerrpc_router_proto_msgTypes[34].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*CircuitKey); i { + switch v := v.(*LinkFailEvent); i { case 0: return &v.state case 1: @@ -4121,7 +4423,7 @@ func file_routerrpc_router_proto_init() { } } file_routerrpc_router_proto_msgTypes[35].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ForwardHtlcInterceptRequest); i { + switch v := v.(*PaymentStatus); i { case 0: return &v.state case 1: @@ -4133,7 +4435,7 @@ func file_routerrpc_router_proto_init() { } } file_routerrpc_router_proto_msgTypes[36].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ForwardHtlcInterceptResponse); i { + switch v := v.(*CircuitKey); i { case 0: return &v.state case 1: @@ -4145,7 +4447,7 @@ func file_routerrpc_router_proto_init() { } } file_routerrpc_router_proto_msgTypes[37].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*UpdateChanStatusRequest); i { + switch v := v.(*ForwardHtlcInterceptRequest); i { case 0: return &v.state case 1: @@ -4157,6 +4459,30 @@ func file_routerrpc_router_proto_init() { } } file_routerrpc_router_proto_msgTypes[38].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ForwardHtlcInterceptResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_routerrpc_router_proto_msgTypes[39].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UpdateChanStatusRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_routerrpc_router_proto_msgTypes[40].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*UpdateChanStatusResponse); i { case 0: return &v.state @@ -4169,7 +4495,11 @@ func file_routerrpc_router_proto_init() { } } } - file_routerrpc_router_proto_msgTypes[25].OneofWrappers = []interface{}{ + file_routerrpc_router_proto_msgTypes[19].OneofWrappers = []interface{}{ + (*MissionControlConfig_Apriori)(nil), + (*MissionControlConfig_Bimodal)(nil), + } + file_routerrpc_router_proto_msgTypes[27].OneofWrappers = []interface{}{ (*HtlcEvent_ForwardEvent)(nil), (*HtlcEvent_ForwardFailEvent)(nil), (*HtlcEvent_SettleEvent)(nil), @@ -4182,8 +4512,8 @@ func file_routerrpc_router_proto_init() { File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_routerrpc_router_proto_rawDesc, - NumEnums: 5, - NumMessages: 41, + NumEnums: 6, + NumMessages: 43, NumExtensions: 0, NumServices: 1, }, diff --git a/lnrpc/routerrpc/router.proto b/lnrpc/routerrpc/router.proto index 600511fe4..4b8ae441f 100644 --- a/lnrpc/routerrpc/router.proto +++ b/lnrpc/routerrpc/router.proto @@ -467,6 +467,93 @@ message SetMissionControlConfigResponse { } message MissionControlConfig { + /* + Deprecated, use AprioriParameters. The amount of time mission control will + take to restore a penalized node or channel back to 50% success probability, + expressed in seconds. Setting this value to a higher value will penalize + failures for longer, making mission control less likely to route through + nodes and channels that we have previously recorded failures for. + */ + uint64 half_life_seconds = 1 [deprecated = true]; + + /* + Deprecated, use AprioriParameters. The probability of success mission + control should assign to hop in a route where it has no other information + available. Higher values will make mission control more willing to try hops + that we have no information about, lower values will discourage trying these + hops. + */ + float hop_probability = 2 [deprecated = true]; + + /* + Deprecated, use AprioriParameters. The importance that mission control + should place on historical results, expressed as a value in [0;1]. Setting + this value to 1 will ignore all historical payments and just use the hop + probability to assess the probability of success for each hop. A zero value + ignores hop probability completely and relies entirely on historical + results, unless none are available. + */ + float weight = 3 [deprecated = true]; + + /* + The maximum number of payment results that mission control will store. + */ + uint32 maximum_payment_results = 4; + + /* + The minimum time that must have passed since the previously recorded failure + before we raise the failure amount. + */ + uint64 minimum_failure_relax_interval = 5; + + enum ProbabilityModel { + APRIORI = 0; + BIMODAL = 1; + } + + /* + ProbabilityModel defines which probability estimator should be used in + pathfinding. + */ + ProbabilityModel Model = 6; + + /* + EstimatorConfig is populated dependent on the estimator type. + */ + oneof EstimatorConfig { + AprioriParameters apriori = 7; + BimodalParameters bimodal = 8; + } +} + +message BimodalParameters { + /* + NodeWeight defines how strongly other previous forwardings on channels of a + router should be taken into account when computing a channel's probability + to route. The allowed values are in the range [0, 1], where a value of 0 + means that only direct information about a channel is taken into account. + */ + double node_weight = 1; + + /* + ScaleMsat describes the scale over which channels statistically have some + liquidity left. The value determines how quickly the bimodal distribution + drops off from the edges of a channel. A larger value (compared to typical + channel capacities) means that the drop off is slow and that channel + balances are distributed more uniformly. A small value leads to the + assumption of very unbalanced channels. + */ + uint64 scale_msat = 2; + + /* + DecayTime describes the information decay of knowledge about previous + successes and failures in channels. The smaller the decay time, the quicker + we forget about past forwardings. + */ + uint64 decay_time = 3; +} + +message AprioriParameters { /* The amount of time mission control will take to restore a penalized node or channel back to 50% success probability, expressed in seconds. Setting @@ -482,7 +569,7 @@ message MissionControlConfig { control more willing to try hops that we have no information about, lower values will discourage trying these hops. */ - float hop_probability = 2; + double hop_probability = 2; /* The importance that mission control should place on historical results, @@ -492,18 +579,7 @@ message MissionControlConfig { completely and relies entirely on historical results, unless none are available. */ - float weight = 3; - - /* - The maximum number of payment results that mission control will store. - */ - uint32 maximum_payment_results = 4; - - /* - The minimum time that must have passed since the previously recorded failure - before we raise the failure amount. - */ - uint64 minimum_failure_relax_interval = 5; + double weight = 3; } message QueryProbabilityRequest { diff --git a/lnrpc/routerrpc/router.swagger.json b/lnrpc/routerrpc/router.swagger.json index e428c57e6..a841ba73c 100644 --- a/lnrpc/routerrpc/router.swagger.json +++ b/lnrpc/routerrpc/router.swagger.json @@ -593,6 +593,14 @@ ], "default": "IN_FLIGHT" }, + "MissionControlConfigProbabilityModel": { + "type": "string", + "enum": [ + "APRIORI", + "BIMODAL" + ], + "default": "APRIORI" + }, "lnrpcAMPRecord": { "type": "object", "properties": { @@ -1073,6 +1081,46 @@ } } }, + "routerrpcAprioriParameters": { + "type": "object", + "properties": { + "half_life_seconds": { + "type": "string", + "format": "uint64", + "description": "The amount of time mission control will take to restore a penalized node\nor channel back to 50% success probability, expressed in seconds. Setting\nthis value to a higher value will penalize failures for longer, making\nmission control less likely to route through nodes and channels that we\nhave previously recorded failures for." + }, + "hop_probability": { + "type": "number", + "format": "double", + "description": "The probability of success mission control should assign to hop in a route\nwhere it has no other information available. Higher values will make mission\ncontrol more willing to try hops that we have no information about, lower\nvalues will discourage trying these hops." + }, + "weight": { + "type": "number", + "format": "double", + "description": "The importance that mission control should place on historical results,\nexpressed as a value in [0;1]. Setting this value to 1 will ignore all\nhistorical payments and just use the hop probability to assess the\nprobability of success for each hop. A zero value ignores hop probability\ncompletely and relies entirely on historical results, unless none are\navailable." + } + } + }, + "routerrpcBimodalParameters": { + "type": "object", + "properties": { + "node_weight": { + "type": "number", + "format": "double", + "description": "NodeWeight defines how strongly other previous forwardings on channels of a\nrouter should be taken into account when computing a channel's probability\nto route. The allowed values are in the range [0, 1], where a value of 0\nmeans that only direct information about a channel is taken into account." + }, + "scale_msat": { + "type": "string", + "format": "uint64", + "description": "ScaleMsat describes the scale over which channels statistically have some\nliquidity left. The value determines how quickly the bimodal distribution\ndrops off from the edges of a channel. A larger value (compared to typical\nchannel capacities) means that the drop off is slow and that channel\nbalances are distributed more uniformly. A small value leads to the\nassumption of very unbalanced channels." + }, + "decay_time": { + "type": "string", + "format": "uint64", + "description": "DecayTime describes the information decay of knowledge about previous\nsuccesses and failures in channels. The smaller the decay time, the quicker\nwe forget about past forwardings." + } + } + }, "routerrpcBuildRouteRequest": { "type": "object", "properties": { @@ -1400,17 +1448,17 @@ "half_life_seconds": { "type": "string", "format": "uint64", - "description": "The amount of time mission control will take to restore a penalized node\nor channel back to 50% success probability, expressed in seconds. Setting\nthis value to a higher value will penalize failures for longer, making\nmission control less likely to route through nodes and channels that we\nhave previously recorded failures for." + "description": "Deprecated, use AprioriParameters. The amount of time mission control will\ntake to restore a penalized node or channel back to 50% success probability,\nexpressed in seconds. Setting this value to a higher value will penalize\nfailures for longer, making mission control less likely to route through\nnodes and channels that we have previously recorded failures for." }, "hop_probability": { "type": "number", "format": "float", - "description": "The probability of success mission control should assign to hop in a route\nwhere it has no other information available. Higher values will make mission\ncontrol more willing to try hops that we have no information about, lower\nvalues will discourage trying these hops." + "description": "Deprecated, use AprioriParameters. The probability of success mission\ncontrol should assign to hop in a route where it has no other information\navailable. Higher values will make mission control more willing to try hops\nthat we have no information about, lower values will discourage trying these\nhops." }, "weight": { "type": "number", "format": "float", - "description": "The importance that mission control should place on historical results,\nexpressed as a value in [0;1]. Setting this value to 1 will ignore all\nhistorical payments and just use the hop probability to assess the\nprobability of success for each hop. A zero value ignores hop probability\ncompletely and relies entirely on historical results, unless none are\navailable." + "description": "Deprecated, use AprioriParameters. The importance that mission control\nshould place on historical results, expressed as a value in [0;1]. Setting\nthis value to 1 will ignore all historical payments and just use the hop\nprobability to assess the probability of success for each hop. A zero value\nignores hop probability completely and relies entirely on historical\nresults, unless none are available." }, "maximum_payment_results": { "type": "integer", @@ -1421,6 +1469,16 @@ "type": "string", "format": "uint64", "description": "The minimum time that must have passed since the previously recorded failure\nbefore we raise the failure amount." + }, + "Model": { + "$ref": "#/definitions/MissionControlConfigProbabilityModel", + "description": "ProbabilityModel defines which probability estimator should be used in\npathfinding." + }, + "apriori": { + "$ref": "#/definitions/routerrpcAprioriParameters" + }, + "bimodal": { + "$ref": "#/definitions/routerrpcBimodalParameters" } } }, diff --git a/lnrpc/routerrpc/router_server.go b/lnrpc/routerrpc/router_server.go index 8f5cdf1a4..a11a63d6c 100644 --- a/lnrpc/routerrpc/router_server.go +++ b/lnrpc/routerrpc/router_server.go @@ -454,55 +454,137 @@ func (s *Server) GetMissionControlConfig(ctx context.Context, req *GetMissionControlConfigRequest) (*GetMissionControlConfigResponse, error) { + // Query the current mission control config. 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{ + resp := &GetMissionControlConfigResponse{ Config: &MissionControlConfig{ - HalfLifeSeconds: uint64( - eCfg.PenaltyHalfLife.Seconds()), - HopProbability: float32( - eCfg.AprioriHopProbability, - ), - Weight: float32(eCfg.AprioriWeight), MaximumPaymentResults: uint32(cfg.MaxMcHistory), MinimumFailureRelaxInterval: uint64( cfg.MinFailureRelaxInterval.Seconds(), ), }, - }, nil + } + + // We only populate fields based on the current estimator. + switch v := cfg.Estimator.Config().(type) { + case routing.AprioriConfig: + resp.Config.Model = MissionControlConfig_APRIORI + aCfg := AprioriParameters{ + HalfLifeSeconds: uint64(v.PenaltyHalfLife.Seconds()), + HopProbability: v.AprioriHopProbability, + Weight: v.AprioriWeight, + } + + // Populate deprecated fields. + resp.Config.HalfLifeSeconds = uint64( + v.PenaltyHalfLife.Seconds(), + ) + resp.Config.HopProbability = float32(v.AprioriHopProbability) + resp.Config.Weight = float32(v.AprioriWeight) + + resp.Config.EstimatorConfig = &MissionControlConfig_Apriori{ + Apriori: &aCfg, + } + + case routing.BimodalConfig: + resp.Config.Model = MissionControlConfig_BIMODAL + bCfg := BimodalParameters{ + NodeWeight: v.BimodalNodeWeight, + ScaleMsat: uint64(v.BimodalScaleMsat), + DecayTime: uint64(v.BimodalDecayTime.Seconds()), + } + + resp.Config.EstimatorConfig = &MissionControlConfig_Bimodal{ + Bimodal: &bCfg, + } + + default: + return nil, fmt.Errorf("unknown estimator config type %T", v) + } + + return resp, nil } -// SetMissionControlConfig returns our current mission control config. +// SetMissionControlConfig sets parameters in the mission control config. 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{ - Estimator: estimator, + mcCfg := &routing.MissionControlConfig{ MaxMcHistory: int(req.Config.MaximumPaymentResults), MinFailureRelaxInterval: time.Duration( req.Config.MinimumFailureRelaxInterval, ) * time.Second, } + switch req.Config.Model { + case MissionControlConfig_APRIORI: + var aprioriConfig routing.AprioriConfig + + // Determine the apriori config with backward compatibility + // should the api use deprecated fields. + switch v := req.Config.EstimatorConfig.(type) { + case *MissionControlConfig_Bimodal: + return nil, fmt.Errorf("bimodal config " + + "provided, but apriori model requested") + + case *MissionControlConfig_Apriori: + aprioriConfig = routing.AprioriConfig{ + PenaltyHalfLife: time.Duration( + v.Apriori.HalfLifeSeconds, + ) * time.Second, + AprioriHopProbability: v.Apriori.HopProbability, + AprioriWeight: v.Apriori.Weight, + } + + default: + aprioriConfig = routing.AprioriConfig{ + PenaltyHalfLife: time.Duration( + int64(req.Config.HalfLifeSeconds), + ) * time.Second, + AprioriHopProbability: float64( + req.Config.HopProbability, + ), + AprioriWeight: float64(req.Config.Weight), + } + } + + estimator, err := routing.NewAprioriEstimator(aprioriConfig) + if err != nil { + return nil, err + } + mcCfg.Estimator = estimator + + case MissionControlConfig_BIMODAL: + cfg, ok := req.Config. + EstimatorConfig.(*MissionControlConfig_Bimodal) + if !ok { + return nil, fmt.Errorf("bimodal estimator requested " + + "but corresponding config not set") + } + bCfg := cfg.Bimodal + + bimodalConfig := routing.BimodalConfig{ + BimodalDecayTime: time.Duration( + bCfg.DecayTime, + ) * time.Second, + BimodalScaleMsat: lnwire.MilliSatoshi(bCfg.ScaleMsat), + BimodalNodeWeight: bCfg.NodeWeight, + } + + estimator, err := routing.NewBimodalEstimator(bimodalConfig) + if err != nil { + return nil, err + } + mcCfg.Estimator = estimator + + default: + return nil, fmt.Errorf("unknown estimator type %v", + req.Config.Model) + } + return &SetMissionControlConfigResponse{}, - s.cfg.RouterBackend.MissionControl.SetConfig(cfg) + s.cfg.RouterBackend.MissionControl.SetConfig(mcCfg) } // QueryMissionControl exposes the internal mission control state to callers. It diff --git a/lntemp/rpc/router.go b/lntemp/rpc/router.go index a61f7b8dc..fbf44cb18 100644 --- a/lntemp/rpc/router.go +++ b/lntemp/rpc/router.go @@ -86,6 +86,20 @@ func (h *HarnessRPC) SetMissionControlConfig( h.NoError(err, "SetMissionControlConfig") } +// SetMissionControlConfigAssertErr makes a RPC call to the node's +// SetMissionControlConfig and asserts that we error. +func (h *HarnessRPC) SetMissionControlConfigAssertErr( + config *routerrpc.MissionControlConfig) { + + ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout) + defer cancel() + + req := &routerrpc.SetMissionControlConfigRequest{Config: config} + _, err := h.Router.SetMissionControlConfig(ctxt, req) + require.Error(h, err, "expect an error from setting import mission "+ + "control") +} + // ResetMissionControl makes a RPC call to the node's ResetMissionControl and // asserts. func (h *HarnessRPC) ResetMissionControl() { diff --git a/lntest/itest/lnd_routing_test.go b/lntest/itest/lnd_routing_test.go index b94e3b488..53d9f2b97 100644 --- a/lntest/itest/lnd_routing_test.go +++ b/lntest/itest/lnd_routing_test.go @@ -1003,25 +1003,85 @@ func testQueryRoutes(ht *lntemp.HarnessTest) { func testMissionControlCfg(t *testing.T, hn *node.HarnessNode) { t.Helper() - startCfg := hn.RPC.GetMissionControlConfig() + // Getting and setting does not alter the configuration. + startCfg := hn.RPC.GetMissionControlConfig().Config + hn.RPC.SetMissionControlConfig(startCfg) + resp := hn.RPC.GetMissionControlConfig() + require.True(t, proto.Equal(startCfg, resp.Config)) + // We test that setting and getting leads to the same config if all + // fields are set. cfg := &routerrpc.MissionControlConfig{ - HalfLifeSeconds: 8000, - HopProbability: 0.8, - Weight: 0.3, MaximumPaymentResults: 30, MinimumFailureRelaxInterval: 60, + Model: routerrpc. + MissionControlConfig_APRIORI, + EstimatorConfig: &routerrpc.MissionControlConfig_Apriori{ + Apriori: &routerrpc.AprioriParameters{ + HalfLifeSeconds: 8000, + HopProbability: 0.8, + Weight: 0.3, + }, + }, } - hn.RPC.SetMissionControlConfig(cfg) - resp := hn.RPC.GetMissionControlConfig() - require.True(t, proto.Equal(cfg, resp.Config)) + // The deprecated fields should be populated. + cfg.HalfLifeSeconds = 8000 + cfg.HopProbability = 0.8 + cfg.Weight = 0.3 + respCfg := hn.RPC.GetMissionControlConfig().Config + require.True(t, proto.Equal(cfg, respCfg)) - hn.RPC.SetMissionControlConfig(startCfg.Config) + // Switching to another estimator is possible. + cfg = &routerrpc.MissionControlConfig{ + Model: routerrpc. + MissionControlConfig_BIMODAL, + EstimatorConfig: &routerrpc.MissionControlConfig_Bimodal{ + Bimodal: &routerrpc.BimodalParameters{ + ScaleMsat: 1_000, + DecayTime: 500, + }, + }, + } + hn.RPC.SetMissionControlConfig(cfg) + respCfg = hn.RPC.GetMissionControlConfig().Config + require.NotNil(t, respCfg.GetBimodal()) + // If parameters are not set in the request, they will have zero values + // after. + require.Zero(t, respCfg.MaximumPaymentResults) + require.Zero(t, respCfg.MinimumFailureRelaxInterval) + require.Zero(t, respCfg.GetBimodal().NodeWeight) + + // Setting deprecated values will initialize the apriori estimator. + cfg = &routerrpc.MissionControlConfig{ + MaximumPaymentResults: 30, + MinimumFailureRelaxInterval: 60, + HopProbability: 0.8, + Weight: 0.3, + HalfLifeSeconds: 8000, + } + hn.RPC.SetMissionControlConfig(cfg) + respCfg = hn.RPC.GetMissionControlConfig().Config + require.NotNil(t, respCfg.GetApriori()) + + // Setting the wrong config results in an error. + cfg = &routerrpc.MissionControlConfig{ + Model: routerrpc. + MissionControlConfig_APRIORI, + EstimatorConfig: &routerrpc.MissionControlConfig_Bimodal{ + Bimodal: &routerrpc.BimodalParameters{ + ScaleMsat: 1_000, + }, + }, + } + hn.RPC.SetMissionControlConfigAssertErr(cfg) + + // Undo any changes. + hn.RPC.SetMissionControlConfig(startCfg) resp = hn.RPC.GetMissionControlConfig() - require.True(t, proto.Equal(startCfg.Config, resp.Config)) + require.True(t, proto.Equal(startCfg, resp.Config)) } // testMissionControlImport tests import of mission control results from an From d78e756406c93149c8a48bded9de713d61bbc65a Mon Sep 17 00:00:00 2001 From: bitromortac Date: Mon, 23 Jan 2023 11:54:03 +0100 Subject: [PATCH 7/7] routing: add probability benchmarks $ go test -bench=PairProbability ./routing goos: linux goarch: amd64 pkg: github.com/lightningnetwork/lnd/routing cpu: AMD Ryzen 5 3600 6-Core Processor BenchmarkBimodalPairProbability-4 2050206 599.8 ns/op BenchmarkAprioriPairProbability-4 9524289 126.7 ns/op * math.Exp: goos: linux goarch: amd64 pkg: github.com/lightningnetwork/lnd/routing cpu: AMD Ryzen 5 3600 6-Core Processor BenchmarkExp-4 143843622 8.700 ns/op * bimodal benchmark profile: Showing nodes accounting for 2000ms, 76.05% of 2630ms total Dropped 156 nodes (cum <= 13.15ms) Showing top 10 nodes out of 141 flat flat% sum% cum cum% 1110ms 42.21% 42.21% 1110ms 42.21% math.archExp 230ms 8.75% 50.95% 230ms 8.75% runtime.memclrNoHeapPointers 180ms 6.84% 57.79% 930ms 35.36% github.com/lightningnetwork/lnd/routing.(*BimodalEstimator).calculateProbability 110ms 4.18% 61.98% 130ms 4.94% runtime.scanobject 90ms 3.42% 65.40% 90ms 3.42% memeqbody 60ms 2.28% 67.68% 940ms 35.74% github.com/lightningnetwork/lnd/routing.(*BimodalEstimator).directProbability 60ms 2.28% 69.96% 230ms 8.75% github.com/lightningnetwork/lnd/routing.cannotSend 60ms 2.28% 72.24% 60ms 2.28% runtime.madvise 50ms 1.90% 74.14% 480ms 18.25% github.com/lightningnetwork/lnd/routing.(*BimodalEstimator).primitive 50ms 1.90% 76.05% 60ms 2.28% runtime.mapiternext * apriori benchmark profile: Showing nodes accounting for 1570ms, 77.34% of 2030ms total Dropped 138 nodes (cum <= 10.15ms) Showing top 10 nodes out of 151 flat flat% sum% cum cum% 340ms 16.75% 16.75% 340ms 16.75% math.archExp 290ms 14.29% 31.03% 980ms 48.28% github.com/lightningnetwork/lnd/routing.(*AprioriEstimator).getNodeProbability 190ms 9.36% 40.39% 260ms 12.81% runtime.mapiternext 190ms 9.36% 49.75% 190ms 9.36% runtime.memclrNoHeapPointers 160ms 7.88% 57.64% 340ms 16.75% github.com/lightningnetwork/lnd/routing.(*AprioriEstimator).calculateProbability 110ms 5.42% 63.05% 110ms 5.42% aeshashbody 110ms 5.42% 68.47% 130ms 6.40% runtime.scanobject 80ms 3.94% 72.41% 420ms 20.69% github.com/lightningnetwork/lnd/routing.capacityFactor 60ms 2.96% 75.37% 60ms 2.96% runtime.madvise 40ms 1.97% 77.34% 40ms 1.97% runtime.isEmpty (inline) --- routing/probability_estimator_test.go | 95 +++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 routing/probability_estimator_test.go diff --git a/routing/probability_estimator_test.go b/routing/probability_estimator_test.go new file mode 100644 index 000000000..3f6fa3511 --- /dev/null +++ b/routing/probability_estimator_test.go @@ -0,0 +1,95 @@ +package routing + +import ( + "math" + "testing" + "time" + + "github.com/lightningnetwork/lnd/routing/route" +) + +// Create a set of test results. +var resultTime = time.Unix(1674169200, 0) // 20.01.2023 +var now = time.Unix(1674190800, 0) // 6 hours later +var results = NodeResults{ + route.Vertex{byte(0)}: TimedPairResult{ + FailAmt: 200_000_000, + FailTime: resultTime, + SuccessAmt: 100_000_000, + SuccessTime: resultTime, + }, + route.Vertex{byte(1)}: TimedPairResult{ + FailAmt: 200_000_000, + FailTime: resultTime, + SuccessAmt: 100_000_000, + SuccessTime: resultTime, + }, + route.Vertex{byte(2)}: TimedPairResult{ + FailAmt: 200_000_000, + FailTime: resultTime, + SuccessAmt: 100_000_000, + SuccessTime: resultTime, + }, + route.Vertex{byte(3)}: TimedPairResult{ + FailAmt: 200_000_000, + FailTime: resultTime, + SuccessAmt: 100_000_000, + SuccessTime: resultTime, + }, + route.Vertex{byte(4)}: TimedPairResult{ + FailAmt: 200_000_000, + FailTime: resultTime, + SuccessAmt: 100_000_000, + SuccessTime: resultTime, + }, +} + +// probability is a package level variable to prevent the compiler from +// optimizing the benchmark. +var probability float64 + +// BenchmarkBimodalPairProbability benchmarks the probability calculation. +func BenchmarkBimodalPairProbability(b *testing.B) { + estimator := BimodalEstimator{ + BimodalConfig: BimodalConfig{ + BimodalScaleMsat: scale, + BimodalNodeWeight: 0.2, + BimodalDecayTime: 48 * time.Hour, + }, + } + + toNode := route.Vertex{byte(0)} + var p float64 + for i := 0; i < b.N; i++ { + p = estimator.PairProbability(now, results, toNode, + 150_000_000, 300_000) + } + probability = p +} + +// BenchmarkAprioriPairProbability benchmarks the probability calculation. +func BenchmarkAprioriPairProbability(b *testing.B) { + estimator := AprioriEstimator{ + AprioriConfig: AprioriConfig{ + AprioriWeight: 0.2, + PenaltyHalfLife: 48 * time.Hour, + AprioriHopProbability: 0.5, + }, + } + + toNode := route.Vertex{byte(0)} + var p float64 + for i := 0; i < b.N; i++ { + p = estimator.PairProbability(now, results, toNode, + 150_000_000, 300_000) + } + probability = p +} + +// BenchmarkExp benchmarks the exponential function as provided by the math +// library. +func BenchmarkExp(b *testing.B) { + for i := 0; i < b.N; i++ { + math.Exp(0.1) + } +}