1pub mod api;
4use alloy_eips::BlockId;
5use alloy_evm::{call::CallError, overrides::StateOverrideError};
6use alloy_primitives::{Address, Bytes, B256, U256};
7use alloy_rpc_types_eth::{error::EthRpcErrorCode, request::TransactionInputError, BlockError};
8use alloy_sol_types::{ContractError, RevertReason};
9use alloy_transport::{RpcError, TransportErrorKind};
10pub use api::{AsEthApiError, FromEthApiError, FromEvmError, IntoEthApiError};
11use core::time::Duration;
12use reth_errors::{BlockExecutionError, BlockValidationError, RethError};
13use reth_primitives_traits::transaction::{error::InvalidTransactionError, signed::RecoveryError};
14use reth_rpc_convert::{CallFeesError, EthTxEnvError, TransactionConversionError};
15use reth_rpc_server_types::result::{
16 block_id_to_str, internal_rpc_err, invalid_params_rpc_err, rpc_err, rpc_error_with_code,
17};
18use reth_transaction_pool::error::{
19 Eip4844PoolTransactionError, Eip7702PoolTransactionError, InvalidPoolTransactionError,
20 PoolError, PoolErrorKind, PoolTransactionError,
21};
22use revm::context_interface::result::{
23 EVMError, HaltReason, InvalidHeader, InvalidTransaction, OutOfGasError,
24};
25use revm_inspectors::tracing::{DebugInspectorError, MuxError};
26use std::convert::Infallible;
27use tokio::sync::oneshot::error::RecvError;
28
29pub trait ToRpcError: core::error::Error + Send + Sync + 'static {
31 fn to_rpc_error(&self) -> jsonrpsee_types::ErrorObject<'static>;
33}
34
35impl ToRpcError for jsonrpsee_types::ErrorObject<'static> {
36 fn to_rpc_error(&self) -> jsonrpsee_types::ErrorObject<'static> {
37 self.clone()
38 }
39}
40
41impl ToRpcError for RpcError<TransportErrorKind> {
42 fn to_rpc_error(&self) -> jsonrpsee_types::ErrorObject<'static> {
43 match self {
44 Self::ErrorResp(payload) => jsonrpsee_types::error::ErrorObject::owned(
45 payload.code as i32,
46 payload.message.clone(),
47 payload.data.clone(),
48 ),
49 err => internal_rpc_err(err.to_string()),
50 }
51 }
52}
53
54pub type EthResult<T> = Result<T, EthApiError>;
56
57#[derive(Debug, thiserror::Error)]
59pub enum EthApiError {
60 #[error("empty transaction data")]
62 EmptyRawTransactionData,
63 #[error("failed to decode signed transaction")]
65 FailedToDecodeSignedTransaction,
66 #[error("invalid transaction signature")]
68 InvalidTransactionSignature,
69 #[error(transparent)]
71 PoolError(#[from] RpcPoolError),
72 #[error("header not found")]
74 HeaderNotFound(BlockId),
75 #[error("header range not found, start block {0:?}, end block {1:?}")]
77 HeaderRangeNotFound(BlockId, BlockId),
78 #[error("pruned history unavailable")]
85 PrunedHistoryUnavailable,
86 #[error("receipts not found")]
88 ReceiptsNotFound(BlockId),
89 #[error("unknown block or tx index")]
91 UnknownBlockOrTxIndex,
92 #[error("invalid block range")]
94 InvalidBlockRange,
95 #[error("request beyond head block: requested {requested}, head {head}")]
97 RequestBeyondHead {
98 requested: u64,
100 head: u64,
102 },
103 #[error("distance to target block exceeds maximum proof window")]
105 ExceedsMaxProofWindow,
106 #[error("prevrandao not in the EVM's environment after merge")]
108 PrevrandaoNotSet,
109 #[error("excess blob gas missing in the EVM's environment after Cancun")]
111 ExcessBlobGasNotSet,
112 #[error("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified")]
115 ConflictingFeeFieldsInRequest,
116 #[error(transparent)]
118 InvalidTransaction(#[from] RpcInvalidTransactionError),
119 #[error(transparent)]
121 InvalidBlockData(#[from] BlockError),
122 #[error("account {0:?} has both 'state' and 'stateDiff'")]
124 BothStateAndStateDiffInOverride(Address),
125 #[error(transparent)]
127 Internal(RethError),
128 #[error(transparent)]
130 Signing(#[from] SignError),
131 #[error("transaction not found")]
133 TransactionNotFound,
134 #[error("unsupported")]
136 Unsupported(&'static str),
137 #[error("{0}")]
139 InvalidParams(String),
140 #[error("invalid tracer config")]
142 InvalidTracerConfig,
143 #[error("invalid reward percentiles")]
145 InvalidRewardPercentiles,
146 #[error("internal blocking task error")]
151 InternalBlockingTaskError,
152 #[error("internal eth error")]
154 InternalEthError,
155 #[error("execution aborted (timeout = {0:?})")]
157 ExecutionTimedOut(Duration),
158 #[error("{0}")]
160 InternalJsTracerError(String),
161 #[error(transparent)]
162 TransactionInputError(#[from] TransactionInputError),
164 #[error("Revm error: {0}")]
166 EvmCustom(String),
167 #[error("Invalid bytecode: {0}")]
173 InvalidBytecode(String),
174 #[error(transparent)]
176 TransactionConversionError(#[from] TransactionConversionError),
177 #[error(transparent)]
179 MuxTracerError(#[from] MuxError),
180 #[error(
182 "Transaction {hash} was added to the mempool but wasn't confirmed within {duration:?}."
183 )]
184 TransactionConfirmationTimeout {
185 hash: B256,
187 duration: Duration,
189 },
190 #[error(transparent)]
192 BatchTxRecvError(#[from] RecvError),
193 #[error("Batch transaction sender channel closed")]
195 BatchTxSendError,
196 #[error("call_many error in bundle {bundle_index} and transaction {tx_index}: {}", .error.message())]
198 CallManyError {
199 bundle_index: usize,
201 tx_index: usize,
203 error: jsonrpsee_types::ErrorObject<'static>,
205 },
206 #[error("{0}")]
208 Other(Box<dyn ToRpcError>),
209}
210
211impl EthApiError {
212 pub fn other<E: ToRpcError>(err: E) -> Self {
214 Self::Other(Box::new(err))
215 }
216
217 pub const fn call_many_error(
219 bundle_index: usize,
220 tx_index: usize,
221 error: jsonrpsee_types::ErrorObject<'static>,
222 ) -> Self {
223 Self::CallManyError { bundle_index, tx_index, error }
224 }
225
226 pub const fn is_gas_too_high(&self) -> bool {
228 matches!(
229 self,
230 Self::InvalidTransaction(
231 RpcInvalidTransactionError::GasTooHigh |
232 RpcInvalidTransactionError::GasLimitTooHigh
233 )
234 )
235 }
236
237 pub const fn is_gas_too_low(&self) -> bool {
239 matches!(self, Self::InvalidTransaction(RpcInvalidTransactionError::GasTooLow))
240 }
241
242 pub const fn as_invalid_transaction(&self) -> Option<&RpcInvalidTransactionError> {
244 match self {
245 Self::InvalidTransaction(e) => Some(e),
246 _ => None,
247 }
248 }
249
250 pub fn from_state_overrides_err<E>(err: StateOverrideError<E>) -> Self
252 where
253 E: Into<Self>,
254 {
255 err.into()
256 }
257
258 pub fn from_call_err<E>(err: CallError<E>) -> Self
260 where
261 E: Into<Self>,
262 {
263 err.into()
264 }
265
266 pub fn into_rpc_err(self) -> jsonrpsee_types::error::ErrorObject<'static> {
268 self.into()
269 }
270}
271
272impl From<EthApiError> for jsonrpsee_types::error::ErrorObject<'static> {
273 fn from(error: EthApiError) -> Self {
274 match error {
275 EthApiError::FailedToDecodeSignedTransaction |
276 EthApiError::InvalidTransactionSignature |
277 EthApiError::EmptyRawTransactionData |
278 EthApiError::InvalidBlockRange |
279 EthApiError::RequestBeyondHead { .. } |
280 EthApiError::ExceedsMaxProofWindow |
281 EthApiError::ConflictingFeeFieldsInRequest |
282 EthApiError::Signing(_) |
283 EthApiError::BothStateAndStateDiffInOverride(_) |
284 EthApiError::InvalidTracerConfig |
285 EthApiError::TransactionConversionError(_) |
286 EthApiError::InvalidRewardPercentiles |
287 EthApiError::InvalidBytecode(_) => invalid_params_rpc_err(error.to_string()),
288 EthApiError::InvalidTransaction(err) => err.into(),
289 EthApiError::PoolError(err) => err.into(),
290 EthApiError::PrevrandaoNotSet |
291 EthApiError::ExcessBlobGasNotSet |
292 EthApiError::InvalidBlockData(_) |
293 EthApiError::Internal(_) |
294 EthApiError::EvmCustom(_) => internal_rpc_err(error.to_string()),
295 EthApiError::UnknownBlockOrTxIndex | EthApiError::TransactionNotFound => {
296 rpc_error_with_code(EthRpcErrorCode::ResourceNotFound.code(), error.to_string())
297 }
298 EthApiError::HeaderNotFound(id) | EthApiError::ReceiptsNotFound(id) => {
299 rpc_error_with_code(
300 EthRpcErrorCode::ResourceNotFound.code(),
301 format!("block not found: {}", block_id_to_str(id)),
302 )
303 }
304 EthApiError::HeaderRangeNotFound(start_id, end_id) => rpc_error_with_code(
305 EthRpcErrorCode::ResourceNotFound.code(),
306 format!(
307 "{error}: start block: {}, end block: {}",
308 block_id_to_str(start_id),
309 block_id_to_str(end_id),
310 ),
311 ),
312 err @ EthApiError::TransactionConfirmationTimeout { .. } => rpc_error_with_code(
313 EthRpcErrorCode::TransactionConfirmationTimeout.code(),
314 err.to_string(),
315 ),
316 EthApiError::Unsupported(msg) => internal_rpc_err(msg),
317 EthApiError::InternalJsTracerError(msg) => internal_rpc_err(msg),
318 EthApiError::InvalidParams(msg) => invalid_params_rpc_err(msg),
319 err @ EthApiError::ExecutionTimedOut(_) => rpc_error_with_code(
320 jsonrpsee_types::error::CALL_EXECUTION_FAILED_CODE,
321 err.to_string(),
322 ),
323 err @ (EthApiError::InternalBlockingTaskError | EthApiError::InternalEthError) => {
324 internal_rpc_err(err.to_string())
325 }
326 err @ EthApiError::TransactionInputError(_) => invalid_params_rpc_err(err.to_string()),
327 EthApiError::PrunedHistoryUnavailable => rpc_error_with_code(4444, error.to_string()),
328 EthApiError::Other(err) => err.to_rpc_error(),
329 EthApiError::MuxTracerError(msg) => internal_rpc_err(msg.to_string()),
330 EthApiError::BatchTxRecvError(err) => internal_rpc_err(err.to_string()),
331 EthApiError::BatchTxSendError => {
332 internal_rpc_err("Batch transaction sender channel closed".to_string())
333 }
334 EthApiError::CallManyError { bundle_index, tx_index, error } => {
335 jsonrpsee_types::error::ErrorObject::owned(
336 error.code(),
337 format!(
338 "call_many error in bundle {bundle_index} and transaction {tx_index}: {}",
339 error.message()
340 ),
341 error.data(),
342 )
343 }
344 }
345 }
346}
347
348impl<E> From<CallError<E>> for EthApiError
349where
350 E: Into<Self>,
351{
352 fn from(value: CallError<E>) -> Self {
353 match value {
354 CallError::Database(err) => err.into(),
355 CallError::InsufficientFunds(insufficient_funds_error) => {
356 Self::InvalidTransaction(RpcInvalidTransactionError::InsufficientFunds {
357 cost: insufficient_funds_error.cost,
358 balance: insufficient_funds_error.balance,
359 })
360 }
361 }
362 }
363}
364
365impl<E> From<StateOverrideError<E>> for EthApiError
366where
367 E: Into<Self>,
368{
369 fn from(value: StateOverrideError<E>) -> Self {
370 match value {
371 StateOverrideError::InvalidBytecode(bytecode_decode_error) => {
372 Self::InvalidBytecode(bytecode_decode_error.to_string())
373 }
374 StateOverrideError::BothStateAndStateDiff(address) => {
375 Self::BothStateAndStateDiffInOverride(address)
376 }
377 StateOverrideError::Database(err) => err.into(),
378 }
379 }
380}
381
382impl From<EthTxEnvError> for EthApiError {
383 fn from(value: EthTxEnvError) -> Self {
384 match value {
385 EthTxEnvError::CallFees(CallFeesError::BlobTransactionMissingBlobHashes) => {
386 Self::InvalidTransaction(
387 RpcInvalidTransactionError::BlobTransactionMissingBlobHashes,
388 )
389 }
390 EthTxEnvError::CallFees(CallFeesError::FeeCapTooLow) => {
391 Self::InvalidTransaction(RpcInvalidTransactionError::FeeCapTooLow)
392 }
393 EthTxEnvError::CallFees(CallFeesError::ConflictingFeeFieldsInRequest) => {
394 Self::ConflictingFeeFieldsInRequest
395 }
396 EthTxEnvError::CallFees(CallFeesError::TipAboveFeeCap) => {
397 Self::InvalidTransaction(RpcInvalidTransactionError::TipAboveFeeCap)
398 }
399 EthTxEnvError::CallFees(CallFeesError::TipVeryHigh) => {
400 Self::InvalidTransaction(RpcInvalidTransactionError::TipVeryHigh)
401 }
402 EthTxEnvError::Input(err) => Self::TransactionInputError(err),
403 }
404 }
405}
406
407#[cfg(feature = "js-tracer")]
408impl From<revm_inspectors::tracing::js::JsInspectorError> for EthApiError {
409 fn from(error: revm_inspectors::tracing::js::JsInspectorError) -> Self {
410 match error {
411 err @ revm_inspectors::tracing::js::JsInspectorError::JsError(_) => {
412 Self::InternalJsTracerError(err.to_string())
413 }
414 err => Self::InvalidParams(err.to_string()),
415 }
416 }
417}
418
419impl<Err> From<DebugInspectorError<Err>> for EthApiError
420where
421 Err: core::error::Error + Send + Sync + 'static,
422{
423 fn from(error: DebugInspectorError<Err>) -> Self {
424 match error {
425 DebugInspectorError::InvalidTracerConfig => Self::InvalidTracerConfig,
426 DebugInspectorError::UnsupportedTracer => Self::Unsupported("unsupported tracer"),
427 DebugInspectorError::JsTracerNotEnabled => {
428 Self::Unsupported("JS Tracer is not enabled")
429 }
430 DebugInspectorError::MuxInspector(err) => err.into(),
431 DebugInspectorError::Database(err) => Self::Internal(RethError::other(err)),
432 #[cfg(feature = "js-tracer")]
433 DebugInspectorError::JsInspector(err) => err.into(),
434 }
435 }
436}
437
438impl From<RethError> for EthApiError {
439 fn from(error: RethError) -> Self {
440 match error {
441 RethError::Provider(err) => err.into(),
442 err => Self::Internal(err),
443 }
444 }
445}
446
447impl From<BlockExecutionError> for EthApiError {
448 fn from(error: BlockExecutionError) -> Self {
449 match error {
450 BlockExecutionError::Validation(validation_error) => match validation_error {
451 BlockValidationError::InvalidTx { error, .. } => {
452 if let Some(invalid_tx) = error.as_invalid_tx_err() {
453 Self::InvalidTransaction(RpcInvalidTransactionError::from(
454 invalid_tx.clone(),
455 ))
456 } else {
457 Self::InvalidTransaction(RpcInvalidTransactionError::other(
458 rpc_error_with_code(
459 EthRpcErrorCode::TransactionRejected.code(),
460 error.to_string(),
461 ),
462 ))
463 }
464 }
465 _ => Self::Internal(RethError::Execution(BlockExecutionError::Validation(
466 validation_error,
467 ))),
468 },
469 BlockExecutionError::Internal(internal_error) => {
470 Self::Internal(RethError::Execution(BlockExecutionError::Internal(internal_error)))
471 }
472 }
473 }
474}
475
476impl From<reth_errors::ProviderError> for EthApiError {
477 fn from(error: reth_errors::ProviderError) -> Self {
478 use reth_errors::ProviderError;
479 match error {
480 ProviderError::HeaderNotFound(hash) => Self::HeaderNotFound(hash.into()),
481 ProviderError::BlockHashNotFound(hash) | ProviderError::UnknownBlockHash(hash) => {
482 Self::HeaderNotFound(hash.into())
483 }
484 ProviderError::BestBlockNotFound => Self::HeaderNotFound(BlockId::latest()),
485 ProviderError::BlockNumberForTransactionIndexNotFound => Self::UnknownBlockOrTxIndex,
486 ProviderError::FinalizedBlockNotFound => Self::HeaderNotFound(BlockId::finalized()),
487 ProviderError::SafeBlockNotFound => Self::HeaderNotFound(BlockId::safe()),
488 err => Self::Internal(err.into()),
489 }
490 }
491}
492
493impl From<InvalidHeader> for EthApiError {
494 fn from(value: InvalidHeader) -> Self {
495 match value {
496 InvalidHeader::ExcessBlobGasNotSet => Self::ExcessBlobGasNotSet,
497 InvalidHeader::PrevrandaoNotSet => Self::PrevrandaoNotSet,
498 }
499 }
500}
501
502impl<T, TxError> From<EVMError<T, TxError>> for EthApiError
503where
504 T: Into<Self>,
505 TxError: reth_evm::InvalidTxError,
506{
507 fn from(err: EVMError<T, TxError>) -> Self {
508 match err {
509 EVMError::Transaction(invalid_tx) => {
510 if let Some(eth_tx_err) = invalid_tx.as_invalid_tx_err() {
512 match eth_tx_err {
514 InvalidTransaction::NonceTooLow { tx, state } => {
515 Self::InvalidTransaction(RpcInvalidTransactionError::NonceTooLow {
516 tx: *tx,
517 state: *state,
518 })
519 }
520 _ => RpcInvalidTransactionError::from(eth_tx_err.clone()).into(),
521 }
522 } else {
523 Self::EvmCustom(invalid_tx.to_string())
526 }
527 }
528 EVMError::Header(err) => err.into(),
529 EVMError::Database(err) => err.into(),
530 EVMError::Custom(err) => Self::EvmCustom(err),
531 }
532 }
533}
534
535impl From<RecoveryError> for EthApiError {
536 fn from(_: RecoveryError) -> Self {
537 Self::InvalidTransactionSignature
538 }
539}
540
541impl From<Infallible> for EthApiError {
542 fn from(_: Infallible) -> Self {
543 unreachable!()
544 }
545}
546
547#[derive(thiserror::Error, Debug)]
562pub enum RpcInvalidTransactionError {
563 #[error("nonce too low: next nonce {state}, tx nonce {tx}")]
565 NonceTooLow {
566 tx: u64,
568 state: u64,
570 },
571 #[error("nonce too high")]
574 NonceTooHigh,
575 #[error("nonce has max value")]
578 NonceMaxValue,
579 #[error("insufficient funds for transfer")]
581 InsufficientFundsForTransfer,
582 #[error("max initcode size exceeded")]
584 MaxInitCodeSizeExceeded,
585 #[error("insufficient funds for gas * price + value: have {balance} want {cost}")]
587 InsufficientFunds {
588 cost: U256,
590 balance: U256,
592 },
593 #[error("gas required exceeds allowance ({gas_limit})")]
599 GasRequiredExceedsAllowance {
600 gas_limit: u64,
602 },
603 #[error("gas uint64 overflow")]
605 GasUintOverflow,
606 #[error("intrinsic gas too low")]
609 GasTooLow,
610 #[error("intrinsic gas too high")]
612 GasTooHigh,
613 #[error("gas limit too high")]
615 GasLimitTooHigh,
616 #[error("transaction type not supported")]
618 TxTypeNotSupported,
619 #[error("max priority fee per gas higher than max fee per gas")]
622 TipAboveFeeCap,
623 #[error("max priority fee per gas higher than 2^256-1")]
625 TipVeryHigh,
626 #[error("max fee per gas higher than 2^256-1")]
628 FeeCapVeryHigh,
629 #[error("max fee per gas less than block base fee")]
631 FeeCapTooLow,
632 #[error("sender is not an EOA")]
634 SenderNoEOA,
635 #[error("out of gas: gas required exceeds: {0}")]
638 BasicOutOfGas(u64),
639 #[error("out of gas: gas exhausted during memory expansion: {0}")]
642 MemoryOutOfGas(u64),
643 #[error("out of memory: memory limit exceeded during memory expansion")]
645 MemoryLimitOutOfGas,
646 #[error("out of gas: gas exhausted during precompiled contract execution: {0}")]
649 PrecompileOutOfGas(u64),
650 #[error("out of gas: invalid operand to an opcode: {0}")]
653 InvalidOperandOutOfGas(u64),
654 #[error(transparent)]
656 Revert(RevertError),
657 #[error("EVM error: {0:?}")]
659 EvmHalt(HaltReason),
660 #[error("invalid chain ID")]
662 InvalidChainId,
663 #[error("transactions before Spurious Dragon should not have a chain ID")]
665 OldLegacyChainId,
666 #[error("transactions before Berlin should not have access list")]
668 AccessListNotSupported,
669 #[error("max_fee_per_blob_gas is not supported for blocks before the Cancun hardfork")]
671 MaxFeePerBlobGasNotSupported,
672 #[error("blob_versioned_hashes is not supported for blocks before the Cancun hardfork")]
675 BlobVersionedHashesNotSupported,
676 #[error("max fee per blob gas less than block blob gas fee")]
678 BlobFeeCapTooLow,
679 #[error("blob hash version mismatch")]
681 BlobHashVersionMismatch,
682 #[error("blob transaction missing blob hashes")]
684 BlobTransactionMissingBlobHashes,
685 #[error("blob transaction exceeds max blobs per block; got {have}")]
687 TooManyBlobs {
688 have: usize,
690 },
691 #[error("blob transaction is a create transaction")]
693 BlobTransactionIsCreate,
694 #[error("EIP-7702 authorization list not supported")]
696 AuthorizationListNotSupported,
697 #[error("EIP-7702 authorization list has invalid fields")]
699 AuthorizationListInvalidFields,
700 #[error("transaction priority fee below minimum required priority fee {minimum_priority_fee}")]
702 PriorityFeeBelowMinimum {
703 minimum_priority_fee: u128,
705 },
706 #[error("{0}")]
708 Other(Box<dyn ToRpcError>),
709}
710
711impl RpcInvalidTransactionError {
712 pub fn other<E: ToRpcError>(err: E) -> Self {
714 Self::Other(Box::new(err))
715 }
716
717 pub const fn error_code(&self) -> i32 {
719 match self {
720 Self::InvalidChainId |
721 Self::GasTooLow |
722 Self::GasTooHigh |
723 Self::GasRequiredExceedsAllowance { .. } |
724 Self::NonceTooLow { .. } |
725 Self::NonceTooHigh { .. } |
726 Self::FeeCapTooLow |
727 Self::FeeCapVeryHigh => EthRpcErrorCode::InvalidInput.code(),
728 Self::Revert(_) => EthRpcErrorCode::ExecutionError.code(),
729 _ => EthRpcErrorCode::TransactionRejected.code(),
730 }
731 }
732
733 pub fn halt(reason: HaltReason, gas_limit: u64) -> Self {
737 match reason {
738 HaltReason::OutOfGas(err) => Self::out_of_gas(err, gas_limit),
739 HaltReason::NonceOverflow => Self::NonceMaxValue,
740 err => Self::EvmHalt(err),
741 }
742 }
743
744 pub const fn out_of_gas(reason: OutOfGasError, gas_limit: u64) -> Self {
746 match reason {
747 OutOfGasError::Basic | OutOfGasError::ReentrancySentry => {
748 Self::BasicOutOfGas(gas_limit)
749 }
750 OutOfGasError::Memory => Self::MemoryOutOfGas(gas_limit),
751 OutOfGasError::MemoryLimit => Self::MemoryLimitOutOfGas,
752 OutOfGasError::Precompile => Self::PrecompileOutOfGas(gas_limit),
753 OutOfGasError::InvalidOperand => Self::InvalidOperandOutOfGas(gas_limit),
754 }
755 }
756
757 pub fn into_rpc_err(self) -> jsonrpsee_types::error::ErrorObject<'static> {
759 self.into()
760 }
761}
762
763impl From<RpcInvalidTransactionError> for jsonrpsee_types::error::ErrorObject<'static> {
764 fn from(err: RpcInvalidTransactionError) -> Self {
765 match err {
766 RpcInvalidTransactionError::Revert(revert) => {
767 rpc_err(
769 revert.error_code(),
770 revert.to_string(),
771 revert.output.as_ref().map(|out| out.as_ref()),
772 )
773 }
774 RpcInvalidTransactionError::Other(err) => err.to_rpc_error(),
775 err => rpc_err(err.error_code(), err.to_string(), None),
776 }
777 }
778}
779
780impl From<InvalidTransaction> for RpcInvalidTransactionError {
781 fn from(err: InvalidTransaction) -> Self {
782 match err {
783 InvalidTransaction::InvalidChainId | InvalidTransaction::MissingChainId => {
784 Self::InvalidChainId
785 }
786 InvalidTransaction::PriorityFeeGreaterThanMaxFee => Self::TipAboveFeeCap,
787 InvalidTransaction::GasPriceLessThanBasefee => Self::FeeCapTooLow,
788 InvalidTransaction::CallerGasLimitMoreThanBlock |
789 InvalidTransaction::TxGasLimitGreaterThanCap { .. } => {
790 Self::GasTooHigh
792 }
793 InvalidTransaction::CallGasCostMoreThanGasLimit { .. } => {
794 Self::GasTooLow
796 }
797 InvalidTransaction::GasFloorMoreThanGasLimit { .. } => {
798 Self::GasTooLow
803 }
804 InvalidTransaction::RejectCallerWithCode => Self::SenderNoEOA,
805 InvalidTransaction::LackOfFundForMaxFee { fee, balance } => {
806 Self::InsufficientFunds { cost: *fee, balance: *balance }
807 }
808 InvalidTransaction::OverflowPaymentInTransaction => Self::GasUintOverflow,
809 InvalidTransaction::NonceOverflowInTransaction => Self::NonceMaxValue,
810 InvalidTransaction::CreateInitCodeSizeLimit => Self::MaxInitCodeSizeExceeded,
811 InvalidTransaction::NonceTooHigh { .. } => Self::NonceTooHigh,
812 InvalidTransaction::NonceTooLow { tx, state } => Self::NonceTooLow { tx, state },
813 InvalidTransaction::AccessListNotSupported => Self::AccessListNotSupported,
814 InvalidTransaction::MaxFeePerBlobGasNotSupported => Self::MaxFeePerBlobGasNotSupported,
815 InvalidTransaction::BlobVersionedHashesNotSupported => {
816 Self::BlobVersionedHashesNotSupported
817 }
818 InvalidTransaction::BlobGasPriceGreaterThanMax { .. } => Self::BlobFeeCapTooLow,
819 InvalidTransaction::EmptyBlobs => Self::BlobTransactionMissingBlobHashes,
820 InvalidTransaction::BlobVersionNotSupported => Self::BlobHashVersionMismatch,
821 InvalidTransaction::TooManyBlobs { have, .. } => Self::TooManyBlobs { have },
822 InvalidTransaction::BlobCreateTransaction => Self::BlobTransactionIsCreate,
823 InvalidTransaction::AuthorizationListNotSupported => {
824 Self::AuthorizationListNotSupported
825 }
826 InvalidTransaction::AuthorizationListInvalidFields |
827 InvalidTransaction::EmptyAuthorizationList => Self::AuthorizationListInvalidFields,
828 InvalidTransaction::Eip2930NotSupported |
829 InvalidTransaction::Eip1559NotSupported |
830 InvalidTransaction::Eip4844NotSupported |
831 InvalidTransaction::Eip7702NotSupported |
832 InvalidTransaction::Eip7873NotSupported => Self::TxTypeNotSupported,
833 InvalidTransaction::Eip7873MissingTarget => {
834 Self::other(internal_rpc_err(err.to_string()))
835 }
836 InvalidTransaction::Str(_) => Self::other(internal_rpc_err(err.to_string())),
837 }
838 }
839}
840
841impl From<InvalidTransactionError> for RpcInvalidTransactionError {
842 fn from(err: InvalidTransactionError) -> Self {
843 use InvalidTransactionError;
844 match err {
847 InvalidTransactionError::InsufficientFunds(res) => {
848 Self::InsufficientFunds { cost: res.expected, balance: res.got }
849 }
850 InvalidTransactionError::NonceNotConsistent { tx, state } => {
851 Self::NonceTooLow { tx, state }
852 }
853 InvalidTransactionError::OldLegacyChainId => {
854 Self::OldLegacyChainId
856 }
857 InvalidTransactionError::ChainIdMismatch => Self::InvalidChainId,
858 InvalidTransactionError::Eip2930Disabled |
859 InvalidTransactionError::Eip1559Disabled |
860 InvalidTransactionError::Eip4844Disabled |
861 InvalidTransactionError::Eip7702Disabled |
862 InvalidTransactionError::TxTypeNotSupported => Self::TxTypeNotSupported,
863 InvalidTransactionError::GasUintOverflow => Self::GasUintOverflow,
864 InvalidTransactionError::GasTooLow => Self::GasTooLow,
865 InvalidTransactionError::GasTooHigh => Self::GasTooHigh,
866 InvalidTransactionError::TipAboveFeeCap => Self::TipAboveFeeCap,
867 InvalidTransactionError::FeeCapTooLow => Self::FeeCapTooLow,
868 InvalidTransactionError::SignerAccountHasBytecode => Self::SenderNoEOA,
869 InvalidTransactionError::GasLimitTooHigh => Self::GasLimitTooHigh,
870 }
871 }
872}
873
874#[derive(Debug, Clone, thiserror::Error)]
878pub struct RevertError {
879 output: Option<Bytes>,
883}
884
885impl RevertError {
888 pub fn new(output: Bytes) -> Self {
892 if output.is_empty() {
893 Self { output: None }
894 } else {
895 Self { output: Some(output) }
896 }
897 }
898
899 pub const fn error_code(&self) -> i32 {
901 EthRpcErrorCode::ExecutionError.code()
902 }
903}
904
905impl std::fmt::Display for RevertError {
906 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
907 f.write_str("execution reverted")?;
908 if let Some(reason) = self.output.as_ref().and_then(|out| RevertReason::decode(out)) {
909 let error = reason.to_string();
910 let mut error = error.as_str();
911 if matches!(reason, RevertReason::ContractError(ContractError::Revert(_))) {
912 error = error.trim_start_matches("revert: ");
914 }
915 write!(f, ": {error}")?;
916 }
917 Ok(())
918 }
919}
920
921#[derive(Debug, thiserror::Error)]
923pub enum RpcPoolError {
924 #[error("already known")]
926 AlreadyKnown,
927 #[error("invalid sender")]
929 InvalidSender,
930 #[error("transaction underpriced")]
932 Underpriced,
933 #[error("txpool is full")]
935 TxPoolOverflow,
936 #[error("replacement transaction underpriced")]
938 ReplaceUnderpriced,
939 #[error("exceeds block gas limit")]
941 ExceedsGasLimit,
942 #[error("exceeds max transaction gas limit")]
944 MaxTxGasLimitExceeded,
945 #[error("tx fee ({max_tx_fee_wei} wei) exceeds the configured cap ({tx_fee_cap_wei} wei)")]
948 ExceedsFeeCap {
949 max_tx_fee_wei: u128,
951 tx_fee_cap_wei: u128,
953 },
954 #[error("negative value")]
956 NegativeValue,
957 #[error("oversized data: transaction size {size}, limit {limit}")]
959 OversizedData {
960 size: usize,
962 limit: usize,
964 },
965 #[error("max initcode size exceeded")]
967 ExceedsMaxInitCodeSize,
968 #[error(transparent)]
970 Invalid(#[from] RpcInvalidTransactionError),
971 #[error(transparent)]
973 PoolTransactionError(Box<dyn PoolTransactionError>),
974 #[error(transparent)]
976 Eip4844(#[from] Eip4844PoolTransactionError),
977 #[error(transparent)]
979 Eip7702(#[from] Eip7702PoolTransactionError),
980 #[error("address already reserved")]
985 AddressAlreadyReserved,
986 #[error(transparent)]
988 Other(Box<dyn core::error::Error + Send + Sync>),
989}
990
991impl From<RpcPoolError> for jsonrpsee_types::error::ErrorObject<'static> {
992 fn from(error: RpcPoolError) -> Self {
993 match error {
994 RpcPoolError::Invalid(err) => err.into(),
995 RpcPoolError::TxPoolOverflow => {
996 rpc_error_with_code(EthRpcErrorCode::TransactionRejected.code(), error.to_string())
997 }
998 RpcPoolError::AlreadyKnown |
999 RpcPoolError::InvalidSender |
1000 RpcPoolError::Underpriced |
1001 RpcPoolError::ReplaceUnderpriced |
1002 RpcPoolError::ExceedsGasLimit |
1003 RpcPoolError::MaxTxGasLimitExceeded |
1004 RpcPoolError::ExceedsFeeCap { .. } |
1005 RpcPoolError::NegativeValue |
1006 RpcPoolError::OversizedData { .. } |
1007 RpcPoolError::ExceedsMaxInitCodeSize |
1008 RpcPoolError::PoolTransactionError(_) |
1009 RpcPoolError::Eip4844(_) |
1010 RpcPoolError::Eip7702(_) |
1011 RpcPoolError::AddressAlreadyReserved => {
1012 rpc_error_with_code(EthRpcErrorCode::InvalidInput.code(), error.to_string())
1013 }
1014 RpcPoolError::Other(other) => internal_rpc_err(other.to_string()),
1015 }
1016 }
1017}
1018
1019impl From<PoolError> for RpcPoolError {
1020 fn from(err: PoolError) -> Self {
1021 match err.kind {
1022 PoolErrorKind::ReplacementUnderpriced => Self::ReplaceUnderpriced,
1023 PoolErrorKind::FeeCapBelowMinimumProtocolFeeCap(_) => Self::Underpriced,
1024 PoolErrorKind::SpammerExceededCapacity(_) | PoolErrorKind::DiscardedOnInsert => {
1025 Self::TxPoolOverflow
1026 }
1027 PoolErrorKind::InvalidTransaction(err) => err.into(),
1028 PoolErrorKind::Other(err) => Self::Other(err),
1029 PoolErrorKind::AlreadyImported => Self::AlreadyKnown,
1030 PoolErrorKind::ExistingConflictingTransactionType(_, _) => Self::AddressAlreadyReserved,
1031 }
1032 }
1033}
1034
1035impl From<InvalidPoolTransactionError> for RpcPoolError {
1036 fn from(err: InvalidPoolTransactionError) -> Self {
1037 match err {
1038 InvalidPoolTransactionError::Consensus(err) => Self::Invalid(err.into()),
1039 InvalidPoolTransactionError::ExceedsGasLimit(_, _) => Self::ExceedsGasLimit,
1040 InvalidPoolTransactionError::MaxTxGasLimitExceeded(_, _) => Self::MaxTxGasLimitExceeded,
1041 InvalidPoolTransactionError::ExceedsFeeCap { max_tx_fee_wei, tx_fee_cap_wei } => {
1042 Self::ExceedsFeeCap { max_tx_fee_wei, tx_fee_cap_wei }
1043 }
1044 InvalidPoolTransactionError::ExceedsMaxInitCodeSize(_, _) => {
1045 Self::ExceedsMaxInitCodeSize
1046 }
1047 InvalidPoolTransactionError::IntrinsicGasTooLow => {
1048 Self::Invalid(RpcInvalidTransactionError::GasTooLow)
1049 }
1050 InvalidPoolTransactionError::OversizedData { size, limit } => {
1051 Self::OversizedData { size, limit }
1052 }
1053 InvalidPoolTransactionError::Underpriced => Self::Underpriced,
1054 InvalidPoolTransactionError::Eip2681 => {
1055 Self::Invalid(RpcInvalidTransactionError::NonceMaxValue)
1056 }
1057 InvalidPoolTransactionError::Other(err) => Self::PoolTransactionError(err),
1058 InvalidPoolTransactionError::Eip4844(err) => Self::Eip4844(err),
1059 InvalidPoolTransactionError::Eip7702(err) => Self::Eip7702(err),
1060 InvalidPoolTransactionError::Overdraft { cost, balance } => {
1061 Self::Invalid(RpcInvalidTransactionError::InsufficientFunds { cost, balance })
1062 }
1063 InvalidPoolTransactionError::PriorityFeeBelowMinimum { minimum_priority_fee } => {
1064 Self::Invalid(RpcInvalidTransactionError::PriorityFeeBelowMinimum {
1065 minimum_priority_fee,
1066 })
1067 }
1068 }
1069 }
1070}
1071
1072impl From<PoolError> for EthApiError {
1073 fn from(err: PoolError) -> Self {
1074 Self::PoolError(RpcPoolError::from(err))
1075 }
1076}
1077
1078#[derive(Debug, thiserror::Error)]
1080pub enum SignError {
1081 #[error("could not sign")]
1083 CouldNotSign,
1084 #[error("unknown account")]
1086 NoAccount,
1087 #[error("given typed data is not valid")]
1089 InvalidTypedData,
1090 #[error("invalid transaction request")]
1092 InvalidTransactionRequest,
1093 #[error("no chainid")]
1095 NoChainId,
1096}
1097
1098#[cfg(test)]
1099mod tests {
1100 use super::*;
1101 use alloy_sol_types::{Revert, SolError};
1102 use revm::primitives::b256;
1103
1104 #[test]
1105 fn timed_out_error() {
1106 let err = EthApiError::ExecutionTimedOut(Duration::from_secs(10));
1107 assert_eq!(err.to_string(), "execution aborted (timeout = 10s)");
1108 }
1109
1110 #[test]
1111 fn header_not_found_message() {
1112 let err: jsonrpsee_types::error::ErrorObject<'static> =
1113 EthApiError::HeaderNotFound(BlockId::hash(b256!(
1114 "0x1a15e3c30cf094a99826869517b16d185d45831d3a494f01030b0001a9d3ebb9"
1115 )))
1116 .into();
1117 assert_eq!(
1118 err.message(),
1119 "block not found: hash 0x1a15e3c30cf094a99826869517b16d185d45831d3a494f01030b0001a9d3ebb9"
1120 );
1121 let err: jsonrpsee_types::error::ErrorObject<'static> =
1122 EthApiError::HeaderNotFound(BlockId::hash_canonical(b256!(
1123 "0x1a15e3c30cf094a99826869517b16d185d45831d3a494f01030b0001a9d3ebb9"
1124 )))
1125 .into();
1126 assert_eq!(
1127 err.message(),
1128 "block not found: canonical hash 0x1a15e3c30cf094a99826869517b16d185d45831d3a494f01030b0001a9d3ebb9"
1129 );
1130 let err: jsonrpsee_types::error::ErrorObject<'static> =
1131 EthApiError::HeaderNotFound(BlockId::number(100000)).into();
1132 assert_eq!(err.message(), "block not found: 0x186a0");
1133 let err: jsonrpsee_types::error::ErrorObject<'static> =
1134 EthApiError::HeaderNotFound(BlockId::latest()).into();
1135 assert_eq!(err.message(), "block not found: latest");
1136 let err: jsonrpsee_types::error::ErrorObject<'static> =
1137 EthApiError::HeaderNotFound(BlockId::safe()).into();
1138 assert_eq!(err.message(), "block not found: safe");
1139 let err: jsonrpsee_types::error::ErrorObject<'static> =
1140 EthApiError::HeaderNotFound(BlockId::finalized()).into();
1141 assert_eq!(err.message(), "block not found: finalized");
1142 }
1143
1144 #[test]
1145 fn receipts_not_found_message() {
1146 let err: jsonrpsee_types::error::ErrorObject<'static> =
1147 EthApiError::ReceiptsNotFound(BlockId::hash(b256!(
1148 "0x1a15e3c30cf094a99826869517b16d185d45831d3a494f01030b0001a9d3ebb9"
1149 )))
1150 .into();
1151 assert_eq!(
1152 err.message(),
1153 "block not found: hash 0x1a15e3c30cf094a99826869517b16d185d45831d3a494f01030b0001a9d3ebb9"
1154 );
1155 let err: jsonrpsee_types::error::ErrorObject<'static> =
1156 EthApiError::ReceiptsNotFound(BlockId::hash_canonical(b256!(
1157 "0x1a15e3c30cf094a99826869517b16d185d45831d3a494f01030b0001a9d3ebb9"
1158 )))
1159 .into();
1160 assert_eq!(
1161 err.message(),
1162 "block not found: canonical hash 0x1a15e3c30cf094a99826869517b16d185d45831d3a494f01030b0001a9d3ebb9"
1163 );
1164 let err: jsonrpsee_types::error::ErrorObject<'static> =
1165 EthApiError::ReceiptsNotFound(BlockId::number(100000)).into();
1166 assert_eq!(err.code(), EthRpcErrorCode::ResourceNotFound.code());
1167 assert_eq!(err.message(), "block not found: 0x186a0");
1168 let err: jsonrpsee_types::error::ErrorObject<'static> =
1169 EthApiError::ReceiptsNotFound(BlockId::latest()).into();
1170 assert_eq!(err.message(), "block not found: latest");
1171 let err: jsonrpsee_types::error::ErrorObject<'static> =
1172 EthApiError::ReceiptsNotFound(BlockId::safe()).into();
1173 assert_eq!(err.message(), "block not found: safe");
1174 let err: jsonrpsee_types::error::ErrorObject<'static> =
1175 EthApiError::ReceiptsNotFound(BlockId::finalized()).into();
1176 assert_eq!(err.message(), "block not found: finalized");
1177 let err: jsonrpsee_types::error::ErrorObject<'static> =
1178 EthApiError::ReceiptsNotFound(BlockId::pending()).into();
1179 assert_eq!(err.message(), "block not found: pending");
1180 let err: jsonrpsee_types::error::ErrorObject<'static> =
1181 EthApiError::ReceiptsNotFound(BlockId::earliest()).into();
1182 assert_eq!(err.message(), "block not found: earliest");
1183 }
1184
1185 #[test]
1186 fn revert_err_display() {
1187 let revert = Revert::from("test_revert_reason");
1188 let err = RevertError::new(revert.abi_encode().into());
1189 let msg = err.to_string();
1190 assert_eq!(msg, "execution reverted: test_revert_reason");
1191 }
1192}