Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,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 bellatrix capella deneb electra
FORKS=phase0 altair bellatrix capella deneb electra fulu

# 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::Bellatrix(_)
| BeaconState::Capella(_)
| BeaconState::Deneb(_)
| BeaconState::Electra(_) => self.compute_attestation_rewards_altair(state, validators),
| BeaconState::Electra(_)
| BeaconState::Fulu(_) => self.compute_attestation_rewards_altair(state, validators),
}
}

Expand Down
5 changes: 4 additions & 1 deletion beacon_node/beacon_chain/src/beacon_block_streamer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use types::{
};
use types::{
ExecutionPayload, ExecutionPayloadBellatrix, ExecutionPayloadCapella, ExecutionPayloadElectra,
ExecutionPayloadHeader,
ExecutionPayloadFulu, ExecutionPayloadHeader,
};

#[derive(PartialEq)]
Expand Down Expand Up @@ -99,6 +99,7 @@ fn reconstruct_default_header_block<E: EthSpec>(
ForkName::Capella => ExecutionPayloadCapella::default().into(),
ForkName::Deneb => ExecutionPayloadDeneb::default().into(),
ForkName::Electra => ExecutionPayloadElectra::default().into(),
ForkName::Fulu => ExecutionPayloadFulu::default().into(),
ForkName::Base | ForkName::Altair => {
return Err(Error::PayloadReconstruction(format!(
"Block with fork variant {} has execution payload",
Expand Down Expand Up @@ -749,6 +750,7 @@ mod tests {
let capella_fork_epoch = 4usize;
let deneb_fork_epoch = 6usize;
let electra_fork_epoch = 8usize;
let fulu_fork_epoch = 10usize;
let num_blocks_produced = num_epochs * slots_per_epoch;

let mut spec = test_spec::<MinimalEthSpec>();
Expand All @@ -757,6 +759,7 @@ mod tests {
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));
spec.fulu_fork_epoch = Some(Epoch::new(fulu_fork_epoch as u64));
let spec = Arc::new(spec);

let harness = get_harness(VALIDATOR_COUNT, spec.clone());
Expand Down
45 changes: 44 additions & 1 deletion beacon_node/beacon_chain/src/beacon_chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5322,7 +5322,8 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
BeaconState::Bellatrix(_)
| BeaconState::Capella(_)
| BeaconState::Deneb(_)
| BeaconState::Electra(_) => {
| BeaconState::Electra(_)
| BeaconState::Fulu(_) => {
let prepare_payload_handle = get_execution_payload(
self.clone(),
&state,
Expand Down Expand Up @@ -5751,6 +5752,48 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
execution_payload_value,
)
}
BeaconState::Fulu(_) => {
let (
payload,
kzg_commitments,
maybe_blobs_and_proofs,
maybe_requests,
execution_payload_value,
) = block_contents
.ok_or(BlockProductionError::MissingExecutionPayload)?
.deconstruct();

(
BeaconBlock::Fulu(BeaconBlockFulu {
slot,
proposer_index,
parent_root,
state_root: Hash256::zero(),
body: BeaconBlockBodyFulu {
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(
Expand Down
11 changes: 7 additions & 4 deletions beacon_node/beacon_chain/src/execution_payload.rs
Original file line number Diff line number Diff line change
Expand Up @@ -379,15 +379,18 @@ pub fn get_execution_payload<T: BeaconChainTypes>(
let latest_execution_payload_header_block_hash = latest_execution_payload_header.block_hash();
let latest_execution_payload_header_gas_limit = latest_execution_payload_header.gas_limit();
let withdrawals = match state {
&BeaconState::Capella(_) | &BeaconState::Deneb(_) | &BeaconState::Electra(_) => {
Some(get_expected_withdrawals(state, spec)?.0.into())
}
&BeaconState::Capella(_)
| &BeaconState::Deneb(_)
| &BeaconState::Electra(_)
| &BeaconState::Fulu(_) => Some(get_expected_withdrawals(state, spec)?.0.into()),
&BeaconState::Bellatrix(_) => 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(_) | BeaconState::Electra(_) => Some(parent_block_root),
BeaconState::Deneb(_) | BeaconState::Electra(_) | BeaconState::Fulu(_) => {
Some(parent_block_root)
}
BeaconState::Bellatrix(_) | BeaconState::Capella(_) => None,
// These shouldn't happen but they're here to make the pattern irrefutable
BeaconState::Base(_) | BeaconState::Altair(_) => None,
Expand Down
114 changes: 114 additions & 0 deletions beacon_node/beacon_chain/src/fulu_readiness.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
//! 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_V5};
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 V4 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_v5 {
missing_methods.push(' ');
missing_methods.push_str(ENGINE_NEW_PAYLOAD_V5);
all_good = false;
}

if all_good {
FuluReadiness::Ready
} else {
FuluReadiness::V5MethodsNotSupported {
error: missing_methods,
}
}
}
}
} else {
FuluReadiness::NoExecutionEndpoint
}
}
}
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 @@ -31,6 +31,7 @@ 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;
mod head_tracker;
pub mod historical_blocks;
Expand Down
38 changes: 32 additions & 6 deletions beacon_node/beacon_chain/src/test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -501,6 +501,9 @@ where
spec.electra_fork_epoch.map(|epoch| {
genesis_time + spec.seconds_per_slot * E::slots_per_epoch() * epoch.as_u64()
});
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()
});

self
}
Expand Down Expand Up @@ -623,6 +626,9 @@ pub fn mock_execution_layer_from_parts<E: EthSpec>(
let prague_time = spec.electra_fork_epoch.map(|epoch| {
HARNESS_GENESIS_TIME + spec.seconds_per_slot * E::slots_per_epoch() * epoch.as_u64()
});
let osaka_time = spec.fulu_fork_epoch.map(|epoch| {
HARNESS_GENESIS_TIME + spec.seconds_per_slot * E::slots_per_epoch() * epoch.as_u64()
});

let kzg = get_kzg(spec);

Expand All @@ -632,6 +638,7 @@ pub fn mock_execution_layer_from_parts<E: EthSpec>(
shanghai_time,
cancun_time,
prague_time,
osaka_time,
Some(JwtKey::from_slice(&DEFAULT_JWT_SECRET).unwrap()),
spec.clone(),
Some(kzg),
Expand Down Expand Up @@ -918,9 +925,9 @@ where
| SignedBeaconBlock::Altair(_)
| SignedBeaconBlock::Bellatrix(_)
| SignedBeaconBlock::Capella(_) => (signed_block, None),
SignedBeaconBlock::Deneb(_) | SignedBeaconBlock::Electra(_) => {
(signed_block, block_response.blob_items)
}
SignedBeaconBlock::Deneb(_)
| SignedBeaconBlock::Electra(_)
| SignedBeaconBlock::Fulu(_) => (signed_block, block_response.blob_items),
};

(block_contents, block_response.state)
Expand Down Expand Up @@ -982,9 +989,9 @@ where
| SignedBeaconBlock::Altair(_)
| SignedBeaconBlock::Bellatrix(_)
| SignedBeaconBlock::Capella(_) => (signed_block, None),
SignedBeaconBlock::Deneb(_) | SignedBeaconBlock::Electra(_) => {
(signed_block, block_response.blob_items)
}
SignedBeaconBlock::Deneb(_)
| SignedBeaconBlock::Electra(_)
| SignedBeaconBlock::Fulu(_) => (signed_block, block_response.blob_items),
};
(block_contents, pre_state)
}
Expand Down Expand Up @@ -2863,6 +2870,25 @@ pub fn generate_rand_block_and_blobs<E: EthSpec>(
message.body.blob_kzg_commitments = bundle.commitments.clone();
bundle
}
SignedBeaconBlock::Fulu(SignedBeaconBlockFulu {
ref mut message, ..
}) => {
// Get either zero blobs or a random number of blobs between 1 and Max Blobs.
let payload: &mut FullPayloadFulu<E> = &mut message.body.execution_payload;
let num_blobs = match num_blobs {
NumBlobs::Random => rng.gen_range(1..=E::max_blobs_per_block()),
NumBlobs::Number(n) => n,
NumBlobs::None => 0,
};
let (bundle, transactions) =
execution_layer::test_utils::generate_blobs::<E>(num_blobs).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),
};

Expand Down
9 changes: 9 additions & 0 deletions beacon_node/beacon_chain/tests/block_verification.rs
Original file line number Diff line number Diff line change
Expand Up @@ -754,6 +754,11 @@ async fn invalid_signature_attester_slashing() {
.push(attester_slashing.as_electra().unwrap().clone())
.expect("should update attester slashing");
}
BeaconBlockBodyRefMut::Fulu(ref mut blk) => {
blk.attester_slashings
.push(attester_slashing.as_electra().unwrap().clone())
.expect("should update attester slashing");
}
}
snapshots[block_index].beacon_block =
Arc::new(SignedBeaconBlock::from_block(block, signature));
Expand Down Expand Up @@ -809,6 +814,10 @@ async fn invalid_signature_attestation() {
.attestations
.get_mut(0)
.map(|att| att.signature = junk_aggregate_signature()),
BeaconBlockBodyRefMut::Fulu(ref mut blk) => blk
.attestations
.get_mut(0)
.map(|att| att.signature = junk_aggregate_signature()),
};

if block.body().attestations_len() > 0 {
Expand Down
1 change: 1 addition & 0 deletions beacon_node/beacon_chain/tests/validator_monitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,7 @@ async fn produces_missed_blocks() {
ForkName::Capella => 11,
ForkName::Deneb => 3,
ForkName::Electra => 1,
ForkName::Fulu => 5,
};

let harness2 = get_harness(validator_count, vec![validator_index_to_monitor]);
Expand Down
Loading