Skip to content

Commit d8636a3

Browse files
committed
Use NodeCounters NodeIds as the blinded path intro references
The router's `introduction_node_id_cache` is used to cache the `NodeId`s of blinded path introduction points so that we don't have to look them up every time we go around the main router loop. When using it, if the introduction point isn't a public node we then look up the introduction in our first-hops map. In either case, we have to end up with a reference to a `NodeId` that outlives our `dist` map. Here we consolidate both the initial cache building and the first-hops map lookup to one place, storing only a reference to a `NodeId` either in the `NetworkGraph` or in the new `NodeCounters` to get the required lifetime without needing to reference into the first-hops map. We then take this opportunity to avoid `clone`ing the first-hops map entries as we now no longer reference into it.
1 parent 8330453 commit d8636a3

File tree

2 files changed

+72
-73
lines changed

2 files changed

+72
-73
lines changed

lightning/src/blinded_path/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -306,7 +306,7 @@ impl_writeable!(BlindedHop, {
306306

307307
impl Direction {
308308
/// Returns the [`NodeId`] from the inputs corresponding to the direction.
309-
pub fn select_node_id<'a>(&self, node_a: &'a NodeId, node_b: &'a NodeId) -> &'a NodeId {
309+
pub fn select_node_id(&self, node_a: NodeId, node_b: NodeId) -> NodeId {
310310
match self {
311311
Direction::NodeOne => core::cmp::min(node_a, node_b),
312312
Direction::NodeTwo => core::cmp::max(node_a, node_b),

lightning/src/routing/router.rs

Lines changed: 71 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1990,39 +1990,6 @@ where L::Target: Logger {
19901990
return Err(LightningError{err: "Cannot send a payment of 0 msat".to_owned(), action: ErrorAction::IgnoreError});
19911991
}
19921992

1993-
let introduction_node_id_cache = payment_params.payee.blinded_route_hints().iter()
1994-
.map(|(_, path)| path.public_introduction_node_id(network_graph))
1995-
.collect::<Vec<_>>();
1996-
match &payment_params.payee {
1997-
Payee::Clear { route_hints, node_id, .. } => {
1998-
for route in route_hints.iter() {
1999-
for hop in &route.0 {
2000-
if hop.src_node_id == *node_id {
2001-
return Err(LightningError{err: "Route hint cannot have the payee as the source.".to_owned(), action: ErrorAction::IgnoreError});
2002-
}
2003-
}
2004-
}
2005-
},
2006-
Payee::Blinded { route_hints, .. } => {
2007-
if introduction_node_id_cache.iter().all(|introduction_node_id| *introduction_node_id == Some(&our_node_id)) {
2008-
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});
2009-
}
2010-
for ((_, blinded_path), introduction_node_id) in route_hints.iter().zip(introduction_node_id_cache.iter()) {
2011-
if blinded_path.blinded_hops.len() == 0 {
2012-
return Err(LightningError{err: "0-hop blinded path provided".to_owned(), action: ErrorAction::IgnoreError});
2013-
} else if *introduction_node_id == Some(&our_node_id) {
2014-
log_info!(logger, "Got blinded path with ourselves as the introduction node, ignoring");
2015-
} else if blinded_path.blinded_hops.len() == 1 &&
2016-
route_hints
2017-
.iter().zip(introduction_node_id_cache.iter())
2018-
.filter(|((_, p), _)| p.blinded_hops.len() == 1)
2019-
.any(|(_, p_introduction_node_id)| p_introduction_node_id != introduction_node_id)
2020-
{
2021-
return Err(LightningError{err: format!("1-hop blinded paths must all have matching introduction node ids"), action: ErrorAction::IgnoreError});
2022-
}
2023-
}
2024-
}
2025-
}
20261993
let final_cltv_expiry_delta = payment_params.payee.final_cltv_expiry_delta().unwrap_or(0);
20271994
if payment_params.max_total_cltv_expiry_delta <= final_cltv_expiry_delta {
20281995
return Err(LightningError{err: "Can't find a route where the maximum total CLTV expiry delta is below the final CLTV expiry.".to_owned(), action: ErrorAction::IgnoreError});
@@ -2139,10 +2106,10 @@ where L::Target: Logger {
21392106
}
21402107
}
21412108

2142-
// Step (1).
2143-
// Prepare the data we'll use for payee-to-payer search by
2144-
// inserting first hops suggested by the caller as targets.
2145-
// Our search will then attempt to reach them while traversing from the payee node.
2109+
// Step (1). Prep first and last hop targets.
2110+
//
2111+
// First cache all our direct channels so that we can insert them in the heap at startup.
2112+
// Then process any blinded routes, resolving their introduction node and caching it.
21462113
let mut first_hop_targets: HashMap<_, Vec<&ChannelDetails>> =
21472114
hash_map_with_capacity(if first_hops.is_some() { first_hops.as_ref().unwrap().len() } else { 0 });
21482115
if let Some(hops) = first_hops {
@@ -2170,6 +2137,64 @@ where L::Target: Logger {
21702137

21712138
let node_counters = node_counters.build();
21722139

2140+
let introduction_node_id_cache = payment_params.payee.blinded_route_hints().iter()
2141+
.map(|(_, path)| {
2142+
match &path.introduction_node {
2143+
IntroductionNode::NodeId(pubkey) => {
2144+
// Note that this will only return `Some` if the `pubkey` is somehow known to
2145+
// us (i.e. a channel counterparty or in the network graph).
2146+
node_counters.node_counter_from_id(&NodeId::from_pubkey(&pubkey))
2147+
},
2148+
IntroductionNode::DirectedShortChannelId(direction, scid) => {
2149+
let node_id = if let Some(node_id) = path.public_introduction_node_id(network_graph) {
2150+
Some(*node_id)
2151+
} else {
2152+
let counterparty_opt = first_hop_targets.iter().find(|(_, channels)|
2153+
channels
2154+
.iter()
2155+
.any(|details| Some(*scid) == details.get_outbound_payment_scid())
2156+
).map(|(counterparty_node_id, _)| counterparty_node_id);
2157+
counterparty_opt.map(|cp| direction.select_node_id(our_node_id, *cp))
2158+
};
2159+
node_id.and_then(|node_id| node_counters.node_counter_from_id(&node_id),)
2160+
},
2161+
}
2162+
})
2163+
.collect::<Vec<_>>();
2164+
match &payment_params.payee {
2165+
Payee::Clear { route_hints, node_id, .. } => {
2166+
for route in route_hints.iter() {
2167+
for hop in &route.0 {
2168+
if hop.src_node_id == *node_id {
2169+
return Err(LightningError{err: "Route hint cannot have the payee as the source.".to_owned(), action: ErrorAction::IgnoreError});
2170+
}
2171+
}
2172+
}
2173+
},
2174+
Payee::Blinded { route_hints, .. } => {
2175+
if introduction_node_id_cache.iter().all(|info_opt| info_opt.map(|(a, _)| a) == Some(&our_node_id)) {
2176+
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});
2177+
}
2178+
for ((_, blinded_path), info_opt) in route_hints.iter().zip(introduction_node_id_cache.iter()) {
2179+
if blinded_path.blinded_hops.len() == 0 {
2180+
return Err(LightningError{err: "0-hop blinded path provided".to_owned(), action: ErrorAction::IgnoreError});
2181+
}
2182+
if info_opt.is_none() { continue }
2183+
let introduction_node_id = info_opt.unwrap().0;
2184+
if *introduction_node_id == our_node_id {
2185+
log_info!(logger, "Got blinded path with ourselves as the introduction node, ignoring");
2186+
} else if blinded_path.blinded_hops.len() == 1 &&
2187+
route_hints
2188+
.iter().zip(introduction_node_id_cache.iter())
2189+
.filter(|((_, p), _)| p.blinded_hops.len() == 1)
2190+
.any(|(_, iter_info_opt)| iter_info_opt.is_some() && iter_info_opt != info_opt)
2191+
{
2192+
return Err(LightningError{err: format!("1-hop blinded paths must all have matching introduction node ids"), action: ErrorAction::IgnoreError});
2193+
}
2194+
}
2195+
}
2196+
}
2197+
21732198
// The main heap containing all candidate next-hops sorted by their score (max(fee,
21742199
// htlc_minimum)). Ideally this would be a heap which allowed cheap score reduction instead of
21752200
// adding duplicate entries when we find a better path to a given node.
@@ -2671,31 +2696,8 @@ where L::Target: Logger {
26712696
// Only add the hops in this route to our candidate set if either
26722697
// we have a direct channel to the first hop or the first hop is
26732698
// in the regular network graph.
2674-
let source_node_id = match introduction_node_id_cache[hint_idx] {
2675-
Some(node_id) => node_id,
2676-
None => match &hint.1.introduction_node {
2677-
IntroductionNode::NodeId(pubkey) => {
2678-
let node_id = NodeId::from_pubkey(&pubkey);
2679-
match first_hop_targets.get_key_value(&node_id).map(|(key, _)| key) {
2680-
Some(node_id) => node_id,
2681-
None => continue,
2682-
}
2683-
},
2684-
IntroductionNode::DirectedShortChannelId(direction, scid) => {
2685-
let first_hop = first_hop_targets.iter().find(|(_, channels)|
2686-
channels
2687-
.iter()
2688-
.any(|details| Some(*scid) == details.get_outbound_payment_scid())
2689-
);
2690-
match first_hop {
2691-
Some((counterparty_node_id, _)) => {
2692-
direction.select_node_id(&our_node_id, counterparty_node_id)
2693-
},
2694-
None => continue,
2695-
}
2696-
},
2697-
},
2698-
};
2699+
let source_node_opt = introduction_node_id_cache[hint_idx];
2700+
let (source_node_id, _source_node_counter) = if let Some(v) = source_node_opt { v } else { continue };
26992701
if our_node_id == *source_node_id { continue }
27002702
let candidate = if hint.1.blinded_hops.len() == 1 {
27012703
CandidateRouteHop::OneHopBlinded(
@@ -2710,10 +2712,9 @@ where L::Target: Logger {
27102712
{
27112713
path_contribution_msat = hop_used_msat;
27122714
} else { continue }
2713-
if let Some(first_channels) = first_hop_targets.get(source_node_id) {
2714-
let mut first_channels = first_channels.clone();
2715+
if let Some(first_channels) = first_hop_targets.get_mut(source_node_id) {
27152716
sort_first_hop_channels(
2716-
&mut first_channels, &used_liquidities, recommended_value_msat, our_node_pubkey
2717+
first_channels, &used_liquidities, recommended_value_msat, our_node_pubkey
27172718
);
27182719
for details in first_channels {
27192720
let first_hop_candidate = CandidateRouteHop::FirstHop(FirstHopCandidate {
@@ -2811,10 +2812,9 @@ where L::Target: Logger {
28112812
.saturating_add(1);
28122813

28132814
// Searching for a direct channel between last checked hop and first_hop_targets
2814-
if let Some(first_channels) = first_hop_targets.get(target) {
2815-
let mut first_channels = first_channels.clone();
2815+
if let Some(first_channels) = first_hop_targets.get_mut(target) {
28162816
sort_first_hop_channels(
2817-
&mut first_channels, &used_liquidities, recommended_value_msat, our_node_pubkey
2817+
first_channels, &used_liquidities, recommended_value_msat, our_node_pubkey
28182818
);
28192819
for details in first_channels {
28202820
let first_hop_candidate = CandidateRouteHop::FirstHop(FirstHopCandidate {
@@ -2860,10 +2860,9 @@ where L::Target: Logger {
28602860
// Note that we *must* check if the last hop was added as `add_entry`
28612861
// always assumes that the third argument is a node to which we have a
28622862
// path.
2863-
if let Some(first_channels) = first_hop_targets.get(&NodeId::from_pubkey(&hop.src_node_id)) {
2864-
let mut first_channels = first_channels.clone();
2863+
if let Some(first_channels) = first_hop_targets.get_mut(&NodeId::from_pubkey(&hop.src_node_id)) {
28652864
sort_first_hop_channels(
2866-
&mut first_channels, &used_liquidities, recommended_value_msat, our_node_pubkey
2865+
first_channels, &used_liquidities, recommended_value_msat, our_node_pubkey
28672866
);
28682867
for details in first_channels {
28692868
let first_hop_candidate = CandidateRouteHop::FirstHop(FirstHopCandidate {
@@ -7726,7 +7725,7 @@ mod tests {
77267725
};
77277726

77287727
let mut invalid_blinded_path_2 = invalid_blinded_path.clone();
7729-
invalid_blinded_path_2.introduction_node = IntroductionNode::NodeId(ln_test_utils::pubkey(45));
7728+
invalid_blinded_path_2.introduction_node = IntroductionNode::NodeId(nodes[3]);
77307729
let payment_params = PaymentParameters::blinded(vec![
77317730
(blinded_payinfo.clone(), invalid_blinded_path.clone()),
77327731
(blinded_payinfo.clone(), invalid_blinded_path_2)]);

0 commit comments

Comments
 (0)