From 86c2b7cfbe28e0ad32fdfd559a7cb04f787af0bd Mon Sep 17 00:00:00 2001 From: chonghe <44791194+chong-he@users.noreply.github.com> Date: Tue, 16 Dec 2025 11:19:28 +0800 Subject: [PATCH 1/4] Append client version info to graffiti (#7558) * #7201 Co-Authored-By: Tan Chee Keong Co-Authored-By: chonghe <44791194+chong-he@users.noreply.github.com> Co-Authored-By: Jimmy Chen Co-Authored-By: Tan Chee Keong --- beacon_node/beacon_chain/src/beacon_chain.rs | 10 +- .../beacon_chain/src/graffiti_calculator.rs | 152 +++++++++++++++++- beacon_node/beacon_chain/src/test_utils.rs | 15 +- beacon_node/execution_layer/src/engine_api.rs | 55 ++++++- beacon_node/http_api/src/produce_block.rs | 12 +- .../http_api/tests/interactive_tests.rs | 2 +- beacon_node/http_api/tests/tests.rs | 128 ++++++++++++--- book/src/help_vc.md | 4 + common/eth2/src/lib.rs | 17 ++ common/eth2/src/types.rs | 8 + validator_client/src/cli.rs | 10 ++ validator_client/src/config.rs | 11 +- validator_client/src/lib.rs | 3 +- .../validator_services/src/block_service.rs | 12 ++ 14 files changed, 392 insertions(+), 47 deletions(-) diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index 25b2aa30cb3..2107f06e1e3 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -33,7 +33,7 @@ use crate::events::ServerSentEventHandler; use crate::execution_payload::{NotifyExecutionLayer, PreparePayloadHandle, get_execution_payload}; use crate::fetch_blobs::EngineGetBlobsOutput; use crate::fork_choice_signal::{ForkChoiceSignalRx, ForkChoiceSignalTx, ForkChoiceWaitResult}; -use crate::graffiti_calculator::GraffitiCalculator; +use crate::graffiti_calculator::{GraffitiCalculator, GraffitiSettings}; use crate::kzg_utils::reconstruct_blobs; use crate::light_client_finality_update_verification::{ Error as LightClientFinalityUpdateError, VerifiedLightClientFinalityUpdate, @@ -4493,7 +4493,7 @@ impl BeaconChain { self: &Arc, randao_reveal: Signature, slot: Slot, - validator_graffiti: Option, + graffiti_settings: GraffitiSettings, verification: ProduceBlockVerification, builder_boost_factor: Option, block_production_version: BlockProductionVersion, @@ -4527,7 +4527,7 @@ impl BeaconChain { state_root_opt, slot, randao_reveal, - validator_graffiti, + graffiti_settings, verification, builder_boost_factor, block_production_version, @@ -5060,7 +5060,7 @@ impl BeaconChain { state_root_opt: Option, produce_at_slot: Slot, randao_reveal: Signature, - validator_graffiti: Option, + graffiti_settings: GraffitiSettings, verification: ProduceBlockVerification, builder_boost_factor: Option, block_production_version: BlockProductionVersion, @@ -5071,7 +5071,7 @@ impl BeaconChain { let chain = self.clone(); let graffiti = self .graffiti_calculator - .get_graffiti(validator_graffiti) + .get_graffiti(graffiti_settings) .await; let span = Span::current(); let mut partial_beacon_block = self diff --git a/beacon_node/beacon_chain/src/graffiti_calculator.rs b/beacon_node/beacon_chain/src/graffiti_calculator.rs index 56808e0e67e..85470715c9f 100644 --- a/beacon_node/beacon_chain/src/graffiti_calculator.rs +++ b/beacon_node/beacon_chain/src/graffiti_calculator.rs @@ -1,5 +1,6 @@ use crate::BeaconChain; use crate::BeaconChainTypes; +use eth2::types::GraffitiPolicy; use execution_layer::{CommitPrefix, ExecutionLayer, http::ENGINE_GET_CLIENT_VERSION_V1}; use logging::crit; use serde::{Deserialize, Serialize}; @@ -48,6 +49,25 @@ impl Debug for GraffitiOrigin { } } +pub enum GraffitiSettings { + Unspecified, + Specified { + graffiti: Graffiti, + policy: GraffitiPolicy, + }, +} + +impl GraffitiSettings { + pub fn new(validator_graffiti: Option, policy: Option) -> Self { + validator_graffiti + .map(|graffiti| Self::Specified { + graffiti, + policy: policy.unwrap_or(GraffitiPolicy::PreserveUserGraffiti), + }) + .unwrap_or(Self::Unspecified) + } +} + pub struct GraffitiCalculator { pub beacon_graffiti: GraffitiOrigin, execution_layer: Option>, @@ -73,11 +93,19 @@ impl GraffitiCalculator { /// 2. Graffiti specified by the user via beacon node CLI options. /// 3. The EL & CL client version string, applicable when the EL supports version specification. /// 4. The default lighthouse version string, used if the EL lacks version specification support. - pub async fn get_graffiti(&self, validator_graffiti: Option) -> Graffiti { - if let Some(graffiti) = validator_graffiti { - return graffiti; + pub async fn get_graffiti(&self, graffiti_settings: GraffitiSettings) -> Graffiti { + match graffiti_settings { + GraffitiSettings::Specified { graffiti, policy } => match policy { + GraffitiPolicy::PreserveUserGraffiti => graffiti, + GraffitiPolicy::AppendClientVersions => { + self.calculate_combined_graffiti(Some(graffiti)).await + } + }, + GraffitiSettings::Unspecified => self.calculate_combined_graffiti(None).await, } + } + async fn calculate_combined_graffiti(&self, validator_graffiti: Option) -> Graffiti { match self.beacon_graffiti { GraffitiOrigin::UserSpecified(graffiti) => graffiti, GraffitiOrigin::Calculated(default_graffiti) => { @@ -133,7 +161,7 @@ impl GraffitiCalculator { CommitPrefix("00000000".to_string()) }); - engine_version.calculate_graffiti(lighthouse_commit_prefix) + engine_version.calculate_graffiti(lighthouse_commit_prefix, validator_graffiti) } } } @@ -224,8 +252,10 @@ async fn engine_version_cache_refresh_service( #[cfg(test)] mod tests { use crate::ChainConfig; + use crate::graffiti_calculator::GraffitiSettings; use crate::test_utils::{BeaconChainHarness, EphemeralHarnessType, test_spec}; use bls::Keypair; + use eth2::types::GraffitiPolicy; use execution_layer::EngineCapabilities; use execution_layer::test_utils::{DEFAULT_CLIENT_VERSION, DEFAULT_ENGINE_CAPABILITIES}; use std::sync::Arc; @@ -281,8 +311,12 @@ mod tests { let version_bytes = std::cmp::min(lighthouse_version::VERSION.len(), GRAFFITI_BYTES_LEN); // grab the slice of the graffiti that corresponds to the lighthouse version - let graffiti_slice = - &harness.chain.graffiti_calculator.get_graffiti(None).await.0[..version_bytes]; + let graffiti_slice = &harness + .chain + .graffiti_calculator + .get_graffiti(GraffitiSettings::Unspecified) + .await + .0[..version_bytes]; // convert graffiti bytes slice to ascii for easy debugging if this test should fail let graffiti_str = @@ -303,7 +337,12 @@ mod tests { let spec = Arc::new(test_spec::()); let harness = get_harness(VALIDATOR_COUNT, spec, None); - let found_graffiti_bytes = harness.chain.graffiti_calculator.get_graffiti(None).await.0; + let found_graffiti_bytes = harness + .chain + .graffiti_calculator + .get_graffiti(GraffitiSettings::Unspecified) + .await + .0; let mock_commit = DEFAULT_CLIENT_VERSION.commit.clone(); let expected_graffiti_string = format!( @@ -352,7 +391,10 @@ mod tests { let found_graffiti = harness .chain .graffiti_calculator - .get_graffiti(Some(Graffiti::from(graffiti_bytes))) + .get_graffiti(GraffitiSettings::new( + Some(Graffiti::from(graffiti_bytes)), + Some(GraffitiPolicy::PreserveUserGraffiti), + )) .await; assert_eq!( @@ -360,4 +402,98 @@ mod tests { "0x6e6963652067726166666974692062726f000000000000000000000000000000" ); } + + #[tokio::test] + async fn check_append_el_version_graffiti_various_length() { + let spec = Arc::new(test_spec::()); + let harness = get_harness(VALIDATOR_COUNT, spec, None); + + let graffiti_vec = vec![ + // less than 20 characters, example below is 19 characters + "This is my graffiti", + // 20-23 characters, example below is 22 characters + "This is my graffiti yo", + // 24-27 characters, example below is 26 characters + "This is my graffiti string", + // 28-29 characters, example below is 29 characters + "This is my graffiti string yo", + // 30-32 characters, example below is 32 characters + "This is my graffiti string yo yo", + ]; + + for graffiti in graffiti_vec { + let mut graffiti_bytes = [0; GRAFFITI_BYTES_LEN]; + graffiti_bytes[..graffiti.len()].copy_from_slice(graffiti.as_bytes()); + + // To test appending client version info with user specified graffiti + let policy = GraffitiPolicy::AppendClientVersions; + let found_graffiti_bytes = harness + .chain + .graffiti_calculator + .get_graffiti(GraffitiSettings::Specified { + graffiti: Graffiti::from(graffiti_bytes), + policy, + }) + .await + .0; + + let mock_commit = DEFAULT_CLIENT_VERSION.commit.clone(); + + let graffiti_length = graffiti.len(); + let append_graffiti_string = match graffiti_length { + 0..=19 => format!( + "{}{}{}{}", + DEFAULT_CLIENT_VERSION.code, + mock_commit + .strip_prefix("0x") + .unwrap_or("&mock_commit") + .get(0..4) + .expect("should get first 2 bytes in hex"), + "LH", + lighthouse_version::COMMIT_PREFIX + .get(0..4) + .expect("should get first 2 bytes in hex") + ), + 20..=23 => format!( + "{}{}{}{}", + DEFAULT_CLIENT_VERSION.code, + mock_commit + .strip_prefix("0x") + .unwrap_or("&mock_commit") + .get(0..2) + .expect("should get first 2 bytes in hex"), + "LH", + lighthouse_version::COMMIT_PREFIX + .get(0..2) + .expect("should get first 2 bytes in hex") + ), + 24..=27 => format!("{}{}", DEFAULT_CLIENT_VERSION.code, "LH",), + 28..=29 => DEFAULT_CLIENT_VERSION.code.to_string(), + // when user graffiti length is 30-32 characters, append nothing + 30..=32 => String::new(), + _ => panic!( + "graffiti length should be less than or equal to GRAFFITI_BYTES_LEN (32 characters)" + ), + }; + + let expected_graffiti_string = if append_graffiti_string.is_empty() { + // for the case of empty append_graffiti_string, i.e., user-specified graffiti is 30-32 characters + graffiti.to_string() + } else { + // There is a space between the client version info and user graffiti + // as defined in calculate_graffiti function in engine_api.rs + format!("{} {}", append_graffiti_string, graffiti) + }; + + let expected_graffiti_prefix_bytes = expected_graffiti_string.as_bytes(); + let expected_graffiti_prefix_len = + std::cmp::min(expected_graffiti_prefix_bytes.len(), GRAFFITI_BYTES_LEN); + + let found_graffiti_string = + std::str::from_utf8(&found_graffiti_bytes[..expected_graffiti_prefix_len]) + .expect("bytes should convert nicely to ascii"); + + assert_eq!(expected_graffiti_string, found_graffiti_string); + } + } } diff --git a/beacon_node/beacon_chain/src/test_utils.rs b/beacon_node/beacon_chain/src/test_utils.rs index 54fa6b5ff09..500c0b22d61 100644 --- a/beacon_node/beacon_chain/src/test_utils.rs +++ b/beacon_node/beacon_chain/src/test_utils.rs @@ -2,6 +2,7 @@ use crate::blob_verification::GossipVerifiedBlob; use crate::block_verification_types::{AsBlock, RpcBlock}; use crate::custody_context::NodeCustodyType; use crate::data_column_verification::CustodyDataColumn; +use crate::graffiti_calculator::GraffitiSettings; use crate::kzg_utils::build_data_column_sidecars; use crate::observed_operations::ObservationOutcome; pub use crate::persisted_beacon_chain::PersistedBeaconChain; @@ -23,7 +24,7 @@ use bls::get_withdrawal_credentials; use bls::{ AggregateSignature, Keypair, PublicKey, PublicKeyBytes, SecretKey, Signature, SignatureBytes, }; -use eth2::types::SignedBlockContentsTuple; +use eth2::types::{GraffitiPolicy, SignedBlockContentsTuple}; use execution_layer::test_utils::generate_genesis_header; use execution_layer::{ ExecutionLayer, @@ -943,6 +944,8 @@ where // BeaconChain errors out with `DuplicateFullyImported`. Vary the graffiti so that we produce // different blocks each time. let graffiti = Graffiti::from(self.rng.lock().random::<[u8; 32]>()); + let graffiti_settings = + GraffitiSettings::new(Some(graffiti), Some(GraffitiPolicy::PreserveUserGraffiti)); let randao_reveal = self.sign_randao_reveal(&state, proposer_index, slot); @@ -956,7 +959,7 @@ where None, slot, randao_reveal, - Some(graffiti), + graffiti_settings, ProduceBlockVerification::VerifyRandao, builder_boost_factor, BlockProductionVersion::V3, @@ -1000,6 +1003,8 @@ where // BeaconChain errors out with `DuplicateFullyImported`. Vary the graffiti so that we produce // different blocks each time. let graffiti = Graffiti::from(self.rng.lock().random::<[u8; 32]>()); + let graffiti_settings = + GraffitiSettings::new(Some(graffiti), Some(GraffitiPolicy::PreserveUserGraffiti)); let randao_reveal = self.sign_randao_reveal(&state, proposer_index, slot); @@ -1010,7 +1015,7 @@ where None, slot, randao_reveal, - Some(graffiti), + graffiti_settings, ProduceBlockVerification::VerifyRandao, None, BlockProductionVersion::FullV2, @@ -1059,6 +1064,8 @@ where // BeaconChain errors out with `DuplicateFullyImported`. Vary the graffiti so that we produce // different blocks each time. let graffiti = Graffiti::from(self.rng.lock().random::<[u8; 32]>()); + let graffiti_settings = + GraffitiSettings::new(Some(graffiti), Some(GraffitiPolicy::PreserveUserGraffiti)); let randao_reveal = self.sign_randao_reveal(&state, proposer_index, slot); @@ -1071,7 +1078,7 @@ where None, slot, randao_reveal, - Some(graffiti), + graffiti_settings, ProduceBlockVerification::VerifyRandao, None, BlockProductionVersion::FullV2, diff --git a/beacon_node/execution_layer/src/engine_api.rs b/beacon_node/execution_layer/src/engine_api.rs index b0cc4dd8241..88567ac6e12 100644 --- a/beacon_node/execution_layer/src/engine_api.rs +++ b/beacon_node/execution_layer/src/engine_api.rs @@ -763,8 +763,12 @@ pub struct ClientVersionV1 { } impl ClientVersionV1 { - pub fn calculate_graffiti(&self, lighthouse_commit_prefix: CommitPrefix) -> Graffiti { - let graffiti_string = format!( + pub fn calculate_graffiti( + &self, + lighthouse_commit_prefix: CommitPrefix, + validator_graffiti: Option, + ) -> Graffiti { + let append_graffiti_full = format!( "{}{}LH{}", self.code, self.commit @@ -778,6 +782,53 @@ impl ClientVersionV1 { .unwrap_or("0000") .to_lowercase(), ); + + // Implement the special case here: + // https://hackmd.io/@wmoBhF17RAOH2NZ5bNXJVg/BJX2c9gja#SPECIAL-CASE-the-flexible-standard + let append_graffiti_one_byte = format!( + "{}{}LH{}", + self.code, + self.commit + .0 + .get(..2) + .unwrap_or(self.commit.0.as_str()) + .to_lowercase(), + lighthouse_commit_prefix + .0 + .get(..2) + .unwrap_or("00") + .to_lowercase(), + ); + + let append_graffiti_no_commit = format!("{}LH", self.code); + let append_graffiti_only_el = format!("{}", self.code); + + let graffiti_string = if let Some(graffiti) = validator_graffiti { + let graffiti_length = graffiti.as_utf8_lossy().len(); + let graffiti_str = graffiti.as_utf8_lossy(); + + // 12 characters for append_graffiti_full, plus one character for spacing + // that leaves user specified graffiti to be 32-12-1 = 19 characters max, i.e., <20 + if graffiti_length < 20 { + format!("{} {}", append_graffiti_full, graffiti_str) + // user-specified graffiti is between 20-23 characters + } else if (20..24).contains(&graffiti_length) { + format!("{} {}", append_graffiti_one_byte, graffiti_str) + // user-specified graffiti is between 24-27 characters + } else if (24..28).contains(&graffiti_length) { + format!("{} {}", append_graffiti_no_commit, graffiti_str) + // user-specified graffiti is between 28-29 characters + } else if (28..30).contains(&graffiti_length) { + format!("{} {}", append_graffiti_only_el, graffiti_str) + // if user-specified graffiti is between 30-32 characters, append nothing + } else { + return graffiti; + } + } else { + // if no validator_graffiti (user doesn't specify), use the full client version info graffiti + append_graffiti_full + }; + let mut graffiti_bytes = [0u8; GRAFFITI_BYTES_LEN]; let bytes_to_copy = std::cmp::min(graffiti_string.len(), GRAFFITI_BYTES_LEN); graffiti_bytes[..bytes_to_copy] diff --git a/beacon_node/http_api/src/produce_block.rs b/beacon_node/http_api/src/produce_block.rs index 472ec0b65e4..3bd0cec7e33 100644 --- a/beacon_node/http_api/src/produce_block.rs +++ b/beacon_node/http_api/src/produce_block.rs @@ -6,6 +6,7 @@ use crate::{ add_ssz_content_type_header, beacon_response, inconsistent_fork_rejection, }, }; +use beacon_chain::graffiti_calculator::GraffitiSettings; use beacon_chain::{ BeaconBlockResponseWrapper, BeaconChain, BeaconChainTypes, ProduceBlockVerification, }; @@ -68,11 +69,13 @@ pub async fn produce_block_v3( query.builder_boost_factor }; + let graffiti_settings = GraffitiSettings::new(query.graffiti, query.graffiti_policy); + let block_response_type = chain .produce_block_with_verification( randao_reveal, slot, - query.graffiti, + graffiti_settings, randao_verification, builder_boost_factor, BlockProductionVersion::V3, @@ -148,11 +151,13 @@ pub async fn produce_blinded_block_v2( })?; let randao_verification = get_randao_verification(&query, randao_reveal.is_infinity())?; + let graffiti_settings = GraffitiSettings::new(query.graffiti, query.graffiti_policy); + let block_response_type = chain .produce_block_with_verification( randao_reveal, slot, - query.graffiti, + graffiti_settings, randao_verification, None, BlockProductionVersion::BlindedV2, @@ -182,12 +187,13 @@ pub async fn produce_block_v2( })?; let randao_verification = get_randao_verification(&query, randao_reveal.is_infinity())?; + let graffiti_settings = GraffitiSettings::new(query.graffiti, query.graffiti_policy); let block_response_type = chain .produce_block_with_verification( randao_reveal, slot, - query.graffiti, + graffiti_settings, randao_verification, None, BlockProductionVersion::FullV2, diff --git a/beacon_node/http_api/tests/interactive_tests.rs b/beacon_node/http_api/tests/interactive_tests.rs index 0119a7645c2..ce61c821b57 100644 --- a/beacon_node/http_api/tests/interactive_tests.rs +++ b/beacon_node/http_api/tests/interactive_tests.rs @@ -640,7 +640,7 @@ pub async fn proposer_boost_re_org_test( .into(); let (unsigned_block_type, _) = tester .client - .get_validator_blocks_v3::(slot_c, &randao_reveal, None, None) + .get_validator_blocks_v3::(slot_c, &randao_reveal, None, None, None) .await .unwrap(); diff --git a/beacon_node/http_api/tests/tests.rs b/beacon_node/http_api/tests/tests.rs index f8eba0ee2b7..ed7abead18a 100644 --- a/beacon_node/http_api/tests/tests.rs +++ b/beacon_node/http_api/tests/tests.rs @@ -3681,7 +3681,7 @@ impl ApiTester { let (response, metadata) = self .client - .get_validator_blocks_v3_ssz::(slot, &randao_reveal, None, None) + .get_validator_blocks_v3_ssz::(slot, &randao_reveal, None, None, None) .await .unwrap(); @@ -4646,7 +4646,7 @@ impl ApiTester { let (payload_type, metadata) = self .client - .get_validator_blocks_v3::(slot, &randao_reveal, None, None) + .get_validator_blocks_v3::(slot, &randao_reveal, None, None, None) .await .unwrap(); Self::check_block_v3_metadata(&metadata, &payload_type); @@ -4673,7 +4673,7 @@ impl ApiTester { let (payload_type, metadata) = self .client - .get_validator_blocks_v3::(slot, &randao_reveal, None, Some(0)) + .get_validator_blocks_v3::(slot, &randao_reveal, None, Some(0), None) .await .unwrap(); Self::check_block_v3_metadata(&metadata, &payload_type); @@ -4701,7 +4701,7 @@ impl ApiTester { let (payload_type, metadata) = self .client - .get_validator_blocks_v3::(slot, &randao_reveal, None, Some(u64::MAX)) + .get_validator_blocks_v3::(slot, &randao_reveal, None, Some(u64::MAX), None) .await .unwrap(); Self::check_block_v3_metadata(&metadata, &payload_type); @@ -4858,7 +4858,7 @@ impl ApiTester { let (payload_type, metadata) = self .client - .get_validator_blocks_v3::(slot, &randao_reveal, None, None) + .get_validator_blocks_v3::(slot, &randao_reveal, None, None, None) .await .unwrap(); Self::check_block_v3_metadata(&metadata, &payload_type); @@ -4939,7 +4939,7 @@ impl ApiTester { let (payload_type, metadata) = self .client - .get_validator_blocks_v3::(slot, &randao_reveal, None, None) + .get_validator_blocks_v3::(slot, &randao_reveal, None, None, None) .await .unwrap(); Self::check_block_v3_metadata(&metadata, &payload_type); @@ -5034,7 +5034,7 @@ impl ApiTester { let (payload_type, metadata) = self .client - .get_validator_blocks_v3::(slot, &randao_reveal, None, None) + .get_validator_blocks_v3::(slot, &randao_reveal, None, None, None) .await .unwrap(); Self::check_block_v3_metadata(&metadata, &payload_type); @@ -5125,7 +5125,7 @@ impl ApiTester { let (payload_type, metadata) = self .client - .get_validator_blocks_v3::(slot, &randao_reveal, None, None) + .get_validator_blocks_v3::(slot, &randao_reveal, None, None, None) .await .unwrap(); Self::check_block_v3_metadata(&metadata, &payload_type); @@ -5216,7 +5216,7 @@ impl ApiTester { let (payload_type, metadata) = self .client - .get_validator_blocks_v3::(slot, &randao_reveal, None, None) + .get_validator_blocks_v3::(slot, &randao_reveal, None, None, None) .await .unwrap(); Self::check_block_v3_metadata(&metadata, &payload_type); @@ -5305,7 +5305,7 @@ impl ApiTester { let (payload_type, metadata) = self .client - .get_validator_blocks_v3::(slot, &randao_reveal, None, None) + .get_validator_blocks_v3::(slot, &randao_reveal, None, None, None) .await .unwrap(); Self::check_block_v3_metadata(&metadata, &payload_type); @@ -5366,7 +5366,7 @@ impl ApiTester { let (payload_type, metadata) = self .client - .get_validator_blocks_v3::(slot, &randao_reveal, None, None) + .get_validator_blocks_v3::(slot, &randao_reveal, None, None, None) .await .unwrap(); Self::check_block_v3_metadata(&metadata, &payload_type); @@ -5437,7 +5437,7 @@ impl ApiTester { let (payload_type, metadata) = self .client - .get_validator_blocks_v3::(slot, &randao_reveal, None, None) + .get_validator_blocks_v3::(slot, &randao_reveal, None, None, None) .await .unwrap(); Self::check_block_v3_metadata(&metadata, &payload_type); @@ -5552,7 +5552,7 @@ impl ApiTester { let (payload_type, metadata) = self .client - .get_validator_blocks_v3::(next_slot, &randao_reveal, None, None) + .get_validator_blocks_v3::(next_slot, &randao_reveal, None, None, None) .await .unwrap(); Self::check_block_v3_metadata(&metadata, &payload_type); @@ -5573,7 +5573,7 @@ impl ApiTester { let (payload_type, metadata) = self .client - .get_validator_blocks_v3::(next_slot, &randao_reveal, None, None) + .get_validator_blocks_v3::(next_slot, &randao_reveal, None, None, None) .await .unwrap(); Self::check_block_v3_metadata(&metadata, &payload_type); @@ -5708,7 +5708,7 @@ impl ApiTester { let (payload_type, metadata) = self .client - .get_validator_blocks_v3::(next_slot, &randao_reveal, None, None) + .get_validator_blocks_v3::(next_slot, &randao_reveal, None, None, None) .await .unwrap(); Self::check_block_v3_metadata(&metadata, &payload_type); @@ -5739,7 +5739,7 @@ impl ApiTester { let (payload_type, metadata) = self .client - .get_validator_blocks_v3::(next_slot, &randao_reveal, None, None) + .get_validator_blocks_v3::(next_slot, &randao_reveal, None, None, None) .await .unwrap(); Self::check_block_v3_metadata(&metadata, &payload_type); @@ -5821,7 +5821,7 @@ impl ApiTester { let (payload_type, metadata) = self .client - .get_validator_blocks_v3::(slot, &randao_reveal, None, None) + .get_validator_blocks_v3::(slot, &randao_reveal, None, None, None) .await .unwrap(); Self::check_block_v3_metadata(&metadata, &payload_type); @@ -5895,7 +5895,7 @@ impl ApiTester { let (payload_type, metadata) = self .client - .get_validator_blocks_v3::(slot, &randao_reveal, None, None) + .get_validator_blocks_v3::(slot, &randao_reveal, None, None, None) .await .unwrap(); Self::check_block_v3_metadata(&metadata, &payload_type); @@ -5964,7 +5964,7 @@ impl ApiTester { let (payload_type, metadata) = self .client - .get_validator_blocks_v3::(slot, &randao_reveal, None, None) + .get_validator_blocks_v3::(slot, &randao_reveal, None, None, None) .await .unwrap(); Self::check_block_v3_metadata(&metadata, &payload_type); @@ -6033,7 +6033,7 @@ impl ApiTester { let (payload_type, metadata) = self .client - .get_validator_blocks_v3::(slot, &randao_reveal, None, None) + .get_validator_blocks_v3::(slot, &randao_reveal, None, None, None) .await .unwrap(); Self::check_block_v3_metadata(&metadata, &payload_type); @@ -6100,7 +6100,7 @@ impl ApiTester { let (payload_type, metadata) = self .client - .get_validator_blocks_v3::(slot, &randao_reveal, None, None) + .get_validator_blocks_v3::(slot, &randao_reveal, None, None, None) .await .unwrap(); Self::check_block_v3_metadata(&metadata, &payload_type); @@ -6174,7 +6174,7 @@ impl ApiTester { let (payload_type, metadata) = self .client - .get_validator_blocks_v3::(slot, &randao_reveal, None, None) + .get_validator_blocks_v3::(slot, &randao_reveal, None, None, None) .await .unwrap(); Self::check_block_v3_metadata(&metadata, &payload_type); @@ -6864,6 +6864,82 @@ impl ApiTester { } self } + + async fn get_validator_blocks_v3_path_graffiti_policy(self) -> Self { + let slot = self.chain.slot().unwrap(); + let epoch = self.chain.epoch().unwrap(); + let (_, randao_reveal) = self.get_test_randao(slot, epoch).await; + let graffiti = Some(Graffiti::from([0; GRAFFITI_BYTES_LEN])); + let builder_boost_factor = None; + + // Default case where GraffitiPolicy is None + let default_path = self + .client + .get_validator_blocks_v3_path( + slot, + &randao_reveal, + graffiti.as_ref(), + SkipRandaoVerification::Yes, + builder_boost_factor, + None, + ) + .await + .unwrap(); + + let query_default_path = default_path.query().unwrap_or(""); + // When GraffitiPolicy is None, the HTTP API query path should not contain "graffiti_policy" + assert!( + !query_default_path.contains("graffiti_policy"), + "URL should not contain graffiti_policy parameter (same as PreserveUserGraffiti). URL is: {}", + query_default_path + ); + + let preserve_path = self + .client + .get_validator_blocks_v3_path( + slot, + &randao_reveal, + graffiti.as_ref(), + SkipRandaoVerification::Yes, + builder_boost_factor, + Some(GraffitiPolicy::PreserveUserGraffiti), + ) + .await + .unwrap(); + + let query_preserve_path = preserve_path.query().unwrap_or(""); + // When GraffitiPolicy is set to PreserveUserGraffiti, the HTTP API query path should not contain "graffiti_policy" + assert!( + !query_preserve_path.contains("graffiti_policy"), + "URL should not contain graffiti_policy parameter when using PreserveUserGraffiti. URL is: {}", + query_preserve_path + ); + + // The HTTP API query path for PreserveUserGraffiti should be the same as the default + assert_eq!(query_default_path, query_preserve_path); + + let append_path = self + .client + .get_validator_blocks_v3_path( + slot, + &randao_reveal, + graffiti.as_ref(), + SkipRandaoVerification::No, + builder_boost_factor, + Some(GraffitiPolicy::AppendClientVersions), + ) + .await + .unwrap(); + + let query_append_path = append_path.query().unwrap_or(""); + // When GraffitiPolicy is AppendClientVersions, the HTTP API query path should contain "graffiti_policy" + assert!( + query_append_path.contains("graffiti_policy"), + "URL should contain graffiti_policy=AppendClientVersions parameter. URL is: {}", + query_append_path + ); + self + } } async fn poll_events, eth2::Error>> + Unpin, E: EthSpec>( @@ -8054,3 +8130,11 @@ async fn get_beacon_rewards_blocks_electra() { .test_beacon_block_rewards_electra() .await; } + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn get_validator_blocks_v3_http_api_path() { + ApiTester::new() + .await + .get_validator_blocks_v3_path_graffiti_policy() + .await; +} diff --git a/book/src/help_vc.md b/book/src/help_vc.md index b19ff0ba388..2a9936d1d2f 100644 --- a/book/src/help_vc.md +++ b/book/src/help_vc.md @@ -221,6 +221,10 @@ Flags: automatically enabled for <= 64 validators. Enabling this metric for higher validator counts will lead to higher volume of prometheus metrics being collected. + --graffiti-append + When used, client version info will be prepended to user custom + graffiti, with a space in between. This should only be used with a + Lighthouse beacon node. -h, --help Prints help information --http diff --git a/common/eth2/src/lib.rs b/common/eth2/src/lib.rs index 820d817d9d8..8746e3c063c 100644 --- a/common/eth2/src/lib.rs +++ b/common/eth2/src/lib.rs @@ -2207,6 +2207,7 @@ impl BeaconNodeHttpClient { graffiti: Option<&Graffiti>, skip_randao_verification: SkipRandaoVerification, builder_booster_factor: Option, + graffiti_policy: Option, ) -> Result { let mut path = self.eth_path(V3)?; @@ -2234,6 +2235,14 @@ impl BeaconNodeHttpClient { .append_pair("builder_boost_factor", &builder_booster_factor.to_string()); } + // Only append the HTTP URL request if the graffiti_policy is to AppendClientVersions + // If PreserveUserGraffiti (default), then the HTTP URL request does not contain graffiti_policy + // so that the default case is compliant to the spec + if let Some(GraffitiPolicy::AppendClientVersions) = graffiti_policy { + path.query_pairs_mut() + .append_pair("graffiti_policy", "AppendClientVersions"); + } + Ok(path) } @@ -2244,6 +2253,7 @@ impl BeaconNodeHttpClient { randao_reveal: &SignatureBytes, graffiti: Option<&Graffiti>, builder_booster_factor: Option, + graffiti_policy: Option, ) -> Result<(JsonProduceBlockV3Response, ProduceBlockV3Metadata), Error> { self.get_validator_blocks_v3_modular( slot, @@ -2251,6 +2261,7 @@ impl BeaconNodeHttpClient { graffiti, SkipRandaoVerification::No, builder_booster_factor, + graffiti_policy, ) .await } @@ -2263,6 +2274,7 @@ impl BeaconNodeHttpClient { graffiti: Option<&Graffiti>, skip_randao_verification: SkipRandaoVerification, builder_booster_factor: Option, + graffiti_policy: Option, ) -> Result<(JsonProduceBlockV3Response, ProduceBlockV3Metadata), Error> { let path = self .get_validator_blocks_v3_path( @@ -2271,6 +2283,7 @@ impl BeaconNodeHttpClient { graffiti, skip_randao_verification, builder_booster_factor, + graffiti_policy, ) .await?; @@ -2313,6 +2326,7 @@ impl BeaconNodeHttpClient { randao_reveal: &SignatureBytes, graffiti: Option<&Graffiti>, builder_booster_factor: Option, + graffiti_policy: Option, ) -> Result<(ProduceBlockV3Response, ProduceBlockV3Metadata), Error> { self.get_validator_blocks_v3_modular_ssz::( slot, @@ -2320,6 +2334,7 @@ impl BeaconNodeHttpClient { graffiti, SkipRandaoVerification::No, builder_booster_factor, + graffiti_policy, ) .await } @@ -2332,6 +2347,7 @@ impl BeaconNodeHttpClient { graffiti: Option<&Graffiti>, skip_randao_verification: SkipRandaoVerification, builder_booster_factor: Option, + graffiti_policy: Option, ) -> Result<(ProduceBlockV3Response, ProduceBlockV3Metadata), Error> { let path = self .get_validator_blocks_v3_path( @@ -2340,6 +2356,7 @@ impl BeaconNodeHttpClient { graffiti, skip_randao_verification, builder_booster_factor, + graffiti_policy, ) .await?; diff --git a/common/eth2/src/types.rs b/common/eth2/src/types.rs index aace8f936c9..b1a61ce00cc 100644 --- a/common/eth2/src/types.rs +++ b/common/eth2/src/types.rs @@ -752,12 +752,20 @@ pub struct ProposerData { pub slot: Slot, } +#[derive(Clone, Copy, Serialize, Deserialize, Default, Debug)] +pub enum GraffitiPolicy { + #[default] + PreserveUserGraffiti, + AppendClientVersions, +} + #[derive(Clone, Deserialize)] pub struct ValidatorBlocksQuery { pub randao_reveal: SignatureBytes, pub graffiti: Option, pub skip_randao_verification: SkipRandaoVerification, pub builder_boost_factor: Option, + pub graffiti_policy: Option, } #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize)] diff --git a/validator_client/src/cli.rs b/validator_client/src/cli.rs index 477781d3e88..3e1c46097f0 100644 --- a/validator_client/src/cli.rs +++ b/validator_client/src/cli.rs @@ -150,6 +150,16 @@ pub struct ValidatorClient { )] pub graffiti: Option, + #[clap( + long, + requires = "graffiti", + help = "When used, client version info will be prepended to user custom graffiti, with a space in between. \ + This should only be used with a Lighthouse beacon node.", + display_order = 0, + help_heading = FLAG_HEADER + )] + pub graffiti_append: bool, + #[clap( long, value_name = "GRAFFITI-FILE", diff --git a/validator_client/src/config.rs b/validator_client/src/config.rs index 04d69dc9dc1..1a286a74dc1 100644 --- a/validator_client/src/config.rs +++ b/validator_client/src/config.rs @@ -7,7 +7,7 @@ use directory::{ DEFAULT_HARDCODED_NETWORK, DEFAULT_ROOT_DIR, DEFAULT_SECRET_DIR, DEFAULT_VALIDATOR_DIR, get_network_dir, }; -use eth2::types::Graffiti; +use eth2::types::{Graffiti, GraffitiPolicy}; use graffiti_file::GraffitiFile; use initialized_validators::Config as InitializedValidatorsConfig; use lighthouse_validator_store::Config as ValidatorStoreConfig; @@ -55,6 +55,8 @@ pub struct Config { pub graffiti: Option, /// Graffiti file to load per validator graffitis. pub graffiti_file: Option, + /// GraffitiPolicy to append client version info + pub graffiti_policy: Option, /// Configuration for the HTTP REST API. pub http_api: validator_http_api::Config, /// Configuration for the HTTP REST API. @@ -119,6 +121,7 @@ impl Default for Config { long_timeouts_multiplier: 1, graffiti: None, graffiti_file: None, + graffiti_policy: None, http_api: <_>::default(), http_metrics: <_>::default(), beacon_node_fallback: <_>::default(), @@ -233,6 +236,12 @@ impl Config { } } + config.graffiti_policy = if validator_client_config.graffiti_append { + Some(GraffitiPolicy::AppendClientVersions) + } else { + Some(GraffitiPolicy::PreserveUserGraffiti) + }; + if let Some(input_fee_recipient) = validator_client_config.suggested_fee_recipient { config.validator_store.fee_recipient = Some(input_fee_recipient); } diff --git a/validator_client/src/lib.rs b/validator_client/src/lib.rs index 71bdde10b02..23541cf6e28 100644 --- a/validator_client/src/lib.rs +++ b/validator_client/src/lib.rs @@ -486,7 +486,8 @@ impl ProductionValidatorClient { .executor(context.executor.clone()) .chain_spec(context.eth2_config.spec.clone()) .graffiti(config.graffiti) - .graffiti_file(config.graffiti_file.clone()); + .graffiti_file(config.graffiti_file.clone()) + .graffiti_policy(config.graffiti_policy); // If we have proposer nodes, add them to the block service builder. if proposer_nodes_num > 0 { diff --git a/validator_client/validator_services/src/block_service.rs b/validator_client/validator_services/src/block_service.rs index 23658af03fd..625f8db7cb9 100644 --- a/validator_client/validator_services/src/block_service.rs +++ b/validator_client/validator_services/src/block_service.rs @@ -1,5 +1,6 @@ use beacon_node_fallback::{ApiTopic, BeaconNodeFallback, Error as FallbackError, Errors}; use bls::PublicKeyBytes; +use eth2::types::GraffitiPolicy; use eth2::{BeaconNodeHttpClient, StatusCode}; use graffiti_file::{GraffitiFile, determine_graffiti}; use logging::crit; @@ -50,6 +51,7 @@ pub struct BlockServiceBuilder { chain_spec: Option>, graffiti: Option, graffiti_file: Option, + graffiti_policy: Option, } impl BlockServiceBuilder { @@ -63,6 +65,7 @@ impl BlockServiceBuilder { chain_spec: None, graffiti: None, graffiti_file: None, + graffiti_policy: None, } } @@ -106,6 +109,11 @@ impl BlockServiceBuilder { self } + pub fn graffiti_policy(mut self, graffiti_policy: Option) -> Self { + self.graffiti_policy = graffiti_policy; + self + } + pub fn build(self) -> Result, String> { Ok(BlockService { inner: Arc::new(Inner { @@ -127,6 +135,7 @@ impl BlockServiceBuilder { proposer_nodes: self.proposer_nodes, graffiti: self.graffiti, graffiti_file: self.graffiti_file, + graffiti_policy: self.graffiti_policy, }), }) } @@ -192,6 +201,7 @@ pub struct Inner { chain_spec: Arc, graffiti: Option, graffiti_file: Option, + graffiti_policy: Option, } /// Attempts to produce attestations for any block producer(s) at the start of the epoch. @@ -466,6 +476,7 @@ impl BlockService { randao_reveal_ref, graffiti.as_ref(), builder_boost_factor, + self_ref.graffiti_policy, ) .await }) @@ -492,6 +503,7 @@ impl BlockService { randao_reveal_ref, graffiti.as_ref(), builder_boost_factor, + self_ref.graffiti_policy, ) .await .map_err(|e| { From a39e991557f6d16f0529eaca1b94de57ccc47f2f Mon Sep 17 00:00:00 2001 From: ethDreamer <37123614+ethDreamer@users.noreply.github.com> Date: Tue, 16 Dec 2025 00:45:45 -0600 Subject: [PATCH 2/4] Gloas(EIP-7732): Containers / Constants (#7923) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * #7850 This is the first round of the conga line! 🎉 Just spec constants and container changes so far. Co-Authored-By: shane-moore Co-Authored-By: Mark Mackey Co-Authored-By: Shane K Moore <41407272+shane-moore@users.noreply.github.com> Co-Authored-By: Eitan Seri- Levi Co-Authored-By: ethDreamer <37123614+ethDreamer@users.noreply.github.com> Co-Authored-By: Jimmy Chen Co-Authored-By: Jimmy Chen Co-Authored-By: Michael Sproul --- Makefile | 19 ++- .../beacon_chain/src/beacon_block_streamer.rs | 5 +- beacon_node/beacon_chain/src/beacon_chain.rs | 55 +------- beacon_node/beacon_chain/src/errors.rs | 2 + beacon_node/beacon_chain/src/test_utils.rs | 4 +- beacon_node/beacon_chain/tests/store_tests.rs | 6 +- beacon_node/execution_layer/src/engine_api.rs | 28 ---- .../src/engine_api/new_payload_request.rs | 17 +-- beacon_node/execution_layer/src/lib.rs | 16 +-- .../test_utils/execution_block_generator.rs | 8 +- .../src/test_utils/mock_builder.rs | 48 ++----- .../http_api/tests/interactive_tests.rs | 10 +- beacon_node/http_api/tests/status_tests.rs | 6 +- .../src/network_beacon_processor/tests.rs | 3 +- beacon_node/store/src/partial_beacon_state.rs | 31 ++++- consensus/state_processing/src/genesis.rs | 5 +- .../src/per_block_processing.rs | 13 +- .../state_processing/src/upgrade/gloas.rs | 21 ++- .../indexed_payload_attestation.rs | 36 +++++ consensus/types/src/attestation/mod.rs | 8 ++ .../src/attestation/payload_attestation.rs | 31 +++++ .../attestation/payload_attestation_data.rs | 28 ++++ .../payload_attestation_message.rs | 26 ++++ consensus/types/src/block/beacon_block.rs | 33 ++++- .../types/src/block/beacon_block_body.rs | 130 ++++++++++-------- .../types/src/block/signed_beacon_block.rs | 82 +++-------- consensus/types/src/builder/builder_bid.rs | 19 +-- .../src/builder/builder_pending_payment.rs | 36 +++++ .../src/builder/builder_pending_withdrawal.rs | 40 ++++++ consensus/types/src/builder/mod.rs | 6 +- consensus/types/src/core/chain_spec.rs | 21 +++ consensus/types/src/core/eth_spec.rs | 42 +++++- consensus/types/src/execution/dumb_macros.rs | 108 +++++++++++++++ .../types/src/execution/execution_payload.rs | 5 +- .../src/execution/execution_payload_bid.rs | 40 ++++++ .../execution/execution_payload_envelope.rs | 36 +++++ .../src/execution/execution_payload_header.rs | 104 +++----------- consensus/types/src/execution/mod.rs | 19 ++- consensus/types/src/execution/payload.rs | 53 ++----- .../execution/signed_execution_payload_bid.rs | 35 +++++ .../signed_execution_payload_envelope.rs | 24 ++++ consensus/types/src/fork/fork_context.rs | 2 + consensus/types/src/lib.rs | 10 ++ consensus/types/src/light_client/error.rs | 1 + .../light_client/light_client_bootstrap.rs | 45 ++---- .../light_client_finality_update.rs | 45 ++---- .../src/light_client/light_client_header.rs | 78 ++--------- .../light_client_optimistic_update.rs | 35 ++--- .../src/light_client/light_client_update.rs | 69 ++-------- consensus/types/src/light_client/mod.rs | 10 +- consensus/types/src/state/beacon_state.rs | 62 +++++++-- testing/ef_tests/src/cases/operations.rs | 3 + 52 files changed, 930 insertions(+), 689 deletions(-) create mode 100644 consensus/types/src/attestation/indexed_payload_attestation.rs create mode 100644 consensus/types/src/attestation/payload_attestation.rs create mode 100644 consensus/types/src/attestation/payload_attestation_data.rs create mode 100644 consensus/types/src/attestation/payload_attestation_message.rs create mode 100644 consensus/types/src/builder/builder_pending_payment.rs create mode 100644 consensus/types/src/builder/builder_pending_withdrawal.rs create mode 100644 consensus/types/src/execution/dumb_macros.rs create mode 100644 consensus/types/src/execution/execution_payload_bid.rs create mode 100644 consensus/types/src/execution/execution_payload_envelope.rs create mode 100644 consensus/types/src/execution/signed_execution_payload_bid.rs create mode 100644 consensus/types/src/execution/signed_execution_payload_envelope.rs diff --git a/Makefile b/Makefile index c14f1d712a7..c1cccb92705 100644 --- a/Makefile +++ b/Makefile @@ -30,6 +30,11 @@ TEST_FEATURES ?= # Cargo profile for regular builds. PROFILE ?= release +# List of all hard forks up to gloas. This list is used to set env variables for several tests so that +# they run for different forks. +# TODO(EIP-7732) Remove this once we extend network tests to support gloas and use RECENT_FORKS instead +RECENT_FORKS_BEFORE_GLOAS=electra fulu + # List of all recent hard forks. This list is used to set env variables for http_api tests RECENT_FORKS=electra fulu gloas @@ -197,29 +202,31 @@ run-ef-tests: cargo nextest run --release -p ef_tests --features "ef_tests,$(EF_TEST_FEATURES),fake_crypto" ./$(EF_TESTS)/check_all_files_accessed.py $(EF_TESTS)/.accessed_file_log.txt $(EF_TESTS)/consensus-spec-tests -# Run the tests in the `beacon_chain` crate for recent forks. -test-beacon-chain: $(patsubst %,test-beacon-chain-%,$(RECENT_FORKS)) +# Run the tests in the `beacon_chain` crate for all known forks. +# TODO(EIP-7732) Extend to support gloas by using RECENT_FORKS instead +test-beacon-chain: $(patsubst %,test-beacon-chain-%,$(RECENT_FORKS_BEFORE_GLOAS)) test-beacon-chain-%: env FORK_NAME=$* cargo nextest run --release --features "fork_from_env,slasher/lmdb,$(TEST_FEATURES)" -p beacon_chain # Run the tests in the `http_api` crate for recent forks. -test-http-api: $(patsubst %,test-http-api-%,$(RECENT_FORKS)) +test-http-api: $(patsubst %,test-http-api-%,$(RECENT_FORKS_BEFORE_GLOAS)) test-http-api-%: env FORK_NAME=$* cargo nextest run --release --features "beacon_chain/fork_from_env" -p http_api # Run the tests in the `operation_pool` crate for all known forks. -test-op-pool: $(patsubst %,test-op-pool-%,$(RECENT_FORKS)) +test-op-pool: $(patsubst %,test-op-pool-%,$(RECENT_FORKS_BEFORE_GLOAS)) test-op-pool-%: env FORK_NAME=$* cargo nextest run --release \ --features "beacon_chain/fork_from_env,$(TEST_FEATURES)"\ -p operation_pool -# Run the tests in the `network` crate for recent forks. -test-network: $(patsubst %,test-network-%,$(RECENT_FORKS)) +# Run the tests in the `network` crate for all known forks. +# TODO(EIP-7732) Extend to support gloas by using RECENT_FORKS instead +test-network: $(patsubst %,test-network-%,$(RECENT_FORKS_BEFORE_GLOAS)) test-network-%: env FORK_NAME=$* cargo nextest run --release \ diff --git a/beacon_node/beacon_chain/src/beacon_block_streamer.rs b/beacon_node/beacon_chain/src/beacon_block_streamer.rs index 7b3bb03e568..a462376cc03 100644 --- a/beacon_node/beacon_chain/src/beacon_block_streamer.rs +++ b/beacon_node/beacon_chain/src/beacon_block_streamer.rs @@ -715,8 +715,9 @@ mod tests { harness } + // TODO(EIP-7732) Extend this test for gloas #[tokio::test] - async fn check_all_blocks_from_altair_to_gloas() { + async fn check_all_blocks_from_altair_to_fulu() { let slots_per_epoch = MinimalEthSpec::slots_per_epoch() as usize; let num_epochs = 12; let bellatrix_fork_epoch = 2usize; @@ -724,7 +725,6 @@ mod tests { let deneb_fork_epoch = 6usize; let electra_fork_epoch = 8usize; let fulu_fork_epoch = 10usize; - let gloas_fork_epoch = 12usize; let num_blocks_produced = num_epochs * slots_per_epoch; let mut spec = test_spec::(); @@ -734,7 +734,6 @@ mod tests { spec.deneb_fork_epoch = Some(Epoch::new(deneb_fork_epoch as u64)); spec.electra_fork_epoch = Some(Epoch::new(electra_fork_epoch as u64)); spec.fulu_fork_epoch = Some(Epoch::new(fulu_fork_epoch as u64)); - spec.gloas_fork_epoch = Some(Epoch::new(gloas_fork_epoch as u64)); let spec = Arc::new(spec); let harness = get_harness(VALIDATOR_COUNT, spec.clone()); diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index 2107f06e1e3..46ba14f596b 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -5795,60 +5795,7 @@ impl BeaconChain { execution_payload_value, ) } - BeaconState::Gloas(_) => { - let ( - payload, - kzg_commitments, - maybe_blobs_and_proofs, - maybe_requests, - execution_payload_value, - ) = block_contents - .ok_or(BlockProductionError::MissingExecutionPayload)? - .deconstruct(); - - ( - BeaconBlock::Gloas(BeaconBlockGloas { - slot, - proposer_index, - parent_root, - state_root: Hash256::zero(), - body: BeaconBlockBodyGloas { - randao_reveal, - eth1_data, - graffiti, - proposer_slashings: proposer_slashings - .try_into() - .map_err(BlockProductionError::SszTypesError)?, - attester_slashings: attester_slashings_electra - .try_into() - .map_err(BlockProductionError::SszTypesError)?, - attestations: attestations_electra - .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: sync_aggregate - .ok_or(BlockProductionError::MissingSyncAggregate)?, - execution_payload: payload - .try_into() - .map_err(|_| BlockProductionError::InvalidPayloadFork)?, - bls_to_execution_changes: bls_to_execution_changes - .try_into() - .map_err(BlockProductionError::SszTypesError)?, - blob_kzg_commitments: kzg_commitments - .ok_or(BlockProductionError::InvalidPayloadFork)?, - execution_requests: maybe_requests - .ok_or(BlockProductionError::MissingExecutionRequests)?, - }, - }), - maybe_blobs_and_proofs, - execution_payload_value, - ) - } + BeaconState::Gloas(_) => return Err(BlockProductionError::GloasNotImplemented), }; let block = SignedBeaconBlock::from_block( diff --git a/beacon_node/beacon_chain/src/errors.rs b/beacon_node/beacon_chain/src/errors.rs index 8f615baab46..b021df2c33b 100644 --- a/beacon_node/beacon_chain/src/errors.rs +++ b/beacon_node/beacon_chain/src/errors.rs @@ -320,6 +320,8 @@ pub enum BlockProductionError { FailedToBuildBlobSidecars(String), MissingExecutionRequests, SszTypesError(ssz_types::Error), + // TODO(gloas): Remove this once Gloas is implemented + GloasNotImplemented, } easy_from_to!(BlockProcessingError, BlockProductionError); diff --git a/beacon_node/beacon_chain/src/test_utils.rs b/beacon_node/beacon_chain/src/test_utils.rs index 500c0b22d61..6d17d6d85c5 100644 --- a/beacon_node/beacon_chain/src/test_utils.rs +++ b/beacon_node/beacon_chain/src/test_utils.rs @@ -3300,9 +3300,7 @@ pub fn generate_rand_block_and_blobs( SignedBeaconBlock::Fulu(SignedBeaconBlockFulu { ref mut message, .. }) => add_blob_transactions!(message, FullPayloadFulu, num_blobs, rng, fork_name), - SignedBeaconBlock::Gloas(SignedBeaconBlockGloas { - ref mut message, .. - }) => add_blob_transactions!(message, FullPayloadGloas, num_blobs, rng, fork_name), + // TODO(EIP-7732) Add `SignedBeaconBlock::Gloas` variant _ => return (block, blob_sidecars), }; diff --git a/beacon_node/beacon_chain/tests/store_tests.rs b/beacon_node/beacon_chain/tests/store_tests.rs index cc49f871843..ba0621ae720 100644 --- a/beacon_node/beacon_chain/tests/store_tests.rs +++ b/beacon_node/beacon_chain/tests/store_tests.rs @@ -159,6 +159,7 @@ fn get_states_descendant_of_block( .collect() } +// TODO(EIP-7732) Extend to support gloas #[tokio::test] async fn light_client_bootstrap_test() { let spec = test_spec::(); @@ -206,7 +207,6 @@ async fn light_client_bootstrap_test() { LightClientBootstrap::Deneb(lc_bootstrap) => lc_bootstrap.header.beacon.slot, LightClientBootstrap::Electra(lc_bootstrap) => lc_bootstrap.header.beacon.slot, LightClientBootstrap::Fulu(lc_bootstrap) => lc_bootstrap.header.beacon.slot, - LightClientBootstrap::Gloas(lc_bootstrap) => lc_bootstrap.header.beacon.slot, }; assert_eq!( @@ -1581,6 +1581,10 @@ async fn proposer_duties_from_head_fulu() { } /// Test that we can compute the proposer shuffling for the Gloas fork epoch itself using lookahead! +// TODO(EIP-7732): Extend to gloas +// `state.latest_execution_payload_header()` not available in Gloas +// called from `add_block_at_slot` -> `make_block` -> `produce_block_on_state` -> `produce_partial_beacon_block` -> `get_execution_payload` -> `Error` +#[ignore] #[tokio::test] async fn proposer_lookahead_gloas_fork_epoch() { let gloas_fork_epoch = Epoch::new(4); diff --git a/beacon_node/execution_layer/src/engine_api.rs b/beacon_node/execution_layer/src/engine_api.rs index 88567ac6e12..32090bccfc9 100644 --- a/beacon_node/execution_layer/src/engine_api.rs +++ b/beacon_node/execution_layer/src/engine_api.rs @@ -541,34 +541,6 @@ impl ExecutionPayloadBodyV1 { )) } } - ExecutionPayloadHeader::Gloas(header) => { - if let Some(withdrawals) = self.withdrawals { - Ok(ExecutionPayload::Gloas(ExecutionPayloadGloas { - parent_hash: header.parent_hash, - fee_recipient: header.fee_recipient, - state_root: header.state_root, - receipts_root: header.receipts_root, - logs_bloom: header.logs_bloom, - prev_randao: header.prev_randao, - block_number: header.block_number, - gas_limit: header.gas_limit, - gas_used: header.gas_used, - timestamp: header.timestamp, - extra_data: header.extra_data, - base_fee_per_gas: header.base_fee_per_gas, - block_hash: header.block_hash, - transactions: self.transactions, - withdrawals, - blob_gas_used: header.blob_gas_used, - excess_blob_gas: header.excess_blob_gas, - })) - } else { - Err(format!( - "block {} is post capella but payload body doesn't have withdrawals", - header.block_hash - )) - } - } } } } 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 617d2e01129..ba94296b859 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 @@ -172,6 +172,7 @@ impl<'block, E: EthSpec> NewPayloadRequest<'block, E> { } } +//TODO(EIP7732): Consider implementing these as methods on the NewPayloadRequest struct impl<'a, E: EthSpec> TryFrom> for NewPayloadRequest<'a, E> { type Error = BeaconStateError; @@ -220,17 +221,7 @@ impl<'a, E: EthSpec> TryFrom> for NewPayloadRequest<'a, E> parent_beacon_block_root: block_ref.parent_root, execution_requests: &block_ref.body.execution_requests, })), - BeaconBlockRef::Gloas(block_ref) => Ok(Self::Gloas(NewPayloadRequestGloas { - execution_payload: &block_ref.body.execution_payload.execution_payload, - versioned_hashes: block_ref - .body - .blob_kzg_commitments - .iter() - .map(kzg_commitment_to_versioned_hash) - .collect(), - parent_beacon_block_root: block_ref.parent_root, - execution_requests: &block_ref.body.execution_requests, - })), + BeaconBlockRef::Gloas(_) => Err(Self::Error::IncorrectStateVariant), } } } @@ -251,11 +242,15 @@ impl<'a, E: EthSpec> TryFrom> for NewPayloadRequest<' ExecutionPayloadRef::Deneb(_) => Err(Self::Error::IncorrectStateVariant), ExecutionPayloadRef::Electra(_) => Err(Self::Error::IncorrectStateVariant), ExecutionPayloadRef::Fulu(_) => Err(Self::Error::IncorrectStateVariant), + //TODO(EIP7732): Probably time to just get rid of this ExecutionPayloadRef::Gloas(_) => Err(Self::Error::IncorrectStateVariant), } } } +// TODO(EIP-7732) build out the following when it's needed like in Mark's branch +// impl<'a, E: EthSpec> TryFrom> for NewPayloadRequest { + #[cfg(test)] mod test { use crate::versioned_hashes::Error as VersionedHashError; diff --git a/beacon_node/execution_layer/src/lib.rs b/beacon_node/execution_layer/src/lib.rs index 34b1832894e..554668dd8a7 100644 --- a/beacon_node/execution_layer/src/lib.rs +++ b/beacon_node/execution_layer/src/lib.rs @@ -55,8 +55,8 @@ use types::{ }; use types::{ BeaconStateError, BlindedPayload, ChainSpec, Epoch, ExecPayload, ExecutionPayloadBellatrix, - ExecutionPayloadCapella, ExecutionPayloadElectra, ExecutionPayloadFulu, ExecutionPayloadGloas, - FullPayload, ProposerPreparationData, Slot, + ExecutionPayloadCapella, ExecutionPayloadElectra, ExecutionPayloadFulu, FullPayload, + ProposerPreparationData, Slot, }; mod block_hash; @@ -131,13 +131,6 @@ impl TryFrom> for ProvenancedPayload BlockProposalContents::PayloadAndBlobs { - payload: ExecutionPayloadHeader::Gloas(builder_bid.header).into(), - block_value: builder_bid.value, - kzg_commitments: builder_bid.blob_kzg_commitments, - blobs_and_proofs: None, - requests: Some(builder_bid.execution_requests), - }, }; Ok(ProvenancedPayload::Builder( BlockProposalContentsType::Blinded(block_proposal_contents), @@ -1368,6 +1361,7 @@ impl ExecutionLayer { } /// Maps to the `engine_newPayload` JSON-RPC call. + /// TODO(EIP-7732) figure out how and why Mark relaxed new_payload_request param's typ to NewPayloadRequest pub async fn notify_new_payload( &self, new_payload_request: NewPayloadRequest<'_, E>, @@ -1839,10 +1833,12 @@ impl ExecutionLayer { ForkName::Deneb => ExecutionPayloadDeneb::default().into(), ForkName::Electra => ExecutionPayloadElectra::default().into(), ForkName::Fulu => ExecutionPayloadFulu::default().into(), - ForkName::Gloas => ExecutionPayloadGloas::default().into(), ForkName::Base | ForkName::Altair => { return Err(Error::InvalidForkForPayload); } + ForkName::Gloas => { + 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 89d2994ce28..6b247a4cd49 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 @@ -909,12 +909,8 @@ pub fn generate_genesis_header( *header.transactions_root_mut() = empty_transactions_root; Some(header) } - ForkName::Gloas => { - let mut header = ExecutionPayloadHeader::Gloas(<_>::default()); - *header.block_hash_mut() = genesis_block_hash.unwrap_or_default(); - *header.transactions_root_mut() = empty_transactions_root; - Some(header) - } + // TODO(EIP-7732): need to look into this + ForkName::Gloas => None, } } 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 1d4f36b62c5..884aa9bf47a 100644 --- a/beacon_node/execution_layer/src/test_utils/mock_builder.rs +++ b/beacon_node/execution_layer/src/test_utils/mock_builder.rs @@ -31,7 +31,7 @@ use tree_hash::TreeHash; use types::ExecutionBlockHash; use types::builder_bid::{ BuilderBid, BuilderBidBellatrix, BuilderBidCapella, BuilderBidDeneb, BuilderBidElectra, - BuilderBidFulu, BuilderBidGloas, SignedBuilderBid, + BuilderBidFulu, SignedBuilderBid, }; use types::{ Address, BeaconState, ChainSpec, Epoch, EthSpec, ExecPayload, ExecutionPayload, @@ -117,9 +117,6 @@ impl BidStuff for BuilderBid { ExecutionPayloadHeaderRefMut::Fulu(header) => { header.fee_recipient = fee_recipient; } - ExecutionPayloadHeaderRefMut::Gloas(header) => { - header.fee_recipient = fee_recipient; - } } } @@ -140,9 +137,6 @@ impl BidStuff for BuilderBid { ExecutionPayloadHeaderRefMut::Fulu(header) => { header.gas_limit = gas_limit; } - ExecutionPayloadHeaderRefMut::Gloas(header) => { - header.gas_limit = gas_limit; - } } } @@ -167,9 +161,6 @@ impl BidStuff for BuilderBid { ExecutionPayloadHeaderRefMut::Fulu(header) => { header.parent_hash = ExecutionBlockHash::from_root(parent_hash); } - ExecutionPayloadHeaderRefMut::Gloas(header) => { - header.parent_hash = ExecutionBlockHash::from_root(parent_hash); - } } } @@ -190,9 +181,6 @@ impl BidStuff for BuilderBid { ExecutionPayloadHeaderRefMut::Fulu(header) => { header.prev_randao = prev_randao; } - ExecutionPayloadHeaderRefMut::Gloas(header) => { - header.prev_randao = prev_randao; - } } } @@ -213,9 +201,6 @@ impl BidStuff for BuilderBid { ExecutionPayloadHeaderRefMut::Fulu(header) => { header.block_number = block_number; } - ExecutionPayloadHeaderRefMut::Gloas(header) => { - header.block_number = block_number; - } } } @@ -236,9 +221,6 @@ impl BidStuff for BuilderBid { ExecutionPayloadHeaderRefMut::Fulu(header) => { header.timestamp = timestamp; } - ExecutionPayloadHeaderRefMut::Gloas(header) => { - header.timestamp = timestamp; - } } } @@ -259,9 +241,6 @@ impl BidStuff for BuilderBid { ExecutionPayloadHeaderRefMut::Fulu(header) => { header.withdrawals_root = withdrawals_root; } - ExecutionPayloadHeaderRefMut::Gloas(header) => { - header.withdrawals_root = withdrawals_root; - } } } @@ -295,10 +274,6 @@ impl BidStuff for BuilderBid { header.extra_data = extra_data; header.block_hash = ExecutionBlockHash::from_root(header.tree_hash_root()); } - ExecutionPayloadHeaderRefMut::Gloas(header) => { - header.extra_data = extra_data; - header.block_hash = ExecutionBlockHash::from_root(header.tree_hash_root()); - } } } } @@ -496,8 +471,9 @@ impl MockBuilder { SignedBlindedBeaconBlock::Fulu(block) => { block.message.body.execution_payload.tree_hash_root() } - SignedBlindedBeaconBlock::Gloas(block) => { - block.message.body.execution_payload.tree_hash_root() + SignedBlindedBeaconBlock::Gloas(_) => { + // TODO(EIP7732) Check if this is how we want to do error handling for gloas + return Err("invalid fork".to_string()); } }; let block_hash = block @@ -613,18 +589,10 @@ impl MockBuilder { ) = payload_response.into(); match fork { - ForkName::Gloas => BuilderBid::Gloas(BuilderBidGloas { - header: payload - .as_gloas() - .map_err(|_| "incorrect payload variant".to_string())? - .into(), - blob_kzg_commitments: maybe_blobs_bundle - .map(|b| b.commitments.clone()) - .unwrap_or_default(), - value: self.get_bid_value(value), - pubkey: self.builder_sk.public_key().compress(), - execution_requests: maybe_requests.unwrap_or_default(), - }), + ForkName::Gloas => { + // TODO(EIP7732) Check if this is how we want to do error handling for gloas + return Err("invalid fork".to_string()); + } ForkName::Fulu => BuilderBid::Fulu(BuilderBidFulu { header: payload .as_fulu() diff --git a/beacon_node/http_api/tests/interactive_tests.rs b/beacon_node/http_api/tests/interactive_tests.rs index ce61c821b57..b04c812773a 100644 --- a/beacon_node/http_api/tests/interactive_tests.rs +++ b/beacon_node/http_api/tests/interactive_tests.rs @@ -61,7 +61,10 @@ async fn state_by_root_pruned_from_fork_choice() { type E = MinimalEthSpec; let validator_count = 24; - let spec = ForkName::latest().make_genesis_spec(E::default_spec()); + // TODO(EIP-7732): extend test for Gloas by reverting back to using `ForkName::latest()` + // Issue is that this test does block production via `extend_chain_with_sync` which expects to be able to use `state.latest_execution_payload_header` during block production, but Gloas uses `latest_execution_bid` instead + // This will be resolved in a subsequent block processing PR + let spec = ForkName::Fulu.make_genesis_spec(E::default_spec()); let tester = InteractiveTester::::new_with_initializer_and_mutator( Some(spec.clone()), @@ -401,7 +404,10 @@ pub async fn proposer_boost_re_org_test( assert!(head_slot > 0); // Test using the latest fork so that we simulate conditions as similar to mainnet as possible. - let mut spec = ForkName::latest().make_genesis_spec(E::default_spec()); + // TODO(EIP-7732): extend test for Gloas by reverting back to using `ForkName::latest()` + // Issue is that `get_validator_blocks_v3` below expects to be able to use `state.latest_execution_payload_header` during `produce_block_on_state` -> `produce_partial_beacon_block` -> `get_execution_payload`, but gloas will no longer support this state field + // This will be resolved in a subsequent block processing PR + let mut spec = ForkName::Fulu.make_genesis_spec(E::default_spec()); spec.terminal_total_difficulty = Uint256::from(1); // Ensure there are enough validators to have `attesters_per_slot`. diff --git a/beacon_node/http_api/tests/status_tests.rs b/beacon_node/http_api/tests/status_tests.rs index fd5e282c5bd..556b75cb85a 100644 --- a/beacon_node/http_api/tests/status_tests.rs +++ b/beacon_node/http_api/tests/status_tests.rs @@ -12,8 +12,10 @@ type E = MinimalEthSpec; /// Create a new test environment that is post-merge with `chain_depth` blocks. async fn post_merge_tester(chain_depth: u64, validator_count: u64) -> InteractiveTester { - // Test using latest fork so that we simulate conditions as similar to mainnet as possible. - let mut spec = ForkName::latest().make_genesis_spec(E::default_spec()); + // TODO(EIP-7732): extend tests for Gloas by reverting back to using `ForkName::latest()` + // Issue is that these tests do block production via `extend_chain_with_sync` which expects to be able to use `state.latest_execution_payload_header` during block production, but Gloas uses `latest_execution_bid` instead + // This will be resolved in a subsequent block processing PR + let mut spec = ForkName::Fulu.make_genesis_spec(E::default_spec()); spec.terminal_total_difficulty = Uint256::from(1); let tester = InteractiveTester::::new(Some(spec), validator_count as usize).await; diff --git a/beacon_node/network/src/network_beacon_processor/tests.rs b/beacon_node/network/src/network_beacon_processor/tests.rs index 841a8679cfd..ed04fe7bb97 100644 --- a/beacon_node/network/src/network_beacon_processor/tests.rs +++ b/beacon_node/network/src/network_beacon_processor/tests.rs @@ -1699,8 +1699,9 @@ async fn test_blobs_by_range_spans_fulu_fork() { spec.fulu_fork_epoch = Some(Epoch::new(1)); spec.gloas_fork_epoch = Some(Epoch::new(2)); + // This test focuses on Electra→Fulu blob counts (epoch 0 to 1). Build 62 blocks since no need for Gloas activation at slot 64. let mut rig = TestRig::new_parametric( - 64, + 62, BeaconProcessorConfig::default(), NodeCustodyType::Fullnode, spec, diff --git a/beacon_node/store/src/partial_beacon_state.rs b/beacon_node/store/src/partial_beacon_state.rs index 8ee37169aca..9e5e1ebbb47 100644 --- a/beacon_node/store/src/partial_beacon_state.rs +++ b/beacon_node/store/src/partial_beacon_state.rs @@ -116,11 +116,12 @@ where partial_getter(rename = "latest_execution_payload_header_fulu") )] pub latest_execution_payload_header: ExecutionPayloadHeaderFulu, + #[superstruct( only(Gloas), - partial_getter(rename = "latest_execution_payload_header_gloas") + partial_getter(rename = "latest_execution_payload_bid_gloas") )] - pub latest_execution_payload_header: ExecutionPayloadHeaderGloas, + pub latest_execution_payload_bid: ExecutionPayloadBid, // Capella #[superstruct(only(Capella, Deneb, Electra, Fulu, Gloas))] @@ -155,6 +156,23 @@ where pub pending_consolidations: List, #[superstruct(only(Fulu, Gloas))] pub proposer_lookahead: Vector, + + // Gloas + #[superstruct(only(Gloas))] + pub execution_payload_availability: BitVector, + + #[superstruct(only(Gloas))] + pub builder_pending_payments: Vector, + + #[superstruct(only(Gloas))] + pub builder_pending_withdrawals: + List, + + #[superstruct(only(Gloas))] + pub latest_block_hash: ExecutionBlockHash, + + #[superstruct(only(Gloas))] + pub latest_withdrawals_root: Hash256, } impl PartialBeaconState { @@ -466,7 +484,7 @@ impl TryInto> for PartialBeaconState { current_sync_committee, next_sync_committee, inactivity_scores, - latest_execution_payload_header, + latest_execution_payload_bid, next_withdrawal_index, next_withdrawal_validator_index, deposit_requests_start_index, @@ -478,7 +496,12 @@ impl TryInto> for PartialBeaconState { pending_deposits, pending_partial_withdrawals, pending_consolidations, - proposer_lookahead + proposer_lookahead, + execution_payload_availability, + builder_pending_payments, + builder_pending_withdrawals, + latest_block_hash, + latest_withdrawals_root ], [historical_summaries] ), diff --git a/consensus/state_processing/src/genesis.rs b/consensus/state_processing/src/genesis.rs index d00e1fcfacc..1575fce22f2 100644 --- a/consensus/state_processing/src/genesis.rs +++ b/consensus/state_processing/src/genesis.rs @@ -168,9 +168,8 @@ pub fn initialize_beacon_state_from_eth1( state.fork_mut().previous_version = spec.gloas_fork_version; // Override latest execution payload header. - if let Some(ExecutionPayloadHeader::Gloas(header)) = execution_payload_header { - *state.latest_execution_payload_header_gloas_mut()? = header.clone(); - } + // Here's where we *would* clone the header but there is no header here so.. + // TODO(EIP7732): check this } // Now that we have our validators, initialize the caches (including the committees) diff --git a/consensus/state_processing/src/per_block_processing.rs b/consensus/state_processing/src/per_block_processing.rs index f78c8c4eb38..07149ff2ee8 100644 --- a/consensus/state_processing/src/per_block_processing.rs +++ b/consensus/state_processing/src/per_block_processing.rs @@ -41,7 +41,6 @@ mod verify_exit; mod verify_proposer_slashing; use crate::common::decrease_balance; - use crate::common::update_progressive_balances_cache::{ initialize_progressive_balances_cache, update_progressive_balances_metrics, }; @@ -173,10 +172,14 @@ pub fn per_block_processing>( // previous block. if is_execution_enabled(state, block.body()) { let body = block.body(); + // TODO(EIP-7732): build out process_withdrawals variant for gloas process_withdrawals::(state, body.execution_payload()?, spec)?; process_execution_payload::(state, body, spec)?; } + // TODO(EIP-7732): build out process_execution_bid + // process_execution_bid(state, block, verify_signatures, spec)?; + process_randao(state, block, verify_randao, ctxt, spec)?; process_eth1_data(state, block.body().eth1_data())?; process_operations(state, block.body(), verify_signatures, ctxt, spec)?; @@ -453,12 +456,6 @@ pub fn process_execution_payload>( _ => return Err(BlockProcessingError::IncorrectStateType), } } - ExecutionPayloadHeaderRefMut::Gloas(header_mut) => { - match payload.to_execution_payload_header() { - ExecutionPayloadHeader::Gloas(header) => *header_mut = header, - _ => return Err(BlockProcessingError::IncorrectStateType), - } - } } Ok(()) @@ -470,6 +467,7 @@ pub fn process_execution_payload>( /// repeatedly write code to treat these errors as false. /// https://github.com/ethereum/consensus-specs/blob/dev/specs/bellatrix/beacon-chain.md#is_merge_transition_complete pub fn is_merge_transition_complete(state: &BeaconState) -> bool { + // TODO(EIP7732): check this cause potuz modified this function for god knows what reason if state.fork_name_unchecked().capella_enabled() { true } else if state.fork_name_unchecked().bellatrix_enabled() { @@ -638,6 +636,7 @@ pub fn get_expected_withdrawals( } /// Apply withdrawals to the state. +/// TODO(EIP-7732): abstract this out and create gloas variant pub fn process_withdrawals>( state: &mut BeaconState, payload: Payload::Ref<'_>, diff --git a/consensus/state_processing/src/upgrade/gloas.rs b/consensus/state_processing/src/upgrade/gloas.rs index 8bb6991bfbe..d6c353cc2a9 100644 --- a/consensus/state_processing/src/upgrade/gloas.rs +++ b/consensus/state_processing/src/upgrade/gloas.rs @@ -1,5 +1,11 @@ +use bls::Hash256; +use milhouse::{List, Vector}; +use ssz_types::BitVector; use std::mem; -use types::{BeaconState, BeaconStateError as Error, BeaconStateGloas, ChainSpec, EthSpec, Fork}; +use types::{ + BeaconState, BeaconStateError as Error, BeaconStateGloas, BuilderPendingPayment, ChainSpec, + EthSpec, ExecutionPayloadBid, Fork, +}; /// Transform a `Fulu` state into a `Gloas` state. pub fn upgrade_to_gloas( @@ -63,8 +69,8 @@ pub fn upgrade_state_to_gloas( // Sync committees current_sync_committee: pre.current_sync_committee.clone(), next_sync_committee: pre.next_sync_committee.clone(), - // Execution - latest_execution_payload_header: pre.latest_execution_payload_header.upgrade_to_gloas(), + // Execution Bid + latest_execution_payload_bid: ExecutionPayloadBid::default(), // Capella next_withdrawal_index: pre.next_withdrawal_index, next_withdrawal_validator_index: pre.next_withdrawal_validator_index, @@ -79,6 +85,15 @@ pub fn upgrade_state_to_gloas( pending_deposits: pre.pending_deposits.clone(), pending_partial_withdrawals: pre.pending_partial_withdrawals.clone(), pending_consolidations: pre.pending_consolidations.clone(), + // Gloas + execution_payload_availability: BitVector::default(), // All bits set to false initially + builder_pending_payments: Vector::new(vec![ + BuilderPendingPayment::default(); + E::builder_pending_payments_limit() + ])?, + builder_pending_withdrawals: List::default(), // Empty list initially, + latest_block_hash: pre.latest_execution_payload_header.block_hash, + latest_withdrawals_root: Hash256::default(), // Caches total_active_balance: pre.total_active_balance, progressive_balances_cache: mem::take(&mut pre.progressive_balances_cache), diff --git a/consensus/types/src/attestation/indexed_payload_attestation.rs b/consensus/types/src/attestation/indexed_payload_attestation.rs new file mode 100644 index 00000000000..4de805570cf --- /dev/null +++ b/consensus/types/src/attestation/indexed_payload_attestation.rs @@ -0,0 +1,36 @@ +use crate::test_utils::TestRandom; +use crate::{EthSpec, ForkName, PayloadAttestationData}; +use bls::AggregateSignature; +use context_deserialize::context_deserialize; +use core::slice::Iter; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; +use ssz_types::VariableList; +use test_random_derive::TestRandom; +use tree_hash_derive::TreeHash; + +#[derive(TestRandom, TreeHash, Debug, Clone, PartialEq, Encode, Decode, Serialize, Deserialize)] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +#[serde(bound = "E: EthSpec", deny_unknown_fields)] +#[cfg_attr(feature = "arbitrary", arbitrary(bound = "E: EthSpec"))] +#[context_deserialize(ForkName)] +pub struct IndexedPayloadAttestation { + #[serde(with = "ssz_types::serde_utils::quoted_u64_var_list")] + pub attesting_indices: VariableList, + pub data: PayloadAttestationData, + pub signature: AggregateSignature, +} + +impl IndexedPayloadAttestation { + pub fn attesting_indices_iter(&self) -> Iter<'_, u64> { + self.attesting_indices.iter() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::MainnetEthSpec; + + ssz_and_tree_hash_tests!(IndexedPayloadAttestation); +} diff --git a/consensus/types/src/attestation/mod.rs b/consensus/types/src/attestation/mod.rs index 2d2bf74e49a..586d99bd900 100644 --- a/consensus/types/src/attestation/mod.rs +++ b/consensus/types/src/attestation/mod.rs @@ -5,7 +5,11 @@ mod attestation_duty; mod beacon_committee; mod checkpoint; mod indexed_attestation; +mod indexed_payload_attestation; mod participation_flags; +mod payload_attestation; +mod payload_attestation_data; +mod payload_attestation_message; mod pending_attestation; mod selection_proof; mod shuffling_id; @@ -26,7 +30,11 @@ pub use checkpoint::Checkpoint; pub use indexed_attestation::{ IndexedAttestation, IndexedAttestationBase, IndexedAttestationElectra, IndexedAttestationRef, }; +pub use indexed_payload_attestation::IndexedPayloadAttestation; pub use participation_flags::ParticipationFlags; +pub use payload_attestation::PayloadAttestation; +pub use payload_attestation_data::PayloadAttestationData; +pub use payload_attestation_message::PayloadAttestationMessage; pub use pending_attestation::PendingAttestation; pub use selection_proof::SelectionProof; pub use shuffling_id::AttestationShufflingId; diff --git a/consensus/types/src/attestation/payload_attestation.rs b/consensus/types/src/attestation/payload_attestation.rs new file mode 100644 index 00000000000..192a4a8fea5 --- /dev/null +++ b/consensus/types/src/attestation/payload_attestation.rs @@ -0,0 +1,31 @@ +use crate::attestation::payload_attestation_data::PayloadAttestationData; +use crate::test_utils::TestRandom; +use crate::{EthSpec, ForkName}; +use bls::AggregateSignature; +use context_deserialize::context_deserialize; +use educe::Educe; +use serde::{Deserialize, Serialize}; +use ssz::BitList; +use ssz_derive::{Decode, Encode}; +use test_random_derive::TestRandom; +use tree_hash_derive::TreeHash; + +#[derive(TestRandom, TreeHash, Debug, Clone, Encode, Decode, Serialize, Deserialize, Educe)] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +#[serde(bound = "E: EthSpec", deny_unknown_fields)] +#[cfg_attr(feature = "arbitrary", arbitrary(bound = "E: EthSpec"))] +#[educe(PartialEq, Hash)] +#[context_deserialize(ForkName)] +pub struct PayloadAttestation { + pub aggregation_bits: BitList, + pub data: PayloadAttestationData, + pub signature: AggregateSignature, +} + +#[cfg(test)] +mod payload_attestation_tests { + use super::*; + use crate::MinimalEthSpec; + + ssz_and_tree_hash_tests!(PayloadAttestation); +} diff --git a/consensus/types/src/attestation/payload_attestation_data.rs b/consensus/types/src/attestation/payload_attestation_data.rs new file mode 100644 index 00000000000..58d36fd01d5 --- /dev/null +++ b/consensus/types/src/attestation/payload_attestation_data.rs @@ -0,0 +1,28 @@ +use crate::test_utils::TestRandom; +use crate::{ForkName, Hash256, SignedRoot, Slot}; +use context_deserialize::context_deserialize; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; +use test_random_derive::TestRandom; +use tree_hash_derive::TreeHash; + +#[derive( + TestRandom, TreeHash, Debug, Clone, PartialEq, Eq, Encode, Decode, Serialize, Deserialize, Hash, +)] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +#[context_deserialize(ForkName)] +pub struct PayloadAttestationData { + pub beacon_block_root: Hash256, + pub slot: Slot, + pub payload_present: bool, + pub blob_data_available: bool, +} + +impl SignedRoot for PayloadAttestationData {} + +#[cfg(test)] +mod payload_attestation_data_tests { + use super::*; + + ssz_and_tree_hash_tests!(PayloadAttestationData); +} diff --git a/consensus/types/src/attestation/payload_attestation_message.rs b/consensus/types/src/attestation/payload_attestation_message.rs new file mode 100644 index 00000000000..82e2137b096 --- /dev/null +++ b/consensus/types/src/attestation/payload_attestation_message.rs @@ -0,0 +1,26 @@ +use crate::ForkName; +use crate::attestation::payload_attestation_data::PayloadAttestationData; +use crate::test_utils::TestRandom; +use bls::Signature; +use context_deserialize::context_deserialize; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; +use test_random_derive::TestRandom; +use tree_hash_derive::TreeHash; + +#[derive(TestRandom, TreeHash, Debug, Clone, PartialEq, Encode, Decode, Serialize, Deserialize)] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +#[context_deserialize(ForkName)] +pub struct PayloadAttestationMessage { + #[serde(with = "serde_utils::quoted_u64")] + pub validator_index: u64, + pub data: PayloadAttestationData, + pub signature: Signature, +} + +#[cfg(test)] +mod tests { + use super::*; + + ssz_and_tree_hash_tests!(PayloadAttestationMessage); +} diff --git a/consensus/types/src/block/beacon_block.rs b/consensus/types/src/block/beacon_block.rs index a4e7e800bcc..bee3cdb2746 100644 --- a/consensus/types/src/block/beacon_block.rs +++ b/consensus/types/src/block/beacon_block.rs @@ -15,6 +15,7 @@ use tree_hash_derive::TreeHash; use typenum::Unsigned; use crate::{ + SignedExecutionPayloadBid, attestation::{AttestationBase, AttestationData, IndexedAttestationBase}, block::{ BeaconBlockBodyAltair, BeaconBlockBodyBase, BeaconBlockBodyBellatrix, @@ -694,15 +695,41 @@ impl> EmptyBlock for BeaconBlockGloa deposits: VariableList::empty(), voluntary_exits: VariableList::empty(), sync_aggregate: SyncAggregate::empty(), - execution_payload: Payload::Gloas::default(), bls_to_execution_changes: VariableList::empty(), - blob_kzg_commitments: VariableList::empty(), - 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 { + +// TODO(EIP-7732) Look into whether we can remove this in the future since no blinded blocks post-gloas +impl From>> + for BeaconBlockGloas> +{ + fn from(block: BeaconBlockGloas>) -> Self { + let BeaconBlockGloas { + slot, + proposer_index, + parent_root, + state_root, + body, + } = block; + + BeaconBlockGloas { + 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> diff --git a/consensus/types/src/block/beacon_block_body.rs b/consensus/types/src/block/beacon_block_body.rs index f85dd8909e1..1a0b3859002 100644 --- a/consensus/types/src/block/beacon_block_body.rs +++ b/consensus/types/src/block/beacon_block_body.rs @@ -13,18 +13,19 @@ use test_random_derive::TestRandom; use tree_hash::{BYTES_PER_CHUNK, TreeHash}; use tree_hash_derive::TreeHash; +use crate::payload_attestation::PayloadAttestation; use crate::{ + SignedExecutionPayloadBid, attestation::{AttestationBase, AttestationElectra, AttestationRef, AttestationRefMut}, core::{EthSpec, Graffiti, Hash256}, deposit::Deposit, execution::{ AbstractExecPayload, BlindedPayload, BlindedPayloadBellatrix, BlindedPayloadCapella, - BlindedPayloadDeneb, BlindedPayloadElectra, BlindedPayloadFulu, BlindedPayloadGloas, - Eth1Data, ExecutionPayload, ExecutionPayloadBellatrix, ExecutionPayloadCapella, - ExecutionPayloadDeneb, ExecutionPayloadElectra, ExecutionPayloadFulu, - ExecutionPayloadGloas, ExecutionRequests, FullPayload, FullPayloadBellatrix, - FullPayloadCapella, FullPayloadDeneb, FullPayloadElectra, FullPayloadFulu, - FullPayloadGloas, SignedBlsToExecutionChange, + BlindedPayloadDeneb, BlindedPayloadElectra, BlindedPayloadFulu, Eth1Data, ExecutionPayload, + ExecutionPayloadBellatrix, ExecutionPayloadCapella, ExecutionPayloadDeneb, + ExecutionPayloadElectra, ExecutionPayloadFulu, ExecutionPayloadGloas, ExecutionRequests, + FullPayload, FullPayloadBellatrix, FullPayloadCapella, FullPayloadDeneb, + FullPayloadElectra, FullPayloadFulu, SignedBlsToExecutionChange, }, exit::SignedVoluntaryExit, fork::{ForkName, map_fork_name}, @@ -157,17 +158,18 @@ pub struct BeaconBlockBody = FullPay #[superstruct(only(Fulu), partial_getter(rename = "execution_payload_fulu"))] #[serde(flatten)] pub execution_payload: Payload::Fulu, - #[superstruct(only(Gloas), partial_getter(rename = "execution_payload_gloas"))] - #[serde(flatten)] - pub execution_payload: Payload::Gloas, #[superstruct(only(Capella, Deneb, Electra, Fulu, Gloas))] pub bls_to_execution_changes: VariableList, - #[superstruct(only(Deneb, Electra, Fulu, Gloas))] + #[superstruct(only(Deneb, Electra, Fulu))] pub blob_kzg_commitments: KzgCommitments, - #[superstruct(only(Electra, Fulu, Gloas))] + #[superstruct(only(Electra, Fulu))] pub execution_requests: ExecutionRequests, - #[superstruct(only(Base, Altair))] + #[superstruct(only(Gloas))] + pub signed_execution_payload_bid: SignedExecutionPayloadBid, + #[superstruct(only(Gloas))] + pub payload_attestations: VariableList, E::MaxPayloadAttestations>, + #[superstruct(only(Base, Altair, Gloas))] #[metastruct(exclude_from(fields))] #[ssz(skip_serializing, skip_deserializing)] #[tree_hash(skip_hashing)] @@ -196,7 +198,7 @@ impl<'a, E: EthSpec, Payload: AbstractExecPayload> BeaconBlockBodyRef<'a, E, Self::Deneb(body) => Ok(Payload::Ref::from(&body.execution_payload)), Self::Electra(body) => Ok(Payload::Ref::from(&body.execution_payload)), Self::Fulu(body) => Ok(Payload::Ref::from(&body.execution_payload)), - Self::Gloas(body) => Ok(Payload::Ref::from(&body.execution_payload)), + Self::Gloas(_) => Err(BeaconStateError::IncorrectStateVariant), } } @@ -254,16 +256,19 @@ impl<'a, E: EthSpec, Payload: AbstractExecPayload> BeaconBlockBodyRef<'a, E, /// Produces the proof of inclusion for a `KzgCommitment` in `self.blob_kzg_commitments` /// at `index` using an existing proof for the `blob_kzg_commitments` field. + /// TODO(EIP7732) Investigate calling functions since this will no longer work for glas since no block_kzg_commitments in the body anymore pub fn complete_kzg_commitment_merkle_proof( &self, index: usize, kzg_commitments_proof: &[Hash256], ) -> Result, BeaconStateError> { match self { - Self::Base(_) | Self::Altair(_) | Self::Bellatrix(_) | Self::Capella(_) => { - Err(BeaconStateError::IncorrectStateVariant) - } - Self::Deneb(_) | Self::Electra(_) | Self::Fulu(_) | Self::Gloas(_) => { + Self::Base(_) + | Self::Altair(_) + | Self::Bellatrix(_) + | Self::Capella(_) + | Self::Gloas(_) => Err(BeaconStateError::IncorrectStateVariant), + Self::Deneb(_) | Self::Electra(_) | Self::Fulu(_) => { // We compute the branches by generating 2 merkle trees: // 1. Merkle tree for the `blob_kzg_commitments` List object // 2. Merkle tree for the `BeaconBlockBody` container @@ -541,6 +546,46 @@ 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 BeaconBlockBodyGloas> +{ + fn from(body: BeaconBlockBodyGloas>) -> Self { + let BeaconBlockBodyGloas { + randao_reveal, + eth1_data, + graffiti, + proposer_slashings, + attester_slashings, + attestations, + deposits, + voluntary_exits, + sync_aggregate, + bls_to_execution_changes, + signed_execution_payload_bid, + payload_attestations, + _phantom, + } = body; + + BeaconBlockBodyGloas { + randao_reveal, + eth1_data, + graffiti, + proposer_slashings, + attester_slashings, + attestations, + deposits, + voluntary_exits, + sync_aggregate, + bls_to_execution_changes, + signed_execution_payload_bid, + payload_attestations, + _phantom: PhantomData, + } + } +} + // Likewise bodies with payloads can be transformed into bodies without. impl From>> for ( @@ -851,10 +896,10 @@ impl From>> deposits, voluntary_exits, sync_aggregate, - execution_payload: FullPayloadGloas { execution_payload }, bls_to_execution_changes, - blob_kzg_commitments, - execution_requests, + signed_execution_payload_bid, + payload_attestations, + _phantom, } = body; ( @@ -868,14 +913,12 @@ impl From>> deposits, voluntary_exits, sync_aggregate, - execution_payload: BlindedPayloadGloas { - execution_payload_header: From::from(&execution_payload), - }, bls_to_execution_changes, - blob_kzg_commitments: blob_kzg_commitments.clone(), - execution_requests, + signed_execution_payload_bid, + payload_attestations, + _phantom: PhantomData, }, - Some(execution_payload), + None, ) } } @@ -1075,39 +1118,8 @@ impl BeaconBlockBodyFulu> { impl BeaconBlockBodyGloas> { pub fn clone_as_blinded(&self) -> BeaconBlockBodyGloas> { - let BeaconBlockBodyGloas { - randao_reveal, - eth1_data, - graffiti, - proposer_slashings, - attester_slashings, - attestations, - deposits, - voluntary_exits, - sync_aggregate, - execution_payload: FullPayloadGloas { execution_payload }, - bls_to_execution_changes, - blob_kzg_commitments, - execution_requests, - } = self; - - BeaconBlockBodyGloas { - randao_reveal: randao_reveal.clone(), - eth1_data: eth1_data.clone(), - graffiti: *graffiti, - proposer_slashings: proposer_slashings.clone(), - attester_slashings: attester_slashings.clone(), - attestations: attestations.clone(), - deposits: deposits.clone(), - voluntary_exits: voluntary_exits.clone(), - sync_aggregate: sync_aggregate.clone(), - execution_payload: BlindedPayloadGloas { - execution_payload_header: execution_payload.into(), - }, - bls_to_execution_changes: bls_to_execution_changes.clone(), - blob_kzg_commitments: blob_kzg_commitments.clone(), - execution_requests: execution_requests.clone(), - } + let (block_body, _payload) = self.clone().into(); + block_body } } diff --git a/consensus/types/src/block/signed_beacon_block.rs b/consensus/types/src/block/signed_beacon_block.rs index e8927ee7659..aeb3c18d957 100644 --- a/consensus/types/src/block/signed_beacon_block.rs +++ b/consensus/types/src/block/signed_beacon_block.rs @@ -17,19 +17,17 @@ use crate::{ block::{ BLOB_KZG_COMMITMENTS_INDEX, BeaconBlock, BeaconBlockAltair, BeaconBlockBase, BeaconBlockBellatrix, BeaconBlockBodyBellatrix, BeaconBlockBodyCapella, - BeaconBlockBodyDeneb, BeaconBlockBodyElectra, BeaconBlockBodyFulu, BeaconBlockBodyGloas, - BeaconBlockCapella, BeaconBlockDeneb, BeaconBlockElectra, BeaconBlockFulu, - BeaconBlockGloas, BeaconBlockHeader, BeaconBlockRef, BeaconBlockRefMut, - SignedBeaconBlockHeader, + BeaconBlockBodyDeneb, BeaconBlockBodyElectra, BeaconBlockBodyFulu, BeaconBlockCapella, + BeaconBlockDeneb, BeaconBlockElectra, BeaconBlockFulu, BeaconBlockGloas, BeaconBlockHeader, + BeaconBlockRef, BeaconBlockRefMut, SignedBeaconBlockHeader, }, core::{ChainSpec, Domain, Epoch, EthSpec, Hash256, SignedRoot, SigningData, Slot}, execution::{ AbstractExecPayload, BlindedPayload, BlindedPayloadBellatrix, BlindedPayloadCapella, - BlindedPayloadDeneb, BlindedPayloadElectra, BlindedPayloadFulu, BlindedPayloadGloas, - ExecutionPayload, ExecutionPayloadBellatrix, ExecutionPayloadCapella, - ExecutionPayloadDeneb, ExecutionPayloadElectra, ExecutionPayloadFulu, - ExecutionPayloadGloas, FullPayload, FullPayloadBellatrix, FullPayloadCapella, - FullPayloadDeneb, FullPayloadElectra, FullPayloadFulu, FullPayloadGloas, + BlindedPayloadDeneb, BlindedPayloadElectra, BlindedPayloadFulu, ExecutionPayload, + ExecutionPayloadBellatrix, ExecutionPayloadCapella, ExecutionPayloadDeneb, + ExecutionPayloadElectra, ExecutionPayloadFulu, FullPayload, FullPayloadBellatrix, + FullPayloadCapella, FullPayloadDeneb, FullPayloadElectra, FullPayloadFulu, }, fork::{Fork, ForkName, ForkVersionDecode, InconsistentFork, map_fork_name}, kzg_ext::format_kzg_commitments, @@ -675,59 +673,15 @@ impl SignedBeaconBlockFulu> { } } -impl SignedBeaconBlockGloas> { - pub fn into_full_block( - self, - execution_payload: ExecutionPayloadGloas, - ) -> SignedBeaconBlockGloas> { - let SignedBeaconBlockGloas { - message: - BeaconBlockGloas { - slot, - proposer_index, - parent_root, - state_root, - body: - BeaconBlockBodyGloas { - randao_reveal, - eth1_data, - graffiti, - proposer_slashings, - attester_slashings, - attestations, - deposits, - voluntary_exits, - sync_aggregate, - execution_payload: BlindedPayloadGloas { .. }, - bls_to_execution_changes, - blob_kzg_commitments, - execution_requests, - }, - }, - signature, - } = self; +// We can convert gloas blocks without payloads into blocks "with" payloads. +// TODO(EIP-7732) Look into whether we can remove this in the future since no blinded blocks post-gloas +impl From>> + for SignedBeaconBlockGloas> +{ + fn from(signed_block: SignedBeaconBlockGloas>) -> Self { + let SignedBeaconBlockGloas { message, signature } = signed_block; SignedBeaconBlockGloas { - message: BeaconBlockGloas { - slot, - proposer_index, - parent_root, - state_root, - body: BeaconBlockBodyGloas { - randao_reveal, - eth1_data, - graffiti, - proposer_slashings, - attester_slashings, - attestations, - deposits, - voluntary_exits, - sync_aggregate, - execution_payload: FullPayloadGloas { execution_payload }, - bls_to_execution_changes, - blob_kzg_commitments, - execution_requests, - }, - }, + message: message.into(), signature, } } @@ -756,9 +710,7 @@ impl SignedBeaconBlock> { (SignedBeaconBlock::Fulu(block), Some(ExecutionPayload::Fulu(payload))) => { SignedBeaconBlock::Fulu(block.into_full_block(payload)) } - (SignedBeaconBlock::Gloas(block), Some(ExecutionPayload::Gloas(payload))) => { - SignedBeaconBlock::Gloas(block.into_full_block(payload)) - } + (SignedBeaconBlock::Gloas(block), _) => SignedBeaconBlock::Gloas(block.into()), // avoid wildcard matching forks so that compiler will // direct us here when a new fork has been added (SignedBeaconBlock::Bellatrix(_), _) => return None, @@ -766,7 +718,7 @@ impl SignedBeaconBlock> { (SignedBeaconBlock::Deneb(_), _) => return None, (SignedBeaconBlock::Electra(_), _) => return None, (SignedBeaconBlock::Fulu(_), _) => return None, - (SignedBeaconBlock::Gloas(_), _) => return None, + // TODO(EIP-7732) Determine if need a match arm for gloas here }; Some(full_block) } diff --git a/consensus/types/src/builder/builder_bid.rs b/consensus/types/src/builder/builder_bid.rs index be9bb281553..1018fadb644 100644 --- a/consensus/types/src/builder/builder_bid.rs +++ b/consensus/types/src/builder/builder_bid.rs @@ -13,8 +13,7 @@ use crate::{ execution::{ ExecutionPayloadHeaderBellatrix, ExecutionPayloadHeaderCapella, ExecutionPayloadHeaderDeneb, ExecutionPayloadHeaderElectra, ExecutionPayloadHeaderFulu, - ExecutionPayloadHeaderGloas, ExecutionPayloadHeaderRef, ExecutionPayloadHeaderRefMut, - ExecutionRequests, + ExecutionPayloadHeaderRef, ExecutionPayloadHeaderRefMut, ExecutionRequests, }, fork::{ForkName, ForkVersionDecode}, kzg_ext::KzgCommitments, @@ -22,7 +21,7 @@ use crate::{ }; #[superstruct( - variants(Bellatrix, Capella, Deneb, Electra, Fulu, Gloas), + variants(Bellatrix, Capella, Deneb, Electra, Fulu), variant_attributes( derive( PartialEq, @@ -55,11 +54,9 @@ pub struct BuilderBid { pub header: ExecutionPayloadHeaderElectra, #[superstruct(only(Fulu), partial_getter(rename = "header_fulu"))] pub header: ExecutionPayloadHeaderFulu, - #[superstruct(only(Gloas), partial_getter(rename = "header_gloas"))] - pub header: ExecutionPayloadHeaderGloas, - #[superstruct(only(Deneb, Electra, Fulu, Gloas))] + #[superstruct(only(Deneb, Electra, Fulu))] pub blob_kzg_commitments: KzgCommitments, - #[superstruct(only(Electra, Fulu, Gloas))] + #[superstruct(only(Electra, Fulu))] pub execution_requests: ExecutionRequests, #[serde(with = "serde_utils::quoted_u256")] pub value: Uint256, @@ -92,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::Altair | ForkName::Base | ForkName::Gloas => { return Err(ssz::DecodeError::BytesInvalid(format!( "unsupported fork for ExecutionPayloadHeader: {fork_name}", ))); @@ -104,7 +101,6 @@ impl ForkVersionDecode for BuilderBid { ForkName::Deneb => BuilderBid::Deneb(BuilderBidDeneb::from_ssz_bytes(bytes)?), ForkName::Electra => BuilderBid::Electra(BuilderBidElectra::from_ssz_bytes(bytes)?), ForkName::Fulu => BuilderBid::Fulu(BuilderBidFulu::from_ssz_bytes(bytes)?), - ForkName::Gloas => BuilderBid::Gloas(BuilderBidGloas::from_ssz_bytes(bytes)?), }; Ok(builder_bid) } @@ -160,10 +156,7 @@ impl<'de, E: EthSpec> ContextDeserialize<'de, ForkName> for BuilderBid { ForkName::Fulu => { Self::Fulu(Deserialize::deserialize(deserializer).map_err(convert_err)?) } - ForkName::Gloas => { - Self::Gloas(Deserialize::deserialize(deserializer).map_err(convert_err)?) - } - ForkName::Base | ForkName::Altair => { + ForkName::Base | ForkName::Altair | ForkName::Gloas => { return Err(serde::de::Error::custom(format!( "BuilderBid failed to deserialize: unsupported fork '{}'", context diff --git a/consensus/types/src/builder/builder_pending_payment.rs b/consensus/types/src/builder/builder_pending_payment.rs new file mode 100644 index 00000000000..0f1b68ad970 --- /dev/null +++ b/consensus/types/src/builder/builder_pending_payment.rs @@ -0,0 +1,36 @@ +use crate::test_utils::TestRandom; +use crate::{BuilderPendingWithdrawal, ForkName}; +use context_deserialize::context_deserialize; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; +use test_random_derive::TestRandom; +use tree_hash_derive::TreeHash; + +#[derive( + Debug, + PartialEq, + Eq, + Hash, + Clone, + Default, + Serialize, + Deserialize, + Encode, + Decode, + TreeHash, + TestRandom, +)] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +#[context_deserialize(ForkName)] +pub struct BuilderPendingPayment { + #[serde(with = "serde_utils::quoted_u64")] + pub weight: u64, + pub withdrawal: BuilderPendingWithdrawal, +} + +#[cfg(test)] +mod tests { + use super::*; + + ssz_and_tree_hash_tests!(BuilderPendingPayment); +} diff --git a/consensus/types/src/builder/builder_pending_withdrawal.rs b/consensus/types/src/builder/builder_pending_withdrawal.rs new file mode 100644 index 00000000000..436d331c003 --- /dev/null +++ b/consensus/types/src/builder/builder_pending_withdrawal.rs @@ -0,0 +1,40 @@ +use crate::test_utils::TestRandom; +use crate::{Address, Epoch, ForkName}; +use context_deserialize::context_deserialize; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; +use test_random_derive::TestRandom; +use tree_hash_derive::TreeHash; + +#[derive( + Debug, + PartialEq, + Eq, + Hash, + Clone, + Default, + Serialize, + Deserialize, + Encode, + Decode, + TreeHash, + TestRandom, +)] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +#[context_deserialize(ForkName)] +pub struct BuilderPendingWithdrawal { + #[serde(with = "serde_utils::address_hex")] + pub fee_recipient: Address, + #[serde(with = "serde_utils::quoted_u64")] + pub amount: u64, + #[serde(with = "serde_utils::quoted_u64")] + pub builder_index: u64, + pub withdrawable_epoch: Epoch, +} + +#[cfg(test)] +mod tests { + use super::*; + + ssz_and_tree_hash_tests!(BuilderPendingWithdrawal); +} diff --git a/consensus/types/src/builder/mod.rs b/consensus/types/src/builder/mod.rs index 88a8e6a01a3..54d0ae4eb73 100644 --- a/consensus/types/src/builder/mod.rs +++ b/consensus/types/src/builder/mod.rs @@ -1,6 +1,10 @@ mod builder_bid; +mod builder_pending_payment; +mod builder_pending_withdrawal; pub use builder_bid::{ BuilderBid, BuilderBidBellatrix, BuilderBidCapella, BuilderBidDeneb, BuilderBidElectra, - BuilderBidFulu, BuilderBidGloas, SignedBuilderBid, + BuilderBidFulu, SignedBuilderBid, }; +pub use builder_pending_payment::BuilderPendingPayment; +pub use builder_pending_withdrawal::BuilderPendingWithdrawal; diff --git a/consensus/types/src/core/chain_spec.rs b/consensus/types/src/core/chain_spec.rs index c8052b502b5..da3f9b90ccc 100644 --- a/consensus/types/src/core/chain_spec.rs +++ b/consensus/types/src/core/chain_spec.rs @@ -36,6 +36,8 @@ pub enum Domain { SyncCommittee, ContributionAndProof, SyncCommitteeSelectionProof, + BeaconBuilder, + PTCAttester, ApplicationMask(ApplicationDomain), } @@ -89,6 +91,7 @@ pub struct ChainSpec { pub bls_withdrawal_prefix_byte: u8, pub eth1_address_withdrawal_prefix_byte: u8, pub compounding_withdrawal_prefix_byte: u8, + pub builder_withdrawal_prefix_byte: u8, /* * Time parameters @@ -127,6 +130,8 @@ pub struct ChainSpec { pub(crate) domain_voluntary_exit: u32, pub(crate) domain_selection_proof: u32, pub(crate) domain_aggregate_and_proof: u32, + pub(crate) domain_beacon_builder: u32, + pub(crate) domain_ptc_attester: u32, /* * Fork choice @@ -228,6 +233,8 @@ pub struct ChainSpec { pub gloas_fork_version: [u8; 4], /// The Gloas fork epoch is optional, with `None` representing "Gloas never happens". pub gloas_fork_epoch: Option, + pub builder_payment_threshold_numerator: u64, + pub builder_payment_threshold_denominator: u64, /* * Networking @@ -535,6 +542,8 @@ impl ChainSpec { Domain::VoluntaryExit => self.domain_voluntary_exit, Domain::SelectionProof => self.domain_selection_proof, Domain::AggregateAndProof => self.domain_aggregate_and_proof, + Domain::BeaconBuilder => self.domain_beacon_builder, + Domain::PTCAttester => self.domain_ptc_attester, Domain::SyncCommittee => self.domain_sync_committee, Domain::ContributionAndProof => self.domain_contribution_and_proof, Domain::SyncCommitteeSelectionProof => self.domain_sync_committee_selection_proof, @@ -972,6 +981,7 @@ impl ChainSpec { bls_withdrawal_prefix_byte: 0x00, eth1_address_withdrawal_prefix_byte: 0x01, compounding_withdrawal_prefix_byte: 0x02, + builder_withdrawal_prefix_byte: 0x03, /* * Time parameters @@ -1011,6 +1021,8 @@ impl ChainSpec { domain_voluntary_exit: 4, domain_selection_proof: 5, domain_aggregate_and_proof: 6, + domain_beacon_builder: 0x1B, + domain_ptc_attester: 0x0C, /* * Fork choice @@ -1131,6 +1143,8 @@ impl ChainSpec { */ gloas_fork_version: [0x07, 0x00, 0x00, 0x00], gloas_fork_epoch: None, + builder_payment_threshold_numerator: 6, + builder_payment_threshold_denominator: 10, /* * Network specific @@ -1333,6 +1347,7 @@ impl ChainSpec { bls_withdrawal_prefix_byte: 0x00, eth1_address_withdrawal_prefix_byte: 0x01, compounding_withdrawal_prefix_byte: 0x02, + builder_withdrawal_prefix_byte: 0x03, /* * Time parameters @@ -1372,6 +1387,8 @@ impl ChainSpec { domain_voluntary_exit: 4, domain_selection_proof: 5, domain_aggregate_and_proof: 6, + domain_beacon_builder: 0x1B, + domain_ptc_attester: 0x0C, /* * Fork choice @@ -1491,6 +1508,8 @@ impl ChainSpec { */ gloas_fork_version: [0x07, 0x00, 0x00, 0x64], gloas_fork_epoch: None, + builder_payment_threshold_numerator: 6, + builder_payment_threshold_denominator: 10, /* * Network specific @@ -2517,6 +2536,8 @@ mod tests { &spec, ); test_domain(Domain::SyncCommittee, spec.domain_sync_committee, &spec); + test_domain(Domain::BeaconBuilder, spec.domain_beacon_builder, &spec); + test_domain(Domain::PTCAttester, spec.domain_ptc_attester, &spec); // The builder domain index is zero let builder_domain_pre_mask = [0; 4]; diff --git a/consensus/types/src/core/eth_spec.rs b/consensus/types/src/core/eth_spec.rs index 72fd1ebc9eb..74795fdfc31 100644 --- a/consensus/types/src/core/eth_spec.rs +++ b/consensus/types/src/core/eth_spec.rs @@ -171,6 +171,14 @@ pub trait EthSpec: 'static + Default + Sync + Send + Clone + Debug + PartialEq + type MaxWithdrawalRequestsPerPayload: Unsigned + Clone + Sync + Send + Debug + PartialEq; type MaxPendingDepositsPerEpoch: Unsigned + Clone + Sync + Send + Debug + PartialEq; + /* + * New in Gloas + */ + type PTCSize: Unsigned + Clone + Sync + Send + Debug + PartialEq; + type MaxPayloadAttestations: Unsigned + Clone + Sync + Send + Debug + PartialEq; + type BuilderPendingPaymentsLimit: Unsigned + Clone + Sync + Send + Debug + PartialEq; + type BuilderPendingWithdrawalsLimit: Unsigned + Clone + Sync + Send + Debug + PartialEq; + fn default_spec() -> ChainSpec; fn spec_name() -> EthSpecId; @@ -357,6 +365,16 @@ pub trait EthSpec: 'static + Default + Sync + Send + Clone + Debug + PartialEq + Self::PendingConsolidationsLimit::to_usize() } + /// Returns the `BUILDER_PENDING_PAYMENTS_LIMIT` constant for this specification. + fn builder_pending_payments_limit() -> usize { + Self::BuilderPendingPaymentsLimit::to_usize() + } + + /// Returns the `BUILDER_PENDING_WITHDRAWALS_LIMIT` constant for this specification. + fn builder_pending_withdrawals_limit() -> usize { + Self::BuilderPendingWithdrawalsLimit::to_usize() + } + /// Returns the `MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD` constant for this specification. fn max_consolidation_requests_per_payload() -> usize { Self::MaxConsolidationRequestsPerPayload::to_usize() @@ -402,6 +420,16 @@ pub trait EthSpec: 'static + Default + Sync + Send + Clone + Debug + PartialEq + fn proposer_lookahead_slots() -> usize { Self::ProposerLookaheadSlots::to_usize() } + + /// Returns the `PTCSize` constant for this specification. + fn ptc_size() -> usize { + Self::PTCSize::to_usize() + } + + /// Returns the `MaxPayloadAttestations` constant for this specification. + fn max_payload_attestations() -> usize { + Self::MaxPayloadAttestations::to_usize() + } } /// Macro to inherit some type values from another EthSpec. @@ -431,6 +459,8 @@ impl EthSpec for MainnetEthSpec { type EpochsPerSlashingsVector = U8192; type HistoricalRootsLimit = U16777216; type ValidatorRegistryLimit = U1099511627776; + type BuilderPendingPaymentsLimit = U64; // 2 * SLOTS_PER_EPOCH = 2 * 32 = 64 + type BuilderPendingWithdrawalsLimit = U1048576; type MaxProposerSlashings = U16; type MaxAttesterSlashings = U2; type MaxAttestations = U128; @@ -471,6 +501,8 @@ impl EthSpec for MainnetEthSpec { type MaxAttestationsElectra = U8; type MaxWithdrawalRequestsPerPayload = U16; type MaxPendingDepositsPerEpoch = U16; + type PTCSize = U512; + type MaxPayloadAttestations = U4; fn default_spec() -> ChainSpec { ChainSpec::mainnet() @@ -513,6 +545,7 @@ impl EthSpec for MinimalEthSpec { type CellsPerExtBlob = U128; type NumberOfColumns = U128; type ProposerLookaheadSlots = U16; // Derived from (MIN_SEED_LOOKAHEAD + 1) * SLOTS_PER_EPOCH + type BuilderPendingPaymentsLimit = U16; // 2 * SLOTS_PER_EPOCH = 2 * 8 = 16 params_from_eth_spec!(MainnetEthSpec { JustificationBitsLength, @@ -522,6 +555,7 @@ impl EthSpec for MinimalEthSpec { GenesisEpoch, HistoricalRootsLimit, ValidatorRegistryLimit, + BuilderPendingWithdrawalsLimit, MaxProposerSlashings, MaxAttesterSlashings, MaxAttestations, @@ -541,7 +575,9 @@ impl EthSpec for MinimalEthSpec { MaxAttesterSlashingsElectra, MaxAttestationsElectra, MaxDepositRequestsPerPayload, - MaxWithdrawalRequestsPerPayload + MaxWithdrawalRequestsPerPayload, + PTCSize, + MaxPayloadAttestations }); fn default_spec() -> ChainSpec { @@ -572,6 +608,8 @@ impl EthSpec for GnosisEthSpec { type EpochsPerSlashingsVector = U8192; type HistoricalRootsLimit = U16777216; type ValidatorRegistryLimit = U1099511627776; + type BuilderPendingPaymentsLimit = U32; // 2 * SLOTS_PER_EPOCH = 2 * 16 = 32 + type BuilderPendingWithdrawalsLimit = U1048576; type MaxProposerSlashings = U16; type MaxAttesterSlashings = U2; type MaxAttestations = U128; @@ -612,6 +650,8 @@ impl EthSpec for GnosisEthSpec { type CellsPerExtBlob = U128; type NumberOfColumns = U128; type ProposerLookaheadSlots = U32; // Derived from (MIN_SEED_LOOKAHEAD + 1) * SLOTS_PER_EPOCH + type PTCSize = U512; + type MaxPayloadAttestations = U2; fn default_spec() -> ChainSpec { ChainSpec::gnosis() diff --git a/consensus/types/src/execution/dumb_macros.rs b/consensus/types/src/execution/dumb_macros.rs new file mode 100644 index 00000000000..4eae416bb56 --- /dev/null +++ b/consensus/types/src/execution/dumb_macros.rs @@ -0,0 +1,108 @@ +// These would usually be created by superstuct but now there's no longer a 1:1 mapping between +// the variants for ExecutionPayload and the variants for +// - ExecutionPayloadHeader +// - FullPayload +// - BlindedPayload +// TODO(EIP-7732): get rid of this whole file and panics once the engine_api is refactored for ePBS + +#[macro_export] +macro_rules! map_execution_payload_into_full_payload { + ($value:expr, $f:expr) => { + match $value { + ExecutionPayload::Bellatrix(inner) => { + let f: fn(ExecutionPayloadBellatrix<_>, fn(_) -> _) -> _ = $f; + f(inner, FullPayload::Bellatrix) + } + ExecutionPayload::Capella(inner) => { + let f: fn(ExecutionPayloadCapella<_>, fn(_) -> _) -> _ = $f; + f(inner, FullPayload::Capella) + } + ExecutionPayload::Deneb(inner) => { + let f: fn(ExecutionPayloadDeneb<_>, fn(_) -> _) -> _ = $f; + f(inner, FullPayload::Deneb) + } + ExecutionPayload::Electra(inner) => { + let f: fn(ExecutionPayloadElectra<_>, fn(_) -> _) -> _ = $f; + f(inner, FullPayload::Electra) + } + ExecutionPayload::Fulu(inner) => { + let f: fn(ExecutionPayloadFulu<_>, fn(_) -> _) -> _ = $f; + f(inner, FullPayload::Fulu) + } + ExecutionPayload::Gloas(_) => panic!("FullPayload::Gloas does not exist!"), + } + }; +} + +#[macro_export] +macro_rules! map_execution_payload_into_blinded_payload { + ($value:expr, $f:expr) => { + match $value { + ExecutionPayload::Bellatrix(inner) => { + let f: fn(ExecutionPayloadBellatrix<_>, fn(_) -> _) -> _ = $f; + f(inner, BlindedPayload::Bellatrix) + } + ExecutionPayload::Capella(inner) => { + let f: fn(ExecutionPayloadCapella<_>, fn(_) -> _) -> _ = $f; + f(inner, BlindedPayload::Capella) + } + ExecutionPayload::Deneb(inner) => { + let f: fn(ExecutionPayloadDeneb<_>, fn(_) -> _) -> _ = $f; + f(inner, BlindedPayload::Deneb) + } + ExecutionPayload::Electra(inner) => { + let f: fn(ExecutionPayloadElectra<_>, fn(_) -> _) -> _ = $f; + f(inner, BlindedPayload::Electra) + } + ExecutionPayload::Fulu(inner) => { + let f: fn(ExecutionPayloadFulu<_>, fn(_) -> _) -> _ = $f; + f(inner, BlindedPayload::Fulu) + } + ExecutionPayload::Gloas(_) => panic!("BlindedPayload::Gloas does not exist!"), + } + }; +} + +#[macro_export] +macro_rules! map_execution_payload_ref_into_execution_payload_header { + (&$lifetime:tt _, $value:expr, $f:expr) => { + match $value { + ExecutionPayloadRef::Bellatrix(inner) => { + let f: fn( + &$lifetime ExecutionPayloadBellatrix<_>, + fn(_) -> _, + ) -> _ = $f; + f(inner, ExecutionPayloadHeader::Bellatrix) + } + ExecutionPayloadRef::Capella(inner) => { + let f: fn( + &$lifetime ExecutionPayloadCapella<_>, + fn(_) -> _, + ) -> _ = $f; + f(inner, ExecutionPayloadHeader::Capella) + } + ExecutionPayloadRef::Deneb(inner) => { + let f: fn( + &$lifetime ExecutionPayloadDeneb<_>, + fn(_) -> _, + ) -> _ = $f; + f(inner, ExecutionPayloadHeader::Deneb) + } + ExecutionPayloadRef::Electra(inner) => { + let f: fn( + &$lifetime ExecutionPayloadElectra<_>, + fn(_) -> _, + ) -> _ = $f; + f(inner, ExecutionPayloadHeader::Electra) + } + ExecutionPayloadRef::Fulu(inner) => { + let f: fn( + &$lifetime ExecutionPayloadFulu<_>, + fn(_) -> _, + ) -> _ = $f; + f(inner, ExecutionPayloadHeader::Fulu) + } + ExecutionPayloadRef::Gloas(_) => panic!("ExecutionPayloadHeader::Gloas does not exist!"), + } + } +} diff --git a/consensus/types/src/execution/execution_payload.rs b/consensus/types/src/execution/execution_payload.rs index 7973b7efdce..b2278c91667 100644 --- a/consensus/types/src/execution/execution_payload.rs +++ b/consensus/types/src/execution/execution_payload.rs @@ -55,9 +55,7 @@ pub type Transactions = VariableList< partial_getter_error( ty = "BeaconStateError", expr = "BeaconStateError::IncorrectStateVariant" - ), - map_into(FullPayload, BlindedPayload), - map_ref_into(ExecutionPayloadHeader) + ) )] #[cfg_attr( feature = "arbitrary", @@ -146,6 +144,7 @@ impl ForkVersionDecode for ExecutionPayload { impl ExecutionPayload { #[allow(clippy::arithmetic_side_effects)] /// Returns the maximum size of an execution payload. + /// TODO(EIP-7732): this seems to only exist for the Bellatrix fork, but Mark's branch has it for all the forks, i.e. max_execution_payload_eip7732_size pub fn max_execution_payload_bellatrix_size() -> usize { // Fixed part ExecutionPayloadBellatrix::::default().as_ssz_bytes().len() diff --git a/consensus/types/src/execution/execution_payload_bid.rs b/consensus/types/src/execution/execution_payload_bid.rs new file mode 100644 index 00000000000..20e461334d3 --- /dev/null +++ b/consensus/types/src/execution/execution_payload_bid.rs @@ -0,0 +1,40 @@ +use crate::test_utils::TestRandom; +use crate::{Address, ExecutionBlockHash, ForkName, Hash256, SignedRoot, Slot}; +use context_deserialize::context_deserialize; +use educe::Educe; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; +use test_random_derive::TestRandom; +use tree_hash_derive::TreeHash; + +#[derive( + Default, Debug, Clone, Serialize, Encode, Decode, Deserialize, TreeHash, Educe, TestRandom, +)] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +#[educe(PartialEq, Hash)] +#[context_deserialize(ForkName)] +// https://github.com/ethereum/consensus-specs/blob/master/specs/gloas/beacon-chain.md#executionpayloadbid +pub struct ExecutionPayloadBid { + pub parent_block_hash: ExecutionBlockHash, + pub parent_block_root: Hash256, + pub block_hash: ExecutionBlockHash, + #[serde(with = "serde_utils::address_hex")] + pub fee_recipient: Address, + #[serde(with = "serde_utils::quoted_u64")] + pub gas_limit: u64, + #[serde(with = "serde_utils::quoted_u64")] + pub builder_index: u64, + pub slot: Slot, + #[serde(with = "serde_utils::quoted_u64")] + pub value: u64, + pub blob_kzg_commitments_root: Hash256, +} + +impl SignedRoot for ExecutionPayloadBid {} + +#[cfg(test)] +mod tests { + use super::*; + + ssz_and_tree_hash_tests!(ExecutionPayloadBid); +} diff --git a/consensus/types/src/execution/execution_payload_envelope.rs b/consensus/types/src/execution/execution_payload_envelope.rs new file mode 100644 index 00000000000..64e03cec5a9 --- /dev/null +++ b/consensus/types/src/execution/execution_payload_envelope.rs @@ -0,0 +1,36 @@ +use crate::test_utils::TestRandom; +use crate::{ + EthSpec, ExecutionPayloadGloas, ExecutionRequests, ForkName, Hash256, KzgCommitments, + SignedRoot, Slot, +}; +use context_deserialize::context_deserialize; +use educe::Educe; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; +use test_random_derive::TestRandom; +use tree_hash_derive::TreeHash; + +#[derive(Debug, Clone, Serialize, Encode, Decode, Deserialize, TestRandom, TreeHash, Educe)] +#[educe(PartialEq, Hash(bound(E: EthSpec)))] +#[context_deserialize(ForkName)] +#[serde(bound = "E: EthSpec")] +pub struct ExecutionPayloadEnvelope { + pub payload: ExecutionPayloadGloas, + pub execution_requests: ExecutionRequests, + #[serde(with = "serde_utils::quoted_u64")] + pub builder_index: u64, + pub beacon_block_root: Hash256, + pub slot: Slot, + pub blob_kzg_commitments: KzgCommitments, + pub state_root: Hash256, +} + +impl SignedRoot for ExecutionPayloadEnvelope {} + +#[cfg(test)] +mod tests { + use super::*; + use crate::MainnetEthSpec; + + ssz_and_tree_hash_tests!(ExecutionPayloadEnvelope); +} diff --git a/consensus/types/src/execution/execution_payload_header.rs b/consensus/types/src/execution/execution_payload_header.rs index bd91a6471b2..cf78f7871b4 100644 --- a/consensus/types/src/execution/execution_payload_header.rs +++ b/consensus/types/src/execution/execution_payload_header.rs @@ -14,16 +14,17 @@ use crate::{ core::{Address, EthSpec, Hash256, Uint256}, execution::{ ExecutionBlockHash, ExecutionPayloadBellatrix, ExecutionPayloadCapella, - ExecutionPayloadDeneb, ExecutionPayloadElectra, ExecutionPayloadFulu, - ExecutionPayloadGloas, ExecutionPayloadRef, Transactions, + ExecutionPayloadDeneb, ExecutionPayloadElectra, ExecutionPayloadFulu, ExecutionPayloadRef, + Transactions, }, fork::ForkName, + map_execution_payload_ref_into_execution_payload_header, state::BeaconStateError, test_utils::TestRandom, }; #[superstruct( - variants(Bellatrix, Capella, Deneb, Electra, Fulu, Gloas), + variants(Bellatrix, Capella, Deneb, Electra, Fulu), variant_attributes( derive( Default, @@ -105,12 +106,12 @@ pub struct ExecutionPayloadHeader { pub block_hash: ExecutionBlockHash, #[superstruct(getter(copy))] pub transactions_root: Hash256, - #[superstruct(only(Capella, Deneb, Electra, Fulu, Gloas), partial_getter(copy))] + #[superstruct(only(Capella, Deneb, Electra, Fulu), partial_getter(copy))] pub withdrawals_root: Hash256, - #[superstruct(only(Deneb, Electra, Fulu, Gloas), partial_getter(copy))] + #[superstruct(only(Deneb, Electra, Fulu), partial_getter(copy))] #[serde(with = "serde_utils::quoted_u64")] pub blob_gas_used: u64, - #[superstruct(only(Deneb, Electra, Fulu, Gloas), partial_getter(copy))] + #[superstruct(only(Deneb, Electra, Fulu), partial_getter(copy))] #[serde(with = "serde_utils::quoted_u64")] pub excess_blob_gas: u64, } @@ -136,14 +137,19 @@ impl ExecutionPayloadHeader { ExecutionPayloadHeaderElectra::from_ssz_bytes(bytes).map(Self::Electra) } ForkName::Fulu => ExecutionPayloadHeaderFulu::from_ssz_bytes(bytes).map(Self::Fulu), - ForkName::Gloas => ExecutionPayloadHeaderGloas::from_ssz_bytes(bytes).map(Self::Gloas), + ForkName::Gloas => Err(ssz::DecodeError::BytesInvalid(format!( + "unsupported fork for ExecutionPayloadHeader: {fork_name}", + ))), } } #[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.bellatrix_enabled() { + if fork_name.gloas_enabled() { + // TODO(EIP7732): check this + 0 + } else if fork_name.bellatrix_enabled() { // Max size of variable length `extra_data` field E::max_extra_data_bytes() * ::ssz_fixed_len() } else { @@ -158,7 +164,6 @@ impl ExecutionPayloadHeader { ExecutionPayloadHeader::Deneb(_) => ForkName::Deneb, ExecutionPayloadHeader::Electra(_) => ForkName::Electra, ExecutionPayloadHeader::Fulu(_) => ForkName::Fulu, - ExecutionPayloadHeader::Gloas(_) => ForkName::Gloas, } } } @@ -266,30 +271,6 @@ impl ExecutionPayloadHeaderElectra { } } -impl ExecutionPayloadHeaderFulu { - pub fn upgrade_to_gloas(&self) -> ExecutionPayloadHeaderGloas { - ExecutionPayloadHeaderGloas { - parent_hash: self.parent_hash, - fee_recipient: self.fee_recipient, - state_root: self.state_root, - receipts_root: self.receipts_root, - logs_bloom: self.logs_bloom.clone(), - prev_randao: self.prev_randao, - block_number: self.block_number, - gas_limit: self.gas_limit, - gas_used: self.gas_used, - timestamp: self.timestamp, - extra_data: self.extra_data.clone(), - base_fee_per_gas: self.base_fee_per_gas, - block_hash: self.block_hash, - transactions_root: self.transactions_root, - withdrawals_root: self.withdrawals_root, - blob_gas_used: self.blob_gas_used, - excess_blob_gas: self.excess_blob_gas, - } - } -} - impl<'a, E: EthSpec> From<&'a ExecutionPayloadBellatrix> for ExecutionPayloadHeaderBellatrix { fn from(payload: &'a ExecutionPayloadBellatrix) -> Self { Self { @@ -405,30 +386,6 @@ impl<'a, E: EthSpec> From<&'a ExecutionPayloadFulu> for ExecutionPayloadHeade } } -impl<'a, E: EthSpec> From<&'a ExecutionPayloadGloas> for ExecutionPayloadHeaderGloas { - fn from(payload: &'a ExecutionPayloadGloas) -> Self { - Self { - parent_hash: payload.parent_hash, - fee_recipient: payload.fee_recipient, - state_root: payload.state_root, - receipts_root: payload.receipts_root, - logs_bloom: payload.logs_bloom.clone(), - prev_randao: payload.prev_randao, - block_number: payload.block_number, - gas_limit: payload.gas_limit, - gas_used: payload.gas_used, - timestamp: payload.timestamp, - extra_data: payload.extra_data.clone(), - base_fee_per_gas: payload.base_fee_per_gas, - block_hash: payload.block_hash, - transactions_root: payload.transactions.tree_hash_root(), - withdrawals_root: payload.withdrawals.tree_hash_root(), - blob_gas_used: payload.blob_gas_used, - excess_blob_gas: payload.excess_blob_gas, - } - } -} - // These impls are required to work around an inelegance in `to_execution_payload_header`. // They only clone headers so they should be relatively cheap. impl<'a, E: EthSpec> From<&'a Self> for ExecutionPayloadHeaderBellatrix { @@ -461,12 +418,6 @@ impl<'a, E: EthSpec> From<&'a Self> for ExecutionPayloadHeaderFulu { } } -impl<'a, E: EthSpec> From<&'a Self> for ExecutionPayloadHeaderGloas { - fn from(payload: &'a Self) -> Self { - payload.clone() - } -} - impl<'a, E: EthSpec> From> for ExecutionPayloadHeader { fn from(payload: ExecutionPayloadRef<'a, E>) -> Self { map_execution_payload_ref_into_execution_payload_header!( @@ -528,9 +479,6 @@ impl ExecutionPayloadHeaderRefMut<'_, E> { ExecutionPayloadHeaderRefMut::Fulu(mut_ref) => { *mut_ref = header.try_into()?; } - ExecutionPayloadHeaderRefMut::Gloas(mut_ref) => { - *mut_ref = header.try_into()?; - } } Ok(()) } @@ -558,16 +506,6 @@ impl TryFrom> for ExecutionPayloadHeaderFu } } -impl TryFrom> for ExecutionPayloadHeaderGloas { - type Error = BeaconStateError; - fn try_from(header: ExecutionPayloadHeader) -> Result { - match header { - ExecutionPayloadHeader::Gloas(execution_payload_header) => Ok(execution_payload_header), - _ => Err(BeaconStateError::IncorrectStateVariant), - } - } -} - impl<'de, E: EthSpec> ContextDeserialize<'de, ForkName> for ExecutionPayloadHeader { fn context_deserialize(deserializer: D, context: ForkName) -> Result where @@ -580,12 +518,6 @@ impl<'de, E: EthSpec> ContextDeserialize<'de, ForkName> for ExecutionPayloadHead )) }; Ok(match context { - ForkName::Base | ForkName::Altair => { - return Err(serde::de::Error::custom(format!( - "ExecutionPayloadHeader failed to deserialize: unsupported fork '{}'", - context - ))); - } ForkName::Bellatrix => { Self::Bellatrix(Deserialize::deserialize(deserializer).map_err(convert_err)?) } @@ -601,8 +533,12 @@ impl<'de, E: EthSpec> ContextDeserialize<'de, ForkName> for ExecutionPayloadHead ForkName::Fulu => { Self::Fulu(Deserialize::deserialize(deserializer).map_err(convert_err)?) } - ForkName::Gloas => { - Self::Gloas(Deserialize::deserialize(deserializer).map_err(convert_err)?) + + ForkName::Base | ForkName::Altair | ForkName::Gloas => { + 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 0708bc5d960..da6c8606002 100644 --- a/consensus/types/src/execution/mod.rs +++ b/consensus/types/src/execution/mod.rs @@ -4,10 +4,15 @@ mod execution_block_header; #[macro_use] mod execution_payload; mod bls_to_execution_change; +mod dumb_macros; +mod execution_payload_bid; +mod execution_payload_envelope; mod execution_payload_header; mod execution_requests; mod payload; mod signed_bls_to_execution_change; +mod signed_execution_payload_bid; +mod signed_execution_payload_envelope; pub use bls_to_execution_change::BlsToExecutionChange; pub use eth1_data::Eth1Data; @@ -18,19 +23,23 @@ pub use execution_payload::{ ExecutionPayloadElectra, ExecutionPayloadFulu, ExecutionPayloadGloas, ExecutionPayloadRef, Transaction, Transactions, }; +pub use execution_payload_bid::ExecutionPayloadBid; +pub use execution_payload_envelope::ExecutionPayloadEnvelope; pub use execution_payload_header::{ ExecutionPayloadHeader, ExecutionPayloadHeaderBellatrix, ExecutionPayloadHeaderCapella, ExecutionPayloadHeaderDeneb, ExecutionPayloadHeaderElectra, ExecutionPayloadHeaderFulu, - ExecutionPayloadHeaderGloas, ExecutionPayloadHeaderRef, ExecutionPayloadHeaderRefMut, + ExecutionPayloadHeaderRef, ExecutionPayloadHeaderRefMut, }; pub use execution_requests::{ ConsolidationRequests, DepositRequests, ExecutionRequests, RequestType, WithdrawalRequests, }; pub use payload::{ AbstractExecPayload, BlindedPayload, BlindedPayloadBellatrix, BlindedPayloadCapella, - BlindedPayloadDeneb, BlindedPayloadElectra, BlindedPayloadFulu, BlindedPayloadGloas, - BlindedPayloadRef, BlockProductionVersion, BlockType, ExecPayload, FullPayload, - FullPayloadBellatrix, FullPayloadCapella, FullPayloadDeneb, FullPayloadElectra, - FullPayloadFulu, FullPayloadGloas, FullPayloadRef, OwnedExecPayload, + BlindedPayloadDeneb, BlindedPayloadElectra, BlindedPayloadFulu, BlindedPayloadRef, + BlockProductionVersion, BlockType, ExecPayload, FullPayload, FullPayloadBellatrix, + FullPayloadCapella, FullPayloadDeneb, FullPayloadElectra, FullPayloadFulu, FullPayloadRef, + OwnedExecPayload, }; pub use signed_bls_to_execution_change::SignedBlsToExecutionChange; +pub use signed_execution_payload_bid::SignedExecutionPayloadBid; +pub use signed_execution_payload_envelope::SignedExecutionPayloadEnvelope; diff --git a/consensus/types/src/execution/payload.rs b/consensus/types/src/execution/payload.rs index c1cc6c4eb66..703b082c182 100644 --- a/consensus/types/src/execution/payload.rs +++ b/consensus/types/src/execution/payload.rs @@ -15,11 +15,12 @@ use crate::{ execution::{ ExecutionBlockHash, ExecutionPayload, ExecutionPayloadBellatrix, ExecutionPayloadCapella, ExecutionPayloadDeneb, ExecutionPayloadElectra, ExecutionPayloadFulu, - ExecutionPayloadGloas, ExecutionPayloadHeader, ExecutionPayloadHeaderBellatrix, - ExecutionPayloadHeaderCapella, ExecutionPayloadHeaderDeneb, ExecutionPayloadHeaderElectra, - ExecutionPayloadHeaderFulu, ExecutionPayloadHeaderGloas, ExecutionPayloadRef, Transactions, + ExecutionPayloadHeader, ExecutionPayloadHeaderBellatrix, ExecutionPayloadHeaderCapella, + ExecutionPayloadHeaderDeneb, ExecutionPayloadHeaderElectra, ExecutionPayloadHeaderFulu, + ExecutionPayloadRef, Transactions, }, fork::ForkName, + map_execution_payload_into_blinded_payload, map_execution_payload_into_full_payload, state::BeaconStateError, test_utils::TestRandom, }; @@ -118,7 +119,6 @@ pub trait AbstractExecPayload: + TryInto + TryInto + TryInto - + TryInto + Sync { type Ref<'a>: ExecPayload @@ -127,8 +127,7 @@ pub trait AbstractExecPayload: + From<&'a Self::Capella> + From<&'a Self::Deneb> + From<&'a Self::Electra> - + From<&'a Self::Fulu> - + From<&'a Self::Gloas>; + + From<&'a Self::Fulu>; type Bellatrix: OwnedExecPayload + Into @@ -155,15 +154,10 @@ pub trait AbstractExecPayload: + for<'a> From>> + TryFrom> + Sync; - type Gloas: OwnedExecPayload - + Into - + for<'a> From>> - + TryFrom> - + Sync; } #[superstruct( - variants(Bellatrix, Capella, Deneb, Electra, Fulu, Gloas), + variants(Bellatrix, Capella, Deneb, Electra, Fulu), variant_attributes( derive( Debug, @@ -224,8 +218,6 @@ pub struct FullPayload { pub execution_payload: ExecutionPayloadElectra, #[superstruct(only(Fulu), partial_getter(rename = "execution_payload_fulu"))] pub execution_payload: ExecutionPayloadFulu, - #[superstruct(only(Gloas), partial_getter(rename = "execution_payload_gloas"))] - pub execution_payload: ExecutionPayloadGloas, } impl From> for ExecutionPayload { @@ -337,7 +329,6 @@ impl ExecPayload for FullPayload { FullPayload::Deneb(inner) => Ok(inner.execution_payload.withdrawals.tree_hash_root()), FullPayload::Electra(inner) => Ok(inner.execution_payload.withdrawals.tree_hash_root()), FullPayload::Fulu(inner) => Ok(inner.execution_payload.withdrawals.tree_hash_root()), - FullPayload::Gloas(inner) => Ok(inner.execution_payload.withdrawals.tree_hash_root()), } } @@ -349,7 +340,6 @@ impl ExecPayload for FullPayload { FullPayload::Deneb(inner) => Ok(inner.execution_payload.blob_gas_used), FullPayload::Electra(inner) => Ok(inner.execution_payload.blob_gas_used), FullPayload::Fulu(inner) => Ok(inner.execution_payload.blob_gas_used), - FullPayload::Gloas(inner) => Ok(inner.execution_payload.blob_gas_used), } } @@ -381,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 => Ok(FullPayloadGloas::default().into()), + ForkName::Gloas => Err(BeaconStateError::IncorrectStateVariant), } } } @@ -482,9 +472,6 @@ impl ExecPayload for FullPayloadRef<'_, E> { Ok(inner.execution_payload.withdrawals.tree_hash_root()) } FullPayloadRef::Fulu(inner) => Ok(inner.execution_payload.withdrawals.tree_hash_root()), - FullPayloadRef::Gloas(inner) => { - Ok(inner.execution_payload.withdrawals.tree_hash_root()) - } } } @@ -496,7 +483,6 @@ impl ExecPayload for FullPayloadRef<'_, E> { FullPayloadRef::Deneb(inner) => Ok(inner.execution_payload.blob_gas_used), FullPayloadRef::Electra(inner) => Ok(inner.execution_payload.blob_gas_used), FullPayloadRef::Fulu(inner) => Ok(inner.execution_payload.blob_gas_used), - FullPayloadRef::Gloas(inner) => Ok(inner.execution_payload.blob_gas_used), } } @@ -520,7 +506,6 @@ impl AbstractExecPayload for FullPayload { type Deneb = FullPayloadDeneb; type Electra = FullPayloadElectra; type Fulu = FullPayloadFulu; - type Gloas = FullPayloadGloas; } impl From> for FullPayload { @@ -539,7 +524,7 @@ impl TryFrom> for FullPayload { } #[superstruct( - variants(Bellatrix, Capella, Deneb, Electra, Fulu, Gloas), + variants(Bellatrix, Capella, Deneb, Electra, Fulu), variant_attributes( derive( Debug, @@ -599,8 +584,6 @@ pub struct BlindedPayload { pub execution_payload_header: ExecutionPayloadHeaderElectra, #[superstruct(only(Fulu), partial_getter(rename = "execution_payload_fulu"))] pub execution_payload_header: ExecutionPayloadHeaderFulu, - #[superstruct(only(Gloas), partial_getter(rename = "execution_payload_gloas"))] - pub execution_payload_header: ExecutionPayloadHeaderGloas, } impl<'a, E: EthSpec> From> for BlindedPayload { @@ -690,7 +673,6 @@ impl ExecPayload for BlindedPayload { BlindedPayload::Deneb(inner) => Ok(inner.execution_payload_header.withdrawals_root), BlindedPayload::Electra(inner) => Ok(inner.execution_payload_header.withdrawals_root), BlindedPayload::Fulu(inner) => Ok(inner.execution_payload_header.withdrawals_root), - BlindedPayload::Gloas(inner) => Ok(inner.execution_payload_header.withdrawals_root), } } @@ -702,7 +684,6 @@ impl ExecPayload for BlindedPayload { BlindedPayload::Deneb(inner) => Ok(inner.execution_payload_header.blob_gas_used), BlindedPayload::Electra(inner) => Ok(inner.execution_payload_header.blob_gas_used), BlindedPayload::Fulu(inner) => Ok(inner.execution_payload_header.blob_gas_used), - BlindedPayload::Gloas(inner) => Ok(inner.execution_payload_header.blob_gas_used), } } @@ -802,7 +783,6 @@ impl<'b, E: EthSpec> ExecPayload for BlindedPayloadRef<'b, E> { Ok(inner.execution_payload_header.withdrawals_root) } BlindedPayloadRef::Fulu(inner) => Ok(inner.execution_payload_header.withdrawals_root), - BlindedPayloadRef::Gloas(inner) => Ok(inner.execution_payload_header.withdrawals_root), } } @@ -814,7 +794,6 @@ impl<'b, E: EthSpec> ExecPayload for BlindedPayloadRef<'b, E> { BlindedPayloadRef::Deneb(inner) => Ok(inner.execution_payload_header.blob_gas_used), BlindedPayloadRef::Electra(inner) => Ok(inner.execution_payload_header.blob_gas_used), BlindedPayloadRef::Fulu(inner) => Ok(inner.execution_payload_header.blob_gas_used), - BlindedPayloadRef::Gloas(inner) => Ok(inner.execution_payload_header.blob_gas_used), } } @@ -1126,13 +1105,6 @@ impl_exec_payload_for_fork!( ExecutionPayloadFulu, Fulu ); -impl_exec_payload_for_fork!( - BlindedPayloadGloas, - FullPayloadGloas, - ExecutionPayloadHeaderGloas, - ExecutionPayloadGloas, - Gloas -); impl AbstractExecPayload for BlindedPayload { type Ref<'a> = BlindedPayloadRef<'a, E>; @@ -1141,7 +1113,6 @@ impl AbstractExecPayload for BlindedPayload { type Deneb = BlindedPayloadDeneb; type Electra = BlindedPayloadElectra; type Fulu = BlindedPayloadFulu; - type Gloas = BlindedPayloadGloas; } impl From> for BlindedPayload { @@ -1183,11 +1154,6 @@ impl From> for BlindedPayload { execution_payload_header, }) } - ExecutionPayloadHeader::Gloas(execution_payload_header) => { - Self::Gloas(BlindedPayloadGloas { - execution_payload_header, - }) - } } } } @@ -1210,9 +1176,6 @@ impl From> for ExecutionPayloadHeader { BlindedPayload::Fulu(blinded_payload) => { ExecutionPayloadHeader::Fulu(blinded_payload.execution_payload_header) } - BlindedPayload::Gloas(blinded_payload) => { - ExecutionPayloadHeader::Gloas(blinded_payload.execution_payload_header) - } } } } diff --git a/consensus/types/src/execution/signed_execution_payload_bid.rs b/consensus/types/src/execution/signed_execution_payload_bid.rs new file mode 100644 index 00000000000..29dfd03ba03 --- /dev/null +++ b/consensus/types/src/execution/signed_execution_payload_bid.rs @@ -0,0 +1,35 @@ +use crate::test_utils::TestRandom; +use crate::{ExecutionPayloadBid, ForkName}; +use bls::Signature; +use context_deserialize::context_deserialize; +use educe::Educe; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; +use test_random_derive::TestRandom; +use tree_hash_derive::TreeHash; + +#[derive(TestRandom, TreeHash, Debug, Clone, Encode, Decode, Serialize, Deserialize, Educe)] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +#[educe(PartialEq, Hash)] +#[context_deserialize(ForkName)] +// https://github.com/ethereum/consensus-specs/blob/master/specs/gloas/beacon-chain.md#signedexecutionpayloadbid +pub struct SignedExecutionPayloadBid { + pub message: ExecutionPayloadBid, + pub signature: Signature, +} + +impl SignedExecutionPayloadBid { + pub fn empty() -> Self { + Self { + message: ExecutionPayloadBid::default(), + signature: Signature::empty(), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + ssz_and_tree_hash_tests!(SignedExecutionPayloadBid); +} diff --git a/consensus/types/src/execution/signed_execution_payload_envelope.rs b/consensus/types/src/execution/signed_execution_payload_envelope.rs new file mode 100644 index 00000000000..16410416157 --- /dev/null +++ b/consensus/types/src/execution/signed_execution_payload_envelope.rs @@ -0,0 +1,24 @@ +use crate::test_utils::TestRandom; +use crate::{EthSpec, ExecutionPayloadEnvelope}; +use bls::Signature; +use educe::Educe; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; +use test_random_derive::TestRandom; +use tree_hash_derive::TreeHash; + +#[derive(Debug, Clone, Serialize, Encode, Decode, Deserialize, TestRandom, TreeHash, Educe)] +#[educe(PartialEq, Hash(bound(E: EthSpec)))] +#[serde(bound = "E: EthSpec")] +pub struct SignedExecutionPayloadEnvelope { + pub message: ExecutionPayloadEnvelope, + pub signature: Signature, +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::MainnetEthSpec; + + ssz_and_tree_hash_tests!(SignedExecutionPayloadEnvelope); +} diff --git a/consensus/types/src/fork/fork_context.rs b/consensus/types/src/fork/fork_context.rs index 89f69bcbb62..3407689e790 100644 --- a/consensus/types/src/fork/fork_context.rs +++ b/consensus/types/src/fork/fork_context.rs @@ -182,6 +182,7 @@ mod tests { spec.deneb_fork_epoch = Some(Epoch::new(4)); spec.electra_fork_epoch = Some(Epoch::new(5)); spec.fulu_fork_epoch = Some(Epoch::new(6)); + spec.gloas_fork_epoch = Some(Epoch::new(7)); spec.blob_schedule = BlobSchedule::new(blob_parameters); spec } @@ -196,6 +197,7 @@ mod tests { assert!(context.fork_exists(ForkName::Electra)); assert!(context.fork_exists(ForkName::Fulu)); + assert!(context.fork_exists(ForkName::Gloas)); } #[test] diff --git a/consensus/types/src/lib.rs b/consensus/types/src/lib.rs index cd9252bde8b..5a89fcb1d48 100644 --- a/consensus/types/src/lib.rs +++ b/consensus/types/src/lib.rs @@ -124,6 +124,10 @@ pub mod execution_requests { }; } +pub mod execution_payload_envelope { + pub use crate::execution::{ExecutionPayloadEnvelope, SignedExecutionPayloadEnvelope}; +} + pub mod data_column_custody_group { pub use crate::data::{ CustodyIndex, compute_columns_for_custody_group, compute_ordered_custody_column_indices, @@ -157,6 +161,12 @@ pub mod signed_aggregate_and_proof { pub use crate::attestation::SignedAggregateAndProofRefMut; } +pub mod payload_attestation { + pub use crate::attestation::{ + PayloadAttestation, PayloadAttestationData, PayloadAttestationMessage, + }; +} + pub mod application_domain { pub use crate::core::ApplicationDomain; } diff --git a/consensus/types/src/light_client/error.rs b/consensus/types/src/light_client/error.rs index c492cfcbde3..4c7a30db5e6 100644 --- a/consensus/types/src/light_client/error.rs +++ b/consensus/types/src/light_client/error.rs @@ -14,6 +14,7 @@ pub enum LightClientError { InvalidFinalizedBlock, BeaconBlockBodyError, InconsistentFork, + GloasNotImplemented, } 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 847b2a2a963..fbcc0ef2b05 100644 --- a/consensus/types/src/light_client/light_client_bootstrap.rs +++ b/consensus/types/src/light_client/light_client_bootstrap.rs @@ -18,7 +18,6 @@ use crate::{ CurrentSyncCommitteeProofLen, CurrentSyncCommitteeProofLenElectra, LightClientError, LightClientHeader, LightClientHeaderAltair, LightClientHeaderCapella, LightClientHeaderDeneb, LightClientHeaderElectra, LightClientHeaderFulu, - LightClientHeaderGloas, }, state::BeaconState, sync_committee::SyncCommittee, @@ -28,7 +27,7 @@ use crate::{ /// A LightClientBootstrap is the initializer we send over to light_client nodes /// that are trying to generate their basic storage when booting up. #[superstruct( - variants(Altair, Capella, Deneb, Electra, Fulu, Gloas), + variants(Altair, Capella, Deneb, Electra, Fulu), variant_attributes( derive( Debug, @@ -73,8 +72,6 @@ pub struct LightClientBootstrap { pub header: LightClientHeaderElectra, #[superstruct(only(Fulu), partial_getter(rename = "header_fulu"))] pub header: LightClientHeaderFulu, - #[superstruct(only(Gloas), partial_getter(rename = "header_gloas"))] - pub header: LightClientHeaderGloas, /// The `SyncCommittee` used in the requested period. pub current_sync_committee: Arc>, /// Merkle proof for sync committee @@ -84,7 +81,7 @@ pub struct LightClientBootstrap { )] pub current_sync_committee_branch: FixedVector, #[superstruct( - only(Electra, Fulu, Gloas), + only(Electra, Fulu), partial_getter(rename = "current_sync_committee_branch_electra") )] pub current_sync_committee_branch: FixedVector, @@ -101,7 +98,6 @@ impl LightClientBootstrap { Self::Deneb(_) => func(ForkName::Deneb), Self::Electra(_) => func(ForkName::Electra), Self::Fulu(_) => func(ForkName::Fulu), - Self::Gloas(_) => func(ForkName::Gloas), } } @@ -121,8 +117,8 @@ impl LightClientBootstrap { ForkName::Deneb => Self::Deneb(LightClientBootstrapDeneb::from_ssz_bytes(bytes)?), ForkName::Electra => Self::Electra(LightClientBootstrapElectra::from_ssz_bytes(bytes)?), ForkName::Fulu => Self::Fulu(LightClientBootstrapFulu::from_ssz_bytes(bytes)?), - ForkName::Gloas => Self::Gloas(LightClientBootstrapGloas::from_ssz_bytes(bytes)?), - ForkName::Base => { + // TODO(gloas): implement Gloas light client + ForkName::Base | ForkName::Gloas => { return Err(ssz::DecodeError::BytesInvalid(format!( "LightClientBootstrap decoding for {fork_name} not implemented" ))); @@ -143,7 +139,8 @@ impl LightClientBootstrap { ForkName::Deneb => as Encode>::ssz_fixed_len(), ForkName::Electra => as Encode>::ssz_fixed_len(), ForkName::Fulu => as Encode>::ssz_fixed_len(), - ForkName::Gloas => as Encode>::ssz_fixed_len(), + // TODO(gloas): implement Gloas light client + ForkName::Gloas => as Encode>::ssz_fixed_len(), }; fixed_len + LightClientHeader::::ssz_max_var_len_for_fork(fork_name) } @@ -194,13 +191,8 @@ impl LightClientBootstrap { .try_into() .map_err(LightClientError::SszTypesError)?, }), - ForkName::Gloas => Self::Gloas(LightClientBootstrapGloas { - header: LightClientHeaderGloas::block_to_light_client_header(block)?, - current_sync_committee, - current_sync_committee_branch: current_sync_committee_branch - .try_into() - .map_err(LightClientError::SszTypesError)?, - }), + // TODO(gloas): implement Gloas light client + ForkName::Gloas => return Err(LightClientError::GloasNotImplemented), }; Ok(light_client_bootstrap) @@ -254,13 +246,8 @@ impl LightClientBootstrap { .try_into() .map_err(LightClientError::SszTypesError)?, }), - ForkName::Gloas => Self::Gloas(LightClientBootstrapGloas { - header: LightClientHeaderGloas::block_to_light_client_header(block)?, - current_sync_committee, - current_sync_committee_branch: current_sync_committee_branch - .try_into() - .map_err(LightClientError::SszTypesError)?, - }), + // TODO(gloas): implement Gloas light client + ForkName::Gloas => return Err(LightClientError::GloasNotImplemented), }; Ok(light_client_bootstrap) @@ -301,7 +288,11 @@ impl<'de, E: EthSpec> ContextDeserialize<'de, ForkName> for LightClientBootstrap Self::Fulu(Deserialize::deserialize(deserializer).map_err(convert_err)?) } ForkName::Gloas => { - Self::Gloas(Deserialize::deserialize(deserializer).map_err(convert_err)?) + // TODO(EIP-7732): check if this is correct + return Err(serde::de::Error::custom(format!( + "LightClientBootstrap failed to deserialize: unsupported fork '{}'", + context + ))); } }) } @@ -339,10 +330,4 @@ mod tests { use crate::{LightClientBootstrapFulu, MainnetEthSpec}; ssz_tests!(LightClientBootstrapFulu); } - - #[cfg(test)] - mod gloas { - use crate::{LightClientBootstrapGloas, MainnetEthSpec}; - ssz_tests!(LightClientBootstrapGloas); - } } 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 04374edcd96..b503785b851 100644 --- a/consensus/types/src/light_client/light_client_finality_update.rs +++ b/consensus/types/src/light_client/light_client_finality_update.rs @@ -16,14 +16,14 @@ use crate::{ light_client::{ FinalizedRootProofLen, FinalizedRootProofLenElectra, LightClientError, LightClientHeader, LightClientHeaderAltair, LightClientHeaderCapella, LightClientHeaderDeneb, - LightClientHeaderElectra, LightClientHeaderFulu, LightClientHeaderGloas, + LightClientHeaderElectra, LightClientHeaderFulu, }, sync_committee::SyncAggregate, test_utils::TestRandom, }; #[superstruct( - variants(Altair, Capella, Deneb, Electra, Fulu, Gloas), + variants(Altair, Capella, Deneb, Electra, Fulu), variant_attributes( derive( Debug, @@ -68,8 +68,6 @@ pub struct LightClientFinalityUpdate { pub attested_header: LightClientHeaderElectra, #[superstruct(only(Fulu), partial_getter(rename = "attested_header_fulu"))] pub attested_header: LightClientHeaderFulu, - #[superstruct(only(Gloas), partial_getter(rename = "attested_header_gloas"))] - pub attested_header: LightClientHeaderGloas, /// The last `BeaconBlockHeader` from the last attested finalized block (end of epoch). #[superstruct(only(Altair), partial_getter(rename = "finalized_header_altair"))] pub finalized_header: LightClientHeaderAltair, @@ -81,8 +79,6 @@ pub struct LightClientFinalityUpdate { pub finalized_header: LightClientHeaderElectra, #[superstruct(only(Fulu), partial_getter(rename = "finalized_header_fulu"))] pub finalized_header: LightClientHeaderFulu, - #[superstruct(only(Gloas), partial_getter(rename = "finalized_header_gloas"))] - pub finalized_header: LightClientHeaderGloas, /// Merkle proof attesting finalized header. #[superstruct( only(Altair, Capella, Deneb), @@ -90,7 +86,7 @@ pub struct LightClientFinalityUpdate { )] pub finality_branch: FixedVector, #[superstruct( - only(Electra, Fulu, Gloas), + only(Electra, Fulu), partial_getter(rename = "finality_branch_electra") )] pub finality_branch: FixedVector, @@ -181,20 +177,7 @@ impl LightClientFinalityUpdate { sync_aggregate, signature_slot, }), - ForkName::Gloas => Self::Gloas(LightClientFinalityUpdateGloas { - attested_header: LightClientHeaderGloas::block_to_light_client_header( - attested_block, - )?, - finalized_header: LightClientHeaderGloas::block_to_light_client_header( - finalized_block, - )?, - finality_branch: finality_branch - .try_into() - .map_err(LightClientError::SszTypesError)?, - sync_aggregate, - signature_slot, - }), - + ForkName::Gloas => return Err(LightClientError::GloasNotImplemented), ForkName::Base => return Err(LightClientError::AltairForkNotActive), }; @@ -211,7 +194,6 @@ impl LightClientFinalityUpdate { Self::Deneb(_) => func(ForkName::Deneb), Self::Electra(_) => func(ForkName::Electra), Self::Fulu(_) => func(ForkName::Fulu), - Self::Gloas(_) => func(ForkName::Gloas), } } @@ -249,8 +231,8 @@ impl LightClientFinalityUpdate { Self::Electra(LightClientFinalityUpdateElectra::from_ssz_bytes(bytes)?) } ForkName::Fulu => Self::Fulu(LightClientFinalityUpdateFulu::from_ssz_bytes(bytes)?), - ForkName::Gloas => Self::Gloas(LightClientFinalityUpdateGloas::from_ssz_bytes(bytes)?), - ForkName::Base => { + // TODO(gloas): implement Gloas light client + ForkName::Base | ForkName::Gloas => { return Err(ssz::DecodeError::BytesInvalid(format!( "LightClientFinalityUpdate decoding for {fork_name} not implemented" ))); @@ -271,7 +253,8 @@ impl LightClientFinalityUpdate { ForkName::Deneb => as Encode>::ssz_fixed_len(), ForkName::Electra => as Encode>::ssz_fixed_len(), ForkName::Fulu => as Encode>::ssz_fixed_len(), - ForkName::Gloas => as Encode>::ssz_fixed_len(), + // TODO(gloas): implement Gloas light client + ForkName::Gloas => 0, }; // `2 *` because there are two headers in the update fixed_size + 2 * LightClientHeader::::ssz_max_var_len_for_fork(fork_name) @@ -325,7 +308,11 @@ impl<'de, E: EthSpec> ContextDeserialize<'de, ForkName> for LightClientFinalityU Self::Fulu(Deserialize::deserialize(deserializer).map_err(convert_err)?) } ForkName::Gloas => { - Self::Gloas(Deserialize::deserialize(deserializer).map_err(convert_err)?) + // TODO(EIP-7732): check if this is correct + return Err(serde::de::Error::custom(format!( + "LightClientBootstrap failed to deserialize: unsupported fork '{}'", + context + ))); } }) } @@ -363,10 +350,4 @@ mod tests { use crate::{LightClientFinalityUpdateFulu, MainnetEthSpec}; ssz_tests!(LightClientFinalityUpdateFulu); } - - #[cfg(test)] - mod gloas { - use crate::{LightClientFinalityUpdateGloas, MainnetEthSpec}; - ssz_tests!(LightClientFinalityUpdateGloas); - } } diff --git a/consensus/types/src/light_client/light_client_header.rs b/consensus/types/src/light_client/light_client_header.rs index a7ecd3b7fb2..fdf9f234efc 100644 --- a/consensus/types/src/light_client/light_client_header.rs +++ b/consensus/types/src/light_client/light_client_header.rs @@ -15,7 +15,7 @@ use crate::{ core::{ChainSpec, EthSpec, Hash256}, execution::{ ExecutionPayloadHeader, ExecutionPayloadHeaderCapella, ExecutionPayloadHeaderDeneb, - ExecutionPayloadHeaderElectra, ExecutionPayloadHeaderFulu, ExecutionPayloadHeaderGloas, + ExecutionPayloadHeaderElectra, ExecutionPayloadHeaderFulu, }, fork::ForkName, light_client::{ExecutionPayloadProofLen, LightClientError, consts::EXECUTION_PAYLOAD_INDEX}, @@ -23,7 +23,7 @@ use crate::{ }; #[superstruct( - variants(Altair, Capella, Deneb, Electra, Fulu, Gloas), + variants(Altair, Capella, Deneb, Electra, Fulu,), variant_attributes( derive( Debug, @@ -73,10 +73,8 @@ pub struct LightClientHeader { pub execution: ExecutionPayloadHeaderElectra, #[superstruct(only(Fulu), partial_getter(rename = "execution_payload_header_fulu"))] pub execution: ExecutionPayloadHeaderFulu, - #[superstruct(only(Gloas), partial_getter(rename = "execution_payload_header_gloas"))] - pub execution: ExecutionPayloadHeaderGloas, - #[superstruct(only(Capella, Deneb, Electra, Fulu, Gloas))] + #[superstruct(only(Capella, Deneb, Electra, Fulu))] pub execution_branch: FixedVector, #[ssz(skip_serializing, skip_deserializing)] @@ -111,9 +109,8 @@ impl LightClientHeader { ForkName::Fulu => { LightClientHeader::Fulu(LightClientHeaderFulu::block_to_light_client_header(block)?) } - ForkName::Gloas => LightClientHeader::Gloas( - LightClientHeaderGloas::block_to_light_client_header(block)?, - ), + // TODO(gloas): implement Gloas light client + ForkName::Gloas => return Err(LightClientError::GloasNotImplemented), }; Ok(header) } @@ -135,10 +132,8 @@ impl LightClientHeader { ForkName::Fulu => { LightClientHeader::Fulu(LightClientHeaderFulu::from_ssz_bytes(bytes)?) } - ForkName::Gloas => { - LightClientHeader::Gloas(LightClientHeaderGloas::from_ssz_bytes(bytes)?) - } - ForkName::Base => { + // TODO(gloas): implement Gloas light client + ForkName::Base | ForkName::Gloas => { return Err(ssz::DecodeError::BytesInvalid(format!( "LightClientHeader decoding for {fork_name} not implemented" ))); @@ -157,7 +152,10 @@ impl LightClientHeader { } pub fn ssz_max_var_len_for_fork(fork_name: ForkName) -> usize { - if fork_name.capella_enabled() { + if fork_name.gloas_enabled() { + // TODO(EIP7732): check this + 0 + } else if fork_name.capella_enabled() { ExecutionPayloadHeader::::ssz_max_var_len_for_fork(fork_name) } else { 0 @@ -353,48 +351,6 @@ impl Default for LightClientHeaderFulu { } } -impl LightClientHeaderGloas { - pub fn block_to_light_client_header( - block: &SignedBlindedBeaconBlock, - ) -> Result { - let payload = block - .message() - .execution_payload()? - .execution_payload_gloas()?; - - let header = ExecutionPayloadHeaderGloas::from(payload); - let beacon_block_body = BeaconBlockBody::from( - block - .message() - .body_gloas() - .map_err(|_| LightClientError::BeaconBlockBodyError)? - .to_owned(), - ); - - let execution_branch = beacon_block_body - .to_ref() - .block_body_merkle_proof(EXECUTION_PAYLOAD_INDEX)?; - - Ok(LightClientHeaderGloas { - beacon: block.message().block_header(), - execution: header, - execution_branch: FixedVector::new(execution_branch)?, - _phantom_data: PhantomData, - }) - } -} - -impl Default for LightClientHeaderGloas { - fn default() -> Self { - Self { - beacon: BeaconBlockHeader::empty(), - execution: ExecutionPayloadHeaderGloas::default(), - execution_branch: FixedVector::default(), - _phantom_data: PhantomData, - } - } -} - impl<'de, E: EthSpec> ContextDeserialize<'de, ForkName> for LightClientHeader { fn context_deserialize(deserializer: D, context: ForkName) -> Result where @@ -407,7 +363,8 @@ impl<'de, E: EthSpec> ContextDeserialize<'de, ForkName> for LightClientHeader )) }; Ok(match context { - ForkName::Base => { + // TODO(gloas): implement Gloas light client + ForkName::Base | ForkName::Gloas => { return Err(serde::de::Error::custom(format!( "LightClientFinalityUpdate failed to deserialize: unsupported fork '{}'", context @@ -428,9 +385,6 @@ impl<'de, E: EthSpec> ContextDeserialize<'de, ForkName> for LightClientHeader ForkName::Fulu => { Self::Fulu(Deserialize::deserialize(deserializer).map_err(convert_err)?) } - ForkName::Gloas => { - Self::Gloas(Deserialize::deserialize(deserializer).map_err(convert_err)?) - } }) } } @@ -467,10 +421,4 @@ mod tests { use crate::{LightClientHeaderFulu, MainnetEthSpec}; ssz_tests!(LightClientHeaderFulu); } - - #[cfg(test)] - mod gloas { - use crate::{LightClientHeaderGloas, MainnetEthSpec}; - ssz_tests!(LightClientHeaderGloas); - } } 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 9266ce647a4..139c4b6a08b 100644 --- a/consensus/types/src/light_client/light_client_optimistic_update.rs +++ b/consensus/types/src/light_client/light_client_optimistic_update.rs @@ -15,7 +15,6 @@ use crate::{ light_client::{ LightClientError, LightClientHeader, LightClientHeaderAltair, LightClientHeaderCapella, LightClientHeaderDeneb, LightClientHeaderElectra, LightClientHeaderFulu, - LightClientHeaderGloas, }, sync_committee::SyncAggregate, test_utils::TestRandom, @@ -24,7 +23,7 @@ use crate::{ /// A LightClientOptimisticUpdate is the update we send on each slot, /// it is based off the current unfinalized epoch is verified only against BLS signature. #[superstruct( - variants(Altair, Capella, Deneb, Electra, Fulu, Gloas), + variants(Altair, Capella, Deneb, Electra, Fulu), variant_attributes( derive( Debug, @@ -69,8 +68,6 @@ pub struct LightClientOptimisticUpdate { pub attested_header: LightClientHeaderElectra, #[superstruct(only(Fulu), partial_getter(rename = "attested_header_fulu"))] pub attested_header: LightClientHeaderFulu, - #[superstruct(only(Gloas), partial_getter(rename = "attested_header_gloas"))] - pub attested_header: LightClientHeaderGloas, /// current sync aggregate pub sync_aggregate: SyncAggregate, /// Slot of the sync aggregated signature @@ -126,13 +123,7 @@ impl LightClientOptimisticUpdate { sync_aggregate, signature_slot, }), - ForkName::Gloas => Self::Gloas(LightClientOptimisticUpdateGloas { - attested_header: LightClientHeaderGloas::block_to_light_client_header( - attested_block, - )?, - sync_aggregate, - signature_slot, - }), + ForkName::Gloas => return Err(LightClientError::GloasNotImplemented), ForkName::Base => return Err(LightClientError::AltairForkNotActive), }; @@ -149,7 +140,6 @@ impl LightClientOptimisticUpdate { Self::Deneb(_) => func(ForkName::Deneb), Self::Electra(_) => func(ForkName::Electra), Self::Fulu(_) => func(ForkName::Fulu), - Self::Gloas(_) => func(ForkName::Gloas), } } @@ -189,10 +179,8 @@ impl LightClientOptimisticUpdate { Self::Electra(LightClientOptimisticUpdateElectra::from_ssz_bytes(bytes)?) } ForkName::Fulu => Self::Fulu(LightClientOptimisticUpdateFulu::from_ssz_bytes(bytes)?), - ForkName::Gloas => { - Self::Gloas(LightClientOptimisticUpdateGloas::from_ssz_bytes(bytes)?) - } - ForkName::Base => { + // TODO(gloas): implement Gloas light client + ForkName::Base | ForkName::Gloas => { return Err(ssz::DecodeError::BytesInvalid(format!( "LightClientOptimisticUpdate decoding for {fork_name} not implemented" ))); @@ -213,7 +201,8 @@ impl LightClientOptimisticUpdate { ForkName::Deneb => as Encode>::ssz_fixed_len(), ForkName::Electra => as Encode>::ssz_fixed_len(), ForkName::Fulu => as Encode>::ssz_fixed_len(), - ForkName::Gloas => as Encode>::ssz_fixed_len(), + // TODO(gloas): implement Gloas light client + ForkName::Gloas => 0, }; fixed_len + LightClientHeader::::ssz_max_var_len_for_fork(fork_name) } @@ -266,7 +255,11 @@ impl<'de, E: EthSpec> ContextDeserialize<'de, ForkName> for LightClientOptimisti Self::Fulu(Deserialize::deserialize(deserializer).map_err(convert_err)?) } ForkName::Gloas => { - Self::Gloas(Deserialize::deserialize(deserializer).map_err(convert_err)?) + // TODO(EIP-7732): check if this is correct + return Err(serde::de::Error::custom(format!( + "LightClientBootstrap failed to deserialize: unsupported fork '{}'", + context + ))); } }) } @@ -304,10 +297,4 @@ mod tests { use crate::{LightClientOptimisticUpdateFulu, MainnetEthSpec}; ssz_tests!(LightClientOptimisticUpdateFulu); } - - #[cfg(test)] - mod gloas { - use crate::{LightClientOptimisticUpdateGloas, MainnetEthSpec}; - ssz_tests!(LightClientOptimisticUpdateGloas); - } } diff --git a/consensus/types/src/light_client/light_client_update.rs b/consensus/types/src/light_client/light_client_update.rs index aa7b800cc89..cd33f6ae547 100644 --- a/consensus/types/src/light_client/light_client_update.rs +++ b/consensus/types/src/light_client/light_client_update.rs @@ -21,7 +21,6 @@ use crate::{ light_client::{ LightClientError, LightClientHeader, LightClientHeaderAltair, LightClientHeaderCapella, LightClientHeaderDeneb, LightClientHeaderElectra, LightClientHeaderFulu, - LightClientHeaderGloas, }, sync_committee::{SyncAggregate, SyncCommittee}, test_utils::TestRandom, @@ -46,7 +45,7 @@ type NextSyncCommitteeBranchElectra = FixedVector { pub attested_header: LightClientHeaderElectra, #[superstruct(only(Fulu), partial_getter(rename = "attested_header_fulu"))] pub attested_header: LightClientHeaderFulu, - #[superstruct(only(Gloas), partial_getter(rename = "attested_header_gloas"))] - pub attested_header: LightClientHeaderGloas, /// The `SyncCommittee` used in the next period. pub next_sync_committee: Arc>, // Merkle proof for next sync committee @@ -102,7 +99,7 @@ pub struct LightClientUpdate { )] pub next_sync_committee_branch: NextSyncCommitteeBranch, #[superstruct( - only(Electra, Fulu, Gloas), + only(Electra, Fulu), partial_getter(rename = "next_sync_committee_branch_electra") )] pub next_sync_committee_branch: NextSyncCommitteeBranchElectra, @@ -117,8 +114,6 @@ pub struct LightClientUpdate { pub finalized_header: LightClientHeaderElectra, #[superstruct(only(Fulu), partial_getter(rename = "finalized_header_fulu"))] pub finalized_header: LightClientHeaderFulu, - #[superstruct(only(Gloas), partial_getter(rename = "finalized_header_gloas"))] - pub finalized_header: LightClientHeaderGloas, /// Merkle proof attesting finalized header. #[superstruct( only(Altair, Capella, Deneb), @@ -126,7 +121,7 @@ pub struct LightClientUpdate { )] pub finality_branch: FinalityBranch, #[superstruct( - only(Electra, Fulu, Gloas), + only(Electra, Fulu), partial_getter(rename = "finality_branch_electra") )] pub finality_branch: FinalityBranchElectra, @@ -145,7 +140,8 @@ impl<'de, E: EthSpec> ContextDeserialize<'de, ForkName> for LightClientUpdate serde::de::Error::custom(format!("LightClientUpdate failed to deserialize: {:?}", e)) }; Ok(match context { - ForkName::Base => { + // TODO(gloas): implement Gloas light client + ForkName::Base | ForkName::Gloas => { return Err(serde::de::Error::custom(format!( "LightClientUpdate failed to deserialize: unsupported fork '{}'", context @@ -166,9 +162,6 @@ impl<'de, E: EthSpec> ContextDeserialize<'de, ForkName> for LightClientUpdate ForkName::Fulu => { Self::Fulu(Deserialize::deserialize(deserializer).map_err(convert_err)?) } - ForkName::Gloas => { - Self::Gloas(Deserialize::deserialize(deserializer).map_err(convert_err)?) - } }) } } @@ -330,36 +323,11 @@ impl LightClientUpdate { signature_slot: block_slot, }) } - fork_name @ ForkName::Gloas => { - let attested_header = - LightClientHeaderGloas::block_to_light_client_header(attested_block)?; - - let finalized_header = if let Some(finalized_block) = finalized_block { - if finalized_block.fork_name_unchecked() == fork_name { - LightClientHeaderGloas::block_to_light_client_header(finalized_block)? - } else { - LightClientHeaderGloas::default() - } - } else { - LightClientHeaderGloas::default() - }; - - Self::Gloas(LightClientUpdateGloas { - attested_header, - next_sync_committee, - next_sync_committee_branch: next_sync_committee_branch - .try_into() - .map_err(LightClientError::SszTypesError)?, - finalized_header, - finality_branch: finality_branch - .try_into() - .map_err(LightClientError::SszTypesError)?, - sync_aggregate: sync_aggregate.clone(), - signature_slot: block_slot, - }) - } // To add a new fork, just append the new fork variant on the latest fork. Forks that - // have a distinct execution header will need a new LightClientUpdate variant only - // if you need to test or support lightclient usages + // To add a new fork, just append the new fork variant on the latest fork. Forks that + // have a distinct execution header will need a new LightClientUpdate variant only + // if you need to test or support lightclient usages + // TODO(gloas): implement Gloas light client + ForkName::Gloas => return Err(LightClientError::GloasNotImplemented), }; Ok(light_client_update) @@ -374,8 +342,8 @@ impl LightClientUpdate { ForkName::Deneb => Self::Deneb(LightClientUpdateDeneb::from_ssz_bytes(bytes)?), ForkName::Electra => Self::Electra(LightClientUpdateElectra::from_ssz_bytes(bytes)?), ForkName::Fulu => Self::Fulu(LightClientUpdateFulu::from_ssz_bytes(bytes)?), - ForkName::Gloas => Self::Gloas(LightClientUpdateGloas::from_ssz_bytes(bytes)?), - ForkName::Base => { + // TODO(gloas): implement Gloas light client + ForkName::Base | ForkName::Gloas => { return Err(ssz::DecodeError::BytesInvalid(format!( "LightClientUpdate decoding for {fork_name} not implemented" ))); @@ -392,7 +360,6 @@ impl LightClientUpdate { LightClientUpdate::Deneb(update) => update.attested_header.beacon.slot, LightClientUpdate::Electra(update) => update.attested_header.beacon.slot, LightClientUpdate::Fulu(update) => update.attested_header.beacon.slot, - LightClientUpdate::Gloas(update) => update.attested_header.beacon.slot, } } @@ -403,7 +370,6 @@ impl LightClientUpdate { LightClientUpdate::Deneb(update) => update.finalized_header.beacon.slot, LightClientUpdate::Electra(update) => update.finalized_header.beacon.slot, LightClientUpdate::Fulu(update) => update.finalized_header.beacon.slot, - LightClientUpdate::Gloas(update) => update.finalized_header.beacon.slot, } } @@ -533,7 +499,8 @@ impl LightClientUpdate { ForkName::Deneb => as Encode>::ssz_fixed_len(), ForkName::Electra => as Encode>::ssz_fixed_len(), ForkName::Fulu => as Encode>::ssz_fixed_len(), - ForkName::Gloas => as Encode>::ssz_fixed_len(), + // TODO(gloas): implement Gloas light client + ForkName::Gloas => 0, }; fixed_len + 2 * LightClientHeader::::ssz_max_var_len_for_fork(fork_name) } @@ -548,7 +515,6 @@ impl LightClientUpdate { Self::Deneb(_) => func(ForkName::Deneb), Self::Electra(_) => func(ForkName::Electra), Self::Fulu(_) => func(ForkName::Fulu), - Self::Gloas(_) => func(ForkName::Gloas), } } } @@ -612,13 +578,6 @@ mod tests { ssz_tests!(LightClientUpdateFulu); } - #[cfg(test)] - mod gloas { - use super::*; - use crate::MainnetEthSpec; - ssz_tests!(LightClientUpdateGloas); - } - #[test] fn finalized_root_params() { assert!(2usize.pow(FINALIZED_ROOT_PROOF_LEN as u32) <= FINALIZED_ROOT_INDEX); diff --git a/consensus/types/src/light_client/mod.rs b/consensus/types/src/light_client/mod.rs index 4e287c22942..24f3fdbb55e 100644 --- a/consensus/types/src/light_client/mod.rs +++ b/consensus/types/src/light_client/mod.rs @@ -11,27 +11,25 @@ pub use error::LightClientError; pub use light_client_bootstrap::{ LightClientBootstrap, LightClientBootstrapAltair, LightClientBootstrapCapella, LightClientBootstrapDeneb, LightClientBootstrapElectra, LightClientBootstrapFulu, - LightClientBootstrapGloas, }; pub use light_client_finality_update::{ LightClientFinalityUpdate, LightClientFinalityUpdateAltair, LightClientFinalityUpdateCapella, LightClientFinalityUpdateDeneb, LightClientFinalityUpdateElectra, - LightClientFinalityUpdateFulu, LightClientFinalityUpdateGloas, + LightClientFinalityUpdateFulu, }; pub use light_client_header::{ LightClientHeader, LightClientHeaderAltair, LightClientHeaderCapella, LightClientHeaderDeneb, - LightClientHeaderElectra, LightClientHeaderFulu, LightClientHeaderGloas, + LightClientHeaderElectra, LightClientHeaderFulu, }; pub use light_client_optimistic_update::{ LightClientOptimisticUpdate, LightClientOptimisticUpdateAltair, LightClientOptimisticUpdateCapella, LightClientOptimisticUpdateDeneb, LightClientOptimisticUpdateElectra, LightClientOptimisticUpdateFulu, - LightClientOptimisticUpdateGloas, }; pub use light_client_update::{ CurrentSyncCommitteeProofLen, CurrentSyncCommitteeProofLenElectra, ExecutionPayloadProofLen, FinalizedRootProofLen, FinalizedRootProofLenElectra, LightClientUpdate, LightClientUpdateAltair, LightClientUpdateCapella, LightClientUpdateDeneb, - LightClientUpdateElectra, LightClientUpdateFulu, LightClientUpdateGloas, - NextSyncCommitteeProofLen, NextSyncCommitteeProofLenElectra, + LightClientUpdateElectra, LightClientUpdateFulu, NextSyncCommitteeProofLen, + NextSyncCommitteeProofLenElectra, }; diff --git a/consensus/types/src/state/beacon_state.rs b/consensus/types/src/state/beacon_state.rs index f36c02ce6bd..c1b6f0dc0c1 100644 --- a/consensus/types/src/state/beacon_state.rs +++ b/consensus/types/src/state/beacon_state.rs @@ -23,6 +23,7 @@ use tree_hash_derive::TreeHash; use typenum::Unsigned; use crate::{ + BuilderPendingPayment, BuilderPendingWithdrawal, ExecutionBlockHash, ExecutionPayloadBid, attestation::{ AttestationDuty, BeaconCommittee, Checkpoint, CommitteeIndex, ParticipationFlags, PendingAttestation, @@ -34,7 +35,7 @@ use crate::{ execution::{ Eth1Data, ExecutionPayloadHeaderBellatrix, ExecutionPayloadHeaderCapella, ExecutionPayloadHeaderDeneb, ExecutionPayloadHeaderElectra, ExecutionPayloadHeaderFulu, - ExecutionPayloadHeaderGloas, ExecutionPayloadHeaderRef, ExecutionPayloadHeaderRefMut, + ExecutionPayloadHeaderRef, ExecutionPayloadHeaderRefMut, }, fork::{Fork, ForkName, ForkVersionDecode, InconsistentFork, map_fork_name}, light_client::consts::{ @@ -542,14 +543,9 @@ where )] #[metastruct(exclude_from(tree_lists))] pub latest_execution_payload_header: ExecutionPayloadHeaderFulu, - #[superstruct( - only(Gloas), - partial_getter(rename = "latest_execution_payload_header_gloas") - )] + #[superstruct(only(Gloas))] #[metastruct(exclude_from(tree_lists))] - pub latest_execution_payload_header: ExecutionPayloadHeaderGloas, - - // Capella + pub latest_execution_payload_bid: ExecutionPayloadBid, #[superstruct(only(Capella, Deneb, Electra, Fulu, Gloas), partial_getter(copy))] #[serde(with = "serde_utils::quoted_u64")] #[metastruct(exclude_from(tree_lists))] @@ -608,6 +604,31 @@ where pub proposer_lookahead: Vector, // Gloas + #[test_random(default)] + #[superstruct(only(Gloas))] + #[metastruct(exclude_from(tree_lists))] + pub execution_payload_availability: BitVector, + + #[compare_fields(as_iter)] + #[test_random(default)] + #[superstruct(only(Gloas))] + pub builder_pending_payments: Vector, + + #[compare_fields(as_iter)] + #[test_random(default)] + #[superstruct(only(Gloas))] + pub builder_pending_withdrawals: + List, + + #[test_random(default)] + #[superstruct(only(Gloas))] + #[metastruct(exclude_from(tree_lists))] + pub latest_block_hash: ExecutionBlockHash, + + #[test_random(default)] + #[superstruct(only(Gloas))] + #[metastruct(exclude_from(tree_lists))] + pub latest_withdrawals_root: Hash256, // Caching (not in the spec) #[serde(skip_serializing, skip_deserializing)] @@ -1169,9 +1190,8 @@ impl BeaconState { BeaconState::Fulu(state) => Ok(ExecutionPayloadHeaderRef::Fulu( &state.latest_execution_payload_header, )), - BeaconState::Gloas(state) => Ok(ExecutionPayloadHeaderRef::Gloas( - &state.latest_execution_payload_header, - )), + // TODO(EIP-7732): investigate calling functions + BeaconState::Gloas(_) => Err(BeaconStateError::IncorrectStateVariant), } } @@ -1197,9 +1217,8 @@ impl BeaconState { BeaconState::Fulu(state) => Ok(ExecutionPayloadHeaderRefMut::Fulu( &mut state.latest_execution_payload_header, )), - BeaconState::Gloas(state) => Ok(ExecutionPayloadHeaderRefMut::Gloas( - &mut state.latest_execution_payload_header, - )), + // TODO(EIP-7732): investigate calling functions + BeaconState::Gloas(_) => Err(BeaconStateError::IncorrectStateVariant), } } @@ -2273,6 +2292,21 @@ impl BeaconState { } } + pub fn is_parent_block_full(&self) -> bool { + match self { + BeaconState::Base(_) | BeaconState::Altair(_) => false, + // TODO(EIP-7732): check the implications of this when we get to forkchoice modifications + BeaconState::Bellatrix(_) + | BeaconState::Capella(_) + | BeaconState::Deneb(_) + | BeaconState::Electra(_) + | BeaconState::Fulu(_) => true, + BeaconState::Gloas(state) => { + state.latest_execution_payload_bid.block_hash == state.latest_block_hash + } + } + } + /// Get the committee cache for some `slot`. /// /// Return an error if the cache for the slot's epoch is not initialized. diff --git a/testing/ef_tests/src/cases/operations.rs b/testing/ef_tests/src/cases/operations.rs index 379fcb1bb4d..a53bce927cb 100644 --- a/testing/ef_tests/src/cases/operations.rs +++ b/testing/ef_tests/src/cases/operations.rs @@ -307,6 +307,7 @@ impl Operation for BeaconBlockBody> { ForkName::Deneb => BeaconBlockBody::Deneb(<_>::from_ssz_bytes(bytes)?), ForkName::Electra => BeaconBlockBody::Electra(<_>::from_ssz_bytes(bytes)?), ForkName::Fulu => BeaconBlockBody::Fulu(<_>::from_ssz_bytes(bytes)?), + // TODO(EIP-7732): See if we need to handle Gloas here _ => panic!(), }) }) @@ -366,6 +367,7 @@ impl Operation for BeaconBlockBody> { let inner = >>::from_ssz_bytes(bytes)?; BeaconBlockBody::Fulu(inner.clone_as_blinded()) } + // TODO(EIP-7732): See if we need to handle Gloas here _ => panic!(), }) }) @@ -417,6 +419,7 @@ impl Operation for WithdrawalsPayload { spec: &ChainSpec, _: &Operations, ) -> Result<(), BlockProcessingError> { + // TODO(EIP-7732): implement separate gloas and non-gloas variants of process_withdrawals process_withdrawals::<_, FullPayload<_>>(state, self.payload.to_ref(), spec) } } From 4c268bc0d576da5f3d0543aa431b7c09c40d536c Mon Sep 17 00:00:00 2001 From: Michael Sproul Date: Tue, 16 Dec 2025 20:02:31 +1100 Subject: [PATCH 3/4] Delete `PartialBeaconState` (#8591) While reviewing Gloas I noticed we were updating `PartialBeaconState`. This code isn't used since v7.1.0 introduced hdiffs, so we can delete it and stop maintaining it :tada: Similarly the `chunked_vector`/`chunked_iter` code can also go! Co-Authored-By: Michael Sproul Co-Authored-By: Pawan Dhananjay --- beacon_node/store/src/chunked_iter.rs | 120 --- beacon_node/store/src/chunked_vector.rs | 922 ------------------ beacon_node/store/src/errors.rs | 9 - beacon_node/store/src/lib.rs | 3 - beacon_node/store/src/partial_beacon_state.rs | 511 ---------- 5 files changed, 1565 deletions(-) delete mode 100644 beacon_node/store/src/chunked_iter.rs delete mode 100644 beacon_node/store/src/chunked_vector.rs delete mode 100644 beacon_node/store/src/partial_beacon_state.rs diff --git a/beacon_node/store/src/chunked_iter.rs b/beacon_node/store/src/chunked_iter.rs deleted file mode 100644 index 72e5d9c7af0..00000000000 --- a/beacon_node/store/src/chunked_iter.rs +++ /dev/null @@ -1,120 +0,0 @@ -use crate::chunked_vector::{Chunk, Field, chunk_key}; -use crate::{HotColdDB, ItemStore}; -use tracing::error; -use types::{ChainSpec, EthSpec, Slot}; - -/// Iterator over the values of a `BeaconState` vector field (like `block_roots`). -/// -/// Uses the freezer DB's separate table to load the values. -pub struct ChunkedVectorIter<'a, F, E, Hot, Cold> -where - F: Field, - E: EthSpec, - Hot: ItemStore, - Cold: ItemStore, -{ - pub(crate) store: &'a HotColdDB, - current_vindex: usize, - pub(crate) end_vindex: usize, - next_cindex: usize, - current_chunk: Chunk, -} - -impl<'a, F, E, Hot, Cold> ChunkedVectorIter<'a, F, E, Hot, Cold> -where - F: Field, - E: EthSpec, - Hot: ItemStore, - Cold: ItemStore, -{ - /// Create a new iterator which can yield elements from `start_vindex` up to the last - /// index stored by the restore point at `last_restore_point_slot`. - /// - /// The `freezer_upper_limit` slot should be the slot of a recent restore point as obtained from - /// `Root::freezer_upper_limit`. We pass it as a parameter so that the caller can - /// maintain a stable view of the database (see `HybridForwardsBlockRootsIterator`). - pub fn new( - store: &'a HotColdDB, - start_vindex: usize, - freezer_upper_limit: Slot, - spec: &ChainSpec, - ) -> Self { - let (_, end_vindex) = F::start_and_end_vindex(freezer_upper_limit, spec); - - // Set the next chunk to the one containing `start_vindex`. - let next_cindex = start_vindex / F::chunk_size(); - // Set the current chunk to the empty chunk, it will never be read. - let current_chunk = Chunk::default(); - - Self { - store, - current_vindex: start_vindex, - end_vindex, - next_cindex, - current_chunk, - } - } -} - -impl Iterator for ChunkedVectorIter<'_, F, E, Hot, Cold> -where - F: Field, - E: EthSpec, - Hot: ItemStore, - Cold: ItemStore, -{ - type Item = (usize, F::Value); - - fn next(&mut self) -> Option { - let chunk_size = F::chunk_size(); - - // Range exhausted, return `None` forever. - if self.current_vindex >= self.end_vindex { - None - } - // Value lies in the current chunk, return it. - else if self.current_vindex < self.next_cindex * chunk_size { - let vindex = self.current_vindex; - let val = self - .current_chunk - .values - .get(vindex % chunk_size) - .cloned() - .or_else(|| { - error!( - vector_index = vindex, - "Missing chunk value in forwards iterator" - ); - None - })?; - self.current_vindex += 1; - Some((vindex, val)) - } - // Need to load the next chunk, load it and recurse back into the in-range case. - else { - self.current_chunk = Chunk::load( - &self.store.cold_db, - F::column(), - &chunk_key(self.next_cindex), - ) - .map_err(|e| { - error!( - chunk_index = self.next_cindex, - error = ?e, - "Database error in forwards iterator" - ); - e - }) - .ok()? - .or_else(|| { - error!( - chunk_index = self.next_cindex, - "Missing chunk in forwards iterator" - ); - None - })?; - self.next_cindex += 1; - self.next() - } - } -} diff --git a/beacon_node/store/src/chunked_vector.rs b/beacon_node/store/src/chunked_vector.rs deleted file mode 100644 index 9c8114e0c14..00000000000 --- a/beacon_node/store/src/chunked_vector.rs +++ /dev/null @@ -1,922 +0,0 @@ -//! Space-efficient storage for `BeaconState` vector fields. -//! -//! This module provides logic for splitting the `Vector` fields of a `BeaconState` into -//! chunks, and storing those chunks in contiguous ranges in the on-disk database. The motiviation -//! for doing this is avoiding massive duplication in every on-disk state. For example, rather than -//! storing the whole `historical_roots` vector, which is updated once every couple of thousand -//! slots, at every slot, we instead store all the historical values as a chunked vector on-disk, -//! and fetch only the slice we need when reconstructing the `historical_roots` of a state. -//! -//! ## Terminology -//! -//! * **Chunk size**: the number of vector values stored per on-disk chunk. -//! * **Vector index** (vindex): index into all the historical values, identifying a single element -//! of the vector being stored. -//! * **Chunk index** (cindex): index into the keyspace of the on-disk database, identifying a chunk -//! of elements. To find the chunk index of a vector index: `cindex = vindex / chunk_size`. -use self::UpdatePattern::*; -use crate::*; -use milhouse::{List, Vector}; -use ssz::{Decode, Encode}; -use typenum::Unsigned; -use types::historical_summary::HistoricalSummary; - -/// Description of how a `BeaconState` field is updated during state processing. -/// -/// When storing a state, this allows us to efficiently store only those entries -/// which are not present in the DB already. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum UpdatePattern { - /// The value is updated once per `n` slots. - OncePerNSlots { - n: u64, - /// The slot at which the field begins to accumulate values. - /// - /// The field should not be read or written until `activation_slot` is reached, and the - /// activation slot should act as an offset when converting slots to vector indices. - activation_slot: Option, - /// The slot at which the field ceases to accumulate values. - /// - /// If this is `None` then the field is continually updated. - deactivation_slot: Option, - }, - /// The value is updated once per epoch, for the epoch `current_epoch - lag`. - OncePerEpoch { lag: u64 }, -} - -/// Map a chunk index to bytes that can be used to key the NoSQL database. -/// -/// We shift chunks up by 1 to make room for a genesis chunk that is handled separately. -pub fn chunk_key(cindex: usize) -> [u8; 8] { - (cindex as u64 + 1).to_be_bytes() -} - -/// Return the database key for the genesis value. -fn genesis_value_key() -> [u8; 8] { - 0u64.to_be_bytes() -} - -/// Trait for types representing fields of the `BeaconState`. -/// -/// All of the required methods are type-level, because we do most things with fields at the -/// type-level. We require their value-level witnesses to be `Copy` so that we can avoid the -/// turbofish when calling functions like `store_updated_vector`. -pub trait Field: Copy { - /// The type of value stored in this field: the `T` from `Vector`. - /// - /// The `Default` impl will be used to fill extra vector entries. - type Value: Default + std::fmt::Debug + milhouse::Value; - // Decode + Encode + Default + Clone + PartialEq + std::fmt::Debug - - /// The length of this field: the `N` from `Vector`. - type Length: Unsigned; - - /// The database column where the integer-indexed chunks for this field should be stored. - /// - /// Each field's column **must** be unique. - fn column() -> DBColumn; - - /// Update pattern for this field, so that we can do differential updates. - fn update_pattern(spec: &ChainSpec) -> UpdatePattern; - - /// The number of values to store per chunk on disk. - /// - /// Default is 128 so that we read/write 4K pages when the values are 32 bytes. - // TODO: benchmark and optimise this parameter - fn chunk_size() -> usize { - 128 - } - - /// Convert a v-index (vector index) to a chunk index. - fn chunk_index(vindex: usize) -> usize { - vindex / Self::chunk_size() - } - - /// Get the value of this field at the given vector index, from the state. - fn get_value( - state: &BeaconState, - vindex: u64, - spec: &ChainSpec, - ) -> Result; - - /// True if this is a `FixedLengthField`, false otherwise. - fn is_fixed_length() -> bool; - - /// Compute the start and end vector indices of the slice of history required at `current_slot`. - /// - /// ## Example - /// - /// If we have a field that is updated once per epoch, then the end vindex will be - /// `current_epoch + 1`, because we want to include the value for the current epoch, and the - /// start vindex will be `end_vindex - Self::Length`, because that's how far back we can look. - fn start_and_end_vindex(current_slot: Slot, spec: &ChainSpec) -> (usize, usize) { - // We take advantage of saturating subtraction on slots and epochs - match Self::update_pattern(spec) { - OncePerNSlots { - n, - activation_slot, - deactivation_slot, - } => { - // Per-slot changes exclude the index for the current slot, because - // it won't be set until the slot completes (think of `state_roots`, `block_roots`). - // This also works for the `historical_roots` because at the `n`th slot, the 0th - // entry of the list is created, and before that the list is empty. - // - // To account for the switch from historical roots to historical summaries at - // Capella we also modify the current slot by the activation and deactivation slots. - // The activation slot acts as an offset (subtraction) while the deactivation slot - // acts as a clamp (min). - let slot_with_clamp = deactivation_slot.map_or(current_slot, |deactivation_slot| { - std::cmp::min(current_slot, deactivation_slot) - }); - let slot_with_clamp_and_offset = if let Some(activation_slot) = activation_slot { - slot_with_clamp - activation_slot - } else { - // Return (0, 0) to indicate that the field should not be read/written. - return (0, 0); - }; - let end_vindex = slot_with_clamp_and_offset / n; - let start_vindex = end_vindex - Self::Length::to_u64(); - (start_vindex.as_usize(), end_vindex.as_usize()) - } - OncePerEpoch { lag } => { - // Per-epoch changes include the index for the current epoch, because it - // will have been set at the most recent epoch boundary. - let current_epoch = current_slot.epoch(E::slots_per_epoch()); - let end_epoch = current_epoch + 1 - lag; - let start_epoch = end_epoch + lag - Self::Length::to_u64(); - (start_epoch.as_usize(), end_epoch.as_usize()) - } - } - } - - /// Given an `existing_chunk` stored in the DB, construct an updated chunk to replace it. - fn get_updated_chunk( - existing_chunk: &Chunk, - chunk_index: usize, - start_vindex: usize, - end_vindex: usize, - state: &BeaconState, - spec: &ChainSpec, - ) -> Result, Error> { - let chunk_size = Self::chunk_size(); - let mut new_chunk = Chunk::new(vec![Self::Value::default(); chunk_size]); - - for i in 0..chunk_size { - let vindex = chunk_index * chunk_size + i; - if vindex >= start_vindex && vindex < end_vindex { - let vector_value = Self::get_value(state, vindex as u64, spec)?; - - if let Some(existing_value) = existing_chunk.values.get(i) - && *existing_value != vector_value - && *existing_value != Self::Value::default() - { - return Err(ChunkError::Inconsistent { - field: Self::column(), - chunk_index, - existing_value: format!("{:?}", existing_value), - new_value: format!("{:?}", vector_value), - } - .into()); - } - - new_chunk.values[i] = vector_value; - } else { - new_chunk.values[i] = existing_chunk.values.get(i).cloned().unwrap_or_default(); - } - } - - Ok(new_chunk) - } - - /// Determine whether a state at `slot` possesses (or requires) the genesis value. - fn slot_needs_genesis_value(slot: Slot, spec: &ChainSpec) -> bool { - let (_, end_vindex) = Self::start_and_end_vindex(slot, spec); - match Self::update_pattern(spec) { - // If the end_vindex is less than the length of the vector, then the vector - // has not yet been completely filled with non-genesis values, and so the genesis - // value is still required. - OncePerNSlots { .. } => { - Self::is_fixed_length() && end_vindex < Self::Length::to_usize() - } - // If the field has lag, then it takes an extra `lag` vindices beyond the - // `end_vindex` before the vector has been filled with non-genesis values. - OncePerEpoch { lag } => { - Self::is_fixed_length() && end_vindex + (lag as usize) < Self::Length::to_usize() - } - } - } - - /// Load the genesis value for a fixed length field from the store. - /// - /// This genesis value should be used to fill the initial state of the vector. - fn load_genesis_value>(store: &S) -> Result { - let key = &genesis_value_key()[..]; - let chunk = - Chunk::load(store, Self::column(), key)?.ok_or(ChunkError::MissingGenesisValue)?; - chunk - .values - .first() - .cloned() - .ok_or_else(|| ChunkError::MissingGenesisValue.into()) - } - - /// Store the given `value` as the genesis value for this field, unless stored already. - /// - /// Check the existing value (if any) for consistency with the value we intend to store, and - /// return an error if they are inconsistent. - fn check_and_store_genesis_value>( - store: &S, - value: Self::Value, - ops: &mut Vec, - ) -> Result<(), Error> { - let key = &genesis_value_key()[..]; - - if let Some(existing_chunk) = Chunk::::load(store, Self::column(), key)? { - if existing_chunk.values.len() != 1 { - Err(ChunkError::InvalidGenesisChunk { - field: Self::column(), - expected_len: 1, - observed_len: existing_chunk.values.len(), - } - .into()) - } else if existing_chunk.values[0] != value { - Err(ChunkError::InconsistentGenesisValue { - field: Self::column(), - existing_value: format!("{:?}", existing_chunk.values[0]), - new_value: format!("{:?}", value), - } - .into()) - } else { - Ok(()) - } - } else { - let chunk = Chunk::new(vec![value]); - chunk.store(Self::column(), &genesis_value_key()[..], ops)?; - Ok(()) - } - } - - /// Extract the genesis value for a fixed length field from an - /// - /// Will only return a correct value if `slot_needs_genesis_value(state.slot(), spec) == true`. - fn extract_genesis_value( - state: &BeaconState, - spec: &ChainSpec, - ) -> Result { - let (_, end_vindex) = Self::start_and_end_vindex(state.slot(), spec); - match Self::update_pattern(spec) { - // Genesis value is guaranteed to exist at `end_vindex`, as it won't yet have been - // updated - OncePerNSlots { .. } => Ok(Self::get_value(state, end_vindex as u64, spec)?), - // If there's lag, the value of the field at the vindex *without the lag* - // should still be set to the genesis value. - OncePerEpoch { lag } => Ok(Self::get_value(state, end_vindex as u64 + lag, spec)?), - } - } -} - -/// Marker trait for fixed-length fields (`Vector`). -pub trait FixedLengthField: Field {} - -/// Marker trait for variable-length fields (`List`). -pub trait VariableLengthField: Field {} - -/// Macro to implement the `Field` trait on a new unit struct type. -macro_rules! field { - ($struct_name:ident, $marker_trait:ident, $value_ty:ty, $length_ty:ty, $column:expr, - $update_pattern:expr, $get_value:expr) => { - #[derive(Clone, Copy)] - pub struct $struct_name; - - impl Field for $struct_name - where - E: EthSpec, - { - type Value = $value_ty; - type Length = $length_ty; - - fn column() -> DBColumn { - $column - } - - fn update_pattern(spec: &ChainSpec) -> UpdatePattern { - let update_pattern = $update_pattern; - update_pattern(spec) - } - - fn get_value( - state: &BeaconState, - vindex: u64, - spec: &ChainSpec, - ) -> Result { - let get_value = $get_value; - get_value(state, vindex, spec) - } - - fn is_fixed_length() -> bool { - stringify!($marker_trait) == "FixedLengthField" - } - } - - impl $marker_trait for $struct_name {} - }; -} - -field!( - BlockRootsChunked, - FixedLengthField, - Hash256, - E::SlotsPerHistoricalRoot, - DBColumn::BeaconBlockRootsChunked, - |_| OncePerNSlots { - n: 1, - activation_slot: Some(Slot::new(0)), - deactivation_slot: None - }, - |state: &BeaconState<_>, index, _| safe_modulo_vector_index(state.block_roots(), index) -); - -field!( - StateRootsChunked, - FixedLengthField, - Hash256, - E::SlotsPerHistoricalRoot, - DBColumn::BeaconStateRootsChunked, - |_| OncePerNSlots { - n: 1, - activation_slot: Some(Slot::new(0)), - deactivation_slot: None, - }, - |state: &BeaconState<_>, index, _| safe_modulo_vector_index(state.state_roots(), index) -); - -field!( - HistoricalRoots, - VariableLengthField, - Hash256, - E::HistoricalRootsLimit, - DBColumn::BeaconHistoricalRoots, - |spec: &ChainSpec| OncePerNSlots { - n: E::SlotsPerHistoricalRoot::to_u64(), - activation_slot: Some(Slot::new(0)), - deactivation_slot: spec - .capella_fork_epoch - .map(|fork_epoch| fork_epoch.start_slot(E::slots_per_epoch())), - }, - |state: &BeaconState<_>, index, _| safe_modulo_list_index(state.historical_roots(), index) -); - -field!( - RandaoMixes, - FixedLengthField, - Hash256, - E::EpochsPerHistoricalVector, - DBColumn::BeaconRandaoMixes, - |_| OncePerEpoch { lag: 1 }, - |state: &BeaconState<_>, index, _| safe_modulo_vector_index(state.randao_mixes(), index) -); - -field!( - HistoricalSummaries, - VariableLengthField, - HistoricalSummary, - E::HistoricalRootsLimit, - DBColumn::BeaconHistoricalSummaries, - |spec: &ChainSpec| OncePerNSlots { - n: E::SlotsPerHistoricalRoot::to_u64(), - activation_slot: spec - .capella_fork_epoch - .map(|fork_epoch| fork_epoch.start_slot(E::slots_per_epoch())), - deactivation_slot: None, - }, - |state: &BeaconState<_>, index, _| safe_modulo_list_index( - state - .historical_summaries() - .map_err(|_| ChunkError::InvalidFork)?, - index - ) -); - -pub fn store_updated_vector, E: EthSpec, S: KeyValueStore>( - field: F, - store: &S, - state: &BeaconState, - spec: &ChainSpec, - ops: &mut Vec, -) -> Result<(), Error> { - let chunk_size = F::chunk_size(); - let (start_vindex, end_vindex) = F::start_and_end_vindex(state.slot(), spec); - let start_cindex = start_vindex / chunk_size; - let end_cindex = end_vindex / chunk_size; - - // Store the genesis value if we have access to it, and it hasn't been stored already. - if F::slot_needs_genesis_value(state.slot(), spec) { - let genesis_value = F::extract_genesis_value(state, spec)?; - F::check_and_store_genesis_value(store, genesis_value, ops)?; - } - - // Start by iterating backwards from the last chunk, storing new chunks in the database. - // Stop once a chunk in the database matches what we were about to store, this indicates - // that a previously stored state has already filled-in a portion of the indices covered. - let full_range_checked = store_range( - field, - (start_cindex..=end_cindex).rev(), - start_vindex, - end_vindex, - store, - state, - spec, - ops, - )?; - - // If the previous `store_range` did not check the entire range, it may be the case that the - // state's vector includes elements at low vector indices that are not yet stored in the - // database, so run another `store_range` to ensure these values are also stored. - if !full_range_checked { - store_range( - field, - start_cindex..end_cindex, - start_vindex, - end_vindex, - store, - state, - spec, - ops, - )?; - } - - Ok(()) -} - -#[allow(clippy::too_many_arguments)] -fn store_range( - _: F, - range: I, - start_vindex: usize, - end_vindex: usize, - store: &S, - state: &BeaconState, - spec: &ChainSpec, - ops: &mut Vec, -) -> Result -where - F: Field, - E: EthSpec, - S: KeyValueStore, - I: Iterator, -{ - for chunk_index in range { - let chunk_key = &chunk_key(chunk_index)[..]; - - let existing_chunk = - Chunk::::load(store, F::column(), chunk_key)?.unwrap_or_default(); - - let new_chunk = F::get_updated_chunk( - &existing_chunk, - chunk_index, - start_vindex, - end_vindex, - state, - spec, - )?; - - if new_chunk == existing_chunk { - return Ok(false); - } - - new_chunk.store(F::column(), chunk_key, ops)?; - } - - Ok(true) -} - -// Chunks at the end index are included. -// TODO: could be more efficient with a real range query (perhaps RocksDB) -fn range_query, E: EthSpec, T: Decode + Encode>( - store: &S, - column: DBColumn, - start_index: usize, - end_index: usize, -) -> Result>, Error> { - let range = start_index..=end_index; - let len = range - .end() - // Add one to account for inclusive range. - .saturating_add(1) - .saturating_sub(*range.start()); - let mut result = Vec::with_capacity(len); - - for chunk_index in range { - let key = &chunk_key(chunk_index)[..]; - let chunk = Chunk::load(store, column, key)?.ok_or(ChunkError::Missing { chunk_index })?; - result.push(chunk); - } - - Ok(result) -} - -/// Combine chunks to form a list or vector of all values with vindex in `start_vindex..end_vindex`. -/// -/// The `length` parameter is the length of the vec to construct, with entries set to `default` if -/// they lie outside the vindex range. -fn stitch( - chunks: Vec>, - start_vindex: usize, - end_vindex: usize, - chunk_size: usize, - length: usize, - default: T, -) -> Result, ChunkError> { - if start_vindex + length < end_vindex { - return Err(ChunkError::OversizedRange { - start_vindex, - end_vindex, - length, - }); - } - - let start_cindex = start_vindex / chunk_size; - let end_cindex = end_vindex / chunk_size; - - let mut result = vec![default; length]; - - for (chunk_index, chunk) in (start_cindex..=end_cindex).zip(chunks.into_iter()) { - // All chunks but the last chunk must be full-sized - if chunk_index != end_cindex && chunk.values.len() != chunk_size { - return Err(ChunkError::InvalidSize { - chunk_index, - expected: chunk_size, - actual: chunk.values.len(), - }); - } - - // Copy the chunk entries into the result vector - for (i, value) in chunk.values.into_iter().enumerate() { - let vindex = chunk_index * chunk_size + i; - - if vindex >= start_vindex && vindex < end_vindex { - result[vindex % length] = value; - } - } - } - - Ok(result) -} - -pub fn load_vector_from_db, E: EthSpec, S: KeyValueStore>( - store: &S, - slot: Slot, - spec: &ChainSpec, -) -> Result, Error> { - // Do a range query - let chunk_size = F::chunk_size(); - let (start_vindex, end_vindex) = F::start_and_end_vindex(slot, spec); - let start_cindex = start_vindex / chunk_size; - let end_cindex = end_vindex / chunk_size; - - let chunks = range_query(store, F::column(), start_cindex, end_cindex)?; - - let default = if F::slot_needs_genesis_value(slot, spec) { - F::load_genesis_value(store)? - } else { - F::Value::default() - }; - - let result = stitch( - chunks, - start_vindex, - end_vindex, - chunk_size, - F::Length::to_usize(), - default, - )?; - - Ok(Vector::new(result).map_err(ChunkError::Milhouse)?) -} - -/// The historical roots are stored in vector chunks, despite not actually being a vector. -pub fn load_variable_list_from_db, E: EthSpec, S: KeyValueStore>( - store: &S, - slot: Slot, - spec: &ChainSpec, -) -> Result, Error> { - let chunk_size = F::chunk_size(); - let (start_vindex, end_vindex) = F::start_and_end_vindex(slot, spec); - let start_cindex = start_vindex / chunk_size; - let end_cindex = end_vindex / chunk_size; - - let chunks: Vec> = range_query(store, F::column(), start_cindex, end_cindex)?; - - let mut result = Vec::with_capacity(chunk_size * chunks.len()); - - for (chunk_index, chunk) in chunks.into_iter().enumerate() { - for (i, value) in chunk.values.into_iter().enumerate() { - let vindex = chunk_index * chunk_size + i; - - if vindex >= start_vindex && vindex < end_vindex { - result.push(value); - } - } - } - - Ok(List::new(result).map_err(ChunkError::Milhouse)?) -} - -/// Index into a `List` field of the state, avoiding out of bounds and division by 0. -fn safe_modulo_list_index( - values: &List, - index: u64, -) -> Result { - if values.is_empty() { - Err(ChunkError::ZeroLengthList) - } else { - values - .get(index as usize % values.len()) - .copied() - .ok_or(ChunkError::IndexOutOfBounds { index }) - } -} - -fn safe_modulo_vector_index( - values: &Vector, - index: u64, -) -> Result { - if values.is_empty() { - Err(ChunkError::ZeroLengthVector) - } else { - values - .get(index as usize % values.len()) - .copied() - .ok_or(ChunkError::IndexOutOfBounds { index }) - } -} - -/// A chunk of a fixed-size vector from the `BeaconState`, stored in the database. -#[derive(Debug, Clone, PartialEq)] -pub struct Chunk { - /// A vector of up-to `chunk_size` values. - pub values: Vec, -} - -impl Default for Chunk -where - T: Decode + Encode, -{ - fn default() -> Self { - Chunk { values: vec![] } - } -} - -impl Chunk -where - T: Decode + Encode, -{ - pub fn new(values: Vec) -> Self { - Chunk { values } - } - - pub fn load, E: EthSpec>( - store: &S, - column: DBColumn, - key: &[u8], - ) -> Result, Error> { - store - .get_bytes(column, key)? - .map(|bytes| Self::decode(&bytes)) - .transpose() - } - - pub fn store( - &self, - column: DBColumn, - key: &[u8], - ops: &mut Vec, - ) -> Result<(), Error> { - ops.push(KeyValueStoreOp::PutKeyValue( - column, - key.to_vec(), - self.encode()?, - )); - Ok(()) - } - - /// Attempt to decode a single chunk. - pub fn decode(bytes: &[u8]) -> Result { - if !::is_ssz_fixed_len() { - return Err(Error::from(ChunkError::InvalidType)); - } - - let value_size = ::ssz_fixed_len(); - - if value_size == 0 { - return Err(Error::from(ChunkError::InvalidType)); - } - - let values = bytes - .chunks(value_size) - .map(T::from_ssz_bytes) - .collect::>()?; - - Ok(Chunk { values }) - } - - pub fn encoded_size(&self) -> usize { - self.values.len() * ::ssz_fixed_len() - } - - /// Encode a single chunk as bytes. - pub fn encode(&self) -> Result, Error> { - if !::is_ssz_fixed_len() { - return Err(Error::from(ChunkError::InvalidType)); - } - - Ok(self.values.iter().flat_map(T::as_ssz_bytes).collect()) - } -} - -#[derive(Debug, PartialEq)] -pub enum ChunkError { - ZeroLengthVector, - ZeroLengthList, - IndexOutOfBounds { - index: u64, - }, - InvalidSize { - chunk_index: usize, - expected: usize, - actual: usize, - }, - Missing { - chunk_index: usize, - }, - MissingGenesisValue, - Inconsistent { - field: DBColumn, - chunk_index: usize, - existing_value: String, - new_value: String, - }, - InconsistentGenesisValue { - field: DBColumn, - existing_value: String, - new_value: String, - }, - InvalidGenesisChunk { - field: DBColumn, - expected_len: usize, - observed_len: usize, - }, - InvalidType, - OversizedRange { - start_vindex: usize, - end_vindex: usize, - length: usize, - }, - InvalidFork, - Milhouse(milhouse::Error), -} - -impl From for ChunkError { - fn from(e: milhouse::Error) -> ChunkError { - Self::Milhouse(e) - } -} - -#[cfg(test)] -mod test { - use super::*; - use fixed_bytes::FixedBytesExtended; - use types::MainnetEthSpec as TestSpec; - use types::*; - - fn v(i: u64) -> Hash256 { - Hash256::from_low_u64_be(i) - } - - #[test] - fn stitch_default() { - let chunk_size = 4; - - let chunks = vec![ - Chunk::new(vec![0u64, 1, 2, 3]), - Chunk::new(vec![4, 5, 0, 0]), - ]; - - assert_eq!( - stitch(chunks, 2, 6, chunk_size, 12, 99).unwrap(), - vec![99, 99, 2, 3, 4, 5, 99, 99, 99, 99, 99, 99] - ); - } - - #[test] - fn stitch_basic() { - let chunk_size = 4; - let default = v(0); - - let chunks = vec![ - Chunk::new(vec![v(0), v(1), v(2), v(3)]), - Chunk::new(vec![v(4), v(5), v(6), v(7)]), - Chunk::new(vec![v(8), v(9), v(10), v(11)]), - ]; - - assert_eq!( - stitch(chunks.clone(), 0, 12, chunk_size, 12, default).unwrap(), - (0..12).map(v).collect::>() - ); - - assert_eq!( - stitch(chunks, 2, 10, chunk_size, 8, default).unwrap(), - vec![v(8), v(9), v(2), v(3), v(4), v(5), v(6), v(7)] - ); - } - - #[test] - fn stitch_oversized_range() { - let chunk_size = 4; - let default = 0; - - let chunks = vec![Chunk::new(vec![20u64, 21, 22, 23])]; - - // Args (start_vindex, end_vindex, length) - let args = vec![(0, 21, 20), (0, 2048, 1024), (0, 2, 1)]; - - for (start_vindex, end_vindex, length) in args { - assert_eq!( - stitch( - chunks.clone(), - start_vindex, - end_vindex, - chunk_size, - length, - default - ), - Err(ChunkError::OversizedRange { - start_vindex, - end_vindex, - length, - }) - ); - } - } - - #[test] - fn fixed_length_fields() { - fn test_fixed_length>(_: F, expected: bool) { - assert_eq!(F::is_fixed_length(), expected); - } - test_fixed_length(BlockRootsChunked, true); - test_fixed_length(StateRootsChunked, true); - test_fixed_length(HistoricalRoots, false); - test_fixed_length(RandaoMixes, true); - } - - fn needs_genesis_value_once_per_slot>(_: F) { - let spec = &TestSpec::default_spec(); - let max = F::Length::to_u64(); - for i in 0..max { - assert!( - F::slot_needs_genesis_value(Slot::new(i), spec), - "slot {}", - i - ); - } - assert!(!F::slot_needs_genesis_value(Slot::new(max), spec)); - } - - #[test] - fn needs_genesis_value_block_roots() { - needs_genesis_value_once_per_slot(BlockRootsChunked); - } - - #[test] - fn needs_genesis_value_state_roots() { - needs_genesis_value_once_per_slot(StateRootsChunked); - } - - #[test] - fn needs_genesis_value_historical_roots() { - let spec = &TestSpec::default_spec(); - assert!( - !>::slot_needs_genesis_value(Slot::new(0), spec) - ); - } - - fn needs_genesis_value_test_randao>(_: F) { - let spec = &TestSpec::default_spec(); - let max = TestSpec::slots_per_epoch() * (F::Length::to_u64() - 1); - for i in 0..max { - assert!( - F::slot_needs_genesis_value(Slot::new(i), spec), - "slot {}", - i - ); - } - assert!(!F::slot_needs_genesis_value(Slot::new(max), spec)); - } - - #[test] - fn needs_genesis_value_randao() { - needs_genesis_value_test_randao(RandaoMixes); - } -} diff --git a/beacon_node/store/src/errors.rs b/beacon_node/store/src/errors.rs index 6da99b7bd63..a07cc838863 100644 --- a/beacon_node/store/src/errors.rs +++ b/beacon_node/store/src/errors.rs @@ -1,4 +1,3 @@ -use crate::chunked_vector::ChunkError; use crate::config::StoreConfigError; use crate::hot_cold_store::{HotColdDBError, StateSummaryIteratorError}; use crate::{DBColumn, hdiff}; @@ -13,9 +12,7 @@ pub type Result = std::result::Result; #[derive(Debug)] pub enum Error { SszDecodeError(DecodeError), - VectorChunkError(ChunkError), BeaconStateError(BeaconStateError), - PartialBeaconStateError, HotColdDBError(HotColdDBError), DBError { message: String, @@ -126,12 +123,6 @@ impl From for Error { } } -impl From for Error { - fn from(e: ChunkError) -> Error { - Error::VectorChunkError(e) - } -} - impl From for Error { fn from(e: HotColdDBError) -> Error { Error::HotColdDBError(e) diff --git a/beacon_node/store/src/lib.rs b/beacon_node/store/src/lib.rs index a3d4e4a8cea..ae5b2e1e571 100644 --- a/beacon_node/store/src/lib.rs +++ b/beacon_node/store/src/lib.rs @@ -8,8 +8,6 @@ //! Provides a simple API for storing/retrieving all types that sometimes needs type-hints. See //! tests for implementation examples. pub mod blob_sidecar_list_from_root; -pub mod chunked_iter; -pub mod chunked_vector; pub mod config; pub mod consensus_context; pub mod errors; @@ -21,7 +19,6 @@ mod impls; mod memory_store; pub mod metadata; pub mod metrics; -pub mod partial_beacon_state; pub mod reconstruct; pub mod state_cache; diff --git a/beacon_node/store/src/partial_beacon_state.rs b/beacon_node/store/src/partial_beacon_state.rs deleted file mode 100644 index 9e5e1ebbb47..00000000000 --- a/beacon_node/store/src/partial_beacon_state.rs +++ /dev/null @@ -1,511 +0,0 @@ -use crate::chunked_vector::{ - BlockRootsChunked, HistoricalRoots, HistoricalSummaries, RandaoMixes, StateRootsChunked, - load_variable_list_from_db, load_vector_from_db, -}; -use crate::{DBColumn, Error, KeyValueStore, KeyValueStoreOp}; -use milhouse::{List, Vector}; -use ssz::{BitVector, Decode, DecodeError, Encode}; -use ssz_derive::{Decode, Encode}; -use std::sync::Arc; -use superstruct::superstruct; -use types::historical_summary::HistoricalSummary; -use types::*; - -/// DEPRECATED Lightweight variant of the `BeaconState` that is stored in the database. -/// -/// Utilises lazy-loading from separate storage for its vector fields. -/// -/// This can be deleted once schema versions prior to V22 are no longer supported. -#[superstruct( - variants(Base, Altair, Bellatrix, Capella, Deneb, Electra, Fulu, Gloas), - variant_attributes(derive(Debug, PartialEq, Clone, Encode, Decode)) -)] -#[derive(Debug, PartialEq, Clone, Encode)] -#[ssz(enum_behaviour = "transparent")] -pub struct PartialBeaconState -where - E: EthSpec, -{ - // Versioning - pub genesis_time: u64, - pub genesis_validators_root: Hash256, - #[superstruct(getter(copy))] - pub slot: Slot, - pub fork: Fork, - - // History - pub latest_block_header: BeaconBlockHeader, - - #[ssz(skip_serializing, skip_deserializing)] - pub block_roots: Option>, - #[ssz(skip_serializing, skip_deserializing)] - pub state_roots: Option>, - - #[ssz(skip_serializing, skip_deserializing)] - pub historical_roots: Option>, - - // Ethereum 1.0 chain data - pub eth1_data: Eth1Data, - pub eth1_data_votes: List, - pub eth1_deposit_index: u64, - - // Registry - pub validators: List, - pub balances: List, - - // Shuffling - /// Randao value from the current slot, for patching into the per-epoch randao vector. - pub latest_randao_value: Hash256, - #[ssz(skip_serializing, skip_deserializing)] - pub randao_mixes: Option>, - - // Slashings - slashings: Vector, - - // Attestations (genesis fork only) - #[superstruct(only(Base))] - pub previous_epoch_attestations: List, E::MaxPendingAttestations>, - #[superstruct(only(Base))] - pub current_epoch_attestations: List, E::MaxPendingAttestations>, - - // Participation (Altair and later) - #[superstruct(only(Altair, Bellatrix, Capella, Deneb, Electra, Fulu, Gloas))] - pub previous_epoch_participation: List, - #[superstruct(only(Altair, Bellatrix, Capella, Deneb, Electra, Fulu, Gloas))] - pub current_epoch_participation: List, - - // Finality - pub justification_bits: BitVector, - pub previous_justified_checkpoint: Checkpoint, - pub current_justified_checkpoint: Checkpoint, - pub finalized_checkpoint: Checkpoint, - - // Inactivity - #[superstruct(only(Altair, Bellatrix, Capella, Deneb, Electra, Fulu, Gloas))] - pub inactivity_scores: List, - - // Light-client sync committees - #[superstruct(only(Altair, Bellatrix, Capella, Deneb, Electra, Fulu, Gloas))] - pub current_sync_committee: Arc>, - #[superstruct(only(Altair, Bellatrix, Capella, Deneb, Electra, Fulu, Gloas))] - pub next_sync_committee: Arc>, - - // Execution - #[superstruct( - only(Bellatrix), - partial_getter(rename = "latest_execution_payload_header_bellatrix") - )] - pub latest_execution_payload_header: ExecutionPayloadHeaderBellatrix, - #[superstruct( - only(Capella), - partial_getter(rename = "latest_execution_payload_header_capella") - )] - pub latest_execution_payload_header: ExecutionPayloadHeaderCapella, - #[superstruct( - only(Deneb), - partial_getter(rename = "latest_execution_payload_header_deneb") - )] - pub latest_execution_payload_header: ExecutionPayloadHeaderDeneb, - #[superstruct( - only(Electra), - partial_getter(rename = "latest_execution_payload_header_electra") - )] - pub latest_execution_payload_header: ExecutionPayloadHeaderElectra, - #[superstruct( - only(Fulu), - partial_getter(rename = "latest_execution_payload_header_fulu") - )] - pub latest_execution_payload_header: ExecutionPayloadHeaderFulu, - - #[superstruct( - only(Gloas), - partial_getter(rename = "latest_execution_payload_bid_gloas") - )] - pub latest_execution_payload_bid: ExecutionPayloadBid, - - // Capella - #[superstruct(only(Capella, Deneb, Electra, Fulu, Gloas))] - pub next_withdrawal_index: u64, - #[superstruct(only(Capella, Deneb, Electra, Fulu, Gloas))] - pub next_withdrawal_validator_index: u64, - - #[ssz(skip_serializing, skip_deserializing)] - #[superstruct(only(Capella, Deneb, Electra, Fulu, Gloas))] - pub historical_summaries: Option>, - - // Electra - #[superstruct(only(Electra, Fulu, Gloas))] - pub deposit_requests_start_index: u64, - #[superstruct(only(Electra, Fulu, Gloas))] - pub deposit_balance_to_consume: u64, - #[superstruct(only(Electra, Fulu, Gloas))] - pub exit_balance_to_consume: u64, - #[superstruct(only(Electra, Fulu, Gloas))] - pub earliest_exit_epoch: Epoch, - #[superstruct(only(Electra, Fulu, Gloas))] - pub consolidation_balance_to_consume: u64, - #[superstruct(only(Electra, Fulu, Gloas))] - pub earliest_consolidation_epoch: Epoch, - - #[superstruct(only(Electra, Fulu, Gloas))] - pub pending_deposits: List, - #[superstruct(only(Electra, Fulu, Gloas))] - pub pending_partial_withdrawals: - List, - #[superstruct(only(Electra, Fulu, Gloas))] - pub pending_consolidations: List, - #[superstruct(only(Fulu, Gloas))] - pub proposer_lookahead: Vector, - - // Gloas - #[superstruct(only(Gloas))] - pub execution_payload_availability: BitVector, - - #[superstruct(only(Gloas))] - pub builder_pending_payments: Vector, - - #[superstruct(only(Gloas))] - pub builder_pending_withdrawals: - List, - - #[superstruct(only(Gloas))] - pub latest_block_hash: ExecutionBlockHash, - - #[superstruct(only(Gloas))] - pub latest_withdrawals_root: Hash256, -} - -impl PartialBeaconState { - /// SSZ decode. - pub fn from_ssz_bytes(bytes: &[u8], spec: &ChainSpec) -> Result { - // Slot is after genesis_time (u64) and genesis_validators_root (Hash256). - let slot_offset = ::ssz_fixed_len() + ::ssz_fixed_len(); - let slot_len = ::ssz_fixed_len(); - let slot_bytes = bytes.get(slot_offset..slot_offset + slot_len).ok_or( - DecodeError::InvalidByteLength { - len: bytes.len(), - expected: slot_offset + slot_len, - }, - )?; - - let slot = Slot::from_ssz_bytes(slot_bytes)?; - let fork_at_slot = spec.fork_name_at_slot::(slot); - - Ok(map_fork_name!( - fork_at_slot, - Self, - <_>::from_ssz_bytes(bytes)? - )) - } - - /// Prepare the partial state for storage in the KV database. - pub fn as_kv_store_op(&self, state_root: Hash256) -> KeyValueStoreOp { - KeyValueStoreOp::PutKeyValue( - DBColumn::BeaconState, - state_root.as_slice().to_vec(), - self.as_ssz_bytes(), - ) - } - - pub fn load_block_roots>( - &mut self, - store: &S, - spec: &ChainSpec, - ) -> Result<(), Error> { - if self.block_roots().is_none() { - *self.block_roots_mut() = Some(load_vector_from_db::( - store, - self.slot(), - spec, - )?); - } - Ok(()) - } - - pub fn load_state_roots>( - &mut self, - store: &S, - spec: &ChainSpec, - ) -> Result<(), Error> { - if self.state_roots().is_none() { - *self.state_roots_mut() = Some(load_vector_from_db::( - store, - self.slot(), - spec, - )?); - } - Ok(()) - } - - pub fn load_historical_roots>( - &mut self, - store: &S, - spec: &ChainSpec, - ) -> Result<(), Error> { - if self.historical_roots().is_none() { - *self.historical_roots_mut() = Some( - load_variable_list_from_db::(store, self.slot(), spec)?, - ); - } - Ok(()) - } - - pub fn load_historical_summaries>( - &mut self, - store: &S, - spec: &ChainSpec, - ) -> Result<(), Error> { - let slot = self.slot(); - if let Ok(historical_summaries) = self.historical_summaries_mut() - && historical_summaries.is_none() - { - *historical_summaries = Some(load_variable_list_from_db::( - store, slot, spec, - )?); - } - Ok(()) - } - - pub fn load_randao_mixes>( - &mut self, - store: &S, - spec: &ChainSpec, - ) -> Result<(), Error> { - if self.randao_mixes().is_none() { - // Load the per-epoch values from the database - let mut randao_mixes = - load_vector_from_db::(store, self.slot(), spec)?; - - // Patch the value for the current slot into the index for the current epoch - let current_epoch = self.slot().epoch(E::slots_per_epoch()); - let len = randao_mixes.len(); - *randao_mixes - .get_mut(current_epoch.as_usize() % len) - .ok_or(Error::RandaoMixOutOfBounds)? = *self.latest_randao_value(); - - *self.randao_mixes_mut() = Some(randao_mixes) - } - Ok(()) - } -} - -/// Implement the conversion from PartialBeaconState -> BeaconState. -macro_rules! impl_try_into_beacon_state { - ($inner:ident, $variant_name:ident, $struct_name:ident, [$($extra_fields:ident),*], [$($extra_opt_fields:ident),*]) => { - BeaconState::$variant_name($struct_name { - // Versioning - genesis_time: $inner.genesis_time, - genesis_validators_root: $inner.genesis_validators_root, - slot: $inner.slot, - fork: $inner.fork, - - // History - latest_block_header: $inner.latest_block_header, - block_roots: unpack_field($inner.block_roots)?, - state_roots: unpack_field($inner.state_roots)?, - historical_roots: unpack_field($inner.historical_roots)?, - - // Eth1 - eth1_data: $inner.eth1_data, - eth1_data_votes: $inner.eth1_data_votes, - eth1_deposit_index: $inner.eth1_deposit_index, - - // Validator registry - validators: $inner.validators, - balances: $inner.balances, - - // Shuffling - randao_mixes: unpack_field($inner.randao_mixes)?, - - // Slashings - slashings: $inner.slashings, - - // Finality - justification_bits: $inner.justification_bits, - previous_justified_checkpoint: $inner.previous_justified_checkpoint, - current_justified_checkpoint: $inner.current_justified_checkpoint, - finalized_checkpoint: $inner.finalized_checkpoint, - - // Caching - total_active_balance: <_>::default(), - progressive_balances_cache: <_>::default(), - committee_caches: <_>::default(), - pubkey_cache: <_>::default(), - exit_cache: <_>::default(), - slashings_cache: <_>::default(), - epoch_cache: <_>::default(), - - // Variant-specific fields - $( - $extra_fields: $inner.$extra_fields - ),*, - - // Variant-specific optional fields - $( - $extra_opt_fields: unpack_field($inner.$extra_opt_fields)? - ),* - }) - } -} - -fn unpack_field(x: Option) -> Result { - x.ok_or(Error::PartialBeaconStateError) -} - -impl TryInto> for PartialBeaconState { - type Error = Error; - - fn try_into(self) -> Result, Error> { - let state = match self { - PartialBeaconState::Base(inner) => impl_try_into_beacon_state!( - inner, - Base, - BeaconStateBase, - [previous_epoch_attestations, current_epoch_attestations], - [] - ), - PartialBeaconState::Altair(inner) => impl_try_into_beacon_state!( - inner, - Altair, - BeaconStateAltair, - [ - previous_epoch_participation, - current_epoch_participation, - current_sync_committee, - next_sync_committee, - inactivity_scores - ], - [] - ), - PartialBeaconState::Bellatrix(inner) => impl_try_into_beacon_state!( - inner, - Bellatrix, - BeaconStateBellatrix, - [ - previous_epoch_participation, - current_epoch_participation, - current_sync_committee, - next_sync_committee, - inactivity_scores, - latest_execution_payload_header - ], - [] - ), - PartialBeaconState::Capella(inner) => impl_try_into_beacon_state!( - inner, - Capella, - BeaconStateCapella, - [ - previous_epoch_participation, - current_epoch_participation, - current_sync_committee, - next_sync_committee, - inactivity_scores, - latest_execution_payload_header, - next_withdrawal_index, - next_withdrawal_validator_index - ], - [historical_summaries] - ), - PartialBeaconState::Deneb(inner) => impl_try_into_beacon_state!( - inner, - Deneb, - BeaconStateDeneb, - [ - previous_epoch_participation, - current_epoch_participation, - current_sync_committee, - next_sync_committee, - inactivity_scores, - latest_execution_payload_header, - next_withdrawal_index, - next_withdrawal_validator_index - ], - [historical_summaries] - ), - PartialBeaconState::Electra(inner) => impl_try_into_beacon_state!( - inner, - Electra, - BeaconStateElectra, - [ - previous_epoch_participation, - current_epoch_participation, - current_sync_committee, - next_sync_committee, - inactivity_scores, - latest_execution_payload_header, - next_withdrawal_index, - next_withdrawal_validator_index, - deposit_requests_start_index, - deposit_balance_to_consume, - exit_balance_to_consume, - earliest_exit_epoch, - consolidation_balance_to_consume, - earliest_consolidation_epoch, - pending_deposits, - pending_partial_withdrawals, - pending_consolidations - ], - [historical_summaries] - ), - PartialBeaconState::Fulu(inner) => impl_try_into_beacon_state!( - inner, - Fulu, - BeaconStateFulu, - [ - previous_epoch_participation, - current_epoch_participation, - current_sync_committee, - next_sync_committee, - inactivity_scores, - latest_execution_payload_header, - next_withdrawal_index, - next_withdrawal_validator_index, - deposit_requests_start_index, - deposit_balance_to_consume, - exit_balance_to_consume, - earliest_exit_epoch, - consolidation_balance_to_consume, - earliest_consolidation_epoch, - pending_deposits, - pending_partial_withdrawals, - pending_consolidations, - proposer_lookahead - ], - [historical_summaries] - ), - PartialBeaconState::Gloas(inner) => impl_try_into_beacon_state!( - inner, - Gloas, - BeaconStateGloas, - [ - previous_epoch_participation, - current_epoch_participation, - current_sync_committee, - next_sync_committee, - inactivity_scores, - latest_execution_payload_bid, - next_withdrawal_index, - next_withdrawal_validator_index, - deposit_requests_start_index, - deposit_balance_to_consume, - exit_balance_to_consume, - earliest_exit_epoch, - consolidation_balance_to_consume, - earliest_consolidation_epoch, - pending_deposits, - pending_partial_withdrawals, - pending_consolidations, - proposer_lookahead, - execution_payload_availability, - builder_pending_payments, - builder_pending_withdrawals, - latest_block_hash, - latest_withdrawals_root - ], - [historical_summaries] - ), - }; - Ok(state) - } -} From 4e35e9d5875679855fa2e9587bcc95cf905bc59d Mon Sep 17 00:00:00 2001 From: Michael Sproul Date: Tue, 16 Dec 2025 20:02:34 +1100 Subject: [PATCH 4/4] Add cargo deny on CI (#8580) Closes: - https://github.com/sigp/lighthouse/issues/8408 Add `cargo deny` on CI with deprecated crates (`ethers` and `ethereum-types`) banned and duplicates banned for `reqwest`. Co-Authored-By: Michael Sproul --- .github/workflows/test-suite.yml | 4 +++- Makefile | 9 +++++++++ deny.toml | 23 +++++++++++++++++++++++ 3 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 deny.toml diff --git a/.github/workflows/test-suite.yml b/.github/workflows/test-suite.yml index cc7282c3517..7344a9367b7 100644 --- a/.github/workflows/test-suite.yml +++ b/.github/workflows/test-suite.yml @@ -324,7 +324,7 @@ jobs: channel: stable cache-target: release components: rustfmt,clippy - bins: cargo-audit + bins: cargo-audit,cargo-deny - name: Check formatting with cargo fmt run: make cargo-fmt - name: Lint code for quality and style with Clippy @@ -337,6 +337,8 @@ jobs: run: make arbitrary-fuzz - name: Run cargo audit run: make audit-CI + - name: Run cargo deny + run: make deny-CI - name: Run cargo vendor to make sure dependencies can be vendored for packaging, reproducibility and archival purpose run: CARGO_HOME=$(readlink -f $HOME) make vendor - name: Markdown-linter diff --git a/Makefile b/Makefile index c1cccb92705..4426b941aaa 100644 --- a/Makefile +++ b/Makefile @@ -326,6 +326,15 @@ install-audit: audit-CI: cargo audit +# Runs cargo deny (check for banned crates, duplicate versions, and source restrictions) +deny: install-deny deny-CI + +install-deny: + cargo install --force cargo-deny --version 0.18.2 + +deny-CI: + cargo deny check bans sources --hide-inclusion-graph + # Runs `cargo vendor` to make sure dependencies can be vendored for packaging, reproducibility and archival purpose. vendor: cargo vendor diff --git a/deny.toml b/deny.toml new file mode 100644 index 00000000000..677396c0c34 --- /dev/null +++ b/deny.toml @@ -0,0 +1,23 @@ +# cargo-deny configuration for Lighthouse +# See https://embarkstudios.github.io/cargo-deny/ + +[bans] +# Warn when multiple versions of the same crate are detected +multiple-versions = "warn" +deny = [ + # Legacy Ethereum crates that have been replaced with alloy + { crate = "ethers", reason = "use alloy instead" }, + { crate = "ethereum-types", reason = "use alloy-primitives instead" }, + # Replaced by quick-protobuf + { crate = "protobuf", reason = "use quick-protobuf instead" }, + # Prevent duplicate versions of reqwest - heavy crate with build scripts + { crate = "reqwest", deny-multiple-versions = true, reason = "prevent duplicate versions" }, +] + +[sources] +unknown-registry = "deny" +unknown-git = "warn" +allow-registry = ["https://github.com/rust-lang/crates.io-index"] + +[sources.allow-org] +github = ["sigp"]