mod relocation;
pub use relocation::{
ChurnId, RelocationInfo, RelocationProof, RelocationState, RelocationTrigger,
};
use crate::network_knowledge::{section_has_room_for_node, Error, Result};
use crate::types::NodeId;
use serde::{Deserialize, Serialize};
use std::{
collections::{BTreeMap, BTreeSet},
fmt::{self, Debug, Formatter},
net::SocketAddr,
};
use xor_name::{Prefix, XorName};
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize, Debug)]
pub enum MembershipState {
Joined,
Left,
Relocated(RelocationTrigger),
}
#[derive(Clone, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize, Deserialize)]
pub struct NodeState {
node_id: NodeId,
state: MembershipState,
previous_name: Option<XorName>,
}
impl Debug for NodeState {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let mut f = f.debug_tuple("NodeState");
let f = f
.field(&self.name())
.field(&self.addr())
.field(&self.state());
let f = if let Some(prev_name) = self.previous_name() {
f.field(&format!("prev_name: {prev_name:?}"))
} else {
f
};
f.finish()
}
}
impl NodeState {
pub fn joined(node_id: NodeId, previous_name: Option<XorName>) -> Self {
Self {
node_id,
state: MembershipState::Joined,
previous_name,
}
}
#[cfg(any(test, feature = "test-utils"))]
pub fn left(node_id: NodeId, previous_name: Option<XorName>) -> Self {
Self {
node_id,
state: MembershipState::Left,
previous_name,
}
}
#[cfg(any(test, feature = "test-utils"))]
pub fn relocated(
node_id: NodeId,
previous_name: Option<XorName>,
relocation_trigger: RelocationTrigger,
) -> Self {
Self {
node_id,
state: MembershipState::Relocated(relocation_trigger),
previous_name,
}
}
pub fn validate_node_state(
&self,
prefix: &Prefix,
current_joined_members: &BTreeMap<XorName, Self>,
archived: &BTreeSet<XorName>,
) -> Result<()> {
let name = self.name();
info!("Validating node state for {name} - {:?}", self.state);
if !prefix.matches(&name) {
warn!("Membership - rejecting node {name}, name doesn't match our prefix {prefix:?}");
return Err(Error::WrongSection);
}
match self.state {
MembershipState::Joined => {
if current_joined_members.contains_key(&name) {
warn!("Rejecting join from existing member {name}");
Err(Error::ExistingMemberNameConflict)
} else if !section_has_room_for_node(
name,
prefix,
current_joined_members.keys().copied(),
) {
warn!("Rejecting join since we are at capacity");
Err(Error::TryJoinLater)
} else if let Some(existing_node) = current_joined_members
.values()
.find(|n| n.node_id().addr() == self.node_id().addr())
{
warn!("Rejecting join since we have an existing node with this address: {existing_node:?}");
Err(Error::ExistingMemberSocketAddrConflict)
} else if archived.contains(&name) {
Err(Error::ArchivedNodeRejoined)
} else {
Ok(())
}
}
MembershipState::Relocated(_) => {
Ok(())
}
MembershipState::Left => {
if !current_joined_members.contains_key(&name) {
warn!("Rejecting leave from non-existing member");
Err(Error::NotAMember)
} else {
Ok(())
}
}
}
}
pub fn node_id(&self) -> &NodeId {
&self.node_id
}
pub fn name(&self) -> XorName {
self.node_id.name()
}
pub fn addr(&self) -> SocketAddr {
self.node_id.addr()
}
pub fn state(&self) -> MembershipState {
self.state.clone()
}
pub fn previous_name(&self) -> Option<XorName> {
self.previous_name
}
pub fn age(&self) -> u8 {
self.node_id.age()
}
pub fn is_relocated(&self) -> bool {
matches!(self.state, MembershipState::Relocated(_))
}
pub fn leave(self) -> Result<Self, Error> {
assert_eq!(self.state, MembershipState::Joined);
Ok(Self {
state: MembershipState::Left,
..self
})
}
pub fn relocate(self, relocation_trigger: RelocationTrigger) -> Self {
Self {
state: MembershipState::Relocated(relocation_trigger),
..self
}
}
}