Skip to content

Commit f0f8194

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 b84842a commit f0f8194

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
@@ -805,11 +805,14 @@ struct ChannelLiquidity<T: Time> {
805805
/// Upper channel liquidity bound in terms of an offset from the effective capacity.
806806
max_liquidity_offset_msat: u64,
807807

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

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

815818
/// A snapshot of [`ChannelLiquidity`] in one direction assuming a certain channel capacity and
@@ -820,6 +823,7 @@ struct DirectedChannelLiquidity<L: Deref<Target = u64>, BRT: Deref<Target = Hist
820823
liquidity_history: HistoricalMinMaxBuckets<BRT>,
821824
capacity_msat: u64,
822825
last_updated: U,
826+
offset_history_last_updated: U,
823827
now: T,
824828
decay_params: ProbabilisticScoringDecayParameters,
825829
}
@@ -858,7 +862,7 @@ impl<G: Deref<Target = NetworkGraph<L>>, L: Deref, T: Time> ProbabilisticScorerU
858862
let dir_liq = liq.as_directed(source, target, amt, self.decay_params);
859863

860864
let (min_buckets, max_buckets) = dir_liq.liquidity_history
861-
.get_decayed_buckets(now, *dir_liq.last_updated,
865+
.get_decayed_buckets(now, *dir_liq.offset_history_last_updated,
862866
self.decay_params.historical_no_updates_half_life)
863867
.unwrap_or(([0; 32], [0; 32]));
864868

@@ -955,7 +959,7 @@ impl<G: Deref<Target = NetworkGraph<L>>, L: Deref, T: Time> ProbabilisticScorerU
955959

956960
let (min_buckets, mut max_buckets) =
957961
dir_liq.liquidity_history.get_decayed_buckets(
958-
dir_liq.now, *dir_liq.last_updated,
962+
dir_liq.now, *dir_liq.offset_history_last_updated,
959963
self.decay_params.historical_no_updates_half_life
960964
)?;
961965

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

990994
return dir_liq.liquidity_history.calculate_success_probability_times_billion(
991-
dir_liq.now, *dir_liq.last_updated,
995+
dir_liq.now, *dir_liq.offset_history_last_updated,
992996
self.decay_params.historical_no_updates_half_life, &params, amount_msat,
993997
capacity_msat
994998
).map(|p| p as f64 / (1024 * 1024 * 1024) as f64);
@@ -1008,6 +1012,7 @@ impl<T: Time> ChannelLiquidity<T> {
10081012
min_liquidity_offset_history: HistoricalBucketRangeTracker::new(),
10091013
max_liquidity_offset_history: HistoricalBucketRangeTracker::new(),
10101014
last_updated: T::now(),
1015+
offset_history_last_updated: T::now(),
10111016
}
10121017
}
10131018

@@ -1034,6 +1039,7 @@ impl<T: Time> ChannelLiquidity<T> {
10341039
},
10351040
capacity_msat,
10361041
last_updated: &self.last_updated,
1042+
offset_history_last_updated: &self.offset_history_last_updated,
10371043
now: T::now(),
10381044
decay_params: decay_params,
10391045
}
@@ -1062,6 +1068,7 @@ impl<T: Time> ChannelLiquidity<T> {
10621068
},
10631069
capacity_msat,
10641070
last_updated: &mut self.last_updated,
1071+
offset_history_last_updated: &mut self.offset_history_last_updated,
10651072
now: T::now(),
10661073
decay_params: decay_params,
10671074
}
@@ -1197,7 +1204,8 @@ impl<L: Deref<Target = u64>, BRT: Deref<Target = HistoricalBucketRangeTracker>,
11971204
if score_params.historical_liquidity_penalty_multiplier_msat != 0 ||
11981205
score_params.historical_liquidity_penalty_amount_multiplier_msat != 0 {
11991206
if let Some(cumulative_success_prob_times_billion) = self.liquidity_history
1200-
.calculate_success_probability_times_billion(self.now, *self.last_updated,
1207+
.calculate_success_probability_times_billion(
1208+
self.now, *self.offset_history_last_updated,
12011209
self.decay_params.historical_no_updates_half_life, score_params, amount_msat,
12021210
self.capacity_msat)
12031211
{
@@ -1316,7 +1324,7 @@ impl<L: DerefMut<Target = u64>, BRT: DerefMut<Target = HistoricalBucketRangeTrac
13161324
/// state"), we allow the caller to set an offset applied to our liquidity bounds which
13171325
/// represents the amount of the successful payment we just made.
13181326
fn update_history_buckets(&mut self, bucket_offset_msat: u64) {
1319-
let half_lives = self.now.duration_since(*self.last_updated).as_secs()
1327+
let half_lives = self.now.duration_since(*self.offset_history_last_updated).as_secs()
13201328
.checked_div(self.decay_params.historical_no_updates_half_life.as_secs())
13211329
.map(|v| v.try_into().unwrap_or(u32::max_value())).unwrap_or(u32::max_value());
13221330
self.liquidity_history.min_liquidity_offset_history.time_decay_data(half_lives);
@@ -1341,6 +1349,7 @@ impl<L: DerefMut<Target = u64>, BRT: DerefMut<Target = HistoricalBucketRangeTrac
13411349
self.decayed_offset_msat(*self.max_liquidity_offset_msat)
13421350
};
13431351
*self.last_updated = self.now;
1352+
*self.offset_history_last_updated = self.now;
13441353
}
13451354

13461355
/// Adjusts the upper bound of the channel liquidity balance in this direction.
@@ -1352,6 +1361,7 @@ impl<L: DerefMut<Target = u64>, BRT: DerefMut<Target = HistoricalBucketRangeTrac
13521361
self.decayed_offset_msat(*self.min_liquidity_offset_msat)
13531362
};
13541363
*self.last_updated = self.now;
1364+
*self.offset_history_last_updated = self.now;
13551365
}
13561366
}
13571367

@@ -1983,9 +1993,9 @@ mod bucketed_history {
19831993
}
19841994

19851995
impl<D: Deref<Target = HistoricalBucketRangeTracker>> HistoricalMinMaxBuckets<D> {
1986-
pub(super) fn get_decayed_buckets<T: Time>(&self, now: T, last_updated: T, half_life: Duration)
1996+
pub(super) fn get_decayed_buckets<T: Time>(&self, now: T, offset_history_last_updated: T, half_life: Duration)
19871997
-> Option<([u16; 32], [u16; 32])> {
1988-
let (_, required_decays) = self.get_total_valid_points(now, last_updated, half_life)?;
1998+
let (_, required_decays) = self.get_total_valid_points(now, offset_history_last_updated, half_life)?;
19891999

19902000
let mut min_buckets = *self.min_liquidity_offset_history;
19912001
min_buckets.time_decay_data(required_decays);
@@ -1994,9 +2004,9 @@ mod bucketed_history {
19942004
Some((min_buckets.buckets, max_buckets.buckets))
19952005
}
19962006
#[inline]
1997-
pub(super) fn get_total_valid_points<T: Time>(&self, now: T, last_updated: T, half_life: Duration)
2007+
pub(super) fn get_total_valid_points<T: Time>(&self, now: T, offset_history_last_updated: T, half_life: Duration)
19982008
-> Option<(u64, u32)> {
1999-
let required_decays = now.duration_since(last_updated).as_secs()
2009+
let required_decays = now.duration_since(offset_history_last_updated).as_secs()
20002010
.checked_div(half_life.as_secs())
20012011
.map_or(u32::max_value(), |decays| cmp::min(decays, u32::max_value() as u64) as u32);
20022012

@@ -2019,7 +2029,7 @@ mod bucketed_history {
20192029

20202030
#[inline]
20212031
pub(super) fn calculate_success_probability_times_billion<T: Time>(
2022-
&self, now: T, last_updated: T, half_life: Duration,
2032+
&self, now: T, offset_history_last_updated: T, half_life: Duration,
20232033
params: &ProbabilisticScoringFeeParameters, amount_msat: u64, capacity_msat: u64
20242034
) -> Option<u64> {
20252035
// If historical penalties are enabled, we try to calculate a probability of success
@@ -2035,7 +2045,7 @@ mod bucketed_history {
20352045
// Check if all our buckets are zero, once decayed and treat it as if we had no data. We
20362046
// don't actually use the decayed buckets, though, as that would lose precision.
20372047
let (total_valid_points_tracked, _)
2038-
= self.get_total_valid_points(now, last_updated, half_life)?;
2048+
= self.get_total_valid_points(now, offset_history_last_updated, half_life)?;
20392049

20402050
let mut cumulative_success_prob_times_billion = 0;
20412051
// Special-case the 0th min bucket - it generally means we failed a payment, so only
@@ -2128,6 +2138,8 @@ ReadableArgs<(ProbabilisticScoringDecayParameters, G, L)> for ProbabilisticScore
21282138
impl<T: Time> Writeable for ChannelLiquidity<T> {
21292139
#[inline]
21302140
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
2141+
let offset_history_duration_since_epoch =
2142+
T::duration_since_epoch() - self.offset_history_last_updated.elapsed();
21312143
let duration_since_epoch = T::duration_since_epoch() - self.last_updated.elapsed();
21322144
write_tlv_fields!(w, {
21332145
(0, self.min_liquidity_offset_msat, required),
@@ -2137,6 +2149,7 @@ impl<T: Time> Writeable for ChannelLiquidity<T> {
21372149
(4, duration_since_epoch, required),
21382150
(5, Some(self.min_liquidity_offset_history), option),
21392151
(7, Some(self.max_liquidity_offset_history), option),
2152+
(9, offset_history_duration_since_epoch, required),
21402153
});
21412154
Ok(())
21422155
}
@@ -2152,6 +2165,7 @@ impl<T: Time> Readable for ChannelLiquidity<T> {
21522165
let mut min_liquidity_offset_history: Option<HistoricalBucketRangeTracker> = None;
21532166
let mut max_liquidity_offset_history: Option<HistoricalBucketRangeTracker> = None;
21542167
let mut duration_since_epoch = Duration::from_secs(0);
2168+
let mut offset_history_duration_since_epoch = None;
21552169
read_tlv_fields!(r, {
21562170
(0, min_liquidity_offset_msat, required),
21572171
(1, legacy_min_liq_offset_history, option),
@@ -2160,6 +2174,7 @@ impl<T: Time> Readable for ChannelLiquidity<T> {
21602174
(4, duration_since_epoch, required),
21612175
(5, min_liquidity_offset_history, option),
21622176
(7, max_liquidity_offset_history, option),
2177+
(9, offset_history_duration_since_epoch, option),
21632178
});
21642179
// On rust prior to 1.60 `Instant::duration_since` will panic if time goes backwards.
21652180
// We write `last_updated` as wallclock time even though its ultimately an `Instant` (which
@@ -2173,6 +2188,13 @@ impl<T: Time> Readable for ChannelLiquidity<T> {
21732188
let last_updated = if wall_clock_now > duration_since_epoch {
21742189
now - (wall_clock_now - duration_since_epoch)
21752190
} else { now };
2191+
2192+
let offset_history_duration_since_epoch =
2193+
offset_history_duration_since_epoch.unwrap_or(duration_since_epoch);
2194+
let offset_history_last_updated = if wall_clock_now > offset_history_duration_since_epoch {
2195+
now - (wall_clock_now - offset_history_duration_since_epoch)
2196+
} else { now };
2197+
21762198
if min_liquidity_offset_history.is_none() {
21772199
if let Some(legacy_buckets) = legacy_min_liq_offset_history {
21782200
min_liquidity_offset_history = Some(legacy_buckets.into_current());
@@ -2193,6 +2215,7 @@ impl<T: Time> Readable for ChannelLiquidity<T> {
21932215
min_liquidity_offset_history: min_liquidity_offset_history.unwrap(),
21942216
max_liquidity_offset_history: max_liquidity_offset_history.unwrap(),
21952217
last_updated,
2218+
offset_history_last_updated,
21962219
})
21972220
}
21982221
}
@@ -2368,18 +2391,21 @@ mod tests {
23682391
fn liquidity_bounds_directed_from_lowest_node_id() {
23692392
let logger = TestLogger::new();
23702393
let last_updated = SinceEpoch::now();
2394+
let offset_history_last_updated = SinceEpoch::now();
23712395
let network_graph = network_graph(&logger);
23722396
let decay_params = ProbabilisticScoringDecayParameters::default();
23732397
let mut scorer = ProbabilisticScorer::new(decay_params, &network_graph, &logger)
23742398
.with_channel(42,
23752399
ChannelLiquidity {
2376-
min_liquidity_offset_msat: 700, max_liquidity_offset_msat: 100, last_updated,
2400+
min_liquidity_offset_msat: 700, max_liquidity_offset_msat: 100,
2401+
last_updated, offset_history_last_updated,
23772402
min_liquidity_offset_history: HistoricalBucketRangeTracker::new(),
23782403
max_liquidity_offset_history: HistoricalBucketRangeTracker::new(),
23792404
})
23802405
.with_channel(43,
23812406
ChannelLiquidity {
2382-
min_liquidity_offset_msat: 700, max_liquidity_offset_msat: 100, last_updated,
2407+
min_liquidity_offset_msat: 700, max_liquidity_offset_msat: 100,
2408+
last_updated, offset_history_last_updated,
23832409
min_liquidity_offset_history: HistoricalBucketRangeTracker::new(),
23842410
max_liquidity_offset_history: HistoricalBucketRangeTracker::new(),
23852411
});
@@ -2446,12 +2472,14 @@ mod tests {
24462472
fn resets_liquidity_upper_bound_when_crossed_by_lower_bound() {
24472473
let logger = TestLogger::new();
24482474
let last_updated = SinceEpoch::now();
2475+
let offset_history_last_updated = SinceEpoch::now();
24492476
let network_graph = network_graph(&logger);
24502477
let decay_params = ProbabilisticScoringDecayParameters::default();
24512478
let mut scorer = ProbabilisticScorer::new(decay_params, &network_graph, &logger)
24522479
.with_channel(42,
24532480
ChannelLiquidity {
2454-
min_liquidity_offset_msat: 200, max_liquidity_offset_msat: 400, last_updated,
2481+
min_liquidity_offset_msat: 200, max_liquidity_offset_msat: 400,
2482+
last_updated, offset_history_last_updated,
24552483
min_liquidity_offset_history: HistoricalBucketRangeTracker::new(),
24562484
max_liquidity_offset_history: HistoricalBucketRangeTracker::new(),
24572485
});
@@ -2505,12 +2533,14 @@ mod tests {
25052533
fn resets_liquidity_lower_bound_when_crossed_by_upper_bound() {
25062534
let logger = TestLogger::new();
25072535
let last_updated = SinceEpoch::now();
2536+
let offset_history_last_updated = SinceEpoch::now();
25082537
let network_graph = network_graph(&logger);
25092538
let decay_params = ProbabilisticScoringDecayParameters::default();
25102539
let mut scorer = ProbabilisticScorer::new(decay_params, &network_graph, &logger)
25112540
.with_channel(42,
25122541
ChannelLiquidity {
2513-
min_liquidity_offset_msat: 200, max_liquidity_offset_msat: 400, last_updated,
2542+
min_liquidity_offset_msat: 200, max_liquidity_offset_msat: 400,
2543+
last_updated, offset_history_last_updated,
25142544
min_liquidity_offset_history: HistoricalBucketRangeTracker::new(),
25152545
max_liquidity_offset_history: HistoricalBucketRangeTracker::new(),
25162546
});
@@ -2616,6 +2646,7 @@ mod tests {
26162646
fn constant_penalty_outside_liquidity_bounds() {
26172647
let logger = TestLogger::new();
26182648
let last_updated = SinceEpoch::now();
2649+
let offset_history_last_updated = SinceEpoch::now();
26192650
let network_graph = network_graph(&logger);
26202651
let params = ProbabilisticScoringFeeParameters {
26212652
liquidity_penalty_multiplier_msat: 1_000,
@@ -2628,7 +2659,8 @@ mod tests {
26282659
let scorer = ProbabilisticScorer::new(decay_params, &network_graph, &logger)
26292660
.with_channel(42,
26302661
ChannelLiquidity {
2631-
min_liquidity_offset_msat: 40, max_liquidity_offset_msat: 40, last_updated,
2662+
min_liquidity_offset_msat: 40, max_liquidity_offset_msat: 40,
2663+
last_updated, offset_history_last_updated,
26322664
min_liquidity_offset_history: HistoricalBucketRangeTracker::new(),
26332665
max_liquidity_offset_history: HistoricalBucketRangeTracker::new(),
26342666
});

0 commit comments

Comments
 (0)