From ac25fd8c182f9fe28c8610981d791989ffcd9e9c Mon Sep 17 00:00:00 2001 From: Roman Krasiuk Date: Mon, 13 Jan 2025 11:40:46 +0100 Subject: [PATCH] feat(engine): validate execution requests (#13685) --- Cargo.lock | 1 + crates/payload/primitives/Cargo.toml | 3 + crates/payload/primitives/src/lib.rs | 77 ++++++++++++++++++++- crates/rpc/rpc-engine-api/src/engine_api.rs | 6 +- 4 files changed, 84 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 03fd74c60664..06bca5a49bbb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8519,6 +8519,7 @@ dependencies = [ "alloy-eips", "alloy-primitives", "alloy-rpc-types-engine", + "assert_matches", "op-alloy-rpc-types-engine", "reth-chain-state", "reth-chainspec", diff --git a/crates/payload/primitives/Cargo.toml b/crates/payload/primitives/Cargo.toml index d4070b4688e0..caeb538e1e2d 100644 --- a/crates/payload/primitives/Cargo.toml +++ b/crates/payload/primitives/Cargo.toml @@ -31,5 +31,8 @@ serde.workspace = true thiserror.workspace = true tokio = { workspace = true, default-features = false, features = ["sync"] } +[dev-dependencies] +assert_matches.workspace = true + [features] op = ["dep:op-alloy-rpc-types-engine"] \ No newline at end of file diff --git a/crates/payload/primitives/src/lib.rs b/crates/payload/primitives/src/lib.rs index 523e6fb057a6..eaafaf9959e6 100644 --- a/crates/payload/primitives/src/lib.rs +++ b/crates/payload/primitives/src/lib.rs @@ -8,6 +8,9 @@ #![cfg_attr(not(test), warn(unused_crate_dependencies))] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +use alloy_primitives::Bytes; +use reth_chainspec::EthereumHardforks; + mod error; pub use error::{ EngineObjectValidationError, InvalidPayloadAttributesError, PayloadBuilderError, @@ -24,7 +27,6 @@ pub use traits::{ mod payload; pub use payload::PayloadOrAttributes; -use reth_chainspec::EthereumHardforks; /// The types that are used by the engine API. pub trait PayloadTypes: Send + Sync + Unpin + core::fmt::Debug + Clone + 'static { /// The built payload type. @@ -363,12 +365,85 @@ pub enum PayloadKind { WaitForPending, } +/// Validates that execution requests are valid according to Engine API specification. +/// +/// `executionRequests`: `Array of DATA` - List of execution layer triggered requests. Each list +/// element is a `requests` byte array as defined by [EIP-7685](https://eips.ethereum.org/EIPS/eip-7685). +/// The first byte of each element is the `request_type` and the remaining bytes are the +/// `request_data`. Elements of the list **MUST** be ordered by `request_type` in ascending order. +/// Elements with empty `request_data` **MUST** be excluded from the list. If any element is out of +/// order or has a length of 1-byte or shorter, client software **MUST** return `-32602: Invalid +/// params` error. +pub fn validate_execution_requests(requests: &[Bytes]) -> Result<(), EngineObjectValidationError> { + let mut last_request_type = None; + for request in requests { + if request.len() <= 1 { + return Err(EngineObjectValidationError::InvalidParams( + "empty execution request".to_string().into(), + )) + } + + let request_type = request[0]; + if Some(request_type) < last_request_type { + return Err(EngineObjectValidationError::InvalidParams( + "execution requests out of order".to_string().into(), + )) + } + + last_request_type = Some(request_type); + } + Ok(()) +} + #[cfg(test)] mod tests { use super::*; + use assert_matches::assert_matches; #[test] fn version_ord() { assert!(EngineApiMessageVersion::V4 > EngineApiMessageVersion::V3); } + + #[test] + fn execution_requests_validation() { + assert_matches!(validate_execution_requests(&[]), Ok(())); + + let valid_requests = [ + Bytes::from_iter([1, 2]), + Bytes::from_iter([2, 3]), + Bytes::from_iter([3, 4]), + Bytes::from_iter([4, 5]), + ]; + assert_matches!(validate_execution_requests(&valid_requests), Ok(())); + + let requests_with_empty = [ + Bytes::from_iter([1, 2]), + Bytes::from_iter([2, 3]), + Bytes::new(), + Bytes::from_iter([3, 4]), + ]; + assert_matches!( + validate_execution_requests(&requests_with_empty), + Err(EngineObjectValidationError::InvalidParams(_)) + ); + + let mut requests_valid_reversed = valid_requests; + requests_valid_reversed.reverse(); + assert_matches!( + validate_execution_requests(&requests_with_empty), + Err(EngineObjectValidationError::InvalidParams(_)) + ); + + let requests_out_of_order = [ + Bytes::from_iter([1, 2]), + Bytes::from_iter([2, 3]), + Bytes::from_iter([4, 5]), + Bytes::from_iter([3, 4]), + ]; + assert_matches!( + validate_execution_requests(&requests_out_of_order), + Err(EngineObjectValidationError::InvalidParams(_)) + ); + } } diff --git a/crates/rpc/rpc-engine-api/src/engine_api.rs b/crates/rpc/rpc-engine-api/src/engine_api.rs index fa3fba285745..c3ed8dc5add9 100644 --- a/crates/rpc/rpc-engine-api/src/engine_api.rs +++ b/crates/rpc/rpc-engine-api/src/engine_api.rs @@ -20,8 +20,8 @@ use reth_chainspec::{EthereumHardfork, EthereumHardforks}; use reth_engine_primitives::{BeaconConsensusEngineHandle, EngineTypes, EngineValidator}; use reth_payload_builder::PayloadStore; use reth_payload_primitives::{ - validate_payload_timestamp, EngineApiMessageVersion, PayloadBuilderAttributes, - PayloadOrAttributes, + validate_execution_requests, validate_payload_timestamp, EngineApiMessageVersion, + PayloadBuilderAttributes, PayloadOrAttributes, }; use reth_rpc_api::EngineApiServer; use reth_rpc_types_compat::engine::payload::convert_to_payload_body_v1; @@ -268,6 +268,8 @@ where .validator .validate_version_specific_fields(EngineApiMessageVersion::V4, payload_or_attrs)?; + validate_execution_requests(&execution_requests)?; + Ok(self .inner .beacon_consensus