@@ -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