@@ -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,38 @@ 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
+ /// Default value: 64 msat
428
+ ///
429
+ /// [`liquidity_penalty_multiplier_msat`]: Self::liquidity_penalty_multiplier_msat
430
+ pub historical_liquidity_penalty_amount_multiplier_msat : u64 ,
431
+
401
432
/// Manual penalties used for the given nodes. Allows to set a particular penalty for a given
402
433
/// node. Note that a manual penalty of `u64::max_value()` means the node would not ever be
403
434
/// considered during path finding.
@@ -605,6 +636,8 @@ impl ProbabilisticScoringParameters {
605
636
liquidity_penalty_multiplier_msat : 0 ,
606
637
liquidity_offset_half_life : Duration :: from_secs ( 3600 ) ,
607
638
liquidity_penalty_amount_multiplier_msat : 0 ,
639
+ historical_liquidity_penalty_multiplier_msat : 0 ,
640
+ historical_liquidity_penalty_amount_multiplier_msat : 0 ,
608
641
manual_node_penalties : HashMap :: new ( ) ,
609
642
anti_probing_penalty_msat : 0 ,
610
643
considered_impossible_penalty_msat : 0 ,
@@ -625,9 +658,11 @@ impl Default for ProbabilisticScoringParameters {
625
658
Self {
626
659
base_penalty_msat : 500 ,
627
660
base_penalty_amount_multiplier_msat : 8192 ,
628
- liquidity_penalty_multiplier_msat : 40_000 ,
661
+ liquidity_penalty_multiplier_msat : 30_000 ,
629
662
liquidity_offset_half_life : Duration :: from_secs ( 3600 ) ,
630
- liquidity_penalty_amount_multiplier_msat : 256 ,
663
+ liquidity_penalty_amount_multiplier_msat : 192 ,
664
+ historical_liquidity_penalty_multiplier_msat : 10_000 ,
665
+ historical_liquidity_penalty_amount_multiplier_msat : 64 ,
631
666
manual_node_penalties : HashMap :: new ( ) ,
632
667
anti_probing_penalty_msat : 250 ,
633
668
considered_impossible_penalty_msat : 1_0000_0000_000 ,
@@ -718,14 +753,17 @@ impl<L: Deref<Target = u64>, BRT: Deref<Target = HistoricalBucketRangeTracker>,
718
753
fn penalty_msat ( & self , amount_msat : u64 , params : & ProbabilisticScoringParameters ) -> u64 {
719
754
let max_liquidity_msat = self . max_liquidity_msat ( ) ;
720
755
let min_liquidity_msat = core:: cmp:: min ( self . min_liquidity_msat ( ) , max_liquidity_msat) ;
721
- if amount_msat <= min_liquidity_msat {
756
+
757
+ let mut res = if amount_msat <= min_liquidity_msat {
722
758
0
723
759
} else if amount_msat >= max_liquidity_msat {
724
760
// Equivalent to hitting the else clause below with the amount equal to the effective
725
761
// capacity and without any certainty on the liquidity upper bound, plus the
726
762
// impossibility penalty.
727
763
let negative_log10_times_2048 = NEGATIVE_LOG10_UPPER_BOUND * 2048 ;
728
- self . combined_penalty_msat ( amount_msat, negative_log10_times_2048, params)
764
+ Self :: combined_penalty_msat ( amount_msat, negative_log10_times_2048,
765
+ params. liquidity_penalty_multiplier_msat ,
766
+ params. liquidity_penalty_amount_multiplier_msat )
729
767
. saturating_add ( params. considered_impossible_penalty_msat )
730
768
} else {
731
769
let numerator = ( max_liquidity_msat - amount_msat) . saturating_add ( 1 ) ;
@@ -738,25 +776,94 @@ impl<L: Deref<Target = u64>, BRT: Deref<Target = HistoricalBucketRangeTracker>,
738
776
} else {
739
777
let negative_log10_times_2048 =
740
778
approx:: negative_log10_times_2048 ( numerator, denominator) ;
741
- self . combined_penalty_msat ( amount_msat, negative_log10_times_2048, params)
779
+ Self :: combined_penalty_msat ( amount_msat, negative_log10_times_2048,
780
+ params. liquidity_penalty_multiplier_msat ,
781
+ params. liquidity_penalty_amount_multiplier_msat )
782
+ }
783
+ } ;
784
+
785
+ if params. historical_liquidity_penalty_multiplier_msat != 0 ||
786
+ params. historical_liquidity_penalty_amount_multiplier_msat != 0 {
787
+ // If historical penalties are enabled, calculate the penalty by walking the set of
788
+ // historical liquidity bucket (min, max) combinations (where min_idx < max_idx)
789
+ // and, for each, calculate the probability of success given our payment amount, then
790
+ // total the weighted average probability of success.
791
+ //
792
+ // We use a sliding scale to decide which point within a given bucket will be compared
793
+ // to the amount being sent - the amount being sent is compared to the lower-bound of
794
+ // the first bucket (i.e. zero), but compared to the upper-bound of the last bucket
795
+ // (i.e. 9 times the index), with each bucket in between increasing the comparison
796
+ // point by 1/64th. This avoids failing to assign penalties to channels at the edges.
797
+ //
798
+ // If we used the bottom edge of buckets, we'd end up never assigning any penalty at
799
+ // all to such a channel when sending less than ~0.19% of the channel's capacity (e.g.
800
+ // ~200k sats for a 1 BTC channel!).
801
+ //
802
+ // If we used the middle of each bucket we'd never assign any penalty at all when
803
+ // sending less than 1/16th of a channel's capacity, or 1/8th if we used the top of the
804
+ // bucket.
805
+ let mut total_valid_points_tracked = 0 ;
806
+ for ( min_idx, min_bucket) in self . min_liquidity_offset_history . buckets . iter ( ) . enumerate ( ) {
807
+ for max_bucket in self . max_liquidity_offset_history . buckets . iter ( ) . take ( 8 - min_idx) {
808
+ total_valid_points_tracked += ( * min_bucket as u64 ) * ( * max_bucket as u64 ) ;
809
+ }
810
+ }
811
+ if total_valid_points_tracked == 0 {
812
+ // If we don't have any valid points, redo the non-historical calculation with no
813
+ // liquidity bounds tracked and the historical penalty multipliers.
814
+ let max_capacity = self . capacity_msat . saturating_sub ( amount_msat) . saturating_add ( 1 ) ;
815
+ let negative_log10_times_2048 =
816
+ approx:: negative_log10_times_2048 ( max_capacity, self . capacity_msat . saturating_add ( 1 ) ) ;
817
+ res = res. saturating_add ( Self :: combined_penalty_msat ( amount_msat, negative_log10_times_2048,
818
+ params. historical_liquidity_penalty_multiplier_msat ,
819
+ params. historical_liquidity_penalty_amount_multiplier_msat ) ) ;
820
+ return res;
821
+ }
822
+
823
+ let payment_amt_64th_bucket = amount_msat * 64 / self . capacity_msat ;
824
+ debug_assert ! ( payment_amt_64th_bucket <= 64 ) ;
825
+ if payment_amt_64th_bucket > 64 { return res; }
826
+
827
+ let mut cumulative_success_prob_billions = 0 ;
828
+ for ( min_idx, min_bucket) in self . min_liquidity_offset_history . buckets . iter ( ) . enumerate ( ) {
829
+ for ( max_idx, max_bucket) in self . max_liquidity_offset_history . buckets . iter ( ) . enumerate ( ) . take ( 8 - min_idx) {
830
+ let bucket_prob_millions = ( * min_bucket as u64 ) * ( * max_bucket as u64 )
831
+ * 1024 * 1024 / total_valid_points_tracked;
832
+ let min_64th_bucket = min_idx as u64 * 9 ;
833
+ let max_64th_bucket = ( 7 - max_idx as u64 ) * 9 + 1 ;
834
+ if payment_amt_64th_bucket > max_64th_bucket {
835
+ // Success probability 0, the payment amount is above the max liquidity
836
+ } else if payment_amt_64th_bucket <= min_64th_bucket {
837
+ cumulative_success_prob_billions += bucket_prob_millions * 1024 ;
838
+ } else {
839
+ cumulative_success_prob_billions += bucket_prob_millions *
840
+ ( max_64th_bucket - payment_amt_64th_bucket) * 1024 /
841
+ ( max_64th_bucket - min_64th_bucket) ;
842
+ }
843
+ }
742
844
}
845
+ let historical_negative_log10_times_2048 = approx:: negative_log10_times_2048 ( cumulative_success_prob_billions + 1 , 1024 * 1024 * 1024 ) ;
846
+ res = res. saturating_add ( Self :: combined_penalty_msat ( amount_msat,
847
+ historical_negative_log10_times_2048, params. historical_liquidity_penalty_multiplier_msat ,
848
+ params. historical_liquidity_penalty_amount_multiplier_msat ) ) ;
743
849
}
850
+
851
+ res
744
852
}
745
853
746
854
/// Computes the liquidity penalty from the penalty multipliers.
747
855
#[ inline( always) ]
748
- fn combined_penalty_msat (
749
- & self , amount_msat : u64 , negative_log10_times_2048 : u64 ,
750
- params : & ProbabilisticScoringParameters
856
+ fn combined_penalty_msat ( amount_msat : u64 , negative_log10_times_2048 : u64 ,
857
+ liquidity_penalty_multiplier_msat : u64 , liquidity_penalty_amount_multiplier_msat : u64 ,
751
858
) -> u64 {
752
859
let liquidity_penalty_msat = {
753
860
// Upper bound the liquidity penalty to ensure some channel is selected.
754
- let multiplier_msat = params . liquidity_penalty_multiplier_msat ;
861
+ let multiplier_msat = liquidity_penalty_multiplier_msat;
755
862
let max_penalty_msat = multiplier_msat. saturating_mul ( NEGATIVE_LOG10_UPPER_BOUND ) ;
756
863
( negative_log10_times_2048. saturating_mul ( multiplier_msat) / 2048 ) . min ( max_penalty_msat)
757
864
} ;
758
865
let amount_penalty_msat = negative_log10_times_2048
759
- . saturating_mul ( params . liquidity_penalty_amount_multiplier_msat )
866
+ . saturating_mul ( liquidity_penalty_amount_multiplier_msat)
760
867
. saturating_mul ( amount_msat) / 2048 / AMOUNT_PENALTY_DIVISOR ;
761
868
762
869
liquidity_penalty_msat. saturating_add ( amount_penalty_msat)
@@ -2195,35 +2302,35 @@ mod tests {
2195
2302
let usage = ChannelUsage {
2196
2303
effective_capacity : EffectiveCapacity :: Total { capacity_msat : 3_950_000_000 , htlc_maximum_msat : Some ( 1_000 ) } , ..usage
2197
2304
} ;
2198
- assert_eq ! ( scorer. channel_penalty_msat( 42 , & source, & target, usage) , 1985 ) ;
2305
+ assert_eq ! ( scorer. channel_penalty_msat( 42 , & source, & target, usage) , 1983 ) ;
2199
2306
let usage = ChannelUsage {
2200
2307
effective_capacity : EffectiveCapacity :: Total { capacity_msat : 4_950_000_000 , htlc_maximum_msat : Some ( 1_000 ) } , ..usage
2201
2308
} ;
2202
- assert_eq ! ( scorer. channel_penalty_msat( 42 , & source, & target, usage) , 1639 ) ;
2309
+ assert_eq ! ( scorer. channel_penalty_msat( 42 , & source, & target, usage) , 1637 ) ;
2203
2310
let usage = ChannelUsage {
2204
2311
effective_capacity : EffectiveCapacity :: Total { capacity_msat : 5_950_000_000 , htlc_maximum_msat : Some ( 1_000 ) } , ..usage
2205
2312
} ;
2206
- assert_eq ! ( scorer. channel_penalty_msat( 42 , & source, & target, usage) , 1607 ) ;
2313
+ assert_eq ! ( scorer. channel_penalty_msat( 42 , & source, & target, usage) , 1606 ) ;
2207
2314
let usage = ChannelUsage {
2208
2315
effective_capacity : EffectiveCapacity :: Total { capacity_msat : 6_950_000_000 , htlc_maximum_msat : Some ( 1_000 ) } , ..usage
2209
2316
} ;
2210
- assert_eq ! ( scorer. channel_penalty_msat( 42 , & source, & target, usage) , 1262 ) ;
2317
+ assert_eq ! ( scorer. channel_penalty_msat( 42 , & source, & target, usage) , 1331 ) ;
2211
2318
let usage = ChannelUsage {
2212
2319
effective_capacity : EffectiveCapacity :: Total { capacity_msat : 7_450_000_000 , htlc_maximum_msat : Some ( 1_000 ) } , ..usage
2213
2320
} ;
2214
- assert_eq ! ( scorer. channel_penalty_msat( 42 , & source, & target, usage) , 1262 ) ;
2321
+ assert_eq ! ( scorer. channel_penalty_msat( 42 , & source, & target, usage) , 1387 ) ;
2215
2322
let usage = ChannelUsage {
2216
2323
effective_capacity : EffectiveCapacity :: Total { capacity_msat : 7_950_000_000 , htlc_maximum_msat : Some ( 1_000 ) } , ..usage
2217
2324
} ;
2218
- assert_eq ! ( scorer. channel_penalty_msat( 42 , & source, & target, usage) , 1262 ) ;
2325
+ assert_eq ! ( scorer. channel_penalty_msat( 42 , & source, & target, usage) , 1379 ) ;
2219
2326
let usage = ChannelUsage {
2220
2327
effective_capacity : EffectiveCapacity :: Total { capacity_msat : 8_950_000_000 , htlc_maximum_msat : Some ( 1_000 ) } , ..usage
2221
2328
} ;
2222
- assert_eq ! ( scorer. channel_penalty_msat( 42 , & source, & target, usage) , 1262 ) ;
2329
+ assert_eq ! ( scorer. channel_penalty_msat( 42 , & source, & target, usage) , 1363 ) ;
2223
2330
let usage = ChannelUsage {
2224
2331
effective_capacity : EffectiveCapacity :: Total { capacity_msat : 9_950_000_000 , htlc_maximum_msat : Some ( 1_000 ) } , ..usage
2225
2332
} ;
2226
- assert_eq ! ( scorer. channel_penalty_msat( 42 , & source, & target, usage) , 1262 ) ;
2333
+ assert_eq ! ( scorer. channel_penalty_msat( 42 , & source, & target, usage) , 1355 ) ;
2227
2334
}
2228
2335
2229
2336
#[ test]
@@ -2247,15 +2354,15 @@ mod tests {
2247
2354
2248
2355
let params = ProbabilisticScoringParameters {
2249
2356
base_penalty_msat : 500 , liquidity_penalty_multiplier_msat : 1_000 ,
2250
- anti_probing_penalty_msat : 0 , ..Default :: default ( )
2357
+ anti_probing_penalty_msat : 0 , ..ProbabilisticScoringParameters :: zero_penalty ( )
2251
2358
} ;
2252
2359
let scorer = ProbabilisticScorer :: new ( params, & network_graph, & logger) ;
2253
2360
assert_eq ! ( scorer. channel_penalty_msat( 42 , & source, & target, usage) , 558 ) ;
2254
2361
2255
2362
let params = ProbabilisticScoringParameters {
2256
2363
base_penalty_msat : 500 , liquidity_penalty_multiplier_msat : 1_000 ,
2257
2364
base_penalty_amount_multiplier_msat : ( 1 << 30 ) ,
2258
- anti_probing_penalty_msat : 0 , ..Default :: default ( )
2365
+ anti_probing_penalty_msat : 0 , ..ProbabilisticScoringParameters :: zero_penalty ( )
2259
2366
} ;
2260
2367
2261
2368
let scorer = ProbabilisticScorer :: new ( params, & network_graph, & logger) ;
@@ -2358,6 +2465,36 @@ mod tests {
2358
2465
assert_eq ! ( scorer. channel_penalty_msat( 42 , & source, & target, usage) , u64 :: max_value( ) ) ;
2359
2466
}
2360
2467
2468
+ #[ test]
2469
+ fn remembers_historical_failures ( ) {
2470
+ let logger = TestLogger :: new ( ) ;
2471
+ let network_graph = network_graph ( & logger) ;
2472
+ let params = ProbabilisticScoringParameters {
2473
+ historical_liquidity_penalty_multiplier_msat : 1024 ,
2474
+ historical_liquidity_penalty_amount_multiplier_msat : 1024 ,
2475
+ ..ProbabilisticScoringParameters :: zero_penalty ( )
2476
+ } ;
2477
+ let mut scorer = ProbabilisticScorer :: new ( params, & network_graph, & logger) ;
2478
+ let source = source_node_id ( ) ;
2479
+ let target = target_node_id ( ) ;
2480
+
2481
+ let usage = ChannelUsage {
2482
+ amount_msat : 100 ,
2483
+ inflight_htlc_msat : 0 ,
2484
+ effective_capacity : EffectiveCapacity :: Total { capacity_msat : 1_024 , htlc_maximum_msat : Some ( 1_024 ) } ,
2485
+ } ;
2486
+ // With no historical data the normal liquidity penalty calculation is used.
2487
+ assert_eq ! ( scorer. channel_penalty_msat( 42 , & source, & target, usage) , 47 ) ;
2488
+
2489
+ scorer. payment_path_failed ( & payment_path_for_amount ( 1 ) . iter ( ) . collect :: < Vec < _ > > ( ) , 42 ) ;
2490
+ assert_eq ! ( scorer. channel_penalty_msat( 42 , & source, & target, usage) , 2048 ) ;
2491
+
2492
+ // Even after we tell the scorer we definitely have enough available liquidity, it will
2493
+ // still remember that there was some failure in the past, and assign a non-0 penalty.
2494
+ scorer. payment_path_failed ( & payment_path_for_amount ( 1000 ) . iter ( ) . collect :: < Vec < _ > > ( ) , 43 ) ;
2495
+ assert_eq ! ( scorer. channel_penalty_msat( 42 , & source, & target, usage) , 198 ) ;
2496
+ }
2497
+
2361
2498
#[ test]
2362
2499
fn adds_anti_probing_penalty ( ) {
2363
2500
let logger = TestLogger :: new ( ) ;
0 commit comments