Skip to content

Commit 210c193

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 set on `ProbabilisticScoringFeeParameters`, rather than assuming a PDF of `1` (across the channel's capacity scaled from 0 to 1), we use `(x - 0.5)^6`. This assumes liquidity is very 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 865f819 commit 210c193

File tree

3 files changed

+90
-3
lines changed

3 files changed

+90
-3
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::bench::bench_sends,

lightning/src/routing/router.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7114,6 +7114,38 @@ pub mod benches {
71147114
"generate_large_mpp_routes_with_probabilistic_scorer");
71157115
}
71167116

7117+
pub fn generate_routes_with_nonlinear_probabilistic_scorer(bench: &mut Criterion) {
7118+
let logger = TestLogger::new();
7119+
let network_graph = bench_utils::read_network_graph(&logger).unwrap();
7120+
let mut params = ProbabilisticScoringParameters::default();
7121+
params.linear_success_probability = false;
7122+
let scorer = ProbabilisticScorer::new(params, &network_graph, &logger);
7123+
generate_routes(bench, &network_graph, scorer, InvoiceFeatures::empty(), 0,
7124+
"generate_routes_with_nonlinear_probabilistic_scorer");
7125+
}
7126+
7127+
pub fn generate_mpp_routes_with_nonlinear_probabilistic_scorer(bench: &mut Criterion) {
7128+
let logger = TestLogger::new();
7129+
let network_graph = bench_utils::read_network_graph(&logger).unwrap();
7130+
let mut params = ProbabilisticScoringParameters::default();
7131+
params.linear_success_probability = false;
7132+
let scorer = ProbabilisticScorer::new(params, &network_graph, &logger);
7133+
generate_routes(bench, &network_graph, scorer,
7134+
channelmanager::provided_invoice_features(&UserConfig::default()), 0,
7135+
"generate_mpp_routes_with_nonlinear_probabilistic_scorer");
7136+
}
7137+
7138+
pub fn generate_large_mpp_routes_with_nonlinear_probabilistic_scorer(bench: &mut Criterion) {
7139+
let logger = TestLogger::new();
7140+
let network_graph = bench_utils::read_network_graph(&logger).unwrap();
7141+
let mut params = ProbabilisticScoringParameters::default();
7142+
params.linear_success_probability = false;
7143+
let scorer = ProbabilisticScorer::new(params, &network_graph, &logger);
7144+
generate_routes(bench, &network_graph, scorer,
7145+
channelmanager::provided_invoice_features(&UserConfig::default()), 100_000_000,
7146+
"generate_large_mpp_routes_with_nonlinear_probabilistic_scorer");
7147+
}
7148+
71177149
fn generate_routes<S: Score>(
71187150
bench: &mut Criterion, graph: &NetworkGraph<&TestLogger>, mut scorer: S,
71197151
score_params: &S::ScoreParams, features: Bolt11InvoiceFeatures, starting_amount: u64,

lightning/src/routing/scoring.rs

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -509,6 +509,28 @@ pub struct ProbabilisticScoringFeeParameters {
509509
/// [`base_penalty_msat`]: Self::base_penalty_msat
510510
/// [`anti_probing_penalty_msat`]: Self::anti_probing_penalty_msat
511511
pub considered_impossible_penalty_msat: u64,
512+
513+
/// In order to calculate most of the scores above, we must first convert a lower and upper
514+
/// bound on the available liquidity in a channel into the probability that we think a payment
515+
/// will succeed. That probability is derived from a Probability Density Function for where we
516+
/// think the liquidity in a channel likely lies, given such bounds.
517+
///
518+
/// If this flag is set, that PDF is simply a constant - we assume that the actual available
519+
/// liquidity in a channel is just as likely to be at any point between our lower and upper
520+
/// bounds.
521+
///
522+
/// If this flag is *not* set, that PDF is `(x - 0.5*capacity) ^ 6`. That is, we use an
523+
/// exponential curve which expects the liquidity of a channel to lie "at the edges". This
524+
/// matches experimental results - most routing nodes do not aggressively rebalance their
525+
/// channels and flows in the network are often unbalanced, leaving liquidity usually
526+
/// unavailable.
527+
///
528+
/// Thus, for the "best" routes, leave this flag `false`. However, the flag does imply a number
529+
/// of floating-point multiplications in the hottest routing code, which may lead to routing
530+
/// performance degradation on some machines.
531+
///
532+
/// Default value: false
533+
pub linear_success_probability: bool,
512534
}
513535

514536
impl Default for ProbabilisticScoringFeeParameters {
@@ -523,6 +545,7 @@ impl Default for ProbabilisticScoringFeeParameters {
523545
considered_impossible_penalty_msat: 1_0000_0000_000,
524546
historical_liquidity_penalty_multiplier_msat: 10_000,
525547
historical_liquidity_penalty_amount_multiplier_msat: 64,
548+
linear_success_probability: false,
526549
}
527550
}
528551
}
@@ -576,6 +599,7 @@ impl ProbabilisticScoringFeeParameters {
576599
manual_node_penalties: HashMap::new(),
577600
anti_probing_penalty_msat: 0,
578601
considered_impossible_penalty_msat: 0,
602+
linear_success_probability: true,
579603
}
580604
}
581605
}
@@ -947,14 +971,42 @@ const BASE_AMOUNT_PENALTY_DIVISOR: u64 = 1 << 30;
947971
/// seen an HTLC successfully complete over this channel.
948972
fn success_probability(
949973
min_liquidity_msat: u64, amount_msat: u64, max_liquidity_msat: u64, capacity_msat: u64,
950-
_params: &ProbabilisticScoringFeeParameters, min_zero_implies_no_successes: bool,
974+
params: &ProbabilisticScoringFeeParameters, min_zero_implies_no_successes: bool,
951975
) -> (u64, u64) {
952976
debug_assert!(min_liquidity_msat <= amount_msat);
953977
debug_assert!(amount_msat < max_liquidity_msat);
954978
debug_assert!(max_liquidity_msat <= capacity_msat);
955979

956-
let numerator = max_liquidity_msat - amount_msat;
957-
let mut denominator = (max_liquidity_msat - min_liquidity_msat).saturating_add(1);
980+
let (numerator, mut denominator) =
981+
if params.linear_success_probability {
982+
(max_liquidity_msat - amount_msat,
983+
(max_liquidity_msat - min_liquidity_msat).saturating_add(1))
984+
} else {
985+
let capacity = capacity_msat as f64;
986+
let min = (min_liquidity_msat as f64) / capacity;
987+
let max = (max_liquidity_msat as f64) / capacity;
988+
let amount = (amount_msat as f64) / capacity;
989+
990+
// Assume the channel has a probability density function of (x - 0.5)^6 for values from 0 to 1
991+
// (where 1 is the channel's full capacity). The success probability given some liquidity
992+
// bounds is thus the integral under the curve from the minimum liquidity to the amount,
993+
// divided by the same integral from the minimum to the maximum liquidity bounds.
994+
//
995+
// For (x - 0.5)^7, this means simply subtracting the two bounds mius 0.5 to the 7th power.
996+
let max_pow = (max - 0.5).powi(7);
997+
let num = max_pow - (amount - 0.5).powi(7);
998+
let den = max_pow - (min - 0.5).powi(7);
999+
1000+
// Because our numerator and denominator max out at 2^-6 we need to multiply them by
1001+
// quite a large factor to get something useful (ideally in the 2^30 range).
1002+
const ALMOST_TRILLION: f64 = 1024.0 * 1024.0 * 1024.0 * 64.0;
1003+
let numerator = (num * ALMOST_TRILLION) as u64 + 1;
1004+
let denominator = (den * ALMOST_TRILLION) as u64 + 1;
1005+
debug_assert!(numerator <= 1 << 31, "Got large numerator ({}) from float {}.", numerator, num);
1006+
debug_assert!(denominator <= 1 << 31, "Got large denominator ({}) from float {}.", denominator, den);
1007+
(numerator, denominator)
1008+
};
1009+
9581010
if min_zero_implies_no_successes && min_liquidity_msat == 0 &&
9591011
numerator < u64::max_value() / 3 {
9601012
// If we have no knowledge of the channel, scale probability down by 75%

0 commit comments

Comments
 (0)