Skip to content

Commit ac25fd8

Browse files
authored
feat(engine): validate execution requests (#13685)
1 parent 4e3810a commit ac25fd8

File tree

4 files changed

+84
-3
lines changed

4 files changed

+84
-3
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/payload/primitives/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,5 +31,8 @@ serde.workspace = true
3131
thiserror.workspace = true
3232
tokio = { workspace = true, default-features = false, features = ["sync"] }
3333

34+
[dev-dependencies]
35+
assert_matches.workspace = true
36+
3437
[features]
3538
op = ["dep:op-alloy-rpc-types-engine"]

crates/payload/primitives/src/lib.rs

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88
#![cfg_attr(not(test), warn(unused_crate_dependencies))]
99
#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
1010

11+
use alloy_primitives::Bytes;
12+
use reth_chainspec::EthereumHardforks;
13+
1114
mod error;
1215
pub use error::{
1316
EngineObjectValidationError, InvalidPayloadAttributesError, PayloadBuilderError,
@@ -24,7 +27,6 @@ pub use traits::{
2427
mod payload;
2528
pub use payload::PayloadOrAttributes;
2629

27-
use reth_chainspec::EthereumHardforks;
2830
/// The types that are used by the engine API.
2931
pub trait PayloadTypes: Send + Sync + Unpin + core::fmt::Debug + Clone + 'static {
3032
/// The built payload type.
@@ -363,12 +365,85 @@ pub enum PayloadKind {
363365
WaitForPending,
364366
}
365367

368+
/// Validates that execution requests are valid according to Engine API specification.
369+
///
370+
/// `executionRequests`: `Array of DATA` - List of execution layer triggered requests. Each list
371+
/// element is a `requests` byte array as defined by [EIP-7685](https://eips.ethereum.org/EIPS/eip-7685).
372+
/// The first byte of each element is the `request_type` and the remaining bytes are the
373+
/// `request_data`. Elements of the list **MUST** be ordered by `request_type` in ascending order.
374+
/// Elements with empty `request_data` **MUST** be excluded from the list. If any element is out of
375+
/// order or has a length of 1-byte or shorter, client software **MUST** return `-32602: Invalid
376+
/// params` error.
377+
pub fn validate_execution_requests(requests: &[Bytes]) -> Result<(), EngineObjectValidationError> {
378+
let mut last_request_type = None;
379+
for request in requests {
380+
if request.len() <= 1 {
381+
return Err(EngineObjectValidationError::InvalidParams(
382+
"empty execution request".to_string().into(),
383+
))
384+
}
385+
386+
let request_type = request[0];
387+
if Some(request_type) < last_request_type {
388+
return Err(EngineObjectValidationError::InvalidParams(
389+
"execution requests out of order".to_string().into(),
390+
))
391+
}
392+
393+
last_request_type = Some(request_type);
394+
}
395+
Ok(())
396+
}
397+
366398
#[cfg(test)]
367399
mod tests {
368400
use super::*;
401+
use assert_matches::assert_matches;
369402

370403
#[test]
371404
fn version_ord() {
372405
assert!(EngineApiMessageVersion::V4 > EngineApiMessageVersion::V3);
373406
}
407+
408+
#[test]
409+
fn execution_requests_validation() {
410+
assert_matches!(validate_execution_requests(&[]), Ok(()));
411+
412+
let valid_requests = [
413+
Bytes::from_iter([1, 2]),
414+
Bytes::from_iter([2, 3]),
415+
Bytes::from_iter([3, 4]),
416+
Bytes::from_iter([4, 5]),
417+
];
418+
assert_matches!(validate_execution_requests(&valid_requests), Ok(()));
419+
420+
let requests_with_empty = [
421+
Bytes::from_iter([1, 2]),
422+
Bytes::from_iter([2, 3]),
423+
Bytes::new(),
424+
Bytes::from_iter([3, 4]),
425+
];
426+
assert_matches!(
427+
validate_execution_requests(&requests_with_empty),
428+
Err(EngineObjectValidationError::InvalidParams(_))
429+
);
430+
431+
let mut requests_valid_reversed = valid_requests;
432+
requests_valid_reversed.reverse();
433+
assert_matches!(
434+
validate_execution_requests(&requests_with_empty),
435+
Err(EngineObjectValidationError::InvalidParams(_))
436+
);
437+
438+
let requests_out_of_order = [
439+
Bytes::from_iter([1, 2]),
440+
Bytes::from_iter([2, 3]),
441+
Bytes::from_iter([4, 5]),
442+
Bytes::from_iter([3, 4]),
443+
];
444+
assert_matches!(
445+
validate_execution_requests(&requests_out_of_order),
446+
Err(EngineObjectValidationError::InvalidParams(_))
447+
);
448+
}
374449
}

crates/rpc/rpc-engine-api/src/engine_api.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ use reth_chainspec::{EthereumHardfork, EthereumHardforks};
2020
use reth_engine_primitives::{BeaconConsensusEngineHandle, EngineTypes, EngineValidator};
2121
use reth_payload_builder::PayloadStore;
2222
use reth_payload_primitives::{
23-
validate_payload_timestamp, EngineApiMessageVersion, PayloadBuilderAttributes,
24-
PayloadOrAttributes,
23+
validate_execution_requests, validate_payload_timestamp, EngineApiMessageVersion,
24+
PayloadBuilderAttributes, PayloadOrAttributes,
2525
};
2626
use reth_rpc_api::EngineApiServer;
2727
use reth_rpc_types_compat::engine::payload::convert_to_payload_body_v1;
@@ -268,6 +268,8 @@ where
268268
.validator
269269
.validate_version_specific_fields(EngineApiMessageVersion::V4, payload_or_attrs)?;
270270

271+
validate_execution_requests(&execution_requests)?;
272+
271273
Ok(self
272274
.inner
273275
.beacon_consensus

0 commit comments

Comments
 (0)