1pub 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
27pub trait ToRpcError: core::error::Error + Send + Sync + 'static {
29 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
39pub type EthResult<T> = Result<T, EthApiError>;
41
42#[derive(Debug, thiserror::Error)]
44pub enum EthApiError {
45 #[error("empty transaction data")]
47 EmptyRawTransactionData,
48 #[error("failed to decode signed transaction")]
50 FailedToDecodeSignedTransaction,
51 #[error("invalid transaction signature")]
53 InvalidTransactionSignature,
54 #[error(transparent)]
56 PoolError(RpcPoolError),
57 #[error("header not found")]
59 HeaderNotFound(BlockId),
60 #[error("header range not found, start block {0:?}, end block {1:?}")]
62 HeaderRangeNotFound(BlockId, BlockId),
63 #[error("receipts not found")]
65 ReceiptsNotFound(BlockId),
66 #[error("unknown block or tx index")]
68 UnknownBlockOrTxIndex,
69 #[error("invalid block range")]
71 InvalidBlockRange,
72 #[error("distance to target block exceeds maximum proof window")]
74 ExceedsMaxProofWindow,
75 #[error("prevrandao not in the EVM's environment after merge")]
77 PrevrandaoNotSet,
78 #[error("excess blob gas missing in the EVM's environment after Cancun")]
80 ExcessBlobGasNotSet,
81 #[error("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified")]
84 ConflictingFeeFieldsInRequest,
85 #[error(transparent)]
87 InvalidTransaction(#[from] RpcInvalidTransactionError),
88 #[error(transparent)]
90 InvalidBlockData(#[from] BlockError),
91 #[error("account {0:?} has both 'state' and 'stateDiff'")]
93 BothStateAndStateDiffInOverride(Address),
94 #[error(transparent)]
96 Internal(RethError),
97 #[error(transparent)]
99 Signing(#[from] SignError),
100 #[error("transaction not found")]
102 TransactionNotFound,
103 #[error("unsupported")]
105 Unsupported(&'static str),
106 #[error("{0}")]
108 InvalidParams(String),
109 #[error("invalid tracer config")]
111 InvalidTracerConfig,
112 #[error("invalid reward percentiles")]
114 InvalidRewardPercentiles,
115 #[error("internal blocking task error")]
120 InternalBlockingTaskError,
121 #[error("internal eth error")]
123 InternalEthError,
124 #[error("execution aborted (timeout = {0:?})")]
126 ExecutionTimedOut(Duration),
127 #[error("{0}")]
129 InternalJsTracerError(String),
130 #[error(transparent)]
131 TransactionInputError(#[from] TransactionInputError),
133 #[error("Revm error: {0}")]
135 EvmCustom(String),
136 #[error("Invalid bytecode: {0}")]
142 InvalidBytecode(String),
143 #[error("Transaction conversion error")]
145 TransactionConversionError,
146 #[error(transparent)]
148 MuxTracerError(#[from] MuxError),
149 #[error("{0}")]
151 Other(Box<dyn ToRpcError>),
152}
153
154impl EthApiError {
155 pub fn other<E: ToRpcError>(err: E) -> Self {
157 Self::Other(Box::new(err))
158 }
159
160 pub const fn is_gas_too_high(&self) -> bool {
162 matches!(self, Self::InvalidTransaction(RpcInvalidTransactionError::GasTooHigh))
163 }
164
165 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 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#[derive(thiserror::Error, Debug)]
333pub enum RpcInvalidTransactionError {
334 #[error("nonce too low: next nonce {state}, tx nonce {tx}")]
336 NonceTooLow {
337 tx: u64,
339 state: u64,
341 },
342 #[error("nonce too high")]
345 NonceTooHigh,
346 #[error("nonce has max value")]
349 NonceMaxValue,
350 #[error("insufficient funds for transfer")]
352 InsufficientFundsForTransfer,
353 #[error("max initcode size exceeded")]
355 MaxInitCodeSizeExceeded,
356 #[error("insufficient funds for gas * price + value: have {balance} want {cost}")]
358 InsufficientFunds {
359 cost: U256,
361 balance: U256,
363 },
364 #[error("gas required exceeds allowance ({gas_limit})")]
370 GasRequiredExceedsAllowance {
371 gas_limit: u64,
373 },
374 #[error("gas uint64 overflow")]
376 GasUintOverflow,
377 #[error("intrinsic gas too low")]
380 GasTooLow,
381 #[error("intrinsic gas too high")]
383 GasTooHigh,
384 #[error("transaction type not supported")]
386 TxTypeNotSupported,
387 #[error("max priority fee per gas higher than max fee per gas")]
390 TipAboveFeeCap,
391 #[error("max priority fee per gas higher than 2^256-1")]
393 TipVeryHigh,
394 #[error("max fee per gas higher than 2^256-1")]
396 FeeCapVeryHigh,
397 #[error("max fee per gas less than block base fee")]
399 FeeCapTooLow,
400 #[error("sender is not an EOA")]
402 SenderNoEOA,
403 #[error("out of gas: gas required exceeds: {0}")]
406 BasicOutOfGas(u64),
407 #[error("out of gas: gas exhausted during memory expansion: {0}")]
410 MemoryOutOfGas(u64),
411 #[error("out of gas: gas exhausted during precompiled contract execution: {0}")]
414 PrecompileOutOfGas(u64),
415 #[error("out of gas: invalid operand to an opcode: {0}")]
418 InvalidOperandOutOfGas(u64),
419 #[error(transparent)]
421 Revert(RevertError),
422 #[error("EVM error: {0:?}")]
424 EvmHalt(HaltReason),
425 #[error("invalid chain ID")]
427 InvalidChainId,
428 #[error("transactions before Spurious Dragon should not have a chain ID")]
430 OldLegacyChainId,
431 #[error("transactions before Berlin should not have access list")]
433 AccessListNotSupported,
434 #[error("max_fee_per_blob_gas is not supported for blocks before the Cancun hardfork")]
436 MaxFeePerBlobGasNotSupported,
437 #[error("blob_versioned_hashes is not supported for blocks before the Cancun hardfork")]
440 BlobVersionedHashesNotSupported,
441 #[error("max fee per blob gas less than block blob gas fee")]
443 BlobFeeCapTooLow,
444 #[error("blob hash version mismatch")]
446 BlobHashVersionMismatch,
447 #[error("blob transaction missing blob hashes")]
449 BlobTransactionMissingBlobHashes,
450 #[error("blob transaction exceeds max blobs per block; got {have}")]
452 TooManyBlobs {
453 have: usize,
455 },
456 #[error("blob transaction is a create transaction")]
458 BlobTransactionIsCreate,
459 #[error("EOF crate should have `to` address")]
461 EofCrateShouldHaveToAddress,
462 #[error("EIP-7702 authorization list not supported")]
464 AuthorizationListNotSupported,
465 #[error("EIP-7702 authorization list has invalid fields")]
467 AuthorizationListInvalidFields,
468 #[error("{0}")]
470 Other(Box<dyn ToRpcError>),
471}
472
473impl RpcInvalidTransactionError {
474 pub fn other<E: ToRpcError>(err: E) -> Self {
476 Self::Other(Box::new(err))
477 }
478}
479
480impl RpcInvalidTransactionError {
481 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 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 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 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 Self::GasTooHigh
543 }
544 InvalidTransaction::CallGasCostMoreThanGasLimit { .. } => {
545 Self::GasTooLow
547 }
548 InvalidTransaction::GasFloorMoreThanGasLimit { .. } => {
549 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 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 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#[derive(Debug, Clone, thiserror::Error)]
628pub struct RevertError {
629 output: Option<Bytes>,
633}
634
635impl RevertError {
638 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 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 error = error.trim_start_matches("revert: ");
664 }
665 write!(f, ": {error}")?;
666 }
667 Ok(())
668 }
669}
670
671#[derive(Debug, thiserror::Error)]
673pub enum RpcPoolError {
674 #[error("already known")]
676 AlreadyKnown,
677 #[error("invalid sender")]
679 InvalidSender,
680 #[error("transaction underpriced")]
682 Underpriced,
683 #[error("txpool is full")]
685 TxPoolOverflow,
686 #[error("replacement transaction underpriced")]
688 ReplaceUnderpriced,
689 #[error("exceeds block gas limit")]
691 ExceedsGasLimit,
692 #[error("tx fee ({max_tx_fee_wei} wei) exceeds the configured cap ({tx_fee_cap_wei} wei)")]
695 ExceedsFeeCap {
696 max_tx_fee_wei: u128,
698 tx_fee_cap_wei: u128,
700 },
701 #[error("negative value")]
703 NegativeValue,
704 #[error("oversized data")]
706 OversizedData,
707 #[error("max initcode size exceeded")]
709 ExceedsMaxInitCodeSize,
710 #[error(transparent)]
712 Invalid(#[from] RpcInvalidTransactionError),
713 #[error(transparent)]
715 PoolTransactionError(Box<dyn PoolTransactionError>),
716 #[error(transparent)]
718 Eip4844(#[from] Eip4844PoolTransactionError),
719 #[error(transparent)]
721 Eip7702(#[from] Eip7702PoolTransactionError),
722 #[error("address already reserved")]
727 AddressAlreadyReserved,
728 #[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#[derive(Debug, thiserror::Error)]
795pub enum SignError {
796 #[error("could not sign")]
798 CouldNotSign,
799 #[error("unknown account")]
801 NoAccount,
802 #[error("given typed data is not valid")]
804 InvalidTypedData,
805 #[error("invalid transaction request")]
807 InvalidTransactionRequest,
808 #[error("no chainid")]
810 NoChainId,
811}
812
813pub 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}