Skip to content

Commit f8fdb71

Browse files
authored
Add Electra fork boilerplate (#5122)
* Add Electra fork boilerplate * Remove electra from spec tests * Fix tests * Remove sneaky log file * Fix more tests * Fix even more tests and add suggestions * Remove unrelated lcli addition * Update more tests * Merge branch 'unstable' into electra * Add comment for test-suite lcli override * Merge branch 'unstable' into electra * Cleanup * Merge branch 'unstable' into electra * Apply suggestions * Merge branch 'unstable' into electra * Merge sigp/unstable into electra * Merge branch 'unstable' into electra
1 parent 3058b96 commit f8fdb71

File tree

105 files changed

+2079
-405
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

105 files changed

+2079
-405
lines changed

.github/workflows/test-suite.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,7 @@ jobs:
309309
run: |
310310
make
311311
- name: Install lcli
312-
# TODO: uncomment after the version of lcli in https://github.com/sigp/lighthouse/pull/5137
312+
# TODO: uncomment after the version of lcli in https://github.com/sigp/lighthouse/pull/5137
313313
# is installed on the runners
314314
# if: env.SELF_HOSTED_RUNNERS == 'false'
315315
run: make install-lcli

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ PROFILE ?= release
3939

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

4444
# Extra flags for Cargo
4545
CARGO_INSTALL_EXTRA_FLAGS?=

beacon_node/beacon_chain/src/attestation_rewards.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,8 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
5757
BeaconState::Altair(_)
5858
| BeaconState::Merge(_)
5959
| BeaconState::Capella(_)
60-
| BeaconState::Deneb(_) => self.compute_attestation_rewards_altair(state, validators),
60+
| BeaconState::Deneb(_)
61+
| BeaconState::Electra(_) => self.compute_attestation_rewards_altair(state, validators),
6162
}
6263
}
6364

beacon_node/beacon_chain/src/attestation_verification.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1065,7 +1065,7 @@ pub fn verify_propagation_slot_range<S: SlotClock, E: EthSpec>(
10651065
let earliest_permissible_slot = match current_fork {
10661066
ForkName::Base | ForkName::Altair | ForkName::Merge | ForkName::Capella => one_epoch_prior,
10671067
// EIP-7045
1068-
ForkName::Deneb => one_epoch_prior
1068+
ForkName::Deneb | ForkName::Electra => one_epoch_prior
10691069
.epoch(E::slots_per_epoch())
10701070
.start_slot(E::slots_per_epoch()),
10711071
};

beacon_node/beacon_chain/src/beacon_block_streamer.rs

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ use types::{
1515
SignedBlindedBeaconBlock, Slot,
1616
};
1717
use types::{
18-
ExecutionPayload, ExecutionPayloadCapella, ExecutionPayloadHeader, ExecutionPayloadMerge,
18+
ExecutionPayload, ExecutionPayloadCapella, ExecutionPayloadElectra, ExecutionPayloadHeader,
19+
ExecutionPayloadMerge,
1920
};
2021

2122
#[derive(PartialEq)]
@@ -98,6 +99,7 @@ fn reconstruct_default_header_block<E: EthSpec>(
9899
ForkName::Merge => ExecutionPayloadMerge::default().into(),
99100
ForkName::Capella => ExecutionPayloadCapella::default().into(),
100101
ForkName::Deneb => ExecutionPayloadDeneb::default().into(),
102+
ForkName::Electra => ExecutionPayloadElectra::default().into(),
101103
ForkName::Base | ForkName::Altair => {
102104
return Err(Error::PayloadReconstruction(format!(
103105
"Block with fork variant {} has execution payload",
@@ -712,19 +714,21 @@ mod tests {
712714
}
713715

714716
#[tokio::test]
715-
async fn check_all_blocks_from_altair_to_deneb() {
717+
async fn check_all_blocks_from_altair_to_electra() {
716718
let slots_per_epoch = MinimalEthSpec::slots_per_epoch() as usize;
717-
let num_epochs = 8;
719+
let num_epochs = 10;
718720
let bellatrix_fork_epoch = 2usize;
719721
let capella_fork_epoch = 4usize;
720722
let deneb_fork_epoch = 6usize;
723+
let electra_fork_epoch = 8usize;
721724
let num_blocks_produced = num_epochs * slots_per_epoch;
722725

723726
let mut spec = test_spec::<MinimalEthSpec>();
724727
spec.altair_fork_epoch = Some(Epoch::new(0));
725728
spec.bellatrix_fork_epoch = Some(Epoch::new(bellatrix_fork_epoch as u64));
726729
spec.capella_fork_epoch = Some(Epoch::new(capella_fork_epoch as u64));
727730
spec.deneb_fork_epoch = Some(Epoch::new(deneb_fork_epoch as u64));
731+
spec.electra_fork_epoch = Some(Epoch::new(electra_fork_epoch as u64));
728732

729733
let harness = get_harness(VALIDATOR_COUNT, spec.clone());
730734
// go to bellatrix fork
@@ -833,19 +837,21 @@ mod tests {
833837
}
834838

835839
#[tokio::test]
836-
async fn check_fallback_altair_to_deneb() {
840+
async fn check_fallback_altair_to_electra() {
837841
let slots_per_epoch = MinimalEthSpec::slots_per_epoch() as usize;
838-
let num_epochs = 8;
842+
let num_epochs = 10;
839843
let bellatrix_fork_epoch = 2usize;
840844
let capella_fork_epoch = 4usize;
841845
let deneb_fork_epoch = 6usize;
846+
let electra_fork_epoch = 8usize;
842847
let num_blocks_produced = num_epochs * slots_per_epoch;
843848

844849
let mut spec = test_spec::<MinimalEthSpec>();
845850
spec.altair_fork_epoch = Some(Epoch::new(0));
846851
spec.bellatrix_fork_epoch = Some(Epoch::new(bellatrix_fork_epoch as u64));
847852
spec.capella_fork_epoch = Some(Epoch::new(capella_fork_epoch as u64));
848853
spec.deneb_fork_epoch = Some(Epoch::new(deneb_fork_epoch as u64));
854+
spec.electra_fork_epoch = Some(Epoch::new(electra_fork_epoch as u64));
849855

850856
let harness = get_harness(VALIDATOR_COUNT, spec);
851857

beacon_node/beacon_chain/src/beacon_chain.rs

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4923,7 +4923,10 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
49234923
// allows it to run concurrently with things like attestation packing.
49244924
let prepare_payload_handle = match &state {
49254925
BeaconState::Base(_) | BeaconState::Altair(_) => None,
4926-
BeaconState::Merge(_) | BeaconState::Capella(_) | BeaconState::Deneb(_) => {
4926+
BeaconState::Merge(_)
4927+
| BeaconState::Capella(_)
4928+
| BeaconState::Deneb(_)
4929+
| BeaconState::Electra(_) => {
49274930
let prepare_payload_handle = get_execution_payload(
49284931
self.clone(),
49294932
&state,
@@ -5284,6 +5287,41 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
52845287
execution_payload_value,
52855288
)
52865289
}
5290+
BeaconState::Electra(_) => {
5291+
let (payload, kzg_commitments, maybe_blobs_and_proofs, execution_payload_value) =
5292+
block_contents
5293+
.ok_or(BlockProductionError::MissingExecutionPayload)?
5294+
.deconstruct();
5295+
5296+
(
5297+
BeaconBlock::Electra(BeaconBlockElectra {
5298+
slot,
5299+
proposer_index,
5300+
parent_root,
5301+
state_root: Hash256::zero(),
5302+
body: BeaconBlockBodyElectra {
5303+
randao_reveal,
5304+
eth1_data,
5305+
graffiti,
5306+
proposer_slashings: proposer_slashings.into(),
5307+
attester_slashings: attester_slashings.into(),
5308+
attestations: attestations.into(),
5309+
deposits: deposits.into(),
5310+
voluntary_exits: voluntary_exits.into(),
5311+
sync_aggregate: sync_aggregate
5312+
.ok_or(BlockProductionError::MissingSyncAggregate)?,
5313+
execution_payload: payload
5314+
.try_into()
5315+
.map_err(|_| BlockProductionError::InvalidPayloadFork)?,
5316+
bls_to_execution_changes: bls_to_execution_changes.into(),
5317+
blob_kzg_commitments: kzg_commitments
5318+
.ok_or(BlockProductionError::InvalidPayloadFork)?,
5319+
},
5320+
}),
5321+
maybe_blobs_and_proofs,
5322+
execution_payload_value,
5323+
)
5324+
}
52875325
};
52885326

52895327
let block = SignedBeaconBlock::from_block(
@@ -5607,7 +5645,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
56075645
let prepare_slot_fork = self.spec.fork_name_at_slot::<T::EthSpec>(prepare_slot);
56085646
let withdrawals = match prepare_slot_fork {
56095647
ForkName::Base | ForkName::Altair | ForkName::Merge => None,
5610-
ForkName::Capella | ForkName::Deneb => {
5648+
ForkName::Capella | ForkName::Deneb | ForkName::Electra => {
56115649
let chain = self.clone();
56125650
self.spawn_blocking_handle(
56135651
move || {
@@ -5622,7 +5660,9 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
56225660

56235661
let parent_beacon_block_root = match prepare_slot_fork {
56245662
ForkName::Base | ForkName::Altair | ForkName::Merge | ForkName::Capella => None,
5625-
ForkName::Deneb => Some(pre_payload_attributes.parent_beacon_block_root),
5663+
ForkName::Deneb | ForkName::Electra => {
5664+
Some(pre_payload_attributes.parent_beacon_block_root)
5665+
}
56265666
};
56275667

56285668
let payload_attributes = PayloadAttributes::new(
@@ -6662,7 +6702,11 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
66626702
.map_err(Error::InconsistentFork)?;
66636703

66646704
match fork_name {
6665-
ForkName::Altair | ForkName::Merge | ForkName::Capella | ForkName::Deneb => {
6705+
ForkName::Altair
6706+
| ForkName::Merge
6707+
| ForkName::Capella
6708+
| ForkName::Deneb
6709+
| ForkName::Electra => {
66666710
LightClientBootstrap::from_beacon_state(&mut state, &block, &self.spec)
66676711
.map(|bootstrap| Some((bootstrap, fork_name)))
66686712
.map_err(Error::LightClientError)
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
//! Provides tools for checking if a node is ready for the Electra upgrade and following merge
2+
//! transition.
3+
4+
use crate::{BeaconChain, BeaconChainTypes};
5+
use execution_layer::http::{
6+
ENGINE_FORKCHOICE_UPDATED_V3, ENGINE_GET_PAYLOAD_V3, ENGINE_NEW_PAYLOAD_V3,
7+
};
8+
use serde::{Deserialize, Serialize};
9+
use std::fmt;
10+
use std::time::Duration;
11+
use types::*;
12+
13+
/// The time before the Electra fork when we will start issuing warnings about preparation.
14+
use super::merge_readiness::SECONDS_IN_A_WEEK;
15+
pub const ELECTRA_READINESS_PREPARATION_SECONDS: u64 = SECONDS_IN_A_WEEK * 2;
16+
pub const ENGINE_CAPABILITIES_REFRESH_INTERVAL: u64 = 300;
17+
18+
#[derive(Debug, Serialize, Deserialize)]
19+
#[serde(rename_all = "snake_case")]
20+
#[serde(tag = "type")]
21+
pub enum ElectraReadiness {
22+
/// The execution engine is electra-enabled (as far as we can tell)
23+
Ready,
24+
/// We are connected to an execution engine which doesn't support the V3 engine api methods
25+
V3MethodsNotSupported { error: String },
26+
/// The transition configuration with the EL failed, there might be a problem with
27+
/// connectivity, authentication or a difference in configuration.
28+
ExchangeCapabilitiesFailed { error: String },
29+
/// The user has not configured an execution endpoint
30+
NoExecutionEndpoint,
31+
}
32+
33+
impl fmt::Display for ElectraReadiness {
34+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
35+
match self {
36+
ElectraReadiness::Ready => {
37+
write!(f, "This node appears ready for Electra.")
38+
}
39+
ElectraReadiness::ExchangeCapabilitiesFailed { error } => write!(
40+
f,
41+
"Could not exchange capabilities with the \
42+
execution endpoint: {}",
43+
error
44+
),
45+
ElectraReadiness::NoExecutionEndpoint => write!(
46+
f,
47+
"The --execution-endpoint flag is not specified, this is a \
48+
requirement post-merge"
49+
),
50+
ElectraReadiness::V3MethodsNotSupported { error } => write!(
51+
f,
52+
"Execution endpoint does not support Electra methods: {}",
53+
error
54+
),
55+
}
56+
}
57+
}
58+
59+
impl<T: BeaconChainTypes> BeaconChain<T> {
60+
/// Returns `true` if electra epoch is set and Electra fork has occurred or will
61+
/// occur within `ELECTRA_READINESS_PREPARATION_SECONDS`
62+
pub fn is_time_to_prepare_for_electra(&self, current_slot: Slot) -> bool {
63+
if let Some(electra_epoch) = self.spec.electra_fork_epoch {
64+
let electra_slot = electra_epoch.start_slot(T::EthSpec::slots_per_epoch());
65+
let electra_readiness_preparation_slots =
66+
ELECTRA_READINESS_PREPARATION_SECONDS / self.spec.seconds_per_slot;
67+
// Return `true` if Electra has happened or is within the preparation time.
68+
current_slot + electra_readiness_preparation_slots > electra_slot
69+
} else {
70+
// The Electra fork epoch has not been defined yet, no need to prepare.
71+
false
72+
}
73+
}
74+
75+
/// Attempts to connect to the EL and confirm that it is ready for electra.
76+
pub async fn check_electra_readiness(&self) -> ElectraReadiness {
77+
if let Some(el) = self.execution_layer.as_ref() {
78+
match el
79+
.get_engine_capabilities(Some(Duration::from_secs(
80+
ENGINE_CAPABILITIES_REFRESH_INTERVAL,
81+
)))
82+
.await
83+
{
84+
Err(e) => {
85+
// The EL was either unreachable or responded with an error
86+
ElectraReadiness::ExchangeCapabilitiesFailed {
87+
error: format!("{:?}", e),
88+
}
89+
}
90+
Ok(capabilities) => {
91+
// TODO(electra): Update in the event we get V4s.
92+
let mut missing_methods = String::from("Required Methods Unsupported:");
93+
let mut all_good = true;
94+
if !capabilities.get_payload_v3 {
95+
missing_methods.push(' ');
96+
missing_methods.push_str(ENGINE_GET_PAYLOAD_V3);
97+
all_good = false;
98+
}
99+
if !capabilities.forkchoice_updated_v3 {
100+
missing_methods.push(' ');
101+
missing_methods.push_str(ENGINE_FORKCHOICE_UPDATED_V3);
102+
all_good = false;
103+
}
104+
if !capabilities.new_payload_v3 {
105+
missing_methods.push(' ');
106+
missing_methods.push_str(ENGINE_NEW_PAYLOAD_V3);
107+
all_good = false;
108+
}
109+
110+
if all_good {
111+
ElectraReadiness::Ready
112+
} else {
113+
ElectraReadiness::V3MethodsNotSupported {
114+
error: missing_methods,
115+
}
116+
}
117+
}
118+
}
119+
} else {
120+
ElectraReadiness::NoExecutionEndpoint
121+
}
122+
}
123+
}

beacon_node/beacon_chain/src/execution_payload.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -412,15 +412,15 @@ pub fn get_execution_payload<T: BeaconChainTypes>(
412412
let latest_execution_payload_header_block_hash =
413413
state.latest_execution_payload_header()?.block_hash();
414414
let withdrawals = match state {
415-
&BeaconState::Capella(_) | &BeaconState::Deneb(_) => {
415+
&BeaconState::Capella(_) | &BeaconState::Deneb(_) | &BeaconState::Electra(_) => {
416416
Some(get_expected_withdrawals(state, spec)?.into())
417417
}
418418
&BeaconState::Merge(_) => None,
419419
// These shouldn't happen but they're here to make the pattern irrefutable
420420
&BeaconState::Base(_) | &BeaconState::Altair(_) => None,
421421
};
422422
let parent_beacon_block_root = match state {
423-
BeaconState::Deneb(_) => Some(parent_block_root),
423+
BeaconState::Deneb(_) | BeaconState::Electra(_) => Some(parent_block_root),
424424
BeaconState::Merge(_) | BeaconState::Capella(_) => None,
425425
// These shouldn't happen but they're here to make the pattern irrefutable
426426
BeaconState::Base(_) | BeaconState::Altair(_) => None,

beacon_node/beacon_chain/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ pub mod chain_config;
2020
pub mod data_availability_checker;
2121
pub mod deneb_readiness;
2222
mod early_attester_cache;
23+
pub mod electra_readiness;
2324
mod errors;
2425
pub mod eth1_chain;
2526
mod eth1_finalization_cache;

0 commit comments

Comments
 (0)