reth_rpc_eth_types/error/
mod.rs

1//! Implementation specific Errors for the `eth_` namespace.
2
3pub mod api;
4use crate::error::api::FromEvmHalt;
5use alloy_eips::BlockId;
6use alloy_primitives::{Address, Bytes, U256};
7use alloy_rpc_types_eth::{error::EthRpcErrorCode, request::TransactionInputError, BlockError};
8use alloy_sol_types::{ContractError, RevertReason};
9pub use api::{AsEthApiError, FromEthApiError, FromEvmError, IntoEthApiError};
10use core::time::Duration;
11use reth_errors::{BlockExecutionError, RethError};
12use reth_primitives_traits::transaction::{error::InvalidTransactionError, signed::RecoveryError};
13use reth_rpc_server_types::result::{
14    block_id_to_str, internal_rpc_err, invalid_params_rpc_err, rpc_err, rpc_error_with_code,
15};
16use reth_transaction_pool::error::{
17    Eip4844PoolTransactionError, Eip7702PoolTransactionError, InvalidPoolTransactionError,
18    PoolError, PoolErrorKind, PoolTransactionError,
19};
20use revm::context_interface::result::{
21    EVMError, ExecutionResult, HaltReason, InvalidHeader, InvalidTransaction, OutOfGasError,
22};
23use revm_inspectors::tracing::MuxError;
24use std::convert::Infallible;
25use tracing::error;
26
27/// A trait to convert an error to an RPC error.
28pub trait ToRpcError: core::error::Error + Send + Sync + 'static {
29    /// Converts the error to a JSON-RPC error object.
30    fn to_rpc_error(&self) -> jsonrpsee_types::ErrorObject<'static>;
31}
32
33impl ToRpcError for jsonrpsee_types::ErrorObject<'static> {
34    fn to_rpc_error(&self) -> jsonrpsee_types::ErrorObject<'static> {
35        self.clone()
36    }
37}
38
39/// Result alias
40pub type EthResult<T> = Result<T, EthApiError>;
41
42/// Errors that can occur when interacting with the `eth_` namespace
43#[derive(Debug, thiserror::Error)]
44pub enum EthApiError {
45    /// When a raw transaction is empty
46    #[error("empty transaction data")]
47    EmptyRawTransactionData,
48    /// When decoding a signed transaction fails
49    #[error("failed to decode signed transaction")]
50    FailedToDecodeSignedTransaction,
51    /// When the transaction signature is invalid
52    #[error("invalid transaction signature")]
53    InvalidTransactionSignature,
54    /// Errors related to the transaction pool
55    #[error(transparent)]
56    PoolError(RpcPoolError),
57    /// Header not found for block hash/number/tag
58    #[error("header not found")]
59    HeaderNotFound(BlockId),
60    /// Header range not found for start block hash/number/tag to end block hash/number/tag
61    #[error("header range not found, start block {0:?}, end block {1:?}")]
62    HeaderRangeNotFound(BlockId, BlockId),
63    /// Receipts not found for block hash/number/tag
64    #[error("receipts not found")]
65    ReceiptsNotFound(BlockId),
66    /// Thrown when an unknown block or transaction index is encountered
67    #[error("unknown block or tx index")]
68    UnknownBlockOrTxIndex,
69    /// When an invalid block range is provided
70    #[error("invalid block range")]
71    InvalidBlockRange,
72    /// Thrown when the target block for proof computation exceeds the maximum configured window.
73    #[error("distance to target block exceeds maximum proof window")]
74    ExceedsMaxProofWindow,
75    /// An internal error where prevrandao is not set in the evm's environment
76    #[error("prevrandao not in the EVM's environment after merge")]
77    PrevrandaoNotSet,
78    /// `excess_blob_gas` is not set for Cancun and above
79    #[error("excess blob gas missing in the EVM's environment after Cancun")]
80    ExcessBlobGasNotSet,
81    /// Thrown when a call or transaction request (`eth_call`, `eth_estimateGas`,
82    /// `eth_sendTransaction`) contains conflicting fields (legacy, EIP-1559)
83    #[error("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified")]
84    ConflictingFeeFieldsInRequest,
85    /// Errors related to invalid transactions
86    #[error(transparent)]
87    InvalidTransaction(#[from] RpcInvalidTransactionError),
88    /// Thrown when constructing an RPC block from primitive block data fails
89    #[error(transparent)]
90    InvalidBlockData(#[from] BlockError),
91    /// Thrown when an `AccountOverride` contains conflicting `state` and `stateDiff` fields
92    #[error("account {0:?} has both 'state' and 'stateDiff'")]
93    BothStateAndStateDiffInOverride(Address),
94    /// Other internal error
95    #[error(transparent)]
96    Internal(RethError),
97    /// Error related to signing
98    #[error(transparent)]
99    Signing(#[from] SignError),
100    /// Thrown when a requested transaction is not found
101    #[error("transaction not found")]
102    TransactionNotFound,
103    /// Some feature is unsupported
104    #[error("unsupported")]
105    Unsupported(&'static str),
106    /// General purpose error for invalid params
107    #[error("{0}")]
108    InvalidParams(String),
109    /// When the tracer config does not match the tracer
110    #[error("invalid tracer config")]
111    InvalidTracerConfig,
112    /// When the percentile array is invalid
113    #[error("invalid reward percentiles")]
114    InvalidRewardPercentiles,
115    /// Error thrown when a spawned blocking task failed to deliver an anticipated response.
116    ///
117    /// This only happens if the blocking task panics and is aborted before it can return a
118    /// response back to the request handler.
119    #[error("internal blocking task error")]
120    InternalBlockingTaskError,
121    /// Error thrown when a spawned blocking task failed to deliver an anticipated response
122    #[error("internal eth error")]
123    InternalEthError,
124    /// Error thrown when a (tracing) call exceeds the configured timeout
125    #[error("execution aborted (timeout = {0:?})")]
126    ExecutionTimedOut(Duration),
127    /// Internal Error thrown by the javascript tracer
128    #[error("{0}")]
129    InternalJsTracerError(String),
130    #[error(transparent)]
131    /// Call Input error when both `data` and `input` fields are set and not equal.
132    TransactionInputError(#[from] TransactionInputError),
133    /// Evm generic purpose error.
134    #[error("Revm error: {0}")]
135    EvmCustom(String),
136    /// Bytecode override is invalid.
137    ///
138    /// This can happen if bytecode provided in an
139    /// [`AccountOverride`](alloy_rpc_types_eth::state::AccountOverride) is malformed, e.g. invalid
140    /// 7702 bytecode.
141    #[error("Invalid bytecode: {0}")]
142    InvalidBytecode(String),
143    /// Error encountered when converting a transaction type
144    #[error("Transaction conversion error")]
145    TransactionConversionError,
146    /// Error thrown when tracing with a muxTracer fails
147    #[error(transparent)]
148    MuxTracerError(#[from] MuxError),
149    /// Any other error
150    #[error("{0}")]
151    Other(Box<dyn ToRpcError>),
152}
153
154impl EthApiError {
155    /// crates a new [`EthApiError::Other`] variant.
156    pub fn other<E: ToRpcError>(err: E) -> Self {
157        Self::Other(Box::new(err))
158    }
159
160    /// Returns `true` if error is [`RpcInvalidTransactionError::GasTooHigh`]
161    pub const fn is_gas_too_high(&self) -> bool {
162        matches!(self, Self::InvalidTransaction(RpcInvalidTransactionError::GasTooHigh))
163    }
164
165    /// Returns `true` if error is [`RpcInvalidTransactionError::GasTooLow`]
166    pub const fn is_gas_too_low(&self) -> bool {
167        matches!(self, Self::InvalidTransaction(RpcInvalidTransactionError::GasTooLow))
168    }
169}
170
171impl From<EthApiError> for jsonrpsee_types::error::ErrorObject<'static> {
172    fn from(error: EthApiError) -> Self {
173        match error {
174            EthApiError::FailedToDecodeSignedTransaction |
175            EthApiError::InvalidTransactionSignature |
176            EthApiError::EmptyRawTransactionData |
177            EthApiError::InvalidBlockRange |
178            EthApiError::ExceedsMaxProofWindow |
179            EthApiError::ConflictingFeeFieldsInRequest |
180            EthApiError::Signing(_) |
181            EthApiError::BothStateAndStateDiffInOverride(_) |
182            EthApiError::InvalidTracerConfig |
183            EthApiError::TransactionConversionError |
184            EthApiError::InvalidRewardPercentiles |
185            EthApiError::InvalidBytecode(_) => invalid_params_rpc_err(error.to_string()),
186            EthApiError::InvalidTransaction(err) => err.into(),
187            EthApiError::PoolError(err) => err.into(),
188            EthApiError::PrevrandaoNotSet |
189            EthApiError::ExcessBlobGasNotSet |
190            EthApiError::InvalidBlockData(_) |
191            EthApiError::Internal(_) |
192            EthApiError::EvmCustom(_) => internal_rpc_err(error.to_string()),
193            EthApiError::UnknownBlockOrTxIndex | EthApiError::TransactionNotFound => {
194                rpc_error_with_code(EthRpcErrorCode::ResourceNotFound.code(), error.to_string())
195            }
196            // TODO(onbjerg): We rewrite the error message here because op-node does string matching
197            // on the error message.
198            //
199            // Until https://github.com/ethereum-optimism/optimism/pull/11759 is released, this must be kept around.
200            EthApiError::HeaderNotFound(id) => rpc_error_with_code(
201                EthRpcErrorCode::ResourceNotFound.code(),
202                format!("block not found: {}", block_id_to_str(id)),
203            ),
204            EthApiError::ReceiptsNotFound(id) => rpc_error_with_code(
205                EthRpcErrorCode::ResourceNotFound.code(),
206                format!("{error}: {}", block_id_to_str(id)),
207            ),
208            EthApiError::HeaderRangeNotFound(start_id, end_id) => rpc_error_with_code(
209                EthRpcErrorCode::ResourceNotFound.code(),
210                format!(
211                    "{error}: start block: {}, end block: {}",
212                    block_id_to_str(start_id),
213                    block_id_to_str(end_id),
214                ),
215            ),
216            EthApiError::Unsupported(msg) => internal_rpc_err(msg),
217            EthApiError::InternalJsTracerError(msg) => internal_rpc_err(msg),
218            EthApiError::InvalidParams(msg) => invalid_params_rpc_err(msg),
219            err @ EthApiError::ExecutionTimedOut(_) => rpc_error_with_code(
220                jsonrpsee_types::error::CALL_EXECUTION_FAILED_CODE,
221                err.to_string(),
222            ),
223            err @ (EthApiError::InternalBlockingTaskError | EthApiError::InternalEthError) => {
224                internal_rpc_err(err.to_string())
225            }
226            err @ EthApiError::TransactionInputError(_) => invalid_params_rpc_err(err.to_string()),
227            EthApiError::Other(err) => err.to_rpc_error(),
228            EthApiError::MuxTracerError(msg) => internal_rpc_err(msg.to_string()),
229        }
230    }
231}
232
233#[cfg(feature = "js-tracer")]
234impl From<revm_inspectors::tracing::js::JsInspectorError> for EthApiError {
235    fn from(error: revm_inspectors::tracing::js::JsInspectorError) -> Self {
236        match error {
237            err @ revm_inspectors::tracing::js::JsInspectorError::JsError(_) => {
238                Self::InternalJsTracerError(err.to_string())
239            }
240            err => Self::InvalidParams(err.to_string()),
241        }
242    }
243}
244
245impl From<RethError> for EthApiError {
246    fn from(error: RethError) -> Self {
247        match error {
248            RethError::Provider(err) => err.into(),
249            err => Self::Internal(err),
250        }
251    }
252}
253
254impl From<BlockExecutionError> for EthApiError {
255    fn from(error: BlockExecutionError) -> Self {
256        Self::Internal(error.into())
257    }
258}
259
260impl From<reth_errors::ProviderError> for EthApiError {
261    fn from(error: reth_errors::ProviderError) -> Self {
262        use reth_errors::ProviderError;
263        match error {
264            ProviderError::HeaderNotFound(hash) => Self::HeaderNotFound(hash.into()),
265            ProviderError::BlockHashNotFound(hash) | ProviderError::UnknownBlockHash(hash) => {
266                Self::HeaderNotFound(hash.into())
267            }
268            ProviderError::BestBlockNotFound => Self::HeaderNotFound(BlockId::latest()),
269            ProviderError::BlockNumberForTransactionIndexNotFound => Self::UnknownBlockOrTxIndex,
270            ProviderError::TotalDifficultyNotFound(num) => Self::HeaderNotFound(num.into()),
271            ProviderError::FinalizedBlockNotFound => Self::HeaderNotFound(BlockId::finalized()),
272            ProviderError::SafeBlockNotFound => Self::HeaderNotFound(BlockId::safe()),
273            err => Self::Internal(err.into()),
274        }
275    }
276}
277
278impl From<InvalidHeader> for EthApiError {
279    fn from(value: InvalidHeader) -> Self {
280        match value {
281            InvalidHeader::ExcessBlobGasNotSet => Self::ExcessBlobGasNotSet,
282            InvalidHeader::PrevrandaoNotSet => Self::PrevrandaoNotSet,
283        }
284    }
285}
286
287impl<T> From<EVMError<T, InvalidTransaction>> for EthApiError
288where
289    T: Into<Self>,
290{
291    fn from(err: EVMError<T, InvalidTransaction>) -> Self {
292        match err {
293            EVMError::Transaction(invalid_tx) => match invalid_tx {
294                InvalidTransaction::NonceTooLow { tx, state } => {
295                    Self::InvalidTransaction(RpcInvalidTransactionError::NonceTooLow { tx, state })
296                }
297                _ => RpcInvalidTransactionError::from(invalid_tx).into(),
298            },
299            EVMError::Header(err) => err.into(),
300            EVMError::Database(err) => err.into(),
301            EVMError::Custom(err) => Self::EvmCustom(err),
302        }
303    }
304}
305
306impl From<RecoveryError> for EthApiError {
307    fn from(_: RecoveryError) -> Self {
308        Self::InvalidTransactionSignature
309    }
310}
311
312impl From<Infallible> for EthApiError {
313    fn from(_: Infallible) -> Self {
314        unreachable!()
315    }
316}
317
318/// An error due to invalid transaction.
319///
320/// The only reason this exists is to maintain compatibility with other clients de-facto standard
321/// error messages.
322///
323/// These error variants can be thrown when the transaction is checked prior to execution.
324///
325/// These variants also cover all errors that can be thrown by revm.
326///
327/// ## Nomenclature
328///
329/// This type is explicitly modeled after geth's error variants and uses
330///   `fee cap` for `max_fee_per_gas`
331///   `tip` for `max_priority_fee_per_gas`
332#[derive(thiserror::Error, Debug)]
333pub enum RpcInvalidTransactionError {
334    /// returned if the nonce of a transaction is lower than the one present in the local chain.
335    #[error("nonce too low: next nonce {state}, tx nonce {tx}")]
336    NonceTooLow {
337        /// The nonce of the transaction.
338        tx: u64,
339        /// The current state of the nonce in the local chain.
340        state: u64,
341    },
342    /// returned if the nonce of a transaction is higher than the next one expected based on the
343    /// local chain.
344    #[error("nonce too high")]
345    NonceTooHigh,
346    /// Returned if the nonce of a transaction is too high
347    /// Incrementing the nonce would lead to invalid state (overflow)
348    #[error("nonce has max value")]
349    NonceMaxValue,
350    /// thrown if the transaction sender doesn't have enough funds for a transfer
351    #[error("insufficient funds for transfer")]
352    InsufficientFundsForTransfer,
353    /// thrown if creation transaction provides the init code bigger than init code size limit.
354    #[error("max initcode size exceeded")]
355    MaxInitCodeSizeExceeded,
356    /// Represents the inability to cover max fee + value (account balance too low).
357    #[error("insufficient funds for gas * price + value: have {balance} want {cost}")]
358    InsufficientFunds {
359        /// Transaction cost.
360        cost: U256,
361        /// Current balance of transaction sender.
362        balance: U256,
363    },
364    /// This is similar to [`Self::InsufficientFunds`] but with a different error message and
365    /// exists for compatibility reasons.
366    ///
367    /// This error is used in `eth_estimateCall` when the highest available gas limit, capped with
368    /// the allowance of the caller is too low: [`Self::GasTooLow`].
369    #[error("gas required exceeds allowance ({gas_limit})")]
370    GasRequiredExceedsAllowance {
371        /// The gas limit the transaction was executed with.
372        gas_limit: u64,
373    },
374    /// Thrown when calculating gas usage
375    #[error("gas uint64 overflow")]
376    GasUintOverflow,
377    /// Thrown if the transaction is specified to use less gas than required to start the
378    /// invocation.
379    #[error("intrinsic gas too low")]
380    GasTooLow,
381    /// Thrown if the transaction gas exceeds the limit
382    #[error("intrinsic gas too high")]
383    GasTooHigh,
384    /// Thrown if a transaction is not supported in the current network configuration.
385    #[error("transaction type not supported")]
386    TxTypeNotSupported,
387    /// Thrown to ensure no one is able to specify a transaction with a tip higher than the total
388    /// fee cap.
389    #[error("max priority fee per gas higher than max fee per gas")]
390    TipAboveFeeCap,
391    /// A sanity error to avoid huge numbers specified in the tip field.
392    #[error("max priority fee per gas higher than 2^256-1")]
393    TipVeryHigh,
394    /// A sanity error to avoid huge numbers specified in the fee cap field.
395    #[error("max fee per gas higher than 2^256-1")]
396    FeeCapVeryHigh,
397    /// Thrown post London if the transaction's fee is less than the base fee of the block
398    #[error("max fee per gas less than block base fee")]
399    FeeCapTooLow,
400    /// Thrown if the sender of a transaction is a contract.
401    #[error("sender is not an EOA")]
402    SenderNoEOA,
403    /// Gas limit was exceeded during execution.
404    /// Contains the gas limit.
405    #[error("out of gas: gas required exceeds: {0}")]
406    BasicOutOfGas(u64),
407    /// Gas limit was exceeded during memory expansion.
408    /// Contains the gas limit.
409    #[error("out of gas: gas exhausted during memory expansion: {0}")]
410    MemoryOutOfGas(u64),
411    /// Gas limit was exceeded during precompile execution.
412    /// Contains the gas limit.
413    #[error("out of gas: gas exhausted during precompiled contract execution: {0}")]
414    PrecompileOutOfGas(u64),
415    /// An operand to an opcode was invalid or out of range.
416    /// Contains the gas limit.
417    #[error("out of gas: invalid operand to an opcode: {0}")]
418    InvalidOperandOutOfGas(u64),
419    /// Thrown if executing a transaction failed during estimate/call
420    #[error(transparent)]
421    Revert(RevertError),
422    /// Unspecific EVM halt error.
423    #[error("EVM error: {0:?}")]
424    EvmHalt(HaltReason),
425    /// Invalid chain id set for the transaction.
426    #[error("invalid chain ID")]
427    InvalidChainId,
428    /// The transaction is before Spurious Dragon and has a chain ID
429    #[error("transactions before Spurious Dragon should not have a chain ID")]
430    OldLegacyChainId,
431    /// The transitions is before Berlin and has access list
432    #[error("transactions before Berlin should not have access list")]
433    AccessListNotSupported,
434    /// `max_fee_per_blob_gas` is not supported for blocks before the Cancun hardfork.
435    #[error("max_fee_per_blob_gas is not supported for blocks before the Cancun hardfork")]
436    MaxFeePerBlobGasNotSupported,
437    /// `blob_hashes`/`blob_versioned_hashes` is not supported for blocks before the Cancun
438    /// hardfork.
439    #[error("blob_versioned_hashes is not supported for blocks before the Cancun hardfork")]
440    BlobVersionedHashesNotSupported,
441    /// Block `blob_base_fee` is greater than tx-specified `max_fee_per_blob_gas` after Cancun.
442    #[error("max fee per blob gas less than block blob gas fee")]
443    BlobFeeCapTooLow,
444    /// Blob transaction has a versioned hash with an invalid blob
445    #[error("blob hash version mismatch")]
446    BlobHashVersionMismatch,
447    /// Blob transaction has no versioned hashes
448    #[error("blob transaction missing blob hashes")]
449    BlobTransactionMissingBlobHashes,
450    /// Blob transaction has too many blobs
451    #[error("blob transaction exceeds max blobs per block; got {have}")]
452    TooManyBlobs {
453        /// The number of blobs in the transaction.
454        have: usize,
455    },
456    /// Blob transaction is a create transaction
457    #[error("blob transaction is a create transaction")]
458    BlobTransactionIsCreate,
459    /// EOF crate should have `to` address
460    #[error("EOF crate should have `to` address")]
461    EofCrateShouldHaveToAddress,
462    /// EIP-7702 is not enabled.
463    #[error("EIP-7702 authorization list not supported")]
464    AuthorizationListNotSupported,
465    /// EIP-7702 transaction has invalid fields set.
466    #[error("EIP-7702 authorization list has invalid fields")]
467    AuthorizationListInvalidFields,
468    /// Any other error
469    #[error("{0}")]
470    Other(Box<dyn ToRpcError>),
471}
472
473impl RpcInvalidTransactionError {
474    /// crates a new [`RpcInvalidTransactionError::Other`] variant.
475    pub fn other<E: ToRpcError>(err: E) -> Self {
476        Self::Other(Box::new(err))
477    }
478}
479
480impl RpcInvalidTransactionError {
481    /// Returns the rpc error code for this error.
482    pub const fn error_code(&self) -> i32 {
483        match self {
484            Self::InvalidChainId |
485            Self::GasTooLow |
486            Self::GasTooHigh |
487            Self::GasRequiredExceedsAllowance { .. } => EthRpcErrorCode::InvalidInput.code(),
488            Self::Revert(_) => EthRpcErrorCode::ExecutionError.code(),
489            _ => EthRpcErrorCode::TransactionRejected.code(),
490        }
491    }
492
493    /// Converts the halt error
494    ///
495    /// Takes the configured gas limit of the transaction which is attached to the error
496    pub const fn halt(reason: HaltReason, gas_limit: u64) -> Self {
497        match reason {
498            HaltReason::OutOfGas(err) => Self::out_of_gas(err, gas_limit),
499            HaltReason::NonceOverflow => Self::NonceMaxValue,
500            err => Self::EvmHalt(err),
501        }
502    }
503
504    /// Converts the out of gas error
505    pub const fn out_of_gas(reason: OutOfGasError, gas_limit: u64) -> Self {
506        match reason {
507            OutOfGasError::Basic | OutOfGasError::ReentrancySentry => {
508                Self::BasicOutOfGas(gas_limit)
509            }
510            OutOfGasError::Memory | OutOfGasError::MemoryLimit => Self::MemoryOutOfGas(gas_limit),
511            OutOfGasError::Precompile => Self::PrecompileOutOfGas(gas_limit),
512            OutOfGasError::InvalidOperand => Self::InvalidOperandOutOfGas(gas_limit),
513        }
514    }
515}
516
517impl From<RpcInvalidTransactionError> for jsonrpsee_types::error::ErrorObject<'static> {
518    fn from(err: RpcInvalidTransactionError) -> Self {
519        match err {
520            RpcInvalidTransactionError::Revert(revert) => {
521                // include out data if some
522                rpc_err(
523                    revert.error_code(),
524                    revert.to_string(),
525                    revert.output.as_ref().map(|out| out.as_ref()),
526                )
527            }
528            RpcInvalidTransactionError::Other(err) => err.to_rpc_error(),
529            err => rpc_err(err.error_code(), err.to_string(), None),
530        }
531    }
532}
533
534impl From<InvalidTransaction> for RpcInvalidTransactionError {
535    fn from(err: InvalidTransaction) -> Self {
536        match err {
537            InvalidTransaction::InvalidChainId => Self::InvalidChainId,
538            InvalidTransaction::PriorityFeeGreaterThanMaxFee => Self::TipAboveFeeCap,
539            InvalidTransaction::GasPriceLessThanBasefee => Self::FeeCapTooLow,
540            InvalidTransaction::CallerGasLimitMoreThanBlock => {
541                // tx.gas > block.gas_limit
542                Self::GasTooHigh
543            }
544            InvalidTransaction::CallGasCostMoreThanGasLimit { .. } => {
545                // tx.gas < cost
546                Self::GasTooLow
547            }
548            InvalidTransaction::GasFloorMoreThanGasLimit { .. } => {
549                // Post prague EIP-7623 tx floor calldata gas cost > tx.gas_limit
550                // where floor gas is the minimum amount of gas that will be spent
551                // In other words, the tx's gas limit is lower that the minimum gas requirements of
552                // the tx's calldata
553                Self::GasTooLow
554            }
555            InvalidTransaction::RejectCallerWithCode => Self::SenderNoEOA,
556            InvalidTransaction::LackOfFundForMaxFee { fee, balance } => {
557                Self::InsufficientFunds { cost: *fee, balance: *balance }
558            }
559            InvalidTransaction::OverflowPaymentInTransaction => Self::GasUintOverflow,
560            InvalidTransaction::NonceOverflowInTransaction => Self::NonceMaxValue,
561            InvalidTransaction::CreateInitCodeSizeLimit => Self::MaxInitCodeSizeExceeded,
562            InvalidTransaction::NonceTooHigh { .. } => Self::NonceTooHigh,
563            InvalidTransaction::NonceTooLow { tx, state } => Self::NonceTooLow { tx, state },
564            InvalidTransaction::AccessListNotSupported => Self::AccessListNotSupported,
565            InvalidTransaction::MaxFeePerBlobGasNotSupported => Self::MaxFeePerBlobGasNotSupported,
566            InvalidTransaction::BlobVersionedHashesNotSupported => {
567                Self::BlobVersionedHashesNotSupported
568            }
569            InvalidTransaction::BlobGasPriceGreaterThanMax => Self::BlobFeeCapTooLow,
570            InvalidTransaction::EmptyBlobs => Self::BlobTransactionMissingBlobHashes,
571            InvalidTransaction::BlobVersionNotSupported => Self::BlobHashVersionMismatch,
572            InvalidTransaction::TooManyBlobs { have, .. } => Self::TooManyBlobs { have },
573            InvalidTransaction::BlobCreateTransaction => Self::BlobTransactionIsCreate,
574            InvalidTransaction::EofCreateShouldHaveToAddress => Self::EofCrateShouldHaveToAddress,
575            InvalidTransaction::AuthorizationListNotSupported => {
576                Self::AuthorizationListNotSupported
577            }
578            InvalidTransaction::AuthorizationListInvalidFields |
579            InvalidTransaction::EmptyAuthorizationList => Self::AuthorizationListInvalidFields,
580            InvalidTransaction::Eip2930NotSupported |
581            InvalidTransaction::Eip1559NotSupported |
582            InvalidTransaction::Eip4844NotSupported |
583            InvalidTransaction::Eip7702NotSupported |
584            InvalidTransaction::Eip7873NotSupported => Self::TxTypeNotSupported,
585            InvalidTransaction::Eip7873MissingTarget => {
586                Self::other(internal_rpc_err(err.to_string()))
587            }
588        }
589    }
590}
591
592impl From<InvalidTransactionError> for RpcInvalidTransactionError {
593    fn from(err: InvalidTransactionError) -> Self {
594        use InvalidTransactionError;
595        // This conversion is used to convert any transaction errors that could occur inside the
596        // txpool (e.g. `eth_sendRawTransaction`) to their corresponding RPC
597        match err {
598            InvalidTransactionError::InsufficientFunds(res) => {
599                Self::InsufficientFunds { cost: res.expected, balance: res.got }
600            }
601            InvalidTransactionError::NonceNotConsistent { tx, state } => {
602                Self::NonceTooLow { tx, state }
603            }
604            InvalidTransactionError::OldLegacyChainId => {
605                // Note: this should be unreachable since Spurious Dragon now enabled
606                Self::OldLegacyChainId
607            }
608            InvalidTransactionError::ChainIdMismatch => Self::InvalidChainId,
609            InvalidTransactionError::Eip2930Disabled |
610            InvalidTransactionError::Eip1559Disabled |
611            InvalidTransactionError::Eip4844Disabled |
612            InvalidTransactionError::Eip7702Disabled |
613            InvalidTransactionError::TxTypeNotSupported => Self::TxTypeNotSupported,
614            InvalidTransactionError::GasUintOverflow => Self::GasUintOverflow,
615            InvalidTransactionError::GasTooLow => Self::GasTooLow,
616            InvalidTransactionError::GasTooHigh => Self::GasTooHigh,
617            InvalidTransactionError::TipAboveFeeCap => Self::TipAboveFeeCap,
618            InvalidTransactionError::FeeCapTooLow => Self::FeeCapTooLow,
619            InvalidTransactionError::SignerAccountHasBytecode => Self::SenderNoEOA,
620        }
621    }
622}
623
624/// Represents a reverted transaction and its output data.
625///
626/// Displays "execution reverted(: reason)?" if the reason is a string.
627#[derive(Debug, Clone, thiserror::Error)]
628pub struct RevertError {
629    /// The transaction output data
630    ///
631    /// Note: this is `None` if output was empty
632    output: Option<Bytes>,
633}
634
635// === impl RevertError ==
636
637impl RevertError {
638    /// Wraps the output bytes
639    ///
640    /// Note: this is intended to wrap an revm output
641    pub fn new(output: Bytes) -> Self {
642        if output.is_empty() {
643            Self { output: None }
644        } else {
645            Self { output: Some(output) }
646        }
647    }
648
649    /// Returns error code to return for this error.
650    pub const fn error_code(&self) -> i32 {
651        EthRpcErrorCode::ExecutionError.code()
652    }
653}
654
655impl std::fmt::Display for RevertError {
656    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
657        f.write_str("execution reverted")?;
658        if let Some(reason) = self.output.as_ref().and_then(|out| RevertReason::decode(out)) {
659            let error = reason.to_string();
660            let mut error = error.as_str();
661            if matches!(reason, RevertReason::ContractError(ContractError::Revert(_))) {
662                // we strip redundant `revert: ` prefix from the revert reason
663                error = error.trim_start_matches("revert: ");
664            }
665            write!(f, ": {error}")?;
666        }
667        Ok(())
668    }
669}
670
671/// A helper error type that's mainly used to mirror `geth` Txpool's error messages
672#[derive(Debug, thiserror::Error)]
673pub enum RpcPoolError {
674    /// When the transaction is already known
675    #[error("already known")]
676    AlreadyKnown,
677    /// When the sender is invalid
678    #[error("invalid sender")]
679    InvalidSender,
680    /// When the transaction is underpriced
681    #[error("transaction underpriced")]
682    Underpriced,
683    /// When the transaction pool is full
684    #[error("txpool is full")]
685    TxPoolOverflow,
686    /// When the replacement transaction is underpriced
687    #[error("replacement transaction underpriced")]
688    ReplaceUnderpriced,
689    /// When the transaction exceeds the block gas limit
690    #[error("exceeds block gas limit")]
691    ExceedsGasLimit,
692    /// Thrown when a new transaction is added to the pool, but then immediately discarded to
693    /// respect the tx fee exceeds the configured cap
694    #[error("tx fee ({max_tx_fee_wei} wei) exceeds the configured cap ({tx_fee_cap_wei} wei)")]
695    ExceedsFeeCap {
696        /// max fee in wei of new tx submitted to the pull (e.g. 0.11534 ETH)
697        max_tx_fee_wei: u128,
698        /// configured tx fee cap in wei (e.g. 1.0 ETH)
699        tx_fee_cap_wei: u128,
700    },
701    /// When a negative value is encountered
702    #[error("negative value")]
703    NegativeValue,
704    /// When oversized data is encountered
705    #[error("oversized data")]
706    OversizedData,
707    /// When the max initcode size is exceeded
708    #[error("max initcode size exceeded")]
709    ExceedsMaxInitCodeSize,
710    /// Errors related to invalid transactions
711    #[error(transparent)]
712    Invalid(#[from] RpcInvalidTransactionError),
713    /// Custom pool error
714    #[error(transparent)]
715    PoolTransactionError(Box<dyn PoolTransactionError>),
716    /// EIP-4844 related error
717    #[error(transparent)]
718    Eip4844(#[from] Eip4844PoolTransactionError),
719    /// EIP-7702 related error
720    #[error(transparent)]
721    Eip7702(#[from] Eip7702PoolTransactionError),
722    /// Thrown if a conflicting transaction type is already in the pool
723    ///
724    /// In other words, thrown if a transaction with the same sender that violates the exclusivity
725    /// constraint (blob vs normal tx)
726    #[error("address already reserved")]
727    AddressAlreadyReserved,
728    /// Other unspecified error
729    #[error(transparent)]
730    Other(Box<dyn core::error::Error + Send + Sync>),
731}
732
733impl From<RpcPoolError> for jsonrpsee_types::error::ErrorObject<'static> {
734    fn from(error: RpcPoolError) -> Self {
735        match error {
736            RpcPoolError::Invalid(err) => err.into(),
737            RpcPoolError::TxPoolOverflow => {
738                rpc_error_with_code(EthRpcErrorCode::TransactionRejected.code(), error.to_string())
739            }
740            error => internal_rpc_err(error.to_string()),
741        }
742    }
743}
744
745impl From<PoolError> for RpcPoolError {
746    fn from(err: PoolError) -> Self {
747        match err.kind {
748            PoolErrorKind::ReplacementUnderpriced => Self::ReplaceUnderpriced,
749            PoolErrorKind::FeeCapBelowMinimumProtocolFeeCap(_) => Self::Underpriced,
750            PoolErrorKind::SpammerExceededCapacity(_) | PoolErrorKind::DiscardedOnInsert => {
751                Self::TxPoolOverflow
752            }
753            PoolErrorKind::InvalidTransaction(err) => err.into(),
754            PoolErrorKind::Other(err) => Self::Other(err),
755            PoolErrorKind::AlreadyImported => Self::AlreadyKnown,
756            PoolErrorKind::ExistingConflictingTransactionType(_, _) => Self::AddressAlreadyReserved,
757        }
758    }
759}
760
761impl From<InvalidPoolTransactionError> for RpcPoolError {
762    fn from(err: InvalidPoolTransactionError) -> Self {
763        match err {
764            InvalidPoolTransactionError::Consensus(err) => Self::Invalid(err.into()),
765            InvalidPoolTransactionError::ExceedsGasLimit(_, _) => Self::ExceedsGasLimit,
766            InvalidPoolTransactionError::ExceedsFeeCap { max_tx_fee_wei, tx_fee_cap_wei } => {
767                Self::ExceedsFeeCap { max_tx_fee_wei, tx_fee_cap_wei }
768            }
769            InvalidPoolTransactionError::ExceedsMaxInitCodeSize(_, _) => {
770                Self::ExceedsMaxInitCodeSize
771            }
772            InvalidPoolTransactionError::IntrinsicGasTooLow => {
773                Self::Invalid(RpcInvalidTransactionError::GasTooLow)
774            }
775            InvalidPoolTransactionError::OversizedData(_, _) => Self::OversizedData,
776            InvalidPoolTransactionError::Underpriced => Self::Underpriced,
777            InvalidPoolTransactionError::Other(err) => Self::PoolTransactionError(err),
778            InvalidPoolTransactionError::Eip4844(err) => Self::Eip4844(err),
779            InvalidPoolTransactionError::Eip7702(err) => Self::Eip7702(err),
780            InvalidPoolTransactionError::Overdraft { cost, balance } => {
781                Self::Invalid(RpcInvalidTransactionError::InsufficientFunds { cost, balance })
782            }
783        }
784    }
785}
786
787impl From<PoolError> for EthApiError {
788    fn from(err: PoolError) -> Self {
789        Self::PoolError(RpcPoolError::from(err))
790    }
791}
792
793/// Errors returned from a sign request.
794#[derive(Debug, thiserror::Error)]
795pub enum SignError {
796    /// Error occurred while trying to sign data.
797    #[error("could not sign")]
798    CouldNotSign,
799    /// Signer for requested account not found.
800    #[error("unknown account")]
801    NoAccount,
802    /// `TypedData` has invalid format.
803    #[error("given typed data is not valid")]
804    InvalidTypedData,
805    /// Invalid transaction request in `sign_transaction`.
806    #[error("invalid transaction request")]
807    InvalidTransactionRequest,
808    /// No chain ID was given.
809    #[error("no chainid")]
810    NoChainId,
811}
812
813/// Converts the evm [`ExecutionResult`] into a result where `Ok` variant is the output bytes if it
814/// is [`ExecutionResult::Success`].
815pub fn ensure_success<Halt, Error: FromEvmHalt<Halt> + FromEthApiError>(
816    result: ExecutionResult<Halt>,
817) -> Result<Bytes, Error> {
818    match result {
819        ExecutionResult::Success { output, .. } => Ok(output.into_data()),
820        ExecutionResult::Revert { output, .. } => {
821            Err(Error::from_eth_err(RpcInvalidTransactionError::Revert(RevertError::new(output))))
822        }
823        ExecutionResult::Halt { reason, gas_used } => Err(Error::from_evm_halt(reason, gas_used)),
824    }
825}
826
827#[cfg(test)]
828mod tests {
829    use super::*;
830    use alloy_sol_types::{Revert, SolError};
831    use revm::primitives::b256;
832
833    #[test]
834    fn timed_out_error() {
835        let err = EthApiError::ExecutionTimedOut(Duration::from_secs(10));
836        assert_eq!(err.to_string(), "execution aborted (timeout = 10s)");
837    }
838
839    #[test]
840    fn header_not_found_message() {
841        let err: jsonrpsee_types::error::ErrorObject<'static> =
842            EthApiError::HeaderNotFound(BlockId::hash(b256!(
843                "0x1a15e3c30cf094a99826869517b16d185d45831d3a494f01030b0001a9d3ebb9"
844            )))
845            .into();
846        assert_eq!(
847            err.message(),
848            "block not found: hash 0x1a15e3c30cf094a99826869517b16d185d45831d3a494f01030b0001a9d3ebb9"
849        );
850        let err: jsonrpsee_types::error::ErrorObject<'static> =
851            EthApiError::HeaderNotFound(BlockId::hash_canonical(b256!(
852                "0x1a15e3c30cf094a99826869517b16d185d45831d3a494f01030b0001a9d3ebb9"
853            )))
854            .into();
855        assert_eq!(
856            err.message(),
857            "block not found: canonical hash 0x1a15e3c30cf094a99826869517b16d185d45831d3a494f01030b0001a9d3ebb9"
858        );
859        let err: jsonrpsee_types::error::ErrorObject<'static> =
860            EthApiError::HeaderNotFound(BlockId::number(100000)).into();
861        assert_eq!(err.message(), "block not found: 0x186a0");
862        let err: jsonrpsee_types::error::ErrorObject<'static> =
863            EthApiError::HeaderNotFound(BlockId::latest()).into();
864        assert_eq!(err.message(), "block not found: latest");
865        let err: jsonrpsee_types::error::ErrorObject<'static> =
866            EthApiError::HeaderNotFound(BlockId::safe()).into();
867        assert_eq!(err.message(), "block not found: safe");
868        let err: jsonrpsee_types::error::ErrorObject<'static> =
869            EthApiError::HeaderNotFound(BlockId::finalized()).into();
870        assert_eq!(err.message(), "block not found: finalized");
871    }
872
873    #[test]
874    fn revert_err_display() {
875        let revert = Revert::from("test_revert_reason");
876        let err = RevertError::new(revert.abi_encode().into());
877        let msg = err.to_string();
878        assert_eq!(msg, "execution reverted: test_revert_reason");
879    }
880}