reth_payload_primitives/lib.rs
1//! This crate defines abstractions to create and update payloads (blocks)
2
3#![doc(
4 html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png",
5 html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256",
6 issue_tracker_base_url = "https://github.com/SeismicSystems/seismic-reth/issues/"
7)]
8#![cfg_attr(not(test), warn(unused_crate_dependencies))]
9#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
10#![cfg_attr(not(feature = "std"), no_std)]
11
12extern crate alloc;
13
14use crate::alloc::string::ToString;
15use alloy_primitives::Bytes;
16use reth_chainspec::EthereumHardforks;
17use reth_primitives_traits::{NodePrimitives, SealedBlock};
18
19mod error;
20pub use error::{
21 EngineObjectValidationError, InvalidPayloadAttributesError, NewPayloadError,
22 PayloadBuilderError, VersionSpecificValidationError,
23};
24
25/// Contains traits to abstract over payload attributes types and default implementations of the
26/// [`PayloadAttributes`] trait for ethereum mainnet and optimism types.
27mod traits;
28pub use traits::{
29 BuiltPayload, PayloadAttributes, PayloadAttributesBuilder, PayloadBuilderAttributes,
30};
31
32mod payload;
33pub use payload::{ExecutionPayload, PayloadOrAttributes};
34
35/// The types that are used by the engine API.
36pub trait PayloadTypes: Send + Sync + Unpin + core::fmt::Debug + Clone + 'static {
37 /// The execution payload type provided as input
38 type ExecutionData: ExecutionPayload;
39 /// The built payload type.
40 type BuiltPayload: BuiltPayload + Clone + Unpin;
41
42 /// The RPC payload attributes type the CL node emits via the engine API.
43 type PayloadAttributes: PayloadAttributes + Unpin;
44
45 /// The payload attributes type that contains information about a running payload job.
46 type PayloadBuilderAttributes: PayloadBuilderAttributes<RpcPayloadAttributes = Self::PayloadAttributes>
47 + Clone
48 + Unpin;
49
50 /// Converts a block into an execution payload.
51 fn block_to_payload(
52 block: SealedBlock<
53 <<Self::BuiltPayload as BuiltPayload>::Primitives as NodePrimitives>::Block,
54 >,
55 ) -> Self::ExecutionData;
56}
57
58/// Validates the timestamp depending on the version called:
59///
60/// * If V2, this ensures that the payload timestamp is pre-Cancun.
61/// * If V3, this ensures that the payload timestamp is within the Cancun timestamp.
62/// * If V4, this ensures that the payload timestamp is within the Prague timestamp.
63///
64/// Otherwise, this will return [`EngineObjectValidationError::UnsupportedFork`].
65pub fn validate_payload_timestamp(
66 chain_spec: impl EthereumHardforks,
67 version: EngineApiMessageVersion,
68 timestamp: u64,
69) -> Result<(), EngineObjectValidationError> {
70 let is_cancun = chain_spec.is_cancun_active_at_timestamp(timestamp);
71 if version.is_v2() && is_cancun {
72 // From the Engine API spec:
73 //
74 // ### Update the methods of previous forks
75 //
76 // This document defines how Cancun payload should be handled by the [`Shanghai
77 // API`](https://github.com/ethereum/execution-apis/blob/ff43500e653abde45aec0f545564abfb648317af/src/engine/shanghai.md).
78 //
79 // For the following methods:
80 //
81 // - [`engine_forkchoiceUpdatedV2`](https://github.com/ethereum/execution-apis/blob/ff43500e653abde45aec0f545564abfb648317af/src/engine/shanghai.md#engine_forkchoiceupdatedv2)
82 // - [`engine_newPayloadV2`](https://github.com/ethereum/execution-apis/blob/ff43500e653abde45aec0f545564abfb648317af/src/engine/shanghai.md#engine_newpayloadV2)
83 // - [`engine_getPayloadV2`](https://github.com/ethereum/execution-apis/blob/ff43500e653abde45aec0f545564abfb648317af/src/engine/shanghai.md#engine_getpayloadv2)
84 //
85 // a validation **MUST** be added:
86 //
87 // 1. Client software **MUST** return `-38005: Unsupported fork` error if the `timestamp` of
88 // payload or payloadAttributes is greater or equal to the Cancun activation timestamp.
89 return Err(EngineObjectValidationError::UnsupportedFork)
90 }
91
92 if version.is_v3() && !is_cancun {
93 // From the Engine API spec:
94 // <https://github.com/ethereum/execution-apis/blob/ff43500e653abde45aec0f545564abfb648317af/src/engine/cancun.md#specification-2>
95 //
96 // For `engine_getPayloadV3`:
97 //
98 // 1. Client software **MUST** return `-38005: Unsupported fork` error if the `timestamp` of
99 // the built payload does not fall within the time frame of the Cancun fork.
100 //
101 // For `engine_forkchoiceUpdatedV3`:
102 //
103 // 2. Client software **MUST** return `-38005: Unsupported fork` error if the
104 // `payloadAttributes` is set and the `payloadAttributes.timestamp` does not fall within
105 // the time frame of the Cancun fork.
106 //
107 // For `engine_newPayloadV3`:
108 //
109 // 2. Client software **MUST** return `-38005: Unsupported fork` error if the `timestamp` of
110 // the payload does not fall within the time frame of the Cancun fork.
111 return Err(EngineObjectValidationError::UnsupportedFork)
112 }
113
114 let is_prague = chain_spec.is_prague_active_at_timestamp(timestamp);
115 if version.is_v4() && !is_prague {
116 // From the Engine API spec:
117 // <https://github.com/ethereum/execution-apis/blob/7907424db935b93c2fe6a3c0faab943adebe8557/src/engine/prague.md#specification-1>
118 //
119 // For `engine_getPayloadV4`:
120 //
121 // 1. Client software **MUST** return `-38005: Unsupported fork` error if the `timestamp` of
122 // the built payload does not fall within the time frame of the Prague fork.
123 //
124 // For `engine_forkchoiceUpdatedV4`:
125 //
126 // 2. Client software **MUST** return `-38005: Unsupported fork` error if the
127 // `payloadAttributes` is set and the `payloadAttributes.timestamp` does not fall within
128 // the time frame of the Prague fork.
129 //
130 // For `engine_newPayloadV4`:
131 //
132 // 2. Client software **MUST** return `-38005: Unsupported fork` error if the `timestamp` of
133 // the payload does not fall within the time frame of the Prague fork.
134 return Err(EngineObjectValidationError::UnsupportedFork)
135 }
136
137 let is_osaka = chain_spec.is_osaka_active_at_timestamp(timestamp);
138 if version.is_v5() && !is_osaka {
139 // From the Engine API spec:
140 // <https://github.com/ethereum/execution-apis/blob/15399c2e2f16a5f800bf3f285640357e2c245ad9/src/engine/osaka.md#specification>
141 //
142 // For `engine_getPayloadV5`
143 //
144 // 1. Client software MUST return -38005: Unsupported fork error if the timestamp of the
145 // built payload does not fall within the time frame of the Osaka fork.
146 return Err(EngineObjectValidationError::UnsupportedFork)
147 }
148
149 Ok(())
150}
151
152/// Validates the presence of the `withdrawals` field according to the payload timestamp.
153/// After Shanghai, withdrawals field must be [Some].
154/// Before Shanghai, withdrawals field must be [None];
155pub fn validate_withdrawals_presence<T: EthereumHardforks>(
156 chain_spec: &T,
157 version: EngineApiMessageVersion,
158 message_validation_kind: MessageValidationKind,
159 timestamp: u64,
160 has_withdrawals: bool,
161) -> Result<(), EngineObjectValidationError> {
162 let is_shanghai_active = chain_spec.is_shanghai_active_at_timestamp(timestamp);
163
164 match version {
165 EngineApiMessageVersion::V1 => {
166 if has_withdrawals {
167 return Err(message_validation_kind
168 .to_error(VersionSpecificValidationError::WithdrawalsNotSupportedInV1))
169 }
170 }
171 EngineApiMessageVersion::V2 |
172 EngineApiMessageVersion::V3 |
173 EngineApiMessageVersion::V4 |
174 EngineApiMessageVersion::V5 => {
175 if is_shanghai_active && !has_withdrawals {
176 return Err(message_validation_kind
177 .to_error(VersionSpecificValidationError::NoWithdrawalsPostShanghai))
178 }
179 if !is_shanghai_active && has_withdrawals {
180 return Err(message_validation_kind
181 .to_error(VersionSpecificValidationError::HasWithdrawalsPreShanghai))
182 }
183 }
184 };
185
186 Ok(())
187}
188
189/// Validate the presence of the `parentBeaconBlockRoot` field according to the given timestamp.
190/// This method is meant to be used with either a `payloadAttributes` field or a full payload, with
191/// the `engine_forkchoiceUpdated` and `engine_newPayload` methods respectively.
192///
193/// After Cancun, the `parentBeaconBlockRoot` field must be [Some].
194/// Before Cancun, the `parentBeaconBlockRoot` field must be [None].
195///
196/// If the engine API message version is V1 or V2, and the timestamp is post-Cancun, then this will
197/// return [`EngineObjectValidationError::UnsupportedFork`].
198///
199/// If the timestamp is before the Cancun fork and the engine API message version is V3, then this
200/// will return [`EngineObjectValidationError::UnsupportedFork`].
201///
202/// If the engine API message version is V3, but the `parentBeaconBlockRoot` is [None], then
203/// this will return [`VersionSpecificValidationError::NoParentBeaconBlockRootPostCancun`].
204///
205/// This implements the following Engine API spec rules:
206///
207/// 1. Client software **MUST** check that provided set of parameters and their fields strictly
208/// matches the expected one and return `-32602: Invalid params` error if this check fails. Any
209/// field having `null` value **MUST** be considered as not provided.
210///
211/// For `engine_forkchoiceUpdatedV3`:
212///
213/// 1. Client software **MUST** check that provided set of parameters and their fields strictly
214/// matches the expected one and return `-32602: Invalid params` error if this check fails. Any
215/// field having `null` value **MUST** be considered as not provided.
216///
217/// 2. Extend point (7) of the `engine_forkchoiceUpdatedV1` specification by defining the following
218/// sequence of checks that **MUST** be run over `payloadAttributes`:
219/// 1. `payloadAttributes` matches the `PayloadAttributesV3` structure, return `-38003: Invalid
220/// payload attributes` on failure.
221/// 2. `payloadAttributes.timestamp` falls within the time frame of the Cancun fork, return
222/// `-38005: Unsupported fork` on failure.
223/// 3. `payloadAttributes.timestamp` is greater than `timestamp` of a block referenced by
224/// `forkchoiceState.headBlockHash`, return `-38003: Invalid payload attributes` on failure.
225/// 4. If any of the above checks fails, the `forkchoiceState` update **MUST NOT** be rolled
226/// back.
227///
228/// For `engine_newPayloadV3`:
229///
230/// 2. Client software **MUST** return `-38005: Unsupported fork` error if the `timestamp` of the
231/// payload does not fall within the time frame of the Cancun fork.
232///
233/// For `engine_newPayloadV4`:
234///
235/// 2. Client software **MUST** return `-38005: Unsupported fork` error if the `timestamp` of the
236/// payload does not fall within the time frame of the Prague fork.
237///
238/// Returning the right error code (ie, if the client should return `-38003: Invalid payload
239/// attributes` is handled by the `message_validation_kind` parameter. If the parameter is
240/// `MessageValidationKind::Payload`, then the error code will be `-32602: Invalid params`. If the
241/// parameter is `MessageValidationKind::PayloadAttributes`, then the error code will be `-38003:
242/// Invalid payload attributes`.
243pub fn validate_parent_beacon_block_root_presence<T: EthereumHardforks>(
244 chain_spec: &T,
245 version: EngineApiMessageVersion,
246 validation_kind: MessageValidationKind,
247 timestamp: u64,
248 has_parent_beacon_block_root: bool,
249) -> Result<(), EngineObjectValidationError> {
250 // 1. Client software **MUST** check that provided set of parameters and their fields strictly
251 // matches the expected one and return `-32602: Invalid params` error if this check fails.
252 // Any field having `null` value **MUST** be considered as not provided.
253 //
254 // For `engine_forkchoiceUpdatedV3`:
255 //
256 // 2. Extend point (7) of the `engine_forkchoiceUpdatedV1` specification by defining the
257 // following sequence of checks that **MUST** be run over `payloadAttributes`:
258 // 1. `payloadAttributes` matches the `PayloadAttributesV3` structure, return `-38003:
259 // Invalid payload attributes` on failure.
260 // 2. `payloadAttributes.timestamp` falls within the time frame of the Cancun fork, return
261 // `-38005: Unsupported fork` on failure.
262 // 3. `payloadAttributes.timestamp` is greater than `timestamp` of a block referenced by
263 // `forkchoiceState.headBlockHash`, return `-38003: Invalid payload attributes` on
264 // failure.
265 // 4. If any of the above checks fails, the `forkchoiceState` update **MUST NOT** be rolled
266 // back.
267 match version {
268 EngineApiMessageVersion::V1 | EngineApiMessageVersion::V2 => {
269 if has_parent_beacon_block_root {
270 return Err(validation_kind.to_error(
271 VersionSpecificValidationError::ParentBeaconBlockRootNotSupportedBeforeV3,
272 ))
273 }
274 }
275 EngineApiMessageVersion::V3 | EngineApiMessageVersion::V4 | EngineApiMessageVersion::V5 => {
276 if !has_parent_beacon_block_root {
277 return Err(validation_kind
278 .to_error(VersionSpecificValidationError::NoParentBeaconBlockRootPostCancun))
279 }
280 }
281 };
282
283 // For `engine_forkchoiceUpdatedV3`:
284 //
285 // 2. Client software **MUST** return `-38005: Unsupported fork` error if the
286 // `payloadAttributes` is set and the `payloadAttributes.timestamp` does not fall within the
287 // time frame of the Cancun fork.
288 //
289 // For `engine_newPayloadV3`:
290 //
291 // 2. Client software **MUST** return `-38005: Unsupported fork` error if the `timestamp` of the
292 // payload does not fall within the time frame of the Cancun fork.
293 validate_payload_timestamp(chain_spec, version, timestamp)?;
294
295 Ok(())
296}
297
298/// A type that represents whether or not we are validating a payload or payload attributes.
299///
300/// This is used to ensure that the correct error code is returned when validating the payload or
301/// payload attributes.
302#[derive(Debug, Clone, Copy, PartialEq, Eq)]
303pub enum MessageValidationKind {
304 /// We are validating fields of a payload attributes.
305 PayloadAttributes,
306 /// We are validating fields of a payload.
307 Payload,
308}
309
310impl MessageValidationKind {
311 /// Returns an `EngineObjectValidationError` based on the given
312 /// `VersionSpecificValidationError` and the current validation kind.
313 pub const fn to_error(
314 self,
315 error: VersionSpecificValidationError,
316 ) -> EngineObjectValidationError {
317 match self {
318 Self::Payload => EngineObjectValidationError::Payload(error),
319 Self::PayloadAttributes => EngineObjectValidationError::PayloadAttributes(error),
320 }
321 }
322}
323
324/// Validates the presence or exclusion of fork-specific fields based on the ethereum execution
325/// payload, or payload attributes, and the message version.
326///
327/// The object being validated is provided by the [`PayloadOrAttributes`] argument, which can be
328/// either an execution payload, or payload attributes.
329///
330/// The version is provided by the [`EngineApiMessageVersion`] argument.
331pub fn validate_version_specific_fields<Payload, Type, T>(
332 chain_spec: &T,
333 version: EngineApiMessageVersion,
334 payload_or_attrs: PayloadOrAttributes<'_, Payload, Type>,
335) -> Result<(), EngineObjectValidationError>
336where
337 Payload: ExecutionPayload,
338 Type: PayloadAttributes,
339 T: EthereumHardforks,
340{
341 validate_withdrawals_presence(
342 chain_spec,
343 version,
344 payload_or_attrs.message_validation_kind(),
345 payload_or_attrs.timestamp(),
346 payload_or_attrs.withdrawals().is_some(),
347 )?;
348 validate_parent_beacon_block_root_presence(
349 chain_spec,
350 version,
351 payload_or_attrs.message_validation_kind(),
352 payload_or_attrs.timestamp(),
353 payload_or_attrs.parent_beacon_block_root().is_some(),
354 )
355}
356
357/// The version of Engine API message.
358#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
359pub enum EngineApiMessageVersion {
360 /// Version 1
361 V1 = 1,
362 /// Version 2
363 ///
364 /// Added in the Shanghai hardfork.
365 V2 = 2,
366 /// Version 3
367 ///
368 /// Added in the Cancun hardfork.
369 V3 = 3,
370 /// Version 4
371 ///
372 /// Added in the Prague hardfork.
373 #[default]
374 V4 = 4,
375 /// Version 5
376 ///
377 /// Added in the Osaka hardfork.
378 V5 = 5,
379}
380
381impl EngineApiMessageVersion {
382 /// Returns true if the version is V1.
383 pub const fn is_v1(&self) -> bool {
384 matches!(self, Self::V1)
385 }
386
387 /// Returns true if the version is V2.
388 pub const fn is_v2(&self) -> bool {
389 matches!(self, Self::V2)
390 }
391
392 /// Returns true if the version is V3.
393 pub const fn is_v3(&self) -> bool {
394 matches!(self, Self::V3)
395 }
396
397 /// Returns true if the version is V4.
398 pub const fn is_v4(&self) -> bool {
399 matches!(self, Self::V4)
400 }
401
402 /// Returns true if the version is V5.
403 pub const fn is_v5(&self) -> bool {
404 matches!(self, Self::V5)
405 }
406}
407
408/// Determines how we should choose the payload to return.
409#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
410pub enum PayloadKind {
411 /// Returns the next best available payload (the earliest available payload).
412 /// This does not wait for a real for pending job to finish if there's no best payload yet and
413 /// is allowed to race various payload jobs (empty, pending best) against each other and
414 /// returns whichever job finishes faster.
415 ///
416 /// This should be used when it's more important to return a valid payload as fast as possible.
417 /// For example, the engine API timeout for `engine_getPayload` is 1s and clients should rather
418 /// return an empty payload than indefinitely waiting for the pending payload job to finish and
419 /// risk missing the deadline.
420 #[default]
421 Earliest,
422 /// Only returns once we have at least one built payload.
423 ///
424 /// Compared to [`PayloadKind::Earliest`] this does not race an empty payload job against the
425 /// already in progress one, and returns the best available built payload or awaits the job in
426 /// progress.
427 WaitForPending,
428}
429
430/// Validates that execution requests are valid according to Engine API specification.
431///
432/// `executionRequests`: `Array of DATA` - List of execution layer triggered requests. Each list
433/// element is a `requests` byte array as defined by [EIP-7685](https://eips.ethereum.org/EIPS/eip-7685).
434/// The first byte of each element is the `request_type` and the remaining bytes are the
435/// `request_data`. Elements of the list **MUST** be ordered by `request_type` in ascending order.
436/// Elements with empty `request_data` **MUST** be excluded from the list. If any element is out of
437/// order, has a length of 1-byte or shorter, or more than one element has the same type byte,
438/// client software **MUST** return `-32602: Invalid params` error.
439pub fn validate_execution_requests(requests: &[Bytes]) -> Result<(), EngineObjectValidationError> {
440 let mut last_request_type = None;
441 for request in requests {
442 if request.len() <= 1 {
443 return Err(EngineObjectValidationError::InvalidParams(
444 "EmptyExecutionRequest".to_string().into(),
445 ))
446 }
447
448 let request_type = request[0];
449 if Some(request_type) < last_request_type {
450 return Err(EngineObjectValidationError::InvalidParams(
451 "OutOfOrderExecutionRequest".to_string().into(),
452 ))
453 }
454
455 if Some(request_type) == last_request_type {
456 return Err(EngineObjectValidationError::InvalidParams(
457 "DuplicatedExecutionRequestType".to_string().into(),
458 ))
459 }
460
461 last_request_type = Some(request_type);
462 }
463 Ok(())
464}
465
466#[cfg(test)]
467mod tests {
468 use super::*;
469 use assert_matches::assert_matches;
470
471 #[test]
472 fn version_ord() {
473 assert!(EngineApiMessageVersion::V4 > EngineApiMessageVersion::V3);
474 }
475
476 #[test]
477 fn execution_requests_validation() {
478 assert_matches!(validate_execution_requests(&[]), Ok(()));
479
480 let valid_requests = [
481 Bytes::from_iter([1, 2]),
482 Bytes::from_iter([2, 3]),
483 Bytes::from_iter([3, 4]),
484 Bytes::from_iter([4, 5]),
485 ];
486 assert_matches!(validate_execution_requests(&valid_requests), Ok(()));
487
488 let requests_with_empty = [
489 Bytes::from_iter([1, 2]),
490 Bytes::from_iter([2, 3]),
491 Bytes::new(),
492 Bytes::from_iter([3, 4]),
493 ];
494 assert_matches!(
495 validate_execution_requests(&requests_with_empty),
496 Err(EngineObjectValidationError::InvalidParams(_))
497 );
498
499 let mut requests_valid_reversed = valid_requests;
500 requests_valid_reversed.reverse();
501 assert_matches!(
502 validate_execution_requests(&requests_with_empty),
503 Err(EngineObjectValidationError::InvalidParams(_))
504 );
505
506 let requests_out_of_order = [
507 Bytes::from_iter([1, 2]),
508 Bytes::from_iter([2, 3]),
509 Bytes::from_iter([4, 5]),
510 Bytes::from_iter([3, 4]),
511 ];
512 assert_matches!(
513 validate_execution_requests(&requests_out_of_order),
514 Err(EngineObjectValidationError::InvalidParams(_))
515 );
516
517 let duplicate_request_types = [
518 Bytes::from_iter([1, 2]),
519 Bytes::from_iter([3, 3]),
520 Bytes::from_iter([4, 5]),
521 Bytes::from_iter([4, 4]),
522 ];
523 assert_matches!(
524 validate_execution_requests(&duplicate_request_types),
525 Err(EngineObjectValidationError::InvalidParams(_))
526 );
527 }
528}