Skip to main content

anvil/eth/error/
mod.rs

1//! Aggregated error type for this module
2
3use alloy_consensus::crypto::RecoveryError;
4use alloy_evm::overrides::StateOverrideError;
5use alloy_primitives::{B256, Bytes, SignatureError, TxHash, U256};
6use alloy_rpc_types::BlockNumberOrTag;
7use alloy_signer::Error as SignerError;
8use alloy_transport::TransportError;
9use anvil_core::eth::wallet::WalletError;
10use anvil_rpc::{
11    error::{ErrorCode, RpcError},
12    response::ResponseResult,
13};
14use foundry_evm::{backend::DatabaseError, decode::RevertDecoder};
15use revm::{
16    context_interface::result::{EVMError, InvalidHeader, InvalidTransaction},
17    interpreter::InstructionResult,
18};
19use serde::Serialize;
20use tempo_revm::TempoInvalidTransaction;
21use tokio::time::Duration;
22
23#[cfg(feature = "optimism")]
24mod optimism;
25
26pub(crate) type Result<T> = std::result::Result<T, BlockchainError>;
27
28#[derive(Debug, thiserror::Error)]
29pub enum BlockchainError {
30    #[error(transparent)]
31    Pool(#[from] PoolError),
32    #[error("No signer available")]
33    NoSignerAvailable,
34    #[error("Chain Id not available")]
35    ChainIdNotAvailable,
36    #[error("Invalid input: `max_priority_fee_per_gas` greater than `max_fee_per_gas`")]
37    InvalidFeeInput,
38    #[error("Transaction data is empty")]
39    EmptyRawTransactionData,
40    #[error("Failed to decode signed transaction")]
41    FailedToDecodeSignedTransaction,
42    #[error("Failed to decode transaction")]
43    FailedToDecodeTransaction,
44    #[error("Failed to decode receipt")]
45    FailedToDecodeReceipt,
46    #[error("Cannot EIP-2718 encode transaction type 0x{0:x}")]
47    UnsupportedTransactionEncoding(u8),
48    #[error("Failed to decode state")]
49    FailedToDecodeStateDump,
50    #[error("Prevrandao not in the EVM's environment after merge")]
51    PrevrandaoNotSet,
52    #[error(transparent)]
53    SignatureError(#[from] SignatureError),
54    #[error(transparent)]
55    RecoveryError(#[from] RecoveryError),
56    #[error(transparent)]
57    SignerError(#[from] SignerError),
58    #[error("Rpc Endpoint not implemented")]
59    RpcUnimplemented,
60    #[error("Rpc error {0:?}")]
61    RpcError(RpcError),
62    #[error(transparent)]
63    InvalidTransaction(#[from] InvalidTransactionError),
64    #[error(transparent)]
65    FeeHistory(#[from] FeeHistoryError),
66    #[error(transparent)]
67    AlloyForkProvider(#[from] TransportError),
68    #[error("EVM error {0:?}")]
69    EvmError(InstructionResult),
70    #[error("Evm override error: {0}")]
71    EvmOverrideError(String),
72    #[error("Invalid url {0:?}")]
73    InvalidUrl(String),
74    #[error("unsupported fork network for chain {chain_id}: {reason}")]
75    UnsupportedForkNetwork { chain_id: u64, reason: &'static str },
76    #[error("Internal error: {0:?}")]
77    Internal(String),
78    #[error("BlockOutOfRangeError: block height is {0} but requested was {1}")]
79    BlockOutOfRange(u64, u64),
80    #[error("Resource not found")]
81    BlockNotFound,
82    #[error("unknown block")]
83    UnknownBlock,
84    /// Thrown when a requested transaction is not found
85    #[error("transaction not found")]
86    TransactionNotFound,
87    #[error("Required data unavailable")]
88    DataUnavailable,
89    #[error("Trie error: {0}")]
90    TrieError(String),
91    #[error("{0}")]
92    UintConversion(&'static str),
93    #[error("State override error: {0}")]
94    StateOverrideError(String),
95    #[error("Timestamp error: {0}")]
96    TimestampError(String),
97    #[error(transparent)]
98    DatabaseError(#[from] DatabaseError),
99    #[error(
100        "EIP-1559 style fee params (maxFeePerGas or maxPriorityFeePerGas) received but they are not supported by the current hardfork.\n\nYou can use them by running anvil with '--hardfork london' or later."
101    )]
102    EIP1559TransactionUnsupportedAtHardfork,
103    #[error(
104        "Access list received but is not supported by the current hardfork.\n\nYou can use it by running anvil with '--hardfork berlin' or later."
105    )]
106    EIP2930TransactionUnsupportedAtHardfork,
107    #[error(
108        "EIP-4844 fields received but is not supported by the current hardfork.\n\nYou can use it by running anvil with '--hardfork cancun' or later."
109    )]
110    EIP4844TransactionUnsupportedAtHardfork,
111    #[error(
112        "EIP-7702 fields received but is not supported by the current hardfork.\n\nYou can use it by running anvil with '--hardfork prague' or later."
113    )]
114    EIP7702TransactionUnsupportedAtHardfork,
115    #[error(
116        "op-stack deposit tx received but is not supported.\n\nYou can use it by running anvil with '--optimism'."
117    )]
118    DepositTransactionUnsupported,
119    #[error(
120        "tempo transaction received but is not supported.\n\nYou can use it by running anvil with '--tempo'."
121    )]
122    TempoTransactionUnsupported,
123    #[error("Unknown transaction type not supported")]
124    UnknownTransactionType,
125    #[error("Excess blob gas not set.")]
126    ExcessBlobGasNotSet,
127    #[error("{0}")]
128    Message(String),
129    #[error("Transaction {hash} was added to the mempool but wasn't confirmed within {duration:?}")]
130    TransactionConfirmationTimeout {
131        /// Hash of the transaction that timed out
132        hash: B256,
133        /// Duration that was waited before timing out
134        duration: Duration,
135    },
136    #[error("Invalid transaction request: {0}")]
137    InvalidTransactionRequest(String),
138    #[error("filter not found")]
139    FilterNotFound,
140}
141
142impl From<eyre::Report> for BlockchainError {
143    fn from(err: eyre::Report) -> Self {
144        Self::Message(err.to_string())
145    }
146}
147
148impl From<RpcError> for BlockchainError {
149    fn from(err: RpcError) -> Self {
150        Self::RpcError(err)
151    }
152}
153
154impl<T> From<EVMError<T>> for BlockchainError
155where
156    T: Into<Self>,
157{
158    fn from(err: EVMError<T>) -> Self {
159        match err {
160            EVMError::Transaction(err) => InvalidTransactionError::from(err).into(),
161            EVMError::Header(err) => match err {
162                InvalidHeader::ExcessBlobGasNotSet => Self::ExcessBlobGasNotSet,
163                InvalidHeader::PrevrandaoNotSet => Self::PrevrandaoNotSet,
164            },
165            EVMError::Database(err) => err.into(),
166            EVMError::Custom(err) => Self::Message(err),
167            EVMError::CustomAny(err) => Self::Message(err.to_string()),
168        }
169    }
170}
171
172impl<T> From<EVMError<T, TempoInvalidTransaction>> for BlockchainError
173where
174    T: Into<Self>,
175{
176    fn from(err: EVMError<T, TempoInvalidTransaction>) -> Self {
177        match err {
178            EVMError::Transaction(err) => match err {
179                TempoInvalidTransaction::EthInvalidTransaction(err) => {
180                    InvalidTransactionError::from(err).into()
181                }
182                err => Self::Message(format!("tempo transaction error: {err}")),
183            },
184            EVMError::Header(err) => match err {
185                InvalidHeader::ExcessBlobGasNotSet => Self::ExcessBlobGasNotSet,
186                InvalidHeader::PrevrandaoNotSet => Self::PrevrandaoNotSet,
187            },
188            EVMError::Database(err) => err.into(),
189            EVMError::Custom(err) => Self::Message(err),
190            EVMError::CustomAny(err) => Self::Message(err.to_string()),
191        }
192    }
193}
194
195impl From<WalletError> for BlockchainError {
196    fn from(value: WalletError) -> Self {
197        Self::Message(value.to_string())
198    }
199}
200
201impl<E> From<StateOverrideError<E>> for BlockchainError
202where
203    E: Into<Self>,
204{
205    fn from(value: StateOverrideError<E>) -> Self {
206        match value {
207            StateOverrideError::InvalidBytecode(err) => Self::StateOverrideError(err.to_string()),
208            StateOverrideError::BothStateAndStateDiff(addr) => Self::StateOverrideError(format!(
209                "state and state_diff can't be used together for account {addr}",
210            )),
211            StateOverrideError::Database(err) => err.into(),
212        }
213    }
214}
215
216/// Errors that can occur in the transaction pool
217#[derive(Debug, thiserror::Error)]
218pub enum PoolError {
219    #[error("Transaction with cyclic dependent transactions")]
220    CyclicTransaction,
221    /// Thrown if a replacement transaction's gas price is below the already imported transaction
222    #[error("Tx: [{0:?}] insufficient gas price to replace existing transaction")]
223    ReplacementUnderpriced(TxHash),
224    #[error("Tx: [{0:?}] already Imported")]
225    AlreadyImported(TxHash),
226}
227
228/// Errors that can occur with `eth_feeHistory`
229#[derive(Debug, thiserror::Error)]
230pub enum FeeHistoryError {
231    #[error("requested block range is out of bounds")]
232    InvalidBlockRange,
233    #[error("reward percentiles must be strictly increasing and between 0 and 100")]
234    InvalidRewardPercentiles,
235    #[error("could not find block number requested: {0}")]
236    BlockNotFound(BlockNumberOrTag),
237}
238
239#[derive(Debug)]
240pub struct ErrDetail {
241    pub detail: String,
242}
243
244/// An error due to invalid transaction
245#[derive(Debug, thiserror::Error)]
246pub enum InvalidTransactionError {
247    /// returned if the nonce of a transaction is lower than the one present in the local chain.
248    #[error("nonce too low")]
249    NonceTooLow,
250    /// returned if the nonce of a transaction is higher than the next one expected based on the
251    /// local chain.
252    #[error("Nonce too high")]
253    NonceTooHigh,
254    /// Returned if the nonce of a transaction is too high
255    /// Incrementing the nonce would lead to invalid state (overflow)
256    #[error("nonce has max value")]
257    NonceMaxValue,
258    /// thrown if the transaction sender doesn't have enough funds for a transfer
259    #[error("insufficient funds for transfer")]
260    InsufficientFundsForTransfer,
261    /// thrown if creation transaction provides the init code bigger than init code size limit.
262    #[error("max initcode size exceeded")]
263    MaxInitCodeSizeExceeded,
264    /// Represents the inability to cover max cost + value (account balance too low).
265    #[error("Insufficient funds for gas * price + value")]
266    InsufficientFunds,
267    /// Thrown when calculating gas usage
268    #[error("gas uint64 overflow")]
269    GasUintOverflow,
270    /// returned if the transaction is specified to use less gas than required to start the
271    /// invocation.
272    #[error("intrinsic gas too low")]
273    GasTooLow,
274    /// returned if the transaction gas exceeds the limit
275    #[error("intrinsic gas too high -- {}",.0.detail)]
276    GasTooHigh(ErrDetail),
277    /// Thrown to ensure no one is able to specify a transaction with a tip higher than the total
278    /// fee cap.
279    #[error("max priority fee per gas higher than max fee per gas")]
280    TipAboveFeeCap,
281    /// Thrown post London if the transaction's fee is less than the base fee of the block
282    #[error("max fee per gas less than block base fee")]
283    FeeCapTooLow,
284    /// Thrown during estimate if caller has insufficient funds to cover the tx.
285    #[error("Out of gas: gas required exceeds allowance: {0:?}")]
286    BasicOutOfGas(u128),
287    /// Thrown if executing a transaction failed during estimate/call
288    #[error("execution reverted: {0:?}")]
289    Revert(Option<Bytes>),
290    /// Thrown if the sender of a transaction is a contract.
291    #[error("sender not an eoa")]
292    SenderNoEOA,
293    /// Thrown when a tx was signed with a different chain_id
294    #[error("invalid chain id for signer")]
295    InvalidChainId,
296    /// Thrown when a legacy tx was signed for a different chain
297    #[error("Incompatible EIP-155 transaction, signed for another chain")]
298    IncompatibleEIP155,
299    /// Thrown when an access list is used before the berlin hard fork.
300    #[error("Access lists are not supported before the Berlin hardfork")]
301    AccessListNotSupported,
302    /// Thrown when the block's `blob_gas_price` is greater than tx-specified
303    /// `max_fee_per_blob_gas` after Cancun.
304    #[error("Block `blob_gas_price` is greater than tx-specified `max_fee_per_blob_gas`")]
305    BlobFeeCapTooLow(u128, u128),
306    /// Thrown when we receive a tx with `blob_versioned_hashes` and we're not on the Cancun hard
307    /// fork.
308    #[error("Block `blob_versioned_hashes` is not supported before the Cancun hardfork")]
309    BlobVersionedHashesNotSupported,
310    /// Thrown when `max_fee_per_blob_gas` is not supported for blocks before the Cancun hardfork.
311    #[error("`max_fee_per_blob_gas` is not supported for blocks before the Cancun hardfork.")]
312    MaxFeePerBlobGasNotSupported,
313    /// Thrown when there are no `blob_hashes` in the transaction, and it is an EIP-4844 tx.
314    #[error("`blob_hashes` are required for EIP-4844 transactions")]
315    NoBlobHashes,
316    #[error("too many blobs in one transaction, have: {0}, max: {1}")]
317    TooManyBlobs(usize, usize),
318    /// Thrown when there's a blob validation error
319    #[error(transparent)]
320    BlobTransactionValidationError(#[from] alloy_consensus::BlobTransactionValidationError),
321    /// Thrown when Blob transaction is a create transaction. `to` must be present.
322    #[error("Blob transaction can't be a create transaction. `to` must be present.")]
323    BlobCreateTransaction,
324    /// Thrown when Blob transaction contains a versioned hash with an incorrect version.
325    #[error("Blob transaction contains a versioned hash with an incorrect version")]
326    BlobVersionNotSupported,
327    /// Thrown when a blob transaction is submitted on a Monad network.
328    #[error("EIP-4844 blob transactions are not supported on Monad")]
329    MonadBlobTransactionUnsupported,
330    /// Thrown when there are no `blob_hashes` in the transaction.
331    #[error("There should be at least one blob in a Blob transaction.")]
332    EmptyBlobs,
333    /// Thrown when an access list is used before the berlin hard fork.
334    #[error("EIP-7702 authorization lists are not supported before the Prague hardfork")]
335    AuthorizationListNotSupported,
336    #[error("Transaction gas limit is greater than the block gas limit, gas_limit: {0}, cap: {1}")]
337    TxGasLimitGreaterThanCap(u64, u64),
338    /// Forwards error from the revm
339    #[error(transparent)]
340    Revm(revm::context_interface::result::InvalidTransaction),
341    /// Deposit transaction error post regolith
342    #[error("op-deposit failure post regolith")]
343    DepositTxErrorPostRegolith,
344    /// Missing enveloped transaction
345    #[error("missing enveloped transaction")]
346    MissingEnvelopedTx,
347    /// Native ETH value transfers are not allowed in Tempo mode
348    #[error("native value transfer not allowed in Tempo mode")]
349    TempoNativeValueTransfer,
350    /// Tempo transaction valid_before is expired or too close to current time
351    #[error("Tempo tx valid_before ({valid_before}) must be > current time + 3s ({min_allowed})")]
352    TempoValidBeforeExpired { valid_before: u64, min_allowed: u64 },
353    /// Tempo transaction valid_after is too far in the future
354    #[error("Tempo tx valid_after ({valid_after}) must be <= current time + 1h ({max_allowed})")]
355    TempoValidAfterTooFar { valid_after: u64, max_allowed: u64 },
356    /// Tempo transaction has too many authorizations
357    #[error("Tempo tx has too many authorizations ({count}), max allowed is {max}")]
358    TempoTooManyAuthorizations { count: usize, max: usize },
359    /// Tempo transaction fee payer has insufficient fee token balance
360    #[error("insufficient fee token balance: have {balance}, need {required}")]
361    TempoInsufficientFeeTokenBalance { balance: U256, required: U256 },
362}
363
364impl From<InvalidTransaction> for InvalidTransactionError {
365    fn from(err: InvalidTransaction) -> Self {
366        match err {
367            InvalidTransaction::InvalidChainId => Self::InvalidChainId,
368            InvalidTransaction::PriorityFeeGreaterThanMaxFee => Self::TipAboveFeeCap,
369            InvalidTransaction::GasPriceLessThanBasefee => Self::FeeCapTooLow,
370            InvalidTransaction::CallerGasLimitMoreThanBlock => {
371                Self::GasTooHigh(ErrDetail { detail: String::from("CallerGasLimitMoreThanBlock") })
372            }
373            InvalidTransaction::CallGasCostMoreThanGasLimit { .. } => {
374                Self::GasTooHigh(ErrDetail { detail: String::from("CallGasCostMoreThanGasLimit") })
375            }
376            InvalidTransaction::GasFloorMoreThanGasLimit { .. } => {
377                Self::GasTooHigh(ErrDetail { detail: String::from("GasFloorMoreThanGasLimit") })
378            }
379            InvalidTransaction::RejectCallerWithCode => Self::SenderNoEOA,
380            InvalidTransaction::LackOfFundForMaxFee { .. } => Self::InsufficientFunds,
381            InvalidTransaction::OverflowPaymentInTransaction => Self::GasUintOverflow,
382            InvalidTransaction::NonceOverflowInTransaction => Self::NonceMaxValue,
383            InvalidTransaction::CreateInitCodeSizeLimit => Self::MaxInitCodeSizeExceeded,
384            InvalidTransaction::NonceTooHigh { .. } => Self::NonceTooHigh,
385            InvalidTransaction::NonceTooLow { .. } => Self::NonceTooLow,
386            InvalidTransaction::AccessListNotSupported => Self::AccessListNotSupported,
387            InvalidTransaction::BlobGasPriceGreaterThanMax {
388                block_blob_gas_price,
389                tx_max_fee_per_blob_gas,
390            } => Self::BlobFeeCapTooLow(block_blob_gas_price, tx_max_fee_per_blob_gas),
391            InvalidTransaction::BlobVersionedHashesNotSupported => {
392                Self::BlobVersionedHashesNotSupported
393            }
394            InvalidTransaction::MaxFeePerBlobGasNotSupported => Self::MaxFeePerBlobGasNotSupported,
395            InvalidTransaction::BlobCreateTransaction => Self::BlobCreateTransaction,
396            InvalidTransaction::BlobVersionNotSupported => Self::BlobVersionNotSupported,
397            InvalidTransaction::EmptyBlobs => Self::EmptyBlobs,
398            InvalidTransaction::TooManyBlobs { have, max } => Self::TooManyBlobs(have, max),
399            InvalidTransaction::AuthorizationListNotSupported => {
400                Self::AuthorizationListNotSupported
401            }
402            InvalidTransaction::TxGasLimitGreaterThanCap { gas_limit, cap } => {
403                Self::TxGasLimitGreaterThanCap(gas_limit, cap)
404            }
405
406            InvalidTransaction::AuthorizationListInvalidFields
407            | InvalidTransaction::Eip1559NotSupported
408            | InvalidTransaction::Eip2930NotSupported
409            | InvalidTransaction::Eip4844NotSupported
410            | InvalidTransaction::Eip7702NotSupported
411            | InvalidTransaction::EmptyAuthorizationList
412            | InvalidTransaction::Eip7873NotSupported
413            | InvalidTransaction::Eip7873MissingTarget
414            | InvalidTransaction::MissingChainId
415            | InvalidTransaction::Str(_) => Self::Revm(err),
416        }
417    }
418}
419
420/// Helper trait to easily convert results to rpc results
421pub(crate) trait ToRpcResponseResult {
422    fn to_rpc_result(self) -> ResponseResult;
423}
424
425/// Converts a serializable value into a `ResponseResult`
426pub fn to_rpc_result<T: Serialize>(val: T) -> ResponseResult {
427    match serde_json::to_value(val) {
428        Ok(success) => ResponseResult::Success(success),
429        Err(err) => {
430            error!(%err, "Failed serialize rpc response");
431            ResponseResult::error(RpcError::internal_error())
432        }
433    }
434}
435
436impl<T: Serialize> ToRpcResponseResult for Result<T> {
437    fn to_rpc_result(self) -> ResponseResult {
438        match self {
439            Ok(val) => to_rpc_result(val),
440            Err(err) => match err {
441                BlockchainError::Pool(err) => {
442                    error!(%err, "txpool error");
443                    match err {
444                        PoolError::CyclicTransaction => {
445                            RpcError::transaction_rejected("Cyclic transaction detected")
446                        }
447                        PoolError::ReplacementUnderpriced(_) => {
448                            RpcError::transaction_rejected("replacement transaction underpriced")
449                        }
450                        PoolError::AlreadyImported(_) => {
451                            RpcError::transaction_rejected("transaction already imported")
452                        }
453                    }
454                }
455                BlockchainError::NoSignerAvailable => {
456                    RpcError::invalid_params("No Signer available")
457                }
458                BlockchainError::ChainIdNotAvailable => {
459                    RpcError::invalid_params("Chain Id not available")
460                }
461                BlockchainError::TransactionConfirmationTimeout { hash, .. } => RpcError {
462                    code: ErrorCode::ServerError(4),
463                    message: "Transaction confirmation timeout".into(),
464                    data: Some(serde_json::Value::String(hash.to_string())),
465                },
466                BlockchainError::InvalidTransaction(err) => match err {
467                    InvalidTransactionError::Revert(data) => {
468                        // this mimics geth revert error
469                        let mut msg = "execution reverted".to_string();
470                        if let Some(reason) = data
471                            .as_ref()
472                            .and_then(|data| RevertDecoder::new().maybe_decode(data, None))
473                        {
474                            msg = format!("{msg}: {reason}");
475                        }
476                        RpcError {
477                            // geth returns this error code on reverts, See <https://eips.ethereum.org/EIPS/eip-1474#specification>
478                            code: ErrorCode::ExecutionError,
479                            message: msg.into(),
480                            data: serde_json::to_value(data).ok(),
481                        }
482                    }
483                    InvalidTransactionError::GasTooLow => {
484                        // <https://eips.ethereum.org/EIPS/eip-1898>
485                        RpcError {
486                            code: ErrorCode::ServerError(-32000),
487                            message: err.to_string().into(),
488                            data: None,
489                        }
490                    }
491                    InvalidTransactionError::GasTooHigh(_) => {
492                        // <https://eips.ethereum.org/EIPS/eip-1898>
493                        RpcError {
494                            code: ErrorCode::ServerError(-32000),
495                            message: err.to_string().into(),
496                            data: None,
497                        }
498                    }
499                    _ => RpcError::transaction_rejected(err.to_string()),
500                },
501                BlockchainError::FeeHistory(err) => RpcError::invalid_params(err.to_string()),
502                BlockchainError::EmptyRawTransactionData => {
503                    RpcError::invalid_params("Empty transaction data")
504                }
505                BlockchainError::FailedToDecodeSignedTransaction => {
506                    RpcError::invalid_params("Failed to decode transaction")
507                }
508                BlockchainError::FailedToDecodeTransaction => {
509                    RpcError::invalid_params("Failed to decode transaction")
510                }
511                BlockchainError::FailedToDecodeReceipt => {
512                    RpcError::invalid_params("Failed to decode receipt")
513                }
514                BlockchainError::UnsupportedTransactionEncoding(_) => {
515                    RpcError::internal_error_with(err.to_string())
516                }
517                BlockchainError::FailedToDecodeStateDump => {
518                    RpcError::invalid_params("Failed to decode state dump")
519                }
520                BlockchainError::SignerError(err) => RpcError::invalid_params(err.to_string()),
521                BlockchainError::SignatureError(err) => RpcError::invalid_params(err.to_string()),
522                BlockchainError::RpcUnimplemented => {
523                    RpcError::internal_error_with("Not implemented")
524                }
525                BlockchainError::PrevrandaoNotSet => RpcError::internal_error_with(err.to_string()),
526                BlockchainError::RpcError(err) => err,
527                BlockchainError::InvalidFeeInput => RpcError::invalid_params(
528                    "Invalid input: `max_priority_fee_per_gas` greater than `max_fee_per_gas`",
529                ),
530                BlockchainError::AlloyForkProvider(err) => {
531                    error!(target: "backend", %err, "fork provider error");
532                    match err {
533                        TransportError::ErrorResp(err) => RpcError {
534                            code: ErrorCode::from(err.code),
535                            message: err.message,
536                            data: err.data.and_then(|data| serde_json::to_value(data).ok()),
537                        },
538                        err => RpcError::internal_error_with(format!("Fork Error: {err:?}")),
539                    }
540                }
541                err @ BlockchainError::EvmError(_) => RpcError {
542                    // VM halts are execution failures, not JSON-RPC server faults. REVERT has a
543                    // dedicated code/data path above; other halts, such as invalid opcode, do not.
544                    code: ErrorCode::TransactionRejected,
545                    message: err.to_string().into(),
546                    data: None,
547                },
548                err @ BlockchainError::EvmOverrideError(_) => {
549                    RpcError::invalid_params(err.to_string())
550                }
551                err @ BlockchainError::InvalidUrl(_) => RpcError::invalid_params(err.to_string()),
552                err @ BlockchainError::UnsupportedForkNetwork { .. } => {
553                    RpcError::invalid_params(err.to_string())
554                }
555                BlockchainError::Internal(err) => RpcError::internal_error_with(err),
556                err @ BlockchainError::BlockOutOfRange(_, _) => {
557                    RpcError::invalid_params(err.to_string())
558                }
559                err @ BlockchainError::BlockNotFound => RpcError {
560                    // <https://eips.ethereum.org/EIPS/eip-1898>
561                    code: ErrorCode::ServerError(-32001),
562                    message: err.to_string().into(),
563                    data: None,
564                },
565                err @ BlockchainError::TransactionNotFound => RpcError {
566                    code: ErrorCode::ServerError(-32001),
567                    message: err.to_string().into(),
568                    data: None,
569                },
570                err @ BlockchainError::DataUnavailable => {
571                    RpcError::internal_error_with(err.to_string())
572                }
573                err @ BlockchainError::TrieError(_) => {
574                    RpcError::internal_error_with(err.to_string())
575                }
576                BlockchainError::UintConversion(err) => RpcError::invalid_params(err),
577                err @ BlockchainError::StateOverrideError(_) => {
578                    RpcError::invalid_params(err.to_string())
579                }
580                err @ BlockchainError::TimestampError(_) => {
581                    RpcError::invalid_params(err.to_string())
582                }
583                BlockchainError::DatabaseError(err) => {
584                    RpcError::internal_error_with(err.to_string())
585                }
586                err @ BlockchainError::EIP1559TransactionUnsupportedAtHardfork => {
587                    RpcError::invalid_params(err.to_string())
588                }
589                err @ BlockchainError::EIP2930TransactionUnsupportedAtHardfork => {
590                    RpcError::invalid_params(err.to_string())
591                }
592                err @ BlockchainError::EIP4844TransactionUnsupportedAtHardfork => {
593                    RpcError::invalid_params(err.to_string())
594                }
595                err @ BlockchainError::EIP7702TransactionUnsupportedAtHardfork => {
596                    RpcError::invalid_params(err.to_string())
597                }
598                err @ BlockchainError::DepositTransactionUnsupported => {
599                    RpcError::invalid_params(err.to_string())
600                }
601                err @ BlockchainError::TempoTransactionUnsupported => {
602                    RpcError::invalid_params(err.to_string())
603                }
604                err @ BlockchainError::ExcessBlobGasNotSet => {
605                    RpcError::invalid_params(err.to_string())
606                }
607                err @ BlockchainError::Message(_) => RpcError::internal_error_with(err.to_string()),
608                err @ BlockchainError::UnknownTransactionType => {
609                    RpcError::invalid_params(err.to_string())
610                }
611                err @ BlockchainError::InvalidTransactionRequest(_) => {
612                    RpcError::invalid_params(err.to_string())
613                }
614                err @ BlockchainError::RecoveryError(_) => {
615                    RpcError::invalid_params(err.to_string())
616                }
617                BlockchainError::FilterNotFound => RpcError {
618                    code: ErrorCode::ServerError(-32000),
619                    message: "filter not found".into(),
620                    data: None,
621                },
622                err @ BlockchainError::UnknownBlock => RpcError {
623                    code: ErrorCode::ServerError(-32000),
624                    message: err.to_string().into(),
625                    data: None,
626                },
627            }
628            .into(),
629        }
630    }
631}