Skip to content

Commit be142a5

Browse files
committed
Track historical liquidity update time separately from the bounds
In the next commit, we'll start to use the new `ScoreUpdate::decay_liquidity_certainty` to decay our bounds in the background. This will result in the `last_updated` field getting updated regularly on decay, rather than only on update. While this isn't an issue for the regular liquidity bounds, it poses a problem for the historical liquidity buckets, which are decayed on a separate (and by default much longer) timer. If we didn't move to tracking their decays separately, we'd never let the `last_updated` field get old enough for the historical buckets to decay at all. Instead, here we introduce a new `Duration` in the `ChannelLiquidity` which tracks the last time the historical liquidity buckets were last updated. We initialize it to a copy of `last_updated` on deserialization if it is missing.
1 parent b54be14 commit be142a5

File tree

1 file changed

+50
-18
lines changed

1 file changed

+50
-18
lines changed

lightning/src/routing/scoring.rs

Lines changed: 50 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -806,11 +806,14 @@ struct ChannelLiquidity<T: Time> {
806806
/// Upper channel liquidity bound in terms of an offset from the effective capacity.
807807
max_liquidity_offset_msat: u64,
808808

809+
min_liquidity_offset_history: HistoricalBucketRangeTracker,
810+
max_liquidity_offset_history: HistoricalBucketRangeTracker,
811+
809812
/// Time when the liquidity bounds were last modified.
810813
last_updated: T,
811814

812-
min_liquidity_offset_history: HistoricalBucketRangeTracker,
813-
max_liquidity_offset_history: HistoricalBucketRangeTracker,
815+
/// Time when the historical liquidity bounds were last modified.
816+
offset_history_last_updated: T,
814817
}
815818

816819
/// A snapshot of [`ChannelLiquidity`] in one direction assuming a certain channel capacity and
@@ -821,6 +824,7 @@ struct DirectedChannelLiquidity<L: Deref<Target = u64>, BRT: Deref<Target = Hist
821824
liquidity_history: HistoricalMinMaxBuckets<BRT>,
822825
capacity_msat: u64,
823826
last_updated: U,
827+
offset_history_last_updated: U,
824828
now: T,
825829
decay_params: ProbabilisticScoringDecayParameters,
826830
}
@@ -859,7 +863,7 @@ impl<G: Deref<Target = NetworkGraph<L>>, L: Deref, T: Time> ProbabilisticScorerU
859863
let dir_liq = liq.as_directed(source, target, amt, self.decay_params);
860864

861865
let (min_buckets, max_buckets) = dir_liq.liquidity_history
862-
.get_decayed_buckets(now, *dir_liq.last_updated,
866+
.get_decayed_buckets(now, *dir_liq.offset_history_last_updated,
863867
self.decay_params.historical_no_updates_half_life)
864868
.unwrap_or(([0; 32], [0; 32]));
865869

@@ -956,7 +960,7 @@ impl<G: Deref<Target = NetworkGraph<L>>, L: Deref, T: Time> ProbabilisticScorerU
956960

957961
let (min_buckets, mut max_buckets) =
958962
dir_liq.liquidity_history.get_decayed_buckets(
959-
dir_liq.now, *dir_liq.last_updated,
963+
dir_liq.now, *dir_liq.offset_history_last_updated,
960964
self.decay_params.historical_no_updates_half_life
961965
)?;
962966

@@ -989,7 +993,7 @@ impl<G: Deref<Target = NetworkGraph<L>>, L: Deref, T: Time> ProbabilisticScorerU
989993
let dir_liq = liq.as_directed(source, target, capacity_msat, self.decay_params);
990994

991995
return dir_liq.liquidity_history.calculate_success_probability_times_billion(
992-
dir_liq.now, *dir_liq.last_updated,
996+
dir_liq.now, *dir_liq.offset_history_last_updated,
993997
self.decay_params.historical_no_updates_half_life, &params, amount_msat,
994998
capacity_msat
995999
).map(|p| p as f64 / (1024 * 1024 * 1024) as f64);
@@ -1009,6 +1013,7 @@ impl<T: Time> ChannelLiquidity<T> {
10091013
min_liquidity_offset_history: HistoricalBucketRangeTracker::new(),
10101014
max_liquidity_offset_history: HistoricalBucketRangeTracker::new(),
10111015
last_updated: T::now(),
1016+
offset_history_last_updated: T::now(),
10121017
}
10131018
}
10141019

@@ -1035,6 +1040,7 @@ impl<T: Time> ChannelLiquidity<T> {
10351040
},
10361041
capacity_msat,
10371042
last_updated: &self.last_updated,
1043+
offset_history_last_updated: &self.offset_history_last_updated,
10381044
now: T::now(),
10391045
decay_params: decay_params,
10401046
}
@@ -1063,6 +1069,7 @@ impl<T: Time> ChannelLiquidity<T> {
10631069
},
10641070
capacity_msat,
10651071
last_updated: &mut self.last_updated,
1072+
offset_history_last_updated: &mut self.offset_history_last_updated,
10661073
now: T::now(),
10671074
decay_params: decay_params,
10681075
}
@@ -1198,7 +1205,8 @@ impl<L: Deref<Target = u64>, BRT: Deref<Target = HistoricalBucketRangeTracker>,
11981205
if score_params.historical_liquidity_penalty_multiplier_msat != 0 ||
11991206
score_params.historical_liquidity_penalty_amount_multiplier_msat != 0 {
12001207
if let Some(cumulative_success_prob_times_billion) = self.liquidity_history
1201-
.calculate_success_probability_times_billion(self.now, *self.last_updated,
1208+
.calculate_success_probability_times_billion(
1209+
self.now, *self.offset_history_last_updated,
12021210
self.decay_params.historical_no_updates_half_life, score_params, amount_msat,
12031211
self.capacity_msat)
12041212
{
@@ -1317,7 +1325,7 @@ impl<L: DerefMut<Target = u64>, BRT: DerefMut<Target = HistoricalBucketRangeTrac
13171325
/// state"), we allow the caller to set an offset applied to our liquidity bounds which
13181326
/// represents the amount of the successful payment we just made.
13191327
fn update_history_buckets(&mut self, bucket_offset_msat: u64) {
1320-
let half_lives = self.now.duration_since(*self.last_updated).as_secs()
1328+
let half_lives = self.now.duration_since(*self.offset_history_last_updated).as_secs()
13211329
.checked_div(self.decay_params.historical_no_updates_half_life.as_secs())
13221330
.map(|v| v.try_into().unwrap_or(u32::max_value())).unwrap_or(u32::max_value());
13231331
self.liquidity_history.min_liquidity_offset_history.time_decay_data(half_lives);
@@ -1342,6 +1350,7 @@ impl<L: DerefMut<Target = u64>, BRT: DerefMut<Target = HistoricalBucketRangeTrac
13421350
self.decayed_offset_msat(*self.max_liquidity_offset_msat)
13431351
};
13441352
*self.last_updated = self.now;
1353+
*self.offset_history_last_updated = self.now;
13451354
}
13461355

13471356
/// Adjusts the upper bound of the channel liquidity balance in this direction.
@@ -1353,6 +1362,7 @@ impl<L: DerefMut<Target = u64>, BRT: DerefMut<Target = HistoricalBucketRangeTrac
13531362
self.decayed_offset_msat(*self.min_liquidity_offset_msat)
13541363
};
13551364
*self.last_updated = self.now;
1365+
*self.offset_history_last_updated = self.now;
13561366
}
13571367
}
13581368

@@ -1977,9 +1987,9 @@ mod bucketed_history {
19771987
}
19781988

19791989
impl<D: Deref<Target = HistoricalBucketRangeTracker>> HistoricalMinMaxBuckets<D> {
1980-
pub(super) fn get_decayed_buckets<T: Time>(&self, now: T, last_updated: T, half_life: Duration)
1990+
pub(super) fn get_decayed_buckets<T: Time>(&self, now: T, offset_history_last_updated: T, half_life: Duration)
19811991
-> Option<([u16; 32], [u16; 32])> {
1982-
let (_, required_decays) = self.get_total_valid_points(now, last_updated, half_life)?;
1992+
let (_, required_decays) = self.get_total_valid_points(now, offset_history_last_updated, half_life)?;
19831993

19841994
let mut min_buckets = *self.min_liquidity_offset_history;
19851995
min_buckets.time_decay_data(required_decays);
@@ -1988,9 +1998,9 @@ mod bucketed_history {
19881998
Some((min_buckets.buckets, max_buckets.buckets))
19891999
}
19902000
#[inline]
1991-
pub(super) fn get_total_valid_points<T: Time>(&self, now: T, last_updated: T, half_life: Duration)
2001+
pub(super) fn get_total_valid_points<T: Time>(&self, now: T, offset_history_last_updated: T, half_life: Duration)
19922002
-> Option<(u64, u32)> {
1993-
let required_decays = now.duration_since(last_updated).as_secs()
2003+
let required_decays = now.duration_since(offset_history_last_updated).as_secs()
19942004
.checked_div(half_life.as_secs())
19952005
.map_or(u32::max_value(), |decays| cmp::min(decays, u32::max_value() as u64) as u32);
19962006

@@ -2013,7 +2023,7 @@ mod bucketed_history {
20132023

20142024
#[inline]
20152025
pub(super) fn calculate_success_probability_times_billion<T: Time>(
2016-
&self, now: T, last_updated: T, half_life: Duration,
2026+
&self, now: T, offset_history_last_updated: T, half_life: Duration,
20172027
params: &ProbabilisticScoringFeeParameters, amount_msat: u64, capacity_msat: u64
20182028
) -> Option<u64> {
20192029
// If historical penalties are enabled, we try to calculate a probability of success
@@ -2029,7 +2039,7 @@ mod bucketed_history {
20292039
// Check if all our buckets are zero, once decayed and treat it as if we had no data. We
20302040
// don't actually use the decayed buckets, though, as that would lose precision.
20312041
let (total_valid_points_tracked, _)
2032-
= self.get_total_valid_points(now, last_updated, half_life)?;
2042+
= self.get_total_valid_points(now, offset_history_last_updated, half_life)?;
20332043

20342044
let mut cumulative_success_prob_times_billion = 0;
20352045
// Special-case the 0th min bucket - it generally means we failed a payment, so only
@@ -2122,6 +2132,8 @@ ReadableArgs<(ProbabilisticScoringDecayParameters, G, L)> for ProbabilisticScore
21222132
impl<T: Time> Writeable for ChannelLiquidity<T> {
21232133
#[inline]
21242134
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
2135+
let offset_history_duration_since_epoch =
2136+
T::duration_since_epoch() - self.offset_history_last_updated.elapsed();
21252137
let duration_since_epoch = T::duration_since_epoch() - self.last_updated.elapsed();
21262138
write_tlv_fields!(w, {
21272139
(0, self.min_liquidity_offset_msat, required),
@@ -2131,6 +2143,7 @@ impl<T: Time> Writeable for ChannelLiquidity<T> {
21312143
(4, duration_since_epoch, required),
21322144
(5, Some(self.min_liquidity_offset_history), option),
21332145
(7, Some(self.max_liquidity_offset_history), option),
2146+
(9, offset_history_duration_since_epoch, required),
21342147
});
21352148
Ok(())
21362149
}
@@ -2146,6 +2159,7 @@ impl<T: Time> Readable for ChannelLiquidity<T> {
21462159
let mut min_liquidity_offset_history: Option<HistoricalBucketRangeTracker> = None;
21472160
let mut max_liquidity_offset_history: Option<HistoricalBucketRangeTracker> = None;
21482161
let mut duration_since_epoch = Duration::from_secs(0);
2162+
let mut offset_history_duration_since_epoch = None;
21492163
read_tlv_fields!(r, {
21502164
(0, min_liquidity_offset_msat, required),
21512165
(1, legacy_min_liq_offset_history, option),
@@ -2154,6 +2168,7 @@ impl<T: Time> Readable for ChannelLiquidity<T> {
21542168
(4, duration_since_epoch, required),
21552169
(5, min_liquidity_offset_history, option),
21562170
(7, max_liquidity_offset_history, option),
2171+
(9, offset_history_duration_since_epoch, option),
21572172
});
21582173
// On rust prior to 1.60 `Instant::duration_since` will panic if time goes backwards.
21592174
// We write `last_updated` as wallclock time even though its ultimately an `Instant` (which
@@ -2167,6 +2182,13 @@ impl<T: Time> Readable for ChannelLiquidity<T> {
21672182
let last_updated = if wall_clock_now > duration_since_epoch {
21682183
now - (wall_clock_now - duration_since_epoch)
21692184
} else { now };
2185+
2186+
let offset_history_duration_since_epoch =
2187+
offset_history_duration_since_epoch.unwrap_or(duration_since_epoch);
2188+
let offset_history_last_updated = if wall_clock_now > offset_history_duration_since_epoch {
2189+
now - (wall_clock_now - duration_since_epoch)
2190+
} else { now };
2191+
21702192
if min_liquidity_offset_history.is_none() {
21712193
if let Some(legacy_buckets) = legacy_min_liq_offset_history {
21722194
min_liquidity_offset_history = Some(legacy_buckets.into_current());
@@ -2187,6 +2209,7 @@ impl<T: Time> Readable for ChannelLiquidity<T> {
21872209
min_liquidity_offset_history: min_liquidity_offset_history.unwrap(),
21882210
max_liquidity_offset_history: max_liquidity_offset_history.unwrap(),
21892211
last_updated,
2212+
offset_history_last_updated,
21902213
})
21912214
}
21922215
}
@@ -2366,18 +2389,21 @@ mod tests {
23662389
fn liquidity_bounds_directed_from_lowest_node_id() {
23672390
let logger = TestLogger::new();
23682391
let last_updated = SinceEpoch::now();
2392+
let offset_history_last_updated = SinceEpoch::now();
23692393
let network_graph = network_graph(&logger);
23702394
let decay_params = ProbabilisticScoringDecayParameters::default();
23712395
let mut scorer = ProbabilisticScorer::new(decay_params, &network_graph, &logger)
23722396
.with_channel(42,
23732397
ChannelLiquidity {
2374-
min_liquidity_offset_msat: 700, max_liquidity_offset_msat: 100, last_updated,
2398+
min_liquidity_offset_msat: 700, max_liquidity_offset_msat: 100,
2399+
last_updated, offset_history_last_updated,
23752400
min_liquidity_offset_history: HistoricalBucketRangeTracker::new(),
23762401
max_liquidity_offset_history: HistoricalBucketRangeTracker::new(),
23772402
})
23782403
.with_channel(43,
23792404
ChannelLiquidity {
2380-
min_liquidity_offset_msat: 700, max_liquidity_offset_msat: 100, last_updated,
2405+
min_liquidity_offset_msat: 700, max_liquidity_offset_msat: 100,
2406+
last_updated, offset_history_last_updated,
23812407
min_liquidity_offset_history: HistoricalBucketRangeTracker::new(),
23822408
max_liquidity_offset_history: HistoricalBucketRangeTracker::new(),
23832409
});
@@ -2444,12 +2470,14 @@ mod tests {
24442470
fn resets_liquidity_upper_bound_when_crossed_by_lower_bound() {
24452471
let logger = TestLogger::new();
24462472
let last_updated = SinceEpoch::now();
2473+
let offset_history_last_updated = SinceEpoch::now();
24472474
let network_graph = network_graph(&logger);
24482475
let decay_params = ProbabilisticScoringDecayParameters::default();
24492476
let mut scorer = ProbabilisticScorer::new(decay_params, &network_graph, &logger)
24502477
.with_channel(42,
24512478
ChannelLiquidity {
2452-
min_liquidity_offset_msat: 200, max_liquidity_offset_msat: 400, last_updated,
2479+
min_liquidity_offset_msat: 200, max_liquidity_offset_msat: 400,
2480+
last_updated, offset_history_last_updated,
24532481
min_liquidity_offset_history: HistoricalBucketRangeTracker::new(),
24542482
max_liquidity_offset_history: HistoricalBucketRangeTracker::new(),
24552483
});
@@ -2503,12 +2531,14 @@ mod tests {
25032531
fn resets_liquidity_lower_bound_when_crossed_by_upper_bound() {
25042532
let logger = TestLogger::new();
25052533
let last_updated = SinceEpoch::now();
2534+
let offset_history_last_updated = SinceEpoch::now();
25062535
let network_graph = network_graph(&logger);
25072536
let decay_params = ProbabilisticScoringDecayParameters::default();
25082537
let mut scorer = ProbabilisticScorer::new(decay_params, &network_graph, &logger)
25092538
.with_channel(42,
25102539
ChannelLiquidity {
2511-
min_liquidity_offset_msat: 200, max_liquidity_offset_msat: 400, last_updated,
2540+
min_liquidity_offset_msat: 200, max_liquidity_offset_msat: 400,
2541+
last_updated, offset_history_last_updated,
25122542
min_liquidity_offset_history: HistoricalBucketRangeTracker::new(),
25132543
max_liquidity_offset_history: HistoricalBucketRangeTracker::new(),
25142544
});
@@ -2608,6 +2638,7 @@ mod tests {
26082638
fn constant_penalty_outside_liquidity_bounds() {
26092639
let logger = TestLogger::new();
26102640
let last_updated = SinceEpoch::now();
2641+
let offset_history_last_updated = SinceEpoch::now();
26112642
let network_graph = network_graph(&logger);
26122643
let params = ProbabilisticScoringFeeParameters {
26132644
liquidity_penalty_multiplier_msat: 1_000,
@@ -2620,7 +2651,8 @@ mod tests {
26202651
let scorer = ProbabilisticScorer::new(decay_params, &network_graph, &logger)
26212652
.with_channel(42,
26222653
ChannelLiquidity {
2623-
min_liquidity_offset_msat: 40, max_liquidity_offset_msat: 40, last_updated,
2654+
min_liquidity_offset_msat: 40, max_liquidity_offset_msat: 40,
2655+
last_updated, offset_history_last_updated,
26242656
min_liquidity_offset_history: HistoricalBucketRangeTracker::new(),
26252657
max_liquidity_offset_history: HistoricalBucketRangeTracker::new(),
26262658
});

0 commit comments

Comments
 (0)