@@ -1265,7 +1265,11 @@ impl<'a> PaymentPath<'a> {
1265
1265
// Note that this function is not aware of the available_liquidity limit, and thus does not
1266
1266
// support increasing the value being transferred beyond what was selected during the initial
1267
1267
// 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 ;
1269
1273
let mut total_fee_paid_msat = 0 as u64 ;
1270
1274
for i in ( 0 ..self . hops . len ( ) ) . rev ( ) {
1271
1275
let last_hop = i == self . hops . len ( ) - 1 ;
@@ -1280,6 +1284,7 @@ impl<'a> PaymentPath<'a> {
1280
1284
1281
1285
let cur_hop = & mut self . hops . get_mut ( i) . unwrap ( ) . 0 ;
1282
1286
cur_hop. next_hops_fee_msat = total_fee_paid_msat;
1287
+ cur_hop. path_penalty_msat += extra_contribution_msat;
1283
1288
// Overpay in fees if we can't save these funds due to htlc_minimum_msat.
1284
1289
// We try to account for htlc_minimum_msat in scoring (add_entry!), so that nodes don't
1285
1290
// set it too high just to maliciously take more fees by exploiting this
@@ -1295,8 +1300,15 @@ impl<'a> PaymentPath<'a> {
1295
1300
// Also, this can't be exploited more heavily than *announce a free path and fail
1296
1301
// all payments*.
1297
1302
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
+ }
1300
1312
}
1301
1313
1302
1314
if last_hop {
@@ -1324,6 +1336,7 @@ impl<'a> PaymentPath<'a> {
1324
1336
}
1325
1337
}
1326
1338
}
1339
+ value_msat + extra_contribution_msat
1327
1340
}
1328
1341
}
1329
1342
@@ -2330,8 +2343,8 @@ where L::Target: Logger {
2330
2343
// recompute the fees again, so that if that's the case, we match the currently
2331
2344
// underpaid htlc_minimum_msat with fees.
2332
2345
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 ) ;
2335
2348
2336
2349
// Since a path allows to transfer as much value as
2337
2350
// the smallest channel it has ("bottleneck"), we should recompute
@@ -7522,6 +7535,67 @@ mod tests {
7522
7535
assert_eq ! ( err, "Failed to find a path to the given destination" ) ;
7523
7536
} else { panic ! ( ) }
7524
7537
}
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
+ }
7525
7599
}
7526
7600
7527
7601
#[ cfg( all( any( test, ldk_bench) , not( feature = "no-std" ) ) ) ]
0 commit comments