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