diff --git a/beacon_node/Cargo.toml b/beacon_node/Cargo.toml index 145151d438..8fc0d8f659 100644 --- a/beacon_node/Cargo.toml +++ b/beacon_node/Cargo.toml @@ -35,3 +35,4 @@ futures = "0.1.29" environment = { path = "../lighthouse/environment" } genesis = { path = "genesis" } eth2_testnet = { path = "../eth2/utils/eth2_testnet" } +eth2-libp2p = { path = "./eth2-libp2p" } diff --git a/beacon_node/client/src/config.rs b/beacon_node/client/src/config.rs index f94ea5ed55..08847e7b7a 100644 --- a/beacon_node/client/src/config.rs +++ b/beacon_node/client/src/config.rs @@ -41,8 +41,8 @@ impl Default for ClientGenesis { pub struct Config { pub data_dir: PathBuf, pub db_type: String, - db_name: String, - freezer_db_path: Option, + pub db_name: String, + pub freezer_db_path: Option, pub log_file: PathBuf, pub spec_constants: String, /// If true, the node will use co-ordinated junk for eth1 values. @@ -69,7 +69,7 @@ impl Default for Config { db_name: "chain_db".to_string(), freezer_db_path: None, genesis: <_>::default(), - network: NetworkConfig::new(), + network: NetworkConfig::default(), rest_api: <_>::default(), websocket_server: <_>::default(), spec_constants: TESTNET_SPEC_CONSTANTS.into(), @@ -135,26 +135,6 @@ impl Config { .ok_or_else(|| "Unable to locate user home directory".to_string())?; ensure_dir_exists(path) } - - /// Apply the following arguments to `self`, replacing values if they are specified in `args`. - /// - /// Returns an error if arguments are obviously invalid. May succeed even if some values are - /// invalid. - pub fn apply_cli_args(&mut self, args: &ArgMatches, _log: &slog::Logger) -> Result<(), String> { - if let Some(dir) = args.value_of("datadir") { - self.data_dir = PathBuf::from(dir); - }; - - if let Some(freezer_dir) = args.value_of("freezer-dir") { - self.freezer_db_path = Some(PathBuf::from(freezer_dir)); - } - - self.network.apply_cli_args(args)?; - self.rest_api.apply_cli_args(args)?; - self.websocket_server.apply_cli_args(args)?; - - Ok(()) - } } /// Ensure that the directory at `path` exists, by creating it and all parents if necessary. diff --git a/beacon_node/eth2-libp2p/Cargo.toml b/beacon_node/eth2-libp2p/Cargo.toml index 307f9fa463..847b9b0075 100644 --- a/beacon_node/eth2-libp2p/Cargo.toml +++ b/beacon_node/eth2-libp2p/Cargo.toml @@ -5,7 +5,6 @@ authors = ["Age Manning "] edition = "2018" [dependencies] -clap = "2.33.0" hex = "0.3" # rust-libp2p is presently being sourced from a Sigma Prime fork of the # `libp2p/rust-libp2p` repository. diff --git a/beacon_node/eth2-libp2p/src/config.rs b/beacon_node/eth2-libp2p/src/config.rs index 39391e5fd6..2eb7b78da8 100644 --- a/beacon_node/eth2-libp2p/src/config.rs +++ b/beacon_node/eth2-libp2p/src/config.rs @@ -1,4 +1,3 @@ -use clap::ArgMatches; use enr::Enr; use libp2p::gossipsub::{GossipsubConfig, GossipsubConfigBuilder}; use libp2p::Multiaddr; @@ -97,84 +96,3 @@ impl Default for Config { } } } - -/// Generates a default Config. -impl Config { - pub fn new() -> Self { - Config::default() - } - - pub fn apply_cli_args(&mut self, args: &ArgMatches) -> Result<(), String> { - // If a `datadir` has been specified, set the network dir to be inside it. - if let Some(dir) = args.value_of("datadir") { - self.network_dir = PathBuf::from(dir).join("network"); - }; - - // If a network dir has been specified, override the `datadir` definition. - if let Some(dir) = args.value_of("network-dir") { - self.network_dir = PathBuf::from(dir); - }; - - if let Some(listen_address_str) = args.value_of("listen-address") { - let listen_address = listen_address_str - .parse() - .map_err(|_| format!("Invalid listen address: {:?}", listen_address_str))?; - self.listen_address = listen_address; - self.discovery_address = listen_address; - } - - if let Some(max_peers_str) = args.value_of("maxpeers") { - self.max_peers = max_peers_str - .parse::() - .map_err(|_| format!("Invalid number of max peers: {}", max_peers_str))?; - } - - if let Some(port_str) = args.value_of("port") { - let port = port_str - .parse::() - .map_err(|_| format!("Invalid port: {}", port_str))?; - self.libp2p_port = port; - self.discovery_port = port; - } - - if let Some(boot_enr_str) = args.value_of("boot-nodes") { - self.boot_nodes = boot_enr_str - .split(',') - .map(|enr| enr.parse().map_err(|_| format!("Invalid ENR: {}", enr))) - .collect::, _>>()?; - } - - if let Some(libp2p_addresses_str) = args.value_of("libp2p-addresses") { - self.libp2p_nodes = libp2p_addresses_str - .split(',') - .map(|multiaddr| { - multiaddr - .parse() - .map_err(|_| format!("Invalid Multiaddr: {}", multiaddr)) - }) - .collect::, _>>()?; - } - - if let Some(topics_str) = args.value_of("topics") { - self.topics = topics_str.split(',').map(|s| s.into()).collect(); - } - - if let Some(discovery_address_str) = args.value_of("discovery-address") { - self.discovery_address = discovery_address_str - .parse() - .map_err(|_| format!("Invalid discovery address: {:?}", discovery_address_str))? - } - - if let Some(disc_port_str) = args.value_of("disc-port") { - self.discovery_port = disc_port_str - .parse::() - .map_err(|_| format!("Invalid discovery port: {}", disc_port_str))?; - } - - if let Some(p2p_priv_key) = args.value_of("p2p-priv-key") { - self.secret_key_hex = Some(p2p_priv_key.to_string()); - } - - Ok(()) - } -} diff --git a/beacon_node/rest_api/Cargo.toml b/beacon_node/rest_api/Cargo.toml index f9370e335d..7baa4dcddf 100644 --- a/beacon_node/rest_api/Cargo.toml +++ b/beacon_node/rest_api/Cargo.toml @@ -22,7 +22,6 @@ eth2_ssz = { path = "../../eth2/utils/ssz" } eth2_ssz_derive = { path = "../../eth2/utils/ssz_derive" } state_processing = { path = "../../eth2/state_processing" } types = { path = "../../eth2/types" } -clap = "2.33" http = "0.1" hyper = "0.12" exit-future = "0.1.4" diff --git a/beacon_node/rest_api/src/config.rs b/beacon_node/rest_api/src/config.rs index 1d7f50b6ee..9b99500e3d 100644 --- a/beacon_node/rest_api/src/config.rs +++ b/beacon_node/rest_api/src/config.rs @@ -1,4 +1,3 @@ -use clap::ArgMatches; use serde::{Deserialize, Serialize}; use std::net::Ipv4Addr; @@ -50,25 +49,3 @@ impl Default for Config { } } } - -impl Config { - pub fn apply_cli_args(&mut self, args: &ArgMatches) -> Result<(), &'static str> { - if args.is_present("no-api") { - self.enabled = false; - } - - if let Some(rpc_address) = args.value_of("api-address") { - self.listen_address = rpc_address - .parse::() - .map_err(|_| "api-address is not a valid IPv4 address.")?; - } - - if let Some(rpc_port) = args.value_of("api-port") { - self.port = rpc_port - .parse::() - .map_err(|_| "api-port is not a valid u16.")?; - } - - Ok(()) - } -} diff --git a/beacon_node/src/cli.rs b/beacon_node/src/cli.rs index 4bfe07a686..bf388edcf5 100644 --- a/beacon_node/src/cli.rs +++ b/beacon_node/src/cli.rs @@ -29,20 +29,19 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> { * Network parameters. */ .arg( - Arg::with_name("port-bump") - .long("port-bump") - .short("b") - .value_name("INCREMENT") - .help("Sets all listening TCP/UDP ports to default values, but with each port increased by \ - INCREMENT. Useful when starting multiple nodes on a single machine. Using increments \ - in multiples of 10 is recommended.") - .takes_value(true), + Arg::with_name("zero-ports") + .long("zero-ports") + .short("z") + .help("Sets all listening TCP/UDP ports to 0, allowing the OS to choose some \ + arbitrary free port number.") + .takes_value(false), ) .arg( Arg::with_name("listen-address") .long("listen-address") .value_name("ADDRESS") - .help("The address lighthouse will listen for UDP and TCP connections. (default 127.0.0.1).") + .help("The address lighthouse will listen for UDP and TCP connections.") + .default_value("127.0.0.1") .takes_value(true) ) .arg( @@ -50,13 +49,14 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> { .long("port") .value_name("PORT") .help("The TCP/UDP port to listen on. The UDP port can be modified by the --discovery-port flag.") - .conflicts_with("port-bump") + .default_value("9000") .takes_value(true), ) .arg( Arg::with_name("maxpeers") .long("maxpeers") - .help("The maximum number of peers (default 10).") + .help("The maximum number of peers.") + .default_value("10") .takes_value(true), ) .arg( @@ -72,64 +72,70 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> { .long("disc-port") .value_name("PORT") .help("The discovery UDP port.") - .conflicts_with("port-bump") + .default_value("9000") .takes_value(true), ) .arg( Arg::with_name("discovery-address") .long("discovery-address") .value_name("ADDRESS") - .help("The IP address to broadcast to other peers on how to reach this node.") + .help("The IP address to broadcast to other peers on how to reach this node. \ + Default is determined automatically.") .takes_value(true), ) .arg( Arg::with_name("topics") .long("topics") .value_name("STRING") - .help("One or more comma-delimited gossipsub topic strings to subscribe to.") + .help("One or more comma-delimited gossipsub topic strings to subscribe to. Default \ + is determined automatically.") .takes_value(true), ) .arg( Arg::with_name("libp2p-addresses") .long("libp2p-addresses") .value_name("MULTIADDR") - .help("One or more comma-delimited multiaddrs to manually connect to a libp2p peer without an ENR.") + .help("One or more comma-delimited multiaddrs to manually connect to a libp2p peer \ + without an ENR.") .takes_value(true), ) .arg( Arg::with_name("p2p-priv-key") .long("p2p-priv-key") .value_name("HEX") - .help("A secp256k1 secret key, represented as ASCII-encoded hex bytes (with or without 0x prefix).") + .help("A secp256k1 secret key, represented as ASCII-encoded hex bytes (with or \ + without 0x prefix). Default is either loaded from disk or generated \ + automatically.") .takes_value(true), ) /* REST API related arguments */ .arg( - Arg::with_name("no-api") - .long("no-api") - .help("Disable RESTful HTTP API server.") + Arg::with_name("http") + .long("http") + .help("Enable RESTful HTTP API server. Disabled by default.") .takes_value(false), ) .arg( - Arg::with_name("api-address") - .long("api-address") + Arg::with_name("http-address") + .long("http-address") .value_name("ADDRESS") .help("Set the listen address for the RESTful HTTP API server.") + .default_value("127.0.0.1") .takes_value(true), ) .arg( - Arg::with_name("api-port") - .long("api-port") + Arg::with_name("http-port") + .long("http-port") .value_name("PORT") .help("Set the listen TCP port for the RESTful HTTP API server.") - .conflicts_with("port-bump") + .default_value("5052") .takes_value(true), ) /* Websocket related arguments */ .arg( - Arg::with_name("no-ws") - .long("no-ws") - .help("Disable websocket server.") + Arg::with_name("ws") + .long("ws") + .help("Enable the websocket server. Disabled by default.") .takes_value(false), ) .arg( @@ -137,7 +143,7 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> { .long("ws-address") .value_name("ADDRESS") .help("Set the listen address for the websocket server.") - .conflicts_with_all(&["no-ws"]) + .default_value("127.0.0.1") .takes_value(true), ) .arg( @@ -145,16 +151,24 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> { .long("ws-port") .value_name("PORT") .help("Set the listen TCP port for the websocket server.") - .conflicts_with_all(&["no-ws", "port-bump"]) + .default_value("5053") .takes_value(true), ) /* * Eth1 Integration */ + .arg( + Arg::with_name("eth1") + .long("eth1") + .help("If present the node will connect to an eth1 node. This is required for \ + block production, you must use this flag if you wish to serve a validator.") + .takes_value(false), + ) .arg( Arg::with_name("dummy-eth1") .long("dummy-eth1") + .conflicts_with("eth1") .help("If present, uses an eth1 backend that generates static dummy data.\ Identical to the method used at the 2019 Canada interop.") ) @@ -166,32 +180,6 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> { .takes_value(true) .default_value("http://localhost:8545") ) - .arg( - Arg::with_name("eth1-follow") - .long("eth1-follow") - .value_name("BLOCK_COUNT") - .help("Specifies how many blocks we should cache behind the eth1 head. A larger number means a smaller cache.") - .takes_value(true) - // TODO: set this higher once we're not using testnets all the time. - .default_value("0") - ) - .arg( - Arg::with_name("deposit-contract") - .long("deposit-contract") - .short("e") - .value_name("DEPOSIT-CONTRACT") - .help("Specifies the deposit contract address on the Eth1 chain.") - .takes_value(true) - ) - .arg( - Arg::with_name("deposit-contract-deploy") - .long("deposit-contract-deploy") - .value_name("BLOCK_NUMBER") - .help("Specifies the block number that the deposit contract was deployed at.") - .takes_value(true) - // TODO: set this higher once we're not using testnets all the time. - .default_value("0") - ) /* * The "testnet" sub-command. * @@ -199,21 +187,6 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> { */ .subcommand(SubCommand::with_name("testnet") .about("Create a new Lighthouse datadir using a testnet strategy.") - .arg( - Arg::with_name("eth2-config") - .long("eth2-config") - .value_name("TOML_FILE") - .help("A existing eth2_spec TOML file (e.g., eth2_spec.toml).") - .takes_value(true) - .conflicts_with("spec") - ) - .arg( - Arg::with_name("client-config") - .long("client-config") - .value_name("TOML_FILE") - .help("An existing beacon_node TOML file (e.g., beacon_node.toml).") - .takes_value(true) - ) .arg( Arg::with_name("random-datadir") .long("random-datadir") @@ -221,13 +194,6 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> { .help("If present, append a random string to the datadir path. Useful for fast development \ iteration.") ) - .arg( - Arg::with_name("random-propagation") - .long("random-propagation") - .value_name("INTEGER") - .takes_value(true) - .help("Specifies (as a percentage) the likelihood of propagating blocks and attestations. This should only be used for testing networking elements. The value must like in the range 1-100.") - ) .arg( Arg::with_name("force") .long("force") @@ -236,33 +202,22 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> { backup directory.") .conflicts_with("random-datadir") ) + .arg( + Arg::with_name("random-propagation") + .long("random-propagation") + .value_name("INTEGER") + .takes_value(true) + .help("Specifies (as a percentage) the likelihood of propagating blocks and \ + attestations. This should only be used for testing networking elements. The \ + value must like in the range 1-100. Default is 100.") + ) .arg( Arg::with_name("slot-time") .long("slot-time") .short("t") .value_name("MILLISECONDS") - .help("Defines the slot time when creating a new testnet.") - ) - /* - * `boostrap` - * - * Start a new node by downloading genesis and network info from another node via the - * HTTP API. - */ - .subcommand(SubCommand::with_name("bootstrap") - .about("Connects to the given HTTP server, downloads a genesis state and attempts to peer with it.") - .arg(Arg::with_name("server") - .value_name("HTTP_SERVER") - .required(true) - .default_value("http://localhost:5052") - .help("A HTTP server, with a http:// prefix")) - .arg(Arg::with_name("libp2p-port") - .short("p") - .long("port") - .value_name("TCP_PORT") - .help("A libp2p listen port used to peer with the bootstrap server. This flag is useful \ - when port-fowarding is used: you may connect using a different port than \ - the one the server is immediately listening on.")) + .help("Defines the slot time when creating a new testnet. The default is \ + specified by the spec.") ) /* * `recent` @@ -282,7 +237,7 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> { .short("m") .value_name("MINUTES") .required(true) - .default_value("0") + .default_value("30") .help("The maximum number of minutes that will have elapsed before genesis")) ) /* @@ -320,25 +275,14 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> { .required(true) .help("A file from which to read the state")) ) - /* - * `eth1` - * - * Connect to a Sigma Prime testnet. - */ - .subcommand(SubCommand::with_name("eth1") - .about("Connect to a testnet defined by an eth2_testnet directory.") - .arg(Arg::with_name("directory") - .value_name("DIRECTORY") - .index(1) - .help("A directory generated by lcli. Defaults to ~/.lighthouse/testnet. See lcli testnet --help")) - ) /* * `prysm` * * Connect to the Prysmatic Labs testnet. */ .subcommand(SubCommand::with_name("prysm") - .about("Connect to the Prysmatic Labs testnet on Goerli.") + .about("Connect to the Prysmatic Labs testnet on Goerli. Not guaranteed to be \ + up-to-date or functioning.") ) ) } diff --git a/beacon_node/src/config.rs b/beacon_node/src/config.rs index 0c423423cf..faaa40de7e 100644 --- a/beacon_node/src/config.rs +++ b/beacon_node/src/config.rs @@ -1,19 +1,22 @@ use clap::ArgMatches; use client::{ClientConfig, ClientGenesis, Eth2Config}; use eth2_config::{read_from_file, write_to_file}; +use eth2_libp2p::{Enr, Multiaddr}; use eth2_testnet::Eth2TestnetDir; use genesis::recent_genesis_time; -use lighthouse_bootstrap::Bootstrapper; use rand::{distributions::Alphanumeric, Rng}; -use slog::{crit, info, warn, Logger}; +use slog::{crit, info, Logger}; use std::fs; use std::net::Ipv4Addr; -use std::path::{Path, PathBuf}; -use types::{Address, Epoch, Fork}; +use std::path::PathBuf; +use types::{Epoch, Fork}; -pub const DEFAULT_DATA_DIR: &str = ".lighthouse"; pub const CLIENT_CONFIG_FILENAME: &str = "beacon-node.toml"; pub const ETH2_CONFIG_FILENAME: &str = "eth2-spec.toml"; +pub const ETH2_TESTNET_DIR: &str = "testnet"; +pub const BEACON_NODE_DIR: &str = "beacon"; + +pub const SECONDS_PER_ETH1_BLOCK: u64 = 15; type Result = std::result::Result; type Config = (ClientConfig, Eth2Config, Logger); @@ -25,89 +28,375 @@ type Config = (ClientConfig, Eth2Config, Logger); /// The output of this function depends primarily upon the given `cli_args`, however it's behaviour /// may be influenced by other external services like the contents of the file system or the /// response of some remote server. -pub fn get_configs(cli_args: &ArgMatches, core_log: Logger) -> Result { +pub fn get_configs( + cli_args: &ArgMatches, + mut eth2_config: Eth2Config, + core_log: Logger, +) -> Result { let log = core_log.clone(); - let mut builder = ConfigBuilder::new(cli_args, core_log)?; + let mut client_config = ClientConfig::default(); + // Read the `--datadir` flag. + // + // If it's not present, try and find the home directory (`~`) and push the default data + // directory onto it. + // + // Note: the `config.data_dir` defines the _global_ lighthouse datadir, not just the beacon + // node specific datadir. + let data_dir: PathBuf = cli_args + .value_of("datadir") + .map(PathBuf::from) + .or_else(|| dirs::home_dir().map(|home| home.join(".lighthouse").join(BEACON_NODE_DIR))) + .ok_or_else(|| "Unable to find a home directory for the datadir".to_string())?; + + client_config.data_dir = data_dir; + + // When present, use an eth1 backend that generates deterministic junk. + // + // Useful for running testnets without the overhead of a deposit contract. if cli_args.is_present("dummy-eth1") { - builder.client_config.dummy_eth1_backend = true; + client_config.dummy_eth1_backend = true; } + // When present, attempt to sync to an eth1 node. + // + // Required for block production. + if cli_args.is_present("eth1") { + client_config.sync_eth1_chain = true; + } else { + client_config.sync_eth1_chain = false; + } + + /* + * Networking + */ + // If a `datadir` has been specified, set the network dir to be inside it. + if let Some(dir) = cli_args.value_of("datadir") { + client_config.network.network_dir = PathBuf::from(dir).join("network"); + }; + + // If a network dir has been specified, override the `datadir` definition. + if let Some(dir) = cli_args.value_of("network-dir") { + client_config.network.network_dir = PathBuf::from(dir); + }; + + if let Some(listen_address_str) = cli_args.value_of("listen-address") { + let listen_address = listen_address_str + .parse() + .map_err(|_| format!("Invalid listen address: {:?}", listen_address_str))?; + client_config.network.listen_address = listen_address; + client_config.network.discovery_address = listen_address; + } + + if let Some(max_peers_str) = cli_args.value_of("maxpeers") { + client_config.network.max_peers = max_peers_str + .parse::() + .map_err(|_| format!("Invalid number of max peers: {}", max_peers_str))?; + } + + if let Some(port_str) = cli_args.value_of("port") { + let port = port_str + .parse::() + .map_err(|_| format!("Invalid port: {}", port_str))?; + client_config.network.libp2p_port = port; + client_config.network.discovery_port = port; + } + + if let Some(boot_enr_str) = cli_args.value_of("boot-nodes") { + client_config.network.boot_nodes = boot_enr_str + .split(',') + .map(|enr| enr.parse().map_err(|_| format!("Invalid ENR: {}", enr))) + .collect::>>()?; + } + + if let Some(libp2p_addresses_str) = cli_args.value_of("libp2p-addresses") { + client_config.network.libp2p_nodes = libp2p_addresses_str + .split(',') + .map(|multiaddr| { + multiaddr + .parse() + .map_err(|_| format!("Invalid Multiaddr: {}", multiaddr)) + }) + .collect::>>()?; + } + + if let Some(topics_str) = cli_args.value_of("topics") { + client_config.network.topics = topics_str.split(',').map(|s| s.into()).collect(); + } + + if let Some(discovery_address_str) = cli_args.value_of("discovery-address") { + client_config.network.discovery_address = discovery_address_str + .parse() + .map_err(|_| format!("Invalid discovery address: {:?}", discovery_address_str))? + } + + if let Some(disc_port_str) = cli_args.value_of("disc-port") { + client_config.network.discovery_port = disc_port_str + .parse::() + .map_err(|_| format!("Invalid discovery port: {}", disc_port_str))?; + } + + if let Some(p2p_priv_key) = cli_args.value_of("p2p-priv-key") { + client_config.network.secret_key_hex = Some(p2p_priv_key.to_string()); + } + + /* + * Http server + */ + + if cli_args.is_present("http") { + client_config.rest_api.enabled = true; + } else { + client_config.rest_api.enabled = false; + } + + if let Some(address) = cli_args.value_of("api-address") { + client_config.rest_api.listen_address = address + .parse::() + .map_err(|_| "api-address is not a valid IPv4 address.")?; + } + + if let Some(port) = cli_args.value_of("api-port") { + client_config.rest_api.port = port + .parse::() + .map_err(|_| "api-port is not a valid u16.")?; + } + + /* + * Websocket server + */ + + if cli_args.is_present("ws") { + client_config.websocket_server.enabled = true; + } else { + client_config.websocket_server.enabled = false; + } + + if let Some(address) = cli_args.value_of("ws-address") { + client_config.websocket_server.listen_address = address + .parse::() + .map_err(|_| "ws-address is not a valid IPv4 address.")?; + } + + if let Some(port) = cli_args.value_of("ws-port") { + client_config.websocket_server.port = port + .parse::() + .map_err(|_| "ws-port is not a valid u16.")?; + } + + /* + * Eth1 + */ + + // Defines the URL to reach the eth1 node. if let Some(val) = cli_args.value_of("eth1-endpoint") { - builder.set_eth1_endpoint(val) - } - - if let Some(val) = cli_args.value_of("deposit-contract") { - builder.set_deposit_contract( - val.parse::
() - .map_err(|e| format!("Unable to parse deposit-contract address: {:?}", e))?, - ) - } - - if let Some(val) = cli_args.value_of("deposit-contract-deploy") { - builder.set_deposit_contract_deploy_block( - val.parse::() - .map_err(|e| format!("Unable to parse deposit-contract-deploy: {:?}", e))?, - ) - } - - if let Some(val) = cli_args.value_of("eth1-follow") { - builder.set_eth1_follow( - val.parse::() - .map_err(|e| format!("Unable to parse follow distance: {:?}", e))?, - ) + client_config.eth1.endpoint = val.to_string(); } match cli_args.subcommand() { ("testnet", Some(sub_cmd_args)) => { - process_testnet_subcommand(&mut builder, sub_cmd_args, &log)? + process_testnet_subcommand(&mut client_config, &mut eth2_config, sub_cmd_args)? } // No sub-command assumes a resume operation. _ => { info!( log, "Resuming from existing datadir"; - "path" => format!("{:?}", builder.client_config.data_dir) + "path" => format!("{:?}", client_config.data_dir) ); // If no primary subcommand was given, start the beacon chain from an existing // database. - builder.set_genesis(ClientGenesis::Resume); + client_config.genesis = ClientGenesis::Resume; // Whilst there is no large testnet or mainnet force the user to specify how they want // to start a new chain (e.g., from a genesis YAML file, another node, etc). - if !builder.client_config.data_dir.exists() { - return Err( - "No datadir found. To start a new beacon chain, see `testnet --help`. \ - Use `--datadir` to specify a different directory" - .into(), - ); + if !client_config.data_dir.exists() { + init_new_client(&mut client_config, &mut eth2_config)? + } else { + // If the `testnet` command was not provided, attempt to load an existing datadir and + // continue with an existing chain. + load_from_datadir(&mut client_config, &mut eth2_config)? } - - // If the `testnet` command was not provided, attempt to load an existing datadir and - // continue with an existing chain. - builder.load_from_datadir()?; } }; - builder.build(cli_args) + if let Some(freezer_dir) = cli_args.value_of("freezer-dir") { + client_config.freezer_db_path = Some(PathBuf::from(freezer_dir)); + } + + if eth2_config.spec_constants != client_config.spec_constants { + crit!(log, "Specification constants do not match."; + "client_config" => client_config.spec_constants.to_string(), + "eth2_config" => eth2_config.spec_constants.to_string() + ); + return Err("Specification constant mismatch".into()); + } + + /* + * Zero-ports + * + * Replaces previously set flags. + */ + if cli_args.is_present("zero-ports") { + client_config.network.libp2p_port = 0; + client_config.network.discovery_port = 0; + client_config.rest_api.port = 0; + client_config.websocket_server.port = 0; + } + + Ok((client_config, eth2_config, log)) +} + +/// Load from an existing database. +fn load_from_datadir(client_config: &mut ClientConfig, eth2_config: &mut Eth2Config) -> Result<()> { + // Check to ensure the datadir exists. + // + // For now we return an error. In the future we may decide to boot a default (e.g., + // public testnet or mainnet). + if !client_config.get_data_dir().map_or(false, |d| d.exists()) { + return Err( + "No datadir found. Either create a new testnet or specify a different `--datadir`." + .into(), + ); + } + + // If there is a path to a database in the config, ensure it exists. + if !client_config + .get_db_path() + .map_or(false, |path| path.exists()) + { + return Err( + "No database found in datadir. Use 'testnet -f' to overwrite the existing \ + datadir, or specify a different `--datadir`." + .into(), + ); + } + + let path = client_config.data_dir.join(CLIENT_CONFIG_FILENAME); + *client_config = read_from_file(path.clone()) + .map_err(|e| format!("Unable to parse {:?} file: {:?}", path, e))? + .ok_or_else(|| format!("{:?} file does not exist", path))?; + + let path = client_config.data_dir.join(ETH2_CONFIG_FILENAME); + *eth2_config = read_from_file(path.clone()) + .map_err(|e| format!("Unable to parse {:?} file: {:?}", path, e))? + .ok_or_else(|| format!("{:?} file does not exist", path))?; + + Ok(()) +} + +/// Create a new client with the default configuration. +fn init_new_client(client_config: &mut ClientConfig, eth2_config: &mut Eth2Config) -> Result<()> { + let spec = &mut eth2_config.spec; + + spec.min_deposit_amount = 100; + spec.max_effective_balance = 3_200_000_000; + spec.ejection_balance = 1_600_000_000; + spec.effective_balance_increment = 100_000_000; + spec.genesis_fork = Fork { + previous_version: [0; 4], + current_version: [0, 0, 0, 42], + epoch: Epoch::new(0), + }; + + let eth2_testnet_dir = Eth2TestnetDir::load(client_config.data_dir.join(ETH2_TESTNET_DIR)) + .map_err(|e| format!("Unable to open testnet dir: {}", e))?; + + client_config.eth1.deposit_contract_address = + format!("{:?}", eth2_testnet_dir.deposit_contract_address()?); + client_config.eth1.deposit_contract_deploy_block = + eth2_testnet_dir.deposit_contract_deploy_block; + spec.min_genesis_time = eth2_testnet_dir.min_genesis_time; + + client_config.eth1.follow_distance = 16; + client_config.dummy_eth1_backend = false; + client_config.sync_eth1_chain = true; + client_config.eth1.lowest_cached_block_number = client_config + .eth1 + .deposit_contract_deploy_block + .saturating_sub(client_config.eth1.follow_distance * 2); + + // Note: these constants _should_ only be used during genesis to determine the genesis + // time. This allows the testnet to start shortly after the time + validator count + // conditions are satisfied, not 1-2 days. + // + // This value must be at least 2x the `ETH1_FOLLOW_DISTANCE` otherwise `all_eth1_data` + // can become a subset of `new_eth1_data`. + // + // See: + // https://github.com/ethereum/eth2.0-specs/blob/v0.9.2/specs/validator/0_beacon-chain-validator.md#eth1-data + // TODO: set from 10 back to 2 + spec.seconds_per_day = SECONDS_PER_ETH1_BLOCK * spec.eth1_follow_distance * 2; + + client_config.genesis = ClientGenesis::DepositContract; + + create_new_datadir(&client_config, ð2_config)?; + + Ok(()) +} + +/// Writes the configs in `self` to `self.data_dir`. +/// +/// Returns an error if `self.data_dir` already exists. +pub fn create_new_datadir(client_config: &ClientConfig, eth2_config: &Eth2Config) -> Result<()> { + if client_config.data_dir.exists() { + return Err(format!( + "Data dir already exists at {:?}", + client_config.data_dir + ))?; + } + + // Create `datadir` and any non-existing parent directories. + fs::create_dir_all(&client_config.data_dir) + .map_err(|e| format!("Failed to create data dir: {}", e))?; + + macro_rules! write_to_file { + ($file: ident, $variable: ident) => { + let file = client_config.data_dir.join($file); + if file.exists() { + return Err(format!("Datadir is not clean, {} exists.", $file)); + } else { + // Write the onfig to a TOML file in the datadir. + write_to_file(client_config.data_dir.join($file), $variable) + .map_err(|e| format!("Unable to write {} file: {:?}", $file, e))?; + } + }; + } + + write_to_file!(CLIENT_CONFIG_FILENAME, client_config); + write_to_file!(ETH2_CONFIG_FILENAME, eth2_config); + + Ok(()) } /// Process the `testnet` CLI subcommand arguments, updating the `builder`. fn process_testnet_subcommand( - builder: &mut ConfigBuilder, + client_config: &mut ClientConfig, + eth2_config: &mut Eth2Config, cli_args: &ArgMatches, - log: &Logger, ) -> Result<()> { + // Specifies that a random datadir should be used. if cli_args.is_present("random-datadir") { - builder.set_random_datadir()?; + client_config + .data_dir + .push(format!("random_{}", random_string(6))); + client_config.network.network_dir = client_config.data_dir.join("network"); } + // Deletes the existing datadir. if cli_args.is_present("force") { - builder.clean_datadir()?; + if client_config.data_dir.exists() { + fs::remove_dir_all(&client_config.data_dir) + .map_err(|e| format!("Unable to delete existing datadir: {:?}", e))?; + } } + // Define a percentage of messages that should be propogated, useful for simulating bad network + // conditions. + // + // WARNING: setting this to anything less than 100 will cause bad behaviour. if let Some(propagation_percentage_string) = cli_args.value_of("random-propagation") { let percentage = propagation_percentage_string .parse::() @@ -115,72 +404,20 @@ fn process_testnet_subcommand( if percentage > 100 { return Err(format!("Propagation percentage greater than 100")); } - builder.client_config.network.propagation_percentage = Some(percentage); - } - - let is_bootstrap = cli_args.subcommand_name() == Some("bootstrap"); - - if let Some(path_string) = cli_args.value_of("eth2-config") { - if is_bootstrap { - return Err("Cannot supply --eth2-config when using bootstrap".to_string()); - } - - let path = path_string - .parse::() - .map_err(|e| format!("Unable to parse eth2-config path: {:?}", e))?; - builder.load_eth2_config(path)?; - } else { - builder.update_spec_from_subcommand(&cli_args)?; + client_config.network.propagation_percentage = Some(percentage); } + // Modify the `SECONDS_PER_SLOT` "constant". if let Some(slot_time) = cli_args.value_of("slot-time") { - if is_bootstrap { - return Err("Cannot supply --slot-time flag whilst using bootstrap.".into()); - } - let slot_time = slot_time .parse::() .map_err(|e| format!("Unable to parse slot-time: {:?}", e))?; - builder.set_slot_time(slot_time); + eth2_config.spec.milliseconds_per_slot = slot_time; } - if let Some(path_string) = cli_args.value_of("client-config") { - let path = path_string - .parse::() - .map_err(|e| format!("Unable to parse client config path: {:?}", e))?; - builder.load_client_config(path)?; - } - - info!( - log, - "Creating new datadir"; - "path" => format!("{:?}", builder.client_config.data_dir) - ); - - // When using the testnet command we listen on all addresses. - builder.set_listen_addresses("0.0.0.0".into())?; - warn!(log, "All services listening on 0.0.0.0"); - // Start matching on the second subcommand (e.g., `testnet bootstrap ...`). match cli_args.subcommand() { - ("bootstrap", Some(cli_args)) => { - let server = cli_args - .value_of("server") - .ok_or_else(|| "No bootstrap server specified")?; - let port: Option = cli_args - .value_of("libp2p-port") - .and_then(|s| s.parse::().ok()); - - builder.import_bootstrap_libp2p_address(server, port)?; - builder.import_bootstrap_enr_address(server)?; - builder.import_bootstrap_eth2_config(server)?; - - builder.set_genesis(ClientGenesis::RemoteNode { - server: server.to_string(), - port, - }) - } ("recent", Some(cli_args)) => { let validator_count = cli_args .value_of("validator_count") @@ -194,12 +431,12 @@ fn process_testnet_subcommand( .parse::() .map_err(|e| format!("Unable to parse minutes: {:?}", e))?; - builder.client_config.dummy_eth1_backend = true; + client_config.dummy_eth1_backend = true; - builder.set_genesis(ClientGenesis::Interop { + client_config.genesis = ClientGenesis::Interop { validator_count, genesis_time: recent_genesis_time(minutes), - }) + }; } ("quick", Some(cli_args)) => { let validator_count = cli_args @@ -214,12 +451,12 @@ fn process_testnet_subcommand( .parse::() .map_err(|e| format!("Unable to parse genesis time: {:?}", e))?; - builder.client_config.dummy_eth1_backend = true; + client_config.dummy_eth1_backend = true; - builder.set_genesis(ClientGenesis::Interop { + client_config.genesis = ClientGenesis::Interop { validator_count, genesis_time, - }) + }; } ("file", Some(cli_args)) => { let path = cli_args @@ -237,11 +474,10 @@ fn process_testnet_subcommand( other => return Err(format!("Unknown genesis file format: {}", other)), }; - builder.set_genesis(start_method) + client_config.genesis = start_method; } ("prysm", Some(_)) => { - let mut spec = &mut builder.eth2_config.spec; - let mut client_config = &mut builder.client_config; + let mut spec = &mut eth2_config.spec; spec.min_deposit_amount = 100; spec.max_effective_balance = 3_200_000_000; @@ -260,58 +496,7 @@ fn process_testnet_subcommand( client_config.eth1.follow_distance = 16; client_config.dummy_eth1_backend = false; - builder.set_genesis(ClientGenesis::DepositContract) - } - ("eth1", Some(_)) => { - let mut spec = &mut builder.eth2_config.spec; - let mut client_config = &mut builder.client_config; - - let directory: PathBuf = cli_args - .value_of("datadir") - .map(PathBuf::from) - .unwrap_or_else(|| client_config.data_dir.join("testnet")); - - let eth2_testnet_dir = Eth2TestnetDir::load(directory) - .map_err(|e| format!("Unable to open testnet dir: {}", e))?; - - spec.min_deposit_amount = 100; - spec.max_effective_balance = 3_200_000_000; - spec.ejection_balance = 1_600_000_000; - spec.effective_balance_increment = 100_000_000; - spec.genesis_fork = Fork { - previous_version: [0; 4], - current_version: [0, 0, 0, 42], - epoch: Epoch::new(0), - }; - - client_config.eth1.deposit_contract_address = - format!("{:?}", eth2_testnet_dir.deposit_contract_address()?); - client_config.eth1.deposit_contract_deploy_block = - eth2_testnet_dir.deposit_contract_deploy_block; - spec.min_genesis_time = eth2_testnet_dir.min_genesis_time; - - client_config.eth1.follow_distance = 16; - client_config.dummy_eth1_backend = false; - client_config.sync_eth1_chain = true; - client_config.eth1.lowest_cached_block_number = client_config - .eth1 - .deposit_contract_deploy_block - .saturating_sub(client_config.eth1.follow_distance * 2); - - // Note: these constants _should_ only be used during genesis to determine the genesis - // time. This allows the testnet to start shortly after the time + validator count - // conditions are satisfied, not 1-2 days. - // - // This value must be at least 2x the `ETH1_FOLLOW_DISTANCE` otherwise `all_eth1_data` - // can become a subset of `new_eth1_data`. - // - // See: - // https://github.com/ethereum/eth2.0-specs/blob/v0.9.2/specs/validator/0_beacon-chain-validator.md#eth1-data - const SECONDS_PER_ETH1_BLOCK: u64 = 15; - // TODO: set from 10 back to 2 - spec.seconds_per_day = SECONDS_PER_ETH1_BLOCK * spec.eth1_follow_distance * 2; - - builder.set_genesis(ClientGenesis::DepositContract) + client_config.genesis = ClientGenesis::DepositContract; } (cmd, Some(_)) => { return Err(format!( @@ -322,369 +507,11 @@ fn process_testnet_subcommand( _ => return Err("No testnet method specified. See 'testnet --help'.".into()), }; - builder.write_configs_to_new_datadir()?; + create_new_datadir(&client_config, ð2_config)?; Ok(()) } -/// Allows for building a set of configurations based upon `clap` arguments. -struct ConfigBuilder { - log: Logger, - pub eth2_config: Eth2Config, - pub client_config: ClientConfig, -} - -impl ConfigBuilder { - /// Create a new builder with default settings. - pub fn new(cli_args: &ArgMatches, log: Logger) -> Result { - // Read the `--datadir` flag. - // - // If it's not present, try and find the home directory (`~`) and push the default data - // directory onto it. - let data_dir: PathBuf = cli_args - .value_of("datadir") - .map(PathBuf::from) - .or_else(|| { - dirs::home_dir().map(|mut home| { - home.push(DEFAULT_DATA_DIR); - home - }) - }) - .ok_or_else(|| "Unable to find a home directory for the datadir".to_string())?; - - let mut client_config = ClientConfig::default(); - client_config.data_dir = data_dir; - - Ok(Self { - log, - eth2_config: Eth2Config::minimal(), - client_config, - }) - } - - /// Clears any configuration files that would interfere with writing new configs. - /// - /// Moves the following files in `data_dir` into a backup directory: - /// - /// - Client config - /// - Eth2 config - /// - All database directories - pub fn clean_datadir(&mut self) -> Result<()> { - let backup_dir = { - let mut s = String::from("backup_"); - s.push_str(&random_string(6)); - self.client_config.data_dir.join(s) - }; - - fs::create_dir_all(&backup_dir) - .map_err(|e| format!("Unable to create config backup dir: {:?}", e))?; - - let move_to_backup_dir = |path: &Path| -> Result<()> { - let file_name = path - .file_name() - .ok_or_else(|| "Invalid path found during datadir clean (no filename).")?; - - let mut new = path.to_path_buf(); - new.pop(); - new.push(backup_dir.clone()); - new.push(file_name); - - let _ = fs::rename(path, new); - - Ok(()) - }; - - move_to_backup_dir(&self.client_config.data_dir.join(CLIENT_CONFIG_FILENAME))?; - move_to_backup_dir(&self.client_config.data_dir.join(ETH2_CONFIG_FILENAME))?; - move_to_backup_dir(&self.client_config.create_db_path()?)?; - move_to_backup_dir(&self.client_config.create_freezer_db_path()?)?; - - Ok(()) - } - - pub fn set_eth1_endpoint(&mut self, endpoint: &str) { - self.client_config.eth1.endpoint = endpoint.to_string(); - } - - pub fn set_deposit_contract(&mut self, deposit_contract: Address) { - self.client_config.eth1.deposit_contract_address = format!("{:?}", deposit_contract); - } - - pub fn set_deposit_contract_deploy_block(&mut self, eth1_block_number: u64) { - self.client_config.eth1.deposit_contract_deploy_block = eth1_block_number; - } - - pub fn set_eth1_follow(&mut self, distance: u64) { - self.client_config.eth1.follow_distance = distance; - } - - pub fn set_genesis(&mut self, method: ClientGenesis) { - self.client_config.genesis = method; - } - - /// Import the libp2p address for `server` into the list of libp2p nodes to connect with. - /// - /// If `port` is `Some`, it is used as the port for the `Multiaddr`. If `port` is `None`, - /// attempts to connect to the `server` via HTTP and retrieve it's libp2p listen port. - pub fn import_bootstrap_libp2p_address( - &mut self, - server: &str, - port: Option, - ) -> Result<()> { - let bootstrapper = Bootstrapper::connect(server.to_string(), &self.log)?; - - if let Some(server_multiaddr) = bootstrapper.best_effort_multiaddr(port) { - info!( - self.log, - "Estimated bootstrapper libp2p address"; - "multiaddr" => format!("{:?}", server_multiaddr) - ); - - self.client_config - .network - .libp2p_nodes - .push(server_multiaddr); - } else { - warn!( - self.log, - "Unable to estimate a bootstrapper libp2p address, this node may not find any peers." - ); - }; - - Ok(()) - } - - /// Import the enr address for `server` into the list of initial enrs (boot nodes). - pub fn import_bootstrap_enr_address(&mut self, server: &str) -> Result<()> { - let bootstrapper = Bootstrapper::connect(server.to_string(), &self.log)?; - - if let Ok(enr) = bootstrapper.enr() { - info!( - self.log, - "Loaded bootstrapper libp2p address"; - "enr" => format!("{:?}", enr) - ); - - self.client_config.network.boot_nodes.push(enr); - } else { - warn!( - self.log, - "Unable to estimate a bootstrapper enr address, this node may not find any peers." - ); - }; - - Ok(()) - } - - /// Set the config data_dir to be an random directory. - /// - /// Useful for easily spinning up ephemeral testnets. - pub fn set_random_datadir(&mut self) -> Result<()> { - self.client_config - .data_dir - .push(format!("random_{}", random_string(6))); - self.client_config.network.network_dir = self.client_config.data_dir.join("network"); - - Ok(()) - } - - /// Imports an `Eth2Config` from `server`, returning an error if this fails. - pub fn import_bootstrap_eth2_config(&mut self, server: &str) -> Result<()> { - let bootstrapper = Bootstrapper::connect(server.to_string(), &self.log)?; - - self.update_eth2_config(bootstrapper.eth2_config()?); - - Ok(()) - } - - fn update_eth2_config(&mut self, eth2_config: Eth2Config) { - self.eth2_config = eth2_config; - } - - fn set_slot_time(&mut self, milliseconds_per_slot: u64) { - self.eth2_config.spec.milliseconds_per_slot = milliseconds_per_slot; - } - - /// Reads the subcommand and tries to update `self.eth2_config` based up on the `--spec` flag. - /// - /// Returns an error if the `--spec` flag is not present in the given `cli_args`. - pub fn update_spec_from_subcommand(&mut self, cli_args: &ArgMatches) -> Result<()> { - // Re-initialise the `Eth2Config`. - // - // If a CLI parameter is set, overwrite any config file present. - // If a parameter is not set, use either the config file present or default to minimal. - let eth2_config = match cli_args.value_of("spec") { - Some("mainnet") => Eth2Config::mainnet(), - Some("minimal") => Eth2Config::minimal(), - Some("interop") => Eth2Config::interop(), - _ => return Err("Unable to determine specification type.".into()), - }; - - self.client_config.spec_constants = cli_args - .value_of("spec") - .expect("Guarded by prior match statement") - .to_string(); - self.eth2_config = eth2_config; - - Ok(()) - } - - /// Writes the configs in `self` to `self.data_dir`. - /// - /// Returns an error if `self.data_dir` already exists. - pub fn write_configs_to_new_datadir(&mut self) -> Result<()> { - let db_exists = self - .client_config - .get_db_path() - .map(|d| d.exists()) - .unwrap_or_else(|| false); - - // Do not permit creating a new config when the datadir exists. - if db_exists { - return Err("Database already exists. See `-f` or `-r` in `testnet --help`".into()); - } - - // Create `datadir` and any non-existing parent directories. - fs::create_dir_all(&self.client_config.data_dir).map_err(|e| { - crit!(self.log, "Failed to initialize data dir"; "error" => format!("{}", e)); - format!("{}", e) - })?; - - let client_config_file = self.client_config.data_dir.join(CLIENT_CONFIG_FILENAME); - if client_config_file.exists() { - return Err(format!( - "Datadir is not clean, {} exists. See `-f` in `testnet --help`.", - CLIENT_CONFIG_FILENAME - )); - } else { - // Write the onfig to a TOML file in the datadir. - write_to_file( - self.client_config.data_dir.join(CLIENT_CONFIG_FILENAME), - &self.client_config, - ) - .map_err(|e| format!("Unable to write {} file: {:?}", CLIENT_CONFIG_FILENAME, e))?; - } - - let eth2_config_file = self.client_config.data_dir.join(ETH2_CONFIG_FILENAME); - if eth2_config_file.exists() { - return Err(format!( - "Datadir is not clean, {} exists. See `-f` in `testnet --help`.", - ETH2_CONFIG_FILENAME - )); - } else { - // Write the config to a TOML file in the datadir. - write_to_file( - self.client_config.data_dir.join(ETH2_CONFIG_FILENAME), - &self.eth2_config, - ) - .map_err(|e| format!("Unable to write {} file: {:?}", ETH2_CONFIG_FILENAME, e))?; - } - - Ok(()) - } - - /// Attempts to load the client and eth2 configs from `self.data_dir`. - /// - /// Returns an error if any files are not found or are invalid. - pub fn load_from_datadir(&mut self) -> Result<()> { - // Check to ensure the datadir exists. - // - // For now we return an error. In the future we may decide to boot a default (e.g., - // public testnet or mainnet). - if !self - .client_config - .get_data_dir() - .map_or(false, |d| d.exists()) - { - return Err( - "No datadir found. Either create a new testnet or specify a different `--datadir`." - .into(), - ); - } - - // If there is a path to a database in the config, ensure it exists. - if !self - .client_config - .get_db_path() - .map_or(false, |path| path.exists()) - { - return Err( - "No database found in datadir. Use 'testnet -f' to overwrite the existing \ - datadir, or specify a different `--datadir`." - .into(), - ); - } - - self.load_eth2_config(self.client_config.data_dir.join(ETH2_CONFIG_FILENAME))?; - self.load_client_config(self.client_config.data_dir.join(CLIENT_CONFIG_FILENAME))?; - - Ok(()) - } - - /// Attempts to load the client config from `path`. - /// - /// Returns an error if any files are not found or are invalid. - pub fn load_client_config(&mut self, path: PathBuf) -> Result<()> { - self.client_config = read_from_file::(path.clone()) - .map_err(|e| format!("Unable to parse {:?} file: {:?}", path, e))? - .ok_or_else(|| format!("{:?} file does not exist", path))?; - - Ok(()) - } - - /// Attempts to load the eth2 config from `path`. - /// - /// Returns an error if any files are not found or are invalid. - pub fn load_eth2_config(&mut self, path: PathBuf) -> Result<()> { - self.eth2_config = read_from_file::(path.clone()) - .map_err(|e| format!("Unable to parse {:?} file: {:?}", path, e))? - .ok_or_else(|| format!("{:?} file does not exist", path))?; - - Ok(()) - } - - /// Sets all listening addresses to the given `addr`. - pub fn set_listen_addresses(&mut self, addr: String) -> Result<()> { - let addr = addr - .parse::() - .map_err(|e| format!("Unable to parse default listen address: {:?}", e))?; - - self.client_config.network.listen_address = addr.into(); - self.client_config.rest_api.listen_address = addr; - - Ok(()) - } - - /// Consumes self, returning the configs. - /// - /// The supplied `cli_args` should be the base-level `clap` cli_args (i.e., not a subcommand - /// cli_args). - pub fn build(mut self, cli_args: &ArgMatches) -> Result { - self.client_config.apply_cli_args(cli_args, &mut self.log)?; - - if let Some(bump) = cli_args.value_of("port-bump") { - let bump = bump - .parse::() - .map_err(|e| format!("Unable to parse port bump: {}", e))?; - - self.client_config.network.libp2p_port += bump; - self.client_config.network.discovery_port += bump; - self.client_config.rest_api.port += bump; - self.client_config.websocket_server.port += bump; - } - - if self.eth2_config.spec_constants != self.client_config.spec_constants { - crit!(self.log, "Specification constants do not match."; - "client_config" => self.client_config.spec_constants.to_string(), - "eth2_config" => self.eth2_config.spec_constants.to_string() - ); - return Err("Specification constant mismatch".into()); - } - - Ok((self.client_config, self.eth2_config, self.log)) - } -} - fn random_string(len: usize) -> String { rand::thread_rng() .sample_iter(&Alphanumeric) diff --git a/beacon_node/src/lib.rs b/beacon_node/src/lib.rs index 6797095b62..f127cd93bf 100644 --- a/beacon_node/src/lib.rs +++ b/beacon_node/src/lib.rs @@ -57,15 +57,15 @@ impl ProductionBeaconNode { ) -> impl Future + 'a { let log = context.log.clone(); - // TODO: the eth2 config in the env is being completely ignored. + // TODO: the eth2 config in the env is being modified. // // See https://github.com/sigp/lighthouse/issues/602 - get_configs(&matches, log).into_future().and_then( - move |(client_config, eth2_config, _log)| { + get_configs(&matches, context.eth2_config.clone(), log) + .into_future() + .and_then(move |(client_config, eth2_config, _log)| { context.eth2_config = eth2_config; Self::new(context, client_config) - }, - ) + }) } /// Starts a new beacon node `Client` in the given `environment`. diff --git a/beacon_node/websocket_server/Cargo.toml b/beacon_node/websocket_server/Cargo.toml index 9ea8d9b650..f45c718e1c 100644 --- a/beacon_node/websocket_server/Cargo.toml +++ b/beacon_node/websocket_server/Cargo.toml @@ -7,7 +7,6 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -clap = "2.33.0" exit-future = "0.1.4" futures = "0.1.29" serde = "1.0.102" diff --git a/beacon_node/websocket_server/src/config.rs b/beacon_node/websocket_server/src/config.rs index c07f0da838..f521ca33d4 100644 --- a/beacon_node/websocket_server/src/config.rs +++ b/beacon_node/websocket_server/src/config.rs @@ -1,5 +1,4 @@ -use clap::ArgMatches; -use serde::{Deserialize, Serialize}; +use serde_derive::{Deserialize, Serialize}; use std::net::Ipv4Addr; /// The core configuration of a Lighthouse beacon node. @@ -21,25 +20,3 @@ impl Default for Config { } } } - -impl Config { - pub fn apply_cli_args(&mut self, args: &ArgMatches) -> Result<(), &'static str> { - if args.is_present("no-ws") { - self.enabled = false; - } - - if let Some(rpc_address) = args.value_of("ws-address") { - self.listen_address = rpc_address - .parse::() - .map_err(|_| "ws-address is not a valid IPv4 address.")?; - } - - if let Some(rpc_port) = args.value_of("ws-port") { - self.port = rpc_port - .parse::() - .map_err(|_| "ws-port is not a valid u16.")?; - } - - Ok(()) - } -} diff --git a/eth2/utils/eth2_testnet/Cargo.toml b/eth2/utils/eth2_testnet/Cargo.toml index 5fcdd3d0c3..543b23fc12 100644 --- a/eth2/utils/eth2_testnet/Cargo.toml +++ b/eth2/utils/eth2_testnet/Cargo.toml @@ -13,3 +13,4 @@ tempdir = "0.3" serde = "1.0" serde_json = "^1.0" types = { path = "../../types"} +eth2-libp2p = { path = "../../../beacon_node/eth2-libp2p"} diff --git a/eth2/utils/eth2_testnet/src/lib.rs b/eth2/utils/eth2_testnet/src/lib.rs index 43f3f832a8..b911c242cd 100644 --- a/eth2/utils/eth2_testnet/src/lib.rs +++ b/eth2/utils/eth2_testnet/src/lib.rs @@ -7,6 +7,7 @@ //! //! https://github.com/sigp/lighthouse/pull/605 +use eth2_libp2p::Enr; use std::fs::{create_dir_all, File}; use std::path::PathBuf; use types::Address; @@ -14,12 +15,14 @@ use types::Address; pub const ADDRESS_FILE: &str = "deposit_contract.txt"; pub const DEPLOY_BLOCK_FILE: &str = "deploy_block.txt"; pub const MIN_GENESIS_TIME_FILE: &str = "min_genesis_time.txt"; +pub const BOOT_NODES_FILE: &str = "boot_nodes.json"; #[derive(Clone, PartialEq, Debug)] pub struct Eth2TestnetDir { deposit_contract_address: String, pub deposit_contract_deploy_block: u64, pub min_genesis_time: u64, + pub boot_nodes: Vec, } impl Eth2TestnetDir { @@ -28,6 +31,7 @@ impl Eth2TestnetDir { deposit_contract_address: String, deposit_contract_deploy_block: u64, min_genesis_time: u64, + boot_nodes: Vec, ) -> Result { if base_dir.exists() { return Err("Testnet directory already exists".to_string()); @@ -36,60 +40,52 @@ impl Eth2TestnetDir { create_dir_all(&base_dir) .map_err(|e| format!("Unable to create testnet directory: {:?}", e))?; - File::create(base_dir.join(ADDRESS_FILE)) - .map_err(|e| format!("Unable to create {}: {:?}", ADDRESS_FILE, e)) - .and_then(|file| { - serde_json::to_writer(file, &deposit_contract_address) - .map_err(|e| format!("Unable to write {}: {:?}", ADDRESS_FILE, e)) - })?; + macro_rules! write_to_file { + ($file: ident, $variable: ident) => { + File::create(base_dir.join($file)) + .map_err(|e| format!("Unable to create {}: {:?}", $file, e)) + .and_then(|file| { + serde_json::to_writer(file, &$variable) + .map_err(|e| format!("Unable to write {}: {:?}", $file, e)) + })?; + }; + } - File::create(base_dir.join(DEPLOY_BLOCK_FILE)) - .map_err(|e| format!("Unable to create {}: {:?}", DEPLOY_BLOCK_FILE, e)) - .and_then(|file| { - serde_json::to_writer(file, &deposit_contract_deploy_block) - .map_err(|e| format!("Unable to write {}: {:?}", DEPLOY_BLOCK_FILE, e)) - })?; - - File::create(base_dir.join(MIN_GENESIS_TIME_FILE)) - .map_err(|e| format!("Unable to create {}: {:?}", MIN_GENESIS_TIME_FILE, e)) - .and_then(|file| { - serde_json::to_writer(file, &min_genesis_time) - .map_err(|e| format!("Unable to write {}: {:?}", MIN_GENESIS_TIME_FILE, e)) - })?; + write_to_file!(ADDRESS_FILE, deposit_contract_address); + write_to_file!(DEPLOY_BLOCK_FILE, deposit_contract_deploy_block); + write_to_file!(MIN_GENESIS_TIME_FILE, min_genesis_time); + write_to_file!(BOOT_NODES_FILE, boot_nodes); Ok(Self { deposit_contract_address, deposit_contract_deploy_block, min_genesis_time, + boot_nodes, }) } pub fn load(base_dir: PathBuf) -> Result { - let deposit_contract_address = File::open(base_dir.join(ADDRESS_FILE)) - .map_err(|e| format!("Unable to open {}: {:?}", ADDRESS_FILE, e)) - .and_then(|file| { - serde_json::from_reader(file) - .map_err(|e| format!("Unable to parse {}: {:?}", ADDRESS_FILE, e)) - })?; + macro_rules! load_from_file { + ($file: ident) => { + File::open(base_dir.join($file)) + .map_err(|e| format!("Unable to open {}: {:?}", $file, e)) + .and_then(|file| { + serde_json::from_reader(file) + .map_err(|e| format!("Unable to parse {}: {:?}", $file, e)) + })?; + }; + } - let deposit_contract_deploy_block = File::open(base_dir.join(DEPLOY_BLOCK_FILE)) - .map_err(|e| format!("Unable to open {}: {:?}", DEPLOY_BLOCK_FILE, e)) - .and_then(|file| { - serde_json::from_reader(file) - .map_err(|e| format!("Unable to parse {}: {:?}", DEPLOY_BLOCK_FILE, e)) - })?; - - let min_genesis_time = File::open(base_dir.join(MIN_GENESIS_TIME_FILE)) - .map_err(|e| format!("Unable to open {}: {:?}", MIN_GENESIS_TIME_FILE, e)) - .and_then(|file| { - serde_json::from_reader(file) - .map_err(|e| format!("Unable to parse {}: {:?}", MIN_GENESIS_TIME_FILE, e)) - })?; + let deposit_contract_address = load_from_file!(ADDRESS_FILE); + let deposit_contract_deploy_block = load_from_file!(DEPLOY_BLOCK_FILE); + let min_genesis_time = load_from_file!(MIN_GENESIS_TIME_FILE); + let boot_nodes = load_from_file!(BOOT_NODES_FILE); Ok(Self { deposit_contract_address, deposit_contract_deploy_block, min_genesis_time, + boot_nodes, }) } @@ -122,6 +118,8 @@ mod tests { deposit_contract_address.clone(), deposit_contract_deploy_block, min_genesis_time, + // TODO: add some Enr for testing. + vec![], ) .expect("should create struct"); diff --git a/lighthouse/src/main.rs b/lighthouse/src/main.rs index 20e65fbb4f..d2fda08852 100644 --- a/lighthouse/src/main.rs +++ b/lighthouse/src/main.rs @@ -57,7 +57,7 @@ fn main() { .short("d") .value_name("DIR") .global(true) - .help("Data directory for keys and databases.") + .help("Data directory for lighthouse keys and databases.") .takes_value(true), ) .subcommand(beacon_node::cli_app())