diff --git a/Cargo.lock b/Cargo.lock index 3a063e7e0e7..ecbfd0cb8d1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3008,6 +3008,7 @@ dependencies = [ "sensitive_url", "serde", "serde_json", + "sha2 0.9.9", "slog", "slot_clock", "ssz_types", diff --git a/beacon_node/execution_layer/Cargo.toml b/beacon_node/execution_layer/Cargo.toml index 93d8086149d..843a7b83cb7 100644 --- a/beacon_node/execution_layer/Cargo.toml +++ b/beacon_node/execution_layer/Cargo.toml @@ -52,3 +52,4 @@ alloy-rlp = { workspace = true } alloy-consensus = { workspace = true } lighthouse_version = { workspace = true } fixed_bytes = { workspace = true } +sha2 = { workspace = true } diff --git a/beacon_node/execution_layer/src/engine_api/http.rs b/beacon_node/execution_layer/src/engine_api/http.rs index 33a77f934b7..e5ed2e640e1 100644 --- a/beacon_node/execution_layer/src/engine_api/http.rs +++ b/beacon_node/execution_layer/src/engine_api/http.rs @@ -793,6 +793,7 @@ impl HttpJsonRpc { JsonExecutionPayload::V4(new_payload_request_electra.execution_payload.clone().into()), new_payload_request_electra.versioned_hashes, new_payload_request_electra.parent_beacon_block_root, + new_payload_request_electra.execution_requests_hash, ]); let response: JsonPayloadStatusV1 = self 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 b0ff6f5236d..13aac62b7fc 100644 --- a/beacon_node/execution_layer/src/engine_api/json_structures.rs +++ b/beacon_node/execution_layer/src/engine_api/json_structures.rs @@ -1,13 +1,14 @@ use super::*; use alloy_rlp::RlpEncodable; -use bytes::Bytes; use serde::{Deserialize, Serialize}; -use ssz::Decode; +use sha2::{Digest, Sha256}; +use ssz::{Decode, Encode}; use strum::EnumString; use superstruct::superstruct; use types::beacon_block_body::KzgCommitments; use types::blob_sidecar::BlobsList; -use types::{DepositRequest, FixedVector, Unsigned, WithdrawalRequest}; +use types::execution_requests::{ConsolidationRequests, DepositRequests, WithdrawalRequests}; +use types::{FixedVector, Unsigned}; #[derive(Debug, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -201,6 +202,18 @@ impl RequestPrefix { } } +/// Computes the hash of the `ExecutionRequests` based on the specification +/// in EIP-7685. +pub fn compute_execution_requests_hash(request: &ExecutionRequests) -> Hash256 { + let depsoits_hash = Sha256::digest(&request.deposits.as_ssz_bytes()); + let withdrawals_hash = Sha256::digest(&request.withdrawals.as_ssz_bytes()); + let consolidation_hash = Sha256::digest(&request.consolidations.as_ssz_bytes()); + + Hash256::from_slice(&Sha256::digest( + &[depsoits_hash, withdrawals_hash, consolidation_hash].concat(), + )) +} + impl From> for JsonExecutionPayloadV4 { fn from(payload: ExecutionPayloadElectra) -> Self { JsonExecutionPayloadV4 { @@ -357,52 +370,48 @@ impl From> for ExecutionPayload { } } +/// Format of `ExecutionRequests` received over json rpc is +/// +/// Array of `prefix-type ++ ssz-encoded requests list` encoded as hex bytes. #[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)] #[serde(transparent)] -/// TODO(pawan): https://github.com/ethereum/execution-apis/pull/587/ has a typed representation, but that's probably going to -/// be changed in https://github.com/ethereum/execution-apis/pull/577/ -pub struct JsonExecutionRequests(pub Vec); +pub struct JsonExecutionRequests(pub Vec); impl TryFrom for ExecutionRequests { type Error = String; fn try_from(value: JsonExecutionRequests) -> Result { - let mut deposits = Vec::with_capacity(E::max_deposit_requests_per_payload()); - let mut withdrawals = Vec::with_capacity(E::max_withdrawal_requests_per_payload()); - let mut consolidations = Vec::with_capacity(E::max_consolidation_requests_per_payload()); + let mut requests = ExecutionRequests::default(); - // TODO(pawan): enforce ordering constraints here for request in value.0.into_iter() { - if let Some((first, rest)) = request.split_first() { + // hex string + let decoded_bytes = hex::decode(request).map_err(|e| format!("Invalid hex {:?}", e))?; + if let Some((first, rest)) = decoded_bytes.split_first() { match RequestPrefix::from_prefix(*first) { Some(RequestPrefix::Deposit) => { - deposits.push(DepositRequest::from_ssz_bytes(rest).map_err(|e| { - format!("Failed to decode DepositRequest from EL: {:?}", e) - })?) + requests.deposits = + DepositRequests::::from_ssz_bytes(rest).map_err(|e| { + format!("Failed to decode DepositRequest from EL: {:?}", e) + })?; } Some(RequestPrefix::Withdrawal) => { - withdrawals.push(WithdrawalRequest::from_ssz_bytes(rest).map_err(|e| { - format!("Failed to decode WithdrawalRequest from EL: {:?}", e) - })?) + requests.withdrawals = WithdrawalRequests::::from_ssz_bytes(rest) + .map_err(|e| { + format!("Failed to decode WithdrawalRequest from EL: {:?}", e) + })?; } Some(RequestPrefix::Consolidation) => { - consolidations.push(ConsolidationRequest::from_ssz_bytes(rest).map_err( - |e| format!("Failed to decode ConsolidationRequest from EL: {:?}", e), - )?) + requests.consolidations = ConsolidationRequests::::from_ssz_bytes(rest) + .map_err(|e| { + format!("Failed to decode ConsolidationRequest from EL: {:?}", e) + })?; } - None => return Err("Empty requests json".to_string()), + None => return Err("Empty requests string".to_string()), } } } - Ok(ExecutionRequests { - deposits: VariableList::new(deposits) - .map_err(|_| "DepositRequests from EL exceeded length limits".to_string())?, - withdrawals: VariableList::new(withdrawals) - .map_err(|_| "WithdrawalRequests from EL exceeded length limits".to_string())?, - consolidations: VariableList::new(consolidations) - .map_err(|_| "ConsolidationRequests from EL exceeded length limits".to_string())?, - }) + Ok(requests) } } 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 8d2e3d5ad06..13565ab67b9 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 @@ -12,6 +12,8 @@ use types::{ ExecutionPayloadElectra, }; +use super::json_structures::compute_execution_requests_hash; + #[superstruct( variants(Bellatrix, Capella, Deneb, Electra), variant_attributes(derive(Clone, Debug, PartialEq),), @@ -43,6 +45,8 @@ pub struct NewPayloadRequest<'block, E: EthSpec> { pub versioned_hashes: Vec, #[superstruct(only(Deneb, Electra))] pub parent_beacon_block_root: Hash256, + #[superstruct(only(Electra))] + pub execution_requests_hash: Hash256, } impl<'block, E: EthSpec> NewPayloadRequest<'block, E> { @@ -183,6 +187,9 @@ impl<'a, E: EthSpec> TryFrom> for NewPayloadRequest<'a, E> .map(kzg_commitment_to_versioned_hash) .collect(), parent_beacon_block_root: block_ref.parent_root, + execution_requests_hash: compute_execution_requests_hash( + &block_ref.body.execution_requests, + ), })), } }