use std::net::SocketAddr;
use std::time::Duration;
use crate::client_events::test::RandomEventGenerator;
use crate::config::GlobalRng;
use crate::simulation::VirtualTime;
use crate::transport::in_memory_socket::{
register_address_network, register_network_time_source, unregister_network_time_source,
};
pub type TurmoilResult = turmoil::Result;
#[derive(Clone)]
pub struct TurmoilConfig {
pub name: String,
pub gateways: usize,
pub nodes: usize,
pub ring_max_htl: usize,
pub rnd_if_htl_above: usize,
pub max_connections: usize,
pub min_connections: usize,
pub seed: u64,
pub max_contract_num: usize,
pub iterations: usize,
pub simulation_duration: Duration,
}
impl Default for TurmoilConfig {
fn default() -> Self {
Self {
name: "turmoil-sim".to_string(),
gateways: 1,
nodes: 5,
ring_max_htl: 7,
rnd_if_htl_above: 3,
max_connections: 10,
min_connections: 2,
seed: 0xDEADBEEF_CAFEBABE,
max_contract_num: 10,
iterations: 100,
simulation_duration: Duration::from_secs(60),
}
}
}
impl TurmoilConfig {
pub fn builder() -> TurmoilConfigBuilder {
TurmoilConfigBuilder::default()
}
}
#[derive(Default)]
pub struct TurmoilConfigBuilder {
config: TurmoilConfig,
}
impl TurmoilConfigBuilder {
pub fn name(mut self, name: impl Into<String>) -> Self {
self.config.name = name.into();
self
}
pub fn gateways(mut self, n: usize) -> Self {
self.config.gateways = n;
self
}
pub fn nodes(mut self, n: usize) -> Self {
self.config.nodes = n;
self
}
pub fn ring_max_htl(mut self, htl: usize) -> Self {
self.config.ring_max_htl = htl;
self
}
pub fn rnd_if_htl_above(mut self, threshold: usize) -> Self {
self.config.rnd_if_htl_above = threshold;
self
}
pub fn max_connections(mut self, n: usize) -> Self {
self.config.max_connections = n;
self
}
pub fn min_connections(mut self, n: usize) -> Self {
self.config.min_connections = n;
self
}
pub fn seed(mut self, seed: u64) -> Self {
self.config.seed = seed;
self
}
pub fn max_contract_num(mut self, n: usize) -> Self {
self.config.max_contract_num = n;
self
}
pub fn iterations(mut self, n: usize) -> Self {
self.config.iterations = n;
self
}
pub fn simulation_duration(mut self, duration: Duration) -> Self {
self.config.simulation_duration = duration;
self
}
pub fn build(self) -> TurmoilConfig {
self.config
}
}
pub fn run_turmoil_simulation<R>(config: TurmoilConfig) -> TurmoilResult
where
R: RandomEventGenerator + Send + 'static,
{
GlobalRng::set_seed(config.seed);
let virtual_time = VirtualTime::new();
register_network_time_source(&config.name, virtual_time.clone());
let mut sim = turmoil::Builder::new()
.simulation_duration(config.simulation_duration)
.rng_seed(config.seed)
.build();
let mut gateway_addrs = Vec::with_capacity(config.gateways);
let mut node_addrs = Vec::with_capacity(config.nodes);
for i in 0..config.gateways {
let port = 10000 + i as u16;
let addr: SocketAddr = format!("[::1]:{}", port).parse().unwrap();
gateway_addrs.push(addr);
register_address_network(addr, &config.name);
}
for i in 0..config.nodes {
let port = 20000 + i as u16;
let addr: SocketAddr = format!("[::1]:{}", port).parse().unwrap();
node_addrs.push(addr);
register_address_network(addr, &config.name);
}
let _total_nodes = config.gateways + config.nodes;
let network_name = config.name.clone();
for (i, _addr) in gateway_addrs.iter().enumerate() {
let host_name = format!("gateway-{}", i);
sim.host(host_name, move || async move {
tracing::info!("Gateway {} starting", i);
tokio::time::sleep(Duration::from_secs(30)).await;
tracing::info!("Gateway {} completed", i);
Ok(())
});
}
for (i, _addr) in node_addrs.iter().enumerate() {
let host_name = format!("node-{}", i);
sim.host(host_name, move || async move {
tracing::info!("Node {} starting", i);
tokio::time::sleep(Duration::from_millis(100)).await;
tokio::time::sleep(Duration::from_secs(25)).await;
tracing::info!("Node {} completed", i);
Ok(())
});
}
let result = sim.run();
unregister_network_time_source(&network_name);
result
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_turmoil_runner_basic() -> TurmoilResult {
let config = TurmoilConfig::builder()
.name("test-basic")
.gateways(1)
.nodes(2)
.seed(42)
.simulation_duration(Duration::from_secs(5))
.build();
run_turmoil_simulation::<rand::rngs::SmallRng>(config)
}
#[test]
fn test_turmoil_runner_determinism() -> TurmoilResult {
for _ in 0..2 {
let config = TurmoilConfig::builder()
.name("test-determinism")
.gateways(1)
.nodes(3)
.seed(12345)
.simulation_duration(Duration::from_secs(5))
.build();
run_turmoil_simulation::<rand::rngs::SmallRng>(config)?;
}
Ok(())
}
}