Skip to content

Commit fec5c63

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 521a72b commit fec5c63

File tree

1 file changed

+85
-11
lines changed

1 file changed

+85
-11
lines changed

lightning/src/routing/scoring.rs

Lines changed: 85 additions & 11 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 (i.e., `-log10 == 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: 0
576+
pub amount_penalty_multiplier_msat: u64,
558577
}
559578

560579
/// Accounting for channel liquidity balance uncertainty.
@@ -608,6 +627,7 @@ impl Default for ProbabilisticScoringParameters {
608627
base_penalty_msat: 500,
609628
liquidity_penalty_multiplier_msat: 40_000,
610629
liquidity_offset_half_life: Duration::from_secs(3600),
630+
amount_penalty_multiplier_msat: 0,
611631
}
612632
}
613633
}
@@ -665,11 +685,17 @@ impl<T: Time> ChannelLiquidity<T> {
665685
}
666686
}
667687

688+
/// Bounds `-log10` to avoid excessive liquidity penalties for payments with low success
689+
/// probabilities.
690+
const NEGATIVE_LOG10_UPPER_BOUND: u64 = 2;
691+
692+
/// The divisor used when computing the amount penalty.
693+
const AMOUNT_PENALTY_DIVISOR: u64 = 1 << 20;
694+
668695
impl<L: Deref<Target = u64>, T: Time, U: Deref<Target = T>> DirectedChannelLiquidity<L, T, U> {
669696
/// Returns a penalty for routing the given HTLC `amount_msat` through the channel in this
670697
/// 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);
698+
fn penalty_msat(&self, amount_msat: u64, params: ProbabilisticScoringParameters) -> u64 {
673699
let max_liquidity_msat = self.max_liquidity_msat();
674700
let min_liquidity_msat = core::cmp::min(self.min_liquidity_msat(), max_liquidity_msat);
675701
if amount_msat <= min_liquidity_msat {
@@ -681,18 +707,40 @@ impl<L: Deref<Target = u64>, T: Time, U: Deref<Target = T>> DirectedChannelLiqui
681707
// Avoid using the failed channel on retry.
682708
u64::max_value()
683709
} else {
684-
max_penalty_msat
710+
// Equivalent to hitting the else clause below with the amount equal to the
711+
// effective capacity and without any certainty on the liquidity upper bound.
712+
let negative_log10_times_1024 = NEGATIVE_LOG10_UPPER_BOUND * 1024;
713+
self.combined_penalty_msat(amount_msat, negative_log10_times_1024, params)
685714
}
686715
} else {
687716
let numerator = (max_liquidity_msat - amount_msat).saturating_add(1);
688717
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)
718+
let negative_log10_times_1024 =
719+
approx::negative_log10_times_1024(numerator, denominator);
720+
self.combined_penalty_msat(amount_msat, negative_log10_times_1024, params)
693721
}
694722
}
695723

724+
#[inline(always)]
725+
fn combined_penalty_msat(
726+
&self, amount_msat: u64, negative_log10_times_1024: u64,
727+
params: ProbabilisticScoringParameters
728+
) -> u64 {
729+
let liquidity_penalty_msat = {
730+
// Upper bound the liquidity penalty to ensure some channel is selected.
731+
let multiplier_msat = params.liquidity_penalty_multiplier_msat;
732+
let max_penalty_msat = multiplier_msat.saturating_mul(NEGATIVE_LOG10_UPPER_BOUND);
733+
(negative_log10_times_1024.saturating_mul(multiplier_msat) / 1024).min(max_penalty_msat)
734+
};
735+
let amount_penalty_msat = negative_log10_times_1024
736+
.saturating_mul(params.amount_penalty_multiplier_msat)
737+
.saturating_mul(amount_msat) / 1024 / AMOUNT_PENALTY_DIVISOR;
738+
739+
params.base_penalty_msat
740+
.saturating_add(liquidity_penalty_msat)
741+
.saturating_add(amount_penalty_msat)
742+
}
743+
696744
/// Returns the lower bound of the channel liquidity balance in this direction.
697745
fn min_liquidity_msat(&self) -> u64 {
698746
self.decayed_offset_msat(*self.min_liquidity_offset_msat)
@@ -762,14 +810,12 @@ impl<G: Deref<Target = NetworkGraph>, T: Time> Score for ProbabilisticScorerUsin
762810
&self, short_channel_id: u64, amount_msat: u64, capacity_msat: u64, source: &NodeId,
763811
target: &NodeId
764812
) -> u64 {
765-
let liquidity_penalty_multiplier_msat = self.params.liquidity_penalty_multiplier_msat;
766813
let liquidity_offset_half_life = self.params.liquidity_offset_half_life;
767814
self.channel_liquidities
768815
.get(&short_channel_id)
769816
.unwrap_or(&ChannelLiquidity::new())
770817
.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)
818+
.penalty_msat(amount_msat, self.params)
773819
}
774820

775821
fn payment_path_failed(&mut self, path: &[&RouteHop], short_channel_id: u64) {
@@ -1884,6 +1930,7 @@ mod tests {
18841930
base_penalty_msat: 0,
18851931
liquidity_penalty_multiplier_msat: 1_000,
18861932
liquidity_offset_half_life: Duration::from_secs(10),
1933+
..Default::default()
18871934
};
18881935
let mut scorer = ProbabilisticScorer::new(params, &network_graph);
18891936
let source = source_node_id();
@@ -1936,6 +1983,7 @@ mod tests {
19361983
base_penalty_msat: 0,
19371984
liquidity_penalty_multiplier_msat: 1_000,
19381985
liquidity_offset_half_life: Duration::from_secs(10),
1986+
..Default::default()
19391987
};
19401988
let mut scorer = ProbabilisticScorer::new(params, &network_graph);
19411989
let source = source_node_id();
@@ -1961,6 +2009,7 @@ mod tests {
19612009
base_penalty_msat: 0,
19622010
liquidity_penalty_multiplier_msat: 1_000,
19632011
liquidity_offset_half_life: Duration::from_secs(10),
2012+
..Default::default()
19642013
};
19652014
let mut scorer = ProbabilisticScorer::new(params, &network_graph);
19662015
let source = source_node_id();
@@ -1999,6 +2048,7 @@ mod tests {
19992048
base_penalty_msat: 0,
20002049
liquidity_penalty_multiplier_msat: 1_000,
20012050
liquidity_offset_half_life: Duration::from_secs(10),
2051+
..Default::default()
20022052
};
20032053
let mut scorer = ProbabilisticScorer::new(params, &network_graph);
20042054
let source = source_node_id();
@@ -2029,6 +2079,7 @@ mod tests {
20292079
base_penalty_msat: 0,
20302080
liquidity_penalty_multiplier_msat: 1_000,
20312081
liquidity_offset_half_life: Duration::from_secs(10),
2082+
..Default::default()
20322083
};
20332084
let mut scorer = ProbabilisticScorer::new(params, &network_graph);
20342085
let source = source_node_id();
@@ -2073,6 +2124,29 @@ mod tests {
20732124
assert_eq!(scorer.channel_penalty_msat(42, 128, 1_024, &source, &target), 558);
20742125
}
20752126

2127+
#[test]
2128+
fn adds_amount_penalty_to_liquidity_penalty() {
2129+
let network_graph = network_graph();
2130+
let source = source_node_id();
2131+
let target = target_node_id();
2132+
2133+
let params = ProbabilisticScoringParameters {
2134+
liquidity_penalty_multiplier_msat: 1_000,
2135+
amount_penalty_multiplier_msat: 0,
2136+
..Default::default()
2137+
};
2138+
let scorer = ProbabilisticScorer::new(params, &network_graph);
2139+
assert_eq!(scorer.channel_penalty_msat(42, 512_000, 1_024_000, &source, &target), 800);
2140+
2141+
let params = ProbabilisticScoringParameters {
2142+
liquidity_penalty_multiplier_msat: 1_000,
2143+
amount_penalty_multiplier_msat: 256,
2144+
..Default::default()
2145+
};
2146+
let scorer = ProbabilisticScorer::new(params, &network_graph);
2147+
assert_eq!(scorer.channel_penalty_msat(42, 512_000, 1_024_000, &source, &target), 837);
2148+
}
2149+
20762150
#[test]
20772151
fn calculates_log10_without_overflowing_u64_max_value() {
20782152
let network_graph = network_graph();

0 commit comments

Comments
 (0)