stratus/eth/primitives/
stratus_error.rs

1use futures::future::BoxFuture;
2use jsonrpsee::MethodResponse;
3use jsonrpsee::ResponsePayload;
4use jsonrpsee::core::middleware::ResponseFuture;
5use jsonrpsee::types::ErrorObjectOwned;
6use jsonrpsee::types::Id;
7use revm::context::DBErrorMarker;
8use revm::context::result::EVMError;
9use stratus_macros::ErrorCode;
10
11use super::execution_result::RevertReason;
12use crate::alias::JsonValue;
13use crate::eth::executor::EvmInput;
14use crate::eth::primitives::Address;
15use crate::eth::primitives::BlockFilter;
16use crate::eth::primitives::BlockNumber;
17use crate::eth::primitives::Bytes;
18use crate::eth::primitives::Nonce;
19use crate::ext::to_json_value;
20
21pub trait ErrorCode {
22    fn error_code(&self) -> i32;
23    fn str_repr_from_err_code(code: i32) -> Option<&'static str>;
24}
25
26#[derive(Debug, thiserror::Error, strum::EnumProperty, strum::IntoStaticStr, ErrorCode)]
27#[major_error_code = 1000]
28pub enum RpcError {
29    #[error("block filter does not point to a valid block.")]
30    #[error_code = 1]
31    BlockFilterInvalid { filter: BlockFilter },
32
33    #[error("denied because will fetch data from {actual} blocks, but the max allowed is {max}.")]
34    #[error_code = 2]
35    BlockRangeInvalid { actual: u64, max: u64 },
36
37    #[error("denied because client did not identify itself.")]
38    #[error_code = 3]
39    ClientMissing,
40
41    #[error("failed to decode {rust_type} parameter.")]
42    #[error_code = 4]
43    ParameterInvalid { rust_type: &'static str, decode_error: String },
44
45    #[error("expected {rust_type} parameter, but received nothing.")]
46    #[error_code = 5]
47    ParameterMissing { rust_type: &'static str },
48
49    #[error("invalid subscription event: {event}")]
50    #[error_code = 6]
51    SubscriptionInvalid { event: String },
52
53    #[error("denied because reached maximum subscription limit of {max}.")]
54    #[error_code = 7]
55    SubscriptionLimit { max: u32 },
56
57    #[error("failed to decode transaction RLP data.")]
58    #[error_code = 8]
59    TransactionInvalid { decode_error: String },
60
61    #[error("miner mode param is invalid.")]
62    #[error_code = 9]
63    MinerModeParamInvalid,
64}
65
66#[derive(Debug, thiserror::Error, strum::EnumProperty, strum::IntoStaticStr, ErrorCode)]
67#[major_error_code = 2000]
68pub enum TransactionError {
69    #[error("function selector was not recognized: account at {address} is not a contract.")]
70    #[error_code = 1]
71    AccountNotContract { address: Address },
72
73    #[error("transaction nonce {transaction} does not match account nonce {account}.")]
74    #[error_code = 2]
75    Nonce { transaction: Nonce, account: Nonce },
76
77    #[error("EVM execution error: {0:?}.")]
78    #[error_code = 3]
79    EvmFailed(String), // TODO: split this in multiple errors
80
81    #[error("failed to execute transaction in leader: {0:?}.")]
82    #[error_code = 4]
83    LeaderFailed(ErrorObjectOwned),
84
85    #[error("failed to forward transaction to leader node.")]
86    #[error_code = 5]
87    ForwardToLeaderFailed,
88
89    #[error("transaction reverted during execution. output: {output}")]
90    #[error_code = 6]
91    RevertedCall { output: Bytes },
92
93    #[error("transaction from zero address is not allowed.")]
94    #[error_code = 7]
95    FromZeroAddress,
96
97    #[error("transaction reverted during execution. reason: {reason}")]
98    #[error_code = 8]
99    RevertedCallWithReason { reason: RevertReason },
100}
101
102#[derive(Debug, thiserror::Error, strum::EnumProperty, strum::IntoStaticStr, ErrorCode)]
103#[major_error_code = 3000]
104pub enum StorageError {
105    #[error("block conflict: {number} already exists in the permanent storage.")]
106    #[error_code = 1]
107    BlockConflict { number: BlockNumber },
108
109    #[error("mined number conflict between new block number ({new}) and mined block number ({mined}).")]
110    #[error_code = 2]
111    MinedNumberConflict { new: BlockNumber, mined: BlockNumber },
112
113    // *deprecated*
114    // #[error("Transaction execution conflicts: {0:?}.")]
115    // #[error_code = 3]
116    // TransactionConflict(Box<ExecutionConflicts>),
117    #[error("transaction input does not match block header")]
118    #[error_code = 4]
119    EvmInputMismatch { expected: Box<EvmInput>, actual: Box<EvmInput> },
120
121    #[error("pending number conflict between new block number ({new}) and pending block number ({pending}).")]
122    #[error_code = 5]
123    PendingNumberConflict { new: BlockNumber, pending: BlockNumber },
124
125    #[error("there are ({pending_txs}) pending transactions.")]
126    #[error_code = 6]
127    PendingTransactionsExist { pending_txs: usize },
128
129    #[error("rocksdb returned an error: {err}")]
130    #[error_code = 7]
131    RocksError { err: anyhow::Error },
132
133    #[error("block not found using filter: {filter}")]
134    #[error_code = 8]
135    BlockNotFound { filter: BlockFilter },
136
137    #[error("unexpected storage error: {msg}")]
138    #[error_code = 9]
139    Unexpected { msg: String },
140}
141
142#[derive(Debug, thiserror::Error, strum::EnumProperty, strum::IntoStaticStr, ErrorCode)]
143#[major_error_code = 4000]
144pub enum ImporterError {
145    #[error("importer is already running.")]
146    #[error_code = 1]
147    AlreadyRunning,
148
149    #[error("importer is already shutdown.")]
150    #[error_code = 2]
151    AlreadyShutdown,
152
153    #[error("failed to parse importer configuration.")]
154    #[error_code = 3]
155    ConfigParseError,
156
157    #[error("failed to initialize importer.")]
158    #[error_code = 4]
159    InitError,
160}
161
162#[derive(Debug, thiserror::Error, strum::EnumProperty, strum::IntoStaticStr, ErrorCode)]
163#[major_error_code = 5000]
164pub enum ConsensusError {
165    #[error("consensus is temporarily unavailable for follower node.")]
166    #[error_code = 1]
167    Unavailable,
168
169    #[error("consensus is set.")]
170    #[error_code = 2]
171    Set,
172
173    #[error("failed to update consensus: Consensus is not set.")]
174    #[error_code = 3]
175    NotSet,
176}
177
178#[derive(Debug, thiserror::Error, strum::EnumProperty, strum::IntoStaticStr, ErrorCode)]
179#[major_error_code = 6000]
180pub enum UnexpectedError {
181    #[error("unexpected channel {channel} closed.")]
182    #[error_code = 1]
183    ChannelClosed { channel: &'static str },
184
185    #[error("unexpected error: {0:?}.")]
186    #[error_code = 2]
187    Unexpected(anyhow::Error),
188}
189
190#[derive(Debug, thiserror::Error, strum::EnumProperty, strum::IntoStaticStr, ErrorCode)]
191#[major_error_code = 7000]
192pub enum StateError {
193    #[error("stratus is not ready to start servicing requests.")]
194    #[error_code = 1]
195    StratusNotReady,
196
197    #[error("stratus is shutting down.")]
198    #[error_code = 2]
199    StratusShutdown,
200
201    #[error("stratus node is not a follower.")]
202    #[error_code = 3]
203    StratusNotFollower,
204
205    #[error("incorrect password, cancelling operation.")]
206    #[error_code = 4]
207    InvalidPassword,
208
209    #[error("stratus node is already in the process of changing mode.")]
210    #[error_code = 5]
211    ModeChangeInProgress,
212
213    #[error("transaction processing is temporarily disabled.")]
214    #[error_code = 6]
215    TransactionsDisabled,
216
217    #[error("can't change miner mode while transactions are enabled.")]
218    #[error_code = 7]
219    TransactionsEnabled,
220}
221
222#[derive(Debug, thiserror::Error, strum::EnumProperty, strum::IntoStaticStr)]
223pub enum StratusError {
224    #[error(transparent)]
225    RPC(#[from] RpcError),
226
227    #[error(transparent)]
228    Transaction(#[from] TransactionError),
229
230    #[error(transparent)]
231    Storage(#[from] StorageError),
232
233    #[error(transparent)]
234    Importer(#[from] ImporterError),
235
236    #[error(transparent)]
237    Consensus(#[from] ConsensusError),
238
239    #[error(transparent)]
240    Unexpected(#[from] UnexpectedError),
241
242    #[error(transparent)]
243    State(#[from] StateError),
244}
245
246impl ErrorCode for StratusError {
247    fn error_code(&self) -> i32 {
248        match self {
249            Self::RPC(err) => err.error_code(),
250            Self::Transaction(err) => err.error_code(),
251            Self::Storage(err) => err.error_code(),
252            Self::Importer(err) => err.error_code(),
253            Self::Consensus(err) => err.error_code(),
254            Self::Unexpected(err) => err.error_code(),
255            Self::State(err) => err.error_code(),
256        }
257    }
258
259    fn str_repr_from_err_code(code: i32) -> Option<&'static str> {
260        let major = code / 1000;
261        match major {
262            1 => RpcError::str_repr_from_err_code(code),
263            2 => TransactionError::str_repr_from_err_code(code),
264            3 => StorageError::str_repr_from_err_code(code),
265            4 => ImporterError::str_repr_from_err_code(code),
266            5 => ConsensusError::str_repr_from_err_code(code),
267            6 => UnexpectedError::str_repr_from_err_code(code),
268            7 => StateError::str_repr_from_err_code(code),
269            _ => None,
270        }
271    }
272}
273
274impl DBErrorMarker for StratusError {}
275
276impl StratusError {
277    /// Error message to be used in JSON-RPC response.
278    pub fn rpc_message(&self) -> String {
279        self.to_string()
280    }
281
282    /// Error additional data to be used in JSON-RPC response.
283    pub fn rpc_data(&self) -> JsonValue {
284        match self {
285            // RPC
286            Self::RPC(RpcError::BlockFilterInvalid { filter }) => to_json_value(filter),
287            Self::RPC(RpcError::ParameterInvalid { decode_error, .. }) => to_json_value(decode_error),
288
289            // Transaction
290            Self::RPC(RpcError::TransactionInvalid { decode_error }) => to_json_value(decode_error),
291            Self::Transaction(TransactionError::EvmFailed(e)) => JsonValue::String(e.to_string()),
292            Self::Transaction(TransactionError::RevertedCall { output }) => to_json_value(output),
293            Self::Transaction(TransactionError::RevertedCallWithReason { reason }) => to_json_value(reason),
294
295            // Unexpected
296            Self::Unexpected(UnexpectedError::Unexpected(e)) => JsonValue::String(e.to_string()),
297
298            _ => JsonValue::Null,
299        }
300    }
301
302    pub fn to_response_future<'a>(self, id: Id<'_>) -> ResponseFuture<BoxFuture<'a, MethodResponse>, MethodResponse> {
303        let response = ResponsePayload::<()>::error(StratusError::RPC(RpcError::ClientMissing));
304        let method_response = MethodResponse::response(id, response, u32::MAX as usize);
305        ResponseFuture::ready(method_response)
306    }
307}
308
309// -----------------------------------------------------------------------------
310// Conversions: Other -> Self
311// -----------------------------------------------------------------------------
312
313impl From<anyhow::Error> for StratusError {
314    fn from(value: anyhow::Error) -> Self {
315        Self::Unexpected(UnexpectedError::Unexpected(value))
316    }
317}
318
319impl From<serde_json::Error> for StratusError {
320    fn from(value: serde_json::Error) -> Self {
321        Self::Unexpected(UnexpectedError::Unexpected(anyhow::anyhow!(value)))
322    }
323}
324
325impl From<EVMError<StratusError>> for StratusError {
326    fn from(value: EVMError<StratusError>) -> Self {
327        match value {
328            EVMError::Database(err) => err,
329            EVMError::Custom(err) => Self::Transaction(TransactionError::EvmFailed(err)),
330            EVMError::Header(err) => Self::Transaction(TransactionError::EvmFailed(err.to_string())),
331            EVMError::Transaction(err) => Self::Transaction(TransactionError::EvmFailed(err.to_string())),
332        }
333    }
334}
335
336// -----------------------------------------------------------------------------
337// Conversions: Self -> Other
338// -----------------------------------------------------------------------------
339impl From<StratusError> for ErrorObjectOwned {
340    fn from(value: StratusError) -> Self {
341        // return response from leader
342        if let StratusError::Transaction(TransactionError::LeaderFailed(response)) = value {
343            return response;
344        }
345        // generate response
346        let data = match value.rpc_data() {
347            serde_json::Value::String(data_str) => {
348                let data_str = data_str.trim_start_matches('\"').trim_end_matches('\"').replace("\\\"", "\"");
349                JsonValue::String(data_str)
350            }
351            data => data,
352        };
353
354        Self::owned(value.error_code(), value.rpc_message(), Some(data))
355    }
356}
357
358#[cfg(test)]
359mod tests {
360    use super::*;
361
362    #[test]
363    fn test_str_repr_from_err_code() {
364        // RPC error
365        assert_eq!(StratusError::str_repr_from_err_code(1001), Some("BlockFilterInvalid"));
366
367        // Transaction error
368        assert_eq!(StratusError::str_repr_from_err_code(2001), Some("AccountNotContract"));
369
370        // Storage error
371        assert_eq!(StratusError::str_repr_from_err_code(3001), Some("BlockConflict"));
372
373        // Importer error
374        assert_eq!(StratusError::str_repr_from_err_code(4001), Some("AlreadyRunning"));
375
376        // Consensus error
377        assert_eq!(StratusError::str_repr_from_err_code(5001), Some("Unavailable"));
378
379        // Unexpected error
380        assert_eq!(StratusError::str_repr_from_err_code(6001), Some("ChannelClosed"));
381
382        // State error
383        assert_eq!(StratusError::str_repr_from_err_code(7003), Some("StratusNotFollower"));
384
385        // Invalid error code
386        assert_eq!(StratusError::str_repr_from_err_code(9999), None);
387    }
388}