use crate::{ database::SubState, env::{fill_cfg_and_block_env, fill_tx_env}, eth_dao_fork::{DAO_HARDFORK_BENEFICIARY, DAO_HARDKFORK_ACCOUNTS}, into_reth_log, stack::{InspectorStack, InspectorStackConfig}, to_reth_acc, }; use reth_consensus_common::calc; use reth_interfaces::executor::{BlockExecutionError, BlockValidationError}; use reth_primitives::{ Account, Address, Block, BlockNumber, Bloom, Bytecode, ChainSpec, Hardfork, Header, Receipt, ReceiptWithBloom, TransactionSigned, Withdrawal, H256, U256, }; use reth_provider::{BlockExecutor, PostState, StateProvider}; use revm::{ db::{AccountState, CacheDB, DatabaseRef}, primitives::{ hash_map::{self, Entry}, Account as RevmAccount, AccountInfo, ResultAndState, }, EVM, }; use std::{ collections::{BTreeMap, HashMap}, sync::Arc, }; /// Main block executor pub struct Executor where DB: StateProvider, { /// The configured chain-spec pub chain_spec: Arc, evm: EVM>, stack: InspectorStack, } impl From> for Executor where DB: StateProvider, { /// Instantiates a new executor from the chainspec. Must call /// `with_db` to set the database before executing. fn from(chain_spec: Arc) -> Self { let evm = EVM::new(); Executor { chain_spec, evm, stack: InspectorStack::new(InspectorStackConfig::default()) } } } impl Executor where DB: StateProvider, { /// Creates a new executor from the given chain spec and database. pub fn new(chain_spec: Arc, db: SubState) -> Self { let mut evm = EVM::new(); evm.database(db); Executor { chain_spec, evm, stack: InspectorStack::new(InspectorStackConfig::default()) } } /// Configures the executor with the given inspectors. pub fn with_stack(mut self, stack: InspectorStack) -> Self { self.stack = stack; self } /// Gives a reference to the database pub fn db(&mut self) -> &mut SubState { self.evm.db().expect("db to not be moved") } fn recover_senders( &self, body: &[TransactionSigned], senders: Option>, ) -> Result, BlockExecutionError> { if let Some(senders) = senders { if body.len() == senders.len() { Ok(senders) } else { Err(BlockValidationError::SenderRecoveryError.into()) } } else { body.iter() .map(|tx| { tx.recover_signer().ok_or(BlockValidationError::SenderRecoveryError.into()) }) .collect() } } /// Initializes the config and block env. fn init_env(&mut self, header: &Header, total_difficulty: U256) { fill_cfg_and_block_env( &mut self.evm.env.cfg, &mut self.evm.env.block, &self.chain_spec, header, total_difficulty, ); } /// Commit change to the run-time database, and update the given [PostState] with the changes /// made in the transaction, which can be persisted to the database. fn commit_changes( &mut self, block_number: BlockNumber, changes: hash_map::HashMap, has_state_clear_eip: bool, post_state: &mut PostState, ) { let db = self.db(); commit_state_changes(db, post_state, block_number, changes, has_state_clear_eip); } /// Collect all balance changes at the end of the block. /// /// Balance changes might include the block reward, uncle rewards, withdrawals, or irregular /// state changes (DAO fork). fn post_block_balance_increments(&self, block: &Block, td: U256) -> HashMap { post_block_balance_increments( &self.chain_spec, block.number, block.difficulty, block.beneficiary, block.timestamp, td, &block.ommers, block.withdrawals.as_deref(), ) } /// Irregular state change at Ethereum DAO hardfork fn apply_dao_fork_changes( &mut self, block_number: BlockNumber, post_state: &mut PostState, ) -> Result<(), BlockExecutionError> { let db = self.db(); let mut drained_balance = U256::ZERO; // drain all accounts ether for address in DAO_HARDKFORK_ACCOUNTS { let db_account = db.load_account(address).map_err(|_| BlockExecutionError::ProviderError)?; let old = to_reth_acc(&db_account.info); // drain balance drained_balance += core::mem::take(&mut db_account.info.balance); let new = to_reth_acc(&db_account.info); // assume it is changeset as it is irregular state change post_state.change_account(block_number, address, old, new); } // add drained ether to beneficiary. let beneficiary = DAO_HARDFORK_BENEFICIARY; self.increment_account_balance(block_number, beneficiary, drained_balance, post_state)?; Ok(()) } /// Increment the balance for the given account in the [PostState]. fn increment_account_balance( &mut self, block_number: BlockNumber, address: Address, increment: U256, post_state: &mut PostState, ) -> Result<(), BlockExecutionError> { increment_account_balance(self.db(), post_state, block_number, address, increment) .map_err(|_| BlockExecutionError::ProviderError) } /// Runs a single transaction in the configured environment and proceeds /// to return the result and state diff (without applying it). /// /// Assumes the rest of the block environment has been filled via `init_block_env`. pub fn transact( &mut self, transaction: &TransactionSigned, sender: Address, ) -> Result { // Fill revm structure. fill_tx_env(&mut self.evm.env.tx, transaction, sender); let hash = transaction.hash(); let out = if self.stack.should_inspect(&self.evm.env, hash) { // execution with inspector. let output = self.evm.inspect(&mut self.stack); tracing::trace!( target: "evm", ?hash, ?output, ?transaction, env = ?self.evm.env, "Executed transaction" ); output } else { // main execution. self.evm.transact() }; out.map_err(|e| BlockValidationError::EVM { hash, message: format!("{e:?}") }.into()) } /// Runs the provided transactions and commits their state to the run-time database. /// /// The returned [PostState] can be used to persist the changes to disk, and contains the /// changes made by each transaction. /// /// The changes in [PostState] have a transition ID associated with them: there is one /// transition ID for each transaction (with the first executed tx having transition ID 0, and /// so on). /// /// The second returned value represents the total gas used by this block of transactions. pub fn execute_transactions( &mut self, block: &Block, total_difficulty: U256, senders: Option>, ) -> Result<(PostState, u64), BlockExecutionError> { // perf: do not execute empty blocks if block.body.is_empty() { return Ok((PostState::default(), 0)) } let senders = self.recover_senders(&block.body, senders)?; self.init_env(&block.header, total_difficulty); let mut cumulative_gas_used = 0; let mut post_state = PostState::with_tx_capacity(block.number, block.body.len()); for (transaction, sender) in block.body.iter().zip(senders.into_iter()) { // The sum of the transaction’s gas limit, Tg, and the gas utilised in this block prior, // must be no greater than the block’s gasLimit. 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()) } // Execute transaction. let ResultAndState { result, state } = self.transact(transaction, sender)?; // commit changes self.commit_changes( block.number, state, self.chain_spec.fork(Hardfork::SpuriousDragon).active_at_block(block.number), &mut post_state, ); // append gas used cumulative_gas_used += result.gas_used(); // Push transaction changeset and calculate header bloom filter for receipt. post_state.add_receipt( block.number, Receipt { tx_type: transaction.tx_type(), // Success flag was added in `EIP-658: Embedding transaction status code in // receipts`. success: result.is_success(), cumulative_gas_used, // convert to reth log logs: result.into_logs().into_iter().map(into_reth_log).collect(), }, ); } Ok((post_state, cumulative_gas_used)) } /// Applies the post-block changes, assuming the poststate is generated after executing /// tranactions pub fn apply_post_block_changes( &mut self, block: &Block, total_difficulty: U256, mut post_state: PostState, ) -> Result { // Add block rewards let balance_increments = self.post_block_balance_increments(block, total_difficulty); for (address, increment) in balance_increments.into_iter() { self.increment_account_balance(block.number, address, increment, &mut post_state)?; } // Perform DAO irregular state change if self.chain_spec.fork(Hardfork::Dao).transitions_at_block(block.number) { self.apply_dao_fork_changes(block.number, &mut post_state)?; } Ok(post_state) } } impl BlockExecutor for Executor where DB: StateProvider, { fn execute( &mut self, block: &Block, total_difficulty: U256, senders: Option>, ) -> Result { let (post_state, cumulative_gas_used) = self.execute_transactions(block, total_difficulty, senders)?; // Check if gas used matches the value set in header. if block.gas_used != cumulative_gas_used { return Err(BlockValidationError::BlockGasUsed { got: cumulative_gas_used, expected: block.gas_used, } .into()) } self.apply_post_block_changes(block, total_difficulty, post_state) } fn execute_and_verify_receipt( &mut self, block: &Block, total_difficulty: U256, senders: Option>, ) -> Result { let post_state = self.execute(block, total_difficulty, senders)?; // TODO Before Byzantium, receipts contained state root that would mean that expensive // operation as hashing that is needed for state root got calculated in every // transaction This was replaced with is_success flag. // See more about EIP here: https://eips.ethereum.org/EIPS/eip-658 if self.chain_spec.fork(Hardfork::Byzantium).active_at_block(block.header.number) { verify_receipt( block.header.receipts_root, block.header.logs_bloom, post_state.receipts(block.number).iter(), )?; } Ok(post_state) } } /// Increment the balance for the given account in the [PostState]. /// /// Returns an error if the database encountered an error while loading the account. pub fn increment_account_balance( db: &mut CacheDB, post_state: &mut PostState, block_number: BlockNumber, address: Address, increment: U256, ) -> Result<(), ::Error> where DB: DatabaseRef, { let beneficiary = db.load_account(address)?; let old = to_reth_acc(&beneficiary.info); // Increment beneficiary balance by mutating db entry in place. beneficiary.info.balance += increment; let new = to_reth_acc(&beneficiary.info); match beneficiary.account_state { AccountState::NotExisting => { // if account was not existing that means that storage is not // present. beneficiary.account_state = AccountState::StorageCleared; // if account was not present append `Created` changeset post_state.create_account( block_number, address, Account { nonce: 0, balance: new.balance, bytecode_hash: None }, ) } AccountState::StorageCleared | AccountState::Touched | AccountState::None => { // If account is None that means that EVM didn't touch it. // we are changing the state to Touched as account can have // storage in db. if beneficiary.account_state == AccountState::None { beneficiary.account_state = AccountState::Touched; } // if account was present, append changed changeset. post_state.change_account(block_number, address, old, new); } } Ok(()) } /// Commit change to the _run-time_ database [CacheDB], and update the given [PostState] with the /// changes made in the transaction, which can be persisted to the database. /// /// Note: This does _not_ commit to the underlying database [DatabaseRef], but only to the /// [CacheDB]. pub fn commit_state_changes( db: &mut CacheDB, post_state: &mut PostState, block_number: BlockNumber, changes: hash_map::HashMap, has_state_clear_eip: bool, ) where DB: DatabaseRef, { // iterate over all changed accounts for (address, account) in changes { if account.is_destroyed { // get old account that we are destroying. let db_account = match db.accounts.entry(address) { Entry::Occupied(entry) => entry.into_mut(), Entry::Vacant(_entry) => { panic!("Left panic to critically jumpout if happens, as every account should be hot loaded."); } }; let account_exists = !matches!(db_account.account_state, AccountState::NotExisting); if account_exists { // Insert into `change` a old account and None for new account // and mark storage to be wiped post_state.destroy_account(block_number, address, to_reth_acc(&db_account.info)); } // clear cached DB and mark account as not existing db_account.storage.clear(); db_account.account_state = AccountState::NotExisting; db_account.info = AccountInfo::default(); continue } else { // check if account code is new or old. // does it exist inside cached contracts if it doesn't it is new bytecode that // we are inserting inside `change` if let Some(ref code) = account.info.code { if !code.is_empty() && !db.contracts.contains_key(&account.info.code_hash) { db.contracts.insert(account.info.code_hash, code.clone()); post_state.add_bytecode(account.info.code_hash, Bytecode(code.clone())); } } // get old account that is going to be overwritten or none if it does not exist // and get new account that was just inserted. new account mut ref is used for // inserting storage let cached_account = match db.accounts.entry(address) { Entry::Vacant(entry) => { let entry = entry.insert(Default::default()); entry.info = account.info.clone(); entry.account_state = AccountState::NotExisting; // we will promote account state down the road let new_account = to_reth_acc(&entry.info); #[allow(clippy::nonminimal_bool)] // If account was touched before state clear EIP, create it. if !has_state_clear_eip || // If account was touched after state clear EIP, create it only if it is not empty. (has_state_clear_eip && !new_account.is_empty()) { post_state.create_account(block_number, address, new_account); } entry } Entry::Occupied(entry) => { let entry = entry.into_mut(); let old_account = to_reth_acc(&entry.info); let new_account = to_reth_acc(&account.info); let account_non_existent = matches!(entry.account_state, AccountState::NotExisting); // Before state clear EIP, create account if it doesn't exist if (!has_state_clear_eip && account_non_existent) // After state clear EIP, create account only if it is not empty || (has_state_clear_eip && entry.info.is_empty() && !new_account.is_empty()) { post_state.create_account(block_number, address, new_account); } else if old_account != new_account { post_state.change_account( block_number, address, to_reth_acc(&entry.info), new_account, ); } else if has_state_clear_eip && new_account.is_empty() && !account_non_existent { // The account was touched, but it is empty, so it should be deleted. // This also deletes empty accounts which were created before state clear // EIP. post_state.destroy_account(block_number, address, new_account); } entry.info = account.info.clone(); entry } }; cached_account.account_state = if account.storage_cleared { cached_account.storage.clear(); AccountState::StorageCleared } else if cached_account.account_state.is_storage_cleared() { // the account already exists and its storage was cleared, preserve its previous // state AccountState::StorageCleared } else if has_state_clear_eip && matches!(cached_account.account_state, AccountState::NotExisting) && cached_account.info.is_empty() { AccountState::NotExisting } else { AccountState::Touched }; // Insert storage. let mut storage_changeset = BTreeMap::new(); // insert storage into new db account. cached_account.storage.extend(account.storage.into_iter().map(|(key, value)| { if value.is_changed() { storage_changeset.insert(key, (value.original_value(), value.present_value())); } (key, value.present_value()) })); // Insert into change. if !storage_changeset.is_empty() { post_state.change_storage(block_number, address, storage_changeset); } } } } /// Verify receipts pub fn verify_receipt<'a>( expected_receipts_root: H256, expected_logs_bloom: Bloom, receipts: impl Iterator + Clone, ) -> Result<(), BlockExecutionError> { // Check receipts root. let receipts_with_bloom = receipts.map(|r| r.clone().into()).collect::>(); let receipts_root = reth_primitives::proofs::calculate_receipt_root(&receipts_with_bloom); if receipts_root != expected_receipts_root { return Err(BlockValidationError::ReceiptRootDiff { got: receipts_root, expected: expected_receipts_root, } .into()) } // Create header log bloom. let logs_bloom = receipts_with_bloom.iter().fold(Bloom::zero(), |bloom, r| bloom | r.bloom); if logs_bloom != expected_logs_bloom { return Err(BlockValidationError::BloomLogDiff { expected: Box::new(expected_logs_bloom), got: Box::new(logs_bloom), } .into()) } Ok(()) } /// Collect all balance changes at the end of the block. /// /// Balance changes might include the block reward, uncle rewards, withdrawals, or irregular /// state changes (DAO fork). #[allow(clippy::too_many_arguments)] #[inline] pub fn post_block_balance_increments( chain_spec: &ChainSpec, block_number: u64, block_difficulty: U256, beneficiary: Address, block_timestamp: u64, total_difficulty: U256, ommers: &[Header], withdrawals: Option<&[Withdrawal]>, ) -> HashMap { let mut balance_increments = HashMap::new(); // Add block rewards if they are enabled. if let Some(base_block_reward) = calc::base_block_reward(chain_spec, block_number, block_difficulty, total_difficulty) { // Ommer rewards for ommer in ommers { *balance_increments.entry(ommer.beneficiary).or_default() += calc::ommer_reward(base_block_reward, block_number, ommer.number); } // Full block reward *balance_increments.entry(beneficiary).or_default() += calc::block_reward(base_block_reward, ommers.len()); } // process withdrawals insert_post_block_withdrawals_balance_increments( chain_spec, block_timestamp, withdrawals, &mut balance_increments, ); balance_increments } /// Returns a map of addresses to their balance increments if shanghai is active at the given /// timestamp. #[inline] pub fn post_block_withdrawals_balance_increments( chain_spec: &ChainSpec, block_timestamp: u64, withdrawals: &[Withdrawal], ) -> HashMap { let mut balance_increments = HashMap::with_capacity(withdrawals.len()); insert_post_block_withdrawals_balance_increments( chain_spec, block_timestamp, Some(withdrawals), &mut balance_increments, ); balance_increments } /// Applies all withdrawal balance increments if shanghai is active at the given timestamp to the /// given `balance_increments` map. #[inline] pub fn insert_post_block_withdrawals_balance_increments( chain_spec: &ChainSpec, block_timestamp: u64, withdrawals: Option<&[Withdrawal]>, balance_increments: &mut HashMap, ) { // Process withdrawals if chain_spec.fork(Hardfork::Shanghai).active_at_timestamp(block_timestamp) { if let Some(withdrawals) = withdrawals { for withdrawal in withdrawals { *balance_increments.entry(withdrawal.address).or_default() += withdrawal.amount_wei(); } } } } #[cfg(test)] mod tests { use super::*; use crate::database::State; use once_cell::sync::Lazy; use reth_consensus_common::calc; use reth_primitives::{ constants::ETH_TO_WEI, hex_literal::hex, keccak256, Account, Address, BlockNumber, Bytecode, Bytes, ChainSpecBuilder, ForkCondition, StorageKey, H256, MAINNET, U256, }; use reth_provider::{ post_state::{AccountChanges, Storage, StorageTransition, StorageWipe}, AccountProvider, BlockHashProvider, StateProvider, StateRootProvider, }; use reth_rlp::Decodable; use std::{collections::HashMap, str::FromStr}; static DEFAULT_REVM_ACCOUNT: Lazy = Lazy::new(|| RevmAccount { info: AccountInfo::default(), storage: hash_map::HashMap::default(), is_destroyed: false, is_touched: false, storage_cleared: false, is_not_existing: false, }); #[derive(Debug, Default, Clone, Eq, PartialEq)] struct StateProviderTest { accounts: HashMap, Account)>, contracts: HashMap, block_hash: HashMap, } impl StateProviderTest { /// Insert account. fn insert_account( &mut self, address: Address, mut account: Account, bytecode: Option, storage: HashMap, ) { if let Some(bytecode) = bytecode { let hash = keccak256(&bytecode); account.bytecode_hash = Some(hash); self.contracts.insert(hash, Bytecode::new_raw(bytecode.into())); } self.accounts.insert(address, (storage, account)); } } impl AccountProvider for StateProviderTest { fn basic_account(&self, address: Address) -> reth_interfaces::Result> { let ret = Ok(self.accounts.get(&address).map(|(_, acc)| *acc)); ret } } impl BlockHashProvider for StateProviderTest { fn block_hash(&self, number: u64) -> reth_interfaces::Result> { Ok(self.block_hash.get(&number).cloned()) } fn canonical_hashes_range( &self, start: BlockNumber, end: BlockNumber, ) -> reth_interfaces::Result> { let range = start..end; Ok(self .block_hash .iter() .filter_map(|(block, hash)| range.contains(block).then_some(*hash)) .collect()) } } impl StateRootProvider for StateProviderTest { fn state_root(&self, _post_state: PostState) -> reth_interfaces::Result { todo!() } } impl StateProvider for StateProviderTest { fn storage( &self, account: Address, storage_key: reth_primitives::StorageKey, ) -> reth_interfaces::Result> { Ok(self .accounts .get(&account) .and_then(|(storage, _)| storage.get(&storage_key).cloned())) } fn bytecode_by_hash(&self, code_hash: H256) -> reth_interfaces::Result> { Ok(self.contracts.get(&code_hash).cloned()) } fn proof( &self, _address: Address, _keys: &[H256], ) -> reth_interfaces::Result<(Vec, H256, Vec>)> { todo!() } } #[test] fn sanity_execution() { // Got rlp block from: src/GeneralStateTestsFiller/stChainId/chainIdGasCostFiller.json let mut block_rlp = hex!("f90262f901f9a075c371ba45999d87f4542326910a11af515897aebce5265d3f6acd1f1161f82fa01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa098f2dcd87c8ae4083e7017a05456c14eea4b1db2032126e27b3b1563d57d7cc0a08151d548273f6683169524b66ca9fe338b9ce42bc3540046c828fd939ae23bcba03f4e5c2ec5b2170b711d97ee755c160457bb58d8daa338e835ec02ae6860bbabb901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083020000018502540be40082a8798203e800a00000000000000000000000000000000000000000000000000000000000000000880000000000000000f863f861800a8405f5e10094100000000000000000000000000000000000000080801ba07e09e26678ed4fac08a249ebe8ed680bf9051a5e14ad223e4b2b9d26e0208f37a05f6e3f188e3e6eab7d7d3b6568f5eac7d687b08d307d3154ccd8c87b4630509bc0").as_slice(); let mut block = Block::decode(&mut block_rlp).unwrap(); let mut ommer = Header::default(); let ommer_beneficiary = Address::from_str("3000000000000000000000000000000000000000").unwrap(); ommer.beneficiary = ommer_beneficiary; ommer.number = block.number; block.ommers = vec![ommer]; let mut db = StateProviderTest::default(); let account1 = Address::from_str("1000000000000000000000000000000000000000").unwrap(); let account2 = Address::from_str("2adc25665018aa1fe0e6bc666dac8fc2697ff9ba").unwrap(); let account3 = Address::from_str("a94f5374fce5edbc8e2a8697c15331677e6ebf0b").unwrap(); // pre state db.insert_account( account1, Account { balance: U256::ZERO, nonce: 0x00, bytecode_hash: None }, Some(hex!("5a465a905090036002900360015500").into()), HashMap::new(), ); let account3_old_info = Account { balance: U256::from(0x3635c9adc5dea00000u128), nonce: 0x00, bytecode_hash: None, }; db.insert_account( account3, Account { balance: U256::from(0x3635c9adc5dea00000u128), nonce: 0x00, bytecode_hash: None, }, None, HashMap::new(), ); // spec at berlin fork let chain_spec = Arc::new(ChainSpecBuilder::mainnet().berlin_activated().build()); let db = SubState::new(State::new(db)); // execute chain and verify receipts let mut executor = Executor::new(chain_spec, db); let post_state = executor.execute_and_verify_receipt(&block, U256::ZERO, None).unwrap(); let base_block_reward = ETH_TO_WEI * 2; let block_reward = calc::block_reward(base_block_reward, 1); let account1_info = Account { balance: U256::ZERO, nonce: 0x00, bytecode_hash: None }; let account2_info = Account { // Block reward decrease balance: U256::from(0x1bc16d674ece94bau128 - 0x1bc16d674ec80000u128), nonce: 0x00, bytecode_hash: None, }; let account2_info_with_block_reward = Account { balance: account2_info.balance + block_reward, nonce: 0x00, bytecode_hash: None, }; let account3_info = Account { balance: U256::from(0x3635c9adc5de996b46u128), nonce: 0x01, bytecode_hash: None, }; let ommer_beneficiary_info = Account { nonce: 0, balance: calc::ommer_reward(base_block_reward, block.number, block.ommers[0].number), bytecode_hash: None, }; // Check if cache is set // account1 let db = executor.db(); let cached_acc1 = db.accounts.get(&account1).unwrap(); assert_eq!(cached_acc1.info.balance, account1_info.balance); assert_eq!(cached_acc1.info.nonce, account1_info.nonce); assert_eq!(cached_acc1.account_state, AccountState::Touched); assert_eq!(cached_acc1.storage.len(), 1); assert_eq!(cached_acc1.storage.get(&U256::from(1)), Some(&U256::from(2))); // account2 Block reward let cached_acc2 = db.accounts.get(&account2).unwrap(); assert_eq!(cached_acc2.info.balance, account2_info.balance + block_reward); assert_eq!(cached_acc2.info.nonce, account2_info.nonce); assert_eq!(cached_acc2.account_state, AccountState::Touched); assert_eq!(cached_acc2.storage.len(), 0); // account3 let cached_acc3 = db.accounts.get(&account3).unwrap(); assert_eq!(cached_acc3.info.balance, account3_info.balance); assert_eq!(cached_acc3.info.nonce, account3_info.nonce); assert_eq!(cached_acc3.account_state, AccountState::Touched); assert_eq!(cached_acc3.storage.len(), 0); assert!( post_state.accounts().get(&account1).is_none(), "Account should not be present in post-state since it was not changed" ); // Clone and sort to make the test deterministic assert_eq!( post_state.account_changes().inner, BTreeMap::from([( block.number, BTreeMap::from([ // New account (account2, None), // Changed account (account3, Some(account3_old_info)), // Ommer reward (ommer_beneficiary, None) ]) ),]), "Account changeset did not match" ); assert_eq!( post_state.storage_changes().inner, BTreeMap::from([( block.number, BTreeMap::from([( account1, StorageTransition { wipe: StorageWipe::None, // Slot 1 changed from 0 to 2 storage: BTreeMap::from([(U256::from(1), U256::ZERO)]) } )]) )]), "Storage changeset did not match" ); // Check final post-state assert_eq!( post_state.storage(), &BTreeMap::from([( account1, Storage { times_wiped: 0, storage: BTreeMap::from([(U256::from(1), U256::from(2))]) } )]), "Should have changed 1 storage slot" ); assert_eq!(post_state.bytecodes().len(), 0, "Should have zero new bytecodes"); let accounts = post_state.accounts(); assert_eq!( accounts.len(), 3, "Should have 4 accounts (account 2, 3 and the ommer beneficiary)" ); assert_eq!( accounts.get(&account2).unwrap(), &Some(account2_info_with_block_reward), "Account 2 state is wrong" ); assert_eq!( accounts.get(&account3).unwrap(), &Some(account3_info), "Account 3 state is wrong" ); assert_eq!( accounts.get(&ommer_beneficiary).unwrap(), &Some(ommer_beneficiary_info), "Ommer beneficiary state is wrong" ); } #[test] fn dao_hardfork_irregular_state_change() { let header = Header { number: 1, ..Header::default() }; let mut db = StateProviderTest::default(); let mut beneficiary_balance = 0; for (i, dao_address) in DAO_HARDKFORK_ACCOUNTS.iter().enumerate() { db.insert_account( *dao_address, Account { balance: U256::from(i), nonce: 0x00, bytecode_hash: None }, None, HashMap::new(), ); beneficiary_balance += i; } let chain_spec = Arc::new( ChainSpecBuilder::from(&*MAINNET) .homestead_activated() .with_fork(Hardfork::Dao, ForkCondition::Block(1)) .build(), ); let db = SubState::new(State::new(db)); // execute chain and verify receipts let mut executor = Executor::new(chain_spec, db); let out = executor .execute_and_verify_receipt( &Block { header, body: vec![], ommers: vec![], withdrawals: None }, U256::ZERO, None, ) .unwrap(); // Check if cache is set // beneficiary let db = executor.db(); let dao_beneficiary = db.accounts.get(&DAO_HARDFORK_BENEFICIARY).unwrap(); assert_eq!(dao_beneficiary.info.balance, U256::from(beneficiary_balance)); for address in DAO_HARDKFORK_ACCOUNTS.iter() { let account = db.accounts.get(address).unwrap(); assert_eq!(account.info.balance, U256::ZERO); } // check changesets let beneficiary_state = out.accounts().get(&DAO_HARDFORK_BENEFICIARY).unwrap().unwrap(); assert_eq!( beneficiary_state, Account { balance: U256::from(beneficiary_balance), ..Default::default() }, ); for address in DAO_HARDKFORK_ACCOUNTS.iter() { let updated_account = out.accounts().get(address).unwrap().unwrap(); assert_eq!(updated_account, Account { balance: U256::ZERO, ..Default::default() }); } } #[test] fn test_selfdestruct() { // Modified version of eth test. Storage is added for selfdestructed account to see // that changeset is set. // Got rlp block from: src/GeneralStateTestsFiller/stArgsZeroOneBalance/suicideNonConst.json let mut block_rlp = hex!("f9025ff901f7a0c86e8cc0310ae7c531c758678ddbfd16fc51c8cef8cec650b032de9869e8b94fa01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa050554882fbbda2c2fd93fdc466db9946ea262a67f7a76cc169e714f105ab583da00967f09ef1dfed20c0eacfaa94d5cd4002eda3242ac47eae68972d07b106d192a0e3c8b47fbfc94667ef4cceb17e5cc21e3b1eebd442cebb27f07562b33836290db90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302000001830f42408238108203e800a00000000000000000000000000000000000000000000000000000000000000000880000000000000000f862f860800a83061a8094095e7baea6a6c7c4c2dfeb977efac326af552d8780801ba072ed817487b84ba367d15d2f039b5fc5f087d0a8882fbdf73e8cb49357e1ce30a0403d800545b8fc544f92ce8124e2255f8c3c6af93f28243a120585d4c4c6a2a3c0").as_slice(); let block = Block::decode(&mut block_rlp).unwrap(); let mut db = StateProviderTest::default(); let address_caller = Address::from_str("a94f5374fce5edbc8e2a8697c15331677e6ebf0b").unwrap(); let address_selfdestruct = Address::from_str("095e7baea6a6c7c4c2dfeb977efac326af552d87").unwrap(); // pre state let pre_account_caller = Account { balance: U256::from(0x0de0b6b3a7640000u64), nonce: 0x00, bytecode_hash: None, }; db.insert_account(address_caller, pre_account_caller, None, HashMap::new()); // insert account that will selfd let pre_account_selfdestroyed = Account { balance: U256::ZERO, nonce: 0x00, bytecode_hash: Some(H256(hex!( "56a7d44a4ecf086c34482ad1feb1007087fc56fae6dbefbd3f416002933f1705" ))), }; let selfdestroyed_storage = BTreeMap::from([(H256::zero(), U256::ZERO), (H256::from_low_u64_be(1), U256::from(1))]); db.insert_account( address_selfdestruct, pre_account_selfdestroyed, Some(hex!("73095e7baea6a6c7c4c2dfeb977efac326af552d8731ff00").into()), selfdestroyed_storage.into_iter().collect::>(), ); // spec at berlin fork let chain_spec = Arc::new(ChainSpecBuilder::mainnet().berlin_activated().build()); let db = SubState::new(State::new(db)); // execute chain and verify receipts let mut executor = Executor::new(chain_spec, db); let out = executor.execute_and_verify_receipt(&block, U256::ZERO, None).unwrap(); assert_eq!(out.bytecodes().len(), 0, "Should have zero new bytecodes"); let post_account_caller = Account { balance: U256::from(0x0de0b6b3a761cf60u64), nonce: 0x01, bytecode_hash: None, }; assert_eq!( out.accounts().get(&address_caller).unwrap().unwrap(), post_account_caller, "Caller account has changed and fee is deduced" ); assert_eq!( out.accounts().get(&address_selfdestruct).unwrap(), &None, "Selfdestructed account should have been deleted" ); assert!( out.storage().get(&address_selfdestruct).unwrap().wiped(), "Selfdestructed account should have its storage wiped" ); } // Test vector from https://github.com/ethereum/tests/blob/3156db5389921125bb9e04142d18e0e7b0cf8d64/BlockchainTests/EIPTests/bc4895-withdrawals/twoIdenticalIndexDifferentValidator.json #[test] fn test_withdrawals() { let block_rlp = hex!("f9028cf90219a0151934ad9b654c50197f37018ee5ee9bb922dec0a1b5e24a6d679cb111cdb107a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa048cd9a5957e45beebf80278a5208b0cbe975ab4b4adb0da1509c67b26f2be3ffa056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008001887fffffffffffffff8082079e42a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b42188000000000000000009a04a220ebe55034d51f8a58175bb504b6ebf883105010a1f6d42e557c18bbd5d69c0c0f86cda808094c94f5374fce5edbc8e2a8697c15331677e6ebf0b822710da028094c94f5374fce5edbc8e2a8697c15331677e6ebf0b822710da018094c94f5374fce5edbc8e2a8697c15331677e6ebf0b822710da020194c94f5374fce5edbc8e2a8697c15331677e6ebf0b822710"); let block = Block::decode(&mut block_rlp.as_slice()).unwrap(); let withdrawals = block.withdrawals.as_ref().unwrap(); assert_eq!(withdrawals.len(), 4); let withdrawal_beneficiary = Address::from_str("c94f5374fce5edbc8e2a8697c15331677e6ebf0b").unwrap(); // spec at shanghai fork let chain_spec = Arc::new(ChainSpecBuilder::mainnet().shanghai_activated().build()); let db = SubState::new(State::new(StateProviderTest::default())); // execute chain and verify receipts let mut executor = Executor::new(chain_spec, db); let out = executor.execute_and_verify_receipt(&block, U256::ZERO, None).unwrap(); let withdrawal_sum = withdrawals.iter().fold(U256::ZERO, |sum, w| sum + w.amount_wei()); let beneficiary_account = executor.db().accounts.get(&withdrawal_beneficiary).unwrap(); assert_eq!(beneficiary_account.info.balance, withdrawal_sum); assert_eq!(beneficiary_account.info.nonce, 0); assert_eq!(beneficiary_account.account_state, AccountState::StorageCleared); assert_eq!( out.accounts().get(&withdrawal_beneficiary).unwrap(), &Some(Account { nonce: 0, balance: withdrawal_sum, bytecode_hash: None }), "Withdrawal account should have gotten its balance set" ); // Execute same block again let out = executor.execute_and_verify_receipt(&block, U256::ZERO, None).unwrap(); assert_eq!( out.accounts().get(&withdrawal_beneficiary).unwrap(), &Some(Account { nonce: 0, balance: withdrawal_sum + withdrawal_sum, bytecode_hash: None }), "Withdrawal account should have gotten its balance set" ); } #[test] fn test_account_state_preserved() { let account = Address::from_str("c94f5374fce5edbc8e2a8697c15331677e6ebf0b").unwrap(); let mut db = StateProviderTest::default(); db.insert_account(account, Account::default(), None, HashMap::default()); let chain_spec = Arc::new(ChainSpecBuilder::mainnet().istanbul_activated().build()); let db = SubState::new(State::new(db)); let mut executor = Executor::new(chain_spec, db); // touch account executor.commit_changes( 1, hash_map::HashMap::from([(account, DEFAULT_REVM_ACCOUNT.clone())]), true, &mut PostState::default(), ); // destroy account executor.commit_changes( 1, hash_map::HashMap::from([( account, RevmAccount { is_destroyed: true, is_touched: true, ..DEFAULT_REVM_ACCOUNT.clone() }, )]), true, &mut PostState::default(), ); // re-create account executor.commit_changes( 1, hash_map::HashMap::from([( account, RevmAccount { is_touched: true, storage_cleared: true, ..DEFAULT_REVM_ACCOUNT.clone() }, )]), true, &mut PostState::default(), ); // touch account executor.commit_changes( 1, hash_map::HashMap::from([(account, DEFAULT_REVM_ACCOUNT.clone())]), true, &mut PostState::default(), ); let db = executor.db(); let account = db.load_account(account).unwrap(); assert_eq!(account.account_state, AccountState::StorageCleared); } /// If the account is created and destroyed within the same transaction, we shouldn't generate /// the changeset. #[test] fn test_account_created_destroyed() { let address = Address::random(); let mut db = SubState::new(State::new(StateProviderTest::default())); db.load_account(address).unwrap(); // hot load the non-existing account let chain_spec = Arc::new(ChainSpecBuilder::mainnet().shanghai_activated().build()); let mut executor = Executor::new(chain_spec, db); let mut post_state = PostState::default(); executor.commit_changes( 1, hash_map::HashMap::from([( address, RevmAccount { is_destroyed: true, storage_cleared: true, ..DEFAULT_REVM_ACCOUNT.clone() }, )]), true, &mut post_state, ); assert!(post_state.account_changes().is_empty()); } /// If the account was touched, but remained unchanged over the course of multiple transactions, /// no changeset should be generated. #[test] fn test_touched_unchanged_account() { let address = Address::random(); let db = SubState::new(State::new(StateProviderTest::default())); let chain_spec = Arc::new(ChainSpecBuilder::mainnet().shanghai_activated().build()); let mut executor = Executor::new(chain_spec, db); let mut post_state = PostState::default(); executor.commit_changes( 1, hash_map::HashMap::from([( address, RevmAccount { is_touched: true, ..DEFAULT_REVM_ACCOUNT.clone() }, )]), true, &mut post_state, ); assert!(post_state.account_changes().is_empty()); executor.commit_changes( 1, hash_map::HashMap::from([( address, RevmAccount { is_touched: true, ..DEFAULT_REVM_ACCOUNT.clone() }, )]), true, &mut post_state, ); assert_eq!(post_state.account_changes(), &AccountChanges::default()); } #[test] fn test_state_clear_eip_touch_account() { let address = Address::random(); let mut state_provider = StateProviderTest::default(); state_provider.insert_account(address, Account::default(), None, HashMap::default()); let mut db = SubState::new(State::new(state_provider)); db.load_account(address).unwrap(); // hot load the account let chain_spec = Arc::new(ChainSpecBuilder::mainnet().shanghai_activated().build()); let mut executor = Executor::new(chain_spec, db); let mut post_state = PostState::default(); // Touch an empty account before state clearing EIP. Nothing should happen. executor.commit_changes( 1, hash_map::HashMap::from([( address, RevmAccount { is_touched: true, ..DEFAULT_REVM_ACCOUNT.clone() }, )]), false, &mut post_state, ); assert_eq!(post_state.accounts(), &BTreeMap::default()); assert_eq!(post_state.account_changes(), &AccountChanges::default()); // Touch an empty account after state clearing EIP. The account should be destroyed. executor.commit_changes( 2, hash_map::HashMap::from([( address, RevmAccount { is_touched: true, ..DEFAULT_REVM_ACCOUNT.clone() }, )]), true, &mut post_state, ); assert_eq!(post_state.accounts(), &BTreeMap::from([(address, None)])); assert_eq!( post_state.account_changes(), &AccountChanges { size: 1, inner: BTreeMap::from([(2, BTreeMap::from([(address, Some(Account::default()))]))]) } ); } #[test] fn test_state_clear_eip_create_account() { let address1 = Address::random(); let address2 = Address::random(); let address3 = Address::random(); let address4 = Address::random(); let state_provider = StateProviderTest::default(); let mut db = SubState::new(State::new(state_provider)); db.load_account(address1).unwrap(); // hot load account 1 let chain_spec = Arc::new(ChainSpecBuilder::mainnet().shanghai_activated().build()); let mut executor = Executor::new(chain_spec, db); // Create empty accounts before state clearing EIP. let mut post_state_before_state_clear = PostState::default(); executor.commit_changes( 1, hash_map::HashMap::from([ (address1, RevmAccount { is_touched: true, ..DEFAULT_REVM_ACCOUNT.clone() }), (address2, RevmAccount { is_touched: true, ..DEFAULT_REVM_ACCOUNT.clone() }), ]), false, &mut post_state_before_state_clear, ); assert_eq!( post_state_before_state_clear.accounts(), &BTreeMap::from([ (address1, Some(Account::default())), (address2, Some(Account::default())) ]) ); assert_eq!( post_state_before_state_clear.account_changes(), &AccountChanges { size: 2, inner: BTreeMap::from([(1, BTreeMap::from([(address1, None), (address2, None)]))]) } ); // Empty accounts should not be created after state clearing EIP. let mut post_state_after_state_clear = PostState::default(); executor.commit_changes( 2, hash_map::HashMap::from([ (address3, RevmAccount { is_touched: true, ..DEFAULT_REVM_ACCOUNT.clone() }), (address4, RevmAccount { is_touched: true, ..DEFAULT_REVM_ACCOUNT.clone() }), ]), true, &mut post_state_after_state_clear, ); assert_eq!(post_state_after_state_clear.accounts(), &BTreeMap::default()); assert_eq!(post_state_after_state_clear.account_changes(), &AccountChanges::default()); } }