use alloy_primitives::{B256, U256};
use jsonrpsee_types::error::{
INTERNAL_ERROR_CODE, INVALID_PARAMS_CODE, INVALID_PARAMS_MSG, SERVER_ERROR_MSG,
};
use reth_beacon_consensus::BeaconForkChoiceUpdateError;
use reth_engine_primitives::BeaconOnNewPayloadError;
use reth_payload_builder_primitives::PayloadBuilderError;
use reth_payload_primitives::EngineObjectValidationError;
use thiserror::Error;
pub type EngineApiResult<Ok> = Result<Ok, EngineApiError>;
pub const INVALID_PAYLOAD_ATTRIBUTES: i32 = -38003;
pub const UNSUPPORTED_FORK_CODE: i32 = -38005;
pub const UNKNOWN_PAYLOAD_CODE: i32 = -38001;
pub const REQUEST_TOO_LARGE_CODE: i32 = -38004;
const REQUEST_TOO_LARGE_MESSAGE: &str = "Too large request";
const INVALID_PAYLOAD_ATTRIBUTES_MSG: &str = "Invalid payload attributes";
#[derive(Error, Debug)]
pub enum EngineApiError {
#[error("Unknown payload")]
UnknownPayload,
#[error("requested count too large: {len}")]
PayloadRequestTooLarge {
len: u64,
},
#[error("requested blob count too large: {len}")]
BlobRequestTooLarge {
len: usize,
},
#[error("invalid start ({start}) or count ({count})")]
InvalidBodiesRange {
start: u64,
count: u64,
},
#[error(
"invalid transition terminal total difficulty: \
execution: {execution}, consensus: {consensus}"
)]
TerminalTD {
execution: U256,
consensus: U256,
},
#[error(
"invalid transition terminal block hash: \
execution: {execution:?}, consensus: {consensus}"
)]
TerminalBlockHash {
execution: Option<B256>,
consensus: B256,
},
#[error(transparent)]
ForkChoiceUpdate(#[from] BeaconForkChoiceUpdateError),
#[error(transparent)]
NewPayload(#[from] BeaconOnNewPayloadError),
#[error(transparent)]
Internal(#[from] Box<dyn core::error::Error + Send + Sync>),
#[error(transparent)]
GetPayloadError(#[from] PayloadBuilderError),
#[error(transparent)]
EngineObjectValidationError(#[from] EngineObjectValidationError),
#[error("{0}")]
Other(jsonrpsee_types::ErrorObject<'static>),
}
impl EngineApiError {
pub const fn other(err: jsonrpsee_types::ErrorObject<'static>) -> Self {
Self::Other(err)
}
}
#[derive(serde::Serialize)]
struct ErrorData {
err: String,
}
impl ErrorData {
#[inline]
fn new(err: impl std::fmt::Display) -> Self {
Self { err: err.to_string() }
}
}
impl From<EngineApiError> for jsonrpsee_types::error::ErrorObject<'static> {
fn from(error: EngineApiError) -> Self {
match error {
EngineApiError::InvalidBodiesRange { .. } |
EngineApiError::EngineObjectValidationError(
EngineObjectValidationError::Payload(_) |
EngineObjectValidationError::InvalidParams(_),
) => {
jsonrpsee_types::error::ErrorObject::owned(
INVALID_PARAMS_CODE,
INVALID_PARAMS_MSG,
Some(ErrorData::new(error)),
)
}
EngineApiError::EngineObjectValidationError(
EngineObjectValidationError::PayloadAttributes(_),
) => {
jsonrpsee_types::error::ErrorObject::owned(
INVALID_PAYLOAD_ATTRIBUTES,
INVALID_PAYLOAD_ATTRIBUTES_MSG,
Some(ErrorData::new(error)),
)
}
EngineApiError::UnknownPayload => jsonrpsee_types::error::ErrorObject::owned(
UNKNOWN_PAYLOAD_CODE,
error.to_string(),
None::<()>,
),
EngineApiError::PayloadRequestTooLarge { .. } |
EngineApiError::BlobRequestTooLarge { .. } => {
jsonrpsee_types::error::ErrorObject::owned(
REQUEST_TOO_LARGE_CODE,
REQUEST_TOO_LARGE_MESSAGE,
Some(ErrorData::new(error)),
)
}
EngineApiError::EngineObjectValidationError(
EngineObjectValidationError::UnsupportedFork,
) => jsonrpsee_types::error::ErrorObject::owned(
UNSUPPORTED_FORK_CODE,
error.to_string(),
None::<()>,
),
EngineApiError::ForkChoiceUpdate(ref err) => match err {
BeaconForkChoiceUpdateError::ForkchoiceUpdateError(err) => (*err).into(),
BeaconForkChoiceUpdateError::EngineUnavailable |
BeaconForkChoiceUpdateError::Internal(_) => {
jsonrpsee_types::error::ErrorObject::owned(
INTERNAL_ERROR_CODE,
SERVER_ERROR_MSG,
Some(ErrorData::new(error)),
)
}
},
EngineApiError::TerminalTD { .. } |
EngineApiError::TerminalBlockHash { .. } |
EngineApiError::NewPayload(_) |
EngineApiError::Internal(_) |
EngineApiError::GetPayloadError(_) => jsonrpsee_types::error::ErrorObject::owned(
INTERNAL_ERROR_CODE,
SERVER_ERROR_MSG,
Some(ErrorData::new(error)),
),
EngineApiError::Other(err) => err,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloy_rpc_types_engine::ForkchoiceUpdateError;
#[track_caller]
fn ensure_engine_rpc_error(
code: i32,
message: &str,
err: impl Into<jsonrpsee_types::error::ErrorObject<'static>>,
) {
let err = err.into();
assert_eq!(err.code(), code);
assert_eq!(err.message(), message);
}
#[test]
fn engine_error_rpc_error_test() {
ensure_engine_rpc_error(
UNSUPPORTED_FORK_CODE,
"Unsupported fork",
EngineApiError::EngineObjectValidationError(
EngineObjectValidationError::UnsupportedFork,
),
);
ensure_engine_rpc_error(
REQUEST_TOO_LARGE_CODE,
"Too large request",
EngineApiError::PayloadRequestTooLarge { len: 0 },
);
ensure_engine_rpc_error(
-38002,
"Invalid forkchoice state",
EngineApiError::ForkChoiceUpdate(BeaconForkChoiceUpdateError::ForkchoiceUpdateError(
ForkchoiceUpdateError::InvalidState,
)),
);
ensure_engine_rpc_error(
-38003,
"Invalid payload attributes",
EngineApiError::ForkChoiceUpdate(BeaconForkChoiceUpdateError::ForkchoiceUpdateError(
ForkchoiceUpdateError::UpdatedInvalidPayloadAttributes,
)),
);
ensure_engine_rpc_error(
UNKNOWN_PAYLOAD_CODE,
"Unknown payload",
EngineApiError::UnknownPayload,
);
}
}