@@ -2204,11 +2204,15 @@ where L::Target: Logger {
22042204 . map_or ( None , |inc| inc. checked_add ( aggregate_next_hops_fee_msat) ) ;
22052205 aggregate_next_hops_fee_msat = if let Some ( val) = hops_fee { val } else { break ; } ;
22062206
2207- let hop_htlc_minimum_msat = candidate. htlc_minimum_msat ( ) ;
2208- let hop_htlc_minimum_msat_inc = if let Some ( val) = compute_fees ( aggregate_next_hops_path_htlc_minimum_msat, hop. fees ) { val } else { break ; } ;
2209- let hops_path_htlc_minimum = aggregate_next_hops_path_htlc_minimum_msat
2210- . checked_add ( hop_htlc_minimum_msat_inc) ;
2211- aggregate_next_hops_path_htlc_minimum_msat = if let Some ( val) = hops_path_htlc_minimum { cmp:: max ( hop_htlc_minimum_msat, val) } else { break ; } ;
2207+ // The next channel will need to relay this channel's min_htlc *plus* the fees taken by
2208+ // this route hint's source node to forward said min over this channel.
2209+ aggregate_next_hops_path_htlc_minimum_msat = {
2210+ let curr_htlc_min = cmp:: max (
2211+ candidate. htlc_minimum_msat ( ) , aggregate_next_hops_path_htlc_minimum_msat
2212+ ) ;
2213+ let curr_htlc_min_fee = if let Some ( val) = compute_fees ( curr_htlc_min, hop. fees ) { val } else { break } ;
2214+ if let Some ( min) = curr_htlc_min. checked_add ( curr_htlc_min_fee) { min } else { break }
2215+ } ;
22122216
22132217 if idx == route. 0 . len ( ) - 1 {
22142218 // The last hop in this iterator is the first hop in
@@ -7279,6 +7283,118 @@ mod tests {
72797283 assert_eq ! ( err, "Failed to find a path to the given destination" ) ;
72807284 } else { panic ! ( ) }
72817285 }
7286+
7287+ #[ test]
7288+ fn min_htlc_overpay_violates_max_htlc ( ) {
7289+ // Test that if overpaying to meet a later hop's min_htlc and causes us to violate an earlier
7290+ // hop's max_htlc, we don't consider that candidate hop valid. Previously we would add this hop
7291+ // to `targets` and build an invalid path with it, and subsquently hit a debug panic asserting
7292+ // that the used liquidity for a hop was less than its available liquidity limit.
7293+ let secp_ctx = Secp256k1 :: new ( ) ;
7294+ let logger = Arc :: new ( ln_test_utils:: TestLogger :: new ( ) ) ;
7295+ let network_graph = Arc :: new ( NetworkGraph :: new ( Network :: Testnet , Arc :: clone ( & logger) ) ) ;
7296+ let scorer = ln_test_utils:: TestScorer :: new ( ) ;
7297+ let keys_manager = ln_test_utils:: TestKeysInterface :: new ( & [ 0u8 ; 32 ] , Network :: Testnet ) ;
7298+ let random_seed_bytes = keys_manager. get_secure_random_bytes ( ) ;
7299+ let config = UserConfig :: default ( ) ;
7300+
7301+ // Values are taken from the fuzz input that uncovered this panic.
7302+ let amt_msat = 7_4009_8048 ;
7303+ let ( _, our_id, _, nodes) = get_nodes ( & secp_ctx) ;
7304+ let first_hop_outbound_capacity = 2_7345_2000 ;
7305+ let first_hops = vec ! [ get_channel_details(
7306+ Some ( 200 ) , nodes[ 0 ] , channelmanager:: provided_init_features( & config) ,
7307+ first_hop_outbound_capacity
7308+ ) ] ;
7309+
7310+ let base_fee = 1_6778_3453 ;
7311+ let htlc_min = 2_5165_8240 ;
7312+ let payment_params = {
7313+ let route_hint = RouteHint ( vec ! [ RouteHintHop {
7314+ src_node_id: nodes[ 0 ] ,
7315+ short_channel_id: 42 ,
7316+ fees: RoutingFees {
7317+ base_msat: base_fee,
7318+ proportional_millionths: 0 ,
7319+ } ,
7320+ cltv_expiry_delta: 10 ,
7321+ htlc_minimum_msat: Some ( htlc_min) ,
7322+ htlc_maximum_msat: None ,
7323+ } ] ) ;
7324+
7325+ PaymentParameters :: from_node_id ( nodes[ 1 ] , 42 )
7326+ . with_route_hints ( vec ! [ route_hint] ) . unwrap ( )
7327+ . with_bolt11_features ( channelmanager:: provided_invoice_features ( & config) ) . unwrap ( )
7328+ } ;
7329+
7330+ let netgraph = network_graph. read_only ( ) ;
7331+ let route_params = RouteParameters :: from_payment_params_and_value (
7332+ payment_params, amt_msat) ;
7333+ if let Err ( LightningError { err, .. } ) = get_route (
7334+ & our_id, & route_params, & netgraph, Some ( & first_hops. iter ( ) . collect :: < Vec < _ > > ( ) ) ,
7335+ Arc :: clone ( & logger) , & scorer, & ( ) , & random_seed_bytes
7336+ ) {
7337+ assert_eq ! ( err, "Failed to find a path to the given destination" ) ;
7338+ } else { panic ! ( ) }
7339+ }
7340+
7341+ #[ test]
7342+ fn previously_used_liquidity_violates_max_htlc ( ) {
7343+ // Test that if a candidate first_hop<>route_hint_src_node channel does not have enough
7344+ // contribution amount to cover the next hop's min_htlc plus fees, we will not consider that
7345+ // candidate. In this case, the candidate does not have enough due to a previous path taking up
7346+ // some of its liquidity. Previously we would construct an invalid path and hit a debug panic
7347+ // asserting that the used liquidity for a hop was less than its available liquidity limit.
7348+ let secp_ctx = Secp256k1 :: new ( ) ;
7349+ let logger = Arc :: new ( ln_test_utils:: TestLogger :: new ( ) ) ;
7350+ let network_graph = Arc :: new ( NetworkGraph :: new ( Network :: Testnet , Arc :: clone ( & logger) ) ) ;
7351+ let scorer = ln_test_utils:: TestScorer :: new ( ) ;
7352+ let keys_manager = ln_test_utils:: TestKeysInterface :: new ( & [ 0u8 ; 32 ] , Network :: Testnet ) ;
7353+ let random_seed_bytes = keys_manager. get_secure_random_bytes ( ) ;
7354+ let config = UserConfig :: default ( ) ;
7355+
7356+ // Values are taken from the fuzz input that uncovered this panic.
7357+ let amt_msat = 52_4288 ;
7358+ let ( _, our_id, _, nodes) = get_nodes ( & secp_ctx) ;
7359+ let first_hops = vec ! [ get_channel_details(
7360+ Some ( 161 ) , nodes[ 0 ] , channelmanager:: provided_init_features( & config) , 486_4000
7361+ ) , get_channel_details(
7362+ Some ( 122 ) , nodes[ 0 ] , channelmanager:: provided_init_features( & config) , 179_5000
7363+ ) ] ;
7364+
7365+ let base_fees = [ 0 , 425_9840 , 0 , 0 ] ;
7366+ let htlc_mins = [ 1_4392 , 19_7401 , 1027 , 6_5535 ] ;
7367+ let payment_params = {
7368+ let mut route_hints = Vec :: new ( ) ;
7369+ for ( idx, ( base_fee, htlc_min) ) in base_fees. iter ( ) . zip ( htlc_mins. iter ( ) ) . enumerate ( ) {
7370+ route_hints. push ( RouteHint ( vec ! [ RouteHintHop {
7371+ src_node_id: nodes[ 0 ] ,
7372+ short_channel_id: 42 + idx as u64 ,
7373+ fees: RoutingFees {
7374+ base_msat: * base_fee,
7375+ proportional_millionths: 0 ,
7376+ } ,
7377+ cltv_expiry_delta: 10 ,
7378+ htlc_minimum_msat: Some ( * htlc_min) ,
7379+ htlc_maximum_msat: Some ( htlc_min * 100 ) ,
7380+ } ] ) ) ;
7381+ }
7382+ PaymentParameters :: from_node_id ( nodes[ 1 ] , 42 )
7383+ . with_route_hints ( route_hints) . unwrap ( )
7384+ . with_bolt11_features ( channelmanager:: provided_invoice_features ( & config) ) . unwrap ( )
7385+ } ;
7386+
7387+ let netgraph = network_graph. read_only ( ) ;
7388+ let route_params = RouteParameters :: from_payment_params_and_value (
7389+ payment_params, amt_msat) ;
7390+
7391+ let route = get_route (
7392+ & our_id, & route_params, & netgraph, Some ( & first_hops. iter ( ) . collect :: < Vec < _ > > ( ) ) ,
7393+ Arc :: clone ( & logger) , & scorer, & ( ) , & random_seed_bytes
7394+ ) . unwrap ( ) ;
7395+ assert_eq ! ( route. paths. len( ) , 1 ) ;
7396+ assert_eq ! ( route. get_total_amount( ) , amt_msat) ;
7397+ }
72827398}
72837399
72847400#[ cfg( all( any( test, ldk_bench) , not( feature = "no-std" ) ) ) ]
0 commit comments