Files
reth/crates/executor/src/executor.rs
2023-01-27 10:49:54 -05:00

940 lines
39 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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::<tables::AccountChangeSet>(
tx_index,
AccountBeforeTx { address, info: Some(old) },
)?;
tx.put::<tables::PlainAccountState>(address, new)?;
}
AccountInfoChangeSet::Created { new } => {
tx.put::<tables::AccountChangeSet>(
tx_index,
AccountBeforeTx { address, info: None },
)?;
tx.put::<tables::PlainAccountState>(address, new)?;
}
AccountInfoChangeSet::Destroyed { old } => {
tx.delete::<tables::PlainAccountState>(address, None)?;
tx.put::<tables::AccountChangeSet>(
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<U256, (U256, U256)>,
/// 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<TransactionChangeSet>,
/// Block reward if present. It represent changeset for block reward slot in
/// [tables::AccountChangeSet] .
pub block_reward: Option<BTreeMap<Address, AccountInfoChangeSet>>,
}
/// 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: StateProvider>(
db: &mut SubState<DB>,
changes: hashbrown::HashMap<H160, RevmAccount>,
) -> (BTreeMap<Address, AccountChangeSet>, BTreeMap<H256, Bytecode>) {
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<Address, AccountChangeSet>,
/// new bytecode created as result of transaction execution.
pub new_bytecodes: BTreeMap<H256, Bytecode>,
}
/// Execute and verify block
pub fn execute_and_verify_receipt<DB: StateProvider>(
header: &Header,
transactions: &[TransactionSignedEcRecovered],
ommers: &[Header],
chain_spec: &ChainSpec,
db: &mut SubState<DB>,
) -> Result<ExecutionResult, Error> {
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<Item = &'a Receipt> + 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<DB: StateProvider>(
header: &Header,
transactions: &[TransactionSignedEcRecovered],
ommers: &[Header],
chain_spec: &ChainSpec,
db: &mut SubState<DB>,
) -> Result<ExecutionResult, Error> {
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 transactions gas limit, Tg, and the gas utilised in this block prior,
// must be no greater than the blocks 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<Log> = 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: StateProvider>(
db: &mut SubState<DB>,
) -> Result<BTreeMap<H160, AccountInfoChangeSet>, 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::<Result<BTreeMap<H160, AccountInfoChangeSet>, _>>()?;
// 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<DB: StateProvider>(
header: &Header,
ommers: &[Header],
db: &mut SubState<DB>,
chain_spec: &ChainSpec,
) -> Result<Option<BTreeMap<H160, AccountInfoChangeSet>>, 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 blocks beneficiary account by Rblock; for each ommer, we raise the
// blocks 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<H160, u128> = 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::<Result<BTreeMap<_, _>, _>>()
})
.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<Address, (HashMap<StorageKey, U256>, Account)>,
contracts: HashMap<H256, Bytes>,
block_hash: HashMap<U256, H256>,
}
impl StateProviderTest {
/// Insert account.
fn insert_account(
&mut self,
address: Address,
mut account: Account,
bytecode: Option<Bytes>,
storage: HashMap<StorageKey, U256>,
) {
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<Option<Account>> {
let ret = Ok(self.accounts.get(&address).map(|(_, acc)| *acc));
ret
}
}
impl BlockHashProvider for StateProviderTest {
fn block_hash(&self, number: U256) -> reth_interfaces::Result<Option<H256>> {
Ok(self.block_hash.get(&number).cloned())
}
}
impl StateProvider for StateProviderTest {
fn storage(
&self,
account: Address,
storage_key: reth_primitives::StorageKey,
) -> reth_interfaces::Result<Option<reth_primitives::StorageValue>> {
Ok(self
.accounts
.get(&account)
.and_then(|(storage, _)| storage.get(&storage_key).cloned()))
}
fn bytecode_by_hash(&self, code_hash: H256) -> reth_interfaces::Result<Option<Bytes>> {
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<TransactionSignedEcRecovered> =
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<Env<WriteMap>> = 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::<tables::AccountChangeSet>(tx_num),
Ok(Some(AccountBeforeTx { address, info: Some(acc2) }))
);
assert_eq!(tx.get::<tables::PlainAccountState>(address), Ok(Some(acc1)));
AccountInfoChangeSet::Created { new: acc1 }.apply_to_db(&tx, address, tx_num).unwrap();
assert_eq!(
tx.get::<tables::AccountChangeSet>(tx_num),
Ok(Some(AccountBeforeTx { address, info: None }))
);
assert_eq!(tx.get::<tables::PlainAccountState>(address), Ok(Some(acc1)));
// delete old value, as it is dupsorted
tx.delete::<tables::AccountChangeSet>(tx_num, None).unwrap();
AccountInfoChangeSet::Destroyed { old: acc2 }.apply_to_db(&tx, address, tx_num).unwrap();
assert_eq!(tx.get::<tables::PlainAccountState>(address), Ok(None));
assert_eq!(
tx.get::<tables::AccountChangeSet>(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::<HashMap<_, _>>(),
);
// spec at berlin fork
let chain_spec = ChainSpecBuilder::mainnet().berlin_activated().build();
let mut db = SubState::new(State::new(db));
let transactions: Vec<TransactionSignedEcRecovered> =
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);
}
}