@@ -84,7 +84,8 @@ pub struct PaymentConstraints {
8484 	/// 
8585 	///[`BlindedHop`]: crate::blinded_path::BlindedHop 
8686 	pub  max_cltv_expiry :  u32 , 
87- 	/// The minimum value, in msat, that may be relayed over this [`BlindedHop`]. 
87+ 	/// The minimum value, in msat, that may be accepted by the node corresponding to this 
88+  	/// [`BlindedHop`]. 
8889 	pub  htlc_minimum_msat :  u64 , 
8990} 
9091
@@ -153,6 +154,29 @@ pub(super) fn blinded_hops<T: secp256k1::Signing + secp256k1::Verification>(
153154	utils:: construct_blinded_hops ( secp_ctx,  pks,  tlvs,  session_priv) 
154155} 
155156
157+ /// `None` if underflow or overflow occurs. 
158+ fn  amt_to_forward_msat ( inbound_amt_msat :  u64 ,  payment_relay :  & PaymentRelay )  -> Option < u64 >  { 
159+ 	let  amt_to_fwd_numerator = inbound_amt_msat. checked_sub ( payment_relay. fee_base_msat  as  u64 ) 
160+ 		. and_then ( |a| a. checked_mul ( 1_000_000 ) ) 
161+ 		. and_then ( |a| a. checked_add ( 1_000_000  - 1 ) ) 
162+ 		. and_then ( |a| a. checked_add ( payment_relay. fee_proportional_millionths  as  u64 ) ) ; 
163+ 	let  amt_to_fwd_denominator = payment_relay. fee_proportional_millionths . checked_add ( 1_000_000 ) ; 
164+ 	let  mut  amt_to_forward = match  ( amt_to_fwd_numerator,  amt_to_fwd_denominator)  { 
165+ 		( Some ( num) ,  Some ( denom) )  => num / denom as  u64 , 
166+ 		_ => return  None , 
167+ 	} ; 
168+ 	let  fee_opt = amt_to_forward. checked_mul ( payment_relay. fee_proportional_millionths  as  u64 ) 
169+ 		. and_then ( |prop| ( prop / 1000000 ) . checked_add ( payment_relay. fee_base_msat  as  u64 ) ) ; 
170+ 	let  fee = if  let  Some ( f)  = fee_opt {  f }  else  {  return  None  } ; 
171+ 	if  inbound_amt_msat - fee < amt_to_forward { 
172+ 		// Rounding up the forwarded amount resulted in underpaying this node, so take an extra 1 msat 
173+ 		// in fee to compensate. 
174+ 		amt_to_forward -= 1 ; 
175+ 	} 
176+ 	debug_assert_eq ! ( amt_to_forward + fee,  inbound_amt_msat) ; 
177+ 	Some ( amt_to_forward) 
178+ } 
179+ 
156180pub ( super )  fn  compute_payinfo ( 
157181	intermediate_nodes :  & [ ( PublicKey ,  ForwardTlvs ) ] ,  payee_tlvs :  & ReceiveTlvs 
158182)  -> Result < BlindedPayInfo ,  ( ) >  { 
@@ -184,11 +208,23 @@ pub(super) fn compute_payinfo(
184208
185209		cltv_expiry_delta = cltv_expiry_delta. checked_add ( tlvs. payment_relay . cltv_expiry_delta ) . ok_or ( ( ) ) ?; 
186210	} 
211+ 
212+ 	let  mut  htlc_minimum_msat:  u64  = 1 ; 
213+ 	for  ( _,  tlvs)  in  intermediate_nodes. iter ( )  { 
214+ 		htlc_minimum_msat = amt_to_forward_msat ( 
215+ 			core:: cmp:: max ( tlvs. payment_constraints . htlc_minimum_msat ,  htlc_minimum_msat) , 
216+ 			& tlvs. payment_relay 
217+ 		) . unwrap_or ( 1 ) ; 
218+ 	} 
219+ 	htlc_minimum_msat = core:: cmp:: max ( 
220+ 		payee_tlvs. payment_constraints . htlc_minimum_msat ,  htlc_minimum_msat
221+ 	) ; 
222+ 
187223	Ok ( BlindedPayInfo  { 
188224		fee_base_msat :  u32:: try_from ( curr_base_fee) . map_err ( |_| ( ) ) ?, 
189225		fee_proportional_millionths :  u32:: try_from ( curr_prop_mil) . map_err ( |_| ( ) ) ?, 
190226		cltv_expiry_delta, 
191- 		htlc_minimum_msat :   1 ,   // TODO 
227+ 		htlc_minimum_msat, 
192228		htlc_maximum_msat :  21_000_000  *  100_000_000  *  1_000 ,  // TODO 
193229		features :  BlindedHopFeatures :: empty ( ) , 
194230	} ) 
@@ -253,6 +289,7 @@ mod tests {
253289		assert_eq ! ( blinded_payinfo. fee_base_msat,  201 ) ; 
254290		assert_eq ! ( blinded_payinfo. fee_proportional_millionths,  1001 ) ; 
255291		assert_eq ! ( blinded_payinfo. cltv_expiry_delta,  288 ) ; 
292+ 		assert_eq ! ( blinded_payinfo. htlc_minimum_msat,  900 ) ; 
256293	} 
257294
258295	#[ test]  
@@ -268,5 +305,89 @@ mod tests {
268305		assert_eq ! ( blinded_payinfo. fee_base_msat,  0 ) ; 
269306		assert_eq ! ( blinded_payinfo. fee_proportional_millionths,  0 ) ; 
270307		assert_eq ! ( blinded_payinfo. cltv_expiry_delta,  0 ) ; 
308+ 		assert_eq ! ( blinded_payinfo. htlc_minimum_msat,  1 ) ; 
309+ 	} 
310+ 
311+ 	#[ test]  
312+ 	fn  simple_aggregated_htlc_min ( )  { 
313+ 		// If no hops charge fees, the htlc_minimum_msat should just be the maximum htlc_minimum_msat 
314+ 		// along the path. 
315+ 		let  dummy_pk = PublicKey :: from_slice ( & [ 2 ;  33 ] ) . unwrap ( ) ; 
316+ 		let  intermediate_nodes = vec ! [ ( dummy_pk,  ForwardTlvs  { 
317+ 			short_channel_id:  0 , 
318+ 			payment_relay:  PaymentRelay  { 
319+ 				cltv_expiry_delta:  0 , 
320+ 				fee_proportional_millionths:  0 , 
321+ 				fee_base_msat:  0 , 
322+ 			} , 
323+ 			payment_constraints:  PaymentConstraints  { 
324+ 				max_cltv_expiry:  0 , 
325+ 				htlc_minimum_msat:  1 , 
326+ 			} , 
327+ 			features:  BlindedHopFeatures :: empty( ) , 
328+ 		} ) ,  ( dummy_pk,  ForwardTlvs  { 
329+ 			short_channel_id:  0 , 
330+ 			payment_relay:  PaymentRelay  { 
331+ 				cltv_expiry_delta:  0 , 
332+ 				fee_proportional_millionths:  0 , 
333+ 				fee_base_msat:  0 , 
334+ 			} , 
335+ 			payment_constraints:  PaymentConstraints  { 
336+ 				max_cltv_expiry:  0 , 
337+ 				htlc_minimum_msat:  2_000 , 
338+ 			} , 
339+ 			features:  BlindedHopFeatures :: empty( ) , 
340+ 		} ) ] ; 
341+ 		let  recv_tlvs = ReceiveTlvs  { 
342+ 			payment_secret :  PaymentSecret ( [ 0 ;  32 ] ) , 
343+ 			payment_constraints :  PaymentConstraints  { 
344+ 				max_cltv_expiry :  0 , 
345+ 				htlc_minimum_msat :  3 , 
346+ 			} , 
347+ 		} ; 
348+ 		let  blinded_payinfo = super :: compute_payinfo ( & intermediate_nodes[ ..] ,  & recv_tlvs) . unwrap ( ) ; 
349+ 		assert_eq ! ( blinded_payinfo. htlc_minimum_msat,  2_000 ) ; 
350+ 	} 
351+ 
352+ 	#[ test]  
353+ 	fn  aggregated_htlc_min ( )  { 
354+ 		// Create a path with varying fees and htlc_mins, and make sure htlc_minimum_msat ends up as the 
355+ 		// max (htlc_min - following_fees) along the path. 
356+ 		let  dummy_pk = PublicKey :: from_slice ( & [ 2 ;  33 ] ) . unwrap ( ) ; 
357+ 		let  intermediate_nodes = vec ! [ ( dummy_pk,  ForwardTlvs  { 
358+ 			short_channel_id:  0 , 
359+ 			payment_relay:  PaymentRelay  { 
360+ 				cltv_expiry_delta:  0 , 
361+ 				fee_proportional_millionths:  500 , 
362+ 				fee_base_msat:  1_000 , 
363+ 			} , 
364+ 			payment_constraints:  PaymentConstraints  { 
365+ 				max_cltv_expiry:  0 , 
366+ 				htlc_minimum_msat:  5_000 , 
367+ 			} , 
368+ 			features:  BlindedHopFeatures :: empty( ) , 
369+ 		} ) ,  ( dummy_pk,  ForwardTlvs  { 
370+ 			short_channel_id:  0 , 
371+ 			payment_relay:  PaymentRelay  { 
372+ 				cltv_expiry_delta:  0 , 
373+ 				fee_proportional_millionths:  500 , 
374+ 				fee_base_msat:  200 , 
375+ 			} , 
376+ 			payment_constraints:  PaymentConstraints  { 
377+ 				max_cltv_expiry:  0 , 
378+ 				htlc_minimum_msat:  2_000 , 
379+ 			} , 
380+ 			features:  BlindedHopFeatures :: empty( ) , 
381+ 		} ) ] ; 
382+ 		let  recv_tlvs = ReceiveTlvs  { 
383+ 			payment_secret :  PaymentSecret ( [ 0 ;  32 ] ) , 
384+ 			payment_constraints :  PaymentConstraints  { 
385+ 				max_cltv_expiry :  0 , 
386+ 				htlc_minimum_msat :  1 , 
387+ 			} , 
388+ 		} ; 
389+ 		let  htlc_minimum_msat = 3798 ; 
390+ 		let  blinded_payinfo = super :: compute_payinfo ( & intermediate_nodes[ ..] ,  & recv_tlvs) . unwrap ( ) ; 
391+ 		assert_eq ! ( blinded_payinfo. htlc_minimum_msat,  htlc_minimum_msat) ; 
271392	} 
272393} 
0 commit comments