diff --git a/lightning-invoice/src/payment.rs b/lightning-invoice/src/payment.rs index d6a3abb0ddf..c095f74dd16 100644 --- a/lightning-invoice/src/payment.rs +++ b/lightning-invoice/src/payment.rs @@ -38,9 +38,9 @@ //! # use lightning::ln::{PaymentHash, PaymentPreimage, PaymentSecret}; //! # use lightning::ln::channelmanager::{ChannelDetails, PaymentId, PaymentSendFailure}; //! # use lightning::ln::msgs::LightningError; -//! # use lightning::routing::scoring::Score; //! # use lightning::routing::network_graph::NodeId; //! # use lightning::routing::router::{Route, RouteHop, RouteParameters}; +//! # use lightning::routing::scoring::{ChannelUsage, Score}; //! # use lightning::util::events::{Event, EventHandler, EventsProvider}; //! # use lightning::util::logger::{Logger, Record}; //! # use lightning::util::ser::{Writeable, Writer}; @@ -90,7 +90,7 @@ //! # } //! # impl Score for FakeScorer { //! # fn channel_penalty_msat( -//! # &self, _short_channel_id: u64, _send_amt: u64, _chan_amt: u64, _source: &NodeId, _target: &NodeId +//! # &self, _short_channel_id: u64, _source: &NodeId, _target: &NodeId, _usage: ChannelUsage //! # ) -> u64 { 0 } //! # fn payment_path_failed(&mut self, _path: &[&RouteHop], _short_channel_id: u64) {} //! # fn payment_path_successful(&mut self, _path: &[&RouteHop]) {} @@ -604,6 +604,7 @@ mod tests { use lightning::ln::msgs::{ChannelMessageHandler, ErrorAction, LightningError}; use lightning::routing::network_graph::NodeId; use lightning::routing::router::{PaymentParameters, Route, RouteHop}; + use lightning::routing::scoring::ChannelUsage; use lightning::util::test_utils::TestLogger; use lightning::util::errors::APIError; use lightning::util::events::{Event, EventsProvider, MessageSendEvent, MessageSendEventsProvider}; @@ -1444,7 +1445,7 @@ mod tests { impl Score for TestScorer { fn channel_penalty_msat( - &self, _short_channel_id: u64, _send_amt: u64, _chan_amt: u64, _source: &NodeId, _target: &NodeId + &self, _short_channel_id: u64, _source: &NodeId, _target: &NodeId, _usage: ChannelUsage ) -> u64 { 0 } fn payment_path_failed(&mut self, actual_path: &[&RouteHop], actual_short_channel_id: u64) { diff --git a/lightning/src/routing/network_graph.rs b/lightning/src/routing/network_graph.rs index 2e0679eba79..a203c2fce8d 100644 --- a/lightning/src/routing/network_graph.rs +++ b/lightning/src/routing/network_graph.rs @@ -695,7 +695,7 @@ impl ChannelInfo { return None; } }; - Some((DirectedChannelInfo { channel: self, direction }, source)) + Some((DirectedChannelInfo::new(self, direction), source)) } /// Returns a [`DirectedChannelInfo`] for the channel directed from the given `source` to a @@ -710,7 +710,7 @@ impl ChannelInfo { return None; } }; - Some((DirectedChannelInfo { channel: self, direction }, target)) + Some((DirectedChannelInfo::new(self, direction), target)) } } @@ -739,35 +739,53 @@ impl_writeable_tlv_based!(ChannelInfo, { pub struct DirectedChannelInfo<'a> { channel: &'a ChannelInfo, direction: Option<&'a ChannelUpdateInfo>, + htlc_maximum_msat: u64, + effective_capacity: EffectiveCapacity, } impl<'a> DirectedChannelInfo<'a> { + #[inline] + fn new(channel: &'a ChannelInfo, direction: Option<&'a ChannelUpdateInfo>) -> Self { + let htlc_maximum_msat = direction.and_then(|direction| direction.htlc_maximum_msat); + let capacity_msat = channel.capacity_sats.map(|capacity_sats| capacity_sats * 1000); + + let (htlc_maximum_msat, effective_capacity) = match (htlc_maximum_msat, capacity_msat) { + (Some(amount_msat), Some(capacity_msat)) => { + let htlc_maximum_msat = cmp::min(amount_msat, capacity_msat); + (htlc_maximum_msat, EffectiveCapacity::Total { capacity_msat }) + }, + (Some(amount_msat), None) => { + (amount_msat, EffectiveCapacity::MaximumHTLC { amount_msat }) + }, + (None, Some(capacity_msat)) => { + (capacity_msat, EffectiveCapacity::Total { capacity_msat }) + }, + (None, None) => (EffectiveCapacity::Unknown.as_msat(), EffectiveCapacity::Unknown), + }; + + Self { + channel, direction, htlc_maximum_msat, effective_capacity + } + } + /// Returns information for the channel. pub fn channel(&self) -> &'a ChannelInfo { self.channel } /// Returns information for the direction. pub fn direction(&self) -> Option<&'a ChannelUpdateInfo> { self.direction } + /// Returns the maximum HTLC amount allowed over the channel in the direction. + pub fn htlc_maximum_msat(&self) -> u64 { + self.htlc_maximum_msat + } + /// Returns the [`EffectiveCapacity`] of the channel in the direction. /// /// This is either the total capacity from the funding transaction, if known, or the /// `htlc_maximum_msat` for the direction as advertised by the gossip network, if known, - /// whichever is smaller. + /// otherwise. pub fn effective_capacity(&self) -> EffectiveCapacity { - let capacity_msat = self.channel.capacity_sats.map(|capacity_sats| capacity_sats * 1000); - self.direction - .and_then(|direction| direction.htlc_maximum_msat) - .map(|max_htlc_msat| { - let capacity_msat = capacity_msat.unwrap_or(u64::max_value()); - if max_htlc_msat < capacity_msat { - EffectiveCapacity::MaximumHTLC { amount_msat: max_htlc_msat } - } else { - EffectiveCapacity::Total { capacity_msat } - } - }) - .or_else(|| capacity_msat.map(|capacity_msat| - EffectiveCapacity::Total { capacity_msat })) - .unwrap_or(EffectiveCapacity::Unknown) + self.effective_capacity } /// Returns `Some` if [`ChannelUpdateInfo`] is available in the direction. @@ -805,6 +823,10 @@ impl<'a> DirectedChannelInfoWithUpdate<'a> { /// Returns the [`EffectiveCapacity`] of the channel in the direction. #[inline] pub(super) fn effective_capacity(&self) -> EffectiveCapacity { self.inner.effective_capacity() } + + /// Returns the maximum HTLC amount allowed over the channel in the direction. + #[inline] + pub(super) fn htlc_maximum_msat(&self) -> u64 { self.inner.htlc_maximum_msat() } } impl<'a> fmt::Debug for DirectedChannelInfoWithUpdate<'a> { @@ -817,6 +839,7 @@ impl<'a> fmt::Debug for DirectedChannelInfoWithUpdate<'a> { /// /// While this may be smaller than the actual channel capacity, amounts greater than /// [`Self::as_msat`] should not be routed through the channel. +#[derive(Clone, Copy)] pub enum EffectiveCapacity { /// The available liquidity in the channel known from being a channel counterparty, and thus a /// direct hop. diff --git a/lightning/src/routing/router.rs b/lightning/src/routing/router.rs index 8d326b7d7f4..d85485fe826 100644 --- a/lightning/src/routing/router.rs +++ b/lightning/src/routing/router.rs @@ -17,7 +17,7 @@ use bitcoin::secp256k1::PublicKey; use ln::channelmanager::ChannelDetails; use ln::features::{ChannelFeatures, InvoiceFeatures, NodeFeatures}; use ln::msgs::{DecodeError, ErrorAction, LightningError, MAX_VALUE_MSAT}; -use routing::scoring::Score; +use routing::scoring::{ChannelUsage, Score}; use routing::network_graph::{DirectedChannelInfoWithUpdate, EffectiveCapacity, NetworkGraph, ReadOnlyNetworkGraph, NodeId, RoutingFees}; use util::ser::{Writeable, Readable}; use util::logger::{Level, Logger}; @@ -414,6 +414,16 @@ impl<'a> CandidateRouteHop<'a> { } } + fn htlc_maximum_msat(&self) -> u64 { + match self { + CandidateRouteHop::FirstHop { details } => details.next_outbound_htlc_limit_msat, + CandidateRouteHop::PublicHop { info, .. } => info.htlc_maximum_msat(), + CandidateRouteHop::PrivateHop { hint } => { + hint.htlc_maximum_msat.unwrap_or(u64::max_value()) + }, + } + } + fn fees(&self) -> RoutingFees { match self { CandidateRouteHop::FirstHop { .. } => RoutingFees { @@ -481,7 +491,8 @@ struct PathBuildingHop<'a> { impl<'a> core::fmt::Debug for PathBuildingHop<'a> { fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> { - f.debug_struct("PathBuildingHop") + let mut debug_struct = f.debug_struct("PathBuildingHop"); + debug_struct .field("node_id", &self.node_id) .field("short_channel_id", &self.candidate.short_channel_id()) .field("total_fee_msat", &self.total_fee_msat) @@ -490,8 +501,11 @@ impl<'a> core::fmt::Debug for PathBuildingHop<'a> { .field("total_fee_msat - (next_hops_fee_msat + hop_use_fee_msat)", &(&self.total_fee_msat - (&self.next_hops_fee_msat + &self.hop_use_fee_msat))) .field("path_penalty_msat", &self.path_penalty_msat) .field("path_htlc_minimum_msat", &self.path_htlc_minimum_msat) - .field("cltv_expiry_delta", &self.candidate.cltv_expiry_delta()) - .finish() + .field("cltv_expiry_delta", &self.candidate.cltv_expiry_delta()); + #[cfg(all(not(feature = "_bench_unstable"), any(test, fuzzing)))] + let debug_struct = debug_struct + .field("value_contribution_msat", &self.value_contribution_msat); + debug_struct.finish() } } @@ -830,12 +844,12 @@ where L::Target: Logger { let recommended_value_msat = final_value_msat * ROUTE_CAPACITY_PROVISION_FACTOR as u64; let mut path_value_msat = final_value_msat; - // We don't want multiple paths (as per MPP) share liquidity of the same channels. - // This map allows paths to be aware of the channel use by other paths in the same call. - // This would help to make a better path finding decisions and not "overbook" channels. - // It is unaware of the directions (except for `next_outbound_htlc_limit_msat` in - // `first_hops`). - let mut bookkept_channels_liquidity_available_msat = HashMap::with_capacity(network_nodes.len()); + // Keep track of how much liquidity has been used in selected channels. Used to determine + // if the channel can be used by additional MPP paths or to inform path finding decisions. It is + // aware of direction *only* to ensure that the correct htlc_maximum_msat value is used. Hence, + // liquidity used in one direction will not offset any used in the opposite direction. + let mut used_channel_liquidities: HashMap<(u64, bool), u64> = + HashMap::with_capacity(network_nodes.len()); // Keeping track of how much value we already collected across other paths. Helps to decide: // - how much a new path should be transferring (upper bound); @@ -885,9 +899,7 @@ where L::Target: Logger { // - for first and last hops early in get_route if $src_node_id != $dest_node_id { let short_channel_id = $candidate.short_channel_id(); - let available_liquidity_msat = bookkept_channels_liquidity_available_msat - .entry(short_channel_id) - .or_insert_with(|| $candidate.effective_capacity().as_msat()); + let htlc_maximum_msat = $candidate.htlc_maximum_msat(); // It is tricky to subtract $next_hops_fee_msat from available liquidity here. // It may be misleading because we might later choose to reduce the value transferred @@ -896,7 +908,14 @@ where L::Target: Logger { // fees caused by one expensive channel, but then this channel could have been used // if the amount being transferred over this path is lower. // We do this for now, but this is a subject for removal. - if let Some(available_value_contribution_msat) = available_liquidity_msat.checked_sub($next_hops_fee_msat) { + if let Some(mut available_value_contribution_msat) = htlc_maximum_msat.checked_sub($next_hops_fee_msat) { + let used_liquidity_msat = used_channel_liquidities + .get(&(short_channel_id, $src_node_id < $dest_node_id)) + .map_or(0, |used_liquidity_msat| { + available_value_contribution_msat = available_value_contribution_msat + .saturating_sub(*used_liquidity_msat); + *used_liquidity_msat + }); // Routing Fragmentation Mitigation heuristic: // @@ -1047,9 +1066,16 @@ where L::Target: Logger { } } - let path_penalty_msat = $next_hops_path_penalty_msat.saturating_add( - scorer.channel_penalty_msat(short_channel_id, amount_to_transfer_over_msat, - *available_liquidity_msat, &$src_node_id, &$dest_node_id)); + let channel_usage = ChannelUsage { + amount_msat: amount_to_transfer_over_msat, + inflight_htlc_msat: used_liquidity_msat, + effective_capacity: $candidate.effective_capacity(), + }; + let channel_penalty_msat = scorer.channel_penalty_msat( + short_channel_id, &$src_node_id, &$dest_node_id, channel_usage + ); + let path_penalty_msat = $next_hops_path_penalty_msat + .saturating_add(channel_penalty_msat); let new_graph_node = RouteGraphNode { node_id: $src_node_id, lowest_fee_to_peer_through_node: total_fee_msat, @@ -1207,9 +1233,8 @@ where L::Target: Logger { // TODO: diversify by nodes (so that all paths aren't doomed if one node is offline). 'paths_collection: loop { - // For every new path, start from scratch, except - // bookkept_channels_liquidity_available_msat, which will improve - // the further iterations of path finding. Also don't erase first_hop_targets. + // For every new path, start from scratch, except for used_channel_liquidities, which + // helps to avoid reusing previously selected paths in future iterations. targets.clear(); dist.clear(); hit_minimum_limit = false; @@ -1276,16 +1301,6 @@ where L::Target: Logger { short_channel_id: hop.short_channel_id, }) .unwrap_or_else(|| CandidateRouteHop::PrivateHop { hint: hop }); - let capacity_msat = candidate.effective_capacity().as_msat(); - aggregate_next_hops_path_penalty_msat = aggregate_next_hops_path_penalty_msat - .saturating_add(scorer.channel_penalty_msat(hop.short_channel_id, - final_value_msat, capacity_msat, &source, &target)); - - aggregate_next_hops_cltv_delta = aggregate_next_hops_cltv_delta - .saturating_add(hop.cltv_expiry_delta as u32); - - aggregate_next_hops_path_length = aggregate_next_hops_path_length - .saturating_add(1); if !add_entry!(candidate, source, target, aggregate_next_hops_fee_msat, path_value_msat, aggregate_next_hops_path_htlc_minimum_msat, @@ -1297,6 +1312,25 @@ where L::Target: Logger { hop_used = false; } + let used_liquidity_msat = used_channel_liquidities + .get(&(hop.short_channel_id, source < target)).copied().unwrap_or(0); + let channel_usage = ChannelUsage { + amount_msat: final_value_msat + aggregate_next_hops_fee_msat, + inflight_htlc_msat: used_liquidity_msat, + effective_capacity: candidate.effective_capacity(), + }; + let channel_penalty_msat = scorer.channel_penalty_msat( + hop.short_channel_id, &source, &target, channel_usage + ); + aggregate_next_hops_path_penalty_msat = aggregate_next_hops_path_penalty_msat + .saturating_add(channel_penalty_msat); + + aggregate_next_hops_cltv_delta = aggregate_next_hops_cltv_delta + .saturating_add(hop.cltv_expiry_delta as u32); + + aggregate_next_hops_path_length = aggregate_next_hops_path_length + .saturating_add(1); + // Searching for a direct channel between last checked hop and first_hop_targets if let Some(first_channels) = first_hop_targets.get(&NodeId::from_pubkey(&prev_hop_id)) { for details in first_channels { @@ -1448,26 +1482,30 @@ where L::Target: Logger { // Remember that we used these channels so that we don't rely // on the same liquidity in future paths. let mut prevented_redundant_path_selection = false; - for (payment_hop, _) in payment_path.hops.iter() { - let channel_liquidity_available_msat = bookkept_channels_liquidity_available_msat.get_mut(&payment_hop.candidate.short_channel_id()).unwrap(); - let mut spent_on_hop_msat = value_contribution_msat; - let next_hops_fee_msat = payment_hop.next_hops_fee_msat; - spent_on_hop_msat += next_hops_fee_msat; - if spent_on_hop_msat == *channel_liquidity_available_msat { + let prev_hop_iter = core::iter::once(&our_node_id) + .chain(payment_path.hops.iter().map(|(hop, _)| &hop.node_id)); + for (prev_hop, (hop, _)) in prev_hop_iter.zip(payment_path.hops.iter()) { + let spent_on_hop_msat = value_contribution_msat + hop.next_hops_fee_msat; + let used_liquidity_msat = used_channel_liquidities + .entry((hop.candidate.short_channel_id(), *prev_hop < hop.node_id)) + .and_modify(|used_liquidity_msat| *used_liquidity_msat += spent_on_hop_msat) + .or_insert(spent_on_hop_msat); + if *used_liquidity_msat == hop.candidate.htlc_maximum_msat() { // If this path used all of this channel's available liquidity, we know // this path will not be selected again in the next loop iteration. prevented_redundant_path_selection = true; } - *channel_liquidity_available_msat -= spent_on_hop_msat; + debug_assert!(*used_liquidity_msat <= hop.candidate.htlc_maximum_msat()); } if !prevented_redundant_path_selection { // If we weren't capped by hitting a liquidity limit on a channel in the path, // we'll probably end up picking the same path again on the next iteration. // Decrease the available liquidity of a hop in the middle of the path. let victim_scid = payment_path.hops[(payment_path.hops.len()) / 2].0.candidate.short_channel_id(); + let exhausted = u64::max_value(); log_trace!(logger, "Disabling channel {} for future path building iterations to avoid duplicates.", victim_scid); - let victim_liquidity = bookkept_channels_liquidity_available_msat.get_mut(&victim_scid).unwrap(); - *victim_liquidity = 0; + *used_channel_liquidities.entry((victim_scid, false)).or_default() = exhausted; + *used_channel_liquidities.entry((victim_scid, true)).or_default() = exhausted; } // Track the total amount all our collected paths allow to send so that we: @@ -1753,7 +1791,7 @@ mod tests { use routing::router::{get_route, add_random_cltv_offset, default_node_features, PaymentParameters, Route, RouteHint, RouteHintHop, RouteHop, RoutingFees, DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA, MAX_PATH_LENGTH_ESTIMATE}; - use routing::scoring::Score; + use routing::scoring::{ChannelUsage, Score}; use chain::transaction::OutPoint; use chain::keysinterface::KeysInterface; use ln::features::{ChannelFeatures, InitFeatures, InvoiceFeatures, NodeFeatures}; @@ -5145,7 +5183,7 @@ mod tests { fn write(&self, _w: &mut W) -> Result<(), ::io::Error> { unimplemented!() } } impl Score for BadChannelScorer { - fn channel_penalty_msat(&self, short_channel_id: u64, _send_amt: u64, _capacity_msat: u64, _source: &NodeId, _target: &NodeId) -> u64 { + fn channel_penalty_msat(&self, short_channel_id: u64, _: &NodeId, _: &NodeId, _: ChannelUsage) -> u64 { if short_channel_id == self.short_channel_id { u64::max_value() } else { 0 } } @@ -5163,7 +5201,7 @@ mod tests { } impl Score for BadNodeScorer { - fn channel_penalty_msat(&self, _short_channel_id: u64, _send_amt: u64, _capacity_msat: u64, _source: &NodeId, target: &NodeId) -> u64 { + fn channel_penalty_msat(&self, _: u64, _: &NodeId, target: &NodeId, _: ChannelUsage) -> u64 { if *target == self.node_id { u64::max_value() } else { 0 } } diff --git a/lightning/src/routing/scoring.rs b/lightning/src/routing/scoring.rs index cffd2d90533..38e3c838425 100644 --- a/lightning/src/routing/scoring.rs +++ b/lightning/src/routing/scoring.rs @@ -55,7 +55,7 @@ //! [`find_route`]: crate::routing::router::find_route use ln::msgs::DecodeError; -use routing::network_graph::{NetworkGraph, NodeId}; +use routing::network_graph::{EffectiveCapacity, NetworkGraph, NodeId}; use routing::router::RouteHop; use util::ser::{Readable, ReadableArgs, Writeable, Writer}; use util::logger::Logger; @@ -93,7 +93,9 @@ pub trait Score $(: $supertrait)* { /// such as a chain data, network gossip, or invoice hints. For invoice hints, a capacity near /// [`u64::max_value`] is given to indicate sufficient capacity for the invoice's full amount. /// Thus, implementations should be overflow-safe. - fn channel_penalty_msat(&self, short_channel_id: u64, send_amt_msat: u64, capacity_msat: u64, source: &NodeId, target: &NodeId) -> u64; + fn channel_penalty_msat( + &self, short_channel_id: u64, source: &NodeId, target: &NodeId, usage: ChannelUsage + ) -> u64; /// Handles updating channel penalties after failing to route through a channel. fn payment_path_failed(&mut self, path: &[&RouteHop], short_channel_id: u64); @@ -103,8 +105,10 @@ pub trait Score $(: $supertrait)* { } impl $(+ $supertrait)*> Score for T { - fn channel_penalty_msat(&self, short_channel_id: u64, send_amt_msat: u64, capacity_msat: u64, source: &NodeId, target: &NodeId) -> u64 { - self.deref().channel_penalty_msat(short_channel_id, send_amt_msat, capacity_msat, source, target) + fn channel_penalty_msat( + &self, short_channel_id: u64, source: &NodeId, target: &NodeId, usage: ChannelUsage + ) -> u64 { + self.deref().channel_penalty_msat(short_channel_id, source, target, usage) } fn payment_path_failed(&mut self, path: &[&RouteHop], short_channel_id: u64) { @@ -202,6 +206,20 @@ impl<'a, S: Writeable> Writeable for MutexGuard<'a, S> { } } +/// Proposed use of a channel passed as a parameter to [`Score::channel_penalty_msat`]. +#[derive(Clone, Copy)] +pub struct ChannelUsage { + /// The amount to send through the channel, denominated in millisatoshis. + pub amount_msat: u64, + + /// Total amount, denominated in millisatoshis, already allocated to send through the channel + /// as part of a multi-path payment. + pub inflight_htlc_msat: u64, + + /// The effective capacity of the channel. + pub effective_capacity: EffectiveCapacity, +} + #[derive(Clone)] /// [`Score`] implementation that uses a fixed penalty. pub struct FixedPenaltyScorer { @@ -216,7 +234,7 @@ impl FixedPenaltyScorer { } impl Score for FixedPenaltyScorer { - fn channel_penalty_msat(&self, _: u64, _: u64, _: u64, _: &NodeId, _: &NodeId) -> u64 { + fn channel_penalty_msat(&self, _: u64, _: &NodeId, _: &NodeId, _: ChannelUsage) -> u64 { self.penalty_msat } @@ -407,13 +425,16 @@ impl Default for ScoringParameters { impl Score for ScorerUsingTime { fn channel_penalty_msat( - &self, short_channel_id: u64, send_amt_msat: u64, capacity_msat: u64, _source: &NodeId, _target: &NodeId + &self, short_channel_id: u64, _source: &NodeId, _target: &NodeId, usage: ChannelUsage ) -> u64 { let failure_penalty_msat = self.channel_failures .get(&short_channel_id) .map_or(0, |value| value.decayed_penalty_msat(self.params.failure_penalty_half_life)); let mut penalty_msat = self.params.base_penalty_msat + failure_penalty_msat; + let send_amt_msat = usage.amount_msat; + let capacity_msat = usage.effective_capacity.as_msat() + .saturating_sub(usage.inflight_htlc_msat); let send_1024ths = send_amt_msat.checked_mul(1024).unwrap_or(u64::max_value()) / capacity_msat; if send_1024ths > self.params.overuse_penalty_start_1024th as u64 { penalty_msat = penalty_msat.checked_add( @@ -880,10 +901,20 @@ impl, T: Time, U: DerefMut> DirectedChanne impl, L: Deref, T: Time> Score for ProbabilisticScorerUsingTime where L::Target: Logger { fn channel_penalty_msat( - &self, short_channel_id: u64, amount_msat: u64, capacity_msat: u64, source: &NodeId, - target: &NodeId + &self, short_channel_id: u64, source: &NodeId, target: &NodeId, usage: ChannelUsage ) -> u64 { + if let EffectiveCapacity::ExactLiquidity { liquidity_msat } = usage.effective_capacity { + if usage.amount_msat > liquidity_msat { + return u64::max_value(); + } else { + return self.params.base_penalty_msat; + }; + } + let liquidity_offset_half_life = self.params.liquidity_offset_half_life; + let amount_msat = usage.amount_msat; + let capacity_msat = usage.effective_capacity.as_msat() + .saturating_sub(usage.inflight_htlc_msat); self.channel_liquidities .get(&short_channel_id) .unwrap_or(&ChannelLiquidity::new()) @@ -1338,8 +1369,8 @@ mod tests { use ln::features::{ChannelFeatures, NodeFeatures}; use ln::msgs::{ChannelAnnouncement, ChannelUpdate, OptionalField, UnsignedChannelAnnouncement, UnsignedChannelUpdate}; - use routing::scoring::Score; - use routing::network_graph::{NetworkGraph, NodeId}; + use routing::scoring::{ChannelUsage, Score}; + use routing::network_graph::{EffectiveCapacity, NetworkGraph, NodeId}; use routing::router::RouteHop; use util::ser::{Readable, ReadableArgs, Writeable}; use util::test_utils::TestLogger; @@ -1392,10 +1423,13 @@ mod tests { }); let source = source_node_id(); let target = target_node_id(); - assert_eq!(scorer.channel_penalty_msat(42, 1, 1, &source, &target), 1_000); + let usage = ChannelUsage { + amount_msat: 1, inflight_htlc_msat: 0, effective_capacity: EffectiveCapacity::Unknown + }; + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 1_000); SinceEpoch::advance(Duration::from_secs(1)); - assert_eq!(scorer.channel_penalty_msat(42, 1, 1, &source, &target), 1_000); + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 1_000); } #[test] @@ -1409,16 +1443,19 @@ mod tests { }); let source = source_node_id(); let target = target_node_id(); - assert_eq!(scorer.channel_penalty_msat(42, 1, 1, &source, &target), 1_000); + let usage = ChannelUsage { + amount_msat: 1, inflight_htlc_msat: 0, effective_capacity: EffectiveCapacity::Unknown + }; + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 1_000); scorer.payment_path_failed(&[], 42); - assert_eq!(scorer.channel_penalty_msat(42, 1, 1, &source, &target), 1_064); + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 1_064); scorer.payment_path_failed(&[], 42); - assert_eq!(scorer.channel_penalty_msat(42, 1, 1, &source, &target), 1_128); + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 1_128); scorer.payment_path_failed(&[], 42); - assert_eq!(scorer.channel_penalty_msat(42, 1, 1, &source, &target), 1_192); + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 1_192); } #[test] @@ -1432,25 +1469,28 @@ mod tests { }); let source = source_node_id(); let target = target_node_id(); - assert_eq!(scorer.channel_penalty_msat(42, 1, 1, &source, &target), 1_000); + let usage = ChannelUsage { + amount_msat: 1, inflight_htlc_msat: 0, effective_capacity: EffectiveCapacity::Unknown + }; + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 1_000); scorer.payment_path_failed(&[], 42); - assert_eq!(scorer.channel_penalty_msat(42, 1, 1, &source, &target), 1_512); + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 1_512); SinceEpoch::advance(Duration::from_secs(9)); - assert_eq!(scorer.channel_penalty_msat(42, 1, 1, &source, &target), 1_512); + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 1_512); SinceEpoch::advance(Duration::from_secs(1)); - assert_eq!(scorer.channel_penalty_msat(42, 1, 1, &source, &target), 1_256); + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 1_256); SinceEpoch::advance(Duration::from_secs(10 * 8)); - assert_eq!(scorer.channel_penalty_msat(42, 1, 1, &source, &target), 1_001); + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 1_001); SinceEpoch::advance(Duration::from_secs(10)); - assert_eq!(scorer.channel_penalty_msat(42, 1, 1, &source, &target), 1_000); + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 1_000); SinceEpoch::advance(Duration::from_secs(10)); - assert_eq!(scorer.channel_penalty_msat(42, 1, 1, &source, &target), 1_000); + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 1_000); } #[test] @@ -1464,18 +1504,21 @@ mod tests { }); let source = source_node_id(); let target = target_node_id(); - assert_eq!(scorer.channel_penalty_msat(42, 1, 1, &source, &target), 1_000); + let usage = ChannelUsage { + amount_msat: 1, inflight_htlc_msat: 0, effective_capacity: EffectiveCapacity::Unknown + }; + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 1_000); scorer.payment_path_failed(&[], 42); - assert_eq!(scorer.channel_penalty_msat(42, 1, 1, &source, &target), 1_512); + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 1_512); // An unchecked right shift 64 bits or more in ChannelFailure::decayed_penalty_msat would // cause an overflow. SinceEpoch::advance(Duration::from_secs(10 * 64)); - assert_eq!(scorer.channel_penalty_msat(42, 1, 1, &source, &target), 1_000); + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 1_000); SinceEpoch::advance(Duration::from_secs(10)); - assert_eq!(scorer.channel_penalty_msat(42, 1, 1, &source, &target), 1_000); + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 1_000); } #[test] @@ -1489,19 +1532,22 @@ mod tests { }); let source = source_node_id(); let target = target_node_id(); - assert_eq!(scorer.channel_penalty_msat(42, 1, 1, &source, &target), 1_000); + let usage = ChannelUsage { + amount_msat: 1, inflight_htlc_msat: 0, effective_capacity: EffectiveCapacity::Unknown + }; + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 1_000); scorer.payment_path_failed(&[], 42); - assert_eq!(scorer.channel_penalty_msat(42, 1, 1, &source, &target), 1_512); + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 1_512); SinceEpoch::advance(Duration::from_secs(10)); - assert_eq!(scorer.channel_penalty_msat(42, 1, 1, &source, &target), 1_256); + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 1_256); scorer.payment_path_failed(&[], 42); - assert_eq!(scorer.channel_penalty_msat(42, 1, 1, &source, &target), 1_768); + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 1_768); SinceEpoch::advance(Duration::from_secs(10)); - assert_eq!(scorer.channel_penalty_msat(42, 1, 1, &source, &target), 1_384); + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 1_384); } #[test] @@ -1515,13 +1561,16 @@ mod tests { }); let source = source_node_id(); let target = target_node_id(); - assert_eq!(scorer.channel_penalty_msat(42, 1, 1, &source, &target), 1_000); + let usage = ChannelUsage { + amount_msat: 1, inflight_htlc_msat: 0, effective_capacity: EffectiveCapacity::Unknown + }; + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 1_000); scorer.payment_path_failed(&[], 42); - assert_eq!(scorer.channel_penalty_msat(42, 1, 1, &source, &target), 1_512); + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 1_512); SinceEpoch::advance(Duration::from_secs(10)); - assert_eq!(scorer.channel_penalty_msat(42, 1, 1, &source, &target), 1_256); + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 1_256); let hop = RouteHop { pubkey: PublicKey::from_slice(target.as_slice()).unwrap(), @@ -1532,10 +1581,10 @@ mod tests { cltv_expiry_delta: 18, }; scorer.payment_path_successful(&[&hop]); - assert_eq!(scorer.channel_penalty_msat(42, 1, 1, &source, &target), 1_128); + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 1_128); SinceEpoch::advance(Duration::from_secs(10)); - assert_eq!(scorer.channel_penalty_msat(42, 1, 1, &source, &target), 1_064); + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 1_064); } #[test] @@ -1549,22 +1598,25 @@ mod tests { }); let source = source_node_id(); let target = target_node_id(); + let usage = ChannelUsage { + amount_msat: 1, inflight_htlc_msat: 0, effective_capacity: EffectiveCapacity::Unknown + }; scorer.payment_path_failed(&[], 42); - assert_eq!(scorer.channel_penalty_msat(42, 1, 1, &source, &target), 1_512); + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 1_512); SinceEpoch::advance(Duration::from_secs(10)); - assert_eq!(scorer.channel_penalty_msat(42, 1, 1, &source, &target), 1_256); + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 1_256); scorer.payment_path_failed(&[], 43); - assert_eq!(scorer.channel_penalty_msat(43, 1, 1, &source, &target), 1_512); + assert_eq!(scorer.channel_penalty_msat(43, &source, &target, usage), 1_512); let mut serialized_scorer = Vec::new(); scorer.write(&mut serialized_scorer).unwrap(); let deserialized_scorer = ::read(&mut io::Cursor::new(&serialized_scorer)).unwrap(); - assert_eq!(deserialized_scorer.channel_penalty_msat(42, 1, 1, &source, &target), 1_256); - assert_eq!(deserialized_scorer.channel_penalty_msat(43, 1, 1, &source, &target), 1_512); + assert_eq!(deserialized_scorer.channel_penalty_msat(42, &source, &target, usage), 1_256); + assert_eq!(deserialized_scorer.channel_penalty_msat(43, &source, &target, usage), 1_512); } #[test] @@ -1578,9 +1630,12 @@ mod tests { }); let source = source_node_id(); let target = target_node_id(); + let usage = ChannelUsage { + amount_msat: 1, inflight_htlc_msat: 0, effective_capacity: EffectiveCapacity::Unknown + }; scorer.payment_path_failed(&[], 42); - assert_eq!(scorer.channel_penalty_msat(42, 1, 1, &source, &target), 1_512); + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 1_512); let mut serialized_scorer = Vec::new(); scorer.write(&mut serialized_scorer).unwrap(); @@ -1588,10 +1643,10 @@ mod tests { SinceEpoch::advance(Duration::from_secs(10)); let deserialized_scorer = ::read(&mut io::Cursor::new(&serialized_scorer)).unwrap(); - assert_eq!(deserialized_scorer.channel_penalty_msat(42, 1, 1, &source, &target), 1_256); + assert_eq!(deserialized_scorer.channel_penalty_msat(42, &source, &target, usage), 1_256); SinceEpoch::advance(Duration::from_secs(10)); - assert_eq!(deserialized_scorer.channel_penalty_msat(42, 1, 1, &source, &target), 1_128); + assert_eq!(deserialized_scorer.channel_penalty_msat(42, &source, &target, usage), 1_128); } #[test] @@ -1606,11 +1661,24 @@ mod tests { let source = source_node_id(); let target = target_node_id(); - assert_eq!(scorer.channel_penalty_msat(42, 1_000, 1_024_000, &source, &target), 0); - assert_eq!(scorer.channel_penalty_msat(42, 256_999, 1_024_000, &source, &target), 0); - assert_eq!(scorer.channel_penalty_msat(42, 257_000, 1_024_000, &source, &target), 100); - assert_eq!(scorer.channel_penalty_msat(42, 258_000, 1_024_000, &source, &target), 200); - assert_eq!(scorer.channel_penalty_msat(42, 512_000, 1_024_000, &source, &target), 256 * 100); + let usage = ChannelUsage { + amount_msat: 1_000, + inflight_htlc_msat: 0, + effective_capacity: EffectiveCapacity::Total { capacity_msat: 1_024_000 }, + }; + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 0); + + let usage = ChannelUsage { amount_msat: 256_999, ..usage }; + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 0); + + let usage = ChannelUsage { amount_msat: 257_000, ..usage }; + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 100); + + let usage = ChannelUsage { amount_msat: 258_000, ..usage }; + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 200); + + let usage = ChannelUsage { amount_msat: 512_000, ..usage }; + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 256 * 100); } // `ProbabilisticScorer` tests @@ -1943,18 +2011,37 @@ mod tests { let source = source_node_id(); let target = target_node_id(); - assert_eq!(scorer.channel_penalty_msat(42, 1_024, 1_024_000, &source, &target), 0); - assert_eq!(scorer.channel_penalty_msat(42, 10_240, 1_024_000, &source, &target), 0); - assert_eq!(scorer.channel_penalty_msat(42, 102_400, 1_024_000, &source, &target), 47); - assert_eq!(scorer.channel_penalty_msat(42, 1_024_000, 1_024_000, &source, &target), 2_000); - - assert_eq!(scorer.channel_penalty_msat(42, 128, 1_024, &source, &target), 58); - assert_eq!(scorer.channel_penalty_msat(42, 256, 1_024, &source, &target), 125); - assert_eq!(scorer.channel_penalty_msat(42, 374, 1_024, &source, &target), 198); - assert_eq!(scorer.channel_penalty_msat(42, 512, 1_024, &source, &target), 300); - assert_eq!(scorer.channel_penalty_msat(42, 640, 1_024, &source, &target), 425); - assert_eq!(scorer.channel_penalty_msat(42, 768, 1_024, &source, &target), 602); - assert_eq!(scorer.channel_penalty_msat(42, 896, 1_024, &source, &target), 902); + let usage = ChannelUsage { + amount_msat: 1_024, + inflight_htlc_msat: 0, + effective_capacity: EffectiveCapacity::Total { capacity_msat: 1_024_000 }, + }; + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 0); + let usage = ChannelUsage { amount_msat: 10_240, ..usage }; + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 0); + let usage = ChannelUsage { amount_msat: 102_400, ..usage }; + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 47); + let usage = ChannelUsage { amount_msat: 1_024_000, ..usage }; + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 2_000); + + let usage = ChannelUsage { + amount_msat: 128, + inflight_htlc_msat: 0, + effective_capacity: EffectiveCapacity::Total { capacity_msat: 1_024 }, + }; + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 58); + let usage = ChannelUsage { amount_msat: 256, ..usage }; + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 125); + let usage = ChannelUsage { amount_msat: 374, ..usage }; + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 198); + let usage = ChannelUsage { amount_msat: 512, ..usage }; + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 300); + let usage = ChannelUsage { amount_msat: 640, ..usage }; + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 425); + let usage = ChannelUsage { amount_msat: 768, ..usage }; + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 602); + let usage = ChannelUsage { amount_msat: 896, ..usage }; + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 902); } #[test] @@ -1974,10 +2061,17 @@ mod tests { let source = source_node_id(); let target = target_node_id(); - assert_eq!(scorer.channel_penalty_msat(42, 39, 100, &source, &target), 0); - assert_ne!(scorer.channel_penalty_msat(42, 50, 100, &source, &target), 0); - assert_ne!(scorer.channel_penalty_msat(42, 50, 100, &source, &target), u64::max_value()); - assert_eq!(scorer.channel_penalty_msat(42, 61, 100, &source, &target), u64::max_value()); + let usage = ChannelUsage { + amount_msat: 39, + inflight_htlc_msat: 0, + effective_capacity: EffectiveCapacity::Total { capacity_msat: 100 }, + }; + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 0); + let usage = ChannelUsage { amount_msat: 50, ..usage }; + assert_ne!(scorer.channel_penalty_msat(42, &source, &target, usage), 0); + assert_ne!(scorer.channel_penalty_msat(42, &source, &target, usage), u64::max_value()); + let usage = ChannelUsage { amount_msat: 61, ..usage }; + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), u64::max_value()); } #[test] @@ -1991,16 +2085,21 @@ mod tests { let mut scorer = ProbabilisticScorer::new(params, &network_graph, &logger); let sender = sender_node_id(); let source = source_node_id(); + let usage = ChannelUsage { + amount_msat: 500, + inflight_htlc_msat: 0, + effective_capacity: EffectiveCapacity::Total { capacity_msat: 1_000 }, + }; let failed_path = payment_path_for_amount(500); let successful_path = payment_path_for_amount(200); - assert_eq!(scorer.channel_penalty_msat(41, 500, 1_000, &sender, &source), 301); + assert_eq!(scorer.channel_penalty_msat(41, &sender, &source, usage), 301); scorer.payment_path_failed(&failed_path.iter().collect::>(), 41); - assert_eq!(scorer.channel_penalty_msat(41, 500, 1_000, &sender, &source), 301); + assert_eq!(scorer.channel_penalty_msat(41, &sender, &source, usage), 301); scorer.payment_path_successful(&successful_path.iter().collect::>()); - assert_eq!(scorer.channel_penalty_msat(41, 500, 1_000, &sender, &source), 301); + assert_eq!(scorer.channel_penalty_msat(41, &sender, &source, usage), 301); } #[test] @@ -2016,15 +2115,25 @@ mod tests { let target = target_node_id(); let path = payment_path_for_amount(500); - assert_eq!(scorer.channel_penalty_msat(42, 250, 1_000, &source, &target), 128); - assert_eq!(scorer.channel_penalty_msat(42, 500, 1_000, &source, &target), 301); - assert_eq!(scorer.channel_penalty_msat(42, 750, 1_000, &source, &target), 602); + let usage = ChannelUsage { + amount_msat: 250, + inflight_htlc_msat: 0, + effective_capacity: EffectiveCapacity::Total { capacity_msat: 1_000 }, + }; + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 128); + let usage = ChannelUsage { amount_msat: 500, ..usage }; + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 301); + let usage = ChannelUsage { amount_msat: 750, ..usage }; + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 602); scorer.payment_path_failed(&path.iter().collect::>(), 43); - assert_eq!(scorer.channel_penalty_msat(42, 250, 1_000, &source, &target), 0); - assert_eq!(scorer.channel_penalty_msat(42, 500, 1_000, &source, &target), 0); - assert_eq!(scorer.channel_penalty_msat(42, 750, 1_000, &source, &target), 300); + let usage = ChannelUsage { amount_msat: 250, ..usage }; + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 0); + let usage = ChannelUsage { amount_msat: 500, ..usage }; + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 0); + let usage = ChannelUsage { amount_msat: 750, ..usage }; + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 300); } #[test] @@ -2040,15 +2149,25 @@ mod tests { let target = target_node_id(); let path = payment_path_for_amount(500); - assert_eq!(scorer.channel_penalty_msat(42, 250, 1_000, &source, &target), 128); - assert_eq!(scorer.channel_penalty_msat(42, 500, 1_000, &source, &target), 301); - assert_eq!(scorer.channel_penalty_msat(42, 750, 1_000, &source, &target), 602); + let usage = ChannelUsage { + amount_msat: 250, + inflight_htlc_msat: 0, + effective_capacity: EffectiveCapacity::Total { capacity_msat: 1_000 }, + }; + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 128); + let usage = ChannelUsage { amount_msat: 500, ..usage }; + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 301); + let usage = ChannelUsage { amount_msat: 750, ..usage }; + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 602); scorer.payment_path_failed(&path.iter().collect::>(), 42); - assert_eq!(scorer.channel_penalty_msat(42, 250, 1_000, &source, &target), 300); - assert_eq!(scorer.channel_penalty_msat(42, 500, 1_000, &source, &target), u64::max_value()); - assert_eq!(scorer.channel_penalty_msat(42, 750, 1_000, &source, &target), u64::max_value()); + let usage = ChannelUsage { amount_msat: 250, ..usage }; + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 300); + let usage = ChannelUsage { amount_msat: 500, ..usage }; + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), u64::max_value()); + let usage = ChannelUsage { amount_msat: 750, ..usage }; + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), u64::max_value()); } #[test] @@ -2064,17 +2183,22 @@ mod tests { let source = source_node_id(); let target = target_node_id(); let recipient = recipient_node_id(); + let usage = ChannelUsage { + amount_msat: 250, + inflight_htlc_msat: 0, + effective_capacity: EffectiveCapacity::Total { capacity_msat: 1_000 }, + }; let path = payment_path_for_amount(500); - assert_eq!(scorer.channel_penalty_msat(41, 250, 1_000, &sender, &source), 128); - assert_eq!(scorer.channel_penalty_msat(42, 250, 1_000, &source, &target), 128); - assert_eq!(scorer.channel_penalty_msat(43, 250, 1_000, &target, &recipient), 128); + assert_eq!(scorer.channel_penalty_msat(41, &sender, &source, usage), 128); + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 128); + assert_eq!(scorer.channel_penalty_msat(43, &target, &recipient, usage), 128); scorer.payment_path_successful(&path.iter().collect::>()); - assert_eq!(scorer.channel_penalty_msat(41, 250, 1_000, &sender, &source), 128); - assert_eq!(scorer.channel_penalty_msat(42, 250, 1_000, &source, &target), 300); - assert_eq!(scorer.channel_penalty_msat(43, 250, 1_000, &target, &recipient), 300); + assert_eq!(scorer.channel_penalty_msat(41, &sender, &source, usage), 128); + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 300); + assert_eq!(scorer.channel_penalty_msat(43, &target, &recipient, usage), 300); } #[test] @@ -2090,44 +2214,70 @@ mod tests { let source = source_node_id(); let target = target_node_id(); - assert_eq!(scorer.channel_penalty_msat(42, 0, 1_024, &source, &target), 0); - assert_eq!(scorer.channel_penalty_msat(42, 1_024, 1_024, &source, &target), 2_000); + let usage = ChannelUsage { + amount_msat: 0, + inflight_htlc_msat: 0, + effective_capacity: EffectiveCapacity::Total { capacity_msat: 1_024 }, + }; + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 0); + let usage = ChannelUsage { amount_msat: 1_024, ..usage }; + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 2_000); scorer.payment_path_failed(&payment_path_for_amount(768).iter().collect::>(), 42); scorer.payment_path_failed(&payment_path_for_amount(128).iter().collect::>(), 43); - assert_eq!(scorer.channel_penalty_msat(42, 128, 1_024, &source, &target), 0); - assert_eq!(scorer.channel_penalty_msat(42, 256, 1_024, &source, &target), 93); - assert_eq!(scorer.channel_penalty_msat(42, 768, 1_024, &source, &target), 1_479); - assert_eq!(scorer.channel_penalty_msat(42, 896, 1_024, &source, &target), u64::max_value()); + let usage = ChannelUsage { amount_msat: 128, ..usage }; + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 0); + let usage = ChannelUsage { amount_msat: 256, ..usage }; + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 93); + let usage = ChannelUsage { amount_msat: 768, ..usage }; + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 1_479); + let usage = ChannelUsage { amount_msat: 896, ..usage }; + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), u64::max_value()); SinceEpoch::advance(Duration::from_secs(9)); - assert_eq!(scorer.channel_penalty_msat(42, 128, 1_024, &source, &target), 0); - assert_eq!(scorer.channel_penalty_msat(42, 256, 1_024, &source, &target), 93); - assert_eq!(scorer.channel_penalty_msat(42, 768, 1_024, &source, &target), 1_479); - assert_eq!(scorer.channel_penalty_msat(42, 896, 1_024, &source, &target), u64::max_value()); + let usage = ChannelUsage { amount_msat: 128, ..usage }; + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 0); + let usage = ChannelUsage { amount_msat: 256, ..usage }; + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 93); + let usage = ChannelUsage { amount_msat: 768, ..usage }; + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 1_479); + let usage = ChannelUsage { amount_msat: 896, ..usage }; + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), u64::max_value()); SinceEpoch::advance(Duration::from_secs(1)); - assert_eq!(scorer.channel_penalty_msat(42, 64, 1_024, &source, &target), 0); - assert_eq!(scorer.channel_penalty_msat(42, 128, 1_024, &source, &target), 34); - assert_eq!(scorer.channel_penalty_msat(42, 896, 1_024, &source, &target), 1_970); - assert_eq!(scorer.channel_penalty_msat(42, 960, 1_024, &source, &target), u64::max_value()); + let usage = ChannelUsage { amount_msat: 64, ..usage }; + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 0); + let usage = ChannelUsage { amount_msat: 128, ..usage }; + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 34); + let usage = ChannelUsage { amount_msat: 896, ..usage }; + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 1_970); + let usage = ChannelUsage { amount_msat: 960, ..usage }; + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), u64::max_value()); // Fully decay liquidity lower bound. SinceEpoch::advance(Duration::from_secs(10 * 7)); - assert_eq!(scorer.channel_penalty_msat(42, 0, 1_024, &source, &target), 0); - assert_eq!(scorer.channel_penalty_msat(42, 1, 1_024, &source, &target), 0); - assert_eq!(scorer.channel_penalty_msat(42, 1_023, 1_024, &source, &target), 2_000); - assert_eq!(scorer.channel_penalty_msat(42, 1_024, 1_024, &source, &target), 2_000); + let usage = ChannelUsage { amount_msat: 0, ..usage }; + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 0); + let usage = ChannelUsage { amount_msat: 1, ..usage }; + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 0); + let usage = ChannelUsage { amount_msat: 1_023, ..usage }; + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 2_000); + let usage = ChannelUsage { amount_msat: 1_024, ..usage }; + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 2_000); // Fully decay liquidity upper bound. SinceEpoch::advance(Duration::from_secs(10)); - assert_eq!(scorer.channel_penalty_msat(42, 0, 1_024, &source, &target), 0); - assert_eq!(scorer.channel_penalty_msat(42, 1_024, 1_024, &source, &target), 2_000); + let usage = ChannelUsage { amount_msat: 0, ..usage }; + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 0); + let usage = ChannelUsage { amount_msat: 1_024, ..usage }; + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 2_000); SinceEpoch::advance(Duration::from_secs(10)); - assert_eq!(scorer.channel_penalty_msat(42, 0, 1_024, &source, &target), 0); - assert_eq!(scorer.channel_penalty_msat(42, 1_024, 1_024, &source, &target), 2_000); + let usage = ChannelUsage { amount_msat: 0, ..usage }; + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 0); + let usage = ChannelUsage { amount_msat: 1_024, ..usage }; + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 2_000); } #[test] @@ -2142,18 +2292,23 @@ mod tests { let mut scorer = ProbabilisticScorer::new(params, &network_graph, &logger); let source = source_node_id(); let target = target_node_id(); - assert_eq!(scorer.channel_penalty_msat(42, 256, 1_024, &source, &target), 125); + let usage = ChannelUsage { + amount_msat: 256, + inflight_htlc_msat: 0, + effective_capacity: EffectiveCapacity::Total { capacity_msat: 1_024 }, + }; + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 125); scorer.payment_path_failed(&payment_path_for_amount(512).iter().collect::>(), 42); - assert_eq!(scorer.channel_penalty_msat(42, 256, 1_024, &source, &target), 281); + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 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)); - assert_eq!(scorer.channel_penalty_msat(42, 256, 1_024, &source, &target), 125); + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 125); SinceEpoch::advance(Duration::from_secs(10)); - assert_eq!(scorer.channel_penalty_msat(42, 256, 1_024, &source, &target), 125); + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 125); } #[test] @@ -2168,31 +2323,36 @@ mod tests { let mut scorer = ProbabilisticScorer::new(params, &network_graph, &logger); let source = source_node_id(); let target = target_node_id(); + let usage = ChannelUsage { + amount_msat: 512, + inflight_htlc_msat: 0, + effective_capacity: EffectiveCapacity::Total { capacity_msat: 1_024 }, + }; - assert_eq!(scorer.channel_penalty_msat(42, 512, 1_024, &source, &target), 300); + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 300); // More knowledge gives higher confidence (256, 768), meaning a lower penalty. scorer.payment_path_failed(&payment_path_for_amount(768).iter().collect::>(), 42); scorer.payment_path_failed(&payment_path_for_amount(256).iter().collect::>(), 43); - assert_eq!(scorer.channel_penalty_msat(42, 512, 1_024, &source, &target), 281); + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 281); // Decaying knowledge gives less confidence (128, 896), meaning a higher penalty. SinceEpoch::advance(Duration::from_secs(10)); - assert_eq!(scorer.channel_penalty_msat(42, 512, 1_024, &source, &target), 291); + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 291); // Reducing the upper bound gives more confidence (128, 832) that the payment amount (512) // is closer to the upper bound, meaning a higher penalty. scorer.payment_path_successful(&payment_path_for_amount(64).iter().collect::>()); - assert_eq!(scorer.channel_penalty_msat(42, 512, 1_024, &source, &target), 331); + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 331); // Increasing the lower bound gives more confidence (256, 832) that the payment amount (512) // is closer to the lower bound, meaning a lower penalty. scorer.payment_path_failed(&payment_path_for_amount(256).iter().collect::>(), 43); - assert_eq!(scorer.channel_penalty_msat(42, 512, 1_024, &source, &target), 245); + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 245); // Further decaying affects the lower bound more than the upper bound (128, 928). SinceEpoch::advance(Duration::from_secs(10)); - assert_eq!(scorer.channel_penalty_msat(42, 512, 1_024, &source, &target), 280); + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 280); } #[test] @@ -2207,15 +2367,20 @@ mod tests { let mut scorer = ProbabilisticScorer::new(params, &network_graph, &logger); let source = source_node_id(); let target = target_node_id(); + let usage = ChannelUsage { + amount_msat: 500, + inflight_htlc_msat: 0, + effective_capacity: EffectiveCapacity::Total { capacity_msat: 1_000 }, + }; scorer.payment_path_failed(&payment_path_for_amount(500).iter().collect::>(), 42); - assert_eq!(scorer.channel_penalty_msat(42, 500, 1_000, &source, &target), u64::max_value()); + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), u64::max_value()); SinceEpoch::advance(Duration::from_secs(10)); - assert_eq!(scorer.channel_penalty_msat(42, 500, 1_000, &source, &target), 473); + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 473); scorer.payment_path_failed(&payment_path_for_amount(250).iter().collect::>(), 43); - assert_eq!(scorer.channel_penalty_msat(42, 500, 1_000, &source, &target), 300); + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 300); let mut serialized_scorer = Vec::new(); scorer.write(&mut serialized_scorer).unwrap(); @@ -2223,7 +2388,7 @@ mod tests { let mut serialized_scorer = io::Cursor::new(&serialized_scorer); let deserialized_scorer = ::read(&mut serialized_scorer, (params, &network_graph, &logger)).unwrap(); - assert_eq!(deserialized_scorer.channel_penalty_msat(42, 500, 1_000, &source, &target), 300); + assert_eq!(deserialized_scorer.channel_penalty_msat(42, &source, &target, usage), 300); } #[test] @@ -2238,9 +2403,14 @@ mod tests { let mut scorer = ProbabilisticScorer::new(params, &network_graph, &logger); let source = source_node_id(); let target = target_node_id(); + let usage = ChannelUsage { + amount_msat: 500, + inflight_htlc_msat: 0, + effective_capacity: EffectiveCapacity::Total { capacity_msat: 1_000 }, + }; scorer.payment_path_failed(&payment_path_for_amount(500).iter().collect::>(), 42); - assert_eq!(scorer.channel_penalty_msat(42, 500, 1_000, &source, &target), u64::max_value()); + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), u64::max_value()); let mut serialized_scorer = Vec::new(); scorer.write(&mut serialized_scorer).unwrap(); @@ -2250,13 +2420,13 @@ mod tests { let mut serialized_scorer = io::Cursor::new(&serialized_scorer); let deserialized_scorer = ::read(&mut serialized_scorer, (params, &network_graph, &logger)).unwrap(); - assert_eq!(deserialized_scorer.channel_penalty_msat(42, 500, 1_000, &source, &target), 473); + assert_eq!(deserialized_scorer.channel_penalty_msat(42, &source, &target, usage), 473); scorer.payment_path_failed(&payment_path_for_amount(250).iter().collect::>(), 43); - assert_eq!(scorer.channel_penalty_msat(42, 500, 1_000, &source, &target), 300); + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 300); SinceEpoch::advance(Duration::from_secs(10)); - assert_eq!(deserialized_scorer.channel_penalty_msat(42, 500, 1_000, &source, &target), 365); + assert_eq!(deserialized_scorer.channel_penalty_msat(42, &source, &target, usage), 365); } #[test] @@ -2270,17 +2440,52 @@ mod tests { let source = source_node_id(); let target = target_node_id(); - assert_eq!(scorer.channel_penalty_msat(42, 100_000_000, 950_000_000, &source, &target), 3613); - assert_eq!(scorer.channel_penalty_msat(42, 100_000_000, 1_950_000_000, &source, &target), 1977); - assert_eq!(scorer.channel_penalty_msat(42, 100_000_000, 2_950_000_000, &source, &target), 1474); - assert_eq!(scorer.channel_penalty_msat(42, 100_000_000, 3_950_000_000, &source, &target), 1223); - assert_eq!(scorer.channel_penalty_msat(42, 100_000_000, 4_950_000_000, &source, &target), 877); - assert_eq!(scorer.channel_penalty_msat(42, 100_000_000, 5_950_000_000, &source, &target), 845); - assert_eq!(scorer.channel_penalty_msat(42, 100_000_000, 6_950_000_000, &source, &target), 500); - assert_eq!(scorer.channel_penalty_msat(42, 100_000_000, 7_450_000_000, &source, &target), 500); - assert_eq!(scorer.channel_penalty_msat(42, 100_000_000, 7_950_000_000, &source, &target), 500); - assert_eq!(scorer.channel_penalty_msat(42, 100_000_000, 8_950_000_000, &source, &target), 500); - assert_eq!(scorer.channel_penalty_msat(42, 100_000_000, 9_950_000_000, &source, &target), 500); + let usage = ChannelUsage { + amount_msat: 100_000_000, + inflight_htlc_msat: 0, + effective_capacity: EffectiveCapacity::Total { capacity_msat: 950_000_000 }, + }; + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 3613); + let usage = ChannelUsage { + effective_capacity: EffectiveCapacity::Total { capacity_msat: 1_950_000_000 }, ..usage + }; + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 1977); + let usage = ChannelUsage { + effective_capacity: EffectiveCapacity::Total { capacity_msat: 2_950_000_000 }, ..usage + }; + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 1474); + let usage = ChannelUsage { + effective_capacity: EffectiveCapacity::Total { capacity_msat: 3_950_000_000 }, ..usage + }; + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 1223); + let usage = ChannelUsage { + effective_capacity: EffectiveCapacity::Total { capacity_msat: 4_950_000_000 }, ..usage + }; + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 877); + let usage = ChannelUsage { + effective_capacity: EffectiveCapacity::Total { capacity_msat: 5_950_000_000 }, ..usage + }; + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 845); + let usage = ChannelUsage { + effective_capacity: EffectiveCapacity::Total { capacity_msat: 6_950_000_000 }, ..usage + }; + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 500); + let usage = ChannelUsage { + effective_capacity: EffectiveCapacity::Total { capacity_msat: 7_450_000_000 }, ..usage + }; + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 500); + let usage = ChannelUsage { + effective_capacity: EffectiveCapacity::Total { capacity_msat: 7_950_000_000 }, ..usage + }; + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 500); + let usage = ChannelUsage { + effective_capacity: EffectiveCapacity::Total { capacity_msat: 8_950_000_000 }, ..usage + }; + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 500); + let usage = ChannelUsage { + effective_capacity: EffectiveCapacity::Total { capacity_msat: 9_950_000_000 }, ..usage + }; + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 500); } #[test] @@ -2289,19 +2494,24 @@ mod tests { let network_graph = network_graph(); let source = source_node_id(); let target = target_node_id(); + let usage = ChannelUsage { + amount_msat: 128, + inflight_htlc_msat: 0, + effective_capacity: EffectiveCapacity::Total { capacity_msat: 1_024 }, + }; let params = ProbabilisticScoringParameters { liquidity_penalty_multiplier_msat: 1_000, ..ProbabilisticScoringParameters::zero_penalty() }; let scorer = ProbabilisticScorer::new(params, &network_graph, &logger); - assert_eq!(scorer.channel_penalty_msat(42, 128, 1_024, &source, &target), 58); + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 58); let params = ProbabilisticScoringParameters { base_penalty_msat: 500, liquidity_penalty_multiplier_msat: 1_000, ..Default::default() }; let scorer = ProbabilisticScorer::new(params, &network_graph, &logger); - assert_eq!(scorer.channel_penalty_msat(42, 128, 1_024, &source, &target), 558); + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 558); } #[test] @@ -2310,6 +2520,11 @@ mod tests { let network_graph = network_graph(); let source = source_node_id(); let target = target_node_id(); + let usage = ChannelUsage { + amount_msat: 512_000, + inflight_htlc_msat: 0, + effective_capacity: EffectiveCapacity::Total { capacity_msat: 1_024_000 }, + }; let params = ProbabilisticScoringParameters { liquidity_penalty_multiplier_msat: 1_000, @@ -2317,7 +2532,7 @@ mod tests { ..ProbabilisticScoringParameters::zero_penalty() }; let scorer = ProbabilisticScorer::new(params, &network_graph, &logger); - assert_eq!(scorer.channel_penalty_msat(42, 512_000, 1_024_000, &source, &target), 300); + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 300); let params = ProbabilisticScoringParameters { liquidity_penalty_multiplier_msat: 1_000, @@ -2325,7 +2540,7 @@ mod tests { ..ProbabilisticScoringParameters::zero_penalty() }; let scorer = ProbabilisticScorer::new(params, &network_graph, &logger); - assert_eq!(scorer.channel_penalty_msat(42, 512_000, 1_024_000, &source, &target), 337); + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 337); } #[test] @@ -2334,15 +2549,61 @@ mod tests { let network_graph = network_graph(); let source = source_node_id(); let target = target_node_id(); + let usage = ChannelUsage { + amount_msat: u64::max_value(), + inflight_htlc_msat: 0, + effective_capacity: EffectiveCapacity::Infinite, + }; let params = ProbabilisticScoringParameters { liquidity_penalty_multiplier_msat: 40_000, ..ProbabilisticScoringParameters::zero_penalty() }; let scorer = ProbabilisticScorer::new(params, &network_graph, &logger); - assert_eq!( - scorer.channel_penalty_msat(42, u64::max_value(), u64::max_value(), &source, &target), - 80_000, - ); + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 80_000); + } + + #[test] + fn accounts_for_inflight_htlc_usage() { + let network_graph = network_graph(); + let logger = TestLogger::new(); + let params = ProbabilisticScoringParameters::default(); + let scorer = ProbabilisticScorer::new(params, &network_graph, &logger); + let source = source_node_id(); + let target = target_node_id(); + + let usage = ChannelUsage { + amount_msat: 750, + inflight_htlc_msat: 0, + effective_capacity: EffectiveCapacity::Total { capacity_msat: 1_000 }, + }; + assert_ne!(scorer.channel_penalty_msat(42, &source, &target, usage), u64::max_value()); + + let usage = ChannelUsage { inflight_htlc_msat: 251, ..usage }; + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), u64::max_value()); + } + + #[test] + fn removes_uncertainity_when_exact_liquidity_known() { + let network_graph = network_graph(); + let logger = TestLogger::new(); + let params = ProbabilisticScoringParameters::default(); + let scorer = ProbabilisticScorer::new(params, &network_graph, &logger); + let source = source_node_id(); + let target = target_node_id(); + + let base_penalty_msat = params.base_penalty_msat; + let usage = ChannelUsage { + amount_msat: 750, + inflight_htlc_msat: 0, + effective_capacity: EffectiveCapacity::ExactLiquidity { liquidity_msat: 1_000 }, + }; + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), base_penalty_msat); + + let usage = ChannelUsage { amount_msat: 1_000, ..usage }; + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), base_penalty_msat); + + let usage = ChannelUsage { amount_msat: 1_001, ..usage }; + assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), u64::max_value()); } }