//! Provides tools for checking if a node is ready for the Bellatrix upgrade and following merge //! transition. use crate::{BeaconChain, BeaconChainError as Error, BeaconChainTypes}; use execution_layer::BlockByNumberQuery; use serde::{Deserialize, Serialize, Serializer}; use std::fmt; use std::fmt::Write; use types::*; /// The time before the Bellatrix fork when we will start issuing warnings about preparation. pub const SECONDS_IN_A_WEEK: u64 = 604800; pub const BELLATRIX_READINESS_PREPARATION_SECONDS: u64 = SECONDS_IN_A_WEEK * 2; #[derive(Default, Debug, Serialize, Deserialize)] pub struct MergeConfig { #[serde(serialize_with = "serialize_uint256")] pub terminal_total_difficulty: Option, #[serde(skip_serializing_if = "Option::is_none")] pub terminal_block_hash: Option, #[serde(skip_serializing_if = "Option::is_none")] 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 { 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. #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] #[serde(tag = "type")] pub enum BellatrixReadiness { /// The node is ready, as far as we can tell. Ready { config: MergeConfig, #[serde(serialize_with = "serialize_uint256")] current_difficulty: Option, }, /// 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 BellatrixReadiness { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { BellatrixReadiness::Ready { config: params, current_difficulty, } => { write!( f, "This node appears ready for Bellatrix \ Params: {}, current_difficulty: {:?}", params, current_difficulty ) } BellatrixReadiness::NotSynced => write!( f, "The execution endpoint is connected and configured, \ however it is not yet synced" ), BellatrixReadiness::NoExecutionEndpoint => write!( f, "The --execution-endpoint flag is not specified, this is a \ requirement for Bellatrix" ), } } } pub enum GenesisExecutionPayloadStatus { Correct(ExecutionBlockHash), BlockHashMismatch { got: ExecutionBlockHash, expected: ExecutionBlockHash, }, TransactionsRootMismatch { got: Hash256, expected: Hash256, }, WithdrawalsRootMismatch { got: Hash256, expected: Hash256, }, OtherMismatch, Irrelevant, AlreadyHappened, } impl BeaconChain { /// Returns `true` if user has an EL configured, or if the Bellatrix fork has occurred or will /// occur within `BELLATRIX_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 bellatrix_readiness_preparation_slots = BELLATRIX_READINESS_PREPARATION_SECONDS / self.spec.seconds_per_slot; if self.execution_layer.is_some() { // The user has already configured an execution layer, start checking for readiness // right away. true } else { // Return `true` if Bellatrix has happened or is within the preparation time. current_slot + bellatrix_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 Bellatrix. pub async fn check_bellatrix_readiness(&self, current_slot: Slot) -> BellatrixReadiness { if let Some(el) = self.execution_layer.as_ref() { if !el.is_synced_for_notifier(current_slot).await { // The EL is not synced. return BellatrixReadiness::NotSynced; } let params = MergeConfig::from_chainspec(&self.spec); let current_difficulty = el.get_current_difficulty().await.ok().flatten(); BellatrixReadiness::Ready { config: params, current_difficulty, } } else { // There is no EL configured. BellatrixReadiness::NoExecutionEndpoint } } /// Check that the execution payload embedded in the genesis state matches the EL's genesis /// block. pub async fn check_genesis_execution_payload_is_correct( &self, ) -> Result { let head_snapshot = self.head_snapshot(); let genesis_state = &head_snapshot.beacon_state; if genesis_state.slot() != 0 { return Ok(GenesisExecutionPayloadStatus::AlreadyHappened); } let Ok(latest_execution_payload_header) = genesis_state.latest_execution_payload_header() else { return Ok(GenesisExecutionPayloadStatus::Irrelevant); }; let execution_layer = self .execution_layer .as_ref() .ok_or(Error::ExecutionLayerMissing)?; let exec_block_hash = latest_execution_payload_header.block_hash(); // Use getBlockByNumber(0) to check that the block hash matches. // At present, Geth does not respond to engine_getPayloadBodiesByRange before genesis. let execution_block = execution_layer .get_block_by_number(BlockByNumberQuery::Tag("0x0")) .await .map_err(|e| Error::ExecutionLayerGetBlockByNumberFailed(Box::new(e)))? .ok_or(Error::BlockHashMissingFromExecutionLayer(exec_block_hash))?; if execution_block.block_hash != exec_block_hash { return Ok(GenesisExecutionPayloadStatus::BlockHashMismatch { got: execution_block.block_hash, expected: exec_block_hash, }); } Ok(GenesisExecutionPayloadStatus::Correct(exec_block_hash)) } } /// Utility function to serialize a Uint256 as a decimal string. fn serialize_uint256(val: &Option, s: S) -> Result where S: Serializer, { match val { Some(v) => v.to_string().serialize(s), None => s.serialize_none(), } }