let (secp_ctx, net_graph_msg_handler, _, logger) = build_graph();
let (_, our_id, _, nodes) = get_nodes(&secp_ctx);
let payee = Payee::new(nodes[2]);
- let scorer = Scorer::new(0);
+ let scorer = Scorer::with_fixed_penalty(0);
// Simple route to 2 via 1
let (secp_ctx, net_graph_msg_handler, _, logger) = build_graph();
let (_, our_id, _, nodes) = get_nodes(&secp_ctx);
let payee = Payee::new(nodes[2]);
- let scorer = Scorer::new(0);
+ let scorer = Scorer::with_fixed_penalty(0);
// Simple route to 2 via 1
let (secp_ctx, net_graph_msg_handler, _, logger) = build_graph();
let (our_privkey, our_id, privkeys, nodes) = get_nodes(&secp_ctx);
let payee = Payee::new(nodes[2]);
- let scorer = Scorer::new(0);
+ let scorer = Scorer::with_fixed_penalty(0);
// Simple route to 2 via 1
let (secp_ctx, net_graph_msg_handler, _, logger) = build_graph();
let (our_privkey, our_id, privkeys, nodes) = get_nodes(&secp_ctx);
let payee = Payee::new(nodes[2]).with_features(InvoiceFeatures::known());
- let scorer = Scorer::new(0);
+ let scorer = Scorer::with_fixed_penalty(0);
// A route to node#2 via two paths.
// One path allows transferring 35-40 sats, another one also allows 35-40 sats.
let (secp_ctx, net_graph_msg_handler, _, logger) = build_graph();
let (our_privkey, our_id, privkeys, nodes) = get_nodes(&secp_ctx);
let payee = Payee::new(nodes[2]);
- let scorer = Scorer::new(0);
+ let scorer = Scorer::with_fixed_penalty(0);
// // Disable channels 4 and 12 by flags=2
update_channel(&net_graph_msg_handler, &secp_ctx, &privkeys[1], UnsignedChannelUpdate {
let (secp_ctx, net_graph_msg_handler, _, logger) = build_graph();
let (_, our_id, privkeys, nodes) = get_nodes(&secp_ctx);
let payee = Payee::new(nodes[2]);
- let scorer = Scorer::new(0);
+ let scorer = Scorer::with_fixed_penalty(0);
// Disable nodes 1, 2, and 8 by requiring unknown feature bits
let unknown_features = NodeFeatures::known().set_unknown_feature_required();
fn our_chans_test() {
let (secp_ctx, net_graph_msg_handler, _, logger) = build_graph();
let (_, our_id, _, nodes) = get_nodes(&secp_ctx);
- let scorer = Scorer::new(0);
+ let scorer = Scorer::with_fixed_penalty(0);
// Route to 1 via 2 and 3 because our channel to 1 is disabled
let payee = Payee::new(nodes[0]);
fn partial_route_hint_test() {
let (secp_ctx, net_graph_msg_handler, _, logger) = build_graph();
let (_, our_id, _, nodes) = get_nodes(&secp_ctx);
- let scorer = Scorer::new(0);
+ let scorer = Scorer::with_fixed_penalty(0);
// Simple test across 2, 3, 5, and 4 via a last_hop channel
// Tests the behaviour when the RouteHint contains a suboptimal hop.
let (secp_ctx, net_graph_msg_handler, _, logger) = build_graph();
let (_, our_id, _, nodes) = get_nodes(&secp_ctx);
let payee = Payee::new(nodes[6]).with_route_hints(empty_last_hop(&nodes));
- let scorer = Scorer::new(0);
+ let scorer = Scorer::with_fixed_penalty(0);
// Test handling of an empty RouteHint passed in Invoice.
let (secp_ctx, net_graph_msg_handler, _, logger) = build_graph();
let (_, our_id, privkeys, nodes) = get_nodes(&secp_ctx);
let payee = Payee::new(nodes[6]).with_route_hints(multi_hint_last_hops(&nodes));
- let scorer = Scorer::new(0);
+ let scorer = Scorer::with_fixed_penalty(0);
// Test through channels 2, 3, 5, 8.
// Test shows that multiple hop hints are considered.
let (secp_ctx, net_graph_msg_handler, _, logger) = build_graph();
let (_, our_id, _, nodes) = get_nodes(&secp_ctx);
let payee = Payee::new(nodes[6]).with_route_hints(last_hops_with_public_channel(&nodes));
- let scorer = Scorer::new(0);
+ let scorer = Scorer::with_fixed_penalty(0);
// This test shows that public routes can be present in the invoice
// which would be handled in the same manner.
fn our_chans_last_hop_connect_test() {
let (secp_ctx, net_graph_msg_handler, _, logger) = build_graph();
let (_, our_id, _, nodes) = get_nodes(&secp_ctx);
- let scorer = Scorer::new(0);
+ let scorer = Scorer::with_fixed_penalty(0);
// Simple test with outbound channel to 4 to test that last_hops and first_hops connect
let our_chans = vec![get_channel_details(Some(42), nodes[3].clone(), InitFeatures::from_le_bytes(vec![0b11]), 250_000_000)];
}]);
let payee = Payee::new(target_node_id).with_route_hints(vec![last_hops]);
let our_chans = vec![get_channel_details(Some(42), middle_node_id, InitFeatures::from_le_bytes(vec![0b11]), outbound_capacity_msat)];
- let scorer = Scorer::new(0);
+ let scorer = Scorer::with_fixed_penalty(0);
get_route(&source_node_id, &payee, &NetworkGraph::new(genesis_block(Network::Testnet).header.block_hash()), Some(&our_chans.iter().collect::<Vec<_>>()), route_val, 42, &test_utils::TestLogger::new(), &scorer)
}
let (secp_ctx, mut net_graph_msg_handler, chain_monitor, logger) = build_graph();
let (our_privkey, our_id, privkeys, nodes) = get_nodes(&secp_ctx);
- let scorer = Scorer::new(0);
+ let scorer = Scorer::with_fixed_penalty(0);
let payee = Payee::new(nodes[2]).with_features(InvoiceFeatures::known());
// We will use a simple single-path route from
// one of the latter hops is limited.
let (secp_ctx, net_graph_msg_handler, _, logger) = build_graph();
let (our_privkey, our_id, privkeys, nodes) = get_nodes(&secp_ctx);
- let scorer = Scorer::new(0);
+ let scorer = Scorer::with_fixed_penalty(0);
let payee = Payee::new(nodes[3]).with_features(InvoiceFeatures::known());
// Path via {node7, node2, node4} is channels {12, 13, 6, 11}.
fn ignore_fee_first_hop_test() {
let (secp_ctx, net_graph_msg_handler, _, logger) = build_graph();
let (our_privkey, our_id, privkeys, nodes) = get_nodes(&secp_ctx);
- let scorer = Scorer::new(0);
+ let scorer = Scorer::with_fixed_penalty(0);
let payee = Payee::new(nodes[2]);
// Path via node0 is channels {1, 3}. Limit them to 100 and 50 sats (total limit 50).
fn simple_mpp_route_test() {
let (secp_ctx, net_graph_msg_handler, _, logger) = build_graph();
let (our_privkey, our_id, privkeys, nodes) = get_nodes(&secp_ctx);
- let scorer = Scorer::new(0);
+ let scorer = Scorer::with_fixed_penalty(0);
let payee = Payee::new(nodes[2]).with_features(InvoiceFeatures::known());
// We need a route consisting of 3 paths:
fn long_mpp_route_test() {
let (secp_ctx, net_graph_msg_handler, _, logger) = build_graph();
let (our_privkey, our_id, privkeys, nodes) = get_nodes(&secp_ctx);
- let scorer = Scorer::new(0);
+ let scorer = Scorer::with_fixed_penalty(0);
let payee = Payee::new(nodes[3]).with_features(InvoiceFeatures::known());
// We need a route consisting of 3 paths:
fn mpp_cheaper_route_test() {
let (secp_ctx, net_graph_msg_handler, _, logger) = build_graph();
let (our_privkey, our_id, privkeys, nodes) = get_nodes(&secp_ctx);
- let scorer = Scorer::new(0);
+ let scorer = Scorer::with_fixed_penalty(0);
let payee = Payee::new(nodes[3]).with_features(InvoiceFeatures::known());
// This test checks that if we have two cheaper paths and one more expensive path,
// if the fee is not properly accounted for, the behavior is different.
let (secp_ctx, net_graph_msg_handler, _, logger) = build_graph();
let (our_privkey, our_id, privkeys, nodes) = get_nodes(&secp_ctx);
- let scorer = Scorer::new(0);
+ let scorer = Scorer::with_fixed_penalty(0);
let payee = Payee::new(nodes[3]).with_features(InvoiceFeatures::known());
// We need a route consisting of 2 paths:
// path finding we realize that we found more capacity than we need.
let (secp_ctx, net_graph_msg_handler, _, logger) = build_graph();
let (our_privkey, our_id, privkeys, nodes) = get_nodes(&secp_ctx);
- let scorer = Scorer::new(0);
+ let scorer = Scorer::with_fixed_penalty(0);
let payee = Payee::new(nodes[2]).with_features(InvoiceFeatures::known());
// We need a route consisting of 3 paths:
let network_graph = NetworkGraph::new(genesis_block(Network::Testnet).header.block_hash());
let net_graph_msg_handler = NetGraphMsgHandler::new(network_graph, None, Arc::clone(&logger));
let (our_privkey, our_id, privkeys, nodes) = get_nodes(&secp_ctx);
- let scorer = Scorer::new(0);
+ let scorer = Scorer::with_fixed_penalty(0);
let payee = Payee::new(nodes[6]);
add_channel(&net_graph_msg_handler, &secp_ctx, &our_privkey, &privkeys[1], ChannelFeatures::from_le_bytes(id_to_feature_flags(6)), 6);
// we calculated fees on a higher value, resulting in us ignoring such paths.
let (secp_ctx, net_graph_msg_handler, _, logger) = build_graph();
let (our_privkey, our_id, _, nodes) = get_nodes(&secp_ctx);
- let scorer = Scorer::new(0);
+ let scorer = Scorer::with_fixed_penalty(0);
let payee = Payee::new(nodes[2]);
// We modify the graph to set the htlc_maximum of channel 2 to below the value we wish to
// resulting in us thinking there is no possible path, even if other paths exist.
let (secp_ctx, net_graph_msg_handler, _, logger) = build_graph();
let (our_privkey, our_id, privkeys, nodes) = get_nodes(&secp_ctx);
- let scorer = Scorer::new(0);
+ let scorer = Scorer::with_fixed_penalty(0);
let payee = Payee::new(nodes[2]).with_features(InvoiceFeatures::known());
// We modify the graph to set the htlc_minimum of channel 2 and 4 as needed - channel 2
let (_, our_id, _, nodes) = get_nodes(&secp_ctx);
let logger = Arc::new(test_utils::TestLogger::new());
let network_graph = NetworkGraph::new(genesis_block(Network::Testnet).header.block_hash());
- let scorer = Scorer::new(0);
+ let scorer = Scorer::with_fixed_penalty(0);
let payee = Payee::new(nodes[0]).with_features(InvoiceFeatures::known());
{
let payee = Payee::new(nodes[6]).with_route_hints(last_hops(&nodes));
// Without penalizing each hop 100 msats, a longer path with lower fees is chosen.
- let scorer = Scorer::new(0);
+ let scorer = Scorer::with_fixed_penalty(0);
let route = get_route(
&our_id, &payee, &net_graph_msg_handler.network_graph, None, 100, 42,
Arc::clone(&logger), &scorer
// Applying a 100 msat penalty to each hop results in taking channels 7 and 10 to nodes[6]
// from nodes[2] rather than channel 6, 11, and 8, even though the longer path is cheaper.
- let scorer = Scorer::new(100);
+ let scorer = Scorer::with_fixed_penalty(100);
let route = get_route(
&our_id, &payee, &net_graph_msg_handler.network_graph, None, 100, 42,
Arc::clone(&logger), &scorer
let payee = Payee::new(nodes[6]).with_route_hints(last_hops(&nodes));
// A path to nodes[6] exists when no penalties are applied to any channel.
- let scorer = Scorer::new(0);
+ let scorer = Scorer::with_fixed_penalty(0);
let route = get_route(
&our_id, &payee, &net_graph_msg_handler.network_graph, None, 100, 42,
Arc::clone(&logger), &scorer
},
};
let graph = NetworkGraph::read(&mut d).unwrap();
- let scorer = Scorer::new(0);
+ let scorer = Scorer::with_fixed_penalty(0);
// First, get 100 (source, destination) pairs for which route-getting actually succeeds...
let mut seed = random_init_seed() as usize;
},
};
let graph = NetworkGraph::read(&mut d).unwrap();
- let scorer = Scorer::new(0);
+ let scorer = Scorer::with_fixed_penalty(0);
// First, get 100 (source, destination) pairs for which route-getting actually succeeds...
let mut seed = random_init_seed() as usize;
let mut d = test_utils::get_route_file().unwrap();
let graph = NetworkGraph::read(&mut d).unwrap();
let nodes = graph.read_only().nodes().clone();
- let scorer = Scorer::new(0);
+ let scorer = Scorer::with_fixed_penalty(0);
// First, get 100 (source, destination) pairs for which route-getting actually succeeds...
let mut path_endpoints = Vec::new();
let mut d = test_utils::get_route_file().unwrap();
let graph = NetworkGraph::read(&mut d).unwrap();
let nodes = graph.read_only().nodes().clone();
- let scorer = Scorer::new(0);
+ let scorer = Scorer::with_fixed_penalty(0);
// First, get 100 (source, destination) pairs for which route-getting actually succeeds...
let mut path_endpoints = Vec::new();
//! #
//! # use lightning::routing::network_graph::NetworkGraph;
//! # use lightning::routing::router::{RouteParameters, find_route};
-//! # use lightning::routing::scorer::Scorer;
+//! # use lightning::routing::scorer::{Scorer, ScoringParameters};
//! # use lightning::util::logger::{Logger, Record};
//! # use secp256k1::key::PublicKey;
//! #
//! # fn find_scored_route(payer: PublicKey, params: RouteParameters, network_graph: NetworkGraph) {
//! # let logger = FakeLogger {};
//! #
-//! // Use the default channel penalty.
+//! // Use the default channel penalties.
//! let scorer = Scorer::default();
//!
-//! // Or use a custom channel penalty.
-//! let scorer = Scorer::new(1_000);
+//! // Or use custom channel penalties.
+//! let scorer = Scorer::new(ScoringParameters {
+//! base_penalty_msat: 1000,
+//! failure_penalty_msat: 2 * 1024 * 1000,
+//! ..ScoringParameters::default()
+//! });
//!
//! let route = find_route(&payer, ¶ms, &network_graph, None, &logger, &scorer);
//! # }
use routing::router::RouteHop;
use prelude::*;
+#[cfg(not(feature = "no-std"))]
+use core::time::Duration;
+#[cfg(not(feature = "no-std"))]
+use std::time::Instant;
/// [`routing::Score`] implementation that provides reasonable default behavior.
///
/// Used to apply a fixed penalty to each channel, thus avoiding long paths when shorter paths with
-/// slightly higher fees are available.
+/// slightly higher fees are available. May also further penalize failed channels.
///
/// See [module-level documentation] for usage.
///
/// [module-level documentation]: crate::routing::scorer
pub struct Scorer {
- base_penalty_msat: u64,
+ params: ScoringParameters,
+ #[cfg(not(feature = "no-std"))]
+ channel_failures: HashMap<u64, (u64, Instant)>,
+ #[cfg(feature = "no-std")]
+ channel_failures: HashMap<u64, u64>,
+}
+
+/// Parameters for configuring [`Scorer`].
+pub struct ScoringParameters {
+ /// A fixed penalty in msats to apply to each channel.
+ pub base_penalty_msat: u64,
+
+ /// A penalty in msats to apply to a channel upon failure.
+ ///
+ /// This may be reduced over time based on [`failure_penalty_half_life`].
+ ///
+ /// [`failure_penalty_half_life`]: Self::failure_penalty_half_life
+ pub failure_penalty_msat: u64,
+
+ /// The time needed before any accumulated channel failure penalties are cut in half.
+ #[cfg(not(feature = "no-std"))]
+ pub failure_penalty_half_life: Duration,
}
impl Scorer {
- /// Creates a new scorer using `base_penalty_msat` as the channel penalty.
- pub fn new(base_penalty_msat: u64) -> Self {
- Self { base_penalty_msat }
+ /// Creates a new scorer using the given scoring parameters.
+ pub fn new(params: ScoringParameters) -> Self {
+ Self {
+ params,
+ channel_failures: HashMap::new(),
+ }
+ }
+
+ /// Creates a new scorer using `penalty_msat` as a fixed channel penalty.
+ #[cfg(any(test, feature = "fuzztarget", feature = "_test_utils"))]
+ pub fn with_fixed_penalty(penalty_msat: u64) -> Self {
+ Self::new(ScoringParameters {
+ base_penalty_msat: penalty_msat,
+ failure_penalty_msat: 0,
+ #[cfg(not(feature = "no-std"))]
+ failure_penalty_half_life: Duration::from_secs(0),
+ })
+ }
+
+ #[cfg(not(feature = "no-std"))]
+ fn decay_from(&self, penalty_msat: u64, last_failure: &Instant) -> u64 {
+ decay_from(penalty_msat, last_failure, self.params.failure_penalty_half_life)
}
}
impl Default for Scorer {
- /// Creates a new scorer using 500 msat as the channel penalty.
fn default() -> Self {
- Scorer::new(500)
+ Scorer::new(ScoringParameters::default())
+ }
+}
+
+impl Default for ScoringParameters {
+ fn default() -> Self {
+ Self {
+ base_penalty_msat: 500,
+ failure_penalty_msat: 1024 * 1000,
+ #[cfg(not(feature = "no-std"))]
+ failure_penalty_half_life: Duration::from_secs(3600),
+ }
}
}
impl routing::Score for Scorer {
fn channel_penalty_msat(
- &self, _short_channel_id: u64, _source: &NodeId, _target: &NodeId
+ &self, short_channel_id: u64, _source: &NodeId, _target: &NodeId
) -> u64 {
- self.base_penalty_msat
+ #[cfg(not(feature = "no-std"))]
+ let failure_penalty_msat = match self.channel_failures.get(&short_channel_id) {
+ Some((penalty_msat, last_failure)) => self.decay_from(*penalty_msat, last_failure),
+ None => 0,
+ };
+ #[cfg(feature = "no-std")]
+ let failure_penalty_msat =
+ self.channel_failures.get(&short_channel_id).copied().unwrap_or(0);
+
+ self.params.base_penalty_msat + failure_penalty_msat
}
- fn payment_path_failed(&mut self, _path: &Vec<RouteHop>, _short_channel_id: u64) {}
+ fn payment_path_failed(&mut self, _path: &Vec<RouteHop>, short_channel_id: u64) {
+ let failure_penalty_msat = self.params.failure_penalty_msat;
+ #[cfg(not(feature = "no-std"))]
+ {
+ let half_life = self.params.failure_penalty_half_life;
+ self.channel_failures
+ .entry(short_channel_id)
+ .and_modify(|(penalty_msat, last_failure)| {
+ let decayed_penalty = decay_from(*penalty_msat, last_failure, half_life);
+ *penalty_msat = decayed_penalty + failure_penalty_msat;
+ *last_failure = Instant::now();
+ })
+ .or_insert_with(|| (failure_penalty_msat, Instant::now()));
+ }
+ #[cfg(feature = "no-std")]
+ self.channel_failures
+ .entry(short_channel_id)
+ .and_modify(|penalty_msat| *penalty_msat += failure_penalty_msat)
+ .or_insert(failure_penalty_msat);
+ }
+}
+
+#[cfg(not(feature = "no-std"))]
+fn decay_from(penalty_msat: u64, last_failure: &Instant, half_life: Duration) -> u64 {
+ let decays = last_failure.elapsed().as_secs().checked_div(half_life.as_secs());
+ match decays {
+ Some(decays) => penalty_msat >> decays,
+ None => 0,
+ }
}