diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index e3de8d7324..fa561ce7af 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -5765,7 +5765,60 @@ impl BeaconChain { execution_payload_value, ) } - BeaconState::Gloas(_) => return Err(BlockProductionError::GloasNotImplemented), + 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, + ) + } }; let block = SignedBeaconBlock::from_block( @@ -6069,6 +6122,12 @@ impl BeaconChain { None }; + let slot_number = if prepare_slot_fork.gloas_enabled() { + Some(prepare_slot.as_u64()) + } else { + None + }; + let payload_attributes = PayloadAttributes::new( self.slot_clock .start_of(prepare_slot) @@ -6078,6 +6137,7 @@ impl BeaconChain { execution_layer.get_suggested_fee_recipient(proposer).await, withdrawals.map(Into::into), parent_beacon_block_root, + slot_number, ); execution_layer diff --git a/beacon_node/beacon_chain/src/execution_payload.rs b/beacon_node/beacon_chain/src/execution_payload.rs index 9459b1acd7..88f2adeef6 100644 --- a/beacon_node/beacon_chain/src/execution_payload.rs +++ b/beacon_node/beacon_chain/src/execution_payload.rs @@ -511,12 +511,20 @@ where let suggested_fee_recipient = execution_layer .get_suggested_fee_recipient(proposer_index) .await; + + let slot_number = if fork.gloas_enabled() { + Some(builder_params.slot.as_u64()) + } else { + None + }; + let payload_attributes = PayloadAttributes::new( timestamp, random, suggested_fee_recipient, withdrawals, parent_beacon_block_root, + slot_number, ); let target_gas_limit = execution_layer.get_proposer_gas_limit(proposer_index).await; diff --git a/beacon_node/beacon_chain/tests/payload_invalidation.rs b/beacon_node/beacon_chain/tests/payload_invalidation.rs index 5bd43835e3..a3b0af43e2 100644 --- a/beacon_node/beacon_chain/tests/payload_invalidation.rs +++ b/beacon_node/beacon_chain/tests/payload_invalidation.rs @@ -1021,6 +1021,7 @@ async fn payload_preparation() { fee_recipient, None, None, + None, ); assert_eq!(rig.previous_payload_attributes(), payload_attributes); } diff --git a/beacon_node/execution_layer/src/block_hash.rs b/beacon_node/execution_layer/src/block_hash.rs index e45bf477a2..2003024fa7 100644 --- a/beacon_node/execution_layer/src/block_hash.rs +++ b/beacon_node/execution_layer/src/block_hash.rs @@ -41,6 +41,14 @@ pub fn calculate_execution_block_hash( let rlp_excess_blob_gas = payload.excess_blob_gas().ok(); let requests_root = execution_requests.map(|requests| requests.requests_hash()); + // Calculate block access list root (Gloas/Amsterdam). + // The BAL root is the keccak256 hash of the BAL bytes. + let rlp_block_access_list_root = payload + .block_access_list() + .ok() + .map(|bal| keccak256(&bal[..])); + let rlp_slot_number = payload.slot_number().ok(); + // Construct the block header. let exec_block_header = ExecutionBlockHeader::from_payload( payload, @@ -51,6 +59,8 @@ pub fn calculate_execution_block_hash( rlp_excess_blob_gas, parent_beacon_block_root, requests_root, + rlp_block_access_list_root, + rlp_slot_number, ); // Hash the RLP encoding of the block header. @@ -122,8 +132,10 @@ mod test { excess_blob_gas: None, parent_beacon_block_root: None, requests_root: None, + block_access_list_root: None, + slot_number: None, }; - let expected_rlp = "f90200a0e0a94a7a3c9617401586b1a27025d2d9671332d22d540e0af72b069170380f2aa01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d4934794ba5e000000000000000000000000000000000000a0ec3c94b18b8a1cff7d60f8d258ec723312932928626b4c9355eb4ab3568ec7f7a050f738580ed699f0469702c7ccc63ed2e51bc034be9479b7bff4e68dee84accfa029b0562f7140574dd0d50dee8a271b22e1a0a7b78fca58f7c60370d8317ba2a9b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000830200000188016345785d8a00008301553482079e42a0000000000000000000000000000000000000000000000000000000000000000088000000000000000082036b"; + let expected_rlp = "f90200a0e0a94a7a3c9617401586b1a27025d2d9671332d22d540e0af72b069170380f2aa01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347394ba5e000000000000000000000000000000000000a0ec3c94b18b8a1cff7d60f8d258ec723312932928626b4c9355eb4ab3568ec7f7a050f738580ed699f0469702c7ccc63ed2e51bc034be9479b7bff4e68dee84accfa029b0562f7140574dd0d50dee8a271b22e1a0a7b78fca58f7c60370d8317ba2a9b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000830200000188016345785d8a00008301553482079e42a0000000000000000000000000000000000000000000000000000000000000000088000000000000000082036b"; let expected_hash = Hash256::from_str("6a251c7c3c5dca7b42407a3752ff48f3bbca1fab7f9868371d9918daf1988d1f") .unwrap(); @@ -154,6 +166,8 @@ mod test { excess_blob_gas: None, parent_beacon_block_root: None, requests_root: None, + block_access_list_root: None, + slot_number: None, }; let expected_rlp = "f901fda0927ca537f06c783a3a2635b8805eef1c8c2124f7444ad4a3389898dd832f2dbea01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d4934794ba5e000000000000000000000000000000000000a0e97859b065bd8dbbb4519c7cb935024de2484c2b7f881181b4360492f0b06b82a050f738580ed699f0469702c7ccc63ed2e51bc034be9479b7bff4e68dee84accfa029b0562f7140574dd0d50dee8a271b22e1a0a7b78fca58f7c60370d8317ba2a9b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800188016345785d8a00008301553482079e42a0000000000000000000000000000000000000000000000000000000000002000088000000000000000082036b"; let expected_hash = @@ -187,6 +201,8 @@ mod test { excess_blob_gas: None, parent_beacon_block_root: None, requests_root: None, + block_access_list_root: None, + slot_number: None, }; let expected_hash = Hash256::from_str("6da69709cd5a34079b6604d29cd78fc01dacd7c6268980057ad92a2bede87351") @@ -218,6 +234,8 @@ mod test { excess_blob_gas: Some(0x0u64), parent_beacon_block_root: Some(Hash256::from_str("f7d327d2c04e4f12e9cdd492e53d39a1d390f8b1571e3b2a22ac6e1e170e5b1a").unwrap()), requests_root: None, + block_access_list_root: None, + slot_number: None, }; let expected_hash = Hash256::from_str("a7448e600ead0a23d16f96aa46e8dea9eef8a7c5669a5f0a5ff32709afe9c408") @@ -249,6 +267,8 @@ mod test { excess_blob_gas: Some(44695552), parent_beacon_block_root: Some(Hash256::from_str("f3a888fee010ebb1ae083547004e96c254b240437823326fdff8354b1fc25629").unwrap()), requests_root: Some(Hash256::from_str("9440d3365f07573919e1e9ac5178c20ec6fe267357ee4baf8b6409901f331b62").unwrap()), + block_access_list_root: None, + slot_number: None, }; let expected_hash = Hash256::from_str("61e67afc96bf21be6aab52c1ace1db48de7b83f03119b0644deb4b69e87e09e1") diff --git a/beacon_node/execution_layer/src/engine_api.rs b/beacon_node/execution_layer/src/engine_api.rs index d3ed3fc9fe..f0238f8f98 100644 --- a/beacon_node/execution_layer/src/engine_api.rs +++ b/beacon_node/execution_layer/src/engine_api.rs @@ -1,11 +1,12 @@ use crate::engines::ForkchoiceState; use crate::http::{ ENGINE_FORKCHOICE_UPDATED_V1, ENGINE_FORKCHOICE_UPDATED_V2, ENGINE_FORKCHOICE_UPDATED_V3, - ENGINE_GET_BLOBS_V1, ENGINE_GET_BLOBS_V2, ENGINE_GET_CLIENT_VERSION_V1, - ENGINE_GET_PAYLOAD_BODIES_BY_HASH_V1, ENGINE_GET_PAYLOAD_BODIES_BY_RANGE_V1, - ENGINE_GET_PAYLOAD_V1, ENGINE_GET_PAYLOAD_V2, ENGINE_GET_PAYLOAD_V3, ENGINE_GET_PAYLOAD_V4, - ENGINE_GET_PAYLOAD_V5, ENGINE_NEW_PAYLOAD_V1, ENGINE_NEW_PAYLOAD_V2, ENGINE_NEW_PAYLOAD_V3, - ENGINE_NEW_PAYLOAD_V4, + ENGINE_FORKCHOICE_UPDATED_V4, ENGINE_GET_BLOBS_V1, ENGINE_GET_BLOBS_V2, + ENGINE_GET_CLIENT_VERSION_V1, ENGINE_GET_PAYLOAD_BODIES_BY_HASH_V1, + ENGINE_GET_PAYLOAD_BODIES_BY_RANGE_V1, ENGINE_GET_PAYLOAD_V1, ENGINE_GET_PAYLOAD_V2, + ENGINE_GET_PAYLOAD_V3, ENGINE_GET_PAYLOAD_V4, ENGINE_GET_PAYLOAD_V5, ENGINE_GET_PAYLOAD_V6, + ENGINE_NEW_PAYLOAD_V1, ENGINE_NEW_PAYLOAD_V2, ENGINE_NEW_PAYLOAD_V3, ENGINE_NEW_PAYLOAD_V4, + ENGINE_NEW_PAYLOAD_V5, }; use eth2::types::{ BlobsBundle, SsePayloadAttributes, SsePayloadAttributesV1, SsePayloadAttributesV2, @@ -159,7 +160,7 @@ impl ExecutionBlock { } #[superstruct( - variants(V1, V2, V3), + variants(V1, V2, V3, V4), variant_attributes(derive(Clone, Debug, Eq, Hash, PartialEq),), cast_error(ty = "Error", expr = "Error::IncorrectStateVariant"), partial_getter_error(ty = "Error", expr = "Error::IncorrectStateVariant") @@ -172,10 +173,12 @@ pub struct PayloadAttributes { pub prev_randao: Hash256, #[superstruct(getter(copy))] pub suggested_fee_recipient: Address, - #[superstruct(only(V2, V3))] + #[superstruct(only(V2, V3, V4))] pub withdrawals: Vec, - #[superstruct(only(V3), partial_getter(copy))] + #[superstruct(only(V3, V4), partial_getter(copy))] pub parent_beacon_block_root: Hash256, + #[superstruct(only(V4), partial_getter(copy))] + pub slot_number: u64, } impl PayloadAttributes { @@ -185,24 +188,35 @@ impl PayloadAttributes { suggested_fee_recipient: Address, withdrawals: Option>, parent_beacon_block_root: Option, + slot_number: Option, ) -> Self { - match withdrawals { - Some(withdrawals) => match parent_beacon_block_root { - Some(parent_beacon_block_root) => PayloadAttributes::V3(PayloadAttributesV3 { + match (withdrawals, parent_beacon_block_root, slot_number) { + (Some(withdrawals), Some(parent_beacon_block_root), Some(slot_number)) => { + PayloadAttributes::V4(PayloadAttributesV4 { timestamp, prev_randao, suggested_fee_recipient, withdrawals, parent_beacon_block_root, - }), - None => PayloadAttributes::V2(PayloadAttributesV2 { + slot_number, + }) + } + (Some(withdrawals), Some(parent_beacon_block_root), None) => { + PayloadAttributes::V3(PayloadAttributesV3 { timestamp, prev_randao, suggested_fee_recipient, withdrawals, - }), - }, - None => PayloadAttributes::V1(PayloadAttributesV1 { + parent_beacon_block_root, + }) + } + (Some(withdrawals), None, _) => PayloadAttributes::V2(PayloadAttributesV2 { + timestamp, + prev_randao, + suggested_fee_recipient, + withdrawals, + }), + (None, _, _) => PayloadAttributes::V1(PayloadAttributesV1 { timestamp, prev_randao, suggested_fee_recipient, @@ -247,6 +261,21 @@ impl From for SsePayloadAttributes { withdrawals, parent_beacon_block_root, }), + // V4 maps to V3 for SSE since we're ignoring SSE for devnet + PayloadAttributes::V4(PayloadAttributesV4 { + timestamp, + prev_randao, + suggested_fee_recipient, + withdrawals, + parent_beacon_block_root, + slot_number: _, + }) => Self::V3(SsePayloadAttributesV3 { + timestamp, + prev_randao, + suggested_fee_recipient, + withdrawals, + parent_beacon_block_root, + }), } } } @@ -566,6 +595,7 @@ impl ExecutionPayloadBodyV1 { // so we initialize it as empty here. The full payload with block_access_list // should be obtained via getPayload when needed. block_access_list: VariableList::empty(), + slot_number: header.slot_number, })) } else { Err(format!( @@ -584,9 +614,11 @@ pub struct EngineCapabilities { pub new_payload_v2: bool, pub new_payload_v3: bool, pub new_payload_v4: bool, + pub new_payload_v5: bool, pub forkchoice_updated_v1: bool, pub forkchoice_updated_v2: bool, pub forkchoice_updated_v3: bool, + pub forkchoice_updated_v4: bool, pub get_payload_bodies_by_hash_v1: bool, pub get_payload_bodies_by_range_v1: bool, pub get_payload_v1: bool, @@ -594,6 +626,7 @@ pub struct EngineCapabilities { pub get_payload_v3: bool, pub get_payload_v4: bool, pub get_payload_v5: bool, + pub get_payload_v6: bool, pub get_client_version_v1: bool, pub get_blobs_v1: bool, pub get_blobs_v2: bool, @@ -614,6 +647,9 @@ impl EngineCapabilities { if self.new_payload_v4 { response.push(ENGINE_NEW_PAYLOAD_V4); } + if self.new_payload_v5 { + response.push(ENGINE_NEW_PAYLOAD_V5); + } if self.forkchoice_updated_v1 { response.push(ENGINE_FORKCHOICE_UPDATED_V1); } @@ -623,6 +659,9 @@ impl EngineCapabilities { if self.forkchoice_updated_v3 { response.push(ENGINE_FORKCHOICE_UPDATED_V3); } + if self.forkchoice_updated_v4 { + response.push(ENGINE_FORKCHOICE_UPDATED_V4); + } if self.get_payload_bodies_by_hash_v1 { response.push(ENGINE_GET_PAYLOAD_BODIES_BY_HASH_V1); } @@ -644,6 +683,9 @@ impl EngineCapabilities { if self.get_payload_v5 { response.push(ENGINE_GET_PAYLOAD_V5); } + if self.get_payload_v6 { + response.push(ENGINE_GET_PAYLOAD_V6); + } if self.get_client_version_v1 { response.push(ENGINE_GET_CLIENT_VERSION_V1); } diff --git a/beacon_node/execution_layer/src/engine_api/http.rs b/beacon_node/execution_layer/src/engine_api/http.rs index c421491f80..92f4cee563 100644 --- a/beacon_node/execution_layer/src/engine_api/http.rs +++ b/beacon_node/execution_layer/src/engine_api/http.rs @@ -11,6 +11,7 @@ use serde_json::json; use std::collections::HashSet; use std::sync::LazyLock; use tokio::sync::Mutex; +use tracing::{debug, error}; use std::time::{Duration, Instant}; @@ -35,6 +36,7 @@ pub const ENGINE_NEW_PAYLOAD_V1: &str = "engine_newPayloadV1"; pub const ENGINE_NEW_PAYLOAD_V2: &str = "engine_newPayloadV2"; pub const ENGINE_NEW_PAYLOAD_V3: &str = "engine_newPayloadV3"; pub const ENGINE_NEW_PAYLOAD_V4: &str = "engine_newPayloadV4"; +pub const ENGINE_NEW_PAYLOAD_V5: &str = "engine_newPayloadV5"; pub const ENGINE_NEW_PAYLOAD_TIMEOUT: Duration = Duration::from_secs(8); pub const ENGINE_GET_PAYLOAD_V1: &str = "engine_getPayloadV1"; @@ -42,11 +44,13 @@ pub const ENGINE_GET_PAYLOAD_V2: &str = "engine_getPayloadV2"; pub const ENGINE_GET_PAYLOAD_V3: &str = "engine_getPayloadV3"; pub const ENGINE_GET_PAYLOAD_V4: &str = "engine_getPayloadV4"; pub const ENGINE_GET_PAYLOAD_V5: &str = "engine_getPayloadV5"; +pub const ENGINE_GET_PAYLOAD_V6: &str = "engine_getPayloadV6"; pub const ENGINE_GET_PAYLOAD_TIMEOUT: Duration = Duration::from_secs(2); pub const ENGINE_FORKCHOICE_UPDATED_V1: &str = "engine_forkchoiceUpdatedV1"; pub const ENGINE_FORKCHOICE_UPDATED_V2: &str = "engine_forkchoiceUpdatedV2"; pub const ENGINE_FORKCHOICE_UPDATED_V3: &str = "engine_forkchoiceUpdatedV3"; +pub const ENGINE_FORKCHOICE_UPDATED_V4: &str = "engine_forkchoiceUpdatedV4"; pub const ENGINE_FORKCHOICE_UPDATED_TIMEOUT: Duration = Duration::from_secs(8); pub const ENGINE_GET_PAYLOAD_BODIES_BY_HASH_V1: &str = "engine_getPayloadBodiesByHashV1"; @@ -74,14 +78,17 @@ pub static LIGHTHOUSE_CAPABILITIES: &[&str] = &[ ENGINE_NEW_PAYLOAD_V2, ENGINE_NEW_PAYLOAD_V3, ENGINE_NEW_PAYLOAD_V4, + ENGINE_NEW_PAYLOAD_V5, ENGINE_GET_PAYLOAD_V1, ENGINE_GET_PAYLOAD_V2, ENGINE_GET_PAYLOAD_V3, ENGINE_GET_PAYLOAD_V4, ENGINE_GET_PAYLOAD_V5, + ENGINE_GET_PAYLOAD_V6, ENGINE_FORKCHOICE_UPDATED_V1, ENGINE_FORKCHOICE_UPDATED_V2, ENGINE_FORKCHOICE_UPDATED_V3, + ENGINE_FORKCHOICE_UPDATED_V4, ENGINE_GET_PAYLOAD_BODIES_BY_HASH_V1, ENGINE_GET_PAYLOAD_BODIES_BY_RANGE_V1, ENGINE_GET_CLIENT_VERSION_V1, @@ -912,6 +919,69 @@ impl HttpJsonRpc { Ok(response.into()) } + pub async fn new_payload_v5_gloas( + &self, + new_payload_request_gloas: NewPayloadRequestGloas<'_, E>, + ) -> Result { + let block_hash = new_payload_request_gloas.execution_payload.block_hash; + let block_number = new_payload_request_gloas.execution_payload.block_number; + let slot_number = new_payload_request_gloas.execution_payload.slot_number; + + debug!( + %block_hash, + block_number, + slot_number, + "Sending engine_newPayloadV5 for Gloas" + ); + + let params = json!([ + JsonExecutionPayload::Gloas( + new_payload_request_gloas + .execution_payload + .clone() + .try_into()? + ), + new_payload_request_gloas.versioned_hashes, + new_payload_request_gloas.parent_beacon_block_root, + new_payload_request_gloas + .execution_requests + .get_execution_requests_list(), + ]); + + let result: Result = self + .rpc_request( + ENGINE_NEW_PAYLOAD_V5, + params, + ENGINE_NEW_PAYLOAD_TIMEOUT * self.execution_timeout_multiplier, + ) + .await; + + match &result { + Ok(response) => { + debug!( + %block_hash, + block_number, + slot_number, + status = ?response.status, + latest_valid_hash = ?response.latest_valid_hash, + validation_error = ?response.validation_error, + "Received engine_newPayloadV5 response" + ); + } + Err(e) => { + error!( + %block_hash, + block_number, + slot_number, + error = ?e, + "engine_newPayloadV5 failed" + ); + } + } + + Ok(result?.into()) + } + pub async fn get_payload_v1( &self, payload_id: PayloadId, @@ -1067,6 +1137,61 @@ impl HttpJsonRpc { } } + pub async fn get_payload_v6( + &self, + fork_name: ForkName, + payload_id: PayloadId, + ) -> Result, Error> { + debug!( + ?payload_id, + %fork_name, + "Sending engine_getPayloadV6 for Gloas" + ); + + let params = json!([JsonPayloadIdRequest::from(payload_id)]); + + match fork_name { + ForkName::Gloas => { + let result: Result, _> = self + .rpc_request( + ENGINE_GET_PAYLOAD_V6, + params, + ENGINE_GET_PAYLOAD_TIMEOUT * self.execution_timeout_multiplier, + ) + .await; + + match &result { + Ok(response) => { + debug!( + ?payload_id, + block_hash = %response.execution_payload.block_hash, + block_number = response.execution_payload.block_number, + slot_number = response.execution_payload.slot_number, + block_value = %response.block_value, + "Received engine_getPayloadV6 response" + ); + } + Err(e) => { + error!( + ?payload_id, + %fork_name, + error = ?e, + "engine_getPayloadV6 failed" + ); + } + } + + JsonGetPayloadResponse::Gloas(result?) + .try_into() + .map_err(Error::BadResponse) + } + _ => Err(Error::UnsupportedForkVariant(format!( + "called get_payload_v6 with {}", + fork_name + ))), + } + } + pub async fn forkchoice_updated_v1( &self, forkchoice_state: ForkchoiceState, @@ -1130,6 +1255,63 @@ impl HttpJsonRpc { Ok(response.into()) } + pub async fn forkchoice_updated_v4( + &self, + forkchoice_state: ForkchoiceState, + payload_attributes: Option, + ) -> Result { + let slot_number = payload_attributes + .as_ref() + .and_then(|pa| pa.slot_number().ok()); + + debug!( + head_block_hash = %forkchoice_state.head_block_hash, + safe_block_hash = %forkchoice_state.safe_block_hash, + finalized_block_hash = %forkchoice_state.finalized_block_hash, + has_payload_attributes = payload_attributes.is_some(), + ?slot_number, + "Sending engine_forkchoiceUpdatedV4 for Gloas" + ); + + let params = json!([ + JsonForkchoiceStateV1::from(forkchoice_state), + payload_attributes.map(JsonPayloadAttributes::from) + ]); + + let result: Result = self + .rpc_request( + ENGINE_FORKCHOICE_UPDATED_V4, + params, + ENGINE_FORKCHOICE_UPDATED_TIMEOUT * self.execution_timeout_multiplier, + ) + .await; + + match &result { + Ok(response) => { + debug!( + head_block_hash = %forkchoice_state.head_block_hash, + status = ?response.payload_status.status, + latest_valid_hash = ?response.payload_status.latest_valid_hash, + payload_id = ?response.payload_id, + ?slot_number, + "Received engine_forkchoiceUpdatedV4 response" + ); + } + Err(e) => { + error!( + head_block_hash = %forkchoice_state.head_block_hash, + safe_block_hash = %forkchoice_state.safe_block_hash, + finalized_block_hash = %forkchoice_state.finalized_block_hash, + ?slot_number, + error = ?e, + "engine_forkchoiceUpdatedV4 failed" + ); + } + } + + Ok(result?.into()) + } + pub async fn get_payload_bodies_by_hash_v1( &self, block_hashes: Vec, @@ -1198,9 +1380,11 @@ impl HttpJsonRpc { new_payload_v2: capabilities.contains(ENGINE_NEW_PAYLOAD_V2), new_payload_v3: capabilities.contains(ENGINE_NEW_PAYLOAD_V3), new_payload_v4: capabilities.contains(ENGINE_NEW_PAYLOAD_V4), + new_payload_v5: capabilities.contains(ENGINE_NEW_PAYLOAD_V5), forkchoice_updated_v1: capabilities.contains(ENGINE_FORKCHOICE_UPDATED_V1), forkchoice_updated_v2: capabilities.contains(ENGINE_FORKCHOICE_UPDATED_V2), forkchoice_updated_v3: capabilities.contains(ENGINE_FORKCHOICE_UPDATED_V3), + forkchoice_updated_v4: capabilities.contains(ENGINE_FORKCHOICE_UPDATED_V4), get_payload_bodies_by_hash_v1: capabilities .contains(ENGINE_GET_PAYLOAD_BODIES_BY_HASH_V1), get_payload_bodies_by_range_v1: capabilities @@ -1210,6 +1394,7 @@ impl HttpJsonRpc { get_payload_v3: capabilities.contains(ENGINE_GET_PAYLOAD_V3), get_payload_v4: capabilities.contains(ENGINE_GET_PAYLOAD_V4), get_payload_v5: capabilities.contains(ENGINE_GET_PAYLOAD_V5), + get_payload_v6: capabilities.contains(ENGINE_GET_PAYLOAD_V6), get_client_version_v1: capabilities.contains(ENGINE_GET_CLIENT_VERSION_V1), get_blobs_v1: capabilities.contains(ENGINE_GET_BLOBS_V1), get_blobs_v2: capabilities.contains(ENGINE_GET_BLOBS_V2), @@ -1353,10 +1538,12 @@ impl HttpJsonRpc { } } NewPayloadRequest::Gloas(new_payload_request_gloas) => { - if engine_capabilities.new_payload_v4 { - self.new_payload_v4_gloas(new_payload_request_gloas).await + if engine_capabilities.new_payload_v5 { + self.new_payload_v5_gloas(new_payload_request_gloas).await } else { - Err(Error::RequiredMethodUnsupported("engine_newPayloadV4")) + Err(Error::RequiredMethodUnsupported( + "engine_newPayloadV5 is required for Gloas/Amsterdam fork", + )) } } } @@ -1402,10 +1589,12 @@ impl HttpJsonRpc { } } ForkName::Gloas => { - if engine_capabilities.get_payload_v5 { - self.get_payload_v5(fork_name, payload_id).await + if engine_capabilities.get_payload_v6 { + self.get_payload_v6(fork_name, payload_id).await } else { - Err(Error::RequiredMethodUnsupported("engine_getPayloadv5")) + Err(Error::RequiredMethodUnsupported( + "engine_getPayloadV6 is required for Gloas/Amsterdam fork", + )) } } ForkName::Base | ForkName::Altair => Err(Error::UnsupportedForkVariant(format!( @@ -1446,6 +1635,16 @@ impl HttpJsonRpc { )) } } + PayloadAttributes::V4(_) => { + if engine_capabilities.forkchoice_updated_v4 { + self.forkchoice_updated_v4(forkchoice_state, maybe_payload_attributes) + .await + } else { + Err(Error::RequiredMethodUnsupported( + "engine_forkchoiceUpdatedV4 is required for Gloas/Amsterdam fork", + )) + } + } } } else if engine_capabilities.forkchoice_updated_v3 { self.forkchoice_updated_v3(forkchoice_state, maybe_payload_attributes) diff --git a/beacon_node/execution_layer/src/engine_api/json_structures.rs b/beacon_node/execution_layer/src/engine_api/json_structures.rs index b7cd4efbcf..b1c009fcef 100644 --- a/beacon_node/execution_layer/src/engine_api/json_structures.rs +++ b/beacon_node/execution_layer/src/engine_api/json_structures.rs @@ -110,6 +110,9 @@ pub struct JsonExecutionPayload { #[superstruct(only(Gloas))] #[serde(with = "ssz_types::serde_utils::hex_var_list")] pub block_access_list: VariableList, + #[superstruct(only(Gloas))] + #[serde(with = "serde_utils::u64_hex_be")] + pub slot_number: u64, } impl From> for JsonExecutionPayloadBellatrix { @@ -256,6 +259,7 @@ impl TryFrom> for JsonExecutionPayloadGloas blob_gas_used: payload.blob_gas_used, excess_blob_gas: payload.excess_blob_gas, block_access_list: payload.block_access_list, + slot_number: payload.slot_number, }) } } @@ -430,6 +434,7 @@ impl TryFrom> for ExecutionPayloadGloas blob_gas_used: payload.blob_gas_used, excess_blob_gas: payload.excess_blob_gas, block_access_list: payload.block_access_list, + slot_number: payload.slot_number, }) } } @@ -721,7 +726,7 @@ impl<'a> From<&'a JsonWithdrawal> for EncodableJsonWithdrawal<'a> { } #[superstruct( - variants(V1, V2, V3), + variants(V1, V2, V3, V4), variant_attributes( derive(Debug, Clone, PartialEq, Serialize, Deserialize), serde(rename_all = "camelCase") @@ -737,10 +742,13 @@ pub struct JsonPayloadAttributes { pub prev_randao: Hash256, #[serde(with = "serde_utils::address_hex")] pub suggested_fee_recipient: Address, - #[superstruct(only(V2, V3))] + #[superstruct(only(V2, V3, V4))] pub withdrawals: Vec, - #[superstruct(only(V3))] + #[superstruct(only(V3, V4))] pub parent_beacon_block_root: Hash256, + #[superstruct(only(V4))] + #[serde(with = "serde_utils::u64_hex_be")] + pub slot_number: u64, } impl From for JsonPayloadAttributes { @@ -764,6 +772,14 @@ impl From for JsonPayloadAttributes { withdrawals: pa.withdrawals.into_iter().map(Into::into).collect(), parent_beacon_block_root: pa.parent_beacon_block_root, }), + PayloadAttributes::V4(pa) => Self::V4(JsonPayloadAttributesV4 { + timestamp: pa.timestamp, + prev_randao: pa.prev_randao, + suggested_fee_recipient: pa.suggested_fee_recipient, + withdrawals: pa.withdrawals.into_iter().map(Into::into).collect(), + parent_beacon_block_root: pa.parent_beacon_block_root, + slot_number: pa.slot_number, + }), } } } @@ -789,6 +805,14 @@ impl From for PayloadAttributes { withdrawals: jpa.withdrawals.into_iter().map(Into::into).collect(), parent_beacon_block_root: jpa.parent_beacon_block_root, }), + JsonPayloadAttributes::V4(jpa) => Self::V4(PayloadAttributesV4 { + timestamp: jpa.timestamp, + prev_randao: jpa.prev_randao, + suggested_fee_recipient: jpa.suggested_fee_recipient, + withdrawals: jpa.withdrawals.into_iter().map(Into::into).collect(), + parent_beacon_block_root: jpa.parent_beacon_block_root, + slot_number: jpa.slot_number, + }), } } } diff --git a/beacon_node/execution_layer/src/engine_api/new_payload_request.rs b/beacon_node/execution_layer/src/engine_api/new_payload_request.rs index ba94296b85..bddb7297a6 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 @@ -221,7 +221,17 @@ impl<'a, E: EthSpec> TryFrom> for NewPayloadRequest<'a, E> parent_beacon_block_root: block_ref.parent_root, execution_requests: &block_ref.body.execution_requests, })), - BeaconBlockRef::Gloas(_) => Err(Self::Error::IncorrectStateVariant), + 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, + })), } } } 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 c5f31c45fd..72af487813 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 @@ -707,6 +707,9 @@ impl ExecutionBlockGenerator { blob_gas_used: 0, excess_blob_gas: 0, }), + _ => unreachable!(), + }, + PayloadAttributes::V4(pa) => match self.get_fork_at_timestamp(pa.timestamp) { ForkName::Gloas => ExecutionPayload::Gloas(ExecutionPayloadGloas { parent_hash: head_block_hash, fee_recipient: pa.suggested_fee_recipient, @@ -726,6 +729,7 @@ impl ExecutionBlockGenerator { blob_gas_used: 0, excess_blob_gas: 0, block_access_list: VariableList::empty(), + slot_number: pa.slot_number, }), _ => unreachable!(), }, diff --git a/beacon_node/execution_layer/src/test_utils/mock_builder.rs b/beacon_node/execution_layer/src/test_utils/mock_builder.rs index 94d921c407..cb1a0d6d1f 100644 --- a/beacon_node/execution_layer/src/test_utils/mock_builder.rs +++ b/beacon_node/execution_layer/src/test_utils/mock_builder.rs @@ -922,16 +922,24 @@ impl MockBuilder { fee_recipient, expected_withdrawals, None, + None, + ), + ForkName::Deneb | ForkName::Electra | ForkName::Fulu => PayloadAttributes::new( + timestamp, + *prev_randao, + fee_recipient, + expected_withdrawals, + Some(head_block_root), + None, + ), + ForkName::Gloas => PayloadAttributes::new( + timestamp, + *prev_randao, + fee_recipient, + expected_withdrawals, + Some(head_block_root), + Some(slot.as_u64()), ), - ForkName::Deneb | ForkName::Electra | ForkName::Fulu | ForkName::Gloas => { - PayloadAttributes::new( - timestamp, - *prev_randao, - fee_recipient, - expected_withdrawals, - Some(head_block_root), - ) - } ForkName::Base | ForkName::Altair => { return Err("invalid fork".to_string()); } diff --git a/beacon_node/execution_layer/src/test_utils/mock_execution_layer.rs b/beacon_node/execution_layer/src/test_utils/mock_execution_layer.rs index c69edb8f39..ef612d1663 100644 --- a/beacon_node/execution_layer/src/test_utils/mock_execution_layer.rs +++ b/beacon_node/execution_layer/src/test_utils/mock_execution_layer.rs @@ -107,8 +107,14 @@ impl MockExecutionLayer { justified_hash: None, finalized_hash: None, }; - let payload_attributes = - PayloadAttributes::new(timestamp, prev_randao, Address::repeat_byte(42), None, None); + let payload_attributes = PayloadAttributes::new( + timestamp, + prev_randao, + Address::repeat_byte(42), + None, + None, + None, + ); // Insert a proposer to ensure the fork choice updated command works. let slot = Slot::new(0); @@ -135,8 +141,14 @@ impl MockExecutionLayer { chain_health: ChainHealth::Healthy, }; let suggested_fee_recipient = self.el.get_suggested_fee_recipient(validator_index).await; - let payload_attributes = - PayloadAttributes::new(timestamp, prev_randao, suggested_fee_recipient, None, None); + let payload_attributes = PayloadAttributes::new( + timestamp, + prev_randao, + suggested_fee_recipient, + None, + None, + None, + ); let payload_parameters = PayloadParameters { parent_hash, @@ -182,8 +194,14 @@ impl MockExecutionLayer { chain_health: ChainHealth::Healthy, }; let suggested_fee_recipient = self.el.get_suggested_fee_recipient(validator_index).await; - let payload_attributes = - PayloadAttributes::new(timestamp, prev_randao, suggested_fee_recipient, None, None); + let payload_attributes = PayloadAttributes::new( + timestamp, + prev_randao, + suggested_fee_recipient, + None, + None, + None, + ); let payload_parameters = PayloadParameters { parent_hash, diff --git a/beacon_node/execution_layer/src/test_utils/mod.rs b/beacon_node/execution_layer/src/test_utils/mod.rs index 2465a41d8b..98611e79a1 100644 --- a/beacon_node/execution_layer/src/test_utils/mod.rs +++ b/beacon_node/execution_layer/src/test_utils/mod.rs @@ -45,9 +45,11 @@ pub const DEFAULT_ENGINE_CAPABILITIES: EngineCapabilities = EngineCapabilities { new_payload_v2: true, new_payload_v3: true, new_payload_v4: true, + new_payload_v5: true, forkchoice_updated_v1: true, forkchoice_updated_v2: true, forkchoice_updated_v3: true, + forkchoice_updated_v4: true, get_payload_bodies_by_hash_v1: true, get_payload_bodies_by_range_v1: true, get_payload_v1: true, @@ -55,6 +57,7 @@ pub const DEFAULT_ENGINE_CAPABILITIES: EngineCapabilities = EngineCapabilities { get_payload_v3: true, get_payload_v4: true, get_payload_v5: true, + get_payload_v6: true, get_client_version_v1: true, get_blobs_v1: true, get_blobs_v2: true, diff --git a/consensus/types/src/execution/execution_block_header.rs b/consensus/types/src/execution/execution_block_header.rs index e596ba1831..52409d5a31 100644 --- a/consensus/types/src/execution/execution_block_header.rs +++ b/consensus/types/src/execution/execution_block_header.rs @@ -58,6 +58,8 @@ pub struct ExecutionBlockHeader { pub excess_blob_gas: Option, pub parent_beacon_block_root: Option, pub requests_root: Option, + pub block_access_list_root: Option, + pub slot_number: Option, } impl ExecutionBlockHeader { @@ -71,6 +73,8 @@ impl ExecutionBlockHeader { rlp_excess_blob_gas: Option, rlp_parent_beacon_block_root: Option, rlp_requests_root: Option, + rlp_block_access_list_root: Option, + rlp_slot_number: Option, ) -> Self { // Most of these field mappings are defined in EIP-3675 except for `mixHash`, which is // defined in EIP-4399. @@ -96,6 +100,8 @@ impl ExecutionBlockHeader { excess_blob_gas: rlp_excess_blob_gas, parent_beacon_block_root: rlp_parent_beacon_block_root, requests_root: rlp_requests_root, + block_access_list_root: rlp_block_access_list_root, + slot_number: rlp_slot_number, } } } @@ -124,6 +130,8 @@ pub struct EncodableExecutionBlockHeader<'a> { pub excess_blob_gas: Option, pub parent_beacon_block_root: Option<&'a [u8]>, pub requests_root: Option<&'a [u8]>, + pub block_access_list_root: Option<&'a [u8]>, + pub slot_number: Option, } impl<'a> From<&'a ExecutionBlockHeader> for EncodableExecutionBlockHeader<'a> { @@ -150,6 +158,8 @@ impl<'a> From<&'a ExecutionBlockHeader> for EncodableExecutionBlockHeader<'a> { excess_blob_gas: header.excess_blob_gas, parent_beacon_block_root: None, requests_root: None, + block_access_list_root: None, + slot_number: header.slot_number, }; if let Some(withdrawals_root) = &header.withdrawals_root { encodable.withdrawals_root = Some(withdrawals_root.as_slice()); @@ -160,6 +170,9 @@ impl<'a> From<&'a ExecutionBlockHeader> for EncodableExecutionBlockHeader<'a> { if let Some(requests_root) = &header.requests_root { encodable.requests_root = Some(requests_root.as_slice()) } + if let Some(block_access_list_root) = &header.block_access_list_root { + encodable.block_access_list_root = Some(block_access_list_root.as_slice()) + } encodable } } diff --git a/consensus/types/src/execution/execution_payload.rs b/consensus/types/src/execution/execution_payload.rs index 4749273c6f..ed2a72eb7b 100644 --- a/consensus/types/src/execution/execution_payload.rs +++ b/consensus/types/src/execution/execution_payload.rs @@ -113,6 +113,10 @@ pub struct ExecutionPayload { #[superstruct(only(Gloas))] #[serde(with = "ssz_types::serde_utils::hex_var_list")] pub block_access_list: VariableList, + /// EIP-7843: Slot number + #[superstruct(only(Gloas), partial_getter(copy))] + #[serde(with = "serde_utils::quoted_u64")] + pub slot_number: u64, } impl<'a, E: EthSpec> ExecutionPayloadRef<'a, E> { diff --git a/consensus/types/src/execution/execution_payload_header.rs b/consensus/types/src/execution/execution_payload_header.rs index 4b2b820f70..06a8e11c34 100644 --- a/consensus/types/src/execution/execution_payload_header.rs +++ b/consensus/types/src/execution/execution_payload_header.rs @@ -117,6 +117,10 @@ pub struct ExecutionPayloadHeader { /// EIP-7928: Block access list root #[superstruct(only(Gloas), partial_getter(copy))] pub block_access_list_root: Hash256, + /// EIP-7843: Slot number + #[superstruct(only(Gloas), partial_getter(copy))] + #[serde(with = "serde_utils::quoted_u64")] + pub slot_number: u64, } impl ExecutionPayloadHeader { @@ -291,6 +295,7 @@ impl ExecutionPayloadHeaderFulu { blob_gas_used: self.blob_gas_used, excess_blob_gas: self.excess_blob_gas, block_access_list_root: Hash256::zero(), + slot_number: 0, } } } @@ -431,6 +436,7 @@ impl<'a, E: EthSpec> From<&'a ExecutionPayloadGloas> for ExecutionPayloadHead blob_gas_used: payload.blob_gas_used, excess_blob_gas: payload.excess_blob_gas, block_access_list_root: payload.block_access_list.tree_hash_root(), + slot_number: payload.slot_number, } } } diff --git a/testing/execution_engine_integration/src/test_rig.rs b/testing/execution_engine_integration/src/test_rig.rs index 24d75f5a11..4c1e8da63c 100644 --- a/testing/execution_engine_integration/src/test_rig.rs +++ b/testing/execution_engine_integration/src/test_rig.rs @@ -311,6 +311,7 @@ impl TestRig { Address::repeat_byte(42), Some(vec![]), None, + None, ), ) .await; @@ -355,6 +356,7 @@ impl TestRig { suggested_fee_recipient, Some(vec![]), None, + None, ); let payload_parameters = PayloadParameters { @@ -513,6 +515,7 @@ impl TestRig { suggested_fee_recipient, Some(vec![]), None, + None, ); let payload_parameters = PayloadParameters { @@ -570,9 +573,10 @@ impl TestRig { let payload_attributes = PayloadAttributes::new( timestamp, prev_randao, - Address::repeat_byte(42), + suggested_fee_recipient, Some(vec![]), None, + None, ); let slot = Slot::new(42); let head_block_root = Hash256::repeat_byte(100);