@@ -1294,6 +1294,16 @@ impl<'a> PaymentPath<'a> {
12941294			} 
12951295		} 
12961296	} 
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+ 	} 
12971307} 
12981308
12991309#[ inline( always) ]  
@@ -2285,6 +2295,7 @@ where L::Target: Logger {
22852295				debug_assert_eq ! ( payment_path. get_value_msat( ) ,  value_contribution_msat) ; 
22862296				value_contribution_msat = cmp:: min ( value_contribution_msat,  final_value_msat) ; 
22872297				payment_path. update_value_and_recompute_fees ( value_contribution_msat) ; 
2298+ 				if  payment_path. violates_max_htlc ( )  {  break  ' path_construction } 
22882299
22892300				// Since a path allows to transfer as much value as 
22902301				// the smallest channel it has ("bottleneck"), we should recompute 
@@ -7185,6 +7196,56 @@ mod tests {
71857196			assert_eq ! ( err,  "Failed to find a path to the given destination" ) ; 
71867197		}  else  {  panic ! ( )  } 
71877198	} 
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+ 	} 
71887249} 
71897250
71907251#[ cfg( all( any( test,  ldk_bench) ,  not( feature = "no-std" ) ) ) ]  
0 commit comments