From a9a9ccfad0651504940c9e12e3cdbe3211cf630c Mon Sep 17 00:00:00 2001 From: Eitan Seri-Levi Date: Mon, 27 Apr 2026 12:51:16 +0200 Subject: [PATCH] heze boilerplate --- .../beacon_chain/src/beacon_block_streamer.rs | 4 +- beacon_node/beacon_chain/src/beacon_chain.rs | 5 + .../src/block_production/gloas.rs | 39 +- beacon_node/beacon_chain/src/builder.rs | 15 +- .../src/data_column_verification.rs | 12 +- beacon_node/beacon_chain/src/errors.rs | 2 + beacon_node/beacon_chain/src/kzg_utils.rs | 92 +- .../src/observed_data_sidecars.rs | 23 +- beacon_node/beacon_chain/src/test_utils.rs | 44 +- beacon_node/client/src/notifier.rs | 2 +- beacon_node/execution_layer/src/engine_api.rs | 21 +- .../execution_layer/src/engine_api/http.rs | 55 + .../src/engine_api/json_structures.rs | 97 +- .../src/engine_api/new_payload_request.rs | 20 +- beacon_node/execution_layer/src/lib.rs | 62 + .../test_utils/execution_block_generator.rs | 36 +- .../src/test_utils/handle_rpc.rs | 64 +- .../src/test_utils/mock_builder.rs | 10 +- .../src/test_utils/mock_execution_layer.rs | 3 + .../execution_layer/src/test_utils/mod.rs | 7 + .../lighthouse_network/src/rpc/codec.rs | 10 +- .../lighthouse_network/src/rpc/protocol.rs | 11 +- .../lighthouse_network/src/types/pubsub.rs | 7 +- .../lighthouse_network/tests/common.rs | 2 + beacon_node/network/src/sync/manager.rs | 4 +- common/eth2/src/types.rs | 17 +- .../src/fork_choice_test_definition.rs | 2 + .../heze_payload.rs | 1239 +++++++++++++++++ .../common/get_attestation_participation.rs | 2 +- consensus/state_processing/src/genesis.rs | 20 +- .../src/per_slot_processing.rs | 7 +- consensus/state_processing/src/upgrade.rs | 2 + .../state_processing/src/upgrade/heze.rs | 105 ++ consensus/types/src/block/beacon_block.rs | 125 +- .../types/src/block/beacon_block_body.rs | 132 +- consensus/types/src/block/mod.rs | 10 +- .../types/src/block/signed_beacon_block.rs | 36 +- consensus/types/src/builder/builder_bid.rs | 4 +- consensus/types/src/core/chain_spec.rs | 55 +- consensus/types/src/core/config_and_preset.rs | 42 +- consensus/types/src/core/mod.rs | 4 +- consensus/types/src/core/preset.rs | 10 + .../types/src/data/data_column_sidecar.rs | 38 +- consensus/types/src/data/mod.rs | 2 +- consensus/types/src/execution/dumb_macros.rs | 3 + .../types/src/execution/execution_payload.rs | 17 +- .../src/execution/execution_payload_header.rs | 9 +- consensus/types/src/execution/mod.rs | 4 +- consensus/types/src/execution/payload.rs | 2 +- consensus/types/src/fork/fork_macros.rs | 4 + consensus/types/src/fork/fork_name.rs | 31 +- consensus/types/src/light_client/error.rs | 1 + .../light_client/light_client_bootstrap.rs | 8 +- .../light_client_finality_update.rs | 7 +- .../src/light_client/light_client_header.rs | 10 +- .../light_client_optimistic_update.rs | 7 +- .../src/light_client/light_client_update.rs | 7 +- consensus/types/src/state/beacon_state.rs | 121 +- consensus/types/src/state/mod.rs | 3 +- .../src/withdrawal/expected_withdrawals.rs | 9 +- consensus/types/src/withdrawal/mod.rs | 2 +- lcli/src/mock_el.rs | 2 + testing/ef_tests/src/cases/fork.rs | 3 +- .../src/cases/merkle_proof_validity.rs | 9 +- testing/ef_tests/src/cases/transition.rs | 10 + testing/ef_tests/src/handler.rs | 24 +- testing/ef_tests/src/type_name.rs | 3 + testing/ef_tests/tests/tests.rs | 12 + .../beacon_node_fallback/src/lib.rs | 7 + .../signing_method/src/web3signer.rs | 6 + 70 files changed, 2643 insertions(+), 177 deletions(-) create mode 100644 consensus/proto_array/src/fork_choice_test_definition/heze_payload.rs create mode 100644 consensus/state_processing/src/upgrade/heze.rs diff --git a/beacon_node/beacon_chain/src/beacon_block_streamer.rs b/beacon_node/beacon_chain/src/beacon_block_streamer.rs index ed74022c3d..1c12a26127 100644 --- a/beacon_node/beacon_chain/src/beacon_block_streamer.rs +++ b/beacon_node/beacon_chain/src/beacon_block_streamer.rs @@ -16,7 +16,7 @@ use types::{ }; use types::{ ExecutionPayload, ExecutionPayloadBellatrix, ExecutionPayloadCapella, ExecutionPayloadElectra, - ExecutionPayloadFulu, ExecutionPayloadGloas, ExecutionPayloadHeader, + ExecutionPayloadFulu, ExecutionPayloadGloas, ExecutionPayloadHeader, ExecutionPayloadHeze, }; #[derive(PartialEq)] @@ -102,6 +102,7 @@ fn reconstruct_default_header_block( ForkName::Electra => ExecutionPayloadElectra::default().into(), ForkName::Fulu => ExecutionPayloadFulu::default().into(), ForkName::Gloas => ExecutionPayloadGloas::default().into(), + ForkName::Heze => ExecutionPayloadHeze::default().into(), ForkName::Base | ForkName::Altair => { return Err(Error::PayloadReconstruction(format!( "Block with fork variant {} has execution payload", @@ -734,6 +735,7 @@ mod tests { spec.electra_fork_epoch = Some(Epoch::new(electra_fork_epoch as u64)); spec.fulu_fork_epoch = Some(Epoch::new(fulu_fork_epoch as u64)); spec.gloas_fork_epoch = None; + spec.heze_fork_epoch = None; 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 bfe1b404e0..a8b46de283 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -5963,6 +5963,11 @@ impl BeaconChain { "Attempting to produce gloas beacon block via non gloas code path".to_owned(), )); } + BeaconState::Heze(_) => { + return Err(BlockProductionError::HezeNotImplemented( + "Attempting to produce heze beacon block via non heze code path".to_owned(), + )); + } }; let block = SignedBeaconBlock::from_block( diff --git a/beacon_node/beacon_chain/src/block_production/gloas.rs b/beacon_node/beacon_chain/src/block_production/gloas.rs index 9b3fc2806e..a0cb2d0f64 100644 --- a/beacon_node/beacon_chain/src/block_production/gloas.rs +++ b/beacon_node/beacon_chain/src/block_production/gloas.rs @@ -26,7 +26,8 @@ use tree_hash::TreeHash; use types::consts::gloas::BUILDER_INDEX_SELF_BUILD; use types::{ Address, Attestation, AttestationElectra, AttesterSlashing, AttesterSlashingElectra, - BeaconBlock, BeaconBlockBodyGloas, BeaconBlockGloas, BeaconState, BeaconStateError, + BeaconBlock, BeaconBlockBodyGloas, BeaconBlockBodyHeze, BeaconBlockGloas, BeaconBlockHeze, + BeaconState, BeaconStateError, BuilderIndex, Deposit, Eth1Data, EthSpec, ExecutionBlockHash, ExecutionPayloadBid, ExecutionPayloadEnvelope, ExecutionPayloadGloas, ExecutionRequests, FullPayload, Graffiti, Hash256, PayloadAttestation, ProposerSlashing, RelativeEpoch, SignedBeaconBlock, @@ -524,6 +525,42 @@ impl BeaconChain { _phantom: PhantomData::>, }, }), + BeaconState::Heze(_) => BeaconBlock::Heze(BeaconBlockHeze { + slot, + proposer_index, + parent_root, + state_root: Hash256::ZERO, + body: BeaconBlockBodyHeze { + randao_reveal, + eth1_data, + graffiti, + proposer_slashings: proposer_slashings + .try_into() + .map_err(BlockProductionError::SszTypesError)?, + attester_slashings: attester_slashings + .try_into() + .map_err(BlockProductionError::SszTypesError)?, + attestations: attestations + .try_into() + .map_err(BlockProductionError::SszTypesError)?, + deposits: deposits + .try_into() + .map_err(BlockProductionError::SszTypesError)?, + voluntary_exits: voluntary_exits + .try_into() + .map_err(BlockProductionError::SszTypesError)?, + sync_aggregate, + bls_to_execution_changes: bls_to_execution_changes + .try_into() + .map_err(BlockProductionError::SszTypesError)?, + parent_execution_requests, + signed_execution_payload_bid, + payload_attestations: payload_attestations + .try_into() + .map_err(BlockProductionError::SszTypesError)?, + _phantom: PhantomData::>, + }, + }), }; let signed_beacon_block = SignedBeaconBlock::from_block( diff --git a/beacon_node/beacon_chain/src/builder.rs b/beacon_node/beacon_chain/src/builder.rs index 19eb1aa877..a629de25c8 100644 --- a/beacon_node/beacon_chain/src/builder.rs +++ b/beacon_node/beacon_chain/src/builder.rs @@ -8,7 +8,10 @@ use crate::custody_context::NodeCustodyType; use crate::data_availability_checker::DataAvailabilityChecker; use crate::fork_choice_signal::ForkChoiceSignalTx; use crate::graffiti_calculator::{GraffitiCalculator, GraffitiOrigin}; -use crate::kzg_utils::{build_data_column_sidecars_fulu, build_data_column_sidecars_gloas}; +use crate::kzg_utils::{ + build_data_column_sidecars_fulu, build_data_column_sidecars_gloas, + build_data_column_sidecars_heze, +}; use crate::light_client_server_cache::LightClientServerCache; use crate::migrate::{BackgroundMigrator, MigratorConfig}; use crate::observed_data_sidecars::ObservedDataSidecars; @@ -1235,7 +1238,15 @@ fn build_data_columns_from_blobs( .cloned() .map_err(|e| format!("Unexpected pre Deneb block: {e:?}"))?; - if block.fork_name_unchecked().gloas_enabled() { + if block.fork_name_unchecked().heze_enabled() { + build_data_column_sidecars_heze( + block.message().tree_hash_root(), + block.slot(), + blob_cells_and_proofs_vec, + spec, + ) + .map_err(|e| format!("Failed to compute weak subjectivity data_columns: {e:?}"))? + } else if block.fork_name_unchecked().gloas_enabled() { build_data_column_sidecars_gloas( block.message().tree_hash_root(), block.slot(), diff --git a/beacon_node/beacon_chain/src/data_column_verification.rs b/beacon_node/beacon_chain/src/data_column_verification.rs index 8ea3c792f4..2f2fe6a781 100644 --- a/beacon_node/beacon_chain/src/data_column_verification.rs +++ b/beacon_node/beacon_chain/src/data_column_verification.rs @@ -320,7 +320,9 @@ impl GossipVerifiedDataColumn }) } // TODO(gloas) support gloas data column variant - DataColumnSidecar::Gloas(_) => Err(GossipDataColumnError::InvalidVariant), + DataColumnSidecar::Gloas(_) | DataColumnSidecar::Heze(_) => { + Err(GossipDataColumnError::InvalidVariant) + } } } @@ -1129,7 +1131,9 @@ fn verify_data_column_sidecar( // TODO(gloas): implement Gloas verification that takes kzg_commitments from block as parameter let commitments_len = match data_column { DataColumnSidecar::Fulu(dc) => dc.kzg_commitments.len(), - DataColumnSidecar::Gloas(_) => return Err(GossipDataColumnError::InvalidVariant), + DataColumnSidecar::Gloas(_) | DataColumnSidecar::Heze(_) => { + return Err(GossipDataColumnError::InvalidVariant) + } }; if commitments_len == 0 { @@ -1564,8 +1568,8 @@ mod test { if !spec.is_fulu_scheduled() { return; } - // Gloas is not supported yet. - if spec.is_gloas_scheduled() { + // Gloas and Heze are not supported yet. + if spec.is_gloas_scheduled() || spec.is_heze_scheduled() { return; } diff --git a/beacon_node/beacon_chain/src/errors.rs b/beacon_node/beacon_chain/src/errors.rs index 9802f091e0..f71afe8e13 100644 --- a/beacon_node/beacon_chain/src/errors.rs +++ b/beacon_node/beacon_chain/src/errors.rs @@ -326,6 +326,8 @@ pub enum BlockProductionError { MissingExecutionPayloadEnvelope(Hash256), // TODO(gloas): Remove this once Gloas is implemented GloasNotImplemented(String), + // TODO(heze): Remove this once Heze is implemented + HezeNotImplemented(String), } easy_from_to!(BlockProcessingError, BlockProductionError); diff --git a/beacon_node/beacon_chain/src/kzg_utils.rs b/beacon_node/beacon_chain/src/kzg_utils.rs index 9641aec47d..51395a75be 100644 --- a/beacon_node/beacon_chain/src/kzg_utils.rs +++ b/beacon_node/beacon_chain/src/kzg_utils.rs @@ -13,7 +13,8 @@ use types::data::{ use types::kzg_ext::KzgCommitments; use types::{ Blob, BlobSidecar, BlobSidecarList, ChainSpec, DataColumnSidecar, DataColumnSidecarFulu, - DataColumnSidecarGloas, DataColumnSidecarList, EthSpec, Hash256, KzgCommitment, KzgProof, + DataColumnSidecarGloas, DataColumnSidecarHeze, DataColumnSidecarList, EthSpec, Hash256, + KzgCommitment, KzgProof, SignedBeaconBlock, SignedBeaconBlockHeader, SignedBlindedBeaconBlock, Slot, }; @@ -80,11 +81,11 @@ pub fn validate_full_data_columns<'a, E: EthSpec>( // This function requires Fulu sidecars with embedded commitments. let kzg_commitments = match data_column.as_ref() { DataColumnSidecar::Fulu(dc) => &dc.kzg_commitments, - DataColumnSidecar::Gloas(_) => { + DataColumnSidecar::Gloas(_) | DataColumnSidecar::Heze(_) => { return Err(( Some(col_index), KzgError::InconsistentArrayLength( - "Gloas data columns require commitments from block".to_string(), + "Gloas/Heze data columns require commitments from block".to_string(), ), )); } @@ -274,7 +275,15 @@ pub fn blobs_to_data_column_sidecars( }) .collect::, KzgError>>()?; - if block.fork_name_unchecked().gloas_enabled() { + if block.fork_name_unchecked().heze_enabled() { + build_data_column_sidecars_heze( + signed_block_header.message.tree_hash_root(), + block.slot(), + blob_cells_and_proofs_vec, + spec, + ) + .map_err(DataColumnSidecarError::BuildSidecarFailed) + } else if block.fork_name_unchecked().gloas_enabled() { build_data_column_sidecars_gloas( signed_block_header.message.tree_hash_root(), block.slot(), @@ -365,7 +374,7 @@ pub(crate) fn build_data_column_sidecars_fulu( .fork_name_at_slot::(signed_block_header.message.slot) .gloas_enabled() { - return Err("Attempting to construct Fulu data columns post-Gloas".to_owned()); + return Err("Attempting to construct Fulu data columns post-Gloas/Heze".to_owned()); } let number_of_columns = E::number_of_columns(); @@ -490,6 +499,68 @@ pub(crate) fn build_data_column_sidecars_gloas( sidecars } +pub(crate) fn build_data_column_sidecars_heze( + beacon_block_root: Hash256, + slot: Slot, + blob_cells_and_proofs_vec: Vec, + spec: &ChainSpec, +) -> Result, String> { + if !spec.fork_name_at_slot::(slot).heze_enabled() { + return Err("Attempting to construct Heze data columns pre-Heze".to_owned()); + } + + let number_of_columns = E::number_of_columns(); + let max_blobs_per_block = spec.max_blobs_per_block(slot.epoch(E::slots_per_epoch())) as usize; + let mut columns = vec![Vec::with_capacity(max_blobs_per_block); number_of_columns]; + let mut column_kzg_proofs = vec![Vec::with_capacity(max_blobs_per_block); number_of_columns]; + + for (blob_cells, blob_cell_proofs) in blob_cells_and_proofs_vec { + for col in 0..number_of_columns { + let cell = blob_cells + .get(col) + .ok_or(format!("Missing blob cell at index {col}"))?; + let cell: Vec = cell.to_vec(); + let cell = + Cell::::try_from(cell).map_err(|e| format!("BytesPerCell exceeded: {e:?}"))?; + + let proof = blob_cell_proofs + .get(col) + .ok_or(format!("Missing blob cell KZG proof at index {col}"))?; + + let column = columns + .get_mut(col) + .ok_or(format!("Missing data column at index {col}"))?; + let column_proofs = column_kzg_proofs + .get_mut(col) + .ok_or(format!("Missing data column proofs at index {col}"))?; + + column.push(cell); + column_proofs.push(*proof); + } + } + + let sidecars: Result>>, String> = columns + .into_iter() + .zip(column_kzg_proofs) + .enumerate() + .map( + |(index, (col, proofs))| -> Result>, String> { + Ok(Arc::new(DataColumnSidecar::Heze(DataColumnSidecarHeze { + index: index as u64, + column: DataColumn::::try_from(col) + .map_err(|e| format!("MaxBlobCommitmentsPerBlock exceeded: {e:?}"))?, + kzg_proofs: VariableList::try_from(proofs) + .map_err(|e| format!("MaxBlobCommitmentsPerBlock exceeded: {e:?}"))?, + beacon_block_root, + slot, + }))) + }, + ) + .collect(); + + sidecars +} + pub(crate) fn build_partial_data_columns( header: &PartialDataColumnHeader, blob_cells_and_proofs_vec: Vec>, @@ -600,7 +671,7 @@ pub fn reconstruct_blobs( // https://github.com/sigp/lighthouse/issues/7413 let num_of_blobs = first_data_column .kzg_commitments() - .map_err(|_| "Gloas blob reconstruction not yet supported".to_string())? + .map_err(|_| "Gloas/Heze blob reconstruction not yet supported".to_string())? .len(); (0..num_of_blobs).collect() } @@ -680,7 +751,7 @@ pub fn reconstruct_data_columns( .kzg_commitments() .map_err(|_| { KzgError::InconsistentArrayLength( - "Gloas data column reconstruction not yet supported".to_string(), + "Gloas/Heze data column reconstruction not yet supported".to_string(), ) })? .len(); @@ -722,6 +793,13 @@ pub fn reconstruct_data_columns( spec, ) .map_err(KzgError::ReconstructFailed), + DataColumnSidecar::Heze(first_column) => build_data_column_sidecars_heze( + first_column.beacon_block_root, + first_column.slot, + blob_cells_and_proofs_vec, + spec, + ) + .map_err(KzgError::ReconstructFailed), } } diff --git a/beacon_node/beacon_chain/src/observed_data_sidecars.rs b/beacon_node/beacon_chain/src/observed_data_sidecars.rs index 2461c8115d..54a165fa56 100644 --- a/beacon_node/beacon_chain/src/observed_data_sidecars.rs +++ b/beacon_node/beacon_chain/src/observed_data_sidecars.rs @@ -265,7 +265,8 @@ mod tests { use bls::{FixedBytesExtended, Signature}; use std::sync::Arc; use types::{ - BeaconBlockHeader, DataColumnSidecarFulu, DataColumnSidecarGloas, ForkName, MainnetEthSpec, + BeaconBlockHeader, DataColumnSidecarFulu, DataColumnSidecarGloas, DataColumnSidecarHeze, + ForkName, MainnetEthSpec, SignedBeaconBlockHeader, }; @@ -320,13 +321,31 @@ mod tests { })) } + /// Creates a Heze DataColumnSidecar for testing. + /// Keyed by (beacon_block_root, slot) in the observation cache. + fn get_data_column_sidecar_heze( + slot: u64, + beacon_block_root: Hash256, + index: u64, + ) -> Arc> { + Arc::new(DataColumnSidecar::Heze(DataColumnSidecarHeze { + index, + column: vec![].try_into().unwrap(), + kzg_proofs: vec![].try_into().unwrap(), + slot: slot.into(), + beacon_block_root, + })) + } + fn get_sidecar( slot: u64, key: u64, index: u64, fork_name: ForkName, ) -> Arc> { - if fork_name.gloas_enabled() { + if fork_name.heze_enabled() { + get_data_column_sidecar_heze(slot, Hash256::from_low_u64_be(key), index) + } else if fork_name.gloas_enabled() { get_data_column_sidecar_gloas(slot, Hash256::from_low_u64_be(key), index) } else { get_data_column_sidecar_fulu(slot, key, index) diff --git a/beacon_node/beacon_chain/src/test_utils.rs b/beacon_node/beacon_chain/src/test_utils.rs index 274f41d1cb..1f9b6fec4d 100644 --- a/beacon_node/beacon_chain/src/test_utils.rs +++ b/beacon_node/beacon_chain/src/test_utils.rs @@ -3,7 +3,10 @@ use crate::block_verification_types::{AsBlock, AvailableBlockData, LookupBlock, use crate::custody_context::NodeCustodyType; use crate::data_availability_checker::DataAvailabilityChecker; use crate::graffiti_calculator::GraffitiSettings; -use crate::kzg_utils::{build_data_column_sidecars_fulu, build_data_column_sidecars_gloas}; +use crate::kzg_utils::{ + build_data_column_sidecars_fulu, build_data_column_sidecars_gloas, + build_data_column_sidecars_heze, +}; use crate::observed_operations::ObservationOutcome; pub use crate::persisted_beacon_chain::PersistedBeaconChain; use crate::{BeaconBlockResponseWrapper, CustodyContext, get_block_root}; @@ -697,6 +700,10 @@ pub fn mock_execution_layer_from_parts( HARNESS_GENESIS_TIME + (spec.get_slot_duration().as_secs()) * E::slots_per_epoch() * epoch.as_u64() }); + let heze_time = spec.heze_fork_epoch.map(|epoch| { + HARNESS_GENESIS_TIME + + (spec.get_slot_duration().as_secs()) * E::slots_per_epoch() * epoch.as_u64() + }); let kzg = get_kzg(&spec); @@ -707,6 +714,7 @@ pub fn mock_execution_layer_from_parts( prague_time, osaka_time, amsterdam_time, + heze_time, Some(JwtKey::from_slice(&DEFAULT_JWT_SECRET).unwrap()), spec, Some(kzg), @@ -3803,7 +3811,39 @@ pub fn generate_data_column_sidecars_from_block( // Load the precomputed column sidecar to avoid computing them for every block in the tests. // Then repeat the cells and proofs for every blob - if block.fork_name_unchecked().gloas_enabled() { + if block.fork_name_unchecked().heze_enabled() { + let template_data_columns = + RuntimeVariableList::>::from_ssz_bytes( + TEST_DATA_COLUMN_SIDECARS_SSZ, + E::number_of_columns(), + ) + .unwrap(); + + let (cells, proofs) = template_data_columns + .into_iter() + .map(|sidecar| { + let DataColumnSidecarHeze { + column, kzg_proofs, .. + } = sidecar; + // There's only one cell per column for a single blob + let cell_bytes: Vec = column.into_iter().next().unwrap().into(); + let kzg_cell = cell_bytes.try_into().unwrap(); + let kzg_proof = kzg_proofs.into_iter().next().unwrap(); + (kzg_cell, kzg_proof) + }) + .collect::<(Vec<_>, Vec<_>)>(); + + let blob_cells_and_proofs_vec = + vec![(cells.try_into().unwrap(), proofs.try_into().unwrap()); kzg_commitments.len()]; + + build_data_column_sidecars_heze( + signed_block_header.message.tree_hash_root(), + signed_block_header.message.slot, + blob_cells_and_proofs_vec, + spec, + ) + .unwrap() + } else if block.fork_name_unchecked().gloas_enabled() { let template_data_columns = RuntimeVariableList::>::from_ssz_bytes( TEST_DATA_COLUMN_SIDECARS_SSZ, diff --git a/beacon_node/client/src/notifier.rs b/beacon_node/client/src/notifier.rs index 0d73a6bf7a..ac1771c5a4 100644 --- a/beacon_node/client/src/notifier.rs +++ b/beacon_node/client/src/notifier.rs @@ -554,7 +554,7 @@ fn methods_required_for_fork( missing_methods.push(ENGINE_NEW_PAYLOAD_V4); } } - ForkName::Gloas => { + ForkName::Gloas | ForkName::Heze => { if !capabilities.get_payload_v6 { missing_methods.push(ENGINE_GET_PAYLOAD_V6); } diff --git a/beacon_node/execution_layer/src/engine_api.rs b/beacon_node/execution_layer/src/engine_api.rs index acf5f2778b..166faaedb1 100644 --- a/beacon_node/execution_layer/src/engine_api.rs +++ b/beacon_node/execution_layer/src/engine_api.rs @@ -26,7 +26,8 @@ pub use types::{ }; use types::{ ExecutionPayloadBellatrix, ExecutionPayloadCapella, ExecutionPayloadDeneb, - ExecutionPayloadElectra, ExecutionPayloadFulu, ExecutionPayloadGloas, ExecutionRequests, + ExecutionPayloadElectra, ExecutionPayloadFulu, ExecutionPayloadGloas, ExecutionPayloadHeze, + ExecutionRequests, KzgProofs, }; use types::{GRAFFITI_BYTES_LEN, Graffiti}; @@ -39,7 +40,7 @@ mod new_payload_request; pub use new_payload_request::{ NewPayloadRequest, NewPayloadRequestBellatrix, NewPayloadRequestCapella, NewPayloadRequestDeneb, NewPayloadRequestElectra, NewPayloadRequestFulu, - NewPayloadRequestGloas, + NewPayloadRequestGloas, NewPayloadRequestHeze, }; pub const LATEST_TAG: &str = "latest"; @@ -300,7 +301,7 @@ pub struct ProposeBlindedBlockResponse { } #[superstruct( - variants(Bellatrix, Capella, Deneb, Electra, Fulu, Gloas), + variants(Bellatrix, Capella, Deneb, Electra, Fulu, Gloas, Heze), variant_attributes(derive(Clone, Debug, PartialEq),), map_into(ExecutionPayload), map_ref_into(ExecutionPayloadRef), @@ -324,12 +325,14 @@ pub struct GetPayloadResponse { pub execution_payload: ExecutionPayloadFulu, #[superstruct(only(Gloas), partial_getter(rename = "execution_payload_gloas"))] pub execution_payload: ExecutionPayloadGloas, + #[superstruct(only(Heze), partial_getter(rename = "execution_payload_heze"))] + pub execution_payload: ExecutionPayloadHeze, pub block_value: Uint256, - #[superstruct(only(Deneb, Electra, Fulu, Gloas))] + #[superstruct(only(Deneb, Electra, Fulu, Gloas, Heze))] pub blobs_bundle: BlobsBundle, - #[superstruct(only(Deneb, Electra, Fulu, Gloas), partial_getter(copy))] + #[superstruct(only(Deneb, Electra, Fulu, Gloas, Heze), partial_getter(copy))] pub should_override_builder: bool, - #[superstruct(only(Electra, Fulu, Gloas))] + #[superstruct(only(Electra, Fulu, Gloas, Heze))] pub requests: ExecutionRequests, } @@ -409,6 +412,12 @@ impl From> Some(inner.blobs_bundle), Some(inner.requests), ), + GetPayloadResponse::Heze(inner) => ( + ExecutionPayload::Heze(inner.execution_payload), + inner.block_value, + Some(inner.blobs_bundle), + Some(inner.requests), + ), } } } diff --git a/beacon_node/execution_layer/src/engine_api/http.rs b/beacon_node/execution_layer/src/engine_api/http.rs index 110e155c77..6febe54f9f 100644 --- a/beacon_node/execution_layer/src/engine_api/http.rs +++ b/beacon_node/execution_layer/src/engine_api/http.rs @@ -933,6 +933,35 @@ impl HttpJsonRpc { Ok(response.into()) } + pub async fn new_payload_v5_heze( + &self, + new_payload_request_heze: NewPayloadRequestHeze<'_, E>, + ) -> Result { + let params = json!([ + JsonExecutionPayload::Heze( + new_payload_request_heze + .execution_payload + .clone() + .try_into()? + ), + new_payload_request_heze.versioned_hashes, + new_payload_request_heze.parent_beacon_block_root, + new_payload_request_heze + .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, @@ -1096,6 +1125,18 @@ impl HttpJsonRpc { .try_into() .map_err(Error::BadResponse) } + ForkName::Heze => { + let response: JsonGetPayloadResponseHeze = self + .rpc_request( + ENGINE_GET_PAYLOAD_V6, + params, + ENGINE_GET_PAYLOAD_TIMEOUT * self.execution_timeout_multiplier, + ) + .await?; + JsonGetPayloadResponse::Heze(response) + .try_into() + .map_err(Error::BadResponse) + } _ => Err(Error::UnsupportedForkVariant(format!( "called get_payload_v6 with {}", fork_name @@ -1420,6 +1461,13 @@ impl HttpJsonRpc { Err(Error::RequiredMethodUnsupported("engine_newPayloadV5")) } } + NewPayloadRequest::Heze(new_payload_request_heze) => { + if engine_capabilities.new_payload_v5 { + self.new_payload_v5_heze(new_payload_request_heze).await + } else { + Err(Error::RequiredMethodUnsupported("engine_newPayloadV5")) + } + } } } @@ -1469,6 +1517,13 @@ impl HttpJsonRpc { Err(Error::RequiredMethodUnsupported("engine_getPayloadV6")) } } + ForkName::Heze => { + if engine_capabilities.get_payload_v6 { + self.get_payload_v6(fork_name, payload_id).await + } else { + Err(Error::RequiredMethodUnsupported("engine_getPayloadV6")) + } + } 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 9d9391a1e1..c0bf4cf829 100644 --- a/beacon_node/execution_layer/src/engine_api/json_structures.rs +++ b/beacon_node/execution_layer/src/engine_api/json_structures.rs @@ -64,7 +64,7 @@ pub struct JsonPayloadIdResponse { } #[superstruct( - variants(Bellatrix, Capella, Deneb, Electra, Fulu, Gloas), + variants(Bellatrix, Capella, Deneb, Electra, Fulu, Gloas, Heze), variant_attributes( derive(Debug, PartialEq, Default, Serialize, Deserialize,), serde(bound = "E: EthSpec", rename_all = "camelCase"), @@ -99,18 +99,18 @@ pub struct JsonExecutionPayload { pub block_hash: ExecutionBlockHash, #[serde(with = "ssz_types::serde_utils::list_of_hex_var_list")] pub transactions: Transactions, - #[superstruct(only(Capella, Deneb, Electra, Fulu, Gloas))] + #[superstruct(only(Capella, Deneb, Electra, Fulu, Gloas, Heze))] pub withdrawals: VariableList, - #[superstruct(only(Deneb, Electra, Fulu, Gloas))] + #[superstruct(only(Deneb, Electra, Fulu, Gloas, Heze))] #[serde(with = "serde_utils::u64_hex_be")] pub blob_gas_used: u64, - #[superstruct(only(Deneb, Electra, Fulu, Gloas))] + #[superstruct(only(Deneb, Electra, Fulu, Gloas, Heze))] #[serde(with = "serde_utils::u64_hex_be")] pub excess_blob_gas: u64, - #[superstruct(only(Gloas))] + #[superstruct(only(Gloas, Heze))] #[serde(with = "ssz_types::serde_utils::hex_var_list")] pub block_access_list: VariableList, - #[superstruct(only(Gloas))] + #[superstruct(only(Gloas, Heze))] #[serde(with = "serde_utils::u64_hex_be")] pub slot_number: u64, } @@ -264,6 +264,34 @@ impl TryFrom> for JsonExecutionPayloadGloas } } +impl TryFrom> for JsonExecutionPayloadHeze { + type Error = ssz_types::Error; + + fn try_from(payload: ExecutionPayloadHeze) -> Result { + Ok(JsonExecutionPayloadHeze { + 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: withdrawals_to_json(payload.withdrawals)?, + blob_gas_used: payload.blob_gas_used, + excess_blob_gas: payload.excess_blob_gas, + block_access_list: payload.block_access_list, + slot_number: payload.slot_number.into(), + }) + } +} + impl TryFrom> for JsonExecutionPayload { type Error = ssz_types::Error; @@ -285,6 +313,9 @@ impl TryFrom> for JsonExecutionPayload { ExecutionPayload::Gloas(payload) => { Ok(JsonExecutionPayload::Gloas(payload.try_into()?)) } + ExecutionPayload::Heze(payload) => { + Ok(JsonExecutionPayload::Heze(payload.try_into()?)) + } } } } @@ -439,6 +470,34 @@ impl TryFrom> for ExecutionPayloadGloas } } +impl TryFrom> for ExecutionPayloadHeze { + type Error = ssz_types::Error; + + fn try_from(payload: JsonExecutionPayloadHeze) -> Result { + Ok(ExecutionPayloadHeze { + 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: withdrawals_from_json(payload.withdrawals)?, + blob_gas_used: payload.blob_gas_used, + excess_blob_gas: payload.excess_blob_gas, + block_access_list: payload.block_access_list, + slot_number: payload.slot_number.into(), + }) + } +} + impl TryFrom> for ExecutionPayload { type Error = ssz_types::Error; @@ -460,6 +519,9 @@ impl TryFrom> for ExecutionPayload { JsonExecutionPayload::Gloas(payload) => { Ok(ExecutionPayload::Gloas(payload.try_into()?)) } + JsonExecutionPayload::Heze(payload) => { + Ok(ExecutionPayload::Heze(payload.try_into()?)) + } } } } @@ -573,7 +635,7 @@ impl TryFrom for ExecutionRequests { } #[superstruct( - variants(Bellatrix, Capella, Deneb, Electra, Fulu, Gloas), + variants(Bellatrix, Capella, Deneb, Electra, Fulu, Gloas, Heze), variant_attributes( derive(Debug, PartialEq, Serialize, Deserialize), serde(bound = "E: EthSpec", rename_all = "camelCase") @@ -599,13 +661,15 @@ pub struct JsonGetPayloadResponse { pub execution_payload: JsonExecutionPayloadFulu, #[superstruct(only(Gloas), partial_getter(rename = "execution_payload_gloas"))] pub execution_payload: JsonExecutionPayloadGloas, + #[superstruct(only(Heze), partial_getter(rename = "execution_payload_heze"))] + pub execution_payload: JsonExecutionPayloadHeze, #[serde(with = "serde_utils::u256_hex_be")] pub block_value: Uint256, - #[superstruct(only(Deneb, Electra, Fulu, Gloas))] + #[superstruct(only(Deneb, Electra, Fulu, Gloas, Heze))] pub blobs_bundle: JsonBlobsBundleV1, - #[superstruct(only(Deneb, Electra, Fulu, Gloas))] + #[superstruct(only(Deneb, Electra, Fulu, Gloas, Heze))] pub should_override_builder: bool, - #[superstruct(only(Electra, Fulu, Gloas))] + #[superstruct(only(Electra, Fulu, Gloas, Heze))] pub execution_requests: JsonExecutionRequests, } @@ -676,6 +740,19 @@ impl TryFrom> for GetPayloadResponse { })?, })) } + JsonGetPayloadResponse::Heze(response) => { + Ok(GetPayloadResponse::Heze(GetPayloadResponseHeze { + execution_payload: response.execution_payload.try_into().map_err(|e| { + format!("Failed to convert json to execution payload: {:?}", e) + })?, + block_value: response.block_value, + blobs_bundle: response.blobs_bundle.into(), + should_override_builder: response.should_override_builder, + requests: response.execution_requests.try_into().map_err(|e| { + format!("Failed to convert json to execution requests: {:?}", e) + })?, + })) + } } } } 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 ba94296b85..56ce563389 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,12 @@ use types::{ }; use types::{ ExecutionPayloadBellatrix, ExecutionPayloadCapella, ExecutionPayloadDeneb, - ExecutionPayloadElectra, ExecutionPayloadFulu, ExecutionPayloadGloas, ExecutionRequests, + ExecutionPayloadElectra, ExecutionPayloadFulu, ExecutionPayloadGloas, ExecutionPayloadHeze, + ExecutionRequests, }; #[superstruct( - variants(Bellatrix, Capella, Deneb, Electra, Fulu, Gloas), + variants(Bellatrix, Capella, Deneb, Electra, Fulu, Gloas, Heze), variant_attributes(derive(Clone, Debug, PartialEq),), map_into(ExecutionPayload), map_ref_into(ExecutionPayloadRef), @@ -43,11 +44,13 @@ pub struct NewPayloadRequest<'block, E: EthSpec> { pub execution_payload: &'block ExecutionPayloadFulu, #[superstruct(only(Gloas), partial_getter(rename = "execution_payload_gloas"))] pub execution_payload: &'block ExecutionPayloadGloas, - #[superstruct(only(Deneb, Electra, Fulu, Gloas))] + #[superstruct(only(Heze), partial_getter(rename = "execution_payload_heze"))] + pub execution_payload: &'block ExecutionPayloadHeze, + #[superstruct(only(Deneb, Electra, Fulu, Gloas, Heze))] pub versioned_hashes: Vec, - #[superstruct(only(Deneb, Electra, Fulu, Gloas))] + #[superstruct(only(Deneb, Electra, Fulu, Gloas, Heze))] pub parent_beacon_block_root: Hash256, - #[superstruct(only(Electra, Fulu, Gloas))] + #[superstruct(only(Electra, Fulu, Gloas, Heze))] pub execution_requests: &'block ExecutionRequests, } @@ -60,6 +63,7 @@ impl<'block, E: EthSpec> NewPayloadRequest<'block, E> { Self::Electra(payload) => payload.execution_payload.parent_hash, Self::Fulu(payload) => payload.execution_payload.parent_hash, Self::Gloas(payload) => payload.execution_payload.parent_hash, + Self::Heze(payload) => payload.execution_payload.parent_hash, } } @@ -71,6 +75,7 @@ impl<'block, E: EthSpec> NewPayloadRequest<'block, E> { Self::Electra(payload) => payload.execution_payload.block_hash, Self::Fulu(payload) => payload.execution_payload.block_hash, Self::Gloas(payload) => payload.execution_payload.block_hash, + Self::Heze(payload) => payload.execution_payload.block_hash, } } @@ -82,6 +87,7 @@ impl<'block, E: EthSpec> NewPayloadRequest<'block, E> { Self::Electra(payload) => payload.execution_payload.block_number, Self::Fulu(payload) => payload.execution_payload.block_number, Self::Gloas(payload) => payload.execution_payload.block_number, + Self::Heze(payload) => payload.execution_payload.block_number, } } @@ -93,6 +99,7 @@ impl<'block, E: EthSpec> NewPayloadRequest<'block, E> { Self::Electra(request) => ExecutionPayloadRef::Electra(request.execution_payload), Self::Fulu(request) => ExecutionPayloadRef::Fulu(request.execution_payload), Self::Gloas(request) => ExecutionPayloadRef::Gloas(request.execution_payload), + Self::Heze(request) => ExecutionPayloadRef::Heze(request.execution_payload), } } @@ -106,6 +113,7 @@ impl<'block, E: EthSpec> NewPayloadRequest<'block, E> { Self::Electra(request) => ExecutionPayload::Electra(request.execution_payload.clone()), Self::Fulu(request) => ExecutionPayload::Fulu(request.execution_payload.clone()), Self::Gloas(request) => ExecutionPayload::Gloas(request.execution_payload.clone()), + Self::Heze(request) => ExecutionPayload::Heze(request.execution_payload.clone()), } } @@ -222,6 +230,7 @@ impl<'a, E: EthSpec> TryFrom> for NewPayloadRequest<'a, E> execution_requests: &block_ref.body.execution_requests, })), BeaconBlockRef::Gloas(_) => Err(Self::Error::IncorrectStateVariant), + BeaconBlockRef::Heze(_) => Err(Self::Error::IncorrectStateVariant), } } } @@ -244,6 +253,7 @@ impl<'a, E: EthSpec> TryFrom> for NewPayloadRequest<' ExecutionPayloadRef::Fulu(_) => Err(Self::Error::IncorrectStateVariant), //TODO(EIP7732): Probably time to just get rid of this ExecutionPayloadRef::Gloas(_) => Err(Self::Error::IncorrectStateVariant), + ExecutionPayloadRef::Heze(_) => Err(Self::Error::IncorrectStateVariant), } } } diff --git a/beacon_node/execution_layer/src/lib.rs b/beacon_node/execution_layer/src/lib.rs index 4146543fd5..930d506461 100644 --- a/beacon_node/execution_layer/src/lib.rs +++ b/beacon_node/execution_layer/src/lib.rs @@ -44,6 +44,7 @@ use tokio_stream::wrappers::WatchStream; use tracing::{Instrument, debug, debug_span, error, info, instrument, warn}; use tree_hash::TreeHash; use types::ExecutionPayloadGloas; +use types::ExecutionPayloadHeze; use types::builder::BuilderBid; use types::execution::BlockProductionVersion; use types::kzg_ext::KzgCommitments; @@ -219,6 +220,26 @@ impl From> for BlockProposalContentsGloas } } +pub struct BlockProposalContentsHeze { + pub payload: ExecutionPayloadHeze, + pub payload_value: Uint256, + pub blob_kzg_commitments: KzgCommitments, + pub blobs_and_proofs: (BlobsList, KzgProofs), + pub execution_requests: ExecutionRequests, +} + +impl From> for BlockProposalContentsHeze { + fn from(response: GetPayloadResponseHeze) -> Self { + Self { + payload: response.execution_payload, + payload_value: response.block_value, + blob_kzg_commitments: response.blobs_bundle.commitments, + blobs_and_proofs: (response.blobs_bundle.blobs, response.blobs_bundle.proofs), + execution_requests: response.requests, + } + } +} + pub enum BlockProposalContents> { Payload { payload: Payload, @@ -930,6 +951,44 @@ impl ExecutionLayer { Ok(payload_response.into()) } + /// Maps to the `engine_getPayload` JSON-RPC call for post-Heze payload construction. + /// + /// However, it will attempt to call `self.prepare_payload` if it cannot find an existing + /// payload id for the given parameters. + /// + /// ## Fallback Behavior + /// + /// The result will be returned from the first node that returns successfully. No more nodes + /// will be contacted. + #[instrument(level = "debug", skip_all)] + pub async fn get_payload_heze( + &self, + payload_parameters: PayloadParameters<'_>, + ) -> Result, Error> { + let payload_response_type = self.get_full_payload_caching(payload_parameters).await?; + let GetPayloadResponseType::Full(payload_response) = payload_response_type else { + return Err(Error::Unexpected( + "get_payload_heze should never return a blinded payload".to_owned(), + )); + }; + let GetPayloadResponse::Heze(payload_response) = payload_response else { + return Err(Error::Unexpected( + "get_payload_heze should always return a heze `GetPayloadResponse` variant" + .to_owned(), + )); + }; + metrics::inc_counter_vec( + &metrics::EXECUTION_LAYER_GET_PAYLOAD_OUTCOME, + &[metrics::SUCCESS], + ); + metrics::inc_counter_vec( + &metrics::EXECUTION_LAYER_GET_PAYLOAD_SOURCE, + &[metrics::LOCAL], + ); + + Ok(payload_response.into()) + } + /// Maps to the `engine_getPayload` JSON-RPC call. /// /// However, it will attempt to call `self.prepare_payload` if it cannot find an existing @@ -1690,6 +1749,9 @@ impl ExecutionLayer { ForkName::Gloas => { return Err(Error::InvalidForkForPayload); } + ForkName::Heze => { + return Err(Error::InvalidForkForPayload); + } }; return Ok(Some(payload)); } 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 16d8c03062..68498bc15d 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 @@ -26,7 +26,8 @@ use tree_hash_derive::TreeHash; use types::{ Blob, ChainSpec, EthSpec, ExecutionBlockHash, ExecutionPayload, ExecutionPayloadBellatrix, ExecutionPayloadCapella, ExecutionPayloadDeneb, ExecutionPayloadElectra, ExecutionPayloadFulu, - ExecutionPayloadGloas, ExecutionPayloadHeader, ExecutionRequests, ForkName, Hash256, KzgProofs, + ExecutionPayloadGloas, ExecutionPayloadHeze, ExecutionPayloadHeader, ExecutionRequests, + ForkName, Hash256, KzgProofs, Transaction, Transactions, Uint256, }; @@ -155,6 +156,7 @@ pub struct ExecutionBlockGenerator { pub prague_time: Option, // electra pub osaka_time: Option, // fulu pub amsterdam_time: Option, // gloas + pub heze_time: Option, // heze /* * deneb stuff */ @@ -185,6 +187,7 @@ impl ExecutionBlockGenerator { prague_time: Option, osaka_time: Option, amsterdam_time: Option, + heze_time: Option, kzg: Option>, ) -> Self { let mut generator = Self { @@ -204,6 +207,7 @@ impl ExecutionBlockGenerator { prague_time, osaka_time, amsterdam_time, + heze_time, blobs_bundles: <_>::default(), kzg, rng: make_rng(), @@ -255,6 +259,7 @@ impl ExecutionBlockGenerator { pub fn get_fork_at_timestamp(&self, timestamp: u64) -> ForkName { let forks = [ + (self.heze_time, ForkName::Heze), (self.amsterdam_time, ForkName::Gloas), (self.osaka_time, ForkName::Fulu), (self.prague_time, ForkName::Electra), @@ -778,6 +783,27 @@ impl ExecutionBlockGenerator { block_access_list: VariableList::empty(), slot_number: pa.slot_number.into(), }), + ForkName::Heze => ExecutionPayload::Heze(ExecutionPayloadHeze { + 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].try_into().unwrap(), + 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().try_into().unwrap(), + base_fee_per_gas: Uint256::from(1u64), + block_hash: ExecutionBlockHash::zero(), + transactions: vec![].try_into().unwrap(), + withdrawals: pa.withdrawals.clone().try_into().unwrap(), + blob_gas_used: 0, + excess_blob_gas: 0, + block_access_list: VariableList::empty(), + slot_number: pa.slot_number.into(), + }), _ => unreachable!(), }, }; @@ -969,6 +995,14 @@ pub fn generate_genesis_header(spec: &ChainSpec) -> Option { + // TODO(heze): we are using a Fulu header for now, but this gets fixed up by the + // genesis builder anyway which translates it to bid/latest_block_hash. + 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) + } } } 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 64eecccc58..44351b0035 100644 --- a/beacon_node/execution_layer/src/test_utils/handle_rpc.rs +++ b/beacon_node/execution_layer/src/test_utils/handle_rpc.rs @@ -126,9 +126,16 @@ pub async fn handle_rpc( .map(|jep| JsonExecutionPayload::Electra(jep)) }) .map_err(|s| (s, BAD_PARAMS_ERROR_CODE))?, - ENGINE_NEW_PAYLOAD_V5 => get_param::>(params, 0) - .map(|jep| JsonExecutionPayload::Gloas(jep)) - .map_err(|s| (s, BAD_PARAMS_ERROR_CODE))?, + ENGINE_NEW_PAYLOAD_V5 => { + // Try Heze first, fall back to Gloas + get_param::>(params, 0) + .map(|jep| JsonExecutionPayload::Heze(jep)) + .or_else(|_| { + get_param::>(params, 0) + .map(|jep| JsonExecutionPayload::Gloas(jep)) + }) + .map_err(|s| (s, BAD_PARAMS_ERROR_CODE))? + } _ => unreachable!(), }; @@ -238,6 +245,14 @@ pub async fn handle_rpc( )); } } + ForkName::Heze => { + if method != ENGINE_NEW_PAYLOAD_V5 { + return Err(( + format!("{} called after Heze fork!", method), + GENERIC_ERROR_CODE, + )); + } + } _ => unreachable!(), }; @@ -377,6 +392,24 @@ pub async fn handle_rpc( )); } + // validate method called correctly according to heze fork time + if ctx + .execution_block_generator + .read() + .get_fork_at_timestamp(response.timestamp()) + == ForkName::Heze + && (method == ENGINE_GET_PAYLOAD_V1 + || method == ENGINE_GET_PAYLOAD_V2 + || method == ENGINE_GET_PAYLOAD_V3 + || method == ENGINE_GET_PAYLOAD_V4 + || method == ENGINE_GET_PAYLOAD_V5) + { + return Err(( + format!("{} called after Heze fork!", method), + FORK_REQUEST_MISMATCH_ERROR_CODE, + )); + } + match method { ENGINE_GET_PAYLOAD_V1 => Ok(serde_json::to_value( JsonExecutionPayload::try_from(response).unwrap(), @@ -488,6 +521,23 @@ pub async fn handle_rpc( }) .unwrap() } + JsonExecutionPayload::Heze(execution_payload) => { + serde_json::to_value(JsonGetPayloadResponseHeze { + execution_payload, + block_value: Uint256::from(DEFAULT_MOCK_EL_PAYLOAD_VALUE_WEI), + blobs_bundle: maybe_blobs + .ok_or(( + "No blobs returned despite V6 Payload".to_string(), + GENERIC_ERROR_CODE, + ))? + .into(), + should_override_builder: false, + execution_requests: maybe_execution_requests + .unwrap_or_default() + .into(), + }) + .unwrap() + } _ => unreachable!(), }) } @@ -653,6 +703,14 @@ pub async fn handle_rpc( )); } } + ForkName::Heze => { + if method != ENGINE_FORKCHOICE_UPDATED_V4 { + return Err(( + format!("{} called after Heze fork! Use V4.", method), + FORK_REQUEST_MISMATCH_ERROR_CODE, + )); + } + } _ => unreachable!(), }; } 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 d6243a7c4d..e2562a40a4 100644 --- a/beacon_node/execution_layer/src/test_utils/mock_builder.rs +++ b/beacon_node/execution_layer/src/test_utils/mock_builder.rs @@ -475,6 +475,10 @@ impl MockBuilder { // TODO(EIP7732) Check if this is how we want to do error handling for gloas return Err("invalid fork".to_string()); } + SignedBlindedBeaconBlock::Heze(_) => { + // TODO(EIP7732) Check if this is how we want to do error handling for heze + return Err("invalid fork".to_string()); + } }; let block_hash = block .message() @@ -593,6 +597,10 @@ impl MockBuilder { // TODO(EIP7732) Check if this is how we want to do error handling for gloas return Err("invalid fork".to_string()); } + ForkName::Heze => { + // TODO(EIP7732) Check if this is how we want to do error handling for heze + return Err("invalid fork".to_string()); + } ForkName::Fulu => BuilderBid::Fulu(BuilderBidFulu { header: payload .as_fulu() @@ -912,7 +920,7 @@ impl MockBuilder { Some(head_block_root), None, ), - ForkName::Gloas => PayloadAttributes::new( + ForkName::Gloas | ForkName::Heze => PayloadAttributes::new( timestamp, *prev_randao, fee_recipient, 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 5b721bcab2..e0cc1ef5f2 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 @@ -24,6 +24,7 @@ impl MockExecutionLayer { None, None, None, + None, Some(JwtKey::from_slice(&DEFAULT_JWT_SECRET).unwrap()), Arc::new(spec), None, @@ -38,6 +39,7 @@ impl MockExecutionLayer { prague_time: Option, osaka_time: Option, amsterdam_time: Option, + heze_time: Option, jwt_key: Option, spec: Arc, kzg: Option>, @@ -53,6 +55,7 @@ impl MockExecutionLayer { prague_time, osaka_time, amsterdam_time, + heze_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 4eb03778f8..f71adf118d 100644 --- a/beacon_node/execution_layer/src/test_utils/mod.rs +++ b/beacon_node/execution_layer/src/test_utils/mod.rs @@ -86,6 +86,7 @@ pub struct MockExecutionConfig { pub prague_time: Option, pub osaka_time: Option, pub amsterdam_time: Option, + pub heze_time: Option, } impl Default for MockExecutionConfig { @@ -98,6 +99,7 @@ impl Default for MockExecutionConfig { prague_time: None, osaka_time: None, amsterdam_time: None, + heze_time: None, } } } @@ -119,6 +121,7 @@ impl MockServer { None, // FIXME(electra): should this be the default? None, // FIXME(fulu): should this be the default? None, // FIXME(gloas): should this be the default? + None, // FIXME(heze): should this be the default? None, ) } @@ -137,6 +140,7 @@ impl MockServer { prague_time, osaka_time, amsterdam_time, + heze_time, } = config; let last_echo_request = Arc::new(RwLock::new(None)); let preloaded_responses = Arc::new(Mutex::new(vec![])); @@ -146,6 +150,7 @@ impl MockServer { prague_time, osaka_time, amsterdam_time, + heze_time, kzg, ); @@ -207,6 +212,7 @@ impl MockServer { prague_time: Option, osaka_time: Option, amsterdam_time: Option, + heze_time: Option, kzg: Option>, ) -> Self { Self::new_with_config( @@ -219,6 +225,7 @@ impl MockServer { prague_time, osaka_time, amsterdam_time, + heze_time, }, kzg, ) diff --git a/beacon_node/lighthouse_network/src/rpc/codec.rs b/beacon_node/lighthouse_network/src/rpc/codec.rs index 75e035ae82..c1e1635d21 100644 --- a/beacon_node/lighthouse_network/src/rpc/codec.rs +++ b/beacon_node/lighthouse_network/src/rpc/codec.rs @@ -22,7 +22,7 @@ use types::{ LightClientOptimisticUpdate, LightClientUpdate, SignedBeaconBlock, SignedBeaconBlockAltair, SignedBeaconBlockBase, SignedBeaconBlockBellatrix, SignedBeaconBlockCapella, SignedBeaconBlockDeneb, SignedBeaconBlockElectra, SignedBeaconBlockFulu, - SignedBeaconBlockGloas, + SignedBeaconBlockGloas, SignedBeaconBlockHeze, }; use unsigned_varint::codec::Uvi; @@ -896,6 +896,9 @@ fn handle_rpc_response( Some(ForkName::Gloas) => Ok(Some(RpcSuccessResponse::BlocksByRange(Arc::new( SignedBeaconBlock::Gloas(SignedBeaconBlockGloas::from_ssz_bytes(decoded_buffer)?), )))), + Some(ForkName::Heze) => Ok(Some(RpcSuccessResponse::BlocksByRange(Arc::new( + SignedBeaconBlock::Heze(SignedBeaconBlockHeze::from_ssz_bytes(decoded_buffer)?), + )))), None => Err(RPCError::ErrorResponse( RpcErrorResponse::InvalidRequest, format!( @@ -935,6 +938,9 @@ fn handle_rpc_response( Some(ForkName::Gloas) => Ok(Some(RpcSuccessResponse::BlocksByRoot(Arc::new( SignedBeaconBlock::Gloas(SignedBeaconBlockGloas::from_ssz_bytes(decoded_buffer)?), )))), + Some(ForkName::Heze) => Ok(Some(RpcSuccessResponse::BlocksByRoot(Arc::new( + SignedBeaconBlock::Heze(SignedBeaconBlockHeze::from_ssz_bytes(decoded_buffer)?), + )))), None => Err(RPCError::ErrorResponse( RpcErrorResponse::InvalidRequest, format!( @@ -992,6 +998,7 @@ mod tests { chain_spec.electra_fork_epoch = Some(Epoch::new(5)); chain_spec.fulu_fork_epoch = Some(Epoch::new(6)); chain_spec.gloas_fork_epoch = Some(Epoch::new(7)); + chain_spec.heze_fork_epoch = Some(Epoch::new(8)); // check that we have all forks covered assert!(chain_spec.fork_epoch(ForkName::latest()).is_some()); @@ -1008,6 +1015,7 @@ mod tests { ForkName::Electra => spec.electra_fork_epoch, ForkName::Fulu => spec.fulu_fork_epoch, ForkName::Gloas => spec.gloas_fork_epoch, + ForkName::Heze => spec.heze_fork_epoch, }; let current_slot = current_epoch.unwrap().start_slot(Spec::slots_per_epoch()); ForkContext::new::(current_slot, Hash256::zero(), spec) diff --git a/beacon_node/lighthouse_network/src/rpc/protocol.rs b/beacon_node/lighthouse_network/src/rpc/protocol.rs index c949dfe17d..9b451d0877 100644 --- a/beacon_node/lighthouse_network/src/rpc/protocol.rs +++ b/beacon_node/lighthouse_network/src/rpc/protocol.rs @@ -155,7 +155,8 @@ pub fn rpc_block_limits_by_fork(current_fork: ForkName) -> RpcLimits { | ForkName::Deneb | ForkName::Electra | ForkName::Fulu - | ForkName::Gloas => RpcLimits::new( + | ForkName::Gloas + | ForkName::Heze => RpcLimits::new( *SIGNED_BEACON_BLOCK_BASE_MIN, *SIGNED_BEACON_BLOCK_BELLATRIX_MAX, ), @@ -184,7 +185,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::Fulu | ForkName::Gloas => { + ForkName::Electra | ForkName::Fulu | ForkName::Gloas | ForkName::Heze => { RpcLimits::new(altair_fixed_len, *LIGHT_CLIENT_UPDATES_BY_RANGE_ELECTRA_MAX) } } @@ -204,7 +205,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::Fulu | ForkName::Gloas => { + ForkName::Electra | ForkName::Fulu | ForkName::Gloas | ForkName::Heze => { RpcLimits::new(altair_fixed_len, *LIGHT_CLIENT_FINALITY_UPDATE_ELECTRA_MAX) } } @@ -225,7 +226,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 | ForkName::Fulu | ForkName::Gloas => RpcLimits::new( + ForkName::Electra | ForkName::Fulu | ForkName::Gloas | ForkName::Heze => RpcLimits::new( altair_fixed_len, *LIGHT_CLIENT_OPTIMISTIC_UPDATE_ELECTRA_MAX, ), @@ -242,7 +243,7 @@ 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 | ForkName::Fulu | ForkName::Gloas => { + ForkName::Electra | ForkName::Fulu | ForkName::Gloas | ForkName::Heze => { 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 9875d4b0c4..5683b55307 100644 --- a/beacon_node/lighthouse_network/src/types/pubsub.rs +++ b/beacon_node/lighthouse_network/src/types/pubsub.rs @@ -14,7 +14,8 @@ use types::{ SignedAggregateAndProofBase, SignedAggregateAndProofElectra, SignedBeaconBlock, SignedBeaconBlockAltair, SignedBeaconBlockBase, SignedBeaconBlockBellatrix, SignedBeaconBlockCapella, SignedBeaconBlockDeneb, SignedBeaconBlockElectra, - SignedBeaconBlockFulu, SignedBeaconBlockGloas, SignedBlsToExecutionChange, + SignedBeaconBlockFulu, SignedBeaconBlockGloas, SignedBeaconBlockHeze, + SignedBlsToExecutionChange, SignedContributionAndProof, SignedExecutionPayloadBid, SignedExecutionPayloadEnvelope, SignedProposerPreferences, SignedVoluntaryExit, SingleAttestation, SubnetId, SyncCommitteeMessage, SyncSubnetId, @@ -257,6 +258,10 @@ impl PubsubMessage { SignedBeaconBlockGloas::from_ssz_bytes(data) .map_err(|e| format!("{:?}", e))?, ), + Some(ForkName::Heze) => SignedBeaconBlock::::Heze( + SignedBeaconBlockHeze::from_ssz_bytes(data) + .map_err(|e| format!("{:?}", e))?, + ), None => { return Err(format!( "Unknown gossipsub fork digest: {:?}", diff --git a/beacon_node/lighthouse_network/tests/common.rs b/beacon_node/lighthouse_network/tests/common.rs index 67fe569b72..3ee65e5ded 100644 --- a/beacon_node/lighthouse_network/tests/common.rs +++ b/beacon_node/lighthouse_network/tests/common.rs @@ -28,6 +28,7 @@ pub fn spec_with_all_forks_enabled() -> ChainSpec { chain_spec.electra_fork_epoch = Some(Epoch::new(5)); chain_spec.fulu_fork_epoch = Some(Epoch::new(6)); chain_spec.gloas_fork_epoch = Some(Epoch::new(7)); + chain_spec.heze_fork_epoch = Some(Epoch::new(8)); // check that we have all forks covered assert!(chain_spec.fork_epoch(ForkName::latest()).is_some()); @@ -45,6 +46,7 @@ pub fn fork_context(fork_name: ForkName, spec: &ChainSpec) -> ForkContext { ForkName::Electra => spec.electra_fork_epoch, ForkName::Fulu => spec.fulu_fork_epoch, ForkName::Gloas => spec.gloas_fork_epoch, + ForkName::Heze => spec.heze_fork_epoch, }; let current_slot = current_epoch .unwrap_or_else(|| panic!("expect fork {fork_name} to be scheduled")) diff --git a/beacon_node/network/src/sync/manager.rs b/beacon_node/network/src/sync/manager.rs index 734295ac1d..5930fd1c07 100644 --- a/beacon_node/network/src/sync/manager.rs +++ b/beacon_node/network/src/sync/manager.rs @@ -906,8 +906,8 @@ impl SyncManager { ); } // TODO(gloas) support gloas data column variant - DataColumnSidecar::Gloas(_) => { - error!("Gloas variant not yet supported") + DataColumnSidecar::Gloas(_) | DataColumnSidecar::Heze(_) => { + error!("Gloas/Heze variant not yet supported") } } } diff --git a/common/eth2/src/types.rs b/common/eth2/src/types.rs index 950abeadd8..fe41ad872e 100644 --- a/common/eth2/src/types.rs +++ b/common/eth2/src/types.rs @@ -1039,6 +1039,7 @@ impl SseDataColumnSidecar { let kzg_commitments: Vec = match data_column_sidecar { DataColumnSidecar::Fulu(dc) => dc.kzg_commitments.to_vec(), DataColumnSidecar::Gloas(_) => vec![], + DataColumnSidecar::Heze(_) => vec![], }; let versioned_hashes = kzg_commitments .iter() @@ -1194,7 +1195,7 @@ impl<'de> ContextDeserialize<'de, ForkName> for SsePayloadAttributes { ForkName::Capella => { Self::V2(Deserialize::deserialize(deserializer).map_err(convert_err)?) } - ForkName::Deneb | ForkName::Electra | ForkName::Fulu | ForkName::Gloas => { + ForkName::Deneb | ForkName::Electra | ForkName::Fulu | ForkName::Gloas | ForkName::Heze => { Self::V3(Deserialize::deserialize(deserializer).map_err(convert_err)?) } }) @@ -2565,6 +2566,9 @@ mod test { ExecutionPayload::Gloas(ExecutionPayloadGloas::::random_for_test( rng, )), + ExecutionPayload::Heze(ExecutionPayloadHeze::::random_for_test( + rng, + )), ]; let merged_forks = &ForkName::list_all()[2..]; assert_eq!( @@ -2630,6 +2634,17 @@ mod test { blobs_bundle, } }, + { + let execution_payload = + ExecutionPayload::Heze( + ExecutionPayloadHeze::::random_for_test(rng), + ); + let blobs_bundle = BlobsBundle::random_for_test(rng); + ExecutionPayloadAndBlobs { + execution_payload, + blobs_bundle, + } + }, ]; let blob_forks = &ForkName::list_all()[4..]; diff --git a/consensus/proto_array/src/fork_choice_test_definition.rs b/consensus/proto_array/src/fork_choice_test_definition.rs index d537f16bb2..9f2a46a38a 100644 --- a/consensus/proto_array/src/fork_choice_test_definition.rs +++ b/consensus/proto_array/src/fork_choice_test_definition.rs @@ -1,6 +1,7 @@ mod execution_status; mod ffg_updates; mod gloas_payload; +mod heze_payload; mod no_votes; mod votes; @@ -20,6 +21,7 @@ use types::{ pub use execution_status::*; pub use ffg_updates::*; pub use gloas_payload::*; +pub use heze_payload::*; pub use no_votes::*; pub use votes::*; diff --git a/consensus/proto_array/src/fork_choice_test_definition/heze_payload.rs b/consensus/proto_array/src/fork_choice_test_definition/heze_payload.rs new file mode 100644 index 0000000000..a420734609 --- /dev/null +++ b/consensus/proto_array/src/fork_choice_test_definition/heze_payload.rs @@ -0,0 +1,1239 @@ +use super::*; + +fn heze_spec() -> ChainSpec { + let mut spec = MainnetEthSpec::default_spec(); + spec.proposer_score_boost = Some(50); + spec.gloas_fork_epoch = Some(Epoch::new(0)); + spec.heze_fork_epoch = Some(Epoch::new(0)); + spec +} + +pub fn get_heze_chain_following_test_definition() -> ForkChoiceTestDefinition { + let mut ops = vec![]; + + // Build two branches off genesis where one child extends parent's payload chain (Full) + // and the other does not (Empty). + ops.push(Operation::ProcessBlock { + slot: Slot::new(1), + root: get_root(1), + parent_root: get_root(0), + justified_checkpoint: get_checkpoint(0), + finalized_checkpoint: get_checkpoint(0), + execution_payload_parent_hash: Some(get_hash(0)), + execution_payload_block_hash: Some(get_hash(1)), + }); + ops.push(Operation::ProcessBlock { + slot: Slot::new(1), + root: get_root(2), + parent_root: get_root(0), + justified_checkpoint: get_checkpoint(0), + finalized_checkpoint: get_checkpoint(0), + execution_payload_parent_hash: Some(get_hash(99)), + execution_payload_block_hash: Some(get_hash(2)), + }); + + // Extend both branches to verify that head selection follows the selected chain. + ops.push(Operation::ProcessBlock { + slot: Slot::new(2), + root: get_root(3), + parent_root: get_root(1), + justified_checkpoint: get_checkpoint(0), + finalized_checkpoint: get_checkpoint(0), + execution_payload_parent_hash: Some(get_hash(1)), + execution_payload_block_hash: Some(get_hash(3)), + }); + ops.push(Operation::ProcessBlock { + slot: Slot::new(2), + root: get_root(4), + parent_root: get_root(2), + justified_checkpoint: get_checkpoint(0), + finalized_checkpoint: get_checkpoint(0), + execution_payload_parent_hash: Some(get_hash(100)), + execution_payload_block_hash: Some(get_hash(4)), + }); + + // Mark root_1 as having received its execution payload so that + // its FULL virtual node exists in the Heze fork choice tree. + ops.push(Operation::ProcessExecutionPayloadEnvelope { + block_root: get_root(1), + }); + + ops.push(Operation::AssertParentPayloadStatus { + block_root: get_root(1), + expected_status: PayloadStatus::Full, + }); + ops.push(Operation::AssertParentPayloadStatus { + block_root: get_root(2), + expected_status: PayloadStatus::Empty, + }); + + // With equal full/empty parent weights, tiebreak decides which chain to follow. + ops.push(Operation::SetPayloadTiebreak { + block_root: get_root(0), + is_timely: true, + is_data_available: true, + }); + ops.push(Operation::FindHead { + justified_checkpoint: get_checkpoint(0), + finalized_checkpoint: get_checkpoint(0), + justified_state_balances: vec![1], + expected_head: get_root(3), + current_slot: Slot::new(0), + expected_payload_status: None, + }); + + // Cross-slot attestation with payload_present=true to Full branch (root 3, slot 2). + // vote_slot=3 differs from block_slot=2 and payload_present=true, so it counts as Full weight. + ops.push(Operation::ProcessGloasAttestation { + validator_index: 0, + block_root: get_root(3), + attestation_slot: Slot::new(3), + payload_present: true, + }); + ops.push(Operation::FindHead { + justified_checkpoint: get_checkpoint(0), + finalized_checkpoint: get_checkpoint(0), + justified_state_balances: vec![1], + expected_head: get_root(3), + current_slot: Slot::new(0), + expected_payload_status: None, + }); + + // Full weight propagated up: root 0 and root 1 should show Full. + ops.push(Operation::AssertPayloadStatusByWeight { + block_root: get_root(0), + expected_status: PayloadStatus::Full, + current_slot: None, + proposer_boost_root: None, + }); + ops.push(Operation::AssertPayloadStatusByWeight { + block_root: get_root(1), + expected_status: PayloadStatus::Full, + current_slot: None, + proposer_boost_root: None, + }); + // Root 2 has no payload received, so it's always Empty. + ops.push(Operation::AssertPayloadStatusByWeight { + block_root: get_root(2), + expected_status: PayloadStatus::Empty, + current_slot: None, + proposer_boost_root: None, + }); + + // Cross-slot attestations with payload_present=false to Empty branch (root 4, slot 2). + // Two validators so Empty branch outweighs Full branch. + ops.push(Operation::ProcessGloasAttestation { + validator_index: 1, + block_root: get_root(4), + attestation_slot: Slot::new(3), + payload_present: false, + }); + ops.push(Operation::ProcessGloasAttestation { + validator_index: 2, + block_root: get_root(4), + attestation_slot: Slot::new(3), + payload_present: false, + }); + ops.push(Operation::FindHead { + justified_checkpoint: get_checkpoint(0), + finalized_checkpoint: get_checkpoint(0), + justified_state_balances: vec![1, 1, 1], + expected_head: get_root(4), + current_slot: Slot::new(0), + expected_payload_status: None, + }); + + // Empty weight now dominates, so root 0 flips to Empty. + ops.push(Operation::AssertPayloadStatusByWeight { + block_root: get_root(0), + expected_status: PayloadStatus::Empty, + current_slot: None, + proposer_boost_root: None, + }); + ops.push(Operation::AssertPayloadStatusByWeight { + block_root: get_root(2), + expected_status: PayloadStatus::Empty, + current_slot: None, + proposer_boost_root: None, + }); + // Root 1 (Full branch) still has 1 Full vote and 0 Empty, so it stays Full. + ops.push(Operation::AssertPayloadStatusByWeight { + block_root: get_root(1), + expected_status: PayloadStatus::Full, + current_slot: None, + proposer_boost_root: None, + }); + + ForkChoiceTestDefinition { + finalized_block_slot: Slot::new(0), + justified_checkpoint: get_checkpoint(0), + finalized_checkpoint: get_checkpoint(0), + operations: ops, + execution_payload_parent_hash: Some(get_hash(42)), + execution_payload_block_hash: Some(get_hash(0)), + spec: Some(heze_spec()), + } +} + +pub fn get_heze_payload_probe_test_definition() -> ForkChoiceTestDefinition { + let mut ops = vec![]; + + // Block 1 at slot 1: child of genesis. Genesis has execution_payload_block_hash=zero + // (no execution payload at genesis), so all children have parent_payload_status=Empty. + ops.push(Operation::ProcessBlock { + slot: Slot::new(1), + root: get_root(1), + parent_root: get_root(0), + justified_checkpoint: get_checkpoint(0), + finalized_checkpoint: get_checkpoint(0), + execution_payload_parent_hash: Some(get_hash(0)), + execution_payload_block_hash: Some(get_hash(1)), + }); + + // One Full and one Empty vote for the same head block: tie probes via runtime tiebreak, + // which defaults to Empty unless timely+data-available evidence is set. + ops.push(Operation::ProcessPayloadAttestation { + validator_index: 0, + block_root: get_root(1), + attestation_slot: Slot::new(2), + payload_present: true, + blob_data_available: false, + }); + ops.push(Operation::ProcessPayloadAttestation { + validator_index: 1, + block_root: get_root(1), + attestation_slot: Slot::new(2), + payload_present: false, + blob_data_available: false, + }); + ops.push(Operation::FindHead { + justified_checkpoint: get_checkpoint(0), + finalized_checkpoint: get_checkpoint(0), + justified_state_balances: vec![1, 1], + expected_head: get_root(1), + current_slot: Slot::new(0), + // With MainnetEthSpec PTC_SIZE=512 and a 256-bit threshold, 1 bit set is not timely, so Empty. + expected_payload_status: Some(PayloadStatus::Empty), + }); + // PTC votes write to bitfields only, not to full/empty weight. + // Weight is 0 because no CL attestations target this block. + ops.push(Operation::AssertPayloadWeights { + block_root: get_root(1), + expected_full_weight: 0, + expected_empty_weight: 0, + }); + + // Flip validator 0 to Empty; both bits now clear. + ops.push(Operation::ProcessPayloadAttestation { + validator_index: 0, + block_root: get_root(1), + attestation_slot: Slot::new(3), + payload_present: false, + blob_data_available: false, + }); + ops.push(Operation::FindHead { + justified_checkpoint: get_checkpoint(0), + finalized_checkpoint: get_checkpoint(0), + justified_state_balances: vec![1, 1], + expected_head: get_root(1), + current_slot: Slot::new(0), + expected_payload_status: Some(PayloadStatus::Empty), + }); + ops.push(Operation::AssertPayloadWeights { + block_root: get_root(1), + expected_full_weight: 0, + expected_empty_weight: 0, + }); + + // Same-slot attestation to a new head candidate should be Pending (no payload bucket change). + // Root 5 is an Empty child of root_1 (parent_hash doesn't match root_1's block_hash), + // so it's reachable through root_1's Empty direction (root_1 has no payload_received). + ops.push(Operation::ProcessBlock { + slot: Slot::new(3), + root: get_root(5), + parent_root: get_root(1), + justified_checkpoint: get_checkpoint(0), + finalized_checkpoint: get_checkpoint(0), + execution_payload_parent_hash: Some(get_hash(101)), + execution_payload_block_hash: Some(get_hash(5)), + }); + ops.push(Operation::ProcessPayloadAttestation { + validator_index: 2, + block_root: get_root(5), + attestation_slot: Slot::new(3), + payload_present: true, + blob_data_available: false, + }); + ops.push(Operation::FindHead { + justified_checkpoint: get_checkpoint(0), + finalized_checkpoint: get_checkpoint(0), + justified_state_balances: vec![1, 1, 1], + expected_head: get_root(5), + current_slot: Slot::new(0), + expected_payload_status: Some(PayloadStatus::Empty), + }); + ops.push(Operation::AssertPayloadWeights { + block_root: get_root(5), + expected_full_weight: 0, + expected_empty_weight: 0, + }); + + ForkChoiceTestDefinition { + finalized_block_slot: Slot::new(0), + justified_checkpoint: get_checkpoint(0), + finalized_checkpoint: get_checkpoint(0), + operations: ops, + // Genesis has zero execution block hash (no payload at genesis), which + // ensures all children get parent_payload_status=Empty. + execution_payload_parent_hash: Some(ExecutionBlockHash::zero()), + execution_payload_block_hash: Some(ExecutionBlockHash::zero()), + spec: Some(heze_spec()), + } +} + +/// Test that CL attestation weight can flip the head between Full/Empty branches, +/// overriding the tiebreaker. +pub fn get_heze_find_head_vote_transition_test_definition() -> ForkChoiceTestDefinition { + let mut ops = vec![]; + + // Competing branches with distinct payload ancestry (Full vs Empty from genesis). + ops.push(Operation::ProcessBlock { + slot: Slot::new(1), + root: get_root(1), + parent_root: get_root(0), + justified_checkpoint: get_checkpoint(0), + finalized_checkpoint: get_checkpoint(0), + execution_payload_parent_hash: Some(get_hash(0)), + execution_payload_block_hash: Some(get_hash(1)), + }); + ops.push(Operation::ProcessBlock { + slot: Slot::new(1), + root: get_root(2), + parent_root: get_root(0), + justified_checkpoint: get_checkpoint(0), + finalized_checkpoint: get_checkpoint(0), + execution_payload_parent_hash: Some(get_hash(99)), + execution_payload_block_hash: Some(get_hash(2)), + }); + ops.push(Operation::ProcessBlock { + slot: Slot::new(2), + root: get_root(3), + parent_root: get_root(1), + justified_checkpoint: get_checkpoint(0), + finalized_checkpoint: get_checkpoint(0), + execution_payload_parent_hash: Some(get_hash(1)), + execution_payload_block_hash: Some(get_hash(3)), + }); + ops.push(Operation::ProcessBlock { + slot: Slot::new(2), + root: get_root(4), + parent_root: get_root(2), + justified_checkpoint: get_checkpoint(0), + finalized_checkpoint: get_checkpoint(0), + execution_payload_parent_hash: Some(get_hash(100)), + execution_payload_block_hash: Some(get_hash(4)), + }); + + // Mark root_1 as having received its execution payload so that + // its FULL virtual node exists in the Heze fork choice tree. + ops.push(Operation::ProcessExecutionPayloadEnvelope { + block_root: get_root(1), + }); + + // Equal branch weights: tiebreak FULL picks branch rooted at 3. + ops.push(Operation::SetPayloadTiebreak { + block_root: get_root(0), + is_timely: true, + is_data_available: true, + }); + ops.push(Operation::FindHead { + justified_checkpoint: get_checkpoint(0), + finalized_checkpoint: get_checkpoint(0), + justified_state_balances: vec![1], + expected_head: get_root(3), + current_slot: Slot::new(0), + expected_payload_status: None, + }); + + // CL attestation to Empty branch (root 4) from validator 0 flips the head to 4. + ops.push(Operation::ProcessAttestation { + validator_index: 0, + block_root: get_root(4), + attestation_slot: Slot::new(3), + }); + ops.push(Operation::FindHead { + justified_checkpoint: get_checkpoint(0), + finalized_checkpoint: get_checkpoint(0), + justified_state_balances: vec![1], + expected_head: get_root(4), + current_slot: Slot::new(0), + expected_payload_status: None, + }); + + // CL attestation back to Full branch (root 3) returns the head to 3. + ops.push(Operation::ProcessAttestation { + validator_index: 0, + block_root: get_root(3), + attestation_slot: Slot::new(4), + }); + ops.push(Operation::FindHead { + justified_checkpoint: get_checkpoint(0), + finalized_checkpoint: get_checkpoint(0), + justified_state_balances: vec![1], + expected_head: get_root(3), + current_slot: Slot::new(0), + expected_payload_status: None, + }); + + ForkChoiceTestDefinition { + finalized_block_slot: Slot::new(0), + justified_checkpoint: get_checkpoint(0), + finalized_checkpoint: get_checkpoint(0), + operations: ops, + execution_payload_parent_hash: Some(get_hash(42)), + execution_payload_block_hash: Some(get_hash(0)), + spec: Some(heze_spec()), + } +} + +/// CL attestation weight overrides payload preference tiebreaker. +pub fn get_heze_weight_priority_over_payload_preference_test_definition() +-> ForkChoiceTestDefinition { + let mut ops = vec![]; + + // Build two branches where one child extends payload (Full) and the other doesn't (Empty). + ops.push(Operation::ProcessBlock { + slot: Slot::new(1), + root: get_root(1), + parent_root: get_root(0), + justified_checkpoint: get_checkpoint(0), + finalized_checkpoint: get_checkpoint(0), + execution_payload_parent_hash: Some(get_hash(0)), + execution_payload_block_hash: Some(get_hash(1)), + }); + ops.push(Operation::ProcessBlock { + slot: Slot::new(1), + root: get_root(2), + parent_root: get_root(0), + justified_checkpoint: get_checkpoint(0), + finalized_checkpoint: get_checkpoint(0), + execution_payload_parent_hash: Some(get_hash(99)), + execution_payload_block_hash: Some(get_hash(2)), + }); + ops.push(Operation::ProcessBlock { + slot: Slot::new(2), + root: get_root(3), + parent_root: get_root(1), + justified_checkpoint: get_checkpoint(0), + finalized_checkpoint: get_checkpoint(0), + execution_payload_parent_hash: Some(get_hash(1)), + execution_payload_block_hash: Some(get_hash(3)), + }); + ops.push(Operation::ProcessBlock { + slot: Slot::new(2), + root: get_root(4), + parent_root: get_root(2), + justified_checkpoint: get_checkpoint(0), + finalized_checkpoint: get_checkpoint(0), + execution_payload_parent_hash: Some(get_hash(100)), + execution_payload_block_hash: Some(get_hash(4)), + }); + + // Mark root_1 as having received its execution payload so that + // its FULL virtual node exists in the Heze fork choice tree. + ops.push(Operation::ProcessExecutionPayloadEnvelope { + block_root: get_root(1), + }); + + // Parent prefers Full on equal branch weights (tiebreaker). + ops.push(Operation::SetPayloadTiebreak { + block_root: get_root(0), + is_timely: true, + is_data_available: true, + }); + ops.push(Operation::FindHead { + justified_checkpoint: get_checkpoint(0), + finalized_checkpoint: get_checkpoint(0), + justified_state_balances: vec![1], + expected_head: get_root(3), + current_slot: Slot::new(0), + expected_payload_status: None, + }); + + // Two CL attestations to the Empty branch make it strictly heavier, + // overriding the Full tiebreaker. + ops.push(Operation::ProcessAttestation { + validator_index: 0, + block_root: get_root(4), + attestation_slot: Slot::new(3), + }); + ops.push(Operation::ProcessAttestation { + validator_index: 1, + block_root: get_root(4), + attestation_slot: Slot::new(3), + }); + ops.push(Operation::FindHead { + justified_checkpoint: get_checkpoint(0), + finalized_checkpoint: get_checkpoint(0), + justified_state_balances: vec![1, 1], + expected_head: get_root(4), + current_slot: Slot::new(0), + expected_payload_status: None, + }); + + ForkChoiceTestDefinition { + finalized_block_slot: Slot::new(0), + justified_checkpoint: get_checkpoint(0), + finalized_checkpoint: get_checkpoint(0), + operations: ops, + execution_payload_parent_hash: Some(get_hash(42)), + execution_payload_block_hash: Some(get_hash(0)), + spec: Some(heze_spec()), + } +} + +pub fn get_heze_parent_empty_when_child_points_to_grandparent_test_definition() +-> ForkChoiceTestDefinition { + let mut ops = vec![]; + + // Build a three-block chain A -> B -> C (CL parent links). + // A: EL parent = genesis hash(0), EL hash = hash(1). + ops.push(Operation::ProcessBlock { + slot: Slot::new(1), + root: get_root(1), + parent_root: get_root(0), + justified_checkpoint: get_checkpoint(0), + finalized_checkpoint: get_checkpoint(0), + execution_payload_parent_hash: Some(get_hash(0)), + execution_payload_block_hash: Some(get_hash(1)), + }); + + // B: EL parent = hash(1), EL hash = hash(2). + ops.push(Operation::ProcessBlock { + slot: Slot::new(2), + root: get_root(2), + parent_root: get_root(1), + justified_checkpoint: get_checkpoint(0), + finalized_checkpoint: get_checkpoint(0), + execution_payload_parent_hash: Some(get_hash(1)), + execution_payload_block_hash: Some(get_hash(2)), + }); + + // C: CL parent is B, but EL parent points to A (hash 1), not B (hash 2). + // This models B's payload not arriving in time, so C records parent status as Empty. + ops.push(Operation::ProcessBlock { + slot: Slot::new(3), + root: get_root(3), + parent_root: get_root(2), + justified_checkpoint: get_checkpoint(0), + finalized_checkpoint: get_checkpoint(0), + execution_payload_parent_hash: Some(get_hash(1)), + execution_payload_block_hash: Some(get_hash(3)), + }); + + ops.push(Operation::AssertParentPayloadStatus { + block_root: get_root(3), + expected_status: PayloadStatus::Empty, + }); + + ForkChoiceTestDefinition { + finalized_block_slot: Slot::new(0), + justified_checkpoint: get_checkpoint(0), + finalized_checkpoint: get_checkpoint(0), + operations: ops, + execution_payload_parent_hash: Some(get_hash(42)), + execution_payload_block_hash: Some(get_hash(0)), + spec: Some(heze_spec()), + } +} + +/// Test interleaving of blocks, regular attestations, and tiebreaker. +/// +/// genesis → block 1 (Full) → block 3 +/// → block 2 (Empty) → block 4 +/// +/// With equal CL weight, tiebreaker determines which branch wins. +/// An extra CL attestation can override the tiebreaker. +pub fn get_heze_interleaved_attestations_test_definition() -> ForkChoiceTestDefinition { + let mut ops = vec![]; + + // Step 1: Two competing blocks at slot 1. + ops.push(Operation::ProcessBlock { + slot: Slot::new(1), + root: get_root(1), + parent_root: get_root(0), + justified_checkpoint: get_checkpoint(0), + finalized_checkpoint: get_checkpoint(0), + execution_payload_parent_hash: Some(get_hash(0)), + execution_payload_block_hash: Some(get_hash(1)), + }); + ops.push(Operation::ProcessBlock { + slot: Slot::new(1), + root: get_root(2), + parent_root: get_root(0), + justified_checkpoint: get_checkpoint(0), + finalized_checkpoint: get_checkpoint(0), + execution_payload_parent_hash: Some(get_hash(99)), + execution_payload_block_hash: Some(get_hash(2)), + }); + + // Step 2: Regular attestations arrive, one per branch (equal CL weight). + ops.push(Operation::ProcessAttestation { + validator_index: 0, + block_root: get_root(1), + attestation_slot: Slot::new(1), + }); + ops.push(Operation::ProcessAttestation { + validator_index: 1, + block_root: get_root(2), + attestation_slot: Slot::new(1), + }); + + // Step 3: Child blocks at slot 2. + ops.push(Operation::ProcessBlock { + slot: Slot::new(2), + root: get_root(3), + parent_root: get_root(1), + justified_checkpoint: get_checkpoint(0), + finalized_checkpoint: get_checkpoint(0), + execution_payload_parent_hash: Some(get_hash(1)), + execution_payload_block_hash: Some(get_hash(3)), + }); + ops.push(Operation::ProcessBlock { + slot: Slot::new(2), + root: get_root(4), + parent_root: get_root(2), + justified_checkpoint: get_checkpoint(0), + finalized_checkpoint: get_checkpoint(0), + execution_payload_parent_hash: Some(get_hash(100)), + execution_payload_block_hash: Some(get_hash(4)), + }); + + // Mark root_1 as having received its execution payload so that + // its FULL virtual node exists in the Heze fork choice tree. + ops.push(Operation::ProcessExecutionPayloadEnvelope { + block_root: get_root(1), + }); + + // Step 4: Set tiebreaker to Empty on genesis so the Empty branch wins. + ops.push(Operation::SetPayloadTiebreak { + block_root: get_root(0), + is_timely: false, + is_data_available: false, + }); + ops.push(Operation::FindHead { + justified_checkpoint: get_checkpoint(0), + finalized_checkpoint: get_checkpoint(0), + justified_state_balances: vec![1, 1], + expected_head: get_root(4), + current_slot: Slot::new(1), + expected_payload_status: None, + }); + // Weights are tied (1 vote each branch), tiebreaker is Empty. + ops.push(Operation::AssertPayloadStatusByWeight { + block_root: get_root(0), + expected_status: PayloadStatus::Empty, + current_slot: None, + proposer_boost_root: None, + }); + + // Step 5: Flip tiebreaker to Full so the Full branch wins. + ops.push(Operation::SetPayloadTiebreak { + block_root: get_root(0), + is_timely: true, + is_data_available: true, + }); + ops.push(Operation::FindHead { + justified_checkpoint: get_checkpoint(0), + finalized_checkpoint: get_checkpoint(0), + justified_state_balances: vec![1, 1], + expected_head: get_root(3), + current_slot: Slot::new(100), + expected_payload_status: None, + }); + // Weights still tied, tiebreaker flipped to Full. + ops.push(Operation::AssertPayloadStatusByWeight { + block_root: get_root(0), + expected_status: PayloadStatus::Full, + current_slot: None, + proposer_boost_root: None, + }); + + // Step 6: Add extra CL weight to the Empty branch; this overrides the Full tiebreaker. + ops.push(Operation::ProcessAttestation { + validator_index: 2, + block_root: get_root(4), + attestation_slot: Slot::new(3), + }); + ops.push(Operation::FindHead { + justified_checkpoint: get_checkpoint(0), + finalized_checkpoint: get_checkpoint(0), + justified_state_balances: vec![1, 1, 1], + expected_head: get_root(4), + current_slot: Slot::new(100), + expected_payload_status: None, + }); + + ForkChoiceTestDefinition { + finalized_block_slot: Slot::new(0), + justified_checkpoint: get_checkpoint(0), + finalized_checkpoint: get_checkpoint(0), + operations: ops, + execution_payload_parent_hash: Some(get_hash(42)), + execution_payload_block_hash: Some(get_hash(0)), + spec: Some(heze_spec()), + } +} + +/// Test interleaving of blocks, payload validation, and attestations. +/// +/// Scenario (branching at block 1 since genesis has no payload): +/// - Genesis block (slot 0) with zero execution block hash +/// - Block 1 (slot 1) child of genesis (Empty parent status since genesis hash=zero) +/// - Block 2 (slot 2) extends block 1 Full chain (parent_hash matches block 1's block_hash) +/// - Block 3 (slot 2) extends block 1 Empty chain (parent_hash doesn't match) +/// - Before payload arrives: payload_received is false for block 1, only Empty reachable +/// - Process execution payload for block 1 → payload_received becomes true +/// - Both Full and Empty directions from block 1 become available +/// - With equal weight, tiebreaker prefers Full → Block 2 wins +pub fn get_heze_payload_received_interleaving_test_definition() -> ForkChoiceTestDefinition { + let mut ops = vec![]; + + // Block 1 at slot 1: child of genesis. Genesis has zero block hash, so + // parent_payload_status = Empty regardless of block 1's execution_payload_parent_hash. + ops.push(Operation::ProcessBlock { + slot: Slot::new(1), + root: get_root(1), + parent_root: get_root(0), + justified_checkpoint: get_checkpoint(0), + finalized_checkpoint: get_checkpoint(0), + execution_payload_parent_hash: Some(get_hash(0)), + execution_payload_block_hash: Some(get_hash(1)), + }); + + // Block 2 at slot 2: Full child of block 1 (parent_hash matches block 1's block_hash). + ops.push(Operation::ProcessBlock { + slot: Slot::new(2), + root: get_root(2), + parent_root: get_root(1), + justified_checkpoint: get_checkpoint(0), + finalized_checkpoint: get_checkpoint(0), + execution_payload_parent_hash: Some(get_hash(1)), + execution_payload_block_hash: Some(get_hash(2)), + }); + + // Block 3 at slot 2: Empty child of block 1 (parent_hash doesn't match block 1's block_hash). + ops.push(Operation::ProcessBlock { + slot: Slot::new(2), + root: get_root(3), + parent_root: get_root(1), + justified_checkpoint: get_checkpoint(0), + finalized_checkpoint: get_checkpoint(0), + execution_payload_parent_hash: Some(get_hash(99)), + execution_payload_block_hash: Some(get_hash(3)), + }); + + // Verify parent_payload_status is set correctly. + ops.push(Operation::AssertParentPayloadStatus { + block_root: get_root(1), + expected_status: PayloadStatus::Empty, + }); + ops.push(Operation::AssertParentPayloadStatus { + block_root: get_root(2), + expected_status: PayloadStatus::Full, + }); + ops.push(Operation::AssertParentPayloadStatus { + block_root: get_root(3), + expected_status: PayloadStatus::Empty, + }); + + // Genesis does NOT have payload_received (no payload at genesis). + ops.push(Operation::AssertPayloadReceived { + block_root: get_root(0), + expected: false, + }); + + // Block 1 does not have payload_received yet. + ops.push(Operation::AssertPayloadReceived { + block_root: get_root(1), + expected: false, + }); + + // Give one vote to each competing child so they have equal weight. + ops.push(Operation::ProcessAttestation { + validator_index: 0, + block_root: get_root(2), + attestation_slot: Slot::new(2), + }); + ops.push(Operation::ProcessAttestation { + validator_index: 1, + block_root: get_root(3), + attestation_slot: Slot::new(2), + }); + + // Before payload_received on block 1: only Empty direction available. + // Block 3 (Empty child) is reachable, Block 2 (Full child) is not. + ops.push(Operation::FindHead { + justified_checkpoint: get_checkpoint(0), + finalized_checkpoint: get_checkpoint(0), + justified_state_balances: vec![1, 1], + expected_head: get_root(3), + current_slot: Slot::new(100), + expected_payload_status: None, + }); + + // Process execution payload envelope for block 1 → payload_received becomes true. + ops.push(Operation::ProcessExecutionPayloadEnvelope { + block_root: get_root(1), + }); + + ops.push(Operation::AssertPayloadReceived { + block_root: get_root(1), + expected: true, + }); + + // After payload_received on block 1: both Full and Empty directions available. + // Equal weight, tiebreaker prefers Full → Block 2 (Full child) wins. + ops.push(Operation::FindHead { + justified_checkpoint: get_checkpoint(0), + finalized_checkpoint: get_checkpoint(0), + justified_state_balances: vec![1, 1], + expected_head: get_root(2), + current_slot: Slot::new(100), + expected_payload_status: None, + }); + + ForkChoiceTestDefinition { + finalized_block_slot: Slot::new(0), + justified_checkpoint: get_checkpoint(0), + finalized_checkpoint: get_checkpoint(0), + operations: ops, + // Genesis has zero execution block hash (no payload at genesis). + execution_payload_parent_hash: Some(ExecutionBlockHash::zero()), + execution_payload_block_hash: Some(ExecutionBlockHash::zero()), + spec: Some(heze_spec()), + } +} + +/// When `current_slot == node.slot + 1`, spec `get_weight` zeroes out Full and Empty +/// weights so the tiebreaker decides. Tests that the zero-out is applied and +/// doesn't just compare raw payload weights. +pub fn get_heze_previous_slot_tiebreaker_test_definition() -> ForkChoiceTestDefinition { + let mut ops = vec![]; + + // Block 1 at slot 1 with its payload received. + // Genesis has zero block hash so all its children are Empty (genesis never has + // payload_received). Block 1's parent_hash doesn't match zero → Empty child. + ops.push(Operation::ProcessBlock { + slot: Slot::new(1), + root: get_root(1), + parent_root: get_root(0), + justified_checkpoint: get_checkpoint(0), + finalized_checkpoint: get_checkpoint(0), + execution_payload_parent_hash: Some(get_hash(0)), + execution_payload_block_hash: Some(get_hash(1)), + }); + ops.push(Operation::ProcessExecutionPayloadEnvelope { + block_root: get_root(1), + }); + + // Block 2 at slot 2 with a mismatched EL parent hash, giving it an Empty parent payload status. + ops.push(Operation::ProcessBlock { + slot: Slot::new(2), + root: get_root(2), + parent_root: get_root(1), + justified_checkpoint: get_checkpoint(0), + finalized_checkpoint: get_checkpoint(0), + execution_payload_parent_hash: Some(get_hash(99)), + execution_payload_block_hash: Some(get_hash(2)), + }); + + // More Full weight than Empty on block 1. + ops.push(Operation::ProcessGloasAttestation { + validator_index: 0, + block_root: get_root(1), + attestation_slot: Slot::new(2), + payload_present: true, + }); + + // Materialize the attestation into `full_payload_weight`. + ops.push(Operation::FindHead { + justified_checkpoint: get_checkpoint(0), + finalized_checkpoint: get_checkpoint(0), + justified_state_balances: vec![1], + expected_head: get_root(1), + current_slot: Slot::new(1), + expected_payload_status: Some(PayloadStatus::Full), + }); + + // Before zero-out (current_slot == block 1's slot), raw weights decide payload status (Full) + ops.push(Operation::AssertPayloadStatusByWeight { + block_root: get_root(1), + expected_status: PayloadStatus::Full, + current_slot: Some(Slot::new(1)), + proposer_boost_root: None, + }); + + // At current_slot == block 1's slot + 1, both weights zero out and the + // tiebreaker picks Empty (block 2 extends block 1 with an Empty parent + // payload status). + ops.push(Operation::AssertPayloadStatusByWeight { + block_root: get_root(1), + expected_status: PayloadStatus::Empty, + current_slot: Some(Slot::new(2)), + proposer_boost_root: Some(get_root(2)), + }); + + ForkChoiceTestDefinition { + finalized_block_slot: Slot::new(0), + justified_checkpoint: get_checkpoint(0), + finalized_checkpoint: get_checkpoint(0), + operations: ops, + execution_payload_parent_hash: Some(ExecutionBlockHash::zero()), + execution_payload_block_hash: Some(ExecutionBlockHash::zero()), + spec: Some(heze_spec()), + } +} + +/// Proposer boost on a descendant can flip an ancestor's canonical payload status. +/// Boost supports the ancestor's Full variant (via the descendant's Full parent +/// payload status) but not Empty, so a large enough boost overrides raw Empty weight. +pub fn get_heze_proposer_boost_flips_ancestor_test_definition() -> ForkChoiceTestDefinition { + let mut ops = vec![]; + + // Block 1 at slot 1 with payload received. + ops.push(Operation::ProcessBlock { + slot: Slot::new(1), + root: get_root(1), + parent_root: get_root(0), + justified_checkpoint: get_checkpoint(0), + finalized_checkpoint: get_checkpoint(0), + execution_payload_parent_hash: Some(get_hash(0)), + execution_payload_block_hash: Some(get_hash(1)), + }); + ops.push(Operation::ProcessExecutionPayloadEnvelope { + block_root: get_root(1), + }); + + // Block 2 at slot 3 with a Full parent payload status (skip slot 2 so + // block 1's previous-slot zero-out doesn't fire at current_slot 3). + ops.push(Operation::ProcessBlock { + slot: Slot::new(3), + root: get_root(2), + parent_root: get_root(1), + justified_checkpoint: get_checkpoint(0), + finalized_checkpoint: get_checkpoint(0), + execution_payload_parent_hash: Some(get_hash(1)), + execution_payload_block_hash: Some(get_hash(2)), + }); + + // One Empty vote on block 1. Balance totals are chosen so the proposer + // boost score exceeds the single Empty voter's balance. + ops.push(Operation::ProcessGloasAttestation { + validator_index: 0, + block_root: get_root(1), + attestation_slot: Slot::new(2), + payload_present: false, + }); + + ops.push(Operation::FindHead { + justified_checkpoint: get_checkpoint(0), + finalized_checkpoint: get_checkpoint(0), + justified_state_balances: vec![100, 10000], + expected_head: get_root(1), + current_slot: Slot::new(3), + expected_payload_status: Some(PayloadStatus::Empty), + }); + + // Without boost the raw weights decide and Empty wins. + ops.push(Operation::AssertPayloadStatusByWeight { + block_root: get_root(1), + expected_status: PayloadStatus::Empty, + current_slot: Some(Slot::new(3)), + proposer_boost_root: None, + }); + + // With boost on block 2 the boost supports block 1's Full variant, so Full wins. + ops.push(Operation::AssertPayloadStatusByWeight { + block_root: get_root(1), + expected_status: PayloadStatus::Full, + current_slot: Some(Slot::new(3)), + proposer_boost_root: Some(get_root(2)), + }); + + ForkChoiceTestDefinition { + finalized_block_slot: Slot::new(0), + justified_checkpoint: get_checkpoint(0), + finalized_checkpoint: get_checkpoint(0), + operations: ops, + execution_payload_parent_hash: Some(ExecutionBlockHash::zero()), + execution_payload_block_hash: Some(ExecutionBlockHash::zero()), + spec: Some(heze_spec()), + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn heze_fork_boundary_spec() -> ChainSpec { + let mut spec = MainnetEthSpec::default_spec(); + spec.proposer_score_boost = Some(50); + spec.heze_fork_epoch = Some(Epoch::new(1)); + spec + } + + /// Heze fork boundary: a chain starting pre-Heze (V17 nodes) that crosses into + /// Heze (V29 nodes). The head should advance through the fork boundary. + /// + /// Parameters: + /// - `skip_first_heze_slot`: if true, there is no block at the first Heze slot (slot 32); + /// the first V29 block appears at slot 33. + /// - `first_heze_block_full`: if true, the first V29 block extends the parent V17 node's + /// EL chain (Full parent payload status). If false, it doesn't (Empty). + fn get_heze_fork_boundary_test_definition( + skip_first_heze_slot: bool, + first_heze_block_full: bool, + ) -> ForkChoiceTestDefinition { + let mut ops = vec![]; + + // Block at slot 31 — last pre-Heze slot. Created as a V17 node because + // heze_fork_epoch = 1 means Heze starts at slot 32. + // + // The test harness sets execution_status = Optimistic(ExecutionBlockHash::from_root(root)), + // so this V17 node's EL block hash = ExecutionBlockHash::from_root(get_root(1)). + ops.push(Operation::ProcessBlock { + slot: Slot::new(31), + root: get_root(1), + parent_root: get_root(0), + justified_checkpoint: get_checkpoint(0), + finalized_checkpoint: get_checkpoint(0), + execution_payload_parent_hash: None, + execution_payload_block_hash: None, + }); + + // First Heze block (V29 node). + let heze_slot = if skip_first_heze_slot { 33 } else { 32 }; + + // The first Heze block should always have the pre-Heze block as its execution parent, + // although this is currently not checked anywhere (the spec doesn't mention this). + ops.push(Operation::ProcessBlock { + slot: Slot::new(heze_slot), + root: get_root(2), + parent_root: get_root(1), + justified_checkpoint: get_checkpoint(0), + finalized_checkpoint: get_checkpoint(0), + execution_payload_parent_hash: Some(get_hash(1)), + execution_payload_block_hash: Some(get_hash(2)), + }); + + // Parent payload status of fork boundary block should always be Empty. + let expected_parent_status = PayloadStatus::Empty; + ops.push(Operation::AssertParentPayloadStatus { + block_root: get_root(2), + expected_status: expected_parent_status, + }); + + // Mark root 2's execution payload as received so the Full virtual child exists. + if first_heze_block_full { + ops.push(Operation::ProcessExecutionPayloadEnvelope { + block_root: get_root(2), + }); + } + + // Extend the chain with another V29 block (Full child of root 2). + ops.push(Operation::ProcessBlock { + slot: Slot::new(heze_slot + 1), + root: get_root(3), + parent_root: get_root(2), + justified_checkpoint: get_checkpoint(0), + finalized_checkpoint: get_checkpoint(0), + execution_payload_parent_hash: if first_heze_block_full { + Some(get_hash(2)) + } else { + Some(get_hash(1)) + }, + execution_payload_block_hash: Some(get_hash(3)), + }); + + // Head should advance to the tip of the chain through the fork boundary. + ops.push(Operation::FindHead { + justified_checkpoint: get_checkpoint(0), + finalized_checkpoint: get_checkpoint(0), + justified_state_balances: vec![1], + expected_head: get_root(3), + current_slot: Slot::new(heze_slot + 1), + expected_payload_status: None, + }); + + ops.push(Operation::AssertParentPayloadStatus { + block_root: get_root(3), + expected_status: if first_heze_block_full { + PayloadStatus::Full + } else { + PayloadStatus::Empty + }, + }); + + ForkChoiceTestDefinition { + finalized_block_slot: Slot::new(0), + justified_checkpoint: get_checkpoint(0), + finalized_checkpoint: get_checkpoint(0), + operations: ops, + // Genesis is V17 (slot 0 < Heze fork slot 32), these are unused for V17. + execution_payload_parent_hash: None, + execution_payload_block_hash: None, + spec: Some(heze_fork_boundary_spec()), + } + } + + #[test] + fn fork_boundary_no_skip_full() { + get_heze_fork_boundary_test_definition(false, true).run(); + } + + #[test] + fn fork_boundary_no_skip_empty() { + get_heze_fork_boundary_test_definition(false, false).run(); + } + + #[test] + fn fork_boundary_skip_first_heze_slot_full() { + get_heze_fork_boundary_test_definition(true, true).run(); + } + + #[test] + fn fork_boundary_skip_first_heze_slot_empty() { + get_heze_fork_boundary_test_definition(true, false).run(); + } + + #[test] + fn chain_following() { + let test = get_heze_chain_following_test_definition(); + test.run(); + } + + #[test] + fn payload_probe() { + let test = get_heze_payload_probe_test_definition(); + test.run(); + } + + #[test] + fn find_head_vote_transition() { + let test = get_heze_find_head_vote_transition_test_definition(); + test.run(); + } + + #[test] + fn weight_priority_over_payload_preference() { + let test = get_heze_weight_priority_over_payload_preference_test_definition(); + test.run(); + } + + #[test] + fn parent_empty_when_child_points_to_grandparent() { + let test = get_heze_parent_empty_when_child_points_to_grandparent_test_definition(); + test.run(); + } + + #[test] + fn interleaved_attestations() { + let test = get_heze_interleaved_attestations_test_definition(); + test.run(); + } + + #[test] + fn payload_received_interleaving() { + let test = get_heze_payload_received_interleaving_test_definition(); + test.run(); + } + + #[test] + fn previous_slot_tiebreaker() { + let test = get_heze_previous_slot_tiebreaker_test_definition(); + test.run(); + } + + #[test] + fn proposer_boost_flips_ancestor() { + let test = get_heze_proposer_boost_flips_ancestor_test_definition(); + test.run(); + } + + /// Test that execution payload invalidation propagates across the V17→V29 fork + /// boundary: after invalidating a V17 parent, head must not select any descendant. + /// + /// genesis(V17) -> block_1(V17, slot 31) -> block_2(V29, slot 32) + #[test] + fn mixed_v17_v29_invalidation() { + let balances = vec![1]; + let mut ops = vec![]; + + // V17 block at slot 31 (pre-Heze). + ops.push(Operation::ProcessBlock { + slot: Slot::new(31), + root: get_root(1), + parent_root: get_root(0), + justified_checkpoint: get_checkpoint(0), + finalized_checkpoint: get_checkpoint(0), + execution_payload_parent_hash: None, + execution_payload_block_hash: None, + }); + + // V29 block at slot 32 (first Heze slot), child of block 1. + ops.push(Operation::ProcessBlock { + slot: Slot::new(32), + root: get_root(2), + parent_root: get_root(1), + justified_checkpoint: get_checkpoint(0), + finalized_checkpoint: get_checkpoint(0), + execution_payload_parent_hash: Some(get_hash(1)), + execution_payload_block_hash: Some(get_hash(2)), + }); + + // Vote for block 2 (V29) so both blocks have weight. + ops.push(Operation::ProcessAttestation { + validator_index: 0, + block_root: get_root(2), + attestation_slot: Slot::new(32), + }); + + // FindHead triggers apply_score_changes which materializes the vote. + ops.push(Operation::FindHead { + justified_checkpoint: get_checkpoint(0), + finalized_checkpoint: get_checkpoint(0), + justified_state_balances: balances.clone(), + expected_head: get_root(2), + current_slot: Slot::new(32), + expected_payload_status: None, + }); + + // Invalidate block 1 (V17). filter_block_tree excludes the entire branch. + ops.push(Operation::InvalidatePayload { + head_block_root: get_root(1), + latest_valid_ancestor_root: Some(get_hash(0)), + }); + + // Head falls back to genesis — the invalid branch is no longer selectable. + ops.push(Operation::FindHead { + justified_checkpoint: get_checkpoint(0), + finalized_checkpoint: get_checkpoint(0), + justified_state_balances: balances.clone(), + expected_head: get_root(0), + current_slot: Slot::new(32), + expected_payload_status: None, + }); + + ForkChoiceTestDefinition { + finalized_block_slot: Slot::new(0), + justified_checkpoint: get_checkpoint(0), + finalized_checkpoint: get_checkpoint(0), + operations: ops, + execution_payload_parent_hash: None, + execution_payload_block_hash: None, + spec: Some(heze_fork_boundary_spec()), + } + .run(); + } +} diff --git a/consensus/state_processing/src/common/get_attestation_participation.rs b/consensus/state_processing/src/common/get_attestation_participation.rs index 2262b59ac1..8f1f000f40 100644 --- a/consensus/state_processing/src/common/get_attestation_participation.rs +++ b/consensus/state_processing/src/common/get_attestation_participation.rs @@ -17,7 +17,7 @@ use types::{ /// This function will return an error if the source of the attestation doesn't match the /// state's relevant justified checkpoint. /// -/// This function has been abstracted to work for all forks from Altair to Gloas. +/// This function has been abstracted to work for all forks from Altair to Heze. pub fn get_attestation_participation_flag_indices( state: &BeaconState, data: &AttestationData, diff --git a/consensus/state_processing/src/genesis.rs b/consensus/state_processing/src/genesis.rs index 9dfbc87b48..16c59efc6e 100644 --- a/consensus/state_processing/src/genesis.rs +++ b/consensus/state_processing/src/genesis.rs @@ -5,7 +5,7 @@ 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_fulu, - upgrade_to_gloas, + upgrade_to_gloas, upgrade_to_heze, }; use fixed_bytes::FixedBytesExtended; use safe_arith::{ArithError, SafeArith}; @@ -184,6 +184,17 @@ pub fn initialize_beacon_state_from_eth1( state.latest_block_header_mut().body_root = block.body_root(); } + // Upgrade to heze if configured from genesis. + if spec + .heze_fork_epoch + .is_some_and(|fork_epoch| fork_epoch == E::genesis_epoch()) + { + upgrade_to_heze(&mut state, spec)?; + + // Remove intermediate Gloas fork from `state.fork`. + state.fork_mut().previous_version = spec.heze_fork_version; + } + // Now that we have our validators, initialize the caches (including the committees) state.build_caches(spec)?; @@ -195,7 +206,7 @@ pub fn initialize_beacon_state_from_eth1( /// Create an unsigned genesis `BeaconBlock` whose body matches the genesis state. /// -/// For Gloas, the block's `signed_execution_payload_bid` is populated from the state's +/// For Gloas and later, the block's `signed_execution_payload_bid` is populated from the state's /// `latest_execution_payload_bid` so that the body root is consistent with /// `state.latest_block_header.body_root`. /// @@ -211,6 +222,11 @@ pub fn genesis_block( let bid = &mut block.body.signed_execution_payload_bid.message; bid.block_hash = state_bid.block_hash; bid.execution_requests_root = state_bid.execution_requests_root; + } else if let Ok(block) = block.as_heze_mut() { + let state_bid = genesis_state.latest_execution_payload_bid()?; + let bid = &mut block.body.signed_execution_payload_bid.message; + bid.block_hash = state_bid.block_hash; + bid.execution_requests_root = state_bid.execution_requests_root; } Ok(block) } diff --git a/consensus/state_processing/src/per_slot_processing.rs b/consensus/state_processing/src/per_slot_processing.rs index f26ea567a2..eee6d9f0e4 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_fulu, upgrade_to_gloas, + upgrade_to_electra, upgrade_to_fulu, upgrade_to_gloas, upgrade_to_heze, }; use crate::{per_epoch_processing::EpochProcessingSummary, *}; use fixed_bytes::FixedBytesExtended; @@ -103,6 +103,11 @@ pub fn per_slot_processing( upgrade_to_gloas(state, spec)?; } + // Heze. + if spec.heze_fork_epoch == Some(state.current_epoch()) { + upgrade_to_heze(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 d175c3ae40..03c9dad5bc 100644 --- a/consensus/state_processing/src/upgrade.rs +++ b/consensus/state_processing/src/upgrade.rs @@ -5,6 +5,7 @@ pub mod deneb; pub mod electra; pub mod fulu; pub mod gloas; +pub mod heze; pub use altair::upgrade_to_altair; pub use bellatrix::upgrade_to_bellatrix; @@ -13,3 +14,4 @@ pub use deneb::upgrade_to_deneb; pub use electra::upgrade_to_electra; pub use fulu::upgrade_to_fulu; pub use gloas::upgrade_to_gloas; +pub use heze::upgrade_to_heze; diff --git a/consensus/state_processing/src/upgrade/heze.rs b/consensus/state_processing/src/upgrade/heze.rs new file mode 100644 index 0000000000..bb5d41791b --- /dev/null +++ b/consensus/state_processing/src/upgrade/heze.rs @@ -0,0 +1,105 @@ +use std::mem; +use types::{ + BeaconState, BeaconStateError as Error, BeaconStateHeze, ChainSpec, EthSpec, Fork, +}; + +/// Transform a `Gloas` state into a `Heze` state. +pub fn upgrade_to_heze( + pre_state: &mut BeaconState, + spec: &ChainSpec, +) -> Result<(), Error> { + let post = upgrade_state_to_heze(pre_state, spec)?; + + *pre_state = post; + + Ok(()) +} + +pub fn upgrade_state_to_heze( + pre_state: &mut BeaconState, + spec: &ChainSpec, +) -> Result, Error> { + let epoch = pre_state.current_epoch(); + let pre = pre_state.as_gloas_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::Heze(BeaconStateHeze { + // 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.heze_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 Bid + latest_execution_payload_bid: pre.latest_execution_payload_bid.clone(), + // 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(), + proposer_lookahead: mem::take(&mut pre.proposer_lookahead), + // Gloas + builders: mem::take(&mut pre.builders), + next_withdrawal_builder_index: pre.next_withdrawal_builder_index, + execution_payload_availability: pre.execution_payload_availability.clone(), + builder_pending_payments: pre.builder_pending_payments.clone(), + builder_pending_withdrawals: mem::take(&mut pre.builder_pending_withdrawals), + latest_block_hash: pre.latest_block_hash, + payload_expected_withdrawals: mem::take(&mut pre.payload_expected_withdrawals), + ptc_window: pre.ptc_window.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/src/block/beacon_block.rs b/consensus/types/src/block/beacon_block.rs index 3360728eaa..3f34d17107 100644 --- a/consensus/types/src/block/beacon_block.rs +++ b/consensus/types/src/block/beacon_block.rs @@ -20,7 +20,8 @@ use crate::{ block::{ BeaconBlockBodyAltair, BeaconBlockBodyBase, BeaconBlockBodyBellatrix, BeaconBlockBodyCapella, BeaconBlockBodyDeneb, BeaconBlockBodyElectra, BeaconBlockBodyFulu, - BeaconBlockBodyGloas, BeaconBlockBodyRef, BeaconBlockBodyRefMut, BeaconBlockHeader, + BeaconBlockBodyGloas, BeaconBlockBodyHeze, BeaconBlockBodyRef, BeaconBlockBodyRefMut, + BeaconBlockHeader, SignedBeaconBlock, SignedBeaconBlockHeader, }, core::{ChainSpec, Domain, Epoch, EthSpec, Graffiti, Hash256, SignedRoot, Slot}, @@ -39,7 +40,7 @@ use crate::{ /// A block of the `BeaconChain`. #[superstruct( - variants(Base, Altair, Bellatrix, Capella, Deneb, Electra, Fulu, Gloas), + variants(Base, Altair, Bellatrix, Capella, Deneb, Electra, Fulu, Gloas, Heze), variant_attributes( derive( Debug, @@ -107,6 +108,8 @@ pub struct BeaconBlock = FullPayload pub body: BeaconBlockBodyFulu, #[superstruct(only(Gloas), partial_getter(rename = "body_gloas"))] pub body: BeaconBlockBodyGloas, + #[superstruct(only(Heze), partial_getter(rename = "body_heze"))] + pub body: BeaconBlockBodyHeze, } pub type BlindedBeaconBlock = BeaconBlock>; @@ -159,8 +162,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 { - BeaconBlockGloas::from_ssz_bytes(bytes) - .map(BeaconBlock::Gloas) + BeaconBlockHeze::from_ssz_bytes(bytes) + .map(BeaconBlock::Heze) + .or_else(|_| BeaconBlockGloas::from_ssz_bytes(bytes).map(BeaconBlock::Gloas)) .or_else(|_| 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)) @@ -262,6 +266,7 @@ impl<'a, E: EthSpec, Payload: AbstractExecPayload> BeaconBlockRef<'a, E, Payl BeaconBlockRef::Electra { .. } => ForkName::Electra, BeaconBlockRef::Fulu { .. } => ForkName::Fulu, BeaconBlockRef::Gloas { .. } => ForkName::Gloas, + BeaconBlockRef::Heze { .. } => ForkName::Heze, } } @@ -327,6 +332,14 @@ impl<'a, E: EthSpec, Payload: AbstractExecPayload> BeaconBlockRef<'a, E, Payl .blob_kzg_commitments .len(), ), + BeaconBlockRef::Heze(block) => Some( + block + .body + .signed_execution_payload_bid + .message + .blob_kzg_commitments + .len(), + ), } } } @@ -725,6 +738,38 @@ impl> EmptyBlock for BeaconBlockGloa } } +impl> EmptyBlock for BeaconBlockHeze { + /// Returns an empty Heze block to be used during genesis. + fn empty(spec: &ChainSpec) -> Self { + BeaconBlockHeze { + slot: spec.genesis_slot, + proposer_index: 0, + parent_root: Hash256::zero(), + state_root: Hash256::zero(), + body: BeaconBlockBodyHeze { + 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(), + bls_to_execution_changes: VariableList::empty(), + parent_execution_requests: ExecutionRequests::default(), + signed_execution_payload_bid: SignedExecutionPayloadBid::empty(), + payload_attestations: VariableList::empty(), + _phantom: PhantomData, + }, + } + } +} + // TODO(EIP-7732) Mark's branch had the following implementation but not sure if it's needed so will just add header below for reference // impl> BeaconBlockEIP7732 { @@ -751,6 +796,29 @@ impl From>> } } +// TODO(EIP-7732) Look into whether we can remove this in the future since no blinded blocks post-gloas +impl From>> + for BeaconBlockHeze> +{ + fn from(block: BeaconBlockHeze>) -> Self { + let BeaconBlockHeze { + slot, + proposer_index, + parent_root, + state_root, + body, + } = block; + + BeaconBlockHeze { + slot, + proposer_index, + parent_root, + state_root, + body: body.into(), + } + } +} + // We can convert pre-Bellatrix blocks without payloads into blocks "with" payloads. impl From>> for BeaconBlockBase> @@ -834,6 +902,7 @@ impl_from!(BeaconBlockDeneb, >, >, |body: impl_from!(BeaconBlockElectra, >, >, |body: BeaconBlockBodyElectra<_, _>| body.into()); impl_from!(BeaconBlockFulu, >, >, |body: BeaconBlockBodyFulu<_, _>| body.into()); impl_from!(BeaconBlockGloas, >, >, |body: BeaconBlockBodyGloas<_, _>| body.into()); +impl_from!(BeaconBlockHeze, >, >, |body: BeaconBlockBodyHeze<_, _>| body.into()); // We can clone blocks with payloads to blocks without payloads, without cloning the payload. macro_rules! impl_clone_as_blinded { @@ -869,6 +938,7 @@ impl_clone_as_blinded!(BeaconBlockDeneb, >, >, >); impl_clone_as_blinded!(BeaconBlockFulu, >, >); impl_clone_as_blinded!(BeaconBlockGloas, >, >); +impl_clone_as_blinded!(BeaconBlockHeze, >, >); // A reference to a full beacon block can be cloned into a blinded beacon block, without cloning the // execution payload. @@ -1061,6 +1131,26 @@ mod tests { }); } + #[test] + fn roundtrip_heze_block() { + let rng = &mut XorShiftRng::from_seed([42; 16]); + let spec = &ForkName::Heze.make_genesis_spec(MainnetEthSpec::default_spec()); + + let inner_block = BeaconBlockHeze { + 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: BeaconBlockBodyHeze::random_for_test(rng), + }; + + let block = BeaconBlock::Heze(inner_block.clone()); + + test_ssz_tree_hash_pair_with(&block, &inner_block, |bytes| { + BeaconBlock::from_ssz_bytes(bytes, spec) + }); + } + #[test] fn roundtrip_gloas_block() { let rng = &mut XorShiftRng::from_seed([42; 16]); @@ -1104,6 +1194,8 @@ mod tests { let fulu_slot = fulu_epoch.start_slot(E::slots_per_epoch()); let gloas_epoch = fulu_epoch + 1; let gloas_slot = gloas_epoch.start_slot(E::slots_per_epoch()); + let heze_epoch = gloas_epoch + 1; + let heze_slot = heze_epoch.start_slot(E::slots_per_epoch()); spec.altair_fork_epoch = Some(altair_epoch); spec.capella_fork_epoch = Some(capella_epoch); @@ -1111,6 +1203,7 @@ mod tests { spec.electra_fork_epoch = Some(electra_epoch); spec.fulu_fork_epoch = Some(fulu_epoch); spec.gloas_fork_epoch = Some(gloas_epoch); + spec.heze_fork_epoch = Some(heze_epoch); // BeaconBlockBase { @@ -1260,5 +1353,29 @@ mod tests { //BeaconBlock::from_ssz_bytes(&bad_block.as_ssz_bytes(), &spec) // .expect_err("bad gloas block cannot be decoded"); } + + // BeaconBlockHeze + { + let good_block = BeaconBlock::Heze(BeaconBlockHeze { + slot: heze_slot, + ..<_>::random_for_test(rng) + }); + let _bad_block = { + let mut bad = good_block.clone(); + *bad.slot_mut() = gloas_slot; + bad + }; + + assert_eq!( + BeaconBlock::from_ssz_bytes(&good_block.as_ssz_bytes(), &spec) + .expect("good heze block can be decoded"), + good_block + ); + + // TODO(heze): Uncomment once Heze has features since without features + // and with a Gloas slot it decodes successfully to Gloas. + //BeaconBlock::from_ssz_bytes(&bad_block.as_ssz_bytes(), &spec) + // .expect_err("bad heze block cannot be decoded"); + } } } diff --git a/consensus/types/src/block/beacon_block_body.rs b/consensus/types/src/block/beacon_block_body.rs index 25695dbdda..001ad132fa 100644 --- a/consensus/types/src/block/beacon_block_body.rs +++ b/consensus/types/src/block/beacon_block_body.rs @@ -55,7 +55,7 @@ pub const BLOB_KZG_COMMITMENTS_INDEX: usize = 11; /// /// This *superstruct* abstracts over the hard-fork. #[superstruct( - variants(Base, Altair, Bellatrix, Capella, Deneb, Electra, Fulu, Gloas), + variants(Base, Altair, Bellatrix, Capella, Deneb, Electra, Fulu, Gloas, Heze), variant_attributes( derive( Debug, @@ -89,6 +89,7 @@ pub const BLOB_KZG_COMMITMENTS_INDEX: usize = 11; Electra(metastruct(mappings(beacon_block_body_electra_fields(groups(fields))))), Fulu(metastruct(mappings(beacon_block_body_fulu_fields(groups(fields))))), Gloas(metastruct(mappings(beacon_block_body_gloas_fields(groups(fields))))), + Heze(metastruct(mappings(beacon_block_body_heze_fields(groups(fields))))), ), cast_error( ty = "BeaconStateError", @@ -120,7 +121,7 @@ pub struct BeaconBlockBody = FullPay )] pub attester_slashings: VariableList, E::MaxAttesterSlashings>, #[superstruct( - only(Electra, Fulu, Gloas), + only(Electra, Fulu, Gloas, Heze), partial_getter(rename = "attester_slashings_electra") )] pub attester_slashings: @@ -131,13 +132,13 @@ pub struct BeaconBlockBody = FullPay )] pub attestations: VariableList, E::MaxAttestations>, #[superstruct( - only(Electra, Fulu, Gloas), + only(Electra, Fulu, Gloas, Heze), partial_getter(rename = "attestations_electra") )] pub attestations: VariableList, E::MaxAttestationsElectra>, pub deposits: VariableList, pub voluntary_exits: VariableList, - #[superstruct(only(Altair, Bellatrix, Capella, Deneb, Electra, Fulu, Gloas))] + #[superstruct(only(Altair, Bellatrix, Capella, Deneb, Electra, Fulu, Gloas, Heze))] 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 @@ -160,20 +161,20 @@ pub struct BeaconBlockBody = FullPay #[superstruct(only(Fulu), partial_getter(rename = "execution_payload_fulu"))] #[serde(flatten)] pub execution_payload: Payload::Fulu, - #[superstruct(only(Capella, Deneb, Electra, Fulu, Gloas))] + #[superstruct(only(Capella, Deneb, Electra, Fulu, Gloas, Heze))] pub bls_to_execution_changes: VariableList, #[superstruct(only(Deneb, Electra, Fulu))] pub blob_kzg_commitments: KzgCommitments, #[superstruct(only(Electra, Fulu))] pub execution_requests: ExecutionRequests, - #[superstruct(only(Gloas))] + #[superstruct(only(Gloas, Heze))] pub signed_execution_payload_bid: SignedExecutionPayloadBid, - #[superstruct(only(Gloas))] + #[superstruct(only(Gloas, Heze))] pub payload_attestations: VariableList, E::MaxPayloadAttestations>, - #[superstruct(only(Gloas))] + #[superstruct(only(Gloas, Heze))] pub parent_execution_requests: ExecutionRequests, - #[superstruct(only(Base, Altair, Gloas))] + #[superstruct(only(Base, Altair, Gloas, Heze))] #[metastruct(exclude_from(fields))] #[ssz(skip_serializing, skip_deserializing)] #[tree_hash(skip_hashing)] @@ -203,6 +204,7 @@ impl<'a, E: EthSpec, Payload: AbstractExecPayload> BeaconBlockBodyRef<'a, E, Self::Electra(body) => Ok(Payload::Ref::from(&body.execution_payload)), Self::Fulu(body) => Ok(Payload::Ref::from(&body.execution_payload)), Self::Gloas(_) => Err(BeaconStateError::IncorrectStateVariant), + Self::Heze(_) => Err(BeaconStateError::IncorrectStateVariant), } } @@ -241,6 +243,10 @@ impl<'a, E: EthSpec, Payload: AbstractExecPayload> BeaconBlockBodyRef<'a, E, beacon_block_body_gloas_fields!(body, |_, field| leaves .push(field.tree_hash_root())); } + Self::Heze(body) => { + beacon_block_body_heze_fields!(body, |_, field| leaves + .push(field.tree_hash_root())); + } } leaves } @@ -271,7 +277,8 @@ impl<'a, E: EthSpec, Payload: AbstractExecPayload> BeaconBlockBodyRef<'a, E, | Self::Altair(_) | Self::Bellatrix(_) | Self::Capella(_) - | Self::Gloas(_) => Err(BeaconStateError::IncorrectStateVariant), + | Self::Gloas(_) + | Self::Heze(_) => Err(BeaconStateError::IncorrectStateVariant), Self::Deneb(_) | Self::Electra(_) | Self::Fulu(_) => { complete_kzg_commitment_merkle_proof::( self.blob_kzg_commitments()?, @@ -356,6 +363,7 @@ impl<'a, E: EthSpec, Payload: AbstractExecPayload> BeaconBlockBodyRef<'a, E, Self::Electra(body) => Box::new(body.attestations.iter().map(AttestationRef::Electra)), Self::Fulu(body) => Box::new(body.attestations.iter().map(AttestationRef::Electra)), Self::Gloas(body) => Box::new(body.attestations.iter().map(AttestationRef::Electra)), + Self::Heze(body) => Box::new(body.attestations.iter().map(AttestationRef::Electra)), } } @@ -401,6 +409,11 @@ impl<'a, E: EthSpec, Payload: AbstractExecPayload> BeaconBlockBodyRef<'a, E, .iter() .map(AttesterSlashingRef::Electra), ), + Self::Heze(body) => Box::new( + body.attester_slashings + .iter() + .map(AttesterSlashingRef::Electra), + ), } } } @@ -432,6 +445,9 @@ impl<'a, E: EthSpec, Payload: AbstractExecPayload> BeaconBlockBodyRefMut<'a, Self::Gloas(body) => { Box::new(body.attestations.iter_mut().map(AttestationRefMut::Electra)) } + Self::Heze(body) => { + Box::new(body.attestations.iter_mut().map(AttestationRefMut::Electra)) + } } } } @@ -448,6 +464,7 @@ impl> BeaconBlockBodyRef<'_, E, Payl BeaconBlockBodyRef::Electra { .. } => ForkName::Electra, BeaconBlockBodyRef::Fulu { .. } => ForkName::Fulu, BeaconBlockBodyRef::Gloas { .. } => ForkName::Gloas, + BeaconBlockBodyRef::Heze { .. } => ForkName::Heze, } } } @@ -557,6 +574,48 @@ impl From>> } } +// Post-Fulu block bodies without payloads can be converted into block bodies with payloads +// TODO(EIP-7732) Look into whether we can remove this in the future since no blinded blocks post-gloas +impl From>> + for BeaconBlockBodyHeze> +{ + fn from(body: BeaconBlockBodyHeze>) -> Self { + let BeaconBlockBodyHeze { + randao_reveal, + eth1_data, + graffiti, + proposer_slashings, + attester_slashings, + attestations, + deposits, + voluntary_exits, + sync_aggregate, + bls_to_execution_changes, + parent_execution_requests, + signed_execution_payload_bid, + payload_attestations, + _phantom, + } = body; + + BeaconBlockBodyHeze { + randao_reveal, + eth1_data, + graffiti, + proposer_slashings, + attester_slashings, + attestations, + deposits, + voluntary_exits, + sync_aggregate, + bls_to_execution_changes, + parent_execution_requests, + signed_execution_payload_bid, + payload_attestations, + _phantom: PhantomData, + } + } +} + // Likewise bodies with payloads can be transformed into bodies without. impl From>> for ( @@ -896,6 +955,52 @@ impl From>> } } +impl From>> + for ( + BeaconBlockBodyHeze>, + Option>, + ) +{ + fn from(body: BeaconBlockBodyHeze>) -> Self { + let BeaconBlockBodyHeze { + randao_reveal, + eth1_data, + graffiti, + proposer_slashings, + attester_slashings, + attestations, + deposits, + voluntary_exits, + sync_aggregate, + bls_to_execution_changes, + parent_execution_requests, + signed_execution_payload_bid, + payload_attestations, + _phantom, + } = body; + + ( + BeaconBlockBodyHeze { + randao_reveal, + eth1_data, + graffiti, + proposer_slashings, + attester_slashings, + attestations, + deposits, + voluntary_exits, + sync_aggregate, + bls_to_execution_changes, + parent_execution_requests, + signed_execution_payload_bid, + payload_attestations, + _phantom: PhantomData, + }, + None, + ) + } +} + // We can clone a full block into a blinded block, without cloning the payload. impl BeaconBlockBodyBase> { pub fn clone_as_blinded(&self) -> BeaconBlockBodyBase> { @@ -1096,6 +1201,13 @@ impl BeaconBlockBodyGloas> { } } +impl BeaconBlockBodyHeze> { + pub fn clone_as_blinded(&self) -> BeaconBlockBodyHeze> { + let (block_body, _payload) = self.clone().into(); + block_body + } +} + impl From>> for ( BeaconBlockBody>, diff --git a/consensus/types/src/block/mod.rs b/consensus/types/src/block/mod.rs index 81c8ffbd63..2aae1511f4 100644 --- a/consensus/types/src/block/mod.rs +++ b/consensus/types/src/block/mod.rs @@ -6,13 +6,14 @@ mod signed_beacon_block_header; pub use beacon_block::{ BeaconBlock, BeaconBlockAltair, BeaconBlockBase, BeaconBlockBellatrix, BeaconBlockCapella, - BeaconBlockDeneb, BeaconBlockElectra, BeaconBlockFulu, BeaconBlockGloas, BeaconBlockRef, - BeaconBlockRefMut, BlindedBeaconBlock, BlockImportSource, EmptyBlock, + BeaconBlockDeneb, BeaconBlockElectra, BeaconBlockFulu, BeaconBlockGloas, BeaconBlockHeze, + BeaconBlockRef, BeaconBlockRefMut, BlindedBeaconBlock, BlockImportSource, EmptyBlock, }; pub use beacon_block_body::{ BLOB_KZG_COMMITMENTS_INDEX, BeaconBlockBody, BeaconBlockBodyAltair, BeaconBlockBodyBase, BeaconBlockBodyBellatrix, BeaconBlockBodyCapella, BeaconBlockBodyDeneb, BeaconBlockBodyElectra, - BeaconBlockBodyFulu, BeaconBlockBodyGloas, BeaconBlockBodyRef, BeaconBlockBodyRefMut, + BeaconBlockBodyFulu, BeaconBlockBodyGloas, BeaconBlockBodyHeze, BeaconBlockBodyRef, + BeaconBlockBodyRefMut, NUM_BEACON_BLOCK_BODY_HASH_TREE_ROOT_LEAVES, }; pub use beacon_block_header::BeaconBlockHeader; @@ -20,7 +21,8 @@ pub use beacon_block_header::BeaconBlockHeader; pub use signed_beacon_block::{ SignedBeaconBlock, SignedBeaconBlockAltair, SignedBeaconBlockBase, SignedBeaconBlockBellatrix, SignedBeaconBlockCapella, SignedBeaconBlockDeneb, SignedBeaconBlockElectra, - SignedBeaconBlockFulu, SignedBeaconBlockGloas, SignedBeaconBlockHash, SignedBlindedBeaconBlock, + SignedBeaconBlockFulu, SignedBeaconBlockGloas, SignedBeaconBlockHeze, SignedBeaconBlockHash, + SignedBlindedBeaconBlock, ssz_tagged_signed_beacon_block, ssz_tagged_signed_beacon_block_arc, }; pub use signed_beacon_block_header::SignedBeaconBlockHeader; diff --git a/consensus/types/src/block/signed_beacon_block.rs b/consensus/types/src/block/signed_beacon_block.rs index 23b01415c8..55a9c85b00 100644 --- a/consensus/types/src/block/signed_beacon_block.rs +++ b/consensus/types/src/block/signed_beacon_block.rs @@ -19,7 +19,8 @@ use crate::{ BLOB_KZG_COMMITMENTS_INDEX, BeaconBlock, BeaconBlockAltair, BeaconBlockBase, BeaconBlockBellatrix, BeaconBlockBodyBellatrix, BeaconBlockBodyCapella, BeaconBlockBodyDeneb, BeaconBlockBodyElectra, BeaconBlockBodyFulu, BeaconBlockCapella, - BeaconBlockDeneb, BeaconBlockElectra, BeaconBlockFulu, BeaconBlockGloas, BeaconBlockHeader, + BeaconBlockDeneb, BeaconBlockElectra, BeaconBlockFulu, BeaconBlockGloas, BeaconBlockHeze, + BeaconBlockHeader, BeaconBlockRef, BeaconBlockRefMut, SignedBeaconBlockHeader, }, core::{ChainSpec, Domain, Epoch, EthSpec, Hash256, SignedRoot, SigningData, Slot}, @@ -66,7 +67,7 @@ impl From for Hash256 { /// A `BeaconBlock` and a signature from its proposer. #[superstruct( - variants(Base, Altair, Bellatrix, Capella, Deneb, Electra, Fulu, Gloas), + variants(Base, Altair, Bellatrix, Capella, Deneb, Electra, Fulu, Gloas, Heze), variant_attributes( derive( Debug, @@ -119,6 +120,8 @@ pub struct SignedBeaconBlock = FullP pub message: BeaconBlockFulu, #[superstruct(only(Gloas), partial_getter(rename = "message_gloas"))] pub message: BeaconBlockGloas, + #[superstruct(only(Heze), partial_getter(rename = "message_heze"))] + pub message: BeaconBlockHeze, pub signature: Signature, } @@ -208,6 +211,9 @@ impl> SignedBeaconBlock BeaconBlock::Gloas(message) => { SignedBeaconBlock::Gloas(SignedBeaconBlockGloas { message, signature }) } + BeaconBlock::Heze(message) => { + SignedBeaconBlock::Heze(SignedBeaconBlockHeze { message, signature }) + } } } @@ -726,6 +732,19 @@ impl From>> } } +// TODO(EIP-7732) Look into whether we can remove this in the future since no blinded blocks post-gloas +impl From>> + for SignedBeaconBlockHeze> +{ + fn from(signed_block: SignedBeaconBlockHeze>) -> Self { + let SignedBeaconBlockHeze { message, signature } = signed_block; + SignedBeaconBlockHeze { + message: message.into(), + signature, + } + } +} + impl SignedBeaconBlock> { pub fn try_into_full_block( self, @@ -750,6 +769,7 @@ impl SignedBeaconBlock> { SignedBeaconBlock::Fulu(block.into_full_block(payload)) } (SignedBeaconBlock::Gloas(block), _) => SignedBeaconBlock::Gloas(block.into()), + (SignedBeaconBlock::Heze(block), _) => SignedBeaconBlock::Heze(block.into()), // avoid wildcard matching forks so that compiler will // direct us here when a new fork has been added (SignedBeaconBlock::Bellatrix(_), _) => return None, @@ -757,7 +777,7 @@ impl SignedBeaconBlock> { (SignedBeaconBlock::Deneb(_), _) => return None, (SignedBeaconBlock::Electra(_), _) => return None, (SignedBeaconBlock::Fulu(_), _) => return None, - // TODO(EIP-7732) Determine if need a match arm for gloas here + // TODO(EIP-7732) Determine if need a match arm for gloas/heze here }; Some(full_block) } @@ -906,6 +926,9 @@ pub mod ssz_tagged_signed_beacon_block { ForkName::Gloas => Ok(SignedBeaconBlock::Gloas( SignedBeaconBlockGloas::from_ssz_bytes(body)?, )), + ForkName::Heze => Ok(SignedBeaconBlock::Heze( + SignedBeaconBlockHeze::from_ssz_bytes(body)?, + )), } } } @@ -987,6 +1010,7 @@ mod test { chain_spec.electra_fork_epoch = Some(Epoch::new(5)); chain_spec.fulu_fork_epoch = Some(Epoch::new(6)); chain_spec.gloas_fork_epoch = Some(Epoch::new(7)); + chain_spec.heze_fork_epoch = Some(Epoch::new(8)); // check that we have all forks covered assert!(chain_spec.fork_epoch(ForkName::latest()).is_some()); @@ -1028,7 +1052,11 @@ mod test { BeaconBlock::Fulu(BeaconBlockFulu::empty(spec)), sig.clone(), ), - SignedBeaconBlock::from_block(BeaconBlock::Gloas(BeaconBlockGloas::empty(spec)), sig), + SignedBeaconBlock::from_block( + BeaconBlock::Gloas(BeaconBlockGloas::empty(spec)), + sig.clone(), + ), + SignedBeaconBlock::from_block(BeaconBlock::Heze(BeaconBlockHeze::empty(spec)), sig), ]; for block in blocks { diff --git a/consensus/types/src/builder/builder_bid.rs b/consensus/types/src/builder/builder_bid.rs index e706b01283..78bf9eac28 100644 --- a/consensus/types/src/builder/builder_bid.rs +++ b/consensus/types/src/builder/builder_bid.rs @@ -89,7 +89,7 @@ impl ForkVersionDecode for BuilderBid { /// SSZ decode with explicit fork variant. fn from_ssz_bytes_by_fork(bytes: &[u8], fork_name: ForkName) -> Result { let builder_bid = match fork_name { - ForkName::Altair | ForkName::Base | ForkName::Gloas => { + ForkName::Altair | ForkName::Base | ForkName::Gloas | ForkName::Heze => { return Err(ssz::DecodeError::BytesInvalid(format!( "unsupported fork for ExecutionPayloadHeader: {fork_name}", ))); @@ -156,7 +156,7 @@ impl<'de, E: EthSpec> ContextDeserialize<'de, ForkName> for BuilderBid { ForkName::Fulu => { Self::Fulu(Deserialize::deserialize(deserializer).map_err(convert_err)?) } - ForkName::Base | ForkName::Altair | ForkName::Gloas => { + ForkName::Base | ForkName::Altair | ForkName::Gloas | ForkName::Heze => { return Err(serde::de::Error::custom(format!( "BuilderBid failed to deserialize: unsupported fork '{}'", context diff --git a/consensus/types/src/core/chain_spec.rs b/consensus/types/src/core/chain_spec.rs index 516ca2288e..ec624a73e2 100644 --- a/consensus/types/src/core/chain_spec.rs +++ b/consensus/types/src/core/chain_spec.rs @@ -252,6 +252,13 @@ pub struct ChainSpec { pub builder_payment_threshold_denominator: u64, pub min_builder_withdrawability_delay: Epoch, + /* + * Heze hard fork params + */ + pub heze_fork_version: [u8; 4], + /// The Heze fork epoch is optional, with `None` representing "Heze never happens". + pub heze_fork_epoch: Option, + /* * Networking */ @@ -374,6 +381,7 @@ impl ChainSpec { /// Returns the name of the fork which is active at `epoch`. pub fn fork_name_at_epoch(&self, epoch: Epoch) -> ForkName { let forks = [ + (self.heze_fork_epoch, ForkName::Heze), (self.gloas_fork_epoch, ForkName::Gloas), (self.fulu_fork_epoch, ForkName::Fulu), (self.electra_fork_epoch, ForkName::Electra), @@ -406,6 +414,7 @@ impl ChainSpec { ForkName::Electra => self.electra_fork_version, ForkName::Fulu => self.fulu_fork_version, ForkName::Gloas => self.gloas_fork_version, + ForkName::Heze => self.heze_fork_version, } } @@ -425,6 +434,7 @@ impl ChainSpec { ForkName::Electra => self.electra_fork_epoch, ForkName::Fulu => self.fulu_fork_epoch, ForkName::Gloas => self.gloas_fork_epoch, + ForkName::Heze => self.heze_fork_epoch, } } @@ -469,6 +479,12 @@ impl ChainSpec { .is_some_and(|gloas_fork_epoch| gloas_fork_epoch != self.far_future_epoch) } + /// Returns true if `HEZE_FORK_EPOCH` is set and is not set to `FAR_FUTURE_EPOCH`. + pub fn is_heze_scheduled(&self) -> bool { + self.heze_fork_epoch + .is_some_and(|heze_fork_epoch| heze_fork_epoch != self.far_future_epoch) + } + /// Returns a full `Fork` struct for a given epoch. pub fn fork_at_epoch(&self, epoch: Epoch) -> Fork { let current_fork_name = self.fork_name_at_epoch(epoch); @@ -1270,6 +1286,12 @@ impl ChainSpec { min_builder_withdrawability_delay: Epoch::new(64), max_request_payloads: 128, + /* + * Heze hard fork params + */ + heze_fork_version: [0x08, 0x00, 0x00, 0x00], + heze_fork_epoch: None, + /* * Network specific */ @@ -1414,6 +1436,9 @@ impl ChainSpec { gloas_fork_version: [0x07, 0x00, 0x00, 0x01], gloas_fork_epoch: None, min_builder_withdrawability_delay: Epoch::new(2), + // Heze + heze_fork_version: [0x08, 0x00, 0x00, 0x01], + heze_fork_epoch: None, /* * Derived time values (set by `compute_derived_values()`) @@ -1677,6 +1702,12 @@ impl ChainSpec { min_builder_withdrawability_delay: Epoch::new(64), max_request_payloads: 128, + /* + * Heze hard fork params + */ + heze_fork_version: [0x08, 0x00, 0x00, 0x64], + heze_fork_epoch: None, + /* * Network specific */ @@ -1951,6 +1982,14 @@ pub struct Config { #[serde(deserialize_with = "deserialize_fork_epoch")] pub gloas_fork_epoch: Option>, + #[serde(default = "default_heze_fork_version")] + #[serde(with = "serde_utils::bytes_4_hex")] + heze_fork_version: [u8; 4], + #[serde(default)] + #[serde(serialize_with = "serialize_fork_epoch")] + #[serde(deserialize_with = "deserialize_fork_epoch")] + pub heze_fork_epoch: Option>, + #[serde(default)] #[serde(skip_serializing_if = "Option::is_none")] seconds_per_slot: Option>, @@ -2156,6 +2195,11 @@ fn default_gloas_fork_version() -> [u8; 4] { [0xff, 0xff, 0xff, 0xff] } +fn default_heze_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 @@ -2532,6 +2576,11 @@ impl Config { .gloas_fork_epoch .map(|epoch| MaybeQuoted { value: epoch }), + heze_fork_version: spec.heze_fork_version, + heze_fork_epoch: spec + .heze_fork_epoch + .map(|epoch| MaybeQuoted { value: epoch }), + seconds_per_slot: Some(MaybeQuoted { value: spec.seconds_per_slot, }), @@ -2649,6 +2698,8 @@ impl Config { fulu_fork_version, gloas_fork_version, gloas_fork_epoch, + heze_fork_version, + heze_fork_epoch, seconds_per_slot, slot_duration_ms, seconds_per_eth1_block, @@ -2744,6 +2795,8 @@ impl Config { fulu_fork_version, gloas_fork_version, gloas_fork_epoch: gloas_fork_epoch.map(|q| q.value), + heze_fork_version, + heze_fork_epoch: heze_fork_epoch.map(|q| q.value), seconds_per_slot: seconds_per_slot .map(|q| q.value) .or_else(|| slot_duration_ms.and_then(|q| q.value.checked_div(1000)))?, @@ -3709,8 +3762,6 @@ mod yaml_tests { /// list as new forks are added. const UPSTREAM_KEYS_NOT_IN_LIGHTHOUSE: &[&str] = &[ // Forks not yet implemented - "HEZE_FORK_VERSION", - "HEZE_FORK_EPOCH", "EIP7928_FORK_VERSION", "EIP7928_FORK_EPOCH", // Gloas params not yet in Config diff --git a/consensus/types/src/core/config_and_preset.rs b/consensus/types/src/core/config_and_preset.rs index 02f9867fcb..94f002c88c 100644 --- a/consensus/types/src/core/config_and_preset.rs +++ b/consensus/types/src/core/config_and_preset.rs @@ -6,14 +6,14 @@ use superstruct::superstruct; use crate::core::{ AltairPreset, BasePreset, BellatrixPreset, CapellaPreset, ChainSpec, Config, DenebPreset, - ElectraPreset, EthSpec, FuluPreset, GloasPreset, consts, + ElectraPreset, EthSpec, FuluPreset, GloasPreset, HezePreset, consts, }; /// Fusion of a runtime-config with the compile-time preset values. /// /// Mostly useful for the API. #[superstruct( - variants(Deneb, Electra, Fulu, Gloas), + variants(Deneb, Electra, Fulu, Gloas, Heze), variant_attributes(derive(Serialize, Deserialize, Debug, PartialEq, Clone)) )] #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] @@ -32,15 +32,18 @@ pub struct ConfigAndPreset { pub capella_preset: CapellaPreset, #[serde(flatten)] pub deneb_preset: DenebPreset, - #[superstruct(only(Electra, Fulu, Gloas))] + #[superstruct(only(Electra, Fulu, Gloas, Heze))] #[serde(flatten)] pub electra_preset: ElectraPreset, - #[superstruct(only(Fulu, Gloas))] + #[superstruct(only(Fulu, Gloas, Heze))] #[serde(flatten)] pub fulu_preset: FuluPreset, - #[superstruct(only(Gloas))] + #[superstruct(only(Gloas, Heze))] #[serde(flatten)] pub gloas_preset: GloasPreset, + #[superstruct(only(Heze))] + #[serde(flatten)] + pub heze_preset: HezePreset, /// The `extra_fields` map allows us to gracefully decode fields intended for future hard forks. #[serde(flatten)] pub extra_fields: HashMap, @@ -56,7 +59,26 @@ impl ConfigAndPreset { let deneb_preset = DenebPreset::from_chain_spec::(spec); let extra_fields = get_extra_fields(spec); - if spec.is_gloas_scheduled() { + if spec.is_heze_scheduled() { + let electra_preset = ElectraPreset::from_chain_spec::(spec); + let fulu_preset = FuluPreset::from_chain_spec::(spec); + let gloas_preset = GloasPreset::from_chain_spec::(spec); + let heze_preset = HezePreset::from_chain_spec::(spec); + + ConfigAndPreset::Heze(ConfigAndPresetHeze { + config, + base_preset, + altair_preset, + bellatrix_preset, + capella_preset, + deneb_preset, + electra_preset, + fulu_preset, + gloas_preset, + heze_preset, + extra_fields, + }) + } else if spec.is_gloas_scheduled() { let electra_preset = ElectraPreset::from_chain_spec::(spec); let fulu_preset = FuluPreset::from_chain_spec::(spec); let gloas_preset = GloasPreset::from_chain_spec::(spec); @@ -165,8 +187,8 @@ mod test { .open(tmp_file.as_ref()) .expect("error opening file"); let mut mainnet_spec = ChainSpec::mainnet(); - // setting gloas_fork_epoch because we are roundtripping a gloas config - mainnet_spec.gloas_fork_epoch = Some(Epoch::new(42)); + // setting heze_fork_epoch because we are roundtripping a heze config + mainnet_spec.heze_fork_epoch = Some(Epoch::new(42)); let mut yamlconfig = ConfigAndPreset::from_chain_spec::(&mainnet_spec); let (k1, v1) = ("SAMPLE_HARDFORK_KEY1", "123456789"); let (k2, v2) = ("SAMPLE_HARDFORK_KEY2", "987654321"); @@ -184,9 +206,9 @@ mod test { .write(false) .open(tmp_file.as_ref()) .expect("error while opening the file"); - let from: ConfigAndPresetGloas = + let from: ConfigAndPresetHeze = yaml_serde::from_reader(reader).expect("error while deserializing"); - assert_eq!(ConfigAndPreset::Gloas(from), yamlconfig); + assert_eq!(ConfigAndPreset::Heze(from), yamlconfig); } #[test] diff --git a/consensus/types/src/core/mod.rs b/consensus/types/src/core/mod.rs index 4e583fbc67..bbd5300c2a 100644 --- a/consensus/types/src/core/mod.rs +++ b/consensus/types/src/core/mod.rs @@ -22,7 +22,7 @@ pub use application_domain::{APPLICATION_DOMAIN_BUILDER, ApplicationDomain}; pub use chain_spec::{BlobParameters, BlobSchedule, ChainSpec, Config, Domain}; pub use config_and_preset::{ ConfigAndPreset, ConfigAndPresetDeneb, ConfigAndPresetElectra, ConfigAndPresetFulu, - ConfigAndPresetGloas, get_extra_fields, + ConfigAndPresetGloas, ConfigAndPresetHeze, get_extra_fields, }; pub use enr_fork_id::EnrForkId; pub use eth_spec::{EthSpec, EthSpecId, GNOSIS, GnosisEthSpec, MainnetEthSpec, MinimalEthSpec}; @@ -31,7 +31,7 @@ pub use graffiti::{GRAFFITI_BYTES_LEN, Graffiti, GraffitiString}; pub use non_zero_usize::new_non_zero_usize; pub use preset::{ AltairPreset, BasePreset, BellatrixPreset, CapellaPreset, DenebPreset, ElectraPreset, - FuluPreset, GloasPreset, + FuluPreset, GloasPreset, HezePreset, }; pub use relative_epoch::{Error as RelativeEpochError, RelativeEpoch}; pub use signing_data::{SignedRoot, SigningData}; diff --git a/consensus/types/src/core/preset.rs b/consensus/types/src/core/preset.rs index 978fc6f4a1..8a22e722fa 100644 --- a/consensus/types/src/core/preset.rs +++ b/consensus/types/src/core/preset.rs @@ -356,6 +356,16 @@ impl GloasPreset { } } +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +#[serde(rename_all = "UPPERCASE")] +pub struct HezePreset {} + +impl HezePreset { + pub fn from_chain_spec(_spec: &ChainSpec) -> Self { + Self {} + } +} + #[cfg(test)] mod test { use super::*; diff --git a/consensus/types/src/data/data_column_sidecar.rs b/consensus/types/src/data/data_column_sidecar.rs index 109c9472a5..5c2404b1ca 100644 --- a/consensus/types/src/data/data_column_sidecar.rs +++ b/consensus/types/src/data/data_column_sidecar.rs @@ -44,7 +44,7 @@ pub struct DataColumnsByRootIdentifier { pub type DataColumnSidecarList = Vec>>; #[superstruct( - variants(Fulu, Gloas), + variants(Fulu, Gloas, Heze), variant_attributes( derive( Debug, @@ -95,9 +95,9 @@ pub struct DataColumnSidecar { /// An inclusion proof, proving the inclusion of `blob_kzg_commitments` in `BeaconBlockBody`. #[superstruct(only(Fulu))] pub kzg_commitments_inclusion_proof: FixedVector, - #[superstruct(only(Gloas), partial_getter(rename = "slot_gloas"))] + #[superstruct(only(Gloas, Heze), partial_getter(rename = "slot_gloas"))] pub slot: Slot, - #[superstruct(only(Gloas))] + #[superstruct(only(Gloas, Heze))] pub beacon_block_root: Hash256, } @@ -106,6 +106,7 @@ impl DataColumnSidecar { match self { DataColumnSidecar::Fulu(column) => column.slot(), DataColumnSidecar::Gloas(column) => column.slot, + DataColumnSidecar::Heze(column) => column.slot, } } @@ -117,6 +118,7 @@ impl DataColumnSidecar { match self { DataColumnSidecar::Fulu(column) => column.block_root(), DataColumnSidecar::Gloas(column) => column.beacon_block_root, + DataColumnSidecar::Heze(column) => column.beacon_block_root, } } @@ -138,6 +140,9 @@ impl DataColumnSidecar { ForkName::Gloas => Ok(DataColumnSidecar::Gloas( DataColumnSidecarGloas::from_ssz_bytes(bytes)?, )), + ForkName::Heze => Ok(DataColumnSidecar::Heze( + DataColumnSidecarHeze::from_ssz_bytes(bytes)?, + )), } } @@ -310,6 +315,33 @@ impl DataColumnSidecarGloas { } } +impl DataColumnSidecarHeze { + pub fn min_size() -> usize { + // min size is one cell + Self { + index: 0, + column: VariableList::new(vec![Cell::::default()]).unwrap(), + kzg_proofs: VariableList::new(vec![KzgProof::empty()]).unwrap(), + slot: Slot::new(0), + beacon_block_root: Hash256::ZERO, + } + .as_ssz_bytes() + .len() + } + + pub fn max_size(max_blobs_per_block: usize) -> usize { + Self { + index: 0, + column: VariableList::new(vec![Cell::::default(); max_blobs_per_block]).unwrap(), + kzg_proofs: VariableList::new(vec![KzgProof::empty(); max_blobs_per_block]).unwrap(), + slot: Slot::new(0), + beacon_block_root: Hash256::ZERO, + } + .as_ssz_bytes() + .len() + } +} + #[derive(Debug)] pub enum DataColumnSidecarError { ArithError(ArithError), diff --git a/consensus/types/src/data/mod.rs b/consensus/types/src/data/mod.rs index 9c7eb42626..9c9d65295a 100644 --- a/consensus/types/src/data/mod.rs +++ b/consensus/types/src/data/mod.rs @@ -14,7 +14,7 @@ pub use data_column_custody_group::{ }; pub use data_column_sidecar::{ Cell, ColumnIndex, DataColumn, DataColumnSidecar, DataColumnSidecarError, - DataColumnSidecarFulu, DataColumnSidecarGloas, DataColumnSidecarList, + DataColumnSidecarFulu, DataColumnSidecarGloas, DataColumnSidecarHeze, DataColumnSidecarList, DataColumnsByRootIdentifier, }; pub use data_column_subnet_id::{DataColumnSubnetId, all_data_column_sidecar_subnets_from_spec}; diff --git a/consensus/types/src/execution/dumb_macros.rs b/consensus/types/src/execution/dumb_macros.rs index 4eae416bb5..98958da2e4 100644 --- a/consensus/types/src/execution/dumb_macros.rs +++ b/consensus/types/src/execution/dumb_macros.rs @@ -30,6 +30,7 @@ macro_rules! map_execution_payload_into_full_payload { f(inner, FullPayload::Fulu) } ExecutionPayload::Gloas(_) => panic!("FullPayload::Gloas does not exist!"), + ExecutionPayload::Heze(_) => panic!("FullPayload::Heze does not exist!"), } }; } @@ -59,6 +60,7 @@ macro_rules! map_execution_payload_into_blinded_payload { f(inner, BlindedPayload::Fulu) } ExecutionPayload::Gloas(_) => panic!("BlindedPayload::Gloas does not exist!"), + ExecutionPayload::Heze(_) => panic!("BlindedPayload::Heze does not exist!"), } }; } @@ -103,6 +105,7 @@ macro_rules! map_execution_payload_ref_into_execution_payload_header { f(inner, ExecutionPayloadHeader::Fulu) } ExecutionPayloadRef::Gloas(_) => panic!("ExecutionPayloadHeader::Gloas does not exist!"), + ExecutionPayloadRef::Heze(_) => panic!("ExecutionPayloadHeader::Heze does not exist!"), } } } diff --git a/consensus/types/src/execution/execution_payload.rs b/consensus/types/src/execution/execution_payload.rs index c84a46874d..be228485c5 100644 --- a/consensus/types/src/execution/execution_payload.rs +++ b/consensus/types/src/execution/execution_payload.rs @@ -24,7 +24,7 @@ pub type Transactions = VariableList< >; #[superstruct( - variants(Bellatrix, Capella, Deneb, Electra, Fulu, Gloas), + variants(Bellatrix, Capella, Deneb, Electra, Fulu, Gloas, Heze), variant_attributes( derive( Default, @@ -101,19 +101,19 @@ 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, Fulu, Gloas))] + #[superstruct(only(Capella, Deneb, Electra, Fulu, Gloas, Heze))] pub withdrawals: Withdrawals, - #[superstruct(only(Deneb, Electra, Fulu, Gloas), partial_getter(copy))] + #[superstruct(only(Deneb, Electra, Fulu, Gloas, Heze), partial_getter(copy))] #[serde(with = "serde_utils::quoted_u64")] pub blob_gas_used: u64, - #[superstruct(only(Deneb, Electra, Fulu, Gloas), partial_getter(copy))] + #[superstruct(only(Deneb, Electra, Fulu, Gloas, Heze), partial_getter(copy))] #[serde(with = "serde_utils::quoted_u64")] pub excess_blob_gas: u64, /// EIP-7928: Block access list - #[superstruct(only(Gloas))] + #[superstruct(only(Gloas, Heze))] #[serde(with = "ssz_types::serde_utils::hex_var_list")] pub block_access_list: VariableList, - #[superstruct(only(Gloas), partial_getter(copy))] + #[superstruct(only(Gloas, Heze), partial_getter(copy))] pub slot_number: Slot, } @@ -142,6 +142,7 @@ impl ForkVersionDecode for ExecutionPayload { ForkName::Electra => ExecutionPayloadElectra::from_ssz_bytes(bytes).map(Self::Electra), ForkName::Fulu => ExecutionPayloadFulu::from_ssz_bytes(bytes).map(Self::Fulu), ForkName::Gloas => ExecutionPayloadGloas::from_ssz_bytes(bytes).map(Self::Gloas), + ForkName::Heze => ExecutionPayloadHeze::from_ssz_bytes(bytes).map(Self::Heze), } } } @@ -193,6 +194,9 @@ impl<'de, E: EthSpec> ContextDeserialize<'de, ForkName> for ExecutionPayload ForkName::Gloas => { Self::Gloas(Deserialize::deserialize(deserializer).map_err(convert_err)?) } + ForkName::Heze => { + Self::Heze(Deserialize::deserialize(deserializer).map_err(convert_err)?) + } }) } } @@ -206,6 +210,7 @@ impl ExecutionPayload { ExecutionPayload::Electra(_) => ForkName::Electra, ExecutionPayload::Fulu(_) => ForkName::Fulu, ExecutionPayload::Gloas(_) => ForkName::Gloas, + ExecutionPayload::Heze(_) => ForkName::Heze, } } } diff --git a/consensus/types/src/execution/execution_payload_header.rs b/consensus/types/src/execution/execution_payload_header.rs index 0b8556634a..27619a3022 100644 --- a/consensus/types/src/execution/execution_payload_header.rs +++ b/consensus/types/src/execution/execution_payload_header.rs @@ -136,7 +136,7 @@ impl ExecutionPayloadHeader { ExecutionPayloadHeaderElectra::from_ssz_bytes(bytes).map(Self::Electra) } ForkName::Fulu => ExecutionPayloadHeaderFulu::from_ssz_bytes(bytes).map(Self::Fulu), - ForkName::Gloas => Err(ssz::DecodeError::BytesInvalid(format!( + ForkName::Gloas | ForkName::Heze => Err(ssz::DecodeError::BytesInvalid(format!( "unsupported fork for ExecutionPayloadHeader: {fork_name}", ))), } @@ -145,7 +145,10 @@ impl ExecutionPayloadHeader { #[allow(clippy::arithmetic_side_effects)] pub fn ssz_max_var_len_for_fork(fork_name: ForkName) -> usize { // TODO(newfork): Add a new case here if there are new variable fields - if fork_name.gloas_enabled() { + if fork_name.heze_enabled() { + // TODO(Heze): check this + 0 + } else if fork_name.gloas_enabled() { // TODO(EIP7732): check this 0 } else if fork_name.bellatrix_enabled() { @@ -533,7 +536,7 @@ impl<'de, E: EthSpec> ContextDeserialize<'de, ForkName> for ExecutionPayloadHead Self::Fulu(Deserialize::deserialize(deserializer).map_err(convert_err)?) } - ForkName::Base | ForkName::Altair | ForkName::Gloas => { + ForkName::Base | ForkName::Altair | ForkName::Gloas | ForkName::Heze => { return Err(serde::de::Error::custom(format!( "ExecutionPayloadHeader failed to deserialize: unsupported fork '{}'", context diff --git a/consensus/types/src/execution/mod.rs b/consensus/types/src/execution/mod.rs index a3d4ed8730..1806b4b474 100644 --- a/consensus/types/src/execution/mod.rs +++ b/consensus/types/src/execution/mod.rs @@ -18,8 +18,8 @@ pub use eth1_data::Eth1Data; pub use execution_block_header::{EncodableExecutionBlockHeader, ExecutionBlockHeader}; pub use execution_payload::{ ExecutionPayload, ExecutionPayloadBellatrix, ExecutionPayloadCapella, ExecutionPayloadDeneb, - ExecutionPayloadElectra, ExecutionPayloadFulu, ExecutionPayloadGloas, ExecutionPayloadRef, - Transaction, Transactions, + ExecutionPayloadElectra, ExecutionPayloadFulu, ExecutionPayloadGloas, ExecutionPayloadHeze, + ExecutionPayloadRef, Transaction, Transactions, }; pub use execution_payload_bid::ExecutionPayloadBid; pub use execution_payload_envelope::ExecutionPayloadEnvelope; diff --git a/consensus/types/src/execution/payload.rs b/consensus/types/src/execution/payload.rs index c51369034c..71f9f43a88 100644 --- a/consensus/types/src/execution/payload.rs +++ b/consensus/types/src/execution/payload.rs @@ -371,7 +371,7 @@ impl FullPayload { ForkName::Deneb => Ok(FullPayloadDeneb::default().into()), ForkName::Electra => Ok(FullPayloadElectra::default().into()), ForkName::Fulu => Ok(FullPayloadFulu::default().into()), - ForkName::Gloas => Err(BeaconStateError::IncorrectStateVariant), + ForkName::Gloas | ForkName::Heze => Err(BeaconStateError::IncorrectStateVariant), } } } diff --git a/consensus/types/src/fork/fork_macros.rs b/consensus/types/src/fork/fork_macros.rs index 0c7f382ffc..3f1d12f0a1 100644 --- a/consensus/types/src/fork/fork_macros.rs +++ b/consensus/types/src/fork/fork_macros.rs @@ -55,6 +55,10 @@ macro_rules! map_fork_name_with { let (value, extra_data) = $body; ($t::Gloas(value), extra_data) } + $crate::fork::ForkName::Heze => { + let (value, extra_data) = $body; + ($t::Heze(value), extra_data) + } } }; } diff --git a/consensus/types/src/fork/fork_name.rs b/consensus/types/src/fork/fork_name.rs index e9ec5fbe41..3b5c146715 100644 --- a/consensus/types/src/fork/fork_name.rs +++ b/consensus/types/src/fork/fork_name.rs @@ -23,6 +23,7 @@ pub enum ForkName { Electra, Fulu, Gloas, + Heze, } impl ForkName { @@ -36,6 +37,7 @@ impl ForkName { ForkName::Electra, ForkName::Fulu, ForkName::Gloas, + ForkName::Heze, ] } @@ -71,6 +73,7 @@ impl ForkName { spec.electra_fork_epoch = None; spec.fulu_fork_epoch = None; spec.gloas_fork_epoch = None; + spec.heze_fork_epoch = None; spec } ForkName::Altair => { @@ -81,6 +84,7 @@ impl ForkName { spec.electra_fork_epoch = None; spec.fulu_fork_epoch = None; spec.gloas_fork_epoch = None; + spec.heze_fork_epoch = None; spec } ForkName::Bellatrix => { @@ -91,6 +95,7 @@ impl ForkName { spec.electra_fork_epoch = None; spec.fulu_fork_epoch = None; spec.gloas_fork_epoch = None; + spec.heze_fork_epoch = None; spec } ForkName::Capella => { @@ -101,6 +106,7 @@ impl ForkName { spec.electra_fork_epoch = None; spec.fulu_fork_epoch = None; spec.gloas_fork_epoch = None; + spec.heze_fork_epoch = None; spec } ForkName::Deneb => { @@ -111,6 +117,7 @@ impl ForkName { spec.electra_fork_epoch = None; spec.fulu_fork_epoch = None; spec.gloas_fork_epoch = None; + spec.heze_fork_epoch = None; spec } ForkName::Electra => { @@ -121,6 +128,7 @@ impl ForkName { spec.electra_fork_epoch = Some(Epoch::new(0)); spec.fulu_fork_epoch = None; spec.gloas_fork_epoch = None; + spec.heze_fork_epoch = None; spec } ForkName::Fulu => { @@ -131,6 +139,7 @@ impl ForkName { spec.electra_fork_epoch = Some(Epoch::new(0)); spec.fulu_fork_epoch = Some(Epoch::new(0)); spec.gloas_fork_epoch = None; + spec.heze_fork_epoch = None; spec } ForkName::Gloas => { @@ -141,6 +150,18 @@ impl ForkName { spec.electra_fork_epoch = Some(Epoch::new(0)); spec.fulu_fork_epoch = Some(Epoch::new(0)); spec.gloas_fork_epoch = Some(Epoch::new(0)); + spec.heze_fork_epoch = None; + spec + } + ForkName::Heze => { + 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.gloas_fork_epoch = Some(Epoch::new(0)); + spec.heze_fork_epoch = Some(Epoch::new(0)); spec } } @@ -159,6 +180,7 @@ impl ForkName { ForkName::Electra => Some(ForkName::Deneb), ForkName::Fulu => Some(ForkName::Electra), ForkName::Gloas => Some(ForkName::Fulu), + ForkName::Heze => Some(ForkName::Gloas), } } @@ -174,7 +196,8 @@ impl ForkName { ForkName::Deneb => Some(ForkName::Electra), ForkName::Electra => Some(ForkName::Fulu), ForkName::Fulu => Some(ForkName::Gloas), - ForkName::Gloas => None, + ForkName::Gloas => Some(ForkName::Heze), + ForkName::Heze => None, } } @@ -206,6 +229,10 @@ impl ForkName { self >= ForkName::Gloas } + pub fn heze_enabled(self) -> bool { + self >= ForkName::Heze + } + pub fn fork_ascii(self) { if self == ForkName::Fulu { println!( @@ -260,6 +287,7 @@ impl FromStr for ForkName { "electra" => ForkName::Electra, "fulu" => ForkName::Fulu, "gloas" => ForkName::Gloas, + "heze" => ForkName::Heze, _ => return Err(format!("unknown fork name: {}", fork_name)), }) } @@ -276,6 +304,7 @@ impl Display for ForkName { ForkName::Electra => "electra".fmt(f), ForkName::Fulu => "fulu".fmt(f), ForkName::Gloas => "gloas".fmt(f), + ForkName::Heze => "heze".fmt(f), } } } diff --git a/consensus/types/src/light_client/error.rs b/consensus/types/src/light_client/error.rs index 4c7a30db5e..17a499962c 100644 --- a/consensus/types/src/light_client/error.rs +++ b/consensus/types/src/light_client/error.rs @@ -15,6 +15,7 @@ pub enum LightClientError { BeaconBlockBodyError, InconsistentFork, GloasNotImplemented, + HezeNotImplemented, } impl From for LightClientError { diff --git a/consensus/types/src/light_client/light_client_bootstrap.rs b/consensus/types/src/light_client/light_client_bootstrap.rs index fbcc0ef2b0..319b53480a 100644 --- a/consensus/types/src/light_client/light_client_bootstrap.rs +++ b/consensus/types/src/light_client/light_client_bootstrap.rs @@ -118,7 +118,7 @@ impl LightClientBootstrap { ForkName::Electra => Self::Electra(LightClientBootstrapElectra::from_ssz_bytes(bytes)?), ForkName::Fulu => Self::Fulu(LightClientBootstrapFulu::from_ssz_bytes(bytes)?), // TODO(gloas): implement Gloas light client - ForkName::Base | ForkName::Gloas => { + ForkName::Base | ForkName::Gloas | ForkName::Heze => { return Err(ssz::DecodeError::BytesInvalid(format!( "LightClientBootstrap decoding for {fork_name} not implemented" ))); @@ -140,7 +140,7 @@ impl LightClientBootstrap { ForkName::Electra => as Encode>::ssz_fixed_len(), ForkName::Fulu => as Encode>::ssz_fixed_len(), // TODO(gloas): implement Gloas light client - ForkName::Gloas => as Encode>::ssz_fixed_len(), + ForkName::Gloas | ForkName::Heze => as Encode>::ssz_fixed_len(), }; fixed_len + LightClientHeader::::ssz_max_var_len_for_fork(fork_name) } @@ -193,6 +193,7 @@ impl LightClientBootstrap { }), // TODO(gloas): implement Gloas light client ForkName::Gloas => return Err(LightClientError::GloasNotImplemented), + ForkName::Heze => return Err(LightClientError::HezeNotImplemented), }; Ok(light_client_bootstrap) @@ -248,6 +249,7 @@ impl LightClientBootstrap { }), // TODO(gloas): implement Gloas light client ForkName::Gloas => return Err(LightClientError::GloasNotImplemented), + ForkName::Heze => return Err(LightClientError::HezeNotImplemented), }; Ok(light_client_bootstrap) @@ -287,7 +289,7 @@ impl<'de, E: EthSpec> ContextDeserialize<'de, ForkName> for LightClientBootstrap ForkName::Fulu => { Self::Fulu(Deserialize::deserialize(deserializer).map_err(convert_err)?) } - ForkName::Gloas => { + ForkName::Gloas | ForkName::Heze => { // TODO(EIP-7732): check if this is correct return Err(serde::de::Error::custom(format!( "LightClientBootstrap failed to deserialize: unsupported fork '{}'", diff --git a/consensus/types/src/light_client/light_client_finality_update.rs b/consensus/types/src/light_client/light_client_finality_update.rs index b503785b85..9d35b88e21 100644 --- a/consensus/types/src/light_client/light_client_finality_update.rs +++ b/consensus/types/src/light_client/light_client_finality_update.rs @@ -178,6 +178,7 @@ impl LightClientFinalityUpdate { signature_slot, }), ForkName::Gloas => return Err(LightClientError::GloasNotImplemented), + ForkName::Heze => return Err(LightClientError::HezeNotImplemented), ForkName::Base => return Err(LightClientError::AltairForkNotActive), }; @@ -232,7 +233,7 @@ impl LightClientFinalityUpdate { } ForkName::Fulu => Self::Fulu(LightClientFinalityUpdateFulu::from_ssz_bytes(bytes)?), // TODO(gloas): implement Gloas light client - ForkName::Base | ForkName::Gloas => { + ForkName::Base | ForkName::Gloas | ForkName::Heze => { return Err(ssz::DecodeError::BytesInvalid(format!( "LightClientFinalityUpdate decoding for {fork_name} not implemented" ))); @@ -254,7 +255,7 @@ impl LightClientFinalityUpdate { ForkName::Electra => as Encode>::ssz_fixed_len(), ForkName::Fulu => as Encode>::ssz_fixed_len(), // TODO(gloas): implement Gloas light client - ForkName::Gloas => 0, + ForkName::Gloas | ForkName::Heze => 0, }; // `2 *` because there are two headers in the update fixed_size + 2 * LightClientHeader::::ssz_max_var_len_for_fork(fork_name) @@ -307,7 +308,7 @@ impl<'de, E: EthSpec> ContextDeserialize<'de, ForkName> for LightClientFinalityU ForkName::Fulu => { Self::Fulu(Deserialize::deserialize(deserializer).map_err(convert_err)?) } - ForkName::Gloas => { + ForkName::Gloas | ForkName::Heze => { // TODO(EIP-7732): check if this is correct return Err(serde::de::Error::custom(format!( "LightClientBootstrap failed to deserialize: unsupported fork '{}'", diff --git a/consensus/types/src/light_client/light_client_header.rs b/consensus/types/src/light_client/light_client_header.rs index fdf9f234ef..f814942537 100644 --- a/consensus/types/src/light_client/light_client_header.rs +++ b/consensus/types/src/light_client/light_client_header.rs @@ -111,6 +111,7 @@ impl LightClientHeader { } // TODO(gloas): implement Gloas light client ForkName::Gloas => return Err(LightClientError::GloasNotImplemented), + ForkName::Heze => return Err(LightClientError::HezeNotImplemented), }; Ok(header) } @@ -133,7 +134,7 @@ impl LightClientHeader { LightClientHeader::Fulu(LightClientHeaderFulu::from_ssz_bytes(bytes)?) } // TODO(gloas): implement Gloas light client - ForkName::Base | ForkName::Gloas => { + ForkName::Base | ForkName::Gloas | ForkName::Heze => { return Err(ssz::DecodeError::BytesInvalid(format!( "LightClientHeader decoding for {fork_name} not implemented" ))); @@ -152,7 +153,10 @@ impl LightClientHeader { } pub fn ssz_max_var_len_for_fork(fork_name: ForkName) -> usize { - if fork_name.gloas_enabled() { + if fork_name.heze_enabled() { + // TODO(EIP7732): check this + 0 + } else if fork_name.gloas_enabled() { // TODO(EIP7732): check this 0 } else if fork_name.capella_enabled() { @@ -364,7 +368,7 @@ impl<'de, E: EthSpec> ContextDeserialize<'de, ForkName> for LightClientHeader }; Ok(match context { // TODO(gloas): implement Gloas light client - ForkName::Base | ForkName::Gloas => { + ForkName::Base | ForkName::Gloas | ForkName::Heze => { return Err(serde::de::Error::custom(format!( "LightClientFinalityUpdate failed to deserialize: unsupported fork '{}'", context diff --git a/consensus/types/src/light_client/light_client_optimistic_update.rs b/consensus/types/src/light_client/light_client_optimistic_update.rs index 139c4b6a08..f77391daf3 100644 --- a/consensus/types/src/light_client/light_client_optimistic_update.rs +++ b/consensus/types/src/light_client/light_client_optimistic_update.rs @@ -124,6 +124,7 @@ impl LightClientOptimisticUpdate { signature_slot, }), ForkName::Gloas => return Err(LightClientError::GloasNotImplemented), + ForkName::Heze => return Err(LightClientError::HezeNotImplemented), ForkName::Base => return Err(LightClientError::AltairForkNotActive), }; @@ -180,7 +181,7 @@ impl LightClientOptimisticUpdate { } ForkName::Fulu => Self::Fulu(LightClientOptimisticUpdateFulu::from_ssz_bytes(bytes)?), // TODO(gloas): implement Gloas light client - ForkName::Base | ForkName::Gloas => { + ForkName::Base | ForkName::Gloas | ForkName::Heze => { return Err(ssz::DecodeError::BytesInvalid(format!( "LightClientOptimisticUpdate decoding for {fork_name} not implemented" ))); @@ -202,7 +203,7 @@ impl LightClientOptimisticUpdate { ForkName::Electra => as Encode>::ssz_fixed_len(), ForkName::Fulu => as Encode>::ssz_fixed_len(), // TODO(gloas): implement Gloas light client - ForkName::Gloas => 0, + ForkName::Gloas | ForkName::Heze => 0, }; fixed_len + LightClientHeader::::ssz_max_var_len_for_fork(fork_name) } @@ -254,7 +255,7 @@ impl<'de, E: EthSpec> ContextDeserialize<'de, ForkName> for LightClientOptimisti ForkName::Fulu => { Self::Fulu(Deserialize::deserialize(deserializer).map_err(convert_err)?) } - ForkName::Gloas => { + ForkName::Gloas | ForkName::Heze => { // TODO(EIP-7732): check if this is correct return Err(serde::de::Error::custom(format!( "LightClientBootstrap failed to deserialize: unsupported fork '{}'", diff --git a/consensus/types/src/light_client/light_client_update.rs b/consensus/types/src/light_client/light_client_update.rs index cd33f6ae54..3bab756855 100644 --- a/consensus/types/src/light_client/light_client_update.rs +++ b/consensus/types/src/light_client/light_client_update.rs @@ -141,7 +141,7 @@ impl<'de, E: EthSpec> ContextDeserialize<'de, ForkName> for LightClientUpdate }; Ok(match context { // TODO(gloas): implement Gloas light client - ForkName::Base | ForkName::Gloas => { + ForkName::Base | ForkName::Gloas | ForkName::Heze => { return Err(serde::de::Error::custom(format!( "LightClientUpdate failed to deserialize: unsupported fork '{}'", context @@ -328,6 +328,7 @@ impl LightClientUpdate { // if you need to test or support lightclient usages // TODO(gloas): implement Gloas light client ForkName::Gloas => return Err(LightClientError::GloasNotImplemented), + ForkName::Heze => return Err(LightClientError::HezeNotImplemented), }; Ok(light_client_update) @@ -343,7 +344,7 @@ impl LightClientUpdate { ForkName::Electra => Self::Electra(LightClientUpdateElectra::from_ssz_bytes(bytes)?), ForkName::Fulu => Self::Fulu(LightClientUpdateFulu::from_ssz_bytes(bytes)?), // TODO(gloas): implement Gloas light client - ForkName::Base | ForkName::Gloas => { + ForkName::Base | ForkName::Gloas | ForkName::Heze => { return Err(ssz::DecodeError::BytesInvalid(format!( "LightClientUpdate decoding for {fork_name} not implemented" ))); @@ -500,7 +501,7 @@ impl LightClientUpdate { ForkName::Electra => as Encode>::ssz_fixed_len(), ForkName::Fulu => as Encode>::ssz_fixed_len(), // TODO(gloas): implement Gloas light client - ForkName::Gloas => 0, + ForkName::Gloas | ForkName::Heze => 0, }; fixed_len + 2 * LightClientHeader::::ssz_max_var_len_for_fork(fork_name) } diff --git a/consensus/types/src/state/beacon_state.rs b/consensus/types/src/state/beacon_state.rs index 7ed3121d6e..b911b965a6 100644 --- a/consensus/types/src/state/beacon_state.rs +++ b/consensus/types/src/state/beacon_state.rs @@ -278,7 +278,7 @@ impl From for Hash256 { /// /// https://github.com/sigp/milhouse/issues/43 #[superstruct( - variants(Base, Altair, Bellatrix, Capella, Deneb, Electra, Fulu, Gloas), + variants(Base, Altair, Bellatrix, Capella, Deneb, Electra, Fulu, Gloas, Heze), variant_attributes( derive( Educe, @@ -412,6 +412,20 @@ impl From for Hash256 { groups(tree_lists) )), num_fields(all()), + )), + Heze(metastruct( + mappings( + map_beacon_state_heze_fields(), + map_beacon_state_heze_tree_list_fields(mutable, fallible, groups(tree_lists)), + map_beacon_state_heze_tree_list_fields_immutable(groups(tree_lists)), + ), + bimappings(bimap_beacon_state_heze_tree_list_fields( + other_type = "BeaconStateHeze", + self_mutable, + fallible, + groups(tree_lists) + )), + num_fields(all()), )) ), cast_error( @@ -504,11 +518,11 @@ where // Participation (Altair and later) #[compare_fields(as_iter)] - #[superstruct(only(Altair, Bellatrix, Capella, Deneb, Electra, Fulu, Gloas))] + #[superstruct(only(Altair, Bellatrix, Capella, Deneb, Electra, Fulu, Gloas, Heze))] #[test_random(default)] #[compare_fields(as_iter)] pub previous_epoch_participation: List, - #[superstruct(only(Altair, Bellatrix, Capella, Deneb, Electra, Fulu, Gloas))] + #[superstruct(only(Altair, Bellatrix, Capella, Deneb, Electra, Fulu, Gloas, Heze))] #[test_random(default)] pub current_epoch_participation: List, @@ -528,15 +542,15 @@ where // Inactivity #[serde(with = "ssz_types::serde_utils::quoted_u64_var_list")] - #[superstruct(only(Altair, Bellatrix, Capella, Deneb, Electra, Fulu, Gloas))] + #[superstruct(only(Altair, Bellatrix, Capella, Deneb, Electra, Fulu, Gloas, Heze))] #[test_random(default)] pub inactivity_scores: List, // Light-client sync committees - #[superstruct(only(Altair, Bellatrix, Capella, Deneb, Electra, Fulu, Gloas))] + #[superstruct(only(Altair, Bellatrix, Capella, Deneb, Electra, Fulu, Gloas, Heze))] #[metastruct(exclude_from(tree_lists))] pub current_sync_committee: Arc>, - #[superstruct(only(Altair, Bellatrix, Capella, Deneb, Electra, Fulu, Gloas))] + #[superstruct(only(Altair, Bellatrix, Capella, Deneb, Electra, Fulu, Gloas, Heze))] #[metastruct(exclude_from(tree_lists))] pub next_sync_committee: Arc>, @@ -572,104 +586,104 @@ where #[metastruct(exclude_from(tree_lists))] pub latest_execution_payload_header: ExecutionPayloadHeaderFulu, #[test_random(default)] - #[superstruct(only(Gloas))] + #[superstruct(only(Gloas, Heze))] #[metastruct(exclude_from(tree_lists))] pub latest_block_hash: ExecutionBlockHash, - #[superstruct(only(Capella, Deneb, Electra, Fulu, Gloas), partial_getter(copy))] + #[superstruct(only(Capella, Deneb, Electra, Fulu, Gloas, Heze), partial_getter(copy))] #[serde(with = "serde_utils::quoted_u64")] #[metastruct(exclude_from(tree_lists))] pub next_withdrawal_index: u64, - #[superstruct(only(Capella, Deneb, Electra, Fulu, Gloas), partial_getter(copy))] + #[superstruct(only(Capella, Deneb, Electra, Fulu, Gloas, Heze), 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, Fulu, Gloas))] + #[superstruct(only(Capella, Deneb, Electra, Fulu, Gloas, Heze))] #[test_random(default)] pub historical_summaries: List, // Electra - #[superstruct(only(Electra, Fulu, Gloas), partial_getter(copy))] + #[superstruct(only(Electra, Fulu, Gloas, Heze), partial_getter(copy))] #[metastruct(exclude_from(tree_lists))] #[serde(with = "serde_utils::quoted_u64")] pub deposit_requests_start_index: u64, - #[superstruct(only(Electra, Fulu, Gloas), partial_getter(copy))] + #[superstruct(only(Electra, Fulu, Gloas, Heze), partial_getter(copy))] #[metastruct(exclude_from(tree_lists))] #[serde(with = "serde_utils::quoted_u64")] pub deposit_balance_to_consume: u64, - #[superstruct(only(Electra, Fulu, Gloas), partial_getter(copy))] + #[superstruct(only(Electra, Fulu, Gloas, Heze), partial_getter(copy))] #[metastruct(exclude_from(tree_lists))] #[serde(with = "serde_utils::quoted_u64")] pub exit_balance_to_consume: u64, - #[superstruct(only(Electra, Fulu, Gloas), partial_getter(copy))] + #[superstruct(only(Electra, Fulu, Gloas, Heze), partial_getter(copy))] #[metastruct(exclude_from(tree_lists))] pub earliest_exit_epoch: Epoch, - #[superstruct(only(Electra, Fulu, Gloas), partial_getter(copy))] + #[superstruct(only(Electra, Fulu, Gloas, Heze), partial_getter(copy))] #[metastruct(exclude_from(tree_lists))] #[serde(with = "serde_utils::quoted_u64")] pub consolidation_balance_to_consume: u64, - #[superstruct(only(Electra, Fulu, Gloas), partial_getter(copy))] + #[superstruct(only(Electra, Fulu, Gloas, Heze), partial_getter(copy))] #[metastruct(exclude_from(tree_lists))] pub earliest_consolidation_epoch: Epoch, #[compare_fields(as_iter)] #[test_random(default)] - #[superstruct(only(Electra, Fulu, Gloas))] + #[superstruct(only(Electra, Fulu, Gloas, Heze))] pub pending_deposits: List, #[compare_fields(as_iter)] #[test_random(default)] - #[superstruct(only(Electra, Fulu, Gloas))] + #[superstruct(only(Electra, Fulu, Gloas, Heze))] pub pending_partial_withdrawals: List, #[compare_fields(as_iter)] #[test_random(default)] - #[superstruct(only(Electra, Fulu, Gloas))] + #[superstruct(only(Electra, Fulu, Gloas, Heze))] pub pending_consolidations: List, // Fulu #[compare_fields(as_iter)] #[test_random(default)] - #[superstruct(only(Fulu, Gloas))] + #[superstruct(only(Fulu, Gloas, Heze))] #[serde(with = "ssz_types::serde_utils::quoted_u64_fixed_vec")] pub proposer_lookahead: Vector, // Gloas #[compare_fields(as_iter)] #[test_random(default)] - #[superstruct(only(Gloas))] + #[superstruct(only(Gloas, Heze))] pub builders: List, #[metastruct(exclude_from(tree_lists))] #[serde(with = "serde_utils::quoted_u64")] - #[superstruct(only(Gloas), partial_getter(copy))] + #[superstruct(only(Gloas, Heze), partial_getter(copy))] pub next_withdrawal_builder_index: BuilderIndex, #[test_random(default)] - #[superstruct(only(Gloas))] + #[superstruct(only(Gloas, Heze))] #[metastruct(exclude_from(tree_lists))] pub execution_payload_availability: BitVector, #[compare_fields(as_iter)] #[test_random(default)] - #[superstruct(only(Gloas))] + #[superstruct(only(Gloas, Heze))] pub builder_pending_payments: Vector, #[compare_fields(as_iter)] #[test_random(default)] - #[superstruct(only(Gloas))] + #[superstruct(only(Gloas, Heze))] pub builder_pending_withdrawals: List, - #[superstruct(only(Gloas))] + #[superstruct(only(Gloas, Heze))] #[metastruct(exclude_from(tree_lists))] pub latest_execution_payload_bid: ExecutionPayloadBid, #[compare_fields(as_iter)] #[test_random(default)] - #[superstruct(only(Gloas))] + #[superstruct(only(Gloas, Heze))] pub payload_expected_withdrawals: List, #[compare_fields(as_iter)] #[test_random(default)] - #[superstruct(only(Gloas))] + #[superstruct(only(Gloas, Heze))] pub ptc_window: Vector, E::PtcWindowLength>, // Caching (not in the spec) @@ -812,6 +826,7 @@ impl BeaconState { BeaconState::Electra { .. } => ForkName::Electra, BeaconState::Fulu { .. } => ForkName::Fulu, BeaconState::Gloas { .. } => ForkName::Gloas, + BeaconState::Heze { .. } => ForkName::Heze, } } @@ -1243,6 +1258,7 @@ impl BeaconState { )), // TODO(EIP-7732): investigate calling functions BeaconState::Gloas(_) => Err(BeaconStateError::IncorrectStateVariant), + BeaconState::Heze(_) => Err(BeaconStateError::IncorrectStateVariant), } } @@ -1270,6 +1286,7 @@ impl BeaconState { )), // TODO(EIP-7732): investigate calling functions BeaconState::Gloas(_) => Err(BeaconStateError::IncorrectStateVariant), + BeaconState::Heze(_) => Err(BeaconStateError::IncorrectStateVariant), } } @@ -1911,6 +1928,16 @@ impl BeaconState { &mut state.exit_cache, &mut state.epoch_cache, )), + BeaconState::Heze(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, + )), } } @@ -2175,7 +2202,8 @@ impl BeaconState { BeaconState::Deneb(_) | BeaconState::Electra(_) | BeaconState::Fulu(_) - | BeaconState::Gloas(_) => std::cmp::min( + | BeaconState::Gloas(_) + | BeaconState::Heze(_) => std::cmp::min( spec.max_per_epoch_activation_churn_limit, self.get_validator_churn_limit(spec)?, ), @@ -2324,6 +2352,7 @@ impl BeaconState { BeaconState::Electra(state) => Ok(&mut state.current_epoch_participation), BeaconState::Fulu(state) => Ok(&mut state.current_epoch_participation), BeaconState::Gloas(state) => Ok(&mut state.current_epoch_participation), + BeaconState::Heze(state) => Ok(&mut state.current_epoch_participation), } } else if epoch == previous_epoch { match self { @@ -2335,6 +2364,7 @@ impl BeaconState { BeaconState::Electra(state) => Ok(&mut state.previous_epoch_participation), BeaconState::Fulu(state) => Ok(&mut state.previous_epoch_participation), BeaconState::Gloas(state) => Ok(&mut state.previous_epoch_participation), + BeaconState::Heze(state) => Ok(&mut state.previous_epoch_participation), } } else { Err(BeaconStateError::EpochOutOfBounds) @@ -2638,6 +2668,11 @@ impl BeaconState { any_pending_mutations |= self_field.has_pending_updates(); }); } + Self::Heze(self_inner) => { + map_beacon_state_heze_tree_list_fields_immutable!(self_inner, |_, self_field| { + any_pending_mutations |= self_field.has_pending_updates(); + }); + } }; any_pending_mutations } @@ -2904,7 +2939,10 @@ impl BeaconState { | BeaconState::Bellatrix(_) | BeaconState::Capella(_) | BeaconState::Deneb(_) => Err(BeaconStateError::IncorrectStateVariant), - BeaconState::Electra(_) | BeaconState::Fulu(_) | BeaconState::Gloas(_) => { + BeaconState::Electra(_) + | BeaconState::Fulu(_) + | BeaconState::Gloas(_) + | BeaconState::Heze(_) => { // Consume the balance and update state variables *self.exit_balance_to_consume_mut()? = exit_balance_to_consume.safe_sub(exit_balance)?; @@ -2951,7 +2989,10 @@ impl BeaconState { | BeaconState::Bellatrix(_) | BeaconState::Capella(_) | BeaconState::Deneb(_) => Err(BeaconStateError::IncorrectStateVariant), - BeaconState::Electra(_) | BeaconState::Fulu(_) | BeaconState::Gloas(_) => { + BeaconState::Electra(_) + | BeaconState::Fulu(_) + | BeaconState::Gloas(_) + | BeaconState::Heze(_) => { // Consume the balance and update state variables. *self.consolidation_balance_to_consume_mut()? = consolidation_balance_to_consume.safe_sub(consolidation_balance)?; @@ -3029,6 +3070,14 @@ impl BeaconState { ); } (Self::Gloas(_), _) => (), + (Self::Heze(self_inner), Self::Heze(base_inner)) => { + bimap_beacon_state_heze_tree_list_fields!( + self_inner, + base_inner, + |_, self_field, base_field| { self_field.rebase_on(base_field) } + ); + } + (Self::Heze(_), _) => (), } // Use sync committees from `base` if they are equal. @@ -3338,6 +3387,7 @@ impl BeaconState { ForkName::Electra => BeaconStateElectra::::NUM_FIELDS.next_power_of_two(), ForkName::Fulu => BeaconStateFulu::::NUM_FIELDS.next_power_of_two(), ForkName::Gloas => BeaconStateGloas::::NUM_FIELDS.next_power_of_two(), + ForkName::Heze => BeaconStateHeze::::NUM_FIELDS.next_power_of_two(), } } @@ -3391,6 +3441,9 @@ impl BeaconState { Self::Gloas(inner) => { map_beacon_state_gloas_tree_list_fields!(inner, |_, x| { x.apply_updates() }) } + Self::Heze(inner) => { + map_beacon_state_heze_tree_list_fields!(inner, |_, x| { x.apply_updates() }) + } } Ok(()) } @@ -3508,6 +3561,11 @@ impl BeaconState { leaves.push(field.tree_hash_root()); }); } + BeaconState::Heze(state) => { + map_beacon_state_heze_fields!(state, |_, field| { + leaves.push(field.tree_hash_root()); + }); + } }; leaves @@ -3567,6 +3625,7 @@ impl CompareFields for BeaconState { (BeaconState::Electra(x), BeaconState::Electra(y)) => x.compare_fields(y), (BeaconState::Fulu(x), BeaconState::Fulu(y)) => x.compare_fields(y), (BeaconState::Gloas(x), BeaconState::Gloas(y)) => x.compare_fields(y), + (BeaconState::Heze(x), BeaconState::Heze(y)) => x.compare_fields(y), _ => panic!("compare_fields: mismatched state variants",), } } diff --git a/consensus/types/src/state/mod.rs b/consensus/types/src/state/mod.rs index a3bb1b8c9f..52e0152577 100644 --- a/consensus/types/src/state/mod.rs +++ b/consensus/types/src/state/mod.rs @@ -17,7 +17,8 @@ pub use balance::Balance; pub use beacon_state::{ BeaconState, BeaconStateAltair, BeaconStateBase, BeaconStateBellatrix, BeaconStateCapella, BeaconStateDeneb, BeaconStateElectra, BeaconStateError, BeaconStateFulu, BeaconStateGloas, - BeaconStateHash, BeaconStateRef, CACHED_EPOCHS, DEFAULT_PRE_ELECTRA_WS_PERIOD, Validators, + BeaconStateHash, BeaconStateHeze, BeaconStateRef, CACHED_EPOCHS, + DEFAULT_PRE_ELECTRA_WS_PERIOD, Validators, }; pub use committee_cache::{ CommitteeCache, compute_committee_index_in_epoch, compute_committee_range_in_epoch, diff --git a/consensus/types/src/withdrawal/expected_withdrawals.rs b/consensus/types/src/withdrawal/expected_withdrawals.rs index f9809e6e73..1c8932a7ad 100644 --- a/consensus/types/src/withdrawal/expected_withdrawals.rs +++ b/consensus/types/src/withdrawal/expected_withdrawals.rs @@ -2,17 +2,17 @@ use crate::{EthSpec, Withdrawals}; use superstruct::superstruct; #[superstruct( - variants(Capella, Electra, Gloas), + variants(Capella, Electra, Gloas, Heze), variant_attributes(derive(Debug, PartialEq, Clone)) )] #[derive(Debug, PartialEq, Clone)] pub struct ExpectedWithdrawals { pub withdrawals: Withdrawals, - #[superstruct(only(Gloas), partial_getter(copy))] + #[superstruct(only(Gloas, Heze), partial_getter(copy))] pub processed_builder_withdrawals_count: u64, - #[superstruct(only(Electra, Gloas), partial_getter(copy))] + #[superstruct(only(Electra, Gloas, Heze), partial_getter(copy))] pub processed_partial_withdrawals_count: u64, - #[superstruct(only(Gloas), partial_getter(copy))] + #[superstruct(only(Gloas, Heze), partial_getter(copy))] pub processed_builders_sweep_count: u64, #[superstruct(getter(copy))] pub processed_sweep_withdrawals_count: u64, @@ -24,6 +24,7 @@ impl From> for Withdrawals { ExpectedWithdrawals::Capella(ew) => ew.withdrawals, ExpectedWithdrawals::Electra(ew) => ew.withdrawals, ExpectedWithdrawals::Gloas(ew) => ew.withdrawals, + ExpectedWithdrawals::Heze(ew) => ew.withdrawals, } } } diff --git a/consensus/types/src/withdrawal/mod.rs b/consensus/types/src/withdrawal/mod.rs index fbe7351754..696dbbd64c 100644 --- a/consensus/types/src/withdrawal/mod.rs +++ b/consensus/types/src/withdrawal/mod.rs @@ -6,7 +6,7 @@ mod withdrawal_request; pub use expected_withdrawals::{ ExpectedWithdrawals, ExpectedWithdrawalsCapella, ExpectedWithdrawalsElectra, - ExpectedWithdrawalsGloas, + ExpectedWithdrawalsGloas, ExpectedWithdrawalsHeze, }; pub use pending_partial_withdrawal::PendingPartialWithdrawal; pub use withdrawal::{Withdrawal, Withdrawals}; diff --git a/lcli/src/mock_el.rs b/lcli/src/mock_el.rs index 6086067a47..628f2cd4d5 100644 --- a/lcli/src/mock_el.rs +++ b/lcli/src/mock_el.rs @@ -20,6 +20,7 @@ pub fn run(mut env: Environment, matches: &ArgMatches) -> Result< let prague_time = parse_optional(matches, "prague-time")?; let osaka_time = parse_optional(matches, "osaka-time")?; let amsterdam_time = parse_optional(matches, "amsterdam-time")?; + let heze_time = parse_optional(matches, "heze-time")?; let handle = env.core_context().executor.handle().unwrap(); @@ -51,6 +52,7 @@ pub fn run(mut env: Environment, matches: &ArgMatches) -> Result< prague_time, osaka_time, amsterdam_time, + heze_time, }; let kzg = None; let server: MockServer = MockServer::new_with_config(&handle, config, kzg); diff --git a/testing/ef_tests/src/cases/fork.rs b/testing/ef_tests/src/cases/fork.rs index 54efb9f9ce..ba5dce04c9 100644 --- a/testing/ef_tests/src/cases/fork.rs +++ b/testing/ef_tests/src/cases/fork.rs @@ -4,7 +4,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_fulu, upgrade_to_gloas, + upgrade_to_electra, upgrade_to_fulu, upgrade_to_gloas, upgrade_to_heze, }; use types::BeaconState; @@ -73,6 +73,7 @@ impl Case for ForkTest { ForkName::Electra => upgrade_to_electra(&mut result_state, spec).map(|_| result_state), ForkName::Fulu => upgrade_to_fulu(&mut result_state, spec).map(|_| result_state), ForkName::Gloas => upgrade_to_gloas(&mut result_state, spec).map(|_| result_state), + ForkName::Heze => upgrade_to_heze(&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 4aa9f980d4..587673ddcb 100644 --- a/testing/ef_tests/src/cases/merkle_proof_validity.rs +++ b/testing/ef_tests/src/cases/merkle_proof_validity.rs @@ -6,7 +6,8 @@ use tree_hash::Hash256; use typenum::Unsigned; use types::{ BeaconBlockBody, BeaconBlockBodyCapella, BeaconBlockBodyDeneb, BeaconBlockBodyElectra, - BeaconBlockBodyFulu, BeaconBlockBodyGloas, BeaconState, FullPayload, light_client, + BeaconBlockBodyFulu, BeaconBlockBodyGloas, BeaconBlockBodyHeze, BeaconState, FullPayload, + light_client, }; #[derive(Debug, Clone, Deserialize)] @@ -177,6 +178,9 @@ impl LoadCase for KzgInclusionMerkleProofValidity { ForkName::Gloas => { ssz_decode_file::>(&path.join("object.ssz_snappy"))?.into() } + ForkName::Heze => { + 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. @@ -298,6 +302,9 @@ impl LoadCase for BeaconBlockBodyMerkleProofValidity { ForkName::Gloas => { ssz_decode_file::>(&path.join("object.ssz_snappy"))?.into() } + ForkName::Heze => { + 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/transition.rs b/testing/ef_tests/src/cases/transition.rs index 06aa813650..2e7c5b6b72 100644 --- a/testing/ef_tests/src/cases/transition.rs +++ b/testing/ef_tests/src/cases/transition.rs @@ -77,6 +77,16 @@ impl LoadCase for TransitionTest { spec.fulu_fork_epoch = Some(Epoch::new(0)); spec.gloas_fork_epoch = Some(metadata.fork_epoch); } + ForkName::Heze => { + 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.gloas_fork_epoch = Some(Epoch::new(0)); + spec.heze_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 96798c910c..c4ce01c9f1 100644 --- a/testing/ef_tests/src/handler.rs +++ b/testing/ef_tests/src/handler.rs @@ -309,6 +309,10 @@ impl SszStaticHandler { Self::for_forks(vec![ForkName::Gloas]) } + pub fn heze_only() -> Self { + Self::for_forks(vec![ForkName::Heze]) + } + pub fn altair_and_later() -> Self { Self::for_forks(ForkName::list_all()[1..].to_vec()) } @@ -337,6 +341,10 @@ impl SszStaticHandler { Self::for_forks(ForkName::list_all()[7..].to_vec()) } + pub fn heze_and_later() -> Self { + Self::for_forks(ForkName::list_all()[8..].to_vec()) + } + pub fn pre_electra() -> Self { Self::for_forks(ForkName::list_all()[0..5].to_vec()) } @@ -772,7 +780,7 @@ impl Handler for OptimisticSyncHandler { fn disabled_forks(&self) -> Vec { // TODO(gloas): remove once we have Gloas optimistic sync tests - vec![ForkName::Gloas] + vec![ForkName::Gloas, ForkName::Heze] } } @@ -997,7 +1005,7 @@ impl Handler for KZGComputeCellsHandler { fn disabled_forks(&self) -> Vec { // TODO(gloas): remove once we have Gloas KZG tests - vec![ForkName::Gloas] + vec![ForkName::Gloas, ForkName::Heze] } } @@ -1022,7 +1030,7 @@ impl Handler for KZGComputeCellsAndKZGProofHandler { fn disabled_forks(&self) -> Vec { // TODO(gloas): remove once we have Gloas KZG tests - vec![ForkName::Gloas] + vec![ForkName::Gloas, ForkName::Heze] } } @@ -1047,7 +1055,7 @@ impl Handler for KZGVerifyCellKZGProofBatchHandler { fn disabled_forks(&self) -> Vec { // TODO(gloas): remove once we have Gloas KZG tests - vec![ForkName::Gloas] + vec![ForkName::Gloas, ForkName::Heze] } } @@ -1072,7 +1080,7 @@ impl Handler for KZGRecoverCellsAndKZGProofHandler { fn disabled_forks(&self) -> Vec { // TODO(gloas): remove once we have Gloas KZG tests - vec![ForkName::Gloas] + vec![ForkName::Gloas, ForkName::Heze] } } @@ -1101,7 +1109,7 @@ impl Handler for KzgInclusionMerkleProofValidityHandler Vec { // TODO(gloas): remove once we have Gloas KZG merkle proof tests - vec![ForkName::Gloas] + vec![ForkName::Gloas, ForkName::Heze] } } @@ -1130,7 +1138,7 @@ impl Handler for MerkleProofValidityHandler { fn disabled_forks(&self) -> Vec { // TODO(gloas): remove once we have Gloas light client tests - vec![ForkName::Gloas] + vec![ForkName::Gloas, ForkName::Heze] } } @@ -1160,7 +1168,7 @@ impl Handler for LightClientUpdateHandler { fn disabled_forks(&self) -> Vec { // TODO(gloas): remove once we have Gloas light client tests - vec![ForkName::Gloas] + vec![ForkName::Gloas, ForkName::Heze] } } diff --git a/testing/ef_tests/src/type_name.rs b/testing/ef_tests/src/type_name.rs index 18666befaa..464cd72cd8 100644 --- a/testing/ef_tests/src/type_name.rs +++ b/testing/ef_tests/src/type_name.rs @@ -56,6 +56,7 @@ type_name_generic!(BeaconBlockBodyDeneb, "BeaconBlockBody"); type_name_generic!(BeaconBlockBodyElectra, "BeaconBlockBody"); type_name_generic!(BeaconBlockBodyFulu, "BeaconBlockBody"); type_name_generic!(BeaconBlockBodyGloas, "BeaconBlockBody"); +type_name_generic!(BeaconBlockBodyHeze, "BeaconBlockBody"); type_name!(BeaconBlockHeader); type_name_generic!(BeaconState); type_name!(BlobIdentifier); @@ -64,6 +65,7 @@ type_name_generic!(BlobSidecar); type_name_generic!(DataColumnSidecar); type_name_generic!(DataColumnSidecarFulu, "DataColumnSidecar"); type_name_generic!(DataColumnSidecarGloas, "DataColumnSidecar"); +type_name_generic!(DataColumnSidecarHeze, "DataColumnSidecar"); type_name!(Checkpoint); type_name!(ConsolidationRequest); type_name_generic!(ContributionAndProof); @@ -83,6 +85,7 @@ type_name_generic!(ExecutionPayloadDeneb, "ExecutionPayload"); type_name_generic!(ExecutionPayloadElectra, "ExecutionPayload"); type_name_generic!(ExecutionPayloadFulu, "ExecutionPayload"); type_name_generic!(ExecutionPayloadGloas, "ExecutionPayload"); +type_name_generic!(ExecutionPayloadHeze, "ExecutionPayload"); type_name_generic!(FullPayload, "ExecutionPayload"); type_name_generic!(ExecutionPayloadHeader); type_name_generic!(ExecutionPayloadHeaderBellatrix, "ExecutionPayloadHeader"); diff --git a/testing/ef_tests/tests/tests.rs b/testing/ef_tests/tests/tests.rs index 79a02d7e80..cd483a14c1 100644 --- a/testing/ef_tests/tests/tests.rs +++ b/testing/ef_tests/tests/tests.rs @@ -400,6 +400,10 @@ mod ssz_static { .run(); SszStaticHandler::, MainnetEthSpec>::gloas_only() .run(); + SszStaticHandler::, MinimalEthSpec>::heze_only() + .run(); + SszStaticHandler::, MainnetEthSpec>::heze_only() + .run(); } // Altair and later @@ -631,6 +635,10 @@ mod ssz_static { .run(); SszStaticHandler::, MainnetEthSpec>::gloas_only() .run(); + SszStaticHandler::, MinimalEthSpec>::heze_only() + .run(); + SszStaticHandler::, MainnetEthSpec>::heze_only() + .run(); } #[test] @@ -717,6 +725,10 @@ mod ssz_static { .run(); SszStaticHandler::, MainnetEthSpec>::gloas_only() .run(); + SszStaticHandler::, MinimalEthSpec>::heze_only() + .run(); + SszStaticHandler::, MainnetEthSpec>::heze_only() + .run(); } #[test] diff --git a/validator_client/beacon_node_fallback/src/lib.rs b/validator_client/beacon_node_fallback/src/lib.rs index b36ec70aa3..bf67205bd7 100644 --- a/validator_client/beacon_node_fallback/src/lib.rs +++ b/validator_client/beacon_node_fallback/src/lib.rs @@ -400,6 +400,13 @@ impl CandidateBeaconNode { hint = UPDATE_REQUIRED_LOG_HINT, "Beacon node has mismatched Gloas fork epoch" ); + } else if beacon_node_spec.heze_fork_epoch != spec.heze_fork_epoch { + warn!( + endpoint = %self.beacon_node, + endpoint_heze_fork_epoch = ?beacon_node_spec.heze_fork_epoch, + hint = UPDATE_REQUIRED_LOG_HINT, + "Beacon node has mismatched Heze fork epoch" + ); } Ok(()) diff --git a/validator_client/signing_method/src/web3signer.rs b/validator_client/signing_method/src/web3signer.rs index e6fc8f3ba2..5bfe8048c8 100644 --- a/validator_client/signing_method/src/web3signer.rs +++ b/validator_client/signing_method/src/web3signer.rs @@ -34,6 +34,7 @@ pub enum ForkName { Electra, Fulu, Gloas, + Heze, } #[derive(Debug, PartialEq, Serialize)] @@ -123,6 +124,11 @@ impl<'a, E: EthSpec, Payload: AbstractExecPayload> Web3SignerObject<'a, E, Pa block: None, block_header: Some(block.block_header()), }), + BeaconBlock::Heze(_) => Ok(Web3SignerObject::BeaconBlock { + version: ForkName::Heze, + block: None, + block_header: Some(block.block_header()), + }), } }