mirror of
https://github.com/sigp/lighthouse.git
synced 2026-03-06 10:11:44 +00:00
Replace local testnet script with Kurtosis (#5865)
* Kurtosis local testnet.
* Remove unused `lcli` subcommands
* Migrate doppelganger_protection test to kurtosis and further cleanup.
* Fix lint
* Add missing download image step and remove unused `lcli` dependencies.
* doppelganger success case working
* Run tests on hosted runner and improve error handling.
* Start the dp vc only after epoch 1
* Add more logging to test results.
* Fix exit code and speed up docker build.
* Fix incorrect exit codes and split doppelganger tests on CI.
* Missing the escape for double quotes 😫
* Remove unnecessary vc params in kurtosis config.
This commit is contained in:
@@ -24,17 +24,14 @@ ethereum_hashing = { workspace = true }
|
||||
ethereum_ssz = { workspace = true }
|
||||
environment = { workspace = true }
|
||||
eth2_network_config = { workspace = true }
|
||||
genesis = { workspace = true }
|
||||
deposit_contract = { workspace = true }
|
||||
tree_hash = { workspace = true }
|
||||
clap_utils = { workspace = true }
|
||||
lighthouse_network = { workspace = true }
|
||||
validator_dir = { workspace = true, features = ["insecure_keys"] }
|
||||
validator_dir = { workspace = true }
|
||||
lighthouse_version = { workspace = true }
|
||||
account_utils = { workspace = true }
|
||||
eth2_wallet = { workspace = true }
|
||||
eth1_test_rig = { workspace = true }
|
||||
sensitive_url = { workspace = true }
|
||||
eth2 = { workspace = true }
|
||||
snap = { workspace = true }
|
||||
beacon_chain = { workspace = true }
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
use clap::ArgMatches;
|
||||
use eth2_network_config::Eth2NetworkConfig;
|
||||
use ssz::Encode;
|
||||
use std::fs::File;
|
||||
use std::io::{Read, Write};
|
||||
use std::path::PathBuf;
|
||||
use types::{BeaconState, EthSpec};
|
||||
|
||||
pub fn run<E: EthSpec>(testnet_dir: PathBuf, matches: &ArgMatches) -> Result<(), String> {
|
||||
let path = matches
|
||||
.get_one::<String>("ssz-state")
|
||||
.ok_or("ssz-state not specified")?
|
||||
.parse::<PathBuf>()
|
||||
.map_err(|e| format!("Unable to parse ssz-state: {}", e))?;
|
||||
|
||||
let genesis_time = matches
|
||||
.get_one::<String>("genesis-time")
|
||||
.ok_or("genesis-time not specified")?
|
||||
.parse::<u64>()
|
||||
.map_err(|e| format!("Unable to parse genesis-time: {}", e))?;
|
||||
|
||||
let eth2_network_config = Eth2NetworkConfig::load(testnet_dir)?;
|
||||
let spec = ð2_network_config.chain_spec::<E>()?;
|
||||
|
||||
let mut state: BeaconState<E> = {
|
||||
let mut file = File::open(&path).map_err(|e| format!("Unable to open file: {}", e))?;
|
||||
|
||||
let mut ssz = vec![];
|
||||
|
||||
file.read_to_end(&mut ssz)
|
||||
.map_err(|e| format!("Unable to read file: {}", e))?;
|
||||
|
||||
BeaconState::from_ssz_bytes(&ssz, spec)
|
||||
.map_err(|e| format!("Unable to decode SSZ: {:?}", e))?
|
||||
};
|
||||
|
||||
*state.genesis_time_mut() = genesis_time;
|
||||
|
||||
let mut file = File::create(path).map_err(|e| format!("Unable to create file: {}", e))?;
|
||||
|
||||
file.write_all(&state.as_ssz_bytes())
|
||||
.map_err(|e| format!("Unable to write to file: {}", e))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
use clap::ArgMatches;
|
||||
use clap_utils::{parse_optional, parse_required};
|
||||
use ssz::Encode;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
use types::{
|
||||
EthSpec, ExecutionPayloadHeader, ExecutionPayloadHeaderBellatrix,
|
||||
ExecutionPayloadHeaderCapella, ExecutionPayloadHeaderDeneb, ExecutionPayloadHeaderElectra,
|
||||
ForkName,
|
||||
};
|
||||
|
||||
pub fn run<E: EthSpec>(matches: &ArgMatches) -> Result<(), String> {
|
||||
let eth1_block_hash = parse_required(matches, "execution-block-hash")?;
|
||||
let genesis_time = parse_optional(matches, "genesis-time")?.unwrap_or(
|
||||
SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.map_err(|e| format!("Unable to get time: {:?}", e))?
|
||||
.as_secs(),
|
||||
);
|
||||
let base_fee_per_gas = parse_required(matches, "base-fee-per-gas")?;
|
||||
let gas_limit = parse_required(matches, "gas-limit")?;
|
||||
let file_name = matches
|
||||
.get_one::<String>("file")
|
||||
.ok_or("No file supplied")?;
|
||||
let fork_name: ForkName = parse_optional(matches, "fork")?.unwrap_or(ForkName::Bellatrix);
|
||||
|
||||
let execution_payload_header: ExecutionPayloadHeader<E> = match fork_name {
|
||||
ForkName::Base | ForkName::Altair => return Err("invalid fork name".to_string()),
|
||||
ForkName::Bellatrix => ExecutionPayloadHeader::Bellatrix(ExecutionPayloadHeaderBellatrix {
|
||||
gas_limit,
|
||||
base_fee_per_gas,
|
||||
timestamp: genesis_time,
|
||||
block_hash: eth1_block_hash,
|
||||
prev_randao: eth1_block_hash.into_root(),
|
||||
..ExecutionPayloadHeaderBellatrix::default()
|
||||
}),
|
||||
ForkName::Capella => ExecutionPayloadHeader::Capella(ExecutionPayloadHeaderCapella {
|
||||
gas_limit,
|
||||
base_fee_per_gas,
|
||||
timestamp: genesis_time,
|
||||
block_hash: eth1_block_hash,
|
||||
prev_randao: eth1_block_hash.into_root(),
|
||||
..ExecutionPayloadHeaderCapella::default()
|
||||
}),
|
||||
ForkName::Deneb => ExecutionPayloadHeader::Deneb(ExecutionPayloadHeaderDeneb {
|
||||
gas_limit,
|
||||
base_fee_per_gas,
|
||||
timestamp: genesis_time,
|
||||
block_hash: eth1_block_hash,
|
||||
prev_randao: eth1_block_hash.into_root(),
|
||||
..ExecutionPayloadHeaderDeneb::default()
|
||||
}),
|
||||
ForkName::Electra => ExecutionPayloadHeader::Electra(ExecutionPayloadHeaderElectra {
|
||||
gas_limit,
|
||||
base_fee_per_gas,
|
||||
timestamp: genesis_time,
|
||||
block_hash: eth1_block_hash,
|
||||
prev_randao: eth1_block_hash.into_root(),
|
||||
..ExecutionPayloadHeaderElectra::default()
|
||||
}),
|
||||
};
|
||||
|
||||
let mut file = File::create(file_name).map_err(|_| "Unable to create file".to_string())?;
|
||||
let bytes = execution_payload_header.as_ssz_bytes();
|
||||
file.write_all(bytes.as_slice())
|
||||
.map_err(|_| "Unable to write to file".to_string())?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
use clap::ArgMatches;
|
||||
use environment::Environment;
|
||||
use types::EthSpec;
|
||||
|
||||
use eth1_test_rig::{Http, Provider};
|
||||
|
||||
pub fn run<E: EthSpec>(env: Environment<E>, matches: &ArgMatches) -> Result<(), String> {
|
||||
let eth1_http: String = clap_utils::parse_required(matches, "eth1-http")?;
|
||||
let confirmations: usize = clap_utils::parse_required(matches, "confirmations")?;
|
||||
let validator_count: Option<usize> = clap_utils::parse_optional(matches, "validator-count")?;
|
||||
|
||||
let client = Provider::<Http>::try_from(ð1_http)
|
||||
.map_err(|e| format!("Unable to connect to eth1 HTTP: {:?}", e))?;
|
||||
|
||||
env.runtime().block_on(async {
|
||||
let contract = eth1_test_rig::DepositContract::deploy(client, confirmations, None)
|
||||
.await
|
||||
.map_err(|e| format!("Failed to deploy deposit contract: {:?}", e))?;
|
||||
|
||||
println!("Deposit contract address: {:?}", contract.address());
|
||||
|
||||
// Deposit insecure validators to the deposit contract created
|
||||
if let Some(validator_count) = validator_count {
|
||||
let amount = env.eth2_config.spec.max_effective_balance;
|
||||
for i in 0..validator_count {
|
||||
println!("Submitting deposit for validator {}...", i);
|
||||
contract.deposit_deterministic_async::<E>(i, amount).await?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
use clap::ArgMatches;
|
||||
use environment::Environment;
|
||||
use eth2_network_config::Eth2NetworkConfig;
|
||||
use genesis::{Eth1Config, Eth1Endpoint, Eth1GenesisService};
|
||||
use sensitive_url::SensitiveUrl;
|
||||
use ssz::Encode;
|
||||
use std::cmp::max;
|
||||
use std::path::PathBuf;
|
||||
use std::time::Duration;
|
||||
use types::EthSpec;
|
||||
|
||||
/// Interval between polling the eth1 node for genesis information.
|
||||
pub const ETH1_GENESIS_UPDATE_INTERVAL: Duration = Duration::from_millis(7_000);
|
||||
|
||||
pub fn run<E: EthSpec>(
|
||||
env: Environment<E>,
|
||||
testnet_dir: PathBuf,
|
||||
matches: &ArgMatches,
|
||||
) -> Result<(), String> {
|
||||
let endpoints = matches
|
||||
.get_one::<String>("eth1-endpoint")
|
||||
.map(|e| {
|
||||
warn!("The --eth1-endpoint flag is deprecated. Please use --eth1-endpoints instead");
|
||||
String::from(e)
|
||||
})
|
||||
.or_else(|| {
|
||||
matches
|
||||
.get_one::<String>("eth1-endpoints")
|
||||
.map(String::from)
|
||||
});
|
||||
|
||||
let mut eth2_network_config = Eth2NetworkConfig::load(testnet_dir.clone())?;
|
||||
|
||||
let spec = eth2_network_config.chain_spec::<E>()?;
|
||||
|
||||
let mut config = Eth1Config::default();
|
||||
if let Some(v) = endpoints.clone() {
|
||||
let endpoint = SensitiveUrl::parse(&v)
|
||||
.map_err(|e| format!("Unable to parse eth1 endpoint URL: {:?}", e))?;
|
||||
config.endpoint = Eth1Endpoint::NoAuth(endpoint);
|
||||
}
|
||||
config.deposit_contract_address = format!("{:?}", spec.deposit_contract_address);
|
||||
config.deposit_contract_deploy_block = eth2_network_config.deposit_contract_deploy_block;
|
||||
config.lowest_cached_block_number = eth2_network_config.deposit_contract_deploy_block;
|
||||
config.follow_distance = spec.eth1_follow_distance / 2;
|
||||
config.node_far_behind_seconds = max(5, config.follow_distance) * spec.seconds_per_eth1_block;
|
||||
|
||||
let genesis_service =
|
||||
Eth1GenesisService::new(config, env.core_context().log().clone(), spec.clone())?;
|
||||
|
||||
env.runtime().block_on(async {
|
||||
let _ = genesis_service
|
||||
.wait_for_genesis_state::<E>(ETH1_GENESIS_UPDATE_INTERVAL, spec)
|
||||
.await
|
||||
.map(move |genesis_state| {
|
||||
eth2_network_config.genesis_state_bytes = Some(genesis_state.as_ssz_bytes().into());
|
||||
eth2_network_config.force_write_to_file(testnet_dir)
|
||||
})
|
||||
.map_err(|e| format!("Failed to find genesis: {}", e))?;
|
||||
|
||||
info!("Starting service to produce genesis BeaconState from eth1");
|
||||
info!("Connecting to eth1 http endpoints: {:?}", endpoints);
|
||||
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
use clap::ArgMatches;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use validator_dir::Builder as ValidatorBuilder;
|
||||
|
||||
/// Generates validator directories with INSECURE, deterministic keypairs given the range
|
||||
/// of indices, validator and secret directories.
|
||||
pub fn generate_validator_dirs(
|
||||
indices: &[usize],
|
||||
validators_dir: PathBuf,
|
||||
secrets_dir: PathBuf,
|
||||
) -> Result<(), String> {
|
||||
if !validators_dir.exists() {
|
||||
fs::create_dir_all(&validators_dir)
|
||||
.map_err(|e| format!("Unable to create validators dir: {:?}", e))?;
|
||||
}
|
||||
|
||||
if !secrets_dir.exists() {
|
||||
fs::create_dir_all(&secrets_dir)
|
||||
.map_err(|e| format!("Unable to create secrets dir: {:?}", e))?;
|
||||
}
|
||||
|
||||
for i in indices {
|
||||
println!("Validator {}", i + 1);
|
||||
|
||||
ValidatorBuilder::new(validators_dir.clone())
|
||||
.password_dir(secrets_dir.clone())
|
||||
.store_withdrawal_keystore(false)
|
||||
.insecure_voting_keypair(*i)
|
||||
.map_err(|e| format!("Unable to generate keys: {:?}", e))?
|
||||
.build()
|
||||
.map_err(|e| format!("Unable to build validator: {:?}", e))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn run(matches: &ArgMatches) -> Result<(), String> {
|
||||
let validator_count: usize = clap_utils::parse_required(matches, "count")?;
|
||||
let base_dir: PathBuf = clap_utils::parse_required(matches, "base-dir")?;
|
||||
let node_count: Option<usize> = clap_utils::parse_optional(matches, "node-count")?;
|
||||
if let Some(node_count) = node_count {
|
||||
let validators_per_node = validator_count / node_count;
|
||||
let validator_range = (0..validator_count).collect::<Vec<_>>();
|
||||
let indices_range = validator_range
|
||||
.chunks(validators_per_node)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for (i, indices) in indices_range.iter().enumerate() {
|
||||
let validators_dir = base_dir.join(format!("node_{}", i + 1)).join("validators");
|
||||
let secrets_dir = base_dir.join(format!("node_{}", i + 1)).join("secrets");
|
||||
generate_validator_dirs(indices, validators_dir, secrets_dir)?;
|
||||
}
|
||||
} else {
|
||||
let validators_dir = base_dir.join("validators");
|
||||
let secrets_dir = base_dir.join("secrets");
|
||||
generate_validator_dirs(
|
||||
(0..validator_count).collect::<Vec<_>>().as_slice(),
|
||||
validators_dir,
|
||||
secrets_dir,
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
use clap::ArgMatches;
|
||||
use clap_utils::parse_ssz_optional;
|
||||
use eth2_network_config::Eth2NetworkConfig;
|
||||
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, Hash256};
|
||||
|
||||
pub fn run<E: EthSpec>(testnet_dir: PathBuf, matches: &ArgMatches) -> Result<(), String> {
|
||||
let validator_count = matches
|
||||
.get_one::<String>("validator-count")
|
||||
.ok_or("validator-count not specified")?
|
||||
.parse::<usize>()
|
||||
.map_err(|e| format!("Unable to parse validator-count: {}", e))?;
|
||||
|
||||
let genesis_time = if let Some(genesis_time) = matches.get_one::<String>("genesis-time") {
|
||||
genesis_time
|
||||
.parse::<u64>()
|
||||
.map_err(|e| format!("Unable to parse genesis-time: {}", e))?
|
||||
} else {
|
||||
SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.map_err(|e| format!("Unable to get time: {:?}", e))?
|
||||
.as_secs()
|
||||
};
|
||||
|
||||
let mut eth2_network_config = Eth2NetworkConfig::load(testnet_dir.clone())?;
|
||||
|
||||
let mut spec = eth2_network_config.chain_spec::<E>()?;
|
||||
|
||||
if let Some(v) = parse_ssz_optional(matches, "genesis-fork-version")? {
|
||||
spec.genesis_fork_version = v;
|
||||
}
|
||||
|
||||
let keypairs = generate_deterministic_keypairs(validator_count);
|
||||
let genesis_state = interop_genesis_state::<E>(
|
||||
&keypairs,
|
||||
genesis_time,
|
||||
Hash256::from_slice(DEFAULT_ETH1_BLOCK_HASH),
|
||||
None,
|
||||
&spec,
|
||||
)?;
|
||||
|
||||
eth2_network_config.genesis_state_bytes = Some(genesis_state.as_ssz_bytes().into());
|
||||
eth2_network_config.force_write_to_file(testnet_dir)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
557
lcli/src/main.rs
557
lcli/src/main.rs
@@ -1,20 +1,12 @@
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
mod block_root;
|
||||
mod change_genesis_time;
|
||||
mod check_deposit_data;
|
||||
mod create_payload_header;
|
||||
mod deploy_deposit_contract;
|
||||
mod eth1_genesis;
|
||||
mod generate_bootnode_enr;
|
||||
mod indexed_attestations;
|
||||
mod insecure_validators;
|
||||
mod interop_genesis;
|
||||
mod mnemonic_validators;
|
||||
mod mock_el;
|
||||
mod new_testnet;
|
||||
mod parse_ssz;
|
||||
mod replace_state_pubkeys;
|
||||
mod skip_slots;
|
||||
mod state_root;
|
||||
mod transition_blocks;
|
||||
@@ -272,489 +264,6 @@ fn main() {
|
||||
.display_order(0)
|
||||
)
|
||||
)
|
||||
.subcommand(
|
||||
Command::new("deploy-deposit-contract")
|
||||
.about(
|
||||
"Deploy a testing eth1 deposit contract.",
|
||||
)
|
||||
.arg(
|
||||
Arg::new("eth1-http")
|
||||
.long("eth1-http")
|
||||
.short('e')
|
||||
.value_name("ETH1_HTTP_PATH")
|
||||
.help("Path to an Eth1 JSON-RPC IPC endpoint")
|
||||
.action(ArgAction::Set)
|
||||
.required(true)
|
||||
.display_order(0)
|
||||
)
|
||||
.arg(
|
||||
Arg::new("confirmations")
|
||||
.value_name("INTEGER")
|
||||
.long("confirmations")
|
||||
.action(ArgAction::Set)
|
||||
.default_value("3")
|
||||
.help("The number of block confirmations before declaring the contract deployed.")
|
||||
.display_order(0)
|
||||
)
|
||||
.arg(
|
||||
Arg::new("validator-count")
|
||||
.value_name("VALIDATOR_COUNT")
|
||||
.long("validator-count")
|
||||
.action(ArgAction::Set)
|
||||
.help("If present, makes `validator_count` number of INSECURE deterministic deposits after \
|
||||
deploying the deposit contract."
|
||||
)
|
||||
.display_order(0)
|
||||
)
|
||||
)
|
||||
.subcommand(
|
||||
Command::new("eth1-genesis")
|
||||
.about("Listens to the eth1 chain and finds the genesis beacon state")
|
||||
.arg(
|
||||
Arg::new("eth1-endpoint")
|
||||
.short('e')
|
||||
.long("eth1-endpoint")
|
||||
.value_name("HTTP_SERVER")
|
||||
.action(ArgAction::Set)
|
||||
.help("Deprecated. Use --eth1-endpoints.")
|
||||
.display_order(0)
|
||||
)
|
||||
.arg(
|
||||
Arg::new("eth1-endpoints")
|
||||
.long("eth1-endpoints")
|
||||
.value_name("HTTP_SERVER_LIST")
|
||||
.action(ArgAction::Set)
|
||||
.conflicts_with("eth1-endpoint")
|
||||
.help(
|
||||
"One or more comma-delimited URLs to eth1 JSON-RPC http APIs. \
|
||||
If multiple endpoints are given the endpoints are used as \
|
||||
fallback in the given order.",
|
||||
)
|
||||
.display_order(0)
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
Command::new("interop-genesis")
|
||||
.about("Produces an interop-compatible genesis state using deterministic keypairs")
|
||||
.arg(
|
||||
Arg::new("validator-count")
|
||||
.long("validator-count")
|
||||
.index(1)
|
||||
.value_name("INTEGER")
|
||||
.action(ArgAction::Set)
|
||||
.default_value("1024")
|
||||
.help("The number of validators in the genesis state.")
|
||||
.display_order(0)
|
||||
)
|
||||
.arg(
|
||||
Arg::new("genesis-time")
|
||||
.long("genesis-time")
|
||||
.short('t')
|
||||
.value_name("UNIX_EPOCH")
|
||||
.action(ArgAction::Set)
|
||||
.help("The value for state.genesis_time. Defaults to now.")
|
||||
.display_order(0)
|
||||
)
|
||||
.arg(
|
||||
Arg::new("genesis-fork-version")
|
||||
.long("genesis-fork-version")
|
||||
.value_name("HEX")
|
||||
.action(ArgAction::Set)
|
||||
.help(
|
||||
"Used to avoid reply attacks between testnets. Recommended to set to
|
||||
non-default.",
|
||||
)
|
||||
.display_order(0)
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
Command::new("change-genesis-time")
|
||||
.about(
|
||||
"Loads a file with an SSZ-encoded BeaconState and modifies the genesis time.",
|
||||
)
|
||||
.arg(
|
||||
Arg::new("ssz-state")
|
||||
.index(1)
|
||||
.value_name("PATH")
|
||||
.action(ArgAction::Set)
|
||||
.required(true)
|
||||
.help("The path to the SSZ file")
|
||||
.display_order(0)
|
||||
)
|
||||
.arg(
|
||||
Arg::new("genesis-time")
|
||||
.index(2)
|
||||
.value_name("UNIX_EPOCH")
|
||||
.action(ArgAction::Set)
|
||||
.required(true)
|
||||
.help("The value for state.genesis_time.")
|
||||
.display_order(0)
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
Command::new("replace-state-pubkeys")
|
||||
.about(
|
||||
"Loads a file with an SSZ-encoded BeaconState and replaces \
|
||||
all the validator pubkeys with ones derived from the mnemonic \
|
||||
such that validator indices correspond to EIP-2334 voting keypair \
|
||||
derivation paths.",
|
||||
)
|
||||
.arg(
|
||||
Arg::new("ssz-state")
|
||||
.index(1)
|
||||
.value_name("PATH")
|
||||
.action(ArgAction::Set)
|
||||
.required(true)
|
||||
.help("The path to the SSZ file")
|
||||
.display_order(0)
|
||||
)
|
||||
.arg(
|
||||
Arg::new("mnemonic")
|
||||
.index(2)
|
||||
.value_name("BIP39_MNENMONIC")
|
||||
.action(ArgAction::Set)
|
||||
.default_value(
|
||||
"replace nephew blur decorate waste convince soup column \
|
||||
orient excite play baby",
|
||||
)
|
||||
.help("The mnemonic for key derivation.")
|
||||
.display_order(0)
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
Command::new("create-payload-header")
|
||||
.about("Generates an SSZ file containing bytes for an `ExecutionPayloadHeader`. \
|
||||
Useful as input for `lcli new-testnet --execution-payload-header FILE`. If `--fork` \
|
||||
is not provided, a payload header for the `Bellatrix` fork will be created.")
|
||||
.arg(
|
||||
Arg::new("execution-block-hash")
|
||||
.long("execution-block-hash")
|
||||
.value_name("BLOCK_HASH")
|
||||
.action(ArgAction::Set)
|
||||
.help("The block hash used when generating an execution payload. This \
|
||||
value is used for `execution_payload_header.block_hash` as well as \
|
||||
`execution_payload_header.random`")
|
||||
.default_value(
|
||||
"0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
)
|
||||
.display_order(0)
|
||||
)
|
||||
.arg(
|
||||
Arg::new("genesis-time")
|
||||
.long("genesis-time")
|
||||
.value_name("INTEGER")
|
||||
.action(ArgAction::Set)
|
||||
.help("The genesis time when generating an execution payload.")
|
||||
.display_order(0)
|
||||
)
|
||||
.arg(
|
||||
Arg::new("base-fee-per-gas")
|
||||
.long("base-fee-per-gas")
|
||||
.value_name("INTEGER")
|
||||
.action(ArgAction::Set)
|
||||
.help("The base fee per gas field in the execution payload generated.")
|
||||
.default_value("1000000000")
|
||||
.display_order(0)
|
||||
)
|
||||
.arg(
|
||||
Arg::new("gas-limit")
|
||||
.long("gas-limit")
|
||||
.value_name("INTEGER")
|
||||
.action(ArgAction::Set)
|
||||
.help("The gas limit field in the execution payload generated.")
|
||||
.default_value("30000000")
|
||||
.display_order(0)
|
||||
)
|
||||
.arg(
|
||||
Arg::new("file")
|
||||
.long("file")
|
||||
.value_name("FILE")
|
||||
.action(ArgAction::Set)
|
||||
.required(true)
|
||||
.help("Output file")
|
||||
.display_order(0)
|
||||
).arg(
|
||||
Arg::new("fork")
|
||||
.long("fork")
|
||||
.value_name("FORK")
|
||||
.action(ArgAction::Set)
|
||||
.default_value("bellatrix")
|
||||
.help("The fork for which the execution payload header should be created.")
|
||||
.value_parser(["bellatrix", "capella", "deneb", "electra"])
|
||||
.display_order(0)
|
||||
)
|
||||
)
|
||||
.subcommand(
|
||||
Command::new("new-testnet")
|
||||
.about(
|
||||
"Produce a new testnet directory. If any of the optional flags are not
|
||||
supplied the values will remain the default for the --spec flag",
|
||||
)
|
||||
.arg(
|
||||
Arg::new("force")
|
||||
.long("force")
|
||||
.short('f')
|
||||
.action(ArgAction::SetTrue)
|
||||
.help_heading(FLAG_HEADER)
|
||||
.help("Overwrites any previous testnet configurations")
|
||||
.display_order(0)
|
||||
)
|
||||
.arg(
|
||||
Arg::new("interop-genesis-state")
|
||||
.long("interop-genesis-state")
|
||||
.action(ArgAction::SetTrue)
|
||||
.help_heading(FLAG_HEADER)
|
||||
.help(
|
||||
"If present, a interop-style genesis.ssz file will be generated.",
|
||||
)
|
||||
.display_order(0)
|
||||
)
|
||||
.arg(
|
||||
Arg::new("derived-genesis-state")
|
||||
.long("derived-genesis-state")
|
||||
.action(ArgAction::SetTrue)
|
||||
.help_heading(FLAG_HEADER)
|
||||
.help(
|
||||
"If present, a genesis.ssz file will be generated with keys generated from a given mnemonic.",
|
||||
)
|
||||
.display_order(0)
|
||||
)
|
||||
.arg(
|
||||
Arg::new("mnemonic-phrase")
|
||||
.long("mnemonic-phrase")
|
||||
.value_name("MNEMONIC_PHRASE")
|
||||
.action(ArgAction::Set)
|
||||
.requires("derived-genesis-state")
|
||||
.help("The mnemonic with which we generate the validator keys for a derived genesis state")
|
||||
.display_order(0)
|
||||
)
|
||||
.arg(
|
||||
Arg::new("min-genesis-time")
|
||||
.long("min-genesis-time")
|
||||
.value_name("UNIX_SECONDS")
|
||||
.action(ArgAction::Set)
|
||||
.help(
|
||||
"The minimum permitted genesis time. For non-eth1 testnets will be
|
||||
the genesis time. Defaults to now.",
|
||||
)
|
||||
.display_order(0)
|
||||
)
|
||||
.arg(
|
||||
Arg::new("min-genesis-active-validator-count")
|
||||
.long("min-genesis-active-validator-count")
|
||||
.value_name("INTEGER")
|
||||
.action(ArgAction::Set)
|
||||
.help("The number of validators required to trigger eth2 genesis.")
|
||||
.display_order(0)
|
||||
)
|
||||
.arg(
|
||||
Arg::new("genesis-delay")
|
||||
.long("genesis-delay")
|
||||
.value_name("SECONDS")
|
||||
.action(ArgAction::Set)
|
||||
.help("The delay between sufficient eth1 deposits and eth2 genesis.")
|
||||
.display_order(0)
|
||||
)
|
||||
.arg(
|
||||
Arg::new("min-deposit-amount")
|
||||
.long("min-deposit-amount")
|
||||
.value_name("GWEI")
|
||||
.action(ArgAction::Set)
|
||||
.help("The minimum permitted deposit amount.")
|
||||
.display_order(0)
|
||||
)
|
||||
.arg(
|
||||
Arg::new("max-effective-balance")
|
||||
.long("max-effective-balance")
|
||||
.value_name("GWEI")
|
||||
.action(ArgAction::Set)
|
||||
.help("The amount required to become a validator.")
|
||||
.display_order(0)
|
||||
)
|
||||
.arg(
|
||||
Arg::new("effective-balance-increment")
|
||||
.long("effective-balance-increment")
|
||||
.value_name("GWEI")
|
||||
.action(ArgAction::Set)
|
||||
.help("The steps in effective balance calculation.")
|
||||
.display_order(0)
|
||||
)
|
||||
.arg(
|
||||
Arg::new("ejection-balance")
|
||||
.long("ejection-balance")
|
||||
.value_name("GWEI")
|
||||
.action(ArgAction::Set)
|
||||
.help("The balance at which a validator gets ejected.")
|
||||
.display_order(0)
|
||||
)
|
||||
.arg(
|
||||
Arg::new("eth1-follow-distance")
|
||||
.long("eth1-follow-distance")
|
||||
.value_name("ETH1_BLOCKS")
|
||||
.action(ArgAction::Set)
|
||||
.help("The distance to follow behind the eth1 chain head.")
|
||||
.display_order(0)
|
||||
)
|
||||
.arg(
|
||||
Arg::new("genesis-fork-version")
|
||||
.long("genesis-fork-version")
|
||||
.value_name("HEX")
|
||||
.action(ArgAction::Set)
|
||||
.help(
|
||||
"Used to avoid reply attacks between testnets. Recommended to set to
|
||||
non-default.",
|
||||
)
|
||||
.display_order(0)
|
||||
)
|
||||
.arg(
|
||||
Arg::new("seconds-per-slot")
|
||||
.long("seconds-per-slot")
|
||||
.value_name("SECONDS")
|
||||
.action(ArgAction::Set)
|
||||
.help("Eth2 slot time")
|
||||
.display_order(0)
|
||||
)
|
||||
.arg(
|
||||
Arg::new("seconds-per-eth1-block")
|
||||
.long("seconds-per-eth1-block")
|
||||
.value_name("SECONDS")
|
||||
.action(ArgAction::Set)
|
||||
.help("Eth1 block time")
|
||||
.display_order(0)
|
||||
)
|
||||
.arg(
|
||||
Arg::new("eth1-id")
|
||||
.long("eth1-id")
|
||||
.value_name("ETH1_ID")
|
||||
.action(ArgAction::Set)
|
||||
.help("The chain id and network id for the eth1 testnet.")
|
||||
.display_order(0)
|
||||
)
|
||||
.arg(
|
||||
Arg::new("deposit-contract-address")
|
||||
.long("deposit-contract-address")
|
||||
.value_name("ETH1_ADDRESS")
|
||||
.action(ArgAction::Set)
|
||||
.required(true)
|
||||
.help("The address of the deposit contract.")
|
||||
.display_order(0)
|
||||
)
|
||||
.arg(
|
||||
Arg::new("deposit-contract-deploy-block")
|
||||
.long("deposit-contract-deploy-block")
|
||||
.value_name("ETH1_BLOCK_NUMBER")
|
||||
.action(ArgAction::Set)
|
||||
.default_value("0")
|
||||
.help(
|
||||
"The block the deposit contract was deployed. Setting this is a huge
|
||||
optimization for nodes, please do it.",
|
||||
)
|
||||
.display_order(0)
|
||||
)
|
||||
.arg(
|
||||
Arg::new("altair-fork-epoch")
|
||||
.long("altair-fork-epoch")
|
||||
.value_name("EPOCH")
|
||||
.action(ArgAction::Set)
|
||||
.help(
|
||||
"The epoch at which to enable the Altair hard fork",
|
||||
)
|
||||
.display_order(0)
|
||||
)
|
||||
.arg(
|
||||
Arg::new("bellatrix-fork-epoch")
|
||||
.long("bellatrix-fork-epoch")
|
||||
.value_name("EPOCH")
|
||||
.action(ArgAction::Set)
|
||||
.help(
|
||||
"The epoch at which to enable the Bellatrix hard fork",
|
||||
)
|
||||
.display_order(0)
|
||||
)
|
||||
.arg(
|
||||
Arg::new("capella-fork-epoch")
|
||||
.long("capella-fork-epoch")
|
||||
.value_name("EPOCH")
|
||||
.action(ArgAction::Set)
|
||||
.help(
|
||||
"The epoch at which to enable the Capella hard fork",
|
||||
)
|
||||
.display_order(0)
|
||||
)
|
||||
.arg(
|
||||
Arg::new("deneb-fork-epoch")
|
||||
.long("deneb-fork-epoch")
|
||||
.value_name("EPOCH")
|
||||
.action(ArgAction::Set)
|
||||
.help(
|
||||
"The epoch at which to enable the Deneb hard fork",
|
||||
)
|
||||
.display_order(0)
|
||||
)
|
||||
.arg(
|
||||
Arg::new("electra-fork-epoch")
|
||||
.long("electra-fork-epoch")
|
||||
.value_name("EPOCH")
|
||||
.action(ArgAction::Set)
|
||||
.help(
|
||||
"The epoch at which to enable the Electra hard fork",
|
||||
)
|
||||
.display_order(0)
|
||||
)
|
||||
.arg(
|
||||
Arg::new("ttd")
|
||||
.long("ttd")
|
||||
.value_name("TTD")
|
||||
.action(ArgAction::Set)
|
||||
.help(
|
||||
"The terminal total difficulty",
|
||||
)
|
||||
.display_order(0)
|
||||
)
|
||||
.arg(
|
||||
Arg::new("eth1-block-hash")
|
||||
.long("eth1-block-hash")
|
||||
.value_name("BLOCK_HASH")
|
||||
.action(ArgAction::Set)
|
||||
.help("The eth1 block hash used when generating a genesis state.")
|
||||
.display_order(0)
|
||||
)
|
||||
.arg(
|
||||
Arg::new("execution-payload-header")
|
||||
.long("execution-payload-header")
|
||||
.value_name("FILE")
|
||||
.action(ArgAction::Set)
|
||||
.required(false)
|
||||
.help("Path to file containing `ExecutionPayloadHeader` SSZ bytes to be \
|
||||
used in the genesis state.")
|
||||
.display_order(0)
|
||||
)
|
||||
.arg(
|
||||
Arg::new("validator-count")
|
||||
.long("validator-count")
|
||||
.value_name("INTEGER")
|
||||
.action(ArgAction::Set)
|
||||
.help("The number of validators when generating a genesis state.")
|
||||
.display_order(0)
|
||||
)
|
||||
.arg(
|
||||
Arg::new("genesis-time")
|
||||
.long("genesis-time")
|
||||
.value_name("INTEGER")
|
||||
.action(ArgAction::Set)
|
||||
.help("The genesis time when generating a genesis state.")
|
||||
.display_order(0)
|
||||
)
|
||||
.arg(
|
||||
Arg::new("proposer-score-boost")
|
||||
.long("proposer-score-boost")
|
||||
.value_name("INTEGER")
|
||||
.action(ArgAction::Set)
|
||||
.help("The proposer score boost to apply as a percentage, e.g. 70 = 70%")
|
||||
.display_order(0)
|
||||
)
|
||||
|
||||
)
|
||||
.subcommand(
|
||||
Command::new("check-deposit-data")
|
||||
.about("Checks the integrity of some deposit data.")
|
||||
@@ -834,36 +343,6 @@ fn main() {
|
||||
.display_order(0)
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
Command::new("insecure-validators")
|
||||
.about("Produces validator directories with INSECURE, deterministic keypairs.")
|
||||
.arg(
|
||||
Arg::new("count")
|
||||
.long("count")
|
||||
.value_name("COUNT")
|
||||
.action(ArgAction::Set)
|
||||
.required(true)
|
||||
.help("Produces validators in the range of 0..count.")
|
||||
.display_order(0)
|
||||
)
|
||||
.arg(
|
||||
Arg::new("base-dir")
|
||||
.long("base-dir")
|
||||
.value_name("BASE_DIR")
|
||||
.action(ArgAction::Set)
|
||||
.required(true)
|
||||
.help("The base directory where validator keypairs and secrets are stored")
|
||||
.display_order(0)
|
||||
)
|
||||
.arg(
|
||||
Arg::new("node-count")
|
||||
.long("node-count")
|
||||
.value_name("NODE_COUNT")
|
||||
.action(ArgAction::Set)
|
||||
.help("The number of nodes to divide the validator keys to")
|
||||
.display_order(0)
|
||||
)
|
||||
)
|
||||
.subcommand(
|
||||
Command::new("mnemonic-validators")
|
||||
.about("Produces validator directories by deriving the keys from \
|
||||
@@ -1128,9 +607,6 @@ fn run<E: EthSpec>(env_builder: EnvironmentBuilder<E>, matches: &ArgMatches) ->
|
||||
(None, Some(network_name))
|
||||
};
|
||||
|
||||
// Lazily load either the testnet dir or the network config, as required.
|
||||
// Some subcommands like new-testnet need the testnet dir but not the network config.
|
||||
let get_testnet_dir = || testnet_dir.clone().ok_or("testnet-dir is required");
|
||||
let get_network_config = || {
|
||||
if let Some(testnet_dir) = &testnet_dir {
|
||||
Eth2NetworkConfig::load(testnet_dir.clone()).map_err(|e| {
|
||||
@@ -1162,43 +638,10 @@ fn run<E: EthSpec>(env_builder: EnvironmentBuilder<E>, matches: &ArgMatches) ->
|
||||
run_parse_ssz::<E>(network_config, matches)
|
||||
.map_err(|e| format!("Failed to pretty print hex: {}", e))
|
||||
}
|
||||
Some(("deploy-deposit-contract", matches)) => {
|
||||
deploy_deposit_contract::run::<E>(env, matches)
|
||||
.map_err(|e| format!("Failed to run deploy-deposit-contract command: {}", e))
|
||||
}
|
||||
Some(("eth1-genesis", matches)) => {
|
||||
let testnet_dir = get_testnet_dir()?;
|
||||
eth1_genesis::run::<E>(env, testnet_dir, matches)
|
||||
.map_err(|e| format!("Failed to run eth1-genesis command: {}", e))
|
||||
}
|
||||
Some(("interop-genesis", matches)) => {
|
||||
let testnet_dir = get_testnet_dir()?;
|
||||
interop_genesis::run::<E>(testnet_dir, matches)
|
||||
.map_err(|e| format!("Failed to run interop-genesis command: {}", e))
|
||||
}
|
||||
Some(("change-genesis-time", matches)) => {
|
||||
let testnet_dir = get_testnet_dir()?;
|
||||
change_genesis_time::run::<E>(testnet_dir, matches)
|
||||
.map_err(|e| format!("Failed to run change-genesis-time command: {}", e))
|
||||
}
|
||||
Some(("create-payload-header", matches)) => create_payload_header::run::<E>(matches)
|
||||
.map_err(|e| format!("Failed to run create-payload-header command: {}", e)),
|
||||
Some(("replace-state-pubkeys", matches)) => {
|
||||
let testnet_dir = get_testnet_dir()?;
|
||||
replace_state_pubkeys::run::<E>(testnet_dir, matches)
|
||||
.map_err(|e| format!("Failed to run replace-state-pubkeys command: {}", e))
|
||||
}
|
||||
Some(("new-testnet", matches)) => {
|
||||
let testnet_dir = get_testnet_dir()?;
|
||||
new_testnet::run::<E>(testnet_dir, matches)
|
||||
.map_err(|e| format!("Failed to run new_testnet command: {}", e))
|
||||
}
|
||||
Some(("check-deposit-data", matches)) => check_deposit_data::run(matches)
|
||||
.map_err(|e| format!("Failed to run check-deposit-data command: {}", e)),
|
||||
Some(("generate-bootnode-enr", matches)) => generate_bootnode_enr::run::<E>(matches)
|
||||
.map_err(|e| format!("Failed to run generate-bootnode-enr command: {}", e)),
|
||||
Some(("insecure-validators", matches)) => insecure_validators::run(matches)
|
||||
.map_err(|e| format!("Failed to run insecure-validators command: {}", e)),
|
||||
Some(("mnemonic-validators", matches)) => mnemonic_validators::run(matches)
|
||||
.map_err(|e| format!("Failed to run mnemonic-validators command: {}", e)),
|
||||
Some(("indexed-attestations", matches)) => indexed_attestations::run::<E>(matches)
|
||||
|
||||
@@ -1,393 +0,0 @@
|
||||
use account_utils::eth2_keystore::keypair_from_secret;
|
||||
use clap::ArgMatches;
|
||||
use clap_utils::{parse_optional, parse_required, parse_ssz_optional};
|
||||
use eth2_network_config::{Eth2NetworkConfig, GenesisStateSource, TRUSTED_SETUP_BYTES};
|
||||
use eth2_wallet::bip39::Seed;
|
||||
use eth2_wallet::bip39::{Language, Mnemonic};
|
||||
use eth2_wallet::{recover_validator_secret_from_mnemonic, KeyType};
|
||||
use ethereum_hashing::hash;
|
||||
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_deneb,
|
||||
upgrade_to_electra,
|
||||
};
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
use types::ExecutionBlockHash;
|
||||
use types::{
|
||||
test_utils::generate_deterministic_keypairs, Address, BeaconState, ChainSpec, Config, Epoch,
|
||||
Eth1Data, EthSpec, ExecutionPayloadHeader, ExecutionPayloadHeaderBellatrix,
|
||||
ExecutionPayloadHeaderCapella, ExecutionPayloadHeaderDeneb, ExecutionPayloadHeaderElectra,
|
||||
ForkName, Hash256, Keypair, PublicKey, Validator,
|
||||
};
|
||||
|
||||
pub fn run<E: EthSpec>(testnet_dir_path: PathBuf, matches: &ArgMatches) -> Result<(), String> {
|
||||
let deposit_contract_address: Address = parse_required(matches, "deposit-contract-address")?;
|
||||
let deposit_contract_deploy_block = parse_required(matches, "deposit-contract-deploy-block")?;
|
||||
|
||||
let overwrite_files = matches.get_flag("force");
|
||||
|
||||
if testnet_dir_path.exists() && !overwrite_files {
|
||||
return Err(format!(
|
||||
"{:?} already exists, will not overwrite. Use --force to overwrite",
|
||||
testnet_dir_path
|
||||
));
|
||||
}
|
||||
|
||||
let mut spec = E::default_spec();
|
||||
|
||||
// Update the spec value if the flag was defined. Otherwise, leave it as the default.
|
||||
macro_rules! maybe_update {
|
||||
($flag: tt, $var: ident) => {
|
||||
if let Some(val) = parse_optional(matches, $flag)? {
|
||||
spec.$var = val
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
spec.deposit_contract_address = deposit_contract_address;
|
||||
|
||||
maybe_update!("min-genesis-time", min_genesis_time);
|
||||
maybe_update!("min-deposit-amount", min_deposit_amount);
|
||||
maybe_update!(
|
||||
"min-genesis-active-validator-count",
|
||||
min_genesis_active_validator_count
|
||||
);
|
||||
maybe_update!("max-effective-balance", max_effective_balance);
|
||||
maybe_update!("effective-balance-increment", effective_balance_increment);
|
||||
maybe_update!("ejection-balance", ejection_balance);
|
||||
maybe_update!("eth1-follow-distance", eth1_follow_distance);
|
||||
maybe_update!("genesis-delay", genesis_delay);
|
||||
maybe_update!("eth1-id", deposit_chain_id);
|
||||
maybe_update!("eth1-id", deposit_network_id);
|
||||
maybe_update!("seconds-per-slot", seconds_per_slot);
|
||||
maybe_update!("seconds-per-eth1-block", seconds_per_eth1_block);
|
||||
|
||||
if let Some(v) = parse_ssz_optional(matches, "genesis-fork-version")? {
|
||||
spec.genesis_fork_version = v;
|
||||
}
|
||||
|
||||
if let Some(proposer_score_boost) = parse_optional(matches, "proposer-score-boost")? {
|
||||
spec.proposer_score_boost = Some(proposer_score_boost);
|
||||
}
|
||||
|
||||
if let Some(fork_epoch) = parse_optional(matches, "altair-fork-epoch")? {
|
||||
spec.altair_fork_epoch = Some(fork_epoch);
|
||||
}
|
||||
|
||||
if let Some(fork_epoch) = parse_optional(matches, "bellatrix-fork-epoch")? {
|
||||
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, "deneb-fork-epoch")? {
|
||||
spec.deneb_fork_epoch = Some(fork_epoch);
|
||||
}
|
||||
|
||||
if let Some(fork_epoch) = parse_optional(matches, "electra-fork-epoch")? {
|
||||
spec.electra_fork_epoch = Some(fork_epoch);
|
||||
}
|
||||
|
||||
if let Some(ttd) = parse_optional(matches, "ttd")? {
|
||||
spec.terminal_total_difficulty = ttd;
|
||||
}
|
||||
|
||||
let validator_count = parse_required(matches, "validator-count")?;
|
||||
let execution_payload_header: Option<ExecutionPayloadHeader<E>> =
|
||||
parse_optional(matches, "execution-payload-header")?
|
||||
.map(|filename: String| {
|
||||
let mut bytes = vec![];
|
||||
let mut file = File::open(filename.as_str())
|
||||
.map_err(|e| format!("Unable to open {}: {}", filename, e))?;
|
||||
file.read_to_end(&mut bytes)
|
||||
.map_err(|e| format!("Unable to read {}: {}", filename, e))?;
|
||||
let fork_name = spec.fork_name_at_epoch(Epoch::new(0));
|
||||
match fork_name {
|
||||
ForkName::Base | ForkName::Altair => Err(ssz::DecodeError::BytesInvalid(
|
||||
"genesis fork must be post-merge".to_string(),
|
||||
)),
|
||||
ForkName::Bellatrix => {
|
||||
ExecutionPayloadHeaderBellatrix::<E>::from_ssz_bytes(bytes.as_slice())
|
||||
.map(ExecutionPayloadHeader::Bellatrix)
|
||||
}
|
||||
ForkName::Capella => {
|
||||
ExecutionPayloadHeaderCapella::<E>::from_ssz_bytes(bytes.as_slice())
|
||||
.map(ExecutionPayloadHeader::Capella)
|
||||
}
|
||||
ForkName::Deneb => {
|
||||
ExecutionPayloadHeaderDeneb::<E>::from_ssz_bytes(bytes.as_slice())
|
||||
.map(ExecutionPayloadHeader::Deneb)
|
||||
}
|
||||
ForkName::Electra => {
|
||||
ExecutionPayloadHeaderElectra::<E>::from_ssz_bytes(bytes.as_slice())
|
||||
.map(ExecutionPayloadHeader::Electra)
|
||||
}
|
||||
}
|
||||
.map_err(|e| format!("SSZ decode failed: {:?}", e))
|
||||
})
|
||||
.transpose()?;
|
||||
|
||||
let (eth1_block_hash, genesis_time) = if let Some(payload) = execution_payload_header.as_ref() {
|
||||
let eth1_block_hash =
|
||||
parse_optional(matches, "eth1-block-hash")?.unwrap_or_else(|| payload.block_hash());
|
||||
let genesis_time =
|
||||
parse_optional(matches, "genesis-time")?.unwrap_or_else(|| payload.timestamp());
|
||||
(eth1_block_hash, genesis_time)
|
||||
} else {
|
||||
let eth1_block_hash = parse_required(matches, "eth1-block-hash").map_err(|_| {
|
||||
"One of `--execution-payload-header` or `--eth1-block-hash` must be set".to_string()
|
||||
})?;
|
||||
let genesis_time = parse_optional(matches, "genesis-time")?.unwrap_or(
|
||||
SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.map_err(|e| format!("Unable to get time: {:?}", e))?
|
||||
.as_secs(),
|
||||
);
|
||||
(eth1_block_hash, genesis_time)
|
||||
};
|
||||
|
||||
let genesis_state_bytes = if matches.get_flag("interop-genesis-state") {
|
||||
let keypairs = generate_deterministic_keypairs(validator_count);
|
||||
let keypairs: Vec<_> = keypairs.into_iter().map(|kp| (kp.clone(), kp)).collect();
|
||||
|
||||
let genesis_state = initialize_state_with_validators::<E>(
|
||||
&keypairs,
|
||||
genesis_time,
|
||||
eth1_block_hash.into_root(),
|
||||
execution_payload_header,
|
||||
&spec,
|
||||
)?;
|
||||
|
||||
Some(genesis_state.as_ssz_bytes())
|
||||
} else if matches.get_flag("derived-genesis-state") {
|
||||
let mnemonic_phrase: String = clap_utils::parse_required(matches, "mnemonic-phrase")?;
|
||||
let mnemonic = Mnemonic::from_phrase(&mnemonic_phrase, Language::English).map_err(|e| {
|
||||
format!(
|
||||
"Unable to derive mnemonic from string {:?}: {:?}",
|
||||
mnemonic_phrase, e
|
||||
)
|
||||
})?;
|
||||
let seed = Seed::new(&mnemonic, "");
|
||||
let keypairs = (0..validator_count as u32)
|
||||
.map(|index| {
|
||||
let (secret, _) =
|
||||
recover_validator_secret_from_mnemonic(seed.as_bytes(), index, KeyType::Voting)
|
||||
.unwrap();
|
||||
|
||||
let voting_keypair = keypair_from_secret(secret.as_bytes()).unwrap();
|
||||
|
||||
let (secret, _) = recover_validator_secret_from_mnemonic(
|
||||
seed.as_bytes(),
|
||||
index,
|
||||
KeyType::Withdrawal,
|
||||
)
|
||||
.unwrap();
|
||||
let withdrawal_keypair = keypair_from_secret(secret.as_bytes()).unwrap();
|
||||
(voting_keypair, withdrawal_keypair)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let genesis_state = initialize_state_with_validators::<E>(
|
||||
&keypairs,
|
||||
genesis_time,
|
||||
eth1_block_hash.into_root(),
|
||||
execution_payload_header,
|
||||
&spec,
|
||||
)?;
|
||||
Some(genesis_state.as_ssz_bytes())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let kzg_trusted_setup = if let Some(epoch) = spec.deneb_fork_epoch {
|
||||
// Only load the trusted setup if the deneb fork epoch is set
|
||||
if epoch != Epoch::max_value() {
|
||||
Some(TRUSTED_SETUP_BYTES.to_vec())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let testnet = Eth2NetworkConfig {
|
||||
deposit_contract_deploy_block,
|
||||
boot_enr: Some(vec![]),
|
||||
genesis_state_bytes: genesis_state_bytes.map(Into::into),
|
||||
genesis_state_source: GenesisStateSource::IncludedBytes,
|
||||
config: Config::from_chain_spec::<E>(&spec),
|
||||
kzg_trusted_setup,
|
||||
};
|
||||
|
||||
testnet.write_to_file(testnet_dir_path, overwrite_files)
|
||||
}
|
||||
|
||||
/// Returns a `BeaconState` with the given validator keypairs embedded into the
|
||||
/// genesis state. This allows us to start testnets without having to deposit validators
|
||||
/// manually.
|
||||
///
|
||||
/// The optional `execution_payload_header` allows us to start a network from the bellatrix
|
||||
/// fork without the need to transition to altair and bellatrix.
|
||||
///
|
||||
/// We need to ensure that `eth1_block_hash` is equal to the genesis block hash that is
|
||||
/// generated from the execution side `genesis.json`.
|
||||
fn initialize_state_with_validators<E: EthSpec>(
|
||||
keypairs: &[(Keypair, Keypair)], // Voting and Withdrawal keypairs
|
||||
genesis_time: u64,
|
||||
eth1_block_hash: Hash256,
|
||||
execution_payload_header: Option<ExecutionPayloadHeader<E>>,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<BeaconState<E>, String> {
|
||||
// If no header is provided, then start from a Bellatrix state by default
|
||||
let default_header: ExecutionPayloadHeader<E> =
|
||||
ExecutionPayloadHeader::Bellatrix(ExecutionPayloadHeaderBellatrix {
|
||||
block_hash: ExecutionBlockHash::from_root(eth1_block_hash),
|
||||
parent_hash: ExecutionBlockHash::zero(),
|
||||
..ExecutionPayloadHeaderBellatrix::default()
|
||||
});
|
||||
let execution_payload_header = execution_payload_header.unwrap_or(default_header);
|
||||
// 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).unwrap();
|
||||
|
||||
for keypair in keypairs.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.0.pk.clone().into(),
|
||||
withdrawal_credentials: withdrawal_credentials(&keypair.1.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 == E::genesis_epoch())
|
||||
{
|
||||
upgrade_to_altair(&mut state, spec).unwrap();
|
||||
|
||||
state.fork_mut().previous_version = spec.altair_fork_version;
|
||||
}
|
||||
|
||||
// Similarly, perform an upgrade to Bellatrix if configured from genesis.
|
||||
if spec
|
||||
.bellatrix_fork_epoch
|
||||
.map_or(false, |fork_epoch| fork_epoch == E::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/bellatrix/beacon-chain.md#testing
|
||||
if let ExecutionPayloadHeader::Bellatrix(ref header) = execution_payload_header {
|
||||
*state
|
||||
.latest_execution_payload_header_bellatrix_mut()
|
||||
.or(Err("mismatched fork".to_string()))? = header.clone();
|
||||
}
|
||||
}
|
||||
|
||||
// Similarly, perform an upgrade to Capella if configured from genesis.
|
||||
if spec
|
||||
.capella_fork_epoch
|
||||
.map_or(false, |fork_epoch| fork_epoch == E::genesis_epoch())
|
||||
{
|
||||
upgrade_to_capella(&mut state, spec).unwrap();
|
||||
|
||||
// Remove intermediate Bellatrix fork from `state.fork`.
|
||||
state.fork_mut().previous_version = spec.capella_fork_version;
|
||||
|
||||
// Override latest execution payload header.
|
||||
// See https://github.com/ethereum/consensus-specs/blob/v1.1.0/specs/bellatrix/beacon-chain.md#testing
|
||||
if let ExecutionPayloadHeader::Capella(ref header) = execution_payload_header {
|
||||
*state
|
||||
.latest_execution_payload_header_capella_mut()
|
||||
.or(Err("mismatched fork".to_string()))? = header.clone();
|
||||
}
|
||||
}
|
||||
|
||||
// Similarly, perform an upgrade to Deneb if configured from genesis.
|
||||
if spec
|
||||
.deneb_fork_epoch
|
||||
.map_or(false, |fork_epoch| fork_epoch == E::genesis_epoch())
|
||||
{
|
||||
upgrade_to_deneb(&mut state, spec).unwrap();
|
||||
|
||||
// Remove intermediate Capella fork from `state.fork`.
|
||||
state.fork_mut().previous_version = spec.deneb_fork_version;
|
||||
|
||||
// Override latest execution payload header.
|
||||
// See https://github.com/ethereum/consensus-specs/blob/v1.1.0/specs/bellatrix/beacon-chain.md#testing
|
||||
if let ExecutionPayloadHeader::Deneb(ref header) = execution_payload_header {
|
||||
*state
|
||||
.latest_execution_payload_header_deneb_mut()
|
||||
.or(Err("mismatched fork".to_string()))? = header.clone();
|
||||
}
|
||||
}
|
||||
|
||||
// Similarly, perform an upgrade to Electra if configured from genesis.
|
||||
if spec
|
||||
.electra_fork_epoch
|
||||
.map_or(false, |fork_epoch| fork_epoch == E::genesis_epoch())
|
||||
{
|
||||
upgrade_to_electra(&mut state, spec).unwrap();
|
||||
|
||||
// Remove intermediate Deneb fork from `state.fork`.
|
||||
state.fork_mut().previous_version = spec.electra_fork_version;
|
||||
|
||||
// Override latest execution payload header.
|
||||
// See https://github.com/ethereum/consensus-specs/blob/v1.1.0/specs/bellatrix/beacon-chain.md#testing
|
||||
if let ExecutionPayloadHeader::Electra(ref header) = execution_payload_header {
|
||||
*state
|
||||
.latest_execution_payload_header_electra_mut()
|
||||
.or(Err("mismatched fork".to_string()))? = header.clone();
|
||||
}
|
||||
}
|
||||
|
||||
// Now that we have our validators, initialize the caches (including the committees)
|
||||
state.build_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();
|
||||
|
||||
// Sanity check for state fork matching config fork.
|
||||
state
|
||||
.fork_name(spec)
|
||||
.map_err(|e| format!("state fork mismatch: {e:?}"))?;
|
||||
|
||||
Ok(state)
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
use account_utils::{eth2_keystore::keypair_from_secret, mnemonic_from_phrase};
|
||||
use clap::ArgMatches;
|
||||
use eth2_network_config::Eth2NetworkConfig;
|
||||
use eth2_wallet::bip39::Seed;
|
||||
use eth2_wallet::{recover_validator_secret_from_mnemonic, KeyType};
|
||||
use ssz::Encode;
|
||||
use state_processing::common::DepositDataTree;
|
||||
use std::fs::File;
|
||||
use std::io::{Read, Write};
|
||||
use std::path::PathBuf;
|
||||
use tree_hash::TreeHash;
|
||||
use types::{BeaconState, DepositData, EthSpec, Hash256, SignatureBytes, DEPOSIT_TREE_DEPTH};
|
||||
|
||||
pub fn run<E: EthSpec>(testnet_dir: PathBuf, matches: &ArgMatches) -> Result<(), String> {
|
||||
let path = matches
|
||||
.get_one::<String>("ssz-state")
|
||||
.ok_or("ssz-state not specified")?
|
||||
.parse::<PathBuf>()
|
||||
.map_err(|e| format!("Unable to parse ssz-state: {}", e))?;
|
||||
|
||||
let mnemonic_phrase = matches
|
||||
.get_one::<String>("mnemonic")
|
||||
.ok_or("mnemonic not specified")?;
|
||||
|
||||
let eth2_network_config = Eth2NetworkConfig::load(testnet_dir)?;
|
||||
let spec = ð2_network_config.chain_spec::<E>()?;
|
||||
|
||||
let mut state: BeaconState<E> = {
|
||||
let mut file = File::open(&path).map_err(|e| format!("Unable to open file: {}", e))?;
|
||||
|
||||
let mut ssz = vec![];
|
||||
|
||||
file.read_to_end(&mut ssz)
|
||||
.map_err(|e| format!("Unable to read file: {}", e))?;
|
||||
|
||||
BeaconState::from_ssz_bytes(&ssz, spec)
|
||||
.map_err(|e| format!("Unable to decode SSZ: {:?}", e))?
|
||||
};
|
||||
|
||||
let mnemonic = mnemonic_from_phrase(mnemonic_phrase)?;
|
||||
let seed = Seed::new(&mnemonic, "");
|
||||
|
||||
let mut deposit_tree = DepositDataTree::create(&[], 0, DEPOSIT_TREE_DEPTH);
|
||||
let mut deposit_root = Hash256::zero();
|
||||
let validators = state.validators_mut();
|
||||
for index in 0..validators.len() {
|
||||
let (secret, _) =
|
||||
recover_validator_secret_from_mnemonic(seed.as_bytes(), index as u32, KeyType::Voting)
|
||||
.map_err(|e| format!("Unable to generate validator key: {:?}", e))?;
|
||||
|
||||
let keypair = keypair_from_secret(secret.as_bytes())
|
||||
.map_err(|e| format!("Unable build keystore: {:?}", e))?;
|
||||
|
||||
eprintln!("{}: {}", index, keypair.pk);
|
||||
|
||||
validators.get_mut(index).unwrap().pubkey = keypair.pk.into();
|
||||
|
||||
// Update the deposit tree.
|
||||
let mut deposit_data = DepositData {
|
||||
pubkey: validators.get(index).unwrap().pubkey,
|
||||
// Set this to a junk value since it's very time consuming to generate the withdrawal
|
||||
// keys and it's not useful for the time being.
|
||||
withdrawal_credentials: Hash256::zero(),
|
||||
amount: spec.min_deposit_amount,
|
||||
signature: SignatureBytes::empty(),
|
||||
};
|
||||
deposit_data.signature = deposit_data.create_signature(&keypair.sk, spec);
|
||||
deposit_tree
|
||||
.push_leaf(deposit_data.tree_hash_root())
|
||||
.map_err(|e| format!("failed to create deposit tree: {:?}", e))?;
|
||||
deposit_root = deposit_tree.root();
|
||||
}
|
||||
|
||||
// Update the genesis validators root since we changed the validators.
|
||||
*state.genesis_validators_root_mut() = state.validators().tree_hash_root();
|
||||
|
||||
// Update the deposit root with our simulated deposits.
|
||||
state.eth1_data_mut().deposit_root = deposit_root;
|
||||
|
||||
let mut file = File::create(path).map_err(|e| format!("Unable to create file: {}", e))?;
|
||||
|
||||
file.write_all(&state.as_ssz_bytes())
|
||||
.map_err(|e| format!("Unable to write to file: {}", e))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user