Skip to content

Commit 3e3aab7

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 4c0abdd commit 3e3aab7

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

584606
impl Default for ProbabilisticScoringFeeParameters {
@@ -593,6 +615,7 @@ impl Default for ProbabilisticScoringFeeParameters {
593615
considered_impossible_penalty_msat: 1_0000_0000_000,
594616
historical_liquidity_penalty_multiplier_msat: 10_000,
595617
historical_liquidity_penalty_amount_multiplier_msat: 64,
618+
linear_success_probability: false,
596619
}
597620
}
598621
}
@@ -646,6 +669,7 @@ impl ProbabilisticScoringFeeParameters {
646669
manual_node_penalties: HashMap::new(),
647670
anti_probing_penalty_msat: 0,
648671
considered_impossible_penalty_msat: 0,
672+
linear_success_probability: true,
649673
}
650674
}
651675
}
@@ -998,6 +1022,12 @@ const PRECISION_LOWER_BOUND_DENOMINATOR: u64 = approx::LOWER_BITS_BOUND;
9981022
const AMOUNT_PENALTY_DIVISOR: u64 = 1 << 20;
9991023
const BASE_AMOUNT_PENALTY_DIVISOR: u64 = 1 << 30;
10001024

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

1017-
let numerator = max_liquidity_msat - amount_msat;
1018-
let mut denominator = (max_liquidity_msat - min_liquidity_msat).saturating_add(1);
1047+
let (numerator, mut denominator) =
1048+
if params.linear_success_probability {
1049+
(max_liquidity_msat - amount_msat,
1050+
(max_liquidity_msat - min_liquidity_msat).saturating_add(1))
1051+
} else {
1052+
let capacity = capacity_msat as f64;
1053+
let min = (min_liquidity_msat as f64) / capacity;
1054+
let max = (max_liquidity_msat as f64) / capacity;
1055+
let amount = (amount_msat as f64) / capacity;
1056+
1057+
// Assume the channel has a probability density function of (x - 0.5)^2 for values from
1058+
// 0 to 1 (where 1 is the channel's full capacity). The success probability given some
1059+
// liquidity bounds is thus the integral under the curve from the amount to maximum
1060+
// estimated liquidity, divided by the same integral from the minimum to the maximum
1061+
// estimated liquidity bounds.
1062+
//
1063+
// Because the integral from x to y is simply (y - 0.5)^3 - (x - 0.5)^3, we can
1064+
// calculate the cumulative density function between the min/max bounds trivially. Note
1065+
// that we don't bother to normalize the CDF to total to 1, as it will come out in the
1066+
// division of num / den.
1067+
let (max_pow, amt_pow, min_pow) = three_f64_pow_3(max - 0.5, amount - 0.5, min - 0.5);
1068+
let num = max_pow - amt_pow;
1069+
let den = max_pow - min_pow;
1070+
1071+
// Because our numerator and denominator max out at 0.5^3 we need to multiply them by
1072+
// quite a large factor to get something useful (ideally in the 2^30 range).
1073+
const BILLIONISH: f64 = 1024.0 * 1024.0 * 1024.0;
1074+
let numerator = (num * BILLIONISH) as u64 + 1;
1075+
let denominator = (den * BILLIONISH) as u64 + 1;
1076+
debug_assert!(numerator <= 1 << 30, "Got large numerator ({}) from float {}.", numerator, num);
1077+
debug_assert!(denominator <= 1 << 30, "Got large denominator ({}) from float {}.", denominator, den);
1078+
(numerator, denominator)
1079+
};
1080+
10191081
if min_zero_implies_no_successes && min_liquidity_msat == 0 &&
10201082
denominator < u64::max_value() / 21
10211083
{
@@ -2963,47 +3025,47 @@ mod tests {
29633025
inflight_htlc_msat: 0,
29643026
effective_capacity: EffectiveCapacity::Total { capacity_msat: 950_000_000, htlc_maximum_msat: 1_000 },
29653027
};
2966-
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, &params), 6262);
3028+
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, &params), 11497);
29673029
let usage = ChannelUsage {
29683030
effective_capacity: EffectiveCapacity::Total { capacity_msat: 1_950_000_000, htlc_maximum_msat: 1_000 }, ..usage
29693031
};
2970-
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, &params), 4634);
3032+
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, &params), 7408);
29713033
let usage = ChannelUsage {
29723034
effective_capacity: EffectiveCapacity::Total { capacity_msat: 2_950_000_000, htlc_maximum_msat: 1_000 }, ..usage
29733035
};
2974-
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, &params), 4186);
3036+
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, &params), 6151);
29753037
let usage = ChannelUsage {
29763038
effective_capacity: EffectiveCapacity::Total { capacity_msat: 3_950_000_000, htlc_maximum_msat: 1_000 }, ..usage
29773039
};
2978-
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, &params), 3909);
3040+
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, &params), 5427);
29793041
let usage = ChannelUsage {
29803042
effective_capacity: EffectiveCapacity::Total { capacity_msat: 4_950_000_000, htlc_maximum_msat: 1_000 }, ..usage
29813043
};
2982-
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, &params), 3556);
3044+
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, &params), 4955);
29833045
let usage = ChannelUsage {
29843046
effective_capacity: EffectiveCapacity::Total { capacity_msat: 5_950_000_000, htlc_maximum_msat: 1_000 }, ..usage
29853047
};
2986-
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, &params), 3533);
3048+
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, &params), 4736);
29873049
let usage = ChannelUsage {
29883050
effective_capacity: EffectiveCapacity::Total { capacity_msat: 6_950_000_000, htlc_maximum_msat: 1_000 }, ..usage
29893051
};
2990-
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, &params), 3172);
3052+
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, &params), 4484);
29913053
let usage = ChannelUsage {
29923054
effective_capacity: EffectiveCapacity::Total { capacity_msat: 7_450_000_000, htlc_maximum_msat: 1_000 }, ..usage
29933055
};
2994-
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, &params), 3211);
3056+
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, &params), 4484);
29953057
let usage = ChannelUsage {
29963058
effective_capacity: EffectiveCapacity::Total { capacity_msat: 7_950_000_000, htlc_maximum_msat: 1_000 }, ..usage
29973059
};
2998-
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, &params), 3243);
3060+
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, &params), 4263);
29993061
let usage = ChannelUsage {
30003062
effective_capacity: EffectiveCapacity::Total { capacity_msat: 8_950_000_000, htlc_maximum_msat: 1_000 }, ..usage
30013063
};
3002-
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, &params), 3297);
3064+
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, &params), 4263);
30033065
let usage = ChannelUsage {
30043066
effective_capacity: EffectiveCapacity::Total { capacity_msat: 9_950_000_000, htlc_maximum_msat: 1_000 }, ..usage
30053067
};
3006-
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, &params), 3250);
3068+
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, &params), 4044);
30073069
}
30083070

30093071
#[test]

0 commit comments

Comments
 (0)