Skip to content

Commit c9f5a75

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. This also led to us hitting unreachable code, see direct_to_matching_intro_nodes test description.
1 parent a24626b commit c9f5a75

File tree

1 file changed

+161
-2
lines changed

1 file changed

+161
-2
lines changed

lightning/src/routing/router.rs

+161-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(path_contribution_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,159 @@ 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+
}
6806+
6807+
#[test]
6808+
fn direct_to_matching_intro_nodes() {
6809+
// This previously caused us to enter `unreachable` code in the following situation:
6810+
// 1. We add a route candidate for intro_node contributing a high amount
6811+
// 2. We add a first_hop<>intro_node route candidate for the same high amount
6812+
// 3. We see a cheaper blinded route hint for the same intro node but a much lower contribution
6813+
// amount, and update our route candidate for intro_node for the lower amount
6814+
// 4. We then attempt to update the aforementioned first_hop<>intro_node route candidate for the
6815+
// lower contribution amount, but fail (this was previously caused by failure to account for
6816+
// blinded path fees when adding first_hop<>intro_node candidates)
6817+
// 5. We go to construct the path from these route candidates and our first_hop<>intro_node
6818+
// candidate still thinks its path is contributing the original higher amount. This caused us
6819+
// to hit an `unreachable` overflow when calculating the cheaper intro_node fees over the
6820+
// larger amount
6821+
let secp_ctx = Secp256k1::new();
6822+
let logger = Arc::new(ln_test_utils::TestLogger::new());
6823+
let network_graph = Arc::new(NetworkGraph::new(Network::Testnet, Arc::clone(&logger)));
6824+
let scorer = ln_test_utils::TestScorer::new();
6825+
let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
6826+
let random_seed_bytes = keys_manager.get_secure_random_bytes();
6827+
let config = UserConfig::default();
6828+
6829+
// Values are taken from the fuzz input that uncovered this panic.
6830+
let amt_msat = 21_7020_5185_1403_2640;
6831+
let (_, _, _, nodes) = get_nodes(&secp_ctx);
6832+
let first_hops = vec![
6833+
get_channel_details(Some(1), nodes[1], channelmanager::provided_init_features(&config),
6834+
18446744073709551615)];
6835+
6836+
let blinded_path = BlindedPath {
6837+
introduction_node_id: nodes[1],
6838+
blinding_point: ln_test_utils::pubkey(42),
6839+
blinded_hops: vec![
6840+
BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() },
6841+
BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() }
6842+
],
6843+
};
6844+
let blinded_payinfo = BlindedPayInfo {
6845+
fee_base_msat: 5046_2720,
6846+
fee_proportional_millionths: 0,
6847+
htlc_minimum_msat: 4503_5996_2737_0496,
6848+
htlc_maximum_msat: 45_0359_9627_3704_9600,
6849+
cltv_expiry_delta: 0,
6850+
features: BlindedHopFeatures::empty(),
6851+
};
6852+
let mut blinded_hints = vec![
6853+
(blinded_payinfo.clone(), blinded_path.clone()),
6854+
(blinded_payinfo.clone(), blinded_path.clone()),
6855+
];
6856+
blinded_hints[1].0.fee_base_msat = 419_4304;
6857+
blinded_hints[1].0.fee_proportional_millionths = 257;
6858+
blinded_hints[1].0.htlc_minimum_msat = 280_8908_6115_8400;
6859+
blinded_hints[1].0.htlc_maximum_msat = 2_8089_0861_1584_0000;
6860+
blinded_hints[1].0.cltv_expiry_delta = 0;
6861+
6862+
let bolt12_features: Bolt12InvoiceFeatures = channelmanager::provided_invoice_features(&config).to_context();
6863+
let payment_params = PaymentParameters::blinded(blinded_hints.clone())
6864+
.with_bolt12_features(bolt12_features.clone()).unwrap();
6865+
6866+
let netgraph = network_graph.read_only();
6867+
let route = get_route(&nodes[0], &payment_params, &netgraph,
6868+
Some(&first_hops.iter().collect::<Vec<_>>()), amt_msat,
6869+
Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
6870+
assert_eq!(route.get_total_fees(), blinded_payinfo.fee_base_msat as u64);
6871+
assert_eq!(route.get_total_amount(), amt_msat);
6872+
}
67146873
}
67156874

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

0 commit comments

Comments
 (0)