From 48733917be2a59ba87b01a0bc4678347ebb96f4f Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sun, 11 Aug 2019 12:12:19 +1000 Subject: [PATCH 01/62] Begin metrics refactor --- beacon_node/beacon_chain/Cargo.toml | 2 +- beacon_node/beacon_chain/src/beacon_chain.rs | 7 +++++++ beacon_node/beacon_chain/src/lib.rs | 6 ++++++ beacon_node/beacon_chain/src/metrics.rs | 12 ++++++++++++ beacon_node/rest_api/Cargo.toml | 1 + beacon_node/rest_api/src/lib.rs | 2 ++ beacon_node/rest_api/src/metrics.rs | 17 +++++++++++++++++ 7 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 beacon_node/rest_api/src/metrics.rs diff --git a/beacon_node/beacon_chain/Cargo.toml b/beacon_node/beacon_chain/Cargo.toml index 778224a3d4..43e7614b6a 100644 --- a/beacon_node/beacon_chain/Cargo.toml +++ b/beacon_node/beacon_chain/Cargo.toml @@ -17,6 +17,7 @@ sloggers = { version = "^0.3" } slot_clock = { path = "../../eth2/utils/slot_clock" } eth2_ssz = "0.1" eth2_ssz_derive = "0.1" +lazy_static = "1.3.0" state_processing = { path = "../../eth2/state_processing" } tree_hash = "0.1" types = { path = "../../eth2/types" } @@ -24,4 +25,3 @@ lmd_ghost = { path = "../../eth2/lmd_ghost" } [dev-dependencies] rand = "0.5.5" -lazy_static = "1.3.0" diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index 9ccf595893..e31844d582 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -2,6 +2,7 @@ use crate::checkpoint::CheckPoint; use crate::errors::{BeaconChainError as Error, BlockProductionError}; use crate::fork_choice::{Error as ForkChoiceError, ForkChoice}; use crate::iter::{ReverseBlockRootIterator, ReverseStateRootIterator}; +use crate::metrics; use crate::metrics::Metrics; use crate::persisted_beacon_chain::{PersistedBeaconChain, BEACON_CHAIN_DB_KEY}; use lmd_ghost::LmdGhost; @@ -848,6 +849,10 @@ impl BeaconChain { return Ok(BlockProcessingOutcome::BlockIsAlreadyKnown); } + // Records the time taken to load the block and state from the database during block + // processing. + let db_read_timer = metrics::BLOCK_PROCESSING_DB_READ.start_timer(); + // Load the blocks parent block from the database, returning invalid if that block is not // found. let parent_block: BeaconBlock = match self.store.get(&block.parent_root)? { @@ -867,6 +872,8 @@ impl BeaconChain { .get(&parent_state_root)? .ok_or_else(|| Error::DBInconsistent(format!("Missing state {}", parent_state_root)))?; + db_read_timer.observe_duration(); + // Transition the parent state to the block slot. let mut state: BeaconState = parent_state; for _ in state.slot.as_u64()..block.slot.as_u64() { diff --git a/beacon_node/beacon_chain/src/lib.rs b/beacon_node/beacon_chain/src/lib.rs index 3188760a42..e24534a2eb 100644 --- a/beacon_node/beacon_chain/src/lib.rs +++ b/beacon_node/beacon_chain/src/lib.rs @@ -1,3 +1,8 @@ +#[macro_use] +extern crate prometheus; +#[macro_use] +extern crate lazy_static; + mod beacon_chain; mod checkpoint; mod errors; @@ -13,6 +18,7 @@ pub use self::beacon_chain::{ pub use self::checkpoint::CheckPoint; pub use self::errors::{BeaconChainError, BlockProductionError}; pub use lmd_ghost; +pub use metrics::gather_metrics; pub use parking_lot; pub use slot_clock; pub use state_processing::per_block_processing::errors::{ diff --git a/beacon_node/beacon_chain/src/metrics.rs b/beacon_node/beacon_chain/src/metrics.rs index fa1718ebfb..fcb564e329 100644 --- a/beacon_node/beacon_chain/src/metrics.rs +++ b/beacon_node/beacon_chain/src/metrics.rs @@ -1,6 +1,18 @@ pub use prometheus::Error; use prometheus::{Histogram, HistogramOpts, IntCounter, Opts, Registry}; +lazy_static! { + pub static ref BLOCK_PROCESSING_DB_READ: Histogram = register_histogram!( + "block_processing_db_read_times", + "Time spent loading block and state from DB" + ) + .unwrap(); +} + +pub fn gather_metrics() -> Vec { + prometheus::gather() +} + pub struct Metrics { pub block_processing_requests: IntCounter, pub block_processing_successes: IntCounter, diff --git a/beacon_node/rest_api/Cargo.toml b/beacon_node/rest_api/Cargo.toml index fb6cb84134..821d6c0ea1 100644 --- a/beacon_node/rest_api/Cargo.toml +++ b/beacon_node/rest_api/Cargo.toml @@ -18,6 +18,7 @@ state_processing = { path = "../../eth2/state_processing" } types = { path = "../../eth2/types" } clap = "2.32.0" http = "^0.1.17" +prometheus = { version = "^0.6", features = ["process"] } hyper = "0.12.32" futures = "0.1" exit-future = "0.1.3" diff --git a/beacon_node/rest_api/src/lib.rs b/beacon_node/rest_api/src/lib.rs index a94a8cdf4a..7dc0df578d 100644 --- a/beacon_node/rest_api/src/lib.rs +++ b/beacon_node/rest_api/src/lib.rs @@ -3,6 +3,7 @@ extern crate hyper; mod beacon; mod config; mod helpers; +mod metrics; mod node; mod url_query; @@ -103,6 +104,7 @@ pub fn start_server( let result = match (req.method(), path.as_ref()) { (&Method::GET, "/beacon/state") => beacon::get_state::(req), (&Method::GET, "/beacon/state_root") => beacon::get_state_root::(req), + (&Method::GET, "/metrics") => metrics::get_prometheus(req), (&Method::GET, "/node/version") => node::get_version(req), (&Method::GET, "/node/genesis_time") => node::get_genesis_time::(req), _ => Err(ApiError::MethodNotAllowed(path.clone())), diff --git a/beacon_node/rest_api/src/metrics.rs b/beacon_node/rest_api/src/metrics.rs new file mode 100644 index 0000000000..1ecdf8b686 --- /dev/null +++ b/beacon_node/rest_api/src/metrics.rs @@ -0,0 +1,17 @@ +use crate::{success_response, ApiError, ApiResult}; +use hyper::{Body, Request}; +use prometheus::{Encoder, TextEncoder}; + +/// Returns the full set of Prometheus metrics for the Beacon Node application. +pub fn get_prometheus(_req: Request) -> ApiResult { + let mut buffer = vec![]; + let encoder = TextEncoder::new(); + + encoder + .encode(&beacon_chain::gather_metrics(), &mut buffer) + .unwrap(); + + String::from_utf8(buffer) + .map(|string| success_response(Body::from(string))) + .map_err(|e| ApiError::ServerError(format!("Failed to encode prometheus info: {:?}", e))) +} From 9995b390b5077ec8c8f92e3fe741590357bad05d Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sun, 11 Aug 2019 14:11:13 +1000 Subject: [PATCH 02/62] Move beacon_chain to new metrics structure. --- beacon_node/beacon_chain/src/beacon_chain.rs | 41 ++-- beacon_node/beacon_chain/src/metrics.rs | 242 ++++++++----------- beacon_node/client/src/lib.rs | 5 - 3 files changed, 117 insertions(+), 171 deletions(-) diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index e31844d582..df9523624a 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -3,7 +3,6 @@ use crate::errors::{BeaconChainError as Error, BlockProductionError}; use crate::fork_choice::{Error as ForkChoiceError, ForkChoice}; use crate::iter::{ReverseBlockRootIterator, ReverseStateRootIterator}; use crate::metrics; -use crate::metrics::Metrics; use crate::persisted_beacon_chain::{PersistedBeaconChain, BEACON_CHAIN_DB_KEY}; use lmd_ghost::LmdGhost; use log::trace; @@ -107,8 +106,6 @@ pub struct BeaconChain { /// A state-machine that is updated with information from the network and chooses a canonical /// head block. pub fork_choice: ForkChoice, - /// Stores metrics about this `BeaconChain`. - pub metrics: Metrics, /// Logging to CLI, etc. log: Logger, } @@ -158,7 +155,6 @@ impl BeaconChain { canonical_head, genesis_block_root, fork_choice: ForkChoice::new(store.clone(), &genesis_block, genesis_block_root), - metrics: Metrics::new()?, store, log, }) @@ -196,7 +192,6 @@ impl BeaconChain { canonical_head: RwLock::new(p.canonical_head), state: RwLock::new(p.state), genesis_block_root: p.genesis_block_root, - metrics: Metrics::new()?, store, log, })) @@ -473,8 +468,8 @@ impl BeaconChain { state: &BeaconState, ) -> Result { // Collect some metrics. - self.metrics.attestation_production_requests.inc(); - let timer = self.metrics.attestation_production_times.start_timer(); + metrics::ATTESTATION_PRODUCTION_REQUESTS.inc(); + let timer = metrics::ATTESTATION_PRODUCTION_TIMES.start_timer(); let slots_per_epoch = T::EthSpec::slots_per_epoch(); let current_epoch_start_slot = state.current_epoch().start_slot(slots_per_epoch); @@ -521,7 +516,7 @@ impl BeaconChain { }; // Collect some metrics. - self.metrics.attestation_production_successes.inc(); + metrics::ATTESTATION_PRODUCTION_SUCCESSES.inc(); timer.observe_duration(); Ok(AttestationData { @@ -708,8 +703,8 @@ impl BeaconChain { state: &BeaconState, block: &BeaconBlock, ) -> Result { - self.metrics.attestation_processing_requests.inc(); - let timer = self.metrics.attestation_processing_times.start_timer(); + metrics::ATTESTATION_PROCESSING_REQUESTS.inc(); + let timer = metrics::ATTESTATION_PROCESSING_TIMES.start_timer(); // Find the highest between: // @@ -754,7 +749,7 @@ impl BeaconChain { .insert_attestation(attestation, state, &self.spec)?; // Update the metrics. - self.metrics.attestation_processing_successes.inc(); + metrics::ATTESTATION_PROCESSING_SUCCESSES.inc(); Ok(AttestationProcessingOutcome::Processed) }; @@ -810,8 +805,8 @@ impl BeaconChain { &self, block: BeaconBlock, ) -> Result { - self.metrics.block_processing_requests.inc(); - let timer = self.metrics.block_processing_times.start_timer(); + metrics::BLOCK_PROCESSING_REQUESTS.inc(); + let timer = metrics::BLOCK_PROCESSING_TIMES.start_timer(); let finalized_slot = self .state @@ -926,10 +921,8 @@ impl BeaconChain { ) }; - self.metrics.block_processing_successes.inc(); - self.metrics - .operations_per_block_attestation - .observe(block.body.attestations.len() as f64); + metrics::BLOCK_PROCESSING_SUCCESSES.inc(); + metrics::OPERATIONS_PER_BLOCK_ATTESTATION.observe(block.body.attestations.len() as f64); timer.observe_duration(); Ok(BlockProcessingOutcome::Processed { block_root }) @@ -965,8 +958,8 @@ impl BeaconChain { produce_at_slot: Slot, randao_reveal: Signature, ) -> Result<(BeaconBlock, BeaconState), BlockProductionError> { - self.metrics.block_production_requests.inc(); - let timer = self.metrics.block_production_times.start_timer(); + metrics::BLOCK_PRODUCTION_REQUESTS.inc(); + let timer = metrics::BLOCK_PRODUCTION_TIMES.start_timer(); // If required, transition the new state to the present slot. while state.slot < produce_at_slot { @@ -1018,7 +1011,7 @@ impl BeaconChain { block.state_root = state_root; - self.metrics.block_production_successes.inc(); + metrics::BLOCK_PRODUCTION_SUCCESSES.inc(); timer.observe_duration(); Ok((block, state)) @@ -1026,10 +1019,10 @@ impl BeaconChain { /// Execute the fork choice algorithm and enthrone the result as the canonical head. pub fn fork_choice(&self) -> Result<(), Error> { - self.metrics.fork_choice_requests.inc(); + metrics::FORK_CHOICE_REQUESTS.inc(); // Start fork choice metrics timer. - let timer = self.metrics.fork_choice_times.start_timer(); + let timer = metrics::FORK_CHOICE_TIMES.start_timer(); // Determine the root of the block that is the head of the chain. let beacon_block_root = self.fork_choice.find_head(&self)?; @@ -1039,7 +1032,7 @@ impl BeaconChain { // If a new head was chosen. if beacon_block_root != self.head().beacon_block_root { - self.metrics.fork_choice_changed_head.inc(); + metrics::FORK_CHOICE_CHANGED_HEAD.inc(); let beacon_block: BeaconBlock = self .store @@ -1057,7 +1050,7 @@ impl BeaconChain { // If we switched to a new chain (instead of building atop the present chain). if self.head().beacon_block_root != beacon_block.parent_root { - self.metrics.fork_choice_reorg_count.inc(); + metrics::FORK_CHOICE_REORG_COUNT.inc(); warn!( self.log, "Beacon chain re-org"; diff --git a/beacon_node/beacon_chain/src/metrics.rs b/beacon_node/beacon_chain/src/metrics.rs index fcb564e329..8b8307e93b 100644 --- a/beacon_node/beacon_chain/src/metrics.rs +++ b/beacon_node/beacon_chain/src/metrics.rs @@ -1,155 +1,113 @@ pub use prometheus::Error; -use prometheus::{Histogram, HistogramOpts, IntCounter, Opts, Registry}; +use prometheus::{Histogram, IntCounter}; lazy_static! { + /* + * Block Processing + */ pub static ref BLOCK_PROCESSING_DB_READ: Histogram = register_histogram!( "block_processing_db_read_times", "Time spent loading block and state from DB" ) .unwrap(); + pub static ref BLOCK_PROCESSING_REQUESTS: IntCounter = register_int_counter!( + "block_processing_requests", + "Count of blocks sumbitted for processing" + ) + .unwrap(); + pub static ref BLOCK_PROCESSING_SUCCESSES: IntCounter = register_int_counter!( + "block_processing_successes", + "Count of blocks processed without error" + ) + .unwrap(); + pub static ref BLOCK_PROCESSING_TIMES: Histogram = + register_histogram!("block_processing_times", "Full runtime of block processing") + .unwrap(); + + /* + * Block Production + */ + pub static ref BLOCK_PRODUCTION_REQUESTS: IntCounter = register_int_counter!( + "block_production_requests", + "Count of all block production requests" + ) + .unwrap(); + pub static ref BLOCK_PRODUCTION_SUCCESSES: IntCounter = register_int_counter!( + "block_production_successes", + "Count of blocks sucessfully produced." + ) + .unwrap(); + pub static ref BLOCK_PRODUCTION_TIMES: Histogram = + register_histogram!("block_production_times", "Full runtime of block production").unwrap(); + + /* + * Block Statistics + */ + pub static ref OPERATIONS_PER_BLOCK_ATTESTATION: Histogram = register_histogram!( + "operations_per_block_attestation", + "Number of attestations in a block" + ) + .unwrap(); + + /* + * Attestation Processing + */ + pub static ref ATTESTATION_PROCESSING_REQUESTS: IntCounter = register_int_counter!( + "attestation_processing_requests", + "Count of all attestations submitted for processing" + ) + .unwrap(); + pub static ref ATTESTATION_PROCESSING_SUCCESSES: IntCounter = register_int_counter!( + "attestation_processing_successes", + "total_attestation_processing_successes" + ) + .unwrap(); + pub static ref ATTESTATION_PROCESSING_TIMES: Histogram = register_histogram!( + "attestation_processing_times", + "Full runtime of attestation processing" + ) + .unwrap(); + + /* + * Attestation Production + */ + pub static ref ATTESTATION_PRODUCTION_REQUESTS: IntCounter = register_int_counter!( + "attestation_production_requests", + "Count of all attestation production requests" + ) + .unwrap(); + pub static ref ATTESTATION_PRODUCTION_SUCCESSES: IntCounter = register_int_counter!( + "attestation_production_successes", + "Count of attestations processed without error" + ) + .unwrap(); + pub static ref ATTESTATION_PRODUCTION_TIMES: Histogram = register_histogram!( + "attestation_production_times", + "Full runtime of attestation production" + ).unwrap(); + + /* + * Fork Choice + */ + pub static ref FORK_CHOICE_REQUESTS: IntCounter = register_int_counter!( + "fork_choice_requests", + "Count of occasions where fork choice has tried to find a head" + ) + .unwrap(); + pub static ref FORK_CHOICE_CHANGED_HEAD: IntCounter = register_int_counter!( + "fork_choice_changed_head", + "Count of occasions fork choice has found a new head" + ) + .unwrap(); + pub static ref FORK_CHOICE_REORG_COUNT: IntCounter = register_int_counter!( + "fork_choice_reorg_count", + "Count of occasions fork choice has switched to a different chain" + ) + .unwrap(); + pub static ref FORK_CHOICE_TIMES: Histogram = + register_histogram!("fork_choice_time", "Full runtime of fork choice").unwrap(); } pub fn gather_metrics() -> Vec { prometheus::gather() } - -pub struct Metrics { - pub block_processing_requests: IntCounter, - pub block_processing_successes: IntCounter, - pub block_processing_times: Histogram, - pub block_production_requests: IntCounter, - pub block_production_successes: IntCounter, - pub block_production_times: Histogram, - pub attestation_production_requests: IntCounter, - pub attestation_production_successes: IntCounter, - pub attestation_production_times: Histogram, - pub attestation_processing_requests: IntCounter, - pub attestation_processing_successes: IntCounter, - pub attestation_processing_times: Histogram, - pub fork_choice_requests: IntCounter, - pub fork_choice_changed_head: IntCounter, - pub fork_choice_reorg_count: IntCounter, - pub fork_choice_times: Histogram, - pub operations_per_block_attestation: Histogram, -} - -impl Metrics { - pub fn new() -> Result { - Ok(Self { - block_processing_requests: { - let opts = Opts::new("block_processing_requests", "total_blocks_processed"); - IntCounter::with_opts(opts)? - }, - block_processing_successes: { - let opts = Opts::new("block_processing_successes", "total_valid_blocks_processed"); - IntCounter::with_opts(opts)? - }, - block_processing_times: { - let opts = HistogramOpts::new("block_processing_times", "block_processing_time"); - Histogram::with_opts(opts)? - }, - block_production_requests: { - let opts = Opts::new("block_production_requests", "attempts_to_produce_new_block"); - IntCounter::with_opts(opts)? - }, - block_production_successes: { - let opts = Opts::new("block_production_successes", "blocks_successfully_produced"); - IntCounter::with_opts(opts)? - }, - block_production_times: { - let opts = HistogramOpts::new("block_production_times", "block_production_time"); - Histogram::with_opts(opts)? - }, - attestation_production_requests: { - let opts = Opts::new( - "attestation_production_requests", - "total_attestation_production_requests", - ); - IntCounter::with_opts(opts)? - }, - attestation_production_successes: { - let opts = Opts::new( - "attestation_production_successes", - "total_attestation_production_successes", - ); - IntCounter::with_opts(opts)? - }, - attestation_production_times: { - let opts = HistogramOpts::new( - "attestation_production_times", - "attestation_production_time", - ); - Histogram::with_opts(opts)? - }, - attestation_processing_requests: { - let opts = Opts::new( - "attestation_processing_requests", - "total_attestation_processing_requests", - ); - IntCounter::with_opts(opts)? - }, - attestation_processing_successes: { - let opts = Opts::new( - "attestation_processing_successes", - "total_attestation_processing_successes", - ); - IntCounter::with_opts(opts)? - }, - attestation_processing_times: { - let opts = HistogramOpts::new( - "attestation_processing_times", - "attestation_processing_time", - ); - Histogram::with_opts(opts)? - }, - fork_choice_requests: { - let opts = Opts::new("fork_choice_requests", "total_times_fork_choice_called"); - IntCounter::with_opts(opts)? - }, - fork_choice_changed_head: { - let opts = Opts::new( - "fork_choice_changed_head", - "total_times_fork_choice_chose_a_new_head", - ); - IntCounter::with_opts(opts)? - }, - fork_choice_reorg_count: { - let opts = Opts::new("fork_choice_reorg_count", "number_of_reorgs"); - IntCounter::with_opts(opts)? - }, - fork_choice_times: { - let opts = HistogramOpts::new("fork_choice_time", "total_time_to_run_fork_choice"); - Histogram::with_opts(opts)? - }, - operations_per_block_attestation: { - let opts = HistogramOpts::new( - "operations_per_block_attestation", - "count_of_attestations_per_block", - ); - Histogram::with_opts(opts)? - }, - }) - } - - pub fn register(&self, registry: &Registry) -> Result<(), Error> { - registry.register(Box::new(self.block_processing_requests.clone()))?; - registry.register(Box::new(self.block_processing_successes.clone()))?; - registry.register(Box::new(self.block_processing_times.clone()))?; - registry.register(Box::new(self.block_production_requests.clone()))?; - registry.register(Box::new(self.block_production_successes.clone()))?; - registry.register(Box::new(self.block_production_times.clone()))?; - registry.register(Box::new(self.attestation_production_requests.clone()))?; - registry.register(Box::new(self.attestation_production_successes.clone()))?; - registry.register(Box::new(self.attestation_production_times.clone()))?; - registry.register(Box::new(self.attestation_processing_requests.clone()))?; - registry.register(Box::new(self.attestation_processing_successes.clone()))?; - registry.register(Box::new(self.attestation_processing_times.clone()))?; - registry.register(Box::new(self.fork_choice_requests.clone()))?; - registry.register(Box::new(self.fork_choice_changed_head.clone()))?; - registry.register(Box::new(self.fork_choice_reorg_count.clone()))?; - registry.register(Box::new(self.fork_choice_times.clone()))?; - registry.register(Box::new(self.operations_per_block_attestation.clone()))?; - - Ok(()) - } -} diff --git a/beacon_node/client/src/lib.rs b/beacon_node/client/src/lib.rs index 65ba071fa1..e06c5b60ee 100644 --- a/beacon_node/client/src/lib.rs +++ b/beacon_node/client/src/lib.rs @@ -71,11 +71,6 @@ where eth2_config.spec.clone(), log.clone(), )?); - // Registry all beacon chain metrics with the global registry. - beacon_chain - .metrics - .register(&metrics_registry) - .expect("Failed to registry metrics"); if beacon_chain.read_slot_clock().is_none() { panic!("Cannot start client before genesis!") From e33d0703efcff8d37936968d3b7d591b4ab07b2a Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sun, 11 Aug 2019 14:43:31 +1000 Subject: [PATCH 03/62] Make metrics not panic if already defined --- beacon_node/beacon_chain/src/beacon_chain.rs | 51 +++++---- beacon_node/beacon_chain/src/metrics.rs | 113 ++++++++++--------- 2 files changed, 88 insertions(+), 76 deletions(-) diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index df9523624a..b0bb6a1592 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -468,8 +468,8 @@ impl BeaconChain { state: &BeaconState, ) -> Result { // Collect some metrics. - metrics::ATTESTATION_PRODUCTION_REQUESTS.inc(); - let timer = metrics::ATTESTATION_PRODUCTION_TIMES.start_timer(); + metrics::inc_counter(&metrics::ATTESTATION_PRODUCTION_REQUESTS); + let timer = metrics::start_timer(&metrics::ATTESTATION_PRODUCTION_TIMES); let slots_per_epoch = T::EthSpec::slots_per_epoch(); let current_epoch_start_slot = state.current_epoch().start_slot(slots_per_epoch); @@ -516,8 +516,8 @@ impl BeaconChain { }; // Collect some metrics. - metrics::ATTESTATION_PRODUCTION_SUCCESSES.inc(); - timer.observe_duration(); + metrics::inc_counter(&metrics::ATTESTATION_PRODUCTION_SUCCESSES); + metrics::stop_timer(timer); Ok(AttestationData { beacon_block_root: head_block_root, @@ -703,8 +703,8 @@ impl BeaconChain { state: &BeaconState, block: &BeaconBlock, ) -> Result { - metrics::ATTESTATION_PROCESSING_REQUESTS.inc(); - let timer = metrics::ATTESTATION_PROCESSING_TIMES.start_timer(); + metrics::inc_counter(&metrics::ATTESTATION_PROCESSING_REQUESTS); + let timer = metrics::start_timer(&metrics::ATTESTATION_PROCESSING_TIMES); // Find the highest between: // @@ -749,12 +749,12 @@ impl BeaconChain { .insert_attestation(attestation, state, &self.spec)?; // Update the metrics. - metrics::ATTESTATION_PROCESSING_SUCCESSES.inc(); + metrics::inc_counter(&metrics::ATTESTATION_PROCESSING_SUCCESSES); Ok(AttestationProcessingOutcome::Processed) }; - timer.observe_duration(); + timer.map(|t| t.observe_duration()); result } @@ -805,8 +805,8 @@ impl BeaconChain { &self, block: BeaconBlock, ) -> Result { - metrics::BLOCK_PROCESSING_REQUESTS.inc(); - let timer = metrics::BLOCK_PROCESSING_TIMES.start_timer(); + metrics::inc_counter(&metrics::BLOCK_PROCESSING_REQUESTS); + let timer = metrics::start_timer(&metrics::BLOCK_PROCESSING_TIMES); let finalized_slot = self .state @@ -846,7 +846,7 @@ impl BeaconChain { // Records the time taken to load the block and state from the database during block // processing. - let db_read_timer = metrics::BLOCK_PROCESSING_DB_READ.start_timer(); + let db_read_timer = metrics::start_timer(&metrics::BLOCK_PROCESSING_DB_READ); // Load the blocks parent block from the database, returning invalid if that block is not // found. @@ -867,7 +867,7 @@ impl BeaconChain { .get(&parent_state_root)? .ok_or_else(|| Error::DBInconsistent(format!("Missing state {}", parent_state_root)))?; - db_read_timer.observe_duration(); + metrics::stop_timer(db_read_timer); // Transition the parent state to the block slot. let mut state: BeaconState = parent_state; @@ -921,9 +921,12 @@ impl BeaconChain { ) }; - metrics::BLOCK_PROCESSING_SUCCESSES.inc(); - metrics::OPERATIONS_PER_BLOCK_ATTESTATION.observe(block.body.attestations.len() as f64); - timer.observe_duration(); + metrics::inc_counter(&metrics::BLOCK_PROCESSING_SUCCESSES); + metrics::observe( + &metrics::OPERATIONS_PER_BLOCK_ATTESTATION, + block.body.attestations.len() as f64, + ); + metrics::stop_timer(timer); Ok(BlockProcessingOutcome::Processed { block_root }) } @@ -958,8 +961,8 @@ impl BeaconChain { produce_at_slot: Slot, randao_reveal: Signature, ) -> Result<(BeaconBlock, BeaconState), BlockProductionError> { - metrics::BLOCK_PRODUCTION_REQUESTS.inc(); - let timer = metrics::BLOCK_PRODUCTION_TIMES.start_timer(); + metrics::inc_counter(&metrics::BLOCK_PRODUCTION_REQUESTS); + let timer = metrics::start_timer(&metrics::BLOCK_PRODUCTION_TIMES); // If required, transition the new state to the present slot. while state.slot < produce_at_slot { @@ -1011,28 +1014,28 @@ impl BeaconChain { block.state_root = state_root; - metrics::BLOCK_PRODUCTION_SUCCESSES.inc(); - timer.observe_duration(); + metrics::inc_counter(&metrics::BLOCK_PRODUCTION_SUCCESSES); + metrics::stop_timer(timer); Ok((block, state)) } /// Execute the fork choice algorithm and enthrone the result as the canonical head. pub fn fork_choice(&self) -> Result<(), Error> { - metrics::FORK_CHOICE_REQUESTS.inc(); + metrics::inc_counter(&metrics::FORK_CHOICE_REQUESTS); // Start fork choice metrics timer. - let timer = metrics::FORK_CHOICE_TIMES.start_timer(); + let timer = metrics::start_timer(&metrics::FORK_CHOICE_TIMES); // Determine the root of the block that is the head of the chain. let beacon_block_root = self.fork_choice.find_head(&self)?; // End fork choice metrics timer. - timer.observe_duration(); + metrics::stop_timer(timer); // If a new head was chosen. if beacon_block_root != self.head().beacon_block_root { - metrics::FORK_CHOICE_CHANGED_HEAD.inc(); + metrics::inc_counter(&metrics::FORK_CHOICE_CHANGED_HEAD); let beacon_block: BeaconBlock = self .store @@ -1050,7 +1053,7 @@ impl BeaconChain { // If we switched to a new chain (instead of building atop the present chain). if self.head().beacon_block_root != beacon_block.parent_root { - metrics::FORK_CHOICE_REORG_COUNT.inc(); + metrics::inc_counter(&metrics::FORK_CHOICE_REORG_COUNT); warn!( self.log, "Beacon chain re-org"; diff --git a/beacon_node/beacon_chain/src/metrics.rs b/beacon_node/beacon_chain/src/metrics.rs index 8b8307e93b..417c2904ab 100644 --- a/beacon_node/beacon_chain/src/metrics.rs +++ b/beacon_node/beacon_chain/src/metrics.rs @@ -1,111 +1,120 @@ pub use prometheus::Error; -use prometheus::{Histogram, IntCounter}; +use prometheus::{Histogram, HistogramTimer, IntCounter, Result}; + +pub fn start_timer(histogram: &Result) -> Option { + if let Ok(histogram) = histogram { + Some(histogram.start_timer()) + } else { + None + } +} + +pub fn stop_timer(timer: Option) { + timer.map(|t| t.observe_duration()); +} + +pub fn inc_counter(counter: &Result) { + if let Ok(counter) = counter { + counter.inc(); + } +} + +pub fn observe(histogram: &Result, value: f64) { + if let Ok(histogram) = histogram { + histogram.observe(value); + } +} lazy_static! { /* * Block Processing */ - pub static ref BLOCK_PROCESSING_DB_READ: Histogram = register_histogram!( + pub static ref BLOCK_PROCESSING_DB_READ: Result = register_histogram!( "block_processing_db_read_times", "Time spent loading block and state from DB" - ) - .unwrap(); - pub static ref BLOCK_PROCESSING_REQUESTS: IntCounter = register_int_counter!( + ); + pub static ref BLOCK_PROCESSING_REQUESTS: Result = register_int_counter!( "block_processing_requests", "Count of blocks sumbitted for processing" - ) - .unwrap(); - pub static ref BLOCK_PROCESSING_SUCCESSES: IntCounter = register_int_counter!( + ); + pub static ref BLOCK_PROCESSING_SUCCESSES: Result = register_int_counter!( "block_processing_successes", "Count of blocks processed without error" - ) - .unwrap(); - pub static ref BLOCK_PROCESSING_TIMES: Histogram = - register_histogram!("block_processing_times", "Full runtime of block processing") - .unwrap(); + ); + pub static ref BLOCK_PROCESSING_TIMES: Result = + register_histogram!("block_processing_times", "Full runtime of block processing"); /* * Block Production */ - pub static ref BLOCK_PRODUCTION_REQUESTS: IntCounter = register_int_counter!( + pub static ref BLOCK_PRODUCTION_REQUESTS: Result = register_int_counter!( "block_production_requests", "Count of all block production requests" - ) - .unwrap(); - pub static ref BLOCK_PRODUCTION_SUCCESSES: IntCounter = register_int_counter!( + ); + pub static ref BLOCK_PRODUCTION_SUCCESSES: Result = register_int_counter!( "block_production_successes", "Count of blocks sucessfully produced." - ) - .unwrap(); - pub static ref BLOCK_PRODUCTION_TIMES: Histogram = - register_histogram!("block_production_times", "Full runtime of block production").unwrap(); + ); + pub static ref BLOCK_PRODUCTION_TIMES: Result = + register_histogram!("block_production_times", "Full runtime of block production"); /* * Block Statistics */ - pub static ref OPERATIONS_PER_BLOCK_ATTESTATION: Histogram = register_histogram!( + pub static ref OPERATIONS_PER_BLOCK_ATTESTATION: Result = register_histogram!( "operations_per_block_attestation", "Number of attestations in a block" - ) - .unwrap(); + ); /* * Attestation Processing */ - pub static ref ATTESTATION_PROCESSING_REQUESTS: IntCounter = register_int_counter!( + pub static ref ATTESTATION_PROCESSING_REQUESTS: Result = register_int_counter!( "attestation_processing_requests", "Count of all attestations submitted for processing" - ) - .unwrap(); - pub static ref ATTESTATION_PROCESSING_SUCCESSES: IntCounter = register_int_counter!( + ); + pub static ref ATTESTATION_PROCESSING_SUCCESSES: Result = register_int_counter!( "attestation_processing_successes", "total_attestation_processing_successes" - ) - .unwrap(); - pub static ref ATTESTATION_PROCESSING_TIMES: Histogram = register_histogram!( + ); + pub static ref ATTESTATION_PROCESSING_TIMES: Result = register_histogram!( "attestation_processing_times", "Full runtime of attestation processing" - ) - .unwrap(); + ); /* * Attestation Production */ - pub static ref ATTESTATION_PRODUCTION_REQUESTS: IntCounter = register_int_counter!( + pub static ref ATTESTATION_PRODUCTION_REQUESTS: Result = register_int_counter!( "attestation_production_requests", "Count of all attestation production requests" - ) - .unwrap(); - pub static ref ATTESTATION_PRODUCTION_SUCCESSES: IntCounter = register_int_counter!( + ); + pub static ref ATTESTATION_PRODUCTION_SUCCESSES: Result = register_int_counter!( "attestation_production_successes", "Count of attestations processed without error" - ) - .unwrap(); - pub static ref ATTESTATION_PRODUCTION_TIMES: Histogram = register_histogram!( + ); + pub static ref ATTESTATION_PRODUCTION_TIMES: Result = register_histogram!( "attestation_production_times", "Full runtime of attestation production" - ).unwrap(); + ); /* * Fork Choice */ - pub static ref FORK_CHOICE_REQUESTS: IntCounter = register_int_counter!( + pub static ref FORK_CHOICE_REQUESTS: Result = register_int_counter!( "fork_choice_requests", "Count of occasions where fork choice has tried to find a head" - ) - .unwrap(); - pub static ref FORK_CHOICE_CHANGED_HEAD: IntCounter = register_int_counter!( + ); + pub static ref FORK_CHOICE_CHANGED_HEAD: Result = register_int_counter!( "fork_choice_changed_head", "Count of occasions fork choice has found a new head" - ) - .unwrap(); - pub static ref FORK_CHOICE_REORG_COUNT: IntCounter = register_int_counter!( + ); + pub static ref FORK_CHOICE_REORG_COUNT: Result = register_int_counter!( "fork_choice_reorg_count", "Count of occasions fork choice has switched to a different chain" - ) - .unwrap(); - pub static ref FORK_CHOICE_TIMES: Histogram = - register_histogram!("fork_choice_time", "Full runtime of fork choice").unwrap(); + ); + pub static ref FORK_CHOICE_TIMES: Result = + register_histogram!("fork_choice_time", "Full runtime of fork choice"); } pub fn gather_metrics() -> Vec { From 36ff115b04a90f767e08d5b52754a643aa2c950d Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sun, 11 Aug 2019 14:46:20 +1000 Subject: [PATCH 04/62] Use global prometheus gather at rest api --- beacon_node/beacon_chain/src/metrics.rs | 4 ---- beacon_node/rest_api/src/metrics.rs | 4 +--- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/beacon_node/beacon_chain/src/metrics.rs b/beacon_node/beacon_chain/src/metrics.rs index 417c2904ab..dc2919cc43 100644 --- a/beacon_node/beacon_chain/src/metrics.rs +++ b/beacon_node/beacon_chain/src/metrics.rs @@ -116,7 +116,3 @@ lazy_static! { pub static ref FORK_CHOICE_TIMES: Result = register_histogram!("fork_choice_time", "Full runtime of fork choice"); } - -pub fn gather_metrics() -> Vec { - prometheus::gather() -} diff --git a/beacon_node/rest_api/src/metrics.rs b/beacon_node/rest_api/src/metrics.rs index 1ecdf8b686..b0f5b8605e 100644 --- a/beacon_node/rest_api/src/metrics.rs +++ b/beacon_node/rest_api/src/metrics.rs @@ -7,9 +7,7 @@ pub fn get_prometheus(_req: Request) -> ApiResult { let mut buffer = vec![]; let encoder = TextEncoder::new(); - encoder - .encode(&beacon_chain::gather_metrics(), &mut buffer) - .unwrap(); + encoder.encode(&prometheus::gather(), &mut buffer).unwrap(); String::from_utf8(buffer) .map(|string| success_response(Body::from(string))) From 2108895fca7c34928b7d0540d8ea0c84740d56ac Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sun, 11 Aug 2019 15:34:10 +1000 Subject: [PATCH 05/62] Unify common metric fns into a crate --- Cargo.toml | 1 + beacon_node/beacon_chain/Cargo.toml | 2 +- beacon_node/beacon_chain/src/errors.rs | 8 --- beacon_node/beacon_chain/src/lib.rs | 3 -- beacon_node/beacon_chain/src/metrics.rs | 67 ++++++++---------------- eth2/utils/lighthouse_metrics/Cargo.toml | 11 ++++ eth2/utils/lighthouse_metrics/src/lib.rs | 49 +++++++++++++++++ 7 files changed, 83 insertions(+), 58 deletions(-) create mode 100644 eth2/utils/lighthouse_metrics/Cargo.toml create mode 100644 eth2/utils/lighthouse_metrics/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index f5ee02a173..9b7b87a0d0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ members = [ "eth2/utils/eth2_interop_keypairs", "eth2/utils/logging", "eth2/utils/eth2_hashing", + "eth2/utils/lighthouse_metrics", "eth2/utils/merkle_proof", "eth2/utils/int_to_bytes", "eth2/utils/serde_hex", diff --git a/beacon_node/beacon_chain/Cargo.toml b/beacon_node/beacon_chain/Cargo.toml index 43e7614b6a..850aa2e947 100644 --- a/beacon_node/beacon_chain/Cargo.toml +++ b/beacon_node/beacon_chain/Cargo.toml @@ -7,7 +7,7 @@ edition = "2018" [dependencies] store = { path = "../store" } parking_lot = "0.7" -prometheus = "^0.6" +lighthouse_metrics = { path = "../../eth2/utils/lighthouse_metrics" } log = "0.4" operation_pool = { path = "../../eth2/operation_pool" } serde = "1.0" diff --git a/beacon_node/beacon_chain/src/errors.rs b/beacon_node/beacon_chain/src/errors.rs index 7a51fc4258..22df90397e 100644 --- a/beacon_node/beacon_chain/src/errors.rs +++ b/beacon_node/beacon_chain/src/errors.rs @@ -1,5 +1,4 @@ use crate::fork_choice::Error as ForkChoiceError; -use crate::metrics::Error as MetricsError; use state_processing::per_block_processing::errors::{ AttestationValidationError, IndexedAttestationValidationError, }; @@ -34,7 +33,6 @@ pub enum BeaconChainError { MissingBeaconBlock(Hash256), MissingBeaconState(Hash256), SlotProcessingError(SlotProcessingError), - MetricsError(String), NoStateForAttestation { beacon_block_root: Hash256, }, @@ -44,12 +42,6 @@ pub enum BeaconChainError { easy_from_to!(SlotProcessingError, BeaconChainError); -impl From for BeaconChainError { - fn from(e: MetricsError) -> BeaconChainError { - BeaconChainError::MetricsError(format!("{:?}", e)) - } -} - #[derive(Debug, PartialEq)] pub enum BlockProductionError { UnableToGetBlockRootFromState, diff --git a/beacon_node/beacon_chain/src/lib.rs b/beacon_node/beacon_chain/src/lib.rs index e24534a2eb..98bd60a35f 100644 --- a/beacon_node/beacon_chain/src/lib.rs +++ b/beacon_node/beacon_chain/src/lib.rs @@ -1,6 +1,4 @@ #[macro_use] -extern crate prometheus; -#[macro_use] extern crate lazy_static; mod beacon_chain; @@ -18,7 +16,6 @@ pub use self::beacon_chain::{ pub use self::checkpoint::CheckPoint; pub use self::errors::{BeaconChainError, BlockProductionError}; pub use lmd_ghost; -pub use metrics::gather_metrics; pub use parking_lot; pub use slot_clock; pub use state_processing::per_block_processing::errors::{ diff --git a/beacon_node/beacon_chain/src/metrics.rs b/beacon_node/beacon_chain/src/metrics.rs index dc2919cc43..03f4783ff9 100644 --- a/beacon_node/beacon_chain/src/metrics.rs +++ b/beacon_node/beacon_chain/src/metrics.rs @@ -1,67 +1,42 @@ -pub use prometheus::Error; -use prometheus::{Histogram, HistogramTimer, IntCounter, Result}; - -pub fn start_timer(histogram: &Result) -> Option { - if let Ok(histogram) = histogram { - Some(histogram.start_timer()) - } else { - None - } -} - -pub fn stop_timer(timer: Option) { - timer.map(|t| t.observe_duration()); -} - -pub fn inc_counter(counter: &Result) { - if let Ok(counter) = counter { - counter.inc(); - } -} - -pub fn observe(histogram: &Result, value: f64) { - if let Ok(histogram) = histogram { - histogram.observe(value); - } -} +pub use lighthouse_metrics::*; lazy_static! { /* * Block Processing */ - pub static ref BLOCK_PROCESSING_DB_READ: Result = register_histogram!( + pub static ref BLOCK_PROCESSING_DB_READ: Result = try_create_histogram( "block_processing_db_read_times", "Time spent loading block and state from DB" ); - pub static ref BLOCK_PROCESSING_REQUESTS: Result = register_int_counter!( + pub static ref BLOCK_PROCESSING_REQUESTS: Result = try_create_int_counter( "block_processing_requests", - "Count of blocks sumbitted for processing" + "Count of blocks submitted for processing" ); - pub static ref BLOCK_PROCESSING_SUCCESSES: Result = register_int_counter!( + pub static ref BLOCK_PROCESSING_SUCCESSES: Result = try_create_int_counter( "block_processing_successes", "Count of blocks processed without error" ); pub static ref BLOCK_PROCESSING_TIMES: Result = - register_histogram!("block_processing_times", "Full runtime of block processing"); + try_create_histogram("block_processing_times", "Full runtime of block processing"); /* * Block Production */ - pub static ref BLOCK_PRODUCTION_REQUESTS: Result = register_int_counter!( + pub static ref BLOCK_PRODUCTION_REQUESTS: Result = try_create_int_counter( "block_production_requests", "Count of all block production requests" ); - pub static ref BLOCK_PRODUCTION_SUCCESSES: Result = register_int_counter!( + pub static ref BLOCK_PRODUCTION_SUCCESSES: Result = try_create_int_counter( "block_production_successes", - "Count of blocks sucessfully produced." + "Count of blocks successfully produced." ); pub static ref BLOCK_PRODUCTION_TIMES: Result = - register_histogram!("block_production_times", "Full runtime of block production"); + try_create_histogram("block_production_times", "Full runtime of block production"); /* * Block Statistics */ - pub static ref OPERATIONS_PER_BLOCK_ATTESTATION: Result = register_histogram!( + pub static ref OPERATIONS_PER_BLOCK_ATTESTATION: Result = try_create_histogram( "operations_per_block_attestation", "Number of attestations in a block" ); @@ -69,15 +44,15 @@ lazy_static! { /* * Attestation Processing */ - pub static ref ATTESTATION_PROCESSING_REQUESTS: Result = register_int_counter!( + pub static ref ATTESTATION_PROCESSING_REQUESTS: Result = try_create_int_counter( "attestation_processing_requests", "Count of all attestations submitted for processing" ); - pub static ref ATTESTATION_PROCESSING_SUCCESSES: Result = register_int_counter!( + pub static ref ATTESTATION_PROCESSING_SUCCESSES: Result = try_create_int_counter( "attestation_processing_successes", "total_attestation_processing_successes" ); - pub static ref ATTESTATION_PROCESSING_TIMES: Result = register_histogram!( + pub static ref ATTESTATION_PROCESSING_TIMES: Result = try_create_histogram( "attestation_processing_times", "Full runtime of attestation processing" ); @@ -85,15 +60,15 @@ lazy_static! { /* * Attestation Production */ - pub static ref ATTESTATION_PRODUCTION_REQUESTS: Result = register_int_counter!( + pub static ref ATTESTATION_PRODUCTION_REQUESTS: Result = try_create_int_counter( "attestation_production_requests", "Count of all attestation production requests" ); - pub static ref ATTESTATION_PRODUCTION_SUCCESSES: Result = register_int_counter!( + pub static ref ATTESTATION_PRODUCTION_SUCCESSES: Result = try_create_int_counter( "attestation_production_successes", "Count of attestations processed without error" ); - pub static ref ATTESTATION_PRODUCTION_TIMES: Result = register_histogram!( + pub static ref ATTESTATION_PRODUCTION_TIMES: Result = try_create_histogram( "attestation_production_times", "Full runtime of attestation production" ); @@ -101,18 +76,18 @@ lazy_static! { /* * Fork Choice */ - pub static ref FORK_CHOICE_REQUESTS: Result = register_int_counter!( + pub static ref FORK_CHOICE_REQUESTS: Result = try_create_int_counter( "fork_choice_requests", "Count of occasions where fork choice has tried to find a head" ); - pub static ref FORK_CHOICE_CHANGED_HEAD: Result = register_int_counter!( + pub static ref FORK_CHOICE_CHANGED_HEAD: Result = try_create_int_counter( "fork_choice_changed_head", "Count of occasions fork choice has found a new head" ); - pub static ref FORK_CHOICE_REORG_COUNT: Result = register_int_counter!( + pub static ref FORK_CHOICE_REORG_COUNT: Result = try_create_int_counter( "fork_choice_reorg_count", "Count of occasions fork choice has switched to a different chain" ); pub static ref FORK_CHOICE_TIMES: Result = - register_histogram!("fork_choice_time", "Full runtime of fork choice"); + try_create_histogram("fork_choice_time", "Full runtime of fork choice"); } diff --git a/eth2/utils/lighthouse_metrics/Cargo.toml b/eth2/utils/lighthouse_metrics/Cargo.toml new file mode 100644 index 0000000000..0a24a96fb5 --- /dev/null +++ b/eth2/utils/lighthouse_metrics/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "lighthouse_metrics" +version = "0.1.0" +authors = ["Paul Hauner "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +lazy_static = "1.3.0" +prometheus = "^0.6" diff --git a/eth2/utils/lighthouse_metrics/src/lib.rs b/eth2/utils/lighthouse_metrics/src/lib.rs new file mode 100644 index 0000000000..e6e30f6bb7 --- /dev/null +++ b/eth2/utils/lighthouse_metrics/src/lib.rs @@ -0,0 +1,49 @@ +use prometheus::{HistogramOpts, HistogramTimer, Opts}; + +pub use prometheus::{Histogram, IntCounter, Result}; + +pub fn try_create_int_counter(name: &str, help: &str) -> Result { + let opts = Opts::new(name, help); + let counter = IntCounter::with_opts(opts)?; + prometheus::register(Box::new(counter.clone()))?; + Ok(counter) +} + +pub fn try_create_histogram(name: &str, help: &str) -> Result { + let opts = HistogramOpts::new(name, help); + let histogram = Histogram::with_opts(opts)?; + prometheus::register(Box::new(histogram.clone()))?; + Ok(histogram) +} + +pub fn start_timer(histogram: &Result) -> Option { + if let Ok(histogram) = histogram { + Some(histogram.start_timer()) + } else { + None + } +} + +pub fn stop_timer(timer: Option) { + timer.map(|t| t.observe_duration()); +} + +pub fn inc_counter(counter: &Result) { + if let Ok(counter) = counter { + counter.inc(); + } +} + +pub fn observe(histogram: &Result, value: f64) { + if let Ok(histogram) = histogram { + histogram.observe(value); + } +} + +#[cfg(test)] +mod tests { + #[test] + fn it_works() { + assert_eq!(2 + 2, 4); + } +} From 441eb41b6bd3a36d5f673c23d392ef5a9796706d Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sun, 11 Aug 2019 15:53:34 +1000 Subject: [PATCH 06/62] Add heavy metering to block processing --- beacon_node/beacon_chain/src/beacon_chain.rs | 34 ++++++++++++++++-- beacon_node/beacon_chain/src/metrics.rs | 36 +++++++++++++++++--- 2 files changed, 64 insertions(+), 6 deletions(-) diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index b0bb6a1592..f5fb954b91 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -806,7 +806,7 @@ impl BeaconChain { block: BeaconBlock, ) -> Result { metrics::inc_counter(&metrics::BLOCK_PROCESSING_REQUESTS); - let timer = metrics::start_timer(&metrics::BLOCK_PROCESSING_TIMES); + let full_timer = metrics::start_timer(&metrics::BLOCK_PROCESSING_TIMES); let finalized_slot = self .state @@ -869,15 +869,25 @@ impl BeaconChain { metrics::stop_timer(db_read_timer); + let catchup_timer = metrics::start_timer(&metrics::BLOCK_PROCESSING_CATCHUP_STATE); + // Transition the parent state to the block slot. let mut state: BeaconState = parent_state; for _ in state.slot.as_u64()..block.slot.as_u64() { per_slot_processing(&mut state, &self.spec)?; } + metrics::stop_timer(catchup_timer); + + let commitee_timer = metrics::start_timer(&metrics::BLOCK_PROCESSING_COMMITTEE); + state.build_committee_cache(RelativeEpoch::Previous, &self.spec)?; state.build_committee_cache(RelativeEpoch::Current, &self.spec)?; + metrics::stop_timer(commitee_timer); + + let core_timer = metrics::start_timer(&metrics::BLOCK_PROCESSING_CORE); + // Apply the received block to its parent state (which has been transitioned into this // slot). match per_block_processing(&mut state, &block, &self.spec) { @@ -888,16 +898,29 @@ impl BeaconChain { _ => {} } + metrics::stop_timer(core_timer); + + let state_root_timer = metrics::start_timer(&metrics::BLOCK_PROCESSING_STATE_ROOT); + let state_root = state.canonical_root(); if block.state_root != state_root { return Ok(BlockProcessingOutcome::StateRootMismatch); } + metrics::stop_timer(state_root_timer); + + let db_write_timer = metrics::start_timer(&metrics::BLOCK_PROCESSING_DB_WRITE); + // Store the block and state. self.store.put(&block_root, &block)?; self.store.put(&state_root, &state)?; + metrics::stop_timer(db_write_timer); + + let fork_choice_register_timer = + metrics::start_timer(&metrics::BLOCK_PROCESSING_FORK_CHOICE_REGISTER); + // Register the new block with the fork choice service. if let Err(e) = self.fork_choice.process_block(&state, &block, block_root) { error!( @@ -909,6 +932,11 @@ impl BeaconChain { ) } + metrics::stop_timer(fork_choice_register_timer); + + let find_head_timer = + metrics::start_timer(&metrics::BLOCK_PROCESSING_FORK_CHOICE_FIND_HEAD); + // Execute the fork choice algorithm, enthroning a new head if discovered. // // Note: in the future we may choose to run fork-choice less often, potentially based upon @@ -921,12 +949,14 @@ impl BeaconChain { ) }; + metrics::stop_timer(find_head_timer); + metrics::inc_counter(&metrics::BLOCK_PROCESSING_SUCCESSES); metrics::observe( &metrics::OPERATIONS_PER_BLOCK_ATTESTATION, block.body.attestations.len() as f64, ); - metrics::stop_timer(timer); + metrics::stop_timer(full_timer); Ok(BlockProcessingOutcome::Processed { block_root }) } diff --git a/beacon_node/beacon_chain/src/metrics.rs b/beacon_node/beacon_chain/src/metrics.rs index 03f4783ff9..38a7af9e15 100644 --- a/beacon_node/beacon_chain/src/metrics.rs +++ b/beacon_node/beacon_chain/src/metrics.rs @@ -4,10 +4,6 @@ lazy_static! { /* * Block Processing */ - pub static ref BLOCK_PROCESSING_DB_READ: Result = try_create_histogram( - "block_processing_db_read_times", - "Time spent loading block and state from DB" - ); pub static ref BLOCK_PROCESSING_REQUESTS: Result = try_create_int_counter( "block_processing_requests", "Count of blocks submitted for processing" @@ -18,6 +14,38 @@ lazy_static! { ); pub static ref BLOCK_PROCESSING_TIMES: Result = try_create_histogram("block_processing_times", "Full runtime of block processing"); + pub static ref BLOCK_PROCESSING_DB_READ: Result = try_create_histogram( + "block_processing_db_read_times", + "Time spent loading block and state from DB for block processing" + ); + pub static ref BLOCK_PROCESSING_CATCHUP_STATE: Result = try_create_histogram( + "block_processing_catch-up_state_times", + "Time spent skipping slots on a state before processing a block." + ); + pub static ref BLOCK_PROCESSING_COMMITTEE: Result = try_create_histogram( + "block_processing_committee_building_times", + "Time spent building/obtaining committees for block processing." + ); + pub static ref BLOCK_PROCESSING_CORE: Result = try_create_histogram( + "block_processing_core_times", + "Time spent doing the core per_block_processing state processing." + ); + pub static ref BLOCK_PROCESSING_STATE_ROOT: Result = try_create_histogram( + "block_processing_state_root_times", + "Time spent calculating the state root when processing a block." + ); + pub static ref BLOCK_PROCESSING_DB_WRITE: Result = try_create_histogram( + "block_processing_db_write_times", + "Time spent writing a newly processed block and state to DB" + ); + pub static ref BLOCK_PROCESSING_FORK_CHOICE_REGISTER: Result = try_create_histogram( + "block_processing_fork_choice_register_times", + "Time spent registering the new block with fork choice (but not finding head)" + ); + pub static ref BLOCK_PROCESSING_FORK_CHOICE_FIND_HEAD: Result = try_create_histogram( + "block_processing_fork_choice_find_head_times", + "Time spent finding the new head after processing a new block" + ); /* * Block Production From 76f42ac7ffd7d25e4c92393370b5b4717cacab49 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sun, 11 Aug 2019 16:15:26 +1000 Subject: [PATCH 07/62] Remove hypen from prometheus metric name --- beacon_node/beacon_chain/src/metrics.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beacon_node/beacon_chain/src/metrics.rs b/beacon_node/beacon_chain/src/metrics.rs index 38a7af9e15..d0b6e27fcd 100644 --- a/beacon_node/beacon_chain/src/metrics.rs +++ b/beacon_node/beacon_chain/src/metrics.rs @@ -19,7 +19,7 @@ lazy_static! { "Time spent loading block and state from DB for block processing" ); pub static ref BLOCK_PROCESSING_CATCHUP_STATE: Result = try_create_histogram( - "block_processing_catch-up_state_times", + "block_processing_catch_up_state_times", "Time spent skipping slots on a state before processing a block." ); pub static ref BLOCK_PROCESSING_COMMITTEE: Result = try_create_histogram( From 42d300bdc35df563598fcd65488b5fb21342a60b Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sun, 11 Aug 2019 17:49:32 +1000 Subject: [PATCH 08/62] Add more beacon chain metrics --- beacon_node/beacon_chain/src/beacon_chain.rs | 18 ++++++++++++++---- beacon_node/beacon_chain/src/fork_choice.rs | 20 +++++++++++++++++--- beacon_node/beacon_chain/src/metrics.rs | 20 ++++++++++++++++++++ 3 files changed, 51 insertions(+), 7 deletions(-) diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index f5fb954b91..6f9a2b414d 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -1060,11 +1060,8 @@ impl BeaconChain { // Determine the root of the block that is the head of the chain. let beacon_block_root = self.fork_choice.find_head(&self)?; - // End fork choice metrics timer. - metrics::stop_timer(timer); - // If a new head was chosen. - if beacon_block_root != self.head().beacon_block_root { + let result = if beacon_block_root != self.head().beacon_block_root { metrics::inc_counter(&metrics::FORK_CHOICE_CHANGED_HEAD); let beacon_block: BeaconBlock = self @@ -1127,11 +1124,22 @@ impl BeaconChain { } } else { Ok(()) + }; + + // End fork choice metrics timer. + metrics::stop_timer(timer); + + if let Err(_) = result { + metrics::inc_counter(&metrics::FORK_CHOICE_ERRORS); } + + result } /// Update the canonical head to `new_head`. fn update_canonical_head(&self, new_head: CheckPoint) -> Result<(), Error> { + let timer = metrics::start_timer(&metrics::UPDATE_HEAD_TIMES); + // Update the checkpoint that stores the head of the chain at the time it received the // block. *self.canonical_head.write() = new_head; @@ -1158,6 +1166,8 @@ impl BeaconChain { // Save `self` to `self.store`. self.persist()?; + metrics::stop_timer(timer); + Ok(()) } diff --git a/beacon_node/beacon_chain/src/fork_choice.rs b/beacon_node/beacon_chain/src/fork_choice.rs index edd426f296..77fdaacdc5 100644 --- a/beacon_node/beacon_chain/src/fork_choice.rs +++ b/beacon_node/beacon_chain/src/fork_choice.rs @@ -1,4 +1,4 @@ -use crate::{BeaconChain, BeaconChainTypes}; +use crate::{metrics, BeaconChain, BeaconChainTypes}; use lmd_ghost::LmdGhost; use state_processing::common::get_attesting_indices; use std::sync::Arc; @@ -46,6 +46,8 @@ impl ForkChoice { } pub fn find_head(&self, chain: &BeaconChain) -> Result { + let timer = metrics::start_timer(&metrics::FORK_CHOICE_FIND_HEAD_TIMES); + let start_slot = |epoch: Epoch| epoch.start_slot(T::EthSpec::slots_per_epoch()); // From the specification: @@ -97,9 +99,14 @@ impl ForkChoice { .map(|v| v.effective_balance) }; - self.backend + let result = self + .backend .find_head(start_block_slot, start_block_root, weight) - .map_err(Into::into) + .map_err(Into::into); + + metrics::stop_timer(timer); + + result } /// Process all attestations in the given `block`. @@ -112,6 +119,7 @@ impl ForkChoice { block: &BeaconBlock, block_root: Hash256, ) -> Result<()> { + let timer = metrics::start_timer(&metrics::FORK_CHOICE_PROCESS_BLOCK_TIMES); // Note: we never count the block as a latest message, only attestations. // // I (Paul H) do not have an explicit reference to this, but I derive it from this @@ -136,6 +144,8 @@ impl ForkChoice { // a block that has the majority of votes applied to it. self.backend.process_block(block, block_root)?; + metrics::stop_timer(timer); + Ok(()) } @@ -148,6 +158,8 @@ impl ForkChoice { attestation: &Attestation, block: &BeaconBlock, ) -> Result<()> { + let timer = metrics::start_timer(&metrics::FORK_CHOICE_PROCESS_ATTESTATION_TIMES); + let block_hash = attestation.data.beacon_block_root; // Ignore any attestations to the zero hash. @@ -175,6 +187,8 @@ impl ForkChoice { } } + metrics::stop_timer(timer); + Ok(()) } diff --git a/beacon_node/beacon_chain/src/metrics.rs b/beacon_node/beacon_chain/src/metrics.rs index d0b6e27fcd..34f359ad8d 100644 --- a/beacon_node/beacon_chain/src/metrics.rs +++ b/beacon_node/beacon_chain/src/metrics.rs @@ -108,6 +108,10 @@ lazy_static! { "fork_choice_requests", "Count of occasions where fork choice has tried to find a head" ); + pub static ref FORK_CHOICE_ERRORS: Result = try_create_int_counter( + "fork_choice_errors", + "Count of occasions where fork choice has returned an error when trying to find a head" + ); pub static ref FORK_CHOICE_CHANGED_HEAD: Result = try_create_int_counter( "fork_choice_changed_head", "Count of occasions fork choice has found a new head" @@ -118,4 +122,20 @@ lazy_static! { ); pub static ref FORK_CHOICE_TIMES: Result = try_create_histogram("fork_choice_time", "Full runtime of fork choice"); + pub static ref FORK_CHOICE_FIND_HEAD_TIMES: Result = + try_create_histogram("fork_choice_find_head_time", "Full runtime of fork choice find_head function"); + pub static ref FORK_CHOICE_PROCESS_BLOCK_TIMES: Result = try_create_histogram( + "fork_choice_process_block_time", + "Time taken to add a block and all attestations to fork choice" + ); + pub static ref FORK_CHOICE_PROCESS_ATTESTATION_TIMES: Result = try_create_histogram( + "fork_choice_process_attestation_time", + "Time taken to add an attestation to fork choice" + ); + + /* + * Head Updating + */ + pub static ref UPDATE_HEAD_TIMES: Result = + try_create_histogram("update_head_times", "Time taken to update the canonical head"); } From 78db947e6e65f4d0960ca5b9340305c663856244 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sun, 11 Aug 2019 18:28:57 +1000 Subject: [PATCH 09/62] Add beacon chain persistence metric --- beacon_node/beacon_chain/src/beacon_chain.rs | 4 ++++ beacon_node/beacon_chain/src/metrics.rs | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index 6f9a2b414d..96ff339a67 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -199,6 +199,8 @@ impl BeaconChain { /// Attempt to save this instance to `self.store`. pub fn persist(&self) -> Result<(), Error> { + let timer = metrics::start_timer(&metrics::PERSIST_CHAIN); + let p: PersistedBeaconChain = PersistedBeaconChain { canonical_head: self.canonical_head.read().clone(), op_pool: PersistedOperationPool::from_operation_pool(&self.op_pool), @@ -209,6 +211,8 @@ impl BeaconChain { let key = Hash256::from_slice(&BEACON_CHAIN_DB_KEY.as_bytes()); self.store.put(&key, &p)?; + metrics::stop_timer(timer); + Ok(()) } diff --git a/beacon_node/beacon_chain/src/metrics.rs b/beacon_node/beacon_chain/src/metrics.rs index 34f359ad8d..b911254633 100644 --- a/beacon_node/beacon_chain/src/metrics.rs +++ b/beacon_node/beacon_chain/src/metrics.rs @@ -138,4 +138,10 @@ lazy_static! { */ pub static ref UPDATE_HEAD_TIMES: Result = try_create_histogram("update_head_times", "Time taken to update the canonical head"); + + /* + * Persisting BeaconChain to disk + */ + pub static ref PERSIST_CHAIN: Result = + try_create_histogram("persist_chain", "Time taken to update the canonical head"); } From 6150f0ae1a549dcc1d76c831c4ca5cae03300dd7 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sun, 11 Aug 2019 18:29:11 +1000 Subject: [PATCH 10/62] Prune op pool on finalization --- beacon_node/beacon_chain/src/beacon_chain.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index 96ff339a67..0e0583309f 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -1199,6 +1199,9 @@ impl BeaconChain { self.fork_choice .process_finalization(&finalized_block, finalized_block_root)?; + self.op_pool + .prune_all(&self.head().beacon_state, &self.spec); + Ok(()) } } From 7140dbc45da4a8895a155450f1777fa1655991ac Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Mon, 12 Aug 2019 13:26:58 +1000 Subject: [PATCH 11/62] Add extra prom beacon chain metrics --- beacon_node/beacon_chain/Cargo.toml | 2 +- beacon_node/beacon_chain/src/lib.rs | 1 + beacon_node/beacon_chain/src/metrics.rs | 156 ++++++++++++++++++++++- beacon_node/client/src/lib.rs | 1 + beacon_node/rest_api/src/lib.rs | 20 ++- beacon_node/rest_api/src/metrics.rs | 18 ++- beacon_node/store/Cargo.toml | 2 + beacon_node/store/src/lib.rs | 4 + beacon_node/store/src/metrics.rs | 25 ++++ eth2/utils/lighthouse_metrics/src/lib.rs | 15 ++- 10 files changed, 233 insertions(+), 11 deletions(-) create mode 100644 beacon_node/store/src/metrics.rs diff --git a/beacon_node/beacon_chain/Cargo.toml b/beacon_node/beacon_chain/Cargo.toml index 850aa2e947..462d44e920 100644 --- a/beacon_node/beacon_chain/Cargo.toml +++ b/beacon_node/beacon_chain/Cargo.toml @@ -7,6 +7,7 @@ edition = "2018" [dependencies] store = { path = "../store" } parking_lot = "0.7" +lazy_static = "1.3.0" lighthouse_metrics = { path = "../../eth2/utils/lighthouse_metrics" } log = "0.4" operation_pool = { path = "../../eth2/operation_pool" } @@ -17,7 +18,6 @@ sloggers = { version = "^0.3" } slot_clock = { path = "../../eth2/utils/slot_clock" } eth2_ssz = "0.1" eth2_ssz_derive = "0.1" -lazy_static = "1.3.0" state_processing = { path = "../../eth2/state_processing" } tree_hash = "0.1" types = { path = "../../eth2/types" } diff --git a/beacon_node/beacon_chain/src/lib.rs b/beacon_node/beacon_chain/src/lib.rs index 98bd60a35f..1262bc5372 100644 --- a/beacon_node/beacon_chain/src/lib.rs +++ b/beacon_node/beacon_chain/src/lib.rs @@ -16,6 +16,7 @@ pub use self::beacon_chain::{ pub use self::checkpoint::CheckPoint; pub use self::errors::{BeaconChainError, BlockProductionError}; pub use lmd_ghost; +pub use metrics::scrape_for_metrics; pub use parking_lot; pub use slot_clock; pub use state_processing::per_block_processing::errors::{ diff --git a/beacon_node/beacon_chain/src/metrics.rs b/beacon_node/beacon_chain/src/metrics.rs index b911254633..6ed8218f02 100644 --- a/beacon_node/beacon_chain/src/metrics.rs +++ b/beacon_node/beacon_chain/src/metrics.rs @@ -1,4 +1,6 @@ +use crate::{BeaconChain, BeaconChainTypes}; pub use lighthouse_metrics::*; +use types::{BeaconState, Epoch, EthSpec, Hash256, Slot}; lazy_static! { /* @@ -133,15 +135,157 @@ lazy_static! { "Time taken to add an attestation to fork choice" ); - /* - * Head Updating - */ - pub static ref UPDATE_HEAD_TIMES: Result = - try_create_histogram("update_head_times", "Time taken to update the canonical head"); - /* * Persisting BeaconChain to disk */ pub static ref PERSIST_CHAIN: Result = try_create_histogram("persist_chain", "Time taken to update the canonical head"); } + +// Lazy-static is split so we don't reach the crate-level recursion limit. +lazy_static! { + /* + * Slot Clock + */ + pub static ref PRESENT_SLOT: Result = + try_create_int_gauge("present_slot", "The present slot, according to system time"); + pub static ref PRESENT_EPOCH: Result = + try_create_int_gauge("present_epoch", "The present epoch, according to system time"); + + /* + * Chain Head + */ + pub static ref UPDATE_HEAD_TIMES: Result = + try_create_histogram("update_head_times", "Time taken to update the canonical head"); + pub static ref HEAD_STATE_SLOT: Result = + try_create_int_gauge("head_state_slot", "Slot of the block at the head of the chain"); + pub static ref HEAD_STATE_ROOT: Result = + try_create_int_gauge("head_state_root", "Root of the block at the head of the chain"); + pub static ref HEAD_STATE_LATEST_BLOCK_SLOT: Result = + try_create_int_gauge("head_state_latest_block_slot", "Latest block slot at the head of the chain"); + pub static ref HEAD_STATE_CURRENT_JUSTIFIED_ROOT: Result = + try_create_int_gauge("head_state_current_justified_root", "Current justified root at the head of the chain"); + pub static ref HEAD_STATE_CURRENT_JUSTIFIED_EPOCH: Result = + try_create_int_gauge("head_state_current_justified_epoch", "Current justified epoch at the head of the chain"); + pub static ref HEAD_STATE_PREVIOUS_JUSTIFIED_ROOT: Result = + try_create_int_gauge("head_state_previous_justified_root", "Previous justified root at the head of the chain"); + pub static ref HEAD_STATE_PREVIOUS_JUSTIFIED_EPOCH: Result = + try_create_int_gauge("head_state_previous_justified_epoch", "Previous justified epoch at the head of the chain"); + pub static ref HEAD_STATE_FINALIZED_ROOT: Result = + try_create_int_gauge("head_state_finalized_root", "Finalized root at the head of the chain"); + pub static ref HEAD_STATE_FINALIZED_EPOCH: Result = + try_create_int_gauge("head_state_finalized_epoch", "Finalized epoch at the head of the chain"); + pub static ref HEAD_STATE_TOTAL_VALIDATORS: Result = + try_create_int_gauge("head_state_total_validators", "Count of validators at the head of the chain"); + pub static ref HEAD_STATE_ACTIVE_VALIDATORS: Result = + try_create_int_gauge("head_state_active_validators", "Count of active validators at the head of the chain"); + pub static ref HEAD_STATE_VALIDATOR_BALANCES: Result = + try_create_int_gauge("head_state_validator_balances", "Sum of all validator balances at the head of the chain"); + pub static ref HEAD_STATE_SLASHED_VALIDATORS: Result = + try_create_int_gauge("head_state_slashed_validators", "Count of all slashed validators at the head of the chain"); + pub static ref HEAD_STATE_WITHDRAWN_VALIDATORS: Result = + try_create_int_gauge("head_state_withdrawn_validators", "Sum of all validator balances at the head of the chain"); + pub static ref HEAD_STATE_ETH1_DEPOSIT_INDEX: Result = + try_create_int_gauge("head_state_eth1_deposit_index", "Eth1 deposit index at the head of the chain"); +} + +/// Scrape the `beacon_chain` for metrics that are not constantly updated (e.g., the present slot, +/// head state info, etc) and update the Prometheus `DEFAULT_REGISTRY`. +pub fn scrape_for_metrics(beacon_chain: &BeaconChain) { + set_gauge_by_slot( + &PRESENT_SLOT, + beacon_chain + .read_slot_clock() + .unwrap_or_else(|| Slot::new(0)), + ); + + set_gauge_by_epoch( + &PRESENT_EPOCH, + beacon_chain + .read_slot_clock() + .map(|s| s.epoch(T::EthSpec::slots_per_epoch())) + .unwrap_or_else(|| Epoch::new(0)), + ); + + scrape_head_state::( + &beacon_chain.head().beacon_state, + beacon_chain.head().beacon_state_root, + ); +} + +/// Scrape the given `state` assuming it's the head state, updating the `DEFAULT_REGISTRY`. +fn scrape_head_state(state: &BeaconState, state_root: Hash256) { + set_gauge_by_slot(&HEAD_STATE_SLOT, state.slot); + set_gauge_by_hash(&HEAD_STATE_ROOT, state_root); + set_gauge_by_slot( + &HEAD_STATE_LATEST_BLOCK_SLOT, + state.latest_block_header.slot, + ); + set_gauge_by_hash( + &HEAD_STATE_CURRENT_JUSTIFIED_ROOT, + state.current_justified_checkpoint.root, + ); + set_gauge_by_epoch( + &HEAD_STATE_CURRENT_JUSTIFIED_EPOCH, + state.current_justified_checkpoint.epoch, + ); + set_gauge_by_hash( + &HEAD_STATE_PREVIOUS_JUSTIFIED_ROOT, + state.previous_justified_checkpoint.root, + ); + set_gauge_by_epoch( + &HEAD_STATE_PREVIOUS_JUSTIFIED_EPOCH, + state.previous_justified_checkpoint.epoch, + ); + set_gauge_by_hash(&HEAD_STATE_FINALIZED_ROOT, state.finalized_checkpoint.root); + set_gauge_by_epoch( + &HEAD_STATE_FINALIZED_EPOCH, + state.finalized_checkpoint.epoch, + ); + set_gauge_by_usize(&HEAD_STATE_TOTAL_VALIDATORS, state.validators.len()); + set_gauge_by_u64( + &HEAD_STATE_VALIDATOR_BALANCES, + state.balances.iter().fold(0_u64, |acc, i| acc + i), + ); + set_gauge_by_usize( + &HEAD_STATE_ACTIVE_VALIDATORS, + state + .validators + .iter() + .filter(|v| v.is_active_at(state.current_epoch())) + .count(), + ); + set_gauge_by_usize( + &HEAD_STATE_SLASHED_VALIDATORS, + state.validators.iter().filter(|v| v.slashed).count(), + ); + set_gauge_by_usize( + &HEAD_STATE_WITHDRAWN_VALIDATORS, + state + .validators + .iter() + .filter(|v| v.is_withdrawable_at(state.current_epoch())) + .count(), + ); + set_gauge_by_u64(&HEAD_STATE_ETH1_DEPOSIT_INDEX, state.eth1_deposit_index); +} + +fn set_gauge_by_slot(gauge: &Result, value: Slot) { + set_gauge(gauge, value.as_u64() as i64); +} + +fn set_gauge_by_epoch(gauge: &Result, value: Epoch) { + set_gauge(gauge, value.as_u64() as i64); +} + +fn set_gauge_by_hash(gauge: &Result, value: Hash256) { + set_gauge(gauge, value.to_low_u64_le() as i64); +} + +fn set_gauge_by_usize(gauge: &Result, value: usize) { + set_gauge(gauge, value as i64); +} + +fn set_gauge_by_u64(gauge: &Result, value: u64) { + set_gauge(gauge, value as i64); +} diff --git a/beacon_node/client/src/lib.rs b/beacon_node/client/src/lib.rs index e06c5b60ee..c74787f606 100644 --- a/beacon_node/client/src/lib.rs +++ b/beacon_node/client/src/lib.rs @@ -142,6 +142,7 @@ where &client_config.rest_api, executor, beacon_chain.clone(), + client_config.db_path().expect("unable to read datadir"), &log, ) { Ok(s) => Some(s), diff --git a/beacon_node/rest_api/src/lib.rs b/beacon_node/rest_api/src/lib.rs index 7dc0df578d..fea67618ba 100644 --- a/beacon_node/rest_api/src/lib.rs +++ b/beacon_node/rest_api/src/lib.rs @@ -13,6 +13,8 @@ use hyper::rt::Future; use hyper::service::service_fn_ok; use hyper::{Body, Method, Response, Server, StatusCode}; use slog::{info, o, warn}; +use std::ops::Deref; +use std::path::PathBuf; use std::sync::Arc; use tokio::runtime::TaskExecutor; use url_query::UrlQuery; @@ -68,6 +70,7 @@ pub fn start_server( config: &ApiConfig, executor: &TaskExecutor, beacon_chain: Arc>, + db_path: PathBuf, log: &slog::Logger, ) -> Result { let log = log.new(o!("Service" => "Api")); @@ -81,6 +84,8 @@ pub fn start_server( Ok(()) }); + let db_path = DBPath(db_path); + // Get the address to bind to let bind_addr = (config.listen_address, config.port).into(); @@ -91,12 +96,14 @@ pub fn start_server( let service = move || { let log = server_log.clone(); let beacon_chain = server_bc.clone(); + let db_path = db_path.clone(); // Create a simple handler for the router, inject our stateful objects into the request. service_fn_ok(move |mut req| { req.extensions_mut().insert::(log.clone()); req.extensions_mut() .insert::>>(beacon_chain.clone()); + req.extensions_mut().insert::(db_path.clone()); let path = req.uri().path().to_string(); @@ -104,7 +111,7 @@ pub fn start_server( let result = match (req.method(), path.as_ref()) { (&Method::GET, "/beacon/state") => beacon::get_state::(req), (&Method::GET, "/beacon/state_root") => beacon::get_state_root::(req), - (&Method::GET, "/metrics") => metrics::get_prometheus(req), + (&Method::GET, "/metrics") => metrics::get_prometheus::(req), (&Method::GET, "/node/version") => node::get_version(req), (&Method::GET, "/node/genesis_time") => node::get_genesis_time::(req), _ => Err(ApiError::MethodNotAllowed(path.clone())), @@ -154,3 +161,14 @@ fn success_response(body: Body) -> Response { .body(body) .expect("We should always be able to make response from the success body.") } + +#[derive(Clone)] +pub struct DBPath(PathBuf); + +impl Deref for DBPath { + type Target = PathBuf; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} diff --git a/beacon_node/rest_api/src/metrics.rs b/beacon_node/rest_api/src/metrics.rs index b0f5b8605e..0cd700c445 100644 --- a/beacon_node/rest_api/src/metrics.rs +++ b/beacon_node/rest_api/src/metrics.rs @@ -1,12 +1,26 @@ -use crate::{success_response, ApiError, ApiResult}; +use crate::{success_response, ApiError, ApiResult, DBPath}; +use beacon_chain::{BeaconChain, BeaconChainTypes}; use hyper::{Body, Request}; use prometheus::{Encoder, TextEncoder}; +use std::sync::Arc; /// Returns the full set of Prometheus metrics for the Beacon Node application. -pub fn get_prometheus(_req: Request) -> ApiResult { +pub fn get_prometheus(req: Request) -> ApiResult { let mut buffer = vec![]; let encoder = TextEncoder::new(); + let beacon_chain = req + .extensions() + .get::>>() + .ok_or_else(|| ApiError::ServerError("Beacon chain extension missing".to_string()))?; + let db_path = req + .extensions() + .get::() + .ok_or_else(|| ApiError::ServerError("DBPath extension missing".to_string()))?; + + store::scrape_for_metrics(&db_path); + beacon_chain::scrape_for_metrics(&beacon_chain); + encoder.encode(&prometheus::gather(), &mut buffer).unwrap(); String::from_utf8(buffer) diff --git a/beacon_node/store/Cargo.toml b/beacon_node/store/Cargo.toml index 9607e8b8e5..cd9711253e 100644 --- a/beacon_node/store/Cargo.toml +++ b/beacon_node/store/Cargo.toml @@ -15,3 +15,5 @@ eth2_ssz = "0.1" eth2_ssz_derive = "0.1" tree_hash = "0.1" types = { path = "../../eth2/types" } +lazy_static = "1.3.0" +lighthouse_metrics = { path = "../../eth2/utils/lighthouse_metrics" } diff --git a/beacon_node/store/src/lib.rs b/beacon_node/store/src/lib.rs index 5b8d583200..9c0e3cbaec 100644 --- a/beacon_node/store/src/lib.rs +++ b/beacon_node/store/src/lib.rs @@ -7,18 +7,22 @@ //! //! Provides a simple API for storing/retrieving all types that sometimes needs type-hints. See //! tests for implementation examples. +#[macro_use] +extern crate lazy_static; mod block_at_slot; mod errors; mod impls; mod leveldb_store; mod memory_store; +mod metrics; pub mod iter; pub use self::leveldb_store::LevelDB as DiskStore; pub use self::memory_store::MemoryStore; pub use errors::Error; +pub use metrics::scrape_for_metrics; pub use types::*; /// An object capable of storing and retrieving objects implementing `StoreItem`. diff --git a/beacon_node/store/src/metrics.rs b/beacon_node/store/src/metrics.rs new file mode 100644 index 0000000000..b6a055f102 --- /dev/null +++ b/beacon_node/store/src/metrics.rs @@ -0,0 +1,25 @@ +pub use lighthouse_metrics::{set_gauge, try_create_int_gauge, *}; + +use std::fs; +use std::path::PathBuf; + +lazy_static! { + pub static ref DISK_DB_SIZE: Result = + try_create_int_gauge("database_size", "Size of the on-disk database (bytes)"); +} + +/// Updates the global metrics registry with store-related information. +pub fn scrape_for_metrics(db_path: &PathBuf) { + let db_size = if let Ok(iter) = fs::read_dir(db_path) { + iter.filter_map(std::result::Result::ok) + .map(size_of_dir_entry) + .fold(0_u64, |sum, val| sum + val) + } else { + 0 + }; + set_gauge(&DISK_DB_SIZE, db_size as i64); +} + +fn size_of_dir_entry(dir: fs::DirEntry) -> u64 { + dir.metadata().map(|m| m.len()).unwrap_or(0) +} diff --git a/eth2/utils/lighthouse_metrics/src/lib.rs b/eth2/utils/lighthouse_metrics/src/lib.rs index e6e30f6bb7..d55fcd3e21 100644 --- a/eth2/utils/lighthouse_metrics/src/lib.rs +++ b/eth2/utils/lighthouse_metrics/src/lib.rs @@ -1,6 +1,6 @@ use prometheus::{HistogramOpts, HistogramTimer, Opts}; -pub use prometheus::{Histogram, IntCounter, Result}; +pub use prometheus::{Histogram, IntCounter, IntGauge, Result}; pub fn try_create_int_counter(name: &str, help: &str) -> Result { let opts = Opts::new(name, help); @@ -9,6 +9,13 @@ pub fn try_create_int_counter(name: &str, help: &str) -> Result { Ok(counter) } +pub fn try_create_int_gauge(name: &str, help: &str) -> Result { + let opts = Opts::new(name, help); + let gauge = IntGauge::with_opts(opts)?; + prometheus::register(Box::new(gauge.clone()))?; + Ok(gauge) +} + pub fn try_create_histogram(name: &str, help: &str) -> Result { let opts = HistogramOpts::new(name, help); let histogram = Histogram::with_opts(opts)?; @@ -34,6 +41,12 @@ pub fn inc_counter(counter: &Result) { } } +pub fn set_gauge(gauge: &Result, value: i64) { + if let Ok(gauge) = gauge { + gauge.set(value); + } +} + pub fn observe(histogram: &Result, value: f64) { if let Ok(histogram) = histogram { histogram.observe(value); From 913ee4694eb4310b08def5feef4f111233b6c3e5 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Mon, 12 Aug 2019 13:35:16 +1000 Subject: [PATCH 12/62] Prefix BeaconChain metrics with "beacon_" --- beacon_node/beacon_chain/src/metrics.rs | 94 ++++++++++++------------- 1 file changed, 47 insertions(+), 47 deletions(-) diff --git a/beacon_node/beacon_chain/src/metrics.rs b/beacon_node/beacon_chain/src/metrics.rs index 6ed8218f02..227f1090f7 100644 --- a/beacon_node/beacon_chain/src/metrics.rs +++ b/beacon_node/beacon_chain/src/metrics.rs @@ -7,45 +7,45 @@ lazy_static! { * Block Processing */ pub static ref BLOCK_PROCESSING_REQUESTS: Result = try_create_int_counter( - "block_processing_requests", + "beacon_block_processing_requests", "Count of blocks submitted for processing" ); pub static ref BLOCK_PROCESSING_SUCCESSES: Result = try_create_int_counter( - "block_processing_successes", + "beacon_block_processing_successes", "Count of blocks processed without error" ); pub static ref BLOCK_PROCESSING_TIMES: Result = try_create_histogram("block_processing_times", "Full runtime of block processing"); pub static ref BLOCK_PROCESSING_DB_READ: Result = try_create_histogram( - "block_processing_db_read_times", + "beacon_block_processing_db_read_times", "Time spent loading block and state from DB for block processing" ); pub static ref BLOCK_PROCESSING_CATCHUP_STATE: Result = try_create_histogram( - "block_processing_catch_up_state_times", + "beacon_block_processing_catch_up_state_times", "Time spent skipping slots on a state before processing a block." ); pub static ref BLOCK_PROCESSING_COMMITTEE: Result = try_create_histogram( - "block_processing_committee_building_times", + "beacon_block_processing_committee_building_times", "Time spent building/obtaining committees for block processing." ); pub static ref BLOCK_PROCESSING_CORE: Result = try_create_histogram( - "block_processing_core_times", + "beacon_block_processing_core_times", "Time spent doing the core per_block_processing state processing." ); pub static ref BLOCK_PROCESSING_STATE_ROOT: Result = try_create_histogram( - "block_processing_state_root_times", + "beacon_block_processing_state_root_times", "Time spent calculating the state root when processing a block." ); pub static ref BLOCK_PROCESSING_DB_WRITE: Result = try_create_histogram( - "block_processing_db_write_times", + "beacon_block_processing_db_write_times", "Time spent writing a newly processed block and state to DB" ); pub static ref BLOCK_PROCESSING_FORK_CHOICE_REGISTER: Result = try_create_histogram( - "block_processing_fork_choice_register_times", + "beacon_block_processing_fork_choice_register_times", "Time spent registering the new block with fork choice (but not finding head)" ); pub static ref BLOCK_PROCESSING_FORK_CHOICE_FIND_HEAD: Result = try_create_histogram( - "block_processing_fork_choice_find_head_times", + "beacon_block_processing_fork_choice_find_head_times", "Time spent finding the new head after processing a new block" ); @@ -53,21 +53,21 @@ lazy_static! { * Block Production */ pub static ref BLOCK_PRODUCTION_REQUESTS: Result = try_create_int_counter( - "block_production_requests", + "beacon_block_production_requests", "Count of all block production requests" ); pub static ref BLOCK_PRODUCTION_SUCCESSES: Result = try_create_int_counter( - "block_production_successes", + "beacon_block_production_successes", "Count of blocks successfully produced." ); pub static ref BLOCK_PRODUCTION_TIMES: Result = - try_create_histogram("block_production_times", "Full runtime of block production"); + try_create_histogram("beacon_block_production_times", "Full runtime of block production"); /* * Block Statistics */ pub static ref OPERATIONS_PER_BLOCK_ATTESTATION: Result = try_create_histogram( - "operations_per_block_attestation", + "beacon_operations_per_block_attestation", "Number of attestations in a block" ); @@ -75,15 +75,15 @@ lazy_static! { * Attestation Processing */ pub static ref ATTESTATION_PROCESSING_REQUESTS: Result = try_create_int_counter( - "attestation_processing_requests", + "beacon_attestation_processing_requests", "Count of all attestations submitted for processing" ); pub static ref ATTESTATION_PROCESSING_SUCCESSES: Result = try_create_int_counter( - "attestation_processing_successes", + "beacon_attestation_processing_successes", "total_attestation_processing_successes" ); pub static ref ATTESTATION_PROCESSING_TIMES: Result = try_create_histogram( - "attestation_processing_times", + "beacon_attestation_processing_times", "Full runtime of attestation processing" ); @@ -91,15 +91,15 @@ lazy_static! { * Attestation Production */ pub static ref ATTESTATION_PRODUCTION_REQUESTS: Result = try_create_int_counter( - "attestation_production_requests", + "beacon_attestation_production_requests", "Count of all attestation production requests" ); pub static ref ATTESTATION_PRODUCTION_SUCCESSES: Result = try_create_int_counter( - "attestation_production_successes", + "beacon_attestation_production_successes", "Count of attestations processed without error" ); pub static ref ATTESTATION_PRODUCTION_TIMES: Result = try_create_histogram( - "attestation_production_times", + "beacon_attestation_production_times", "Full runtime of attestation production" ); @@ -107,31 +107,31 @@ lazy_static! { * Fork Choice */ pub static ref FORK_CHOICE_REQUESTS: Result = try_create_int_counter( - "fork_choice_requests", + "beacon_fork_choice_requests", "Count of occasions where fork choice has tried to find a head" ); pub static ref FORK_CHOICE_ERRORS: Result = try_create_int_counter( - "fork_choice_errors", + "beacon_fork_choice_errors", "Count of occasions where fork choice has returned an error when trying to find a head" ); pub static ref FORK_CHOICE_CHANGED_HEAD: Result = try_create_int_counter( - "fork_choice_changed_head", + "beacon_fork_choice_changed_head", "Count of occasions fork choice has found a new head" ); pub static ref FORK_CHOICE_REORG_COUNT: Result = try_create_int_counter( - "fork_choice_reorg_count", + "beacon_fork_choice_reorg_count", "Count of occasions fork choice has switched to a different chain" ); pub static ref FORK_CHOICE_TIMES: Result = - try_create_histogram("fork_choice_time", "Full runtime of fork choice"); + try_create_histogram("beacon_fork_choice_time", "Full runtime of fork choice"); pub static ref FORK_CHOICE_FIND_HEAD_TIMES: Result = - try_create_histogram("fork_choice_find_head_time", "Full runtime of fork choice find_head function"); + try_create_histogram("beacon_fork_choice_find_head_time", "Full runtime of fork choice find_head function"); pub static ref FORK_CHOICE_PROCESS_BLOCK_TIMES: Result = try_create_histogram( - "fork_choice_process_block_time", + "beacon_fork_choice_process_block_time", "Time taken to add a block and all attestations to fork choice" ); pub static ref FORK_CHOICE_PROCESS_ATTESTATION_TIMES: Result = try_create_histogram( - "fork_choice_process_attestation_time", + "beacon_fork_choice_process_attestation_time", "Time taken to add an attestation to fork choice" ); @@ -139,7 +139,7 @@ lazy_static! { * Persisting BeaconChain to disk */ pub static ref PERSIST_CHAIN: Result = - try_create_histogram("persist_chain", "Time taken to update the canonical head"); + try_create_histogram("beacon_persist_chain", "Time taken to update the canonical head"); } // Lazy-static is split so we don't reach the crate-level recursion limit. @@ -148,45 +148,45 @@ lazy_static! { * Slot Clock */ pub static ref PRESENT_SLOT: Result = - try_create_int_gauge("present_slot", "The present slot, according to system time"); + try_create_int_gauge("beacon_present_slot", "The present slot, according to system time"); pub static ref PRESENT_EPOCH: Result = - try_create_int_gauge("present_epoch", "The present epoch, according to system time"); + try_create_int_gauge("beacon_present_epoch", "The present epoch, according to system time"); /* * Chain Head */ pub static ref UPDATE_HEAD_TIMES: Result = - try_create_histogram("update_head_times", "Time taken to update the canonical head"); + try_create_histogram("beacon_update_head_times", "Time taken to update the canonical head"); pub static ref HEAD_STATE_SLOT: Result = - try_create_int_gauge("head_state_slot", "Slot of the block at the head of the chain"); + try_create_int_gauge("beacon_head_state_slot", "Slot of the block at the head of the chain"); pub static ref HEAD_STATE_ROOT: Result = - try_create_int_gauge("head_state_root", "Root of the block at the head of the chain"); + try_create_int_gauge("beacon_head_state_root", "Root of the block at the head of the chain"); pub static ref HEAD_STATE_LATEST_BLOCK_SLOT: Result = - try_create_int_gauge("head_state_latest_block_slot", "Latest block slot at the head of the chain"); + try_create_int_gauge("beacon_head_state_latest_block_slot", "Latest block slot at the head of the chain"); pub static ref HEAD_STATE_CURRENT_JUSTIFIED_ROOT: Result = - try_create_int_gauge("head_state_current_justified_root", "Current justified root at the head of the chain"); + try_create_int_gauge("beacon_head_state_current_justified_root", "Current justified root at the head of the chain"); pub static ref HEAD_STATE_CURRENT_JUSTIFIED_EPOCH: Result = - try_create_int_gauge("head_state_current_justified_epoch", "Current justified epoch at the head of the chain"); + try_create_int_gauge("beacon_head_state_current_justified_epoch", "Current justified epoch at the head of the chain"); pub static ref HEAD_STATE_PREVIOUS_JUSTIFIED_ROOT: Result = - try_create_int_gauge("head_state_previous_justified_root", "Previous justified root at the head of the chain"); + try_create_int_gauge("beacon_head_state_previous_justified_root", "Previous justified root at the head of the chain"); pub static ref HEAD_STATE_PREVIOUS_JUSTIFIED_EPOCH: Result = - try_create_int_gauge("head_state_previous_justified_epoch", "Previous justified epoch at the head of the chain"); + try_create_int_gauge("beacon_head_state_previous_justified_epoch", "Previous justified epoch at the head of the chain"); pub static ref HEAD_STATE_FINALIZED_ROOT: Result = - try_create_int_gauge("head_state_finalized_root", "Finalized root at the head of the chain"); + try_create_int_gauge("beacon_head_state_finalized_root", "Finalized root at the head of the chain"); pub static ref HEAD_STATE_FINALIZED_EPOCH: Result = - try_create_int_gauge("head_state_finalized_epoch", "Finalized epoch at the head of the chain"); + try_create_int_gauge("beacon_head_state_finalized_epoch", "Finalized epoch at the head of the chain"); pub static ref HEAD_STATE_TOTAL_VALIDATORS: Result = - try_create_int_gauge("head_state_total_validators", "Count of validators at the head of the chain"); + try_create_int_gauge("beacon_head_state_total_validators", "Count of validators at the head of the chain"); pub static ref HEAD_STATE_ACTIVE_VALIDATORS: Result = - try_create_int_gauge("head_state_active_validators", "Count of active validators at the head of the chain"); + try_create_int_gauge("beacon_head_state_active_validators", "Count of active validators at the head of the chain"); pub static ref HEAD_STATE_VALIDATOR_BALANCES: Result = - try_create_int_gauge("head_state_validator_balances", "Sum of all validator balances at the head of the chain"); + try_create_int_gauge("beacon_head_state_validator_balances", "Sum of all validator balances at the head of the chain"); pub static ref HEAD_STATE_SLASHED_VALIDATORS: Result = - try_create_int_gauge("head_state_slashed_validators", "Count of all slashed validators at the head of the chain"); + try_create_int_gauge("beacon_head_state_slashed_validators", "Count of all slashed validators at the head of the chain"); pub static ref HEAD_STATE_WITHDRAWN_VALIDATORS: Result = - try_create_int_gauge("head_state_withdrawn_validators", "Sum of all validator balances at the head of the chain"); + try_create_int_gauge("beacon_head_state_withdrawn_validators", "Sum of all validator balances at the head of the chain"); pub static ref HEAD_STATE_ETH1_DEPOSIT_INDEX: Result = - try_create_int_gauge("head_state_eth1_deposit_index", "Eth1 deposit index at the head of the chain"); + try_create_int_gauge("beacon_head_state_eth1_deposit_index", "Eth1 deposit index at the head of the chain"); } /// Scrape the `beacon_chain` for metrics that are not constantly updated (e.g., the present slot, From 0b4a8893a4a94826b723e39b29ea8aaf64bb8912 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Mon, 12 Aug 2019 13:49:09 +1000 Subject: [PATCH 13/62] Add more store metrics --- beacon_node/store/src/leveldb_store.rs | 22 ++++++++++++++++++-- beacon_node/store/src/metrics.rs | 26 +++++++++++++++++++++++- eth2/utils/lighthouse_metrics/src/lib.rs | 6 ++++++ 3 files changed, 51 insertions(+), 3 deletions(-) diff --git a/beacon_node/store/src/leveldb_store.rs b/beacon_node/store/src/leveldb_store.rs index 699861e3ae..a085d845a8 100644 --- a/beacon_node/store/src/leveldb_store.rs +++ b/beacon_node/store/src/leveldb_store.rs @@ -1,4 +1,5 @@ use super::*; +use crate::metrics; use db_key::Key; use leveldb::database::kv::KV; use leveldb::database::Database; @@ -62,15 +63,27 @@ impl Store for LevelDB { fn get_bytes(&self, col: &str, key: &[u8]) -> Result>, Error> { let column_key = Self::get_key_for_col(col, key); - self.db + metrics::inc_counter(&metrics::DISK_DB_READ_COUNT); + + let result = self + .db .get(self.read_options(), column_key) - .map_err(Into::into) + .map_err(Into::into); + + if let Ok(Some(bytes)) = &result { + metrics::inc_counter_by(&metrics::DISK_DB_READ_BYTES, bytes.len() as i64) + } + + result } /// Store some `value` in `column`, indexed with `key`. fn put_bytes(&self, col: &str, key: &[u8], val: &[u8]) -> Result<(), Error> { let column_key = Self::get_key_for_col(col, key); + metrics::inc_counter(&metrics::DISK_DB_WRITE_COUNT); + metrics::inc_counter_by(&metrics::DISK_DB_WRITE_BYTES, val.len() as i64); + self.db .put(self.write_options(), column_key, val) .map_err(Into::into) @@ -80,6 +93,8 @@ impl Store for LevelDB { fn key_exists(&self, col: &str, key: &[u8]) -> Result { let column_key = Self::get_key_for_col(col, key); + metrics::inc_counter(&metrics::DISK_DB_EXISTS_COUNT); + self.db .get(self.read_options(), column_key) .map_err(Into::into) @@ -89,6 +104,9 @@ impl Store for LevelDB { /// Removes `key` from `column`. fn key_delete(&self, col: &str, key: &[u8]) -> Result<(), Error> { let column_key = Self::get_key_for_col(col, key); + + metrics::inc_counter(&metrics::DISK_DB_DELETE_COUNT); + self.db .delete(self.write_options(), column_key) .map_err(Into::into) diff --git a/beacon_node/store/src/metrics.rs b/beacon_node/store/src/metrics.rs index b6a055f102..430e9c38e5 100644 --- a/beacon_node/store/src/metrics.rs +++ b/beacon_node/store/src/metrics.rs @@ -5,7 +5,31 @@ use std::path::PathBuf; lazy_static! { pub static ref DISK_DB_SIZE: Result = - try_create_int_gauge("database_size", "Size of the on-disk database (bytes)"); + try_create_int_gauge("store_disk_db_size", "Size of the on-disk database (bytes)"); + pub static ref DISK_DB_WRITE_BYTES: Result = try_create_int_counter( + "store_disk_db_write_bytes", + "Number of bytes attempted to be written to the on-disk DB" + ); + pub static ref DISK_DB_READ_BYTES: Result = try_create_int_counter( + "store_disk_db_read_bytes", + "Number of bytes read from the on-disk DB" + ); + pub static ref DISK_DB_READ_COUNT: Result = try_create_int_counter( + "store_disk_db_read_count", + "Total number of reads to the on-disk DB" + ); + pub static ref DISK_DB_WRITE_COUNT: Result = try_create_int_counter( + "store_disk_db_write_count", + "Total number of writes to the on-disk DB" + ); + pub static ref DISK_DB_EXISTS_COUNT: Result = try_create_int_counter( + "store_disk_db_exists_count", + "Total number of checks if a key is in the on-disk DB" + ); + pub static ref DISK_DB_DELETE_COUNT: Result = try_create_int_counter( + "store_disk_db_delete_count", + "Total number of deletions from the on-disk DB" + ); } /// Updates the global metrics registry with store-related information. diff --git a/eth2/utils/lighthouse_metrics/src/lib.rs b/eth2/utils/lighthouse_metrics/src/lib.rs index d55fcd3e21..a8656d0171 100644 --- a/eth2/utils/lighthouse_metrics/src/lib.rs +++ b/eth2/utils/lighthouse_metrics/src/lib.rs @@ -41,6 +41,12 @@ pub fn inc_counter(counter: &Result) { } } +pub fn inc_counter_by(counter: &Result, value: i64) { + if let Ok(counter) = counter { + counter.inc_by(value); + } +} + pub fn set_gauge(gauge: &Result, value: i64) { if let Ok(gauge) = gauge { gauge.set(value); From cac0e5c83284fb05cf9d465cb8e2fc8dc0f3e4aa Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Mon, 12 Aug 2019 14:16:20 +1000 Subject: [PATCH 14/62] Add basic metrics to libp2p --- beacon_node/eth2-libp2p/Cargo.toml | 2 ++ beacon_node/eth2-libp2p/src/discovery.rs | 4 ++++ beacon_node/eth2-libp2p/src/lib.rs | 4 ++++ beacon_node/eth2-libp2p/src/metrics.rs | 16 ++++++++++++++++ 4 files changed, 26 insertions(+) create mode 100644 beacon_node/eth2-libp2p/src/metrics.rs diff --git a/beacon_node/eth2-libp2p/Cargo.toml b/beacon_node/eth2-libp2p/Cargo.toml index 794b097128..006b895a1c 100644 --- a/beacon_node/eth2-libp2p/Cargo.toml +++ b/beacon_node/eth2-libp2p/Cargo.toml @@ -26,3 +26,5 @@ smallvec = "0.6.10" fnv = "1.0.6" unsigned-varint = "0.2.2" bytes = "0.4.12" +lazy_static = "1.3.0" +lighthouse_metrics = { path = "../../eth2/utils/lighthouse_metrics" } diff --git a/beacon_node/eth2-libp2p/src/discovery.rs b/beacon_node/eth2-libp2p/src/discovery.rs index 4c1794945d..d9f2f7465a 100644 --- a/beacon_node/eth2-libp2p/src/discovery.rs +++ b/beacon_node/eth2-libp2p/src/discovery.rs @@ -1,3 +1,4 @@ +use crate::metrics; use crate::{error, NetworkConfig}; /// This manages the discovery and management of peers. /// @@ -158,10 +159,12 @@ where } fn inject_connected(&mut self, peer_id: PeerId, _endpoint: ConnectedPoint) { + metrics::inc_counter(&metrics::PEER_CONNECT_COUNT); self.connected_peers.insert(peer_id); } fn inject_disconnected(&mut self, peer_id: &PeerId, _endpoint: ConnectedPoint) { + metrics::inc_counter(&metrics::PEER_DISCONNECT_COUNT); self.connected_peers.remove(peer_id); } @@ -217,6 +220,7 @@ where } Discv5Event::SocketUpdated(socket) => { info!(self.log, "Address updated"; "IP" => format!("{}",socket.ip())); + metrics::inc_counter(&metrics::ADDRESS_UPDATE_COUNT); let mut address = Multiaddr::from(socket.ip()); address.push(Protocol::Tcp(self.tcp_port)); let enr = self.discovery.local_enr(); diff --git a/beacon_node/eth2-libp2p/src/lib.rs b/beacon_node/eth2-libp2p/src/lib.rs index 54a4f2a998..33d5ba9ed9 100644 --- a/beacon_node/eth2-libp2p/src/lib.rs +++ b/beacon_node/eth2-libp2p/src/lib.rs @@ -2,10 +2,14 @@ /// all required libp2p functionality. /// /// This crate builds and manages the libp2p services required by the beacon node. +#[macro_use] +extern crate lazy_static; + pub mod behaviour; mod config; mod discovery; pub mod error; +mod metrics; pub mod rpc; mod service; diff --git a/beacon_node/eth2-libp2p/src/metrics.rs b/beacon_node/eth2-libp2p/src/metrics.rs new file mode 100644 index 0000000000..a47037669e --- /dev/null +++ b/beacon_node/eth2-libp2p/src/metrics.rs @@ -0,0 +1,16 @@ +pub use lighthouse_metrics::*; + +lazy_static! { + pub static ref ADDRESS_UPDATE_COUNT: Result = try_create_int_counter( + "libp2p_address_update_count", + "Count of libp2p socked updated events (when our view of our IP address has changed)" + ); + pub static ref PEER_CONNECT_COUNT: Result = try_create_int_counter( + "libp2p_peer_connect_count", + "Count of libp2p peer connect events (not the current number of connected peers)" + ); + pub static ref PEER_DISCONNECT_COUNT: Result = try_create_int_counter( + "libp2p_peer_disconnect_count", + "Count of libp2p peer disconnect events" + ); +} From af334b2cf0a6278c576b23d93d1748fdb4a51960 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Mon, 12 Aug 2019 14:30:46 +1000 Subject: [PATCH 15/62] Add metrics to HTTP server --- beacon_node/rest_api/Cargo.toml | 2 ++ beacon_node/rest_api/src/lib.rs | 17 +++++++++++++---- beacon_node/rest_api/src/metrics.rs | 21 +++++++++++++++++++++ 3 files changed, 36 insertions(+), 4 deletions(-) diff --git a/beacon_node/rest_api/Cargo.toml b/beacon_node/rest_api/Cargo.toml index 821d6c0ea1..100e680de8 100644 --- a/beacon_node/rest_api/Cargo.toml +++ b/beacon_node/rest_api/Cargo.toml @@ -24,3 +24,5 @@ futures = "0.1" exit-future = "0.1.3" tokio = "0.1.17" url = "2.0" +lazy_static = "1.3.0" +lighthouse_metrics = { path = "../../eth2/utils/lighthouse_metrics" } diff --git a/beacon_node/rest_api/src/lib.rs b/beacon_node/rest_api/src/lib.rs index fea67618ba..57019deea0 100644 --- a/beacon_node/rest_api/src/lib.rs +++ b/beacon_node/rest_api/src/lib.rs @@ -1,5 +1,6 @@ -extern crate futures; -extern crate hyper; +#[macro_use] +extern crate lazy_static; + mod beacon; mod config; mod helpers; @@ -100,6 +101,9 @@ pub fn start_server( // Create a simple handler for the router, inject our stateful objects into the request. service_fn_ok(move |mut req| { + metrics::inc_counter(&metrics::REQUEST_COUNT); + let timer = metrics::start_timer(&metrics::REQUEST_RESPONSE_TIME); + req.extensions_mut().insert::(log.clone()); req.extensions_mut() .insert::>>(beacon_chain.clone()); @@ -117,9 +121,10 @@ pub fn start_server( _ => Err(ApiError::MethodNotAllowed(path.clone())), }; - match result { + let response = match result { // Return the `hyper::Response`. Ok(response) => { + metrics::inc_counter(&metrics::SUCCESS_COUNT); slog::debug!(log, "Request successful: {:?}", path); response } @@ -128,7 +133,11 @@ pub fn start_server( slog::debug!(log, "Request failure: {:?}", path); e.into() } - } + }; + + metrics::stop_timer(timer); + + response }) }; diff --git a/beacon_node/rest_api/src/metrics.rs b/beacon_node/rest_api/src/metrics.rs index 0cd700c445..c0db810b60 100644 --- a/beacon_node/rest_api/src/metrics.rs +++ b/beacon_node/rest_api/src/metrics.rs @@ -4,7 +4,28 @@ use hyper::{Body, Request}; use prometheus::{Encoder, TextEncoder}; use std::sync::Arc; +pub use lighthouse_metrics::*; + +lazy_static! { + pub static ref REQUEST_RESPONSE_TIME: Result = try_create_histogram( + "http_server_request_response_time", + "Time taken to build a response to a HTTP request" + ); + pub static ref REQUEST_COUNT: Result = try_create_int_counter( + "http_server_request_count", + "Total count of HTTP requests received" + ); + pub static ref SUCCESS_COUNT: Result = try_create_int_counter( + "http_server_success_count", + "Total count of HTTP 200 responses sent" + ); +} + /// Returns the full set of Prometheus metrics for the Beacon Node application. +/// +/// # Note +/// +/// This is a HTTP handler method. pub fn get_prometheus(req: Request) -> ApiResult { let mut buffer = vec![]; let encoder = TextEncoder::new(); From 6a1e5f6d26c4dffd126faaedd970b4d8446a1ce3 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Mon, 12 Aug 2019 15:19:39 +1000 Subject: [PATCH 16/62] Remove old `http_server` crate --- Cargo.toml | 1 - beacon_node/client/Cargo.toml | 1 - beacon_node/client/src/config.rs | 4 - beacon_node/client/src/lib.rs | 22 --- beacon_node/http_server/Cargo.toml | 23 --- beacon_node/http_server/src/api.rs | 71 -------- beacon_node/http_server/src/key.rs | 33 ---- beacon_node/http_server/src/lib.rs | 145 ----------------- beacon_node/http_server/src/metrics.rs | 72 -------- .../http_server/src/metrics/local_metrics.rs | 154 ------------------ beacon_node/rest_api/src/config.rs | 2 +- docs/config_examples/beacon-node.toml | 10 +- 12 files changed, 2 insertions(+), 536 deletions(-) delete mode 100644 beacon_node/http_server/Cargo.toml delete mode 100644 beacon_node/http_server/src/api.rs delete mode 100644 beacon_node/http_server/src/key.rs delete mode 100644 beacon_node/http_server/src/lib.rs delete mode 100644 beacon_node/http_server/src/metrics.rs delete mode 100644 beacon_node/http_server/src/metrics/local_metrics.rs diff --git a/Cargo.toml b/Cargo.toml index 9b7b87a0d0..f087539e6a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,7 +26,6 @@ members = [ "beacon_node", "beacon_node/store", "beacon_node/client", - "beacon_node/http_server", "beacon_node/rest_api", "beacon_node/network", "beacon_node/eth2-libp2p", diff --git a/beacon_node/client/Cargo.toml b/beacon_node/client/Cargo.toml index 8c72fa4171..b13f175a9b 100644 --- a/beacon_node/client/Cargo.toml +++ b/beacon_node/client/Cargo.toml @@ -7,7 +7,6 @@ edition = "2018" [dependencies] beacon_chain = { path = "../beacon_chain" } network = { path = "../network" } -http_server = { path = "../http_server" } rpc = { path = "../rpc" } rest_api = { path = "../rest_api" } prometheus = "^0.6" diff --git a/beacon_node/client/src/config.rs b/beacon_node/client/src/config.rs index ee62b62815..fcc2cc7dac 100644 --- a/beacon_node/client/src/config.rs +++ b/beacon_node/client/src/config.rs @@ -1,6 +1,5 @@ use crate::Eth2Config; use clap::ArgMatches; -use http_server::HttpServerConfig; use network::NetworkConfig; use serde_derive::{Deserialize, Serialize}; use slog::{info, o, Drain}; @@ -25,7 +24,6 @@ pub struct Config { pub genesis_state: GenesisState, pub network: network::NetworkConfig, pub rpc: rpc::RPCConfig, - pub http: HttpServerConfig, pub rest_api: rest_api::ApiConfig, } @@ -59,7 +57,6 @@ impl Default for Config { db_name: "chain_db".to_string(), network: NetworkConfig::new(), rpc: rpc::RPCConfig::default(), - http: HttpServerConfig::default(), rest_api: rest_api::ApiConfig::default(), spec_constants: TESTNET_SPEC_CONSTANTS.into(), genesis_state: GenesisState::RecentGenesis { @@ -143,7 +140,6 @@ impl Config { self.network.apply_cli_args(args)?; self.rpc.apply_cli_args(args)?; - self.http.apply_cli_args(args)?; self.rest_api.apply_cli_args(args)?; if let Some(log_file) = args.value_of("logfile") { diff --git a/beacon_node/client/src/lib.rs b/beacon_node/client/src/lib.rs index c74787f606..5c37ac3e9c 100644 --- a/beacon_node/client/src/lib.rs +++ b/beacon_node/client/src/lib.rs @@ -10,7 +10,6 @@ use beacon_chain::BeaconChain; use exit_future::Signal; use futures::{future::Future, Stream}; use network::Service as NetworkService; -use prometheus::Registry; use slog::{error, info, o}; use slot_clock::SlotClock; use std::marker::PhantomData; @@ -36,8 +35,6 @@ pub struct Client { pub network: Arc>, /// Signal to terminate the RPC server. pub rpc_exit_signal: Option, - /// Signal to terminate the HTTP server. - pub http_exit_signal: Option, /// Signal to terminate the slot timer. pub slot_timer_exit_signal: Option, /// Signal to terminate the API @@ -60,7 +57,6 @@ where log: slog::Logger, executor: &TaskExecutor, ) -> error::Result { - let metrics_registry = Registry::new(); let store = Arc::new(store); let seconds_per_slot = eth2_config.spec.seconds_per_slot; @@ -119,23 +115,6 @@ where None }; - // Start the `http_server` service. - // - // Note: presently we are ignoring the config and _always_ starting a HTTP server. - let http_exit_signal = if client_config.http.enabled { - Some(http_server::start_service( - &client_config.http, - executor, - network_send, - beacon_chain.clone(), - client_config.db_path().expect("unable to read datadir"), - metrics_registry, - &log, - )) - } else { - None - }; - // Start the `rest_api` service let api_exit_signal = if client_config.rest_api.enabled { match rest_api::start_server( @@ -184,7 +163,6 @@ where Ok(Client { _client_config: client_config, beacon_chain, - http_exit_signal, rpc_exit_signal, slot_timer_exit_signal: Some(slot_timer_exit_signal), api_exit_signal, diff --git a/beacon_node/http_server/Cargo.toml b/beacon_node/http_server/Cargo.toml deleted file mode 100644 index e87ff29972..0000000000 --- a/beacon_node/http_server/Cargo.toml +++ /dev/null @@ -1,23 +0,0 @@ -[package] -name = "http_server" -version = "0.1.0" -authors = ["Paul Hauner "] -edition = "2018" - -[dependencies] -beacon_chain = { path = "../beacon_chain" } -iron = "^0.6" -router = "^0.6" -network = { path = "../network" } -types = { path = "../../eth2/types" } -slot_clock = { path = "../../eth2/utils/slot_clock" } -persistent = "^0.4" -prometheus = { version = "^0.6", features = ["process"] } -clap = "2.32.0" -futures = "0.1.23" -serde = "1.0" -serde_derive = "1.0" -serde_json = "1.0" -slog = { version = "^2.2.3" , features = ["max_level_trace"] } -tokio = "0.1.17" -exit-future = "0.1.4" diff --git a/beacon_node/http_server/src/api.rs b/beacon_node/http_server/src/api.rs deleted file mode 100644 index 8cb023b02c..0000000000 --- a/beacon_node/http_server/src/api.rs +++ /dev/null @@ -1,71 +0,0 @@ -use crate::{key::BeaconChainKey, map_persistent_err_to_500}; -use beacon_chain::{BeaconChain, BeaconChainTypes}; -use iron::prelude::*; -use iron::{ - headers::{CacheControl, CacheDirective, ContentType}, - status::Status, - AfterMiddleware, Handler, IronResult, Request, Response, -}; -use persistent::Read; -use router::Router; -use serde_json::json; -use std::sync::Arc; - -/// Yields a handler for the HTTP API. -pub fn build_handler( - beacon_chain: Arc>, -) -> impl Handler { - let mut router = Router::new(); - - router.get("/node/fork", handle_fork::, "fork"); - - let mut chain = Chain::new(router); - - // Insert `BeaconChain` so it may be accessed in a request. - chain.link(Read::>::both(beacon_chain.clone())); - // Set the content-type headers. - chain.link_after(SetJsonContentType); - // Set the cache headers. - chain.link_after(SetCacheDirectives); - - chain -} - -/// Sets the `cache-control` headers on _all_ responses, unless they are already set. -struct SetCacheDirectives; -impl AfterMiddleware for SetCacheDirectives { - fn after(&self, _req: &mut Request, mut resp: Response) -> IronResult { - // This is run for every requests, AFTER all handlers have been executed - if resp.headers.get::() == None { - resp.headers.set(CacheControl(vec![ - CacheDirective::NoCache, - CacheDirective::NoStore, - ])); - } - Ok(resp) - } -} - -/// Sets the `content-type` headers on _all_ responses, unless they are already set. -struct SetJsonContentType; -impl AfterMiddleware for SetJsonContentType { - fn after(&self, _req: &mut Request, mut resp: Response) -> IronResult { - if resp.headers.get::() == None { - resp.headers.set(ContentType::json()); - } - Ok(resp) - } -} - -fn handle_fork(req: &mut Request) -> IronResult { - let beacon_chain = req - .get::>>() - .map_err(map_persistent_err_to_500)?; - - let response = json!({ - "fork": beacon_chain.head().beacon_state.fork, - "network_id": beacon_chain.spec.network_id - }); - - Ok(Response::with((Status::Ok, response.to_string()))) -} diff --git a/beacon_node/http_server/src/key.rs b/beacon_node/http_server/src/key.rs deleted file mode 100644 index a69da6747f..0000000000 --- a/beacon_node/http_server/src/key.rs +++ /dev/null @@ -1,33 +0,0 @@ -use crate::metrics::LocalMetrics; -use beacon_chain::{BeaconChain, BeaconChainTypes}; -use iron::typemap::Key; -use prometheus::Registry; -use std::marker::PhantomData; -use std::path::PathBuf; -use std::sync::Arc; - -pub struct BeaconChainKey { - _phantom: PhantomData, -} - -impl Key for BeaconChainKey { - type Value = Arc>; -} - -pub struct MetricsRegistryKey; - -impl Key for MetricsRegistryKey { - type Value = Registry; -} - -pub struct LocalMetricsKey; - -impl Key for LocalMetricsKey { - type Value = LocalMetrics; -} - -pub struct DBPathKey; - -impl Key for DBPathKey { - type Value = PathBuf; -} diff --git a/beacon_node/http_server/src/lib.rs b/beacon_node/http_server/src/lib.rs deleted file mode 100644 index f1d006a5bc..0000000000 --- a/beacon_node/http_server/src/lib.rs +++ /dev/null @@ -1,145 +0,0 @@ -mod api; -mod key; -mod metrics; - -use beacon_chain::{BeaconChain, BeaconChainTypes}; -use clap::ArgMatches; -use futures::Future; -use iron::prelude::*; -use network::NetworkMessage; -use prometheus::Registry; -use router::Router; -use serde_derive::{Deserialize, Serialize}; -use slog::{info, o, warn}; -use std::path::PathBuf; -use std::sync::Arc; -use tokio::runtime::TaskExecutor; -use tokio::sync::mpsc; - -#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)] -pub struct HttpServerConfig { - pub enabled: bool, - pub listen_address: String, - pub listen_port: String, -} - -impl Default for HttpServerConfig { - fn default() -> Self { - Self { - enabled: false, - listen_address: "127.0.0.1".to_string(), - listen_port: "5052".to_string(), - } - } -} - -impl HttpServerConfig { - pub fn apply_cli_args(&mut self, args: &ArgMatches) -> Result<(), &'static str> { - if args.is_present("http") { - self.enabled = true; - } - - if let Some(listen_address) = args.value_of("http-address") { - self.listen_address = listen_address.to_string(); - } - - if let Some(listen_port) = args.value_of("http-port") { - self.listen_port = listen_port.to_string(); - } - - Ok(()) - } -} - -/// Build the `iron` HTTP server, defining the core routes. -pub fn create_iron_http_server( - beacon_chain: Arc>, - db_path: PathBuf, - metrics_registry: Registry, -) -> Iron { - let mut router = Router::new(); - - // A `GET` request to `/metrics` is handled by the `metrics` module. - router.get( - "/metrics", - metrics::build_handler(beacon_chain.clone(), db_path, metrics_registry), - "metrics", - ); - - // Any request to all other endpoints is handled by the `api` module. - router.any("/*", api::build_handler(beacon_chain.clone()), "api"); - - Iron::new(router) -} - -/// Start the HTTP service on the tokio `TaskExecutor`. -pub fn start_service( - config: &HttpServerConfig, - executor: &TaskExecutor, - _network_chan: mpsc::UnboundedSender, - beacon_chain: Arc>, - db_path: PathBuf, - metrics_registry: Registry, - log: &slog::Logger, -) -> exit_future::Signal { - let log = log.new(o!("Service"=>"HTTP")); - - // Create: - // - `shutdown_trigger` a one-shot to shut down this service. - // - `wait_for_shutdown` a future that will wait until someone calls shutdown. - let (shutdown_trigger, wait_for_shutdown) = exit_future::signal(); - - // Create an `iron` http, without starting it yet. - let iron = create_iron_http_server(beacon_chain, db_path, metrics_registry); - - // Create a HTTP server future. - // - // 1. Start the HTTP server - // 2. Build an exit future that will shutdown the server when requested. - // 3. Return the exit future, so the caller may shutdown the service when desired. - let http_service = { - let listen_address = format!("{}:{}", config.listen_address, config.listen_port); - // Start the HTTP server - let server_start_result = iron.http(listen_address.clone()); - - if server_start_result.is_ok() { - info!(log, "HTTP server running on {}", listen_address); - } else { - warn!(log, "HTTP server failed to start on {}", listen_address); - } - - // Build a future that will shutdown the HTTP server when the `shutdown_trigger` is - // triggered. - wait_for_shutdown.and_then(move |_| { - info!(log, "HTTP server shutting down"); - - if let Ok(mut server) = server_start_result { - // According to the documentation, `server.close()` "doesn't work" and the server - // keeps listening. - // - // It is being called anyway, because it seems like the right thing to do. If you - // know this has negative side-effects, please create an issue to discuss. - // - // See: https://docs.rs/iron/0.6.0/iron/struct.Listening.html#impl - match server.close() { - _ => (), - }; - } - info!(log, "HTTP server shutdown complete."); - Ok(()) - }) - }; - - // Attach the HTTP server to the executor. - executor.spawn(http_service); - - shutdown_trigger -} - -/// Helper function for mapping a failure to read state to a 500 server error. -fn map_persistent_err_to_500(e: persistent::PersistentError) -> iron::error::IronError { - iron::error::IronError { - error: Box::new(e), - response: iron::Response::with(iron::status::Status::InternalServerError), - } -} diff --git a/beacon_node/http_server/src/metrics.rs b/beacon_node/http_server/src/metrics.rs deleted file mode 100644 index 1b1ed1f3d4..0000000000 --- a/beacon_node/http_server/src/metrics.rs +++ /dev/null @@ -1,72 +0,0 @@ -use crate::{ - key::{BeaconChainKey, DBPathKey, LocalMetricsKey, MetricsRegistryKey}, - map_persistent_err_to_500, -}; -use beacon_chain::{BeaconChain, BeaconChainTypes}; -use iron::prelude::*; -use iron::{status::Status, Handler, IronResult, Request, Response}; -use persistent::Read; -use prometheus::{Encoder, Registry, TextEncoder}; -use std::path::PathBuf; -use std::sync::Arc; - -pub use local_metrics::LocalMetrics; - -mod local_metrics; - -/// Yields a handler for the metrics endpoint. -pub fn build_handler( - beacon_chain: Arc>, - db_path: PathBuf, - metrics_registry: Registry, -) -> impl Handler { - let mut chain = Chain::new(handle_metrics::); - - let local_metrics = LocalMetrics::new().unwrap(); - local_metrics.register(&metrics_registry).unwrap(); - - chain.link(Read::>::both(beacon_chain)); - chain.link(Read::::both(metrics_registry)); - chain.link(Read::::both(local_metrics)); - chain.link(Read::::both(db_path)); - - chain -} - -/// Handle a request for Prometheus metrics. -/// -/// Returns a text string containing all metrics. -fn handle_metrics(req: &mut Request) -> IronResult { - let beacon_chain = req - .get::>>() - .map_err(map_persistent_err_to_500)?; - - let r = req - .get::>() - .map_err(map_persistent_err_to_500)?; - - let local_metrics = req - .get::>() - .map_err(map_persistent_err_to_500)?; - - let db_path = req - .get::>() - .map_err(map_persistent_err_to_500)?; - - // Update metrics that are calculated on each scrape. - local_metrics.update(&beacon_chain, &db_path); - - let mut buffer = vec![]; - let encoder = TextEncoder::new(); - - // Gather `DEFAULT_REGISTRY` metrics. - encoder.encode(&prometheus::gather(), &mut buffer).unwrap(); - - // Gather metrics from our registry. - let metric_families = r.gather(); - encoder.encode(&metric_families, &mut buffer).unwrap(); - - let prom_string = String::from_utf8(buffer).unwrap(); - - Ok(Response::with((Status::Ok, prom_string))) -} diff --git a/beacon_node/http_server/src/metrics/local_metrics.rs b/beacon_node/http_server/src/metrics/local_metrics.rs deleted file mode 100644 index b342cca81c..0000000000 --- a/beacon_node/http_server/src/metrics/local_metrics.rs +++ /dev/null @@ -1,154 +0,0 @@ -use beacon_chain::{BeaconChain, BeaconChainTypes}; -use prometheus::{IntGauge, Opts, Registry}; -use slot_clock::SlotClock; -use std::fs; -use std::path::PathBuf; -use types::{EthSpec, Slot}; - -// If set to `true` will iterate and sum the balances of all validators in the state for each -// scrape. -const SHOULD_SUM_VALIDATOR_BALANCES: bool = true; - -pub struct LocalMetrics { - present_slot: IntGauge, - present_epoch: IntGauge, - best_slot: IntGauge, - best_beacon_block_root: IntGauge, - justified_beacon_block_root: IntGauge, - finalized_beacon_block_root: IntGauge, - validator_count: IntGauge, - justified_epoch: IntGauge, - finalized_epoch: IntGauge, - validator_balances_sum: IntGauge, - database_size: IntGauge, -} - -impl LocalMetrics { - /// Create a new instance. - pub fn new() -> Result { - Ok(Self { - present_slot: { - let opts = Opts::new("present_slot", "slot_at_time_of_scrape"); - IntGauge::with_opts(opts)? - }, - present_epoch: { - let opts = Opts::new("present_epoch", "epoch_at_time_of_scrape"); - IntGauge::with_opts(opts)? - }, - best_slot: { - let opts = Opts::new("best_slot", "slot_of_block_at_chain_head"); - IntGauge::with_opts(opts)? - }, - best_beacon_block_root: { - let opts = Opts::new("best_beacon_block_root", "root_of_block_at_chain_head"); - IntGauge::with_opts(opts)? - }, - justified_beacon_block_root: { - let opts = Opts::new( - "justified_beacon_block_root", - "root_of_block_at_justified_head", - ); - IntGauge::with_opts(opts)? - }, - finalized_beacon_block_root: { - let opts = Opts::new( - "finalized_beacon_block_root", - "root_of_block_at_finalized_head", - ); - IntGauge::with_opts(opts)? - }, - validator_count: { - let opts = Opts::new("validator_count", "number_of_validators"); - IntGauge::with_opts(opts)? - }, - justified_epoch: { - let opts = Opts::new("justified_epoch", "state_justified_epoch"); - IntGauge::with_opts(opts)? - }, - finalized_epoch: { - let opts = Opts::new("finalized_epoch", "state_finalized_epoch"); - IntGauge::with_opts(opts)? - }, - validator_balances_sum: { - let opts = Opts::new("validator_balances_sum", "sum_of_all_validator_balances"); - IntGauge::with_opts(opts)? - }, - database_size: { - let opts = Opts::new("database_size", "size_of_on_disk_db_in_mb"); - IntGauge::with_opts(opts)? - }, - }) - } - - /// Registry this instance with the `registry`. - pub fn register(&self, registry: &Registry) -> Result<(), prometheus::Error> { - registry.register(Box::new(self.present_slot.clone()))?; - registry.register(Box::new(self.present_epoch.clone()))?; - registry.register(Box::new(self.best_slot.clone()))?; - registry.register(Box::new(self.best_beacon_block_root.clone()))?; - registry.register(Box::new(self.justified_beacon_block_root.clone()))?; - registry.register(Box::new(self.finalized_beacon_block_root.clone()))?; - registry.register(Box::new(self.validator_count.clone()))?; - registry.register(Box::new(self.finalized_epoch.clone()))?; - registry.register(Box::new(self.justified_epoch.clone()))?; - registry.register(Box::new(self.validator_balances_sum.clone()))?; - registry.register(Box::new(self.database_size.clone()))?; - - Ok(()) - } - - /// Update the metrics in `self` to the latest values. - pub fn update(&self, beacon_chain: &BeaconChain, db_path: &PathBuf) { - let state = &beacon_chain.head().beacon_state; - - let present_slot = beacon_chain - .slot_clock - .present_slot() - .unwrap_or_else(|_| None) - .unwrap_or_else(|| Slot::new(0)); - self.present_slot.set(present_slot.as_u64() as i64); - self.present_epoch - .set(present_slot.epoch(T::EthSpec::slots_per_epoch()).as_u64() as i64); - - self.best_slot.set(state.slot.as_u64() as i64); - self.best_beacon_block_root - .set(beacon_chain.head().beacon_block_root.to_low_u64_le() as i64); - self.justified_beacon_block_root.set( - beacon_chain - .head() - .beacon_state - .current_justified_checkpoint - .root - .to_low_u64_le() as i64, - ); - self.finalized_beacon_block_root.set( - beacon_chain - .head() - .beacon_state - .finalized_checkpoint - .root - .to_low_u64_le() as i64, - ); - self.validator_count.set(state.validators.len() as i64); - self.justified_epoch - .set(state.current_justified_checkpoint.epoch.as_u64() as i64); - self.finalized_epoch - .set(state.finalized_checkpoint.epoch.as_u64() as i64); - if SHOULD_SUM_VALIDATOR_BALANCES { - self.validator_balances_sum - .set(state.balances.iter().sum::() as i64); - } - let db_size = if let Ok(iter) = fs::read_dir(db_path) { - iter.filter_map(Result::ok) - .map(size_of_dir_entry) - .fold(0_u64, |sum, val| sum + val) - } else { - 0 - }; - self.database_size.set(db_size as i64); - } -} - -fn size_of_dir_entry(dir: fs::DirEntry) -> u64 { - dir.metadata().map(|m| m.len()).unwrap_or(0) -} diff --git a/beacon_node/rest_api/src/config.rs b/beacon_node/rest_api/src/config.rs index c4a9c738a0..90ac0821b1 100644 --- a/beacon_node/rest_api/src/config.rs +++ b/beacon_node/rest_api/src/config.rs @@ -18,7 +18,7 @@ impl Default for Config { Config { enabled: true, // rest_api enabled by default listen_address: Ipv4Addr::new(127, 0, 0, 1), - port: 1248, + port: 5052, } } } diff --git a/docs/config_examples/beacon-node.toml b/docs/config_examples/beacon-node.toml index 3c9f8b613c..f0863934e7 100644 --- a/docs/config_examples/beacon-node.toml +++ b/docs/config_examples/beacon-node.toml @@ -78,14 +78,6 @@ enabled = false listen_address = "127.0.0.1" port = 5051 -# -# Legacy HTTP server configuration. To be removed. -# -[http] -enabled = false -listen_address = "127.0.0.1" -listen_port = "5052" - # # RESTful HTTP API server configuration. # @@ -95,4 +87,4 @@ enabled = true # The listen port for the HTTP server. listen_address = "127.0.0.1" # The listen port for the HTTP server. -port = 1248 +port = 5052 From 95a320817e0c724b6d4ed64b9bf2fefacc918aa6 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Mon, 12 Aug 2019 15:40:51 +1000 Subject: [PATCH 17/62] Update metrics names to be more like standard --- beacon_node/beacon_chain/src/metrics.rs | 65 +++++++++++++------------ beacon_node/rest_api/src/metrics.rs | 6 +-- beacon_node/store/src/metrics.rs | 12 ++--- 3 files changed, 43 insertions(+), 40 deletions(-) diff --git a/beacon_node/beacon_chain/src/metrics.rs b/beacon_node/beacon_chain/src/metrics.rs index 227f1090f7..00a3e5eb2e 100644 --- a/beacon_node/beacon_chain/src/metrics.rs +++ b/beacon_node/beacon_chain/src/metrics.rs @@ -7,45 +7,45 @@ lazy_static! { * Block Processing */ pub static ref BLOCK_PROCESSING_REQUESTS: Result = try_create_int_counter( - "beacon_block_processing_requests", + "beacon_block_processing_requests_total", "Count of blocks submitted for processing" ); pub static ref BLOCK_PROCESSING_SUCCESSES: Result = try_create_int_counter( - "beacon_block_processing_successes", + "beacon_block_processing_successes_total", "Count of blocks processed without error" ); pub static ref BLOCK_PROCESSING_TIMES: Result = - try_create_histogram("block_processing_times", "Full runtime of block processing"); + try_create_histogram("block_processing_seconds", "Full runtime of block processing"); pub static ref BLOCK_PROCESSING_DB_READ: Result = try_create_histogram( - "beacon_block_processing_db_read_times", + "beacon_block_processing_db_read_seconds", "Time spent loading block and state from DB for block processing" ); pub static ref BLOCK_PROCESSING_CATCHUP_STATE: Result = try_create_histogram( - "beacon_block_processing_catch_up_state_times", + "beacon_block_processing_catch_up_state_seconds", "Time spent skipping slots on a state before processing a block." ); pub static ref BLOCK_PROCESSING_COMMITTEE: Result = try_create_histogram( - "beacon_block_processing_committee_building_times", + "beacon_block_processing_committee_building_seconds", "Time spent building/obtaining committees for block processing." ); pub static ref BLOCK_PROCESSING_CORE: Result = try_create_histogram( - "beacon_block_processing_core_times", + "beacon_block_processing_core_seconds", "Time spent doing the core per_block_processing state processing." ); pub static ref BLOCK_PROCESSING_STATE_ROOT: Result = try_create_histogram( - "beacon_block_processing_state_root_times", + "beacon_block_processing_state_root_seconds", "Time spent calculating the state root when processing a block." ); pub static ref BLOCK_PROCESSING_DB_WRITE: Result = try_create_histogram( - "beacon_block_processing_db_write_times", + "beacon_block_processing_db_write_seconds", "Time spent writing a newly processed block and state to DB" ); pub static ref BLOCK_PROCESSING_FORK_CHOICE_REGISTER: Result = try_create_histogram( - "beacon_block_processing_fork_choice_register_times", + "beacon_block_processing_fork_choice_register_seconds", "Time spent registering the new block with fork choice (but not finding head)" ); pub static ref BLOCK_PROCESSING_FORK_CHOICE_FIND_HEAD: Result = try_create_histogram( - "beacon_block_processing_fork_choice_find_head_times", + "beacon_block_processing_fork_choice_find_head_seconds", "Time spent finding the new head after processing a new block" ); @@ -53,21 +53,21 @@ lazy_static! { * Block Production */ pub static ref BLOCK_PRODUCTION_REQUESTS: Result = try_create_int_counter( - "beacon_block_production_requests", + "beacon_block_production_requests_total", "Count of all block production requests" ); pub static ref BLOCK_PRODUCTION_SUCCESSES: Result = try_create_int_counter( - "beacon_block_production_successes", + "beacon_block_production_successes_total", "Count of blocks successfully produced." ); pub static ref BLOCK_PRODUCTION_TIMES: Result = - try_create_histogram("beacon_block_production_times", "Full runtime of block production"); + try_create_histogram("beacon_block_production_seconds", "Full runtime of block production"); /* * Block Statistics */ pub static ref OPERATIONS_PER_BLOCK_ATTESTATION: Result = try_create_histogram( - "beacon_operations_per_block_attestation", + "beacon_operations_per_block_attestation_total", "Number of attestations in a block" ); @@ -75,15 +75,15 @@ lazy_static! { * Attestation Processing */ pub static ref ATTESTATION_PROCESSING_REQUESTS: Result = try_create_int_counter( - "beacon_attestation_processing_requests", + "beacon_attestation_processing_requests_total", "Count of all attestations submitted for processing" ); pub static ref ATTESTATION_PROCESSING_SUCCESSES: Result = try_create_int_counter( - "beacon_attestation_processing_successes", + "beacon_attestation_processing_successes_total", "total_attestation_processing_successes" ); pub static ref ATTESTATION_PROCESSING_TIMES: Result = try_create_histogram( - "beacon_attestation_processing_times", + "beacon_attestation_processing_seconds", "Full runtime of attestation processing" ); @@ -91,15 +91,15 @@ lazy_static! { * Attestation Production */ pub static ref ATTESTATION_PRODUCTION_REQUESTS: Result = try_create_int_counter( - "beacon_attestation_production_requests", + "beacon_attestation_production_requests_total", "Count of all attestation production requests" ); pub static ref ATTESTATION_PRODUCTION_SUCCESSES: Result = try_create_int_counter( - "beacon_attestation_production_successes", + "beacon_attestation_production_successes_total", "Count of attestations processed without error" ); pub static ref ATTESTATION_PRODUCTION_TIMES: Result = try_create_histogram( - "beacon_attestation_production_times", + "beacon_attestation_production_seconds", "Full runtime of attestation production" ); @@ -107,19 +107,19 @@ lazy_static! { * Fork Choice */ pub static ref FORK_CHOICE_REQUESTS: Result = try_create_int_counter( - "beacon_fork_choice_requests", + "beacon_fork_choice_requests_total", "Count of occasions where fork choice has tried to find a head" ); pub static ref FORK_CHOICE_ERRORS: Result = try_create_int_counter( - "beacon_fork_choice_errors", + "beacon_fork_choice_errors_total", "Count of occasions where fork choice has returned an error when trying to find a head" ); pub static ref FORK_CHOICE_CHANGED_HEAD: Result = try_create_int_counter( - "beacon_fork_choice_changed_head", + "beacon_fork_choice_changed_head_total", "Count of occasions fork choice has found a new head" ); pub static ref FORK_CHOICE_REORG_COUNT: Result = try_create_int_counter( - "beacon_fork_choice_reorg_count", + "beacon_fork_choice_reorg_total", "Count of occasions fork choice has switched to a different chain" ); pub static ref FORK_CHOICE_TIMES: Result = @@ -156,7 +156,7 @@ lazy_static! { * Chain Head */ pub static ref UPDATE_HEAD_TIMES: Result = - try_create_histogram("beacon_update_head_times", "Time taken to update the canonical head"); + try_create_histogram("beacon_update_head_seconds", "Time taken to update the canonical head"); pub static ref HEAD_STATE_SLOT: Result = try_create_int_gauge("beacon_head_state_slot", "Slot of the block at the head of the chain"); pub static ref HEAD_STATE_ROOT: Result = @@ -175,16 +175,18 @@ lazy_static! { try_create_int_gauge("beacon_head_state_finalized_root", "Finalized root at the head of the chain"); pub static ref HEAD_STATE_FINALIZED_EPOCH: Result = try_create_int_gauge("beacon_head_state_finalized_epoch", "Finalized epoch at the head of the chain"); + pub static ref HEAD_STATE_SHARDS: Result = + try_create_int_gauge("beacon_head_state_shard_total", "Count of shards in the beacon chain"); pub static ref HEAD_STATE_TOTAL_VALIDATORS: Result = - try_create_int_gauge("beacon_head_state_total_validators", "Count of validators at the head of the chain"); + try_create_int_gauge("beacon_head_state_total_validators_total", "Count of validators at the head of the chain"); pub static ref HEAD_STATE_ACTIVE_VALIDATORS: Result = - try_create_int_gauge("beacon_head_state_active_validators", "Count of active validators at the head of the chain"); + try_create_int_gauge("beacon_head_state_active_validators_total", "Count of active validators at the head of the chain"); pub static ref HEAD_STATE_VALIDATOR_BALANCES: Result = - try_create_int_gauge("beacon_head_state_validator_balances", "Sum of all validator balances at the head of the chain"); + try_create_int_gauge("beacon_head_state_validator_balances_total", "Sum of all validator balances at the head of the chain"); pub static ref HEAD_STATE_SLASHED_VALIDATORS: Result = - try_create_int_gauge("beacon_head_state_slashed_validators", "Count of all slashed validators at the head of the chain"); + try_create_int_gauge("beacon_head_state_slashed_validators_total", "Count of all slashed validators at the head of the chain"); pub static ref HEAD_STATE_WITHDRAWN_VALIDATORS: Result = - try_create_int_gauge("beacon_head_state_withdrawn_validators", "Sum of all validator balances at the head of the chain"); + try_create_int_gauge("beacon_head_state_withdrawn_validators_total", "Sum of all validator balances at the head of the chain"); pub static ref HEAD_STATE_ETH1_DEPOSIT_INDEX: Result = try_create_int_gauge("beacon_head_state_eth1_deposit_index", "Eth1 deposit index at the head of the chain"); } @@ -242,6 +244,7 @@ fn scrape_head_state(state: &BeaconState, state &HEAD_STATE_FINALIZED_EPOCH, state.finalized_checkpoint.epoch, ); + set_gauge_by_usize(&HEAD_STATE_SHARDS, state.previous_crosslinks.len()); set_gauge_by_usize(&HEAD_STATE_TOTAL_VALIDATORS, state.validators.len()); set_gauge_by_u64( &HEAD_STATE_VALIDATOR_BALANCES, diff --git a/beacon_node/rest_api/src/metrics.rs b/beacon_node/rest_api/src/metrics.rs index c0db810b60..b0f1c1b980 100644 --- a/beacon_node/rest_api/src/metrics.rs +++ b/beacon_node/rest_api/src/metrics.rs @@ -8,15 +8,15 @@ pub use lighthouse_metrics::*; lazy_static! { pub static ref REQUEST_RESPONSE_TIME: Result = try_create_histogram( - "http_server_request_response_time", + "http_server_request_duration_seconds", "Time taken to build a response to a HTTP request" ); pub static ref REQUEST_COUNT: Result = try_create_int_counter( - "http_server_request_count", + "http_server_request_total", "Total count of HTTP requests received" ); pub static ref SUCCESS_COUNT: Result = try_create_int_counter( - "http_server_success_count", + "http_server_success_total", "Total count of HTTP 200 responses sent" ); } diff --git a/beacon_node/store/src/metrics.rs b/beacon_node/store/src/metrics.rs index 430e9c38e5..30cbb878b6 100644 --- a/beacon_node/store/src/metrics.rs +++ b/beacon_node/store/src/metrics.rs @@ -7,27 +7,27 @@ lazy_static! { pub static ref DISK_DB_SIZE: Result = try_create_int_gauge("store_disk_db_size", "Size of the on-disk database (bytes)"); pub static ref DISK_DB_WRITE_BYTES: Result = try_create_int_counter( - "store_disk_db_write_bytes", + "store_disk_db_write_bytes_total", "Number of bytes attempted to be written to the on-disk DB" ); pub static ref DISK_DB_READ_BYTES: Result = try_create_int_counter( - "store_disk_db_read_bytes", + "store_disk_db_read_bytes_total", "Number of bytes read from the on-disk DB" ); pub static ref DISK_DB_READ_COUNT: Result = try_create_int_counter( - "store_disk_db_read_count", + "store_disk_db_read_count_total", "Total number of reads to the on-disk DB" ); pub static ref DISK_DB_WRITE_COUNT: Result = try_create_int_counter( - "store_disk_db_write_count", + "store_disk_db_write_count_total", "Total number of writes to the on-disk DB" ); pub static ref DISK_DB_EXISTS_COUNT: Result = try_create_int_counter( - "store_disk_db_exists_count", + "store_disk_db_exists_count_total", "Total number of checks if a key is in the on-disk DB" ); pub static ref DISK_DB_DELETE_COUNT: Result = try_create_int_counter( - "store_disk_db_delete_count", + "store_disk_db_delete_count_total", "Total number of deletions from the on-disk DB" ); } From d7c546844cfaf58ab63739a181fbf73c924fb4d5 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Mon, 12 Aug 2019 17:44:47 +1000 Subject: [PATCH 18/62] Fix broken beacon chain metrics, add slot clock metrics --- beacon_node/beacon_chain/src/lib.rs | 1 + beacon_node/beacon_chain/src/metrics.rs | 28 +----------------- beacon_node/rest_api/Cargo.toml | 1 + beacon_node/rest_api/src/metrics.rs | 17 +++++++++++ eth2/utils/slot_clock/Cargo.toml | 2 ++ eth2/utils/slot_clock/src/lib.rs | 10 ++++++- eth2/utils/slot_clock/src/metrics.rs | 29 +++++++++++++++++++ .../slot_clock/src/system_time_slot_clock.rs | 4 +++ .../slot_clock/src/testing_slot_clock.rs | 4 +++ 9 files changed, 68 insertions(+), 28 deletions(-) create mode 100644 eth2/utils/slot_clock/src/metrics.rs diff --git a/beacon_node/beacon_chain/src/lib.rs b/beacon_node/beacon_chain/src/lib.rs index 1262bc5372..cc7725dd83 100644 --- a/beacon_node/beacon_chain/src/lib.rs +++ b/beacon_node/beacon_chain/src/lib.rs @@ -1,3 +1,4 @@ +#![recursion_limit = "128"] // For lazy-static #[macro_use] extern crate lazy_static; diff --git a/beacon_node/beacon_chain/src/metrics.rs b/beacon_node/beacon_chain/src/metrics.rs index 00a3e5eb2e..a4b36cd375 100644 --- a/beacon_node/beacon_chain/src/metrics.rs +++ b/beacon_node/beacon_chain/src/metrics.rs @@ -1,6 +1,6 @@ use crate::{BeaconChain, BeaconChainTypes}; pub use lighthouse_metrics::*; -use types::{BeaconState, Epoch, EthSpec, Hash256, Slot}; +use types::{BeaconState, Epoch, Hash256, Slot}; lazy_static! { /* @@ -140,17 +140,6 @@ lazy_static! { */ pub static ref PERSIST_CHAIN: Result = try_create_histogram("beacon_persist_chain", "Time taken to update the canonical head"); -} - -// Lazy-static is split so we don't reach the crate-level recursion limit. -lazy_static! { - /* - * Slot Clock - */ - pub static ref PRESENT_SLOT: Result = - try_create_int_gauge("beacon_present_slot", "The present slot, according to system time"); - pub static ref PRESENT_EPOCH: Result = - try_create_int_gauge("beacon_present_epoch", "The present epoch, according to system time"); /* * Chain Head @@ -194,21 +183,6 @@ lazy_static! { /// Scrape the `beacon_chain` for metrics that are not constantly updated (e.g., the present slot, /// head state info, etc) and update the Prometheus `DEFAULT_REGISTRY`. pub fn scrape_for_metrics(beacon_chain: &BeaconChain) { - set_gauge_by_slot( - &PRESENT_SLOT, - beacon_chain - .read_slot_clock() - .unwrap_or_else(|| Slot::new(0)), - ); - - set_gauge_by_epoch( - &PRESENT_EPOCH, - beacon_chain - .read_slot_clock() - .map(|s| s.epoch(T::EthSpec::slots_per_epoch())) - .unwrap_or_else(|| Epoch::new(0)), - ); - scrape_head_state::( &beacon_chain.head().beacon_state, beacon_chain.head().beacon_state_root, diff --git a/beacon_node/rest_api/Cargo.toml b/beacon_node/rest_api/Cargo.toml index 100e680de8..c7026014c4 100644 --- a/beacon_node/rest_api/Cargo.toml +++ b/beacon_node/rest_api/Cargo.toml @@ -26,3 +26,4 @@ tokio = "0.1.17" url = "2.0" lazy_static = "1.3.0" lighthouse_metrics = { path = "../../eth2/utils/lighthouse_metrics" } +slot_clock = { path = "../../eth2/utils/slot_clock" } diff --git a/beacon_node/rest_api/src/metrics.rs b/beacon_node/rest_api/src/metrics.rs index b0f1c1b980..f0ccef5f8d 100644 --- a/beacon_node/rest_api/src/metrics.rs +++ b/beacon_node/rest_api/src/metrics.rs @@ -39,6 +39,23 @@ pub fn get_prometheus(req: Request) -> ApiR .get::() .ok_or_else(|| ApiError::ServerError("DBPath extension missing".to_string()))?; + // There are two categories of metrics: + // + // - Dynamically updated: things like histograms and event counters that are updated on the + // fly. + // - Statically updated: things which are only updated at the time of the scrape (used where we + // can avoid cluttering up code with metrics calls). + // + // The `prometheus` crate has a `DEFAULT_REGISTRY` global singleton (via `lazy_static`) which + // keeps the state of all the metrics. Dynamically updated things will already be up-to-date in + // the registry (because they update themselves) however statically updated things need to be + // "scraped". + // + // We proceed by, first updating all the static metrics using `scrape_for_metrics(..)`. Then, + // using `prometheus::gather(..)` to collect the global `DEFAULT_REGISTRY` metrics into a + // string that can be returned via HTTP. + + slot_clock::scrape_for_metrics::(&beacon_chain.slot_clock); store::scrape_for_metrics(&db_path); beacon_chain::scrape_for_metrics(&beacon_chain); diff --git a/eth2/utils/slot_clock/Cargo.toml b/eth2/utils/slot_clock/Cargo.toml index 31a4357251..c4b9df5edd 100644 --- a/eth2/utils/slot_clock/Cargo.toml +++ b/eth2/utils/slot_clock/Cargo.toml @@ -6,3 +6,5 @@ edition = "2018" [dependencies] types = { path = "../../types" } +lazy_static = "1.3.0" +lighthouse_metrics = { path = "../lighthouse_metrics" } diff --git a/eth2/utils/slot_clock/src/lib.rs b/eth2/utils/slot_clock/src/lib.rs index 7b86684fa4..871743c9e6 100644 --- a/eth2/utils/slot_clock/src/lib.rs +++ b/eth2/utils/slot_clock/src/lib.rs @@ -1,9 +1,15 @@ +#[macro_use] +extern crate lazy_static; + +mod metrics; mod system_time_slot_clock; mod testing_slot_clock; +use std::time::Duration; + pub use crate::system_time_slot_clock::{Error as SystemTimeSlotClockError, SystemTimeSlotClock}; pub use crate::testing_slot_clock::{Error as TestingSlotClockError, TestingSlotClock}; -use std::time::Duration; +pub use metrics::scrape_for_metrics; pub use types::Slot; pub trait SlotClock: Send + Sync + Sized { @@ -17,4 +23,6 @@ pub trait SlotClock: Send + Sync + Sized { fn present_slot(&self) -> Result, Self::Error>; fn duration_to_next_slot(&self) -> Result, Self::Error>; + + fn slot_duration_millis(&self) -> u64; } diff --git a/eth2/utils/slot_clock/src/metrics.rs b/eth2/utils/slot_clock/src/metrics.rs new file mode 100644 index 0000000000..a9153a10ca --- /dev/null +++ b/eth2/utils/slot_clock/src/metrics.rs @@ -0,0 +1,29 @@ +use crate::SlotClock; +pub use lighthouse_metrics::*; +use types::{EthSpec, Slot}; + +lazy_static! { + pub static ref PRESENT_SLOT: Result = + try_create_int_gauge("slotclock_present_slot", "The present wall-clock slot"); + pub static ref PRESENT_EPOCH: Result = + try_create_int_gauge("slotclock_present_epoch", "The present wall-clock epoch"); + pub static ref MILLISECONDS_PER_SLOT: Result = try_create_int_gauge( + "slotclock_slot_time_milliseconds", + "The duration in milliseconds between each slot" + ); +} + +/// Update the global metrics `DEFAULT_REGISTRY` with info from the slot clock. +pub fn scrape_for_metrics(clock: &U) { + let present_slot = match clock.present_slot() { + Ok(Some(slot)) => slot, + _ => Slot::new(0), + }; + + set_gauge(&PRESENT_SLOT, present_slot.as_u64() as i64); + set_gauge( + &PRESENT_EPOCH, + present_slot.epoch(T::slots_per_epoch()).as_u64() as i64, + ); + set_gauge(&MILLISECONDS_PER_SLOT, clock.slot_duration_millis() as i64); +} diff --git a/eth2/utils/slot_clock/src/system_time_slot_clock.rs b/eth2/utils/slot_clock/src/system_time_slot_clock.rs index 7c184b02bf..c493a8be83 100644 --- a/eth2/utils/slot_clock/src/system_time_slot_clock.rs +++ b/eth2/utils/slot_clock/src/system_time_slot_clock.rs @@ -52,6 +52,10 @@ impl SlotClock for SystemTimeSlotClock { fn duration_to_next_slot(&self) -> Result, Error> { duration_to_next_slot(self.genesis_seconds, self.slot_duration_seconds) } + + fn slot_duration_millis(&self) -> u64 { + self.slot_duration_seconds * 1000 + } } impl From for Error { diff --git a/eth2/utils/slot_clock/src/testing_slot_clock.rs b/eth2/utils/slot_clock/src/testing_slot_clock.rs index ab00d2baa7..f741d3b87a 100644 --- a/eth2/utils/slot_clock/src/testing_slot_clock.rs +++ b/eth2/utils/slot_clock/src/testing_slot_clock.rs @@ -40,6 +40,10 @@ impl SlotClock for TestingSlotClock { fn duration_to_next_slot(&self) -> Result, Error> { Ok(Some(Duration::from_secs(1))) } + + fn slot_duration_millis(&self) -> u64 { + 0 + } } #[cfg(test)] From 7165598b7fe3346ece3420bf808d14391106295a Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Mon, 12 Aug 2019 18:19:50 +1000 Subject: [PATCH 19/62] Add lighthouse_metrics gather fn --- beacon_node/rest_api/src/metrics.rs | 16 +++++++++------- eth2/utils/lighthouse_metrics/src/lib.rs | 4 ++++ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/beacon_node/rest_api/src/metrics.rs b/beacon_node/rest_api/src/metrics.rs index f0ccef5f8d..064359337a 100644 --- a/beacon_node/rest_api/src/metrics.rs +++ b/beacon_node/rest_api/src/metrics.rs @@ -46,20 +46,22 @@ pub fn get_prometheus(req: Request) -> ApiR // - Statically updated: things which are only updated at the time of the scrape (used where we // can avoid cluttering up code with metrics calls). // - // The `prometheus` crate has a `DEFAULT_REGISTRY` global singleton (via `lazy_static`) which - // keeps the state of all the metrics. Dynamically updated things will already be up-to-date in - // the registry (because they update themselves) however statically updated things need to be - // "scraped". + // The `lighthouse_metrics` crate has a `DEFAULT_REGISTRY` global singleton (via `lazy_static`) + // which keeps the state of all the metrics. Dynamically updated things will already be + // up-to-date in the registry (because they update themselves) however statically updated + // things need to be "scraped". // // We proceed by, first updating all the static metrics using `scrape_for_metrics(..)`. Then, - // using `prometheus::gather(..)` to collect the global `DEFAULT_REGISTRY` metrics into a - // string that can be returned via HTTP. + // using `lighthouse_metrics::gather(..)` to collect the global `DEFAULT_REGISTRY` metrics into + // a string that can be returned via HTTP. slot_clock::scrape_for_metrics::(&beacon_chain.slot_clock); store::scrape_for_metrics(&db_path); beacon_chain::scrape_for_metrics(&beacon_chain); - encoder.encode(&prometheus::gather(), &mut buffer).unwrap(); + encoder + .encode(&lighthouse_metrics::gather(), &mut buffer) + .unwrap(); String::from_utf8(buffer) .map(|string| success_response(Body::from(string))) diff --git a/eth2/utils/lighthouse_metrics/src/lib.rs b/eth2/utils/lighthouse_metrics/src/lib.rs index a8656d0171..c9e66e9712 100644 --- a/eth2/utils/lighthouse_metrics/src/lib.rs +++ b/eth2/utils/lighthouse_metrics/src/lib.rs @@ -2,6 +2,10 @@ use prometheus::{HistogramOpts, HistogramTimer, Opts}; pub use prometheus::{Histogram, IntCounter, IntGauge, Result}; +pub fn gather() -> Vec { + prometheus::gather() +} + pub fn try_create_int_counter(name: &str, help: &str) -> Result { let opts = Opts::new(name, help); let counter = IntCounter::with_opts(opts)?; From d5d60874e5e38368e8e538f5d4f4a89b30c7423a Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Mon, 12 Aug 2019 18:20:05 +1000 Subject: [PATCH 20/62] Remove http args --- beacon_node/src/main.rs | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/beacon_node/src/main.rs b/beacon_node/src/main.rs index 2e3ad06918..9a52f2638c 100644 --- a/beacon_node/src/main.rs +++ b/beacon_node/src/main.rs @@ -128,28 +128,6 @@ fn main() { .help("Listen port for RPC endpoint.") .takes_value(true), ) - /* - * HTTP server parameters. - */ - .arg( - Arg::with_name("http") - .long("http") - .help("Enable the HTTP server.") - .takes_value(false), - ) - .arg( - Arg::with_name("http-address") - .long("http-address") - .value_name("Address") - .help("Listen address for the HTTP server.") - .takes_value(true), - ) - .arg( - Arg::with_name("http-port") - .long("http-port") - .help("Listen port for the HTTP server.") - .takes_value(true), - ) /* Client related arguments */ .arg( Arg::with_name("api") From 24b2f83713f5e3fd5147e99be44a5f842a6332fb Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Tue, 13 Aug 2019 07:35:52 +1000 Subject: [PATCH 21/62] Fix wrong state given to op pool prune --- beacon_node/beacon_chain/src/beacon_chain.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index 0e0583309f..bed50202d4 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -1199,8 +1199,12 @@ impl BeaconChain { self.fork_choice .process_finalization(&finalized_block, finalized_block_root)?; - self.op_pool - .prune_all(&self.head().beacon_state, &self.spec); + let finalized_state = self + .store + .get::>(&finalized_block.state_root)? + .ok_or_else(|| Error::MissingBeaconState(finalized_block.state_root))?; + + self.op_pool.prune_all(&finalized_state, &self.spec); Ok(()) } From e369e293a507c602be1901b462001bfd8e4e825c Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Tue, 13 Aug 2019 09:20:39 +1000 Subject: [PATCH 22/62] Make prom metric names more consistent --- beacon_node/beacon_chain/src/metrics.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/beacon_node/beacon_chain/src/metrics.rs b/beacon_node/beacon_chain/src/metrics.rs index a4b36cd375..574fbb4a46 100644 --- a/beacon_node/beacon_chain/src/metrics.rs +++ b/beacon_node/beacon_chain/src/metrics.rs @@ -15,7 +15,7 @@ lazy_static! { "Count of blocks processed without error" ); pub static ref BLOCK_PROCESSING_TIMES: Result = - try_create_histogram("block_processing_seconds", "Full runtime of block processing"); + try_create_histogram("beacon_block_processing_seconds", "Full runtime of block processing"); pub static ref BLOCK_PROCESSING_DB_READ: Result = try_create_histogram( "beacon_block_processing_db_read_seconds", "Time spent loading block and state from DB for block processing" @@ -123,15 +123,15 @@ lazy_static! { "Count of occasions fork choice has switched to a different chain" ); pub static ref FORK_CHOICE_TIMES: Result = - try_create_histogram("beacon_fork_choice_time", "Full runtime of fork choice"); + try_create_histogram("beacon_fork_choice_seconds", "Full runtime of fork choice"); pub static ref FORK_CHOICE_FIND_HEAD_TIMES: Result = - try_create_histogram("beacon_fork_choice_find_head_time", "Full runtime of fork choice find_head function"); + try_create_histogram("beacon_fork_choice_find_head_seconds", "Full runtime of fork choice find_head function"); pub static ref FORK_CHOICE_PROCESS_BLOCK_TIMES: Result = try_create_histogram( - "beacon_fork_choice_process_block_time", + "beacon_fork_choice_process_block_seconds", "Time taken to add a block and all attestations to fork choice" ); pub static ref FORK_CHOICE_PROCESS_ATTESTATION_TIMES: Result = try_create_histogram( - "beacon_fork_choice_process_attestation_time", + "beacon_fork_choice_process_attestation_seconds", "Time taken to add an attestation to fork choice" ); From b076b07022c9f359315b52700de301d23530e1f0 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Tue, 13 Aug 2019 12:11:18 +1000 Subject: [PATCH 23/62] Add more metrics, tidy existing metrics --- beacon_node/beacon_chain/src/beacon_chain.rs | 4 ++ beacon_node/beacon_chain/src/metrics.rs | 4 ++ beacon_node/eth2-libp2p/src/discovery.rs | 8 ++- beacon_node/eth2-libp2p/src/metrics.rs | 14 +++-- beacon_node/store/src/impls.rs | 20 ++++++- beacon_node/store/src/impls/beacon_state.rs | 21 +++++++- beacon_node/store/src/metrics.rs | 57 ++++++++++++++++++++ eth2/utils/slot_clock/src/metrics.rs | 3 ++ 8 files changed, 120 insertions(+), 11 deletions(-) diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index 0e0583309f..faffa46f52 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -827,8 +827,12 @@ impl BeaconChain { return Ok(BlockProcessingOutcome::GenesisBlock); } + let block_root_timer = metrics::start_timer(&metrics::BLOCK_PROCESSING_BLOCK_ROOT); + let block_root = block.canonical_root(); + metrics::stop_timer(block_root_timer); + if block_root == self.genesis_block_root { return Ok(BlockProcessingOutcome::GenesisBlock); } diff --git a/beacon_node/beacon_chain/src/metrics.rs b/beacon_node/beacon_chain/src/metrics.rs index 574fbb4a46..db213a0cf7 100644 --- a/beacon_node/beacon_chain/src/metrics.rs +++ b/beacon_node/beacon_chain/src/metrics.rs @@ -16,6 +16,10 @@ lazy_static! { ); pub static ref BLOCK_PROCESSING_TIMES: Result = try_create_histogram("beacon_block_processing_seconds", "Full runtime of block processing"); + pub static ref BLOCK_PROCESSING_BLOCK_ROOT: Result = try_create_histogram( + "beacon_block_processing_block_root_seconds", + "Time spent calculating the block root when processing a block." + ); pub static ref BLOCK_PROCESSING_DB_READ: Result = try_create_histogram( "beacon_block_processing_db_read_seconds", "Time spent loading block and state from DB for block processing" diff --git a/beacon_node/eth2-libp2p/src/discovery.rs b/beacon_node/eth2-libp2p/src/discovery.rs index d9f2f7465a..ca98db3246 100644 --- a/beacon_node/eth2-libp2p/src/discovery.rs +++ b/beacon_node/eth2-libp2p/src/discovery.rs @@ -159,13 +159,17 @@ where } fn inject_connected(&mut self, peer_id: PeerId, _endpoint: ConnectedPoint) { - metrics::inc_counter(&metrics::PEER_CONNECT_COUNT); self.connected_peers.insert(peer_id); + + metrics::inc_counter(&metrics::PEER_CONNECT_EVENT_COUNT); + metrics::set_gauge(&metrics::PEERS_CONNECTED, self.connected_peers() as i64); } fn inject_disconnected(&mut self, peer_id: &PeerId, _endpoint: ConnectedPoint) { - metrics::inc_counter(&metrics::PEER_DISCONNECT_COUNT); self.connected_peers.remove(peer_id); + + metrics::inc_counter(&metrics::PEER_DISCONNECT_EVENT_COUNT); + metrics::set_gauge(&metrics::PEERS_CONNECTED, self.connected_peers() as i64); } fn inject_replaced( diff --git a/beacon_node/eth2-libp2p/src/metrics.rs b/beacon_node/eth2-libp2p/src/metrics.rs index a47037669e..b678ef6b41 100644 --- a/beacon_node/eth2-libp2p/src/metrics.rs +++ b/beacon_node/eth2-libp2p/src/metrics.rs @@ -2,15 +2,19 @@ pub use lighthouse_metrics::*; lazy_static! { pub static ref ADDRESS_UPDATE_COUNT: Result = try_create_int_counter( - "libp2p_address_update_count", + "libp2p_address_update_total", "Count of libp2p socked updated events (when our view of our IP address has changed)" ); - pub static ref PEER_CONNECT_COUNT: Result = try_create_int_counter( - "libp2p_peer_connect_count", + pub static ref PEERS_CONNECTED: Result = try_create_int_gauge( + "libp2p_peer_connected_peers_total", + "Count of libp2p peers currently connected" + ); + pub static ref PEER_CONNECT_EVENT_COUNT: Result = try_create_int_counter( + "libp2p_peer_connect_event_total", "Count of libp2p peer connect events (not the current number of connected peers)" ); - pub static ref PEER_DISCONNECT_COUNT: Result = try_create_int_counter( - "libp2p_peer_disconnect_count", + pub static ref PEER_DISCONNECT_EVENT_COUNT: Result = try_create_int_counter( + "libp2p_peer_disconnect_event_total", "Count of libp2p peer disconnect events" ); } diff --git a/beacon_node/store/src/impls.rs b/beacon_node/store/src/impls.rs index e88b70f396..1c29c245b2 100644 --- a/beacon_node/store/src/impls.rs +++ b/beacon_node/store/src/impls.rs @@ -9,10 +9,26 @@ impl StoreItem for BeaconBlock { } fn as_store_bytes(&self) -> Vec { - self.as_ssz_bytes() + let timer = metrics::start_timer(&metrics::BEACON_STATE_WRITE_TIMES); + let bytes = self.as_ssz_bytes(); + + metrics::stop_timer(timer); + metrics::inc_counter(&metrics::BEACON_STATE_WRITE_COUNT); + metrics::inc_counter_by(&metrics::BEACON_STATE_WRITE_BYTES, bytes.len() as i64); + + bytes } fn from_store_bytes(bytes: &mut [u8]) -> Result { - Self::from_ssz_bytes(bytes).map_err(Into::into) + let timer = metrics::start_timer(&metrics::BEACON_STATE_READ_TIMES); + + let len = bytes.len(); + let result = Self::from_ssz_bytes(bytes).map_err(Into::into); + + metrics::stop_timer(timer); + metrics::inc_counter(&metrics::BEACON_STATE_READ_COUNT); + metrics::inc_counter_by(&metrics::BEACON_STATE_READ_BYTES, len as i64); + + result } } diff --git a/beacon_node/store/src/impls/beacon_state.rs b/beacon_node/store/src/impls/beacon_state.rs index 591663fe05..69e83cd636 100644 --- a/beacon_node/store/src/impls/beacon_state.rs +++ b/beacon_node/store/src/impls/beacon_state.rs @@ -53,12 +53,29 @@ impl StoreItem for BeaconState { } fn as_store_bytes(&self) -> Vec { + let timer = metrics::start_timer(&metrics::BEACON_STATE_WRITE_TIMES); + let container = StorageContainer::new(self); - container.as_ssz_bytes() + let bytes = container.as_ssz_bytes(); + + metrics::stop_timer(timer); + metrics::inc_counter(&metrics::BEACON_STATE_WRITE_COUNT); + metrics::inc_counter_by(&metrics::BEACON_STATE_WRITE_BYTES, bytes.len() as i64); + + bytes } fn from_store_bytes(bytes: &mut [u8]) -> Result { + let timer = metrics::start_timer(&metrics::BEACON_STATE_READ_TIMES); + + let len = bytes.len(); let container = StorageContainer::from_ssz_bytes(bytes)?; - container.try_into() + let result = container.try_into(); + + metrics::stop_timer(timer); + metrics::inc_counter(&metrics::BEACON_STATE_READ_COUNT); + metrics::inc_counter_by(&metrics::BEACON_STATE_READ_BYTES, len as i64); + + result } } diff --git a/beacon_node/store/src/metrics.rs b/beacon_node/store/src/metrics.rs index 30cbb878b6..90237824d2 100644 --- a/beacon_node/store/src/metrics.rs +++ b/beacon_node/store/src/metrics.rs @@ -4,6 +4,9 @@ use std::fs; use std::path::PathBuf; lazy_static! { + /* + * General + */ pub static ref DISK_DB_SIZE: Result = try_create_int_gauge("store_disk_db_size", "Size of the on-disk database (bytes)"); pub static ref DISK_DB_WRITE_BYTES: Result = try_create_int_counter( @@ -30,6 +33,60 @@ lazy_static! { "store_disk_db_delete_count_total", "Total number of deletions from the on-disk DB" ); + /* + * Beacon State + */ + pub static ref BEACON_STATE_READ_TIMES: Result = try_create_histogram( + "store_beacon_state_read_overhead_seconds", + "Overhead on reading a beacon state from the DB (e.g., decoding)" + ); + pub static ref BEACON_STATE_READ_COUNT: Result = try_create_int_counter( + "store_beacon_state_read_total", + "Total number of beacon state reads from the DB" + ); + pub static ref BEACON_STATE_READ_BYTES: Result = try_create_int_counter( + "store_beacon_state_read_bytes_total", + "Total number of beacon state bytes read from the DB" + ); + pub static ref BEACON_STATE_WRITE_TIMES: Result = try_create_histogram( + "store_beacon_state_write_overhead_seconds", + "Overhead on writing a beacon state to the DB (e.g., encoding)" + ); + pub static ref BEACON_STATE_WRITE_COUNT: Result = try_create_int_counter( + "store_beacon_state_write_total", + "Total number of beacon state writes the DB" + ); + pub static ref BEACON_STATE_WRITE_BYTES: Result = try_create_int_counter( + "store_beacon_state_write_bytes_total", + "Total number of beacon state bytes written to the DB" + ); + /* + * Beacon Block + */ + pub static ref BEACON_BLOCK_READ_TIMES: Result = try_create_histogram( + "store_beacon_block_read_overhead_seconds", + "Overhead on reading a beacon block from the DB (e.g., decoding)" + ); + pub static ref BEACON_BLOCK_READ_COUNT: Result = try_create_int_counter( + "store_beacon_block_read_total", + "Total number of beacon block reads from the DB" + ); + pub static ref BEACON_BLOCK_READ_BYTES: Result = try_create_int_counter( + "store_beacon_block_read_bytes_total", + "Total number of beacon block bytes read from the DB" + ); + pub static ref BEACON_BLOCK_WRITE_TIMES: Result = try_create_histogram( + "store_beacon_block_write_overhead_seconds", + "Overhead on writing a beacon block to the DB (e.g., encoding)" + ); + pub static ref BEACON_BLOCK_WRITE_COUNT: Result = try_create_int_counter( + "store_beacon_block_write_total", + "Total number of beacon block writes the DB" + ); + pub static ref BEACON_BLOCK_WRITE_BYTES: Result = try_create_int_counter( + "store_beacon_block_write_bytes_total", + "Total number of beacon block bytes written to the DB" + ); } /// Updates the global metrics registry with store-related information. diff --git a/eth2/utils/slot_clock/src/metrics.rs b/eth2/utils/slot_clock/src/metrics.rs index a9153a10ca..e0d3923e00 100644 --- a/eth2/utils/slot_clock/src/metrics.rs +++ b/eth2/utils/slot_clock/src/metrics.rs @@ -7,6 +7,8 @@ lazy_static! { try_create_int_gauge("slotclock_present_slot", "The present wall-clock slot"); pub static ref PRESENT_EPOCH: Result = try_create_int_gauge("slotclock_present_epoch", "The present wall-clock epoch"); + pub static ref SLOTS_PER_EPOCH: Result = + try_create_int_gauge("slotclock_slots_per_epoch", "Slots per epoch (constant)"); pub static ref MILLISECONDS_PER_SLOT: Result = try_create_int_gauge( "slotclock_slot_time_milliseconds", "The duration in milliseconds between each slot" @@ -25,5 +27,6 @@ pub fn scrape_for_metrics(clock: &U) { &PRESENT_EPOCH, present_slot.epoch(T::slots_per_epoch()).as_u64() as i64, ); + set_gauge(&SLOTS_PER_EPOCH, T::slots_per_epoch() as i64); set_gauge(&MILLISECONDS_PER_SLOT, clock.slot_duration_millis() as i64); } From a3e464078af39e10132bac3d1ac37dbebae8b41a Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Tue, 13 Aug 2019 13:00:01 +1000 Subject: [PATCH 24/62] Fix store block read metrics --- beacon_node/store/src/impls.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/beacon_node/store/src/impls.rs b/beacon_node/store/src/impls.rs index 1c29c245b2..ed724480c3 100644 --- a/beacon_node/store/src/impls.rs +++ b/beacon_node/store/src/impls.rs @@ -9,25 +9,25 @@ impl StoreItem for BeaconBlock { } fn as_store_bytes(&self) -> Vec { - let timer = metrics::start_timer(&metrics::BEACON_STATE_WRITE_TIMES); + let timer = metrics::start_timer(&metrics::BEACON_BLOCK_WRITE_TIMES); let bytes = self.as_ssz_bytes(); metrics::stop_timer(timer); - metrics::inc_counter(&metrics::BEACON_STATE_WRITE_COUNT); - metrics::inc_counter_by(&metrics::BEACON_STATE_WRITE_BYTES, bytes.len() as i64); + metrics::inc_counter(&metrics::BEACON_BLOCK_WRITE_COUNT); + metrics::inc_counter_by(&metrics::BEACON_BLOCK_WRITE_BYTES, bytes.len() as i64); bytes } fn from_store_bytes(bytes: &mut [u8]) -> Result { - let timer = metrics::start_timer(&metrics::BEACON_STATE_READ_TIMES); + let timer = metrics::start_timer(&metrics::BEACON_BLOCK_READ_TIMES); let len = bytes.len(); let result = Self::from_ssz_bytes(bytes).map_err(Into::into); metrics::stop_timer(timer); - metrics::inc_counter(&metrics::BEACON_STATE_READ_COUNT); - metrics::inc_counter_by(&metrics::BEACON_STATE_READ_BYTES, len as i64); + metrics::inc_counter(&metrics::BEACON_BLOCK_READ_COUNT); + metrics::inc_counter_by(&metrics::BEACON_BLOCK_READ_BYTES, len as i64); result } From 341a83b9e8d5f3733b09ac9dae2e8aa6d5602ef5 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Tue, 13 Aug 2019 16:17:11 +1000 Subject: [PATCH 25/62] Tidy attestation metrics --- beacon_node/beacon_chain/src/beacon_chain.rs | 28 +++++++++++++++----- beacon_node/beacon_chain/src/metrics.rs | 4 +++ 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index faffa46f52..0cb6d5f980 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -547,11 +547,14 @@ impl BeaconChain { &self, attestation: Attestation, ) -> Result { + metrics::inc_counter(&metrics::ATTESTATION_PROCESSING_REQUESTS); + let timer = metrics::start_timer(&metrics::ATTESTATION_PROCESSING_TIMES); + // From the store, load the attestation's "head block". // // An honest validator would have set this block to be the head of the chain (i.e., the // result of running fork choice). - if let Some(attestation_head_block) = self + let result = if let Some(attestation_head_block) = self .store .get::>(&attestation.data.beacon_block_root)? { @@ -680,7 +683,15 @@ impl BeaconChain { Ok(AttestationProcessingOutcome::UnknownHeadBlock { beacon_block_root: attestation.data.beacon_block_root, }) + }; + + metrics::stop_timer(timer); + + if let Ok(AttestationProcessingOutcome::Processed) = &result { + metrics::inc_counter(&metrics::ATTESTATION_PROCESSING_SUCCESSES); } + + result } /// Verifies the `attestation` against the `state` to which it is attesting. @@ -707,9 +718,6 @@ impl BeaconChain { state: &BeaconState, block: &BeaconBlock, ) -> Result { - metrics::inc_counter(&metrics::ATTESTATION_PROCESSING_REQUESTS); - let timer = metrics::start_timer(&metrics::ATTESTATION_PROCESSING_TIMES); - // Find the highest between: // // - The highest valid finalized epoch we've ever seen (i.e., the head). @@ -719,6 +727,16 @@ impl BeaconChain { state.finalized_checkpoint.epoch, ); + // A helper function to allow attestation processing to be metered. + let verify_attestation_for_state = |state, attestation, spec, verify_signatures| { + let timer = metrics::start_timer(&metrics::ATTESTATION_PROCESSING_CORE); + + let result = verify_attestation_for_state(state, attestation, spec, verify_signatures); + + metrics::stop_timer(timer); + result + }; + let result = if block.slot <= finalized_epoch.start_slot(T::EthSpec::slots_per_epoch()) { // Ignore any attestation where the slot of `data.beacon_block_root` is equal to or // prior to the finalized epoch. @@ -758,8 +776,6 @@ impl BeaconChain { Ok(AttestationProcessingOutcome::Processed) }; - timer.map(|t| t.observe_duration()); - result } diff --git a/beacon_node/beacon_chain/src/metrics.rs b/beacon_node/beacon_chain/src/metrics.rs index db213a0cf7..6efa4b3f2b 100644 --- a/beacon_node/beacon_chain/src/metrics.rs +++ b/beacon_node/beacon_chain/src/metrics.rs @@ -90,6 +90,10 @@ lazy_static! { "beacon_attestation_processing_seconds", "Full runtime of attestation processing" ); + pub static ref ATTESTATION_PROCESSING_CORE: Result = try_create_histogram( + "beacon_attestation_processing_core_seconds", + "Time spent on the core spec processing of attestation processing" + ); /* * Attestation Production From b7e43b56f9dc4167414c61d6b52238782e0caf47 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Tue, 13 Aug 2019 19:37:14 +1000 Subject: [PATCH 26/62] Fix minor PR comments --- beacon_node/beacon_chain/src/beacon_chain.rs | 6 +++--- eth2/lmd_ghost/src/reduced_tree.rs | 6 +----- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index 73ebb7007f..76442fb8d1 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -536,7 +536,7 @@ impl BeaconChain { /// If valid, the attestation is added to `self.op_pool` and `self.fork_choice`. /// /// Returns an `Ok(AttestationProcessingOutcome)` if the chain was able to make a determination - /// about the `attestation` (wether it was invalid or not). Returns an `Err` if the was an + /// about the `attestation` (whether it was invalid or not). Returns an `Err` if there was an /// error during this process and no determination was able to be made. /// /// ## Notes @@ -620,7 +620,7 @@ impl BeaconChain { outcome } else { // Use the `data.beacon_block_root` to load the state from the latest non-skipped - // slot preceding the attestations creation. + // slot preceding the attestation's creation. // // This state is guaranteed to be in the same chain as the attestation, but it's // not guaranteed to be from the same slot or epoch as the attestation. @@ -703,7 +703,7 @@ impl BeaconChain { /// The given `state` must fulfil one of the following conditions: /// /// - `state` corresponds to the `block.state_root` identified by - /// `attestation.data.beacon_block_root`. (Viz., `attestation` was created using `state`. + /// `attestation.data.beacon_block_root`. (Viz., `attestation` was created using `state`). /// - `state.slot` is in the same epoch as `data.target.epoch` and /// `attestation.data.beacon_block_root` is in the history of `state`. /// diff --git a/eth2/lmd_ghost/src/reduced_tree.rs b/eth2/lmd_ghost/src/reduced_tree.rs index 822c388f6a..deda02e1fd 100644 --- a/eth2/lmd_ghost/src/reduced_tree.rs +++ b/eth2/lmd_ghost/src/reduced_tree.rs @@ -777,11 +777,7 @@ where } pub fn get_ref(&self, i: usize) -> Option<&T> { - if i < self.0.len() { - Some(&self.0[i]) - } else { - None - } + self.0.get(i) } pub fn insert(&mut self, i: usize, element: T) { From 6cd0af766e0ef97c258545fec7369169801cb9a5 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Tue, 13 Aug 2019 19:37:14 +1000 Subject: [PATCH 27/62] Fix minor PR comments --- beacon_node/beacon_chain/src/beacon_chain.rs | 6 +++--- eth2/lmd_ghost/src/reduced_tree.rs | 6 +----- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index 9ccf595893..61998b5de3 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -536,7 +536,7 @@ impl BeaconChain { /// If valid, the attestation is added to `self.op_pool` and `self.fork_choice`. /// /// Returns an `Ok(AttestationProcessingOutcome)` if the chain was able to make a determination - /// about the `attestation` (wether it was invalid or not). Returns an `Err` if the was an + /// about the `attestation` (whether it was invalid or not). Returns an `Err` if there was an /// error during this process and no determination was able to be made. /// /// ## Notes @@ -617,7 +617,7 @@ impl BeaconChain { outcome } else { // Use the `data.beacon_block_root` to load the state from the latest non-skipped - // slot preceding the attestations creation. + // slot preceding the attestation's creation. // // This state is guaranteed to be in the same chain as the attestation, but it's // not guaranteed to be from the same slot or epoch as the attestation. @@ -692,7 +692,7 @@ impl BeaconChain { /// The given `state` must fulfil one of the following conditions: /// /// - `state` corresponds to the `block.state_root` identified by - /// `attestation.data.beacon_block_root`. (Viz., `attestation` was created using `state`. + /// `attestation.data.beacon_block_root`. (Viz., `attestation` was created using `state`). /// - `state.slot` is in the same epoch as `data.target.epoch` and /// `attestation.data.beacon_block_root` is in the history of `state`. /// diff --git a/eth2/lmd_ghost/src/reduced_tree.rs b/eth2/lmd_ghost/src/reduced_tree.rs index 822c388f6a..deda02e1fd 100644 --- a/eth2/lmd_ghost/src/reduced_tree.rs +++ b/eth2/lmd_ghost/src/reduced_tree.rs @@ -777,11 +777,7 @@ where } pub fn get_ref(&self, i: usize) -> Option<&T> { - if i < self.0.len() { - Some(&self.0[i]) - } else { - None - } + self.0.get(i) } pub fn insert(&mut self, i: usize, element: T) { From 8fb9e1f648b75b488f798a25cf2bce487ff8206e Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Tue, 13 Aug 2019 19:48:03 +1000 Subject: [PATCH 28/62] Remove duplicated attestation finalization check --- beacon_node/beacon_chain/src/beacon_chain.rs | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index 61998b5de3..9ee51c1629 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -555,23 +555,6 @@ impl BeaconChain { .store .get::>(&attestation.data.beacon_block_root)? { - let finalized_epoch = self.head().beacon_state.finalized_checkpoint.epoch; - - if attestation_head_block.slot - <= finalized_epoch.start_slot(T::EthSpec::slots_per_epoch()) - { - // Ignore any attestation where the slot of `data.beacon_block_root` is equal to or - // prior to the finalized epoch. - // - // For any valid attestation if the `beacon_block_root` is prior to finalization, then - // all other parameters (source, target, etc) must all be prior to finalization and - // therefore no longer interesting. - return Ok(AttestationProcessingOutcome::FinalizedSlot { - attestation: attestation_head_block.epoch(), - finalized: finalized_epoch, - }); - } - // Attempt to process the attestation using the `self.head()` state. // // This is purely an effort to avoid loading a `BeaconState` unnecessarily from the DB. From 82e8aafb014484e72926dc476184634fd8b9afdf Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Tue, 13 Aug 2019 19:59:29 +1000 Subject: [PATCH 29/62] Remove awkward `let` statement --- beacon_node/beacon_chain/src/beacon_chain.rs | 170 +++++++++---------- 1 file changed, 82 insertions(+), 88 deletions(-) diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index 9ee51c1629..96d3065306 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -558,97 +558,91 @@ impl BeaconChain { // Attempt to process the attestation using the `self.head()` state. // // This is purely an effort to avoid loading a `BeaconState` unnecessarily from the DB. - let optional_outcome: Option> = { - // Take a read lock on the head beacon state. - // - // The purpose of this whole `let processed ...` block is to ensure that the read - // lock is dropped if we don't end up using the head beacon state. - let state = &self.head().beacon_state; + // Take a read lock on the head beacon state. + let state = &self.head().beacon_state; - // If it turns out that the attestation was made using the head state, then there - // is no need to load a state from the database to process the attestation. - // - // Note: use the epoch of the target because it indicates which epoch the - // attestation was created in. You cannot use the epoch of the head block, because - // the block doesn't necessarily need to be in the same epoch as the attestation - // (e.g., if there are skip slots between the epoch the block was created in and - // the epoch for the attestation). - // - // This check also ensures that the slot for `data.beacon_block_root` is not higher - // than `state.root` by ensuring that the block is in the history of `state`. - if state.current_epoch() == attestation.data.target.epoch - && (attestation.data.beacon_block_root == self.head().beacon_block_root - || state - .get_block_root(attestation_head_block.slot) - .map(|root| *root == attestation.data.beacon_block_root) - .unwrap_or_else(|_| false)) - { - // The head state is able to be used to validate this attestation. No need to load - // anything from the database. - Some(self.process_attestation_for_state_and_block( - attestation.clone(), - state, - &attestation_head_block, - )) - } else { - None - } - }; + // If it turns out that the attestation was made using the head state, then there + // is no need to load a state from the database to process the attestation. + // + // Note: use the epoch of the target because it indicates which epoch the + // attestation was created in. You cannot use the epoch of the head block, because + // the block doesn't necessarily need to be in the same epoch as the attestation + // (e.g., if there are skip slots between the epoch the block was created in and + // the epoch for the attestation). + // + // This check also ensures that the slot for `data.beacon_block_root` is not higher + // than `state.root` by ensuring that the block is in the history of `state`. + if state.current_epoch() == attestation.data.target.epoch + && (attestation.data.beacon_block_root == self.head().beacon_block_root + || state + .get_block_root(attestation_head_block.slot) + .map(|root| *root == attestation.data.beacon_block_root) + .unwrap_or_else(|_| false)) + { + // The head state is able to be used to validate this attestation. No need to load + // anything from the database. + return self.process_attestation_for_state_and_block( + attestation.clone(), + state, + &attestation_head_block, + ); + } - if let Some(outcome) = optional_outcome { - // Verification was already completed with an in-memory state. Return that result. - outcome + // Ensure the read-lock from `self.head()` is dropped. + // + // This is likely unnecessary, however it remains as a reminder to ensure this lock + // isn't hogged. + std::mem::drop(state); + + // Use the `data.beacon_block_root` to load the state from the latest non-skipped + // slot preceding the attestation's creation. + // + // This state is guaranteed to be in the same chain as the attestation, but it's + // not guaranteed to be from the same slot or epoch as the attestation. + let mut state: BeaconState = self + .store + .get(&attestation_head_block.state_root)? + .ok_or_else(|| Error::MissingBeaconState(attestation_head_block.state_root))?; + + // Ensure the state loaded from the database matches the state of the attestation + // head block. + // + // The state needs to be advanced from the current slot through to the epoch in + // which the attestation was created in. It would be an error to try and use + // `state.get_attestation_data_slot(..)` because the state matching the + // `data.beacon_block_root` isn't necessarily in a nearby epoch to the attestation + // (e.g., if there were lots of skip slots since the head of the chain and the + // epoch creation epoch). + for _ in state.slot.as_u64() + ..attestation + .data + .target + .epoch + .start_slot(T::EthSpec::slots_per_epoch()) + .as_u64() + { + per_slot_processing(&mut state, &self.spec)?; + } + + state.build_committee_cache(RelativeEpoch::Current, &self.spec)?; + + let attestation_slot = state.get_attestation_data_slot(&attestation.data)?; + + // Reject any attestation where the `state` loaded from `data.beacon_block_root` + // has a higher slot than the attestation. + // + // Permitting this would allow for attesters to vote on _future_ slots. + if attestation_slot > state.slot { + Ok(AttestationProcessingOutcome::AttestsToFutureState { + state: state.slot, + attestation: attestation_slot, + }) } else { - // Use the `data.beacon_block_root` to load the state from the latest non-skipped - // slot preceding the attestation's creation. - // - // This state is guaranteed to be in the same chain as the attestation, but it's - // not guaranteed to be from the same slot or epoch as the attestation. - let mut state: BeaconState = self - .store - .get(&attestation_head_block.state_root)? - .ok_or_else(|| Error::MissingBeaconState(attestation_head_block.state_root))?; - - // Ensure the state loaded from the database matches the state of the attestation - // head block. - // - // The state needs to be advanced from the current slot through to the epoch in - // which the attestation was created in. It would be an error to try and use - // `state.get_attestation_data_slot(..)` because the state matching the - // `data.beacon_block_root` isn't necessarily in a nearby epoch to the attestation - // (e.g., if there were lots of skip slots since the head of the chain and the - // epoch creation epoch). - for _ in state.slot.as_u64() - ..attestation - .data - .target - .epoch - .start_slot(T::EthSpec::slots_per_epoch()) - .as_u64() - { - per_slot_processing(&mut state, &self.spec)?; - } - - state.build_committee_cache(RelativeEpoch::Current, &self.spec)?; - - let attestation_slot = state.get_attestation_data_slot(&attestation.data)?; - - // Reject any attestation where the `state` loaded from `data.beacon_block_root` - // has a higher slot than the attestation. - // - // Permitting this would allow for attesters to vote on _future_ slots. - if attestation_slot > state.slot { - Ok(AttestationProcessingOutcome::AttestsToFutureState { - state: state.slot, - attestation: attestation_slot, - }) - } else { - self.process_attestation_for_state_and_block( - attestation, - &state, - &attestation_head_block, - ) - } + self.process_attestation_for_state_and_block( + attestation, + &state, + &attestation_head_block, + ) } } else { // Drop any attestation where we have not processed `attestation.data.beacon_block_root`. From 4f98a3985fc1714799ac5897d002fa26ea74bb96 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Wed, 14 Aug 2019 10:36:55 +1000 Subject: [PATCH 30/62] Add first attempts at HTTP bootstrap --- beacon_node/client/Cargo.toml | 1 + beacon_node/client/src/beacon_chain_types.rs | 11 +++ beacon_node/client/src/config.rs | 2 + beacon_node/client/src/lib.rs | 1 + beacon_node/client/src/local_bootstrap.rs | 93 ++++++++++++++++++++ beacon_node/rest_api/src/beacon.rs | 21 +++++ beacon_node/rest_api/src/lib.rs | 6 ++ beacon_node/rest_api/src/spec.rs | 27 ++++++ 8 files changed, 162 insertions(+) create mode 100644 beacon_node/client/src/local_bootstrap.rs create mode 100644 beacon_node/rest_api/src/spec.rs diff --git a/beacon_node/client/Cargo.toml b/beacon_node/client/Cargo.toml index 8c72fa4171..b0524b17d2 100644 --- a/beacon_node/client/Cargo.toml +++ b/beacon_node/client/Cargo.toml @@ -27,3 +27,4 @@ clap = "2.32.0" dirs = "1.0.3" exit-future = "0.1.3" futures = "0.1.25" +reqwest = "0.9" diff --git a/beacon_node/client/src/beacon_chain_types.rs b/beacon_node/client/src/beacon_chain_types.rs index 0b86c95838..a5b89b86a2 100644 --- a/beacon_node/client/src/beacon_chain_types.rs +++ b/beacon_node/client/src/beacon_chain_types.rs @@ -1,4 +1,5 @@ use crate::error::Result; +use crate::local_bootstrap::BootstrapParams; use crate::{config::GenesisState, ClientConfig}; use beacon_chain::{ lmd_ghost::{LmdGhost, ThreadSafeReducedTree}, @@ -6,6 +7,7 @@ use beacon_chain::{ store::Store, BeaconChain, BeaconChainTypes, }; +use reqwest::Url; use slog::{crit, info, Logger}; use slot_clock::SlotClock; use std::fs::File; @@ -74,6 +76,15 @@ where serde_yaml::from_reader(file) .map_err(|e| format!("Unable to parse YAML genesis state file: {:?}", e))? } + GenesisState::HttpBootstrap { server } => { + let url: Url = + Url::parse(&server).map_err(|e| format!("Invalid bootstrap server url: {}", e))?; + + let params = BootstrapParams::from_http_api(url) + .map_err(|e| format!("Failed to bootstrap from HTTP server: {:?}", e))?; + + params.genesis_state + } }; let mut genesis_block = BeaconBlock::empty(&spec); diff --git a/beacon_node/client/src/config.rs b/beacon_node/client/src/config.rs index ee62b62815..2b410312b1 100644 --- a/beacon_node/client/src/config.rs +++ b/beacon_node/client/src/config.rs @@ -48,6 +48,8 @@ pub enum GenesisState { }, /// Load a YAML-encoded genesis state from a file. Yaml { file: PathBuf }, + /// Use a HTTP server (running our REST-API) to load genesis and finalized states and blocks. + HttpBootstrap { server: String }, } impl Default for Config { diff --git a/beacon_node/client/src/lib.rs b/beacon_node/client/src/lib.rs index 65ba071fa1..7a9152ee0e 100644 --- a/beacon_node/client/src/lib.rs +++ b/beacon_node/client/src/lib.rs @@ -2,6 +2,7 @@ extern crate slog; mod beacon_chain_types; mod config; +mod local_bootstrap; pub mod error; pub mod notifier; diff --git a/beacon_node/client/src/local_bootstrap.rs b/beacon_node/client/src/local_bootstrap.rs new file mode 100644 index 0000000000..f38762b3be --- /dev/null +++ b/beacon_node/client/src/local_bootstrap.rs @@ -0,0 +1,93 @@ +use reqwest::{Error as HttpError, Url}; +use types::{BeaconBlock, BeaconState, Checkpoint, EthSpec, Slot}; + +#[derive(Debug)] +pub enum Error { + UrlCannotBeBase, + HttpError(HttpError), +} + +impl From for Error { + fn from(e: HttpError) -> Error { + Error::HttpError(e) + } +} + +pub struct BootstrapParams { + pub finalized_block: BeaconBlock, + pub finalized_state: BeaconState, + pub genesis_block: BeaconBlock, + pub genesis_state: BeaconState, +} + +impl BootstrapParams { + pub fn from_http_api(url: Url) -> Result { + let slots_per_epoch = get_slots_per_epoch(url.clone())?; + let genesis_slot = Slot::new(0); + let finalized_slot = get_finalized_slot(url.clone(), slots_per_epoch.as_u64())?; + + Ok(Self { + finalized_block: get_block(url.clone(), finalized_slot)?, + finalized_state: get_state(url.clone(), finalized_slot)?, + genesis_block: get_block(url.clone(), genesis_slot)?, + genesis_state: get_state(url.clone(), genesis_slot)?, + }) + } +} + +fn get_slots_per_epoch(mut url: Url) -> Result { + url.path_segments_mut() + .map(|mut url| { + url.push("spec").push("slots_per_epoch"); + }) + .map_err(|_| Error::UrlCannotBeBase)?; + + reqwest::get(url)? + .error_for_status()? + .json() + .map_err(Into::into) +} + +fn get_finalized_slot(mut url: Url, slots_per_epoch: u64) -> Result { + url.path_segments_mut() + .map(|mut url| { + url.push("beacon").push("latest_finalized_checkpoint"); + }) + .map_err(|_| Error::UrlCannotBeBase)?; + + let checkpoint: Checkpoint = reqwest::get(url)?.error_for_status()?.json()?; + + Ok(checkpoint.epoch.start_slot(slots_per_epoch)) +} + +fn get_state(mut url: Url, slot: Slot) -> Result, Error> { + url.path_segments_mut() + .map(|mut url| { + url.push("beacon").push("state"); + }) + .map_err(|_| Error::UrlCannotBeBase)?; + + url.query_pairs_mut() + .append_pair("slot", &format!("{}", slot.as_u64())); + + reqwest::get(url)? + .error_for_status()? + .json() + .map_err(Into::into) +} + +fn get_block(mut url: Url, slot: Slot) -> Result, Error> { + url.path_segments_mut() + .map(|mut url| { + url.push("beacon").push("block"); + }) + .map_err(|_| Error::UrlCannotBeBase)?; + + url.query_pairs_mut() + .append_pair("slot", &format!("{}", slot.as_u64())); + + reqwest::get(url)? + .error_for_status()? + .json() + .map_err(Into::into) +} diff --git a/beacon_node/rest_api/src/beacon.rs b/beacon_node/rest_api/src/beacon.rs index cef23abe81..8b089f542b 100644 --- a/beacon_node/rest_api/src/beacon.rs +++ b/beacon_node/rest_api/src/beacon.rs @@ -58,3 +58,24 @@ pub fn get_state_root(req: Request) -> ApiR Ok(success_response(Body::from(json))) } + +/// HTTP handler to return the highest finalized slot. +pub fn get_latest_finalized_checkpoint( + req: Request, +) -> ApiResult { + let beacon_chain = req + .extensions() + .get::>>() + .ok_or_else(|| ApiError::ServerError("Beacon chain extension missing".to_string()))?; + + let checkpoint = beacon_chain + .head() + .beacon_state + .finalized_checkpoint + .clone(); + + let json: String = serde_json::to_string(&checkpoint) + .map_err(|e| ApiError::ServerError(format!("Unable to serialize checkpoint: {:?}", e)))?; + + Ok(success_response(Body::from(json))) +} diff --git a/beacon_node/rest_api/src/lib.rs b/beacon_node/rest_api/src/lib.rs index a94a8cdf4a..57c5482cd8 100644 --- a/beacon_node/rest_api/src/lib.rs +++ b/beacon_node/rest_api/src/lib.rs @@ -4,6 +4,7 @@ mod beacon; mod config; mod helpers; mod node; +mod spec; mod url_query; use beacon_chain::{BeaconChain, BeaconChainTypes}; @@ -101,10 +102,15 @@ pub fn start_server( // Route the request to the correct handler. let result = match (req.method(), path.as_ref()) { + (&Method::GET, "/beacon/latest_finalized_checkpoint") => { + beacon::get_latest_finalized_checkpoint::(req) + } (&Method::GET, "/beacon/state") => beacon::get_state::(req), (&Method::GET, "/beacon/state_root") => beacon::get_state_root::(req), (&Method::GET, "/node/version") => node::get_version(req), (&Method::GET, "/node/genesis_time") => node::get_genesis_time::(req), + (&Method::GET, "/spec") => spec::get_spec::(req), + (&Method::GET, "/spec/slots_per_epoch") => spec::get_slots_per_epoch::(req), _ => Err(ApiError::MethodNotAllowed(path.clone())), }; diff --git a/beacon_node/rest_api/src/spec.rs b/beacon_node/rest_api/src/spec.rs new file mode 100644 index 0000000000..d0c8e4368d --- /dev/null +++ b/beacon_node/rest_api/src/spec.rs @@ -0,0 +1,27 @@ +use super::{success_response, ApiResult}; +use crate::ApiError; +use beacon_chain::{BeaconChain, BeaconChainTypes}; +use hyper::{Body, Request}; +use std::sync::Arc; +use types::EthSpec; + +/// HTTP handler to return the full spec object. +pub fn get_spec(req: Request) -> ApiResult { + let beacon_chain = req + .extensions() + .get::>>() + .ok_or_else(|| ApiError::ServerError("Beacon chain extension missing".to_string()))?; + + let json: String = serde_json::to_string(&beacon_chain.spec) + .map_err(|e| ApiError::ServerError(format!("Unable to serialize spec: {:?}", e)))?; + + Ok(success_response(Body::from(json))) +} + +/// HTTP handler to return the full spec object. +pub fn get_slots_per_epoch(_req: Request) -> ApiResult { + let json: String = serde_json::to_string(&T::EthSpec::slots_per_epoch()) + .map_err(|e| ApiError::ServerError(format!("Unable to serialize epoch: {:?}", e)))?; + + Ok(success_response(Body::from(json))) +} From 2bf0d5c071efee2f24bda10afe5f21ec6a9c4884 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Wed, 14 Aug 2019 11:22:43 +1000 Subject: [PATCH 31/62] Add beacon_block methods to rest api --- beacon_node/client/src/local_bootstrap.rs | 22 ++++--- beacon_node/rest_api/src/beacon.rs | 74 ++++++++++++++++++++++- beacon_node/rest_api/src/lib.rs | 2 + 3 files changed, 87 insertions(+), 11 deletions(-) diff --git a/beacon_node/client/src/local_bootstrap.rs b/beacon_node/client/src/local_bootstrap.rs index f38762b3be..79fad7ec2e 100644 --- a/beacon_node/client/src/local_bootstrap.rs +++ b/beacon_node/client/src/local_bootstrap.rs @@ -2,7 +2,7 @@ use reqwest::{Error as HttpError, Url}; use types::{BeaconBlock, BeaconState, Checkpoint, EthSpec, Slot}; #[derive(Debug)] -pub enum Error { +enum Error { UrlCannotBeBase, HttpError(HttpError), } @@ -21,16 +21,22 @@ pub struct BootstrapParams { } impl BootstrapParams { - pub fn from_http_api(url: Url) -> Result { - let slots_per_epoch = get_slots_per_epoch(url.clone())?; + pub fn from_http_api(url: Url) -> Result { + let slots_per_epoch = get_slots_per_epoch(url.clone()) + .map_err(|e| format!("Unable to get slots per epoch: {:?}", e))?; let genesis_slot = Slot::new(0); - let finalized_slot = get_finalized_slot(url.clone(), slots_per_epoch.as_u64())?; + let finalized_slot = get_finalized_slot(url.clone(), slots_per_epoch.as_u64()) + .map_err(|e| format!("Unable to get finalized slot: {:?}", e))?; Ok(Self { - finalized_block: get_block(url.clone(), finalized_slot)?, - finalized_state: get_state(url.clone(), finalized_slot)?, - genesis_block: get_block(url.clone(), genesis_slot)?, - genesis_state: get_state(url.clone(), genesis_slot)?, + finalized_block: get_block(url.clone(), finalized_slot) + .map_err(|e| format!("Unable to get finalized block: {:?}", e))?, + finalized_state: get_state(url.clone(), finalized_slot) + .map_err(|e| format!("Unable to get finalized state: {:?}", e))?, + genesis_block: get_block(url.clone(), genesis_slot) + .map_err(|e| format!("Unable to get genesis block: {:?}", e))?, + genesis_state: get_state(url.clone(), genesis_slot) + .map_err(|e| format!("Unable to get genesis state: {:?}", e))?, }) } } diff --git a/beacon_node/rest_api/src/beacon.rs b/beacon_node/rest_api/src/beacon.rs index 8b089f542b..a2afb10010 100644 --- a/beacon_node/rest_api/src/beacon.rs +++ b/beacon_node/rest_api/src/beacon.rs @@ -4,7 +4,75 @@ use beacon_chain::{BeaconChain, BeaconChainTypes}; use hyper::{Body, Request}; use std::sync::Arc; use store::Store; -use types::BeaconState; +use types::{BeaconBlock, BeaconState}; + +/// HTTP handler to return a `BeaconBlock` at a given `root` or `slot`. +pub fn get_block(req: Request) -> ApiResult { + let beacon_chain = req + .extensions() + .get::>>() + .ok_or_else(|| ApiError::ServerError("Beacon chain extension missing".to_string()))?; + + let query_params = ["root", "slot"]; + let (key, value) = UrlQuery::from_request(&req)?.first_of(&query_params)?; + + let block_root = match (key.as_ref(), value) { + ("slot", value) => { + let target = parse_slot(&value)?; + + beacon_chain + .rev_iter_block_roots() + .take_while(|(_root, slot)| *slot >= target) + .find(|(_root, slot)| *slot == target) + .map(|(root, _slot)| root) + .ok_or_else(|| { + ApiError::NotFound(format!("Unable to find BeaconBlock for slot {}", target)) + })? + } + ("root", value) => parse_root(&value)?, + _ => return Err(ApiError::ServerError("Unexpected query parameter".into())), + }; + + let block = beacon_chain + .store + .get::>(&block_root)? + .ok_or_else(|| { + ApiError::NotFound(format!( + "Unable to find BeaconBlock for root {}", + block_root + )) + })?; + + let json: String = serde_json::to_string(&block) + .map_err(|e| ApiError::ServerError(format!("Unable to serialize BeaconBlock: {:?}", e)))?; + + Ok(success_response(Body::from(json))) +} + +/// HTTP handler to return a `BeaconBlock` root at a given `slot`. +pub fn get_block_root(req: Request) -> ApiResult { + let beacon_chain = req + .extensions() + .get::>>() + .ok_or_else(|| ApiError::ServerError("Beacon chain extension missing".to_string()))?; + + let slot_string = UrlQuery::from_request(&req)?.only_one("slot")?; + let target = parse_slot(&slot_string)?; + + let root = beacon_chain + .rev_iter_block_roots() + .take_while(|(_root, slot)| *slot >= target) + .find(|(_root, slot)| *slot == target) + .map(|(root, _slot)| root) + .ok_or_else(|| { + ApiError::NotFound(format!("Unable to find BeaconBlock for slot {}", target)) + })?; + + let json: String = serde_json::to_string(&root) + .map_err(|e| ApiError::ServerError(format!("Unable to serialize root: {:?}", e)))?; + + Ok(success_response(Body::from(json))) +} /// HTTP handler to return a `BeaconState` at a given `root` or `slot`. /// @@ -29,7 +97,7 @@ pub fn get_state(req: Request) -> ApiResult .get(root)? .ok_or_else(|| ApiError::NotFound(format!("No state for root: {}", root)))? } - _ => unreachable!("Guarded by UrlQuery::from_request()"), + _ => return Err(ApiError::ServerError("Unexpected query parameter".into())), }; let json: String = serde_json::to_string(&state) @@ -38,7 +106,7 @@ pub fn get_state(req: Request) -> ApiResult Ok(success_response(Body::from(json))) } -/// HTTP handler to return a `BeaconState` root at a given or `slot`. +/// HTTP handler to return a `BeaconState` root at a given `slot`. /// /// Will not return a state if the request slot is in the future. Will return states higher than /// the current head by skipping slots. diff --git a/beacon_node/rest_api/src/lib.rs b/beacon_node/rest_api/src/lib.rs index 57c5482cd8..4f07b482a4 100644 --- a/beacon_node/rest_api/src/lib.rs +++ b/beacon_node/rest_api/src/lib.rs @@ -102,6 +102,8 @@ pub fn start_server( // Route the request to the correct handler. let result = match (req.method(), path.as_ref()) { + (&Method::GET, "/beacon/block") => beacon::get_block::(req), + (&Method::GET, "/beacon/block_root") => beacon::get_block_root::(req), (&Method::GET, "/beacon/latest_finalized_checkpoint") => { beacon::get_latest_finalized_checkpoint::(req) } From 980f533b3b1156c89cc9f46d396a216b13af9205 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Wed, 14 Aug 2019 11:55:12 +1000 Subject: [PATCH 32/62] Fix serde for block.body.grafitti --- eth2/types/src/beacon_block_body.rs | 7 +++++-- eth2/types/src/utils/serde_utils.rs | 16 ++++++++++++++-- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/eth2/types/src/beacon_block_body.rs b/eth2/types/src/beacon_block_body.rs index 64dc229ed8..c1f66b816c 100644 --- a/eth2/types/src/beacon_block_body.rs +++ b/eth2/types/src/beacon_block_body.rs @@ -1,5 +1,5 @@ use crate::test_utils::TestRandom; -use crate::utils::graffiti_from_hex_str; +use crate::utils::{graffiti_from_hex_str, graffiti_to_hex_str}; use crate::*; use serde_derive::{Deserialize, Serialize}; @@ -16,7 +16,10 @@ use tree_hash_derive::TreeHash; pub struct BeaconBlockBody { pub randao_reveal: Signature, pub eth1_data: Eth1Data, - #[serde(deserialize_with = "graffiti_from_hex_str")] + #[serde( + serialize_with = "graffiti_to_hex_str", + deserialize_with = "graffiti_from_hex_str" + )] pub graffiti: [u8; 32], pub proposer_slashings: VariableList, pub attester_slashings: VariableList, T::MaxAttesterSlashings>, diff --git a/eth2/types/src/utils/serde_utils.rs b/eth2/types/src/utils/serde_utils.rs index 4b46fc0dc6..a9b27d75b5 100644 --- a/eth2/types/src/utils/serde_utils.rs +++ b/eth2/types/src/utils/serde_utils.rs @@ -46,8 +46,20 @@ where Ok(array) } -// #[allow(clippy::trivially_copy_pass_by_ref)] // Serde requires the `byte` to be a ref. -pub fn fork_to_hex_str(bytes: &[u8; 4], serializer: S) -> Result +pub fn fork_to_hex_str(bytes: &[u8; FORK_BYTES_LEN], serializer: S) -> Result +where + S: Serializer, +{ + let mut hex_string: String = "0x".to_string(); + hex_string.push_str(&hex::encode(&bytes)); + + serializer.serialize_str(&hex_string) +} + +pub fn graffiti_to_hex_str( + bytes: &[u8; GRAFFITI_BYTES_LEN], + serializer: S, +) -> Result where S: Serializer, { From 9b3c9f8c0fe1908b37ca7c6b8f98b68cc07adfac Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Wed, 14 Aug 2019 12:03:03 +1000 Subject: [PATCH 33/62] Allow travis failures on beta (see desc) There's a non-backward compatible change in `cargo fmt`. Stable and beta do not agree. --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index def7435a1f..b9754eb1eb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,6 +17,7 @@ rust: - nightly matrix: allow_failures: + - rust: beta - rust: nightly fast_finish: true install: From c93d2baa912a3ff41fba711ae5b2ae387298c265 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Wed, 14 Aug 2019 18:23:26 +1000 Subject: [PATCH 34/62] Add network routes to API --- beacon_node/client/Cargo.toml | 1 + beacon_node/client/src/lib.rs | 3 +- beacon_node/client/src/local_bootstrap.rs | 16 ++++++ beacon_node/eth2-libp2p/src/behaviour.rs | 5 ++ beacon_node/eth2-libp2p/src/discovery.rs | 9 ++++ beacon_node/eth2-libp2p/src/lib.rs | 1 + beacon_node/eth2-libp2p/src/service.rs | 2 +- beacon_node/network/src/service.rs | 26 +++++++++- beacon_node/rest_api/Cargo.toml | 2 + beacon_node/rest_api/src/lib.rs | 12 ++++- beacon_node/rest_api/src/network.rs | 61 +++++++++++++++++++++++ 11 files changed, 134 insertions(+), 4 deletions(-) create mode 100644 beacon_node/rest_api/src/network.rs diff --git a/beacon_node/client/Cargo.toml b/beacon_node/client/Cargo.toml index 9aa3557a95..9d5d49e17b 100644 --- a/beacon_node/client/Cargo.toml +++ b/beacon_node/client/Cargo.toml @@ -7,6 +7,7 @@ edition = "2018" [dependencies] beacon_chain = { path = "../beacon_chain" } network = { path = "../network" } +eth2-libp2p = { path = "../eth2-libp2p" } rpc = { path = "../rpc" } rest_api = { path = "../rest_api" } prometheus = "^0.6" diff --git a/beacon_node/client/src/lib.rs b/beacon_node/client/src/lib.rs index e7c3d2d8ac..93e80df421 100644 --- a/beacon_node/client/src/lib.rs +++ b/beacon_node/client/src/lib.rs @@ -48,7 +48,7 @@ pub struct Client { impl Client where - T: BeaconChainTypes + InitialiseBeaconChain + Clone + 'static, + T: BeaconChainTypes + InitialiseBeaconChain + Clone + Send + Sync + 'static, { /// Generate an instance of the client. Spawn and link all internal sub-processes. pub fn new( @@ -122,6 +122,7 @@ where &client_config.rest_api, executor, beacon_chain.clone(), + network.clone(), client_config.db_path().expect("unable to read datadir"), &log, ) { diff --git a/beacon_node/client/src/local_bootstrap.rs b/beacon_node/client/src/local_bootstrap.rs index 79fad7ec2e..5fe5e1b4fb 100644 --- a/beacon_node/client/src/local_bootstrap.rs +++ b/beacon_node/client/src/local_bootstrap.rs @@ -1,3 +1,4 @@ +use eth2_libp2p::Enr; use reqwest::{Error as HttpError, Url}; use types::{BeaconBlock, BeaconState, Checkpoint, EthSpec, Slot}; @@ -18,6 +19,7 @@ pub struct BootstrapParams { pub finalized_state: BeaconState, pub genesis_block: BeaconBlock, pub genesis_state: BeaconState, + pub enr: Enr, } impl BootstrapParams { @@ -37,6 +39,7 @@ impl BootstrapParams { .map_err(|e| format!("Unable to get genesis block: {:?}", e))?, genesis_state: get_state(url.clone(), genesis_slot) .map_err(|e| format!("Unable to get genesis state: {:?}", e))?, + enr: get_enr(url.clone()).map_err(|e| format!("Unable to get ENR: {:?}", e))?, }) } } @@ -97,3 +100,16 @@ fn get_block(mut url: Url, slot: Slot) -> Result, Err .json() .map_err(Into::into) } + +fn get_enr(mut url: Url) -> Result { + url.path_segments_mut() + .map(|mut url| { + url.push("node").push("network").push("enr"); + }) + .map_err(|_| Error::UrlCannotBeBase)?; + + reqwest::get(url)? + .error_for_status()? + .json() + .map_err(Into::into) +} diff --git a/beacon_node/eth2-libp2p/src/behaviour.rs b/beacon_node/eth2-libp2p/src/behaviour.rs index b87f8a0613..24aacbfa12 100644 --- a/beacon_node/eth2-libp2p/src/behaviour.rs +++ b/beacon_node/eth2-libp2p/src/behaviour.rs @@ -7,6 +7,7 @@ use futures::prelude::*; use libp2p::{ core::identity::Keypair, discv5::Discv5Event, + enr::Enr, gossipsub::{Gossipsub, GossipsubEvent}, identify::{Identify, IdentifyEvent}, ping::{Ping, PingConfig, PingEvent}, @@ -78,6 +79,10 @@ impl Behaviour { log: behaviour_log, }) } + + pub fn discovery(&self) -> &Discovery { + &self.discovery + } } // Implement the NetworkBehaviourEventProcess trait so that we can derive NetworkBehaviour for Behaviour diff --git a/beacon_node/eth2-libp2p/src/discovery.rs b/beacon_node/eth2-libp2p/src/discovery.rs index ca98db3246..87d5dd5581 100644 --- a/beacon_node/eth2-libp2p/src/discovery.rs +++ b/beacon_node/eth2-libp2p/src/discovery.rs @@ -103,6 +103,10 @@ impl Discovery { }) } + pub fn local_enr(&self) -> &Enr { + self.discovery.local_enr() + } + /// Manually search for peers. This restarts the discovery round, sparking multiple rapid /// queries. pub fn discover_peers(&mut self) { @@ -120,6 +124,11 @@ impl Discovery { self.connected_peers.len() } + /// The current number of connected libp2p peers. + pub fn connected_peer_set(&self) -> &HashSet { + &self.connected_peers + } + /// Search for new peers using the underlying discovery mechanism. fn find_peers(&mut self) { // pick a random NodeId diff --git a/beacon_node/eth2-libp2p/src/lib.rs b/beacon_node/eth2-libp2p/src/lib.rs index 33d5ba9ed9..8c2644fbbc 100644 --- a/beacon_node/eth2-libp2p/src/lib.rs +++ b/beacon_node/eth2-libp2p/src/lib.rs @@ -17,6 +17,7 @@ pub use behaviour::PubsubMessage; pub use config::{ Config as NetworkConfig, BEACON_ATTESTATION_TOPIC, BEACON_BLOCK_TOPIC, SHARD_TOPIC_PREFIX, }; +pub use libp2p::enr::Enr; pub use libp2p::gossipsub::{Topic, TopicHash}; pub use libp2p::multiaddr; pub use libp2p::Multiaddr; diff --git a/beacon_node/eth2-libp2p/src/service.rs b/beacon_node/eth2-libp2p/src/service.rs index 316aa05798..4c343fa26c 100644 --- a/beacon_node/eth2-libp2p/src/service.rs +++ b/beacon_node/eth2-libp2p/src/service.rs @@ -15,7 +15,7 @@ use libp2p::core::{ transport::boxed::Boxed, upgrade::{InboundUpgradeExt, OutboundUpgradeExt}, }; -use libp2p::{core, secio, PeerId, Swarm, Transport}; +use libp2p::{core, enr::Enr, secio, PeerId, Swarm, Transport}; use slog::{debug, info, trace, warn}; use std::fs::File; use std::io::prelude::*; diff --git a/beacon_node/network/src/service.rs b/beacon_node/network/src/service.rs index e5ca2a9175..ed3c9da0b3 100644 --- a/beacon_node/network/src/service.rs +++ b/beacon_node/network/src/service.rs @@ -5,7 +5,7 @@ use beacon_chain::{BeaconChain, BeaconChainTypes}; use core::marker::PhantomData; use eth2_libp2p::Service as LibP2PService; use eth2_libp2p::Topic; -use eth2_libp2p::{Libp2pEvent, PeerId}; +use eth2_libp2p::{Enr, Libp2pEvent, PeerId}; use eth2_libp2p::{PubsubMessage, RPCEvent}; use futures::prelude::*; use futures::Stream; @@ -64,6 +64,30 @@ impl Service { Ok((Arc::new(network_service), network_send)) } + pub fn local_enr(&self) -> Enr { + self.libp2p_service + .lock() + .swarm + .discovery() + .local_enr() + .clone() + } + + pub fn connected_peers(&self) -> usize { + self.libp2p_service.lock().swarm.connected_peers() + } + + pub fn connected_peer_set(&self) -> Vec { + self.libp2p_service + .lock() + .swarm + .discovery() + .connected_peer_set() + .iter() + .cloned() + .collect() + } + pub fn libp2p_service(&self) -> Arc> { self.libp2p_service.clone() } diff --git a/beacon_node/rest_api/Cargo.toml b/beacon_node/rest_api/Cargo.toml index c7026014c4..cac196d9cb 100644 --- a/beacon_node/rest_api/Cargo.toml +++ b/beacon_node/rest_api/Cargo.toml @@ -7,6 +7,8 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] beacon_chain = { path = "../beacon_chain" } +network = { path = "../network" } +eth2-libp2p = { path = "../eth2-libp2p" } store = { path = "../store" } version = { path = "../version" } serde = { version = "1.0", features = ["derive"] } diff --git a/beacon_node/rest_api/src/lib.rs b/beacon_node/rest_api/src/lib.rs index e267ce313e..86b5b35db0 100644 --- a/beacon_node/rest_api/src/lib.rs +++ b/beacon_node/rest_api/src/lib.rs @@ -1,15 +1,18 @@ #[macro_use] extern crate lazy_static; +extern crate network as client_network; mod beacon; mod config; mod helpers; mod metrics; +mod network; mod node; mod spec; mod url_query; use beacon_chain::{BeaconChain, BeaconChainTypes}; +use client_network::Service as NetworkService; pub use config::Config as ApiConfig; use hyper::rt::Future; use hyper::service::service_fn_ok; @@ -68,10 +71,11 @@ impl From for ApiError { } } -pub fn start_server( +pub fn start_server( config: &ApiConfig, executor: &TaskExecutor, beacon_chain: Arc>, + network_service: Arc>, db_path: PathBuf, log: &slog::Logger, ) -> Result { @@ -99,6 +103,7 @@ pub fn start_server( let log = server_log.clone(); let beacon_chain = server_bc.clone(); let db_path = db_path.clone(); + let network_service = network_service.clone(); // Create a simple handler for the router, inject our stateful objects into the request. service_fn_ok(move |mut req| { @@ -109,6 +114,8 @@ pub fn start_server( req.extensions_mut() .insert::>>(beacon_chain.clone()); req.extensions_mut().insert::(db_path.clone()); + req.extensions_mut() + .insert::>>(network_service.clone()); let path = req.uri().path().to_string(); @@ -124,6 +131,9 @@ pub fn start_server( (&Method::GET, "/metrics") => metrics::get_prometheus::(req), (&Method::GET, "/node/version") => node::get_version(req), (&Method::GET, "/node/genesis_time") => node::get_genesis_time::(req), + (&Method::GET, "/node/network/enr") => network::get_enr::(req), + (&Method::GET, "/node/network/peer_count") => network::get_peer_count::(req), + (&Method::GET, "/node/network/peers") => network::get_peer_list::(req), (&Method::GET, "/spec") => spec::get_spec::(req), (&Method::GET, "/spec/slots_per_epoch") => spec::get_slots_per_epoch::(req), _ => Err(ApiError::MethodNotAllowed(path.clone())), diff --git a/beacon_node/rest_api/src/network.rs b/beacon_node/rest_api/src/network.rs new file mode 100644 index 0000000000..2fd88f4985 --- /dev/null +++ b/beacon_node/rest_api/src/network.rs @@ -0,0 +1,61 @@ +use crate::{success_response, ApiError, ApiResult, NetworkService}; +use beacon_chain::BeaconChainTypes; +use eth2_libp2p::{Enr, PeerId}; +use hyper::{Body, Request}; +use std::sync::Arc; + +/// HTTP handle to return the Discv5 ENR from the client's libp2p service. +/// +/// ENR is encoded as base64 string. +pub fn get_enr(req: Request) -> ApiResult { + let network = req + .extensions() + .get::>>() + .ok_or_else(|| ApiError::ServerError("NetworkService extension missing".to_string()))?; + + let enr: Enr = network.local_enr(); + + Ok(success_response(Body::from( + serde_json::to_string(&enr.to_base64()) + .map_err(|e| ApiError::ServerError(format!("Unable to serialize Enr: {:?}", e)))?, + ))) +} + +/// HTTP handle to return the number of peers connected in the client's libp2p service. +pub fn get_peer_count( + req: Request, +) -> ApiResult { + let network = req + .extensions() + .get::>>() + .ok_or_else(|| ApiError::ServerError("NetworkService extension missing".to_string()))?; + + let connected_peers: usize = network.connected_peers(); + + Ok(success_response(Body::from( + serde_json::to_string(&connected_peers) + .map_err(|e| ApiError::ServerError(format!("Unable to serialize Enr: {:?}", e)))?, + ))) +} + +/// HTTP handle to return the list of peers connected to the client's libp2p service. +/// +/// Peers are presented as a list of `PeerId::to_string()`. +pub fn get_peer_list(req: Request) -> ApiResult { + let network = req + .extensions() + .get::>>() + .ok_or_else(|| ApiError::ServerError("NetworkService extension missing".to_string()))?; + + let connected_peers: Vec = network + .connected_peer_set() + .iter() + .map(PeerId::to_string) + .collect(); + + Ok(success_response(Body::from( + serde_json::to_string(&connected_peers).map_err(|e| { + ApiError::ServerError(format!("Unable to serialize Vec: {:?}", e)) + })?, + ))) +} From bb166a25992535460aecdec2fe94403b1521254a Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Wed, 14 Aug 2019 18:58:01 +1000 Subject: [PATCH 35/62] Fix rustc warnings --- beacon_node/eth2-libp2p/src/behaviour.rs | 1 - beacon_node/eth2-libp2p/src/service.rs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/beacon_node/eth2-libp2p/src/behaviour.rs b/beacon_node/eth2-libp2p/src/behaviour.rs index 24aacbfa12..9158fe4858 100644 --- a/beacon_node/eth2-libp2p/src/behaviour.rs +++ b/beacon_node/eth2-libp2p/src/behaviour.rs @@ -7,7 +7,6 @@ use futures::prelude::*; use libp2p::{ core::identity::Keypair, discv5::Discv5Event, - enr::Enr, gossipsub::{Gossipsub, GossipsubEvent}, identify::{Identify, IdentifyEvent}, ping::{Ping, PingConfig, PingEvent}, diff --git a/beacon_node/eth2-libp2p/src/service.rs b/beacon_node/eth2-libp2p/src/service.rs index 4c343fa26c..316aa05798 100644 --- a/beacon_node/eth2-libp2p/src/service.rs +++ b/beacon_node/eth2-libp2p/src/service.rs @@ -15,7 +15,7 @@ use libp2p::core::{ transport::boxed::Boxed, upgrade::{InboundUpgradeExt, OutboundUpgradeExt}, }; -use libp2p::{core, enr::Enr, secio, PeerId, Swarm, Transport}; +use libp2p::{core, secio, PeerId, Swarm, Transport}; use slog::{debug, info, trace, warn}; use std::fs::File; use std::io::prelude::*; From c97b3b20cb1cfa6ae6ac5e9658b5f5a27f2bf4af Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Wed, 14 Aug 2019 20:58:51 +1000 Subject: [PATCH 36/62] Add best_slot method --- beacon_node/rest_api/src/beacon.rs | 15 +++++++++++++++ beacon_node/rest_api/src/lib.rs | 1 + 2 files changed, 16 insertions(+) diff --git a/beacon_node/rest_api/src/beacon.rs b/beacon_node/rest_api/src/beacon.rs index a2afb10010..66e31ae41c 100644 --- a/beacon_node/rest_api/src/beacon.rs +++ b/beacon_node/rest_api/src/beacon.rs @@ -6,6 +6,21 @@ use std::sync::Arc; use store::Store; use types::{BeaconBlock, BeaconState}; +/// HTTP handler to return a `BeaconBlock` at a given `root` or `slot`. +pub fn get_best_slot(req: Request) -> ApiResult { + let beacon_chain = req + .extensions() + .get::>>() + .ok_or_else(|| ApiError::ServerError("Beacon chain extension missing".to_string()))?; + + let slot = beacon_chain.head().beacon_state.slot; + + let json: String = serde_json::to_string(&slot) + .map_err(|e| ApiError::ServerError(format!("Unable to serialize Slot: {:?}", e)))?; + + Ok(success_response(Body::from(json))) +} + /// HTTP handler to return a `BeaconBlock` at a given `root` or `slot`. pub fn get_block(req: Request) -> ApiResult { let beacon_chain = req diff --git a/beacon_node/rest_api/src/lib.rs b/beacon_node/rest_api/src/lib.rs index 86b5b35db0..349a62c3fe 100644 --- a/beacon_node/rest_api/src/lib.rs +++ b/beacon_node/rest_api/src/lib.rs @@ -121,6 +121,7 @@ pub fn start_server( // Route the request to the correct handler. let result = match (req.method(), path.as_ref()) { + (&Method::GET, "/beacon/best_slot") => beacon::get_best_slot::(req), (&Method::GET, "/beacon/block") => beacon::get_block::(req), (&Method::GET, "/beacon/block_root") => beacon::get_block_root::(req), (&Method::GET, "/beacon/latest_finalized_checkpoint") => { From fda208b103284a156d801f2cea0e556642b10fe5 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Thu, 15 Aug 2019 12:48:34 +1000 Subject: [PATCH 37/62] Add --bootstrap arg to beacon node --- beacon_node/client/src/lib.rs | 2 +- beacon_node/src/main.rs | 19 ++++++++++++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/beacon_node/client/src/lib.rs b/beacon_node/client/src/lib.rs index 93e80df421..44b5c0ce3d 100644 --- a/beacon_node/client/src/lib.rs +++ b/beacon_node/client/src/lib.rs @@ -22,7 +22,7 @@ use tokio::timer::Interval; pub use beacon_chain::BeaconChainTypes; pub use beacon_chain_types::ClientType; pub use beacon_chain_types::InitialiseBeaconChain; -pub use config::Config as ClientConfig; +pub use config::{Config as ClientConfig, GenesisState}; pub use eth2_config::Eth2Config; /// Main beacon node client service. This provides the connection and initialisation of the clients diff --git a/beacon_node/src/main.rs b/beacon_node/src/main.rs index 9a52f2638c..862ca4a90e 100644 --- a/beacon_node/src/main.rs +++ b/beacon_node/src/main.rs @@ -1,7 +1,7 @@ mod run; use clap::{App, Arg}; -use client::{ClientConfig, Eth2Config}; +use client::{ClientConfig, Eth2Config, GenesisState}; use env_logger::{Builder, Env}; use eth2_config::{read_from_file, write_to_file}; use slog::{crit, o, warn, Drain, Level}; @@ -200,6 +200,16 @@ fn main() { .help("Sets the verbosity level") .takes_value(true), ) + /* + * Bootstrap. + */ + .arg( + Arg::with_name("bootstrap") + .long("bootstrap") + .value_name("HTTP_SERVER") + .help("Load the genesis state and libp2p address from the HTTP API of another Lighthouse node.") + .takes_value(true) + ) .get_matches(); // build the initial logger @@ -288,6 +298,13 @@ fn main() { } }; + // If the `--bootstrap` flag is provided, overwrite the default configuration. + if let Some(server) = matches.value_of("bootstrap") { + client_config.genesis_state = GenesisState::HttpBootstrap { + server: server.to_string(), + }; + } + let eth2_config_path = data_dir.join(ETH2_CONFIG_FILENAME); // Initialise the `Eth2Config`. From b24482674933406460b40f83fee00a98c6c84135 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Thu, 15 Aug 2019 13:58:04 +1000 Subject: [PATCH 38/62] Get bootstrapper working for ENR address --- beacon_node/client/src/beacon_chain_types.rs | 14 ++--- .../{local_bootstrap.rs => bootstrapper.rs} | 55 +++++++++++-------- beacon_node/client/src/lib.rs | 3 +- beacon_node/src/main.rs | 21 ++++++- 4 files changed, 62 insertions(+), 31 deletions(-) rename beacon_node/client/src/{local_bootstrap.rs => bootstrapper.rs} (64%) diff --git a/beacon_node/client/src/beacon_chain_types.rs b/beacon_node/client/src/beacon_chain_types.rs index a5b89b86a2..f2f95226ad 100644 --- a/beacon_node/client/src/beacon_chain_types.rs +++ b/beacon_node/client/src/beacon_chain_types.rs @@ -1,5 +1,5 @@ +use crate::bootstrapper::Bootstrapper; use crate::error::Result; -use crate::local_bootstrap::BootstrapParams; use crate::{config::GenesisState, ClientConfig}; use beacon_chain::{ lmd_ghost::{LmdGhost, ThreadSafeReducedTree}, @@ -7,7 +7,6 @@ use beacon_chain::{ store::Store, BeaconChain, BeaconChainTypes, }; -use reqwest::Url; use slog::{crit, info, Logger}; use slot_clock::SlotClock; use std::fs::File; @@ -77,13 +76,14 @@ where .map_err(|e| format!("Unable to parse YAML genesis state file: {:?}", e))? } GenesisState::HttpBootstrap { server } => { - let url: Url = - Url::parse(&server).map_err(|e| format!("Invalid bootstrap server url: {}", e))?; + let bootstrapper = Bootstrapper::from_server_string(server.to_string()) + .map_err(|e| format!("Failed to initialize bootstrap client: {}", e))?; - let params = BootstrapParams::from_http_api(url) - .map_err(|e| format!("Failed to bootstrap from HTTP server: {:?}", e))?; + let (state, _block) = bootstrapper + .genesis() + .map_err(|e| format!("Failed to bootstrap genesis state: {}", e))?; - params.genesis_state + state } }; diff --git a/beacon_node/client/src/local_bootstrap.rs b/beacon_node/client/src/bootstrapper.rs similarity index 64% rename from beacon_node/client/src/local_bootstrap.rs rename to beacon_node/client/src/bootstrapper.rs index 5fe5e1b4fb..9537f6f909 100644 --- a/beacon_node/client/src/local_bootstrap.rs +++ b/beacon_node/client/src/bootstrapper.rs @@ -14,33 +14,44 @@ impl From for Error { } } -pub struct BootstrapParams { - pub finalized_block: BeaconBlock, - pub finalized_state: BeaconState, - pub genesis_block: BeaconBlock, - pub genesis_state: BeaconState, - pub enr: Enr, +pub struct Bootstrapper { + url: Url, } -impl BootstrapParams { - pub fn from_http_api(url: Url) -> Result { - let slots_per_epoch = get_slots_per_epoch(url.clone()) - .map_err(|e| format!("Unable to get slots per epoch: {:?}", e))?; +impl Bootstrapper { + pub fn from_server_string(server: String) -> Result { + Ok(Self { + url: Url::parse(&server).map_err(|e| format!("Invalid bootstrap server url: {}", e))?, + }) + } + + pub fn enr(&self) -> Result { + get_enr(self.url.clone()).map_err(|e| format!("Unable to get ENR: {:?}", e)) + } + + pub fn genesis(&self) -> Result<(BeaconState, BeaconBlock), String> { let genesis_slot = Slot::new(0); - let finalized_slot = get_finalized_slot(url.clone(), slots_per_epoch.as_u64()) + + let block = get_block(self.url.clone(), genesis_slot) + .map_err(|e| format!("Unable to get genesis block: {:?}", e))?; + let state = get_state(self.url.clone(), genesis_slot) + .map_err(|e| format!("Unable to get genesis state: {:?}", e))?; + + Ok((state, block)) + } + + pub fn finalized(&self) -> Result<(BeaconState, BeaconBlock), 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))?; - Ok(Self { - finalized_block: get_block(url.clone(), finalized_slot) - .map_err(|e| format!("Unable to get finalized block: {:?}", e))?, - finalized_state: get_state(url.clone(), finalized_slot) - .map_err(|e| format!("Unable to get finalized state: {:?}", e))?, - genesis_block: get_block(url.clone(), genesis_slot) - .map_err(|e| format!("Unable to get genesis block: {:?}", e))?, - genesis_state: get_state(url.clone(), genesis_slot) - .map_err(|e| format!("Unable to get genesis state: {:?}", e))?, - enr: get_enr(url.clone()).map_err(|e| format!("Unable to get ENR: {:?}", e))?, - }) + let block = get_block(self.url.clone(), finalized_slot) + .map_err(|e| format!("Unable to get finalized block: {:?}", e))?; + let state = get_state(self.url.clone(), finalized_slot) + .map_err(|e| format!("Unable to get finalized state: {:?}", e))?; + + Ok((state, block)) } } diff --git a/beacon_node/client/src/lib.rs b/beacon_node/client/src/lib.rs index 44b5c0ce3d..798aedec92 100644 --- a/beacon_node/client/src/lib.rs +++ b/beacon_node/client/src/lib.rs @@ -1,8 +1,8 @@ extern crate slog; mod beacon_chain_types; +mod bootstrapper; mod config; -mod local_bootstrap; pub mod error; pub mod notifier; @@ -22,6 +22,7 @@ use tokio::timer::Interval; pub use beacon_chain::BeaconChainTypes; pub use beacon_chain_types::ClientType; pub use beacon_chain_types::InitialiseBeaconChain; +pub use bootstrapper::Bootstrapper; pub use config::{Config as ClientConfig, GenesisState}; pub use eth2_config::Eth2Config; diff --git a/beacon_node/src/main.rs b/beacon_node/src/main.rs index 862ca4a90e..5199bddb6e 100644 --- a/beacon_node/src/main.rs +++ b/beacon_node/src/main.rs @@ -1,7 +1,7 @@ mod run; use clap::{App, Arg}; -use client::{ClientConfig, Eth2Config, GenesisState}; +use client::{Bootstrapper, ClientConfig, Eth2Config, GenesisState}; use env_logger::{Builder, Env}; use eth2_config::{read_from_file, write_to_file}; use slog::{crit, o, warn, Drain, Level}; @@ -300,9 +300,28 @@ fn main() { // If the `--bootstrap` flag is provided, overwrite the default configuration. if let Some(server) = matches.value_of("bootstrap") { + // Set the genesis state source. client_config.genesis_state = GenesisState::HttpBootstrap { server: server.to_string(), }; + + let bootstrapper = match Bootstrapper::from_server_string(server.to_string()) { + Ok(b) => b, + Err(e) => { + crit!(log, "Failed to load bootstrapper"; "error" => format!("{:?}", e)); + return; + } + }; + + let enr = match bootstrapper.enr() { + Ok(b) => b, + Err(e) => { + crit!(log, "Failed to read ENR from bootstrap server"; "error" => format!("{:?}", e)); + return; + } + }; + + client_config.network.boot_nodes.push(enr); } let eth2_config_path = data_dir.join(ETH2_CONFIG_FILENAME); From 4678524659f4915037b7d64b9ce8f52498a7bb54 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Thu, 15 Aug 2019 14:52:00 +1000 Subject: [PATCH 39/62] Store intermediate states during block processing --- beacon_node/beacon_chain/src/beacon_chain.rs | 25 +++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index 891f76d373..7faca0dfd1 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -870,9 +870,16 @@ impl BeaconChain { let catchup_timer = metrics::start_timer(&metrics::BLOCK_PROCESSING_CATCHUP_STATE); + // Keep a list of any states that were "skipped" (block-less) in between the parent state + // slot and the block slot. These will need to be stored in the database. + let mut intermediate_states = vec![]; + // Transition the parent state to the block slot. let mut state: BeaconState = parent_state; - for _ in state.slot.as_u64()..block.slot.as_u64() { + for i in state.slot.as_u64()..block.slot.as_u64() { + if i > 0 { + intermediate_states.push(state.clone()); + } per_slot_processing(&mut state, &self.spec)?; } @@ -911,6 +918,22 @@ impl BeaconChain { let db_write_timer = metrics::start_timer(&metrics::BLOCK_PROCESSING_DB_WRITE); + // Store all the states between the parent block state and this blocks slot before storing + // the final state. + for (i, intermediate_state) in intermediate_states.iter().enumerate() { + // To avoid doing an unnecessary tree hash, use the following (slot + 1) state's + // state_roots field to find the root. + let following_state = match intermediate_states.get(i + 1) { + Some(following_state) => following_state, + None => &state, + }; + let intermediate_state_root = + following_state.get_state_root(intermediate_state.slot)?; + + self.store + .put(&intermediate_state_root, intermediate_state)?; + } + // Store the block and state. self.store.put(&block_root, &block)?; self.store.put(&state_root, &state)?; From ce37f958612229370791ae170e85780a07362656 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Thu, 15 Aug 2019 16:41:02 +1000 Subject: [PATCH 40/62] Allow bootstrapper to scrape libp2p address --- beacon_node/client/Cargo.toml | 1 + beacon_node/client/src/bootstrapper.rs | 29 +++++++++++++++++- beacon_node/client/src/config.rs | 42 ++++++++++++++++++++++++-- beacon_node/eth2-libp2p/src/lib.rs | 2 +- beacon_node/network/src/service.rs | 14 ++++++++- beacon_node/rest_api/src/lib.rs | 3 ++ beacon_node/rest_api/src/network.rs | 21 ++++++++++++- beacon_node/src/main.rs | 28 +---------------- 8 files changed, 107 insertions(+), 33 deletions(-) diff --git a/beacon_node/client/Cargo.toml b/beacon_node/client/Cargo.toml index 9d5d49e17b..9b5a9cf42c 100644 --- a/beacon_node/client/Cargo.toml +++ b/beacon_node/client/Cargo.toml @@ -28,3 +28,4 @@ dirs = "1.0.3" exit-future = "0.1.3" futures = "0.1.25" reqwest = "0.9" +url = "1.2" diff --git a/beacon_node/client/src/bootstrapper.rs b/beacon_node/client/src/bootstrapper.rs index 9537f6f909..1fd8f16592 100644 --- a/beacon_node/client/src/bootstrapper.rs +++ b/beacon_node/client/src/bootstrapper.rs @@ -1,6 +1,8 @@ -use eth2_libp2p::Enr; +use eth2_libp2p::{Enr, Multiaddr}; use reqwest::{Error as HttpError, Url}; +use std::net::Ipv4Addr; use types::{BeaconBlock, BeaconState, Checkpoint, EthSpec, Slot}; +use url::Host; #[derive(Debug)] enum Error { @@ -25,10 +27,22 @@ impl Bootstrapper { }) } + pub fn server_ipv4_addr(&self) -> Option { + match self.url.host()? { + Host::Ipv4(addr) => Some(addr), + _ => None, + } + } + pub fn enr(&self) -> Result { get_enr(self.url.clone()).map_err(|e| format!("Unable to get ENR: {:?}", e)) } + pub fn listen_addresses(&self) -> Result, String> { + get_listen_addresses(self.url.clone()) + .map_err(|e| format!("Unable to get listen addresses: {:?}", e)) + } + pub fn genesis(&self) -> Result<(BeaconState, BeaconBlock), String> { let genesis_slot = Slot::new(0); @@ -124,3 +138,16 @@ fn get_enr(mut url: Url) -> Result { .json() .map_err(Into::into) } + +fn get_listen_addresses(mut url: Url) -> Result, Error> { + url.path_segments_mut() + .map(|mut url| { + url.push("node").push("network").push("listen_addresses"); + }) + .map_err(|_| Error::UrlCannotBeBase)?; + + reqwest::get(url)? + .error_for_status()? + .json() + .map_err(Into::into) +} diff --git a/beacon_node/client/src/config.rs b/beacon_node/client/src/config.rs index 0d5d5f81de..5dd0eef52f 100644 --- a/beacon_node/client/src/config.rs +++ b/beacon_node/client/src/config.rs @@ -1,8 +1,9 @@ -use crate::Eth2Config; +use crate::{Bootstrapper, Eth2Config}; use clap::ArgMatches; +use eth2_libp2p::multiaddr::{Multiaddr, Protocol}; use network::NetworkConfig; use serde_derive::{Deserialize, Serialize}; -use slog::{info, o, Drain}; +use slog::{info, o, warn, Drain}; use std::fs::{self, OpenOptions}; use std::path::PathBuf; use std::sync::Mutex; @@ -149,6 +150,43 @@ impl Config { self.update_logger(log)?; }; + // If the `--bootstrap` flag is provided, overwrite the default configuration. + if let Some(server) = args.value_of("bootstrap") { + do_bootstrapping(self, server.to_string(), &log)?; + } + Ok(()) } } + +fn do_bootstrapping(config: &mut Config, server: String, log: &slog::Logger) -> Result<(), String> { + // Set the genesis state source. + config.genesis_state = GenesisState::HttpBootstrap { + server: server.to_string(), + }; + + let bootstrapper = Bootstrapper::from_server_string(server.to_string())?; + + config.network.boot_nodes.push(bootstrapper.enr()?); + + if let Some(server_ip) = bootstrapper.server_ipv4_addr() { + let server_multiaddr: Multiaddr = bootstrapper + .listen_addresses()? + .first() + .ok_or_else(|| "Bootstrap peer returned an empty list of listen addresses")? + // Iterate through the components of the Multiaddr, replacing any Ipv4 address with the + // server address. + .iter() + .map(|protocol| match protocol { + Protocol::Ip4(_) => Protocol::Ip4(server_ip), + _ => protocol, + }) + .collect::(); + + config.network.libp2p_nodes.push(server_multiaddr); + } else { + warn!(log, "Unable to determine bootstrap server Ipv4 address. Unable to add server as libp2p peer."); + } + + Ok(()) +} diff --git a/beacon_node/eth2-libp2p/src/lib.rs b/beacon_node/eth2-libp2p/src/lib.rs index 8c2644fbbc..4c84469cea 100644 --- a/beacon_node/eth2-libp2p/src/lib.rs +++ b/beacon_node/eth2-libp2p/src/lib.rs @@ -23,7 +23,7 @@ pub use libp2p::multiaddr; pub use libp2p::Multiaddr; pub use libp2p::{ gossipsub::{GossipsubConfig, GossipsubConfigBuilder}, - PeerId, + PeerId, Swarm, }; pub use rpc::RPCEvent; pub use service::Libp2pEvent; diff --git a/beacon_node/network/src/service.rs b/beacon_node/network/src/service.rs index ed3c9da0b3..4bec038309 100644 --- a/beacon_node/network/src/service.rs +++ b/beacon_node/network/src/service.rs @@ -5,7 +5,7 @@ use beacon_chain::{BeaconChain, BeaconChainTypes}; use core::marker::PhantomData; use eth2_libp2p::Service as LibP2PService; use eth2_libp2p::Topic; -use eth2_libp2p::{Enr, Libp2pEvent, PeerId}; +use eth2_libp2p::{Enr, Libp2pEvent, Multiaddr, PeerId, Swarm}; use eth2_libp2p::{PubsubMessage, RPCEvent}; use futures::prelude::*; use futures::Stream; @@ -64,6 +64,8 @@ impl Service { Ok((Arc::new(network_service), network_send)) } + /// Returns the local ENR from the underlying Discv5 behaviour that external peers may connect + /// to. pub fn local_enr(&self) -> Enr { self.libp2p_service .lock() @@ -73,10 +75,19 @@ impl Service { .clone() } + /// Returns the list of `Multiaddr` that the underlying libp2p instance is listening on. + pub fn listen_multiaddrs(&self) -> Vec { + Swarm::listeners(&self.libp2p_service.lock().swarm) + .cloned() + .collect() + } + + /// Returns the number of libp2p connected peers. pub fn connected_peers(&self) -> usize { self.libp2p_service.lock().swarm.connected_peers() } + /// Returns the set of `PeerId` that are connected via libp2p. pub fn connected_peer_set(&self) -> Vec { self.libp2p_service .lock() @@ -88,6 +99,7 @@ impl Service { .collect() } + /// Provides a reference to the underlying libp2p service. pub fn libp2p_service(&self) -> Arc> { self.libp2p_service.clone() } diff --git a/beacon_node/rest_api/src/lib.rs b/beacon_node/rest_api/src/lib.rs index 349a62c3fe..8ef48ad72c 100644 --- a/beacon_node/rest_api/src/lib.rs +++ b/beacon_node/rest_api/src/lib.rs @@ -135,6 +135,9 @@ pub fn start_server( (&Method::GET, "/node/network/enr") => network::get_enr::(req), (&Method::GET, "/node/network/peer_count") => network::get_peer_count::(req), (&Method::GET, "/node/network/peers") => network::get_peer_list::(req), + (&Method::GET, "/node/network/listen_addresses") => { + network::get_listen_addresses::(req) + } (&Method::GET, "/spec") => spec::get_spec::(req), (&Method::GET, "/spec/slots_per_epoch") => spec::get_slots_per_epoch::(req), _ => Err(ApiError::MethodNotAllowed(path.clone())), diff --git a/beacon_node/rest_api/src/network.rs b/beacon_node/rest_api/src/network.rs index 2fd88f4985..0e2448270c 100644 --- a/beacon_node/rest_api/src/network.rs +++ b/beacon_node/rest_api/src/network.rs @@ -1,9 +1,28 @@ use crate::{success_response, ApiError, ApiResult, NetworkService}; use beacon_chain::BeaconChainTypes; -use eth2_libp2p::{Enr, PeerId}; +use eth2_libp2p::{Enr, Multiaddr, PeerId}; use hyper::{Body, Request}; use std::sync::Arc; +/// HTTP handle to return the list of libp2p multiaddr the client is listening on. +/// +/// Returns a list of `Multiaddr`, serialized according to their `serde` impl. +pub fn get_listen_addresses( + req: Request, +) -> ApiResult { + let network = req + .extensions() + .get::>>() + .ok_or_else(|| ApiError::ServerError("NetworkService extension missing".to_string()))?; + + let multiaddresses: Vec = network.listen_multiaddrs(); + + Ok(success_response(Body::from( + serde_json::to_string(&multiaddresses) + .map_err(|e| ApiError::ServerError(format!("Unable to serialize Enr: {:?}", e)))?, + ))) +} + /// HTTP handle to return the Discv5 ENR from the client's libp2p service. /// /// ENR is encoded as base64 string. diff --git a/beacon_node/src/main.rs b/beacon_node/src/main.rs index 5199bddb6e..ae48f692b6 100644 --- a/beacon_node/src/main.rs +++ b/beacon_node/src/main.rs @@ -1,7 +1,7 @@ mod run; use clap::{App, Arg}; -use client::{Bootstrapper, ClientConfig, Eth2Config, GenesisState}; +use client::{ClientConfig, Eth2Config}; use env_logger::{Builder, Env}; use eth2_config::{read_from_file, write_to_file}; use slog::{crit, o, warn, Drain, Level}; @@ -298,32 +298,6 @@ fn main() { } }; - // If the `--bootstrap` flag is provided, overwrite the default configuration. - if let Some(server) = matches.value_of("bootstrap") { - // Set the genesis state source. - client_config.genesis_state = GenesisState::HttpBootstrap { - server: server.to_string(), - }; - - let bootstrapper = match Bootstrapper::from_server_string(server.to_string()) { - Ok(b) => b, - Err(e) => { - crit!(log, "Failed to load bootstrapper"; "error" => format!("{:?}", e)); - return; - } - }; - - let enr = match bootstrapper.enr() { - Ok(b) => b, - Err(e) => { - crit!(log, "Failed to read ENR from bootstrap server"; "error" => format!("{:?}", e)); - return; - } - }; - - client_config.network.boot_nodes.push(enr); - } - let eth2_config_path = data_dir.join(ETH2_CONFIG_FILENAME); // Initialise the `Eth2Config`. From 7cd963e6bb7ad35458defc94f3c6a24eb24f249c Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Thu, 15 Aug 2019 18:48:39 +1000 Subject: [PATCH 41/62] Update bootstrapper libp2p address finding --- beacon_node/client/src/bootstrapper.rs | 31 +++++++++++++++++++++++++- beacon_node/client/src/config.rs | 26 +++++++++------------ beacon_node/src/main.rs | 5 +++++ beacon_node/src/run.rs | 7 +----- 4 files changed, 46 insertions(+), 23 deletions(-) diff --git a/beacon_node/client/src/bootstrapper.rs b/beacon_node/client/src/bootstrapper.rs index 1fd8f16592..2c8cf6afc2 100644 --- a/beacon_node/client/src/bootstrapper.rs +++ b/beacon_node/client/src/bootstrapper.rs @@ -1,5 +1,9 @@ -use eth2_libp2p::{Enr, Multiaddr}; +use eth2_libp2p::{ + multiaddr::{Multiaddr, Protocol}, + Enr, +}; use reqwest::{Error as HttpError, Url}; +use std::borrow::Cow; use std::net::Ipv4Addr; use types::{BeaconBlock, BeaconState, Checkpoint, EthSpec, Slot}; use url::Host; @@ -27,6 +31,31 @@ impl Bootstrapper { }) } + pub fn best_effort_multiaddr(&self) -> Option { + let tcp_port = self.first_listening_tcp_port()?; + + 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) + } + + fn first_listening_tcp_port(&self) -> Option { + self.listen_addresses().ok()?.iter().find_map(|multiaddr| { + multiaddr.iter().find_map(|protocol| match protocol { + Protocol::Tcp(port) => Some(port), + _ => None, + }) + }) + } + pub fn server_ipv4_addr(&self) -> Option { match self.url.host()? { Host::Ipv4(addr) => Some(addr), diff --git a/beacon_node/client/src/config.rs b/beacon_node/client/src/config.rs index 5dd0eef52f..1a985fb4af 100644 --- a/beacon_node/client/src/config.rs +++ b/beacon_node/client/src/config.rs @@ -1,6 +1,5 @@ use crate::{Bootstrapper, Eth2Config}; use clap::ArgMatches; -use eth2_libp2p::multiaddr::{Multiaddr, Protocol}; use network::NetworkConfig; use serde_derive::{Deserialize, Serialize}; use slog::{info, o, warn, Drain}; @@ -169,23 +168,18 @@ fn do_bootstrapping(config: &mut Config, server: String, log: &slog::Logger) -> config.network.boot_nodes.push(bootstrapper.enr()?); - if let Some(server_ip) = bootstrapper.server_ipv4_addr() { - let server_multiaddr: Multiaddr = bootstrapper - .listen_addresses()? - .first() - .ok_or_else(|| "Bootstrap peer returned an empty list of listen addresses")? - // Iterate through the components of the Multiaddr, replacing any Ipv4 address with the - // server address. - .iter() - .map(|protocol| match protocol { - Protocol::Ip4(_) => Protocol::Ip4(server_ip), - _ => protocol, - }) - .collect::(); - + if let Some(server_multiaddr) = bootstrapper.best_effort_multiaddr() { + info!( + log, + "Estimated bootstrapper libp2p address"; + "multiaddr" => format!("{:?}", server_multiaddr) + ); config.network.libp2p_nodes.push(server_multiaddr); } else { - warn!(log, "Unable to determine bootstrap server Ipv4 address. Unable to add server as libp2p peer."); + warn!( + log, + "Unable to estimate a bootstrapper libp2p address, this node may not find any peers." + ); } Ok(()) diff --git a/beacon_node/src/main.rs b/beacon_node/src/main.rs index ae48f692b6..04366baa7a 100644 --- a/beacon_node/src/main.rs +++ b/beacon_node/src/main.rs @@ -237,6 +237,11 @@ fn main() { let mut log = slog::Logger::root(drain.fuse(), o!()); + warn!( + log, + "Ethereum 2.0 is pre-release. This software is experimental." + ); + let data_dir = match matches .value_of("datadir") .and_then(|v| Some(PathBuf::from(v))) diff --git a/beacon_node/src/run.rs b/beacon_node/src/run.rs index c16d23e5f1..5066231d55 100644 --- a/beacon_node/src/run.rs +++ b/beacon_node/src/run.rs @@ -4,7 +4,7 @@ use client::{ }; use futures::sync::oneshot; use futures::Future; -use slog::{error, info, warn}; +use slog::{error, info}; use std::cell::RefCell; use std::path::Path; use std::path::PathBuf; @@ -42,11 +42,6 @@ pub fn run_beacon_node( let other_client_config = client_config.clone(); - warn!( - log, - "Ethereum 2.0 is pre-release. This software is experimental." - ); - info!( log, "BeaconNode init"; From a8daf46d5f557d45d1add6c974d654d366e31a6f Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Wed, 21 Aug 2019 14:48:49 +1000 Subject: [PATCH 42/62] Add comments --- beacon_node/client/src/bootstrapper.rs | 21 +++++++++++++++++++++ beacon_node/client/src/config.rs | 2 ++ 2 files changed, 23 insertions(+) diff --git a/beacon_node/client/src/bootstrapper.rs b/beacon_node/client/src/bootstrapper.rs index 2c8cf6afc2..9843ceec77 100644 --- a/beacon_node/client/src/bootstrapper.rs +++ b/beacon_node/client/src/bootstrapper.rs @@ -20,17 +20,31 @@ impl From for Error { } } +/// 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`. pub fn from_server_string(server: String) -> Result { Ok(Self { url: Url::parse(&server).map_err(|e| format!("Invalid bootstrap server url: {}", e))?, }) } + /// 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 it's 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) -> Option { let tcp_port = self.first_listening_tcp_port()?; @@ -47,6 +61,8 @@ impl Bootstrapper { Some(multiaddr) } + /// Reads the server's listening libp2p addresses and returns the first TCP port protocol it + /// finds, if any. fn first_listening_tcp_port(&self) -> Option { self.listen_addresses().ok()?.iter().find_map(|multiaddr| { multiaddr.iter().find_map(|protocol| match protocol { @@ -56,6 +72,7 @@ impl Bootstrapper { }) } + /// Returns the IPv4 address of the server URL, unless it contains a FQDN. pub fn server_ipv4_addr(&self) -> Option { match self.url.host()? { Host::Ipv4(addr) => Some(addr), @@ -63,15 +80,18 @@ impl Bootstrapper { } } + /// Returns the servers ENR address. pub fn enr(&self) -> Result { get_enr(self.url.clone()).map_err(|e| format!("Unable to get ENR: {:?}", e)) } + /// Returns the servers listening libp2p addresses. pub fn listen_addresses(&self) -> Result, String> { get_listen_addresses(self.url.clone()) .map_err(|e| format!("Unable to get listen addresses: {:?}", e)) } + /// Returns the genesis block and state. pub fn genesis(&self) -> Result<(BeaconState, BeaconBlock), String> { let genesis_slot = Slot::new(0); @@ -83,6 +103,7 @@ impl Bootstrapper { Ok((state, block)) } + /// Returns the most recent finalized state and block. pub fn finalized(&self) -> Result<(BeaconState, BeaconBlock), String> { let slots_per_epoch = get_slots_per_epoch(self.url.clone()) .map_err(|e| format!("Unable to get slots per epoch: {:?}", e))?; diff --git a/beacon_node/client/src/config.rs b/beacon_node/client/src/config.rs index 1a985fb4af..ea8186dbc9 100644 --- a/beacon_node/client/src/config.rs +++ b/beacon_node/client/src/config.rs @@ -158,6 +158,8 @@ impl Config { } } +/// Perform the HTTP bootstrapping procedure, reading an ENR and multiaddr from the HTTP server and +/// adding them to the `config`. fn do_bootstrapping(config: &mut Config, server: String, log: &slog::Logger) -> Result<(), String> { // Set the genesis state source. config.genesis_state = GenesisState::HttpBootstrap { From b912e26b7938270392e251f213cc50278aa0cc99 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Thu, 22 Aug 2019 14:37:47 +1000 Subject: [PATCH 43/62] Tidy API to be more consistent with recent decisions --- beacon_node/eth2-libp2p/src/service.rs | 4 +- beacon_node/network/src/service.rs | 5 ++ beacon_node/rest_api/src/beacon.rs | 64 +++++++++++++++++++++----- beacon_node/rest_api/src/helpers.rs | 13 ++++-- beacon_node/rest_api/src/lib.rs | 15 +++--- beacon_node/rest_api/src/network.rs | 17 +++++++ 6 files changed, 92 insertions(+), 26 deletions(-) diff --git a/beacon_node/eth2-libp2p/src/service.rs b/beacon_node/eth2-libp2p/src/service.rs index 316aa05798..e1e112e2d8 100644 --- a/beacon_node/eth2-libp2p/src/service.rs +++ b/beacon_node/eth2-libp2p/src/service.rs @@ -33,7 +33,7 @@ pub struct Service { //TODO: Make this private pub swarm: Swarm, /// This node's PeerId. - _local_peer_id: PeerId, + pub local_peer_id: PeerId, /// The libp2p logger handle. pub log: slog::Logger, } @@ -113,7 +113,7 @@ impl Service { info!(log, "Subscribed to topics: {:?}", subscribed_topics); Ok(Service { - _local_peer_id: local_peer_id, + local_peer_id, swarm, log, }) diff --git a/beacon_node/network/src/service.rs b/beacon_node/network/src/service.rs index 4bec038309..dc7e941409 100644 --- a/beacon_node/network/src/service.rs +++ b/beacon_node/network/src/service.rs @@ -75,6 +75,11 @@ impl Service { .clone() } + /// Returns the local libp2p PeerID. + pub fn local_peer_id(&self) -> PeerId { + self.libp2p_service.lock().local_peer_id.clone() + } + /// Returns the list of `Multiaddr` that the underlying libp2p instance is listening on. pub fn listen_multiaddrs(&self) -> Vec { Swarm::listeners(&self.libp2p_service.lock().swarm) diff --git a/beacon_node/rest_api/src/beacon.rs b/beacon_node/rest_api/src/beacon.rs index 66e31ae41c..88427c9a4b 100644 --- a/beacon_node/rest_api/src/beacon.rs +++ b/beacon_node/rest_api/src/beacon.rs @@ -2,25 +2,44 @@ use super::{success_response, ApiResult}; use crate::{helpers::*, ApiError, UrlQuery}; use beacon_chain::{BeaconChain, BeaconChainTypes}; use hyper::{Body, Request}; +use serde::Serialize; use std::sync::Arc; use store::Store; -use types::{BeaconBlock, BeaconState}; +use types::{BeaconBlock, BeaconState, EthSpec, Hash256, Slot}; + +#[derive(Serialize)] +struct HeadResponse { + pub slot: Slot, + pub block_root: Hash256, + pub state_root: Hash256, +} /// HTTP handler to return a `BeaconBlock` at a given `root` or `slot`. -pub fn get_best_slot(req: Request) -> ApiResult { +pub fn get_head(req: Request) -> ApiResult { let beacon_chain = req .extensions() .get::>>() .ok_or_else(|| ApiError::ServerError("Beacon chain extension missing".to_string()))?; - let slot = beacon_chain.head().beacon_state.slot; + let head = HeadResponse { + slot: beacon_chain.head().beacon_state.slot, + block_root: beacon_chain.head().beacon_block_root, + state_root: beacon_chain.head().beacon_state_root, + }; - let json: String = serde_json::to_string(&slot) - .map_err(|e| ApiError::ServerError(format!("Unable to serialize Slot: {:?}", e)))?; + let json: String = serde_json::to_string(&head) + .map_err(|e| ApiError::ServerError(format!("Unable to serialize HeadResponse: {:?}", e)))?; Ok(success_response(Body::from(json))) } +#[derive(Serialize)] +#[serde(bound = "T: EthSpec")] +struct BlockResponse { + pub root: Hash256, + pub beacon_block: BeaconBlock, +} + /// HTTP handler to return a `BeaconBlock` at a given `root` or `slot`. pub fn get_block(req: Request) -> ApiResult { let beacon_chain = req @@ -58,8 +77,14 @@ pub fn get_block(req: Request) -> ApiResult )) })?; - let json: String = serde_json::to_string(&block) - .map_err(|e| ApiError::ServerError(format!("Unable to serialize BeaconBlock: {:?}", e)))?; + let response = BlockResponse { + root: block_root, + beacon_block: block, + }; + + let json: String = serde_json::to_string(&response).map_err(|e| { + ApiError::ServerError(format!("Unable to serialize BlockResponse: {:?}", e)) + })?; Ok(success_response(Body::from(json))) } @@ -89,6 +114,13 @@ pub fn get_block_root(req: Request) -> ApiR Ok(success_response(Body::from(json))) } +#[derive(Serialize)] +#[serde(bound = "T: EthSpec")] +struct StateResponse { + pub root: Hash256, + pub beacon_state: BeaconState, +} + /// HTTP handler to return a `BeaconState` at a given `root` or `slot`. /// /// Will not return a state if the request slot is in the future. Will return states higher than @@ -102,21 +134,29 @@ pub fn get_state(req: Request) -> ApiResult let query_params = ["root", "slot"]; let (key, value) = UrlQuery::from_request(&req)?.first_of(&query_params)?; - let state: BeaconState = match (key.as_ref(), value) { + let (root, state): (Hash256, BeaconState) = match (key.as_ref(), value) { ("slot", value) => state_at_slot(&beacon_chain, parse_slot(&value)?)?, ("root", value) => { let root = &parse_root(&value)?; - beacon_chain + let state = beacon_chain .store .get(root)? - .ok_or_else(|| ApiError::NotFound(format!("No state for root: {}", root)))? + .ok_or_else(|| ApiError::NotFound(format!("No state for root: {}", root)))?; + + (*root, state) } _ => return Err(ApiError::ServerError("Unexpected query parameter".into())), }; - let json: String = serde_json::to_string(&state) - .map_err(|e| ApiError::ServerError(format!("Unable to serialize BeaconState: {:?}", e)))?; + let response = StateResponse { + root, + beacon_state: state, + }; + + let json: String = serde_json::to_string(&response).map_err(|e| { + ApiError::ServerError(format!("Unable to serialize StateResponse: {:?}", e)) + })?; Ok(success_response(Body::from(json))) } diff --git a/beacon_node/rest_api/src/helpers.rs b/beacon_node/rest_api/src/helpers.rs index 2a429076c7..a65c7c1ac9 100644 --- a/beacon_node/rest_api/src/helpers.rs +++ b/beacon_node/rest_api/src/helpers.rs @@ -31,22 +31,25 @@ pub fn parse_root(string: &str) -> Result { } } -/// Returns a `BeaconState` in the canonical chain of `beacon_chain` at the given `slot`, if -/// possible. +/// Returns a `BeaconState` and it's root in the canonical chain of `beacon_chain` at the given +/// `slot`, if possible. /// /// Will not return a state if the request slot is in the future. Will return states higher than /// the current head by skipping slots. pub fn state_at_slot( beacon_chain: &BeaconChain, slot: Slot, -) -> Result, ApiError> { +) -> Result<(Hash256, BeaconState), ApiError> { let head_state = &beacon_chain.head().beacon_state; if head_state.slot == slot { // The request slot is the same as the best block (head) slot. // I'm not sure if this `.clone()` will be optimized out. If not, it seems unnecessary. - Ok(beacon_chain.head().beacon_state.clone()) + Ok(( + beacon_chain.head().beacon_state_root, + beacon_chain.head().beacon_state.clone(), + )) } else { let root = state_root_at_slot(beacon_chain, slot)?; @@ -55,7 +58,7 @@ pub fn state_at_slot( .get(&root)? .ok_or_else(|| ApiError::NotFound(format!("Unable to find state at root {}", root)))?; - Ok(state) + Ok((root, state)) } } diff --git a/beacon_node/rest_api/src/lib.rs b/beacon_node/rest_api/src/lib.rs index 8ef48ad72c..839aa7abca 100644 --- a/beacon_node/rest_api/src/lib.rs +++ b/beacon_node/rest_api/src/lib.rs @@ -121,7 +121,7 @@ pub fn start_server( // Route the request to the correct handler. let result = match (req.method(), path.as_ref()) { - (&Method::GET, "/beacon/best_slot") => beacon::get_best_slot::(req), + (&Method::GET, "/beacon/head") => beacon::get_head::(req), (&Method::GET, "/beacon/block") => beacon::get_block::(req), (&Method::GET, "/beacon/block_root") => beacon::get_block_root::(req), (&Method::GET, "/beacon/latest_finalized_checkpoint") => { @@ -130,14 +130,15 @@ pub fn start_server( (&Method::GET, "/beacon/state") => beacon::get_state::(req), (&Method::GET, "/beacon/state_root") => beacon::get_state_root::(req), (&Method::GET, "/metrics") => metrics::get_prometheus::(req), - (&Method::GET, "/node/version") => node::get_version(req), - (&Method::GET, "/node/genesis_time") => node::get_genesis_time::(req), - (&Method::GET, "/node/network/enr") => network::get_enr::(req), - (&Method::GET, "/node/network/peer_count") => network::get_peer_count::(req), - (&Method::GET, "/node/network/peers") => network::get_peer_list::(req), - (&Method::GET, "/node/network/listen_addresses") => { + (&Method::GET, "/network/enr") => network::get_enr::(req), + (&Method::GET, "/network/peer_count") => network::get_peer_count::(req), + (&Method::GET, "/network/peer_id") => network::get_peer_id::(req), + (&Method::GET, "/network/peers") => network::get_peer_list::(req), + (&Method::GET, "/network/listen_addresses") => { network::get_listen_addresses::(req) } + (&Method::GET, "/node/version") => node::get_version(req), + (&Method::GET, "/node/genesis_time") => node::get_genesis_time::(req), (&Method::GET, "/spec") => spec::get_spec::(req), (&Method::GET, "/spec/slots_per_epoch") => spec::get_slots_per_epoch::(req), _ => Err(ApiError::MethodNotAllowed(path.clone())), diff --git a/beacon_node/rest_api/src/network.rs b/beacon_node/rest_api/src/network.rs index 0e2448270c..154cd142d4 100644 --- a/beacon_node/rest_api/src/network.rs +++ b/beacon_node/rest_api/src/network.rs @@ -40,6 +40,23 @@ pub fn get_enr(req: Request) ))) } +/// HTTP handle to return the `PeerId` from the client's libp2p service. +/// +/// PeerId is encoded as base58 string. +pub fn get_peer_id(req: Request) -> ApiResult { + let network = req + .extensions() + .get::>>() + .ok_or_else(|| ApiError::ServerError("NetworkService extension missing".to_string()))?; + + let peer_id: PeerId = network.local_peer_id(); + + Ok(success_response(Body::from( + serde_json::to_string(&peer_id.to_base58()) + .map_err(|e| ApiError::ServerError(format!("Unable to serialize Enr: {:?}", e)))?, + ))) +} + /// HTTP handle to return the number of peers connected in the client's libp2p service. pub fn get_peer_count( req: Request, From 5a34f86e770dedae20d4c383293bdb8cce722000 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Thu, 22 Aug 2019 16:14:51 +1000 Subject: [PATCH 44/62] Address some review comments --- beacon_node/client/src/bootstrapper.rs | 16 ++++++++-------- beacon_node/rest_api/src/beacon.rs | 22 ++++++---------------- beacon_node/rest_api/src/helpers.rs | 15 +++++++++++++++ 3 files changed, 29 insertions(+), 24 deletions(-) diff --git a/beacon_node/client/src/bootstrapper.rs b/beacon_node/client/src/bootstrapper.rs index 9843ceec77..19f13e2da8 100644 --- a/beacon_node/client/src/bootstrapper.rs +++ b/beacon_node/client/src/bootstrapper.rs @@ -10,7 +10,7 @@ use url::Host; #[derive(Debug)] enum Error { - UrlCannotBeBase, + InvalidUrl, HttpError(HttpError), } @@ -38,7 +38,7 @@ impl 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 it's listening libp2p addresses. + /// 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. /// @@ -124,7 +124,7 @@ fn get_slots_per_epoch(mut url: Url) -> Result { .map(|mut url| { url.push("spec").push("slots_per_epoch"); }) - .map_err(|_| Error::UrlCannotBeBase)?; + .map_err(|_| Error::InvalidUrl)?; reqwest::get(url)? .error_for_status()? @@ -137,7 +137,7 @@ fn get_finalized_slot(mut url: Url, slots_per_epoch: u64) -> Result .map(|mut url| { url.push("beacon").push("latest_finalized_checkpoint"); }) - .map_err(|_| Error::UrlCannotBeBase)?; + .map_err(|_| Error::InvalidUrl)?; let checkpoint: Checkpoint = reqwest::get(url)?.error_for_status()?.json()?; @@ -149,7 +149,7 @@ fn get_state(mut url: Url, slot: Slot) -> Result, Err .map(|mut url| { url.push("beacon").push("state"); }) - .map_err(|_| Error::UrlCannotBeBase)?; + .map_err(|_| Error::InvalidUrl)?; url.query_pairs_mut() .append_pair("slot", &format!("{}", slot.as_u64())); @@ -165,7 +165,7 @@ fn get_block(mut url: Url, slot: Slot) -> Result, Err .map(|mut url| { url.push("beacon").push("block"); }) - .map_err(|_| Error::UrlCannotBeBase)?; + .map_err(|_| Error::InvalidUrl)?; url.query_pairs_mut() .append_pair("slot", &format!("{}", slot.as_u64())); @@ -181,7 +181,7 @@ fn get_enr(mut url: Url) -> Result { .map(|mut url| { url.push("node").push("network").push("enr"); }) - .map_err(|_| Error::UrlCannotBeBase)?; + .map_err(|_| Error::InvalidUrl)?; reqwest::get(url)? .error_for_status()? @@ -194,7 +194,7 @@ fn get_listen_addresses(mut url: Url) -> Result, Error> { .map(|mut url| { url.push("node").push("network").push("listen_addresses"); }) - .map_err(|_| Error::UrlCannotBeBase)?; + .map_err(|_| Error::InvalidUrl)?; reqwest::get(url)? .error_for_status()? diff --git a/beacon_node/rest_api/src/beacon.rs b/beacon_node/rest_api/src/beacon.rs index 88427c9a4b..4e3cc02fd2 100644 --- a/beacon_node/rest_api/src/beacon.rs +++ b/beacon_node/rest_api/src/beacon.rs @@ -54,14 +54,9 @@ pub fn get_block(req: Request) -> ApiResult ("slot", value) => { let target = parse_slot(&value)?; - beacon_chain - .rev_iter_block_roots() - .take_while(|(_root, slot)| *slot >= target) - .find(|(_root, slot)| *slot == target) - .map(|(root, _slot)| root) - .ok_or_else(|| { - ApiError::NotFound(format!("Unable to find BeaconBlock for slot {}", target)) - })? + block_root_at_slot(&beacon_chain, target).ok_or_else(|| { + ApiError::NotFound(format!("Unable to find BeaconBlock for slot {}", target)) + })? } ("root", value) => parse_root(&value)?, _ => return Err(ApiError::ServerError("Unexpected query parameter".into())), @@ -99,14 +94,9 @@ pub fn get_block_root(req: Request) -> ApiR let slot_string = UrlQuery::from_request(&req)?.only_one("slot")?; let target = parse_slot(&slot_string)?; - let root = beacon_chain - .rev_iter_block_roots() - .take_while(|(_root, slot)| *slot >= target) - .find(|(_root, slot)| *slot == target) - .map(|(root, _slot)| root) - .ok_or_else(|| { - ApiError::NotFound(format!("Unable to find BeaconBlock for slot {}", target)) - })?; + let root = block_root_at_slot(&beacon_chain, target).ok_or_else(|| { + ApiError::NotFound(format!("Unable to find BeaconBlock for slot {}", target)) + })?; let json: String = serde_json::to_string(&root) .map_err(|e| ApiError::ServerError(format!("Unable to serialize root: {:?}", e)))?; diff --git a/beacon_node/rest_api/src/helpers.rs b/beacon_node/rest_api/src/helpers.rs index a65c7c1ac9..5365086df7 100644 --- a/beacon_node/rest_api/src/helpers.rs +++ b/beacon_node/rest_api/src/helpers.rs @@ -31,6 +31,21 @@ pub fn parse_root(string: &str) -> Result { } } +/// Returns the root of the `BeaconBlock` in the canonical chain of `beacon_chain` at the given +/// `slot`, if possible. +/// +/// May return a root for a previous slot, in the case of skip slots. +pub fn block_root_at_slot( + beacon_chain: &BeaconChain, + target: Slot, +) -> Option { + beacon_chain + .rev_iter_block_roots() + .take_while(|(_root, slot)| *slot >= target) + .find(|(_root, slot)| *slot == target) + .map(|(root, _slot)| root) +} + /// Returns a `BeaconState` and it's root in the canonical chain of `beacon_chain` at the given /// `slot`, if possible. /// From 853344af8a6127a70df2207402a317fc7282b8cd Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Thu, 22 Aug 2019 16:34:21 +1000 Subject: [PATCH 45/62] Make BeaconChainTypes Send + Sync + 'static --- beacon_node/beacon_chain/src/beacon_chain.rs | 2 +- beacon_node/beacon_chain/src/test_utils.rs | 8 ++++---- beacon_node/client/src/beacon_chain_types.rs | 6 +++++- beacon_node/client/src/lib.rs | 2 +- beacon_node/client/src/notifier.rs | 6 +----- beacon_node/rest_api/src/lib.rs | 2 +- beacon_node/rest_api/src/network.rs | 14 +++++--------- beacon_node/src/run.rs | 2 +- 8 files changed, 19 insertions(+), 23 deletions(-) diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index bd7f37fbab..5feefd8417 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -77,7 +77,7 @@ pub enum AttestationProcessingOutcome { Invalid(AttestationValidationError), } -pub trait BeaconChainTypes { +pub trait BeaconChainTypes: Send + Sync + 'static { type Store: store::Store; type SlotClock: slot_clock::SlotClock; type LmdGhost: LmdGhost; diff --git a/beacon_node/beacon_chain/src/test_utils.rs b/beacon_node/beacon_chain/src/test_utils.rs index 298c637dbd..bd51f86203 100644 --- a/beacon_node/beacon_chain/src/test_utils.rs +++ b/beacon_node/beacon_chain/src/test_utils.rs @@ -54,8 +54,8 @@ where impl BeaconChainTypes for CommonTypes where - L: LmdGhost, - E: EthSpec, + L: LmdGhost + 'static, + E: EthSpec + 'static, { type Store = MemoryStore; type SlotClock = TestingSlotClock; @@ -69,8 +69,8 @@ where /// Used for testing. pub struct BeaconChainHarness where - L: LmdGhost, - E: EthSpec, + L: LmdGhost + 'static, + E: EthSpec + 'static, { pub chain: BeaconChain>, pub keypairs: Vec, diff --git a/beacon_node/client/src/beacon_chain_types.rs b/beacon_node/client/src/beacon_chain_types.rs index f2f95226ad..adea8c7b53 100644 --- a/beacon_node/client/src/beacon_chain_types.rs +++ b/beacon_node/client/src/beacon_chain_types.rs @@ -36,7 +36,11 @@ pub struct ClientType { _phantom_u: PhantomData, } -impl BeaconChainTypes for ClientType { +impl BeaconChainTypes for ClientType +where + S: Store + 'static, + E: EthSpec + 'static + Clone, +{ type Store = S; type SlotClock = SystemTimeSlotClock; type LmdGhost = ThreadSafeReducedTree; diff --git a/beacon_node/client/src/lib.rs b/beacon_node/client/src/lib.rs index 798aedec92..6405e05e71 100644 --- a/beacon_node/client/src/lib.rs +++ b/beacon_node/client/src/lib.rs @@ -49,7 +49,7 @@ pub struct Client { impl Client where - T: BeaconChainTypes + InitialiseBeaconChain + Clone + Send + Sync + 'static, + T: BeaconChainTypes + InitialiseBeaconChain + Clone, { /// Generate an instance of the client. Spawn and link all internal sub-processes. pub fn new( diff --git a/beacon_node/client/src/notifier.rs b/beacon_node/client/src/notifier.rs index 1c7cf38670..78e50ac79d 100644 --- a/beacon_node/client/src/notifier.rs +++ b/beacon_node/client/src/notifier.rs @@ -17,11 +17,7 @@ pub const WARN_PEER_COUNT: usize = 1; /// durations. /// /// Presently unused, but remains for future use. -pub fn run( - client: &Client, - executor: TaskExecutor, - exit: Exit, -) { +pub fn run(client: &Client, executor: TaskExecutor, exit: Exit) { // notification heartbeat let interval = Interval::new( Instant::now(), diff --git a/beacon_node/rest_api/src/lib.rs b/beacon_node/rest_api/src/lib.rs index 839aa7abca..354b234031 100644 --- a/beacon_node/rest_api/src/lib.rs +++ b/beacon_node/rest_api/src/lib.rs @@ -71,7 +71,7 @@ impl From for ApiError { } } -pub fn start_server( +pub fn start_server( config: &ApiConfig, executor: &TaskExecutor, beacon_chain: Arc>, diff --git a/beacon_node/rest_api/src/network.rs b/beacon_node/rest_api/src/network.rs index 154cd142d4..daded9d3d6 100644 --- a/beacon_node/rest_api/src/network.rs +++ b/beacon_node/rest_api/src/network.rs @@ -7,9 +7,7 @@ use std::sync::Arc; /// HTTP handle to return the list of libp2p multiaddr the client is listening on. /// /// Returns a list of `Multiaddr`, serialized according to their `serde` impl. -pub fn get_listen_addresses( - req: Request, -) -> ApiResult { +pub fn get_listen_addresses(req: Request) -> ApiResult { let network = req .extensions() .get::>>() @@ -26,7 +24,7 @@ pub fn get_listen_addresses( /// HTTP handle to return the Discv5 ENR from the client's libp2p service. /// /// ENR is encoded as base64 string. -pub fn get_enr(req: Request) -> ApiResult { +pub fn get_enr(req: Request) -> ApiResult { let network = req .extensions() .get::>>() @@ -43,7 +41,7 @@ pub fn get_enr(req: Request) /// HTTP handle to return the `PeerId` from the client's libp2p service. /// /// PeerId is encoded as base58 string. -pub fn get_peer_id(req: Request) -> ApiResult { +pub fn get_peer_id(req: Request) -> ApiResult { let network = req .extensions() .get::>>() @@ -58,9 +56,7 @@ pub fn get_peer_id(req: Request( - req: Request, -) -> ApiResult { +pub fn get_peer_count(req: Request) -> ApiResult { let network = req .extensions() .get::>>() @@ -77,7 +73,7 @@ pub fn get_peer_count( /// HTTP handle to return the list of peers connected to the client's libp2p service. /// /// Peers are presented as a list of `PeerId::to_string()`. -pub fn get_peer_list(req: Request) -> ApiResult { +pub fn get_peer_list(req: Request) -> ApiResult { let network = req .extensions() .get::>>() diff --git a/beacon_node/src/run.rs b/beacon_node/src/run.rs index 5066231d55..f88cb7460b 100644 --- a/beacon_node/src/run.rs +++ b/beacon_node/src/run.rs @@ -118,7 +118,7 @@ fn run( log: &slog::Logger, ) -> error::Result<()> where - T: BeaconChainTypes + InitialiseBeaconChain + Clone + Send + Sync + 'static, + T: BeaconChainTypes + InitialiseBeaconChain + Clone, T::Store: OpenDatabase, { let store = T::Store::open_database(&db_path)?; From 11dc72a4422e7c164c2d79619b6c92d12ae2ab4b Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Thu, 22 Aug 2019 17:48:13 +1000 Subject: [PATCH 46/62] Start implementing BeaconChainBuilder --- beacon_node/beacon_chain/Cargo.toml | 1 + .../beacon_chain/src/beacon_chain_builder.rs | 68 +++++++++++++++++++ beacon_node/beacon_chain/src/lib.rs | 2 + 3 files changed, 71 insertions(+) create mode 100644 beacon_node/beacon_chain/src/beacon_chain_builder.rs diff --git a/beacon_node/beacon_chain/Cargo.toml b/beacon_node/beacon_chain/Cargo.toml index 1d3fc03b81..31f3412865 100644 --- a/beacon_node/beacon_chain/Cargo.toml +++ b/beacon_node/beacon_chain/Cargo.toml @@ -13,6 +13,7 @@ log = "0.4" operation_pool = { path = "../../eth2/operation_pool" } serde = "1.0" serde_derive = "1.0" +serde_yaml = "0.8" slog = { version = "^2.2.3" , features = ["max_level_trace"] } sloggers = { version = "^0.3" } slot_clock = { path = "../../eth2/utils/slot_clock" } diff --git a/beacon_node/beacon_chain/src/beacon_chain_builder.rs b/beacon_node/beacon_chain/src/beacon_chain_builder.rs new file mode 100644 index 0000000000..a6c77cb63c --- /dev/null +++ b/beacon_node/beacon_chain/src/beacon_chain_builder.rs @@ -0,0 +1,68 @@ +use crate::BeaconChainTypes; +use std::fs::File; +use std::path::PathBuf; +use std::time::SystemTime; +use types::{ + test_utils::TestingBeaconStateBuilder, BeaconBlock, BeaconState, ChainSpec, EthSpec, Hash256, +}; + +pub struct BeaconChainBuilder { + genesis_state: BeaconState, + genesis_block: BeaconBlock, + spec: ChainSpec, +} + +impl BeaconChainBuilder { + pub fn recent_genesis(validator_count: usize, spec: ChainSpec) -> Self { + Self::quick_start(recent_genesis_time(), validator_count, spec) + } + + pub fn quick_start(genesis_time: u64, validator_count: usize, spec: ChainSpec) -> Self { + let (mut genesis_state, _keypairs) = + TestingBeaconStateBuilder::from_default_keypairs_file_if_exists(validator_count, &spec) + .build(); + + genesis_state.genesis_time = genesis_time; + + Self::from_genesis_state(genesis_state, spec) + } + + pub fn yaml_state(file: PathBuf, spec: ChainSpec) -> Result { + let file = File::open(file.clone()) + .map_err(|e| format!("Unable to open YAML genesis state file {:?}: {:?}", file, e))?; + + let genesis_state = serde_yaml::from_reader(file) + .map_err(|e| format!("Unable to parse YAML genesis state file: {:?}", e))?; + + Ok(Self::from_genesis_state(genesis_state, spec)) + } + + pub fn from_genesis_state(genesis_state: BeaconState, spec: ChainSpec) -> Self { + Self { + genesis_block: genesis_block(&genesis_state, &spec), + genesis_state, + spec, + } + } +} + +fn genesis_block(genesis_state: &BeaconState, spec: &ChainSpec) -> BeaconBlock { + let mut genesis_block = BeaconBlock::empty(&spec); + + genesis_block.state_root = genesis_state.canonical_root(); + + genesis_block +} + +/// Returns the system time, mod 30 minutes. +/// +/// Used for easily creating testnets. +fn recent_genesis_time() -> u64 { + let now = SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap() + .as_secs(); + let secs_after_last_period = now.checked_rem(30 * 60).unwrap_or(0); + // genesis is now the last 30 minute block. + now - secs_after_last_period +} diff --git a/beacon_node/beacon_chain/src/lib.rs b/beacon_node/beacon_chain/src/lib.rs index cc7725dd83..9c833f778d 100644 --- a/beacon_node/beacon_chain/src/lib.rs +++ b/beacon_node/beacon_chain/src/lib.rs @@ -3,6 +3,7 @@ extern crate lazy_static; mod beacon_chain; +mod beacon_chain_builder; mod checkpoint; mod errors; mod fork_choice; @@ -16,6 +17,7 @@ pub use self::beacon_chain::{ }; pub use self::checkpoint::CheckPoint; pub use self::errors::{BeaconChainError, BlockProductionError}; +pub use beacon_chain_builder::BeaconChainBuilder; pub use lmd_ghost; pub use metrics::scrape_for_metrics; pub use parking_lot; From 94d987cb6aaa6fcd7920803c494a00a870f1ffae Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Fri, 23 Aug 2019 12:12:29 +1000 Subject: [PATCH 47/62] Add `/network/listen_port` API endpoint --- beacon_node/client/src/bootstrapper.rs | 24 ++++++------------------ beacon_node/network/src/service.rs | 7 +++++++ beacon_node/rest_api/src/lib.rs | 1 + beacon_node/rest_api/src/network.rs | 15 +++++++++++++++ 4 files changed, 29 insertions(+), 18 deletions(-) diff --git a/beacon_node/client/src/bootstrapper.rs b/beacon_node/client/src/bootstrapper.rs index 19f13e2da8..eaaee4aa1c 100644 --- a/beacon_node/client/src/bootstrapper.rs +++ b/beacon_node/client/src/bootstrapper.rs @@ -46,7 +46,7 @@ impl Bootstrapper { /// `/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) -> Option { - let tcp_port = self.first_listening_tcp_port()?; + let tcp_port = self.listen_port().ok()?; let mut multiaddr = Multiaddr::with_capacity(2); @@ -61,17 +61,6 @@ impl Bootstrapper { Some(multiaddr) } - /// Reads the server's listening libp2p addresses and returns the first TCP port protocol it - /// finds, if any. - fn first_listening_tcp_port(&self) -> Option { - self.listen_addresses().ok()?.iter().find_map(|multiaddr| { - multiaddr.iter().find_map(|protocol| match protocol { - Protocol::Tcp(port) => Some(port), - _ => None, - }) - }) - } - /// Returns the IPv4 address of the server URL, unless it contains a FQDN. pub fn server_ipv4_addr(&self) -> Option { match self.url.host()? { @@ -86,9 +75,8 @@ impl Bootstrapper { } /// Returns the servers listening libp2p addresses. - pub fn listen_addresses(&self) -> Result, String> { - get_listen_addresses(self.url.clone()) - .map_err(|e| format!("Unable to get listen addresses: {:?}", e)) + pub fn listen_port(&self) -> Result { + get_listen_port(self.url.clone()).map_err(|e| format!("Unable to get listen port: {:?}", e)) } /// Returns the genesis block and state. @@ -179,7 +167,7 @@ fn get_block(mut url: Url, slot: Slot) -> Result, Err fn get_enr(mut url: Url) -> Result { url.path_segments_mut() .map(|mut url| { - url.push("node").push("network").push("enr"); + url.push("network").push("enr"); }) .map_err(|_| Error::InvalidUrl)?; @@ -189,10 +177,10 @@ fn get_enr(mut url: Url) -> Result { .map_err(Into::into) } -fn get_listen_addresses(mut url: Url) -> Result, Error> { +fn get_listen_port(mut url: Url) -> Result { url.path_segments_mut() .map(|mut url| { - url.push("node").push("network").push("listen_addresses"); + url.push("network").push("listen_port"); }) .map_err(|_| Error::InvalidUrl)?; diff --git a/beacon_node/network/src/service.rs b/beacon_node/network/src/service.rs index dc7e941409..152f4dc77d 100644 --- a/beacon_node/network/src/service.rs +++ b/beacon_node/network/src/service.rs @@ -18,6 +18,7 @@ use tokio::sync::{mpsc, oneshot}; /// Service that handles communication between internal services and the eth2_libp2p network service. pub struct Service { libp2p_service: Arc>, + libp2p_port: u16, _libp2p_exit: oneshot::Sender<()>, _network_send: mpsc::UnboundedSender, _phantom: PhantomData, //message_handler: MessageHandler, @@ -56,6 +57,7 @@ impl Service { )?; let network_service = Service { libp2p_service, + libp2p_port: config.libp2p_port, _libp2p_exit: libp2p_exit, _network_send: network_send.clone(), _phantom: PhantomData, @@ -87,6 +89,11 @@ impl Service { .collect() } + /// Returns the libp2p port that this node has been configured to listen using. + pub fn listen_port(&self) -> u16 { + self.libp2p_port + } + /// Returns the number of libp2p connected peers. pub fn connected_peers(&self) -> usize { self.libp2p_service.lock().swarm.connected_peers() diff --git a/beacon_node/rest_api/src/lib.rs b/beacon_node/rest_api/src/lib.rs index 354b234031..a382c49e30 100644 --- a/beacon_node/rest_api/src/lib.rs +++ b/beacon_node/rest_api/src/lib.rs @@ -134,6 +134,7 @@ pub fn start_server( (&Method::GET, "/network/peer_count") => network::get_peer_count::(req), (&Method::GET, "/network/peer_id") => network::get_peer_id::(req), (&Method::GET, "/network/peers") => network::get_peer_list::(req), + (&Method::GET, "/network/listen_port") => network::get_listen_port::(req), (&Method::GET, "/network/listen_addresses") => { network::get_listen_addresses::(req) } diff --git a/beacon_node/rest_api/src/network.rs b/beacon_node/rest_api/src/network.rs index daded9d3d6..a3e4c5ee72 100644 --- a/beacon_node/rest_api/src/network.rs +++ b/beacon_node/rest_api/src/network.rs @@ -21,6 +21,21 @@ pub fn get_listen_addresses(req: Request) -> ApiResul ))) } +/// HTTP handle to return the list of libp2p multiaddr the client is listening on. +/// +/// Returns a list of `Multiaddr`, serialized according to their `serde` impl. +pub fn get_listen_port(req: Request) -> ApiResult { + let network = req + .extensions() + .get::>>() + .ok_or_else(|| ApiError::ServerError("NetworkService extension missing".to_string()))?; + + Ok(success_response(Body::from( + serde_json::to_string(&network.listen_port()) + .map_err(|e| ApiError::ServerError(format!("Unable to serialize port: {:?}", e)))?, + ))) +} + /// HTTP handle to return the Discv5 ENR from the client's libp2p service. /// /// ENR is encoded as base64 string. From 7d11d782992fc8a8780026860a97be56bb0325b6 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Fri, 23 Aug 2019 12:43:34 +1000 Subject: [PATCH 48/62] Abandon starting the node if libp2p doesn't start --- beacon_node/eth2-libp2p/src/service.rs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/beacon_node/eth2-libp2p/src/service.rs b/beacon_node/eth2-libp2p/src/service.rs index e1e112e2d8..e208dbecac 100644 --- a/beacon_node/eth2-libp2p/src/service.rs +++ b/beacon_node/eth2-libp2p/src/service.rs @@ -16,7 +16,7 @@ use libp2p::core::{ upgrade::{InboundUpgradeExt, OutboundUpgradeExt}, }; use libp2p::{core, secio, PeerId, Swarm, Transport}; -use slog::{debug, info, trace, warn}; +use slog::{crit, debug, info, trace, warn}; use std::fs::File; use std::io::prelude::*; use std::io::{Error, ErrorKind}; @@ -69,10 +69,15 @@ impl Service { log_address.push(Protocol::P2p(local_peer_id.clone().into())); info!(log, "Listening on: {}", log_address); } - Err(err) => warn!( - log, - "Cannot listen on: {} because: {:?}", listen_multiaddr, err - ), + Err(err) => { + crit!( + log, + "Unable to listen on libp2p address"; + "error" => format!("{:?}", err), + "listen_multiaddr" => format!("{}", listen_multiaddr), + ); + return Err("Libp2p was unable to listen on the given listen address.".into()); + } }; // attempt to connect to user-input libp2p nodes From a358bbc1b1bc04a852c792fa33f3ca85f77aabbc Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Fri, 23 Aug 2019 12:45:31 +1000 Subject: [PATCH 49/62] Update bootstrapper for API changes --- beacon_node/client/src/bootstrapper.rs | 33 ++++++++++++++++++++------ beacon_node/rest_api/src/beacon.rs | 6 ++--- beacon_node/rest_api/src/lib.rs | 4 +++- 3 files changed, 32 insertions(+), 11 deletions(-) diff --git a/beacon_node/client/src/bootstrapper.rs b/beacon_node/client/src/bootstrapper.rs index eaaee4aa1c..c94d9a51d8 100644 --- a/beacon_node/client/src/bootstrapper.rs +++ b/beacon_node/client/src/bootstrapper.rs @@ -3,9 +3,10 @@ use eth2_libp2p::{ Enr, }; use reqwest::{Error as HttpError, Url}; +use serde::Deserialize; use std::borrow::Cow; use std::net::Ipv4Addr; -use types::{BeaconBlock, BeaconState, Checkpoint, EthSpec, Slot}; +use types::{BeaconBlock, BeaconState, Checkpoint, EthSpec, Hash256, Slot}; use url::Host; #[derive(Debug)] @@ -84,9 +85,11 @@ impl Bootstrapper { 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))?; + .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))?; + .map_err(|e| format!("Unable to get genesis state: {:?}", e))? + .beacon_state; Ok((state, block)) } @@ -99,9 +102,11 @@ impl Bootstrapper { .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))?; + .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))?; + .map_err(|e| format!("Unable to get finalized state: {:?}", e))? + .beacon_state; Ok((state, block)) } @@ -132,7 +137,14 @@ fn get_finalized_slot(mut url: Url, slots_per_epoch: u64) -> Result Ok(checkpoint.epoch.start_slot(slots_per_epoch)) } -fn get_state(mut url: Url, slot: Slot) -> Result, Error> { +#[derive(Deserialize)] +#[serde(bound = "T: EthSpec")] +pub struct StateResponse { + pub root: Hash256, + pub beacon_state: BeaconState, +} + +fn get_state(mut url: Url, slot: Slot) -> Result, Error> { url.path_segments_mut() .map(|mut url| { url.push("beacon").push("state"); @@ -148,7 +160,14 @@ fn get_state(mut url: Url, slot: Slot) -> Result, Err .map_err(Into::into) } -fn get_block(mut url: Url, slot: Slot) -> Result, Error> { +#[derive(Deserialize)] +#[serde(bound = "T: EthSpec")] +pub struct BlockResponse { + pub root: Hash256, + pub beacon_block: BeaconBlock, +} + +fn get_block(mut url: Url, slot: Slot) -> Result, Error> { url.path_segments_mut() .map(|mut url| { url.push("beacon").push("block"); diff --git a/beacon_node/rest_api/src/beacon.rs b/beacon_node/rest_api/src/beacon.rs index 4e3cc02fd2..1c66a2819f 100644 --- a/beacon_node/rest_api/src/beacon.rs +++ b/beacon_node/rest_api/src/beacon.rs @@ -8,7 +8,7 @@ use store::Store; use types::{BeaconBlock, BeaconState, EthSpec, Hash256, Slot}; #[derive(Serialize)] -struct HeadResponse { +pub struct HeadResponse { pub slot: Slot, pub block_root: Hash256, pub state_root: Hash256, @@ -35,7 +35,7 @@ pub fn get_head(req: Request) -> ApiResult #[derive(Serialize)] #[serde(bound = "T: EthSpec")] -struct BlockResponse { +pub struct BlockResponse { pub root: Hash256, pub beacon_block: BeaconBlock, } @@ -106,7 +106,7 @@ pub fn get_block_root(req: Request) -> ApiR #[derive(Serialize)] #[serde(bound = "T: EthSpec")] -struct StateResponse { +pub struct StateResponse { pub root: Hash256, pub beacon_state: BeaconState, } diff --git a/beacon_node/rest_api/src/lib.rs b/beacon_node/rest_api/src/lib.rs index a382c49e30..964dd79982 100644 --- a/beacon_node/rest_api/src/lib.rs +++ b/beacon_node/rest_api/src/lib.rs @@ -13,7 +13,6 @@ mod url_query; use beacon_chain::{BeaconChain, BeaconChainTypes}; use client_network::Service as NetworkService; -pub use config::Config as ApiConfig; use hyper::rt::Future; use hyper::service::service_fn_ok; use hyper::{Body, Method, Response, Server, StatusCode}; @@ -24,6 +23,9 @@ use std::sync::Arc; use tokio::runtime::TaskExecutor; use url_query::UrlQuery; +pub use beacon::{BlockResponse, HeadResponse, StateResponse}; +pub use config::Config as ApiConfig; + #[derive(PartialEq, Debug)] pub enum ApiError { MethodNotAllowed(String), From a8de94ca133ddfb63ced7d02d3432f3166b8bcbb Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Fri, 23 Aug 2019 13:02:17 +1000 Subject: [PATCH 50/62] Remove unnecessary trait bounds --- beacon_node/beacon_chain/src/test_utils.rs | 4 ++-- beacon_node/client/src/beacon_chain_types.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/beacon_node/beacon_chain/src/test_utils.rs b/beacon_node/beacon_chain/src/test_utils.rs index bd51f86203..09f4749ea3 100644 --- a/beacon_node/beacon_chain/src/test_utils.rs +++ b/beacon_node/beacon_chain/src/test_utils.rs @@ -55,7 +55,7 @@ where impl BeaconChainTypes for CommonTypes where L: LmdGhost + 'static, - E: EthSpec + 'static, + E: EthSpec, { type Store = MemoryStore; type SlotClock = TestingSlotClock; @@ -70,7 +70,7 @@ where pub struct BeaconChainHarness where L: LmdGhost + 'static, - E: EthSpec + 'static, + E: EthSpec, { pub chain: BeaconChain>, pub keypairs: Vec, diff --git a/beacon_node/client/src/beacon_chain_types.rs b/beacon_node/client/src/beacon_chain_types.rs index adea8c7b53..5168c067a9 100644 --- a/beacon_node/client/src/beacon_chain_types.rs +++ b/beacon_node/client/src/beacon_chain_types.rs @@ -39,7 +39,7 @@ pub struct ClientType { impl BeaconChainTypes for ClientType where S: Store + 'static, - E: EthSpec + 'static + Clone, + E: EthSpec, { type Store = S; type SlotClock = SystemTimeSlotClock; From 453c8e2255263b5116b8fb7f94a29254e7836e4a Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Fri, 23 Aug 2019 16:39:32 +1000 Subject: [PATCH 51/62] Re-arrange CLI to suit new "testnet" pattern --- beacon_node/Cargo.toml | 1 + beacon_node/client/src/config.rs | 11 +- beacon_node/src/config.rs | 206 ++++++++++++++++++++++++++++ beacon_node/src/main.rs | 227 ++++++++++--------------------- beacon_node/src/run.rs | 1 - 5 files changed, 280 insertions(+), 166 deletions(-) create mode 100644 beacon_node/src/config.rs diff --git a/beacon_node/Cargo.toml b/beacon_node/Cargo.toml index 9124047e45..9ce724c148 100644 --- a/beacon_node/Cargo.toml +++ b/beacon_node/Cargo.toml @@ -11,6 +11,7 @@ store = { path = "./store" } client = { path = "client" } version = { path = "version" } clap = "2.32.0" +rand = "0.7" slog = { version = "^2.2.3" , features = ["max_level_trace"] } slog-term = "^2.4.0" slog-async = "^2.3.0" diff --git a/beacon_node/client/src/config.rs b/beacon_node/client/src/config.rs index ea8186dbc9..e1464e5b43 100644 --- a/beacon_node/client/src/config.rs +++ b/beacon_node/client/src/config.rs @@ -1,4 +1,4 @@ -use crate::{Bootstrapper, Eth2Config}; +use crate::Bootstrapper; use clap::ArgMatches; use network::NetworkConfig; use serde_derive::{Deserialize, Serialize}; @@ -127,15 +127,6 @@ impl Config { self.data_dir = PathBuf::from(dir); }; - if let Some(default_spec) = args.value_of("default-spec") { - match default_spec { - "mainnet" => self.spec_constants = Eth2Config::mainnet().spec_constants, - "minimal" => self.spec_constants = Eth2Config::minimal().spec_constants, - "interop" => self.spec_constants = Eth2Config::interop().spec_constants, - _ => {} // not supported - } - } - if let Some(dir) = args.value_of("db") { self.db_type = dir.to_string(); }; diff --git a/beacon_node/src/config.rs b/beacon_node/src/config.rs new file mode 100644 index 0000000000..959edbd607 --- /dev/null +++ b/beacon_node/src/config.rs @@ -0,0 +1,206 @@ +use clap::ArgMatches; +use client::{ClientConfig, Eth2Config}; +use eth2_config::{read_from_file, write_to_file}; +use rand::{distributions::Alphanumeric, Rng}; +use slog::{crit, info, Logger}; +use std::fs; +use std::path::PathBuf; + +pub const DEFAULT_DATA_DIR: &str = ".lighthouse"; +pub const CLIENT_CONFIG_FILENAME: &str = "beacon-node.toml"; +pub const ETH2_CONFIG_FILENAME: &str = "eth2-spec.toml"; + +type Result = std::result::Result; +type Config = (ClientConfig, Eth2Config); + +/// Gets the fully-initialized global client and eth2 configuration objects. +pub fn get_configs(matches: &ArgMatches, log: &Logger) -> Result { + let mut builder = ConfigBuilder::new(matches, log)?; + + match matches.subcommand() { + ("testnet", Some(sub_matches)) => { + if sub_matches.is_present("random-datadir") { + builder.set_random_datadir()?; + } + + info!( + log, + "Creating new datadir"; + "path" => format!("{:?}", builder.data_dir) + ); + + builder.update_spec_from_subcommand(&sub_matches)?; + builder.write_configs_to_new_datadir()?; + } + _ => { + info!( + log, + "Resuming from existing datadir"; + "path" => format!("{:?}", builder.data_dir) + ); + + // If the `testnet` command was not provided, attempt to load an existing datadir and + // continue with an existing chain. + builder.load_from_datadir()?; + } + }; + + builder.build() +} + +/// Allows for building a set of configurations based upon `clap` arguments. +struct ConfigBuilder<'a> { + matches: &'a ArgMatches<'a>, + log: &'a Logger, + pub data_dir: PathBuf, + eth2_config: Eth2Config, + client_config: ClientConfig, +} + +impl<'a> ConfigBuilder<'a> { + /// Create a new builder with default settings. + pub fn new(matches: &'a ArgMatches, log: &'a Logger) -> Result { + // Read the `--datadir` flag. + // + // If it's not present, try and find the home directory (`~`) and push the default data + // directory onto it. + let data_dir: PathBuf = matches + .value_of("datadir") + .map(|string| PathBuf::from(string)) + .or_else(|| { + dirs::home_dir().map(|mut home| { + home.push(DEFAULT_DATA_DIR); + home + }) + }) + .ok_or_else(|| "Unable to find a home directory for the datadir".to_string())?; + + Ok(Self { + matches, + log, + data_dir, + eth2_config: Eth2Config::minimal(), + client_config: ClientConfig::default(), + }) + } + + /// Consumes self, returning the configs. + pub fn build(mut self) -> Result { + self.eth2_config.apply_cli_args(&self.matches)?; + self.client_config + .apply_cli_args(&self.matches, &mut self.log.clone())?; + + if self.eth2_config.spec_constants != self.client_config.spec_constants { + crit!(self.log, "Specification constants do not match."; + "client_config" => format!("{}", self.client_config.spec_constants), + "eth2_config" => format!("{}", self.eth2_config.spec_constants) + ); + return Err("Specification constant mismatch".into()); + } + + self.client_config.data_dir = self.data_dir; + + Ok((self.client_config, self.eth2_config)) + } + + /// Set the config data_dir to be an random directory. + /// + /// Useful for easily spinning up ephemeral testnets. + pub fn set_random_datadir(&mut self) -> Result<()> { + let random = rand::thread_rng() + .sample_iter(&Alphanumeric) + .take(10) + .collect::(); + + let mut s = DEFAULT_DATA_DIR.to_string(); + s.push_str("_random_"); + s.push_str(&random); + + self.data_dir.pop(); + self.data_dir.push(s); + + Ok(()) + } + + /// Reads the subcommand and tries to update `self.eth2_config` based up on the `--spec` flag. + /// + /// Returns an error if the `--spec` flag is not present. + pub fn update_spec_from_subcommand(&mut self, sub_matches: &ArgMatches) -> Result<()> { + // Re-initialise the `Eth2Config`. + // + // If a CLI parameter is set, overwrite any config file present. + // If a parameter is not set, use either the config file present or default to minimal. + let eth2_config = match sub_matches.value_of("spec") { + Some("mainnet") => Eth2Config::mainnet(), + Some("minimal") => Eth2Config::minimal(), + Some("interop") => Eth2Config::interop(), + _ => return Err("Unable to determine specification type.".into()), + }; + + self.client_config.spec_constants = sub_matches + .value_of("spec") + .expect("Guarded by prior match statement") + .to_string(); + self.eth2_config = eth2_config; + + Ok(()) + } + + /// Writes the configs in `self` to `self.data_dir`. + /// + /// Returns an error if `self.data_dir` already exists. + pub fn write_configs_to_new_datadir(&mut self) -> Result<()> { + // Do not permit creating a new config when the datadir exists. + if self.data_dir.exists() { + return Err( + "Datadir already exists, will not overwrite. Remove the directory or use --datadir." + .into(), + ); + } + + // Create `datadir` and any non-existing parent directories. + fs::create_dir_all(&self.data_dir).map_err(|e| { + crit!(self.log, "Failed to initialize data dir"; "error" => format!("{}", e)); + format!("{}", e) + })?; + + // Write the client config to a TOML file in the datadir. + write_to_file( + self.data_dir.join(CLIENT_CONFIG_FILENAME), + &self.client_config, + ) + .map_err(|e| format!("Unable to write {} file: {:?}", CLIENT_CONFIG_FILENAME, e))?; + + // Write the eth2 config to a TOML file in the datadir. + write_to_file(self.data_dir.join(ETH2_CONFIG_FILENAME), &self.eth2_config) + .map_err(|e| format!("Unable to write {} file: {:?}", ETH2_CONFIG_FILENAME, e))?; + + Ok(()) + } + + /// Attempts to load the client and eth2 configs from `self.data_dir`. + /// + /// Returns an error if any files are not found or are invalid. + pub fn load_from_datadir(&mut self) -> Result<()> { + // Check to ensure the datadir exists. + // + // For now we return an error. In the future we may decide to boot a default (e.g., + // public testnet or mainnet). + if !self.data_dir.exists() { + return Err( + "No datadir found. Use the 'testnet' sub-command to select a testnet type.".into(), + ); + } + + self.eth2_config = read_from_file::(self.data_dir.join(ETH2_CONFIG_FILENAME)) + .map_err(|e| format!("Unable to parse {} file: {:?}", ETH2_CONFIG_FILENAME, e))? + .ok_or_else(|| format!("{} file does not exist", ETH2_CONFIG_FILENAME))?; + + self.client_config = + read_from_file::(self.data_dir.join(CLIENT_CONFIG_FILENAME)) + .map_err(|e| format!("Unable to parse {} file: {:?}", CLIENT_CONFIG_FILENAME, e))? + .ok_or_else(|| format!("{} file does not exist", ETH2_CONFIG_FILENAME))?; + + Ok(()) + } +} diff --git a/beacon_node/src/main.rs b/beacon_node/src/main.rs index 04366baa7a..12c9b8a017 100644 --- a/beacon_node/src/main.rs +++ b/beacon_node/src/main.rs @@ -1,12 +1,10 @@ +mod config; mod run; -use clap::{App, Arg}; -use client::{ClientConfig, Eth2Config}; +use clap::{App, Arg, SubCommand}; +use config::get_configs; use env_logger::{Builder, Env}; -use eth2_config::{read_from_file, write_to_file}; use slog::{crit, o, warn, Drain, Level}; -use std::fs; -use std::path::PathBuf; pub const DEFAULT_DATA_DIR: &str = ".lighthouse"; @@ -31,6 +29,7 @@ fn main() { .value_name("DIR") .help("Data directory for keys and databases.") .takes_value(true) + .global(true) ) .arg( Arg::with_name("logfile") @@ -45,6 +44,7 @@ fn main() { .value_name("NETWORK-DIR") .help("Data directory for network keys.") .takes_value(true) + .global(true) ) /* * Network parameters. @@ -163,24 +163,6 @@ fn main() { .possible_values(&["disk", "memory"]) .default_value("memory"), ) - /* - * Specification/testnet params. - */ - .arg( - Arg::with_name("default-spec") - .long("default-spec") - .value_name("TITLE") - .short("default-spec") - .help("Specifies the default eth2 spec to be used. This will override any spec written to disk and will therefore be used by default in future instances.") - .takes_value(true) - .possible_values(&["mainnet", "minimal", "interop"]) - ) - .arg( - Arg::with_name("recent-genesis") - .long("recent-genesis") - .short("r") - .help("When present, genesis will be within 30 minutes prior. Only for testing"), - ) /* * Logging. */ @@ -201,14 +183,68 @@ fn main() { .takes_value(true), ) /* - * Bootstrap. + * The "testnet" sub-command. + * + * Allows for creating a new datadir with testnet-specific configs. */ - .arg( - Arg::with_name("bootstrap") - .long("bootstrap") - .value_name("HTTP_SERVER") - .help("Load the genesis state and libp2p address from the HTTP API of another Lighthouse node.") - .takes_value(true) + .subcommand(SubCommand::with_name("testnet") + .about("Create a new Lighthouse datadir using a testnet strategy.") + .arg( + Arg::with_name("spec") + .short("s") + .long("spec") + .value_name("TITLE") + .help("Specifies the default eth2 spec type. Only effective when creating a new datadir.") + .takes_value(true) + .required(true) + .possible_values(&["mainnet", "minimal", "interop"]) + ) + .arg( + Arg::with_name("random-datadir") + .long("random-datadir") + .short("r") + .help("If present, append a random string to the datadir path. Useful for fast development \ + iteration.") + ) + .arg( + Arg::with_name("force-create") + .long("force-create") + .short("f") + .help("If present, will delete any existing datadir before creating a new one. Cannot be \ + used when specifying --random-datadir (logic error).") + .conflicts_with("random-datadir") + ) + /* + * Testnet sub-commands. + */ + .subcommand(SubCommand::with_name("bootstrap") + .about("Connects to the given HTTP server, downloads a genesis state and attempts to peer with it.") + .arg(Arg::with_name("server") + .value_name("HTTP_SERVER") + .required(true) + .help("A HTTP server, with a http:// prefix")) + .arg(Arg::with_name("libp2p-port") + .short("p") + .long("port") + .value_name("TCP_PORT") + .help("A libp2p listen port used to peer with the bootstrap server")) + ) + .subcommand(SubCommand::with_name("recent") + .about("Creates a new genesis state where the genesis time was at the previous \ + 30-minute boundary (e.g., 12:00, 12:30, 13:00, etc.)") + .arg(Arg::with_name("validator_count") + .value_name("VALIDATOR_COUNT") + .required(true) + .help("The number of validators in the genesis state")) + ) + .subcommand(SubCommand::with_name("yaml-genesis-state") + .about("Creates a new datadir where the genesis state is read from YAML. Will fail to parse \ + a YAML state that was generated to a different spec than that specified by --spec.") + .arg(Arg::with_name("file") + .value_name("YAML_FILE") + .required(true) + .help("A YAML file from which to read the state")) + ) ) .get_matches(); @@ -235,143 +271,24 @@ fn main() { _ => drain.filter_level(Level::Trace), }; - let mut log = slog::Logger::root(drain.fuse(), o!()); + let log = slog::Logger::root(drain.fuse(), o!()); warn!( log, "Ethereum 2.0 is pre-release. This software is experimental." ); - let data_dir = match matches - .value_of("datadir") - .and_then(|v| Some(PathBuf::from(v))) - { - Some(v) => v, - None => { - // use the default - let mut default_dir = match dirs::home_dir() { - Some(v) => v, - None => { - crit!(log, "Failed to find a home directory"); - return; - } - }; - default_dir.push(DEFAULT_DATA_DIR); - default_dir - } - }; - - // create the directory if needed - match fs::create_dir_all(&data_dir) { - Ok(_) => {} - Err(e) => { - crit!(log, "Failed to initialize data dir"; "error" => format!("{}", e)); - return; - } - } - - let client_config_path = data_dir.join(CLIENT_CONFIG_FILENAME); - - // Attempt to load the `ClientConfig` from disk. + // Load the process-wide configuration. // - // If file doesn't exist, create a new, default one. - let mut client_config = match read_from_file::(client_config_path.clone()) { - Ok(Some(c)) => c, - Ok(None) => { - let default = ClientConfig::default(); - if let Err(e) = write_to_file(client_config_path, &default) { - crit!(log, "Failed to write default ClientConfig to file"; "error" => format!("{:?}", e)); - return; - } - default - } + // May load this from disk or create a new configuration, depending on the CLI flags supplied. + let (client_config, eth2_config) = match get_configs(&matches, &log) { + Ok(configs) => configs, Err(e) => { - crit!(log, "Failed to load a ChainConfig file"; "error" => format!("{:?}", e)); + crit!(log, "Failed to load configuration"; "error" => e); return; } }; - // Ensure the `data_dir` in the config matches that supplied to the CLI. - client_config.data_dir = data_dir.clone(); - - // Update the client config with any CLI args. - match client_config.apply_cli_args(&matches, &mut log) { - Ok(()) => (), - Err(s) => { - crit!(log, "Failed to parse ClientConfig CLI arguments"; "error" => s); - return; - } - }; - - let eth2_config_path = data_dir.join(ETH2_CONFIG_FILENAME); - - // Initialise the `Eth2Config`. - // - // If a CLI parameter is set, overwrite any config file present. - // If a parameter is not set, use either the config file present or default to minimal. - let cli_config = match matches.value_of("default-spec") { - Some("mainnet") => Some(Eth2Config::mainnet()), - Some("minimal") => Some(Eth2Config::minimal()), - Some("interop") => Some(Eth2Config::interop()), - _ => None, - }; - // if a CLI flag is specified, write the new config if it doesn't exist, - // otherwise notify the user that the file will not be written. - let eth2_config_from_file = match read_from_file::(eth2_config_path.clone()) { - Ok(config) => config, - Err(e) => { - crit!(log, "Failed to read the Eth2Config from file"; "error" => format!("{:?}", e)); - return; - } - }; - - let mut eth2_config = { - if let Some(cli_config) = cli_config { - if eth2_config_from_file.is_none() { - // write to file if one doesn't exist - if let Err(e) = write_to_file(eth2_config_path, &cli_config) { - crit!(log, "Failed to write default Eth2Config to file"; "error" => format!("{:?}", e)); - return; - } - } else { - warn!( - log, - "Eth2Config file exists. Configuration file is ignored, using default" - ); - } - cli_config - } else { - // CLI config not specified, read from disk - match eth2_config_from_file { - Some(config) => config, - None => { - // set default to minimal - let eth2_config = Eth2Config::minimal(); - if let Err(e) = write_to_file(eth2_config_path, ð2_config) { - crit!(log, "Failed to write default Eth2Config to file"; "error" => format!("{:?}", e)); - return; - } - eth2_config - } - } - } - }; - - // Update the eth2 config with any CLI flags. - match eth2_config.apply_cli_args(&matches) { - Ok(()) => (), - Err(s) => { - crit!(log, "Failed to parse Eth2Config CLI arguments"; "error" => s); - return; - } - }; - - // check to ensure the spec constants between the client and eth2_config match - if eth2_config.spec_constants != client_config.spec_constants { - crit!(log, "Specification constants do not match."; "client_config" => format!("{}", client_config.spec_constants), "eth2_config" => format!("{}", eth2_config.spec_constants)); - return; - } - // Start the node using a `tokio` executor. match run::run_beacon_node(client_config, eth2_config, &log) { Ok(_) => {} diff --git a/beacon_node/src/run.rs b/beacon_node/src/run.rs index f88cb7460b..e23b5bc72d 100644 --- a/beacon_node/src/run.rs +++ b/beacon_node/src/run.rs @@ -46,7 +46,6 @@ pub fn run_beacon_node( log, "BeaconNode init"; "p2p_listen_address" => format!("{:?}", &other_client_config.network.listen_address), - "data_dir" => format!("{:?}", other_client_config.data_dir()), "network_dir" => format!("{:?}", other_client_config.network.network_dir), "spec_constants" => &spec_constants, "db_type" => &other_client_config.db_type, From cdf3ade63fd32bea919c8e7fa847855352569148 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Fri, 23 Aug 2019 18:23:58 +1000 Subject: [PATCH 52/62] Add further CLI progress --- beacon_node/client/src/bootstrapper.rs | 8 ++- beacon_node/client/src/config.rs | 72 ++++++++++++-------------- beacon_node/src/config.rs | 41 +++++++++++++-- 3 files changed, 76 insertions(+), 45 deletions(-) diff --git a/beacon_node/client/src/bootstrapper.rs b/beacon_node/client/src/bootstrapper.rs index c94d9a51d8..9baf1dc7ed 100644 --- a/beacon_node/client/src/bootstrapper.rs +++ b/beacon_node/client/src/bootstrapper.rs @@ -46,8 +46,12 @@ impl Bootstrapper { /// 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) -> Option { - let tcp_port = self.listen_port().ok()?; + pub fn best_effort_multiaddr(&self, port: Option) -> Option { + let tcp_port = if let Some(port) = port { + port + } else { + self.listen_port().ok()? + }; let mut multiaddr = Multiaddr::with_capacity(2); diff --git a/beacon_node/client/src/config.rs b/beacon_node/client/src/config.rs index e1464e5b43..e802a93a3d 100644 --- a/beacon_node/client/src/config.rs +++ b/beacon_node/client/src/config.rs @@ -21,14 +21,42 @@ pub struct Config { db_name: String, pub log_file: PathBuf, pub spec_constants: String, - pub genesis_state: GenesisState, + #[serde(skip)] + pub boot_method: BootMethod, pub network: network::NetworkConfig, pub rpc: rpc::RPCConfig, pub rest_api: rest_api::ApiConfig, } -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(tag = "type")] +#[derive(Debug, Clone)] +pub enum BootMethod { + /// Resume from an existing database. + Resume, + /// Generate a state with `validator_count` validators, all with well-known secret keys. + /// + /// Set the genesis time to be the start of the previous 30-minute window. + RecentGenesis { validator_count: usize }, + /// Generate a state with `genesis_time` and `validator_count` validators, all with well-known + /// secret keys. + Generated { + validator_count: usize, + genesis_time: u64, + }, + /// Load a YAML-encoded genesis state from a file. + Yaml { file: PathBuf }, + /// Use a HTTP server (running our REST-API) to load genesis and finalized states and blocks. + HttpBootstrap { + server: String, + port: Option, + }, +} + +impl Default for BootMethod { + fn default() -> Self { + BootMethod::Resume + } +} + pub enum GenesisState { /// Use the mainnet genesis state. /// @@ -61,9 +89,7 @@ impl Default for Config { rpc: rpc::RPCConfig::default(), rest_api: rest_api::ApiConfig::default(), spec_constants: TESTNET_SPEC_CONSTANTS.into(), - genesis_state: GenesisState::RecentGenesis { - validator_count: TESTNET_VALIDATOR_COUNT, - }, + boot_method: BootMethod::default(), } } } @@ -140,40 +166,6 @@ impl Config { self.update_logger(log)?; }; - // If the `--bootstrap` flag is provided, overwrite the default configuration. - if let Some(server) = args.value_of("bootstrap") { - do_bootstrapping(self, server.to_string(), &log)?; - } - Ok(()) } } - -/// Perform the HTTP bootstrapping procedure, reading an ENR and multiaddr from the HTTP server and -/// adding them to the `config`. -fn do_bootstrapping(config: &mut Config, server: String, log: &slog::Logger) -> Result<(), String> { - // Set the genesis state source. - config.genesis_state = GenesisState::HttpBootstrap { - server: server.to_string(), - }; - - let bootstrapper = Bootstrapper::from_server_string(server.to_string())?; - - config.network.boot_nodes.push(bootstrapper.enr()?); - - if let Some(server_multiaddr) = bootstrapper.best_effort_multiaddr() { - info!( - log, - "Estimated bootstrapper libp2p address"; - "multiaddr" => format!("{:?}", server_multiaddr) - ); - config.network.libp2p_nodes.push(server_multiaddr); - } else { - warn!( - log, - "Unable to estimate a bootstrapper libp2p address, this node may not find any peers." - ); - } - - Ok(()) -} diff --git a/beacon_node/src/config.rs b/beacon_node/src/config.rs index 959edbd607..b66a00abba 100644 --- a/beacon_node/src/config.rs +++ b/beacon_node/src/config.rs @@ -1,5 +1,5 @@ use clap::ArgMatches; -use client::{ClientConfig, Eth2Config}; +use client::{Bootstrapper, ClientConfig, Eth2Config}; use eth2_config::{read_from_file, write_to_file}; use rand::{distributions::Alphanumeric, Rng}; use slog::{crit, info, Logger}; @@ -30,6 +30,41 @@ pub fn get_configs(matches: &ArgMatches, log: &Logger) -> Result { ); builder.update_spec_from_subcommand(&sub_matches)?; + + match sub_matches.subcommand() { + // The bootstrap testnet method requires inserting a libp2p address into the + // network config. + ("bootstrap", Some(sub_matches)) => { + let server = sub_matches + .value_of("server") + .ok_or_else(|| "No bootstrap server specified".into())?; + + let bootstrapper = Bootstrapper::from_server_string(server.to_string())?; + + if let Some(server_multiaddr) = + bootstrapper.best_effort_multiaddr(sub_matches.value_of("libp2p_port")) + { + info!( + log, + "Estimated bootstrapper libp2p address"; + "multiaddr" => format!("{:?}", server_multiaddr) + ); + + builder + .client_config + .network + .libp2p_nodes + .push(server_multiaddr); + } else { + warn!( + log, + "Unable to estimate a bootstrapper libp2p address, this node may not find any peers." + ); + }; + } + _ => (), + }; + builder.write_configs_to_new_datadir()?; } _ => { @@ -53,8 +88,8 @@ struct ConfigBuilder<'a> { matches: &'a ArgMatches<'a>, log: &'a Logger, pub data_dir: PathBuf, - eth2_config: Eth2Config, - client_config: ClientConfig, + pub eth2_config: Eth2Config, + pub client_config: ClientConfig, } impl<'a> ConfigBuilder<'a> { From 66d78387079c187545646bf2047428d872327113 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sun, 25 Aug 2019 09:43:03 +1000 Subject: [PATCH 53/62] Remove GenesisConfig, add BeaconChainStartMethod --- beacon_node/client/src/beacon_chain_types.rs | 14 ++--- beacon_node/client/src/config.rs | 61 ++++++++------------ beacon_node/client/src/lib.rs | 2 +- beacon_node/src/config.rs | 18 ++++-- beacon_node/src/main.rs | 4 +- 5 files changed, 46 insertions(+), 53 deletions(-) diff --git a/beacon_node/client/src/beacon_chain_types.rs b/beacon_node/client/src/beacon_chain_types.rs index 5168c067a9..37e4a055e8 100644 --- a/beacon_node/client/src/beacon_chain_types.rs +++ b/beacon_node/client/src/beacon_chain_types.rs @@ -1,6 +1,6 @@ use crate::bootstrapper::Bootstrapper; use crate::error::Result; -use crate::{config::GenesisState, ClientConfig}; +use crate::{config::BeaconChainStartMethod, ClientConfig}; use beacon_chain::{ lmd_ghost::{LmdGhost, ThreadSafeReducedTree}, slot_clock::SystemTimeSlotClock, @@ -59,19 +59,19 @@ where T: BeaconChainTypes, T::LmdGhost: LmdGhost, { - let genesis_state = match &config.genesis_state { - GenesisState::Mainnet => { + let genesis_state = match &config.beacon_chain_start_method { + BeaconChainStartMethod::Resume => { crit!(log, "This release does not support mainnet genesis state."); return Err("Mainnet is unsupported".into()); } - GenesisState::RecentGenesis { validator_count } => { + BeaconChainStartMethod::RecentGenesis { validator_count } => { generate_testnet_genesis_state(*validator_count, recent_genesis_time(), &spec) } - GenesisState::Generated { + BeaconChainStartMethod::Generated { validator_count, genesis_time, } => generate_testnet_genesis_state(*validator_count, *genesis_time, &spec), - GenesisState::Yaml { file } => { + BeaconChainStartMethod::Yaml { file } => { let file = File::open(file).map_err(|e| { format!("Unable to open YAML genesis state file {:?}: {:?}", file, e) })?; @@ -79,7 +79,7 @@ where serde_yaml::from_reader(file) .map_err(|e| format!("Unable to parse YAML genesis state file: {:?}", e))? } - GenesisState::HttpBootstrap { server } => { + BeaconChainStartMethod::HttpBootstrap { server, .. } => { let bootstrapper = Bootstrapper::from_server_string(server.to_string()) .map_err(|e| format!("Failed to initialize bootstrap client: {}", e))?; diff --git a/beacon_node/client/src/config.rs b/beacon_node/client/src/config.rs index e802a93a3d..1e8f60f6ef 100644 --- a/beacon_node/client/src/config.rs +++ b/beacon_node/client/src/config.rs @@ -1,15 +1,11 @@ -use crate::Bootstrapper; use clap::ArgMatches; use network::NetworkConfig; use serde_derive::{Deserialize, Serialize}; -use slog::{info, o, warn, Drain}; +use slog::{info, o, Drain}; use std::fs::{self, OpenOptions}; use std::path::PathBuf; use std::sync::Mutex; -/// The number initial validators when starting the `Minimal`. -const TESTNET_VALIDATOR_COUNT: usize = 16; - /// The number initial validators when starting the `Minimal`. const TESTNET_SPEC_CONSTANTS: &str = "minimal"; @@ -21,63 +17,52 @@ pub struct Config { db_name: String, pub log_file: PathBuf, pub spec_constants: String, + /// Defines how we should initialize a BeaconChain instances. + /// + /// This field is not serialized, there for it will not be written to (or loaded from) config + /// files. It can only be configured via the CLI. #[serde(skip)] - pub boot_method: BootMethod, + pub beacon_chain_start_method: BeaconChainStartMethod, pub network: network::NetworkConfig, pub rpc: rpc::RPCConfig, pub rest_api: rest_api::ApiConfig, } +/// Defines how the client should initialize a BeaconChain. +/// +/// In general, there are two methods: +/// - resuming a new chain, or +/// - initializing a new one. #[derive(Debug, Clone)] -pub enum BootMethod { - /// Resume from an existing database. +pub enum BeaconChainStartMethod { + /// Resume from an existing BeaconChain, loaded from the existing local database. Resume, - /// Generate a state with `validator_count` validators, all with well-known secret keys. + /// Create a new beacon chain with `validator_count` validators, all with well-known secret keys. /// /// Set the genesis time to be the start of the previous 30-minute window. RecentGenesis { validator_count: usize }, - /// Generate a state with `genesis_time` and `validator_count` validators, all with well-known + /// Create a new beacon chain with `genesis_time` and `validator_count` validators, all with well-known /// secret keys. Generated { validator_count: usize, genesis_time: u64, }, - /// Load a YAML-encoded genesis state from a file. + /// Create a new beacon chain by loading a YAML-encoded genesis state from a file. Yaml { file: PathBuf }, - /// Use a HTTP server (running our REST-API) to load genesis and finalized states and blocks. + /// Create a new beacon chain by using a HTTP server (running our REST-API) to load genesis and + /// finalized states and blocks. HttpBootstrap { server: String, port: Option, }, } -impl Default for BootMethod { +impl Default for BeaconChainStartMethod { fn default() -> Self { - BootMethod::Resume + BeaconChainStartMethod::Resume } } -pub enum GenesisState { - /// Use the mainnet genesis state. - /// - /// Mainnet genesis state is not presently known, so this is a place-holder. - Mainnet, - /// Generate a state with `validator_count` validators, all with well-known secret keys. - /// - /// Set the genesis time to be the start of the previous 30-minute window. - RecentGenesis { validator_count: usize }, - /// Generate a state with `genesis_time` and `validator_count` validators, all with well-known - /// secret keys. - Generated { - validator_count: usize, - genesis_time: u64, - }, - /// Load a YAML-encoded genesis state from a file. - Yaml { file: PathBuf }, - /// Use a HTTP server (running our REST-API) to load genesis and finalized states and blocks. - HttpBootstrap { server: String }, -} - impl Default for Config { fn default() -> Self { Self { @@ -86,10 +71,10 @@ impl Default for Config { db_type: "disk".to_string(), db_name: "chain_db".to_string(), network: NetworkConfig::new(), - rpc: rpc::RPCConfig::default(), - rest_api: rest_api::ApiConfig::default(), + rpc: <_>::default(), + rest_api: <_>::default(), spec_constants: TESTNET_SPEC_CONSTANTS.into(), - boot_method: BootMethod::default(), + beacon_chain_start_method: <_>::default(), } } } diff --git a/beacon_node/client/src/lib.rs b/beacon_node/client/src/lib.rs index 6405e05e71..3eb5553696 100644 --- a/beacon_node/client/src/lib.rs +++ b/beacon_node/client/src/lib.rs @@ -23,7 +23,7 @@ pub use beacon_chain::BeaconChainTypes; pub use beacon_chain_types::ClientType; pub use beacon_chain_types::InitialiseBeaconChain; pub use bootstrapper::Bootstrapper; -pub use config::{Config as ClientConfig, GenesisState}; +pub use config::Config as ClientConfig; pub use eth2_config::Eth2Config; /// Main beacon node client service. This provides the connection and initialisation of the clients diff --git a/beacon_node/src/config.rs b/beacon_node/src/config.rs index b66a00abba..a97ec3708d 100644 --- a/beacon_node/src/config.rs +++ b/beacon_node/src/config.rs @@ -2,7 +2,7 @@ use clap::ArgMatches; use client::{Bootstrapper, ClientConfig, Eth2Config}; use eth2_config::{read_from_file, write_to_file}; use rand::{distributions::Alphanumeric, Rng}; -use slog::{crit, info, Logger}; +use slog::{crit, info, warn, Logger}; use std::fs; use std::path::PathBuf; @@ -35,15 +35,16 @@ pub fn get_configs(matches: &ArgMatches, log: &Logger) -> Result { // The bootstrap testnet method requires inserting a libp2p address into the // network config. ("bootstrap", Some(sub_matches)) => { - let server = sub_matches + let server: String = sub_matches .value_of("server") - .ok_or_else(|| "No bootstrap server specified".into())?; + .ok_or_else(|| "No bootstrap server specified")? + .to_string(); let bootstrapper = Bootstrapper::from_server_string(server.to_string())?; - if let Some(server_multiaddr) = - bootstrapper.best_effort_multiaddr(sub_matches.value_of("libp2p_port")) - { + if let Some(server_multiaddr) = bootstrapper.best_effort_multiaddr( + parse_port_option(sub_matches.value_of("libp2p_port")), + ) { info!( log, "Estimated bootstrapper libp2p address"; @@ -83,6 +84,11 @@ pub fn get_configs(matches: &ArgMatches, log: &Logger) -> Result { builder.build() } +/// Decodes an optional string into an optional u16. +fn parse_port_option(o: Option<&str>) -> Option { + o.and_then(|s| s.parse::().ok()) +} + /// Allows for building a set of configurations based upon `clap` arguments. struct ConfigBuilder<'a> { matches: &'a ArgMatches<'a>, diff --git a/beacon_node/src/main.rs b/beacon_node/src/main.rs index 12c9b8a017..d7a4bae795 100644 --- a/beacon_node/src/main.rs +++ b/beacon_node/src/main.rs @@ -227,7 +227,9 @@ fn main() { .short("p") .long("port") .value_name("TCP_PORT") - .help("A libp2p listen port used to peer with the bootstrap server")) + .help("A libp2p listen port used to peer with the bootstrap server. This flag is useful \ + when port-fowarding is used: you may connect using a different port than \ + the one the server is immediately listening on.")) ) .subcommand(SubCommand::with_name("recent") .about("Creates a new genesis state where the genesis time was at the previous \ From 7fd7aa2cdbe1538232440c22ea8f752ede465bd2 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sun, 25 Aug 2019 10:09:51 +1000 Subject: [PATCH 54/62] Tidy ConfigBuilder --- beacon_node/src/config.rs | 136 +++++++++++++++++++++----------------- 1 file changed, 77 insertions(+), 59 deletions(-) diff --git a/beacon_node/src/config.rs b/beacon_node/src/config.rs index a97ec3708d..c1074da03d 100644 --- a/beacon_node/src/config.rs +++ b/beacon_node/src/config.rs @@ -14,12 +14,12 @@ type Result = std::result::Result; type Config = (ClientConfig, Eth2Config); /// Gets the fully-initialized global client and eth2 configuration objects. -pub fn get_configs(matches: &ArgMatches, log: &Logger) -> Result { - let mut builder = ConfigBuilder::new(matches, log)?; +pub fn get_configs(cli_args: &ArgMatches, log: &Logger) -> Result { + let mut builder = ConfigBuilder::new(cli_args, log)?; - match matches.subcommand() { - ("testnet", Some(sub_matches)) => { - if sub_matches.is_present("random-datadir") { + match cli_args.subcommand() { + ("testnet", Some(sub_cmd_args)) => { + if sub_cmd_args.is_present("random-datadir") { builder.set_random_datadir()?; } @@ -29,39 +29,13 @@ pub fn get_configs(matches: &ArgMatches, log: &Logger) -> Result { "path" => format!("{:?}", builder.data_dir) ); - builder.update_spec_from_subcommand(&sub_matches)?; + builder.update_spec_from_subcommand(&sub_cmd_args)?; - match sub_matches.subcommand() { + match sub_cmd_args.subcommand() { // The bootstrap testnet method requires inserting a libp2p address into the // network config. - ("bootstrap", Some(sub_matches)) => { - let server: String = sub_matches - .value_of("server") - .ok_or_else(|| "No bootstrap server specified")? - .to_string(); - - let bootstrapper = Bootstrapper::from_server_string(server.to_string())?; - - if let Some(server_multiaddr) = bootstrapper.best_effort_multiaddr( - parse_port_option(sub_matches.value_of("libp2p_port")), - ) { - info!( - log, - "Estimated bootstrapper libp2p address"; - "multiaddr" => format!("{:?}", server_multiaddr) - ); - - builder - .client_config - .network - .libp2p_nodes - .push(server_multiaddr); - } else { - warn!( - log, - "Unable to estimate a bootstrapper libp2p address, this node may not find any peers." - ); - }; + ("bootstrap", Some(sub_cmd_args)) => { + builder.import_bootstrap_libp2p_address(&sub_cmd_args)?; } _ => (), }; @@ -81,7 +55,7 @@ pub fn get_configs(matches: &ArgMatches, log: &Logger) -> Result { } }; - builder.build() + builder.build(cli_args) } /// Decodes an optional string into an optional u16. @@ -91,21 +65,20 @@ fn parse_port_option(o: Option<&str>) -> Option { /// Allows for building a set of configurations based upon `clap` arguments. struct ConfigBuilder<'a> { - matches: &'a ArgMatches<'a>, log: &'a Logger, pub data_dir: PathBuf, - pub eth2_config: Eth2Config, - pub client_config: ClientConfig, + eth2_config: Eth2Config, + client_config: ClientConfig, } impl<'a> ConfigBuilder<'a> { /// Create a new builder with default settings. - pub fn new(matches: &'a ArgMatches, log: &'a Logger) -> Result { + pub fn new(cli_args: &'a ArgMatches, log: &'a Logger) -> Result { // Read the `--datadir` flag. // // If it's not present, try and find the home directory (`~`) and push the default data // directory onto it. - let data_dir: PathBuf = matches + let data_dir: PathBuf = cli_args .value_of("datadir") .map(|string| PathBuf::from(string)) .or_else(|| { @@ -117,7 +90,6 @@ impl<'a> ConfigBuilder<'a> { .ok_or_else(|| "Unable to find a home directory for the datadir".to_string())?; Ok(Self { - matches, log, data_dir, eth2_config: Eth2Config::minimal(), @@ -125,23 +97,47 @@ impl<'a> ConfigBuilder<'a> { }) } - /// Consumes self, returning the configs. - pub fn build(mut self) -> Result { - self.eth2_config.apply_cli_args(&self.matches)?; - self.client_config - .apply_cli_args(&self.matches, &mut self.log.clone())?; + pub fn set_beacon_chain_start_method(&mut self, cli_args: &ArgMatches) -> Result<()> { + // + } - if self.eth2_config.spec_constants != self.client_config.spec_constants { - crit!(self.log, "Specification constants do not match."; - "client_config" => format!("{}", self.client_config.spec_constants), - "eth2_config" => format!("{}", self.eth2_config.spec_constants) + /// Reads a `server` flag from `cli_args` and attempts to generate a libp2p `Multiaddr` that + /// this client can use to connect to the given `server`. + /// + /// Also reads for a `libp2p_port` flag in `cli_args`, using that as the port for the + /// `Multiaddr`. If `libp2p_port` is not in `cli_args`, attempts to connect to `server` via HTTP + /// and retrieve it's libp2p listen port. + /// + /// Returns an error if the `server` flag is not present in `cli_args`. + pub fn import_bootstrap_libp2p_address(&mut self, cli_args: &ArgMatches) -> Result<()> { + let server: String = cli_args + .value_of("server") + .ok_or_else(|| "No bootstrap server specified")? + .to_string(); + + let bootstrapper = Bootstrapper::from_server_string(server.to_string())?; + + if let Some(server_multiaddr) = + bootstrapper.best_effort_multiaddr(parse_port_option(cli_args.value_of("libp2p_port"))) + { + info!( + self.log, + "Estimated bootstrapper libp2p address"; + "multiaddr" => format!("{:?}", server_multiaddr) ); - return Err("Specification constant mismatch".into()); - } - self.client_config.data_dir = self.data_dir; + self.client_config + .network + .libp2p_nodes + .push(server_multiaddr); + } else { + warn!( + self.log, + "Unable to estimate a bootstrapper libp2p address, this node may not find any peers." + ); + }; - Ok((self.client_config, self.eth2_config)) + Ok(()) } /// Set the config data_dir to be an random directory. @@ -165,20 +161,20 @@ impl<'a> ConfigBuilder<'a> { /// Reads the subcommand and tries to update `self.eth2_config` based up on the `--spec` flag. /// - /// Returns an error if the `--spec` flag is not present. - pub fn update_spec_from_subcommand(&mut self, sub_matches: &ArgMatches) -> Result<()> { + /// Returns an error if the `--spec` flag is not present in the given `cli_args`. + pub fn update_spec_from_subcommand(&mut self, cli_args: &ArgMatches) -> Result<()> { // Re-initialise the `Eth2Config`. // // If a CLI parameter is set, overwrite any config file present. // If a parameter is not set, use either the config file present or default to minimal. - let eth2_config = match sub_matches.value_of("spec") { + let eth2_config = match cli_args.value_of("spec") { Some("mainnet") => Eth2Config::mainnet(), Some("minimal") => Eth2Config::minimal(), Some("interop") => Eth2Config::interop(), _ => return Err("Unable to determine specification type.".into()), }; - self.client_config.spec_constants = sub_matches + self.client_config.spec_constants = cli_args .value_of("spec") .expect("Guarded by prior match statement") .to_string(); @@ -244,4 +240,26 @@ impl<'a> ConfigBuilder<'a> { Ok(()) } + + /// Consumes self, returning the configs. + /// + /// The supplied `cli_args` should be the base-level `clap` cli_args (i.e., not a subcommand + /// cli_args). + pub fn build(mut self, cli_args: &ArgMatches) -> Result { + self.eth2_config.apply_cli_args(cli_args)?; + self.client_config + .apply_cli_args(cli_args, &mut self.log.clone())?; + + if self.eth2_config.spec_constants != self.client_config.spec_constants { + crit!(self.log, "Specification constants do not match."; + "client_config" => format!("{}", self.client_config.spec_constants), + "eth2_config" => format!("{}", self.eth2_config.spec_constants) + ); + return Err("Specification constant mismatch".into()); + } + + self.client_config.data_dir = self.data_dir; + + Ok((self.client_config, self.eth2_config)) + } } From 140c677a38d16a8f51f4b6521ee0f74f1cd1ddca Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sun, 25 Aug 2019 12:14:04 +1000 Subject: [PATCH 55/62] Add much more progress to new CLI setup --- beacon_node/client/src/beacon_chain_types.rs | 7 +- beacon_node/client/src/config.rs | 9 +- beacon_node/client/src/lib.rs | 2 +- beacon_node/src/config.rs | 258 ++++++++++++++----- beacon_node/src/main.rs | 19 +- 5 files changed, 214 insertions(+), 81 deletions(-) diff --git a/beacon_node/client/src/beacon_chain_types.rs b/beacon_node/client/src/beacon_chain_types.rs index 37e4a055e8..7a57aa4757 100644 --- a/beacon_node/client/src/beacon_chain_types.rs +++ b/beacon_node/client/src/beacon_chain_types.rs @@ -60,9 +60,10 @@ where T::LmdGhost: LmdGhost, { let genesis_state = match &config.beacon_chain_start_method { - BeaconChainStartMethod::Resume => { - crit!(log, "This release does not support mainnet genesis state."); - return Err("Mainnet is unsupported".into()); + BeaconChainStartMethod::Resume => unimplemented!("No resume code yet"), + BeaconChainStartMethod::Mainnet => { + crit!(log, "No mainnet beacon chain startup specification."); + return Err("Mainnet is not yet specified. We're working on it.".into()); } BeaconChainStartMethod::RecentGenesis { validator_count } => { generate_testnet_genesis_state(*validator_count, recent_genesis_time(), &spec) diff --git a/beacon_node/client/src/config.rs b/beacon_node/client/src/config.rs index 1e8f60f6ef..f2725b3e79 100644 --- a/beacon_node/client/src/config.rs +++ b/beacon_node/client/src/config.rs @@ -37,7 +37,9 @@ pub struct Config { pub enum BeaconChainStartMethod { /// Resume from an existing BeaconChain, loaded from the existing local database. Resume, - /// Create a new beacon chain with `validator_count` validators, all with well-known secret keys. + /// Resume from an existing BeaconChain, loaded from the existing local database. + Mainnet, + /// Create a new beacon chain that can connect to mainnet. /// /// Set the genesis time to be the start of the previous 30-minute window. RecentGenesis { validator_count: usize }, @@ -51,10 +53,7 @@ pub enum BeaconChainStartMethod { Yaml { file: PathBuf }, /// Create a new beacon chain by using a HTTP server (running our REST-API) to load genesis and /// finalized states and blocks. - HttpBootstrap { - server: String, - port: Option, - }, + HttpBootstrap { server: String, port: Option }, } impl Default for BeaconChainStartMethod { diff --git a/beacon_node/client/src/lib.rs b/beacon_node/client/src/lib.rs index 3eb5553696..9d3e001faf 100644 --- a/beacon_node/client/src/lib.rs +++ b/beacon_node/client/src/lib.rs @@ -23,7 +23,7 @@ pub use beacon_chain::BeaconChainTypes; pub use beacon_chain_types::ClientType; pub use beacon_chain_types::InitialiseBeaconChain; pub use bootstrapper::Bootstrapper; -pub use config::Config as ClientConfig; +pub use config::{BeaconChainStartMethod, Config as ClientConfig}; pub use eth2_config::Eth2Config; /// Main beacon node client service. This provides the connection and initialisation of the clients diff --git a/beacon_node/src/config.rs b/beacon_node/src/config.rs index c1074da03d..68d905ed22 100644 --- a/beacon_node/src/config.rs +++ b/beacon_node/src/config.rs @@ -1,10 +1,10 @@ use clap::ArgMatches; -use client::{Bootstrapper, ClientConfig, Eth2Config}; +use client::{BeaconChainStartMethod, Bootstrapper, ClientConfig, Eth2Config}; use eth2_config::{read_from_file, write_to_file}; use rand::{distributions::Alphanumeric, Rng}; use slog::{crit, info, warn, Logger}; use std::fs; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; pub const DEFAULT_DATA_DIR: &str = ".lighthouse"; pub const CLIENT_CONFIG_FILENAME: &str = "beacon-node.toml"; @@ -19,29 +19,9 @@ pub fn get_configs(cli_args: &ArgMatches, log: &Logger) -> Result { match cli_args.subcommand() { ("testnet", Some(sub_cmd_args)) => { - if sub_cmd_args.is_present("random-datadir") { - builder.set_random_datadir()?; - } - - info!( - log, - "Creating new datadir"; - "path" => format!("{:?}", builder.data_dir) - ); - - builder.update_spec_from_subcommand(&sub_cmd_args)?; - - match sub_cmd_args.subcommand() { - // The bootstrap testnet method requires inserting a libp2p address into the - // network config. - ("bootstrap", Some(sub_cmd_args)) => { - builder.import_bootstrap_libp2p_address(&sub_cmd_args)?; - } - _ => (), - }; - - builder.write_configs_to_new_datadir()?; + process_testnet_subcommand(&mut builder, sub_cmd_args, log)? } + // No sub-command assumes a resume operation. _ => { info!( log, @@ -49,6 +29,20 @@ pub fn get_configs(cli_args: &ArgMatches, log: &Logger) -> Result { "path" => format!("{:?}", builder.data_dir) ); + // If no primary subcommand was given, start the beacon chain from an existing + // database. + builder.set_beacon_chain_start_method(BeaconChainStartMethod::Resume); + + // Whilst there is no large testnet or mainnet force the user to specify how they want + // to start a new chain (e.g., from a genesis YAML file, another node, etc). + if !builder.data_dir.exists() { + return Err( + "No datadir found. To start a new beacon chain, see `testnet --help`. \ + Use `--datadir` to specify a different directory" + .into(), + ); + } + // If the `testnet` command was not provided, attempt to load an existing datadir and // continue with an existing chain. builder.load_from_datadir()?; @@ -58,9 +52,62 @@ pub fn get_configs(cli_args: &ArgMatches, log: &Logger) -> Result { builder.build(cli_args) } -/// Decodes an optional string into an optional u16. -fn parse_port_option(o: Option<&str>) -> Option { - o.and_then(|s| s.parse::().ok()) +/// Process the `testnet` CLI subcommand arguments, updating the `builder`. +fn process_testnet_subcommand( + builder: &mut ConfigBuilder, + cli_args: &ArgMatches, + log: &Logger, +) -> Result<()> { + if cli_args.is_present("random-datadir") { + builder.set_random_datadir()?; + } + + if cli_args.is_present("force") { + builder.clean_datadir()?; + } + + info!( + log, + "Creating new datadir"; + "path" => format!("{:?}", builder.data_dir) + ); + + builder.update_spec_from_subcommand(&cli_args)?; + + // Start matching on the second subcommand (e.g., `testnet bootstrap ...`) + match cli_args.subcommand() { + ("bootstrap", Some(cli_args)) => { + let server = cli_args + .value_of("server") + .ok_or_else(|| "No bootstrap server specified")?; + let port: Option = cli_args + .value_of("port") + .and_then(|s| s.parse::().ok()); + + builder.import_bootstrap_libp2p_address(server, port)?; + + builder.set_beacon_chain_start_method(BeaconChainStartMethod::HttpBootstrap { + server: server.to_string(), + port, + }) + } + ("recent", Some(cli_args)) => { + let validator_count = cli_args + .value_of("validator_count") + .ok_or_else(|| "No validator_count specified")? + .parse::() + .map_err(|e| format!("Unable to parse validator_count: {:?}", e))?; + + builder.set_beacon_chain_start_method(BeaconChainStartMethod::RecentGenesis { + validator_count, + }) + } + _ => return Err("No testnet method specified. See 'testnet --help'.".into()), + }; + + builder.write_configs_to_new_datadir()?; + + Ok(()) } /// Allows for building a set of configurations based upon `clap` arguments. @@ -97,29 +144,65 @@ impl<'a> ConfigBuilder<'a> { }) } - pub fn set_beacon_chain_start_method(&mut self, cli_args: &ArgMatches) -> Result<()> { - // + /// Clears any configuration files that would interfere with writing new configs. + /// + /// Moves the following files in `data_dir` into a backup directory: + /// + /// - Client config + /// - Eth2 config + /// - The entire database directory + pub fn clean_datadir(&mut self) -> Result<()> { + let backup_dir = { + let mut s = String::from("backup_"); + s.push_str(&random_string(6)); + self.data_dir.join(s) + }; + + fs::create_dir_all(&backup_dir) + .map_err(|e| format!("Unable to create config backup dir: {:?}", e))?; + + let move_to_backup_dir = |path: &Path| -> Result<()> { + let file_name = path + .file_name() + .ok_or_else(|| "Invalid path found during datadir clean (no filename).")?; + + let mut new = path.to_path_buf(); + new.pop(); + new.push(backup_dir.clone()); + new.push(file_name); + + let _ = fs::rename(path, new); + + Ok(()) + }; + + move_to_backup_dir(&self.data_dir.join(CLIENT_CONFIG_FILENAME))?; + move_to_backup_dir(&self.data_dir.join(ETH2_CONFIG_FILENAME))?; + + if let Some(db_path) = self.client_config.db_path() { + move_to_backup_dir(&db_path)?; + } + + Ok(()) } - /// Reads a `server` flag from `cli_args` and attempts to generate a libp2p `Multiaddr` that - /// this client can use to connect to the given `server`. - /// - /// Also reads for a `libp2p_port` flag in `cli_args`, using that as the port for the - /// `Multiaddr`. If `libp2p_port` is not in `cli_args`, attempts to connect to `server` via HTTP - /// and retrieve it's libp2p listen port. - /// - /// Returns an error if the `server` flag is not present in `cli_args`. - pub fn import_bootstrap_libp2p_address(&mut self, cli_args: &ArgMatches) -> Result<()> { - let server: String = cli_args - .value_of("server") - .ok_or_else(|| "No bootstrap server specified")? - .to_string(); + /// Sets the method for starting the beacon chain. + pub fn set_beacon_chain_start_method(&mut self, method: BeaconChainStartMethod) { + self.client_config.beacon_chain_start_method = method; + } + /// Import the libp2p address for `server` into the list of bootnodes in `self`. + /// + /// If `port` is `Some`, it is used as the port for the `Multiaddr`. If `port` is `None`, + /// attempts to connect to the `server` via HTTP and retrieve it's libp2p listen port. + pub fn import_bootstrap_libp2p_address( + &mut self, + server: &str, + port: Option, + ) -> Result<()> { let bootstrapper = Bootstrapper::from_server_string(server.to_string())?; - if let Some(server_multiaddr) = - bootstrapper.best_effort_multiaddr(parse_port_option(cli_args.value_of("libp2p_port"))) - { + if let Some(server_multiaddr) = bootstrapper.best_effort_multiaddr(port) { info!( self.log, "Estimated bootstrapper libp2p address"; @@ -132,9 +215,9 @@ impl<'a> ConfigBuilder<'a> { .push(server_multiaddr); } else { warn!( - self.log, - "Unable to estimate a bootstrapper libp2p address, this node may not find any peers." - ); + self.log, + "Unable to estimate a bootstrapper libp2p address, this node may not find any peers." + ); }; Ok(()) @@ -144,14 +227,9 @@ impl<'a> ConfigBuilder<'a> { /// /// Useful for easily spinning up ephemeral testnets. pub fn set_random_datadir(&mut self) -> Result<()> { - let random = rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(10) - .collect::(); - let mut s = DEFAULT_DATA_DIR.to_string(); s.push_str("_random_"); - s.push_str(&random); + s.push_str(&random_string(6)); self.data_dir.pop(); self.data_dir.push(s); @@ -187,12 +265,15 @@ impl<'a> ConfigBuilder<'a> { /// /// Returns an error if `self.data_dir` already exists. pub fn write_configs_to_new_datadir(&mut self) -> Result<()> { + let db_exists = self + .client_config + .db_path() + .map(|d| d.exists()) + .unwrap_or_else(|| false); + // Do not permit creating a new config when the datadir exists. - if self.data_dir.exists() { - return Err( - "Datadir already exists, will not overwrite. Remove the directory or use --datadir." - .into(), - ); + if db_exists { + return Err("Database already exists. See `-f` in `testnet --help`".into()); } // Create `datadir` and any non-existing parent directories. @@ -201,16 +282,35 @@ impl<'a> ConfigBuilder<'a> { format!("{}", e) })?; - // Write the client config to a TOML file in the datadir. - write_to_file( - self.data_dir.join(CLIENT_CONFIG_FILENAME), - &self.client_config, - ) - .map_err(|e| format!("Unable to write {} file: {:?}", CLIENT_CONFIG_FILENAME, e))?; + let client_config_file = self.data_dir.join(CLIENT_CONFIG_FILENAME); + if client_config_file.exists() { + return Err(format!( + "Datadir is not clean, {} exists. See `-f` in `testnet --help`.", + CLIENT_CONFIG_FILENAME + )); + } else { + // Write the onfig to a TOML file in the datadir. + write_to_file( + self.data_dir.join(CLIENT_CONFIG_FILENAME), + &self.client_config, + ) + .map_err(|e| format!("Unable to write {} file: {:?}", CLIENT_CONFIG_FILENAME, e))?; + } - // Write the eth2 config to a TOML file in the datadir. - write_to_file(self.data_dir.join(ETH2_CONFIG_FILENAME), &self.eth2_config) + let eth2_config_file = self.data_dir.join(ETH2_CONFIG_FILENAME); + if eth2_config_file.exists() { + return Err(format!( + "Datadir is not clean, {} exists. See `-f` in `testnet --help`.", + ETH2_CONFIG_FILENAME + )); + } else { + // Write the config to a TOML file in the datadir. + write_to_file( + self.data_dir.join(ETH2_CONFIG_FILENAME), + &self.client_config, + ) .map_err(|e| format!("Unable to write {} file: {:?}", ETH2_CONFIG_FILENAME, e))?; + } Ok(()) } @@ -225,7 +325,22 @@ impl<'a> ConfigBuilder<'a> { // public testnet or mainnet). if !self.data_dir.exists() { return Err( - "No datadir found. Use the 'testnet' sub-command to select a testnet type.".into(), + "No datadir found. Either create a new testnet or specify a different `--datadir`." + .into(), + ); + } + + // If there is a path to a databse in the config, ensure it exists. + if !self + .client_config + .db_path() + .map(|path| path.exists()) + .unwrap_or_else(|| true) + { + return Err( + "No database found in datadir. Use the 'testnet -f' sub-command to overwrite the \ + existing datadir, or specify a different `--datadir`." + .into(), ); } @@ -263,3 +378,10 @@ impl<'a> ConfigBuilder<'a> { Ok((self.client_config, self.eth2_config)) } } + +fn random_string(len: usize) -> String { + rand::thread_rng() + .sample_iter(&Alphanumeric) + .take(len) + .collect::() +} diff --git a/beacon_node/src/main.rs b/beacon_node/src/main.rs index d7a4bae795..4430db1287 100644 --- a/beacon_node/src/main.rs +++ b/beacon_node/src/main.rs @@ -161,7 +161,7 @@ fn main() { .help("Type of database to use.") .takes_value(true) .possible_values(&["disk", "memory"]) - .default_value("memory"), + .default_value("disk"), ) /* * Logging. @@ -207,15 +207,20 @@ fn main() { iteration.") ) .arg( - Arg::with_name("force-create") - .long("force-create") + Arg::with_name("force") + .long("force") .short("f") - .help("If present, will delete any existing datadir before creating a new one. Cannot be \ + .help("If present, will backup any existing config files before creating new ones. Cannot be \ used when specifying --random-datadir (logic error).") .conflicts_with("random-datadir") ) /* * Testnet sub-commands. + * + * `boostrap` + * + * Start a new node by downloading genesis and network info from another node via the + * HTTP API. */ .subcommand(SubCommand::with_name("bootstrap") .about("Connects to the given HTTP server, downloads a genesis state and attempts to peer with it.") @@ -231,6 +236,12 @@ fn main() { when port-fowarding is used: you may connect using a different port than \ the one the server is immediately listening on.")) ) + /* + * `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 \ 30-minute boundary (e.g., 12:00, 12:30, 13:00, etc.)") From cf435d96536567414141ccf3c1bfaa9b292cb523 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Mon, 26 Aug 2019 14:45:49 +1000 Subject: [PATCH 56/62] Refactor beacon chain start code --- beacon_node/Cargo.toml | 1 + beacon_node/beacon_chain/Cargo.toml | 3 + beacon_node/beacon_chain/src/beacon_chain.rs | 8 +- .../beacon_chain/src/beacon_chain_builder.rs | 98 ++++++++-- .../src/bootstrapper.rs | 0 beacon_node/beacon_chain/src/lib.rs | 2 + beacon_node/beacon_chain/src/test_utils.rs | 20 +-- beacon_node/client/Cargo.toml | 2 - beacon_node/client/src/beacon_chain_types.rs | 170 ------------------ beacon_node/client/src/lib.rs | 74 ++++++-- beacon_node/src/config.rs | 3 +- beacon_node/src/run.rs | 7 +- 12 files changed, 161 insertions(+), 227 deletions(-) rename beacon_node/{client => beacon_chain}/src/bootstrapper.rs (100%) delete mode 100644 beacon_node/client/src/beacon_chain_types.rs diff --git a/beacon_node/Cargo.toml b/beacon_node/Cargo.toml index 9ce724c148..5efb734239 100644 --- a/beacon_node/Cargo.toml +++ b/beacon_node/Cargo.toml @@ -6,6 +6,7 @@ edition = "2018" [dependencies] eth2_config = { path = "../eth2/utils/eth2_config" } +beacon_chain = { path = "beacon_chain" } types = { path = "../eth2/types" } store = { path = "./store" } client = { path = "client" } diff --git a/beacon_node/beacon_chain/Cargo.toml b/beacon_node/beacon_chain/Cargo.toml index 31f3412865..018ea19766 100644 --- a/beacon_node/beacon_chain/Cargo.toml +++ b/beacon_node/beacon_chain/Cargo.toml @@ -11,9 +11,11 @@ lazy_static = "1.3.0" lighthouse_metrics = { path = "../../eth2/utils/lighthouse_metrics" } log = "0.4" operation_pool = { path = "../../eth2/operation_pool" } +reqwest = "0.9" serde = "1.0" serde_derive = "1.0" serde_yaml = "0.8" +eth2-libp2p = { path = "../eth2-libp2p" } slog = { version = "^2.2.3" , features = ["max_level_trace"] } sloggers = { version = "^0.3" } slot_clock = { path = "../../eth2/utils/slot_clock" } @@ -22,6 +24,7 @@ eth2_ssz_derive = "0.1" state_processing = { path = "../../eth2/state_processing" } tree_hash = "0.1" types = { path = "../../eth2/types" } +url = "1.2" lmd_ghost = { path = "../../eth2/lmd_ghost" } [dev-dependencies] diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index 5feefd8417..d79d8c3589 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -114,7 +114,6 @@ impl BeaconChain { /// Instantiate a new Beacon Chain, from genesis. pub fn from_genesis( store: Arc, - slot_clock: T::SlotClock, mut genesis_state: BeaconState, mut genesis_block: BeaconBlock, spec: ChainSpec, @@ -147,6 +146,13 @@ impl BeaconChain { "genesis_block_root" => format!("{}", genesis_block_root), ); + // Slot clock + let slot_clock = T::SlotClock::new( + spec.genesis_slot, + genesis_state.genesis_time, + spec.seconds_per_slot, + ); + Ok(Self { spec, slot_clock, diff --git a/beacon_node/beacon_chain/src/beacon_chain_builder.rs b/beacon_node/beacon_chain/src/beacon_chain_builder.rs index a6c77cb63c..79c74b0068 100644 --- a/beacon_node/beacon_chain/src/beacon_chain_builder.rs +++ b/beacon_node/beacon_chain/src/beacon_chain_builder.rs @@ -1,49 +1,115 @@ -use crate::BeaconChainTypes; +use super::bootstrapper::Bootstrapper; +use crate::{BeaconChain, BeaconChainTypes}; +use slog::Logger; use std::fs::File; use std::path::PathBuf; +use std::sync::Arc; use std::time::SystemTime; -use types::{ - test_utils::TestingBeaconStateBuilder, BeaconBlock, BeaconState, ChainSpec, EthSpec, Hash256, -}; +use types::{test_utils::TestingBeaconStateBuilder, BeaconBlock, BeaconState, ChainSpec, EthSpec}; + +enum BuildStrategy { + FromGenesis { + genesis_state: Box>, + genesis_block: Box>, + }, + LoadFromStore, +} pub struct BeaconChainBuilder { - genesis_state: BeaconState, - genesis_block: BeaconBlock, + build_strategy: BuildStrategy, spec: ChainSpec, + log: Logger, } impl BeaconChainBuilder { - pub fn recent_genesis(validator_count: usize, spec: ChainSpec) -> Self { - Self::quick_start(recent_genesis_time(), validator_count, spec) + pub fn recent_genesis(validator_count: usize, spec: ChainSpec, log: Logger) -> Self { + Self::quick_start(recent_genesis_time(), validator_count, spec, log) } - pub fn quick_start(genesis_time: u64, validator_count: usize, spec: ChainSpec) -> Self { + pub fn quick_start( + genesis_time: u64, + validator_count: usize, + spec: ChainSpec, + log: Logger, + ) -> Self { let (mut genesis_state, _keypairs) = TestingBeaconStateBuilder::from_default_keypairs_file_if_exists(validator_count, &spec) .build(); genesis_state.genesis_time = genesis_time; - Self::from_genesis_state(genesis_state, spec) + Self::from_genesis_state(genesis_state, spec, log) } - pub fn yaml_state(file: PathBuf, spec: ChainSpec) -> Result { + pub fn yaml_state(file: &PathBuf, spec: ChainSpec, log: Logger) -> Result { let file = File::open(file.clone()) .map_err(|e| format!("Unable to open YAML genesis state file {:?}: {:?}", file, e))?; let genesis_state = serde_yaml::from_reader(file) .map_err(|e| format!("Unable to parse YAML genesis state file: {:?}", e))?; - Ok(Self::from_genesis_state(genesis_state, spec)) + Ok(Self::from_genesis_state(genesis_state, spec, log)) } - pub fn from_genesis_state(genesis_state: BeaconState, spec: ChainSpec) -> Self { - Self { - genesis_block: genesis_block(&genesis_state, &spec), - genesis_state, + pub fn http_bootstrap(server: &str, spec: ChainSpec, log: Logger) -> Result { + let bootstrapper = Bootstrapper::from_server_string(server.to_string()) + .map_err(|e| format!("Failed to initialize bootstrap client: {}", e))?; + + let (genesis_state, genesis_block) = bootstrapper + .genesis() + .map_err(|e| format!("Failed to bootstrap genesis state: {}", e))?; + + Ok(Self { + build_strategy: BuildStrategy::FromGenesis { + genesis_block: Box::new(genesis_block), + genesis_state: Box::new(genesis_state), + }, spec, + log, + }) + } + + fn from_genesis_state( + genesis_state: BeaconState, + spec: ChainSpec, + log: Logger, + ) -> Self { + Self { + build_strategy: BuildStrategy::FromGenesis { + genesis_block: Box::new(genesis_block(&genesis_state, &spec)), + genesis_state: Box::new(genesis_state), + }, + spec, + log, } } + + pub fn from_store(spec: ChainSpec, log: Logger) -> Self { + Self { + build_strategy: BuildStrategy::LoadFromStore, + spec, + log, + } + } + + pub fn build(self, store: Arc) -> Result, String> { + Ok(match self.build_strategy { + BuildStrategy::LoadFromStore => BeaconChain::from_store(store, self.spec, self.log) + .map_err(|e| format!("Error loading BeaconChain from database: {:?}", e))? + .ok_or_else(|| format!("Unable to find exising BeaconChain in database."))?, + BuildStrategy::FromGenesis { + genesis_block, + genesis_state, + } => BeaconChain::from_genesis( + store, + genesis_state.as_ref().clone(), + genesis_block.as_ref().clone(), + self.spec, + self.log, + ) + .map_err(|e| format!("Failed to initialize new beacon chain: {:?}", e))?, + }) + } } fn genesis_block(genesis_state: &BeaconState, spec: &ChainSpec) -> BeaconBlock { diff --git a/beacon_node/client/src/bootstrapper.rs b/beacon_node/beacon_chain/src/bootstrapper.rs similarity index 100% rename from beacon_node/client/src/bootstrapper.rs rename to beacon_node/beacon_chain/src/bootstrapper.rs diff --git a/beacon_node/beacon_chain/src/lib.rs b/beacon_node/beacon_chain/src/lib.rs index 9c833f778d..560da65197 100644 --- a/beacon_node/beacon_chain/src/lib.rs +++ b/beacon_node/beacon_chain/src/lib.rs @@ -4,6 +4,7 @@ extern crate lazy_static; mod beacon_chain; mod beacon_chain_builder; +mod bootstrapper; mod checkpoint; mod errors; mod fork_choice; @@ -18,6 +19,7 @@ pub use self::beacon_chain::{ pub use self::checkpoint::CheckPoint; pub use self::errors::{BeaconChainError, BlockProductionError}; pub use beacon_chain_builder::BeaconChainBuilder; +pub use bootstrapper::Bootstrapper; pub use lmd_ghost; pub use metrics::scrape_for_metrics; pub use parking_lot; diff --git a/beacon_node/beacon_chain/src/test_utils.rs b/beacon_node/beacon_chain/src/test_utils.rs index 09f4749ea3..29696b771a 100644 --- a/beacon_node/beacon_chain/src/test_utils.rs +++ b/beacon_node/beacon_chain/src/test_utils.rs @@ -1,7 +1,6 @@ use crate::{BeaconChain, BeaconChainTypes, BlockProcessingOutcome}; use lmd_ghost::LmdGhost; use sloggers::{null::NullLoggerBuilder, Build}; -use slot_clock::SlotClock; use slot_clock::TestingSlotClock; use state_processing::per_slot_processing; use std::marker::PhantomData; @@ -114,22 +113,9 @@ where let builder = NullLoggerBuilder; let log = builder.build().expect("logger should build"); - // Slot clock - let slot_clock = TestingSlotClock::new( - spec.genesis_slot, - genesis_state.genesis_time, - spec.seconds_per_slot, - ); - - let chain = BeaconChain::from_genesis( - store, - slot_clock, - genesis_state, - genesis_block, - spec.clone(), - log, - ) - .expect("Terminate if beacon chain generation fails"); + let chain = + BeaconChain::from_genesis(store, genesis_state, genesis_block, spec.clone(), log) + .expect("Terminate if beacon chain generation fails"); Self { chain, diff --git a/beacon_node/client/Cargo.toml b/beacon_node/client/Cargo.toml index 9b5a9cf42c..05c58cc8ba 100644 --- a/beacon_node/client/Cargo.toml +++ b/beacon_node/client/Cargo.toml @@ -27,5 +27,3 @@ clap = "2.32.0" dirs = "1.0.3" exit-future = "0.1.3" futures = "0.1.25" -reqwest = "0.9" -url = "1.2" diff --git a/beacon_node/client/src/beacon_chain_types.rs b/beacon_node/client/src/beacon_chain_types.rs deleted file mode 100644 index 7a57aa4757..0000000000 --- a/beacon_node/client/src/beacon_chain_types.rs +++ /dev/null @@ -1,170 +0,0 @@ -use crate::bootstrapper::Bootstrapper; -use crate::error::Result; -use crate::{config::BeaconChainStartMethod, ClientConfig}; -use beacon_chain::{ - lmd_ghost::{LmdGhost, ThreadSafeReducedTree}, - slot_clock::SystemTimeSlotClock, - store::Store, - BeaconChain, BeaconChainTypes, -}; -use slog::{crit, info, Logger}; -use slot_clock::SlotClock; -use std::fs::File; -use std::marker::PhantomData; -use std::sync::Arc; -use std::time::SystemTime; -use tree_hash::TreeHash; -use types::{ - test_utils::TestingBeaconStateBuilder, BeaconBlock, BeaconState, ChainSpec, EthSpec, Hash256, -}; - -/// Provides a new, initialized `BeaconChain` -pub trait InitialiseBeaconChain { - fn initialise_beacon_chain( - store: Arc, - config: &ClientConfig, - spec: ChainSpec, - log: Logger, - ) -> Result> { - maybe_load_from_store_for_testnet::<_, T::Store, T::EthSpec>(store, config, spec, log) - } -} - -#[derive(Clone)] -pub struct ClientType { - _phantom_t: PhantomData, - _phantom_u: PhantomData, -} - -impl BeaconChainTypes for ClientType -where - S: Store + 'static, - E: EthSpec, -{ - type Store = S; - type SlotClock = SystemTimeSlotClock; - type LmdGhost = ThreadSafeReducedTree; - type EthSpec = E; -} -impl InitialiseBeaconChain for ClientType {} - -/// Loads a `BeaconChain` from `store`, if it exists. Otherwise, create a new chain from genesis. -fn maybe_load_from_store_for_testnet( - store: Arc, - config: &ClientConfig, - spec: ChainSpec, - log: Logger, -) -> Result> -where - T: BeaconChainTypes, - T::LmdGhost: LmdGhost, -{ - let genesis_state = match &config.beacon_chain_start_method { - BeaconChainStartMethod::Resume => unimplemented!("No resume code yet"), - BeaconChainStartMethod::Mainnet => { - crit!(log, "No mainnet beacon chain startup specification."); - return Err("Mainnet is not yet specified. We're working on it.".into()); - } - BeaconChainStartMethod::RecentGenesis { validator_count } => { - generate_testnet_genesis_state(*validator_count, recent_genesis_time(), &spec) - } - BeaconChainStartMethod::Generated { - validator_count, - genesis_time, - } => generate_testnet_genesis_state(*validator_count, *genesis_time, &spec), - BeaconChainStartMethod::Yaml { file } => { - let file = File::open(file).map_err(|e| { - format!("Unable to open YAML genesis state file {:?}: {:?}", file, e) - })?; - - serde_yaml::from_reader(file) - .map_err(|e| format!("Unable to parse YAML genesis state file: {:?}", e))? - } - BeaconChainStartMethod::HttpBootstrap { server, .. } => { - let bootstrapper = Bootstrapper::from_server_string(server.to_string()) - .map_err(|e| format!("Failed to initialize bootstrap client: {}", e))?; - - let (state, _block) = bootstrapper - .genesis() - .map_err(|e| format!("Failed to bootstrap genesis state: {}", e))?; - - state - } - }; - - let mut genesis_block = BeaconBlock::empty(&spec); - genesis_block.state_root = Hash256::from_slice(&genesis_state.tree_hash_root()); - let genesis_block_root = genesis_block.canonical_root(); - - // Slot clock - let slot_clock = T::SlotClock::new( - spec.genesis_slot, - genesis_state.genesis_time, - spec.seconds_per_slot, - ); - - // Try load an existing `BeaconChain` from the store. If unable, create a new one. - if let Ok(Some(beacon_chain)) = - BeaconChain::from_store(store.clone(), spec.clone(), log.clone()) - { - // Here we check to ensure that the `BeaconChain` loaded from store has the expected - // genesis block. - // - // Without this check, it's possible that there will be an existing DB with a `BeaconChain` - // that has different parameters than provided to this executable. - if beacon_chain.genesis_block_root == genesis_block_root { - info!( - log, - "Loaded BeaconChain from store"; - "slot" => beacon_chain.head().beacon_state.slot, - "best_slot" => beacon_chain.best_slot(), - ); - - Ok(beacon_chain) - } else { - crit!( - log, - "The BeaconChain loaded from disk has an incorrect genesis root. \ - This may be caused by an old database in located in datadir." - ); - Err("Incorrect genesis root".into()) - } - } else { - BeaconChain::from_genesis( - store, - slot_clock, - genesis_state, - genesis_block, - spec, - log.clone(), - ) - .map_err(|e| format!("Failed to initialize new beacon chain: {:?}", e).into()) - } -} - -fn generate_testnet_genesis_state( - validator_count: usize, - genesis_time: u64, - spec: &ChainSpec, -) -> BeaconState { - let (mut genesis_state, _keypairs) = - TestingBeaconStateBuilder::from_default_keypairs_file_if_exists(validator_count, spec) - .build(); - - genesis_state.genesis_time = genesis_time; - - genesis_state -} - -/// Returns the system time, mod 30 minutes. -/// -/// Used for easily creating testnets. -fn recent_genesis_time() -> u64 { - let now = SystemTime::now() - .duration_since(SystemTime::UNIX_EPOCH) - .unwrap() - .as_secs(); - let secs_after_last_period = now.checked_rem(30 * 60).unwrap_or(0); - // genesis is now the last 30 minute block. - now - secs_after_last_period -} diff --git a/beacon_node/client/src/lib.rs b/beacon_node/client/src/lib.rs index 9d3e001faf..e2baf22d5f 100644 --- a/beacon_node/client/src/lib.rs +++ b/beacon_node/client/src/lib.rs @@ -1,31 +1,47 @@ extern crate slog; -mod beacon_chain_types; -mod bootstrapper; mod config; pub mod error; pub mod notifier; -use beacon_chain::BeaconChain; +use beacon_chain::{ + lmd_ghost::ThreadSafeReducedTree, slot_clock::SystemTimeSlotClock, store::Store, BeaconChain, + BeaconChainBuilder, +}; use exit_future::Signal; use futures::{future::Future, Stream}; use network::Service as NetworkService; -use slog::{error, info, o}; +use slog::{crit, error, info, o}; use slot_clock::SlotClock; use std::marker::PhantomData; use std::sync::Arc; use std::time::{Duration, Instant}; use tokio::runtime::TaskExecutor; use tokio::timer::Interval; +use types::EthSpec; pub use beacon_chain::BeaconChainTypes; -pub use beacon_chain_types::ClientType; -pub use beacon_chain_types::InitialiseBeaconChain; -pub use bootstrapper::Bootstrapper; pub use config::{BeaconChainStartMethod, Config as ClientConfig}; pub use eth2_config::Eth2Config; +#[derive(Clone)] +pub struct ClientType { + _phantom_t: PhantomData, + _phantom_u: PhantomData, +} + +impl BeaconChainTypes for ClientType +where + S: Store + 'static, + E: EthSpec, +{ + type Store = S; + type SlotClock = SystemTimeSlotClock; + type LmdGhost = ThreadSafeReducedTree; + type EthSpec = E; +} + /// Main beacon node client service. This provides the connection and initialisation of the clients /// sub-services in multiple threads. pub struct Client { @@ -49,7 +65,7 @@ pub struct Client { impl Client where - T: BeaconChainTypes + InitialiseBeaconChain + Clone, + T: BeaconChainTypes + Clone, { /// Generate an instance of the client. Spawn and link all internal sub-processes. pub fn new( @@ -62,13 +78,41 @@ where let store = Arc::new(store); let seconds_per_slot = eth2_config.spec.seconds_per_slot; - // Load a `BeaconChain` from the store, or create a new one if it does not exist. - let beacon_chain = Arc::new(T::initialise_beacon_chain( - store, - &client_config, - eth2_config.spec.clone(), - log.clone(), - )?); + let spec = ð2_config.spec.clone(); + + let beacon_chain_builder = match &client_config.beacon_chain_start_method { + BeaconChainStartMethod::Resume => { + BeaconChainBuilder::from_store(spec.clone(), log.clone()) + } + BeaconChainStartMethod::Mainnet => { + crit!(log, "No mainnet beacon chain startup specification."); + return Err("Mainnet is not yet specified. We're working on it.".into()); + } + BeaconChainStartMethod::RecentGenesis { validator_count } => { + BeaconChainBuilder::recent_genesis(*validator_count, spec.clone(), log.clone()) + } + BeaconChainStartMethod::Generated { + validator_count, + genesis_time, + } => BeaconChainBuilder::quick_start( + *genesis_time, + *validator_count, + spec.clone(), + log.clone(), + ), + BeaconChainStartMethod::Yaml { file } => { + BeaconChainBuilder::yaml_state(file, spec.clone(), log.clone())? + } + BeaconChainStartMethod::HttpBootstrap { server, .. } => { + BeaconChainBuilder::http_bootstrap(server, spec.clone(), log.clone())? + } + }; + + let beacon_chain: Arc> = Arc::new( + beacon_chain_builder + .build(store) + .map_err(error::Error::from)?, + ); if beacon_chain.read_slot_clock().is_none() { panic!("Cannot start client before genesis!") diff --git a/beacon_node/src/config.rs b/beacon_node/src/config.rs index 68d905ed22..9fac9b49a4 100644 --- a/beacon_node/src/config.rs +++ b/beacon_node/src/config.rs @@ -1,5 +1,6 @@ +use beacon_chain::Bootstrapper; use clap::ArgMatches; -use client::{BeaconChainStartMethod, Bootstrapper, ClientConfig, Eth2Config}; +use client::{BeaconChainStartMethod, ClientConfig, Eth2Config}; use eth2_config::{read_from_file, write_to_file}; use rand::{distributions::Alphanumeric, Rng}; use slog::{crit, info, warn, Logger}; diff --git a/beacon_node/src/run.rs b/beacon_node/src/run.rs index e23b5bc72d..620cb64bb5 100644 --- a/beacon_node/src/run.rs +++ b/beacon_node/src/run.rs @@ -1,7 +1,4 @@ -use client::{ - error, notifier, BeaconChainTypes, Client, ClientConfig, ClientType, Eth2Config, - InitialiseBeaconChain, -}; +use client::{error, notifier, BeaconChainTypes, Client, ClientConfig, ClientType, Eth2Config}; use futures::sync::oneshot; use futures::Future; use slog::{error, info}; @@ -117,7 +114,7 @@ fn run( log: &slog::Logger, ) -> error::Result<()> where - T: BeaconChainTypes + InitialiseBeaconChain + Clone, + T: BeaconChainTypes + Clone, T::Store: OpenDatabase, { let store = T::Store::open_database(&db_path)?; From b58aa1d1481b4b7104032c48e30a5de99aed7a20 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Mon, 26 Aug 2019 15:47:03 +1000 Subject: [PATCH 57/62] Add custom config options to testnet sub-cmd --- beacon_node/src/config.rs | 61 ++++++++++++++++++++++++++++----------- beacon_node/src/main.rs | 20 +++++++++++-- 2 files changed, 62 insertions(+), 19 deletions(-) diff --git a/beacon_node/src/config.rs b/beacon_node/src/config.rs index 9fac9b49a4..c8a9299a58 100644 --- a/beacon_node/src/config.rs +++ b/beacon_node/src/config.rs @@ -67,14 +67,28 @@ fn process_testnet_subcommand( builder.clean_datadir()?; } + if let Some(path_string) = cli_args.value_of("eth2-config") { + let path = path_string + .parse::() + .map_err(|e| format!("Unable to parse eth2-config path: {:?}", e))?; + builder.load_eth2_config(path)?; + } else { + builder.update_spec_from_subcommand(&cli_args)?; + } + + if let Some(path_string) = cli_args.value_of("config") { + let path = path_string + .parse::() + .map_err(|e| format!("Unable to parse config path: {:?}", e))?; + builder.load_client_config(path)?; + } + info!( log, "Creating new datadir"; "path" => format!("{:?}", builder.data_dir) ); - builder.update_spec_from_subcommand(&cli_args)?; - // Start matching on the second subcommand (e.g., `testnet bootstrap ...`) match cli_args.subcommand() { ("bootstrap", Some(cli_args)) => { @@ -82,7 +96,7 @@ fn process_testnet_subcommand( .value_of("server") .ok_or_else(|| "No bootstrap server specified")?; let port: Option = cli_args - .value_of("port") + .value_of("libp2p-port") .and_then(|s| s.parse::().ok()); builder.import_bootstrap_libp2p_address(server, port)?; @@ -306,11 +320,8 @@ impl<'a> ConfigBuilder<'a> { )); } else { // Write the config to a TOML file in the datadir. - write_to_file( - self.data_dir.join(ETH2_CONFIG_FILENAME), - &self.client_config, - ) - .map_err(|e| format!("Unable to write {} file: {:?}", ETH2_CONFIG_FILENAME, e))?; + write_to_file(self.data_dir.join(ETH2_CONFIG_FILENAME), &self.eth2_config) + .map_err(|e| format!("Unable to write {} file: {:?}", ETH2_CONFIG_FILENAME, e))?; } Ok(()) @@ -339,20 +350,36 @@ impl<'a> ConfigBuilder<'a> { .unwrap_or_else(|| true) { return Err( - "No database found in datadir. Use the 'testnet -f' sub-command to overwrite the \ - existing datadir, or specify a different `--datadir`." + "No database found in datadir. Use 'testnet -f' to overwrite the existing \ + datadir, or specify a different `--datadir`." .into(), ); } - self.eth2_config = read_from_file::(self.data_dir.join(ETH2_CONFIG_FILENAME)) - .map_err(|e| format!("Unable to parse {} file: {:?}", ETH2_CONFIG_FILENAME, e))? - .ok_or_else(|| format!("{} file does not exist", ETH2_CONFIG_FILENAME))?; + self.load_eth2_config(self.data_dir.join(ETH2_CONFIG_FILENAME))?; + self.load_client_config(self.data_dir.join(CLIENT_CONFIG_FILENAME))?; - self.client_config = - read_from_file::(self.data_dir.join(CLIENT_CONFIG_FILENAME)) - .map_err(|e| format!("Unable to parse {} file: {:?}", CLIENT_CONFIG_FILENAME, e))? - .ok_or_else(|| format!("{} file does not exist", ETH2_CONFIG_FILENAME))?; + Ok(()) + } + + /// Attempts to load the client config from `path`. + /// + /// Returns an error if any files are not found or are invalid. + pub fn load_client_config(&mut self, path: PathBuf) -> Result<()> { + self.client_config = read_from_file::(path) + .map_err(|e| format!("Unable to parse ClientConfig file: {:?}", e))? + .ok_or_else(|| "ClientConfig file does not exist".to_string())?; + + Ok(()) + } + + /// Attempts to load the eth2 config from `path`. + /// + /// Returns an error if any files are not found or are invalid. + pub fn load_eth2_config(&mut self, path: PathBuf) -> Result<()> { + self.eth2_config = read_from_file::(path) + .map_err(|e| format!("Unable to parse Eth2Config file: {:?}", e))? + .ok_or_else(|| "Eth2Config file does not exist".to_string())?; Ok(()) } diff --git a/beacon_node/src/main.rs b/beacon_node/src/main.rs index 4430db1287..a9659362ca 100644 --- a/beacon_node/src/main.rs +++ b/beacon_node/src/main.rs @@ -198,6 +198,22 @@ fn main() { .takes_value(true) .required(true) .possible_values(&["mainnet", "minimal", "interop"]) + .default_value("minimal") + ) + .arg( + Arg::with_name("eth2-config") + .long("eth2-config") + .value_name("TOML_FILE") + .help("A existing eth2_spec TOML file (e.g., eth2_spec.toml).") + .takes_value(true) + .conflicts_with("spec") + ) + .arg( + Arg::with_name("config") + .long("config") + .value_name("TOML_FILE") + .help("An existing beacon_node TOML file (e.g., beacon_node.toml).") + .takes_value(true) ) .arg( Arg::with_name("random-datadir") @@ -210,8 +226,8 @@ fn main() { Arg::with_name("force") .long("force") .short("f") - .help("If present, will backup any existing config files before creating new ones. Cannot be \ - used when specifying --random-datadir (logic error).") + .help("If present, will create new config and database files and move the any existing to a \ + backup directory.") .conflicts_with("random-datadir") ) /* From bab1f2b06423445e4aa72958bb293c0f65afb190 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Mon, 26 Aug 2019 15:51:11 +1000 Subject: [PATCH 58/62] Rename CLI flag --- beacon_node/src/config.rs | 4 ++-- beacon_node/src/main.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/beacon_node/src/config.rs b/beacon_node/src/config.rs index c8a9299a58..0aa2d29bd3 100644 --- a/beacon_node/src/config.rs +++ b/beacon_node/src/config.rs @@ -76,10 +76,10 @@ fn process_testnet_subcommand( builder.update_spec_from_subcommand(&cli_args)?; } - if let Some(path_string) = cli_args.value_of("config") { + if let Some(path_string) = cli_args.value_of("client-config") { let path = path_string .parse::() - .map_err(|e| format!("Unable to parse config path: {:?}", e))?; + .map_err(|e| format!("Unable to parse client config path: {:?}", e))?; builder.load_client_config(path)?; } diff --git a/beacon_node/src/main.rs b/beacon_node/src/main.rs index a9659362ca..243e4b7160 100644 --- a/beacon_node/src/main.rs +++ b/beacon_node/src/main.rs @@ -209,8 +209,8 @@ fn main() { .conflicts_with("spec") ) .arg( - Arg::with_name("config") - .long("config") + Arg::with_name("client-config") + .long("client-config") .value_name("TOML_FILE") .help("An existing beacon_node TOML file (e.g., beacon_node.toml).") .takes_value(true) From 39be2ed1d24f53b5494e53a89cf00f6b1023dd0f Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Mon, 26 Aug 2019 15:57:40 +1000 Subject: [PATCH 59/62] Improve CLI error messages --- beacon_node/src/config.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/beacon_node/src/config.rs b/beacon_node/src/config.rs index 0aa2d29bd3..2c928ad449 100644 --- a/beacon_node/src/config.rs +++ b/beacon_node/src/config.rs @@ -366,9 +366,9 @@ impl<'a> ConfigBuilder<'a> { /// /// Returns an error if any files are not found or are invalid. pub fn load_client_config(&mut self, path: PathBuf) -> Result<()> { - self.client_config = read_from_file::(path) - .map_err(|e| format!("Unable to parse ClientConfig file: {:?}", e))? - .ok_or_else(|| "ClientConfig file does not exist".to_string())?; + self.client_config = read_from_file::(path.clone()) + .map_err(|e| format!("Unable to parse {:?} file: {:?}", path, e))? + .ok_or_else(|| format!("{:?} file does not exist", path))?; Ok(()) } @@ -377,9 +377,9 @@ impl<'a> ConfigBuilder<'a> { /// /// Returns an error if any files are not found or are invalid. pub fn load_eth2_config(&mut self, path: PathBuf) -> Result<()> { - self.eth2_config = read_from_file::(path) - .map_err(|e| format!("Unable to parse Eth2Config file: {:?}", e))? - .ok_or_else(|| "Eth2Config file does not exist".to_string())?; + self.eth2_config = read_from_file::(path.clone()) + .map_err(|e| format!("Unable to parse {:?} file: {:?}", path, e))? + .ok_or_else(|| format!("{:?} file does not exist", path))?; Ok(()) } From 901393b6642e5f01971d04fa79cd7ccfb4dac9ef Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Mon, 26 Aug 2019 16:02:05 +1000 Subject: [PATCH 60/62] Clean datadir after config files have been loaded --- beacon_node/src/config.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/beacon_node/src/config.rs b/beacon_node/src/config.rs index 2c928ad449..f47a2ddb06 100644 --- a/beacon_node/src/config.rs +++ b/beacon_node/src/config.rs @@ -63,10 +63,6 @@ fn process_testnet_subcommand( builder.set_random_datadir()?; } - if cli_args.is_present("force") { - builder.clean_datadir()?; - } - if let Some(path_string) = cli_args.value_of("eth2-config") { let path = path_string .parse::() @@ -83,6 +79,10 @@ fn process_testnet_subcommand( builder.load_client_config(path)?; } + if cli_args.is_present("force") { + builder.clean_datadir()?; + } + info!( log, "Creating new datadir"; From 6875ae8af510ea2fe4bc86671f28770344368def Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Tue, 27 Aug 2019 00:04:15 +1000 Subject: [PATCH 61/62] Pull Eth2Config during bootstrap --- beacon_node/beacon_chain/Cargo.toml | 1 + beacon_node/beacon_chain/src/bootstrapper.rs | 19 +++++++++++++++++++ beacon_node/client/src/lib.rs | 1 + beacon_node/rest_api/Cargo.toml | 1 + beacon_node/rest_api/src/lib.rs | 7 +++++++ beacon_node/rest_api/src/spec.rs | 14 ++++++++++++++ beacon_node/src/config.rs | 20 ++++++++++++++++++++ 7 files changed, 63 insertions(+) diff --git a/beacon_node/beacon_chain/Cargo.toml b/beacon_node/beacon_chain/Cargo.toml index 018ea19766..f6763d1671 100644 --- a/beacon_node/beacon_chain/Cargo.toml +++ b/beacon_node/beacon_chain/Cargo.toml @@ -5,6 +5,7 @@ authors = ["Paul Hauner ", "Age Manning Result { + 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 { get_enr(self.url.clone()).map_err(|e| format!("Unable to get ENR: {:?}", e)) @@ -129,6 +135,19 @@ fn get_slots_per_epoch(mut url: Url) -> Result { .map_err(Into::into) } +fn get_eth2_config(mut url: Url) -> Result { + 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 { url.path_segments_mut() .map(|mut url| { diff --git a/beacon_node/client/src/lib.rs b/beacon_node/client/src/lib.rs index 0bb30d0aff..2612fd6489 100644 --- a/beacon_node/client/src/lib.rs +++ b/beacon_node/client/src/lib.rs @@ -162,6 +162,7 @@ where beacon_chain.clone(), network.clone(), client_config.db_path().expect("unable to read datadir"), + eth2_config.clone(), &log, ) { Ok(s) => Some(s), diff --git a/beacon_node/rest_api/Cargo.toml b/beacon_node/rest_api/Cargo.toml index cac196d9cb..5303dc8bdc 100644 --- a/beacon_node/rest_api/Cargo.toml +++ b/beacon_node/rest_api/Cargo.toml @@ -27,5 +27,6 @@ exit-future = "0.1.3" tokio = "0.1.17" url = "2.0" lazy_static = "1.3.0" +eth2_config = { path = "../../eth2/utils/eth2_config" } lighthouse_metrics = { path = "../../eth2/utils/lighthouse_metrics" } slot_clock = { path = "../../eth2/utils/slot_clock" } diff --git a/beacon_node/rest_api/src/lib.rs b/beacon_node/rest_api/src/lib.rs index 964dd79982..b1137c2493 100644 --- a/beacon_node/rest_api/src/lib.rs +++ b/beacon_node/rest_api/src/lib.rs @@ -13,6 +13,7 @@ mod url_query; use beacon_chain::{BeaconChain, BeaconChainTypes}; use client_network::Service as NetworkService; +use eth2_config::Eth2Config; use hyper::rt::Future; use hyper::service::service_fn_ok; use hyper::{Body, Method, Response, Server, StatusCode}; @@ -79,6 +80,7 @@ pub fn start_server( beacon_chain: Arc>, network_service: Arc>, db_path: PathBuf, + eth2_config: Eth2Config, log: &slog::Logger, ) -> Result { let log = log.new(o!("Service" => "Api")); @@ -100,12 +102,14 @@ pub fn start_server( // Clone our stateful objects, for use in service closure. let server_log = log.clone(); let server_bc = beacon_chain.clone(); + let eth2_config = Arc::new(eth2_config); let service = move || { let log = server_log.clone(); let beacon_chain = server_bc.clone(); let db_path = db_path.clone(); let network_service = network_service.clone(); + let eth2_config = eth2_config.clone(); // Create a simple handler for the router, inject our stateful objects into the request. service_fn_ok(move |mut req| { @@ -118,6 +122,8 @@ pub fn start_server( req.extensions_mut().insert::(db_path.clone()); req.extensions_mut() .insert::>>(network_service.clone()); + req.extensions_mut() + .insert::>(eth2_config.clone()); let path = req.uri().path().to_string(); @@ -144,6 +150,7 @@ pub fn start_server( (&Method::GET, "/node/genesis_time") => node::get_genesis_time::(req), (&Method::GET, "/spec") => spec::get_spec::(req), (&Method::GET, "/spec/slots_per_epoch") => spec::get_slots_per_epoch::(req), + (&Method::GET, "/spec/eth2_config") => spec::get_eth2_config::(req), _ => Err(ApiError::MethodNotAllowed(path.clone())), }; diff --git a/beacon_node/rest_api/src/spec.rs b/beacon_node/rest_api/src/spec.rs index d0c8e4368d..86d1c227d3 100644 --- a/beacon_node/rest_api/src/spec.rs +++ b/beacon_node/rest_api/src/spec.rs @@ -1,6 +1,7 @@ use super::{success_response, ApiResult}; use crate::ApiError; use beacon_chain::{BeaconChain, BeaconChainTypes}; +use eth2_config::Eth2Config; use hyper::{Body, Request}; use std::sync::Arc; use types::EthSpec; @@ -18,6 +19,19 @@ pub fn get_spec(req: Request) -> ApiResult Ok(success_response(Body::from(json))) } +/// HTTP handler to return the full Eth2Config object. +pub fn get_eth2_config(req: Request) -> ApiResult { + let eth2_config = req + .extensions() + .get::>() + .ok_or_else(|| ApiError::ServerError("Eth2Config extension missing".to_string()))?; + + let json: String = serde_json::to_string(eth2_config.as_ref()) + .map_err(|e| ApiError::ServerError(format!("Unable to serialize Eth2Config: {:?}", e)))?; + + Ok(success_response(Body::from(json))) +} + /// HTTP handler to return the full spec object. pub fn get_slots_per_epoch(_req: Request) -> ApiResult { let json: String = serde_json::to_string(&T::EthSpec::slots_per_epoch()) diff --git a/beacon_node/src/config.rs b/beacon_node/src/config.rs index f47a2ddb06..e76bd48fa7 100644 --- a/beacon_node/src/config.rs +++ b/beacon_node/src/config.rs @@ -63,7 +63,13 @@ fn process_testnet_subcommand( builder.set_random_datadir()?; } + let is_bootstrap = cli_args.subcommand_name() == Some("bootstrap"); + if let Some(path_string) = cli_args.value_of("eth2-config") { + if is_bootstrap { + return Err("Cannot supply --eth2-config when using bootsrap".to_string()); + } + let path = path_string .parse::() .map_err(|e| format!("Unable to parse eth2-config path: {:?}", e))?; @@ -100,6 +106,7 @@ fn process_testnet_subcommand( .and_then(|s| s.parse::().ok()); builder.import_bootstrap_libp2p_address(server, port)?; + builder.import_bootstrap_eth2_config(server)?; builder.set_beacon_chain_start_method(BeaconChainStartMethod::HttpBootstrap { server: server.to_string(), @@ -252,6 +259,19 @@ impl<'a> ConfigBuilder<'a> { Ok(()) } + /// Imports an `Eth2Config` from `server`, returning an error if this fails. + pub fn import_bootstrap_eth2_config(&mut self, server: &str) -> Result<()> { + let bootstrapper = Bootstrapper::from_server_string(server.to_string())?; + + self.update_eth2_config(bootstrapper.eth2_config()?); + + Ok(()) + } + + fn update_eth2_config(&mut self, eth2_config: Eth2Config) { + self.eth2_config = eth2_config; + } + /// Reads the subcommand and tries to update `self.eth2_config` based up on the `--spec` flag. /// /// Returns an error if the `--spec` flag is not present in the given `cli_args`. From 7f6b700b983429f4c67b1592bc76b4fd2486716a Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Tue, 27 Aug 2019 00:05:25 +1000 Subject: [PATCH 62/62] Remove old git merge relic --- beacon_node/src/main.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/beacon_node/src/main.rs b/beacon_node/src/main.rs index 797217af03..aba44e6fe5 100644 --- a/beacon_node/src/main.rs +++ b/beacon_node/src/main.rs @@ -182,7 +182,6 @@ fn main() { .takes_value(true), ) /* -<<<<<<< HEAD * The "testnet" sub-command. * * Allows for creating a new datadir with testnet-specific configs.