diff --git a/crates/consensus/src/beacon/beacon_consensus.rs b/crates/consensus/src/beacon/beacon_consensus.rs index 779073d503..b2efe58b09 100644 --- a/crates/consensus/src/beacon/beacon_consensus.rs +++ b/crates/consensus/src/beacon/beacon_consensus.rs @@ -1,6 +1,6 @@ //! Consensus for ethereum network use crate::validation; -use reth_interfaces::consensus::{Consensus, Error, ForkchoiceState}; +use reth_interfaces::consensus::{Consensus, ConsensusError, ForkchoiceState}; use reth_primitives::{ChainSpec, Hardfork, SealedBlock, SealedHeader, EMPTY_OMMER_ROOT, U256}; use tokio::sync::watch; @@ -42,28 +42,32 @@ impl Consensus for BeaconConsensus { &self, header: &SealedHeader, parent: &SealedHeader, - ) -> Result<(), Error> { + ) -> Result<(), ConsensusError> { validation::validate_header_standalone(header, &self.chain_spec)?; validation::validate_header_regarding_parent(parent, header, &self.chain_spec)?; Ok(()) } - fn validate_header(&self, header: &SealedHeader, total_difficulty: U256) -> Result<(), Error> { + fn validate_header( + &self, + header: &SealedHeader, + total_difficulty: U256, + ) -> Result<(), ConsensusError> { if self.chain_spec.fork(Hardfork::Paris).active_at_ttd(total_difficulty, header.difficulty) { // EIP-3675: Upgrade consensus to Proof-of-Stake: // https://eips.ethereum.org/EIPS/eip-3675#replacing-difficulty-with-0 if header.difficulty != U256::ZERO { - return Err(Error::TheMergeDifficultyIsNotZero) + return Err(ConsensusError::TheMergeDifficultyIsNotZero) } if header.nonce != 0 { - return Err(Error::TheMergeNonceIsNotZero) + return Err(ConsensusError::TheMergeNonceIsNotZero) } if header.ommers_hash != EMPTY_OMMER_ROOT { - return Err(Error::TheMergeOmmerRootIsNotEmpty) + return Err(ConsensusError::TheMergeOmmerRootIsNotEmpty) } // mixHash is used instead of difficulty inside EVM @@ -77,7 +81,7 @@ impl Consensus for BeaconConsensus { Ok(()) } - fn pre_validate_block(&self, block: &SealedBlock) -> Result<(), Error> { + fn pre_validate_block(&self, block: &SealedBlock) -> Result<(), ConsensusError> { validation::validate_block_standalone(block, &self.chain_spec) } diff --git a/crates/consensus/src/validation.rs b/crates/consensus/src/validation.rs index b786fd618b..ff4310757b 100644 --- a/crates/consensus/src/validation.rs +++ b/crates/consensus/src/validation.rs @@ -1,8 +1,8 @@ //! Collection of methods for block validation. -use reth_interfaces::{consensus::Error, Result as RethResult}; +use reth_interfaces::{consensus::ConsensusError, Result as RethResult}; use reth_primitives::{ - BlockNumber, ChainSpec, Hardfork, Header, SealedBlock, SealedHeader, Transaction, - TransactionSignedEcRecovered, TxEip1559, TxEip2930, TxLegacy, + BlockNumber, ChainSpec, Hardfork, Header, InvalidTransactionError, SealedBlock, SealedHeader, + Transaction, TransactionSignedEcRecovered, TxEip1559, TxEip2930, TxLegacy, }; use reth_provider::{AccountProvider, HeaderProvider}; use std::{ @@ -16,10 +16,10 @@ use reth_primitives::constants; pub fn validate_header_standalone( header: &SealedHeader, chain_spec: &ChainSpec, -) -> Result<(), Error> { +) -> Result<(), ConsensusError> { // Gas used needs to be less then gas limit. Gas used is going to be check after execution. if header.gas_used > header.gas_limit { - return Err(Error::HeaderGasUsedExceedsGasLimit { + return Err(ConsensusError::HeaderGasUsedExceedsGasLimit { gas_used: header.gas_used, gas_limit: header.gas_limit, }) @@ -29,31 +29,34 @@ pub fn validate_header_standalone( let present_timestamp = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs(); if header.timestamp > present_timestamp { - return Err(Error::TimestampIsInFuture { timestamp: header.timestamp, present_timestamp }) + return Err(ConsensusError::TimestampIsInFuture { + timestamp: header.timestamp, + present_timestamp, + }) } // From yellow paper: extraData: An arbitrary byte array containing data // relevant to this block. This must be 32 bytes or fewer; formally Hx. if header.extra_data.len() > 32 { - return Err(Error::ExtraDataExceedsMax { len: header.extra_data.len() }) + return Err(ConsensusError::ExtraDataExceedsMax { len: header.extra_data.len() }) } // Check if base fee is set. if chain_spec.fork(Hardfork::London).active_at_block(header.number) && header.base_fee_per_gas.is_none() { - return Err(Error::BaseFeeMissing) + return Err(ConsensusError::BaseFeeMissing) } // EIP-4895: Beacon chain push withdrawals as operations if chain_spec.fork(Hardfork::Shanghai).active_at_timestamp(header.timestamp) && header.withdrawals_root.is_none() { - return Err(Error::WithdrawalsRootMissing) + return Err(ConsensusError::WithdrawalsRootMissing) } else if !chain_spec.fork(Hardfork::Shanghai).active_at_timestamp(header.timestamp) && header.withdrawals_root.is_some() { - return Err(Error::WithdrawalsRootUnexpected) + return Err(ConsensusError::WithdrawalsRootUnexpected) } Ok(()) @@ -67,21 +70,21 @@ pub fn validate_transaction_regarding_header( chain_spec: &ChainSpec, at_block_number: BlockNumber, base_fee: Option, -) -> Result<(), Error> { +) -> Result<(), ConsensusError> { let chain_id = match transaction { Transaction::Legacy(TxLegacy { chain_id, .. }) => { // EIP-155: Simple replay attack protection: https://eips.ethereum.org/EIPS/eip-155 if chain_spec.fork(Hardfork::SpuriousDragon).active_at_block(at_block_number) && chain_id.is_some() { - return Err(Error::TransactionOldLegacyChainId) + return Err(InvalidTransactionError::OldLegacyChainId.into()) } *chain_id } Transaction::Eip2930(TxEip2930 { chain_id, .. }) => { // EIP-2930: Optional access lists: https://eips.ethereum.org/EIPS/eip-2930 (New transaction type) if !chain_spec.fork(Hardfork::Berlin).active_at_block(at_block_number) { - return Err(Error::TransactionEip2930Disabled) + return Err(InvalidTransactionError::Eip2930Disabled.into()) } Some(*chain_id) } @@ -93,13 +96,13 @@ pub fn validate_transaction_regarding_header( }) => { // EIP-1559: Fee market change for ETH 1.0 chain https://eips.ethereum.org/EIPS/eip-1559 if !chain_spec.fork(Hardfork::Berlin).active_at_block(at_block_number) { - return Err(Error::TransactionEip1559Disabled) + return Err(InvalidTransactionError::Eip1559Disabled.into()) } // EIP-1559: add more constraints to the tx validation // https://github.com/ethereum/EIPs/pull/3594 if max_priority_fee_per_gas > max_fee_per_gas { - return Err(Error::TransactionPriorityFeeMoreThenMaxFee) + return Err(InvalidTransactionError::PriorityFeeMoreThenMaxFee.into()) } Some(*chain_id) @@ -107,14 +110,14 @@ pub fn validate_transaction_regarding_header( }; if let Some(chain_id) = chain_id { if chain_id != chain_spec.chain().id() { - return Err(Error::TransactionChainId) + return Err(InvalidTransactionError::ChainIdMismatch.into()) } } // Check basefee and few checks that are related to that. // https://github.com/ethereum/EIPs/pull/3594 if let Some(base_fee_per_gas) = base_fee { if transaction.max_fee_per_gas() < base_fee_per_gas as u128 { - return Err(Error::TransactionMaxFeeLessThenBaseFee) + return Err(InvalidTransactionError::MaxFeeLessThenBaseFee.into()) } } @@ -155,7 +158,10 @@ pub fn validate_all_transaction_regarding_block_and_nonces< // Signer account shouldn't have bytecode. Presence of bytecode means this is a // smartcontract. if account.has_bytecode() { - return Err(Error::SignerAccountHasBytecode.into()) + return Err(ConsensusError::from( + InvalidTransactionError::SignerAccountHasBytecode, + ) + .into()) } let nonce = account.nonce; entry.insert(account.nonce + 1); @@ -165,7 +171,7 @@ pub fn validate_all_transaction_regarding_block_and_nonces< // check nonce if transaction.nonce() != nonce { - return Err(Error::TransactionNonceNotConsistent.into()) + return Err(ConsensusError::from(InvalidTransactionError::NonceNotConsistent).into()) } } @@ -178,13 +184,16 @@ pub fn validate_all_transaction_regarding_block_and_nonces< /// - Compares the transactions root in the block header to the block body /// - Pre-execution transaction validation /// - (Optionally) Compares the receipts root in the block header to the block body -pub fn validate_block_standalone(block: &SealedBlock, chain_spec: &ChainSpec) -> Result<(), Error> { +pub fn validate_block_standalone( + block: &SealedBlock, + chain_spec: &ChainSpec, +) -> Result<(), ConsensusError> { // Check ommers hash // TODO(onbjerg): This should probably be accessible directly on [Block] let ommers_hash = reth_primitives::proofs::calculate_ommers_root(block.ommers.iter().map(|h| h.as_ref())); if block.header.ommers_hash != ommers_hash { - return Err(Error::BodyOmmersHashDiff { + return Err(ConsensusError::BodyOmmersHashDiff { got: ommers_hash, expected: block.header.ommers_hash, }) @@ -194,7 +203,7 @@ pub fn validate_block_standalone(block: &SealedBlock, chain_spec: &ChainSpec) -> // TODO(onbjerg): This should probably be accessible directly on [Block] let transaction_root = reth_primitives::proofs::calculate_transaction_root(block.body.iter()); if block.header.transactions_root != transaction_root { - return Err(Error::BodyTransactionRootDiff { + return Err(ConsensusError::BodyTransactionRootDiff { got: transaction_root, expected: block.header.transactions_root, }) @@ -202,13 +211,14 @@ pub fn validate_block_standalone(block: &SealedBlock, chain_spec: &ChainSpec) -> // EIP-4895: Beacon chain push withdrawals as operations if chain_spec.fork(Hardfork::Shanghai).active_at_timestamp(block.timestamp) { - let withdrawals = block.withdrawals.as_ref().ok_or(Error::BodyWithdrawalsMissing)?; + let withdrawals = + block.withdrawals.as_ref().ok_or(ConsensusError::BodyWithdrawalsMissing)?; let withdrawals_root = reth_primitives::proofs::calculate_withdrawals_root(withdrawals.iter()); let header_withdrawals_root = - block.withdrawals_root.as_ref().ok_or(Error::WithdrawalsRootMissing)?; + block.withdrawals_root.as_ref().ok_or(ConsensusError::WithdrawalsRootMissing)?; if withdrawals_root != *header_withdrawals_root { - return Err(Error::BodyWithdrawalsRootDiff { + return Err(ConsensusError::BodyWithdrawalsRootDiff { got: withdrawals_root, expected: *header_withdrawals_root, }) @@ -220,7 +230,10 @@ pub fn validate_block_standalone(block: &SealedBlock, chain_spec: &ChainSpec) -> for withdrawal in withdrawals.iter().skip(1) { let expected = prev_index + 1; if expected != withdrawal.index { - return Err(Error::WithdrawalIndexInvalid { got: withdrawal.index, expected }) + return Err(ConsensusError::WithdrawalIndexInvalid { + got: withdrawal.index, + expected, + }) } prev_index = withdrawal.index; } @@ -261,10 +274,10 @@ pub fn validate_header_regarding_parent( parent: &SealedHeader, child: &SealedHeader, chain_spec: &ChainSpec, -) -> Result<(), Error> { +) -> Result<(), ConsensusError> { // Parent number is consistent. if parent.number + 1 != child.number { - return Err(Error::ParentBlockNumberMismatch { + return Err(ConsensusError::ParentBlockNumberMismatch { parent_block_number: parent.number, block_number: child.number, }) @@ -272,7 +285,7 @@ pub fn validate_header_regarding_parent( // timestamp in past check if child.timestamp < parent.timestamp { - return Err(Error::TimestampIsInPast { + return Err(ConsensusError::TimestampIsInPast { parent_timestamp: parent.timestamp, timestamp: child.timestamp, }) @@ -295,13 +308,13 @@ pub fn validate_header_regarding_parent( // Check gas limit, max diff between child/parent gas_limit should be max_diff=parent_gas/1024 if child.gas_limit > parent_gas_limit { if child.gas_limit - parent_gas_limit >= parent_gas_limit / 1024 { - return Err(Error::GasLimitInvalidIncrease { + return Err(ConsensusError::GasLimitInvalidIncrease { parent_gas_limit, child_gas_limit: child.gas_limit, }) } } else if parent_gas_limit - child.gas_limit >= parent_gas_limit / 1024 { - return Err(Error::GasLimitInvalidDecrease { + return Err(ConsensusError::GasLimitInvalidDecrease { parent_gas_limit, child_gas_limit: child.gas_limit, }) @@ -309,7 +322,7 @@ pub fn validate_header_regarding_parent( // EIP-1559 check base fee if chain_spec.fork(Hardfork::London).active_at_block(child.number) { - let base_fee = child.base_fee_per_gas.ok_or(Error::BaseFeeMissing)?; + let base_fee = child.base_fee_per_gas.ok_or(ConsensusError::BaseFeeMissing)?; let expected_base_fee = if chain_spec.fork(Hardfork::London).transitions_at_block(child.number) { @@ -319,11 +332,11 @@ pub fn validate_header_regarding_parent( calculate_next_block_base_fee( parent.gas_used, parent.gas_limit, - parent.base_fee_per_gas.ok_or(Error::BaseFeeMissing)?, + parent.base_fee_per_gas.ok_or(ConsensusError::BaseFeeMissing)?, ) }; if expected_base_fee != base_fee { - return Err(Error::BaseFeeDiff { expected: expected_base_fee, got: base_fee }) + return Err(ConsensusError::BaseFeeDiff { expected: expected_base_fee, got: base_fee }) } } @@ -345,13 +358,13 @@ pub fn validate_block_regarding_chain( // Check if block is known. if provider.is_known(&hash)? { - return Err(Error::BlockKnown { hash, number: block.header.number }.into()) + return Err(ConsensusError::BlockKnown { hash, number: block.header.number }.into()) } // Check if parent is known. let parent = provider .header(&block.parent_hash)? - .ok_or(Error::ParentUnknown { hash: block.parent_hash })?; + .ok_or(ConsensusError::ParentUnknown { hash: block.parent_hash })?; // Return parent header. Ok(parent.seal(block.parent_hash)) @@ -372,7 +385,7 @@ pub fn full_validation( let transactions = block .body .iter() - .map(|tx| tx.try_ecrecovered().ok_or(Error::TransactionSignerRecoveryError)) + .map(|tx| tx.try_ecrecovered().ok_or(ConsensusError::TransactionSignerRecoveryError)) .collect::, _>>()?; validate_all_transaction_regarding_block_and_nonces( @@ -543,7 +556,7 @@ mod tests { assert_eq!( full_validation(&block, provider, &MAINNET), - Err(Error::BlockKnown { hash: block.hash(), number: block.number }.into()), + Err(ConsensusError::BlockKnown { hash: block.hash(), number: block.number }.into()), "Should fail with error" ); } @@ -579,7 +592,7 @@ mod tests { provider, &MAINNET, ), - Err(Error::TransactionNonceNotConsistent.into()) + Err(ConsensusError::from(InvalidTransactionError::NonceNotConsistent).into()) ) } @@ -598,7 +611,7 @@ mod tests { provider, &MAINNET, ), - Err(Error::TransactionNonceNotConsistent.into()) + Err(ConsensusError::from(InvalidTransactionError::NonceNotConsistent).into()) ); } @@ -636,12 +649,12 @@ mod tests { let block = create_block_with_withdrawals(&[100, 102]); assert_matches!( validate_block_standalone(&block, &chain_spec), - Err(Error::WithdrawalIndexInvalid { .. }) + Err(ConsensusError::WithdrawalIndexInvalid { .. }) ); let block = create_block_with_withdrawals(&[5, 6, 7, 9]); assert_matches!( validate_block_standalone(&block, &chain_spec), - Err(Error::WithdrawalIndexInvalid { .. }) + Err(ConsensusError::WithdrawalIndexInvalid { .. }) ); } diff --git a/crates/interfaces/src/consensus.rs b/crates/interfaces/src/consensus.rs index 2c926c89eb..6886da85e4 100644 --- a/crates/interfaces/src/consensus.rs +++ b/crates/interfaces/src/consensus.rs @@ -1,5 +1,7 @@ use async_trait::async_trait; -use reth_primitives::{BlockHash, BlockNumber, SealedBlock, SealedHeader, H256, U256}; +use reth_primitives::{ + BlockHash, BlockNumber, InvalidTransactionError, SealedBlock, SealedHeader, H256, U256, +}; use std::fmt::Debug; use tokio::sync::watch::Receiver; @@ -23,13 +25,17 @@ pub trait Consensus: Debug + Send + Sync { &self, header: &SealedHeader, parent: &SealedHeader, - ) -> Result<(), Error>; + ) -> Result<(), ConsensusError>; /// Validate if the header is correct and follows the consensus specification, including /// computed properties (like total difficulty). /// /// Some consensus engines may want to do additional checks here. - fn validate_header(&self, header: &SealedHeader, total_difficulty: U256) -> Result<(), Error>; + fn validate_header( + &self, + header: &SealedHeader, + total_difficulty: U256, + ) -> Result<(), ConsensusError>; /// Validate a block disregarding world state, i.e. things that can be checked before sender /// recovery and execution. @@ -38,7 +44,7 @@ pub trait Consensus: Debug + Send + Sync { /// 11.1 "Ommer Validation". /// /// **This should not be called for the genesis block**. - fn pre_validate_block(&self, block: &SealedBlock) -> Result<(), Error>; + fn pre_validate_block(&self, block: &SealedBlock) -> Result<(), ConsensusError>; /// After the Merge (aka Paris) block rewards became obsolete. /// @@ -51,7 +57,7 @@ pub trait Consensus: Debug + Send + Sync { /// Consensus Errors #[allow(missing_docs)] #[derive(thiserror::Error, Debug, PartialEq, Eq, Clone)] -pub enum Error { +pub enum ConsensusError { #[error("Block used gas ({gas_used:?}) is greater than gas limit ({gas_limit:?}).")] HeaderGasUsedExceedsGasLimit { gas_used: u64, gas_limit: u64 }, #[error("Block ommer hash ({got:?}) is different from expected: ({expected:?})")] @@ -71,7 +77,7 @@ pub enum Error { #[error("Block number {block_number:?} is mismatch with parent block number {parent_block_number:?}")] ParentBlockNumberMismatch { parent_block_number: BlockNumber, block_number: BlockNumber }, #[error( - "Block timestamp {timestamp:?} is in past in comparison with parent timestamp {parent_timestamp:?}." + "Block timestamp {timestamp:?} is in past in comparison with parent timestamp {parent_timestamp:?}." )] TimestampIsInPast { parent_timestamp: u64, timestamp: u64 }, #[error("Block timestamp {timestamp:?} is in future in comparison of our clock time {present_timestamp:?}.")] @@ -84,26 +90,6 @@ pub enum Error { BaseFeeMissing, #[error("Block base fee ({got:?}) is different then expected: ({expected:?}).")] BaseFeeDiff { expected: u64, got: u64 }, - #[error("Transaction eip1559 priority fee is more then max fee.")] - TransactionPriorityFeeMoreThenMaxFee, - #[error("Transaction chain_id does not match.")] - TransactionChainId, - #[error("Transaction max fee is less them block base fee.")] - TransactionMaxFeeLessThenBaseFee, - #[error("Transaction signer does not have account.")] - SignerAccountNotExisting, - #[error("Transaction signer has bytecode set.")] - SignerAccountHasBytecode, - #[error("Transaction nonce is not consistent.")] - TransactionNonceNotConsistent, - #[error("Account does not have enough funds ({available_funds:?}) to cover transaction max fee: {max_fee:?}.")] - InsufficientFunds { max_fee: u128, available_funds: U256 }, - #[error("Eip2930 transaction is enabled after berlin hardfork.")] - TransactionEip2930Disabled, - #[error("Old legacy transaction before Spurious Dragon should not have chain_id.")] - TransactionOldLegacyChainId, - #[error("Eip2930 transaction is enabled after london hardfork.")] - TransactionEip1559Disabled, #[error("Transaction signer recovery error.")] TransactionSignerRecoveryError, #[error( @@ -130,39 +116,7 @@ pub enum Error { WithdrawalIndexInvalid { got: u64, expected: u64 }, #[error("Missing withdrawals")] BodyWithdrawalsMissing, - /// Thrown when calculating gas usage - #[error("gas uint64 overflow")] - GasUintOverflow, - /// returned if the transaction is specified to use less gas than required to start the - /// invocation. - #[error("intrinsic gas too low")] - GasTooLow, - /// returned if the transaction gas exceeds the limit - #[error("intrinsic gas too high")] - GasTooHigh, - /// thrown if a transaction is not supported in the current network configuration. - #[error("transaction type not supported")] - TxTypeNotSupported, - /// Thrown to ensure no one is able to specify a transaction with a tip higher than the total - /// fee cap. - #[error("max priority fee per gas higher than max fee per gas")] - TipAboveFeeCap, - /// A sanity error to avoid huge numbers specified in the tip field. - #[error("max priority fee per gas higher than 2^256-1")] - TipVeryHigh, - /// A sanity error to avoid huge numbers specified in the fee cap field. - #[error("max fee per gas higher than 2^256-1")] - FeeCapVeryHigh, - /// Thrown post London if the transaction's fee is less than the base fee of the block - #[error("max fee per gas less than block base fee")] - FeeCapTooLow, - /// Thrown if the sender of a transaction is a contract. - #[error("sender not an eoa")] - SenderNoEOA, -} - -impl From for Error { - fn from(_: crate::error::Error) -> Self { - Error::TransactionSignerRecoveryError - } + /// Error for a transaction that violates consensus. + #[error(transparent)] + InvalidTransaction(#[from] InvalidTransactionError), } diff --git a/crates/interfaces/src/error.rs b/crates/interfaces/src/error.rs index 6b27e906bb..746dffcb55 100644 --- a/crates/interfaces/src/error.rs +++ b/crates/interfaces/src/error.rs @@ -9,7 +9,7 @@ pub enum Error { Execution(#[from] crate::executor::Error), #[error(transparent)] - Consensus(#[from] crate::consensus::Error), + Consensus(#[from] crate::consensus::ConsensusError), #[error(transparent)] Database(#[from] crate::db::Error), diff --git a/crates/interfaces/src/p2p/error.rs b/crates/interfaces/src/p2p/error.rs index 92922e40b0..78623328a6 100644 --- a/crates/interfaces/src/p2p/error.rs +++ b/crates/interfaces/src/p2p/error.rs @@ -125,7 +125,7 @@ pub enum DownloadError { hash: H256, /// The details of validation failure #[source] - error: consensus::Error, + error: consensus::ConsensusError, }, /// Error when checking that the current [`Header`] has the parent's hash as the parent_hash /// field, and that they have sequential block numbers. @@ -172,7 +172,7 @@ pub enum DownloadError { hash: H256, /// The details of validation failure #[source] - error: consensus::Error, + error: consensus::ConsensusError, }, /// Received more bodies than requested. #[error("Received more bodies than requested. Expected: {expected}. Received: {received}")] diff --git a/crates/interfaces/src/test_utils/headers.rs b/crates/interfaces/src/test_utils/headers.rs index eaa8f804de..d4f7fce563 100644 --- a/crates/interfaces/src/test_utils/headers.rs +++ b/crates/interfaces/src/test_utils/headers.rs @@ -1,6 +1,6 @@ //! Testing support for headers related interfaces. use crate::{ - consensus::{self, Consensus, Error}, + consensus::{self, Consensus, ConsensusError}, p2p::{ download::DownloadClient, error::{DownloadError, DownloadResult, PeerRequestResult, RequestError}, @@ -331,25 +331,29 @@ impl Consensus for TestConsensus { &self, header: &SealedHeader, parent: &SealedHeader, - ) -> Result<(), Error> { + ) -> Result<(), ConsensusError> { if self.fail_validation() { - Err(consensus::Error::BaseFeeMissing) + Err(consensus::ConsensusError::BaseFeeMissing) } else { Ok(()) } } - fn validate_header(&self, header: &SealedHeader, total_difficulty: U256) -> Result<(), Error> { + fn validate_header( + &self, + header: &SealedHeader, + total_difficulty: U256, + ) -> Result<(), ConsensusError> { if self.fail_validation() { - Err(consensus::Error::BaseFeeMissing) + Err(consensus::ConsensusError::BaseFeeMissing) } else { Ok(()) } } - fn pre_validate_block(&self, _block: &SealedBlock) -> Result<(), consensus::Error> { + fn pre_validate_block(&self, _block: &SealedBlock) -> Result<(), consensus::ConsensusError> { if self.fail_validation() { - Err(consensus::Error::BaseFeeMissing) + Err(consensus::ConsensusError::BaseFeeMissing) } else { Ok(()) } diff --git a/crates/net/network/src/import.rs b/crates/net/network/src/import.rs index 87f7e30ded..2146772c2a 100644 --- a/crates/net/network/src/import.rs +++ b/crates/net/network/src/import.rs @@ -47,7 +47,7 @@ pub enum BlockValidation { pub enum BlockImportError { /// Consensus error #[error(transparent)] - Consensus(#[from] reth_interfaces::consensus::Error), + Consensus(#[from] reth_interfaces::consensus::ConsensusError), } /// An implementation of `BlockImport` used in Proof-of-Stake consensus that does nothing. diff --git a/crates/primitives/src/lib.rs b/crates/primitives/src/lib.rs index 59f969e184..c4b8a8a1c4 100644 --- a/crates/primitives/src/lib.rs +++ b/crates/primitives/src/lib.rs @@ -61,9 +61,9 @@ pub use serde_helper::JsonU256; pub use storage::{StorageEntry, StorageTrieEntry}; pub use transaction::{ AccessList, AccessListItem, AccessListWithGasUsed, FromRecoveredTransaction, - IntoRecoveredTransaction, Signature, Transaction, TransactionKind, TransactionSigned, - TransactionSignedEcRecovered, TxEip1559, TxEip2930, TxLegacy, TxType, EIP1559_TX_TYPE_ID, - EIP2930_TX_TYPE_ID, LEGACY_TX_TYPE_ID, + IntoRecoveredTransaction, InvalidTransactionError, Signature, Transaction, TransactionKind, + TransactionSigned, TransactionSignedEcRecovered, TxEip1559, TxEip2930, TxLegacy, TxType, + EIP1559_TX_TYPE_ID, EIP2930_TX_TYPE_ID, LEGACY_TX_TYPE_ID, }; pub use withdrawal::Withdrawal; diff --git a/crates/primitives/src/transaction/error.rs b/crates/primitives/src/transaction/error.rs new file mode 100644 index 0000000000..649ecbe36b --- /dev/null +++ b/crates/primitives/src/transaction/error.rs @@ -0,0 +1,47 @@ +use crate::U256; + +/// Represents error variants that can happen when trying to validate a +/// [Transaction](crate::Transaction) +#[allow(missing_docs)] +#[derive(Debug, Clone, Eq, PartialEq, thiserror::Error)] +pub enum InvalidTransactionError { + #[error("Transaction eip1559 priority fee is more then max fee.")] + PriorityFeeMoreThenMaxFee, + #[error("Account does not have enough funds ({available_funds:?}) to cover transaction max fee: {max_fee:?}.")] + InsufficientFunds { max_fee: u128, available_funds: U256 }, + #[error("Transaction nonce is not consistent.")] + NonceNotConsistent, + #[error("Old legacy transaction before Spurious Dragon should not have chain_id.")] + OldLegacyChainId, + #[error("Transaction chain_id does not match.")] + ChainIdMismatch, + #[error("Transaction max fee is less them block base fee.")] + MaxFeeLessThenBaseFee, + #[error("Eip2930 transaction is enabled after berlin hardfork.")] + Eip2930Disabled, + #[error("Eip2930 transaction is enabled after london hardfork.")] + Eip1559Disabled, + /// Thrown when calculating gas usage + #[error("gas uint64 overflow")] + GasUintOverflow, + /// returned if the transaction is specified to use less gas than required to start the + /// invocation. + #[error("intrinsic gas too low")] + GasTooLow, + /// returned if the transaction gas exceeds the limit + #[error("intrinsic gas too high")] + GasTooHigh, + /// thrown if a transaction is not supported in the current network configuration. + #[error("transaction type not supported")] + TxTypeNotSupported, + /// Thrown to ensure no one is able to specify a transaction with a tip higher than the total + /// fee cap. + #[error("max priority fee per gas higher than max fee per gas")] + TipAboveFeeCap, + /// Thrown post London if the transaction's fee is less than the base fee of the block + #[error("max fee per gas less than block base fee")] + FeeCapTooLow, + /// Thrown if the sender of a transaction is a contract. + #[error("Transaction signer has bytecode set.")] + SignerAccountHasBytecode, +} diff --git a/crates/primitives/src/transaction/mod.rs b/crates/primitives/src/transaction/mod.rs index 45708a048c..23885b8b0c 100644 --- a/crates/primitives/src/transaction/mod.rs +++ b/crates/primitives/src/transaction/mod.rs @@ -2,6 +2,7 @@ use crate::{keccak256, Address, Bytes, ChainId, TxHash, H256}; pub use access_list::{AccessList, AccessListItem, AccessListWithGasUsed}; use bytes::{Buf, BytesMut}; use derive_more::{AsRef, Deref}; +pub use error::InvalidTransactionError; use reth_codecs::{add_arbitrary_tests, main_codec, Compact}; use reth_rlp::{ length_of_length, Decodable, DecodeError, Encodable, Header, EMPTY_LIST_CODE, EMPTY_STRING_CODE, @@ -10,6 +11,7 @@ pub use signature::Signature; pub use tx_type::{TxType, EIP1559_TX_TYPE_ID, EIP2930_TX_TYPE_ID, LEGACY_TX_TYPE_ID}; mod access_list; +mod error; mod signature; mod tx_type; mod util; diff --git a/crates/stages/src/error.rs b/crates/stages/src/error.rs index 0698bf092f..bd1c91bc12 100644 --- a/crates/stages/src/error.rs +++ b/crates/stages/src/error.rs @@ -17,7 +17,7 @@ pub enum StageError { block: BlockNumber, /// The underlying consensus error. #[source] - error: consensus::Error, + error: consensus::ConsensusError, }, /// The stage encountered a database error. #[error("An internal database error occurred: {0}")] diff --git a/crates/stages/src/pipeline/mod.rs b/crates/stages/src/pipeline/mod.rs index 794503bc58..cd8bbbac43 100644 --- a/crates/stages/src/pipeline/mod.rs +++ b/crates/stages/src/pipeline/mod.rs @@ -620,7 +620,7 @@ mod tests { TestStage::new(StageId("B")) .add_exec(Err(StageError::Validation { block: 5, - error: consensus::Error::BaseFeeMissing, + error: consensus::ConsensusError::BaseFeeMissing, })) .add_unwind(Ok(UnwindOutput { stage_progress: 0 })) .add_exec(Ok(ExecOutput { stage_progress: 10, done: true })), diff --git a/crates/stages/src/stages/merkle.rs b/crates/stages/src/stages/merkle.rs index ee29dd74d9..ea310fc46d 100644 --- a/crates/stages/src/stages/merkle.rs +++ b/crates/stages/src/stages/merkle.rs @@ -124,7 +124,10 @@ impl Stage for MerkleStage { warn!(target: "sync::stages::merkle::exec", ?previous_stage_progress, got = ?block_root, expected = ?trie_root, "Block's root state failed verification"); return Err(StageError::Validation { block: previous_stage_progress, - error: consensus::Error::BodyStateRootDiff { got: trie_root, expected: block_root }, + error: consensus::ConsensusError::BodyStateRootDiff { + got: trie_root, + expected: block_root, + }, }) } @@ -167,7 +170,7 @@ impl Stage for MerkleStage { warn!(target: "sync::stages::merkle::unwind", ?unwind_to, got = ?block_root, expected = ?target_root, "Block's root state failed verification"); return Err(StageError::Validation { block: unwind_to, - error: consensus::Error::BodyStateRootDiff { + error: consensus::ConsensusError::BodyStateRootDiff { got: block_root, expected: target_root, }, diff --git a/crates/stages/src/stages/total_difficulty.rs b/crates/stages/src/stages/total_difficulty.rs index 6556bf81df..a02b368fa8 100644 --- a/crates/stages/src/stages/total_difficulty.rs +++ b/crates/stages/src/stages/total_difficulty.rs @@ -8,7 +8,7 @@ use reth_db::{ tables, transaction::{DbTx, DbTxMut}, }; -use reth_interfaces::{consensus::Error, provider::ProviderError}; +use reth_interfaces::{consensus::ConsensusError, provider::ProviderError}; use reth_primitives::{ChainSpec, Hardfork, EMPTY_OMMER_ROOT, MAINNET, U256}; use reth_provider::Transaction; use tracing::*; @@ -78,21 +78,21 @@ impl Stage for TotalDifficultyStage { if header.difficulty != U256::ZERO { return Err(StageError::Validation { block: header.number, - error: Error::TheMergeDifficultyIsNotZero, + error: ConsensusError::TheMergeDifficultyIsNotZero, }) } if header.nonce != 0 { return Err(StageError::Validation { block: header.number, - error: Error::TheMergeNonceIsNotZero, + error: ConsensusError::TheMergeNonceIsNotZero, }) } if header.ommers_hash != EMPTY_OMMER_ROOT { return Err(StageError::Validation { block: header.number, - error: Error::TheMergeOmmerRootIsNotEmpty, + error: ConsensusError::TheMergeOmmerRootIsNotEmpty, }) } } diff --git a/crates/transaction-pool/src/lib.rs b/crates/transaction-pool/src/lib.rs index 11ef1812f6..1304d607a2 100644 --- a/crates/transaction-pool/src/lib.rs +++ b/crates/transaction-pool/src/lib.rs @@ -94,7 +94,7 @@ use crate::{ traits::{NewTransactionEvent, PoolSize}, }; -use reth_interfaces::consensus::Error; +use reth_interfaces::consensus::ConsensusError; use reth_primitives::{TxHash, U256}; use std::{collections::HashMap, sync::Arc}; use tokio::sync::mpsc::Receiver; @@ -164,8 +164,9 @@ where &self, origin: TransactionOrigin, transactions: impl IntoIterator, - ) -> PoolResult, Error>>> - { + ) -> PoolResult< + HashMap, ConsensusError>>, + > { let outcome = futures_util::future::join_all( transactions.into_iter().map(|tx| self.validate(origin, tx)), ) @@ -181,7 +182,7 @@ where &self, origin: TransactionOrigin, transaction: V::Transaction, - ) -> (TxHash, Result, Error>) { + ) -> (TxHash, Result, ConsensusError>) { let hash = *transaction.hash(); // TODO(mattsse): this is where additional validate checks would go, like banned senders diff --git a/crates/transaction-pool/src/test_utils/mod.rs b/crates/transaction-pool/src/test_utils/mod.rs index 52e6ff2c21..a20cf98dcd 100644 --- a/crates/transaction-pool/src/test_utils/mod.rs +++ b/crates/transaction-pool/src/test_utils/mod.rs @@ -9,7 +9,7 @@ use crate::{ }; use async_trait::async_trait; pub use mock::*; -use reth_interfaces::consensus::Error; +use reth_interfaces::consensus::ConsensusError; use std::{marker::PhantomData, sync::Arc}; /// A [Pool] used for testing @@ -37,7 +37,7 @@ impl TransactionValidator for NoopTransactionValidator { &self, origin: TransactionOrigin, transaction: Self::Transaction, - ) -> Result, Error> { + ) -> Result, ConsensusError> { Ok(TransactionValidationOutcome::Valid { balance: Default::default(), state_nonce: 0, diff --git a/crates/transaction-pool/src/validate.rs b/crates/transaction-pool/src/validate.rs index b2ebf339da..5a5f9a553e 100644 --- a/crates/transaction-pool/src/validate.rs +++ b/crates/transaction-pool/src/validate.rs @@ -6,10 +6,10 @@ use crate::{ traits::{PoolTransaction, TransactionOrigin}, MAX_INIT_CODE_SIZE, TX_MAX_SIZE, }; -use reth_interfaces::consensus::Error; +use reth_interfaces::consensus::ConsensusError; use reth_primitives::{ - Address, TransactionKind, TxHash, EIP1559_TX_TYPE_ID, EIP2930_TX_TYPE_ID, LEGACY_TX_TYPE_ID, - U256, + Address, InvalidTransactionError, TransactionKind, TxHash, EIP1559_TX_TYPE_ID, + EIP2930_TX_TYPE_ID, LEGACY_TX_TYPE_ID, U256, }; use reth_provider::AccountProvider; use std::{fmt, time::Instant}; @@ -59,7 +59,7 @@ pub trait TransactionValidator: Send + Sync { &self, origin: TransactionOrigin, transaction: Self::Transaction, - ) -> Result, Error>; + ) -> Result, ConsensusError>; /// Ensure that the code size is not greater than `max_init_code_size`. /// `max_init_code_size` should be configurable so this will take it as an argument. @@ -109,7 +109,7 @@ impl TransactionValidator &self, origin: TransactionOrigin, transaction: Self::Transaction, - ) -> Result, Error> { + ) -> Result, ConsensusError> { // Checks for tx_type match transaction.tx_type() { LEGACY_TX_TYPE_ID => { @@ -119,18 +119,18 @@ impl TransactionValidator EIP2930_TX_TYPE_ID => { // Accept only legacy transactions until EIP-2718/2930 activates if !self.eip2718 { - return Err(Error::TransactionEip2930Disabled) + return Err(InvalidTransactionError::Eip2930Disabled.into()) } } EIP1559_TX_TYPE_ID => { // Reject dynamic fee transactions until EIP-1559 activates. if !self.eip1559 { - return Err(Error::TransactionEip1559Disabled) + return Err(InvalidTransactionError::Eip1559Disabled.into()) } } - _ => return Err(Error::TxTypeNotSupported), + _ => return Err(InvalidTransactionError::TxTypeNotSupported.into()), }; // Reject transactions over defined size to prevent DOS attacks @@ -163,25 +163,25 @@ impl TransactionValidator // Ensure max_fee_per_gas is greater than or equal to max_priority_fee_per_gas. if transaction.max_fee_per_gas() <= transaction.max_priority_fee_per_gas() { - return Err(Error::TipAboveFeeCap) + return Err(InvalidTransactionError::TipAboveFeeCap.into()) } // Drop non-local transactions under our own minimal accepted gas price or tip if !origin.is_local() && transaction.max_fee_per_gas() < self.gas_price { - return Err(Error::TransactionMaxFeeLessThenBaseFee) + return Err(InvalidTransactionError::MaxFeeLessThenBaseFee.into()) } // Checks for chainid if transaction.chain_id() != Some(self.chain_id) { - return Err(Error::TransactionChainId) + return Err(InvalidTransactionError::ChainIdMismatch.into()) } - let account = match self.client.basic_account(transaction.sender())? { + let account = match self.client.basic_account(transaction.sender()).unwrap() { Some(account) => { // Signer account shouldn't have bytecode. Presence of bytecode means this is a // smartcontract. if account.has_bytecode() { - return Err(Error::SignerAccountHasBytecode) + return Err(InvalidTransactionError::SignerAccountHasBytecode.into()) } else { account } @@ -196,15 +196,16 @@ impl TransactionValidator // Checks for nonce if transaction.nonce() < account.nonce { - return Err(Error::TransactionNonceNotConsistent) + return Err(InvalidTransactionError::NonceNotConsistent.into()) } // Checks for max cost if transaction.cost() > account.balance { - return Err(Error::InsufficientFunds { + return Err(InvalidTransactionError::InsufficientFunds { max_fee: transaction.max_fee_per_gas().unwrap_or_default(), available_funds: account.balance, - }) + } + .into()) } // Return the valid transaction