use crate::{
dao_fork::{DAO_HARDFORK_BENEFICIARY, DAO_HARDKFORK_ACCOUNTS},
EthEvmConfig,
};
use alloc::{boxed::Box, sync::Arc, vec, vec::Vec};
use core::fmt::Display;
use reth_chainspec::{ChainSpec, EthereumHardforks, MAINNET};
use reth_ethereum_consensus::validate_block_post_execution;
use reth_evm::{
execute::{
BatchExecutor, BlockExecutionError, BlockExecutionInput, BlockExecutionOutput,
BlockExecutorProvider, BlockValidationError, Executor, ProviderError,
},
system_calls::{
apply_beacon_root_contract_call, apply_blockhashes_contract_call,
apply_consolidation_requests_contract_call, apply_withdrawal_requests_contract_call,
},
ConfigureEvm,
};
use reth_execution_types::ExecutionOutcome;
use reth_primitives::{
BlockNumber, BlockWithSenders, EthereumHardfork, Header, Receipt, Request, U256,
};
use reth_prune_types::PruneModes;
use reth_revm::{
batch::BlockBatchRecord,
db::{
states::{bundle_state::BundleRetention, StorageSlot},
BundleAccount, State,
},
state_change::post_block_balance_increments,
Evm,
};
use revm_primitives::{
db::{Database, DatabaseCommit},
BlockEnv, CfgEnvWithHandlerCfg, EnvWithHandlerCfg, ResultAndState,
};
use std::collections::hash_map::Entry;
#[derive(Debug, Clone)]
pub struct EthExecutorProvider<EvmConfig = EthEvmConfig> {
chain_spec: Arc<ChainSpec>,
evm_config: EvmConfig,
}
impl EthExecutorProvider {
pub fn ethereum(chain_spec: Arc<ChainSpec>) -> Self {
Self::new(chain_spec.clone(), EthEvmConfig::new(chain_spec))
}
pub fn mainnet() -> Self {
Self::ethereum(MAINNET.clone())
}
}
impl<EvmConfig> EthExecutorProvider<EvmConfig> {
pub const fn new(chain_spec: Arc<ChainSpec>, evm_config: EvmConfig) -> Self {
Self { chain_spec, evm_config }
}
}
impl<EvmConfig> EthExecutorProvider<EvmConfig>
where
EvmConfig: ConfigureEvm<Header = Header>,
{
fn eth_executor<DB>(&self, db: DB) -> EthBlockExecutor<EvmConfig, DB>
where
DB: Database<Error: Into<ProviderError>>,
{
EthBlockExecutor::new(
self.chain_spec.clone(),
self.evm_config.clone(),
State::builder().with_database(db).with_bundle_update().without_state_clear().build(),
)
}
}
impl<EvmConfig> BlockExecutorProvider for EthExecutorProvider<EvmConfig>
where
EvmConfig: ConfigureEvm<Header = Header>,
{
type Executor<DB: Database<Error: Into<ProviderError> + Display>> =
EthBlockExecutor<EvmConfig, DB>;
type BatchExecutor<DB: Database<Error: Into<ProviderError> + Display>> =
EthBatchExecutor<EvmConfig, DB>;
fn executor<DB>(&self, db: DB) -> Self::Executor<DB>
where
DB: Database<Error: Into<ProviderError> + Display>,
{
self.eth_executor(db)
}
fn batch_executor<DB>(&self, db: DB) -> Self::BatchExecutor<DB>
where
DB: Database<Error: Into<ProviderError> + Display>,
{
let executor = self.eth_executor(db);
EthBatchExecutor { executor, batch_record: BlockBatchRecord::default() }
}
}
#[derive(Debug, Clone)]
struct EthExecuteOutput {
receipts: Vec<Receipt>,
requests: Vec<Request>,
gas_used: u64,
}
#[derive(Debug, Clone)]
struct EthEvmExecutor<EvmConfig> {
chain_spec: Arc<ChainSpec>,
evm_config: EvmConfig,
}
impl<EvmConfig> EthEvmExecutor<EvmConfig>
where
EvmConfig: ConfigureEvm<Header = Header>,
{
fn execute_state_transitions<Ext, DB>(
&self,
block: &BlockWithSenders,
mut evm: Evm<'_, Ext, &mut State<DB>>,
) -> Result<EthExecuteOutput, BlockExecutionError>
where
DB: Database,
DB::Error: Into<ProviderError> + Display,
{
apply_beacon_root_contract_call(
&self.evm_config,
&self.chain_spec,
block.timestamp,
block.number,
block.parent_beacon_block_root,
&mut evm,
)?;
apply_blockhashes_contract_call(
&self.evm_config,
&self.chain_spec,
block.timestamp,
block.number,
block.parent_hash,
&mut evm,
)?;
let mut cumulative_gas_used = 0;
let mut receipts = Vec::with_capacity(block.body.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 {
return Err(BlockValidationError::TransactionGasLimitMoreThanAvailableBlockGas {
transaction_gas_limit: transaction.gas_limit(),
block_available_gas,
}
.into())
}
self.evm_config.fill_tx_env(evm.tx_mut(), transaction, *sender);
let ResultAndState { result, 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),
}
})?;
evm.db_mut().commit(state);
cumulative_gas_used += result.gas_used();
receipts.push(
#[allow(clippy::needless_update)] Receipt {
tx_type: transaction.tx_type(),
success: result.is_success(),
cumulative_gas_used,
logs: result.into_logs(),
..Default::default()
},
);
}
let requests = if self.chain_spec.is_prague_active_at_timestamp(block.timestamp) {
let deposit_requests =
crate::eip6110::parse_deposits_from_receipts(&self.chain_spec, &receipts)?;
let withdrawal_requests =
apply_withdrawal_requests_contract_call(&self.evm_config, &mut evm)?;
let consolidation_requests =
apply_consolidation_requests_contract_call(&self.evm_config, &mut evm)?;
[deposit_requests, withdrawal_requests, consolidation_requests].concat()
} else {
vec![]
};
Ok(EthExecuteOutput { receipts, requests, gas_used: cumulative_gas_used })
}
}
#[derive(Debug)]
pub struct EthBlockExecutor<EvmConfig, DB> {
executor: EthEvmExecutor<EvmConfig>,
state: State<DB>,
}
impl<EvmConfig, DB> EthBlockExecutor<EvmConfig, DB> {
pub const fn new(chain_spec: Arc<ChainSpec>, evm_config: EvmConfig, state: State<DB>) -> Self {
Self { executor: EthEvmExecutor { chain_spec, evm_config }, state }
}
#[inline]
fn chain_spec(&self) -> &ChainSpec {
&self.executor.chain_spec
}
#[allow(unused)]
fn state_mut(&mut self) -> &mut State<DB> {
&mut self.state
}
}
impl<EvmConfig, DB> EthBlockExecutor<EvmConfig, DB>
where
EvmConfig: ConfigureEvm<Header = Header>,
DB: Database<Error: Into<ProviderError> + Display>,
{
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.executor.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())
}
fn execute_without_verification(
&mut self,
block: &BlockWithSenders,
total_difficulty: U256,
) -> Result<EthExecuteOutput, BlockExecutionError> {
self.on_new_block(&block.header);
let env = self.evm_env_for_block(&block.header, total_difficulty);
let output = {
let evm = self.executor.evm_config.evm_with_env(&mut self.state, env);
self.executor.execute_state_transitions(block, evm)
}?;
self.post_execution(block, total_difficulty)?;
Ok(output)
}
pub(crate) fn on_new_block(&mut self, header: &Header) {
let state_clear_flag = self.chain_spec().is_spurious_dragon_active_at_block(header.number);
self.state.set_state_clear_flag(state_clear_flag);
}
pub fn post_execution(
&mut self,
block: &BlockWithSenders,
total_difficulty: U256,
) -> Result<(), BlockExecutionError> {
let mut balance_increments =
post_block_balance_increments(self.chain_spec(), block, total_difficulty);
if self.chain_spec().fork(EthereumHardfork::Dao).transitions_at_block(block.number) {
let drained_balance: u128 = self
.state
.drain_balances(DAO_HARDKFORK_ACCOUNTS)
.map_err(|_| BlockValidationError::IncrementBalanceFailed)?
.into_iter()
.sum();
*balance_increments.entry(DAO_HARDFORK_BENEFICIARY).or_default() += drained_balance;
}
self.state
.increment_balances(balance_increments)
.map_err(|_| BlockValidationError::IncrementBalanceFailed)?;
Ok(())
}
}
impl<EvmConfig, DB> Executor<DB> for EthBlockExecutor<EvmConfig, DB>
where
EvmConfig: ConfigureEvm<Header = Header>,
DB: Database<Error: Into<ProviderError> + Display>,
{
type Input<'a> = BlockExecutionInput<'a, BlockWithSenders>;
type Output = BlockExecutionOutput<Receipt>;
type Error = BlockExecutionError;
fn execute(mut self, input: Self::Input<'_>) -> Result<Self::Output, Self::Error> {
let BlockExecutionInput { block, total_difficulty } = input;
let EthExecuteOutput { receipts, requests, gas_used } =
self.execute_without_verification(block, total_difficulty)?;
self.state.merge_transitions(BundleRetention::Reverts);
Ok(BlockExecutionOutput { state: self.state.take_bundle(), receipts, requests, gas_used })
}
}
#[derive(Debug)]
pub struct BlockAccessListExecutor<EvmConfig, DB> {
executor: EthBlockExecutor<EvmConfig, DB>,
}
impl<EvmConfig, DB> Executor<DB> for BlockAccessListExecutor<EvmConfig, DB>
where
EvmConfig: ConfigureEvm<Header = Header>,
DB: Database<Error: Into<ProviderError> + Display>,
{
type Input<'a> = BlockExecutionInput<'a, BlockWithSenders>;
type Output = BlockExecutionOutput<Receipt>;
type Error = BlockExecutionError;
fn execute(mut self, input: Self::Input<'_>) -> Result<Self::Output, Self::Error> {
let BlockExecutionInput { block, total_difficulty } = input;
let EthExecuteOutput { receipts, requests, gas_used } =
self.executor.execute_without_verification(block, total_difficulty)?;
self.executor.state.merge_transitions(BundleRetention::Reverts);
let mut bundle_state = self.executor.state.take_bundle();
for (address, account) in self.executor.state.cache.accounts {
let account_info = account.account_info();
let account_storage = account.account.map(|a| a.storage).unwrap_or_default();
match bundle_state.state.entry(address) {
Entry::Vacant(entry) => {
let extracted_storage = account_storage
.into_iter()
.map(|(k, v)| {
(k, StorageSlot { previous_or_original_value: v, present_value: v })
})
.collect();
let bundle_account = BundleAccount {
info: account_info.clone(),
original_info: account_info,
storage: extracted_storage,
status: account.status,
};
entry.insert(bundle_account);
}
Entry::Occupied(mut entry) => {
let current_account = entry.get_mut();
for (k, v) in account_storage {
if let Entry::Vacant(storage_entry) = current_account.storage.entry(k) {
storage_entry.insert(StorageSlot {
previous_or_original_value: v,
present_value: v,
});
}
}
}
}
}
Ok(BlockExecutionOutput { state: bundle_state, receipts, requests, gas_used })
}
}
#[derive(Debug)]
pub struct EthBatchExecutor<EvmConfig, DB> {
executor: EthBlockExecutor<EvmConfig, DB>,
batch_record: BlockBatchRecord,
}
impl<EvmConfig, DB> EthBatchExecutor<EvmConfig, DB> {
#[allow(unused)]
fn state_mut(&mut self) -> &mut State<DB> {
self.executor.state_mut()
}
}
impl<EvmConfig, DB> BatchExecutor<DB> for EthBatchExecutor<EvmConfig, DB>
where
EvmConfig: ConfigureEvm<Header = Header>,
DB: Database<Error: Into<ProviderError> + Display>,
{
type Input<'a> = BlockExecutionInput<'a, BlockWithSenders>;
type Output = ExecutionOutcome;
type Error = BlockExecutionError;
fn execute_and_verify_one(&mut self, input: Self::Input<'_>) -> Result<(), Self::Error> {
let BlockExecutionInput { block, total_difficulty } = input;
if self.batch_record.first_block().is_none() {
self.batch_record.set_first_block(block.number);
}
let EthExecuteOutput { receipts, requests, gas_used: _ } =
self.executor.execute_without_verification(block, total_difficulty)?;
validate_block_post_execution(block, self.executor.chain_spec(), &receipts, &requests)?;
let retention = self.batch_record.bundle_retention(block.number);
self.executor.state.merge_transitions(retention);
self.batch_record.save_receipts(receipts)?;
self.batch_record.save_requests(requests);
Ok(())
}
fn finalize(mut self) -> Self::Output {
ExecutionOutcome::new(
self.executor.state.take_bundle(),
self.batch_record.take_receipts(),
self.batch_record.first_block().unwrap_or_default(),
self.batch_record.take_requests(),
)
}
fn set_tip(&mut self, tip: BlockNumber) {
self.batch_record.set_tip(tip);
}
fn set_prune_modes(&mut self, prune_modes: PruneModes) {
self.batch_record.set_prune_modes(prune_modes);
}
fn size_hint(&self) -> Option<usize> {
Some(self.executor.state.bundle_state.size_hint())
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloy_eips::{
eip2935::{HISTORY_STORAGE_ADDRESS, HISTORY_STORAGE_CODE},
eip4788::{BEACON_ROOTS_ADDRESS, BEACON_ROOTS_CODE, SYSTEM_ADDRESS},
eip7002::{WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS, WITHDRAWAL_REQUEST_PREDEPLOY_CODE},
};
use reth_chainspec::{ChainSpecBuilder, ForkCondition};
use reth_primitives::{
constants::{EMPTY_ROOT_HASH, ETH_TO_WEI},
keccak256, public_key_to_address, Account, Block, Transaction, TxKind, TxLegacy, B256,
};
use reth_revm::{
database::StateProviderDatabase, test_utils::StateProviderTest, TransitionState,
};
use reth_testing_utils::generators::{self, sign_tx_with_key_pair};
use revm_primitives::{b256, fixed_bytes, Bytes, BLOCKHASH_SERVE_WINDOW};
use secp256k1::{Keypair, Secp256k1};
use std::collections::HashMap;
fn create_state_provider_with_beacon_root_contract() -> StateProviderTest {
let mut db = StateProviderTest::default();
let beacon_root_contract_account = Account {
balance: U256::ZERO,
bytecode_hash: Some(keccak256(BEACON_ROOTS_CODE.clone())),
nonce: 1,
};
db.insert_account(
BEACON_ROOTS_ADDRESS,
beacon_root_contract_account,
Some(BEACON_ROOTS_CODE.clone()),
HashMap::new(),
);
db
}
fn create_state_provider_with_withdrawal_requests_contract() -> StateProviderTest {
let mut db = StateProviderTest::default();
let withdrawal_requests_contract_account = Account {
nonce: 1,
balance: U256::ZERO,
bytecode_hash: Some(keccak256(WITHDRAWAL_REQUEST_PREDEPLOY_CODE.clone())),
};
db.insert_account(
WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS,
withdrawal_requests_contract_account,
Some(WITHDRAWAL_REQUEST_PREDEPLOY_CODE.clone()),
HashMap::new(),
);
db
}
fn executor_provider(chain_spec: Arc<ChainSpec>) -> EthExecutorProvider<EthEvmConfig> {
EthExecutorProvider { evm_config: EthEvmConfig::new(chain_spec.clone()), chain_spec }
}
#[test]
fn eip_4788_non_genesis_call() {
let mut header =
Header { timestamp: 1, number: 1, excess_blob_gas: Some(0), ..Header::default() };
let db = create_state_provider_with_beacon_root_contract();
let chain_spec = Arc::new(
ChainSpecBuilder::from(&*MAINNET)
.shanghai_activated()
.with_fork(EthereumHardfork::Cancun, ForkCondition::Timestamp(1))
.build(),
);
let provider = executor_provider(chain_spec);
let err = provider
.executor(StateProviderDatabase::new(&db))
.execute(
(
&BlockWithSenders {
block: Block {
header: header.clone(),
body: vec![],
ommers: vec![],
withdrawals: None,
requests: None,
},
senders: vec![],
},
U256::ZERO,
)
.into(),
)
.expect_err(
"Executing cancun block without parent beacon block root field should fail",
);
assert_eq!(
err.as_validation().unwrap().clone(),
BlockValidationError::MissingParentBeaconBlockRoot
);
header.parent_beacon_block_root = Some(B256::with_last_byte(0x69));
let mut executor = provider.executor(StateProviderDatabase::new(&db));
executor
.execute_without_verification(
&BlockWithSenders {
block: Block {
header: header.clone(),
body: vec![],
ommers: vec![],
withdrawals: None,
requests: None,
},
senders: vec![],
},
U256::ZERO,
)
.unwrap();
let history_buffer_length = 8191u64;
let timestamp_index = header.timestamp % history_buffer_length;
let parent_beacon_block_root_index =
timestamp_index % history_buffer_length + history_buffer_length;
let timestamp_storage =
executor.state.storage(BEACON_ROOTS_ADDRESS, U256::from(timestamp_index)).unwrap();
assert_eq!(timestamp_storage, U256::from(header.timestamp));
let parent_beacon_block_root_storage = executor
.state
.storage(BEACON_ROOTS_ADDRESS, U256::from(parent_beacon_block_root_index))
.expect("storage value should exist");
assert_eq!(parent_beacon_block_root_storage, U256::from(0x69));
}
#[test]
fn eip_4788_no_code_cancun() {
let header = Header {
timestamp: 1,
number: 1,
parent_beacon_block_root: Some(B256::with_last_byte(0x69)),
excess_blob_gas: Some(0),
..Header::default()
};
let db = StateProviderTest::default();
let chain_spec = Arc::new(
ChainSpecBuilder::from(&*MAINNET)
.shanghai_activated()
.with_fork(EthereumHardfork::Cancun, ForkCondition::Timestamp(1))
.build(),
);
let provider = executor_provider(chain_spec);
provider
.batch_executor(StateProviderDatabase::new(&db))
.execute_and_verify_one(
(
&BlockWithSenders {
block: Block {
header,
body: vec![],
ommers: vec![],
withdrawals: None,
requests: None,
},
senders: vec![],
},
U256::ZERO,
)
.into(),
)
.expect(
"Executing a block with no transactions while cancun is active should not fail",
);
}
#[test]
fn eip_4788_empty_account_call() {
let mut db = create_state_provider_with_beacon_root_contract();
db.insert_account(SYSTEM_ADDRESS, Account::default(), None, HashMap::new());
let chain_spec = Arc::new(
ChainSpecBuilder::from(&*MAINNET)
.shanghai_activated()
.with_fork(EthereumHardfork::Cancun, ForkCondition::Timestamp(1))
.build(),
);
let provider = executor_provider(chain_spec);
let header = Header {
timestamp: 1,
number: 1,
parent_beacon_block_root: Some(B256::with_last_byte(0x69)),
excess_blob_gas: Some(0),
..Header::default()
};
let mut executor = provider.batch_executor(StateProviderDatabase::new(&db));
executor
.execute_and_verify_one(
(
&BlockWithSenders {
block: Block {
header,
body: vec![],
ommers: vec![],
withdrawals: None,
requests: None,
},
senders: vec![],
},
U256::ZERO,
)
.into(),
)
.expect(
"Executing a block with no transactions while cancun is active should not fail",
);
let nonce = executor.state_mut().basic(SYSTEM_ADDRESS).unwrap().unwrap().nonce;
assert_eq!(nonce, 0);
}
#[test]
fn eip_4788_genesis_call() {
let db = create_state_provider_with_beacon_root_contract();
let chain_spec = Arc::new(
ChainSpecBuilder::from(&*MAINNET)
.shanghai_activated()
.with_fork(EthereumHardfork::Cancun, ForkCondition::Timestamp(0))
.build(),
);
let mut header = chain_spec.genesis_header().clone();
let provider = executor_provider(chain_spec);
let mut executor = provider.batch_executor(StateProviderDatabase::new(&db));
header.parent_beacon_block_root = Some(B256::with_last_byte(0x69));
let _err = executor
.execute_and_verify_one(
(
&BlockWithSenders {
block: Block {
header: header.clone(),
body: vec![],
ommers: vec![],
withdrawals: None,
requests: None,
},
senders: vec![],
},
U256::ZERO,
)
.into(),
)
.expect_err(
"Executing genesis cancun block with non-zero parent beacon block root field
should fail",
);
header.parent_beacon_block_root = Some(B256::ZERO);
executor
.execute_and_verify_one(
(
&BlockWithSenders {
block: Block {
header,
body: vec![],
ommers: vec![],
withdrawals: None,
requests: None,
},
senders: vec![],
},
U256::ZERO,
)
.into(),
)
.unwrap();
let transition_state = executor
.state_mut()
.transition_state
.take()
.expect("the evm should be initialized with bundle updates");
assert_eq!(transition_state, TransitionState::default());
}
#[test]
fn eip_4788_high_base_fee() {
let header = Header {
timestamp: 1,
number: 1,
parent_beacon_block_root: Some(B256::with_last_byte(0x69)),
base_fee_per_gas: Some(u64::MAX),
excess_blob_gas: Some(0),
..Header::default()
};
let db = create_state_provider_with_beacon_root_contract();
let chain_spec = Arc::new(
ChainSpecBuilder::from(&*MAINNET)
.shanghai_activated()
.with_fork(EthereumHardfork::Cancun, ForkCondition::Timestamp(1))
.build(),
);
let provider = executor_provider(chain_spec);
let mut executor = provider.batch_executor(StateProviderDatabase::new(&db));
executor
.execute_and_verify_one(
(
&BlockWithSenders {
block: Block {
header: header.clone(),
body: vec![],
ommers: vec![],
withdrawals: None,
requests: None,
},
senders: vec![],
},
U256::ZERO,
)
.into(),
)
.unwrap();
let history_buffer_length = 8191u64;
let timestamp_index = header.timestamp % history_buffer_length;
let parent_beacon_block_root_index =
timestamp_index % history_buffer_length + history_buffer_length;
let timestamp_storage = executor
.state_mut()
.storage(BEACON_ROOTS_ADDRESS, U256::from(timestamp_index))
.unwrap();
assert_eq!(timestamp_storage, U256::from(header.timestamp));
let parent_beacon_block_root_storage = executor
.state_mut()
.storage(BEACON_ROOTS_ADDRESS, U256::from(parent_beacon_block_root_index))
.unwrap();
assert_eq!(parent_beacon_block_root_storage, U256::from(0x69));
}
fn create_state_provider_with_block_hashes(latest_block: u64) -> StateProviderTest {
let mut db = StateProviderTest::default();
for block_number in 0..=latest_block {
db.insert_block_hash(block_number, keccak256(block_number.to_string()));
}
let blockhashes_contract_account = Account {
balance: U256::ZERO,
bytecode_hash: Some(keccak256(HISTORY_STORAGE_CODE.clone())),
nonce: 1,
};
db.insert_account(
HISTORY_STORAGE_ADDRESS,
blockhashes_contract_account,
Some(HISTORY_STORAGE_CODE.clone()),
HashMap::new(),
);
db
}
#[test]
fn eip_2935_pre_fork() {
let db = create_state_provider_with_block_hashes(1);
let chain_spec = Arc::new(
ChainSpecBuilder::from(&*MAINNET)
.shanghai_activated()
.with_fork(EthereumHardfork::Prague, ForkCondition::Never)
.build(),
);
let provider = executor_provider(chain_spec);
let mut executor = provider.batch_executor(StateProviderDatabase::new(&db));
let header = Header { timestamp: 1, number: 1, ..Header::default() };
executor
.execute_and_verify_one(
(
&BlockWithSenders {
block: Block {
header,
body: vec![],
ommers: vec![],
withdrawals: None,
requests: None,
},
senders: vec![],
},
U256::ZERO,
)
.into(),
)
.expect(
"Executing a block with no transactions while Prague is active should not fail",
);
executor.state_mut().basic(HISTORY_STORAGE_ADDRESS).unwrap();
assert!(executor
.state_mut()
.storage(HISTORY_STORAGE_ADDRESS, U256::ZERO)
.unwrap()
.is_zero());
}
#[test]
fn eip_2935_fork_activation_genesis() {
let db = create_state_provider_with_block_hashes(0);
let chain_spec = Arc::new(
ChainSpecBuilder::from(&*MAINNET)
.shanghai_activated()
.with_fork(EthereumHardfork::Prague, ForkCondition::Timestamp(0))
.build(),
);
let header = chain_spec.genesis_header().clone();
let provider = executor_provider(chain_spec);
let mut executor = provider.batch_executor(StateProviderDatabase::new(&db));
executor
.execute_and_verify_one(
(
&BlockWithSenders {
block: Block {
header,
body: vec![],
ommers: vec![],
withdrawals: None,
requests: None,
},
senders: vec![],
},
U256::ZERO,
)
.into(),
)
.expect(
"Executing a block with no transactions while Prague is active should not fail",
);
executor.state_mut().basic(HISTORY_STORAGE_ADDRESS).unwrap();
assert!(executor
.state_mut()
.storage(HISTORY_STORAGE_ADDRESS, U256::ZERO)
.unwrap()
.is_zero());
}
#[test]
fn eip_2935_fork_activation_within_window_bounds() {
let fork_activation_block = (BLOCKHASH_SERVE_WINDOW - 10) as u64;
let db = create_state_provider_with_block_hashes(fork_activation_block);
let chain_spec = Arc::new(
ChainSpecBuilder::from(&*MAINNET)
.shanghai_activated()
.with_fork(EthereumHardfork::Prague, ForkCondition::Timestamp(1))
.build(),
);
let header = Header {
parent_hash: B256::random(),
timestamp: 1,
number: fork_activation_block,
requests_root: Some(EMPTY_ROOT_HASH),
..Header::default()
};
let provider = executor_provider(chain_spec);
let mut executor = provider.batch_executor(StateProviderDatabase::new(&db));
executor
.execute_and_verify_one(
(
&BlockWithSenders {
block: Block {
header,
body: vec![],
ommers: vec![],
withdrawals: None,
requests: None,
},
senders: vec![],
},
U256::ZERO,
)
.into(),
)
.expect(
"Executing a block with no transactions while Prague is active should not fail",
);
assert!(executor.state_mut().basic(HISTORY_STORAGE_ADDRESS).unwrap().is_some());
assert_ne!(
executor
.state_mut()
.storage(HISTORY_STORAGE_ADDRESS, U256::from(fork_activation_block - 1))
.unwrap(),
U256::ZERO
);
assert!(executor
.state_mut()
.storage(HISTORY_STORAGE_ADDRESS, U256::from(fork_activation_block))
.unwrap()
.is_zero());
}
#[test]
fn eip_2935_fork_activation_outside_window_bounds() {
let fork_activation_block = (BLOCKHASH_SERVE_WINDOW + 256) as u64;
let db = create_state_provider_with_block_hashes(fork_activation_block);
let chain_spec = Arc::new(
ChainSpecBuilder::from(&*MAINNET)
.shanghai_activated()
.with_fork(EthereumHardfork::Prague, ForkCondition::Timestamp(1))
.build(),
);
let provider = executor_provider(chain_spec);
let mut executor = provider.batch_executor(StateProviderDatabase::new(&db));
let header = Header {
parent_hash: B256::random(),
timestamp: 1,
number: fork_activation_block,
requests_root: Some(EMPTY_ROOT_HASH),
..Header::default()
};
executor
.execute_and_verify_one(
(
&BlockWithSenders {
block: Block {
header,
body: vec![],
ommers: vec![],
withdrawals: None,
requests: None,
},
senders: vec![],
},
U256::ZERO,
)
.into(),
)
.expect(
"Executing a block with no transactions while Prague is active should not fail",
);
assert!(executor.state_mut().basic(HISTORY_STORAGE_ADDRESS).unwrap().is_some());
assert_ne!(
executor
.state_mut()
.storage(
HISTORY_STORAGE_ADDRESS,
U256::from(fork_activation_block % BLOCKHASH_SERVE_WINDOW as u64 - 1)
)
.unwrap(),
U256::ZERO
);
}
#[test]
fn eip_2935_state_transition_inside_fork() {
let db = create_state_provider_with_block_hashes(2);
let chain_spec = Arc::new(
ChainSpecBuilder::from(&*MAINNET)
.shanghai_activated()
.with_fork(EthereumHardfork::Prague, ForkCondition::Timestamp(0))
.build(),
);
let mut header = chain_spec.genesis_header().clone();
header.requests_root = Some(EMPTY_ROOT_HASH);
let header_hash = header.hash_slow();
let provider = executor_provider(chain_spec);
let mut executor = provider.batch_executor(StateProviderDatabase::new(&db));
executor
.execute_and_verify_one(
(
&BlockWithSenders {
block: Block {
header,
body: vec![],
ommers: vec![],
withdrawals: None,
requests: None,
},
senders: vec![],
},
U256::ZERO,
)
.into(),
)
.expect(
"Executing a block with no transactions while Prague is active should not fail",
);
executor.state_mut().basic(HISTORY_STORAGE_ADDRESS).unwrap();
assert!(executor
.state_mut()
.storage(HISTORY_STORAGE_ADDRESS, U256::ZERO)
.unwrap()
.is_zero());
let header = Header {
parent_hash: header_hash,
timestamp: 1,
number: 1,
requests_root: Some(EMPTY_ROOT_HASH),
..Header::default()
};
let header_hash = header.hash_slow();
executor
.execute_and_verify_one(
(
&BlockWithSenders {
block: Block {
header,
body: vec![],
ommers: vec![],
withdrawals: None,
requests: None,
},
senders: vec![],
},
U256::ZERO,
)
.into(),
)
.expect(
"Executing a block with no transactions while Prague is active should not fail",
);
assert!(executor.state_mut().basic(HISTORY_STORAGE_ADDRESS).unwrap().is_some());
assert_ne!(
executor.state_mut().storage(HISTORY_STORAGE_ADDRESS, U256::ZERO).unwrap(),
U256::ZERO
);
assert!(executor
.state_mut()
.storage(HISTORY_STORAGE_ADDRESS, U256::from(1))
.unwrap()
.is_zero());
let header = Header {
parent_hash: header_hash,
timestamp: 1,
number: 2,
requests_root: Some(EMPTY_ROOT_HASH),
..Header::default()
};
executor
.execute_and_verify_one(
(
&BlockWithSenders {
block: Block {
header,
body: vec![],
ommers: vec![],
withdrawals: None,
requests: None,
},
senders: vec![],
},
U256::ZERO,
)
.into(),
)
.expect(
"Executing a block with no transactions while Prague is active should not fail",
);
assert!(executor.state_mut().basic(HISTORY_STORAGE_ADDRESS).unwrap().is_some());
assert_ne!(
executor.state_mut().storage(HISTORY_STORAGE_ADDRESS, U256::ZERO).unwrap(),
U256::ZERO
);
assert_ne!(
executor.state_mut().storage(HISTORY_STORAGE_ADDRESS, U256::from(1)).unwrap(),
U256::ZERO
);
assert!(executor
.state_mut()
.storage(HISTORY_STORAGE_ADDRESS, U256::from(2))
.unwrap()
.is_zero());
}
#[test]
fn eip_7002() {
let chain_spec = Arc::new(
ChainSpecBuilder::from(&*MAINNET)
.shanghai_activated()
.with_fork(EthereumHardfork::Prague, ForkCondition::Timestamp(0))
.build(),
);
let mut db = create_state_provider_with_withdrawal_requests_contract();
let secp = Secp256k1::new();
let sender_key_pair = Keypair::new(&secp, &mut generators::rng());
let sender_address = public_key_to_address(sender_key_pair.public_key());
db.insert_account(
sender_address,
Account { nonce: 1, balance: U256::from(ETH_TO_WEI), bytecode_hash: None },
None,
HashMap::new(),
);
let validator_public_key = fixed_bytes!("111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111");
let withdrawal_amount = fixed_bytes!("2222222222222222");
let input: Bytes = [&validator_public_key[..], &withdrawal_amount[..]].concat().into();
assert_eq!(input.len(), 56);
let mut header = chain_spec.genesis_header().clone();
header.gas_limit = 1_500_000;
header.gas_used = 134_807;
header.receipts_root =
b256!("b31a3e47b902e9211c4d349af4e4c5604ce388471e79ca008907ae4616bb0ed3");
let tx = sign_tx_with_key_pair(
sender_key_pair,
Transaction::Legacy(TxLegacy {
chain_id: Some(chain_spec.chain.id()),
nonce: 1,
gas_price: header.base_fee_per_gas.unwrap().into(),
gas_limit: 134_807,
to: TxKind::Call(WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS),
value: U256::from(1),
input,
}),
);
let provider = executor_provider(chain_spec);
let executor = provider.executor(StateProviderDatabase::new(&db));
let BlockExecutionOutput { receipts, requests, .. } = executor
.execute(
(
&Block {
header,
body: vec![tx],
ommers: vec![],
withdrawals: None,
requests: None,
}
.with_recovered_senders()
.unwrap(),
U256::ZERO,
)
.into(),
)
.unwrap();
let receipt = receipts.first().unwrap();
assert!(receipt.success);
let request = requests.first().unwrap();
let withdrawal_request = request.as_withdrawal_request().unwrap();
assert_eq!(withdrawal_request.source_address, sender_address);
assert_eq!(withdrawal_request.validator_pubkey, validator_public_key);
assert_eq!(withdrawal_request.amount, u64::from_be_bytes(withdrawal_amount.into()));
}
#[test]
fn block_gas_limit_error() {
let chain_spec = Arc::new(
ChainSpecBuilder::from(&*MAINNET)
.shanghai_activated()
.with_fork(EthereumHardfork::Prague, ForkCondition::Timestamp(0))
.build(),
);
let mut db = create_state_provider_with_withdrawal_requests_contract();
let secp = Secp256k1::new();
let sender_key_pair = Keypair::new(&secp, &mut generators::rng());
let sender_address = public_key_to_address(sender_key_pair.public_key());
db.insert_account(
sender_address,
Account { nonce: 1, balance: U256::from(ETH_TO_WEI), bytecode_hash: None },
None,
HashMap::new(),
);
let validator_public_key = fixed_bytes!("111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111");
let withdrawal_amount = fixed_bytes!("2222222222222222");
let input: Bytes = [&validator_public_key[..], &withdrawal_amount[..]].concat().into();
assert_eq!(input.len(), 56);
let mut header = chain_spec.genesis_header().clone();
header.gas_limit = 1_500_000;
header.gas_used = 134_807;
header.receipts_root =
b256!("b31a3e47b902e9211c4d349af4e4c5604ce388471e79ca008907ae4616bb0ed3");
let tx = sign_tx_with_key_pair(
sender_key_pair,
Transaction::Legacy(TxLegacy {
chain_id: Some(chain_spec.chain.id()),
nonce: 1,
gas_price: header.base_fee_per_gas.unwrap().into(),
gas_limit: 2_500_000, to: TxKind::Call(WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS),
value: U256::from(1),
input,
}),
);
let executor = executor_provider(chain_spec).executor(StateProviderDatabase::new(&db));
let exec_result = executor.execute(
(
&Block {
header,
body: vec![tx],
ommers: vec![],
withdrawals: None,
requests: None,
}
.with_recovered_senders()
.unwrap(),
U256::ZERO,
)
.into(),
);
match exec_result {
Ok(_) => panic!("Expected block gas limit error"),
Err(err) => assert_eq!(
*err.as_validation().unwrap(),
BlockValidationError::TransactionGasLimitMoreThanAvailableBlockGas {
transaction_gas_limit: 2_500_000,
block_available_gas: 1_500_000,
}
),
}
}
}