Skip to content

Commit ce5bc08

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

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 amount_penalty_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.
@@ -563,6 +568,8 @@ impl ProbabilisticScoringParameters {
563568
liquidity_penalty_multiplier_msat: 0,
564569
liquidity_offset_half_life: Duration::from_secs(3600),
565570
amount_penalty_multiplier_msat: 0,
571+
historical_liquidity_penalty_multiplier_msat: 0,
572+
historical_liquidity_penalty_amount_multiplier_msat: 0,
566573
manual_node_penalties: HashMap::new(),
567574
anti_probing_penalty_msat: 0,
568575
considered_impossible_penalty_msat: 0,
@@ -586,6 +593,8 @@ impl Default for ProbabilisticScoringParameters {
586593
liquidity_penalty_multiplier_msat: 40_000,
587594
liquidity_offset_half_life: Duration::from_secs(3600),
588595
amount_penalty_multiplier_msat: 256,
596+
historical_liquidity_penalty_multiplier_msat: 0,
597+
historical_liquidity_penalty_amount_multiplier_msat: 0,
589598
manual_node_penalties: HashMap::new(),
590599
anti_probing_penalty_msat: 250,
591600
considered_impossible_penalty_msat: 1_0000_0000_000,
@@ -676,14 +685,17 @@ impl<L: Deref<Target = u64>, LA: Deref<Target = [u16; 8]>, T: Time, U: Deref<Tar
676685
fn penalty_msat(&self, amount_msat: u64, params: &ProbabilisticScoringParameters) -> u64 {
677686
let max_liquidity_msat = self.max_liquidity_msat();
678687
let min_liquidity_msat = core::cmp::min(self.min_liquidity_msat(), max_liquidity_msat);
679-
if amount_msat <= min_liquidity_msat {
688+
689+
let mut res = if amount_msat <= min_liquidity_msat {
680690
0
681691
} else if amount_msat >= max_liquidity_msat {
682692
// Equivalent to hitting the else clause below with the amount equal to the effective
683693
// capacity and without any certainty on the liquidity upper bound, plus the
684694
// impossibility penalty.
685695
let negative_log10_times_2048 = NEGATIVE_LOG10_UPPER_BOUND * 2048;
686-
self.combined_penalty_msat(amount_msat, negative_log10_times_2048, params)
696+
Self::combined_penalty_msat(amount_msat, negative_log10_times_2048,
697+
params.liquidity_penalty_multiplier_msat,
698+
params.amount_penalty_multiplier_msat)
687699
.saturating_add(params.considered_impossible_penalty_msat)
688700
} else {
689701
let numerator = (max_liquidity_msat - amount_msat).saturating_add(1);
@@ -696,25 +708,85 @@ impl<L: Deref<Target = u64>, LA: Deref<Target = [u16; 8]>, T: Time, U: Deref<Tar
696708
} else {
697709
let negative_log10_times_2048 =
698710
approx::negative_log10_times_2048(numerator, denominator);
699-
self.combined_penalty_msat(amount_msat, negative_log10_times_2048, params)
711+
Self::combined_penalty_msat(amount_msat, negative_log10_times_2048,
712+
params.liquidity_penalty_multiplier_msat,
713+
params.amount_penalty_multiplier_msat)
714+
}
715+
};
716+
717+
if params.historical_liquidity_penalty_multiplier_msat != 0 ||
718+
params.historical_liquidity_penalty_amount_multiplier_msat != 0 {
719+
// If historical penalties are enabled, calculate the penalty by walking the (sane) set
720+
// of historical liquidity bucket (min, max) combinations and, for each, calculate the
721+
// probability of success given our payment amount, then total the weighted average
722+
// probability of success.
723+
//
724+
// We use a sliding scale to decide which value will be used for a given bucket - the
725+
// first bucket receives zero (ie the lower-bound of the bucket), the last bucket
726+
// receives nine (ie the upper-bound of the bucket). This avoids failing to assign
727+
// penalties to channels at the edges.
728+
//
729+
// If we used the bottom edge of buckets, we'd end up never assigning any penalty at
730+
// all to such a channel when sending less than ~0.19% of the channel's capacity (e.g.
731+
// ~200k sats for a 1 BTC channel!).
732+
//
733+
// If we used the middle of each bucket we'd never assign any penalty at all when
734+
// sending less than 1/16th of a channel's capacity, or 1/8th if we used the top of the
735+
// bucket.
736+
let mut total_valid_points_tracked = 0;
737+
for (min_idx, min_bucket) in self.min_liquidity_offset_history.iter().enumerate() {
738+
for max_bucket in self.max_liquidity_offset_history.iter().take(8 - min_idx) {
739+
total_valid_points_tracked += (*min_bucket as u64) * (*max_bucket as u64);
740+
}
741+
}
742+
if total_valid_points_tracked == 0 {
743+
//XXX: Assign some penalty here, maybe?
744+
return res;
745+
}
746+
747+
let payment_amt_64th_bucket = amount_msat * 64 / self.capacity_msat;
748+
debug_assert!(payment_amt_64th_bucket <= 64);
749+
if payment_amt_64th_bucket > 64 { return res; }
750+
751+
let mut cumulative_success_prob_billions = 0;
752+
for (min_idx, min_bucket) in self.min_liquidity_offset_history.iter().enumerate() {
753+
for (max_idx, max_bucket) in self.max_liquidity_offset_history.iter().enumerate().take(8 - min_idx) {
754+
let bucket_prob_millions = (*min_bucket as u64) * (*max_bucket as u64)
755+
* 1024 * 1024 / total_valid_points_tracked;
756+
let min_64th_bucket = min_idx as u64 * 9;
757+
let max_64th_bucket = (7 - max_idx as u64) * 9 + 1;
758+
if payment_amt_64th_bucket > max_64th_bucket {
759+
// Success probability 0, the payment amount is above the max liquidity
760+
} else if payment_amt_64th_bucket <= min_64th_bucket {
761+
cumulative_success_prob_billions += bucket_prob_millions * 1024;
762+
} else {
763+
//TODO: Make the multiplier here a lookup table to avoid division, probably.
764+
cumulative_success_prob_billions += bucket_prob_millions * (max_64th_bucket - payment_amt_64th_bucket) * 1024 / (max_64th_bucket - min_64th_bucket);
765+
}
766+
}
700767
}
768+
let historical_negative_log10_times_2048 = approx::negative_log10_times_2048(cumulative_success_prob_billions + 1, 1024 * 1024 * 1024);
769+
res = res.saturating_add(Self::combined_penalty_msat(amount_msat,
770+
historical_negative_log10_times_2048, params.historical_liquidity_penalty_multiplier_msat,
771+
params.historical_liquidity_penalty_amount_multiplier_msat));
701772
}
773+
774+
res
702775
}
703776

704777
/// Computes and combines the liquidity and amount penalties.
705778
#[inline(always)]
706-
fn combined_penalty_msat(
707-
&self, amount_msat: u64, negative_log10_times_2048: u64,
708-
params: &ProbabilisticScoringParameters
779+
fn combined_penalty_msat(amount_msat: u64, negative_log10_times_2048: u64,
780+
liquidity_penalty_multiplier_msat: u64, liquidity_penalty_amount_multiplier_msat: u64,
709781
) -> u64 {
710782
let liquidity_penalty_msat = {
711783
// Upper bound the liquidity penalty to ensure some channel is selected.
712-
let multiplier_msat = params.liquidity_penalty_multiplier_msat;
784+
let multiplier_msat = liquidity_penalty_multiplier_msat;
713785
let max_penalty_msat = multiplier_msat.saturating_mul(NEGATIVE_LOG10_UPPER_BOUND);
714786
(negative_log10_times_2048.saturating_mul(multiplier_msat) / 2048).min(max_penalty_msat)
715787
};
716788
let amount_penalty_msat = negative_log10_times_2048
717-
.saturating_mul(params.amount_penalty_multiplier_msat)
789+
.saturating_mul(liquidity_penalty_amount_multiplier_msat)
718790
.saturating_mul(amount_msat) / 2048 / AMOUNT_PENALTY_DIVISOR;
719791

720792
liquidity_penalty_msat.saturating_add(amount_penalty_msat)
@@ -2335,6 +2407,35 @@ mod tests {
23352407
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), u64::max_value());
23362408
}
23372409

2410+
#[test]
2411+
fn remembers_historical_failures() {
2412+
let logger = TestLogger::new();
2413+
let network_graph = network_graph(&logger);
2414+
let params = ProbabilisticScoringParameters {
2415+
historical_liquidity_penalty_multiplier_msat: 1024,
2416+
historical_liquidity_penalty_amount_multiplier_msat: 1024,
2417+
..ProbabilisticScoringParameters::zero_penalty()
2418+
};
2419+
let mut scorer = ProbabilisticScorer::new(params, &network_graph, &logger);
2420+
let source = source_node_id();
2421+
let target = target_node_id();
2422+
2423+
let usage = ChannelUsage {
2424+
amount_msat: 100,
2425+
inflight_htlc_msat: 0,
2426+
effective_capacity: EffectiveCapacity::Total { capacity_msat: 1_024, htlc_maximum_msat: Some(1_024) },
2427+
};
2428+
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 0);
2429+
2430+
scorer.payment_path_failed(&payment_path_for_amount(1).iter().collect::<Vec<_>>(), 42);
2431+
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 2048);
2432+
2433+
// Even after we tell the scorer we definitely have enough available liquidity, it will
2434+
// still remember that there was some failure in the past, and assign a non-0 penalty.
2435+
scorer.payment_path_failed(&payment_path_for_amount(1000).iter().collect::<Vec<_>>(), 43);
2436+
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 171);
2437+
}
2438+
23382439
#[test]
23392440
fn adds_anti_probing_penalty() {
23402441
let logger = TestLogger::new();

0 commit comments

Comments
 (0)