Skip to main content

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        Self::internal(error)
36    }
37}
38
39impl From<InsertBlockProcessingError> for BeaconOnNewPayloadError {
40    fn from(error: InsertBlockProcessingError) -> Self {
41        match error {
42            InsertBlockProcessingError::MalformedInput(error) => Self::InvalidParams(error),
43            InsertBlockProcessingError::Fatal(error) => Self::internal(error),
44        }
45    }
46}
47
48/// Represents error cases for an applied forkchoice update.
49///
50/// This represents all possible error cases, that must be returned as JSON RPC errors back to the
51/// beacon node.
52#[derive(Debug, thiserror::Error)]
53pub enum BeaconForkChoiceUpdateError {
54    /// Thrown when a forkchoice update resulted in an error.
55    #[error("forkchoice update error: {0}")]
56    ForkchoiceUpdateError(#[from] ForkchoiceUpdateError),
57    /// Thrown when the engine task is unavailable/stopped.
58    #[error("beacon consensus engine task stopped")]
59    EngineUnavailable,
60    /// An internal error occurred, not necessarily related to the update.
61    #[error(transparent)]
62    Internal(Box<dyn core::error::Error + Send + Sync>),
63}
64
65impl BeaconForkChoiceUpdateError {
66    /// Create a new internal error.
67    pub fn internal<E: core::error::Error + Send + Sync + 'static>(e: E) -> Self {
68        Self::Internal(Box::new(e))
69    }
70}
71
72/// All error variants possible when inserting or validating a block.
73#[derive(Debug, thiserror::Error)]
74pub enum InsertBlockErrorKind {
75    /// Block violated consensus rules.
76    #[error(transparent)]
77    Consensus(#[from] ConsensusError),
78    /// Supplemental block access list bytes could not be decoded.
79    #[error(transparent)]
80    BlockAccessListDecode(#[from] BlockAccessListDecodeError),
81    /// Block execution failed.
82    #[error(transparent)]
83    Execution(#[from] BlockExecutionError),
84    /// Provider error.
85    #[error(transparent)]
86    Provider(#[from] ProviderError),
87    /// Other errors.
88    #[error(transparent)]
89    Other(#[from] Box<dyn core::error::Error + Send + Sync + 'static>),
90}
91
92impl InsertBlockErrorKind {
93    /// Returns whether the error must be reported as an invalid payload.
94    pub const fn is_validation_error(&self) -> bool {
95        matches!(
96            self,
97            Self::Consensus(_) |
98                Self::BlockAccessListDecode(_) |
99                Self::Execution(BlockExecutionError::Validation(_))
100        )
101    }
102
103    /// Returns an [`InsertBlockValidationError`] if the failure must be reported as an invalid
104    /// payload, or an [`InsertBlockProcessingError`] if block processing itself failed.
105    ///
106    /// This distinction controls whether the payload may be returned as `INVALID`. Errors
107    /// classified as invalid payloads by the Engine API are validation errors; malformed request
108    /// parameters and internal failures remain processing errors instead.
109    pub fn ensure_validation_error(
110        self,
111    ) -> Result<InsertBlockValidationError, InsertBlockProcessingError> {
112        match self {
113            Self::BlockAccessListDecode(error) => {
114                Ok(InsertBlockValidationError::BlockAccessListDecode(error))
115            }
116            Self::Consensus(err) => Ok(InsertBlockValidationError::Consensus(err)),
117            Self::Execution(err) => match err {
118                BlockExecutionError::Validation(err) => {
119                    Ok(InsertBlockValidationError::Validation(err))
120                }
121                BlockExecutionError::Internal(error) => Err(InsertBlockProcessingError::Fatal(
122                    InsertBlockFatalError::BlockExecutionError(error),
123                )),
124            },
125            Self::Provider(err) => {
126                Err(InsertBlockProcessingError::Fatal(InsertBlockFatalError::Provider(err)))
127            }
128            Self::Other(err) => Err(InsertBlockProcessingError::Fatal(
129                InternalBlockExecutionError::Other(err).into(),
130            )),
131        }
132    }
133}
134
135/// Error decoding supplemental block access list bytes.
136#[derive(Debug, thiserror::Error)]
137#[error("failed to decode block access list: {0}")]
138pub struct BlockAccessListDecodeError(#[source] Box<dyn core::error::Error + Send + Sync>);
139
140impl BlockAccessListDecodeError {
141    /// Creates a new block access list decode error.
142    pub fn new<E>(error: E) -> Self
143    where
144        E: core::error::Error + Send + Sync + 'static,
145    {
146        Self(Box::new(error))
147    }
148}
149
150/// An error that occurs while processing a block but does not invalidate the block itself.
151///
152/// These errors must not produce an `INVALID`
153/// [`PayloadStatus`](alloy_rpc_types_engine::PayloadStatus) or cause the block hash to be cached as
154/// invalid. Malformed supplemental input remains distinct from fatal processing failures so
155/// `newPayload` can reject it as invalid params, while internal ingestion paths can discard it
156/// without terminating the engine task.
157#[derive(Debug, thiserror::Error)]
158pub enum InsertBlockProcessingError {
159    /// Supplemental input is malformed, but the block itself is not known to be invalid.
160    #[error(transparent)]
161    MalformedInput(Box<dyn core::error::Error + Send + Sync>),
162    /// Block processing cannot continue because of an internal failure.
163    #[error(transparent)]
164    Fatal(#[from] InsertBlockFatalError),
165}
166
167/// Internal errors that prevent block processing from continuing.
168#[derive(Debug, thiserror::Error)]
169pub enum InsertBlockFatalError {
170    /// A provider error.
171    #[error(transparent)]
172    Provider(#[from] ProviderError),
173    /// An internal or fatal block execution error.
174    #[error(transparent)]
175    BlockExecutionError(#[from] InternalBlockExecutionError),
176}
177
178/// Error variants that are caused by invalid blocks.
179#[derive(Debug, thiserror::Error)]
180pub enum InsertBlockValidationError {
181    /// Block violated consensus rules.
182    #[error(transparent)]
183    Consensus(#[from] ConsensusError),
184    /// Block access list bytes could not be decoded.
185    #[error(transparent)]
186    BlockAccessListDecode(#[from] BlockAccessListDecodeError),
187    /// Validation error, transparently wrapping [`BlockValidationError`].
188    #[error(transparent)]
189    Validation(#[from] BlockValidationError),
190}
191
192#[cfg(test)]
193mod tests {
194    use super::*;
195
196    #[test]
197    fn ensure_insert_block_validation_error() {
198        let err = InsertBlockErrorKind::BlockAccessListDecode(BlockAccessListDecodeError::new(
199            alloy_rlp::Error::UnexpectedString,
200        ));
201        assert!(err.is_validation_error());
202        assert!(matches!(
203            err.ensure_validation_error(),
204            Ok(InsertBlockValidationError::BlockAccessListDecode(_))
205        ));
206
207        assert!(matches!(
208            InsertBlockErrorKind::Consensus(ConsensusError::BlockAccessListHashMissing)
209                .ensure_validation_error(),
210            Ok(InsertBlockValidationError::Consensus(_))
211        ));
212
213        assert!(matches!(
214            InsertBlockErrorKind::Provider(ProviderError::BestBlockNotFound)
215                .ensure_validation_error(),
216            Err(InsertBlockProcessingError::Fatal(InsertBlockFatalError::Provider(_)))
217        ));
218    }
219}