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