use crate::post_state::PostState; use reth_interfaces::executor::Error; use reth_primitives::{ bloom::logs_bloom, Account, Address, Block, Bloom, Bytecode, ChainSpec, Hardfork, Header, Log, Receipt, TransactionSigned, H256, U256, }; use reth_provider::{BlockExecutor, StateProvider}; use reth_revm::{ config::{WEI_2ETH, WEI_3ETH, WEI_5ETH}, database::SubState, env::{fill_cfg_and_block_env, fill_tx_env}, into_reth_log, to_reth_acc, }; use reth_revm_inspectors::stack::{InspectorStack, InspectorStackConfig}; use revm::{ db::AccountState, 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, Error> { if let Some(senders) = senders { if body.len() == senders.len() { Ok(senders) } else { Err(Error::SenderRecoveryError) } } else { body.iter().map(|tx| tx.recover_signer().ok_or(Error::SenderRecoveryError)).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, changes: hash_map::HashMap, has_state_clear_eip: bool, post_state: &mut PostState, ) { let db = self.db(); // 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."); } }; // Insert into `change` a old account and None for new account // and mark storage to be mapped post_state.destroy_account(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(); let account = to_reth_acc(&entry.info); if !(has_state_clear_eip && account.is_empty()) { post_state.create_account(address, account); } entry } Entry::Occupied(entry) => { let entry = entry.into_mut(); if matches!(entry.account_state, AccountState::NotExisting) { let account = to_reth_acc(&account.info); if !(has_state_clear_eip && account.is_empty()) { post_state.create_account(address, account); } } else if entry.info != account.info { post_state.change_account( address, to_reth_acc(&entry.info), to_reth_acc(&account.info), ); } else if has_state_clear_eip && account.is_empty() { // The account was touched, but it is empty, so it should be deleted. post_state.destroy_account(address, to_reth_acc(&account.info)); } 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 { 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)| { 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(address, storage_changeset); } } } } /// 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, ) -> Result, Error> { let mut balance_increments = HashMap::::default(); // Collect balance increments for block and uncle rewards. if let Some(reward) = self.get_block_reward(block, td) { // Calculate Uncle reward // OpenEthereum code: https://github.com/openethereum/openethereum/blob/6c2d392d867b058ff867c4373e40850ca3f96969/crates/ethcore/src/ethereum/ethash.rs#L319-L333 for ommer in block.ommers.iter() { let ommer_reward = U256::from(((8 + ommer.number - block.number) as u128 * reward) >> 3); // From yellowpaper Page 15: // If there are collisions of the beneficiary addresses between ommers and the // block (i.e. two ommers with the same beneficiary address // or an ommer with the same beneficiary address as the // present block), additions are applied cumulatively *balance_increments.entry(ommer.beneficiary).or_default() += ommer_reward; } // Increment balance for main block reward. let block_reward = U256::from(reward + (reward >> 5) * block.ommers.len() as u128); *balance_increments.entry(block.beneficiary).or_default() += block_reward; } if self.chain_spec.fork(Hardfork::Shanghai).active_at_timestamp(block.timestamp) { if let Some(withdrawals) = block.withdrawals.as_ref() { for withdrawal in withdrawals { *balance_increments.entry(withdrawal.address).or_default() += withdrawal.amount_wei(); } } } Ok(balance_increments) } /// From yellowpapper Page 15: /// 11.3. Reward Application. The application of rewards to a block involves raising the /// balance of the accounts of the beneficiary address of the block and each ommer by /// a certain amount. We raise the block’s beneficiary account by Rblock; for each /// ommer, we raise the block’s beneficiary by an additional 1/32 of the block reward /// and the beneficiary of the ommer gets rewarded depending on the blocknumber. /// Formally we define the function Ω. /// /// NOTE: Related to Ethereum reward change, for other network this is probably going to be /// moved to config. fn get_block_reward(&self, header: &Header, total_difficulty: U256) -> Option { if self.chain_spec.fork(Hardfork::Paris).active_at_ttd(total_difficulty, header.difficulty) { None } else if self.chain_spec.fork(Hardfork::Petersburg).active_at_block(header.number) { Some(WEI_2ETH) } else if self.chain_spec.fork(Hardfork::Byzantium).active_at_block(header.number) { Some(WEI_3ETH) } else { Some(WEI_5ETH) } } /// Irregular state change at Ethereum DAO hardfork fn apply_dao_fork_changes(&mut self, post_state: &mut PostState) -> Result<(), Error> { let db = self.db(); let mut drained_balance = U256::ZERO; // drain all accounts ether for address in crate::eth_dao_fork::DAO_HARDKFORK_ACCOUNTS { let db_account = db.load_account(address).map_err(|_| Error::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(address, old, new); } // add drained ether to beneficiary. let beneficiary = crate::eth_dao_fork::DAO_HARDFORK_BENEFICIARY; self.increment_account_balance(beneficiary, drained_balance, post_state)?; Ok(()) } /// Increment the balance for the given account in the [PostState]. fn increment_account_balance( &mut self, address: Address, increment: U256, post_state: &mut PostState, ) -> Result<(), Error> { let db = self.db(); let beneficiary = db.load_account(address).map_err(|_| Error::ProviderError)?; 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( 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(address, old, new); } } Ok(()) } /// 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| Error::EVM { hash, message: format!("{e:?}") }) } /// 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), Error> { 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.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(Error::TransactionGasLimitMoreThenAvailableBlockGas { transaction_gas_limit: transaction.gas_limit(), block_available_gas, }) } // Execute transaction. let ResultAndState { result, state } = self.transact(transaction, sender)?; // commit changes self.commit_changes( state, self.chain_spec.fork(Hardfork::SpuriousDragon).active_at_block(block.number), &mut post_state, ); // append gas used cumulative_gas_used += result.gas_used(); // cast revm logs to reth logs let logs: Vec = result.logs().into_iter().map(into_reth_log).collect(); // Push transaction changeset and calculate header bloom filter for receipt. post_state.add_receipt(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, bloom: logs_bloom(logs.iter()), logs, }); post_state.finish_transition(); } Ok((post_state, cumulative_gas_used)) } } impl BlockExecutor for Executor where DB: StateProvider, { fn execute( &mut self, block: &Block, total_difficulty: U256, senders: Option>, ) -> Result { let (mut 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(Error::BlockGasUsed { got: cumulative_gas_used, expected: block.gas_used }) } let balance_increments = self.post_block_balance_increments(block, total_difficulty)?; let mut includes_block_transition = !balance_increments.is_empty(); for (address, increment) in balance_increments.into_iter() { self.increment_account_balance(address, increment, &mut post_state)?; } if self.chain_spec.fork(Hardfork::Dao).transitions_at_block(block.number) { includes_block_transition = true; self.apply_dao_fork_changes(&mut post_state)?; } if includes_block_transition { post_state.finish_transition(); } Ok(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)?; 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().iter(), )?; } // 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 Ok(post_state) } } /// Verify receipts pub fn verify_receipt<'a>( expected_receipts_root: H256, expected_logs_bloom: Bloom, receipts: impl Iterator + Clone, ) -> Result<(), Error> { // Check receipts root. let receipts_root = reth_primitives::proofs::calculate_receipt_root(receipts.clone()); if receipts_root != expected_receipts_root { return Err(Error::ReceiptRootDiff { got: receipts_root, expected: expected_receipts_root }) } // Create header log bloom. let logs_bloom = receipts.fold(Bloom::zero(), |bloom, r| bloom | r.bloom); if logs_bloom != expected_logs_bloom { return Err(Error::BloomLogDiff { expected: Box::new(expected_logs_bloom), got: Box::new(logs_bloom), }) } Ok(()) } #[cfg(test)] mod tests { use super::*; use reth_primitives::{ hex_literal::hex, keccak256, Account, Address, BlockNumber, Bytecode, Bytes, ChainSpecBuilder, ForkCondition, StorageKey, H256, MAINNET, U256, }; use reth_provider::{ post_state::{Change, Storage}, AccountProvider, BlockHashProvider, StateProvider, }; use reth_revm::database::State; use reth_rlp::Decodable; use std::{collections::HashMap, str::FromStr}; #[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 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(); assert_eq!( post_state.transitions_count(), 2, "Should executed two transitions (1 tx and 1 block reward)" ); let block_reward = U256::from(WEI_2ETH + (WEI_2ETH >> 5)); 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: U256::from((8 * WEI_2ETH) >> 3), 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" ); // Check changes const TX_TRANSITION_ID: u64 = 0; const BLOCK_TRANSITION_ID: u64 = 1; // Clone and sort to make the test deterministic let mut changes = post_state.changes().to_vec(); changes.sort_by_key(|change| (change.transition_id(), change.address())); assert_eq!( changes, &[ // Storage changes on account 1 Change::StorageChanged { id: TX_TRANSITION_ID, address: account1, changeset: [(U256::from(1), (U256::ZERO, U256::from(2)))].into() }, // New account Change::AccountCreated { id: TX_TRANSITION_ID, address: account2, account: account2_info }, // Changed account Change::AccountChanged { id: TX_TRANSITION_ID, address: account3, old: account3_old_info, new: account3_info }, // Block reward Change::AccountChanged { id: BLOCK_TRANSITION_ID, address: account2, old: account2_info, new: account2_info_with_block_reward }, // Ommer reward Change::AccountCreated { id: BLOCK_TRANSITION_ID, address: ommer_beneficiary, account: ommer_beneficiary_info }, ], "Changeset did not match" ); // Check final post-state assert_eq!( post_state.storage(), &BTreeMap::from([( account1, Storage { wiped: false, 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 crate::eth_dao_fork::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(); assert_eq!( out.transitions_count(), 1, "Should only have 1 transition (the block transition)" ); // Check if cache is set // beneficiary let db = executor.db(); let dao_beneficiary = db.accounts.get(&crate::eth_dao_fork::DAO_HARDFORK_BENEFICIARY).unwrap(); assert_eq!(dao_beneficiary.info.balance, U256::from(beneficiary_balance)); for address in crate::eth_dao_fork::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(&crate::eth_dao_fork::DAO_HARDFORK_BENEFICIARY).unwrap().unwrap(); assert_eq!( beneficiary_state, Account { balance: U256::from(beneficiary_balance), ..Default::default() }, ); for address in crate::eth_dao_fork::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.transitions_count(), 2, "Should only have two transitions (the transaction and the block)" ); 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(); assert_eq!(out.transitions_count(), 1, "Only one transition (the block transition)"); 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.transitions_count(), 1, "Should only have one transition (the block transition)" ); 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 default_acc = RevmAccount { info: AccountInfo::default(), storage: hash_map::HashMap::default(), is_destroyed: false, is_touched: false, storage_cleared: false, is_not_existing: false, }; let mut executor = Executor::new(chain_spec, db); // touch account executor.commit_changes( hash_map::HashMap::from([(account, RevmAccount { ..default_acc.clone() })]), true, &mut PostState::default(), ); // destroy account executor.commit_changes( hash_map::HashMap::from([( account, RevmAccount { is_destroyed: true, is_touched: true, ..default_acc.clone() }, )]), true, &mut PostState::default(), ); // re-create account executor.commit_changes( hash_map::HashMap::from([( account, RevmAccount { is_touched: true, storage_cleared: true, ..default_acc.clone() }, )]), true, &mut PostState::default(), ); // touch account executor.commit_changes( hash_map::HashMap::from([(account, RevmAccount { ..default_acc })]), true, &mut PostState::default(), ); let db = executor.db(); let account = db.load_account(account).unwrap(); assert_eq!(account.account_state, AccountState::StorageCleared); } }