@@ -2163,11 +2163,13 @@ where L::Target: Logger {
21632163						. map_or ( None ,  |inc| inc. checked_add ( aggregate_next_hops_fee_msat) ) ; 
21642164					aggregate_next_hops_fee_msat = if  let  Some ( val)  = hops_fee {  val }  else  {  break ;  } ; 
21652165
2166- 					let  hop_htlc_minimum_msat = candidate. htlc_minimum_msat ( ) ; 
2167- 					let  hop_htlc_minimum_msat_inc = if  let  Some ( val)  = compute_fees ( aggregate_next_hops_path_htlc_minimum_msat,  hop. fees )  {  val }  else  {  break ;  } ; 
2168- 					let  hops_path_htlc_minimum = aggregate_next_hops_path_htlc_minimum_msat
2169- 						. checked_add ( hop_htlc_minimum_msat_inc) ; 
2170- 					aggregate_next_hops_path_htlc_minimum_msat = if  let  Some ( val)  = hops_path_htlc_minimum {  cmp:: max ( hop_htlc_minimum_msat,  val)  }  else  {  break ;  } ; 
2166+ 					aggregate_next_hops_path_htlc_minimum_msat = { 
2167+ 						let  curr_htlc_min = cmp:: max ( 
2168+ 							candidate. htlc_minimum_msat ( ) ,  aggregate_next_hops_path_htlc_minimum_msat
2169+ 						) ; 
2170+ 						let  curr_htlc_min_fee = if  let  Some ( val)  = compute_fees ( curr_htlc_min,  hop. fees )  {  val }  else  {  break  } ; 
2171+ 						if  let  Some ( min)  = curr_htlc_min. checked_add ( curr_htlc_min_fee)  {  min }  else  {  break  } 
2172+ 					} ; 
21712173
21722174					if  idx == route. 0 . len ( )  - 1  { 
21732175						// The last hop in this iterator is the first hop in 
@@ -7185,6 +7187,134 @@ mod tests {
71857187			assert_eq ! ( err,  "Failed to find a path to the given destination" ) ; 
71867188		}  else  {  panic ! ( )  } 
71877189	} 
7190+ 
7191+ 	#[ test]  
7192+ 	fn  min_htlc_overpay_violates_max_htlc ( )  { 
7193+ 		// Test that if overpaying to meet a later hop's min_htlc and causes us to violate an earlier 
7194+ 		// hop's max_htlc, we discard that invalid path. Previously we would consider the path to be 
7195+ 		// valid and hit a debug panic asserting that the used liquidity for a hop was less than its 
7196+ 		// available liquidity limit. 
7197+ 		let  secp_ctx = Secp256k1 :: new ( ) ; 
7198+ 		let  logger = Arc :: new ( ln_test_utils:: TestLogger :: new ( ) ) ; 
7199+ 		let  network_graph = Arc :: new ( NetworkGraph :: new ( Network :: Testnet ,  Arc :: clone ( & logger) ) ) ; 
7200+ 		let  scorer = ln_test_utils:: TestScorer :: new ( ) ; 
7201+ 		let  keys_manager = ln_test_utils:: TestKeysInterface :: new ( & [ 0u8 ;  32 ] ,  Network :: Testnet ) ; 
7202+ 		let  random_seed_bytes = keys_manager. get_secure_random_bytes ( ) ; 
7203+ 		let  config = UserConfig :: default ( ) ; 
7204+ 
7205+ 		// Values are taken from the fuzz input that uncovered this panic. 
7206+ 		let  amt_msat = 7_4009_8048 ; 
7207+ 		let  ( _,  our_id,  _,  nodes)  = get_nodes ( & secp_ctx) ; 
7208+ 		let  first_hop_outbound_capacity = 2_7345_2000 ; 
7209+ 		let  first_hops = vec ! [ get_channel_details( 
7210+ 			Some ( 200 ) ,  nodes[ 0 ] ,  channelmanager:: provided_init_features( & config) , 
7211+ 			first_hop_outbound_capacity
7212+ 		) ] ; 
7213+ 
7214+ 		let  route_hint = RouteHint ( vec ! [ RouteHintHop  { 
7215+ 			src_node_id:  nodes[ 0 ] , 
7216+ 			short_channel_id:  44 , 
7217+ 			fees:  RoutingFees  { 
7218+ 				base_msat:  1_6778_3453 , 
7219+ 				proportional_millionths:  0 , 
7220+ 			} , 
7221+ 			cltv_expiry_delta:  10 , 
7222+ 			htlc_minimum_msat:  Some ( 2_5165_8240 ) , 
7223+ 			htlc_maximum_msat:  None , 
7224+ 		} ] ) ; 
7225+ 
7226+ 		let  payment_params = PaymentParameters :: from_node_id ( nodes[ 1 ] ,  42 ) 
7227+ 			. with_route_hints ( vec ! [ route_hint] ) . unwrap ( ) 
7228+ 			. with_bolt11_features ( channelmanager:: provided_invoice_features ( & config) ) . unwrap ( ) ; 
7229+ 
7230+ 		let  netgraph = network_graph. read_only ( ) ; 
7231+ 		let  route_params = RouteParameters :: from_payment_params_and_value ( 
7232+ 			payment_params,  amt_msat) ; 
7233+ 		if  let  Err ( LightningError  {  err,  .. } )  = get_route ( 
7234+ 			& our_id,  & route_params,  & netgraph,  Some ( & first_hops. iter ( ) . collect :: < Vec < _ > > ( ) ) , 
7235+ 			Arc :: clone ( & logger) ,  & scorer,  & ( ) ,  & random_seed_bytes
7236+ 		)  { 
7237+ 			assert_eq ! ( err,  "Failed to find a path to the given destination" ) ; 
7238+ 		}  else  {  panic ! ( )  } 
7239+ 	} 
7240+ 
7241+ 	#[ test]  
7242+ 	fn  previously_used_liquidity_violates_max_htlc ( )  { 
7243+ 		// Test that if a candidate hop would cause us to violate an upstream hop's available 
7244+ 		// contribution amount due to a previously found path, we will not consider that candidate in 
7245+ 		// path construction. Previously we would construct an invalid path and hit a debug panic 
7246+ 		// asserting that the used liquidity for a hop was less than its available liquidity limit. 
7247+ 		let  secp_ctx = Secp256k1 :: new ( ) ; 
7248+ 		let  logger = Arc :: new ( ln_test_utils:: TestLogger :: new ( ) ) ; 
7249+ 		let  network_graph = Arc :: new ( NetworkGraph :: new ( Network :: Testnet ,  Arc :: clone ( & logger) ) ) ; 
7250+ 		let  scorer = ln_test_utils:: TestScorer :: new ( ) ; 
7251+ 		let  keys_manager = ln_test_utils:: TestKeysInterface :: new ( & [ 0u8 ;  32 ] ,  Network :: Testnet ) ; 
7252+ 		let  random_seed_bytes = keys_manager. get_secure_random_bytes ( ) ; 
7253+ 		let  config = UserConfig :: default ( ) ; 
7254+ 
7255+ 		// Values are taken from the fuzz input that uncovered this panic. 
7256+ 		let  amt_msat = 52_4288 ; 
7257+ 		let  ( _,  our_id,  _,  nodes)  = get_nodes ( & secp_ctx) ; 
7258+ 		let  first_hops = vec ! [ get_channel_details( 
7259+ 			Some ( 161 ) ,  nodes[ 0 ] ,  channelmanager:: provided_init_features( & config) ,  486_4000 
7260+ 		) ,  get_channel_details( 
7261+ 			Some ( 122 ) ,  nodes[ 0 ] ,  channelmanager:: provided_init_features( & config) ,  179_5000 
7262+ 		) ] ; 
7263+ 
7264+ 		let  route_hints = vec ! [ RouteHint ( vec![ RouteHintHop  { 
7265+ 			src_node_id:  nodes[ 0 ] , 
7266+ 			short_channel_id:  42 , 
7267+ 			fees:  RoutingFees  { 
7268+ 				base_msat:  0 , 
7269+ 				proportional_millionths:  0 , 
7270+ 			} , 
7271+ 			cltv_expiry_delta:  10 , 
7272+ 			htlc_minimum_msat:  Some ( 1_4392 ) , 
7273+ 			htlc_maximum_msat:  Some ( 143_9200 ) , 
7274+ 		} ] ) ,  RouteHint ( vec![ RouteHintHop  { 
7275+ 			src_node_id:  nodes[ 0 ] , 
7276+ 			short_channel_id:  43 , 
7277+ 			fees:  RoutingFees  { 
7278+ 				base_msat:  425_9840 , 
7279+ 				proportional_millionths:  0 , 
7280+ 			} , 
7281+ 			cltv_expiry_delta:  10 , 
7282+ 			htlc_minimum_msat:  Some ( 19_7401 ) , 
7283+ 			htlc_maximum_msat:  Some ( 1974_0100 ) , 
7284+ 		} ] ) ,  RouteHint ( vec![ RouteHintHop  { 
7285+ 			src_node_id:  nodes[ 0 ] , 
7286+ 			short_channel_id:  44 , 
7287+ 			fees:  RoutingFees  { 
7288+ 				base_msat:  0 , 
7289+ 				proportional_millionths:  0 , 
7290+ 			} , 
7291+ 			cltv_expiry_delta:  10 , 
7292+ 			htlc_minimum_msat:  Some ( 1027 ) , 
7293+ 			htlc_maximum_msat:  Some ( 10_2700 ) , 
7294+ 		} ] ) ,  RouteHint ( vec![ RouteHintHop  { 
7295+ 			src_node_id:  nodes[ 0 ] , 
7296+ 			short_channel_id:  45 , 
7297+ 			fees:  RoutingFees  { 
7298+ 				base_msat:  0 , 
7299+ 				proportional_millionths:  0 , 
7300+ 			} , 
7301+ 			cltv_expiry_delta:  10 , 
7302+ 			htlc_minimum_msat:  Some ( 6_5535 ) , 
7303+ 			htlc_maximum_msat:  Some ( 655_3500 ) , 
7304+ 		} ] ) ] ; 
7305+ 
7306+ 		let  payment_params = PaymentParameters :: from_node_id ( nodes[ 1 ] ,  42 ) 
7307+ 			. with_route_hints ( route_hints) . unwrap ( ) 
7308+ 			. with_bolt11_features ( channelmanager:: provided_invoice_features ( & config) ) . unwrap ( ) ; 
7309+ 
7310+ 		let  netgraph = network_graph. read_only ( ) ; 
7311+ 		let  route_params = RouteParameters :: from_payment_params_and_value ( 
7312+ 			payment_params,  amt_msat) ; 
7313+ 		assert ! ( get_route( 
7314+ 			& our_id,  & route_params,  & netgraph,  Some ( & first_hops. iter( ) . collect:: <Vec <_>>( ) ) , 
7315+ 			Arc :: clone( & logger) ,  & scorer,  & ( ) ,  & random_seed_bytes
7316+ 		) . is_ok( ) ) ; 
7317+ 	} 
71887318} 
71897319
71907320#[ cfg( all( any( test,  ldk_bench) ,  not( feature = "no-std" ) ) ) ]  
0 commit comments