Skip to content

Commit c5eabaa

Browse files
committed
Split out success probability calculation to allow for changes
Our "what is the success probability of paying over a channel with the given liquidity bounds" calculation is reused in a few places, and is a key assumption across our main score calculation and the historical bucket score calculations. Here we break it out into a function to make it easier to experiment with different success probability calculations. Note that this drops the numerator +1 in the liquidity scorer, which is slightly less correct but not by any material amount.
1 parent 44e5b5c commit c5eabaa

File tree

1 file changed

+48
-30
lines changed

1 file changed

+48
-30
lines changed

lightning/src/routing/scoring.rs

Lines changed: 48 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -832,7 +832,7 @@ impl<G: Deref<Target = NetworkGraph<L>>, L: Deref, T: Time> ProbabilisticScorerU
832832
/// [`Self::historical_estimated_channel_liquidity_probabilities`] (but not those returned by
833833
/// [`Self::estimated_channel_liquidity_range`]).
834834
pub fn historical_estimated_payment_success_probability(
835-
&self, scid: u64, target: &NodeId, amount_msat: u64)
835+
&self, scid: u64, target: &NodeId, amount_msat: u64, params: &ProbabilisticScoringFeeParameters)
836836
-> Option<f64> {
837837
let graph = self.network_graph.read_only();
838838

@@ -844,7 +844,8 @@ impl<G: Deref<Target = NetworkGraph<L>>, L: Deref, T: Time> ProbabilisticScorerU
844844

845845
return dir_liq.liquidity_history.calculate_success_probability_times_billion(
846846
dir_liq.now, *dir_liq.last_updated,
847-
self.decay_params.historical_no_updates_half_life, amount_msat, capacity_msat
847+
self.decay_params.historical_no_updates_half_life, &params, amount_msat,
848+
capacity_msat
848849
).map(|p| p as f64 / (1024 * 1024 * 1024) as f64);
849850
}
850851
}
@@ -936,6 +937,23 @@ const PRECISION_LOWER_BOUND_DENOMINATOR: u64 = approx::LOWER_BITS_BOUND;
936937
const AMOUNT_PENALTY_DIVISOR: u64 = 1 << 20;
937938
const BASE_AMOUNT_PENALTY_DIVISOR: u64 = 1 << 30;
938939

940+
#[inline(always)]
941+
/// Given liquidity bounds, calculates the success probability (in the form of a numerator and
942+
/// denominator) of an HTLC. This is a key assumption in our scoring models.
943+
///
944+
/// Must not return a numerator or denominator greater than 2^31 for arguments less than 2^31.
945+
946+
/// min_zero_implies_no_successes signals that an `amount_msat` of 0 means we've not (recently)
947+
/// seen an HTLC successfully complete over this channel.
948+
fn success_probability(
949+
min_liquidity_msat: u64, amount_msat: u64, max_liquidity_msat: u64, _capacity_msat: u64,
950+
_params: &ProbabilisticScoringFeeParameters, _min_zero_implies_no_successes: bool,
951+
) -> (u64, u64) {
952+
let numerator = max_liquidity_msat - amount_msat;
953+
let denominator = (max_liquidity_msat - min_liquidity_msat).saturating_add(1);
954+
(numerator, denominator)
955+
}
956+
939957
impl<L: Deref<Target = u64>, BRT: Deref<Target = HistoricalBucketRangeTracker>, T: Time, U: Deref<Target = T>> DirectedChannelLiquidity< L, BRT, T, U> {
940958
/// Returns a liquidity penalty for routing the given HTLC `amount_msat` through the channel in
941959
/// this direction.
@@ -956,9 +974,9 @@ impl<L: Deref<Target = u64>, BRT: Deref<Target = HistoricalBucketRangeTracker>,
956974
score_params.liquidity_penalty_amount_multiplier_msat)
957975
.saturating_add(score_params.considered_impossible_penalty_msat)
958976
} else {
959-
let numerator = (max_liquidity_msat - amount_msat).saturating_add(1);
960-
let denominator = (max_liquidity_msat - min_liquidity_msat).saturating_add(1);
961-
if amount_msat - min_liquidity_msat < denominator / PRECISION_LOWER_BOUND_DENOMINATOR {
977+
let (numerator, denominator) = success_probability(min_liquidity_msat,
978+
amount_msat, max_liquidity_msat, available_capacity, score_params, false);
979+
if denominator - numerator < denominator / PRECISION_LOWER_BOUND_DENOMINATOR {
962980
// If the failure probability is < 1.5625% (as 1 - numerator/denominator < 1/64),
963981
// don't bother trying to use the log approximation as it gets too noisy to be
964982
// particularly helpful, instead just round down to 0.
@@ -985,7 +1003,8 @@ impl<L: Deref<Target = u64>, BRT: Deref<Target = HistoricalBucketRangeTracker>,
9851003
score_params.historical_liquidity_penalty_amount_multiplier_msat != 0 {
9861004
if let Some(cumulative_success_prob_times_billion) = self.liquidity_history
9871005
.calculate_success_probability_times_billion(self.now, *self.last_updated,
988-
self.decay_params.historical_no_updates_half_life, amount_msat, self.capacity_msat)
1006+
self.decay_params.historical_no_updates_half_life, score_params, amount_msat,
1007+
self.capacity_msat)
9891008
{
9901009
let historical_negative_log10_times_2048 = approx::negative_log10_times_2048(cumulative_success_prob_times_billion + 1, 1024 * 1024 * 1024);
9911010
res = res.saturating_add(Self::combined_penalty_msat(amount_msat,
@@ -995,9 +1014,8 @@ impl<L: Deref<Target = u64>, BRT: Deref<Target = HistoricalBucketRangeTracker>,
9951014
// If we don't have any valid points (or, once decayed, we have less than a full
9961015
// point), redo the non-historical calculation with no liquidity bounds tracked and
9971016
// the historical penalty multipliers.
998-
let available_capacity = self.available_capacity();
999-
let numerator = available_capacity.saturating_sub(amount_msat).saturating_add(1);
1000-
let denominator = available_capacity.saturating_add(1);
1017+
let (numerator, denominator) = success_probability(0, amount_msat,
1018+
available_capacity, available_capacity, score_params, true);
10011019
let negative_log10_times_2048 =
10021020
approx::negative_log10_times_2048(numerator, denominator);
10031021
res = res.saturating_add(Self::combined_penalty_msat(amount_msat, negative_log10_times_2048,
@@ -1777,8 +1795,9 @@ mod bucketed_history {
17771795

17781796
#[inline]
17791797
pub(super) fn calculate_success_probability_times_billion<T: Time>(
1780-
&self, now: T, last_updated: T, half_life: Duration, amount_msat: u64, capacity_msat: u64)
1781-
-> Option<u64> {
1798+
&self, now: T, last_updated: T, half_life: Duration,
1799+
params: &ProbabilisticScoringFeeParameters, amount_msat: u64, capacity_msat: u64
1800+
) -> Option<u64> {
17821801
// If historical penalties are enabled, calculate the penalty by walking the set of
17831802
// historical liquidity bucket (min, max) combinations (where min_idx < max_idx) and, for
17841803
// each, calculate the probability of success given our payment amount, then total the
@@ -1814,14 +1833,13 @@ mod bucketed_history {
18141833
}
18151834
let max_bucket_end_pos = BUCKET_START_POS[32 - highest_max_bucket_with_points] - 1;
18161835
if payment_pos < max_bucket_end_pos {
1836+
let (numerator, denominator) = success_probability(0, payment_pos as u64,
1837+
max_bucket_end_pos as u64, MIN_SIZE_BUCKETS as u64 - 1, params, true);
18171838
let bucket_prob_times_billion =
18181839
(self.min_liquidity_offset_history.buckets[0] as u64) * total_max_points
18191840
* 1024 * 1024 * 1024 / total_valid_points_tracked;
18201841
cumulative_success_prob_times_billion += bucket_prob_times_billion *
1821-
((max_bucket_end_pos - payment_pos) as u64) /
1822-
// Add an additional one in the divisor as the payment bucket has been
1823-
// rounded down.
1824-
(max_bucket_end_pos + 1) as u64;
1842+
numerator / denominator;
18251843
}
18261844
}
18271845

@@ -1839,11 +1857,11 @@ mod bucketed_history {
18391857
} else if payment_pos < min_bucket_start_pos {
18401858
cumulative_success_prob_times_billion += bucket_prob_times_billion;
18411859
} else {
1860+
let (numerator, denominator) = success_probability(
1861+
min_bucket_start_pos as u64, payment_pos as u64,
1862+
max_bucket_end_pos as u64, MIN_SIZE_BUCKETS as u64 - 1, params, true);
18421863
cumulative_success_prob_times_billion += bucket_prob_times_billion *
1843-
((max_bucket_end_pos - payment_pos) as u64) /
1844-
// Add an additional one in the divisor as the payment bucket has been
1845-
// rounded down.
1846-
((max_bucket_end_pos - min_bucket_start_pos + 1) as u64);
1864+
numerator / denominator;
18471865
}
18481866
}
18491867
}
@@ -2846,7 +2864,7 @@ mod tests {
28462864
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, &params), 300);
28472865

28482866
SinceEpoch::advance(Duration::from_secs(10));
2849-
assert_eq!(deserialized_scorer.channel_penalty_msat(42, &source, &target, usage, &params), 365);
2867+
assert_eq!(deserialized_scorer.channel_penalty_msat(42, &source, &target, usage, &params), 370);
28502868
}
28512869

28522870
#[test]
@@ -3073,7 +3091,7 @@ mod tests {
30733091
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, &params), 47);
30743092
assert_eq!(scorer.historical_estimated_channel_liquidity_probabilities(42, &target),
30753093
None);
3076-
assert_eq!(scorer.historical_estimated_payment_success_probability(42, &target, 42),
3094+
assert_eq!(scorer.historical_estimated_payment_success_probability(42, &target, 42, &params),
30773095
None);
30783096

30793097
scorer.payment_path_failed(&payment_path_for_amount(1), 42);
@@ -3084,9 +3102,9 @@ mod tests {
30843102
assert_eq!(scorer.historical_estimated_channel_liquidity_probabilities(42, &target),
30853103
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],
30863104
[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])));
3087-
assert!(scorer.historical_estimated_payment_success_probability(42, &target, 1)
3105+
assert!(scorer.historical_estimated_payment_success_probability(42, &target, 1, &params)
30883106
.unwrap() > 0.35);
3089-
assert_eq!(scorer.historical_estimated_payment_success_probability(42, &target, 500),
3107+
assert_eq!(scorer.historical_estimated_payment_success_probability(42, &target, 500, &params),
30903108
Some(0.0));
30913109

30923110
// Even after we tell the scorer we definitely have enough available liquidity, it will
@@ -3101,11 +3119,11 @@ mod tests {
31013119
// The exact success probability is a bit complicated and involves integer rounding, so we
31023120
// simply check bounds here.
31033121
let five_hundred_prob =
3104-
scorer.historical_estimated_payment_success_probability(42, &target, 500).unwrap();
3122+
scorer.historical_estimated_payment_success_probability(42, &target, 500, &params).unwrap();
31053123
assert!(five_hundred_prob > 0.66);
31063124
assert!(five_hundred_prob < 0.68);
31073125
let one_prob =
3108-
scorer.historical_estimated_payment_success_probability(42, &target, 1).unwrap();
3126+
scorer.historical_estimated_payment_success_probability(42, &target, 1, &params).unwrap();
31093127
assert!(one_prob < 1.0);
31103128
assert!(one_prob > 0.95);
31113129

@@ -3117,7 +3135,7 @@ mod tests {
31173135
// data entirely instead.
31183136
assert_eq!(scorer.historical_estimated_channel_liquidity_probabilities(42, &target),
31193137
None);
3120-
assert_eq!(scorer.historical_estimated_payment_success_probability(42, &target, 1), None);
3138+
assert_eq!(scorer.historical_estimated_payment_success_probability(42, &target, 1, &params), None);
31213139

31223140
let mut usage = ChannelUsage {
31233141
amount_msat: 100,
@@ -3281,7 +3299,7 @@ mod tests {
32813299
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, &params), 0);
32823300
assert_eq!(scorer.historical_estimated_channel_liquidity_probabilities(42, &target),
32833301
None);
3284-
assert_eq!(scorer.historical_estimated_payment_success_probability(42, &target, 42),
3302+
assert_eq!(scorer.historical_estimated_payment_success_probability(42, &target, 42, &params),
32853303
None);
32863304

32873305
// Fail to pay once, and then check the buckets and penalty.
@@ -3296,14 +3314,14 @@ mod tests {
32963314
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],
32973315
[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])));
32983316
// The success probability estimate itself should be zero.
3299-
assert_eq!(scorer.historical_estimated_payment_success_probability(42, &target, amount_msat),
3317+
assert_eq!(scorer.historical_estimated_payment_success_probability(42, &target, amount_msat, &params),
33003318
Some(0.0));
33013319

33023320
// Now test again with the amount in the bottom bucket.
33033321
amount_msat /= 2;
33043322
// The new amount is entirely within the only minimum bucket with score, so the probability
33053323
// we assign is 1/2.
3306-
assert_eq!(scorer.historical_estimated_payment_success_probability(42, &target, amount_msat),
3324+
assert_eq!(scorer.historical_estimated_payment_success_probability(42, &target, amount_msat, &params),
33073325
Some(0.5));
33083326

33093327
// ...but once we see a failure, we consider the payment to be substantially less likely,
@@ -3313,7 +3331,7 @@ mod tests {
33133331
assert_eq!(scorer.historical_estimated_channel_liquidity_probabilities(42, &target),
33143332
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],
33153333
[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])));
3316-
assert_eq!(scorer.historical_estimated_payment_success_probability(42, &target, amount_msat),
3334+
assert_eq!(scorer.historical_estimated_payment_success_probability(42, &target, amount_msat, &params),
33173335
Some(0.0));
33183336
}
33193337
}

0 commit comments

Comments
 (0)