cfx_rpc_utils/error/
errors.rs

1// Copyright 2021 Conflux Foundation. All rights reserved.
2// Conflux is free software and distributed under GNU General Public License.
3// See http://www.gnu.org/licenses/
4
5use crate::error::jsonrpc_error_helpers::*;
6use alloy_primitives::{hex, Address, Bytes};
7use alloy_rpc_types::error::EthRpcErrorCode;
8use alloy_sol_types::decode_revert_reason;
9use jsonrpc_core::{Error as JsonRpcError, ErrorCode};
10use jsonrpsee::types::ErrorObjectOwned;
11use revm_context_interface::result::{HaltReason, OutOfGasError};
12use std::time::Duration;
13
14// The key point is the error code and message.
15
16/// Result alias
17pub type EthResult<T> = Result<T, EthApiError>;
18
19/// Errors that can occur when interacting with the `eth_` namespace
20#[derive(Debug, thiserror::Error)]
21pub enum EthApiError {
22    /// When a raw transaction is empty
23    #[error("empty transaction data")]
24    EmptyRawTransactionData,
25    /// When decoding a signed transaction fails
26    #[error("failed to decode signed transaction")]
27    FailedToDecodeSignedTransaction,
28    /// When the transaction signature is invalid
29    #[error("invalid transaction signature")]
30    InvalidTransactionSignature,
31    /// Errors related to the transaction pool
32    #[error(transparent)]
33    PoolError(RpcPoolError),
34    /// When an unknown block number is encountered
35    #[error("unknown block number")]
36    UnknownBlockNumber,
37    /// Thrown when querying for `finalized` or `safe` block before the merge
38    /// transition is finalized, <https://github.com/ethereum/execution-apis/blob/6d17705a875e52c26826124c2a8a15ed542aeca2/src/schemas/block.yaml#L109>
39    ///
40    /// op-node now checks for either `Unknown block` OR `unknown block`:
41    /// <https://github.com/ethereum-optimism/optimism/blob/3b374c292e2b05cc51b52212ba68dd88ffce2a3b/op-service/sources/l2_client.go#L105>
42    ///
43    /// TODO(#8045): Temporary, until a version of <https://github.com/ethereum-optimism/optimism/pull/10071> is pushed through that doesn't require this to figure out the EL sync status.
44    #[error("unknown block")]
45    UnknownSafeOrFinalizedBlock,
46    /// Thrown when an unknown block or transaction index is encountered
47    #[error("unknown block or tx index")]
48    UnknownBlockOrTxIndex,
49    /// When an invalid block range is provided
50    #[error("invalid block range")]
51    InvalidBlockRange,
52    /// An internal error where prevrandao is not set in the evm's environment
53    #[error("prevrandao not in the EVM's environment after merge")]
54    PrevrandaoNotSet,
55    /// `excess_blob_gas` is not set for Cancun and above
56    #[error("excess blob gas missing in the EVM's environment after Cancun")]
57    ExcessBlobGasNotSet,
58    /// Thrown when a call or transaction request (`eth_call`,
59    /// `eth_estimateGas`, `eth_sendTransaction`) contains conflicting
60    /// fields (legacy, EIP-1559)
61    #[error(
62        "both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified"
63    )]
64    ConflictingFeeFieldsInRequest,
65    /// Errors related to invalid transactions
66    #[error(transparent)]
67    InvalidTransaction(#[from] RpcInvalidTransactionError),
68    /// Thrown when constructing an RPC block from primitive block data fails
69    // #[error(transparent)]
70    // InvalidBlockData(#[from] BlockError),
71    /// Thrown when an `AccountOverride` contains conflicting `state` and
72    /// `stateDiff` fields
73    #[error("account {0:?} has both 'state' and 'stateDiff'")]
74    BothStateAndStateDiffInOverride(Address),
75    /// Other internal error
76    // #[error(transparent)]
77    // Internal(RethError),
78    /// Error related to signing
79    // #[error(transparent)]
80    // Signing(#[from] SignError),
81    /// Thrown when a requested transaction is not found
82    #[error("transaction not found")]
83    TransactionNotFound,
84    /// Some feature is unsupported
85    #[error("unsupported")]
86    Unsupported(&'static str),
87    /// General purpose error for invalid params
88    #[error("{0}")]
89    InvalidParams(String),
90    /// When the tracer config does not match the tracer
91    #[error("invalid tracer config")]
92    InvalidTracerConfig,
93    /// When the percentile array is invalid
94    #[error("invalid reward percentiles")]
95    InvalidRewardPercentiles,
96    /// Error thrown when a spawned blocking task failed to deliver an
97    /// anticipated response.
98    ///
99    /// This only happens if the blocking task panics and is aborted before it
100    /// can return a response back to the request handler.
101    #[error("internal blocking task error")]
102    InternalBlockingTaskError,
103    /// Error thrown when a spawned blocking task failed to deliver an
104    /// anticipated response
105    #[error("internal eth error")]
106    InternalEthError,
107    /// Error thrown when a (tracing) call exceeds the configured timeout
108    #[error("execution aborted (timeout = {0:?})")]
109    ExecutionTimedOut(Duration),
110    /// Internal Error thrown by the javascript tracer
111    #[error("{0}")]
112    InternalJsTracerError(String),
113    /// Call Input error when both `data` and `input` fields are set and not
114    /// equal.
115    #[error(transparent)]
116    TransactionInputError(#[from] TransactionInputError),
117    /// Evm generic purpose error.
118    #[error("Revm error: {0}")]
119    EvmCustom(String),
120    /// Error encountered when converting a transaction type
121    #[error("Transaction conversion error")]
122    TransactionConversionError,
123    /// Error thrown when tracing with a muxTracer fails
124    // #[error(transparent)]
125    // MuxTracerError(#[from] MuxError),
126    /// Any other error
127    #[error("{0}")]
128    Other(String),
129}
130
131impl From<EthApiError> for JsonRpcError {
132    fn from(error: EthApiError) -> Self {
133        match error {
134            EthApiError::FailedToDecodeSignedTransaction |
135            EthApiError::InvalidTransactionSignature |
136            EthApiError::EmptyRawTransactionData |
137            EthApiError::InvalidBlockRange |
138            EthApiError::ConflictingFeeFieldsInRequest |
139            // EthApiError::Signing(_) |
140            EthApiError::BothStateAndStateDiffInOverride(_) |
141            EthApiError::InvalidTracerConfig |
142            EthApiError::TransactionConversionError => invalid_params_rpc_err(error.to_string()),
143            EthApiError::InvalidTransaction(err) => err.into(),
144            EthApiError::PoolError(err) => err.into(),
145            EthApiError::PrevrandaoNotSet |
146            EthApiError::ExcessBlobGasNotSet |
147            // EthApiError::InvalidBlockData(_) |
148            // EthApiError::Internal(_) |
149            EthApiError::TransactionNotFound |
150            EthApiError::EvmCustom(_) |
151            EthApiError::InvalidRewardPercentiles => internal_rpc_err(error.to_string()),
152            EthApiError::UnknownBlockNumber | EthApiError::UnknownBlockOrTxIndex => {
153                build_rpc_server_error(EthRpcErrorCode::ResourceNotFound.code() as i64, error.to_string())
154            }
155            EthApiError::UnknownSafeOrFinalizedBlock => {
156                build_rpc_server_error(EthRpcErrorCode::UnknownBlock.code() as i64, error.to_string())
157            }
158            EthApiError::Unsupported(msg) => internal_rpc_err(msg),
159            EthApiError::InternalJsTracerError(msg) => internal_rpc_err(msg),
160            EthApiError::InvalidParams(msg) => invalid_params_rpc_err(msg),
161            err @ EthApiError::ExecutionTimedOut(_) => {
162                build_rpc_server_error(-32000, err.to_string()) // CALL_EXECUTION_FAILED_CODE = -32000
163            }
164            err @ EthApiError::InternalBlockingTaskError | err @ EthApiError::InternalEthError => {
165                internal_rpc_err(err.to_string())
166            }
167            err @ EthApiError::TransactionInputError(_) => invalid_params_rpc_err(err.to_string()),
168            EthApiError::Other(err) => internal_rpc_err(err),
169            // EthApiError::MuxTracerError(msg) => internal_rpc_err(msg.to_string()),
170        }
171    }
172}
173
174impl From<EthApiError> for ErrorObjectOwned {
175    fn from(e: EthApiError) -> Self {
176        let err = JsonRpcError::from(e);
177        ErrorObjectOwned::owned(err.code.code() as i32, err.message, err.data)
178    }
179}
180
181/// An error due to invalid transaction.
182///
183/// The only reason this exists is to maintain compatibility with other clients
184/// de-facto standard error messages.
185///
186/// These error variants can be thrown when the transaction is checked prior to
187/// execution.
188///
189/// These variants also cover all errors that can be thrown by revm.
190///
191/// ## Nomenclature
192///
193/// This type is explicitly modeled after geth's error variants and uses
194///   `fee cap` for `max_fee_per_gas`
195///   `tip` for `max_priority_fee_per_gas`
196#[derive(thiserror::Error, Debug)]
197pub enum RpcInvalidTransactionError {
198    /// returned if the nonce of a transaction is lower than the one present in
199    /// the local chain.
200    #[error("nonce too low")]
201    NonceTooLow,
202    /// returned if the nonce of a transaction is higher than the next one
203    /// expected based on the local chain.
204    #[error("nonce too high")]
205    NonceTooHigh,
206    /// Returned if the nonce of a transaction is too high
207    /// Incrementing the nonce would lead to invalid state (overflow)
208    #[error("nonce has max value")]
209    NonceMaxValue,
210    /// thrown if the transaction sender doesn't have enough funds for a
211    /// transfer
212    #[error("insufficient funds for transfer")]
213    InsufficientFundsForTransfer,
214    /// thrown if creation transaction provides the init code bigger than init
215    /// code size limit.
216    #[error("max initcode size exceeded")]
217    MaxInitCodeSizeExceeded,
218    /// Represents the inability to cover max cost + value (account balance too
219    /// low).
220    #[error("insufficient funds for gas * price + value")]
221    InsufficientFunds,
222    /// Thrown when calculating gas usage
223    #[error("gas uint64 overflow")]
224    GasUintOverflow,
225    /// Thrown if the transaction is specified to use less gas than required to
226    /// start the invocation.
227    #[error("intrinsic gas too low")]
228    GasTooLow,
229    /// Thrown if the transaction gas exceeds the limit
230    #[error("intrinsic gas too high")]
231    GasTooHigh,
232    /// Thrown if a transaction is not supported in the current network
233    /// configuration.
234    #[error("transaction type not supported")]
235    TxTypeNotSupported,
236    /// Thrown to ensure no one is able to specify a transaction with a tip
237    /// higher than the total fee cap.
238    #[error("max priority fee per gas higher than max fee per gas")]
239    TipAboveFeeCap,
240    /// A sanity error to avoid huge numbers specified in the tip field.
241    #[error("max priority fee per gas higher than 2^256-1")]
242    TipVeryHigh,
243    /// A sanity error to avoid huge numbers specified in the fee cap field.
244    #[error("max fee per gas higher than 2^256-1")]
245    FeeCapVeryHigh,
246    /// Thrown post London if the transaction's fee is less than the base fee
247    /// of the block
248    #[error("max fee per gas less than block base fee")]
249    FeeCapTooLow,
250    /// Thrown if the sender of a transaction is a contract.
251    #[error("sender is not an EOA")]
252    SenderNoEOA,
253    /// Gas limit was exceeded during execution.
254    /// Contains the gas limit.
255    #[error("out of gas: gas required exceeds allowance: {0}")]
256    BasicOutOfGas(u64),
257    /// Gas limit was exceeded during memory expansion.
258    /// Contains the gas limit.
259    #[error("out of gas: gas exhausted during memory expansion: {0}")]
260    MemoryOutOfGas(u64),
261    /// Gas limit was exceeded during precompile execution.
262    /// Contains the gas limit.
263    #[error(
264        "out of gas: gas exhausted during precompiled contract execution: {0}"
265    )]
266    PrecompileOutOfGas(u64),
267    /// An operand to an opcode was invalid or out of range.
268    /// Contains the gas limit.
269    #[error("out of gas: invalid operand to an opcode; {0}")]
270    InvalidOperandOutOfGas(u64),
271    /// Thrown if executing a transaction failed during estimate/call
272    #[error(transparent)]
273    Revert(RevertError),
274    /// Unspecific EVM halt error.
275    #[error("EVM error: {0:?}")]
276    EvmHalt(HaltReason),
277    /// Invalid chain id set for the transaction.
278    #[error("invalid chain ID")]
279    InvalidChainId,
280    /// The transaction is before Spurious Dragon and has a chain ID
281    #[error("transactions before Spurious Dragon should not have a chain ID")]
282    OldLegacyChainId,
283    /// The transitions is before Berlin and has access list
284    #[error("transactions before Berlin should not have access list")]
285    AccessListNotSupported,
286    /// `max_fee_per_blob_gas` is not supported for blocks before the Cancun
287    /// hardfork.
288    #[error("max_fee_per_blob_gas is not supported for blocks before the Cancun hardfork")]
289    MaxFeePerBlobGasNotSupported,
290    /// `blob_hashes`/`blob_versioned_hashes` is not supported for blocks
291    /// before the Cancun hardfork.
292    #[error("blob_versioned_hashes is not supported for blocks before the Cancun hardfork")]
293    BlobVersionedHashesNotSupported,
294    /// Block `blob_base_fee` is greater than tx-specified
295    /// `max_fee_per_blob_gas` after Cancun.
296    #[error("max fee per blob gas less than block blob gas fee")]
297    BlobFeeCapTooLow,
298    /// Blob transaction has a versioned hash with an invalid blob
299    #[error("blob hash version mismatch")]
300    BlobHashVersionMismatch,
301    /// Blob transaction has no versioned hashes
302    #[error("blob transaction missing blob hashes")]
303    BlobTransactionMissingBlobHashes,
304    /// Blob transaction has too many blobs
305    #[error(
306        "blob transaction exceeds max blobs per block; got {have}, max {max}"
307    )]
308    TooManyBlobs {
309        /// The maximum number of blobs allowed.
310        max: usize,
311        /// The number of blobs in the transaction.
312        have: usize,
313    },
314    /// Blob transaction is a create transaction
315    #[error("blob transaction is a create transaction")]
316    BlobTransactionIsCreate,
317    /// Empty authorization list (EIP-7702)
318    #[error("empty authorization list")]
319    EmptyAuthorizationList,
320    /// Max priority fee greater than max fee (EIP-1559)
321    #[error("blob transaction is a create transaction")]
322    PriortyGreaterThanMaxFee,
323}
324
325impl RpcInvalidTransactionError {
326    /// Returns the rpc error code for this error.
327    const fn error_code(&self) -> i32 {
328        match self {
329            Self::InvalidChainId | Self::GasTooLow | Self::GasTooHigh => {
330                EthRpcErrorCode::InvalidInput.code()
331            }
332            Self::Revert(_) => EthRpcErrorCode::ExecutionError.code(),
333            _ => EthRpcErrorCode::TransactionRejected.code(),
334        }
335    }
336
337    /// Converts the halt error
338    ///
339    /// Takes the configured gas limit of the transaction which is attached to
340    /// the error
341    #[allow(dead_code)]
342    pub(crate) fn halt(reason: HaltReason, gas_limit: u64) -> Self {
343        match reason {
344            HaltReason::OutOfGas(err) => Self::out_of_gas(err, gas_limit),
345            HaltReason::NonceOverflow => Self::NonceMaxValue,
346            err => Self::EvmHalt(err),
347        }
348    }
349
350    /// Converts the out of gas error
351    #[allow(dead_code)]
352    pub(crate) const fn out_of_gas(
353        reason: OutOfGasError, gas_limit: u64,
354    ) -> Self {
355        match reason {
356            OutOfGasError::Basic | OutOfGasError::ReentrancySentry => {
357                Self::BasicOutOfGas(gas_limit)
358            }
359            OutOfGasError::Memory | OutOfGasError::MemoryLimit => {
360                Self::MemoryOutOfGas(gas_limit)
361            }
362            OutOfGasError::Precompile => Self::PrecompileOutOfGas(gas_limit),
363            OutOfGasError::InvalidOperand => {
364                Self::InvalidOperandOutOfGas(gas_limit)
365            }
366        }
367    }
368}
369
370impl From<RpcInvalidTransactionError> for JsonRpcError {
371    fn from(e: RpcInvalidTransactionError) -> Self {
372        match e {
373            RpcInvalidTransactionError::Revert(revert) => JsonRpcError {
374                code: ErrorCode::ServerError(revert.error_code() as i64),
375                message: revert.to_string(),
376                data: revert.output.as_ref().map(|out| out.as_ref()).map(|v| {
377                    serde_json::Value::String(hex::encode_prefixed(v))
378                }),
379            },
380            err => JsonRpcError {
381                code: ErrorCode::ServerError(err.error_code() as i64),
382                message: err.to_string(),
383                data: None,
384            },
385        }
386    }
387}
388
389/// Error thrown when both `data` and `input` fields are set and not equal.
390#[derive(Debug, Default, thiserror::Error)]
391#[error("both \"data\" and \"input\" are set and not equal. Please use \"input\" to pass transaction call data")]
392#[non_exhaustive]
393pub struct TransactionInputError;
394/// Represents a reverted transaction and its output data.
395///
396/// Displays "execution reverted(: reason)?" if the reason is a string.
397#[derive(Debug, Clone)]
398pub struct RevertError {
399    /// The transaction output data
400    ///
401    /// Note: this is `None` if output was empty
402    output: Option<Bytes>,
403}
404
405// === impl RevertError ==
406
407impl RevertError {
408    /// Wraps the output bytes
409    ///
410    /// Note: this is intended to wrap an revm output
411    pub fn new(output: Bytes) -> Self {
412        if output.is_empty() {
413            Self { output: None }
414        } else {
415            Self {
416                output: Some(output),
417            }
418        }
419    }
420
421    const fn error_code(&self) -> i32 { EthRpcErrorCode::ExecutionError.code() }
422}
423
424impl std::fmt::Display for RevertError {
425    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
426        f.write_str("execution reverted")?;
427        if let Some(reason) = self
428            .output
429            .as_ref()
430            .and_then(|bytes| decode_revert_reason(bytes))
431        {
432            write!(f, ": {reason}")?;
433        }
434        Ok(())
435    }
436}
437
438impl std::error::Error for RevertError {}
439
440/// A helper error type that's mainly used to mirror `geth` Txpool's error
441/// messages
442#[derive(Debug, thiserror::Error)]
443pub enum RpcPoolError {
444    /// When the transaction is already known
445    #[error("already known")]
446    AlreadyKnown,
447    /// When the sender is invalid
448    #[error("invalid sender")]
449    InvalidSender,
450    /// When the transaction is underpriced
451    #[error("transaction underpriced")]
452    Underpriced,
453    /// When the transaction pool is full
454    #[error("txpool is full")]
455    TxPoolOverflow,
456    /// When the replacement transaction is underpriced
457    #[error("replacement transaction underpriced")]
458    ReplaceUnderpriced,
459    /// When the transaction exceeds the block gas limit
460    #[error("exceeds block gas limit")]
461    ExceedsGasLimit,
462    /// When a negative value is encountered
463    #[error("negative value")]
464    NegativeValue,
465    /// When oversized data is encountered
466    #[error("oversized data")]
467    OversizedData,
468    /// When the max initcode size is exceeded
469    #[error("max initcode size exceeded")]
470    ExceedsMaxInitCodeSize,
471    /// Errors related to invalid transactions
472    #[error(transparent)]
473    Invalid(#[from] RpcInvalidTransactionError),
474    /// Custom pool error
475    // #[error(transparent)]
476    // PoolTransactionError(Box<dyn PoolTransactionError>),
477    /// Eip-4844 related error
478    // #[error(transparent)]
479    // Eip4844(#[from] Eip4844PoolTransactionError),
480    /// Thrown if a conflicting transaction type is already in the pool
481    ///
482    /// In other words, thrown if a transaction with the same sender that
483    /// violates the exclusivity constraint (blob vs normal tx)
484    #[error("address already reserved")]
485    AddressAlreadyReserved,
486    /// Other unspecified error
487    #[error(transparent)]
488    Other(Box<dyn std::error::Error + Send + Sync>),
489}
490
491impl From<RpcPoolError> for JsonRpcError {
492    fn from(e: RpcPoolError) -> Self {
493        match e {
494            RpcPoolError::Invalid(err) => err.into(),
495            error => JsonRpcError {
496                code: ErrorCode::InternalError,
497                message: error.to_string(),
498                data: None,
499            },
500        }
501    }
502}