From bc8a318ec2d1580aec5454abab30f7df6d90684c Mon Sep 17 00:00:00 2001 From: Matt Corallo Date: Sat, 23 Nov 2024 17:07:49 +0000 Subject: [PATCH] Add `probing_diversity_penalty_msat` to the scorer parameters When doing background probing, its important to get a diverse view of the network graph to ensure you have as many options when pathfinding as possible. Sadly, the naive probing we currently recommend users do does not accomplish that - using the same success-optimized pathfinder when sending as when probing results in always testing the same (good) path over and over and over again. Instead, here, we add a `probing_diversity_penalty_msat` parameter to the scorer, allowing us to penalize channels for which we already have recent data. It applies a penalty which scales with the square of the inverse time since the channel was last updated, up to one day. --- lightning/src/routing/scoring.rs | 44 +++++++++++++++++++++++++++++--- 1 file changed, 41 insertions(+), 3 deletions(-) diff --git a/lightning/src/routing/scoring.rs b/lightning/src/routing/scoring.rs index 3dfc06944a8..4a53dd6cb91 100644 --- a/lightning/src/routing/scoring.rs +++ b/lightning/src/routing/scoring.rs @@ -475,6 +475,9 @@ where L::Target: Logger { network_graph: G, logger: L, channel_liquidities: HashMap, + /// The last time we were given via a [`ScoreUpdate`] method. This does not imply that we've + /// decayed every liquidity bound up to that time. + last_duration_since_epoch: Duration, } /// Parameters for configuring [`ProbabilisticScorer`]. @@ -637,6 +640,22 @@ pub struct ProbabilisticScoringFeeParameters { /// /// Default value: false pub linear_success_probability: bool, + + /// In order to ensure we have knowledge for as many paths as possible, when probing it makes + /// sense to bias away from channels for which we have very recent data. + /// + /// This value is a penalty that is applied based on the last time that we updated the bounds + /// on the available liquidity in a channel. The specified value is the maximum penalty that + /// will be applied. + /// + /// It obviously does not make sense to assign a non-0 value here unless you are using the + /// pathfinding result for background probing. + /// + /// Specifically, the following penalty is applied + /// `probing_diversity_penalty_msat * max(0, (86400 - current time + last update))^2 / 86400^2` is + /// + /// Default value: 0 + pub probing_diversity_penalty_msat: u64, } impl Default for ProbabilisticScoringFeeParameters { @@ -652,6 +671,7 @@ impl Default for ProbabilisticScoringFeeParameters { historical_liquidity_penalty_multiplier_msat: 10_000, historical_liquidity_penalty_amount_multiplier_msat: 64, linear_success_probability: false, + probing_diversity_penalty_msat: 0, } } } @@ -706,6 +726,7 @@ impl ProbabilisticScoringFeeParameters { anti_probing_penalty_msat: 0, considered_impossible_penalty_msat: 0, linear_success_probability: true, + probing_diversity_penalty_msat: 0, } } } @@ -823,6 +844,7 @@ impl>, L: Deref> ProbabilisticScorer whe network_graph, logger, channel_liquidities: new_hash_map(), + last_duration_since_epoch: Duration::from_secs(0), } } @@ -1141,7 +1163,7 @@ DirectedChannelLiquidity< L, HT, T> { /// Returns a liquidity penalty for routing the given HTLC `amount_msat` through the channel in /// this direction. fn penalty_msat( - &self, amount_msat: u64, inflight_htlc_msat: u64, + &self, amount_msat: u64, inflight_htlc_msat: u64, last_duration_since_epoch: Duration, score_params: &ProbabilisticScoringFeeParameters, ) -> u64 { let total_inflight_amount_msat = amount_msat.saturating_add(inflight_htlc_msat); @@ -1216,6 +1238,13 @@ DirectedChannelLiquidity< L, HT, T> { } } + if score_params.probing_diversity_penalty_msat != 0 { + let time_since_update = last_duration_since_epoch.saturating_sub(*self.last_updated); + let mul = Duration::from_secs(60 * 60 * 24).saturating_sub(time_since_update).as_secs(); + let penalty = score_params.probing_diversity_penalty_msat.saturating_mul(mul * mul); + res = res.saturating_add(penalty / ((60 * 60 * 24) * (60 * 60 * 24))); + } + res } @@ -1365,11 +1394,12 @@ impl>, L: Deref> ScoreLookUp for Probabilistic } let capacity_msat = usage.effective_capacity.as_msat(); + let time = self.last_duration_since_epoch; self.channel_liquidities .get(scid) .unwrap_or(&ChannelLiquidity::new(Duration::ZERO)) .as_directed(&source, &target, capacity_msat) - .penalty_msat(usage.amount_msat, usage.inflight_htlc_msat, score_params) + .penalty_msat(usage.amount_msat, usage.inflight_htlc_msat, time, score_params) .saturating_add(anti_probing_penalty_msat) .saturating_add(base_penalty_msat) } @@ -1415,6 +1445,7 @@ impl>, L: Deref> ScoreUpdate for Probabilistic } if at_failed_channel { break; } } + self.last_duration_since_epoch = duration_since_epoch; } fn payment_path_successful(&mut self, path: &Path, duration_since_epoch: Duration) { @@ -1442,6 +1473,7 @@ impl>, L: Deref> ScoreUpdate for Probabilistic hop.short_channel_id); } } + self.last_duration_since_epoch = duration_since_epoch; } fn probe_failed(&mut self, path: &Path, short_channel_id: u64, duration_since_epoch: Duration) { @@ -1473,6 +1505,7 @@ impl>, L: Deref> ScoreUpdate for Probabilistic liquidity.min_liquidity_offset_msat != 0 || liquidity.max_liquidity_offset_msat != 0 || liquidity.liquidity_history.has_datapoints() }); + self.last_duration_since_epoch = duration_since_epoch; } } @@ -1906,15 +1939,20 @@ ReadableArgs<(ProbabilisticScoringDecayParameters, G, L)> for ProbabilisticScore r: &mut R, args: (ProbabilisticScoringDecayParameters, G, L) ) -> Result { let (decay_params, network_graph, logger) = args; - let mut channel_liquidities = new_hash_map(); + let mut channel_liquidities: HashMap = new_hash_map(); read_tlv_fields!(r, { (0, channel_liquidities, required), }); + let mut last_duration_since_epoch = Duration::from_secs(0); + for (_, liq) in channel_liquidities.iter() { + last_duration_since_epoch = cmp::max(last_duration_since_epoch, liq.last_updated); + } Ok(Self { decay_params, network_graph, logger, channel_liquidities, + last_duration_since_epoch, }) } }