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
This commit is contained in:
gusto 2023-10-26 18:43:49 +02:00 committed by GitHub
parent 1553f29bd9
commit 54dd96dfff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 432 additions and 185 deletions

View File

@ -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"] }

View File

@ -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<BoxedNode<CarnotSettings, CarnotState>> = node_ids
.par_iter()
.copied()
@ -206,6 +221,11 @@ fn load_json_from_file<T: DeserializeOwned>(path: &Path) -> anyhow::Result<T> {
Ok(serde_json::from_reader(f)?)
}
fn dump_json_to_file<T: Serialize>(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);

View File

@ -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<NodeId>,
leader: NodeId,
overlay_settings: &OverlaySettings,
) -> OverlayInfo {
match &overlay_settings {
simulations::settings::OverlaySettings::Flat => {
FlatOverlay::<RoundRobin, FisherYatesShuffle>::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<R: Rng>(
node_id: NodeId,
nodes: Vec<NodeId>,

View File

@ -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,
},

View File

@ -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;

View File

@ -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<NodeId>,
}
impl Committee {
#[must_use]
pub fn is_empty(&self) -> bool {
self.nodes.len() == 0
}
}
pub type Leaders = BTreeSet<NodeId>;
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct Layout {
pub committees: HashMap<CommitteeId, Committee>,
pub from_committee: HashMap<NodeId, CommitteeId>,
pub parent: HashMap<CommitteeId, CommitteeId>,
pub children: HashMap<CommitteeId, Vec<CommitteeId>>,
pub layers: HashMap<CommitteeId, Vec<CommitteeId>>,
}
impl Layout {
pub fn new(
committees: HashMap<CommitteeId, Committee>,
parent: HashMap<CommitteeId, CommitteeId>,
children: HashMap<CommitteeId, Vec<CommitteeId>>,
layers: HashMap<CommitteeId, Vec<CommitteeId>>,
) -> 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<CommitteeId> {
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<CommitteeId> {
self.parent.get(&committee_id).copied()
}
pub fn parent_nodes(&self, committee_id: CommitteeId) -> Option<Committee> {
self.parent(committee_id)
.map(|c| self.committees[&c].clone())
}
pub fn children(&self, committee_id: CommitteeId) -> Option<&Vec<CommitteeId>> {
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<Item = NodeId> + '_ {
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<TreeSettings> for OverlaySettings {
fn from(settings: TreeSettings) -> OverlaySettings {
OverlaySettings::Tree(settings)
}
}
impl TryInto<TreeSettings> for OverlaySettings {
type Error = String;
fn try_into(self) -> Result<TreeSettings, Self::Error> {
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<NodeId> {
match self {
SimulationOverlay::Flat(overlay) => overlay.nodes(),
SimulationOverlay::Tree(overlay) => overlay.nodes(),
}
}
fn leaders<R: Rng>(
&self,
nodes: &[NodeId],
size: usize,
rng: &mut R,
) -> Box<dyn Iterator<Item = NodeId>> {
match self {
SimulationOverlay::Flat(overlay) => overlay.leaders(nodes, size, rng),
SimulationOverlay::Tree(overlay) => overlay.leaders(nodes, size, rng),
}
}
fn layout<R: Rng>(&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<NodeId>;
fn leaders<R: Rng>(
&self,
nodes: &[NodeId],
size: usize,
rng: &mut R,
) -> Box<dyn Iterator<Item = NodeId>>;
fn layout<R: Rng>(&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;

View File

@ -0,0 +1,181 @@
use consensus_engine::{CommitteeId, NodeId, Overlay};
use serde::Serialize;
use std::collections::{BTreeSet, HashMap, VecDeque};
pub type Blake2bU32 = blake2::Blake2b<digest::typenum::U32>;
#[derive(Debug, Serialize)]
pub struct OverlayInfo {
pub committees: BTreeSet<CommitteeId>,
pub committee_sizes: HashMap<CommitteeId, usize>,
pub edges: Vec<(CommitteeId, CommitteeId)>,
pub next_leader: NodeId,
pub root_id: CommitteeId,
}
pub trait OverlayInfoExt {
fn info(&self) -> OverlayInfo;
}
impl<T: Overlay> 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::<Blake2bU32>();
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::<Blake2bU32>();
if let Some(committee_node) = current_committee.iter().next() {
for child in self.child_committees(*committee_node) {
let child_id = child.id::<Blake2bU32>();
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::<Blake2bU32>(), 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::<Blake2bU32>(), *info_child1);
assert_eq!(child2.id::<Blake2bU32>(), *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::<Blake2bU32>(), 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::<Blake2bU32>(), *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::<Blake2bU32>())
.collect();
assert_eq!(info_layer2.len(), 1);
let info_layer2 = info_layer2.first().map(|(_, c)| c).unwrap();
assert_eq!(layer2.id::<Blake2bU32>(), *info_layer2);
assert_eq!(
layer2.len(),
*info.committee_sizes.get(info_layer2).unwrap()
);
}
}

View File

@ -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 {

View File

@ -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<NodeId>,
}
impl Committee {
#[must_use]
pub fn is_empty(&self) -> bool {
self.nodes.len() == 0
}
}
pub type Leaders = BTreeSet<NodeId>;
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct Layout {
pub committees: HashMap<CommitteeId, Committee>,
pub from_committee: HashMap<NodeId, CommitteeId>,
pub parent: HashMap<CommitteeId, CommitteeId>,
pub children: HashMap<CommitteeId, Vec<CommitteeId>>,
pub layers: HashMap<CommitteeId, Vec<CommitteeId>>,
}
impl Layout {
pub fn new(
committees: HashMap<CommitteeId, Committee>,
parent: HashMap<CommitteeId, CommitteeId>,
children: HashMap<CommitteeId, Vec<CommitteeId>>,
layers: HashMap<CommitteeId, Vec<CommitteeId>>,
) -> 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<CommitteeId> {
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<CommitteeId> {
self.parent.get(&committee_id).copied()
}
pub fn parent_nodes(&self, committee_id: CommitteeId) -> Option<Committee> {
self.parent(committee_id)
.map(|c| self.committees[&c].clone())
}
pub fn children(&self, committee_id: CommitteeId) -> Option<&Vec<CommitteeId>> {
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<Item = NodeId> + '_ {
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<TreeSettings> for OverlaySettings {
fn from(settings: TreeSettings) -> OverlaySettings {
OverlaySettings::Tree(settings)
}
}
impl TryInto<TreeSettings> for OverlaySettings {
type Error = String;
fn try_into(self) -> Result<TreeSettings, Self::Error> {
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<NodeId> {
match self {
SimulationOverlay::Flat(overlay) => overlay.nodes(),
SimulationOverlay::Tree(overlay) => overlay.nodes(),
}
}
fn leaders<R: Rng>(
&self,
nodes: &[NodeId],
size: usize,
rng: &mut R,
) -> Box<dyn Iterator<Item = NodeId>> {
match self {
SimulationOverlay::Flat(overlay) => overlay.leaders(nodes, size, rng),
SimulationOverlay::Tree(overlay) => overlay.leaders(nodes, size, rng),
}
}
fn layout<R: Rng>(&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<NodeId>;
fn leaders<R: Rng>(
&self,
nodes: &[NodeId],
size: usize,
rng: &mut R,
) -> Box<dyn Iterator<Item = NodeId>>;
fn layout<R: Rng>(&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()))
}
}
}

View File

@ -75,7 +75,7 @@ mod tests {
Node, NodeId, NodeIdExt, OverlayState, SharedState, ViewOverlay,
},
output_processors::OutData,
overlay::{
overlay::tests::{
tree::{TreeOverlay, TreeSettings},
Overlay, SimulationOverlay,
},