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}