@@ -62,7 +62,7 @@ use util::logger::Logger;
6262use util:: time:: Time ;
6363
6464use prelude:: * ;
65- use core:: fmt;
65+ use core:: { cmp , fmt} ;
6666use core:: cell:: { RefCell , RefMut } ;
6767use core:: convert:: TryInto ;
6868use core:: ops:: { Deref , DerefMut } ;
@@ -436,6 +436,16 @@ pub struct ProbabilisticScoringParameters {
436436 /// [`liquidity_penalty_amount_multiplier_msat`]: Self::liquidity_penalty_amount_multiplier_msat
437437 pub historical_liquidity_penalty_amount_multiplier_msat : u64 ,
438438
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+
439449 /// Manual penalties used for the given nodes. Allows to set a particular penalty for a given
440450 /// node. Note that a manual penalty of `u64::max_value()` means the node would not ever be
441451 /// considered during path finding.
@@ -509,6 +519,13 @@ impl HistoricalBucketRangeTracker {
509519 self . buckets [ bucket_idx as usize ] = self . buckets [ bucket_idx as usize ] . saturating_add ( 32 ) ;
510520 }
511521 }
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+ }
512529}
513530
514531impl_writeable_tlv_based ! ( HistoricalBucketRangeTracker , { ( 0 , buckets, required) } ) ;
@@ -645,6 +662,7 @@ impl ProbabilisticScoringParameters {
645662 liquidity_penalty_amount_multiplier_msat : 0 ,
646663 historical_liquidity_penalty_multiplier_msat : 0 ,
647664 historical_liquidity_penalty_amount_multiplier_msat : 0 ,
665+ historical_no_updates_half_life : Duration :: from_secs ( 60 * 60 * 24 * 14 ) ,
648666 manual_node_penalties : HashMap :: new ( ) ,
649667 anti_probing_penalty_msat : 0 ,
650668 considered_impossible_penalty_msat : 0 ,
@@ -670,6 +688,7 @@ impl Default for ProbabilisticScoringParameters {
670688 liquidity_penalty_amount_multiplier_msat : 192 ,
671689 historical_liquidity_penalty_multiplier_msat : 10_000 ,
672690 historical_liquidity_penalty_amount_multiplier_msat : 64 ,
691+ historical_no_updates_half_life : Duration :: from_secs ( 60 * 60 * 24 * 14 ) ,
673692 manual_node_penalties : HashMap :: new ( ) ,
674693 anti_probing_penalty_msat : 250 ,
675694 considered_impossible_penalty_msat : 1_0000_0000_000 ,
@@ -810,14 +829,30 @@ impl<L: Deref<Target = u64>, BRT: Deref<Target = HistoricalBucketRangeTracker>,
810829 // sending less than 1/16th of a channel's capacity, or 1/8th if we used the top of the
811830 // bucket.
812831 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+
813843 for ( min_idx, min_bucket) in self . min_liquidity_offset_history . buckets . iter ( ) . enumerate ( ) {
844+ check_track_bucket_contains_undecayed_points ( * min_bucket) ;
814845 for max_bucket in self . max_liquidity_offset_history . buckets . iter ( ) . take ( 8 - min_idx) {
815846 total_valid_points_tracked += ( * min_bucket as u64 ) * ( * max_bucket as u64 ) ;
847+ check_track_bucket_contains_undecayed_points ( * max_bucket) ;
816848 }
817849 }
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.
821856 let max_capacity = self . capacity_msat . saturating_sub ( amount_msat) . saturating_add ( 1 ) ;
822857 let negative_log10_times_2048 =
823858 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
925960 }
926961
927962 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+
928969 debug_assert ! ( * self . min_liquidity_offset_msat <= self . capacity_msat) ;
929970 self . min_liquidity_offset_history . track_datapoint (
930971 ( self . min_liquidity_offset_msat . saturating_sub ( 1 ) * 8 / self . capacity_msat )
@@ -943,8 +984,8 @@ impl<L: DerefMut<Target = u64>, BRT: DerefMut<Target = HistoricalBucketRangeTrac
943984 } else {
944985 self . decayed_offset_msat ( * self . max_liquidity_offset_msat )
945986 } ;
946- * self . last_updated = self . now ;
947987 self . update_history_buckets ( ) ;
988+ * self . last_updated = self . now ;
948989 }
949990
950991 /// Adjusts the upper bound of the channel liquidity balance in this direction.
@@ -955,8 +996,8 @@ impl<L: DerefMut<Target = u64>, BRT: DerefMut<Target = HistoricalBucketRangeTrac
955996 } else {
956997 self . decayed_offset_msat ( * self . min_liquidity_offset_msat )
957998 } ;
958- * self . last_updated = self . now ;
959999 self . update_history_buckets ( ) ;
1000+ * self . last_updated = self . now ;
9601001 }
9611002}
9621003
0 commit comments