use crate::messaging::system::SectionSigned;
use crate::network_knowledge::{section_has_room_for_node, Error, Result};
use crate::types::Peer;
use ed25519_dalek::{PublicKey, Signature, Verifier};
use hex_fmt::HexFmt;
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(RelocationDst),
}
#[derive(Clone, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize, Deserialize)]
pub struct NodeState {
peer: Peer,
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(peer: Peer, previous_name: Option<XorName>) -> Self {
Self {
peer,
state: MembershipState::Joined,
previous_name,
}
}
#[cfg(any(test, feature = "test-utils"))]
pub fn left(peer: Peer, previous_name: Option<XorName>) -> Self {
Self {
peer,
state: MembershipState::Left,
previous_name,
}
}
#[cfg(any(test, feature = "test-utils"))]
pub fn relocated(
peer: Peer,
previous_name: Option<XorName>,
relocation_dst: RelocationDst,
) -> Self {
Self {
peer,
state: MembershipState::Relocated(relocation_dst),
previous_name,
}
}
pub fn validate(
&self,
prefix: &Prefix,
members: &BTreeMap<XorName, Self>,
archived: &BTreeSet<XorName>,
) -> Result<()> {
let name = self.name();
info!("Validating node state for {name} - {:?}", self.state);
if !prefix.matches(&name) {
info!("Membership - rejecting node {name}, name doesn't match our prefix {prefix:?}");
return Err(Error::WrongSection);
}
match self.state {
MembershipState::Joined => {
if members.contains_key(&name) {
info!("Rejecting join from existing member {name}");
Err(Error::ExistingMemberConflict)
} else if !section_has_room_for_node(name, prefix, members.keys().copied()) {
info!("Rejecting join since we are at capacity");
Err(Error::TryJoinLater)
} else if let Some(existing_node) = members
.values()
.find(|n| n.peer().addr() == self.peer().addr())
{
info!("Rejecting join since we have an existing node with this address: {existing_node:?}");
Err(Error::ExistingMemberConflict)
} else if archived.contains(&name) {
Err(Error::ArchivedNodeRejoined)
} else {
Ok(())
}
}
MembershipState::Relocated(_) => {
Ok(())
}
MembershipState::Left => {
if !members.contains_key(&name) {
info!("Rejecting leave from non-existing member");
Err(Error::NotAMember)
} else {
Ok(())
}
}
}
}
pub fn peer(&self) -> &Peer {
&self.peer
}
pub fn name(&self) -> XorName {
self.peer.name()
}
pub fn addr(&self) -> SocketAddr {
self.peer.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.peer.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_dst: RelocationDst) -> Self {
Self {
state: MembershipState::Relocated(relocation_dst),
..self
}
}
}
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize, Debug)]
pub struct RelocationDst(XorName);
impl RelocationDst {
pub fn new(dst_name: XorName) -> Self {
Self(dst_name)
}
pub fn name(&self) -> &XorName {
&self.0
}
}
#[derive(Clone, PartialEq, Serialize, Deserialize, Debug)]
pub struct RelocationInfo {
signed_relocation: SectionSigned<NodeState>,
new_name: XorName,
}
#[derive(Clone, PartialEq, Serialize, Deserialize, custom_debug::Debug)]
pub struct RelocationProof {
info: RelocationInfo,
#[serde(with = "serde_bytes")]
#[debug(with = "Self::fmt_ed25519")]
self_sig: Signature,
self_old_key: PublicKey,
}
impl RelocationInfo {
pub fn new(signed_relocation: SectionSigned<NodeState>, new_name: XorName) -> Self {
Self {
signed_relocation,
new_name,
}
}
}
impl RelocationProof {
pub fn new(info: RelocationInfo, self_sig: Signature, self_old_key: PublicKey) -> Self {
Self {
info,
self_sig,
self_old_key,
}
}
pub fn signed_by(&self) -> &bls::PublicKey {
&self.info.signed_relocation.sig.public_key
}
pub fn verify(&self) -> Result<()> {
if self.old_key_name() != self.info.signed_relocation.name() {
return Err(Error::InvalidRelocationProof);
}
let serialized_info =
bincode::serialize(&self.info).map_err(|_err| Error::InvalidRelocationProof)?;
self.self_old_key
.verify(&serialized_info, &self.self_sig)
.map_err(|_err| Error::InvalidRelocationProof)?;
let serialized_state = bincode::serialize(&self.info.signed_relocation.value)
.map_err(|_err| Error::InvalidRelocationProof)?;
if !self.info.signed_relocation.sig.verify(&serialized_state) {
Err(Error::InvalidRelocationProof)
} else {
Ok(())
}
}
pub fn previous_name(&self) -> XorName {
self.info.signed_relocation.name()
}
pub fn previous_age(&self) -> u8 {
self.info.signed_relocation.age()
}
pub fn fmt_ed25519(sig: &Signature, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "Signature({:0.10})", HexFmt(sig))
}
fn old_key_name(&self) -> XorName {
use crate::types::PublicKey::Ed25519;
XorName::from(Ed25519(self.self_old_key))
}
}