mod errors;
mod node_info;
mod section_member_history;
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, RelocationInfo, RelocationProof, RelocationState},
section_authority_provider::{SapCandidate, SectionAuthUtils, SectionAuthorityProvider},
section_keys::{SectionKeyShare, SectionKeysProvider},
section_tree::{SectionTree, SectionTreeUpdate},
sections_dag::SectionsDAG,
};
use self::{node_state::ChurnId, section_member_history::SectionMemberHistory};
use crate::{
messaging::{
system::{SectionDecisions, SectionSig, SectionSigned},
AntiEntropyMsg, Dst, NetworkMsg,
},
types::NodeId,
};
use sn_consensus::Decision;
use bls::PublicKey as BlsPublicKey;
use serde::Serialize;
use std::{
collections::{BTreeMap, BTreeSet},
iter,
};
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_members: SectionMemberHistory,
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)?;
let initial_members = BTreeSet::from_iter(signed_sap.value.members().cloned());
let mut section_members = SectionMemberHistory::default();
section_members.reset_initial_members(initial_members);
Ok(Self {
signed_sap,
section_members,
section_tree,
})
}
pub fn first_node(
node_id: NodeId,
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,
node_id,
)?;
SectionTree::new(genesis_signed_sap)?
};
let mut network_knowledge = Self::new(Prefix::default(), section_tree)?;
let initial_members = BTreeSet::from_iter([NodeState::joined(node_id, None)]);
network_knowledge
.section_members
.reset_initial_members(initial_members);
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);
}
let initial_members = BTreeSet::from_iter(dst_sap.value.members().cloned());
self.reset_initial_members(initial_members);
self.signed_sap = dst_sap;
Ok(())
}
pub fn update_sap_knowledge_if_valid(
&mut self,
section_tree_update: SectionTreeUpdate,
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 {:?}",
signed_sap.value
);
if sap_prefix.matches(our_name) {
let initial_members: BTreeSet<_> = signed_sap.members().cloned().collect();
self.reset_initial_members(initial_members);
let our_prev_prefix = self.prefix();
self.section_members.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_members
.prune_members_archive(&proof_chain, &signed_sap.section_key())?;
self.signed_sap = signed_sap;
}
}
Ok(false) => {
warn!("Anti-Entropy: discarded SAP for {sap_prefix:?} since it's the same as the one in our records: {:?}", signed_sap.value);
}
Err(err) => {
warn!("Anti-Entropy: discarded SAP for {sap_prefix:?} since we failed to update section tree with: {:?} - {err:?}", signed_sap.value);
return Err(err);
}
}
Ok(there_was_an_update)
}
pub fn update_section_member_knowledge(
&mut self,
updated_members: Option<SectionDecisions>,
) -> Result<bool> {
trace!("Attempting to update section members` knowledge");
let mut there_was_an_update = false;
if let Some(members) = updated_members {
if self.update_members(members)? {
there_was_an_update = true;
let prefix = self.prefix();
info!(
"Updated our section's members ({:?}): {:?}",
prefix, self.section_members
);
}
}
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_decisions(&self) -> SectionDecisions {
self.section_members.section_decisions()
}
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 reset_initial_members(&mut self, new_initial_members: BTreeSet<NodeState>) {
self.section_members
.reset_initial_members(new_initial_members)
}
pub fn update_members(&mut self, peers: SectionDecisions) -> Result<bool> {
Ok(self
.section_members
.update_peers(&self.signed_sap.section_key(), peers))
}
pub fn try_update_member(&mut self, decision: Decision<NodeState>) -> Result<bool> {
self.section_members
.update(&self.signed_sap.section_key(), decision)
}
pub fn members(&self) -> BTreeSet<NodeId> {
self.elders().into_iter().chain(self.adults()).collect()
}
pub fn elders(&self) -> BTreeSet<NodeId> {
self.section_auth().elders_set()
}
pub fn adults(&self) -> BTreeSet<NodeId> {
let mut live_adults = BTreeSet::new();
for node_state in self.section_members.members() {
if !self.is_elder(&node_state.name()) {
let _ = live_adults.insert(*node_state.node_id());
}
}
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_members.members()
}
pub fn members_at_gen(&self, gen: u64) -> BTreeMap<XorName, NodeState> {
self.section_members.members_at_gen(gen)
}
pub fn archived_members(&self) -> BTreeSet<NodeState> {
self.section_members.archived_members()
}
pub fn get_section_member(&self, name: &XorName) -> Option<NodeState> {
self.section_members.get(name)
}
pub fn is_section_member(&self, name: &XorName) -> bool {
self.section_members.is_member(name)
}
pub fn anti_entropy_probe(&self) -> NetworkMsg {
NetworkMsg::AntiEntropy(AntiEntropyMsg::Probe(self.section_key()))
}
}
fn create_first_section_authority_provider(
pk_set: &bls::PublicKeySet,
sk_share: &bls::SecretKeyShare,
node_id: NodeId,
) -> Result<SectionSigned<SectionAuthorityProvider>> {
let section_auth = SectionAuthorityProvider::new(
iter::once(node_id),
Prefix::default(),
[NodeState::joined(node_id, 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,
})
}
pub fn trailing_zeros(bytes: &[u8]) -> u32 {
let mut output = 0;
for &byte in bytes.iter().rev() {
if byte == 0 {
output += 8;
} else {
output += byte.trailing_zeros();
break;
}
}
output
}
pub fn relocation_check(age: u8, churn_id: &ChurnId) -> bool {
trailing_zeros(&churn_id.0) >= age as u32
}
#[cfg(test)]
mod tests {
use super::{supermajority, NetworkKnowledge};
use crate::{
network_knowledge::trailing_zeros,
test_utils::{gen_addr, prefix, TestKeys, TestSapBuilder, TestSectionTree},
types::NodeId,
};
use bls::SecretKeySet;
use eyre::Result;
use proptest::prelude::*;
use rand::thread_rng;
use xor_name::XorName;
#[test]
fn byte_slice_trailing_zeros() {
assert_eq!(trailing_zeros(&[0]), 8);
assert_eq!(trailing_zeros(&[1]), 0);
assert_eq!(trailing_zeros(&[2]), 1);
assert_eq!(trailing_zeros(&[4]), 2);
assert_eq!(trailing_zeros(&[8]), 3);
assert_eq!(trailing_zeros(&[0, 0]), 16);
assert_eq!(trailing_zeros(&[1, 0]), 8);
assert_eq!(trailing_zeros(&[2, 0]), 9);
}
#[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 node_id = NodeId::new(XorName::random(&mut rng), gen_addr());
let (mut knowledge, _) = NetworkKnowledge::first_node(node_id, 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_sap_knowledge_if_valid(section_tree_update, &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_sap_knowledge_if_valid(section_tree_update, &our_node_name_prefix_1)?);
assert_eq!(knowledge.signed_sap, sap1);
Ok(())
}
}