Skip to content

Commit 50a4184

Browse files
committed
Decay historical liquidity tracking when no new data is added
To avoid scoring based on incredibly old historical liquidity data, we add a new half-life here which is used to (very slowly) decay historical liquidity tracking buckets.
1 parent 4119750 commit 50a4184

File tree

1 file changed

+40
-6
lines changed

1 file changed

+40
-6
lines changed

lightning/src/routing/scoring.rs

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ use util::logger::Logger;
6262
use util::time::Time;
6363

6464
use prelude::*;
65-
use core::fmt;
65+
use core::{cmp, fmt};
6666
use core::cell::{RefCell, RefMut};
6767
use core::convert::TryInto;
6868
use core::ops::{Deref, DerefMut};
@@ -429,6 +429,16 @@ pub struct ProbabilisticScoringParameters {
429429
/// [`liquidity_penalty_multiplier_msat`]: Self::liquidity_penalty_multiplier_msat
430430
pub historical_liquidity_penalty_amount_multiplier_msat: u64,
431431

432+
/// If we aren't learning any new datapoints for a channel, the historical liquidity bounds
433+
/// tracking can simply live on with increasingly stale data. Instead, when a channel has not
434+
/// seen a liquidity estimate update for this amount of time, the historical datapoints are
435+
/// decayed by half.
436+
///
437+
/// Note that 16 or more half lives guarantees that all historical data will be wiped.
438+
///
439+
/// Default value: 14 days
440+
pub no_updates_historical_half_life: Duration,
441+
432442
/// Manual penalties used for the given nodes. Allows to set a particular penalty for a given
433443
/// node. Note that a manual penalty of `u64::max_value()` means the node would not ever be
434444
/// considered during path finding.
@@ -502,6 +512,11 @@ impl HistoricalBucketRangeTracker {
502512
}
503513
}
504514
}
515+
fn decay_data(&mut self, half_lives: u32) {
516+
for e in self.buckets.iter_mut() {
517+
*e = e.checked_shr(half_lives).unwrap_or(0);
518+
}
519+
}
505520
}
506521

507522
impl_writeable_tlv_based!(HistoricalBucketRangeTracker, { (0, buckets, required) });
@@ -638,6 +653,7 @@ impl ProbabilisticScoringParameters {
638653
liquidity_penalty_amount_multiplier_msat: 0,
639654
historical_liquidity_penalty_multiplier_msat: 0,
640655
historical_liquidity_penalty_amount_multiplier_msat: 0,
656+
no_updates_historical_half_life: Duration::from_secs(60 * 60 * 24 * 14),
641657
manual_node_penalties: HashMap::new(),
642658
anti_probing_penalty_msat: 0,
643659
considered_impossible_penalty_msat: 0,
@@ -663,6 +679,7 @@ impl Default for ProbabilisticScoringParameters {
663679
liquidity_penalty_amount_multiplier_msat: 192,
664680
historical_liquidity_penalty_multiplier_msat: 10_000,
665681
historical_liquidity_penalty_amount_multiplier_msat: 64,
682+
no_updates_historical_half_life: Duration::from_secs(60 * 60 * 24 * 14),
666683
manual_node_penalties: HashMap::new(),
667684
anti_probing_penalty_msat: 250,
668685
considered_impossible_penalty_msat: 1_0000_0000_000,
@@ -802,14 +819,25 @@ impl<L: Deref<Target = u64>, BRT: Deref<Target = HistoricalBucketRangeTracker>,
802819
// sending less than 1/16th of a channel's capacity, or 1/8th if we used the top of the
803820
// bucket.
804821
let mut total_valid_points_tracked = 0;
822+
let decays = self.now.duration_since(*self.last_updated).as_secs()
823+
.checked_div(params.no_updates_historical_half_life.as_secs())
824+
.map(|decays| cmp::min(decays, u32::max_value() as u64) as u32)
825+
.unwrap_or(u32::max_value());
826+
// Rather than actually decaying the individual buckets, which would lose precision, we
827+
// simply track whether any buckets have data which wouldn't be decayed to zero, and if
828+
// there are none, treat it as if we had no data.
829+
let mut is_fully_decayed = true;
805830
for (min_idx, min_bucket) in self.min_liquidity_offset_history.buckets.iter().enumerate() {
831+
if min_bucket.checked_shr(decays).unwrap_or(0) > 0 { is_fully_decayed = false; }
806832
for max_bucket in self.max_liquidity_offset_history.buckets.iter().take(8 - min_idx) {
807833
total_valid_points_tracked += (*min_bucket as u64) * (*max_bucket as u64);
834+
if max_bucket.checked_shr(decays).unwrap_or(0) > 0 { is_fully_decayed = false; }
808835
}
809836
}
810-
if total_valid_points_tracked == 0 {
811-
// If we don't have any valid points, redo the non-historical calculation with no
812-
// liquidity bounds tracked and the historical penalty multipliers.
837+
if total_valid_points_tracked.checked_shr(decays).unwrap_or(0) < 32*32 || is_fully_decayed {
838+
// If we don't have any valid points (or, once decayed, we have less than a full
839+
// point), redo the non-historical calculation with no liquidity bounds tracked and
840+
// the historical penalty multipliers.
813841
let max_capacity = self.capacity_msat.saturating_sub(amount_msat).saturating_add(1);
814842
let negative_log10_times_2048 =
815843
approx::negative_log10_times_2048(max_capacity, self.capacity_msat.saturating_add(1));
@@ -917,6 +945,12 @@ impl<L: DerefMut<Target = u64>, BRT: DerefMut<Target = HistoricalBucketRangeTrac
917945
}
918946

919947
fn update_history_buckets(&mut self) {
948+
let half_lives = self.now.duration_since(*self.last_updated).as_secs()
949+
.checked_div(self.params.no_updates_historical_half_life.as_secs())
950+
.map(|v| v.try_into().unwrap_or(u32::max_value())).unwrap_or(u32::max_value());
951+
self.min_liquidity_offset_history.decay_data(half_lives);
952+
self.max_liquidity_offset_history.decay_data(half_lives);
953+
920954
debug_assert!(*self.min_liquidity_offset_msat <= self.capacity_msat);
921955
self.min_liquidity_offset_history.track_datapoint(
922956
(self.min_liquidity_offset_msat.saturating_sub(1) * 8 / self.capacity_msat)
@@ -935,8 +969,8 @@ impl<L: DerefMut<Target = u64>, BRT: DerefMut<Target = HistoricalBucketRangeTrac
935969
} else {
936970
self.decayed_offset_msat(*self.max_liquidity_offset_msat)
937971
};
938-
*self.last_updated = self.now;
939972
self.update_history_buckets();
973+
*self.last_updated = self.now;
940974
}
941975

942976
/// Adjusts the upper bound of the channel liquidity balance in this direction.
@@ -947,8 +981,8 @@ impl<L: DerefMut<Target = u64>, BRT: DerefMut<Target = HistoricalBucketRangeTrac
947981
} else {
948982
self.decayed_offset_msat(*self.min_liquidity_offset_msat)
949983
};
950-
*self.last_updated = self.now;
951984
self.update_history_buckets();
985+
*self.last_updated = self.now;
952986
}
953987
}
954988

0 commit comments

Comments
 (0)