Skip to content

Commit c305cda

Browse files
Router: account for blinded path fee, etc on first_hop<>intro hop add
This previously led to a debug panic in the router because we wouldn't account for the blinded path fee when calculating first_hop<>intro_node hop's available liquidity and construct an invalid path that forwarded more over said hop than was actually available.
1 parent a24626b commit c305cda

File tree

1 file changed

+94
-2
lines changed

1 file changed

+94
-2
lines changed

lightning/src/routing/router.rs

+94-2
Original file line numberDiff line numberDiff line change
@@ -2037,8 +2037,14 @@ where L::Target: Logger {
20372037
our_node_pubkey);
20382038
for details in first_channels {
20392039
let first_hop_candidate = CandidateRouteHop::FirstHop { details };
2040-
add_entry!(first_hop_candidate, our_node_id, intro_node_id, 0, path_contribution_msat, 0,
2041-
0_u64, 0, 0);
2040+
let blinded_path_fee = match compute_fees(final_value_msat, candidate.fees()) {
2041+
Some(fee) => fee,
2042+
None => continue
2043+
};
2044+
add_entry!(first_hop_candidate, our_node_id, intro_node_id, blinded_path_fee,
2045+
path_contribution_msat, candidate.htlc_minimum_msat(), 0_u64,
2046+
candidate.cltv_expiry_delta(),
2047+
candidate.blinded_path().map_or(1, |bp| bp.blinded_hops.len() as u8));
20422048
}
20432049
}
20442050
}
@@ -6711,6 +6717,92 @@ mod tests {
67116717
}
67126718
assert_eq!(total_amount_paid_msat, 100_000);
67136719
}
6720+
6721+
#[test]
6722+
fn direct_to_intro_node() {
6723+
// This previously caused a debug panic in the router when asserting
6724+
// `used_liquidity_msat <= hop_max_msat`, because when adding first_hop<>blinded_route_hint
6725+
// direct channels we failed to account for the fee charged for use of the blinded path.
6726+
6727+
// Build a graph:
6728+
// node0 -1(1)2 - node1
6729+
// such that there isn't enough liquidity to reach node1, but the router thinks there is if it
6730+
// doesn't account for the blinded path fee.
6731+
6732+
let secp_ctx = Secp256k1::new();
6733+
let logger = Arc::new(ln_test_utils::TestLogger::new());
6734+
let network_graph = Arc::new(NetworkGraph::new(Network::Testnet, Arc::clone(&logger)));
6735+
let gossip_sync = P2PGossipSync::new(Arc::clone(&network_graph), None, Arc::clone(&logger));
6736+
let scorer = ln_test_utils::TestScorer::new();
6737+
let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
6738+
let random_seed_bytes = keys_manager.get_secure_random_bytes();
6739+
6740+
let amt_msat = 10_000_000;
6741+
let (_, _, privkeys, nodes) = get_nodes(&secp_ctx);
6742+
add_channel(&gossip_sync, &secp_ctx, &privkeys[0], &privkeys[1],
6743+
ChannelFeatures::from_le_bytes(id_to_feature_flags(1)), 1);
6744+
update_channel(&gossip_sync, &secp_ctx, &privkeys[0], UnsignedChannelUpdate {
6745+
chain_hash: genesis_block(Network::Testnet).header.block_hash(),
6746+
short_channel_id: 1,
6747+
timestamp: 1,
6748+
flags: 0,
6749+
cltv_expiry_delta: 42,
6750+
htlc_minimum_msat: 1_000,
6751+
htlc_maximum_msat: 10_000_000,
6752+
fee_base_msat: 800,
6753+
fee_proportional_millionths: 0,
6754+
excess_data: Vec::new()
6755+
});
6756+
update_channel(&gossip_sync, &secp_ctx, &privkeys[1], UnsignedChannelUpdate {
6757+
chain_hash: genesis_block(Network::Testnet).header.block_hash(),
6758+
short_channel_id: 1,
6759+
timestamp: 1,
6760+
flags: 1,
6761+
cltv_expiry_delta: 42,
6762+
htlc_minimum_msat: 1_000,
6763+
htlc_maximum_msat: 10_000_000,
6764+
fee_base_msat: 800,
6765+
fee_proportional_millionths: 0,
6766+
excess_data: Vec::new()
6767+
});
6768+
let first_hops = vec![
6769+
get_channel_details(Some(1), nodes[1], InitFeatures::from_le_bytes(vec![0b11]), 10_000_000)];
6770+
6771+
let blinded_path = BlindedPath {
6772+
introduction_node_id: nodes[1],
6773+
blinding_point: ln_test_utils::pubkey(42),
6774+
blinded_hops: vec![
6775+
BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() },
6776+
BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() }
6777+
],
6778+
};
6779+
let blinded_payinfo = BlindedPayInfo {
6780+
fee_base_msat: 1000,
6781+
fee_proportional_millionths: 0,
6782+
htlc_minimum_msat: 1000,
6783+
htlc_maximum_msat: MAX_VALUE_MSAT,
6784+
cltv_expiry_delta: 0,
6785+
features: BlindedHopFeatures::empty(),
6786+
};
6787+
let blinded_hints = vec![(blinded_payinfo.clone(), blinded_path)];
6788+
6789+
let payment_params = PaymentParameters::blinded(blinded_hints.clone());
6790+
6791+
let netgraph = network_graph.read_only();
6792+
if let Err(LightningError { err, .. }) = get_route(&nodes[0], &payment_params, &netgraph,
6793+
Some(&first_hops.iter().collect::<Vec<_>>()), amt_msat, Arc::clone(&logger), &scorer, &(),
6794+
&random_seed_bytes) {
6795+
assert_eq!(err, "Failed to find a path to the given destination");
6796+
} else { panic!("Expected error") }
6797+
6798+
// Sending an exact amount accounting for the blinded path fee works.
6799+
let amt_minus_blinded_path_fee = amt_msat - blinded_payinfo.fee_base_msat as u64;
6800+
let route = get_route(&nodes[0], &payment_params, &netgraph,
6801+
Some(&first_hops.iter().collect::<Vec<_>>()), amt_minus_blinded_path_fee,
6802+
Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
6803+
assert_eq!(route.get_total_fees(), blinded_payinfo.fee_base_msat as u64);
6804+
assert_eq!(route.get_total_amount(), amt_minus_blinded_path_fee);
6805+
}
67146806
}
67156807

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

0 commit comments

Comments
 (0)