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