use crate::NodeAddr;
use crate::config::RoutingMode;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) struct LookupPeerCandidate {
pub(crate) addr: NodeAddr,
pub(crate) can_send: bool,
pub(crate) is_healthy: bool,
pub(crate) is_tree_peer: bool,
pub(crate) may_reach_target: bool,
pub(crate) reply_learned_fallback_allowed: bool,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct LookupPeerPlan {
pub(crate) peers: Vec<NodeAddr>,
pub(crate) tree_match_count: usize,
pub(crate) used_fallback: bool,
}
pub(crate) fn plan_forward_peers(
from: NodeAddr,
origin: NodeAddr,
target: NodeAddr,
routing_mode: RoutingMode,
reply_learned_fallback_enabled: bool,
candidates: &[LookupPeerCandidate],
extra_peer_budget: usize,
) -> LookupPeerPlan {
let mut peers: Vec<NodeAddr> = candidates
.iter()
.filter(|candidate| {
candidate.addr != from
&& candidate.is_tree_peer
&& candidate.can_send
&& candidate.may_reach_target
&& (routing_mode != RoutingMode::ReplyLearned
|| candidate.addr != target
|| candidate.is_healthy)
})
.map(|candidate| candidate.addr)
.collect();
let tree_match_count = peers.len();
if routing_mode == RoutingMode::ReplyLearned && reply_learned_fallback_enabled {
let fallback_budget = extra_peer_budget.saturating_sub(peers.len());
let extra_peers: Vec<NodeAddr> = candidates
.iter()
.filter(|candidate| {
candidate.addr != from
&& candidate.addr != origin
&& candidate.can_send
&& (candidate.addr != target || candidate.is_healthy)
&& candidate.reply_learned_fallback_allowed
})
.map(|candidate| candidate.addr)
.filter(|addr| !peers.contains(addr))
.take(fallback_budget)
.collect();
peers.extend(extra_peers);
} else if peers.is_empty() {
peers = candidates
.iter()
.filter(|candidate| {
candidate.addr != from
&& !candidate.is_tree_peer
&& candidate.can_send
&& candidate.may_reach_target
})
.map(|candidate| candidate.addr)
.collect();
}
let used_fallback = !peers.is_empty()
&& ((routing_mode == RoutingMode::ReplyLearned && peers.len() > tree_match_count)
|| (routing_mode != RoutingMode::ReplyLearned && tree_match_count == 0));
LookupPeerPlan {
peers,
tree_match_count,
used_fallback,
}
}
pub(crate) fn plan_initiate_peers(
routing_mode: RoutingMode,
reply_learned_fallback_enabled: bool,
candidates: &[LookupPeerCandidate],
extra_peer_budget: usize,
) -> LookupPeerPlan {
let mut peers: Vec<NodeAddr> = candidates
.iter()
.filter(|candidate| {
candidate.is_tree_peer && candidate.can_send && candidate.may_reach_target
})
.map(|candidate| candidate.addr)
.collect();
let tree_match_count = peers.len();
if routing_mode == RoutingMode::ReplyLearned && reply_learned_fallback_enabled {
let fallback_budget = extra_peer_budget.saturating_sub(peers.len());
let extra_peers: Vec<NodeAddr> = candidates
.iter()
.filter(|candidate| candidate.can_send && candidate.reply_learned_fallback_allowed)
.map(|candidate| candidate.addr)
.filter(|addr| !peers.contains(addr))
.take(fallback_budget)
.collect();
peers.extend(extra_peers);
}
let used_fallback = routing_mode == RoutingMode::ReplyLearned && peers.len() > tree_match_count;
LookupPeerPlan {
peers,
tree_match_count,
used_fallback,
}
}
#[cfg(test)]
mod tests {
use super::*;
fn addr(val: u8) -> NodeAddr {
let mut bytes = [0u8; 16];
bytes[0] = val;
NodeAddr::from_bytes(bytes)
}
fn candidate(
val: u8,
can_send: bool,
is_healthy: bool,
is_tree_peer: bool,
may_reach_target: bool,
reply_learned_fallback_allowed: bool,
) -> LookupPeerCandidate {
LookupPeerCandidate {
addr: addr(val),
can_send,
is_healthy,
is_tree_peer,
may_reach_target,
reply_learned_fallback_allowed,
}
}
#[test]
fn forward_prefers_sendable_tree_bloom_matches() {
let from = addr(1);
let origin = addr(9);
let candidates = vec![
candidate(1, true, true, true, true, true),
candidate(2, true, true, true, true, false),
candidate(3, false, false, true, true, false),
candidate(4, true, true, false, true, true),
candidate(5, true, true, true, false, true),
];
let plan = plan_forward_peers(
from,
origin,
addr(42),
RoutingMode::Tree,
false,
&candidates,
16,
);
assert_eq!(plan.peers, vec![addr(2)]);
assert_eq!(plan.tree_match_count, 1);
assert!(!plan.used_fallback);
}
#[test]
fn forward_tree_mode_falls_back_to_non_tree_bloom_matches() {
let candidates = vec![
candidate(2, true, true, true, false, false),
candidate(3, true, true, false, true, false),
candidate(4, true, true, false, true, false),
];
let plan = plan_forward_peers(
addr(1),
addr(9),
addr(42),
RoutingMode::Tree,
false,
&candidates,
16,
);
assert_eq!(plan.peers, vec![addr(3), addr(4)]);
assert_eq!(plan.tree_match_count, 0);
assert!(plan.used_fallback);
}
#[test]
fn forward_reply_learned_adds_allowed_live_neighbors_with_budget() {
let candidates = vec![
candidate(2, true, true, true, true, false),
candidate(3, true, true, false, false, true),
candidate(4, true, true, false, false, true),
candidate(5, true, true, false, false, true),
candidate(9, true, true, false, false, true),
];
let plan = plan_forward_peers(
addr(1),
addr(9),
addr(42),
RoutingMode::ReplyLearned,
true,
&candidates,
3,
);
assert_eq!(plan.peers, vec![addr(2), addr(3), addr(4)]);
assert_eq!(plan.tree_match_count, 1);
assert!(plan.used_fallback);
}
#[test]
fn forward_reply_learned_excludes_stale_target_when_fallback_exists() {
let candidates = vec![
candidate(42, true, false, true, true, true),
candidate(3, true, true, false, false, true),
];
let plan = plan_forward_peers(
addr(1),
addr(9),
addr(42),
RoutingMode::ReplyLearned,
true,
&candidates,
16,
);
assert_eq!(plan.peers, vec![addr(3)]);
assert_eq!(plan.tree_match_count, 0);
assert!(plan.used_fallback);
}
#[test]
fn forward_reply_learned_leaves_stale_target_for_shell_probe_when_alone() {
let candidates = vec![candidate(42, true, false, false, false, true)];
let plan = plan_forward_peers(
addr(1),
addr(9),
addr(42),
RoutingMode::ReplyLearned,
true,
&candidates,
16,
);
assert!(plan.peers.is_empty());
assert_eq!(plan.tree_match_count, 0);
assert!(!plan.used_fallback);
}
#[test]
fn initiate_reply_learned_extends_tree_matches() {
let candidates = vec![
candidate(2, true, true, true, true, false),
candidate(3, true, true, false, false, true),
candidate(4, false, false, false, false, true),
];
let plan = plan_initiate_peers(RoutingMode::ReplyLearned, true, &candidates, 16);
assert_eq!(plan.peers, vec![addr(2), addr(3)]);
assert_eq!(plan.tree_match_count, 1);
assert!(plan.used_fallback);
}
}