use super::{
member_info::{MemberInfo, PeerState},
EldersInfo,
};
use crate::{consensus::Proven, peer::Peer};
use itertools::Itertools;
use serde::{Deserialize, Serialize};
use std::{
cmp::Ordering,
collections::{
btree_map::{self, Entry},
BTreeMap,
},
hash::{Hash, Hasher},
mem,
};
use xor_name::{Prefix, XorName};
#[derive(Clone, Default, Debug, Eq, Serialize, Deserialize)]
pub(crate) struct SectionPeers {
members: BTreeMap<XorName, Proven<MemberInfo>>,
}
impl SectionPeers {
pub fn all(&self) -> impl Iterator<Item = &MemberInfo> {
self.members.values().map(|info| &info.value)
}
pub fn joined(&self) -> impl Iterator<Item = &MemberInfo> {
self.members
.values()
.map(|info| &info.value)
.filter(|member| member.state == PeerState::Joined)
}
pub fn mature(&self) -> impl Iterator<Item = &Peer> {
self.joined()
.filter(|info| info.is_mature())
.map(|info| &info.peer)
}
pub fn get(&self, name: &XorName) -> Option<&MemberInfo> {
self.members.get(name).map(|info| &info.value)
}
pub fn get_proven(&self, name: &XorName) -> Option<&Proven<MemberInfo>> {
self.members.get(name)
}
pub fn elder_candidates(&self, elder_size: usize, current_elders: &EldersInfo) -> Vec<Peer> {
elder_candidates(
elder_size,
current_elders,
self.members
.values()
.filter(|info| is_active(&info.value, current_elders)),
)
}
pub fn elder_candidates_matching_prefix(
&self,
prefix: &Prefix,
elder_size: usize,
current_elders: &EldersInfo,
) -> Vec<Peer> {
elder_candidates(
elder_size,
current_elders,
self.members.values().filter(|info| {
info.value.state == PeerState::Joined && prefix.matches(info.value.peer.name())
}),
)
}
pub fn is_joined(&self, name: &XorName) -> bool {
self.members
.get(name)
.map(|info| info.value.state == PeerState::Joined)
.unwrap_or(false)
}
pub fn update(&mut self, new_info: Proven<MemberInfo>) -> bool {
match self.members.entry(*new_info.value.peer.name()) {
Entry::Vacant(entry) => {
let _ = entry.insert(new_info);
true
}
Entry::Occupied(mut entry) => {
match (entry.get().value.state, new_info.value.state) {
(PeerState::Joined, PeerState::Joined)
if new_info.value.peer.age() > entry.get().value.peer.age() => {}
(PeerState::Joined, PeerState::Left)
| (PeerState::Joined, PeerState::Relocated(_))
| (PeerState::Relocated(_), PeerState::Left) => {}
_ => return false,
};
let _ = entry.insert(new_info);
true
}
}
}
pub fn prune_not_matching(&mut self, prefix: &Prefix) {
self.members = mem::take(&mut self.members)
.into_iter()
.filter(|(name, _)| prefix.matches(name))
.collect();
}
}
impl PartialEq for SectionPeers {
fn eq(&self, other: &Self) -> bool {
self.members == other.members
}
}
impl Hash for SectionPeers {
fn hash<H: Hasher>(&self, state: &mut H) {
self.members.hash(state)
}
}
pub struct IntoIter(btree_map::IntoIter<XorName, Proven<MemberInfo>>);
impl Iterator for IntoIter {
type Item = Proven<MemberInfo>;
fn next(&mut self) -> Option<Self::Item> {
self.0.next().map(|(_, info)| info)
}
}
impl IntoIterator for SectionPeers {
type IntoIter = IntoIter;
type Item = <Self::IntoIter as Iterator>::Item;
fn into_iter(self) -> Self::IntoIter {
IntoIter(self.members.into_iter())
}
}
fn elder_candidates<'a, I>(elder_size: usize, current_elders: &EldersInfo, members: I) -> Vec<Peer>
where
I: IntoIterator<Item = &'a Proven<MemberInfo>>,
{
members
.into_iter()
.sorted_by(|lhs, rhs| cmp_elder_candidates(lhs, rhs, current_elders))
.map(|info| info.value.peer)
.take(elder_size)
.collect()
}
fn cmp_elder_candidates(
lhs: &Proven<MemberInfo>,
rhs: &Proven<MemberInfo>,
current_elders: &EldersInfo,
) -> Ordering {
cmp_elder_candidates_by_peer_state(&lhs.value.state, &rhs.value.state)
.then_with(|| rhs.value.peer.age().cmp(&lhs.value.peer.age()))
.then_with(|| {
let lhs_is_elder = is_elder(&lhs.value, current_elders);
let rhs_is_elder = is_elder(&rhs.value, current_elders);
match (lhs_is_elder, rhs_is_elder) {
(true, false) => Ordering::Less,
(false, true) => Ordering::Greater,
_ => Ordering::Equal,
}
})
.then_with(|| lhs.proof.signature.cmp(&rhs.proof.signature))
}
fn cmp_elder_candidates_by_peer_state(lhs: &PeerState, rhs: &PeerState) -> Ordering {
use PeerState::*;
match (lhs, rhs) {
(Joined, Joined) | (Relocated(_), Relocated(_)) => Ordering::Equal,
(Joined, Relocated(_)) | (_, Left) => Ordering::Less,
(Relocated(_), Joined) | (Left, _) => Ordering::Greater,
}
}
fn is_active(info: &MemberInfo, current_elders: &EldersInfo) -> bool {
match info.state {
PeerState::Joined => true,
PeerState::Relocated(_) if is_elder(info, current_elders) => true,
_ => false,
}
}
fn is_elder(info: &MemberInfo, current_elders: &EldersInfo) -> bool {
current_elders.elders.contains_key(info.peer.name())
}