mirror of
https://github.com/lightningdevkit/rust-lightning.git
synced 2025-03-15 15:39:09 +01:00
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.
This commit is contained in:
parent
a24626b919
commit
c9f5a75c8e
1 changed files with 161 additions and 2 deletions
|
@ -2037,8 +2037,14 @@ where L::Target: Logger {
|
|||
our_node_pubkey);
|
||||
for details in first_channels {
|
||||
let first_hop_candidate = CandidateRouteHop::FirstHop { details };
|
||||
add_entry!(first_hop_candidate, our_node_id, intro_node_id, 0, path_contribution_msat, 0,
|
||||
0_u64, 0, 0);
|
||||
let blinded_path_fee = match compute_fees(path_contribution_msat, candidate.fees()) {
|
||||
Some(fee) => fee,
|
||||
None => continue
|
||||
};
|
||||
add_entry!(first_hop_candidate, our_node_id, intro_node_id, blinded_path_fee,
|
||||
path_contribution_msat, candidate.htlc_minimum_msat(), 0_u64,
|
||||
candidate.cltv_expiry_delta(),
|
||||
candidate.blinded_path().map_or(1, |bp| bp.blinded_hops.len() as u8));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6711,6 +6717,159 @@ mod tests {
|
|||
}
|
||||
assert_eq!(total_amount_paid_msat, 100_000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn direct_to_intro_node() {
|
||||
// This previously caused a debug panic in the router when asserting
|
||||
// `used_liquidity_msat <= hop_max_msat`, because when adding first_hop<>blinded_route_hint
|
||||
// direct channels we failed to account for the fee charged for use of the blinded path.
|
||||
|
||||
// Build a graph:
|
||||
// node0 -1(1)2 - node1
|
||||
// such that there isn't enough liquidity to reach node1, but the router thinks there is if it
|
||||
// doesn't account for the blinded path fee.
|
||||
|
||||
let secp_ctx = Secp256k1::new();
|
||||
let logger = Arc::new(ln_test_utils::TestLogger::new());
|
||||
let network_graph = Arc::new(NetworkGraph::new(Network::Testnet, Arc::clone(&logger)));
|
||||
let gossip_sync = P2PGossipSync::new(Arc::clone(&network_graph), None, Arc::clone(&logger));
|
||||
let scorer = ln_test_utils::TestScorer::new();
|
||||
let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
|
||||
let random_seed_bytes = keys_manager.get_secure_random_bytes();
|
||||
|
||||
let amt_msat = 10_000_000;
|
||||
let (_, _, privkeys, nodes) = get_nodes(&secp_ctx);
|
||||
add_channel(&gossip_sync, &secp_ctx, &privkeys[0], &privkeys[1],
|
||||
ChannelFeatures::from_le_bytes(id_to_feature_flags(1)), 1);
|
||||
update_channel(&gossip_sync, &secp_ctx, &privkeys[0], UnsignedChannelUpdate {
|
||||
chain_hash: genesis_block(Network::Testnet).header.block_hash(),
|
||||
short_channel_id: 1,
|
||||
timestamp: 1,
|
||||
flags: 0,
|
||||
cltv_expiry_delta: 42,
|
||||
htlc_minimum_msat: 1_000,
|
||||
htlc_maximum_msat: 10_000_000,
|
||||
fee_base_msat: 800,
|
||||
fee_proportional_millionths: 0,
|
||||
excess_data: Vec::new()
|
||||
});
|
||||
update_channel(&gossip_sync, &secp_ctx, &privkeys[1], UnsignedChannelUpdate {
|
||||
chain_hash: genesis_block(Network::Testnet).header.block_hash(),
|
||||
short_channel_id: 1,
|
||||
timestamp: 1,
|
||||
flags: 1,
|
||||
cltv_expiry_delta: 42,
|
||||
htlc_minimum_msat: 1_000,
|
||||
htlc_maximum_msat: 10_000_000,
|
||||
fee_base_msat: 800,
|
||||
fee_proportional_millionths: 0,
|
||||
excess_data: Vec::new()
|
||||
});
|
||||
let first_hops = vec![
|
||||
get_channel_details(Some(1), nodes[1], InitFeatures::from_le_bytes(vec![0b11]), 10_000_000)];
|
||||
|
||||
let blinded_path = BlindedPath {
|
||||
introduction_node_id: nodes[1],
|
||||
blinding_point: ln_test_utils::pubkey(42),
|
||||
blinded_hops: vec![
|
||||
BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() },
|
||||
BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() }
|
||||
],
|
||||
};
|
||||
let blinded_payinfo = BlindedPayInfo {
|
||||
fee_base_msat: 1000,
|
||||
fee_proportional_millionths: 0,
|
||||
htlc_minimum_msat: 1000,
|
||||
htlc_maximum_msat: MAX_VALUE_MSAT,
|
||||
cltv_expiry_delta: 0,
|
||||
features: BlindedHopFeatures::empty(),
|
||||
};
|
||||
let blinded_hints = vec![(blinded_payinfo.clone(), blinded_path)];
|
||||
|
||||
let payment_params = PaymentParameters::blinded(blinded_hints.clone());
|
||||
|
||||
let netgraph = network_graph.read_only();
|
||||
if let Err(LightningError { err, .. }) = get_route(&nodes[0], &payment_params, &netgraph,
|
||||
Some(&first_hops.iter().collect::<Vec<_>>()), amt_msat, Arc::clone(&logger), &scorer, &(),
|
||||
&random_seed_bytes) {
|
||||
assert_eq!(err, "Failed to find a path to the given destination");
|
||||
} else { panic!("Expected error") }
|
||||
|
||||
// Sending an exact amount accounting for the blinded path fee works.
|
||||
let amt_minus_blinded_path_fee = amt_msat - blinded_payinfo.fee_base_msat as u64;
|
||||
let route = get_route(&nodes[0], &payment_params, &netgraph,
|
||||
Some(&first_hops.iter().collect::<Vec<_>>()), amt_minus_blinded_path_fee,
|
||||
Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
|
||||
assert_eq!(route.get_total_fees(), blinded_payinfo.fee_base_msat as u64);
|
||||
assert_eq!(route.get_total_amount(), amt_minus_blinded_path_fee);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn direct_to_matching_intro_nodes() {
|
||||
// This previously caused us to enter `unreachable` code in the following situation:
|
||||
// 1. We add a route candidate for intro_node contributing a high amount
|
||||
// 2. We add a first_hop<>intro_node route candidate for the same high amount
|
||||
// 3. We see a cheaper blinded route hint for the same intro node but a much lower contribution
|
||||
// amount, and update our route candidate for intro_node for the lower amount
|
||||
// 4. We then attempt to update the aforementioned first_hop<>intro_node route candidate for the
|
||||
// lower contribution amount, but fail (this was previously caused by failure to account for
|
||||
// blinded path fees when adding first_hop<>intro_node candidates)
|
||||
// 5. We go to construct the path from these route candidates and our first_hop<>intro_node
|
||||
// candidate still thinks its path is contributing the original higher amount. This caused us
|
||||
// to hit an `unreachable` overflow when calculating the cheaper intro_node fees over the
|
||||
// larger amount
|
||||
let secp_ctx = Secp256k1::new();
|
||||
let logger = Arc::new(ln_test_utils::TestLogger::new());
|
||||
let network_graph = Arc::new(NetworkGraph::new(Network::Testnet, Arc::clone(&logger)));
|
||||
let scorer = ln_test_utils::TestScorer::new();
|
||||
let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
|
||||
let random_seed_bytes = keys_manager.get_secure_random_bytes();
|
||||
let config = UserConfig::default();
|
||||
|
||||
// Values are taken from the fuzz input that uncovered this panic.
|
||||
let amt_msat = 21_7020_5185_1403_2640;
|
||||
let (_, _, _, nodes) = get_nodes(&secp_ctx);
|
||||
let first_hops = vec![
|
||||
get_channel_details(Some(1), nodes[1], channelmanager::provided_init_features(&config),
|
||||
18446744073709551615)];
|
||||
|
||||
let blinded_path = BlindedPath {
|
||||
introduction_node_id: nodes[1],
|
||||
blinding_point: ln_test_utils::pubkey(42),
|
||||
blinded_hops: vec![
|
||||
BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() },
|
||||
BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() }
|
||||
],
|
||||
};
|
||||
let blinded_payinfo = BlindedPayInfo {
|
||||
fee_base_msat: 5046_2720,
|
||||
fee_proportional_millionths: 0,
|
||||
htlc_minimum_msat: 4503_5996_2737_0496,
|
||||
htlc_maximum_msat: 45_0359_9627_3704_9600,
|
||||
cltv_expiry_delta: 0,
|
||||
features: BlindedHopFeatures::empty(),
|
||||
};
|
||||
let mut blinded_hints = vec![
|
||||
(blinded_payinfo.clone(), blinded_path.clone()),
|
||||
(blinded_payinfo.clone(), blinded_path.clone()),
|
||||
];
|
||||
blinded_hints[1].0.fee_base_msat = 419_4304;
|
||||
blinded_hints[1].0.fee_proportional_millionths = 257;
|
||||
blinded_hints[1].0.htlc_minimum_msat = 280_8908_6115_8400;
|
||||
blinded_hints[1].0.htlc_maximum_msat = 2_8089_0861_1584_0000;
|
||||
blinded_hints[1].0.cltv_expiry_delta = 0;
|
||||
|
||||
let bolt12_features: Bolt12InvoiceFeatures = channelmanager::provided_invoice_features(&config).to_context();
|
||||
let payment_params = PaymentParameters::blinded(blinded_hints.clone())
|
||||
.with_bolt12_features(bolt12_features.clone()).unwrap();
|
||||
|
||||
let netgraph = network_graph.read_only();
|
||||
let route = get_route(&nodes[0], &payment_params, &netgraph,
|
||||
Some(&first_hops.iter().collect::<Vec<_>>()), amt_msat,
|
||||
Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
|
||||
assert_eq!(route.get_total_fees(), blinded_payinfo.fee_base_msat as u64);
|
||||
assert_eq!(route.get_total_amount(), amt_msat);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(any(test, ldk_bench), not(feature = "no-std")))]
|
||||
|
|
Loading…
Add table
Reference in a new issue