Skip to content

Commit 51e1de9

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 edacd23 commit 51e1de9

File tree

1 file changed

+113
-24
lines changed

1 file changed

+113
-24
lines changed

lightning/src/routing/scoring.rs

Lines changed: 113 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 composes 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,40 @@ 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+
#[inline(always)]
737+
fn combined_penalty_msat(
738+
&self, amount_msat: u64, negative_log10_times_1024: u64,
739+
params: ProbabilisticScoringParameters
740+
) -> u64 {
741+
let liquidity_penalty_msat = {
742+
// Upper bound the liquidity penalty to ensure some channel is selected.
743+
let multiplier_msat = params.liquidity_penalty_multiplier_msat;
744+
let max_penalty_msat = multiplier_msat.saturating_mul(NEGATIVE_LOG10_UPPER_BOUND);
745+
(negative_log10_times_1024.saturating_mul(multiplier_msat) / 1024).min(max_penalty_msat)
746+
};
747+
let amount_penalty_msat = negative_log10_times_1024
748+
.saturating_mul(params.amount_penalty_multiplier_msat)
749+
.saturating_mul(amount_msat) / 1024 / AMOUNT_PENALTY_DIVISOR;
750+
751+
params.base_penalty_msat
752+
.saturating_add(liquidity_penalty_msat)
753+
.saturating_add(amount_penalty_msat)
754+
}
755+
696756
/// Returns the lower bound of the channel liquidity balance in this direction.
697757
fn min_liquidity_msat(&self) -> u64 {
698758
self.decayed_offset_msat(*self.min_liquidity_offset_msat)
@@ -762,14 +822,12 @@ impl<G: Deref<Target = NetworkGraph>, T: Time> Score for ProbabilisticScorerUsin
762822
&self, short_channel_id: u64, amount_msat: u64, capacity_msat: u64, source: &NodeId,
763823
target: &NodeId
764824
) -> u64 {
765-
let liquidity_penalty_multiplier_msat = self.params.liquidity_penalty_multiplier_msat;
766825
let liquidity_offset_half_life = self.params.liquidity_offset_half_life;
767826
self.channel_liquidities
768827
.get(&short_channel_id)
769828
.unwrap_or(&ChannelLiquidity::new())
770829
.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)
830+
.penalty_msat(amount_msat, self.params)
773831
}
774832

775833
fn payment_path_failed(&mut self, path: &[&RouteHop], short_channel_id: u64) {
@@ -1747,7 +1805,8 @@ mod tests {
17471805
fn increased_penalty_nearing_liquidity_upper_bound() {
17481806
let network_graph = network_graph();
17491807
let params = ProbabilisticScoringParameters {
1750-
base_penalty_msat: 0, liquidity_penalty_multiplier_msat: 1_000, ..Default::default()
1808+
liquidity_penalty_multiplier_msat: 1_000,
1809+
..ProbabilisticScoringParameters::zero_penalty()
17511810
};
17521811
let scorer = ProbabilisticScorer::new(params, &network_graph);
17531812
let source = source_node_id();
@@ -1772,7 +1831,8 @@ mod tests {
17721831
let last_updated = SinceEpoch::now();
17731832
let network_graph = network_graph();
17741833
let params = ProbabilisticScoringParameters {
1775-
base_penalty_msat: 0, liquidity_penalty_multiplier_msat: 1_000, ..Default::default()
1834+
liquidity_penalty_multiplier_msat: 1_000,
1835+
..ProbabilisticScoringParameters::zero_penalty()
17761836
};
17771837
let scorer = ProbabilisticScorer::new(params, &network_graph)
17781838
.with_channel(42,
@@ -1792,7 +1852,8 @@ mod tests {
17921852
fn does_not_further_penalize_own_channel() {
17931853
let network_graph = network_graph();
17941854
let params = ProbabilisticScoringParameters {
1795-
base_penalty_msat: 0, liquidity_penalty_multiplier_msat: 1_000, ..Default::default()
1855+
liquidity_penalty_multiplier_msat: 1_000,
1856+
..ProbabilisticScoringParameters::zero_penalty()
17961857
};
17971858
let mut scorer = ProbabilisticScorer::new(params, &network_graph);
17981859
let sender = sender_node_id();
@@ -1813,7 +1874,8 @@ mod tests {
18131874
fn sets_liquidity_lower_bound_on_downstream_failure() {
18141875
let network_graph = network_graph();
18151876
let params = ProbabilisticScoringParameters {
1816-
base_penalty_msat: 0, liquidity_penalty_multiplier_msat: 1_000, ..Default::default()
1877+
liquidity_penalty_multiplier_msat: 1_000,
1878+
..ProbabilisticScoringParameters::zero_penalty()
18171879
};
18181880
let mut scorer = ProbabilisticScorer::new(params, &network_graph);
18191881
let source = source_node_id();
@@ -1835,7 +1897,8 @@ mod tests {
18351897
fn sets_liquidity_upper_bound_on_failure() {
18361898
let network_graph = network_graph();
18371899
let params = ProbabilisticScoringParameters {
1838-
base_penalty_msat: 0, liquidity_penalty_multiplier_msat: 1_000, ..Default::default()
1900+
liquidity_penalty_multiplier_msat: 1_000,
1901+
..ProbabilisticScoringParameters::zero_penalty()
18391902
};
18401903
let mut scorer = ProbabilisticScorer::new(params, &network_graph);
18411904
let source = source_node_id();
@@ -1857,7 +1920,8 @@ mod tests {
18571920
fn reduces_liquidity_upper_bound_along_path_on_success() {
18581921
let network_graph = network_graph();
18591922
let params = ProbabilisticScoringParameters {
1860-
base_penalty_msat: 0, liquidity_penalty_multiplier_msat: 1_000, ..Default::default()
1923+
liquidity_penalty_multiplier_msat: 1_000,
1924+
..ProbabilisticScoringParameters::zero_penalty()
18611925
};
18621926
let mut scorer = ProbabilisticScorer::new(params, &network_graph);
18631927
let sender = sender_node_id();
@@ -1881,9 +1945,9 @@ mod tests {
18811945
fn decays_liquidity_bounds_over_time() {
18821946
let network_graph = network_graph();
18831947
let params = ProbabilisticScoringParameters {
1884-
base_penalty_msat: 0,
18851948
liquidity_penalty_multiplier_msat: 1_000,
18861949
liquidity_offset_half_life: Duration::from_secs(10),
1950+
..ProbabilisticScoringParameters::zero_penalty()
18871951
};
18881952
let mut scorer = ProbabilisticScorer::new(params, &network_graph);
18891953
let source = source_node_id();
@@ -1933,9 +1997,9 @@ mod tests {
19331997
fn decays_liquidity_bounds_without_shift_overflow() {
19341998
let network_graph = network_graph();
19351999
let params = ProbabilisticScoringParameters {
1936-
base_penalty_msat: 0,
19372000
liquidity_penalty_multiplier_msat: 1_000,
19382001
liquidity_offset_half_life: Duration::from_secs(10),
2002+
..ProbabilisticScoringParameters::zero_penalty()
19392003
};
19402004
let mut scorer = ProbabilisticScorer::new(params, &network_graph);
19412005
let source = source_node_id();
@@ -1958,9 +2022,9 @@ mod tests {
19582022
fn restricts_liquidity_bounds_after_decay() {
19592023
let network_graph = network_graph();
19602024
let params = ProbabilisticScoringParameters {
1961-
base_penalty_msat: 0,
19622025
liquidity_penalty_multiplier_msat: 1_000,
19632026
liquidity_offset_half_life: Duration::from_secs(10),
2027+
..ProbabilisticScoringParameters::zero_penalty()
19642028
};
19652029
let mut scorer = ProbabilisticScorer::new(params, &network_graph);
19662030
let source = source_node_id();
@@ -1996,9 +2060,9 @@ mod tests {
19962060
fn restores_persisted_liquidity_bounds() {
19972061
let network_graph = network_graph();
19982062
let params = ProbabilisticScoringParameters {
1999-
base_penalty_msat: 0,
20002063
liquidity_penalty_multiplier_msat: 1_000,
20012064
liquidity_offset_half_life: Duration::from_secs(10),
2065+
..ProbabilisticScoringParameters::zero_penalty()
20022066
};
20032067
let mut scorer = ProbabilisticScorer::new(params, &network_graph);
20042068
let source = source_node_id();
@@ -2026,9 +2090,9 @@ mod tests {
20262090
fn decays_persisted_liquidity_bounds() {
20272091
let network_graph = network_graph();
20282092
let params = ProbabilisticScoringParameters {
2029-
base_penalty_msat: 0,
20302093
liquidity_penalty_multiplier_msat: 1_000,
20312094
liquidity_offset_half_life: Duration::from_secs(10),
2095+
..ProbabilisticScoringParameters::zero_penalty()
20322096
};
20332097
let mut scorer = ProbabilisticScorer::new(params, &network_graph);
20342098
let source = source_node_id();
@@ -2061,7 +2125,8 @@ mod tests {
20612125
let target = target_node_id();
20622126

20632127
let params = ProbabilisticScoringParameters {
2064-
base_penalty_msat: 0, liquidity_penalty_multiplier_msat: 1_000, ..Default::default()
2128+
liquidity_penalty_multiplier_msat: 1_000,
2129+
..ProbabilisticScoringParameters::zero_penalty()
20652130
};
20662131
let scorer = ProbabilisticScorer::new(params, &network_graph);
20672132
assert_eq!(scorer.channel_penalty_msat(42, 128, 1_024, &source, &target), 58);
@@ -2073,14 +2138,38 @@ mod tests {
20732138
assert_eq!(scorer.channel_penalty_msat(42, 128, 1_024, &source, &target), 558);
20742139
}
20752140

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

20822170
let params = ProbabilisticScoringParameters {
2083-
base_penalty_msat: 0, ..Default::default()
2171+
liquidity_penalty_multiplier_msat: 40_000,
2172+
..ProbabilisticScoringParameters::zero_penalty()
20842173
};
20852174
let scorer = ProbabilisticScorer::new(params, &network_graph);
20862175
assert_eq!(

0 commit comments

Comments
 (0)