Skip to content

Commit fca8471

Browse files
committed
Calculate a new penalty based on historical channel liquidity range
XXX: Write some text here
1 parent f9533d8 commit fca8471

File tree

1 file changed

+109
-8
lines changed

1 file changed

+109
-8
lines changed

lightning/src/routing/scoring.rs

+109-8
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,11 @@ pub struct ProbabilisticScoringParameters {
398398
/// Default value: 256 msat
399399
pub liquidity_penalty_amount_multiplier_msat: u64,
400400

401+
/// XXX: Write docs
402+
pub historical_liquidity_penalty_multiplier_msat: u64,
403+
/// XXX: Write docs
404+
pub historical_liquidity_penalty_amount_multiplier_msat: u64,
405+
401406
/// Manual penalties used for the given nodes. Allows to set a particular penalty for a given
402407
/// node. Note that a manual penalty of `u64::max_value()` means the node would not ever be
403408
/// considered during path finding.
@@ -605,6 +610,8 @@ impl ProbabilisticScoringParameters {
605610
liquidity_penalty_multiplier_msat: 0,
606611
liquidity_offset_half_life: Duration::from_secs(3600),
607612
liquidity_penalty_amount_multiplier_msat: 0,
613+
historical_liquidity_penalty_multiplier_msat: 0,
614+
historical_liquidity_penalty_amount_multiplier_msat: 0,
608615
manual_node_penalties: HashMap::new(),
609616
anti_probing_penalty_msat: 0,
610617
considered_impossible_penalty_msat: 0,
@@ -628,6 +635,8 @@ impl Default for ProbabilisticScoringParameters {
628635
liquidity_penalty_multiplier_msat: 40_000,
629636
liquidity_offset_half_life: Duration::from_secs(3600),
630637
liquidity_penalty_amount_multiplier_msat: 256,
638+
historical_liquidity_penalty_multiplier_msat: 0,
639+
historical_liquidity_penalty_amount_multiplier_msat: 0,
631640
manual_node_penalties: HashMap::new(),
632641
anti_probing_penalty_msat: 250,
633642
considered_impossible_penalty_msat: 1_0000_0000_000,
@@ -718,14 +727,17 @@ impl<L: Deref<Target = u64>, BRT: Deref<Target = HistoricalBucketRangeTracker>,
718727
fn penalty_msat(&self, amount_msat: u64, params: &ProbabilisticScoringParameters) -> u64 {
719728
let max_liquidity_msat = self.max_liquidity_msat();
720729
let min_liquidity_msat = core::cmp::min(self.min_liquidity_msat(), max_liquidity_msat);
721-
if amount_msat <= min_liquidity_msat {
730+
731+
let mut res = if amount_msat <= min_liquidity_msat {
722732
0
723733
} else if amount_msat >= max_liquidity_msat {
724734
// Equivalent to hitting the else clause below with the amount equal to the effective
725735
// capacity and without any certainty on the liquidity upper bound, plus the
726736
// impossibility penalty.
727737
let negative_log10_times_2048 = NEGATIVE_LOG10_UPPER_BOUND * 2048;
728-
self.combined_penalty_msat(amount_msat, negative_log10_times_2048, params)
738+
Self::combined_penalty_msat(amount_msat, negative_log10_times_2048,
739+
params.liquidity_penalty_multiplier_msat,
740+
params.amount_penalty_multiplier_msat)
729741
.saturating_add(params.considered_impossible_penalty_msat)
730742
} else {
731743
let numerator = (max_liquidity_msat - amount_msat).saturating_add(1);
@@ -738,25 +750,85 @@ impl<L: Deref<Target = u64>, BRT: Deref<Target = HistoricalBucketRangeTracker>,
738750
} else {
739751
let negative_log10_times_2048 =
740752
approx::negative_log10_times_2048(numerator, denominator);
741-
self.combined_penalty_msat(amount_msat, negative_log10_times_2048, params)
753+
Self::combined_penalty_msat(amount_msat, negative_log10_times_2048,
754+
params.liquidity_penalty_multiplier_msat,
755+
params.amount_penalty_multiplier_msat)
756+
}
757+
};
758+
759+
if params.historical_liquidity_penalty_multiplier_msat != 0 ||
760+
params.historical_liquidity_penalty_amount_multiplier_msat != 0 {
761+
// If historical penalties are enabled, calculate the penalty by walking the (sane) set
762+
// of historical liquidity bucket (min, max) combinations and, for each, calculate the
763+
// probability of success given our payment amount, then total the weighted average
764+
// probability of success.
765+
//
766+
// We use a sliding scale to decide which value will be used for a given bucket - the
767+
// first bucket receives zero (ie the lower-bound of the bucket), the last bucket
768+
// receives nine (ie the upper-bound of the bucket). This avoids failing to assign
769+
// penalties to channels at the edges.
770+
//
771+
// If we used the bottom edge of buckets, we'd end up never assigning any penalty at
772+
// all to such a channel when sending less than ~0.19% of the channel's capacity (e.g.
773+
// ~200k sats for a 1 BTC channel!).
774+
//
775+
// If we used the middle of each bucket we'd never assign any penalty at all when
776+
// sending less than 1/16th of a channel's capacity, or 1/8th if we used the top of the
777+
// bucket.
778+
let mut total_valid_points_tracked = 0;
779+
for (min_idx, min_bucket) in self.min_liquidity_offset_history.iter().enumerate() {
780+
for max_bucket in self.max_liquidity_offset_history.iter().take(8 - min_idx) {
781+
total_valid_points_tracked += (*min_bucket as u64) * (*max_bucket as u64);
782+
}
783+
}
784+
if total_valid_points_tracked == 0 {
785+
//XXX: Assign some penalty here, maybe?
786+
return res;
787+
}
788+
789+
let payment_amt_64th_bucket = amount_msat * 64 / self.capacity_msat;
790+
debug_assert!(payment_amt_64th_bucket <= 64);
791+
if payment_amt_64th_bucket > 64 { return res; }
792+
793+
let mut cumulative_success_prob_billions = 0;
794+
for (min_idx, min_bucket) in self.min_liquidity_offset_history.iter().enumerate() {
795+
for (max_idx, max_bucket) in self.max_liquidity_offset_history.iter().enumerate().take(8 - min_idx) {
796+
let bucket_prob_millions = (*min_bucket as u64) * (*max_bucket as u64)
797+
* 1024 * 1024 / total_valid_points_tracked;
798+
let min_64th_bucket = min_idx as u64 * 9;
799+
let max_64th_bucket = (7 - max_idx as u64) * 9 + 1;
800+
if payment_amt_64th_bucket > max_64th_bucket {
801+
// Success probability 0, the payment amount is above the max liquidity
802+
} else if payment_amt_64th_bucket <= min_64th_bucket {
803+
cumulative_success_prob_billions += bucket_prob_millions * 1024;
804+
} else {
805+
//TODO: Make the multiplier here a lookup table to avoid division, probably.
806+
cumulative_success_prob_billions += bucket_prob_millions * (max_64th_bucket - payment_amt_64th_bucket) * 1024 / (max_64th_bucket - min_64th_bucket);
807+
}
808+
}
742809
}
810+
let historical_negative_log10_times_2048 = approx::negative_log10_times_2048(cumulative_success_prob_billions + 1, 1024 * 1024 * 1024);
811+
res = res.saturating_add(Self::combined_penalty_msat(amount_msat,
812+
historical_negative_log10_times_2048, params.historical_liquidity_penalty_multiplier_msat,
813+
params.historical_liquidity_penalty_amount_multiplier_msat));
743814
}
815+
816+
res
744817
}
745818

746819
/// Computes the liquidity penalty from the penalty multipliers.
747820
#[inline(always)]
748-
fn combined_penalty_msat(
749-
&self, amount_msat: u64, negative_log10_times_2048: u64,
750-
params: &ProbabilisticScoringParameters
821+
fn combined_penalty_msat(amount_msat: u64, negative_log10_times_2048: u64,
822+
liquidity_penalty_multiplier_msat: u64, liquidity_penalty_amount_multiplier_msat: u64,
751823
) -> u64 {
752824
let liquidity_penalty_msat = {
753825
// Upper bound the liquidity penalty to ensure some channel is selected.
754-
let multiplier_msat = params.liquidity_penalty_multiplier_msat;
826+
let multiplier_msat = liquidity_penalty_multiplier_msat;
755827
let max_penalty_msat = multiplier_msat.saturating_mul(NEGATIVE_LOG10_UPPER_BOUND);
756828
(negative_log10_times_2048.saturating_mul(multiplier_msat) / 2048).min(max_penalty_msat)
757829
};
758830
let amount_penalty_msat = negative_log10_times_2048
759-
.saturating_mul(params.liquidity_penalty_amount_multiplier_msat)
831+
.saturating_mul(liquidity_penalty_amount_multiplier_msat)
760832
.saturating_mul(amount_msat) / 2048 / AMOUNT_PENALTY_DIVISOR;
761833

762834
liquidity_penalty_msat.saturating_add(amount_penalty_msat)
@@ -2358,6 +2430,35 @@ mod tests {
23582430
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), u64::max_value());
23592431
}
23602432

2433+
#[test]
2434+
fn remembers_historical_failures() {
2435+
let logger = TestLogger::new();
2436+
let network_graph = network_graph(&logger);
2437+
let params = ProbabilisticScoringParameters {
2438+
historical_liquidity_penalty_multiplier_msat: 1024,
2439+
historical_liquidity_penalty_amount_multiplier_msat: 1024,
2440+
..ProbabilisticScoringParameters::zero_penalty()
2441+
};
2442+
let mut scorer = ProbabilisticScorer::new(params, &network_graph, &logger);
2443+
let source = source_node_id();
2444+
let target = target_node_id();
2445+
2446+
let usage = ChannelUsage {
2447+
amount_msat: 100,
2448+
inflight_htlc_msat: 0,
2449+
effective_capacity: EffectiveCapacity::Total { capacity_msat: 1_024, htlc_maximum_msat: Some(1_024) },
2450+
};
2451+
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 0);
2452+
2453+
scorer.payment_path_failed(&payment_path_for_amount(1).iter().collect::<Vec<_>>(), 42);
2454+
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 2048);
2455+
2456+
// Even after we tell the scorer we definitely have enough available liquidity, it will
2457+
// still remember that there was some failure in the past, and assign a non-0 penalty.
2458+
scorer.payment_path_failed(&payment_path_for_amount(1000).iter().collect::<Vec<_>>(), 43);
2459+
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 171);
2460+
}
2461+
23612462
#[test]
23622463
fn adds_anti_probing_penalty() {
23632464
let logger = TestLogger::new();

0 commit comments

Comments
 (0)