Skip to content

Commit 8cacd7d

Browse files
committed
Use a new PDF for our channel liquidity estimation when scoring
Utilizing the results of probes sent once a minute to a random node in the network for a random amount (within a reasonable range), we were able to analyze the accuracy of our resulting success probability estimation with various PDFs. For each candidate PDF (as well as other parameters, to be tuned in the coming commits), we used the `min_zero_implies_no_successes` fudge factor in `success_probability` as well as a total probability multiple fudge factor to get both the historical success model and the a priori model to be neither too optimistic nor too pessimistic (as measured by the relative log-loss between succeeding and failing hops in our sample data). We then compared the resulting log-loss for the historical success model and selected the candidate PDF with the lowest log-loss, skipping a few candidates with similar resulting log-loss but with more extreme constants (such as a power of 11 with a higher `min_zero_implies_no_successes` penalty). This resulted in a PDF of `128 * (1/256 + 9*(x - 0.5)^8)` with a `min_zero_implies_no_successes` probability multiplier of 64/78.
1 parent 42cc4e7 commit 8cacd7d

File tree

1 file changed

+48
-43
lines changed

1 file changed

+48
-43
lines changed

lightning/src/routing/scoring.rs

Lines changed: 48 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1119,10 +1119,12 @@ const PRECISION_LOWER_BOUND_DENOMINATOR: u64 = log_approx::LOWER_BITS_BOUND;
11191119
const AMOUNT_PENALTY_DIVISOR: u64 = 1 << 20;
11201120
const BASE_AMOUNT_PENALTY_DIVISOR: u64 = 1 << 30;
11211121

1122-
/// Raises three `f64`s to the 3rd power, without `powi` because it requires `std` (dunno why).
1122+
/// Raises three `f64`s to the 9th power, without `powi` because it requires `std` (dunno why).
11231123
#[inline(always)]
1124-
fn three_f64_pow_3(a: f64, b: f64, c: f64) -> (f64, f64, f64) {
1125-
(a * a * a, b * b * b, c * c * c)
1124+
fn three_f64_pow_9(a: f64, b: f64, c: f64) -> (f64, f64, f64) {
1125+
let (a2, b2, c2) = (a * a, b * b, c * c);
1126+
let (a4, b4, c4) = (a2 * a2, b2 * b2, c2 * c2);
1127+
(a * a4 * a4, b * b4 * b4, c * c4 * c4)
11261128
}
11271129

11281130
/// Given liquidity bounds, calculates the success probability (in the form of a numerator and
@@ -1155,23 +1157,26 @@ fn success_probability(
11551157
let max = (max_liquidity_msat as f64) / capacity;
11561158
let amount = (total_inflight_amount_msat as f64) / capacity;
11571159

1158-
// Assume the channel has a probability density function of (x - 0.5)^2 for values from
1159-
// 0 to 1 (where 1 is the channel's full capacity). The success probability given some
1160-
// liquidity bounds is thus the integral under the curve from the amount to maximum
1161-
// estimated liquidity, divided by the same integral from the minimum to the maximum
1162-
// estimated liquidity bounds.
1160+
// Assume the channel has a probability density function of
1161+
// `128 * (1/256 + 9*(x - 0.5)^8)` for values from 0 to 1 (where 1 is the channel's
1162+
// full capacity). The success probability given some liquidity bounds is thus the
1163+
// integral under the curve from the amount to maximum estimated liquidity, divided by
1164+
// the same integral from the minimum to the maximum estimated liquidity bounds.
11631165
//
1164-
// Because the integral from x to y is simply (y - 0.5)^3 - (x - 0.5)^3, we can
1165-
// calculate the cumulative density function between the min/max bounds trivially. Note
1166-
// that we don't bother to normalize the CDF to total to 1, as it will come out in the
1167-
// division of num / den.
1168-
let (max_pow, amt_pow, min_pow) = three_f64_pow_3(max - 0.5, amount - 0.5, min - 0.5);
1169-
let num = max_pow - amt_pow;
1170-
let den = max_pow - min_pow;
1171-
1172-
// Because our numerator and denominator max out at 0.5^3 we need to multiply them by
1173-
// quite a large factor to get something useful (ideally in the 2^30 range).
1174-
const BILLIONISH: f64 = 1024.0 * 1024.0 * 1024.0;
1166+
// Because the integral from x to y is simply
1167+
// `128*(1/256 * (y - 0.5) + (y - 0.5)^9) - 128*(1/256 * (x - 0.5) + (x - 0.5)^9), we
1168+
// can calculate the cumulative density function between the min/max bounds trivially.
1169+
// Note that we don't bother to normalize the CDF to total to 1 (using the 128
1170+
// multiple), as it will come out in the division of num / den.
1171+
let (max_norm, amt_norm, min_norm) = (max - 0.5, amount - 0.5, min - 0.5);
1172+
let (max_pow, amt_pow, min_pow) = three_f64_pow_9(max_norm, amt_norm, min_norm);
1173+
let (max_v, amt_v, min_v) = (max_pow + max_norm / 256.0, amt_pow + amt_norm / 256.0, min_pow + min_norm / 256.0);
1174+
let num = max_v - amt_v;
1175+
let den = max_v - min_v;
1176+
1177+
// Because our numerator and denominator max out at 0.0078125 we need to multiply them
1178+
// by quite a large factor to get something useful (ideally in the 2^30 range).
1179+
const BILLIONISH: f64 = 1024.0 * 1024.0 * 1024.0 * 64.0;
11751180
let numerator = (num * BILLIONISH) as u64 + 1;
11761181
let denominator = (den * BILLIONISH) as u64 + 1;
11771182
debug_assert!(numerator <= 1 << 30, "Got large numerator ({}) from float {}.", numerator, num);
@@ -1180,13 +1185,13 @@ fn success_probability(
11801185
};
11811186

11821187
if min_zero_implies_no_successes && min_liquidity_msat == 0 &&
1183-
denominator < u64::max_value() / 21
1188+
denominator < u64::max_value() / 78
11841189
{
1185-
// If we have no knowledge of the channel, scale probability down by ~75%
1190+
// If we have no knowledge of the channel, scale probability down by a multiple of ~82%.
11861191
// Note that we prefer to increase the denominator rather than decrease the numerator as
11871192
// the denominator is more likely to be larger and thus provide greater precision. This is
11881193
// mostly an overoptimization but makes a large difference in tests.
1189-
denominator = denominator * 21 / 16
1194+
denominator = denominator * 78 / 64
11901195
}
11911196

11921197
(numerator, denominator)
@@ -3012,47 +3017,47 @@ mod tests {
30123017
info,
30133018
short_channel_id: 42,
30143019
});
3015-
assert_eq!(scorer.channel_penalty_msat(&candidate, usage, &params), 11497);
3020+
assert_eq!(scorer.channel_penalty_msat(&candidate, usage, &params), 11577);
30163021
let usage = ChannelUsage {
30173022
effective_capacity: EffectiveCapacity::Total { capacity_msat: 1_950_000_000, htlc_maximum_msat: 1_000 }, ..usage
30183023
};
3019-
assert_eq!(scorer.channel_penalty_msat(&candidate, usage, &params), 7408);
3024+
assert_eq!(scorer.channel_penalty_msat(&candidate, usage, &params), 8462);
30203025
let usage = ChannelUsage {
30213026
effective_capacity: EffectiveCapacity::Total { capacity_msat: 2_950_000_000, htlc_maximum_msat: 1_000 }, ..usage
30223027
};
3023-
assert_eq!(scorer.channel_penalty_msat(&candidate, usage, &params), 6151);
3028+
assert_eq!(scorer.channel_penalty_msat(&candidate, usage, &params), 6889);
30243029
let usage = ChannelUsage {
30253030
effective_capacity: EffectiveCapacity::Total { capacity_msat: 3_950_000_000, htlc_maximum_msat: 1_000 }, ..usage
30263031
};
3027-
assert_eq!(scorer.channel_penalty_msat(&candidate, usage, &params), 5427);
3032+
assert_eq!(scorer.channel_penalty_msat(&candidate, usage, &params), 5883);
30283033
let usage = ChannelUsage {
30293034
effective_capacity: EffectiveCapacity::Total { capacity_msat: 4_950_000_000, htlc_maximum_msat: 1_000 }, ..usage
30303035
};
3031-
assert_eq!(scorer.channel_penalty_msat(&candidate, usage, &params), 4955);
3036+
assert_eq!(scorer.channel_penalty_msat(&candidate, usage, &params), 5412);
30323037
let usage = ChannelUsage {
30333038
effective_capacity: EffectiveCapacity::Total { capacity_msat: 5_950_000_000, htlc_maximum_msat: 1_000 }, ..usage
30343039
};
3035-
assert_eq!(scorer.channel_penalty_msat(&candidate, usage, &params), 4736);
3040+
assert_eq!(scorer.channel_penalty_msat(&candidate, usage, &params), 4940);
30363041
let usage = ChannelUsage {
30373042
effective_capacity: EffectiveCapacity::Total { capacity_msat: 6_950_000_000, htlc_maximum_msat: 1_000 }, ..usage
30383043
};
3039-
assert_eq!(scorer.channel_penalty_msat(&candidate, usage, &params), 4484);
3044+
assert_eq!(scorer.channel_penalty_msat(&candidate, usage, &params), 4689);
30403045
let usage = ChannelUsage {
30413046
effective_capacity: EffectiveCapacity::Total { capacity_msat: 7_450_000_000, htlc_maximum_msat: 1_000 }, ..usage
30423047
};
3043-
assert_eq!(scorer.channel_penalty_msat(&candidate, usage, &params), 4484);
3048+
assert_eq!(scorer.channel_penalty_msat(&candidate, usage, &params), 4468);
30443049
let usage = ChannelUsage {
30453050
effective_capacity: EffectiveCapacity::Total { capacity_msat: 7_950_000_000, htlc_maximum_msat: 1_000 }, ..usage
30463051
};
3047-
assert_eq!(scorer.channel_penalty_msat(&candidate, usage, &params), 4263);
3052+
assert_eq!(scorer.channel_penalty_msat(&candidate, usage, &params), 4468);
30483053
let usage = ChannelUsage {
30493054
effective_capacity: EffectiveCapacity::Total { capacity_msat: 8_950_000_000, htlc_maximum_msat: 1_000 }, ..usage
30503055
};
3051-
assert_eq!(scorer.channel_penalty_msat(&candidate, usage, &params), 4263);
3056+
assert_eq!(scorer.channel_penalty_msat(&candidate, usage, &params), 4217);
30523057
let usage = ChannelUsage {
30533058
effective_capacity: EffectiveCapacity::Total { capacity_msat: 9_950_000_000, htlc_maximum_msat: 1_000 }, ..usage
30543059
};
3055-
assert_eq!(scorer.channel_penalty_msat(&candidate, usage, &params), 4044);
3060+
assert_eq!(scorer.channel_penalty_msat(&candidate, usage, &params), 3996);
30563061
}
30573062

30583063
#[test]
@@ -3252,7 +3257,7 @@ mod tests {
32523257
});
32533258

32543259
// With no historical data the normal liquidity penalty calculation is used.
3255-
assert_eq!(scorer.channel_penalty_msat(&candidate, usage, &params), 168);
3260+
assert_eq!(scorer.channel_penalty_msat(&candidate, usage, &params), 135);
32563261
}
32573262
assert_eq!(scorer.historical_estimated_channel_liquidity_probabilities(42, &target),
32583263
None);
@@ -3270,7 +3275,7 @@ mod tests {
32703275
});
32713276

32723277
assert_eq!(scorer.channel_penalty_msat(&candidate, usage, &params), 2048);
3273-
assert_eq!(scorer.channel_penalty_msat(&candidate, usage_1, &params), 249);
3278+
assert_eq!(scorer.channel_penalty_msat(&candidate, usage_1, &params), 220);
32743279
}
32753280
// The "it failed" increment is 32, where the probability should lie several buckets into
32763281
// the first octile.
@@ -3294,7 +3299,7 @@ mod tests {
32943299
short_channel_id: 42,
32953300
});
32963301

3297-
assert_eq!(scorer.channel_penalty_msat(&candidate, usage, &params), 105);
3302+
assert_eq!(scorer.channel_penalty_msat(&candidate, usage, &params), 83);
32983303
}
32993304
// The first points should be decayed just slightly and the last bucket has a new point.
33003305
assert_eq!(scorer.historical_estimated_channel_liquidity_probabilities(42, &target),
@@ -3305,12 +3310,12 @@ mod tests {
33053310
// simply check bounds here.
33063311
let five_hundred_prob =
33073312
scorer.historical_estimated_payment_success_probability(42, &target, 500, &params, false).unwrap();
3308-
assert!(five_hundred_prob > 0.59);
3309-
assert!(five_hundred_prob < 0.60);
3313+
assert!(five_hundred_prob > 0.61, "{}", five_hundred_prob);
3314+
assert!(five_hundred_prob < 0.62, "{}", five_hundred_prob);
33103315
let one_prob =
33113316
scorer.historical_estimated_payment_success_probability(42, &target, 1, &params, false).unwrap();
3312-
assert!(one_prob < 0.85);
3313-
assert!(one_prob > 0.84);
3317+
assert!(one_prob < 0.89, "{}", one_prob);
3318+
assert!(one_prob > 0.88, "{}", one_prob);
33143319

33153320
// Advance the time forward 16 half-lives (which the docs claim will ensure all data is
33163321
// gone), and check that we're back to where we started.
@@ -3324,7 +3329,7 @@ mod tests {
33243329
short_channel_id: 42,
33253330
});
33263331

3327-
assert_eq!(scorer.channel_penalty_msat(&candidate, usage, &params), 168);
3332+
assert_eq!(scorer.channel_penalty_msat(&candidate, usage, &params), 135);
33283333
}
33293334
// Once fully decayed we still have data, but its all-0s. In the future we may remove the
33303335
// data entirely instead.
@@ -3512,8 +3517,8 @@ mod tests {
35123517
short_channel_id: 42,
35133518
});
35143519
// With no historical data the normal liquidity penalty calculation is used, which results
3515-
// in a success probability of ~75%.
3516-
assert_eq!(scorer.channel_penalty_msat(&candidate, usage, &params), 1269);
3520+
// in a success probability of ~82%.
3521+
assert_eq!(scorer.channel_penalty_msat(&candidate, usage, &params), 910);
35173522
assert_eq!(scorer.historical_estimated_channel_liquidity_probabilities(42, &target),
35183523
None);
35193524
assert_eq!(scorer.historical_estimated_payment_success_probability(42, &target, 42, &params, false),

0 commit comments

Comments
 (0)