diff --git a/.github/workflows/ci_l1.yaml b/.github/workflows/ci_l1.yaml index e6518b03c0..8a08794ac6 100644 --- a/.github/workflows/ci_l1.yaml +++ b/.github/workflows/ci_l1.yaml @@ -179,7 +179,7 @@ jobs: test_pattern: "engine-api/RPC|Re-Org Back to Canonical Chain From Syncing Chain|Re-org to Previously Validated Sidechain Payload|Re-Org Back into Canonical Chain, Depth=5|Safe Re-Org|Transaction Re-Org|Inconsistent|Suggested Fee|PrevRandao|Fork ID|Unknown|Invalid PayloadAttributes|Bad Hash|Unique Payload ID|Re-Execute Payload|In-Order|Multiple New Payloads|Valid NewPayload|NewPayload with|Invalid NewPayload|Payload Build|Invalid NewPayload, Transaction|ParentHash equals|Build Payload|Invalid Missing Ancestor ReOrg" - name: "Engine withdrawal tests" simulation: ethereum/engine - test_pattern: "engine-withdrawals/engine-withdrawals test loader|GetPayloadV2 Block Value|Sync after 2 blocks - Withdrawals on Genesis|Max Initcode Size|Pre-Merge Fork Number > 0|Empty Withdrawals|Corrupted Block Hash Payload|Withdrawals Fork on Block 2|Withdrawals Fork on Block 3" + test_pattern: "engine-withdrawals/engine-withdrawals test loader|GetPayloadV2 Block Value|Sync after 2 blocks - Withdrawals on Genesis|Max Initcode Size|Pre-Merge Fork Number > 0|Empty Withdrawals|Corrupted Block Hash Payload|Withdrawals Fork on Block 2|Withdrawals Fork on Block 3|GetPayloadBodies" - name: "Sync" simulation: ethereum/sync test_pattern: "" diff --git a/crates/networking/rpc/engine/payload.rs b/crates/networking/rpc/engine/payload.rs index 5207d9995d..07c803adf6 100644 --- a/crates/networking/rpc/engine/payload.rs +++ b/crates/networking/rpc/engine/payload.rs @@ -1,15 +1,22 @@ use ethrex_blockchain::add_block; use ethrex_blockchain::error::ChainError; use ethrex_blockchain::payload::build_payload; -use ethrex_core::types::{BlobsBundle, Block, Fork}; +use ethrex_core::types::{BlobsBundle, Block, BlockBody, BlockHash, BlockNumber, Fork}; use ethrex_core::{H256, U256}; use serde_json::Value; use tracing::{error, info, warn}; -use crate::types::payload::{ExecutionPayload, ExecutionPayloadResponse, PayloadStatus}; -use crate::utils::RpcRequest; +use crate::types::payload::{ + ExecutionPayload, ExecutionPayloadBody, ExecutionPayloadResponse, PayloadStatus, +}; +use crate::utils::{parse_json_hex, RpcRequest}; use crate::{RpcApiContext, RpcErr, RpcHandler}; +// Must support rquest sizes of at least 32 blocks +// Chosen an arbitrary x4 value +// -> https://github.com/ethereum/execution-apis/blob/main/src/engine/shanghai.md#specification-3 +const GET_PAYLOAD_BODIES_REQUEST_MAX_SIZE: usize = 128; + // NewPayload V1-V2-V3 implementations pub struct NewPayloadV1Request { pub payload: ExecutionPayload, @@ -190,6 +197,82 @@ impl RpcHandler for GetPayloadV3Request { } } +pub struct GetPayloadBodiesByHashV1Request { + pub hashes: Vec, +} + +impl RpcHandler for GetPayloadBodiesByHashV1Request { + fn parse(params: &Option>) -> Result { + let params = params + .as_ref() + .ok_or(RpcErr::BadParams("No params provided".to_owned()))?; + if params.len() != 1 { + return Err(RpcErr::BadParams("Expected 1 param".to_owned())); + }; + + Ok(GetPayloadBodiesByHashV1Request { + hashes: serde_json::from_value(params[0].clone())?, + }) + } + + fn handle(&self, context: RpcApiContext) -> Result { + if self.hashes.len() >= GET_PAYLOAD_BODIES_REQUEST_MAX_SIZE { + return Err(RpcErr::TooLargeRequest); + } + let bodies = self + .hashes + .iter() + .map(|hash| context.storage.get_block_body_by_hash(*hash)) + .collect::>, _>>()?; + build_payload_body_response(bodies) + } +} + +pub struct GetPayloadBodiesByRangeV1Request { + start: BlockNumber, + count: u64, +} + +impl RpcHandler for GetPayloadBodiesByRangeV1Request { + fn parse(params: &Option>) -> Result { + let params = params + .as_ref() + .ok_or(RpcErr::BadParams("No params provided".to_owned()))?; + if params.len() != 2 { + return Err(RpcErr::BadParams("Expected 1 param".to_owned())); + }; + let start = parse_json_hex(¶ms[0]).map_err(|_| RpcErr::BadHexFormat(0))?; + let count = parse_json_hex(¶ms[1]).map_err(|_| RpcErr::BadHexFormat(1))?; + if start < 1 { + return Err(RpcErr::WrongParam("start".to_owned())); + } + if count < 1 { + return Err(RpcErr::WrongParam("count".to_owned())); + } + Ok(GetPayloadBodiesByRangeV1Request { start, count }) + } + + fn handle(&self, context: RpcApiContext) -> Result { + if self.count as usize >= GET_PAYLOAD_BODIES_REQUEST_MAX_SIZE { + return Err(RpcErr::TooLargeRequest); + } + let latest_block_number = context.storage.get_latest_block_number()?; + let last = latest_block_number.min(self.start + self.count - 1); + let bodies = (self.start..=last) + .map(|block_num| context.storage.get_block_body(block_num)) + .collect::>, _>>()?; + build_payload_body_response(bodies) + } +} + +fn build_payload_body_response(bodies: Vec>) -> Result { + let response: Vec> = bodies + .into_iter() + .map(|body| body.map(Into::into)) + .collect(); + serde_json::to_value(response).map_err(|error| RpcErr::Internal(error.to_string())) +} + fn parse_execution_payload(params: &Option>) -> Result { let params = params .as_ref() diff --git a/crates/networking/rpc/rpc.rs b/crates/networking/rpc/rpc.rs index 336d0d896f..38efee747d 100644 --- a/crates/networking/rpc/rpc.rs +++ b/crates/networking/rpc/rpc.rs @@ -9,8 +9,9 @@ use engine::{ exchange_transition_config::ExchangeTransitionConfigV1Req, fork_choice::{ForkChoiceUpdatedV1, ForkChoiceUpdatedV2, ForkChoiceUpdatedV3}, payload::{ - GetPayloadV1Request, GetPayloadV2Request, GetPayloadV3Request, NewPayloadV1Request, - NewPayloadV2Request, NewPayloadV3Request, + GetPayloadBodiesByHashV1Request, GetPayloadBodiesByRangeV1Request, GetPayloadV1Request, + GetPayloadV2Request, GetPayloadV3Request, NewPayloadV1Request, NewPayloadV2Request, + NewPayloadV3Request, }, ExchangeCapabilitiesRequest, }; @@ -271,6 +272,8 @@ pub fn map_engine_requests(req: &RpcRequest, context: RpcApiContext) -> Result GetPayloadV3Request::call(req, context), "engine_getPayloadV2" => GetPayloadV2Request::call(req, context), "engine_getPayloadV1" => GetPayloadV1Request::call(req, context), + "engine_getPayloadBodiesByHashV1" => GetPayloadBodiesByHashV1Request::call(req, context), + "engine_getPayloadBodiesByRangeV1" => GetPayloadBodiesByRangeV1Request::call(req, context), unknown_engine_method => Err(RpcErr::MethodNotFound(unknown_engine_method.to_owned())), } } diff --git a/crates/networking/rpc/types/payload.rs b/crates/networking/rpc/types/payload.rs index 320c354025..db055b2769 100644 --- a/crates/networking/rpc/types/payload.rs +++ b/crates/networking/rpc/types/payload.rs @@ -234,6 +234,26 @@ impl PayloadStatus { } } +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ExecutionPayloadBody { + pub transactions: Vec, + pub withdrawals: Option>, +} + +impl From for ExecutionPayloadBody { + fn from(body: BlockBody) -> Self { + Self { + transactions: body + .transactions + .iter() + .map(EncodedTransaction::encode) + .collect(), + withdrawals: body.withdrawals, + } + } +} + #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ExecutionPayloadResponse { diff --git a/crates/networking/rpc/utils.rs b/crates/networking/rpc/utils.rs index 65fed08702..092612c78f 100644 --- a/crates/networking/rpc/utils.rs +++ b/crates/networking/rpc/utils.rs @@ -12,6 +12,7 @@ pub enum RpcErr { WrongParam(String), BadParams(String), MissingParam(String), + TooLargeRequest, BadHexFormat(u64), UnsuportedFork(String), Internal(String), @@ -47,6 +48,11 @@ impl From for RpcErrorMetadata { data: None, message: format!("Expected parameter: {parameter_name} is missing"), }, + RpcErr::TooLargeRequest => RpcErrorMetadata { + code: -38004, + data: None, + message: "Too large request".to_string(), + }, RpcErr::UnsuportedFork(context) => RpcErrorMetadata { code: -38005, data: None,