mod errors;
mod node_info;
mod section_peers;
mod section_tree;
mod sections_dag;
pub mod node_state;
pub mod section_authority_provider;
pub mod section_keys;
#[cfg(any(test, feature = "test-utils"))]
pub mod test_utils;
#[cfg(any(test, feature = "test-utils"))]
pub use section_tree::test_utils as test_utils_st;
pub use self::{
errors::{Error, Result},
node_info::MyNodeInfo,
node_state::{MembershipState, NodeState, RelocationDst, RelocationInfo, RelocationProof},
section_authority_provider::{SapCandidate, SectionAuthUtils, SectionAuthorityProvider},
section_keys::{SectionKeyShare, SectionKeysProvider},
section_tree::{SectionTree, SectionTreeUpdate},
sections_dag::SectionsDAG,
};
use crate::{
messaging::{
system::{NodeMsg, SectionPeers as SectionPeersMsg, SectionSig, SectionSigned},
Dst,
},
types::Peer,
};
use bls::PublicKey as BlsPublicKey;
use section_peers::SectionPeers;
use serde::Serialize;
use std::{collections::BTreeSet, iter, net::SocketAddr};
use xor_name::{Prefix, XorName};
pub const GENESIS_DBC_SK: &str = "0c5152498fc5b2f9ed691ef875f2c16f1f950910391f7ba1df63e9f0ce4b2780";
pub const MIN_ADULT_AGE: u8 = 5;
const SN_ELDER_COUNT: &str = "SN_ELDER_COUNT";
pub const DEFAULT_ELDER_COUNT: usize = 7;
pub fn elder_count() -> usize {
match std::env::var(SN_ELDER_COUNT) {
Ok(count) => {
match count.parse() {
Ok(count) => {
warn!("ELDER_COUNT count set from env var SN_ELDER_COUNT: {SN_ELDER_COUNT:?}={count}");
count
}
Err(error) => {
warn!("There was an error parsing {SN_ELDER_COUNT:?} env var. DEFAULT_ELDER_COUNT={DEFAULT_ELDER_COUNT} will be used: {error:?}");
DEFAULT_ELDER_COUNT
}
}
}
Err(_) => DEFAULT_ELDER_COUNT,
}
}
pub fn recommended_section_size() -> usize {
2 * elder_count()
}
#[inline]
pub const fn supermajority(group_size: usize) -> usize {
threshold(group_size) + 1
}
#[inline]
pub const fn threshold(group_size: usize) -> usize {
group_size * 2 / 3
}
pub fn partition_by_prefix(
prefix: &Prefix,
nodes: impl IntoIterator<Item = XorName>,
) -> Option<(BTreeSet<XorName>, BTreeSet<XorName>)> {
let decision_index: u8 = if let Ok(idx) = prefix.bit_count().try_into() {
idx
} else {
return None;
};
let (one, zero) = nodes
.into_iter()
.filter(|name| prefix.matches(name))
.partition(|name| name.bit(decision_index));
Some((zero, one))
}
pub fn section_has_room_for_node(
joining_node: XorName,
prefix: &Prefix,
members: impl IntoIterator<Item = XorName>,
) -> bool {
let split_section_size_cap = recommended_section_size() * 2;
match partition_by_prefix(prefix, members) {
Some((zeros, ones)) => {
let n_zeros = zeros.len();
let n_ones = ones.len();
info!("Section {prefix:?} would split into {n_zeros} zero and {n_ones} one nodes");
match joining_node.bit(prefix.bit_count() as u8) {
true => n_ones < split_section_size_cap,
false => n_zeros < split_section_size_cap,
}
}
None => false,
}
}
#[derive(Clone, Debug)]
pub struct NetworkKnowledge {
signed_sap: SectionSigned<SectionAuthorityProvider>,
section_peers: SectionPeers,
section_tree: SectionTree,
}
impl NetworkKnowledge {
pub fn new(prefix: Prefix, section_tree: SectionTree) -> Result<Self, Error> {
let signed_sap = section_tree
.get_signed(&prefix)
.cloned()
.ok_or(Error::NoMatchingSection)?;
Ok(Self {
signed_sap,
section_peers: SectionPeers::default(),
section_tree,
})
}
pub fn first_node(
peer: Peer,
genesis_sk_set: bls::SecretKeySet,
) -> Result<(Self, SectionKeyShare)> {
let public_key_set = genesis_sk_set.public_keys();
let secret_key_index = 0u8;
let secret_key_share = genesis_sk_set.secret_key_share(secret_key_index as u64);
let section_tree = {
let genesis_signed_sap =
create_first_section_authority_provider(&public_key_set, &secret_key_share, peer)?;
SectionTree::new(genesis_signed_sap)?
};
let mut network_knowledge = Self::new(Prefix::default(), section_tree)?;
for peer in network_knowledge.signed_sap.elders() {
let node_state = NodeState::joined(*peer, None);
let sig = create_first_sig(&public_key_set, &secret_key_share, &node_state)?;
let _changed = network_knowledge.section_peers.update(SectionSigned {
value: node_state,
sig,
});
}
let section_key_share = SectionKeyShare {
public_key_set,
index: 0,
secret_key_share,
};
Ok((network_knowledge, section_key_share))
}
pub fn switch_section(
&mut self,
dst_sap: SectionSigned<SectionAuthorityProvider>,
) -> Result<()> {
if self.section_tree().get(&dst_sap.prefix()).is_none() {
return Err(Error::NoMatchingSection);
}
self.signed_sap = dst_sap;
Ok(())
}
pub fn update_knowledge_if_valid(
&mut self,
section_tree_update: SectionTreeUpdate,
updated_members: Option<SectionPeersMsg>,
our_name: &XorName,
) -> Result<bool> {
trace!("Attempting to update network knoweldge");
let mut there_was_an_update = false;
let signed_sap = section_tree_update.signed_sap.clone();
let sap_prefix = signed_sap.prefix();
match self
.section_tree
.update_the_section_tree(section_tree_update)
{
Ok(true) => {
there_was_an_update = true;
info!("Updated network section tree with SAP for {:?}", sap_prefix);
if sap_prefix.matches(our_name) {
let our_prev_prefix = self.prefix();
self.section_peers.retain(&sap_prefix);
info!("Updated our section's SAP ({our_prev_prefix:?} to {sap_prefix:?}) with new one: {:?}", signed_sap.value);
let proof_chain = self
.section_tree
.get_sections_dag()
.partial_dag(self.genesis_key(), &signed_sap.section_key())?;
self.section_peers
.prune_members_archive(&proof_chain, &signed_sap.section_key())?;
self.signed_sap = signed_sap;
}
}
Ok(false) => {
debug!("Anti-Entropy: discarded SAP for {sap_prefix:?} since it's the same as the one in our records: {:?}", signed_sap.value);
}
Err(err) => {
debug!("Anti-Entropy: discarded SAP for {sap_prefix:?} since we failed to update section tree with: {err:?}");
return Err(err);
}
}
if let Some(members) = updated_members {
if !members.is_empty() && self.merge_members(members)? {
there_was_an_update = true;
let prefix = self.prefix();
info!(
"Updated our section's members ({:?}): {:?}",
prefix, self.section_peers
);
}
}
Ok(there_was_an_update)
}
pub fn genesis_key(&self) -> &bls::PublicKey {
self.section_tree.genesis_key()
}
pub fn prefix(&self) -> Prefix {
self.signed_sap.prefix()
}
pub fn prefixes(&self) -> impl Iterator<Item = &Prefix> {
self.section_tree.prefixes()
}
pub fn section_tree(&self) -> &SectionTree {
&self.section_tree
}
pub fn section_tree_mut(&mut self) -> &mut SectionTree {
&mut self.section_tree
}
pub fn section_key(&self) -> bls::PublicKey {
self.signed_sap.section_key()
}
pub fn section_auth(&self) -> SectionAuthorityProvider {
self.signed_sap.value.clone()
}
pub fn section_auth_by_name(&self, name: &XorName) -> Result<SectionAuthorityProvider> {
self.section_tree
.get_signed_by_name(name)
.map(|sap| sap.value)
}
pub fn section_auth_by_prefix(&self, prefix: &Prefix) -> Result<SectionAuthorityProvider> {
self.section_tree
.get_signed_by_prefix(prefix)
.map(|sap| sap.value)
}
pub fn signed_sap(&self) -> SectionSigned<SectionAuthorityProvider> {
self.signed_sap.clone()
}
pub fn closest_signed_sap(
&self,
name: &XorName,
) -> Option<SectionSigned<SectionAuthorityProvider>> {
let closest_sap = self
.section_tree
.closest(name, Some(&self.prefix()))
.unwrap_or(self.section_tree.get_signed(&self.prefix())?);
Some(closest_sap.clone())
}
pub fn closest_signed_sap_with_chain(
&self,
name: &XorName,
) -> Option<(SectionSigned<SectionAuthorityProvider>, SectionsDAG)> {
let closest_sap = self.closest_signed_sap(name)?;
if let Ok(section_chain) = self
.section_tree
.get_sections_dag()
.partial_dag(self.genesis_key(), &closest_sap.value.section_key())
{
return Some((closest_sap, section_chain));
}
None
}
pub fn get_proof_chain_to_current_section(
&self,
from_key: &BlsPublicKey,
) -> Result<SectionsDAG> {
let our_section_key = self.signed_sap.section_key();
let proof_chain = self
.section_tree
.get_sections_dag()
.partial_dag(from_key, &our_section_key)?;
Ok(proof_chain)
}
pub fn section_chain(&self) -> SectionsDAG {
self.get_proof_chain_to_current_section(self.genesis_key())
.unwrap_or_else(|_| SectionsDAG::new(*self.genesis_key()))
}
pub fn section_chain_len(&self) -> u64 {
self.section_chain().keys().count() as u64
}
pub fn has_chain_key(&self, key: &bls::PublicKey) -> bool {
self.section_chain().has_key(key)
}
pub fn verify_section_key_is_known(&self, section_key: &BlsPublicKey) -> bool {
self.section_tree.get_sections_dag().has_key(section_key)
}
pub fn known_keys(&self) -> BTreeSet<bls::PublicKey> {
self.section_tree.get_sections_dag().keys().collect()
}
pub fn merge_members(&mut self, peers: BTreeSet<SectionSigned<NodeState>>) -> Result<bool> {
let mut there_was_an_update = false;
let our_current_members = self.section_peers.members();
for node_state in &peers {
if our_current_members.contains(node_state) {
continue;
}
trace!(
"Attempting to update section members. Name: {:?}, new state: {:?}",
node_state.name(),
node_state.state()
);
if !node_state.verify(&self.section_chain()) {
error!(
"Can't update section member, name: {:?}, new state: {:?}",
node_state.name(),
node_state.state()
);
} else if self.section_peers.update(node_state.clone()) {
there_was_an_update = true;
}
}
self.section_peers.retain(&self.prefix());
Ok(there_was_an_update)
}
pub fn update_member(&mut self, node_state: SectionSigned<NodeState>) -> bool {
let node_name = node_state.name();
trace!(
"Updating section member state, name: {node_name:?}, new state: {:?}",
node_state.state()
);
if !node_state.verify(&self.section_chain()) {
error!(
"Can't update section member, name: {node_name:?}, new state: {:?}",
node_state.state()
);
return false;
}
let updated = self.section_peers.update(node_state);
trace!("Section member state, name: {node_name:?}, updated: {updated}");
updated
}
pub fn members(&self) -> BTreeSet<Peer> {
self.elders().into_iter().chain(self.adults()).collect()
}
pub fn elders(&self) -> BTreeSet<Peer> {
self.section_auth().elders_set()
}
pub fn adults(&self) -> BTreeSet<Peer> {
let mut live_adults = BTreeSet::new();
for node_state in self.section_peers.members() {
if !self.is_elder(&node_state.name()) {
let _ = live_adults.insert(*node_state.peer());
}
}
live_adults
}
pub fn is_elder(&self, name: &XorName) -> bool {
self.signed_sap.contains_elder(name)
}
pub fn is_adult(&self, name: &XorName) -> bool {
self.adults().iter().any(|a| a.name() == *name)
}
pub fn generate_dst(&self, recipient: &XorName) -> Result<Dst> {
Ok(Dst {
name: *recipient,
section_key: self.section_auth_by_name(recipient)?.section_key(),
})
}
pub fn section_members(&self) -> BTreeSet<NodeState> {
self.section_peers
.members()
.into_iter()
.map(|state| state.value)
.collect()
}
pub fn section_signed_members(&self) -> BTreeSet<SectionSigned<NodeState>> {
self.section_peers.members()
}
pub fn section_size(&self) -> usize {
self.section_peers.num_of_members()
}
pub fn get_section_member(&self, name: &XorName) -> Option<NodeState> {
self.section_peers.get(name)
}
pub fn is_either_member_or_archived(&self, name: &XorName) -> Option<SectionSigned<NodeState>> {
self.section_peers.is_either_member_or_archived(name)
}
pub fn is_section_member(&self, name: &XorName) -> bool {
self.section_peers.is_member(name)
}
pub fn find_member_by_addr(&self, addr: &SocketAddr) -> Option<Peer> {
self.section_peers
.members()
.into_iter()
.find(|info| info.addr() == *addr)
.map(|info| *info.peer())
}
pub fn anti_entropy_probe(&self) -> NodeMsg {
NodeMsg::AntiEntropyProbe(self.section_key())
}
}
fn create_first_section_authority_provider(
pk_set: &bls::PublicKeySet,
sk_share: &bls::SecretKeyShare,
peer: Peer,
) -> Result<SectionSigned<SectionAuthorityProvider>> {
let section_auth = SectionAuthorityProvider::new(
iter::once(peer),
Prefix::default(),
[NodeState::joined(peer, None)],
pk_set.clone(),
0,
);
let sig = create_first_sig(pk_set, sk_share, §ion_auth)?;
Ok(SectionSigned::new(section_auth, sig))
}
fn create_first_sig<T: Serialize>(
pk_set: &bls::PublicKeySet,
sk_share: &bls::SecretKeyShare,
payload: &T,
) -> Result<SectionSig> {
let bytes = bincode::serialize(payload).map_err(|_| Error::InvalidPayload)?;
let signature_share = sk_share.sign(bytes);
let signature = pk_set
.combine_signatures(iter::once((0, &signature_share)))
.map_err(|_| Error::InvalidSignatureShare)?;
Ok(SectionSig {
public_key: pk_set.public_key(),
signature,
})
}
#[cfg(test)]
mod tests {
use super::{supermajority, NetworkKnowledge};
use crate::{
test_utils::{gen_addr, prefix, TestKeys, TestSapBuilder, TestSectionTree},
types::Peer,
};
use bls::SecretKeySet;
use eyre::Result;
use proptest::prelude::*;
use rand::thread_rng;
use xor_name::XorName;
#[test]
fn supermajority_of_small_group() {
assert_eq!(supermajority(0), 1);
assert_eq!(supermajority(1), 1);
assert_eq!(supermajority(2), 2);
assert_eq!(supermajority(3), 3);
assert_eq!(supermajority(4), 3);
assert_eq!(supermajority(5), 4);
assert_eq!(supermajority(6), 5);
assert_eq!(supermajority(7), 5);
assert_eq!(supermajority(8), 6);
assert_eq!(supermajority(9), 7);
}
proptest! {
#[test]
fn proptest_supermajority(a in 0usize..10000) {
let n = 3 * a;
assert_eq!(supermajority(n), 2 * a + 1);
assert_eq!(supermajority(n + 1), 2 * a + 1);
assert_eq!(supermajority(n + 2), 2 * a + 2);
}
}
#[test]
fn signed_sap_field_should_not_be_changed_if_the_update_is_for_a_different_prefix() -> Result<()>
{
let mut rng = thread_rng();
let sk_gen = SecretKeySet::random(0, &mut rng);
let peer = Peer::new(XorName::random(&mut rng), gen_addr());
let (mut knowledge, _) = NetworkKnowledge::first_node(peer, sk_gen.clone())?;
let (sap1, sk_1, ..) = TestSapBuilder::new(prefix("1")).elder_count(0).build();
let sap1 = TestKeys::get_section_signed(&sk_1.secret_key(), sap1);
let our_node_name_prefix_1 = sap1.prefix().name();
let proof_chain = knowledge.section_chain();
let section_tree_update =
TestSectionTree::get_section_tree_update(&sap1, &proof_chain, &sk_gen.secret_key());
assert!(knowledge.update_knowledge_if_valid(
section_tree_update,
None,
&our_node_name_prefix_1
)?);
assert_eq!(knowledge.signed_sap, sap1);
let (sap0, sk_0, ..) = TestSapBuilder::new(prefix("0")).elder_count(0).build();
let sap0 = TestKeys::get_section_signed(&sk_0.secret_key(), sap0);
let section_tree_update =
TestSectionTree::get_section_tree_update(&sap0, &proof_chain, &sk_gen.secret_key());
assert!(knowledge.update_knowledge_if_valid(
section_tree_update,
None,
&our_node_name_prefix_1
)?);
assert_eq!(knowledge.signed_sap, sap1);
Ok(())
}
}