diff --git a/Makefile b/Makefile index 8faf8a2e54b..4d95f50c5ce 100644 --- a/Makefile +++ b/Makefile @@ -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?= diff --git a/beacon_node/beacon_chain/src/attestation_rewards.rs b/beacon_node/beacon_chain/src/attestation_rewards.rs index 87b7384ea68..3b37b09e402 100644 --- a/beacon_node/beacon_chain/src/attestation_rewards.rs +++ b/beacon_node/beacon_chain/src/attestation_rewards.rs @@ -51,13 +51,10 @@ impl BeaconChain { .get_state(&state_root, Some(state_slot))? .ok_or(BeaconChainError::MissingBeaconState(state_root))?; - match state { - BeaconState::Base(_) => self.compute_attestation_rewards_base(state, validators), - BeaconState::Altair(_) - | BeaconState::Bellatrix(_) - | BeaconState::Capella(_) - | BeaconState::Deneb(_) - | BeaconState::Electra(_) => self.compute_attestation_rewards_altair(state, validators), + if state.fork_name_unchecked().altair_enabled() { + self.compute_attestation_rewards_altair(state, validators) + } else { + self.compute_attestation_rewards_base(state, validators) } } diff --git a/beacon_node/beacon_chain/src/beacon_block_streamer.rs b/beacon_node/beacon_chain/src/beacon_block_streamer.rs index b76dba88fd0..32ec7768680 100644 --- a/beacon_node/beacon_chain/src/beacon_block_streamer.rs +++ b/beacon_node/beacon_chain/src/beacon_block_streamer.rs @@ -15,7 +15,7 @@ use types::{ }; use types::{ ExecutionPayload, ExecutionPayloadBellatrix, ExecutionPayloadCapella, ExecutionPayloadElectra, - ExecutionPayloadHeader, + ExecutionPayloadFulu, ExecutionPayloadHeader, }; #[derive(PartialEq)] @@ -99,6 +99,7 @@ fn reconstruct_default_header_block( 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", @@ -742,13 +743,14 @@ mod tests { } #[tokio::test] - async fn check_all_blocks_from_altair_to_electra() { + async fn check_all_blocks_from_altair_to_fulu() { let slots_per_epoch = MinimalEthSpec::slots_per_epoch() as usize; - let num_epochs = 10; + let num_epochs = 12; let bellatrix_fork_epoch = 2usize; 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::(); @@ -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()); diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index 7bbb9ff74d6..d84cd9615aa 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -5317,23 +5317,19 @@ impl BeaconChain { // If required, start the process of loading an execution payload from the EL early. This // allows it to run concurrently with things like attestation packing. - let prepare_payload_handle = match &state { - BeaconState::Base(_) | BeaconState::Altair(_) => None, - BeaconState::Bellatrix(_) - | BeaconState::Capella(_) - | BeaconState::Deneb(_) - | BeaconState::Electra(_) => { - let prepare_payload_handle = get_execution_payload( - self.clone(), - &state, - parent_root, - proposer_index, - builder_params, - builder_boost_factor, - block_production_version, - )?; - Some(prepare_payload_handle) - } + let prepare_payload_handle = if state.fork_name_unchecked().bellatrix_enabled() { + let prepare_payload_handle = get_execution_payload( + self.clone(), + &state, + parent_root, + proposer_index, + builder_params, + builder_boost_factor, + block_production_version, + )?; + Some(prepare_payload_handle) + } else { + None }; let (mut proposer_slashings, mut attester_slashings, mut voluntary_exits) = @@ -5751,6 +5747,48 @@ impl BeaconChain { 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( diff --git a/beacon_node/beacon_chain/src/execution_payload.rs b/beacon_node/beacon_chain/src/execution_payload.rs index 502a7918a11..720f98e2986 100644 --- a/beacon_node/beacon_chain/src/execution_payload.rs +++ b/beacon_node/beacon_chain/src/execution_payload.rs @@ -374,19 +374,15 @@ pub fn get_execution_payload( let latest_execution_payload_header = state.latest_execution_payload_header()?; 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::Bellatrix(_) => None, - // These shouldn't happen but they're here to make the pattern irrefutable - &BeaconState::Base(_) | &BeaconState::Altair(_) => None, + let withdrawals = if state.fork_name_unchecked().capella_enabled() { + Some(get_expected_withdrawals(state, spec)?.0.into()) + } else { + None }; - let parent_beacon_block_root = match state { - BeaconState::Deneb(_) | BeaconState::Electra(_) => 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, + let parent_beacon_block_root = if state.fork_name_unchecked().deneb_enabled() { + Some(parent_block_root) + } else { + None }; // Spawn a task to obtain the execution payload from the EL via a series of async calls. The diff --git a/beacon_node/beacon_chain/src/fulu_readiness.rs b/beacon_node/beacon_chain/src/fulu_readiness.rs new file mode 100644 index 00000000000..71494623f83 --- /dev/null +++ b/beacon_node/beacon_chain/src/fulu_readiness.rs @@ -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 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 BeaconChain { + /// 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 + } + } +} diff --git a/beacon_node/beacon_chain/src/lib.rs b/beacon_node/beacon_chain/src/lib.rs index d9728b9fd41..4783945eb15 100644 --- a/beacon_node/beacon_chain/src/lib.rs +++ b/beacon_node/beacon_chain/src/lib.rs @@ -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; diff --git a/beacon_node/beacon_chain/src/test_utils.rs b/beacon_node/beacon_chain/src/test_utils.rs index 093ee0c44b4..d37398e4e02 100644 --- a/beacon_node/beacon_chain/src/test_utils.rs +++ b/beacon_node/beacon_chain/src/test_utils.rs @@ -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 } @@ -623,6 +626,9 @@ pub fn mock_execution_layer_from_parts( 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); @@ -632,6 +638,7 @@ pub fn mock_execution_layer_from_parts( shanghai_time, cancun_time, prague_time, + osaka_time, Some(JwtKey::from_slice(&DEFAULT_JWT_SECRET).unwrap()), spec.clone(), Some(kzg), @@ -913,15 +920,12 @@ where &self.spec, )); - let block_contents: SignedBlockContentsTuple = match *signed_block { - SignedBeaconBlock::Base(_) - | SignedBeaconBlock::Altair(_) - | SignedBeaconBlock::Bellatrix(_) - | SignedBeaconBlock::Capella(_) => (signed_block, None), - SignedBeaconBlock::Deneb(_) | SignedBeaconBlock::Electra(_) => { + let block_contents: SignedBlockContentsTuple = + if signed_block.fork_name_unchecked().deneb_enabled() { (signed_block, block_response.blob_items) - } - }; + } else { + (signed_block, None) + }; (block_contents, block_response.state) } @@ -977,15 +981,12 @@ where &self.spec, )); - let block_contents: SignedBlockContentsTuple = match *signed_block { - SignedBeaconBlock::Base(_) - | SignedBeaconBlock::Altair(_) - | SignedBeaconBlock::Bellatrix(_) - | SignedBeaconBlock::Capella(_) => (signed_block, None), - SignedBeaconBlock::Deneb(_) | SignedBeaconBlock::Electra(_) => { + let block_contents: SignedBlockContentsTuple = + if signed_block.fork_name_unchecked().deneb_enabled() { (signed_block, block_response.blob_items) - } - }; + } else { + (signed_block, None) + }; (block_contents, pre_state) } @@ -2863,6 +2864,25 @@ pub fn generate_rand_block_and_blobs( 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 = &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::(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), }; diff --git a/beacon_node/beacon_chain/tests/block_verification.rs b/beacon_node/beacon_chain/tests/block_verification.rs index f094a173eec..103734b2247 100644 --- a/beacon_node/beacon_chain/tests/block_verification.rs +++ b/beacon_node/beacon_chain/tests/block_verification.rs @@ -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)); @@ -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 { diff --git a/beacon_node/beacon_chain/tests/store_tests.rs b/beacon_node/beacon_chain/tests/store_tests.rs index e1258ccdea7..ed97b8d6345 100644 --- a/beacon_node/beacon_chain/tests/store_tests.rs +++ b/beacon_node/beacon_chain/tests/store_tests.rs @@ -147,6 +147,7 @@ async fn light_client_bootstrap_test() { LightClientBootstrap::Capella(lc_bootstrap) => lc_bootstrap.header.beacon.slot, LightClientBootstrap::Deneb(lc_bootstrap) => lc_bootstrap.header.beacon.slot, LightClientBootstrap::Electra(lc_bootstrap) => lc_bootstrap.header.beacon.slot, + LightClientBootstrap::Fulu(lc_bootstrap) => lc_bootstrap.header.beacon.slot, }; assert_eq!( diff --git a/beacon_node/beacon_chain/tests/validator_monitor.rs b/beacon_node/beacon_chain/tests/validator_monitor.rs index b4a54d26676..91de4fe2702 100644 --- a/beacon_node/beacon_chain/tests/validator_monitor.rs +++ b/beacon_node/beacon_chain/tests/validator_monitor.rs @@ -214,6 +214,7 @@ async fn produces_missed_blocks() { ForkName::Capella => 11, ForkName::Deneb => 3, ForkName::Electra => 1, + ForkName::Fulu => 6, }; let harness2 = get_harness(validator_count, vec![validator_index_to_monitor]); diff --git a/beacon_node/client/src/notifier.rs b/beacon_node/client/src/notifier.rs index e88803e94f3..0c3b1578d6e 100644 --- a/beacon_node/client/src/notifier.rs +++ b/beacon_node/client/src/notifier.rs @@ -4,6 +4,7 @@ use beacon_chain::{ capella_readiness::CapellaReadiness, deneb_readiness::DenebReadiness, electra_readiness::ElectraReadiness, + fulu_readiness::FuluReadiness, BeaconChain, BeaconChainTypes, ExecutionStatus, }; use lighthouse_network::{types::SyncState, NetworkGlobals}; @@ -315,6 +316,7 @@ pub fn spawn_notifier( capella_readiness_logging(current_slot, &beacon_chain, &log).await; deneb_readiness_logging(current_slot, &beacon_chain, &log).await; electra_readiness_logging(current_slot, &beacon_chain, &log).await; + fulu_readiness_logging(current_slot, &beacon_chain, &log).await; } }; @@ -586,6 +588,62 @@ async fn electra_readiness_logging( } } +/// Provides some helpful logging to users to indicate if their node is ready for Fulu. +async fn fulu_readiness_logging( + current_slot: Slot, + beacon_chain: &BeaconChain, + log: &Logger, +) { + let fulu_completed = beacon_chain + .canonical_head + .cached_head() + .snapshot + .beacon_state + .fork_name_unchecked() + .fulu_enabled(); + + let has_execution_layer = beacon_chain.execution_layer.is_some(); + + if fulu_completed && has_execution_layer + || !beacon_chain.is_time_to_prepare_for_fulu(current_slot) + { + return; + } + + if fulu_completed && !has_execution_layer { + error!( + log, + "Execution endpoint required"; + "info" => "you need a Fulu enabled execution engine to validate blocks." + ); + return; + } + + match beacon_chain.check_fulu_readiness().await { + FuluReadiness::Ready => { + info!( + log, + "Ready for Fulu"; + "info" => "ensure the execution endpoint is updated to the latest Fulu release" + ) + } + readiness @ FuluReadiness::ExchangeCapabilitiesFailed { error: _ } => { + error!( + log, + "Not ready for Fulu"; + "hint" => "the execution endpoint may be offline", + "info" => %readiness, + ) + } + readiness => warn!( + log, + "Not ready for Fulu"; + "hint" => "try updating the execution endpoint", + "info" => %readiness, + ), + } +} + async fn genesis_execution_payload_logging( beacon_chain: &BeaconChain, log: &Logger, diff --git a/beacon_node/execution_layer/src/engine_api.rs b/beacon_node/execution_layer/src/engine_api.rs index 083aaf2e258..b9d878b1f86 100644 --- a/beacon_node/execution_layer/src/engine_api.rs +++ b/beacon_node/execution_layer/src/engine_api.rs @@ -3,8 +3,8 @@ use crate::http::{ ENGINE_FORKCHOICE_UPDATED_V1, ENGINE_FORKCHOICE_UPDATED_V2, ENGINE_FORKCHOICE_UPDATED_V3, ENGINE_GET_BLOBS_V1, ENGINE_GET_CLIENT_VERSION_V1, ENGINE_GET_PAYLOAD_BODIES_BY_HASH_V1, ENGINE_GET_PAYLOAD_BODIES_BY_RANGE_V1, ENGINE_GET_PAYLOAD_V1, ENGINE_GET_PAYLOAD_V2, - ENGINE_GET_PAYLOAD_V3, ENGINE_GET_PAYLOAD_V4, ENGINE_NEW_PAYLOAD_V1, ENGINE_NEW_PAYLOAD_V2, - ENGINE_NEW_PAYLOAD_V3, ENGINE_NEW_PAYLOAD_V4, + ENGINE_GET_PAYLOAD_V3, ENGINE_GET_PAYLOAD_V4, ENGINE_GET_PAYLOAD_V5, ENGINE_NEW_PAYLOAD_V1, + ENGINE_NEW_PAYLOAD_V2, ENGINE_NEW_PAYLOAD_V3, ENGINE_NEW_PAYLOAD_V4, ENGINE_NEW_PAYLOAD_V5, }; use eth2::types::{ BlobsBundle, SsePayloadAttributes, SsePayloadAttributesV1, SsePayloadAttributesV2, @@ -24,7 +24,7 @@ pub use types::{ }; use types::{ ExecutionPayloadBellatrix, ExecutionPayloadCapella, ExecutionPayloadDeneb, - ExecutionPayloadElectra, ExecutionRequests, KzgProofs, + ExecutionPayloadElectra, ExecutionPayloadFulu, ExecutionRequests, KzgProofs, }; use types::{Graffiti, GRAFFITI_BYTES_LEN}; @@ -35,7 +35,7 @@ mod new_payload_request; pub use new_payload_request::{ NewPayloadRequest, NewPayloadRequestBellatrix, NewPayloadRequestCapella, - NewPayloadRequestDeneb, NewPayloadRequestElectra, + NewPayloadRequestDeneb, NewPayloadRequestElectra, NewPayloadRequestFulu, }; pub const LATEST_TAG: &str = "latest"; @@ -261,7 +261,7 @@ pub struct ProposeBlindedBlockResponse { } #[superstruct( - variants(Bellatrix, Capella, Deneb, Electra), + variants(Bellatrix, Capella, Deneb, Electra, Fulu), variant_attributes(derive(Clone, Debug, PartialEq),), map_into(ExecutionPayload), map_ref_into(ExecutionPayloadRef), @@ -281,12 +281,14 @@ pub struct GetPayloadResponse { pub execution_payload: ExecutionPayloadDeneb, #[superstruct(only(Electra), partial_getter(rename = "execution_payload_electra"))] pub execution_payload: ExecutionPayloadElectra, + #[superstruct(only(Fulu), partial_getter(rename = "execution_payload_fulu"))] + pub execution_payload: ExecutionPayloadFulu, pub block_value: Uint256, - #[superstruct(only(Deneb, Electra))] + #[superstruct(only(Deneb, Electra, Fulu))] pub blobs_bundle: BlobsBundle, - #[superstruct(only(Deneb, Electra), partial_getter(copy))] + #[superstruct(only(Deneb, Electra, Fulu), partial_getter(copy))] pub should_override_builder: bool, - #[superstruct(only(Electra))] + #[superstruct(only(Electra, Fulu))] pub requests: ExecutionRequests, } @@ -354,6 +356,12 @@ impl From> Some(inner.blobs_bundle), Some(inner.requests), ), + GetPayloadResponse::Fulu(inner) => ( + ExecutionPayload::Fulu(inner.execution_payload), + inner.block_value, + Some(inner.blobs_bundle), + Some(inner.requests), + ), } } } @@ -487,6 +495,34 @@ impl ExecutionPayloadBodyV1 { )) } } + ExecutionPayloadHeader::Fulu(header) => { + if let Some(withdrawals) = self.withdrawals { + Ok(ExecutionPayload::Fulu(ExecutionPayloadFulu { + parent_hash: header.parent_hash, + fee_recipient: header.fee_recipient, + state_root: header.state_root, + receipts_root: header.receipts_root, + logs_bloom: header.logs_bloom, + prev_randao: header.prev_randao, + block_number: header.block_number, + gas_limit: header.gas_limit, + gas_used: header.gas_used, + timestamp: header.timestamp, + extra_data: header.extra_data, + base_fee_per_gas: header.base_fee_per_gas, + block_hash: header.block_hash, + transactions: self.transactions, + withdrawals, + blob_gas_used: header.blob_gas_used, + excess_blob_gas: header.excess_blob_gas, + })) + } else { + Err(format!( + "block {} is post capella but payload body doesn't have withdrawals", + header.block_hash + )) + } + } } } } @@ -497,6 +533,7 @@ pub struct EngineCapabilities { pub new_payload_v2: bool, pub new_payload_v3: bool, pub new_payload_v4: bool, + pub new_payload_v5: bool, pub forkchoice_updated_v1: bool, pub forkchoice_updated_v2: bool, pub forkchoice_updated_v3: bool, @@ -506,6 +543,7 @@ pub struct EngineCapabilities { pub get_payload_v2: bool, pub get_payload_v3: bool, pub get_payload_v4: bool, + pub get_payload_v5: bool, pub get_client_version_v1: bool, pub get_blobs_v1: bool, } @@ -525,6 +563,9 @@ impl EngineCapabilities { if self.new_payload_v4 { response.push(ENGINE_NEW_PAYLOAD_V4); } + if self.new_payload_v5 { + response.push(ENGINE_NEW_PAYLOAD_V5); + } if self.forkchoice_updated_v1 { response.push(ENGINE_FORKCHOICE_UPDATED_V1); } @@ -552,6 +593,9 @@ impl EngineCapabilities { if self.get_payload_v4 { response.push(ENGINE_GET_PAYLOAD_V4); } + if self.get_payload_v5 { + response.push(ENGINE_GET_PAYLOAD_V5); + } if self.get_client_version_v1 { response.push(ENGINE_GET_CLIENT_VERSION_V1); } diff --git a/beacon_node/execution_layer/src/engine_api/http.rs b/beacon_node/execution_layer/src/engine_api/http.rs index e2a81c072c4..1fd9f81d46f 100644 --- a/beacon_node/execution_layer/src/engine_api/http.rs +++ b/beacon_node/execution_layer/src/engine_api/http.rs @@ -35,12 +35,14 @@ pub const ENGINE_NEW_PAYLOAD_V1: &str = "engine_newPayloadV1"; pub const ENGINE_NEW_PAYLOAD_V2: &str = "engine_newPayloadV2"; pub const ENGINE_NEW_PAYLOAD_V3: &str = "engine_newPayloadV3"; pub const ENGINE_NEW_PAYLOAD_V4: &str = "engine_newPayloadV4"; +pub const ENGINE_NEW_PAYLOAD_V5: &str = "engine_newPayloadV5"; pub const ENGINE_NEW_PAYLOAD_TIMEOUT: Duration = Duration::from_secs(8); pub const ENGINE_GET_PAYLOAD_V1: &str = "engine_getPayloadV1"; pub const ENGINE_GET_PAYLOAD_V2: &str = "engine_getPayloadV2"; pub const ENGINE_GET_PAYLOAD_V3: &str = "engine_getPayloadV3"; pub const ENGINE_GET_PAYLOAD_V4: &str = "engine_getPayloadV4"; +pub const ENGINE_GET_PAYLOAD_V5: &str = "engine_getPayloadV5"; pub const ENGINE_GET_PAYLOAD_TIMEOUT: Duration = Duration::from_secs(2); pub const ENGINE_FORKCHOICE_UPDATED_V1: &str = "engine_forkchoiceUpdatedV1"; @@ -72,10 +74,12 @@ pub static LIGHTHOUSE_CAPABILITIES: &[&str] = &[ ENGINE_NEW_PAYLOAD_V2, ENGINE_NEW_PAYLOAD_V3, ENGINE_NEW_PAYLOAD_V4, + ENGINE_NEW_PAYLOAD_V5, ENGINE_GET_PAYLOAD_V1, ENGINE_GET_PAYLOAD_V2, ENGINE_GET_PAYLOAD_V3, ENGINE_GET_PAYLOAD_V4, + ENGINE_GET_PAYLOAD_V5, ENGINE_FORKCHOICE_UPDATED_V1, ENGINE_FORKCHOICE_UPDATED_V2, ENGINE_FORKCHOICE_UPDATED_V3, @@ -825,6 +829,30 @@ impl HttpJsonRpc { Ok(response.into()) } + pub async fn new_payload_v5_fulu( + &self, + new_payload_request_fulu: NewPayloadRequestFulu<'_, E>, + ) -> Result { + let params = json!([ + JsonExecutionPayload::V5(new_payload_request_fulu.execution_payload.clone().into()), + new_payload_request_fulu.versioned_hashes, + new_payload_request_fulu.parent_beacon_block_root, + new_payload_request_fulu + .execution_requests + .get_execution_requests_list(), + ]); + + let response: JsonPayloadStatusV1 = self + .rpc_request( + ENGINE_NEW_PAYLOAD_V5, + params, + ENGINE_NEW_PAYLOAD_TIMEOUT * self.execution_timeout_multiplier, + ) + .await?; + + Ok(response.into()) + } + pub async fn get_payload_v1( &self, payload_id: PayloadId, @@ -880,9 +908,10 @@ impl HttpJsonRpc { .try_into() .map_err(Error::BadResponse) } - ForkName::Base | ForkName::Altair | ForkName::Deneb | ForkName::Electra => Err( - Error::UnsupportedForkVariant(format!("called get_payload_v2 with {}", fork_name)), - ), + _ => Err(Error::UnsupportedForkVariant(format!( + "called get_payload_v2 with {}", + fork_name + ))), } } @@ -906,11 +935,7 @@ impl HttpJsonRpc { .try_into() .map_err(Error::BadResponse) } - ForkName::Base - | ForkName::Altair - | ForkName::Bellatrix - | ForkName::Capella - | ForkName::Electra => Err(Error::UnsupportedForkVariant(format!( + _ => Err(Error::UnsupportedForkVariant(format!( "called get_payload_v3 with {}", fork_name ))), @@ -937,17 +962,40 @@ impl HttpJsonRpc { .try_into() .map_err(Error::BadResponse) } - ForkName::Base - | ForkName::Altair - | ForkName::Bellatrix - | ForkName::Capella - | ForkName::Deneb => Err(Error::UnsupportedForkVariant(format!( + _ => Err(Error::UnsupportedForkVariant(format!( "called get_payload_v4 with {}", fork_name ))), } } + pub async fn get_payload_v5( + &self, + fork_name: ForkName, + payload_id: PayloadId, + ) -> Result, Error> { + let params = json!([JsonPayloadIdRequest::from(payload_id)]); + + match fork_name { + ForkName::Fulu => { + let response: JsonGetPayloadResponseV5 = self + .rpc_request( + ENGINE_GET_PAYLOAD_V5, + params, + ENGINE_GET_PAYLOAD_TIMEOUT * self.execution_timeout_multiplier, + ) + .await?; + JsonGetPayloadResponse::V5(response) + .try_into() + .map_err(Error::BadResponse) + } + _ => Err(Error::UnsupportedForkVariant(format!( + "called get_payload_v5 with {}", + fork_name + ))), + } + } + pub async fn forkchoice_updated_v1( &self, forkchoice_state: ForkchoiceState, @@ -1071,6 +1119,7 @@ impl HttpJsonRpc { new_payload_v2: capabilities.contains(ENGINE_NEW_PAYLOAD_V2), new_payload_v3: capabilities.contains(ENGINE_NEW_PAYLOAD_V3), new_payload_v4: capabilities.contains(ENGINE_NEW_PAYLOAD_V4), + new_payload_v5: capabilities.contains(ENGINE_NEW_PAYLOAD_V5), forkchoice_updated_v1: capabilities.contains(ENGINE_FORKCHOICE_UPDATED_V1), forkchoice_updated_v2: capabilities.contains(ENGINE_FORKCHOICE_UPDATED_V2), forkchoice_updated_v3: capabilities.contains(ENGINE_FORKCHOICE_UPDATED_V3), @@ -1082,6 +1131,7 @@ impl HttpJsonRpc { get_payload_v2: capabilities.contains(ENGINE_GET_PAYLOAD_V2), get_payload_v3: capabilities.contains(ENGINE_GET_PAYLOAD_V3), get_payload_v4: capabilities.contains(ENGINE_GET_PAYLOAD_V4), + get_payload_v5: capabilities.contains(ENGINE_GET_PAYLOAD_V5), get_client_version_v1: capabilities.contains(ENGINE_GET_CLIENT_VERSION_V1), get_blobs_v1: capabilities.contains(ENGINE_GET_BLOBS_V1), }) @@ -1212,6 +1262,13 @@ impl HttpJsonRpc { Err(Error::RequiredMethodUnsupported("engine_newPayloadV4")) } } + NewPayloadRequest::Fulu(new_payload_request_fulu) => { + if engine_capabilities.new_payload_v5 { + self.new_payload_v5_fulu(new_payload_request_fulu).await + } else { + Err(Error::RequiredMethodUnsupported("engine_newPayloadV5")) + } + } } } @@ -1247,6 +1304,13 @@ impl HttpJsonRpc { Err(Error::RequiredMethodUnsupported("engine_getPayloadv4")) } } + ForkName::Fulu => { + if engine_capabilities.get_payload_v5 { + self.get_payload_v5(fork_name, payload_id).await + } else { + Err(Error::RequiredMethodUnsupported("engine_getPayloadv5")) + } + } ForkName::Base | ForkName::Altair => Err(Error::UnsupportedForkVariant(format!( "called get_payload with {}", fork_name diff --git a/beacon_node/execution_layer/src/engine_api/json_structures.rs b/beacon_node/execution_layer/src/engine_api/json_structures.rs index 1c6639804e3..86acaaaf3bd 100644 --- a/beacon_node/execution_layer/src/engine_api/json_structures.rs +++ b/beacon_node/execution_layer/src/engine_api/json_structures.rs @@ -65,7 +65,7 @@ pub struct JsonPayloadIdResponse { } #[superstruct( - variants(V1, V2, V3, V4), + variants(V1, V2, V3, V4, V5), variant_attributes( derive(Debug, PartialEq, Default, Serialize, Deserialize,), serde(bound = "E: EthSpec", rename_all = "camelCase"), @@ -100,12 +100,12 @@ pub struct JsonExecutionPayload { pub block_hash: ExecutionBlockHash, #[serde(with = "ssz_types::serde_utils::list_of_hex_var_list")] pub transactions: Transactions, - #[superstruct(only(V2, V3, V4))] + #[superstruct(only(V2, V3, V4, V5))] pub withdrawals: VariableList, - #[superstruct(only(V3, V4))] + #[superstruct(only(V3, V4, V5))] #[serde(with = "serde_utils::u64_hex_be")] pub blob_gas_used: u64, - #[superstruct(only(V3, V4))] + #[superstruct(only(V3, V4, V5))] #[serde(with = "serde_utils::u64_hex_be")] pub excess_blob_gas: u64, } @@ -214,6 +214,35 @@ impl From> for JsonExecutionPayloadV4 } } +impl From> for JsonExecutionPayloadV5 { + fn from(payload: ExecutionPayloadFulu) -> Self { + JsonExecutionPayloadV5 { + parent_hash: payload.parent_hash, + fee_recipient: payload.fee_recipient, + state_root: payload.state_root, + receipts_root: payload.receipts_root, + logs_bloom: payload.logs_bloom, + prev_randao: payload.prev_randao, + block_number: payload.block_number, + gas_limit: payload.gas_limit, + gas_used: payload.gas_used, + timestamp: payload.timestamp, + extra_data: payload.extra_data, + base_fee_per_gas: payload.base_fee_per_gas, + block_hash: payload.block_hash, + transactions: payload.transactions, + withdrawals: payload + .withdrawals + .into_iter() + .map(Into::into) + .collect::>() + .into(), + blob_gas_used: payload.blob_gas_used, + excess_blob_gas: payload.excess_blob_gas, + } + } +} + impl From> for JsonExecutionPayload { fn from(execution_payload: ExecutionPayload) -> Self { match execution_payload { @@ -221,6 +250,7 @@ impl From> for JsonExecutionPayload { ExecutionPayload::Capella(payload) => JsonExecutionPayload::V2(payload.into()), ExecutionPayload::Deneb(payload) => JsonExecutionPayload::V3(payload.into()), ExecutionPayload::Electra(payload) => JsonExecutionPayload::V4(payload.into()), + ExecutionPayload::Fulu(payload) => JsonExecutionPayload::V5(payload.into()), } } } @@ -330,6 +360,35 @@ impl From> for ExecutionPayloadElectra } } +impl From> for ExecutionPayloadFulu { + fn from(payload: JsonExecutionPayloadV5) -> Self { + ExecutionPayloadFulu { + parent_hash: payload.parent_hash, + fee_recipient: payload.fee_recipient, + state_root: payload.state_root, + receipts_root: payload.receipts_root, + logs_bloom: payload.logs_bloom, + prev_randao: payload.prev_randao, + block_number: payload.block_number, + gas_limit: payload.gas_limit, + gas_used: payload.gas_used, + timestamp: payload.timestamp, + extra_data: payload.extra_data, + base_fee_per_gas: payload.base_fee_per_gas, + block_hash: payload.block_hash, + transactions: payload.transactions, + withdrawals: payload + .withdrawals + .into_iter() + .map(Into::into) + .collect::>() + .into(), + blob_gas_used: payload.blob_gas_used, + excess_blob_gas: payload.excess_blob_gas, + } + } +} + impl From> for ExecutionPayload { fn from(json_execution_payload: JsonExecutionPayload) -> Self { match json_execution_payload { @@ -337,6 +396,7 @@ impl From> for ExecutionPayload { JsonExecutionPayload::V2(payload) => ExecutionPayload::Capella(payload.into()), JsonExecutionPayload::V3(payload) => ExecutionPayload::Deneb(payload.into()), JsonExecutionPayload::V4(payload) => ExecutionPayload::Electra(payload.into()), + JsonExecutionPayload::V5(payload) => ExecutionPayload::Fulu(payload.into()), } } } @@ -389,7 +449,7 @@ impl TryFrom for ExecutionRequests { } #[superstruct( - variants(V1, V2, V3, V4), + variants(V1, V2, V3, V4, V5), variant_attributes( derive(Debug, PartialEq, Serialize, Deserialize), serde(bound = "E: EthSpec", rename_all = "camelCase") @@ -408,13 +468,15 @@ pub struct JsonGetPayloadResponse { pub execution_payload: JsonExecutionPayloadV3, #[superstruct(only(V4), partial_getter(rename = "execution_payload_v4"))] pub execution_payload: JsonExecutionPayloadV4, + #[superstruct(only(V5), partial_getter(rename = "execution_payload_v5"))] + pub execution_payload: JsonExecutionPayloadV5, #[serde(with = "serde_utils::u256_hex_be")] pub block_value: Uint256, - #[superstruct(only(V3, V4))] + #[superstruct(only(V3, V4, V5))] pub blobs_bundle: JsonBlobsBundleV1, - #[superstruct(only(V3, V4))] + #[superstruct(only(V3, V4, V5))] pub should_override_builder: bool, - #[superstruct(only(V4))] + #[superstruct(only(V4, V5))] pub execution_requests: JsonExecutionRequests, } @@ -451,6 +513,15 @@ impl TryFrom> for GetPayloadResponse { requests: response.execution_requests.try_into()?, })) } + JsonGetPayloadResponse::V5(response) => { + Ok(GetPayloadResponse::Fulu(GetPayloadResponseFulu { + execution_payload: response.execution_payload.into(), + block_value: response.block_value, + blobs_bundle: response.blobs_bundle.into(), + should_override_builder: response.should_override_builder, + requests: response.execution_requests.try_into()?, + })) + } } } } diff --git a/beacon_node/execution_layer/src/engine_api/new_payload_request.rs b/beacon_node/execution_layer/src/engine_api/new_payload_request.rs index 60bc8489744..a86b2fd9bbf 100644 --- a/beacon_node/execution_layer/src/engine_api/new_payload_request.rs +++ b/beacon_node/execution_layer/src/engine_api/new_payload_request.rs @@ -9,11 +9,11 @@ use types::{ }; use types::{ ExecutionPayloadBellatrix, ExecutionPayloadCapella, ExecutionPayloadDeneb, - ExecutionPayloadElectra, ExecutionRequests, + ExecutionPayloadElectra, ExecutionPayloadFulu, ExecutionRequests, }; #[superstruct( - variants(Bellatrix, Capella, Deneb, Electra), + variants(Bellatrix, Capella, Deneb, Electra, Fulu), variant_attributes(derive(Clone, Debug, PartialEq),), map_into(ExecutionPayload), map_ref_into(ExecutionPayloadRef), @@ -39,11 +39,13 @@ pub struct NewPayloadRequest<'block, E: EthSpec> { pub execution_payload: &'block ExecutionPayloadDeneb, #[superstruct(only(Electra), partial_getter(rename = "execution_payload_electra"))] pub execution_payload: &'block ExecutionPayloadElectra, - #[superstruct(only(Deneb, Electra))] + #[superstruct(only(Fulu), partial_getter(rename = "execution_payload_fulu"))] + pub execution_payload: &'block ExecutionPayloadFulu, + #[superstruct(only(Deneb, Electra, Fulu))] pub versioned_hashes: Vec, - #[superstruct(only(Deneb, Electra))] + #[superstruct(only(Deneb, Electra, Fulu))] pub parent_beacon_block_root: Hash256, - #[superstruct(only(Electra))] + #[superstruct(only(Electra, Fulu))] pub execution_requests: &'block ExecutionRequests, } @@ -54,6 +56,7 @@ impl<'block, E: EthSpec> NewPayloadRequest<'block, E> { Self::Capella(payload) => payload.execution_payload.parent_hash, Self::Deneb(payload) => payload.execution_payload.parent_hash, Self::Electra(payload) => payload.execution_payload.parent_hash, + Self::Fulu(payload) => payload.execution_payload.parent_hash, } } @@ -63,6 +66,7 @@ impl<'block, E: EthSpec> NewPayloadRequest<'block, E> { Self::Capella(payload) => payload.execution_payload.block_hash, Self::Deneb(payload) => payload.execution_payload.block_hash, Self::Electra(payload) => payload.execution_payload.block_hash, + Self::Fulu(payload) => payload.execution_payload.block_hash, } } @@ -72,6 +76,7 @@ impl<'block, E: EthSpec> NewPayloadRequest<'block, E> { Self::Capella(payload) => payload.execution_payload.block_number, Self::Deneb(payload) => payload.execution_payload.block_number, Self::Electra(payload) => payload.execution_payload.block_number, + Self::Fulu(payload) => payload.execution_payload.block_number, } } @@ -81,6 +86,7 @@ impl<'block, E: EthSpec> NewPayloadRequest<'block, E> { Self::Capella(request) => ExecutionPayloadRef::Capella(request.execution_payload), Self::Deneb(request) => ExecutionPayloadRef::Deneb(request.execution_payload), Self::Electra(request) => ExecutionPayloadRef::Electra(request.execution_payload), + Self::Fulu(request) => ExecutionPayloadRef::Fulu(request.execution_payload), } } @@ -92,6 +98,7 @@ impl<'block, E: EthSpec> NewPayloadRequest<'block, E> { Self::Capella(request) => ExecutionPayload::Capella(request.execution_payload.clone()), Self::Deneb(request) => ExecutionPayload::Deneb(request.execution_payload.clone()), Self::Electra(request) => ExecutionPayload::Electra(request.execution_payload.clone()), + Self::Fulu(request) => ExecutionPayload::Fulu(request.execution_payload.clone()), } } @@ -190,6 +197,17 @@ impl<'a, E: EthSpec> TryFrom> for NewPayloadRequest<'a, E> parent_beacon_block_root: block_ref.parent_root, execution_requests: &block_ref.body.execution_requests, })), + BeaconBlockRef::Fulu(block_ref) => Ok(Self::Fulu(NewPayloadRequestFulu { + execution_payload: &block_ref.body.execution_payload.execution_payload, + versioned_hashes: block_ref + .body + .blob_kzg_commitments + .iter() + .map(kzg_commitment_to_versioned_hash) + .collect(), + parent_beacon_block_root: block_ref.parent_root, + execution_requests: &block_ref.body.execution_requests, + })), } } } @@ -209,6 +227,7 @@ impl<'a, E: EthSpec> TryFrom> for NewPayloadRequest<' })), ExecutionPayloadRef::Deneb(_) => Err(Self::Error::IncorrectStateVariant), ExecutionPayloadRef::Electra(_) => Err(Self::Error::IncorrectStateVariant), + ExecutionPayloadRef::Fulu(_) => Err(Self::Error::IncorrectStateVariant), } } } diff --git a/beacon_node/execution_layer/src/lib.rs b/beacon_node/execution_layer/src/lib.rs index f3b12b21d17..118d7adfcaa 100644 --- a/beacon_node/execution_layer/src/lib.rs +++ b/beacon_node/execution_layer/src/lib.rs @@ -54,8 +54,8 @@ use types::{ }; use types::{ BeaconStateError, BlindedPayload, ChainSpec, Epoch, ExecPayload, ExecutionPayloadBellatrix, - ExecutionPayloadCapella, ExecutionPayloadElectra, FullPayload, ProposerPreparationData, - PublicKeyBytes, Signature, Slot, + ExecutionPayloadCapella, ExecutionPayloadElectra, ExecutionPayloadFulu, FullPayload, + ProposerPreparationData, PublicKeyBytes, Signature, Slot, }; mod block_hash; @@ -124,6 +124,14 @@ impl TryFrom> for ProvenancedPayload BlockProposalContents::PayloadAndBlobs { + payload: ExecutionPayloadHeader::Fulu(builder_bid.header).into(), + block_value: builder_bid.value, + kzg_commitments: builder_bid.blob_kzg_commitments, + blobs_and_proofs: None, + // TODO(fulu): update this with builder api returning the requests + requests: None, + }, }; Ok(ProvenancedPayload::Builder( BlockProposalContentsType::Blinded(block_proposal_contents), @@ -1821,6 +1829,7 @@ impl ExecutionLayer { 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::InvalidForkForPayload); } diff --git a/beacon_node/execution_layer/src/test_utils/execution_block_generator.rs b/beacon_node/execution_layer/src/test_utils/execution_block_generator.rs index 4fab7150ce3..2a39796707b 100644 --- a/beacon_node/execution_layer/src/test_utils/execution_block_generator.rs +++ b/beacon_node/execution_layer/src/test_utils/execution_block_generator.rs @@ -19,7 +19,7 @@ use tree_hash::TreeHash; use tree_hash_derive::TreeHash; use types::{ Blob, ChainSpec, EthSpec, ExecutionBlockHash, ExecutionPayload, ExecutionPayloadBellatrix, - ExecutionPayloadCapella, ExecutionPayloadDeneb, ExecutionPayloadElectra, + ExecutionPayloadCapella, ExecutionPayloadDeneb, ExecutionPayloadElectra, ExecutionPayloadFulu, ExecutionPayloadHeader, FixedBytesExtended, ForkName, Hash256, Transaction, Transactions, Uint256, }; @@ -147,6 +147,7 @@ pub struct ExecutionBlockGenerator { pub shanghai_time: Option, // capella pub cancun_time: Option, // deneb pub prague_time: Option, // electra + pub osaka_time: Option, // fulu /* * deneb stuff */ @@ -162,6 +163,7 @@ fn make_rng() -> Arc> { } impl ExecutionBlockGenerator { + #[allow(clippy::too_many_arguments)] pub fn new( terminal_total_difficulty: Uint256, terminal_block_number: u64, @@ -169,6 +171,7 @@ impl ExecutionBlockGenerator { shanghai_time: Option, cancun_time: Option, prague_time: Option, + osaka_time: Option, kzg: Option>, ) -> Self { let mut gen = Self { @@ -185,6 +188,7 @@ impl ExecutionBlockGenerator { shanghai_time, cancun_time, prague_time, + osaka_time, blobs_bundles: <_>::default(), kzg, rng: make_rng(), @@ -233,13 +237,16 @@ impl ExecutionBlockGenerator { } pub fn get_fork_at_timestamp(&self, timestamp: u64) -> ForkName { - match self.prague_time { - Some(fork_time) if timestamp >= fork_time => ForkName::Electra, - _ => match self.cancun_time { - Some(fork_time) if timestamp >= fork_time => ForkName::Deneb, - _ => match self.shanghai_time { - Some(fork_time) if timestamp >= fork_time => ForkName::Capella, - _ => ForkName::Bellatrix, + match self.osaka_time { + Some(fork_time) if timestamp >= fork_time => ForkName::Fulu, + _ => match self.prague_time { + Some(fork_time) if timestamp >= fork_time => ForkName::Electra, + _ => match self.cancun_time { + Some(fork_time) if timestamp >= fork_time => ForkName::Deneb, + _ => match self.shanghai_time { + Some(fork_time) if timestamp >= fork_time => ForkName::Capella, + _ => ForkName::Bellatrix, + }, }, }, } @@ -664,6 +671,25 @@ impl ExecutionBlockGenerator { blob_gas_used: 0, excess_blob_gas: 0, }), + ForkName::Fulu => ExecutionPayload::Fulu(ExecutionPayloadFulu { + parent_hash: head_block_hash, + fee_recipient: pa.suggested_fee_recipient, + receipts_root: Hash256::repeat_byte(42), + state_root: Hash256::repeat_byte(43), + logs_bloom: vec![0; 256].into(), + prev_randao: pa.prev_randao, + block_number: parent.block_number() + 1, + gas_limit: DEFAULT_GAS_LIMIT, + gas_used: GAS_USED, + timestamp: pa.timestamp, + extra_data: "block gen was here".as_bytes().to_vec().into(), + base_fee_per_gas: Uint256::from(1u64), + block_hash: ExecutionBlockHash::zero(), + transactions: vec![].into(), + withdrawals: pa.withdrawals.clone().into(), + blob_gas_used: 0, + excess_blob_gas: 0, + }), _ => unreachable!(), }, }; @@ -811,6 +837,12 @@ pub fn generate_genesis_header( *header.transactions_root_mut() = empty_transactions_root; Some(header) } + ForkName::Fulu => { + let mut header = ExecutionPayloadHeader::Fulu(<_>::default()); + *header.block_hash_mut() = genesis_block_hash.unwrap_or_default(); + *header.transactions_root_mut() = empty_transactions_root; + Some(header) + } } } @@ -883,6 +915,7 @@ mod test { None, None, None, + None, ); for i in 0..=TERMINAL_BLOCK { diff --git a/beacon_node/execution_layer/src/test_utils/handle_rpc.rs b/beacon_node/execution_layer/src/test_utils/handle_rpc.rs index 9365024ffb7..0babb9d1a3e 100644 --- a/beacon_node/execution_layer/src/test_utils/handle_rpc.rs +++ b/beacon_node/execution_layer/src/test_utils/handle_rpc.rs @@ -99,7 +99,8 @@ pub async fn handle_rpc( ENGINE_NEW_PAYLOAD_V1 | ENGINE_NEW_PAYLOAD_V2 | ENGINE_NEW_PAYLOAD_V3 - | ENGINE_NEW_PAYLOAD_V4 => { + | ENGINE_NEW_PAYLOAD_V4 + | ENGINE_NEW_PAYLOAD_V5 => { let request = match method { ENGINE_NEW_PAYLOAD_V1 => JsonExecutionPayload::V1( get_param::>(params, 0) @@ -121,6 +122,9 @@ pub async fn handle_rpc( ENGINE_NEW_PAYLOAD_V4 => get_param::>(params, 0) .map(|jep| JsonExecutionPayload::V4(jep)) .map_err(|s| (s, BAD_PARAMS_ERROR_CODE))?, + ENGINE_NEW_PAYLOAD_V5 => get_param::>(params, 0) + .map(|jep| JsonExecutionPayload::V5(jep)) + .map_err(|s| (s, BAD_PARAMS_ERROR_CODE))?, _ => unreachable!(), }; @@ -222,6 +226,54 @@ pub async fn handle_rpc( )); } } + ForkName::Fulu => { + if method == ENGINE_NEW_PAYLOAD_V1 + || method == ENGINE_NEW_PAYLOAD_V2 + || method == ENGINE_NEW_PAYLOAD_V3 + || method == ENGINE_NEW_PAYLOAD_V4 + { + return Err(( + format!("{} called after Fulu fork!", method), + GENERIC_ERROR_CODE, + )); + } + if matches!(request, JsonExecutionPayload::V1(_)) { + return Err(( + format!( + "{} called with `ExecutionPayloadV1` after Fulu fork!", + method + ), + GENERIC_ERROR_CODE, + )); + } + if matches!(request, JsonExecutionPayload::V2(_)) { + return Err(( + format!( + "{} called with `ExecutionPayloadV2` after Fulu fork!", + method + ), + GENERIC_ERROR_CODE, + )); + } + if matches!(request, JsonExecutionPayload::V3(_)) { + return Err(( + format!( + "{} called with `ExecutionPayloadV3` after Fulu fork!", + method + ), + GENERIC_ERROR_CODE, + )); + } + if matches!(request, JsonExecutionPayload::V4(_)) { + return Err(( + format!( + "{} called with `ExecutionPayloadV4` after Fulu fork!", + method + ), + GENERIC_ERROR_CODE, + )); + } + } _ => unreachable!(), }; @@ -260,7 +312,8 @@ pub async fn handle_rpc( ENGINE_GET_PAYLOAD_V1 | ENGINE_GET_PAYLOAD_V2 | ENGINE_GET_PAYLOAD_V3 - | ENGINE_GET_PAYLOAD_V4 => { + | ENGINE_GET_PAYLOAD_V4 + | ENGINE_GET_PAYLOAD_V5 => { let request: JsonPayloadIdRequest = get_param(params, 0).map_err(|s| (s, BAD_PARAMS_ERROR_CODE))?; let id = request.into(); @@ -320,6 +373,23 @@ pub async fn handle_rpc( )); } + // validate method called correctly according to fulu fork time + if ctx + .execution_block_generator + .read() + .get_fork_at_timestamp(response.timestamp()) + == ForkName::Fulu + && (method == ENGINE_GET_PAYLOAD_V1 + || method == ENGINE_GET_PAYLOAD_V2 + || method == ENGINE_GET_PAYLOAD_V3 + || method == ENGINE_GET_PAYLOAD_V4) + { + return Err(( + format!("{} called after Fulu fork!", method), + FORK_REQUEST_MISMATCH_ERROR_CODE, + )); + } + match method { ENGINE_GET_PAYLOAD_V1 => { Ok(serde_json::to_value(JsonExecutionPayload::from(response)).unwrap()) @@ -380,6 +450,24 @@ pub async fn handle_rpc( } _ => unreachable!(), }), + ENGINE_GET_PAYLOAD_V5 => Ok(match JsonExecutionPayload::from(response) { + JsonExecutionPayload::V5(execution_payload) => { + serde_json::to_value(JsonGetPayloadResponseV5 { + execution_payload, + block_value: Uint256::from(DEFAULT_MOCK_EL_PAYLOAD_VALUE_WEI), + blobs_bundle: maybe_blobs + .ok_or(( + "No blobs returned despite V5 Payload".to_string(), + GENERIC_ERROR_CODE, + ))? + .into(), + should_override_builder: false, + execution_requests: Default::default(), + }) + .unwrap() + } + _ => unreachable!(), + }), _ => unreachable!(), } } @@ -411,7 +499,10 @@ pub async fn handle_rpc( .map(|opt| opt.map(JsonPayloadAttributes::V1)) .transpose() } - ForkName::Capella | ForkName::Deneb | ForkName::Electra => { + ForkName::Capella + | ForkName::Deneb + | ForkName::Electra + | ForkName::Fulu => { get_param::>(params, 1) .map(|opt| opt.map(JsonPayloadAttributes::V2)) .transpose() @@ -475,7 +566,7 @@ pub async fn handle_rpc( )); } } - ForkName::Deneb | ForkName::Electra => { + ForkName::Deneb | ForkName::Electra | ForkName::Fulu => { if method == ENGINE_FORKCHOICE_UPDATED_V1 { return Err(( format!("{} called after Deneb fork!", method), diff --git a/beacon_node/execution_layer/src/test_utils/mock_builder.rs b/beacon_node/execution_layer/src/test_utils/mock_builder.rs index 879b54eb075..65181dcf4f2 100644 --- a/beacon_node/execution_layer/src/test_utils/mock_builder.rs +++ b/beacon_node/execution_layer/src/test_utils/mock_builder.rs @@ -16,7 +16,7 @@ use tempfile::NamedTempFile; use tree_hash::TreeHash; use types::builder_bid::{ BuilderBid, BuilderBidBellatrix, BuilderBidCapella, BuilderBidDeneb, BuilderBidElectra, - SignedBuilderBid, + BuilderBidFulu, SignedBuilderBid, }; use types::{ Address, BeaconState, ChainSpec, EthSpec, ExecPayload, ExecutionPayload, @@ -95,6 +95,9 @@ impl BidStuff for BuilderBid { ExecutionPayloadHeaderRefMut::Electra(header) => { header.fee_recipient = fee_recipient; } + ExecutionPayloadHeaderRefMut::Fulu(header) => { + header.fee_recipient = fee_recipient; + } } } @@ -112,6 +115,9 @@ impl BidStuff for BuilderBid { ExecutionPayloadHeaderRefMut::Electra(header) => { header.gas_limit = gas_limit; } + ExecutionPayloadHeaderRefMut::Fulu(header) => { + header.gas_limit = gas_limit; + } } } @@ -133,6 +139,9 @@ impl BidStuff for BuilderBid { ExecutionPayloadHeaderRefMut::Electra(header) => { header.parent_hash = ExecutionBlockHash::from_root(parent_hash); } + ExecutionPayloadHeaderRefMut::Fulu(header) => { + header.parent_hash = ExecutionBlockHash::from_root(parent_hash); + } } } @@ -150,6 +159,9 @@ impl BidStuff for BuilderBid { ExecutionPayloadHeaderRefMut::Electra(header) => { header.prev_randao = prev_randao; } + ExecutionPayloadHeaderRefMut::Fulu(header) => { + header.prev_randao = prev_randao; + } } } @@ -167,6 +179,9 @@ impl BidStuff for BuilderBid { ExecutionPayloadHeaderRefMut::Electra(header) => { header.block_number = block_number; } + ExecutionPayloadHeaderRefMut::Fulu(header) => { + header.block_number = block_number; + } } } @@ -184,6 +199,9 @@ impl BidStuff for BuilderBid { ExecutionPayloadHeaderRefMut::Electra(header) => { header.timestamp = timestamp; } + ExecutionPayloadHeaderRefMut::Fulu(header) => { + header.timestamp = timestamp; + } } } @@ -201,6 +219,9 @@ impl BidStuff for BuilderBid { ExecutionPayloadHeaderRefMut::Electra(header) => { header.withdrawals_root = withdrawals_root; } + ExecutionPayloadHeaderRefMut::Fulu(header) => { + header.withdrawals_root = withdrawals_root; + } } } @@ -230,6 +251,10 @@ impl BidStuff for BuilderBid { header.extra_data = extra_data; header.block_hash = ExecutionBlockHash::from_root(header.tree_hash_root()); } + ExecutionPayloadHeaderRefMut::Fulu(header) => { + header.extra_data = extra_data; + header.block_hash = ExecutionBlockHash::from_root(header.tree_hash_root()); + } } } } @@ -378,6 +403,9 @@ pub fn serve( SignedBlindedBeaconBlock::Electra(block) => { block.message.body.execution_payload.tree_hash_root() } + SignedBlindedBeaconBlock::Fulu(block) => { + block.message.body.execution_payload.tree_hash_root() + } }; let payload = builder .el @@ -536,7 +564,7 @@ pub fn serve( expected_withdrawals, None, ), - ForkName::Deneb | ForkName::Electra => PayloadAttributes::new( + ForkName::Deneb | ForkName::Electra | ForkName::Fulu => PayloadAttributes::new( timestamp, *prev_randao, fee_recipient, @@ -592,6 +620,17 @@ pub fn serve( ) = payload_response.into(); match fork { + ForkName::Fulu => BuilderBid::Fulu(BuilderBidFulu { + header: payload + .as_fulu() + .map_err(|_| reject("incorrect payload variant"))? + .into(), + blob_kzg_commitments: maybe_blobs_bundle + .map(|b| b.commitments) + .unwrap_or_default(), + value: Uint256::from(DEFAULT_BUILDER_PAYLOAD_VALUE_WEI), + pubkey: builder.builder_sk.public_key().compress(), + }), ForkName::Electra => BuilderBid::Electra(BuilderBidElectra { header: payload .as_electra() @@ -644,6 +683,17 @@ pub fn serve( Option>, ) = payload_response.into(); match fork { + ForkName::Fulu => BuilderBid::Fulu(BuilderBidFulu { + header: payload + .as_fulu() + .map_err(|_| reject("incorrect payload variant"))? + .into(), + blob_kzg_commitments: maybe_blobs_bundle + .map(|b| b.commitments) + .unwrap_or_default(), + value: Uint256::from(DEFAULT_BUILDER_PAYLOAD_VALUE_WEI), + pubkey: builder.builder_sk.public_key().compress(), + }), ForkName::Electra => BuilderBid::Electra(BuilderBidElectra { header: payload .as_electra() diff --git a/beacon_node/execution_layer/src/test_utils/mock_execution_layer.rs b/beacon_node/execution_layer/src/test_utils/mock_execution_layer.rs index dc90d91c0f2..9df8d9cc5cf 100644 --- a/beacon_node/execution_layer/src/test_utils/mock_execution_layer.rs +++ b/beacon_node/execution_layer/src/test_utils/mock_execution_layer.rs @@ -28,6 +28,7 @@ impl MockExecutionLayer { None, None, None, + None, Some(JwtKey::from_slice(&DEFAULT_JWT_SECRET).unwrap()), spec, None, @@ -41,6 +42,7 @@ impl MockExecutionLayer { shanghai_time: Option, cancun_time: Option, prague_time: Option, + osaka_time: Option, jwt_key: Option, spec: ChainSpec, kzg: Option>, @@ -57,6 +59,7 @@ impl MockExecutionLayer { shanghai_time, cancun_time, prague_time, + osaka_time, kzg, ); diff --git a/beacon_node/execution_layer/src/test_utils/mod.rs b/beacon_node/execution_layer/src/test_utils/mod.rs index faf6d4ef0b6..5934c069a2f 100644 --- a/beacon_node/execution_layer/src/test_utils/mod.rs +++ b/beacon_node/execution_layer/src/test_utils/mod.rs @@ -44,6 +44,7 @@ pub const DEFAULT_ENGINE_CAPABILITIES: EngineCapabilities = EngineCapabilities { new_payload_v2: true, new_payload_v3: true, new_payload_v4: true, + new_payload_v5: true, forkchoice_updated_v1: true, forkchoice_updated_v2: true, forkchoice_updated_v3: true, @@ -53,6 +54,7 @@ pub const DEFAULT_ENGINE_CAPABILITIES: EngineCapabilities = EngineCapabilities { get_payload_v2: true, get_payload_v3: true, get_payload_v4: true, + get_payload_v5: true, get_client_version_v1: true, get_blobs_v1: true, }; @@ -82,6 +84,7 @@ pub struct MockExecutionConfig { pub shanghai_time: Option, pub cancun_time: Option, pub prague_time: Option, + pub osaka_time: Option, } impl Default for MockExecutionConfig { @@ -95,6 +98,7 @@ impl Default for MockExecutionConfig { shanghai_time: None, cancun_time: None, prague_time: None, + osaka_time: None, } } } @@ -117,6 +121,7 @@ impl MockServer { None, // FIXME(capella): should this be the default? None, // FIXME(deneb): should this be the default? None, // FIXME(electra): should this be the default? + None, // FIXME(fulu): should this be the default? None, ) } @@ -135,6 +140,7 @@ impl MockServer { shanghai_time, cancun_time, prague_time, + osaka_time, } = config; let last_echo_request = Arc::new(RwLock::new(None)); let preloaded_responses = Arc::new(Mutex::new(vec![])); @@ -145,6 +151,7 @@ impl MockServer { shanghai_time, cancun_time, prague_time, + osaka_time, kzg, ); @@ -208,6 +215,7 @@ impl MockServer { shanghai_time: Option, cancun_time: Option, prague_time: Option, + osaka_time: Option, kzg: Option>, ) -> Self { Self::new_with_config( @@ -221,6 +229,7 @@ impl MockServer { shanghai_time, cancun_time, prague_time, + osaka_time, }, kzg, ) diff --git a/beacon_node/http_api/tests/broadcast_validation_tests.rs b/beacon_node/http_api/tests/broadcast_validation_tests.rs index e1ecf2d4fc3..8e0a51a32a2 100644 --- a/beacon_node/http_api/tests/broadcast_validation_tests.rs +++ b/beacon_node/http_api/tests/broadcast_validation_tests.rs @@ -75,7 +75,7 @@ pub async fn gossip_invalid() { let response: Result<(), eth2::Error> = tester .client - .post_beacon_blocks_v2(&PublishBlockRequest::new(block, blobs), validation_level) + .post_beacon_blocks_v2_ssz(&PublishBlockRequest::new(block, blobs), validation_level) .await; assert!(response.is_err()); @@ -124,7 +124,7 @@ pub async fn gossip_partial_pass() { let response: Result<(), eth2::Error> = tester .client - .post_beacon_blocks_v2(&PublishBlockRequest::new(block, blobs), validation_level) + .post_beacon_blocks_v2_ssz(&PublishBlockRequest::new(block, blobs), validation_level) .await; assert!(response.is_err()); @@ -165,7 +165,7 @@ pub async fn gossip_full_pass() { let response: Result<(), eth2::Error> = tester .client - .post_beacon_blocks_v2( + .post_beacon_blocks_v2_ssz( &PublishBlockRequest::new(block.clone(), blobs), validation_level, ) @@ -261,7 +261,7 @@ pub async fn consensus_invalid() { let response: Result<(), eth2::Error> = tester .client - .post_beacon_blocks_v2(&PublishBlockRequest::new(block, blobs), validation_level) + .post_beacon_blocks_v2_ssz(&PublishBlockRequest::new(block, blobs), validation_level) .await; assert!(response.is_err()); @@ -307,7 +307,7 @@ pub async fn consensus_gossip() { let response: Result<(), eth2::Error> = tester .client - .post_beacon_blocks_v2(&PublishBlockRequest::new(block, blobs), validation_level) + .post_beacon_blocks_v2_ssz(&PublishBlockRequest::new(block, blobs), validation_level) .await; assert!(response.is_err()); @@ -423,7 +423,7 @@ pub async fn consensus_full_pass() { let response: Result<(), eth2::Error> = tester .client - .post_beacon_blocks_v2( + .post_beacon_blocks_v2_ssz( &PublishBlockRequest::new(block.clone(), blobs), validation_level, ) @@ -475,7 +475,7 @@ pub async fn equivocation_invalid() { let response: Result<(), eth2::Error> = tester .client - .post_beacon_blocks_v2(&PublishBlockRequest::new(block, blobs), validation_level) + .post_beacon_blocks_v2_ssz(&PublishBlockRequest::new(block, blobs), validation_level) .await; assert!(response.is_err()); @@ -533,7 +533,7 @@ pub async fn equivocation_consensus_early_equivocation() { /* submit `block_a` as valid */ assert!(tester .client - .post_beacon_blocks_v2( + .post_beacon_blocks_v2_ssz( &PublishBlockRequest::new(block_a.clone(), blobs_a), validation_level ) @@ -547,7 +547,7 @@ pub async fn equivocation_consensus_early_equivocation() { /* submit `block_b` which should induce equivocation */ let response: Result<(), eth2::Error> = tester .client - .post_beacon_blocks_v2( + .post_beacon_blocks_v2_ssz( &PublishBlockRequest::new(block_b.clone(), blobs_b), validation_level, ) @@ -596,7 +596,7 @@ pub async fn equivocation_gossip() { let response: Result<(), eth2::Error> = tester .client - .post_beacon_blocks_v2(&PublishBlockRequest::new(block, blobs), validation_level) + .post_beacon_blocks_v2_ssz(&PublishBlockRequest::new(block, blobs), validation_level) .await; assert!(response.is_err()); @@ -721,7 +721,7 @@ pub async fn equivocation_full_pass() { let response: Result<(), eth2::Error> = tester .client - .post_beacon_blocks_v2( + .post_beacon_blocks_v2_ssz( &PublishBlockRequest::new(block.clone(), blobs), validation_level, ) @@ -1413,7 +1413,7 @@ pub async fn block_seen_on_gossip_without_blobs() { // Post the block *and* blobs to the HTTP API. let response: Result<(), eth2::Error> = tester .client - .post_beacon_blocks_v2( + .post_beacon_blocks_v2_ssz( &PublishBlockRequest::new(block.clone(), Some(blobs)), validation_level, ) @@ -1498,7 +1498,7 @@ pub async fn block_seen_on_gossip_with_some_blobs() { // Post the block *and* all blobs to the HTTP API. let response: Result<(), eth2::Error> = tester .client - .post_beacon_blocks_v2( + .post_beacon_blocks_v2_ssz( &PublishBlockRequest::new(block.clone(), Some(blobs)), validation_level, ) @@ -1571,7 +1571,7 @@ pub async fn blobs_seen_on_gossip_without_block() { // Post the block *and* all blobs to the HTTP API. let response: Result<(), eth2::Error> = tester .client - .post_beacon_blocks_v2( + .post_beacon_blocks_v2_ssz( &PublishBlockRequest::new(block.clone(), Some((kzg_proofs, blobs))), validation_level, ) @@ -1645,7 +1645,7 @@ pub async fn blobs_seen_on_gossip_without_block_and_no_http_blobs() { // Post just the block to the HTTP API (blob lists are empty). let response: Result<(), eth2::Error> = tester .client - .post_beacon_blocks_v2( + .post_beacon_blocks_v2_ssz( &PublishBlockRequest::new( block.clone(), Some((Default::default(), Default::default())), @@ -1717,7 +1717,7 @@ pub async fn slashable_blobs_seen_on_gossip_cause_failure() { // Post block A *and* all its blobs to the HTTP API. let response: Result<(), eth2::Error> = tester .client - .post_beacon_blocks_v2( + .post_beacon_blocks_v2_ssz( &PublishBlockRequest::new(block_a.clone(), Some((kzg_proofs_a, blobs_a))), validation_level, ) @@ -1778,7 +1778,7 @@ pub async fn duplicate_block_status_code() { let block_request = PublishBlockRequest::new(block.clone(), Some((kzg_proofs, blobs))); let response: Result<(), eth2::Error> = tester .client - .post_beacon_blocks_v2(&block_request, validation_level) + .post_beacon_blocks_v2_ssz(&block_request, validation_level) .await; // This should result in the block being fully imported. @@ -1791,7 +1791,7 @@ pub async fn duplicate_block_status_code() { // Post again. let duplicate_response: Result<(), eth2::Error> = tester .client - .post_beacon_blocks_v2(&block_request, validation_level) + .post_beacon_blocks_v2_ssz(&block_request, validation_level) .await; let err = duplicate_response.unwrap_err(); assert_eq!(err.status().unwrap(), duplicate_block_status_code); diff --git a/beacon_node/http_api/tests/tests.rs b/beacon_node/http_api/tests/tests.rs index 1efe44a613c..85d3b4e9bae 100644 --- a/beacon_node/http_api/tests/tests.rs +++ b/beacon_node/http_api/tests/tests.rs @@ -2232,9 +2232,9 @@ impl ApiTester { pub async fn test_get_config_spec(self) -> Self { let result = self .client - .get_config_spec::() + .get_config_spec::() .await - .map(|res| ConfigAndPreset::Electra(res.data)) + .map(|res| ConfigAndPreset::Fulu(res.data)) .unwrap(); let expected = ConfigAndPreset::from_chain_spec::(&self.chain.spec, None); diff --git a/beacon_node/lighthouse_network/src/rpc/codec.rs b/beacon_node/lighthouse_network/src/rpc/codec.rs index 5d86936d41d..c3d20bbfb17 100644 --- a/beacon_node/lighthouse_network/src/rpc/codec.rs +++ b/beacon_node/lighthouse_network/src/rpc/codec.rs @@ -20,7 +20,7 @@ use types::{ LightClientBootstrap, LightClientFinalityUpdate, LightClientOptimisticUpdate, LightClientUpdate, RuntimeVariableList, SignedBeaconBlock, SignedBeaconBlockAltair, SignedBeaconBlockBase, SignedBeaconBlockBellatrix, SignedBeaconBlockCapella, - SignedBeaconBlockDeneb, SignedBeaconBlockElectra, + SignedBeaconBlockDeneb, SignedBeaconBlockElectra, SignedBeaconBlockFulu, }; use unsigned_varint::codec::Uvi; @@ -458,6 +458,9 @@ fn context_bytes( return match **ref_box_block { // NOTE: If you are adding another fork type here, be sure to modify the // `fork_context.to_context_bytes()` function to support it as well! + SignedBeaconBlock::Fulu { .. } => { + fork_context.to_context_bytes(ForkName::Fulu) + } SignedBeaconBlock::Electra { .. } => { fork_context.to_context_bytes(ForkName::Electra) } @@ -682,18 +685,18 @@ fn handle_rpc_response( SignedBeaconBlock::Base(SignedBeaconBlockBase::from_ssz_bytes(decoded_buffer)?), )))), SupportedProtocol::BlobsByRangeV1 => match fork_name { - Some(ForkName::Deneb) | Some(ForkName::Electra) => { - Ok(Some(RpcSuccessResponse::BlobsByRange(Arc::new( - BlobSidecar::from_ssz_bytes(decoded_buffer)?, - )))) + Some(fork_name) => { + if fork_name.deneb_enabled() { + Ok(Some(RpcSuccessResponse::BlobsByRange(Arc::new( + BlobSidecar::from_ssz_bytes(decoded_buffer)?, + )))) + } else { + Err(RPCError::ErrorResponse( + RpcErrorResponse::InvalidRequest, + "Invalid fork name for blobs by range".to_string(), + )) + } } - Some(ForkName::Base) - | Some(ForkName::Altair) - | Some(ForkName::Bellatrix) - | Some(ForkName::Capella) => Err(RPCError::ErrorResponse( - RpcErrorResponse::InvalidRequest, - "Invalid fork name for blobs by range".to_string(), - )), None => Err(RPCError::ErrorResponse( RpcErrorResponse::InvalidRequest, format!( @@ -703,18 +706,18 @@ fn handle_rpc_response( )), }, SupportedProtocol::BlobsByRootV1 => match fork_name { - Some(ForkName::Deneb) | Some(ForkName::Electra) => { - Ok(Some(RpcSuccessResponse::BlobsByRoot(Arc::new( - BlobSidecar::from_ssz_bytes(decoded_buffer)?, - )))) + Some(fork_name) => { + if fork_name.deneb_enabled() { + Ok(Some(RpcSuccessResponse::BlobsByRoot(Arc::new( + BlobSidecar::from_ssz_bytes(decoded_buffer)?, + )))) + } else { + Err(RPCError::ErrorResponse( + RpcErrorResponse::InvalidRequest, + "Invalid fork name for blobs by root".to_string(), + )) + } } - Some(ForkName::Base) - | Some(ForkName::Altair) - | Some(ForkName::Bellatrix) - | Some(ForkName::Capella) => Err(RPCError::ErrorResponse( - RpcErrorResponse::InvalidRequest, - "Invalid fork name for blobs by root".to_string(), - )), None => Err(RPCError::ErrorResponse( RpcErrorResponse::InvalidRequest, format!( @@ -864,6 +867,9 @@ fn handle_rpc_response( decoded_buffer, )?), )))), + Some(ForkName::Fulu) => Ok(Some(RpcSuccessResponse::BlocksByRange(Arc::new( + SignedBeaconBlock::Fulu(SignedBeaconBlockFulu::from_ssz_bytes(decoded_buffer)?), + )))), None => Err(RPCError::ErrorResponse( RpcErrorResponse::InvalidRequest, format!( @@ -897,6 +903,9 @@ fn handle_rpc_response( decoded_buffer, )?), )))), + Some(ForkName::Fulu) => Ok(Some(RpcSuccessResponse::BlocksByRoot(Arc::new( + SignedBeaconBlock::Fulu(SignedBeaconBlockFulu::from_ssz_bytes(decoded_buffer)?), + )))), None => Err(RPCError::ErrorResponse( RpcErrorResponse::InvalidRequest, format!( @@ -948,12 +957,14 @@ mod tests { let capella_fork_epoch = Epoch::new(3); let deneb_fork_epoch = Epoch::new(4); let electra_fork_epoch = Epoch::new(5); + let fulu_fork_epoch = Epoch::new(6); chain_spec.altair_fork_epoch = Some(altair_fork_epoch); chain_spec.bellatrix_fork_epoch = Some(bellatrix_fork_epoch); chain_spec.capella_fork_epoch = Some(capella_fork_epoch); chain_spec.deneb_fork_epoch = Some(deneb_fork_epoch); chain_spec.electra_fork_epoch = Some(electra_fork_epoch); + chain_spec.fulu_fork_epoch = Some(fulu_fork_epoch); let current_slot = match fork_name { ForkName::Base => Slot::new(0), @@ -962,6 +973,7 @@ mod tests { ForkName::Capella => capella_fork_epoch.start_slot(Spec::slots_per_epoch()), ForkName::Deneb => deneb_fork_epoch.start_slot(Spec::slots_per_epoch()), ForkName::Electra => electra_fork_epoch.start_slot(Spec::slots_per_epoch()), + ForkName::Fulu => fulu_fork_epoch.start_slot(Spec::slots_per_epoch()), }; ForkContext::new::(current_slot, Hash256::zero(), &chain_spec) } @@ -1396,6 +1408,16 @@ mod tests { Ok(Some(RpcSuccessResponse::BlobsByRange(empty_blob_sidecar()))), ); + assert_eq!( + encode_then_decode_response( + SupportedProtocol::BlobsByRangeV1, + RpcResponse::Success(RpcSuccessResponse::BlobsByRange(empty_blob_sidecar())), + ForkName::Fulu, + &chain_spec + ), + Ok(Some(RpcSuccessResponse::BlobsByRange(empty_blob_sidecar()))), + ); + assert_eq!( encode_then_decode_response( SupportedProtocol::BlobsByRootV1, @@ -1416,6 +1438,16 @@ mod tests { Ok(Some(RpcSuccessResponse::BlobsByRoot(empty_blob_sidecar()))), ); + assert_eq!( + encode_then_decode_response( + SupportedProtocol::BlobsByRootV1, + RpcResponse::Success(RpcSuccessResponse::BlobsByRoot(empty_blob_sidecar())), + ForkName::Fulu, + &chain_spec + ), + Ok(Some(RpcSuccessResponse::BlobsByRoot(empty_blob_sidecar()))), + ); + assert_eq!( encode_then_decode_response( SupportedProtocol::DataColumnsByRangeV1, @@ -1444,6 +1476,20 @@ mod tests { ))), ); + assert_eq!( + encode_then_decode_response( + SupportedProtocol::DataColumnsByRangeV1, + RpcResponse::Success(RpcSuccessResponse::DataColumnsByRange( + empty_data_column_sidecar() + )), + ForkName::Fulu, + &chain_spec + ), + Ok(Some(RpcSuccessResponse::DataColumnsByRange( + empty_data_column_sidecar() + ))), + ); + assert_eq!( encode_then_decode_response( SupportedProtocol::DataColumnsByRootV1, @@ -1471,6 +1517,20 @@ mod tests { empty_data_column_sidecar() ))), ); + + assert_eq!( + encode_then_decode_response( + SupportedProtocol::DataColumnsByRootV1, + RpcResponse::Success(RpcSuccessResponse::DataColumnsByRoot( + empty_data_column_sidecar() + )), + ForkName::Fulu, + &chain_spec + ), + Ok(Some(RpcSuccessResponse::DataColumnsByRoot( + empty_data_column_sidecar() + ))), + ); } // Test RPCResponse encoding/decoding for V1 messages diff --git a/beacon_node/lighthouse_network/src/rpc/protocol.rs b/beacon_node/lighthouse_network/src/rpc/protocol.rs index 57c2795b04f..87bde58292b 100644 --- a/beacon_node/lighthouse_network/src/rpc/protocol.rs +++ b/beacon_node/lighthouse_network/src/rpc/protocol.rs @@ -18,9 +18,9 @@ use tokio_util::{ }; use types::{ BeaconBlock, BeaconBlockAltair, BeaconBlockBase, BeaconBlockCapella, BeaconBlockElectra, - BlobSidecar, ChainSpec, DataColumnSidecar, EmptyBlock, EthSpec, EthSpecId, ForkContext, - ForkName, LightClientBootstrap, LightClientBootstrapAltair, LightClientFinalityUpdate, - LightClientFinalityUpdateAltair, LightClientOptimisticUpdate, + BeaconBlockFulu, BlobSidecar, ChainSpec, DataColumnSidecar, EmptyBlock, EthSpec, EthSpecId, + ForkContext, ForkName, LightClientBootstrap, LightClientBootstrapAltair, + LightClientFinalityUpdate, LightClientFinalityUpdateAltair, LightClientOptimisticUpdate, LightClientOptimisticUpdateAltair, LightClientUpdate, MainnetEthSpec, MinimalEthSpec, Signature, SignedBeaconBlock, }; @@ -73,6 +73,15 @@ pub static SIGNED_BEACON_BLOCK_ELECTRA_MAX_WITHOUT_PAYLOAD: LazyLock = La .len() }); +pub static SIGNED_BEACON_BLOCK_FULU_MAX_WITHOUT_PAYLOAD: LazyLock = LazyLock::new(|| { + SignedBeaconBlock::::from_block( + BeaconBlock::Fulu(BeaconBlockFulu::full(&MainnetEthSpec::default_spec())), + Signature::empty(), + ) + .as_ssz_bytes() + .len() +}); + /// The `BeaconBlockBellatrix` block has an `ExecutionPayload` field which has a max size ~16 GiB for future proofing. /// We calculate the value from its fields instead of constructing the block and checking the length. /// Note: This is only the theoretical upper bound. We further bound the max size we receive over the network @@ -105,6 +114,15 @@ pub static SIGNED_BEACON_BLOCK_ELECTRA_MAX: LazyLock = LazyLock::new(|| { + ssz::BYTES_PER_LENGTH_OFFSET }); // Length offset for the blob commitments field. +pub static SIGNED_BEACON_BLOCK_FULU_MAX: LazyLock = LazyLock::new(|| { + *SIGNED_BEACON_BLOCK_FULU_MAX_WITHOUT_PAYLOAD + + types::ExecutionPayload::::max_execution_payload_fulu_size() + + ssz::BYTES_PER_LENGTH_OFFSET + + (::ssz_fixed_len() + * ::max_blobs_per_block()) + + ssz::BYTES_PER_LENGTH_OFFSET +}); + pub static BLOB_SIDECAR_SIZE: LazyLock = LazyLock::new(BlobSidecar::::max_size); @@ -209,6 +227,10 @@ pub fn rpc_block_limits_by_fork(current_fork: ForkName) -> RpcLimits { *SIGNED_BEACON_BLOCK_BASE_MIN, // Base block is smaller than altair and bellatrix blocks *SIGNED_BEACON_BLOCK_ELECTRA_MAX, // Electra block is larger than Deneb block ), + ForkName::Fulu => RpcLimits::new( + *SIGNED_BEACON_BLOCK_BASE_MIN, // Base block is smaller than all other blocks + *SIGNED_BEACON_BLOCK_FULU_MAX, // Fulu block is largest + ), } } @@ -226,7 +248,7 @@ fn rpc_light_client_updates_by_range_limits_by_fork(current_fork: ForkName) -> R ForkName::Deneb => { RpcLimits::new(altair_fixed_len, *LIGHT_CLIENT_UPDATES_BY_RANGE_DENEB_MAX) } - ForkName::Electra => { + ForkName::Electra | ForkName::Fulu => { RpcLimits::new(altair_fixed_len, *LIGHT_CLIENT_UPDATES_BY_RANGE_ELECTRA_MAX) } } @@ -246,7 +268,7 @@ fn rpc_light_client_finality_update_limits_by_fork(current_fork: ForkName) -> Rp ForkName::Deneb => { RpcLimits::new(altair_fixed_len, *LIGHT_CLIENT_FINALITY_UPDATE_DENEB_MAX) } - ForkName::Electra => { + ForkName::Electra | ForkName::Fulu => { RpcLimits::new(altair_fixed_len, *LIGHT_CLIENT_FINALITY_UPDATE_ELECTRA_MAX) } } @@ -267,7 +289,7 @@ fn rpc_light_client_optimistic_update_limits_by_fork(current_fork: ForkName) -> ForkName::Deneb => { RpcLimits::new(altair_fixed_len, *LIGHT_CLIENT_OPTIMISTIC_UPDATE_DENEB_MAX) } - ForkName::Electra => RpcLimits::new( + ForkName::Electra | ForkName::Fulu => RpcLimits::new( altair_fixed_len, *LIGHT_CLIENT_OPTIMISTIC_UPDATE_ELECTRA_MAX, ), @@ -284,7 +306,9 @@ fn rpc_light_client_bootstrap_limits_by_fork(current_fork: ForkName) -> RpcLimit } ForkName::Capella => RpcLimits::new(altair_fixed_len, *LIGHT_CLIENT_BOOTSTRAP_CAPELLA_MAX), ForkName::Deneb => RpcLimits::new(altair_fixed_len, *LIGHT_CLIENT_BOOTSTRAP_DENEB_MAX), - ForkName::Electra => RpcLimits::new(altair_fixed_len, *LIGHT_CLIENT_BOOTSTRAP_ELECTRA_MAX), + ForkName::Electra | ForkName::Fulu => { + RpcLimits::new(altair_fixed_len, *LIGHT_CLIENT_BOOTSTRAP_ELECTRA_MAX) + } } } diff --git a/beacon_node/lighthouse_network/src/types/pubsub.rs b/beacon_node/lighthouse_network/src/types/pubsub.rs index 9f68278e284..c9769594708 100644 --- a/beacon_node/lighthouse_network/src/types/pubsub.rs +++ b/beacon_node/lighthouse_network/src/types/pubsub.rs @@ -13,8 +13,9 @@ use types::{ ProposerSlashing, SignedAggregateAndProof, SignedAggregateAndProofBase, SignedAggregateAndProofElectra, SignedBeaconBlock, SignedBeaconBlockAltair, SignedBeaconBlockBase, SignedBeaconBlockBellatrix, SignedBeaconBlockCapella, - SignedBeaconBlockDeneb, SignedBeaconBlockElectra, SignedBlsToExecutionChange, - SignedContributionAndProof, SignedVoluntaryExit, SubnetId, SyncCommitteeMessage, SyncSubnetId, + SignedBeaconBlockDeneb, SignedBeaconBlockElectra, SignedBeaconBlockFulu, + SignedBlsToExecutionChange, SignedContributionAndProof, SignedVoluntaryExit, SubnetId, + SyncCommitteeMessage, SyncSubnetId, }; #[derive(Debug, Clone, PartialEq)] @@ -242,6 +243,10 @@ impl PubsubMessage { SignedBeaconBlockElectra::from_ssz_bytes(data) .map_err(|e| format!("{:?}", e))?, ), + Some(ForkName::Fulu) => SignedBeaconBlock::::Fulu( + SignedBeaconBlockFulu::from_ssz_bytes(data) + .map_err(|e| format!("{:?}", e))?, + ), None => { return Err(format!( "Unknown gossipsub fork digest: {:?}", diff --git a/beacon_node/lighthouse_network/src/types/topics.rs b/beacon_node/lighthouse_network/src/types/topics.rs index 174787f999c..8cdecc6bfa2 100644 --- a/beacon_node/lighthouse_network/src/types/topics.rs +++ b/beacon_node/lighthouse_network/src/types/topics.rs @@ -61,6 +61,7 @@ pub fn fork_core_topics(fork_name: &ForkName, spec: &ChainSpec) -> V deneb_topics } ForkName::Electra => vec![], + ForkName::Fulu => vec![], } } diff --git a/beacon_node/lighthouse_network/tests/common.rs b/beacon_node/lighthouse_network/tests/common.rs index 84e19c81d04..6a3ec6dd322 100644 --- a/beacon_node/lighthouse_network/tests/common.rs +++ b/beacon_node/lighthouse_network/tests/common.rs @@ -25,12 +25,14 @@ pub fn fork_context(fork_name: ForkName) -> ForkContext { let capella_fork_epoch = Epoch::new(3); let deneb_fork_epoch = Epoch::new(4); let electra_fork_epoch = Epoch::new(5); + let fulu_fork_epoch = Epoch::new(6); chain_spec.altair_fork_epoch = Some(altair_fork_epoch); chain_spec.bellatrix_fork_epoch = Some(bellatrix_fork_epoch); chain_spec.capella_fork_epoch = Some(capella_fork_epoch); chain_spec.deneb_fork_epoch = Some(deneb_fork_epoch); chain_spec.electra_fork_epoch = Some(electra_fork_epoch); + chain_spec.fulu_fork_epoch = Some(fulu_fork_epoch); let current_slot = match fork_name { ForkName::Base => Slot::new(0), @@ -39,6 +41,7 @@ pub fn fork_context(fork_name: ForkName) -> ForkContext { ForkName::Capella => capella_fork_epoch.start_slot(E::slots_per_epoch()), ForkName::Deneb => deneb_fork_epoch.start_slot(E::slots_per_epoch()), ForkName::Electra => electra_fork_epoch.start_slot(E::slots_per_epoch()), + ForkName::Fulu => fulu_fork_epoch.start_slot(E::slots_per_epoch()), }; ForkContext::new::(current_slot, Hash256::zero(), &chain_spec) } diff --git a/beacon_node/network/src/sync/tests/lookups.rs b/beacon_node/network/src/sync/tests/lookups.rs index 94aacad3e81..a43b3bd022f 100644 --- a/beacon_node/network/src/sync/tests/lookups.rs +++ b/beacon_node/network/src/sync/tests/lookups.rs @@ -174,7 +174,7 @@ impl TestRig { } pub fn after_deneb(&self) -> bool { - matches!(self.fork_name, ForkName::Deneb | ForkName::Electra) + self.fork_name.deneb_enabled() } fn trigger_unknown_parent_block(&mut self, peer_id: PeerId, block: Arc>) { diff --git a/beacon_node/operation_pool/src/lib.rs b/beacon_node/operation_pool/src/lib.rs index d8183de7529..835133a0598 100644 --- a/beacon_node/operation_pool/src/lib.rs +++ b/beacon_node/operation_pool/src/lib.rs @@ -1239,14 +1239,11 @@ mod release_tests { let stats = op_pool.attestation_stats(); let fork_name = state.fork_name_unchecked(); - match fork_name { - ForkName::Electra => { - assert_eq!(stats.num_attestation_data, 1); - } - _ => { - assert_eq!(stats.num_attestation_data, committees.len()); - } - }; + if fork_name.electra_enabled() { + assert_eq!(stats.num_attestation_data, 1); + } else { + assert_eq!(stats.num_attestation_data, committees.len()); + } assert_eq!( stats.num_attestations, @@ -1258,25 +1255,19 @@ mod release_tests { let best_attestations = op_pool .get_attestations(&state, |_| true, |_| true, spec) .expect("should have best attestations"); - match fork_name { - ForkName::Electra => { - assert_eq!(best_attestations.len(), 8); - } - _ => { - assert_eq!(best_attestations.len(), max_attestations); - } - }; + if fork_name.electra_enabled() { + assert_eq!(best_attestations.len(), 8); + } else { + assert_eq!(best_attestations.len(), max_attestations); + } // All the best attestations should be signed by at least `big_step_size` (4) validators. for att in &best_attestations { - match fork_name { - ForkName::Electra => { - assert!(att.num_set_aggregation_bits() >= small_step_size); - } - _ => { - assert!(att.num_set_aggregation_bits() >= big_step_size); - } - }; + if fork_name.electra_enabled() { + assert!(att.num_set_aggregation_bits() >= small_step_size); + } else { + assert!(att.num_set_aggregation_bits() >= big_step_size); + } } } @@ -1357,17 +1348,14 @@ mod release_tests { let num_big = target_committee_size / big_step_size; let fork_name = state.fork_name_unchecked(); - match fork_name { - ForkName::Electra => { - assert_eq!(op_pool.attestation_stats().num_attestation_data, 1); - } - _ => { - assert_eq!( - op_pool.attestation_stats().num_attestation_data, - committees.len() - ); - } - }; + if fork_name.electra_enabled() { + assert_eq!(op_pool.attestation_stats().num_attestation_data, 1); + } else { + assert_eq!( + op_pool.attestation_stats().num_attestation_data, + committees.len() + ); + } assert_eq!( op_pool.num_attestations(), @@ -1380,14 +1368,11 @@ mod release_tests { .get_attestations(&state, |_| true, |_| true, spec) .expect("should have valid best attestations"); - match fork_name { - ForkName::Electra => { - assert_eq!(best_attestations.len(), 8); - } - _ => { - assert_eq!(best_attestations.len(), max_attestations); - } - }; + if fork_name.electra_enabled() { + assert_eq!(best_attestations.len(), 8); + } else { + assert_eq!(best_attestations.len(), max_attestations); + } let total_active_balance = state.get_total_active_balance().unwrap(); diff --git a/beacon_node/src/lib.rs b/beacon_node/src/lib.rs index cca617d8c61..0c4cbf0f579 100644 --- a/beacon_node/src/lib.rs +++ b/beacon_node/src/lib.rs @@ -238,6 +238,7 @@ mod test { spec.bellatrix_fork_epoch = Some(Epoch::new(256)); spec.deneb_fork_epoch = Some(Epoch::new(257)); spec.electra_fork_epoch = None; + spec.fulu_fork_epoch = None; let result = validator_fork_epochs(&spec); assert_eq!( result, diff --git a/beacon_node/store/src/impls/execution_payload.rs b/beacon_node/store/src/impls/execution_payload.rs index 14fc10ad6de..5c60aa8d7e3 100644 --- a/beacon_node/store/src/impls/execution_payload.rs +++ b/beacon_node/store/src/impls/execution_payload.rs @@ -2,7 +2,7 @@ use crate::{DBColumn, Error, StoreItem}; use ssz::{Decode, Encode}; use types::{ BlobSidecarList, EthSpec, ExecutionPayload, ExecutionPayloadBellatrix, ExecutionPayloadCapella, - ExecutionPayloadDeneb, ExecutionPayloadElectra, + ExecutionPayloadDeneb, ExecutionPayloadElectra, ExecutionPayloadFulu, }; macro_rules! impl_store_item { @@ -26,6 +26,7 @@ impl_store_item!(ExecutionPayloadBellatrix); impl_store_item!(ExecutionPayloadCapella); impl_store_item!(ExecutionPayloadDeneb); impl_store_item!(ExecutionPayloadElectra); +impl_store_item!(ExecutionPayloadFulu); impl_store_item!(BlobSidecarList); /// This fork-agnostic implementation should be only used for writing. @@ -42,17 +43,21 @@ impl StoreItem for ExecutionPayload { } fn from_store_bytes(bytes: &[u8]) -> Result { - ExecutionPayloadElectra::from_ssz_bytes(bytes) - .map(Self::Electra) + ExecutionPayloadFulu::from_ssz_bytes(bytes) + .map(Self::Fulu) .or_else(|_| { - ExecutionPayloadDeneb::from_ssz_bytes(bytes) - .map(Self::Deneb) + ExecutionPayloadElectra::from_ssz_bytes(bytes) + .map(Self::Electra) .or_else(|_| { - ExecutionPayloadCapella::from_ssz_bytes(bytes) - .map(Self::Capella) + ExecutionPayloadDeneb::from_ssz_bytes(bytes) + .map(Self::Deneb) .or_else(|_| { - ExecutionPayloadBellatrix::from_ssz_bytes(bytes) - .map(Self::Bellatrix) + ExecutionPayloadCapella::from_ssz_bytes(bytes) + .map(Self::Capella) + .or_else(|_| { + ExecutionPayloadBellatrix::from_ssz_bytes(bytes) + .map(Self::Bellatrix) + }) }) }) }) diff --git a/beacon_node/store/src/partial_beacon_state.rs b/beacon_node/store/src/partial_beacon_state.rs index 22eecdcc605..0b8bc2e0d4b 100644 --- a/beacon_node/store/src/partial_beacon_state.rs +++ b/beacon_node/store/src/partial_beacon_state.rs @@ -16,7 +16,7 @@ use types::*; /// /// This can be deleted once schema versions prior to V22 are no longer supported. #[superstruct( - variants(Base, Altair, Bellatrix, Capella, Deneb, Electra), + variants(Base, Altair, Bellatrix, Capella, Deneb, Electra, Fulu), variant_attributes(derive(Debug, PartialEq, Clone, Encode, Decode)) )] #[derive(Debug, PartialEq, Clone, Encode)] @@ -68,9 +68,9 @@ where pub current_epoch_attestations: List, E::MaxPendingAttestations>, // Participation (Altair and later) - #[superstruct(only(Altair, Bellatrix, Capella, Deneb, Electra))] + #[superstruct(only(Altair, Bellatrix, Capella, Deneb, Electra, Fulu))] pub previous_epoch_participation: List, - #[superstruct(only(Altair, Bellatrix, Capella, Deneb, Electra))] + #[superstruct(only(Altair, Bellatrix, Capella, Deneb, Electra, Fulu))] pub current_epoch_participation: List, // Finality @@ -80,13 +80,13 @@ where pub finalized_checkpoint: Checkpoint, // Inactivity - #[superstruct(only(Altair, Bellatrix, Capella, Deneb, Electra))] + #[superstruct(only(Altair, Bellatrix, Capella, Deneb, Electra, Fulu))] pub inactivity_scores: List, // Light-client sync committees - #[superstruct(only(Altair, Bellatrix, Capella, Deneb, Electra))] + #[superstruct(only(Altair, Bellatrix, Capella, Deneb, Electra, Fulu))] pub current_sync_committee: Arc>, - #[superstruct(only(Altair, Bellatrix, Capella, Deneb, Electra))] + #[superstruct(only(Altair, Bellatrix, Capella, Deneb, Electra, Fulu))] pub next_sync_committee: Arc>, // Execution @@ -110,37 +110,42 @@ where partial_getter(rename = "latest_execution_payload_header_electra") )] pub latest_execution_payload_header: ExecutionPayloadHeaderElectra, + #[superstruct( + only(Fulu), + partial_getter(rename = "latest_execution_payload_header_fulu") + )] + pub latest_execution_payload_header: ExecutionPayloadHeaderFulu, // Capella - #[superstruct(only(Capella, Deneb, Electra))] + #[superstruct(only(Capella, Deneb, Electra, Fulu))] pub next_withdrawal_index: u64, - #[superstruct(only(Capella, Deneb, Electra))] + #[superstruct(only(Capella, Deneb, Electra, Fulu))] pub next_withdrawal_validator_index: u64, #[ssz(skip_serializing, skip_deserializing)] - #[superstruct(only(Capella, Deneb, Electra))] + #[superstruct(only(Capella, Deneb, Electra, Fulu))] pub historical_summaries: Option>, // Electra - #[superstruct(only(Electra))] + #[superstruct(only(Electra, Fulu))] pub deposit_requests_start_index: u64, - #[superstruct(only(Electra))] + #[superstruct(only(Electra, Fulu))] pub deposit_balance_to_consume: u64, - #[superstruct(only(Electra))] + #[superstruct(only(Electra, Fulu))] pub exit_balance_to_consume: u64, - #[superstruct(only(Electra))] + #[superstruct(only(Electra, Fulu))] pub earliest_exit_epoch: Epoch, - #[superstruct(only(Electra))] + #[superstruct(only(Electra, Fulu))] pub consolidation_balance_to_consume: u64, - #[superstruct(only(Electra))] + #[superstruct(only(Electra, Fulu))] pub earliest_consolidation_epoch: Epoch, - #[superstruct(only(Electra))] + #[superstruct(only(Electra, Fulu))] pub pending_deposits: List, - #[superstruct(only(Electra))] + #[superstruct(only(Electra, Fulu))] pub pending_partial_withdrawals: List, - #[superstruct(only(Electra))] + #[superstruct(only(Electra, Fulu))] pub pending_consolidations: List, } @@ -409,6 +414,31 @@ impl TryInto> for PartialBeaconState { ], [historical_summaries] ), + PartialBeaconState::Fulu(inner) => impl_try_into_beacon_state!( + inner, + Fulu, + BeaconStateFulu, + [ + previous_epoch_participation, + current_epoch_participation, + current_sync_committee, + next_sync_committee, + inactivity_scores, + latest_execution_payload_header, + next_withdrawal_index, + next_withdrawal_validator_index, + deposit_requests_start_index, + deposit_balance_to_consume, + exit_balance_to_consume, + earliest_exit_epoch, + consolidation_balance_to_consume, + earliest_consolidation_epoch, + pending_deposits, + pending_partial_withdrawals, + pending_consolidations + ], + [historical_summaries] + ), }; Ok(state) } diff --git a/common/eth2/src/types.rs b/common/eth2/src/types.rs index a303953a863..695d536944b 100644 --- a/common/eth2/src/types.rs +++ b/common/eth2/src/types.rs @@ -1078,6 +1078,9 @@ impl ForkVersionDeserialize for SsePayloadAttributes { ForkName::Electra => serde_json::from_value(value) .map(Self::V3) .map_err(serde::de::Error::custom), + ForkName::Fulu => serde_json::from_value(value) + .map(Self::V3) + .map_err(serde::de::Error::custom), ForkName::Base | ForkName::Altair => Err(serde::de::Error::custom(format!( "SsePayloadAttributes deserialization for {fork_name} not implemented" ))), @@ -1861,14 +1864,10 @@ impl PublishBlockRequest { impl TryFrom>> for PublishBlockRequest { type Error = &'static str; fn try_from(block: Arc>) -> Result { - match *block { - SignedBeaconBlock::Base(_) - | SignedBeaconBlock::Altair(_) - | SignedBeaconBlock::Bellatrix(_) - | SignedBeaconBlock::Capella(_) => Ok(PublishBlockRequest::Block(block)), - SignedBeaconBlock::Deneb(_) | SignedBeaconBlock::Electra(_) => Err( - "post-Deneb block contents cannot be fully constructed from just the signed block", - ), + if block.message().fork_name_unchecked().deneb_enabled() { + Err("post-Deneb block contents cannot be fully constructed from just the signed block") + } else { + Ok(PublishBlockRequest::Block(block)) } } } @@ -1972,16 +1971,18 @@ impl ForkVersionDeserialize for FullPayloadContents { value: Value, fork_name: ForkName, ) -> Result { - match fork_name { - ForkName::Bellatrix | ForkName::Capella => serde_json::from_value(value) - .map(Self::Payload) - .map_err(serde::de::Error::custom), - ForkName::Deneb | ForkName::Electra => serde_json::from_value(value) + if fork_name.deneb_enabled() { + serde_json::from_value(value) .map(Self::PayloadAndBlobs) - .map_err(serde::de::Error::custom), - ForkName::Base | ForkName::Altair => Err(serde::de::Error::custom(format!( + .map_err(serde::de::Error::custom) + } else if fork_name.bellatrix_enabled() { + serde_json::from_value(value) + .map(Self::Payload) + .map_err(serde::de::Error::custom) + } else { + Err(serde::de::Error::custom(format!( "FullPayloadContents deserialization for {fork_name} not implemented" - ))), + ))) } } } diff --git a/common/eth2_network_config/built_in_network_configs/chiado/config.yaml b/common/eth2_network_config/built_in_network_configs/chiado/config.yaml index 1eca01bbeef..a107f6147ae 100644 --- a/common/eth2_network_config/built_in_network_configs/chiado/config.yaml +++ b/common/eth2_network_config/built_in_network_configs/chiado/config.yaml @@ -15,7 +15,6 @@ TERMINAL_TOTAL_DIFFICULTY: 231707791542740786049188744689299064356246512 TERMINAL_BLOCK_HASH: 0x0000000000000000000000000000000000000000000000000000000000000000 TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH: 18446744073709551615 - # Genesis # --------------------------------------------------------------- # *CUSTOM @@ -27,7 +26,6 @@ GENESIS_FORK_VERSION: 0x0000006f # *CUSTOM GENESIS_DELAY: 300 - # Forking # --------------------------------------------------------------- # Some forks are disabled for now: @@ -49,6 +47,9 @@ DENEB_FORK_EPOCH: 516608 # Wed Jan 31 2024 18:15:40 GMT+0000 # Electra ELECTRA_FORK_VERSION: 0x0500006f ELECTRA_FORK_EPOCH: 18446744073709551615 +# Fulu +FULU_FORK_VERSION: 0x0600006f +FULU_FORK_EPOCH: 18446744073709551615 # Time parameters # --------------------------------------------------------------- @@ -63,7 +64,6 @@ SHARD_COMMITTEE_PERIOD: 256 # 2**10 (= 1024) ~1.4 hour ETH1_FOLLOW_DISTANCE: 1024 - # Validator cycle # --------------------------------------------------------------- # 2**2 (= 4) @@ -90,7 +90,6 @@ REORG_PARENT_WEIGHT_THRESHOLD: 160 # `2` epochs REORG_MAX_EPOCHS_SINCE_FINALIZATION: 2 - # Deposit contract # --------------------------------------------------------------- # xDai Mainnet @@ -141,4 +140,4 @@ BLOB_SIDECAR_SUBNET_COUNT: 6 CUSTODY_REQUIREMENT: 4 DATA_COLUMN_SIDECAR_SUBNET_COUNT: 128 NUMBER_OF_COLUMNS: 128 -SAMPLES_PER_SLOT: 8 \ No newline at end of file +SAMPLES_PER_SLOT: 8 diff --git a/common/eth2_network_config/built_in_network_configs/gnosis/config.yaml b/common/eth2_network_config/built_in_network_configs/gnosis/config.yaml index 500555a2694..f71984059aa 100644 --- a/common/eth2_network_config/built_in_network_configs/gnosis/config.yaml +++ b/common/eth2_network_config/built_in_network_configs/gnosis/config.yaml @@ -23,7 +23,6 @@ GENESIS_FORK_VERSION: 0x00000064 # 6000 seconds (100 minutes) GENESIS_DELAY: 6000 - # Forking # --------------------------------------------------------------- # Some forks are disabled for now: @@ -45,7 +44,9 @@ DENEB_FORK_EPOCH: 889856 # 2024-03-11T18:30:20.000Z # Electra ELECTRA_FORK_VERSION: 0x05000064 ELECTRA_FORK_EPOCH: 18446744073709551615 - +# Fulu +FULU_FORK_VERSION: 0x06000064 +FULU_FORK_EPOCH: 18446744073709551615 # Time parameters # --------------------------------------------------------------- @@ -60,7 +61,6 @@ SHARD_COMMITTEE_PERIOD: 256 # 2**10 (= 1024) ~1.4 hour ETH1_FOLLOW_DISTANCE: 1024 - # Validator cycle # --------------------------------------------------------------- # 2**2 (= 4) @@ -76,7 +76,6 @@ MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT: 2 # 2**12 (= 4096) CHURN_LIMIT_QUOTIENT: 4096 - # Fork choice # --------------------------------------------------------------- # 40% @@ -124,4 +123,4 @@ BLOB_SIDECAR_SUBNET_COUNT: 6 CUSTODY_REQUIREMENT: 4 DATA_COLUMN_SIDECAR_SUBNET_COUNT: 128 NUMBER_OF_COLUMNS: 128 -SAMPLES_PER_SLOT: 8 \ No newline at end of file +SAMPLES_PER_SLOT: 8 diff --git a/common/eth2_network_config/built_in_network_configs/holesky/config.yaml b/common/eth2_network_config/built_in_network_configs/holesky/config.yaml index d67d77d3bea..6d344b5b524 100644 --- a/common/eth2_network_config/built_in_network_configs/holesky/config.yaml +++ b/common/eth2_network_config/built_in_network_configs/holesky/config.yaml @@ -12,7 +12,6 @@ GENESIS_FORK_VERSION: 0x01017000 # Genesis delay 5 mins GENESIS_DELAY: 300 - # Forking # --------------------------------------------------------------- # Some forks are disabled for now: @@ -37,6 +36,9 @@ DENEB_FORK_EPOCH: 29696 # Electra ELECTRA_FORK_VERSION: 0x06017000 ELECTRA_FORK_EPOCH: 18446744073709551615 +# Fulu +FULU_FORK_VERSION: 0x07017000 +FULU_FORK_EPOCH: 18446744073709551615 # Time parameters # --------------------------------------------------------------- @@ -51,7 +53,6 @@ SHARD_COMMITTEE_PERIOD: 256 # 2**11 (= 2,048) Eth1 blocks ~8 hours ETH1_FOLLOW_DISTANCE: 2048 - # Validator cycle # --------------------------------------------------------------- # 2**2 (= 4) @@ -128,4 +129,4 @@ BLOB_SIDECAR_SUBNET_COUNT: 6 CUSTODY_REQUIREMENT: 4 DATA_COLUMN_SIDECAR_SUBNET_COUNT: 128 NUMBER_OF_COLUMNS: 128 -SAMPLES_PER_SLOT: 8 \ No newline at end of file +SAMPLES_PER_SLOT: 8 diff --git a/common/eth2_network_config/built_in_network_configs/mainnet/config.yaml b/common/eth2_network_config/built_in_network_configs/mainnet/config.yaml index 18591fecdcd..244ddd564d2 100644 --- a/common/eth2_network_config/built_in_network_configs/mainnet/config.yaml +++ b/common/eth2_network_config/built_in_network_configs/mainnet/config.yaml @@ -18,8 +18,6 @@ TERMINAL_TOTAL_DIFFICULTY: 58750000000000000000000 TERMINAL_BLOCK_HASH: 0x0000000000000000000000000000000000000000000000000000000000000000 TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH: 18446744073709551615 - - # Genesis # --------------------------------------------------------------- # `2**14` (= 16,384) @@ -31,7 +29,6 @@ GENESIS_FORK_VERSION: 0x00000000 # 604800 seconds (7 days) GENESIS_DELAY: 604800 - # Forking # --------------------------------------------------------------- # Some forks are disabled for now: @@ -40,23 +37,25 @@ GENESIS_DELAY: 604800 # Altair ALTAIR_FORK_VERSION: 0x01000000 -ALTAIR_FORK_EPOCH: 74240 # Oct 27, 2021, 10:56:23am UTC +ALTAIR_FORK_EPOCH: 74240 # Oct 27, 2021, 10:56:23am UTC # Bellatrix BELLATRIX_FORK_VERSION: 0x02000000 -BELLATRIX_FORK_EPOCH: 144896 # Sept 6, 2022, 11:34:47am UTC +BELLATRIX_FORK_EPOCH: 144896 # Sept 6, 2022, 11:34:47am UTC # Capella CAPELLA_FORK_VERSION: 0x03000000 -CAPELLA_FORK_EPOCH: 194048 # April 12, 2023, 10:27:35pm UTC +CAPELLA_FORK_EPOCH: 194048 # April 12, 2023, 10:27:35pm UTC # Deneb DENEB_FORK_VERSION: 0x04000000 -DENEB_FORK_EPOCH: 269568 # March 13, 2024, 01:55:35pm UTC +DENEB_FORK_EPOCH: 269568 # March 13, 2024, 01:55:35pm UTC # Electra ELECTRA_FORK_VERSION: 0x05000000 ELECTRA_FORK_EPOCH: 18446744073709551615 +# Fulu +FULU_FORK_VERSION: 0x06000000 +FULU_FORK_EPOCH: 18446744073709551615 # PeerDAS EIP7594_FORK_EPOCH: 18446744073709551615 - # Time parameters # --------------------------------------------------------------- # 12 seconds @@ -70,7 +69,6 @@ SHARD_COMMITTEE_PERIOD: 256 # 2**11 (= 2,048) Eth1 blocks ~8 hours ETH1_FOLLOW_DISTANCE: 2048 - # Validator cycle # --------------------------------------------------------------- # 2**2 (= 4) @@ -97,7 +95,6 @@ REORG_PARENT_WEIGHT_THRESHOLD: 160 # `2` epochs REORG_MAX_EPOCHS_SINCE_FINALIZATION: 2 - # Deposit contract # --------------------------------------------------------------- # Ethereum PoW Mainnet @@ -105,7 +102,6 @@ DEPOSIT_CHAIN_ID: 1 DEPOSIT_NETWORK_ID: 1 DEPOSIT_CONTRACT_ADDRESS: 0x00000000219ab540356cBB839Cbe05303d7705Fa - # Networking # --------------------------------------------------------------- # `10 * 2**20` (= 10485760, 10 MiB) @@ -150,4 +146,4 @@ BLOB_SIDECAR_SUBNET_COUNT: 6 CUSTODY_REQUIREMENT: 4 DATA_COLUMN_SIDECAR_SUBNET_COUNT: 128 NUMBER_OF_COLUMNS: 128 -SAMPLES_PER_SLOT: 8 \ No newline at end of file +SAMPLES_PER_SLOT: 8 diff --git a/common/eth2_network_config/built_in_network_configs/sepolia/config.yaml b/common/eth2_network_config/built_in_network_configs/sepolia/config.yaml index b08a6180bf0..88f8359bd13 100644 --- a/common/eth2_network_config/built_in_network_configs/sepolia/config.yaml +++ b/common/eth2_network_config/built_in_network_configs/sepolia/config.yaml @@ -124,4 +124,4 @@ BLOB_SIDECAR_SUBNET_COUNT: 6 CUSTODY_REQUIREMENT: 4 DATA_COLUMN_SIDECAR_SUBNET_COUNT: 128 NUMBER_OF_COLUMNS: 128 -SAMPLES_PER_SLOT: 8 \ No newline at end of file +SAMPLES_PER_SLOT: 8 diff --git a/consensus/fork_choice/src/fork_choice.rs b/consensus/fork_choice/src/fork_choice.rs index 85704042df4..4c25be950b0 100644 --- a/consensus/fork_choice/src/fork_choice.rs +++ b/consensus/fork_choice/src/fork_choice.rs @@ -755,20 +755,15 @@ where if let Some((parent_justified, parent_finalized)) = parent_checkpoints { (parent_justified, parent_finalized) } else { - let justification_and_finalization_state = match block { - BeaconBlockRef::Electra(_) - | BeaconBlockRef::Deneb(_) - | BeaconBlockRef::Capella(_) - | BeaconBlockRef::Bellatrix(_) - | BeaconBlockRef::Altair(_) => { + let justification_and_finalization_state = + if block.fork_name_unchecked().altair_enabled() { // NOTE: Processing justification & finalization requires the progressive // balances cache, but we cannot initialize it here as we only have an // immutable reference. The state *should* have come straight from block // processing, which initialises the cache, but if we add other `on_block` // calls in future it could be worth passing a mutable reference. per_epoch_processing::altair::process_justification_and_finalization(state)? - } - BeaconBlockRef::Base(_) => { + } else { let mut validator_statuses = per_epoch_processing::base::ValidatorStatuses::new(state, spec) .map_err(Error::ValidatorStatuses)?; @@ -780,8 +775,7 @@ where &validator_statuses.total_balances, spec, )? - } - }; + }; ( justification_and_finalization_state.current_justified_checkpoint(), diff --git a/consensus/fork_choice/tests/tests.rs b/consensus/fork_choice/tests/tests.rs index ef017159a02..001b80fe113 100644 --- a/consensus/fork_choice/tests/tests.rs +++ b/consensus/fork_choice/tests/tests.rs @@ -1263,7 +1263,7 @@ async fn progressive_balances_cache_proposer_slashing() { // (`HeaderInvalid::ProposerSlashed`). The harness should be re-worked to successfully skip // the slot in this scenario rather than panic-ing. The same applies to // `progressive_balances_cache_attester_slashing`. - .apply_blocks(2) + .apply_blocks(1) .await .add_previous_epoch_proposer_slashing(MainnetEthSpec::slots_per_epoch()) .await diff --git a/consensus/state_processing/src/common/get_attestation_participation.rs b/consensus/state_processing/src/common/get_attestation_participation.rs index fc09dad1f4e..2c6fd3b215f 100644 --- a/consensus/state_processing/src/common/get_attestation_participation.rs +++ b/consensus/state_processing/src/common/get_attestation_participation.rs @@ -44,22 +44,15 @@ pub fn get_attestation_participation_flag_indices( if is_matching_source && inclusion_delay <= E::slots_per_epoch().integer_sqrt() { participation_flag_indices.push(TIMELY_SOURCE_FLAG_INDEX); } - match state { - &BeaconState::Base(_) - | &BeaconState::Altair(_) - | &BeaconState::Bellatrix(_) - | &BeaconState::Capella(_) => { - if is_matching_target && inclusion_delay <= E::slots_per_epoch() { - participation_flag_indices.push(TIMELY_TARGET_FLAG_INDEX); - } - } - &BeaconState::Deneb(_) | &BeaconState::Electra(_) => { - if is_matching_target { - // [Modified in Deneb:EIP7045] - participation_flag_indices.push(TIMELY_TARGET_FLAG_INDEX); - } + if state.fork_name_unchecked().deneb_enabled() { + if is_matching_target { + // [Modified in Deneb:EIP7045] + participation_flag_indices.push(TIMELY_TARGET_FLAG_INDEX); } + } else if is_matching_target && inclusion_delay <= E::slots_per_epoch() { + participation_flag_indices.push(TIMELY_TARGET_FLAG_INDEX); } + if is_matching_head && inclusion_delay == spec.min_attestation_inclusion_delay { participation_flag_indices.push(TIMELY_HEAD_FLAG_INDEX); } diff --git a/consensus/state_processing/src/common/slash_validator.rs b/consensus/state_processing/src/common/slash_validator.rs index 80d857cc009..bd60f16014c 100644 --- a/consensus/state_processing/src/common/slash_validator.rs +++ b/consensus/state_processing/src/common/slash_validator.rs @@ -55,15 +55,12 @@ pub fn slash_validator( let whistleblower_index = opt_whistleblower_index.unwrap_or(proposer_index); let whistleblower_reward = validator_effective_balance .safe_div(spec.whistleblower_reward_quotient_for_state(state))?; - let proposer_reward = match state { - BeaconState::Base(_) => whistleblower_reward.safe_div(spec.proposer_reward_quotient)?, - BeaconState::Altair(_) - | BeaconState::Bellatrix(_) - | BeaconState::Capella(_) - | BeaconState::Deneb(_) - | BeaconState::Electra(_) => whistleblower_reward + let proposer_reward = if state.fork_name_unchecked().altair_enabled() { + whistleblower_reward .safe_mul(PROPOSER_WEIGHT)? - .safe_div(WEIGHT_DENOMINATOR)?, + .safe_div(WEIGHT_DENOMINATOR)? + } else { + whistleblower_reward.safe_div(spec.proposer_reward_quotient)? }; // Ensure the whistleblower index is in the validator registry. diff --git a/consensus/state_processing/src/genesis.rs b/consensus/state_processing/src/genesis.rs index ccff3d80c03..10723ecc519 100644 --- a/consensus/state_processing/src/genesis.rs +++ b/consensus/state_processing/src/genesis.rs @@ -4,7 +4,7 @@ use super::per_block_processing::{ use crate::common::DepositDataTree; use crate::upgrade::electra::upgrade_state_to_electra; use crate::upgrade::{ - upgrade_to_altair, upgrade_to_bellatrix, upgrade_to_capella, upgrade_to_deneb, + upgrade_to_altair, upgrade_to_bellatrix, upgrade_to_capella, upgrade_to_deneb, upgrade_to_fulu, }; use safe_arith::{ArithError, SafeArith}; use std::sync::Arc; @@ -135,11 +135,27 @@ pub fn initialize_beacon_state_from_eth1( // Override latest execution payload header. // See https://github.com/ethereum/consensus-specs/blob/dev/specs/capella/beacon-chain.md#testing - if let Some(ExecutionPayloadHeader::Electra(header)) = execution_payload_header { + if let Some(ExecutionPayloadHeader::Electra(ref header)) = execution_payload_header { *state.latest_execution_payload_header_electra_mut()? = header.clone(); } } + // Upgrade to fulu if configured from genesis. + if spec + .fulu_fork_epoch + .is_some_and(|fork_epoch| fork_epoch == E::genesis_epoch()) + { + upgrade_to_fulu(&mut state, spec)?; + + // Remove intermediate Electra fork from `state.fork`. + state.fork_mut().previous_version = spec.fulu_fork_version; + + // Override latest execution payload header. + if let Some(ExecutionPayloadHeader::Fulu(header)) = execution_payload_header { + *state.latest_execution_payload_header_fulu_mut()? = header.clone(); + } + } + // Now that we have our validators, initialize the caches (including the committees) state.build_caches(spec)?; diff --git a/consensus/state_processing/src/per_block_processing.rs b/consensus/state_processing/src/per_block_processing.rs index 436f4934b90..22e0a5eab30 100644 --- a/consensus/state_processing/src/per_block_processing.rs +++ b/consensus/state_processing/src/per_block_processing.rs @@ -442,6 +442,12 @@ pub fn process_execution_payload>( _ => return Err(BlockProcessingError::IncorrectStateType), } } + ExecutionPayloadHeaderRefMut::Fulu(header_mut) => { + match payload.to_execution_payload_header() { + ExecutionPayloadHeader::Fulu(header) => *header_mut = header, + _ => return Err(BlockProcessingError::IncorrectStateType), + } + } } Ok(()) @@ -453,15 +459,17 @@ pub fn process_execution_payload>( /// repeatedly write code to treat these errors as false. /// https://github.com/ethereum/consensus-specs/blob/dev/specs/bellatrix/beacon-chain.md#is_merge_transition_complete pub fn is_merge_transition_complete(state: &BeaconState) -> bool { - match state { + if state.fork_name_unchecked().capella_enabled() { + true + } else if state.fork_name_unchecked().bellatrix_enabled() { // We must check defaultness against the payload header with 0x0 roots, as that's what's meant // by `ExecutionPayloadHeader()` in the spec. - BeaconState::Bellatrix(_) => state + state .latest_execution_payload_header() .map(|header| !header.is_default_with_zero_roots()) - .unwrap_or(false), - BeaconState::Electra(_) | BeaconState::Deneb(_) | BeaconState::Capella(_) => true, - BeaconState::Base(_) | BeaconState::Altair(_) => false, + .unwrap_or(false) + } else { + false } } /// https://github.com/ethereum/consensus-specs/blob/dev/specs/bellatrix/beacon-chain.md#is_merge_transition_block @@ -603,66 +611,65 @@ pub fn process_withdrawals>( payload: Payload::Ref<'_>, spec: &ChainSpec, ) -> Result<(), BlockProcessingError> { - match state { - BeaconState::Capella(_) | BeaconState::Deneb(_) | BeaconState::Electra(_) => { - let (expected_withdrawals, partial_withdrawals_count) = - get_expected_withdrawals(state, spec)?; - let expected_root = expected_withdrawals.tree_hash_root(); - let withdrawals_root = payload.withdrawals_root()?; - - if expected_root != withdrawals_root { - return Err(BlockProcessingError::WithdrawalsRootMismatch { - expected: expected_root, - found: withdrawals_root, - }); - } + if state.fork_name_unchecked().capella_enabled() { + let (expected_withdrawals, partial_withdrawals_count) = + get_expected_withdrawals(state, spec)?; + let expected_root = expected_withdrawals.tree_hash_root(); + let withdrawals_root = payload.withdrawals_root()?; + + if expected_root != withdrawals_root { + return Err(BlockProcessingError::WithdrawalsRootMismatch { + expected: expected_root, + found: withdrawals_root, + }); + } - for withdrawal in expected_withdrawals.iter() { - decrease_balance( - state, - withdrawal.validator_index as usize, - withdrawal.amount, - )?; - } + for withdrawal in expected_withdrawals.iter() { + decrease_balance( + state, + withdrawal.validator_index as usize, + withdrawal.amount, + )?; + } - // Update pending partial withdrawals [New in Electra:EIP7251] - if let Some(partial_withdrawals_count) = partial_withdrawals_count { - // TODO(electra): Use efficient pop_front after milhouse release https://github.com/sigp/milhouse/pull/38 - let new_partial_withdrawals = state - .pending_partial_withdrawals()? - .iter_from(partial_withdrawals_count)? - .cloned() - .collect::>(); - *state.pending_partial_withdrawals_mut()? = List::new(new_partial_withdrawals)?; - } + // Update pending partial withdrawals [New in Electra:EIP7251] + if let Some(partial_withdrawals_count) = partial_withdrawals_count { + // TODO(electra): Use efficient pop_front after milhouse release https://github.com/sigp/milhouse/pull/38 + let new_partial_withdrawals = state + .pending_partial_withdrawals()? + .iter_from(partial_withdrawals_count)? + .cloned() + .collect::>(); + *state.pending_partial_withdrawals_mut()? = List::new(new_partial_withdrawals)?; + } - // Update the next withdrawal index if this block contained withdrawals - if let Some(latest_withdrawal) = expected_withdrawals.last() { - *state.next_withdrawal_index_mut()? = latest_withdrawal.index.safe_add(1)?; - - // Update the next validator index to start the next withdrawal sweep - if expected_withdrawals.len() == E::max_withdrawals_per_payload() { - // Next sweep starts after the latest withdrawal's validator index - let next_validator_index = latest_withdrawal - .validator_index - .safe_add(1)? - .safe_rem(state.validators().len() as u64)?; - *state.next_withdrawal_validator_index_mut()? = next_validator_index; - } - } + // Update the next withdrawal index if this block contained withdrawals + if let Some(latest_withdrawal) = expected_withdrawals.last() { + *state.next_withdrawal_index_mut()? = latest_withdrawal.index.safe_add(1)?; - // Advance sweep by the max length of the sweep if there was not a full set of withdrawals - if expected_withdrawals.len() != E::max_withdrawals_per_payload() { - let next_validator_index = state - .next_withdrawal_validator_index()? - .safe_add(spec.max_validators_per_withdrawals_sweep)? + // Update the next validator index to start the next withdrawal sweep + if expected_withdrawals.len() == E::max_withdrawals_per_payload() { + // Next sweep starts after the latest withdrawal's validator index + let next_validator_index = latest_withdrawal + .validator_index + .safe_add(1)? .safe_rem(state.validators().len() as u64)?; *state.next_withdrawal_validator_index_mut()? = next_validator_index; } + } - Ok(()) + // Advance sweep by the max length of the sweep if there was not a full set of withdrawals + if expected_withdrawals.len() != E::max_withdrawals_per_payload() { + let next_validator_index = state + .next_withdrawal_validator_index()? + .safe_add(spec.max_validators_per_withdrawals_sweep)? + .safe_rem(state.validators().len() as u64)?; + *state.next_withdrawal_validator_index_mut()? = next_validator_index; } + + Ok(()) + } else { // these shouldn't even be encountered but they're here for completeness - BeaconState::Base(_) | BeaconState::Altair(_) | BeaconState::Bellatrix(_) => Ok(()), + Ok(()) } } diff --git a/consensus/state_processing/src/per_block_processing/process_operations.rs b/consensus/state_processing/src/per_block_processing/process_operations.rs index 22d8592364c..4977f7c7e9d 100644 --- a/consensus/state_processing/src/per_block_processing/process_operations.rs +++ b/consensus/state_processing/src/per_block_processing/process_operations.rs @@ -284,29 +284,22 @@ pub fn process_attestations>( ctxt: &mut ConsensusContext, spec: &ChainSpec, ) -> Result<(), BlockProcessingError> { - match block_body { - BeaconBlockBodyRef::Base(_) => { - base::process_attestations( - state, - block_body.attestations(), - verify_signatures, - ctxt, - spec, - )?; - } - BeaconBlockBodyRef::Altair(_) - | BeaconBlockBodyRef::Bellatrix(_) - | BeaconBlockBodyRef::Capella(_) - | BeaconBlockBodyRef::Deneb(_) - | BeaconBlockBodyRef::Electra(_) => { - altair_deneb::process_attestations( - state, - block_body.attestations(), - verify_signatures, - ctxt, - spec, - )?; - } + if state.fork_name_unchecked().altair_enabled() { + altair_deneb::process_attestations( + state, + block_body.attestations(), + verify_signatures, + ctxt, + spec, + )?; + } else { + base::process_attestations( + state, + block_body.attestations(), + verify_signatures, + ctxt, + spec, + )?; } Ok(()) } diff --git a/consensus/state_processing/src/per_block_processing/signature_sets.rs b/consensus/state_processing/src/per_block_processing/signature_sets.rs index 2e00ee03418..39f438f97f6 100644 --- a/consensus/state_processing/src/per_block_processing/signature_sets.rs +++ b/consensus/state_processing/src/per_block_processing/signature_sets.rs @@ -387,22 +387,20 @@ where let exit = &signed_exit.message; let proposer_index = exit.validator_index as usize; - let domain = match state { - BeaconState::Base(_) - | BeaconState::Altair(_) - | BeaconState::Bellatrix(_) - | BeaconState::Capella(_) => spec.get_domain( - exit.epoch, - Domain::VoluntaryExit, - &state.fork(), - state.genesis_validators_root(), - ), + let domain = if state.fork_name_unchecked().deneb_enabled() { // EIP-7044 - BeaconState::Deneb(_) | BeaconState::Electra(_) => spec.compute_domain( + spec.compute_domain( Domain::VoluntaryExit, spec.capella_fork_version, state.genesis_validators_root(), - ), + ) + } else { + spec.get_domain( + exit.epoch, + Domain::VoluntaryExit, + &state.fork(), + state.genesis_validators_root(), + ) }; let message = exit.signing_root(domain); diff --git a/consensus/state_processing/src/per_block_processing/verify_attestation.rs b/consensus/state_processing/src/per_block_processing/verify_attestation.rs index 6bfb51d475b..0b399bea6cf 100644 --- a/consensus/state_processing/src/per_block_processing/verify_attestation.rs +++ b/consensus/state_processing/src/per_block_processing/verify_attestation.rs @@ -32,21 +32,16 @@ pub fn verify_attestation_for_block_inclusion<'ctxt, E: EthSpec>( attestation: data.slot, } ); - match state { - BeaconState::Base(_) - | BeaconState::Altair(_) - | BeaconState::Bellatrix(_) - | BeaconState::Capella(_) => { - verify!( - state.slot() <= data.slot.safe_add(E::slots_per_epoch())?, - Invalid::IncludedTooLate { - state: state.slot(), - attestation: data.slot, - } - ); - } + if state.fork_name_unchecked().deneb_enabled() { // [Modified in Deneb:EIP7045] - BeaconState::Deneb(_) | BeaconState::Electra(_) => {} + } else { + verify!( + state.slot() <= data.slot.safe_add(E::slots_per_epoch())?, + Invalid::IncludedTooLate { + state: state.slot(), + attestation: data.slot, + } + ); } verify_attestation_for_state(state, attestation, ctxt, verify_signatures, spec) diff --git a/consensus/state_processing/src/per_epoch_processing.rs b/consensus/state_processing/src/per_epoch_processing.rs index 55e8853f3f8..41c30c49318 100644 --- a/consensus/state_processing/src/per_epoch_processing.rs +++ b/consensus/state_processing/src/per_epoch_processing.rs @@ -41,13 +41,10 @@ pub fn process_epoch( .fork_name(spec) .map_err(Error::InconsistentStateFork)?; - match state { - BeaconState::Base(_) => base::process_epoch(state, spec), - BeaconState::Altair(_) - | BeaconState::Bellatrix(_) - | BeaconState::Capella(_) - | BeaconState::Deneb(_) - | BeaconState::Electra(_) => altair::process_epoch(state, spec), + if state.fork_name_unchecked().altair_enabled() { + altair::process_epoch(state, spec) + } else { + base::process_epoch(state, spec) } } diff --git a/consensus/state_processing/src/per_slot_processing.rs b/consensus/state_processing/src/per_slot_processing.rs index 6554423199f..af1cce602c3 100644 --- a/consensus/state_processing/src/per_slot_processing.rs +++ b/consensus/state_processing/src/per_slot_processing.rs @@ -1,6 +1,6 @@ use crate::upgrade::{ upgrade_to_altair, upgrade_to_bellatrix, upgrade_to_capella, upgrade_to_deneb, - upgrade_to_electra, + upgrade_to_electra, upgrade_to_fulu, }; use crate::{per_epoch_processing::EpochProcessingSummary, *}; use safe_arith::{ArithError, SafeArith}; @@ -71,6 +71,11 @@ pub fn per_slot_processing( upgrade_to_electra(state, spec)?; } + // Fulu. + if spec.fulu_fork_epoch == Some(state.current_epoch()) { + upgrade_to_fulu(state, spec)?; + } + // Additionally build all caches so that all valid states that are advanced always have // committee caches built, and we don't have to worry about initialising them at higher // layers. diff --git a/consensus/state_processing/src/upgrade.rs b/consensus/state_processing/src/upgrade.rs index 93cafa73d03..88bc87849f3 100644 --- a/consensus/state_processing/src/upgrade.rs +++ b/consensus/state_processing/src/upgrade.rs @@ -3,9 +3,11 @@ pub mod bellatrix; pub mod capella; pub mod deneb; pub mod electra; +pub mod fulu; pub use altair::upgrade_to_altair; pub use bellatrix::upgrade_to_bellatrix; pub use capella::upgrade_to_capella; pub use deneb::upgrade_to_deneb; pub use electra::upgrade_to_electra; +pub use fulu::upgrade_to_fulu; diff --git a/consensus/state_processing/src/upgrade/fulu.rs b/consensus/state_processing/src/upgrade/fulu.rs new file mode 100644 index 00000000000..6e0cd3fa9de --- /dev/null +++ b/consensus/state_processing/src/upgrade/fulu.rs @@ -0,0 +1,94 @@ +use std::mem; +use types::{BeaconState, BeaconStateError as Error, BeaconStateFulu, ChainSpec, EthSpec, Fork}; + +/// Transform a `Electra` state into an `Fulu` state. +pub fn upgrade_to_fulu( + pre_state: &mut BeaconState, + spec: &ChainSpec, +) -> Result<(), Error> { + let _epoch = pre_state.current_epoch(); + + let post = upgrade_state_to_fulu(pre_state, spec)?; + + *pre_state = post; + + Ok(()) +} + +pub fn upgrade_state_to_fulu( + pre_state: &mut BeaconState, + spec: &ChainSpec, +) -> Result, Error> { + let epoch = pre_state.current_epoch(); + let pre = pre_state.as_electra_mut()?; + // Where possible, use something like `mem::take` to move fields from behind the &mut + // reference. For other fields that don't have a good default value, use `clone`. + // + // Fixed size vectors get cloned because replacing them would require the same size + // allocation as cloning. + let post = BeaconState::Fulu(BeaconStateFulu { + // Versioning + genesis_time: pre.genesis_time, + genesis_validators_root: pre.genesis_validators_root, + slot: pre.slot, + fork: Fork { + previous_version: pre.fork.current_version, + current_version: spec.fulu_fork_version, + epoch, + }, + // History + latest_block_header: pre.latest_block_header.clone(), + block_roots: pre.block_roots.clone(), + state_roots: pre.state_roots.clone(), + historical_roots: mem::take(&mut pre.historical_roots), + // Eth1 + eth1_data: pre.eth1_data.clone(), + eth1_data_votes: mem::take(&mut pre.eth1_data_votes), + eth1_deposit_index: pre.eth1_deposit_index, + // Registry + validators: mem::take(&mut pre.validators), + balances: mem::take(&mut pre.balances), + // Randomness + randao_mixes: pre.randao_mixes.clone(), + // Slashings + slashings: pre.slashings.clone(), + // `Participation + previous_epoch_participation: mem::take(&mut pre.previous_epoch_participation), + current_epoch_participation: mem::take(&mut pre.current_epoch_participation), + // Finality + justification_bits: pre.justification_bits.clone(), + previous_justified_checkpoint: pre.previous_justified_checkpoint, + current_justified_checkpoint: pre.current_justified_checkpoint, + finalized_checkpoint: pre.finalized_checkpoint, + // Inactivity + inactivity_scores: mem::take(&mut pre.inactivity_scores), + // Sync committees + current_sync_committee: pre.current_sync_committee.clone(), + next_sync_committee: pre.next_sync_committee.clone(), + // Execution + latest_execution_payload_header: pre.latest_execution_payload_header.upgrade_to_fulu(), + // Capella + next_withdrawal_index: pre.next_withdrawal_index, + next_withdrawal_validator_index: pre.next_withdrawal_validator_index, + historical_summaries: pre.historical_summaries.clone(), + // Electra + deposit_requests_start_index: pre.deposit_requests_start_index, + deposit_balance_to_consume: pre.deposit_balance_to_consume, + exit_balance_to_consume: pre.exit_balance_to_consume, + earliest_exit_epoch: pre.earliest_exit_epoch, + consolidation_balance_to_consume: pre.consolidation_balance_to_consume, + earliest_consolidation_epoch: pre.earliest_consolidation_epoch, + pending_deposits: pre.pending_deposits.clone(), + pending_partial_withdrawals: pre.pending_partial_withdrawals.clone(), + pending_consolidations: pre.pending_consolidations.clone(), + // Caches + total_active_balance: pre.total_active_balance, + progressive_balances_cache: mem::take(&mut pre.progressive_balances_cache), + committee_caches: mem::take(&mut pre.committee_caches), + pubkey_cache: mem::take(&mut pre.pubkey_cache), + exit_cache: mem::take(&mut pre.exit_cache), + slashings_cache: mem::take(&mut pre.slashings_cache), + epoch_cache: mem::take(&mut pre.epoch_cache), + }); + Ok(post) +} diff --git a/consensus/types/presets/gnosis/fulu.yaml b/consensus/types/presets/gnosis/fulu.yaml new file mode 100644 index 00000000000..35a7c98fbf3 --- /dev/null +++ b/consensus/types/presets/gnosis/fulu.yaml @@ -0,0 +1,3 @@ +# Gnosis preset - Fulu + +FULU_PLACEHOLDER: 0 diff --git a/consensus/types/presets/mainnet/fulu.yaml b/consensus/types/presets/mainnet/fulu.yaml new file mode 100644 index 00000000000..8aa9ccdcc37 --- /dev/null +++ b/consensus/types/presets/mainnet/fulu.yaml @@ -0,0 +1,3 @@ +# Mainnet preset - Fulu + +FULU_PLACEHOLDER: 0 diff --git a/consensus/types/presets/minimal/fulu.yaml b/consensus/types/presets/minimal/fulu.yaml new file mode 100644 index 00000000000..121c9858f41 --- /dev/null +++ b/consensus/types/presets/minimal/fulu.yaml @@ -0,0 +1,3 @@ +# Minimal preset - Fulu + +FULU_PLACEHOLDER: 0 diff --git a/consensus/types/src/beacon_block.rs b/consensus/types/src/beacon_block.rs index 801b7dd1c78..d72550aa121 100644 --- a/consensus/types/src/beacon_block.rs +++ b/consensus/types/src/beacon_block.rs @@ -16,7 +16,7 @@ use self::indexed_attestation::{IndexedAttestationBase, IndexedAttestationElectr /// A block of the `BeaconChain`. #[superstruct( - variants(Base, Altair, Bellatrix, Capella, Deneb, Electra), + variants(Base, Altair, Bellatrix, Capella, Deneb, Electra, Fulu), variant_attributes( derive( Debug, @@ -75,6 +75,8 @@ pub struct BeaconBlock = FullPayload pub body: BeaconBlockBodyDeneb, #[superstruct(only(Electra), partial_getter(rename = "body_electra"))] pub body: BeaconBlockBodyElectra, + #[superstruct(only(Fulu), partial_getter(rename = "body_fulu"))] + pub body: BeaconBlockBodyFulu, } pub type BlindedBeaconBlock = BeaconBlock>; @@ -127,8 +129,9 @@ impl> BeaconBlock { /// Usually it's better to prefer `from_ssz_bytes` which will decode the correct variant based /// on the fork slot. pub fn any_from_ssz_bytes(bytes: &[u8]) -> Result { - BeaconBlockElectra::from_ssz_bytes(bytes) - .map(BeaconBlock::Electra) + BeaconBlockFulu::from_ssz_bytes(bytes) + .map(BeaconBlock::Fulu) + .or_else(|_| BeaconBlockElectra::from_ssz_bytes(bytes).map(BeaconBlock::Electra)) .or_else(|_| BeaconBlockDeneb::from_ssz_bytes(bytes).map(BeaconBlock::Deneb)) .or_else(|_| BeaconBlockCapella::from_ssz_bytes(bytes).map(BeaconBlock::Capella)) .or_else(|_| BeaconBlockBellatrix::from_ssz_bytes(bytes).map(BeaconBlock::Bellatrix)) @@ -226,6 +229,7 @@ impl<'a, E: EthSpec, Payload: AbstractExecPayload> BeaconBlockRef<'a, E, Payl BeaconBlockRef::Capella { .. } => ForkName::Capella, BeaconBlockRef::Deneb { .. } => ForkName::Deneb, BeaconBlockRef::Electra { .. } => ForkName::Electra, + BeaconBlockRef::Fulu { .. } => ForkName::Fulu, } } @@ -704,6 +708,110 @@ impl> EmptyBlock for BeaconBlockElec } } +impl> BeaconBlockFulu { + /// Return a Fulu block where the block has maximum size. + pub fn full(spec: &ChainSpec) -> Self { + let base_block: BeaconBlockBase<_, Payload> = BeaconBlockBase::full(spec); + let indexed_attestation: IndexedAttestationElectra = IndexedAttestationElectra { + attesting_indices: VariableList::new(vec![0_u64; E::MaxValidatorsPerSlot::to_usize()]) + .unwrap(), + data: AttestationData::default(), + signature: AggregateSignature::empty(), + }; + let attester_slashings = vec![ + AttesterSlashingElectra { + attestation_1: indexed_attestation.clone(), + attestation_2: indexed_attestation, + }; + E::max_attester_slashings_electra() + ] + .into(); + let attestation = AttestationElectra { + aggregation_bits: BitList::with_capacity(E::MaxValidatorsPerSlot::to_usize()).unwrap(), + data: AttestationData::default(), + signature: AggregateSignature::empty(), + committee_bits: BitVector::new(), + }; + let mut attestations_electra = vec![]; + for _ in 0..E::MaxAttestationsElectra::to_usize() { + attestations_electra.push(attestation.clone()); + } + + let bls_to_execution_changes = vec![ + SignedBlsToExecutionChange { + message: BlsToExecutionChange { + validator_index: 0, + from_bls_pubkey: PublicKeyBytes::empty(), + to_execution_address: Address::ZERO, + }, + signature: Signature::empty() + }; + E::max_bls_to_execution_changes() + ] + .into(); + let sync_aggregate = SyncAggregate { + sync_committee_signature: AggregateSignature::empty(), + sync_committee_bits: BitVector::default(), + }; + BeaconBlockFulu { + slot: spec.genesis_slot, + proposer_index: 0, + parent_root: Hash256::zero(), + state_root: Hash256::zero(), + body: BeaconBlockBodyFulu { + proposer_slashings: base_block.body.proposer_slashings, + attester_slashings, + attestations: attestations_electra.into(), + deposits: base_block.body.deposits, + voluntary_exits: base_block.body.voluntary_exits, + bls_to_execution_changes, + sync_aggregate, + randao_reveal: Signature::empty(), + eth1_data: Eth1Data { + deposit_root: Hash256::zero(), + block_hash: Hash256::zero(), + deposit_count: 0, + }, + graffiti: Graffiti::default(), + execution_payload: Payload::Fulu::default(), + blob_kzg_commitments: VariableList::empty(), + execution_requests: ExecutionRequests::default(), + }, + } + } +} + +impl> EmptyBlock for BeaconBlockFulu { + /// Returns an empty Fulu block to be used during genesis. + fn empty(spec: &ChainSpec) -> Self { + BeaconBlockFulu { + slot: spec.genesis_slot, + proposer_index: 0, + parent_root: Hash256::zero(), + state_root: Hash256::zero(), + body: BeaconBlockBodyFulu { + randao_reveal: Signature::empty(), + eth1_data: Eth1Data { + deposit_root: Hash256::zero(), + block_hash: Hash256::zero(), + deposit_count: 0, + }, + graffiti: Graffiti::default(), + proposer_slashings: VariableList::empty(), + attester_slashings: VariableList::empty(), + attestations: VariableList::empty(), + deposits: VariableList::empty(), + voluntary_exits: VariableList::empty(), + sync_aggregate: SyncAggregate::empty(), + execution_payload: Payload::Fulu::default(), + bls_to_execution_changes: VariableList::empty(), + blob_kzg_commitments: VariableList::empty(), + execution_requests: ExecutionRequests::default(), + }, + } + } +} + // We can convert pre-Bellatrix blocks without payloads into blocks "with" payloads. impl From>> for BeaconBlockBase> @@ -785,6 +893,7 @@ impl_from!(BeaconBlockBellatrix, >, >, |b impl_from!(BeaconBlockCapella, >, >, |body: BeaconBlockBodyCapella<_, _>| body.into()); impl_from!(BeaconBlockDeneb, >, >, |body: BeaconBlockBodyDeneb<_, _>| body.into()); impl_from!(BeaconBlockElectra, >, >, |body: BeaconBlockBodyElectra<_, _>| body.into()); +impl_from!(BeaconBlockFulu, >, >, |body: BeaconBlockBodyFulu<_, _>| body.into()); // We can clone blocks with payloads to blocks without payloads, without cloning the payload. macro_rules! impl_clone_as_blinded { @@ -818,6 +927,7 @@ impl_clone_as_blinded!(BeaconBlockBellatrix, >, >, >); impl_clone_as_blinded!(BeaconBlockDeneb, >, >); impl_clone_as_blinded!(BeaconBlockElectra, >, >); +impl_clone_as_blinded!(BeaconBlockFulu, >, >); // A reference to a full beacon block can be cloned into a blinded beacon block, without cloning the // execution payload. @@ -988,6 +1098,26 @@ mod tests { }); } + #[test] + fn roundtrip_fulu_block() { + let rng = &mut XorShiftRng::from_seed([42; 16]); + let spec = &ForkName::Fulu.make_genesis_spec(MainnetEthSpec::default_spec()); + + let inner_block = BeaconBlockFulu { + slot: Slot::random_for_test(rng), + proposer_index: u64::random_for_test(rng), + parent_root: Hash256::random_for_test(rng), + state_root: Hash256::random_for_test(rng), + body: BeaconBlockBodyFulu::random_for_test(rng), + }; + + let block = BeaconBlock::Fulu(inner_block.clone()); + + test_ssz_tree_hash_pair_with(&block, &inner_block, |bytes| { + BeaconBlock::from_ssz_bytes(bytes, spec) + }); + } + #[test] fn decode_base_and_altair() { type E = MainnetEthSpec; @@ -1007,11 +1137,14 @@ mod tests { let deneb_slot = deneb_epoch.start_slot(E::slots_per_epoch()); let electra_epoch = deneb_epoch + 1; let electra_slot = electra_epoch.start_slot(E::slots_per_epoch()); + let fulu_epoch = electra_epoch + 1; + let fulu_slot = fulu_epoch.start_slot(E::slots_per_epoch()); spec.altair_fork_epoch = Some(altair_epoch); spec.capella_fork_epoch = Some(capella_epoch); spec.deneb_fork_epoch = Some(deneb_epoch); spec.electra_fork_epoch = Some(electra_epoch); + spec.fulu_fork_epoch = Some(fulu_epoch); // BeaconBlockBase { @@ -1122,5 +1255,29 @@ mod tests { BeaconBlock::from_ssz_bytes(&bad_block.as_ssz_bytes(), &spec) .expect_err("bad electra block cannot be decoded"); } + + // BeaconBlockFulu + { + let good_block = BeaconBlock::Fulu(BeaconBlockFulu { + slot: fulu_slot, + ..<_>::random_for_test(rng) + }); + // It's invalid to have a Fulu block with a epoch lower than the fork epoch. + let _bad_block = { + let mut bad = good_block.clone(); + *bad.slot_mut() = electra_slot; + bad + }; + + assert_eq!( + BeaconBlock::from_ssz_bytes(&good_block.as_ssz_bytes(), &spec) + .expect("good fulu block can be decoded"), + good_block + ); + // TODO(fulu): Uncomment once Fulu has features since without features + // and with an Electra slot it decodes successfully to Electra. + //BeaconBlock::from_ssz_bytes(&bad_block.as_ssz_bytes(), &spec) + // .expect_err("bad fulu block cannot be decoded"); + } } } diff --git a/consensus/types/src/beacon_block_body.rs b/consensus/types/src/beacon_block_body.rs index f7a701fed6d..a198cdf28f4 100644 --- a/consensus/types/src/beacon_block_body.rs +++ b/consensus/types/src/beacon_block_body.rs @@ -30,7 +30,7 @@ pub const BLOB_KZG_COMMITMENTS_INDEX: usize = 11; /// /// This *superstruct* abstracts over the hard-fork. #[superstruct( - variants(Base, Altair, Bellatrix, Capella, Deneb, Electra), + variants(Base, Altair, Bellatrix, Capella, Deneb, Electra, Fulu), variant_attributes( derive( Debug, @@ -58,6 +58,7 @@ pub const BLOB_KZG_COMMITMENTS_INDEX: usize = 11; Capella(metastruct(mappings(beacon_block_body_capella_fields(groups(fields))))), Deneb(metastruct(mappings(beacon_block_body_deneb_fields(groups(fields))))), Electra(metastruct(mappings(beacon_block_body_electra_fields(groups(fields))))), + Fulu(metastruct(mappings(beacon_block_body_fulu_fields(groups(fields))))) ), cast_error(ty = "Error", expr = "Error::IncorrectStateVariant"), partial_getter_error(ty = "Error", expr = "Error::IncorrectStateVariant") @@ -77,7 +78,10 @@ pub struct BeaconBlockBody = FullPay partial_getter(rename = "attester_slashings_base") )] pub attester_slashings: VariableList, E::MaxAttesterSlashings>, - #[superstruct(only(Electra), partial_getter(rename = "attester_slashings_electra"))] + #[superstruct( + only(Electra, Fulu), + partial_getter(rename = "attester_slashings_electra") + )] pub attester_slashings: VariableList, E::MaxAttesterSlashingsElectra>, #[superstruct( @@ -85,11 +89,11 @@ pub struct BeaconBlockBody = FullPay partial_getter(rename = "attestations_base") )] pub attestations: VariableList, E::MaxAttestations>, - #[superstruct(only(Electra), partial_getter(rename = "attestations_electra"))] + #[superstruct(only(Electra, Fulu), partial_getter(rename = "attestations_electra"))] pub attestations: VariableList, E::MaxAttestationsElectra>, pub deposits: VariableList, pub voluntary_exits: VariableList, - #[superstruct(only(Altair, Bellatrix, Capella, Deneb, Electra))] + #[superstruct(only(Altair, Bellatrix, Capella, Deneb, Electra, Fulu))] pub sync_aggregate: SyncAggregate, // We flatten the execution payload so that serde can use the name of the inner type, // either `execution_payload` for full payloads, or `execution_payload_header` for blinded @@ -109,12 +113,15 @@ pub struct BeaconBlockBody = FullPay #[superstruct(only(Electra), partial_getter(rename = "execution_payload_electra"))] #[serde(flatten)] pub execution_payload: Payload::Electra, - #[superstruct(only(Capella, Deneb, Electra))] + #[superstruct(only(Fulu), partial_getter(rename = "execution_payload_fulu"))] + #[serde(flatten)] + pub execution_payload: Payload::Fulu, + #[superstruct(only(Capella, Deneb, Electra, Fulu))] pub bls_to_execution_changes: VariableList, - #[superstruct(only(Deneb, Electra))] + #[superstruct(only(Deneb, Electra, Fulu))] pub blob_kzg_commitments: KzgCommitments, - #[superstruct(only(Electra))] + #[superstruct(only(Electra, Fulu))] pub execution_requests: ExecutionRequests, #[superstruct(only(Base, Altair))] #[metastruct(exclude_from(fields))] @@ -144,6 +151,7 @@ impl<'a, E: EthSpec, Payload: AbstractExecPayload> BeaconBlockBodyRef<'a, E, Self::Capella(body) => Ok(Payload::Ref::from(&body.execution_payload)), Self::Deneb(body) => Ok(Payload::Ref::from(&body.execution_payload)), Self::Electra(body) => Ok(Payload::Ref::from(&body.execution_payload)), + Self::Fulu(body) => Ok(Payload::Ref::from(&body.execution_payload)), } } @@ -174,6 +182,10 @@ impl<'a, E: EthSpec, Payload: AbstractExecPayload> BeaconBlockBodyRef<'a, E, beacon_block_body_electra_fields!(body, |_, field| leaves .push(field.tree_hash_root())); } + Self::Fulu(body) => { + beacon_block_body_fulu_fields!(body, |_, field| leaves + .push(field.tree_hash_root())); + } } leaves } @@ -202,7 +214,7 @@ impl<'a, E: EthSpec, Payload: AbstractExecPayload> BeaconBlockBodyRef<'a, E, Self::Base(_) | Self::Altair(_) | Self::Bellatrix(_) | Self::Capella(_) => { Err(Error::IncorrectStateVariant) } - Self::Deneb(_) | Self::Electra(_) => { + Self::Deneb(_) | Self::Electra(_) | Self::Fulu(_) => { // We compute the branches by generating 2 merkle trees: // 1. Merkle tree for the `blob_kzg_commitments` List object // 2. Merkle tree for the `BeaconBlockBody` container @@ -294,6 +306,7 @@ impl<'a, E: EthSpec, Payload: AbstractExecPayload> BeaconBlockBodyRef<'a, E, Self::Capella(body) => body.attestations.len(), Self::Deneb(body) => body.attestations.len(), Self::Electra(body) => body.attestations.len(), + Self::Fulu(body) => body.attestations.len(), } } @@ -305,6 +318,7 @@ impl<'a, E: EthSpec, Payload: AbstractExecPayload> BeaconBlockBodyRef<'a, E, Self::Capella(body) => body.attester_slashings.len(), Self::Deneb(body) => body.attester_slashings.len(), Self::Electra(body) => body.attester_slashings.len(), + Self::Fulu(body) => body.attester_slashings.len(), } } @@ -316,6 +330,7 @@ impl<'a, E: EthSpec, Payload: AbstractExecPayload> BeaconBlockBodyRef<'a, E, Self::Capella(body) => Box::new(body.attestations.iter().map(AttestationRef::Base)), Self::Deneb(body) => Box::new(body.attestations.iter().map(AttestationRef::Base)), Self::Electra(body) => Box::new(body.attestations.iter().map(AttestationRef::Electra)), + Self::Fulu(body) => Box::new(body.attestations.iter().map(AttestationRef::Electra)), } } @@ -351,6 +366,11 @@ impl<'a, E: EthSpec, Payload: AbstractExecPayload> BeaconBlockBodyRef<'a, E, .iter() .map(AttesterSlashingRef::Electra), ), + Self::Fulu(body) => Box::new( + body.attester_slashings + .iter() + .map(AttesterSlashingRef::Electra), + ), } } } @@ -376,6 +396,9 @@ impl<'a, E: EthSpec, Payload: AbstractExecPayload> BeaconBlockBodyRefMut<'a, Self::Electra(body) => { Box::new(body.attestations.iter_mut().map(AttestationRefMut::Electra)) } + Self::Fulu(body) => { + Box::new(body.attestations.iter_mut().map(AttestationRefMut::Electra)) + } } } } @@ -390,6 +413,7 @@ impl> BeaconBlockBodyRef<'_, E, Payl BeaconBlockBodyRef::Capella { .. } => ForkName::Capella, BeaconBlockBodyRef::Deneb { .. } => ForkName::Deneb, BeaconBlockBodyRef::Electra { .. } => ForkName::Electra, + BeaconBlockBodyRef::Fulu { .. } => ForkName::Fulu, } } } @@ -704,6 +728,52 @@ impl From>> } } +impl From>> + for ( + BeaconBlockBodyFulu>, + Option>, + ) +{ + fn from(body: BeaconBlockBodyFulu>) -> Self { + let BeaconBlockBodyFulu { + randao_reveal, + eth1_data, + graffiti, + proposer_slashings, + attester_slashings, + attestations, + deposits, + voluntary_exits, + sync_aggregate, + execution_payload: FullPayloadFulu { execution_payload }, + bls_to_execution_changes, + blob_kzg_commitments, + execution_requests, + } = body; + + ( + BeaconBlockBodyFulu { + randao_reveal, + eth1_data, + graffiti, + proposer_slashings, + attester_slashings, + attestations, + deposits, + voluntary_exits, + sync_aggregate, + execution_payload: BlindedPayloadFulu { + execution_payload_header: From::from(&execution_payload), + }, + bls_to_execution_changes, + blob_kzg_commitments: blob_kzg_commitments.clone(), + execution_requests, + }, + Some(execution_payload), + ) + } +} + // We can clone a full block into a blinded block, without cloning the payload. impl BeaconBlockBodyBase> { pub fn clone_as_blinded(&self) -> BeaconBlockBodyBase> { @@ -859,6 +929,44 @@ impl BeaconBlockBodyElectra> { } } +impl BeaconBlockBodyFulu> { + pub fn clone_as_blinded(&self) -> BeaconBlockBodyFulu> { + let BeaconBlockBodyFulu { + randao_reveal, + eth1_data, + graffiti, + proposer_slashings, + attester_slashings, + attestations, + deposits, + voluntary_exits, + sync_aggregate, + execution_payload: FullPayloadFulu { execution_payload }, + bls_to_execution_changes, + blob_kzg_commitments, + execution_requests, + } = self; + + BeaconBlockBodyFulu { + randao_reveal: randao_reveal.clone(), + eth1_data: eth1_data.clone(), + graffiti: *graffiti, + proposer_slashings: proposer_slashings.clone(), + attester_slashings: attester_slashings.clone(), + attestations: attestations.clone(), + deposits: deposits.clone(), + voluntary_exits: voluntary_exits.clone(), + sync_aggregate: sync_aggregate.clone(), + execution_payload: BlindedPayloadFulu { + execution_payload_header: execution_payload.into(), + }, + bls_to_execution_changes: bls_to_execution_changes.clone(), + blob_kzg_commitments: blob_kzg_commitments.clone(), + execution_requests: execution_requests.clone(), + } + } +} + impl From>> for ( BeaconBlockBody>, diff --git a/consensus/types/src/beacon_state.rs b/consensus/types/src/beacon_state.rs index 05f28744faf..de6077bf940 100644 --- a/consensus/types/src/beacon_state.rs +++ b/consensus/types/src/beacon_state.rs @@ -223,7 +223,7 @@ impl From for Hash256 { /// /// https://github.com/sigp/milhouse/issues/43 #[superstruct( - variants(Base, Altair, Bellatrix, Capella, Deneb, Electra), + variants(Base, Altair, Bellatrix, Capella, Deneb, Electra, Fulu), variant_attributes( derive( Derivative, @@ -326,6 +326,20 @@ impl From for Hash256 { groups(tree_lists) )), num_fields(all()), + )), + Fulu(metastruct( + mappings( + map_beacon_state_fulu_fields(), + map_beacon_state_fulu_tree_list_fields(mutable, fallible, groups(tree_lists)), + map_beacon_state_fulu_tree_list_fields_immutable(groups(tree_lists)), + ), + bimappings(bimap_beacon_state_fulu_tree_list_fields( + other_type = "BeaconStateFulu", + self_mutable, + fallible, + groups(tree_lists) + )), + num_fields(all()), )) ), cast_error(ty = "Error", expr = "Error::IncorrectStateVariant"), @@ -408,11 +422,11 @@ where // Participation (Altair and later) #[compare_fields(as_iter)] - #[superstruct(only(Altair, Bellatrix, Capella, Deneb, Electra))] + #[superstruct(only(Altair, Bellatrix, Capella, Deneb, Electra, Fulu))] #[test_random(default)] #[compare_fields(as_iter)] pub previous_epoch_participation: List, - #[superstruct(only(Altair, Bellatrix, Capella, Deneb, Electra))] + #[superstruct(only(Altair, Bellatrix, Capella, Deneb, Electra, Fulu))] #[test_random(default)] pub current_epoch_participation: List, @@ -432,15 +446,15 @@ where // Inactivity #[serde(with = "ssz_types::serde_utils::quoted_u64_var_list")] - #[superstruct(only(Altair, Bellatrix, Capella, Deneb, Electra))] + #[superstruct(only(Altair, Bellatrix, Capella, Deneb, Electra, Fulu))] #[test_random(default)] pub inactivity_scores: List, // Light-client sync committees - #[superstruct(only(Altair, Bellatrix, Capella, Deneb, Electra))] + #[superstruct(only(Altair, Bellatrix, Capella, Deneb, Electra, Fulu))] #[metastruct(exclude_from(tree_lists))] pub current_sync_committee: Arc>, - #[superstruct(only(Altair, Bellatrix, Capella, Deneb, Electra))] + #[superstruct(only(Altair, Bellatrix, Capella, Deneb, Electra, Fulu))] #[metastruct(exclude_from(tree_lists))] pub next_sync_committee: Arc>, @@ -469,56 +483,62 @@ where )] #[metastruct(exclude_from(tree_lists))] pub latest_execution_payload_header: ExecutionPayloadHeaderElectra, + #[superstruct( + only(Fulu), + partial_getter(rename = "latest_execution_payload_header_fulu") + )] + #[metastruct(exclude_from(tree_lists))] + pub latest_execution_payload_header: ExecutionPayloadHeaderFulu, // Capella - #[superstruct(only(Capella, Deneb, Electra), partial_getter(copy))] + #[superstruct(only(Capella, Deneb, Electra, Fulu), partial_getter(copy))] #[serde(with = "serde_utils::quoted_u64")] #[metastruct(exclude_from(tree_lists))] pub next_withdrawal_index: u64, - #[superstruct(only(Capella, Deneb, Electra), partial_getter(copy))] + #[superstruct(only(Capella, Deneb, Electra, Fulu), partial_getter(copy))] #[serde(with = "serde_utils::quoted_u64")] #[metastruct(exclude_from(tree_lists))] pub next_withdrawal_validator_index: u64, // Deep history valid from Capella onwards. - #[superstruct(only(Capella, Deneb, Electra))] + #[superstruct(only(Capella, Deneb, Electra, Fulu))] #[test_random(default)] pub historical_summaries: List, // Electra - #[superstruct(only(Electra), partial_getter(copy))] + #[superstruct(only(Electra, Fulu), partial_getter(copy))] #[metastruct(exclude_from(tree_lists))] #[serde(with = "serde_utils::quoted_u64")] pub deposit_requests_start_index: u64, - #[superstruct(only(Electra), partial_getter(copy))] + #[superstruct(only(Electra, Fulu), partial_getter(copy))] #[metastruct(exclude_from(tree_lists))] #[serde(with = "serde_utils::quoted_u64")] pub deposit_balance_to_consume: u64, - #[superstruct(only(Electra), partial_getter(copy))] + #[superstruct(only(Electra, Fulu), partial_getter(copy))] #[metastruct(exclude_from(tree_lists))] #[serde(with = "serde_utils::quoted_u64")] pub exit_balance_to_consume: u64, - #[superstruct(only(Electra), partial_getter(copy))] + #[superstruct(only(Electra, Fulu), partial_getter(copy))] #[metastruct(exclude_from(tree_lists))] pub earliest_exit_epoch: Epoch, - #[superstruct(only(Electra), partial_getter(copy))] + #[superstruct(only(Electra, Fulu), partial_getter(copy))] #[metastruct(exclude_from(tree_lists))] #[serde(with = "serde_utils::quoted_u64")] pub consolidation_balance_to_consume: u64, - #[superstruct(only(Electra), partial_getter(copy))] + #[superstruct(only(Electra, Fulu), partial_getter(copy))] #[metastruct(exclude_from(tree_lists))] pub earliest_consolidation_epoch: Epoch, #[compare_fields(as_iter)] #[test_random(default)] - #[superstruct(only(Electra))] + #[superstruct(only(Electra, Fulu))] pub pending_deposits: List, #[compare_fields(as_iter)] #[test_random(default)] - #[superstruct(only(Electra))] + #[superstruct(only(Electra, Fulu))] pub pending_partial_withdrawals: List, #[compare_fields(as_iter)] #[test_random(default)] - #[superstruct(only(Electra))] + #[superstruct(only(Electra, Fulu))] pub pending_consolidations: List, // Caching (not in the spec) @@ -659,6 +679,7 @@ impl BeaconState { BeaconState::Capella { .. } => ForkName::Capella, BeaconState::Deneb { .. } => ForkName::Deneb, BeaconState::Electra { .. } => ForkName::Electra, + BeaconState::Fulu { .. } => ForkName::Fulu, } } @@ -948,6 +969,9 @@ impl BeaconState { BeaconState::Electra(state) => Ok(ExecutionPayloadHeaderRef::Electra( &state.latest_execution_payload_header, )), + BeaconState::Fulu(state) => Ok(ExecutionPayloadHeaderRef::Fulu( + &state.latest_execution_payload_header, + )), } } @@ -968,6 +992,9 @@ impl BeaconState { BeaconState::Electra(state) => Ok(ExecutionPayloadHeaderRefMut::Electra( &mut state.latest_execution_payload_header, )), + BeaconState::Fulu(state) => Ok(ExecutionPayloadHeaderRefMut::Fulu( + &mut state.latest_execution_payload_header, + )), } } @@ -1481,6 +1508,16 @@ impl BeaconState { &mut state.exit_cache, &mut state.epoch_cache, )), + BeaconState::Fulu(state) => Ok(( + &mut state.validators, + &mut state.balances, + &state.previous_epoch_participation, + &state.current_epoch_participation, + &mut state.inactivity_scores, + &mut state.progressive_balances_cache, + &mut state.exit_cache, + &mut state.epoch_cache, + )), } } @@ -1662,10 +1699,12 @@ impl BeaconState { | BeaconState::Altair(_) | BeaconState::Bellatrix(_) | BeaconState::Capella(_) => self.get_validator_churn_limit(spec)?, - BeaconState::Deneb(_) | BeaconState::Electra(_) => std::cmp::min( - spec.max_per_epoch_activation_churn_limit, - self.get_validator_churn_limit(spec)?, - ), + BeaconState::Deneb(_) | BeaconState::Electra(_) | BeaconState::Fulu(_) => { + std::cmp::min( + spec.max_per_epoch_activation_churn_limit, + self.get_validator_churn_limit(spec)?, + ) + } }) } @@ -1783,6 +1822,7 @@ impl BeaconState { BeaconState::Capella(state) => Ok(&mut state.current_epoch_participation), BeaconState::Deneb(state) => Ok(&mut state.current_epoch_participation), BeaconState::Electra(state) => Ok(&mut state.current_epoch_participation), + BeaconState::Fulu(state) => Ok(&mut state.current_epoch_participation), } } else if epoch == previous_epoch { match self { @@ -1792,6 +1832,7 @@ impl BeaconState { BeaconState::Capella(state) => Ok(&mut state.previous_epoch_participation), BeaconState::Deneb(state) => Ok(&mut state.previous_epoch_participation), BeaconState::Electra(state) => Ok(&mut state.previous_epoch_participation), + BeaconState::Fulu(state) => Ok(&mut state.previous_epoch_participation), } } else { Err(BeaconStateError::EpochOutOfBounds) @@ -2045,6 +2086,11 @@ impl BeaconState { } ); } + Self::Fulu(self_inner) => { + map_beacon_state_fulu_tree_list_fields_immutable!(self_inner, |_, self_field| { + any_pending_mutations |= self_field.has_pending_updates(); + }); + } }; any_pending_mutations } @@ -2238,12 +2284,29 @@ impl BeaconState { exit_balance_to_consume .safe_add_assign(additional_epochs.safe_mul(per_epoch_churn)?)?; } - let state = self.as_electra_mut()?; - // Consume the balance and update state variables - state.exit_balance_to_consume = exit_balance_to_consume.safe_sub(exit_balance)?; - state.earliest_exit_epoch = earliest_exit_epoch; + match self { + BeaconState::Base(_) + | BeaconState::Altair(_) + | BeaconState::Bellatrix(_) + | BeaconState::Capella(_) + | BeaconState::Deneb(_) => Err(Error::IncorrectStateVariant), + BeaconState::Electra(_) => { + let state = self.as_electra_mut()?; + + // Consume the balance and update state variables + state.exit_balance_to_consume = exit_balance_to_consume.safe_sub(exit_balance)?; + state.earliest_exit_epoch = earliest_exit_epoch; + Ok(state.earliest_exit_epoch) + } + BeaconState::Fulu(_) => { + let state = self.as_fulu_mut()?; - Ok(state.earliest_exit_epoch) + // Consume the balance and update state variables + state.exit_balance_to_consume = exit_balance_to_consume.safe_sub(exit_balance)?; + state.earliest_exit_epoch = earliest_exit_epoch; + Ok(state.earliest_exit_epoch) + } + } } pub fn compute_consolidation_epoch_and_update_churn( @@ -2277,13 +2340,31 @@ impl BeaconState { consolidation_balance_to_consume .safe_add_assign(additional_epochs.safe_mul(per_epoch_consolidation_churn)?)?; } - // Consume the balance and update state variables - let state = self.as_electra_mut()?; - state.consolidation_balance_to_consume = - consolidation_balance_to_consume.safe_sub(consolidation_balance)?; - state.earliest_consolidation_epoch = earliest_consolidation_epoch; - - Ok(state.earliest_consolidation_epoch) + match self { + BeaconState::Base(_) + | BeaconState::Altair(_) + | BeaconState::Bellatrix(_) + | BeaconState::Capella(_) + | BeaconState::Deneb(_) => Err(Error::IncorrectStateVariant), + BeaconState::Electra(_) => { + let state = self.as_electra_mut()?; + + // Consume the balance and update state variables. + state.consolidation_balance_to_consume = + consolidation_balance_to_consume.safe_sub(consolidation_balance)?; + state.earliest_consolidation_epoch = earliest_consolidation_epoch; + Ok(state.earliest_consolidation_epoch) + } + BeaconState::Fulu(_) => { + let state = self.as_fulu_mut()?; + + // Consume the balance and update state variables. + state.consolidation_balance_to_consume = + consolidation_balance_to_consume.safe_sub(consolidation_balance)?; + state.earliest_consolidation_epoch = earliest_consolidation_epoch; + Ok(state.earliest_consolidation_epoch) + } + } } #[allow(clippy::arithmetic_side_effects)] @@ -2339,6 +2420,14 @@ impl BeaconState { ); } (Self::Electra(_), _) => (), + (Self::Fulu(self_inner), Self::Fulu(base_inner)) => { + bimap_beacon_state_fulu_tree_list_fields!( + self_inner, + base_inner, + |_, self_field, base_field| { self_field.rebase_on(base_field) } + ); + } + (Self::Fulu(_), _) => (), } // Use sync committees from `base` if they are equal. @@ -2411,6 +2500,7 @@ impl BeaconState { ForkName::Capella => BeaconStateCapella::::NUM_FIELDS.next_power_of_two(), ForkName::Deneb => BeaconStateDeneb::::NUM_FIELDS.next_power_of_two(), ForkName::Electra => BeaconStateElectra::::NUM_FIELDS.next_power_of_two(), + ForkName::Fulu => BeaconStateFulu::::NUM_FIELDS.next_power_of_two(), } } @@ -2459,6 +2549,9 @@ impl BeaconState { Self::Electra(inner) => { map_beacon_state_electra_tree_list_fields!(inner, |_, x| { x.apply_updates() }) } + Self::Fulu(inner) => { + map_beacon_state_fulu_tree_list_fields!(inner, |_, x| { x.apply_updates() }) + } } Ok(()) } @@ -2554,6 +2647,11 @@ impl BeaconState { leaves.push(field.tree_hash_root()); }); } + BeaconState::Fulu(state) => { + map_beacon_state_fulu_fields!(state, |_, field| { + leaves.push(field.tree_hash_root()); + }); + } }; leaves @@ -2611,6 +2709,7 @@ impl CompareFields for BeaconState { (BeaconState::Capella(x), BeaconState::Capella(y)) => x.compare_fields(y), (BeaconState::Deneb(x), BeaconState::Deneb(y)) => x.compare_fields(y), (BeaconState::Electra(x), BeaconState::Electra(y)) => x.compare_fields(y), + (BeaconState::Fulu(x), BeaconState::Fulu(y)) => x.compare_fields(y), _ => panic!("compare_fields: mismatched state variants",), } } diff --git a/consensus/types/src/beacon_state/progressive_balances_cache.rs b/consensus/types/src/beacon_state/progressive_balances_cache.rs index bc258ef68de..8e8a1a6aa94 100644 --- a/consensus/types/src/beacon_state/progressive_balances_cache.rs +++ b/consensus/types/src/beacon_state/progressive_balances_cache.rs @@ -285,12 +285,5 @@ impl ProgressiveBalancesCache { /// `ProgressiveBalancesCache` is only enabled from `Altair` as it uses Altair-specific logic. pub fn is_progressive_balances_enabled(state: &BeaconState) -> bool { - match state { - BeaconState::Base(_) => false, - BeaconState::Altair(_) - | BeaconState::Bellatrix(_) - | BeaconState::Capella(_) - | BeaconState::Deneb(_) - | BeaconState::Electra(_) => true, - } + state.fork_name_unchecked().altair_enabled() } diff --git a/consensus/types/src/builder_bid.rs b/consensus/types/src/builder_bid.rs index 9885f78474f..2ce46ca704b 100644 --- a/consensus/types/src/builder_bid.rs +++ b/consensus/types/src/builder_bid.rs @@ -1,8 +1,9 @@ use crate::beacon_block_body::KzgCommitments; use crate::{ ChainSpec, EthSpec, ExecutionPayloadHeaderBellatrix, ExecutionPayloadHeaderCapella, - ExecutionPayloadHeaderDeneb, ExecutionPayloadHeaderElectra, ExecutionPayloadHeaderRef, - ExecutionPayloadHeaderRefMut, ForkName, ForkVersionDeserialize, SignedRoot, Uint256, + ExecutionPayloadHeaderDeneb, ExecutionPayloadHeaderElectra, ExecutionPayloadHeaderFulu, + ExecutionPayloadHeaderRef, ExecutionPayloadHeaderRefMut, ForkName, ForkVersionDeserialize, + SignedRoot, Uint256, }; use bls::PublicKeyBytes; use bls::Signature; @@ -11,7 +12,7 @@ use superstruct::superstruct; use tree_hash_derive::TreeHash; #[superstruct( - variants(Bellatrix, Capella, Deneb, Electra), + variants(Bellatrix, Capella, Deneb, Electra, Fulu), variant_attributes( derive(PartialEq, Debug, Serialize, Deserialize, TreeHash, Clone), serde(bound = "E: EthSpec", deny_unknown_fields) @@ -31,7 +32,9 @@ pub struct BuilderBid { pub header: ExecutionPayloadHeaderDeneb, #[superstruct(only(Electra), partial_getter(rename = "header_electra"))] pub header: ExecutionPayloadHeaderElectra, - #[superstruct(only(Deneb, Electra))] + #[superstruct(only(Fulu), partial_getter(rename = "header_fulu"))] + pub header: ExecutionPayloadHeaderFulu, + #[superstruct(only(Deneb, Electra, Fulu))] pub blob_kzg_commitments: KzgCommitments, #[serde(with = "serde_utils::quoted_u256")] pub value: Uint256, @@ -85,6 +88,7 @@ impl ForkVersionDeserialize for BuilderBid { ForkName::Capella => Self::Capella(serde_json::from_value(value).map_err(convert_err)?), ForkName::Deneb => Self::Deneb(serde_json::from_value(value).map_err(convert_err)?), ForkName::Electra => Self::Electra(serde_json::from_value(value).map_err(convert_err)?), + ForkName::Fulu => Self::Fulu(serde_json::from_value(value).map_err(convert_err)?), ForkName::Base | ForkName::Altair => { return Err(serde::de::Error::custom(format!( "BuilderBid failed to deserialize: unsupported fork '{}'", diff --git a/consensus/types/src/chain_spec.rs b/consensus/types/src/chain_spec.rs index 9d3308cf234..f0bfeba680b 100644 --- a/consensus/types/src/chain_spec.rs +++ b/consensus/types/src/chain_spec.rs @@ -192,6 +192,14 @@ pub struct ChainSpec { pub min_per_epoch_churn_limit_electra: u64, pub max_per_epoch_activation_exit_churn_limit: u64, + /* + * Fulu hard fork params + */ + pub fulu_fork_version: [u8; 4], + /// The Fulu fork epoch is optional, with `None` representing "Fulu never happens". + pub fulu_fork_epoch: Option, + pub fulu_placeholder: u64, + /* * DAS params */ @@ -313,17 +321,20 @@ impl ChainSpec { /// Returns the name of the fork which is active at `epoch`. pub fn fork_name_at_epoch(&self, epoch: Epoch) -> ForkName { - match self.electra_fork_epoch { - Some(fork_epoch) if epoch >= fork_epoch => ForkName::Electra, - _ => match self.deneb_fork_epoch { - Some(fork_epoch) if epoch >= fork_epoch => ForkName::Deneb, - _ => match self.capella_fork_epoch { - Some(fork_epoch) if epoch >= fork_epoch => ForkName::Capella, - _ => match self.bellatrix_fork_epoch { - Some(fork_epoch) if epoch >= fork_epoch => ForkName::Bellatrix, - _ => match self.altair_fork_epoch { - Some(fork_epoch) if epoch >= fork_epoch => ForkName::Altair, - _ => ForkName::Base, + match self.fulu_fork_epoch { + Some(fork_epoch) if epoch >= fork_epoch => ForkName::Fulu, + _ => match self.electra_fork_epoch { + Some(fork_epoch) if epoch >= fork_epoch => ForkName::Electra, + _ => match self.deneb_fork_epoch { + Some(fork_epoch) if epoch >= fork_epoch => ForkName::Deneb, + _ => match self.capella_fork_epoch { + Some(fork_epoch) if epoch >= fork_epoch => ForkName::Capella, + _ => match self.bellatrix_fork_epoch { + Some(fork_epoch) if epoch >= fork_epoch => ForkName::Bellatrix, + _ => match self.altair_fork_epoch { + Some(fork_epoch) if epoch >= fork_epoch => ForkName::Altair, + _ => ForkName::Base, + }, }, }, }, @@ -340,6 +351,7 @@ impl ChainSpec { ForkName::Capella => self.capella_fork_version, ForkName::Deneb => self.deneb_fork_version, ForkName::Electra => self.electra_fork_version, + ForkName::Fulu => self.fulu_fork_version, } } @@ -352,6 +364,7 @@ impl ChainSpec { ForkName::Capella => self.capella_fork_epoch, ForkName::Deneb => self.deneb_fork_epoch, ForkName::Electra => self.electra_fork_epoch, + ForkName::Fulu => self.fulu_fork_epoch, } } @@ -802,6 +815,13 @@ impl ChainSpec { }) .expect("calculation does not overflow"), + /* + * Fulu hard fork params + */ + fulu_fork_version: [0x06, 0x00, 0x00, 0x00], + fulu_fork_epoch: None, + fulu_placeholder: 0, + /* * DAS params */ @@ -917,6 +937,9 @@ impl ChainSpec { u64::checked_pow(2, 7)?.checked_mul(u64::checked_pow(10, 9)?) }) .expect("calculation does not overflow"), + // Fulu + fulu_fork_version: [0x06, 0x00, 0x00, 0x01], + fulu_fork_epoch: None, // PeerDAS eip7594_fork_epoch: None, // Other @@ -1121,6 +1144,13 @@ impl ChainSpec { }) .expect("calculation does not overflow"), + /* + * Fulu hard fork params + */ + fulu_fork_version: [0x06, 0x00, 0x00, 0x64], + fulu_fork_epoch: None, + fulu_placeholder: 0, + /* * DAS params */ @@ -1255,6 +1285,14 @@ pub struct Config { #[serde(deserialize_with = "deserialize_fork_epoch")] pub electra_fork_epoch: Option>, + #[serde(default = "default_fulu_fork_version")] + #[serde(with = "serde_utils::bytes_4_hex")] + fulu_fork_version: [u8; 4], + #[serde(default)] + #[serde(serialize_with = "serialize_fork_epoch")] + #[serde(deserialize_with = "deserialize_fork_epoch")] + pub fulu_fork_epoch: Option>, + #[serde(default)] #[serde(serialize_with = "serialize_fork_epoch")] #[serde(deserialize_with = "deserialize_fork_epoch")] @@ -1392,6 +1430,11 @@ fn default_electra_fork_version() -> [u8; 4] { [0xff, 0xff, 0xff, 0xff] } +fn default_fulu_fork_version() -> [u8; 4] { + // This value shouldn't be used. + [0xff, 0xff, 0xff, 0xff] +} + /// Placeholder value: 2^256-2^10 (115792089237316195423570985008687907853269984665640564039457584007913129638912). /// /// Taken from https://github.com/ethereum/consensus-specs/blob/d5e4828aecafaf1c57ef67a5f23c4ae7b08c5137/configs/mainnet.yaml#L15-L16 @@ -1655,6 +1698,11 @@ impl Config { .electra_fork_epoch .map(|epoch| MaybeQuoted { value: epoch }), + fulu_fork_version: spec.fulu_fork_version, + fulu_fork_epoch: spec + .fulu_fork_epoch + .map(|epoch| MaybeQuoted { value: epoch }), + eip7594_fork_epoch: spec .eip7594_fork_epoch .map(|epoch| MaybeQuoted { value: epoch }), @@ -1738,6 +1786,8 @@ impl Config { deneb_fork_version, electra_fork_epoch, electra_fork_version, + fulu_fork_epoch, + fulu_fork_version, eip7594_fork_epoch, seconds_per_slot, seconds_per_eth1_block, @@ -1801,6 +1851,8 @@ impl Config { deneb_fork_version, electra_fork_epoch: electra_fork_epoch.map(|q| q.value), electra_fork_version, + fulu_fork_epoch: fulu_fork_epoch.map(|q| q.value), + fulu_fork_version, eip7594_fork_epoch: eip7594_fork_epoch.map(|q| q.value), seconds_per_slot, seconds_per_eth1_block, diff --git a/consensus/types/src/config_and_preset.rs b/consensus/types/src/config_and_preset.rs index c80d678b2a3..235bf202382 100644 --- a/consensus/types/src/config_and_preset.rs +++ b/consensus/types/src/config_and_preset.rs @@ -1,6 +1,6 @@ use crate::{ consts::altair, consts::deneb, AltairPreset, BasePreset, BellatrixPreset, CapellaPreset, - ChainSpec, Config, DenebPreset, ElectraPreset, EthSpec, ForkName, + ChainSpec, Config, DenebPreset, ElectraPreset, EthSpec, ForkName, FuluPreset, }; use maplit::hashmap; use serde::{Deserialize, Serialize}; @@ -12,7 +12,7 @@ use superstruct::superstruct; /// /// Mostly useful for the API. #[superstruct( - variants(Capella, Deneb, Electra), + variants(Deneb, Electra, Fulu), variant_attributes(derive(Serialize, Deserialize, Debug, PartialEq, Clone)) )] #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] @@ -29,12 +29,14 @@ pub struct ConfigAndPreset { pub bellatrix_preset: BellatrixPreset, #[serde(flatten)] pub capella_preset: CapellaPreset, - #[superstruct(only(Deneb, Electra))] #[serde(flatten)] pub deneb_preset: DenebPreset, - #[superstruct(only(Electra))] + #[superstruct(only(Electra, Fulu))] #[serde(flatten)] pub electra_preset: ElectraPreset, + #[superstruct(only(Fulu))] + #[serde(flatten)] + pub fulu_preset: FuluPreset, /// The `extra_fields` map allows us to gracefully decode fields intended for future hard forks. #[serde(flatten)] pub extra_fields: HashMap, @@ -48,16 +50,17 @@ impl ConfigAndPreset { let altair_preset = AltairPreset::from_chain_spec::(spec); let bellatrix_preset = BellatrixPreset::from_chain_spec::(spec); let capella_preset = CapellaPreset::from_chain_spec::(spec); + let deneb_preset = DenebPreset::from_chain_spec::(spec); let extra_fields = get_extra_fields(spec); - if spec.electra_fork_epoch.is_some() + if spec.fulu_fork_epoch.is_some() || fork_name.is_none() - || fork_name == Some(ForkName::Electra) + || fork_name == Some(ForkName::Fulu) { - let deneb_preset = DenebPreset::from_chain_spec::(spec); let electra_preset = ElectraPreset::from_chain_spec::(spec); + let fulu_preset = FuluPreset::from_chain_spec::(spec); - ConfigAndPreset::Electra(ConfigAndPresetElectra { + ConfigAndPreset::Fulu(ConfigAndPresetFulu { config, base_preset, altair_preset, @@ -65,29 +68,33 @@ impl ConfigAndPreset { capella_preset, deneb_preset, electra_preset, + fulu_preset, extra_fields, }) - } else if spec.deneb_fork_epoch.is_some() + } else if spec.electra_fork_epoch.is_some() || fork_name.is_none() - || fork_name == Some(ForkName::Deneb) + || fork_name == Some(ForkName::Electra) { - let deneb_preset = DenebPreset::from_chain_spec::(spec); - ConfigAndPreset::Deneb(ConfigAndPresetDeneb { + let electra_preset = ElectraPreset::from_chain_spec::(spec); + + ConfigAndPreset::Electra(ConfigAndPresetElectra { config, base_preset, altair_preset, bellatrix_preset, capella_preset, deneb_preset, + electra_preset, extra_fields, }) } else { - ConfigAndPreset::Capella(ConfigAndPresetCapella { + ConfigAndPreset::Deneb(ConfigAndPresetDeneb { config, base_preset, altair_preset, bellatrix_preset, capella_preset, + deneb_preset, extra_fields, }) } @@ -164,8 +171,8 @@ mod test { .write(false) .open(tmp_file.as_ref()) .expect("error while opening the file"); - let from: ConfigAndPresetElectra = + let from: ConfigAndPresetFulu = serde_yaml::from_reader(reader).expect("error while deserializing"); - assert_eq!(ConfigAndPreset::Electra(from), yamlconfig); + assert_eq!(ConfigAndPreset::Fulu(from), yamlconfig); } } diff --git a/consensus/types/src/execution_payload.rs b/consensus/types/src/execution_payload.rs index 9f16b676a6a..c619d614871 100644 --- a/consensus/types/src/execution_payload.rs +++ b/consensus/types/src/execution_payload.rs @@ -15,7 +15,7 @@ pub type Transactions = VariableList< pub type Withdrawals = VariableList::MaxWithdrawalsPerPayload>; #[superstruct( - variants(Bellatrix, Capella, Deneb, Electra), + variants(Bellatrix, Capella, Deneb, Electra, Fulu), variant_attributes( derive( Default, @@ -82,12 +82,12 @@ pub struct ExecutionPayload { pub block_hash: ExecutionBlockHash, #[serde(with = "ssz_types::serde_utils::list_of_hex_var_list")] pub transactions: Transactions, - #[superstruct(only(Capella, Deneb, Electra))] + #[superstruct(only(Capella, Deneb, Electra, Fulu))] pub withdrawals: Withdrawals, - #[superstruct(only(Deneb, Electra), partial_getter(copy))] + #[superstruct(only(Deneb, Electra, Fulu), partial_getter(copy))] #[serde(with = "serde_utils::quoted_u64")] pub blob_gas_used: u64, - #[superstruct(only(Deneb, Electra), partial_getter(copy))] + #[superstruct(only(Deneb, Electra, Fulu), partial_getter(copy))] #[serde(with = "serde_utils::quoted_u64")] pub excess_blob_gas: u64, } @@ -114,6 +114,7 @@ impl ExecutionPayload { ForkName::Capella => ExecutionPayloadCapella::from_ssz_bytes(bytes).map(Self::Capella), ForkName::Deneb => ExecutionPayloadDeneb::from_ssz_bytes(bytes).map(Self::Deneb), ForkName::Electra => ExecutionPayloadElectra::from_ssz_bytes(bytes).map(Self::Electra), + ForkName::Fulu => ExecutionPayloadFulu::from_ssz_bytes(bytes).map(Self::Fulu), } } @@ -166,6 +167,19 @@ impl ExecutionPayload { // Max size of variable length `withdrawals` field + (E::max_withdrawals_per_payload() * ::ssz_fixed_len()) } + + #[allow(clippy::arithmetic_side_effects)] + /// Returns the maximum size of an execution payload. + pub fn max_execution_payload_fulu_size() -> usize { + // Fixed part + ExecutionPayloadFulu::::default().as_ssz_bytes().len() + // Max size of variable length `extra_data` field + + (E::max_extra_data_bytes() * ::ssz_fixed_len()) + // Max size of variable length `transactions` field + + (E::max_transactions_per_payload() * (ssz::BYTES_PER_LENGTH_OFFSET + E::max_bytes_per_transaction())) + // Max size of variable length `withdrawals` field + + (E::max_withdrawals_per_payload() * ::ssz_fixed_len()) + } } impl ForkVersionDeserialize for ExecutionPayload { @@ -184,6 +198,7 @@ impl ForkVersionDeserialize for ExecutionPayload { ForkName::Capella => Self::Capella(serde_json::from_value(value).map_err(convert_err)?), ForkName::Deneb => Self::Deneb(serde_json::from_value(value).map_err(convert_err)?), ForkName::Electra => Self::Electra(serde_json::from_value(value).map_err(convert_err)?), + ForkName::Fulu => Self::Fulu(serde_json::from_value(value).map_err(convert_err)?), ForkName::Base | ForkName::Altair => { return Err(serde::de::Error::custom(format!( "ExecutionPayload failed to deserialize: unsupported fork '{}'", @@ -201,6 +216,7 @@ impl ExecutionPayload { ExecutionPayload::Capella(_) => ForkName::Capella, ExecutionPayload::Deneb(_) => ForkName::Deneb, ExecutionPayload::Electra(_) => ForkName::Electra, + ExecutionPayload::Fulu(_) => ForkName::Fulu, } } } diff --git a/consensus/types/src/execution_payload_header.rs b/consensus/types/src/execution_payload_header.rs index 4bfbfee9bf0..3012041b8b1 100644 --- a/consensus/types/src/execution_payload_header.rs +++ b/consensus/types/src/execution_payload_header.rs @@ -8,7 +8,7 @@ use tree_hash::TreeHash; use tree_hash_derive::TreeHash; #[superstruct( - variants(Bellatrix, Capella, Deneb, Electra), + variants(Bellatrix, Capella, Deneb, Electra, Fulu), variant_attributes( derive( Default, @@ -78,12 +78,12 @@ pub struct ExecutionPayloadHeader { pub block_hash: ExecutionBlockHash, #[superstruct(getter(copy))] pub transactions_root: Hash256, - #[superstruct(only(Capella, Deneb, Electra), partial_getter(copy))] + #[superstruct(only(Capella, Deneb, Electra, Fulu), partial_getter(copy))] pub withdrawals_root: Hash256, - #[superstruct(only(Deneb, Electra), partial_getter(copy))] + #[superstruct(only(Deneb, Electra, Fulu), partial_getter(copy))] #[serde(with = "serde_utils::quoted_u64")] pub blob_gas_used: u64, - #[superstruct(only(Deneb, Electra), partial_getter(copy))] + #[superstruct(only(Deneb, Electra, Fulu), partial_getter(copy))] #[serde(with = "serde_utils::quoted_u64")] pub excess_blob_gas: u64, } @@ -108,18 +108,18 @@ impl ExecutionPayloadHeader { ForkName::Electra => { ExecutionPayloadHeaderElectra::from_ssz_bytes(bytes).map(Self::Electra) } + ForkName::Fulu => ExecutionPayloadHeaderFulu::from_ssz_bytes(bytes).map(Self::Fulu), } } #[allow(clippy::arithmetic_side_effects)] pub fn ssz_max_var_len_for_fork(fork_name: ForkName) -> usize { - // Matching here in case variable fields are added in future forks. - match fork_name { - ForkName::Base | ForkName::Altair => 0, - ForkName::Bellatrix | ForkName::Capella | ForkName::Deneb | ForkName::Electra => { - // Max size of variable length `extra_data` field - E::max_extra_data_bytes() * ::ssz_fixed_len() - } + // TODO(newfork): Add a new case here if there are new variable fields + if fork_name.bellatrix_enabled() { + // Max size of variable length `extra_data` field + E::max_extra_data_bytes() * ::ssz_fixed_len() + } else { + 0 } } @@ -129,6 +129,7 @@ impl ExecutionPayloadHeader { ExecutionPayloadHeader::Capella(_) => ForkName::Capella, ExecutionPayloadHeader::Deneb(_) => ForkName::Deneb, ExecutionPayloadHeader::Electra(_) => ForkName::Electra, + ExecutionPayloadHeader::Fulu(_) => ForkName::Fulu, } } } @@ -212,6 +213,30 @@ impl ExecutionPayloadHeaderDeneb { } } +impl ExecutionPayloadHeaderElectra { + pub fn upgrade_to_fulu(&self) -> ExecutionPayloadHeaderFulu { + ExecutionPayloadHeaderFulu { + parent_hash: self.parent_hash, + fee_recipient: self.fee_recipient, + state_root: self.state_root, + receipts_root: self.receipts_root, + logs_bloom: self.logs_bloom.clone(), + prev_randao: self.prev_randao, + block_number: self.block_number, + gas_limit: self.gas_limit, + gas_used: self.gas_used, + timestamp: self.timestamp, + extra_data: self.extra_data.clone(), + base_fee_per_gas: self.base_fee_per_gas, + block_hash: self.block_hash, + transactions_root: self.transactions_root, + withdrawals_root: self.withdrawals_root, + blob_gas_used: self.blob_gas_used, + excess_blob_gas: self.excess_blob_gas, + } + } +} + impl<'a, E: EthSpec> From<&'a ExecutionPayloadBellatrix> for ExecutionPayloadHeaderBellatrix { fn from(payload: &'a ExecutionPayloadBellatrix) -> Self { Self { @@ -303,6 +328,30 @@ impl<'a, E: EthSpec> From<&'a ExecutionPayloadElectra> for ExecutionPayloadHe } } +impl<'a, E: EthSpec> From<&'a ExecutionPayloadFulu> for ExecutionPayloadHeaderFulu { + fn from(payload: &'a ExecutionPayloadFulu) -> Self { + Self { + parent_hash: payload.parent_hash, + fee_recipient: payload.fee_recipient, + state_root: payload.state_root, + receipts_root: payload.receipts_root, + logs_bloom: payload.logs_bloom.clone(), + prev_randao: payload.prev_randao, + block_number: payload.block_number, + gas_limit: payload.gas_limit, + gas_used: payload.gas_used, + timestamp: payload.timestamp, + extra_data: payload.extra_data.clone(), + base_fee_per_gas: payload.base_fee_per_gas, + block_hash: payload.block_hash, + transactions_root: payload.transactions.tree_hash_root(), + withdrawals_root: payload.withdrawals.tree_hash_root(), + blob_gas_used: payload.blob_gas_used, + excess_blob_gas: payload.excess_blob_gas, + } + } +} + // These impls are required to work around an inelegance in `to_execution_payload_header`. // They only clone headers so they should be relatively cheap. impl<'a, E: EthSpec> From<&'a Self> for ExecutionPayloadHeaderBellatrix { @@ -329,6 +378,12 @@ impl<'a, E: EthSpec> From<&'a Self> for ExecutionPayloadHeaderElectra { } } +impl<'a, E: EthSpec> From<&'a Self> for ExecutionPayloadHeaderFulu { + fn from(payload: &'a Self) -> Self { + payload.clone() + } +} + impl<'a, E: EthSpec> From> for ExecutionPayloadHeader { fn from(payload: ExecutionPayloadRef<'a, E>) -> Self { map_execution_payload_ref_into_execution_payload_header!( @@ -387,6 +442,9 @@ impl ExecutionPayloadHeaderRefMut<'_, E> { ExecutionPayloadHeaderRefMut::Electra(mut_ref) => { *mut_ref = header.try_into()?; } + ExecutionPayloadHeaderRefMut::Fulu(mut_ref) => { + *mut_ref = header.try_into()?; + } } Ok(()) } @@ -404,6 +462,16 @@ impl TryFrom> for ExecutionPayloadHeaderEl } } +impl TryFrom> for ExecutionPayloadHeaderFulu { + type Error = BeaconStateError; + fn try_from(header: ExecutionPayloadHeader) -> Result { + match header { + ExecutionPayloadHeader::Fulu(execution_payload_header) => Ok(execution_payload_header), + _ => Err(BeaconStateError::IncorrectStateVariant), + } + } +} + impl ForkVersionDeserialize for ExecutionPayloadHeader { fn deserialize_by_fork<'de, D: serde::Deserializer<'de>>( value: serde_json::value::Value, @@ -423,6 +491,7 @@ impl ForkVersionDeserialize for ExecutionPayloadHeader { ForkName::Capella => Self::Capella(serde_json::from_value(value).map_err(convert_err)?), ForkName::Deneb => Self::Deneb(serde_json::from_value(value).map_err(convert_err)?), ForkName::Electra => Self::Electra(serde_json::from_value(value).map_err(convert_err)?), + ForkName::Fulu => Self::Fulu(serde_json::from_value(value).map_err(convert_err)?), ForkName::Base | ForkName::Altair => { return Err(serde::de::Error::custom(format!( "ExecutionPayloadHeader failed to deserialize: unsupported fork '{}'", diff --git a/consensus/types/src/fork_context.rs b/consensus/types/src/fork_context.rs index 0f7f0eb769e..33f1c51d446 100644 --- a/consensus/types/src/fork_context.rs +++ b/consensus/types/src/fork_context.rs @@ -69,6 +69,13 @@ impl ForkContext { )); } + if spec.fulu_fork_epoch.is_some() { + fork_to_digest.push(( + ForkName::Fulu, + ChainSpec::compute_fork_digest(spec.fulu_fork_version, genesis_validators_root), + )); + } + let fork_to_digest: HashMap = fork_to_digest.into_iter().collect(); let digest_to_fork = fork_to_digest diff --git a/consensus/types/src/fork_name.rs b/consensus/types/src/fork_name.rs index 51a5b3813ba..b61e0a4d4a5 100644 --- a/consensus/types/src/fork_name.rs +++ b/consensus/types/src/fork_name.rs @@ -17,6 +17,7 @@ pub enum ForkName { Capella, Deneb, Electra, + Fulu, } impl ForkName { @@ -28,6 +29,7 @@ impl ForkName { ForkName::Capella, ForkName::Deneb, ForkName::Electra, + ForkName::Fulu, ] } @@ -38,6 +40,7 @@ impl ForkName { (ForkName::Capella, spec.capella_fork_epoch), (ForkName::Deneb, spec.deneb_fork_epoch), (ForkName::Electra, spec.electra_fork_epoch), + (ForkName::Fulu, spec.fulu_fork_epoch), ] } @@ -57,6 +60,7 @@ impl ForkName { spec.capella_fork_epoch = None; spec.deneb_fork_epoch = None; spec.electra_fork_epoch = None; + spec.fulu_fork_epoch = None; spec } ForkName::Altair => { @@ -65,6 +69,7 @@ impl ForkName { spec.capella_fork_epoch = None; spec.deneb_fork_epoch = None; spec.electra_fork_epoch = None; + spec.fulu_fork_epoch = None; spec } ForkName::Bellatrix => { @@ -73,6 +78,7 @@ impl ForkName { spec.capella_fork_epoch = None; spec.deneb_fork_epoch = None; spec.electra_fork_epoch = None; + spec.fulu_fork_epoch = None; spec } ForkName::Capella => { @@ -81,6 +87,7 @@ impl ForkName { spec.capella_fork_epoch = Some(Epoch::new(0)); spec.deneb_fork_epoch = None; spec.electra_fork_epoch = None; + spec.fulu_fork_epoch = None; spec } ForkName::Deneb => { @@ -89,6 +96,7 @@ impl ForkName { spec.capella_fork_epoch = Some(Epoch::new(0)); spec.deneb_fork_epoch = Some(Epoch::new(0)); spec.electra_fork_epoch = None; + spec.fulu_fork_epoch = None; spec } ForkName::Electra => { @@ -97,6 +105,16 @@ impl ForkName { spec.capella_fork_epoch = Some(Epoch::new(0)); spec.deneb_fork_epoch = Some(Epoch::new(0)); spec.electra_fork_epoch = Some(Epoch::new(0)); + spec.fulu_fork_epoch = None; + spec + } + ForkName::Fulu => { + spec.altair_fork_epoch = Some(Epoch::new(0)); + spec.bellatrix_fork_epoch = Some(Epoch::new(0)); + spec.capella_fork_epoch = Some(Epoch::new(0)); + spec.deneb_fork_epoch = Some(Epoch::new(0)); + spec.electra_fork_epoch = Some(Epoch::new(0)); + spec.fulu_fork_epoch = Some(Epoch::new(0)); spec } } @@ -113,6 +131,7 @@ impl ForkName { ForkName::Capella => Some(ForkName::Bellatrix), ForkName::Deneb => Some(ForkName::Capella), ForkName::Electra => Some(ForkName::Deneb), + ForkName::Fulu => Some(ForkName::Electra), } } @@ -126,7 +145,8 @@ impl ForkName { ForkName::Bellatrix => Some(ForkName::Capella), ForkName::Capella => Some(ForkName::Deneb), ForkName::Deneb => Some(ForkName::Electra), - ForkName::Electra => None, + ForkName::Electra => Some(ForkName::Fulu), + ForkName::Fulu => None, } } @@ -149,6 +169,10 @@ impl ForkName { pub fn electra_enabled(self) -> bool { self >= ForkName::Electra } + + pub fn fulu_enabled(self) -> bool { + self >= ForkName::Fulu + } } /// Map a fork name into a fork-versioned superstruct type like `BeaconBlock`. @@ -200,6 +224,10 @@ macro_rules! map_fork_name_with { let (value, extra_data) = $body; ($t::Electra(value), extra_data) } + ForkName::Fulu => { + let (value, extra_data) = $body; + ($t::Fulu(value), extra_data) + } } }; } @@ -215,6 +243,7 @@ impl FromStr for ForkName { "capella" => ForkName::Capella, "deneb" => ForkName::Deneb, "electra" => ForkName::Electra, + "fulu" => ForkName::Fulu, _ => return Err(format!("unknown fork name: {}", fork_name)), }) } @@ -229,6 +258,7 @@ impl Display for ForkName { ForkName::Capella => "capella".fmt(f), ForkName::Deneb => "deneb".fmt(f), ForkName::Electra => "electra".fmt(f), + ForkName::Fulu => "fulu".fmt(f), } } } diff --git a/consensus/types/src/lib.rs b/consensus/types/src/lib.rs index dd304c6296c..282f27a5179 100644 --- a/consensus/types/src/lib.rs +++ b/consensus/types/src/lib.rs @@ -126,13 +126,13 @@ pub use crate::attester_slashing::{ }; pub use crate::beacon_block::{ BeaconBlock, BeaconBlockAltair, BeaconBlockBase, BeaconBlockBellatrix, BeaconBlockCapella, - BeaconBlockDeneb, BeaconBlockElectra, BeaconBlockRef, BeaconBlockRefMut, BlindedBeaconBlock, - BlockImportSource, EmptyBlock, + BeaconBlockDeneb, BeaconBlockElectra, BeaconBlockFulu, BeaconBlockRef, BeaconBlockRefMut, + BlindedBeaconBlock, BlockImportSource, EmptyBlock, }; pub use crate::beacon_block_body::{ BeaconBlockBody, BeaconBlockBodyAltair, BeaconBlockBodyBase, BeaconBlockBodyBellatrix, - BeaconBlockBodyCapella, BeaconBlockBodyDeneb, BeaconBlockBodyElectra, BeaconBlockBodyRef, - BeaconBlockBodyRefMut, + BeaconBlockBodyCapella, BeaconBlockBodyDeneb, BeaconBlockBodyElectra, BeaconBlockBodyFulu, + BeaconBlockBodyRef, BeaconBlockBodyRefMut, }; pub use crate::beacon_block_header::BeaconBlockHeader; pub use crate::beacon_committee::{BeaconCommittee, OwnedBeaconCommittee}; @@ -142,7 +142,7 @@ pub use crate::bls_to_execution_change::BlsToExecutionChange; pub use crate::chain_spec::{ChainSpec, Config, Domain}; pub use crate::checkpoint::Checkpoint; pub use crate::config_and_preset::{ - ConfigAndPreset, ConfigAndPresetCapella, ConfigAndPresetDeneb, ConfigAndPresetElectra, + ConfigAndPreset, ConfigAndPresetDeneb, ConfigAndPresetElectra, ConfigAndPresetFulu, }; pub use crate::consolidation_request::ConsolidationRequest; pub use crate::contribution_and_proof::ContributionAndProof; @@ -163,12 +163,13 @@ pub use crate::execution_block_hash::ExecutionBlockHash; pub use crate::execution_block_header::{EncodableExecutionBlockHeader, ExecutionBlockHeader}; pub use crate::execution_payload::{ ExecutionPayload, ExecutionPayloadBellatrix, ExecutionPayloadCapella, ExecutionPayloadDeneb, - ExecutionPayloadElectra, ExecutionPayloadRef, Transaction, Transactions, Withdrawals, + ExecutionPayloadElectra, ExecutionPayloadFulu, ExecutionPayloadRef, Transaction, Transactions, + Withdrawals, }; pub use crate::execution_payload_header::{ ExecutionPayloadHeader, ExecutionPayloadHeaderBellatrix, ExecutionPayloadHeaderCapella, - ExecutionPayloadHeaderDeneb, ExecutionPayloadHeaderElectra, ExecutionPayloadHeaderRef, - ExecutionPayloadHeaderRefMut, + ExecutionPayloadHeaderDeneb, ExecutionPayloadHeaderElectra, ExecutionPayloadHeaderFulu, + ExecutionPayloadHeaderRef, ExecutionPayloadHeaderRefMut, }; pub use crate::execution_requests::{ExecutionRequests, RequestPrefix}; pub use crate::fork::Fork; @@ -183,31 +184,33 @@ pub use crate::indexed_attestation::{ }; pub use crate::light_client_bootstrap::{ LightClientBootstrap, LightClientBootstrapAltair, LightClientBootstrapCapella, - LightClientBootstrapDeneb, LightClientBootstrapElectra, + LightClientBootstrapDeneb, LightClientBootstrapElectra, LightClientBootstrapFulu, }; pub use crate::light_client_finality_update::{ LightClientFinalityUpdate, LightClientFinalityUpdateAltair, LightClientFinalityUpdateCapella, LightClientFinalityUpdateDeneb, LightClientFinalityUpdateElectra, + LightClientFinalityUpdateFulu, }; pub use crate::light_client_header::{ LightClientHeader, LightClientHeaderAltair, LightClientHeaderCapella, LightClientHeaderDeneb, - LightClientHeaderElectra, + LightClientHeaderElectra, LightClientHeaderFulu, }; pub use crate::light_client_optimistic_update::{ LightClientOptimisticUpdate, LightClientOptimisticUpdateAltair, LightClientOptimisticUpdateCapella, LightClientOptimisticUpdateDeneb, - LightClientOptimisticUpdateElectra, + LightClientOptimisticUpdateElectra, LightClientOptimisticUpdateFulu, }; pub use crate::light_client_update::{ Error as LightClientUpdateError, LightClientUpdate, LightClientUpdateAltair, - LightClientUpdateCapella, LightClientUpdateDeneb, LightClientUpdateElectra, MerkleProof, + LightClientUpdateCapella, LightClientUpdateDeneb, LightClientUpdateElectra, + LightClientUpdateFulu, MerkleProof, }; pub use crate::participation_flags::ParticipationFlags; pub use crate::payload::{ AbstractExecPayload, BlindedPayload, BlindedPayloadBellatrix, BlindedPayloadCapella, - BlindedPayloadDeneb, BlindedPayloadElectra, BlindedPayloadRef, BlockType, ExecPayload, - FullPayload, FullPayloadBellatrix, FullPayloadCapella, FullPayloadDeneb, FullPayloadElectra, - FullPayloadRef, OwnedExecPayload, + BlindedPayloadDeneb, BlindedPayloadElectra, BlindedPayloadFulu, BlindedPayloadRef, BlockType, + ExecPayload, FullPayload, FullPayloadBellatrix, FullPayloadCapella, FullPayloadDeneb, + FullPayloadElectra, FullPayloadFulu, FullPayloadRef, OwnedExecPayload, }; pub use crate::pending_attestation::PendingAttestation; pub use crate::pending_consolidation::PendingConsolidation; @@ -215,6 +218,7 @@ pub use crate::pending_deposit::PendingDeposit; pub use crate::pending_partial_withdrawal::PendingPartialWithdrawal; pub use crate::preset::{ AltairPreset, BasePreset, BellatrixPreset, CapellaPreset, DenebPreset, ElectraPreset, + FuluPreset, }; pub use crate::proposer_preparation_data::ProposerPreparationData; pub use crate::proposer_slashing::ProposerSlashing; @@ -229,7 +233,7 @@ pub use crate::signed_beacon_block::{ ssz_tagged_signed_beacon_block, ssz_tagged_signed_beacon_block_arc, SignedBeaconBlock, SignedBeaconBlockAltair, SignedBeaconBlockBase, SignedBeaconBlockBellatrix, SignedBeaconBlockCapella, SignedBeaconBlockDeneb, SignedBeaconBlockElectra, - SignedBeaconBlockHash, SignedBlindedBeaconBlock, + SignedBeaconBlockFulu, SignedBeaconBlockHash, SignedBlindedBeaconBlock, }; pub use crate::signed_beacon_block_header::SignedBeaconBlockHeader; pub use crate::signed_bls_to_execution_change::SignedBlsToExecutionChange; diff --git a/consensus/types/src/light_client_bootstrap.rs b/consensus/types/src/light_client_bootstrap.rs index 21a7e5416f2..aa0d8836d14 100644 --- a/consensus/types/src/light_client_bootstrap.rs +++ b/consensus/types/src/light_client_bootstrap.rs @@ -2,7 +2,7 @@ use crate::{ light_client_update::*, test_utils::TestRandom, BeaconState, ChainSpec, EthSpec, FixedVector, ForkName, ForkVersionDeserialize, Hash256, LightClientHeader, LightClientHeaderAltair, LightClientHeaderCapella, LightClientHeaderDeneb, LightClientHeaderElectra, - SignedBlindedBeaconBlock, Slot, SyncCommittee, + LightClientHeaderFulu, SignedBlindedBeaconBlock, Slot, SyncCommittee, }; use derivative::Derivative; use serde::{Deserialize, Deserializer, Serialize}; @@ -17,7 +17,7 @@ use tree_hash_derive::TreeHash; /// A LightClientBootstrap is the initializer we send over to light_client nodes /// that are trying to generate their basic storage when booting up. #[superstruct( - variants(Altair, Capella, Deneb, Electra), + variants(Altair, Capella, Deneb, Electra, Fulu), variant_attributes( derive( Debug, @@ -54,6 +54,8 @@ pub struct LightClientBootstrap { pub header: LightClientHeaderDeneb, #[superstruct(only(Electra), partial_getter(rename = "header_electra"))] pub header: LightClientHeaderElectra, + #[superstruct(only(Fulu), partial_getter(rename = "header_fulu"))] + pub header: LightClientHeaderFulu, /// The `SyncCommittee` used in the requested period. pub current_sync_committee: Arc>, /// Merkle proof for sync committee @@ -63,7 +65,7 @@ pub struct LightClientBootstrap { )] pub current_sync_committee_branch: FixedVector, #[superstruct( - only(Electra), + only(Electra, Fulu), partial_getter(rename = "current_sync_committee_branch_electra") )] pub current_sync_committee_branch: FixedVector, @@ -79,6 +81,7 @@ impl LightClientBootstrap { Self::Capella(_) => func(ForkName::Capella), Self::Deneb(_) => func(ForkName::Deneb), Self::Electra(_) => func(ForkName::Electra), + Self::Fulu(_) => func(ForkName::Fulu), } } @@ -97,6 +100,7 @@ impl LightClientBootstrap { ForkName::Capella => Self::Capella(LightClientBootstrapCapella::from_ssz_bytes(bytes)?), ForkName::Deneb => Self::Deneb(LightClientBootstrapDeneb::from_ssz_bytes(bytes)?), ForkName::Electra => Self::Electra(LightClientBootstrapElectra::from_ssz_bytes(bytes)?), + ForkName::Fulu => Self::Fulu(LightClientBootstrapFulu::from_ssz_bytes(bytes)?), ForkName::Base => { return Err(ssz::DecodeError::BytesInvalid(format!( "LightClientBootstrap decoding for {fork_name} not implemented" @@ -117,6 +121,7 @@ impl LightClientBootstrap { ForkName::Capella => as Encode>::ssz_fixed_len(), ForkName::Deneb => as Encode>::ssz_fixed_len(), ForkName::Electra => as Encode>::ssz_fixed_len(), + ForkName::Fulu => as Encode>::ssz_fixed_len(), }; fixed_len + LightClientHeader::::ssz_max_var_len_for_fork(fork_name) } @@ -152,6 +157,11 @@ impl LightClientBootstrap { current_sync_committee, current_sync_committee_branch: current_sync_committee_branch.into(), }), + ForkName::Fulu => Self::Fulu(LightClientBootstrapFulu { + header: LightClientHeaderFulu::block_to_light_client_header(block)?, + current_sync_committee, + current_sync_committee_branch: current_sync_committee_branch.into(), + }), }; Ok(light_client_bootstrap) @@ -192,6 +202,11 @@ impl LightClientBootstrap { current_sync_committee, current_sync_committee_branch: current_sync_committee_branch.into(), }), + ForkName::Fulu => Self::Fulu(LightClientBootstrapFulu { + header: LightClientHeaderFulu::block_to_light_client_header(block)?, + current_sync_committee, + current_sync_committee_branch: current_sync_committee_branch.into(), + }), }; Ok(light_client_bootstrap) @@ -241,4 +256,10 @@ mod tests { use crate::{LightClientBootstrapElectra, MainnetEthSpec}; ssz_tests!(LightClientBootstrapElectra); } + + #[cfg(test)] + mod fulu { + use crate::{LightClientBootstrapFulu, MainnetEthSpec}; + ssz_tests!(LightClientBootstrapFulu); + } } diff --git a/consensus/types/src/light_client_finality_update.rs b/consensus/types/src/light_client_finality_update.rs index ba2f2083cd9..ee3b53c853e 100644 --- a/consensus/types/src/light_client_finality_update.rs +++ b/consensus/types/src/light_client_finality_update.rs @@ -3,7 +3,7 @@ use crate::ChainSpec; use crate::{ light_client_update::*, test_utils::TestRandom, ForkName, ForkVersionDeserialize, LightClientHeaderAltair, LightClientHeaderCapella, LightClientHeaderDeneb, - LightClientHeaderElectra, SignedBlindedBeaconBlock, + LightClientHeaderElectra, LightClientHeaderFulu, SignedBlindedBeaconBlock, }; use derivative::Derivative; use serde::{Deserialize, Deserializer, Serialize}; @@ -16,7 +16,7 @@ use test_random_derive::TestRandom; use tree_hash_derive::TreeHash; #[superstruct( - variants(Altair, Capella, Deneb, Electra), + variants(Altair, Capella, Deneb, Electra, Fulu), variant_attributes( derive( Debug, @@ -53,6 +53,8 @@ pub struct LightClientFinalityUpdate { pub attested_header: LightClientHeaderDeneb, #[superstruct(only(Electra), partial_getter(rename = "attested_header_electra"))] pub attested_header: LightClientHeaderElectra, + #[superstruct(only(Fulu), partial_getter(rename = "attested_header_fulu"))] + pub attested_header: LightClientHeaderFulu, /// The last `BeaconBlockHeader` from the last attested finalized block (end of epoch). #[superstruct(only(Altair), partial_getter(rename = "finalized_header_altair"))] pub finalized_header: LightClientHeaderAltair, @@ -62,13 +64,18 @@ pub struct LightClientFinalityUpdate { pub finalized_header: LightClientHeaderDeneb, #[superstruct(only(Electra), partial_getter(rename = "finalized_header_electra"))] pub finalized_header: LightClientHeaderElectra, + #[superstruct(only(Fulu), partial_getter(rename = "finalized_header_fulu"))] + pub finalized_header: LightClientHeaderFulu, /// Merkle proof attesting finalized header. #[superstruct( only(Altair, Capella, Deneb), partial_getter(rename = "finality_branch_altair") )] pub finality_branch: FixedVector, - #[superstruct(only(Electra), partial_getter(rename = "finality_branch_electra"))] + #[superstruct( + only(Electra, Fulu), + partial_getter(rename = "finality_branch_electra") + )] pub finality_branch: FixedVector, /// current sync aggregate pub sync_aggregate: SyncAggregate, @@ -135,6 +142,17 @@ impl LightClientFinalityUpdate { sync_aggregate, signature_slot, }), + ForkName::Fulu => Self::Fulu(LightClientFinalityUpdateFulu { + attested_header: LightClientHeaderFulu::block_to_light_client_header( + attested_block, + )?, + finalized_header: LightClientHeaderFulu::block_to_light_client_header( + finalized_block, + )?, + finality_branch: finality_branch.into(), + sync_aggregate, + signature_slot, + }), ForkName::Base => return Err(Error::AltairForkNotActive), }; @@ -151,6 +169,7 @@ impl LightClientFinalityUpdate { Self::Capella(_) => func(ForkName::Capella), Self::Deneb(_) => func(ForkName::Deneb), Self::Electra(_) => func(ForkName::Electra), + Self::Fulu(_) => func(ForkName::Fulu), } } @@ -173,6 +192,7 @@ impl LightClientFinalityUpdate { ForkName::Electra => { Self::Electra(LightClientFinalityUpdateElectra::from_ssz_bytes(bytes)?) } + ForkName::Fulu => Self::Fulu(LightClientFinalityUpdateFulu::from_ssz_bytes(bytes)?), ForkName::Base => { return Err(ssz::DecodeError::BytesInvalid(format!( "LightClientFinalityUpdate decoding for {fork_name} not implemented" @@ -193,6 +213,7 @@ impl LightClientFinalityUpdate { ForkName::Capella => as Encode>::ssz_fixed_len(), ForkName::Deneb => as Encode>::ssz_fixed_len(), ForkName::Electra => as Encode>::ssz_fixed_len(), + ForkName::Fulu => as Encode>::ssz_fixed_len(), }; // `2 *` because there are two headers in the update fixed_size + 2 * LightClientHeader::::ssz_max_var_len_for_fork(fork_name) @@ -255,4 +276,10 @@ mod tests { use crate::{LightClientFinalityUpdateElectra, MainnetEthSpec}; ssz_tests!(LightClientFinalityUpdateElectra); } + + #[cfg(test)] + mod fulu { + use crate::{LightClientFinalityUpdateFulu, MainnetEthSpec}; + ssz_tests!(LightClientFinalityUpdateFulu); + } } diff --git a/consensus/types/src/light_client_header.rs b/consensus/types/src/light_client_header.rs index 6655e0a093b..0be26a70360 100644 --- a/consensus/types/src/light_client_header.rs +++ b/consensus/types/src/light_client_header.rs @@ -4,7 +4,8 @@ use crate::ForkVersionDeserialize; use crate::{light_client_update::*, BeaconBlockBody}; use crate::{ test_utils::TestRandom, EthSpec, ExecutionPayloadHeaderCapella, ExecutionPayloadHeaderDeneb, - ExecutionPayloadHeaderElectra, FixedVector, Hash256, SignedBlindedBeaconBlock, + ExecutionPayloadHeaderElectra, ExecutionPayloadHeaderFulu, FixedVector, Hash256, + SignedBlindedBeaconBlock, }; use crate::{BeaconBlockHeader, ExecutionPayloadHeader}; use derivative::Derivative; @@ -17,7 +18,7 @@ use test_random_derive::TestRandom; use tree_hash_derive::TreeHash; #[superstruct( - variants(Altair, Capella, Deneb, Electra), + variants(Altair, Capella, Deneb, Electra, Fulu), variant_attributes( derive( Debug, @@ -59,8 +60,10 @@ pub struct LightClientHeader { partial_getter(rename = "execution_payload_header_electra") )] pub execution: ExecutionPayloadHeaderElectra, + #[superstruct(only(Fulu), partial_getter(rename = "execution_payload_header_fulu"))] + pub execution: ExecutionPayloadHeaderFulu, - #[superstruct(only(Capella, Deneb, Electra))] + #[superstruct(only(Capella, Deneb, Electra, Fulu))] pub execution_branch: FixedVector, #[ssz(skip_serializing, skip_deserializing)] @@ -92,6 +95,9 @@ impl LightClientHeader { ForkName::Electra => LightClientHeader::Electra( LightClientHeaderElectra::block_to_light_client_header(block)?, ), + ForkName::Fulu => { + LightClientHeader::Fulu(LightClientHeaderFulu::block_to_light_client_header(block)?) + } }; Ok(header) } @@ -110,6 +116,9 @@ impl LightClientHeader { ForkName::Electra => { LightClientHeader::Electra(LightClientHeaderElectra::from_ssz_bytes(bytes)?) } + ForkName::Fulu => { + LightClientHeader::Fulu(LightClientHeaderFulu::from_ssz_bytes(bytes)?) + } ForkName::Base => { return Err(ssz::DecodeError::BytesInvalid(format!( "LightClientHeader decoding for {fork_name} not implemented" @@ -283,6 +292,48 @@ impl Default for LightClientHeaderElectra { } } +impl LightClientHeaderFulu { + pub fn block_to_light_client_header( + block: &SignedBlindedBeaconBlock, + ) -> Result { + let payload = block + .message() + .execution_payload()? + .execution_payload_fulu()?; + + let header = ExecutionPayloadHeaderFulu::from(payload); + let beacon_block_body = BeaconBlockBody::from( + block + .message() + .body_fulu() + .map_err(|_| Error::BeaconBlockBodyError)? + .to_owned(), + ); + + let execution_branch = beacon_block_body + .to_ref() + .block_body_merkle_proof(EXECUTION_PAYLOAD_INDEX)?; + + Ok(LightClientHeaderFulu { + beacon: block.message().block_header(), + execution: header, + execution_branch: FixedVector::new(execution_branch)?, + _phantom_data: PhantomData, + }) + } +} + +impl Default for LightClientHeaderFulu { + fn default() -> Self { + Self { + beacon: BeaconBlockHeader::empty(), + execution: ExecutionPayloadHeaderFulu::default(), + execution_branch: FixedVector::default(), + _phantom_data: PhantomData, + } + } +} + impl ForkVersionDeserialize for LightClientHeader { fn deserialize_by_fork<'de, D: serde::Deserializer<'de>>( value: serde_json::value::Value, @@ -301,6 +352,9 @@ impl ForkVersionDeserialize for LightClientHeader { ForkName::Electra => serde_json::from_value(value) .map(|light_client_header| Self::Electra(light_client_header)) .map_err(serde::de::Error::custom), + ForkName::Fulu => serde_json::from_value(value) + .map(|light_client_header| Self::Fulu(light_client_header)) + .map_err(serde::de::Error::custom), ForkName::Base => Err(serde::de::Error::custom(format!( "LightClientHeader deserialization for {fork_name} not implemented" ))), diff --git a/consensus/types/src/light_client_optimistic_update.rs b/consensus/types/src/light_client_optimistic_update.rs index 209388af87b..fcf357757b1 100644 --- a/consensus/types/src/light_client_optimistic_update.rs +++ b/consensus/types/src/light_client_optimistic_update.rs @@ -2,7 +2,8 @@ use super::{EthSpec, ForkName, ForkVersionDeserialize, LightClientHeader, Slot, use crate::test_utils::TestRandom; use crate::{ light_client_update::*, ChainSpec, LightClientHeaderAltair, LightClientHeaderCapella, - LightClientHeaderDeneb, LightClientHeaderElectra, SignedBlindedBeaconBlock, + LightClientHeaderDeneb, LightClientHeaderElectra, LightClientHeaderFulu, + SignedBlindedBeaconBlock, }; use derivative::Derivative; use serde::{Deserialize, Deserializer, Serialize}; @@ -18,7 +19,7 @@ use tree_hash_derive::TreeHash; /// A LightClientOptimisticUpdate is the update we send on each slot, /// it is based off the current unfinalized epoch is verified only against BLS signature. #[superstruct( - variants(Altair, Capella, Deneb, Electra), + variants(Altair, Capella, Deneb, Electra, Fulu), variant_attributes( derive( Debug, @@ -55,6 +56,8 @@ pub struct LightClientOptimisticUpdate { pub attested_header: LightClientHeaderDeneb, #[superstruct(only(Electra), partial_getter(rename = "attested_header_electra"))] pub attested_header: LightClientHeaderElectra, + #[superstruct(only(Fulu), partial_getter(rename = "attested_header_fulu"))] + pub attested_header: LightClientHeaderFulu, /// current sync aggregate pub sync_aggregate: SyncAggregate, /// Slot of the sync aggregated signature @@ -102,6 +105,13 @@ impl LightClientOptimisticUpdate { sync_aggregate, signature_slot, }), + ForkName::Fulu => Self::Fulu(LightClientOptimisticUpdateFulu { + attested_header: LightClientHeaderFulu::block_to_light_client_header( + attested_block, + )?, + sync_aggregate, + signature_slot, + }), ForkName::Base => return Err(Error::AltairForkNotActive), }; @@ -117,6 +127,7 @@ impl LightClientOptimisticUpdate { Self::Capella(_) => func(ForkName::Capella), Self::Deneb(_) => func(ForkName::Deneb), Self::Electra(_) => func(ForkName::Electra), + Self::Fulu(_) => func(ForkName::Fulu), } } @@ -155,6 +166,7 @@ impl LightClientOptimisticUpdate { ForkName::Electra => { Self::Electra(LightClientOptimisticUpdateElectra::from_ssz_bytes(bytes)?) } + ForkName::Fulu => Self::Fulu(LightClientOptimisticUpdateFulu::from_ssz_bytes(bytes)?), ForkName::Base => { return Err(ssz::DecodeError::BytesInvalid(format!( "LightClientOptimisticUpdate decoding for {fork_name} not implemented" @@ -175,6 +187,7 @@ impl LightClientOptimisticUpdate { ForkName::Capella => as Encode>::ssz_fixed_len(), ForkName::Deneb => as Encode>::ssz_fixed_len(), ForkName::Electra => as Encode>::ssz_fixed_len(), + ForkName::Fulu => as Encode>::ssz_fixed_len(), }; fixed_len + LightClientHeader::::ssz_max_var_len_for_fork(fork_name) } @@ -238,4 +251,10 @@ mod tests { use crate::{LightClientOptimisticUpdateElectra, MainnetEthSpec}; ssz_tests!(LightClientOptimisticUpdateElectra); } + + #[cfg(test)] + mod fulu { + use crate::{LightClientOptimisticUpdateFulu, MainnetEthSpec}; + ssz_tests!(LightClientOptimisticUpdateFulu); + } } diff --git a/consensus/types/src/light_client_update.rs b/consensus/types/src/light_client_update.rs index c3a50e71c15..0dd91edc3c4 100644 --- a/consensus/types/src/light_client_update.rs +++ b/consensus/types/src/light_client_update.rs @@ -4,7 +4,7 @@ use crate::LightClientHeader; use crate::{ beacon_state, test_utils::TestRandom, ChainSpec, Epoch, ForkName, ForkVersionDeserialize, LightClientHeaderAltair, LightClientHeaderCapella, LightClientHeaderDeneb, - SignedBlindedBeaconBlock, + LightClientHeaderFulu, SignedBlindedBeaconBlock, }; use derivative::Derivative; use safe_arith::ArithError; @@ -100,7 +100,7 @@ impl From for Error { /// or to sync up to the last committee period, we need to have one ready for each ALTAIR period /// we go over, note: there is no need to keep all of the updates from [ALTAIR_PERIOD, CURRENT_PERIOD]. #[superstruct( - variants(Altair, Capella, Deneb, Electra), + variants(Altair, Capella, Deneb, Electra, Fulu), variant_attributes( derive( Debug, @@ -137,6 +137,8 @@ pub struct LightClientUpdate { pub attested_header: LightClientHeaderDeneb, #[superstruct(only(Electra), partial_getter(rename = "attested_header_electra"))] pub attested_header: LightClientHeaderElectra, + #[superstruct(only(Fulu), partial_getter(rename = "attested_header_fulu"))] + pub attested_header: LightClientHeaderFulu, /// The `SyncCommittee` used in the next period. pub next_sync_committee: Arc>, // Merkle proof for next sync committee @@ -146,7 +148,7 @@ pub struct LightClientUpdate { )] pub next_sync_committee_branch: NextSyncCommitteeBranch, #[superstruct( - only(Electra), + only(Electra, Fulu), partial_getter(rename = "next_sync_committee_branch_electra") )] pub next_sync_committee_branch: NextSyncCommitteeBranchElectra, @@ -159,13 +161,18 @@ pub struct LightClientUpdate { pub finalized_header: LightClientHeaderDeneb, #[superstruct(only(Electra), partial_getter(rename = "finalized_header_electra"))] pub finalized_header: LightClientHeaderElectra, + #[superstruct(only(Fulu), partial_getter(rename = "finalized_header_fulu"))] + pub finalized_header: LightClientHeaderFulu, /// Merkle proof attesting finalized header. #[superstruct( only(Altair, Capella, Deneb), partial_getter(rename = "finality_branch_altair") )] pub finality_branch: FinalityBranch, - #[superstruct(only(Electra), partial_getter(rename = "finality_branch_electra"))] + #[superstruct( + only(Electra, Fulu), + partial_getter(rename = "finality_branch_electra") + )] pub finality_branch: FinalityBranchElectra, /// current sync aggreggate pub sync_aggregate: SyncAggregate, @@ -285,6 +292,26 @@ impl LightClientUpdate { sync_aggregate: sync_aggregate.clone(), signature_slot: block_slot, }) + } + ForkName::Fulu => { + let attested_header = + LightClientHeaderFulu::block_to_light_client_header(attested_block)?; + + let finalized_header = if let Some(finalized_block) = finalized_block { + LightClientHeaderFulu::block_to_light_client_header(finalized_block)? + } else { + LightClientHeaderFulu::default() + }; + + Self::Fulu(LightClientUpdateFulu { + attested_header, + next_sync_committee, + next_sync_committee_branch: next_sync_committee_branch.into(), + finalized_header, + finality_branch: finality_branch.into(), + sync_aggregate: sync_aggregate.clone(), + signature_slot: block_slot, + }) } // To add a new fork, just append the new fork variant on the latest fork. Forks that // have a distinct execution header will need a new LightClientUpdate variant only // if you need to test or support lightclient usages @@ -301,6 +328,7 @@ impl LightClientUpdate { ForkName::Capella => Self::Capella(LightClientUpdateCapella::from_ssz_bytes(bytes)?), ForkName::Deneb => Self::Deneb(LightClientUpdateDeneb::from_ssz_bytes(bytes)?), ForkName::Electra => Self::Electra(LightClientUpdateElectra::from_ssz_bytes(bytes)?), + ForkName::Fulu => Self::Fulu(LightClientUpdateFulu::from_ssz_bytes(bytes)?), ForkName::Base => { return Err(ssz::DecodeError::BytesInvalid(format!( "LightClientUpdate decoding for {fork_name} not implemented" @@ -317,6 +345,7 @@ impl LightClientUpdate { LightClientUpdate::Capella(update) => update.attested_header.beacon.slot, LightClientUpdate::Deneb(update) => update.attested_header.beacon.slot, LightClientUpdate::Electra(update) => update.attested_header.beacon.slot, + LightClientUpdate::Fulu(update) => update.attested_header.beacon.slot, } } @@ -326,6 +355,7 @@ impl LightClientUpdate { LightClientUpdate::Capella(update) => update.finalized_header.beacon.slot, LightClientUpdate::Deneb(update) => update.finalized_header.beacon.slot, LightClientUpdate::Electra(update) => update.finalized_header.beacon.slot, + LightClientUpdate::Fulu(update) => update.finalized_header.beacon.slot, } } @@ -445,6 +475,7 @@ impl LightClientUpdate { ForkName::Capella => as Encode>::ssz_fixed_len(), ForkName::Deneb => as Encode>::ssz_fixed_len(), ForkName::Electra => as Encode>::ssz_fixed_len(), + ForkName::Fulu => as Encode>::ssz_fixed_len(), }; fixed_len + 2 * LightClientHeader::::ssz_max_var_len_for_fork(fork_name) } @@ -458,6 +489,7 @@ impl LightClientUpdate { Self::Capella(_) => func(ForkName::Capella), Self::Deneb(_) => func(ForkName::Deneb), Self::Electra(_) => func(ForkName::Electra), + Self::Fulu(_) => func(ForkName::Fulu), } } } @@ -513,6 +545,13 @@ mod tests { ssz_tests!(LightClientUpdateElectra); } + #[cfg(test)] + mod fulu { + use super::*; + use crate::MainnetEthSpec; + ssz_tests!(LightClientUpdateFulu); + } + #[test] fn finalized_root_params() { assert!(2usize.pow(FINALIZED_ROOT_PROOF_LEN as u32) <= FINALIZED_ROOT_INDEX); diff --git a/consensus/types/src/payload.rs b/consensus/types/src/payload.rs index e68801840af..abc9afd34c8 100644 --- a/consensus/types/src/payload.rs +++ b/consensus/types/src/payload.rs @@ -84,13 +84,15 @@ pub trait AbstractExecPayload: + TryInto + TryInto + TryInto + + TryInto { type Ref<'a>: ExecPayload + Copy + From<&'a Self::Bellatrix> + From<&'a Self::Capella> + From<&'a Self::Deneb> - + From<&'a Self::Electra>; + + From<&'a Self::Electra> + + From<&'a Self::Fulu>; type Bellatrix: OwnedExecPayload + Into @@ -108,10 +110,14 @@ pub trait AbstractExecPayload: + Into + for<'a> From>> + TryFrom>; + type Fulu: OwnedExecPayload + + Into + + for<'a> From>> + + TryFrom>; } #[superstruct( - variants(Bellatrix, Capella, Deneb, Electra), + variants(Bellatrix, Capella, Deneb, Electra, Fulu), variant_attributes( derive( Debug, @@ -157,6 +163,8 @@ pub struct FullPayload { pub execution_payload: ExecutionPayloadDeneb, #[superstruct(only(Electra), partial_getter(rename = "execution_payload_electra"))] pub execution_payload: ExecutionPayloadElectra, + #[superstruct(only(Fulu), partial_getter(rename = "execution_payload_fulu"))] + pub execution_payload: ExecutionPayloadFulu, } impl From> for ExecutionPayload { @@ -273,6 +281,9 @@ impl ExecPayload for FullPayload { FullPayload::Electra(ref inner) => { Ok(inner.execution_payload.withdrawals.tree_hash_root()) } + FullPayload::Fulu(ref inner) => { + Ok(inner.execution_payload.withdrawals.tree_hash_root()) + } } } @@ -283,6 +294,7 @@ impl ExecPayload for FullPayload { } FullPayload::Deneb(ref inner) => Ok(inner.execution_payload.blob_gas_used), FullPayload::Electra(ref inner) => Ok(inner.execution_payload.blob_gas_used), + FullPayload::Fulu(ref inner) => Ok(inner.execution_payload.blob_gas_used), } } @@ -313,6 +325,7 @@ impl FullPayload { ForkName::Capella => Ok(FullPayloadCapella::default().into()), ForkName::Deneb => Ok(FullPayloadDeneb::default().into()), ForkName::Electra => Ok(FullPayloadElectra::default().into()), + ForkName::Fulu => Ok(FullPayloadFulu::default().into()), } } } @@ -412,6 +425,7 @@ impl ExecPayload for FullPayloadRef<'_, E> { FullPayloadRef::Electra(inner) => { Ok(inner.execution_payload.withdrawals.tree_hash_root()) } + FullPayloadRef::Fulu(inner) => Ok(inner.execution_payload.withdrawals.tree_hash_root()), } } @@ -422,6 +436,7 @@ impl ExecPayload for FullPayloadRef<'_, E> { } FullPayloadRef::Deneb(inner) => Ok(inner.execution_payload.blob_gas_used), FullPayloadRef::Electra(inner) => Ok(inner.execution_payload.blob_gas_used), + FullPayloadRef::Fulu(inner) => Ok(inner.execution_payload.blob_gas_used), } } @@ -444,6 +459,7 @@ impl AbstractExecPayload for FullPayload { type Capella = FullPayloadCapella; type Deneb = FullPayloadDeneb; type Electra = FullPayloadElectra; + type Fulu = FullPayloadFulu; } impl From> for FullPayload { @@ -462,7 +478,7 @@ impl TryFrom> for FullPayload { } #[superstruct( - variants(Bellatrix, Capella, Deneb, Electra), + variants(Bellatrix, Capella, Deneb, Electra, Fulu), variant_attributes( derive( Debug, @@ -507,6 +523,8 @@ pub struct BlindedPayload { pub execution_payload_header: ExecutionPayloadHeaderDeneb, #[superstruct(only(Electra), partial_getter(rename = "execution_payload_electra"))] pub execution_payload_header: ExecutionPayloadHeaderElectra, + #[superstruct(only(Fulu), partial_getter(rename = "execution_payload_fulu"))] + pub execution_payload_header: ExecutionPayloadHeaderFulu, } impl<'a, E: EthSpec> From> for BlindedPayload { @@ -599,6 +617,7 @@ impl ExecPayload for BlindedPayload { BlindedPayload::Electra(ref inner) => { Ok(inner.execution_payload_header.withdrawals_root) } + BlindedPayload::Fulu(ref inner) => Ok(inner.execution_payload_header.withdrawals_root), } } @@ -609,6 +628,7 @@ impl ExecPayload for BlindedPayload { } BlindedPayload::Deneb(ref inner) => Ok(inner.execution_payload_header.blob_gas_used), BlindedPayload::Electra(ref inner) => Ok(inner.execution_payload_header.blob_gas_used), + BlindedPayload::Fulu(ref inner) => Ok(inner.execution_payload_header.blob_gas_used), } } @@ -707,6 +727,7 @@ impl<'b, E: EthSpec> ExecPayload for BlindedPayloadRef<'b, E> { BlindedPayloadRef::Electra(inner) => { Ok(inner.execution_payload_header.withdrawals_root) } + BlindedPayloadRef::Fulu(inner) => Ok(inner.execution_payload_header.withdrawals_root), } } @@ -717,6 +738,7 @@ impl<'b, E: EthSpec> ExecPayload for BlindedPayloadRef<'b, E> { } BlindedPayloadRef::Deneb(inner) => Ok(inner.execution_payload_header.blob_gas_used), BlindedPayloadRef::Electra(inner) => Ok(inner.execution_payload_header.blob_gas_used), + BlindedPayloadRef::Fulu(inner) => Ok(inner.execution_payload_header.blob_gas_used), } } @@ -1020,6 +1042,13 @@ impl_exec_payload_for_fork!( ExecutionPayloadElectra, Electra ); +impl_exec_payload_for_fork!( + BlindedPayloadFulu, + FullPayloadFulu, + ExecutionPayloadHeaderFulu, + ExecutionPayloadFulu, + Fulu +); impl AbstractExecPayload for BlindedPayload { type Ref<'a> = BlindedPayloadRef<'a, E>; @@ -1027,6 +1056,7 @@ impl AbstractExecPayload for BlindedPayload { type Capella = BlindedPayloadCapella; type Deneb = BlindedPayloadDeneb; type Electra = BlindedPayloadElectra; + type Fulu = BlindedPayloadFulu; } impl From> for BlindedPayload { @@ -1063,6 +1093,11 @@ impl From> for BlindedPayload { execution_payload_header, }) } + ExecutionPayloadHeader::Fulu(execution_payload_header) => { + Self::Fulu(BlindedPayloadFulu { + execution_payload_header, + }) + } } } } @@ -1082,6 +1117,9 @@ impl From> for ExecutionPayloadHeader { BlindedPayload::Electra(blinded_payload) => { ExecutionPayloadHeader::Electra(blinded_payload.execution_payload_header) } + BlindedPayload::Fulu(blinded_payload) => { + ExecutionPayloadHeader::Fulu(blinded_payload.execution_payload_header) + } } } } diff --git a/consensus/types/src/preset.rs b/consensus/types/src/preset.rs index b469b7b777a..f8b36654093 100644 --- a/consensus/types/src/preset.rs +++ b/consensus/types/src/preset.rs @@ -276,6 +276,21 @@ impl ElectraPreset { } } +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +#[serde(rename_all = "UPPERCASE")] +pub struct FuluPreset { + #[serde(with = "serde_utils::quoted_u64")] + pub fulu_placeholder: u64, +} + +impl FuluPreset { + pub fn from_chain_spec(spec: &ChainSpec) -> Self { + Self { + fulu_placeholder: spec.fulu_placeholder, + } + } +} + #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] #[serde(rename_all = "UPPERCASE")] pub struct Eip7594Preset { @@ -343,6 +358,9 @@ mod test { let electra: ElectraPreset = preset_from_file(&preset_name, "electra.yaml"); assert_eq!(electra, ElectraPreset::from_chain_spec::(&spec)); + let fulu: FuluPreset = preset_from_file(&preset_name, "fulu.yaml"); + assert_eq!(fulu, FuluPreset::from_chain_spec::(&spec)); + let eip7594: Eip7594Preset = preset_from_file(&preset_name, "eip7594.yaml"); assert_eq!(eip7594, Eip7594Preset::from_chain_spec::(&spec)); } diff --git a/consensus/types/src/signed_beacon_block.rs b/consensus/types/src/signed_beacon_block.rs index bb5e1ea34b7..d9bf9bf55dd 100644 --- a/consensus/types/src/signed_beacon_block.rs +++ b/consensus/types/src/signed_beacon_block.rs @@ -38,7 +38,7 @@ impl From for Hash256 { /// A `BeaconBlock` and a signature from its proposer. #[superstruct( - variants(Base, Altair, Bellatrix, Capella, Deneb, Electra), + variants(Base, Altair, Bellatrix, Capella, Deneb, Electra, Fulu), variant_attributes( derive( Debug, @@ -81,6 +81,8 @@ pub struct SignedBeaconBlock = FullP pub message: BeaconBlockDeneb, #[superstruct(only(Electra), partial_getter(rename = "message_electra"))] pub message: BeaconBlockElectra, + #[superstruct(only(Fulu), partial_getter(rename = "message_fulu"))] + pub message: BeaconBlockFulu, pub signature: Signature, } @@ -163,6 +165,9 @@ impl> SignedBeaconBlock BeaconBlock::Electra(message) => { SignedBeaconBlock::Electra(SignedBeaconBlockElectra { message, signature }) } + BeaconBlock::Fulu(message) => { + SignedBeaconBlock::Fulu(SignedBeaconBlockFulu { message, signature }) + } } } @@ -570,6 +575,64 @@ impl SignedBeaconBlockElectra> { } } +impl SignedBeaconBlockFulu> { + pub fn into_full_block( + self, + execution_payload: ExecutionPayloadFulu, + ) -> SignedBeaconBlockFulu> { + let SignedBeaconBlockFulu { + message: + BeaconBlockFulu { + slot, + proposer_index, + parent_root, + state_root, + body: + BeaconBlockBodyFulu { + randao_reveal, + eth1_data, + graffiti, + proposer_slashings, + attester_slashings, + attestations, + deposits, + voluntary_exits, + sync_aggregate, + execution_payload: BlindedPayloadFulu { .. }, + bls_to_execution_changes, + blob_kzg_commitments, + execution_requests, + }, + }, + signature, + } = self; + SignedBeaconBlockFulu { + message: BeaconBlockFulu { + slot, + proposer_index, + parent_root, + state_root, + body: BeaconBlockBodyFulu { + randao_reveal, + eth1_data, + graffiti, + proposer_slashings, + attester_slashings, + attestations, + deposits, + voluntary_exits, + sync_aggregate, + execution_payload: FullPayloadFulu { execution_payload }, + bls_to_execution_changes, + blob_kzg_commitments, + execution_requests, + }, + }, + signature, + } + } +} + impl SignedBeaconBlock> { pub fn try_into_full_block( self, @@ -590,12 +653,16 @@ impl SignedBeaconBlock> { (SignedBeaconBlock::Electra(block), Some(ExecutionPayload::Electra(payload))) => { SignedBeaconBlock::Electra(block.into_full_block(payload)) } + (SignedBeaconBlock::Fulu(block), Some(ExecutionPayload::Fulu(payload))) => { + SignedBeaconBlock::Fulu(block.into_full_block(payload)) + } // avoid wildcard matching forks so that compiler will // direct us here when a new fork has been added (SignedBeaconBlock::Bellatrix(_), _) => return None, (SignedBeaconBlock::Capella(_), _) => return None, (SignedBeaconBlock::Deneb(_), _) => return None, (SignedBeaconBlock::Electra(_), _) => return None, + (SignedBeaconBlock::Fulu(_), _) => return None, }; Some(full_block) } @@ -741,6 +808,9 @@ pub mod ssz_tagged_signed_beacon_block { ForkName::Electra => Ok(SignedBeaconBlock::Electra( SignedBeaconBlockElectra::from_ssz_bytes(body)?, )), + ForkName::Fulu => Ok(SignedBeaconBlock::Fulu( + SignedBeaconBlockFulu::from_ssz_bytes(body)?, + )), } } } @@ -841,8 +911,9 @@ mod test { ), SignedBeaconBlock::from_block( BeaconBlock::Electra(BeaconBlockElectra::empty(spec)), - sig, + sig.clone(), ), + SignedBeaconBlock::from_block(BeaconBlock::Fulu(BeaconBlockFulu::empty(spec)), sig), ]; for block in blocks { diff --git a/lcli/src/mock_el.rs b/lcli/src/mock_el.rs index 8d3220b1df8..7719f02aa33 100644 --- a/lcli/src/mock_el.rs +++ b/lcli/src/mock_el.rs @@ -19,6 +19,7 @@ pub fn run(mut env: Environment, matches: &ArgMatches) -> Result< let shanghai_time = parse_required(matches, "shanghai-time")?; let cancun_time = parse_optional(matches, "cancun-time")?; let prague_time = parse_optional(matches, "prague-time")?; + let osaka_time = parse_optional(matches, "osaka-time")?; let handle = env.core_context().executor.handle().unwrap(); let spec = &E::default_spec(); @@ -37,6 +38,7 @@ pub fn run(mut env: Environment, matches: &ArgMatches) -> Result< shanghai_time: Some(shanghai_time), cancun_time, prague_time, + osaka_time, }; let kzg = None; let server: MockServer = MockServer::new_with_config(&handle, config, kzg); diff --git a/testing/ef_tests/src/cases/common.rs b/testing/ef_tests/src/cases/common.rs index e16f5b257f9..62f834820fd 100644 --- a/testing/ef_tests/src/cases/common.rs +++ b/testing/ef_tests/src/cases/common.rs @@ -66,6 +66,7 @@ pub fn previous_fork(fork_name: ForkName) -> ForkName { ForkName::Capella => ForkName::Bellatrix, ForkName::Deneb => ForkName::Capella, ForkName::Electra => ForkName::Deneb, + ForkName::Fulu => ForkName::Electra, } } diff --git a/testing/ef_tests/src/cases/epoch_processing.rs b/testing/ef_tests/src/cases/epoch_processing.rs index c1adf107704..e05225c1715 100644 --- a/testing/ef_tests/src/cases/epoch_processing.rs +++ b/testing/ef_tests/src/cases/epoch_processing.rs @@ -100,47 +100,35 @@ type_name!(ParticipationFlagUpdates, "participation_flag_updates"); impl EpochTransition for JustificationAndFinalization { fn run(state: &mut BeaconState, spec: &ChainSpec) -> Result<(), EpochProcessingError> { - match state { - BeaconState::Base(_) => { - let mut validator_statuses = base::ValidatorStatuses::new(state, spec)?; - validator_statuses.process_attestations(state)?; - let justification_and_finalization_state = - base::process_justification_and_finalization( - state, - &validator_statuses.total_balances, - spec, - )?; - justification_and_finalization_state.apply_changes_to_state(state); - Ok(()) - } - BeaconState::Altair(_) - | BeaconState::Bellatrix(_) - | BeaconState::Capella(_) - | BeaconState::Deneb(_) - | BeaconState::Electra(_) => { - initialize_progressive_balances_cache(state, spec)?; - let justification_and_finalization_state = - altair::process_justification_and_finalization(state)?; - justification_and_finalization_state.apply_changes_to_state(state); - Ok(()) - } + if state.fork_name_unchecked().altair_enabled() { + initialize_progressive_balances_cache(state, spec)?; + let justification_and_finalization_state = + altair::process_justification_and_finalization(state)?; + justification_and_finalization_state.apply_changes_to_state(state); + Ok(()) + } else { + let mut validator_statuses = base::ValidatorStatuses::new(state, spec)?; + validator_statuses.process_attestations(state)?; + let justification_and_finalization_state = + base::process_justification_and_finalization( + state, + &validator_statuses.total_balances, + spec, + )?; + justification_and_finalization_state.apply_changes_to_state(state); + Ok(()) } } } impl EpochTransition for RewardsAndPenalties { fn run(state: &mut BeaconState, spec: &ChainSpec) -> Result<(), EpochProcessingError> { - match state { - BeaconState::Base(_) => { - let mut validator_statuses = base::ValidatorStatuses::new(state, spec)?; - validator_statuses.process_attestations(state)?; - base::process_rewards_and_penalties(state, &validator_statuses, spec) - } - BeaconState::Altair(_) - | BeaconState::Bellatrix(_) - | BeaconState::Capella(_) - | BeaconState::Deneb(_) - | BeaconState::Electra(_) => altair::process_rewards_and_penalties_slow(state, spec), + if state.fork_name_unchecked().altair_enabled() { + altair::process_rewards_and_penalties_slow(state, spec) + } else { + let mut validator_statuses = base::ValidatorStatuses::new(state, spec)?; + validator_statuses.process_attestations(state)?; + base::process_rewards_and_penalties(state, &validator_statuses, spec) } } } @@ -159,24 +147,17 @@ impl EpochTransition for RegistryUpdates { impl EpochTransition for Slashings { fn run(state: &mut BeaconState, spec: &ChainSpec) -> Result<(), EpochProcessingError> { - match state { - BeaconState::Base(_) => { - let mut validator_statuses = base::ValidatorStatuses::new(state, spec)?; - validator_statuses.process_attestations(state)?; - process_slashings( - state, - validator_statuses.total_balances.current_epoch(), - spec, - )?; - } - BeaconState::Altair(_) - | BeaconState::Bellatrix(_) - | BeaconState::Capella(_) - | BeaconState::Deneb(_) - | BeaconState::Electra(_) => { - process_slashings_slow(state, spec)?; - } - }; + if state.fork_name_unchecked().altair_enabled() { + process_slashings_slow(state, spec)?; + } else { + let mut validator_statuses = base::ValidatorStatuses::new(state, spec)?; + validator_statuses.process_attestations(state)?; + process_slashings( + state, + validator_statuses.total_balances.current_epoch(), + spec, + )?; + } Ok(()) } } @@ -251,11 +232,10 @@ impl EpochTransition for HistoricalRootsUpdate { impl EpochTransition for HistoricalSummariesUpdate { fn run(state: &mut BeaconState, _spec: &ChainSpec) -> Result<(), EpochProcessingError> { - match state { - BeaconState::Capella(_) | BeaconState::Deneb(_) | BeaconState::Electra(_) => { - process_historical_summaries_update(state) - } - _ => Ok(()), + if state.fork_name_unchecked().capella_enabled() { + process_historical_summaries_update(state) + } else { + Ok(()) } } } @@ -272,39 +252,30 @@ impl EpochTransition for ParticipationRecordUpdates { impl EpochTransition for SyncCommitteeUpdates { fn run(state: &mut BeaconState, spec: &ChainSpec) -> Result<(), EpochProcessingError> { - match state { - BeaconState::Base(_) => Ok(()), - BeaconState::Altair(_) - | BeaconState::Bellatrix(_) - | BeaconState::Capella(_) - | BeaconState::Deneb(_) - | BeaconState::Electra(_) => altair::process_sync_committee_updates(state, spec), + if state.fork_name_unchecked().altair_enabled() { + altair::process_sync_committee_updates(state, spec) + } else { + Ok(()) } } } impl EpochTransition for InactivityUpdates { fn run(state: &mut BeaconState, spec: &ChainSpec) -> Result<(), EpochProcessingError> { - match state { - BeaconState::Base(_) => Ok(()), - BeaconState::Altair(_) - | BeaconState::Bellatrix(_) - | BeaconState::Capella(_) - | BeaconState::Deneb(_) - | BeaconState::Electra(_) => altair::process_inactivity_updates_slow(state, spec), + if state.fork_name_unchecked().altair_enabled() { + altair::process_inactivity_updates_slow(state, spec) + } else { + Ok(()) } } } impl EpochTransition for ParticipationFlagUpdates { fn run(state: &mut BeaconState, _: &ChainSpec) -> Result<(), EpochProcessingError> { - match state { - BeaconState::Base(_) => Ok(()), - BeaconState::Altair(_) - | BeaconState::Bellatrix(_) - | BeaconState::Capella(_) - | BeaconState::Deneb(_) - | BeaconState::Electra(_) => altair::process_participation_flag_updates(state), + if state.fork_name_unchecked().altair_enabled() { + altair::process_participation_flag_updates(state) + } else { + Ok(()) } } } diff --git a/testing/ef_tests/src/cases/fork.rs b/testing/ef_tests/src/cases/fork.rs index 132cfb4c0ae..85301e22f6d 100644 --- a/testing/ef_tests/src/cases/fork.rs +++ b/testing/ef_tests/src/cases/fork.rs @@ -5,7 +5,7 @@ use crate::decode::{ssz_decode_state, yaml_decode_file}; use serde::Deserialize; use state_processing::upgrade::{ upgrade_to_altair, upgrade_to_bellatrix, upgrade_to_capella, upgrade_to_deneb, - upgrade_to_electra, + upgrade_to_electra, upgrade_to_fulu, }; use types::BeaconState; @@ -69,6 +69,7 @@ impl Case for ForkTest { ForkName::Capella => upgrade_to_capella(&mut result_state, spec).map(|_| result_state), ForkName::Deneb => upgrade_to_deneb(&mut result_state, spec).map(|_| result_state), ForkName::Electra => upgrade_to_electra(&mut result_state, spec).map(|_| result_state), + ForkName::Fulu => upgrade_to_fulu(&mut result_state, spec).map(|_| result_state), }; compare_beacon_state_results_without_caches(&mut result, &mut expected) diff --git a/testing/ef_tests/src/cases/merkle_proof_validity.rs b/testing/ef_tests/src/cases/merkle_proof_validity.rs index 49c07197848..109d2cc7969 100644 --- a/testing/ef_tests/src/cases/merkle_proof_validity.rs +++ b/testing/ef_tests/src/cases/merkle_proof_validity.rs @@ -4,7 +4,7 @@ use serde::Deserialize; use tree_hash::Hash256; use types::{ light_client_update, BeaconBlockBody, BeaconBlockBodyCapella, BeaconBlockBodyDeneb, - BeaconBlockBodyElectra, BeaconState, FixedVector, FullPayload, Unsigned, + BeaconBlockBodyElectra, BeaconBlockBodyFulu, BeaconState, FixedVector, FullPayload, Unsigned, }; #[derive(Debug, Clone, Deserialize)] @@ -131,6 +131,9 @@ impl LoadCase for KzgInclusionMerkleProofValidity { ssz_decode_file::>(&path.join("object.ssz_snappy"))? .into() } + ForkName::Fulu => { + ssz_decode_file::>(&path.join("object.ssz_snappy"))?.into() + } }; let merkle_proof = yaml_decode_file(&path.join("proof.yaml"))?; // Metadata does not exist in these tests but it is left like this just in case. @@ -246,6 +249,9 @@ impl LoadCase for BeaconBlockBodyMerkleProofValidity { ssz_decode_file::>(&path.join("object.ssz_snappy"))? .into() } + ForkName::Fulu => { + ssz_decode_file::>(&path.join("object.ssz_snappy"))?.into() + } }; let merkle_proof = yaml_decode_file(&path.join("proof.yaml"))?; // Metadata does not exist in these tests but it is left like this just in case. diff --git a/testing/ef_tests/src/cases/operations.rs b/testing/ef_tests/src/cases/operations.rs index d8cade296bc..adb5bee7681 100644 --- a/testing/ef_tests/src/cases/operations.rs +++ b/testing/ef_tests/src/cases/operations.rs @@ -98,29 +98,24 @@ impl Operation for Attestation { ) -> Result<(), BlockProcessingError> { initialize_epoch_cache(state, spec)?; let mut ctxt = ConsensusContext::new(state.slot()); - match state { - BeaconState::Base(_) => base::process_attestations( + if state.fork_name_unchecked().altair_enabled() { + initialize_progressive_balances_cache(state, spec)?; + altair_deneb::process_attestation( + state, + self.to_ref(), + 0, + &mut ctxt, + VerifySignatures::True, + spec, + ) + } else { + base::process_attestations( state, [self.clone().to_ref()].into_iter(), VerifySignatures::True, &mut ctxt, spec, - ), - BeaconState::Altair(_) - | BeaconState::Bellatrix(_) - | BeaconState::Capella(_) - | BeaconState::Deneb(_) - | BeaconState::Electra(_) => { - initialize_progressive_balances_cache(state, spec)?; - altair_deneb::process_attestation( - state, - self.to_ref(), - 0, - &mut ctxt, - VerifySignatures::True, - spec, - ) - } + ) } } } @@ -131,14 +126,11 @@ impl Operation for AttesterSlashing { } fn decode(path: &Path, fork_name: ForkName, _spec: &ChainSpec) -> Result { - Ok(match fork_name { - ForkName::Base - | ForkName::Altair - | ForkName::Bellatrix - | ForkName::Capella - | ForkName::Deneb => Self::Base(ssz_decode_file(path)?), - ForkName::Electra => Self::Electra(ssz_decode_file(path)?), - }) + if fork_name.electra_enabled() { + Ok(Self::Electra(ssz_decode_file(path)?)) + } else { + Ok(Self::Base(ssz_decode_file(path)?)) + } } fn apply_to( @@ -308,6 +300,7 @@ impl Operation for BeaconBlockBody> { ForkName::Capella => BeaconBlockBody::Capella(<_>::from_ssz_bytes(bytes)?), ForkName::Deneb => BeaconBlockBody::Deneb(<_>::from_ssz_bytes(bytes)?), ForkName::Electra => BeaconBlockBody::Electra(<_>::from_ssz_bytes(bytes)?), + ForkName::Fulu => BeaconBlockBody::Fulu(<_>::from_ssz_bytes(bytes)?), _ => panic!(), }) }) @@ -363,6 +356,10 @@ impl Operation for BeaconBlockBody> { let inner = >>::from_ssz_bytes(bytes)?; BeaconBlockBody::Electra(inner.clone_as_blinded()) } + ForkName::Fulu => { + let inner = >>::from_ssz_bytes(bytes)?; + BeaconBlockBody::Electra(inner.clone_as_blinded()) + } _ => panic!(), }) }) diff --git a/testing/ef_tests/src/cases/transition.rs b/testing/ef_tests/src/cases/transition.rs index dc5029d53e7..6d037dae87d 100644 --- a/testing/ef_tests/src/cases/transition.rs +++ b/testing/ef_tests/src/cases/transition.rs @@ -60,6 +60,14 @@ impl LoadCase for TransitionTest { spec.deneb_fork_epoch = Some(Epoch::new(0)); spec.electra_fork_epoch = Some(metadata.fork_epoch); } + ForkName::Fulu => { + spec.altair_fork_epoch = Some(Epoch::new(0)); + spec.bellatrix_fork_epoch = Some(Epoch::new(0)); + spec.capella_fork_epoch = Some(Epoch::new(0)); + spec.deneb_fork_epoch = Some(Epoch::new(0)); + spec.electra_fork_epoch = Some(Epoch::new(0)); + spec.fulu_fork_epoch = Some(metadata.fork_epoch); + } } // Load blocks diff --git a/testing/ef_tests/src/handler.rs b/testing/ef_tests/src/handler.rs index f4a09de32cb..e7c148645c4 100644 --- a/testing/ef_tests/src/handler.rs +++ b/testing/ef_tests/src/handler.rs @@ -24,7 +24,7 @@ pub trait Handler { // Add forks here to exclude them from EF spec testing. Helpful for adding future or // unspecified forks. fn disabled_forks(&self) -> Vec { - vec![] + vec![ForkName::Fulu] } fn is_enabled_for_fork(&self, fork_name: ForkName) -> bool { @@ -287,6 +287,10 @@ impl SszStaticHandler { Self::for_forks(vec![ForkName::Electra]) } + pub fn fulu_only() -> Self { + Self::for_forks(vec![ForkName::Fulu]) + } + pub fn altair_and_later() -> Self { Self::for_forks(ForkName::list_all()[1..].to_vec()) } @@ -307,6 +311,10 @@ impl SszStaticHandler { Self::for_forks(ForkName::list_all()[5..].to_vec()) } + pub fn fulu_and_later() -> Self { + Self::for_forks(ForkName::list_all()[6..].to_vec()) + } + pub fn pre_electra() -> Self { Self::for_forks(ForkName::list_all()[0..5].to_vec()) } diff --git a/testing/simulator/src/basic_sim.rs b/testing/simulator/src/basic_sim.rs index 8f659a893f3..82a70285827 100644 --- a/testing/simulator/src/basic_sim.rs +++ b/testing/simulator/src/basic_sim.rs @@ -22,7 +22,8 @@ const ALTAIR_FORK_EPOCH: u64 = 0; const BELLATRIX_FORK_EPOCH: u64 = 0; const CAPELLA_FORK_EPOCH: u64 = 1; const DENEB_FORK_EPOCH: u64 = 2; -//const ELECTRA_FORK_EPOCH: u64 = 3; +// const ELECTRA_FORK_EPOCH: u64 = 3; +// const FULU_FORK_EPOCH: u64 = 4; const SUGGESTED_FEE_RECIPIENT: [u8; 20] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]; @@ -118,6 +119,7 @@ pub fn run_basic_sim(matches: &ArgMatches) -> Result<(), String> { spec.capella_fork_epoch = Some(Epoch::new(CAPELLA_FORK_EPOCH)); spec.deneb_fork_epoch = Some(Epoch::new(DENEB_FORK_EPOCH)); //spec.electra_fork_epoch = Some(Epoch::new(ELECTRA_FORK_EPOCH)); + //spec.fulu_fork_epoch = Some(Epoch::new(FULU_FORK_EPOCH)); let spec = Arc::new(spec); env.eth2_config.spec = spec.clone(); diff --git a/testing/simulator/src/fallback_sim.rs b/testing/simulator/src/fallback_sim.rs index b3b9a460018..7d4bdfa264e 100644 --- a/testing/simulator/src/fallback_sim.rs +++ b/testing/simulator/src/fallback_sim.rs @@ -21,7 +21,8 @@ const ALTAIR_FORK_EPOCH: u64 = 0; const BELLATRIX_FORK_EPOCH: u64 = 0; const CAPELLA_FORK_EPOCH: u64 = 1; const DENEB_FORK_EPOCH: u64 = 2; -//const ELECTRA_FORK_EPOCH: u64 = 3; +// const ELECTRA_FORK_EPOCH: u64 = 3; +// const FULU_FORK_EPOCH: u64 = 4; // Since simulator tests are non-deterministic and there is a non-zero chance of missed // attestations, define an acceptable network-wide attestation performance. @@ -123,6 +124,7 @@ pub fn run_fallback_sim(matches: &ArgMatches) -> Result<(), String> { spec.capella_fork_epoch = Some(Epoch::new(CAPELLA_FORK_EPOCH)); spec.deneb_fork_epoch = Some(Epoch::new(DENEB_FORK_EPOCH)); //spec.electra_fork_epoch = Some(Epoch::new(ELECTRA_FORK_EPOCH)); + //spec.fulu_fork_epoch = Some(Epoch::new(FULU_FORK_EPOCH)); let spec = Arc::new(spec); env.eth2_config.spec = spec.clone(); diff --git a/testing/simulator/src/local_network.rs b/testing/simulator/src/local_network.rs index 59efc09baa6..a95c15c2313 100644 --- a/testing/simulator/src/local_network.rs +++ b/testing/simulator/src/local_network.rs @@ -88,6 +88,11 @@ fn default_mock_execution_config( + spec.seconds_per_slot * E::slots_per_epoch() * electra_fork_epoch.as_u64(), ) } + if let Some(fulu_fork_epoch) = spec.fulu_fork_epoch { + mock_execution_config.osaka_time = Some( + genesis_time + spec.seconds_per_slot * E::slots_per_epoch() * fulu_fork_epoch.as_u64(), + ) + } mock_execution_config } diff --git a/validator_client/beacon_node_fallback/src/lib.rs b/validator_client/beacon_node_fallback/src/lib.rs index 95a221f1897..beae176193c 100644 --- a/validator_client/beacon_node_fallback/src/lib.rs +++ b/validator_client/beacon_node_fallback/src/lib.rs @@ -361,6 +361,14 @@ impl CandidateBeaconNode { "endpoint_electra_fork_epoch" => ?beacon_node_spec.electra_fork_epoch, "hint" => UPDATE_REQUIRED_LOG_HINT, ); + } else if beacon_node_spec.fulu_fork_epoch != spec.fulu_fork_epoch { + warn!( + log, + "Beacon node has mismatched Fulu fork epoch"; + "endpoint" => %self.beacon_node, + "endpoint_fulu_fork_epoch" => ?beacon_node_spec.fulu_fork_epoch, + "hint" => UPDATE_REQUIRED_LOG_HINT, + ); } Ok(()) diff --git a/validator_client/http_api/src/test_utils.rs b/validator_client/http_api/src/test_utils.rs index 390095eec73..0531626846b 100644 --- a/validator_client/http_api/src/test_utils.rs +++ b/validator_client/http_api/src/test_utils.rs @@ -251,9 +251,9 @@ impl ApiTester { pub async fn test_get_lighthouse_spec(self) -> Self { let result = self .client - .get_lighthouse_spec::() + .get_lighthouse_spec::() .await - .map(|res| ConfigAndPreset::Electra(res.data)) + .map(|res| ConfigAndPreset::Fulu(res.data)) .unwrap(); let expected = ConfigAndPreset::from_chain_spec::(&E::default_spec(), None); diff --git a/validator_client/http_api/src/tests.rs b/validator_client/http_api/src/tests.rs index 7ea3d7ebaab..4e9acc42378 100644 --- a/validator_client/http_api/src/tests.rs +++ b/validator_client/http_api/src/tests.rs @@ -214,9 +214,9 @@ impl ApiTester { pub async fn test_get_lighthouse_spec(self) -> Self { let result = self .client - .get_lighthouse_spec::() + .get_lighthouse_spec::() .await - .map(|res| ConfigAndPreset::Electra(res.data)) + .map(|res| ConfigAndPreset::Fulu(res.data)) .unwrap(); let expected = ConfigAndPreset::from_chain_spec::(&E::default_spec(), None); diff --git a/validator_client/signing_method/src/web3signer.rs b/validator_client/signing_method/src/web3signer.rs index 86e7015ad35..d286449d203 100644 --- a/validator_client/signing_method/src/web3signer.rs +++ b/validator_client/signing_method/src/web3signer.rs @@ -29,6 +29,7 @@ pub enum ForkName { Capella, Deneb, Electra, + Fulu, } #[derive(Debug, PartialEq, Serialize)] @@ -107,6 +108,11 @@ impl<'a, E: EthSpec, Payload: AbstractExecPayload> Web3SignerObject<'a, E, Pa block: None, block_header: Some(block.block_header()), }), + BeaconBlock::Fulu(_) => Ok(Web3SignerObject::BeaconBlock { + version: ForkName::Fulu, + block: None, + block_header: Some(block.block_header()), + }), } }