Skip to content

Commit 259ceb0

Browse files
committed
Add an option to make the success probability estimation nonlinear
Our "what is the success probability of paying over a channel with the given liquidity bounds" calculation currently assumes the probability of where the liquidity lies in a channel is constant across the entire capacity of a channel. This is obviously a somewhat dubious assumption given most nodes don't materially rebalance and flows within the network often push liquidity "towards the edges". Here we add an option to consider this when scoring channels during routefinding. Specifically, if a new `linear_success_probability` flag is unset on `ProbabilisticScoringFeeParameters`, rather than assuming a PDF of `1` (across the channel's capacity scaled from 0 to 1), we use `(x - 0.5)^2`. This assumes liquidity is likely to be near the edges, which matches experimental results. Further, calculating the CDF (i.e. integral) between arbitrary points on the PDF is trivial, which we do as our main scoring function. While this (finally) introduces floats in our scoring, its not practical to exponentiate using fixed-precision, and benchmarks show this is a performance regression, but not a huge one, more than made up for by the increase in payment success rates.
1 parent df52da7 commit 259ceb0

File tree

3 files changed

+115
-14
lines changed

3 files changed

+115
-14
lines changed

bench/benches/bench.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ criterion_group!(benches,
1313
lightning::routing::router::benches::generate_routes_with_probabilistic_scorer,
1414
lightning::routing::router::benches::generate_mpp_routes_with_probabilistic_scorer,
1515
lightning::routing::router::benches::generate_large_mpp_routes_with_probabilistic_scorer,
16+
lightning::routing::router::benches::generate_routes_with_nonlinear_probabilistic_scorer,
17+
lightning::routing::router::benches::generate_mpp_routes_with_nonlinear_probabilistic_scorer,
18+
lightning::routing::router::benches::generate_large_mpp_routes_with_nonlinear_probabilistic_scorer,
1619
lightning::sign::benches::bench_get_secure_random_bytes,
1720
lightning::ln::channelmanager::bench::bench_sends,
1821
lightning_persister::fs_store::bench::bench_sends,

lightning/src/routing/router.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7324,6 +7324,42 @@ pub mod benches {
73247324
"generate_large_mpp_routes_with_probabilistic_scorer");
73257325
}
73267326

7327+
pub fn generate_routes_with_nonlinear_probabilistic_scorer(bench: &mut Criterion) {
7328+
let logger = TestLogger::new();
7329+
let network_graph = bench_utils::read_network_graph(&logger).unwrap();
7330+
let mut params = ProbabilisticScoringFeeParameters::default();
7331+
params.linear_success_probability = false;
7332+
let scorer = ProbabilisticScorer::new(
7333+
ProbabilisticScoringDecayParameters::default(), &network_graph, &logger);
7334+
generate_routes(bench, &network_graph, scorer, &params,
7335+
channelmanager::provided_invoice_features(&UserConfig::default()), 0,
7336+
"generate_routes_with_nonlinear_probabilistic_scorer");
7337+
}
7338+
7339+
pub fn generate_mpp_routes_with_nonlinear_probabilistic_scorer(bench: &mut Criterion) {
7340+
let logger = TestLogger::new();
7341+
let network_graph = bench_utils::read_network_graph(&logger).unwrap();
7342+
let mut params = ProbabilisticScoringFeeParameters::default();
7343+
params.linear_success_probability = false;
7344+
let scorer = ProbabilisticScorer::new(
7345+
ProbabilisticScoringDecayParameters::default(), &network_graph, &logger);
7346+
generate_routes(bench, &network_graph, scorer, &params,
7347+
channelmanager::provided_invoice_features(&UserConfig::default()), 0,
7348+
"generate_mpp_routes_with_nonlinear_probabilistic_scorer");
7349+
}
7350+
7351+
pub fn generate_large_mpp_routes_with_nonlinear_probabilistic_scorer(bench: &mut Criterion) {
7352+
let logger = TestLogger::new();
7353+
let network_graph = bench_utils::read_network_graph(&logger).unwrap();
7354+
let mut params = ProbabilisticScoringFeeParameters::default();
7355+
params.linear_success_probability = false;
7356+
let scorer = ProbabilisticScorer::new(
7357+
ProbabilisticScoringDecayParameters::default(), &network_graph, &logger);
7358+
generate_routes(bench, &network_graph, scorer, &params,
7359+
channelmanager::provided_invoice_features(&UserConfig::default()), 100_000_000,
7360+
"generate_large_mpp_routes_with_nonlinear_probabilistic_scorer");
7361+
}
7362+
73277363
fn generate_routes<S: ScoreLookUp + ScoreUpdate>(
73287364
bench: &mut Criterion, graph: &NetworkGraph<&TestLogger>, mut scorer: S,
73297365
score_params: &S::ScoreParams, features: Bolt11InvoiceFeatures, starting_amount: u64,

lightning/src/routing/scoring.rs

Lines changed: 76 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -580,6 +580,28 @@ pub struct ProbabilisticScoringFeeParameters {
580580
/// [`base_penalty_msat`]: Self::base_penalty_msat
581581
/// [`anti_probing_penalty_msat`]: Self::anti_probing_penalty_msat
582582
pub considered_impossible_penalty_msat: u64,
583+
584+
/// In order to calculate most of the scores above, we must first convert a lower and upper
585+
/// bound on the available liquidity in a channel into the probability that we think a payment
586+
/// will succeed. That probability is derived from a Probability Density Function for where we
587+
/// think the liquidity in a channel likely lies, given such bounds.
588+
///
589+
/// If this flag is set, that PDF is simply a constant - we assume that the actual available
590+
/// liquidity in a channel is just as likely to be at any point between our lower and upper
591+
/// bounds.
592+
///
593+
/// If this flag is *not* set, that PDF is `(x - 0.5*capacity) ^ 2`. That is, we use an
594+
/// exponential curve which expects the liquidity of a channel to lie "at the edges". This
595+
/// matches experimental results - most routing nodes do not aggressively rebalance their
596+
/// channels and flows in the network are often unbalanced, leaving liquidity usually
597+
/// unavailable.
598+
///
599+
/// Thus, for the "best" routes, leave this flag `false`. However, the flag does imply a number
600+
/// of floating-point multiplications in the hottest routing code, which may lead to routing
601+
/// performance degradation on some machines.
602+
///
603+
/// Default value: false
604+
pub linear_success_probability: bool,
583605
}
584606

585607
impl Default for ProbabilisticScoringFeeParameters {
@@ -594,6 +616,7 @@ impl Default for ProbabilisticScoringFeeParameters {
594616
considered_impossible_penalty_msat: 1_0000_0000_000,
595617
historical_liquidity_penalty_multiplier_msat: 10_000,
596618
historical_liquidity_penalty_amount_multiplier_msat: 64,
619+
linear_success_probability: false,
597620
}
598621
}
599622
}
@@ -647,6 +670,7 @@ impl ProbabilisticScoringFeeParameters {
647670
manual_node_penalties: HashMap::new(),
648671
anti_probing_penalty_msat: 0,
649672
considered_impossible_penalty_msat: 0,
673+
linear_success_probability: true,
650674
}
651675
}
652676
}
@@ -999,6 +1023,12 @@ const PRECISION_LOWER_BOUND_DENOMINATOR: u64 = approx::LOWER_BITS_BOUND;
9991023
const AMOUNT_PENALTY_DIVISOR: u64 = 1 << 20;
10001024
const BASE_AMOUNT_PENALTY_DIVISOR: u64 = 1 << 30;
10011025

1026+
/// Raises three `f64`s to the 3rd power, without `powi` because it requires `std` (dunno why).
1027+
#[inline(always)]
1028+
fn three_f64_pow_3(a: f64, b: f64, c: f64) -> (f64, f64, f64) {
1029+
(a * a * a, b * b * b, c * c * c)
1030+
}
1031+
10021032
/// Given liquidity bounds, calculates the success probability (in the form of a numerator and
10031033
/// denominator) of an HTLC. This is a key assumption in our scoring models.
10041034
///
@@ -1009,14 +1039,46 @@ const BASE_AMOUNT_PENALTY_DIVISOR: u64 = 1 << 30;
10091039
#[inline(always)]
10101040
fn success_probability(
10111041
amount_msat: u64, min_liquidity_msat: u64, max_liquidity_msat: u64, capacity_msat: u64,
1012-
_params: &ProbabilisticScoringFeeParameters, min_zero_implies_no_successes: bool,
1042+
params: &ProbabilisticScoringFeeParameters, min_zero_implies_no_successes: bool,
10131043
) -> (u64, u64) {
10141044
debug_assert!(min_liquidity_msat <= amount_msat);
10151045
debug_assert!(amount_msat < max_liquidity_msat);
10161046
debug_assert!(max_liquidity_msat <= capacity_msat);
10171047

1018-
let numerator = max_liquidity_msat - amount_msat;
1019-
let mut denominator = (max_liquidity_msat - min_liquidity_msat).saturating_add(1);
1048+
let (numerator, mut denominator) =
1049+
if params.linear_success_probability {
1050+
(max_liquidity_msat - amount_msat,
1051+
(max_liquidity_msat - min_liquidity_msat).saturating_add(1))
1052+
} else {
1053+
let capacity = capacity_msat as f64;
1054+
let min = (min_liquidity_msat as f64) / capacity;
1055+
let max = (max_liquidity_msat as f64) / capacity;
1056+
let amount = (amount_msat as f64) / capacity;
1057+
1058+
// Assume the channel has a probability density function of (x - 0.5)^2 for values from
1059+
// 0 to 1 (where 1 is the channel's full capacity). The success probability given some
1060+
// liquidity bounds is thus the integral under the curve from the amount to maximum
1061+
// estimated liquidity, divided by the same integral from the minimum to the maximum
1062+
// estimated liquidity bounds.
1063+
//
1064+
// Because the integral from x to y is simply (y - 0.5)^3 - (x - 0.5)^3, we can
1065+
// calculate the cumulative density function between the min/max bounds trivially. Note
1066+
// that we don't bother to normalize the CDF to total to 1, as it will come out in the
1067+
// division of num / den.
1068+
let (max_pow, amt_pow, min_pow) = three_f64_pow_3(max - 0.5, amount - 0.5, min - 0.5);
1069+
let num = max_pow - amt_pow;
1070+
let den = max_pow - min_pow;
1071+
1072+
// Because our numerator and denominator max out at 0.5^3 we need to multiply them by
1073+
// quite a large factor to get something useful (ideally in the 2^30 range).
1074+
const BILLIONISH: f64 = 1024.0 * 1024.0 * 1024.0;
1075+
let numerator = (num * BILLIONISH) as u64 + 1;
1076+
let denominator = (den * BILLIONISH) as u64 + 1;
1077+
debug_assert!(numerator <= 1 << 30, "Got large numerator ({}) from float {}.", numerator, num);
1078+
debug_assert!(denominator <= 1 << 30, "Got large denominator ({}) from float {}.", denominator, den);
1079+
(numerator, denominator)
1080+
};
1081+
10201082
if min_zero_implies_no_successes && min_liquidity_msat == 0 &&
10211083
denominator < u64::max_value() / 21
10221084
{
@@ -2964,47 +3026,47 @@ mod tests {
29643026
inflight_htlc_msat: 0,
29653027
effective_capacity: EffectiveCapacity::Total { capacity_msat: 950_000_000, htlc_maximum_msat: 1_000 },
29663028
};
2967-
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, &params), 6262);
3029+
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, &params), 11497);
29683030
let usage = ChannelUsage {
29693031
effective_capacity: EffectiveCapacity::Total { capacity_msat: 1_950_000_000, htlc_maximum_msat: 1_000 }, ..usage
29703032
};
2971-
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, &params), 4634);
3033+
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, &params), 7408);
29723034
let usage = ChannelUsage {
29733035
effective_capacity: EffectiveCapacity::Total { capacity_msat: 2_950_000_000, htlc_maximum_msat: 1_000 }, ..usage
29743036
};
2975-
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, &params), 4186);
3037+
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, &params), 6151);
29763038
let usage = ChannelUsage {
29773039
effective_capacity: EffectiveCapacity::Total { capacity_msat: 3_950_000_000, htlc_maximum_msat: 1_000 }, ..usage
29783040
};
2979-
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, &params), 3909);
3041+
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, &params), 5427);
29803042
let usage = ChannelUsage {
29813043
effective_capacity: EffectiveCapacity::Total { capacity_msat: 4_950_000_000, htlc_maximum_msat: 1_000 }, ..usage
29823044
};
2983-
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, &params), 3556);
3045+
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, &params), 4955);
29843046
let usage = ChannelUsage {
29853047
effective_capacity: EffectiveCapacity::Total { capacity_msat: 5_950_000_000, htlc_maximum_msat: 1_000 }, ..usage
29863048
};
2987-
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, &params), 3533);
3049+
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, &params), 4736);
29883050
let usage = ChannelUsage {
29893051
effective_capacity: EffectiveCapacity::Total { capacity_msat: 6_950_000_000, htlc_maximum_msat: 1_000 }, ..usage
29903052
};
2991-
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, &params), 3172);
3053+
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, &params), 4484);
29923054
let usage = ChannelUsage {
29933055
effective_capacity: EffectiveCapacity::Total { capacity_msat: 7_450_000_000, htlc_maximum_msat: 1_000 }, ..usage
29943056
};
2995-
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, &params), 3211);
3057+
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, &params), 4484);
29963058
let usage = ChannelUsage {
29973059
effective_capacity: EffectiveCapacity::Total { capacity_msat: 7_950_000_000, htlc_maximum_msat: 1_000 }, ..usage
29983060
};
2999-
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, &params), 3243);
3061+
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, &params), 4263);
30003062
let usage = ChannelUsage {
30013063
effective_capacity: EffectiveCapacity::Total { capacity_msat: 8_950_000_000, htlc_maximum_msat: 1_000 }, ..usage
30023064
};
3003-
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, &params), 3297);
3065+
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, &params), 4263);
30043066
let usage = ChannelUsage {
30053067
effective_capacity: EffectiveCapacity::Total { capacity_msat: 9_950_000_000, htlc_maximum_msat: 1_000 }, ..usage
30063068
};
3007-
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, &params), 3250);
3069+
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, &params), 4044);
30083070
}
30093071

30103072
#[test]

0 commit comments

Comments
 (0)