Skip to main content

reth_rpc_engine_api/
error.rs

1use alloy_primitives::B256;
2use alloy_rpc_types_engine::{
3    ForkchoiceUpdateError, INVALID_FORK_CHOICE_STATE_ERROR, INVALID_FORK_CHOICE_STATE_ERROR_MSG,
4    INVALID_PAYLOAD_ATTRIBUTES_ERROR, INVALID_PAYLOAD_ATTRIBUTES_ERROR_MSG, TOO_DEEP_REORG_ERROR,
5    TOO_DEEP_REORG_ERROR_MSG,
6};
7use jsonrpsee_types::error::{
8    INTERNAL_ERROR_CODE, INVALID_PARAMS_CODE, INVALID_PARAMS_MSG, SERVER_ERROR_MSG,
9};
10use reth_engine_primitives::{BeaconForkChoiceUpdateError, BeaconOnNewPayloadError};
11use reth_payload_builder_primitives::PayloadBuilderError;
12use reth_payload_primitives::{EngineObjectValidationError, VersionSpecificValidationError};
13use thiserror::Error;
14
15/// The Engine API result type
16pub type EngineApiResult<Ok> = Result<Ok, EngineApiError>;
17
18/// Payload unsupported fork code.
19pub const UNSUPPORTED_FORK_CODE: i32 = -38005;
20/// Payload unknown error code.
21pub const UNKNOWN_PAYLOAD_CODE: i32 = -38001;
22/// Request too large error code.
23pub const REQUEST_TOO_LARGE_CODE: i32 = -38004;
24
25/// Error message for the request too large error.
26const REQUEST_TOO_LARGE_MESSAGE: &str = "Too large request";
27
28/// Error returned by [`EngineApi`][crate::EngineApi]
29///
30/// Note: This is a high-fidelity error type which can be converted to an RPC error that adheres to
31/// the [Engine API spec](https://github.com/ethereum/execution-apis/blob/main/src/engine/common.md#errors).
32#[derive(Error, Debug)]
33pub enum EngineApiError {
34    // **IMPORTANT**: keep error messages in sync with the Engine API spec linked above.
35    /// Payload does not exist / is not available.
36    #[error("Unknown payload")]
37    UnknownPayload,
38    /// The payload body request length is too large.
39    #[error("requested count too large: {len}")]
40    PayloadRequestTooLarge {
41        /// The length that was requested.
42        len: u64,
43    },
44    /// Too many requested versioned hashes for blobs request
45    #[error("requested blob count too large: {len}")]
46    BlobRequestTooLarge {
47        /// The length that was requested.
48        len: usize,
49    },
50    /// Thrown if `engine_getPayloadBodiesByRangeV1` contains an invalid range
51    #[error("invalid start ({start}) or count ({count})")]
52    InvalidBodiesRange {
53        /// Start of the range
54        start: u64,
55        /// Requested number of items
56        count: u64,
57    },
58    /// Terminal block hash mismatch during transition configuration exchange.
59    #[error(
60        "invalid transition terminal block hash: \
61         execution: {execution:?}, consensus: {consensus}"
62    )]
63    TerminalBlockHash {
64        /// Execution terminal block hash. `None` if block number is not found in the database.
65        execution: Option<B256>,
66        /// Consensus terminal block hash.
67        consensus: B256,
68    },
69    /// An error occurred while processing the fork choice update in the beacon consensus engine.
70    #[error(transparent)]
71    ForkChoiceUpdate(#[from] BeaconForkChoiceUpdateError),
72    /// An error occurred while processing a new payload in the beacon consensus engine.
73    #[error(transparent)]
74    NewPayload(#[from] BeaconOnNewPayloadError),
75    /// Encountered an internal error.
76    #[error(transparent)]
77    Internal(#[from] Box<dyn core::error::Error + Send + Sync>),
78    /// Fetching the payload failed
79    #[error(transparent)]
80    GetPayloadError(#[from] PayloadBuilderError),
81    /// The payload or attributes are known to be malformed before processing.
82    #[error(transparent)]
83    EngineObjectValidationError(#[from] EngineObjectValidationError),
84    /// Requests hash provided, but can't be accepted by the API.
85    #[error("requests hash cannot be accepted by the API without `--engine.accept-execution-requests-hash` flag")]
86    UnexpectedRequestsHash,
87    /// Any other rpc error
88    #[error("{0}")]
89    Other(jsonrpsee_types::ErrorObject<'static>),
90}
91
92impl EngineApiError {
93    /// Crates a new [`EngineApiError::Other`] variant.
94    pub const fn other(err: jsonrpsee_types::ErrorObject<'static>) -> Self {
95        Self::Other(err)
96    }
97}
98
99/// Helper type to represent the `error` field in the error response:
100/// <https://github.com/ethereum/execution-apis/blob/main/src/engine/common.md#errors>
101#[derive(serde::Serialize)]
102struct ErrorData {
103    err: String,
104}
105
106impl ErrorData {
107    #[inline]
108    fn new(err: impl std::fmt::Display) -> Self {
109        Self { err: err.to_string() }
110    }
111}
112
113impl From<EngineApiError> for jsonrpsee_types::error::ErrorObject<'static> {
114    fn from(error: EngineApiError) -> Self {
115        match error {
116            // Per the Shanghai Engine API spec, FCU V2 must return -38003 when the wrong
117            // PayloadAttributes version is used.
118            // Spec: https://github.com/ethereum/execution-apis/blob/main/src/engine/shanghai.md
119            // Change: https://github.com/ethereum/execution-apis/pull/761
120            EngineApiError::EngineObjectValidationError(
121                EngineObjectValidationError::PayloadAttributes(
122                    VersionSpecificValidationError::WithdrawalsNotSupportedInV1 |
123                    VersionSpecificValidationError::NoWithdrawalsPostShanghai |
124                    VersionSpecificValidationError::HasWithdrawalsPreShanghai |
125                    VersionSpecificValidationError::BlockAccessListNotSupported |
126                    VersionSpecificValidationError::HasBlockAccessListPreAmsterdam |
127                    VersionSpecificValidationError::NoBlockAccessListPostAmsterdam |
128                    VersionSpecificValidationError::HasSlotNumberPreAmsterdam |
129                    VersionSpecificValidationError::NoSlotNumberPostAmsterdam |
130                    VersionSpecificValidationError::SlotNumberNotSupported,
131                ),
132            ) |
133            EngineApiError::UnexpectedRequestsHash => {
134                // Note: the data field is not required by the spec, but is also included by other
135                // clients
136                jsonrpsee_types::error::ErrorObject::owned(
137                    INVALID_PAYLOAD_ATTRIBUTES_ERROR,
138                    INVALID_PAYLOAD_ATTRIBUTES_ERROR_MSG,
139                    Some(ErrorData::new(error)),
140                )
141            }
142            EngineApiError::InvalidBodiesRange { .. } |
143            EngineApiError::NewPayload(BeaconOnNewPayloadError::InvalidParams(_)) |
144            EngineApiError::EngineObjectValidationError(
145                EngineObjectValidationError::Payload(_) |
146                EngineObjectValidationError::InvalidParams(_),
147            ) => jsonrpsee_types::error::ErrorObject::owned(
148                INVALID_PARAMS_CODE,
149                INVALID_PARAMS_MSG,
150                Some(ErrorData::new(error)),
151            ),
152            EngineApiError::UnknownPayload => jsonrpsee_types::error::ErrorObject::owned(
153                UNKNOWN_PAYLOAD_CODE,
154                error.to_string(),
155                None::<()>,
156            ),
157            EngineApiError::PayloadRequestTooLarge { .. } |
158            EngineApiError::BlobRequestTooLarge { .. } => {
159                jsonrpsee_types::error::ErrorObject::owned(
160                    REQUEST_TOO_LARGE_CODE,
161                    REQUEST_TOO_LARGE_MESSAGE,
162                    Some(ErrorData::new(error)),
163                )
164            }
165            EngineApiError::EngineObjectValidationError(
166                EngineObjectValidationError::PayloadAttributes(
167                    VersionSpecificValidationError::ParentBeaconBlockRootNotSupportedBeforeV3 |
168                    VersionSpecificValidationError::NoParentBeaconBlockRootPostCancun,
169                ),
170            ) => jsonrpsee_types::error::ErrorObject::owned(
171                INVALID_PAYLOAD_ATTRIBUTES_ERROR,
172                INVALID_PAYLOAD_ATTRIBUTES_ERROR_MSG,
173                Some(ErrorData::new(error)),
174            ),
175            EngineApiError::EngineObjectValidationError(
176                EngineObjectValidationError::UnsupportedFork,
177            ) => jsonrpsee_types::error::ErrorObject::owned(
178                UNSUPPORTED_FORK_CODE,
179                error.to_string(),
180                None::<()>,
181            ),
182            // Error responses from the consensus engine
183            EngineApiError::ForkChoiceUpdate(ref err) => match err {
184                BeaconForkChoiceUpdateError::ForkchoiceUpdateError(err) => match err {
185                    ForkchoiceUpdateError::UpdatedInvalidPayloadAttributes => {
186                        jsonrpsee_types::error::ErrorObject::owned(
187                            INVALID_PAYLOAD_ATTRIBUTES_ERROR,
188                            INVALID_PAYLOAD_ATTRIBUTES_ERROR_MSG,
189                            None::<()>,
190                        )
191                    }
192                    ForkchoiceUpdateError::InvalidState |
193                    ForkchoiceUpdateError::UnknownFinalBlock => {
194                        jsonrpsee_types::error::ErrorObject::owned(
195                            INVALID_FORK_CHOICE_STATE_ERROR,
196                            INVALID_FORK_CHOICE_STATE_ERROR_MSG,
197                            None::<()>,
198                        )
199                    }
200                    ForkchoiceUpdateError::TooDeepReorg => {
201                        jsonrpsee_types::error::ErrorObject::owned(
202                            TOO_DEEP_REORG_ERROR,
203                            TOO_DEEP_REORG_ERROR_MSG,
204                            None::<()>,
205                        )
206                    }
207                    // Map future alloy forkchoice errors as internal until handled.
208                    #[allow(unreachable_patterns, clippy::needless_return)]
209                    _ => {
210                        return jsonrpsee_types::error::ErrorObject::owned(
211                            INTERNAL_ERROR_CODE,
212                            SERVER_ERROR_MSG,
213                            Some(ErrorData::new(error)),
214                        );
215                    }
216                },
217                BeaconForkChoiceUpdateError::EngineUnavailable |
218                BeaconForkChoiceUpdateError::Internal(_) => {
219                    jsonrpsee_types::error::ErrorObject::owned(
220                        INTERNAL_ERROR_CODE,
221                        SERVER_ERROR_MSG,
222                        Some(ErrorData::new(error)),
223                    )
224                }
225            },
226            // Any other server error
227            EngineApiError::TerminalBlockHash { .. } |
228            EngineApiError::NewPayload(_) |
229            EngineApiError::Internal(_) |
230            EngineApiError::GetPayloadError(_) => jsonrpsee_types::error::ErrorObject::owned(
231                INTERNAL_ERROR_CODE,
232                SERVER_ERROR_MSG,
233                Some(ErrorData::new(error)),
234            ),
235            EngineApiError::Other(err) => err,
236        }
237    }
238}
239
240#[cfg(test)]
241mod tests {
242    use super::*;
243    use alloy_rpc_types_engine::ForkchoiceUpdateError;
244    #[track_caller]
245    fn ensure_engine_rpc_error(
246        code: i32,
247        message: &str,
248        err: impl Into<jsonrpsee_types::error::ErrorObject<'static>>,
249    ) {
250        let err = err.into();
251        assert_eq!(err.code(), code);
252        assert_eq!(err.message(), message);
253    }
254
255    // Tests that engine errors are formatted correctly according to the engine API spec
256    // <https://github.com/ethereum/execution-apis/blob/main/src/engine/common.md#errors>
257    #[test]
258    fn engine_error_rpc_error_test() {
259        ensure_engine_rpc_error(
260            UNSUPPORTED_FORK_CODE,
261            "Unsupported fork",
262            EngineApiError::EngineObjectValidationError(
263                EngineObjectValidationError::UnsupportedFork,
264            ),
265        );
266
267        ensure_engine_rpc_error(
268            REQUEST_TOO_LARGE_CODE,
269            "Too large request",
270            EngineApiError::PayloadRequestTooLarge { len: 0 },
271        );
272
273        ensure_engine_rpc_error(
274            -38002,
275            "Invalid forkchoice state",
276            EngineApiError::ForkChoiceUpdate(BeaconForkChoiceUpdateError::ForkchoiceUpdateError(
277                ForkchoiceUpdateError::InvalidState,
278            )),
279        );
280
281        // ForkchoiceUpdateError::UpdatedInvalidPayloadAttributes is for semantic validation
282        // errors that occur AFTER the structure check passes, so it returns -38003
283        ensure_engine_rpc_error(
284            -38003,
285            "Invalid payload attributes",
286            EngineApiError::ForkChoiceUpdate(BeaconForkChoiceUpdateError::ForkchoiceUpdateError(
287                ForkchoiceUpdateError::UpdatedInvalidPayloadAttributes,
288            )),
289        );
290
291        ensure_engine_rpc_error(
292            -38006,
293            "Too deep reorg",
294            EngineApiError::ForkChoiceUpdate(BeaconForkChoiceUpdateError::ForkchoiceUpdateError(
295                ForkchoiceUpdateError::TooDeepReorg,
296            )),
297        );
298
299        ensure_engine_rpc_error(
300            UNKNOWN_PAYLOAD_CODE,
301            "Unknown payload",
302            EngineApiError::UnknownPayload,
303        );
304
305        // Malformed payload params, e.g. undecodable block access list bytes, are rejected with
306        // an invalid params error instead of an `INVALID` payload status.
307        ensure_engine_rpc_error(
308            INVALID_PARAMS_CODE,
309            INVALID_PARAMS_MSG,
310            EngineApiError::NewPayload(BeaconOnNewPayloadError::InvalidParams(
311                "undecodable block access list".into(),
312            )),
313        );
314
315        // Per the Shanghai Engine API spec, FCU V2 must return -38003 when the wrong
316        // PayloadAttributes version is used.
317        // Spec: https://github.com/ethereum/execution-apis/blob/main/src/engine/shanghai.md
318        // Change: https://github.com/ethereum/execution-apis/pull/761
319        ensure_engine_rpc_error(
320            INVALID_PAYLOAD_ATTRIBUTES_ERROR,
321            INVALID_PAYLOAD_ATTRIBUTES_ERROR_MSG,
322            EngineApiError::EngineObjectValidationError(
323                EngineObjectValidationError::PayloadAttributes(
324                    VersionSpecificValidationError::NoWithdrawalsPostShanghai,
325                ),
326            ),
327        );
328
329        ensure_engine_rpc_error(
330            INVALID_PARAMS_CODE,
331            INVALID_PARAMS_MSG,
332            EngineApiError::EngineObjectValidationError(EngineObjectValidationError::Payload(
333                VersionSpecificValidationError::NoWithdrawalsPostShanghai,
334            )),
335        );
336
337        ensure_engine_rpc_error(
338            INVALID_PARAMS_CODE,
339            INVALID_PARAMS_MSG,
340            EngineApiError::EngineObjectValidationError(EngineObjectValidationError::Payload(
341                VersionSpecificValidationError::HasWithdrawalsPreShanghai,
342            )),
343        );
344
345        ensure_engine_rpc_error(
346            INVALID_PAYLOAD_ATTRIBUTES_ERROR,
347            INVALID_PAYLOAD_ATTRIBUTES_ERROR_MSG,
348            EngineApiError::EngineObjectValidationError(
349                EngineObjectValidationError::PayloadAttributes(
350                    VersionSpecificValidationError::HasWithdrawalsPreShanghai,
351                ),
352            ),
353        );
354
355        // Beacon root shape mismatches on PayloadAttributes are reported as -38003.
356        ensure_engine_rpc_error(
357            INVALID_PAYLOAD_ATTRIBUTES_ERROR,
358            INVALID_PAYLOAD_ATTRIBUTES_ERROR_MSG,
359            EngineApiError::EngineObjectValidationError(
360                EngineObjectValidationError::PayloadAttributes(
361                    VersionSpecificValidationError::ParentBeaconBlockRootNotSupportedBeforeV3,
362                ),
363            ),
364        );
365    }
366}