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
15pub type EngineApiResult<Ok> = Result<Ok, EngineApiError>;
17
18pub const UNSUPPORTED_FORK_CODE: i32 = -38005;
20pub const UNKNOWN_PAYLOAD_CODE: i32 = -38001;
22pub const REQUEST_TOO_LARGE_CODE: i32 = -38004;
24
25const REQUEST_TOO_LARGE_MESSAGE: &str = "Too large request";
27
28#[derive(Error, Debug)]
33pub enum EngineApiError {
34 #[error("Unknown payload")]
37 UnknownPayload,
38 #[error("requested count too large: {len}")]
40 PayloadRequestTooLarge {
41 len: u64,
43 },
44 #[error("requested blob count too large: {len}")]
46 BlobRequestTooLarge {
47 len: usize,
49 },
50 #[error("invalid start ({start}) or count ({count})")]
52 InvalidBodiesRange {
53 start: u64,
55 count: u64,
57 },
58 #[error(
60 "invalid transition terminal block hash: \
61 execution: {execution:?}, consensus: {consensus}"
62 )]
63 TerminalBlockHash {
64 execution: Option<B256>,
66 consensus: B256,
68 },
69 #[error(transparent)]
71 ForkChoiceUpdate(#[from] BeaconForkChoiceUpdateError),
72 #[error(transparent)]
74 NewPayload(#[from] BeaconOnNewPayloadError),
75 #[error(transparent)]
77 Internal(#[from] Box<dyn core::error::Error + Send + Sync>),
78 #[error(transparent)]
80 GetPayloadError(#[from] PayloadBuilderError),
81 #[error(transparent)]
83 EngineObjectValidationError(#[from] EngineObjectValidationError),
84 #[error("requests hash cannot be accepted by the API without `--engine.accept-execution-requests-hash` flag")]
86 UnexpectedRequestsHash,
87 #[error("{0}")]
89 Other(jsonrpsee_types::ErrorObject<'static>),
90}
91
92impl EngineApiError {
93 pub const fn other(err: jsonrpsee_types::ErrorObject<'static>) -> Self {
95 Self::Other(err)
96 }
97}
98
99#[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 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 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 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 #[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 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 #[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 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 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 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 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}