Skip to content

Commit 5e742b6

Browse files
get_route: fix path value contribution to include min htlc overpay
Previously, the fuzzer hit a debug panic because we wouldn't include the amount overpaid to meet a last hop's min_htlc in the total collected paths value. We now include this value and also penalize hops along the overpaying path to ensure that it gets deprioritized in path selection.
1 parent d4d6021 commit 5e742b6

File tree

1 file changed

+79
-5
lines changed

1 file changed

+79
-5
lines changed

lightning/src/routing/router.rs

+79-5
Original file line numberDiff line numberDiff line change
@@ -1265,7 +1265,11 @@ impl<'a> PaymentPath<'a> {
12651265
// Note that this function is not aware of the available_liquidity limit, and thus does not
12661266
// support increasing the value being transferred beyond what was selected during the initial
12671267
// routing passes.
1268-
fn update_value_and_recompute_fees(&mut self, value_msat: u64) {
1268+
//
1269+
// Returns the amount that this path contributes to the total payment value, which may be greater
1270+
// than `value_msat` if we had to overpay to meet the final node's `htlc_minimum_msat`.
1271+
fn update_value_and_recompute_fees(&mut self, value_msat: u64) -> u64 {
1272+
let mut extra_contribution_msat = 0;
12691273
let mut total_fee_paid_msat = 0 as u64;
12701274
for i in (0..self.hops.len()).rev() {
12711275
let last_hop = i == self.hops.len() - 1;
@@ -1280,6 +1284,7 @@ impl<'a> PaymentPath<'a> {
12801284

12811285
let cur_hop = &mut self.hops.get_mut(i).unwrap().0;
12821286
cur_hop.next_hops_fee_msat = total_fee_paid_msat;
1287+
cur_hop.path_penalty_msat += extra_contribution_msat;
12831288
// Overpay in fees if we can't save these funds due to htlc_minimum_msat.
12841289
// We try to account for htlc_minimum_msat in scoring (add_entry!), so that nodes don't
12851290
// set it too high just to maliciously take more fees by exploiting this
@@ -1295,8 +1300,15 @@ impl<'a> PaymentPath<'a> {
12951300
// Also, this can't be exploited more heavily than *announce a free path and fail
12961301
// all payments*.
12971302
cur_hop_transferred_amount_msat += extra_fees_msat;
1298-
total_fee_paid_msat += extra_fees_msat;
1299-
cur_hop_fees_msat += extra_fees_msat;
1303+
1304+
// We remember and return the extra fees on the final hop to allow accounting for
1305+
// them in the path's value contribution.
1306+
if last_hop {
1307+
extra_contribution_msat = extra_fees_msat;
1308+
} else {
1309+
total_fee_paid_msat += extra_fees_msat;
1310+
cur_hop_fees_msat += extra_fees_msat;
1311+
}
13001312
}
13011313

13021314
if last_hop {
@@ -1324,6 +1336,7 @@ impl<'a> PaymentPath<'a> {
13241336
}
13251337
}
13261338
}
1339+
value_msat + extra_contribution_msat
13271340
}
13281341
}
13291342

@@ -2330,8 +2343,8 @@ where L::Target: Logger {
23302343
// recompute the fees again, so that if that's the case, we match the currently
23312344
// underpaid htlc_minimum_msat with fees.
23322345
debug_assert_eq!(payment_path.get_value_msat(), value_contribution_msat);
2333-
value_contribution_msat = cmp::min(value_contribution_msat, final_value_msat);
2334-
payment_path.update_value_and_recompute_fees(value_contribution_msat);
2346+
let desired_value_contribution = cmp::min(value_contribution_msat, final_value_msat);
2347+
value_contribution_msat = payment_path.update_value_and_recompute_fees(desired_value_contribution);
23352348

23362349
// Since a path allows to transfer as much value as
23372350
// the smallest channel it has ("bottleneck"), we should recompute
@@ -7522,6 +7535,67 @@ mod tests {
75227535
assert_eq!(err, "Failed to find a path to the given destination");
75237536
} else { panic!() }
75247537
}
7538+
7539+
#[test]
7540+
fn path_contribution_includes_min_htlc_overpay() {
7541+
// Previously, the fuzzer hit a debug panic because we wouldn't include the amount overpaid to
7542+
// meet a last hop's min_htlc in the total collected paths value. We now include this value and
7543+
// also penalize hops along the overpaying path to ensure that it gets deprioritized in path
7544+
// selection, both tested here.
7545+
let secp_ctx = Secp256k1::new();
7546+
let logger = Arc::new(ln_test_utils::TestLogger::new());
7547+
let network_graph = Arc::new(NetworkGraph::new(Network::Testnet, Arc::clone(&logger)));
7548+
let scorer = ProbabilisticScorer::new(ProbabilisticScoringDecayParameters::default(), network_graph.clone(), logger.clone());
7549+
let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
7550+
let random_seed_bytes = keys_manager.get_secure_random_bytes();
7551+
let config = UserConfig::default();
7552+
7553+
// Values are taken from the fuzz input that uncovered this panic.
7554+
let amt_msat = 562_0000;
7555+
let (_, our_id, _, nodes) = get_nodes(&secp_ctx);
7556+
let first_hops = vec![
7557+
get_channel_details(
7558+
Some(83), nodes[0], channelmanager::provided_init_features(&config), 2199_0000,
7559+
),
7560+
];
7561+
7562+
let htlc_mins = [49_0000, 1125_0000];
7563+
let payment_params = {
7564+
let blinded_path = BlindedPath {
7565+
introduction_node_id: nodes[0],
7566+
blinding_point: ln_test_utils::pubkey(42),
7567+
blinded_hops: vec![
7568+
BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() },
7569+
BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() }
7570+
],
7571+
};
7572+
let mut blinded_hints = Vec::new();
7573+
for htlc_min in htlc_mins {
7574+
blinded_hints.push((BlindedPayInfo {
7575+
fee_base_msat: 0,
7576+
fee_proportional_millionths: 0,
7577+
htlc_minimum_msat: htlc_min,
7578+
htlc_maximum_msat: htlc_min * 100,
7579+
cltv_expiry_delta: 10,
7580+
features: BlindedHopFeatures::empty(),
7581+
}, blinded_path.clone()));
7582+
}
7583+
let bolt12_features: Bolt12InvoiceFeatures = channelmanager::provided_invoice_features(&config).to_context();
7584+
PaymentParameters::blinded(blinded_hints.clone())
7585+
.with_bolt12_features(bolt12_features.clone()).unwrap()
7586+
};
7587+
7588+
let netgraph = network_graph.read_only();
7589+
let route_params = RouteParameters::from_payment_params_and_value(
7590+
payment_params, amt_msat);
7591+
let route = get_route(
7592+
&our_id, &route_params, &netgraph, Some(&first_hops.iter().collect::<Vec<_>>()),
7593+
Arc::clone(&logger), &scorer, &ProbabilisticScoringFeeParameters::default(),
7594+
&random_seed_bytes
7595+
).unwrap();
7596+
assert_eq!(route.paths.len(), 1);
7597+
assert_eq!(route.get_total_amount(), amt_msat);
7598+
}
75257599
}
75267600

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

0 commit comments

Comments
 (0)