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