@@ -62,7 +62,7 @@ use util::logger::Logger;
62
62
use util:: time:: Time ;
63
63
64
64
use prelude:: * ;
65
- use core:: fmt;
65
+ use core:: { cmp , fmt} ;
66
66
use core:: cell:: { RefCell , RefMut } ;
67
67
use core:: convert:: TryInto ;
68
68
use core:: ops:: { Deref , DerefMut } ;
@@ -436,6 +436,16 @@ pub struct ProbabilisticScoringParameters {
436
436
/// [`liquidity_penalty_amount_multiplier_msat`]: Self::liquidity_penalty_amount_multiplier_msat
437
437
pub historical_liquidity_penalty_amount_multiplier_msat : u64 ,
438
438
439
+ /// If we aren't learning any new datapoints for a channel, the historical liquidity bounds
440
+ /// tracking can simply live on with increasingly stale data. Instead, when a channel has not
441
+ /// seen a liquidity estimate update for this amount of time, the historical datapoints are
442
+ /// decayed by half.
443
+ ///
444
+ /// Note that after 16 or more half lives all historical data will be completely gone.
445
+ ///
446
+ /// Default value: 14 days
447
+ pub historical_no_updates_half_life : Duration ,
448
+
439
449
/// Manual penalties used for the given nodes. Allows to set a particular penalty for a given
440
450
/// node. Note that a manual penalty of `u64::max_value()` means the node would not ever be
441
451
/// considered during path finding.
@@ -509,6 +519,13 @@ impl HistoricalBucketRangeTracker {
509
519
self . buckets [ bucket_idx as usize ] = self . buckets [ bucket_idx as usize ] . saturating_add ( 32 ) ;
510
520
}
511
521
}
522
+ /// Decay all buckets by the given number of half-lives. Used to more aggressively remove old
523
+ /// datapoints as we receive newer information.
524
+ fn time_decay_data ( & mut self , half_lives : u32 ) {
525
+ for e in self . buckets . iter_mut ( ) {
526
+ * e = e. checked_shr ( half_lives) . unwrap_or ( 0 ) ;
527
+ }
528
+ }
512
529
}
513
530
514
531
impl_writeable_tlv_based ! ( HistoricalBucketRangeTracker , { ( 0 , buckets, required) } ) ;
@@ -645,6 +662,7 @@ impl ProbabilisticScoringParameters {
645
662
liquidity_penalty_amount_multiplier_msat : 0 ,
646
663
historical_liquidity_penalty_multiplier_msat : 0 ,
647
664
historical_liquidity_penalty_amount_multiplier_msat : 0 ,
665
+ historical_no_updates_half_life : Duration :: from_secs ( 60 * 60 * 24 * 14 ) ,
648
666
manual_node_penalties : HashMap :: new ( ) ,
649
667
anti_probing_penalty_msat : 0 ,
650
668
considered_impossible_penalty_msat : 0 ,
@@ -670,6 +688,7 @@ impl Default for ProbabilisticScoringParameters {
670
688
liquidity_penalty_amount_multiplier_msat : 192 ,
671
689
historical_liquidity_penalty_multiplier_msat : 10_000 ,
672
690
historical_liquidity_penalty_amount_multiplier_msat : 64 ,
691
+ historical_no_updates_half_life : Duration :: from_secs ( 60 * 60 * 24 * 14 ) ,
673
692
manual_node_penalties : HashMap :: new ( ) ,
674
693
anti_probing_penalty_msat : 250 ,
675
694
considered_impossible_penalty_msat : 1_0000_0000_000 ,
@@ -810,14 +829,30 @@ impl<L: Deref<Target = u64>, BRT: Deref<Target = HistoricalBucketRangeTracker>,
810
829
// sending less than 1/16th of a channel's capacity, or 1/8th if we used the top of the
811
830
// bucket.
812
831
let mut total_valid_points_tracked = 0 ;
832
+ let required_decays = self . now . duration_since ( * self . last_updated ) . as_secs ( )
833
+ . checked_div ( params. historical_no_updates_half_life . as_secs ( ) )
834
+ . map_or ( u32:: max_value ( ) , |decays| cmp:: min ( decays, u32:: max_value ( ) as u64 ) as u32 ) ;
835
+
836
+ // Rather than actually decaying the individual buckets, which would lose precision, we
837
+ // simply track whether all buckets would be decayed to zero, in which case we treat it
838
+ // as if we had no data.
839
+ let mut is_fully_decayed = true ;
840
+ let mut check_track_bucket_contains_undecayed_points =
841
+ |bucket_val : u16 | if bucket_val. checked_shr ( required_decays) . unwrap_or ( 0 ) > 0 { is_fully_decayed = false ; } ;
842
+
813
843
for ( min_idx, min_bucket) in self . min_liquidity_offset_history . buckets . iter ( ) . enumerate ( ) {
844
+ check_track_bucket_contains_undecayed_points ( * min_bucket) ;
814
845
for max_bucket in self . max_liquidity_offset_history . buckets . iter ( ) . take ( 8 - min_idx) {
815
846
total_valid_points_tracked += ( * min_bucket as u64 ) * ( * max_bucket as u64 ) ;
847
+ check_track_bucket_contains_undecayed_points ( * max_bucket) ;
816
848
}
817
849
}
818
- if total_valid_points_tracked == 0 {
819
- // If we don't have any valid points, redo the non-historical calculation with no
820
- // liquidity bounds tracked and the historical penalty multipliers.
850
+ // If the total valid points is smaller than 1.0 (i.e. 32 in our fixed-point scheme),
851
+ // treat it as if we were fully decayed.
852
+ if total_valid_points_tracked. checked_shr ( required_decays) . unwrap_or ( 0 ) < 32 * 32 || is_fully_decayed {
853
+ // If we don't have any valid points (or, once decayed, we have less than a full
854
+ // point), redo the non-historical calculation with no liquidity bounds tracked and
855
+ // the historical penalty multipliers.
821
856
let max_capacity = self . capacity_msat . saturating_sub ( amount_msat) . saturating_add ( 1 ) ;
822
857
let negative_log10_times_2048 =
823
858
approx:: negative_log10_times_2048 ( max_capacity, self . capacity_msat . saturating_add ( 1 ) ) ;
@@ -925,6 +960,12 @@ impl<L: DerefMut<Target = u64>, BRT: DerefMut<Target = HistoricalBucketRangeTrac
925
960
}
926
961
927
962
fn update_history_buckets ( & mut self ) {
963
+ let half_lives = self . now . duration_since ( * self . last_updated ) . as_secs ( )
964
+ . checked_div ( self . params . historical_no_updates_half_life . as_secs ( ) )
965
+ . map ( |v| v. try_into ( ) . unwrap_or ( u32:: max_value ( ) ) ) . unwrap_or ( u32:: max_value ( ) ) ;
966
+ self . min_liquidity_offset_history . time_decay_data ( half_lives) ;
967
+ self . max_liquidity_offset_history . time_decay_data ( half_lives) ;
968
+
928
969
debug_assert ! ( * self . min_liquidity_offset_msat <= self . capacity_msat) ;
929
970
self . min_liquidity_offset_history . track_datapoint (
930
971
// Ensure the bucket index we pass is in the range [0, 7], even if the liquidity offset
@@ -947,8 +988,8 @@ impl<L: DerefMut<Target = u64>, BRT: DerefMut<Target = HistoricalBucketRangeTrac
947
988
} else {
948
989
self . decayed_offset_msat ( * self . max_liquidity_offset_msat )
949
990
} ;
950
- * self . last_updated = self . now ;
951
991
self . update_history_buckets ( ) ;
992
+ * self . last_updated = self . now ;
952
993
}
953
994
954
995
/// Adjusts the upper bound of the channel liquidity balance in this direction.
@@ -959,8 +1000,8 @@ impl<L: DerefMut<Target = u64>, BRT: DerefMut<Target = HistoricalBucketRangeTrac
959
1000
} else {
960
1001
self . decayed_offset_msat ( * self . min_liquidity_offset_msat )
961
1002
} ;
962
- * self . last_updated = self . now ;
963
1003
self . update_history_buckets ( ) ;
1004
+ * self . last_updated = self . now ;
964
1005
}
965
1006
}
966
1007
0 commit comments