/// decrease as well. Thus, we have to explicitly track which nodes have been processed and
/// avoid processing them again.
was_processed: bool,
+ /// When processing a node as the next best-score candidate, we want to quickly check if it is
+ /// a direct counterparty of ours, using our local channel information immediately if we can.
+ ///
+ /// In order to do so efficiently, we cache whether a node is a direct counterparty here at the
+ /// start of a route-finding pass. Unlike all other fields in this struct, this field is never
+ /// updated after being initialized - it is set at the start of a route-finding pass and only
+ /// read thereafter.
+ is_first_hop_target: bool,
/// Used to compare channels when choosing the for routing.
/// Includes paying for the use of a hop and the following hops, as well as
/// an estimated cost of reaching this hop.
.field("source_node_id", &self.candidate.source())
.field("target_node_id", &self.candidate.target())
.field("short_channel_id", &self.candidate.short_channel_id())
+ .field("is_first_hop_target", &self.is_first_hop_target)
.field("total_fee_msat", &self.total_fee_msat)
.field("next_hops_fee_msat", &self.next_hops_fee_msat)
.field("hop_use_fee_msat", &self.hop_use_fee_msat)
path_htlc_minimum_msat,
path_penalty_msat: u64::max_value(),
was_processed: false,
+ is_first_hop_target: false,
#[cfg(all(not(ldk_bench), any(test, fuzzing)))]
value_contribution_msat,
});
let fee_to_target_msat;
let next_hops_path_htlc_minimum_msat;
let next_hops_path_penalty_msat;
+ let is_first_hop_target;
let skip_node = if let Some(elem) = &mut dist[$node.node_counter as usize] {
let was_processed = elem.was_processed;
elem.was_processed = true;
fee_to_target_msat = elem.total_fee_msat;
next_hops_path_htlc_minimum_msat = elem.path_htlc_minimum_msat;
next_hops_path_penalty_msat = elem.path_penalty_msat;
+ is_first_hop_target = elem.is_first_hop_target;
was_processed
} else {
// Entries are added to dist in add_entry!() when there is a channel from a node.
fee_to_target_msat = 0;
next_hops_path_htlc_minimum_msat = 0;
next_hops_path_penalty_msat = 0;
+ is_first_hop_target = false;
false
};
if !skip_node {
- if let Some((first_channels, peer_node_counter)) = first_hop_targets.get(&$node_id) {
- for details in first_channels {
- debug_assert_eq!(*peer_node_counter, $node.node_counter);
- let candidate = CandidateRouteHop::FirstHop(FirstHopCandidate {
- details, payer_node_id: &our_node_id, payer_node_counter,
- target_node_counter: $node.node_counter,
- });
- add_entry!(&candidate, fee_to_target_msat,
- $next_hops_value_contribution,
- next_hops_path_htlc_minimum_msat, next_hops_path_penalty_msat,
- $next_hops_cltv_delta, $next_hops_path_length);
+ if is_first_hop_target {
+ if let Some((first_channels, peer_node_counter)) = first_hop_targets.get(&$node_id) {
+ for details in first_channels {
+ debug_assert_eq!(*peer_node_counter, $node.node_counter);
+ let candidate = CandidateRouteHop::FirstHop(FirstHopCandidate {
+ details, payer_node_id: &our_node_id, payer_node_counter,
+ target_node_counter: $node.node_counter,
+ });
+ add_entry!(&candidate, fee_to_target_msat,
+ $next_hops_value_contribution,
+ next_hops_path_htlc_minimum_msat, next_hops_path_penalty_msat,
+ $next_hops_cltv_delta, $next_hops_path_length);
+ }
}
}
for e in dist.iter_mut() {
*e = None;
}
+ for (_, (chans, peer_node_counter)) in first_hop_targets.iter() {
+ // In order to avoid looking up whether each node is a first-hop target, we store a
+ // dummy entry in dist for each first-hop target, allowing us to do this lookup for
+ // free since we're already looking at the `was_processed` flag.
+ //
+ // Note that all the fields (except `is_first_hop_target`) will be overwritten whenever
+ // we find a path to the target, so are left as dummies here.
+ dist[*peer_node_counter as usize] = Some(PathBuildingHop {
+ candidate: CandidateRouteHop::FirstHop(FirstHopCandidate {
+ details: &chans[0],
+ payer_node_id: &our_node_id,
+ target_node_counter: u32::max_value(),
+ payer_node_counter: u32::max_value(),
+ }),
+ fee_msat: 0,
+ next_hops_fee_msat: u64::max_value(),
+ hop_use_fee_msat: u64::max_value(),
+ total_fee_msat: u64::max_value(),
+ path_htlc_minimum_msat: u64::max_value(),
+ path_penalty_msat: u64::max_value(),
+ was_processed: false,
+ is_first_hop_target: true,
+ #[cfg(all(not(ldk_bench), any(test, fuzzing)))]
+ value_contribution_msat: 0,
+ });
+ }
hit_minimum_limit = false;
// If first hop is a private channel and the only way to reach the payee, this is the only