Skip to content

Commit 31b032f

Browse files
get_route: fix path_min for first_hop<>network_node candidates
Previously, we would add a first_hop<>network_node channel that did not have enough contribution amount to cover the next channel's min htlc plus fees, because we were storing the next hop as having a path_min that did not include fees, and would add a connecting first_hop node that did not have enough contribution amount, leading to a debug panic upon invalid path construction.
1 parent 65cf4f8 commit 31b032f

File tree

1 file changed

+77
-4
lines changed

1 file changed

+77
-4
lines changed

lightning/src/routing/router.rs

Lines changed: 77 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1787,10 +1787,11 @@ where L::Target: Logger {
17871787
// might violate htlc_minimum_msat on the hops which are next along the
17881788
// payment path (upstream to the payee). To avoid that, we recompute
17891789
// path fees knowing the final path contribution after constructing it.
1790-
let path_htlc_minimum_msat = cmp::max(
1791-
compute_fees_saturating($next_hops_path_htlc_minimum_msat, $candidate.fees())
1792-
.saturating_add($next_hops_path_htlc_minimum_msat),
1793-
$candidate.htlc_minimum_msat());
1790+
let curr_min = cmp::max(
1791+
$next_hops_path_htlc_minimum_msat, $candidate.htlc_minimum_msat()
1792+
);
1793+
let path_htlc_minimum_msat = compute_fees_saturating(curr_min, $candidate.fees())
1794+
.saturating_add(curr_min);
17941795
let hm_entry = dist.entry($src_node_id);
17951796
let old_entry = hm_entry.or_insert_with(|| {
17961797
// If there was previously no known way to access the source node
@@ -7354,6 +7355,78 @@ mod tests {
73547355
assert_eq!(route.paths.len(), 1);
73557356
assert_eq!(route.get_total_amount(), amt_msat);
73567357
}
7358+
7359+
#[test]
7360+
fn candidate_path_min() {
7361+
// Test that if a candidate first_hop<>network_node channel does not have enough contribution
7362+
// amount to cover the next channel's min htlc plus fees, we will not consider that candidate.
7363+
// Previously, we were storing RouteGraphNodes with a path_min that did not include fees, and
7364+
// would add a connecting first_hop node that did not have enough contribution amount, leading
7365+
// to a debug panic upon invalid path construction.
7366+
let secp_ctx = Secp256k1::new();
7367+
let logger = Arc::new(ln_test_utils::TestLogger::new());
7368+
let network_graph = Arc::new(NetworkGraph::new(Network::Testnet, Arc::clone(&logger)));
7369+
let gossip_sync = P2PGossipSync::new(network_graph.clone(), None, logger.clone());
7370+
let scorer = ProbabilisticScorer::new(ProbabilisticScoringDecayParameters::default(), network_graph.clone(), logger.clone());
7371+
let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
7372+
let random_seed_bytes = keys_manager.get_secure_random_bytes();
7373+
let config = UserConfig::default();
7374+
7375+
// Values are taken from the fuzz input that uncovered this panic.
7376+
let amt_msat = 7_4009_8048;
7377+
let (_, our_id, privkeys, nodes) = get_nodes(&secp_ctx);
7378+
let first_hops = vec![get_channel_details(
7379+
Some(200), nodes[0], channelmanager::provided_init_features(&config), 2_7345_2000
7380+
)];
7381+
7382+
add_channel(&gossip_sync, &secp_ctx, &privkeys[0], &privkeys[6], ChannelFeatures::from_le_bytes(id_to_feature_flags(6)), 6);
7383+
update_channel(&gossip_sync, &secp_ctx, &privkeys[0], UnsignedChannelUpdate {
7384+
chain_hash: genesis_block(Network::Testnet).header.block_hash(),
7385+
short_channel_id: 6,
7386+
timestamp: 1,
7387+
flags: 0,
7388+
cltv_expiry_delta: (6 << 4) | 0,
7389+
htlc_minimum_msat: 0,
7390+
htlc_maximum_msat: MAX_VALUE_MSAT,
7391+
fee_base_msat: 0,
7392+
fee_proportional_millionths: 0,
7393+
excess_data: Vec::new()
7394+
});
7395+
add_or_update_node(&gossip_sync, &secp_ctx, &privkeys[0], NodeFeatures::from_le_bytes(id_to_feature_flags(1)), 0);
7396+
7397+
let htlc_min = 2_5165_8240;
7398+
let blinded_hints = vec![
7399+
(BlindedPayInfo {
7400+
fee_base_msat: 1_6778_3453,
7401+
fee_proportional_millionths: 0,
7402+
htlc_minimum_msat: htlc_min,
7403+
htlc_maximum_msat: htlc_min * 100,
7404+
cltv_expiry_delta: 10,
7405+
features: BlindedHopFeatures::empty(),
7406+
}, BlindedPath {
7407+
introduction_node_id: nodes[0],
7408+
blinding_point: ln_test_utils::pubkey(42),
7409+
blinded_hops: vec![
7410+
BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() },
7411+
BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() }
7412+
],
7413+
})
7414+
];
7415+
let bolt12_features: Bolt12InvoiceFeatures = channelmanager::provided_invoice_features(&config).to_context();
7416+
let payment_params = PaymentParameters::blinded(blinded_hints.clone())
7417+
.with_bolt12_features(bolt12_features.clone()).unwrap();
7418+
let route_params = RouteParameters::from_payment_params_and_value(
7419+
payment_params, amt_msat);
7420+
let netgraph = network_graph.read_only();
7421+
7422+
if let Err(LightningError { err, .. }) = get_route(
7423+
&our_id, &route_params, &netgraph, Some(&first_hops.iter().collect::<Vec<_>>()),
7424+
Arc::clone(&logger), &scorer, &ProbabilisticScoringFeeParameters::default(),
7425+
&random_seed_bytes
7426+
) {
7427+
assert_eq!(err, "Failed to find a path to the given destination");
7428+
} else { panic!() }
7429+
}
73577430
}
73587431

73597432
#[cfg(all(any(test, ldk_bench), not(feature = "no-std")))]

0 commit comments

Comments
 (0)