From 40e288db3b61b536c336893e61589837d68f56b1 Mon Sep 17 00:00:00 2001 From: hopinheimer Date: Sun, 6 Sep 2026 17:59:21 -0700 Subject: [PATCH 1/2] Implement `engine_getPayloadBodiesByHashV2` and `eth_getBlockByHash` --- beacon_node/execution_layer/src/engine_api.rs | 75 +++++++++++++-- .../execution_layer/src/engine_api/http.rs | 52 ++++++++++ .../src/engine_api/json_structures.rs | 94 +++++++++++++++++++ beacon_node/execution_layer/src/lib.rs | 77 ++++++++++++++- .../src/test_utils/handle_rpc.rs | 59 ++++++++++++ .../execution_layer/src/test_utils/mod.rs | 1 + 6 files changed, 351 insertions(+), 7 deletions(-) diff --git a/beacon_node/execution_layer/src/engine_api.rs b/beacon_node/execution_layer/src/engine_api.rs index 52a58c88fec..8d5b648cd8f 100644 --- a/beacon_node/execution_layer/src/engine_api.rs +++ b/beacon_node/execution_layer/src/engine_api.rs @@ -3,10 +3,10 @@ use crate::http::{ ENGINE_FORKCHOICE_UPDATED_V1, ENGINE_FORKCHOICE_UPDATED_V2, ENGINE_FORKCHOICE_UPDATED_V3, ENGINE_FORKCHOICE_UPDATED_V4, ENGINE_GET_BLOBS_V2, ENGINE_GET_CLIENT_VERSION_V1, ENGINE_GET_INCLUSION_LIST_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, + ENGINE_GET_PAYLOAD_BODIES_BY_HASH_V2, 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, @@ -25,9 +25,9 @@ pub use types::{ Withdrawal, Withdrawals, }; use types::{ - ExecutionPayloadBellatrix, ExecutionPayloadCapella, ExecutionPayloadDeneb, + BlockAccessList, ExecutionPayloadBellatrix, ExecutionPayloadCapella, ExecutionPayloadDeneb, ExecutionPayloadElectra, ExecutionPayloadFulu, ExecutionPayloadGloas, ExecutionPayloadHeze, - ExecutionRequests, KzgProofs, + ExecutionRequests, KzgProofs, ProgressiveTransactions, ProgressiveWithdrawals, Slot, }; use types::{GRAFFITI_BYTES_LEN, Graffiti}; @@ -449,6 +449,65 @@ pub struct ExecutionPayloadBodyV1 { pub withdrawals: Option>, } +/// The execution payload body returned by `engine_getPayloadBodiesByHashV2`. +#[derive(Clone, Debug)] +pub struct ExecutionPayloadBodyV2 { + pub transactions: ProgressiveTransactions, + pub withdrawals: ProgressiveWithdrawals, + pub block_access_list: BlockAccessList, +} + +/// Header fields obtained from `eth_getBlockByHash` which, together with an +/// `ExecutionPayloadBodyV2`, comprise a Gloas execution payload. +#[derive(Clone, Debug)] +pub struct ExecutionBlockHeaderGloas { + pub parent_hash: ExecutionBlockHash, + pub fee_recipient: Address, + pub state_root: Hash256, + pub receipts_root: Hash256, + pub logs_bloom: ssz_types::FixedVector, + pub prev_randao: Hash256, + pub block_number: u64, + pub gas_limit: u64, + pub gas_used: u64, + pub timestamp: u64, + pub extra_data: ssz_types::VariableList, + pub base_fee_per_gas: Uint256, + pub block_hash: ExecutionBlockHash, + pub blob_gas_used: u64, + pub excess_blob_gas: u64, +} + +impl ExecutionPayloadBodyV2 { + pub fn to_payload( + self, + header: ExecutionBlockHeaderGloas, + slot: Slot, + ) -> ExecutionPayloadGloas { + 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: self.withdrawals, + blob_gas_used: header.blob_gas_used, + excess_blob_gas: header.excess_blob_gas, + block_access_list: self.block_access_list, + slot_number: slot, + } + } +} + impl ExecutionPayloadBodyV1 { pub fn to_payload( self, @@ -605,6 +664,7 @@ pub struct EngineCapabilities { pub forkchoice_updated_v3: bool, pub forkchoice_updated_v4: bool, pub get_payload_bodies_by_hash_v1: bool, + pub get_payload_bodies_by_hash_v2: bool, pub get_payload_bodies_by_range_v1: bool, pub get_payload_v1: bool, pub get_payload_v2: bool, @@ -651,6 +711,9 @@ impl EngineCapabilities { if self.get_payload_bodies_by_hash_v1 { response.push(ENGINE_GET_PAYLOAD_BODIES_BY_HASH_V1); } + if self.get_payload_bodies_by_hash_v2 { + response.push(ENGINE_GET_PAYLOAD_BODIES_BY_HASH_V2); + } if self.get_payload_bodies_by_range_v1 { response.push(ENGINE_GET_PAYLOAD_BODIES_BY_RANGE_V1); } diff --git a/beacon_node/execution_layer/src/engine_api/http.rs b/beacon_node/execution_layer/src/engine_api/http.rs index b6e755471e0..34ed37c3287 100644 --- a/beacon_node/execution_layer/src/engine_api/http.rs +++ b/beacon_node/execution_layer/src/engine_api/http.rs @@ -29,6 +29,8 @@ pub const RETURN_FULL_TRANSACTION_OBJECTS: bool = false; pub const ETH_GET_BLOCK_BY_NUMBER: &str = "eth_getBlockByNumber"; pub const ETH_GET_BLOCK_BY_NUMBER_TIMEOUT: Duration = Duration::from_secs(1); +pub const ETH_GET_BLOCK_BY_HASH: &str = "eth_getBlockByHash"; +pub const ETH_GET_BLOCK_BY_HASH_TIMEOUT: Duration = Duration::from_secs(10); pub const ETH_SYNCING: &str = "eth_syncing"; pub const ETH_SYNCING_TIMEOUT: Duration = Duration::from_secs(1); @@ -55,6 +57,7 @@ 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"; +pub const ENGINE_GET_PAYLOAD_BODIES_BY_HASH_V2: &str = "engine_getPayloadBodiesByHashV2"; pub const ENGINE_GET_PAYLOAD_BODIES_BY_RANGE_V1: &str = "engine_getPayloadBodiesByRangeV1"; pub const ENGINE_GET_PAYLOAD_BODIES_TIMEOUT: Duration = Duration::from_secs(10); @@ -94,6 +97,7 @@ pub static LIGHTHOUSE_CAPABILITIES: &[&str] = &[ ENGINE_FORKCHOICE_UPDATED_V3, ENGINE_FORKCHOICE_UPDATED_V4, ENGINE_GET_PAYLOAD_BODIES_BY_HASH_V1, + ENGINE_GET_PAYLOAD_BODIES_BY_HASH_V2, ENGINE_GET_PAYLOAD_BODIES_BY_RANGE_V1, ENGINE_GET_CLIENT_VERSION_V1, ENGINE_GET_BLOBS_V2, @@ -1204,6 +1208,52 @@ impl HttpJsonRpc { .collect::, _>>() } + pub async fn get_payload_bodies_by_hash_v2( + &self, + block_hashes: Vec, + ) -> Result>, Error> { + let params = json!([block_hashes]); + + let response: Vec> = self + .rpc_request( + ENGINE_GET_PAYLOAD_BODIES_BY_HASH_V2, + params, + ENGINE_GET_PAYLOAD_BODIES_TIMEOUT * self.execution_timeout_multiplier, + ) + .await?; + + Ok(response + .into_iter() + .map(|body| body.map(Into::into)) + .collect()) + } + + pub async fn get_execution_block_header_gloas( + &self, + block_hash: ExecutionBlockHash, + ) -> Result>, Error> { + let params = json!([block_hash, RETURN_FULL_TRANSACTION_OBJECTS]); + let response: Option> = self + .rpc_request( + ETH_GET_BLOCK_BY_HASH, + params, + ETH_GET_BLOCK_BY_HASH_TIMEOUT * self.execution_timeout_multiplier, + ) + .await?; + + response + .map(|header| { + if header.block_hash != block_hash { + return Err(Error::BadResponse(format!( + "eth_getBlockByHash returned block {} for requested block {}", + header.block_hash, block_hash + ))); + } + Ok(header.into()) + }) + .transpose() + } + pub async fn get_payload_bodies_by_range_v1( &self, start: u64, @@ -1255,6 +1305,8 @@ impl HttpJsonRpc { 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_hash_v2: capabilities + .contains(ENGINE_GET_PAYLOAD_BODIES_BY_HASH_V2), get_payload_bodies_by_range_v1: capabilities .contains(ENGINE_GET_PAYLOAD_BODIES_BY_RANGE_V1), get_payload_v1: capabilities.contains(ENGINE_GET_PAYLOAD_V1), 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 b6f5f820034..63f7e3ca280 100644 --- a/beacon_node/execution_layer/src/engine_api/json_structures.rs +++ b/beacon_node/execution_layer/src/engine_api/json_structures.rs @@ -1215,6 +1215,100 @@ pub struct JsonExecutionPayloadBodyV1 { pub withdrawals: Option>, } +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct JsonExecutionPayloadBodyV2 { + #[serde(with = "ssz_types::serde_utils::prog_list_of_hex_prog_var_list")] + pub transactions: ProgressiveTransactions, + pub withdrawals: ProgressiveVariableList, + #[serde(with = "ssz_types::serde_utils::hex_prog_var_list")] + pub block_access_list: BlockAccessList, +} + +impl From for ExecutionPayloadBodyV2 { + fn from(value: JsonExecutionPayloadBodyV2) -> Self { + Self { + transactions: value.transactions, + withdrawals: value.withdrawals.into_iter().map(Into::into).collect(), + block_access_list: value.block_access_list, + } + } +} + +impl From for JsonExecutionPayloadBodyV2 { + fn from(value: ExecutionPayloadBodyV2) -> Self { + Self { + transactions: value.transactions, + withdrawals: value.withdrawals.into_iter().map(Into::into).collect(), + block_access_list: value.block_access_list, + } + } +} + +/// The execution header fields returned by `eth_getBlockByHash` that are required to +/// reconstruct a Gloas execution payload. Unknown Ethereum block fields are ignored. +#[derive(Clone, Debug, Deserialize)] +#[serde(bound = "E: EthSpec", rename_all = "camelCase")] +pub struct JsonExecutionBlockHeaderGloas { + pub parent_hash: ExecutionBlockHash, + #[serde( + rename = "miner", + alias = "feeRecipient", + with = "serde_utils::address_hex" + )] + pub fee_recipient: Address, + pub state_root: Hash256, + pub receipts_root: Hash256, + #[serde(with = "serde_logs_bloom")] + pub logs_bloom: FixedVector, + #[serde(rename = "mixHash", alias = "prevRandao")] + pub prev_randao: Hash256, + #[serde( + rename = "number", + alias = "blockNumber", + with = "serde_utils::u64_hex_be" + )] + pub block_number: u64, + #[serde(with = "serde_utils::u64_hex_be")] + pub gas_limit: u64, + #[serde(with = "serde_utils::u64_hex_be")] + pub gas_used: u64, + #[serde(with = "serde_utils::u64_hex_be")] + pub timestamp: u64, + #[serde(with = "ssz_types::serde_utils::hex_var_list")] + pub extra_data: VariableList, + #[serde(with = "serde_utils::u256_hex_be")] + pub base_fee_per_gas: Uint256, + #[serde(rename = "hash", alias = "blockHash")] + pub block_hash: ExecutionBlockHash, + #[serde(with = "serde_utils::u64_hex_be")] + pub blob_gas_used: u64, + #[serde(with = "serde_utils::u64_hex_be")] + pub excess_blob_gas: u64, +} + +impl From> for ExecutionBlockHeaderGloas { + fn from(value: JsonExecutionBlockHeaderGloas) -> Self { + Self { + parent_hash: value.parent_hash, + fee_recipient: value.fee_recipient, + state_root: value.state_root, + receipts_root: value.receipts_root, + logs_bloom: value.logs_bloom, + prev_randao: value.prev_randao, + block_number: value.block_number, + gas_limit: value.gas_limit, + gas_used: value.gas_used, + timestamp: value.timestamp, + extra_data: value.extra_data, + base_fee_per_gas: value.base_fee_per_gas, + block_hash: value.block_hash, + blob_gas_used: value.blob_gas_used, + excess_blob_gas: value.excess_blob_gas, + } + } +} + impl TryFrom> for ExecutionPayloadBodyV1 { type Error = ssz_types::Error; diff --git a/beacon_node/execution_layer/src/lib.rs b/beacon_node/execution_layer/src/lib.rs index 29236ca7687..1490cc12f48 100644 --- a/beacon_node/execution_layer/src/lib.rs +++ b/beacon_node/execution_layer/src/lib.rs @@ -1674,6 +1674,57 @@ impl ExecutionLayer { .map_err(Error::EngineError) } + /// Fetch Gloas execution payloads by hash. The Engine API supplies the payload bodies, + /// while `eth_getBlockByHash` supplies the execution header fields. The slot is consensus + /// data and must be supplied by the caller. + pub async fn get_payloads_by_hash_gloas( + &self, + requests: Vec<(ExecutionBlockHash, Slot)>, + ) -> Result>>, Error> { + self.engine() + .request(|engine: &Engine| async move { + let block_hashes = requests + .iter() + .map(|(block_hash, _)| *block_hash) + .collect::>(); + let bodies = engine + .api + .get_payload_bodies_by_hash_v2(block_hashes) + .await?; + + if bodies.len() != requests.len() { + return Err(ApiError::BadResponse(format!( + "engine_getPayloadBodiesByHashV2 returned {} bodies for {} requests", + bodies.len(), + requests.len() + ))); + } + + let mut payloads = Vec::with_capacity(requests.len()); + for ((block_hash, slot), body) in requests.into_iter().zip(bodies) { + let Some(body) = body else { + payloads.push(None); + continue; + }; + let Some(header) = engine + .api + .get_execution_block_header_gloas(block_hash) + .await? + else { + payloads.push(None); + continue; + }; + + payloads.push(Some(body.to_payload(header, slot))); + } + + Ok(payloads) + }) + .await + .map_err(Box::new) + .map_err(Error::EngineError) + } + pub async fn get_payload_bodies_by_range( &self, start: u64, @@ -2175,7 +2226,7 @@ fn noop( #[cfg(test)] mod test { use super::*; - use crate::test_utils::MockExecutionLayer as GenericMockExecutionLayer; + use crate::test_utils::{Block, MockExecutionLayer as GenericMockExecutionLayer}; use task_executor::test_utils::TestRuntime; use types::MainnetEthSpec; @@ -2193,6 +2244,30 @@ mod test { .await; } + #[tokio::test] + async fn reconstruct_gloas_payload_by_hash() { + let runtime = TestRuntime::default(); + let mock = MockExecutionLayer::default_params(runtime.task_executor.clone()); + let block_hash = ExecutionBlockHash::repeat_byte(0x42); + let slot = Slot::new(12); + let payload = ExecutionPayloadGloas { + block_hash, + slot_number: slot, + ..Default::default() + }; + mock.server + .execution_block_generator() + .insert_block_without_checks(Block::PoS(payload.clone().into())); + + let payloads = mock + .el + .get_payloads_by_hash_gloas(vec![(block_hash, slot)]) + .await + .expect("payload reconstruction should succeed"); + + assert_eq!(payloads, vec![Some(payload)]); + } + #[tokio::test] async fn test_expected_gas_limit() { let spec = ChainSpec::mainnet(); diff --git a/beacon_node/execution_layer/src/test_utils/handle_rpc.rs b/beacon_node/execution_layer/src/test_utils/handle_rpc.rs index c1e72a6f3ed..24af365bf79 100644 --- a/beacon_node/execution_layer/src/test_utils/handle_rpc.rs +++ b/beacon_node/execution_layer/src/test_utils/handle_rpc.rs @@ -64,6 +64,38 @@ pub async fn handle_rpc( )), } } + ETH_GET_BLOCK_BY_HASH => { + let block_hash = get_param::(params, 0) + .map_err(|s| (s, BAD_PARAMS_ERROR_CODE))?; + let payload = ctx + .execution_block_generator + .read() + .execution_payload_by_hash(block_hash); + + match payload { + Some(ExecutionPayload::Gloas(payload)) => { + let json_payload = + JsonExecutionPayloadGloas::try_from(payload).map_err(|e| { + (format!("invalid Gloas payload: {e:?}"), GENERIC_ERROR_CODE) + })?; + let mut json_block = serde_json::to_value(json_payload).unwrap(); + if let JsonValue::Object(fields) = &mut json_block { + for (payload_field, block_field) in [ + ("feeRecipient", "miner"), + ("prevRandao", "mixHash"), + ("blockNumber", "number"), + ("blockHash", "hash"), + ] { + if let Some(value) = fields.remove(payload_field) { + fields.insert(block_field.to_owned(), value); + } + } + } + Ok(json_block) + } + _ => Ok(JsonValue::Null), + } + } ENGINE_NEW_PAYLOAD_V1 | ENGINE_NEW_PAYLOAD_V2 | ENGINE_NEW_PAYLOAD_V3 @@ -768,6 +800,33 @@ pub async fn handle_rpc( Ok(serde_json::to_value(response).unwrap()) } + ENGINE_GET_PAYLOAD_BODIES_BY_HASH_V2 => { + let block_hashes = get_param::>(params, 0) + .map_err(|s| (s, BAD_PARAMS_ERROR_CODE))?; + + let mut response = vec![]; + for block_hash in block_hashes { + let payload = ctx + .execution_block_generator + .read() + .execution_payload_by_hash(block_hash); + + match payload { + Some(ExecutionPayload::Gloas(payload)) => { + response.push(Some(JsonExecutionPayloadBodyV2::from( + ExecutionPayloadBodyV2 { + transactions: payload.transactions, + withdrawals: payload.withdrawals, + block_access_list: payload.block_access_list, + }, + ))); + } + _ => response.push(None), + } + } + + Ok(serde_json::to_value(response).unwrap()) + } ENGINE_GET_PAYLOAD_BODIES_BY_RANGE_V1 => { #[derive(Deserialize)] #[serde(transparent)] diff --git a/beacon_node/execution_layer/src/test_utils/mod.rs b/beacon_node/execution_layer/src/test_utils/mod.rs index 82b55551367..2bdc0d11c07 100644 --- a/beacon_node/execution_layer/src/test_utils/mod.rs +++ b/beacon_node/execution_layer/src/test_utils/mod.rs @@ -49,6 +49,7 @@ pub const DEFAULT_ENGINE_CAPABILITIES: EngineCapabilities = EngineCapabilities { forkchoice_updated_v3: true, forkchoice_updated_v4: true, get_payload_bodies_by_hash_v1: true, + get_payload_bodies_by_hash_v2: true, get_payload_bodies_by_range_v1: true, get_payload_v1: true, get_payload_v2: true, From e01ebed61eed802004b0d0a7c926c9958aeaa326 Mon Sep 17 00:00:00 2001 From: hopinheimer Date: Sun, 6 Sep 2026 20:04:58 -0700 Subject: [PATCH 2/2] Remove `eth_getBlockByHash` --- beacon_node/execution_layer/src/engine_api.rs | 55 +--------------- .../execution_layer/src/engine_api/http.rs | 28 -------- .../src/engine_api/json_structures.rs | 64 ------------------- beacon_node/execution_layer/src/lib.rs | 64 +++++-------------- .../src/test_utils/handle_rpc.rs | 32 ---------- 5 files changed, 17 insertions(+), 226 deletions(-) diff --git a/beacon_node/execution_layer/src/engine_api.rs b/beacon_node/execution_layer/src/engine_api.rs index 8d5b648cd8f..3d996c7df2d 100644 --- a/beacon_node/execution_layer/src/engine_api.rs +++ b/beacon_node/execution_layer/src/engine_api.rs @@ -27,7 +27,7 @@ pub use types::{ use types::{ BlockAccessList, ExecutionPayloadBellatrix, ExecutionPayloadCapella, ExecutionPayloadDeneb, ExecutionPayloadElectra, ExecutionPayloadFulu, ExecutionPayloadGloas, ExecutionPayloadHeze, - ExecutionRequests, KzgProofs, ProgressiveTransactions, ProgressiveWithdrawals, Slot, + ExecutionRequests, KzgProofs, ProgressiveTransactions, ProgressiveWithdrawals, }; use types::{GRAFFITI_BYTES_LEN, Graffiti}; @@ -450,64 +450,13 @@ pub struct ExecutionPayloadBodyV1 { } /// The execution payload body returned by `engine_getPayloadBodiesByHashV2`. -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub struct ExecutionPayloadBodyV2 { pub transactions: ProgressiveTransactions, pub withdrawals: ProgressiveWithdrawals, pub block_access_list: BlockAccessList, } -/// Header fields obtained from `eth_getBlockByHash` which, together with an -/// `ExecutionPayloadBodyV2`, comprise a Gloas execution payload. -#[derive(Clone, Debug)] -pub struct ExecutionBlockHeaderGloas { - pub parent_hash: ExecutionBlockHash, - pub fee_recipient: Address, - pub state_root: Hash256, - pub receipts_root: Hash256, - pub logs_bloom: ssz_types::FixedVector, - pub prev_randao: Hash256, - pub block_number: u64, - pub gas_limit: u64, - pub gas_used: u64, - pub timestamp: u64, - pub extra_data: ssz_types::VariableList, - pub base_fee_per_gas: Uint256, - pub block_hash: ExecutionBlockHash, - pub blob_gas_used: u64, - pub excess_blob_gas: u64, -} - -impl ExecutionPayloadBodyV2 { - pub fn to_payload( - self, - header: ExecutionBlockHeaderGloas, - slot: Slot, - ) -> ExecutionPayloadGloas { - 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: self.withdrawals, - blob_gas_used: header.blob_gas_used, - excess_blob_gas: header.excess_blob_gas, - block_access_list: self.block_access_list, - slot_number: slot, - } - } -} - impl ExecutionPayloadBodyV1 { pub fn to_payload( self, diff --git a/beacon_node/execution_layer/src/engine_api/http.rs b/beacon_node/execution_layer/src/engine_api/http.rs index 34ed37c3287..e5bcf0ee29d 100644 --- a/beacon_node/execution_layer/src/engine_api/http.rs +++ b/beacon_node/execution_layer/src/engine_api/http.rs @@ -29,8 +29,6 @@ pub const RETURN_FULL_TRANSACTION_OBJECTS: bool = false; pub const ETH_GET_BLOCK_BY_NUMBER: &str = "eth_getBlockByNumber"; pub const ETH_GET_BLOCK_BY_NUMBER_TIMEOUT: Duration = Duration::from_secs(1); -pub const ETH_GET_BLOCK_BY_HASH: &str = "eth_getBlockByHash"; -pub const ETH_GET_BLOCK_BY_HASH_TIMEOUT: Duration = Duration::from_secs(10); pub const ETH_SYNCING: &str = "eth_syncing"; pub const ETH_SYNCING_TIMEOUT: Duration = Duration::from_secs(1); @@ -1228,32 +1226,6 @@ impl HttpJsonRpc { .collect()) } - pub async fn get_execution_block_header_gloas( - &self, - block_hash: ExecutionBlockHash, - ) -> Result>, Error> { - let params = json!([block_hash, RETURN_FULL_TRANSACTION_OBJECTS]); - let response: Option> = self - .rpc_request( - ETH_GET_BLOCK_BY_HASH, - params, - ETH_GET_BLOCK_BY_HASH_TIMEOUT * self.execution_timeout_multiplier, - ) - .await?; - - response - .map(|header| { - if header.block_hash != block_hash { - return Err(Error::BadResponse(format!( - "eth_getBlockByHash returned block {} for requested block {}", - header.block_hash, block_hash - ))); - } - Ok(header.into()) - }) - .transpose() - } - pub async fn get_payload_bodies_by_range_v1( &self, start: u64, 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 63f7e3ca280..2f18232d6ad 100644 --- a/beacon_node/execution_layer/src/engine_api/json_structures.rs +++ b/beacon_node/execution_layer/src/engine_api/json_structures.rs @@ -1245,70 +1245,6 @@ impl From for JsonExecutionPayloadBodyV2 { } } -/// The execution header fields returned by `eth_getBlockByHash` that are required to -/// reconstruct a Gloas execution payload. Unknown Ethereum block fields are ignored. -#[derive(Clone, Debug, Deserialize)] -#[serde(bound = "E: EthSpec", rename_all = "camelCase")] -pub struct JsonExecutionBlockHeaderGloas { - pub parent_hash: ExecutionBlockHash, - #[serde( - rename = "miner", - alias = "feeRecipient", - with = "serde_utils::address_hex" - )] - pub fee_recipient: Address, - pub state_root: Hash256, - pub receipts_root: Hash256, - #[serde(with = "serde_logs_bloom")] - pub logs_bloom: FixedVector, - #[serde(rename = "mixHash", alias = "prevRandao")] - pub prev_randao: Hash256, - #[serde( - rename = "number", - alias = "blockNumber", - with = "serde_utils::u64_hex_be" - )] - pub block_number: u64, - #[serde(with = "serde_utils::u64_hex_be")] - pub gas_limit: u64, - #[serde(with = "serde_utils::u64_hex_be")] - pub gas_used: u64, - #[serde(with = "serde_utils::u64_hex_be")] - pub timestamp: u64, - #[serde(with = "ssz_types::serde_utils::hex_var_list")] - pub extra_data: VariableList, - #[serde(with = "serde_utils::u256_hex_be")] - pub base_fee_per_gas: Uint256, - #[serde(rename = "hash", alias = "blockHash")] - pub block_hash: ExecutionBlockHash, - #[serde(with = "serde_utils::u64_hex_be")] - pub blob_gas_used: u64, - #[serde(with = "serde_utils::u64_hex_be")] - pub excess_blob_gas: u64, -} - -impl From> for ExecutionBlockHeaderGloas { - fn from(value: JsonExecutionBlockHeaderGloas) -> Self { - Self { - parent_hash: value.parent_hash, - fee_recipient: value.fee_recipient, - state_root: value.state_root, - receipts_root: value.receipts_root, - logs_bloom: value.logs_bloom, - prev_randao: value.prev_randao, - block_number: value.block_number, - gas_limit: value.gas_limit, - gas_used: value.gas_used, - timestamp: value.timestamp, - extra_data: value.extra_data, - base_fee_per_gas: value.base_fee_per_gas, - block_hash: value.block_hash, - blob_gas_used: value.blob_gas_used, - excess_blob_gas: value.excess_blob_gas, - } - } -} - impl TryFrom> for ExecutionPayloadBodyV1 { type Error = ssz_types::Error; diff --git a/beacon_node/execution_layer/src/lib.rs b/beacon_node/execution_layer/src/lib.rs index 1490cc12f48..d78bd9f2e4c 100644 --- a/beacon_node/execution_layer/src/lib.rs +++ b/beacon_node/execution_layer/src/lib.rs @@ -1674,51 +1674,14 @@ impl ExecutionLayer { .map_err(Error::EngineError) } - /// Fetch Gloas execution payloads by hash. The Engine API supplies the payload bodies, - /// while `eth_getBlockByHash` supplies the execution header fields. The slot is consensus - /// data and must be supplied by the caller. - pub async fn get_payloads_by_hash_gloas( + /// Fetch Gloas execution payload bodies by block hash. + pub async fn get_payload_bodies_by_hash_v2( &self, - requests: Vec<(ExecutionBlockHash, Slot)>, - ) -> Result>>, Error> { + hashes: Vec, + ) -> Result>, Error> { self.engine() .request(|engine: &Engine| async move { - let block_hashes = requests - .iter() - .map(|(block_hash, _)| *block_hash) - .collect::>(); - let bodies = engine - .api - .get_payload_bodies_by_hash_v2(block_hashes) - .await?; - - if bodies.len() != requests.len() { - return Err(ApiError::BadResponse(format!( - "engine_getPayloadBodiesByHashV2 returned {} bodies for {} requests", - bodies.len(), - requests.len() - ))); - } - - let mut payloads = Vec::with_capacity(requests.len()); - for ((block_hash, slot), body) in requests.into_iter().zip(bodies) { - let Some(body) = body else { - payloads.push(None); - continue; - }; - let Some(header) = engine - .api - .get_execution_block_header_gloas(block_hash) - .await? - else { - payloads.push(None); - continue; - }; - - payloads.push(Some(body.to_payload(header, slot))); - } - - Ok(payloads) + engine.api.get_payload_bodies_by_hash_v2(hashes).await }) .await .map_err(Box::new) @@ -2245,27 +2208,30 @@ mod test { } #[tokio::test] - async fn reconstruct_gloas_payload_by_hash() { + async fn get_gloas_payload_bodies_by_hash() { let runtime = TestRuntime::default(); let mock = MockExecutionLayer::default_params(runtime.task_executor.clone()); let block_hash = ExecutionBlockHash::repeat_byte(0x42); - let slot = Slot::new(12); let payload = ExecutionPayloadGloas { block_hash, - slot_number: slot, ..Default::default() }; + let expected_body = ExecutionPayloadBodyV2 { + transactions: payload.transactions.clone(), + withdrawals: payload.withdrawals.clone(), + block_access_list: payload.block_access_list.clone(), + }; mock.server .execution_block_generator() .insert_block_without_checks(Block::PoS(payload.clone().into())); - let payloads = mock + let bodies = mock .el - .get_payloads_by_hash_gloas(vec![(block_hash, slot)]) + .get_payload_bodies_by_hash_v2(vec![block_hash, ExecutionBlockHash::zero()]) .await - .expect("payload reconstruction should succeed"); + .expect("payload body request should succeed"); - assert_eq!(payloads, vec![Some(payload)]); + assert_eq!(bodies, vec![Some(expected_body), None]); } #[tokio::test] diff --git a/beacon_node/execution_layer/src/test_utils/handle_rpc.rs b/beacon_node/execution_layer/src/test_utils/handle_rpc.rs index 24af365bf79..a048f157925 100644 --- a/beacon_node/execution_layer/src/test_utils/handle_rpc.rs +++ b/beacon_node/execution_layer/src/test_utils/handle_rpc.rs @@ -64,38 +64,6 @@ pub async fn handle_rpc( )), } } - ETH_GET_BLOCK_BY_HASH => { - let block_hash = get_param::(params, 0) - .map_err(|s| (s, BAD_PARAMS_ERROR_CODE))?; - let payload = ctx - .execution_block_generator - .read() - .execution_payload_by_hash(block_hash); - - match payload { - Some(ExecutionPayload::Gloas(payload)) => { - let json_payload = - JsonExecutionPayloadGloas::try_from(payload).map_err(|e| { - (format!("invalid Gloas payload: {e:?}"), GENERIC_ERROR_CODE) - })?; - let mut json_block = serde_json::to_value(json_payload).unwrap(); - if let JsonValue::Object(fields) = &mut json_block { - for (payload_field, block_field) in [ - ("feeRecipient", "miner"), - ("prevRandao", "mixHash"), - ("blockNumber", "number"), - ("blockHash", "hash"), - ] { - if let Some(value) = fields.remove(payload_field) { - fields.insert(block_field.to_owned(), value); - } - } - } - Ok(json_block) - } - _ => Ok(JsonValue::Null), - } - } ENGINE_NEW_PAYLOAD_V1 | ENGINE_NEW_PAYLOAD_V2 | ENGINE_NEW_PAYLOAD_V3