Add Gloas boilerplate (#7728)

Adds the required boilerplate code for the Gloas (Glamsterdam) hard fork. This allows PRs testing Gloas-candidate features to test fork transition.

This also includes de-duplication of post-Bellatrix readiness notifiers from #6797 (credit to @dapplion)
This commit is contained in:
Mac L
2025-08-26 12:49:48 +10:00
committed by GitHub
parent daf1c7c3af
commit e438691683
75 changed files with 1801 additions and 917 deletions

View File

@@ -16,7 +16,7 @@ use types::{
};
use types::{
ExecutionPayload, ExecutionPayloadBellatrix, ExecutionPayloadCapella, ExecutionPayloadElectra,
ExecutionPayloadFulu, ExecutionPayloadHeader,
ExecutionPayloadFulu, ExecutionPayloadGloas, ExecutionPayloadHeader,
};
#[derive(PartialEq)]
@@ -101,6 +101,7 @@ fn reconstruct_default_header_block<E: EthSpec>(
ForkName::Deneb => ExecutionPayloadDeneb::default().into(),
ForkName::Electra => ExecutionPayloadElectra::default().into(),
ForkName::Fulu => ExecutionPayloadFulu::default().into(),
ForkName::Gloas => ExecutionPayloadGloas::default().into(),
ForkName::Base | ForkName::Altair => {
return Err(Error::PayloadReconstruction(format!(
"Block with fork variant {} has execution payload",
@@ -715,7 +716,7 @@ mod tests {
}
#[tokio::test]
async fn check_all_blocks_from_altair_to_fulu() {
async fn check_all_blocks_from_altair_to_gloas() {
let slots_per_epoch = MinimalEthSpec::slots_per_epoch() as usize;
let num_epochs = 12;
let bellatrix_fork_epoch = 2usize;
@@ -723,6 +724,7 @@ mod tests {
let deneb_fork_epoch = 6usize;
let electra_fork_epoch = 8usize;
let fulu_fork_epoch = 10usize;
let gloas_fork_epoch = 12usize;
let num_blocks_produced = num_epochs * slots_per_epoch;
let mut spec = test_spec::<MinimalEthSpec>();
@@ -732,6 +734,7 @@ mod tests {
spec.deneb_fork_epoch = Some(Epoch::new(deneb_fork_epoch as u64));
spec.electra_fork_epoch = Some(Epoch::new(electra_fork_epoch as u64));
spec.fulu_fork_epoch = Some(Epoch::new(fulu_fork_epoch as u64));
spec.gloas_fork_epoch = Some(Epoch::new(gloas_fork_epoch as u64));
let spec = Arc::new(spec);
let harness = get_harness(VALIDATOR_COUNT, spec.clone());

View File

@@ -5726,6 +5726,48 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
execution_payload_value,
)
}
BeaconState::Gloas(_) => {
let (
payload,
kzg_commitments,
maybe_blobs_and_proofs,
maybe_requests,
execution_payload_value,
) = block_contents
.ok_or(BlockProductionError::MissingExecutionPayload)?
.deconstruct();
(
BeaconBlock::Gloas(BeaconBlockGloas {
slot,
proposer_index,
parent_root,
state_root: Hash256::zero(),
body: BeaconBlockBodyGloas {
randao_reveal,
eth1_data,
graffiti,
proposer_slashings: proposer_slashings.into(),
attester_slashings: attester_slashings_electra.into(),
attestations: attestations_electra.into(),
deposits: deposits.into(),
voluntary_exits: voluntary_exits.into(),
sync_aggregate: sync_aggregate
.ok_or(BlockProductionError::MissingSyncAggregate)?,
execution_payload: payload
.try_into()
.map_err(|_| BlockProductionError::InvalidPayloadFork)?,
bls_to_execution_changes: bls_to_execution_changes.into(),
blob_kzg_commitments: kzg_commitments
.ok_or(BlockProductionError::InvalidPayloadFork)?,
execution_requests: maybe_requests
.ok_or(BlockProductionError::MissingExecutionRequests)?,
},
}),
maybe_blobs_and_proofs,
execution_payload_value,
)
}
};
let block = SignedBeaconBlock::from_block(

View File

@@ -1,121 +0,0 @@
//! Provides tools for checking if a node is ready for the Capella upgrade.
use crate::{BeaconChain, BeaconChainTypes};
use execution_layer::http::{
ENGINE_FORKCHOICE_UPDATED_V2, ENGINE_GET_PAYLOAD_V2, ENGINE_NEW_PAYLOAD_V2,
};
use serde::{Deserialize, Serialize};
use std::fmt;
use std::time::Duration;
use types::*;
/// The time before the Capella fork when we will start issuing warnings about preparation.
use super::bellatrix_readiness::SECONDS_IN_A_WEEK;
pub const CAPELLA_READINESS_PREPARATION_SECONDS: u64 = SECONDS_IN_A_WEEK * 2;
pub const ENGINE_CAPABILITIES_REFRESH_INTERVAL: u64 = 300;
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[serde(tag = "type")]
pub enum CapellaReadiness {
/// The execution engine is capella-enabled (as far as we can tell)
Ready,
/// We are connected to an execution engine which doesn't support the V2 engine api methods
V2MethodsNotSupported { error: String },
/// The transition configuration with the EL failed, there might be a problem with
/// connectivity, authentication or a difference in configuration.
ExchangeCapabilitiesFailed { error: String },
/// The user has not configured an execution endpoint
NoExecutionEndpoint,
}
impl fmt::Display for CapellaReadiness {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
CapellaReadiness::Ready => {
write!(f, "This node appears ready for Capella.")
}
CapellaReadiness::ExchangeCapabilitiesFailed { error } => write!(
f,
"Could not exchange capabilities with the \
execution endpoint: {}",
error
),
CapellaReadiness::NoExecutionEndpoint => write!(
f,
"The --execution-endpoint flag is not specified, this is a \
requirement post-merge"
),
CapellaReadiness::V2MethodsNotSupported { error } => write!(
f,
"Execution endpoint does not support Capella methods: {}",
error
),
}
}
}
impl<T: BeaconChainTypes> BeaconChain<T> {
/// Returns `true` if capella epoch is set and Capella fork has occurred or will
/// occur within `CAPELLA_READINESS_PREPARATION_SECONDS`
pub fn is_time_to_prepare_for_capella(&self, current_slot: Slot) -> bool {
if let Some(capella_epoch) = self.spec.capella_fork_epoch {
let capella_slot = capella_epoch.start_slot(T::EthSpec::slots_per_epoch());
let capella_readiness_preparation_slots =
CAPELLA_READINESS_PREPARATION_SECONDS / self.spec.seconds_per_slot;
// Return `true` if Capella has happened or is within the preparation time.
current_slot + capella_readiness_preparation_slots > capella_slot
} else {
// The Capella 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 capella.
pub async fn check_capella_readiness(&self) -> CapellaReadiness {
if let Some(el) = self.execution_layer.as_ref() {
match el
.get_engine_capabilities(Some(Duration::from_secs(
ENGINE_CAPABILITIES_REFRESH_INTERVAL,
)))
.await
{
Err(e) => {
// The EL was either unreachable or responded with an error
CapellaReadiness::ExchangeCapabilitiesFailed {
error: format!("{:?}", e),
}
}
Ok(capabilities) => {
let mut missing_methods = String::from("Required Methods Unsupported:");
let mut all_good = true;
if !capabilities.get_payload_v2 {
missing_methods.push(' ');
missing_methods.push_str(ENGINE_GET_PAYLOAD_V2);
all_good = false;
}
if !capabilities.forkchoice_updated_v2 {
missing_methods.push(' ');
missing_methods.push_str(ENGINE_FORKCHOICE_UPDATED_V2);
all_good = false;
}
if !capabilities.new_payload_v2 {
missing_methods.push(' ');
missing_methods.push_str(ENGINE_NEW_PAYLOAD_V2);
all_good = false;
}
if all_good {
CapellaReadiness::Ready
} else {
CapellaReadiness::V2MethodsNotSupported {
error: missing_methods,
}
}
}
}
} else {
CapellaReadiness::NoExecutionEndpoint
}
}
}

View File

@@ -1,121 +0,0 @@
//! Provides tools for checking if a node is ready for the Deneb upgrade.
use crate::{BeaconChain, BeaconChainTypes};
use execution_layer::http::{
ENGINE_FORKCHOICE_UPDATED_V3, ENGINE_GET_PAYLOAD_V3, ENGINE_NEW_PAYLOAD_V3,
};
use serde::{Deserialize, Serialize};
use std::fmt;
use std::time::Duration;
use types::*;
/// The time before the Deneb fork when we will start issuing warnings about preparation.
use super::bellatrix_readiness::SECONDS_IN_A_WEEK;
pub const DENEB_READINESS_PREPARATION_SECONDS: u64 = SECONDS_IN_A_WEEK * 2;
pub const ENGINE_CAPABILITIES_REFRESH_INTERVAL: u64 = 300;
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[serde(tag = "type")]
pub enum DenebReadiness {
/// The execution engine is deneb-enabled (as far as we can tell)
Ready,
/// We are connected to an execution engine which doesn't support the V3 engine api methods
V3MethodsNotSupported { error: String },
/// The transition configuration with the EL failed, there might be a problem with
/// connectivity, authentication or a difference in configuration.
ExchangeCapabilitiesFailed { error: String },
/// The user has not configured an execution endpoint
NoExecutionEndpoint,
}
impl fmt::Display for DenebReadiness {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
DenebReadiness::Ready => {
write!(f, "This node appears ready for Deneb.")
}
DenebReadiness::ExchangeCapabilitiesFailed { error } => write!(
f,
"Could not exchange capabilities with the \
execution endpoint: {}",
error
),
DenebReadiness::NoExecutionEndpoint => write!(
f,
"The --execution-endpoint flag is not specified, this is a \
requirement post-merge"
),
DenebReadiness::V3MethodsNotSupported { error } => write!(
f,
"Execution endpoint does not support Deneb methods: {}",
error
),
}
}
}
impl<T: BeaconChainTypes> BeaconChain<T> {
/// Returns `true` if deneb epoch is set and Deneb fork has occurred or will
/// occur within `DENEB_READINESS_PREPARATION_SECONDS`
pub fn is_time_to_prepare_for_deneb(&self, current_slot: Slot) -> bool {
if let Some(deneb_epoch) = self.spec.deneb_fork_epoch {
let deneb_slot = deneb_epoch.start_slot(T::EthSpec::slots_per_epoch());
let deneb_readiness_preparation_slots =
DENEB_READINESS_PREPARATION_SECONDS / self.spec.seconds_per_slot;
// Return `true` if Deneb has happened or is within the preparation time.
current_slot + deneb_readiness_preparation_slots > deneb_slot
} else {
// The Deneb 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 capella.
pub async fn check_deneb_readiness(&self) -> DenebReadiness {
if let Some(el) = self.execution_layer.as_ref() {
match el
.get_engine_capabilities(Some(Duration::from_secs(
ENGINE_CAPABILITIES_REFRESH_INTERVAL,
)))
.await
{
Err(e) => {
// The EL was either unreachable or responded with an error
DenebReadiness::ExchangeCapabilitiesFailed {
error: format!("{:?}", e),
}
}
Ok(capabilities) => {
let mut missing_methods = String::from("Required Methods Unsupported:");
let mut all_good = true;
if !capabilities.get_payload_v3 {
missing_methods.push(' ');
missing_methods.push_str(ENGINE_GET_PAYLOAD_V3);
all_good = false;
}
if !capabilities.forkchoice_updated_v3 {
missing_methods.push(' ');
missing_methods.push_str(ENGINE_FORKCHOICE_UPDATED_V3);
all_good = false;
}
if !capabilities.new_payload_v3 {
missing_methods.push(' ');
missing_methods.push_str(ENGINE_NEW_PAYLOAD_V3);
all_good = false;
}
if all_good {
DenebReadiness::Ready
} else {
DenebReadiness::V3MethodsNotSupported {
error: missing_methods,
}
}
}
}
} else {
DenebReadiness::NoExecutionEndpoint
}
}
}

View File

@@ -1,115 +0,0 @@
//! Provides tools for checking if a node is ready for the Electra upgrade and following merge
//! transition.
use crate::{BeaconChain, BeaconChainTypes};
use execution_layer::http::{ENGINE_GET_PAYLOAD_V4, ENGINE_NEW_PAYLOAD_V4};
use serde::{Deserialize, Serialize};
use std::fmt;
use std::time::Duration;
use types::*;
/// The time before the Electra fork when we will start issuing warnings about preparation.
use super::bellatrix_readiness::SECONDS_IN_A_WEEK;
pub const ELECTRA_READINESS_PREPARATION_SECONDS: u64 = SECONDS_IN_A_WEEK * 2;
pub const ENGINE_CAPABILITIES_REFRESH_INTERVAL: u64 = 300;
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[serde(tag = "type")]
pub enum ElectraReadiness {
/// The execution engine is electra-enabled (as far as we can tell)
Ready,
/// We are connected to an execution engine which doesn't support the V4 engine api methods
V4MethodsNotSupported { error: String },
/// The transition configuration with the EL failed, there might be a problem with
/// connectivity, authentication or a difference in configuration.
ExchangeCapabilitiesFailed { error: String },
/// The user has not configured an execution endpoint
NoExecutionEndpoint,
}
impl fmt::Display for ElectraReadiness {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ElectraReadiness::Ready => {
write!(f, "This node appears ready for Electra.")
}
ElectraReadiness::ExchangeCapabilitiesFailed { error } => write!(
f,
"Could not exchange capabilities with the \
execution endpoint: {}",
error
),
ElectraReadiness::NoExecutionEndpoint => write!(
f,
"The --execution-endpoint flag is not specified, this is a \
requirement post-merge"
),
ElectraReadiness::V4MethodsNotSupported { error } => write!(
f,
"Execution endpoint does not support Electra methods: {}",
error
),
}
}
}
impl<T: BeaconChainTypes> BeaconChain<T> {
/// Returns `true` if electra epoch is set and Electra fork has occurred or will
/// occur within `ELECTRA_READINESS_PREPARATION_SECONDS`
pub fn is_time_to_prepare_for_electra(&self, current_slot: Slot) -> bool {
if let Some(electra_epoch) = self.spec.electra_fork_epoch {
let electra_slot = electra_epoch.start_slot(T::EthSpec::slots_per_epoch());
let electra_readiness_preparation_slots =
ELECTRA_READINESS_PREPARATION_SECONDS / self.spec.seconds_per_slot;
// Return `true` if Electra has happened or is within the preparation time.
current_slot + electra_readiness_preparation_slots > electra_slot
} else {
// The Electra 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 electra.
pub async fn check_electra_readiness(&self) -> ElectraReadiness {
if let Some(el) = self.execution_layer.as_ref() {
match el
.get_engine_capabilities(Some(Duration::from_secs(
ENGINE_CAPABILITIES_REFRESH_INTERVAL,
)))
.await
{
Err(e) => {
// The EL was either unreachable or responded with an error
ElectraReadiness::ExchangeCapabilitiesFailed {
error: format!("{:?}", e),
}
}
Ok(capabilities) => {
let mut missing_methods = String::from("Required Methods Unsupported:");
let mut all_good = true;
if !capabilities.get_payload_v4 {
missing_methods.push(' ');
missing_methods.push_str(ENGINE_GET_PAYLOAD_V4);
all_good = false;
}
if !capabilities.new_payload_v4 {
missing_methods.push(' ');
missing_methods.push_str(ENGINE_NEW_PAYLOAD_V4);
all_good = false;
}
if all_good {
ElectraReadiness::Ready
} else {
ElectraReadiness::V4MethodsNotSupported {
error: missing_methods,
}
}
}
}
} else {
ElectraReadiness::NoExecutionEndpoint
}
}
}

View File

@@ -1,114 +0,0 @@
//! Provides tools for checking if a node is ready for the Fulu upgrade.
use crate::{BeaconChain, BeaconChainTypes};
use execution_layer::http::{ENGINE_GET_PAYLOAD_V5, ENGINE_NEW_PAYLOAD_V4};
use serde::{Deserialize, Serialize};
use std::fmt;
use std::time::Duration;
use types::*;
/// The time before the Fulu fork when we will start issuing warnings about preparation.
use super::bellatrix_readiness::SECONDS_IN_A_WEEK;
pub const FULU_READINESS_PREPARATION_SECONDS: u64 = SECONDS_IN_A_WEEK * 2;
pub const ENGINE_CAPABILITIES_REFRESH_INTERVAL: u64 = 300;
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[serde(tag = "type")]
pub enum FuluReadiness {
/// The execution engine is fulu-enabled (as far as we can tell)
Ready,
/// We are connected to an execution engine which doesn't support the V5 engine api methods
V5MethodsNotSupported { error: String },
/// The transition configuration with the EL failed, there might be a problem with
/// connectivity, authentication or a difference in configuration.
ExchangeCapabilitiesFailed { error: String },
/// The user has not configured an execution endpoint
NoExecutionEndpoint,
}
impl fmt::Display for FuluReadiness {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
FuluReadiness::Ready => {
write!(f, "This node appears ready for Fulu.")
}
FuluReadiness::ExchangeCapabilitiesFailed { error } => write!(
f,
"Could not exchange capabilities with the \
execution endpoint: {}",
error
),
FuluReadiness::NoExecutionEndpoint => write!(
f,
"The --execution-endpoint flag is not specified, this is a \
requirement post-merge"
),
FuluReadiness::V5MethodsNotSupported { error } => write!(
f,
"Execution endpoint does not support Fulu methods: {}",
error
),
}
}
}
impl<T: BeaconChainTypes> BeaconChain<T> {
/// Returns `true` if fulu epoch is set and Fulu fork has occurred or will
/// occur within `FULU_READINESS_PREPARATION_SECONDS`
pub fn is_time_to_prepare_for_fulu(&self, current_slot: Slot) -> bool {
if let Some(fulu_epoch) = self.spec.fulu_fork_epoch {
let fulu_slot = fulu_epoch.start_slot(T::EthSpec::slots_per_epoch());
let fulu_readiness_preparation_slots =
FULU_READINESS_PREPARATION_SECONDS / self.spec.seconds_per_slot;
// Return `true` if Fulu has happened or is within the preparation time.
current_slot + fulu_readiness_preparation_slots > fulu_slot
} else {
// The Fulu 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 fulu.
pub async fn check_fulu_readiness(&self) -> FuluReadiness {
if let Some(el) = self.execution_layer.as_ref() {
match el
.get_engine_capabilities(Some(Duration::from_secs(
ENGINE_CAPABILITIES_REFRESH_INTERVAL,
)))
.await
{
Err(e) => {
// The EL was either unreachable or responded with an error
FuluReadiness::ExchangeCapabilitiesFailed {
error: format!("{:?}", e),
}
}
Ok(capabilities) => {
let mut missing_methods = String::from("Required Methods Unsupported:");
let mut all_good = true;
if !capabilities.get_payload_v5 {
missing_methods.push(' ');
missing_methods.push_str(ENGINE_GET_PAYLOAD_V5);
all_good = false;
}
if !capabilities.new_payload_v4 {
missing_methods.push(' ');
missing_methods.push_str(ENGINE_NEW_PAYLOAD_V4);
all_good = false;
}
if all_good {
FuluReadiness::Ready
} else {
FuluReadiness::V5MethodsNotSupported {
error: missing_methods,
}
}
}
}
} else {
FuluReadiness::NoExecutionEndpoint
}
}
}

View File

@@ -16,20 +16,16 @@ mod block_verification;
pub mod block_verification_types;
pub mod builder;
pub mod canonical_head;
pub mod capella_readiness;
pub mod chain_config;
pub mod data_availability_checker;
pub mod data_column_verification;
pub mod deneb_readiness;
mod early_attester_cache;
pub mod electra_readiness;
mod errors;
pub mod events;
pub mod execution_payload;
pub mod fetch_blobs;
pub mod fork_choice_signal;
pub mod fork_revert;
pub mod fulu_readiness;
pub mod graffiti_calculator;
pub mod historical_blocks;
pub mod kzg_utils;

View File

@@ -496,6 +496,10 @@ where
mock.server.execution_block_generator().osaka_time = spec.fulu_fork_epoch.map(|epoch| {
genesis_time + spec.seconds_per_slot * E::slots_per_epoch() * epoch.as_u64()
});
mock.server.execution_block_generator().amsterdam_time =
spec.gloas_fork_epoch.map(|epoch| {
genesis_time + spec.seconds_per_slot * E::slots_per_epoch() * epoch.as_u64()
});
self
}
@@ -630,6 +634,9 @@ pub fn mock_execution_layer_from_parts<E: EthSpec>(
let osaka_time = spec.fulu_fork_epoch.map(|epoch| {
HARNESS_GENESIS_TIME + spec.seconds_per_slot * E::slots_per_epoch() * epoch.as_u64()
});
let amsterdam_time = spec.gloas_fork_epoch.map(|epoch| {
HARNESS_GENESIS_TIME + spec.seconds_per_slot * E::slots_per_epoch() * epoch.as_u64()
});
let kzg = get_kzg(&spec);
@@ -640,6 +647,7 @@ pub fn mock_execution_layer_from_parts<E: EthSpec>(
cancun_time,
prague_time,
osaka_time,
amsterdam_time,
Some(JwtKey::from_slice(&DEFAULT_JWT_SECRET).unwrap()),
spec,
Some(kzg),
@@ -3244,6 +3252,25 @@ pub fn generate_rand_block_and_blobs<E: EthSpec>(
message.body.blob_kzg_commitments = bundle.commitments.clone();
bundle
}
SignedBeaconBlock::Gloas(SignedBeaconBlockGloas {
ref mut message, ..
}) => {
// Get either zero blobs or a random number of blobs between 1 and Max Blobs.
let payload: &mut FullPayloadGloas<E> = &mut message.body.execution_payload;
let num_blobs = match num_blobs {
NumBlobs::Random => rng.random_range(1..=max_blobs),
NumBlobs::Number(n) => n,
NumBlobs::None => 0,
};
let (bundle, transactions) =
execution_layer::test_utils::generate_blobs::<E>(num_blobs, fork_name).unwrap();
payload.execution_payload.transactions = <_>::default();
for tx in Vec::from(transactions) {
payload.execution_payload.transactions.push(tx).unwrap();
}
message.body.blob_kzg_commitments = bundle.commitments.clone();
bundle
}
_ => return (block, blob_sidecars),
};