X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=blobdiff_plain;f=lightning%2Fsrc%2Frouting%2Fscoring.rs;h=aaafbc35b6134f4edd839e13c4b3a4c7830759e2;hb=e9d9711de4ddc20b78eb110abfe400da6eef863d;hp=cae22f76987f159fcdaa223536d5e7a7eeeb7fba;hpb=2bd2637b7e0fe338fa7841ba5473991b0c5150d1;p=rust-lightning diff --git a/lightning/src/routing/scoring.rs b/lightning/src/routing/scoring.rs index cae22f76..aaafbc35 100644 --- a/lightning/src/routing/scoring.rs +++ b/lightning/src/routing/scoring.rs @@ -10,7 +10,7 @@ //! Utilities for scoring payment channels. //! //! [`ProbabilisticScorer`] may be given to [`find_route`] to score payment channels during path -//! finding when a custom [`Score`] implementation is not needed. +//! finding when a custom [`ScoreLookUp`] implementation is not needed. //! //! # Example //! @@ -65,12 +65,12 @@ use crate::util::time::Time; use crate::prelude::*; use core::{cmp, fmt}; -use core::cell::{RefCell, RefMut}; +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}; +use crate::sync::{Mutex, MutexGuard, RwLock, RwLockReadGuard, RwLockWriteGuard}; /// 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 @@ -86,8 +86,10 @@ use crate::sync::{Mutex, MutexGuard}; macro_rules! define_score { ($($supertrait: path)*) => { /// An interface used to score payment channels for path finding. /// -/// Scoring is in terms of fees willing to be paid in order to avoid routing through a channel. -pub trait Score $(: $supertrait)* { +/// `ScoreLookUp` is used to determine the penalty for a given channel. +/// +/// Scoring is in terms of fees willing to be paid in order to avoid routing through a channel. +pub trait ScoreLookUp $(: $supertrait)* { /// A configurable type which should contain various passed-in parameters for configuring the scorer, /// on a per-routefinding-call basis through to the scorer methods, /// which are used to determine the parameters for the suitability of channels for use. @@ -103,7 +105,10 @@ pub trait Score $(: $supertrait)* { fn channel_penalty_msat( &self, short_channel_id: u64, source: &NodeId, target: &NodeId, usage: ChannelUsage, score_params: &Self::ScoreParams ) -> u64; +} +/// `ScoreUpdate` is used to update the scorer's internal state after a payment attempt. +pub trait ScoreUpdate $(: $supertrait)* { /// Handles updating channel penalties after failing to route through a channel. fn payment_path_failed(&mut self, path: &Path, short_channel_id: u64); @@ -117,14 +122,16 @@ pub trait Score $(: $supertrait)* { fn probe_successful(&mut self, path: &Path); } -impl $(+ $supertrait)*> Score for T { - type ScoreParams = S::ScoreParams; +impl, T: Deref $(+ $supertrait)*> ScoreLookUp for T { + type ScoreParams = SP; fn channel_penalty_msat( &self, short_channel_id: u64, source: &NodeId, target: &NodeId, usage: ChannelUsage, score_params: &Self::ScoreParams ) -> u64 { self.deref().channel_penalty_msat(short_channel_id, source, target, usage, score_params) } +} +impl $(+ $supertrait)*> ScoreUpdate for T { fn payment_path_failed(&mut self, path: &Path, short_channel_id: u64) { self.deref_mut().payment_path_failed(path, short_channel_id) } @@ -145,23 +152,35 @@ impl $(+ $supertrait)*> Score for T { #[cfg(c_bindings)] define_score!(Writeable); + #[cfg(not(c_bindings))] define_score!(); /// A scorer that is accessed under a lock. /// -/// Needed so that calls to [`Score::channel_penalty_msat`] in [`find_route`] can be made while -/// having shared ownership of a scorer but without requiring internal locking in [`Score`] +/// Needed so that calls to [`ScoreLookUp::channel_penalty_msat`] in [`find_route`] can be made while +/// having shared ownership of a scorer but without requiring internal locking in [`ScoreUpdate`] /// implementations. Internal locking would be detrimental to route finding performance and could -/// result in [`Score::channel_penalty_msat`] returning a different value for the same channel. +/// result in [`ScoreLookUp::channel_penalty_msat`] returning a different value for the same channel. /// /// [`find_route`]: crate::routing::router::find_route pub trait LockableScore<'a> { - /// The locked [`Score`] type. - type Locked: 'a + Score; + /// The [`ScoreUpdate`] type. + type ScoreUpdate: 'a + ScoreUpdate; + /// The [`ScoreLookUp`] type. + type ScoreLookUp: 'a + ScoreLookUp; + + /// The write locked [`ScoreUpdate`] type. + type WriteLocked: DerefMut + Sized; - /// Returns the locked scorer. - fn lock(&'a self) -> Self::Locked; + /// The read locked [`ScoreLookUp`] type. + type ReadLocked: Deref + Sized; + + /// Returns read locked scorer. + fn read_lock(&'a self) -> Self::ReadLocked; + + /// Returns write locked scorer. + fn write_lock(&'a self) -> Self::WriteLocked; } /// Refers to a scorer that is accessible under lock and also writeable to disk @@ -172,101 +191,139 @@ pub trait WriteableScore<'a>: LockableScore<'a> + Writeable {} #[cfg(not(c_bindings))] impl<'a, T> WriteableScore<'a> for T where T: LockableScore<'a> + Writeable {} -/// This is not exported to bindings users -impl<'a, T: 'a + Score> LockableScore<'a> for Mutex { - type Locked = MutexGuard<'a, T>; +#[cfg(not(c_bindings))] +impl<'a, T: 'a + ScoreLookUp + ScoreUpdate> LockableScore<'a> for Mutex { + type ScoreUpdate = T; + type ScoreLookUp = T; + + type WriteLocked = MutexGuard<'a, Self::ScoreUpdate>; + type ReadLocked = MutexGuard<'a, Self::ScoreLookUp>; - fn lock(&'a self) -> MutexGuard<'a, T> { + fn read_lock(&'a self) -> Self::ReadLocked { + Mutex::lock(self).unwrap() + } + + fn write_lock(&'a self) -> Self::WriteLocked { Mutex::lock(self).unwrap() } } -impl<'a, T: 'a + Score> LockableScore<'a> for RefCell { - type Locked = RefMut<'a, T>; +#[cfg(not(c_bindings))] +impl<'a, T: 'a + ScoreUpdate + ScoreLookUp> LockableScore<'a> for RefCell { + type ScoreUpdate = T; + type ScoreLookUp = T; + + type WriteLocked = RefMut<'a, Self::ScoreUpdate>; + type ReadLocked = Ref<'a, Self::ScoreLookUp>; - fn lock(&'a self) -> RefMut<'a, T> { + fn write_lock(&'a self) -> Self::WriteLocked { self.borrow_mut() } + + fn read_lock(&'a self) -> Self::ReadLocked { + self.borrow() + } +} + +#[cfg(not(c_bindings))] +impl<'a, SP:Sized, T: 'a + ScoreUpdate + ScoreLookUp> LockableScore<'a> for RwLock { + type ScoreUpdate = T; + type ScoreLookUp = T; + + type WriteLocked = RwLockWriteGuard<'a, Self::ScoreLookUp>; + type ReadLocked = RwLockReadGuard<'a, Self::ScoreUpdate>; + + fn read_lock(&'a self) -> Self::ReadLocked { + RwLock::read(self).unwrap() + } + + fn write_lock(&'a self) -> Self::WriteLocked { + RwLock::write(self).unwrap() + } } #[cfg(c_bindings)] /// A concrete implementation of [`LockableScore`] which supports multi-threading. -pub struct MultiThreadedLockableScore { - score: Mutex, +pub struct MultiThreadedLockableScore { + score: RwLock, } + #[cfg(c_bindings)] -/// A locked `MultiThreadedLockableScore`. -pub struct MultiThreadedScoreLock<'a, S: Score>(MutexGuard<'a, S>); -#[cfg(c_bindings)] -impl<'a, T: Score + 'a> Score for MultiThreadedScoreLock<'a, T> { - type ScoreParams = ::ScoreParams; - fn channel_penalty_msat(&self, scid: u64, source: &NodeId, target: &NodeId, usage: ChannelUsage, score_params: &Self::ScoreParams) -> u64 { - self.0.channel_penalty_msat(scid, source, target, usage, score_params) - } - fn payment_path_failed(&mut self, path: &Path, short_channel_id: u64) { - self.0.payment_path_failed(path, short_channel_id) - } - fn payment_path_successful(&mut self, path: &Path) { - self.0.payment_path_successful(path) - } - fn probe_failed(&mut self, path: &Path, short_channel_id: u64) { - self.0.probe_failed(path, short_channel_id) +impl<'a, SP:Sized, T: 'a + ScoreLookUp + ScoreUpdate> LockableScore<'a> for MultiThreadedLockableScore { + type ScoreUpdate = T; + type ScoreLookUp = T; + type WriteLocked = MultiThreadedScoreLockWrite<'a, Self::ScoreUpdate>; + type ReadLocked = MultiThreadedScoreLockRead<'a, Self::ScoreLookUp>; + + fn read_lock(&'a self) -> Self::ReadLocked { + MultiThreadedScoreLockRead(self.score.read().unwrap()) } - fn probe_successful(&mut self, path: &Path) { - self.0.probe_successful(path) + + fn write_lock(&'a self) -> Self::WriteLocked { + MultiThreadedScoreLockWrite(self.score.write().unwrap()) } } + #[cfg(c_bindings)] -impl<'a, T: Score + 'a> Writeable for MultiThreadedScoreLock<'a, T> { +impl Writeable for MultiThreadedLockableScore { fn write(&self, writer: &mut W) -> Result<(), io::Error> { - self.0.write(writer) + self.score.read().unwrap().write(writer) } } #[cfg(c_bindings)] -impl<'a, T: Score + 'a> LockableScore<'a> for MultiThreadedLockableScore { - type Locked = MultiThreadedScoreLock<'a, T>; +impl<'a, T: 'a + ScoreUpdate + ScoreLookUp> WriteableScore<'a> for MultiThreadedLockableScore {} - fn lock(&'a self) -> MultiThreadedScoreLock<'a, T> { - MultiThreadedScoreLock(Mutex::lock(&self.score).unwrap()) +#[cfg(c_bindings)] +impl MultiThreadedLockableScore { + /// Creates a new [`MultiThreadedLockableScore`] given an underlying [`Score`]. + pub fn new(score: T) -> Self { + MultiThreadedLockableScore { score: RwLock::new(score) } } } #[cfg(c_bindings)] -impl Writeable for MultiThreadedLockableScore { - fn write(&self, writer: &mut W) -> Result<(), io::Error> { - self.lock().write(writer) - } -} +/// A locked `MultiThreadedLockableScore`. +pub struct MultiThreadedScoreLockRead<'a, T: ScoreLookUp>(RwLockReadGuard<'a, T>); #[cfg(c_bindings)] -impl<'a, T: Score + 'a> WriteableScore<'a> for MultiThreadedLockableScore {} +/// A locked `MultiThreadedLockableScore`. +pub struct MultiThreadedScoreLockWrite<'a, T: ScoreUpdate>(RwLockWriteGuard<'a, T>); #[cfg(c_bindings)] -impl MultiThreadedLockableScore { - /// Creates a new [`MultiThreadedLockableScore`] given an underlying [`Score`]. - pub fn new(score: T) -> Self { - MultiThreadedLockableScore { score: Mutex::new(score) } +impl<'a, T: 'a + ScoreLookUp> Deref for MultiThreadedScoreLockRead<'a, T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + self.0.deref() } } #[cfg(c_bindings)] -/// This is not exported to bindings users -impl<'a, T: Writeable> Writeable for RefMut<'a, T> { +impl<'a, T: 'a + ScoreUpdate> Writeable for MultiThreadedScoreLockWrite<'a, T> { fn write(&self, writer: &mut W) -> Result<(), io::Error> { - T::write(&**self, writer) + self.0.write(writer) } } #[cfg(c_bindings)] -/// This is not exported to bindings users -impl<'a, S: Writeable> Writeable for MutexGuard<'a, S> { - fn write(&self, writer: &mut W) -> Result<(), io::Error> { - S::write(&**self, writer) +impl<'a, T: 'a + ScoreUpdate> Deref for MultiThreadedScoreLockWrite<'a, T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + self.0.deref() } } -/// Proposed use of a channel passed as a parameter to [`Score::channel_penalty_msat`]. +#[cfg(c_bindings)] +impl<'a, T: 'a + ScoreUpdate> DerefMut for MultiThreadedScoreLockWrite<'a, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + self.0.deref_mut() + } +} + + +/// Proposed use of a channel passed as a parameter to [`ScoreLookUp::channel_penalty_msat`]. #[derive(Clone, Copy, Debug, PartialEq)] pub struct ChannelUsage { /// The amount to send through the channel, denominated in millisatoshis. @@ -281,7 +338,7 @@ pub struct ChannelUsage { } #[derive(Clone)] -/// [`Score`] implementation that uses a fixed penalty. +/// [`ScoreLookUp`] implementation that uses a fixed penalty. pub struct FixedPenaltyScorer { penalty_msat: u64, } @@ -293,12 +350,14 @@ impl FixedPenaltyScorer { } } -impl Score for FixedPenaltyScorer { +impl ScoreLookUp for FixedPenaltyScorer { type ScoreParams = (); fn channel_penalty_msat(&self, _: u64, _: &NodeId, _: &NodeId, _: ChannelUsage, _score_params: &Self::ScoreParams) -> u64 { self.penalty_msat } +} +impl ScoreUpdate for FixedPenaltyScorer { fn payment_path_failed(&mut self, _path: &Path, _short_channel_id: u64) {} fn payment_path_successful(&mut self, _path: &Path) {} @@ -325,13 +384,13 @@ impl ReadableArgs for FixedPenaltyScorer { } #[cfg(not(feature = "no-std"))] -type ConfiguredTime = std::time::Instant; +type ConfiguredTime = crate::util::time::MonotonicTime; #[cfg(feature = "no-std")] use crate::util::time::Eternity; #[cfg(feature = "no-std")] type ConfiguredTime = Eternity; -/// [`Score`] implementation using channel success probability distributions. +/// [`ScoreLookUp`] implementation using channel success probability distributions. /// /// Channels are tracked with upper and lower liquidity bounds - when an HTLC fails at a channel, /// we learn that the upper-bound on the available liquidity is lower than the amount of the HTLC. @@ -369,7 +428,7 @@ type ConfiguredTime = Eternity; /// [`historical_liquidity_penalty_amount_multiplier_msat`]: ProbabilisticScoringFeeParameters::historical_liquidity_penalty_amount_multiplier_msat pub type ProbabilisticScorer = ProbabilisticScorerUsingTime::; -/// Probabilistic [`Score`] implementation. +/// 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> @@ -491,7 +550,7 @@ pub struct ProbabilisticScoringFeeParameters { pub manual_node_penalties: HashMap, /// This penalty is applied when `htlc_maximum_msat` is equal to or larger than half of the - /// channel's capacity, (ie. htlc_maximum_msat ≥ 0.5 * channel_capacity) which makes us + /// channel's capacity, (ie. htlc_maximum_msat >= 0.5 * channel_capacity) which makes us /// prefer nodes with a smaller `htlc_maximum_msat`. We treat such nodes preferentially /// as this makes balance discovery attacks harder to execute, thereby creating an incentive /// to restrict `htlc_maximum_msat` and improve privacy. @@ -1029,10 +1088,25 @@ impl, BRT: Deref, } fn decayed_offset_msat(&self, offset_msat: u64) -> u64 { - self.now.duration_since(*self.last_updated).as_secs() - .checked_div(self.decay_params.liquidity_offset_half_life.as_secs()) - .and_then(|decays| offset_msat.checked_shr(decays as u32)) - .unwrap_or(0) + let half_life = self.decay_params.liquidity_offset_half_life.as_secs(); + if half_life != 0 { + // Decay the offset by the appropriate number of half lives. If half of the next half + // life has passed, approximate an additional three-quarter life to help smooth out the + // decay. + let elapsed_time = self.now.duration_since(*self.last_updated).as_secs(); + let half_decays = elapsed_time / (half_life / 2); + let decays = half_decays / 2; + let decayed_offset_msat = offset_msat.checked_shr(decays as u32).unwrap_or(0); + if half_decays % 2 == 0 { + decayed_offset_msat + } else { + // 11_585 / 16_384 ~= core::f64::consts::FRAC_1_SQRT_2 + // 16_384 == 2^14 + (decayed_offset_msat as u128 * 11_585 / 16_384) as u64 + } + } else { + 0 + } } } @@ -1111,7 +1185,7 @@ impl, BRT: DerefMut>, L: Deref, T: Time> Score for ProbabilisticScorerUsingTime where L::Target: Logger { +impl>, L: Deref, T: Time> ScoreLookUp for ProbabilisticScorerUsingTime where L::Target: Logger { type ScoreParams = ProbabilisticScoringFeeParameters; fn channel_penalty_msat( &self, short_channel_id: u64, source: &NodeId, target: &NodeId, usage: ChannelUsage, score_params: &ProbabilisticScoringFeeParameters @@ -1126,8 +1200,10 @@ impl>, L: Deref, T: Time> Score for Probabilis let mut anti_probing_penalty_msat = 0; match usage.effective_capacity { - EffectiveCapacity::ExactLiquidity { liquidity_msat } => { - if usage.amount_msat > liquidity_msat { + EffectiveCapacity::ExactLiquidity { liquidity_msat: amount_msat } | + EffectiveCapacity::HintMaxHTLC { amount_msat } => + { + if usage.amount_msat > amount_msat { return u64::max_value(); } else { return base_penalty_msat; @@ -1152,7 +1228,9 @@ impl>, L: Deref, T: Time> Score for Probabilis .saturating_add(anti_probing_penalty_msat) .saturating_add(base_penalty_msat) } +} +impl>, L: Deref, T: Time> ScoreUpdate for ProbabilisticScorerUsingTime where L::Target: Logger { fn payment_path_failed(&mut self, path: &Path, short_channel_id: u64) { 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); @@ -1791,7 +1869,7 @@ mod tests { use crate::ln::msgs::{ChannelAnnouncement, ChannelUpdate, UnsignedChannelAnnouncement, UnsignedChannelUpdate}; use crate::routing::gossip::{EffectiveCapacity, NetworkGraph, NodeId}; use crate::routing::router::{BlindedTail, Path, RouteHop}; - use crate::routing::scoring::{ChannelUsage, Score}; + use crate::routing::scoring::{ChannelUsage, ScoreLookUp, ScoreUpdate}; use crate::util::ser::{ReadableArgs, Writeable}; use crate::util::test_utils::{self, TestLogger}; @@ -2441,6 +2519,7 @@ mod tests { scorer.payment_path_failed(&payment_path_for_amount(768), 42); scorer.payment_path_failed(&payment_path_for_amount(128), 43); + // Initial penalties let usage = ChannelUsage { amount_msat: 128, ..usage }; assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, ¶ms), 0); let usage = ChannelUsage { amount_msat: 256, ..usage }; @@ -2450,7 +2529,8 @@ mod tests { let usage = ChannelUsage { amount_msat: 896, ..usage }; assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, ¶ms), u64::max_value()); - SinceEpoch::advance(Duration::from_secs(9)); + // No decay + SinceEpoch::advance(Duration::from_secs(4)); let usage = ChannelUsage { amount_msat: 128, ..usage }; assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, ¶ms), 0); let usage = ChannelUsage { amount_msat: 256, ..usage }; @@ -2460,7 +2540,19 @@ mod tests { let usage = ChannelUsage { amount_msat: 896, ..usage }; assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, ¶ms), u64::max_value()); + // Half decay (i.e., three-quarter life) SinceEpoch::advance(Duration::from_secs(1)); + let usage = ChannelUsage { amount_msat: 128, ..usage }; + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, ¶ms), 22); + let usage = ChannelUsage { amount_msat: 256, ..usage }; + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, ¶ms), 106); + let usage = ChannelUsage { amount_msat: 768, ..usage }; + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, ¶ms), 916); + let usage = ChannelUsage { amount_msat: 896, ..usage }; + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, ¶ms), u64::max_value()); + + // One decay (i.e., half life) + SinceEpoch::advance(Duration::from_secs(5)); let usage = ChannelUsage { amount_msat: 64, ..usage }; assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, ¶ms), 0); let usage = ChannelUsage { amount_msat: 128, ..usage }; @@ -2932,7 +3024,7 @@ mod tests { let usage = ChannelUsage { amount_msat: 1, inflight_htlc_msat: 0, - effective_capacity: EffectiveCapacity::MaximumHTLC { amount_msat: 0 }, + effective_capacity: EffectiveCapacity::AdvertisedMaxHTLC { amount_msat: 0 }, }; assert_eq!(scorer.channel_penalty_msat(42, &target, &source, usage, ¶ms), 2048);