Penalize failed channels in Scorer

As payments fail, the channel responsible for the failure may be
penalized. Implement Scorer::payment_path_failed to penalize the failed
channel using a configured penalty. As time passes, the penalty is
reduced using exponential decay, though penalties will accumulate if the
channel continues to fail. The decay interval is also configurable.
This commit is contained in:
Jeffrey Czyz 2021-10-27 10:39:22 -05:00
parent 7a8954e1ca
commit c34ab42961
No known key found for this signature in database
GPG Key ID: 3A4E08275D5E96D2
9 changed files with 156 additions and 61 deletions

View File

@ -382,7 +382,7 @@ pub fn do_test(data: &[u8], logger: &Arc<dyn Logger>) {
let our_id = PublicKey::from_secret_key(&Secp256k1::signing_only(), &keys_manager.get_node_secret());
let network_graph = NetworkGraph::new(genesis_block(network).block_hash());
let net_graph_msg_handler = Arc::new(NetGraphMsgHandler::new(network_graph, None, Arc::clone(&logger)));
let scorer = Scorer::new(0);
let scorer = Scorer::with_fixed_penalty(0);
let peers = RefCell::new([false; 256]);
let mut loss_detector = MoneyLossDetector::new(&peers, channelmanager.clone(), monitor.clone(), PeerManager::new(MessageHandler {

View File

@ -248,7 +248,7 @@ pub fn do_test<Out: test_logger::Output>(data: &[u8], out: Out) {
}]));
}
}
let scorer = Scorer::new(0);
let scorer = Scorer::with_fixed_penalty(0);
for target in node_pks.iter() {
let params = RouteParameters {
payee: Payee::new(*target).with_route_hints(last_hops.clone()),

View File

@ -183,7 +183,7 @@ mod test {
let first_hops = nodes[0].node.list_usable_channels();
let network_graph = &nodes[0].net_graph_msg_handler.network_graph;
let logger = test_utils::TestLogger::new();
let scorer = Scorer::new(0);
let scorer = Scorer::with_fixed_penalty(0);
let route = find_route(
&nodes[0].node.get_our_node_id(), &params, network_graph,
Some(&first_hops.iter().collect::<Vec<_>>()), &logger, &scorer,

View File

@ -6279,7 +6279,7 @@ mod tests {
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
create_announced_chan_between_nodes(&nodes, 0, 1, InitFeatures::known(), InitFeatures::known());
let scorer = Scorer::new(0);
let scorer = Scorer::with_fixed_penalty(0);
// To start (1), send a regular payment but don't claim it.
let expected_route = [&nodes[1]];
@ -6384,7 +6384,7 @@ mod tests {
};
let network_graph = &nodes[0].net_graph_msg_handler.network_graph;
let first_hops = nodes[0].node.list_usable_channels();
let scorer = Scorer::new(0);
let scorer = Scorer::with_fixed_penalty(0);
let route = find_route(
&payer_pubkey, &params, network_graph, Some(&first_hops.iter().collect::<Vec<_>>()),
nodes[0].logger, &scorer
@ -6427,7 +6427,7 @@ mod tests {
};
let network_graph = &nodes[0].net_graph_msg_handler.network_graph;
let first_hops = nodes[0].node.list_usable_channels();
let scorer = Scorer::new(0);
let scorer = Scorer::with_fixed_penalty(0);
let route = find_route(
&payer_pubkey, &params, network_graph, Some(&first_hops.iter().collect::<Vec<_>>()),
nodes[0].logger, &scorer
@ -6602,7 +6602,7 @@ pub mod bench {
let usable_channels = $node_a.list_usable_channels();
let payee = Payee::new($node_b.get_our_node_id())
.with_features(InvoiceFeatures::known());
let scorer = Scorer::new(0);
let scorer = Scorer::with_fixed_penalty(0);
let route = get_route(&$node_a.get_our_node_id(), &payee, &dummy_graph,
Some(&usable_channels.iter().map(|r| r).collect::<Vec<_>>()), 10_000, TEST_FINAL_CLTV, &logger_a, &scorer).unwrap();

View File

@ -1015,7 +1015,7 @@ macro_rules! get_route_and_payment_hash {
.with_features($crate::ln::features::InvoiceFeatures::known())
.with_route_hints($last_hops);
let net_graph_msg_handler = &$send_node.net_graph_msg_handler;
let scorer = ::routing::scorer::Scorer::new(0);
let scorer = ::routing::scorer::Scorer::with_fixed_penalty(0);
let route = ::routing::router::get_route(
&$send_node.node.get_our_node_id(), &payee, &net_graph_msg_handler.network_graph,
Some(&$send_node.node.list_usable_channels().iter().collect::<Vec<_>>()),
@ -1339,7 +1339,7 @@ pub fn route_payment<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, expected_route:
let payee = Payee::new(expected_route.last().unwrap().node.get_our_node_id())
.with_features(InvoiceFeatures::known());
let net_graph_msg_handler = &origin_node.net_graph_msg_handler;
let scorer = Scorer::new(0);
let scorer = Scorer::with_fixed_penalty(0);
let route = get_route(
&origin_node.node.get_our_node_id(), &payee, &net_graph_msg_handler.network_graph,
Some(&origin_node.node.list_usable_channels().iter().collect::<Vec<_>>()),
@ -1358,7 +1358,7 @@ pub fn route_over_limit<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, expected_rou
let payee = Payee::new(expected_route.last().unwrap().node.get_our_node_id())
.with_features(InvoiceFeatures::known());
let net_graph_msg_handler = &origin_node.net_graph_msg_handler;
let scorer = Scorer::new(0);
let scorer = Scorer::with_fixed_penalty(0);
let route = get_route(&origin_node.node.get_our_node_id(), &payee, &net_graph_msg_handler.network_graph, None, recv_value, TEST_FINAL_CLTV, origin_node.logger, &scorer).unwrap();
assert_eq!(route.paths.len(), 1);
assert_eq!(route.paths[0].len(), expected_route.len());

View File

@ -7161,7 +7161,7 @@ fn test_check_htlc_underpaying() {
// Create some initial channels
create_announced_chan_between_nodes(&nodes, 0, 1, InitFeatures::known(), InitFeatures::known());
let scorer = Scorer::new(0);
let scorer = Scorer::with_fixed_penalty(0);
let payee = Payee::new(nodes[1].node.get_our_node_id()).with_features(InvoiceFeatures::known());
let route = get_route(&nodes[0].node.get_our_node_id(), &payee, &nodes[0].net_graph_msg_handler.network_graph, None, 10_000, TEST_FINAL_CLTV, nodes[0].logger, &scorer).unwrap();
let (_, our_payment_hash, _) = get_payment_preimage_hash!(nodes[0]);
@ -7561,7 +7561,7 @@ fn test_bump_penalty_txn_on_revoked_htlcs() {
let chan = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1000000, 59000000, InitFeatures::known(), InitFeatures::known());
// Lock HTLC in both directions (using a slightly lower CLTV delay to provide timely RBF bumps)
let payee = Payee::new(nodes[1].node.get_our_node_id()).with_features(InvoiceFeatures::known());
let scorer = Scorer::new(0);
let scorer = Scorer::with_fixed_penalty(0);
let route = get_route(&nodes[0].node.get_our_node_id(), &payee, &nodes[0].net_graph_msg_handler.network_graph,
None, 3_000_000, 50, nodes[0].logger, &scorer).unwrap();
let payment_preimage = send_along_route(&nodes[0], route, &[&nodes[1]], 3_000_000).0;
@ -9061,7 +9061,7 @@ fn test_keysend_payments_to_public_node() {
final_value_msat: 10000,
final_cltv_expiry_delta: 40,
};
let scorer = Scorer::new(0);
let scorer = Scorer::with_fixed_penalty(0);
let route = find_route(&payer_pubkey, &params, &network_graph, None, nodes[0].logger, &scorer).unwrap();
let test_preimage = PaymentPreimage([42; 32]);
@ -9095,7 +9095,7 @@ fn test_keysend_payments_to_private_node() {
};
let network_graph = &nodes[0].net_graph_msg_handler.network_graph;
let first_hops = nodes[0].node.list_usable_channels();
let scorer = Scorer::new(0);
let scorer = Scorer::with_fixed_penalty(0);
let route = find_route(
&payer_pubkey, &params, &network_graph, Some(&first_hops.iter().collect::<Vec<_>>()),
nodes[0].logger, &scorer

View File

@ -82,7 +82,7 @@ fn updates_shutdown_wait() {
let chan_1 = create_announced_chan_between_nodes(&nodes, 0, 1, InitFeatures::known(), InitFeatures::known());
let chan_2 = create_announced_chan_between_nodes(&nodes, 1, 2, InitFeatures::known(), InitFeatures::known());
let logger = test_utils::TestLogger::new();
let scorer = Scorer::new(0);
let scorer = Scorer::with_fixed_penalty(0);
let (our_payment_preimage, our_payment_hash, _) = route_payment(&nodes[0], &[&nodes[1], &nodes[2]], 100000);

View File

@ -1928,7 +1928,7 @@ mod tests {
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
@ -1959,7 +1959,7 @@ mod tests {
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
@ -1978,7 +1978,7 @@ mod tests {
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
@ -2103,7 +2103,7 @@ mod tests {
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.
@ -2239,7 +2239,7 @@ mod tests {
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 {
@ -2297,7 +2297,7 @@ mod tests {
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();
@ -2338,7 +2338,7 @@ mod tests {
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]);
@ -2467,7 +2467,7 @@ mod tests {
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.
@ -2566,7 +2566,7 @@ mod tests {
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.
@ -2648,7 +2648,7 @@ mod tests {
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.
@ -2754,7 +2754,7 @@ mod tests {
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.
@ -2803,7 +2803,7 @@ mod tests {
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)];
@ -2924,7 +2924,7 @@ mod tests {
}]);
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)
}
@ -2978,7 +2978,7 @@ mod tests {
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
@ -3250,7 +3250,7 @@ mod tests {
// 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}.
@ -3373,7 +3373,7 @@ mod tests {
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).
@ -3419,7 +3419,7 @@ mod tests {
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:
@ -3550,7 +3550,7 @@ mod tests {
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:
@ -3712,7 +3712,7 @@ mod tests {
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,
@ -3879,7 +3879,7 @@ mod tests {
// 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:
@ -4048,7 +4048,7 @@ mod tests {
// 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:
@ -4205,7 +4205,7 @@ mod tests {
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);
@ -4334,7 +4334,7 @@ mod tests {
// 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
@ -4396,7 +4396,7 @@ mod tests {
// 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
@ -4463,7 +4463,7 @@ mod tests {
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());
{
@ -4504,7 +4504,7 @@ mod tests {
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
@ -4517,7 +4517,7 @@ mod tests {
// 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
@ -4560,7 +4560,7 @@ mod tests {
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
@ -4689,7 +4689,7 @@ mod tests {
},
};
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;
@ -4720,7 +4720,7 @@ mod tests {
},
};
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;
@ -4786,7 +4786,7 @@ mod benches {
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();
@ -4821,7 +4821,7 @@ mod benches {
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();

View File

@ -19,7 +19,7 @@
//! #
//! # 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;
//! #
@ -30,11 +30,15 @@
//! # 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, &params, &network_graph, None, &logger, &scorer);
//! # }
@ -48,39 +52,130 @@ use routing::network_graph::NodeId;
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,
}
}