use crate::{l1::ensure_create2_deployer, OpEvmConfig, OptimismBlockExecutionError};
use alloc::{boxed::Box, sync::Arc, vec::Vec};
use alloy_consensus::Transaction as _;
use alloy_eips::eip7685::Requests;
use core::fmt::Display;
use op_alloy_consensus::DepositTransaction;
use reth_chainspec::EthereumHardforks;
use reth_consensus::ConsensusError;
use reth_evm::{
execute::{
BasicBlockExecutorProvider, BlockExecutionError, BlockExecutionStrategy,
BlockExecutionStrategyFactory, BlockValidationError, ExecuteOutput, ProviderError,
},
state_change::post_block_balance_increments,
system_calls::{OnStateHook, SystemCaller},
ConfigureEvm,
};
use reth_optimism_chainspec::OpChainSpec;
use reth_optimism_consensus::validate_block_post_execution;
use reth_optimism_forks::OptimismHardfork;
use reth_primitives::{BlockWithSenders, Header, Receipt, TxType};
use reth_revm::{Database, State};
use revm_primitives::{
db::DatabaseCommit, BlockEnv, CfgEnvWithHandlerCfg, EnvWithHandlerCfg, ResultAndState, U256,
};
use tracing::trace;
#[derive(Debug, Clone)]
pub struct OpExecutionStrategyFactory<EvmConfig = OpEvmConfig> {
chain_spec: Arc<OpChainSpec>,
evm_config: EvmConfig,
}
impl OpExecutionStrategyFactory {
pub fn optimism(chain_spec: Arc<OpChainSpec>) -> Self {
Self::new(chain_spec.clone(), OpEvmConfig::new(chain_spec))
}
}
impl<EvmConfig> OpExecutionStrategyFactory<EvmConfig> {
pub const fn new(chain_spec: Arc<OpChainSpec>, evm_config: EvmConfig) -> Self {
Self { chain_spec, evm_config }
}
}
impl<EvmConfig> BlockExecutionStrategyFactory for OpExecutionStrategyFactory<EvmConfig>
where
EvmConfig:
Clone + Unpin + Sync + Send + 'static + ConfigureEvm<Header = alloy_consensus::Header>,
{
type Strategy<DB: Database<Error: Into<ProviderError> + Display>> =
OpExecutionStrategy<DB, EvmConfig>;
fn create_strategy<DB>(&self, db: DB) -> Self::Strategy<DB>
where
DB: Database<Error: Into<ProviderError> + Display>,
{
let state =
State::builder().with_database(db).with_bundle_update().without_state_clear().build();
OpExecutionStrategy::new(state, self.chain_spec.clone(), self.evm_config.clone())
}
}
#[allow(missing_debug_implementations)]
pub struct OpExecutionStrategy<DB, EvmConfig>
where
EvmConfig: Clone,
{
chain_spec: Arc<OpChainSpec>,
evm_config: EvmConfig,
state: State<DB>,
system_caller: SystemCaller<EvmConfig, OpChainSpec>,
}
impl<DB, EvmConfig> OpExecutionStrategy<DB, EvmConfig>
where
EvmConfig: Clone,
{
pub fn new(state: State<DB>, chain_spec: Arc<OpChainSpec>, evm_config: EvmConfig) -> Self {
let system_caller = SystemCaller::new(evm_config.clone(), chain_spec.clone());
Self { state, chain_spec, evm_config, system_caller }
}
}
impl<DB, EvmConfig> OpExecutionStrategy<DB, EvmConfig>
where
DB: Database<Error: Into<ProviderError> + Display>,
EvmConfig: ConfigureEvm<Header = alloy_consensus::Header>,
{
fn evm_env_for_block(&self, header: &Header, total_difficulty: U256) -> EnvWithHandlerCfg {
let mut cfg = CfgEnvWithHandlerCfg::new(Default::default(), Default::default());
let mut block_env = BlockEnv::default();
self.evm_config.fill_cfg_and_block_env(&mut cfg, &mut block_env, header, total_difficulty);
EnvWithHandlerCfg::new_with_cfg_env(cfg, block_env, Default::default())
}
}
impl<DB, EvmConfig> BlockExecutionStrategy<DB> for OpExecutionStrategy<DB, EvmConfig>
where
DB: Database<Error: Into<ProviderError> + Display>,
EvmConfig: ConfigureEvm<Header = alloy_consensus::Header>,
{
type Error = BlockExecutionError;
fn apply_pre_execution_changes(
&mut self,
block: &BlockWithSenders,
total_difficulty: U256,
) -> Result<(), Self::Error> {
let state_clear_flag =
(*self.chain_spec).is_spurious_dragon_active_at_block(block.header.number);
self.state.set_state_clear_flag(state_clear_flag);
let env = self.evm_env_for_block(&block.header, total_difficulty);
let mut evm = self.evm_config.evm_with_env(&mut self.state, env);
self.system_caller.apply_beacon_root_contract_call(
block.timestamp,
block.number,
block.parent_beacon_block_root,
&mut evm,
)?;
ensure_create2_deployer(self.chain_spec.clone(), block.timestamp, evm.db_mut())
.map_err(|_| OptimismBlockExecutionError::ForceCreate2DeployerFail)?;
Ok(())
}
fn execute_transactions(
&mut self,
block: &BlockWithSenders,
total_difficulty: U256,
) -> Result<ExecuteOutput, Self::Error> {
let env = self.evm_env_for_block(&block.header, total_difficulty);
let mut evm = self.evm_config.evm_with_env(&mut self.state, env);
let is_regolith =
self.chain_spec.fork(OptimismHardfork::Regolith).active_at_timestamp(block.timestamp);
let mut cumulative_gas_used = 0;
let mut receipts = Vec::with_capacity(block.body.transactions.len());
for (sender, transaction) in block.transactions_with_sender() {
let block_available_gas = block.header.gas_limit - cumulative_gas_used;
if transaction.gas_limit() > block_available_gas &&
(is_regolith || !transaction.is_system_transaction())
{
return Err(BlockValidationError::TransactionGasLimitMoreThanAvailableBlockGas {
transaction_gas_limit: transaction.gas_limit(),
block_available_gas,
}
.into())
}
if matches!(transaction.tx_type(), TxType::Eip4844) {
return Err(OptimismBlockExecutionError::BlobTransactionRejected.into())
}
let depositor = (is_regolith && transaction.is_deposit())
.then(|| {
evm.db_mut()
.load_cache_account(*sender)
.map(|acc| acc.account_info().unwrap_or_default())
})
.transpose()
.map_err(|_| OptimismBlockExecutionError::AccountLoadFailed(*sender))?;
self.evm_config.fill_tx_env(evm.tx_mut(), transaction, *sender);
let result_and_state = evm.transact().map_err(move |err| {
let new_err = err.map_db_err(|e| e.into());
BlockValidationError::EVM {
hash: transaction.recalculate_hash(),
error: Box::new(new_err),
}
})?;
trace!(
target: "evm",
?transaction,
"Executed transaction"
);
self.system_caller.on_state(&result_and_state);
let ResultAndState { result, state } = result_and_state;
evm.db_mut().commit(state);
cumulative_gas_used += result.gas_used();
receipts.push(Receipt {
tx_type: transaction.tx_type(),
success: result.is_success(),
cumulative_gas_used,
logs: result.into_logs(),
deposit_nonce: depositor.map(|account| account.nonce),
deposit_receipt_version: (transaction.is_deposit() &&
self.chain_spec
.is_fork_active_at_timestamp(OptimismHardfork::Canyon, block.timestamp))
.then_some(1),
});
}
Ok(ExecuteOutput { receipts, gas_used: cumulative_gas_used })
}
fn apply_post_execution_changes(
&mut self,
block: &BlockWithSenders,
total_difficulty: U256,
_receipts: &[Receipt],
) -> Result<Requests, Self::Error> {
let balance_increments =
post_block_balance_increments(&self.chain_spec.clone(), block, total_difficulty);
self.state
.increment_balances(balance_increments)
.map_err(|_| BlockValidationError::IncrementBalanceFailed)?;
Ok(Requests::default())
}
fn state_ref(&self) -> &State<DB> {
&self.state
}
fn state_mut(&mut self) -> &mut State<DB> {
&mut self.state
}
fn with_state_hook(&mut self, hook: Option<Box<dyn OnStateHook>>) {
self.system_caller.with_state_hook(hook);
}
fn validate_block_post_execution(
&self,
block: &BlockWithSenders,
receipts: &[Receipt],
_requests: &Requests,
) -> Result<(), ConsensusError> {
validate_block_post_execution(block, &self.chain_spec.clone(), receipts)
}
}
#[derive(Debug)]
pub struct OpExecutorProvider;
impl OpExecutorProvider {
pub fn optimism(
chain_spec: Arc<OpChainSpec>,
) -> BasicBlockExecutorProvider<OpExecutionStrategyFactory> {
BasicBlockExecutorProvider::new(OpExecutionStrategyFactory::optimism(chain_spec))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::OpChainSpec;
use alloy_consensus::TxEip1559;
use alloy_primitives::{b256, Address, Signature, StorageKey, StorageValue};
use op_alloy_consensus::TxDeposit;
use reth_chainspec::MIN_TRANSACTION_GAS;
use reth_evm::execute::{BasicBlockExecutorProvider, BatchExecutor, BlockExecutorProvider};
use reth_optimism_chainspec::OpChainSpecBuilder;
use reth_primitives::{Account, Block, BlockBody, Transaction, TransactionSigned};
use reth_revm::{
database::StateProviderDatabase, test_utils::StateProviderTest, L1_BLOCK_CONTRACT,
};
use std::{collections::HashMap, str::FromStr};
fn create_op_state_provider() -> StateProviderTest {
let mut db = StateProviderTest::default();
let l1_block_contract_account =
Account { balance: U256::ZERO, bytecode_hash: None, nonce: 1 };
let mut l1_block_storage = HashMap::default();
l1_block_storage.insert(StorageKey::with_last_byte(1), StorageValue::from(1000000000));
l1_block_storage.insert(StorageKey::with_last_byte(5), StorageValue::from(188));
l1_block_storage.insert(StorageKey::with_last_byte(6), StorageValue::from(684000));
l1_block_storage.insert(
StorageKey::with_last_byte(3),
StorageValue::from_str(
"0x0000000000000000000000000000000000001db0000d27300000000000000005",
)
.unwrap(),
);
db.insert_account(L1_BLOCK_CONTRACT, l1_block_contract_account, None, l1_block_storage);
db
}
fn executor_provider(
chain_spec: Arc<OpChainSpec>,
) -> BasicBlockExecutorProvider<OpExecutionStrategyFactory> {
let strategy_factory =
OpExecutionStrategyFactory::new(chain_spec.clone(), OpEvmConfig::new(chain_spec));
BasicBlockExecutorProvider::new(strategy_factory)
}
#[test]
fn op_deposit_fields_pre_canyon() {
let header = Header {
timestamp: 1,
number: 1,
gas_limit: 1_000_000,
gas_used: 42_000,
receipts_root: b256!(
"83465d1e7d01578c0d609be33570f91242f013e9e295b0879905346abbd63731"
),
..Default::default()
};
let mut db = create_op_state_provider();
let addr = Address::ZERO;
let account = Account { balance: U256::MAX, ..Account::default() };
db.insert_account(addr, account, None, HashMap::default());
let chain_spec = Arc::new(OpChainSpecBuilder::base_mainnet().regolith_activated().build());
let tx = TransactionSigned::from_transaction_and_signature(
Transaction::Eip1559(TxEip1559 {
chain_id: chain_spec.chain.id(),
nonce: 0,
gas_limit: MIN_TRANSACTION_GAS,
to: addr.into(),
..Default::default()
}),
Signature::test_signature(),
);
let tx_deposit = TransactionSigned::from_transaction_and_signature(
Transaction::Deposit(op_alloy_consensus::TxDeposit {
from: addr,
to: addr.into(),
gas_limit: MIN_TRANSACTION_GAS,
..Default::default()
}),
Signature::test_signature(),
);
let provider = executor_provider(chain_spec);
let mut executor = provider.batch_executor(StateProviderDatabase::new(&db));
executor.with_state_mut(|state| {
state.load_cache_account(L1_BLOCK_CONTRACT).unwrap();
});
executor
.execute_and_verify_one(
(
&BlockWithSenders {
block: Block {
header,
body: BlockBody {
transactions: vec![tx, tx_deposit],
..Default::default()
},
},
senders: vec![addr, addr],
},
U256::ZERO,
)
.into(),
)
.unwrap();
let receipts = executor.receipts();
let tx_receipt = receipts[0][0].as_ref().unwrap();
let deposit_receipt = receipts[0][1].as_ref().unwrap();
assert!(deposit_receipt.deposit_receipt_version.is_none());
assert!(tx_receipt.deposit_receipt_version.is_none());
assert!(deposit_receipt.deposit_nonce.is_some());
assert!(tx_receipt.deposit_nonce.is_none());
}
#[test]
fn op_deposit_fields_post_canyon() {
let header = Header {
timestamp: 2,
number: 1,
gas_limit: 1_000_000,
gas_used: 42_000,
receipts_root: b256!(
"fffc85c4004fd03c7bfbe5491fae98a7473126c099ac11e8286fd0013f15f908"
),
..Default::default()
};
let mut db = create_op_state_provider();
let addr = Address::ZERO;
let account = Account { balance: U256::MAX, ..Account::default() };
db.insert_account(addr, account, None, HashMap::default());
let chain_spec = Arc::new(OpChainSpecBuilder::base_mainnet().canyon_activated().build());
let tx = TransactionSigned::from_transaction_and_signature(
Transaction::Eip1559(TxEip1559 {
chain_id: chain_spec.chain.id(),
nonce: 0,
gas_limit: MIN_TRANSACTION_GAS,
to: addr.into(),
..Default::default()
}),
Signature::test_signature(),
);
let tx_deposit = TransactionSigned::from_transaction_and_signature(
Transaction::Deposit(op_alloy_consensus::TxDeposit {
from: addr,
to: addr.into(),
gas_limit: MIN_TRANSACTION_GAS,
..Default::default()
}),
TxDeposit::signature(),
);
let provider = executor_provider(chain_spec);
let mut executor = provider.batch_executor(StateProviderDatabase::new(&db));
executor.with_state_mut(|state| {
state.load_cache_account(L1_BLOCK_CONTRACT).unwrap();
});
executor
.execute_and_verify_one(
(
&BlockWithSenders {
block: Block {
header,
body: BlockBody {
transactions: vec![tx, tx_deposit],
..Default::default()
},
},
senders: vec![addr, addr],
},
U256::ZERO,
)
.into(),
)
.expect("Executing a block while canyon is active should not fail");
let receipts = executor.receipts();
let tx_receipt = receipts[0][0].as_ref().unwrap();
let deposit_receipt = receipts[0][1].as_ref().unwrap();
assert_eq!(deposit_receipt.deposit_receipt_version, Some(1));
assert!(tx_receipt.deposit_receipt_version.is_none());
assert!(deposit_receipt.deposit_nonce.is_some());
assert!(tx_receipt.deposit_nonce.is_none());
}
}