Skip to main content

reth_stages_api/
error.rs

1use crate::PipelineEvent;
2use alloy_eips::eip1898::BlockWithParent;
3use reth_codecs::DecompressError;
4use reth_consensus::ConsensusError;
5use reth_errors::{BlockExecutionError, DatabaseError, RethError};
6use reth_network_p2p::error::DownloadError;
7use reth_provider::ProviderError;
8use reth_prune::{PruneSegment, PruneSegmentError, PrunerError, UnwindTargetPrunedError};
9use reth_stages_types::StageId;
10use reth_static_file_types::StaticFileSegment;
11use thiserror::Error;
12use tokio::sync::broadcast::error::SendError;
13
14/// Represents the specific error type within a block error.
15#[derive(Error, Debug)]
16pub enum BlockErrorKind {
17    /// The block encountered a validation error.
18    #[error("validation error: {0}")]
19    Validation(#[from] ConsensusError),
20    /// The block encountered an execution error.
21    #[error("execution error: {0}")]
22    Execution(#[from] BlockExecutionError),
23}
24
25impl BlockErrorKind {
26    /// Returns `true` if the error is a state root error.
27    pub const fn is_state_root_error(&self) -> bool {
28        matches!(self, Self::Validation(err) if err.is_state_root_error())
29    }
30}
31
32/// A stage execution error.
33#[derive(Error, Debug)]
34pub enum StageError {
35    /// The stage encountered an error related to a block.
36    #[error("stage encountered an error in block #{number}: {error}", number = block.block.number)]
37    Block {
38        /// The block that caused the error.
39        block: Box<BlockWithParent>,
40        /// The specific error type, either consensus or execution error.
41        #[source]
42        error: BlockErrorKind,
43    },
44    /// The stage encountered a downloader error where the responses cannot be attached to the
45    /// current head.
46    #[error(
47        "stage encountered inconsistent chain: \
48         downloaded header #{header_number} ({header_hash}) is detached from \
49         local head #{head_number} ({head_hash}): {error}",
50        header_number = header.block.number,
51        header_hash = header.block.hash,
52        head_number = local_head.block.number,
53        head_hash = local_head.block.hash,
54    )]
55    DetachedHead {
56        /// The local head we attempted to attach to.
57        local_head: Box<BlockWithParent>,
58        /// The header we attempted to attach.
59        header: Box<BlockWithParent>,
60        /// The error that occurred when attempting to attach the header.
61        #[source]
62        error: Box<ConsensusError>,
63    },
64    /// The headers stage is missing sync gap.
65    #[error("missing sync gap")]
66    MissingSyncGap,
67    /// The stage encountered a database error.
68    #[error("internal database error occurred: {0}")]
69    Database(#[from] DatabaseError),
70    /// Invalid pruning configuration
71    #[error(transparent)]
72    PruningConfiguration(#[from] PruneSegmentError),
73    /// Pruner error
74    #[error(transparent)]
75    Pruner(#[from] PrunerError),
76    /// Invalid checkpoint passed to the stage
77    #[error("invalid stage checkpoint: {0}")]
78    StageCheckpoint(u64),
79    /// Missing download buffer on stage execution.
80    /// Returned if stage execution was called without polling for readiness.
81    #[error("missing download buffer")]
82    MissingDownloadBuffer,
83    /// Download channel closed
84    #[error("download channel closed")]
85    ChannelClosed,
86    /// The stage encountered a database integrity error.
87    #[error("database integrity error occurred: {0}")]
88    DatabaseIntegrity(#[from] ProviderError),
89    /// Invalid download response. Applicable for stages which
90    /// rely on external downloaders
91    #[error("invalid download response: {0}")]
92    Download(#[from] DownloadError),
93    /// Database is ahead of static file data.
94    #[error("missing static file data for block number: {number}", number = block.block.number)]
95    MissingStaticFileData {
96        /// Starting block with missing data.
97        block: Box<BlockWithParent>,
98        /// Static File segment
99        segment: StaticFileSegment,
100    },
101    /// The prune checkpoint for the given segment is missing.
102    #[error("missing prune checkpoint for {0}")]
103    MissingPruneCheckpoint(PruneSegment),
104    /// Post Execute Commit error
105    #[error("post execute commit error occurred: {_0}")]
106    PostExecuteCommit(&'static str),
107    /// Internal error
108    #[error(transparent)]
109    Internal(#[from] RethError),
110    /// The stage encountered a recoverable error.
111    ///
112    /// These types of errors are caught by the [Pipeline][crate::Pipeline] and trigger a restart
113    /// of the stage.
114    #[error(transparent)]
115    Recoverable(Box<dyn core::error::Error + Send + Sync>),
116    /// The stage encountered a fatal error.
117    ///
118    /// These types of errors stop the pipeline.
119    #[error(transparent)]
120    Fatal(Box<dyn core::error::Error + Send + Sync>),
121}
122
123impl StageError {
124    /// If the error is fatal the pipeline will stop.
125    pub const fn is_fatal(&self) -> bool {
126        matches!(
127            self,
128            Self::Database(_) |
129                Self::Download(_) |
130                Self::DatabaseIntegrity(_) |
131                Self::StageCheckpoint(_) |
132                Self::MissingDownloadBuffer |
133                Self::MissingSyncGap |
134                Self::ChannelClosed |
135                Self::Internal(_) |
136                Self::Fatal(_)
137        )
138    }
139}
140
141impl From<std::io::Error> for StageError {
142    fn from(source: std::io::Error) -> Self {
143        Self::Fatal(Box::new(source))
144    }
145}
146
147impl From<DecompressError> for StageError {
148    fn from(error: DecompressError) -> Self {
149        Self::Database(DatabaseError::from(error))
150    }
151}
152
153/// A pipeline execution error.
154#[derive(Error, Debug)]
155pub enum PipelineError {
156    /// The pipeline encountered an irrecoverable error in one of the stages.
157    #[error(transparent)]
158    Stage(#[from] StageError),
159    /// The pipeline encountered a database error.
160    #[error(transparent)]
161    Database(#[from] DatabaseError),
162    /// Provider error.
163    #[error(transparent)]
164    Provider(#[from] ProviderError),
165    /// The pipeline encountered an error while trying to send an event.
166    #[error("pipeline encountered an error while trying to send an event")]
167    Channel(#[from] Box<SendError<PipelineEvent>>),
168    /// Internal error
169    #[error(transparent)]
170    Internal(#[from] RethError),
171    /// The pipeline encountered an unwind when `fail_on_unwind` was set to `true`.
172    #[error("unexpected unwind")]
173    UnexpectedUnwind,
174    /// Unwind target pruned error.
175    #[error(transparent)]
176    UnwindTargetPruned(#[from] UnwindTargetPrunedError),
177    /// A partial run named a stage this pipeline does not contain.
178    #[error("pipeline does not contain stage {0}")]
179    MissingStage(StageId),
180}