Merge pull request #1166 from TheBlueMatt/2021-11-chan-size-scoring

This commit is contained in:
Matt Corallo 2021-11-16 22:10:42 +00:00 committed by GitHub
commit 77948dbcd7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 211 additions and 138 deletions

View file

@ -39,7 +39,7 @@ use lightning::ln::msgs::DecodeError;
use lightning::ln::script::ShutdownScript; use lightning::ln::script::ShutdownScript;
use lightning::routing::network_graph::{NetGraphMsgHandler, NetworkGraph}; use lightning::routing::network_graph::{NetGraphMsgHandler, NetworkGraph};
use lightning::routing::router::{find_route, Payee, RouteParameters}; use lightning::routing::router::{find_route, Payee, RouteParameters};
use lightning::routing::scorer::Scorer; use lightning::routing::scoring::Scorer;
use lightning::util::config::UserConfig; use lightning::util::config::UserConfig;
use lightning::util::errors::APIError; use lightning::util::errors::APIError;
use lightning::util::events::Event; use lightning::util::events::Event;

View file

@ -17,7 +17,7 @@ use lightning::ln::channelmanager::{ChannelDetails, ChannelCounterparty};
use lightning::ln::features::InitFeatures; use lightning::ln::features::InitFeatures;
use lightning::ln::msgs; use lightning::ln::msgs;
use lightning::routing::router::{find_route, Payee, RouteHint, RouteHintHop, RouteParameters}; use lightning::routing::router::{find_route, Payee, RouteHint, RouteHintHop, RouteParameters};
use lightning::routing::scorer::Scorer; use lightning::routing::scoring::Scorer;
use lightning::util::logger::Logger; use lightning::util::logger::Logger;
use lightning::util::ser::Readable; use lightning::util::ser::Readable;
use lightning::routing::network_graph::{NetworkGraph, RoutingFees}; use lightning::routing::network_graph::{NetworkGraph, RoutingFees};

View file

@ -31,7 +31,7 @@
//! # use lightning::ln::{PaymentHash, PaymentPreimage, PaymentSecret}; //! # use lightning::ln::{PaymentHash, PaymentPreimage, PaymentSecret};
//! # use lightning::ln::channelmanager::{ChannelDetails, PaymentId, PaymentSendFailure}; //! # use lightning::ln::channelmanager::{ChannelDetails, PaymentId, PaymentSendFailure};
//! # use lightning::ln::msgs::LightningError; //! # use lightning::ln::msgs::LightningError;
//! # use lightning::routing; //! # use lightning::routing::scoring::Score;
//! # use lightning::routing::network_graph::NodeId; //! # use lightning::routing::network_graph::NodeId;
//! # use lightning::routing::router::{Route, RouteHop, RouteParameters}; //! # use lightning::routing::router::{Route, RouteHop, RouteParameters};
//! # use lightning::util::events::{Event, EventHandler, EventsProvider}; //! # use lightning::util::events::{Event, EventHandler, EventsProvider};
@ -63,7 +63,7 @@
//! # } //! # }
//! # //! #
//! # struct FakeRouter {}; //! # struct FakeRouter {};
//! # impl<S: routing::Score> Router<S> for FakeRouter { //! # impl<S: Score> Router<S> for FakeRouter {
//! # fn find_route( //! # fn find_route(
//! # &self, payer: &PublicKey, params: &RouteParameters, payment_hash: &PaymentHash, //! # &self, payer: &PublicKey, params: &RouteParameters, payment_hash: &PaymentHash,
//! # first_hops: Option<&[&ChannelDetails]>, scorer: &S //! # first_hops: Option<&[&ChannelDetails]>, scorer: &S
@ -71,9 +71,9 @@
//! # } //! # }
//! # //! #
//! # struct FakeScorer {}; //! # struct FakeScorer {};
//! # impl routing::Score for FakeScorer { //! # impl Score for FakeScorer {
//! # fn channel_penalty_msat( //! # fn channel_penalty_msat(
//! # &self, _short_channel_id: u64, _source: &NodeId, _target: &NodeId //! # &self, _short_channel_id: u64, _send_amt: u64, _chan_amt: Option<u64>, _source: &NodeId, _target: &NodeId
//! # ) -> u64 { 0 } //! # ) -> u64 { 0 }
//! # fn payment_path_failed(&mut self, _path: &[&RouteHop], _short_channel_id: u64) {} //! # fn payment_path_failed(&mut self, _path: &[&RouteHop], _short_channel_id: u64) {}
//! # } //! # }
@ -122,8 +122,7 @@ use bitcoin_hashes::sha256::Hash as Sha256;
use lightning::ln::{PaymentHash, PaymentPreimage, PaymentSecret}; use lightning::ln::{PaymentHash, PaymentPreimage, PaymentSecret};
use lightning::ln::channelmanager::{ChannelDetails, PaymentId, PaymentSendFailure}; use lightning::ln::channelmanager::{ChannelDetails, PaymentId, PaymentSendFailure};
use lightning::ln::msgs::LightningError; use lightning::ln::msgs::LightningError;
use lightning::routing; use lightning::routing::scoring::{LockableScore, Score};
use lightning::routing::{LockableScore, Score};
use lightning::routing::router::{Payee, Route, RouteParameters}; use lightning::routing::router::{Payee, Route, RouteParameters};
use lightning::util::events::{Event, EventHandler}; use lightning::util::events::{Event, EventHandler};
use lightning::util::logger::Logger; use lightning::util::logger::Logger;
@ -139,8 +138,8 @@ use std::time::{Duration, SystemTime};
pub struct InvoicePayer<P: Deref, R, S: Deref, L: Deref, E> pub struct InvoicePayer<P: Deref, R, S: Deref, L: Deref, E>
where where
P::Target: Payer, P::Target: Payer,
R: for <'a> Router<<<S as Deref>::Target as routing::LockableScore<'a>>::Locked>, R: for <'a> Router<<<S as Deref>::Target as LockableScore<'a>>::Locked>,
S::Target: for <'a> routing::LockableScore<'a>, S::Target: for <'a> LockableScore<'a>,
L::Target: Logger, L::Target: Logger,
E: EventHandler, E: EventHandler,
{ {
@ -177,7 +176,7 @@ pub trait Payer {
} }
/// A trait defining behavior for routing an [`Invoice`] payment. /// A trait defining behavior for routing an [`Invoice`] payment.
pub trait Router<S: routing::Score> { pub trait Router<S: Score> {
/// Finds a [`Route`] between `payer` and `payee` for a payment with the given values. /// Finds a [`Route`] between `payer` and `payee` for a payment with the given values.
fn find_route( fn find_route(
&self, payer: &PublicKey, params: &RouteParameters, payment_hash: &PaymentHash, &self, payer: &PublicKey, params: &RouteParameters, payment_hash: &PaymentHash,
@ -207,8 +206,8 @@ pub enum PaymentError {
impl<P: Deref, R, S: Deref, L: Deref, E> InvoicePayer<P, R, S, L, E> impl<P: Deref, R, S: Deref, L: Deref, E> InvoicePayer<P, R, S, L, E>
where where
P::Target: Payer, P::Target: Payer,
R: for <'a> Router<<<S as Deref>::Target as routing::LockableScore<'a>>::Locked>, R: for <'a> Router<<<S as Deref>::Target as LockableScore<'a>>::Locked>,
S::Target: for <'a> routing::LockableScore<'a>, S::Target: for <'a> LockableScore<'a>,
L::Target: Logger, L::Target: Logger,
E: EventHandler, E: EventHandler,
{ {
@ -441,8 +440,8 @@ fn has_expired(params: &RouteParameters) -> bool {
impl<P: Deref, R, S: Deref, L: Deref, E> EventHandler for InvoicePayer<P, R, S, L, E> impl<P: Deref, R, S: Deref, L: Deref, E> EventHandler for InvoicePayer<P, R, S, L, E>
where where
P::Target: Payer, P::Target: Payer,
R: for <'a> Router<<<S as Deref>::Target as routing::LockableScore<'a>>::Locked>, R: for <'a> Router<<<S as Deref>::Target as LockableScore<'a>>::Locked>,
S::Target: for <'a> routing::LockableScore<'a>, S::Target: for <'a> LockableScore<'a>,
L::Target: Logger, L::Target: Logger,
E: EventHandler, E: EventHandler,
{ {
@ -1186,7 +1185,7 @@ mod tests {
} }
} }
impl<S: routing::Score> Router<S> for TestRouter { impl<S: Score> Router<S> for TestRouter {
fn find_route( fn find_route(
&self, _payer: &PublicKey, params: &RouteParameters, _payment_hash: &PaymentHash, &self, _payer: &PublicKey, params: &RouteParameters, _payment_hash: &PaymentHash,
_first_hops: Option<&[&ChannelDetails]>, _scorer: &S _first_hops: Option<&[&ChannelDetails]>, _scorer: &S
@ -1199,7 +1198,7 @@ mod tests {
struct FailingRouter; struct FailingRouter;
impl<S: routing::Score> Router<S> for FailingRouter { impl<S: Score> Router<S> for FailingRouter {
fn find_route( fn find_route(
&self, _payer: &PublicKey, _params: &RouteParameters, _payment_hash: &PaymentHash, &self, _payer: &PublicKey, _params: &RouteParameters, _payment_hash: &PaymentHash,
_first_hops: Option<&[&ChannelDetails]>, _scorer: &S _first_hops: Option<&[&ChannelDetails]>, _scorer: &S
@ -1225,9 +1224,9 @@ mod tests {
} }
} }
impl routing::Score for TestScorer { impl Score for TestScorer {
fn channel_penalty_msat( fn channel_penalty_msat(
&self, _short_channel_id: u64, _source: &NodeId, _target: &NodeId &self, _short_channel_id: u64, _send_amt: u64, _chan_amt: Option<u64>, _source: &NodeId, _target: &NodeId
) -> u64 { 0 } ) -> u64 { 0 }
fn payment_path_failed(&mut self, _path: &[&RouteHop], short_channel_id: u64) { fn payment_path_failed(&mut self, _path: &[&RouteHop], short_channel_id: u64) {
@ -1364,7 +1363,7 @@ mod tests {
// *** Full Featured Functional Tests with a Real ChannelManager *** // *** Full Featured Functional Tests with a Real ChannelManager ***
struct ManualRouter(RefCell<VecDeque<Result<Route, LightningError>>>); struct ManualRouter(RefCell<VecDeque<Result<Route, LightningError>>>);
impl<S: routing::Score> Router<S> for ManualRouter { impl<S: Score> Router<S> for ManualRouter {
fn find_route( fn find_route(
&self, _payer: &PublicKey, _params: &RouteParameters, _payment_hash: &PaymentHash, &self, _payer: &PublicKey, _params: &RouteParameters, _payment_hash: &PaymentHash,
_first_hops: Option<&[&ChannelDetails]>, _scorer: &S _first_hops: Option<&[&ChannelDetails]>, _scorer: &S

View file

@ -11,7 +11,7 @@ use lightning::chain::keysinterface::{Sign, KeysInterface};
use lightning::ln::{PaymentHash, PaymentPreimage, PaymentSecret}; use lightning::ln::{PaymentHash, PaymentPreimage, PaymentSecret};
use lightning::ln::channelmanager::{ChannelDetails, ChannelManager, PaymentId, PaymentSendFailure, MIN_FINAL_CLTV_EXPIRY}; use lightning::ln::channelmanager::{ChannelDetails, ChannelManager, PaymentId, PaymentSendFailure, MIN_FINAL_CLTV_EXPIRY};
use lightning::ln::msgs::LightningError; use lightning::ln::msgs::LightningError;
use lightning::routing; use lightning::routing::scoring::Score;
use lightning::routing::network_graph::{NetworkGraph, RoutingFees}; use lightning::routing::network_graph::{NetworkGraph, RoutingFees};
use lightning::routing::router::{Route, RouteHint, RouteHintHop, RouteParameters, find_route}; use lightning::routing::router::{Route, RouteHint, RouteHintHop, RouteParameters, find_route};
use lightning::util::logger::Logger; use lightning::util::logger::Logger;
@ -109,7 +109,7 @@ impl<G, L: Deref> DefaultRouter<G, L> where G: Deref<Target = NetworkGraph>, L::
} }
} }
impl<G, L: Deref, S: routing::Score> Router<S> for DefaultRouter<G, L> impl<G, L: Deref, S: Score> Router<S> for DefaultRouter<G, L>
where G: Deref<Target = NetworkGraph>, L::Target: Logger { where G: Deref<Target = NetworkGraph>, L::Target: Logger {
fn find_route( fn find_route(
&self, payer: &PublicKey, params: &RouteParameters, _payment_hash: &PaymentHash, &self, payer: &PublicKey, params: &RouteParameters, _payment_hash: &PaymentHash,

View file

@ -6552,7 +6552,7 @@ pub mod bench {
use ln::msgs::{ChannelMessageHandler, Init}; use ln::msgs::{ChannelMessageHandler, Init};
use routing::network_graph::NetworkGraph; use routing::network_graph::NetworkGraph;
use routing::router::{Payee, get_route}; use routing::router::{Payee, get_route};
use routing::scorer::Scorer; use routing::scoring::Scorer;
use util::test_utils; use util::test_utils;
use util::config::UserConfig; use util::config::UserConfig;
use util::events::{Event, MessageSendEvent, MessageSendEventsProvider, PaymentPurpose}; use util::events::{Event, MessageSendEvent, MessageSendEventsProvider, PaymentPurpose};

View file

@ -11,65 +11,4 @@
pub mod network_graph; pub mod network_graph;
pub mod router; pub mod router;
pub mod scorer; pub mod scoring;
use routing::network_graph::NodeId;
use routing::router::RouteHop;
use core::cell::{RefCell, RefMut};
use core::ops::DerefMut;
use sync::{Mutex, MutexGuard};
/// 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 {
/// Returns the fee in msats willing to be paid to avoid routing through the given channel
/// in the direction from `source` to `target`.
fn channel_penalty_msat(&self, short_channel_id: u64, source: &NodeId, target: &NodeId) -> u64;
/// Handles updating channel penalties after failing to route through a channel.
fn payment_path_failed(&mut self, path: &[&RouteHop], short_channel_id: u64);
}
/// 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`]
/// 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.
///
/// [`find_route`]: crate::routing::router::find_route
pub trait LockableScore<'a> {
/// The locked [`Score`] type.
type Locked: 'a + Score;
/// Returns the locked scorer.
fn lock(&'a self) -> Self::Locked;
}
impl<'a, T: 'a + Score> LockableScore<'a> for Mutex<T> {
type Locked = MutexGuard<'a, T>;
fn lock(&'a self) -> MutexGuard<'a, T> {
Mutex::lock(self).unwrap()
}
}
impl<'a, T: 'a + Score> LockableScore<'a> for RefCell<T> {
type Locked = RefMut<'a, T>;
fn lock(&'a self) -> RefMut<'a, T> {
self.borrow_mut()
}
}
impl<S: Score, T: DerefMut<Target=S>> Score for T {
fn channel_penalty_msat(&self, short_channel_id: u64, source: &NodeId, target: &NodeId) -> u64 {
self.deref().channel_penalty_msat(short_channel_id, source, target)
}
fn payment_path_failed(&mut self, path: &[&RouteHop], short_channel_id: u64) {
self.deref_mut().payment_path_failed(path, short_channel_id)
}
}

View file

@ -17,7 +17,7 @@ use bitcoin::secp256k1::key::PublicKey;
use ln::channelmanager::ChannelDetails; use ln::channelmanager::ChannelDetails;
use ln::features::{ChannelFeatures, InvoiceFeatures, NodeFeatures}; use ln::features::{ChannelFeatures, InvoiceFeatures, NodeFeatures};
use ln::msgs::{DecodeError, ErrorAction, LightningError, MAX_VALUE_MSAT}; use ln::msgs::{DecodeError, ErrorAction, LightningError, MAX_VALUE_MSAT};
use routing; use routing::scoring::Score;
use routing::network_graph::{NetworkGraph, NodeId, RoutingFees}; use routing::network_graph::{NetworkGraph, NodeId, RoutingFees};
use util::ser::{Writeable, Readable}; use util::ser::{Writeable, Readable};
use util::logger::{Level, Logger}; use util::logger::{Level, Logger};
@ -529,7 +529,7 @@ fn compute_fees(amount_msat: u64, channel_fees: RoutingFees) -> Option<u64> {
/// ///
/// [`ChannelManager::list_usable_channels`]: crate::ln::channelmanager::ChannelManager::list_usable_channels /// [`ChannelManager::list_usable_channels`]: crate::ln::channelmanager::ChannelManager::list_usable_channels
/// [`Event::PaymentPathFailed`]: crate::util::events::Event::PaymentPathFailed /// [`Event::PaymentPathFailed`]: crate::util::events::Event::PaymentPathFailed
pub fn find_route<L: Deref, S: routing::Score>( pub fn find_route<L: Deref, S: Score>(
our_node_pubkey: &PublicKey, params: &RouteParameters, network: &NetworkGraph, our_node_pubkey: &PublicKey, params: &RouteParameters, network: &NetworkGraph,
first_hops: Option<&[&ChannelDetails]>, logger: L, scorer: &S first_hops: Option<&[&ChannelDetails]>, logger: L, scorer: &S
) -> Result<Route, LightningError> ) -> Result<Route, LightningError>
@ -540,7 +540,7 @@ where L::Target: Logger {
) )
} }
pub(crate) fn get_route<L: Deref, S: routing::Score>( pub(crate) fn get_route<L: Deref, S: Score>(
our_node_pubkey: &PublicKey, payee: &Payee, network: &NetworkGraph, our_node_pubkey: &PublicKey, payee: &Payee, network: &NetworkGraph,
first_hops: Option<&[&ChannelDetails]>, final_value_msat: u64, final_cltv_expiry_delta: u32, first_hops: Option<&[&ChannelDetails]>, final_value_msat: u64, final_cltv_expiry_delta: u32,
logger: L, scorer: &S logger: L, scorer: &S
@ -892,9 +892,9 @@ where L::Target: Logger {
} }
} }
let path_penalty_msat = $next_hops_path_penalty_msat let path_penalty_msat = $next_hops_path_penalty_msat.checked_add(
.checked_add(scorer.channel_penalty_msat($chan_id.clone(), &$src_node_id, &$dest_node_id)) scorer.channel_penalty_msat($chan_id.clone(), amount_to_transfer_over_msat, Some(*available_liquidity_msat),
.unwrap_or_else(|| u64::max_value()); &$src_node_id, &$dest_node_id)).unwrap_or_else(|| u64::max_value());
let new_graph_node = RouteGraphNode { let new_graph_node = RouteGraphNode {
node_id: $src_node_id, node_id: $src_node_id,
lowest_fee_to_peer_through_node: total_fee_msat, lowest_fee_to_peer_through_node: total_fee_msat,
@ -1121,7 +1121,7 @@ where L::Target: Logger {
let src_node_id = NodeId::from_pubkey(&hop.src_node_id); let src_node_id = NodeId::from_pubkey(&hop.src_node_id);
let dest_node_id = NodeId::from_pubkey(&prev_hop_id); let dest_node_id = NodeId::from_pubkey(&prev_hop_id);
aggregate_next_hops_path_penalty_msat = aggregate_next_hops_path_penalty_msat aggregate_next_hops_path_penalty_msat = aggregate_next_hops_path_penalty_msat
.checked_add(scorer.channel_penalty_msat(hop.short_channel_id, &src_node_id, &dest_node_id)) .checked_add(scorer.channel_penalty_msat(hop.short_channel_id, final_value_msat, None, &src_node_id, &dest_node_id))
.unwrap_or_else(|| u64::max_value()); .unwrap_or_else(|| u64::max_value());
// We assume that the recipient only included route hints for routes which had // We assume that the recipient only included route hints for routes which had
@ -1472,7 +1472,7 @@ where L::Target: Logger {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use routing; use routing::scoring::Score;
use routing::network_graph::{NetworkGraph, NetGraphMsgHandler, NodeId}; use routing::network_graph::{NetworkGraph, NetGraphMsgHandler, NodeId};
use routing::router::{get_route, Payee, Route, RouteHint, RouteHintHop, RouteHop, RoutingFees}; use routing::router::{get_route, Payee, Route, RouteHint, RouteHintHop, RouteHop, RoutingFees};
use chain::transaction::OutPoint; use chain::transaction::OutPoint;
@ -4549,8 +4549,8 @@ mod tests {
short_channel_id: u64, short_channel_id: u64,
} }
impl routing::Score for BadChannelScorer { impl Score for BadChannelScorer {
fn channel_penalty_msat(&self, short_channel_id: u64, _source: &NodeId, _target: &NodeId) -> u64 { fn channel_penalty_msat(&self, short_channel_id: u64, _send_amt: u64, _chan_amt: Option<u64>, _source: &NodeId, _target: &NodeId) -> u64 {
if short_channel_id == self.short_channel_id { u64::max_value() } else { 0 } if short_channel_id == self.short_channel_id { u64::max_value() } else { 0 }
} }
@ -4561,8 +4561,8 @@ mod tests {
node_id: NodeId, node_id: NodeId,
} }
impl routing::Score for BadNodeScorer { impl Score for BadNodeScorer {
fn channel_penalty_msat(&self, _short_channel_id: u64, _source: &NodeId, target: &NodeId) -> u64 { fn channel_penalty_msat(&self, _short_channel_id: u64, _send_amt: u64, _chan_amt: Option<u64>, _source: &NodeId, target: &NodeId) -> u64 {
if *target == self.node_id { u64::max_value() } else { 0 } if *target == self.node_id { u64::max_value() } else { 0 }
} }
@ -4787,7 +4787,7 @@ pub(crate) mod test_utils {
#[cfg(all(test, feature = "unstable", not(feature = "no-std")))] #[cfg(all(test, feature = "unstable", not(feature = "no-std")))]
mod benches { mod benches {
use super::*; use super::*;
use routing::scorer::Scorer; use routing::scoring::Scorer;
use util::logger::{Logger, Record}; use util::logger::{Logger, Record};
use test::Bencher; use test::Bencher;

View file

@ -10,7 +10,7 @@
//! Utilities for scoring payment channels. //! Utilities for scoring payment channels.
//! //!
//! [`Scorer`] may be given to [`find_route`] to score payment channels during path finding when a //! [`Scorer`] may be given to [`find_route`] to score payment channels during path finding when a
//! custom [`routing::Score`] implementation is not needed. //! custom [`Score`] implementation is not needed.
//! //!
//! # Example //! # Example
//! //!
@ -19,7 +19,7 @@
//! # //! #
//! # use lightning::routing::network_graph::NetworkGraph; //! # use lightning::routing::network_graph::NetworkGraph;
//! # use lightning::routing::router::{RouteParameters, find_route}; //! # use lightning::routing::router::{RouteParameters, find_route};
//! # use lightning::routing::scorer::{Scorer, ScoringParameters}; //! # use lightning::routing::scoring::{Scorer, ScoringParameters};
//! # use lightning::util::logger::{Logger, Record}; //! # use lightning::util::logger::{Logger, Record};
//! # use secp256k1::key::PublicKey; //! # use secp256k1::key::PublicKey;
//! # //! #
@ -52,26 +52,89 @@
//! //!
//! [`find_route`]: crate::routing::router::find_route //! [`find_route`]: crate::routing::router::find_route
use routing;
use ln::msgs::DecodeError; use ln::msgs::DecodeError;
use routing::network_graph::NodeId; use routing::network_graph::NodeId;
use routing::router::RouteHop; use routing::router::RouteHop;
use util::ser::{Readable, Writeable, Writer}; use util::ser::{Readable, Writeable, Writer};
use prelude::*; use prelude::*;
use core::ops::Sub; use core::cell::{RefCell, RefMut};
use core::ops::{DerefMut, Sub};
use core::time::Duration; use core::time::Duration;
use io::{self, Read}; use io::{self, Read}; use sync::{Mutex, MutexGuard};
/// [`routing::Score`] implementation that provides reasonable default behavior. /// 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 {
/// Returns the fee in msats willing to be paid to avoid routing `send_amt_msat` through the
/// given channel in the direction from `source` to `target`.
///
/// The channel's capacity (less any other MPP parts which are also being considered for use in
/// the same payment) is given by `channel_capacity_msat`. It may be guessed from various
/// sources or assumed from no data at all.
///
/// For hints provided in the invoice, we assume the channel has sufficient capacity to accept
/// the invoice's full amount, and provide a `channel_capacity_msat` of `None`. In all other
/// cases it is set to `Some`, even if we're guessing at the channel value.
///
/// Your code should be overflow-safe through a `channel_capacity_msat` of 21 million BTC.
fn channel_penalty_msat(&self, short_channel_id: u64, send_amt_msat: u64, channel_capacity_msat: Option<u64>, source: &NodeId, target: &NodeId) -> u64;
/// Handles updating channel penalties after failing to route through a channel.
fn payment_path_failed(&mut self, path: &[&RouteHop], short_channel_id: u64);
}
/// 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`]
/// 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.
///
/// [`find_route`]: crate::routing::router::find_route
pub trait LockableScore<'a> {
/// The locked [`Score`] type.
type Locked: 'a + Score;
/// Returns the locked scorer.
fn lock(&'a self) -> Self::Locked;
}
impl<'a, T: 'a + Score> LockableScore<'a> for Mutex<T> {
type Locked = MutexGuard<'a, T>;
fn lock(&'a self) -> MutexGuard<'a, T> {
Mutex::lock(self).unwrap()
}
}
impl<'a, T: 'a + Score> LockableScore<'a> for RefCell<T> {
type Locked = RefMut<'a, T>;
fn lock(&'a self) -> RefMut<'a, T> {
self.borrow_mut()
}
}
impl<S: Score, T: DerefMut<Target=S>> Score for T {
fn channel_penalty_msat(&self, short_channel_id: u64, send_amt_msat: u64, channel_capacity_msat: Option<u64>, source: &NodeId, target: &NodeId) -> u64 {
self.deref().channel_penalty_msat(short_channel_id, send_amt_msat, channel_capacity_msat, source, target)
}
fn payment_path_failed(&mut self, path: &[&RouteHop], short_channel_id: u64) {
self.deref_mut().payment_path_failed(path, short_channel_id)
}
}
/// [`Score`] implementation that provides reasonable default behavior.
/// ///
/// Used to apply a fixed penalty to each channel, thus avoiding long paths when shorter paths with /// Used to apply a fixed penalty to each channel, thus avoiding long paths when shorter paths with
/// slightly higher fees are available. Will further penalize channels that fail to relay payments. /// slightly higher fees are available. Will further penalize channels that fail to relay payments.
/// ///
/// See [module-level documentation] for usage. /// See [module-level documentation] for usage.
/// ///
/// [module-level documentation]: crate::routing::scorer /// [module-level documentation]: crate::routing::scoring
pub type Scorer = ScorerUsingTime::<DefaultTime>; pub type Scorer = ScorerUsingTime::<DefaultTime>;
/// Time used by [`Scorer`]. /// Time used by [`Scorer`].
@ -82,7 +145,7 @@ pub type DefaultTime = std::time::Instant;
#[cfg(feature = "no-std")] #[cfg(feature = "no-std")]
pub type DefaultTime = Eternity; pub type DefaultTime = Eternity;
/// [`routing::Score`] implementation parameterized by [`Time`]. /// [`Score`] implementation parameterized by [`Time`].
/// ///
/// See [`Scorer`] for details. /// See [`Scorer`] for details.
/// ///
@ -98,6 +161,8 @@ pub struct ScorerUsingTime<T: Time> {
/// Parameters for configuring [`Scorer`]. /// Parameters for configuring [`Scorer`].
pub struct ScoringParameters { pub struct ScoringParameters {
/// A fixed penalty in msats to apply to each channel. /// A fixed penalty in msats to apply to each channel.
///
/// Default value: 500 msat
pub base_penalty_msat: u64, pub base_penalty_msat: u64,
/// A penalty in msats to apply to a channel upon failing to relay a payment. /// A penalty in msats to apply to a channel upon failing to relay a payment.
@ -105,9 +170,28 @@ pub struct ScoringParameters {
/// This accumulates for each failure but may be reduced over time based on /// This accumulates for each failure but may be reduced over time based on
/// [`failure_penalty_half_life`]. /// [`failure_penalty_half_life`].
/// ///
/// Default value: 1,024,000 msat
///
/// [`failure_penalty_half_life`]: Self::failure_penalty_half_life /// [`failure_penalty_half_life`]: Self::failure_penalty_half_life
pub failure_penalty_msat: u64, pub failure_penalty_msat: u64,
/// When the amount being sent over a channel is this many 1024ths of the total channel
/// capacity, we begin applying [`overuse_penalty_msat_per_1024th`].
///
/// Default value: 128 1024ths (i.e. begin penalizing when an HTLC uses 1/8th of a channel)
///
/// [`overuse_penalty_msat_per_1024th`]: Self::overuse_penalty_msat_per_1024th
pub overuse_penalty_start_1024th: u16,
/// A penalty applied, per whole 1024ths of the channel capacity which the amount being sent
/// over the channel exceeds [`overuse_penalty_start_1024th`] by.
///
/// Default value: 20 msat (i.e. 2560 msat penalty to use 1/4th of a channel, 7680 msat penalty
/// to use half a channel, and 12,560 msat penalty to use 3/4ths of a channel)
///
/// [`overuse_penalty_start_1024th`]: Self::overuse_penalty_start_1024th
pub overuse_penalty_msat_per_1024th: u64,
/// The time required to elapse before any accumulated [`failure_penalty_msat`] penalties are /// The time required to elapse before any accumulated [`failure_penalty_msat`] penalties are
/// cut in half. /// cut in half.
/// ///
@ -122,7 +206,9 @@ pub struct ScoringParameters {
impl_writeable_tlv_based!(ScoringParameters, { impl_writeable_tlv_based!(ScoringParameters, {
(0, base_penalty_msat, required), (0, base_penalty_msat, required),
(1, overuse_penalty_start_1024th, (default_value, 128)),
(2, failure_penalty_msat, required), (2, failure_penalty_msat, required),
(3, overuse_penalty_msat_per_1024th, (default_value, 20)),
(4, failure_penalty_half_life, required), (4, failure_penalty_half_life, required),
}); });
@ -167,6 +253,8 @@ impl<T: Time> ScorerUsingTime<T> {
base_penalty_msat: penalty_msat, base_penalty_msat: penalty_msat,
failure_penalty_msat: 0, failure_penalty_msat: 0,
failure_penalty_half_life: Duration::from_secs(0), failure_penalty_half_life: Duration::from_secs(0),
overuse_penalty_start_1024th: 1024,
overuse_penalty_msat_per_1024th: 0,
}) })
} }
} }
@ -205,19 +293,34 @@ impl Default for ScoringParameters {
base_penalty_msat: 500, base_penalty_msat: 500,
failure_penalty_msat: 1024 * 1000, failure_penalty_msat: 1024 * 1000,
failure_penalty_half_life: Duration::from_secs(3600), failure_penalty_half_life: Duration::from_secs(3600),
overuse_penalty_start_1024th: 1024 / 8,
overuse_penalty_msat_per_1024th: 20,
} }
} }
} }
impl<T: Time> routing::Score for ScorerUsingTime<T> { impl<T: Time> Score for ScorerUsingTime<T> {
fn channel_penalty_msat( fn channel_penalty_msat(
&self, short_channel_id: u64, _source: &NodeId, _target: &NodeId &self, short_channel_id: u64, send_amt_msat: u64, chan_capacity_opt: Option<u64>, _source: &NodeId, _target: &NodeId
) -> u64 { ) -> u64 {
let failure_penalty_msat = self.channel_failures let failure_penalty_msat = self.channel_failures
.get(&short_channel_id) .get(&short_channel_id)
.map_or(0, |value| value.decayed_penalty_msat(self.params.failure_penalty_half_life)); .map_or(0, |value| value.decayed_penalty_msat(self.params.failure_penalty_half_life));
self.params.base_penalty_msat + failure_penalty_msat let mut penalty_msat = self.params.base_penalty_msat + failure_penalty_msat;
if let Some(chan_capacity_msat) = chan_capacity_opt {
let send_1024ths = send_amt_msat.checked_mul(1024).unwrap_or(u64::max_value()) / chan_capacity_msat;
if send_1024ths > self.params.overuse_penalty_start_1024th as u64 {
penalty_msat = penalty_msat.checked_add(
(send_1024ths - self.params.overuse_penalty_start_1024th as u64)
.checked_mul(self.params.overuse_penalty_msat_per_1024th).unwrap_or(u64::max_value()))
.unwrap_or(u64::max_value());
}
}
penalty_msat
} }
fn payment_path_failed(&mut self, _path: &[&RouteHop], short_channel_id: u64) { fn payment_path_failed(&mut self, _path: &[&RouteHop], short_channel_id: u64) {
@ -326,7 +429,7 @@ impl<T: Time> Readable for ChannelFailure<T> {
mod tests { mod tests {
use super::{Eternity, ScoringParameters, ScorerUsingTime, Time}; use super::{Eternity, ScoringParameters, ScorerUsingTime, Time};
use routing::Score; use routing::scoring::Score;
use routing::network_graph::NodeId; use routing::network_graph::NodeId;
use util::ser::{Readable, Writeable}; use util::ser::{Readable, Writeable};
@ -414,13 +517,15 @@ mod tests {
base_penalty_msat: 1_000, base_penalty_msat: 1_000,
failure_penalty_msat: 512, failure_penalty_msat: 512,
failure_penalty_half_life: Duration::from_secs(1), failure_penalty_half_life: Duration::from_secs(1),
overuse_penalty_start_1024th: 1024,
overuse_penalty_msat_per_1024th: 0,
}); });
let source = source_node_id(); let source = source_node_id();
let target = target_node_id(); let target = target_node_id();
assert_eq!(scorer.channel_penalty_msat(42, &source, &target), 1_000); assert_eq!(scorer.channel_penalty_msat(42, 1, Some(1), &source, &target), 1_000);
SinceEpoch::advance(Duration::from_secs(1)); SinceEpoch::advance(Duration::from_secs(1));
assert_eq!(scorer.channel_penalty_msat(42, &source, &target), 1_000); assert_eq!(scorer.channel_penalty_msat(42, 1, Some(1), &source, &target), 1_000);
} }
#[test] #[test]
@ -429,19 +534,21 @@ mod tests {
base_penalty_msat: 1_000, base_penalty_msat: 1_000,
failure_penalty_msat: 64, failure_penalty_msat: 64,
failure_penalty_half_life: Duration::from_secs(10), failure_penalty_half_life: Duration::from_secs(10),
overuse_penalty_start_1024th: 1024,
overuse_penalty_msat_per_1024th: 0,
}); });
let source = source_node_id(); let source = source_node_id();
let target = target_node_id(); let target = target_node_id();
assert_eq!(scorer.channel_penalty_msat(42, &source, &target), 1_000); assert_eq!(scorer.channel_penalty_msat(42, 1, Some(1), &source, &target), 1_000);
scorer.payment_path_failed(&[], 42); scorer.payment_path_failed(&[], 42);
assert_eq!(scorer.channel_penalty_msat(42, &source, &target), 1_064); assert_eq!(scorer.channel_penalty_msat(42, 1, Some(1), &source, &target), 1_064);
scorer.payment_path_failed(&[], 42); scorer.payment_path_failed(&[], 42);
assert_eq!(scorer.channel_penalty_msat(42, &source, &target), 1_128); assert_eq!(scorer.channel_penalty_msat(42, 1, Some(1), &source, &target), 1_128);
scorer.payment_path_failed(&[], 42); scorer.payment_path_failed(&[], 42);
assert_eq!(scorer.channel_penalty_msat(42, &source, &target), 1_192); assert_eq!(scorer.channel_penalty_msat(42, 1, Some(1), &source, &target), 1_192);
} }
#[test] #[test]
@ -450,28 +557,30 @@ mod tests {
base_penalty_msat: 1_000, base_penalty_msat: 1_000,
failure_penalty_msat: 512, failure_penalty_msat: 512,
failure_penalty_half_life: Duration::from_secs(10), failure_penalty_half_life: Duration::from_secs(10),
overuse_penalty_start_1024th: 1024,
overuse_penalty_msat_per_1024th: 0,
}); });
let source = source_node_id(); let source = source_node_id();
let target = target_node_id(); let target = target_node_id();
assert_eq!(scorer.channel_penalty_msat(42, &source, &target), 1_000); assert_eq!(scorer.channel_penalty_msat(42, 1, Some(1), &source, &target), 1_000);
scorer.payment_path_failed(&[], 42); scorer.payment_path_failed(&[], 42);
assert_eq!(scorer.channel_penalty_msat(42, &source, &target), 1_512); assert_eq!(scorer.channel_penalty_msat(42, 1, Some(1), &source, &target), 1_512);
SinceEpoch::advance(Duration::from_secs(9)); SinceEpoch::advance(Duration::from_secs(9));
assert_eq!(scorer.channel_penalty_msat(42, &source, &target), 1_512); assert_eq!(scorer.channel_penalty_msat(42, 1, Some(1), &source, &target), 1_512);
SinceEpoch::advance(Duration::from_secs(1)); SinceEpoch::advance(Duration::from_secs(1));
assert_eq!(scorer.channel_penalty_msat(42, &source, &target), 1_256); assert_eq!(scorer.channel_penalty_msat(42, 1, Some(1), &source, &target), 1_256);
SinceEpoch::advance(Duration::from_secs(10 * 8)); SinceEpoch::advance(Duration::from_secs(10 * 8));
assert_eq!(scorer.channel_penalty_msat(42, &source, &target), 1_001); assert_eq!(scorer.channel_penalty_msat(42, 1, Some(1), &source, &target), 1_001);
SinceEpoch::advance(Duration::from_secs(10)); SinceEpoch::advance(Duration::from_secs(10));
assert_eq!(scorer.channel_penalty_msat(42, &source, &target), 1_000); assert_eq!(scorer.channel_penalty_msat(42, 1, Some(1), &source, &target), 1_000);
SinceEpoch::advance(Duration::from_secs(10)); SinceEpoch::advance(Duration::from_secs(10));
assert_eq!(scorer.channel_penalty_msat(42, &source, &target), 1_000); assert_eq!(scorer.channel_penalty_msat(42, 1, Some(1), &source, &target), 1_000);
} }
#[test] #[test]
@ -480,22 +589,24 @@ mod tests {
base_penalty_msat: 1_000, base_penalty_msat: 1_000,
failure_penalty_msat: 512, failure_penalty_msat: 512,
failure_penalty_half_life: Duration::from_secs(10), failure_penalty_half_life: Duration::from_secs(10),
overuse_penalty_start_1024th: 1024,
overuse_penalty_msat_per_1024th: 0,
}); });
let source = source_node_id(); let source = source_node_id();
let target = target_node_id(); let target = target_node_id();
assert_eq!(scorer.channel_penalty_msat(42, &source, &target), 1_000); assert_eq!(scorer.channel_penalty_msat(42, 1, Some(1), &source, &target), 1_000);
scorer.payment_path_failed(&[], 42); scorer.payment_path_failed(&[], 42);
assert_eq!(scorer.channel_penalty_msat(42, &source, &target), 1_512); assert_eq!(scorer.channel_penalty_msat(42, 1, Some(1), &source, &target), 1_512);
SinceEpoch::advance(Duration::from_secs(10)); SinceEpoch::advance(Duration::from_secs(10));
assert_eq!(scorer.channel_penalty_msat(42, &source, &target), 1_256); assert_eq!(scorer.channel_penalty_msat(42, 1, Some(1), &source, &target), 1_256);
scorer.payment_path_failed(&[], 42); scorer.payment_path_failed(&[], 42);
assert_eq!(scorer.channel_penalty_msat(42, &source, &target), 1_768); assert_eq!(scorer.channel_penalty_msat(42, 1, Some(1), &source, &target), 1_768);
SinceEpoch::advance(Duration::from_secs(10)); SinceEpoch::advance(Duration::from_secs(10));
assert_eq!(scorer.channel_penalty_msat(42, &source, &target), 1_384); assert_eq!(scorer.channel_penalty_msat(42, 1, Some(1), &source, &target), 1_384);
} }
#[test] #[test]
@ -504,25 +615,27 @@ mod tests {
base_penalty_msat: 1_000, base_penalty_msat: 1_000,
failure_penalty_msat: 512, failure_penalty_msat: 512,
failure_penalty_half_life: Duration::from_secs(10), failure_penalty_half_life: Duration::from_secs(10),
overuse_penalty_start_1024th: 1024,
overuse_penalty_msat_per_1024th: 0,
}); });
let source = source_node_id(); let source = source_node_id();
let target = target_node_id(); let target = target_node_id();
scorer.payment_path_failed(&[], 42); scorer.payment_path_failed(&[], 42);
assert_eq!(scorer.channel_penalty_msat(42, &source, &target), 1_512); assert_eq!(scorer.channel_penalty_msat(42, 1, Some(1), &source, &target), 1_512);
SinceEpoch::advance(Duration::from_secs(10)); SinceEpoch::advance(Duration::from_secs(10));
assert_eq!(scorer.channel_penalty_msat(42, &source, &target), 1_256); assert_eq!(scorer.channel_penalty_msat(42, 1, Some(1), &source, &target), 1_256);
scorer.payment_path_failed(&[], 43); scorer.payment_path_failed(&[], 43);
assert_eq!(scorer.channel_penalty_msat(43, &source, &target), 1_512); assert_eq!(scorer.channel_penalty_msat(43, 1, Some(1), &source, &target), 1_512);
let mut serialized_scorer = Vec::new(); let mut serialized_scorer = Vec::new();
scorer.write(&mut serialized_scorer).unwrap(); scorer.write(&mut serialized_scorer).unwrap();
let deserialized_scorer = <Scorer>::read(&mut io::Cursor::new(&serialized_scorer)).unwrap(); let deserialized_scorer = <Scorer>::read(&mut io::Cursor::new(&serialized_scorer)).unwrap();
assert_eq!(deserialized_scorer.channel_penalty_msat(42, &source, &target), 1_256); assert_eq!(deserialized_scorer.channel_penalty_msat(42, 1, Some(1), &source, &target), 1_256);
assert_eq!(deserialized_scorer.channel_penalty_msat(43, &source, &target), 1_512); assert_eq!(deserialized_scorer.channel_penalty_msat(43, 1, Some(1), &source, &target), 1_512);
} }
#[test] #[test]
@ -531,12 +644,14 @@ mod tests {
base_penalty_msat: 1_000, base_penalty_msat: 1_000,
failure_penalty_msat: 512, failure_penalty_msat: 512,
failure_penalty_half_life: Duration::from_secs(10), failure_penalty_half_life: Duration::from_secs(10),
overuse_penalty_start_1024th: 1024,
overuse_penalty_msat_per_1024th: 0,
}); });
let source = source_node_id(); let source = source_node_id();
let target = target_node_id(); let target = target_node_id();
scorer.payment_path_failed(&[], 42); scorer.payment_path_failed(&[], 42);
assert_eq!(scorer.channel_penalty_msat(42, &source, &target), 1_512); assert_eq!(scorer.channel_penalty_msat(42, 1, Some(1), &source, &target), 1_512);
let mut serialized_scorer = Vec::new(); let mut serialized_scorer = Vec::new();
scorer.write(&mut serialized_scorer).unwrap(); scorer.write(&mut serialized_scorer).unwrap();
@ -544,9 +659,29 @@ mod tests {
SinceEpoch::advance(Duration::from_secs(10)); SinceEpoch::advance(Duration::from_secs(10));
let deserialized_scorer = <Scorer>::read(&mut io::Cursor::new(&serialized_scorer)).unwrap(); let deserialized_scorer = <Scorer>::read(&mut io::Cursor::new(&serialized_scorer)).unwrap();
assert_eq!(deserialized_scorer.channel_penalty_msat(42, &source, &target), 1_256); assert_eq!(deserialized_scorer.channel_penalty_msat(42, 1, Some(1), &source, &target), 1_256);
SinceEpoch::advance(Duration::from_secs(10)); SinceEpoch::advance(Duration::from_secs(10));
assert_eq!(deserialized_scorer.channel_penalty_msat(42, &source, &target), 1_128); assert_eq!(deserialized_scorer.channel_penalty_msat(42, 1, Some(1), &source, &target), 1_128);
}
#[test]
fn charges_per_1024th_penalty() {
let scorer = Scorer::new(ScoringParameters {
base_penalty_msat: 0,
failure_penalty_msat: 0,
failure_penalty_half_life: Duration::from_secs(0),
overuse_penalty_start_1024th: 256,
overuse_penalty_msat_per_1024th: 100,
});
let source = source_node_id();
let target = target_node_id();
assert_eq!(scorer.channel_penalty_msat(42, 1_000, None, &source, &target), 0);
assert_eq!(scorer.channel_penalty_msat(42, 1_000, Some(1_024_000), &source, &target), 0);
assert_eq!(scorer.channel_penalty_msat(42, 256_999, Some(1_024_000), &source, &target), 0);
assert_eq!(scorer.channel_penalty_msat(42, 257_000, Some(1_024_000), &source, &target), 100);
assert_eq!(scorer.channel_penalty_msat(42, 258_000, Some(1_024_000), &source, &target), 200);
assert_eq!(scorer.channel_penalty_msat(42, 512_000, Some(1_024_000), &source, &target), 256 * 100);
} }
} }

View file

@ -21,7 +21,7 @@ use ln::features::{ChannelFeatures, InitFeatures};
use ln::msgs; use ln::msgs;
use ln::msgs::OptionalField; use ln::msgs::OptionalField;
use ln::script::ShutdownScript; use ln::script::ShutdownScript;
use routing::scorer::{Eternity, ScorerUsingTime}; use routing::scoring::{Eternity, ScorerUsingTime};
use util::enforcing_trait_impls::{EnforcingSigner, EnforcementState}; use util::enforcing_trait_impls::{EnforcingSigner, EnforcementState};
use util::events; use util::events;
use util::logger::{Logger, Level, Record}; use util::logger::{Logger, Level, Record};