mirror of
https://github.com/sigp/lighthouse.git
synced 2026-03-06 10:11:44 +00:00
Eth1 Integration (#542)
* Refactor to cache Eth1Data * Fix merge conflicts and minor refactorings * Rename Eth1Cache to Eth1DataCache * Refactor events subscription * Add deposits module to interface with BeaconChain deposits * Remove utils * Rename to types.rs and add trait constraints to Eth1DataFetcher * Confirm to trait constraints. Make Web3DataFetcher cloneable * Make fetcher object member of deposit and eth1_data cache and other fixes * Fix update_cache function * Move fetch_eth1_data to impl block * Fix deposit tests * Create Eth1 object for interfacing with Beacon chain * Add `run` function for running update_cache and subscribe_deposit_logs tasks * Add logging * Run `cargo fmt` and make tests pass * Convert sync functions to async * Add timeouts to web3 functions * Return futures from cache functions * Add failed chaining of futures * Working cache updation * Clean up tests and `update_cache` function * Refactor `get_eth1_data` functions to work with future returning functions * Refactor eth1 `run` function to work with modified `update_cache` api * Minor changes * Add distance parameter to `update_cache` * Fix tests and other minor fixes * Working integration with cache and deposits * Add merkle_tree construction, proof generation and verification code * Add function to construct and fetch Deposits for BeaconNode * Add error handling * Import ssz * Add error handling to eth1 cache and fix minor errors * Run rustfmt * Fix minor bug * Rename Eth1Error and change to Result<T> * Change deposit fetching mechanism from notification based to poll based * Add deposits from eth1 chain in a given range every `x` blocks * Modify `run` function to accommodate changes * Minor fixes * Fix formatting * Initial commit. web3 api working. * Tidied up lib. Add function for fetching logs. * Refactor with `Eth1DataFetcher` trait * Add parsing for deposit contract logs and get_eth1_data function * Add `get_eth1_votes` function * Refactor to cache Eth1Data * Fix merge conflicts and minor refactorings * Rename Eth1Cache to Eth1DataCache * Refactor events subscription * Add deposits module to interface with BeaconChain deposits * Remove utils * Rename to types.rs and add trait constraints to Eth1DataFetcher * Confirm to trait constraints. Make Web3DataFetcher cloneable * Make fetcher object member of deposit and eth1_data cache and other fixes * Fix update_cache function * Move fetch_eth1_data to impl block * Fix deposit tests * Create Eth1 object for interfacing with Beacon chain * Add `run` function for running update_cache and subscribe_deposit_logs tasks * Add logging * Run `cargo fmt` and make tests pass * Convert sync functions to async * Add timeouts to web3 functions * Return futures from cache functions * Add failed chaining of futures * Working cache updation * Clean up tests and `update_cache` function * Refactor `get_eth1_data` functions to work with future returning functions * Refactor eth1 `run` function to work with modified `update_cache` api * Minor changes * Add distance parameter to `update_cache` * Fix tests and other minor fixes * Working integration with cache and deposits * Add merkle_tree construction, proof generation and verification code * Add function to construct and fetch Deposits for BeaconNode * Add error handling * Import ssz * Add error handling to eth1 cache and fix minor errors * Run rustfmt * Fix minor bug * Rename Eth1Error and change to Result<T> * Change deposit fetching mechanism from notification based to poll based * Add deposits from eth1 chain in a given range every `x` blocks * Modify `run` function to accommodate changes * Minor fixes * Fix formatting * Fix merge issue * Refactor with `Config` struct. Remote `ContractConfig` * Rename eth1_chain crate to eth1 * Rename files and read abi file using `fs::read` * Move eth1 to lib * Remove unnecessary mutability constraint * Add `Web3Backend` for returning actual eth1 data * Refactor `get_eth1_votes` to return a Result * Delete `eth1_chain` crate * Return `Result` from `get_deposits` * Fix range of deposits to return to beacon chain * Add `get_block_height_by_hash` trait function * Add naive method for getting `previous_eth1_distance` * Add eth1 config params to main config * Add instructions for setting up eth1 testing environment * Add build script to fetch deposit contract abi * Contract ABI is part of compiled binary * Fix minor bugs * Move docs to lib * Add timeout to config * Remove print statements * Change warn to error * Fix typos * Removed prints in test and get timeout value from config * Fixed error types * Added logging to web3_fetcher * Refactor for modified web3 api * Fix minor stuff * Add build script * Tidy, hide eth1 integration tests behind flag * Add http crate * Add first stages of eth1_test_rig * Fix deposits on test rig * Fix bug with deposit count method * Add block hash getter to http eth1 * Clean eth1 http crate and tests * Add script to start ganache * Adds deposit tree to eth1-http * Extend deposit tree tests * Tidy tests in eth1-http * Add more detail to get block request * Add block cache to eth1-http * Rename deposit tree to deposit cache * Add inital updating to eth1-http * Tidy updater * Fix compile bugs in tests * Adds an Eth1DataCache builder * Reorg eth1-http files * Add (failing) tests for eth1 updater * Rename files, fix bug in eth1-http * Ensure that ganache timestamps are increasing * Fix bugs with getting eth1data ancestors * Improve eth1 testing, fix bugs * Add truncate method to block cache * Add pruning to block cache update process * Add tests for block pruning * Allow for dropping an expired cache. * Add more comments * Add first compiling version of deposit updater * Add common fn for getting range of required blocks * Add passing deposit update test * Improve tests * Fix block pruning bug * Add tests for running two updates at once * Add updater services to eth1 * Add deposit collection to beacon chain * Add incomplete builder experiments * Add first working version of beacon chain builder * Update test harness to new beacon chain type * Rename builder file, tidy * Add first working client builder * Progress further on client builder * Update becaon node binary to use client builder * Ensure release tests compile * Remove old eth1 crate * Add first pass of new lighthouse binary * Fix websocket server startup * Remove old binary code from beacon_node crate * Add first working beacon node tests * Add genesis crate, new eth1 cache_2 * Add Serivce to Eth1Cache * Refactor with general eth1 improvements * Add passing genesis test * Tidy, add comments * Add more comments to eth1 service * Add further eth1 progress * Fix some bugs with genesis * Fix eth1 bugs, make eth1 linking more efficient * Shift logic in genesis service * Add more comments to genesis service * Add gzip, max request values, timeouts to http * Update testnet parameters to suit goerli testnet * Add ability to vary Fork, fix custom spec * Be more explicit about deposit fork version * Start adding beacon chain eth1 option * Add more flexibility to prod client * Further runtime refactoring * Allow for starting from store * Add bootstrapping to client config * Add remote_beacon_node crate * Update eth1 service for more configurability * Update eth1 tests to use less runtimes * Patch issues with tests using too many files * Move dummy eth1 backend flag * Ensure all tests pass * Add ganache-cli to Dockerfile * Use a special docker hub image for testing * Appease clippy * Move validator client into lighthouse binary * Allow starting with dummy eth1 backend * Improve logging * Fix dummy eth1 backend from cli * Add extra testnet command * Ensure consistent spec in beacon node * Update eth1 rig to work on goerli * Tidy lcli, start adding support for yaml config * Add incomplete YamlConfig struct * Remove efforts at YamlConfig * Add incomplete eth1 voting. Blocked on spec issues * Add (untested) first pass at eth1 vote algo * Add tests for winning vote * Add more tests for eth1 chain * Add more eth1 voting tests * Added more eth1 voting testing * Change test name * Add more tests to eth1 chain * Tidy eth1 generics, add more tests * Improve comments * Tidy beacon_node tests * Tidy, rename JsonRpc.. to Caching.. * Tidy voting logic * Tidy builder docs * Add comments, tidy eth1 * Add more comments to eth1 * Fix bug with winning_vote * Add doc comments to the `ClientBuilder` * Remove commented-out code * Improve `ClientBuilder` docs * Add comments to client config * Add decoding test for `ClientConfig` * Remove unused `DepositSet` struct * Tidy `block_cache` * Remove commented out lines * Remove unused code in `eth1` crate * Remove old validator binary `main.rs` * Tidy, fix tests compile error * Add initial tests for get_deposits * Remove dead code in eth1_test_rig * Update TestingDepositBuilder * Add testing for getting eth1 deposits * Fix duplicate rand dep * Remove dead code * Remove accidentally-added files * Fix comment in eth1_genesis_service * Add .gitignore for eth1_test_rig * Fix bug in eth1_genesis_service * Remove dead code from eth2_config * Fix tabs/spaces in root Cargo.toml * Tidy eth1 crate * Allow for re-use of eth1 service after genesis * Update docs for new CLI * Change README gif * Tidy eth1 http module * Tidy eth1 service * Tidy environment crate * Remove unused file * Tidy, add comments * Remove commented-out code * Address majority of Michael's comments * Address other PR comments * Add link to issue alongside TODO
This commit is contained in:
@@ -4,10 +4,6 @@ version = "0.1.0"
|
||||
authors = ["Paul Hauner <paul@paulhauner.com>", "Age Manning <Age@AgeManning.com>", "Luke Anderson <luke@lukeanderson.com.au>"]
|
||||
edition = "2018"
|
||||
|
||||
[[bin]]
|
||||
name = "validator_client"
|
||||
path = "src/main.rs"
|
||||
|
||||
[lib]
|
||||
name = "validator_client"
|
||||
path = "src/lib.rs"
|
||||
@@ -38,4 +34,7 @@ bincode = "1.2.0"
|
||||
futures = "0.1.29"
|
||||
dirs = "2.0.2"
|
||||
logging = { path = "../eth2/utils/logging" }
|
||||
environment = { path = "../lighthouse/environment" }
|
||||
parking_lot = "0.7"
|
||||
exit-future = "0.1.4"
|
||||
libc = "0.2.65"
|
||||
|
||||
123
validator_client/src/cli.rs
Normal file
123
validator_client/src/cli.rs
Normal file
@@ -0,0 +1,123 @@
|
||||
use crate::config::{DEFAULT_SERVER, DEFAULT_SERVER_GRPC_PORT, DEFAULT_SERVER_HTTP_PORT};
|
||||
use clap::{App, Arg, SubCommand};
|
||||
|
||||
pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
|
||||
App::new("Validator Client")
|
||||
.visible_aliases(&["v", "vc", "validator", "validator_client"])
|
||||
.version("0.0.1")
|
||||
.author("Sigma Prime <contact@sigmaprime.io>")
|
||||
.about("Eth 2.0 Validator Client")
|
||||
.arg(
|
||||
Arg::with_name("datadir")
|
||||
.long("datadir")
|
||||
.short("d")
|
||||
.value_name("DIR")
|
||||
.help("Data directory for keys and databases.")
|
||||
.takes_value(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("logfile")
|
||||
.long("logfile")
|
||||
.value_name("logfile")
|
||||
.help("File path where output will be written.")
|
||||
.takes_value(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("spec")
|
||||
.long("spec")
|
||||
.value_name("TITLE")
|
||||
.help("Specifies the default eth2 spec type.")
|
||||
.takes_value(true)
|
||||
.possible_values(&["mainnet", "minimal", "interop"])
|
||||
.conflicts_with("eth2-config")
|
||||
.global(true)
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("eth2-config")
|
||||
.long("eth2-config")
|
||||
.short("e")
|
||||
.value_name("TOML_FILE")
|
||||
.help("Path to Ethereum 2.0 config and specification file (e.g., eth2_spec.toml).")
|
||||
.takes_value(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("server")
|
||||
.long("server")
|
||||
.value_name("NETWORK_ADDRESS")
|
||||
.help("Address to connect to BeaconNode.")
|
||||
.default_value(DEFAULT_SERVER)
|
||||
.takes_value(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("server-grpc-port")
|
||||
.long("server-grpc-port")
|
||||
.short("g")
|
||||
.value_name("PORT")
|
||||
.help("Port to use for gRPC API connection to the server.")
|
||||
.default_value(DEFAULT_SERVER_GRPC_PORT)
|
||||
.takes_value(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("server-http-port")
|
||||
.long("server-http-port")
|
||||
.short("h")
|
||||
.value_name("PORT")
|
||||
.help("Port to use for HTTP API connection to the server.")
|
||||
.default_value(DEFAULT_SERVER_HTTP_PORT)
|
||||
.takes_value(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("debug-level")
|
||||
.long("debug-level")
|
||||
.value_name("LEVEL")
|
||||
.short("s")
|
||||
.help("The title of the spec constants for chain config.")
|
||||
.takes_value(true)
|
||||
.possible_values(&["info", "debug", "trace", "warn", "error", "crit"])
|
||||
.default_value("trace"),
|
||||
)
|
||||
/*
|
||||
* The "testnet" sub-command.
|
||||
*
|
||||
* Used for starting testnet validator clients.
|
||||
*/
|
||||
.subcommand(SubCommand::with_name("testnet")
|
||||
.about("Starts a testnet validator using INSECURE, predicatable private keys, based off the canonical \
|
||||
validator index. ONLY USE FOR TESTING PURPOSES!")
|
||||
.arg(
|
||||
Arg::with_name("bootstrap")
|
||||
.short("b")
|
||||
.long("bootstrap")
|
||||
.help("Connect to the RPC server to download the eth2_config via the HTTP API.")
|
||||
)
|
||||
.subcommand(SubCommand::with_name("insecure")
|
||||
.about("Uses the standard, predicatable `interop` keygen method to produce a range \
|
||||
of predicatable private keys and starts performing their validator duties.")
|
||||
.arg(Arg::with_name("first_validator")
|
||||
.value_name("VALIDATOR_INDEX")
|
||||
.required(true)
|
||||
.help("The first validator public key to be generated for this client."))
|
||||
.arg(Arg::with_name("validator_count")
|
||||
.value_name("COUNT")
|
||||
.required(true)
|
||||
.help("The number of validators."))
|
||||
)
|
||||
.subcommand(SubCommand::with_name("interop-yaml")
|
||||
.about("Loads plain-text secret keys from YAML files. Expects the interop format defined
|
||||
in the ethereum/eth2.0-pm repo.")
|
||||
.arg(Arg::with_name("path")
|
||||
.value_name("PATH")
|
||||
.required(true)
|
||||
.help("Path to a YAML file."))
|
||||
)
|
||||
)
|
||||
.subcommand(SubCommand::with_name("sign_block")
|
||||
.about("Connects to the beacon server, requests a new block (after providing reveal),\
|
||||
and prints the signed block to standard out")
|
||||
.arg(Arg::with_name("validator")
|
||||
.value_name("VALIDATOR")
|
||||
.required(true)
|
||||
.help("The pubkey of the validator that should sign the block.")
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -10,10 +10,10 @@ use self::epoch_duties::{EpochDuties, EpochDutiesMapError};
|
||||
pub use self::epoch_duties::{EpochDutiesMap, WorkInfo};
|
||||
use super::signer::Signer;
|
||||
use futures::Async;
|
||||
use parking_lot::RwLock;
|
||||
use slog::{debug, error, info};
|
||||
use std::fmt::Display;
|
||||
use std::sync::Arc;
|
||||
use std::sync::RwLock;
|
||||
use types::{Epoch, PublicKey, Slot};
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
@@ -55,20 +55,20 @@ impl<U: BeaconNodeDuties, S: Signer + Display> DutiesManager<U, S> {
|
||||
let duties = self.beacon_node.request_duties(epoch, &public_keys)?;
|
||||
{
|
||||
// If these duties were known, check to see if they're updates or identical.
|
||||
if let Some(known_duties) = self.duties_map.read()?.get(&epoch) {
|
||||
if let Some(known_duties) = self.duties_map.read().get(&epoch) {
|
||||
if *known_duties == duties {
|
||||
return Ok(UpdateOutcome::NoChange(epoch));
|
||||
}
|
||||
}
|
||||
}
|
||||
if !self.duties_map.read()?.contains_key(&epoch) {
|
||||
if !self.duties_map.read().contains_key(&epoch) {
|
||||
//TODO: Remove clone by removing duties from outcome
|
||||
self.duties_map.write()?.insert(epoch, duties.clone());
|
||||
self.duties_map.write().insert(epoch, duties.clone());
|
||||
return Ok(UpdateOutcome::NewDuties(epoch, duties));
|
||||
}
|
||||
// duties have changed
|
||||
//TODO: Duties could be large here. Remove from display and avoid the clone.
|
||||
self.duties_map.write()?.insert(epoch, duties.clone());
|
||||
self.duties_map.write().insert(epoch, duties.clone());
|
||||
Ok(UpdateOutcome::DutiesChanged(epoch, duties))
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@ impl<U: BeaconNodeDuties, S: Signer + Display> DutiesManager<U, S> {
|
||||
let mut current_work: Vec<(usize, WorkInfo)> = Vec::new();
|
||||
|
||||
// if the map is poisoned, return None
|
||||
let duties = self.duties_map.read().ok()?;
|
||||
let duties = self.duties_map.read();
|
||||
|
||||
for (index, validator_signer) in self.signers.iter().enumerate() {
|
||||
match duties.is_work_slot(slot, &validator_signer.to_public()) {
|
||||
|
||||
@@ -1,4 +1,258 @@
|
||||
extern crate libc;
|
||||
pub mod config;
|
||||
mod attestation_producer;
|
||||
mod block_producer;
|
||||
mod cli;
|
||||
mod config;
|
||||
mod duties;
|
||||
mod error;
|
||||
mod service;
|
||||
mod signer;
|
||||
|
||||
pub use crate::config::Config;
|
||||
pub use cli::cli_app;
|
||||
pub use config::Config;
|
||||
|
||||
use clap::ArgMatches;
|
||||
use config::{Config as ClientConfig, KeySource};
|
||||
use environment::RuntimeContext;
|
||||
use eth2_config::Eth2Config;
|
||||
use exit_future::Signal;
|
||||
use futures::Stream;
|
||||
use lighthouse_bootstrap::Bootstrapper;
|
||||
use parking_lot::RwLock;
|
||||
use protos::services_grpc::ValidatorServiceClient;
|
||||
use service::Service;
|
||||
use slog::{error, info, warn, Logger};
|
||||
use slot_clock::SlotClock;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, Instant};
|
||||
use tokio::timer::Interval;
|
||||
use types::{EthSpec, Keypair};
|
||||
|
||||
/// A fixed amount of time after a slot to perform operations. This gives the node time to complete
|
||||
/// per-slot processes.
|
||||
const TIME_DELAY_FROM_SLOT: Duration = Duration::from_millis(100);
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ProductionValidatorClient<T: EthSpec> {
|
||||
context: RuntimeContext<T>,
|
||||
service: Arc<Service<ValidatorServiceClient, Keypair, T>>,
|
||||
exit_signals: Arc<RwLock<Vec<Signal>>>,
|
||||
}
|
||||
|
||||
impl<T: EthSpec> ProductionValidatorClient<T> {
|
||||
/// Instantiates the validator client, _without_ starting the timers to trigger block
|
||||
/// and attestation production.
|
||||
pub fn new_from_cli(context: RuntimeContext<T>, matches: &ArgMatches) -> Result<Self, String> {
|
||||
let mut log = context.log.clone();
|
||||
|
||||
let (client_config, eth2_config) = get_configs(&matches, &mut log)
|
||||
.map_err(|e| format!("Unable to initialize config: {}", e))?;
|
||||
|
||||
info!(
|
||||
log,
|
||||
"Starting validator client";
|
||||
"datadir" => client_config.full_data_dir().expect("Unable to find datadir").to_str(),
|
||||
);
|
||||
|
||||
let service: Service<ValidatorServiceClient, Keypair, T> =
|
||||
Service::initialize_service(client_config, eth2_config, log.clone())
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
Ok(Self {
|
||||
context,
|
||||
service: Arc::new(service),
|
||||
exit_signals: Arc::new(RwLock::new(vec![])),
|
||||
})
|
||||
}
|
||||
|
||||
/// Starts the timers to trigger block and attestation production.
|
||||
pub fn start_service(&self) -> Result<(), String> {
|
||||
let service = self.clone().service;
|
||||
let log = self.context.log.clone();
|
||||
|
||||
let duration_to_next_slot = service
|
||||
.slot_clock
|
||||
.duration_to_next_slot()
|
||||
.ok_or_else(|| "Unable to determine duration to next slot. Exiting.".to_string())?;
|
||||
|
||||
// set up the validator work interval - start at next slot and proceed every slot
|
||||
let interval = {
|
||||
// Set the interval to start at the next slot, and every slot after
|
||||
let slot_duration = Duration::from_millis(service.spec.milliseconds_per_slot);
|
||||
//TODO: Handle checked add correctly
|
||||
Interval::new(Instant::now() + duration_to_next_slot, slot_duration)
|
||||
};
|
||||
|
||||
if service.slot_clock.now().is_none() {
|
||||
warn!(
|
||||
log,
|
||||
"Starting node prior to genesis";
|
||||
);
|
||||
}
|
||||
|
||||
info!(
|
||||
log,
|
||||
"Waiting for next slot";
|
||||
"seconds_to_wait" => duration_to_next_slot.as_secs()
|
||||
);
|
||||
|
||||
let (exit_signal, exit_fut) = exit_future::signal();
|
||||
|
||||
self.exit_signals.write().push(exit_signal);
|
||||
|
||||
/* kick off the core service */
|
||||
self.context.executor.spawn(
|
||||
interval
|
||||
.map_err(move |e| {
|
||||
error! {
|
||||
log,
|
||||
"Timer thread failed";
|
||||
"error" => format!("{}", e)
|
||||
}
|
||||
})
|
||||
.and_then(move |_| if exit_fut.is_live() { Ok(()) } else { Err(()) })
|
||||
.for_each(move |_| {
|
||||
// wait for node to process
|
||||
std::thread::sleep(TIME_DELAY_FROM_SLOT);
|
||||
// if a non-fatal error occurs, proceed to the next slot.
|
||||
let _ignore_error = service.per_slot_execution();
|
||||
// completed a slot process
|
||||
Ok(())
|
||||
}),
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses the CLI arguments and attempts to load the client and eth2 configuration.
|
||||
///
|
||||
/// This is not a pure function, it reads from disk and may contact network servers.
|
||||
fn get_configs(
|
||||
cli_args: &ArgMatches,
|
||||
mut log: &mut Logger,
|
||||
) -> Result<(ClientConfig, Eth2Config), String> {
|
||||
let mut client_config = ClientConfig::default();
|
||||
|
||||
client_config.apply_cli_args(&cli_args, &mut log)?;
|
||||
|
||||
if let Some(server) = cli_args.value_of("server") {
|
||||
client_config.server = server.to_string();
|
||||
}
|
||||
|
||||
if let Some(port) = cli_args.value_of("server-http-port") {
|
||||
client_config.server_http_port = port
|
||||
.parse::<u16>()
|
||||
.map_err(|e| format!("Unable to parse HTTP port: {:?}", e))?;
|
||||
}
|
||||
|
||||
if let Some(port) = cli_args.value_of("server-grpc-port") {
|
||||
client_config.server_grpc_port = port
|
||||
.parse::<u16>()
|
||||
.map_err(|e| format!("Unable to parse gRPC port: {:?}", e))?;
|
||||
}
|
||||
|
||||
info!(
|
||||
*log,
|
||||
"Beacon node connection info";
|
||||
"grpc_port" => client_config.server_grpc_port,
|
||||
"http_port" => client_config.server_http_port,
|
||||
"server" => &client_config.server,
|
||||
);
|
||||
|
||||
let (client_config, eth2_config) = match cli_args.subcommand() {
|
||||
("testnet", Some(sub_cli_args)) => {
|
||||
if cli_args.is_present("eth2-config") && sub_cli_args.is_present("bootstrap") {
|
||||
return Err(
|
||||
"Cannot specify --eth2-config and --bootstrap as it may result \
|
||||
in ambiguity."
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
process_testnet_subcommand(sub_cli_args, client_config, log)
|
||||
}
|
||||
_ => return Err("You must use the testnet command. See '--help'.".into()),
|
||||
}?;
|
||||
|
||||
Ok((client_config, eth2_config))
|
||||
}
|
||||
|
||||
/// Parses the `testnet` CLI subcommand.
|
||||
///
|
||||
/// This is not a pure function, it reads from disk and may contact network servers.
|
||||
fn process_testnet_subcommand(
|
||||
cli_args: &ArgMatches,
|
||||
mut client_config: ClientConfig,
|
||||
log: &Logger,
|
||||
) -> Result<(ClientConfig, Eth2Config), String> {
|
||||
let eth2_config = if cli_args.is_present("bootstrap") {
|
||||
info!(log, "Connecting to bootstrap server");
|
||||
let bootstrapper = Bootstrapper::connect(
|
||||
format!(
|
||||
"http://{}:{}",
|
||||
client_config.server, client_config.server_http_port
|
||||
),
|
||||
&log,
|
||||
)?;
|
||||
|
||||
let eth2_config = bootstrapper.eth2_config()?;
|
||||
|
||||
info!(
|
||||
log,
|
||||
"Bootstrapped eth2 config via HTTP";
|
||||
"slot_time_millis" => eth2_config.spec.milliseconds_per_slot,
|
||||
"spec" => ð2_config.spec_constants,
|
||||
);
|
||||
|
||||
eth2_config
|
||||
} else {
|
||||
match cli_args.value_of("spec") {
|
||||
Some("mainnet") => Eth2Config::mainnet(),
|
||||
Some("minimal") => Eth2Config::minimal(),
|
||||
Some("interop") => Eth2Config::interop(),
|
||||
_ => return Err("No --spec flag provided. See '--help'.".into()),
|
||||
}
|
||||
};
|
||||
|
||||
client_config.key_source = match cli_args.subcommand() {
|
||||
("insecure", Some(sub_cli_args)) => {
|
||||
let first = sub_cli_args
|
||||
.value_of("first_validator")
|
||||
.ok_or_else(|| "No first validator supplied")?
|
||||
.parse::<usize>()
|
||||
.map_err(|e| format!("Unable to parse first validator: {:?}", e))?;
|
||||
let count = sub_cli_args
|
||||
.value_of("validator_count")
|
||||
.ok_or_else(|| "No validator count supplied")?
|
||||
.parse::<usize>()
|
||||
.map_err(|e| format!("Unable to parse validator count: {:?}", e))?;
|
||||
|
||||
info!(
|
||||
log,
|
||||
"Generating unsafe testing keys";
|
||||
"first_validator" => first,
|
||||
"count" => count
|
||||
);
|
||||
|
||||
KeySource::TestingKeypairRange(first..first + count)
|
||||
}
|
||||
("interop-yaml", Some(sub_cli_args)) => {
|
||||
let path = sub_cli_args
|
||||
.value_of("path")
|
||||
.ok_or_else(|| "No yaml path supplied")?
|
||||
.parse::<PathBuf>()
|
||||
.map_err(|e| format!("Unable to parse yaml path: {:?}", e))?;
|
||||
|
||||
info!(
|
||||
log,
|
||||
"Loading keypairs from interop YAML format";
|
||||
"path" => format!("{:?}", path),
|
||||
);
|
||||
|
||||
KeySource::YamlKeypairs(path)
|
||||
}
|
||||
_ => KeySource::Disk,
|
||||
};
|
||||
|
||||
Ok((client_config, eth2_config))
|
||||
}
|
||||
|
||||
@@ -1,354 +0,0 @@
|
||||
mod attestation_producer;
|
||||
mod block_producer;
|
||||
mod config;
|
||||
mod duties;
|
||||
pub mod error;
|
||||
mod service;
|
||||
mod signer;
|
||||
|
||||
use crate::config::{
|
||||
Config as ClientConfig, KeySource, DEFAULT_SERVER, DEFAULT_SERVER_GRPC_PORT,
|
||||
DEFAULT_SERVER_HTTP_PORT,
|
||||
};
|
||||
use crate::service::Service as ValidatorService;
|
||||
use clap::{App, Arg, ArgMatches, SubCommand};
|
||||
use eth2_config::Eth2Config;
|
||||
use lighthouse_bootstrap::Bootstrapper;
|
||||
use protos::services_grpc::ValidatorServiceClient;
|
||||
use slog::{crit, error, info, o, Drain, Level, Logger};
|
||||
use std::path::PathBuf;
|
||||
use types::{InteropEthSpec, Keypair, MainnetEthSpec, MinimalEthSpec};
|
||||
|
||||
pub const DEFAULT_SPEC: &str = "minimal";
|
||||
pub const DEFAULT_DATA_DIR: &str = ".lighthouse-validator";
|
||||
pub const CLIENT_CONFIG_FILENAME: &str = "validator-client.toml";
|
||||
pub const ETH2_CONFIG_FILENAME: &str = "eth2-spec.toml";
|
||||
|
||||
type Result<T> = core::result::Result<T, String>;
|
||||
|
||||
fn main() {
|
||||
// Logging
|
||||
let decorator = slog_term::TermDecorator::new().build();
|
||||
let decorator = logging::AlignedTermDecorator::new(decorator, logging::MAX_MESSAGE_WIDTH);
|
||||
let drain = slog_term::FullFormat::new(decorator).build().fuse();
|
||||
let drain = slog_async::Async::new(drain).build().fuse();
|
||||
|
||||
// CLI
|
||||
let matches = App::new("Lighthouse Validator Client")
|
||||
.version("0.0.1")
|
||||
.author("Sigma Prime <contact@sigmaprime.io>")
|
||||
.about("Eth 2.0 Validator Client")
|
||||
.arg(
|
||||
Arg::with_name("datadir")
|
||||
.long("datadir")
|
||||
.short("d")
|
||||
.value_name("DIR")
|
||||
.help("Data directory for keys and databases.")
|
||||
.takes_value(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("logfile")
|
||||
.long("logfile")
|
||||
.value_name("logfile")
|
||||
.help("File path where output will be written.")
|
||||
.takes_value(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("spec")
|
||||
.long("spec")
|
||||
.value_name("TITLE")
|
||||
.help("Specifies the default eth2 spec type.")
|
||||
.takes_value(true)
|
||||
.possible_values(&["mainnet", "minimal", "interop"])
|
||||
.conflicts_with("eth2-config")
|
||||
.global(true)
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("eth2-config")
|
||||
.long("eth2-config")
|
||||
.short("e")
|
||||
.value_name("TOML_FILE")
|
||||
.help("Path to Ethereum 2.0 config and specification file (e.g., eth2_spec.toml).")
|
||||
.takes_value(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("server")
|
||||
.long("server")
|
||||
.value_name("NETWORK_ADDRESS")
|
||||
.help("Address to connect to BeaconNode.")
|
||||
.default_value(DEFAULT_SERVER)
|
||||
.takes_value(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("server-grpc-port")
|
||||
.long("server-grpc-port")
|
||||
.short("g")
|
||||
.value_name("PORT")
|
||||
.help("Port to use for gRPC API connection to the server.")
|
||||
.default_value(DEFAULT_SERVER_GRPC_PORT)
|
||||
.takes_value(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("server-http-port")
|
||||
.long("server-http-port")
|
||||
.short("h")
|
||||
.value_name("PORT")
|
||||
.help("Port to use for HTTP API connection to the server.")
|
||||
.default_value(DEFAULT_SERVER_HTTP_PORT)
|
||||
.takes_value(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("debug-level")
|
||||
.long("debug-level")
|
||||
.value_name("LEVEL")
|
||||
.short("s")
|
||||
.help("The title of the spec constants for chain config.")
|
||||
.takes_value(true)
|
||||
.possible_values(&["info", "debug", "trace", "warn", "error", "crit"])
|
||||
.default_value("trace"),
|
||||
)
|
||||
/*
|
||||
* The "testnet" sub-command.
|
||||
*
|
||||
* Used for starting testnet validator clients.
|
||||
*/
|
||||
.subcommand(SubCommand::with_name("testnet")
|
||||
.about("Starts a testnet validator using INSECURE, predicatable private keys, based off the canonical \
|
||||
validator index. ONLY USE FOR TESTING PURPOSES!")
|
||||
.arg(
|
||||
Arg::with_name("bootstrap")
|
||||
.short("b")
|
||||
.long("bootstrap")
|
||||
.help("Connect to the RPC server to download the eth2_config via the HTTP API.")
|
||||
)
|
||||
.subcommand(SubCommand::with_name("insecure")
|
||||
.about("Uses the standard, predicatable `interop` keygen method to produce a range \
|
||||
of predicatable private keys and starts performing their validator duties.")
|
||||
.arg(Arg::with_name("first_validator")
|
||||
.value_name("VALIDATOR_INDEX")
|
||||
.required(true)
|
||||
.help("The first validator public key to be generated for this client."))
|
||||
.arg(Arg::with_name("validator_count")
|
||||
.value_name("COUNT")
|
||||
.required(true)
|
||||
.help("The number of validators."))
|
||||
)
|
||||
.subcommand(SubCommand::with_name("interop-yaml")
|
||||
.about("Loads plain-text secret keys from YAML files. Expects the interop format defined
|
||||
in the ethereum/eth2.0-pm repo.")
|
||||
.arg(Arg::with_name("path")
|
||||
.value_name("PATH")
|
||||
.required(true)
|
||||
.help("Path to a YAML file."))
|
||||
)
|
||||
)
|
||||
.subcommand(SubCommand::with_name("sign_block")
|
||||
.about("Connects to the beacon server, requests a new block (after providing reveal),\
|
||||
and prints the signed block to standard out")
|
||||
.arg(Arg::with_name("validator")
|
||||
.value_name("VALIDATOR")
|
||||
.required(true)
|
||||
.help("The pubkey of the validator that should sign the block.")
|
||||
)
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
let drain = match matches.value_of("debug-level") {
|
||||
Some("info") => drain.filter_level(Level::Info),
|
||||
Some("debug") => drain.filter_level(Level::Debug),
|
||||
Some("trace") => drain.filter_level(Level::Trace),
|
||||
Some("warn") => drain.filter_level(Level::Warning),
|
||||
Some("error") => drain.filter_level(Level::Error),
|
||||
Some("crit") => drain.filter_level(Level::Critical),
|
||||
_ => unreachable!("guarded by clap"),
|
||||
};
|
||||
|
||||
let mut log = slog::Logger::root(drain.fuse(), o!());
|
||||
|
||||
if std::mem::size_of::<usize>() != 8 {
|
||||
crit!(
|
||||
log,
|
||||
"Lighthouse only supports 64bit CPUs";
|
||||
"detected" => format!("{}bit", std::mem::size_of::<usize>() * 8)
|
||||
);
|
||||
}
|
||||
|
||||
let (client_config, eth2_config) = match get_configs(&matches, &mut log) {
|
||||
Ok(tuple) => tuple,
|
||||
Err(e) => {
|
||||
crit!(
|
||||
log,
|
||||
"Unable to initialize configuration";
|
||||
"error" => e
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
info!(
|
||||
log,
|
||||
"Starting validator client";
|
||||
"datadir" => client_config.full_data_dir().expect("Unable to find datadir").to_str(),
|
||||
);
|
||||
|
||||
let result = match eth2_config.spec_constants.as_str() {
|
||||
"mainnet" => ValidatorService::<ValidatorServiceClient, Keypair, MainnetEthSpec>::start(
|
||||
client_config,
|
||||
eth2_config,
|
||||
log.clone(),
|
||||
),
|
||||
"minimal" => ValidatorService::<ValidatorServiceClient, Keypair, MinimalEthSpec>::start(
|
||||
client_config,
|
||||
eth2_config,
|
||||
log.clone(),
|
||||
),
|
||||
"interop" => ValidatorService::<ValidatorServiceClient, Keypair, InteropEthSpec>::start(
|
||||
client_config,
|
||||
eth2_config,
|
||||
log.clone(),
|
||||
),
|
||||
other => {
|
||||
crit!(log, "Unknown spec constants"; "title" => other);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// start the validator service.
|
||||
// this specifies the GRPC and signer type to use as the duty manager beacon node.
|
||||
match result {
|
||||
Ok(_) => info!(log, "Validator client shutdown successfully."),
|
||||
Err(e) => crit!(log, "Validator client exited with error"; "error" => e.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses the CLI arguments and attempts to load the client and eth2 configuration.
|
||||
///
|
||||
/// This is not a pure function, it reads from disk and may contact network servers.
|
||||
pub fn get_configs(
|
||||
cli_args: &ArgMatches,
|
||||
mut log: &mut Logger,
|
||||
) -> Result<(ClientConfig, Eth2Config)> {
|
||||
let mut client_config = ClientConfig::default();
|
||||
|
||||
client_config.apply_cli_args(&cli_args, &mut log)?;
|
||||
|
||||
if let Some(server) = cli_args.value_of("server") {
|
||||
client_config.server = server.to_string();
|
||||
}
|
||||
|
||||
if let Some(port) = cli_args.value_of("server-http-port") {
|
||||
client_config.server_http_port = port
|
||||
.parse::<u16>()
|
||||
.map_err(|e| format!("Unable to parse HTTP port: {:?}", e))?;
|
||||
}
|
||||
|
||||
if let Some(port) = cli_args.value_of("server-grpc-port") {
|
||||
client_config.server_grpc_port = port
|
||||
.parse::<u16>()
|
||||
.map_err(|e| format!("Unable to parse gRPC port: {:?}", e))?;
|
||||
}
|
||||
|
||||
info!(
|
||||
*log,
|
||||
"Beacon node connection info";
|
||||
"grpc_port" => client_config.server_grpc_port,
|
||||
"http_port" => client_config.server_http_port,
|
||||
"server" => &client_config.server,
|
||||
);
|
||||
|
||||
let (client_config, eth2_config) = match cli_args.subcommand() {
|
||||
("testnet", Some(sub_cli_args)) => {
|
||||
if cli_args.is_present("eth2-config") && sub_cli_args.is_present("bootstrap") {
|
||||
return Err(
|
||||
"Cannot specify --eth2-config and --bootstrap as it may result \
|
||||
in ambiguity."
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
process_testnet_subcommand(sub_cli_args, client_config, log)
|
||||
}
|
||||
_ => return Err("You must use the testnet command. See '--help'.".into()),
|
||||
}?;
|
||||
|
||||
Ok((client_config, eth2_config))
|
||||
}
|
||||
|
||||
/// Parses the `testnet` CLI subcommand.
|
||||
///
|
||||
/// This is not a pure function, it reads from disk and may contact network servers.
|
||||
fn process_testnet_subcommand(
|
||||
cli_args: &ArgMatches,
|
||||
mut client_config: ClientConfig,
|
||||
log: &Logger,
|
||||
) -> Result<(ClientConfig, Eth2Config)> {
|
||||
let eth2_config = if cli_args.is_present("bootstrap") {
|
||||
info!(log, "Connecting to bootstrap server");
|
||||
let bootstrapper = Bootstrapper::connect(
|
||||
format!(
|
||||
"http://{}:{}",
|
||||
client_config.server, client_config.server_http_port
|
||||
),
|
||||
&log,
|
||||
)?;
|
||||
|
||||
let eth2_config = bootstrapper.eth2_config()?;
|
||||
|
||||
info!(
|
||||
log,
|
||||
"Bootstrapped eth2 config via HTTP";
|
||||
"slot_time_millis" => eth2_config.spec.milliseconds_per_slot,
|
||||
"spec" => ð2_config.spec_constants,
|
||||
);
|
||||
|
||||
eth2_config
|
||||
} else {
|
||||
match cli_args.value_of("spec") {
|
||||
Some("mainnet") => Eth2Config::mainnet(),
|
||||
Some("minimal") => Eth2Config::minimal(),
|
||||
Some("interop") => Eth2Config::interop(),
|
||||
_ => return Err("No --spec flag provided. See '--help'.".into()),
|
||||
}
|
||||
};
|
||||
|
||||
client_config.key_source = match cli_args.subcommand() {
|
||||
("insecure", Some(sub_cli_args)) => {
|
||||
let first = sub_cli_args
|
||||
.value_of("first_validator")
|
||||
.ok_or_else(|| "No first validator supplied")?
|
||||
.parse::<usize>()
|
||||
.map_err(|e| format!("Unable to parse first validator: {:?}", e))?;
|
||||
let count = sub_cli_args
|
||||
.value_of("validator_count")
|
||||
.ok_or_else(|| "No validator count supplied")?
|
||||
.parse::<usize>()
|
||||
.map_err(|e| format!("Unable to parse validator count: {:?}", e))?;
|
||||
|
||||
info!(
|
||||
log,
|
||||
"Generating unsafe testing keys";
|
||||
"first_validator" => first,
|
||||
"count" => count
|
||||
);
|
||||
|
||||
KeySource::TestingKeypairRange(first..first + count)
|
||||
}
|
||||
("interop-yaml", Some(sub_cli_args)) => {
|
||||
let path = sub_cli_args
|
||||
.value_of("path")
|
||||
.ok_or_else(|| "No yaml path supplied")?
|
||||
.parse::<PathBuf>()
|
||||
.map_err(|e| format!("Unable to parse yaml path: {:?}", e))?;
|
||||
|
||||
info!(
|
||||
log,
|
||||
"Loading keypairs from interop YAML format";
|
||||
"path" => format!("{:?}", path),
|
||||
);
|
||||
|
||||
KeySource::YamlKeypairs(path)
|
||||
}
|
||||
_ => KeySource::Disk,
|
||||
};
|
||||
|
||||
Ok((client_config, eth2_config))
|
||||
}
|
||||
@@ -17,6 +17,7 @@ use crate::signer::Signer;
|
||||
use bls::Keypair;
|
||||
use eth2_config::Eth2Config;
|
||||
use grpcio::{ChannelBuilder, EnvBuilder};
|
||||
use parking_lot::RwLock;
|
||||
use protos::services::Empty;
|
||||
use protos::services_grpc::{
|
||||
AttestationServiceClient, BeaconBlockServiceClient, BeaconNodeServiceClient,
|
||||
@@ -26,18 +27,9 @@ use slog::{crit, error, info, trace, warn};
|
||||
use slot_clock::{SlotClock, SystemTimeSlotClock};
|
||||
use std::marker::PhantomData;
|
||||
use std::sync::Arc;
|
||||
use std::sync::RwLock;
|
||||
use std::time::{Duration, Instant};
|
||||
use tokio::prelude::*;
|
||||
use tokio::runtime::Builder;
|
||||
use tokio::timer::Interval;
|
||||
use tokio_timer::clock::Clock;
|
||||
use std::time::Duration;
|
||||
use types::{ChainSpec, Epoch, EthSpec, Fork, Slot};
|
||||
|
||||
/// A fixed amount of time after a slot to perform operations. This gives the node time to complete
|
||||
/// per-slot processes.
|
||||
const TIME_DELAY_FROM_SLOT: Duration = Duration::from_millis(100);
|
||||
|
||||
/// The validator service. This is the main thread that executes and maintains validator
|
||||
/// duties.
|
||||
//TODO: Generalize the BeaconNode types to use testing
|
||||
@@ -45,12 +37,12 @@ pub struct Service<B: BeaconNodeDuties + 'static, S: Signer + 'static, E: EthSpe
|
||||
/// The node's current fork version we are processing on.
|
||||
fork: Fork,
|
||||
/// The slot clock for this service.
|
||||
slot_clock: SystemTimeSlotClock,
|
||||
pub slot_clock: SystemTimeSlotClock,
|
||||
/// The slot that is currently, or was previously processed by the service.
|
||||
current_slot: Option<Slot>,
|
||||
current_slot: RwLock<Option<Slot>>,
|
||||
slots_per_epoch: u64,
|
||||
/// The chain specification for this clients instance.
|
||||
spec: Arc<ChainSpec>,
|
||||
pub spec: Arc<ChainSpec>,
|
||||
/// The duties manager which maintains the state of when to perform actions.
|
||||
duties_manager: Arc<DutiesManager<B, S>>,
|
||||
// GRPC Clients
|
||||
@@ -63,12 +55,12 @@ pub struct Service<B: BeaconNodeDuties + 'static, S: Signer + 'static, E: EthSpe
|
||||
_phantom: PhantomData<E>,
|
||||
}
|
||||
|
||||
impl<B: BeaconNodeDuties + 'static, S: Signer + 'static, E: EthSpec> Service<B, S, E> {
|
||||
impl<E: EthSpec> Service<ValidatorServiceClient, Keypair, E> {
|
||||
/// Initial connection to the beacon node to determine its properties.
|
||||
///
|
||||
/// This tries to connect to a beacon node. Once connected, it initialised the gRPC clients
|
||||
/// and returns an instance of the service.
|
||||
fn initialize_service(
|
||||
pub fn initialize_service(
|
||||
client_config: ValidatorConfig,
|
||||
eth2_config: Eth2Config,
|
||||
log: slog::Logger,
|
||||
@@ -195,7 +187,7 @@ impl<B: BeaconNodeDuties + 'static, S: Signer + 'static, E: EthSpec> Service<B,
|
||||
Ok(Service {
|
||||
fork,
|
||||
slot_clock,
|
||||
current_slot: None,
|
||||
current_slot: RwLock::new(None),
|
||||
slots_per_epoch,
|
||||
spec,
|
||||
duties_manager,
|
||||
@@ -205,78 +197,12 @@ impl<B: BeaconNodeDuties + 'static, S: Signer + 'static, E: EthSpec> Service<B,
|
||||
_phantom: PhantomData,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Initialise the service then run the core thread.
|
||||
// TODO: Improve handling of generic BeaconNode types, to stub grpcClient
|
||||
pub fn start(
|
||||
client_config: ValidatorConfig,
|
||||
eth2_config: Eth2Config,
|
||||
log: slog::Logger,
|
||||
) -> error_chain::Result<()> {
|
||||
// connect to the node and retrieve its properties and initialize the gRPC clients
|
||||
let mut service = Service::<ValidatorServiceClient, Keypair, E>::initialize_service(
|
||||
client_config,
|
||||
eth2_config,
|
||||
log.clone(),
|
||||
)?;
|
||||
|
||||
// we have connected to a node and established its parameters. Spin up the core service
|
||||
|
||||
// set up the validator service runtime
|
||||
let mut runtime = Builder::new()
|
||||
.clock(Clock::system())
|
||||
.name_prefix("validator-client-")
|
||||
.build()
|
||||
.map_err(|e| format!("Tokio runtime failed: {}", e))?;
|
||||
|
||||
let duration_to_next_slot = service
|
||||
.slot_clock
|
||||
.duration_to_next_slot()
|
||||
.ok_or_else::<error_chain::Error, _>(|| {
|
||||
"Unable to determine duration to next slot. Exiting.".into()
|
||||
})?;
|
||||
|
||||
// set up the validator work interval - start at next slot and proceed every slot
|
||||
let interval = {
|
||||
// Set the interval to start at the next slot, and every slot after
|
||||
let slot_duration = Duration::from_millis(service.spec.milliseconds_per_slot);
|
||||
//TODO: Handle checked add correctly
|
||||
Interval::new(Instant::now() + duration_to_next_slot, slot_duration)
|
||||
};
|
||||
|
||||
if service.slot_clock.now().is_none() {
|
||||
warn!(
|
||||
log,
|
||||
"Starting node prior to genesis";
|
||||
);
|
||||
}
|
||||
|
||||
info!(
|
||||
log,
|
||||
"Waiting for next slot";
|
||||
"seconds_to_wait" => duration_to_next_slot.as_secs()
|
||||
);
|
||||
|
||||
/* kick off the core service */
|
||||
runtime.block_on(
|
||||
interval
|
||||
.for_each(move |_| {
|
||||
// wait for node to process
|
||||
std::thread::sleep(TIME_DELAY_FROM_SLOT);
|
||||
// if a non-fatal error occurs, proceed to the next slot.
|
||||
let _ignore_error = service.per_slot_execution();
|
||||
// completed a slot process
|
||||
Ok(())
|
||||
})
|
||||
.map_err(|e| format!("Service thread failed: {:?}", e)),
|
||||
)?;
|
||||
// validator client exited
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl<B: BeaconNodeDuties + 'static, S: Signer + 'static, E: EthSpec> Service<B, S, E> {
|
||||
/// The execution logic that runs every slot.
|
||||
// Errors are logged to output, and core execution continues unless fatal errors occur.
|
||||
fn per_slot_execution(&mut self) -> error_chain::Result<()> {
|
||||
pub fn per_slot_execution(&self) -> error_chain::Result<()> {
|
||||
/* get the new current slot and epoch */
|
||||
self.update_current_slot()?;
|
||||
|
||||
@@ -295,7 +221,7 @@ impl<B: BeaconNodeDuties + 'static, S: Signer + 'static, E: EthSpec> Service<B,
|
||||
}
|
||||
|
||||
/// Updates the known current slot and epoch.
|
||||
fn update_current_slot(&mut self) -> error_chain::Result<()> {
|
||||
fn update_current_slot(&self) -> error_chain::Result<()> {
|
||||
let wall_clock_slot = self
|
||||
.slot_clock
|
||||
.now()
|
||||
@@ -304,11 +230,12 @@ impl<B: BeaconNodeDuties + 'static, S: Signer + 'static, E: EthSpec> Service<B,
|
||||
})?;
|
||||
|
||||
let wall_clock_epoch = wall_clock_slot.epoch(self.slots_per_epoch);
|
||||
let mut current_slot = self.current_slot.write();
|
||||
|
||||
// this is a non-fatal error. If the slot clock repeats, the node could
|
||||
// have been slow to process the previous slot and is now duplicating tasks.
|
||||
// We ignore duplicated but raise a critical error.
|
||||
if let Some(current_slot) = self.current_slot {
|
||||
if let Some(current_slot) = *current_slot {
|
||||
if wall_clock_slot <= current_slot {
|
||||
crit!(
|
||||
self.log,
|
||||
@@ -317,17 +244,18 @@ impl<B: BeaconNodeDuties + 'static, S: Signer + 'static, E: EthSpec> Service<B,
|
||||
return Err("Duplicate slot".into());
|
||||
}
|
||||
}
|
||||
self.current_slot = Some(wall_clock_slot);
|
||||
*current_slot = Some(wall_clock_slot);
|
||||
info!(self.log, "Processing"; "slot" => wall_clock_slot.as_u64(), "epoch" => wall_clock_epoch.as_u64());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// For all known validator keypairs, update any known duties from the beacon node.
|
||||
fn check_for_duties(&mut self) {
|
||||
fn check_for_duties(&self) {
|
||||
let cloned_manager = self.duties_manager.clone();
|
||||
let cloned_log = self.log.clone();
|
||||
let current_epoch = self
|
||||
.current_slot
|
||||
.read()
|
||||
.expect("The current slot must be updated before checking for duties")
|
||||
.epoch(self.slots_per_epoch);
|
||||
|
||||
@@ -349,9 +277,10 @@ impl<B: BeaconNodeDuties + 'static, S: Signer + 'static, E: EthSpec> Service<B,
|
||||
}
|
||||
|
||||
/// If there are any duties to process, spawn a separate thread and perform required actions.
|
||||
fn process_duties(&mut self) {
|
||||
fn process_duties(&self) {
|
||||
if let Some(work) = self.duties_manager.get_current_work(
|
||||
self.current_slot
|
||||
.read()
|
||||
.expect("The current slot must be updated before processing duties"),
|
||||
) {
|
||||
trace!(
|
||||
@@ -368,6 +297,7 @@ impl<B: BeaconNodeDuties + 'static, S: Signer + 'static, E: EthSpec> Service<B,
|
||||
let fork = self.fork.clone();
|
||||
let slot = self
|
||||
.current_slot
|
||||
.read()
|
||||
.expect("The current slot must be updated before processing duties");
|
||||
let spec = self.spec.clone();
|
||||
let beacon_node = self.beacon_block_client.clone();
|
||||
@@ -399,6 +329,7 @@ impl<B: BeaconNodeDuties + 'static, S: Signer + 'static, E: EthSpec> Service<B,
|
||||
// spawns a thread to produce and sign an attestation
|
||||
let slot = self
|
||||
.current_slot
|
||||
.read()
|
||||
.expect("The current slot must be updated before processing duties");
|
||||
let signers = self.duties_manager.signers.clone(); // this is an arc
|
||||
let fork = self.fork.clone();
|
||||
|
||||
Reference in New Issue
Block a user