diff --git a/lcli/Cargo.toml b/lcli/Cargo.toml index edfbb436d3..4130c0f6af 100644 --- a/lcli/Cargo.toml +++ b/lcli/Cargo.toml @@ -23,6 +23,7 @@ types = { path = "../consensus/types" } state_processing = { path = "../consensus/state_processing" } int_to_bytes = { path = "../consensus/int_to_bytes" } eth2_ssz = "0.4.1" +eth2_hashing = "0.3.0" environment = { path = "../lighthouse/environment" } eth2_network_config = { path = "../common/eth2_network_config" } genesis = { path = "../beacon_node/genesis" } diff --git a/lcli/src/new_testnet.rs b/lcli/src/new_testnet.rs index 6935604572..e832fb08ee 100644 --- a/lcli/src/new_testnet.rs +++ b/lcli/src/new_testnet.rs @@ -1,16 +1,23 @@ use clap::ArgMatches; use clap_utils::{parse_optional, parse_required, parse_ssz_optional}; +use eth2_hashing::hash; use eth2_network_config::Eth2NetworkConfig; use genesis::interop_genesis_state; use ssz::Decode; use ssz::Encode; +use state_processing::process_activations; +use state_processing::upgrade::{ + upgrade_to_altair, upgrade_to_bellatrix, upgrade_to_capella, upgrade_to_eip4844, +}; use std::fs::File; use std::io::Read; use std::path::PathBuf; +use std::str::FromStr; use std::time::{SystemTime, UNIX_EPOCH}; use types::{ - test_utils::generate_deterministic_keypairs, Address, Config, EthSpec, ExecutionPayloadHeader, - ExecutionPayloadHeaderMerge, + test_utils::generate_deterministic_keypairs, Address, BeaconState, ChainSpec, Config, Eth1Data, + EthSpec, ExecutionPayloadHeader, ExecutionPayloadHeaderMerge, Hash256, Keypair, PublicKey, + Validator, }; pub fn run(testnet_dir_path: PathBuf, matches: &ArgMatches) -> Result<(), String> { @@ -67,6 +74,14 @@ pub fn run(testnet_dir_path: PathBuf, matches: &ArgMatches) -> Resul spec.bellatrix_fork_epoch = Some(fork_epoch); } + if let Some(fork_epoch) = parse_optional(matches, "capella-fork-epoch")? { + spec.capella_fork_epoch = Some(fork_epoch); + } + + if let Some(fork_epoch) = parse_optional(matches, "eip4844-fork-epoch")? { + spec.eip4844_fork_epoch = Some(fork_epoch); + } + let genesis_state_bytes = if matches.is_present("interop-genesis-state") { let execution_payload_header: Option> = parse_optional(matches, "execution-payload-header")? @@ -108,7 +123,7 @@ pub fn run(testnet_dir_path: PathBuf, matches: &ArgMatches) -> Resul let keypairs = generate_deterministic_keypairs(validator_count); - let genesis_state = interop_genesis_state::( + let genesis_state = initialize_state_with_validators::( &keypairs, genesis_time, eth1_block_hash.into_root(), @@ -130,3 +145,84 @@ pub fn run(testnet_dir_path: PathBuf, matches: &ArgMatches) -> Resul testnet.write_to_file(testnet_dir_path, overwrite_files) } + +fn initialize_state_with_validators( + keypairs: &[Keypair], + genesis_time: u64, + eth1_block_hash: Hash256, + execution_payload_header: Option>, + spec: &ChainSpec, +) -> Result, String> { + // Empty eth1 data + let eth1_data = Eth1Data { + block_hash: eth1_block_hash, + deposit_count: 0, + deposit_root: Hash256::from_str( + "0xd70a234731285c6804c2a4f56711ddb8c82c99740f207854891028af34e27e5e", + ) + .unwrap(), // empty deposit tree root + }; + let mut state = BeaconState::new(genesis_time, eth1_data, spec); + + // Seed RANDAO with Eth1 entropy + state.fill_randao_mixes_with(eth1_block_hash); + + for keypair in keypairs.into_iter() { + let withdrawal_credentials = |pubkey: &PublicKey| { + let mut credentials = hash(&pubkey.as_ssz_bytes()); + credentials[0] = spec.bls_withdrawal_prefix_byte; + Hash256::from_slice(&credentials) + }; + let amount = spec.max_effective_balance; + // Create a new validator. + let validator = Validator { + pubkey: keypair.pk.clone().into(), + withdrawal_credentials: withdrawal_credentials(&keypair.pk), + activation_eligibility_epoch: spec.far_future_epoch, + activation_epoch: spec.far_future_epoch, + exit_epoch: spec.far_future_epoch, + withdrawable_epoch: spec.far_future_epoch, + effective_balance: std::cmp::min( + amount - amount % (spec.effective_balance_increment), + spec.max_effective_balance, + ), + slashed: false, + }; + state.validators_mut().push(validator).unwrap(); + state.balances_mut().push(amount).unwrap(); + } + + process_activations(&mut state, spec).unwrap(); + + if spec + .altair_fork_epoch + .map_or(false, |fork_epoch| fork_epoch == T::genesis_epoch()) + { + upgrade_to_altair(&mut state, spec).unwrap(); + + state.fork_mut().previous_version = spec.altair_fork_version; + } + + // Similarly, perform an upgrade to the merge if configured from genesis. + if spec + .bellatrix_fork_epoch + .map_or(false, |fork_epoch| fork_epoch == T::genesis_epoch()) + { + upgrade_to_bellatrix(&mut state, spec).unwrap(); + + // Remove intermediate Altair fork from `state.fork`. + state.fork_mut().previous_version = spec.bellatrix_fork_version; + + // Override latest execution payload header. + // See https://github.com/ethereum/consensus-specs/blob/v1.1.0/specs/merge/beacon-chain.md#testing + *state.latest_execution_payload_header_mut() = execution_payload_header.unwrap_or_default(); + } + + // Now that we have our validators, initialize the caches (including the committees) + state.build_all_caches(spec).unwrap(); + + // Set genesis validators root for domain separation and chain versioning + *state.genesis_validators_root_mut() = state.update_validators_tree_hash_cache().unwrap(); + + Ok(state) +}