From 5b5cf9cfaa6efb740135639b87801f03356dcf4f Mon Sep 17 00:00:00 2001 From: Pawan Dhananjay Date: Wed, 20 Jul 2022 23:16:54 +0000 Subject: [PATCH] Log ttd (#3339) ## Issue Addressed Resolves #3249 ## Proposed Changes Log merge related parameters and EE status in the beacon notifier before the merge. Co-authored-by: Paul Hauner --- beacon_node/beacon_chain/src/lib.rs | 1 + .../beacon_chain/src/merge_readiness.rs | 169 ++++++++++++++++++ beacon_node/client/src/notifier.rs | 99 +++++++++- beacon_node/execution_layer/src/lib.rs | 33 ++++ .../src/test_utils/handle_rpc.rs | 9 + 5 files changed, 305 insertions(+), 6 deletions(-) create mode 100644 beacon_node/beacon_chain/src/merge_readiness.rs diff --git a/beacon_node/beacon_chain/src/lib.rs b/beacon_node/beacon_chain/src/lib.rs index b82b690d2..b54964aa3 100644 --- a/beacon_node/beacon_chain/src/lib.rs +++ b/beacon_node/beacon_chain/src/lib.rs @@ -20,6 +20,7 @@ pub mod fork_choice_signal; pub mod fork_revert; mod head_tracker; pub mod historical_blocks; +pub mod merge_readiness; mod metrics; pub mod migrate; mod naive_aggregation_pool; diff --git a/beacon_node/beacon_chain/src/merge_readiness.rs b/beacon_node/beacon_chain/src/merge_readiness.rs new file mode 100644 index 000000000..be158ecbe --- /dev/null +++ b/beacon_node/beacon_chain/src/merge_readiness.rs @@ -0,0 +1,169 @@ +//! Provides tools for checking if a node is ready for the Bellatrix upgrade and following merge +//! transition. + +use crate::{BeaconChain, BeaconChainTypes}; +use execution_layer::Error as EngineError; +use std::fmt; +use std::fmt::Write; +use types::*; + +/// The time before the Bellatrix fork when we will start issuing warnings about preparation. +const SECONDS_IN_A_WEEK: u64 = 604800; +pub const MERGE_READINESS_PREPARATION_SECONDS: u64 = SECONDS_IN_A_WEEK; + +#[derive(Default, Debug)] +pub struct MergeConfig { + pub terminal_total_difficulty: Option, + pub terminal_block_hash: Option, + pub terminal_block_hash_epoch: Option, +} + +impl fmt::Display for MergeConfig { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.terminal_block_hash.is_none() + && self.terminal_block_hash_epoch.is_none() + && self.terminal_total_difficulty.is_none() + { + return write!( + f, + "Merge terminal difficulty parameters not configured, check your config" + ); + } + let mut display_string = String::new(); + if let Some(terminal_total_difficulty) = self.terminal_total_difficulty { + write!( + display_string, + "terminal_total_difficulty: {},", + terminal_total_difficulty + )?; + } + if let Some(terminal_block_hash) = self.terminal_block_hash { + write!( + display_string, + "terminal_block_hash: {},", + terminal_block_hash + )?; + } + if let Some(terminal_block_hash_epoch) = self.terminal_block_hash_epoch { + write!( + display_string, + "terminal_block_hash_epoch: {},", + terminal_block_hash_epoch + )?; + } + write!(f, "{}", display_string.trim_end_matches(','))?; + Ok(()) + } +} +impl MergeConfig { + /// Instantiate `self` from the values in a `ChainSpec`. + pub fn from_chainspec(spec: &ChainSpec) -> Self { + let mut params = MergeConfig::default(); + if spec.terminal_total_difficulty != Uint256::max_value() { + params.terminal_total_difficulty = Some(spec.terminal_total_difficulty); + } + if spec.terminal_block_hash != ExecutionBlockHash::zero() { + params.terminal_block_hash = Some(spec.terminal_block_hash); + } + if spec.terminal_block_hash_activation_epoch != Epoch::max_value() { + params.terminal_block_hash_epoch = Some(spec.terminal_block_hash_activation_epoch); + } + params + } +} + +/// Indicates if a node is ready for the Bellatrix upgrade and subsequent merge transition. +pub enum MergeReadiness { + /// The node is ready, as far as we can tell. + Ready { + config: MergeConfig, + current_difficulty: Result, + }, + /// The transition configuration with the EL failed, there might be a problem with + /// connectivity, authentication or a difference in configuration. + ExchangeTransitionConfigurationFailed(EngineError), + /// The EL can be reached and has the correct configuration, however it's not yet synced. + NotSynced, + /// The user has not configured this node to use an execution endpoint. + NoExecutionEndpoint, +} + +impl fmt::Display for MergeReadiness { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + MergeReadiness::Ready { + config: params, + current_difficulty, + } => { + write!( + f, + "This node appears ready for the merge. \ + Params: {}, current_difficulty: {:?}", + params, current_difficulty + ) + } + MergeReadiness::ExchangeTransitionConfigurationFailed(e) => write!( + f, + "Could not confirm the transition configuration with the \ + execution endpoint: {:?}", + e + ), + MergeReadiness::NotSynced => write!( + f, + "The execution endpoint is connected and configured, \ + however it is not yet synced" + ), + MergeReadiness::NoExecutionEndpoint => write!( + f, + "The --execution-endpoint flag is not specified, this is a \ + requirement for the merge" + ), + } + } +} + +impl BeaconChain { + /// Returns `true` if the Bellatrix fork has occurred or will occur within + /// `MERGE_READINESS_PREPARATION_SECONDS`. + pub fn is_time_to_prepare_for_bellatrix(&self, current_slot: Slot) -> bool { + if let Some(bellatrix_epoch) = self.spec.bellatrix_fork_epoch { + let bellatrix_slot = bellatrix_epoch.start_slot(T::EthSpec::slots_per_epoch()); + let merge_readiness_preparation_slots = + MERGE_READINESS_PREPARATION_SECONDS / self.spec.seconds_per_slot; + + // Return `true` if Bellatrix has happened or is within the preparation time. + current_slot + merge_readiness_preparation_slots > bellatrix_slot + } else { + // The Bellatrix fork epoch has not been defined yet, no need to prepare. + false + } + } + + /// Attempts to connect to the EL and confirm that it is ready for the merge. + pub async fn check_merge_readiness(&self) -> MergeReadiness { + if let Some(el) = self.execution_layer.as_ref() { + if let Err(e) = el.exchange_transition_configuration(&self.spec).await { + // The EL was either unreachable, responded with an error or has a different + // configuration. + return MergeReadiness::ExchangeTransitionConfigurationFailed(e); + } + + if !el.is_synced_for_notifier().await { + // The EL is not synced. + return MergeReadiness::NotSynced; + } + let params = MergeConfig::from_chainspec(&self.spec); + let current_difficulty = el + .get_current_difficulty() + .await + .map_err(|_| "Failed to get current difficulty from execution node".to_string()); + MergeReadiness::Ready { + config: params, + current_difficulty, + } + } else { + // There is no EL configured. + MergeReadiness::NoExecutionEndpoint + } + } +} diff --git a/beacon_node/client/src/notifier.rs b/beacon_node/client/src/notifier.rs index 9476819a4..53478971a 100644 --- a/beacon_node/client/src/notifier.rs +++ b/beacon_node/client/src/notifier.rs @@ -1,13 +1,16 @@ use crate::metrics; -use beacon_chain::{BeaconChain, BeaconChainTypes, ExecutionStatus}; +use beacon_chain::{ + merge_readiness::{MergeConfig, MergeReadiness}, + BeaconChain, BeaconChainTypes, ExecutionStatus, +}; use lighthouse_network::{types::SyncState, NetworkGlobals}; -use parking_lot::Mutex; use slog::{crit, debug, error, info, warn, Logger}; use slot_clock::SlotClock; use std::sync::Arc; use std::time::{Duration, Instant}; +use tokio::sync::Mutex; use tokio::time::sleep; -use types::{EthSpec, Slot}; +use types::*; /// Create a warning log whenever the peer count is at or below this value. pub const WARN_PEER_COUNT: usize = 1; @@ -77,6 +80,7 @@ pub fn spawn_notifier( // Perform post-genesis logging. let mut last_backfill_log_slot = None; + loop { interval.tick().await; let connected_peer_count = network.connected_peers(); @@ -87,12 +91,12 @@ pub fn spawn_notifier( match (current_sync_state, &sync_state) { (_, SyncState::BackFillSyncing { .. }) => { // We have transitioned to a backfill sync. Reset the speedo. - let mut speedo = speedo.lock(); + let mut speedo = speedo.lock().await; speedo.clear(); } (SyncState::BackFillSyncing { .. }, _) => { // We have transitioned from a backfill sync, reset the speedo - let mut speedo = speedo.lock(); + let mut speedo = speedo.lock().await; speedo.clear(); } (_, _) => {} @@ -125,7 +129,7 @@ pub fn spawn_notifier( // progress. let mut sync_distance = current_slot - head_slot; - let mut speedo = speedo.lock(); + let mut speedo = speedo.lock().await; match current_sync_state { SyncState::BackFillSyncing { .. } => { // Observe backfilling sync info. @@ -306,6 +310,7 @@ pub fn spawn_notifier( } eth1_logging(&beacon_chain, &log); + merge_readiness_logging(current_slot, &beacon_chain, &log).await; } }; @@ -315,6 +320,88 @@ pub fn spawn_notifier( Ok(()) } +/// Provides some helpful logging to users to indicate if their node is ready for the Bellatrix +/// fork and subsequent merge transition. +async fn merge_readiness_logging( + current_slot: Slot, + beacon_chain: &BeaconChain, + log: &Logger, +) { + let merge_completed = beacon_chain + .canonical_head + .cached_head() + .snapshot + .beacon_block + .message() + .body() + .execution_payload() + .map_or(false, |payload| { + payload.parent_hash() != ExecutionBlockHash::zero() + }); + + if merge_completed || !beacon_chain.is_time_to_prepare_for_bellatrix(current_slot) { + return; + } + + match beacon_chain.check_merge_readiness().await { + MergeReadiness::Ready { + config, + current_difficulty, + } => match config { + MergeConfig { + terminal_total_difficulty: Some(ttd), + terminal_block_hash: None, + terminal_block_hash_epoch: None, + } => { + info!( + log, + "Ready for the merge"; + "terminal_total_difficulty" => %ttd, + "current_difficulty" => current_difficulty + .map(|d| d.to_string()) + .unwrap_or_else(|_| "??".into()), + ) + } + MergeConfig { + terminal_total_difficulty: _, + terminal_block_hash: Some(terminal_block_hash), + terminal_block_hash_epoch: Some(terminal_block_hash_epoch), + } => { + info!( + log, + "Ready for the merge"; + "info" => "you are using override parameters, please ensure that you \ + understand these parameters and their implications.", + "terminal_block_hash" => ?terminal_block_hash, + "terminal_block_hash_epoch" => ?terminal_block_hash_epoch, + ) + } + other => error!( + log, + "Inconsistent merge configuration"; + "config" => ?other + ), + }, + readiness @ MergeReadiness::ExchangeTransitionConfigurationFailed(_) => { + error!( + log, + "Not ready for merge"; + "info" => %readiness, + ) + } + readiness @ MergeReadiness::NotSynced => warn!( + log, + "Not ready for merge"; + "info" => %readiness, + ), + readiness @ MergeReadiness::NoExecutionEndpoint => warn!( + log, + "Not ready for merge"; + "info" => %readiness, + ), + } +} + fn eth1_logging(beacon_chain: &BeaconChain, log: &Logger) { let current_slot_opt = beacon_chain.slot().ok(); diff --git a/beacon_node/execution_layer/src/lib.rs b/beacon_node/execution_layer/src/lib.rs index d85f9eb81..4ab38cb3a 100644 --- a/beacon_node/execution_layer/src/lib.rs +++ b/beacon_node/execution_layer/src/lib.rs @@ -234,6 +234,16 @@ impl ExecutionLayer { &self.inner.executor } + /// Get the current difficulty of the PoW chain. + pub async fn get_current_difficulty(&self) -> Result { + let block = self + .engine() + .api + .get_block_by_number(BlockByNumberQuery::Tag(LATEST_TAG)) + .await? + .ok_or(ApiError::ExecutionHeadBlockNotFound)?; + Ok(block.total_difficulty) + } /// Note: this function returns a mutex guard, be careful to avoid deadlocks. async fn execution_blocks( &self, @@ -355,6 +365,29 @@ impl ExecutionLayer { self.engine().is_synced().await } + /// Execution nodes return a "SYNCED" response when they do not have any peers. + /// + /// This function is a wrapper over `Self::is_synced` that makes an additional + /// check for the execution layer sync status. Checks if the latest block has + /// a `block_number != 0`. + /// Returns the `Self::is_synced` response if unable to get latest block. + pub async fn is_synced_for_notifier(&self) -> bool { + let synced = self.is_synced().await; + if synced { + if let Ok(Some(block)) = self + .engine() + .api + .get_block_by_number(BlockByNumberQuery::Tag(LATEST_TAG)) + .await + { + if block.block_number == 0 { + return false; + } + } + } + synced + } + /// Updates the proposer preparation data provided by validators pub async fn update_proposer_preparation( &self, diff --git a/beacon_node/execution_layer/src/test_utils/handle_rpc.rs b/beacon_node/execution_layer/src/test_utils/handle_rpc.rs index 5e0e0591c..eceb50df2 100644 --- a/beacon_node/execution_layer/src/test_utils/handle_rpc.rs +++ b/beacon_node/execution_layer/src/test_utils/handle_rpc.rs @@ -132,6 +132,15 @@ pub async fn handle_rpc( Ok(serde_json::to_value(response).unwrap()) } + ENGINE_EXCHANGE_TRANSITION_CONFIGURATION_V1 => { + let block_generator = ctx.execution_block_generator.read(); + let transition_config: TransitionConfigurationV1 = TransitionConfigurationV1 { + terminal_total_difficulty: block_generator.terminal_total_difficulty, + terminal_block_hash: block_generator.terminal_block_hash, + terminal_block_number: block_generator.terminal_block_number, + }; + Ok(serde_json::to_value(transition_config).unwrap()) + } other => Err(format!( "The method {} does not exist/is not available", other