use crate::{ config::{revm_spec, WEI_2ETH, WEI_3ETH, WEI_5ETH}, revm_wrap::{self, to_reth_acc, SubState}, }; use hashbrown::hash_map::Entry; use reth_db::{models::AccountBeforeTx, tables, transaction::DbTxMut, Error as DbError}; use reth_interfaces::executor::Error; use reth_primitives::{ bloom::logs_bloom, Account, Address, Bloom, ChainSpec, Hardfork, Header, Log, Receipt, TransactionSignedEcRecovered, H160, H256, U256, }; use reth_provider::StateProvider; use revm::{ db::AccountState, Account as RevmAccount, AccountInfo, AnalysisKind, Bytecode, Return, SpecId, EVM, }; use std::collections::BTreeMap; /// Main block executor pub struct Executor {} /// Contains old/new account changes #[derive(Debug, Clone, Eq, PartialEq)] pub enum AccountInfoChangeSet { /// The account is newly created. Created { /// The newly created account. new: Account, }, /// An account was deleted (selfdestructed) or we have touched /// an empty account and we need to remove/destroy it. /// (Look at state clearing [EIP-158](https://eips.ethereum.org/EIPS/eip-158)) Destroyed { /// The account that was destroyed. old: Account, }, /// The account was changed. Changed { /// The account after the change. new: Account, /// The account prior to the change. old: Account, }, /// Nothing was changed for the account (nonce/balance). NoChange, } impl AccountInfoChangeSet { /// Apply the changes from the changeset to a database transaction. pub fn apply_to_db<'a, TX: DbTxMut<'a>>( self, tx: &TX, address: Address, tx_index: u64, ) -> Result<(), DbError> { match self { AccountInfoChangeSet::Changed { old, new } => { // insert old account in AccountChangeSet // check for old != new was already done tx.put::( tx_index, AccountBeforeTx { address, info: Some(old) }, )?; tx.put::(address, new)?; } AccountInfoChangeSet::Created { new } => { tx.put::( tx_index, AccountBeforeTx { address, info: None }, )?; tx.put::(address, new)?; } AccountInfoChangeSet::Destroyed { old } => { tx.delete::(address, None)?; tx.put::( tx_index, AccountBeforeTx { address, info: Some(old) }, )?; } AccountInfoChangeSet::NoChange => { // do nothing storage account didn't change } } Ok(()) } } /// Diff change set that is needed for creating history index and updating current world state. #[derive(Debug, Clone)] pub struct AccountChangeSet { /// Old and New account account change. pub account: AccountInfoChangeSet, /// Storage containing key -> (OldValue,NewValue). in case that old value is not existing /// we can expect to have U256::ZERO, same with new value. pub storage: BTreeMap, /// Just to make sure that we are taking selfdestruct cleaning we have this field that wipes /// storage. There are instances where storage is changed but account is not touched, so we /// can't take into account that if new account is None that it is selfdestruct. pub wipe_storage: bool, } /// Execution Result containing vector of transaction changesets /// and block reward if present #[derive(Debug)] pub struct ExecutionResult { /// Transaction changeset containing [Receipt], changed [Accounts][Account] and Storages. pub changesets: Vec, /// Block reward if present. It represent changeset for block reward slot in /// [tables::AccountChangeSet] . pub block_reward: Option>, } /// Commit change to database and return change diff that is used to update state and create /// history index /// /// ChangeDiff consists of: /// address->AccountChangeSet (It contains old and new account info,storage wipe flag, and /// old/new storage) bytecode_hash->bytecodes mapping /// /// BTreeMap is used to have sorted values pub fn commit_changes( db: &mut SubState, changes: hashbrown::HashMap, ) -> (BTreeMap, BTreeMap) { let mut change = BTreeMap::new(); let mut new_bytecodes = BTreeMap::new(); // 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 change.insert( address, AccountChangeSet { account: AccountInfoChangeSet::Destroyed { old: to_reth_acc(&db_account.info) }, storage: BTreeMap::new(), wipe_storage: true, }, ); // 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() { match db.contracts.entry(account.info.code_hash) { Entry::Vacant(entry) => { entry.insert(code.clone()); new_bytecodes.insert(H256(account.info.code_hash.0), code.clone()); } Entry::Occupied(mut entry) => { entry.insert(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 (account_info_changeset, new_account) = match db.accounts.entry(address) { Entry::Vacant(entry) => { let entry = entry.insert(Default::default()); entry.info = account.info.clone(); // account was not existing, so this means new account is created (AccountInfoChangeSet::Created { new: to_reth_acc(&entry.info) }, entry) } Entry::Occupied(entry) => { let entry = entry.into_mut(); // account is present inside cache but is marked as NotExisting. let account_changeset = if matches!(entry.account_state, AccountState::NotExisting) { AccountInfoChangeSet::Created { new: to_reth_acc(&account.info) } } else if entry.info != account.info { AccountInfoChangeSet::Changed { old: to_reth_acc(&entry.info), new: to_reth_acc(&account.info), } } else { AccountInfoChangeSet::NoChange }; entry.info = account.info.clone(); (account_changeset, entry) } }; let mut wipe_storage = false; new_account.account_state = if account.storage_cleared { new_account.storage.clear(); wipe_storage = true; AccountState::StorageCleared } else { AccountState::Touched }; // Insert storage. let mut storage = BTreeMap::new(); // insert storage into new db account. new_account.storage.extend(account.storage.into_iter().map(|(key, value)| { storage.insert(key, (value.original_value(), value.present_value())); (key, value.present_value()) })); // Insert into change. change.insert( address, AccountChangeSet { account: account_info_changeset, storage, wipe_storage }, ); } } (change, new_bytecodes) } /// After transaction is executed this structure contain /// transaction [Receipt] every change to state ([Account], Storage, [Bytecode]) /// that this transaction made and its old values /// so that history account table can be updated. #[derive(Debug, Clone)] pub struct TransactionChangeSet { /// Transaction receipt pub receipt: Receipt, /// State change that this transaction made on state. pub changeset: BTreeMap, /// new bytecode created as result of transaction execution. pub new_bytecodes: BTreeMap, } /// Execute and verify block pub fn execute_and_verify_receipt( header: &Header, transactions: &[TransactionSignedEcRecovered], ommers: &[Header], chain_spec: &ChainSpec, db: &mut SubState, ) -> Result { let transaction_change_set = execute(header, transactions, ommers, chain_spec, db)?; let receipts_iter = transaction_change_set.changesets.iter().map(|changeset| &changeset.receipt); if Some(header.number) >= chain_spec.fork_block(Hardfork::Byzantium) { verify_receipt(header.receipts_root, header.logs_bloom, 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(transaction_change_set) } /// 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(()) } /// Verify block. Execute all transaction and compare results. /// Returns ChangeSet on transaction granularity. /// NOTE: If block reward is still active (Before Paris/Merge) we would return /// additional TransactionStatechangeset for account that receives the reward. pub fn execute( header: &Header, transactions: &[TransactionSignedEcRecovered], ommers: &[Header], chain_spec: &ChainSpec, db: &mut SubState, ) -> Result { let mut evm = EVM::new(); evm.database(db); let spec_id = revm_spec(chain_spec, header.into()); evm.env.cfg.chain_id = U256::from(chain_spec.chain().id()); evm.env.cfg.spec_id = spec_id; evm.env.cfg.perf_all_precompiles_have_balance = false; evm.env.cfg.perf_analyse_created_bytecodes = AnalysisKind::Raw; revm_wrap::fill_block_env(&mut evm.env.block, header, spec_id >= SpecId::MERGE); let mut cumulative_gas_used = 0; // output of verification let mut changesets = Vec::with_capacity(transactions.len()); for transaction in transactions.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 = 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, }) } // Fill revm structure. revm_wrap::fill_tx_env(&mut evm.env.tx, transaction); // Execute transaction. let out = evm.transact(); // Useful for debugging // let out = evm.inspect(revm::inspectors::CustomPrintTracer::default()); // tracing::trace!(target:"evm","Executing transaction {:?}, \n:{out:?}: {:?} // \nENV:{:?}",transaction.hash(),transaction,evm.env); let (revm::ExecutionResult { exit_reason, gas_used, logs, .. }, state) = out; // Fatal internal error. if exit_reason == revm::Return::FatalExternalError { return Err(Error::ExecutionFatalError) } // Success flag was added in `EIP-658: Embedding transaction status code in receipts`. // TODO for verification (exit_reason): some error should return EVM error as the block with // that transaction can have consensus error that would make block invalid. let is_success = match exit_reason { revm::return_ok!() => true, revm::return_revert!() => false, _ => false, //e => return Err(Error::EVMError { error_code: e as u32 }), }; // Add spend gas. cumulative_gas_used += gas_used; // Transform logs to reth format. let logs: Vec = logs .into_iter() .map(|l| Log { address: H160(l.address.0), topics: l.topics.into_iter().map(|h| H256(h.0)).collect(), data: l.data.into(), }) .collect(); // commit state let (changeset, new_bytecodes) = commit_changes(evm.db().expect("Db to not be moved."), state); // Push transaction changeset and calculte header bloom filter for receipt. changesets.push(TransactionChangeSet { receipt: Receipt { tx_type: transaction.tx_type(), success: is_success, cumulative_gas_used, bloom: logs_bloom(logs.iter()), logs, }, changeset, new_bytecodes, }) } // Check if gas used matches the value set in header. if header.gas_used != cumulative_gas_used { return Err(Error::BlockGasUsed { got: cumulative_gas_used, expected: header.gas_used }) } let db = evm.db.expect("Db is set at the start of the function"); let mut block_reward = block_reward_changeset(header, ommers, db, chain_spec)?; if chain_spec.fork_block(Hardfork::Dao) == Some(header.number) { let mut irregular_state_changeset = dao_fork_changeset(db)?; irregular_state_changeset.extend(block_reward.take().unwrap_or_default().into_iter()); block_reward = Some(irregular_state_changeset); } Ok(ExecutionResult { changesets, block_reward }) } /// Irregular state change at Ethereum DAO hardfork pub fn dao_fork_changeset( db: &mut SubState, ) -> Result, Error> { let mut drained_balance = U256::ZERO; // drain all accounts ether let mut changesets = crate::eth_dao_fork::DAO_HARDKFORK_ACCOUNTS .iter() .map(|&address| { 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 Ok((address, AccountInfoChangeSet::Changed { new, old })) }) .collect::, _>>()?; // add drained ether to beneficiary. let beneficiary = crate::eth_dao_fork::DAO_HARDFORK_BENEFICIARY; let beneficiary_db_account = db.load_account(beneficiary).map_err(|_| Error::ProviderError)?; let old = to_reth_acc(&beneficiary_db_account.info); beneficiary_db_account.info.balance += drained_balance; let new = to_reth_acc(&beneficiary_db_account.info); let beneficiary_changeset = AccountInfoChangeSet::Changed { new, old }; // insert changeset changesets.insert(beneficiary, beneficiary_changeset); Ok(changesets) } /// Calculate Block reward changeset pub fn block_reward_changeset( header: &Header, ommers: &[Header], db: &mut SubState, chain_spec: &ChainSpec, ) -> Result>, Error> { // NOTE: Related to Ethereum reward change, for other network this is probably going to be moved // to config. // 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 Ω: match header.into() { d if chain_spec.fork_active(Hardfork::Paris, d) => None, d if chain_spec.fork_active(Hardfork::Petersburg, d) => Some(WEI_2ETH), d if chain_spec.fork_active(Hardfork::Byzantium, d) => Some(WEI_3ETH), _ => Some(WEI_5ETH), } .map(|reward| -> Result<_, _> { let mut reward_beneficiaries: BTreeMap = BTreeMap::new(); // Calculate Uncle reward // OpenEthereum code: https://github.com/openethereum/openethereum/blob/6c2d392d867b058ff867c4373e40850ca3f96969/crates/ethcore/src/ethereum/ethash.rs#L319-L333 for ommer in ommers { let ommer_reward = ((8 + ommer.number - header.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 *reward_beneficiaries.entry(ommer.beneficiary).or_default() += ommer_reward; } // insert main block reward *reward_beneficiaries.entry(header.beneficiary).or_default() += reward + (reward >> 5) * ommers.len() as u128; // // create changesets for beneficiaries rewards (Main block and ommers); reward_beneficiaries .into_iter() .map(|(beneficiary, reward)| -> Result<_, _> { let changeset = db .load_account(beneficiary) .map_err(|_| Error::ProviderError) // if account is present append `Changed` changeset for block reward .map(|db_acc| { let old = to_reth_acc(&db_acc.info); let mut new = old; new.balance += U256::from(reward); db_acc.info.balance = new.balance; match db_acc.account_state { AccountState::NotExisting => { // if account was not existing that means that storage is not // present. db_acc.account_state = AccountState::StorageCleared; // if account was not present append `Created` changeset AccountInfoChangeSet::Created { new: 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 db_acc.account_state == AccountState::None { db_acc.account_state = AccountState::Touched; } // if account was present, append changed changeset. AccountInfoChangeSet::Changed { new, old } } } })?; Ok((beneficiary, changeset)) }) .collect::, _>>() }) .transpose() } #[cfg(test)] mod tests { use std::{collections::HashMap, sync::Arc}; use crate::revm_wrap::State; use reth_db::{ database::Database, mdbx::{test_utils, Env, EnvKind, WriteMap}, transaction::DbTx, }; use reth_primitives::{ hex_literal::hex, keccak256, Account, Address, Bytes, ChainSpecBuilder, ForkKind, SealedBlock, StorageKey, H160, H256, MAINNET, U256, }; use reth_provider::{AccountProvider, BlockHashProvider, StateProvider}; use reth_rlp::Decodable; use super::*; #[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); } 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: U256) -> reth_interfaces::Result> { Ok(self.block_hash.get(&number).cloned()) } } 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()) } } #[test] fn sanity_execution() { // Got rlp block from: src/GeneralStateTestsFiller/stChainId/chainIdGasCostFiller.json let mut block_rlp = hex!("f90262f901f9a075c371ba45999d87f4542326910a11af515897aebce5265d3f6acd1f1161f82fa01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa098f2dcd87c8ae4083e7017a05456c14eea4b1db2032126e27b3b1563d57d7cc0a08151d548273f6683169524b66ca9fe338b9ce42bc3540046c828fd939ae23bcba03f4e5c2ec5b2170b711d97ee755c160457bb58d8daa338e835ec02ae6860bbabb901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083020000018502540be40082a8798203e800a00000000000000000000000000000000000000000000000000000000000000000880000000000000000f863f861800a8405f5e10094100000000000000000000000000000000000000080801ba07e09e26678ed4fac08a249ebe8ed680bf9051a5e14ad223e4b2b9d26e0208f37a05f6e3f188e3e6eab7d7d3b6568f5eac7d687b08d307d3154ccd8c87b4630509bc0").as_slice(); let block = SealedBlock::decode(&mut block_rlp).unwrap(); let mut ommer = Header::default(); let ommer_beneficiary = H160(hex!("3000000000000000000000000000000000000000")); ommer.beneficiary = ommer_beneficiary; ommer.number = block.number; let ommers = vec![ommer]; let mut db = StateProviderTest::default(); let account1 = H160(hex!("1000000000000000000000000000000000000000")); let account2 = H160(hex!("2adc25665018aa1fe0e6bc666dac8fc2697ff9ba")); let account3 = H160(hex!("a94f5374fce5edbc8e2a8697c15331677e6ebf0b")); // pre staet 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 = ChainSpecBuilder::mainnet().berlin_activated().build(); let mut db = SubState::new(State::new(db)); let transactions: Vec = block.body.iter().map(|tx| tx.try_ecrecovered().unwrap()).collect(); // execute chain and verify receipts let out = execute_and_verify_receipt(&block.header, &transactions, &ommers, &chain_spec, &mut db) .unwrap(); assert_eq!(out.changesets.len(), 1, "Should executed one transaction"); let changesets = out.changesets[0].clone(); assert_eq!(changesets.new_bytecodes.len(), 0, "Should have zero new bytecodes"); let account1_info = Account { balance: U256::ZERO, nonce: 0x00, bytecode_hash: None }; let account2_info = Account { balance: U256::from(0x1bc16d674ece94bau128 - 0x1bc16d674ec80000u128), /* decrease for * block reward */ nonce: 0x00, bytecode_hash: None, }; let account3_info = Account { balance: U256::from(0x3635c9adc5de996b46u128), nonce: 0x01, bytecode_hash: None, }; let block_reward = U256::from(WEI_2ETH + (WEI_2ETH >> 5)); // Check if cache is set // account1 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!(matches!(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!(matches!(cached_acc3.account_state, AccountState::Touched)); assert_eq!(cached_acc3.storage.len(), 0); assert_eq!( changesets.changeset.get(&account1).unwrap().account, AccountInfoChangeSet::NoChange, "No change to account" ); assert_eq!( changesets.changeset.get(&account2).unwrap().account, AccountInfoChangeSet::Created { new: account2_info }, "New account" ); assert_eq!( changesets.changeset.get(&account3).unwrap().account, AccountInfoChangeSet::Changed { old: account3_old_info, new: account3_info }, "Change to account state" ); // check block rewards changeset. let mut block_rewarded_acc_info = account2_info; // add Blocks 2 eth reward and 2>>5 for one ommer block_rewarded_acc_info.balance += block_reward; // check block reward changeset assert_eq!( out.block_reward, Some(BTreeMap::from([ ( account2, AccountInfoChangeSet::Changed { new: block_rewarded_acc_info, old: account2_info } ), ( ommer_beneficiary, AccountInfoChangeSet::Created { new: Account { nonce: 0, balance: U256::from((8 * WEI_2ETH) >> 3), bytecode_hash: None } } ) ])) ); assert_eq!(changesets.new_bytecodes.len(), 0, "No new bytecodes"); // check storage let storage = &changesets.changeset.get(&account1).unwrap().storage; assert_eq!(storage.len(), 1, "Only one storage change"); assert_eq!( storage.get(&U256::from(1)), Some(&(U256::ZERO, U256::from(2))), "Storage change from 0 to 2 on slot 1" ); } #[test] fn dao_hardfork_irregular_state_change() { let mut header = Header::default(); header.number = 1; 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 = ChainSpecBuilder::from(&*MAINNET) .homestead_activated() .with_fork(Hardfork::Dao, ForkKind::Block(1)) .build(); let mut db = SubState::new(State::new(db)); // execute chain and verify receipts let out = execute_and_verify_receipt(&header, &[], &[], &chain_spec, &mut db).unwrap(); assert_eq!(out.changesets.len(), 0, "No tx"); // Check if cache is set // beneficiary 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 block_reward = out.block_reward.unwrap(); let change_set = block_reward.get(&crate::eth_dao_fork::DAO_HARDFORK_BENEFICIARY).unwrap(); assert_eq!( *change_set, AccountInfoChangeSet::Changed { new: Account { balance: U256::from(beneficiary_balance), ..Default::default() }, old: Account { balance: U256::ZERO, ..Default::default() } } ); for (i, address) in crate::eth_dao_fork::DAO_HARDKFORK_ACCOUNTS.iter().enumerate() { let change_set = block_reward.get(address).unwrap(); assert_eq!( *change_set, AccountInfoChangeSet::Changed { new: Account { balance: U256::ZERO, ..Default::default() }, old: Account { balance: U256::from(i), ..Default::default() } } ); } } #[test] fn apply_account_info_changeset() { let db: Arc> = test_utils::create_test_db(EnvKind::RW); let address = H160::zero(); let tx_num = 0; let acc1 = Account { balance: U256::from(1), nonce: 2, bytecode_hash: Some(H256::zero()) }; let acc2 = Account { balance: U256::from(3), nonce: 4, bytecode_hash: Some(H256::zero()) }; let tx = db.tx_mut().unwrap(); // check Changed changeset AccountInfoChangeSet::Changed { new: acc1, old: acc2 } .apply_to_db(&tx, address, tx_num) .unwrap(); assert_eq!( tx.get::(tx_num), Ok(Some(AccountBeforeTx { address, info: Some(acc2) })) ); assert_eq!(tx.get::(address), Ok(Some(acc1))); AccountInfoChangeSet::Created { new: acc1 }.apply_to_db(&tx, address, tx_num).unwrap(); assert_eq!( tx.get::(tx_num), Ok(Some(AccountBeforeTx { address, info: None })) ); assert_eq!(tx.get::(address), Ok(Some(acc1))); // delete old value, as it is dupsorted tx.delete::(tx_num, None).unwrap(); AccountInfoChangeSet::Destroyed { old: acc2 }.apply_to_db(&tx, address, tx_num).unwrap(); assert_eq!(tx.get::(address), Ok(None)); assert_eq!( tx.get::(tx_num), Ok(Some(AccountBeforeTx { address, info: Some(acc2) })) ); } #[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 = SealedBlock::decode(&mut block_rlp).unwrap(); let mut db = StateProviderTest::default(); let address_caller = H160(hex!("a94f5374fce5edbc8e2a8697c15331677e6ebf0b")); let address_selfdestruct = H160(hex!("095e7baea6a6c7c4c2dfeb977efac326af552d87")); // 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.clone().into_iter().collect::>(), ); // spec at berlin fork let chain_spec = ChainSpecBuilder::mainnet().berlin_activated().build(); let mut db = SubState::new(State::new(db)); let transactions: Vec = block.body.iter().map(|tx| tx.try_ecrecovered().unwrap()).collect(); // execute chain and verify receipts let out = execute_and_verify_receipt(&block.header, &transactions, &[], &chain_spec, &mut db) .unwrap(); assert_eq!(out.changesets.len(), 1, "Should executed one transaction"); let changesets = out.changesets[0].clone(); assert_eq!(changesets.new_bytecodes.len(), 0, "Should have zero new bytecodes"); let post_account_caller = Account { balance: U256::from(0x0de0b6b3a761cf60u64), nonce: 0x01, bytecode_hash: None, }; assert_eq!( changesets.changeset.get(&address_caller).unwrap().account, AccountInfoChangeSet::Changed { new: post_account_caller, old: pre_account_caller }, "Caller account has changed and fee is deduced" ); let selfdestroyer_changeset = changesets.changeset.get(&address_selfdestruct).unwrap(); // check account assert_eq!( selfdestroyer_changeset.account, AccountInfoChangeSet::Destroyed { old: pre_account_selfdestroyed }, "Selfdestroyed account" ); assert_eq!(selfdestroyer_changeset.wipe_storage, true); } }