Skip to content

Commit d9eb201

Browse files
authored
Merge pull request #2503 from valentinewallace/2023-08-fix-router-debug-panic
Fix debug panic in the case where a first hop has a channel with an introduction node
2 parents af3a369 + c9f5a75 commit d9eb201

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
}
@@ -6710,6 +6716,159 @@ mod tests {
67106716
}
67116717
assert_eq!(total_amount_paid_msat, 100_000);
67126718
}
6719+
6720+
#[test]
6721+
fn direct_to_intro_node() {
6722+
// This previously caused a debug panic in the router when asserting
6723+
// `used_liquidity_msat <= hop_max_msat`, because when adding first_hop<>blinded_route_hint
6724+
// direct channels we failed to account for the fee charged for use of the blinded path.
6725+
6726+
// Build a graph:
6727+
// node0 -1(1)2 - node1
6728+
// such that there isn't enough liquidity to reach node1, but the router thinks there is if it
6729+
// doesn't account for the blinded path fee.
6730+
6731+
let secp_ctx = Secp256k1::new();
6732+
let logger = Arc::new(ln_test_utils::TestLogger::new());
6733+
let network_graph = Arc::new(NetworkGraph::new(Network::Testnet, Arc::clone(&logger)));
6734+
let gossip_sync = P2PGossipSync::new(Arc::clone(&network_graph), None, Arc::clone(&logger));
6735+
let scorer = ln_test_utils::TestScorer::new();
6736+
let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
6737+
let random_seed_bytes = keys_manager.get_secure_random_bytes();
6738+
6739+
let amt_msat = 10_000_000;
6740+
let (_, _, privkeys, nodes) = get_nodes(&secp_ctx);
6741+
add_channel(&gossip_sync, &secp_ctx, &privkeys[0], &privkeys[1],
6742+
ChannelFeatures::from_le_bytes(id_to_feature_flags(1)), 1);
6743+
update_channel(&gossip_sync, &secp_ctx, &privkeys[0], UnsignedChannelUpdate {
6744+
chain_hash: genesis_block(Network::Testnet).header.block_hash(),
6745+
short_channel_id: 1,
6746+
timestamp: 1,
6747+
flags: 0,
6748+
cltv_expiry_delta: 42,
6749+
htlc_minimum_msat: 1_000,
6750+
htlc_maximum_msat: 10_000_000,
6751+
fee_base_msat: 800,
6752+
fee_proportional_millionths: 0,
6753+
excess_data: Vec::new()
6754+
});
6755+
update_channel(&gossip_sync, &secp_ctx, &privkeys[1], UnsignedChannelUpdate {
6756+
chain_hash: genesis_block(Network::Testnet).header.block_hash(),
6757+
short_channel_id: 1,
6758+
timestamp: 1,
6759+
flags: 1,
6760+
cltv_expiry_delta: 42,
6761+
htlc_minimum_msat: 1_000,
6762+
htlc_maximum_msat: 10_000_000,
6763+
fee_base_msat: 800,
6764+
fee_proportional_millionths: 0,
6765+
excess_data: Vec::new()
6766+
});
6767+
let first_hops = vec![
6768+
get_channel_details(Some(1), nodes[1], InitFeatures::from_le_bytes(vec![0b11]), 10_000_000)];
6769+
6770+
let blinded_path = BlindedPath {
6771+
introduction_node_id: nodes[1],
6772+
blinding_point: ln_test_utils::pubkey(42),
6773+
blinded_hops: vec![
6774+
BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() },
6775+
BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() }
6776+
],
6777+
};
6778+
let blinded_payinfo = BlindedPayInfo {
6779+
fee_base_msat: 1000,
6780+
fee_proportional_millionths: 0,
6781+
htlc_minimum_msat: 1000,
6782+
htlc_maximum_msat: MAX_VALUE_MSAT,
6783+
cltv_expiry_delta: 0,
6784+
features: BlindedHopFeatures::empty(),
6785+
};
6786+
let blinded_hints = vec![(blinded_payinfo.clone(), blinded_path)];
6787+
6788+
let payment_params = PaymentParameters::blinded(blinded_hints.clone());
6789+
6790+
let netgraph = network_graph.read_only();
6791+
if let Err(LightningError { err, .. }) = get_route(&nodes[0], &payment_params, &netgraph,
6792+
Some(&first_hops.iter().collect::<Vec<_>>()), amt_msat, Arc::clone(&logger), &scorer, &(),
6793+
&random_seed_bytes) {
6794+
assert_eq!(err, "Failed to find a path to the given destination");
6795+
} else { panic!("Expected error") }
6796+
6797+
// Sending an exact amount accounting for the blinded path fee works.
6798+
let amt_minus_blinded_path_fee = amt_msat - blinded_payinfo.fee_base_msat as u64;
6799+
let route = get_route(&nodes[0], &payment_params, &netgraph,
6800+
Some(&first_hops.iter().collect::<Vec<_>>()), amt_minus_blinded_path_fee,
6801+
Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
6802+
assert_eq!(route.get_total_fees(), blinded_payinfo.fee_base_msat as u64);
6803+
assert_eq!(route.get_total_amount(), amt_minus_blinded_path_fee);
6804+
}
6805+
6806+
#[test]
6807+
fn direct_to_matching_intro_nodes() {
6808+
// This previously caused us to enter `unreachable` code in the following situation:
6809+
// 1. We add a route candidate for intro_node contributing a high amount
6810+
// 2. We add a first_hop<>intro_node route candidate for the same high amount
6811+
// 3. We see a cheaper blinded route hint for the same intro node but a much lower contribution
6812+
// amount, and update our route candidate for intro_node for the lower amount
6813+
// 4. We then attempt to update the aforementioned first_hop<>intro_node route candidate for the
6814+
// lower contribution amount, but fail (this was previously caused by failure to account for
6815+
// blinded path fees when adding first_hop<>intro_node candidates)
6816+
// 5. We go to construct the path from these route candidates and our first_hop<>intro_node
6817+
// candidate still thinks its path is contributing the original higher amount. This caused us
6818+
// to hit an `unreachable` overflow when calculating the cheaper intro_node fees over the
6819+
// larger amount
6820+
let secp_ctx = Secp256k1::new();
6821+
let logger = Arc::new(ln_test_utils::TestLogger::new());
6822+
let network_graph = Arc::new(NetworkGraph::new(Network::Testnet, Arc::clone(&logger)));
6823+
let scorer = ln_test_utils::TestScorer::new();
6824+
let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
6825+
let random_seed_bytes = keys_manager.get_secure_random_bytes();
6826+
let config = UserConfig::default();
6827+
6828+
// Values are taken from the fuzz input that uncovered this panic.
6829+
let amt_msat = 21_7020_5185_1403_2640;
6830+
let (_, _, _, nodes) = get_nodes(&secp_ctx);
6831+
let first_hops = vec![
6832+
get_channel_details(Some(1), nodes[1], channelmanager::provided_init_features(&config),
6833+
18446744073709551615)];
6834+
6835+
let blinded_path = BlindedPath {
6836+
introduction_node_id: nodes[1],
6837+
blinding_point: ln_test_utils::pubkey(42),
6838+
blinded_hops: vec![
6839+
BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() },
6840+
BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() }
6841+
],
6842+
};
6843+
let blinded_payinfo = BlindedPayInfo {
6844+
fee_base_msat: 5046_2720,
6845+
fee_proportional_millionths: 0,
6846+
htlc_minimum_msat: 4503_5996_2737_0496,
6847+
htlc_maximum_msat: 45_0359_9627_3704_9600,
6848+
cltv_expiry_delta: 0,
6849+
features: BlindedHopFeatures::empty(),
6850+
};
6851+
let mut blinded_hints = vec![
6852+
(blinded_payinfo.clone(), blinded_path.clone()),
6853+
(blinded_payinfo.clone(), blinded_path.clone()),
6854+
];
6855+
blinded_hints[1].0.fee_base_msat = 419_4304;
6856+
blinded_hints[1].0.fee_proportional_millionths = 257;
6857+
blinded_hints[1].0.htlc_minimum_msat = 280_8908_6115_8400;
6858+
blinded_hints[1].0.htlc_maximum_msat = 2_8089_0861_1584_0000;
6859+
blinded_hints[1].0.cltv_expiry_delta = 0;
6860+
6861+
let bolt12_features: Bolt12InvoiceFeatures = channelmanager::provided_invoice_features(&config).to_context();
6862+
let payment_params = PaymentParameters::blinded(blinded_hints.clone())
6863+
.with_bolt12_features(bolt12_features.clone()).unwrap();
6864+
6865+
let netgraph = network_graph.read_only();
6866+
let route = get_route(&nodes[0], &payment_params, &netgraph,
6867+
Some(&first_hops.iter().collect::<Vec<_>>()), amt_msat,
6868+
Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
6869+
assert_eq!(route.get_total_fees(), blinded_payinfo.fee_base_msat as u64);
6870+
assert_eq!(route.get_total_amount(), amt_msat);
6871+
}
67136872
}
67146873

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

0 commit comments

Comments
 (0)