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
14pub type EngineApiResult<Ok> = Result<Ok, EngineApiError>;
16
17pub const UNSUPPORTED_FORK_CODE: i32 = -38005;
19pub const UNKNOWN_PAYLOAD_CODE: i32 = -38001;
21pub const REQUEST_TOO_LARGE_CODE: i32 = -38004;
23
24const REQUEST_TOO_LARGE_MESSAGE: &str = "Too large request";
26
27#[derive(Error, Debug)]
32pub enum EngineApiError {
33 #[error("Unknown payload")]
36 UnknownPayload,
37 #[error("requested count too large: {len}")]
39 PayloadRequestTooLarge {
40 len: u64,
42 },
43 #[error("requested blob count too large: {len}")]
45 BlobRequestTooLarge {
46 len: usize,
48 },
49 #[error("invalid start ({start}) or count ({count})")]
51 InvalidBodiesRange {
52 start: u64,
54 count: u64,
56 },
57 #[error(
59 "invalid transition terminal block hash: \
60 execution: {execution:?}, consensus: {consensus}"
61 )]
62 TerminalBlockHash {
63 execution: Option<B256>,
65 consensus: B256,
67 },
68 #[error(transparent)]
70 ForkChoiceUpdate(#[from] BeaconForkChoiceUpdateError),
71 #[error(transparent)]
73 NewPayload(#[from] BeaconOnNewPayloadError),
74 #[error(transparent)]
76 Internal(#[from] Box<dyn core::error::Error + Send + Sync>),
77 #[error(transparent)]
79 GetPayloadError(#[from] PayloadBuilderError),
80 #[error(transparent)]
82 EngineObjectValidationError(#[from] EngineObjectValidationError),
83 #[error("requests hash cannot be accepted by the API without `--engine.accept-execution-requests-hash` flag")]
85 UnexpectedRequestsHash,
86 #[error("{0}")]
88 Other(jsonrpsee_types::ErrorObject<'static>),
89}
90
91impl EngineApiError {
92 pub const fn other(err: jsonrpsee_types::ErrorObject<'static>) -> Self {
94 Self::Other(err)
95 }
96}
97
98#[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 EngineApiError::EngineObjectValidationError(
120 EngineObjectValidationError::PayloadAttributes(
121 VersionSpecificValidationError::WithdrawalsNotSupportedInV1 |
122 VersionSpecificValidationError::NoWithdrawalsPostShanghai |
123 VersionSpecificValidationError::HasWithdrawalsPreShanghai |
124 VersionSpecificValidationError::BlockAccessListNotSupported |
125 VersionSpecificValidationError::HasBlockAccessListPreAmsterdam |
126 VersionSpecificValidationError::NoBlockAccessListPostAmsterdam |
127 VersionSpecificValidationError::HasSlotNumberPreAmsterdam |
128 VersionSpecificValidationError::NoSlotNumberPostAmsterdam |
129 VersionSpecificValidationError::SlotNumberNotSupported,
130 ),
131 ) |
132 EngineApiError::UnexpectedRequestsHash => {
133 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 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 #[allow(unreachable_patterns, clippy::needless_return)]
200 _ => {
201 return jsonrpsee_types::error::ErrorObject::owned(
202 INTERNAL_ERROR_CODE,
203 SERVER_ERROR_MSG,
204 Some(ErrorData::new(error)),
205 );
206 }
207 },
208 BeaconForkChoiceUpdateError::EngineUnavailable |
209 BeaconForkChoiceUpdateError::Internal(_) => {
210 jsonrpsee_types::error::ErrorObject::owned(
211 INTERNAL_ERROR_CODE,
212 SERVER_ERROR_MSG,
213 Some(ErrorData::new(error)),
214 )
215 }
216 },
217 EngineApiError::TerminalBlockHash { .. } |
219 EngineApiError::NewPayload(_) |
220 EngineApiError::Internal(_) |
221 EngineApiError::GetPayloadError(_) => jsonrpsee_types::error::ErrorObject::owned(
222 INTERNAL_ERROR_CODE,
223 SERVER_ERROR_MSG,
224 Some(ErrorData::new(error)),
225 ),
226 EngineApiError::Other(err) => err,
227 }
228 }
229}
230
231#[cfg(test)]
232mod tests {
233 use super::*;
234 use alloy_rpc_types_engine::ForkchoiceUpdateError;
235 #[track_caller]
236 fn ensure_engine_rpc_error(
237 code: i32,
238 message: &str,
239 err: impl Into<jsonrpsee_types::error::ErrorObject<'static>>,
240 ) {
241 let err = err.into();
242 assert_eq!(err.code(), code);
243 assert_eq!(err.message(), message);
244 }
245
246 #[test]
249 fn engine_error_rpc_error_test() {
250 ensure_engine_rpc_error(
251 UNSUPPORTED_FORK_CODE,
252 "Unsupported fork",
253 EngineApiError::EngineObjectValidationError(
254 EngineObjectValidationError::UnsupportedFork,
255 ),
256 );
257
258 ensure_engine_rpc_error(
259 REQUEST_TOO_LARGE_CODE,
260 "Too large request",
261 EngineApiError::PayloadRequestTooLarge { len: 0 },
262 );
263
264 ensure_engine_rpc_error(
265 -38002,
266 "Invalid forkchoice state",
267 EngineApiError::ForkChoiceUpdate(BeaconForkChoiceUpdateError::ForkchoiceUpdateError(
268 ForkchoiceUpdateError::InvalidState,
269 )),
270 );
271
272 ensure_engine_rpc_error(
275 -38003,
276 "Invalid payload attributes",
277 EngineApiError::ForkChoiceUpdate(BeaconForkChoiceUpdateError::ForkchoiceUpdateError(
278 ForkchoiceUpdateError::UpdatedInvalidPayloadAttributes,
279 )),
280 );
281
282 ensure_engine_rpc_error(
283 UNKNOWN_PAYLOAD_CODE,
284 "Unknown payload",
285 EngineApiError::UnknownPayload,
286 );
287
288 ensure_engine_rpc_error(
293 INVALID_PAYLOAD_ATTRIBUTES_ERROR,
294 INVALID_PAYLOAD_ATTRIBUTES_ERROR_MSG,
295 EngineApiError::EngineObjectValidationError(
296 EngineObjectValidationError::PayloadAttributes(
297 VersionSpecificValidationError::NoWithdrawalsPostShanghai,
298 ),
299 ),
300 );
301
302 ensure_engine_rpc_error(
303 INVALID_PARAMS_CODE,
304 INVALID_PARAMS_MSG,
305 EngineApiError::EngineObjectValidationError(EngineObjectValidationError::Payload(
306 VersionSpecificValidationError::NoWithdrawalsPostShanghai,
307 )),
308 );
309
310 ensure_engine_rpc_error(
311 INVALID_PARAMS_CODE,
312 INVALID_PARAMS_MSG,
313 EngineApiError::EngineObjectValidationError(EngineObjectValidationError::Payload(
314 VersionSpecificValidationError::HasWithdrawalsPreShanghai,
315 )),
316 );
317
318 ensure_engine_rpc_error(
319 INVALID_PAYLOAD_ATTRIBUTES_ERROR,
320 INVALID_PAYLOAD_ATTRIBUTES_ERROR_MSG,
321 EngineApiError::EngineObjectValidationError(
322 EngineObjectValidationError::PayloadAttributes(
323 VersionSpecificValidationError::HasWithdrawalsPreShanghai,
324 ),
325 ),
326 );
327
328 ensure_engine_rpc_error(
330 INVALID_PAYLOAD_ATTRIBUTES_ERROR,
331 INVALID_PAYLOAD_ATTRIBUTES_ERROR_MSG,
332 EngineApiError::EngineObjectValidationError(
333 EngineObjectValidationError::PayloadAttributes(
334 VersionSpecificValidationError::ParentBeaconBlockRootNotSupportedBeforeV3,
335 ),
336 ),
337 );
338 }
339}