reth_rpc_engine_api/
error.rs

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