Skip to content

Commit f2ec9e6

Browse files
committed
Linear cost function ProbabilisticScorer option
Without recent knowledge of channel liquidity balances, ProbabilisticScorer tends to give low penalties unless the payment amount is a large portion of a channel's capacity. Provide an option that gives a linear penalty increase as the success probability decreases, which may be useful in such scenarios.
1 parent 5ed2985 commit f2ec9e6

File tree

1 file changed

+85
-22
lines changed

1 file changed

+85
-22
lines changed

lightning/src/routing/scoring.rs

Lines changed: 85 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -506,19 +506,29 @@ pub struct ProbabilisticScorerUsingTime<G: Deref<Target = NetworkGraph>, T: Time
506506
/// Parameters for configuring [`ProbabilisticScorer`].
507507
#[derive(Clone, Copy)]
508508
pub struct ProbabilisticScoringParameters {
509-
/// A multiplier used to determine the amount in msats willing to be paid to avoid routing
510-
/// through a channel, as per multiplying by the negative `log10` of the channel's success
511-
/// probability for a payment.
509+
/// The function calculating the cost of routing an amount through a channel.
512510
///
513-
/// The success probability is determined by the effective channel capacity, the payment amount,
514-
/// and knowledge learned from prior successful and unsuccessful payments. The lower bound of
515-
/// the success probability is 0.01, effectively limiting the penalty to the range
516-
/// `0..=2*liquidity_penalty_multiplier_msat`. The knowledge learned is decayed over time based
517-
/// on [`liquidity_offset_half_life`].
511+
/// The cost is multiplied by [`liquidity_penalty_multiplier_msat`] to determine the channel
512+
/// penalty (i.e., the amount msats willing to be paid to avoid routing through the channel).
513+
/// Penalties are limited to `2 * liquidity_penalty_multiplier_msat`.
518514
///
519-
/// Default value: 10,000 msat
515+
/// The cost is based in part by the knowledge learned from prior successful and unsuccessful
516+
/// payments. This knowledge is decayed over time based on [`liquidity_offset_half_life`].
517+
///
518+
/// Default value: [`ProbabilisticScoringCostFunction::NegativeLogSuccessProbability`]
520519
///
520+
/// [`liquidity_penalty_multiplier_msat`]: Self::liquidity_penalty_multiplier_msat
521521
/// [`liquidity_offset_half_life`]: Self::liquidity_offset_half_life
522+
pub cost_function: ProbabilisticScoringCostFunction,
523+
524+
/// A multiplier used in conjunction with [`cost_function`] to determine the channel penalty.
525+
///
526+
/// The channel penalty is the amount in msats willing to be paid to avoid routing through a
527+
/// channel. It is effectively limited to `2 * liquidity_penalty_multiplier_msat`.
528+
///
529+
/// Default value: 10,000 msat
530+
///
531+
/// [`cost_function`]: Self::cost_function
522532
pub liquidity_penalty_multiplier_msat: u64,
523533

524534
/// The time required to elapse before any knowledge learned about channel liquidity balances is
@@ -537,10 +547,22 @@ pub struct ProbabilisticScoringParameters {
537547
pub liquidity_offset_half_life: Duration,
538548
}
539549

540-
impl_writeable_tlv_based!(ProbabilisticScoringParameters, {
541-
(0, liquidity_penalty_multiplier_msat, required),
542-
(2, liquidity_offset_half_life, required),
543-
});
550+
/// A function calculating the cost of routing an amount through a channel.
551+
///
552+
/// Costs are calculated in terms of a payment `success_probability`, which is determined by the
553+
/// effective channel capacity, the payment amount, and knowledge learned from prior successful and
554+
/// unsuccessful payments. Some cost functions may instead use the `failure_probability`, which is
555+
/// simply `1.0 - success_probability`. The `success_probability` has a lower bound of `0.01`.
556+
#[derive(Clone, Copy)]
557+
pub enum ProbabilisticScoringCostFunction {
558+
/// Uses `-log10(success_probability)`, which slowly increases the cost as `success_probability`
559+
/// decreases before rapidly increasing.
560+
NegativeLogSuccessProbability,
561+
562+
/// Uses `2 * failure_probability`, which increases the cost linearly as `success_probability`
563+
/// decreases.
564+
TwiceFailureProbability,
565+
}
544566

545567
/// Accounting for channel liquidity balance uncertainty.
546568
///
@@ -590,6 +612,7 @@ impl<G: Deref<Target = NetworkGraph>, T: Time> ProbabilisticScorerUsingTime<G, T
590612
impl Default for ProbabilisticScoringParameters {
591613
fn default() -> Self {
592614
Self {
615+
cost_function: ProbabilisticScoringCostFunction::NegativeLogSuccessProbability,
593616
liquidity_penalty_multiplier_msat: 10_000,
594617
liquidity_offset_half_life: Duration::from_secs(3600),
595618
}
@@ -652,7 +675,8 @@ impl<T: Time> ChannelLiquidity<T> {
652675
impl<L: Deref<Target = u64>, T: Time, U: Deref<Target = T>> DirectedChannelLiquidity<L, T, U> {
653676
/// Returns a penalty for routing the given HTLC `amount_msat` through the channel in this
654677
/// direction.
655-
fn penalty_msat(&self, amount_msat: u64, liquidity_penalty_multiplier_msat: u64) -> u64 {
678+
fn penalty_msat(&self, amount_msat: u64, params: &ProbabilisticScoringParameters) -> u64 {
679+
let max_penalty_msat = params.liquidity_penalty_multiplier_msat.saturating_mul(2);
656680
let max_liquidity_msat = self.max_liquidity_msat();
657681
let min_liquidity_msat = core::cmp::min(self.min_liquidity_msat(), max_liquidity_msat);
658682
if amount_msat > max_liquidity_msat {
@@ -662,11 +686,21 @@ impl<L: Deref<Target = u64>, T: Time, U: Deref<Target = T>> DirectedChannelLiqui
662686
} else {
663687
let numerator = max_liquidity_msat + 1 - amount_msat;
664688
let denominator = max_liquidity_msat + 1 - min_liquidity_msat;
665-
approx::negative_log10_times_1024(numerator, denominator)
666-
.saturating_mul(liquidity_penalty_multiplier_msat) / 1024
689+
match params.cost_function {
690+
ProbabilisticScoringCostFunction::NegativeLogSuccessProbability => {
691+
approx::negative_log10_times_1024(numerator, denominator)
692+
.saturating_mul(params.liquidity_penalty_multiplier_msat) / 1024
693+
},
694+
ProbabilisticScoringCostFunction::TwiceFailureProbability => {
695+
// Avoid floating-point operations by multiplying the coefficient through:
696+
// 2 * liquidity_penalty_multiplier_msat * (1 - success_probability)
697+
let coefficient = max_penalty_msat;
698+
coefficient.saturating_sub(coefficient.saturating_mul(numerator) / denominator)
699+
},
700+
}
667701
}
668702
// Upper bound the penalty to ensure some channel is selected.
669-
.min(2 * liquidity_penalty_multiplier_msat)
703+
.min(max_penalty_msat)
670704
}
671705

672706
/// Returns the lower bound of the channel liquidity balance in this direction.
@@ -738,13 +772,11 @@ impl<G: Deref<Target = NetworkGraph>, T: Time> Score for ProbabilisticScorerUsin
738772
&self, short_channel_id: u64, amount_msat: u64, capacity_msat: u64, source: &NodeId,
739773
target: &NodeId
740774
) -> u64 {
741-
let liquidity_penalty_multiplier_msat = self.params.liquidity_penalty_multiplier_msat;
742-
let liquidity_offset_half_life = self.params.liquidity_offset_half_life;
743775
self.channel_liquidities
744776
.get(&short_channel_id)
745777
.unwrap_or(&ChannelLiquidity::new())
746-
.as_directed(source, target, capacity_msat, liquidity_offset_half_life)
747-
.penalty_msat(amount_msat, liquidity_penalty_multiplier_msat)
778+
.as_directed(source, target, capacity_msat, self.params.liquidity_offset_half_life)
779+
.penalty_msat(amount_msat, &self.params)
748780
}
749781

750782
fn payment_path_failed(&mut self, path: &[&RouteHop], short_channel_id: u64) {
@@ -1056,7 +1088,7 @@ pub(crate) use self::time::Time;
10561088

10571089
#[cfg(test)]
10581090
mod tests {
1059-
use super::{ChannelLiquidity, ProbabilisticScoringParameters, ProbabilisticScorerUsingTime, ScoringParameters, ScorerUsingTime, Time};
1091+
use super::{ChannelLiquidity, ProbabilisticScoringCostFunction, ProbabilisticScoringParameters, ProbabilisticScorerUsingTime, ScoringParameters, ScorerUsingTime, Time};
10601092
use super::time::Eternity;
10611093

10621094
use ln::features::{ChannelFeatures, NodeFeatures};
@@ -1745,6 +1777,32 @@ mod tests {
17451777
assert_eq!(scorer.channel_penalty_msat(42, 896, 1_024, &source, &target), 903);
17461778
}
17471779

1780+
#[test]
1781+
fn increased_penalty_linearly_nearing_liquidity_upper_bound() {
1782+
let network_graph = network_graph();
1783+
let params = ProbabilisticScoringParameters {
1784+
cost_function: ProbabilisticScoringCostFunction::TwiceFailureProbability,
1785+
liquidity_penalty_multiplier_msat: 1_000,
1786+
..Default::default()
1787+
};
1788+
let scorer = ProbabilisticScorer::new(params, &network_graph);
1789+
let source = source_node_id();
1790+
let target = target_node_id();
1791+
1792+
assert_eq!(scorer.channel_penalty_msat(42, 1_024, 1_024_000, &source, &target), 2);
1793+
assert_eq!(scorer.channel_penalty_msat(42, 10_240, 1_024_000, &source, &target), 20);
1794+
assert_eq!(scorer.channel_penalty_msat(42, 102_400, 1_024_000, &source, &target), 200);
1795+
assert_eq!(scorer.channel_penalty_msat(42, 1_024_000, 1_024_000, &source, &target), 2_000);
1796+
1797+
assert_eq!(scorer.channel_penalty_msat(42, 125, 1_000, &source, &target), 250);
1798+
assert_eq!(scorer.channel_penalty_msat(42, 250, 1_000, &source, &target), 500);
1799+
assert_eq!(scorer.channel_penalty_msat(42, 375, 1_000, &source, &target), 750);
1800+
assert_eq!(scorer.channel_penalty_msat(42, 500, 1_000, &source, &target), 1_000);
1801+
assert_eq!(scorer.channel_penalty_msat(42, 625, 1_000, &source, &target), 1_249);
1802+
assert_eq!(scorer.channel_penalty_msat(42, 750, 1_000, &source, &target), 1_499);
1803+
assert_eq!(scorer.channel_penalty_msat(42, 875, 1_000, &source, &target), 1_749);
1804+
}
1805+
17481806
#[test]
17491807
fn constant_penalty_outside_liquidity_bounds() {
17501808
let last_updated = SinceEpoch::now();
@@ -1861,6 +1919,7 @@ mod tests {
18611919
let params = ProbabilisticScoringParameters {
18621920
liquidity_penalty_multiplier_msat: 1_000,
18631921
liquidity_offset_half_life: Duration::from_secs(10),
1922+
..Default::default()
18641923
};
18651924
let mut scorer = ProbabilisticScorer::new(params, &network_graph);
18661925
let source = source_node_id();
@@ -1912,6 +1971,7 @@ mod tests {
19121971
let params = ProbabilisticScoringParameters {
19131972
liquidity_penalty_multiplier_msat: 1_000,
19141973
liquidity_offset_half_life: Duration::from_secs(10),
1974+
..Default::default()
19151975
};
19161976
let mut scorer = ProbabilisticScorer::new(params, &network_graph);
19171977
let source = source_node_id();
@@ -1936,6 +1996,7 @@ mod tests {
19361996
let params = ProbabilisticScoringParameters {
19371997
liquidity_penalty_multiplier_msat: 1_000,
19381998
liquidity_offset_half_life: Duration::from_secs(10),
1999+
..Default::default()
19392000
};
19402001
let mut scorer = ProbabilisticScorer::new(params, &network_graph);
19412002
let source = source_node_id();
@@ -1973,6 +2034,7 @@ mod tests {
19732034
let params = ProbabilisticScoringParameters {
19742035
liquidity_penalty_multiplier_msat: 1_000,
19752036
liquidity_offset_half_life: Duration::from_secs(10),
2037+
..Default::default()
19762038
};
19772039
let mut scorer = ProbabilisticScorer::new(params, &network_graph);
19782040
let source = source_node_id();
@@ -2002,6 +2064,7 @@ mod tests {
20022064
let params = ProbabilisticScoringParameters {
20032065
liquidity_penalty_multiplier_msat: 1_000,
20042066
liquidity_offset_half_life: Duration::from_secs(10),
2067+
..Default::default()
20052068
};
20062069
let mut scorer = ProbabilisticScorer::new(params, &network_graph);
20072070
let source = source_node_id();

0 commit comments

Comments
 (0)