diff --git a/.github/workflows/test-suite.yml b/.github/workflows/test-suite.yml index 0f9689ea93..ae3a176500 100644 --- a/.github/workflows/test-suite.yml +++ b/.github/workflows/test-suite.yml @@ -49,12 +49,21 @@ jobs: - uses: actions/checkout@v1 - name: Build the root Dockerfile run: docker build . - simulator-ubuntu: + eth1-simulator-ubuntu: runs-on: ubuntu-latest needs: cargo-fmt steps: - uses: actions/checkout@v1 - name: Install ganache-cli run: sudo npm install -g ganache-cli - - name: Run the beacon chain sim - run: cargo run --release --bin simulator beacon-chain-sim + - name: Run the beacon chain sim that starts from an eth1 contract + run: cargo run --release --bin simulator eth1-sim + no-eth1-simulator-ubuntu: + runs-on: ubuntu-latest + needs: cargo-fmt + steps: + - uses: actions/checkout@v1 + - name: Install ganache-cli + run: sudo npm install -g ganache-cli + - name: Run the beacon chain sim without an eth1 connection + run: cargo run --release --bin simulator no-eth1-sim diff --git a/tests/simulator/src/cli.rs b/tests/simulator/src/cli.rs index 93c8cb051b..95c4bcc19f 100644 --- a/tests/simulator/src/cli.rs +++ b/tests/simulator/src/cli.rs @@ -6,7 +6,7 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> { .author("Sigma Prime ") .about("Options for interacting with simulator") .subcommand( - SubCommand::with_name("beacon-chain-sim") + SubCommand::with_name("eth1-sim") .about( "Lighthouse Beacon Chain Simulator creates `n` beacon node and validator clients, \ each with `v` validators. A deposit contract is deployed at the start of the \ @@ -22,17 +22,48 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> { .short("n") .long("nodes") .takes_value(true) - .help("Number of beacon nodes (default 4)")) + .default_value("4") + .help("Number of beacon nodes")) .arg(Arg::with_name("validators_per_node") .short("v") .long("validators_per_node") .takes_value(true) - .help("Number of validators (default 20)")) + .default_value("20") + .help("Number of validators")) .arg(Arg::with_name("speed_up_factor") .short("s") .long("speed_up_factor") .takes_value(true) - .help("Speed up factor (default 4)")) + .default_value("4") + .help("Speed up factor")) + .arg(Arg::with_name("end_after_checks") + .short("e") + .long("end_after_checks") + .takes_value(false) + .help("End after checks (default true)")) + ) + .subcommand( + SubCommand::with_name("no-eth1-sim") + .about("Runs a simulator that bypasses the eth1 chain. Useful for faster testing of + components that don't rely upon eth1") + .arg(Arg::with_name("nodes") + .short("n") + .long("nodes") + .takes_value(true) + .default_value("4") + .help("Number of beacon nodes")) + .arg(Arg::with_name("validators_per_node") + .short("v") + .long("validators_per_node") + .takes_value(true) + .default_value("20") + .help("Number of validators")) + .arg(Arg::with_name("speed_up_factor") + .short("s") + .long("speed_up_factor") + .takes_value(true) + .default_value("4") + .help("Speed up factor")) .arg(Arg::with_name("end_after_checks") .short("e") .long("end_after_checks") diff --git a/tests/simulator/src/eth1_sim.rs b/tests/simulator/src/eth1_sim.rs new file mode 100644 index 0000000000..3413701c5d --- /dev/null +++ b/tests/simulator/src/eth1_sim.rs @@ -0,0 +1,208 @@ +use crate::{checks, LocalNetwork, E}; +use clap::ArgMatches; +use eth1_test_rig::GanacheEth1Instance; +use futures::{future, stream, Future, Stream}; +use node_test_rig::{ + environment::EnvironmentBuilder, testing_client_config, ClientGenesis, ValidatorConfig, +}; +use std::net::{IpAddr, Ipv4Addr}; +use std::time::{Duration, Instant}; +use tokio::timer::Interval; + +pub fn run_eth1_sim(matches: &ArgMatches) -> Result<(), String> { + let node_count = value_t!(matches, "nodes", usize).expect("missing nodes default"); + let validators_per_node = value_t!(matches, "validators_per_node", usize) + .expect("missing validators_per_node default"); + let speed_up_factor = + value_t!(matches, "speed_up_factor", u64).expect("missing speed_up_factor default"); + let mut end_after_checks = true; + if matches.is_present("end_after_checks") { + end_after_checks = false; + } + + println!("Beacon Chain Simulator:"); + println!(" nodes:{}", node_count); + println!(" validators_per_node:{}", validators_per_node); + println!(" end_after_checks:{}", end_after_checks); + + let log_level = "debug"; + let log_format = None; + + let mut env = EnvironmentBuilder::minimal() + .async_logger(log_level, log_format)? + .multi_threaded_tokio_runtime()? + .build()?; + + let eth1_block_time = Duration::from_millis(15_000 / speed_up_factor); + + let spec = &mut env.eth2_config.spec; + + spec.milliseconds_per_slot /= speed_up_factor; + spec.eth1_follow_distance = 16; + spec.min_genesis_delay = eth1_block_time.as_secs() * spec.eth1_follow_distance * 2; + spec.min_genesis_time = 0; + spec.min_genesis_active_validator_count = 64; + spec.seconds_per_eth1_block = 1; + + let slot_duration = Duration::from_millis(spec.milliseconds_per_slot); + let initial_validator_count = spec.min_genesis_active_validator_count as usize; + let total_validator_count = validators_per_node * node_count; + let deposit_amount = env.eth2_config.spec.max_effective_balance; + + let context = env.core_context(); + let executor = context.executor.clone(); + + let future = GanacheEth1Instance::new() + /* + * Deploy the deposit contract, spawn tasks to keep creating new blocks and deposit + * validators. + */ + .map(move |ganache_eth1_instance| { + let deposit_contract = ganache_eth1_instance.deposit_contract; + let ganache = ganache_eth1_instance.ganache; + let eth1_endpoint = ganache.endpoint(); + let deposit_contract_address = deposit_contract.address(); + + // Start a timer that produces eth1 blocks on an interval. + executor.spawn( + Interval::new(Instant::now(), eth1_block_time) + .map_err(|_| eprintln!("Eth1 block timer failed")) + .for_each(move |_| ganache.evm_mine().map_err(|_| ())) + .map_err(|_| eprintln!("Eth1 evm_mine failed")) + .map(|_| ()), + ); + + // Submit deposits to the deposit contract. + executor.spawn( + stream::unfold(0..total_validator_count, move |mut iter| { + iter.next().map(|i| { + println!("Submitting deposit for validator {}...", i); + deposit_contract + .deposit_deterministic_async::(i, deposit_amount) + .map(|_| ((), iter)) + }) + }) + .collect() + .map(|_| ()) + .map_err(|e| eprintln!("Error submitting deposit: {}", e)), + ); + + let mut beacon_config = testing_client_config(); + + beacon_config.genesis = ClientGenesis::DepositContract; + beacon_config.eth1.endpoint = eth1_endpoint; + beacon_config.eth1.deposit_contract_address = deposit_contract_address; + beacon_config.eth1.deposit_contract_deploy_block = 0; + beacon_config.eth1.lowest_cached_block_number = 0; + beacon_config.eth1.follow_distance = 1; + beacon_config.dummy_eth1_backend = false; + beacon_config.sync_eth1_chain = true; + + beacon_config.network.enr_address = Some(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))); + + beacon_config + }) + /* + * Create a new `LocalNetwork` with one beacon node. + */ + .and_then(move |beacon_config| { + LocalNetwork::new(context, beacon_config.clone()) + .map(|network| (network, beacon_config)) + }) + /* + * One by one, add beacon nodes to the network. + */ + .and_then(move |(network, beacon_config)| { + let network_1 = network.clone(); + + stream::unfold(0..node_count - 1, move |mut iter| { + iter.next().map(|_| { + network_1 + .add_beacon_node(beacon_config.clone()) + .map(|()| ((), iter)) + }) + }) + .collect() + .map(|_| network) + }) + /* + * One by one, add validator clients to the network. Each validator client is attached to + * a single corresponding beacon node. + */ + .and_then(move |network| { + let network_1 = network.clone(); + + // Note: presently the validator client future will only resolve once genesis time + // occurs. This is great for this scenario, but likely to change in the future. + // + // If the validator client future behaviour changes, we would need to add a new future + // that delays until genesis. Otherwise, all of the checks that start in the next + // future will start too early. + + stream::unfold(0..node_count, move |mut iter| { + iter.next().map(|i| { + let indices = (i * validators_per_node..(i + 1) * validators_per_node) + .collect::>(); + + network_1 + .add_validator_client(ValidatorConfig::default(), i, indices) + .map(|()| ((), iter)) + }) + }) + .collect() + .map(|_| network) + }) + /* + * Start the processes that will run checks on the network as it runs. + */ + .and_then(move |network| { + // The `final_future` either completes immediately or never completes, depending on the value + // of `end_after_checks`. + let final_future: Box + Send> = + if end_after_checks { + Box::new(future::ok(()).map_err(|()| "".to_string())) + } else { + Box::new(future::empty().map_err(|()| "".to_string())) + }; + + future::ok(()) + // Check that the chain finalizes at the first given opportunity. + .join(checks::verify_first_finalization( + network.clone(), + slot_duration, + )) + // Check that the chain starts with the expected validator count. + .join(checks::verify_initial_validator_count( + network.clone(), + slot_duration, + initial_validator_count, + )) + // Check that validators greater than `spec.min_genesis_active_validator_count` are + // onboarded at the first possible opportunity. + .join(checks::verify_validator_onboarding( + network.clone(), + slot_duration, + total_validator_count, + )) + // End now or run forever, depending on the `end_after_checks` flag. + .join(final_future) + .map(|_| network) + }) + /* + * End the simulation by dropping the network. This will kill all running beacon nodes and + * validator clients. + */ + .map(|network| { + println!( + "Simulation complete. Finished with {} beacon nodes and {} validator clients", + network.beacon_node_count(), + network.validator_client_count() + ); + + // Be explicit about dropping the network, as this kills all the nodes. This ensures + // all the checks have adequate time to pass. + drop(network) + }); + + env.runtime().block_on(future) +} diff --git a/tests/simulator/src/main.rs b/tests/simulator/src/main.rs index 85be8ccae0..742f110874 100644 --- a/tests/simulator/src/main.rs +++ b/tests/simulator/src/main.rs @@ -18,21 +18,14 @@ extern crate clap; mod checks; mod cli; +mod eth1_sim; mod local_network; +mod no_eth1_sim; mod sync_sim; -use clap::ArgMatches; use cli::cli_app; use env_logger::{Builder, Env}; -use eth1_test_rig::GanacheEth1Instance; -use futures::{future, stream, Future, Stream}; use local_network::LocalNetwork; -use node_test_rig::{ - environment::EnvironmentBuilder, testing_client_config, ClientGenesis, ValidatorConfig, -}; -use std::time::{Duration, Instant}; -use sync_sim::*; -use tokio::timer::Interval; use types::MinimalEthSpec; pub type E = MinimalEthSpec; @@ -43,14 +36,21 @@ fn main() { let matches = cli_app().get_matches(); match matches.subcommand() { - ("beacon-chain-sim", Some(matches)) => match run_beacon_chain_sim(matches) { + ("eth1-sim", Some(matches)) => match eth1_sim::run_eth1_sim(matches) { Ok(()) => println!("Simulation exited successfully"), Err(e) => { eprintln!("Simulation exited with error: {}", e); std::process::exit(1) } }, - ("syncing-sim", Some(matches)) => match run_syncing_sim(matches) { + ("no-eth1-sim", Some(matches)) => match no_eth1_sim::run_no_eth1_sim(matches) { + Ok(()) => println!("Simulation exited successfully"), + Err(e) => { + eprintln!("Simulation exited with error: {}", e); + std::process::exit(1) + } + }, + ("syncing-sim", Some(matches)) => match sync_sim::run_syncing_sim(matches) { Ok(()) => println!("Simulation exited successfully"), Err(e) => { eprintln!("Simulation exited with error: {}", e); @@ -63,319 +63,3 @@ fn main() { } } } - -fn run_beacon_chain_sim(matches: &ArgMatches) -> Result<(), String> { - let nodes = value_t!(matches, "nodes", usize).unwrap_or(4); - let validators_per_node = value_t!(matches, "validators_per_node", usize).unwrap_or(20); - let speed_up_factor = value_t!(matches, "nodes", u64).unwrap_or(4); - let mut end_after_checks = true; - if matches.is_present("end_after_checks") { - end_after_checks = false; - } - - println!("Beacon Chain Simulator:"); - println!(" nodes:{}", nodes); - println!(" validators_per_node:{}", validators_per_node); - println!(" end_after_checks:{}", end_after_checks); - - let log_level = "debug"; - let log_format = None; - - beacon_chain_sim( - nodes, - validators_per_node, - speed_up_factor, - log_level, - log_format, - end_after_checks, - ) -} - -fn run_syncing_sim(matches: &ArgMatches) -> Result<(), String> { - let initial_delay = value_t!(matches, "initial_delay", u64).unwrap_or(50); - let sync_delay = value_t!(matches, "sync_delay", u64).unwrap_or(10); - let speed_up_factor = value_t!(matches, "speedup", u64).unwrap_or(15); - let strategy = value_t!(matches, "strategy", String).unwrap_or("all".into()); - - println!("Syncing Simulator:"); - println!(" initial_delay:{}", initial_delay); - println!(" sync delay:{}", sync_delay); - println!(" speed up factor:{}", speed_up_factor); - println!(" strategy:{}", strategy); - - let log_level = "debug"; - let log_format = None; - - syncing_sim( - speed_up_factor, - initial_delay, - sync_delay, - strategy, - log_level, - log_format, - ) -} - -fn syncing_sim( - speed_up_factor: u64, - initial_delay: u64, - sync_delay: u64, - strategy: String, - log_level: &str, - log_format: Option<&str>, -) -> Result<(), String> { - let mut env = EnvironmentBuilder::minimal() - .async_logger(log_level, log_format)? - .multi_threaded_tokio_runtime()? - .build()?; - - let spec = &mut env.eth2_config.spec; - let end_after_checks = true; - - spec.milliseconds_per_slot = spec.milliseconds_per_slot / speed_up_factor; - spec.min_genesis_time = 0; - spec.min_genesis_active_validator_count = 16; - - let slot_duration = Duration::from_millis(spec.milliseconds_per_slot); - - let context = env.core_context(); - let beacon_config = testing_client_config(); - let num_validators = 8; - let future = LocalNetwork::new(context, beacon_config.clone()) - /* - * Add a validator client which handles all validators from the genesis state. - */ - .and_then(move |network| { - network - .add_validator_client(ValidatorConfig::default(), 0, (0..num_validators).collect()) - .map(|_| network) - }) - /* - * Start the processes that will run checks on the network as it runs. - */ - .and_then(move |network| { - // The `final_future` either completes immediately or never completes, depending on the value - // of `end_after_checks`. - let final_future: Box + Send> = - if end_after_checks { - Box::new(future::ok(()).map_err(|()| "".to_string())) - } else { - Box::new(future::empty().map_err(|()| "".to_string())) - }; - - future::ok(()) - // Check all syncing strategies one after other. - .join(pick_strategy( - &strategy, - network.clone(), - beacon_config.clone(), - slot_duration, - initial_delay, - sync_delay, - )) - .join(final_future) - .map(|_| network) - }) - /* - * End the simulation by dropping the network. This will kill all running beacon nodes and - * validator clients. - */ - .map(|network| { - println!( - "Simulation complete. Finished with {} beacon nodes and {} validator clients", - network.beacon_node_count(), - network.validator_client_count() - ); - - // Be explicit about dropping the network, as this kills all the nodes. This ensures - // all the checks have adequate time to pass. - drop(network) - }); - - env.runtime().block_on(future) -} - -fn beacon_chain_sim( - node_count: usize, - validators_per_node: usize, - speed_up_factor: u64, - log_level: &str, - log_format: Option<&str>, - end_after_checks: bool, -) -> Result<(), String> { - let mut env = EnvironmentBuilder::minimal() - .async_logger(log_level, log_format)? - .multi_threaded_tokio_runtime()? - .build()?; - - let eth1_block_time = Duration::from_millis(15_000 / speed_up_factor); - - let spec = &mut env.eth2_config.spec; - - spec.milliseconds_per_slot /= speed_up_factor; - spec.eth1_follow_distance = 16; - spec.min_genesis_delay = eth1_block_time.as_secs() * spec.eth1_follow_distance * 2; - spec.min_genesis_time = 0; - spec.min_genesis_active_validator_count = 64; - spec.seconds_per_eth1_block = 1; - - let slot_duration = Duration::from_millis(spec.milliseconds_per_slot); - let initial_validator_count = spec.min_genesis_active_validator_count as usize; - let total_validator_count = validators_per_node * node_count; - let deposit_amount = env.eth2_config.spec.max_effective_balance; - - let context = env.core_context(); - let executor = context.executor.clone(); - - let future = GanacheEth1Instance::new() - /* - * Deploy the deposit contract, spawn tasks to keep creating new blocks and deposit - * validators. - */ - .map(move |ganache_eth1_instance| { - let deposit_contract = ganache_eth1_instance.deposit_contract; - let ganache = ganache_eth1_instance.ganache; - let eth1_endpoint = ganache.endpoint(); - let deposit_contract_address = deposit_contract.address(); - - // Start a timer that produces eth1 blocks on an interval. - executor.spawn( - Interval::new(Instant::now(), eth1_block_time) - .map_err(|_| eprintln!("Eth1 block timer failed")) - .for_each(move |_| ganache.evm_mine().map_err(|_| ())) - .map_err(|_| eprintln!("Eth1 evm_mine failed")) - .map(|_| ()), - ); - - // Submit deposits to the deposit contract. - executor.spawn( - stream::unfold(0..total_validator_count, move |mut iter| { - iter.next().map(|i| { - println!("Submitting deposit for validator {}...", i); - deposit_contract - .deposit_deterministic_async::(i, deposit_amount) - .map(|_| ((), iter)) - }) - }) - .collect() - .map(|_| ()) - .map_err(|e| eprintln!("Error submitting deposit: {}", e)), - ); - - let mut beacon_config = testing_client_config(); - - beacon_config.genesis = ClientGenesis::DepositContract; - beacon_config.eth1.endpoint = eth1_endpoint; - beacon_config.eth1.deposit_contract_address = deposit_contract_address; - beacon_config.eth1.deposit_contract_deploy_block = 0; - beacon_config.eth1.lowest_cached_block_number = 0; - beacon_config.eth1.follow_distance = 1; - beacon_config.dummy_eth1_backend = false; - beacon_config.sync_eth1_chain = true; - - beacon_config - }) - /* - * Create a new `LocalNetwork` with one beacon node. - */ - .and_then(move |beacon_config| { - LocalNetwork::new(context, beacon_config.clone()) - .map(|network| (network, beacon_config)) - }) - /* - * One by one, add beacon nodes to the network. - */ - .and_then(move |(network, beacon_config)| { - let network_1 = network.clone(); - - stream::unfold(0..node_count - 1, move |mut iter| { - iter.next().map(|_| { - network_1 - .add_beacon_node(beacon_config.clone()) - .map(|()| ((), iter)) - }) - }) - .collect() - .map(|_| network) - }) - /* - * One by one, add validator clients to the network. Each validator client is attached to - * a single corresponding beacon node. - */ - .and_then(move |network| { - let network_1 = network.clone(); - - // Note: presently the validator client future will only resolve once genesis time - // occurs. This is great for this scenario, but likely to change in the future. - // - // If the validator client future behaviour changes, we would need to add a new future - // that delays until genesis. Otherwise, all of the checks that start in the next - // future will start too early. - - stream::unfold(0..node_count, move |mut iter| { - iter.next().map(|i| { - let indices = (i * validators_per_node..(i + 1) * validators_per_node) - .collect::>(); - - network_1 - .add_validator_client(ValidatorConfig::default(), i, indices) - .map(|()| ((), iter)) - }) - }) - .collect() - .map(|_| network) - }) - /* - * Start the processes that will run checks on the network as it runs. - */ - .and_then(move |network| { - // The `final_future` either completes immediately or never completes, depending on the value - // of `end_after_checks`. - let final_future: Box + Send> = - if end_after_checks { - Box::new(future::ok(()).map_err(|()| "".to_string())) - } else { - Box::new(future::empty().map_err(|()| "".to_string())) - }; - - future::ok(()) - // Check that the chain finalizes at the first given opportunity. - .join(checks::verify_first_finalization( - network.clone(), - slot_duration, - )) - // Check that the chain starts with the expected validator count. - .join(checks::verify_initial_validator_count( - network.clone(), - slot_duration, - initial_validator_count, - )) - // Check that validators greater than `spec.min_genesis_active_validator_count` are - // onboarded at the first possible opportunity. - .join(checks::verify_validator_onboarding( - network.clone(), - slot_duration, - total_validator_count, - )) - // End now or run forever, depending on the `end_after_checks` flag. - .join(final_future) - .map(|_| network) - }) - /* - * End the simulation by dropping the network. This will kill all running beacon nodes and - * validator clients. - */ - .map(|network| { - println!( - "Simulation complete. Finished with {} beacon nodes and {} validator clients", - network.beacon_node_count(), - network.validator_client_count() - ); - - // Be explicit about dropping the network, as this kills all the nodes. This ensures - // all the checks have adequate time to pass. - drop(network) - }); - - env.runtime().block_on(future) -} diff --git a/tests/simulator/src/no_eth1_sim.rs b/tests/simulator/src/no_eth1_sim.rs new file mode 100644 index 0000000000..b4d233909f --- /dev/null +++ b/tests/simulator/src/no_eth1_sim.rs @@ -0,0 +1,150 @@ +use crate::{checks, LocalNetwork}; +use clap::ArgMatches; +use futures::{future, stream, Future, Stream}; +use node_test_rig::{ + environment::EnvironmentBuilder, testing_client_config, ClientGenesis, ValidatorConfig, +}; +use std::net::{IpAddr, Ipv4Addr}; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; + +pub fn run_no_eth1_sim(matches: &ArgMatches) -> Result<(), String> { + let node_count = value_t!(matches, "nodes", usize).expect("missing nodes default"); + let validators_per_node = value_t!(matches, "validators_per_node", usize) + .expect("missing validators_per_node default"); + let speed_up_factor = + value_t!(matches, "speed_up_factor", u64).expect("missing speed_up_factor default"); + let mut end_after_checks = true; + if matches.is_present("end_after_checks") { + end_after_checks = false; + } + + println!("Beacon Chain Simulator:"); + println!(" nodes:{}", node_count); + println!(" validators_per_node:{}", validators_per_node); + println!(" end_after_checks:{}", end_after_checks); + + let log_level = "debug"; + let log_format = None; + + let mut env = EnvironmentBuilder::mainnet() + .async_logger(log_level, log_format)? + .multi_threaded_tokio_runtime()? + .build()?; + + let eth1_block_time = Duration::from_millis(15_000 / speed_up_factor); + + let spec = &mut env.eth2_config.spec; + + spec.milliseconds_per_slot /= speed_up_factor; + spec.eth1_follow_distance = 16; + spec.min_genesis_delay = eth1_block_time.as_secs() * spec.eth1_follow_distance * 2; + spec.min_genesis_time = 0; + spec.min_genesis_active_validator_count = 64; + spec.seconds_per_eth1_block = 1; + + let genesis_time = SystemTime::now() + .duration_since(UNIX_EPOCH) + .map_err(|_| "should get system time")? + + Duration::from_secs(5); + + let slot_duration = Duration::from_millis(spec.milliseconds_per_slot); + let total_validator_count = validators_per_node * node_count; + + let context = env.core_context(); + + let mut beacon_config = testing_client_config(); + + beacon_config.genesis = ClientGenesis::Interop { + validator_count: total_validator_count, + genesis_time: genesis_time.as_secs(), + }; + beacon_config.dummy_eth1_backend = true; + beacon_config.sync_eth1_chain = true; + + beacon_config.network.enr_address = Some(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))); + + let future = LocalNetwork::new(context, beacon_config.clone()) + /* + * One by one, add beacon nodes to the network. + */ + .and_then(move |network| { + let network_1 = network.clone(); + + stream::unfold(0..node_count - 1, move |mut iter| { + iter.next().map(|_| { + network_1 + .add_beacon_node(beacon_config.clone()) + .map(|()| ((), iter)) + }) + }) + .collect() + .map(|_| network) + }) + /* + * One by one, add validator clients to the network. Each validator client is attached to + * a single corresponding beacon node. + */ + .and_then(move |network| { + let network_1 = network.clone(); + + // Note: presently the validator client future will only resolve once genesis time + // occurs. This is great for this scenario, but likely to change in the future. + // + // If the validator client future behaviour changes, we would need to add a new future + // that delays until genesis. Otherwise, all of the checks that start in the next + // future will start too early. + + stream::unfold(0..node_count, move |mut iter| { + iter.next().map(|i| { + let indices = (i * validators_per_node..(i + 1) * validators_per_node) + .collect::>(); + + network_1 + .add_validator_client(ValidatorConfig::default(), i, indices) + .map(|()| ((), iter)) + }) + }) + .collect() + .map(|_| network) + }) + /* + * Start the processes that will run checks on the network as it runs. + */ + .and_then(move |network| { + // The `final_future` either completes immediately or never completes, depending on the value + // of `end_after_checks`. + let final_future: Box + Send> = + if end_after_checks { + Box::new(future::ok(()).map_err(|()| "".to_string())) + } else { + Box::new(future::empty().map_err(|()| "".to_string())) + }; + + future::ok(()) + // Check that the chain finalizes at the first given opportunity. + .join(checks::verify_first_finalization( + network.clone(), + slot_duration, + )) + // End now or run forever, depending on the `end_after_checks` flag. + .join(final_future) + .map(|_| network) + }) + /* + * End the simulation by dropping the network. This will kill all running beacon nodes and + * validator clients. + */ + .map(|network| { + println!( + "Simulation complete. Finished with {} beacon nodes and {} validator clients", + network.beacon_node_count(), + network.validator_client_count() + ); + + // Be explicit about dropping the network, as this kills all the nodes. This ensures + // all the checks have adequate time to pass. + drop(network) + }); + + env.runtime().block_on(future) +} diff --git a/tests/simulator/src/sync_sim.rs b/tests/simulator/src/sync_sim.rs index 6c18406b6e..68dec54f90 100644 --- a/tests/simulator/src/sync_sim.rs +++ b/tests/simulator/src/sync_sim.rs @@ -1,10 +1,116 @@ use crate::checks::{epoch_delay, verify_all_finalized_at}; use crate::local_network::LocalNetwork; -use futures::{Future, IntoFuture}; +use clap::ArgMatches; +use futures::{future, Future, IntoFuture}; use node_test_rig::ClientConfig; +use node_test_rig::{environment::EnvironmentBuilder, testing_client_config, ValidatorConfig}; use std::time::Duration; use types::{Epoch, EthSpec}; +pub fn run_syncing_sim(matches: &ArgMatches) -> Result<(), String> { + let initial_delay = value_t!(matches, "initial_delay", u64).unwrap_or(50); + let sync_delay = value_t!(matches, "sync_delay", u64).unwrap_or(10); + let speed_up_factor = value_t!(matches, "speedup", u64).unwrap_or(15); + let strategy = value_t!(matches, "strategy", String).unwrap_or("all".into()); + + println!("Syncing Simulator:"); + println!(" initial_delay:{}", initial_delay); + println!(" sync delay:{}", sync_delay); + println!(" speed up factor:{}", speed_up_factor); + println!(" strategy:{}", strategy); + + let log_level = "debug"; + let log_format = None; + + syncing_sim( + speed_up_factor, + initial_delay, + sync_delay, + strategy, + log_level, + log_format, + ) +} + +fn syncing_sim( + speed_up_factor: u64, + initial_delay: u64, + sync_delay: u64, + strategy: String, + log_level: &str, + log_format: Option<&str>, +) -> Result<(), String> { + let mut env = EnvironmentBuilder::minimal() + .async_logger(log_level, log_format)? + .multi_threaded_tokio_runtime()? + .build()?; + + let spec = &mut env.eth2_config.spec; + let end_after_checks = true; + + spec.milliseconds_per_slot = spec.milliseconds_per_slot / speed_up_factor; + spec.min_genesis_time = 0; + spec.min_genesis_active_validator_count = 16; + + let slot_duration = Duration::from_millis(spec.milliseconds_per_slot); + let context = env.core_context(); + let num_validators = 8; + let beacon_config = testing_client_config(); + + let future = LocalNetwork::new(context, beacon_config.clone()) + /* + * Add a validator client which handles all validators from the genesis state. + */ + .and_then(move |network| { + network + .add_validator_client(ValidatorConfig::default(), 0, (0..num_validators).collect()) + .map(|_| network) + }) + /* + * Start the processes that will run checks on the network as it runs. + */ + .and_then(move |network| { + // The `final_future` either completes immediately or never completes, depending on the value + // of `end_after_checks`. + let final_future: Box + Send> = + if end_after_checks { + Box::new(future::ok(()).map_err(|()| "".to_string())) + } else { + Box::new(future::empty().map_err(|()| "".to_string())) + }; + + future::ok(()) + // Check all syncing strategies one after other. + .join(pick_strategy( + &strategy, + network.clone(), + beacon_config.clone(), + slot_duration, + initial_delay, + sync_delay, + )) + .join(final_future) + .map(|_| network) + }) + /* + * End the simulation by dropping the network. This will kill all running beacon nodes and + * validator clients. + */ + .map(|network| { + println!( + "Simulation complete. Finished with {} beacon nodes and {} validator clients", + network.beacon_node_count(), + network.validator_client_count() + ); + + // Be explicit about dropping the network, as this kills all the nodes. This ensures + // all the checks have adequate time to pass. + drop(network) + }); + + env.runtime().block_on(future) +} + pub fn pick_strategy( strategy: &str, network: LocalNetwork,