diff --git a/.github/workflows/prysm-geth.yaml b/.github/workflows/prysm-geth.yaml index 76db210..0b0df93 100644 --- a/.github/workflows/prysm-geth.yaml +++ b/.github/workflows/prysm-geth.yaml @@ -42,7 +42,7 @@ jobs: run : | sudo apt update sudo apt install -y git gcc g++ make cmake pkg-config llvm-dev libclang-dev clang - - name: Install Lighthouse + - name: Install Lighthouse lcli run: | git clone https://github.com/sigp/lighthouse.git cd lighthouse @@ -55,6 +55,8 @@ jobs: run: | curl -L https://github.com/prysmaticlabs/prysm/releases/download/v5.3.0/beacon-chain-v5.3.0-linux-amd64 -o prysm chmod +x prysm + curl -L https://github.com/prysmaticlabs/prysm/releases/download/v5.3.0/validator-v5.3.0-linux-amd64 -o prysm_vc + chmod +x prysm_vc - name: Install Geth run: | git clone https://github.com/ethereum/go-ethereum.git @@ -79,7 +81,7 @@ jobs: block_published_lines=$(cat ./data/shadow/hosts/node*/prysm.*.stderr | grep "Finished building block" | wc -l) echo "block_published_lines=$block_published_lines" test $block_published_lines -eq 24 - attestation_published_lines=$(cat ./data/shadow/hosts/node*/lighthouse.*.stderr | grep "Successfully published attestations" | wc -l) + attestation_published_lines=$(cat ./data/shadow/hosts/node*/prysm_vc.*.stderr | grep -a "Submitted new attestations" | wc -l) echo "attestation_published_lines=$attestation_published_lines" test $attestation_published_lines -gt 22 chain_segment_imported_lines=$(cat ./data/shadow/hosts/node*/geth.*.stderr | grep "Imported new potential chain segment" | wc -l) diff --git a/README.md b/README.md index 4c70562..e1467fe 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,7 @@ cd lighthouse git checkout v5.3.0 # The latest tested version make make install-lcli +cd .. # Geth git clone https://github.com/ethereum/go-ethereum.git @@ -38,6 +39,7 @@ git checkout v1.14.11 # The latest tested version make all sudo cp build/bin/geth /usr/local/bin/geth # Make it globally accessible sudo cp build/bin/bootnode /usr/local/bin/bootnode # Make it globally accessible +cd .. ``` Install Shadow @@ -48,6 +50,7 @@ cd shadow ./setup build --clean ./setup install echo 'export PATH="${PATH}:/home/${USER}/.local/bin"' >> ~/.bashrc && source ~/.bashrc +cd .. ``` Install Ethshadow. @@ -55,6 +58,7 @@ Install Ethshadow. git clone https://github.com/ethereum/ethshadow.git cd ethshadow cargo install --path . +cd .. ``` Save the following file to a config file `myfirstsim.yaml`. @@ -117,7 +121,7 @@ features might not work. | [Lighthouse](docs/clients/lighthouse.md) | ✅ | ✅ | ✅ | v5.3.0 | | Lodestar | ❔ | ❔ | ❔ | | | Nimbus | ❔ | ❔ | ❔ | | -| Prysm | ✅ | ❔ | ❔ | v5.3.0 | +| [Prysm](docs/clients/prysm.md) | ✅ | ❔ | ✅ | v5.3.0 | | Teku | ❌ | ❔ | ❔ | | diff --git a/ci/prysm-geth.yaml b/ci/prysm-geth.yaml index bc31192..2691374 100644 --- a/ci/prysm-geth.yaml +++ b/ci/prysm-geth.yaml @@ -19,10 +19,13 @@ ethereum: clients: el: geth cl: prysm - vc: lighthouse_vc + vc: prysm_vc count: per_combination: 10 clients: prysm: type: prysm executable: ./prysm + prysm_vc: + type: prysm_vc + executable: ./prysm_vc diff --git a/docs/clients/prysm.md b/docs/clients/prysm.md new file mode 100644 index 0000000..401e437 --- /dev/null +++ b/docs/clients/prysm.md @@ -0,0 +1,23 @@ +# Prysm + +## Installation + +Download Prysm beacon chain and validator binaries from the Github release page. + +```sh +curl -L https://github.com/prysmaticlabs/prysm/releases/download/v5.3.0/beacon-chain-v5.3.0-linux-amd64 -o prysm +chmod +x prysm +curl -L https://github.com/prysmaticlabs/prysm/releases/download/v5.3.0/validator-v5.3.0-linux-amd64 -o prysm_vc +chmod +x prysm_vc +``` + +## Configuration + +### Prysm beacon chain + +- `executable`: Specify path of the prysm beacon chain binary to use. This field is required and there is no default value for it. + +### Prysm validator + +- `executable`: Specify path of the prysm validator binary to use. This field is required and there is no default value for it. +PATH. diff --git a/lib/src/clients/blobssss.rs b/lib/src/clients/blobssss.rs index 9dc6b83..0f0cdb2 100644 --- a/lib/src/clients/blobssss.rs +++ b/lib/src/clients/blobssss.rs @@ -2,7 +2,7 @@ use crate::clients::Client; use crate::clients::CommonParams; use crate::config::shadow::Process; use crate::node::{NodeInfo, SimulationContext}; -use crate::validators::Validator; +use crate::validators::ValidatorSet; use crate::Error; use itertools::Itertools; use serde::Deserialize; @@ -24,7 +24,7 @@ impl Client for Blobssss { &self, _node: &NodeInfo, ctx: &mut SimulationContext, - _validators: &[Validator], + _vs: &ValidatorSet, ) -> Result { Ok(Process { path: self.common.executable_or("blobssss"), diff --git a/lib/src/clients/geth.rs b/lib/src/clients/geth.rs index 96bd052..fa179f7 100644 --- a/lib/src/clients/geth.rs +++ b/lib/src/clients/geth.rs @@ -4,7 +4,7 @@ use crate::clients::{Client, JSON_RPC_PORT}; use crate::config::shadow::Process; use crate::node::{NodeInfo, SimulationContext}; use crate::utils::log_and_wait; -use crate::validators::Validator; +use crate::validators::ValidatorSet; use crate::Error; use log::debug; use serde::Deserialize; @@ -26,7 +26,7 @@ impl Client for Geth { &self, node: &NodeInfo, ctx: &mut SimulationContext, - _validators: &[Validator], + _vs: &ValidatorSet, ) -> Result { let genesis_file = ctx.metadata_path().join("genesis.json"); let genesis_file = genesis_file.to_str().ok_or(Error::NonUTF8Path)?; diff --git a/lib/src/clients/geth_bootnode.rs b/lib/src/clients/geth_bootnode.rs index 0a0d2fe..72044c4 100644 --- a/lib/src/clients/geth_bootnode.rs +++ b/lib/src/clients/geth_bootnode.rs @@ -6,7 +6,7 @@ use std::io::Write; use libsecp256k1::{PublicKey, SecretKey}; use serde::Deserialize; -use crate::clients::{Client, Validator}; +use crate::clients::{Client, ValidatorSet}; use crate::config::shadow::Process; use crate::node::{NodeInfo, SimulationContext}; use crate::Error; @@ -26,7 +26,7 @@ impl Client for GethBootnode { &self, node: &NodeInfo, ctx: &mut SimulationContext, - _validators: &[Validator], + _vs: &ValidatorSet, ) -> Result { let dir = node.dir().join("geth_bootnode"); create_dir(&dir)?; diff --git a/lib/src/clients/lighthouse.rs b/lib/src/clients/lighthouse.rs index a3323fc..793682c 100644 --- a/lib/src/clients/lighthouse.rs +++ b/lib/src/clients/lighthouse.rs @@ -3,7 +3,7 @@ use crate::clients::CommonParams; use crate::clients::{BEACON_API_PORT, CL_PROMETHEUS_PORT, ENGINE_API_PORT}; use crate::config::shadow::Process; use crate::node::{NodeInfo, SimulationContext}; -use crate::validators::Validator; +use crate::validators::ValidatorSet; use crate::Error; use serde::Deserialize; use std::collections::HashMap; @@ -33,7 +33,7 @@ impl Client for Lighthouse { &self, node: &NodeInfo<'a>, ctx: &mut SimulationContext<'a>, - _validators: &[Validator], + _vs: &ValidatorSet, ) -> Result { let dir = node.dir().join("lighthouse"); let dir = dir.to_str().ok_or(Error::NonUTF8Path)?; diff --git a/lib/src/clients/lighthouse_bootnode.rs b/lib/src/clients/lighthouse_bootnode.rs index 0f52de5..028013c 100644 --- a/lib/src/clients/lighthouse_bootnode.rs +++ b/lib/src/clients/lighthouse_bootnode.rs @@ -5,7 +5,7 @@ use std::collections::HashMap; use std::fs::read_to_string; use std::process::Command; -use crate::clients::{Client, Validator}; +use crate::clients::{Client, ValidatorSet}; use crate::config::shadow::Process; use crate::node::{NodeInfo, SimulationContext}; use crate::utils::log_and_wait; @@ -37,7 +37,7 @@ impl Client for LighthouseBootnode { &self, node: &NodeInfo, ctx: &mut SimulationContext, - _validators: &[Validator], + _vs: &ValidatorSet, ) -> Result { let dir = node.dir().join("lighthouse_bootnode"); debug!("Calling lcli generate-bootnode-enr"); diff --git a/lib/src/clients/lighthouse_vc.rs b/lib/src/clients/lighthouse_vc.rs index 18e8231..7ff6051 100644 --- a/lib/src/clients/lighthouse_vc.rs +++ b/lib/src/clients/lighthouse_vc.rs @@ -3,7 +3,7 @@ use crate::clients::BEACON_API_PORT; use crate::clients::{Client, ValidatorDemand}; use crate::config::shadow::Process; use crate::node::{NodeInfo, SimulationContext}; -use crate::validators::Validator; +use crate::validators::ValidatorSet; use crate::Error; use serde::Deserialize; use std::collections::HashMap; @@ -24,34 +24,15 @@ impl Client for LighthouseValidatorClient { &self, node: &NodeInfo, ctx: &mut SimulationContext, - validators: &[Validator], + vs: &ValidatorSet, ) -> Result { let dir = node.dir().join("lighthouse"); let dir_str = dir.to_str().ok_or(Error::NonUTF8Path)?; if !dir.exists() { create_dir(&dir)?; } - - let secrets_dest = dir.join("secrets"); - if !secrets_dest.exists() { - create_dir(&secrets_dest)?; - } - let validators_dest = dir.join("validators"); - if !validators_dest.exists() { - create_dir(&validators_dest)?; - } - - for validator in validators { - let key = validator.key(); - fs::rename( - validator.base_path().join("secrets").join(key), - secrets_dest.join(key), - )?; - fs::rename( - validator.base_path().join("keys").join(key), - validators_dest.join(key), - )?; - } + fs::rename(vs.base_path().join("secrets"), dir.join("secrets"))?; + fs::rename(vs.base_path().join("keys"), dir.join("validators"))?; Ok(Process { path: self.common.executable_or("lighthouse"), diff --git a/lib/src/clients/mod.rs b/lib/src/clients/mod.rs index 23cc1a6..8d0c178 100644 --- a/lib/src/clients/mod.rs +++ b/lib/src/clients/mod.rs @@ -1,6 +1,6 @@ use crate::config::shadow::Process; use crate::node::{NodeInfo, SimulationContext}; -use crate::validators::Validator; +use crate::validators::ValidatorSet; use crate::CowStr; use crate::Error; use serde::Deserialize; @@ -19,6 +19,7 @@ pub mod lighthouse_bootnode; pub mod lighthouse_vc; pub mod prometheus; pub mod prysm; +pub mod prysm_vc; pub mod reth; pub enum ValidatorDemand { @@ -38,7 +39,7 @@ pub trait Client: Debug { &self, node: &NodeInfo<'a>, ctx: &mut SimulationContext<'a>, - validators: &[Validator], + validators: &ValidatorSet, ) -> Result; fn validator_demand(&self) -> ValidatorDemand { diff --git a/lib/src/clients/prometheus.rs b/lib/src/clients/prometheus.rs index a8c9e40..1108d9d 100644 --- a/lib/src/clients/prometheus.rs +++ b/lib/src/clients/prometheus.rs @@ -2,7 +2,7 @@ use crate::clients::Client; use crate::clients::CommonParams; use crate::config::shadow::Process; use crate::node::{NodeInfo, SimulationContext}; -use crate::validators::Validator; +use crate::validators::ValidatorSet; use crate::Error; use serde::{Deserialize, Serialize}; use serde_yaml::to_writer; @@ -40,7 +40,7 @@ impl Client for Prometheus { &self, node: &NodeInfo, ctx: &mut SimulationContext, - _validators: &[Validator], + _vs: &ValidatorSet, ) -> Result { let dir = node.dir().join("prometheus"); let config_file = node.dir().join("prometheus.yaml"); diff --git a/lib/src/clients/prysm.rs b/lib/src/clients/prysm.rs index 88416bb..74c7100 100644 --- a/lib/src/clients/prysm.rs +++ b/lib/src/clients/prysm.rs @@ -3,7 +3,7 @@ use crate::clients::CommonParams; use crate::clients::{BEACON_API_PORT, ENGINE_API_PORT}; use crate::config::shadow::Process; use crate::node::{NodeInfo, SimulationContext}; -use crate::validators::Validator; +use crate::validators::ValidatorSet; use crate::Error; use serde::Deserialize; use std::collections::HashMap; @@ -33,7 +33,7 @@ impl Client for Prysm { &self, node: &NodeInfo<'a>, ctx: &mut SimulationContext<'a>, - _validators: &[Validator], + _vs: &ValidatorSet, ) -> Result { if self.common.executable.is_empty() { return Err(Error::MissingExecutable(String::from("prysm"))); diff --git a/lib/src/clients/prysm_vc.rs b/lib/src/clients/prysm_vc.rs new file mode 100644 index 0000000..5e71868 --- /dev/null +++ b/lib/src/clients/prysm_vc.rs @@ -0,0 +1,73 @@ +use crate::clients::CommonParams; +use crate::clients::{Client, ValidatorDemand}; +use crate::config::shadow::Process; +use crate::node::{NodeInfo, SimulationContext}; +use crate::validators::ValidatorSet; +use crate::Error; +use serde::Deserialize; +use std::collections::HashMap; +use std::fs; +use std::fs::create_dir; + +#[derive(Deserialize, Debug, Clone, Default)] +#[serde(default)] +pub struct PrysmValidatorClient { + #[serde(flatten)] + pub common: CommonParams, + pub validators: Option, +} + +#[typetag::deserialize(name = "prysm_vc")] +impl Client for PrysmValidatorClient { + fn add_to_node( + &self, + node: &NodeInfo, + ctx: &mut SimulationContext, + vs: &ValidatorSet, + ) -> Result { + if self.common.executable.is_empty() { + return Err(Error::MissingExecutable(String::from("prysm_vc"))); + } + let dir = node.dir().join("prysm"); + let dir_str = dir.to_str().ok_or(Error::NonUTF8Path)?; + if !dir.exists() { + create_dir(&dir)?; + } + let wallet_dir = dir.join("wallet"); + let wallet_dir_str = wallet_dir.to_str().ok_or(Error::NonUTF8Path)?; + + let password_file = dir.join("passphrase"); + let password_file_str = password_file.to_str().ok_or(Error::NonUTF8Path)?; + + fs::rename(vs.base_path().join("prysm"), &wallet_dir)?; + fs::write(&password_file, "12345678")?; + + let meta_dir = ctx.metadata_path().to_str().ok_or(Error::NonUTF8Path)?; + + let args = format!( + "--chain-config-file \"{meta_dir}/config.yaml\" \ + --accept-terms-of-use \ + --datadir \"{dir_str}\" \ + --wallet-dir \"{wallet_dir_str}\" \ + --wallet-password-file \"{password_file_str}\" \ + {}", + self.common + .arguments("--suggested-fee-recipient 0xf97e180c050e5Ab072211Ad2C213Eb5AEE4DF134"), + ); + + Ok(Process { + path: self.common.executable.clone().into(), + args, + environment: HashMap::new(), + expected_final_state: "running".into(), + start_time: "5s".into(), + }) + } + + fn validator_demand(&self) -> ValidatorDemand { + match self.validators { + None => ValidatorDemand::Any, + Some(num) => ValidatorDemand::Count(num), + } + } +} diff --git a/lib/src/clients/reth.rs b/lib/src/clients/reth.rs index 3a96b34..c1cc4ec 100644 --- a/lib/src/clients/reth.rs +++ b/lib/src/clients/reth.rs @@ -3,7 +3,7 @@ use crate::clients::ENGINE_API_PORT; use crate::clients::{Client, JSON_RPC_PORT}; use crate::config::shadow::Process; use crate::node::{NodeInfo, SimulationContext}; -use crate::validators::Validator; +use crate::validators::ValidatorSet; use crate::Error; use serde::Deserialize; use std::collections::HashMap; @@ -23,7 +23,7 @@ impl Client for Reth { &self, node: &NodeInfo, ctx: &mut SimulationContext, - _validators: &[Validator], + _vs: &ValidatorSet, ) -> Result { let genesis_file = ctx.metadata_path().join("genesis.json"); let genesis_file = genesis_file.to_str().ok_or(Error::NonUTF8Path)?; diff --git a/lib/src/node.rs b/lib/src/node.rs index d7576eb..8145c19 100644 --- a/lib/src/node.rs +++ b/lib/src/node.rs @@ -105,8 +105,8 @@ impl<'c, 'n> NodeManager<'c, 'n> { }; for &client in clients { - let validators = self.validator_manager.assign(client); - let process = client.add_to_node(&node, &mut self.ctx, validators)?; + let vs = self.validator_manager.assign(client)?; + let process = client.add_to_node(&node, &mut self.ctx, &vs)?; host.processes.push(process); } diff --git a/lib/src/validators.rs b/lib/src/validators.rs index a53c6f2..c238ab8 100644 --- a/lib/src/validators.rs +++ b/lib/src/validators.rs @@ -3,20 +3,20 @@ use crate::config::ethshadow::{Node, DEFAULT_GENESIS_GEN_IMAGE, DEFAULT_MNEMONIC use crate::config::EthShadowConfig; use crate::utils::log_and_wait; use crate::Error; -use itertools::Itertools; use log::info; -use std::cmp::min; -use std::ffi::OsString; -use std::fs::read_dir; use std::path::{Path, PathBuf}; use std::process::Command; use users::get_current_uid; pub struct ValidatorManager { - validators: Vec, + image_name: String, + mnemonic: String, + output_path: PathBuf, + validator_count: usize, val_for_each_any: usize, remainder: usize, already_assigned: usize, + idx: usize, } impl ValidatorManager { pub fn new( @@ -59,31 +59,32 @@ impl ValidatorManager { validator_count = requested; }; - info!("Generating {validator_count} validators"); - let validators = generate( - config - .genesis - .generator_image - .as_deref() - .unwrap_or(DEFAULT_GENESIS_GEN_IMAGE), - output_path, - config - .genesis - .mnemonic - .as_deref() - .unwrap_or(DEFAULT_MNEMONIC), - validator_count, - )?; + let image_name = config + .genesis + .generator_image + .as_deref() + .unwrap_or(DEFAULT_GENESIS_GEN_IMAGE) + .into(); + let mnemonic = config + .genesis + .mnemonic + .as_deref() + .unwrap_or(DEFAULT_MNEMONIC) + .into(); Ok(ValidatorManager { - validators, + image_name, + mnemonic, + output_path: output_path.to_path_buf(), + validator_count, val_for_each_any, remainder, already_assigned: 0, + idx: 0, }) } - pub fn assign(&mut self, client: &dyn Client) -> &[Validator] { + pub fn assign(&mut self, client: &dyn Client) -> Result { let count = match client.validator_demand() { ValidatorDemand::None => 0, ValidatorDemand::Any => { @@ -96,36 +97,19 @@ impl ValidatorManager { } ValidatorDemand::Count(count) => count, }; + if count == 0 { + return Ok(ValidatorSet::default()); + } let start = self.already_assigned; let end = start + count; - self.already_assigned = end; - &self.validators[start..end] - } - - pub fn total_count(&self) -> usize { - self.validators.len() - } -} -fn generate( - image_name: &str, - output_path: &Path, - mnemonic: &str, - total_val: usize, -) -> Result, Error> { - const BATCH_SIZE: usize = 4000; - let mut validators = Vec::with_capacity(total_val); - let mut idx = 0; - let mut data_mount = output_path.as_os_str().to_owned(); - data_mount.push(":/data"); - while validators.len() < total_val { - if total_val > BATCH_SIZE { - info!( - "Generating validator batch {} of {}", - idx + 1, - total_val.div_ceil(BATCH_SIZE) - ); - } + let mut data_mount = self.output_path.as_os_str().to_owned(); + data_mount.push(":/data"); + info!( + "Generating validator of index from {} to {}", + start, + end - 1 + ); let status = log_and_wait( Command::new("docker") .args(["run", "--rm", "-i", "-u"]) @@ -133,44 +117,52 @@ fn generate( .arg("-v") .arg(&data_mount) .arg("--entrypoint=eth2-val-tools") - .arg(image_name) + .arg(&self.image_name) .arg("keystores") .arg("--insecure") .arg("--out-loc") - .arg(format!("/data/validator_keys_{idx}")) + .arg(format!("/data/validator_keys_{}", self.idx)) .arg("--source-mnemonic") - .arg(mnemonic) + .arg(&self.mnemonic) .arg("--source-min") - .arg(validators.len().to_string()) + .arg(start.to_string()) .arg("--source-max") - .arg(min(validators.len() + BATCH_SIZE, total_val).to_string()), + .arg(end.to_string()) + .arg("--prysm-pass") + .arg("12345678"), )?; if !status.success() { - return Err(Error::ChildProcessFailure(image_name.to_string())); - } - let base_path = output_path.join(format!("validator_keys_{idx}")); - for validator in read_dir(base_path.join("keys"))?.map_ok(|e| Validator { - base_path: base_path.clone(), - key: e.file_name(), - }) { - validators.push(validator?); + return Err(Error::ChildProcessFailure(self.image_name.to_string())); } - idx += 1; + self.already_assigned = end; + let base_path = self + .output_path + .join(format!("validator_keys_{}", self.idx)); + + self.idx += 1; + Ok(ValidatorSet { + base_path, + count: end - start, + }) + } + + pub fn total_count(&self) -> usize { + self.validator_count } - Ok(validators) } -pub struct Validator { +#[derive(Default)] +pub struct ValidatorSet { base_path: PathBuf, - key: OsString, + count: usize, } -impl Validator { +impl ValidatorSet { pub fn base_path(&self) -> &PathBuf { &self.base_path } - pub fn key(&self) -> &OsString { - &self.key + pub fn count(&self) -> usize { + self.count } }