mirror of
https://github.com/paradigmxyz/reth.git
synced 2026-01-29 09:08:05 -05:00
refactor: extract transaction consensus errors to standalone type (#1697)
This commit is contained in:
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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 { .. })
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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}")]
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
47
crates/primitives/src/transaction/error.rs
Normal file
47
crates/primitives/src/transaction/error.rs
Normal 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,
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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}")]
|
||||
|
||||
@@ -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 })),
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user