Penalize failed channels in Scorer
authorJeffrey Czyz <jkczyz@gmail.com>
Wed, 27 Oct 2021 15:39:22 +0000 (10:39 -0500)
committerJeffrey Czyz <jkczyz@gmail.com>
Fri, 29 Oct 2021 19:26:58 +0000 (14:26 -0500)
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.

fuzz/src/full_stack.rs
fuzz/src/router.rs
lightning-invoice/src/utils.rs
lightning/src/ln/channelmanager.rs
lightning/src/ln/functional_test_utils.rs
lightning/src/ln/functional_tests.rs
lightning/src/ln/shutdown_tests.rs
lightning/src/routing/router.rs
lightning/src/routing/scorer.rs

index b01506871ec10ab9677590bdc22cd925596c067c..2e447dac6da1f2de57d5ea7a5ff602414b23830a 100644 (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 {
index 7f7d9585cc279df031ad14304272fae6782fdfca..abd83fa58c669265d5096a9e6ac526fc1316324d 100644 (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()),
index e47141161b533fb3008ddd10c2a18e8bfb139f23..8da9994a3f7729ea1df9c407dd567fc811782d6a 100644 (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,
index 90f1a2a91b263ab364d3c3aa04dafffdf4d7c689..e6c27efc8eccdb332c98b17e314284b5ceb3c4ff 100644 (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();
 
index 014cf3b7ddb777c22ce125039252ceea890f4ee8..03fa562555e362f168f8aa82044bd32c88544d31 100644 (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());
index 564a9e5a09035f0bc9554e2d2f91d93f05650436..76190b671ff093a04c40c95898e4c5682f35910b 100644 (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
index 388104bf0239a4252f7338bab4b900107ba59587..06833ef45096bb037fcc1ba6a6ad791b650aa5f4 100644 (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);
 
index 226dbe04ff9e20e1b89aefc0957032bdd18978d5..93128c02401c91cee866dbab3fe332d11f739847 100644 (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();
index 01481f16c297f9218aa05082b3fbdc7f22e886ba..d2b167675e083748c813a65928a3b71859292b4b 100644 (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;
 //! #
 //! # 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,
+       }
 }