Skip to main content

reth_payload_primitives/
error.rs

1//! Error types for payload operations.
2
3use alloc::{boxed::Box, string::ToString};
4use alloy_primitives::B256;
5use alloy_rpc_types_engine::{ForkchoiceUpdateError, PayloadError, PayloadStatusEnum};
6use core::error;
7use reth_errors::{BlockExecutionError, ProviderError, RethError};
8use tokio::sync::{mpsc, oneshot};
9
10/// Possible error variants during payload building.
11#[derive(Debug, thiserror::Error)]
12pub enum PayloadBuilderError {
13    /// Thrown when the parent header cannot be found
14    #[error("missing parent header: {0}")]
15    MissingParentHeader(B256),
16    /// Thrown when the parent block is missing.
17    #[error("missing parent block {0}")]
18    MissingParentBlock(B256),
19    /// An oneshot channels has been closed.
20    #[error("sender has been dropped")]
21    ChannelClosed,
22    /// If there's no payload to resolve.
23    #[error("missing payload")]
24    MissingPayload,
25    /// Other internal error
26    #[error(transparent)]
27    Internal(#[from] RethError),
28    /// Unrecoverable error during evm execution.
29    #[error("evm execution error: {0}")]
30    EvmExecutionError(Box<dyn core::error::Error + Send + Sync>),
31    /// Any other payload building errors.
32    #[error(transparent)]
33    Other(Box<dyn core::error::Error + Send + Sync>),
34    /// The payload attributes are invalid on top of the parent block, e.g. a transaction they
35    /// include is invalid.
36    ///
37    /// Returned from `PayloadJobGenerator::new_payload_job`, this is the only variant that rejects
38    /// the attributes; returned later from a running job, it is an internal error like any other.
39    /// Failures of this node, such as a failed state read during execution, must use another
40    /// variant.
41    #[error("invalid payload attributes: {0}")]
42    InvalidAttributes(Box<dyn core::error::Error + Send + Sync>),
43}
44
45impl PayloadBuilderError {
46    /// Create a new EVM error from a boxed error.
47    pub fn evm<E>(error: E) -> Self
48    where
49        E: core::error::Error + Send + Sync + 'static,
50    {
51        Self::EvmExecutionError(Box::new(error))
52    }
53
54    /// Create a new error from a boxed error.
55    pub fn other<E>(error: E) -> Self
56    where
57        E: core::error::Error + Send + Sync + 'static,
58    {
59        Self::Other(Box::new(error))
60    }
61
62    /// Create a new error that rejects the payload attributes.
63    pub fn invalid_attributes<E>(error: E) -> Self
64    where
65        E: core::error::Error + Send + Sync + 'static,
66    {
67        Self::InvalidAttributes(Box::new(error))
68    }
69}
70
71impl From<ProviderError> for PayloadBuilderError {
72    fn from(error: ProviderError) -> Self {
73        Self::Internal(RethError::Provider(error))
74    }
75}
76
77impl From<oneshot::error::RecvError> for PayloadBuilderError {
78    fn from(_: oneshot::error::RecvError) -> Self {
79        Self::ChannelClosed
80    }
81}
82
83impl From<BlockExecutionError> for PayloadBuilderError {
84    fn from(error: BlockExecutionError) -> Self {
85        Self::evm(error)
86    }
87}
88
89impl<T> From<mpsc::error::SendError<T>> for PayloadBuilderError {
90    fn from(_: mpsc::error::SendError<T>) -> Self {
91        Self::ChannelClosed
92    }
93}
94
95/// Thrown when the payload or attributes are known to be invalid __before__ processing.
96///
97/// This is used mainly for
98/// [`validate_version_specific_fields`](crate::validate_version_specific_fields), which validates
99/// both execution payloads and forkchoice update attributes with respect to a method version.
100#[derive(thiserror::Error, Debug)]
101pub enum EngineObjectValidationError {
102    /// Thrown when the underlying validation error occurred while validating an
103    /// `ExecutionPayload`.
104    #[error("Payload validation error: {0}")]
105    Payload(VersionSpecificValidationError),
106
107    /// Thrown when the underlying validation error occurred while validating a
108    /// `PayloadAttributes`.
109    #[error("Payload attributes validation error: {0}")]
110    PayloadAttributes(VersionSpecificValidationError),
111
112    /// Thrown if `PayloadAttributes` or `ExecutionPayload` were provided with a timestamp, but the
113    /// version of the engine method called is meant for a fork that occurs after the provided
114    /// timestamp.
115    #[error("Unsupported fork")]
116    UnsupportedFork,
117    /// Another type of error that is not covered by the above variants.
118    #[error("Invalid params: {0}")]
119    InvalidParams(#[from] Box<dyn core::error::Error + Send + Sync>),
120}
121
122/// Thrown when validating an execution payload OR payload attributes fails due to:
123/// * The existence of a new field that is not supported in the given engine method version, or
124/// * The absence of a field that is required in the given engine method version
125#[derive(thiserror::Error, Debug)]
126pub enum VersionSpecificValidationError {
127    /// Thrown if the pre-V3 `PayloadAttributes` or `ExecutionPayload` contains a parent beacon
128    /// block root
129    #[error("parent beacon block root not supported before V3")]
130    ParentBeaconBlockRootNotSupportedBeforeV3,
131    /// Thrown if `engine_forkchoiceUpdatedV1` or `engine_newPayloadV1` contains withdrawals
132    #[error("withdrawals not supported in V1")]
133    WithdrawalsNotSupportedInV1,
134    /// Thrown if `engine_forkchoiceUpdated` or `engine_newPayload` contains no withdrawals after
135    /// Shanghai
136    #[error("no withdrawals post-Shanghai")]
137    NoWithdrawalsPostShanghai,
138    /// Thrown if `engine_forkchoiceUpdated` or `engine_newPayload` contains withdrawals before
139    /// Shanghai
140    #[error("withdrawals pre-Shanghai")]
141    HasWithdrawalsPreShanghai,
142    /// Thrown if the `PayloadAttributes` or `ExecutionPayload` contains no parent beacon block
143    /// root after Cancun
144    #[error("no parent beacon block root post-cancun")]
145    NoParentBeaconBlockRootPostCancun,
146    /// Thrown if the current engine method version does not support a block access list
147    #[error("block access list not supported in this engine API version")]
148    BlockAccessListNotSupported,
149    /// Thrown if  `engine_newPayload` contains no block access list
150    /// after Amsterdam
151    #[error("no block access list post-Amsterdam")]
152    NoBlockAccessListPostAmsterdam,
153    /// Thrown if  `engine_newPayload` contains block access list
154    /// before Amsterdam
155    #[error("block access list pre-Amsterdam")]
156    HasBlockAccessListPreAmsterdam,
157    /// Thrown if the current engine method version does not support a slot number
158    #[error("slot number not supported in this engine API version")]
159    SlotNumberNotSupported,
160    /// Thrown if  `engine_newPayload` contains no slot number
161    /// after Amsterdam
162    #[error("no slot number post-Amsterdam")]
163    NoSlotNumberPostAmsterdam,
164    /// Thrown if  `engine_newPayload` contains slot number
165    /// before Amsterdam
166    #[error("slot number pre-Amsterdam")]
167    HasSlotNumberPreAmsterdam,
168}
169
170/// Error validating payload received over `newPayload` API.
171#[derive(thiserror::Error, Debug)]
172pub enum NewPayloadError {
173    /// Payload validation error.
174    #[error(transparent)]
175    Eth(#[from] PayloadError),
176    /// Custom payload validation error.
177    #[error(transparent)]
178    Other(Box<dyn error::Error + Send + Sync>),
179}
180
181impl NewPayloadError {
182    /// Creates instance of variant [`NewPayloadError::Other`].
183    #[inline]
184    pub fn other(err: impl error::Error + Send + Sync + 'static) -> Self {
185        Self::Other(Box::new(err))
186    }
187
188    /// Returns `true` if the error is caused by a block hash mismatch.
189    #[inline]
190    pub const fn is_block_hash_mismatch(&self) -> bool {
191        matches!(self, Self::Eth(PayloadError::BlockHash { .. }))
192    }
193
194    /// Returns `true` if the error is caused by invalid block hashes (Cancun).
195    #[inline]
196    pub const fn is_invalid_versioned_hashes(&self) -> bool {
197        matches!(self, Self::Eth(PayloadError::InvalidVersionedHashes))
198    }
199}
200
201impl From<NewPayloadError> for PayloadStatusEnum {
202    fn from(error: NewPayloadError) -> Self {
203        Self::Invalid { validation_error: error.to_string() }
204    }
205}
206
207impl EngineObjectValidationError {
208    /// Creates an instance of the `InvalidParams` variant with the given error.
209    pub fn invalid_params<E>(error: E) -> Self
210    where
211        E: core::error::Error + Send + Sync + 'static,
212    {
213        Self::InvalidParams(Box::new(error))
214    }
215}
216
217/// Thrown when validating the correctness of a payloadattributes object.
218#[derive(thiserror::Error, Debug)]
219pub enum InvalidPayloadAttributesError {
220    /// Thrown if the timestamp of the payload attributes is invalid according to the engine specs.
221    #[error("invalid timestamp")]
222    InvalidTimestamp,
223    /// Another type of error that is not covered by the above variants.
224    #[error("Invalid params: {0}")]
225    InvalidParams(#[from] Box<dyn core::error::Error + Send + Sync>),
226}
227
228impl From<InvalidPayloadAttributesError> for ForkchoiceUpdateError {
229    fn from(_: InvalidPayloadAttributesError) -> Self {
230        Self::UpdatedInvalidPayloadAttributes
231    }
232}