From f9871137124f62bad031e93223eec644a45b8dcc Mon Sep 17 00:00:00 2001 From: Mac L Date: Thu, 4 Apr 2024 21:04:56 +1100 Subject: [PATCH 01/10] Rewrite Simulator --- Cargo.lock | 5 + beacon_node/client/Cargo.toml | 1 + beacon_node/client/src/builder.rs | 29 +- beacon_node/client/src/config.rs | 5 + .../test_utils/execution_block_generator.rs | 27 +- .../src/test_utils/handle_rpc.rs | 6 + .../execution_layer/src/test_utils/mod.rs | 1 + beacon_node/genesis/src/interop.rs | 3 + beacon_node/genesis/src/lib.rs | 1 + testing/simulator/Cargo.toml | 4 + .../src/{eth1_sim.rs => basic_sim.rs} | 266 ++++-------- testing/simulator/src/checks.rs | 37 +- testing/simulator/src/cli.rs | 96 +---- testing/simulator/src/local_network.rs | 328 ++++++++++----- testing/simulator/src/main.rs | 20 +- testing/simulator/src/no_eth1_sim.rs | 172 -------- testing/simulator/src/sync_sim.rs | 392 ------------------ validator_client/src/block_service.rs | 1 + 18 files changed, 425 insertions(+), 969 deletions(-) rename testing/simulator/src/{eth1_sim.rs => basic_sim.rs} (51%) delete mode 100644 testing/simulator/src/no_eth1_sim.rs delete mode 100644 testing/simulator/src/sync_sim.rs diff --git a/Cargo.lock b/Cargo.lock index d798e9b8f2a..fffe3c9bcd6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1355,6 +1355,7 @@ dependencies = [ "time", "timer", "tokio", + "tree_hash", "types", ] @@ -7522,12 +7523,16 @@ dependencies = [ "env_logger 0.9.3", "eth1", "eth1_test_rig", + "eth2_network_config", + "ethereum-types 0.14.1", "execution_layer", "futures", "node_test_rig", "parking_lot 0.12.1", "rayon", "sensitive_url", + "serde_json", + "ssz_types", "tokio", "types", ] diff --git a/beacon_node/client/Cargo.toml b/beacon_node/client/Cargo.toml index 03cbcc9ff7f..16c4a947a66 100644 --- a/beacon_node/client/Cargo.toml +++ b/beacon_node/client/Cargo.toml @@ -46,3 +46,4 @@ execution_layer = { workspace = true } beacon_processor = { workspace = true } num_cpus = { workspace = true } ethereum_ssz = { workspace = true } +tree_hash = { workspace = true } diff --git a/beacon_node/client/src/builder.rs b/beacon_node/client/src/builder.rs index 8ae4b9e2500..2e603f69404 100644 --- a/beacon_node/client/src/builder.rs +++ b/beacon_node/client/src/builder.rs @@ -28,7 +28,10 @@ use eth2::{ }; use execution_layer::ExecutionLayer; use futures::channel::mpsc::Receiver; -use genesis::{interop_genesis_state, Eth1GenesisService, DEFAULT_ETH1_BLOCK_HASH}; +use genesis::{ + interop_genesis_state, Eth1GenesisService, DEFAULT_ETH1_BLOCK_HASH, + DEFAULT_EXECUTION_PAYLOAD_TRANSACTIONS_ROOT, +}; use lighthouse_network::{prometheus_client::registry::Registry, NetworkGlobals}; use monitoring_api::{MonitoringHttpClient, ProcessType}; use network::{NetworkConfig, NetworkSenders, NetworkService}; @@ -38,6 +41,7 @@ use slog::{debug, info, warn, Logger}; use ssz::Decode; use std::net::TcpListener; use std::path::{Path, PathBuf}; +use std::str::FromStr; use std::sync::Arc; use std::time::Duration; use std::time::{SystemTime, UNIX_EPOCH}; @@ -45,7 +49,7 @@ use timer::spawn_timer; use tokio::sync::oneshot; use types::{ test_utils::generate_deterministic_keypairs, BeaconState, BlobSidecarList, ChainSpec, EthSpec, - ExecutionBlockHash, Hash256, SignedBeaconBlock, + ExecutionBlockHash, ExecutionPayloadHeaderMerge, Hash256, SignedBeaconBlock, }; /// Interval between polling the eth1 node for genesis information. @@ -267,6 +271,27 @@ where )?; builder.genesis_state(genesis_state).map(|v| (v, None))? } + ClientGenesis::InteropMerge { + validator_count, + genesis_time, + } => { + let execution_payload_header = ExecutionPayloadHeaderMerge { + transactions_root: Hash256::from_str( + DEFAULT_EXECUTION_PAYLOAD_TRANSACTIONS_ROOT, + ) + .unwrap(), + ..Default::default() + }; + let keypairs = generate_deterministic_keypairs(validator_count); + let genesis_state = interop_genesis_state( + &keypairs, + genesis_time, + Hash256::from_slice(DEFAULT_ETH1_BLOCK_HASH), + Some(execution_payload_header.into()), + &spec, + )?; + builder.genesis_state(genesis_state).map(|v| (v, None))? + } ClientGenesis::GenesisState => { info!( context.log(), diff --git a/beacon_node/client/src/config.rs b/beacon_node/client/src/config.rs index 48ad77abc58..a441e2c186c 100644 --- a/beacon_node/client/src/config.rs +++ b/beacon_node/client/src/config.rs @@ -24,6 +24,11 @@ pub enum ClientGenesis { validator_count: usize, genesis_time: u64, }, + // Creates a genesis state similar to the 2019 Canada specs, but starting post-Merge. + InteropMerge { + validator_count: usize, + genesis_time: u64, + }, /// Reads the genesis state and other persisted data from the `Store`. FromStore, /// Connects to an eth1 node and waits until it can create the genesis state from the deposit diff --git a/beacon_node/execution_layer/src/test_utils/execution_block_generator.rs b/beacon_node/execution_layer/src/test_utils/execution_block_generator.rs index 87484ced67c..5b1e9850e47 100644 --- a/beacon_node/execution_layer/src/test_utils/execution_block_generator.rs +++ b/beacon_node/execution_layer/src/test_utils/execution_block_generator.rs @@ -176,11 +176,23 @@ impl ExecutionBlockGenerator { rng: make_rng(), }; - gen.insert_pow_block(0).unwrap(); + //gen.insert_pow_block(0).unwrap(); + // Merge from genesis + gen.insert_genesis_pos_block().unwrap(); gen } + pub fn insert_genesis_pos_block(&mut self) -> Result<(), String> { + // Insert block into block tree. + self.insert_block(Block::PoS(ExecutionPayloadMerge::default().into()))?; + + // Set block has head. + self.head_block = Some(Block::PoS(ExecutionPayloadMerge::default().into())); + + Ok(()) + } + pub fn latest_block(&self) -> Option> { self.head_block.clone() } @@ -190,6 +202,19 @@ impl ExecutionBlockGenerator { .map(|block| block.as_execution_block(self.terminal_total_difficulty)) } + pub fn genesis_block(&self) -> Option> { + if let Some(genesis_block_hash) = self.block_hashes.get(&0) { + self.blocks.get(genesis_block_hash.first()?).cloned() + } else { + None + } + } + + pub fn genesis_execution_block(&self) -> Option { + self.genesis_block() + .map(|block| block.as_execution_block(self.terminal_total_difficulty)) + } + pub fn block_by_number(&self, number: u64) -> Option> { // Get the latest canonical head block let mut latest_block = self.latest_block()?; diff --git a/beacon_node/execution_layer/src/test_utils/handle_rpc.rs b/beacon_node/execution_layer/src/test_utils/handle_rpc.rs index 77d972ab88e..e0ca07dcc6e 100644 --- a/beacon_node/execution_layer/src/test_utils/handle_rpc.rs +++ b/beacon_node/execution_layer/src/test_utils/handle_rpc.rs @@ -49,6 +49,12 @@ pub async fn handle_rpc( .latest_execution_block(), ) .unwrap()), + "0x0" => Ok(serde_json::to_value( + ctx.execution_block_generator + .read() + .genesis_execution_block(), + ) + .unwrap()), other => Err(( format!("The tag {} is not supported", other), BAD_PARAMS_ERROR_CODE, diff --git a/beacon_node/execution_layer/src/test_utils/mod.rs b/beacon_node/execution_layer/src/test_utils/mod.rs index 29ef1bb08df..c8d27defad5 100644 --- a/beacon_node/execution_layer/src/test_utils/mod.rs +++ b/beacon_node/execution_layer/src/test_utils/mod.rs @@ -58,6 +58,7 @@ mod mock_builder; mod mock_execution_layer; /// Configuration for the MockExecutionLayer. +#[derive(Clone)] pub struct MockExecutionConfig { pub server_config: Config, pub jwt_key: JwtKey, diff --git a/beacon_node/genesis/src/interop.rs b/beacon_node/genesis/src/interop.rs index b4753e92f1f..dbc791155da 100644 --- a/beacon_node/genesis/src/interop.rs +++ b/beacon_node/genesis/src/interop.rs @@ -10,6 +10,9 @@ use types::{ pub const DEFAULT_ETH1_BLOCK_HASH: &[u8] = &[0x42; 32]; +pub const DEFAULT_EXECUTION_PAYLOAD_TRANSACTIONS_ROOT: &str = + "0x7ffe241ea60187fdb0187bfa22de35d1f9bed7ab061d9401fd47e34a54fbede1"; + pub fn bls_withdrawal_credentials(pubkey: &PublicKey, spec: &ChainSpec) -> Hash256 { let mut credentials = hash(&pubkey.as_ssz_bytes()); credentials[0] = spec.bls_withdrawal_prefix_byte; diff --git a/beacon_node/genesis/src/lib.rs b/beacon_node/genesis/src/lib.rs index 3fb053bf880..ccad7bef920 100644 --- a/beacon_node/genesis/src/lib.rs +++ b/beacon_node/genesis/src/lib.rs @@ -8,5 +8,6 @@ pub use eth1_genesis_service::{Eth1GenesisService, Statistics}; pub use interop::{ bls_withdrawal_credentials, interop_genesis_state, interop_genesis_state_with_eth1, interop_genesis_state_with_withdrawal_credentials, DEFAULT_ETH1_BLOCK_HASH, + DEFAULT_EXECUTION_PAYLOAD_TRANSACTIONS_ROOT, }; pub use types::test_utils::generate_deterministic_keypairs; diff --git a/testing/simulator/Cargo.toml b/testing/simulator/Cargo.toml index eadcaf51b20..9de0d290232 100644 --- a/testing/simulator/Cargo.toml +++ b/testing/simulator/Cargo.toml @@ -19,3 +19,7 @@ env_logger = { workspace = true } clap = { workspace = true } rayon = { workspace = true } sensitive_url = { path = "../../common/sensitive_url" } +ssz_types = { workspace = true } +ethereum-types = { workspace = true } +eth2_network_config = { workspace = true } +serde_json = { workspace = true } diff --git a/testing/simulator/src/eth1_sim.rs b/testing/simulator/src/basic_sim.rs similarity index 51% rename from testing/simulator/src/eth1_sim.rs rename to testing/simulator/src/basic_sim.rs index 8d6ffc42ffa..d7c0a8283c3 100644 --- a/testing/simulator/src/eth1_sim.rs +++ b/testing/simulator/src/basic_sim.rs @@ -1,50 +1,47 @@ -use crate::local_network::{EXECUTION_PORT, TERMINAL_BLOCK, TERMINAL_DIFFICULTY}; +use crate::local_network::LocalNetworkParams; +use crate::local_network::TERMINAL_BLOCK; use crate::{checks, LocalNetwork}; use clap::ArgMatches; -use eth1::{Eth1Endpoint, DEFAULT_CHAIN_ID}; -use eth1_test_rig::AnvilEth1Instance; use crate::retry::with_retry; -use execution_layer::http::deposit_methods::Eth1Id; use futures::prelude::*; -use node_test_rig::environment::RuntimeContext; use node_test_rig::{ environment::{EnvironmentBuilder, LoggerConfig}, - testing_client_config, testing_validator_config, ApiTopic, ClientConfig, ClientGenesis, - ValidatorFiles, + testing_validator_config, ValidatorFiles, }; use rayon::prelude::*; -use sensitive_url::SensitiveUrl; use std::cmp::max; -use std::net::Ipv4Addr; use std::time::Duration; use tokio::time::sleep; use types::{Epoch, EthSpec, MinimalEthSpec}; -const END_EPOCH: u64 = 16; -const ALTAIR_FORK_EPOCH: u64 = 1; -const BELLATRIX_FORK_EPOCH: u64 = 2; +const END_EPOCH: u64 = 10; +const GENESIS_DELAY: u64 = 32; +const ALTAIR_FORK_EPOCH: u64 = 0; +const BELLATRIX_FORK_EPOCH: u64 = 0; +const CAPELLA_FORK_EPOCH: u64 = 1; +const DENEB_FORK_EPOCH: u64 = 2; +//const ELECTRA_FORK_EPOCH: u64 = 3; const SUGGESTED_FEE_RECIPIENT: [u8; 20] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]; -pub fn run_eth1_sim(matches: &ArgMatches) -> Result<(), String> { +pub fn run_basic_sim(matches: &ArgMatches) -> Result<(), String> { let node_count = value_t!(matches, "nodes", usize).expect("missing nodes default"); let proposer_nodes = value_t!(matches, "proposer-nodes", usize).unwrap_or(0); println!("PROPOSER-NODES: {}", proposer_nodes); - let validators_per_node = value_t!(matches, "validators_per_node", usize) + 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 continue_after_checks = matches.is_present("continue_after_checks"); - let post_merge_sim = matches.is_present("post-merge"); + value_t!(matches, "speed-up-factor", u64).expect("missing speed-up-factor default"); + let continue_after_checks = matches.is_present("continue-after-checks"); + let log_level = value_t!(matches, "debug-level", String).expect("Missing default log-level"); println!("Beacon Chain Simulator:"); println!(" nodes:{}, proposer_nodes: {}", node_count, proposer_nodes); - println!(" validators_per_node:{}", validators_per_node); - println!(" post merge simulation:{}", post_merge_sim); - println!(" continue_after_checks:{}", continue_after_checks); + println!(" validators-per-node:{}", validators_per_node); + println!(" continue-after-checks:{}", continue_after_checks); // Generate the directories and keystores required for the validator clients. let validator_files = (0..node_count) @@ -65,8 +62,8 @@ pub fn run_eth1_sim(matches: &ArgMatches) -> Result<(), String> { let mut env = EnvironmentBuilder::minimal() .initialize_logger(LoggerConfig { path: None, - debug_level: String::from("debug"), - logfile_debug_level: String::from("debug"), + debug_level: log_level.clone(), + logfile_debug_level: log_level, log_format: None, logfile_format: None, log_color: false, @@ -80,32 +77,30 @@ pub fn run_eth1_sim(matches: &ArgMatches) -> Result<(), String> { .multi_threaded_tokio_runtime()? .build()?; - let eth1_block_time = Duration::from_millis(15_000 / speed_up_factor); - let spec = &mut env.eth2_config.spec; let total_validator_count = validators_per_node * node_count; - let altair_fork_version = spec.altair_fork_version; - let bellatrix_fork_version = spec.bellatrix_fork_version; + let genesis_delay = GENESIS_DELAY; + let deneb_fork_version = spec.deneb_fork_version; + let _electra_fork_version = spec.electra_fork_version; + + // Convenience variables. Update these values when adding a newer fork. + let latest_fork_version = deneb_fork_version; + let latest_fork_start_epoch = DENEB_FORK_EPOCH; spec.seconds_per_slot /= speed_up_factor; spec.seconds_per_slot = max(1, spec.seconds_per_slot); - spec.eth1_follow_distance = 16; - spec.genesis_delay = eth1_block_time.as_secs() * spec.eth1_follow_distance * 2; + spec.genesis_delay = genesis_delay; spec.min_genesis_time = 0; spec.min_genesis_active_validator_count = total_validator_count as u64; - spec.seconds_per_eth1_block = eth1_block_time.as_secs(); spec.altair_fork_epoch = Some(Epoch::new(ALTAIR_FORK_EPOCH)); - // Set these parameters only if we are doing a merge simulation - if post_merge_sim { - spec.terminal_total_difficulty = TERMINAL_DIFFICULTY.into(); - spec.bellatrix_fork_epoch = Some(Epoch::new(BELLATRIX_FORK_EPOCH)); - } + spec.bellatrix_fork_epoch = Some(Epoch::new(BELLATRIX_FORK_EPOCH)); + spec.capella_fork_epoch = Some(Epoch::new(CAPELLA_FORK_EPOCH)); + spec.deneb_fork_epoch = Some(Epoch::new(DENEB_FORK_EPOCH)); + //spec.electra_fork_epoch = Some(Epoch::new(ELECTRA_FORK_EPOCH)); - let seconds_per_slot = spec.seconds_per_slot; let slot_duration = Duration::from_secs(spec.seconds_per_slot); let initial_validator_count = spec.min_genesis_active_validator_count as usize; - let deposit_amount = env.eth2_config.spec.max_effective_balance; let context = env.core_context(); @@ -114,37 +109,35 @@ pub fn run_eth1_sim(matches: &ArgMatches) -> Result<(), String> { * Create a new `LocalNetwork` with one beacon node. */ let max_retries = 3; - let (network, beacon_config) = with_retry(max_retries, || { - Box::pin(create_local_network( + let (network, beacon_config, mock_execution_config) = with_retry(max_retries, || { + Box::pin(LocalNetwork::create_local_network( + None, + None, LocalNetworkParams { - eth1_block_time, - total_validator_count, - deposit_amount, + validator_count: total_validator_count, node_count, proposer_nodes, - post_merge_sim, + genesis_delay, }, context.clone(), )) }) .await?; - /* - * One by one, add beacon nodes to the network. - */ - for _ in 0..node_count - 1 { + // Add nodes to the network. + for _ in 0..node_count { network - .add_beacon_node(beacon_config.clone(), false) + .add_beacon_node(beacon_config.clone(), mock_execution_config.clone(), false) .await?; } /* * One by one, add proposer nodes to the network. */ - for _ in 0..proposer_nodes - 1 { - println!("Adding a proposer node"); - network.add_beacon_node(beacon_config.clone(), true).await?; - } + //for _ in 0..proposer_nodes - 1 { + // println!("Adding a proposer node"); + // network.add_beacon_node(beacon_config.clone(), true).await?; + //} /* * One by one, add validators to the network. @@ -156,53 +149,41 @@ pub fn run_eth1_sim(matches: &ArgMatches) -> Result<(), String> { executor.spawn( async move { let mut validator_config = testing_validator_config(); - if post_merge_sim { - validator_config.fee_recipient = Some(SUGGESTED_FEE_RECIPIENT.into()); - } + validator_config.fee_recipient = Some(SUGGESTED_FEE_RECIPIENT.into()); println!("Adding validator client {}", i); // Enable broadcast on every 4th node. - if i % 4 == 0 { - validator_config.broadcast_topics = ApiTopic::all(); - let beacon_nodes = vec![i, (i + 1) % node_count]; - network_1 - .add_validator_client_with_fallbacks( - validator_config, - i, - beacon_nodes, - files, - ) - .await - } else { - network_1 - .add_validator_client(validator_config, i, files, i % 2 == 0) - .await - } - .expect("should add validator"); + //if i % 4 == 0 { + // validator_config.broadcast_topics = ApiTopic::all(); + // let beacon_nodes = vec![i, (i + 1) % node_count]; + // network_1 + // .add_validator_client_with_fallbacks( + // validator_config, + // i, + // beacon_nodes, + // files, + // ) + // .await + //} else { + network_1 + .add_validator_client(validator_config, i, files, false) //i % 2 == 0) + .await + //} + .expect("should add validator"); }, "vc", ); } + // Set all payloads as valid. This effectively assumes the EL is infalliable. + network.execution_nodes.write().iter().for_each(|node| { + node.server.all_payloads_valid(); + }); + let duration_to_genesis = network.duration_to_genesis().await; println!("Duration to genesis: {}", duration_to_genesis.as_secs()); sleep(duration_to_genesis).await; - if post_merge_sim { - let executor = executor.clone(); - let network_2 = network.clone(); - executor.spawn( - async move { - println!("Mining pow blocks"); - let mut interval = tokio::time::interval(Duration::from_secs(seconds_per_slot)); - for i in 1..=TERMINAL_BLOCK + 1 { - interval.tick().await; - let _ = network_2.mine_pow_blocks(i); - } - }, - "pow_mining", - ); - } /* * Start the checks that ensure the network performs as expected. * @@ -221,6 +202,7 @@ pub fn run_eth1_sim(matches: &ArgMatches) -> Result<(), String> { sync_aggregate, transition, light_client_update, + blobs, ) = futures::join!( // Check that the chain finalizes at the first given opportunity. checks::verify_first_finalization(network.clone(), slot_duration), @@ -246,17 +228,9 @@ pub fn run_eth1_sim(matches: &ArgMatches) -> Result<(), String> { // Check that all nodes have transitioned to the required fork. checks::verify_fork_version( network.clone(), - if post_merge_sim { - Epoch::new(BELLATRIX_FORK_EPOCH) - } else { - Epoch::new(ALTAIR_FORK_EPOCH) - }, + Epoch::new(latest_fork_start_epoch), slot_duration, - if post_merge_sim { - bellatrix_fork_version - } else { - altair_fork_version - } + latest_fork_version, ), // Check that all sync aggregates are full. checks::verify_full_sync_aggregates_up_to( @@ -272,7 +246,7 @@ pub fn run_eth1_sim(matches: &ArgMatches) -> Result<(), String> { network.clone(), Epoch::new(TERMINAL_BLOCK / MinimalEthSpec::slots_per_epoch()), slot_duration, - post_merge_sim + true, ), checks::verify_light_client_updates( network.clone(), @@ -280,7 +254,14 @@ pub fn run_eth1_sim(matches: &ArgMatches) -> Result<(), String> { Epoch::new(ALTAIR_FORK_EPOCH).start_slot(MinimalEthSpec::slots_per_epoch()) + 1, Epoch::new(END_EPOCH).start_slot(MinimalEthSpec::slots_per_epoch()), slot_duration - ) + ), + checks::verify_full_blob_production_up_to( + network.clone(), + // Blobs should be available from the first slot after the Deneb fork. + Epoch::new(DENEB_FORK_EPOCH + 1).start_slot(MinimalEthSpec::slots_per_epoch()) + 1, + Epoch::new(END_EPOCH).start_slot(MinimalEthSpec::slots_per_epoch()), + slot_duration + ), ); block_prod?; @@ -291,6 +272,7 @@ pub fn run_eth1_sim(matches: &ArgMatches) -> Result<(), String> { sync_aggregate?; transition?; light_client_update?; + blobs?; // The `final_future` either completes immediately or never completes, depending on the value // of `continue_after_checks`. @@ -321,91 +303,3 @@ pub fn run_eth1_sim(matches: &ArgMatches) -> Result<(), String> { Ok(()) } - -struct LocalNetworkParams { - eth1_block_time: Duration, - total_validator_count: usize, - deposit_amount: u64, - node_count: usize, - proposer_nodes: usize, - post_merge_sim: bool, -} - -async fn create_local_network( - LocalNetworkParams { - eth1_block_time, - total_validator_count, - deposit_amount, - node_count, - proposer_nodes, - post_merge_sim, - }: LocalNetworkParams, - context: RuntimeContext, -) -> Result<(LocalNetwork, ClientConfig), String> { - /* - * Deploy the deposit contract, spawn tasks to keep creating new blocks and deposit - * validators. - */ - let anvil_eth1_instance = AnvilEth1Instance::new(DEFAULT_CHAIN_ID.into()).await?; - let deposit_contract = anvil_eth1_instance.deposit_contract; - let chain_id = anvil_eth1_instance.anvil.chain_id(); - let anvil = anvil_eth1_instance.anvil; - let eth1_endpoint = - SensitiveUrl::parse(anvil.endpoint().as_str()).expect("Unable to parse anvil endpoint."); - let deposit_contract_address = deposit_contract.address(); - - // Start a timer that produces eth1 blocks on an interval. - tokio::spawn(async move { - let mut interval = tokio::time::interval(eth1_block_time); - loop { - interval.tick().await; - let _ = anvil.evm_mine().await; - } - }); - - // Submit deposits to the deposit contract. - tokio::spawn(async move { - for i in 0..total_validator_count { - println!("Submitting deposit for validator {}...", i); - let _ = deposit_contract - .deposit_deterministic_async::(i, deposit_amount) - .await; - } - }); - - let mut beacon_config = testing_client_config(); - - beacon_config.genesis = ClientGenesis::DepositContract; - beacon_config.eth1.endpoint = Eth1Endpoint::NoAuth(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.eth1.node_far_behind_seconds = 20; - beacon_config.dummy_eth1_backend = false; - beacon_config.sync_eth1_chain = true; - beacon_config.eth1.auto_update_interval_millis = eth1_block_time.as_millis() as u64; - beacon_config.eth1.chain_id = Eth1Id::from(chain_id); - beacon_config.network.target_peers = node_count + proposer_nodes - 1; - - beacon_config.network.enr_address = (Some(Ipv4Addr::LOCALHOST), None); - beacon_config.network.enable_light_client_server = true; - beacon_config.chain.enable_light_client_server = true; - beacon_config.http_api.enable_light_client_server = true; - - if post_merge_sim { - let el_config = execution_layer::Config { - execution_endpoints: vec![SensitiveUrl::parse(&format!( - "http://localhost:{}", - EXECUTION_PORT - )) - .unwrap()], - ..Default::default() - }; - - beacon_config.execution_layer = Some(el_config); - } - - let network = LocalNetwork::new(context, beacon_config.clone()).await?; - Ok((network, beacon_config)) -} diff --git a/testing/simulator/src/checks.rs b/testing/simulator/src/checks.rs index d30e44a1174..009e1b39118 100644 --- a/testing/simulator/src/checks.rs +++ b/testing/simulator/src/checks.rs @@ -1,7 +1,7 @@ use crate::local_network::LocalNetwork; use node_test_rig::eth2::types::{BlockId, FinalityCheckpointsData, StateId}; use std::time::Duration; -use types::{Epoch, EthSpec, ExecPayload, ExecutionBlockHash, Hash256, Slot, Unsigned}; +use types::{Epoch, EthSpec, ExecPayload, ExecutionBlockHash, Slot, Unsigned}; /// Checks that all of the validators have on-boarded by the start of the second eth1 voting /// period. @@ -234,7 +234,7 @@ pub async fn verify_transition_block_finalized( } let first = block_hashes[0]; - if first.into_root() != Hash256::zero() && block_hashes.iter().all(|&item| item == first) { + if block_hashes.iter().all(|&item| item == first) { Ok(()) } else { Err(format!( @@ -333,3 +333,36 @@ pub(crate) async fn verify_light_client_updates( Ok(()) } + +/// Verifies that there's been a block produced at every slot up to and including `slot`. +pub async fn verify_full_blob_production_up_to( + network: LocalNetwork, + blob_start_slot: Slot, + upto_slot: Slot, + slot_duration: Duration, +) -> Result<(), String> { + slot_delay(upto_slot, slot_duration).await; + let remote_nodes = network.remote_nodes()?; + let remote_node = remote_nodes.first().unwrap(); + + for slot in blob_start_slot.as_u64()..=upto_slot.as_u64() { + // Ensure block exists. + let block = remote_node + .get_beacon_blocks::(BlockId::Slot(Slot::new(slot))) + .await + .ok() + .flatten(); + + // Only check blobs if the blob exist. If you also want to ensure full block production, use + // the `verify_full_block_production_up_to` function. + if block.is_some() { + remote_node + .get_blobs::(BlockId::Slot(Slot::new(slot)), None) + .await + .map_err(|e| format!("Failed to get blobs at slot {slot:?}: {e:?}"))? + .ok_or_else(|| format!("No blobs available at slot {slot:?}"))?; + } + } + + Ok(()) +} diff --git a/testing/simulator/src/cli.rs b/testing/simulator/src/cli.rs index ff80201051f..240ad9c3f05 100644 --- a/testing/simulator/src/cli.rs +++ b/testing/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("eth1-sim") + SubCommand::with_name("basic-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,104 +22,36 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> { .short("n") .long("nodes") .takes_value(true) - .default_value("4") + .default_value("3") .help("Number of beacon nodes")) .arg(Arg::with_name("proposer-nodes") .short("p") - .long("proposer_nodes") + .long("proposer-nodes") .takes_value(true) - .default_value("2") + .default_value("0") .help("Number of proposer-only beacon nodes")) - .arg(Arg::with_name("validators_per_node") + .arg(Arg::with_name("validators-per-node") .short("v") - .long("validators_per_node") + .long("validators-per-node") .takes_value(true) .default_value("20") .help("Number of validators")) - .arg(Arg::with_name("speed_up_factor") + .arg(Arg::with_name("speed-up-factor") .short("s") - .long("speed_up_factor") + .long("speed-up-factor") .takes_value(true) .default_value("3") .help("Speed up factor. Please use a divisor of 12.")) - .arg(Arg::with_name("post-merge") - .short("m") - .long("post-merge") - .takes_value(false) - .help("Simulate the merge transition")) - .arg(Arg::with_name("continue_after_checks") - .short("c") - .long("continue_after_checks") - .takes_value(false) - .help("Continue after checks (default false)")) - ) - .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("proposer-nodes") - .short("p") - .long("proposer_nodes") + .arg(Arg::with_name("debug-level") + .short("d") + .long("debug-level") .takes_value(true) - .default_value("2") - .help("Number of proposer-only 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("3") - .help("Speed up factor")) - .arg(Arg::with_name("continue_after_checks") + .default_value("debug") + .help("Set the severity level of the logs.")) + .arg(Arg::with_name("continue-after-checks") .short("c") .long("continue_after_checks") .takes_value(false) .help("Continue after checks (default false)")) ) - .subcommand( - SubCommand::with_name("syncing-sim") - .about("Run the syncing simulation") - .arg( - Arg::with_name("speedup") - .short("s") - .long("speedup") - .takes_value(true) - .default_value("15") - .help("Speed up factor for eth1 blocks and slot production"), - ) - .arg( - Arg::with_name("initial_delay") - .short("i") - .long("initial_delay") - .takes_value(true) - .default_value("5") - .help("Epoch delay for new beacon node to start syncing"), - ) - .arg( - Arg::with_name("sync_timeout") - .long("sync_timeout") - .takes_value(true) - .default_value("10") - .help("Number of epochs after which newly added beacon nodes must be synced"), - ) - .arg( - Arg::with_name("strategy") - .long("strategy") - .takes_value(true) - .default_value("all") - .possible_values(&["one-node", "two-nodes", "mixed", "all"]) - .help("Sync verification strategy to run."), - ), - ) } diff --git a/testing/simulator/src/local_network.rs b/testing/simulator/src/local_network.rs index dc8bf0d27dd..caf71fed976 100644 --- a/testing/simulator/src/local_network.rs +++ b/testing/simulator/src/local_network.rs @@ -1,17 +1,19 @@ +use eth2_network_config::TRUSTED_SETUP_BYTES; use node_test_rig::{ environment::RuntimeContext, eth2::{types::StateId, BeaconNodeHttpClient}, - ClientConfig, LocalBeaconNode, LocalExecutionNode, LocalValidatorClient, MockExecutionConfig, - MockServerConfig, ValidatorConfig, ValidatorFiles, + testing_client_config, ClientConfig, ClientGenesis, LocalBeaconNode, LocalExecutionNode, + LocalValidatorClient, MockExecutionConfig, MockServerConfig, ValidatorConfig, ValidatorFiles, }; use parking_lot::RwLock; use sensitive_url::SensitiveUrl; use std::{ + net::Ipv4Addr, ops::Deref, - time::{SystemTime, UNIX_EPOCH}, + sync::Arc, + time::{Duration, SystemTime, UNIX_EPOCH}, }; -use std::{sync::Arc, time::Duration}; -use types::{Epoch, EthSpec}; +use types::{ChainSpec, Epoch, EthSpec}; const BOOTNODE_PORT: u16 = 42424; const QUIC_PORT: u16 = 43424; @@ -19,8 +21,75 @@ pub const INVALID_ADDRESS: &str = "http://127.0.0.1:42423"; pub const EXECUTION_PORT: u16 = 4000; -pub const TERMINAL_DIFFICULTY: u64 = 6400; -pub const TERMINAL_BLOCK: u64 = 64; +pub const TERMINAL_BLOCK: u64 = 0; + +pub struct LocalNetworkParams { + pub validator_count: usize, + pub node_count: usize, + pub proposer_nodes: usize, + pub genesis_delay: u64, +} + +fn default_client_config(network_params: LocalNetworkParams, genesis_time: u64) -> ClientConfig { + let mut beacon_config = testing_client_config(); + + beacon_config.genesis = ClientGenesis::InteropMerge { + validator_count: network_params.validator_count, + genesis_time, + }; + beacon_config.network.target_peers = + network_params.node_count + network_params.proposer_nodes - 1; + beacon_config.network.enr_address = (Some(Ipv4Addr::LOCALHOST), None); + beacon_config.network.enable_light_client_server = true; + beacon_config.chain.enable_light_client_server = true; + beacon_config.http_api.enable_light_client_server = true; + beacon_config.trusted_setup = + serde_json::from_reader(TRUSTED_SETUP_BYTES).expect("Trusted setup bytes should be valid"); + + let el_config = execution_layer::Config { + execution_endpoints: vec![SensitiveUrl::parse(&format!( + "http://localhost:{}", + EXECUTION_PORT + )) + .unwrap()], + ..Default::default() + }; + beacon_config.execution_layer = Some(el_config); + beacon_config +} + +fn default_mock_execution_config( + spec: &ChainSpec, + genesis_time: u64, +) -> MockExecutionConfig { + let mut mock_execution_config = MockExecutionConfig { + server_config: MockServerConfig { + listen_port: EXECUTION_PORT, + ..Default::default() + }, + ..Default::default() + }; + + if let Some(capella_fork_epoch) = spec.capella_fork_epoch { + mock_execution_config.shanghai_time = Some( + genesis_time + + spec.seconds_per_slot * E::slots_per_epoch() * capella_fork_epoch.as_u64(), + ) + } + if let Some(deneb_fork_epoch) = spec.deneb_fork_epoch { + mock_execution_config.cancun_time = Some( + genesis_time + spec.seconds_per_slot * E::slots_per_epoch() * deneb_fork_epoch.as_u64(), + ) + } + if let Some(electra_fork_epoch) = spec.electra_fork_epoch { + mock_execution_config.prague_time = Some( + genesis_time + + spec.seconds_per_slot * E::slots_per_epoch() * electra_fork_epoch.as_u64(), + ) + } + + mock_execution_config +} /// Helper struct to reduce `Arc` usage. pub struct Inner { @@ -55,56 +124,42 @@ impl Deref for LocalNetwork { } impl LocalNetwork { - /// Creates a new network with a single `BeaconNode` and a connected `ExecutionNode`. - pub async fn new( + pub async fn create_local_network( + client_config: Option, + mock_execution_config: Option, + network_params: LocalNetworkParams, context: RuntimeContext, - mut beacon_config: ClientConfig, - ) -> Result { - beacon_config.network.set_ipv4_listening_address( - std::net::Ipv4Addr::UNSPECIFIED, - BOOTNODE_PORT, - BOOTNODE_PORT, - QUIC_PORT, - ); - beacon_config.network.enr_udp4_port = Some(BOOTNODE_PORT.try_into().expect("non zero")); - beacon_config.network.enr_tcp4_port = Some(BOOTNODE_PORT.try_into().expect("non zero")); - beacon_config.network.discv5_config.table_filter = |_| true; + ) -> Result<(LocalNetwork, ClientConfig, MockExecutionConfig), String> { + let genesis_time: u64 = (SystemTime::now() + .duration_since(UNIX_EPOCH) + .map_err(|_| "should get system time")? + + Duration::from_secs(network_params.genesis_delay)) + .as_secs(); - let execution_node = if let Some(el_config) = &mut beacon_config.execution_layer { - let mock_execution_config = MockExecutionConfig { - server_config: MockServerConfig { - listen_port: EXECUTION_PORT, - ..Default::default() - }, - terminal_block: TERMINAL_BLOCK, - terminal_difficulty: TERMINAL_DIFFICULTY.into(), - ..Default::default() - }; - let execution_node = LocalExecutionNode::new( - context.service_context("boot_node_el".into()), - mock_execution_config, - ); - el_config.default_datadir = execution_node.datadir.path().to_path_buf(); - el_config.secret_files = vec![execution_node.datadir.path().join("jwt.hex")]; - el_config.execution_endpoints = - vec![SensitiveUrl::parse(&execution_node.server.url()).unwrap()]; - vec![execution_node] + let beacon_config = if let Some(config) = client_config { + config + } else { + default_client_config(network_params, genesis_time) + }; + + let execution_config = if let Some(config) = mock_execution_config { + config } else { - vec![] + default_mock_execution_config::(&context.eth2_config().spec, genesis_time) }; - let beacon_node = - LocalBeaconNode::production(context.service_context("boot_node".into()), beacon_config) - .await?; - Ok(Self { + //let network = LocalNetwork::new(context, beacon_config.clone(), mock_execution_config).await?; + let network = Self { inner: Arc::new(Inner { context, - beacon_nodes: RwLock::new(vec![beacon_node]), + beacon_nodes: RwLock::new(vec![]), proposer_nodes: RwLock::new(vec![]), - execution_nodes: RwLock::new(execution_node), + execution_nodes: RwLock::new(vec![]), validator_clients: RwLock::new(vec![]), }), - }) + }; + + Ok((network, beacon_config, execution_config)) } /// Returns the number of beacon nodes in the network. @@ -131,73 +186,128 @@ impl LocalNetwork { self.validator_clients.read().len() } - /// Adds a beacon node to the network, connecting to the 0'th beacon node via ENR. - pub async fn add_beacon_node( + async fn construct_boot_node( &self, mut beacon_config: ClientConfig, - is_proposer: bool, - ) -> Result<(), String> { - let self_1 = self.clone(); - let count = self.beacon_node_count() as u16; - println!("Adding beacon node.."); - { - let read_lock = self.beacon_nodes.read(); + mock_execution_config: MockExecutionConfig, + ) -> Result<(LocalBeaconNode, LocalExecutionNode), String> { + beacon_config.network.set_ipv4_listening_address( + std::net::Ipv4Addr::UNSPECIFIED, + BOOTNODE_PORT, + BOOTNODE_PORT, + QUIC_PORT, + ); + + beacon_config.network.enr_udp4_port = Some(BOOTNODE_PORT.try_into().expect("non zero")); + beacon_config.network.enr_tcp4_port = Some(BOOTNODE_PORT.try_into().expect("non zero")); + beacon_config.network.discv5_config.table_filter = |_| true; - let boot_node = read_lock.first().expect("should have at least one node"); + let execution_node = LocalExecutionNode::new( + self.context.service_context("boot_node_el".into()), + mock_execution_config, + ); - beacon_config.network.boot_nodes_enr.push( - boot_node - .client - .enr() - .expect("bootnode must have a network"), - ); - let count = (self.beacon_node_count() + self.proposer_node_count()) as u16; - let libp2p_tcp_port = BOOTNODE_PORT + count; - let discv5_port = BOOTNODE_PORT + count; - beacon_config.network.set_ipv4_listening_address( - std::net::Ipv4Addr::UNSPECIFIED, - libp2p_tcp_port, - discv5_port, - QUIC_PORT + count, - ); - beacon_config.network.enr_udp4_port = Some(discv5_port.try_into().unwrap()); - beacon_config.network.enr_tcp4_port = Some(libp2p_tcp_port.try_into().unwrap()); - beacon_config.network.discv5_config.table_filter = |_| true; - beacon_config.network.proposer_only = is_proposer; - } - if let Some(el_config) = &mut beacon_config.execution_layer { - let config = MockExecutionConfig { - server_config: MockServerConfig { - listen_port: EXECUTION_PORT + count, - ..Default::default() - }, - terminal_block: TERMINAL_BLOCK, - terminal_difficulty: TERMINAL_DIFFICULTY.into(), - ..Default::default() - }; - let execution_node = LocalExecutionNode::new( - self.context.service_context(format!("node_{}_el", count)), - config, - ); - el_config.default_datadir = execution_node.datadir.path().to_path_buf(); - el_config.secret_files = vec![execution_node.datadir.path().join("jwt.hex")]; - el_config.execution_endpoints = - vec![SensitiveUrl::parse(&execution_node.server.url()).unwrap()]; - self.execution_nodes.write().push(execution_node); - } + beacon_config.execution_layer = Some(execution_layer::Config { + execution_endpoints: vec![SensitiveUrl::parse(&execution_node.server.url()).unwrap()], + default_datadir: execution_node.datadir.path().to_path_buf(), + secret_files: vec![execution_node.datadir.path().join("jwt.hex")], + ..Default::default() + }); + + let beacon_node = LocalBeaconNode::production( + self.context.service_context("boot_node".into()), + beacon_config, + ) + .await?; + + Ok((beacon_node, execution_node)) + } + + async fn construct_beacon_node( + &self, + mut beacon_config: ClientConfig, + mut mock_execution_config: MockExecutionConfig, + ) -> Result<(LocalBeaconNode, LocalExecutionNode), String> { + let count = (self.beacon_node_count() + self.proposer_node_count()) as u16; + + // Set config. + let libp2p_tcp_port = BOOTNODE_PORT + count; + let discv5_port = BOOTNODE_PORT + count; + beacon_config.network.set_ipv4_listening_address( + std::net::Ipv4Addr::UNSPECIFIED, + libp2p_tcp_port, + discv5_port, + QUIC_PORT + count, + ); + beacon_config.network.enr_udp4_port = Some(discv5_port.try_into().unwrap()); + beacon_config.network.enr_tcp4_port = Some(libp2p_tcp_port.try_into().unwrap()); + beacon_config.network.discv5_config.table_filter = |_| true; + //beacon_config.network.proposer_only = is_proposer; - // We create the beacon node without holding the lock, so that the lock isn't held - // across the await. This is only correct if this function never runs in parallel - // with itself (which at the time of writing, it does not). + mock_execution_config.server_config.listen_port = EXECUTION_PORT + count; + + // Construct execution node. + let execution_node = LocalExecutionNode::new( + self.context.service_context(format!("node_{}_el", count)), + mock_execution_config, + ); + + // Pair the beacon node and execution node. + beacon_config.execution_layer = Some(execution_layer::Config { + execution_endpoints: vec![SensitiveUrl::parse(&execution_node.server.url()).unwrap()], + default_datadir: execution_node.datadir.path().to_path_buf(), + secret_files: vec![execution_node.datadir.path().join("jwt.hex")], + ..Default::default() + }); + + // Construct beacon node using the config, let beacon_node = LocalBeaconNode::production( self.context.service_context(format!("node_{}", count)), beacon_config, ) .await?; + + Ok((beacon_node, execution_node)) + } + + /// Adds a beacon node to the network, connecting to the 0'th beacon node via ENR. + pub async fn add_beacon_node( + &self, + mut beacon_config: ClientConfig, + mock_execution_config: MockExecutionConfig, + is_proposer: bool, + ) -> Result<(), String> { + let first_bn_exists: bool; + { + let read_lock = self.beacon_nodes.read(); + let boot_node = read_lock.first(); + first_bn_exists = boot_node.is_some(); + + if let Some(boot_node) = boot_node { + // Modify beacon_config to add boot node details. + beacon_config.network.boot_nodes_enr.push( + boot_node + .client + .enr() + .expect("Bootnode must have a network."), + ); + } + } + let (beacon_node, execution_node) = if first_bn_exists { + // Network already exists. We construct a new node. + self.construct_beacon_node(beacon_config, mock_execution_config) + .await? + } else { + // Network does not exist. We construct a boot node. + self.construct_boot_node(beacon_config, mock_execution_config) + .await? + }; + // Add nodes to the network. + self.execution_nodes.write().push(execution_node); if is_proposer { - self_1.proposer_nodes.write().push(beacon_node); + self.proposer_nodes.write().push(beacon_node); } else { - self_1.beacon_nodes.write().push(beacon_node); + self.beacon_nodes.write().push(beacon_node); } Ok(()) } @@ -270,7 +380,7 @@ impl LocalNetwork { Ok(()) } - pub async fn add_validator_client_with_fallbacks( + pub async fn _add_validator_client_with_fallbacks( &self, mut validator_config: ValidatorConfig, validator_index: usize, @@ -325,7 +435,7 @@ impl LocalNetwork { } /// Return current epoch of bootnode. - pub async fn bootnode_epoch(&self) -> Result { + pub async fn _bootnode_epoch(&self) -> Result { let nodes = self.remote_nodes().expect("Failed to get remote nodes"); let bootnode = nodes.first().expect("Should contain bootnode"); bootnode @@ -335,16 +445,6 @@ impl LocalNetwork { .map(|body| body.unwrap().data.finalized.epoch) } - pub fn mine_pow_blocks(&self, block_number: u64) -> Result<(), String> { - let execution_nodes = self.execution_nodes.read(); - for execution_node in execution_nodes.iter() { - let mut block_gen = execution_node.server.ctx.execution_block_generator.write(); - block_gen.insert_pow_block(block_number)?; - println!("Mined pow block {}", block_number); - } - Ok(()) - } - pub async fn duration_to_genesis(&self) -> Duration { let nodes = self.remote_nodes().expect("Failed to get remote nodes"); let bootnode = nodes.first().expect("Should contain bootnode"); diff --git a/testing/simulator/src/main.rs b/testing/simulator/src/main.rs index e8af9c18067..068b369fbc5 100644 --- a/testing/simulator/src/main.rs +++ b/testing/simulator/src/main.rs @@ -16,13 +16,11 @@ #[macro_use] extern crate clap; +mod basic_sim; mod checks; mod cli; -mod eth1_sim; mod local_network; -mod no_eth1_sim; mod retry; -mod sync_sim; use cli::cli_app; use env_logger::{Builder, Env}; @@ -37,21 +35,7 @@ fn main() { let matches = cli_app().get_matches(); match matches.subcommand() { - ("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) - } - }, - ("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) { + ("basic-sim", Some(matches)) => match basic_sim::run_basic_sim(matches) { Ok(()) => println!("Simulation exited successfully"), Err(e) => { eprintln!("Simulation exited with error: {}", e); diff --git a/testing/simulator/src/no_eth1_sim.rs b/testing/simulator/src/no_eth1_sim.rs deleted file mode 100644 index fc18b1cd489..00000000000 --- a/testing/simulator/src/no_eth1_sim.rs +++ /dev/null @@ -1,172 +0,0 @@ -use crate::{checks, LocalNetwork}; -use clap::ArgMatches; -use futures::prelude::*; -use node_test_rig::{ - environment::{EnvironmentBuilder, LoggerConfig}, - testing_client_config, testing_validator_config, ClientGenesis, ValidatorFiles, -}; -use rayon::prelude::*; -use std::cmp::max; -use std::net::Ipv4Addr; -use std::time::{Duration, SystemTime, UNIX_EPOCH}; -use tokio::time::sleep; -use types::{Epoch, EthSpec, MainnetEthSpec}; - -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 continue_after_checks = matches.is_present("continue_after_checks"); - - println!("Beacon Chain Simulator:"); - println!(" nodes:{}", node_count); - println!(" validators_per_node:{}", validators_per_node); - println!(" continue_after_checks:{}", continue_after_checks); - - // Generate the directories and keystores required for the validator clients. - let validator_files = (0..node_count) - .into_par_iter() - .map(|i| { - println!( - "Generating keystores for validator {} of {}", - i + 1, - node_count - ); - - let indices = - (i * validators_per_node..(i + 1) * validators_per_node).collect::>(); - ValidatorFiles::with_keystores(&indices).unwrap() - }) - .collect::>(); - - let mut env = EnvironmentBuilder::mainnet() - .initialize_logger(LoggerConfig { - path: None, - debug_level: String::from("debug"), - logfile_debug_level: String::from("debug"), - log_format: None, - logfile_format: None, - log_color: false, - disable_log_timestamp: false, - max_log_size: 0, - max_log_number: 0, - compression: false, - is_restricted: true, - sse_logging: false, - })? - .multi_threaded_tokio_runtime()? - .build()?; - - let eth1_block_time = Duration::from_millis(15_000 / speed_up_factor); - - let spec = &mut env.eth2_config.spec; - - let total_validator_count = validators_per_node * node_count; - - spec.seconds_per_slot /= speed_up_factor; - spec.seconds_per_slot = max(1, spec.seconds_per_slot); - spec.eth1_follow_distance = 16; - spec.genesis_delay = eth1_block_time.as_secs() * spec.eth1_follow_distance * 2; - spec.min_genesis_time = 0; - spec.min_genesis_active_validator_count = total_validator_count as u64; - spec.seconds_per_eth1_block = 1; - - let genesis_delay = Duration::from_secs(5); - let genesis_time = SystemTime::now() - .duration_since(UNIX_EPOCH) - .map_err(|_| "should get system time")? - + genesis_delay; - - let slot_duration = Duration::from_secs(spec.seconds_per_slot); - - 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(Ipv4Addr::LOCALHOST), None); - - let main_future = async { - let network = LocalNetwork::new(context.clone(), beacon_config.clone()).await?; - /* - * One by one, add beacon nodes to the network. - */ - - for _ in 0..node_count - 1 { - network - .add_beacon_node(beacon_config.clone(), false) - .await?; - } - - /* - * Create a future that will add validator clients to the network. Each validator client is - * attached to a single corresponding beacon node. Spawn each validator in a new task. - */ - let executor = context.executor.clone(); - for (i, files) in validator_files.into_iter().enumerate() { - let network_1 = network.clone(); - executor.spawn( - async move { - println!("Adding validator client {}", i); - network_1 - .add_validator_client(testing_validator_config(), i, files, i % 2 == 0) - .await - .expect("should add validator"); - }, - "vc", - ); - } - - let duration_to_genesis = network.duration_to_genesis().await; - println!("Duration to genesis: {}", duration_to_genesis.as_secs()); - sleep(duration_to_genesis).await; - - let (finalization, block_prod) = futures::join!( - // Check that the chain finalizes at the first given opportunity. - checks::verify_first_finalization(network.clone(), slot_duration), - // Check that a block is produced at every slot. - checks::verify_full_block_production_up_to( - network.clone(), - Epoch::new(4).start_slot(MainnetEthSpec::slots_per_epoch()), - slot_duration, - ), - ); - finalization?; - block_prod?; - - // The `final_future` either completes immediately or never completes, depending on the value - // of `continue_after_checks`. - - if continue_after_checks { - future::pending::<()>().await; - } - /* - * End the simulation by dropping the network. This will kill all running beacon nodes and - * validator clients. - */ - println!( - "Simulation complete. Finished with {} beacon nodes and {} validator clients", - network.beacon_node_count() + network.proposer_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); - Ok::<(), String>(()) - }; - - env.runtime().block_on(main_future).unwrap(); - - env.fire_signal(); - env.shutdown_on_idle(); - Ok(()) -} diff --git a/testing/simulator/src/sync_sim.rs b/testing/simulator/src/sync_sim.rs deleted file mode 100644 index 78f7e1ee9fb..00000000000 --- a/testing/simulator/src/sync_sim.rs +++ /dev/null @@ -1,392 +0,0 @@ -use crate::checks::{epoch_delay, verify_all_finalized_at}; -use crate::local_network::LocalNetwork; -use clap::ArgMatches; -use futures::prelude::*; -use node_test_rig::{ - environment::{EnvironmentBuilder, LoggerConfig}, - testing_client_config, ClientGenesis, ValidatorFiles, -}; -use node_test_rig::{testing_validator_config, ClientConfig}; -use std::cmp::max; -use std::net::Ipv4Addr; -use std::time::{Duration, SystemTime, UNIX_EPOCH}; -use types::{Epoch, EthSpec}; - -pub fn run_syncing_sim(matches: &ArgMatches) -> Result<(), String> { - let initial_delay = value_t!(matches, "initial_delay", u64).unwrap(); - let sync_timeout = value_t!(matches, "sync_timeout", u64).unwrap(); - let speed_up_factor = value_t!(matches, "speedup", u64).unwrap(); - let strategy = value_t!(matches, "strategy", String).unwrap(); - - println!("Syncing Simulator:"); - println!(" initial_delay:{}", initial_delay); - println!(" sync timeout: {}", sync_timeout); - 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_timeout, - strategy, - log_level, - log_format, - ) -} - -fn syncing_sim( - speed_up_factor: u64, - initial_delay: u64, - sync_timeout: u64, - strategy: String, - log_level: &str, - log_format: Option<&str>, -) -> Result<(), String> { - let mut env = EnvironmentBuilder::minimal() - .initialize_logger(LoggerConfig { - path: None, - debug_level: String::from(log_level), - logfile_debug_level: String::from("debug"), - log_format: log_format.map(String::from), - logfile_format: None, - log_color: false, - disable_log_timestamp: false, - max_log_size: 0, - max_log_number: 0, - compression: false, - is_restricted: true, - sse_logging: false, - })? - .multi_threaded_tokio_runtime()? - .build()?; - - let spec = &mut env.eth2_config.spec; - let end_after_checks = true; - let eth1_block_time = Duration::from_millis(15_000 / speed_up_factor); - - // Set fork epochs to test syncing across fork boundaries - spec.altair_fork_epoch = Some(Epoch::new(1)); - spec.bellatrix_fork_epoch = Some(Epoch::new(2)); - spec.seconds_per_slot /= speed_up_factor; - spec.seconds_per_slot = max(1, spec.seconds_per_slot); - spec.eth1_follow_distance = 16; - spec.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 num_validators = 8; - let slot_duration = Duration::from_secs(spec.seconds_per_slot); - let context = env.core_context(); - let mut beacon_config = testing_client_config(); - - let genesis_time = SystemTime::now() - .duration_since(UNIX_EPOCH) - .map_err(|_| "should get system time")? - + Duration::from_secs(5); - beacon_config.genesis = ClientGenesis::Interop { - validator_count: num_validators, - genesis_time: genesis_time.as_secs(), - }; - beacon_config.dummy_eth1_backend = true; - beacon_config.sync_eth1_chain = true; - - beacon_config.http_api.allow_sync_stalled = true; - - beacon_config.network.enr_address = (Some(Ipv4Addr::LOCALHOST), None); - - // Generate the directories and keystores required for the validator clients. - let validator_indices = (0..num_validators).collect::>(); - let validator_files = ValidatorFiles::with_keystores(&validator_indices).unwrap(); - - let main_future = async { - /* - * Create a new `LocalNetwork` with one beacon node. - */ - let network = LocalNetwork::new(context, beacon_config.clone()).await?; - - /* - * Add a validator client which handles all validators from the genesis state. - */ - network - .add_validator_client(testing_validator_config(), 0, validator_files, true) - .await?; - - // Check all syncing strategies one after other. - pick_strategy( - &strategy, - network.clone(), - beacon_config.clone(), - slot_duration, - initial_delay, - sync_timeout, - ) - .await?; - - // The `final_future` either completes immediately or never completes, depending on the value - // of `end_after_checks`. - - if !end_after_checks { - future::pending::<()>().await; - } - - /* - * End the simulation by dropping the network. This will kill all running beacon nodes and - * validator clients. - */ - 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); - Ok::<(), String>(()) - }; - - env.runtime().block_on(main_future).unwrap(); - - env.fire_signal(); - env.shutdown_on_idle(); - - Ok(()) -} - -pub async fn pick_strategy( - strategy: &str, - network: LocalNetwork, - beacon_config: ClientConfig, - slot_duration: Duration, - initial_delay: u64, - sync_timeout: u64, -) -> Result<(), String> { - match strategy { - "one-node" => { - verify_one_node_sync( - network, - beacon_config, - slot_duration, - initial_delay, - sync_timeout, - ) - .await - } - "two-nodes" => { - verify_two_nodes_sync( - network, - beacon_config, - slot_duration, - initial_delay, - sync_timeout, - ) - .await - } - "mixed" => { - verify_in_between_sync( - network, - beacon_config, - slot_duration, - initial_delay, - sync_timeout, - ) - .await - } - "all" => { - verify_syncing( - network, - beacon_config, - slot_duration, - initial_delay, - sync_timeout, - ) - .await - } - _ => Err("Invalid strategy".into()), - } -} - -/// Verify one node added after `initial_delay` epochs is in sync -/// after `sync_timeout` epochs. -pub async fn verify_one_node_sync( - network: LocalNetwork, - beacon_config: ClientConfig, - slot_duration: Duration, - initial_delay: u64, - sync_timeout: u64, -) -> Result<(), String> { - let epoch_duration = slot_duration * (E::slots_per_epoch() as u32); - let network_c = network.clone(); - // Delay for `initial_delay` epochs before adding another node to start syncing - epoch_delay( - Epoch::new(initial_delay), - slot_duration, - E::slots_per_epoch(), - ) - .await; - // Add a beacon node - network.add_beacon_node(beacon_config, false).await?; - // Check every `epoch_duration` if nodes are synced - // limited to at most `sync_timeout` epochs - let mut interval = tokio::time::interval(epoch_duration); - let mut count = 0; - loop { - interval.tick().await; - if count >= sync_timeout || !check_still_syncing(&network_c).await? { - break; - } - count += 1; - } - let epoch = network.bootnode_epoch().await?; - verify_all_finalized_at(network, epoch) - .map_err(|e| format!("One node sync error: {}", e)) - .await -} - -/// Verify two nodes added after `initial_delay` epochs are in sync -/// after `sync_timeout` epochs. -pub async fn verify_two_nodes_sync( - network: LocalNetwork, - beacon_config: ClientConfig, - slot_duration: Duration, - initial_delay: u64, - sync_timeout: u64, -) -> Result<(), String> { - let epoch_duration = slot_duration * (E::slots_per_epoch() as u32); - let network_c = network.clone(); - // Delay for `initial_delay` epochs before adding another node to start syncing - epoch_delay( - Epoch::new(initial_delay), - slot_duration, - E::slots_per_epoch(), - ) - .await; - // Add beacon nodes - network - .add_beacon_node(beacon_config.clone(), false) - .await?; - network.add_beacon_node(beacon_config, false).await?; - // Check every `epoch_duration` if nodes are synced - // limited to at most `sync_timeout` epochs - let mut interval = tokio::time::interval(epoch_duration); - let mut count = 0; - loop { - interval.tick().await; - if count >= sync_timeout || !check_still_syncing(&network_c).await? { - break; - } - count += 1; - } - let epoch = network.bootnode_epoch().await?; - verify_all_finalized_at(network, epoch) - .map_err(|e| format!("One node sync error: {}", e)) - .await -} - -/// Add 2 syncing nodes after `initial_delay` epochs, -/// Add another node after `sync_timeout - 5` epochs and verify all are -/// in sync after `sync_timeout + 5` epochs. -pub async fn verify_in_between_sync( - network: LocalNetwork, - beacon_config: ClientConfig, - slot_duration: Duration, - initial_delay: u64, - sync_timeout: u64, -) -> Result<(), String> { - let epoch_duration = slot_duration * (E::slots_per_epoch() as u32); - let network_c = network.clone(); - // Delay for `initial_delay` epochs before adding another node to start syncing - let config1 = beacon_config.clone(); - epoch_delay( - Epoch::new(initial_delay), - slot_duration, - E::slots_per_epoch(), - ) - .await; - // Add two beacon nodes - network - .add_beacon_node(beacon_config.clone(), false) - .await?; - network.add_beacon_node(beacon_config, false).await?; - // Delay before adding additional syncing nodes. - epoch_delay( - Epoch::new(sync_timeout - 5), - slot_duration, - E::slots_per_epoch(), - ) - .await; - // Add a beacon node - network.add_beacon_node(config1.clone(), false).await?; - // Check every `epoch_duration` if nodes are synced - // limited to at most `sync_timeout` epochs - let mut interval = tokio::time::interval(epoch_duration); - let mut count = 0; - loop { - interval.tick().await; - if count >= sync_timeout || !check_still_syncing(&network_c).await? { - break; - } - count += 1; - } - let epoch = network.bootnode_epoch().await?; - verify_all_finalized_at(network, epoch) - .map_err(|e| format!("One node sync error: {}", e)) - .await -} - -/// Run syncing strategies one after other. -pub async fn verify_syncing( - network: LocalNetwork, - beacon_config: ClientConfig, - slot_duration: Duration, - initial_delay: u64, - sync_timeout: u64, -) -> Result<(), String> { - verify_one_node_sync( - network.clone(), - beacon_config.clone(), - slot_duration, - initial_delay, - sync_timeout, - ) - .await?; - println!("Completed one node sync"); - verify_two_nodes_sync( - network.clone(), - beacon_config.clone(), - slot_duration, - initial_delay, - sync_timeout, - ) - .await?; - println!("Completed two node sync"); - verify_in_between_sync( - network, - beacon_config, - slot_duration, - initial_delay, - sync_timeout, - ) - .await?; - println!("Completed in between sync"); - Ok(()) -} - -pub async fn check_still_syncing(network: &LocalNetwork) -> Result { - // get syncing status of nodes - let mut status = Vec::new(); - for remote_node in network.remote_nodes()? { - status.push( - remote_node - .get_node_syncing() - .await - .map(|body| body.data.is_syncing) - .map_err(|e| format!("Get syncing status via http failed: {:?}", e))?, - ) - } - Ok(status.iter().any(|is_syncing| *is_syncing)) -} diff --git a/validator_client/src/block_service.rs b/validator_client/src/block_service.rs index 445d4f1a5d9..06d484a52bb 100644 --- a/validator_client/src/block_service.rs +++ b/validator_client/src/block_service.rs @@ -892,6 +892,7 @@ impl UnsignedBlock { } } +#[derive(Debug)] pub enum SignedBlock { Full(PublishBlockRequest), Blinded(Arc>), From b18d3261fdbe2a1befa0f803a03e07018fb170bc Mon Sep 17 00:00:00 2001 From: Mac L Date: Thu, 11 Apr 2024 15:32:02 +1000 Subject: [PATCH 02/10] Add fallback simulator --- .github/workflows/test-suite.yml | 44 ++--- beacon_node/client/src/builder.rs | 14 +- beacon_node/genesis/src/interop.rs | 3 - beacon_node/genesis/src/lib.rs | 1 - testing/simulator/src/basic_sim.rs | 73 +++---- testing/simulator/src/checks.rs | 102 ++++++++++ testing/simulator/src/cli.rs | 51 ++++- testing/simulator/src/fallback_sim.rs | 255 +++++++++++++++++++++++++ testing/simulator/src/local_network.rs | 20 +- testing/simulator/src/main.rs | 8 + 10 files changed, 475 insertions(+), 96 deletions(-) create mode 100644 testing/simulator/src/fallback_sim.rs diff --git a/.github/workflows/test-suite.yml b/.github/workflows/test-suite.yml index a8db6fab8fd..e36ca530faa 100644 --- a/.github/workflows/test-suite.yml +++ b/.github/workflows/test-suite.yml @@ -225,8 +225,8 @@ jobs: run: docker build --build-arg FEATURES=portable -t lighthouse:local . - name: Test the built image run: docker run -t lighthouse:local lighthouse --version - eth1-simulator-ubuntu: - name: eth1-simulator-ubuntu + basic-simulator-ubuntu: + name: basic-simulator-ubuntu runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -235,30 +235,10 @@ jobs: with: channel: stable cache-target: release - - name: Install Foundry (anvil) - uses: foundry-rs/foundry-toolchain@v1 - with: - version: nightly-ca67d15f4abd46394b324c50e21e66f306a1162d - - name: Run the beacon chain sim that starts from an eth1 contract - run: cargo run --release --bin simulator eth1-sim - merge-transition-ubuntu: - name: merge-transition-ubuntu - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Get latest version of stable Rust - uses: moonrepo/setup-rust@v1 - with: - channel: stable - cache-target: release - - name: Install Foundry (anvil) - uses: foundry-rs/foundry-toolchain@v1 - with: - version: nightly-ca67d15f4abd46394b324c50e21e66f306a1162d - - name: Run the beacon chain sim and go through the merge transition - run: cargo run --release --bin simulator eth1-sim --post-merge - no-eth1-simulator-ubuntu: - name: no-eth1-simulator-ubuntu + - name: Run a basic beacon chain sim that starts from Bellatrix + run: cargo run --release --bin simulator basic-sim + fallback-simulator-ubuntu: + name: fallback-simulator-ubuntu runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -267,10 +247,10 @@ jobs: with: channel: stable cache-target: release - - name: Run the beacon chain sim without an eth1 connection - run: cargo run --release --bin simulator no-eth1-sim - syncing-simulator-ubuntu: - name: syncing-simulator-ubuntu + - name: Run a beacon chain sim which tests VC fallback behaviour + run: cargo run --release --bin simulator fallback-sim + merge-transition-ubuntu: + name: merge-transition-ubuntu runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -283,8 +263,8 @@ jobs: uses: foundry-rs/foundry-toolchain@v1 with: version: nightly-ca67d15f4abd46394b324c50e21e66f306a1162d - - name: Run the syncing simulator - run: cargo run --release --bin simulator syncing-sim + - name: Run the beacon chain sim and go through the merge transition + run: cargo run --release --bin simulator eth1-sim --post-merge doppelganger-protection-test: name: doppelganger-protection-test runs-on: ${{ github.repository == 'sigp/lighthouse' && fromJson('["self-hosted", "linux", "CI", "small"]') || 'ubuntu-latest' }} diff --git a/beacon_node/client/src/builder.rs b/beacon_node/client/src/builder.rs index 2e603f69404..91050dc2902 100644 --- a/beacon_node/client/src/builder.rs +++ b/beacon_node/client/src/builder.rs @@ -28,10 +28,7 @@ use eth2::{ }; use execution_layer::ExecutionLayer; use futures::channel::mpsc::Receiver; -use genesis::{ - interop_genesis_state, Eth1GenesisService, DEFAULT_ETH1_BLOCK_HASH, - DEFAULT_EXECUTION_PAYLOAD_TRANSACTIONS_ROOT, -}; +use genesis::{interop_genesis_state, Eth1GenesisService, DEFAULT_ETH1_BLOCK_HASH}; use lighthouse_network::{prometheus_client::registry::Registry, NetworkGlobals}; use monitoring_api::{MonitoringHttpClient, ProcessType}; use network::{NetworkConfig, NetworkSenders, NetworkService}; @@ -41,15 +38,15 @@ use slog::{debug, info, warn, Logger}; use ssz::Decode; use std::net::TcpListener; use std::path::{Path, PathBuf}; -use std::str::FromStr; use std::sync::Arc; use std::time::Duration; use std::time::{SystemTime, UNIX_EPOCH}; use timer::spawn_timer; use tokio::sync::oneshot; +use tree_hash::TreeHash; use types::{ test_utils::generate_deterministic_keypairs, BeaconState, BlobSidecarList, ChainSpec, EthSpec, - ExecutionBlockHash, ExecutionPayloadHeaderMerge, Hash256, SignedBeaconBlock, + ExecutionBlockHash, ExecutionPayloadHeaderMerge, Hash256, SignedBeaconBlock, Transactions, }; /// Interval between polling the eth1 node for genesis information. @@ -276,10 +273,7 @@ where genesis_time, } => { let execution_payload_header = ExecutionPayloadHeaderMerge { - transactions_root: Hash256::from_str( - DEFAULT_EXECUTION_PAYLOAD_TRANSACTIONS_ROOT, - ) - .unwrap(), + transactions_root: Transactions::::empty().tree_hash_root(), ..Default::default() }; let keypairs = generate_deterministic_keypairs(validator_count); diff --git a/beacon_node/genesis/src/interop.rs b/beacon_node/genesis/src/interop.rs index dbc791155da..b4753e92f1f 100644 --- a/beacon_node/genesis/src/interop.rs +++ b/beacon_node/genesis/src/interop.rs @@ -10,9 +10,6 @@ use types::{ pub const DEFAULT_ETH1_BLOCK_HASH: &[u8] = &[0x42; 32]; -pub const DEFAULT_EXECUTION_PAYLOAD_TRANSACTIONS_ROOT: &str = - "0x7ffe241ea60187fdb0187bfa22de35d1f9bed7ab061d9401fd47e34a54fbede1"; - pub fn bls_withdrawal_credentials(pubkey: &PublicKey, spec: &ChainSpec) -> Hash256 { let mut credentials = hash(&pubkey.as_ssz_bytes()); credentials[0] = spec.bls_withdrawal_prefix_byte; diff --git a/beacon_node/genesis/src/lib.rs b/beacon_node/genesis/src/lib.rs index ccad7bef920..3fb053bf880 100644 --- a/beacon_node/genesis/src/lib.rs +++ b/beacon_node/genesis/src/lib.rs @@ -8,6 +8,5 @@ pub use eth1_genesis_service::{Eth1GenesisService, Statistics}; pub use interop::{ bls_withdrawal_credentials, interop_genesis_state, interop_genesis_state_with_eth1, interop_genesis_state_with_withdrawal_credentials, DEFAULT_ETH1_BLOCK_HASH, - DEFAULT_EXECUTION_PAYLOAD_TRANSACTIONS_ROOT, }; pub use types::test_utils::generate_deterministic_keypairs; diff --git a/testing/simulator/src/basic_sim.rs b/testing/simulator/src/basic_sim.rs index d7c0a8283c3..9a2c6cff511 100644 --- a/testing/simulator/src/basic_sim.rs +++ b/testing/simulator/src/basic_sim.rs @@ -7,7 +7,7 @@ use crate::retry::with_retry; use futures::prelude::*; use node_test_rig::{ environment::{EnvironmentBuilder, LoggerConfig}, - testing_validator_config, ValidatorFiles, + testing_validator_config, ApiTopic, ValidatorFiles, }; use rayon::prelude::*; use std::cmp::max; @@ -27,21 +27,22 @@ const SUGGESTED_FEE_RECIPIENT: [u8; 20] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]; pub fn run_basic_sim(matches: &ArgMatches) -> Result<(), String> { - let node_count = value_t!(matches, "nodes", usize).expect("missing nodes default"); - let proposer_nodes = value_t!(matches, "proposer-nodes", usize).unwrap_or(0); - println!("PROPOSER-NODES: {}", proposer_nodes); + let node_count = value_t!(matches, "nodes", usize).expect("Missing nodes default"); + let proposer_nodes = + value_t!(matches, "proposer-nodes", usize).expect("Missing proposer-nodes default"); let validators_per_node = value_t!(matches, "validators-per-node", usize) - .expect("missing validators_per_node default"); + .expect("Missing validators-per-node default"); let speed_up_factor = - value_t!(matches, "speed-up-factor", u64).expect("missing speed-up-factor default"); - let continue_after_checks = matches.is_present("continue-after-checks"); + value_t!(matches, "speed-up-factor", u64).expect("Missing speed-up-factor default"); let log_level = value_t!(matches, "debug-level", String).expect("Missing default log-level"); + let continue_after_checks = matches.is_present("continue-after-checks"); - println!("Beacon Chain Simulator:"); - println!(" nodes:{}, proposer_nodes: {}", node_count, proposer_nodes); - - println!(" validators-per-node:{}", validators_per_node); - println!(" continue-after-checks:{}", continue_after_checks); + println!("Basic Simulator:"); + println!(" nodes: {}", node_count); + println!(" proposer-nodes: {}", proposer_nodes); + println!(" validators-per-node: {}", validators_per_node); + println!(" speed-up-factor: {}", speed_up_factor); + println!(" continue-after-checks: {}", continue_after_checks); // Generate the directories and keystores required for the validator clients. let validator_files = (0..node_count) @@ -81,11 +82,9 @@ pub fn run_basic_sim(matches: &ArgMatches) -> Result<(), String> { let total_validator_count = validators_per_node * node_count; let genesis_delay = GENESIS_DELAY; - let deneb_fork_version = spec.deneb_fork_version; - let _electra_fork_version = spec.electra_fork_version; // Convenience variables. Update these values when adding a newer fork. - let latest_fork_version = deneb_fork_version; + let latest_fork_version = spec.deneb_fork_version; let latest_fork_start_epoch = DENEB_FORK_EPOCH; spec.seconds_per_slot /= speed_up_factor; @@ -134,10 +133,12 @@ pub fn run_basic_sim(matches: &ArgMatches) -> Result<(), String> { /* * One by one, add proposer nodes to the network. */ - //for _ in 0..proposer_nodes - 1 { - // println!("Adding a proposer node"); - // network.add_beacon_node(beacon_config.clone(), true).await?; - //} + for _ in 0..proposer_nodes { + println!("Adding a proposer node"); + network + .add_beacon_node(beacon_config.clone(), mock_execution_config.clone(), true) + .await?; + } /* * One by one, add validators to the network. @@ -153,23 +154,23 @@ pub fn run_basic_sim(matches: &ArgMatches) -> Result<(), String> { println!("Adding validator client {}", i); // Enable broadcast on every 4th node. - //if i % 4 == 0 { - // validator_config.broadcast_topics = ApiTopic::all(); - // let beacon_nodes = vec![i, (i + 1) % node_count]; - // network_1 - // .add_validator_client_with_fallbacks( - // validator_config, - // i, - // beacon_nodes, - // files, - // ) - // .await - //} else { - network_1 - .add_validator_client(validator_config, i, files, false) //i % 2 == 0) - .await - //} - .expect("should add validator"); + if i % 4 == 0 { + validator_config.broadcast_topics = ApiTopic::all(); + let beacon_nodes = vec![i, (i + 1) % node_count]; + network_1 + .add_validator_client_with_fallbacks( + validator_config, + i, + beacon_nodes, + files, + ) + .await + } else { + network_1 + .add_validator_client(validator_config, i, files) + .await + } + .expect("should add validator"); }, "vc", ); diff --git a/testing/simulator/src/checks.rs b/testing/simulator/src/checks.rs index 009e1b39118..5f55820cb26 100644 --- a/testing/simulator/src/checks.rs +++ b/testing/simulator/src/checks.rs @@ -366,3 +366,105 @@ pub async fn verify_full_blob_production_up_to( Ok(()) } + +// Causes the beacon node at `node_index` to disconnect from the execution layer. +pub async fn disconnect_from_execution_layer( + network: LocalNetwork, + node_index: usize, +) -> Result<(), String> { + eprintln!("Disabling Execution Node {node_index}"); + + // Force the execution node to return the `syncing` status. + network.execution_nodes.read()[node_index] + .server + .all_payloads_syncing(false); + Ok(()) +} + +// Causes the beacon node at `node_index` to reconnect from the execution layer. +pub async fn reconnect_to_execution_layer( + network: LocalNetwork, + node_index: usize, +) -> Result<(), String> { + network.execution_nodes.read()[node_index] + .server + .all_payloads_valid(); + + eprintln!("Re-enabling Execution Node {node_index}"); + Ok(()) +} + +/// Ensure all validators have attested correctly. +pub async fn check_attestation_correctness( + network: LocalNetwork, + start_epoch: u64, + upto_epoch: u64, + slot_duration: Duration, + // Select which node to query. Will use this node to determine the global network performance. + node_index: usize, + acceptable_attestation_performance: f64, +) -> Result<(), String> { + epoch_delay(Epoch::new(upto_epoch), slot_duration, E::slots_per_epoch()).await; + + let remote_node = &network.remote_nodes()?[node_index]; + + let results = remote_node + .get_lighthouse_analysis_attestation_performance( + Epoch::new(start_epoch), + Epoch::new(upto_epoch - 2), + "global".to_string(), + ) + .await + .map_err(|e| format!("Unable to get attestation performance: {e}"))?; + + let mut active_successes: f64 = 0.0; + let mut head_successes: f64 = 0.0; + let mut target_successes: f64 = 0.0; + let mut source_successes: f64 = 0.0; + + let mut total: f64 = 0.0; + + for result in results { + for epochs in result.epochs.values() { + total += 1.0; + + if epochs.active { + active_successes += 1.0; + } + if epochs.head { + head_successes += 1.0; + } + if epochs.target { + target_successes += 1.0; + } + if epochs.source { + source_successes += 1.0; + } + } + } + let active_percent = active_successes / total * 100.0; + let head_percent = head_successes / total * 100.0; + let target_percent = target_successes / total * 100.0; + let source_percent = source_successes / total * 100.0; + + eprintln!("Total Attestations: {}", total); + eprintln!("Active: {}: {}%", active_successes, active_percent); + eprintln!("Head: {}: {}%", head_successes, head_percent); + eprintln!("Target: {}: {}%", target_successes, target_percent); + eprintln!("Source: {}: {}%", source_successes, source_percent); + + if active_percent < acceptable_attestation_performance { + return Err("Active percent was below required level".to_string()); + } + if head_percent < acceptable_attestation_performance { + return Err("Head percent was below required level".to_string()); + } + if target_percent < acceptable_attestation_performance { + return Err("Target percent was below required level".to_string()); + } + if source_percent < acceptable_attestation_performance { + return Err("Source percent was below required level".to_string()); + } + + Ok(()) +} diff --git a/testing/simulator/src/cli.rs b/testing/simulator/src/cli.rs index 240ad9c3f05..bad6ffbebf0 100644 --- a/testing/simulator/src/cli.rs +++ b/testing/simulator/src/cli.rs @@ -28,7 +28,7 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> { .short("p") .long("proposer-nodes") .takes_value(true) - .default_value("0") + .default_value("3") .help("Number of proposer-only beacon nodes")) .arg(Arg::with_name("validators-per-node") .short("v") @@ -54,4 +54,53 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> { .takes_value(false) .help("Continue after checks (default false)")) ) + .subcommand( + SubCommand::with_name("fallback-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 \ + simulation using a local `anvil` instance (you must have `anvil` \ + installed and avaliable on your path). All beacon nodes independently listen \ + for genesis from the deposit contract, then start operating. \ + \ + As the simulation runs, there are checks made to ensure that all components \ + are running correctly. If any of these checks fail, the simulation will \ + exit immediately.", + ) + .arg(Arg::with_name("vc-count") + .short("c") + .long("vc-count") + .takes_value(true) + .default_value("3") + .help("Number of validator clients.")) + .arg(Arg::with_name("bns-per-vc") + .short("b") + .long("bns-per-vc") + .takes_value(true) + .default_value("2") + .help("Number of beacon nodes per validator client.")) + .arg(Arg::with_name("validators-per-vc") + .short("v") + .long("validators-per-vc") + .takes_value(true) + .default_value("20") + .help("Number of validators per client.")) + .arg(Arg::with_name("speed-up-factor") + .short("s") + .long("speed-up-factor") + .takes_value(true) + .default_value("3") + .help("Speed up factor. Please use a divisor of 12.")) + .arg(Arg::with_name("debug-level") + .short("d") + .long("debug-level") + .takes_value(true) + .default_value("debug") + .help("Set the severity level of the logs.")) + .arg(Arg::with_name("continue-after-checks") + .short("c") + .long("continue_after_checks") + .takes_value(false) + .help("Continue after checks (default false)")) + ) } diff --git a/testing/simulator/src/fallback_sim.rs b/testing/simulator/src/fallback_sim.rs new file mode 100644 index 00000000000..8ea3e97d792 --- /dev/null +++ b/testing/simulator/src/fallback_sim.rs @@ -0,0 +1,255 @@ +use crate::local_network::LocalNetworkParams; +use crate::{checks, LocalNetwork}; +use clap::ArgMatches; + +use crate::retry::with_retry; +use futures::prelude::*; +use node_test_rig::{ + environment::{EnvironmentBuilder, LoggerConfig}, + testing_validator_config, ValidatorFiles, +}; +use rayon::prelude::*; +use std::cmp::max; +use std::time::Duration; +use tokio::time::sleep; +use types::{Epoch, EthSpec, MinimalEthSpec}; + +const END_EPOCH: u64 = 10; +const GENESIS_DELAY: u64 = 32; +const ALTAIR_FORK_EPOCH: u64 = 0; +const BELLATRIX_FORK_EPOCH: u64 = 0; +const CAPELLA_FORK_EPOCH: u64 = 1; +const DENEB_FORK_EPOCH: u64 = 2; +//const ELECTRA_FORK_EPOCH: u64 = 3; + +// Since simulator tests are non-deterministic and there is a non-zero chance of missed +// attestations, define an acceptable network-wide attestation performance. +// +// This has potential to block CI so it should be set conservatively enough that spurious failures +// don't become very common, but not so conservatively that regressions to the fallback mechanism +// cannot be detected. +const ACCEPTABLE_FALLBACK_ATTESTATION_HIT_PERCENTAGE: f64 = 95.0; + +const SUGGESTED_FEE_RECIPIENT: [u8; 20] = + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]; + +pub fn run_fallback_sim(matches: &ArgMatches) -> Result<(), String> { + let vc_count = value_t!(matches, "vc-count", usize).expect("Missing validator-count default"); + let validators_per_vc = + value_t!(matches, "validators-per-vc", usize).expect("Missing validators-per-vc default"); + let bns_per_vc = value_t!(matches, "bns-per-vc", usize).expect("Missing bns-per-vc default"); + assert!(bns_per_vc > 1); + let speed_up_factor = + value_t!(matches, "speed-up-factor", u64).expect("Missing speed-up-factor default"); + let log_level = value_t!(matches, "debug-level", String).expect("Missing default log-level"); + let continue_after_checks = matches.is_present("continue-after-checks"); + + println!("Fallback Simulator:"); + println!(" vc-count: {}", vc_count); + println!(" validators-per-vc: {}", validators_per_vc); + println!(" bns-per-vc: {}", bns_per_vc); + println!(" speed-up-factor: {}", speed_up_factor); + println!(" continue-after-checks: {}", continue_after_checks); + + // Generate the directories and keystores required for the validator clients. + let validator_files = (0..vc_count) + .into_par_iter() + .map(|i| { + println!( + "Generating keystores for validator {} of {}", + i + 1, + vc_count + ); + + let indices = (i * validators_per_vc..(i + 1) * validators_per_vc).collect::>(); + ValidatorFiles::with_keystores(&indices).unwrap() + }) + .collect::>(); + + let mut env = EnvironmentBuilder::minimal() + .initialize_logger(LoggerConfig { + path: None, + debug_level: log_level.clone(), + logfile_debug_level: log_level, + log_format: None, + logfile_format: None, + log_color: false, + disable_log_timestamp: false, + max_log_size: 0, + max_log_number: 0, + compression: false, + is_restricted: true, + sse_logging: false, + })? + .multi_threaded_tokio_runtime()? + .build()?; + + let spec = &mut env.eth2_config.spec; + + let total_validator_count = validators_per_vc * vc_count; + let node_count = vc_count * bns_per_vc; + + let genesis_delay = GENESIS_DELAY; + + spec.seconds_per_slot /= speed_up_factor; + spec.seconds_per_slot = max(1, spec.seconds_per_slot); + spec.genesis_delay = genesis_delay; + spec.min_genesis_time = 0; + spec.min_genesis_active_validator_count = total_validator_count as u64; + spec.altair_fork_epoch = Some(Epoch::new(ALTAIR_FORK_EPOCH)); + spec.bellatrix_fork_epoch = Some(Epoch::new(BELLATRIX_FORK_EPOCH)); + spec.capella_fork_epoch = Some(Epoch::new(CAPELLA_FORK_EPOCH)); + spec.deneb_fork_epoch = Some(Epoch::new(DENEB_FORK_EPOCH)); + //spec.electra_fork_epoch = Some(Epoch::new(ELECTRA_FORK_EPOCH)); + + let slot_duration = Duration::from_secs(spec.seconds_per_slot); + let slots_per_epoch = MinimalEthSpec::slots_per_epoch(); + + let disconnection_epoch = 2; + let epochs_disconnected = 5; + + let context = env.core_context(); + + let main_future = async { + /* + * Create a new `LocalNetwork` with one beacon node. + */ + let max_retries = 3; + let (network, beacon_config, mock_execution_config) = with_retry(max_retries, || { + Box::pin(LocalNetwork::create_local_network( + None, + None, + LocalNetworkParams { + validator_count: total_validator_count, + node_count, + proposer_nodes: 0, + genesis_delay, + }, + context.clone(), + )) + }) + .await?; + + // Add nodes to the network. + for _ in 0..node_count { + network + .add_beacon_node(beacon_config.clone(), mock_execution_config.clone(), false) + .await?; + } + + /* + * One by one, add validators to the network. + */ + let executor = context.executor.clone(); + for (i, files) in validator_files.into_iter().enumerate() { + let network_1 = network.clone(); + + let mut beacon_nodes = Vec::with_capacity(vc_count * bns_per_vc); + // Each VC gets a unique set of BNs which are not shared with any other VC. + for j in 0..bns_per_vc { + beacon_nodes.push(bns_per_vc * i + j) + } + + executor.spawn( + async move { + let mut validator_config = testing_validator_config(); + validator_config.fee_recipient = Some(SUGGESTED_FEE_RECIPIENT.into()); + println!("Adding validator client {}", i); + network_1 + .add_validator_client_with_fallbacks( + validator_config, + i, + beacon_nodes, + files, + ) + .await + .expect("should add validator"); + }, + "vc", + ); + } + + let duration_to_genesis = network.duration_to_genesis().await; + println!("Duration to genesis: {}", duration_to_genesis.as_secs()); + sleep(duration_to_genesis).await; + + let test_sequence = async { + checks::epoch_delay( + Epoch::new(disconnection_epoch), + slot_duration, + slots_per_epoch, + ) + .await; + // Iterate through each VC and disconnect all BNs but the last node for each VC. + for i in 0..vc_count { + for j in 0..(bns_per_vc - 1) { + let node_index = bns_per_vc * i + j; + checks::disconnect_from_execution_layer(network.clone(), node_index).await?; + } + } + checks::epoch_delay( + Epoch::new(epochs_disconnected), + slot_duration, + slots_per_epoch, + ) + .await; + // Enable all BNs. + for i in 0..node_count { + checks::reconnect_to_execution_layer(network.clone(), i).await?; + } + Ok::<(), String>(()) + }; + + /* + * Start the checks that ensure the network performs as expected. + * + * We start these checks immediately after the validators have started. This means we're + * relying on the validator futures to all return immediately after genesis so that these + * tests start at the right time. Whilst this is works well for now, it's subject to + * breakage by changes to the VC. + */ + + let (sequence, check_attestations) = futures::join!( + test_sequence, + checks::check_attestation_correctness( + network.clone(), + 0, + END_EPOCH, + slot_duration, + // Use the last node index as this will never have been disabled. + node_count - 1, + ACCEPTABLE_FALLBACK_ATTESTATION_HIT_PERCENTAGE, + ), + ); + sequence?; + check_attestations?; + + // The `final_future` either completes immediately or never completes, depending on the value + // of `continue_after_checks`. + + if continue_after_checks { + future::pending::<()>().await; + } + /* + * End the simulation by dropping the network. This will kill all running beacon nodes and + * validator clients. + */ + 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); + Ok::<(), String>(()) + }; + + env.runtime().block_on(main_future).unwrap(); + + env.fire_signal(); + env.shutdown_on_idle(); + + Ok(()) +} diff --git a/testing/simulator/src/local_network.rs b/testing/simulator/src/local_network.rs index caf71fed976..cd8b26d846d 100644 --- a/testing/simulator/src/local_network.rs +++ b/testing/simulator/src/local_network.rs @@ -17,7 +17,6 @@ use types::{ChainSpec, Epoch, EthSpec}; const BOOTNODE_PORT: u16 = 42424; const QUIC_PORT: u16 = 43424; -pub const INVALID_ADDRESS: &str = "http://127.0.0.1:42423"; pub const EXECUTION_PORT: u16 = 4000; @@ -148,7 +147,6 @@ impl LocalNetwork { default_mock_execution_config::(&context.eth2_config().spec, genesis_time) }; - //let network = LocalNetwork::new(context, beacon_config.clone(), mock_execution_config).await?; let network = Self { inner: Arc::new(Inner { context, @@ -227,6 +225,7 @@ impl LocalNetwork { &self, mut beacon_config: ClientConfig, mut mock_execution_config: MockExecutionConfig, + is_proposer: bool, ) -> Result<(LocalBeaconNode, LocalExecutionNode), String> { let count = (self.beacon_node_count() + self.proposer_node_count()) as u16; @@ -242,7 +241,7 @@ impl LocalNetwork { beacon_config.network.enr_udp4_port = Some(discv5_port.try_into().unwrap()); beacon_config.network.enr_tcp4_port = Some(libp2p_tcp_port.try_into().unwrap()); beacon_config.network.discv5_config.table_filter = |_| true; - //beacon_config.network.proposer_only = is_proposer; + beacon_config.network.proposer_only = is_proposer; mock_execution_config.server_config.listen_port = EXECUTION_PORT + count; @@ -295,7 +294,7 @@ impl LocalNetwork { } let (beacon_node, execution_node) = if first_bn_exists { // Network already exists. We construct a new node. - self.construct_beacon_node(beacon_config, mock_execution_config) + self.construct_beacon_node(beacon_config, mock_execution_config, is_proposer) .await? } else { // Network does not exist. We construct a boot node. @@ -319,7 +318,6 @@ impl LocalNetwork { mut validator_config: ValidatorConfig, beacon_node: usize, validator_files: ValidatorFiles, - invalid_first_beacon_node: bool, //to test beacon node fallbacks ) -> Result<(), String> { let context = self .context @@ -350,11 +348,7 @@ impl LocalNetwork { format!("http://{}:{}", socket_addr.ip(), socket_addr.port()).as_str(), ) .unwrap(); - validator_config.beacon_nodes = if invalid_first_beacon_node { - vec![SensitiveUrl::parse(INVALID_ADDRESS).unwrap(), beacon_node] - } else { - vec![beacon_node] - }; + validator_config.beacon_nodes = vec![beacon_node]; // If we have a proposer node established, use it. if let Some(proposer_socket_addr) = proposer_socket_addr { @@ -380,7 +374,7 @@ impl LocalNetwork { Ok(()) } - pub async fn _add_validator_client_with_fallbacks( + pub async fn add_validator_client_with_fallbacks( &self, mut validator_config: ValidatorConfig, validator_index: usize, @@ -403,11 +397,11 @@ impl LocalNetwork { .http_api_listen_addr() .expect("Must have http started") }; - let beacon_node = SensitiveUrl::parse( + let beacon_node_url = SensitiveUrl::parse( format!("http://{}:{}", socket_addr.ip(), socket_addr.port()).as_str(), ) .unwrap(); - beacon_node_urls.push(beacon_node); + beacon_node_urls.push(beacon_node_url); } validator_config.beacon_nodes = beacon_node_urls; diff --git a/testing/simulator/src/main.rs b/testing/simulator/src/main.rs index 068b369fbc5..3f35654f335 100644 --- a/testing/simulator/src/main.rs +++ b/testing/simulator/src/main.rs @@ -19,6 +19,7 @@ extern crate clap; mod basic_sim; mod checks; mod cli; +mod fallback_sim; mod local_network; mod retry; @@ -42,6 +43,13 @@ fn main() { std::process::exit(1) } }, + ("fallback-sim", Some(matches)) => match fallback_sim::run_fallback_sim(matches) { + Ok(()) => println!("Simulation exited successfully"), + Err(e) => { + eprintln!("Simulation exited with error: {}", e); + std::process::exit(1) + } + }, _ => { eprintln!("Invalid subcommand. Use --help to see available options"); std::process::exit(1) From abc18751556ef9fc8de20ed0ca31dc90ffae2031 Mon Sep 17 00:00:00 2001 From: Mac L Date: Thu, 11 Apr 2024 16:06:09 +1000 Subject: [PATCH 03/10] Try Sean's test fix --- beacon_node/client/src/builder.rs | 15 +++++----- .../test_utils/execution_block_generator.rs | 28 +++++++++---------- 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/beacon_node/client/src/builder.rs b/beacon_node/client/src/builder.rs index 91050dc2902..b40e4a4de00 100644 --- a/beacon_node/client/src/builder.rs +++ b/beacon_node/client/src/builder.rs @@ -26,6 +26,7 @@ use eth2::{ types::{BlockId, StateId}, BeaconNodeHttpClient, Error as ApiError, Timeouts, }; +use execution_layer::test_utils::generate_genesis_header; use execution_layer::ExecutionLayer; use futures::channel::mpsc::Receiver; use genesis::{interop_genesis_state, Eth1GenesisService, DEFAULT_ETH1_BLOCK_HASH}; @@ -43,10 +44,9 @@ use std::time::Duration; use std::time::{SystemTime, UNIX_EPOCH}; use timer::spawn_timer; use tokio::sync::oneshot; -use tree_hash::TreeHash; use types::{ test_utils::generate_deterministic_keypairs, BeaconState, BlobSidecarList, ChainSpec, EthSpec, - ExecutionBlockHash, ExecutionPayloadHeaderMerge, Hash256, SignedBeaconBlock, Transactions, + ExecutionBlockHash, Hash256, SignedBeaconBlock, }; /// Interval between polling the eth1 node for genesis information. @@ -272,16 +272,17 @@ where validator_count, genesis_time, } => { - let execution_payload_header = ExecutionPayloadHeaderMerge { - transactions_root: Transactions::::empty().tree_hash_root(), - ..Default::default() - }; + //let execution_payload_header = ExecutionPayloadHeaderMerge { + // transactions_root: Transactions::::empty().tree_hash_root(), + // ..Default::default() + //}; + let execution_payload_header = generate_genesis_header(&spec, true); let keypairs = generate_deterministic_keypairs(validator_count); let genesis_state = interop_genesis_state( &keypairs, genesis_time, Hash256::from_slice(DEFAULT_ETH1_BLOCK_HASH), - Some(execution_payload_header.into()), + execution_payload_header, &spec, )?; builder.genesis_state(genesis_state).map(|v| (v, None))? diff --git a/beacon_node/execution_layer/src/test_utils/execution_block_generator.rs b/beacon_node/execution_layer/src/test_utils/execution_block_generator.rs index 5b1e9850e47..3a44f6e8d63 100644 --- a/beacon_node/execution_layer/src/test_utils/execution_block_generator.rs +++ b/beacon_node/execution_layer/src/test_utils/execution_block_generator.rs @@ -91,7 +91,14 @@ impl Block { pub fn as_execution_block_with_tx(&self) -> Option> { match self { Block::PoS(payload) => Some(payload.clone().try_into().unwrap()), - Block::PoW(_) => None, + Block::PoW(block) => Some( + ExecutionPayload::Merge(ExecutionPayloadMerge { + block_hash: block.block_hash, + ..Default::default() + }) + .try_into() + .unwrap(), + ), } } } @@ -176,23 +183,11 @@ impl ExecutionBlockGenerator { rng: make_rng(), }; - //gen.insert_pow_block(0).unwrap(); - // Merge from genesis - gen.insert_genesis_pos_block().unwrap(); + gen.insert_pow_block(0).unwrap(); gen } - pub fn insert_genesis_pos_block(&mut self) -> Result<(), String> { - // Insert block into block tree. - self.insert_block(Block::PoS(ExecutionPayloadMerge::default().into()))?; - - // Set block has head. - self.head_block = Some(Block::PoS(ExecutionPayloadMerge::default().into())); - - Ok(()) - } - pub fn latest_block(&self) -> Option> { self.head_block.clone() } @@ -791,12 +786,14 @@ pub fn generate_genesis_header( generate_genesis_block(spec.terminal_total_difficulty, DEFAULT_TERMINAL_BLOCK) .ok() .map(|block| block.block_hash); + let empty_transactions_root = Transactions::::empty().tree_hash_root(); match genesis_fork { ForkName::Base | ForkName::Altair => None, ForkName::Merge => { if post_transition_merge { let mut header = ExecutionPayloadHeader::Merge(<_>::default()); *header.block_hash_mut() = genesis_block_hash.unwrap_or_default(); + *header.transactions_root_mut() = empty_transactions_root; Some(header) } else { Some(ExecutionPayloadHeader::::Merge(<_>::default())) @@ -805,16 +802,19 @@ pub fn generate_genesis_header( ForkName::Capella => { let mut header = ExecutionPayloadHeader::Capella(<_>::default()); *header.block_hash_mut() = genesis_block_hash.unwrap_or_default(); + *header.transactions_root_mut() = empty_transactions_root; Some(header) } ForkName::Deneb => { let mut header = ExecutionPayloadHeader::Deneb(<_>::default()); *header.block_hash_mut() = genesis_block_hash.unwrap_or_default(); + *header.transactions_root_mut() = empty_transactions_root; Some(header) } ForkName::Electra => { let mut header = ExecutionPayloadHeader::Electra(<_>::default()); *header.block_hash_mut() = genesis_block_hash.unwrap_or_default(); + *header.transactions_root_mut() = empty_transactions_root; Some(header) } } From 1b5fcc40db6aee2e5a0678103c478a3e14325e04 Mon Sep 17 00:00:00 2001 From: Mac L Date: Fri, 12 Apr 2024 11:03:52 +1000 Subject: [PATCH 04/10] More fixes --- .github/workflows/test-suite.yml | 22 ++----------------- Cargo.lock | 1 - .../test_utils/execution_block_generator.rs | 7 ------ testing/simulator/Cargo.toml | 1 - testing/simulator/src/fallback_sim.rs | 2 +- 5 files changed, 3 insertions(+), 30 deletions(-) diff --git a/.github/workflows/test-suite.yml b/.github/workflows/test-suite.yml index e36ca530faa..413dd2b95dd 100644 --- a/.github/workflows/test-suite.yml +++ b/.github/workflows/test-suite.yml @@ -249,22 +249,6 @@ jobs: cache-target: release - name: Run a beacon chain sim which tests VC fallback behaviour run: cargo run --release --bin simulator fallback-sim - merge-transition-ubuntu: - name: merge-transition-ubuntu - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Get latest version of stable Rust - uses: moonrepo/setup-rust@v1 - with: - channel: stable - cache-target: release - - name: Install Foundry (anvil) - uses: foundry-rs/foundry-toolchain@v1 - with: - version: nightly-ca67d15f4abd46394b324c50e21e66f306a1162d - - name: Run the beacon chain sim and go through the merge transition - run: cargo run --release --bin simulator eth1-sim --post-merge doppelganger-protection-test: name: doppelganger-protection-test runs-on: ${{ github.repository == 'sigp/lighthouse' && fromJson('["self-hosted", "linux", "CI", "small"]') || 'ubuntu-latest' }} @@ -422,10 +406,8 @@ jobs: 'state-transition-vectors-ubuntu', 'ef-tests-ubuntu', 'dockerfile-ubuntu', - 'eth1-simulator-ubuntu', - 'merge-transition-ubuntu', - 'no-eth1-simulator-ubuntu', - 'syncing-simulator-ubuntu', + 'basic-simulator-ubuntu', + 'fallback-simulator-ubuntu', 'doppelganger-protection-test', 'execution-engine-integration-ubuntu', 'check-code', diff --git a/Cargo.lock b/Cargo.lock index fffe3c9bcd6..dedeed2aaff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7522,7 +7522,6 @@ dependencies = [ "clap", "env_logger 0.9.3", "eth1", - "eth1_test_rig", "eth2_network_config", "ethereum-types 0.14.1", "execution_layer", diff --git a/beacon_node/execution_layer/src/test_utils/execution_block_generator.rs b/beacon_node/execution_layer/src/test_utils/execution_block_generator.rs index 3a44f6e8d63..5e6e2c6ff38 100644 --- a/beacon_node/execution_layer/src/test_utils/execution_block_generator.rs +++ b/beacon_node/execution_layer/src/test_utils/execution_block_generator.rs @@ -522,13 +522,6 @@ impl ExecutionBlockGenerator { let id = match payload_attributes { None => None, Some(attributes) => { - if !self.blocks.iter().any(|(_, block)| { - block.block_hash() == self.terminal_block_hash - || block.block_number() == self.terminal_block_number - }) { - return Err("refusing to create payload id before terminal block".to_string()); - } - let parent = self .blocks .get(&head_block_hash) diff --git a/testing/simulator/Cargo.toml b/testing/simulator/Cargo.toml index 9de0d290232..d7ff7b3dd85 100644 --- a/testing/simulator/Cargo.toml +++ b/testing/simulator/Cargo.toml @@ -14,7 +14,6 @@ types = { workspace = true } parking_lot = { workspace = true } futures = { workspace = true } tokio = { workspace = true } -eth1_test_rig = { workspace = true } env_logger = { workspace = true } clap = { workspace = true } rayon = { workspace = true } diff --git a/testing/simulator/src/fallback_sim.rs b/testing/simulator/src/fallback_sim.rs index 8ea3e97d792..909813eec17 100644 --- a/testing/simulator/src/fallback_sim.rs +++ b/testing/simulator/src/fallback_sim.rs @@ -28,7 +28,7 @@ const DENEB_FORK_EPOCH: u64 = 2; // This has potential to block CI so it should be set conservatively enough that spurious failures // don't become very common, but not so conservatively that regressions to the fallback mechanism // cannot be detected. -const ACCEPTABLE_FALLBACK_ATTESTATION_HIT_PERCENTAGE: f64 = 95.0; +const ACCEPTABLE_FALLBACK_ATTESTATION_HIT_PERCENTAGE: f64 = 80.0; const SUGGESTED_FEE_RECIPIENT: [u8; 20] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]; From aec601353266b3ad99ca194b7249072ade52b170 Mon Sep 17 00:00:00 2001 From: Mac L Date: Fri, 12 Apr 2024 14:50:30 +1000 Subject: [PATCH 05/10] Cleanup --- beacon_node/client/src/builder.rs | 4 ---- testing/simulator/src/cli.rs | 26 +++++++++++--------------- testing/simulator/src/main.rs | 10 ++++------ 3 files changed, 15 insertions(+), 25 deletions(-) diff --git a/beacon_node/client/src/builder.rs b/beacon_node/client/src/builder.rs index b40e4a4de00..cd6f580892d 100644 --- a/beacon_node/client/src/builder.rs +++ b/beacon_node/client/src/builder.rs @@ -272,10 +272,6 @@ where validator_count, genesis_time, } => { - //let execution_payload_header = ExecutionPayloadHeaderMerge { - // transactions_root: Transactions::::empty().tree_hash_root(), - // ..Default::default() - //}; let execution_payload_header = generate_genesis_header(&spec, true); let keypairs = generate_deterministic_keypairs(validator_count); let genesis_state = interop_genesis_state( diff --git a/testing/simulator/src/cli.rs b/testing/simulator/src/cli.rs index bad6ffbebf0..65bc1c7516e 100644 --- a/testing/simulator/src/cli.rs +++ b/testing/simulator/src/cli.rs @@ -8,12 +8,9 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> { .subcommand( SubCommand::with_name("basic-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 \ - simulation using a local `anvil` instance (you must have `anvil` \ - installed and avaliable on your path). All beacon nodes independently listen \ - for genesis from the deposit contract, then start operating. \ - \ + "Runs a Beacon Chain simulation with `n` beacon node and validator clients, \ + each with `v` validators. \ + The simulation runs with a post-Merge Genesis using `mock-el`. \ As the simulation runs, there are checks made to ensure that all components \ are running correctly. If any of these checks fail, the simulation will \ exit immediately.", @@ -57,15 +54,14 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> { .subcommand( SubCommand::with_name("fallback-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 \ - simulation using a local `anvil` instance (you must have `anvil` \ - installed and avaliable on your path). All beacon nodes independently listen \ - for genesis from the deposit contract, then start operating. \ - \ - As the simulation runs, there are checks made to ensure that all components \ - are running correctly. If any of these checks fail, the simulation will \ - exit immediately.", + "Runs a Beacon Chain simulation with `c` validator clients where each VC is \ + connected to `b` beacon nodes with `v` validators. \ + During the simulation, all but the last connected BN for each VC are \ + disconnected from the execution layer, which causes the VC to fallback to the \ + single remaining BN. \ + At the end of the simulation, there are checks made to ensure that all VCs \ + efficiently performed this fallback, within a certain tolerance. \ + Otherwise, the simulation will exit and an error will be reported.", ) .arg(Arg::with_name("vc-count") .short("c") diff --git a/testing/simulator/src/main.rs b/testing/simulator/src/main.rs index 3f35654f335..d1a2d0dc672 100644 --- a/testing/simulator/src/main.rs +++ b/testing/simulator/src/main.rs @@ -1,10 +1,8 @@ -//! This crate provides a simluation that creates `n` beacon node and validator clients, each with -//! `v` validators. A deposit contract is deployed at the start of the simulation using a local -//! `anvil` instance (you must have `anvil` installed and avaliable on your path). All -//! beacon nodes independently listen for genesis from the deposit contract, then start operating. +//! This crate provides various simulations that create both beacon nodes and validator clients, +//! each with `v` validators. //! -//! As the simulation runs, there are checks made to ensure that all components are running -//! correctly. If any of these checks fail, the simulation will exit immediately. +//! When a simulation runs, there are checks made to ensure that all components are operating +//! as expected. If any of these checks fail, the simulation will exit immediately. //! //! ## Future works //! From 836f2f7bcdbf4d6777959eccb71ce28b06d783cb Mon Sep 17 00:00:00 2001 From: Lion - dapplion <35266934+dapplion@users.noreply.github.com> Date: Sat, 13 Apr 2024 09:13:04 +0900 Subject: [PATCH 06/10] Update cli.rs --- testing/simulator/src/cli.rs | 86 +++++++++++++++++++++++------------- 1 file changed, 55 insertions(+), 31 deletions(-) diff --git a/testing/simulator/src/cli.rs b/testing/simulator/src/cli.rs index 65bc1c7516e..00af7e560ce 100644 --- a/testing/simulator/src/cli.rs +++ b/testing/simulator/src/cli.rs @@ -7,54 +7,66 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> { .about("Options for interacting with simulator") .subcommand( SubCommand::with_name("basic-sim") - .about( - "Runs a Beacon Chain simulation with `n` beacon node and validator clients, \ + .about( + "Runs a Beacon Chain simulation with `n` beacon node and validator clients, \ each with `v` validators. \ The simulation runs with a post-Merge Genesis using `mock-el`. \ As the simulation runs, there are checks made to ensure that all components \ are running correctly. If any of these checks fail, the simulation will \ exit immediately.", - ) - .arg(Arg::with_name("nodes") + ) + .arg( + Arg::with_name("nodes") .short("n") .long("nodes") .takes_value(true) .default_value("3") - .help("Number of beacon nodes")) - .arg(Arg::with_name("proposer-nodes") + .help("Number of beacon nodes"), + ) + .arg( + Arg::with_name("proposer-nodes") .short("p") .long("proposer-nodes") .takes_value(true) .default_value("3") - .help("Number of proposer-only beacon nodes")) - .arg(Arg::with_name("validators-per-node") + .help("Number of proposer-only 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") + .help("Number of validators"), + ) + .arg( + Arg::with_name("speed-up-factor") .short("s") .long("speed-up-factor") .takes_value(true) .default_value("3") - .help("Speed up factor. Please use a divisor of 12.")) - .arg(Arg::with_name("debug-level") + .help("Speed up factor. Please use a divisor of 12."), + ) + .arg( + Arg::with_name("debug-level") .short("d") .long("debug-level") .takes_value(true) .default_value("debug") - .help("Set the severity level of the logs.")) - .arg(Arg::with_name("continue-after-checks") + .help("Set the severity level of the logs."), + ) + .arg( + Arg::with_name("continue-after-checks") .short("c") .long("continue_after_checks") .takes_value(false) - .help("Continue after checks (default false)")) + .help("Continue after checks (default false)"), + ), ) .subcommand( - SubCommand::with_name("fallback-sim") - .about( - "Runs a Beacon Chain simulation with `c` validator clients where each VC is \ + SubCommand::with_name("fallback-sim") + .about( + "Runs a Beacon Chain simulation with `c` validator clients where each VC is \ connected to `b` beacon nodes with `v` validators. \ During the simulation, all but the last connected BN for each VC are \ disconnected from the execution layer, which causes the VC to fallback to the \ @@ -62,41 +74,53 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> { At the end of the simulation, there are checks made to ensure that all VCs \ efficiently performed this fallback, within a certain tolerance. \ Otherwise, the simulation will exit and an error will be reported.", - ) - .arg(Arg::with_name("vc-count") + ) + .arg( + Arg::with_name("vc-count") .short("c") .long("vc-count") .takes_value(true) .default_value("3") - .help("Number of validator clients.")) - .arg(Arg::with_name("bns-per-vc") + .help("Number of validator clients."), + ) + .arg( + Arg::with_name("bns-per-vc") .short("b") .long("bns-per-vc") .takes_value(true) .default_value("2") - .help("Number of beacon nodes per validator client.")) - .arg(Arg::with_name("validators-per-vc") + .help("Number of beacon nodes per validator client."), + ) + .arg( + Arg::with_name("validators-per-vc") .short("v") .long("validators-per-vc") .takes_value(true) .default_value("20") - .help("Number of validators per client.")) - .arg(Arg::with_name("speed-up-factor") + .help("Number of validators per client."), + ) + .arg( + Arg::with_name("speed-up-factor") .short("s") .long("speed-up-factor") .takes_value(true) .default_value("3") - .help("Speed up factor. Please use a divisor of 12.")) - .arg(Arg::with_name("debug-level") + .help("Speed up factor. Please use a divisor of 12."), + ) + .arg( + Arg::with_name("debug-level") .short("d") .long("debug-level") .takes_value(true) .default_value("debug") - .help("Set the severity level of the logs.")) - .arg(Arg::with_name("continue-after-checks") + .help("Set the severity level of the logs."), + ) + .arg( + Arg::with_name("continue-after-checks") .short("c") .long("continue_after_checks") .takes_value(false) - .help("Continue after checks (default false)")) + .help("Continue after checks (default false)"), + ), ) } From 169e9f498c0a5eaefa61dff97a3b822fb29b7b8c Mon Sep 17 00:00:00 2001 From: Mac L Date: Mon, 15 Apr 2024 20:34:43 +1000 Subject: [PATCH 07/10] Add sync sim to basic sim --- testing/simulator/src/basic_sim.rs | 37 ++++++++++++++++++++------ testing/simulator/src/checks.rs | 32 +++++++++++++++++++++- testing/simulator/src/fallback_sim.rs | 6 ++--- testing/simulator/src/local_network.rs | 21 +++++++++++++++ 4 files changed, 84 insertions(+), 12 deletions(-) diff --git a/testing/simulator/src/basic_sim.rs b/testing/simulator/src/basic_sim.rs index 9a2c6cff511..594130adbe8 100644 --- a/testing/simulator/src/basic_sim.rs +++ b/testing/simulator/src/basic_sim.rs @@ -99,6 +99,7 @@ pub fn run_basic_sim(matches: &ArgMatches) -> Result<(), String> { //spec.electra_fork_epoch = Some(Epoch::new(ELECTRA_FORK_EPOCH)); let slot_duration = Duration::from_secs(spec.seconds_per_slot); + let slots_per_epoch = MinimalEthSpec::slots_per_epoch(); let initial_validator_count = spec.min_genesis_active_validator_count as usize; let context = env.core_context(); @@ -193,6 +194,7 @@ pub fn run_basic_sim(matches: &ArgMatches) -> Result<(), String> { * tests start at the right time. Whilst this is works well for now, it's subject to * breakage by changes to the VC. */ + let network_1 = network.clone(); let ( finalization, @@ -204,13 +206,15 @@ pub fn run_basic_sim(matches: &ArgMatches) -> Result<(), String> { transition, light_client_update, blobs, + start_node_with_delay, + sync, ) = futures::join!( // Check that the chain finalizes at the first given opportunity. checks::verify_first_finalization(network.clone(), slot_duration), // Check that a block is produced at every slot. checks::verify_full_block_production_up_to( network.clone(), - Epoch::new(END_EPOCH).start_slot(MinimalEthSpec::slots_per_epoch()), + Epoch::new(END_EPOCH).start_slot(slots_per_epoch), slot_duration, ), // Check that the chain starts with the expected validator count. @@ -238,31 +242,46 @@ pub fn run_basic_sim(matches: &ArgMatches) -> Result<(), String> { network.clone(), // Start checking for sync_aggregates at `FORK_EPOCH + 1` to account for // inefficiencies in finding subnet peers at the `fork_slot`. - Epoch::new(ALTAIR_FORK_EPOCH + 1).start_slot(MinimalEthSpec::slots_per_epoch()), - Epoch::new(END_EPOCH).start_slot(MinimalEthSpec::slots_per_epoch()), + Epoch::new(ALTAIR_FORK_EPOCH + 1).start_slot(slots_per_epoch), + Epoch::new(END_EPOCH).start_slot(slots_per_epoch), slot_duration, ), // Check that the transition block is finalized. checks::verify_transition_block_finalized( network.clone(), - Epoch::new(TERMINAL_BLOCK / MinimalEthSpec::slots_per_epoch()), + Epoch::new(TERMINAL_BLOCK / slots_per_epoch), slot_duration, true, ), checks::verify_light_client_updates( network.clone(), // Sync aggregate available from slot 1 after Altair fork transition. - Epoch::new(ALTAIR_FORK_EPOCH).start_slot(MinimalEthSpec::slots_per_epoch()) + 1, - Epoch::new(END_EPOCH).start_slot(MinimalEthSpec::slots_per_epoch()), + Epoch::new(ALTAIR_FORK_EPOCH).start_slot(slots_per_epoch) + 1, + Epoch::new(END_EPOCH).start_slot(slots_per_epoch), slot_duration ), checks::verify_full_blob_production_up_to( network.clone(), // Blobs should be available from the first slot after the Deneb fork. - Epoch::new(DENEB_FORK_EPOCH + 1).start_slot(MinimalEthSpec::slots_per_epoch()) + 1, - Epoch::new(END_EPOCH).start_slot(MinimalEthSpec::slots_per_epoch()), + Epoch::new(DENEB_FORK_EPOCH + 1).start_slot(slots_per_epoch) + 1, + Epoch::new(END_EPOCH).start_slot(slots_per_epoch), slot_duration ), + network_1.add_beacon_node_with_delay( + beacon_config.clone(), + mock_execution_config.clone(), + END_EPOCH - 1, + slot_duration, + slots_per_epoch + ), + checks::ensure_node_synced_up_to_slot( + network.clone(), + // This must be set to be the node which was just created. Should be equal to + // `node_count`. + node_count, + Epoch::new(END_EPOCH).start_slot(slots_per_epoch), + slot_duration, + ), ); block_prod?; @@ -274,6 +293,8 @@ pub fn run_basic_sim(matches: &ArgMatches) -> Result<(), String> { transition?; light_client_update?; blobs?; + start_node_with_delay?; + sync?; // The `final_future` either completes immediately or never completes, depending on the value // of `continue_after_checks`. diff --git a/testing/simulator/src/checks.rs b/testing/simulator/src/checks.rs index 5f55820cb26..edc0046529e 100644 --- a/testing/simulator/src/checks.rs +++ b/testing/simulator/src/checks.rs @@ -334,6 +334,36 @@ pub(crate) async fn verify_light_client_updates( Ok(()) } +/// Checks that a node is synced with the network. +/// Useful for ensuring that a node which started after genesis is able to sync to the head. +pub async fn ensure_node_synced_up_to_slot( + network: LocalNetwork, + node_index: usize, + upto_slot: Slot, + slot_duration: Duration, +) -> Result<(), String> { + slot_delay(upto_slot, slot_duration).await; + let node = &network.remote_nodes()?.get(node_index).expect("Should get node").clone(); + + let head = node + .get_beacon_blocks::(BlockId::Head) + .await + .ok() + .flatten() + .ok_or(format!("No head block exists on node {node_index}"))? + .data; + + // Check the head block is synced with the rest of the network. + if head.slot() >= upto_slot { + Ok(()) + } else { + Err(format!( + "Head not synced for node {node_index}. Found {}; Should be {upto_slot}", + head.slot() + )) + } +} + /// Verifies that there's been a block produced at every slot up to and including `slot`. pub async fn verify_full_blob_production_up_to( network: LocalNetwork, @@ -353,7 +383,7 @@ pub async fn verify_full_blob_production_up_to( .ok() .flatten(); - // Only check blobs if the blob exist. If you also want to ensure full block production, use + // Only check blobs if the block exists. If you also want to ensure full block production, use // the `verify_full_block_production_up_to` function. if block.is_some() { remote_node diff --git a/testing/simulator/src/fallback_sim.rs b/testing/simulator/src/fallback_sim.rs index 909813eec17..4ae3f583589 100644 --- a/testing/simulator/src/fallback_sim.rs +++ b/testing/simulator/src/fallback_sim.rs @@ -28,7 +28,7 @@ const DENEB_FORK_EPOCH: u64 = 2; // This has potential to block CI so it should be set conservatively enough that spurious failures // don't become very common, but not so conservatively that regressions to the fallback mechanism // cannot be detected. -const ACCEPTABLE_FALLBACK_ATTESTATION_HIT_PERCENTAGE: f64 = 80.0; +const ACCEPTABLE_FALLBACK_ATTESTATION_HIT_PERCENTAGE: f64 = 85.0; const SUGGESTED_FEE_RECIPIENT: [u8; 20] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]; @@ -105,8 +105,8 @@ pub fn run_fallback_sim(matches: &ArgMatches) -> Result<(), String> { let slot_duration = Duration::from_secs(spec.seconds_per_slot); let slots_per_epoch = MinimalEthSpec::slots_per_epoch(); - let disconnection_epoch = 2; - let epochs_disconnected = 5; + let disconnection_epoch = 1; + let epochs_disconnected = 6; let context = env.core_context(); diff --git a/testing/simulator/src/local_network.rs b/testing/simulator/src/local_network.rs index cd8b26d846d..c3f83040f84 100644 --- a/testing/simulator/src/local_network.rs +++ b/testing/simulator/src/local_network.rs @@ -1,3 +1,4 @@ +use crate::checks::epoch_delay; use eth2_network_config::TRUSTED_SETUP_BYTES; use node_test_rig::{ environment::RuntimeContext, @@ -40,8 +41,10 @@ fn default_client_config(network_params: LocalNetworkParams, genesis_time: u64) network_params.node_count + network_params.proposer_nodes - 1; beacon_config.network.enr_address = (Some(Ipv4Addr::LOCALHOST), None); beacon_config.network.enable_light_client_server = true; + beacon_config.network.discv5_config.enable_packet_filter = false; beacon_config.chain.enable_light_client_server = true; beacon_config.http_api.enable_light_client_server = true; + beacon_config.chain.optimistic_finalized_sync = false; beacon_config.trusted_setup = serde_json::from_reader(TRUSTED_SETUP_BYTES).expect("Trusted setup bytes should be valid"); @@ -311,6 +314,24 @@ impl LocalNetwork { Ok(()) } + // Add a new node with a delay. This node will not have validators and is only used to test + // sync. + pub async fn add_beacon_node_with_delay( + &self, + beacon_config: ClientConfig, + mock_execution_config: MockExecutionConfig, + wait_until_epoch: u64, + slot_duration: Duration, + slots_per_epoch: u64, + ) -> Result<(), String> { + epoch_delay(Epoch::new(wait_until_epoch), slot_duration, slots_per_epoch).await; + + self.add_beacon_node(beacon_config, mock_execution_config, false) + .await?; + + Ok(()) + } + /// Adds a validator client to the network, connecting it to the beacon node with index /// `beacon_node`. pub async fn add_validator_client( From cf34dcf450e3026232d928bdf2f76ce88c6419c2 Mon Sep 17 00:00:00 2001 From: Mac L Date: Mon, 15 Apr 2024 20:49:11 +1000 Subject: [PATCH 08/10] Formatting --- testing/simulator/src/checks.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/testing/simulator/src/checks.rs b/testing/simulator/src/checks.rs index edc0046529e..5944138ab48 100644 --- a/testing/simulator/src/checks.rs +++ b/testing/simulator/src/checks.rs @@ -343,7 +343,11 @@ pub async fn ensure_node_synced_up_to_slot( slot_duration: Duration, ) -> Result<(), String> { slot_delay(upto_slot, slot_duration).await; - let node = &network.remote_nodes()?.get(node_index).expect("Should get node").clone(); + let node = &network + .remote_nodes()? + .get(node_index) + .expect("Should get node") + .clone(); let head = node .get_beacon_blocks::(BlockId::Head) From 3284c1e6359c05e075e4a62e7b3511277c363563 Mon Sep 17 00:00:00 2001 From: Mac L Date: Thu, 18 Apr 2024 16:06:23 +1000 Subject: [PATCH 09/10] Add fixes and new block production check --- testing/simulator/src/basic_sim.rs | 6 +++--- testing/simulator/src/checks.rs | 5 +++-- testing/simulator/src/fallback_sim.rs | 12 +++++++++--- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/testing/simulator/src/basic_sim.rs b/testing/simulator/src/basic_sim.rs index 594130adbe8..755bb71b430 100644 --- a/testing/simulator/src/basic_sim.rs +++ b/testing/simulator/src/basic_sim.rs @@ -15,7 +15,7 @@ use std::time::Duration; use tokio::time::sleep; use types::{Epoch, EthSpec, MinimalEthSpec}; -const END_EPOCH: u64 = 10; +const END_EPOCH: u64 = 16; const GENESIS_DELAY: u64 = 32; const ALTAIR_FORK_EPOCH: u64 = 0; const BELLATRIX_FORK_EPOCH: u64 = 0; @@ -262,8 +262,8 @@ pub fn run_basic_sim(matches: &ArgMatches) -> Result<(), String> { ), checks::verify_full_blob_production_up_to( network.clone(), - // Blobs should be available from the first slot after the Deneb fork. - Epoch::new(DENEB_FORK_EPOCH + 1).start_slot(slots_per_epoch) + 1, + // Blobs should be available immediately after the Deneb fork. + Epoch::new(DENEB_FORK_EPOCH).start_slot(slots_per_epoch), Epoch::new(END_EPOCH).start_slot(slots_per_epoch), slot_duration ), diff --git a/testing/simulator/src/checks.rs b/testing/simulator/src/checks.rs index 5944138ab48..03cc17fab3e 100644 --- a/testing/simulator/src/checks.rs +++ b/testing/simulator/src/checks.rs @@ -368,7 +368,8 @@ pub async fn ensure_node_synced_up_to_slot( } } -/// Verifies that there's been a block produced at every slot up to and including `slot`. +/// Verifies that there's been blobs produced at every slot with a block from `blob_start_slot` up +/// to and including `upto_slot`. pub async fn verify_full_blob_production_up_to( network: LocalNetwork, blob_start_slot: Slot, @@ -424,7 +425,7 @@ pub async fn reconnect_to_execution_layer( .server .all_payloads_valid(); - eprintln!("Re-enabling Execution Node {node_index}"); + eprintln!("Enabling Execution Node {node_index}"); Ok(()) } diff --git a/testing/simulator/src/fallback_sim.rs b/testing/simulator/src/fallback_sim.rs index 4ae3f583589..c9deeba04d9 100644 --- a/testing/simulator/src/fallback_sim.rs +++ b/testing/simulator/src/fallback_sim.rs @@ -14,7 +14,7 @@ use std::time::Duration; use tokio::time::sleep; use types::{Epoch, EthSpec, MinimalEthSpec}; -const END_EPOCH: u64 = 10; +const END_EPOCH: u64 = 16; const GENESIS_DELAY: u64 = 32; const ALTAIR_FORK_EPOCH: u64 = 0; const BELLATRIX_FORK_EPOCH: u64 = 0; @@ -106,7 +106,7 @@ pub fn run_fallback_sim(matches: &ArgMatches) -> Result<(), String> { let slots_per_epoch = MinimalEthSpec::slots_per_epoch(); let disconnection_epoch = 1; - let epochs_disconnected = 6; + let epochs_disconnected = 14; let context = env.core_context(); @@ -209,7 +209,7 @@ pub fn run_fallback_sim(matches: &ArgMatches) -> Result<(), String> { * breakage by changes to the VC. */ - let (sequence, check_attestations) = futures::join!( + let (sequence, check_attestations, block_production) = futures::join!( test_sequence, checks::check_attestation_correctness( network.clone(), @@ -220,8 +220,14 @@ pub fn run_fallback_sim(matches: &ArgMatches) -> Result<(), String> { node_count - 1, ACCEPTABLE_FALLBACK_ATTESTATION_HIT_PERCENTAGE, ), + checks::verify_full_block_production_up_to( + network.clone(), + Epoch::new(END_EPOCH).start_slot(slots_per_epoch), + slot_duration, + ), ); sequence?; + block_production?; check_attestations?; // The `final_future` either completes immediately or never completes, depending on the value From 42ef9f0887cf54d9f94d795bf688438d20a10bb5 Mon Sep 17 00:00:00 2001 From: realbigsean Date: Thu, 18 Apr 2024 14:28:54 -0400 Subject: [PATCH 10/10] fix compile --- testing/simulator/src/local_network.rs | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/testing/simulator/src/local_network.rs b/testing/simulator/src/local_network.rs index c3f83040f84..63f2ec93537 100644 --- a/testing/simulator/src/local_network.rs +++ b/testing/simulator/src/local_network.rs @@ -49,11 +49,9 @@ fn default_client_config(network_params: LocalNetworkParams, genesis_time: u64) serde_json::from_reader(TRUSTED_SETUP_BYTES).expect("Trusted setup bytes should be valid"); let el_config = execution_layer::Config { - execution_endpoints: vec![SensitiveUrl::parse(&format!( - "http://localhost:{}", - EXECUTION_PORT - )) - .unwrap()], + execution_endpoint: Some( + SensitiveUrl::parse(&format!("http://localhost:{}", EXECUTION_PORT)).unwrap(), + ), ..Default::default() }; beacon_config.execution_layer = Some(el_config); @@ -209,9 +207,9 @@ impl LocalNetwork { ); beacon_config.execution_layer = Some(execution_layer::Config { - execution_endpoints: vec![SensitiveUrl::parse(&execution_node.server.url()).unwrap()], + execution_endpoint: Some(SensitiveUrl::parse(&execution_node.server.url()).unwrap()), default_datadir: execution_node.datadir.path().to_path_buf(), - secret_files: vec![execution_node.datadir.path().join("jwt.hex")], + secret_file: Some(execution_node.datadir.path().join("jwt.hex")), ..Default::default() }); @@ -256,9 +254,9 @@ impl LocalNetwork { // Pair the beacon node and execution node. beacon_config.execution_layer = Some(execution_layer::Config { - execution_endpoints: vec![SensitiveUrl::parse(&execution_node.server.url()).unwrap()], + execution_endpoint: Some(SensitiveUrl::parse(&execution_node.server.url()).unwrap()), default_datadir: execution_node.datadir.path().to_path_buf(), - secret_files: vec![execution_node.datadir.path().join("jwt.hex")], + secret_file: Some(execution_node.datadir.path().join("jwt.hex")), ..Default::default() });