use crate::{
messaging::system::SectionSigned,
network_knowledge::{errors::Result, MembershipState, NodeState, SectionsDAG},
};
use std::collections::{btree_map::Entry, BTreeMap, BTreeSet};
use xor_name::{Prefix, XorName};
#[cfg(not(test))]
const ELDER_CHURN_EVENTS_TO_PRUNE_ARCHIVE: usize = 5;
#[cfg(test)]
const ELDER_CHURN_EVENTS_TO_PRUNE_ARCHIVE: usize = 3;
#[derive(Clone, Default, Debug)]
pub(super) struct SectionPeers {
members: BTreeMap<XorName, SectionSigned<NodeState>>,
archive: BTreeMap<XorName, SectionSigned<NodeState>>,
}
impl SectionPeers {
pub(super) fn members(&self) -> BTreeSet<SectionSigned<NodeState>> {
self.members.values().cloned().collect()
}
pub(super) fn num_of_members(&self) -> usize {
self.members.len()
}
pub(super) fn get(&self, name: &XorName) -> Option<NodeState> {
self.members.get(name).map(|state| state.value.clone())
}
pub(super) fn is_member(&self, name: &XorName) -> bool {
self.members.get(name).is_some()
}
pub(super) fn is_either_member_or_archived(
&self,
name: &XorName,
) -> Option<SectionSigned<NodeState>> {
if let Some(member) = self.members.get(name).cloned() {
Some(member)
} else {
self.archive.get(name).cloned()
}
}
pub(super) fn update(&mut self, new_state: SectionSigned<NodeState>) -> bool {
let node_name = new_state.name();
match (self.members.entry(node_name), new_state.state()) {
(Entry::Vacant(entry), MembershipState::Joined) => {
if self.archive.contains_key(&node_name) {
false
} else {
entry.insert(new_state);
true
}
}
(Entry::Vacant(_), MembershipState::Left | MembershipState::Relocated(_)) => {
let _prev = self.archive.insert(node_name, new_state.clone());
true
}
(Entry::Occupied(_), MembershipState::Joined) => false,
(Entry::Occupied(entry), MembershipState::Left | MembershipState::Relocated(_)) => {
let _ = entry.remove();
let _ = self.archive.insert(node_name, new_state);
true
}
}
}
pub(super) fn retain(&mut self, prefix: &Prefix) {
self.members.retain(|name, _| prefix.matches(name))
}
pub(super) fn prune_members_archive(
&mut self,
proof_chain: &SectionsDAG,
last_key: &bls::PublicKey,
) -> Result<()> {
let mut latest_section_keys = proof_chain.get_ancestors(last_key)?;
latest_section_keys.truncate(ELDER_CHURN_EVENTS_TO_PRUNE_ARCHIVE - 1);
latest_section_keys.push(*last_key);
self.archive
.retain(|_, node_state| latest_section_keys.contains(&node_state.sig.public_key));
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::{SectionPeers, SectionsDAG};
use crate::{
messaging::system::SectionSigned,
network_knowledge::{MembershipState, NodeState, RelocationDst},
test_utils::{assert_lists, gen_addr, TestKeys},
types::Peer,
};
use eyre::Result;
use rand::thread_rng;
use xor_name::XorName;
#[test]
fn retain_archived_members_of_the_latest_sections_while_pruning() -> Result<()> {
let mut rng = thread_rng();
let mut section_peers = SectionPeers::default();
let sk_1 = bls::SecretKeySet::random(0, &mut thread_rng()).secret_key();
let nodes_1 = gen_random_signed_node_states(1, MembershipState::Left, &sk_1);
nodes_1.iter().for_each(|node| {
section_peers.update(node.clone());
});
let mut proof_chain = SectionsDAG::new(sk_1.public_key());
section_peers.prune_members_archive(&proof_chain, &sk_1.public_key())?;
assert_lists(section_peers.archive.values(), &nodes_1);
let sk_2 = bls::SecretKeySet::random(0, &mut thread_rng()).secret_key();
let dst = RelocationDst::new(XorName::random(&mut rng));
let nodes_2 = gen_random_signed_node_states(1, MembershipState::Relocated(dst), &sk_2);
nodes_2.iter().for_each(|node| {
section_peers.update(node.clone());
});
let sig = TestKeys::sign(&sk_1, &sk_2.public_key());
proof_chain.verify_and_insert(&sk_1.public_key(), sk_2.public_key(), sig)?;
section_peers.prune_members_archive(&proof_chain, &sk_2.public_key())?;
assert_lists(
section_peers.archive.values(),
nodes_1.iter().chain(&nodes_2),
);
let sk_3 = bls::SecretKeySet::random(0, &mut thread_rng()).secret_key();
let nodes_3 = gen_random_signed_node_states(1, MembershipState::Left, &sk_3);
nodes_3.iter().for_each(|node| {
section_peers.update(node.clone());
});
let sig = TestKeys::sign(&sk_2, &sk_3.public_key());
proof_chain.verify_and_insert(&sk_2.public_key(), sk_3.public_key(), sig)?;
section_peers.prune_members_archive(&proof_chain, &sk_3.public_key())?;
assert_lists(
section_peers.archive.values(),
nodes_1.iter().chain(&nodes_2).chain(&nodes_3),
);
let sk_4 = bls::SecretKeySet::random(0, &mut thread_rng()).secret_key();
let nodes_4 = gen_random_signed_node_states(1, MembershipState::Left, &sk_4);
nodes_4.iter().for_each(|node| {
section_peers.update(node.clone());
});
let sig = TestKeys::sign(&sk_3, &sk_4.public_key());
proof_chain.verify_and_insert(&sk_3.public_key(), sk_4.public_key(), sig)?;
section_peers.prune_members_archive(&proof_chain, &sk_4.public_key())?;
assert_lists(
section_peers.archive.values(),
nodes_2.iter().chain(&nodes_3).chain(&nodes_4),
);
let sk_5 = bls::SecretKeySet::random(0, &mut thread_rng()).secret_key();
let nodes_5 = gen_random_signed_node_states(1, MembershipState::Left, &sk_5);
nodes_5.iter().for_each(|node| {
section_peers.update(node.clone());
});
let sig = TestKeys::sign(&sk_3, &sk_5.public_key());
proof_chain.verify_and_insert(&sk_3.public_key(), sk_5.public_key(), sig)?;
section_peers.prune_members_archive(&proof_chain, &sk_5.public_key())?;
assert_lists(
section_peers.archive.values(),
nodes_2.iter().chain(&nodes_3).chain(&nodes_5),
);
Ok(())
}
#[test]
fn archived_members_should_not_be_moved_to_members_list() {
let mut rng = thread_rng();
let mut section_peers = SectionPeers::default();
let sk = bls::SecretKeySet::random(0, &mut thread_rng()).secret_key();
let node_left = gen_random_signed_node_states(1, MembershipState::Left, &sk)[0].clone();
let dst = RelocationDst::new(XorName::random(&mut rng));
let node_relocated =
gen_random_signed_node_states(1, MembershipState::Relocated(dst), &sk)[0].clone();
assert!(section_peers.update(node_left.clone()));
assert!(section_peers.update(node_relocated.clone()));
let node_left_joins =
TestKeys::get_section_signed(&sk, NodeState::joined(*node_left.peer(), None));
let node_relocated_joins =
TestKeys::get_section_signed(&sk, NodeState::joined(*node_relocated.peer(), None));
assert!(!section_peers.update(node_left_joins));
assert!(!section_peers.update(node_relocated_joins));
assert_lists(section_peers.archive.values(), &[node_left, node_relocated]);
assert!(section_peers.members().is_empty());
}
#[test]
fn members_should_be_archived_if_they_leave_or_relocate() {
let mut rng = thread_rng();
let mut section_peers = SectionPeers::default();
let sk = bls::SecretKeySet::random(0, &mut thread_rng()).secret_key();
let node_1 = gen_random_signed_node_states(1, MembershipState::Joined, &sk)[0].clone();
let dst = RelocationDst::new(XorName::random(&mut rng));
let node_2 =
gen_random_signed_node_states(1, MembershipState::Relocated(dst), &sk)[0].clone();
assert!(section_peers.update(node_1.clone()));
assert!(section_peers.update(node_2.clone()));
let node_1 = NodeState::left(*node_1.peer(), Some(node_1.name()));
let node_1 = TestKeys::get_section_signed(&sk, node_1);
let node_2 = NodeState::left(*node_2.peer(), Some(node_2.name()));
let node_2 = TestKeys::get_section_signed(&sk, node_2);
assert!(section_peers.update(node_1.clone()));
assert!(section_peers.update(node_2.clone()));
assert!(section_peers.members().is_empty());
assert_lists(section_peers.archive.values(), &[node_1, node_2]);
}
fn gen_random_signed_node_states(
num_nodes: usize,
membership_state: MembershipState,
secret_key: &bls::SecretKey,
) -> Vec<SectionSigned<NodeState>> {
let mut rng = thread_rng();
let mut signed_node_states = Vec::new();
for _ in 0..num_nodes {
let addr = gen_addr();
let name = XorName::random(&mut rng);
let peer = Peer::new(name, addr);
let node_state = match membership_state {
MembershipState::Joined => NodeState::joined(peer, None),
MembershipState::Left => NodeState::left(peer, None),
MembershipState::Relocated(ref dst) => {
NodeState::relocated(peer, None, (*dst).clone())
}
};
let sig = TestKeys::get_section_signed(secret_key, node_state);
signed_node_states.push(sig);
}
signed_node_states
}
}