From b162b067de4740033448b798af786159db35ab33 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sun, 3 Oct 2021 06:57:23 +1100 Subject: [PATCH] Misc changes for merge testnets (#2667) * Thread eth1_block_hash into interop genesis state * Add merge-fork-epoch flag * Build LH with minimal spec by default * Add verbose logs to execution_layer * Add --http-allow-sync-stalled flag * Update lcli new-testnet to create genesis state * Fix http test * Fix compile errors in tests --- Makefile | 4 +- beacon_node/beacon_chain/src/builder.rs | 14 +++- beacon_node/beacon_chain/src/test_utils.rs | 4 +- beacon_node/client/src/builder.rs | 12 ++- beacon_node/execution_layer/src/lib.rs | 55 +++++++++++++- beacon_node/genesis/src/interop.rs | 14 +++- beacon_node/genesis/src/lib.rs | 2 +- beacon_node/http_api/src/lib.rs | 74 ++++++++++--------- beacon_node/http_api/tests/common.rs | 1 + .../network/src/subnet_service/tests/mod.rs | 15 ++-- beacon_node/src/cli.rs | 7 ++ beacon_node/src/config.rs | 4 + consensus/types/src/beacon_state/tests.rs | 9 ++- lcli/src/interop_genesis.rs | 11 ++- lcli/src/main.rs | 38 ++++++++++ lcli/src/new_testnet.rs | 32 +++++++- 16 files changed, 236 insertions(+), 60 deletions(-) diff --git a/Makefile b/Makefile index 6856635eb..bf4a5a015 100644 --- a/Makefile +++ b/Makefile @@ -23,9 +23,9 @@ FORKS=phase0 altair # Binaries will most likely be found in `./target/release` install: ifeq ($(PORTABLE), true) - cargo install --path lighthouse --force --locked --features portable + cargo install --path lighthouse --force --locked --features portable,spec-minimal else - cargo install --path lighthouse --force --locked + cargo install --path lighthouse --force --locked --features spec-minimal endif # Builds the lcli binary in release (optimized). diff --git a/beacon_node/beacon_chain/src/builder.rs b/beacon_node/beacon_chain/src/builder.rs index ab0cf50c3..fb2fc6c1f 100644 --- a/beacon_node/beacon_chain/src/builder.rs +++ b/beacon_node/beacon_chain/src/builder.rs @@ -919,7 +919,9 @@ fn descriptive_db_error(item: &str, error: &StoreError) -> String { mod test { use super::*; use eth2_hashing::hash; - use genesis::{generate_deterministic_keypairs, interop_genesis_state}; + use genesis::{ + generate_deterministic_keypairs, interop_genesis_state, DEFAULT_ETH1_BLOCK_HASH, + }; use sloggers::{null::NullLoggerBuilder, Build}; use ssz::Encode; use std::time::Duration; @@ -951,6 +953,7 @@ mod test { let genesis_state = interop_genesis_state( &generate_deterministic_keypairs(validator_count), genesis_time, + Hash256::from_slice(DEFAULT_ETH1_BLOCK_HASH), &spec, ) .expect("should create interop genesis state"); @@ -1016,8 +1019,13 @@ mod test { let keypairs = generate_deterministic_keypairs(validator_count); - let state = interop_genesis_state::(&keypairs, genesis_time, spec) - .expect("should build state"); + let state = interop_genesis_state::( + &keypairs, + genesis_time, + Hash256::from_slice(DEFAULT_ETH1_BLOCK_HASH), + spec, + ) + .expect("should build state"); assert_eq!( state.eth1_data().block_hash, diff --git a/beacon_node/beacon_chain/src/test_utils.rs b/beacon_node/beacon_chain/src/test_utils.rs index ac34ecf86..ed5fc127c 100644 --- a/beacon_node/beacon_chain/src/test_utils.rs +++ b/beacon_node/beacon_chain/src/test_utils.rs @@ -18,7 +18,7 @@ use execution_layer::{ ExecutionLayer, }; use futures::channel::mpsc::Receiver; -pub use genesis::interop_genesis_state; +pub use genesis::{interop_genesis_state, DEFAULT_ETH1_BLOCK_HASH}; use int_to_bytes::int_to_bytes32; use logging::test_logger; use merkle_proof::MerkleTree; @@ -181,6 +181,7 @@ impl Builder> { let genesis_state = interop_genesis_state::( &validator_keypairs, HARNESS_GENESIS_TIME, + Hash256::from_slice(DEFAULT_ETH1_BLOCK_HASH), builder.get_spec(), ) .expect("should generate interop state"); @@ -226,6 +227,7 @@ impl Builder> { let genesis_state = interop_genesis_state::( &validator_keypairs, HARNESS_GENESIS_TIME, + Hash256::from_slice(DEFAULT_ETH1_BLOCK_HASH), builder.get_spec(), ) .expect("should generate interop state"); diff --git a/beacon_node/client/src/builder.rs b/beacon_node/client/src/builder.rs index a535b4612..2bb1fbe6a 100644 --- a/beacon_node/client/src/builder.rs +++ b/beacon_node/client/src/builder.rs @@ -17,7 +17,7 @@ use eth2::{ BeaconNodeHttpClient, Error as ApiError, Timeouts, }; use execution_layer::ExecutionLayer; -use genesis::{interop_genesis_state, Eth1GenesisService}; +use genesis::{interop_genesis_state, Eth1GenesisService, DEFAULT_ETH1_BLOCK_HASH}; use lighthouse_network::NetworkGlobals; use monitoring_api::{MonitoringHttpClient, ProcessType}; use network::{NetworkConfig, NetworkMessage, NetworkService}; @@ -31,7 +31,8 @@ use std::time::Duration; use timer::spawn_timer; use tokio::sync::{mpsc::UnboundedSender, oneshot}; use types::{ - test_utils::generate_deterministic_keypairs, BeaconState, ChainSpec, EthSpec, SignedBeaconBlock, + test_utils::generate_deterministic_keypairs, BeaconState, ChainSpec, EthSpec, Hash256, + SignedBeaconBlock, }; /// Interval between polling the eth1 node for genesis information. @@ -229,7 +230,12 @@ where genesis_time, } => { let keypairs = generate_deterministic_keypairs(validator_count); - let genesis_state = interop_genesis_state(&keypairs, genesis_time, &spec)?; + let genesis_state = interop_genesis_state( + &keypairs, + genesis_time, + Hash256::from_slice(DEFAULT_ETH1_BLOCK_HASH), + &spec, + )?; builder.genesis_state(genesis_state).map(|v| (v, None))? } ClientGenesis::SszBytes { diff --git a/beacon_node/execution_layer/src/lib.rs b/beacon_node/execution_layer/src/lib.rs index bba43dca5..ad76be882 100644 --- a/beacon_node/execution_layer/src/lib.rs +++ b/beacon_node/execution_layer/src/lib.rs @@ -8,7 +8,7 @@ use engine_api::{Error as ApiError, *}; use engines::{Engine, EngineError, Engines}; use lru::LruCache; use sensitive_url::SensitiveUrl; -use slog::{crit, Logger}; +use slog::{crit, info, Logger}; use std::future::Future; use std::sync::Arc; use task_executor::TaskExecutor; @@ -177,6 +177,14 @@ impl ExecutionLayer { random: Hash256, ) -> Result { let fee_recipient = self.fee_recipient()?; + info!( + self.log(), + "Issuing engine_preparePayload"; + "fee_recipient" => ?fee_recipient, + "random" => ?random, + "timestamp" => timestamp, + "parent_hash" => ?parent_hash, + ); self.engines() .first_success(|engine| { // TODO(merge): make a cache for these IDs, so we don't always have to perform this @@ -205,6 +213,14 @@ impl ExecutionLayer { random: Hash256, ) -> Result, Error> { let fee_recipient = self.fee_recipient()?; + info!( + self.log(), + "Issuing engine_getPayload"; + "fee_recipient" => ?fee_recipient, + "random" => ?random, + "timestamp" => timestamp, + "parent_hash" => ?parent_hash, + ); self.engines() .first_success(|engine| async move { // TODO(merge): make a cache for these IDs, so we don't always have to perform this @@ -236,6 +252,14 @@ impl ExecutionLayer { &self, execution_payload: &ExecutionPayload, ) -> Result<(ExecutePayloadResponse, ExecutePayloadHandle), Error> { + info!( + self.log(), + "Issuing engine_executePayload"; + "parent_hash" => ?execution_payload.parent_hash, + "block_hash" => ?execution_payload.block_hash, + "block_number" => execution_payload.block_number, + ); + let broadcast_results = self .engines() .broadcast(|engine| engine.api.execute_payload(execution_payload.clone())) @@ -296,6 +320,12 @@ impl ExecutionLayer { block_hash: Hash256, status: ConsensusStatus, ) -> Result<(), Error> { + info!( + self.log(), + "Issuing engine_consensusValidated"; + "status" => ?status, + "block_hash" => ?block_hash, + ); let broadcast_results = self .engines() .broadcast(|engine| engine.api.consensus_validated(block_hash, status)) @@ -328,6 +358,12 @@ impl ExecutionLayer { head_block_hash: Hash256, finalized_block_hash: Hash256, ) -> Result<(), Error> { + info!( + self.log(), + "Issuing engine_forkchoiceUpdated"; + "finalized_block_hash" => ?finalized_block_hash, + "head_block_hash" => ?head_block_hash, + ); let broadcast_results = self .engines() .broadcast(|engine| { @@ -357,7 +393,8 @@ impl ExecutionLayer { /// /// https://github.com/ethereum/consensus-specs/blob/v1.1.0/specs/merge/validator.md pub async fn get_terminal_pow_block_hash(&self) -> Result, Error> { - self.engines() + let hash_opt = self + .engines() .first_success(|engine| async move { if self.terminal_block_hash() != Hash256::zero() { // Note: the specification is written such that if there are multiple blocks in @@ -376,7 +413,19 @@ impl ExecutionLayer { } }) .await - .map_err(Error::EngineErrors) + .map_err(Error::EngineErrors)?; + + if let Some(hash) = &hash_opt { + info!( + self.log(), + "Found terminal block hash"; + "terminal_block_hash_override" => ?self.terminal_block_hash(), + "terminal_total_difficulty" => ?self.terminal_total_difficulty(), + "block_hash" => ?hash, + ); + } + + Ok(hash_opt) } /// This function should remain internal. External users should use diff --git a/beacon_node/genesis/src/interop.rs b/beacon_node/genesis/src/interop.rs index e36c115b4..42b7dd516 100644 --- a/beacon_node/genesis/src/interop.rs +++ b/beacon_node/genesis/src/interop.rs @@ -5,6 +5,8 @@ use ssz::Encode; use state_processing::initialize_beacon_state_from_eth1; use types::{BeaconState, ChainSpec, DepositData, EthSpec, Hash256, Keypair, PublicKey, Signature}; +pub const DEFAULT_ETH1_BLOCK_HASH: &[u8] = &[0x42; 32]; + /// Builds a genesis state as defined by the Eth2 interop procedure (see below). /// /// Reference: @@ -12,9 +14,10 @@ use types::{BeaconState, ChainSpec, DepositData, EthSpec, Hash256, Keypair, Publ pub fn interop_genesis_state( keypairs: &[Keypair], genesis_time: u64, + eth1_block_hash: Hash256, spec: &ChainSpec, ) -> Result, String> { - let eth1_block_hash = Hash256::from_slice(&[0x42; 32]); + let eth1_block_hash = eth1_block_hash; let eth1_timestamp = 2_u64.pow(40); let amount = spec.max_effective_balance; @@ -73,8 +76,13 @@ mod test { let keypairs = generate_deterministic_keypairs(validator_count); - let state = interop_genesis_state::(&keypairs, genesis_time, spec) - .expect("should build state"); + let state = interop_genesis_state::( + &keypairs, + genesis_time, + Hash256::from_slice(DEFAULT_ETH1_BLOCK_HASH), + spec, + ) + .expect("should build state"); assert_eq!( state.eth1_data().block_hash, diff --git a/beacon_node/genesis/src/lib.rs b/beacon_node/genesis/src/lib.rs index 5390e30d9..ccf8fe10c 100644 --- a/beacon_node/genesis/src/lib.rs +++ b/beacon_node/genesis/src/lib.rs @@ -4,5 +4,5 @@ mod interop; pub use eth1::Config as Eth1Config; pub use eth1_genesis_service::{Eth1GenesisService, Statistics}; -pub use interop::interop_genesis_state; +pub use interop::{interop_genesis_state, DEFAULT_ETH1_BLOCK_HASH}; pub use types::test_utils::generate_deterministic_keypairs; diff --git a/beacon_node/http_api/src/lib.rs b/beacon_node/http_api/src/lib.rs index 25f051ac1..35a22afc4 100644 --- a/beacon_node/http_api/src/lib.rs +++ b/beacon_node/http_api/src/lib.rs @@ -97,6 +97,7 @@ pub struct Config { pub allow_origin: Option, pub serve_legacy_spec: bool, pub tls_config: Option, + pub allow_sync_stalled: bool, } impl Default for Config { @@ -108,6 +109,7 @@ impl Default for Config { allow_origin: None, serve_legacy_spec: true, tls_config: None, + allow_sync_stalled: false, } } } @@ -237,6 +239,7 @@ pub fn serve( shutdown: impl Future + Send + Sync + 'static, ) -> Result { let config = ctx.config.clone(); + let allow_sync_stalled = config.allow_sync_stalled; let log = ctx.log.clone(); // Configure CORS. @@ -338,44 +341,49 @@ pub fn serve( }); // Create a `warp` filter that rejects request whilst the node is syncing. - let not_while_syncing_filter = warp::any() - .and(network_globals.clone()) - .and(chain_filter.clone()) - .and_then( - |network_globals: Arc>, chain: Arc>| async move { - match *network_globals.sync_state.read() { - SyncState::SyncingFinalized { .. } => { - let head_slot = chain.best_slot().map_err(warp_utils::reject::beacon_chain_error)?; + let not_while_syncing_filter = + warp::any() + .and(network_globals.clone()) + .and(chain_filter.clone()) + .and_then( + move |network_globals: Arc>, + chain: Arc>| async move { + match *network_globals.sync_state.read() { + SyncState::SyncingFinalized { .. } => { + let head_slot = chain + .best_slot() + .map_err(warp_utils::reject::beacon_chain_error)?; - let current_slot = chain - .slot_clock - .now_or_genesis() - .ok_or_else(|| { - warp_utils::reject::custom_server_error( - "unable to read slot clock".to_string(), - ) - })?; + let current_slot = + chain.slot_clock.now_or_genesis().ok_or_else(|| { + warp_utils::reject::custom_server_error( + "unable to read slot clock".to_string(), + ) + })?; - let tolerance = SYNC_TOLERANCE_EPOCHS * T::EthSpec::slots_per_epoch(); + let tolerance = SYNC_TOLERANCE_EPOCHS * T::EthSpec::slots_per_epoch(); - if head_slot + tolerance >= current_slot { - Ok(()) - } else { - Err(warp_utils::reject::not_synced(format!( - "head slot is {}, current slot is {}", - head_slot, current_slot - ))) + if head_slot + tolerance >= current_slot { + Ok(()) + } else { + Err(warp_utils::reject::not_synced(format!( + "head slot is {}, current slot is {}", + head_slot, current_slot + ))) + } } + SyncState::SyncingHead { .. } + | SyncState::SyncTransition + | SyncState::BackFillSyncing { .. } => Ok(()), + SyncState::Synced => Ok(()), + SyncState::Stalled if allow_sync_stalled => Ok(()), + SyncState::Stalled => Err(warp_utils::reject::not_synced( + "sync is stalled".to_string(), + )), } - SyncState::SyncingHead { .. } | SyncState::SyncTransition | SyncState::BackFillSyncing { .. } => Ok(()), - SyncState::Synced => Ok(()), - SyncState::Stalled => Err(warp_utils::reject::not_synced( - "sync is stalled".to_string(), - )), - } - }, - ) - .untuple_one(); + }, + ) + .untuple_one(); // Create a `warp` filter that provides access to the logger. let inner_ctx = ctx.clone(); diff --git a/beacon_node/http_api/tests/common.rs b/beacon_node/http_api/tests/common.rs index dd2a40efa..758c29a60 100644 --- a/beacon_node/http_api/tests/common.rs +++ b/beacon_node/http_api/tests/common.rs @@ -133,6 +133,7 @@ pub async fn create_api_server( allow_origin: None, serve_legacy_spec: true, tls_config: None, + allow_sync_stalled: false, }, chain: Some(chain.clone()), network_tx: Some(network_tx), diff --git a/beacon_node/network/src/subnet_service/tests/mod.rs b/beacon_node/network/src/subnet_service/tests/mod.rs index da0c1fc8c..2cc4b5872 100644 --- a/beacon_node/network/src/subnet_service/tests/mod.rs +++ b/beacon_node/network/src/subnet_service/tests/mod.rs @@ -5,7 +5,7 @@ use beacon_chain::{ BeaconChain, }; use futures::prelude::*; -use genesis::{generate_deterministic_keypairs, interop_genesis_state}; +use genesis::{generate_deterministic_keypairs, interop_genesis_state, DEFAULT_ETH1_BLOCK_HASH}; use lazy_static::lazy_static; use lighthouse_network::NetworkConfig; use slog::Logger; @@ -16,8 +16,8 @@ use std::time::{Duration, SystemTime}; use store::config::StoreConfig; use store::{HotColdDB, MemoryStore}; use types::{ - CommitteeIndex, Epoch, EthSpec, MainnetEthSpec, Slot, SubnetId, SyncCommitteeSubscription, - SyncSubnetId, ValidatorSubscription, + CommitteeIndex, Epoch, EthSpec, Hash256, MainnetEthSpec, Slot, SubnetId, + SyncCommitteeSubscription, SyncSubnetId, ValidatorSubscription, }; const SLOT_DURATION_MILLIS: u64 = 400; @@ -52,8 +52,13 @@ impl TestBeaconChain { .custom_spec(spec.clone()) .store(Arc::new(store)) .genesis_state( - interop_genesis_state::(&keypairs, 0, &spec) - .expect("should generate interop state"), + interop_genesis_state::( + &keypairs, + 0, + Hash256::from_slice(DEFAULT_ETH1_BLOCK_HASH), + &spec, + ) + .expect("should generate interop state"), ) .expect("should build state using recent genesis") .dummy_eth1_backend() diff --git a/beacon_node/src/cli.rs b/beacon_node/src/cli.rs index 8aadfbc11..1a8e9ef5b 100644 --- a/beacon_node/src/cli.rs +++ b/beacon_node/src/cli.rs @@ -240,6 +240,13 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> { over TLS. Must not be password-protected.") .takes_value(true) ) + .arg( + Arg::with_name("http-allow-sync-stalled") + .long("http-allow-sync-stalled") + .help("Forces the HTTP to indicate that the node is synced when sync is actually \ + stalled. This is useful for very small testnets. TESTING ONLY. DO NOT USE ON \ + MAINNET.") + ) /* Prometheus metrics HTTP server related arguments */ .arg( Arg::with_name("metrics") diff --git a/beacon_node/src/config.rs b/beacon_node/src/config.rs index f613c5fb1..9061da574 100644 --- a/beacon_node/src/config.rs +++ b/beacon_node/src/config.rs @@ -131,6 +131,10 @@ pub fn get_config( }); } + if cli_args.is_present("http-allow-sync-stalled") { + client_config.http_api.allow_sync_stalled = true; + } + /* * Prometheus metrics HTTP server */ diff --git a/consensus/types/src/beacon_state/tests.rs b/consensus/types/src/beacon_state/tests.rs index d8b6c796c..ffe04969c 100644 --- a/consensus/types/src/beacon_state/tests.rs +++ b/consensus/types/src/beacon_state/tests.rs @@ -3,6 +3,7 @@ use crate::test_utils::*; use crate::test_utils::{SeedableRng, XorShiftRng}; use beacon_chain::test_utils::{ interop_genesis_state, test_spec, BeaconChainHarness, EphemeralHarnessType, + DEFAULT_ETH1_BLOCK_HASH, }; use beacon_chain::types::{ test_utils::TestRandom, BeaconState, BeaconStateAltair, BeaconStateBase, BeaconStateError, @@ -557,7 +558,13 @@ fn tree_hash_cache_linear_history_long_skip() { let spec = &test_spec::(); // This state has a cache that advances normally each slot. - let mut state: BeaconState = interop_genesis_state(&keypairs, 0, spec).unwrap(); + let mut state: BeaconState = interop_genesis_state( + &keypairs, + 0, + Hash256::from_slice(DEFAULT_ETH1_BLOCK_HASH), + spec, + ) + .unwrap(); state.update_tree_hash_cache().unwrap(); diff --git a/lcli/src/interop_genesis.rs b/lcli/src/interop_genesis.rs index 6f35699fc..20e221fb9 100644 --- a/lcli/src/interop_genesis.rs +++ b/lcli/src/interop_genesis.rs @@ -1,11 +1,11 @@ use clap::ArgMatches; use clap_utils::parse_ssz_optional; use eth2_network_config::Eth2NetworkConfig; -use genesis::interop_genesis_state; +use genesis::{interop_genesis_state, DEFAULT_ETH1_BLOCK_HASH}; use ssz::Encode; use std::path::PathBuf; use std::time::{SystemTime, UNIX_EPOCH}; -use types::{test_utils::generate_deterministic_keypairs, EthSpec}; +use types::{test_utils::generate_deterministic_keypairs, EthSpec, Hash256}; pub fn run(testnet_dir: PathBuf, matches: &ArgMatches) -> Result<(), String> { let validator_count = matches @@ -34,7 +34,12 @@ pub fn run(testnet_dir: PathBuf, matches: &ArgMatches) -> Result<(), } let keypairs = generate_deterministic_keypairs(validator_count); - let genesis_state = interop_genesis_state::(&keypairs, genesis_time, &spec)?; + let genesis_state = interop_genesis_state::( + &keypairs, + genesis_time, + Hash256::from_slice(DEFAULT_ETH1_BLOCK_HASH), + &spec, + )?; eth2_network_config.genesis_state_bytes = Some(genesis_state.as_ssz_bytes()); eth2_network_config.force_write_to_file(testnet_dir)?; diff --git a/lcli/src/main.rs b/lcli/src/main.rs index e9ce219cf..f463fdaac 100644 --- a/lcli/src/main.rs +++ b/lcli/src/main.rs @@ -284,6 +284,14 @@ fn main() { .takes_value(false) .help("Overwrites any previous testnet configurations"), ) + .arg( + Arg::with_name("interop-genesis-state") + .long("interop-genesis-state") + .takes_value(false) + .help( + "If present, a interop-style genesis.ssz file will be generated.", + ), + ) .arg( Arg::with_name("min-genesis-time") .long("min-genesis-time") @@ -402,6 +410,36 @@ fn main() { "The epoch at which to enable the Altair hard fork", ), ) + .arg( + Arg::with_name("merge-fork-epoch") + .long("merge-fork-epoch") + .value_name("EPOCH") + .takes_value(true) + .help( + "The epoch at which to enable the Merge hard fork", + ), + ) + .arg( + Arg::with_name("eth1-block-hash") + .long("eth1-block-hash") + .value_name("BLOCK_HASH") + .takes_value(true) + .help("The eth1 block hash used when generating a genesis state."), + ) + .arg( + Arg::with_name("validator-count") + .long("validator-count") + .value_name("INTEGER") + .takes_value(true) + .help("The number of validators when generating a genesis state."), + ) + .arg( + Arg::with_name("genesis-time") + .long("genesis-time") + .value_name("INTEGER") + .takes_value(true) + .help("The genesis time when generating a genesis state."), + ) ) .subcommand( SubCommand::with_name("check-deposit-data") diff --git a/lcli/src/new_testnet.rs b/lcli/src/new_testnet.rs index e37145bf0..8cea19d05 100644 --- a/lcli/src/new_testnet.rs +++ b/lcli/src/new_testnet.rs @@ -1,8 +1,11 @@ use clap::ArgMatches; use clap_utils::{parse_optional, parse_required, parse_ssz_optional}; use eth2_network_config::Eth2NetworkConfig; +use genesis::interop_genesis_state; +use ssz::Encode; use std::path::PathBuf; -use types::{Address, Config, EthSpec}; +use std::time::{SystemTime, UNIX_EPOCH}; +use types::{test_utils::generate_deterministic_keypairs, Address, Config, EthSpec}; pub fn run(testnet_dir_path: PathBuf, matches: &ArgMatches) -> Result<(), String> { let deposit_contract_address: Address = parse_required(matches, "deposit-contract-address")?; @@ -54,10 +57,35 @@ pub fn run(testnet_dir_path: PathBuf, matches: &ArgMatches) -> Resul spec.altair_fork_epoch = Some(fork_epoch); } + if let Some(fork_epoch) = parse_optional(matches, "merge-fork-epoch")? { + spec.merge_fork_epoch = Some(fork_epoch); + } + + let genesis_state_bytes = if matches.is_present("interop-genesis-state") { + let eth1_block_hash = parse_required(matches, "eth1-block-hash")?; + let validator_count = parse_required(matches, "validator-count")?; + let genesis_time = if let Some(time) = parse_optional(matches, "genesis-time")? { + time + } else { + SystemTime::now() + .duration_since(UNIX_EPOCH) + .map_err(|e| format!("Unable to get time: {:?}", e))? + .as_secs() + }; + + let keypairs = generate_deterministic_keypairs(validator_count); + let genesis_state = + interop_genesis_state::(&keypairs, genesis_time, eth1_block_hash, &spec)?; + + Some(genesis_state.as_ssz_bytes()) + } else { + None + }; + let testnet = Eth2NetworkConfig { deposit_contract_deploy_block, boot_enr: Some(vec![]), - genesis_state_bytes: None, + genesis_state_bytes, config: Config::from_chain_spec::(&spec), };