Tree overlay for simulation app (#101)

* Add simulations crate

* Firs working runner

* Cleanupt

* Extract overlay from runner

* Fix off id calls

* Fix testing values

* WIP: Tree overlay for simulation app

* Build tree overlay from depth and size

* Tree overlay generation tests

* WIP: Committee role in layout

* Use commtittee role when constructing node

* Pairprogramming role calculation

* WIP: Extract step runner to node

* Aggregate times from different overlay layers

* Runner for overlay with layers

* Use StepRng in runner tests

* Decouple role from node

* Define leader steps and behaviour

* Get possible node ids from the overlay

* Handle unknown state in flat overlay

* Use solver type for deserialization

* Remove refcell from nodes hashmap

* Create layout from the provided node ids

* Break runner loops into seperate functions

* Use for loop to collect node step times

* Revert leader times collection

* Add interegion latency tests

---------

Co-authored-by: danielsanchezq <sanchez.quiros.daniel@gmail.com>
This commit is contained in:
gusto 2023-03-23 10:32:54 +02:00 committed by GitHub
parent 8a1af8c234
commit 2d8c9f1099
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 734 additions and 178 deletions

View File

@ -14,4 +14,3 @@ clap = { version = "4", features = ["derive"] }
[target.'cfg(target_arch = "wasm32")'.dependencies]
getrandom = { version = "0.2", features = ["js"] }

View File

@ -10,7 +10,7 @@ use simulations::{
Node, StepTime,
},
overlay::{flat::FlatOverlay, Overlay},
runner::{ConsensusRunner, LayoutNodes},
runner::ConsensusRunner,
};
/// Simple program to greet a person
@ -96,7 +96,7 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
_,
Config<
<CarnotNode as Node>::Settings,
<FlatOverlay as Overlay>::Settings,
<FlatOverlay as Overlay<CarnotNode>>::Settings,
CarnotStep,
>,
>(std::fs::File::open(config)?)?;
@ -107,23 +107,10 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
let layout = overlay.layout(&node_ids, &mut rng);
let leaders = overlay.leaders(&node_ids, 1, &mut rng).collect();
let carnot_steps: Vec<_> = cfg
.steps
.iter()
.copied()
.map(|step| {
(
LayoutNodes::Leader,
step,
Box::new(|times: &[StepTime]| *times.iter().max().unwrap())
as Box<dyn Fn(&[StepTime]) -> StepTime>,
)
})
.collect();
let mut runner: simulations::runner::ConsensusRunner<CarnotNode> =
ConsensusRunner::new(&mut rng, layout, leaders, cfg.node_settings);
runner.run(&carnot_steps)
runner.run(Box::new(|times: &[StepTime]| *times.iter().max().unwrap())
as Box<dyn Fn(&[StepTime]) -> StepTime>)
}
};

View File

@ -2,6 +2,7 @@
use std::collections::HashMap;
use std::hash::Hash;
use std::rc::Rc;
use std::time::Duration;
// crates
use rand::prelude::SmallRng;
use rand::{Rng, SeedableRng};
@ -10,12 +11,28 @@ use crate::network::Network;
use crate::node::{Node, NodeId, StepTime};
use crate::overlay::{Committee, Layout};
pub type RootCommitteeReceiverSolver = fn(&mut SmallRng, NodeId, &[NodeId], &Network) -> StepTime;
pub type ParentCommitteeReceiverSolver =
fn(&mut SmallRng, NodeId, &Committee, &Network) -> StepTime;
pub type ChildCommitteeReceiverSolver =
fn(&mut SmallRng, NodeId, &[&Committee], &Network) -> StepTime;
fn leader_receive_proposal(
rng: &mut SmallRng,
node: NodeId,
leader_nodes: &[NodeId],
network: &Network,
) -> StepTime {
assert!(!leader_nodes.is_empty());
leader_nodes
.iter()
.filter_map(|&sender| network.send_message_cost(rng, sender, node))
.max()
.unwrap()
}
fn receive_proposal(
rng: &mut SmallRng,
node: NodeId,
@ -24,6 +41,7 @@ fn receive_proposal(
) -> StepTime {
assert!(!committee.is_empty());
committee
.nodes
.iter()
.filter_map(|&sender| network.send_message_cost(rng, sender, node))
.max()
@ -41,6 +59,7 @@ fn receive_commit(
.iter()
.filter_map(|committee| {
committee
.nodes
.iter()
.filter_map(|&sender| network.send_message_cost(rng, sender, node))
.max()
@ -51,10 +70,13 @@ fn receive_commit(
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize)]
pub enum CarnotStep {
RootReceiveProposal,
ReceiveProposal,
ValidateProposal,
LeaderReceiveVote,
ReceiveVote,
ValidateVote,
Ignore,
}
impl core::str::FromStr for CarnotStep {
@ -76,6 +98,8 @@ pub enum CarnotStepSolver {
Plain(StepTime),
ParentCommitteeReceiverSolver(ParentCommitteeReceiverSolver),
ChildCommitteeReceiverSolver(ChildCommitteeReceiverSolver),
LeaderToCommitteeReceiverSolver(ChildCommitteeReceiverSolver),
RootCommitteeReceiverSolver(RootCommitteeReceiverSolver),
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize)]
@ -83,6 +107,8 @@ pub enum CarnotStepSolverType {
Plain(StepTime),
ParentCommitteeReceiverSolver,
ChildCommitteeReceiverSolver,
LeaderToCommitteeReceiverSolver,
RootCommitteeReceiverSolver,
}
impl CarnotStepSolverType {
@ -95,6 +121,12 @@ impl CarnotStepSolverType {
Self::ChildCommitteeReceiverSolver => {
CarnotStepSolver::ChildCommitteeReceiverSolver(receive_commit)
}
Self::LeaderToCommitteeReceiverSolver => {
CarnotStepSolver::LeaderToCommitteeReceiverSolver(receive_commit)
}
Self::RootCommitteeReceiverSolver => {
CarnotStepSolver::RootCommitteeReceiverSolver(leader_receive_proposal)
}
}
}
}
@ -104,15 +136,29 @@ pub struct CarnotNodeSettings {
pub steps_costs: HashMap<CarnotStep, CarnotStepSolverType>,
pub network: Network,
pub layout: Layout,
pub leaders: Vec<NodeId>,
}
#[derive(Clone)]
pub struct CarnotNode {
id: NodeId,
rng: SmallRng,
settings: Rc<CarnotNodeSettings>,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize)]
pub enum CarnotRole {
Leader,
Root,
Intermediate,
Leaf,
}
pub const CARNOT_STEPS_COSTS: &[(CarnotStep, CarnotStepSolverType)] = &[
(
CarnotStep::RootReceiveProposal,
CarnotStepSolverType::RootCommitteeReceiverSolver,
),
(
CarnotStep::ReceiveProposal,
CarnotStepSolverType::ParentCommitteeReceiverSolver,
@ -121,6 +167,10 @@ pub const CARNOT_STEPS_COSTS: &[(CarnotStep, CarnotStepSolverType)] = &[
CarnotStep::ValidateProposal,
CarnotStepSolverType::Plain(StepTime::from_secs(1)),
),
(
CarnotStep::LeaderReceiveVote,
CarnotStepSolverType::LeaderToCommitteeReceiverSolver,
),
(
CarnotStep::ReceiveVote,
CarnotStepSolverType::ChildCommitteeReceiverSolver,
@ -129,12 +179,17 @@ pub const CARNOT_STEPS_COSTS: &[(CarnotStep, CarnotStepSolverType)] = &[
CarnotStep::ValidateVote,
CarnotStepSolverType::Plain(StepTime::from_secs(1)),
),
(
CarnotStep::Ignore,
CarnotStepSolverType::Plain(StepTime::from_secs(0)),
),
];
pub const CARNOT_LEADER_STEPS: &[CarnotStep] = &[CarnotStep::ReceiveVote, CarnotStep::ValidateVote];
pub const CARNOT_LEADER_STEPS: &[CarnotStep] =
&[CarnotStep::LeaderReceiveVote, CarnotStep::ValidateVote];
pub const CARNOT_ROOT_STEPS: &[CarnotStep] = &[
CarnotStep::ReceiveProposal,
CarnotStep::RootReceiveProposal,
CarnotStep::ValidateProposal,
CarnotStep::ReceiveVote,
CarnotStep::ValidateVote,
@ -150,32 +205,39 @@ pub const CARNOT_INTERMEDIATE_STEPS: &[CarnotStep] = &[
pub const CARNOT_LEAF_STEPS: &[CarnotStep] =
&[CarnotStep::ReceiveProposal, CarnotStep::ValidateProposal];
pub const CARNOT_UNKNOWN_MESSAGE_RECEIVED_STEPS: &[CarnotStep] = &[CarnotStep::Ignore];
impl Node for CarnotNode {
type Settings = Rc<CarnotNodeSettings>;
type Step = CarnotStep;
fn new<R: Rng>(rng: &mut R, id: NodeId, settings: Self::Settings) -> Self {
let rng = SmallRng::from_rng(rng).unwrap();
Self { id, settings, rng }
Self { id, rng, settings }
}
fn id(&self) -> usize {
self.id
}
fn run_step(&mut self, step: Self::Step) -> StepTime {
fn run_steps(&mut self, steps: &[CarnotStep]) -> StepTime {
use CarnotStepSolver::*;
match self.settings.steps_costs.get(&step) {
Some(step) => match step.to_solver() {
let mut overall_steps_time = Duration::ZERO;
for step in steps.iter() {
let step_time = match self.settings.steps_costs.get(step).unwrap().to_solver() {
Plain(t) => t,
ParentCommitteeReceiverSolver(solver) => solver(
&mut self.rng,
self.id,
self.settings
ParentCommitteeReceiverSolver(solver) => {
match self
.settings
.layout
.parent_nodes(self.settings.layout.committee(self.id)),
&self.settings.network,
),
.parent_nodes(self.settings.layout.committee(self.id))
{
Some(parent) => {
solver(&mut self.rng, self.id, &parent, &self.settings.network)
}
None => Duration::ZERO,
}
}
ChildCommitteeReceiverSolver(solver) => solver(
&mut self.rng,
self.id,
@ -185,8 +247,27 @@ impl Node for CarnotNode {
.children_nodes(self.settings.layout.committee(self.id)),
&self.settings.network,
),
},
None => panic!("Unknown step: {step:?}"),
RootCommitteeReceiverSolver(solver) => solver(
&mut self.rng,
self.id,
&self.settings.leaders,
&self.settings.network,
),
LeaderToCommitteeReceiverSolver(solver) => {
let committees: Vec<&Committee> =
self.settings.layout.committees.values().collect();
solver(
&mut self.rng,
self.id,
&committees[..],
&self.settings.network,
)
}
};
overall_steps_time += step_time
}
overall_steps_time
}
}

View File

@ -4,16 +4,17 @@ pub mod carnot;
use std::time::Duration;
// crates
use rand::Rng;
use self::carnot::CarnotStep;
// internal
pub type NodeId = usize;
pub type CommitteeId = usize;
pub type StepTime = Duration;
pub trait Node {
pub trait Node: Clone {
type Settings;
type Step;
fn new<R: Rng>(rng: &mut R, id: NodeId, settings: Self::Settings) -> Self;
fn id(&self) -> NodeId;
fn run_step(&mut self, steps: Self::Step) -> StepTime;
fn run_steps(&mut self, steps: &[CarnotStep]) -> StepTime;
}

View File

@ -1,21 +1,26 @@
use rand::prelude::IteratorRandom;
use rand::Rng;
// std
// crates
use rand::prelude::IteratorRandom;
use rand::Rng;
// internal
use super::Overlay;
use crate::node::carnot::{CarnotNode, CarnotRole};
use crate::node::NodeId;
use crate::overlay::{Committee, Layout};
pub struct FlatOverlay;
impl Overlay for FlatOverlay {
impl Overlay<CarnotNode> for FlatOverlay {
type Settings = ();
fn new(_settings: Self::Settings) -> Self {
Self
}
fn nodes(&self) -> Vec<NodeId> {
(0..10).collect()
}
fn leaders<R: Rng>(
&self,
nodes: &[NodeId],
@ -27,10 +32,17 @@ impl Overlay for FlatOverlay {
}
fn layout<R: Rng>(&self, nodes: &[NodeId], _rng: &mut R) -> Layout {
let committees =
std::iter::once((0, nodes.iter().copied().collect::<Committee>())).collect();
let committees = std::iter::once((
0,
Committee {
nodes: nodes.iter().copied().collect(),
role: CarnotRole::Leader,
},
))
.collect();
let parent = std::iter::once((0, 0)).collect();
let children = std::iter::once((0, vec![0])).collect();
Layout::new(committees, parent, children)
let layers = std::iter::once((0, vec![0])).collect();
Layout::new(committees, parent, children, layers)
}
}

View File

@ -1,21 +1,35 @@
pub mod flat;
pub mod tree;
// std
use std::collections::{BTreeSet, HashMap};
// crates
use rand::Rng;
// internal
use crate::node::{CommitteeId, NodeId};
use crate::node::{carnot::CarnotRole, CommitteeId, Node, NodeId};
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct Committee {
pub nodes: BTreeSet<NodeId>,
pub role: CarnotRole,
}
impl Committee {
#[must_use]
pub fn is_empty(&self) -> bool {
self.nodes.len() == 0
}
}
pub type Committee = BTreeSet<NodeId>;
pub type Leaders = BTreeSet<NodeId>;
#[derive(Debug, serde::Serialize, serde::Deserialize)]
#[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<usize, Vec<CommitteeId>>,
}
impl Layout {
@ -23,11 +37,13 @@ impl Layout {
committees: HashMap<CommitteeId, Committee>,
parent: HashMap<CommitteeId, CommitteeId>,
children: HashMap<CommitteeId, Vec<CommitteeId>>,
layers: HashMap<usize, Vec<usize>>,
) -> Self {
let from_committee = committees
.iter()
.flat_map(|(&committee_id, committee)| {
committee
.nodes
.iter()
.map(move |&node_id| (node_id, committee_id))
})
@ -37,6 +53,7 @@ impl Layout {
from_committee,
parent,
children,
layers,
}
}
@ -48,12 +65,13 @@ impl Layout {
&self.committees[&committee_id]
}
pub fn parent(&self, committee_id: CommitteeId) -> CommitteeId {
self.parent[&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) -> &Committee {
&self.committees[&self.parent(committee_id)]
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) -> &[CommitteeId] {
@ -72,10 +90,11 @@ impl Layout {
}
}
pub trait Overlay {
pub trait Overlay<N: Node> {
type Settings;
fn new(settings: Self::Settings) -> Self;
fn nodes(&self) -> Vec<NodeId>;
fn leaders<R: Rng>(
&self,
nodes: &[NodeId],

View File

@ -0,0 +1,253 @@
// std
use std::collections::HashMap;
// crates
use rand::seq::IteratorRandom;
// internal
use super::{Committee, Layout, Overlay};
use crate::node::{
carnot::{CarnotNode, CarnotRole},
NodeId,
};
pub enum TreeType {
FullBinaryTree,
}
pub struct TreeSettings {
pub tree_type: TreeType,
pub committee_size: usize,
pub depth: usize,
}
pub struct TreeOverlay {
settings: TreeSettings,
}
struct TreeProperties {
committee_count: usize,
node_count: usize,
}
impl TreeOverlay {
fn build_full_binary_tree<R: rand::Rng>(
node_ids: &[NodeId],
rng: &mut R,
settings: &TreeSettings,
) -> Layout {
let properties = get_tree_properties(settings);
// For full binary tree to be formed from existing nodes
// a certain unique node count needs to be provided.
assert!(properties.node_count <= node_ids.len());
let mut committees = HashMap::new();
let mut parents = HashMap::new();
let mut children = HashMap::new();
let mut layers = HashMap::new();
for (committee_id, nodes) in node_ids
.iter()
.choose_multiple(rng, properties.node_count)
.chunks(settings.committee_size)
.enumerate()
{
let mut has_children = false;
let left_child_id = 2 * committee_id + 1;
let right_child_id = left_child_id + 1;
// Check for leaf nodes.
if right_child_id <= properties.committee_count {
children.insert(committee_id, vec![left_child_id, right_child_id]);
has_children = true;
}
// Root node has no parent.
if committee_id > 0 {
let parent_id = get_parent_id(committee_id);
parents.insert(committee_id, parent_id);
}
let role = match (committee_id, has_children) {
(0, _) => CarnotRole::Root,
(_, true) => CarnotRole::Intermediate,
(_, false) => CarnotRole::Leaf,
};
let committee = Committee {
nodes: nodes.iter().copied().copied().collect(),
role,
};
committees.insert(committee_id, committee);
layers
.entry(get_layer(committee_id))
.or_insert_with(Vec::new)
.push(committee_id);
}
Layout::new(committees, parents, children, layers)
}
}
impl Overlay<CarnotNode> for TreeOverlay {
type Settings = TreeSettings;
fn new(settings: Self::Settings) -> Self {
Self { settings }
}
fn nodes(&self) -> Vec<NodeId> {
let properties = get_tree_properties(&self.settings);
(0..properties.node_count).collect()
}
fn leaders<R: rand::Rng>(
&self,
nodes: &[NodeId],
size: usize,
rng: &mut R,
) -> Box<dyn Iterator<Item = NodeId>> {
let leaders = nodes.iter().copied().choose_multiple(rng, size).into_iter();
Box::new(leaders)
}
fn layout<R: rand::Rng>(&self, nodes: &[NodeId], rng: &mut R) -> Layout {
match self.settings.tree_type {
TreeType::FullBinaryTree => Self::build_full_binary_tree(nodes, rng, &self.settings),
}
}
}
fn get_tree_properties(settings: &TreeSettings) -> TreeProperties {
let committee_count = committee_count(settings.depth);
let node_count = committee_count * settings.committee_size;
TreeProperties {
committee_count,
node_count,
}
}
/// Returns the number of nodes in the whole tree.
/// `depth` parameter assumes that roots is included.
fn committee_count(depth: usize) -> usize {
(1 << depth) - 1
}
fn get_parent_id(id: usize) -> usize {
(id - 1 + id % 2) / 2
}
/// Get a layer in a tree of a given committee id.
fn get_layer(id: usize) -> usize {
(id as f64 + 1.).log2().floor() as usize
}
#[cfg(test)]
mod tests {
use super::*;
use rand::rngs::mock::StepRng;
#[test]
fn build_full_depth_1() {
let mut rng = StepRng::new(1, 0);
let overlay = TreeOverlay::new(TreeSettings {
tree_type: TreeType::FullBinaryTree,
depth: 1,
committee_size: 1,
});
let nodes = overlay.nodes();
let layout = overlay.layout(&nodes, &mut rng);
assert_eq!(layout.committees.len(), 1);
assert!(layout.children.is_empty());
assert!(layout.parent.is_empty());
}
#[test]
fn build_full_depth_3() {
let mut rng = StepRng::new(1, 0);
let overlay = TreeOverlay::new(TreeSettings {
tree_type: TreeType::FullBinaryTree,
depth: 3,
committee_size: 1,
});
let nodes = overlay.nodes();
let layout = overlay.layout(&nodes, &mut rng);
assert_eq!(layout.children[&0], vec![1, 2]);
assert_eq!(layout.parent[&1], 0);
assert_eq!(layout.parent[&2], 0);
assert_eq!(layout.children[&1], vec![3, 4]);
assert_eq!(layout.children[&2], vec![5, 6]);
assert_eq!(layout.parent[&3], 1);
assert_eq!(layout.children.get(&3), None);
assert_eq!(layout.parent[&4], 1);
assert_eq!(layout.children.get(&4), None);
assert_eq!(layout.parent[&5], 2);
assert_eq!(layout.children.get(&5), None);
assert_eq!(layout.parent[&6], 2);
assert_eq!(layout.children.get(&6), None);
}
#[test]
fn build_full_committee_size() {
let mut rng = StepRng::new(1, 0);
let overlay = TreeOverlay::new(TreeSettings {
tree_type: TreeType::FullBinaryTree,
depth: 10,
committee_size: 10,
});
let nodes = overlay.nodes();
let layout = overlay.layout(&nodes, &mut rng);
// 2^h - 1
assert_eq!(layout.committees.len(), 1023);
let root_nodes = &layout.committees[&0].nodes;
assert_eq!(root_nodes.len(), 10);
assert_eq!(root_nodes.first(), Some(&0));
assert_eq!(root_nodes.last(), Some(&9));
let last_nodes = &layout.committees[&1022].nodes;
assert_eq!(last_nodes.len(), 10);
assert_eq!(last_nodes.first(), Some(&10220));
assert_eq!(last_nodes.last(), Some(&10229));
}
#[test]
fn check_committee_role() {
let mut rng = StepRng::new(1, 0);
let overlay = TreeOverlay::new(TreeSettings {
tree_type: TreeType::FullBinaryTree,
depth: 3,
committee_size: 1,
});
let nodes = overlay.nodes();
let layout = overlay.layout(&nodes, &mut rng);
assert_eq!(layout.committees[&0].role, CarnotRole::Root);
assert_eq!(layout.committees[&1].role, CarnotRole::Intermediate);
assert_eq!(layout.committees[&2].role, CarnotRole::Intermediate);
assert_eq!(layout.committees[&3].role, CarnotRole::Leaf);
assert_eq!(layout.committees[&6].role, CarnotRole::Leaf);
}
#[test]
fn check_layers() {
let mut rng = StepRng::new(1, 0);
let overlay = TreeOverlay::new(TreeSettings {
tree_type: TreeType::FullBinaryTree,
depth: 4,
committee_size: 1,
});
let nodes = overlay.nodes();
let layout = overlay.layout(&nodes, &mut rng);
assert_eq!(layout.layers[&0], vec![0]);
assert_eq!(layout.layers[&1], vec![1, 2]);
assert_eq!(layout.layers[&2], vec![3, 4, 5, 6]);
}
}

View File

@ -1,16 +1,17 @@
// 1 - Leader forwards a proposal - Leader builds proposal
// 2 - Every committee member receives the proposal and validates it
//
use crate::node::carnot::{
CarnotRole, CARNOT_INTERMEDIATE_STEPS, CARNOT_LEADER_STEPS, CARNOT_LEAF_STEPS,
CARNOT_ROOT_STEPS, CARNOT_UNKNOWN_MESSAGE_RECEIVED_STEPS,
};
use crate::node::{Node, NodeId, StepTime};
use crate::overlay::Layout;
use rand::Rng;
use std::collections::HashMap;
use std::time::Duration;
pub struct ConsensusRunner<N> {
nodes: Vec<N>,
layout: Layout,
pub struct ConsensusRunner<N: Node> {
nodes: HashMap<NodeId, N>,
leaders: Vec<NodeId>,
layout: Layout,
}
#[allow(dead_code)]
@ -19,14 +20,7 @@ pub struct Report {
round_time: Duration,
}
#[derive(Copy, Clone)]
pub enum LayoutNodes {
Leader,
Committee,
LeafCommittee,
}
pub type ExecutionSteps<S> = [(LayoutNodes, S, Box<dyn Fn(&[StepTime]) -> StepTime>)];
type Reducer = Box<dyn Fn(&[StepTime]) -> StepTime>;
impl<N: Node> ConsensusRunner<N>
where
@ -40,7 +34,10 @@ where
) -> Self {
let nodes = layout
.node_ids()
.map(|id| N::new(&mut rng, id, node_settings.clone()))
.map(|id| {
let node = N::new(&mut rng, id, node_settings.clone());
(id, node)
})
.collect();
Self {
nodes,
@ -49,166 +46,373 @@ where
}
}
pub fn node_ids(&self) -> impl Iterator<Item = NodeId> + '_ {
self.nodes.iter().map(Node::id)
}
pub fn run(&mut self, execution: &ExecutionSteps<N::Step>) -> Report
where
N::Step: Clone,
{
pub fn run(&mut self, reducer: Reducer) -> Report {
let leaders = &self.leaders;
let layout = &self.layout;
let round_time = execution
let mut leader_times = leaders
.iter()
.map(|(layout_node, step, reducer)| {
let times: Vec<StepTime> = match layout_node {
LayoutNodes::Leader => leaders
.iter()
.map(|&leader| self.nodes[leader].run_step(step.clone()))
.collect(),
LayoutNodes::Committee => {
let non_leaf_committees = layout
.children
.iter()
.filter_map(|(id, children)| (!children.is_empty()).then_some(id));
non_leaf_committees
.flat_map(|committee_id| {
layout
.committees
.get(committee_id)
.unwrap()
.iter()
.map(|&node| self.nodes[node].run_step(step.clone()))
.max()
})
.collect()
}
LayoutNodes::LeafCommittee => {
let leaf_committees =
layout.children.iter().filter_map(|(id, children)| {
(children.is_empty() || (layout.parent(*id) == *id)).then_some(id)
});
leaf_committees
.flat_map(|committee_id| {
layout
.committees
.get(committee_id)
.unwrap()
.iter()
.map(|&node| self.nodes[node].run_step(step.clone()))
.max()
})
.collect()
}
};
reducer(&times)
.map(|leader_node| {
vec![self
.nodes
.get_mut(leader_node)
.unwrap()
.run_steps(CARNOT_LEADER_STEPS)]
})
.sum();
.collect();
let mut layer_times = Vec::new();
for layer_nodes in layout
.layers
.values()
.map(|committees| get_layer_nodes(committees, layout))
{
let times: Vec<StepTime> = layer_nodes
.iter()
.map(|(committee_id, node_id)| {
let steps = match layout.committees[committee_id].role {
CarnotRole::Root => CARNOT_ROOT_STEPS,
CarnotRole::Intermediate => CARNOT_INTERMEDIATE_STEPS,
CarnotRole::Leaf => CARNOT_LEAF_STEPS,
_ => {
// TODO: Should leader act as a leaf in a flat overlay?
CARNOT_UNKNOWN_MESSAGE_RECEIVED_STEPS
}
};
self.nodes.get_mut(node_id).unwrap().run_steps(steps)
})
.collect();
layer_times.push(times)
}
layer_times.append(&mut leader_times);
let round_time = layer_times.iter().map(|d| reducer(d)).sum();
Report { round_time }
}
}
fn get_layer_nodes(layer_committees: &[NodeId], layout: &Layout) -> Vec<(NodeId, NodeId)> {
layer_committees
.iter()
.flat_map(|committee_id| get_committee_nodes(committee_id, layout))
.collect()
}
fn get_committee_nodes(committee: &NodeId, layout: &Layout) -> Vec<(NodeId, NodeId)> {
layout.committees[committee]
.nodes
.clone()
.into_iter()
.map(|node_id| (*committee, node_id))
.collect()
}
#[cfg(test)]
mod test {
use crate::network::behaviour::NetworkBehaviour;
use crate::network::regions::{Region, RegionsData};
use crate::network::Network;
use crate::node::carnot::{
CarnotNode, CarnotNodeSettings, CARNOT_LEADER_STEPS, CARNOT_LEAF_STEPS, CARNOT_STEPS_COSTS,
use crate::{
network::{
behaviour::NetworkBehaviour,
regions::{Region, RegionsData},
Network,
},
node::{
carnot::{CarnotNode, CarnotNodeSettings, CARNOT_STEPS_COSTS},
NodeId, StepTime,
},
overlay::{
flat::FlatOverlay,
tree::{TreeOverlay, TreeSettings, TreeType},
Overlay,
},
runner::{ConsensusRunner, Reducer},
};
use crate::node::{NodeId, StepTime};
use crate::overlay::flat::FlatOverlay;
use crate::overlay::Overlay;
use crate::runner::{ConsensusRunner, LayoutNodes};
use rand::rngs::SmallRng;
use rand::{Rng, SeedableRng};
use std::rc::Rc;
use std::time::Duration;
use rand::{rngs::mock::StepRng, Rng};
use std::{collections::HashMap, rc::Rc, time::Duration};
fn setup_runner<R: Rng, O: Overlay>(
fn setup_runner<R: Rng, O: Overlay<CarnotNode>>(
mut rng: &mut R,
overlay: &O,
) -> ConsensusRunner<CarnotNode> {
let regions = std::iter::once((Region::Europe, (0..10).collect())).collect();
let node_ids = overlay.nodes();
let layout = overlay.layout(&node_ids, &mut rng);
let leaders: Vec<NodeId> = overlay.leaders(&node_ids, 1, &mut rng).collect();
let regions = std::iter::once((Region::Europe, node_ids.clone())).collect();
let network_behaviour = std::iter::once((
(Region::Europe, Region::Europe),
NetworkBehaviour::new(Duration::from_millis(100), 0.0),
))
.collect();
let node_ids: Vec<NodeId> = (0..10).collect();
let layout = overlay.layout(&node_ids, &mut rng);
let leaders = overlay.leaders(&node_ids, 1, &mut rng).collect();
let node_settings: CarnotNodeSettings = CarnotNodeSettings {
steps_costs: CARNOT_STEPS_COSTS.iter().cloned().collect(),
network: Network::new(RegionsData::new(regions, network_behaviour)),
layout: overlay.layout(&node_ids, &mut rng),
leaders: leaders.clone(),
};
ConsensusRunner::new(&mut rng, layout, leaders, Rc::new(node_settings))
}
#[test]
fn test_run_flat_single_leader_steps() {
let mut rng = SmallRng::seed_from_u64(0);
fn run_flat_single_leader_steps() {
let mut rng = StepRng::new(1, 0);
let overlay = FlatOverlay::new(());
let mut runner = setup_runner(&mut rng, &overlay);
let carnot_steps: Vec<_> = CARNOT_LEADER_STEPS
.iter()
.copied()
.map(|step| {
(
LayoutNodes::Leader,
step,
Box::new(|times: &[StepTime]| *times.iter().max().unwrap())
as Box<dyn Fn(&[StepTime]) -> StepTime>,
)
})
.collect();
assert_eq!(
Duration::from_millis(1100),
runner.run(&carnot_steps).round_time
runner
.run(Box::new(|times: &[StepTime]| *times.iter().max().unwrap()) as Reducer)
.round_time
);
}
#[test]
fn test_run_flat_single_leader_single_committee() {
let mut rng = SmallRng::seed_from_u64(0);
let overlay = FlatOverlay::new(());
fn run_tree_committee_1() {
let mut rng = StepRng::new(1, 0);
let overlay = TreeOverlay::new(TreeSettings {
tree_type: TreeType::FullBinaryTree,
depth: 3,
committee_size: 1,
});
let mut runner: ConsensusRunner<CarnotNode> = setup_runner(&mut rng, &overlay);
let leader_steps = CARNOT_LEADER_STEPS.iter().copied().map(|step| {
(
LayoutNodes::Leader,
step,
Box::new(|times: &[StepTime]| *times.iter().max().unwrap())
as Box<dyn Fn(&[StepTime]) -> StepTime>,
)
});
// # Leader (1 node):
//
// - 100ms - LeaderReceiveVote,
// - 1s - ValidateVote,
//
// Expected times [1.1s]
let committee_steps = CARNOT_LEAF_STEPS.iter().copied().map(|step| {
(
LayoutNodes::LeafCommittee,
step,
Box::new(|times: &[StepTime]| *times.iter().max().unwrap())
as Box<dyn Fn(&[StepTime]) -> StepTime>,
)
});
// # Root (1 node):
//
// - 100ms - RootReceiveProposal,
// - 1s - ValidateProposal,
// - 100ms - ReceiveVote,
// - 1s - ValidateVote,
//
// Expected times [2.2s]
let carnot_steps: Vec<_> = leader_steps.chain(committee_steps).collect();
// # Intermediary (2 nodes):
//
// - 100ms - ReceiveProposal,
// - 1s - ValidateProposal,
// - 100ms - ReceiveVote,
// - 1s - ValidateVote,
//
// Expected times [2.2s, 2.2s]
// # Leaf (4 nodes):
//
// - 100ms - ReceiveProposal
// - 1s - ValidateProposal
//
// Expected times [1.1s, 1.1s, 1.1s, 1.1s]
assert_eq!(
Duration::from_millis(2200),
runner.run(&carnot_steps).round_time
Duration::from_millis(6600),
runner
.run(Box::new(|times: &[StepTime]| *times.iter().max().unwrap()) as Reducer)
.round_time
);
}
#[test]
fn run_tree_committee_100() {
let mut rng = StepRng::new(1, 0);
let overlay = TreeOverlay::new(TreeSettings {
tree_type: TreeType::FullBinaryTree,
depth: 3,
committee_size: 100,
});
let mut runner: ConsensusRunner<CarnotNode> = setup_runner(&mut rng, &overlay);
assert_eq!(
Duration::from_millis(6600),
runner
.run(Box::new(|times: &[StepTime]| *times.iter().max().unwrap()) as Reducer)
.round_time
);
}
#[test]
fn run_tree_network_config_1() {
let mut rng = StepRng::new(1, 0);
let overlay = TreeOverlay::new(TreeSettings {
tree_type: TreeType::FullBinaryTree,
depth: 3,
committee_size: 1,
});
let node_ids = overlay.nodes();
let layout = overlay.layout(&node_ids, &mut rng);
// Normaly a leaders would be selected randomly, here, we're assuming it's NodeID 1.
// let leaders: Vec<NodeId> = overlay.leaders(&node_ids, 1, &mut rng).collect();
let leaders = vec![1];
let first_five = &node_ids[..5];
let rest = &node_ids[5..];
// 0
// 1 2
// 3 4 5 6
//
// # Leader - NodeID 1
//
// Sends vote to all committees.
//
// LeaderReceiveVote:
// - 100ms - Asia - Asia (1 to 0, 1, 2, 3, 4)
// - 500ms - Asia - Europe (1 to 5, 6)
//
// ValidateVote: 1s
//
// Expected times: [1.5s]
// # Root - NodeID 0
//
// Sends vote to child committees.
//
// RootReceiveProposal:
// - 100ms - Asia - Asia (0 to 1)
//
// ReceiveVote:
// - 100ms - Asia - Asia (0 to 1, 2)
//
// No network:
// - 1s - ValidateVote
// - 1s - ValidateProposal
//
// Expected times: [2.2s]
// # Intermediary - NodeID 1, 2:
//
// ReceiveVote:
// - 100ms - Asia - Asia (1 to 3, 4)
// - 500ms - Asia - Europe (2 to 5, 6)
//
// ReceiveProposal:
// - 100ms - Asia - Asia (1 to 0, 2 to 0)
//
// No network:
// - 1s - ValidateVote
// - 1s - ValidateProposal
//
// Expected times [2.2s, 2.6s]
// # Leaf - NodeID 3, 4, 5, 6
//
// ReceiveProposal:
// - 100ms - Asia - Asia ( 3, 4 to 1)
// - 500ms - Asia - Europe ( 5, 6 to 2)
//
// No network:
// - 1s - ValidateProposal
//
// Expected times [1.1s, 1.1s, 1.5s, 1.5s]
let regions = HashMap::from([
(Region::Asia, first_five.to_vec()),
(Region::Europe, rest.to_vec()),
]);
let network_behaviour = HashMap::from([
(
(Region::Asia, Region::Asia),
NetworkBehaviour::new(Duration::from_millis(100), 0.0),
),
(
(Region::Asia, Region::Europe),
NetworkBehaviour::new(Duration::from_millis(500), 0.0),
),
(
(Region::Europe, Region::Europe),
NetworkBehaviour::new(Duration::from_millis(100), 0.0),
),
]);
let node_settings: CarnotNodeSettings = CarnotNodeSettings {
steps_costs: CARNOT_STEPS_COSTS.iter().cloned().collect(),
network: Network::new(RegionsData::new(regions, network_behaviour)),
layout: overlay.layout(&node_ids, &mut rng),
leaders: leaders.clone(),
};
let mut runner =
ConsensusRunner::<CarnotNode>::new(&mut rng, layout, leaders, Rc::new(node_settings));
assert_eq!(
Duration::from_millis(7800),
runner
.run(Box::new(|times: &[StepTime]| *times.iter().max().unwrap()) as Reducer)
.round_time
);
}
#[test]
fn run_tree_network_config_100() {
let mut rng = StepRng::new(1, 0);
let overlay = TreeOverlay::new(TreeSettings {
tree_type: TreeType::FullBinaryTree,
depth: 3,
committee_size: 100,
});
let node_ids = overlay.nodes();
let layout = overlay.layout(&node_ids, &mut rng);
// Normaly a leaders would be selected randomly, here, we're assuming it's NodeID 1.
// let leaders: Vec<NodeId> = overlay.leaders(&node_ids, 1, &mut rng).collect();
let leaders = vec![1];
let two_thirds = node_ids.len() as f32 * 0.66;
let rest = &node_ids[two_thirds as usize..];
let two_thirds = &node_ids[..two_thirds as usize];
let regions = HashMap::from([
(Region::Asia, two_thirds.to_vec()),
(Region::Europe, rest.to_vec()),
]);
let network_behaviour = HashMap::from([
(
(Region::Asia, Region::Asia),
NetworkBehaviour::new(Duration::from_millis(100), 0.0),
),
(
(Region::Asia, Region::Europe),
NetworkBehaviour::new(Duration::from_millis(500), 0.0),
),
(
(Region::Europe, Region::Europe),
NetworkBehaviour::new(Duration::from_millis(100), 0.0),
),
]);
let node_settings: CarnotNodeSettings = CarnotNodeSettings {
steps_costs: CARNOT_STEPS_COSTS.iter().cloned().collect(),
network: Network::new(RegionsData::new(regions, network_behaviour)),
layout: overlay.layout(&node_ids, &mut rng),
leaders: leaders.clone(),
};
let mut runner =
ConsensusRunner::<CarnotNode>::new(&mut rng, layout, leaders, Rc::new(node_settings));
assert_eq!(
Duration::from_millis(7800),
runner
.run(Box::new(|times: &[StepTime]| *times.iter().max().unwrap()) as Reducer)
.round_time
);
}
}