Files
reth/crates/executor/src/execution_result.rs
Roman Krasiuk e97753c768 feat: withdrawals (#1322)
Co-authored-by: rakita <rakita@users.noreply.github.com>
2023-02-16 04:44:05 -08:00

172 lines
6.4 KiB
Rust

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<TransactionChangeSet>,
/// Post block account changesets. This might include block reward, uncle rewards, withdrawals
/// or irregular state changes (DAO fork).
pub block_changesets: BTreeMap<Address, AccountInfoChangeSet>,
}
/// 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>,
}
/// 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::<tables::AccountChangeSet>(
tx_index,
AccountBeforeTx { address, info: Some(old) },
)?;
tx.put::<tables::PlainAccountState>(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::<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,
}
#[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<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, true)
.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, true)
.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, true)
.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) }))
);
}
}