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        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}