use crate::constants::{GAS_PER_BLOB, MAX_RLP_BLOCK_SIZE, POST_OSAKA_GAS_LIMIT_CAP};
use crate::errors::InvalidBlockError;
use crate::types::requests::{EncodedRequests, Requests, compute_requests_hash};
use crate::types::{
Block, BlockHeader, ChainConfig, EIP4844Transaction, Receipt, Transaction,
compute_receipts_root_and_logs_bloom, validate_block_header, validate_cancun_header_fields,
validate_prague_header_fields, validate_pre_cancun_header_fields,
};
use ethrex_crypto::Crypto;
use ethrex_rlp::encode::RLPEncode;
pub fn validate_block_pre_execution(
block: &Block,
parent_header: &BlockHeader,
chain_config: &ChainConfig,
elasticity_multiplier: u64,
) -> Result<(), InvalidBlockError> {
validate_block_header(&block.header, parent_header, elasticity_multiplier)?;
if chain_config.is_osaka_activated(block.header.timestamp) {
let block_rlp_size = block.length();
if block_rlp_size > MAX_RLP_BLOCK_SIZE as usize {
return Err(InvalidBlockError::MaximumRlpSizeExceeded(
MAX_RLP_BLOCK_SIZE,
block_rlp_size as u64,
));
}
}
if chain_config.is_prague_activated(block.header.timestamp) {
validate_prague_header_fields(&block.header, parent_header, chain_config)?;
verify_blob_gas_usage(block, chain_config)?;
if chain_config.is_osaka_activated(block.header.timestamp)
&& !chain_config.is_amsterdam_activated(block.header.timestamp)
{
verify_transaction_max_gas_limit(block)?;
}
} else if chain_config.is_cancun_activated(block.header.timestamp) {
validate_cancun_header_fields(&block.header, parent_header, chain_config)?;
verify_blob_gas_usage(block, chain_config)?;
} else {
validate_pre_cancun_header_fields(&block.header)?;
}
for tx in &block.body.transactions {
if matches!(tx, Transaction::PrivilegedL2Transaction(_)) {
continue;
}
if let Some(tx_chain_id) = tx.chain_id()
&& tx_chain_id != chain_config.chain_id
{
return Err(InvalidBlockError::InvalidTransactionChainId {
have: tx_chain_id,
want: chain_config.chain_id,
});
}
}
Ok(())
}
pub fn validate_gas_used(
block_gas_used: u64,
block_header: &BlockHeader,
) -> Result<(), InvalidBlockError> {
if block_gas_used != block_header.gas_used {
return Err(InvalidBlockError::GasUsedMismatch(
block_gas_used,
block_header.gas_used,
));
}
Ok(())
}
pub fn validate_receipts_root_and_logs_bloom(
block_header: &BlockHeader,
receipts: &[Receipt],
crypto: &dyn Crypto,
) -> Result<(), InvalidBlockError> {
let (receipts_root, logs_bloom) = compute_receipts_root_and_logs_bloom(receipts, crypto);
if receipts_root != block_header.receipts_root {
return Err(InvalidBlockError::ReceiptsRootMismatch);
}
if logs_bloom != block_header.logs_bloom {
return Err(InvalidBlockError::LogsBloomMismatch);
}
Ok(())
}
pub fn validate_requests_hash(
header: &BlockHeader,
chain_config: &ChainConfig,
requests: &[Requests],
) -> Result<(), InvalidBlockError> {
if !chain_config.is_prague_activated(header.timestamp) {
return Ok(());
}
let encoded_requests: Vec<EncodedRequests> = requests.iter().map(|r| r.encode()).collect();
let computed_requests_hash = compute_requests_hash(&encoded_requests);
let valid = header
.requests_hash
.map(|requests_hash| requests_hash == computed_requests_hash)
.unwrap_or(false);
if !valid {
return Err(InvalidBlockError::RequestsHashMismatch);
}
Ok(())
}
fn validate_bal_indices(
indices: impl Iterator<Item = u32>,
max_valid_index: u32,
) -> Result<(), InvalidBlockError> {
for index in indices {
if index > max_valid_index {
return Err(InvalidBlockError::BlockAccessListIndexOutOfBounds {
index,
max: max_valid_index,
});
}
}
Ok(())
}
pub fn validate_header_bal_indices(
bal: &crate::types::block_access_list::BlockAccessList,
transaction_count: usize,
) -> Result<(), InvalidBlockError> {
let max_valid_index = u32::try_from(transaction_count + 1).unwrap_or(u32::MAX);
for account in bal.accounts() {
validate_bal_indices(
account
.storage_changes
.iter()
.flat_map(|slot| slot.slot_changes.iter().map(|c| c.block_access_index)),
max_valid_index,
)?;
validate_bal_indices(
account.balance_changes.iter().map(|c| c.block_access_index),
max_valid_index,
)?;
validate_bal_indices(
account.nonce_changes.iter().map(|c| c.block_access_index),
max_valid_index,
)?;
validate_bal_indices(
account.code_changes.iter().map(|c| c.block_access_index),
max_valid_index,
)?;
}
Ok(())
}
pub fn validate_block_access_list_hash(
header: &BlockHeader,
chain_config: &ChainConfig,
computed_bal: &crate::types::block_access_list::BlockAccessList,
transaction_count: usize,
) -> Result<(), InvalidBlockError> {
use crate::constants::BAL_ITEM_COST;
if !chain_config.is_amsterdam_activated(header.timestamp) {
return Ok(());
}
let max_valid_index = u32::try_from(transaction_count + 1).unwrap_or(u32::MAX);
let mut bal_items: u64 = 0;
for account in computed_bal.accounts() {
bal_items += 1; bal_items += account.storage_reads.len() as u64;
bal_items += account.storage_changes.len() as u64;
validate_bal_indices(
account
.storage_changes
.iter()
.flat_map(|slot| slot.slot_changes.iter().map(|c| c.block_access_index)),
max_valid_index,
)?;
validate_bal_indices(
account.balance_changes.iter().map(|c| c.block_access_index),
max_valid_index,
)?;
validate_bal_indices(
account.nonce_changes.iter().map(|c| c.block_access_index),
max_valid_index,
)?;
validate_bal_indices(
account.code_changes.iter().map(|c| c.block_access_index),
max_valid_index,
)?;
}
let max_items = header.gas_limit / BAL_ITEM_COST;
if bal_items > max_items {
return Err(InvalidBlockError::BlockAccessListSizeExceeded {
items: bal_items,
max_items,
});
}
let computed_hash = computed_bal.compute_hash();
let valid = header
.block_access_list_hash
.map(|expected_hash| expected_hash == computed_hash)
.unwrap_or(false);
if !valid {
return Err(InvalidBlockError::BlockAccessListHashMismatch);
}
Ok(())
}
pub fn validate_block_access_list_size(
header: &BlockHeader,
chain_config: &ChainConfig,
computed_bal: &crate::types::block_access_list::BlockAccessList,
) -> Result<(), InvalidBlockError> {
use crate::constants::BAL_ITEM_COST;
if !chain_config.is_amsterdam_activated(header.timestamp) {
return Ok(());
}
let bal_items = computed_bal.item_count();
let max_items = header.gas_limit / BAL_ITEM_COST;
if bal_items > max_items {
return Err(InvalidBlockError::BlockAccessListSizeExceeded {
items: bal_items,
max_items,
});
}
Ok(())
}
fn verify_blob_gas_usage(block: &Block, config: &ChainConfig) -> Result<(), InvalidBlockError> {
let mut blob_gas_used = 0_u32;
let mut blobs_in_block = 0_u32;
let max_blob_number_per_block = config
.get_fork_blob_schedule(block.header.timestamp)
.map(|schedule| schedule.max)
.ok_or(InvalidBlockError::InvalidBlockFork)?;
let max_blob_gas_per_block = max_blob_number_per_block * GAS_PER_BLOB;
for transaction in block.body.transactions.iter() {
if let crate::types::Transaction::EIP4844Transaction(tx) = transaction {
blob_gas_used += get_total_blob_gas(tx);
blobs_in_block += tx.blob_versioned_hashes.len() as u32;
}
}
if blob_gas_used > max_blob_gas_per_block {
return Err(InvalidBlockError::ExceededMaxBlobGasPerBlock);
}
if blobs_in_block > max_blob_number_per_block {
return Err(InvalidBlockError::ExceededMaxBlobNumberPerBlock);
}
if block
.header
.blob_gas_used
.is_some_and(|header_blob_gas_used| header_blob_gas_used != blob_gas_used as u64)
{
return Err(InvalidBlockError::BlobGasUsedMismatch);
}
Ok(())
}
fn verify_transaction_max_gas_limit(block: &Block) -> Result<(), InvalidBlockError> {
for transaction in block.body.transactions.iter() {
if transaction.gas_limit() > POST_OSAKA_GAS_LIMIT_CAP {
return Err(InvalidBlockError::InvalidTransaction(format!(
"Transaction gas limit exceeds maximum. Transaction hash: {}, transaction gas limit: {}",
transaction.hash(),
transaction.gas_limit()
)));
}
}
Ok(())
}
pub fn get_total_blob_gas(tx: &EIP4844Transaction) -> u32 {
GAS_PER_BLOB * tx.blob_versioned_hashes.len() as u32
}