@@ -951,7 +951,10 @@ impl<'a> CandidateRouteHop<'a> {
951
951
liquidity_msat : details. next_outbound_htlc_limit_msat ,
952
952
} ,
953
953
CandidateRouteHop :: PublicHop { info, .. } => info. effective_capacity ( ) ,
954
- CandidateRouteHop :: PrivateHop { .. } => EffectiveCapacity :: Infinite ,
954
+ CandidateRouteHop :: PrivateHop { hint } => {
955
+ hint. htlc_maximum_msat . map_or ( EffectiveCapacity :: Infinite ,
956
+ |max| EffectiveCapacity :: HintMaxHTLC { amount_msat : max } )
957
+ } ,
955
958
}
956
959
}
957
960
}
@@ -965,6 +968,7 @@ fn max_htlc_from_capacity(capacity: EffectiveCapacity, max_channel_saturation_po
965
968
EffectiveCapacity :: Unknown => EffectiveCapacity :: Unknown . as_msat ( ) ,
966
969
EffectiveCapacity :: AdvertisedMaxHTLC { amount_msat } =>
967
970
amount_msat. checked_shr ( saturation_shift) . unwrap_or ( 0 ) ,
971
+ EffectiveCapacity :: HintMaxHTLC { amount_msat } => amount_msat,
968
972
EffectiveCapacity :: Total { capacity_msat, htlc_maximum_msat } =>
969
973
cmp:: min ( capacity_msat. checked_shr ( saturation_shift) . unwrap_or ( 0 ) , htlc_maximum_msat) ,
970
974
}
@@ -1833,18 +1837,20 @@ where L::Target: Logger {
1833
1837
} )
1834
1838
. unwrap_or_else ( || CandidateRouteHop :: PrivateHop { hint : hop } ) ;
1835
1839
1840
+ let used_liquidity_msat = used_channel_liquidities
1841
+ . get ( & ( hop. short_channel_id , source < target) ) . copied ( ) . unwrap_or ( 0 ) ;
1842
+ let hint_candidate_contribution_msat = cmp:: min ( path_value_msat,
1843
+ candidate. effective_capacity ( ) . as_msat ( ) . saturating_sub ( used_liquidity_msat) ) ;
1836
1844
if !add_entry ! ( candidate, source, target, aggregate_next_hops_fee_msat,
1837
- path_value_msat , aggregate_next_hops_path_htlc_minimum_msat,
1838
- aggregate_next_hops_path_penalty_msat,
1839
- aggregate_next_hops_cltv_delta , aggregate_next_hops_path_length) {
1845
+ hint_candidate_contribution_msat , aggregate_next_hops_path_htlc_minimum_msat,
1846
+ aggregate_next_hops_path_penalty_msat, aggregate_next_hops_cltv_delta ,
1847
+ aggregate_next_hops_path_length) {
1840
1848
// If this hop was not used then there is no use checking the preceding
1841
1849
// hops in the RouteHint. We can break by just searching for a direct
1842
1850
// channel between last checked hop and first_hop_targets.
1843
1851
hop_used = false ;
1844
1852
}
1845
1853
1846
- let used_liquidity_msat = used_channel_liquidities
1847
- . get ( & ( hop. short_channel_id , source < target) ) . copied ( ) . unwrap_or ( 0 ) ;
1848
1854
let channel_usage = ChannelUsage {
1849
1855
amount_msat : final_value_msat + aggregate_next_hops_fee_msat,
1850
1856
inflight_htlc_msat : used_liquidity_msat,
@@ -1865,12 +1871,11 @@ where L::Target: Logger {
1865
1871
// Searching for a direct channel between last checked hop and first_hop_targets
1866
1872
if let Some ( first_channels) = first_hop_targets. get ( & NodeId :: from_pubkey ( & prev_hop_id) ) {
1867
1873
for details in first_channels {
1868
- let candidate = CandidateRouteHop :: FirstHop { details } ;
1869
- add_entry ! ( candidate, our_node_id, NodeId :: from_pubkey( & prev_hop_id) ,
1870
- aggregate_next_hops_fee_msat, path_value_msat,
1871
- aggregate_next_hops_path_htlc_minimum_msat,
1872
- aggregate_next_hops_path_penalty_msat, aggregate_next_hops_cltv_delta,
1873
- aggregate_next_hops_path_length) ;
1874
+ let first_hop_candidate = CandidateRouteHop :: FirstHop { details } ;
1875
+ add_entry ! ( first_hop_candidate, our_node_id, NodeId :: from_pubkey( & prev_hop_id) ,
1876
+ aggregate_next_hops_fee_msat, hint_candidate_contribution_msat,
1877
+ aggregate_next_hops_path_htlc_minimum_msat, aggregate_next_hops_path_penalty_msat,
1878
+ aggregate_next_hops_cltv_delta, aggregate_next_hops_path_length) ;
1874
1879
}
1875
1880
}
1876
1881
@@ -1905,10 +1910,11 @@ where L::Target: Logger {
1905
1910
// path.
1906
1911
if let Some ( first_channels) = first_hop_targets. get ( & NodeId :: from_pubkey ( & hop. src_node_id ) ) {
1907
1912
for details in first_channels {
1908
- let candidate = CandidateRouteHop :: FirstHop { details } ;
1909
- add_entry ! ( candidate , our_node_id,
1913
+ let first_hop_candidate = CandidateRouteHop :: FirstHop { details } ;
1914
+ add_entry ! ( first_hop_candidate , our_node_id,
1910
1915
NodeId :: from_pubkey( & hop. src_node_id) ,
1911
- aggregate_next_hops_fee_msat, path_value_msat,
1916
+ aggregate_next_hops_fee_msat,
1917
+ hint_candidate_contribution_msat,
1912
1918
aggregate_next_hops_path_htlc_minimum_msat,
1913
1919
aggregate_next_hops_path_penalty_msat,
1914
1920
aggregate_next_hops_cltv_delta,
@@ -5906,6 +5912,119 @@ mod tests {
5906
5912
assert ! ( route. is_ok( ) ) ;
5907
5913
}
5908
5914
5915
+ #[ test]
5916
+ fn respect_route_hint_max_htlc ( ) {
5917
+ // Make sure that any max_htlc provided in the route hints of the payment params is respected in
5918
+ // the final route.
5919
+ let ( secp_ctx, network_graph, _, _, logger) = build_graph ( ) ;
5920
+ let netgraph = network_graph. read_only ( ) ;
5921
+ let ( _, our_id, _, nodes) = get_nodes ( & secp_ctx) ;
5922
+ let scorer = ln_test_utils:: TestScorer :: new ( ) ;
5923
+ let keys_manager = ln_test_utils:: TestKeysInterface :: new ( & [ 0u8 ; 32 ] , Network :: Testnet ) ;
5924
+ let random_seed_bytes = keys_manager. get_secure_random_bytes ( ) ;
5925
+ let config = UserConfig :: default ( ) ;
5926
+
5927
+ let max_htlc_msat = 50_000 ;
5928
+ let route_hint_1 = RouteHint ( vec ! [ RouteHintHop {
5929
+ src_node_id: nodes[ 2 ] ,
5930
+ short_channel_id: 42 ,
5931
+ fees: RoutingFees {
5932
+ base_msat: 100 ,
5933
+ proportional_millionths: 0 ,
5934
+ } ,
5935
+ cltv_expiry_delta: 10 ,
5936
+ htlc_minimum_msat: None ,
5937
+ htlc_maximum_msat: Some ( max_htlc_msat) ,
5938
+ } ] ) ;
5939
+ let dest_node_id = ln_test_utils:: pubkey ( 42 ) ;
5940
+ let payment_params = PaymentParameters :: from_node_id ( dest_node_id, 42 )
5941
+ . with_route_hints ( vec ! [ route_hint_1. clone( ) ] ) . unwrap ( )
5942
+ . with_bolt11_features ( channelmanager:: provided_invoice_features ( & config) ) . unwrap ( ) ;
5943
+
5944
+ // Make sure we'll error if our route hints don't have enough liquidity according to their
5945
+ // max_htlc.
5946
+ if let Err ( LightningError { err, action : ErrorAction :: IgnoreError } ) = get_route ( & our_id,
5947
+ & payment_params, & netgraph, None , max_htlc_msat + 1 , Arc :: clone ( & logger) , & scorer, & ( ) ,
5948
+ & random_seed_bytes)
5949
+ {
5950
+ assert_eq ! ( err, "Failed to find a sufficient route to the given destination" ) ;
5951
+ } else { panic ! ( ) ; }
5952
+
5953
+ // Make sure we'll split an MPP payment across route hints if their max_htlcs warrant it.
5954
+ let mut route_hint_2 = route_hint_1. clone ( ) ;
5955
+ route_hint_2. 0 [ 0 ] . short_channel_id = 43 ;
5956
+ let payment_params = PaymentParameters :: from_node_id ( dest_node_id, 42 )
5957
+ . with_route_hints ( vec ! [ route_hint_1, route_hint_2] ) . unwrap ( )
5958
+ . with_bolt11_features ( channelmanager:: provided_invoice_features ( & config) ) . unwrap ( ) ;
5959
+ let route = get_route ( & our_id, & payment_params, & netgraph, None , max_htlc_msat + 1 ,
5960
+ Arc :: clone ( & logger) , & scorer, & ( ) , & random_seed_bytes) . unwrap ( ) ;
5961
+ assert_eq ! ( route. paths. len( ) , 2 ) ;
5962
+ assert ! ( route. paths[ 0 ] . hops. last( ) . unwrap( ) . fee_msat <= max_htlc_msat) ;
5963
+ assert ! ( route. paths[ 1 ] . hops. last( ) . unwrap( ) . fee_msat <= max_htlc_msat) ;
5964
+ }
5965
+
5966
+ #[ test]
5967
+ fn direct_channel_to_hints_with_max_htlc ( ) {
5968
+ // Check that if we have a first hop channel peer that's connected to multiple provided route
5969
+ // hints, that we properly split the payment between the route hints if needed.
5970
+ let logger = Arc :: new ( ln_test_utils:: TestLogger :: new ( ) ) ;
5971
+ let network_graph = Arc :: new ( NetworkGraph :: new ( Network :: Testnet , Arc :: clone ( & logger) ) ) ;
5972
+ let scorer = ln_test_utils:: TestScorer :: new ( ) ;
5973
+ let keys_manager = ln_test_utils:: TestKeysInterface :: new ( & [ 0u8 ; 32 ] , Network :: Testnet ) ;
5974
+ let random_seed_bytes = keys_manager. get_secure_random_bytes ( ) ;
5975
+ let config = UserConfig :: default ( ) ;
5976
+
5977
+ let our_node_id = ln_test_utils:: pubkey ( 42 ) ;
5978
+ let intermed_node_id = ln_test_utils:: pubkey ( 43 ) ;
5979
+ let first_hop = vec ! [ get_channel_details( Some ( 42 ) , intermed_node_id, InitFeatures :: from_le_bytes( vec![ 0b11 ] ) , 10_000_000 ) ] ;
5980
+
5981
+ let amt_msat = 900_000 ;
5982
+ let max_htlc_msat = 500_000 ;
5983
+ let route_hint_1 = RouteHint ( vec ! [ RouteHintHop {
5984
+ src_node_id: intermed_node_id,
5985
+ short_channel_id: 44 ,
5986
+ fees: RoutingFees {
5987
+ base_msat: 100 ,
5988
+ proportional_millionths: 0 ,
5989
+ } ,
5990
+ cltv_expiry_delta: 10 ,
5991
+ htlc_minimum_msat: None ,
5992
+ htlc_maximum_msat: Some ( max_htlc_msat) ,
5993
+ } ] ) ;
5994
+ let mut route_hint_2 = route_hint_1. clone ( ) ;
5995
+ route_hint_2. 0 [ 0 ] . short_channel_id = 45 ;
5996
+ route_hint_2. 0 [ 0 ] . htlc_maximum_msat = Some ( max_htlc_msat) ;
5997
+ let dest_node_id = ln_test_utils:: pubkey ( 44 ) ;
5998
+ let payment_params = PaymentParameters :: from_node_id ( dest_node_id, 42 )
5999
+ . with_route_hints ( vec ! [ route_hint_1, route_hint_2] ) . unwrap ( )
6000
+ . with_bolt11_features ( channelmanager:: provided_invoice_features ( & config) ) . unwrap ( ) ;
6001
+
6002
+ let route = get_route ( & our_node_id, & payment_params, & network_graph. read_only ( ) ,
6003
+ Some ( & first_hop. iter ( ) . collect :: < Vec < _ > > ( ) ) , amt_msat, Arc :: clone ( & logger) , & scorer, & ( ) ,
6004
+ & random_seed_bytes) . unwrap ( ) ;
6005
+ assert_eq ! ( route. paths. len( ) , 2 ) ;
6006
+ assert ! ( route. paths[ 0 ] . hops. last( ) . unwrap( ) . fee_msat <= max_htlc_msat) ;
6007
+ assert ! ( route. paths[ 1 ] . hops. last( ) . unwrap( ) . fee_msat <= max_htlc_msat) ;
6008
+ assert_eq ! ( route. get_total_amount( ) , amt_msat) ;
6009
+
6010
+ // Re-run but with two first hop channels connected to the same route hint peers that must be
6011
+ // split between.
6012
+ let first_hops = vec ! [
6013
+ get_channel_details( Some ( 42 ) , intermed_node_id, InitFeatures :: from_le_bytes( vec![ 0b11 ] ) , amt_msat - 10 ) ,
6014
+ get_channel_details( Some ( 43 ) , intermed_node_id, InitFeatures :: from_le_bytes( vec![ 0b11 ] ) , amt_msat - 10 ) ,
6015
+ ] ;
6016
+ let route = get_route ( & our_node_id, & payment_params, & network_graph. read_only ( ) ,
6017
+ Some ( & first_hops. iter ( ) . collect :: < Vec < _ > > ( ) ) , amt_msat, Arc :: clone ( & logger) , & scorer, & ( ) ,
6018
+ & random_seed_bytes) . unwrap ( ) ;
6019
+ // TODO: `get_route` returns a suboptimal route here because first hop channels are not sorted
6020
+ // in order of available liquidity during pathfinding.
6021
+ assert_eq ! ( route. paths. len( ) , 3 ) ;
6022
+ assert ! ( route. paths[ 0 ] . hops. last( ) . unwrap( ) . fee_msat <= max_htlc_msat) ;
6023
+ assert ! ( route. paths[ 1 ] . hops. last( ) . unwrap( ) . fee_msat <= max_htlc_msat) ;
6024
+ assert ! ( route. paths[ 2 ] . hops. last( ) . unwrap( ) . fee_msat <= max_htlc_msat) ;
6025
+ assert_eq ! ( route. get_total_amount( ) , amt_msat) ;
6026
+ }
6027
+
5909
6028
#[ test]
5910
6029
fn blinded_route_ser ( ) {
5911
6030
let blinded_path_1 = BlindedPath {
0 commit comments