@@ -2300,12 +2300,19 @@ where L::Target: Logger {
23002300 . chain ( payment_path. hops . iter ( ) . map ( |( hop, _) | & hop. node_id ) ) ;
23012301 for ( prev_hop, ( hop, _) ) in prev_hop_iter. zip ( payment_path. hops . iter ( ) ) {
23022302 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+
23032312 let used_liquidity_msat = used_liquidities
23042313 . entry ( hop. candidate . id ( * prev_hop < hop. node_id ) )
23052314 . and_modify ( |used_liquidity_msat| * used_liquidity_msat += spent_on_hop_msat)
23062315 . 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) ;
23092316 if * used_liquidity_msat == hop_max_msat {
23102317 // If this path used all of this channel's available liquidity, we know
23112318 // this path will not be selected again in the next loop iteration.
@@ -7185,6 +7192,134 @@ mod tests {
71857192 assert_eq ! ( err, "Failed to find a path to the given destination" ) ;
71867193 } else { panic ! ( ) }
71877194 }
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+ }
71887323}
71897324
71907325#[ cfg( all( any( test, ldk_bench) , not( feature = "no-std" ) ) ) ]
0 commit comments