@@ -2163,11 +2163,15 @@ 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+ // The next channel will need to relay this channel's min_htlc *plus* the fees taken by
2167+ // this route hint's source node to forward said min over this channel.
2168+ aggregate_next_hops_path_htlc_minimum_msat = {
2169+ let curr_htlc_min = cmp:: max (
2170+ candidate. htlc_minimum_msat ( ) , aggregate_next_hops_path_htlc_minimum_msat
2171+ ) ;
2172+ let curr_htlc_min_fee = if let Some ( val) = compute_fees ( curr_htlc_min, hop. fees ) { val } else { break } ;
2173+ if let Some ( min) = curr_htlc_min. checked_add ( curr_htlc_min_fee) { min } else { break }
2174+ } ;
21712175
21722176 if idx == route. 0 . len ( ) - 1 {
21732177 // The last hop in this iterator is the first hop in
@@ -7185,6 +7189,118 @@ mod tests {
71857189 assert_eq ! ( err, "Failed to find a path to the given destination" ) ;
71867190 } else { panic ! ( ) }
71877191 }
7192+
7193+ #[ test]
7194+ fn min_htlc_overpay_violates_max_htlc ( ) {
7195+ // Test that if overpaying to meet a later hop's min_htlc and causes us to violate an earlier
7196+ // hop's max_htlc, we don't consider that candidate hop valid. Previously we would add this hop
7197+ // to `targets` and build an invalid path with it, and subsquently hit a debug panic asserting
7198+ // that the used liquidity for a hop was less than its available liquidity limit.
7199+ let secp_ctx = Secp256k1 :: new ( ) ;
7200+ let logger = Arc :: new ( ln_test_utils:: TestLogger :: new ( ) ) ;
7201+ let network_graph = Arc :: new ( NetworkGraph :: new ( Network :: Testnet , Arc :: clone ( & logger) ) ) ;
7202+ let scorer = ln_test_utils:: TestScorer :: new ( ) ;
7203+ let keys_manager = ln_test_utils:: TestKeysInterface :: new ( & [ 0u8 ; 32 ] , Network :: Testnet ) ;
7204+ let random_seed_bytes = keys_manager. get_secure_random_bytes ( ) ;
7205+ let config = UserConfig :: default ( ) ;
7206+
7207+ // Values are taken from the fuzz input that uncovered this panic.
7208+ let amt_msat = 7_4009_8048 ;
7209+ let ( _, our_id, _, nodes) = get_nodes ( & secp_ctx) ;
7210+ let first_hop_outbound_capacity = 2_7345_2000 ;
7211+ let first_hops = vec ! [ get_channel_details(
7212+ Some ( 200 ) , nodes[ 0 ] , channelmanager:: provided_init_features( & config) ,
7213+ first_hop_outbound_capacity
7214+ ) ] ;
7215+
7216+ let base_fee = 1_6778_3453 ;
7217+ let htlc_min = 2_5165_8240 ;
7218+ let payment_params = {
7219+ let route_hint = RouteHint ( vec ! [ RouteHintHop {
7220+ src_node_id: nodes[ 0 ] ,
7221+ short_channel_id: 42 ,
7222+ fees: RoutingFees {
7223+ base_msat: base_fee,
7224+ proportional_millionths: 0 ,
7225+ } ,
7226+ cltv_expiry_delta: 10 ,
7227+ htlc_minimum_msat: Some ( htlc_min) ,
7228+ htlc_maximum_msat: None ,
7229+ } ] ) ;
7230+
7231+ PaymentParameters :: from_node_id ( nodes[ 1 ] , 42 )
7232+ . with_route_hints ( vec ! [ route_hint] ) . unwrap ( )
7233+ . with_bolt11_features ( channelmanager:: provided_invoice_features ( & config) ) . unwrap ( )
7234+ } ;
7235+
7236+ let netgraph = network_graph. read_only ( ) ;
7237+ let route_params = RouteParameters :: from_payment_params_and_value (
7238+ payment_params, amt_msat) ;
7239+ if let Err ( LightningError { err, .. } ) = get_route (
7240+ & our_id, & route_params, & netgraph, Some ( & first_hops. iter ( ) . collect :: < Vec < _ > > ( ) ) ,
7241+ Arc :: clone ( & logger) , & scorer, & ( ) , & random_seed_bytes
7242+ ) {
7243+ assert_eq ! ( err, "Failed to find a path to the given destination" ) ;
7244+ } else { panic ! ( ) }
7245+ }
7246+
7247+ #[ test]
7248+ fn previously_used_liquidity_violates_max_htlc ( ) {
7249+ // Test that if a candidate first_hop<>route_hint_src_node channel does not have enough
7250+ // contribution amount to cover the next hop's min_htlc plus fees, we will not consider that
7251+ // candidate. In this case, the candidate does not have enough due to a previous path taking up
7252+ // some of its liquidity. Previously we would construct an invalid path and hit a debug panic
7253+ // asserting that the used liquidity for a hop was less than its available liquidity limit.
7254+ let secp_ctx = Secp256k1 :: new ( ) ;
7255+ let logger = Arc :: new ( ln_test_utils:: TestLogger :: new ( ) ) ;
7256+ let network_graph = Arc :: new ( NetworkGraph :: new ( Network :: Testnet , Arc :: clone ( & logger) ) ) ;
7257+ let scorer = ln_test_utils:: TestScorer :: new ( ) ;
7258+ let keys_manager = ln_test_utils:: TestKeysInterface :: new ( & [ 0u8 ; 32 ] , Network :: Testnet ) ;
7259+ let random_seed_bytes = keys_manager. get_secure_random_bytes ( ) ;
7260+ let config = UserConfig :: default ( ) ;
7261+
7262+ // Values are taken from the fuzz input that uncovered this panic.
7263+ let amt_msat = 52_4288 ;
7264+ let ( _, our_id, _, nodes) = get_nodes ( & secp_ctx) ;
7265+ let first_hops = vec ! [ get_channel_details(
7266+ Some ( 161 ) , nodes[ 0 ] , channelmanager:: provided_init_features( & config) , 486_4000
7267+ ) , get_channel_details(
7268+ Some ( 122 ) , nodes[ 0 ] , channelmanager:: provided_init_features( & config) , 179_5000
7269+ ) ] ;
7270+
7271+ let base_fees = [ 0 , 425_9840 , 0 , 0 ] ;
7272+ let htlc_mins = [ 1_4392 , 19_7401 , 1027 , 6_5535 ] ;
7273+ let payment_params = {
7274+ let mut route_hints = Vec :: new ( ) ;
7275+ for ( idx, ( base_fee, htlc_min) ) in base_fees. iter ( ) . zip ( htlc_mins. iter ( ) ) . enumerate ( ) {
7276+ route_hints. push ( RouteHint ( vec ! [ RouteHintHop {
7277+ src_node_id: nodes[ 0 ] ,
7278+ short_channel_id: 42 + idx as u64 ,
7279+ fees: RoutingFees {
7280+ base_msat: * base_fee,
7281+ proportional_millionths: 0 ,
7282+ } ,
7283+ cltv_expiry_delta: 10 ,
7284+ htlc_minimum_msat: Some ( * htlc_min) ,
7285+ htlc_maximum_msat: Some ( htlc_min * 100 ) ,
7286+ } ] ) ) ;
7287+ }
7288+ PaymentParameters :: from_node_id ( nodes[ 1 ] , 42 )
7289+ . with_route_hints ( route_hints) . unwrap ( )
7290+ . with_bolt11_features ( channelmanager:: provided_invoice_features ( & config) ) . unwrap ( )
7291+ } ;
7292+
7293+ let netgraph = network_graph. read_only ( ) ;
7294+ let route_params = RouteParameters :: from_payment_params_and_value (
7295+ payment_params, amt_msat) ;
7296+
7297+ let route = get_route (
7298+ & our_id, & route_params, & netgraph, Some ( & first_hops. iter ( ) . collect :: < Vec < _ > > ( ) ) ,
7299+ Arc :: clone ( & logger) , & scorer, & ( ) , & random_seed_bytes
7300+ ) . unwrap ( ) ;
7301+ assert_eq ! ( route. paths. len( ) , 1 ) ;
7302+ assert_eq ! ( route. get_total_amount( ) , amt_msat) ;
7303+ }
71887304}
71897305
71907306#[ cfg( all( any( test, ldk_bench) , not( feature = "no-std" ) ) ) ]
0 commit comments