From 54dd96dfff64ebbf1f4e5a431ec94a7a13b3151f Mon Sep 17 00:00:00 2001 From: gusto Date: Thu, 26 Oct 2023 18:43:49 +0200 Subject: [PATCH] Simulations overlay topo info (#479) * Missing json feature for building standalone simapp * Move dummy sim overlay to tests module * OverlayInfoExt in simulation app * Dump overlay info in simapp --- simulations/Cargo.toml | 4 +- simulations/src/bin/app/main.rs | 20 +++ simulations/src/bin/app/overlay_node.rs | 45 ++++- simulations/src/node/dummy.rs | 2 +- simulations/src/node/mod.rs | 2 +- simulations/src/overlay/mod.rs | 180 +------------------ simulations/src/overlay/overlay_info.rs | 181 ++++++++++++++++++++ simulations/src/overlay/{ => tests}/flat.rs | 3 +- simulations/src/overlay/tests/mod.rs | 178 +++++++++++++++++++ simulations/src/overlay/{ => tests}/tree.rs | 0 simulations/src/runner/sync_runner.rs | 2 +- 11 files changed, 432 insertions(+), 185 deletions(-) create mode 100644 simulations/src/overlay/overlay_info.rs rename simulations/src/overlay/{ => tests}/flat.rs (95%) create mode 100644 simulations/src/overlay/tests/mod.rs rename simulations/src/overlay/{ => tests}/tree.rs (100%) diff --git a/simulations/Cargo.toml b/simulations/Cargo.toml index a13960a9..c0fa9be4 100644 --- a/simulations/Cargo.toml +++ b/simulations/Cargo.toml @@ -11,7 +11,9 @@ path = "src/bin/app/main.rs" [dependencies] anyhow = "1" +blake2 = "0.10" bls-signatures = "0.14" +digest = "0.10" csv = "1" clap = { version = "4", features = ["derive"] } ctrlc = "3.4" @@ -36,7 +38,7 @@ serde_with = "2.3" serde_json = "1.0" thiserror = "1" tracing = { version = "0.1", default-features = false, features = ["log", "attributes"] } -tracing-subscriber = { version = "0.3", features = ["env-filter", "tracing-log"]} +tracing-subscriber = { version = "0.3", features = ["json", "env-filter", "tracing-log"]} [target.'cfg(target_arch = "wasm32")'.dependencies] getrandom = { version = "0.2", features = ["js"] } diff --git a/simulations/src/bin/app/main.rs b/simulations/src/bin/app/main.rs index d5aaebda..6e391ef7 100644 --- a/simulations/src/bin/app/main.rs +++ b/simulations/src/bin/app/main.rs @@ -44,6 +44,8 @@ pub struct SimulationApp { log_format: log::LogFormat, #[clap(long, default_value = "stdout")] log_to: log::LogOutput, + #[clap(long)] + dump_overlay_info: bool, } impl SimulationApp { @@ -53,6 +55,7 @@ impl SimulationApp { stream_type, log_format: _, log_to: _, + dump_overlay_info, } = self; let simulation_settings: SimulationSettings = load_json_from_file(&input_settings)?; @@ -74,6 +77,18 @@ impl SimulationApp { let ids = node_ids.clone(); let network = Arc::new(Mutex::new(Network::new(regions_data, seed))); + + if dump_overlay_info { + dump_json_to_file( + Path::new("overlay_info.json"), + &overlay_node::overlay_info( + node_ids.clone(), + node_ids.first().copied().unwrap(), + &simulation_settings.overlay_settings, + ), + )?; + } + let nodes: Vec> = node_ids .par_iter() .copied() @@ -206,6 +221,11 @@ fn load_json_from_file(path: &Path) -> anyhow::Result { Ok(serde_json::from_reader(f)?) } +fn dump_json_to_file(path: &Path, data: &T) -> anyhow::Result<()> { + let f = File::create(path).map_err(Box::new)?; + Ok(serde_json::to_writer(f, data)?) +} + fn main() -> anyhow::Result<()> { let app: SimulationApp = SimulationApp::parse(); log::config_tracing(app.log_format, &app.log_to); diff --git a/simulations/src/bin/app/overlay_node.rs b/simulations/src/bin/app/overlay_node.rs index dbde33a9..1103802d 100644 --- a/simulations/src/bin/app/overlay_node.rs +++ b/simulations/src/bin/app/overlay_node.rs @@ -1,9 +1,12 @@ -use consensus_engine::overlay::{BranchOverlay, RandomBeaconState}; +use consensus_engine::overlay::{BranchOverlay, FisherYatesShuffle, RandomBeaconState}; +use consensus_engine::Overlay; use consensus_engine::{ overlay::{FlatOverlay, FreezeMembership, RoundRobin, TreeOverlay}, NodeId, }; use rand::Rng; +use simulations::overlay::overlay_info::{OverlayInfo, OverlayInfoExt}; +use simulations::settings::OverlaySettings; use simulations::{ network::InMemoryNetworkInterface, node::carnot::{messages::CarnotMessage, CarnotNode, CarnotSettings, CarnotState}, @@ -11,6 +14,46 @@ use simulations::{ settings::SimulationSettings, }; +pub fn overlay_info( + nodes: Vec, + leader: NodeId, + overlay_settings: &OverlaySettings, +) -> OverlayInfo { + match &overlay_settings { + simulations::settings::OverlaySettings::Flat => { + FlatOverlay::::new( + consensus_engine::overlay::FlatOverlaySettings { + nodes: nodes.to_vec(), + leader: RoundRobin::new(), + leader_super_majority_threshold: None, + }, + ) + .info() + } + simulations::settings::OverlaySettings::Tree(tree_settings) => { + TreeOverlay::new(consensus_engine::overlay::TreeOverlaySettings { + nodes, + current_leader: leader, + number_of_committees: tree_settings.number_of_committees, + leader: RoundRobin::new(), + committee_membership: RandomBeaconState::initial_sad_from_entropy([0; 32]), + super_majority_threshold: None, + }) + .info() + } + simulations::settings::OverlaySettings::Branch(branch_settings) => { + BranchOverlay::new(consensus_engine::overlay::BranchOverlaySettings { + nodes, + current_leader: leader, + branch_depth: branch_settings.branch_depth, + leader: RoundRobin::new(), + committee_membership: RandomBeaconState::initial_sad_from_entropy([0; 32]), + }) + .info() + } + } +} + pub fn to_overlay_node( node_id: NodeId, nodes: Vec, diff --git a/simulations/src/node/dummy.rs b/simulations/src/node/dummy.rs index 24a01482..2284020a 100644 --- a/simulations/src/node/dummy.rs +++ b/simulations/src/node/dummy.rs @@ -451,7 +451,7 @@ mod tests { dummy::{get_child_nodes, get_parent_nodes, get_roles, DummyRole}, Node, NodeId, NodeIdExt, OverlayState, SharedState, SimulationOverlay, ViewOverlay, }, - overlay::{ + overlay::tests::{ tree::{TreeOverlay, TreeSettings}, Overlay, }, diff --git a/simulations/src/node/mod.rs b/simulations/src/node/mod.rs index b22d7605..f3aa38e2 100644 --- a/simulations/src/node/mod.rs +++ b/simulations/src/node/mod.rs @@ -16,7 +16,7 @@ use std::{ use parking_lot::RwLock; use serde::{Deserialize, Serialize}; // internal -use crate::overlay::{Layout, OverlaySettings, SimulationOverlay}; +use crate::overlay::tests::{Layout, OverlaySettings, SimulationOverlay}; pub use consensus_engine::NodeId; diff --git a/simulations/src/overlay/mod.rs b/simulations/src/overlay/mod.rs index 4a62cfef..ed65281b 100644 --- a/simulations/src/overlay/mod.rs +++ b/simulations/src/overlay/mod.rs @@ -1,178 +1,2 @@ -pub mod flat; -pub mod tree; - -// std -use std::collections::{BTreeSet, HashMap}; -// crates -use rand::Rng; -use serde::{Deserialize, Serialize}; -// internal -use crate::node::{CommitteeId, NodeId}; - -use self::tree::TreeSettings; - -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -pub struct Committee { - pub nodes: BTreeSet, -} - -impl Committee { - #[must_use] - pub fn is_empty(&self) -> bool { - self.nodes.len() == 0 - } -} - -pub type Leaders = BTreeSet; - -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -pub struct Layout { - pub committees: HashMap, - pub from_committee: HashMap, - pub parent: HashMap, - pub children: HashMap>, - pub layers: HashMap>, -} - -impl Layout { - pub fn new( - committees: HashMap, - parent: HashMap, - children: HashMap>, - layers: HashMap>, - ) -> Self { - let from_committee = committees - .iter() - .flat_map(|(&committee_id, committee)| { - committee - .nodes - .iter() - .map(move |&node_id| (node_id, committee_id)) - }) - .collect(); - Self { - committees, - from_committee, - parent, - children, - layers, - } - } - - pub fn committee(&self, node_id: NodeId) -> Option { - self.from_committee.get(&node_id).copied() - } - - pub fn committee_nodes(&self, committee_id: CommitteeId) -> &Committee { - &self.committees[&committee_id] - } - - pub fn parent(&self, committee_id: CommitteeId) -> Option { - self.parent.get(&committee_id).copied() - } - - pub fn parent_nodes(&self, committee_id: CommitteeId) -> Option { - self.parent(committee_id) - .map(|c| self.committees[&c].clone()) - } - - pub fn children(&self, committee_id: CommitteeId) -> Option<&Vec> { - self.children.get(&committee_id) - } - - pub fn children_nodes(&self, committee_id: CommitteeId) -> Vec<&Committee> { - self.children(committee_id) - .iter() - .flat_map(|&committees| committees.iter().map(|c| &self.committees[c])) - .collect() - } - - pub fn node_ids(&self) -> impl Iterator + '_ { - self.from_committee.keys().copied() - } -} - -pub enum SimulationOverlay { - Flat(flat::FlatOverlay), - Tree(tree::TreeOverlay), -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub enum OverlaySettings { - Flat, - Tree(TreeSettings), -} - -impl Default for OverlaySettings { - fn default() -> Self { - Self::Tree(Default::default()) - } -} - -impl From for OverlaySettings { - fn from(settings: TreeSettings) -> OverlaySettings { - OverlaySettings::Tree(settings) - } -} - -impl TryInto for OverlaySettings { - type Error = String; - - fn try_into(self) -> Result { - if let Self::Tree(settings) = self { - Ok(settings) - } else { - Err("unable to convert to tree settings".into()) - } - } -} - -impl Overlay for SimulationOverlay { - fn nodes(&self) -> Vec { - match self { - SimulationOverlay::Flat(overlay) => overlay.nodes(), - SimulationOverlay::Tree(overlay) => overlay.nodes(), - } - } - - fn leaders( - &self, - nodes: &[NodeId], - size: usize, - rng: &mut R, - ) -> Box> { - match self { - SimulationOverlay::Flat(overlay) => overlay.leaders(nodes, size, rng), - SimulationOverlay::Tree(overlay) => overlay.leaders(nodes, size, rng), - } - } - - fn layout(&self, nodes: &[NodeId], rng: &mut R) -> Layout { - match self { - SimulationOverlay::Flat(overlay) => overlay.layout(nodes, rng), - SimulationOverlay::Tree(overlay) => overlay.layout(nodes, rng), - } - } -} - -pub trait Overlay { - fn nodes(&self) -> Vec; - fn leaders( - &self, - nodes: &[NodeId], - size: usize, - rng: &mut R, - ) -> Box>; - fn layout(&self, nodes: &[NodeId], rng: &mut R) -> Layout; -} - -// Takes a reference to the simulation_settings and returns a SimulationOverlay instance based -// on the overlay settings specified in simulation_settings. -pub fn create_overlay(overlay_settings: &OverlaySettings) -> SimulationOverlay { - match &overlay_settings { - OverlaySettings::Flat => SimulationOverlay::Flat(flat::FlatOverlay::new()), - OverlaySettings::Tree(settings) => { - SimulationOverlay::Tree(tree::TreeOverlay::new(settings.clone())) - } - } -} +pub mod overlay_info; +pub mod tests; diff --git a/simulations/src/overlay/overlay_info.rs b/simulations/src/overlay/overlay_info.rs new file mode 100644 index 00000000..f3c4870d --- /dev/null +++ b/simulations/src/overlay/overlay_info.rs @@ -0,0 +1,181 @@ +use consensus_engine::{CommitteeId, NodeId, Overlay}; +use serde::Serialize; +use std::collections::{BTreeSet, HashMap, VecDeque}; + +pub type Blake2bU32 = blake2::Blake2b; + +#[derive(Debug, Serialize)] +pub struct OverlayInfo { + pub committees: BTreeSet, + pub committee_sizes: HashMap, + pub edges: Vec<(CommitteeId, CommitteeId)>, + pub next_leader: NodeId, + pub root_id: CommitteeId, +} + +pub trait OverlayInfoExt { + fn info(&self) -> OverlayInfo; +} + +impl OverlayInfoExt for T { + fn info(&self) -> OverlayInfo { + let mut committees = BTreeSet::new(); + let mut edges = Vec::new(); + let mut committee_sizes = HashMap::new(); + + let next_leader = self.next_leader(); + let root = self.root_committee(); + let root_id = root.id::(); + committees.insert(root_id); + committee_sizes.insert(root_id, root.len()); + + let mut queue = VecDeque::new(); + queue.push_back(root); + + while let Some(current_committee) = queue.pop_front() { + let current_id = current_committee.id::(); + + if let Some(committee_node) = current_committee.iter().next() { + for child in self.child_committees(*committee_node) { + let child_id = child.id::(); + committees.insert(child_id); + committee_sizes.insert(child_id, child.len()); + edges.push((current_id, child_id)); + queue.push_back(child); + } + } + } + + OverlayInfo { + committees, + committee_sizes, + edges, + next_leader, + root_id, + } + } +} + +#[cfg(test)] +mod tests { + use consensus_engine::{ + overlay::{ + BranchOverlay, BranchOverlaySettings, FisherYatesShuffle, RoundRobin, TreeOverlay, + TreeOverlaySettings, + }, + NodeId, Overlay, + }; + + use super::*; + const ENTROPY: [u8; 32] = [0; 32]; + + #[test] + fn tree_overlay_info() { + let nodes: Vec<_> = (0..7).map(|i| NodeId::new([i as u8; 32])).collect(); + let overlay = TreeOverlay::new(TreeOverlaySettings { + nodes: nodes.clone(), + current_leader: nodes[0], + number_of_committees: 3, + leader: RoundRobin::new(), + committee_membership: FisherYatesShuffle::new(ENTROPY), + super_majority_threshold: None, + }); + + let root_committee = overlay.root_committee(); + let root_node = root_committee.iter().next().unwrap(); + let child_committees = overlay.child_committees(*root_node); + let child1 = child_committees[0].clone(); + let child2 = child_committees[1].clone(); + + let info = overlay.info(); + let info_children: Vec<&(CommitteeId, CommitteeId)> = info + .edges + .iter() + .filter(|(p, _)| *p == info.root_id) + .collect(); + + assert_eq!(info.committees.len(), 3); + assert_eq!(root_committee.id::(), info.root_id); + + let mut info_child_iter = info_children.iter(); + let info_child1 = info_child_iter.next().map(|(_, c)| c).unwrap(); + let info_child2 = info_child_iter.next().map(|(_, c)| c).unwrap(); + + assert_eq!(child1.id::(), *info_child1); + assert_eq!(child2.id::(), *info_child2); + + assert_eq!( + child1.len(), + *info.committee_sizes.get(info_child1).unwrap() + ); + assert_eq!( + child2.len(), + *info.committee_sizes.get(info_child2).unwrap() + ); + } + + #[test] + fn branch_overlay_info() { + let nodes: Vec<_> = (0..7).map(|i| NodeId::new([i as u8; 32])).collect(); + let overlay = BranchOverlay::new(BranchOverlaySettings { + nodes: nodes.clone(), + current_leader: nodes[0], + branch_depth: 3, + leader: RoundRobin::new(), + committee_membership: FisherYatesShuffle::new(ENTROPY), + }); + + let root_committee = overlay.root_committee(); + let root_node = root_committee.iter().next().unwrap(); + + let info = overlay.info(); + assert_eq!(info.committees.len(), 3); + assert_eq!(root_committee.id::(), info.root_id); + + assert_eq!(overlay.child_committees(*root_node).len(), 1); + let layer1 = overlay + .child_committees(*root_node) + .first() + .unwrap() + .clone(); + let layer1_node = layer1.iter().next().unwrap(); + + assert_eq!(overlay.child_committees(*layer1_node).len(), 1); + let info_layer1: Vec<&(CommitteeId, CommitteeId)> = info + .edges + .iter() + .filter(|(p, _)| *p == info.root_id) + .collect(); + assert_eq!(info_layer1.len(), 1); + let info_layer1 = info_layer1.first().map(|(_, c)| c).unwrap(); + + assert_eq!(layer1.id::(), *info_layer1); + assert_eq!( + layer1.len(), + *info.committee_sizes.get(info_layer1).unwrap() + ); + + let layer2 = overlay + .child_committees(*layer1_node) + .first() + .unwrap() + .clone(); + let layer2_node = layer2.iter().next().unwrap(); + assert_eq!(overlay.child_committees(*layer2_node).len(), 0); + + let info_layer2: Vec<&(CommitteeId, CommitteeId)> = info + .edges + .iter() + .filter(|(p, _)| *p == layer1.id::()) + .collect(); + + assert_eq!(info_layer2.len(), 1); + let info_layer2 = info_layer2.first().map(|(_, c)| c).unwrap(); + + assert_eq!(layer2.id::(), *info_layer2); + assert_eq!( + layer2.len(), + *info.committee_sizes.get(info_layer2).unwrap() + ); + } +} diff --git a/simulations/src/overlay/flat.rs b/simulations/src/overlay/tests/flat.rs similarity index 95% rename from simulations/src/overlay/flat.rs rename to simulations/src/overlay/tests/flat.rs index d9050eda..9028efe8 100644 --- a/simulations/src/overlay/flat.rs +++ b/simulations/src/overlay/tests/flat.rs @@ -4,9 +4,8 @@ use consensus_engine::NodeId; use rand::prelude::IteratorRandom; use rand::Rng; // internal -use super::Overlay; +use super::{Committee, Layout, Overlay}; use crate::node::NodeIdExt; -use crate::overlay::{Committee, Layout}; pub struct FlatOverlay; impl FlatOverlay { diff --git a/simulations/src/overlay/tests/mod.rs b/simulations/src/overlay/tests/mod.rs new file mode 100644 index 00000000..4a62cfef --- /dev/null +++ b/simulations/src/overlay/tests/mod.rs @@ -0,0 +1,178 @@ +pub mod flat; +pub mod tree; + +// std +use std::collections::{BTreeSet, HashMap}; +// crates +use rand::Rng; +use serde::{Deserialize, Serialize}; +// internal +use crate::node::{CommitteeId, NodeId}; + +use self::tree::TreeSettings; + +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct Committee { + pub nodes: BTreeSet, +} + +impl Committee { + #[must_use] + pub fn is_empty(&self) -> bool { + self.nodes.len() == 0 + } +} + +pub type Leaders = BTreeSet; + +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct Layout { + pub committees: HashMap, + pub from_committee: HashMap, + pub parent: HashMap, + pub children: HashMap>, + pub layers: HashMap>, +} + +impl Layout { + pub fn new( + committees: HashMap, + parent: HashMap, + children: HashMap>, + layers: HashMap>, + ) -> Self { + let from_committee = committees + .iter() + .flat_map(|(&committee_id, committee)| { + committee + .nodes + .iter() + .map(move |&node_id| (node_id, committee_id)) + }) + .collect(); + Self { + committees, + from_committee, + parent, + children, + layers, + } + } + + pub fn committee(&self, node_id: NodeId) -> Option { + self.from_committee.get(&node_id).copied() + } + + pub fn committee_nodes(&self, committee_id: CommitteeId) -> &Committee { + &self.committees[&committee_id] + } + + pub fn parent(&self, committee_id: CommitteeId) -> Option { + self.parent.get(&committee_id).copied() + } + + pub fn parent_nodes(&self, committee_id: CommitteeId) -> Option { + self.parent(committee_id) + .map(|c| self.committees[&c].clone()) + } + + pub fn children(&self, committee_id: CommitteeId) -> Option<&Vec> { + self.children.get(&committee_id) + } + + pub fn children_nodes(&self, committee_id: CommitteeId) -> Vec<&Committee> { + self.children(committee_id) + .iter() + .flat_map(|&committees| committees.iter().map(|c| &self.committees[c])) + .collect() + } + + pub fn node_ids(&self) -> impl Iterator + '_ { + self.from_committee.keys().copied() + } +} + +pub enum SimulationOverlay { + Flat(flat::FlatOverlay), + Tree(tree::TreeOverlay), +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub enum OverlaySettings { + Flat, + Tree(TreeSettings), +} + +impl Default for OverlaySettings { + fn default() -> Self { + Self::Tree(Default::default()) + } +} + +impl From for OverlaySettings { + fn from(settings: TreeSettings) -> OverlaySettings { + OverlaySettings::Tree(settings) + } +} + +impl TryInto for OverlaySettings { + type Error = String; + + fn try_into(self) -> Result { + if let Self::Tree(settings) = self { + Ok(settings) + } else { + Err("unable to convert to tree settings".into()) + } + } +} + +impl Overlay for SimulationOverlay { + fn nodes(&self) -> Vec { + match self { + SimulationOverlay::Flat(overlay) => overlay.nodes(), + SimulationOverlay::Tree(overlay) => overlay.nodes(), + } + } + + fn leaders( + &self, + nodes: &[NodeId], + size: usize, + rng: &mut R, + ) -> Box> { + match self { + SimulationOverlay::Flat(overlay) => overlay.leaders(nodes, size, rng), + SimulationOverlay::Tree(overlay) => overlay.leaders(nodes, size, rng), + } + } + + fn layout(&self, nodes: &[NodeId], rng: &mut R) -> Layout { + match self { + SimulationOverlay::Flat(overlay) => overlay.layout(nodes, rng), + SimulationOverlay::Tree(overlay) => overlay.layout(nodes, rng), + } + } +} + +pub trait Overlay { + fn nodes(&self) -> Vec; + fn leaders( + &self, + nodes: &[NodeId], + size: usize, + rng: &mut R, + ) -> Box>; + fn layout(&self, nodes: &[NodeId], rng: &mut R) -> Layout; +} + +// Takes a reference to the simulation_settings and returns a SimulationOverlay instance based +// on the overlay settings specified in simulation_settings. +pub fn create_overlay(overlay_settings: &OverlaySettings) -> SimulationOverlay { + match &overlay_settings { + OverlaySettings::Flat => SimulationOverlay::Flat(flat::FlatOverlay::new()), + OverlaySettings::Tree(settings) => { + SimulationOverlay::Tree(tree::TreeOverlay::new(settings.clone())) + } + } +} diff --git a/simulations/src/overlay/tree.rs b/simulations/src/overlay/tests/tree.rs similarity index 100% rename from simulations/src/overlay/tree.rs rename to simulations/src/overlay/tests/tree.rs diff --git a/simulations/src/runner/sync_runner.rs b/simulations/src/runner/sync_runner.rs index 5bd7863a..7924394d 100644 --- a/simulations/src/runner/sync_runner.rs +++ b/simulations/src/runner/sync_runner.rs @@ -75,7 +75,7 @@ mod tests { Node, NodeId, NodeIdExt, OverlayState, SharedState, ViewOverlay, }, output_processors::OutData, - overlay::{ + overlay::tests::{ tree::{TreeOverlay, TreeSettings}, Overlay, SimulationOverlay, },