diff --git a/Cargo.lock b/Cargo.lock index f5ce18f05..b11c5ce4e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -479,11 +479,15 @@ version = "2.0.1" dependencies = [ "beacon_node", "clap", + "clap_utils", "eth2_ssz", "hex", "lighthouse_network", "log", "logging", + "serde", + "serde_derive", + "serde_json", "slog", "slog-async", "slog-scope", @@ -3193,6 +3197,7 @@ dependencies = [ "lighthouse_network", "lighthouse_version", "malloc_utils", + "serde", "serde_json", "slashing_protection", "slog", diff --git a/boot_node/Cargo.toml b/boot_node/Cargo.toml index 47dbaa86b..bd21b11a8 100644 --- a/boot_node/Cargo.toml +++ b/boot_node/Cargo.toml @@ -7,6 +7,7 @@ edition = "2018" [dependencies] beacon_node = { path = "../beacon_node" } clap = "2.33.3" +clap_utils = { path = "../common/clap_utils" } lighthouse_network = { path = "../beacon_node/lighthouse_network" } types = { path = "../consensus/types" } eth2_ssz = "0.4.0" @@ -19,3 +20,6 @@ slog-async = "2.5.0" slog-scope = "4.3.0" slog-stdlog = "4.0.0" hex = "0.4.2" +serde = "1.0.116" +serde_derive = "1.0.116" +serde_json = "1.0.66" diff --git a/boot_node/src/config.rs b/boot_node/src/config.rs index 93be6b05c..704cbb2a8 100644 --- a/boot_node/src/config.rs +++ b/boot_node/src/config.rs @@ -5,6 +5,7 @@ use lighthouse_network::{ discovery::{create_enr_builder_from_config, load_enr_from_disk, use_or_load_enr}, load_private_key, CombinedKeyExt, NetworkConfig, }; +use serde_derive::{Deserialize, Serialize}; use ssz::Encode; use std::convert::TryFrom; use std::net::SocketAddr; @@ -130,3 +131,35 @@ impl TryFrom<&ArgMatches<'_>> for BootNodeConfig { }) } } + +/// The set of configuration parameters that can safely be (de)serialized. +/// +/// Its fields are a subset of the fields of `BootNodeConfig`. +#[derive(Serialize, Deserialize)] +pub struct BootNodeConfigSerialization { + pub listen_socket: SocketAddr, + // TODO: Generalise to multiaddr + pub boot_nodes: Vec, + pub local_enr: Enr, +} + +impl BootNodeConfigSerialization { + /// Returns a `BootNodeConfigSerialization` obtained from copying resp. cloning the + /// relevant fields of `config` + pub fn from_config_ref(config: &BootNodeConfig) -> Self { + let BootNodeConfig { + listen_socket, + boot_nodes, + local_enr, + local_key: _, + discv5_config: _, + phantom: _, + } = config; + + BootNodeConfigSerialization { + listen_socket: *listen_socket, + boot_nodes: boot_nodes.clone(), + local_enr: local_enr.clone(), + } + } +} diff --git a/boot_node/src/lib.rs b/boot_node/src/lib.rs index 3f69f53ba..ed3a5655b 100644 --- a/boot_node/src/lib.rs +++ b/boot_node/src/lib.rs @@ -3,17 +3,24 @@ use clap::ArgMatches; use slog::{o, Drain, Level, Logger}; use std::convert::TryFrom; +use std::fs::File; +use std::path::PathBuf; mod cli; -mod config; +pub mod config; mod server; pub use cli::cli_app; -use config::BootNodeConfig; +use config::{BootNodeConfig, BootNodeConfigSerialization}; use types::{EthSpec, EthSpecId}; const LOG_CHANNEL_SIZE: usize = 2048; /// Run the bootnode given the CLI configuration. -pub fn run(matches: &ArgMatches<'_>, eth_spec_id: EthSpecId, debug_level: String) { +pub fn run( + lh_matches: &ArgMatches<'_>, + bn_matches: &ArgMatches<'_>, + eth_spec_id: EthSpecId, + debug_level: String, +) { let debug_level = match debug_level.as_str() { "trace" => log::Level::Trace, "debug" => log::Level::Debug, @@ -49,24 +56,40 @@ pub fn run(matches: &ArgMatches<'_>, eth_spec_id: EthSpecId, debug_level: String let log = slog_scope::logger(); // Run the main function emitting any errors if let Err(e) = match eth_spec_id { - EthSpecId::Minimal => main::(matches, log), - EthSpecId::Mainnet => main::(matches, log), + EthSpecId::Minimal => main::(lh_matches, bn_matches, log), + EthSpecId::Mainnet => main::(lh_matches, bn_matches, log), } { slog::crit!(slog_scope::logger(), "{}", e); } } -fn main(matches: &ArgMatches<'_>, log: slog::Logger) -> Result<(), String> { +fn main( + lh_matches: &ArgMatches<'_>, + bn_matches: &ArgMatches<'_>, + log: slog::Logger, +) -> Result<(), String> { // Builds a custom executor for the bootnode let runtime = tokio::runtime::Builder::new_multi_thread() .enable_all() .build() .map_err(|e| format!("Failed to build runtime: {}", e))?; - // parse the CLI args into a useable config - let config: BootNodeConfig = BootNodeConfig::try_from(matches)?; + // Parse the CLI args into a useable config + let config: BootNodeConfig = BootNodeConfig::try_from(bn_matches)?; + + // Dump config if `dump-config` flag is set + let dump_config = clap_utils::parse_optional::(lh_matches, "dump-config")?; + if let Some(dump_path) = dump_config { + let config_sz = BootNodeConfigSerialization::from_config_ref(&config); + let mut file = File::create(dump_path) + .map_err(|e| format!("Failed to create dumped config: {:?}", e))?; + serde_json::to_writer(&mut file, &config_sz) + .map_err(|e| format!("Error serializing config: {:?}", e))?; + } // Run the boot node - runtime.block_on(server::run(config, log)); + if !lh_matches.is_present("immediate-shutdown") { + runtime.block_on(server::run(config, log)); + } Ok(()) } diff --git a/lighthouse/Cargo.toml b/lighthouse/Cargo.toml index fbcc9d172..f3eec21d0 100644 --- a/lighthouse/Cargo.toml +++ b/lighthouse/Cargo.toml @@ -37,6 +37,7 @@ lighthouse_version = { path = "../common/lighthouse_version" } account_utils = { path = "../common/account_utils" } lighthouse_metrics = { path = "../common/lighthouse_metrics" } lazy_static = "1.4.0" +serde = { version = "1.0.116", features = ["derive"] } serde_json = "1.0.59" task_executor = { path = "../common/task_executor" } malloc_utils = { path = "../common/malloc_utils" } diff --git a/lighthouse/src/main.rs b/lighthouse/src/main.rs index 97e00477a..99775d71d 100644 --- a/lighthouse/src/main.rs +++ b/lighthouse/src/main.rs @@ -204,7 +204,7 @@ fn main() { .expect("Debug-level must be present") .into(); - boot_node::run(bootnode_matches, eth_spec_id, debug_info); + boot_node::run(&matches, bootnode_matches, eth_spec_id, debug_info); return Ok(()); } diff --git a/lighthouse/tests/beacon_node.rs b/lighthouse/tests/beacon_node.rs index 290fc7aef..b8dd31beb 100644 --- a/lighthouse/tests/beacon_node.rs +++ b/lighthouse/tests/beacon_node.rs @@ -1,25 +1,21 @@ use beacon_node::ClientConfig as Config; +use crate::exec::{CommandLineTestExec, CompletedTest}; use lighthouse_network::PeerId; -use serde_json::from_reader; use std::fs::File; use std::io::Write; use std::net::{IpAddr, Ipv4Addr}; use std::net::{TcpListener, UdpSocket}; use std::path::PathBuf; -use std::process::{Command, Output}; -use std::str::{from_utf8, FromStr}; +use std::process::Command; +use std::str::FromStr; use std::string::ToString; use tempfile::TempDir; use types::{Checkpoint, Epoch, Hash256}; -const BEACON_CMD: &str = "beacon_node"; -const CONFIG_NAME: &str = "bn_dump.json"; -const DUMP_CONFIG_CMD: &str = "dump-config"; -const IMMEDIATE_SHUTDOWN_CMD: &str = "immediate-shutdown"; const DEFAULT_ETH1_ENDPOINT: &str = "http://localhost:8545/"; -/// Returns the `lighthouse beacon_node --immediate-shutdown` command. +/// Returns the `lighthouse beacon_node` command. fn base_cmd() -> Command { let lighthouse_bin = env!("CARGO_BIN_EXE_lighthouse"); let path = lighthouse_bin @@ -27,25 +23,10 @@ fn base_cmd() -> Command { .expect("should parse CARGO_TARGET_DIR"); let mut cmd = Command::new(path); - cmd.arg(BEACON_CMD) - .arg(format!("--{}", IMMEDIATE_SHUTDOWN_CMD)); - + cmd.arg("beacon_node"); cmd } -/// Executes a `Command`, returning a `Result` based upon the success exit code of the command. -fn output_result(cmd: &mut Command) -> Result { - let output = cmd.output().expect("should run command"); - - if output.status.success() { - Ok(output) - } else { - Err(from_utf8(&output.stderr) - .expect("stderr is not utf8") - .to_string()) - } -} - // Wrapper around `Command` for easier Command Line Testing. struct CommandLineTest { cmd: Command, @@ -56,89 +37,24 @@ impl CommandLineTest { CommandLineTest { cmd: base_cmd } } - fn flag(mut self, flag: &str, value: Option<&str>) -> Self { - // Build the command by adding the flag and any values. - self.cmd.arg(format!("--{}", flag)); - if let Some(value) = value { - self.cmd.arg(value); - } - self - } - - fn run(&mut self) -> CompletedTest { - // Setup temp directories. - let tmp_dir = TempDir::new().expect("Unable to create temporary directory"); - let tmp_path: PathBuf = tmp_dir.path().join(CONFIG_NAME); - - // Add --datadir --dump-config -z to cmd. - self.cmd - .arg("--datadir") - .arg(tmp_dir.path().as_os_str()) - .arg(format!("--{}", DUMP_CONFIG_CMD)) - .arg(tmp_path.as_os_str()) - .arg("-z"); - - // Run the command. - let output = output_result(&mut self.cmd); - if let Err(e) = output { - panic!("{:?}", e); - } - - // Grab the config. - let config: Config = - from_reader(File::open(tmp_path).expect("Unable to open dumped config")) - .expect("Unable to deserialize to ClientConfig"); - CompletedTest { - config, - dir: tmp_dir, - } - } - - fn run_with_no_zero_port(&mut self) -> CompletedTest { - // Setup temp directories. - let tmp_dir = TempDir::new().expect("Unable to create temporary directory"); - let tmp_path: PathBuf = tmp_dir.path().join(CONFIG_NAME); - - // Add --datadir --dump-config to cmd. - self.cmd - .arg("--datadir") - .arg(tmp_dir.path().as_os_str()) - .arg(format!("--{}", DUMP_CONFIG_CMD)) - .arg(tmp_path.as_os_str()); - - // Run the command. - let output = output_result(&mut self.cmd); - if let Err(e) = output { - panic!("{:?}", e); - } - - // Grab the config. - let config: Config = - from_reader(File::open(tmp_path).expect("Unable to open dumped config")) - .expect("Unable to deserialize to ClientConfig"); - CompletedTest { - config, - dir: tmp_dir, - } + fn run_with_zero_port(&mut self) -> CompletedTest { + self.cmd.arg("-z"); + self.run() } } -struct CompletedTest { - config: Config, - dir: TempDir, -} -impl CompletedTest { - fn with_config(self, func: F) { - func(&self.config); - } - fn with_config_and_dir(self, func: F) { - func(&self.config, &self.dir); + +impl CommandLineTestExec for CommandLineTest { + type Config = Config; + + fn cmd_mut(&mut self) -> &mut Command { + &mut self.cmd } } #[test] fn datadir_flag() { CommandLineTest::new() - .run() + .run_with_zero_port() .with_config_and_dir(|config, dir| assert_eq!(config.data_dir, dir.path().join("beacon"))); } @@ -146,7 +62,7 @@ fn datadir_flag() { fn staking_flag() { CommandLineTest::new() .flag("staking", None) - .run() + .run_with_zero_port() .with_config(|config| { assert!(config.http_api.enabled); assert!(config.sync_eth1_chain); @@ -166,21 +82,21 @@ fn wss_checkpoint_flag() { "wss-checkpoint", Some("0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef:1010"), ) - .run() + .run_with_zero_port() .with_config(|config| assert_eq!(config.chain.weak_subjectivity_checkpoint, state)); } #[test] fn max_skip_slots_flag() { CommandLineTest::new() .flag("max-skip-slots", Some("10")) - .run() + .run_with_zero_port() .with_config(|config| assert_eq!(config.chain.import_max_skip_slots, Some(10))); } #[test] fn enable_lock_timeouts_default() { CommandLineTest::new() - .run() + .run_with_zero_port() .with_config(|config| assert!(config.chain.enable_lock_timeouts)); } @@ -188,7 +104,7 @@ fn enable_lock_timeouts_default() { fn disable_lock_timeouts_flag() { CommandLineTest::new() .flag("disable-lock-timeouts", None) - .run() + .run_with_zero_port() .with_config(|config| assert!(!config.chain.enable_lock_timeouts)); } @@ -197,7 +113,7 @@ fn freezer_dir_flag() { let dir = TempDir::new().expect("Unable to create temporary directory"); CommandLineTest::new() .flag("freezer-dir", dir.path().as_os_str().to_str()) - .run() + .run_with_zero_port() .with_config(|config| assert_eq!(config.freezer_db_path, Some(dir.path().to_path_buf()))); } @@ -205,7 +121,7 @@ fn freezer_dir_flag() { fn graffiti_flag() { CommandLineTest::new() .flag("graffiti", Some("nice-graffiti")) - .run() + .run_with_zero_port() .with_config(|config| { assert_eq!( config.graffiti.to_string(), @@ -222,7 +138,7 @@ fn trusted_peers_flag() { "trusted-peers", Some(format!("{},{}", peers[0].to_string(), peers[1].to_string()).as_str()), ) - .run() + .run_with_zero_port() .with_config(|config| { assert_eq!( PeerId::from(config.network.trusted_peers[0].clone()).to_bytes(), @@ -240,14 +156,14 @@ fn trusted_peers_flag() { fn dummy_eth1_flag() { CommandLineTest::new() .flag("dummy-eth1", None) - .run() + .run_with_zero_port() .with_config(|config| assert!(config.dummy_eth1_backend)); } #[test] fn eth1_flag() { CommandLineTest::new() .flag("eth1", None) - .run() + .run_with_zero_port() .with_config(|config| assert!(config.sync_eth1_chain)); } #[test] @@ -257,7 +173,7 @@ fn eth1_endpoints_flag() { "eth1-endpoints", Some("http://localhost:9545,https://infura.io/secret"), ) - .run() + .run_with_zero_port() .with_config(|config| { assert_eq!( config.eth1.endpoints[0].full.to_string(), @@ -279,14 +195,14 @@ fn eth1_endpoints_flag() { fn eth1_blocks_per_log_query_flag() { CommandLineTest::new() .flag("eth1-blocks-per-log-query", Some("500")) - .run() + .run_with_zero_port() .with_config(|config| assert_eq!(config.eth1.blocks_per_log_query, 500)); } #[test] fn eth1_purge_cache_flag() { CommandLineTest::new() .flag("eth1-purge-cache", None) - .run() + .run_with_zero_port() .with_config(|config| assert!(config.eth1.purge_cache)); } @@ -296,14 +212,14 @@ fn network_dir_flag() { let dir = TempDir::new().expect("Unable to create temporary directory"); CommandLineTest::new() .flag("network-dir", dir.path().as_os_str().to_str()) - .run() + .run_with_zero_port() .with_config(|config| assert_eq!(config.network.network_dir, dir.path())); } #[test] fn network_target_peers_flag() { CommandLineTest::new() .flag("target-peers", Some("55")) - .run() + .run_with_zero_port() .with_config(|config| { assert_eq!(config.network.target_peers, "55".parse::().unwrap()); }); @@ -312,27 +228,27 @@ fn network_target_peers_flag() { fn network_subscribe_all_subnets_flag() { CommandLineTest::new() .flag("subscribe-all-subnets", None) - .run() + .run_with_zero_port() .with_config(|config| assert!(config.network.subscribe_all_subnets)); } #[test] fn network_import_all_attestations_flag() { CommandLineTest::new() .flag("import-all-attestations", None) - .run() + .run_with_zero_port() .with_config(|config| assert!(config.network.import_all_attestations)); } #[test] fn network_shutdown_after_sync_flag() { CommandLineTest::new() .flag("shutdown-after-sync", None) - .run() + .run_with_zero_port() .with_config(|config| assert!(config.network.shutdown_after_sync)); } #[test] fn network_shutdown_after_sync_disabled_flag() { CommandLineTest::new() - .run() + .run_with_zero_port() .with_config(|config| assert!(!config.network.shutdown_after_sync)); } #[test] @@ -340,7 +256,7 @@ fn network_listen_address_flag() { let addr = "127.0.0.2".parse::().unwrap(); CommandLineTest::new() .flag("listen-address", Some("127.0.0.2")) - .run() + .run_with_zero_port() .with_config(|config| assert_eq!(config.network.listen_address, addr)); } #[test] @@ -348,7 +264,7 @@ fn network_port_flag() { let port = unused_port("tcp").expect("Unable to find unused port."); CommandLineTest::new() .flag("port", Some(port.to_string().as_str())) - .run_with_no_zero_port() + .run() .with_config(|config| { assert_eq!(config.network.libp2p_port, port); assert_eq!(config.network.discovery_port, port); @@ -361,7 +277,7 @@ fn network_port_and_discovery_port_flags() { CommandLineTest::new() .flag("port", Some(port1.to_string().as_str())) .flag("discovery-port", Some(port2.to_string().as_str())) - .run_with_no_zero_port() + .run() .with_config(|config| { assert_eq!(config.network.libp2p_port, port1); assert_eq!(config.network.discovery_port, port2); @@ -371,14 +287,14 @@ fn network_port_and_discovery_port_flags() { fn disable_discovery_flag() { CommandLineTest::new() .flag("disable-discovery", None) - .run() + .run_with_zero_port() .with_config(|config| assert!(config.network.disable_discovery)); } #[test] fn disable_upnp_flag() { CommandLineTest::new() .flag("disable-upnp", None) - .run() + .run_with_zero_port() .with_config(|config| assert!(!config.network.upnp_enabled)); } #[test] @@ -386,7 +302,7 @@ fn default_boot_nodes() { let mainnet = vec![ // Lighthouse Team (Sigma Prime) "enr:-Jq4QItoFUuug_n_qbYbU0OY04-np2wT8rUCauOOXNi0H3BWbDj-zbfZb7otA7jZ6flbBpx1LNZK2TDebZ9dEKx84LYBhGV0aDKQtTA_KgEAAAD__________4JpZIJ2NIJpcISsaa0ZiXNlY3AyNTZrMaEDHAD2JKYevx89W0CcFJFiskdcEzkH_Wdv9iW42qLK79ODdWRwgiMo", -"enr:-Jq4QN_YBsUOqQsty1OGvYv48PMaiEt1AzGD1NkYQHaxZoTyVGqMYXg0K9c0LPNWC9pkXmggApp8nygYLsQwScwAgfgBhGV0aDKQtTA_KgEAAAD__________4JpZIJ2NIJpcISLosQxiXNlY3AyNTZrMaEDBJj7_dLFACaxBfaI8KZTh_SSJUjhyAyfshimvSqo22WDdWRwgiMo", + "enr:-Jq4QN_YBsUOqQsty1OGvYv48PMaiEt1AzGD1NkYQHaxZoTyVGqMYXg0K9c0LPNWC9pkXmggApp8nygYLsQwScwAgfgBhGV0aDKQtTA_KgEAAAD__________4JpZIJ2NIJpcISLosQxiXNlY3AyNTZrMaEDBJj7_dLFACaxBfaI8KZTh_SSJUjhyAyfshimvSqo22WDdWRwgiMo", // EF Team "enr:-Ku4QHqVeJ8PPICcWk1vSn_XcSkjOkNiTg6Fmii5j6vUQgvzMc9L1goFnLKgXqBJspJjIsB91LTOleFmyWWrFVATGngBh2F0dG5ldHOIAAAAAAAAAACEZXRoMpC1MD8qAAAAAP__________gmlkgnY0gmlwhAMRHkWJc2VjcDI1NmsxoQKLVXFOhp2uX6jeT0DvvDpPcU8FWMjQdR4wMuORMhpX24N1ZHCCIyg", "enr:-Ku4QG-2_Md3sZIAUebGYT6g0SMskIml77l6yR-M_JXc-UdNHCmHQeOiMLbylPejyJsdAPsTHJyjJB2sYGDLe0dn8uYBh2F0dG5ldHOIAAAAAAAAAACEZXRoMpC1MD8qAAAAAP__________gmlkgnY0gmlwhBLY-NyJc2VjcDI1NmsxoQORcM6e19T1T9gi7jxEZjk_sjVLGFscUNqAY9obgZaxbIN1ZHCCIyg", @@ -404,26 +320,28 @@ fn default_boot_nodes() { "enr:-LK4QKWrXTpV9T78hNG6s8AM6IO4XH9kFT91uZtFg1GcsJ6dKovDOr1jtAAFPnS2lvNltkOGA9k29BUN7lFh_sjuc9QBh2F0dG5ldHOIAAAAAAAAAACEZXRoMpC1MD8qAAAAAP__________gmlkgnY0gmlwhANAdd-Jc2VjcDI1NmsxoQLQa6ai7y9PMN5hpLe5HmiJSlYzMuzP7ZhwRiwHvqNXdoN0Y3CCI4yDdWRwgiOM" ]; - CommandLineTest::new().run().with_config(|config| { - // Lighthouse Team (Sigma Prime) - assert_eq!(config.network.boot_nodes_enr[0].to_base64(), mainnet[0]); - assert_eq!(config.network.boot_nodes_enr[1].to_base64(), mainnet[1]); - // EF Team - assert_eq!(config.network.boot_nodes_enr[2].to_base64(), mainnet[2]); - assert_eq!(config.network.boot_nodes_enr[3].to_base64(), mainnet[3]); - assert_eq!(config.network.boot_nodes_enr[4].to_base64(), mainnet[4]); - assert_eq!(config.network.boot_nodes_enr[5].to_base64(), mainnet[5]); - // Teku team (Consensys) - assert_eq!(config.network.boot_nodes_enr[6].to_base64(), mainnet[6]); - assert_eq!(config.network.boot_nodes_enr[7].to_base64(), mainnet[7]); - // Prysm team (Prysmatic Labs) - assert_eq!(config.network.boot_nodes_enr[8].to_base64(), mainnet[8]); - assert_eq!(config.network.boot_nodes_enr[9].to_base64(), mainnet[9]); - assert_eq!(config.network.boot_nodes_enr[10].to_base64(), mainnet[10]); - // Nimbus team - assert_eq!(config.network.boot_nodes_enr[11].to_base64(), mainnet[11]); - assert_eq!(config.network.boot_nodes_enr[12].to_base64(), mainnet[12]); - }); + CommandLineTest::new() + .run_with_zero_port() + .with_config(|config| { + // Lighthouse Team (Sigma Prime) + assert_eq!(config.network.boot_nodes_enr[0].to_base64(), mainnet[0]); + assert_eq!(config.network.boot_nodes_enr[1].to_base64(), mainnet[1]); + // EF Team + assert_eq!(config.network.boot_nodes_enr[2].to_base64(), mainnet[2]); + assert_eq!(config.network.boot_nodes_enr[3].to_base64(), mainnet[3]); + assert_eq!(config.network.boot_nodes_enr[4].to_base64(), mainnet[4]); + assert_eq!(config.network.boot_nodes_enr[5].to_base64(), mainnet[5]); + // Teku team (Consensys) + assert_eq!(config.network.boot_nodes_enr[6].to_base64(), mainnet[6]); + assert_eq!(config.network.boot_nodes_enr[7].to_base64(), mainnet[7]); + // Prysm team (Prysmatic Labs) + assert_eq!(config.network.boot_nodes_enr[8].to_base64(), mainnet[8]); + assert_eq!(config.network.boot_nodes_enr[9].to_base64(), mainnet[9]); + assert_eq!(config.network.boot_nodes_enr[10].to_base64(), mainnet[10]); + // Nimbus team + assert_eq!(config.network.boot_nodes_enr[11].to_base64(), mainnet[11]); + assert_eq!(config.network.boot_nodes_enr[12].to_base64(), mainnet[12]); + }); } #[test] fn boot_nodes_flag() { @@ -432,7 +350,7 @@ fn boot_nodes_flag() { let enr: Vec<&str> = nodes.split(',').collect(); CommandLineTest::new() .flag("boot-nodes", Some(nodes)) - .run() + .run_with_zero_port() .with_config(|config| { assert_eq!(config.network.boot_nodes_enr[0].to_base64(), enr[0]); assert_eq!(config.network.boot_nodes_enr[1].to_base64(), enr[1]); @@ -445,7 +363,7 @@ fn boot_nodes_multiaddr_flag() { let multiaddr: Vec<&str> = nodes.split(',').collect(); CommandLineTest::new() .flag("boot-nodes", Some(nodes)) - .run() + .run_with_zero_port() .with_config(|config| { assert_eq!( config.network.boot_nodes_multiaddr[0].to_string(), @@ -461,16 +379,18 @@ fn boot_nodes_multiaddr_flag() { fn private_flag() { CommandLineTest::new() .flag("private", None) - .run() + .run_with_zero_port() .with_config(|config| assert!(config.network.private)); } #[test] fn zero_ports_flag() { - CommandLineTest::new().run().with_config(|config| { - assert_eq!(config.network.enr_address, None); - assert_eq!(config.http_api.listen_port, 0); - assert_eq!(config.http_metrics.listen_port, 0); - }); + CommandLineTest::new() + .run_with_zero_port() + .with_config(|config| { + assert_eq!(config.network.enr_address, None); + assert_eq!(config.http_api.listen_port, 0); + assert_eq!(config.http_metrics.listen_port, 0); + }); } // Tests for ENR flags. @@ -479,7 +399,7 @@ fn enr_udp_port_flags() { let port = unused_port("udp").expect("Unable to find unused port."); CommandLineTest::new() .flag("enr-udp-port", Some(port.to_string().as_str())) - .run() + .run_with_zero_port() .with_config(|config| assert_eq!(config.network.enr_udp_port, Some(port))); } #[test] @@ -487,7 +407,7 @@ fn enr_tcp_port_flags() { let port = unused_port("tcp").expect("Unable to find unused port."); CommandLineTest::new() .flag("enr-tcp-port", Some(port.to_string().as_str())) - .run() + .run_with_zero_port() .with_config(|config| assert_eq!(config.network.enr_tcp_port, Some(port))); } #[test] @@ -500,7 +420,7 @@ fn enr_match_flag() { .flag("listen-address", Some("127.0.0.2")) .flag("discovery-port", Some(port1.to_string().as_str())) .flag("port", Some(port2.to_string().as_str())) - .run_with_no_zero_port() + .run() .with_config(|config| { assert_eq!(config.network.listen_address, addr); assert_eq!(config.network.enr_address, Some(addr)); @@ -515,7 +435,7 @@ fn enr_address_flag() { CommandLineTest::new() .flag("enr-address", Some("192.167.1.1")) .flag("enr-udp-port", Some(port.to_string().as_str())) - .run() + .run_with_zero_port() .with_config(|config| { assert_eq!(config.network.enr_address, Some(addr)); assert_eq!(config.network.enr_udp_port, Some(port)); @@ -529,7 +449,7 @@ fn enr_address_dns_flag() { CommandLineTest::new() .flag("enr-address", Some("localhost")) .flag("enr-udp-port", Some(port.to_string().as_str())) - .run() + .run_with_zero_port() .with_config(|config| { assert!( config.network.enr_address == Some(addr) @@ -542,7 +462,7 @@ fn enr_address_dns_flag() { fn disable_enr_auto_update_flag() { CommandLineTest::new() .flag("disable-enr-auto-update", None) - .run() + .run_with_zero_port() .with_config(|config| assert!(config.network.discv5_config.enr_update)); } @@ -551,7 +471,7 @@ fn disable_enr_auto_update_flag() { fn http_flag() { CommandLineTest::new() .flag("http", None) - .run() + .run_with_zero_port() .with_config(|config| assert!(config.http_api.enabled)); } #[test] @@ -559,7 +479,7 @@ fn http_address_flag() { let addr = "127.0.0.99".parse::().unwrap(); CommandLineTest::new() .flag("http-address", Some("127.0.0.99")) - .run() + .run_with_zero_port() .with_config(|config| assert_eq!(config.http_api.listen_addr, addr)); } #[test] @@ -569,14 +489,14 @@ fn http_port_flag() { CommandLineTest::new() .flag("http-port", Some(port1.to_string().as_str())) .flag("port", Some(port2.to_string().as_str())) - .run_with_no_zero_port() + .run() .with_config(|config| assert_eq!(config.http_api.listen_port, port1)); } #[test] fn http_allow_origin_flag() { CommandLineTest::new() .flag("http-allow-origin", Some("127.0.0.99")) - .run() + .run_with_zero_port() .with_config(|config| { assert_eq!(config.http_api.allow_origin, Some("127.0.0.99".to_string())); }); @@ -585,7 +505,7 @@ fn http_allow_origin_flag() { fn http_allow_origin_all_flag() { CommandLineTest::new() .flag("http-allow-origin", Some("*")) - .run() + .run_with_zero_port() .with_config(|config| assert_eq!(config.http_api.allow_origin, Some("*".to_string()))); } #[test] @@ -601,7 +521,7 @@ fn http_tls_flags() { "http-tls-key", dir.path().join("private.key").as_os_str().to_str(), ) - .run() + .run_with_zero_port() .with_config(|config| { let tls_config = config .http_api @@ -618,7 +538,7 @@ fn http_tls_flags() { fn metrics_flag() { CommandLineTest::new() .flag("metrics", None) - .run() + .run_with_zero_port() .with_config(|config| { assert!(config.http_metrics.enabled); assert!(config.network.metrics_enabled); @@ -630,7 +550,7 @@ fn metrics_address_flag() { CommandLineTest::new() .flag("metrics", None) .flag("metrics-address", Some("127.0.0.99")) - .run() + .run_with_zero_port() .with_config(|config| assert_eq!(config.http_metrics.listen_addr, addr)); } #[test] @@ -641,7 +561,7 @@ fn metrics_port_flag() { .flag("metrics", None) .flag("metrics-port", Some(port1.to_string().as_str())) .flag("port", Some(port2.to_string().as_str())) - .run_with_no_zero_port() + .run() .with_config(|config| assert_eq!(config.http_metrics.listen_port, port1)); } #[test] @@ -649,7 +569,7 @@ fn metrics_allow_origin_flag() { CommandLineTest::new() .flag("metrics", None) .flag("metrics-allow-origin", Some("http://localhost:5059")) - .run() + .run_with_zero_port() .with_config(|config| { assert_eq!( config.http_metrics.allow_origin, @@ -662,7 +582,7 @@ fn metrics_allow_origin_all_flag() { CommandLineTest::new() .flag("metrics", None) .flag("metrics-allow-origin", Some("*")) - .run() + .run_with_zero_port() .with_config(|config| assert_eq!(config.http_metrics.allow_origin, Some("*".to_string()))); } @@ -671,7 +591,7 @@ fn metrics_allow_origin_all_flag() { fn validator_monitor_auto_flag() { CommandLineTest::new() .flag("validator-monitor-auto", None) - .run() + .run_with_zero_port() .with_config(|config| assert!(config.validator_monitor_auto)); } #[test] @@ -679,7 +599,7 @@ fn validator_monitor_pubkeys_flag() { CommandLineTest::new() .flag("validator-monitor-pubkeys", Some("0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef,\ 0xbeefdeadbeefdeaddeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef")) - .run() + .run_with_zero_port() .with_config(|config| { assert_eq!(config.validator_monitor_pubkeys[0].to_string(), "0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"); assert_eq!(config.validator_monitor_pubkeys[1].to_string(), "0xbeefdeadbeefdeaddeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"); @@ -694,7 +614,7 @@ fn validator_monitor_file_flag() { .expect("Unable to write to file"); CommandLineTest::new() .flag("validator-monitor-file", dir.path().join("pubkeys.txt").as_os_str().to_str()) - .run() + .run_with_zero_port() .with_config(|config| { assert_eq!(config.validator_monitor_pubkeys[0].to_string(), "0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"); assert_eq!(config.validator_monitor_pubkeys[1].to_string(), "0xbeefdeadbeefdeaddeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"); @@ -706,21 +626,21 @@ fn validator_monitor_file_flag() { fn slots_per_restore_point_flag() { CommandLineTest::new() .flag("slots-per-restore-point", Some("64")) - .run() + .run_with_zero_port() .with_config(|config| assert_eq!(config.store.slots_per_restore_point, 64)); } #[test] fn block_cache_size_flag() { CommandLineTest::new() .flag("block-cache-size", Some("4")) - .run() + .run_with_zero_port() .with_config(|config| assert_eq!(config.store.block_cache_size, 4_usize)); } #[test] fn auto_compact_db_flag() { CommandLineTest::new() .flag("auto-compact-db", Some("false")) - .run() + .run_with_zero_port() .with_config(|config| assert!(!config.store.compact_on_prune)); } #[test] @@ -728,20 +648,20 @@ fn compact_db_flag() { CommandLineTest::new() .flag("auto-compact-db", Some("false")) .flag("compact-db", None) - .run() + .run_with_zero_port() .with_config(|config| assert!(config.store.compact_on_init)); } #[test] fn reconstruct_historic_states_flag() { CommandLineTest::new() .flag("reconstruct-historic-states", None) - .run() + .run_with_zero_port() .with_config(|config| assert!(config.chain.reconstruct_historic_states)); } #[test] fn no_reconstruct_historic_states_flag() { CommandLineTest::new() - .run() + .run_with_zero_port() .with_config(|config| assert!(!config.chain.reconstruct_historic_states)); } @@ -751,7 +671,7 @@ fn slasher_flag() { CommandLineTest::new() .flag("slasher", None) .flag("slasher-max-db-size", Some("16")) - .run() + .run_with_zero_port() .with_config_and_dir(|config, dir| { if let Some(slasher_config) = &config.slasher { assert_eq!( @@ -770,7 +690,7 @@ fn slasher_dir_flag() { .flag("slasher", None) .flag("slasher-dir", dir.path().as_os_str().to_str()) .flag("slasher-max-db-size", Some("16")) - .run() + .run_with_zero_port() .with_config(|config| { if let Some(slasher_config) = &config.slasher { assert_eq!(slasher_config.database_path, dir.path()); @@ -785,7 +705,7 @@ fn slasher_update_period_flag() { .flag("slasher", None) .flag("slasher-max-db-size", Some("16")) .flag("slasher-update-period", Some("100")) - .run() + .run_with_zero_port() .with_config(|config| { if let Some(slasher_config) = &config.slasher { assert_eq!(slasher_config.update_period, 100); @@ -819,7 +739,7 @@ fn slasher_history_length_flag() { .flag("slasher", None) .flag("slasher-max-db-size", Some("16")) .flag("slasher-history-length", Some("2048")) - .run() + .run_with_zero_port() .with_config(|config| { if let Some(slasher_config) = &config.slasher { assert_eq!(slasher_config.history_length, 2048); @@ -833,7 +753,7 @@ fn slasher_max_db_size_flag() { CommandLineTest::new() .flag("slasher", None) .flag("slasher-max-db-size", Some("10")) - .run() + .run_with_zero_port() .with_config(|config| { let slasher_config = config .slasher @@ -848,7 +768,7 @@ fn slasher_chunk_size_flag() { .flag("slasher", None) .flag("slasher-chunk-size", Some("32")) .flag("slasher-max-db-size", Some("16")) - .run() + .run_with_zero_port() .with_config(|config| { let slasher_config = config .slasher @@ -863,7 +783,7 @@ fn slasher_validator_chunk_size_flag() { .flag("slasher", None) .flag("slasher-max-db-size", Some("16")) .flag("slasher-validator-chunk-size", Some("512")) - .run() + .run_with_zero_port() .with_config(|config| { let slasher_config = config .slasher @@ -878,7 +798,7 @@ fn slasher_broadcast_flag() { .flag("slasher", None) .flag("slasher-broadcast", None) .flag("slasher-max-db-size", Some("16")) - .run() + .run_with_zero_port() .with_config(|config| { let slasher_config = config .slasher @@ -891,7 +811,7 @@ fn slasher_broadcast_flag() { pub fn malloc_tuning_flag() { CommandLineTest::new() .flag("disable-malloc-tuning", None) - .run() + .run_with_zero_port() .with_config(|config| { assert!(!config.http_metrics.allocator_metrics_enabled); }); @@ -902,7 +822,7 @@ fn ensure_panic_on_failed_launch() { CommandLineTest::new() .flag("slasher", None) .flag("slasher-chunk-size", Some("10")) - .run() + .run_with_zero_port() .with_config(|config| { let slasher_config = config .slasher diff --git a/lighthouse/tests/boot_node.rs b/lighthouse/tests/boot_node.rs new file mode 100644 index 000000000..04437aca9 --- /dev/null +++ b/lighthouse/tests/boot_node.rs @@ -0,0 +1,164 @@ +use boot_node::config::BootNodeConfigSerialization; + +use crate::exec::{CommandLineTestExec, CompletedTest}; +use beacon_node::get_eth2_network_config; +use clap::ArgMatches; +use lighthouse_network::discovery::ENR_FILENAME; +use lighthouse_network::Enr; +use std::fs::File; +use std::io::Write; +use std::net::{Ipv4Addr, UdpSocket}; +use std::path::{Path, PathBuf}; +use std::process::Command; +use std::str::FromStr; +use tempfile::TempDir; + +const IP_ADDRESS: &str = "192.168.2.108"; + +/// Returns the `lighthouse boot_node` command. +fn base_cmd() -> Command { + let lighthouse_bin = env!("CARGO_BIN_EXE_lighthouse"); + let path = lighthouse_bin + .parse::() + .expect("should parse CARGO_TARGET_DIR"); + + let mut cmd = Command::new(path); + cmd.arg("boot_node"); + cmd +} + +struct CommandLineTest { + cmd: Command, +} + +impl CommandLineTest { + fn new() -> CommandLineTest { + let base_cmd = base_cmd(); + CommandLineTest { cmd: base_cmd } + } + + fn run_with_ip(&mut self) -> CompletedTest { + self.cmd.arg(IP_ADDRESS); + self.run() + } +} + +impl CommandLineTestExec for CommandLineTest { + type Config = BootNodeConfigSerialization; + + fn cmd_mut(&mut self) -> &mut Command { + &mut self.cmd + } +} + +fn unused_port() -> u16 { + let socket = + UdpSocket::bind("127.0.0.1:0").expect("should create udp socket to find unused port"); + let local_addr = socket + .local_addr() + .expect("should read udp socket to find unused port"); + local_addr.port() +} + +#[test] +fn enr_address_arg() { + let mut test = CommandLineTest::new(); + test.run_with_ip().with_config(|config| { + assert_eq!(config.local_enr.ip(), Some(IP_ADDRESS.parse().unwrap())); + }); +} + +#[test] +fn port_flag() { + let port = unused_port(); + CommandLineTest::new() + .flag("port", Some(port.to_string().as_str())) + .run_with_ip() + .with_config(|config| { + assert_eq!(config.listen_socket.port(), port); + }) +} + +#[test] +fn listen_address_flag() { + let addr = "127.0.0.2".parse::().unwrap(); + CommandLineTest::new() + .flag("listen-address", Some("127.0.0.2")) + .run_with_ip() + .with_config(|config| { + assert_eq!(config.listen_socket.ip(), addr); + }); +} + +#[test] +fn boot_nodes_flag() { + // Get hardcoded boot-nodes to verify they end up in the config. + // Pass empty `ArgMatches` to `get_eth2_network_config` as we want to + // receive the default `Eth2NetworkConfig`. + let empty_args = ArgMatches::default(); + let default_enr = get_eth2_network_config(&empty_args) + .unwrap() + .boot_enr + .unwrap(); + let default_enr: Vec = default_enr.iter().map(|enr| enr.to_base64()).collect(); + + // Nodes passed via `--boot-nodes` are added to the local routing table. + let extra_nodes = "enr:-IS4QHCYrYZbAKWCBRlAy5zzaDZXJBGkcnh4MHcBFZntXNFrdvJjX04jRzjzCBOonrkTfj499SZuOh8R33Ls8RRcy5wBgmlkgnY0gmlwhH8AAAGJc2VjcDI1NmsxoQPKY0yuDUmstAHYpMa2_oxVtw0RW_QAdpzBQA8yWM0xOIN1ZHCCdl8,enr:-LK4QFOFWca5ABQzxiCRcy37G7wy1K6zD4qMYBSN5ozzanwze_XVvXVhCk9JvF0cHXOBZrHK1E4vU7Gn-a0bHVczoDU6h2F0dG5ldHOIAAAAAAAAAACEZXRoMpA7CIeVAAAgCf__________gmlkgnY0gmlwhNIy-4iJc2VjcDI1NmsxoQJA3AXQJ6M3NpBWtJS3HPtbXG14t7qHjXuIaL6IOz89T4N0Y3CCIyiDdWRwgiMo"; + let extra_enr: Vec<&str> = extra_nodes.split(",").collect(); + + // Construct vector of enr expected in config. + let default_enr_str: Vec<&str> = default_enr.iter().map(|s| s.as_str()).collect(); + let mut expect_enr = Vec::new(); + expect_enr.extend_from_slice(&default_enr_str); + expect_enr.extend_from_slice(&extra_enr); + + CommandLineTest::new() + .flag("boot-nodes", Some(extra_nodes)) + .run_with_ip() + .with_config(|config| { + assert_eq!(config.boot_nodes.len(), expect_enr.len()); + for (i, enr) in config.boot_nodes.iter().enumerate() { + assert_eq!( + enr.to_base64(), + expect_enr[i], + "ENR missmatch at index [{}]", + i + ); + } + }) +} + +#[test] +fn enr_port_flag() { + let port = unused_port(); + CommandLineTest::new() + .flag("enr-port", Some(port.to_string().as_str())) + .run_with_ip() + .with_config(|config| { + assert_eq!(config.local_enr.udp(), Some(port)); + }) +} + +// TODO add tests for flags `enable-enr-auto-update` and `disable-packet-filter`. +// +// These options end up in `Discv5Config`, which doesn't support serde (de)serialization. + +#[test] +fn network_dir_flag() { + // Save enr to temp dir. + let enr = Enr::from_str("enr:-IS4QHCYrYZbAKWCBRlAy5zzaDZXJBGkcnh4MHcBFZntXNFrdvJjX04jRzjzCBOonrkTfj499SZuOh8R33Ls8RRcy5wBgmlkgnY0gmlwhH8AAAGJc2VjcDI1NmsxoQPKY0yuDUmstAHYpMa2_oxVtw0RW_QAdpzBQA8yWM0xOIN1ZHCCdl8").unwrap(); + let tmp_dir = TempDir::new().unwrap(); + save_enr_to_disk(tmp_dir.path(), &enr).unwrap(); + + CommandLineTest::new() + .flag("network-dir", Some(tmp_dir.path().to_str().unwrap())) + .run() + .with_config(|config| assert_eq!(config.local_enr, enr)) +} + +fn save_enr_to_disk(dir: &Path, enr: &Enr) -> Result<(), String> { + let mut file = File::create(dir.join(Path::new(ENR_FILENAME))) + .map_err(|e| format!("Could not create ENR file: {:?}", e))?; + file.write_all(enr.to_base64().as_bytes()) + .map_err(|e| format!("Could not write ENR to file: {:?}", e)) +} diff --git a/lighthouse/tests/exec.rs b/lighthouse/tests/exec.rs new file mode 100644 index 000000000..9526a1caf --- /dev/null +++ b/lighthouse/tests/exec.rs @@ -0,0 +1,113 @@ +use serde::de::DeserializeOwned; +use serde_json::from_reader; +use std::fs::File; +use std::path::PathBuf; +use std::process::{Command, Output}; +use std::str::from_utf8; +use tempfile::TempDir; + +pub trait CommandLineTestExec { + type Config: DeserializeOwned; + + fn cmd_mut(&mut self) -> &mut Command; + + /// Adds a flag with optional value to the command. + fn flag(&mut self, flag: &str, value: Option<&str>) -> &mut Self { + self.cmd_mut().arg(format!("--{}", flag)); + if let Some(value) = value { + self.cmd_mut().arg(value); + } + self + } + + /// Executes the `Command` returned by `Self::cmd_mut` with temporary data directory, dumps + /// the configuration and shuts down immediately. + /// + /// Options `--datadir`, `--dump-config` and `--immediate-shutdown` must not be set on the + /// command. + fn run(&mut self) -> CompletedTest { + // Setup temp directory. + let tmp_dir = TempDir::new().expect("Unable to create temporary directory"); + let tmp_config_path: PathBuf = tmp_dir.path().join("config.json"); + + // Add args --datadir --dump-config --immediate-shutdown + let cmd = self.cmd_mut(); + cmd.arg("--datadir") + .arg(tmp_dir.path().as_os_str()) + .arg(format!("--{}", "--dump-config")) + .arg(tmp_config_path.as_os_str()) + .arg("--immediate-shutdown"); + + // Run the command. + let output = output_result(cmd); + if let Err(e) = output { + panic!("{:?}", e); + } + + // Grab the config. + let config_file = File::open(tmp_config_path).expect("Unable to open dumped config"); + let config: Self::Config = from_reader(config_file).expect("Unable to deserialize config"); + + CompletedTest::new(config, tmp_dir) + } + + /// Executes the `Command` returned by `Self::cmd_mut` dumps the configuration and + /// shuts down immediately. + /// + /// Options `--dump-config` and `--immediate-shutdown` must not be set on the command. + fn run_with_no_datadir(&mut self) -> CompletedTest { + // Setup temp directory. + let tmp_dir = TempDir::new().expect("Unable to create temporary directory"); + let tmp_config_path: PathBuf = tmp_dir.path().join("config.json"); + + // Add args --datadir --dump-config --immediate-shutdown + let cmd = self.cmd_mut(); + cmd.arg(format!("--{}", "--dump-config")) + .arg(tmp_config_path.as_os_str()) + .arg("--immediate-shutdown"); + + // Run the command. + let output = output_result(cmd); + if let Err(e) = output { + panic!("{:?}", e); + } + + // Grab the config. + let config_file = File::open(tmp_config_path).expect("Unable to open dumped config"); + let config: Self::Config = from_reader(config_file).expect("Unable to deserialize config"); + + CompletedTest::new(config, tmp_dir) + } +} + +/// Executes a `Command`, returning a `Result` based upon the success exit code of the command. +fn output_result(cmd: &mut Command) -> Result { + let output = cmd.output().expect("should run command"); + + if output.status.success() { + Ok(output) + } else { + Err(from_utf8(&output.stderr) + .expect("stderr is not utf8") + .to_string()) + } +} + +pub struct CompletedTest { + config: C, + dir: TempDir, +} + +impl CompletedTest { + fn new(config: C, dir: TempDir) -> Self { + CompletedTest { config, dir } + } + + pub fn with_config(self, func: F) { + func(&self.config); + } + + pub fn with_config_and_dir(self, func: F) { + func(&self.config, &self.dir); + } +} diff --git a/lighthouse/tests/main.rs b/lighthouse/tests/main.rs index c800e855d..806524cab 100644 --- a/lighthouse/tests/main.rs +++ b/lighthouse/tests/main.rs @@ -2,4 +2,6 @@ mod account_manager; mod beacon_node; +mod boot_node; +mod exec; mod validator_client; diff --git a/lighthouse/tests/validator_client.rs b/lighthouse/tests/validator_client.rs index fc07492a9..eacc57d95 100644 --- a/lighthouse/tests/validator_client.rs +++ b/lighthouse/tests/validator_client.rs @@ -1,22 +1,16 @@ use validator_client::Config; +use crate::exec::CommandLineTestExec; use bls::{Keypair, PublicKeyBytes}; -use serde_json::from_reader; use std::fs::File; use std::io::Write; use std::net::Ipv4Addr; use std::path::PathBuf; -use std::process::{Command, Output}; -use std::str::from_utf8; +use std::process::Command; use std::string::ToString; use tempfile::TempDir; -const VALIDATOR_CMD: &str = "validator_client"; -const CONFIG_NAME: &str = "vc_dump.json"; -const DUMP_CONFIG_CMD: &str = "dump-config"; -const IMMEDIATE_SHUTDOWN_CMD: &str = "immediate-shutdown"; - -/// Returns the `lighthouse validator_client --immediate-shutdown` command. +/// Returns the `lighthouse validator_client` command. fn base_cmd() -> Command { let lighthouse_bin = env!("CARGO_BIN_EXE_lighthouse"); let path = lighthouse_bin @@ -24,25 +18,10 @@ fn base_cmd() -> Command { .expect("should parse CARGO_TARGET_DIR"); let mut cmd = Command::new(path); - cmd.arg(VALIDATOR_CMD) - .arg(format!("--{}", IMMEDIATE_SHUTDOWN_CMD)); - + cmd.arg("validator_client"); cmd } -/// Executes a `Command`, returning a `Result` based upon the success exit code of the command. -fn output_result(cmd: &mut Command) -> Result { - let output = cmd.output().expect("should run command"); - - if output.status.success() { - Ok(output) - } else { - Err(from_utf8(&output.stderr) - .expect("stderr is not utf8") - .to_string()) - } -} - // Wrapper around `Command` for easier Command Line Testing. struct CommandLineTest { cmd: Command, @@ -52,76 +31,13 @@ impl CommandLineTest { let base_cmd = base_cmd(); CommandLineTest { cmd: base_cmd } } - - fn flag(mut self, flag: &str, value: Option<&str>) -> Self { - // Build the command by adding the flag and any values. - self.cmd.arg(format!("--{}", flag)); - if let Some(value) = value { - self.cmd.arg(value); - } - self - } - - fn run(&mut self) -> CompletedTest { - // Setup temp directories. - let tmp_dir = TempDir::new().expect("Unable to create temporary directory"); - let tmp_path: PathBuf = tmp_dir.path().join(CONFIG_NAME); - - // Add --datadir --dump-config to cmd. - self.cmd - .arg("--datadir") - .arg(tmp_dir.path().as_os_str()) - .arg(format!("--{}", DUMP_CONFIG_CMD)) - .arg(tmp_path.as_os_str()); - - // Run the command. - let _output = output_result(&mut self.cmd).expect("Unable to run command"); - - // Grab the config. - let config: Config = - from_reader(File::open(tmp_path).expect("Unable to open dumped config")) - .expect("Unable to deserialize to ClientConfig"); - CompletedTest { - config, - dir: tmp_dir, - } - } - - // In order to test custom validator and secrets directory flags, - // datadir cannot be defined. - fn run_with_no_datadir(&mut self) -> CompletedTest { - // Setup temp directories - let tmp_dir = TempDir::new().expect("Unable to create temporary directory"); - let tmp_path: PathBuf = tmp_dir.path().join(CONFIG_NAME); - - // Add --dump-config to cmd. - self.cmd - .arg(format!("--{}", DUMP_CONFIG_CMD)) - .arg(tmp_path.as_os_str()); - - // Run the command. - let _output = output_result(&mut self.cmd).expect("Unable to run command"); - - // Grab the config. - let config: Config = - from_reader(File::open(tmp_path).expect("Unable to open dumped config")) - .expect("Unable to deserialize to ClientConfig"); - CompletedTest { - config, - dir: tmp_dir, - } - } } -struct CompletedTest { - config: Config, - dir: TempDir, -} -impl CompletedTest { - fn with_config(self, func: F) { - func(&self.config); - } - fn with_config_and_dir(self, func: F) { - func(&self.config, &self.dir); + +impl CommandLineTestExec for CommandLineTest { + type Config = Config; + + fn cmd_mut(&mut self) -> &mut Command { + &mut self.cmd } }