Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Electra fork boilerplate #5122

Merged
merged 17 commits into from
Apr 2, 2024
Merged
Show file tree
Hide file tree
Changes from 5 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: 0 additions & 1 deletion .github/workflows/test-suite.yml
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,6 @@ jobs:
run: |
make
- name: Install lcli
if: env.SELF_HOSTED_RUNNERS == 'false'
macladson marked this conversation as resolved.
Show resolved Hide resolved
run: make install-lcli
- name: Run the doppelganger protection failure test script
run: |
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ PROFILE ?= release

# List of all hard forks. This list is used to set env variables for several tests so that
# they run for different forks.
FORKS=phase0 altair merge capella deneb
FORKS=phase0 altair merge capella deneb electra

# Extra flags for Cargo
CARGO_INSTALL_EXTRA_FLAGS?=
Expand Down
3 changes: 2 additions & 1 deletion beacon_node/beacon_chain/src/attestation_rewards.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
BeaconState::Altair(_)
| BeaconState::Merge(_)
| BeaconState::Capella(_)
| BeaconState::Deneb(_) => self.compute_attestation_rewards_altair(state, validators),
| BeaconState::Deneb(_)
| BeaconState::Electra(_) => self.compute_attestation_rewards_altair(state, validators),
}
}

Expand Down
2 changes: 1 addition & 1 deletion beacon_node/beacon_chain/src/attestation_verification.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1059,7 +1059,7 @@ pub fn verify_propagation_slot_range<S: SlotClock, E: EthSpec>(
let earliest_permissible_slot = match current_fork {
ForkName::Base | ForkName::Altair | ForkName::Merge | ForkName::Capella => one_epoch_prior,
// EIP-7045
ForkName::Deneb => one_epoch_prior
ForkName::Deneb | ForkName::Electra => one_epoch_prior
.epoch(E::slots_per_epoch())
.start_slot(E::slots_per_epoch()),
};
Expand Down
16 changes: 11 additions & 5 deletions beacon_node/beacon_chain/src/beacon_block_streamer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ use types::{
SignedBlindedBeaconBlock, Slot,
};
use types::{
ExecutionPayload, ExecutionPayloadCapella, ExecutionPayloadHeader, ExecutionPayloadMerge,
ExecutionPayload, ExecutionPayloadCapella, ExecutionPayloadElectra, ExecutionPayloadHeader,
ExecutionPayloadMerge,
};

#[derive(PartialEq)]
Expand Down Expand Up @@ -98,6 +99,7 @@ fn reconstruct_default_header_block<E: EthSpec>(
ForkName::Merge => ExecutionPayloadMerge::default().into(),
ForkName::Capella => ExecutionPayloadCapella::default().into(),
ForkName::Deneb => ExecutionPayloadDeneb::default().into(),
ForkName::Electra => ExecutionPayloadElectra::default().into(),
ForkName::Base | ForkName::Altair => {
return Err(Error::PayloadReconstruction(format!(
"Block with fork variant {} has execution payload",
Expand Down Expand Up @@ -715,19 +717,21 @@ mod tests {
}

#[tokio::test]
async fn check_all_blocks_from_altair_to_deneb() {
async fn check_all_blocks_from_altair_to_electra() {
let slots_per_epoch = MinimalEthSpec::slots_per_epoch() as usize;
let num_epochs = 8;
let num_epochs = 10;
let bellatrix_fork_epoch = 2usize;
let capella_fork_epoch = 4usize;
let deneb_fork_epoch = 6usize;
let electra_fork_epoch = 8usize;
let num_blocks_produced = num_epochs * slots_per_epoch;

let mut spec = test_spec::<MinimalEthSpec>();
spec.altair_fork_epoch = Some(Epoch::new(0));
spec.bellatrix_fork_epoch = Some(Epoch::new(bellatrix_fork_epoch as u64));
spec.capella_fork_epoch = Some(Epoch::new(capella_fork_epoch as u64));
spec.deneb_fork_epoch = Some(Epoch::new(deneb_fork_epoch as u64));
spec.electra_fork_epoch = Some(Epoch::new(electra_fork_epoch as u64));

let harness = get_harness(VALIDATOR_COUNT, spec.clone());
// go to bellatrix fork
Expand Down Expand Up @@ -836,19 +840,21 @@ mod tests {
}

#[tokio::test]
async fn check_fallback_altair_to_deneb() {
async fn check_fallback_altair_to_electra() {
let slots_per_epoch = MinimalEthSpec::slots_per_epoch() as usize;
let num_epochs = 8;
let num_epochs = 10;
let bellatrix_fork_epoch = 2usize;
let capella_fork_epoch = 4usize;
let deneb_fork_epoch = 6usize;
let electra_fork_epoch = 8usize;
let num_blocks_produced = num_epochs * slots_per_epoch;

let mut spec = test_spec::<MinimalEthSpec>();
spec.altair_fork_epoch = Some(Epoch::new(0));
spec.bellatrix_fork_epoch = Some(Epoch::new(bellatrix_fork_epoch as u64));
spec.capella_fork_epoch = Some(Epoch::new(capella_fork_epoch as u64));
spec.deneb_fork_epoch = Some(Epoch::new(deneb_fork_epoch as u64));
spec.electra_fork_epoch = Some(Epoch::new(electra_fork_epoch as u64));

let harness = get_harness(VALIDATOR_COUNT, spec);

Expand Down
50 changes: 46 additions & 4 deletions beacon_node/beacon_chain/src/beacon_chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4860,7 +4860,10 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
// allows it to run concurrently with things like attestation packing.
let prepare_payload_handle = match &state {
BeaconState::Base(_) | BeaconState::Altair(_) => None,
BeaconState::Merge(_) | BeaconState::Capella(_) | BeaconState::Deneb(_) => {
BeaconState::Merge(_)
| BeaconState::Capella(_)
| BeaconState::Deneb(_)
| BeaconState::Electra(_) => {
let prepare_payload_handle = get_execution_payload(
self.clone(),
&state,
Expand Down Expand Up @@ -5221,6 +5224,41 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
execution_payload_value,
)
}
BeaconState::Electra(_) => {
let (payload, kzg_commitments, maybe_blobs_and_proofs, execution_payload_value) =
block_contents
.ok_or(BlockProductionError::MissingExecutionPayload)?
.deconstruct();

(
BeaconBlock::Electra(BeaconBlockElectra {
slot,
proposer_index,
parent_root,
state_root: Hash256::zero(),
body: BeaconBlockBodyElectra {
randao_reveal,
eth1_data,
graffiti,
proposer_slashings: proposer_slashings.into(),
attester_slashings: attester_slashings.into(),
attestations: attestations.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)?,
},
}),
maybe_blobs_and_proofs,
execution_payload_value,
)
}
};

let block = SignedBeaconBlock::from_block(
Expand Down Expand Up @@ -5544,7 +5582,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
let prepare_slot_fork = self.spec.fork_name_at_slot::<T::EthSpec>(prepare_slot);
let withdrawals = match prepare_slot_fork {
ForkName::Base | ForkName::Altair | ForkName::Merge => None,
ForkName::Capella | ForkName::Deneb => {
ForkName::Capella | ForkName::Deneb | ForkName::Electra => {
let chain = self.clone();
self.spawn_blocking_handle(
move || {
Expand All @@ -5559,7 +5597,9 @@ impl<T: BeaconChainTypes> BeaconChain<T> {

let parent_beacon_block_root = match prepare_slot_fork {
ForkName::Base | ForkName::Altair | ForkName::Merge | ForkName::Capella => None,
ForkName::Deneb => Some(pre_payload_attributes.parent_beacon_block_root),
ForkName::Deneb | ForkName::Electra => {
Some(pre_payload_attributes.parent_beacon_block_root)
}
};

let payload_attributes = PayloadAttributes::new(
Expand Down Expand Up @@ -6600,7 +6640,9 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
.map(|bootstrap| Some((bootstrap, fork_name)))
.map_err(Error::LightClientError)
}
ForkName::Base | ForkName::Capella | ForkName::Deneb => Err(Error::UnsupportedFork),
ForkName::Base | ForkName::Capella | ForkName::Deneb | ForkName::Electra => {
Err(Error::UnsupportedFork)
}
}
}
}
Expand Down
122 changes: 122 additions & 0 deletions beacon_node/beacon_chain/src/electra_readiness.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
//! 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_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 Electra fork when we will start issuing warnings about preparation.
use super::merge_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 V2 engine api methods
V2MethodsNotSupported { error: String },
dapplion marked this conversation as resolved.
Show resolved Hide resolved
/// 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::V2MethodsNotSupported { 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_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 {
ElectraReadiness::Ready
} else {
ElectraReadiness::V2MethodsNotSupported {
error: missing_methods,
}
}
}
}
} else {
ElectraReadiness::NoExecutionEndpoint
}
}
}
4 changes: 2 additions & 2 deletions beacon_node/beacon_chain/src/execution_payload.rs
Original file line number Diff line number Diff line change
Expand Up @@ -419,15 +419,15 @@ pub fn get_execution_payload<T: BeaconChainTypes>(
let latest_execution_payload_header_block_hash =
state.latest_execution_payload_header()?.block_hash();
let withdrawals = match state {
&BeaconState::Capella(_) | &BeaconState::Deneb(_) => {
&BeaconState::Capella(_) | &BeaconState::Deneb(_) | &BeaconState::Electra(_) => {
Some(get_expected_withdrawals(state, spec)?.into())
}
&BeaconState::Merge(_) => None,
// These shouldn't happen but they're here to make the pattern irrefutable
&BeaconState::Base(_) | &BeaconState::Altair(_) => None,
};
let parent_beacon_block_root = match state {
BeaconState::Deneb(_) => Some(parent_block_root),
BeaconState::Deneb(_) | BeaconState::Electra(_) => Some(parent_block_root),
BeaconState::Merge(_) | BeaconState::Capella(_) => None,
// These shouldn't happen but they're here to make the pattern irrefutable
BeaconState::Base(_) | BeaconState::Altair(_) => None,
Expand Down
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 chain_config;
pub mod data_availability_checker;
pub mod deneb_readiness;
mod early_attester_cache;
pub mod electra_readiness;
mod errors;
pub mod eth1_chain;
mod eth1_finalization_cache;
Expand Down
12 changes: 10 additions & 2 deletions beacon_node/beacon_chain/src/test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -580,6 +580,9 @@ pub fn mock_execution_layer_from_parts<T: EthSpec>(
let cancun_time = spec.deneb_fork_epoch.map(|epoch| {
HARNESS_GENESIS_TIME + spec.seconds_per_slot * T::slots_per_epoch() * epoch.as_u64()
});
let prague_time = spec.electra_fork_epoch.map(|epoch| {
HARNESS_GENESIS_TIME + spec.seconds_per_slot * T::slots_per_epoch() * epoch.as_u64()
});

let trusted_setup: TrustedSetup = serde_json::from_reader(TRUSTED_SETUP_BYTES)
.map_err(|e| format!("Unable to read trusted setup file: {}", e))
Expand All @@ -591,6 +594,7 @@ pub fn mock_execution_layer_from_parts<T: EthSpec>(
DEFAULT_TERMINAL_BLOCK,
shanghai_time,
cancun_time,
prague_time,
Some(JwtKey::from_slice(&DEFAULT_JWT_SECRET).unwrap()),
spec.clone(),
Some(kzg),
Expand Down Expand Up @@ -878,7 +882,9 @@ where
| SignedBeaconBlock::Altair(_)
| SignedBeaconBlock::Merge(_)
| SignedBeaconBlock::Capella(_) => (signed_block, None),
SignedBeaconBlock::Deneb(_) => (signed_block, block_response.blob_items),
SignedBeaconBlock::Deneb(_) | SignedBeaconBlock::Electra(_) => {
(signed_block, block_response.blob_items)
}
};

(block_contents, block_response.state)
Expand Down Expand Up @@ -940,7 +946,9 @@ where
| SignedBeaconBlock::Altair(_)
| SignedBeaconBlock::Merge(_)
| SignedBeaconBlock::Capella(_) => (signed_block, None),
SignedBeaconBlock::Deneb(_) => (signed_block, block_response.blob_items),
SignedBeaconBlock::Deneb(_) | SignedBeaconBlock::Electra(_) => {
(signed_block, block_response.blob_items)
}
};
(block_contents, pre_state)
}
Expand Down
4 changes: 2 additions & 2 deletions beacon_node/beacon_chain/tests/attestation_verification.rs
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,7 @@ impl GossipTester {
E::slots_per_epoch() + 1
}
// EIP-7045
ForkName::Deneb => {
ForkName::Deneb | ForkName::Electra => {
let epoch_slot_offset = (self.slot() % E::slots_per_epoch()).as_u64();
if epoch_slot_offset != 0 {
E::slots_per_epoch() + epoch_slot_offset
Expand Down Expand Up @@ -1235,7 +1235,7 @@ async fn attestation_to_finalized_block() {
.chain
.verify_unaggregated_attestation_for_gossip(&attestation, Some(subnet_id));
assert!(
matches!(res, Err(AttnError:: HeadBlockFinalized { beacon_block_root })
matches!(res, Err(AttnError::HeadBlockFinalized { beacon_block_root })
if beacon_block_root == earlier_block_root
)
);
Expand Down
Loading
Loading