mirror of
https://github.com/sigp/lighthouse.git
synced 2026-05-07 16:55:46 +00:00
Refactor beacon node CLI
This commit is contained in:
@@ -35,3 +35,4 @@ futures = "0.1.29"
|
|||||||
environment = { path = "../lighthouse/environment" }
|
environment = { path = "../lighthouse/environment" }
|
||||||
genesis = { path = "genesis" }
|
genesis = { path = "genesis" }
|
||||||
eth2_testnet = { path = "../eth2/utils/eth2_testnet" }
|
eth2_testnet = { path = "../eth2/utils/eth2_testnet" }
|
||||||
|
eth2-libp2p = { path = "./eth2-libp2p" }
|
||||||
|
|||||||
@@ -41,8 +41,8 @@ impl Default for ClientGenesis {
|
|||||||
pub struct Config {
|
pub struct Config {
|
||||||
pub data_dir: PathBuf,
|
pub data_dir: PathBuf,
|
||||||
pub db_type: String,
|
pub db_type: String,
|
||||||
db_name: String,
|
pub db_name: String,
|
||||||
freezer_db_path: Option<PathBuf>,
|
pub freezer_db_path: Option<PathBuf>,
|
||||||
pub log_file: PathBuf,
|
pub log_file: PathBuf,
|
||||||
pub spec_constants: String,
|
pub spec_constants: String,
|
||||||
/// If true, the node will use co-ordinated junk for eth1 values.
|
/// 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(),
|
db_name: "chain_db".to_string(),
|
||||||
freezer_db_path: None,
|
freezer_db_path: None,
|
||||||
genesis: <_>::default(),
|
genesis: <_>::default(),
|
||||||
network: NetworkConfig::new(),
|
network: NetworkConfig::default(),
|
||||||
rest_api: <_>::default(),
|
rest_api: <_>::default(),
|
||||||
websocket_server: <_>::default(),
|
websocket_server: <_>::default(),
|
||||||
spec_constants: TESTNET_SPEC_CONSTANTS.into(),
|
spec_constants: TESTNET_SPEC_CONSTANTS.into(),
|
||||||
@@ -135,26 +135,6 @@ impl Config {
|
|||||||
.ok_or_else(|| "Unable to locate user home directory".to_string())?;
|
.ok_or_else(|| "Unable to locate user home directory".to_string())?;
|
||||||
ensure_dir_exists(path)
|
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.
|
/// Ensure that the directory at `path` exists, by creating it and all parents if necessary.
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ authors = ["Age Manning <Age@AgeManning.com>"]
|
|||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap = "2.33.0"
|
|
||||||
hex = "0.3"
|
hex = "0.3"
|
||||||
# rust-libp2p is presently being sourced from a Sigma Prime fork of the
|
# rust-libp2p is presently being sourced from a Sigma Prime fork of the
|
||||||
# `libp2p/rust-libp2p` repository.
|
# `libp2p/rust-libp2p` repository.
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
use clap::ArgMatches;
|
|
||||||
use enr::Enr;
|
use enr::Enr;
|
||||||
use libp2p::gossipsub::{GossipsubConfig, GossipsubConfigBuilder};
|
use libp2p::gossipsub::{GossipsubConfig, GossipsubConfigBuilder};
|
||||||
use libp2p::Multiaddr;
|
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::<usize>()
|
|
||||||
.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::<u16>()
|
|
||||||
.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::<Result<Vec<Enr>, _>>()?;
|
|
||||||
}
|
|
||||||
|
|
||||||
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::<Result<Vec<Multiaddr>, _>>()?;
|
|
||||||
}
|
|
||||||
|
|
||||||
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::<u16>()
|
|
||||||
.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(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ eth2_ssz = { path = "../../eth2/utils/ssz" }
|
|||||||
eth2_ssz_derive = { path = "../../eth2/utils/ssz_derive" }
|
eth2_ssz_derive = { path = "../../eth2/utils/ssz_derive" }
|
||||||
state_processing = { path = "../../eth2/state_processing" }
|
state_processing = { path = "../../eth2/state_processing" }
|
||||||
types = { path = "../../eth2/types" }
|
types = { path = "../../eth2/types" }
|
||||||
clap = "2.33"
|
|
||||||
http = "0.1"
|
http = "0.1"
|
||||||
hyper = "0.12"
|
hyper = "0.12"
|
||||||
exit-future = "0.1.4"
|
exit-future = "0.1.4"
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
use clap::ArgMatches;
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::net::Ipv4Addr;
|
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::<Ipv4Addr>()
|
|
||||||
.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::<u16>()
|
|
||||||
.map_err(|_| "api-port is not a valid u16.")?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -29,20 +29,19 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
|
|||||||
* Network parameters.
|
* Network parameters.
|
||||||
*/
|
*/
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name("port-bump")
|
Arg::with_name("zero-ports")
|
||||||
.long("port-bump")
|
.long("zero-ports")
|
||||||
.short("b")
|
.short("z")
|
||||||
.value_name("INCREMENT")
|
.help("Sets all listening TCP/UDP ports to 0, allowing the OS to choose some \
|
||||||
.help("Sets all listening TCP/UDP ports to default values, but with each port increased by \
|
arbitrary free port number.")
|
||||||
INCREMENT. Useful when starting multiple nodes on a single machine. Using increments \
|
.takes_value(false),
|
||||||
in multiples of 10 is recommended.")
|
|
||||||
.takes_value(true),
|
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name("listen-address")
|
Arg::with_name("listen-address")
|
||||||
.long("listen-address")
|
.long("listen-address")
|
||||||
.value_name("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)
|
.takes_value(true)
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
@@ -50,13 +49,14 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
|
|||||||
.long("port")
|
.long("port")
|
||||||
.value_name("PORT")
|
.value_name("PORT")
|
||||||
.help("The TCP/UDP port to listen on. The UDP port can be modified by the --discovery-port flag.")
|
.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),
|
.takes_value(true),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name("maxpeers")
|
Arg::with_name("maxpeers")
|
||||||
.long("maxpeers")
|
.long("maxpeers")
|
||||||
.help("The maximum number of peers (default 10).")
|
.help("The maximum number of peers.")
|
||||||
|
.default_value("10")
|
||||||
.takes_value(true),
|
.takes_value(true),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
@@ -72,64 +72,70 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
|
|||||||
.long("disc-port")
|
.long("disc-port")
|
||||||
.value_name("PORT")
|
.value_name("PORT")
|
||||||
.help("The discovery UDP port.")
|
.help("The discovery UDP port.")
|
||||||
.conflicts_with("port-bump")
|
.default_value("9000")
|
||||||
.takes_value(true),
|
.takes_value(true),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name("discovery-address")
|
Arg::with_name("discovery-address")
|
||||||
.long("discovery-address")
|
.long("discovery-address")
|
||||||
.value_name("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),
|
.takes_value(true),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name("topics")
|
Arg::with_name("topics")
|
||||||
.long("topics")
|
.long("topics")
|
||||||
.value_name("STRING")
|
.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),
|
.takes_value(true),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name("libp2p-addresses")
|
Arg::with_name("libp2p-addresses")
|
||||||
.long("libp2p-addresses")
|
.long("libp2p-addresses")
|
||||||
.value_name("MULTIADDR")
|
.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),
|
.takes_value(true),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name("p2p-priv-key")
|
Arg::with_name("p2p-priv-key")
|
||||||
.long("p2p-priv-key")
|
.long("p2p-priv-key")
|
||||||
.value_name("HEX")
|
.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),
|
.takes_value(true),
|
||||||
)
|
)
|
||||||
/* REST API related arguments */
|
/* REST API related arguments */
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name("no-api")
|
Arg::with_name("http")
|
||||||
.long("no-api")
|
.long("http")
|
||||||
.help("Disable RESTful HTTP API server.")
|
.help("Enable RESTful HTTP API server. Disabled by default.")
|
||||||
.takes_value(false),
|
.takes_value(false),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name("api-address")
|
Arg::with_name("http-address")
|
||||||
.long("api-address")
|
.long("http-address")
|
||||||
.value_name("ADDRESS")
|
.value_name("ADDRESS")
|
||||||
.help("Set the listen address for the RESTful HTTP API server.")
|
.help("Set the listen address for the RESTful HTTP API server.")
|
||||||
|
.default_value("127.0.0.1")
|
||||||
.takes_value(true),
|
.takes_value(true),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name("api-port")
|
Arg::with_name("http-port")
|
||||||
.long("api-port")
|
.long("http-port")
|
||||||
.value_name("PORT")
|
.value_name("PORT")
|
||||||
.help("Set the listen TCP port for the RESTful HTTP API server.")
|
.help("Set the listen TCP port for the RESTful HTTP API server.")
|
||||||
.conflicts_with("port-bump")
|
.default_value("5052")
|
||||||
.takes_value(true),
|
.takes_value(true),
|
||||||
)
|
)
|
||||||
/* Websocket related arguments */
|
/* Websocket related arguments */
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name("no-ws")
|
Arg::with_name("ws")
|
||||||
.long("no-ws")
|
.long("ws")
|
||||||
.help("Disable websocket server.")
|
.help("Enable the websocket server. Disabled by default.")
|
||||||
.takes_value(false),
|
.takes_value(false),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
@@ -137,7 +143,7 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
|
|||||||
.long("ws-address")
|
.long("ws-address")
|
||||||
.value_name("ADDRESS")
|
.value_name("ADDRESS")
|
||||||
.help("Set the listen address for the websocket server.")
|
.help("Set the listen address for the websocket server.")
|
||||||
.conflicts_with_all(&["no-ws"])
|
.default_value("127.0.0.1")
|
||||||
.takes_value(true),
|
.takes_value(true),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
@@ -145,16 +151,24 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
|
|||||||
.long("ws-port")
|
.long("ws-port")
|
||||||
.value_name("PORT")
|
.value_name("PORT")
|
||||||
.help("Set the listen TCP port for the websocket server.")
|
.help("Set the listen TCP port for the websocket server.")
|
||||||
.conflicts_with_all(&["no-ws", "port-bump"])
|
.default_value("5053")
|
||||||
.takes_value(true),
|
.takes_value(true),
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Eth1 Integration
|
* 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(
|
||||||
Arg::with_name("dummy-eth1")
|
Arg::with_name("dummy-eth1")
|
||||||
.long("dummy-eth1")
|
.long("dummy-eth1")
|
||||||
|
.conflicts_with("eth1")
|
||||||
.help("If present, uses an eth1 backend that generates static dummy data.\
|
.help("If present, uses an eth1 backend that generates static dummy data.\
|
||||||
Identical to the method used at the 2019 Canada interop.")
|
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)
|
.takes_value(true)
|
||||||
.default_value("http://localhost:8545")
|
.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.
|
* The "testnet" sub-command.
|
||||||
*
|
*
|
||||||
@@ -199,21 +187,6 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
|
|||||||
*/
|
*/
|
||||||
.subcommand(SubCommand::with_name("testnet")
|
.subcommand(SubCommand::with_name("testnet")
|
||||||
.about("Create a new Lighthouse datadir using a testnet strategy.")
|
.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(
|
||||||
Arg::with_name("random-datadir")
|
Arg::with_name("random-datadir")
|
||||||
.long("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 \
|
.help("If present, append a random string to the datadir path. Useful for fast development \
|
||||||
iteration.")
|
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(
|
||||||
Arg::with_name("force")
|
Arg::with_name("force")
|
||||||
.long("force")
|
.long("force")
|
||||||
@@ -236,33 +202,22 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
|
|||||||
backup directory.")
|
backup directory.")
|
||||||
.conflicts_with("random-datadir")
|
.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(
|
||||||
Arg::with_name("slot-time")
|
Arg::with_name("slot-time")
|
||||||
.long("slot-time")
|
.long("slot-time")
|
||||||
.short("t")
|
.short("t")
|
||||||
.value_name("MILLISECONDS")
|
.value_name("MILLISECONDS")
|
||||||
.help("Defines the slot time when creating a new testnet.")
|
.help("Defines the slot time when creating a new testnet. The default is \
|
||||||
)
|
specified by the spec.")
|
||||||
/*
|
|
||||||
* `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."))
|
|
||||||
)
|
)
|
||||||
/*
|
/*
|
||||||
* `recent`
|
* `recent`
|
||||||
@@ -282,7 +237,7 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
|
|||||||
.short("m")
|
.short("m")
|
||||||
.value_name("MINUTES")
|
.value_name("MINUTES")
|
||||||
.required(true)
|
.required(true)
|
||||||
.default_value("0")
|
.default_value("30")
|
||||||
.help("The maximum number of minutes that will have elapsed before genesis"))
|
.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)
|
.required(true)
|
||||||
.help("A file from which to read the state"))
|
.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`
|
* `prysm`
|
||||||
*
|
*
|
||||||
* Connect to the Prysmatic Labs testnet.
|
* Connect to the Prysmatic Labs testnet.
|
||||||
*/
|
*/
|
||||||
.subcommand(SubCommand::with_name("prysm")
|
.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.")
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,22 @@
|
|||||||
use clap::ArgMatches;
|
use clap::ArgMatches;
|
||||||
use client::{ClientConfig, ClientGenesis, Eth2Config};
|
use client::{ClientConfig, ClientGenesis, Eth2Config};
|
||||||
use eth2_config::{read_from_file, write_to_file};
|
use eth2_config::{read_from_file, write_to_file};
|
||||||
|
use eth2_libp2p::{Enr, Multiaddr};
|
||||||
use eth2_testnet::Eth2TestnetDir;
|
use eth2_testnet::Eth2TestnetDir;
|
||||||
use genesis::recent_genesis_time;
|
use genesis::recent_genesis_time;
|
||||||
use lighthouse_bootstrap::Bootstrapper;
|
|
||||||
use rand::{distributions::Alphanumeric, Rng};
|
use rand::{distributions::Alphanumeric, Rng};
|
||||||
use slog::{crit, info, warn, Logger};
|
use slog::{crit, info, Logger};
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::net::Ipv4Addr;
|
use std::net::Ipv4Addr;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::PathBuf;
|
||||||
use types::{Address, Epoch, Fork};
|
use types::{Epoch, Fork};
|
||||||
|
|
||||||
pub const DEFAULT_DATA_DIR: &str = ".lighthouse";
|
|
||||||
pub const CLIENT_CONFIG_FILENAME: &str = "beacon-node.toml";
|
pub const CLIENT_CONFIG_FILENAME: &str = "beacon-node.toml";
|
||||||
pub const ETH2_CONFIG_FILENAME: &str = "eth2-spec.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<T> = std::result::Result<T, String>;
|
type Result<T> = std::result::Result<T, String>;
|
||||||
type Config = (ClientConfig, Eth2Config, Logger);
|
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
|
/// 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
|
/// may be influenced by other external services like the contents of the file system or the
|
||||||
/// response of some remote server.
|
/// response of some remote server.
|
||||||
pub fn get_configs(cli_args: &ArgMatches, core_log: Logger) -> Result<Config> {
|
pub fn get_configs(
|
||||||
|
cli_args: &ArgMatches,
|
||||||
|
mut eth2_config: Eth2Config,
|
||||||
|
core_log: Logger,
|
||||||
|
) -> Result<Config> {
|
||||||
let log = core_log.clone();
|
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") {
|
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::<usize>()
|
||||||
|
.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::<u16>()
|
||||||
|
.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::<Result<Vec<Enr>>>()?;
|
||||||
|
}
|
||||||
|
|
||||||
|
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::<Result<Vec<Multiaddr>>>()?;
|
||||||
|
}
|
||||||
|
|
||||||
|
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::<u16>()
|
||||||
|
.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::<Ipv4Addr>()
|
||||||
|
.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::<u16>()
|
||||||
|
.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::<Ipv4Addr>()
|
||||||
|
.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::<u16>()
|
||||||
|
.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") {
|
if let Some(val) = cli_args.value_of("eth1-endpoint") {
|
||||||
builder.set_eth1_endpoint(val)
|
client_config.eth1.endpoint = val.to_string();
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(val) = cli_args.value_of("deposit-contract") {
|
|
||||||
builder.set_deposit_contract(
|
|
||||||
val.parse::<Address>()
|
|
||||||
.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::<u64>()
|
|
||||||
.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::<u64>()
|
|
||||||
.map_err(|e| format!("Unable to parse follow distance: {:?}", e))?,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
match cli_args.subcommand() {
|
match cli_args.subcommand() {
|
||||||
("testnet", Some(sub_cmd_args)) => {
|
("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.
|
// No sub-command assumes a resume operation.
|
||||||
_ => {
|
_ => {
|
||||||
info!(
|
info!(
|
||||||
log,
|
log,
|
||||||
"Resuming from existing datadir";
|
"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
|
// If no primary subcommand was given, start the beacon chain from an existing
|
||||||
// database.
|
// 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
|
// 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).
|
// to start a new chain (e.g., from a genesis YAML file, another node, etc).
|
||||||
if !builder.client_config.data_dir.exists() {
|
if !client_config.data_dir.exists() {
|
||||||
return Err(
|
init_new_client(&mut client_config, &mut eth2_config)?
|
||||||
"No datadir found. To start a new beacon chain, see `testnet --help`. \
|
} else {
|
||||||
Use `--datadir` to specify a different directory"
|
// If the `testnet` command was not provided, attempt to load an existing datadir and
|
||||||
.into(),
|
// 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`.
|
/// Process the `testnet` CLI subcommand arguments, updating the `builder`.
|
||||||
fn process_testnet_subcommand(
|
fn process_testnet_subcommand(
|
||||||
builder: &mut ConfigBuilder,
|
client_config: &mut ClientConfig,
|
||||||
|
eth2_config: &mut Eth2Config,
|
||||||
cli_args: &ArgMatches,
|
cli_args: &ArgMatches,
|
||||||
log: &Logger,
|
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
|
// Specifies that a random datadir should be used.
|
||||||
if cli_args.is_present("random-datadir") {
|
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") {
|
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") {
|
if let Some(propagation_percentage_string) = cli_args.value_of("random-propagation") {
|
||||||
let percentage = propagation_percentage_string
|
let percentage = propagation_percentage_string
|
||||||
.parse::<u8>()
|
.parse::<u8>()
|
||||||
@@ -115,72 +404,20 @@ fn process_testnet_subcommand(
|
|||||||
if percentage > 100 {
|
if percentage > 100 {
|
||||||
return Err(format!("Propagation percentage greater than 100"));
|
return Err(format!("Propagation percentage greater than 100"));
|
||||||
}
|
}
|
||||||
builder.client_config.network.propagation_percentage = Some(percentage);
|
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::<PathBuf>()
|
|
||||||
.map_err(|e| format!("Unable to parse eth2-config path: {:?}", e))?;
|
|
||||||
builder.load_eth2_config(path)?;
|
|
||||||
} else {
|
|
||||||
builder.update_spec_from_subcommand(&cli_args)?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Modify the `SECONDS_PER_SLOT` "constant".
|
||||||
if let Some(slot_time) = cli_args.value_of("slot-time") {
|
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
|
let slot_time = slot_time
|
||||||
.parse::<u64>()
|
.parse::<u64>()
|
||||||
.map_err(|e| format!("Unable to parse slot-time: {:?}", e))?;
|
.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::<PathBuf>()
|
|
||||||
.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 ...`).
|
// Start matching on the second subcommand (e.g., `testnet bootstrap ...`).
|
||||||
match cli_args.subcommand() {
|
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<u16> = cli_args
|
|
||||||
.value_of("libp2p-port")
|
|
||||||
.and_then(|s| s.parse::<u16>().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)) => {
|
("recent", Some(cli_args)) => {
|
||||||
let validator_count = cli_args
|
let validator_count = cli_args
|
||||||
.value_of("validator_count")
|
.value_of("validator_count")
|
||||||
@@ -194,12 +431,12 @@ fn process_testnet_subcommand(
|
|||||||
.parse::<u64>()
|
.parse::<u64>()
|
||||||
.map_err(|e| format!("Unable to parse minutes: {:?}", e))?;
|
.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,
|
validator_count,
|
||||||
genesis_time: recent_genesis_time(minutes),
|
genesis_time: recent_genesis_time(minutes),
|
||||||
})
|
};
|
||||||
}
|
}
|
||||||
("quick", Some(cli_args)) => {
|
("quick", Some(cli_args)) => {
|
||||||
let validator_count = cli_args
|
let validator_count = cli_args
|
||||||
@@ -214,12 +451,12 @@ fn process_testnet_subcommand(
|
|||||||
.parse::<u64>()
|
.parse::<u64>()
|
||||||
.map_err(|e| format!("Unable to parse genesis time: {:?}", e))?;
|
.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,
|
validator_count,
|
||||||
genesis_time,
|
genesis_time,
|
||||||
})
|
};
|
||||||
}
|
}
|
||||||
("file", Some(cli_args)) => {
|
("file", Some(cli_args)) => {
|
||||||
let path = cli_args
|
let path = cli_args
|
||||||
@@ -237,11 +474,10 @@ fn process_testnet_subcommand(
|
|||||||
other => return Err(format!("Unknown genesis file format: {}", other)),
|
other => return Err(format!("Unknown genesis file format: {}", other)),
|
||||||
};
|
};
|
||||||
|
|
||||||
builder.set_genesis(start_method)
|
client_config.genesis = start_method;
|
||||||
}
|
}
|
||||||
("prysm", Some(_)) => {
|
("prysm", Some(_)) => {
|
||||||
let mut spec = &mut builder.eth2_config.spec;
|
let mut spec = &mut eth2_config.spec;
|
||||||
let mut client_config = &mut builder.client_config;
|
|
||||||
|
|
||||||
spec.min_deposit_amount = 100;
|
spec.min_deposit_amount = 100;
|
||||||
spec.max_effective_balance = 3_200_000_000;
|
spec.max_effective_balance = 3_200_000_000;
|
||||||
@@ -260,58 +496,7 @@ fn process_testnet_subcommand(
|
|||||||
client_config.eth1.follow_distance = 16;
|
client_config.eth1.follow_distance = 16;
|
||||||
client_config.dummy_eth1_backend = false;
|
client_config.dummy_eth1_backend = false;
|
||||||
|
|
||||||
builder.set_genesis(ClientGenesis::DepositContract)
|
client_config.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)
|
|
||||||
}
|
}
|
||||||
(cmd, Some(_)) => {
|
(cmd, Some(_)) => {
|
||||||
return Err(format!(
|
return Err(format!(
|
||||||
@@ -322,369 +507,11 @@ fn process_testnet_subcommand(
|
|||||||
_ => return Err("No testnet method specified. See 'testnet --help'.".into()),
|
_ => return Err("No testnet method specified. See 'testnet --help'.".into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
builder.write_configs_to_new_datadir()?;
|
create_new_datadir(&client_config, ð2_config)?;
|
||||||
|
|
||||||
Ok(())
|
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<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 = 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<u16>,
|
|
||||||
) -> 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::<ClientConfig>(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::<Eth2Config>(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::<Ipv4Addr>()
|
|
||||||
.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<Config> {
|
|
||||||
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::<u16>()
|
|
||||||
.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 {
|
fn random_string(len: usize) -> String {
|
||||||
rand::thread_rng()
|
rand::thread_rng()
|
||||||
.sample_iter(&Alphanumeric)
|
.sample_iter(&Alphanumeric)
|
||||||
|
|||||||
@@ -57,15 +57,15 @@ impl<E: EthSpec> ProductionBeaconNode<E> {
|
|||||||
) -> impl Future<Item = Self, Error = String> + 'a {
|
) -> impl Future<Item = Self, Error = String> + 'a {
|
||||||
let log = context.log.clone();
|
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
|
// See https://github.com/sigp/lighthouse/issues/602
|
||||||
get_configs(&matches, log).into_future().and_then(
|
get_configs(&matches, context.eth2_config.clone(), log)
|
||||||
move |(client_config, eth2_config, _log)| {
|
.into_future()
|
||||||
|
.and_then(move |(client_config, eth2_config, _log)| {
|
||||||
context.eth2_config = eth2_config;
|
context.eth2_config = eth2_config;
|
||||||
Self::new(context, client_config)
|
Self::new(context, client_config)
|
||||||
},
|
})
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Starts a new beacon node `Client` in the given `environment`.
|
/// Starts a new beacon node `Client` in the given `environment`.
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ edition = "2018"
|
|||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap = "2.33.0"
|
|
||||||
exit-future = "0.1.4"
|
exit-future = "0.1.4"
|
||||||
futures = "0.1.29"
|
futures = "0.1.29"
|
||||||
serde = "1.0.102"
|
serde = "1.0.102"
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
use clap::ArgMatches;
|
use serde_derive::{Deserialize, Serialize};
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use std::net::Ipv4Addr;
|
use std::net::Ipv4Addr;
|
||||||
|
|
||||||
/// The core configuration of a Lighthouse beacon node.
|
/// 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::<Ipv4Addr>()
|
|
||||||
.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::<u16>()
|
|
||||||
.map_err(|_| "ws-port is not a valid u16.")?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -13,3 +13,4 @@ tempdir = "0.3"
|
|||||||
serde = "1.0"
|
serde = "1.0"
|
||||||
serde_json = "^1.0"
|
serde_json = "^1.0"
|
||||||
types = { path = "../../types"}
|
types = { path = "../../types"}
|
||||||
|
eth2-libp2p = { path = "../../../beacon_node/eth2-libp2p"}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
//!
|
//!
|
||||||
//! https://github.com/sigp/lighthouse/pull/605
|
//! https://github.com/sigp/lighthouse/pull/605
|
||||||
|
|
||||||
|
use eth2_libp2p::Enr;
|
||||||
use std::fs::{create_dir_all, File};
|
use std::fs::{create_dir_all, File};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use types::Address;
|
use types::Address;
|
||||||
@@ -14,12 +15,14 @@ use types::Address;
|
|||||||
pub const ADDRESS_FILE: &str = "deposit_contract.txt";
|
pub const ADDRESS_FILE: &str = "deposit_contract.txt";
|
||||||
pub const DEPLOY_BLOCK_FILE: &str = "deploy_block.txt";
|
pub const DEPLOY_BLOCK_FILE: &str = "deploy_block.txt";
|
||||||
pub const MIN_GENESIS_TIME_FILE: &str = "min_genesis_time.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)]
|
#[derive(Clone, PartialEq, Debug)]
|
||||||
pub struct Eth2TestnetDir {
|
pub struct Eth2TestnetDir {
|
||||||
deposit_contract_address: String,
|
deposit_contract_address: String,
|
||||||
pub deposit_contract_deploy_block: u64,
|
pub deposit_contract_deploy_block: u64,
|
||||||
pub min_genesis_time: u64,
|
pub min_genesis_time: u64,
|
||||||
|
pub boot_nodes: Vec<Enr>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Eth2TestnetDir {
|
impl Eth2TestnetDir {
|
||||||
@@ -28,6 +31,7 @@ impl Eth2TestnetDir {
|
|||||||
deposit_contract_address: String,
|
deposit_contract_address: String,
|
||||||
deposit_contract_deploy_block: u64,
|
deposit_contract_deploy_block: u64,
|
||||||
min_genesis_time: u64,
|
min_genesis_time: u64,
|
||||||
|
boot_nodes: Vec<Enr>,
|
||||||
) -> Result<Self, String> {
|
) -> Result<Self, String> {
|
||||||
if base_dir.exists() {
|
if base_dir.exists() {
|
||||||
return Err("Testnet directory already exists".to_string());
|
return Err("Testnet directory already exists".to_string());
|
||||||
@@ -36,60 +40,52 @@ impl Eth2TestnetDir {
|
|||||||
create_dir_all(&base_dir)
|
create_dir_all(&base_dir)
|
||||||
.map_err(|e| format!("Unable to create testnet directory: {:?}", e))?;
|
.map_err(|e| format!("Unable to create testnet directory: {:?}", e))?;
|
||||||
|
|
||||||
File::create(base_dir.join(ADDRESS_FILE))
|
macro_rules! write_to_file {
|
||||||
.map_err(|e| format!("Unable to create {}: {:?}", ADDRESS_FILE, e))
|
($file: ident, $variable: ident) => {
|
||||||
.and_then(|file| {
|
File::create(base_dir.join($file))
|
||||||
serde_json::to_writer(file, &deposit_contract_address)
|
.map_err(|e| format!("Unable to create {}: {:?}", $file, e))
|
||||||
.map_err(|e| format!("Unable to write {}: {:?}", ADDRESS_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))
|
write_to_file!(ADDRESS_FILE, deposit_contract_address);
|
||||||
.map_err(|e| format!("Unable to create {}: {:?}", DEPLOY_BLOCK_FILE, e))
|
write_to_file!(DEPLOY_BLOCK_FILE, deposit_contract_deploy_block);
|
||||||
.and_then(|file| {
|
write_to_file!(MIN_GENESIS_TIME_FILE, min_genesis_time);
|
||||||
serde_json::to_writer(file, &deposit_contract_deploy_block)
|
write_to_file!(BOOT_NODES_FILE, boot_nodes);
|
||||||
.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))
|
|
||||||
})?;
|
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
deposit_contract_address,
|
deposit_contract_address,
|
||||||
deposit_contract_deploy_block,
|
deposit_contract_deploy_block,
|
||||||
min_genesis_time,
|
min_genesis_time,
|
||||||
|
boot_nodes,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load(base_dir: PathBuf) -> Result<Self, String> {
|
pub fn load(base_dir: PathBuf) -> Result<Self, String> {
|
||||||
let deposit_contract_address = File::open(base_dir.join(ADDRESS_FILE))
|
macro_rules! load_from_file {
|
||||||
.map_err(|e| format!("Unable to open {}: {:?}", ADDRESS_FILE, e))
|
($file: ident) => {
|
||||||
.and_then(|file| {
|
File::open(base_dir.join($file))
|
||||||
serde_json::from_reader(file)
|
.map_err(|e| format!("Unable to open {}: {:?}", $file, e))
|
||||||
.map_err(|e| format!("Unable to parse {}: {:?}", ADDRESS_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))
|
let deposit_contract_address = load_from_file!(ADDRESS_FILE);
|
||||||
.map_err(|e| format!("Unable to open {}: {:?}", DEPLOY_BLOCK_FILE, e))
|
let deposit_contract_deploy_block = load_from_file!(DEPLOY_BLOCK_FILE);
|
||||||
.and_then(|file| {
|
let min_genesis_time = load_from_file!(MIN_GENESIS_TIME_FILE);
|
||||||
serde_json::from_reader(file)
|
let boot_nodes = load_from_file!(BOOT_NODES_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))
|
|
||||||
})?;
|
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
deposit_contract_address,
|
deposit_contract_address,
|
||||||
deposit_contract_deploy_block,
|
deposit_contract_deploy_block,
|
||||||
min_genesis_time,
|
min_genesis_time,
|
||||||
|
boot_nodes,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,6 +118,8 @@ mod tests {
|
|||||||
deposit_contract_address.clone(),
|
deposit_contract_address.clone(),
|
||||||
deposit_contract_deploy_block,
|
deposit_contract_deploy_block,
|
||||||
min_genesis_time,
|
min_genesis_time,
|
||||||
|
// TODO: add some Enr for testing.
|
||||||
|
vec![],
|
||||||
)
|
)
|
||||||
.expect("should create struct");
|
.expect("should create struct");
|
||||||
|
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ fn main() {
|
|||||||
.short("d")
|
.short("d")
|
||||||
.value_name("DIR")
|
.value_name("DIR")
|
||||||
.global(true)
|
.global(true)
|
||||||
.help("Data directory for keys and databases.")
|
.help("Data directory for lighthouse keys and databases.")
|
||||||
.takes_value(true),
|
.takes_value(true),
|
||||||
)
|
)
|
||||||
.subcommand(beacon_node::cli_app())
|
.subcommand(beacon_node::cli_app())
|
||||||
|
|||||||
Reference in New Issue
Block a user