mirror of
https://github.com/sigp/lighthouse.git
synced 2026-05-08 09:16:00 +00:00
Add tests for validator create
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -3626,6 +3626,7 @@ dependencies = [
|
|||||||
"env_logger 0.9.0",
|
"env_logger 0.9.0",
|
||||||
"environment",
|
"environment",
|
||||||
"eth1",
|
"eth1",
|
||||||
|
"eth2",
|
||||||
"eth2_hashing",
|
"eth2_hashing",
|
||||||
"eth2_network_config",
|
"eth2_network_config",
|
||||||
"futures",
|
"futures",
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ slashing_protection = { path = "../validator_client/slashing_protection" }
|
|||||||
lighthouse_network = { path = "../beacon_node/lighthouse_network" }
|
lighthouse_network = { path = "../beacon_node/lighthouse_network" }
|
||||||
sensitive_url = { path = "../common/sensitive_url" }
|
sensitive_url = { path = "../common/sensitive_url" }
|
||||||
eth1 = { path = "../beacon_node/eth1" }
|
eth1 = { path = "../beacon_node/eth1" }
|
||||||
|
eth2 = { path = "../common/eth2" }
|
||||||
|
|
||||||
[[test]]
|
[[test]]
|
||||||
name = "lighthouse_tests"
|
name = "lighthouse_tests"
|
||||||
|
|||||||
@@ -1,15 +1,20 @@
|
|||||||
|
use eth2::SensitiveUrl;
|
||||||
use serde::de::DeserializeOwned;
|
use serde::de::DeserializeOwned;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::process::Command;
|
use std::process::{Command, Stdio};
|
||||||
|
use std::str::FromStr;
|
||||||
use tempfile::{tempdir, TempDir};
|
use tempfile::{tempdir, TempDir};
|
||||||
|
use types::*;
|
||||||
use validator_manager::validators::create_validators::CreateConfig;
|
use validator_manager::validators::create_validators::CreateConfig;
|
||||||
|
|
||||||
|
const EXAMPLE_ETH1_ADDRESS: &str = "0x00000000219ab540356cBB839Cbe05303d7705Fa";
|
||||||
|
|
||||||
struct CommandLineTest<T> {
|
struct CommandLineTest<T> {
|
||||||
cmd: Command,
|
cmd: Command,
|
||||||
dir: TempDir,
|
|
||||||
config_path: PathBuf,
|
config_path: PathBuf,
|
||||||
|
_dir: TempDir,
|
||||||
_phantom: PhantomData<T>,
|
_phantom: PhantomData<T>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -18,13 +23,16 @@ impl<T> Default for CommandLineTest<T> {
|
|||||||
let dir = tempdir().unwrap();
|
let dir = tempdir().unwrap();
|
||||||
let config_path = dir.path().join("config.json");
|
let config_path = dir.path().join("config.json");
|
||||||
let mut cmd = Command::new(env!("CARGO_BIN_EXE_lighthouse"));
|
let mut cmd = Command::new(env!("CARGO_BIN_EXE_lighthouse"));
|
||||||
cmd.arg("--dump_config")
|
cmd.arg("--dump-config")
|
||||||
.arg(format!("{:?}", config_path))
|
.arg(config_path.as_os_str())
|
||||||
.arg("validator-manager");
|
.arg("validator-manager")
|
||||||
|
.stdin(Stdio::null())
|
||||||
|
.stdout(Stdio::null())
|
||||||
|
.stderr(Stdio::null());
|
||||||
Self {
|
Self {
|
||||||
cmd,
|
cmd,
|
||||||
dir,
|
|
||||||
config_path,
|
config_path,
|
||||||
|
_dir: dir,
|
||||||
_phantom: PhantomData,
|
_phantom: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -38,26 +46,115 @@ impl<T> CommandLineTest<T> {
|
|||||||
}
|
}
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn run(mut cmd: Command, should_succeed: bool) {
|
||||||
|
let output = cmd.output().expect("process should complete");
|
||||||
|
if output.status.success() != should_succeed {
|
||||||
|
let stdout = String::from_utf8(output.stdout).unwrap();
|
||||||
|
let stderr = String::from_utf8(output.stderr).unwrap();
|
||||||
|
eprintln!("{}", stdout);
|
||||||
|
eprintln!("{}", stderr);
|
||||||
|
panic!(
|
||||||
|
"Command success was {} when expecting {}",
|
||||||
|
!should_succeed, should_succeed
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: DeserializeOwned> CommandLineTest<T> {
|
impl<T: DeserializeOwned> CommandLineTest<T> {
|
||||||
fn assert_success<F: Fn(T)>(mut self, func: F) {
|
fn assert_success<F: Fn(T)>(self, func: F) {
|
||||||
let output = self.cmd.output().expect("should run command");
|
Self::run(self.cmd, true);
|
||||||
assert!(output.status.success(), "command should succeed");
|
|
||||||
|
|
||||||
let contents = fs::read_to_string(self.config_path).unwrap();
|
let contents = fs::read_to_string(self.config_path).unwrap();
|
||||||
let config: T = serde_json::from_str(&contents).unwrap();
|
let config: T = serde_json::from_str(&contents).unwrap();
|
||||||
func(config)
|
func(config)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn assert_failed(self) {
|
||||||
|
Self::run(self.cmd, false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CommandLineTest<CreateConfig> {
|
impl CommandLineTest<CreateConfig> {
|
||||||
fn validator_create() -> Self {
|
fn validator_create() -> Self {
|
||||||
Self::default().flag("validator", None).flag("create", None)
|
Self::default()
|
||||||
|
.flag("validators", None)
|
||||||
|
.flag("create", None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
pub fn validator_create_defaults() {
|
pub fn validator_create_without_output_path() {
|
||||||
todo!()
|
CommandLineTest::validator_create().assert_failed();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
pub fn validator_create_defaults() {
|
||||||
|
CommandLineTest::validator_create()
|
||||||
|
.flag("--output-path", Some("./meow"))
|
||||||
|
.flag("--count", Some("1"))
|
||||||
|
.assert_success(|config| {
|
||||||
|
let expected = CreateConfig {
|
||||||
|
output_path: PathBuf::from("./meow"),
|
||||||
|
first_index: 0,
|
||||||
|
count: 1,
|
||||||
|
deposit_gwei: MainnetEthSpec::default_spec().max_effective_balance,
|
||||||
|
mnemonic_path: None,
|
||||||
|
stdin_inputs: false,
|
||||||
|
disable_deposits: false,
|
||||||
|
specify_voting_keystore_password: false,
|
||||||
|
eth1_withdrawal_address: None,
|
||||||
|
builder_proposals: false,
|
||||||
|
fee_recipient: None,
|
||||||
|
gas_limit: None,
|
||||||
|
bn_url: None,
|
||||||
|
};
|
||||||
|
assert_eq!(expected, config);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
pub fn validator_create_misc_flags_01() {
|
||||||
|
CommandLineTest::validator_create()
|
||||||
|
.flag("--output-path", Some("./meow"))
|
||||||
|
.flag("--deposit-gwei", Some("42"))
|
||||||
|
.flag("--first-index", Some("12"))
|
||||||
|
.flag("--count", Some("9"))
|
||||||
|
.flag("--mnemonic-path", Some("./woof"))
|
||||||
|
.flag("--stdin-inputs", None)
|
||||||
|
.flag("--specify-voting-keystore-password", None)
|
||||||
|
.flag("--eth1-withdrawal-address", Some(EXAMPLE_ETH1_ADDRESS))
|
||||||
|
.flag("--builder-proposals", None)
|
||||||
|
.flag("--suggested-fee-recipient", Some(EXAMPLE_ETH1_ADDRESS))
|
||||||
|
.flag("--gas-limit", Some("1337"))
|
||||||
|
.flag("--beacon-node", Some("http://localhost:1001"))
|
||||||
|
.assert_success(|config| {
|
||||||
|
let expected = CreateConfig {
|
||||||
|
output_path: PathBuf::from("./meow"),
|
||||||
|
first_index: 12,
|
||||||
|
count: 9,
|
||||||
|
deposit_gwei: 42,
|
||||||
|
mnemonic_path: Some(PathBuf::from("./woof")),
|
||||||
|
stdin_inputs: true,
|
||||||
|
disable_deposits: false,
|
||||||
|
specify_voting_keystore_password: true,
|
||||||
|
eth1_withdrawal_address: Some(Address::from_str(EXAMPLE_ETH1_ADDRESS).unwrap()),
|
||||||
|
builder_proposals: true,
|
||||||
|
fee_recipient: Some(Address::from_str(EXAMPLE_ETH1_ADDRESS).unwrap()),
|
||||||
|
gas_limit: Some(1337),
|
||||||
|
bn_url: Some(SensitiveUrl::parse("http://localhost:1001").unwrap()),
|
||||||
|
};
|
||||||
|
assert_eq!(expected, config);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
pub fn validator_create_disable_deposits() {
|
||||||
|
CommandLineTest::validator_create()
|
||||||
|
.flag("--output-path", Some("./meow"))
|
||||||
|
.flag("--count", Some("1"))
|
||||||
|
.flag("--disable-deposits", None)
|
||||||
|
.assert_success(|config| {
|
||||||
|
assert_eq!(config.disable_deposits, true);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,29 +11,33 @@ pub mod validators;
|
|||||||
pub const CMD: &str = "validator_manager";
|
pub const CMD: &str = "validator_manager";
|
||||||
|
|
||||||
/// This flag is on the top-level `lighthouse` binary.
|
/// This flag is on the top-level `lighthouse` binary.
|
||||||
const DUMP_CONFIGS_FLAG: &str = "dump-configs";
|
const DUMP_CONFIGS_FLAG: &str = "dump-config";
|
||||||
|
|
||||||
/// Used only in testing, this allows a command to dump its configuration to a file and then exit
|
/// Used only in testing, this allows a command to dump its configuration to a file and then exit
|
||||||
/// successfully. This allows for testing how the CLI arguments translate to some configuration.
|
/// successfully. This allows for testing how the CLI arguments translate to some configuration.
|
||||||
pub enum DumpConfigs {
|
pub enum DumpConfig {
|
||||||
Disabled,
|
Disabled,
|
||||||
Enabled(PathBuf),
|
Enabled(PathBuf),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DumpConfigs {
|
impl DumpConfig {
|
||||||
/// Returns `Ok(true)` if the configuration was successfully written to a file and the
|
/// Returns `Ok(true)` if the configuration was successfully written to a file and the
|
||||||
/// application should exit successfully without doing anything else.
|
/// application should exit successfully without doing anything else.
|
||||||
pub fn should_exit_early<T: Serialize>(&self, config: &T) -> Result<bool, String> {
|
pub fn should_exit_early<T: Serialize>(&self, config: &T) -> Result<bool, String> {
|
||||||
match self {
|
match self {
|
||||||
DumpConfigs::Disabled => Ok(false),
|
DumpConfig::Disabled => Ok(false),
|
||||||
DumpConfigs::Enabled(dump_path) => write_to_json_file(dump_path, config).map(|()| true),
|
DumpConfig::Enabled(dump_path) => {
|
||||||
|
dbg!(dump_path);
|
||||||
|
write_to_json_file(dump_path, config)?;
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
|
pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
|
||||||
App::new(CMD)
|
App::new(CMD)
|
||||||
.visible_aliases(&["vm", CMD])
|
.visible_aliases(&["vm", "validator-manager", CMD])
|
||||||
.about("Utilities for managing a Lighthouse validator client via the HTTP API.")
|
.about("Utilities for managing a Lighthouse validator client via the HTTP API.")
|
||||||
.subcommand(validators::cli_app())
|
.subcommand(validators::cli_app())
|
||||||
}
|
}
|
||||||
@@ -45,9 +49,9 @@ pub fn run<'a, T: EthSpec>(
|
|||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let context = env.core_context();
|
let context = env.core_context();
|
||||||
let spec = context.eth2_config.spec.clone();
|
let spec = context.eth2_config.spec.clone();
|
||||||
let dump_configs = clap_utils::parse_optional(matches, DUMP_CONFIGS_FLAG)?
|
let dump_config = clap_utils::parse_optional(matches, DUMP_CONFIGS_FLAG)?
|
||||||
.map(DumpConfigs::Enabled)
|
.map(DumpConfig::Enabled)
|
||||||
.unwrap_or_else(|| DumpConfigs::Disabled);
|
.unwrap_or_else(|| DumpConfig::Disabled);
|
||||||
|
|
||||||
context
|
context
|
||||||
.executor
|
.executor
|
||||||
@@ -58,7 +62,7 @@ pub fn run<'a, T: EthSpec>(
|
|||||||
async {
|
async {
|
||||||
match matches.subcommand() {
|
match matches.subcommand() {
|
||||||
(validators::CMD, Some(matches)) => {
|
(validators::CMD, Some(matches)) => {
|
||||||
validators::cli_run::<T>(matches, &spec, dump_configs).await
|
validators::cli_run::<T>(matches, &spec, dump_config).await
|
||||||
}
|
}
|
||||||
(unknown, _) => Err(format!(
|
(unknown, _) => Err(format!(
|
||||||
"{} is not a valid {} command. See --help.",
|
"{} is not a valid {} command. See --help.",
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use super::common::*;
|
use super::common::*;
|
||||||
use crate::DumpConfigs;
|
use crate::DumpConfig;
|
||||||
use account_utils::{random_password_string, read_mnemonic_from_cli, read_password_from_user};
|
use account_utils::{random_password_string, read_mnemonic_from_cli, read_password_from_user};
|
||||||
use clap::{App, Arg, ArgMatches};
|
use clap::{App, Arg, ArgMatches};
|
||||||
use eth2::{
|
use eth2::{
|
||||||
@@ -51,7 +51,6 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
|
|||||||
"The path to a directory where the validator and (optionally) deposits \
|
"The path to a directory where the validator and (optionally) deposits \
|
||||||
files will be created. The directory will be created if it does not exist.",
|
files will be created. The directory will be created if it does not exist.",
|
||||||
)
|
)
|
||||||
.conflicts_with(DISABLE_DEPOSITS_FLAG)
|
|
||||||
.required(true)
|
.required(true)
|
||||||
.takes_value(true),
|
.takes_value(true),
|
||||||
)
|
)
|
||||||
@@ -100,7 +99,6 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
|
|||||||
.arg(
|
.arg(
|
||||||
Arg::with_name(DISABLE_DEPOSITS_FLAG)
|
Arg::with_name(DISABLE_DEPOSITS_FLAG)
|
||||||
.long(DISABLE_DEPOSITS_FLAG)
|
.long(DISABLE_DEPOSITS_FLAG)
|
||||||
.value_name("PATH")
|
|
||||||
.help(
|
.help(
|
||||||
"When provided don't generate the deposits JSON file that is \
|
"When provided don't generate the deposits JSON file that is \
|
||||||
commonly used for submitting validator deposits via a web UI. \
|
commonly used for submitting validator deposits via a web UI. \
|
||||||
@@ -111,8 +109,6 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
|
|||||||
.arg(
|
.arg(
|
||||||
Arg::with_name(SPECIFY_VOTING_KEYSTORE_PASSWORD_FLAG)
|
Arg::with_name(SPECIFY_VOTING_KEYSTORE_PASSWORD_FLAG)
|
||||||
.long(SPECIFY_VOTING_KEYSTORE_PASSWORD_FLAG)
|
.long(SPECIFY_VOTING_KEYSTORE_PASSWORD_FLAG)
|
||||||
.value_name("STRING")
|
|
||||||
.takes_value(true)
|
|
||||||
.help(
|
.help(
|
||||||
"If present, the user will be prompted to enter the voting keystore \
|
"If present, the user will be prompted to enter the voting keystore \
|
||||||
password that will be used to encrypt the voting keystores. If this \
|
password that will be used to encrypt the voting keystores. If this \
|
||||||
@@ -181,7 +177,7 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
|
|||||||
|
|
||||||
/// The CLI arguments are parsed into this struct before running the application. This step of
|
/// The CLI arguments are parsed into this struct before running the application. This step of
|
||||||
/// indirection allows for testing the underlying logic without needing to parse CLI arguments.
|
/// indirection allows for testing the underlying logic without needing to parse CLI arguments.
|
||||||
#[derive(Clone, PartialEq, Serialize, Deserialize)]
|
#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
|
||||||
pub struct CreateConfig {
|
pub struct CreateConfig {
|
||||||
pub output_path: PathBuf,
|
pub output_path: PathBuf,
|
||||||
pub first_index: u32,
|
pub first_index: u32,
|
||||||
@@ -461,10 +457,10 @@ impl ValidatorsAndDeposits {
|
|||||||
pub async fn cli_run<'a, T: EthSpec>(
|
pub async fn cli_run<'a, T: EthSpec>(
|
||||||
matches: &'a ArgMatches<'a>,
|
matches: &'a ArgMatches<'a>,
|
||||||
spec: &ChainSpec,
|
spec: &ChainSpec,
|
||||||
dump_configs: DumpConfigs,
|
dump_config: DumpConfig,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let config = CreateConfig::from_cli(matches, spec)?;
|
let config = CreateConfig::from_cli(matches, spec)?;
|
||||||
if dump_configs.should_exit_early(&config)? {
|
if dump_config.should_exit_early(&config)? {
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
run::<T>(config, spec).await
|
run::<T>(config, spec).await
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use super::common::*;
|
use super::common::*;
|
||||||
use crate::DumpConfigs;
|
use crate::DumpConfig;
|
||||||
use clap::{App, Arg, ArgMatches};
|
use clap::{App, Arg, ArgMatches};
|
||||||
use eth2::{
|
use eth2::{
|
||||||
lighthouse_vc::{
|
lighthouse_vc::{
|
||||||
@@ -95,10 +95,10 @@ impl ImportConfig {
|
|||||||
|
|
||||||
pub async fn cli_run<'a>(
|
pub async fn cli_run<'a>(
|
||||||
matches: &'a ArgMatches<'a>,
|
matches: &'a ArgMatches<'a>,
|
||||||
dump_configs: DumpConfigs,
|
dump_config: DumpConfig,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let config = ImportConfig::from_cli(matches)?;
|
let config = ImportConfig::from_cli(matches)?;
|
||||||
if dump_configs.should_exit_early(&config)? {
|
if dump_config.should_exit_early(&config)? {
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
run(config).await
|
run(config).await
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ pub mod common;
|
|||||||
pub mod create_validators;
|
pub mod create_validators;
|
||||||
pub mod import_validators;
|
pub mod import_validators;
|
||||||
|
|
||||||
use crate::DumpConfigs;
|
use crate::DumpConfig;
|
||||||
use clap::{App, ArgMatches};
|
use clap::{App, ArgMatches};
|
||||||
use types::{ChainSpec, EthSpec};
|
use types::{ChainSpec, EthSpec};
|
||||||
|
|
||||||
@@ -18,14 +18,14 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
|
|||||||
pub async fn cli_run<'a, T: EthSpec>(
|
pub async fn cli_run<'a, T: EthSpec>(
|
||||||
matches: &'a ArgMatches<'a>,
|
matches: &'a ArgMatches<'a>,
|
||||||
spec: &ChainSpec,
|
spec: &ChainSpec,
|
||||||
dump_configs: DumpConfigs,
|
dump_config: DumpConfig,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
match matches.subcommand() {
|
match matches.subcommand() {
|
||||||
(create_validators::CMD, Some(matches)) => {
|
(create_validators::CMD, Some(matches)) => {
|
||||||
create_validators::cli_run::<T>(matches, spec, dump_configs).await
|
create_validators::cli_run::<T>(matches, spec, dump_config).await
|
||||||
}
|
}
|
||||||
(import_validators::CMD, Some(matches)) => {
|
(import_validators::CMD, Some(matches)) => {
|
||||||
import_validators::cli_run(matches, dump_configs).await
|
import_validators::cli_run(matches, dump_config).await
|
||||||
}
|
}
|
||||||
(unknown, _) => Err(format!(
|
(unknown, _) => Err(format!(
|
||||||
"{} does not have a {} command. See --help",
|
"{} does not have a {} command. See --help",
|
||||||
|
|||||||
Reference in New Issue
Block a user