Re-arrange CLI to suit new "testnet" pattern
This commit is contained in:
parent
bb63d300f2
commit
453c8e2255
@ -11,6 +11,7 @@ store = { path = "./store" }
|
||||
client = { path = "client" }
|
||||
version = { path = "version" }
|
||||
clap = "2.32.0"
|
||||
rand = "0.7"
|
||||
slog = { version = "^2.2.3" , features = ["max_level_trace"] }
|
||||
slog-term = "^2.4.0"
|
||||
slog-async = "^2.3.0"
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::{Bootstrapper, Eth2Config};
|
||||
use crate::Bootstrapper;
|
||||
use clap::ArgMatches;
|
||||
use network::NetworkConfig;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
@ -127,15 +127,6 @@ impl Config {
|
||||
self.data_dir = PathBuf::from(dir);
|
||||
};
|
||||
|
||||
if let Some(default_spec) = args.value_of("default-spec") {
|
||||
match default_spec {
|
||||
"mainnet" => self.spec_constants = Eth2Config::mainnet().spec_constants,
|
||||
"minimal" => self.spec_constants = Eth2Config::minimal().spec_constants,
|
||||
"interop" => self.spec_constants = Eth2Config::interop().spec_constants,
|
||||
_ => {} // not supported
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(dir) = args.value_of("db") {
|
||||
self.db_type = dir.to_string();
|
||||
};
|
||||
|
206
beacon_node/src/config.rs
Normal file
206
beacon_node/src/config.rs
Normal file
@ -0,0 +1,206 @@
|
||||
use clap::ArgMatches;
|
||||
use client::{ClientConfig, Eth2Config};
|
||||
use eth2_config::{read_from_file, write_to_file};
|
||||
use rand::{distributions::Alphanumeric, Rng};
|
||||
use slog::{crit, info, Logger};
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
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";
|
||||
|
||||
type Result<T> = std::result::Result<T, String>;
|
||||
type Config = (ClientConfig, Eth2Config);
|
||||
|
||||
/// Gets the fully-initialized global client and eth2 configuration objects.
|
||||
pub fn get_configs(matches: &ArgMatches, log: &Logger) -> Result<Config> {
|
||||
let mut builder = ConfigBuilder::new(matches, log)?;
|
||||
|
||||
match matches.subcommand() {
|
||||
("testnet", Some(sub_matches)) => {
|
||||
if sub_matches.is_present("random-datadir") {
|
||||
builder.set_random_datadir()?;
|
||||
}
|
||||
|
||||
info!(
|
||||
log,
|
||||
"Creating new datadir";
|
||||
"path" => format!("{:?}", builder.data_dir)
|
||||
);
|
||||
|
||||
builder.update_spec_from_subcommand(&sub_matches)?;
|
||||
builder.write_configs_to_new_datadir()?;
|
||||
}
|
||||
_ => {
|
||||
info!(
|
||||
log,
|
||||
"Resuming from existing datadir";
|
||||
"path" => format!("{:?}", builder.data_dir)
|
||||
);
|
||||
|
||||
// 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()
|
||||
}
|
||||
|
||||
/// Allows for building a set of configurations based upon `clap` arguments.
|
||||
struct ConfigBuilder<'a> {
|
||||
matches: &'a ArgMatches<'a>,
|
||||
log: &'a Logger,
|
||||
pub data_dir: PathBuf,
|
||||
eth2_config: Eth2Config,
|
||||
client_config: ClientConfig,
|
||||
}
|
||||
|
||||
impl<'a> ConfigBuilder<'a> {
|
||||
/// Create a new builder with default settings.
|
||||
pub fn new(matches: &'a ArgMatches, log: &'a Logger) -> Result<Self> {
|
||||
// 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 = matches
|
||||
.value_of("datadir")
|
||||
.map(|string| PathBuf::from(string))
|
||||
.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())?;
|
||||
|
||||
Ok(Self {
|
||||
matches,
|
||||
log,
|
||||
data_dir,
|
||||
eth2_config: Eth2Config::minimal(),
|
||||
client_config: ClientConfig::default(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Consumes self, returning the configs.
|
||||
pub fn build(mut self) -> Result<Config> {
|
||||
self.eth2_config.apply_cli_args(&self.matches)?;
|
||||
self.client_config
|
||||
.apply_cli_args(&self.matches, &mut self.log.clone())?;
|
||||
|
||||
if self.eth2_config.spec_constants != self.client_config.spec_constants {
|
||||
crit!(self.log, "Specification constants do not match.";
|
||||
"client_config" => format!("{}", self.client_config.spec_constants),
|
||||
"eth2_config" => format!("{}", self.eth2_config.spec_constants)
|
||||
);
|
||||
return Err("Specification constant mismatch".into());
|
||||
}
|
||||
|
||||
self.client_config.data_dir = self.data_dir;
|
||||
|
||||
Ok((self.client_config, self.eth2_config))
|
||||
}
|
||||
|
||||
/// 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<()> {
|
||||
let random = rand::thread_rng()
|
||||
.sample_iter(&Alphanumeric)
|
||||
.take(10)
|
||||
.collect::<String>();
|
||||
|
||||
let mut s = DEFAULT_DATA_DIR.to_string();
|
||||
s.push_str("_random_");
|
||||
s.push_str(&random);
|
||||
|
||||
self.data_dir.pop();
|
||||
self.data_dir.push(s);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 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.
|
||||
pub fn update_spec_from_subcommand(&mut self, sub_matches: &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 sub_matches.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 = sub_matches
|
||||
.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<()> {
|
||||
// Do not permit creating a new config when the datadir exists.
|
||||
if self.data_dir.exists() {
|
||||
return Err(
|
||||
"Datadir already exists, will not overwrite. Remove the directory or use --datadir."
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
|
||||
// Create `datadir` and any non-existing parent directories.
|
||||
fs::create_dir_all(&self.data_dir).map_err(|e| {
|
||||
crit!(self.log, "Failed to initialize data dir"; "error" => format!("{}", e));
|
||||
format!("{}", e)
|
||||
})?;
|
||||
|
||||
// Write the client config to a TOML file in the datadir.
|
||||
write_to_file(
|
||||
self.data_dir.join(CLIENT_CONFIG_FILENAME),
|
||||
&self.client_config,
|
||||
)
|
||||
.map_err(|e| format!("Unable to write {} file: {:?}", CLIENT_CONFIG_FILENAME, e))?;
|
||||
|
||||
// Write the eth2 config to a TOML file in the datadir.
|
||||
write_to_file(self.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.data_dir.exists() {
|
||||
return Err(
|
||||
"No datadir found. Use the 'testnet' sub-command to select a testnet type.".into(),
|
||||
);
|
||||
}
|
||||
|
||||
self.eth2_config = read_from_file::<Eth2Config>(self.data_dir.join(ETH2_CONFIG_FILENAME))
|
||||
.map_err(|e| format!("Unable to parse {} file: {:?}", ETH2_CONFIG_FILENAME, e))?
|
||||
.ok_or_else(|| format!("{} file does not exist", ETH2_CONFIG_FILENAME))?;
|
||||
|
||||
self.client_config =
|
||||
read_from_file::<ClientConfig>(self.data_dir.join(CLIENT_CONFIG_FILENAME))
|
||||
.map_err(|e| format!("Unable to parse {} file: {:?}", CLIENT_CONFIG_FILENAME, e))?
|
||||
.ok_or_else(|| format!("{} file does not exist", ETH2_CONFIG_FILENAME))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
@ -1,12 +1,10 @@
|
||||
mod config;
|
||||
mod run;
|
||||
|
||||
use clap::{App, Arg};
|
||||
use client::{ClientConfig, Eth2Config};
|
||||
use clap::{App, Arg, SubCommand};
|
||||
use config::get_configs;
|
||||
use env_logger::{Builder, Env};
|
||||
use eth2_config::{read_from_file, write_to_file};
|
||||
use slog::{crit, o, warn, Drain, Level};
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub const DEFAULT_DATA_DIR: &str = ".lighthouse";
|
||||
|
||||
@ -31,6 +29,7 @@ fn main() {
|
||||
.value_name("DIR")
|
||||
.help("Data directory for keys and databases.")
|
||||
.takes_value(true)
|
||||
.global(true)
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("logfile")
|
||||
@ -45,6 +44,7 @@ fn main() {
|
||||
.value_name("NETWORK-DIR")
|
||||
.help("Data directory for network keys.")
|
||||
.takes_value(true)
|
||||
.global(true)
|
||||
)
|
||||
/*
|
||||
* Network parameters.
|
||||
@ -163,24 +163,6 @@ fn main() {
|
||||
.possible_values(&["disk", "memory"])
|
||||
.default_value("memory"),
|
||||
)
|
||||
/*
|
||||
* Specification/testnet params.
|
||||
*/
|
||||
.arg(
|
||||
Arg::with_name("default-spec")
|
||||
.long("default-spec")
|
||||
.value_name("TITLE")
|
||||
.short("default-spec")
|
||||
.help("Specifies the default eth2 spec to be used. This will override any spec written to disk and will therefore be used by default in future instances.")
|
||||
.takes_value(true)
|
||||
.possible_values(&["mainnet", "minimal", "interop"])
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("recent-genesis")
|
||||
.long("recent-genesis")
|
||||
.short("r")
|
||||
.help("When present, genesis will be within 30 minutes prior. Only for testing"),
|
||||
)
|
||||
/*
|
||||
* Logging.
|
||||
*/
|
||||
@ -201,14 +183,68 @@ fn main() {
|
||||
.takes_value(true),
|
||||
)
|
||||
/*
|
||||
* Bootstrap.
|
||||
* The "testnet" sub-command.
|
||||
*
|
||||
* Allows for creating a new datadir with testnet-specific configs.
|
||||
*/
|
||||
.subcommand(SubCommand::with_name("testnet")
|
||||
.about("Create a new Lighthouse datadir using a testnet strategy.")
|
||||
.arg(
|
||||
Arg::with_name("bootstrap")
|
||||
.long("bootstrap")
|
||||
.value_name("HTTP_SERVER")
|
||||
.help("Load the genesis state and libp2p address from the HTTP API of another Lighthouse node.")
|
||||
Arg::with_name("spec")
|
||||
.short("s")
|
||||
.long("spec")
|
||||
.value_name("TITLE")
|
||||
.help("Specifies the default eth2 spec type. Only effective when creating a new datadir.")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.possible_values(&["mainnet", "minimal", "interop"])
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("random-datadir")
|
||||
.long("random-datadir")
|
||||
.short("r")
|
||||
.help("If present, append a random string to the datadir path. Useful for fast development \
|
||||
iteration.")
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("force-create")
|
||||
.long("force-create")
|
||||
.short("f")
|
||||
.help("If present, will delete any existing datadir before creating a new one. Cannot be \
|
||||
used when specifying --random-datadir (logic error).")
|
||||
.conflicts_with("random-datadir")
|
||||
)
|
||||
/*
|
||||
* Testnet sub-commands.
|
||||
*/
|
||||
.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)
|
||||
.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"))
|
||||
)
|
||||
.subcommand(SubCommand::with_name("recent")
|
||||
.about("Creates a new genesis state where the genesis time was at the previous \
|
||||
30-minute boundary (e.g., 12:00, 12:30, 13:00, etc.)")
|
||||
.arg(Arg::with_name("validator_count")
|
||||
.value_name("VALIDATOR_COUNT")
|
||||
.required(true)
|
||||
.help("The number of validators in the genesis state"))
|
||||
)
|
||||
.subcommand(SubCommand::with_name("yaml-genesis-state")
|
||||
.about("Creates a new datadir where the genesis state is read from YAML. Will fail to parse \
|
||||
a YAML state that was generated to a different spec than that specified by --spec.")
|
||||
.arg(Arg::with_name("file")
|
||||
.value_name("YAML_FILE")
|
||||
.required(true)
|
||||
.help("A YAML file from which to read the state"))
|
||||
)
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
@ -235,143 +271,24 @@ fn main() {
|
||||
_ => drain.filter_level(Level::Trace),
|
||||
};
|
||||
|
||||
let mut log = slog::Logger::root(drain.fuse(), o!());
|
||||
let log = slog::Logger::root(drain.fuse(), o!());
|
||||
|
||||
warn!(
|
||||
log,
|
||||
"Ethereum 2.0 is pre-release. This software is experimental."
|
||||
);
|
||||
|
||||
let data_dir = match matches
|
||||
.value_of("datadir")
|
||||
.and_then(|v| Some(PathBuf::from(v)))
|
||||
{
|
||||
Some(v) => v,
|
||||
None => {
|
||||
// use the default
|
||||
let mut default_dir = match dirs::home_dir() {
|
||||
Some(v) => v,
|
||||
None => {
|
||||
crit!(log, "Failed to find a home directory");
|
||||
return;
|
||||
}
|
||||
};
|
||||
default_dir.push(DEFAULT_DATA_DIR);
|
||||
default_dir
|
||||
}
|
||||
};
|
||||
|
||||
// create the directory if needed
|
||||
match fs::create_dir_all(&data_dir) {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
crit!(log, "Failed to initialize data dir"; "error" => format!("{}", e));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let client_config_path = data_dir.join(CLIENT_CONFIG_FILENAME);
|
||||
|
||||
// Attempt to load the `ClientConfig` from disk.
|
||||
// Load the process-wide configuration.
|
||||
//
|
||||
// If file doesn't exist, create a new, default one.
|
||||
let mut client_config = match read_from_file::<ClientConfig>(client_config_path.clone()) {
|
||||
Ok(Some(c)) => c,
|
||||
Ok(None) => {
|
||||
let default = ClientConfig::default();
|
||||
if let Err(e) = write_to_file(client_config_path, &default) {
|
||||
crit!(log, "Failed to write default ClientConfig to file"; "error" => format!("{:?}", e));
|
||||
return;
|
||||
}
|
||||
default
|
||||
}
|
||||
// May load this from disk or create a new configuration, depending on the CLI flags supplied.
|
||||
let (client_config, eth2_config) = match get_configs(&matches, &log) {
|
||||
Ok(configs) => configs,
|
||||
Err(e) => {
|
||||
crit!(log, "Failed to load a ChainConfig file"; "error" => format!("{:?}", e));
|
||||
crit!(log, "Failed to load configuration"; "error" => e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// Ensure the `data_dir` in the config matches that supplied to the CLI.
|
||||
client_config.data_dir = data_dir.clone();
|
||||
|
||||
// Update the client config with any CLI args.
|
||||
match client_config.apply_cli_args(&matches, &mut log) {
|
||||
Ok(()) => (),
|
||||
Err(s) => {
|
||||
crit!(log, "Failed to parse ClientConfig CLI arguments"; "error" => s);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let eth2_config_path = data_dir.join(ETH2_CONFIG_FILENAME);
|
||||
|
||||
// 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 cli_config = match matches.value_of("default-spec") {
|
||||
Some("mainnet") => Some(Eth2Config::mainnet()),
|
||||
Some("minimal") => Some(Eth2Config::minimal()),
|
||||
Some("interop") => Some(Eth2Config::interop()),
|
||||
_ => None,
|
||||
};
|
||||
// if a CLI flag is specified, write the new config if it doesn't exist,
|
||||
// otherwise notify the user that the file will not be written.
|
||||
let eth2_config_from_file = match read_from_file::<Eth2Config>(eth2_config_path.clone()) {
|
||||
Ok(config) => config,
|
||||
Err(e) => {
|
||||
crit!(log, "Failed to read the Eth2Config from file"; "error" => format!("{:?}", e));
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let mut eth2_config = {
|
||||
if let Some(cli_config) = cli_config {
|
||||
if eth2_config_from_file.is_none() {
|
||||
// write to file if one doesn't exist
|
||||
if let Err(e) = write_to_file(eth2_config_path, &cli_config) {
|
||||
crit!(log, "Failed to write default Eth2Config to file"; "error" => format!("{:?}", e));
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
warn!(
|
||||
log,
|
||||
"Eth2Config file exists. Configuration file is ignored, using default"
|
||||
);
|
||||
}
|
||||
cli_config
|
||||
} else {
|
||||
// CLI config not specified, read from disk
|
||||
match eth2_config_from_file {
|
||||
Some(config) => config,
|
||||
None => {
|
||||
// set default to minimal
|
||||
let eth2_config = Eth2Config::minimal();
|
||||
if let Err(e) = write_to_file(eth2_config_path, ð2_config) {
|
||||
crit!(log, "Failed to write default Eth2Config to file"; "error" => format!("{:?}", e));
|
||||
return;
|
||||
}
|
||||
eth2_config
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Update the eth2 config with any CLI flags.
|
||||
match eth2_config.apply_cli_args(&matches) {
|
||||
Ok(()) => (),
|
||||
Err(s) => {
|
||||
crit!(log, "Failed to parse Eth2Config CLI arguments"; "error" => s);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// check to ensure the spec constants between the client and eth2_config match
|
||||
if eth2_config.spec_constants != client_config.spec_constants {
|
||||
crit!(log, "Specification constants do not match."; "client_config" => format!("{}", client_config.spec_constants), "eth2_config" => format!("{}", eth2_config.spec_constants));
|
||||
return;
|
||||
}
|
||||
|
||||
// Start the node using a `tokio` executor.
|
||||
match run::run_beacon_node(client_config, eth2_config, &log) {
|
||||
Ok(_) => {}
|
||||
|
@ -46,7 +46,6 @@ pub fn run_beacon_node(
|
||||
log,
|
||||
"BeaconNode init";
|
||||
"p2p_listen_address" => format!("{:?}", &other_client_config.network.listen_address),
|
||||
"data_dir" => format!("{:?}", other_client_config.data_dir()),
|
||||
"network_dir" => format!("{:?}", other_client_config.network.network_dir),
|
||||
"spec_constants" => &spec_constants,
|
||||
"db_type" => &other_client_config.db_type,
|
||||
|
Loading…
Reference in New Issue
Block a user