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
11mod error;
12pub use error::{
13 EngineObjectValidationError, InvalidPayloadAttributesError, PayloadBuilderError,
14 VersionSpecificValidationError,
15};
16
17/// Contains traits to abstract over payload attributes types and default implementations of the
18/// [`PayloadAttributes`] trait for ethereum mainnet and optimism types.
19mod traits;
20pub use traits::{
21 BuiltPayload, PayloadAttributes, PayloadAttributesBuilder, PayloadBuilderAttributes,
22};
23
24mod payload;
25pub use payload::PayloadOrAttributes;
26
27use reth_chainspec::EthereumHardforks;
28/// The types that are used by the engine API.
29pub trait PayloadTypes: Send + Sync + Unpin + core::fmt::Debug + Clone + 'static {
30 /// The built payload type.
31 type BuiltPayload: BuiltPayload + Clone + Unpin;
32
33 /// The RPC payload attributes type the CL node emits via the engine API.
34 type PayloadAttributes: PayloadAttributes + Unpin;
35
36 /// The payload attributes type that contains information about a running payload job.
37 type PayloadBuilderAttributes: PayloadBuilderAttributes<RpcPayloadAttributes = Self::PayloadAttributes>
38 + Clone
39 + Unpin;
40}
41
42/// Validates the timestamp depending on the version called:
43///
44/// * If V2, this ensures that the payload timestamp is pre-Cancun.
45/// * If V3, this ensures that the payload timestamp is within the Cancun timestamp.
46/// * If V4, this ensures that the payload timestamp is within the Prague timestamp.
47///
48/// Otherwise, this will return [`EngineObjectValidationError::UnsupportedFork`].
49pub fn validate_payload_timestamp(
50 chain_spec: impl EthereumHardforks,
51 version: EngineApiMessageVersion,
52 timestamp: u64,
53) -> Result<(), EngineObjectValidationError> {
54 let is_cancun = chain_spec.is_cancun_active_at_timestamp(timestamp);
55 if version == EngineApiMessageVersion::V2 && is_cancun {
56 // From the Engine API spec:
57 //
58 // ### Update the methods of previous forks
59 //
60 // This document defines how Cancun payload should be handled by the [`Shanghai
61 // API`](https://github.com/ethereum/execution-apis/blob/ff43500e653abde45aec0f545564abfb648317af/src/engine/shanghai.md).
62 //
63 // For the following methods:
64 //
65 // - [`engine_forkchoiceUpdatedV2`](https://github.com/ethereum/execution-apis/blob/ff43500e653abde45aec0f545564abfb648317af/src/engine/shanghai.md#engine_forkchoiceupdatedv2)
66 // - [`engine_newPayloadV2`](https://github.com/ethereum/execution-apis/blob/ff43500e653abde45aec0f545564abfb648317af/src/engine/shanghai.md#engine_newpayloadV2)
67 // - [`engine_getPayloadV2`](https://github.com/ethereum/execution-apis/blob/ff43500e653abde45aec0f545564abfb648317af/src/engine/shanghai.md#engine_getpayloadv2)
68 //
69 // a validation **MUST** be added:
70 //
71 // 1. Client software **MUST** return `-38005: Unsupported fork` error if the `timestamp` of
72 // payload or payloadAttributes is greater or equal to the Cancun activation timestamp.
73 return Err(EngineObjectValidationError::UnsupportedFork)
74 }
75
76 if version == EngineApiMessageVersion::V3 && !is_cancun {
77 // From the Engine API spec:
78 // <https://github.com/ethereum/execution-apis/blob/ff43500e653abde45aec0f545564abfb648317af/src/engine/cancun.md#specification-2>
79 //
80 // For `engine_getPayloadV3`:
81 //
82 // 1. Client software **MUST** return `-38005: Unsupported fork` error if the `timestamp` of
83 // the built payload does not fall within the time frame of the Cancun fork.
84 //
85 // For `engine_forkchoiceUpdatedV3`:
86 //
87 // 2. Client software **MUST** return `-38005: Unsupported fork` error if the
88 // `payloadAttributes` is set and the `payloadAttributes.timestamp` does not fall within
89 // the time frame of the Cancun fork.
90 //
91 // For `engine_newPayloadV3`:
92 //
93 // 2. Client software **MUST** return `-38005: Unsupported fork` error if the `timestamp` of
94 // the payload does not fall within the time frame of the Cancun fork.
95 return Err(EngineObjectValidationError::UnsupportedFork)
96 }
97
98 let is_prague = chain_spec.is_prague_active_at_timestamp(timestamp);
99 if version == EngineApiMessageVersion::V4 && !is_prague {
100 // From the Engine API spec:
101 // <https://github.com/ethereum/execution-apis/blob/7907424db935b93c2fe6a3c0faab943adebe8557/src/engine/prague.md#specification-1>
102 //
103 // For `engine_getPayloadV4`:
104 //
105 // 1. Client software **MUST** return `-38005: Unsupported fork` error if the `timestamp` of
106 // the built payload does not fall within the time frame of the Prague fork.
107 //
108 // For `engine_forkchoiceUpdatedV4`:
109 //
110 // 2. Client software **MUST** return `-38005: Unsupported fork` error if the
111 // `payloadAttributes` is set and the `payloadAttributes.timestamp` does not fall within
112 // the time frame of the Prague fork.
113 //
114 // For `engine_newPayloadV4`:
115 //
116 // 2. Client software **MUST** return `-38005: Unsupported fork` error if the `timestamp` of
117 // the payload does not fall within the time frame of the Prague fork.
118 return Err(EngineObjectValidationError::UnsupportedFork)
119 }
120 Ok(())
121}
122
123/// Validates the presence of the `withdrawals` field according to the payload timestamp.
124/// After Shanghai, withdrawals field must be [Some].
125/// Before Shanghai, withdrawals field must be [None];
126pub fn validate_withdrawals_presence<T: EthereumHardforks>(
127 chain_spec: &T,
128 version: EngineApiMessageVersion,
129 message_validation_kind: MessageValidationKind,
130 timestamp: u64,
131 has_withdrawals: bool,
132) -> Result<(), EngineObjectValidationError> {
133 let is_shanghai_active = chain_spec.is_shanghai_active_at_timestamp(timestamp);
134
135 match version {
136 EngineApiMessageVersion::V1 => {
137 if has_withdrawals {
138 return Err(message_validation_kind
139 .to_error(VersionSpecificValidationError::WithdrawalsNotSupportedInV1))
140 }
141 }
142 EngineApiMessageVersion::V2 | EngineApiMessageVersion::V3 | EngineApiMessageVersion::V4 => {
143 if is_shanghai_active && !has_withdrawals {
144 return Err(message_validation_kind
145 .to_error(VersionSpecificValidationError::NoWithdrawalsPostShanghai))
146 }
147 if !is_shanghai_active && has_withdrawals {
148 return Err(message_validation_kind
149 .to_error(VersionSpecificValidationError::HasWithdrawalsPreShanghai))
150 }
151 }
152 };
153
154 Ok(())
155}
156
157/// Validate the presence of the `parentBeaconBlockRoot` field according to the given timestamp.
158/// This method is meant to be used with either a `payloadAttributes` field or a full payload, with
159/// the `engine_forkchoiceUpdated` and `engine_newPayload` methods respectively.
160///
161/// After Cancun, the `parentBeaconBlockRoot` field must be [Some].
162/// Before Cancun, the `parentBeaconBlockRoot` field must be [None].
163///
164/// If the engine API message version is V1 or V2, and the timestamp is post-Cancun, then this will
165/// return [`EngineObjectValidationError::UnsupportedFork`].
166///
167/// If the timestamp is before the Cancun fork and the engine API message version is V3, then this
168/// will return [`EngineObjectValidationError::UnsupportedFork`].
169///
170/// If the engine API message version is V3, but the `parentBeaconBlockRoot` is [None], then
171/// this will return [`VersionSpecificValidationError::NoParentBeaconBlockRootPostCancun`].
172///
173/// This implements the following Engine API spec rules:
174///
175/// 1. Client software **MUST** check that provided set of parameters and their fields strictly
176/// matches the expected one and return `-32602: Invalid params` error if this check fails. Any
177/// field having `null` value **MUST** be considered as not provided.
178///
179/// For `engine_forkchoiceUpdatedV3`:
180///
181/// 1. Client software **MUST** check that provided set of parameters and their fields strictly
182/// matches the expected one and return `-32602: Invalid params` error if this check fails. Any
183/// field having `null` value **MUST** be considered as not provided.
184///
185/// 2. Extend point (7) of the `engine_forkchoiceUpdatedV1` specification by defining the following
186/// sequence of checks that **MUST** be run over `payloadAttributes`:
187/// 1. `payloadAttributes` matches the `PayloadAttributesV3` structure, return `-38003: Invalid
188/// payload attributes` on failure.
189/// 2. `payloadAttributes.timestamp` falls within the time frame of the Cancun fork, return
190/// `-38005: Unsupported fork` on failure.
191/// 3. `payloadAttributes.timestamp` is greater than `timestamp` of a block referenced by
192/// `forkchoiceState.headBlockHash`, return `-38003: Invalid payload attributes` on failure.
193/// 4. If any of the above checks fails, the `forkchoiceState` update **MUST NOT** be rolled
194/// back.
195///
196/// For `engine_newPayloadV3`:
197///
198/// 2. Client software **MUST** return `-38005: Unsupported fork` error if the `timestamp` of the
199/// payload does not fall within the time frame of the Cancun fork.
200///
201/// For `engine_newPayloadV4`:
202///
203/// 2. Client software **MUST** return `-38005: Unsupported fork` error if the `timestamp` of the
204/// payload does not fall within the time frame of the Prague fork.
205///
206/// Returning the right error code (ie, if the client should return `-38003: Invalid payload
207/// attributes` is handled by the `message_validation_kind` parameter. If the parameter is
208/// `MessageValidationKind::Payload`, then the error code will be `-32602: Invalid params`. If the
209/// parameter is `MessageValidationKind::PayloadAttributes`, then the error code will be `-38003:
210/// Invalid payload attributes`.
211pub fn validate_parent_beacon_block_root_presence<T: EthereumHardforks>(
212 chain_spec: &T,
213 version: EngineApiMessageVersion,
214 validation_kind: MessageValidationKind,
215 timestamp: u64,
216 has_parent_beacon_block_root: bool,
217) -> Result<(), EngineObjectValidationError> {
218 // 1. Client software **MUST** check that provided set of parameters and their fields strictly
219 // matches the expected one and return `-32602: Invalid params` error if this check fails.
220 // Any field having `null` value **MUST** be considered as not provided.
221 //
222 // For `engine_forkchoiceUpdatedV3`:
223 //
224 // 2. Extend point (7) of the `engine_forkchoiceUpdatedV1` specification by defining the
225 // following sequence of checks that **MUST** be run over `payloadAttributes`:
226 // 1. `payloadAttributes` matches the `PayloadAttributesV3` structure, return `-38003:
227 // Invalid payload attributes` on failure.
228 // 2. `payloadAttributes.timestamp` falls within the time frame of the Cancun fork, return
229 // `-38005: Unsupported fork` on failure.
230 // 3. `payloadAttributes.timestamp` is greater than `timestamp` of a block referenced by
231 // `forkchoiceState.headBlockHash`, return `-38003: Invalid payload attributes` on
232 // failure.
233 // 4. If any of the above checks fails, the `forkchoiceState` update **MUST NOT** be rolled
234 // back.
235 match version {
236 EngineApiMessageVersion::V1 | EngineApiMessageVersion::V2 => {
237 if has_parent_beacon_block_root {
238 return Err(validation_kind.to_error(
239 VersionSpecificValidationError::ParentBeaconBlockRootNotSupportedBeforeV3,
240 ))
241 }
242 }
243 EngineApiMessageVersion::V3 | EngineApiMessageVersion::V4 => {
244 if !has_parent_beacon_block_root {
245 return Err(validation_kind
246 .to_error(VersionSpecificValidationError::NoParentBeaconBlockRootPostCancun))
247 }
248 }
249 };
250
251 // For `engine_forkchoiceUpdatedV3`:
252 //
253 // 2. Client software **MUST** return `-38005: Unsupported fork` error if the
254 // `payloadAttributes` is set and the `payloadAttributes.timestamp` does not fall within the
255 // time frame of the Cancun fork.
256 //
257 // For `engine_newPayloadV3`:
258 //
259 // 2. Client software **MUST** return `-38005: Unsupported fork` error if the `timestamp` of the
260 // payload does not fall within the time frame of the Cancun fork.
261 validate_payload_timestamp(chain_spec, version, timestamp)?;
262
263 Ok(())
264}
265
266/// A type that represents whether or not we are validating a payload or payload attributes.
267///
268/// This is used to ensure that the correct error code is returned when validating the payload or
269/// payload attributes.
270#[derive(Debug, Clone, Copy, PartialEq, Eq)]
271pub enum MessageValidationKind {
272 /// We are validating fields of a payload attributes.
273 PayloadAttributes,
274 /// We are validating fields of a payload.
275 Payload,
276}
277
278impl MessageValidationKind {
279 /// Returns an `EngineObjectValidationError` based on the given
280 /// `VersionSpecificValidationError` and the current validation kind.
281 pub const fn to_error(
282 self,
283 error: VersionSpecificValidationError,
284 ) -> EngineObjectValidationError {
285 match self {
286 Self::Payload => EngineObjectValidationError::Payload(error),
287 Self::PayloadAttributes => EngineObjectValidationError::PayloadAttributes(error),
288 }
289 }
290}
291
292/// Validates the presence or exclusion of fork-specific fields based on the ethereum execution
293/// payload, or payload attributes, and the message version.
294///
295/// The object being validated is provided by the [`PayloadOrAttributes`] argument, which can be
296/// either an execution payload, or payload attributes.
297///
298/// The version is provided by the [`EngineApiMessageVersion`] argument.
299pub fn validate_version_specific_fields<Type, T>(
300 chain_spec: &T,
301 version: EngineApiMessageVersion,
302 payload_or_attrs: PayloadOrAttributes<'_, Type>,
303) -> Result<(), EngineObjectValidationError>
304where
305 Type: PayloadAttributes,
306 T: EthereumHardforks,
307{
308 validate_withdrawals_presence(
309 chain_spec,
310 version,
311 payload_or_attrs.message_validation_kind(),
312 payload_or_attrs.timestamp(),
313 payload_or_attrs.withdrawals().is_some(),
314 )?;
315 validate_parent_beacon_block_root_presence(
316 chain_spec,
317 version,
318 payload_or_attrs.message_validation_kind(),
319 payload_or_attrs.timestamp(),
320 payload_or_attrs.parent_beacon_block_root().is_some(),
321 )
322}
323
324/// The version of Engine API message.
325#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
326pub enum EngineApiMessageVersion {
327 /// Version 1
328 V1 = 1,
329 /// Version 2
330 ///
331 /// Added in the Shanghai hardfork.
332 V2 = 2,
333 /// Version 3
334 ///
335 /// Added in the Cancun hardfork.
336 #[default]
337 V3 = 3,
338 /// Version 4
339 ///
340 /// Added in the Prague hardfork.
341 V4 = 4,
342}
343
344/// Determines how we should choose the payload to return.
345#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
346pub enum PayloadKind {
347 /// Returns the next best available payload (the earliest available payload).
348 /// This does not wait for a real for pending job to finish if there's no best payload yet and
349 /// is allowed to race various payload jobs (empty, pending best) against each other and
350 /// returns whichever job finishes faster.
351 ///
352 /// This should be used when it's more important to return a valid payload as fast as possible.
353 /// For example, the engine API timeout for `engine_getPayload` is 1s and clients should rather
354 /// return an empty payload than indefinitely waiting for the pending payload job to finish and
355 /// risk missing the deadline.
356 #[default]
357 Earliest,
358 /// Only returns once we have at least one built payload.
359 ///
360 /// Compared to [`PayloadKind::Earliest`] this does not race an empty payload job against the
361 /// already in progress one, and returns the best available built payload or awaits the job in
362 /// progress.
363 WaitForPending,
364}
365
366#[cfg(test)]
367mod tests {
368 use super::*;
369
370 #[test]
371 fn version_ord() {
372 assert!(EngineApiMessageVersion::V4 > EngineApiMessageVersion::V3);
373 }
374}