use crate::{
IdBytes, Peer,
cenc::calculate_peer_id,
kbucket::{ALPHA_VALUE, Distance, K_VALUE, distance},
};
use std::{
collections::btree_map::{BTreeMap, Entry},
iter::FromIterator,
net::SocketAddr,
num::NonZeroUsize,
time::Duration,
};
use tracing::{debug, instrument, trace, warn};
use wasm_timer::Instant;
use super::peers::PeersIterState;
#[derive(Debug, Clone)]
pub(crate) struct ClosestPeersIter {
config: ClosestPeersIterConfig,
pub target: IdBytes,
state: State,
closest_peers: BTreeMap<Distance, IterPeer>,
num_waiting: usize,
}
#[derive(Debug, Clone)]
pub struct ClosestPeersIterConfig {
pub parallelism: NonZeroUsize,
pub num_results: NonZeroUsize,
pub peer_timeout: Duration,
}
impl Default for ClosestPeersIterConfig {
fn default() -> Self {
ClosestPeersIterConfig {
parallelism: ALPHA_VALUE,
num_results: K_VALUE,
peer_timeout: Duration::from_secs(10),
}
}
}
impl ClosestPeersIter {
#[instrument(skip_all)]
pub fn new<I>(target: IdBytes, known_closest_peers: I) -> Self
where
I: IntoIterator<Item = Peer>,
{
Self::with_config(
ClosestPeersIterConfig::default(),
target,
known_closest_peers,
)
}
#[instrument(skip_all)]
pub fn with_config<I>(
config: ClosestPeersIterConfig,
target: IdBytes,
known_closest_peers: I,
) -> Self
where
I: IntoIterator<Item = Peer>,
{
let closest_peers = BTreeMap::from_iter(
known_closest_peers
.into_iter()
.map(|p| {
let id = calculate_peer_id(&p);
let distance = target.distance(id.as_slice());
(
distance,
IterPeer {
id: id.into(),
state: PeerState::NotContacted,
addr: p.addr,
},
)
})
.take(K_VALUE.into()),
);
debug!(
n_closest_peers = closest_peers.len(),
"ClosestPeersIter new"
);
let state = State::Iterating { no_progress: 0 };
trace!("Creating new ClosestPeersIter");
ClosestPeersIter {
config,
target,
state,
closest_peers,
num_waiting: 0,
}
}
#[instrument(skip_all)]
pub fn on_success(
&mut self,
peer: &Peer,
closer_peers: &[Peer],
) -> bool {
if let State::Finished = self.state {
return false;
}
let id = calculate_peer_id(peer);
let distance = distance(&id, self.target.as_ref());
match self.closest_peers.entry(distance) {
Entry::Vacant(..) => {
debug!(
"Responding Peer's distance [{:?}] is not in self.closest_peers",
distance
);
return false;
}
Entry::Occupied(mut e) => match e.get().state {
PeerState::Waiting(..) => {
debug_assert!(self.num_waiting > 0);
self.num_waiting -= 1;
trace!(num_waiting = self.num_waiting, "Good response from peer!");
e.get_mut().state = PeerState::Succeeded;
}
PeerState::Unresponsive => {
trace!("Response from unresponsive peer");
e.get_mut().state = PeerState::Succeeded;
}
state @ (PeerState::NotContacted | PeerState::Failed | PeerState::Succeeded) => {
warn!("Got response from Peer in strange state [{:?}]", state);
return false;
}
},
}
let mut cur_range = distance;
let num_results = self.config.num_results.get();
let furthest_peer = self
.closest_peers
.iter()
.enumerate()
.nth(num_results - 1)
.map(|(_, peer)| peer)
.or_else(|| self.closest_peers.iter().last());
if let Some((dist, _)) = furthest_peer {
cur_range = *dist;
}
let mut progress = self.closest_peers.len() < self.config.num_results.get();
debug!(
iter_progress = progress,
n_closest_peers = closer_peers.len(),
"incorperate peers"
);
for peer in closer_peers {
let peer = IterPeer::from(peer.clone());
let distance = peer.distance(self.target.as_ref());
let is_first_insert = match self.closest_peers.entry(distance) {
Entry::Occupied(_) => false,
Entry::Vacant(entry) => {
entry.insert(peer);
true
}
};
progress = (is_first_insert && distance < cur_range) || progress;
}
self.state = match self.state {
State::Iterating { no_progress } => {
let no_progress = if progress { 0 } else { no_progress + 1 };
if no_progress >= self.config.parallelism.get() {
State::Stalled
} else {
State::Iterating { no_progress }
}
}
State::Stalled => {
if progress {
State::Iterating { no_progress: 0 }
} else {
State::Stalled
}
}
State::Finished => State::Finished,
};
true
}
pub fn on_failure(&mut self, peer: &Peer) -> bool {
if let State::Finished = self.state {
return false;
}
let id = calculate_peer_id(peer);
let distance = distance(&id, self.target.as_ref());
match self.closest_peers.entry(distance) {
Entry::Vacant(_) => return false,
Entry::Occupied(mut e) => match e.get().state {
PeerState::Waiting(_) => {
debug_assert!(self.num_waiting > 0);
self.num_waiting -= 1;
e.get_mut().state = PeerState::Failed
}
PeerState::Unresponsive => e.get_mut().state = PeerState::Failed,
PeerState::NotContacted | PeerState::Failed | PeerState::Succeeded => return false,
},
}
true
}
pub fn waiting(&self) -> impl Iterator<Item = &IterPeer> {
self.closest_peers
.values()
.filter(|peer| matches!(peer.state, PeerState::Waiting { .. }))
}
#[expect(unused)] pub fn num_waiting(&self) -> usize {
self.num_waiting
}
#[expect(unused)] pub fn is_waiting(&self, peer: &IdBytes) -> bool {
self.waiting().any(|p| *peer == p.id)
}
#[instrument(skip_all)]
pub fn next(&mut self, now: Instant) -> PeersIterState {
if let State::Finished = self.state {
trace!("Finished ClosestPeersIter");
return PeersIterState::Finished;
}
let mut result_counter = Some(0);
let at_capacity = self.at_capacity();
trace!(
"Iterating over closest N=[{}] peers",
self.closest_peers.len()
);
for peer in self.closest_peers.values_mut() {
match peer.state {
PeerState::Waiting(timeout) => {
trace!(id = ?peer.id, "Peer Waing");
if now >= timeout {
debug_assert!(self.num_waiting > 0);
self.num_waiting -= 1;
trace!(peer_id = ?peer.id, num_waiting = self.num_waiting, "waiting peer timed out");
peer.state = PeerState::Unresponsive
} else if at_capacity {
trace!(peer_id = ?peer.id, "waiting peer but iterator at capacity");
return PeersIterState::WaitingAtCapacity;
} else {
trace!(peer_id = ?peer.id, "waiting peer but ...? TODO what is this block");
result_counter = None;
}
}
PeerState::Succeeded => {
if let Some(ref mut cnt) = result_counter {
*cnt += 1;
if *cnt >= self.config.num_results.get() {
trace!("Got enough results to finish iterating!!!");
self.state = State::Finished;
trace!("PeerState::Succeeded so PeersIterState::Finished");
return PeersIterState::Finished;
}
}
}
PeerState::NotContacted => {
if !at_capacity {
let timeout = now + self.config.peer_timeout;
peer.state = PeerState::Waiting(timeout);
self.num_waiting += 1;
trace!(peer_id = ? peer.id, num_waiting = self.num_waiting, "uncontacte peer, start wating on it");
return PeersIterState::Waiting(Some(Peer::from(peer.clone())));
} else {
trace!(peer_id = ? peer.id, "uncontacte peer, but iterator at capacity");
return PeersIterState::WaitingAtCapacity;
}
}
state @ (PeerState::Unresponsive | PeerState::Failed) => {
trace!(peer_id = ? peer.id, state = tracing::field::debug(state), "Skip this peer");
}
}
}
if self.num_waiting > 0 {
PeersIterState::Waiting(None)
} else {
self.state = State::Finished;
trace!(
num_waiting = self.num_waiting,
"No more peers waiting PeersIterState::Finished"
);
PeersIterState::Finished
}
}
#[expect(unused)] pub fn finish(&mut self) {
self.state = State::Finished
}
#[expect(unused)] pub fn is_finished(&self) -> bool {
self.state == State::Finished
}
#[expect(unused)] pub fn into_result(self) -> impl Iterator<Item = Peer> {
self.closest_peers
.into_iter()
.filter_map(|(_, peer)| {
if let PeerState::Succeeded = peer.state {
Some(Peer {
id: Some(peer.id.0),
addr: peer.addr,
referrer: None,
})
} else {
None
}
})
.take(self.config.num_results.get())
}
fn at_capacity(&self) -> bool {
match self.state {
State::Stalled => {
self.num_waiting
>= usize::max(self.config.num_results.get(), self.config.parallelism.get())
}
State::Iterating { .. } => self.num_waiting >= self.config.parallelism.get(),
State::Finished => true,
}
}
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
enum State {
Iterating {
no_progress: usize,
},
Stalled,
Finished,
}
#[derive(Debug, Clone)]
pub(crate) struct IterPeer {
id: IdBytes,
state: PeerState,
addr: SocketAddr,
}
impl From<Peer> for IterPeer {
fn from(value: Peer) -> Self {
Self {
id: IdBytes::from(calculate_peer_id(&value)),
state: PeerState::NotContacted,
addr: value.addr,
}
}
}
impl From<IterPeer> for Peer {
fn from(value: IterPeer) -> Self {
Self {
id: Some(value.id.0),
addr: value.addr,
referrer: None,
}
}
}
impl IterPeer {
fn distance(&self, other: &[u8]) -> Distance {
distance(self.id.0.as_slice(), other)
}
}
#[derive(Debug, Copy, Clone)]
enum PeerState {
NotContacted,
Waiting(Instant),
Unresponsive,
Failed,
Succeeded,
}