diff --git a/lightning/src/routing/scoring.rs b/lightning/src/routing/scoring.rs index bd79d6e2057..487bebbc182 100644 --- a/lightning/src/routing/scoring.rs +++ b/lightning/src/routing/scoring.rs @@ -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. @@ -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, @@ -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, @@ -676,14 +685,17 @@ impl, LA: Deref, T: Time, U: Deref 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); @@ -696,25 +708,85 @@ impl, LA: Deref, T: Time, U: Deref 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) @@ -2335,6 +2407,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::>(), 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::>(), 43); + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 171); + } + #[test] fn adds_anti_probing_penalty() { let logger = TestLogger::new();