Skip to content

Commit e1e8ce0

Browse files
authored
Merge pull request #3420 from TheBlueMatt/2024-11-live-success-prob
Add the ability to fetch a probability from live liquidity bounds
2 parents 7cfcb14 + d3efe5c commit e1e8ce0

File tree

1 file changed

+72
-16
lines changed

1 file changed

+72
-16
lines changed

lightning/src/routing/scoring.rs

+72-16
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252
//! [`find_route`]: crate::routing::router::find_route
5353
5454
use crate::ln::msgs::DecodeError;
55-
use crate::routing::gossip::{EffectiveCapacity, NetworkGraph, NodeId};
55+
use crate::routing::gossip::{DirectedChannelInfo, EffectiveCapacity, NetworkGraph, NodeId};
5656
use crate::routing::router::{Path, CandidateRouteHop, PublicHopCandidate};
5757
use crate::routing::log_approx;
5858
use crate::util::ser::{Readable, ReadableArgs, Writeable, Writer};
@@ -956,28 +956,84 @@ impl<G: Deref<Target = NetworkGraph<L>>, L: Deref> ProbabilisticScorer<G, L> whe
956956
/// with `scid` towards the given `target` node, based on the historical estimated liquidity
957957
/// bounds.
958958
///
959+
/// Returns `None` if:
960+
/// - the given channel is not in the network graph, the provided `target` is not a party to
961+
/// the channel, or we don't have forwarding parameters for either direction in the channel.
962+
/// - `allow_fallback_estimation` is *not* set and there is no (or insufficient) historical
963+
/// data for the given channel.
964+
///
959965
/// These are the same bounds as returned by
960966
/// [`Self::historical_estimated_channel_liquidity_probabilities`] (but not those returned by
961967
/// [`Self::estimated_channel_liquidity_range`]).
962968
pub fn historical_estimated_payment_success_probability(
963-
&self, scid: u64, target: &NodeId, amount_msat: u64, params: &ProbabilisticScoringFeeParameters)
964-
-> Option<f64> {
969+
&self, scid: u64, target: &NodeId, amount_msat: u64, params: &ProbabilisticScoringFeeParameters,
970+
allow_fallback_estimation: bool,
971+
) -> Option<f64> {
965972
let graph = self.network_graph.read_only();
966973

967974
if let Some(chan) = graph.channels().get(&scid) {
968-
if let Some(liq) = self.channel_liquidities.get(&scid) {
969-
if let Some((directed_info, source)) = chan.as_directed_to(target) {
975+
if let Some((directed_info, source)) = chan.as_directed_to(target) {
976+
if let Some(liq) = self.channel_liquidities.get(&scid) {
970977
let capacity_msat = directed_info.effective_capacity().as_msat();
971978
let dir_liq = liq.as_directed(source, target, capacity_msat);
972979

973-
return dir_liq.liquidity_history.calculate_success_probability_times_billion(
980+
let res = dir_liq.liquidity_history.calculate_success_probability_times_billion(
974981
&params, amount_msat, capacity_msat
975982
).map(|p| p as f64 / (1024 * 1024 * 1024) as f64);
983+
if res.is_some() {
984+
return res;
985+
}
986+
}
987+
if allow_fallback_estimation {
988+
let amt = amount_msat;
989+
return Some(
990+
self.calc_live_prob(scid, source, target, directed_info, amt, params, true)
991+
);
976992
}
977993
}
978994
}
979995
None
980996
}
997+
998+
fn calc_live_prob(
999+
&self, scid: u64, source: &NodeId, target: &NodeId, directed_info: DirectedChannelInfo,
1000+
amt: u64, params: &ProbabilisticScoringFeeParameters,
1001+
min_zero_penalty: bool,
1002+
) -> f64 {
1003+
let capacity_msat = directed_info.effective_capacity().as_msat();
1004+
let dummy_liq = ChannelLiquidity::new(Duration::ZERO);
1005+
let liq = self.channel_liquidities.get(&scid)
1006+
.unwrap_or(&dummy_liq)
1007+
.as_directed(&source, &target, capacity_msat);
1008+
let min_liq = liq.min_liquidity_msat();
1009+
let max_liq = liq.max_liquidity_msat();
1010+
if amt <= liq.min_liquidity_msat() {
1011+
return 1.0;
1012+
} else if amt > liq.max_liquidity_msat() {
1013+
return 0.0;
1014+
}
1015+
let (num, den) =
1016+
success_probability(amt, min_liq, max_liq, capacity_msat, &params, min_zero_penalty);
1017+
num as f64 / den as f64
1018+
}
1019+
1020+
/// Query the probability of payment success sending the given `amount_msat` over the channel
1021+
/// with `scid` towards the given `target` node, based on the live estimated liquidity bounds.
1022+
///
1023+
/// This will return `Some` for any channel which is present in the [`NetworkGraph`], including
1024+
/// if we have no bound information beside the channel's capacity.
1025+
pub fn live_estimated_payment_success_probability(
1026+
&self, scid: u64, target: &NodeId, amount_msat: u64, params: &ProbabilisticScoringFeeParameters,
1027+
) -> Option<f64> {
1028+
let graph = self.network_graph.read_only();
1029+
1030+
if let Some(chan) = graph.channels().get(&scid) {
1031+
if let Some((directed_info, source)) = chan.as_directed_to(target) {
1032+
return Some(self.calc_live_prob(scid, source, target, directed_info, amount_msat, params, false));
1033+
}
1034+
}
1035+
None
1036+
}
9811037
}
9821038

9831039
impl ChannelLiquidity {
@@ -3200,7 +3256,7 @@ mod tests {
32003256
}
32013257
assert_eq!(scorer.historical_estimated_channel_liquidity_probabilities(42, &target),
32023258
None);
3203-
assert_eq!(scorer.historical_estimated_payment_success_probability(42, &target, 42, &params),
3259+
assert_eq!(scorer.historical_estimated_payment_success_probability(42, &target, 42, &params, false),
32043260
None);
32053261

32063262
scorer.payment_path_failed(&payment_path_for_amount(1), 42, Duration::ZERO);
@@ -3221,9 +3277,9 @@ mod tests {
32213277
assert_eq!(scorer.historical_estimated_channel_liquidity_probabilities(42, &target),
32223278
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],
32233279
[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])));
3224-
assert!(scorer.historical_estimated_payment_success_probability(42, &target, 1, &params)
3280+
assert!(scorer.historical_estimated_payment_success_probability(42, &target, 1, &params, false)
32253281
.unwrap() > 0.35);
3226-
assert_eq!(scorer.historical_estimated_payment_success_probability(42, &target, 500, &params),
3282+
assert_eq!(scorer.historical_estimated_payment_success_probability(42, &target, 500, &params, false),
32273283
Some(0.0));
32283284

32293285
// Even after we tell the scorer we definitely have enough available liquidity, it will
@@ -3248,11 +3304,11 @@ mod tests {
32483304
// The exact success probability is a bit complicated and involves integer rounding, so we
32493305
// simply check bounds here.
32503306
let five_hundred_prob =
3251-
scorer.historical_estimated_payment_success_probability(42, &target, 500, &params).unwrap();
3307+
scorer.historical_estimated_payment_success_probability(42, &target, 500, &params, false).unwrap();
32523308
assert!(five_hundred_prob > 0.59);
32533309
assert!(five_hundred_prob < 0.60);
32543310
let one_prob =
3255-
scorer.historical_estimated_payment_success_probability(42, &target, 1, &params).unwrap();
3311+
scorer.historical_estimated_payment_success_probability(42, &target, 1, &params, false).unwrap();
32563312
assert!(one_prob < 0.85);
32573313
assert!(one_prob > 0.84);
32583314

@@ -3274,7 +3330,7 @@ mod tests {
32743330
// data entirely instead.
32753331
assert_eq!(scorer.historical_estimated_channel_liquidity_probabilities(42, &target),
32763332
Some(([0; 32], [0; 32])));
3277-
assert_eq!(scorer.historical_estimated_payment_success_probability(42, &target, 1, &params), None);
3333+
assert_eq!(scorer.historical_estimated_payment_success_probability(42, &target, 1, &params, false), None);
32783334

32793335
let usage = ChannelUsage {
32803336
amount_msat: 100,
@@ -3460,7 +3516,7 @@ mod tests {
34603516
assert_eq!(scorer.channel_penalty_msat(&candidate, usage, &params), 1269);
34613517
assert_eq!(scorer.historical_estimated_channel_liquidity_probabilities(42, &target),
34623518
None);
3463-
assert_eq!(scorer.historical_estimated_payment_success_probability(42, &target, 42, &params),
3519+
assert_eq!(scorer.historical_estimated_payment_success_probability(42, &target, 42, &params, false),
34643520
None);
34653521

34663522
// Fail to pay once, and then check the buckets and penalty.
@@ -3475,14 +3531,14 @@ mod tests {
34753531
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],
34763532
[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])));
34773533
// The success probability estimate itself should be zero.
3478-
assert_eq!(scorer.historical_estimated_payment_success_probability(42, &target, amount_msat, &params),
3534+
assert_eq!(scorer.historical_estimated_payment_success_probability(42, &target, amount_msat, &params, false),
34793535
Some(0.0));
34803536

34813537
// Now test again with the amount in the bottom bucket.
34823538
amount_msat /= 2;
34833539
// The new amount is entirely within the only minimum bucket with score, so the probability
34843540
// we assign is 1/2.
3485-
assert_eq!(scorer.historical_estimated_payment_success_probability(42, &target, amount_msat, &params),
3541+
assert_eq!(scorer.historical_estimated_payment_success_probability(42, &target, amount_msat, &params, false),
34863542
Some(0.5));
34873543

34883544
// ...but once we see a failure, we consider the payment to be substantially less likely,
@@ -3492,7 +3548,7 @@ mod tests {
34923548
assert_eq!(scorer.historical_estimated_channel_liquidity_probabilities(42, &target),
34933549
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],
34943550
[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])));
3495-
assert_eq!(scorer.historical_estimated_payment_success_probability(42, &target, amount_msat, &params),
3551+
assert_eq!(scorer.historical_estimated_payment_success_probability(42, &target, amount_msat, &params, false),
34963552
Some(0.0));
34973553
}
34983554
}

0 commit comments

Comments
 (0)