use reth_db::{models::AccountBeforeTx, tables, transaction::DbTxMut, Error as DbError}; use reth_primitives::{Account, Address, Receipt, H256, U256}; use revm::primitives::Bytecode; use std::collections::BTreeMap; /// 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 tx_changesets: Vec, /// Post block account changesets. This might include block reward, uncle rewards, withdrawals /// or irregular state changes (DAO fork). pub block_changesets: BTreeMap, } /// 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, } /// 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, has_state_clear_eip: bool, ) -> 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 } => { // Ignore account that are created empty and state clear (SpuriousDragon) hardfork // is activated. if has_state_clear_eip && new.is_empty() { return Ok(()) } 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, } #[cfg(test)] mod tests { use std::sync::Arc; use reth_db::{ database::Database, mdbx::{test_utils, Env, EnvKind, WriteMap}, transaction::DbTx, }; use reth_primitives::H160; use super::*; #[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, true) .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, true) .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, true) .unwrap(); assert_eq!(tx.get::(address), Ok(None)); assert_eq!( tx.get::(tx_num), Ok(Some(AccountBeforeTx { address, info: Some(acc2) })) ); } }