Add Nethermind integration tests (#3100)

## Proposed Changes

Extend the current Geth merge integration tests to support Nethermind.
This commit is contained in:
Mac L 2022-03-24 00:04:48 +00:00
parent 788b6af3c4
commit 3c675a9dfc
8 changed files with 395 additions and 165 deletions

View File

@ -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

View File

@ -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");
}
}

View File

@ -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()
}
}

View File

@ -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<E: GenericExecutionEngine> ExecutionEngine<E> {
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()
}
}

View File

@ -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"
}
}
})
}

View File

@ -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")
}
}

View File

@ -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();
}

View File

@ -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")
}
}