Skip to content

Commit 7543890

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 was added to compensate for the divisor + 1 (which exists to avoid divide-by-zero), making the new math slightly less correct but not by any material amount.
1 parent 6d5c5ba commit 7543890

File tree

1 file changed

+49
-31
lines changed

1 file changed

+49
-31
lines changed

lightning/src/routing/scoring.rs

Lines changed: 49 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -893,7 +893,7 @@ impl<G: Deref<Target = NetworkGraph<L>>, L: Deref, T: Time> ProbabilisticScorerU
893893
/// [`Self::historical_estimated_channel_liquidity_probabilities`] (but not those returned by
894894
/// [`Self::estimated_channel_liquidity_range`]).
895895
pub fn historical_estimated_payment_success_probability(
896-
&self, scid: u64, target: &NodeId, amount_msat: u64)
896+
&self, scid: u64, target: &NodeId, amount_msat: u64, params: &ProbabilisticScoringFeeParameters)
897897
-> Option<f64> {
898898
let graph = self.network_graph.read_only();
899899

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

906906
return dir_liq.liquidity_history.calculate_success_probability_times_billion(
907907
dir_liq.now, *dir_liq.last_updated,
908-
self.decay_params.historical_no_updates_half_life, amount_msat, capacity_msat
908+
self.decay_params.historical_no_updates_half_life, &params, amount_msat,
909+
capacity_msat
909910
).map(|p| p as f64 / (1024 * 1024 * 1024) as f64);
910911
}
911912
}
@@ -997,6 +998,23 @@ const PRECISION_LOWER_BOUND_DENOMINATOR: u64 = approx::LOWER_BITS_BOUND;
997998
const AMOUNT_PENALTY_DIVISOR: u64 = 1 << 20;
998999
const BASE_AMOUNT_PENALTY_DIVISOR: u64 = 1 << 30;
9991000

1001+
/// Given liquidity bounds, calculates the success probability (in the form of a numerator and
1002+
/// denominator) of an HTLC. This is a key assumption in our scoring models.
1003+
///
1004+
/// Must not return a numerator or denominator greater than 2^31 for arguments less than 2^31.
1005+
///
1006+
/// min_zero_implies_no_successes signals that a `min_liquidity_msat` of 0 means we've not
1007+
/// (recently) seen an HTLC successfully complete over this channel.
1008+
#[inline(always)]
1009+
fn success_probability(
1010+
amount_msat: u64, min_liquidity_msat: u64, max_liquidity_msat: u64, _capacity_msat: u64,
1011+
_params: &ProbabilisticScoringFeeParameters, _min_zero_implies_no_successes: bool,
1012+
) -> (u64, u64) {
1013+
let numerator = max_liquidity_msat - amount_msat;
1014+
let denominator = (max_liquidity_msat - min_liquidity_msat).saturating_add(1);
1015+
(numerator, denominator)
1016+
}
1017+
10001018
impl<L: Deref<Target = u64>, BRT: Deref<Target = HistoricalBucketRangeTracker>, T: Time, U: Deref<Target = T>> DirectedChannelLiquidity< L, BRT, T, U> {
10011019
/// Returns a liquidity penalty for routing the given HTLC `amount_msat` through the channel in
10021020
/// this direction.
@@ -1017,9 +1035,9 @@ impl<L: Deref<Target = u64>, BRT: Deref<Target = HistoricalBucketRangeTracker>,
10171035
score_params.liquidity_penalty_amount_multiplier_msat)
10181036
.saturating_add(score_params.considered_impossible_penalty_msat)
10191037
} else {
1020-
let numerator = (max_liquidity_msat - amount_msat).saturating_add(1);
1021-
let denominator = (max_liquidity_msat - min_liquidity_msat).saturating_add(1);
1022-
if amount_msat - min_liquidity_msat < denominator / PRECISION_LOWER_BOUND_DENOMINATOR {
1038+
let (numerator, denominator) = success_probability(amount_msat,
1039+
min_liquidity_msat, max_liquidity_msat, available_capacity, score_params, false);
1040+
if denominator - numerator < denominator / PRECISION_LOWER_BOUND_DENOMINATOR {
10231041
// If the failure probability is < 1.5625% (as 1 - numerator/denominator < 1/64),
10241042
// don't bother trying to use the log approximation as it gets too noisy to be
10251043
// particularly helpful, instead just round down to 0.
@@ -1046,7 +1064,8 @@ impl<L: Deref<Target = u64>, BRT: Deref<Target = HistoricalBucketRangeTracker>,
10461064
score_params.historical_liquidity_penalty_amount_multiplier_msat != 0 {
10471065
if let Some(cumulative_success_prob_times_billion) = self.liquidity_history
10481066
.calculate_success_probability_times_billion(self.now, *self.last_updated,
1049-
self.decay_params.historical_no_updates_half_life, amount_msat, self.capacity_msat)
1067+
self.decay_params.historical_no_updates_half_life, score_params, amount_msat,
1068+
self.capacity_msat)
10501069
{
10511070
let historical_negative_log10_times_2048 = approx::negative_log10_times_2048(cumulative_success_prob_times_billion + 1, 1024 * 1024 * 1024);
10521071
res = res.saturating_add(Self::combined_penalty_msat(amount_msat,
@@ -1056,9 +1075,8 @@ impl<L: Deref<Target = u64>, BRT: Deref<Target = HistoricalBucketRangeTracker>,
10561075
// If we don't have any valid points (or, once decayed, we have less than a full
10571076
// point), redo the non-historical calculation with no liquidity bounds tracked and
10581077
// the historical penalty multipliers.
1059-
let available_capacity = self.available_capacity();
1060-
let numerator = available_capacity.saturating_sub(amount_msat).saturating_add(1);
1061-
let denominator = available_capacity.saturating_add(1);
1078+
let (numerator, denominator) = success_probability(amount_msat, 0,
1079+
available_capacity, available_capacity, score_params, true);
10621080
let negative_log10_times_2048 =
10631081
approx::negative_log10_times_2048(numerator, denominator);
10641082
res = res.saturating_add(Self::combined_penalty_msat(amount_msat, negative_log10_times_2048,
@@ -1851,8 +1869,9 @@ mod bucketed_history {
18511869

18521870
#[inline]
18531871
pub(super) fn calculate_success_probability_times_billion<T: Time>(
1854-
&self, now: T, last_updated: T, half_life: Duration, amount_msat: u64, capacity_msat: u64)
1855-
-> Option<u64> {
1872+
&self, now: T, last_updated: T, half_life: Duration,
1873+
params: &ProbabilisticScoringFeeParameters, amount_msat: u64, capacity_msat: u64
1874+
) -> Option<u64> {
18561875
// If historical penalties are enabled, we try to calculate a probability of success
18571876
// given our historical distribution of min- and max-liquidity bounds in a channel.
18581877
// To do so, we walk the set of historical liquidity bucket (min, max) combinations
@@ -1887,14 +1906,13 @@ mod bucketed_history {
18871906
}
18881907
let max_bucket_end_pos = BUCKET_START_POS[32 - highest_max_bucket_with_points] - 1;
18891908
if payment_pos < max_bucket_end_pos {
1909+
let (numerator, denominator) = success_probability(payment_pos as u64, 0,
1910+
max_bucket_end_pos as u64, POSITION_TICKS as u64 - 1, params, true);
18901911
let bucket_prob_times_billion =
18911912
(self.min_liquidity_offset_history.buckets[0] as u64) * total_max_points
18921913
* 1024 * 1024 * 1024 / total_valid_points_tracked;
18931914
cumulative_success_prob_times_billion += bucket_prob_times_billion *
1894-
((max_bucket_end_pos - payment_pos) as u64) /
1895-
// Add an additional one in the divisor as the payment bucket has been
1896-
// rounded down.
1897-
(max_bucket_end_pos + 1) as u64;
1915+
numerator / denominator;
18981916
}
18991917
}
19001918

@@ -1912,11 +1930,11 @@ mod bucketed_history {
19121930
} else if payment_pos < min_bucket_start_pos {
19131931
cumulative_success_prob_times_billion += bucket_prob_times_billion;
19141932
} else {
1933+
let (numerator, denominator) = success_probability(payment_pos as u64,
1934+
min_bucket_start_pos as u64, max_bucket_end_pos as u64,
1935+
POSITION_TICKS as u64 - 1, params, true);
19151936
cumulative_success_prob_times_billion += bucket_prob_times_billion *
1916-
((max_bucket_end_pos - payment_pos) as u64) /
1917-
// Add an additional one in the divisor as the payment bucket has been
1918-
// rounded down.
1919-
((max_bucket_end_pos - min_bucket_start_pos + 1) as u64);
1937+
numerator / denominator;
19201938
}
19211939
}
19221940
}
@@ -2719,7 +2737,7 @@ mod tests {
27192737
let usage = ChannelUsage { amount_msat: 256, ..usage };
27202738
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, &params), 106);
27212739
let usage = ChannelUsage { amount_msat: 768, ..usage };
2722-
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, &params), 916);
2740+
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, &params), 921);
27232741
let usage = ChannelUsage { amount_msat: 896, ..usage };
27242742
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, &params), u64::max_value());
27252743

@@ -2919,7 +2937,7 @@ mod tests {
29192937
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, &params), 300);
29202938

29212939
SinceEpoch::advance(Duration::from_secs(10));
2922-
assert_eq!(deserialized_scorer.channel_penalty_msat(42, &source, &target, usage, &params), 365);
2940+
assert_eq!(deserialized_scorer.channel_penalty_msat(42, &source, &target, usage, &params), 370);
29232941
}
29242942

29252943
#[test]
@@ -3146,7 +3164,7 @@ mod tests {
31463164
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, &params), 47);
31473165
assert_eq!(scorer.historical_estimated_channel_liquidity_probabilities(42, &target),
31483166
None);
3149-
assert_eq!(scorer.historical_estimated_payment_success_probability(42, &target, 42),
3167+
assert_eq!(scorer.historical_estimated_payment_success_probability(42, &target, 42, &params),
31503168
None);
31513169

31523170
scorer.payment_path_failed(&payment_path_for_amount(1), 42);
@@ -3157,9 +3175,9 @@ mod tests {
31573175
assert_eq!(scorer.historical_estimated_channel_liquidity_probabilities(42, &target),
31583176
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],
31593177
[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])));
3160-
assert!(scorer.historical_estimated_payment_success_probability(42, &target, 1)
3178+
assert!(scorer.historical_estimated_payment_success_probability(42, &target, 1, &params)
31613179
.unwrap() > 0.35);
3162-
assert_eq!(scorer.historical_estimated_payment_success_probability(42, &target, 500),
3180+
assert_eq!(scorer.historical_estimated_payment_success_probability(42, &target, 500, &params),
31633181
Some(0.0));
31643182

31653183
// Even after we tell the scorer we definitely have enough available liquidity, it will
@@ -3174,11 +3192,11 @@ mod tests {
31743192
// The exact success probability is a bit complicated and involves integer rounding, so we
31753193
// simply check bounds here.
31763194
let five_hundred_prob =
3177-
scorer.historical_estimated_payment_success_probability(42, &target, 500).unwrap();
3195+
scorer.historical_estimated_payment_success_probability(42, &target, 500, &params).unwrap();
31783196
assert!(five_hundred_prob > 0.66);
31793197
assert!(five_hundred_prob < 0.68);
31803198
let one_prob =
3181-
scorer.historical_estimated_payment_success_probability(42, &target, 1).unwrap();
3199+
scorer.historical_estimated_payment_success_probability(42, &target, 1, &params).unwrap();
31823200
assert!(one_prob < 1.0);
31833201
assert!(one_prob > 0.95);
31843202

@@ -3190,7 +3208,7 @@ mod tests {
31903208
// data entirely instead.
31913209
assert_eq!(scorer.historical_estimated_channel_liquidity_probabilities(42, &target),
31923210
None);
3193-
assert_eq!(scorer.historical_estimated_payment_success_probability(42, &target, 1), None);
3211+
assert_eq!(scorer.historical_estimated_payment_success_probability(42, &target, 1, &params), None);
31943212

31953213
let mut usage = ChannelUsage {
31963214
amount_msat: 100,
@@ -3354,7 +3372,7 @@ mod tests {
33543372
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, &params), 0);
33553373
assert_eq!(scorer.historical_estimated_channel_liquidity_probabilities(42, &target),
33563374
None);
3357-
assert_eq!(scorer.historical_estimated_payment_success_probability(42, &target, 42),
3375+
assert_eq!(scorer.historical_estimated_payment_success_probability(42, &target, 42, &params),
33583376
None);
33593377

33603378
// Fail to pay once, and then check the buckets and penalty.
@@ -3369,14 +3387,14 @@ mod tests {
33693387
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],
33703388
[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])));
33713389
// The success probability estimate itself should be zero.
3372-
assert_eq!(scorer.historical_estimated_payment_success_probability(42, &target, amount_msat),
3390+
assert_eq!(scorer.historical_estimated_payment_success_probability(42, &target, amount_msat, &params),
33733391
Some(0.0));
33743392

33753393
// Now test again with the amount in the bottom bucket.
33763394
amount_msat /= 2;
33773395
// The new amount is entirely within the only minimum bucket with score, so the probability
33783396
// we assign is 1/2.
3379-
assert_eq!(scorer.historical_estimated_payment_success_probability(42, &target, amount_msat),
3397+
assert_eq!(scorer.historical_estimated_payment_success_probability(42, &target, amount_msat, &params),
33803398
Some(0.5));
33813399

33823400
// ...but once we see a failure, we consider the payment to be substantially less likely,
@@ -3386,7 +3404,7 @@ mod tests {
33863404
assert_eq!(scorer.historical_estimated_channel_liquidity_probabilities(42, &target),
33873405
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],
33883406
[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])));
3389-
assert_eq!(scorer.historical_estimated_payment_success_probability(42, &target, amount_msat),
3407+
assert_eq!(scorer.historical_estimated_payment_success_probability(42, &target, amount_msat, &params),
33903408
Some(0.0));
33913409
}
33923410
}

0 commit comments

Comments
 (0)