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