@@ -398,6 +398,11 @@ pub struct ProbabilisticScoringParameters {
398
398
/// Default value: 256 msat
399
399
pub amount_penalty_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.
@@ -563,6 +568,8 @@ impl ProbabilisticScoringParameters {
563
568
liquidity_penalty_multiplier_msat : 0 ,
564
569
liquidity_offset_half_life : Duration :: from_secs ( 3600 ) ,
565
570
amount_penalty_multiplier_msat : 0 ,
571
+ historical_liquidity_penalty_multiplier_msat : 0 ,
572
+ historical_liquidity_penalty_amount_multiplier_msat : 0 ,
566
573
manual_node_penalties : HashMap :: new ( ) ,
567
574
anti_probing_penalty_msat : 0 ,
568
575
considered_impossible_penalty_msat : 0 ,
@@ -586,6 +593,8 @@ impl Default for ProbabilisticScoringParameters {
586
593
liquidity_penalty_multiplier_msat : 40_000 ,
587
594
liquidity_offset_half_life : Duration :: from_secs ( 3600 ) ,
588
595
amount_penalty_multiplier_msat : 256 ,
596
+ historical_liquidity_penalty_multiplier_msat : 0 ,
597
+ historical_liquidity_penalty_amount_multiplier_msat : 0 ,
589
598
manual_node_penalties : HashMap :: new ( ) ,
590
599
anti_probing_penalty_msat : 250 ,
591
600
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
676
685
fn penalty_msat ( & self , amount_msat : u64 , params : & ProbabilisticScoringParameters ) -> u64 {
677
686
let max_liquidity_msat = self . max_liquidity_msat ( ) ;
678
687
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 {
680
690
0
681
691
} else if amount_msat >= max_liquidity_msat {
682
692
// Equivalent to hitting the else clause below with the amount equal to the effective
683
693
// capacity and without any certainty on the liquidity upper bound, plus the
684
694
// impossibility penalty.
685
695
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 )
687
699
. saturating_add ( params. considered_impossible_penalty_msat )
688
700
} else {
689
701
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
696
708
} else {
697
709
let negative_log10_times_2048 =
698
710
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
+ }
700
767
}
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 ) ) ;
701
772
}
773
+
774
+ res
702
775
}
703
776
704
777
/// Computes and combines the liquidity and amount penalties.
705
778
#[ 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 ,
709
781
) -> u64 {
710
782
let liquidity_penalty_msat = {
711
783
// 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;
713
785
let max_penalty_msat = multiplier_msat. saturating_mul ( NEGATIVE_LOG10_UPPER_BOUND ) ;
714
786
( negative_log10_times_2048. saturating_mul ( multiplier_msat) / 2048 ) . min ( max_penalty_msat)
715
787
} ;
716
788
let amount_penalty_msat = negative_log10_times_2048
717
- . saturating_mul ( params . amount_penalty_multiplier_msat )
789
+ . saturating_mul ( liquidity_penalty_amount_multiplier_msat )
718
790
. saturating_mul ( amount_msat) / 2048 / AMOUNT_PENALTY_DIVISOR ;
719
791
720
792
liquidity_penalty_msat. saturating_add ( amount_penalty_msat)
@@ -2337,6 +2409,35 @@ mod tests {
2337
2409
assert_eq ! ( scorer. channel_penalty_msat( 42 , & source, & target, usage) , u64 :: max_value( ) ) ;
2338
2410
}
2339
2411
2412
+ #[ test]
2413
+ fn remembers_historical_failures ( ) {
2414
+ let logger = TestLogger :: new ( ) ;
2415
+ let network_graph = network_graph ( & logger) ;
2416
+ let params = ProbabilisticScoringParameters {
2417
+ historical_liquidity_penalty_multiplier_msat : 1024 ,
2418
+ historical_liquidity_penalty_amount_multiplier_msat : 1024 ,
2419
+ ..ProbabilisticScoringParameters :: zero_penalty ( )
2420
+ } ;
2421
+ let mut scorer = ProbabilisticScorer :: new ( params, & network_graph, & logger) ;
2422
+ let source = source_node_id ( ) ;
2423
+ let target = target_node_id ( ) ;
2424
+
2425
+ let usage = ChannelUsage {
2426
+ amount_msat : 100 ,
2427
+ inflight_htlc_msat : 0 ,
2428
+ effective_capacity : EffectiveCapacity :: Total { capacity_msat : 1_024 , htlc_maximum_msat : Some ( 1_024 ) } ,
2429
+ } ;
2430
+ assert_eq ! ( scorer. channel_penalty_msat( 42 , & source, & target, usage) , 0 ) ;
2431
+
2432
+ scorer. payment_path_failed ( & payment_path_for_amount ( 1 ) . iter ( ) . collect :: < Vec < _ > > ( ) , 42 ) ;
2433
+ assert_eq ! ( scorer. channel_penalty_msat( 42 , & source, & target, usage) , 2048 ) ;
2434
+
2435
+ // Even after we tell the scorer we definitely have enough available liquidity, it will
2436
+ // still remember that there was some failure in the past, and assign a non-0 penalty.
2437
+ scorer. payment_path_failed ( & payment_path_for_amount ( 1000 ) . iter ( ) . collect :: < Vec < _ > > ( ) , 43 ) ;
2438
+ assert_eq ! ( scorer. channel_penalty_msat( 42 , & source, & target, usage) , 171 ) ;
2439
+ }
2440
+
2340
2441
#[ test]
2341
2442
fn adds_anti_probing_penalty ( ) {
2342
2443
let logger = TestLogger :: new ( ) ;
0 commit comments