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::PayloadJob(
221 PayloadBuilderError::InvalidAttributes(_),
222 ) => jsonrpsee_types::error::ErrorObject::owned(
223 INVALID_PAYLOAD_ATTRIBUTES_ERROR,
224 INVALID_PAYLOAD_ATTRIBUTES_ERROR_MSG,
225 Some(ErrorData::new(error)),
226 ),
227 BeaconForkChoiceUpdateError::EngineUnavailable |
228 BeaconForkChoiceUpdateError::PayloadJob(_) |
229 BeaconForkChoiceUpdateError::Internal(_) => {
230 jsonrpsee_types::error::ErrorObject::owned(
231 INTERNAL_ERROR_CODE,
232 SERVER_ERROR_MSG,
233 Some(ErrorData::new(error)),
234 )
235 }
236 },
237 EngineApiError::TerminalBlockHash { .. } |
239 EngineApiError::NewPayload(_) |
240 EngineApiError::Internal(_) |
241 EngineApiError::GetPayloadError(_) => jsonrpsee_types::error::ErrorObject::owned(
242 INTERNAL_ERROR_CODE,
243 SERVER_ERROR_MSG,
244 Some(ErrorData::new(error)),
245 ),
246 EngineApiError::Other(err) => err,
247 }
248 }
249}
250
251#[cfg(test)]
252mod tests {
253 use super::*;
254 use alloy_rpc_types_engine::ForkchoiceUpdateError;
255 use reth_payload_primitives::InvalidPayloadAttributesError;
256 #[track_caller]
257 fn ensure_engine_rpc_error(
258 code: i32,
259 message: &str,
260 err: impl Into<jsonrpsee_types::error::ErrorObject<'static>>,
261 ) {
262 let err = err.into();
263 assert_eq!(err.code(), code);
264 assert_eq!(err.message(), message);
265 }
266
267 #[test]
270 fn engine_error_rpc_error_test() {
271 ensure_engine_rpc_error(
272 UNSUPPORTED_FORK_CODE,
273 "Unsupported fork",
274 EngineApiError::EngineObjectValidationError(
275 EngineObjectValidationError::UnsupportedFork,
276 ),
277 );
278
279 ensure_engine_rpc_error(
280 REQUEST_TOO_LARGE_CODE,
281 "Too large request",
282 EngineApiError::PayloadRequestTooLarge { len: 0 },
283 );
284
285 ensure_engine_rpc_error(
286 -38002,
287 "Invalid forkchoice state",
288 EngineApiError::ForkChoiceUpdate(BeaconForkChoiceUpdateError::ForkchoiceUpdateError(
289 ForkchoiceUpdateError::InvalidState,
290 )),
291 );
292
293 ensure_engine_rpc_error(
296 -38003,
297 "Invalid payload attributes",
298 EngineApiError::ForkChoiceUpdate(BeaconForkChoiceUpdateError::ForkchoiceUpdateError(
299 ForkchoiceUpdateError::UpdatedInvalidPayloadAttributes,
300 )),
301 );
302
303 ensure_engine_rpc_error(
304 -38006,
305 "Too deep reorg",
306 EngineApiError::ForkChoiceUpdate(BeaconForkChoiceUpdateError::ForkchoiceUpdateError(
307 ForkchoiceUpdateError::TooDeepReorg,
308 )),
309 );
310
311 ensure_engine_rpc_error(
312 UNKNOWN_PAYLOAD_CODE,
313 "Unknown payload",
314 EngineApiError::UnknownPayload,
315 );
316
317 ensure_engine_rpc_error(
320 INVALID_PARAMS_CODE,
321 INVALID_PARAMS_MSG,
322 EngineApiError::NewPayload(BeaconOnNewPayloadError::InvalidParams(
323 "malformed payload parameters".into(),
324 )),
325 );
326
327 ensure_engine_rpc_error(
332 INVALID_PAYLOAD_ATTRIBUTES_ERROR,
333 INVALID_PAYLOAD_ATTRIBUTES_ERROR_MSG,
334 EngineApiError::EngineObjectValidationError(
335 EngineObjectValidationError::PayloadAttributes(
336 VersionSpecificValidationError::NoWithdrawalsPostShanghai,
337 ),
338 ),
339 );
340
341 ensure_engine_rpc_error(
342 INVALID_PARAMS_CODE,
343 INVALID_PARAMS_MSG,
344 EngineApiError::EngineObjectValidationError(EngineObjectValidationError::Payload(
345 VersionSpecificValidationError::NoWithdrawalsPostShanghai,
346 )),
347 );
348
349 ensure_engine_rpc_error(
350 INVALID_PARAMS_CODE,
351 INVALID_PARAMS_MSG,
352 EngineApiError::EngineObjectValidationError(EngineObjectValidationError::Payload(
353 VersionSpecificValidationError::HasWithdrawalsPreShanghai,
354 )),
355 );
356
357 ensure_engine_rpc_error(
358 INVALID_PAYLOAD_ATTRIBUTES_ERROR,
359 INVALID_PAYLOAD_ATTRIBUTES_ERROR_MSG,
360 EngineApiError::EngineObjectValidationError(
361 EngineObjectValidationError::PayloadAttributes(
362 VersionSpecificValidationError::HasWithdrawalsPreShanghai,
363 ),
364 ),
365 );
366
367 ensure_engine_rpc_error(
369 INVALID_PAYLOAD_ATTRIBUTES_ERROR,
370 INVALID_PAYLOAD_ATTRIBUTES_ERROR_MSG,
371 EngineApiError::EngineObjectValidationError(
372 EngineObjectValidationError::PayloadAttributes(
373 VersionSpecificValidationError::ParentBeaconBlockRootNotSupportedBeforeV3,
374 ),
375 ),
376 );
377 }
378
379 fn payload_job_error(err: PayloadBuilderError) -> jsonrpsee_types::error::ErrorObject<'static> {
380 EngineApiError::ForkChoiceUpdate(BeaconForkChoiceUpdateError::PayloadJob(err)).into()
381 }
382
383 #[test]
384 fn payload_job_rejecting_the_attributes_is_invalid_payload_attributes() {
385 let err = payload_job_error(PayloadBuilderError::invalid_attributes(
386 InvalidPayloadAttributesError::InvalidTimestamp,
387 ));
388
389 assert_eq!(err.code(), INVALID_PAYLOAD_ATTRIBUTES_ERROR);
390 assert_eq!(err.message(), INVALID_PAYLOAD_ATTRIBUTES_ERROR_MSG);
391 assert_eq!(
392 err.data().unwrap().get(),
393 r#"{"err":"failed to start payload job: invalid payload attributes: invalid timestamp"}"#
394 );
395 }
396
397 #[test]
398 fn payload_job_failing_on_this_node_is_an_internal_error() {
399 let node_local = [
400 PayloadBuilderError::MissingParentHeader(B256::ZERO),
401 PayloadBuilderError::ChannelClosed,
402 PayloadBuilderError::other(std::io::Error::other("other")),
403 ];
404
405 for payload_job in node_local {
406 let message = payload_job.to_string();
407 let err = payload_job_error(payload_job);
408 assert_eq!(err.code(), INTERNAL_ERROR_CODE, "{message}");
409 assert_eq!(err.message(), SERVER_ERROR_MSG, "{message}");
410 assert_eq!(
411 err.data().unwrap().get(),
412 format!(r#"{{"err":"failed to start payload job: {message}"}}"#)
413 );
414 }
415 }
416}