@@ -1294,6 +1294,16 @@ impl<'a> PaymentPath<'a> {
1294
1294
}
1295
1295
}
1296
1296
}
1297
+ fn violates_max_htlc ( & self ) -> bool {
1298
+ let mut hop_transferred_amount = self . get_value_msat ( ) + self . get_total_fee_paid_msat ( ) ;
1299
+ for ( hop, _) in self . hops . iter ( ) {
1300
+ if hop_transferred_amount > hop. candidate . effective_capacity ( ) . as_msat ( ) {
1301
+ return true
1302
+ }
1303
+ hop_transferred_amount = hop_transferred_amount. saturating_sub ( hop. fee_msat ) ;
1304
+ }
1305
+ false
1306
+ }
1297
1307
}
1298
1308
1299
1309
#[ inline( always) ]
@@ -2285,6 +2295,7 @@ where L::Target: Logger {
2285
2295
debug_assert_eq ! ( payment_path. get_value_msat( ) , value_contribution_msat) ;
2286
2296
value_contribution_msat = cmp:: min ( value_contribution_msat, final_value_msat) ;
2287
2297
payment_path. update_value_and_recompute_fees ( value_contribution_msat) ;
2298
+ if payment_path. violates_max_htlc ( ) { break ' path_construction }
2288
2299
2289
2300
// Since a path allows to transfer as much value as
2290
2301
// the smallest channel it has ("bottleneck"), we should recompute
@@ -7185,6 +7196,56 @@ mod tests {
7185
7196
assert_eq ! ( err, "Failed to find a path to the given destination" ) ;
7186
7197
} else { panic ! ( ) }
7187
7198
}
7199
+
7200
+ #[ test]
7201
+ fn min_htlc_overpay_violates_max_htlc ( ) {
7202
+ // Test that if overpaying to meet a later hop's min_htlc and causes us to violate an earlier
7203
+ // hop's max_htlc, we discard that invalid path. Previously we would consider the path to be
7204
+ // valid and hit a debug panic asserting that the used liquidity for a hop was less than its
7205
+ // available liquidity limit.
7206
+ let secp_ctx = Secp256k1 :: new ( ) ;
7207
+ let logger = Arc :: new ( ln_test_utils:: TestLogger :: new ( ) ) ;
7208
+ let network_graph = Arc :: new ( NetworkGraph :: new ( Network :: Testnet , Arc :: clone ( & logger) ) ) ;
7209
+ let scorer = ln_test_utils:: TestScorer :: new ( ) ;
7210
+ let keys_manager = ln_test_utils:: TestKeysInterface :: new ( & [ 0u8 ; 32 ] , Network :: Testnet ) ;
7211
+ let random_seed_bytes = keys_manager. get_secure_random_bytes ( ) ;
7212
+ let config = UserConfig :: default ( ) ;
7213
+
7214
+ // Values are taken from the fuzz input that uncovered this panic.
7215
+ let amt_msat = 7_4009_8048 ;
7216
+ let ( _, our_id, _, nodes) = get_nodes ( & secp_ctx) ;
7217
+ let first_hop_outbound_capacity = 2_7345_2000 ;
7218
+ let first_hops = vec ! [ get_channel_details(
7219
+ Some ( 200 ) , nodes[ 0 ] , channelmanager:: provided_init_features( & config) ,
7220
+ first_hop_outbound_capacity
7221
+ ) ] ;
7222
+
7223
+ let route_hint = RouteHint ( vec ! [ RouteHintHop {
7224
+ src_node_id: nodes[ 0 ] ,
7225
+ short_channel_id: 44 ,
7226
+ fees: RoutingFees {
7227
+ base_msat: 1_6778_3453 ,
7228
+ proportional_millionths: 0 ,
7229
+ } ,
7230
+ cltv_expiry_delta: 10 ,
7231
+ htlc_minimum_msat: Some ( 2_5165_8240 ) ,
7232
+ htlc_maximum_msat: Some ( 251_6582_4000 ) ,
7233
+ } ] ) ;
7234
+
7235
+ let payment_params = PaymentParameters :: from_node_id ( nodes[ 1 ] , 42 )
7236
+ . with_route_hints ( vec ! [ route_hint] ) . unwrap ( )
7237
+ . with_bolt11_features ( channelmanager:: provided_invoice_features ( & config) ) . unwrap ( ) ;
7238
+
7239
+ let netgraph = network_graph. read_only ( ) ;
7240
+ let route_params = RouteParameters :: from_payment_params_and_value (
7241
+ payment_params, amt_msat) ;
7242
+ if let Err ( LightningError { err, .. } ) = get_route (
7243
+ & our_id, & route_params, & netgraph, Some ( & first_hops. iter ( ) . collect :: < Vec < _ > > ( ) ) ,
7244
+ Arc :: clone ( & logger) , & scorer, & ( ) , & random_seed_bytes
7245
+ ) {
7246
+ assert_eq ! ( err, "Failed to find a path to the given destination" ) ;
7247
+ } else { panic ! ( ) }
7248
+ }
7188
7249
}
7189
7250
7190
7251
#[ cfg( all( any( test, ldk_bench) , not( feature = "no-std" ) ) ) ]
0 commit comments