## 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 <paul@paulhauner.com>
This commit is contained in:
Pawan Dhananjay 2022-07-20 23:16:54 +00:00
parent 7c3ff903ca
commit 5b5cf9cfaa
5 changed files with 305 additions and 6 deletions

View File

@ -20,6 +20,7 @@ pub mod fork_choice_signal;
pub mod fork_revert; pub mod fork_revert;
mod head_tracker; mod head_tracker;
pub mod historical_blocks; pub mod historical_blocks;
pub mod merge_readiness;
mod metrics; mod metrics;
pub mod migrate; pub mod migrate;
mod naive_aggregation_pool; mod naive_aggregation_pool;

View File

@ -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<Uint256>,
pub terminal_block_hash: Option<ExecutionBlockHash>,
pub terminal_block_hash_epoch: Option<Epoch>,
}
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<Uint256, String>,
},
/// 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<T: BeaconChainTypes> BeaconChain<T> {
/// 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
}
}
}

View File

@ -1,13 +1,16 @@
use crate::metrics; 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 lighthouse_network::{types::SyncState, NetworkGlobals};
use parking_lot::Mutex;
use slog::{crit, debug, error, info, warn, Logger}; use slog::{crit, debug, error, info, warn, Logger};
use slot_clock::SlotClock; use slot_clock::SlotClock;
use std::sync::Arc; use std::sync::Arc;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use tokio::sync::Mutex;
use tokio::time::sleep; use tokio::time::sleep;
use types::{EthSpec, Slot}; use types::*;
/// Create a warning log whenever the peer count is at or below this value. /// Create a warning log whenever the peer count is at or below this value.
pub const WARN_PEER_COUNT: usize = 1; pub const WARN_PEER_COUNT: usize = 1;
@ -77,6 +80,7 @@ pub fn spawn_notifier<T: BeaconChainTypes>(
// Perform post-genesis logging. // Perform post-genesis logging.
let mut last_backfill_log_slot = None; let mut last_backfill_log_slot = None;
loop { loop {
interval.tick().await; interval.tick().await;
let connected_peer_count = network.connected_peers(); let connected_peer_count = network.connected_peers();
@ -87,12 +91,12 @@ pub fn spawn_notifier<T: BeaconChainTypes>(
match (current_sync_state, &sync_state) { match (current_sync_state, &sync_state) {
(_, SyncState::BackFillSyncing { .. }) => { (_, SyncState::BackFillSyncing { .. }) => {
// We have transitioned to a backfill sync. Reset the speedo. // We have transitioned to a backfill sync. Reset the speedo.
let mut speedo = speedo.lock(); let mut speedo = speedo.lock().await;
speedo.clear(); speedo.clear();
} }
(SyncState::BackFillSyncing { .. }, _) => { (SyncState::BackFillSyncing { .. }, _) => {
// We have transitioned from a backfill sync, reset the speedo // We have transitioned from a backfill sync, reset the speedo
let mut speedo = speedo.lock(); let mut speedo = speedo.lock().await;
speedo.clear(); speedo.clear();
} }
(_, _) => {} (_, _) => {}
@ -125,7 +129,7 @@ pub fn spawn_notifier<T: BeaconChainTypes>(
// progress. // progress.
let mut sync_distance = current_slot - head_slot; let mut sync_distance = current_slot - head_slot;
let mut speedo = speedo.lock(); let mut speedo = speedo.lock().await;
match current_sync_state { match current_sync_state {
SyncState::BackFillSyncing { .. } => { SyncState::BackFillSyncing { .. } => {
// Observe backfilling sync info. // Observe backfilling sync info.
@ -306,6 +310,7 @@ pub fn spawn_notifier<T: BeaconChainTypes>(
} }
eth1_logging(&beacon_chain, &log); eth1_logging(&beacon_chain, &log);
merge_readiness_logging(current_slot, &beacon_chain, &log).await;
} }
}; };
@ -315,6 +320,88 @@ pub fn spawn_notifier<T: BeaconChainTypes>(
Ok(()) 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<T: BeaconChainTypes>(
current_slot: Slot,
beacon_chain: &BeaconChain<T>,
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<T: BeaconChainTypes>(beacon_chain: &BeaconChain<T>, log: &Logger) { fn eth1_logging<T: BeaconChainTypes>(beacon_chain: &BeaconChain<T>, log: &Logger) {
let current_slot_opt = beacon_chain.slot().ok(); let current_slot_opt = beacon_chain.slot().ok();

View File

@ -234,6 +234,16 @@ impl<T: EthSpec> ExecutionLayer<T> {
&self.inner.executor &self.inner.executor
} }
/// Get the current difficulty of the PoW chain.
pub async fn get_current_difficulty(&self) -> Result<Uint256, ApiError> {
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. /// Note: this function returns a mutex guard, be careful to avoid deadlocks.
async fn execution_blocks( async fn execution_blocks(
&self, &self,
@ -355,6 +365,29 @@ impl<T: EthSpec> ExecutionLayer<T> {
self.engine().is_synced().await 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 /// Updates the proposer preparation data provided by validators
pub async fn update_proposer_preparation( pub async fn update_proposer_preparation(
&self, &self,

View File

@ -132,6 +132,15 @@ pub async fn handle_rpc<T: EthSpec>(
Ok(serde_json::to_value(response).unwrap()) 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!( other => Err(format!(
"The method {} does not exist/is not available", "The method {} does not exist/is not available",
other other