@@ -350,7 +350,8 @@ pub struct ProbabilisticScoringParameters {
350
350
pub base_penalty_amount_multiplier_msat : u64 ,
351
351
352
352
/// A multiplier used in conjunction with the negative `log10` of the channel's success
353
- /// probability for a payment to determine the liquidity penalty.
353
+ /// probability for a payment, as determined by our latest estimates of the channel's
354
+ /// liquidity, to determine the liquidity penalty.
354
355
///
355
356
/// The penalty is based in part on the knowledge learned from prior successful and unsuccessful
356
357
/// payments. This knowledge is decayed over time based on [`liquidity_offset_half_life`]. The
@@ -359,7 +360,7 @@ pub struct ProbabilisticScoringParameters {
359
360
/// uncertainty bounds of the channel liquidity balance. Amounts above the upper bound will
360
361
/// result in a `u64::max_value` penalty, however.
361
362
///
362
- /// Default value: 40 ,000 msat
363
+ /// Default value: 30 ,000 msat
363
364
///
364
365
/// [`liquidity_offset_half_life`]: Self::liquidity_offset_half_life
365
366
pub liquidity_penalty_multiplier_msat : u64 ,
@@ -380,7 +381,8 @@ pub struct ProbabilisticScoringParameters {
380
381
pub liquidity_offset_half_life : Duration ,
381
382
382
383
/// A multiplier used in conjunction with a payment amount and the negative `log10` of the
383
- /// channel's success probability for the payment to determine the amount penalty.
384
+ /// channel's success probability for the payment, as determined by our latest estimates of the
385
+ /// channel's liquidity, to determine the amount penalty.
384
386
///
385
387
/// The purpose of the amount penalty is to avoid having fees dominate the channel cost (i.e.,
386
388
/// fees plus penalty) for large payments. The penalty is computed as the product of this
@@ -395,9 +397,45 @@ pub struct ProbabilisticScoringParameters {
395
397
/// probabilities, the multiplier will have a decreasing effect as the negative `log10` will
396
398
/// fall below `1`.
397
399
///
398
- /// Default value: 256 msat
400
+ /// Default value: 192 msat
399
401
pub liquidity_penalty_amount_multiplier_msat : u64 ,
400
402
403
+ /// A multiplier used in conjunction with the negative `log10` of the channel's success
404
+ /// probability for the payment, as determined based on the history of our estimates of the
405
+ /// channel's available liquidity, to determine a penalty.
406
+ ///
407
+ /// This penalty is similar to [`liquidity_penalty_multiplier_msat`], however, instead of using
408
+ /// only our latest estimate for the current liquidity available in the channel, it estimates
409
+ /// success probability based on the estimated liquidity available in the channel through
410
+ /// history. Specifically, every time we update our liquidity bounds on a given channel, we
411
+ /// track which of several buckets those bounds fall into, exponentially decaying the
412
+ /// probability of each bucket as new samples are added.
413
+ ///
414
+ /// Default value: 10,000 msat
415
+ ///
416
+ /// [`liquidity_penalty_multiplier_msat`]: Self::liquidity_penalty_multiplier_msat
417
+ pub historical_liquidity_penalty_multiplier_msat : u64 ,
418
+
419
+ /// A multiplier used in conjunction with the payment amount and the negative `log10` of the
420
+ /// channel's success probability for the payment, as determined based on the history of our
421
+ /// estimates of the channel's available liquidity, to determine a penalty.
422
+ ///
423
+ /// The purpose of the amount penalty is to avoid having fees dominate the channel cost for
424
+ /// large payments. The penalty is computed as the product of this multiplier and the `2^20`ths
425
+ /// of the payment amount, weighted by the negative `log10` of the success probability.
426
+ ///
427
+ /// This penalty is similar to [`liquidity_penalty_amount_multiplier_msat`], however, instead
428
+ /// of using only our latest estimate for the current liquidity available in the channel, it
429
+ /// estimates success probability based on the estimated liquidity available in the channel
430
+ /// through history. Specifically, every time we update our liquidity bounds on a given
431
+ /// channel, we track which of several buckets those bounds fall into, exponentially decaying
432
+ /// the probability of each bucket as new samples are added.
433
+ ///
434
+ /// Default value: 64 msat
435
+ ///
436
+ /// [`liquidity_penalty_amount_multiplier_msat`]: Self::liquidity_penalty_amount_multiplier_msat
437
+ pub historical_liquidity_penalty_amount_multiplier_msat : u64 ,
438
+
401
439
/// Manual penalties used for the given nodes. Allows to set a particular penalty for a given
402
440
/// node. Note that a manual penalty of `u64::max_value()` means the node would not ever be
403
441
/// considered during path finding.
@@ -605,6 +643,8 @@ impl ProbabilisticScoringParameters {
605
643
liquidity_penalty_multiplier_msat : 0 ,
606
644
liquidity_offset_half_life : Duration :: from_secs ( 3600 ) ,
607
645
liquidity_penalty_amount_multiplier_msat : 0 ,
646
+ historical_liquidity_penalty_multiplier_msat : 0 ,
647
+ historical_liquidity_penalty_amount_multiplier_msat : 0 ,
608
648
manual_node_penalties : HashMap :: new ( ) ,
609
649
anti_probing_penalty_msat : 0 ,
610
650
considered_impossible_penalty_msat : 0 ,
@@ -625,9 +665,11 @@ impl Default for ProbabilisticScoringParameters {
625
665
Self {
626
666
base_penalty_msat : 500 ,
627
667
base_penalty_amount_multiplier_msat : 8192 ,
628
- liquidity_penalty_multiplier_msat : 40_000 ,
668
+ liquidity_penalty_multiplier_msat : 30_000 ,
629
669
liquidity_offset_half_life : Duration :: from_secs ( 3600 ) ,
630
- liquidity_penalty_amount_multiplier_msat : 256 ,
670
+ liquidity_penalty_amount_multiplier_msat : 192 ,
671
+ historical_liquidity_penalty_multiplier_msat : 10_000 ,
672
+ historical_liquidity_penalty_amount_multiplier_msat : 64 ,
631
673
manual_node_penalties : HashMap :: new ( ) ,
632
674
anti_probing_penalty_msat : 250 ,
633
675
considered_impossible_penalty_msat : 1_0000_0000_000 ,
@@ -718,14 +760,17 @@ impl<L: Deref<Target = u64>, BRT: Deref<Target = HistoricalBucketRangeTracker>,
718
760
fn penalty_msat ( & self , amount_msat : u64 , params : & ProbabilisticScoringParameters ) -> u64 {
719
761
let max_liquidity_msat = self . max_liquidity_msat ( ) ;
720
762
let min_liquidity_msat = core:: cmp:: min ( self . min_liquidity_msat ( ) , max_liquidity_msat) ;
721
- if amount_msat <= min_liquidity_msat {
763
+
764
+ let mut res = if amount_msat <= min_liquidity_msat {
722
765
0
723
766
} else if amount_msat >= max_liquidity_msat {
724
767
// Equivalent to hitting the else clause below with the amount equal to the effective
725
768
// capacity and without any certainty on the liquidity upper bound, plus the
726
769
// impossibility penalty.
727
770
let negative_log10_times_2048 = NEGATIVE_LOG10_UPPER_BOUND * 2048 ;
728
- self . combined_penalty_msat ( amount_msat, negative_log10_times_2048, params)
771
+ Self :: combined_penalty_msat ( amount_msat, negative_log10_times_2048,
772
+ params. liquidity_penalty_multiplier_msat ,
773
+ params. liquidity_penalty_amount_multiplier_msat )
729
774
. saturating_add ( params. considered_impossible_penalty_msat )
730
775
} else {
731
776
let numerator = ( max_liquidity_msat - amount_msat) . saturating_add ( 1 ) ;
@@ -738,25 +783,96 @@ impl<L: Deref<Target = u64>, BRT: Deref<Target = HistoricalBucketRangeTracker>,
738
783
} else {
739
784
let negative_log10_times_2048 =
740
785
approx:: negative_log10_times_2048 ( numerator, denominator) ;
741
- self . combined_penalty_msat ( amount_msat, negative_log10_times_2048, params)
786
+ Self :: combined_penalty_msat ( amount_msat, negative_log10_times_2048,
787
+ params. liquidity_penalty_multiplier_msat ,
788
+ params. liquidity_penalty_amount_multiplier_msat )
789
+ }
790
+ } ;
791
+
792
+ if params. historical_liquidity_penalty_multiplier_msat != 0 ||
793
+ params. historical_liquidity_penalty_amount_multiplier_msat != 0 {
794
+ // If historical penalties are enabled, calculate the penalty by walking the set of
795
+ // historical liquidity bucket (min, max) combinations (where min_idx < max_idx)
796
+ // and, for each, calculate the probability of success given our payment amount, then
797
+ // total the weighted average probability of success.
798
+ //
799
+ // We use a sliding scale to decide which point within a given bucket will be compared
800
+ // to the amount being sent - for lower-bounds, the amount being sent is compared to
801
+ // the lower edge of the first bucket (i.e. zero), but compared to the upper 7/8ths of
802
+ // the last bucket (i.e. 9 times the index, or 63), with each bucket in between
803
+ // increasing the comparison point by 1/64th. For upper-bounds, the same applies,
804
+ // however with an offset of 1/64th (i.e. starting at one and ending at 64). This
805
+ // avoids failing to assign penalties to channels at the edges.
806
+ //
807
+ // If we used the bottom edge of buckets, we'd end up never assigning any penalty at
808
+ // all to such a channel when sending less than ~0.19% of the channel's capacity (e.g.
809
+ // ~200k sats for a 1 BTC channel!).
810
+ //
811
+ // If we used the middle of each bucket we'd never assign any penalty at all when
812
+ // sending less than 1/16th of a channel's capacity, or 1/8th if we used the top of the
813
+ // bucket.
814
+ let mut total_valid_points_tracked = 0 ;
815
+ for ( min_idx, min_bucket) in self . min_liquidity_offset_history . buckets . iter ( ) . enumerate ( ) {
816
+ for max_bucket in self . max_liquidity_offset_history . buckets . iter ( ) . take ( 8 - min_idx) {
817
+ total_valid_points_tracked += ( * min_bucket as u64 ) * ( * max_bucket as u64 ) ;
818
+ }
819
+ }
820
+ if total_valid_points_tracked == 0 {
821
+ // If we don't have any valid points, redo the non-historical calculation with no
822
+ // liquidity bounds tracked and the historical penalty multipliers.
823
+ let max_capacity = self . capacity_msat . saturating_sub ( amount_msat) . saturating_add ( 1 ) ;
824
+ let negative_log10_times_2048 =
825
+ approx:: negative_log10_times_2048 ( max_capacity, self . capacity_msat . saturating_add ( 1 ) ) ;
826
+ res = res. saturating_add ( Self :: combined_penalty_msat ( amount_msat, negative_log10_times_2048,
827
+ params. historical_liquidity_penalty_multiplier_msat ,
828
+ params. historical_liquidity_penalty_amount_multiplier_msat ) ) ;
829
+ return res;
742
830
}
831
+
832
+ let payment_amt_64th_bucket = amount_msat * 64 / self . capacity_msat ;
833
+ debug_assert ! ( payment_amt_64th_bucket <= 64 ) ;
834
+ if payment_amt_64th_bucket > 64 { return res; }
835
+
836
+ let mut cumulative_success_prob_times_billion = 0 ;
837
+ for ( min_idx, min_bucket) in self . min_liquidity_offset_history . buckets . iter ( ) . enumerate ( ) {
838
+ for ( max_idx, max_bucket) in self . max_liquidity_offset_history . buckets . iter ( ) . enumerate ( ) . take ( 8 - min_idx) {
839
+ let bucket_prob_times_million = ( * min_bucket as u64 ) * ( * max_bucket as u64 )
840
+ * 1024 * 1024 / total_valid_points_tracked;
841
+ let min_64th_bucket = min_idx as u64 * 9 ;
842
+ let max_64th_bucket = ( 7 - max_idx as u64 ) * 9 + 1 ;
843
+ if payment_amt_64th_bucket > max_64th_bucket {
844
+ // Success probability 0, the payment amount is above the max liquidity
845
+ } else if payment_amt_64th_bucket <= min_64th_bucket {
846
+ cumulative_success_prob_times_billion += bucket_prob_times_million * 1024 ;
847
+ } else {
848
+ cumulative_success_prob_times_billion += bucket_prob_times_million *
849
+ ( max_64th_bucket - payment_amt_64th_bucket) * 1024 /
850
+ ( max_64th_bucket - min_64th_bucket) ;
851
+ }
852
+ }
853
+ }
854
+ let historical_negative_log10_times_2048 = approx:: negative_log10_times_2048 ( cumulative_success_prob_times_billion + 1 , 1024 * 1024 * 1024 ) ;
855
+ res = res. saturating_add ( Self :: combined_penalty_msat ( amount_msat,
856
+ historical_negative_log10_times_2048, params. historical_liquidity_penalty_multiplier_msat ,
857
+ params. historical_liquidity_penalty_amount_multiplier_msat ) ) ;
743
858
}
859
+
860
+ res
744
861
}
745
862
746
863
/// Computes the liquidity penalty from the penalty multipliers.
747
864
#[ inline( always) ]
748
- fn combined_penalty_msat (
749
- & self , amount_msat : u64 , negative_log10_times_2048 : u64 ,
750
- params : & ProbabilisticScoringParameters
865
+ fn combined_penalty_msat ( amount_msat : u64 , negative_log10_times_2048 : u64 ,
866
+ liquidity_penalty_multiplier_msat : u64 , liquidity_penalty_amount_multiplier_msat : u64 ,
751
867
) -> u64 {
752
868
let liquidity_penalty_msat = {
753
869
// Upper bound the liquidity penalty to ensure some channel is selected.
754
- let multiplier_msat = params . liquidity_penalty_multiplier_msat ;
870
+ let multiplier_msat = liquidity_penalty_multiplier_msat;
755
871
let max_penalty_msat = multiplier_msat. saturating_mul ( NEGATIVE_LOG10_UPPER_BOUND ) ;
756
872
( negative_log10_times_2048. saturating_mul ( multiplier_msat) / 2048 ) . min ( max_penalty_msat)
757
873
} ;
758
874
let amount_penalty_msat = negative_log10_times_2048
759
- . saturating_mul ( params . liquidity_penalty_amount_multiplier_msat )
875
+ . saturating_mul ( liquidity_penalty_amount_multiplier_msat)
760
876
. saturating_mul ( amount_msat) / 2048 / AMOUNT_PENALTY_DIVISOR ;
761
877
762
878
liquidity_penalty_msat. saturating_add ( amount_penalty_msat)
@@ -2199,35 +2315,35 @@ mod tests {
2199
2315
let usage = ChannelUsage {
2200
2316
effective_capacity : EffectiveCapacity :: Total { capacity_msat : 3_950_000_000 , htlc_maximum_msat : Some ( 1_000 ) } , ..usage
2201
2317
} ;
2202
- assert_eq ! ( scorer. channel_penalty_msat( 42 , & source, & target, usage) , 1985 ) ;
2318
+ assert_eq ! ( scorer. channel_penalty_msat( 42 , & source, & target, usage) , 1983 ) ;
2203
2319
let usage = ChannelUsage {
2204
2320
effective_capacity : EffectiveCapacity :: Total { capacity_msat : 4_950_000_000 , htlc_maximum_msat : Some ( 1_000 ) } , ..usage
2205
2321
} ;
2206
- assert_eq ! ( scorer. channel_penalty_msat( 42 , & source, & target, usage) , 1639 ) ;
2322
+ assert_eq ! ( scorer. channel_penalty_msat( 42 , & source, & target, usage) , 1637 ) ;
2207
2323
let usage = ChannelUsage {
2208
2324
effective_capacity : EffectiveCapacity :: Total { capacity_msat : 5_950_000_000 , htlc_maximum_msat : Some ( 1_000 ) } , ..usage
2209
2325
} ;
2210
- assert_eq ! ( scorer. channel_penalty_msat( 42 , & source, & target, usage) , 1607 ) ;
2326
+ assert_eq ! ( scorer. channel_penalty_msat( 42 , & source, & target, usage) , 1606 ) ;
2211
2327
let usage = ChannelUsage {
2212
2328
effective_capacity : EffectiveCapacity :: Total { capacity_msat : 6_950_000_000 , htlc_maximum_msat : Some ( 1_000 ) } , ..usage
2213
2329
} ;
2214
- assert_eq ! ( scorer. channel_penalty_msat( 42 , & source, & target, usage) , 1262 ) ;
2330
+ assert_eq ! ( scorer. channel_penalty_msat( 42 , & source, & target, usage) , 1331 ) ;
2215
2331
let usage = ChannelUsage {
2216
2332
effective_capacity : EffectiveCapacity :: Total { capacity_msat : 7_450_000_000 , htlc_maximum_msat : Some ( 1_000 ) } , ..usage
2217
2333
} ;
2218
- assert_eq ! ( scorer. channel_penalty_msat( 42 , & source, & target, usage) , 1262 ) ;
2334
+ assert_eq ! ( scorer. channel_penalty_msat( 42 , & source, & target, usage) , 1387 ) ;
2219
2335
let usage = ChannelUsage {
2220
2336
effective_capacity : EffectiveCapacity :: Total { capacity_msat : 7_950_000_000 , htlc_maximum_msat : Some ( 1_000 ) } , ..usage
2221
2337
} ;
2222
- assert_eq ! ( scorer. channel_penalty_msat( 42 , & source, & target, usage) , 1262 ) ;
2338
+ assert_eq ! ( scorer. channel_penalty_msat( 42 , & source, & target, usage) , 1379 ) ;
2223
2339
let usage = ChannelUsage {
2224
2340
effective_capacity : EffectiveCapacity :: Total { capacity_msat : 8_950_000_000 , htlc_maximum_msat : Some ( 1_000 ) } , ..usage
2225
2341
} ;
2226
- assert_eq ! ( scorer. channel_penalty_msat( 42 , & source, & target, usage) , 1262 ) ;
2342
+ assert_eq ! ( scorer. channel_penalty_msat( 42 , & source, & target, usage) , 1363 ) ;
2227
2343
let usage = ChannelUsage {
2228
2344
effective_capacity : EffectiveCapacity :: Total { capacity_msat : 9_950_000_000 , htlc_maximum_msat : Some ( 1_000 ) } , ..usage
2229
2345
} ;
2230
- assert_eq ! ( scorer. channel_penalty_msat( 42 , & source, & target, usage) , 1262 ) ;
2346
+ assert_eq ! ( scorer. channel_penalty_msat( 42 , & source, & target, usage) , 1355 ) ;
2231
2347
}
2232
2348
2233
2349
#[ test]
@@ -2251,15 +2367,15 @@ mod tests {
2251
2367
2252
2368
let params = ProbabilisticScoringParameters {
2253
2369
base_penalty_msat : 500 , liquidity_penalty_multiplier_msat : 1_000 ,
2254
- anti_probing_penalty_msat : 0 , ..Default :: default ( )
2370
+ anti_probing_penalty_msat : 0 , ..ProbabilisticScoringParameters :: zero_penalty ( )
2255
2371
} ;
2256
2372
let scorer = ProbabilisticScorer :: new ( params, & network_graph, & logger) ;
2257
2373
assert_eq ! ( scorer. channel_penalty_msat( 42 , & source, & target, usage) , 558 ) ;
2258
2374
2259
2375
let params = ProbabilisticScoringParameters {
2260
2376
base_penalty_msat : 500 , liquidity_penalty_multiplier_msat : 1_000 ,
2261
2377
base_penalty_amount_multiplier_msat : ( 1 << 30 ) ,
2262
- anti_probing_penalty_msat : 0 , ..Default :: default ( )
2378
+ anti_probing_penalty_msat : 0 , ..ProbabilisticScoringParameters :: zero_penalty ( )
2263
2379
} ;
2264
2380
2265
2381
let scorer = ProbabilisticScorer :: new ( params, & network_graph, & logger) ;
@@ -2362,6 +2478,36 @@ mod tests {
2362
2478
assert_eq ! ( scorer. channel_penalty_msat( 42 , & source, & target, usage) , u64 :: max_value( ) ) ;
2363
2479
}
2364
2480
2481
+ #[ test]
2482
+ fn remembers_historical_failures ( ) {
2483
+ let logger = TestLogger :: new ( ) ;
2484
+ let network_graph = network_graph ( & logger) ;
2485
+ let params = ProbabilisticScoringParameters {
2486
+ historical_liquidity_penalty_multiplier_msat : 1024 ,
2487
+ historical_liquidity_penalty_amount_multiplier_msat : 1024 ,
2488
+ ..ProbabilisticScoringParameters :: zero_penalty ( )
2489
+ } ;
2490
+ let mut scorer = ProbabilisticScorer :: new ( params, & network_graph, & logger) ;
2491
+ let source = source_node_id ( ) ;
2492
+ let target = target_node_id ( ) ;
2493
+
2494
+ let usage = ChannelUsage {
2495
+ amount_msat : 100 ,
2496
+ inflight_htlc_msat : 0 ,
2497
+ effective_capacity : EffectiveCapacity :: Total { capacity_msat : 1_024 , htlc_maximum_msat : Some ( 1_024 ) } ,
2498
+ } ;
2499
+ // With no historical data the normal liquidity penalty calculation is used.
2500
+ assert_eq ! ( scorer. channel_penalty_msat( 42 , & source, & target, usage) , 47 ) ;
2501
+
2502
+ scorer. payment_path_failed ( & payment_path_for_amount ( 1 ) . iter ( ) . collect :: < Vec < _ > > ( ) , 42 ) ;
2503
+ assert_eq ! ( scorer. channel_penalty_msat( 42 , & source, & target, usage) , 2048 ) ;
2504
+
2505
+ // Even after we tell the scorer we definitely have enough available liquidity, it will
2506
+ // still remember that there was some failure in the past, and assign a non-0 penalty.
2507
+ scorer. payment_path_failed ( & payment_path_for_amount ( 1000 ) . iter ( ) . collect :: < Vec < _ > > ( ) , 43 ) ;
2508
+ assert_eq ! ( scorer. channel_penalty_msat( 42 , & source, & target, usage) , 198 ) ;
2509
+ }
2510
+
2365
2511
#[ test]
2366
2512
fn adds_anti_probing_penalty ( ) {
2367
2513
let logger = TestLogger :: new ( ) ;
0 commit comments