use context_interface::{
journaled_state::Journal,
result::{InvalidHeader, InvalidTransaction},
transaction::{Transaction, TransactionType},
Block, BlockGetter, Cfg, CfgGetter, JournalDBError, JournalGetter, TransactionGetter,
};
use core::cmp::{self, Ordering};
use handler_interface::{InitialAndFloorGas, ValidationHandler};
use interpreter::gas::{self};
use primitives::{B256, U256};
use specification::{eip4844, hardfork::SpecId};
use state::Account;
use std::boxed::Box;
pub struct EthValidation<CTX, ERROR> {
pub _phantom: core::marker::PhantomData<fn() -> (CTX, ERROR)>,
}
impl<CTX, ERROR> Default for EthValidation<CTX, ERROR> {
fn default() -> Self {
Self {
_phantom: core::marker::PhantomData,
}
}
}
impl<CTX, ERROR> EthValidation<CTX, ERROR> {
pub fn new() -> Self {
Self {
_phantom: core::marker::PhantomData,
}
}
pub fn new_boxed() -> Box<Self> {
Box::new(Self::new())
}
}
impl<CTX, ERROR> ValidationHandler for EthValidation<CTX, ERROR>
where
CTX: EthValidationContext,
ERROR: From<InvalidTransaction> + From<InvalidHeader> + From<JournalDBError<CTX>>,
{
type Context = CTX;
type Error = ERROR;
fn validate_env(&self, context: &Self::Context) -> Result<(), Self::Error> {
let spec = context.cfg().spec().into();
if spec.is_enabled_in(SpecId::MERGE) && context.block().prevrandao().is_none() {
return Err(InvalidHeader::PrevrandaoNotSet.into());
}
if spec.is_enabled_in(SpecId::CANCUN)
&& context.block().blob_excess_gas_and_price().is_none()
{
return Err(InvalidHeader::ExcessBlobGasNotSet.into());
}
validate_tx_env::<&Self::Context, InvalidTransaction>(context, spec).map_err(Into::into)
}
fn validate_tx_against_state(&self, context: &mut Self::Context) -> Result<(), Self::Error> {
let tx_caller = context.tx().caller();
let account = &mut context.journal().load_account_code(tx_caller)?;
let account = account.data.clone();
validate_tx_against_account::<CTX, ERROR>(&account, context)
}
fn validate_initial_tx_gas(
&self,
context: &Self::Context,
) -> Result<InitialAndFloorGas, Self::Error> {
let spec = context.cfg().spec().into();
validate_initial_tx_gas::<&Self::Context, InvalidTransaction>(context, spec)
.map_err(Into::into)
}
}
pub fn validate_priority_fee_tx(
max_fee: u128,
max_priority_fee: u128,
base_fee: Option<u128>,
) -> Result<(), InvalidTransaction> {
if max_priority_fee > max_fee {
return Err(InvalidTransaction::PriorityFeeGreaterThanMaxFee);
}
if let Some(base_fee) = base_fee {
let effective_gas_price = cmp::min(max_fee, base_fee.saturating_add(max_priority_fee));
if effective_gas_price < base_fee {
return Err(InvalidTransaction::GasPriceLessThanBasefee);
}
}
Ok(())
}
pub fn validate_eip4844_tx(
blobs: &[B256],
max_blob_fee: u128,
block_blob_gas_price: u128,
max_blobs: u8,
) -> Result<(), InvalidTransaction> {
if block_blob_gas_price > max_blob_fee {
return Err(InvalidTransaction::BlobGasPriceGreaterThanMax);
}
if blobs.is_empty() {
return Err(InvalidTransaction::EmptyBlobs);
}
for blob in blobs {
if blob[0] != eip4844::VERSIONED_HASH_VERSION_KZG {
return Err(InvalidTransaction::BlobVersionNotSupported);
}
}
if blobs.len() > max_blobs as usize {
return Err(InvalidTransaction::TooManyBlobs {
have: blobs.len(),
max: max_blobs as usize,
});
}
Ok(())
}
pub fn validate_tx_env<CTX: TransactionGetter + BlockGetter + CfgGetter, Error>(
context: CTX,
spec_id: SpecId,
) -> Result<(), Error>
where
Error: From<InvalidTransaction>,
{
let tx_type = context.tx().tx_type();
let tx = context.tx();
let base_fee = if context.cfg().is_base_fee_check_disabled() {
None
} else {
Some(context.block().basefee() as u128)
};
match TransactionType::from(tx_type) {
TransactionType::Legacy => {
if let Some(chain_id) = tx.chain_id() {
if chain_id != context.cfg().chain_id() {
return Err(InvalidTransaction::InvalidChainId.into());
}
}
if let Some(base_fee) = base_fee {
if tx.gas_price() < base_fee {
return Err(InvalidTransaction::GasPriceLessThanBasefee.into());
}
}
}
TransactionType::Eip2930 => {
if !spec_id.is_enabled_in(SpecId::BERLIN) {
return Err(InvalidTransaction::Eip2930NotSupported.into());
}
if Some(context.cfg().chain_id()) != tx.chain_id() {
return Err(InvalidTransaction::InvalidChainId.into());
}
if let Some(base_fee) = base_fee {
if tx.gas_price() < base_fee {
return Err(InvalidTransaction::GasPriceLessThanBasefee.into());
}
}
}
TransactionType::Eip1559 => {
if !spec_id.is_enabled_in(SpecId::LONDON) {
return Err(InvalidTransaction::Eip1559NotSupported.into());
}
if Some(context.cfg().chain_id()) != tx.chain_id() {
return Err(InvalidTransaction::InvalidChainId.into());
}
validate_priority_fee_tx(
tx.max_fee_per_gas(),
tx.max_priority_fee_per_gas().unwrap_or_default(),
base_fee,
)?;
}
TransactionType::Eip4844 => {
if !spec_id.is_enabled_in(SpecId::CANCUN) {
return Err(InvalidTransaction::Eip4844NotSupported.into());
}
if Some(context.cfg().chain_id()) != tx.chain_id() {
return Err(InvalidTransaction::InvalidChainId.into());
}
validate_priority_fee_tx(
tx.max_fee_per_gas(),
tx.max_priority_fee_per_gas().unwrap_or_default(),
base_fee,
)?;
validate_eip4844_tx(
tx.blob_versioned_hashes(),
tx.max_fee_per_blob_gas(),
context.block().blob_gasprice().unwrap_or_default(),
context.cfg().blob_max_count(spec_id),
)?;
}
TransactionType::Eip7702 => {
if !spec_id.is_enabled_in(SpecId::PRAGUE) {
return Err(InvalidTransaction::Eip7702NotSupported.into());
}
if Some(context.cfg().chain_id()) != tx.chain_id() {
return Err(InvalidTransaction::InvalidChainId.into());
}
validate_priority_fee_tx(
tx.max_fee_per_gas(),
tx.max_priority_fee_per_gas().unwrap_or_default(),
base_fee,
)?;
let auth_list_len = tx.authorization_list_len();
if auth_list_len == 0 {
return Err(InvalidTransaction::EmptyAuthorizationList.into());
}
}
TransactionType::Custom => {
}
};
if !context.cfg().is_block_gas_limit_disabled() && tx.gas_limit() > context.block().gas_limit()
{
return Err(InvalidTransaction::CallerGasLimitMoreThanBlock.into());
}
if spec_id.is_enabled_in(SpecId::SHANGHAI) && tx.kind().is_create() {
let max_initcode_size = context.cfg().max_code_size().saturating_mul(2);
if context.tx().input().len() > max_initcode_size {
return Err(InvalidTransaction::CreateInitCodeSizeLimit.into());
}
}
Ok(())
}
#[inline]
pub fn validate_tx_against_account<CTX: TransactionGetter + CfgGetter, ERROR>(
account: &Account,
context: &CTX,
) -> Result<(), ERROR>
where
ERROR: From<InvalidTransaction>,
{
let tx = context.tx();
let tx_type = context.tx().tx_type();
if !context.cfg().is_eip3607_disabled() {
let bytecode = &account.info.code.as_ref().unwrap();
if !bytecode.is_empty() && !bytecode.is_eip7702() {
return Err(InvalidTransaction::RejectCallerWithCode.into());
}
}
if !context.cfg().is_nonce_check_disabled() {
let tx = tx.nonce();
let state = account.info.nonce;
match tx.cmp(&state) {
Ordering::Greater => {
return Err(InvalidTransaction::NonceTooHigh { tx, state }.into());
}
Ordering::Less => {
return Err(InvalidTransaction::NonceTooLow { tx, state }.into());
}
_ => {}
}
}
let mut balance_check = U256::from(tx.gas_limit())
.checked_mul(U256::from(tx.max_fee_per_gas()))
.and_then(|gas_cost| gas_cost.checked_add(tx.value()))
.ok_or(InvalidTransaction::OverflowPaymentInTransaction)?;
if tx_type == TransactionType::Eip4844 {
let data_fee = tx.calc_max_data_fee();
balance_check = balance_check
.checked_add(data_fee)
.ok_or(InvalidTransaction::OverflowPaymentInTransaction)?;
}
if balance_check > account.info.balance && !context.cfg().is_balance_check_disabled() {
return Err(InvalidTransaction::LackOfFundForMaxFee {
fee: Box::new(balance_check),
balance: Box::new(account.info.balance),
}
.into());
}
Ok(())
}
pub fn validate_initial_tx_gas<CTX, Error>(
context: CTX,
spec_id: SpecId,
) -> Result<InitialAndFloorGas, Error>
where
CTX: TransactionGetter + CfgGetter,
Error: From<InvalidTransaction>,
{
let spec = context.cfg().spec().into();
let tx = context.tx();
let (accounts, storages) = tx.access_list_nums().unwrap_or_default();
let gas = gas::calculate_initial_tx_gas(
spec_id,
tx.input(),
tx.kind().is_create(),
accounts as u64,
storages as u64,
tx.authorization_list_len() as u64,
);
if gas.initial_gas > tx.gas_limit() {
return Err(InvalidTransaction::CallGasCostMoreThanGasLimit.into());
}
if spec.is_enabled_in(SpecId::PRAGUE) && gas.floor_gas > tx.gas_limit() {
return Err(InvalidTransaction::GasFloorMoreThanGasLimit.into());
};
Ok(gas)
}
pub trait EthValidationContext:
TransactionGetter + BlockGetter + JournalGetter + CfgGetter
{
}
impl<T: TransactionGetter + BlockGetter + JournalGetter + CfgGetter> EthValidationContext for T {}
pub trait EthValidationError<CTX: JournalGetter>:
From<InvalidTransaction> + From<InvalidHeader> + From<JournalDBError<CTX>>
{
}
impl<
CTX: JournalGetter,
T: From<InvalidTransaction> + From<InvalidHeader> + From<JournalDBError<CTX>>,
> EthValidationError<CTX> for T
{
}