@@ -951,7 +951,10 @@ impl<'a> CandidateRouteHop<'a> {
951951 liquidity_msat : details. next_outbound_htlc_limit_msat ,
952952 } ,
953953 CandidateRouteHop :: PublicHop { info, .. } => info. effective_capacity ( ) ,
954- CandidateRouteHop :: PrivateHop { .. } => EffectiveCapacity :: Infinite ,
954+ CandidateRouteHop :: PrivateHop { hint : RouteHintHop { htlc_maximum_msat : Some ( max) , .. } } =>
955+ EffectiveCapacity :: HintMaxHTLC { amount_msat : * max } ,
956+ CandidateRouteHop :: PrivateHop { hint : RouteHintHop { htlc_maximum_msat : None , .. } } =>
957+ EffectiveCapacity :: Infinite ,
955958 }
956959 }
957960}
@@ -965,6 +968,9 @@ fn max_htlc_from_capacity(capacity: EffectiveCapacity, max_channel_saturation_po
965968 EffectiveCapacity :: Unknown => EffectiveCapacity :: Unknown . as_msat ( ) ,
966969 EffectiveCapacity :: AdvertisedMaxHTLC { amount_msat } =>
967970 amount_msat. checked_shr ( saturation_shift) . unwrap_or ( 0 ) ,
971+ // Treat htlc_maximum_msat from a route hint as an exact liquidity amount, since the invoice is
972+ // expected to have been generated from up-to-date capacity information.
973+ EffectiveCapacity :: HintMaxHTLC { amount_msat } => amount_msat,
968974 EffectiveCapacity :: Total { capacity_msat, htlc_maximum_msat } =>
969975 cmp:: min ( capacity_msat. checked_shr ( saturation_shift) . unwrap_or ( 0 ) , htlc_maximum_msat) ,
970976 }
@@ -1470,8 +1476,9 @@ where L::Target: Logger {
14701476 ( $candidate: expr, $src_node_id: expr, $dest_node_id: expr, $next_hops_fee_msat: expr,
14711477 $next_hops_value_contribution: expr, $next_hops_path_htlc_minimum_msat: expr,
14721478 $next_hops_path_penalty_msat: expr, $next_hops_cltv_delta: expr, $next_hops_path_length: expr ) => { {
1473- // We "return" whether we updated the path at the end, via this:
1474- let mut did_add_update_path_to_src_node = false ;
1479+ // We "return" whether we updated the path at the end, and how much we can route via
1480+ // this channel, via this:
1481+ let mut did_add_update_path_to_src_node = None ;
14751482 // Channels to self should not be used. This is more of belt-and-suspenders, because in
14761483 // practice these cases should be caught earlier:
14771484 // - for regular channels at channel announcement (TODO)
@@ -1652,7 +1659,7 @@ where L::Target: Logger {
16521659 {
16531660 old_entry. value_contribution_msat = value_contribution_msat;
16541661 }
1655- did_add_update_path_to_src_node = true ;
1662+ did_add_update_path_to_src_node = Some ( value_contribution_msat ) ;
16561663 } else if old_entry. was_processed && new_cost < old_cost {
16571664 #[ cfg( all( not( feature = "_bench_unstable" ) , any( test, fuzzing) ) ) ]
16581665 {
@@ -1773,7 +1780,7 @@ where L::Target: Logger {
17731780 for details in first_channels {
17741781 let candidate = CandidateRouteHop :: FirstHop { details } ;
17751782 let added = add_entry ! ( candidate, our_node_id, payee, 0 , path_value_msat,
1776- 0 , 0u64 , 0 , 0 ) ;
1783+ 0 , 0u64 , 0 , 0 ) . is_some ( ) ;
17771784 log_trace ! ( logger, "{} direct route to payee via SCID {}" ,
17781785 if added { "Added" } else { "Skipped" } , candidate. short_channel_id( ) ) ;
17791786 }
@@ -1820,6 +1827,7 @@ where L::Target: Logger {
18201827 let mut aggregate_next_hops_path_penalty_msat: u64 = 0 ;
18211828 let mut aggregate_next_hops_cltv_delta: u32 = 0 ;
18221829 let mut aggregate_next_hops_path_length: u8 = 0 ;
1830+ let mut aggregate_path_contribution_msat = path_value_msat;
18231831
18241832 for ( idx, ( hop, prev_hop_id) ) in hop_iter. zip ( prev_hop_iter) . enumerate ( ) {
18251833 let source = NodeId :: from_pubkey ( & hop. src_node_id ) ;
@@ -1833,10 +1841,13 @@ where L::Target: Logger {
18331841 } )
18341842 . unwrap_or_else ( || CandidateRouteHop :: PrivateHop { hint : hop } ) ;
18351843
1836- 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) {
1844+ if let Some ( hop_used_msat) = add_entry ! ( candidate, source, target,
1845+ aggregate_next_hops_fee_msat, aggregate_path_contribution_msat,
1846+ aggregate_next_hops_path_htlc_minimum_msat, aggregate_next_hops_path_penalty_msat,
1847+ aggregate_next_hops_cltv_delta, aggregate_next_hops_path_length)
1848+ {
1849+ aggregate_path_contribution_msat = hop_used_msat;
1850+ } else {
18401851 // If this hop was not used then there is no use checking the preceding
18411852 // hops in the RouteHint. We can break by just searching for a direct
18421853 // channel between last checked hop and first_hop_targets.
@@ -1865,12 +1876,11 @@ where L::Target: Logger {
18651876 // Searching for a direct channel between last checked hop and first_hop_targets
18661877 if let Some ( first_channels) = first_hop_targets. get ( & NodeId :: from_pubkey ( & prev_hop_id) ) {
18671878 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) ;
1879+ let first_hop_candidate = CandidateRouteHop :: FirstHop { details } ;
1880+ add_entry ! ( first_hop_candidate, our_node_id, NodeId :: from_pubkey( & prev_hop_id) ,
1881+ aggregate_next_hops_fee_msat, aggregate_path_contribution_msat,
1882+ aggregate_next_hops_path_htlc_minimum_msat, aggregate_next_hops_path_penalty_msat,
1883+ aggregate_next_hops_cltv_delta, aggregate_next_hops_path_length) ;
18741884 }
18751885 }
18761886
@@ -1905,10 +1915,11 @@ where L::Target: Logger {
19051915 // path.
19061916 if let Some ( first_channels) = first_hop_targets. get ( & NodeId :: from_pubkey ( & hop. src_node_id ) ) {
19071917 for details in first_channels {
1908- let candidate = CandidateRouteHop :: FirstHop { details } ;
1909- add_entry ! ( candidate , our_node_id,
1918+ let first_hop_candidate = CandidateRouteHop :: FirstHop { details } ;
1919+ add_entry ! ( first_hop_candidate , our_node_id,
19101920 NodeId :: from_pubkey( & hop. src_node_id) ,
1911- aggregate_next_hops_fee_msat, path_value_msat,
1921+ aggregate_next_hops_fee_msat,
1922+ aggregate_path_contribution_msat,
19121923 aggregate_next_hops_path_htlc_minimum_msat,
19131924 aggregate_next_hops_path_penalty_msat,
19141925 aggregate_next_hops_cltv_delta,
@@ -5906,6 +5917,130 @@ mod tests {
59065917 assert ! ( route. is_ok( ) ) ;
59075918 }
59085919
5920+ #[ test]
5921+ fn abide_by_route_hint_max_htlc ( ) {
5922+ // Check that we abide by any htlc_maximum_msat provided in the route hints of the payment
5923+ // params in the final route.
5924+ let ( secp_ctx, network_graph, _, _, logger) = build_graph ( ) ;
5925+ let netgraph = network_graph. read_only ( ) ;
5926+ let ( _, our_id, _, nodes) = get_nodes ( & secp_ctx) ;
5927+ let scorer = ln_test_utils:: TestScorer :: new ( ) ;
5928+ let keys_manager = ln_test_utils:: TestKeysInterface :: new ( & [ 0u8 ; 32 ] , Network :: Testnet ) ;
5929+ let random_seed_bytes = keys_manager. get_secure_random_bytes ( ) ;
5930+ let config = UserConfig :: default ( ) ;
5931+
5932+ let max_htlc_msat = 50_000 ;
5933+ let route_hint_1 = RouteHint ( vec ! [ RouteHintHop {
5934+ src_node_id: nodes[ 2 ] ,
5935+ short_channel_id: 42 ,
5936+ fees: RoutingFees {
5937+ base_msat: 100 ,
5938+ proportional_millionths: 0 ,
5939+ } ,
5940+ cltv_expiry_delta: 10 ,
5941+ htlc_minimum_msat: None ,
5942+ htlc_maximum_msat: Some ( max_htlc_msat) ,
5943+ } ] ) ;
5944+ let dest_node_id = ln_test_utils:: pubkey ( 42 ) ;
5945+ let payment_params = PaymentParameters :: from_node_id ( dest_node_id, 42 )
5946+ . with_route_hints ( vec ! [ route_hint_1. clone( ) ] ) . unwrap ( )
5947+ . with_bolt11_features ( channelmanager:: provided_invoice_features ( & config) ) . unwrap ( ) ;
5948+
5949+ // Make sure we'll error if our route hints don't have enough liquidity according to their
5950+ // htlc_maximum_msat.
5951+ if let Err ( LightningError { err, action : ErrorAction :: IgnoreError } ) = get_route ( & our_id,
5952+ & payment_params, & netgraph, None , max_htlc_msat + 1 , Arc :: clone ( & logger) , & scorer, & ( ) ,
5953+ & random_seed_bytes)
5954+ {
5955+ assert_eq ! ( err, "Failed to find a sufficient route to the given destination" ) ;
5956+ } else { panic ! ( ) ; }
5957+
5958+ // Make sure we'll split an MPP payment across route hints if their htlc_maximum_msat warrants.
5959+ let mut route_hint_2 = route_hint_1. clone ( ) ;
5960+ route_hint_2. 0 [ 0 ] . short_channel_id = 43 ;
5961+ let payment_params = PaymentParameters :: from_node_id ( dest_node_id, 42 )
5962+ . with_route_hints ( vec ! [ route_hint_1, route_hint_2] ) . unwrap ( )
5963+ . with_bolt11_features ( channelmanager:: provided_invoice_features ( & config) ) . unwrap ( ) ;
5964+ let route = get_route ( & our_id, & payment_params, & netgraph, None , max_htlc_msat + 1 ,
5965+ Arc :: clone ( & logger) , & scorer, & ( ) , & random_seed_bytes) . unwrap ( ) ;
5966+ assert_eq ! ( route. paths. len( ) , 2 ) ;
5967+ assert ! ( route. paths[ 0 ] . hops. last( ) . unwrap( ) . fee_msat <= max_htlc_msat) ;
5968+ assert ! ( route. paths[ 1 ] . hops. last( ) . unwrap( ) . fee_msat <= max_htlc_msat) ;
5969+ }
5970+
5971+ #[ test]
5972+ fn direct_channel_to_hints_with_max_htlc ( ) {
5973+ // Check that if we have a first hop channel peer that's connected to multiple provided route
5974+ // hints, that we properly split the payment between the route hints if needed.
5975+ let logger = Arc :: new ( ln_test_utils:: TestLogger :: new ( ) ) ;
5976+ let network_graph = Arc :: new ( NetworkGraph :: new ( Network :: Testnet , Arc :: clone ( & logger) ) ) ;
5977+ let scorer = ln_test_utils:: TestScorer :: new ( ) ;
5978+ let keys_manager = ln_test_utils:: TestKeysInterface :: new ( & [ 0u8 ; 32 ] , Network :: Testnet ) ;
5979+ let random_seed_bytes = keys_manager. get_secure_random_bytes ( ) ;
5980+ let config = UserConfig :: default ( ) ;
5981+
5982+ let our_node_id = ln_test_utils:: pubkey ( 42 ) ;
5983+ let intermed_node_id = ln_test_utils:: pubkey ( 43 ) ;
5984+ let first_hop = vec ! [ get_channel_details( Some ( 42 ) , intermed_node_id, InitFeatures :: from_le_bytes( vec![ 0b11 ] ) , 10_000_000 ) ] ;
5985+
5986+ let amt_msat = 900_000 ;
5987+ let max_htlc_msat = 500_000 ;
5988+ let route_hint_1 = RouteHint ( vec ! [ RouteHintHop {
5989+ src_node_id: intermed_node_id,
5990+ short_channel_id: 44 ,
5991+ fees: RoutingFees {
5992+ base_msat: 100 ,
5993+ proportional_millionths: 0 ,
5994+ } ,
5995+ cltv_expiry_delta: 10 ,
5996+ htlc_minimum_msat: None ,
5997+ htlc_maximum_msat: Some ( max_htlc_msat) ,
5998+ } , RouteHintHop {
5999+ src_node_id: intermed_node_id,
6000+ short_channel_id: 45 ,
6001+ fees: RoutingFees {
6002+ base_msat: 100 ,
6003+ proportional_millionths: 0 ,
6004+ } ,
6005+ cltv_expiry_delta: 10 ,
6006+ htlc_minimum_msat: None ,
6007+ // Check that later route hint max htlcs don't override earlier ones
6008+ htlc_maximum_msat: Some ( max_htlc_msat - 50 ) ,
6009+ } ] ) ;
6010+ let mut route_hint_2 = route_hint_1. clone ( ) ;
6011+ route_hint_2. 0 [ 0 ] . short_channel_id = 46 ;
6012+ route_hint_2. 0 [ 1 ] . short_channel_id = 47 ;
6013+ let dest_node_id = ln_test_utils:: pubkey ( 44 ) ;
6014+ let payment_params = PaymentParameters :: from_node_id ( dest_node_id, 42 )
6015+ . with_route_hints ( vec ! [ route_hint_1, route_hint_2] ) . unwrap ( )
6016+ . with_bolt11_features ( channelmanager:: provided_invoice_features ( & config) ) . unwrap ( ) ;
6017+
6018+ let route = get_route ( & our_node_id, & payment_params, & network_graph. read_only ( ) ,
6019+ Some ( & first_hop. iter ( ) . collect :: < Vec < _ > > ( ) ) , amt_msat, Arc :: clone ( & logger) , & scorer, & ( ) ,
6020+ & random_seed_bytes) . unwrap ( ) ;
6021+ assert_eq ! ( route. paths. len( ) , 2 ) ;
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_eq ! ( route. get_total_amount( ) , amt_msat) ;
6025+
6026+ // Re-run but with two first hop channels connected to the same route hint peers that must be
6027+ // split between.
6028+ let first_hops = vec ! [
6029+ get_channel_details( Some ( 42 ) , intermed_node_id, InitFeatures :: from_le_bytes( vec![ 0b11 ] ) , amt_msat - 10 ) ,
6030+ get_channel_details( Some ( 43 ) , intermed_node_id, InitFeatures :: from_le_bytes( vec![ 0b11 ] ) , amt_msat - 10 ) ,
6031+ ] ;
6032+ let route = get_route ( & our_node_id, & payment_params, & network_graph. read_only ( ) ,
6033+ Some ( & first_hops. iter ( ) . collect :: < Vec < _ > > ( ) ) , amt_msat, Arc :: clone ( & logger) , & scorer, & ( ) ,
6034+ & random_seed_bytes) . unwrap ( ) ;
6035+ // TODO: `get_route` returns a suboptimal route here because first hop channels are not
6036+ // resorted on the fly when processing route hints.
6037+ assert_eq ! ( route. paths. len( ) , 3 ) ;
6038+ assert ! ( route. paths[ 0 ] . hops. last( ) . unwrap( ) . fee_msat <= max_htlc_msat) ;
6039+ assert ! ( route. paths[ 1 ] . hops. last( ) . unwrap( ) . fee_msat <= max_htlc_msat) ;
6040+ assert ! ( route. paths[ 2 ] . hops. last( ) . unwrap( ) . fee_msat <= max_htlc_msat) ;
6041+ assert_eq ! ( route. get_total_amount( ) , amt_msat) ;
6042+ }
6043+
59096044 #[ test]
59106045 fn blinded_route_ser ( ) {
59116046 let blinded_path_1 = BlindedPath {
0 commit comments