/// Upper channel liquidity bound in terms of an offset from the effective capacity.
max_liquidity_offset_msat: u64,
+ min_liquidity_offset_history: HistoricalBucketRangeTracker,
+ max_liquidity_offset_history: HistoricalBucketRangeTracker,
+
/// Time when the liquidity bounds were last modified.
last_updated: T,
- min_liquidity_offset_history: HistoricalBucketRangeTracker,
- max_liquidity_offset_history: HistoricalBucketRangeTracker,
+ /// Time when the historical liquidity bounds were last modified.
+ offset_history_last_updated: T,
}
/// A snapshot of [`ChannelLiquidity`] in one direction assuming a certain channel capacity and
liquidity_history: HistoricalMinMaxBuckets<BRT>,
capacity_msat: u64,
last_updated: U,
+ offset_history_last_updated: U,
now: T,
decay_params: ProbabilisticScoringDecayParameters,
}
let dir_liq = liq.as_directed(source, target, amt, self.decay_params);
let (min_buckets, max_buckets) = dir_liq.liquidity_history
- .get_decayed_buckets(now, *dir_liq.last_updated,
+ .get_decayed_buckets(now, *dir_liq.offset_history_last_updated,
self.decay_params.historical_no_updates_half_life)
.unwrap_or(([0; 32], [0; 32]));
let (min_buckets, mut max_buckets) =
dir_liq.liquidity_history.get_decayed_buckets(
- dir_liq.now, *dir_liq.last_updated,
+ dir_liq.now, *dir_liq.offset_history_last_updated,
self.decay_params.historical_no_updates_half_life
)?;
let dir_liq = liq.as_directed(source, target, capacity_msat, self.decay_params);
return dir_liq.liquidity_history.calculate_success_probability_times_billion(
- dir_liq.now, *dir_liq.last_updated,
+ dir_liq.now, *dir_liq.offset_history_last_updated,
self.decay_params.historical_no_updates_half_life, ¶ms, amount_msat,
capacity_msat
).map(|p| p as f64 / (1024 * 1024 * 1024) as f64);
min_liquidity_offset_history: HistoricalBucketRangeTracker::new(),
max_liquidity_offset_history: HistoricalBucketRangeTracker::new(),
last_updated: T::now(),
+ offset_history_last_updated: T::now(),
}
}
},
capacity_msat,
last_updated: &self.last_updated,
+ offset_history_last_updated: &self.offset_history_last_updated,
now: T::now(),
decay_params: decay_params,
}
},
capacity_msat,
last_updated: &mut self.last_updated,
+ offset_history_last_updated: &mut self.offset_history_last_updated,
now: T::now(),
decay_params: decay_params,
}
if score_params.historical_liquidity_penalty_multiplier_msat != 0 ||
score_params.historical_liquidity_penalty_amount_multiplier_msat != 0 {
if let Some(cumulative_success_prob_times_billion) = self.liquidity_history
- .calculate_success_probability_times_billion(self.now, *self.last_updated,
+ .calculate_success_probability_times_billion(
+ self.now, *self.offset_history_last_updated,
self.decay_params.historical_no_updates_half_life, score_params, amount_msat,
self.capacity_msat)
{
/// state"), we allow the caller to set an offset applied to our liquidity bounds which
/// represents the amount of the successful payment we just made.
fn update_history_buckets(&mut self, bucket_offset_msat: u64) {
- let half_lives = self.now.duration_since(*self.last_updated).as_secs()
+ let half_lives = self.now.duration_since(*self.offset_history_last_updated).as_secs()
.checked_div(self.decay_params.historical_no_updates_half_life.as_secs())
.map(|v| v.try_into().unwrap_or(u32::max_value())).unwrap_or(u32::max_value());
self.liquidity_history.min_liquidity_offset_history.time_decay_data(half_lives);
self.decayed_offset_msat(*self.max_liquidity_offset_msat)
};
*self.last_updated = self.now;
+ *self.offset_history_last_updated = self.now;
}
/// Adjusts the upper bound of the channel liquidity balance in this direction.
self.decayed_offset_msat(*self.min_liquidity_offset_msat)
};
*self.last_updated = self.now;
+ *self.offset_history_last_updated = self.now;
}
}
}
impl<D: Deref<Target = HistoricalBucketRangeTracker>> HistoricalMinMaxBuckets<D> {
- pub(super) fn get_decayed_buckets<T: Time>(&self, now: T, last_updated: T, half_life: Duration)
+ pub(super) fn get_decayed_buckets<T: Time>(&self, now: T, offset_history_last_updated: T, half_life: Duration)
-> Option<([u16; 32], [u16; 32])> {
- let (_, required_decays) = self.get_total_valid_points(now, last_updated, half_life)?;
+ let (_, required_decays) = self.get_total_valid_points(now, offset_history_last_updated, half_life)?;
let mut min_buckets = *self.min_liquidity_offset_history;
min_buckets.time_decay_data(required_decays);
Some((min_buckets.buckets, max_buckets.buckets))
}
#[inline]
- pub(super) fn get_total_valid_points<T: Time>(&self, now: T, last_updated: T, half_life: Duration)
+ pub(super) fn get_total_valid_points<T: Time>(&self, now: T, offset_history_last_updated: T, half_life: Duration)
-> Option<(u64, u32)> {
- let required_decays = now.duration_since(last_updated).as_secs()
+ let required_decays = now.duration_since(offset_history_last_updated).as_secs()
.checked_div(half_life.as_secs())
.map_or(u32::max_value(), |decays| cmp::min(decays, u32::max_value() as u64) as u32);
#[inline]
pub(super) fn calculate_success_probability_times_billion<T: Time>(
- &self, now: T, last_updated: T, half_life: Duration,
+ &self, now: T, offset_history_last_updated: T, half_life: Duration,
params: &ProbabilisticScoringFeeParameters, amount_msat: u64, capacity_msat: u64
) -> Option<u64> {
// If historical penalties are enabled, we try to calculate a probability of success
// Check if all our buckets are zero, once decayed and treat it as if we had no data. We
// don't actually use the decayed buckets, though, as that would lose precision.
let (total_valid_points_tracked, _)
- = self.get_total_valid_points(now, last_updated, half_life)?;
+ = self.get_total_valid_points(now, offset_history_last_updated, half_life)?;
let mut cumulative_success_prob_times_billion = 0;
// Special-case the 0th min bucket - it generally means we failed a payment, so only
impl<T: Time> Writeable for ChannelLiquidity<T> {
#[inline]
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
+ let offset_history_duration_since_epoch =
+ T::duration_since_epoch() - self.offset_history_last_updated.elapsed();
let duration_since_epoch = T::duration_since_epoch() - self.last_updated.elapsed();
write_tlv_fields!(w, {
(0, self.min_liquidity_offset_msat, required),
(4, duration_since_epoch, required),
(5, Some(self.min_liquidity_offset_history), option),
(7, Some(self.max_liquidity_offset_history), option),
+ (9, offset_history_duration_since_epoch, required),
});
Ok(())
}
let mut min_liquidity_offset_history: Option<HistoricalBucketRangeTracker> = None;
let mut max_liquidity_offset_history: Option<HistoricalBucketRangeTracker> = None;
let mut duration_since_epoch = Duration::from_secs(0);
+ let mut offset_history_duration_since_epoch = None;
read_tlv_fields!(r, {
(0, min_liquidity_offset_msat, required),
(1, legacy_min_liq_offset_history, option),
(4, duration_since_epoch, required),
(5, min_liquidity_offset_history, option),
(7, max_liquidity_offset_history, option),
+ (9, offset_history_duration_since_epoch, option),
});
// On rust prior to 1.60 `Instant::duration_since` will panic if time goes backwards.
// We write `last_updated` as wallclock time even though its ultimately an `Instant` (which
let last_updated = if wall_clock_now > duration_since_epoch {
now - (wall_clock_now - duration_since_epoch)
} else { now };
+
+ let offset_history_duration_since_epoch =
+ offset_history_duration_since_epoch.unwrap_or(duration_since_epoch);
+ let offset_history_last_updated = if wall_clock_now > offset_history_duration_since_epoch {
+ now - (wall_clock_now - offset_history_duration_since_epoch)
+ } else { now };
+
if min_liquidity_offset_history.is_none() {
if let Some(legacy_buckets) = legacy_min_liq_offset_history {
min_liquidity_offset_history = Some(legacy_buckets.into_current());
min_liquidity_offset_history: min_liquidity_offset_history.unwrap(),
max_liquidity_offset_history: max_liquidity_offset_history.unwrap(),
last_updated,
+ offset_history_last_updated,
})
}
}
fn liquidity_bounds_directed_from_lowest_node_id() {
let logger = TestLogger::new();
let last_updated = SinceEpoch::now();
+ let offset_history_last_updated = SinceEpoch::now();
let network_graph = network_graph(&logger);
let decay_params = ProbabilisticScoringDecayParameters::default();
let mut scorer = ProbabilisticScorer::new(decay_params, &network_graph, &logger)
.with_channel(42,
ChannelLiquidity {
- min_liquidity_offset_msat: 700, max_liquidity_offset_msat: 100, last_updated,
+ min_liquidity_offset_msat: 700, max_liquidity_offset_msat: 100,
+ last_updated, offset_history_last_updated,
min_liquidity_offset_history: HistoricalBucketRangeTracker::new(),
max_liquidity_offset_history: HistoricalBucketRangeTracker::new(),
})
.with_channel(43,
ChannelLiquidity {
- min_liquidity_offset_msat: 700, max_liquidity_offset_msat: 100, last_updated,
+ min_liquidity_offset_msat: 700, max_liquidity_offset_msat: 100,
+ last_updated, offset_history_last_updated,
min_liquidity_offset_history: HistoricalBucketRangeTracker::new(),
max_liquidity_offset_history: HistoricalBucketRangeTracker::new(),
});
fn resets_liquidity_upper_bound_when_crossed_by_lower_bound() {
let logger = TestLogger::new();
let last_updated = SinceEpoch::now();
+ let offset_history_last_updated = SinceEpoch::now();
let network_graph = network_graph(&logger);
let decay_params = ProbabilisticScoringDecayParameters::default();
let mut scorer = ProbabilisticScorer::new(decay_params, &network_graph, &logger)
.with_channel(42,
ChannelLiquidity {
- min_liquidity_offset_msat: 200, max_liquidity_offset_msat: 400, last_updated,
+ min_liquidity_offset_msat: 200, max_liquidity_offset_msat: 400,
+ last_updated, offset_history_last_updated,
min_liquidity_offset_history: HistoricalBucketRangeTracker::new(),
max_liquidity_offset_history: HistoricalBucketRangeTracker::new(),
});
fn resets_liquidity_lower_bound_when_crossed_by_upper_bound() {
let logger = TestLogger::new();
let last_updated = SinceEpoch::now();
+ let offset_history_last_updated = SinceEpoch::now();
let network_graph = network_graph(&logger);
let decay_params = ProbabilisticScoringDecayParameters::default();
let mut scorer = ProbabilisticScorer::new(decay_params, &network_graph, &logger)
.with_channel(42,
ChannelLiquidity {
- min_liquidity_offset_msat: 200, max_liquidity_offset_msat: 400, last_updated,
+ min_liquidity_offset_msat: 200, max_liquidity_offset_msat: 400,
+ last_updated, offset_history_last_updated,
min_liquidity_offset_history: HistoricalBucketRangeTracker::new(),
max_liquidity_offset_history: HistoricalBucketRangeTracker::new(),
});
fn constant_penalty_outside_liquidity_bounds() {
let logger = TestLogger::new();
let last_updated = SinceEpoch::now();
+ let offset_history_last_updated = SinceEpoch::now();
let network_graph = network_graph(&logger);
let params = ProbabilisticScoringFeeParameters {
liquidity_penalty_multiplier_msat: 1_000,
let scorer = ProbabilisticScorer::new(decay_params, &network_graph, &logger)
.with_channel(42,
ChannelLiquidity {
- min_liquidity_offset_msat: 40, max_liquidity_offset_msat: 40, last_updated,
+ min_liquidity_offset_msat: 40, max_liquidity_offset_msat: 40,
+ last_updated, offset_history_last_updated,
min_liquidity_offset_history: HistoricalBucketRangeTracker::new(),
max_liquidity_offset_history: HistoricalBucketRangeTracker::new(),
});