diff --git a/crates/consensus/consensus/src/lib.rs b/crates/consensus/consensus/src/lib.rs index 63e74dd83ec..5bb8061fbcd 100644 --- a/crates/consensus/consensus/src/lib.rs +++ b/crates/consensus/consensus/src/lib.rs @@ -30,10 +30,16 @@ extern crate alloc; -use alloc::{boxed::Box, fmt::Debug, string::String, sync::Arc, vec::Vec}; +use alloc::{ + boxed::Box, + fmt::Debug, + string::{String, ToString}, + sync::Arc, + vec::Vec, +}; use alloy_consensus::Header; use alloy_primitives::{BlockHash, BlockNumber, Bloom, B256}; -use core::error::Error; +use core::{error::Error, fmt::Display}; /// Pre-computed receipt root and logs bloom. /// @@ -456,19 +462,49 @@ pub enum ConsensusError { /// EIP-7825: Transaction gas limit exceeds maximum allowed #[error(transparent)] TransactionGasLimitTooHigh(Box), - /// Other, likely an injected L2 error. - #[error("{0}")] - Other(String), - /// Other unspecified error. + /// Any additional consensus error, for example L2-specific errors. #[error(transparent)] - Custom(#[from] Arc), + Other(#[from] Arc), } impl ConsensusError { + /// Returns a new [`ConsensusError::Other`] instance with the given error. + pub fn other(error: E) -> Self + where + E: Error + Send + Sync + 'static, + { + Self::Other(Arc::new(error)) + } + + /// Returns a new [`ConsensusError::Other`] instance with the given message. + pub fn msg(msg: impl Display) -> Self { + Self::other(MessageError(msg.to_string())) + } + /// Returns `true` if the error is a state root error. pub const fn is_state_root_error(&self) -> bool { matches!(self, Self::BodyStateRootDiff(_)) } + + /// Returns the arbitrary error if it is [`ConsensusError::Other`]. + pub fn as_other(&self) -> Option<&(dyn Error + Send + Sync + 'static)> { + match self { + Self::Other(err) => Some(err.as_ref()), + _ => None, + } + } + + /// Returns a reference to the [`ConsensusError::Other`] value if it is of that type. + /// Returns `None` otherwise. + pub fn downcast_other_ref(&self) -> Option<&T> { + let other = self.as_other()?; + other.downcast_ref() + } + + /// Returns `true` if this type is a [`ConsensusError::Other`] of that error type. + pub fn is_other(&self) -> bool { + self.as_other().map(|err| err.is::()).unwrap_or(false) + } } impl From for ConsensusError { @@ -500,6 +536,10 @@ pub struct TxGasLimitTooHighErr { pub max_allowed: u64, } +#[derive(Debug, thiserror::Error)] +#[error("{0}")] +struct MessageError(String); + #[cfg(test)] mod tests { use super::*; @@ -509,24 +549,31 @@ mod tests { struct CustomL2Error; #[test] - fn test_custom_error_conversion() { - // Test conversion from custom error to ConsensusError - let custom_err = CustomL2Error; - let arc_err: Arc = Arc::new(custom_err); - let consensus_err: ConsensusError = arc_err.into(); - - // Verify it's the Custom variant - assert!(matches!(consensus_err, ConsensusError::Custom(_))); + fn test_other_error_conversion() { + let consensus_err = ConsensusError::other(CustomL2Error); + assert!(matches!(consensus_err, ConsensusError::Other(_))); } #[test] - fn test_custom_error_display() { - let custom_err = CustomL2Error; - let arc_err: Arc = Arc::new(custom_err); - let consensus_err: ConsensusError = arc_err.into(); - - // Verify the error message is preserved through transparent attribute + fn test_other_error_display() { + let consensus_err = ConsensusError::other(CustomL2Error); let error_message = format!("{}", consensus_err); assert_eq!(error_message, "Custom L2 consensus error"); } + + #[test] + fn test_other_error_downcast() { + let consensus_err = ConsensusError::other(CustomL2Error); + + assert!(consensus_err.is_other::()); + assert!(consensus_err.downcast_other_ref::().is_some()); + } + + #[test] + fn test_other_msg() { + let consensus_err = ConsensusError::msg("consensus message"); + + assert_eq!(consensus_err.to_string(), "consensus message"); + assert!(consensus_err.downcast_other_ref::().is_some()); + } }