-
Notifications
You must be signed in to change notification settings - Fork 409
Misc routing optimization #2803
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
df9c15d
3e90240
98f9e8b
9566c27
bed1fb0
f689e01
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1772,6 +1772,14 @@ struct PathBuildingHop<'a> { | |
/// 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. | ||
|
@@ -1810,6 +1818,7 @@ impl<'a> core::fmt::Debug for PathBuildingHop<'a> { | |
.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) | ||
|
@@ -2516,6 +2525,7 @@ where L::Target: Logger { | |
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, | ||
}); | ||
|
@@ -2679,12 +2689,14 @@ where L::Target: Logger { | |
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. | ||
|
@@ -2695,21 +2707,24 @@ where L::Target: Logger { | |
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); | ||
} | ||
Comment on lines
+2719
to
+2731
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The handling of first hop targets within the routing algorithm is complex and should be carefully reviewed to ensure correctness. Review the logic for handling first hop targets to ensure correctness. Consider adding more comments to explain how first hop targets are processed in the routing algorithm. |
||
} | ||
} | ||
|
||
|
@@ -2756,6 +2771,32 @@ where L::Target: Logger { | |
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would it be worth adding There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, it would be, but I'm not sure I know how to write such an assertion? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess we could check that if one field is updated, all others are too? But maybe not worth it? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, I'm not sure how to check on a per-field basis. The likely failure case is we add another field and fail to update it when relevant, but I'm not aware of a way to iterate over the fields of a struct? |
||
// 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 | ||
|
Uh oh!
There was an error while loading. Please reload this page.