Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions beacon_node/beacon_chain/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
169 changes: 169 additions & 0 deletions beacon_node/beacon_chain/src/merge_readiness.rs
Original file line number Diff line number Diff line change
@@ -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
}
}
}
99 changes: 93 additions & 6 deletions beacon_node/client/src/notifier.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -77,6 +80,7 @@ pub fn spawn_notifier<T: BeaconChainTypes>(

// Perform post-genesis logging.
let mut last_backfill_log_slot = None;

loop {
interval.tick().await;
let connected_peer_count = network.connected_peers();
Expand All @@ -87,12 +91,12 @@ pub fn spawn_notifier<T: BeaconChainTypes>(
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();
}
(_, _) => {}
Expand Down Expand Up @@ -125,7 +129,7 @@ pub fn spawn_notifier<T: BeaconChainTypes>(
// 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.
Expand Down Expand Up @@ -306,6 +310,7 @@ pub fn spawn_notifier<T: BeaconChainTypes>(
}

eth1_logging(&beacon_chain, &log);
merge_readiness_logging(current_slot, &beacon_chain, &log).await;
}
};

Expand All @@ -315,6 +320,88 @@ pub fn spawn_notifier<T: BeaconChainTypes>(
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) {
let current_slot_opt = beacon_chain.slot().ok();

Expand Down
33 changes: 33 additions & 0 deletions beacon_node/execution_layer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,16 @@ impl<T: EthSpec> ExecutionLayer<T> {
&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.
async fn execution_blocks(
&self,
Expand Down Expand Up @@ -355,6 +365,29 @@ impl<T: EthSpec> ExecutionLayer<T> {
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,
Expand Down
9 changes: 9 additions & 0 deletions beacon_node/execution_layer/src/test_utils/handle_rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,15 @@ pub async fn handle_rpc<T: EthSpec>(

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
Expand Down