Engine API v1.0.0.alpha.6 + interop tests (#3024)

## Issue Addressed

NA

## Proposed Changes

This PR extends #3018 to address my review comments there and add automated integration tests with Geth (and other implementations, in the future).

I've also de-duplicated the "unused port" logic by creating an  `common/unused_port` crate.

## Additional Info

I'm not sure if we want to merge this PR, or update #3018 and merge that. I don't mind, I'm primarily opening this PR to make sure CI works.


Co-authored-by: Mark Mackey <mark@sigmaprime.io>
This commit is contained in:
Paul Hauner
2022-02-17 21:47:06 +00:00
parent 2f8531dc60
commit 0a6a8ea3b0
40 changed files with 1125 additions and 363 deletions

View File

@@ -0,0 +1,131 @@
use crate::{genesis_json::geth_genesis_json, SUPPRESS_LOGS};
use sensitive_url::SensitiveUrl;
use std::path::PathBuf;
use std::process::{Child, Command, Output, Stdio};
use std::{env, fs::File};
use tempfile::TempDir;
use unused_port::unused_tcp_port;
/// Defined for each EE type (e.g., Geth, Nethermind, etc).
pub trait GenericExecutionEngine: Clone {
fn init_datadir() -> TempDir;
fn start_client(datadir: &TempDir, http_port: u16) -> Child;
}
/// Holds handle to a running EE process, plus some other metadata.
pub struct ExecutionEngine<E> {
#[allow(dead_code)]
engine: E,
#[allow(dead_code)]
datadir: TempDir,
http_port: u16,
child: Child,
}
impl<E> Drop for ExecutionEngine<E> {
fn drop(&mut self) {
// Ensure the EE process is killed on drop.
if let Err(e) = self.child.kill() {
eprintln!("failed to kill child: {:?}", e)
}
}
}
impl<E: GenericExecutionEngine> ExecutionEngine<E> {
pub fn new(engine: E) -> Self {
let datadir = E::init_datadir();
let http_port = unused_tcp_port().unwrap();
let child = E::start_client(&datadir, http_port);
Self {
engine,
datadir,
http_port,
child,
}
}
pub fn http_url(&self) -> SensitiveUrl {
SensitiveUrl::parse(&format!("http://127.0.0.1:{}", self.http_port)).unwrap()
}
}
/*
* 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) -> 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("--port")
.arg(network_port.to_string())
.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()
}
}