@@ -1234,7 +1234,11 @@ impl<'a> PaymentPath<'a> {
12341234	// Note that this function is not aware of the available_liquidity limit, and thus does not 
12351235	// support increasing the value being transferred beyond what was selected during the initial 
12361236	// routing passes. 
1237- 	fn  update_value_and_recompute_fees ( & mut  self ,  value_msat :  u64 )  { 
1237+ 	// 
1238+ 	// Returns the amount that this path contributes to the total payment value, which may be greater 
1239+ 	// than `value_msat` if we had to overpay to meet the final node's `htlc_minimum_msat`. 
1240+ 	fn  update_value_and_recompute_fees ( & mut  self ,  value_msat :  u64 )  -> u64  { 
1241+ 		let  mut  extra_contribution_msat = 0 ; 
12381242		let  mut  total_fee_paid_msat = 0  as  u64 ; 
12391243		for  i in  ( 0 ..self . hops . len ( ) ) . rev ( )  { 
12401244			let  last_hop = i == self . hops . len ( )  - 1 ; 
@@ -1249,6 +1253,7 @@ impl<'a> PaymentPath<'a> {
12491253
12501254			let  cur_hop = & mut  self . hops . get_mut ( i) . unwrap ( ) . 0 ; 
12511255			cur_hop. next_hops_fee_msat  = total_fee_paid_msat; 
1256+ 			cur_hop. path_penalty_msat  += extra_contribution_msat; 
12521257			// Overpay in fees if we can't save these funds due to htlc_minimum_msat. 
12531258			// We try to account for htlc_minimum_msat in scoring (add_entry!), so that nodes don't 
12541259			// set it too high just to maliciously take more fees by exploiting this 
@@ -1264,8 +1269,15 @@ impl<'a> PaymentPath<'a> {
12641269				// Also, this can't be exploited more heavily than *announce a free path and fail 
12651270				// all payments*. 
12661271				cur_hop_transferred_amount_msat += extra_fees_msat; 
1267- 				total_fee_paid_msat += extra_fees_msat; 
1268- 				cur_hop_fees_msat += extra_fees_msat; 
1272+ 
1273+ 				// We remember and return the extra fees on the final hop to allow accounting for 
1274+ 				// them in the path's value contribution. 
1275+ 				if  last_hop { 
1276+ 					extra_contribution_msat = extra_fees_msat; 
1277+ 				}  else  { 
1278+ 					total_fee_paid_msat += extra_fees_msat; 
1279+ 					cur_hop_fees_msat += extra_fees_msat; 
1280+ 				} 
12691281			} 
12701282
12711283			if  last_hop { 
@@ -1293,6 +1305,7 @@ impl<'a> PaymentPath<'a> {
12931305				} 
12941306			} 
12951307		} 
1308+ 		value_msat + extra_contribution_msat
12961309	} 
12971310} 
12981311
@@ -2289,8 +2302,8 @@ where L::Target: Logger {
22892302				// recompute the fees again, so that if that's the case, we match the currently 
22902303				// underpaid htlc_minimum_msat with fees. 
22912304				debug_assert_eq ! ( payment_path. get_value_msat( ) ,  value_contribution_msat) ; 
2292- 				value_contribution_msat  = cmp:: min ( value_contribution_msat,  final_value_msat) ; 
2293- 				payment_path. update_value_and_recompute_fees ( value_contribution_msat ) ; 
2305+ 				let  desired_value_contribution  = cmp:: min ( value_contribution_msat,  final_value_msat) ; 
2306+ 				value_contribution_msat =  payment_path. update_value_and_recompute_fees ( desired_value_contribution ) ; 
22942307
22952308				// Since a path allows to transfer as much value as 
22962309				// the smallest channel it has ("bottleneck"), we should recompute 
@@ -7427,6 +7440,67 @@ mod tests {
74277440			assert_eq ! ( err,  "Failed to find a path to the given destination" ) ; 
74287441		}  else  {  panic ! ( )  } 
74297442	} 
7443+ 
7444+ 	#[ test]  
7445+ 	fn  path_contribution_includes_min_htlc_overpay ( )  { 
7446+ 		// Previously, the fuzzer hit a debug panic because we wouldn't include the amount overpaid to 
7447+ 		// meet a last hop's min_htlc in the total collected paths value. We now include this value and 
7448+ 		// also penalize hops along the overpaying path to ensure that it gets deprioritized in path 
7449+ 		// selection, both tested here. 
7450+ 		let  secp_ctx = Secp256k1 :: new ( ) ; 
7451+ 		let  logger = Arc :: new ( ln_test_utils:: TestLogger :: new ( ) ) ; 
7452+ 		let  network_graph = Arc :: new ( NetworkGraph :: new ( Network :: Testnet ,  Arc :: clone ( & logger) ) ) ; 
7453+ 		let  scorer = ProbabilisticScorer :: new ( ProbabilisticScoringDecayParameters :: default ( ) ,  network_graph. clone ( ) ,  logger. clone ( ) ) ; 
7454+ 		let  keys_manager = ln_test_utils:: TestKeysInterface :: new ( & [ 0u8 ;  32 ] ,  Network :: Testnet ) ; 
7455+ 		let  random_seed_bytes = keys_manager. get_secure_random_bytes ( ) ; 
7456+ 		let  config = UserConfig :: default ( ) ; 
7457+ 
7458+ 		// Values are taken from the fuzz input that uncovered this panic. 
7459+ 		let  amt_msat = 562_0000 ; 
7460+ 		let  ( _,  our_id,  _,  nodes)  = get_nodes ( & secp_ctx) ; 
7461+ 		let  first_hops = vec ! [ 
7462+ 			get_channel_details( 
7463+ 				Some ( 83 ) ,  nodes[ 0 ] ,  channelmanager:: provided_init_features( & config) ,  2199_0000 , 
7464+ 			) , 
7465+ 		] ; 
7466+ 
7467+ 		let  htlc_mins = [ 49_0000 ,  1125_0000 ] ; 
7468+ 		let  payment_params = { 
7469+ 			let  blinded_path = BlindedPath  { 
7470+ 				introduction_node_id :  nodes[ 0 ] , 
7471+ 				blinding_point :  ln_test_utils:: pubkey ( 42 ) , 
7472+ 				blinded_hops :  vec ! [ 
7473+ 					BlindedHop  {  blinded_node_id:  ln_test_utils:: pubkey( 42  as  u8 ) ,  encrypted_payload:  Vec :: new( )  } , 
7474+ 					BlindedHop  {  blinded_node_id:  ln_test_utils:: pubkey( 42  as  u8 ) ,  encrypted_payload:  Vec :: new( )  } 
7475+ 				] , 
7476+ 			} ; 
7477+ 			let  mut  blinded_hints = Vec :: new ( ) ; 
7478+ 			for  htlc_min in  htlc_mins { 
7479+ 				blinded_hints. push ( ( BlindedPayInfo  { 
7480+ 					fee_base_msat :  0 , 
7481+ 					fee_proportional_millionths :  0 , 
7482+ 					htlc_minimum_msat :  htlc_min, 
7483+ 					htlc_maximum_msat :  htlc_min *  100 , 
7484+ 					cltv_expiry_delta :  10 , 
7485+ 					features :  BlindedHopFeatures :: empty ( ) , 
7486+ 				} ,  blinded_path. clone ( ) ) ) ; 
7487+ 			} 
7488+ 			let  bolt12_features:  Bolt12InvoiceFeatures  = channelmanager:: provided_invoice_features ( & config) . to_context ( ) ; 
7489+ 			PaymentParameters :: blinded ( blinded_hints. clone ( ) ) 
7490+ 				. with_bolt12_features ( bolt12_features. clone ( ) ) . unwrap ( ) 
7491+ 		} ; 
7492+ 
7493+ 		let  netgraph = network_graph. read_only ( ) ; 
7494+ 		let  route_params = RouteParameters :: from_payment_params_and_value ( 
7495+ 			payment_params,  amt_msat) ; 
7496+ 		let  route = get_route ( 
7497+ 			& our_id,  & route_params,  & netgraph,  Some ( & first_hops. iter ( ) . collect :: < Vec < _ > > ( ) ) , 
7498+ 			Arc :: clone ( & logger) ,  & scorer,  & ProbabilisticScoringFeeParameters :: default ( ) , 
7499+ 			& random_seed_bytes
7500+ 		) . unwrap ( ) ; 
7501+ 		assert_eq ! ( route. paths. len( ) ,  1 ) ; 
7502+ 		assert_eq ! ( route. get_total_amount( ) ,  amt_msat) ; 
7503+ 	} 
74307504} 
74317505
74327506#[ cfg( all( any( test,  ldk_bench) ,  not( feature = "no-std" ) ) ) ]  
0 commit comments