Skip to content

Commit 5425b2b

Browse files
committed
Add an amount penalty to ProbabilisticScorer
The cost of large payments tends to be dominated by the channel fees. To avoid this, add an amount penalty to ProbabilisticScorer with a user configurable multiplier. The multiplier is applied for every 2^20th of the amount weighted by the negative log10 of the channel's success probability for the payment.
1 parent 5337f89 commit 5425b2b

File tree

1 file changed

+114
-24
lines changed

1 file changed

+114
-24
lines changed

lightning/src/routing/scoring.rs

Lines changed: 114 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -517,7 +517,7 @@ pub struct ProbabilisticScorerUsingTime<G: Deref<Target = NetworkGraph>, T: Time
517517

518518
/// Parameters for configuring [`ProbabilisticScorer`].
519519
///
520-
/// Used to configure a base penalty and a liquidity penalty, the sum of which is the channel
520+
/// Used to configure base, liquidity, and amount penalties, the sum of which comprises the channel
521521
/// penalty (i.e., the amount in msats willing to be paid to avoid routing through the channel).
522522
#[derive(Clone, Copy)]
523523
pub struct ProbabilisticScoringParameters {
@@ -555,6 +555,25 @@ pub struct ProbabilisticScoringParameters {
555555
/// When built with the `no-std` feature, time will never elapse. Therefore, the channel
556556
/// liquidity knowledge will never decay except when the bounds cross.
557557
pub liquidity_offset_half_life: Duration,
558+
559+
/// A multiplier used in conjunction with a payment amount and the negative `log10` of the
560+
/// channel's success probability for the payment to determine the amount penalty.
561+
///
562+
/// The purpose of the amount penalty is to avoid having fees dominate the channel cost (i.e.,
563+
/// fees plus penalty) for large payments. The penalty is computed as the product of this
564+
/// multiplier and `2^20`ths of the payment amount, weighted by the negative `log10` of the
565+
/// success probability.
566+
///
567+
/// `-log10(success_probability) * amount_penalty_multiplier_msat * amount_msat / 2^20`
568+
///
569+
/// In practice, this means for 0.1 success probability (`-log10(0.1) == 1`) each `2^20`th of
570+
/// the amount will result in a penalty of the multiplier. And, as the success probability
571+
/// decreases, the negative `log10` weighting will increase dramatically. For higher success
572+
/// probabilities, the multiplier will have a decreasing effect as the negative `log10` will
573+
/// fall below `1`.
574+
///
575+
/// Default value: 256 msat
576+
pub amount_penalty_multiplier_msat: u64,
558577
}
559578

560579
/// Accounting for channel liquidity balance uncertainty.
@@ -602,12 +621,25 @@ impl<G: Deref<Target = NetworkGraph>, T: Time> ProbabilisticScorerUsingTime<G, T
602621
}
603622
}
604623

624+
impl ProbabilisticScoringParameters {
625+
#[cfg(test)]
626+
fn zero_penalty() -> Self {
627+
Self {
628+
base_penalty_msat: 0,
629+
liquidity_penalty_multiplier_msat: 0,
630+
liquidity_offset_half_life: Duration::from_secs(3600),
631+
amount_penalty_multiplier_msat: 0,
632+
}
633+
}
634+
}
635+
605636
impl Default for ProbabilisticScoringParameters {
606637
fn default() -> Self {
607638
Self {
608639
base_penalty_msat: 500,
609640
liquidity_penalty_multiplier_msat: 40_000,
610641
liquidity_offset_half_life: Duration::from_secs(3600),
642+
amount_penalty_multiplier_msat: 256,
611643
}
612644
}
613645
}
@@ -665,11 +697,17 @@ impl<T: Time> ChannelLiquidity<T> {
665697
}
666698
}
667699

700+
/// Bounds `-log10` to avoid excessive liquidity penalties for payments with low success
701+
/// probabilities.
702+
const NEGATIVE_LOG10_UPPER_BOUND: u64 = 2;
703+
704+
/// The divisor used when computing the amount penalty.
705+
const AMOUNT_PENALTY_DIVISOR: u64 = 1 << 20;
706+
668707
impl<L: Deref<Target = u64>, T: Time, U: Deref<Target = T>> DirectedChannelLiquidity<L, T, U> {
669708
/// Returns a penalty for routing the given HTLC `amount_msat` through the channel in this
670709
/// direction.
671-
fn penalty_msat(&self, amount_msat: u64, liquidity_penalty_multiplier_msat: u64) -> u64 {
672-
let max_penalty_msat = liquidity_penalty_multiplier_msat.saturating_mul(2);
710+
fn penalty_msat(&self, amount_msat: u64, params: ProbabilisticScoringParameters) -> u64 {
673711
let max_liquidity_msat = self.max_liquidity_msat();
674712
let min_liquidity_msat = core::cmp::min(self.min_liquidity_msat(), max_liquidity_msat);
675713
if amount_msat <= min_liquidity_msat {
@@ -681,18 +719,41 @@ impl<L: Deref<Target = u64>, T: Time, U: Deref<Target = T>> DirectedChannelLiqui
681719
// Avoid using the failed channel on retry.
682720
u64::max_value()
683721
} else {
684-
max_penalty_msat
722+
// Equivalent to hitting the else clause below with the amount equal to the
723+
// effective capacity and without any certainty on the liquidity upper bound.
724+
let negative_log10_times_1024 = NEGATIVE_LOG10_UPPER_BOUND * 1024;
725+
self.combined_penalty_msat(amount_msat, negative_log10_times_1024, params)
685726
}
686727
} else {
687728
let numerator = (max_liquidity_msat - amount_msat).saturating_add(1);
688729
let denominator = (max_liquidity_msat - min_liquidity_msat).saturating_add(1);
689-
let penalty_msat = approx::negative_log10_times_1024(numerator, denominator)
690-
.saturating_mul(liquidity_penalty_multiplier_msat) / 1024;
691-
// Upper bound the penalty to ensure some channel is selected.
692-
penalty_msat.min(max_penalty_msat)
730+
let negative_log10_times_1024 =
731+
approx::negative_log10_times_1024(numerator, denominator);
732+
self.combined_penalty_msat(amount_msat, negative_log10_times_1024, params)
693733
}
694734
}
695735

736+
/// Computes the liquidity and amount penalties and adds them to the base penalty.
737+
#[inline(always)]
738+
fn combined_penalty_msat(
739+
&self, amount_msat: u64, negative_log10_times_1024: u64,
740+
params: ProbabilisticScoringParameters
741+
) -> u64 {
742+
let liquidity_penalty_msat = {
743+
// Upper bound the liquidity penalty to ensure some channel is selected.
744+
let multiplier_msat = params.liquidity_penalty_multiplier_msat;
745+
let max_penalty_msat = multiplier_msat.saturating_mul(NEGATIVE_LOG10_UPPER_BOUND);
746+
(negative_log10_times_1024.saturating_mul(multiplier_msat) / 1024).min(max_penalty_msat)
747+
};
748+
let amount_penalty_msat = negative_log10_times_1024
749+
.saturating_mul(params.amount_penalty_multiplier_msat)
750+
.saturating_mul(amount_msat) / 1024 / AMOUNT_PENALTY_DIVISOR;
751+
752+
params.base_penalty_msat
753+
.saturating_add(liquidity_penalty_msat)
754+
.saturating_add(amount_penalty_msat)
755+
}
756+
696757
/// Returns the lower bound of the channel liquidity balance in this direction.
697758
fn min_liquidity_msat(&self) -> u64 {
698759
self.decayed_offset_msat(*self.min_liquidity_offset_msat)
@@ -762,14 +823,12 @@ impl<G: Deref<Target = NetworkGraph>, T: Time> Score for ProbabilisticScorerUsin
762823
&self, short_channel_id: u64, amount_msat: u64, capacity_msat: u64, source: &NodeId,
763824
target: &NodeId
764825
) -> u64 {
765-
let liquidity_penalty_multiplier_msat = self.params.liquidity_penalty_multiplier_msat;
766826
let liquidity_offset_half_life = self.params.liquidity_offset_half_life;
767827
self.channel_liquidities
768828
.get(&short_channel_id)
769829
.unwrap_or(&ChannelLiquidity::new())
770830
.as_directed(source, target, capacity_msat, liquidity_offset_half_life)
771-
.penalty_msat(amount_msat, liquidity_penalty_multiplier_msat)
772-
.saturating_add(self.params.base_penalty_msat)
831+
.penalty_msat(amount_msat, self.params)
773832
}
774833

775834
fn payment_path_failed(&mut self, path: &[&RouteHop], short_channel_id: u64) {
@@ -1747,7 +1806,8 @@ mod tests {
17471806
fn increased_penalty_nearing_liquidity_upper_bound() {
17481807
let network_graph = network_graph();
17491808
let params = ProbabilisticScoringParameters {
1750-
base_penalty_msat: 0, liquidity_penalty_multiplier_msat: 1_000, ..Default::default()
1809+
liquidity_penalty_multiplier_msat: 1_000,
1810+
..ProbabilisticScoringParameters::zero_penalty()
17511811
};
17521812
let scorer = ProbabilisticScorer::new(params, &network_graph);
17531813
let source = source_node_id();
@@ -1772,7 +1832,8 @@ mod tests {
17721832
let last_updated = SinceEpoch::now();
17731833
let network_graph = network_graph();
17741834
let params = ProbabilisticScoringParameters {
1775-
base_penalty_msat: 0, liquidity_penalty_multiplier_msat: 1_000, ..Default::default()
1835+
liquidity_penalty_multiplier_msat: 1_000,
1836+
..ProbabilisticScoringParameters::zero_penalty()
17761837
};
17771838
let scorer = ProbabilisticScorer::new(params, &network_graph)
17781839
.with_channel(42,
@@ -1792,7 +1853,8 @@ mod tests {
17921853
fn does_not_further_penalize_own_channel() {
17931854
let network_graph = network_graph();
17941855
let params = ProbabilisticScoringParameters {
1795-
base_penalty_msat: 0, liquidity_penalty_multiplier_msat: 1_000, ..Default::default()
1856+
liquidity_penalty_multiplier_msat: 1_000,
1857+
..ProbabilisticScoringParameters::zero_penalty()
17961858
};
17971859
let mut scorer = ProbabilisticScorer::new(params, &network_graph);
17981860
let sender = sender_node_id();
@@ -1813,7 +1875,8 @@ mod tests {
18131875
fn sets_liquidity_lower_bound_on_downstream_failure() {
18141876
let network_graph = network_graph();
18151877
let params = ProbabilisticScoringParameters {
1816-
base_penalty_msat: 0, liquidity_penalty_multiplier_msat: 1_000, ..Default::default()
1878+
liquidity_penalty_multiplier_msat: 1_000,
1879+
..ProbabilisticScoringParameters::zero_penalty()
18171880
};
18181881
let mut scorer = ProbabilisticScorer::new(params, &network_graph);
18191882
let source = source_node_id();
@@ -1835,7 +1898,8 @@ mod tests {
18351898
fn sets_liquidity_upper_bound_on_failure() {
18361899
let network_graph = network_graph();
18371900
let params = ProbabilisticScoringParameters {
1838-
base_penalty_msat: 0, liquidity_penalty_multiplier_msat: 1_000, ..Default::default()
1901+
liquidity_penalty_multiplier_msat: 1_000,
1902+
..ProbabilisticScoringParameters::zero_penalty()
18391903
};
18401904
let mut scorer = ProbabilisticScorer::new(params, &network_graph);
18411905
let source = source_node_id();
@@ -1857,7 +1921,8 @@ mod tests {
18571921
fn reduces_liquidity_upper_bound_along_path_on_success() {
18581922
let network_graph = network_graph();
18591923
let params = ProbabilisticScoringParameters {
1860-
base_penalty_msat: 0, liquidity_penalty_multiplier_msat: 1_000, ..Default::default()
1924+
liquidity_penalty_multiplier_msat: 1_000,
1925+
..ProbabilisticScoringParameters::zero_penalty()
18611926
};
18621927
let mut scorer = ProbabilisticScorer::new(params, &network_graph);
18631928
let sender = sender_node_id();
@@ -1881,9 +1946,9 @@ mod tests {
18811946
fn decays_liquidity_bounds_over_time() {
18821947
let network_graph = network_graph();
18831948
let params = ProbabilisticScoringParameters {
1884-
base_penalty_msat: 0,
18851949
liquidity_penalty_multiplier_msat: 1_000,
18861950
liquidity_offset_half_life: Duration::from_secs(10),
1951+
..ProbabilisticScoringParameters::zero_penalty()
18871952
};
18881953
let mut scorer = ProbabilisticScorer::new(params, &network_graph);
18891954
let source = source_node_id();
@@ -1933,9 +1998,9 @@ mod tests {
19331998
fn decays_liquidity_bounds_without_shift_overflow() {
19341999
let network_graph = network_graph();
19352000
let params = ProbabilisticScoringParameters {
1936-
base_penalty_msat: 0,
19372001
liquidity_penalty_multiplier_msat: 1_000,
19382002
liquidity_offset_half_life: Duration::from_secs(10),
2003+
..ProbabilisticScoringParameters::zero_penalty()
19392004
};
19402005
let mut scorer = ProbabilisticScorer::new(params, &network_graph);
19412006
let source = source_node_id();
@@ -1958,9 +2023,9 @@ mod tests {
19582023
fn restricts_liquidity_bounds_after_decay() {
19592024
let network_graph = network_graph();
19602025
let params = ProbabilisticScoringParameters {
1961-
base_penalty_msat: 0,
19622026
liquidity_penalty_multiplier_msat: 1_000,
19632027
liquidity_offset_half_life: Duration::from_secs(10),
2028+
..ProbabilisticScoringParameters::zero_penalty()
19642029
};
19652030
let mut scorer = ProbabilisticScorer::new(params, &network_graph);
19662031
let source = source_node_id();
@@ -1996,9 +2061,9 @@ mod tests {
19962061
fn restores_persisted_liquidity_bounds() {
19972062
let network_graph = network_graph();
19982063
let params = ProbabilisticScoringParameters {
1999-
base_penalty_msat: 0,
20002064
liquidity_penalty_multiplier_msat: 1_000,
20012065
liquidity_offset_half_life: Duration::from_secs(10),
2066+
..ProbabilisticScoringParameters::zero_penalty()
20022067
};
20032068
let mut scorer = ProbabilisticScorer::new(params, &network_graph);
20042069
let source = source_node_id();
@@ -2026,9 +2091,9 @@ mod tests {
20262091
fn decays_persisted_liquidity_bounds() {
20272092
let network_graph = network_graph();
20282093
let params = ProbabilisticScoringParameters {
2029-
base_penalty_msat: 0,
20302094
liquidity_penalty_multiplier_msat: 1_000,
20312095
liquidity_offset_half_life: Duration::from_secs(10),
2096+
..ProbabilisticScoringParameters::zero_penalty()
20322097
};
20332098
let mut scorer = ProbabilisticScorer::new(params, &network_graph);
20342099
let source = source_node_id();
@@ -2061,7 +2126,8 @@ mod tests {
20612126
let target = target_node_id();
20622127

20632128
let params = ProbabilisticScoringParameters {
2064-
base_penalty_msat: 0, liquidity_penalty_multiplier_msat: 1_000, ..Default::default()
2129+
liquidity_penalty_multiplier_msat: 1_000,
2130+
..ProbabilisticScoringParameters::zero_penalty()
20652131
};
20662132
let scorer = ProbabilisticScorer::new(params, &network_graph);
20672133
assert_eq!(scorer.channel_penalty_msat(42, 128, 1_024, &source, &target), 58);
@@ -2073,14 +2139,38 @@ mod tests {
20732139
assert_eq!(scorer.channel_penalty_msat(42, 128, 1_024, &source, &target), 558);
20742140
}
20752141

2142+
#[test]
2143+
fn adds_amount_penalty_to_liquidity_penalty() {
2144+
let network_graph = network_graph();
2145+
let source = source_node_id();
2146+
let target = target_node_id();
2147+
2148+
let params = ProbabilisticScoringParameters {
2149+
liquidity_penalty_multiplier_msat: 1_000,
2150+
amount_penalty_multiplier_msat: 0,
2151+
..ProbabilisticScoringParameters::zero_penalty()
2152+
};
2153+
let scorer = ProbabilisticScorer::new(params, &network_graph);
2154+
assert_eq!(scorer.channel_penalty_msat(42, 512_000, 1_024_000, &source, &target), 300);
2155+
2156+
let params = ProbabilisticScoringParameters {
2157+
liquidity_penalty_multiplier_msat: 1_000,
2158+
amount_penalty_multiplier_msat: 256,
2159+
..ProbabilisticScoringParameters::zero_penalty()
2160+
};
2161+
let scorer = ProbabilisticScorer::new(params, &network_graph);
2162+
assert_eq!(scorer.channel_penalty_msat(42, 512_000, 1_024_000, &source, &target), 337);
2163+
}
2164+
20762165
#[test]
20772166
fn calculates_log10_without_overflowing_u64_max_value() {
20782167
let network_graph = network_graph();
20792168
let source = source_node_id();
20802169
let target = target_node_id();
20812170

20822171
let params = ProbabilisticScoringParameters {
2083-
base_penalty_msat: 0, ..Default::default()
2172+
liquidity_penalty_multiplier_msat: 40_000,
2173+
..ProbabilisticScoringParameters::zero_penalty()
20842174
};
20852175
let scorer = ProbabilisticScorer::new(params, &network_graph);
20862176
assert_eq!(

0 commit comments

Comments
 (0)