1use 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("Failed to decode state")]
47 FailedToDecodeStateDump,
48 #[error("Prevrandao not in the EVM's environment after merge")]
49 PrevrandaoNotSet,
50 #[error(transparent)]
51 SignatureError(#[from] SignatureError),
52 #[error(transparent)]
53 RecoveryError(#[from] RecoveryError),
54 #[error(transparent)]
55 SignerError(#[from] SignerError),
56 #[error("Rpc Endpoint not implemented")]
57 RpcUnimplemented,
58 #[error("Rpc error {0:?}")]
59 RpcError(RpcError),
60 #[error(transparent)]
61 InvalidTransaction(#[from] InvalidTransactionError),
62 #[error(transparent)]
63 FeeHistory(#[from] FeeHistoryError),
64 #[error(transparent)]
65 AlloyForkProvider(#[from] TransportError),
66 #[error("EVM error {0:?}")]
67 EvmError(InstructionResult),
68 #[error("Evm override error: {0}")]
69 EvmOverrideError(String),
70 #[error("Invalid url {0:?}")]
71 InvalidUrl(String),
72 #[error("unsupported fork network for chain {chain_id}: {reason}")]
73 UnsupportedForkNetwork { chain_id: u64, reason: &'static str },
74 #[error("Internal error: {0:?}")]
75 Internal(String),
76 #[error("BlockOutOfRangeError: block height is {0} but requested was {1}")]
77 BlockOutOfRange(u64, u64),
78 #[error("Resource not found")]
79 BlockNotFound,
80 #[error("unknown block")]
81 UnknownBlock,
82 #[error("transaction not found")]
84 TransactionNotFound,
85 #[error("Required data unavailable")]
86 DataUnavailable,
87 #[error("Trie error: {0}")]
88 TrieError(String),
89 #[error("{0}")]
90 UintConversion(&'static str),
91 #[error("State override error: {0}")]
92 StateOverrideError(String),
93 #[error("Timestamp error: {0}")]
94 TimestampError(String),
95 #[error(transparent)]
96 DatabaseError(#[from] DatabaseError),
97 #[error(
98 "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."
99 )]
100 EIP1559TransactionUnsupportedAtHardfork,
101 #[error(
102 "Access list received but is not supported by the current hardfork.\n\nYou can use it by running anvil with '--hardfork berlin' or later."
103 )]
104 EIP2930TransactionUnsupportedAtHardfork,
105 #[error(
106 "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."
107 )]
108 EIP4844TransactionUnsupportedAtHardfork,
109 #[error(
110 "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."
111 )]
112 EIP7702TransactionUnsupportedAtHardfork,
113 #[error(
114 "op-stack deposit tx received but is not supported.\n\nYou can use it by running anvil with '--optimism'."
115 )]
116 DepositTransactionUnsupported,
117 #[error(
118 "tempo transaction received but is not supported.\n\nYou can use it by running anvil with '--tempo'."
119 )]
120 TempoTransactionUnsupported,
121 #[error("Unknown transaction type not supported")]
122 UnknownTransactionType,
123 #[error("Excess blob gas not set.")]
124 ExcessBlobGasNotSet,
125 #[error("{0}")]
126 Message(String),
127 #[error("Transaction {hash} was added to the mempool but wasn't confirmed within {duration:?}")]
128 TransactionConfirmationTimeout {
129 hash: B256,
131 duration: Duration,
133 },
134 #[error("Invalid transaction request: {0}")]
135 InvalidTransactionRequest(String),
136 #[error("filter not found")]
137 FilterNotFound,
138}
139
140impl From<eyre::Report> for BlockchainError {
141 fn from(err: eyre::Report) -> Self {
142 Self::Message(err.to_string())
143 }
144}
145
146impl From<RpcError> for BlockchainError {
147 fn from(err: RpcError) -> Self {
148 Self::RpcError(err)
149 }
150}
151
152impl<T> From<EVMError<T>> for BlockchainError
153where
154 T: Into<Self>,
155{
156 fn from(err: EVMError<T>) -> Self {
157 match err {
158 EVMError::Transaction(err) => InvalidTransactionError::from(err).into(),
159 EVMError::Header(err) => match err {
160 InvalidHeader::ExcessBlobGasNotSet => Self::ExcessBlobGasNotSet,
161 InvalidHeader::PrevrandaoNotSet => Self::PrevrandaoNotSet,
162 },
163 EVMError::Database(err) => err.into(),
164 EVMError::Custom(err) => Self::Message(err),
165 EVMError::CustomAny(err) => Self::Message(err.to_string()),
166 }
167 }
168}
169
170impl<T> From<EVMError<T, TempoInvalidTransaction>> for BlockchainError
171where
172 T: Into<Self>,
173{
174 fn from(err: EVMError<T, TempoInvalidTransaction>) -> Self {
175 match err {
176 EVMError::Transaction(err) => match err {
177 TempoInvalidTransaction::EthInvalidTransaction(err) => {
178 InvalidTransactionError::from(err).into()
179 }
180 err => Self::Message(format!("tempo transaction error: {err}")),
181 },
182 EVMError::Header(err) => match err {
183 InvalidHeader::ExcessBlobGasNotSet => Self::ExcessBlobGasNotSet,
184 InvalidHeader::PrevrandaoNotSet => Self::PrevrandaoNotSet,
185 },
186 EVMError::Database(err) => err.into(),
187 EVMError::Custom(err) => Self::Message(err),
188 EVMError::CustomAny(err) => Self::Message(err.to_string()),
189 }
190 }
191}
192
193impl From<WalletError> for BlockchainError {
194 fn from(value: WalletError) -> Self {
195 Self::Message(value.to_string())
196 }
197}
198
199impl<E> From<StateOverrideError<E>> for BlockchainError
200where
201 E: Into<Self>,
202{
203 fn from(value: StateOverrideError<E>) -> Self {
204 match value {
205 StateOverrideError::InvalidBytecode(err) => Self::StateOverrideError(err.to_string()),
206 StateOverrideError::BothStateAndStateDiff(addr) => Self::StateOverrideError(format!(
207 "state and state_diff can't be used together for account {addr}",
208 )),
209 StateOverrideError::Database(err) => err.into(),
210 }
211 }
212}
213
214#[derive(Debug, thiserror::Error)]
216pub enum PoolError {
217 #[error("Transaction with cyclic dependent transactions")]
218 CyclicTransaction,
219 #[error("Tx: [{0:?}] insufficient gas price to replace existing transaction")]
221 ReplacementUnderpriced(TxHash),
222 #[error("Tx: [{0:?}] already Imported")]
223 AlreadyImported(TxHash),
224}
225
226#[derive(Debug, thiserror::Error)]
228pub enum FeeHistoryError {
229 #[error("requested block range is out of bounds")]
230 InvalidBlockRange,
231 #[error("could not find block number requested: {0}")]
232 BlockNotFound(BlockNumberOrTag),
233}
234
235#[derive(Debug)]
236pub struct ErrDetail {
237 pub detail: String,
238}
239
240#[derive(Debug, thiserror::Error)]
242pub enum InvalidTransactionError {
243 #[error("nonce too low")]
245 NonceTooLow,
246 #[error("Nonce too high")]
249 NonceTooHigh,
250 #[error("nonce has max value")]
253 NonceMaxValue,
254 #[error("insufficient funds for transfer")]
256 InsufficientFundsForTransfer,
257 #[error("max initcode size exceeded")]
259 MaxInitCodeSizeExceeded,
260 #[error("Insufficient funds for gas * price + value")]
262 InsufficientFunds,
263 #[error("gas uint64 overflow")]
265 GasUintOverflow,
266 #[error("intrinsic gas too low")]
269 GasTooLow,
270 #[error("intrinsic gas too high -- {}",.0.detail)]
272 GasTooHigh(ErrDetail),
273 #[error("max priority fee per gas higher than max fee per gas")]
276 TipAboveFeeCap,
277 #[error("max fee per gas less than block base fee")]
279 FeeCapTooLow,
280 #[error("Out of gas: gas required exceeds allowance: {0:?}")]
282 BasicOutOfGas(u128),
283 #[error("execution reverted: {0:?}")]
285 Revert(Option<Bytes>),
286 #[error("sender not an eoa")]
288 SenderNoEOA,
289 #[error("invalid chain id for signer")]
291 InvalidChainId,
292 #[error("Incompatible EIP-155 transaction, signed for another chain")]
294 IncompatibleEIP155,
295 #[error("Access lists are not supported before the Berlin hardfork")]
297 AccessListNotSupported,
298 #[error("Block `blob_gas_price` is greater than tx-specified `max_fee_per_blob_gas`")]
301 BlobFeeCapTooLow(u128, u128),
302 #[error("Block `blob_versioned_hashes` is not supported before the Cancun hardfork")]
305 BlobVersionedHashesNotSupported,
306 #[error("`max_fee_per_blob_gas` is not supported for blocks before the Cancun hardfork.")]
308 MaxFeePerBlobGasNotSupported,
309 #[error("`blob_hashes` are required for EIP-4844 transactions")]
311 NoBlobHashes,
312 #[error("too many blobs in one transaction, have: {0}, max: {1}")]
313 TooManyBlobs(usize, usize),
314 #[error(transparent)]
316 BlobTransactionValidationError(#[from] alloy_consensus::BlobTransactionValidationError),
317 #[error("Blob transaction can't be a create transaction. `to` must be present.")]
319 BlobCreateTransaction,
320 #[error("Blob transaction contains a versioned hash with an incorrect version")]
322 BlobVersionNotSupported,
323 #[error("There should be at least one blob in a Blob transaction.")]
325 EmptyBlobs,
326 #[error("EIP-7702 authorization lists are not supported before the Prague hardfork")]
328 AuthorizationListNotSupported,
329 #[error("Transaction gas limit is greater than the block gas limit, gas_limit: {0}, cap: {1}")]
330 TxGasLimitGreaterThanCap(u64, u64),
331 #[error(transparent)]
333 Revm(revm::context_interface::result::InvalidTransaction),
334 #[error("op-deposit failure post regolith")]
336 DepositTxErrorPostRegolith,
337 #[error("missing enveloped transaction")]
339 MissingEnvelopedTx,
340 #[error("native value transfer not allowed in Tempo mode")]
342 TempoNativeValueTransfer,
343 #[error("Tempo tx valid_before ({valid_before}) must be > current time + 3s ({min_allowed})")]
345 TempoValidBeforeExpired { valid_before: u64, min_allowed: u64 },
346 #[error("Tempo tx valid_after ({valid_after}) must be <= current time + 1h ({max_allowed})")]
348 TempoValidAfterTooFar { valid_after: u64, max_allowed: u64 },
349 #[error("Tempo tx has too many authorizations ({count}), max allowed is {max}")]
351 TempoTooManyAuthorizations { count: usize, max: usize },
352 #[error("insufficient fee token balance: have {balance}, need {required}")]
354 TempoInsufficientFeeTokenBalance { balance: U256, required: U256 },
355}
356
357impl From<InvalidTransaction> for InvalidTransactionError {
358 fn from(err: InvalidTransaction) -> Self {
359 match err {
360 InvalidTransaction::InvalidChainId => Self::InvalidChainId,
361 InvalidTransaction::PriorityFeeGreaterThanMaxFee => Self::TipAboveFeeCap,
362 InvalidTransaction::GasPriceLessThanBasefee => Self::FeeCapTooLow,
363 InvalidTransaction::CallerGasLimitMoreThanBlock => {
364 Self::GasTooHigh(ErrDetail { detail: String::from("CallerGasLimitMoreThanBlock") })
365 }
366 InvalidTransaction::CallGasCostMoreThanGasLimit { .. } => {
367 Self::GasTooHigh(ErrDetail { detail: String::from("CallGasCostMoreThanGasLimit") })
368 }
369 InvalidTransaction::GasFloorMoreThanGasLimit { .. } => {
370 Self::GasTooHigh(ErrDetail { detail: String::from("GasFloorMoreThanGasLimit") })
371 }
372 InvalidTransaction::RejectCallerWithCode => Self::SenderNoEOA,
373 InvalidTransaction::LackOfFundForMaxFee { .. } => Self::InsufficientFunds,
374 InvalidTransaction::OverflowPaymentInTransaction => Self::GasUintOverflow,
375 InvalidTransaction::NonceOverflowInTransaction => Self::NonceMaxValue,
376 InvalidTransaction::CreateInitCodeSizeLimit => Self::MaxInitCodeSizeExceeded,
377 InvalidTransaction::NonceTooHigh { .. } => Self::NonceTooHigh,
378 InvalidTransaction::NonceTooLow { .. } => Self::NonceTooLow,
379 InvalidTransaction::AccessListNotSupported => Self::AccessListNotSupported,
380 InvalidTransaction::BlobGasPriceGreaterThanMax {
381 block_blob_gas_price,
382 tx_max_fee_per_blob_gas,
383 } => Self::BlobFeeCapTooLow(block_blob_gas_price, tx_max_fee_per_blob_gas),
384 InvalidTransaction::BlobVersionedHashesNotSupported => {
385 Self::BlobVersionedHashesNotSupported
386 }
387 InvalidTransaction::MaxFeePerBlobGasNotSupported => Self::MaxFeePerBlobGasNotSupported,
388 InvalidTransaction::BlobCreateTransaction => Self::BlobCreateTransaction,
389 InvalidTransaction::BlobVersionNotSupported => Self::BlobVersionNotSupported,
390 InvalidTransaction::EmptyBlobs => Self::EmptyBlobs,
391 InvalidTransaction::TooManyBlobs { have, max } => Self::TooManyBlobs(have, max),
392 InvalidTransaction::AuthorizationListNotSupported => {
393 Self::AuthorizationListNotSupported
394 }
395 InvalidTransaction::TxGasLimitGreaterThanCap { gas_limit, cap } => {
396 Self::TxGasLimitGreaterThanCap(gas_limit, cap)
397 }
398
399 InvalidTransaction::AuthorizationListInvalidFields
400 | InvalidTransaction::Eip1559NotSupported
401 | InvalidTransaction::Eip2930NotSupported
402 | InvalidTransaction::Eip4844NotSupported
403 | InvalidTransaction::Eip7702NotSupported
404 | InvalidTransaction::EmptyAuthorizationList
405 | InvalidTransaction::Eip7873NotSupported
406 | InvalidTransaction::Eip7873MissingTarget
407 | InvalidTransaction::MissingChainId
408 | InvalidTransaction::Str(_) => Self::Revm(err),
409 }
410 }
411}
412
413pub(crate) trait ToRpcResponseResult {
415 fn to_rpc_result(self) -> ResponseResult;
416}
417
418pub fn to_rpc_result<T: Serialize>(val: T) -> ResponseResult {
420 match serde_json::to_value(val) {
421 Ok(success) => ResponseResult::Success(success),
422 Err(err) => {
423 error!(%err, "Failed serialize rpc response");
424 ResponseResult::error(RpcError::internal_error())
425 }
426 }
427}
428
429impl<T: Serialize> ToRpcResponseResult for Result<T> {
430 fn to_rpc_result(self) -> ResponseResult {
431 match self {
432 Ok(val) => to_rpc_result(val),
433 Err(err) => match err {
434 BlockchainError::Pool(err) => {
435 error!(%err, "txpool error");
436 match err {
437 PoolError::CyclicTransaction => {
438 RpcError::transaction_rejected("Cyclic transaction detected")
439 }
440 PoolError::ReplacementUnderpriced(_) => {
441 RpcError::transaction_rejected("replacement transaction underpriced")
442 }
443 PoolError::AlreadyImported(_) => {
444 RpcError::transaction_rejected("transaction already imported")
445 }
446 }
447 }
448 BlockchainError::NoSignerAvailable => {
449 RpcError::invalid_params("No Signer available")
450 }
451 BlockchainError::ChainIdNotAvailable => {
452 RpcError::invalid_params("Chain Id not available")
453 }
454 BlockchainError::TransactionConfirmationTimeout { hash, .. } => RpcError {
455 code: ErrorCode::ServerError(4),
456 message: "Transaction confirmation timeout".into(),
457 data: Some(serde_json::Value::String(hash.to_string())),
458 },
459 BlockchainError::InvalidTransaction(err) => match err {
460 InvalidTransactionError::Revert(data) => {
461 let mut msg = "execution reverted".to_string();
463 if let Some(reason) = data
464 .as_ref()
465 .and_then(|data| RevertDecoder::new().maybe_decode(data, None))
466 {
467 msg = format!("{msg}: {reason}");
468 }
469 RpcError {
470 code: ErrorCode::ExecutionError,
472 message: msg.into(),
473 data: serde_json::to_value(data).ok(),
474 }
475 }
476 InvalidTransactionError::GasTooLow => {
477 RpcError {
479 code: ErrorCode::ServerError(-32000),
480 message: err.to_string().into(),
481 data: None,
482 }
483 }
484 InvalidTransactionError::GasTooHigh(_) => {
485 RpcError {
487 code: ErrorCode::ServerError(-32000),
488 message: err.to_string().into(),
489 data: None,
490 }
491 }
492 _ => RpcError::transaction_rejected(err.to_string()),
493 },
494 BlockchainError::FeeHistory(err) => RpcError::invalid_params(err.to_string()),
495 BlockchainError::EmptyRawTransactionData => {
496 RpcError::invalid_params("Empty transaction data")
497 }
498 BlockchainError::FailedToDecodeSignedTransaction => {
499 RpcError::invalid_params("Failed to decode transaction")
500 }
501 BlockchainError::FailedToDecodeTransaction => {
502 RpcError::invalid_params("Failed to decode transaction")
503 }
504 BlockchainError::FailedToDecodeReceipt => {
505 RpcError::invalid_params("Failed to decode receipt")
506 }
507 BlockchainError::FailedToDecodeStateDump => {
508 RpcError::invalid_params("Failed to decode state dump")
509 }
510 BlockchainError::SignerError(err) => RpcError::invalid_params(err.to_string()),
511 BlockchainError::SignatureError(err) => RpcError::invalid_params(err.to_string()),
512 BlockchainError::RpcUnimplemented => {
513 RpcError::internal_error_with("Not implemented")
514 }
515 BlockchainError::PrevrandaoNotSet => RpcError::internal_error_with(err.to_string()),
516 BlockchainError::RpcError(err) => err,
517 BlockchainError::InvalidFeeInput => RpcError::invalid_params(
518 "Invalid input: `max_priority_fee_per_gas` greater than `max_fee_per_gas`",
519 ),
520 BlockchainError::AlloyForkProvider(err) => {
521 error!(target: "backend", %err, "fork provider error");
522 match err {
523 TransportError::ErrorResp(err) => RpcError {
524 code: ErrorCode::from(err.code),
525 message: err.message,
526 data: err.data.and_then(|data| serde_json::to_value(data).ok()),
527 },
528 err => RpcError::internal_error_with(format!("Fork Error: {err:?}")),
529 }
530 }
531 err @ BlockchainError::EvmError(_) => RpcError {
532 code: ErrorCode::TransactionRejected,
535 message: err.to_string().into(),
536 data: None,
537 },
538 err @ BlockchainError::EvmOverrideError(_) => {
539 RpcError::invalid_params(err.to_string())
540 }
541 err @ BlockchainError::InvalidUrl(_) => RpcError::invalid_params(err.to_string()),
542 err @ BlockchainError::UnsupportedForkNetwork { .. } => {
543 RpcError::invalid_params(err.to_string())
544 }
545 BlockchainError::Internal(err) => RpcError::internal_error_with(err),
546 err @ BlockchainError::BlockOutOfRange(_, _) => {
547 RpcError::invalid_params(err.to_string())
548 }
549 err @ BlockchainError::BlockNotFound => RpcError {
550 code: ErrorCode::ServerError(-32001),
552 message: err.to_string().into(),
553 data: None,
554 },
555 err @ BlockchainError::TransactionNotFound => RpcError {
556 code: ErrorCode::ServerError(-32001),
557 message: err.to_string().into(),
558 data: None,
559 },
560 err @ BlockchainError::DataUnavailable => {
561 RpcError::internal_error_with(err.to_string())
562 }
563 err @ BlockchainError::TrieError(_) => {
564 RpcError::internal_error_with(err.to_string())
565 }
566 BlockchainError::UintConversion(err) => RpcError::invalid_params(err),
567 err @ BlockchainError::StateOverrideError(_) => {
568 RpcError::invalid_params(err.to_string())
569 }
570 err @ BlockchainError::TimestampError(_) => {
571 RpcError::invalid_params(err.to_string())
572 }
573 BlockchainError::DatabaseError(err) => {
574 RpcError::internal_error_with(err.to_string())
575 }
576 err @ BlockchainError::EIP1559TransactionUnsupportedAtHardfork => {
577 RpcError::invalid_params(err.to_string())
578 }
579 err @ BlockchainError::EIP2930TransactionUnsupportedAtHardfork => {
580 RpcError::invalid_params(err.to_string())
581 }
582 err @ BlockchainError::EIP4844TransactionUnsupportedAtHardfork => {
583 RpcError::invalid_params(err.to_string())
584 }
585 err @ BlockchainError::EIP7702TransactionUnsupportedAtHardfork => {
586 RpcError::invalid_params(err.to_string())
587 }
588 err @ BlockchainError::DepositTransactionUnsupported => {
589 RpcError::invalid_params(err.to_string())
590 }
591 err @ BlockchainError::TempoTransactionUnsupported => {
592 RpcError::invalid_params(err.to_string())
593 }
594 err @ BlockchainError::ExcessBlobGasNotSet => {
595 RpcError::invalid_params(err.to_string())
596 }
597 err @ BlockchainError::Message(_) => RpcError::internal_error_with(err.to_string()),
598 err @ BlockchainError::UnknownTransactionType => {
599 RpcError::invalid_params(err.to_string())
600 }
601 err @ BlockchainError::InvalidTransactionRequest(_) => {
602 RpcError::invalid_params(err.to_string())
603 }
604 err @ BlockchainError::RecoveryError(_) => {
605 RpcError::invalid_params(err.to_string())
606 }
607 BlockchainError::FilterNotFound => RpcError {
608 code: ErrorCode::ServerError(-32000),
609 message: "filter not found".into(),
610 data: None,
611 },
612 err @ BlockchainError::UnknownBlock => RpcError {
613 code: ErrorCode::ServerError(-32000),
614 message: err.to_string().into(),
615 data: None,
616 },
617 }
618 .into(),
619 }
620 }
621}