Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a per-amount base penalty in the ProbabilisticScorer #1617

Merged
merged 2 commits into from Jul 25, 2022
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
90 changes: 61 additions & 29 deletions lightning/src/routing/scoring.rs
Expand Up @@ -324,13 +324,30 @@ where L::Target: Logger {
///
/// Used to configure base, liquidity, and amount penalties, the sum of which comprises the channel
/// penalty (i.e., the amount in msats willing to be paid to avoid routing through the channel).
///
/// The penalty applied to any channel by the [`ProbabilisticScorer`] is the sum of each of the
/// parameters here.
#[derive(Clone)]
pub struct ProbabilisticScoringParameters {
/// A fixed penalty in msats to apply to each channel.
///
/// Default value: 500 msat
pub base_penalty_msat: u64,

/// A multiplier used with the payment amount to calculate a fixed penalty applied to each
/// channel, in excess of the [`base_penalty_msat`].
///
/// The purpose of the amount penalty is to avoid having fees dominate the channel cost (i.e.,
TheBlueMatt marked this conversation as resolved.
Show resolved Hide resolved
/// fees plus penalty) for large payments. The penalty is computed as the product of this
/// multiplier and `2^30`ths of the payment amount.
///
/// ie `base_penalty_amount_multiplier_msat * amount_msat / 2^30`
///
/// Default value: 8,192 msat
///
/// [`base_penalty_msat`]: Self::base_penalty_msat
pub base_penalty_amount_multiplier_msat: u64,

/// A multiplier used in conjunction with the negative `log10` of the channel's success
/// probability for a payment to determine the liquidity penalty.
///
Expand Down Expand Up @@ -369,7 +386,7 @@ pub struct ProbabilisticScoringParameters {
/// multiplier and `2^20`ths of the payment amount, weighted by the negative `log10` of the
/// success probability.
///
/// `-log10(success_probability) * amount_penalty_multiplier_msat * amount_msat / 2^20`
/// `-log10(success_probability) * liquidity_penalty_amount_multiplier_msat * amount_msat / 2^20`
///
/// In practice, this means for 0.1 success probability (`-log10(0.1) == 1`) each `2^20`th of
/// the amount will result in a penalty of the multiplier. And, as the success probability
Expand All @@ -378,7 +395,7 @@ pub struct ProbabilisticScoringParameters {
/// fall below `1`.
///
/// Default value: 256 msat
pub amount_penalty_multiplier_msat: u64,
pub liquidity_penalty_amount_multiplier_msat: u64,

/// Manual penalties used for the given nodes. Allows to set a particular penalty for a given
/// node. Note that a manual penalty of `u64::max_value()` means the node would not ever be
Expand All @@ -399,7 +416,7 @@ pub struct ProbabilisticScoringParameters {
/// current estimate of the channel's available liquidity.
///
/// Note that in this case all other penalties, including the
/// [`liquidity_penalty_multiplier_msat`] and [`amount_penalty_multiplier_msat`]-based
/// [`liquidity_penalty_multiplier_msat`] and [`liquidity_penalty_amount_multiplier_msat`]-based
/// penalties, as well as the [`base_penalty_msat`] and the [`anti_probing_penalty_msat`], if
/// applicable, are still included in the overall penalty.
///
Expand All @@ -409,7 +426,7 @@ pub struct ProbabilisticScoringParameters {
/// Default value: 1_0000_0000_000 msat (1 Bitcoin)
///
/// [`liquidity_penalty_multiplier_msat`]: Self::liquidity_penalty_multiplier_msat
/// [`amount_penalty_multiplier_msat`]: Self::amount_penalty_multiplier_msat
/// [`liquidity_penalty_amount_multiplier_msat`]: Self::liquidity_penalty_amount_multiplier_msat
/// [`base_penalty_msat`]: Self::base_penalty_msat
/// [`anti_probing_penalty_msat`]: Self::anti_probing_penalty_msat
pub considered_impossible_penalty_msat: u64,
Expand Down Expand Up @@ -536,9 +553,10 @@ impl ProbabilisticScoringParameters {
fn zero_penalty() -> Self {
Self {
base_penalty_msat: 0,
base_penalty_amount_multiplier_msat: 0,
liquidity_penalty_multiplier_msat: 0,
liquidity_offset_half_life: Duration::from_secs(3600),
amount_penalty_multiplier_msat: 0,
liquidity_penalty_amount_multiplier_msat: 0,
manual_node_penalties: HashMap::new(),
anti_probing_penalty_msat: 0,
considered_impossible_penalty_msat: 0,
Expand All @@ -558,9 +576,10 @@ impl Default for ProbabilisticScoringParameters {
fn default() -> Self {
Self {
base_penalty_msat: 500,
base_penalty_amount_multiplier_msat: 8192,
liquidity_penalty_multiplier_msat: 40_000,
liquidity_offset_half_life: Duration::from_secs(3600),
amount_penalty_multiplier_msat: 256,
liquidity_penalty_amount_multiplier_msat: 256,
manual_node_penalties: HashMap::new(),
anti_probing_penalty_msat: 250,
considered_impossible_penalty_msat: 1_0000_0000_000,
Expand Down Expand Up @@ -631,10 +650,11 @@ const PRECISION_LOWER_BOUND_DENOMINATOR: u64 = approx::LOWER_BITS_BOUND;

/// The divisor used when computing the amount penalty.
const AMOUNT_PENALTY_DIVISOR: u64 = 1 << 20;
const BASE_AMOUNT_PENALTY_DIVISOR: u64 = 1 << 30;

impl<L: Deref<Target = u64>, T: Time, U: Deref<Target = T>> DirectedChannelLiquidity<L, T, U> {
/// Returns a penalty for routing the given HTLC `amount_msat` through the channel in this
/// direction.
/// Returns a liquidity penalty for routing the given HTLC `amount_msat` through the channel in
/// this direction.
fn penalty_msat(&self, amount_msat: u64, params: &ProbabilisticScoringParameters) -> u64 {
TheBlueMatt marked this conversation as resolved.
Show resolved Hide resolved
let max_liquidity_msat = self.max_liquidity_msat();
let min_liquidity_msat = core::cmp::min(self.min_liquidity_msat(), max_liquidity_msat);
Expand All @@ -653,8 +673,8 @@ impl<L: Deref<Target = u64>, T: Time, U: Deref<Target = T>> DirectedChannelLiqui
if amount_msat - min_liquidity_msat < denominator / PRECISION_LOWER_BOUND_DENOMINATOR {
// If the failure probability is < 1.5625% (as 1 - numerator/denominator < 1/64),
// don't bother trying to use the log approximation as it gets too noisy to be
// particularly helpful, instead just round down to 0 and return the base penalty.
params.base_penalty_msat
// particularly helpful, instead just round down to 0.
0
} else {
let negative_log10_times_2048 =
approx::negative_log10_times_2048(numerator, denominator);
Expand All @@ -663,7 +683,7 @@ impl<L: Deref<Target = u64>, T: Time, U: Deref<Target = T>> DirectedChannelLiqui
}
}

/// Computes the liquidity and amount penalties and adds them to the base penalty.
/// Computes the liquidity penalty from the penalty multipliers.
#[inline(always)]
fn combined_penalty_msat(
&self, amount_msat: u64, negative_log10_times_2048: u64,
Expand All @@ -676,12 +696,10 @@ impl<L: Deref<Target = u64>, T: Time, U: Deref<Target = T>> DirectedChannelLiqui
(negative_log10_times_2048.saturating_mul(multiplier_msat) / 2048).min(max_penalty_msat)
};
let amount_penalty_msat = negative_log10_times_2048
.saturating_mul(params.amount_penalty_multiplier_msat)
.saturating_mul(params.liquidity_penalty_amount_multiplier_msat)
.saturating_mul(amount_msat) / 2048 / AMOUNT_PENALTY_DIVISOR;

params.base_penalty_msat
.saturating_add(liquidity_penalty_msat)
.saturating_add(amount_penalty_msat)
liquidity_penalty_msat.saturating_add(amount_penalty_msat)
TheBlueMatt marked this conversation as resolved.
Show resolved Hide resolved
}

/// Returns the lower bound of the channel liquidity balance in this direction.
Expand Down Expand Up @@ -763,13 +781,17 @@ impl<G: Deref<Target = NetworkGraph<L>>, L: Deref, T: Time> Score for Probabilis
return *penalty;
}

let base_penalty_msat = self.params.base_penalty_msat.saturating_add(
self.params.base_penalty_amount_multiplier_msat
.saturating_mul(usage.amount_msat) / BASE_AMOUNT_PENALTY_DIVISOR);

let mut anti_probing_penalty_msat = 0;
match usage.effective_capacity {
EffectiveCapacity::ExactLiquidity { liquidity_msat } => {
if usage.amount_msat > liquidity_msat {
return u64::max_value();
} else {
return self.params.base_penalty_msat;
return base_penalty_msat;
}
},
EffectiveCapacity::Total { capacity_msat, htlc_maximum_msat: Some(htlc_maximum_msat) } => {
Expand All @@ -790,6 +812,7 @@ impl<G: Deref<Target = NetworkGraph<L>>, L: Deref, T: Time> Score for Probabilis
.as_directed(source, target, capacity_msat, liquidity_offset_half_life)
.penalty_msat(amount_msat, &self.params)
.saturating_add(anti_probing_penalty_msat)
.saturating_add(base_penalty_msat)
}

fn payment_path_failed(&mut self, path: &[&RouteHop], short_channel_id: u64) {
Expand Down Expand Up @@ -2069,47 +2092,47 @@ mod tests {
inflight_htlc_msat: 0,
effective_capacity: EffectiveCapacity::Total { capacity_msat: 950_000_000, htlc_maximum_msat: Some(1_000) },
};
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 3613);
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 4375);
let usage = ChannelUsage {
effective_capacity: EffectiveCapacity::Total { capacity_msat: 1_950_000_000, htlc_maximum_msat: Some(1_000) }, ..usage
};
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 1977);
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 2739);
let usage = ChannelUsage {
effective_capacity: EffectiveCapacity::Total { capacity_msat: 2_950_000_000, htlc_maximum_msat: Some(1_000) }, ..usage
};
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 1474);
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 2236);
let usage = ChannelUsage {
effective_capacity: EffectiveCapacity::Total { capacity_msat: 3_950_000_000, htlc_maximum_msat: Some(1_000) }, ..usage
};
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 1223);
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 1985);
let usage = ChannelUsage {
effective_capacity: EffectiveCapacity::Total { capacity_msat: 4_950_000_000, htlc_maximum_msat: Some(1_000) }, ..usage
};
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 877);
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 1639);
let usage = ChannelUsage {
effective_capacity: EffectiveCapacity::Total { capacity_msat: 5_950_000_000, htlc_maximum_msat: Some(1_000) }, ..usage
};
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 845);
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 1607);
let usage = ChannelUsage {
effective_capacity: EffectiveCapacity::Total { capacity_msat: 6_950_000_000, htlc_maximum_msat: Some(1_000) }, ..usage
};
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 500);
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 1262);
let usage = ChannelUsage {
effective_capacity: EffectiveCapacity::Total { capacity_msat: 7_450_000_000, htlc_maximum_msat: Some(1_000) }, ..usage
};
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 500);
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 1262);
let usage = ChannelUsage {
effective_capacity: EffectiveCapacity::Total { capacity_msat: 7_950_000_000, htlc_maximum_msat: Some(1_000) }, ..usage
};
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 500);
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 1262);
let usage = ChannelUsage {
effective_capacity: EffectiveCapacity::Total { capacity_msat: 8_950_000_000, htlc_maximum_msat: Some(1_000) }, ..usage
};
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 500);
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 1262);
let usage = ChannelUsage {
effective_capacity: EffectiveCapacity::Total { capacity_msat: 9_950_000_000, htlc_maximum_msat: Some(1_000) }, ..usage
};
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 500);
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 1262);
}

#[test]
Expand Down Expand Up @@ -2137,6 +2160,15 @@ mod tests {
};
let scorer = ProbabilisticScorer::new(params, &network_graph, &logger);
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 558);

let params = ProbabilisticScoringParameters {
base_penalty_msat: 500, liquidity_penalty_multiplier_msat: 1_000,
base_penalty_amount_multiplier_msat: (1 << 30),
anti_probing_penalty_msat: 0, ..Default::default()
};

let scorer = ProbabilisticScorer::new(params, &network_graph, &logger);
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 558 + 128);
}

#[test]
Expand All @@ -2153,15 +2185,15 @@ mod tests {

let params = ProbabilisticScoringParameters {
liquidity_penalty_multiplier_msat: 1_000,
amount_penalty_multiplier_msat: 0,
liquidity_penalty_amount_multiplier_msat: 0,
..ProbabilisticScoringParameters::zero_penalty()
};
let scorer = ProbabilisticScorer::new(params, &network_graph, &logger);
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 300);

let params = ProbabilisticScoringParameters {
liquidity_penalty_multiplier_msat: 1_000,
amount_penalty_multiplier_msat: 256,
liquidity_penalty_amount_multiplier_msat: 256,
..ProbabilisticScoringParameters::zero_penalty()
};
let scorer = ProbabilisticScorer::new(params, &network_graph, &logger);
Expand Down