Skip to content

Commit

Permalink
Calculate a new penalty based on historical channel liquidity range
Browse files Browse the repository at this point in the history
XXX: Write some text here
  • Loading branch information
TheBlueMatt committed Jul 21, 2022
1 parent ea8b426 commit 10921f9
Showing 1 changed file with 109 additions and 8 deletions.
117 changes: 109 additions & 8 deletions lightning/src/routing/scoring.rs
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,11 @@ pub struct ProbabilisticScoringParameters {
/// Default value: 256 msat
pub amount_penalty_multiplier_msat: u64,

/// XXX: Write docs
pub historical_liquidity_penalty_multiplier_msat: u64,
/// XXX: Write docs
pub historical_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
/// considered during path finding.
Expand Down Expand Up @@ -563,6 +568,8 @@ impl ProbabilisticScoringParameters {
liquidity_penalty_multiplier_msat: 0,
liquidity_offset_half_life: Duration::from_secs(3600),
amount_penalty_multiplier_msat: 0,
historical_liquidity_penalty_multiplier_msat: 0,
historical_liquidity_penalty_amount_multiplier_msat: 0,
manual_node_penalties: HashMap::new(),
anti_probing_penalty_msat: 0,
considered_impossible_penalty_msat: 0,
Expand All @@ -586,6 +593,8 @@ impl Default for ProbabilisticScoringParameters {
liquidity_penalty_multiplier_msat: 40_000,
liquidity_offset_half_life: Duration::from_secs(3600),
amount_penalty_multiplier_msat: 256,
historical_liquidity_penalty_multiplier_msat: 0,
historical_liquidity_penalty_amount_multiplier_msat: 0,
manual_node_penalties: HashMap::new(),
anti_probing_penalty_msat: 250,
considered_impossible_penalty_msat: 1_0000_0000_000,
Expand Down Expand Up @@ -676,14 +685,17 @@ impl<L: Deref<Target = u64>, LA: Deref<Target = [u16; 8]>, T: Time, U: Deref<Tar
fn penalty_msat(&self, amount_msat: u64, params: &ProbabilisticScoringParameters) -> u64 {
let max_liquidity_msat = self.max_liquidity_msat();
let min_liquidity_msat = core::cmp::min(self.min_liquidity_msat(), max_liquidity_msat);
if amount_msat <= min_liquidity_msat {

let mut res = if amount_msat <= min_liquidity_msat {
0
} else if amount_msat >= max_liquidity_msat {
// Equivalent to hitting the else clause below with the amount equal to the effective
// capacity and without any certainty on the liquidity upper bound, plus the
// impossibility penalty.
let negative_log10_times_2048 = NEGATIVE_LOG10_UPPER_BOUND * 2048;
self.combined_penalty_msat(amount_msat, negative_log10_times_2048, params)
Self::combined_penalty_msat(amount_msat, negative_log10_times_2048,
params.liquidity_penalty_multiplier_msat,
params.amount_penalty_multiplier_msat)
.saturating_add(params.considered_impossible_penalty_msat)
} else {
let numerator = (max_liquidity_msat - amount_msat).saturating_add(1);
Expand All @@ -696,25 +708,85 @@ impl<L: Deref<Target = u64>, LA: Deref<Target = [u16; 8]>, T: Time, U: Deref<Tar
} else {
let negative_log10_times_2048 =
approx::negative_log10_times_2048(numerator, denominator);
self.combined_penalty_msat(amount_msat, negative_log10_times_2048, params)
Self::combined_penalty_msat(amount_msat, negative_log10_times_2048,
params.liquidity_penalty_multiplier_msat,
params.amount_penalty_multiplier_msat)
}
};

if params.historical_liquidity_penalty_multiplier_msat != 0 ||
params.historical_liquidity_penalty_amount_multiplier_msat != 0 {
// If historical penalties are enabled, calculate the penalty by walking the (sane) set
// of historical liquidity bucket (min, max) combinations and, for each, calculate the
// probability of success given our payment amount, then total the weighted average
// probability of success.
//
// We use a sliding scale to decide which value will be used for a given bucket - the
// first bucket receives zero (ie the lower-bound of the bucket), the last bucket
// receives nine (ie the upper-bound of the bucket). This avoids failing to assign
// penalties to channels at the edges.
//
// If we used the bottom edge of buckets, we'd end up never assigning any penalty at
// all to such a channel when sending less than ~0.19% of the channel's capacity (e.g.
// ~200k sats for a 1 BTC channel!).
//
// If we used the middle of each bucket we'd never assign any penalty at all when
// sending less than 1/16th of a channel's capacity, or 1/8th if we used the top of the
// bucket.
let mut total_valid_points_tracked = 0;
for (min_idx, min_bucket) in self.min_liquidity_offset_history.iter().enumerate() {
for max_bucket in self.max_liquidity_offset_history.iter().take(8 - min_idx) {
total_valid_points_tracked += (*min_bucket as u64) * (*max_bucket as u64);
}
}
if total_valid_points_tracked == 0 {
//XXX: Assign some penalty here, maybe?
return res;
}

let payment_amt_64th_bucket = amount_msat * 64 / self.capacity_msat;
debug_assert!(payment_amt_64th_bucket <= 64);
if payment_amt_64th_bucket > 64 { return res; }

let mut cumulative_success_prob_billions = 0;
for (min_idx, min_bucket) in self.min_liquidity_offset_history.iter().enumerate() {
for (max_idx, max_bucket) in self.max_liquidity_offset_history.iter().enumerate().take(8 - min_idx) {
let bucket_prob_millions = (*min_bucket as u64) * (*max_bucket as u64)
* 1024 * 1024 / total_valid_points_tracked;
let min_64th_bucket = min_idx as u64 * 9;
let max_64th_bucket = (7 - max_idx as u64) * 9 + 1;
if payment_amt_64th_bucket > max_64th_bucket {
// Success probability 0, the payment amount is above the max liquidity
} else if payment_amt_64th_bucket <= min_64th_bucket {
cumulative_success_prob_billions += bucket_prob_millions * 1024;
} else {
//TODO: Make the multiplier here a lookup table to avoid division, probably.
cumulative_success_prob_billions += bucket_prob_millions * (max_64th_bucket - payment_amt_64th_bucket) * 1024 / (max_64th_bucket - min_64th_bucket);
}
}
}
let historical_negative_log10_times_2048 = approx::negative_log10_times_2048(cumulative_success_prob_billions + 1, 1024 * 1024 * 1024);
res = res.saturating_add(Self::combined_penalty_msat(amount_msat,
historical_negative_log10_times_2048, params.historical_liquidity_penalty_multiplier_msat,
params.historical_liquidity_penalty_amount_multiplier_msat));
}

res
}

/// Computes and combines the liquidity and amount penalties.
#[inline(always)]
fn combined_penalty_msat(
&self, amount_msat: u64, negative_log10_times_2048: u64,
params: &ProbabilisticScoringParameters
fn combined_penalty_msat(amount_msat: u64, negative_log10_times_2048: u64,
liquidity_penalty_multiplier_msat: u64, liquidity_penalty_amount_multiplier_msat: u64,
) -> u64 {
let liquidity_penalty_msat = {
// Upper bound the liquidity penalty to ensure some channel is selected.
let multiplier_msat = params.liquidity_penalty_multiplier_msat;
let multiplier_msat = liquidity_penalty_multiplier_msat;
let max_penalty_msat = multiplier_msat.saturating_mul(NEGATIVE_LOG10_UPPER_BOUND);
(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(liquidity_penalty_amount_multiplier_msat)
.saturating_mul(amount_msat) / 2048 / AMOUNT_PENALTY_DIVISOR;

liquidity_penalty_msat.saturating_add(amount_penalty_msat)
Expand Down Expand Up @@ -2337,6 +2409,35 @@ mod tests {
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), u64::max_value());
}

#[test]
fn remembers_historical_failures() {
let logger = TestLogger::new();
let network_graph = network_graph(&logger);
let params = ProbabilisticScoringParameters {
historical_liquidity_penalty_multiplier_msat: 1024,
historical_liquidity_penalty_amount_multiplier_msat: 1024,
..ProbabilisticScoringParameters::zero_penalty()
};
let mut scorer = ProbabilisticScorer::new(params, &network_graph, &logger);
let source = source_node_id();
let target = target_node_id();

let usage = ChannelUsage {
amount_msat: 100,
inflight_htlc_msat: 0,
effective_capacity: EffectiveCapacity::Total { capacity_msat: 1_024, htlc_maximum_msat: Some(1_024) },
};
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 0);

scorer.payment_path_failed(&payment_path_for_amount(1).iter().collect::<Vec<_>>(), 42);
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 2048);

// Even after we tell the scorer we definitely have enough available liquidity, it will
// still remember that there was some failure in the past, and assign a non-0 penalty.
scorer.payment_path_failed(&payment_path_for_amount(1000).iter().collect::<Vec<_>>(), 43);
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 171);
}

#[test]
fn adds_anti_probing_penalty() {
let logger = TestLogger::new();
Expand Down

0 comments on commit 10921f9

Please sign in to comment.