@@ -2300,12 +2300,19 @@ where L::Target: Logger {
2300
2300
. chain ( payment_path. hops . iter ( ) . map ( |( hop, _) | & hop. node_id ) ) ;
2301
2301
for ( prev_hop, ( hop, _) ) in prev_hop_iter. zip ( payment_path. hops . iter ( ) ) {
2302
2302
let spent_on_hop_msat = value_contribution_msat + hop. next_hops_fee_msat ;
2303
+ let hop_max_msat = max_htlc_from_capacity (
2304
+ hop. candidate . effective_capacity ( ) , channel_saturation_pow_half
2305
+ ) ;
2306
+ let used_liquidity_candidate = used_liquidities
2307
+ . get ( & hop. candidate . id ( * prev_hop < hop. node_id ) )
2308
+ . map_or ( spent_on_hop_msat, |used_liquidity_msat|
2309
+ used_liquidity_msat + spent_on_hop_msat) ;
2310
+ if used_liquidity_candidate > hop_max_msat { break ' path_construction }
2311
+
2303
2312
let used_liquidity_msat = used_liquidities
2304
2313
. entry ( hop. candidate . id ( * prev_hop < hop. node_id ) )
2305
2314
. and_modify ( |used_liquidity_msat| * used_liquidity_msat += spent_on_hop_msat)
2306
2315
. or_insert ( spent_on_hop_msat) ;
2307
- let hop_capacity = hop. candidate . effective_capacity ( ) ;
2308
- let hop_max_msat = max_htlc_from_capacity ( hop_capacity, channel_saturation_pow_half) ;
2309
2316
if * used_liquidity_msat == hop_max_msat {
2310
2317
// If this path used all of this channel's available liquidity, we know
2311
2318
// this path will not be selected again in the next loop iteration.
@@ -7185,6 +7192,134 @@ mod tests {
7185
7192
assert_eq ! ( err, "Failed to find a path to the given destination" ) ;
7186
7193
} else { panic ! ( ) }
7187
7194
}
7195
+
7196
+ #[ test]
7197
+ fn min_htlc_overpay_violates_max_htlc ( ) {
7198
+ // Test that if overpaying to meet a later hop's min_htlc and causes us to violate an earlier
7199
+ // hop's max_htlc, we discard that invalid path. Previously we would consider the path to be
7200
+ // valid and hit a debug panic asserting that the used liquidity for a hop was less than its
7201
+ // available liquidity limit.
7202
+ let secp_ctx = Secp256k1 :: new ( ) ;
7203
+ let logger = Arc :: new ( ln_test_utils:: TestLogger :: new ( ) ) ;
7204
+ let network_graph = Arc :: new ( NetworkGraph :: new ( Network :: Testnet , Arc :: clone ( & logger) ) ) ;
7205
+ let scorer = ln_test_utils:: TestScorer :: new ( ) ;
7206
+ let keys_manager = ln_test_utils:: TestKeysInterface :: new ( & [ 0u8 ; 32 ] , Network :: Testnet ) ;
7207
+ let random_seed_bytes = keys_manager. get_secure_random_bytes ( ) ;
7208
+ let config = UserConfig :: default ( ) ;
7209
+
7210
+ // Values are taken from the fuzz input that uncovered this panic.
7211
+ let amt_msat = 7_4009_8048 ;
7212
+ let ( _, our_id, _, nodes) = get_nodes ( & secp_ctx) ;
7213
+ let first_hop_outbound_capacity = 2_7345_2000 ;
7214
+ let first_hops = vec ! [ get_channel_details(
7215
+ Some ( 200 ) , nodes[ 0 ] , channelmanager:: provided_init_features( & config) ,
7216
+ first_hop_outbound_capacity
7217
+ ) ] ;
7218
+
7219
+ let route_hint = RouteHint ( vec ! [ RouteHintHop {
7220
+ src_node_id: nodes[ 0 ] ,
7221
+ short_channel_id: 44 ,
7222
+ fees: RoutingFees {
7223
+ base_msat: 1_6778_3453 ,
7224
+ proportional_millionths: 0 ,
7225
+ } ,
7226
+ cltv_expiry_delta: 10 ,
7227
+ htlc_minimum_msat: Some ( 2_5165_8240 ) ,
7228
+ htlc_maximum_msat: Some ( 251_6582_4000 ) ,
7229
+ } ] ) ;
7230
+
7231
+ let payment_params = PaymentParameters :: from_node_id ( nodes[ 1 ] , 42 )
7232
+ . with_route_hints ( vec ! [ route_hint] ) . unwrap ( )
7233
+ . with_bolt11_features ( channelmanager:: provided_invoice_features ( & config) ) . unwrap ( ) ;
7234
+
7235
+ let netgraph = network_graph. read_only ( ) ;
7236
+ let route_params = RouteParameters :: from_payment_params_and_value (
7237
+ payment_params, amt_msat) ;
7238
+ if let Err ( LightningError { err, .. } ) = get_route (
7239
+ & our_id, & route_params, & netgraph, Some ( & first_hops. iter ( ) . collect :: < Vec < _ > > ( ) ) ,
7240
+ Arc :: clone ( & logger) , & scorer, & ( ) , & random_seed_bytes
7241
+ ) {
7242
+ assert_eq ! ( err, "Failed to find a path to the given destination" ) ;
7243
+ } else { panic ! ( ) }
7244
+ }
7245
+
7246
+ #[ test]
7247
+ fn previously_used_liquidity_violates_max_htlc ( ) {
7248
+ // Test that if a previously found path causes us to violate a newly found path hop's max_htlc,
7249
+ // we will discard that invalid path. Previously we would consider the path to be valid and hit
7250
+ // a debug panic asserting that the used liquidity for a hop was less than its available
7251
+ // liquidity limit.
7252
+ let secp_ctx = Secp256k1 :: new ( ) ;
7253
+ let logger = Arc :: new ( ln_test_utils:: TestLogger :: new ( ) ) ;
7254
+ let network_graph = Arc :: new ( NetworkGraph :: new ( Network :: Testnet , Arc :: clone ( & logger) ) ) ;
7255
+ let scorer = ln_test_utils:: TestScorer :: new ( ) ;
7256
+ let keys_manager = ln_test_utils:: TestKeysInterface :: new ( & [ 0u8 ; 32 ] , Network :: Testnet ) ;
7257
+ let random_seed_bytes = keys_manager. get_secure_random_bytes ( ) ;
7258
+ let config = UserConfig :: default ( ) ;
7259
+
7260
+ // Values are taken from the fuzz input that uncovered this panic.
7261
+ let amt_msat = 52_4288 ;
7262
+ let ( _, our_id, _, nodes) = get_nodes ( & secp_ctx) ;
7263
+ let first_hops = vec ! [ get_channel_details(
7264
+ Some ( 161 ) , nodes[ 0 ] , channelmanager:: provided_init_features( & config) , 486_4000
7265
+ ) , get_channel_details(
7266
+ Some ( 122 ) , nodes[ 0 ] , channelmanager:: provided_init_features( & config) , 179_5000
7267
+ ) ] ;
7268
+
7269
+ let route_hints = vec ! [ RouteHint ( vec![ RouteHintHop {
7270
+ src_node_id: nodes[ 0 ] ,
7271
+ short_channel_id: 42 ,
7272
+ fees: RoutingFees {
7273
+ base_msat: 0 ,
7274
+ proportional_millionths: 0 ,
7275
+ } ,
7276
+ cltv_expiry_delta: 10 ,
7277
+ htlc_minimum_msat: Some ( 1_4392 ) ,
7278
+ htlc_maximum_msat: Some ( 143_9200 ) ,
7279
+ } ] ) , RouteHint ( vec![ RouteHintHop {
7280
+ src_node_id: nodes[ 0 ] ,
7281
+ short_channel_id: 43 ,
7282
+ fees: RoutingFees {
7283
+ base_msat: 425_9840 ,
7284
+ proportional_millionths: 0 ,
7285
+ } ,
7286
+ cltv_expiry_delta: 10 ,
7287
+ htlc_minimum_msat: Some ( 19_7401 ) ,
7288
+ htlc_maximum_msat: Some ( 1974_0100 ) ,
7289
+ } ] ) , RouteHint ( vec![ RouteHintHop {
7290
+ src_node_id: nodes[ 0 ] ,
7291
+ short_channel_id: 44 ,
7292
+ fees: RoutingFees {
7293
+ base_msat: 0 ,
7294
+ proportional_millionths: 0 ,
7295
+ } ,
7296
+ cltv_expiry_delta: 10 ,
7297
+ htlc_minimum_msat: Some ( 1027 ) ,
7298
+ htlc_maximum_msat: Some ( 10_2700 ) ,
7299
+ } ] ) , RouteHint ( vec![ RouteHintHop {
7300
+ src_node_id: nodes[ 0 ] ,
7301
+ short_channel_id: 45 ,
7302
+ fees: RoutingFees {
7303
+ base_msat: 0 ,
7304
+ proportional_millionths: 0 ,
7305
+ } ,
7306
+ cltv_expiry_delta: 10 ,
7307
+ htlc_minimum_msat: Some ( 6_5535 ) ,
7308
+ htlc_maximum_msat: Some ( 655_3500 ) ,
7309
+ } ] ) ] ;
7310
+
7311
+ let payment_params = PaymentParameters :: from_node_id ( nodes[ 1 ] , 42 )
7312
+ . with_route_hints ( route_hints) . unwrap ( )
7313
+ . with_bolt11_features ( channelmanager:: provided_invoice_features ( & config) ) . unwrap ( ) ;
7314
+
7315
+ let netgraph = network_graph. read_only ( ) ;
7316
+ let route_params = RouteParameters :: from_payment_params_and_value (
7317
+ payment_params, amt_msat) ;
7318
+ assert ! ( get_route(
7319
+ & our_id, & route_params, & netgraph, Some ( & first_hops. iter( ) . collect:: <Vec <_>>( ) ) ,
7320
+ Arc :: clone( & logger) , & scorer, & ( ) , & random_seed_bytes
7321
+ ) . is_ok( ) ) ;
7322
+ }
7188
7323
}
7189
7324
7190
7325
#[ cfg( all( any( test, ldk_bench) , not( feature = "no-std" ) ) ) ]
0 commit comments