diff --git a/.github/workflows/test-suite.yml b/.github/workflows/test-suite.yml index f2be1d9b27..57ccbdaa14 100644 --- a/.github/workflows/test-suite.yml +++ b/.github/workflows/test-suite.yml @@ -213,6 +213,9 @@ jobs: - uses: actions/setup-go@v2 with: go-version: '1.17' + - uses: actions/setup-dotnet@v1 + with: + dotnet-version: '6.0.201' - name: Get latest version of stable Rust run: rustup update stable - name: Run exec engine integration tests in release diff --git a/testing/execution_engine_integration/src/build_geth.rs b/testing/execution_engine_integration/src/build_geth.rs deleted file mode 100644 index 772d3e3d85..0000000000 --- a/testing/execution_engine_integration/src/build_geth.rs +++ /dev/null @@ -1,66 +0,0 @@ -use std::env; -use std::fs; -use std::path::{Path, PathBuf}; -use std::process::Command; - -const GETH_BRANCH: &str = "merge-kiln-v2"; -const GETH_REPO_URL: &str = "https://github.com/MariusVanDerWijden/go-ethereum"; - -pub fn build() { - let manifest_dir: PathBuf = env::var("CARGO_MANIFEST_DIR").unwrap().into(); - let execution_clients_dir = manifest_dir.join("execution_clients"); - - if !execution_clients_dir.exists() { - fs::create_dir(&execution_clients_dir).unwrap(); - } - - build_geth(&execution_clients_dir); -} - -fn build_geth(execution_clients_dir: &Path) { - let repo_dir = execution_clients_dir.join("go-ethereum"); - - if !repo_dir.exists() { - // Clone the repo - assert!(Command::new("git") - .arg("clone") - .arg(GETH_REPO_URL) - .current_dir(&execution_clients_dir) - .output() - .expect("failed to clone geth repo") - .status - .success()); - } - - // Checkout the correct branch - assert!(Command::new("git") - .arg("checkout") - .arg(GETH_BRANCH) - .current_dir(&repo_dir) - .output() - .expect("failed to checkout geth branch") - .status - .success()); - - // Update the branch - assert!(Command::new("git") - .arg("pull") - .current_dir(&repo_dir) - .output() - .expect("failed to update geth branch") - .status - .success()); - - // Build geth - let make_result = Command::new("make") - .arg("geth") - .current_dir(&repo_dir) - .output() - .expect("failed to make geth"); - - if !make_result.status.success() { - dbg!(String::from_utf8_lossy(&make_result.stdout)); - dbg!(String::from_utf8_lossy(&make_result.stderr)); - panic!("make failed"); - } -} diff --git a/testing/execution_engine_integration/src/build_utils.rs b/testing/execution_engine_integration/src/build_utils.rs new file mode 100644 index 0000000000..4d4a7bf1ce --- /dev/null +++ b/testing/execution_engine_integration/src/build_utils.rs @@ -0,0 +1,73 @@ +use crate::SUPPRESS_LOGS; +use std::env; +use std::fs; +use std::path::{Path, PathBuf}; +use std::process::{Command, Output, Stdio}; + +pub fn prepare_dir() -> PathBuf { + let manifest_dir: PathBuf = env::var("CARGO_MANIFEST_DIR").unwrap().into(); + let execution_clients_dir = manifest_dir.join("execution_clients"); + + if !execution_clients_dir.exists() { + fs::create_dir(&execution_clients_dir).unwrap(); + } + + execution_clients_dir +} + +pub fn clone_repo(repo_dir: &Path, repo_url: &str) -> bool { + Command::new("git") + .arg("clone") + .arg(repo_url) + .arg("--recursive") + .current_dir(repo_dir) + .output() + .unwrap_or_else(|_| panic!("failed to clone repo at {}", repo_url)) + .status + .success() +} + +pub fn checkout_branch(repo_dir: &Path, branch_name: &str) -> bool { + Command::new("git") + .arg("checkout") + .arg(branch_name) + .current_dir(repo_dir) + .output() + .unwrap_or_else(|_| { + panic!( + "failed to checkout branch at {:?}/{}", + repo_dir, branch_name, + ) + }) + .status + .success() +} + +pub fn update_branch(repo_dir: &Path, branch_name: &str) -> bool { + Command::new("git") + .arg("pull") + .current_dir(repo_dir) + .output() + .unwrap_or_else(|_| panic!("failed to update branch at {:?}/{}", repo_dir, branch_name)) + .status + .success() +} + +pub fn check_command_output(output: Output, failure_msg: &'static str) { + if !output.status.success() { + if !SUPPRESS_LOGS { + dbg!(String::from_utf8_lossy(&output.stdout)); + dbg!(String::from_utf8_lossy(&output.stderr)); + } + panic!("{}", failure_msg); + } +} + +/// Builds the stdout/stderr handler for commands which might output to the terminal. +pub fn build_stdio() -> Stdio { + if SUPPRESS_LOGS { + Stdio::null() + } else { + Stdio::inherit() + } +} diff --git a/testing/execution_engine_integration/src/execution_engine.rs b/testing/execution_engine_integration/src/execution_engine.rs index a7928f0866..dd5d03be89 100644 --- a/testing/execution_engine_integration/src/execution_engine.rs +++ b/testing/execution_engine_integration/src/execution_engine.rs @@ -1,9 +1,7 @@ -use crate::{genesis_json::geth_genesis_json, SUPPRESS_LOGS}; use execution_layer::DEFAULT_JWT_FILE; use sensitive_url::SensitiveUrl; use std::path::PathBuf; -use std::process::{Child, Command, Output, Stdio}; -use std::{env, fs::File}; +use std::process::Child; use tempfile::TempDir; use unused_port::unused_tcp_port; @@ -66,93 +64,3 @@ impl ExecutionEngine { self.datadir.path().to_path_buf() } } - -/* - * Geth-specific Implementation - */ - -#[derive(Clone)] -pub struct Geth; - -impl Geth { - fn binary_path() -> PathBuf { - let manifest_dir: PathBuf = env::var("CARGO_MANIFEST_DIR").unwrap().into(); - manifest_dir - .join("execution_clients") - .join("go-ethereum") - .join("build") - .join("bin") - .join("geth") - } -} - -impl GenericExecutionEngine for Geth { - fn init_datadir() -> TempDir { - let datadir = TempDir::new().unwrap(); - - let genesis_json_path = datadir.path().join("genesis.json"); - let mut file = File::create(&genesis_json_path).unwrap(); - let json = geth_genesis_json(); - serde_json::to_writer(&mut file, &json).unwrap(); - - let output = Command::new(Self::binary_path()) - .arg("--datadir") - .arg(datadir.path().to_str().unwrap()) - .arg("init") - .arg(genesis_json_path.to_str().unwrap()) - .output() - .expect("failed to init geth"); - - check_command_output(output, "geth init failed"); - - datadir - } - - fn start_client( - datadir: &TempDir, - http_port: u16, - http_auth_port: u16, - jwt_secret_path: PathBuf, - ) -> Child { - let network_port = unused_tcp_port().unwrap(); - - Command::new(Self::binary_path()) - .arg("--datadir") - .arg(datadir.path().to_str().unwrap()) - .arg("--http") - .arg("--http.api") - .arg("engine,eth") - .arg("--http.port") - .arg(http_port.to_string()) - .arg("--authrpc.port") - .arg(http_auth_port.to_string()) - .arg("--port") - .arg(network_port.to_string()) - .arg("--authrpc.jwtsecret") - .arg(jwt_secret_path.as_path().to_str().unwrap()) - .stdout(build_stdio()) - .stderr(build_stdio()) - .spawn() - .expect("failed to start beacon node") - } -} - -fn check_command_output(output: Output, failure_msg: &'static str) { - if !output.status.success() { - let stdout = String::from_utf8_lossy(&output.stdout); - let stderr = String::from_utf8_lossy(&output.stderr); - - dbg!(stdout); - dbg!(stderr); - panic!("{}", failure_msg); - } -} - -/// Builds the stdout/stderr handler for commands which might output to the terminal. -fn build_stdio() -> Stdio { - if SUPPRESS_LOGS { - Stdio::null() - } else { - Stdio::inherit() - } -} diff --git a/testing/execution_engine_integration/src/genesis_json.rs b/testing/execution_engine_integration/src/genesis_json.rs index 87fdaec14a..0de56fba3c 100644 --- a/testing/execution_engine_integration/src/genesis_json.rs +++ b/testing/execution_engine_integration/src/genesis_json.rs @@ -40,3 +40,77 @@ pub fn geth_genesis_json() -> Value { "baseFeePerGas":"0x7" }) } + +/// Sourced from: +/// +/// https://github.com/NethermindEth/nethermind/blob/themerge_kintsugi/src/Nethermind/Chains/themerge_kintsugi_m2.json +pub fn nethermind_genesis_json() -> Value { + json!({ + "name": "TheMerge_Devnet", + "engine": { + "clique": { + "params": { + "period": 5, + "epoch": 30000 + } + } + }, + "params": { + "gasLimitBoundDivisor": "0x400", + "accountStartNonce": "0x0", + "maximumExtraDataSize": "0x20", + "minGasLimit": "0x1388", + "networkID": 1, + "eip150Transition": "0x0", + "eip155Transition": "0x0", + "eip158Transition": "0x0", + "eip160Transition": "0x0", + "eip161abcTransition": "0x0", + "eip161dTransition": "0x0", + "eip140Transition": "0x0", + "eip211Transition": "0x0", + "eip214Transition": "0x0", + "eip658Transition": "0x0", + "eip145Transition": "0x0", + "eip1014Transition": "0x0", + "eip1052Transition": "0x0", + "eip1283Transition": "0x0", + "eip1283DisableTransition": "0x0", + "eip152Transition": "0x0", + "eip1108Transition": "0x0", + "eip1344Transition": "0x0", + "eip1884Transition": "0x0", + "eip2028Transition": "0x0", + "eip2200Transition": "0x0", + "eip2565Transition": "0x0", + "eip2929Transition": "0x0", + "eip2930Transition": "0x0", + "eip1559Transition": "0x0", + "eip3198Transition": "0x0", + "eip3529Transition": "0x0", + "eip3541Transition": "0x0" + }, + "genesis": { + "seal": { + "ethereum": { + "nonce": "0x42", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000" + } + }, + "difficulty": "0x000000000", + "author": "0x0000000000000000000000000000000000000000", + "timestamp": "0x0", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "extraData":"0x0000000000000000000000000000000000000000000000000000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "gasLimit":"0x1C9C380", + "author": "0x0000000000000000000000000000000000000000", + "parentHash":"0x0000000000000000000000000000000000000000000000000000000000000000", + "baseFeePerGas":"0x7" + }, + "accounts": { + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "balance":"0x6d6172697573766477000000" + } + } + }) +} diff --git a/testing/execution_engine_integration/src/geth.rs b/testing/execution_engine_integration/src/geth.rs new file mode 100644 index 0000000000..bc07cea343 --- /dev/null +++ b/testing/execution_engine_integration/src/geth.rs @@ -0,0 +1,110 @@ +use crate::build_utils; +use crate::execution_engine::GenericExecutionEngine; +use crate::genesis_json::geth_genesis_json; +use std::path::{Path, PathBuf}; +use std::process::{Child, Command, Output}; +use std::{env, fs::File}; +use tempfile::TempDir; +use unused_port::unused_tcp_port; + +const GETH_BRANCH: &str = "merge-kiln-v2"; +const GETH_REPO_URL: &str = "https://github.com/MariusVanDerWijden/go-ethereum"; + +pub fn build_result(repo_dir: &Path) -> Output { + Command::new("make") + .arg("geth") + .current_dir(&repo_dir) + .output() + .expect("failed to make geth") +} + +pub fn build(execution_clients_dir: &Path) { + let repo_dir = execution_clients_dir.join("go-ethereum"); + + if !repo_dir.exists() { + // Clone the repo + assert!(build_utils::clone_repo( + execution_clients_dir, + GETH_REPO_URL + )); + } + + // Checkout the correct branch + assert!(build_utils::checkout_branch(&repo_dir, GETH_BRANCH)); + + // Update the branch + assert!(build_utils::update_branch(&repo_dir, GETH_BRANCH)); + + // Build geth + build_utils::check_command_output(build_result(&repo_dir), "make failed"); +} + +/* + * Geth-specific Implementation for GenericExecutionEngine + */ + +#[derive(Clone)] +pub struct GethEngine; + +impl GethEngine { + fn binary_path() -> PathBuf { + let manifest_dir: PathBuf = env::var("CARGO_MANIFEST_DIR").unwrap().into(); + manifest_dir + .join("execution_clients") + .join("go-ethereum") + .join("build") + .join("bin") + .join("geth") + } +} + +impl GenericExecutionEngine for GethEngine { + fn init_datadir() -> TempDir { + let datadir = TempDir::new().unwrap(); + + let genesis_json_path = datadir.path().join("genesis.json"); + let mut file = File::create(&genesis_json_path).unwrap(); + let json = geth_genesis_json(); + serde_json::to_writer(&mut file, &json).unwrap(); + + let output = Command::new(Self::binary_path()) + .arg("--datadir") + .arg(datadir.path().to_str().unwrap()) + .arg("init") + .arg(genesis_json_path.to_str().unwrap()) + .output() + .expect("failed to init geth"); + + build_utils::check_command_output(output, "geth init failed"); + + datadir + } + + fn start_client( + datadir: &TempDir, + http_port: u16, + http_auth_port: u16, + jwt_secret_path: PathBuf, + ) -> Child { + let network_port = unused_tcp_port().unwrap(); + + Command::new(Self::binary_path()) + .arg("--datadir") + .arg(datadir.path().to_str().unwrap()) + .arg("--http") + .arg("--http.api") + .arg("engine,eth") + .arg("--http.port") + .arg(http_port.to_string()) + .arg("--authrpc.port") + .arg(http_auth_port.to_string()) + .arg("--port") + .arg(network_port.to_string()) + .arg("--authrpc.jwtsecret") + .arg(jwt_secret_path.as_path().to_str().unwrap()) + .stdout(build_utils::build_stdio()) + .stderr(build_utils::build_stdio()) + .spawn() + .expect("failed to start geth") + } +} diff --git a/testing/execution_engine_integration/src/main.rs b/testing/execution_engine_integration/src/main.rs index ef9cbd7948..30c8132b7c 100644 --- a/testing/execution_engine_integration/src/main.rs +++ b/testing/execution_engine_integration/src/main.rs @@ -3,26 +3,37 @@ /// It will first attempt to build any supported integration clients, then it will run tests. /// /// A return code of `0` indicates the tests succeeded. -mod build_geth; +mod build_utils; mod execution_engine; mod genesis_json; +mod geth; +mod nethermind; mod test_rig; -use execution_engine::Geth; +use geth::GethEngine; +use nethermind::NethermindEngine; use test_rig::TestRig; /// Set to `false` to send logs to the console during tests. Logs are useful when debugging. -const SUPPRESS_LOGS: bool = false; +const SUPPRESS_LOGS: bool = true; fn main() { if cfg!(windows) { panic!("windows is not supported, only linux"); } - test_geth() + test_geth(); + test_nethermind(); } fn test_geth() { - build_geth::build(); - TestRig::new(Geth).perform_tests_blocking(); + let test_dir = build_utils::prepare_dir(); + geth::build(&test_dir); + TestRig::new(GethEngine).perform_tests_blocking(); +} + +fn test_nethermind() { + let test_dir = build_utils::prepare_dir(); + nethermind::build(&test_dir); + TestRig::new(NethermindEngine).perform_tests_blocking(); } diff --git a/testing/execution_engine_integration/src/nethermind.rs b/testing/execution_engine_integration/src/nethermind.rs new file mode 100644 index 0000000000..833409c69e --- /dev/null +++ b/testing/execution_engine_integration/src/nethermind.rs @@ -0,0 +1,117 @@ +use crate::build_utils; +use crate::execution_engine::GenericExecutionEngine; +use crate::genesis_json::nethermind_genesis_json; +use std::path::{Path, PathBuf}; +use std::process::{Child, Command, Output}; +use std::{env, fs::File}; +use tempfile::TempDir; +use unused_port::unused_tcp_port; + +const NETHERMIND_BRANCH: &str = "kiln"; +const NETHERMIND_REPO_URL: &str = "https://github.com/NethermindEth/nethermind"; + +fn build_result(repo_dir: &Path) -> Output { + Command::new("dotnet") + .arg("build") + .arg("src/Nethermind/Nethermind.sln") + .arg("-c") + .arg("Release") + .current_dir(repo_dir) + .output() + .expect("failed to make nethermind") +} + +pub fn build(execution_clients_dir: &Path) { + let repo_dir = execution_clients_dir.join("nethermind"); + + if !repo_dir.exists() { + // Clone the repo + assert!(build_utils::clone_repo( + execution_clients_dir, + NETHERMIND_REPO_URL + )); + } + + // Checkout the correct branch + assert!(build_utils::checkout_branch(&repo_dir, NETHERMIND_BRANCH)); + + // Update the branch + assert!(build_utils::update_branch(&repo_dir, NETHERMIND_BRANCH)); + + // Build nethermind + build_utils::check_command_output(build_result(&repo_dir), "dotnet build failed"); + + // Build nethermind a second time to enable Merge-related features. + // Not sure why this is necessary. + build_utils::check_command_output(build_result(&repo_dir), "dotnet build failed"); +} + +/* + * Nethermind-specific Implementation for GenericExecutionEngine + */ + +#[derive(Clone)] +pub struct NethermindEngine; + +impl NethermindEngine { + fn binary_path() -> PathBuf { + let manifest_dir: PathBuf = env::var("CARGO_MANIFEST_DIR").unwrap().into(); + manifest_dir + .join("execution_clients") + .join("nethermind") + .join("src") + .join("Nethermind") + .join("Nethermind.Runner") + .join("bin") + .join("Release") + .join("net6.0") + .join("Nethermind.Runner") + } +} + +impl GenericExecutionEngine for NethermindEngine { + fn init_datadir() -> TempDir { + let datadir = TempDir::new().unwrap(); + + let genesis_json_path = datadir.path().join("genesis.json"); + let mut file = File::create(&genesis_json_path).unwrap(); + let json = nethermind_genesis_json(); + serde_json::to_writer(&mut file, &json).unwrap(); + + datadir + } + + fn start_client( + datadir: &TempDir, + http_port: u16, + http_auth_port: u16, + jwt_secret_path: PathBuf, + ) -> Child { + let network_port = unused_tcp_port().unwrap(); + let genesis_json_path = datadir.path().join("genesis.json"); + + Command::new(Self::binary_path()) + .arg("--datadir") + .arg(datadir.path().to_str().unwrap()) + .arg("--config") + .arg("themerge_kiln_testvectors") + .arg("--Init.ChainSpecPath") + .arg(genesis_json_path.to_str().unwrap()) + .arg("--JsonRpc.AdditionalRpcUrls") + .arg(format!("http://localhost:{}|http;ws|net;eth;subscribe;engine;web3;client|no-auth,http://localhost:{}|http;ws|net;eth;subscribe;engine;web3;client", http_port, http_auth_port)) + .arg("--JsonRpc.EnabledModules") + .arg("net,eth,subscribe,web3,admin,engine") + .arg("--JsonRpc.Port") + .arg(http_port.to_string()) + .arg("--Network.DiscoveryPort") + .arg(network_port.to_string()) + .arg("--Network.P2PPort") + .arg(network_port.to_string()) + .arg("--JsonRpc.JwtSecretFile") + .arg(jwt_secret_path.as_path().to_str().unwrap()) + .stdout(build_utils::build_stdio()) + .stderr(build_utils::build_stdio()) + .spawn() + .expect("failed to start nethermind") + } +}