@@ -1635,6 +1635,79 @@ impl<'a> NodeCounters<'a> {
16351635 }
16361636}
16371637
1638+ /// Calculates the introduction point for each blinded path in the given [`PaymentParameters`], if
1639+ /// they can be found.
1640+ fn calculate_blinded_path_intro_points < ' a , L : Deref > (
1641+ payment_params : & PaymentParameters , node_counters : & ' a NodeCounters ,
1642+ network_graph : & ReadOnlyNetworkGraph , logger : & L , our_node_id : NodeId ,
1643+ first_hop_targets : & HashMap < NodeId , ( Vec < & ChannelDetails > , u32 ) > ,
1644+ ) -> Result < Vec < Option < ( & ' a NodeId , u32 ) > > , LightningError >
1645+ where L :: Target : Logger {
1646+ let introduction_node_id_cache = payment_params. payee . blinded_route_hints ( ) . iter ( )
1647+ . map ( |( _, path) | {
1648+ match & path. introduction_node {
1649+ IntroductionNode :: NodeId ( pubkey) => {
1650+ // Note that this will only return `Some` if the `pubkey` is somehow known to
1651+ // us (i.e. a channel counterparty or in the network graph).
1652+ node_counters. node_counter_from_id ( & NodeId :: from_pubkey ( & pubkey) )
1653+ } ,
1654+ IntroductionNode :: DirectedShortChannelId ( direction, scid) => {
1655+ path. public_introduction_node_id ( network_graph)
1656+ . map ( |node_id_ref| * node_id_ref)
1657+ . or_else ( || {
1658+ first_hop_targets. iter ( ) . find ( |( _, ( channels, _) ) |
1659+ channels
1660+ . iter ( )
1661+ . any ( |details| Some ( * scid) == details. get_outbound_payment_scid ( ) )
1662+ ) . map ( |( cp, _) | direction. select_node_id ( our_node_id, * cp) )
1663+ } )
1664+ . and_then ( |node_id| node_counters. node_counter_from_id ( & node_id) )
1665+ } ,
1666+ }
1667+ } )
1668+ . collect :: < Vec < _ > > ( ) ;
1669+ match & payment_params. payee {
1670+ Payee :: Clear { route_hints, node_id, .. } => {
1671+ for route in route_hints. iter ( ) {
1672+ for hop in & route. 0 {
1673+ if hop. src_node_id == * node_id {
1674+ return Err ( LightningError {
1675+ err : "Route hint cannot have the payee as the source." . to_owned ( ) ,
1676+ action : ErrorAction :: IgnoreError
1677+ } ) ;
1678+ }
1679+ }
1680+ }
1681+ } ,
1682+ Payee :: Blinded { route_hints, .. } => {
1683+ if introduction_node_id_cache. iter ( ) . all ( |info_opt| info_opt. map ( |( a, _) | a) == Some ( & our_node_id) ) {
1684+ return Err ( LightningError { err : "Cannot generate a route to blinded paths if we are the introduction node to all of them" . to_owned ( ) , action : ErrorAction :: IgnoreError } ) ;
1685+ }
1686+ for ( ( _, blinded_path) , info_opt) in route_hints. iter ( ) . zip ( introduction_node_id_cache. iter ( ) ) {
1687+ if blinded_path. blinded_hops . len ( ) == 0 {
1688+ return Err ( LightningError { err : "0-hop blinded path provided" . to_owned ( ) , action : ErrorAction :: IgnoreError } ) ;
1689+ }
1690+ let introduction_node_id = match info_opt {
1691+ None => continue ,
1692+ Some ( info) => info. 0 ,
1693+ } ;
1694+ if * introduction_node_id == our_node_id {
1695+ log_info ! ( logger, "Got blinded path with ourselves as the introduction node, ignoring" ) ;
1696+ } else if blinded_path. blinded_hops . len ( ) == 1 &&
1697+ route_hints
1698+ . iter ( ) . zip ( introduction_node_id_cache. iter ( ) )
1699+ . filter ( |( ( _, p) , _) | p. blinded_hops . len ( ) == 1 )
1700+ . any ( |( _, iter_info_opt) | iter_info_opt. is_some ( ) && iter_info_opt != info_opt)
1701+ {
1702+ return Err ( LightningError { err : format ! ( "1-hop blinded paths must all have matching introduction node ids" ) , action : ErrorAction :: IgnoreError } ) ;
1703+ }
1704+ }
1705+ }
1706+ }
1707+
1708+ Ok ( introduction_node_id_cache)
1709+ }
1710+
16381711#[ inline]
16391712fn max_htlc_from_capacity ( capacity : EffectiveCapacity , max_channel_saturation_power_of_half : u8 ) -> u64 {
16401713 let saturation_shift: u32 = max_channel_saturation_power_of_half as u32 ;
@@ -2183,67 +2256,9 @@ where L::Target: Logger {
21832256
21842257 let node_counters = node_counter_builder. build ( ) ;
21852258
2186- let introduction_node_id_cache = payment_params. payee . blinded_route_hints ( ) . iter ( )
2187- . map ( |( _, path) | {
2188- match & path. introduction_node {
2189- IntroductionNode :: NodeId ( pubkey) => {
2190- // Note that this will only return `Some` if the `pubkey` is somehow known to
2191- // us (i.e. a channel counterparty or in the network graph).
2192- node_counters. node_counter_from_id ( & NodeId :: from_pubkey ( & pubkey) )
2193- } ,
2194- IntroductionNode :: DirectedShortChannelId ( direction, scid) => {
2195- path. public_introduction_node_id ( network_graph)
2196- . map ( |node_id_ref| * node_id_ref)
2197- . or_else ( || {
2198- first_hop_targets. iter ( ) . find ( |( _, ( channels, _) ) |
2199- channels
2200- . iter ( )
2201- . any ( |details| Some ( * scid) == details. get_outbound_payment_scid ( ) )
2202- ) . map ( |( cp, _) | direction. select_node_id ( our_node_id, * cp) )
2203- } )
2204- . and_then ( |node_id| node_counters. node_counter_from_id ( & node_id) )
2205- } ,
2206- }
2207- } )
2208- . collect :: < Vec < _ > > ( ) ;
2209- match & payment_params. payee {
2210- Payee :: Clear { route_hints, node_id, .. } => {
2211- for route in route_hints. iter ( ) {
2212- for hop in & route. 0 {
2213- if hop. src_node_id == * node_id {
2214- return Err ( LightningError {
2215- err : "Route hint cannot have the payee as the source." . to_owned ( ) ,
2216- action : ErrorAction :: IgnoreError
2217- } ) ;
2218- }
2219- }
2220- }
2221- } ,
2222- Payee :: Blinded { route_hints, .. } => {
2223- if introduction_node_id_cache. iter ( ) . all ( |info_opt| info_opt. map ( |( a, _) | a) == Some ( & our_node_id) ) {
2224- return Err ( LightningError { err : "Cannot generate a route to blinded paths if we are the introduction node to all of them" . to_owned ( ) , action : ErrorAction :: IgnoreError } ) ;
2225- }
2226- for ( ( _, blinded_path) , info_opt) in route_hints. iter ( ) . zip ( introduction_node_id_cache. iter ( ) ) {
2227- if blinded_path. blinded_hops . len ( ) == 0 {
2228- return Err ( LightningError { err : "0-hop blinded path provided" . to_owned ( ) , action : ErrorAction :: IgnoreError } ) ;
2229- }
2230- let introduction_node_id = match info_opt {
2231- None => continue ,
2232- Some ( info) => info. 0 ,
2233- } ;
2234- if * introduction_node_id == our_node_id {
2235- log_info ! ( logger, "Got blinded path with ourselves as the introduction node, ignoring" ) ;
2236- } else if blinded_path. blinded_hops . len ( ) == 1 &&
2237- route_hints
2238- . iter ( ) . zip ( introduction_node_id_cache. iter ( ) )
2239- . filter ( |( ( _, p) , _) | p. blinded_hops . len ( ) == 1 )
2240- . any ( |( _, iter_info_opt) | iter_info_opt. is_some ( ) && iter_info_opt != info_opt)
2241- {
2242- return Err ( LightningError { err : format ! ( "1-hop blinded paths must all have matching introduction node ids" ) , action : ErrorAction :: IgnoreError } ) ;
2243- }
2244- }
2245- }
2246- }
2259+ let introduction_node_id_cache = calculate_blinded_path_intro_points (
2260+ & payment_params, & node_counters, network_graph, & logger, our_node_id, & first_hop_targets,
2261+ ) ?;
22472262
22482263 // The main heap containing all candidate next-hops sorted by their score (max(fee,
22492264 // htlc_minimum)). Ideally this would be a heap which allowed cheap score reduction instead of
0 commit comments