Add configuration for simulation app (#94)

* make simulation app compile to wasm

* add configuration

* improve args parsing

* add steps in configuration

---------

Co-authored-by: Daniel Sanchez Quiros <sanchez.quiros.daniel@gmail.com>
This commit is contained in:
Al Liu 2023-03-20 17:13:55 +08:00 committed by GitHub
parent 1ec4231a7a
commit 8a1af8c234
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 241 additions and 38 deletions

View File

@ -113,7 +113,7 @@ impl HttpBackend for AxumBackend {
let router = self.router.clone();
let service = tower::service_fn(move |request: Request<Body>| {
let mut router = router.lock().clone();
async move { router.call(request).await }
router.call(request)
});
axum::Server::bind(&self.config.address)
@ -125,8 +125,8 @@ impl HttpBackend for AxumBackend {
impl AxumBackend {
fn add_data_route(&self, method: HttpMethod, path: &str, req_stream: Sender<HttpRequest>) {
let handle_data = |Query(query): Query<HashMap<String, String>>, payload: Option<Bytes>| async move {
handle_req(req_stream, query, payload).await
let handle_data = |Query(query): Query<HashMap<String, String>>, payload: Option<Bytes>| {
handle_req(req_stream, query, payload)
};
let handler = match method {

View File

@ -7,9 +7,11 @@ edition = "2021"
[dependencies]
rand = { version = "0.8", features = ["small_rng"] }
serde = { version = "1.0", features = ["derive"] }
serde = { version = "1.0", features = ["derive", "rc"] }
serde_json = "1.0"
clap = { version = "4", features = ["derive"] }
[target.'cfg(target_arch = "wasm32")'.dependencies]
getrandom = { version = "0.2", features = ["js"] }

View File

@ -1,3 +1,146 @@
pub fn main() {
println!("WIP");
use std::{path::PathBuf, str::FromStr};
use clap::Parser;
use rand::thread_rng;
use serde::{Deserialize, Serialize};
use simulations::{
config::Config,
node::{
carnot::{CarnotNode, CarnotStep},
Node, StepTime,
},
overlay::{flat::FlatOverlay, Overlay},
runner::{ConsensusRunner, LayoutNodes},
};
/// Simple program to greet a person
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Args {
/// Path for a yaml-encoded network config file
config: std::path::PathBuf,
#[arg(long, default_value_t = OverlayType::Flat)]
overlay_type: OverlayType,
#[arg(long, default_value_t = NodeType::Carnot)]
node_type: NodeType,
#[arg(short, long, default_value_t = OutputType::StdOut)]
output: OutputType,
}
#[derive(clap::ValueEnum, Debug, Copy, Clone, Serialize, Deserialize)]
enum OverlayType {
Flat,
}
impl core::fmt::Display for OverlayType {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::Flat => write!(f, "flat"),
}
}
}
#[derive(clap::ValueEnum, Debug, Copy, Clone, Serialize, Deserialize)]
enum NodeType {
Carnot,
}
impl core::fmt::Display for NodeType {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::Carnot => write!(f, "carnot"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
enum OutputType {
File(PathBuf),
StdOut,
StdErr,
}
impl core::fmt::Display for OutputType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
OutputType::File(path) => write!(f, "{}", path.display()),
OutputType::StdOut => write!(f, "stdout"),
OutputType::StdErr => write!(f, "stderr"),
}
}
}
impl FromStr for OutputType {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"stdout" => Ok(Self::StdOut),
"stderr" => Ok(Self::StdErr),
path => Ok(Self::File(PathBuf::from(path))),
}
}
}
pub fn main() -> Result<(), Box<dyn std::error::Error>> {
let Args {
config,
overlay_type,
node_type,
output,
} = Args::parse();
let report = match (overlay_type, node_type) {
(OverlayType::Flat, NodeType::Carnot) => {
let cfg = serde_json::from_reader::<
_,
Config<
<CarnotNode as Node>::Settings,
<FlatOverlay as Overlay>::Settings,
CarnotStep,
>,
>(std::fs::File::open(config)?)?;
#[allow(clippy::unit_arg)]
let overlay = FlatOverlay::new(cfg.overlay_settings);
let node_ids = (0..cfg.node_count).collect::<Vec<_>>();
let mut rng = thread_rng();
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)
}
};
let json = serde_json::to_string_pretty(&report)?;
match output {
OutputType::File(f) => {
use std::{fs::OpenOptions, io::Write};
let mut file = OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(f)?;
file.write_all(json.as_bytes())?;
}
OutputType::StdOut => println!("{json}"),
OutputType::StdErr => eprintln!("{json}"),
}
Ok(())
}

18
simulations/src/config.rs Normal file
View File

@ -0,0 +1,18 @@
use crate::network::regions::Region;
use crate::node::StepTime;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Serialize, Deserialize)]
pub struct Config<N, O, S>
where
S: core::str::FromStr,
{
pub network_behaviors: HashMap<(Region, Region), StepTime>,
pub regions: Vec<Region>,
pub overlay_settings: O,
pub node_settings: N,
pub node_count: usize,
pub committee_size: usize,
pub steps: Vec<S>,
}

View File

@ -1,3 +1,4 @@
pub mod config;
pub mod network;
pub mod node;
pub mod overlay;

View File

@ -5,7 +5,7 @@ use rand::Rng;
use serde::{Deserialize, Serialize};
// internal
#[derive(Default, Serialize, Deserialize)]
#[derive(Default, Debug, Serialize, Deserialize)]
pub struct NetworkBehaviour {
pub delay: Duration,
pub drop: f64,

View File

@ -8,6 +8,7 @@ use crate::node::NodeId;
pub mod behaviour;
pub mod regions;
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct Network {
pub regions: regions::RegionsData,
}

View File

@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize};
// internal
use crate::{network::behaviour::NetworkBehaviour, node::NodeId};
#[derive(Copy, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
pub enum Region {
NorthAmerica,
Europe,
@ -15,7 +15,7 @@ pub enum Region {
Australia,
}
#[derive(Serialize, Deserialize)]
#[derive(Debug, Serialize, Deserialize)]
pub struct RegionsData {
pub regions: HashMap<Region, Vec<NodeId>>,
#[serde(skip)]

View File

@ -49,7 +49,7 @@ fn receive_commit(
.unwrap()
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize)]
pub enum CarnotStep {
ReceiveProposal,
ValidateProposal,
@ -57,6 +57,20 @@ pub enum CarnotStep {
ValidateVote,
}
impl core::str::FromStr for CarnotStep {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.trim().to_lowercase().as_str() {
"receiveproposal" => Ok(Self::ReceiveProposal),
"validateproposal" => Ok(Self::ValidateProposal),
"receivevote" => Ok(Self::ReceiveVote),
"validatevote" => Ok(Self::ValidateVote),
_ => Err(format!("Unknown step: {s}")),
}
}
}
#[derive(Clone)]
pub enum CarnotStepSolver {
Plain(StepTime),
@ -64,8 +78,30 @@ pub enum CarnotStepSolver {
ChildCommitteeReceiverSolver(ChildCommitteeReceiverSolver),
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize)]
pub enum CarnotStepSolverType {
Plain(StepTime),
ParentCommitteeReceiverSolver,
ChildCommitteeReceiverSolver,
}
impl CarnotStepSolverType {
pub fn to_solver(self) -> CarnotStepSolver {
match self {
Self::Plain(time) => CarnotStepSolver::Plain(time),
Self::ParentCommitteeReceiverSolver => {
CarnotStepSolver::ParentCommitteeReceiverSolver(receive_proposal)
}
Self::ChildCommitteeReceiverSolver => {
CarnotStepSolver::ChildCommitteeReceiverSolver(receive_commit)
}
}
}
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct CarnotNodeSettings {
pub steps_costs: HashMap<CarnotStep, CarnotStepSolver>,
pub steps_costs: HashMap<CarnotStep, CarnotStepSolverType>,
pub network: Network,
pub layout: Layout,
}
@ -76,22 +112,22 @@ pub struct CarnotNode {
settings: Rc<CarnotNodeSettings>,
}
pub const CARNOT_STEPS_COSTS: &[(CarnotStep, CarnotStepSolver)] = &[
pub const CARNOT_STEPS_COSTS: &[(CarnotStep, CarnotStepSolverType)] = &[
(
CarnotStep::ReceiveProposal,
CarnotStepSolver::ParentCommitteeReceiverSolver(receive_proposal),
CarnotStepSolverType::ParentCommitteeReceiverSolver,
),
(
CarnotStep::ValidateProposal,
CarnotStepSolver::Plain(StepTime::from_secs(1)),
CarnotStepSolverType::Plain(StepTime::from_secs(1)),
),
(
CarnotStep::ReceiveVote,
CarnotStepSolver::ChildCommitteeReceiverSolver(receive_commit),
CarnotStepSolverType::ChildCommitteeReceiverSolver,
),
(
CarnotStep::ValidateVote,
CarnotStepSolver::Plain(StepTime::from_secs(1)),
CarnotStepSolverType::Plain(StepTime::from_secs(1)),
),
];
@ -130,27 +166,27 @@ impl Node for CarnotNode {
fn run_step(&mut self, step: Self::Step) -> StepTime {
use CarnotStepSolver::*;
match self.settings.steps_costs.get(&step) {
Some(Plain(t)) => *t,
Some(ParentCommitteeReceiverSolver(solver)) => solver(
&mut self.rng,
self.id,
self.settings
.layout
.parent_nodes(self.settings.layout.committee(self.id)),
&self.settings.network,
),
Some(ChildCommitteeReceiverSolver(solver)) => solver(
&mut self.rng,
self.id,
&self
.settings
.layout
.children_nodes(self.settings.layout.committee(self.id)),
&self.settings.network,
),
None => {
panic!("Unknown step: {step:?}");
}
Some(step) => match step.to_solver() {
Plain(t) => t,
ParentCommitteeReceiverSolver(solver) => solver(
&mut self.rng,
self.id,
self.settings
.layout
.parent_nodes(self.settings.layout.committee(self.id)),
&self.settings.network,
),
ChildCommitteeReceiverSolver(solver) => solver(
&mut self.rng,
self.id,
&self
.settings
.layout
.children_nodes(self.settings.layout.committee(self.id)),
&self.settings.network,
),
},
None => panic!("Unknown step: {step:?}"),
}
}
}

View File

@ -10,6 +10,7 @@ use crate::node::{CommitteeId, NodeId};
pub type Committee = BTreeSet<NodeId>;
pub type Leaders = BTreeSet<NodeId>;
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct Layout {
pub committees: HashMap<CommitteeId, Committee>,
pub from_committee: HashMap<NodeId, CommitteeId>,
@ -73,6 +74,7 @@ impl Layout {
pub trait Overlay {
type Settings;
fn new(settings: Self::Settings) -> Self;
fn leaders<R: Rng>(
&self,

View File

@ -14,7 +14,7 @@ pub struct ConsensusRunner<N> {
}
#[allow(dead_code)]
#[derive(Debug)]
#[derive(Debug, serde::Serialize)]
pub struct Report {
round_time: Duration,
}