@@ -2204,11 +2204,15 @@ where L::Target: Logger {
2204
2204
. map_or ( None , |inc| inc. checked_add ( aggregate_next_hops_fee_msat) ) ;
2205
2205
aggregate_next_hops_fee_msat = if let Some ( val) = hops_fee { val } else { break ; } ;
2206
2206
2207
- let hop_htlc_minimum_msat = candidate. htlc_minimum_msat ( ) ;
2208
- let hop_htlc_minimum_msat_inc = if let Some ( val) = compute_fees ( aggregate_next_hops_path_htlc_minimum_msat, hop. fees ) { val } else { break ; } ;
2209
- let hops_path_htlc_minimum = aggregate_next_hops_path_htlc_minimum_msat
2210
- . checked_add ( hop_htlc_minimum_msat_inc) ;
2211
- aggregate_next_hops_path_htlc_minimum_msat = if let Some ( val) = hops_path_htlc_minimum { cmp:: max ( hop_htlc_minimum_msat, val) } else { break ; } ;
2207
+ // The next channel will need to relay this channel's min_htlc *plus* the fees taken by
2208
+ // this route hint's source node to forward said min over this channel.
2209
+ aggregate_next_hops_path_htlc_minimum_msat = {
2210
+ let curr_htlc_min = cmp:: max (
2211
+ candidate. htlc_minimum_msat ( ) , aggregate_next_hops_path_htlc_minimum_msat
2212
+ ) ;
2213
+ let curr_htlc_min_fee = if let Some ( val) = compute_fees ( curr_htlc_min, hop. fees ) { val } else { break } ;
2214
+ if let Some ( min) = curr_htlc_min. checked_add ( curr_htlc_min_fee) { min } else { break }
2215
+ } ;
2212
2216
2213
2217
if idx == route. 0 . len ( ) - 1 {
2214
2218
// The last hop in this iterator is the first hop in
@@ -7279,6 +7283,118 @@ mod tests {
7279
7283
assert_eq ! ( err, "Failed to find a path to the given destination" ) ;
7280
7284
} else { panic ! ( ) }
7281
7285
}
7286
+
7287
+ #[ test]
7288
+ fn min_htlc_overpay_violates_max_htlc ( ) {
7289
+ // Test that if overpaying to meet a later hop's min_htlc and causes us to violate an earlier
7290
+ // hop's max_htlc, we don't consider that candidate hop valid. Previously we would add this hop
7291
+ // to `targets` and build an invalid path with it, and subsquently hit a debug panic asserting
7292
+ // that the used liquidity for a hop was less than its available liquidity limit.
7293
+ let secp_ctx = Secp256k1 :: new ( ) ;
7294
+ let logger = Arc :: new ( ln_test_utils:: TestLogger :: new ( ) ) ;
7295
+ let network_graph = Arc :: new ( NetworkGraph :: new ( Network :: Testnet , Arc :: clone ( & logger) ) ) ;
7296
+ let scorer = ln_test_utils:: TestScorer :: new ( ) ;
7297
+ let keys_manager = ln_test_utils:: TestKeysInterface :: new ( & [ 0u8 ; 32 ] , Network :: Testnet ) ;
7298
+ let random_seed_bytes = keys_manager. get_secure_random_bytes ( ) ;
7299
+ let config = UserConfig :: default ( ) ;
7300
+
7301
+ // Values are taken from the fuzz input that uncovered this panic.
7302
+ let amt_msat = 7_4009_8048 ;
7303
+ let ( _, our_id, _, nodes) = get_nodes ( & secp_ctx) ;
7304
+ let first_hop_outbound_capacity = 2_7345_2000 ;
7305
+ let first_hops = vec ! [ get_channel_details(
7306
+ Some ( 200 ) , nodes[ 0 ] , channelmanager:: provided_init_features( & config) ,
7307
+ first_hop_outbound_capacity
7308
+ ) ] ;
7309
+
7310
+ let base_fee = 1_6778_3453 ;
7311
+ let htlc_min = 2_5165_8240 ;
7312
+ let payment_params = {
7313
+ let route_hint = RouteHint ( vec ! [ RouteHintHop {
7314
+ src_node_id: nodes[ 0 ] ,
7315
+ short_channel_id: 42 ,
7316
+ fees: RoutingFees {
7317
+ base_msat: base_fee,
7318
+ proportional_millionths: 0 ,
7319
+ } ,
7320
+ cltv_expiry_delta: 10 ,
7321
+ htlc_minimum_msat: Some ( htlc_min) ,
7322
+ htlc_maximum_msat: None ,
7323
+ } ] ) ;
7324
+
7325
+ PaymentParameters :: from_node_id ( nodes[ 1 ] , 42 )
7326
+ . with_route_hints ( vec ! [ route_hint] ) . unwrap ( )
7327
+ . with_bolt11_features ( channelmanager:: provided_invoice_features ( & config) ) . unwrap ( )
7328
+ } ;
7329
+
7330
+ let netgraph = network_graph. read_only ( ) ;
7331
+ let route_params = RouteParameters :: from_payment_params_and_value (
7332
+ payment_params, amt_msat) ;
7333
+ if let Err ( LightningError { err, .. } ) = get_route (
7334
+ & our_id, & route_params, & netgraph, Some ( & first_hops. iter ( ) . collect :: < Vec < _ > > ( ) ) ,
7335
+ Arc :: clone ( & logger) , & scorer, & ( ) , & random_seed_bytes
7336
+ ) {
7337
+ assert_eq ! ( err, "Failed to find a path to the given destination" ) ;
7338
+ } else { panic ! ( ) }
7339
+ }
7340
+
7341
+ #[ test]
7342
+ fn previously_used_liquidity_violates_max_htlc ( ) {
7343
+ // Test that if a candidate first_hop<>route_hint_src_node channel does not have enough
7344
+ // contribution amount to cover the next hop's min_htlc plus fees, we will not consider that
7345
+ // candidate. In this case, the candidate does not have enough due to a previous path taking up
7346
+ // some of its liquidity. Previously we would construct an invalid path and hit a debug panic
7347
+ // asserting that the used liquidity for a hop was less than its available liquidity limit.
7348
+ let secp_ctx = Secp256k1 :: new ( ) ;
7349
+ let logger = Arc :: new ( ln_test_utils:: TestLogger :: new ( ) ) ;
7350
+ let network_graph = Arc :: new ( NetworkGraph :: new ( Network :: Testnet , Arc :: clone ( & logger) ) ) ;
7351
+ let scorer = ln_test_utils:: TestScorer :: new ( ) ;
7352
+ let keys_manager = ln_test_utils:: TestKeysInterface :: new ( & [ 0u8 ; 32 ] , Network :: Testnet ) ;
7353
+ let random_seed_bytes = keys_manager. get_secure_random_bytes ( ) ;
7354
+ let config = UserConfig :: default ( ) ;
7355
+
7356
+ // Values are taken from the fuzz input that uncovered this panic.
7357
+ let amt_msat = 52_4288 ;
7358
+ let ( _, our_id, _, nodes) = get_nodes ( & secp_ctx) ;
7359
+ let first_hops = vec ! [ get_channel_details(
7360
+ Some ( 161 ) , nodes[ 0 ] , channelmanager:: provided_init_features( & config) , 486_4000
7361
+ ) , get_channel_details(
7362
+ Some ( 122 ) , nodes[ 0 ] , channelmanager:: provided_init_features( & config) , 179_5000
7363
+ ) ] ;
7364
+
7365
+ let base_fees = [ 0 , 425_9840 , 0 , 0 ] ;
7366
+ let htlc_mins = [ 1_4392 , 19_7401 , 1027 , 6_5535 ] ;
7367
+ let payment_params = {
7368
+ let mut route_hints = Vec :: new ( ) ;
7369
+ for ( idx, ( base_fee, htlc_min) ) in base_fees. iter ( ) . zip ( htlc_mins. iter ( ) ) . enumerate ( ) {
7370
+ route_hints. push ( RouteHint ( vec ! [ RouteHintHop {
7371
+ src_node_id: nodes[ 0 ] ,
7372
+ short_channel_id: 42 + idx as u64 ,
7373
+ fees: RoutingFees {
7374
+ base_msat: * base_fee,
7375
+ proportional_millionths: 0 ,
7376
+ } ,
7377
+ cltv_expiry_delta: 10 ,
7378
+ htlc_minimum_msat: Some ( * htlc_min) ,
7379
+ htlc_maximum_msat: Some ( htlc_min * 100 ) ,
7380
+ } ] ) ) ;
7381
+ }
7382
+ PaymentParameters :: from_node_id ( nodes[ 1 ] , 42 )
7383
+ . with_route_hints ( route_hints) . unwrap ( )
7384
+ . with_bolt11_features ( channelmanager:: provided_invoice_features ( & config) ) . unwrap ( )
7385
+ } ;
7386
+
7387
+ let netgraph = network_graph. read_only ( ) ;
7388
+ let route_params = RouteParameters :: from_payment_params_and_value (
7389
+ payment_params, amt_msat) ;
7390
+
7391
+ let route = get_route (
7392
+ & our_id, & route_params, & netgraph, Some ( & first_hops. iter ( ) . collect :: < Vec < _ > > ( ) ) ,
7393
+ Arc :: clone ( & logger) , & scorer, & ( ) , & random_seed_bytes
7394
+ ) . unwrap ( ) ;
7395
+ assert_eq ! ( route. paths. len( ) , 1 ) ;
7396
+ assert_eq ! ( route. get_total_amount( ) , amt_msat) ;
7397
+ }
7282
7398
}
7283
7399
7284
7400
#[ cfg( all( any( test, ldk_bench) , not( feature = "no-std" ) ) ) ]
0 commit comments