Files
reth/crates/revm/src/executor.rs

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