refactor: extract transaction consensus errors to standalone type (#1697)

This commit is contained in:
Matthias Seitz
2023-03-10 13:43:04 +01:00
committed by GitHub
parent ba96b9d165
commit ad5f9aa78c
17 changed files with 183 additions and 154 deletions

View File

@@ -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)
}

View File

@@ -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<u64>,
) -> 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<PROV: HeaderProvider>(
// 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<Provider: HeaderProvider + AccountProvider>(
let transactions = block
.body
.iter()
.map(|tx| tx.try_ecrecovered().ok_or(Error::TransactionSignerRecoveryError))
.map(|tx| tx.try_ecrecovered().ok_or(ConsensusError::TransactionSignerRecoveryError))
.collect::<Result<Vec<_>, _>>()?;
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 { .. })
);
}

View File

@@ -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<crate::error::Error> for Error {
fn from(_: crate::error::Error) -> Self {
Error::TransactionSignerRecoveryError
}
/// Error for a transaction that violates consensus.
#[error(transparent)]
InvalidTransaction(#[from] InvalidTransactionError),
}

View File

@@ -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),

View File

@@ -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}")]

View File

@@ -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(())
}

View File

@@ -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.

View File

@@ -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;

View File

@@ -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,
}

View File

@@ -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;

View File

@@ -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}")]

View File

@@ -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 })),

View File

@@ -124,7 +124,10 @@ impl<DB: Database> Stage<DB> 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<DB: Database> Stage<DB> 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,
},

View File

@@ -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<DB: Database> Stage<DB> 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,
})
}
}

View File

@@ -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<Item = V::Transaction>,
) -> PoolResult<HashMap<TxHash, Result<TransactionValidationOutcome<V::Transaction>, Error>>>
{
) -> PoolResult<
HashMap<TxHash, Result<TransactionValidationOutcome<V::Transaction>, 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<TransactionValidationOutcome<V::Transaction>, Error>) {
) -> (TxHash, Result<TransactionValidationOutcome<V::Transaction>, ConsensusError>) {
let hash = *transaction.hash();
// TODO(mattsse): this is where additional validate checks would go, like banned senders

View File

@@ -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<T: PoolTransaction> TransactionValidator for NoopTransactionValidator<T> {
&self,
origin: TransactionOrigin,
transaction: Self::Transaction,
) -> Result<TransactionValidationOutcome<Self::Transaction>, Error> {
) -> Result<TransactionValidationOutcome<Self::Transaction>, ConsensusError> {
Ok(TransactionValidationOutcome::Valid {
balance: Default::default(),
state_nonce: 0,

View File

@@ -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<TransactionValidationOutcome<Self::Transaction>, Error>;
) -> Result<TransactionValidationOutcome<Self::Transaction>, 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<T: PoolTransaction + AccountProvider + Clone> TransactionValidator
&self,
origin: TransactionOrigin,
transaction: Self::Transaction,
) -> Result<TransactionValidationOutcome<Self::Transaction>, Error> {
) -> Result<TransactionValidationOutcome<Self::Transaction>, ConsensusError> {
// Checks for tx_type
match transaction.tx_type() {
LEGACY_TX_TYPE_ID => {
@@ -119,18 +119,18 @@ impl<T: PoolTransaction + AccountProvider + Clone> 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<T: PoolTransaction + AccountProvider + Clone> 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<T: PoolTransaction + AccountProvider + Clone> 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