Fix issues with testnet dir, update docs (#992)
* Fix issues with testnet dir, update docs * Remove "simple testnet" docs * Tear out old "bn testnet" stuff * Add back ClientGenesis::Interop * Tidy * Remove lighthouse-bootstrap module * Fix bug with spec constant mismatch * Ensure beacon-node.toml is written to correct dir * Add -t alias for --testnet-dir * Update book/src/local-testnets.md Co-Authored-By: Age Manning <Age@AgeManning.com> * Add --purge CLI flag * Update purge docs * Perform manual delete of files in purge * Rename --purge to --purge-db * Address Michael's comments Co-authored-by: Age Manning <Age@AgeManning.com>
This commit is contained in:
parent
a8ee3389c2
commit
1a3d1b3077
19
Cargo.lock
generated
19
Cargo.lock
generated
@ -217,7 +217,6 @@ dependencies = [
|
||||
"genesis 0.1.0",
|
||||
"integer-sqrt 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lighthouse_bootstrap 0.1.0",
|
||||
"lighthouse_metrics 0.1.0",
|
||||
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lru 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@ -260,16 +259,17 @@ dependencies = [
|
||||
"exit-future 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"genesis 0.1.0",
|
||||
"lighthouse_bootstrap 0.1.0",
|
||||
"logging 0.1.0",
|
||||
"node_test_rig 0.1.0",
|
||||
"rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"slog 2.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"slog-async 2.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"slog-term 2.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"store 0.1.0",
|
||||
"tokio 0.1.22 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tokio-timer 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"toml 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"types 0.1.0",
|
||||
"version 0.1.0",
|
||||
]
|
||||
@ -521,7 +521,6 @@ dependencies = [
|
||||
"futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"genesis 0.1.0",
|
||||
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lighthouse_bootstrap 0.1.0",
|
||||
"lighthouse_metrics 0.1.0",
|
||||
"network 0.1.0",
|
||||
"parking_lot 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@ -2384,19 +2383,6 @@ dependencies = [
|
||||
"validator_client 0.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lighthouse_bootstrap"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"eth2-libp2p 0.1.0",
|
||||
"eth2_config 0.1.0",
|
||||
"reqwest 0.9.24 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"slog 2.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"types 0.1.0",
|
||||
"url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lighthouse_metrics"
|
||||
version = "0.1.0"
|
||||
@ -4704,7 +4690,6 @@ dependencies = [
|
||||
"futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lighthouse_bootstrap 0.1.0",
|
||||
"logging 0.1.0",
|
||||
"parking_lot 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rayon 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -14,7 +14,6 @@ members = [
|
||||
"eth2/utils/logging",
|
||||
"eth2/utils/eth2_hashing",
|
||||
"eth2/utils/lighthouse_metrics",
|
||||
"eth2/utils/lighthouse_bootstrap",
|
||||
"eth2/utils/merkle_proof",
|
||||
"eth2/utils/int_to_bytes",
|
||||
"eth2/utils/serde_hex",
|
||||
|
@ -13,7 +13,6 @@ node_test_rig = { path = "../tests/node_test_rig" }
|
||||
|
||||
[dependencies]
|
||||
eth2_config = { path = "../eth2/utils/eth2_config" }
|
||||
lighthouse_bootstrap = { path = "../eth2/utils/lighthouse_bootstrap" }
|
||||
beacon_chain = { path = "beacon_chain" }
|
||||
types = { path = "../eth2/types" }
|
||||
store = { path = "./store" }
|
||||
@ -37,3 +36,5 @@ genesis = { path = "genesis" }
|
||||
eth2_testnet_config = { path = "../eth2/utils/eth2_testnet_config" }
|
||||
eth2-libp2p = { path = "./eth2-libp2p" }
|
||||
eth2_ssz = { path = "../eth2/utils/ssz" }
|
||||
toml = "0.5.4"
|
||||
serde = "1.0.102"
|
||||
|
@ -15,7 +15,6 @@ store = { path = "../store" }
|
||||
parking_lot = "0.9.0"
|
||||
lazy_static = "1.4.0"
|
||||
lighthouse_metrics = { path = "../../eth2/utils/lighthouse_metrics" }
|
||||
lighthouse_bootstrap = { path = "../../eth2/utils/lighthouse_bootstrap" }
|
||||
log = "0.4.8"
|
||||
operation_pool = { path = "../../eth2/operation_pool" }
|
||||
rayon = "1.2.0"
|
||||
|
@ -36,7 +36,6 @@ url = "2.1.0"
|
||||
eth1 = { path = "../eth1" }
|
||||
genesis = { path = "../genesis" }
|
||||
environment = { path = "../../lighthouse/environment" }
|
||||
lighthouse_bootstrap = { path = "../../eth2/utils/lighthouse_bootstrap" }
|
||||
eth2_ssz = { path = "../../eth2/utils/ssz" }
|
||||
lazy_static = "1.4.0"
|
||||
lighthouse_metrics = { path = "../../eth2/utils/lighthouse_metrics" }
|
||||
|
@ -16,10 +16,7 @@ use eth1::{Config as Eth1Config, Service as Eth1Service};
|
||||
use eth2_config::Eth2Config;
|
||||
use exit_future::Signal;
|
||||
use futures::{future, Future, IntoFuture};
|
||||
use genesis::{
|
||||
generate_deterministic_keypairs, interop_genesis_state, state_from_ssz_file, Eth1GenesisService,
|
||||
};
|
||||
use lighthouse_bootstrap::Bootstrapper;
|
||||
use genesis::{interop_genesis_state, Eth1GenesisService};
|
||||
use network::{NetworkConfig, NetworkMessage, Service as NetworkService};
|
||||
use slog::info;
|
||||
use ssz::Decode;
|
||||
@ -28,7 +25,7 @@ use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tokio::sync::mpsc::UnboundedSender;
|
||||
use types::{BeaconState, ChainSpec, EthSpec};
|
||||
use types::{test_utils::generate_deterministic_keypairs, BeaconState, ChainSpec, EthSpec};
|
||||
use websocket_server::{Config as WebSocketConfig, WebSocketSender};
|
||||
|
||||
/// Interval between polling the eth1 node for genesis information.
|
||||
@ -161,12 +158,13 @@ where
|
||||
//
|
||||
// Alternatively, if there's a beacon chain in the database then always resume
|
||||
// using it.
|
||||
let client_genesis = if client_genesis == ClientGenesis::Resume && !chain_exists {
|
||||
let client_genesis = if client_genesis == ClientGenesis::FromStore && !chain_exists
|
||||
{
|
||||
info!(context.log, "Defaulting to deposit contract genesis");
|
||||
|
||||
ClientGenesis::DepositContract
|
||||
} else if chain_exists {
|
||||
ClientGenesis::Resume
|
||||
ClientGenesis::FromStore
|
||||
} else {
|
||||
client_genesis
|
||||
};
|
||||
@ -187,16 +185,6 @@ where
|
||||
|
||||
Box::new(future)
|
||||
}
|
||||
ClientGenesis::SszFile { path } => {
|
||||
let result = state_from_ssz_file(path);
|
||||
|
||||
let future = result
|
||||
.and_then(move |genesis_state| builder.genesis_state(genesis_state))
|
||||
.into_future()
|
||||
.map(|v| (v, None));
|
||||
|
||||
Box::new(future)
|
||||
}
|
||||
ClientGenesis::SszBytes {
|
||||
genesis_state_bytes,
|
||||
} => {
|
||||
@ -235,25 +223,7 @@ where
|
||||
|
||||
Box::new(future)
|
||||
}
|
||||
ClientGenesis::RemoteNode { server, .. } => {
|
||||
let future = Bootstrapper::connect(server, &context.log)
|
||||
.map_err(|e| {
|
||||
format!("Failed to initialize bootstrap client: {}", e)
|
||||
})
|
||||
.into_future()
|
||||
.and_then(|bootstrapper| {
|
||||
let (genesis_state, _genesis_block) =
|
||||
bootstrapper.genesis().map_err(|e| {
|
||||
format!("Failed to bootstrap genesis state: {}", e)
|
||||
})?;
|
||||
|
||||
builder.genesis_state(genesis_state)
|
||||
})
|
||||
.map(|v| (v, None));
|
||||
|
||||
Box::new(future)
|
||||
}
|
||||
ClientGenesis::Resume => {
|
||||
ClientGenesis::FromStore => {
|
||||
let future = builder.resume_from_db().into_future().map(|v| (v, None));
|
||||
|
||||
Box::new(future)
|
||||
|
@ -1,4 +1,3 @@
|
||||
use beacon_chain::builder::PUBKEY_CACHE_FILENAME;
|
||||
use network::NetworkConfig;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::fs;
|
||||
@ -12,32 +11,24 @@ const TESTNET_SPEC_CONSTANTS: &str = "minimal";
|
||||
/// Default directory name for the freezer database under the top-level data dir.
|
||||
const DEFAULT_FREEZER_DB_DIR: &str = "freezer_db";
|
||||
|
||||
/// Trap file indicating if chain_db was purged
|
||||
const CHAIN_DB_PURGED_TRAP_FILE: &str = ".db_purged";
|
||||
|
||||
/// Defines how the client should initialize the `BeaconChain` and other components.
|
||||
#[derive(PartialEq, Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum ClientGenesis {
|
||||
/// Reads the genesis state and other persisted data from the `Store`.
|
||||
Resume,
|
||||
/// Creates a genesis state as per the 2019 Canada interop specifications.
|
||||
Interop {
|
||||
validator_count: usize,
|
||||
genesis_time: u64,
|
||||
},
|
||||
/// Reads the genesis state and other persisted data from the `Store`.
|
||||
FromStore,
|
||||
/// Connects to an eth1 node and waits until it can create the genesis state from the deposit
|
||||
/// contract.
|
||||
DepositContract,
|
||||
/// Loads the genesis state from a SSZ-encoded `BeaconState` file.
|
||||
SszFile { path: PathBuf },
|
||||
/// Loads the genesis state from SSZ-encoded `BeaconState` bytes.
|
||||
///
|
||||
/// We include the bytes instead of the `BeaconState<E>` because the `EthSpec` type
|
||||
/// parameter would be very annoying.
|
||||
SszBytes { genesis_state_bytes: Vec<u8> },
|
||||
/// Connects to another Lighthouse instance and reads the genesis state and other data via the
|
||||
/// HTTP API.
|
||||
RemoteNode { server: String, port: Option<u16> },
|
||||
}
|
||||
|
||||
impl Default for ClientGenesis {
|
||||
@ -101,68 +92,6 @@ impl Config {
|
||||
.map(|data_dir| data_dir.join(&self.db_name))
|
||||
}
|
||||
|
||||
/// Get the path of the chain db purged trap file
|
||||
pub fn get_db_purged_trap_file_path(&self) -> Option<PathBuf> {
|
||||
self.get_data_dir()
|
||||
.map(|data_dir| data_dir.join(CHAIN_DB_PURGED_TRAP_FILE))
|
||||
}
|
||||
|
||||
/// returns whether chain_db was recently purged
|
||||
pub fn chain_db_was_purged(&self) -> bool {
|
||||
self.get_db_purged_trap_file_path()
|
||||
.map_or(false, |trap_file| trap_file.exists())
|
||||
}
|
||||
|
||||
/// purges the chain_db and creates trap file
|
||||
pub fn purge_chain_db(&self) -> Result<(), String> {
|
||||
// create the trap file
|
||||
let trap_file = self
|
||||
.get_db_purged_trap_file_path()
|
||||
.ok_or("Failed to get trap file path".to_string())?;
|
||||
fs::File::create(trap_file)
|
||||
.map_err(|err| format!("Failed to create trap file: {}", err))?;
|
||||
|
||||
// remove the chain_db
|
||||
fs::remove_dir_all(
|
||||
self.get_db_path()
|
||||
.ok_or("Failed to get db_path".to_string())?,
|
||||
)
|
||||
.map_err(|err| format!("Failed to remove chain_db: {}", err))?;
|
||||
|
||||
// remove the freezer db
|
||||
fs::remove_dir_all(
|
||||
self.get_freezer_db_path()
|
||||
.ok_or("Failed to get freezer db path".to_string())?,
|
||||
)
|
||||
.map_err(|err| format!("Failed to remove chain_db: {}", err))?;
|
||||
|
||||
// also need to remove pubkey cache file if it exists
|
||||
let pubkey_cache_file = self
|
||||
.get_data_dir()
|
||||
.map(|data_dir| data_dir.join(PUBKEY_CACHE_FILENAME))
|
||||
.ok_or("Failed to get pubkey cache file path".to_string())?;
|
||||
if !pubkey_cache_file.exists() {
|
||||
return Ok(());
|
||||
}
|
||||
fs::remove_file(pubkey_cache_file)
|
||||
.map_err(|err| format!("Failed to remove pubkey cache: {}", err))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// cleans up purge_db trap file
|
||||
pub fn cleanup_after_purge_db(&self) -> Result<(), String> {
|
||||
let trap_file = self
|
||||
.get_db_purged_trap_file_path()
|
||||
.ok_or("Failed to get trap file path".to_string())?;
|
||||
if !trap_file.exists() {
|
||||
return Ok(());
|
||||
}
|
||||
fs::remove_file(trap_file).map_err(|err| format!("Failed to remove trap file: {}", err))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get the database path, creating it if necessary.
|
||||
pub fn create_db_path(&self) -> Result<PathBuf, String> {
|
||||
let db_path = self
|
||||
|
@ -3,7 +3,6 @@ use eth2_hashing::hash;
|
||||
use rayon::prelude::*;
|
||||
use ssz::Encode;
|
||||
use state_processing::initialize_beacon_state_from_eth1;
|
||||
use std::time::SystemTime;
|
||||
use types::{BeaconState, ChainSpec, DepositData, EthSpec, Hash256, Keypair, PublicKey, Signature};
|
||||
|
||||
/// Builds a genesis state as defined by the Eth2 interop procedure (see below).
|
||||
@ -57,18 +56,6 @@ pub fn interop_genesis_state<T: EthSpec>(
|
||||
Ok(state)
|
||||
}
|
||||
|
||||
/// Returns the system time, mod 30 minutes.
|
||||
///
|
||||
/// Used for easily creating testnets.
|
||||
pub fn recent_genesis_time(minutes: u64) -> u64 {
|
||||
let now = SystemTime::now()
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs();
|
||||
let secs_after_last_period = now.checked_rem(minutes * 60).unwrap_or(0);
|
||||
now - secs_after_last_period
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
@ -4,28 +4,5 @@ mod interop;
|
||||
|
||||
pub use eth1::Config as Eth1Config;
|
||||
pub use eth1_genesis_service::Eth1GenesisService;
|
||||
pub use interop::{interop_genesis_state, recent_genesis_time};
|
||||
pub use interop::interop_genesis_state;
|
||||
pub use types::test_utils::generate_deterministic_keypairs;
|
||||
|
||||
use ssz::Decode;
|
||||
use std::fs::File;
|
||||
use std::io::prelude::*;
|
||||
use std::path::PathBuf;
|
||||
use types::{BeaconState, EthSpec};
|
||||
|
||||
/// Load a `BeaconState` from the given `path`. The file should contain raw SSZ bytes (i.e., no
|
||||
/// ASCII encoding or schema).
|
||||
pub fn state_from_ssz_file<E: EthSpec>(path: PathBuf) -> Result<BeaconState<E>, String> {
|
||||
File::open(path.clone())
|
||||
.map_err(move |e| format!("Unable to open SSZ genesis state file {:?}: {:?}", path, e))
|
||||
.and_then(|mut file| {
|
||||
let mut bytes = vec![];
|
||||
file.read_to_end(&mut bytes)
|
||||
.map_err(|e| format!("Failed to read SSZ file: {:?}", e))?;
|
||||
Ok(bytes)
|
||||
})
|
||||
.and_then(|bytes| {
|
||||
BeaconState::from_ssz_bytes(&bytes)
|
||||
.map_err(|e| format!("Unable to parse SSZ genesis state file: {:?}", e))
|
||||
})
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
use clap::{App, Arg, SubCommand};
|
||||
use clap::{App, Arg};
|
||||
|
||||
pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
|
||||
App::new("beacon_node")
|
||||
@ -26,15 +26,6 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
|
||||
.help("Data directory for the freezer database.")
|
||||
.takes_value(true)
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("testnet-dir")
|
||||
.long("testnet-dir")
|
||||
.value_name("DIR")
|
||||
.help("Path to directory containing eth2_testnet specs. Defaults to \
|
||||
a hard-coded Lighthouse testnet. Only effective if there is no \
|
||||
existing database.")
|
||||
.takes_value(true)
|
||||
)
|
||||
/*
|
||||
* Network parameters.
|
||||
*/
|
||||
@ -119,6 +110,15 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
|
||||
automatically.")
|
||||
.takes_value(true),
|
||||
)
|
||||
.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.")
|
||||
)
|
||||
/* REST API related arguments */
|
||||
.arg(
|
||||
Arg::with_name("http")
|
||||
@ -214,107 +214,11 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
|
||||
.takes_value(true)
|
||||
)
|
||||
/*
|
||||
* The "testnet" sub-command.
|
||||
*
|
||||
* Allows for creating a new datadir with testnet-specific configs.
|
||||
* Purge.
|
||||
*/
|
||||
.subcommand(SubCommand::with_name("testnet")
|
||||
.about("Create a new Lighthouse datadir using a testnet strategy.")
|
||||
.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")
|
||||
.long("force")
|
||||
.short("f")
|
||||
.help("If present, will create new config and database files and move the any existing to a \
|
||||
backup directory.")
|
||||
.conflicts_with("random-datadir")
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("random-propagation")
|
||||
.long("random-propagation")
|
||||
.value_name("INTEGER")
|
||||
.takes_value(true)
|
||||
.help("Specifies (as a percentage) the likelihood of propagating blocks and \
|
||||
attestations. This should only be used for testing networking elements. The \
|
||||
value must like in the range 1-100. Default is 100.")
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("slot-time")
|
||||
.long("slot-time")
|
||||
.short("t")
|
||||
.value_name("MILLISECONDS")
|
||||
.help("Defines the slot time when creating a new testnet. The default is \
|
||||
specified by the spec.")
|
||||
)
|
||||
/*
|
||||
* `recent`
|
||||
*
|
||||
* Start a new node, with a specified number of validators with a genesis time in the last
|
||||
* 30-minutes.
|
||||
*/
|
||||
.subcommand(SubCommand::with_name("recent")
|
||||
.about("Creates a new genesis state where the genesis time was at the previous \
|
||||
MINUTES boundary (e.g., when MINUTES == 30; 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"))
|
||||
.arg(Arg::with_name("minutes")
|
||||
.long("minutes")
|
||||
.short("m")
|
||||
.value_name("MINUTES")
|
||||
.required(true)
|
||||
.default_value("30")
|
||||
.help("The maximum number of minutes that will have elapsed before genesis"))
|
||||
)
|
||||
/*
|
||||
* `quick`
|
||||
*
|
||||
* Start a new node, specifying the number of validators and genesis time
|
||||
*/
|
||||
.subcommand(SubCommand::with_name("quick")
|
||||
.about("Creates a new genesis state from the specified validator count and genesis time. \
|
||||
Compatible with the `quick-start genesis` defined in the eth2.0-pm repo.")
|
||||
.arg(Arg::with_name("validator_count")
|
||||
.value_name("VALIDATOR_COUNT")
|
||||
.required(true)
|
||||
.help("The number of validators in the genesis state"))
|
||||
.arg(Arg::with_name("genesis_time")
|
||||
.value_name("UNIX_EPOCH_SECONDS")
|
||||
.required(true)
|
||||
.help("The genesis time for the given state."))
|
||||
)
|
||||
/*
|
||||
* `yaml`
|
||||
*
|
||||
* Start a new node, using a genesis state loaded from a YAML file
|
||||
*/
|
||||
.subcommand(SubCommand::with_name("file")
|
||||
.about("Creates a new datadir where the genesis state is read from file. May fail to parse \
|
||||
a file that was generated to a different spec than that specified by --spec.")
|
||||
.arg(Arg::with_name("format")
|
||||
.value_name("FORMAT")
|
||||
.required(true)
|
||||
.possible_values(&["ssz"])
|
||||
.help("The encoding of the state in the file."))
|
||||
.arg(Arg::with_name("file")
|
||||
.value_name("FILE")
|
||||
.required(true)
|
||||
.help("A file from which to read the state"))
|
||||
)
|
||||
)
|
||||
/*
|
||||
* The "purge" sub-command.
|
||||
*
|
||||
* Allows user to purge beacon database
|
||||
*/
|
||||
.subcommand(SubCommand::with_name("purge")
|
||||
.about("Purge the beacon chain database.")
|
||||
Arg::with_name("purge-db")
|
||||
.long("purge-db")
|
||||
.help("If present, the chain database will be deleted. Use with caution.")
|
||||
)
|
||||
}
|
||||
|
@ -1,14 +1,13 @@
|
||||
use beacon_chain::builder::PUBKEY_CACHE_FILENAME;
|
||||
use clap::ArgMatches;
|
||||
use client::{config::DEFAULT_DATADIR, ClientConfig, ClientGenesis, Eth2Config};
|
||||
use environment::ETH2_CONFIG_FILENAME;
|
||||
use eth2_config::{read_from_file, write_to_file};
|
||||
use client::{config::DEFAULT_DATADIR, ClientConfig, ClientGenesis};
|
||||
use eth2_libp2p::{Enr, Multiaddr};
|
||||
use eth2_testnet_config::Eth2TestnetConfig;
|
||||
use genesis::recent_genesis_time;
|
||||
use rand::{distributions::Alphanumeric, Rng};
|
||||
use slog::{crit, info, warn, Logger};
|
||||
use slog::{crit, warn, Logger};
|
||||
use ssz::Encode;
|
||||
use std::fs;
|
||||
use std::fs::File;
|
||||
use std::io::prelude::*;
|
||||
use std::net::{IpAddr, Ipv4Addr};
|
||||
use std::net::{TcpListener, UdpSocket};
|
||||
use std::path::PathBuf;
|
||||
@ -18,8 +17,6 @@ pub const CLIENT_CONFIG_FILENAME: &str = "beacon-node.toml";
|
||||
pub const BEACON_NODE_DIR: &str = "beacon";
|
||||
pub const NETWORK_DIR: &str = "network";
|
||||
|
||||
type Result<T> = std::result::Result<T, String>;
|
||||
|
||||
/// Gets the fully-initialized global client.
|
||||
///
|
||||
/// The top-level `clap` arguments should be provided as `cli_args`.
|
||||
@ -30,22 +27,52 @@ type Result<T> = std::result::Result<T, String>;
|
||||
#[allow(clippy::cognitive_complexity)]
|
||||
pub fn get_config<E: EthSpec>(
|
||||
cli_args: &ArgMatches,
|
||||
eth2_config: Eth2Config,
|
||||
core_log: Logger,
|
||||
) -> Result<ClientConfig> {
|
||||
let log = core_log.clone();
|
||||
|
||||
spec_constants: &str,
|
||||
log: Logger,
|
||||
) -> Result<ClientConfig, String> {
|
||||
let mut client_config = ClientConfig::default();
|
||||
|
||||
client_config.spec_constants = eth2_config.spec_constants.clone();
|
||||
client_config.data_dir = get_data_dir(cli_args);
|
||||
|
||||
// If necessary, remove any existing database and configuration
|
||||
if client_config.data_dir.exists() && cli_args.is_present("purge-db") {
|
||||
// Remove the chain_db.
|
||||
fs::remove_dir_all(
|
||||
client_config
|
||||
.get_db_path()
|
||||
.ok_or("Failed to get db_path".to_string())?,
|
||||
)
|
||||
.map_err(|err| format!("Failed to remove chain_db: {}", err))?;
|
||||
|
||||
// Remove the freezer db.
|
||||
fs::remove_dir_all(
|
||||
client_config
|
||||
.get_freezer_db_path()
|
||||
.ok_or("Failed to get freezer db path".to_string())?,
|
||||
)
|
||||
.map_err(|err| format!("Failed to remove chain_db: {}", err))?;
|
||||
|
||||
// Remove the pubkey cache file if it exists
|
||||
let pubkey_cache_file = client_config.data_dir.join(PUBKEY_CACHE_FILENAME);
|
||||
if pubkey_cache_file.exists() {
|
||||
fs::remove_file(&pubkey_cache_file)
|
||||
.map_err(|e| format!("Failed to remove {:?}: {:?}", pubkey_cache_file, e))?;
|
||||
}
|
||||
}
|
||||
|
||||
// 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))?;
|
||||
|
||||
// Load the client config, if it exists .
|
||||
let path = client_config.data_dir.join(CLIENT_CONFIG_FILENAME);
|
||||
if path.exists() {
|
||||
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 config_file_path = client_config.data_dir.join(CLIENT_CONFIG_FILENAME);
|
||||
let config_file_existed = config_file_path.exists();
|
||||
if config_file_existed {
|
||||
client_config = read_from_file(config_file_path.clone())
|
||||
.map_err(|e| format!("Unable to parse {:?} file: {:?}", config_file_path, e))?
|
||||
.ok_or_else(|| format!("{:?} file does not exist", config_file_path))?;
|
||||
} else {
|
||||
client_config.spec_constants = spec_constants.into();
|
||||
}
|
||||
|
||||
client_config.testnet_dir = get_testnet_dir(cli_args);
|
||||
@ -85,7 +112,7 @@ pub fn get_config<E: EthSpec>(
|
||||
client_config.network.boot_nodes = boot_enr_str
|
||||
.split(',')
|
||||
.map(|enr| enr.parse().map_err(|_| format!("Invalid ENR: {}", enr)))
|
||||
.collect::<Result<Vec<Enr>>>()?;
|
||||
.collect::<Result<Vec<Enr>, _>>()?;
|
||||
}
|
||||
|
||||
if let Some(libp2p_addresses_str) = cli_args.value_of("libp2p-addresses") {
|
||||
@ -96,7 +123,7 @@ pub fn get_config<E: EthSpec>(
|
||||
.parse()
|
||||
.map_err(|_| format!("Invalid Multiaddr: {}", multiaddr))
|
||||
})
|
||||
.collect::<Result<Vec<Multiaddr>>>()?;
|
||||
.collect::<Result<Vec<Multiaddr>, _>>()?;
|
||||
}
|
||||
|
||||
if let Some(topics_str) = cli_args.value_of("topics") {
|
||||
@ -121,6 +148,20 @@ pub fn get_config<E: EthSpec>(
|
||||
client_config.network.secret_key_hex = Some(p2p_priv_key.to_string());
|
||||
}
|
||||
|
||||
// Define a percentage of messages that should be propogated, useful for simulating bad network
|
||||
// conditions.
|
||||
//
|
||||
// WARNING: setting this to anything less than 100 will cause bad behaviour.
|
||||
if let Some(propagation_percentage_string) = cli_args.value_of("random-propagation") {
|
||||
let percentage = propagation_percentage_string
|
||||
.parse::<u8>()
|
||||
.map_err(|_| "Unable to parse the propagation percentage".to_string())?;
|
||||
if percentage > 100 {
|
||||
return Err("Propagation percentage greater than 100".to_string());
|
||||
}
|
||||
client_config.network.propagation_percentage = Some(percentage);
|
||||
}
|
||||
|
||||
/*
|
||||
* Http server
|
||||
*/
|
||||
@ -185,50 +226,6 @@ pub fn get_config<E: EthSpec>(
|
||||
client_config.eth1.endpoint = val.to_string();
|
||||
}
|
||||
|
||||
match cli_args.subcommand() {
|
||||
("testnet", Some(sub_cmd_args)) => {
|
||||
process_testnet_subcommand(&mut client_config, ð2_config, sub_cmd_args)?
|
||||
}
|
||||
("purge", _) => {
|
||||
client_config.purge_chain_db()?;
|
||||
println!("Successfully purged chain db");
|
||||
std::process::exit(0);
|
||||
}
|
||||
// No sub-command assumes a resume operation.
|
||||
_ => {
|
||||
// If no primary subcommand was given, start the beacon chain from an existing
|
||||
// database.
|
||||
client_config.genesis = ClientGenesis::Resume;
|
||||
|
||||
let db_path_exists: bool = match client_config.get_db_path() {
|
||||
Some(path) => path.exists(),
|
||||
None => false,
|
||||
};
|
||||
|
||||
// Whilst there is no large testnet or mainnet force the user to specify how they want
|
||||
// to start a new chain (e.g., from a genesis YAML file, another node, etc).
|
||||
if !client_config.data_dir.exists()
|
||||
|| (!db_path_exists && client_config.chain_db_was_purged())
|
||||
{
|
||||
info!(
|
||||
log,
|
||||
"Starting from an empty database";
|
||||
"data_dir" => format!("{:?}", client_config.data_dir)
|
||||
);
|
||||
init_new_client::<E>(&mut client_config, ð2_config)?
|
||||
} else {
|
||||
info!(
|
||||
log,
|
||||
"Resuming from existing datadir";
|
||||
"data_dir" => format!("{:?}", client_config.data_dir)
|
||||
);
|
||||
// If the `testnet` command was not provided, attempt to load an existing datadir and
|
||||
// continue with an existing chain.
|
||||
load_from_datadir(&mut client_config)?
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(freezer_dir) = cli_args.value_of("freezer-dir") {
|
||||
client_config.freezer_db_path = Some(PathBuf::from(freezer_dir));
|
||||
}
|
||||
@ -256,10 +253,10 @@ pub fn get_config<E: EthSpec>(
|
||||
.map_err(|_| "block-cache-size is not a valid integer".to_string())?;
|
||||
}
|
||||
|
||||
if eth2_config.spec_constants != client_config.spec_constants {
|
||||
if 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
|
||||
"eth2_config" => spec_constants
|
||||
);
|
||||
return Err("Specification constant mismatch".into());
|
||||
}
|
||||
@ -294,6 +291,39 @@ pub fn get_config<E: EthSpec>(
|
||||
client_config.network.discovery_address =
|
||||
Some("127.0.0.1".parse().expect("Valid IP address"))
|
||||
}
|
||||
|
||||
/*
|
||||
* Load the eth2 testnet dir to obtain some additional config values.
|
||||
*/
|
||||
let eth2_testnet_config: Eth2TestnetConfig<E> =
|
||||
get_eth2_testnet_config(&client_config.testnet_dir)?;
|
||||
|
||||
client_config.eth1.deposit_contract_address =
|
||||
format!("{:?}", eth2_testnet_config.deposit_contract_address()?);
|
||||
client_config.eth1.deposit_contract_deploy_block =
|
||||
eth2_testnet_config.deposit_contract_deploy_block;
|
||||
client_config.eth1.lowest_cached_block_number =
|
||||
client_config.eth1.deposit_contract_deploy_block;
|
||||
|
||||
if let Some(mut boot_nodes) = eth2_testnet_config.boot_enr {
|
||||
client_config.network.boot_nodes.append(&mut boot_nodes)
|
||||
}
|
||||
|
||||
if let Some(genesis_state) = eth2_testnet_config.genesis_state {
|
||||
// Note: re-serializing the genesis state is not so efficient, however it avoids adding
|
||||
// trait bounds to the `ClientGenesis` enum. This would have significant flow-on
|
||||
// effects.
|
||||
client_config.genesis = ClientGenesis::SszBytes {
|
||||
genesis_state_bytes: genesis_state.as_ssz_bytes(),
|
||||
};
|
||||
} else {
|
||||
client_config.genesis = ClientGenesis::DepositContract;
|
||||
}
|
||||
|
||||
if !config_file_existed {
|
||||
write_to_file(config_file_path, &client_config)?;
|
||||
}
|
||||
|
||||
Ok(client_config)
|
||||
}
|
||||
|
||||
@ -320,239 +350,26 @@ pub fn get_testnet_dir(cli_args: &ArgMatches) -> Option<PathBuf> {
|
||||
}
|
||||
}
|
||||
|
||||
/// If `testnet_dir` is `Some`, returns the `Eth2TestnetConfig` at that path or returns an error.
|
||||
/// If it is `None`, returns the "hard coded" config.
|
||||
pub fn get_eth2_testnet_config<E: EthSpec>(
|
||||
testnet_dir: &Option<PathBuf>,
|
||||
) -> Result<Eth2TestnetConfig<E>> {
|
||||
) -> Result<Eth2TestnetConfig<E>, String> {
|
||||
Ok(if let Some(testnet_dir) = testnet_dir {
|
||||
Eth2TestnetConfig::load(testnet_dir.clone())
|
||||
.map_err(|e| format!("Unable to open testnet dir at {:?}: {}", testnet_dir, e))?
|
||||
} else {
|
||||
Eth2TestnetConfig::hard_coded()
|
||||
.map_err(|e| format!("Unable to load hard-coded testnet dir: {}", e))?
|
||||
Eth2TestnetConfig::hard_coded().map_err(|e| {
|
||||
format!(
|
||||
"The hard-coded testnet directory was invalid. \
|
||||
This happens when Lighthouse is migrating between spec versions. \
|
||||
Error : {}",
|
||||
e
|
||||
)
|
||||
})?
|
||||
})
|
||||
}
|
||||
|
||||
/// Load from an existing database.
|
||||
fn load_from_datadir(client_config: &mut ClientConfig) -> 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. Please make sure the directory provided is valid, or specify a different `--datadir`."
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
|
||||
client_config.genesis = ClientGenesis::Resume;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Create a new client with the default configuration.
|
||||
fn init_new_client<E: EthSpec>(
|
||||
client_config: &mut ClientConfig,
|
||||
eth2_config: &Eth2Config,
|
||||
) -> Result<()> {
|
||||
let eth2_testnet_config: Eth2TestnetConfig<E> =
|
||||
get_eth2_testnet_config(&client_config.testnet_dir)?;
|
||||
|
||||
client_config.eth1.deposit_contract_address =
|
||||
format!("{:?}", eth2_testnet_config.deposit_contract_address()?);
|
||||
client_config.eth1.deposit_contract_deploy_block =
|
||||
eth2_testnet_config.deposit_contract_deploy_block;
|
||||
|
||||
client_config.eth1.follow_distance = eth2_config.spec.eth1_follow_distance / 2;
|
||||
client_config.eth1.lowest_cached_block_number = client_config
|
||||
.eth1
|
||||
.deposit_contract_deploy_block
|
||||
.saturating_sub(client_config.eth1.follow_distance * 2);
|
||||
|
||||
if let Some(mut boot_nodes) = eth2_testnet_config.boot_enr {
|
||||
client_config.network.boot_nodes.append(&mut boot_nodes)
|
||||
}
|
||||
|
||||
if let Some(genesis_state) = eth2_testnet_config.genesis_state {
|
||||
// Note: re-serializing the genesis state is not so efficient, however it avoids adding
|
||||
// trait bounds to the `ClientGenesis` enum. This would have significant flow-on
|
||||
// effects.
|
||||
client_config.genesis = ClientGenesis::SszBytes {
|
||||
genesis_state_bytes: genesis_state.as_ssz_bytes(),
|
||||
};
|
||||
} else {
|
||||
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<()> {
|
||||
let rebuild_db = client_config.chain_db_was_purged();
|
||||
if client_config.data_dir.exists() && !rebuild_db {
|
||||
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() {
|
||||
if !rebuild_db {
|
||||
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);
|
||||
client_config.cleanup_after_purge_db()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Process the `testnet` CLI subcommand arguments, updating the `builder`.
|
||||
fn process_testnet_subcommand(
|
||||
client_config: &mut ClientConfig,
|
||||
eth2_config: &Eth2Config,
|
||||
cli_args: &ArgMatches,
|
||||
) -> Result<()> {
|
||||
// Specifies that a random datadir should be used.
|
||||
if cli_args.is_present("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") && client_config.data_dir.exists() {
|
||||
fs::remove_dir_all(&client_config.data_dir)
|
||||
.map_err(|e| format!("Unable to delete existing datadir: {:?}", e))?;
|
||||
}
|
||||
|
||||
// Define a percentage of messages that should be propogated, useful for simulating bad network
|
||||
// conditions.
|
||||
//
|
||||
// WARNING: setting this to anything less than 100 will cause bad behaviour.
|
||||
if let Some(propagation_percentage_string) = cli_args.value_of("random-propagation") {
|
||||
let percentage = propagation_percentage_string
|
||||
.parse::<u8>()
|
||||
.map_err(|_| "Unable to parse the propagation percentage".to_string())?;
|
||||
if percentage > 100 {
|
||||
return Err("Propagation percentage greater than 100".to_string());
|
||||
}
|
||||
client_config.network.propagation_percentage = Some(percentage);
|
||||
}
|
||||
|
||||
// Start matching on the second subcommand (e.g., `testnet bootstrap ...`).
|
||||
match cli_args.subcommand() {
|
||||
("recent", Some(cli_args)) => {
|
||||
let validator_count = cli_args
|
||||
.value_of("validator_count")
|
||||
.ok_or_else(|| "No validator_count specified")?
|
||||
.parse::<usize>()
|
||||
.map_err(|e| format!("Unable to parse validator_count: {:?}", e))?;
|
||||
|
||||
let minutes = cli_args
|
||||
.value_of("minutes")
|
||||
.ok_or_else(|| "No recent genesis minutes supplied")?
|
||||
.parse::<u64>()
|
||||
.map_err(|e| format!("Unable to parse minutes: {:?}", e))?;
|
||||
|
||||
client_config.dummy_eth1_backend = true;
|
||||
|
||||
client_config.genesis = ClientGenesis::Interop {
|
||||
validator_count,
|
||||
genesis_time: recent_genesis_time(minutes),
|
||||
};
|
||||
}
|
||||
("quick", Some(cli_args)) => {
|
||||
let validator_count = cli_args
|
||||
.value_of("validator_count")
|
||||
.ok_or_else(|| "No validator_count specified")?
|
||||
.parse::<usize>()
|
||||
.map_err(|e| format!("Unable to parse validator_count: {:?}", e))?;
|
||||
|
||||
let genesis_time = cli_args
|
||||
.value_of("genesis_time")
|
||||
.ok_or_else(|| "No genesis time supplied")?
|
||||
.parse::<u64>()
|
||||
.map_err(|e| format!("Unable to parse genesis time: {:?}", e))?;
|
||||
|
||||
client_config.dummy_eth1_backend = true;
|
||||
|
||||
client_config.genesis = ClientGenesis::Interop {
|
||||
validator_count,
|
||||
genesis_time,
|
||||
};
|
||||
}
|
||||
("file", Some(cli_args)) => {
|
||||
let path = cli_args
|
||||
.value_of("file")
|
||||
.ok_or_else(|| "No filename specified")?
|
||||
.parse::<PathBuf>()
|
||||
.map_err(|e| format!("Unable to parse filename: {:?}", e))?;
|
||||
|
||||
let format = cli_args
|
||||
.value_of("format")
|
||||
.ok_or_else(|| "No file format specified")?;
|
||||
|
||||
let start_method = match format {
|
||||
"ssz" => ClientGenesis::SszFile { path },
|
||||
other => return Err(format!("Unknown genesis file format: {}", other)),
|
||||
};
|
||||
|
||||
client_config.genesis = start_method;
|
||||
}
|
||||
(cmd, Some(_)) => {
|
||||
return Err(format!(
|
||||
"Invalid valid method specified: {}. See 'testnet --help'.",
|
||||
cmd
|
||||
))
|
||||
}
|
||||
_ => return Err("No testnet method specified. See 'testnet --help'.".into()),
|
||||
};
|
||||
|
||||
create_new_datadir(&client_config, ð2_config)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn random_string(len: usize) -> String {
|
||||
rand::thread_rng()
|
||||
.sample_iter(&Alphanumeric)
|
||||
.take(len)
|
||||
.collect::<String>()
|
||||
}
|
||||
|
||||
/// A bit of hack to find an unused port.
|
||||
///
|
||||
/// Does not guarantee that the given port is unused after the function exists, just that it was
|
||||
@ -566,7 +383,7 @@ fn random_string(len: usize) -> String {
|
||||
/// it doesn't allow binding to the same port even after the socket is closed.
|
||||
/// We might have to use SO_REUSEADDR socket option from `std::net2` crate in
|
||||
/// that case.
|
||||
pub fn unused_port(transport: &str) -> Result<u16> {
|
||||
pub fn unused_port(transport: &str) -> Result<u16, String> {
|
||||
let local_addr = match transport {
|
||||
"tcp" => {
|
||||
let listener = TcpListener::bind("127.0.0.1:0").map_err(|e| {
|
||||
@ -593,3 +410,42 @@ pub fn unused_port(transport: &str) -> Result<u16> {
|
||||
};
|
||||
Ok(local_addr.port())
|
||||
}
|
||||
|
||||
/// Write a configuration to file.
|
||||
pub fn write_to_file<T>(path: PathBuf, config: &T) -> Result<(), String>
|
||||
where
|
||||
T: Default + serde::de::DeserializeOwned + serde::Serialize,
|
||||
{
|
||||
if let Ok(mut file) = File::create(path.clone()) {
|
||||
let toml_encoded = toml::to_string(&config).map_err(|e| {
|
||||
format!(
|
||||
"Failed to write configuration to {:?}. Error: {:?}",
|
||||
path, e
|
||||
)
|
||||
})?;
|
||||
file.write_all(toml_encoded.as_bytes())
|
||||
.unwrap_or_else(|_| panic!("Unable to write to {:?}", path));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Loads a `ClientConfig` from file. If unable to load from file, generates a default
|
||||
/// configuration and saves that as a sample file.
|
||||
pub fn read_from_file<T>(path: PathBuf) -> Result<Option<T>, String>
|
||||
where
|
||||
T: Default + serde::de::DeserializeOwned + serde::Serialize,
|
||||
{
|
||||
if let Ok(mut file) = File::open(path.clone()) {
|
||||
let mut contents = String::new();
|
||||
file.read_to_string(&mut contents)
|
||||
.map_err(|e| format!("Unable to read {:?}. Error: {:?}", path, e))?;
|
||||
|
||||
let config = toml::from_str(&contents)
|
||||
.map_err(|e| format!("Unable to parse {:?}: {:?}", path, e))?;
|
||||
|
||||
Ok(Some(config))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
@ -55,7 +55,11 @@ impl<E: EthSpec> ProductionBeaconNode<E> {
|
||||
context: RuntimeContext<E>,
|
||||
matches: &ArgMatches<'b>,
|
||||
) -> impl Future<Item = Self, Error = String> + 'a {
|
||||
get_config::<E>(&matches, context.eth2_config.clone(), context.log.clone())
|
||||
get_config::<E>(
|
||||
&matches,
|
||||
&context.eth2_config.spec_constants,
|
||||
context.log.clone(),
|
||||
)
|
||||
.into_future()
|
||||
.and_then(move |client_config| Self::new(context, client_config))
|
||||
}
|
||||
|
@ -6,9 +6,7 @@
|
||||
* [Building from Source](./become-a-validator-source.md)
|
||||
* [Installation](./installation.md)
|
||||
* [Docker](./docker.md)
|
||||
* [CLI](./cli.md)
|
||||
* [Simple Local Testnet](./simple-testnet.md)
|
||||
* [Local Testnets](./local-testnets.md)
|
||||
* [Local Testnets](./local-testnets.md)
|
||||
* [API](./api.md)
|
||||
* [HTTP (RESTful JSON)](./http.md)
|
||||
* [/beacon](./http_beacon.md)
|
||||
|
@ -21,7 +21,7 @@ You may read this book from start to finish, or jump to some of these topics:
|
||||
|
||||
- Follow the [Installation Guide](./installation.md) to install Lighthouse.
|
||||
- Get hacking with the [Development Environment Guide](./setup.md).
|
||||
- Utilize the whole stack by starting a [simple local testnet](./simple-testnet.md).
|
||||
- Utilize the whole stack by starting a [local testnet](./local-testnets.md).
|
||||
- Query the [RESTful HTTP API](./http.md) using `curl`.
|
||||
- Listen to events with the [JSON WebSocket API](./websockets.md).
|
||||
- View the [Rust code docs](http://lighthouse-docs.sigmaprime.io/).
|
||||
|
@ -3,143 +3,104 @@
|
||||
> This section is about running your own private local testnets.
|
||||
> - If you wish to join the ongoing public testnet, please read [become a validator](./become-a-validator.md).
|
||||
|
||||
The `beacon_node` and `validator` commands have a `testnet` sub-command to
|
||||
allow creating or connecting to Eth2 beacon chain testnets.
|
||||
It is possible to create local, short-lived Lighthouse testnets that _don't_
|
||||
require a deposit contract and Eth1 connection. There are two components
|
||||
required for this:
|
||||
|
||||
For detailed documentation, use the `--help` flag on the CLI:
|
||||
1. Creating a "testnet directory", containing the configuration of your new
|
||||
testnet.
|
||||
1. Using the `--dummy-eth1` flag on your beacon node to avoid needing an Eth1
|
||||
node for block production.
|
||||
|
||||
There is a TL;DR (too long; didn't read), followed by detailed steps if the
|
||||
TL;DR isn't adequate.
|
||||
|
||||
## TL;DR
|
||||
|
||||
```bash
|
||||
$ lighthouse bn testnet --help
|
||||
lcli new-testnet
|
||||
lcli interop-genesis 128
|
||||
lighthouse bn --testnet-dir ~/.lighthouse/testnet --dummy-eth1 --http
|
||||
lighthouse vc --testnet-dir ~/.lighthouse/testnet --allow-unsynced testnet insecure 0 128
|
||||
```
|
||||
|
||||
Optionally update the genesis time to now:
|
||||
|
||||
```bash
|
||||
$ lighthouse vc testnet --help
|
||||
lcli change-genesis-time ~/.lighthouse/testnet/genesis.ssz $(date +%s)
|
||||
```
|
||||
|
||||
## Examples
|
||||
## 1. Creating a testnet directory
|
||||
|
||||
All examples assume a working [development environment](./setup.md) and
|
||||
commands are based in the `target/release` directory (this is the build dir for
|
||||
`cargo`).
|
||||
### 1.1 Install `lcli`
|
||||
|
||||
### Start a beacon node given a validator count and genesis_time
|
||||
This guide requires `lcli`, the "Lighthouse CLI tool". It is a development tool
|
||||
used for starting testnets and debugging.
|
||||
|
||||
|
||||
To start a brand-new beacon node (with no history) use:
|
||||
Install `lcli` from the root directory of this repository with:
|
||||
|
||||
```bash
|
||||
$ lighthouse bn testnet -f quick 8 <GENESIS_TIME>
|
||||
cargo install --path lcli --force
|
||||
```
|
||||
|
||||
Where `GENESIS_TIME` is in [unix time](https://duckduckgo.com/?q=unix+time&t=ffab&ia=answer).
|
||||
### 1.2 Create a testnet directory
|
||||
|
||||
> Notes:
|
||||
>
|
||||
> - This method conforms the ["Quick-start
|
||||
genesis"](https://github.com/ethereum/eth2.0-pm/tree/6e41fcf383ebeb5125938850d8e9b4e9888389b4/interop/mocked_start#quick-start-genesis)
|
||||
method in the `ethereum/eth2.0-pm` repository.
|
||||
> - The `-f` flag ignores any existing database or configuration, backing them
|
||||
> up before re-initializing.
|
||||
> - `8` is the validator count and `1567222226` is the genesis time.
|
||||
> - See `$ lighthouse bn testnet quick --help` for more configuration options.
|
||||
The default location for a testnet directory is `~/.lighthouse/testnet`. We'll
|
||||
use this directory to keep the examples simple, however you can always specify
|
||||
a different directory using the `--testnet-dir` flag.
|
||||
|
||||
### Start a beacon node given a genesis state file
|
||||
|
||||
A genesis state can be read from file using the `testnet file` subcommand.
|
||||
There are three supported formats:
|
||||
|
||||
- `ssz` (default)
|
||||
- `json`
|
||||
- `yaml`
|
||||
|
||||
Start a new node using `/tmp/genesis.ssz` as the genesis state:
|
||||
Once you have `lcli` installed, create a new testnet directory with:
|
||||
|
||||
```bash
|
||||
$ lighthouse bn testnet --spec minimal -f file ssz /tmp/genesis.ssz
|
||||
lcli new-testnet
|
||||
```
|
||||
|
||||
> Notes:
|
||||
>
|
||||
> - The `-f` flag ignores any existing database or configuration, backing them
|
||||
> up before re-initializing.
|
||||
> - See `$ lighthouse bn testnet file --help` for more configuration options.
|
||||
> - The `--spec` flag is required to allow SSZ parsing of fixed-length lists.
|
||||
> Here the `minimal` eth2 specification is chosen, allowing for lower
|
||||
> validator counts. See
|
||||
> [eth2.0-specs/configs](https://github.com/ethereum/eth2.0-specs/tree/dev/configs)
|
||||
> for more info.
|
||||
> - This will create a "mainnet" spec testnet. To create a minimal spec use `lcli --spec minim new-testnet`.
|
||||
> - The `lcli new-testnet` command has many options, use `lcli new-testnet --help` to see them.
|
||||
|
||||
### Start an auto-configured validator client
|
||||
### 1.3 Create a genesis state
|
||||
|
||||
To start a brand-new validator client (with no history) use:
|
||||
Your new testnet directory at `~/.lighthouse/testnet` doesn't yet have a
|
||||
genesis state (`genesis.ssz`). Since there's no deposit contract in this
|
||||
testnet, there's no way for nodes to find genesis themselves.
|
||||
|
||||
Manually create an "interop" genesis state with `128` validators:
|
||||
|
||||
```bash
|
||||
$ lighthouse vc testnet insecure 0 8
|
||||
lcli interop-genesis 128
|
||||
```
|
||||
|
||||
> Notes:
|
||||
>
|
||||
> - The `insecure` command dictates that the [interop keypairs](https://github.com/ethereum/eth2.0-pm/tree/6e41fcf383ebeb5125938850d8e9b4e9888389b4/interop/mocked_start#pubkeyprivkey-generation)
|
||||
> will be used.
|
||||
> - The `0 8` indicates that this validator client should manage 8 validators,
|
||||
> starting at validator 0 (the first deposited validator).
|
||||
> - The validator client will try to connect to the beacon node at `localhost`.
|
||||
> See `--help` to configure that address and other features.
|
||||
> - The validator client will operate very unsafely in `testnet` mode, happily
|
||||
> swapping between chains and creating double-votes.
|
||||
> - A custom genesis time can be provided with `-t`.
|
||||
> - See `lcli interop-genesis --help` for more info.
|
||||
|
||||
### Exporting a genesis file
|
||||
## 2. Start the beacon nodes and validator clients
|
||||
|
||||
Genesis states can downloaded from a running Lighthouse node via the HTTP API. Three content-types are supported:
|
||||
Now the testnet has been specified in `~/.lighthouse/testnet`, it's time to
|
||||
start a beacon node and validator client.
|
||||
|
||||
- `application/json`
|
||||
- `application/yaml`
|
||||
- `application/ssz`
|
||||
### 2.1 Start a beacon node
|
||||
|
||||
Using `curl`, a genesis state can be downloaded to `/tmp/genesis.ssz`:
|
||||
Start a beacon node:
|
||||
|
||||
```bash
|
||||
$ curl --header "Content-Type: application/ssz" "localhost:5052/beacon/state/genesis" -o /tmp/genesis.ssz
|
||||
lighthouse bn --testnet-dir ~/.lighthouse/testnet --dummy-eth1 --http
|
||||
```
|
||||
|
||||
## Advanced
|
||||
> - `--testnet-dir` instructs the beacon node to use the spec we generated earlier.
|
||||
> - `--dummy-eth1` uses deterministic "junk data" for linking to the eth1 chain, avoiding the requirement for an eth1 node. The downside is that new validators cannot be on-boarded after genesis.
|
||||
> - `--http` starts the REST API so the validator client can produce blocks.
|
||||
|
||||
Below are some CLI commands useful when working with testnets.
|
||||
### 2.2 Start a validator client
|
||||
|
||||
### Specify a boot node by multiaddr
|
||||
|
||||
You can specify a static list of multiaddrs when booting Lighthouse using
|
||||
the `--libp2p-addresses` command.
|
||||
|
||||
#### Example:
|
||||
Once the beacon node has started and begun trying to sync, start a validator
|
||||
client:
|
||||
|
||||
```bash
|
||||
$ lighthouse bn --libp2p-addresses /ip4/192.168.0.1/tcp/9000
|
||||
lighthouse vc --testnet-dir ~/.lighthouse/testnet --allow-unsynced testnet insecure 0 128
|
||||
```
|
||||
|
||||
### Specify a boot node by ENR (Ethereum Name Record)
|
||||
|
||||
You can specify a static list of Discv5 addresses when booting Lighthouse using
|
||||
the `--boot-nodes` command.
|
||||
|
||||
#### Example:
|
||||
|
||||
```bash
|
||||
$ lighthouse bn --boot-nodes -IW4QB2Hi8TPuEzQ41Cdf1r2AUU1FFVFDBJdJyOkWk2qXpZfFZQy2YnJIyoT_5fnbtrXUouoskmydZl4pIg90clIkYUDgmlwhH8AAAGDdGNwgiMog3VkcIIjKIlzZWNwMjU2azGhAjg0-DsTkQynhJCRnLLttBK1RS78lmUkLa-wgzAi-Ob5
|
||||
```
|
||||
|
||||
### Start a testnet with a custom slot time
|
||||
|
||||
Lighthouse can run at quite low slot times when there are few validators (e.g.,
|
||||
`500 ms` slot times should be fine for 8 validators).
|
||||
|
||||
#### Example:
|
||||
|
||||
The `-t` (`--slot-time`) flag specifies the milliseconds per slot.
|
||||
|
||||
```bash
|
||||
$ lighthouse bn testnet -t 500 recent 8
|
||||
```
|
||||
|
||||
> Note: `bootstrap` loads the slot time via HTTP and therefore conflicts with
|
||||
> this flag.
|
||||
> - `--testnet-dir` instructs the validator client to use the spec we generated earlier.
|
||||
> - `--allow-unsynced` stops the validator client checking to see if the beacon node is synced prior to producing blocks.
|
||||
> - `testnet insecure 0 128` instructs the validator client to use insecure
|
||||
> testnet private keys and that it should control validators from `0` to
|
||||
> `127` (inclusive).
|
||||
|
@ -1,70 +0,0 @@
|
||||
# Simple Local Testnet
|
||||
|
||||
> This guide is about running your own private local testnet.
|
||||
> - If you wish to join the ongoing public testnet, please read [become a validator](./become-a-validator.md).
|
||||
|
||||
This guide will help you setup your own private local testnet.
|
||||
|
||||
First, [install Lighthouse](./installation.md).
|
||||
|
||||
Then, get the current unix time in seconds; you can use
|
||||
[epochconverter.com](https://www.epochconverter.com/) or `$ date +%s`. It
|
||||
should look like this `1576803034` and you should use it wherever we put
|
||||
`<time>`.
|
||||
|
||||
> If you choose a time that's more than several minutes in the past the
|
||||
> validator client will refuse to produce blocks. We will loosen this
|
||||
> restriction in the future, the issue is tracked
|
||||
> [here](https://github.com/sigp/lighthouse/issues/714).
|
||||
|
||||
## Starting a beacon node
|
||||
|
||||
Start a new node with:
|
||||
|
||||
```bash
|
||||
$ lighthouse bn --http testnet -r quick 8 <time>
|
||||
```
|
||||
|
||||
> Notes:
|
||||
>
|
||||
> - The `--http` flag starts the API so the validator can produce blocks.
|
||||
> - The `-r` flag creates a random data directory to avoid clashes with other
|
||||
> nodes.
|
||||
> - `8` is number of validators with deposits in the genesis state.
|
||||
> - See `$ lighthouse bn testnet --help` for more configuration options,
|
||||
> including `minimal`/`mainnet` specification.
|
||||
|
||||
## Starting a validator client
|
||||
|
||||
In a new terminal window, start the validator client with:
|
||||
|
||||
```bash
|
||||
$ lighthouse vc testnet insecure 0 8
|
||||
```
|
||||
|
||||
> Notes:
|
||||
>
|
||||
> - The `insecure` command uses predictable, well-known private keys. Since
|
||||
> this is just a local testnet, these are fine.
|
||||
> - The `0 8` indicates that this validator client should manage 8 validators,
|
||||
> starting at validator 0 (the first deposited validator).
|
||||
> - The validator client will try to connect to the beacon node at `localhost`.
|
||||
> See `--help` to configure that address and other features.
|
||||
|
||||
## Adding another beacon node
|
||||
|
||||
You may connect another (non-validating) node to your local network by starting
|
||||
a new terminal and running:
|
||||
|
||||
|
||||
```bash
|
||||
lighthouse bn -z --libp2p-addresses /ip4/127.0.0.1/tcp/9000 testnet -r quick 8 <time>
|
||||
```
|
||||
|
||||
> Notes:
|
||||
>
|
||||
> - The `z` (or `--zero-ports`) flag sets all listening ports to be zero, which then
|
||||
> means that the OS chooses random available ports. This avoids port
|
||||
> collisions with the first node.
|
||||
> - The `--libp2p-addresses` flag instructs the new node to connect to the
|
||||
> first node.
|
@ -1,7 +1,4 @@
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::fs::File;
|
||||
use std::io::prelude::*;
|
||||
use std::path::PathBuf;
|
||||
use types::ChainSpec;
|
||||
|
||||
/// The core configuration of a Lighthouse beacon node.
|
||||
@ -44,45 +41,6 @@ impl Eth2Config {
|
||||
}
|
||||
}
|
||||
|
||||
/// Write a configuration to file.
|
||||
pub fn write_to_file<T>(path: PathBuf, config: &T) -> Result<(), String>
|
||||
where
|
||||
T: Default + serde::de::DeserializeOwned + serde::Serialize,
|
||||
{
|
||||
if let Ok(mut file) = File::create(path.clone()) {
|
||||
let toml_encoded = toml::to_string(&config).map_err(|e| {
|
||||
format!(
|
||||
"Failed to write configuration to {:?}. Error: {:?}",
|
||||
path, e
|
||||
)
|
||||
})?;
|
||||
file.write_all(toml_encoded.as_bytes())
|
||||
.unwrap_or_else(|_| panic!("Unable to write to {:?}", path));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Loads a `ClientConfig` from file. If unable to load from file, generates a default
|
||||
/// configuration and saves that as a sample file.
|
||||
pub fn read_from_file<T>(path: PathBuf) -> Result<Option<T>, String>
|
||||
where
|
||||
T: Default + serde::de::DeserializeOwned + serde::Serialize,
|
||||
{
|
||||
if let Ok(mut file) = File::open(path.clone()) {
|
||||
let mut contents = String::new();
|
||||
file.read_to_string(&mut contents)
|
||||
.map_err(|e| format!("Unable to read {:?}. Error: {:?}", path, e))?;
|
||||
|
||||
let config = toml::from_str(&contents)
|
||||
.map_err(|e| format!("Unable to parse {:?}: {:?}", path, e))?;
|
||||
|
||||
Ok(Some(config))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
@ -1,16 +0,0 @@
|
||||
[package]
|
||||
name = "lighthouse_bootstrap"
|
||||
version = "0.1.0"
|
||||
authors = ["Paul Hauner <paul@paulhauner.com>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
eth2_config = { path = "../eth2_config" }
|
||||
eth2-libp2p = { path = "../../../beacon_node/eth2-libp2p" }
|
||||
reqwest = "0.9.22"
|
||||
url = "1.2"
|
||||
types = { path = "../../types" }
|
||||
serde = "1.0.102"
|
||||
slog = { version = "2.5.2", features = ["max_level_trace", "release_max_level_trace"] }
|
@ -1,262 +0,0 @@
|
||||
use eth2_config::Eth2Config;
|
||||
use eth2_libp2p::{
|
||||
multiaddr::{Multiaddr, Protocol},
|
||||
Enr,
|
||||
};
|
||||
use reqwest::{Error as HttpError, Url};
|
||||
use serde::Deserialize;
|
||||
use slog::{error, Logger};
|
||||
use std::borrow::Cow;
|
||||
use std::net::Ipv4Addr;
|
||||
use std::time::Duration;
|
||||
use types::{BeaconBlock, BeaconState, Checkpoint, EthSpec, Hash256, Slot};
|
||||
use url::Host;
|
||||
|
||||
pub const RETRY_SLEEP_MILLIS: u64 = 100;
|
||||
pub const RETRY_WARN_INTERVAL: u64 = 30;
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Error {
|
||||
InvalidUrl,
|
||||
HttpError(HttpError),
|
||||
}
|
||||
|
||||
impl From<HttpError> for Error {
|
||||
fn from(e: HttpError) -> Error {
|
||||
Error::HttpError(e)
|
||||
}
|
||||
}
|
||||
|
||||
/// Used to load "bootstrap" information from the HTTP API of another Lighthouse beacon node.
|
||||
///
|
||||
/// Bootstrapping information includes things like genesis and finalized states and blocks, and
|
||||
/// libp2p connection details.
|
||||
pub struct Bootstrapper {
|
||||
url: Url,
|
||||
}
|
||||
|
||||
impl Bootstrapper {
|
||||
/// Parses the given `server` as a URL, instantiating `Self` and blocking until a connection
|
||||
/// can be made with the server.
|
||||
///
|
||||
/// Never times out.
|
||||
pub fn connect(server: String, log: &Logger) -> Result<Self, String> {
|
||||
let bootstrapper = Self {
|
||||
url: Url::parse(&server).map_err(|e| format!("Invalid bootstrap server url: {}", e))?,
|
||||
};
|
||||
|
||||
let mut retry_count = 0;
|
||||
loop {
|
||||
match bootstrapper.enr() {
|
||||
Ok(_) => break,
|
||||
Err(_) => {
|
||||
if retry_count % RETRY_WARN_INTERVAL == 0 {
|
||||
error!(
|
||||
log,
|
||||
"Failed to contact bootstrap server";
|
||||
"retry_count" => retry_count,
|
||||
"retry_delay_millis" => RETRY_SLEEP_MILLIS,
|
||||
);
|
||||
}
|
||||
retry_count += 1;
|
||||
std::thread::sleep(Duration::from_millis(RETRY_SLEEP_MILLIS));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(bootstrapper)
|
||||
}
|
||||
|
||||
/// Build a multiaddr using the HTTP server URL that is not guaranteed to be correct.
|
||||
///
|
||||
/// The address is created by querying the HTTP server for its listening libp2p addresses.
|
||||
/// Then, we find the first TCP port in those addresses and combine the port with the URL of
|
||||
/// the server.
|
||||
///
|
||||
/// For example, the server `http://192.168.0.1` might end up with a `best_effort_multiaddr` of
|
||||
/// `/ipv4/192.168.0.1/tcp/9000` if the server advertises a listening address of
|
||||
/// `/ipv4/172.0.0.1/tcp/9000`.
|
||||
pub fn best_effort_multiaddr(&self, port: Option<u16>) -> Option<Multiaddr> {
|
||||
let tcp_port = if let Some(port) = port {
|
||||
port
|
||||
} else {
|
||||
self.listen_port().ok()?
|
||||
};
|
||||
|
||||
let mut multiaddr = Multiaddr::with_capacity(2);
|
||||
|
||||
match self.url.host()? {
|
||||
Host::Ipv4(addr) => multiaddr.push(Protocol::Ip4(addr)),
|
||||
Host::Domain(s) => multiaddr.push(Protocol::Dns4(Cow::Borrowed(s))),
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
multiaddr.push(Protocol::Tcp(tcp_port));
|
||||
|
||||
Some(multiaddr)
|
||||
}
|
||||
|
||||
/// Returns the IPv4 address of the server URL, unless it contains a FQDN.
|
||||
pub fn server_ipv4_addr(&self) -> Option<Ipv4Addr> {
|
||||
match self.url.host()? {
|
||||
Host::Ipv4(addr) => Some(addr),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the servers Eth2Config.
|
||||
pub fn eth2_config(&self) -> Result<Eth2Config, String> {
|
||||
get_eth2_config(self.url.clone()).map_err(|e| format!("Unable to get Eth2Config: {:?}", e))
|
||||
}
|
||||
|
||||
/// Returns the servers ENR address.
|
||||
pub fn enr(&self) -> Result<Enr, String> {
|
||||
get_enr(self.url.clone()).map_err(|e| format!("Unable to get ENR: {:?}", e))
|
||||
}
|
||||
|
||||
/// Returns the servers listening libp2p addresses.
|
||||
pub fn listen_port(&self) -> Result<u16, String> {
|
||||
get_listen_port(self.url.clone()).map_err(|e| format!("Unable to get listen port: {:?}", e))
|
||||
}
|
||||
|
||||
/// Returns the genesis block and state.
|
||||
pub fn genesis<T: EthSpec>(&self) -> Result<(BeaconState<T>, BeaconBlock<T>), String> {
|
||||
let genesis_slot = Slot::new(0);
|
||||
|
||||
let block = get_block(self.url.clone(), genesis_slot)
|
||||
.map_err(|e| format!("Unable to get genesis block: {:?}", e))?
|
||||
.beacon_block;
|
||||
let state = get_state(self.url.clone(), genesis_slot)
|
||||
.map_err(|e| format!("Unable to get genesis state: {:?}", e))?
|
||||
.beacon_state;
|
||||
|
||||
Ok((state, block))
|
||||
}
|
||||
|
||||
/// Returns the most recent finalized state and block.
|
||||
pub fn finalized<T: EthSpec>(&self) -> Result<(BeaconState<T>, BeaconBlock<T>), String> {
|
||||
let slots_per_epoch = get_slots_per_epoch(self.url.clone())
|
||||
.map_err(|e| format!("Unable to get slots per epoch: {:?}", e))?;
|
||||
let finalized_slot = get_finalized_slot(self.url.clone(), slots_per_epoch.as_u64())
|
||||
.map_err(|e| format!("Unable to get finalized slot: {:?}", e))?;
|
||||
|
||||
let block = get_block(self.url.clone(), finalized_slot)
|
||||
.map_err(|e| format!("Unable to get finalized block: {:?}", e))?
|
||||
.beacon_block;
|
||||
let state = get_state(self.url.clone(), finalized_slot)
|
||||
.map_err(|e| format!("Unable to get finalized state: {:?}", e))?
|
||||
.beacon_state;
|
||||
|
||||
Ok((state, block))
|
||||
}
|
||||
}
|
||||
|
||||
fn get_slots_per_epoch(mut url: Url) -> Result<Slot, Error> {
|
||||
url.path_segments_mut()
|
||||
.map(|mut url| {
|
||||
url.push("spec").push("slots_per_epoch");
|
||||
})
|
||||
.map_err(|_| Error::InvalidUrl)?;
|
||||
|
||||
reqwest::get(url)?
|
||||
.error_for_status()?
|
||||
.json()
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
fn get_eth2_config(mut url: Url) -> Result<Eth2Config, Error> {
|
||||
url.path_segments_mut()
|
||||
.map(|mut url| {
|
||||
url.push("spec").push("eth2_config");
|
||||
})
|
||||
.map_err(|_| Error::InvalidUrl)?;
|
||||
|
||||
reqwest::get(url)?
|
||||
.error_for_status()?
|
||||
.json()
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
fn get_finalized_slot(mut url: Url, slots_per_epoch: u64) -> Result<Slot, Error> {
|
||||
url.path_segments_mut()
|
||||
.map(|mut url| {
|
||||
url.push("beacon").push("latest_finalized_checkpoint");
|
||||
})
|
||||
.map_err(|_| Error::InvalidUrl)?;
|
||||
|
||||
let checkpoint: Checkpoint = reqwest::get(url)?.error_for_status()?.json()?;
|
||||
|
||||
Ok(checkpoint.epoch.start_slot(slots_per_epoch))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(bound = "T: EthSpec")]
|
||||
pub struct StateResponse<T: EthSpec> {
|
||||
pub root: Hash256,
|
||||
pub beacon_state: BeaconState<T>,
|
||||
}
|
||||
|
||||
fn get_state<T: EthSpec>(mut url: Url, slot: Slot) -> Result<StateResponse<T>, Error> {
|
||||
url.path_segments_mut()
|
||||
.map(|mut url| {
|
||||
url.push("beacon").push("state");
|
||||
})
|
||||
.map_err(|_| Error::InvalidUrl)?;
|
||||
|
||||
url.query_pairs_mut()
|
||||
.append_pair("slot", &format!("{}", slot.as_u64()));
|
||||
|
||||
reqwest::get(url)?
|
||||
.error_for_status()?
|
||||
.json()
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(bound = "T: EthSpec")]
|
||||
pub struct BlockResponse<T: EthSpec> {
|
||||
pub root: Hash256,
|
||||
pub beacon_block: BeaconBlock<T>,
|
||||
}
|
||||
|
||||
fn get_block<T: EthSpec>(mut url: Url, slot: Slot) -> Result<BlockResponse<T>, Error> {
|
||||
url.path_segments_mut()
|
||||
.map(|mut url| {
|
||||
url.push("beacon").push("block");
|
||||
})
|
||||
.map_err(|_| Error::InvalidUrl)?;
|
||||
|
||||
url.query_pairs_mut()
|
||||
.append_pair("slot", &format!("{}", slot.as_u64()));
|
||||
|
||||
reqwest::get(url)?
|
||||
.error_for_status()?
|
||||
.json()
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
fn get_enr(mut url: Url) -> Result<Enr, Error> {
|
||||
url.path_segments_mut()
|
||||
.map(|mut url| {
|
||||
url.push("network").push("enr");
|
||||
})
|
||||
.map_err(|_| Error::InvalidUrl)?;
|
||||
|
||||
reqwest::get(url)?
|
||||
.error_for_status()?
|
||||
.json()
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
fn get_listen_port(mut url: Url) -> Result<u16, Error> {
|
||||
url.path_segments_mut()
|
||||
.map(|mut url| {
|
||||
url.push("network").push("listen_port");
|
||||
})
|
||||
.map_err(|_| Error::InvalidUrl)?;
|
||||
|
||||
reqwest::get(url)?
|
||||
.error_for_status()?
|
||||
.json()
|
||||
.map_err(Into::into)
|
||||
}
|
@ -7,8 +7,7 @@
|
||||
//! `Context` which can be handed to any service that wishes to start async tasks or perform
|
||||
//! logging.
|
||||
|
||||
use clap::ArgMatches;
|
||||
use eth2_config::{read_from_file, Eth2Config};
|
||||
use eth2_config::Eth2Config;
|
||||
use eth2_testnet_config::Eth2TestnetConfig;
|
||||
use futures::{sync::oneshot, Future};
|
||||
use slog::{info, o, Drain, Level, Logger};
|
||||
@ -139,34 +138,15 @@ impl<E: EthSpec> EnvironmentBuilder<E> {
|
||||
}
|
||||
|
||||
/// Setups eth2 config using the CLI arguments.
|
||||
pub fn setup_eth2_config(
|
||||
pub fn eth2_testnet_config(
|
||||
mut self,
|
||||
datadir: PathBuf,
|
||||
eth2_testnet_config: Eth2TestnetConfig<E>,
|
||||
cli_args: &ArgMatches,
|
||||
eth2_testnet_config: &Eth2TestnetConfig<E>,
|
||||
) -> Result<Self, String> {
|
||||
self.load_eth2_config(&datadir)?;
|
||||
|
||||
match cli_args.subcommand() {
|
||||
("testnet", Some(sub_cli_args)) => {
|
||||
// Modify the `SECONDS_PER_SLOT` "constant".
|
||||
if let Some(slot_time) = sub_cli_args.value_of("slot-time") {
|
||||
let slot_time = slot_time
|
||||
.parse::<u64>()
|
||||
.map_err(|e| format!("Unable to parse slot-time: {:?}", e))?;
|
||||
|
||||
self.eth2_config.spec.milliseconds_per_slot = slot_time;
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
if !datadir.exists() {
|
||||
// Create a new chain spec from the default configuration.
|
||||
self.eth2_config.spec = eth2_testnet_config
|
||||
.yaml_config
|
||||
.as_ref()
|
||||
.ok_or_else(|| {
|
||||
"The testnet directory must contain a spec config".to_string()
|
||||
})?
|
||||
.ok_or_else(|| "The testnet directory must contain a spec config".to_string())?
|
||||
.apply_to_chain_spec::<E>(&self.eth2_config.spec)
|
||||
.ok_or_else(|| {
|
||||
format!(
|
||||
@ -174,37 +154,10 @@ impl<E: EthSpec> EnvironmentBuilder<E> {
|
||||
&self.eth2_config.spec_constants
|
||||
)
|
||||
})?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
/// Loads the eth2 config if the file exists.
|
||||
fn load_eth2_config(&mut self, datadir: &PathBuf) -> Result<(), String> {
|
||||
let filename = datadir.join(ETH2_CONFIG_FILENAME);
|
||||
if filename.exists() {
|
||||
let loaded_eth2_config: Eth2Config = read_from_file(filename.clone())
|
||||
.map_err(|e| format!("Unable to parse {:?} file: {:?}", filename, e))?
|
||||
.ok_or_else(|| format!("{:?} file does not exist", filename))?;
|
||||
|
||||
// The loaded spec must be using the same spec constants (e.g., minimal, mainnet) as the
|
||||
// client expects.
|
||||
if loaded_eth2_config.spec_constants == self.eth2_config.spec_constants {
|
||||
self.eth2_config = loaded_eth2_config;
|
||||
} else {
|
||||
return Err(format!(
|
||||
"Eth2 config loaded from disk does not match client spec version. Got {} \
|
||||
expected {}",
|
||||
&loaded_eth2_config.spec_constants, &self.eth2_config.spec_constants
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Consumes the builder, returning an `Environment`.
|
||||
pub fn build(self) -> Result<Environment<E>, String> {
|
||||
Ok(Environment {
|
||||
|
@ -1,4 +1,8 @@
|
||||
#![cfg(test)]
|
||||
/*
|
||||
*
|
||||
* TODO: disabled until hardcoded testnet config is updated for v0.11
|
||||
*
|
||||
|
||||
use clap::ArgMatches;
|
||||
use environment::EnvironmentBuilder;
|
||||
@ -22,10 +26,6 @@ fn eth2_testnet_config() -> Eth2TestnetConfig<MainnetEthSpec> {
|
||||
Eth2TestnetConfig::hard_coded().expect("should decode hard_coded params")
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
* TODO: disabled until hardcoded testnet config is updated for v0.11
|
||||
*
|
||||
mod setup_eth2_config {
|
||||
use super::*;
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
#[macro_use]
|
||||
extern crate clap;
|
||||
|
||||
use beacon_node::{get_data_dir, get_eth2_testnet_config, get_testnet_dir, ProductionBeaconNode};
|
||||
use beacon_node::{get_eth2_testnet_config, get_testnet_dir, ProductionBeaconNode};
|
||||
use clap::{App, Arg, ArgMatches};
|
||||
use env_logger::{Builder, Env};
|
||||
use environment::EnvironmentBuilder;
|
||||
@ -73,6 +73,19 @@ fn main() {
|
||||
.help("Data directory for lighthouse keys and databases.")
|
||||
.takes_value(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("testnet-dir")
|
||||
.short("t")
|
||||
.long("testnet-dir")
|
||||
.value_name("DIR")
|
||||
.help(
|
||||
"Path to directory containing eth2_testnet specs. Defaults to \
|
||||
a hard-coded Lighthouse testnet. Only effective if there is no \
|
||||
existing database.",
|
||||
)
|
||||
.takes_value(true)
|
||||
.global(true),
|
||||
)
|
||||
.subcommand(beacon_node::cli_app())
|
||||
.subcommand(validator_client::cli_app())
|
||||
.subcommand(account_manager::cli_app())
|
||||
@ -110,15 +123,12 @@ fn run<E: EthSpec>(
|
||||
.ok_or_else(|| "Expected --debug-level flag".to_string())?;
|
||||
|
||||
let log_format = matches.value_of("log-format");
|
||||
let eth2_testnet_config = get_eth2_testnet_config(&get_testnet_dir(matches))?;
|
||||
|
||||
let mut environment = environment_builder
|
||||
.async_logger(debug_level, log_format)?
|
||||
.multi_threaded_tokio_runtime()?
|
||||
.setup_eth2_config(
|
||||
get_data_dir(matches),
|
||||
get_eth2_testnet_config(&get_testnet_dir(matches))?,
|
||||
matches,
|
||||
)?
|
||||
.eth2_testnet_config(ð2_testnet_config)?
|
||||
.build()?;
|
||||
|
||||
let log = environment.core_context().log;
|
||||
|
@ -13,7 +13,6 @@ eth2_ssz = "0.1.2"
|
||||
eth2_config = { path = "../eth2/utils/eth2_config" }
|
||||
tree_hash = "0.1.0"
|
||||
clap = "2.33.0"
|
||||
lighthouse_bootstrap = { path = "../eth2/utils/lighthouse_bootstrap" }
|
||||
eth2_interop_keypairs = { path = "../eth2/utils/eth2_interop_keypairs" }
|
||||
slot_clock = { path = "../eth2/utils/slot_clock" }
|
||||
types = { path = "../eth2/types" }
|
||||
|
Loading…
Reference in New Issue
Block a user