#[cfg(not(feature = "std"))]
use alloc as std;
use std::boxed::Box;
use alloy_consensus::Transaction;
use alloy_evm::{
block::{BlockExecutionError, BlockValidationError},
RecoveredTx,
};
use alloy_primitives::TxHash;
use op_revm::transaction::deposit::DEPOSIT_TRANSACTION_TYPE;
use crate::{
BlockMegaTransactionOutcome, EvmTxRuntimeLimits, MegaBlockLimitExceededError, MegaHardfork,
MegaTransactionExt, MegaTxLimitExceededError,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct BlockLimits {
pub tx_gas_limit: u64,
pub block_gas_limit: u64,
pub tx_encode_size_limit: u64,
pub block_txs_encode_size_limit: u64,
pub tx_da_size_limit: u64,
pub block_da_size_limit: u64,
pub tx_data_limit: u64,
pub block_txs_data_limit: u64,
pub tx_kv_update_limit: u64,
pub block_kv_update_limit: u64,
pub tx_compute_gas_limit: u64,
pub block_compute_gas_limit: u64,
pub tx_state_growth_limit: u64,
pub block_state_growth_limit: u64,
pub block_env_access_compute_gas_limit: u64,
pub oracle_access_compute_gas_limit: u64,
}
impl BlockLimits {
pub fn no_limits() -> Self {
Self {
tx_gas_limit: u64::MAX,
block_gas_limit: u64::MAX,
tx_encode_size_limit: u64::MAX,
block_txs_encode_size_limit: u64::MAX,
tx_da_size_limit: u64::MAX,
block_da_size_limit: u64::MAX,
tx_data_limit: u64::MAX,
block_txs_data_limit: u64::MAX,
tx_kv_update_limit: u64::MAX,
block_kv_update_limit: u64::MAX,
tx_compute_gas_limit: u64::MAX,
block_compute_gas_limit: u64::MAX,
tx_state_growth_limit: u64::MAX,
block_state_growth_limit: u64::MAX,
block_env_access_compute_gas_limit: u64::MAX,
oracle_access_compute_gas_limit: u64::MAX,
}
}
pub fn from_hardfork_and_block_gas_limit(hardfork: MegaHardfork, block_gas_limit: u64) -> Self {
let spec = hardfork.spec_id();
let tx_runtime_limits = EvmTxRuntimeLimits::from_spec(spec);
let limits = Self::no_limits()
.with_tx_runtime_limits(tx_runtime_limits)
.with_block_gas_limit(block_gas_limit);
match hardfork {
MegaHardfork::Rex |
MegaHardfork::Rex1 |
MegaHardfork::Rex2 |
MegaHardfork::Rex3 |
MegaHardfork::Rex4 |
MegaHardfork::Rex5 => Self {
block_txs_data_limit: crate::constants::mini_rex::BLOCK_DATA_LIMIT,
block_kv_update_limit: crate::constants::mini_rex::BLOCK_KV_UPDATE_LIMIT,
block_state_growth_limit: crate::constants::rex::BLOCK_STATE_GROWTH_LIMIT,
..limits
},
MegaHardfork::MiniRex | MegaHardfork::MiniRex2 => Self {
block_txs_data_limit: crate::constants::mini_rex::BLOCK_DATA_LIMIT,
block_kv_update_limit: crate::constants::mini_rex::BLOCK_KV_UPDATE_LIMIT,
..limits
},
MegaHardfork::MiniRex1 => limits,
}
}
}
impl BlockLimits {
pub fn with_tx_runtime_limits(mut self, limits: EvmTxRuntimeLimits) -> Self {
self.tx_data_limit = limits.tx_data_size_limit;
self.tx_kv_update_limit = limits.tx_kv_updates_limit;
self.tx_compute_gas_limit = limits.tx_compute_gas_limit;
self.tx_state_growth_limit = limits.tx_state_growth_limit;
self.block_env_access_compute_gas_limit = limits.block_env_access_compute_gas_limit;
self.oracle_access_compute_gas_limit = limits.oracle_access_compute_gas_limit;
self
}
pub fn with_tx_gas_limit(mut self, limit: u64) -> Self {
self.tx_gas_limit = limit;
self
}
pub fn with_block_gas_limit(mut self, limit: u64) -> Self {
self.block_gas_limit = limit;
self
}
pub fn with_tx_encode_size_limit(mut self, limit: u64) -> Self {
self.tx_encode_size_limit = limit;
self
}
pub fn with_block_txs_encode_size_limit(mut self, limit: u64) -> Self {
self.block_txs_encode_size_limit = limit;
self
}
pub fn with_tx_da_size_limit(mut self, limit: u64) -> Self {
self.tx_da_size_limit = limit;
self
}
pub fn with_block_da_size_limit(mut self, limit: u64) -> Self {
self.block_da_size_limit = limit;
self
}
pub fn with_tx_data_limit(mut self, limit: u64) -> Self {
self.tx_data_limit = limit;
self
}
pub fn with_block_txs_data_limit(mut self, limit: u64) -> Self {
self.block_txs_data_limit = limit;
self
}
pub fn with_tx_kv_update_limit(mut self, limit: u64) -> Self {
self.tx_kv_update_limit = limit;
self
}
pub fn with_block_kv_update_limit(mut self, limit: u64) -> Self {
self.block_kv_update_limit = limit;
self
}
pub fn with_tx_compute_gas_limit(mut self, limit: u64) -> Self {
self.tx_compute_gas_limit = limit;
self
}
pub fn with_block_compute_gas_limit(mut self, limit: u64) -> Self {
self.block_compute_gas_limit = limit;
self
}
pub fn with_tx_state_growth_limit(mut self, limit: u64) -> Self {
self.tx_state_growth_limit = limit;
self
}
pub fn with_block_state_growth_limit(mut self, limit: u64) -> Self {
self.block_state_growth_limit = limit;
self
}
pub fn with_block_env_access_compute_gas_limit(mut self, limit: u64) -> Self {
self.block_env_access_compute_gas_limit = limit;
self
}
pub fn with_oracle_access_compute_gas_limit(mut self, limit: u64) -> Self {
self.oracle_access_compute_gas_limit = limit;
self
}
pub fn to_block_limiter(self) -> BlockLimiter {
BlockLimiter {
limits: self,
block_gas_used: 0,
block_data_used: 0,
block_kv_updates_used: 0,
block_tx_size_used: 0,
block_da_size_used: 0,
block_compute_gas_used: 0,
block_state_growth_used: 0,
}
}
pub fn to_evm_tx_runtime_limits(&self) -> EvmTxRuntimeLimits {
EvmTxRuntimeLimits {
tx_data_size_limit: self.tx_data_limit,
tx_kv_updates_limit: self.tx_kv_update_limit,
tx_compute_gas_limit: self.tx_compute_gas_limit,
tx_state_growth_limit: self.tx_state_growth_limit,
block_env_access_compute_gas_limit: self.block_env_access_compute_gas_limit,
oracle_access_compute_gas_limit: self.oracle_access_compute_gas_limit,
}
}
}
#[derive(Debug, Clone)]
pub struct BlockLimiter {
pub limits: BlockLimits,
pub block_gas_used: u64,
pub block_tx_size_used: u64,
pub block_da_size_used: u64,
pub block_data_used: u64,
pub block_kv_updates_used: u64,
pub block_compute_gas_used: u64,
pub block_state_growth_used: u64,
}
impl BlockLimiter {
pub fn new(limits: BlockLimits) -> Self {
Self {
limits,
block_gas_used: 0,
block_data_used: 0,
block_kv_updates_used: 0,
block_tx_size_used: 0,
block_da_size_used: 0,
block_compute_gas_used: 0,
block_state_growth_used: 0,
}
}
pub fn pre_execution_check(
&self,
tx_hash: TxHash,
gas_limit: u64,
tx_size: u64,
da_size: u64,
is_deposit: bool,
) -> Result<(), BlockExecutionError> {
if gas_limit > self.limits.tx_gas_limit {
return Err(BlockExecutionError::Validation(BlockValidationError::InvalidTx {
hash: tx_hash,
error: Box::new(MegaTxLimitExceededError::TransactionGasLimit {
tx_gas_limit: gas_limit,
limit: self.limits.tx_gas_limit,
}),
}));
}
if self.block_gas_used.saturating_add(gas_limit) > self.limits.block_gas_limit {
return Err(BlockExecutionError::Validation(
BlockValidationError::TransactionGasLimitMoreThanAvailableBlockGas {
transaction_gas_limit: gas_limit,
block_available_gas: self
.limits
.block_gas_limit
.saturating_sub(self.block_gas_used),
},
));
}
if tx_size > self.limits.tx_encode_size_limit {
return Err(BlockExecutionError::Validation(BlockValidationError::InvalidTx {
hash: tx_hash,
error: Box::new(MegaTxLimitExceededError::TransactionEncodeSizeLimit {
tx_size,
limit: self.limits.tx_encode_size_limit,
}),
}));
}
if tx_size.saturating_add(self.block_tx_size_used) > self.limits.block_txs_encode_size_limit
{
return Err(BlockExecutionError::Validation(BlockValidationError::InvalidTx {
hash: tx_hash,
error: Box::new(MegaBlockLimitExceededError::TransactionEncodeSizeLimit {
block_used: self.block_tx_size_used,
tx_used: tx_size,
limit: self.limits.block_txs_encode_size_limit,
}),
}));
}
if !is_deposit {
if da_size > self.limits.tx_da_size_limit {
return Err(BlockExecutionError::Validation(BlockValidationError::InvalidTx {
hash: tx_hash,
error: Box::new(MegaTxLimitExceededError::DataAvailabilitySizeLimit {
da_size,
limit: self.limits.tx_da_size_limit,
}),
}));
}
if da_size.saturating_add(self.block_da_size_used) > self.limits.block_da_size_limit {
return Err(BlockExecutionError::Validation(BlockValidationError::InvalidTx {
hash: tx_hash,
error: Box::new(MegaBlockLimitExceededError::DataAvailabilitySizeLimit {
block_used: self.block_da_size_used,
tx_used: da_size,
limit: self.limits.block_da_size_limit,
}),
}));
}
}
if self.block_data_used >= self.limits.block_txs_data_limit {
return Err(BlockExecutionError::Validation(BlockValidationError::InvalidTx {
hash: tx_hash,
error: Box::new(MegaBlockLimitExceededError::TransactionDataLimit {
block_used: self.block_data_used,
limit: self.limits.block_txs_data_limit,
}),
}));
}
if self.block_kv_updates_used >= self.limits.block_kv_update_limit {
return Err(BlockExecutionError::Validation(BlockValidationError::InvalidTx {
hash: tx_hash,
error: Box::new(MegaBlockLimitExceededError::KVUpdateLimit {
block_used: self.block_kv_updates_used,
limit: self.limits.block_kv_update_limit,
}),
}));
}
if self.block_compute_gas_used >= self.limits.block_compute_gas_limit {
return Err(BlockExecutionError::Validation(BlockValidationError::InvalidTx {
hash: tx_hash,
error: Box::new(MegaBlockLimitExceededError::ComputeGasLimit {
block_used: self.block_compute_gas_used,
limit: self.limits.block_compute_gas_limit,
}),
}));
}
if self.block_state_growth_used >= self.limits.block_state_growth_limit {
return Err(BlockExecutionError::Validation(BlockValidationError::InvalidTx {
hash: tx_hash,
error: Box::new(MegaBlockLimitExceededError::StateGrowthLimit {
block_used: self.block_state_growth_used,
limit: self.limits.block_state_growth_limit,
}),
}));
}
Ok(())
}
pub fn post_execution_update<T: Transaction + MegaTransactionExt>(
&mut self,
outcome: &BlockMegaTransactionOutcome<impl RecoveredTx<T>>,
) -> Result<(), BlockExecutionError> {
let is_deposit = outcome.tx.tx().ty() == DEPOSIT_TRANSACTION_TYPE;
self.post_execution_update_raw(
outcome.result.gas_used(),
outcome.tx_size,
outcome.da_size,
outcome.data_size,
outcome.kv_updates,
outcome.compute_gas_used,
outcome.state_growth_used,
is_deposit,
);
Ok(())
}
#[allow(clippy::too_many_arguments)]
pub fn post_execution_update_raw(
&mut self,
gas_used: u64,
tx_size: u64,
da_size: u64,
tx_data: u64,
kv_updates: u64,
compute_gas_used: u64,
state_growth_used: u64,
is_deposit: bool,
) {
self.block_gas_used = self.block_gas_used.saturating_add(gas_used);
self.block_tx_size_used = self.block_tx_size_used.saturating_add(tx_size);
if !is_deposit {
self.block_da_size_used = self.block_da_size_used.saturating_add(da_size);
}
self.block_data_used = self.block_data_used.saturating_add(tx_data);
self.block_kv_updates_used = self.block_kv_updates_used.saturating_add(kv_updates);
self.block_compute_gas_used = self.block_compute_gas_used.saturating_add(compute_gas_used);
self.block_state_growth_used =
self.block_state_growth_used.saturating_add(state_growth_used);
}
pub fn is_block_limit_reached(&self) -> bool {
self.block_gas_used >= self.limits.block_gas_limit ||
self.block_tx_size_used >= self.limits.block_txs_encode_size_limit ||
self.block_da_size_used >= self.limits.block_da_size_limit ||
self.block_data_used >= self.limits.block_txs_data_limit ||
self.block_kv_updates_used >= self.limits.block_kv_update_limit ||
self.block_compute_gas_used >= self.limits.block_compute_gas_limit ||
self.block_state_growth_used >= self.limits.block_state_growth_limit
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloy_primitives::B256;
fn limits_with_block_gas(block_gas_limit: u64) -> BlockLimits {
let mut limits = BlockLimits::no_limits();
limits.block_gas_limit = block_gas_limit;
limits.tx_gas_limit = u64::MAX;
limits
}
fn limits_with_block_tx_size(block_txs_encode_size_limit: u64) -> BlockLimits {
let mut limits = BlockLimits::no_limits();
limits.block_txs_encode_size_limit = block_txs_encode_size_limit;
limits
}
fn limits_with_block_da_size(block_da_size_limit: u64) -> BlockLimits {
let mut limits = BlockLimits::no_limits();
limits.block_da_size_limit = block_da_size_limit;
limits
}
#[test]
fn test_pre_execution_check_block_gas_addition_saturates() {
let mut limiter = BlockLimiter::new(limits_with_block_gas(1_000_000));
limiter.block_gas_used = u64::MAX - 10;
let result = limiter.pre_execution_check(B256::ZERO, u64::MAX, 0, 0, false);
assert!(result.is_err(), "saturating_add must keep the rejection in place");
}
#[test]
fn test_pre_execution_check_block_tx_size_addition_saturates() {
let mut limiter = BlockLimiter::new(limits_with_block_tx_size(1_000_000));
limiter.block_tx_size_used = u64::MAX - 10;
let result = limiter.pre_execution_check(B256::ZERO, 0, u64::MAX, 0, false);
assert!(result.is_err());
}
#[test]
fn test_pre_execution_check_block_da_size_addition_saturates() {
let mut limiter = BlockLimiter::new(limits_with_block_da_size(1_000_000));
limiter.block_da_size_used = u64::MAX - 10;
let result = limiter.pre_execution_check(B256::ZERO, 0, 0, u64::MAX, false);
assert!(result.is_err());
}
#[test]
fn test_post_execution_update_raw_saturates_all_counters() {
let mut limiter = BlockLimiter::new(BlockLimits::no_limits());
limiter.block_gas_used = u64::MAX - 1;
limiter.block_tx_size_used = u64::MAX - 1;
limiter.block_da_size_used = u64::MAX - 1;
limiter.block_data_used = u64::MAX - 1;
limiter.block_kv_updates_used = u64::MAX - 1;
limiter.block_compute_gas_used = u64::MAX - 1;
limiter.block_state_growth_used = u64::MAX - 1;
limiter.post_execution_update_raw(
u64::MAX,
u64::MAX,
u64::MAX,
u64::MAX,
u64::MAX,
u64::MAX,
u64::MAX,
false,
);
assert_eq!(limiter.block_gas_used, u64::MAX);
assert_eq!(limiter.block_tx_size_used, u64::MAX);
assert_eq!(limiter.block_da_size_used, u64::MAX);
assert_eq!(limiter.block_data_used, u64::MAX);
assert_eq!(limiter.block_kv_updates_used, u64::MAX);
assert_eq!(limiter.block_compute_gas_used, u64::MAX);
assert_eq!(limiter.block_state_growth_used, u64::MAX);
}
#[test]
fn test_post_execution_update_raw_skips_da_for_deposits() {
let mut limiter = BlockLimiter::new(BlockLimits::no_limits());
limiter.block_da_size_used = 100;
limiter.post_execution_update_raw(0, 0, u64::MAX, 0, 0, 0, 0, true);
assert_eq!(limiter.block_da_size_used, 100);
}
}