@@ -398,6 +398,11 @@ pub struct ProbabilisticScoringParameters {
398
398
/// Default value: 256 msat
399
399
pub liquidity_penalty_amount_multiplier_msat : u64 ,
400
400
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
+
401
406
/// Manual penalties used for the given nodes. Allows to set a particular penalty for a given
402
407
/// node. Note that a manual penalty of `u64::max_value()` means the node would not ever be
403
408
/// considered during path finding.
@@ -605,6 +610,8 @@ impl ProbabilisticScoringParameters {
605
610
liquidity_penalty_multiplier_msat : 0 ,
606
611
liquidity_offset_half_life : Duration :: from_secs ( 3600 ) ,
607
612
liquidity_penalty_amount_multiplier_msat : 0 ,
613
+ historical_liquidity_penalty_multiplier_msat : 0 ,
614
+ historical_liquidity_penalty_amount_multiplier_msat : 0 ,
608
615
manual_node_penalties : HashMap :: new ( ) ,
609
616
anti_probing_penalty_msat : 0 ,
610
617
considered_impossible_penalty_msat : 0 ,
@@ -628,6 +635,8 @@ impl Default for ProbabilisticScoringParameters {
628
635
liquidity_penalty_multiplier_msat : 40_000 ,
629
636
liquidity_offset_half_life : Duration :: from_secs ( 3600 ) ,
630
637
liquidity_penalty_amount_multiplier_msat : 256 ,
638
+ historical_liquidity_penalty_multiplier_msat : 0 ,
639
+ historical_liquidity_penalty_amount_multiplier_msat : 0 ,
631
640
manual_node_penalties : HashMap :: new ( ) ,
632
641
anti_probing_penalty_msat : 250 ,
633
642
considered_impossible_penalty_msat : 1_0000_0000_000 ,
@@ -718,14 +727,17 @@ impl<L: Deref<Target = u64>, BRT: Deref<Target = HistoricalBucketRangeTracker>,
718
727
fn penalty_msat ( & self , amount_msat : u64 , params : & ProbabilisticScoringParameters ) -> u64 {
719
728
let max_liquidity_msat = self . max_liquidity_msat ( ) ;
720
729
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 {
722
732
0
723
733
} else if amount_msat >= max_liquidity_msat {
724
734
// Equivalent to hitting the else clause below with the amount equal to the effective
725
735
// capacity and without any certainty on the liquidity upper bound, plus the
726
736
// impossibility penalty.
727
737
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 )
729
741
. saturating_add ( params. considered_impossible_penalty_msat )
730
742
} else {
731
743
let numerator = ( max_liquidity_msat - amount_msat) . saturating_add ( 1 ) ;
@@ -738,25 +750,85 @@ impl<L: Deref<Target = u64>, BRT: Deref<Target = HistoricalBucketRangeTracker>,
738
750
} else {
739
751
let negative_log10_times_2048 =
740
752
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
+ }
742
809
}
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 ) ) ;
743
814
}
815
+
816
+ res
744
817
}
745
818
746
819
/// Computes the liquidity penalty from the penalty multipliers.
747
820
#[ 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 ,
751
823
) -> u64 {
752
824
let liquidity_penalty_msat = {
753
825
// 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;
755
827
let max_penalty_msat = multiplier_msat. saturating_mul ( NEGATIVE_LOG10_UPPER_BOUND ) ;
756
828
( negative_log10_times_2048. saturating_mul ( multiplier_msat) / 2048 ) . min ( max_penalty_msat)
757
829
} ;
758
830
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)
760
832
. saturating_mul ( amount_msat) / 2048 / AMOUNT_PENALTY_DIVISOR ;
761
833
762
834
liquidity_penalty_msat. saturating_add ( amount_penalty_msat)
@@ -2358,6 +2430,35 @@ mod tests {
2358
2430
assert_eq ! ( scorer. channel_penalty_msat( 42 , & source, & target, usage) , u64 :: max_value( ) ) ;
2359
2431
}
2360
2432
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
+
2361
2462
#[ test]
2362
2463
fn adds_anti_probing_penalty ( ) {
2363
2464
let logger = TestLogger :: new ( ) ;
0 commit comments