X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=blobdiff_plain;f=lightning%2Fsrc%2Frouting%2Fscoring.rs;h=4cb9144d3394ed94896b861e5ac847994776f122;hb=59778dac488cff735004671cdefb3f4ac1f920fd;hp=a74331df7e1e804295bcb67b624b47e398595978;hpb=d54c93065acedba911d04ef8dcc48e199c452406;p=rust-lightning diff --git a/lightning/src/routing/scoring.rs b/lightning/src/routing/scoring.rs index a74331df..4cb9144d 100644 --- a/lightning/src/routing/scoring.rs +++ b/lightning/src/routing/scoring.rs @@ -58,19 +58,21 @@ use crate::ln::msgs::DecodeError; use crate::routing::gossip::{EffectiveCapacity, NetworkGraph, NodeId}; -use crate::routing::router::{Path, CandidateRouteHop}; +use crate::routing::router::{Path, CandidateRouteHop, PublicHopCandidate}; use crate::util::ser::{Readable, ReadableArgs, Writeable, Writer}; use crate::util::logger::Logger; -use crate::util::time::Time; use crate::prelude::*; use core::{cmp, fmt}; -use core::cell::{RefCell, RefMut, Ref}; -use core::convert::TryInto; use core::ops::{Deref, DerefMut}; use core::time::Duration; use crate::io::{self, Read}; -use crate::sync::{Mutex, MutexGuard, RwLock, RwLockReadGuard, RwLockWriteGuard}; +use crate::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard}; +#[cfg(not(c_bindings))] +use { + core::cell::{RefCell, RefMut, Ref}, + crate::sync::{Mutex, MutexGuard}, +}; /// We define Score ever-so-slightly differently based on whether we are being built for C bindings /// or not. For users, `LockableScore` must somehow be writeable to disk. For Rust users, this is @@ -248,7 +250,7 @@ impl<'a, T: Score + 'a> LockableScore<'a> for RefCell { } } -#[cfg(not(c_bindings))] +#[cfg(any(not(c_bindings), feature = "_test_utils", test))] impl<'a, T: Score + 'a> LockableScore<'a> for RwLock { type ScoreUpdate = T; type ScoreLookUp = T; @@ -440,13 +442,6 @@ impl ReadableArgs for FixedPenaltyScorer { } } -#[cfg(not(feature = "no-std"))] -type ConfiguredTime = crate::util::time::MonotonicTime; -#[cfg(feature = "no-std")] -use crate::util::time::Eternity; -#[cfg(feature = "no-std")] -type ConfiguredTime = Eternity; - /// [`ScoreLookUp`] implementation using channel success probability distributions. /// /// Channels are tracked with upper and lower liquidity bounds - when an HTLC fails at a channel, @@ -472,28 +467,18 @@ type ConfiguredTime = Eternity; /// formula, but using the history of a channel rather than our latest estimates for the liquidity /// bounds. /// -/// # Note -/// -/// Mixing the `no-std` feature between serialization and deserialization results in undefined -/// behavior. -/// /// [1]: https://arxiv.org/abs/2107.05322 /// [`liquidity_penalty_multiplier_msat`]: ProbabilisticScoringFeeParameters::liquidity_penalty_multiplier_msat /// [`liquidity_penalty_amount_multiplier_msat`]: ProbabilisticScoringFeeParameters::liquidity_penalty_amount_multiplier_msat /// [`liquidity_offset_half_life`]: ProbabilisticScoringDecayParameters::liquidity_offset_half_life /// [`historical_liquidity_penalty_multiplier_msat`]: ProbabilisticScoringFeeParameters::historical_liquidity_penalty_multiplier_msat /// [`historical_liquidity_penalty_amount_multiplier_msat`]: ProbabilisticScoringFeeParameters::historical_liquidity_penalty_amount_multiplier_msat -pub type ProbabilisticScorer = ProbabilisticScorerUsingTime::; - -/// Probabilistic [`ScoreLookUp`] implementation. -/// -/// This is not exported to bindings users generally all users should use the [`ProbabilisticScorer`] type alias. -pub struct ProbabilisticScorerUsingTime>, L: Deref, T: Time> +pub struct ProbabilisticScorer>, L: Deref> where L::Target: Logger { decay_params: ProbabilisticScoringDecayParameters, network_graph: G, logger: L, - channel_liquidities: HashMap>, + channel_liquidities: HashMap, } /// Parameters for configuring [`ProbabilisticScorer`]. @@ -667,7 +652,7 @@ impl Default for ProbabilisticScoringFeeParameters { base_penalty_amount_multiplier_msat: 8192, liquidity_penalty_multiplier_msat: 30_000, liquidity_penalty_amount_multiplier_msat: 192, - manual_node_penalties: HashMap::new(), + manual_node_penalties: new_hash_map(), anti_probing_penalty_msat: 250, considered_impossible_penalty_msat: 1_0000_0000_000, historical_liquidity_penalty_multiplier_msat: 10_000, @@ -709,7 +694,7 @@ impl ProbabilisticScoringFeeParameters { /// Clears the list of manual penalties that are applied during path finding. pub fn clear_manual_penalties(&mut self) { - self.manual_node_penalties = HashMap::new(); + self.manual_node_penalties = new_hash_map(); } } @@ -723,7 +708,7 @@ impl ProbabilisticScoringFeeParameters { liquidity_penalty_amount_multiplier_msat: 0, historical_liquidity_penalty_multiplier_msat: 0, historical_liquidity_penalty_amount_multiplier_msat: 0, - manual_node_penalties: HashMap::new(), + manual_node_penalties: new_hash_map(), anti_probing_penalty_msat: 0, considered_impossible_penalty_msat: 0, linear_success_probability: true, @@ -748,7 +733,7 @@ pub struct ProbabilisticScoringDecayParameters { /// /// Default value: 14 days /// - /// [`historical_estimated_channel_liquidity_probabilities`]: ProbabilisticScorerUsingTime::historical_estimated_channel_liquidity_probabilities + /// [`historical_estimated_channel_liquidity_probabilities`]: ProbabilisticScorer::historical_estimated_channel_liquidity_probabilities pub historical_no_updates_half_life: Duration, /// Whenever this amount of time elapses since the last update to a channel's liquidity bounds, @@ -797,7 +782,7 @@ impl ProbabilisticScoringDecayParameters { /// Direction is defined in terms of [`NodeId`] partial ordering, where the source node is the /// first node in the ordering of the channel's counterparties. Thus, swapping the two liquidity /// offset fields gives the opposite direction. -struct ChannelLiquidity { +struct ChannelLiquidity { /// Lower channel liquidity bound in terms of an offset from zero. min_liquidity_offset_msat: u64, @@ -807,27 +792,25 @@ struct ChannelLiquidity { min_liquidity_offset_history: HistoricalBucketRangeTracker, max_liquidity_offset_history: HistoricalBucketRangeTracker, - /// Time when the liquidity bounds were last modified. - last_updated: T, + /// Time when either liquidity bound was last modified as an offset since the unix epoch. + last_updated: Duration, - /// Time when the historical liquidity bounds were last modified. - offset_history_last_updated: T, + /// Time when the historical liquidity bounds were last modified as an offset against the unix + /// epoch. + offset_history_last_updated: Duration, } -/// A snapshot of [`ChannelLiquidity`] in one direction assuming a certain channel capacity and -/// decayed with a given half life. -struct DirectedChannelLiquidity, BRT: Deref, T: Time, U: Deref> { +/// A snapshot of [`ChannelLiquidity`] in one direction assuming a certain channel capacity. +struct DirectedChannelLiquidity, BRT: Deref, T: Deref> { min_liquidity_offset_msat: L, max_liquidity_offset_msat: L, liquidity_history: HistoricalMinMaxBuckets, capacity_msat: u64, - last_updated: U, - offset_history_last_updated: U, - now: T, - decay_params: ProbabilisticScoringDecayParameters, + last_updated: T, + offset_history_last_updated: T, } -impl>, L: Deref, T: Time> ProbabilisticScorerUsingTime where L::Target: Logger { +impl>, L: Deref> ProbabilisticScorer where L::Target: Logger { /// Creates a new scorer using the given scoring parameters for sending payments from a node /// through a network graph. pub fn new(decay_params: ProbabilisticScoringDecayParameters, network_graph: G, logger: L) -> Self { @@ -835,12 +818,12 @@ impl>, L: Deref, T: Time> ProbabilisticScorerU decay_params, network_graph, logger, - channel_liquidities: HashMap::new(), + channel_liquidities: new_hash_map(), } } #[cfg(test)] - fn with_channel(mut self, short_channel_id: u64, liquidity: ChannelLiquidity) -> Self { + fn with_channel(mut self, short_channel_id: u64, liquidity: ChannelLiquidity) -> Self { assert!(self.channel_liquidities.insert(short_channel_id, liquidity).is_none()); self } @@ -856,7 +839,7 @@ impl>, L: Deref, T: Time> ProbabilisticScorerU let log_direction = |source, target| { if let Some((directed_info, _)) = chan_debug.as_directed_to(target) { let amt = directed_info.effective_capacity().as_msat(); - let dir_liq = liq.as_directed(source, target, amt, self.decay_params); + let dir_liq = liq.as_directed(source, target, amt); let min_buckets = &dir_liq.liquidity_history.min_liquidity_offset_history.buckets; let max_buckets = &dir_liq.liquidity_history.max_liquidity_offset_history.buckets; @@ -908,7 +891,7 @@ impl>, L: Deref, T: Time> ProbabilisticScorerU if let Some(liq) = self.channel_liquidities.get(&scid) { if let Some((directed_info, source)) = chan.as_directed_to(target) { let amt = directed_info.effective_capacity().as_msat(); - let dir_liq = liq.as_directed(source, target, amt, self.decay_params); + let dir_liq = liq.as_directed(source, target, amt); return Some((dir_liq.min_liquidity_msat(), dir_liq.max_liquidity_msat())); } } @@ -950,7 +933,7 @@ impl>, L: Deref, T: Time> ProbabilisticScorerU if let Some(liq) = self.channel_liquidities.get(&scid) { if let Some((directed_info, source)) = chan.as_directed_to(target) { let amt = directed_info.effective_capacity().as_msat(); - let dir_liq = liq.as_directed(source, target, amt, self.decay_params); + let dir_liq = liq.as_directed(source, target, amt); let min_buckets = dir_liq.liquidity_history.min_liquidity_offset_history.buckets; let mut max_buckets = dir_liq.liquidity_history.max_liquidity_offset_history.buckets; @@ -981,7 +964,7 @@ impl>, L: Deref, T: Time> ProbabilisticScorerU if let Some(liq) = self.channel_liquidities.get(&scid) { if let Some((directed_info, source)) = chan.as_directed_to(target) { let capacity_msat = directed_info.effective_capacity().as_msat(); - let dir_liq = liq.as_directed(source, target, capacity_msat, self.decay_params); + let dir_liq = liq.as_directed(source, target, capacity_msat); return dir_liq.liquidity_history.calculate_success_probability_times_billion( ¶ms, amount_msat, capacity_msat @@ -993,24 +976,23 @@ impl>, L: Deref, T: Time> ProbabilisticScorerU } } -impl ChannelLiquidity { - #[inline] - fn new() -> Self { +impl ChannelLiquidity { + fn new(last_updated: Duration) -> Self { Self { min_liquidity_offset_msat: 0, max_liquidity_offset_msat: 0, min_liquidity_offset_history: HistoricalBucketRangeTracker::new(), max_liquidity_offset_history: HistoricalBucketRangeTracker::new(), - last_updated: T::now(), - offset_history_last_updated: T::now(), + last_updated, + offset_history_last_updated: last_updated, } } /// Returns a view of the channel liquidity directed from `source` to `target` assuming /// `capacity_msat`. fn as_directed( - &self, source: &NodeId, target: &NodeId, capacity_msat: u64, decay_params: ProbabilisticScoringDecayParameters - ) -> DirectedChannelLiquidity<&u64, &HistoricalBucketRangeTracker, T, &T> { + &self, source: &NodeId, target: &NodeId, capacity_msat: u64, + ) -> DirectedChannelLiquidity<&u64, &HistoricalBucketRangeTracker, &Duration> { let (min_liquidity_offset_msat, max_liquidity_offset_msat, min_liquidity_offset_history, max_liquidity_offset_history) = if source < target { (&self.min_liquidity_offset_msat, &self.max_liquidity_offset_msat, @@ -1030,16 +1012,14 @@ impl ChannelLiquidity { capacity_msat, last_updated: &self.last_updated, offset_history_last_updated: &self.offset_history_last_updated, - now: T::now(), - decay_params: decay_params, } } /// Returns a mutable view of the channel liquidity directed from `source` to `target` assuming /// `capacity_msat`. fn as_directed_mut( - &mut self, source: &NodeId, target: &NodeId, capacity_msat: u64, decay_params: ProbabilisticScoringDecayParameters - ) -> DirectedChannelLiquidity<&mut u64, &mut HistoricalBucketRangeTracker, T, &mut T> { + &mut self, source: &NodeId, target: &NodeId, capacity_msat: u64, + ) -> DirectedChannelLiquidity<&mut u64, &mut HistoricalBucketRangeTracker, &mut Duration> { let (min_liquidity_offset_msat, max_liquidity_offset_msat, min_liquidity_offset_history, max_liquidity_offset_history) = if source < target { (&mut self.min_liquidity_offset_msat, &mut self.max_liquidity_offset_msat, @@ -1059,8 +1039,6 @@ impl ChannelLiquidity { 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, } } @@ -1070,7 +1048,7 @@ impl ChannelLiquidity { ) -> u64 { let half_life = decay_params.liquidity_offset_half_life.as_secs_f64(); if half_life != 0.0 { - let elapsed_time = T::now().duration_since(self.last_updated).as_secs_f64(); + let elapsed_time = duration_since_epoch.saturating_sub(self.last_updated).as_secs_f64(); ((offset as f64) * powf64(0.5, elapsed_time / half_life)) as u64 } else { 0 @@ -1159,7 +1137,8 @@ fn success_probability( (numerator, denominator) } -impl, BRT: Deref, T: Time, U: Deref> DirectedChannelLiquidity< L, BRT, T, U> { +impl, BRT: Deref, T: Deref> +DirectedChannelLiquidity< L, BRT, T> { /// Returns a liquidity penalty for routing the given HTLC `amount_msat` through the channel in /// this direction. fn penalty_msat(&self, amount_msat: u64, score_params: &ProbabilisticScoringFeeParameters) -> u64 { @@ -1252,22 +1231,19 @@ impl, BRT: Deref, /// Returns the lower bound of the channel liquidity balance in this direction. #[inline(always)] fn min_liquidity_msat(&self) -> u64 { - self.decayed_offset_msat(*self.min_liquidity_offset_msat) + *self.min_liquidity_offset_msat } /// Returns the upper bound of the channel liquidity balance in this direction. #[inline(always)] fn max_liquidity_msat(&self) -> u64 { self.capacity_msat - .saturating_sub(self.decayed_offset_msat(*self.max_liquidity_offset_msat)) - } - - fn decayed_offset_msat(&self, offset_msat: u64) -> u64 { - offset_msat + .saturating_sub(*self.max_liquidity_offset_msat) } } -impl, BRT: DerefMut, T: Time, U: DerefMut> DirectedChannelLiquidity { +impl, BRT: DerefMut, T: DerefMut> +DirectedChannelLiquidity { /// Adjusts the channel liquidity balance bounds when failing to route `amount_msat`. fn failed_at_channel( &mut self, amount_msat: u64, duration_since_epoch: Duration, chan_descr: fmt::Arguments, logger: &Log @@ -1313,59 +1289,47 @@ impl, BRT: DerefMut self.max_liquidity_msat() { - 0 - } else { - self.decayed_offset_msat(*self.max_liquidity_offset_msat) - }; - *self.last_updated = self.now; + if amount_msat > self.max_liquidity_msat() { + *self.max_liquidity_offset_msat = 0; + } + *self.last_updated = duration_since_epoch; } /// Adjusts the upper bound of the channel liquidity balance in this direction. fn set_max_liquidity_msat(&mut self, amount_msat: u64, duration_since_epoch: Duration) { *self.max_liquidity_offset_msat = self.capacity_msat.checked_sub(amount_msat).unwrap_or(0); - *self.min_liquidity_offset_msat = if amount_msat < self.min_liquidity_msat() { - 0 - } else { - self.decayed_offset_msat(*self.min_liquidity_offset_msat) - }; - *self.last_updated = self.now; + if amount_msat < *self.min_liquidity_offset_msat { + *self.min_liquidity_offset_msat = 0; + } + *self.last_updated = duration_since_epoch; } } -impl>, L: Deref, T: Time> ScoreLookUp for ProbabilisticScorerUsingTime where L::Target: Logger { +impl>, L: Deref> ScoreLookUp for ProbabilisticScorer where L::Target: Logger { type ScoreParams = ProbabilisticScoringFeeParameters; fn channel_penalty_msat( &self, candidate: &CandidateRouteHop, usage: ChannelUsage, score_params: &ProbabilisticScoringFeeParameters ) -> u64 { let (scid, target) = match candidate { - CandidateRouteHop::PublicHop { info, short_channel_id } => { + CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id }) => { (short_channel_id, info.target()) }, _ => return 0, }; let source = candidate.source(); - if let Some(penalty) = score_params.manual_node_penalties.get(&target) { + if let Some(penalty) = score_params.manual_node_penalties.get(target) { return *penalty; } @@ -1395,16 +1359,16 @@ impl>, L: Deref, T: Time> ScoreLookUp for Prob let amount_msat = usage.amount_msat.saturating_add(usage.inflight_htlc_msat); let capacity_msat = usage.effective_capacity.as_msat(); self.channel_liquidities - .get(&scid) - .unwrap_or(&ChannelLiquidity::new()) - .as_directed(&source, &target, capacity_msat, self.decay_params) + .get(scid) + .unwrap_or(&ChannelLiquidity::new(Duration::ZERO)) + .as_directed(&source, &target, capacity_msat) .penalty_msat(amount_msat, score_params) .saturating_add(anti_probing_penalty_msat) .saturating_add(base_penalty_msat) } } -impl>, L: Deref, T: Time> ScoreUpdate for ProbabilisticScorerUsingTime where L::Target: Logger { +impl>, L: Deref> ScoreUpdate for ProbabilisticScorer where L::Target: Logger { fn payment_path_failed(&mut self, path: &Path, short_channel_id: u64, duration_since_epoch: Duration) { let amount_msat = path.final_value_msat(); log_trace!(self.logger, "Scoring path through to SCID {} as having failed at {} msat", short_channel_id, amount_msat); @@ -1426,15 +1390,15 @@ impl>, L: Deref, T: Time> ScoreUpdate for Prob if at_failed_channel { self.channel_liquidities .entry(hop.short_channel_id) - .or_insert_with(ChannelLiquidity::new) - .as_directed_mut(source, &target, capacity_msat, self.decay_params) + .or_insert_with(|| ChannelLiquidity::new(duration_since_epoch)) + .as_directed_mut(source, &target, capacity_msat) .failed_at_channel(amount_msat, duration_since_epoch, format_args!("SCID {}, towards {:?}", hop.short_channel_id, target), &self.logger); } else { self.channel_liquidities .entry(hop.short_channel_id) - .or_insert_with(ChannelLiquidity::new) - .as_directed_mut(source, &target, capacity_msat, self.decay_params) + .or_insert_with(|| ChannelLiquidity::new(duration_since_epoch)) + .as_directed_mut(source, &target, capacity_msat) .failed_downstream(amount_msat, duration_since_epoch, format_args!("SCID {}, towards {:?}", hop.short_channel_id, target), &self.logger); } @@ -1462,8 +1426,8 @@ impl>, L: Deref, T: Time> ScoreUpdate for Prob let capacity_msat = channel.effective_capacity().as_msat(); self.channel_liquidities .entry(hop.short_channel_id) - .or_insert_with(ChannelLiquidity::new) - .as_directed_mut(source, &target, capacity_msat, self.decay_params) + .or_insert_with(|| ChannelLiquidity::new(duration_since_epoch)) + .as_directed_mut(source, &target, capacity_msat) .successful(amount_msat, duration_since_epoch, format_args!("SCID {}, towards {:?}", hop.short_channel_id, target), &self.logger); } else { @@ -1488,10 +1452,10 @@ impl>, L: Deref, T: Time> ScoreUpdate for Prob liquidity.decayed_offset(liquidity.min_liquidity_offset_msat, duration_since_epoch, decay_params); liquidity.max_liquidity_offset_msat = liquidity.decayed_offset(liquidity.max_liquidity_offset_msat, duration_since_epoch, decay_params); - liquidity.last_updated = T::now(); + liquidity.last_updated = duration_since_epoch; let elapsed_time = - T::now().duration_since(liquidity.offset_history_last_updated); + duration_since_epoch.saturating_sub(liquidity.offset_history_last_updated); if elapsed_time > decay_params.historical_no_updates_half_life { let half_life = decay_params.historical_no_updates_half_life.as_secs_f64(); if half_life != 0.0 { @@ -1502,7 +1466,7 @@ impl>, L: Deref, T: Time> ScoreUpdate for Prob for bucket in liquidity.max_liquidity_offset_history.buckets.iter_mut() { *bucket = ((*bucket as u64) * 1024 / divisor) as u16; } - liquidity.offset_history_last_updated = T::now(); + liquidity.offset_history_last_updated = duration_since_epoch; } } liquidity.min_liquidity_offset_msat != 0 || liquidity.max_liquidity_offset_msat != 0 || @@ -1513,7 +1477,7 @@ impl>, L: Deref, T: Time> ScoreUpdate for Prob } #[cfg(c_bindings)] -impl>, L: Deref, T: Time> Score for ProbabilisticScorerUsingTime +impl>, L: Deref> Score for ProbabilisticScorer where L::Target: Logger {} #[cfg(feature = "std")] @@ -1987,14 +1951,6 @@ mod bucketed_history { self.buckets[bucket] = self.buckets[bucket].saturating_add(BUCKET_FIXED_POINT_ONE); } } - /// Decay all buckets by the given number of half-lives. Used to more aggressively remove old - /// datapoints as we receive newer information. - #[inline] - pub(super) fn time_decay_data(&mut self, half_lives: u32) { - for e in self.buckets.iter_mut() { - *e = e.checked_shr(half_lives).unwrap_or(0); - } - } } impl_writeable_tlv_based!(HistoricalBucketRangeTracker, { (0, buckets, required) }); @@ -2099,7 +2055,7 @@ mod bucketed_history { } use bucketed_history::{LegacyHistoricalBucketRangeTracker, HistoricalBucketRangeTracker, HistoricalMinMaxBuckets}; -impl>, L: Deref, T: Time> Writeable for ProbabilisticScorerUsingTime where L::Target: Logger { +impl>, L: Deref> Writeable for ProbabilisticScorer where L::Target: Logger { #[inline] fn write(&self, w: &mut W) -> Result<(), io::Error> { write_tlv_fields!(w, { @@ -2109,14 +2065,14 @@ impl>, L: Deref, T: Time> Writeable for Probab } } -impl>, L: Deref, T: Time> -ReadableArgs<(ProbabilisticScoringDecayParameters, G, L)> for ProbabilisticScorerUsingTime where L::Target: Logger { +impl>, L: Deref> +ReadableArgs<(ProbabilisticScoringDecayParameters, G, L)> for ProbabilisticScorer where L::Target: Logger { #[inline] fn read( r: &mut R, args: (ProbabilisticScoringDecayParameters, G, L) ) -> Result { let (decay_params, network_graph, logger) = args; - let mut channel_liquidities = HashMap::new(); + let mut channel_liquidities = new_hash_map(); read_tlv_fields!(r, { (0, channel_liquidities, required), }); @@ -2129,27 +2085,24 @@ ReadableArgs<(ProbabilisticScoringDecayParameters, G, L)> for ProbabilisticScore } } -impl Writeable for ChannelLiquidity { +impl Writeable for ChannelLiquidity { #[inline] fn write(&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), // 1 was the min_liquidity_offset_history in octile form (2, self.max_liquidity_offset_msat, required), // 3 was the max_liquidity_offset_history in octile form - (4, duration_since_epoch, required), + (4, self.last_updated, required), (5, Some(self.min_liquidity_offset_history), option), (7, Some(self.max_liquidity_offset_history), option), - (9, offset_history_duration_since_epoch, required), + (9, self.offset_history_last_updated, required), }); Ok(()) } } -impl Readable for ChannelLiquidity { +impl Readable for ChannelLiquidity { #[inline] fn read(r: &mut R) -> Result { let mut min_liquidity_offset_msat = 0; @@ -2158,36 +2111,18 @@ impl Readable for ChannelLiquidity { let mut legacy_max_liq_offset_history: Option = None; let mut min_liquidity_offset_history: Option = None; let mut max_liquidity_offset_history: Option = None; - let mut duration_since_epoch = Duration::from_secs(0); - let mut offset_history_duration_since_epoch = None; + let mut last_updated = Duration::from_secs(0); + let mut offset_history_last_updated = None; read_tlv_fields!(r, { (0, min_liquidity_offset_msat, required), (1, legacy_min_liq_offset_history, option), (2, max_liquidity_offset_msat, required), (3, legacy_max_liq_offset_history, option), - (4, duration_since_epoch, required), + (4, last_updated, required), (5, min_liquidity_offset_history, option), (7, max_liquidity_offset_history, option), - (9, offset_history_duration_since_epoch, option), + (9, offset_history_last_updated, 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 - // is a time from a monotonic clock usually represented as an offset against boot time). - // Thus, we have to construct an `Instant` by subtracting the difference in wallclock time - // from the one that was written. However, because `Instant` can panic if we construct one - // in the future, we must handle wallclock time jumping backwards, which we do by simply - // using `Instant::now()` in that case. - let wall_clock_now = T::duration_since_epoch(); - let now = T::now(); - 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 { @@ -2209,23 +2144,21 @@ impl Readable for ChannelLiquidity { min_liquidity_offset_history: min_liquidity_offset_history.unwrap(), max_liquidity_offset_history: max_liquidity_offset_history.unwrap(), last_updated, - offset_history_last_updated, + offset_history_last_updated: offset_history_last_updated.unwrap_or(last_updated), }) } } #[cfg(test)] mod tests { - use super::{ChannelLiquidity, HistoricalBucketRangeTracker, ProbabilisticScoringFeeParameters, ProbabilisticScoringDecayParameters, ProbabilisticScorerUsingTime}; - use crate::blinded_path::{BlindedHop, BlindedPath}; + use super::{ChannelLiquidity, HistoricalBucketRangeTracker, ProbabilisticScoringFeeParameters, ProbabilisticScoringDecayParameters, ProbabilisticScorer}; + use crate::blinded_path::{BlindedHop, BlindedPath, IntroductionNode}; use crate::util::config::UserConfig; - use crate::util::time::Time; - use crate::util::time::tests::SinceEpoch; use crate::ln::channelmanager; use crate::ln::msgs::{ChannelAnnouncement, ChannelUpdate, UnsignedChannelAnnouncement, UnsignedChannelUpdate}; use crate::routing::gossip::{EffectiveCapacity, NetworkGraph, NodeId}; - use crate::routing::router::{BlindedTail, Path, RouteHop, CandidateRouteHop}; + use crate::routing::router::{BlindedTail, Path, RouteHop, CandidateRouteHop, PublicHopCandidate}; use crate::routing::scoring::{ChannelUsage, ScoreLookUp, ScoreUpdate}; use crate::util::ser::{ReadableArgs, Writeable}; use crate::util::test_utils::{self, TestLogger}; @@ -2266,9 +2199,6 @@ mod tests { // `ProbabilisticScorer` tests - /// A probabilistic scorer for testing with time that can be manually advanced. - type ProbabilisticScorer<'a> = ProbabilisticScorerUsingTime::<&'a NetworkGraph<&'a TestLogger>, &'a TestLogger, SinceEpoch>; - fn sender_privkey() -> SecretKey { SecretKey::from_slice(&[41; 32]).unwrap() } @@ -2384,8 +2314,8 @@ mod tests { #[test] 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 last_updated = Duration::ZERO; + let offset_history_last_updated = Duration::ZERO; let network_graph = network_graph(&logger); let decay_params = ProbabilisticScoringDecayParameters::default(); let mut scorer = ProbabilisticScorer::new(decay_params, &network_graph, &logger) @@ -2412,52 +2342,52 @@ mod tests { // Update minimum liquidity. let liquidity = scorer.channel_liquidities.get(&42).unwrap() - .as_directed(&source, &target, 1_000, decay_params); + .as_directed(&source, &target, 1_000); assert_eq!(liquidity.min_liquidity_msat(), 100); assert_eq!(liquidity.max_liquidity_msat(), 300); let liquidity = scorer.channel_liquidities.get(&42).unwrap() - .as_directed(&target, &source, 1_000, decay_params); + .as_directed(&target, &source, 1_000); assert_eq!(liquidity.min_liquidity_msat(), 700); assert_eq!(liquidity.max_liquidity_msat(), 900); scorer.channel_liquidities.get_mut(&42).unwrap() - .as_directed_mut(&source, &target, 1_000, decay_params) + .as_directed_mut(&source, &target, 1_000) .set_min_liquidity_msat(200, Duration::ZERO); let liquidity = scorer.channel_liquidities.get(&42).unwrap() - .as_directed(&source, &target, 1_000, decay_params); + .as_directed(&source, &target, 1_000); assert_eq!(liquidity.min_liquidity_msat(), 200); assert_eq!(liquidity.max_liquidity_msat(), 300); let liquidity = scorer.channel_liquidities.get(&42).unwrap() - .as_directed(&target, &source, 1_000, decay_params); + .as_directed(&target, &source, 1_000); assert_eq!(liquidity.min_liquidity_msat(), 700); assert_eq!(liquidity.max_liquidity_msat(), 800); // Update maximum liquidity. let liquidity = scorer.channel_liquidities.get(&43).unwrap() - .as_directed(&target, &recipient, 1_000, decay_params); + .as_directed(&target, &recipient, 1_000); assert_eq!(liquidity.min_liquidity_msat(), 700); assert_eq!(liquidity.max_liquidity_msat(), 900); let liquidity = scorer.channel_liquidities.get(&43).unwrap() - .as_directed(&recipient, &target, 1_000, decay_params); + .as_directed(&recipient, &target, 1_000); assert_eq!(liquidity.min_liquidity_msat(), 100); assert_eq!(liquidity.max_liquidity_msat(), 300); scorer.channel_liquidities.get_mut(&43).unwrap() - .as_directed_mut(&target, &recipient, 1_000, decay_params) + .as_directed_mut(&target, &recipient, 1_000) .set_max_liquidity_msat(200, Duration::ZERO); let liquidity = scorer.channel_liquidities.get(&43).unwrap() - .as_directed(&target, &recipient, 1_000, decay_params); + .as_directed(&target, &recipient, 1_000); assert_eq!(liquidity.min_liquidity_msat(), 0); assert_eq!(liquidity.max_liquidity_msat(), 200); let liquidity = scorer.channel_liquidities.get(&43).unwrap() - .as_directed(&recipient, &target, 1_000, decay_params); + .as_directed(&recipient, &target, 1_000); assert_eq!(liquidity.min_liquidity_msat(), 800); assert_eq!(liquidity.max_liquidity_msat(), 1000); } @@ -2465,8 +2395,8 @@ mod tests { #[test] 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 last_updated = Duration::ZERO; + let offset_history_last_updated = Duration::ZERO; let network_graph = network_graph(&logger); let decay_params = ProbabilisticScoringDecayParameters::default(); let mut scorer = ProbabilisticScorer::new(decay_params, &network_graph, &logger) @@ -2483,42 +2413,42 @@ mod tests { // Check initial bounds. let liquidity = scorer.channel_liquidities.get(&42).unwrap() - .as_directed(&source, &target, 1_000, decay_params); + .as_directed(&source, &target, 1_000); assert_eq!(liquidity.min_liquidity_msat(), 400); assert_eq!(liquidity.max_liquidity_msat(), 800); let liquidity = scorer.channel_liquidities.get(&42).unwrap() - .as_directed(&target, &source, 1_000, decay_params); + .as_directed(&target, &source, 1_000); assert_eq!(liquidity.min_liquidity_msat(), 200); assert_eq!(liquidity.max_liquidity_msat(), 600); // Reset from source to target. scorer.channel_liquidities.get_mut(&42).unwrap() - .as_directed_mut(&source, &target, 1_000, decay_params) + .as_directed_mut(&source, &target, 1_000) .set_min_liquidity_msat(900, Duration::ZERO); let liquidity = scorer.channel_liquidities.get(&42).unwrap() - .as_directed(&source, &target, 1_000, decay_params); + .as_directed(&source, &target, 1_000); assert_eq!(liquidity.min_liquidity_msat(), 900); assert_eq!(liquidity.max_liquidity_msat(), 1_000); let liquidity = scorer.channel_liquidities.get(&42).unwrap() - .as_directed(&target, &source, 1_000, decay_params); + .as_directed(&target, &source, 1_000); assert_eq!(liquidity.min_liquidity_msat(), 0); assert_eq!(liquidity.max_liquidity_msat(), 100); // Reset from target to source. scorer.channel_liquidities.get_mut(&42).unwrap() - .as_directed_mut(&target, &source, 1_000, decay_params) + .as_directed_mut(&target, &source, 1_000) .set_min_liquidity_msat(400, Duration::ZERO); let liquidity = scorer.channel_liquidities.get(&42).unwrap() - .as_directed(&source, &target, 1_000, decay_params); + .as_directed(&source, &target, 1_000); assert_eq!(liquidity.min_liquidity_msat(), 0); assert_eq!(liquidity.max_liquidity_msat(), 600); let liquidity = scorer.channel_liquidities.get(&42).unwrap() - .as_directed(&target, &source, 1_000, decay_params); + .as_directed(&target, &source, 1_000); assert_eq!(liquidity.min_liquidity_msat(), 400); assert_eq!(liquidity.max_liquidity_msat(), 1_000); } @@ -2526,8 +2456,8 @@ mod tests { #[test] 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 last_updated = Duration::ZERO; + let offset_history_last_updated = Duration::ZERO; let network_graph = network_graph(&logger); let decay_params = ProbabilisticScoringDecayParameters::default(); let mut scorer = ProbabilisticScorer::new(decay_params, &network_graph, &logger) @@ -2544,42 +2474,42 @@ mod tests { // Check initial bounds. let liquidity = scorer.channel_liquidities.get(&42).unwrap() - .as_directed(&source, &target, 1_000, decay_params); + .as_directed(&source, &target, 1_000); assert_eq!(liquidity.min_liquidity_msat(), 400); assert_eq!(liquidity.max_liquidity_msat(), 800); let liquidity = scorer.channel_liquidities.get(&42).unwrap() - .as_directed(&target, &source, 1_000, decay_params); + .as_directed(&target, &source, 1_000); assert_eq!(liquidity.min_liquidity_msat(), 200); assert_eq!(liquidity.max_liquidity_msat(), 600); // Reset from source to target. scorer.channel_liquidities.get_mut(&42).unwrap() - .as_directed_mut(&source, &target, 1_000, decay_params) + .as_directed_mut(&source, &target, 1_000) .set_max_liquidity_msat(300, Duration::ZERO); let liquidity = scorer.channel_liquidities.get(&42).unwrap() - .as_directed(&source, &target, 1_000, decay_params); + .as_directed(&source, &target, 1_000); assert_eq!(liquidity.min_liquidity_msat(), 0); assert_eq!(liquidity.max_liquidity_msat(), 300); let liquidity = scorer.channel_liquidities.get(&42).unwrap() - .as_directed(&target, &source, 1_000, decay_params); + .as_directed(&target, &source, 1_000); assert_eq!(liquidity.min_liquidity_msat(), 700); assert_eq!(liquidity.max_liquidity_msat(), 1_000); // Reset from target to source. scorer.channel_liquidities.get_mut(&42).unwrap() - .as_directed_mut(&target, &source, 1_000, decay_params) + .as_directed_mut(&target, &source, 1_000) .set_max_liquidity_msat(600, Duration::ZERO); let liquidity = scorer.channel_liquidities.get(&42).unwrap() - .as_directed(&source, &target, 1_000, decay_params); + .as_directed(&source, &target, 1_000); assert_eq!(liquidity.min_liquidity_msat(), 400); assert_eq!(liquidity.max_liquidity_msat(), 1_000); let liquidity = scorer.channel_liquidities.get(&42).unwrap() - .as_directed(&target, &source, 1_000, decay_params); + .as_directed(&target, &source, 1_000); assert_eq!(liquidity.min_liquidity_msat(), 0); assert_eq!(liquidity.max_liquidity_msat(), 600); } @@ -2604,10 +2534,10 @@ mod tests { let network_graph = network_graph.read_only(); let channel = network_graph.channel(42).unwrap(); let (info, _) = channel.as_directed_from(&source).unwrap(); - let candidate = CandidateRouteHop::PublicHop { + let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 42, - }; + }); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 0); let usage = ChannelUsage { amount_msat: 10_240, ..usage }; assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 0); @@ -2639,8 +2569,8 @@ mod tests { #[test] fn constant_penalty_outside_liquidity_bounds() { let logger = TestLogger::new(); - let last_updated = SinceEpoch::now(); - let offset_history_last_updated = SinceEpoch::now(); + let last_updated = Duration::ZERO; + let offset_history_last_updated = Duration::ZERO; let network_graph = network_graph(&logger); let params = ProbabilisticScoringFeeParameters { liquidity_penalty_multiplier_msat: 1_000, @@ -2667,10 +2597,10 @@ mod tests { }; let channel = network_graph.read_only().channel(42).unwrap().to_owned(); let (info, _) = channel.as_directed_from(&source).unwrap(); - let candidate = CandidateRouteHop::PublicHop { + let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 42, - }; + }); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 0); let usage = ChannelUsage { amount_msat: 50, ..usage }; assert_ne!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 0); @@ -2698,10 +2628,10 @@ mod tests { let successful_path = payment_path_for_amount(200); let channel = &network_graph.read_only().channel(42).unwrap().to_owned(); let (info, _) = channel.as_directed_from(&source).unwrap(); - let candidate = CandidateRouteHop::PublicHop { + let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 41, - }; + }); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 301); @@ -2731,10 +2661,10 @@ mod tests { }; let channel = network_graph.read_only().channel(42).unwrap().to_owned(); let (info, _) = channel.as_directed_from(&source).unwrap(); - let candidate = CandidateRouteHop::PublicHop { + let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 42, - }; + }); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 128); let usage = ChannelUsage { amount_msat: 500, ..usage }; assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 301); @@ -2771,10 +2701,10 @@ mod tests { }; let channel = network_graph.read_only().channel(42).unwrap().to_owned(); let (info, _) = channel.as_directed_from(&source).unwrap(); - let candidate = CandidateRouteHop::PublicHop { + let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 42, - }; + }); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 128); let usage = ChannelUsage { amount_msat: 500, ..usage }; assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 301); @@ -2837,50 +2767,50 @@ mod tests { }; let channel = network_graph.read_only().channel(42).unwrap().to_owned(); let (info, _) = channel.as_directed_from(&node_a).unwrap(); - let candidate = CandidateRouteHop::PublicHop { + let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 42, - }; + }); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 128); // Note that a default liquidity bound is used for B -> C as no channel exists let channel = network_graph.read_only().channel(42).unwrap().to_owned(); let (info, _) = channel.as_directed_from(&node_b).unwrap(); - let candidate = CandidateRouteHop::PublicHop { + let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 43, - }; + }); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 128); let channel = network_graph.read_only().channel(44).unwrap().to_owned(); let (info, _) = channel.as_directed_from(&node_c).unwrap(); - let candidate = CandidateRouteHop::PublicHop { + let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 44, - }; + }); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 128); scorer.payment_path_failed(&Path { hops: path, blinded_tail: None }, 43, Duration::ZERO); let channel = network_graph.read_only().channel(42).unwrap().to_owned(); let (info, _) = channel.as_directed_from(&node_a).unwrap(); - let candidate = CandidateRouteHop::PublicHop { + let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 42, - }; + }); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 80); // Note that a default liquidity bound is used for B -> C as no channel exists let channel = network_graph.read_only().channel(42).unwrap().to_owned(); let (info, _) = channel.as_directed_from(&node_b).unwrap(); - let candidate = CandidateRouteHop::PublicHop { + let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 43, - }; + }); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 128); let channel = network_graph.read_only().channel(44).unwrap().to_owned(); let (info, _) = channel.as_directed_from(&node_c).unwrap(); - let candidate = CandidateRouteHop::PublicHop { + let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 44, - }; + }); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 128); } @@ -2903,20 +2833,20 @@ mod tests { let channel_42 = network_graph.get(&42).unwrap(); let channel_43 = network_graph.get(&43).unwrap(); let (info, _) = channel_42.as_directed_from(&source).unwrap(); - let candidate_41 = CandidateRouteHop::PublicHop { + let candidate_41 = CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 41, - }; + }); let (info, target) = channel_42.as_directed_from(&source).unwrap(); - let candidate_42 = CandidateRouteHop::PublicHop { + let candidate_42 = CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 42, - }; + }); let (info, _) = channel_43.as_directed_from(&target).unwrap(); - let candidate_43 = CandidateRouteHop::PublicHop { + let candidate_43 = CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 43, - }; + }); assert_eq!(scorer.channel_penalty_msat(&candidate_41, usage, ¶ms), 128); assert_eq!(scorer.channel_penalty_msat(&candidate_42, usage, ¶ms), 128); assert_eq!(scorer.channel_penalty_msat(&candidate_43, usage, ¶ms), 128); @@ -2951,10 +2881,10 @@ mod tests { }; let channel = network_graph.read_only().channel(42).unwrap().to_owned(); let (info, _) = channel.as_directed_from(&source).unwrap(); - let candidate = CandidateRouteHop::PublicHop { + let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 42, - }; + }); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 0); let usage = ChannelUsage { amount_msat: 1_023, ..usage }; assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 2_000); @@ -2973,7 +2903,6 @@ mod tests { assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), u64::max_value()); // Half decay (i.e., three-quarter life) - SinceEpoch::advance(Duration::from_secs(5)); scorer.time_passed(Duration::from_secs(5)); let usage = ChannelUsage { amount_msat: 128, ..usage }; assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 22); @@ -2985,7 +2914,6 @@ mod tests { assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), u64::max_value()); // One decay (i.e., half life) - SinceEpoch::advance(Duration::from_secs(5)); scorer.time_passed(Duration::from_secs(10)); let usage = ChannelUsage { amount_msat: 64, ..usage }; assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 0); @@ -2997,7 +2925,6 @@ mod tests { assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), u64::max_value()); // Fully decay liquidity lower bound. - SinceEpoch::advance(Duration::from_secs(10 * 7)); scorer.time_passed(Duration::from_secs(10 * 8)); let usage = ChannelUsage { amount_msat: 0, ..usage }; assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 0); @@ -3009,14 +2936,12 @@ mod tests { assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), u64::max_value()); // Fully decay liquidity upper bound. - SinceEpoch::advance(Duration::from_secs(10)); scorer.time_passed(Duration::from_secs(10 * 9)); let usage = ChannelUsage { amount_msat: 0, ..usage }; assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 0); let usage = ChannelUsage { amount_msat: 1_024, ..usage }; assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), u64::max_value()); - SinceEpoch::advance(Duration::from_secs(10)); scorer.time_passed(Duration::from_secs(10 * 10)); let usage = ChannelUsage { amount_msat: 0, ..usage }; assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 0); @@ -3024,47 +2949,6 @@ mod tests { assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), u64::max_value()); } - #[test] - fn decays_liquidity_bounds_without_shift_overflow() { - let logger = TestLogger::new(); - let network_graph = network_graph(&logger); - let params = ProbabilisticScoringFeeParameters { - liquidity_penalty_multiplier_msat: 1_000, - ..ProbabilisticScoringFeeParameters::zero_penalty() - }; - let decay_params = ProbabilisticScoringDecayParameters { - liquidity_offset_half_life: Duration::from_secs(10), - ..ProbabilisticScoringDecayParameters::default() - }; - let mut scorer = ProbabilisticScorer::new(decay_params, &network_graph, &logger); - let source = source_node_id(); - let usage = ChannelUsage { - amount_msat: 256, - inflight_htlc_msat: 0, - effective_capacity: EffectiveCapacity::Total { capacity_msat: 1_024, htlc_maximum_msat: 1_000 }, - }; - let channel = network_graph.read_only().channel(42).unwrap().to_owned(); - let (info, _) = channel.as_directed_from(&source).unwrap(); - let candidate = CandidateRouteHop::PublicHop { - info, - short_channel_id: 42, - }; - assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 125); - - scorer.payment_path_failed(&payment_path_for_amount(512), 42, Duration::ZERO); - assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 281); - - // An unchecked right shift 64 bits or more in DirectedChannelLiquidity::decayed_offset_msat - // would cause an overflow. - SinceEpoch::advance(Duration::from_secs(10 * 64)); - scorer.time_passed(Duration::from_secs(10 * 64)); - assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 125); - - SinceEpoch::advance(Duration::from_secs(10)); - scorer.time_passed(Duration::from_secs(10 * 65)); - assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 125); - } - #[test] fn restricts_liquidity_bounds_after_decay() { let logger = TestLogger::new(); @@ -3086,10 +2970,10 @@ mod tests { }; let channel = network_graph.read_only().channel(42).unwrap().to_owned(); let (info, _) = channel.as_directed_from(&source).unwrap(); - let candidate = CandidateRouteHop::PublicHop { + let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 42, - }; + }); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 300); @@ -3099,7 +2983,6 @@ mod tests { assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 281); // Decaying knowledge gives less confidence (128, 896), meaning a higher penalty. - SinceEpoch::advance(Duration::from_secs(10)); scorer.time_passed(Duration::from_secs(10)); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 291); @@ -3114,7 +2997,6 @@ mod tests { assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 245); // Further decaying affects the lower bound more than the upper bound (128, 928). - SinceEpoch::advance(Duration::from_secs(10)); scorer.time_passed(Duration::from_secs(20)); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 280); } @@ -3143,13 +3025,12 @@ mod tests { scorer.payment_path_failed(&payment_path_for_amount(500), 42, Duration::ZERO); let channel = network_graph.read_only().channel(42).unwrap().to_owned(); let (info, _) = channel.as_directed_from(&source).unwrap(); - let candidate = CandidateRouteHop::PublicHop { + let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 42, - }; + }); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), u64::max_value()); - SinceEpoch::advance(Duration::from_secs(10)); scorer.time_passed(Duration::from_secs(10)); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 473); @@ -3161,7 +3042,7 @@ mod tests { let mut serialized_scorer = io::Cursor::new(&serialized_scorer); let deserialized_scorer = - ::read(&mut serialized_scorer, (decay_params, &network_graph, &logger)).unwrap(); + >::read(&mut serialized_scorer, (decay_params, &network_graph, &logger)).unwrap(); assert_eq!(deserialized_scorer.channel_penalty_msat(&candidate, usage, ¶ms), 300); } @@ -3188,14 +3069,13 @@ mod tests { scorer.payment_path_failed(&payment_path_for_amount(500), 42, Duration::ZERO); let channel = network_graph.read_only().channel(42).unwrap().to_owned(); let (info, _) = channel.as_directed_from(&source).unwrap(); - let candidate = CandidateRouteHop::PublicHop { + let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 42, - }; + }); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), u64::max_value()); if decay_before_reload { - SinceEpoch::advance(Duration::from_secs(10)); scorer.time_passed(Duration::from_secs(10)); } @@ -3204,9 +3084,8 @@ mod tests { let mut serialized_scorer = io::Cursor::new(&serialized_scorer); let mut deserialized_scorer = - ::read(&mut serialized_scorer, (decay_params, &network_graph, &logger)).unwrap(); + >::read(&mut serialized_scorer, (decay_params, &network_graph, &logger)).unwrap(); if !decay_before_reload { - SinceEpoch::advance(Duration::from_secs(10)); scorer.time_passed(Duration::from_secs(10)); deserialized_scorer.time_passed(Duration::from_secs(10)); } @@ -3215,7 +3094,6 @@ mod tests { scorer.payment_path_failed(&payment_path_for_amount(250), 43, Duration::from_secs(10)); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 300); - SinceEpoch::advance(Duration::from_secs(10)); deserialized_scorer.time_passed(Duration::from_secs(20)); assert_eq!(deserialized_scorer.channel_penalty_msat(&candidate, usage, ¶ms), 370); } @@ -3243,10 +3121,10 @@ mod tests { }; let channel = network_graph.read_only().channel(42).unwrap().to_owned(); let (info, _) = channel.as_directed_from(&source).unwrap(); - let candidate = CandidateRouteHop::PublicHop { + let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 42, - }; + }); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 11497); let usage = ChannelUsage { effective_capacity: EffectiveCapacity::Total { capacity_msat: 1_950_000_000, htlc_maximum_msat: 1_000 }, ..usage @@ -3308,10 +3186,10 @@ mod tests { let scorer = ProbabilisticScorer::new(ProbabilisticScoringDecayParameters::default(), &network_graph, &logger); let channel = network_graph.read_only().channel(42).unwrap().to_owned(); let (info, _) = channel.as_directed_from(&source).unwrap(); - let candidate = CandidateRouteHop::PublicHop { + let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 42, - }; + }); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 58); let params = ProbabilisticScoringFeeParameters { @@ -3350,10 +3228,10 @@ mod tests { let scorer = ProbabilisticScorer::new(ProbabilisticScoringDecayParameters::default(), &network_graph, &logger); let channel = network_graph.read_only().channel(42).unwrap().to_owned(); let (info, _) = channel.as_directed_from(&source).unwrap(); - let candidate = CandidateRouteHop::PublicHop { + let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 42, - }; + }); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 300); let params = ProbabilisticScoringFeeParameters { @@ -3382,10 +3260,10 @@ mod tests { let decay_params = ProbabilisticScoringDecayParameters::zero_penalty(); let channel = network_graph.read_only().channel(42).unwrap().to_owned(); let (info, _) = channel.as_directed_from(&source).unwrap(); - let candidate = CandidateRouteHop::PublicHop { + let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 42, - }; + }); let scorer = ProbabilisticScorer::new(decay_params, &network_graph, &logger); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 80_000); } @@ -3409,10 +3287,10 @@ mod tests { let network_graph = network_graph.read_only(); let channel = network_graph.channel(42).unwrap(); let (info, _) = channel.as_directed_from(&source).unwrap(); - let candidate = CandidateRouteHop::PublicHop { + let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 42, - }; + }); assert_ne!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), u64::max_value()); let usage = ChannelUsage { inflight_htlc_msat: 251, ..usage }; @@ -3436,10 +3314,10 @@ mod tests { let network_graph = network_graph.read_only(); let channel = network_graph.channel(42).unwrap(); let (info, _) = channel.as_directed_from(&source).unwrap(); - let candidate = CandidateRouteHop::PublicHop { + let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 42, - }; + }); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), base_penalty_msat); let usage = ChannelUsage { amount_msat: 1_000, ..usage }; @@ -3481,10 +3359,10 @@ mod tests { let network_graph = network_graph.read_only(); let channel = network_graph.channel(42).unwrap(); let (info, _) = channel.as_directed_from(&source).unwrap(); - let candidate = CandidateRouteHop::PublicHop { + let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 42, - }; + }); // With no historical data the normal liquidity penalty calculation is used. assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 168); @@ -3499,10 +3377,10 @@ mod tests { let network_graph = network_graph.read_only(); let channel = network_graph.channel(42).unwrap(); let (info, _) = channel.as_directed_from(&source).unwrap(); - let candidate = CandidateRouteHop::PublicHop { + let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 42, - }; + }); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 2048); assert_eq!(scorer.channel_penalty_msat(&candidate, usage_1, ¶ms), 249); @@ -3524,10 +3402,10 @@ mod tests { let network_graph = network_graph.read_only(); let channel = network_graph.channel(42).unwrap(); let (info, _) = channel.as_directed_from(&source).unwrap(); - let candidate = CandidateRouteHop::PublicHop { + let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 42, - }; + }); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 105); } @@ -3549,16 +3427,15 @@ mod tests { // Advance the time forward 16 half-lives (which the docs claim will ensure all data is // gone), and check that we're back to where we started. - SinceEpoch::advance(Duration::from_secs(10 * 16)); scorer.time_passed(Duration::from_secs(10 * 16)); { let network_graph = network_graph.read_only(); let channel = network_graph.channel(42).unwrap(); let (info, _) = channel.as_directed_from(&source).unwrap(); - let candidate = CandidateRouteHop::PublicHop { + let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 42, - }; + }); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 168); } @@ -3568,7 +3445,7 @@ mod tests { Some(([0; 32], [0; 32]))); assert_eq!(scorer.historical_estimated_payment_success_probability(42, &target, 1, ¶ms), None); - let mut usage = ChannelUsage { + let usage = ChannelUsage { amount_msat: 100, inflight_htlc_msat: 1024, effective_capacity: EffectiveCapacity::Total { capacity_msat: 1_024, htlc_maximum_msat: 1_024 }, @@ -3578,10 +3455,10 @@ mod tests { let network_graph = network_graph.read_only(); let channel = network_graph.channel(42).unwrap(); let (info, _) = channel.as_directed_from(&source).unwrap(); - let candidate = CandidateRouteHop::PublicHop { + let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 42, - }; + }); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 2050); @@ -3594,7 +3471,6 @@ mod tests { } // Advance to decay all liquidity offsets to zero. - SinceEpoch::advance(Duration::from_secs(60 * 60 * 10)); scorer.time_passed(Duration::from_secs(10 * (16 + 60 * 60))); // Once even the bounds have decayed information about the channel should be removed @@ -3632,10 +3508,10 @@ mod tests { let network_graph = network_graph.read_only(); let channel = network_graph.channel(42).unwrap(); let (info, _) = channel.as_directed_from(&source).unwrap(); - let candidate = CandidateRouteHop::PublicHop { + let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 42, - }; + }); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 0); // Check we receive anti-probing penalty for htlc_maximum_msat == channel_capacity. @@ -3682,16 +3558,16 @@ mod tests { }; let channel = network_graph.read_only().channel(42).unwrap().to_owned(); let (info, target) = channel.as_directed_from(&source).unwrap(); - let candidate = CandidateRouteHop::PublicHop { + let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 42, - }; + }); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 300); let mut path = payment_path_for_amount(768); let recipient_hop = path.hops.pop().unwrap(); let blinded_path = BlindedPath { - introduction_node_id: path.hops.last().as_ref().unwrap().pubkey, + introduction_node: IntroductionNode::NodeId(path.hops.last().as_ref().unwrap().pubkey), blinding_point: test_utils::pubkey(42), blinded_hops: vec![ BlindedHop { blinded_node_id: test_utils::pubkey(44), encrypted_payload: Vec::new() } @@ -3713,7 +3589,7 @@ mod tests { scorer.payment_path_failed(&path, 43, Duration::ZERO); let liquidity = scorer.channel_liquidities.get(&42).unwrap() - .as_directed(&source, &target, 1_000, decay_params); + .as_directed(&source, &target, 1_000); assert_eq!(liquidity.min_liquidity_msat(), 256); assert_eq!(liquidity.max_liquidity_msat(), 768); } @@ -3751,10 +3627,10 @@ mod tests { }; let channel = network_graph.read_only().channel(42).unwrap().to_owned(); let (info, target) = channel.as_directed_from(&source).unwrap(); - let candidate = CandidateRouteHop::PublicHop { + let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 42, - }; + }); // With no historical data the normal liquidity penalty calculation is used, which results // in a success probability of ~75%. assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 1269); @@ -3796,3 +3672,61 @@ mod tests { Some(0.0)); } } + +#[cfg(ldk_bench)] +pub mod benches { + use super::*; + use criterion::Criterion; + use crate::routing::router::{bench_utils, RouteHop}; + use crate::util::test_utils::TestLogger; + use crate::ln::features::{ChannelFeatures, NodeFeatures}; + + pub fn decay_100k_channel_bounds(bench: &mut Criterion) { + let logger = TestLogger::new(); + let network_graph = bench_utils::read_network_graph(&logger).unwrap(); + let mut scorer = ProbabilisticScorer::new(Default::default(), &network_graph, &logger); + // Score a number of random channels + let mut seed: u64 = 0xdeadbeef; + for _ in 0..100_000 { + seed = seed.overflowing_mul(6364136223846793005).0.overflowing_add(1).0; + let (victim, victim_dst, amt) = { + let rong = network_graph.read_only(); + let channels = rong.channels(); + let chan = channels.unordered_iter() + .skip((seed as usize) % channels.len()) + .next().unwrap(); + seed = seed.overflowing_mul(6364136223846793005).0.overflowing_add(1).0; + let amt = seed % chan.1.capacity_sats.map(|c| c * 1000) + .or(chan.1.one_to_two.as_ref().map(|info| info.htlc_maximum_msat)) + .or(chan.1.two_to_one.as_ref().map(|info| info.htlc_maximum_msat)) + .unwrap_or(1_000_000_000).saturating_add(1); + (*chan.0, chan.1.node_two, amt) + }; + let path = Path { + hops: vec![RouteHop { + pubkey: victim_dst.as_pubkey().unwrap(), + node_features: NodeFeatures::empty(), + short_channel_id: victim, + channel_features: ChannelFeatures::empty(), + fee_msat: amt, + cltv_expiry_delta: 42, + maybe_announced_channel: true, + }], + blinded_tail: None + }; + seed = seed.overflowing_mul(6364136223846793005).0.overflowing_add(1).0; + if seed % 1 == 0 { + scorer.probe_failed(&path, victim, Duration::ZERO); + } else { + scorer.probe_successful(&path, Duration::ZERO); + } + } + let mut cur_time = Duration::ZERO; + cur_time += Duration::from_millis(1); + scorer.time_passed(cur_time); + bench.bench_function("decay_100k_channel_bounds", |b| b.iter(|| { + cur_time += Duration::from_millis(1); + scorer.time_passed(cur_time); + })); + } +}