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_revm::db::bal::EvmDatabaseError;
15use reth_rpc_convert::{CallFeesError, EthTxEnvError, TransactionConversionError};
16use reth_rpc_server_types::result::{
17 block_id_to_str, internal_rpc_err, invalid_params_rpc_err, rpc_err, rpc_error_with_code,
18};
19use reth_transaction_pool::error::{
20 Eip4844PoolTransactionError, Eip7702PoolTransactionError, InvalidPoolTransactionError,
21 PoolError, PoolErrorKind, PoolTransactionError, RawPoolTransactionError,
22};
23use revm::{
24 context_interface::result::{
25 EVMError, HaltReason, InvalidHeader, InvalidTransaction, OutOfGasError,
26 },
27 state::bal::BalError,
28};
29use revm_inspectors::tracing::{DebugInspectorError, MuxError};
30use std::convert::Infallible;
31use tokio::sync::oneshot::error::RecvError;
32
33pub trait ToRpcError: core::error::Error + Send + Sync + 'static {
35 fn to_rpc_error(&self) -> jsonrpsee_types::ErrorObject<'static>;
37}
38
39impl ToRpcError for jsonrpsee_types::ErrorObject<'static> {
40 fn to_rpc_error(&self) -> jsonrpsee_types::ErrorObject<'static> {
41 self.clone()
42 }
43}
44
45impl ToRpcError for RpcError<TransportErrorKind> {
46 fn to_rpc_error(&self) -> jsonrpsee_types::ErrorObject<'static> {
47 match self {
48 Self::ErrorResp(payload) => jsonrpsee_types::error::ErrorObject::owned(
49 payload.code as i32,
50 payload.message.clone(),
51 payload.data.clone(),
52 ),
53 err => internal_rpc_err(err.to_string()),
54 }
55 }
56}
57
58pub type EthResult<T> = Result<T, EthApiError>;
60
61#[derive(Debug, thiserror::Error)]
63pub enum EthApiError {
64 #[error("empty transaction data")]
66 EmptyRawTransactionData,
67 #[error("failed to decode signed transaction")]
69 FailedToDecodeSignedTransaction,
70 #[error("invalid transaction signature")]
72 InvalidTransactionSignature,
73 #[error(transparent)]
75 PoolError(#[from] RpcPoolError),
76 #[error("header not found")]
78 HeaderNotFound(BlockId),
79 #[error("header range not found, start block {0:?}, end block {1:?}")]
81 HeaderRangeNotFound(BlockId, BlockId),
82 #[error("Pruned history unavailable")]
89 PrunedHistoryUnavailable,
90 #[error("receipts not found")]
92 ReceiptsNotFound(BlockId),
93 #[error("unknown block or tx index")]
95 UnknownBlockOrTxIndex,
96 #[error("invalid block range")]
98 InvalidBlockRange,
99 #[error("request beyond head block: requested {requested}, head {head}")]
101 RequestBeyondHead {
102 requested: u64,
104 head: u64,
106 },
107 #[error("distance to target block exceeds maximum proof window")]
109 ExceedsMaxProofWindow,
110 #[error("prevrandao not in the EVM's environment after merge")]
112 PrevrandaoNotSet,
113 #[error("excess blob gas missing in the EVM's environment after Cancun")]
115 ExcessBlobGasNotSet,
116 #[error("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified")]
119 ConflictingFeeFieldsInRequest,
120 #[error(transparent)]
122 InvalidTransaction(#[from] RpcInvalidTransactionError),
123 #[error(transparent)]
125 InvalidBlockData(#[from] BlockError),
126 #[error("account {0:?} has both 'state' and 'stateDiff'")]
128 BothStateAndStateDiffInOverride(Address),
129 #[error(transparent)]
131 Internal(RethError),
132 #[error(transparent)]
134 Signing(#[from] SignError),
135 #[error("transaction not found")]
137 TransactionNotFound,
138 #[error("unsupported")]
140 Unsupported(&'static str),
141 #[error("{0}")]
143 InvalidParams(String),
144 #[error("invalid tracer config")]
146 InvalidTracerConfig,
147 #[error("invalid reward percentiles")]
149 InvalidRewardPercentiles,
150 #[error("internal blocking task error")]
155 InternalBlockingTaskError,
156 #[error("internal eth error")]
158 InternalEthError,
159 #[error("execution aborted (timeout = {0:?})")]
161 ExecutionTimedOut(Duration),
162 #[error("{0}")]
164 InternalJsTracerError(String),
165 #[error(transparent)]
166 TransactionInputError(#[from] TransactionInputError),
168 #[error("Revm error: {0}")]
170 EvmCustom(String),
171 #[error("Invalid bytecode: {0}")]
177 InvalidBytecode(String),
178 #[error(transparent)]
180 TransactionConversionError(#[from] TransactionConversionError),
181 #[error(transparent)]
183 MuxTracerError(#[from] MuxError),
184 #[error(
186 "Transaction {hash} was added to the mempool but wasn't confirmed within {duration:?}."
187 )]
188 TransactionConfirmationTimeout {
189 hash: B256,
191 duration: Duration,
193 },
194 #[error(transparent)]
196 BatchTxRecvError(#[from] RecvError),
197 #[error("Batch transaction sender channel closed")]
199 BatchTxSendError,
200 #[error("call_many error in bundle {bundle_index} and transaction {tx_index}: {}", .error.message())]
202 CallManyError {
203 bundle_index: usize,
205 tx_index: usize,
207 error: jsonrpsee_types::ErrorObject<'static>,
209 },
210 #[error("Block access list not available for pre-Amsterdam blocks")]
212 BlockAccessListNotAvailablePreAmsterdam,
213 #[error("{0}")]
215 Other(Box<dyn ToRpcError>),
216}
217
218impl EthApiError {
219 pub fn other<E: ToRpcError>(err: E) -> Self {
221 Self::Other(Box::new(err))
222 }
223
224 pub const fn call_many_error(
226 bundle_index: usize,
227 tx_index: usize,
228 error: jsonrpsee_types::ErrorObject<'static>,
229 ) -> Self {
230 Self::CallManyError { bundle_index, tx_index, error }
231 }
232
233 pub const fn is_gas_too_high(&self) -> bool {
235 matches!(
236 self,
237 Self::InvalidTransaction(
238 RpcInvalidTransactionError::GasTooHigh |
239 RpcInvalidTransactionError::GasLimitTooHigh
240 )
241 )
242 }
243
244 pub const fn is_gas_too_low(&self) -> bool {
246 matches!(self, Self::InvalidTransaction(RpcInvalidTransactionError::GasTooLow))
247 }
248
249 pub const fn as_invalid_transaction(&self) -> Option<&RpcInvalidTransactionError> {
251 match self {
252 Self::InvalidTransaction(e) => Some(e),
253 _ => None,
254 }
255 }
256
257 pub fn from_state_overrides_err<E>(err: StateOverrideError<E>) -> Self
259 where
260 E: Into<Self>,
261 {
262 err.into()
263 }
264
265 pub fn from_call_err<E>(err: CallError<E>) -> Self
267 where
268 E: Into<Self>,
269 {
270 err.into()
271 }
272
273 pub fn into_rpc_err(self) -> jsonrpsee_types::error::ErrorObject<'static> {
275 self.into()
276 }
277}
278
279impl From<EthApiError> for jsonrpsee_types::error::ErrorObject<'static> {
280 fn from(error: EthApiError) -> Self {
281 match error {
282 EthApiError::FailedToDecodeSignedTransaction |
283 EthApiError::InvalidTransactionSignature |
284 EthApiError::EmptyRawTransactionData |
285 EthApiError::InvalidBlockRange |
286 EthApiError::RequestBeyondHead { .. } |
287 EthApiError::ExceedsMaxProofWindow |
288 EthApiError::ConflictingFeeFieldsInRequest |
289 EthApiError::Signing(_) |
290 EthApiError::BothStateAndStateDiffInOverride(_) |
291 EthApiError::InvalidTracerConfig |
292 EthApiError::TransactionConversionError(_) |
293 EthApiError::InvalidRewardPercentiles |
294 EthApiError::InvalidBytecode(_) => invalid_params_rpc_err(error.to_string()),
295 EthApiError::InvalidTransaction(err) => err.into(),
296 EthApiError::PoolError(err) => err.into(),
297 EthApiError::PrevrandaoNotSet |
298 EthApiError::ExcessBlobGasNotSet |
299 EthApiError::InvalidBlockData(_) |
300 EthApiError::Internal(_) |
301 EthApiError::EvmCustom(_) => internal_rpc_err(error.to_string()),
302 EthApiError::UnknownBlockOrTxIndex | EthApiError::TransactionNotFound => {
303 rpc_error_with_code(EthRpcErrorCode::ResourceNotFound.code(), error.to_string())
304 }
305 EthApiError::HeaderNotFound(id) | EthApiError::ReceiptsNotFound(id) => {
306 rpc_error_with_code(
307 EthRpcErrorCode::ResourceNotFound.code(),
308 format!("block not found: {}", block_id_to_str(id)),
309 )
310 }
311 EthApiError::HeaderRangeNotFound(start_id, end_id) => rpc_error_with_code(
312 EthRpcErrorCode::ResourceNotFound.code(),
313 format!(
314 "{error}: start block: {}, end block: {}",
315 block_id_to_str(start_id),
316 block_id_to_str(end_id),
317 ),
318 ),
319 err @ EthApiError::TransactionConfirmationTimeout { .. } => rpc_error_with_code(
320 EthRpcErrorCode::TransactionConfirmationTimeout.code(),
321 err.to_string(),
322 ),
323 EthApiError::Unsupported(msg) => internal_rpc_err(msg),
324 EthApiError::InternalJsTracerError(msg) => internal_rpc_err(msg),
325 EthApiError::InvalidParams(msg) => invalid_params_rpc_err(msg),
326 err @ EthApiError::ExecutionTimedOut(_) => rpc_error_with_code(
327 jsonrpsee_types::error::CALL_EXECUTION_FAILED_CODE,
328 err.to_string(),
329 ),
330 err @ (EthApiError::InternalBlockingTaskError | EthApiError::InternalEthError) => {
331 internal_rpc_err(err.to_string())
332 }
333 err @ EthApiError::TransactionInputError(_) => invalid_params_rpc_err(err.to_string()),
334 EthApiError::PrunedHistoryUnavailable => rpc_error_with_code(4444, error.to_string()),
335 EthApiError::Other(err) => err.to_rpc_error(),
336 EthApiError::MuxTracerError(msg) => internal_rpc_err(msg.to_string()),
337 EthApiError::BatchTxRecvError(err) => internal_rpc_err(err.to_string()),
338 EthApiError::BatchTxSendError => {
339 internal_rpc_err("Batch transaction sender channel closed".to_string())
340 }
341 EthApiError::CallManyError { bundle_index, tx_index, error } => {
342 jsonrpsee_types::error::ErrorObject::owned(
343 error.code(),
344 format!(
345 "call_many error in bundle {bundle_index} and transaction {tx_index}: {}",
346 error.message()
347 ),
348 error.data(),
349 )
350 }
351 EthApiError::BlockAccessListNotAvailablePreAmsterdam => {
352 rpc_error_with_code(4445, error.to_string())
353 }
354 }
355 }
356}
357
358impl<E> From<CallError<E>> for EthApiError
359where
360 E: Into<Self>,
361{
362 fn from(value: CallError<E>) -> Self {
363 match value {
364 CallError::Database(err) => err.into(),
365 CallError::InsufficientFunds(insufficient_funds_error) => {
366 Self::InvalidTransaction(RpcInvalidTransactionError::InsufficientFunds {
367 cost: insufficient_funds_error.cost,
368 balance: insufficient_funds_error.balance,
369 })
370 }
371 }
372 }
373}
374
375impl<E> From<StateOverrideError<E>> for EthApiError
376where
377 E: Into<Self>,
378{
379 fn from(value: StateOverrideError<E>) -> Self {
380 match value {
381 StateOverrideError::InvalidBytecode(bytecode_decode_error) => {
382 Self::InvalidBytecode(bytecode_decode_error.to_string())
383 }
384 StateOverrideError::BothStateAndStateDiff(address) => {
385 Self::BothStateAndStateDiffInOverride(address)
386 }
387 StateOverrideError::Database(err) => err.into(),
388 }
389 }
390}
391
392impl From<EthTxEnvError> for EthApiError {
393 fn from(value: EthTxEnvError) -> Self {
394 match value {
395 EthTxEnvError::CallFees(CallFeesError::BlobTransactionMissingBlobHashes) => {
396 Self::InvalidTransaction(
397 RpcInvalidTransactionError::BlobTransactionMissingBlobHashes,
398 )
399 }
400 EthTxEnvError::CallFees(CallFeesError::FeeCapTooLow) => {
401 Self::InvalidTransaction(RpcInvalidTransactionError::FeeCapTooLow)
402 }
403 EthTxEnvError::CallFees(CallFeesError::ConflictingFeeFieldsInRequest) => {
404 Self::ConflictingFeeFieldsInRequest
405 }
406 EthTxEnvError::CallFees(CallFeesError::TipAboveFeeCap) => {
407 Self::InvalidTransaction(RpcInvalidTransactionError::TipAboveFeeCap)
408 }
409 EthTxEnvError::CallFees(CallFeesError::TipVeryHigh) => {
410 Self::InvalidTransaction(RpcInvalidTransactionError::TipVeryHigh)
411 }
412 EthTxEnvError::Input(err) => Self::TransactionInputError(err),
413 }
414 }
415}
416
417impl<E> From<EvmDatabaseError<E>> for EthApiError
418where
419 E: Into<Self>,
420{
421 fn from(value: EvmDatabaseError<E>) -> Self {
422 match value {
423 EvmDatabaseError::Bal(err) => err.into(),
424 EvmDatabaseError::Database(err) => err.into(),
425 }
426 }
427}
428
429impl From<BalError> for EthApiError {
430 fn from(err: BalError) -> Self {
431 Self::EvmCustom(format!("bal error: {:?}", err))
432 }
433}
434
435#[cfg(feature = "js-tracer")]
436impl From<revm_inspectors::tracing::js::JsInspectorError> for EthApiError {
437 fn from(error: revm_inspectors::tracing::js::JsInspectorError) -> Self {
438 match error {
439 err @ revm_inspectors::tracing::js::JsInspectorError::JsError(_) => {
440 Self::InternalJsTracerError(err.to_string())
441 }
442 err => Self::InvalidParams(err.to_string()),
443 }
444 }
445}
446
447impl<Err> From<DebugInspectorError<Err>> for EthApiError
448where
449 Err: core::error::Error + Send + Sync + 'static,
450{
451 fn from(error: DebugInspectorError<Err>) -> Self {
452 match error {
453 DebugInspectorError::InvalidTracerConfig => Self::InvalidTracerConfig,
454 DebugInspectorError::UnsupportedTracer => Self::Unsupported("unsupported tracer"),
455 DebugInspectorError::JsTracerNotEnabled => {
456 Self::Unsupported("JS Tracer is not enabled")
457 }
458 DebugInspectorError::MuxInspector(err) => err.into(),
459 DebugInspectorError::Database(err) => Self::Internal(RethError::other(err)),
460 #[cfg(feature = "js-tracer")]
461 DebugInspectorError::JsInspector(err) => err.into(),
462 #[allow(unreachable_patterns)]
463 _ => Self::Unsupported("unsupported tracer error"),
464 }
465 }
466}
467
468impl From<RethError> for EthApiError {
469 fn from(error: RethError) -> Self {
470 match error {
471 RethError::Provider(err) => err.into(),
472 err => Self::Internal(err),
473 }
474 }
475}
476
477impl From<BlockExecutionError> for EthApiError {
478 fn from(error: BlockExecutionError) -> Self {
479 match error {
480 BlockExecutionError::Validation(validation_error) => match validation_error {
481 BlockValidationError::InvalidTx { error, .. } => {
482 if let Some(invalid_tx) = error.as_invalid_tx_err() {
483 Self::InvalidTransaction(RpcInvalidTransactionError::from(
484 invalid_tx.clone(),
485 ))
486 } else {
487 Self::InvalidTransaction(RpcInvalidTransactionError::other(
488 rpc_error_with_code(
489 EthRpcErrorCode::TransactionRejected.code(),
490 error.to_string(),
491 ),
492 ))
493 }
494 }
495 _ => Self::Internal(RethError::Execution(BlockExecutionError::Validation(
496 validation_error,
497 ))),
498 },
499 BlockExecutionError::Internal(internal_error) => {
500 Self::Internal(RethError::Execution(BlockExecutionError::Internal(internal_error)))
501 }
502 }
503 }
504}
505
506impl From<reth_errors::ProviderError> for EthApiError {
507 fn from(error: reth_errors::ProviderError) -> Self {
508 use reth_errors::ProviderError;
509 match error {
510 ProviderError::HeaderNotFound(hash) => Self::HeaderNotFound(hash.into()),
511 ProviderError::BlockHashNotFound(hash) | ProviderError::UnknownBlockHash(hash) => {
512 Self::HeaderNotFound(hash.into())
513 }
514 ProviderError::BestBlockNotFound => Self::HeaderNotFound(BlockId::latest()),
515 ProviderError::BlockNumberForTransactionIndexNotFound => Self::UnknownBlockOrTxIndex,
516 ProviderError::FinalizedBlockNotFound => Self::HeaderNotFound(BlockId::finalized()),
517 ProviderError::SafeBlockNotFound => Self::HeaderNotFound(BlockId::safe()),
518 ProviderError::BlockExpired { .. } => Self::PrunedHistoryUnavailable,
519 err => Self::Internal(err.into()),
520 }
521 }
522}
523
524impl From<InvalidHeader> for EthApiError {
525 fn from(value: InvalidHeader) -> Self {
526 match value {
527 InvalidHeader::ExcessBlobGasNotSet => Self::ExcessBlobGasNotSet,
528 InvalidHeader::PrevrandaoNotSet => Self::PrevrandaoNotSet,
529 }
530 }
531}
532
533impl<T, TxError> From<EVMError<T, TxError>> for EthApiError
534where
535 T: Into<Self>,
536 TxError: reth_evm::InvalidTxError,
537{
538 fn from(err: EVMError<T, TxError>) -> Self {
539 match err {
540 EVMError::Transaction(invalid_tx) => {
541 if let Some(eth_tx_err) = invalid_tx.as_invalid_tx_err() {
543 match eth_tx_err {
545 InvalidTransaction::NonceTooLow { tx, state } => {
546 Self::InvalidTransaction(RpcInvalidTransactionError::NonceTooLow {
547 tx: *tx,
548 state: *state,
549 })
550 }
551 _ => RpcInvalidTransactionError::from(eth_tx_err.clone()).into(),
552 }
553 } else {
554 Self::EvmCustom(invalid_tx.to_string())
557 }
558 }
559 EVMError::Header(err) => err.into(),
560 EVMError::Database(err) => err.into(),
561 EVMError::Custom(err) => Self::EvmCustom(err),
562 EVMError::CustomAny(err) => Self::EvmCustom(err.to_string()),
563 }
564 }
565}
566
567impl From<RecoveryError> for EthApiError {
568 fn from(_: RecoveryError) -> Self {
569 Self::InvalidTransactionSignature
570 }
571}
572
573impl From<RawPoolTransactionError> for EthApiError {
574 fn from(err: RawPoolTransactionError) -> Self {
575 match err {
576 RawPoolTransactionError::EmptyRawTransactionData => Self::EmptyRawTransactionData,
577 RawPoolTransactionError::FailedToDecodeSignedTransaction => {
578 Self::FailedToDecodeSignedTransaction
579 }
580 RawPoolTransactionError::InvalidTransactionSignature => {
581 Self::InvalidTransactionSignature
582 }
583 RawPoolTransactionError::Other(err) => Self::PoolError(RpcPoolError::Other(err)),
584 }
585 }
586}
587
588impl From<Infallible> for EthApiError {
589 fn from(_: Infallible) -> Self {
590 unreachable!()
591 }
592}
593
594#[derive(thiserror::Error, Debug)]
609pub enum RpcInvalidTransactionError {
610 #[error("nonce too low: next nonce {state}, tx nonce {tx}")]
612 NonceTooLow {
613 tx: u64,
615 state: u64,
617 },
618 #[error("nonce too high")]
621 NonceTooHigh,
622 #[error("nonce has max value")]
625 NonceMaxValue,
626 #[error("insufficient funds for transfer")]
628 InsufficientFundsForTransfer,
629 #[error("max initcode size exceeded")]
631 MaxInitCodeSizeExceeded,
632 #[error("insufficient funds for gas * price + value: have {balance} want {cost}")]
634 InsufficientFunds {
635 cost: U256,
637 balance: U256,
639 },
640 #[error("gas required exceeds allowance ({gas_limit})")]
646 GasRequiredExceedsAllowance {
647 gas_limit: u64,
649 },
650 #[error("gas uint64 overflow")]
652 GasUintOverflow,
653 #[error("intrinsic gas too low")]
656 GasTooLow,
657 #[error("intrinsic gas too high")]
659 GasTooHigh,
660 #[error("gas limit too high")]
662 GasLimitTooHigh,
663 #[error("transaction type not supported")]
665 TxTypeNotSupported,
666 #[error("max priority fee per gas higher than max fee per gas")]
669 TipAboveFeeCap,
670 #[error("max priority fee per gas higher than 2^256-1")]
672 TipVeryHigh,
673 #[error("max fee per gas higher than 2^256-1")]
675 FeeCapVeryHigh,
676 #[error("max fee per gas less than block base fee")]
678 FeeCapTooLow,
679 #[error("sender is not an EOA")]
681 SenderNoEOA,
682 #[error("out of gas: gas required exceeds: {0}")]
685 BasicOutOfGas(u64),
686 #[error("out of gas: gas exhausted during memory expansion: {0}")]
689 MemoryOutOfGas(u64),
690 #[error("out of memory: memory limit exceeded during memory expansion")]
692 MemoryLimitOutOfGas,
693 #[error("out of gas: gas exhausted during precompiled contract execution: {0}")]
696 PrecompileOutOfGas(u64),
697 #[error("out of gas: invalid operand to an opcode: {0}")]
700 InvalidOperandOutOfGas(u64),
701 #[error(transparent)]
703 Revert(RevertError),
704 #[error("EVM error: {0:?}")]
706 EvmHalt(HaltReason),
707 #[error("invalid chain ID")]
709 InvalidChainId,
710 #[error("transactions before Spurious Dragon should not have a chain ID")]
712 OldLegacyChainId,
713 #[error("transactions before Berlin should not have access list")]
715 AccessListNotSupported,
716 #[error("max_fee_per_blob_gas is not supported for blocks before the Cancun hardfork")]
718 MaxFeePerBlobGasNotSupported,
719 #[error("blob_versioned_hashes is not supported for blocks before the Cancun hardfork")]
722 BlobVersionedHashesNotSupported,
723 #[error("max fee per blob gas less than block blob gas fee")]
725 BlobFeeCapTooLow,
726 #[error("blob hash version mismatch")]
728 BlobHashVersionMismatch,
729 #[error("blob transaction missing blob hashes")]
731 BlobTransactionMissingBlobHashes,
732 #[error("blob transaction exceeds max blobs per block; got {have}")]
734 TooManyBlobs {
735 have: usize,
737 },
738 #[error("blob transaction is a create transaction")]
740 BlobTransactionIsCreate,
741 #[error("EIP-7702 authorization list not supported")]
743 AuthorizationListNotSupported,
744 #[error("EIP-7702 authorization list has invalid fields")]
746 AuthorizationListInvalidFields,
747 #[error("transaction priority fee below minimum required priority fee {minimum_priority_fee}")]
749 PriorityFeeBelowMinimum {
750 minimum_priority_fee: u128,
752 },
753 #[error("{0}")]
755 Other(Box<dyn ToRpcError>),
756}
757
758impl RpcInvalidTransactionError {
759 pub fn other<E: ToRpcError>(err: E) -> Self {
761 Self::Other(Box::new(err))
762 }
763
764 pub const fn error_code(&self) -> i32 {
766 match self {
767 Self::InvalidChainId |
768 Self::GasTooLow |
769 Self::GasTooHigh |
770 Self::GasRequiredExceedsAllowance { .. } |
771 Self::NonceTooLow { .. } |
772 Self::NonceTooHigh { .. } |
773 Self::FeeCapTooLow |
774 Self::FeeCapVeryHigh => EthRpcErrorCode::InvalidInput.code(),
775 Self::Revert(_) => EthRpcErrorCode::ExecutionError.code(),
776 _ => EthRpcErrorCode::TransactionRejected.code(),
777 }
778 }
779
780 pub fn halt(reason: HaltReason, gas_limit: u64) -> Self {
784 match reason {
785 HaltReason::OutOfGas(err) => Self::out_of_gas(err, gas_limit),
786 HaltReason::NonceOverflow => Self::NonceMaxValue,
787 err => Self::EvmHalt(err),
788 }
789 }
790
791 pub const fn out_of_gas(reason: OutOfGasError, gas_limit: u64) -> Self {
793 match reason {
794 OutOfGasError::Basic | OutOfGasError::ReentrancySentry => {
795 Self::BasicOutOfGas(gas_limit)
796 }
797 OutOfGasError::Memory => Self::MemoryOutOfGas(gas_limit),
798 OutOfGasError::MemoryLimit => Self::MemoryLimitOutOfGas,
799 OutOfGasError::Precompile => Self::PrecompileOutOfGas(gas_limit),
800 OutOfGasError::InvalidOperand => Self::InvalidOperandOutOfGas(gas_limit),
801 }
802 }
803
804 pub fn into_rpc_err(self) -> jsonrpsee_types::error::ErrorObject<'static> {
806 self.into()
807 }
808}
809
810impl From<RpcInvalidTransactionError> for jsonrpsee_types::error::ErrorObject<'static> {
811 fn from(err: RpcInvalidTransactionError) -> Self {
812 match err {
813 RpcInvalidTransactionError::Revert(revert) => {
814 rpc_err(
816 revert.error_code(),
817 revert.to_string(),
818 revert.output.as_ref().map(|out| out.as_ref()),
819 )
820 }
821 RpcInvalidTransactionError::Other(err) => err.to_rpc_error(),
822 err => rpc_err(err.error_code(), err.to_string(), None),
823 }
824 }
825}
826
827impl From<InvalidTransaction> for RpcInvalidTransactionError {
828 fn from(err: InvalidTransaction) -> Self {
829 match err {
830 InvalidTransaction::InvalidChainId | InvalidTransaction::MissingChainId => {
831 Self::InvalidChainId
832 }
833 InvalidTransaction::PriorityFeeGreaterThanMaxFee => Self::TipAboveFeeCap,
834 InvalidTransaction::GasPriceLessThanBasefee => Self::FeeCapTooLow,
835 InvalidTransaction::CallerGasLimitMoreThanBlock |
836 InvalidTransaction::TxGasLimitGreaterThanCap { .. } => {
837 Self::GasTooHigh
839 }
840 InvalidTransaction::CallGasCostMoreThanGasLimit { .. } => {
841 Self::GasTooLow
843 }
844 InvalidTransaction::GasFloorMoreThanGasLimit { .. } => {
845 Self::GasTooLow
850 }
851 InvalidTransaction::RejectCallerWithCode => Self::SenderNoEOA,
852 InvalidTransaction::LackOfFundForMaxFee { fee, balance } => {
853 Self::InsufficientFunds { cost: *fee, balance: *balance }
854 }
855 InvalidTransaction::OverflowPaymentInTransaction => Self::GasUintOverflow,
856 InvalidTransaction::NonceOverflowInTransaction => Self::NonceMaxValue,
857 InvalidTransaction::CreateInitCodeSizeLimit => Self::MaxInitCodeSizeExceeded,
858 InvalidTransaction::NonceTooHigh { .. } => Self::NonceTooHigh,
859 InvalidTransaction::NonceTooLow { tx, state } => Self::NonceTooLow { tx, state },
860 InvalidTransaction::AccessListNotSupported => Self::AccessListNotSupported,
861 InvalidTransaction::MaxFeePerBlobGasNotSupported => Self::MaxFeePerBlobGasNotSupported,
862 InvalidTransaction::BlobVersionedHashesNotSupported => {
863 Self::BlobVersionedHashesNotSupported
864 }
865 InvalidTransaction::BlobGasPriceGreaterThanMax { .. } => Self::BlobFeeCapTooLow,
866 InvalidTransaction::EmptyBlobs => Self::BlobTransactionMissingBlobHashes,
867 InvalidTransaction::BlobVersionNotSupported => Self::BlobHashVersionMismatch,
868 InvalidTransaction::TooManyBlobs { have, .. } => Self::TooManyBlobs { have },
869 InvalidTransaction::BlobCreateTransaction => Self::BlobTransactionIsCreate,
870 InvalidTransaction::AuthorizationListNotSupported => {
871 Self::AuthorizationListNotSupported
872 }
873 InvalidTransaction::AuthorizationListInvalidFields |
874 InvalidTransaction::EmptyAuthorizationList => Self::AuthorizationListInvalidFields,
875 InvalidTransaction::Eip2930NotSupported |
876 InvalidTransaction::Eip1559NotSupported |
877 InvalidTransaction::Eip4844NotSupported |
878 InvalidTransaction::Eip7702NotSupported |
879 InvalidTransaction::Eip7873NotSupported => Self::TxTypeNotSupported,
880 InvalidTransaction::Eip7873MissingTarget => {
881 Self::other(internal_rpc_err(err.to_string()))
882 }
883 InvalidTransaction::Str(_) => Self::other(internal_rpc_err(err.to_string())),
884 }
885 }
886}
887
888impl From<InvalidTransactionError> for RpcInvalidTransactionError {
889 fn from(err: InvalidTransactionError) -> Self {
890 use InvalidTransactionError;
891 match err {
894 InvalidTransactionError::InsufficientFunds(res) => {
895 Self::InsufficientFunds { cost: res.expected, balance: res.got }
896 }
897 InvalidTransactionError::NonceNotConsistent { tx, state } => {
898 Self::NonceTooLow { tx, state }
899 }
900 InvalidTransactionError::OldLegacyChainId => {
901 Self::OldLegacyChainId
903 }
904 InvalidTransactionError::ChainIdMismatch => Self::InvalidChainId,
905 InvalidTransactionError::Eip2930Disabled |
906 InvalidTransactionError::Eip1559Disabled |
907 InvalidTransactionError::Eip4844Disabled |
908 InvalidTransactionError::Eip7702Disabled |
909 InvalidTransactionError::TxTypeNotSupported => Self::TxTypeNotSupported,
910 InvalidTransactionError::GasUintOverflow => Self::GasUintOverflow,
911 InvalidTransactionError::GasTooLow => Self::GasTooLow,
912 InvalidTransactionError::GasTooHigh => Self::GasTooHigh,
913 InvalidTransactionError::TipAboveFeeCap => Self::TipAboveFeeCap,
914 InvalidTransactionError::FeeCapTooLow => Self::FeeCapTooLow,
915 InvalidTransactionError::SignerAccountHasBytecode => Self::SenderNoEOA,
916 InvalidTransactionError::GasLimitTooHigh => Self::GasLimitTooHigh,
917 }
918 }
919}
920
921#[derive(Debug, Clone, thiserror::Error)]
925pub struct RevertError {
926 output: Option<Bytes>,
930}
931
932impl RevertError {
935 pub fn new(output: Bytes) -> Self {
939 if output.is_empty() {
940 Self { output: None }
941 } else {
942 Self { output: Some(output) }
943 }
944 }
945
946 pub const fn error_code(&self) -> i32 {
948 EthRpcErrorCode::ExecutionError.code()
949 }
950}
951
952impl std::fmt::Display for RevertError {
953 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
954 f.write_str("execution reverted")?;
955 if let Some(reason) = self.output.as_ref().and_then(|out| RevertReason::decode(out)) {
956 let error = reason.to_string();
957 let mut error = error.as_str();
958 if matches!(reason, RevertReason::ContractError(ContractError::Revert(_))) {
959 error = error.trim_start_matches("revert: ");
961 }
962 write!(f, ": {error}")?;
963 }
964 Ok(())
965 }
966}
967
968#[derive(Debug, thiserror::Error)]
970pub enum RpcPoolError {
971 #[error("already known")]
973 AlreadyKnown,
974 #[error("invalid sender")]
976 InvalidSender,
977 #[error("transaction underpriced")]
979 Underpriced,
980 #[error("txpool is full")]
982 TxPoolOverflow,
983 #[error("replacement transaction underpriced")]
985 ReplaceUnderpriced,
986 #[error("exceeds block gas limit")]
988 ExceedsGasLimit,
989 #[error("exceeds max transaction gas limit")]
991 MaxTxGasLimitExceeded,
992 #[error("tx fee ({max_tx_fee_wei} wei) exceeds the configured cap ({tx_fee_cap_wei} wei)")]
995 ExceedsFeeCap {
996 max_tx_fee_wei: u128,
998 tx_fee_cap_wei: u128,
1000 },
1001 #[error("negative value")]
1003 NegativeValue,
1004 #[error("oversized data: transaction size {size}, limit {limit}")]
1006 OversizedData {
1007 size: usize,
1009 limit: usize,
1011 },
1012 #[error("max initcode size exceeded")]
1014 ExceedsMaxInitCodeSize,
1015 #[error(transparent)]
1017 Invalid(#[from] RpcInvalidTransactionError),
1018 #[error(transparent)]
1020 PoolTransactionError(Box<dyn PoolTransactionError>),
1021 #[error(transparent)]
1023 Eip4844(#[from] Eip4844PoolTransactionError),
1024 #[error(transparent)]
1026 Eip7702(#[from] Eip7702PoolTransactionError),
1027 #[error("address already reserved")]
1032 AddressAlreadyReserved,
1033 #[error(transparent)]
1035 Other(Box<dyn core::error::Error + Send + Sync>),
1036}
1037
1038impl From<RpcPoolError> for jsonrpsee_types::error::ErrorObject<'static> {
1039 fn from(error: RpcPoolError) -> Self {
1040 match error {
1041 RpcPoolError::Invalid(err) => err.into(),
1042 RpcPoolError::TxPoolOverflow => {
1043 rpc_error_with_code(EthRpcErrorCode::TransactionRejected.code(), error.to_string())
1044 }
1045 RpcPoolError::AlreadyKnown |
1046 RpcPoolError::InvalidSender |
1047 RpcPoolError::Underpriced |
1048 RpcPoolError::ReplaceUnderpriced |
1049 RpcPoolError::ExceedsGasLimit |
1050 RpcPoolError::MaxTxGasLimitExceeded |
1051 RpcPoolError::ExceedsFeeCap { .. } |
1052 RpcPoolError::NegativeValue |
1053 RpcPoolError::OversizedData { .. } |
1054 RpcPoolError::ExceedsMaxInitCodeSize |
1055 RpcPoolError::PoolTransactionError(_) |
1056 RpcPoolError::Eip4844(_) |
1057 RpcPoolError::Eip7702(_) |
1058 RpcPoolError::AddressAlreadyReserved => {
1059 rpc_error_with_code(EthRpcErrorCode::InvalidInput.code(), error.to_string())
1060 }
1061 RpcPoolError::Other(other) => internal_rpc_err(other.to_string()),
1062 }
1063 }
1064}
1065
1066impl From<PoolError> for RpcPoolError {
1067 fn from(err: PoolError) -> Self {
1068 match err.kind {
1069 PoolErrorKind::ReplacementUnderpriced => Self::ReplaceUnderpriced,
1070 PoolErrorKind::FeeCapBelowMinimumProtocolFeeCap(_) => Self::Underpriced,
1071 PoolErrorKind::SpammerExceededCapacity(_) | PoolErrorKind::DiscardedOnInsert => {
1072 Self::TxPoolOverflow
1073 }
1074 PoolErrorKind::InvalidTransaction(err) => err.into(),
1075 PoolErrorKind::Other(err) => Self::Other(err),
1076 PoolErrorKind::AlreadyImported => Self::AlreadyKnown,
1077 PoolErrorKind::ExistingConflictingTransactionType(_, _) => Self::AddressAlreadyReserved,
1078 }
1079 }
1080}
1081
1082impl From<InvalidPoolTransactionError> for RpcPoolError {
1083 fn from(err: InvalidPoolTransactionError) -> Self {
1084 match err {
1085 InvalidPoolTransactionError::Consensus(err) => Self::Invalid(err.into()),
1086 InvalidPoolTransactionError::ExceedsGasLimit(_, _) => Self::ExceedsGasLimit,
1087 InvalidPoolTransactionError::MaxTxGasLimitExceeded(_, _) => Self::MaxTxGasLimitExceeded,
1088 InvalidPoolTransactionError::ExceedsFeeCap { max_tx_fee_wei, tx_fee_cap_wei } => {
1089 Self::ExceedsFeeCap { max_tx_fee_wei, tx_fee_cap_wei }
1090 }
1091 InvalidPoolTransactionError::ExceedsMaxInitCodeSize(_, _) => {
1092 Self::ExceedsMaxInitCodeSize
1093 }
1094 InvalidPoolTransactionError::IntrinsicGasTooLow => {
1095 Self::Invalid(RpcInvalidTransactionError::GasTooLow)
1096 }
1097 InvalidPoolTransactionError::OversizedData { size, limit } => {
1098 Self::OversizedData { size, limit }
1099 }
1100 InvalidPoolTransactionError::Underpriced => Self::Underpriced,
1101 InvalidPoolTransactionError::Eip2681 => {
1102 Self::Invalid(RpcInvalidTransactionError::NonceMaxValue)
1103 }
1104 InvalidPoolTransactionError::Other(err) => Self::PoolTransactionError(err),
1105 InvalidPoolTransactionError::Eip4844(err) => Self::Eip4844(err),
1106 InvalidPoolTransactionError::Eip7702(err) => Self::Eip7702(err),
1107 InvalidPoolTransactionError::Overdraft { cost, balance } => {
1108 Self::Invalid(RpcInvalidTransactionError::InsufficientFunds { cost, balance })
1109 }
1110 InvalidPoolTransactionError::PriorityFeeBelowMinimum { minimum_priority_fee } => {
1111 Self::Invalid(RpcInvalidTransactionError::PriorityFeeBelowMinimum {
1112 minimum_priority_fee,
1113 })
1114 }
1115 }
1116 }
1117}
1118
1119impl From<PoolError> for EthApiError {
1120 fn from(err: PoolError) -> Self {
1121 Self::PoolError(RpcPoolError::from(err))
1122 }
1123}
1124
1125#[derive(Debug, thiserror::Error)]
1127pub enum SignError {
1128 #[error("could not sign")]
1130 CouldNotSign,
1131 #[error("unknown account")]
1133 NoAccount,
1134 #[error("given typed data is not valid")]
1136 InvalidTypedData,
1137 #[error("invalid transaction request")]
1139 InvalidTransactionRequest,
1140 #[error("no chainid")]
1142 NoChainId,
1143}
1144
1145#[cfg(test)]
1146mod tests {
1147 use super::*;
1148 use alloy_primitives::b256;
1149 use alloy_sol_types::{Revert, SolError};
1150
1151 #[test]
1152 fn timed_out_error() {
1153 let err = EthApiError::ExecutionTimedOut(Duration::from_secs(10));
1154 assert_eq!(err.to_string(), "execution aborted (timeout = 10s)");
1155 }
1156
1157 #[test]
1158 fn header_not_found_message() {
1159 let err: jsonrpsee_types::error::ErrorObject<'static> =
1160 EthApiError::HeaderNotFound(BlockId::hash(b256!(
1161 "0x1a15e3c30cf094a99826869517b16d185d45831d3a494f01030b0001a9d3ebb9"
1162 )))
1163 .into();
1164 assert_eq!(
1165 err.message(),
1166 "block not found: hash 0x1a15e3c30cf094a99826869517b16d185d45831d3a494f01030b0001a9d3ebb9"
1167 );
1168 let err: jsonrpsee_types::error::ErrorObject<'static> =
1169 EthApiError::HeaderNotFound(BlockId::hash_canonical(b256!(
1170 "0x1a15e3c30cf094a99826869517b16d185d45831d3a494f01030b0001a9d3ebb9"
1171 )))
1172 .into();
1173 assert_eq!(
1174 err.message(),
1175 "block not found: canonical hash 0x1a15e3c30cf094a99826869517b16d185d45831d3a494f01030b0001a9d3ebb9"
1176 );
1177 let err: jsonrpsee_types::error::ErrorObject<'static> =
1178 EthApiError::HeaderNotFound(BlockId::number(100000)).into();
1179 assert_eq!(err.message(), "block not found: 0x186a0");
1180 let err: jsonrpsee_types::error::ErrorObject<'static> =
1181 EthApiError::HeaderNotFound(BlockId::latest()).into();
1182 assert_eq!(err.message(), "block not found: latest");
1183 let err: jsonrpsee_types::error::ErrorObject<'static> =
1184 EthApiError::HeaderNotFound(BlockId::safe()).into();
1185 assert_eq!(err.message(), "block not found: safe");
1186 let err: jsonrpsee_types::error::ErrorObject<'static> =
1187 EthApiError::HeaderNotFound(BlockId::finalized()).into();
1188 assert_eq!(err.message(), "block not found: finalized");
1189 }
1190
1191 #[test]
1192 fn receipts_not_found_message() {
1193 let err: jsonrpsee_types::error::ErrorObject<'static> =
1194 EthApiError::ReceiptsNotFound(BlockId::hash(b256!(
1195 "0x1a15e3c30cf094a99826869517b16d185d45831d3a494f01030b0001a9d3ebb9"
1196 )))
1197 .into();
1198 assert_eq!(
1199 err.message(),
1200 "block not found: hash 0x1a15e3c30cf094a99826869517b16d185d45831d3a494f01030b0001a9d3ebb9"
1201 );
1202 let err: jsonrpsee_types::error::ErrorObject<'static> =
1203 EthApiError::ReceiptsNotFound(BlockId::hash_canonical(b256!(
1204 "0x1a15e3c30cf094a99826869517b16d185d45831d3a494f01030b0001a9d3ebb9"
1205 )))
1206 .into();
1207 assert_eq!(
1208 err.message(),
1209 "block not found: canonical hash 0x1a15e3c30cf094a99826869517b16d185d45831d3a494f01030b0001a9d3ebb9"
1210 );
1211 let err: jsonrpsee_types::error::ErrorObject<'static> =
1212 EthApiError::ReceiptsNotFound(BlockId::number(100000)).into();
1213 assert_eq!(err.code(), EthRpcErrorCode::ResourceNotFound.code());
1214 assert_eq!(err.message(), "block not found: 0x186a0");
1215 let err: jsonrpsee_types::error::ErrorObject<'static> =
1216 EthApiError::ReceiptsNotFound(BlockId::latest()).into();
1217 assert_eq!(err.message(), "block not found: latest");
1218 let err: jsonrpsee_types::error::ErrorObject<'static> =
1219 EthApiError::ReceiptsNotFound(BlockId::safe()).into();
1220 assert_eq!(err.message(), "block not found: safe");
1221 let err: jsonrpsee_types::error::ErrorObject<'static> =
1222 EthApiError::ReceiptsNotFound(BlockId::finalized()).into();
1223 assert_eq!(err.message(), "block not found: finalized");
1224 let err: jsonrpsee_types::error::ErrorObject<'static> =
1225 EthApiError::ReceiptsNotFound(BlockId::pending()).into();
1226 assert_eq!(err.message(), "block not found: pending");
1227 let err: jsonrpsee_types::error::ErrorObject<'static> =
1228 EthApiError::ReceiptsNotFound(BlockId::earliest()).into();
1229 assert_eq!(err.message(), "block not found: earliest");
1230 }
1231
1232 #[test]
1233 fn revert_err_display() {
1234 let revert = Revert::from("test_revert_reason");
1235 let err = RevertError::new(revert.abi_encode().into());
1236 let msg = err.to_string();
1237 assert_eq!(msg, "execution reverted: test_revert_reason");
1238 }
1239}