From b1770ba8f679f0045ed21762d1e54699c7f402b6 Mon Sep 17 00:00:00 2001 From: Matt Corallo Date: Thu, 21 Nov 2024 22:24:16 +0000 Subject: [PATCH 1/2] Add the ability to fetch a probability from live liquidity bounds We already expose the estimated success probability from the historical liquidity bounds from `historical_estimated_payment_success_probability`, but we don't do that for the live liquidity bounds. Here we add a `live_estimated_payment_success_probability` which exposes the probability result from the live liquidity bounds as well. --- lightning/src/routing/scoring.rs | 34 ++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/lightning/src/routing/scoring.rs b/lightning/src/routing/scoring.rs index 3dfc06944a8..65de723dc82 100644 --- a/lightning/src/routing/scoring.rs +++ b/lightning/src/routing/scoring.rs @@ -956,6 +956,9 @@ impl>, L: Deref> ProbabilisticScorer whe /// with `scid` towards the given `target` node, based on the historical estimated liquidity /// bounds. /// + /// Returns `None` if there is no (or insufficient) historical data for the given channel (or + /// the provided `target` is not a party to the channel). + /// /// These are the same bounds as returned by /// [`Self::historical_estimated_channel_liquidity_probabilities`] (but not those returned by /// [`Self::estimated_channel_liquidity_range`]). @@ -978,6 +981,37 @@ impl>, L: Deref> ProbabilisticScorer whe } None } + + /// Query the probability of payment success sending the given `amount_msat` over the channel + /// with `scid` towards the given `target` node, based on the live estimated liquidity bounds. + /// + /// This will return `Some` for any channel which is present in the [`NetworkGraph`], including + /// if we have no bound information beside the channel's capacity. + pub fn live_estimated_payment_success_probability( + &self, scid: u64, target: &NodeId, amount_msat: u64, params: &ProbabilisticScoringFeeParameters, + ) -> Option { + let graph = self.network_graph.read_only(); + + if let Some(chan) = graph.channels().get(&scid) { + if let Some((directed_info, source)) = chan.as_directed_to(target) { + let capacity_msat = directed_info.effective_capacity().as_msat(); + let dummy_liq = ChannelLiquidity::new(Duration::ZERO); + let liq = self.channel_liquidities.get(&scid) + .unwrap_or(&dummy_liq) + .as_directed(&source, &target, capacity_msat); + let min_liq = liq.min_liquidity_msat(); + let max_liq = liq.max_liquidity_msat(); + if amount_msat <= liq.min_liquidity_msat() { + return Some(1.0); + } else if amount_msat > liq.max_liquidity_msat() { + return Some(0.0); + } + let (num, den) = success_probability(amount_msat, min_liq, max_liq, capacity_msat, ¶ms, false); + return Some(num as f64 / den as f64); + } + } + None + } } impl ChannelLiquidity { From d3efe5cbb75adee9b8fc5a94e7c96d54225d6964 Mon Sep 17 00:00:00 2001 From: Matt Corallo Date: Mon, 25 Nov 2024 19:04:30 +0000 Subject: [PATCH 2/2] Add a fallback-allowed param to historical success prob estimator `historical_estimated_payment_success_probability` exposes the historical success probability estimator publicly, but only allows fetching data from channels where we have sufficient information. In the previous commit, `live_estimated_payment_success_probability` was added to enable querying the live bounds success probability estimator. Sadly, while the historical success probability estimator falls back to the live probability estimator, it does so with a different final parameter to `success_probability`, making `live_estimated_payment_success_probability` not useful for calculating the actual historical model output when we have insufficient data. Instead, here, we add a new parameter to `historical_estimated_payment_success_probability` which determines whether it will return fallback data from the live model instead. --- lightning/src/routing/scoring.rs | 86 ++++++++++++++++++++------------ 1 file changed, 54 insertions(+), 32 deletions(-) diff --git a/lightning/src/routing/scoring.rs b/lightning/src/routing/scoring.rs index 65de723dc82..1bcb7fa7492 100644 --- a/lightning/src/routing/scoring.rs +++ b/lightning/src/routing/scoring.rs @@ -52,7 +52,7 @@ //! [`find_route`]: crate::routing::router::find_route use crate::ln::msgs::DecodeError; -use crate::routing::gossip::{EffectiveCapacity, NetworkGraph, NodeId}; +use crate::routing::gossip::{DirectedChannelInfo, EffectiveCapacity, NetworkGraph, NodeId}; use crate::routing::router::{Path, CandidateRouteHop, PublicHopCandidate}; use crate::routing::log_approx; use crate::util::ser::{Readable, ReadableArgs, Writeable, Writer}; @@ -956,32 +956,67 @@ impl>, L: Deref> ProbabilisticScorer whe /// with `scid` towards the given `target` node, based on the historical estimated liquidity /// bounds. /// - /// Returns `None` if there is no (or insufficient) historical data for the given channel (or - /// the provided `target` is not a party to the channel). + /// Returns `None` if: + /// - the given channel is not in the network graph, the provided `target` is not a party to + /// the channel, or we don't have forwarding parameters for either direction in the channel. + /// - `allow_fallback_estimation` is *not* set and there is no (or insufficient) historical + /// data for the given channel. /// /// These are the same bounds as returned by /// [`Self::historical_estimated_channel_liquidity_probabilities`] (but not those returned by /// [`Self::estimated_channel_liquidity_range`]). pub fn historical_estimated_payment_success_probability( - &self, scid: u64, target: &NodeId, amount_msat: u64, params: &ProbabilisticScoringFeeParameters) - -> Option { + &self, scid: u64, target: &NodeId, amount_msat: u64, params: &ProbabilisticScoringFeeParameters, + allow_fallback_estimation: bool, + ) -> Option { let graph = self.network_graph.read_only(); if let Some(chan) = graph.channels().get(&scid) { - if let Some(liq) = self.channel_liquidities.get(&scid) { - if let Some((directed_info, source)) = chan.as_directed_to(target) { + if let Some((directed_info, source)) = chan.as_directed_to(target) { + if let Some(liq) = self.channel_liquidities.get(&scid) { let capacity_msat = directed_info.effective_capacity().as_msat(); let dir_liq = liq.as_directed(source, target, capacity_msat); - return dir_liq.liquidity_history.calculate_success_probability_times_billion( + let res = dir_liq.liquidity_history.calculate_success_probability_times_billion( ¶ms, amount_msat, capacity_msat ).map(|p| p as f64 / (1024 * 1024 * 1024) as f64); + if res.is_some() { + return res; + } + } + if allow_fallback_estimation { + let amt = amount_msat; + return Some( + self.calc_live_prob(scid, source, target, directed_info, amt, params, true) + ); } } } None } + fn calc_live_prob( + &self, scid: u64, source: &NodeId, target: &NodeId, directed_info: DirectedChannelInfo, + amt: u64, params: &ProbabilisticScoringFeeParameters, + min_zero_penalty: bool, + ) -> f64 { + let capacity_msat = directed_info.effective_capacity().as_msat(); + let dummy_liq = ChannelLiquidity::new(Duration::ZERO); + let liq = self.channel_liquidities.get(&scid) + .unwrap_or(&dummy_liq) + .as_directed(&source, &target, capacity_msat); + let min_liq = liq.min_liquidity_msat(); + let max_liq = liq.max_liquidity_msat(); + if amt <= liq.min_liquidity_msat() { + return 1.0; + } else if amt > liq.max_liquidity_msat() { + return 0.0; + } + let (num, den) = + success_probability(amt, min_liq, max_liq, capacity_msat, ¶ms, min_zero_penalty); + num as f64 / den as f64 + } + /// Query the probability of payment success sending the given `amount_msat` over the channel /// with `scid` towards the given `target` node, based on the live estimated liquidity bounds. /// @@ -994,20 +1029,7 @@ impl>, L: Deref> ProbabilisticScorer whe if let Some(chan) = graph.channels().get(&scid) { if let Some((directed_info, source)) = chan.as_directed_to(target) { - let capacity_msat = directed_info.effective_capacity().as_msat(); - let dummy_liq = ChannelLiquidity::new(Duration::ZERO); - let liq = self.channel_liquidities.get(&scid) - .unwrap_or(&dummy_liq) - .as_directed(&source, &target, capacity_msat); - let min_liq = liq.min_liquidity_msat(); - let max_liq = liq.max_liquidity_msat(); - if amount_msat <= liq.min_liquidity_msat() { - return Some(1.0); - } else if amount_msat > liq.max_liquidity_msat() { - return Some(0.0); - } - let (num, den) = success_probability(amount_msat, min_liq, max_liq, capacity_msat, ¶ms, false); - return Some(num as f64 / den as f64); + return Some(self.calc_live_prob(scid, source, target, directed_info, amount_msat, params, false)); } } None @@ -3234,7 +3256,7 @@ mod tests { } assert_eq!(scorer.historical_estimated_channel_liquidity_probabilities(42, &target), None); - assert_eq!(scorer.historical_estimated_payment_success_probability(42, &target, 42, ¶ms), + assert_eq!(scorer.historical_estimated_payment_success_probability(42, &target, 42, ¶ms, false), None); scorer.payment_path_failed(&payment_path_for_amount(1), 42, Duration::ZERO); @@ -3255,9 +3277,9 @@ mod tests { assert_eq!(scorer.historical_estimated_channel_liquidity_probabilities(42, &target), Some(([32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]))); - assert!(scorer.historical_estimated_payment_success_probability(42, &target, 1, ¶ms) + assert!(scorer.historical_estimated_payment_success_probability(42, &target, 1, ¶ms, false) .unwrap() > 0.35); - assert_eq!(scorer.historical_estimated_payment_success_probability(42, &target, 500, ¶ms), + assert_eq!(scorer.historical_estimated_payment_success_probability(42, &target, 500, ¶ms, false), Some(0.0)); // Even after we tell the scorer we definitely have enough available liquidity, it will @@ -3282,11 +3304,11 @@ mod tests { // The exact success probability is a bit complicated and involves integer rounding, so we // simply check bounds here. let five_hundred_prob = - scorer.historical_estimated_payment_success_probability(42, &target, 500, ¶ms).unwrap(); + scorer.historical_estimated_payment_success_probability(42, &target, 500, ¶ms, false).unwrap(); assert!(five_hundred_prob > 0.59); assert!(five_hundred_prob < 0.60); let one_prob = - scorer.historical_estimated_payment_success_probability(42, &target, 1, ¶ms).unwrap(); + scorer.historical_estimated_payment_success_probability(42, &target, 1, ¶ms, false).unwrap(); assert!(one_prob < 0.85); assert!(one_prob > 0.84); @@ -3308,7 +3330,7 @@ mod tests { // data entirely instead. assert_eq!(scorer.historical_estimated_channel_liquidity_probabilities(42, &target), Some(([0; 32], [0; 32]))); - assert_eq!(scorer.historical_estimated_payment_success_probability(42, &target, 1, ¶ms), None); + assert_eq!(scorer.historical_estimated_payment_success_probability(42, &target, 1, ¶ms, false), None); let usage = ChannelUsage { amount_msat: 100, @@ -3494,7 +3516,7 @@ mod tests { assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 1269); assert_eq!(scorer.historical_estimated_channel_liquidity_probabilities(42, &target), None); - assert_eq!(scorer.historical_estimated_payment_success_probability(42, &target, 42, ¶ms), + assert_eq!(scorer.historical_estimated_payment_success_probability(42, &target, 42, ¶ms, false), None); // Fail to pay once, and then check the buckets and penalty. @@ -3509,14 +3531,14 @@ mod tests { Some(([32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]))); // The success probability estimate itself should be zero. - assert_eq!(scorer.historical_estimated_payment_success_probability(42, &target, amount_msat, ¶ms), + assert_eq!(scorer.historical_estimated_payment_success_probability(42, &target, amount_msat, ¶ms, false), Some(0.0)); // Now test again with the amount in the bottom bucket. amount_msat /= 2; // The new amount is entirely within the only minimum bucket with score, so the probability // we assign is 1/2. - assert_eq!(scorer.historical_estimated_payment_success_probability(42, &target, amount_msat, ¶ms), + assert_eq!(scorer.historical_estimated_payment_success_probability(42, &target, amount_msat, ¶ms, false), Some(0.5)); // ...but once we see a failure, we consider the payment to be substantially less likely, @@ -3526,7 +3548,7 @@ mod tests { assert_eq!(scorer.historical_estimated_channel_liquidity_probabilities(42, &target), Some(([63, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [32, 31, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]))); - assert_eq!(scorer.historical_estimated_payment_success_probability(42, &target, amount_msat, ¶ms), + assert_eq!(scorer.historical_estimated_payment_success_probability(42, &target, amount_msat, ¶ms, false), Some(0.0)); } }