reth_engine_primitives/error.rs
1use alloc::boxed::Box;
2use alloy_rpc_types_engine::ForkchoiceUpdateError;
3use reth_errors::{BlockExecutionError, BlockValidationError, ConsensusError, ProviderError};
4use reth_execution_errors::InternalBlockExecutionError;
5
6/// Represents all error cases when handling a new payload.
7///
8/// This represents all possible error cases that must be returned as JSON RPC errors back to the
9/// beacon node.
10#[derive(Debug, thiserror::Error)]
11pub enum BeaconOnNewPayloadError {
12 /// Thrown when the engine task is unavailable/stopped.
13 #[error("beacon consensus engine task stopped")]
14 EngineUnavailable,
15 /// Thrown when the payload params are malformed, e.g. a field's raw bytes cannot be decoded.
16 ///
17 /// Per the engine API spec this must be rejected with an invalid params error instead of an
18 /// `INVALID` payload status.
19 #[error(transparent)]
20 InvalidParams(Box<dyn core::error::Error + Send + Sync>),
21 /// An internal error occurred, not necessarily related to the payload.
22 #[error(transparent)]
23 Internal(Box<dyn core::error::Error + Send + Sync>),
24}
25
26impl BeaconOnNewPayloadError {
27 /// Create a new internal error.
28 pub fn internal<E: core::error::Error + Send + Sync + 'static>(e: E) -> Self {
29 Self::Internal(Box::new(e))
30 }
31}
32
33impl From<InsertBlockFatalError> for BeaconOnNewPayloadError {
34 fn from(error: InsertBlockFatalError) -> Self {
35 match error {
36 InsertBlockFatalError::InvalidParams(err) => Self::InvalidParams(err),
37 error => Self::internal(error),
38 }
39 }
40}
41
42/// Represents error cases for an applied forkchoice update.
43///
44/// This represents all possible error cases, that must be returned as JSON RPC errors back to the
45/// beacon node.
46#[derive(Debug, thiserror::Error)]
47pub enum BeaconForkChoiceUpdateError {
48 /// Thrown when a forkchoice update resulted in an error.
49 #[error("forkchoice update error: {0}")]
50 ForkchoiceUpdateError(#[from] ForkchoiceUpdateError),
51 /// Thrown when the engine task is unavailable/stopped.
52 #[error("beacon consensus engine task stopped")]
53 EngineUnavailable,
54 /// An internal error occurred, not necessarily related to the update.
55 #[error(transparent)]
56 Internal(Box<dyn core::error::Error + Send + Sync>),
57}
58
59impl BeaconForkChoiceUpdateError {
60 /// Create a new internal error.
61 pub fn internal<E: core::error::Error + Send + Sync + 'static>(e: E) -> Self {
62 Self::Internal(Box::new(e))
63 }
64}
65
66/// All error variants possible when inserting or validating a block.
67#[derive(Debug, thiserror::Error)]
68pub enum InsertBlockErrorKind {
69 /// Block violated consensus rules.
70 #[error(transparent)]
71 Consensus(#[from] ConsensusError),
72 /// Block execution failed.
73 #[error(transparent)]
74 Execution(#[from] BlockExecutionError),
75 /// Provider error.
76 #[error(transparent)]
77 Provider(#[from] ProviderError),
78 /// Other errors.
79 #[error(transparent)]
80 Other(#[from] Box<dyn core::error::Error + Send + Sync + 'static>),
81}
82
83impl InsertBlockErrorKind {
84 /// Returns whether the error was caused by an invalid block.
85 pub const fn is_validation_error(&self) -> bool {
86 matches!(self, Self::Consensus(_) | Self::Execution(BlockExecutionError::Validation(_)))
87 }
88
89 /// Returns an [`InsertBlockValidationError`] if the error is caused by an invalid block.
90 ///
91 /// Returns an [`InsertBlockFatalError`] if the failure is not attributable to the block
92 /// itself, either an internal error or malformed request params.
93 ///
94 /// This split decides how `newPayload` responds: validation errors become an `INVALID`
95 /// payload status and mark the block hash as invalid, while fatal errors are returned as
96 /// actual errors to the caller. This distinction is required because responding `INVALID`
97 /// has consensus meaning (the block is rejected and its hash cached as invalid), which must
98 /// not happen for failures the block is not responsible for.
99 pub fn ensure_validation_error(
100 self,
101 ) -> Result<InsertBlockValidationError, InsertBlockFatalError> {
102 match self {
103 // Undecodable block access list bytes are malformed request params, not an invalid
104 // block, and must be rejected with an invalid params error instead of an `INVALID`
105 // payload status.
106 Self::Consensus(ConsensusError::BlockAccessListDecode(err)) => {
107 Err(InsertBlockFatalError::InvalidParams(Box::new(err)))
108 }
109 Self::Consensus(err) => Ok(InsertBlockValidationError::Consensus(err)),
110 Self::Execution(err) => match err {
111 BlockExecutionError::Validation(err) => {
112 Ok(InsertBlockValidationError::Validation(err))
113 }
114 BlockExecutionError::Internal(error) => {
115 Err(InsertBlockFatalError::BlockExecutionError(error))
116 }
117 },
118 Self::Provider(err) => Err(InsertBlockFatalError::Provider(err)),
119 Self::Other(err) => Err(InternalBlockExecutionError::Other(err).into()),
120 }
121 }
122}
123
124/// Error variants that are not caused by invalid blocks.
125///
126/// "Fatal" means block processing failed for a reason other than the block itself being invalid.
127/// This includes errors caused by additional payload data that is not part of the block, such as
128/// undecodable block access list bytes: their malformation says nothing about the validity of
129/// the block and is therefore treated differently with respect to the `PayloadStatus`.
130///
131/// These failures must not be answered with an `INVALID` payload status or mark the block hash
132/// as invalid. Instead they are propagated as actual errors: for `newPayload` they convert into
133/// [`BeaconOnNewPayloadError`] and are returned as a JSON-RPC error object instead of a
134/// `PayloadStatus` result, [`Self::InvalidParams`] as invalid params (`-32602`) and all other
135/// variants as internal error (`-32603`).
136#[derive(Debug, thiserror::Error)]
137pub enum InsertBlockFatalError {
138 /// A provider error.
139 #[error(transparent)]
140 Provider(#[from] ProviderError),
141 /// An internal or fatal block execution error.
142 #[error(transparent)]
143 BlockExecutionError(#[from] InternalBlockExecutionError),
144 /// The payload params are malformed, e.g. undecodable block access list bytes, and the
145 /// request must be rejected with an invalid params error.
146 #[error(transparent)]
147 InvalidParams(Box<dyn core::error::Error + Send + Sync>),
148}
149
150/// Error variants that are caused by invalid blocks.
151#[derive(Debug, thiserror::Error)]
152pub enum InsertBlockValidationError {
153 /// Block violated consensus rules.
154 #[error(transparent)]
155 Consensus(#[from] ConsensusError),
156 /// Validation error, transparently wrapping [`BlockValidationError`].
157 #[error(transparent)]
158 Validation(#[from] BlockValidationError),
159}
160
161#[cfg(test)]
162mod tests {
163 use super::*;
164
165 // Undecodable block access list bytes are malformed request params and must not be treated
166 // as a block validation error.
167 #[test]
168 fn bal_decode_error_is_invalid_params() {
169 let err = InsertBlockErrorKind::Consensus(ConsensusError::BlockAccessListDecode(
170 alloy_rlp::Error::UnexpectedString,
171 ));
172 assert!(matches!(
173 err.ensure_validation_error(),
174 Err(InsertBlockFatalError::InvalidParams(_))
175 ));
176 assert!(matches!(
177 BeaconOnNewPayloadError::from(InsertBlockFatalError::InvalidParams(Box::new(
178 alloy_rlp::Error::UnexpectedString
179 ))),
180 BeaconOnNewPayloadError::InvalidParams(_)
181 ));
182 }
183}