Files
nanoreth/crates/storage/provider/src/post_state.rs
2023-04-12 21:36:20 +02:00

951 lines
36 KiB
Rust

//! Output of execution.
use reth_db::{
cursor::{DbCursorRO, DbCursorRW, DbDupCursorRO, DbDupCursorRW},
models::{AccountBeforeTx, TransitionIdAddress},
tables,
transaction::{DbTx, DbTxMut},
Error as DbError,
};
use reth_primitives::{
bloom::logs_bloom, proofs::calculate_receipt_root_ref, Account, Address, Bloom, Bytecode, Log,
Receipt, StorageEntry, TransitionId, H256, U256,
};
use std::collections::BTreeMap;
/// Storage for an account.
///
/// # Wiped Storage
///
/// The field `wiped` denotes whether the pre-existing storage in the database should be cleared or
/// not.
///
/// If `wiped` is true, then the account was selfdestructed at some point, and the values contained
/// in `storage` should be the only values written to the database.
#[derive(Debug, Default, Clone, Eq, PartialEq)]
pub struct Storage {
/// Whether the storage was wiped or not.
pub wiped: bool,
/// The storage slots.
pub storage: BTreeMap<U256, U256>,
}
/// Storage for an account with the old and new values for each slot.
/// TODO: Do we actually need (old, new) anymore, or is (old) sufficient? (Check the writes)
/// If we don't, we can unify this and [Storage].
pub type StorageChangeset = BTreeMap<U256, (U256, U256)>;
/// A change to the state of accounts or storage.
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum Change {
/// A new account was created.
AccountCreated {
/// The ID of the transition this change is a part of.
id: TransitionId,
/// The address of the account that was created.
address: Address,
/// The account.
account: Account,
},
/// An existing account was changed.
AccountChanged {
/// The ID of the transition this change is a part of.
id: TransitionId,
/// The address of the account that was changed.
address: Address,
/// The account before the change.
old: Account,
/// The account after the change.
new: Account,
},
/// Storage slots for an account were changed.
StorageChanged {
/// The ID of the transition this change is a part of.
id: TransitionId,
/// The address of the account associated with the storage slots.
address: Address,
/// The storage changeset.
changeset: StorageChangeset,
},
/// Storage was wiped
StorageWiped {
/// The ID of the transition this change is a part of.
id: TransitionId,
/// The address of the account whose storage was wiped.
address: Address,
},
/// An account was destroyed.
///
/// This removes all of the information associated with the account. An accompanying
/// [Change::StorageWiped] will also be present to mark the deletion of storage.
///
/// If a change to an account satisfies the conditions for EIP-158, this change variant is also
/// applied instead of the change that would otherwise have happened.
AccountDestroyed {
/// The ID of the transition this change is a part of.
id: TransitionId,
/// The address of the destroyed account.
address: Address,
/// The account before it was destroyed.
old: Account,
},
}
impl Change {
/// Get the transition ID for the change
pub fn transition_id(&self) -> TransitionId {
match self {
Change::AccountChanged { id, .. } |
Change::AccountCreated { id, .. } |
Change::StorageChanged { id, .. } |
Change::StorageWiped { id, .. } |
Change::AccountDestroyed { id, .. } => *id,
}
}
/// Get the address of the account this change operates on.
pub fn address(&self) -> Address {
match self {
Change::AccountChanged { address, .. } |
Change::AccountCreated { address, .. } |
Change::StorageChanged { address, .. } |
Change::StorageWiped { address, .. } |
Change::AccountDestroyed { address, .. } => *address,
}
}
/// Set the transition ID of this change.
pub fn set_transition_id(&mut self, new_id: TransitionId) {
match self {
Change::AccountChanged { ref mut id, .. } |
Change::AccountCreated { ref mut id, .. } |
Change::StorageChanged { ref mut id, .. } |
Change::StorageWiped { ref mut id, .. } |
Change::AccountDestroyed { ref mut id, .. } => {
*id = new_id;
}
}
}
}
/// The state of accounts after execution of one or more transactions, including receipts and new
/// bytecode.
///
/// The latest state can be found in `accounts`, `storage`, and `bytecode`. The receipts for the
/// transactions that lead to these changes can be found in `receipts`, and each change leading to
/// this state can be found in `changes`.
///
/// # Wiped Storage
///
/// The [Storage] type has a field, `wiped` which denotes whether the pre-existing storage in the
/// database should be cleared or not.
///
/// If `wiped` is true, then the account was selfdestructed at some point, and the values contained
/// in `storage` should be the only values written to the database.
///
/// # Transitions
///
/// Each [Change] has an `id` field that marks what transition it is part of. Each transaction is
/// its own transition, but there may be 0 or 1 transitions associated with the block.
///
/// The block level transition includes:
///
/// - Block rewards
/// - Ommer rewards
/// - Withdrawals
/// - The irregular state change for the DAO hardfork
///
/// [PostState::finish_transition] *must* be called after every transaction, and after every block.
///
/// The first transaction executed and added to the [PostState] has a transition ID of 0, the next
/// one a transition ID of 1, and so on. If the [PostState] is for a single block, and the number of
/// transitions ([PostState::transitions_count]) is greater than the number of transactions in the
/// block, then the last transition is the block transition.
///
/// For multi-block [PostState]s it is not possible to figure out what transition ID maps on to a
/// transaction or a block.
///
/// # Shaving Allocations
///
/// Since most [PostState]s in reth are for multiple blocks it is better to pre-allocate capacity
/// for receipts and changes, which [PostState::new] does, and thus it (or
/// [PostState::with_tx_capacity]) should be preferred to using the [Default] implementation.
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct PostState {
/// The ID of the current transition.
current_transition_id: TransitionId,
/// The state of all modified accounts after execution.
///
/// If the value contained is `None`, then the account should be deleted.
accounts: BTreeMap<Address, Option<Account>>,
/// The state of all modified storage after execution
///
/// If the contained [Storage] is marked as wiped, then all storage values should be cleared
/// from the database.
storage: BTreeMap<Address, Storage>,
/// The changes to state that happened during execution
changes: Vec<Change>,
/// New code created during the execution
bytecode: BTreeMap<H256, Bytecode>,
/// The receipt(s) of the executed transaction(s).
receipts: Vec<Receipt>,
}
/// Used to determine preallocation sizes of [PostState]'s internal [Vec]s. It denotes the number of
/// best-guess changes each transaction causes to state.
const BEST_GUESS_CHANGES_PER_TX: usize = 8;
/// How many [Change]s to preallocate for in [PostState].
///
/// This is just a guesstimate based on:
///
/// - Each block having ~200-300 transactions
/// - Each transaction having some amount of changes
const PREALLOC_CHANGES_SIZE: usize = 256 * BEST_GUESS_CHANGES_PER_TX;
impl PostState {
/// Create an empty [PostState].
pub fn new() -> Self {
Self::default()
}
/// Create an empty [PostState] with pre-allocated space for a certain amount of transactions.
pub fn with_tx_capacity(txs: usize) -> Self {
Self {
changes: Vec::with_capacity(txs * BEST_GUESS_CHANGES_PER_TX),
receipts: Vec::with_capacity(txs),
..Default::default()
}
}
/// Get the latest state of accounts.
pub fn accounts(&self) -> &BTreeMap<Address, Option<Account>> {
&self.accounts
}
/// Get the latest state for a specific account.
///
/// # Returns
///
/// - `None` if the account does not exist
/// - `Some(&None)` if the account existed, but has since been deleted.
/// - `Some(..)` if the account currently exists
pub fn account(&self, address: &Address) -> Option<&Option<Account>> {
self.accounts.get(address)
}
/// Get the latest state of storage.
pub fn storage(&self) -> &BTreeMap<Address, Storage> {
&self.storage
}
/// Get the storage for an account.
pub fn account_storage(&self, address: &Address) -> Option<&Storage> {
self.storage.get(address)
}
/// Get the changes causing this [PostState].
pub fn changes(&self) -> &[Change] {
&self.changes
}
/// Get the newly created bytecodes
pub fn bytecodes(&self) -> &BTreeMap<H256, Bytecode> {
&self.bytecode
}
/// Get a bytecode in the post-state.
pub fn bytecode(&self, code_hash: &H256) -> Option<&Bytecode> {
self.bytecode.get(code_hash)
}
/// Get the receipts for the transactions executed to form this [PostState].
pub fn receipts(&self) -> &[Receipt] {
&self.receipts
}
/// Returns an iterator over all logs in this [PostState].
pub fn logs(&self) -> impl Iterator<Item = &Log> + '_ {
self.receipts().iter().flat_map(|r| r.logs.iter())
}
/// Returns the logs bloom for all recorded logs.
pub fn logs_bloom(&self) -> Bloom {
logs_bloom(self.logs())
}
/// Returns the receipt root for all recorded receipts.
pub fn receipts_root(&self) -> H256 {
calculate_receipt_root_ref(self.receipts().iter().map(Into::into))
}
/// Get the number of transitions causing this [PostState]
pub fn transitions_count(&self) -> TransitionId {
self.current_transition_id
}
/// Extend this [PostState] with the changes in another [PostState].
pub fn extend(&mut self, other: PostState) {
if other.changes.is_empty() {
return
}
self.changes.reserve(other.changes.len());
let mut next_transition_id = self.current_transition_id;
for mut change in other.changes.into_iter() {
next_transition_id = self.current_transition_id + change.transition_id();
change.set_transition_id(next_transition_id);
self.add_and_apply(change);
}
self.receipts.extend(other.receipts);
self.bytecode.extend(other.bytecode);
self.current_transition_id = next_transition_id + 1;
}
/// Reverts each change up to and including any change that is part of `transition_id`.
///
/// The reverted changes are removed from this post-state, and their effects are reverted.
///
/// The reverted changes are returned.
pub fn revert_to(&mut self, transition_id: TransitionId) -> Vec<Change> {
let mut changes_to_revert = Vec::new();
self.changes.retain(|change| {
if change.transition_id() >= transition_id {
changes_to_revert.push(change.clone());
false
} else {
true
}
});
for change in changes_to_revert.iter_mut().rev() {
change.set_transition_id(change.transition_id() - transition_id as TransitionId);
self.revert(change.clone());
}
self.current_transition_id = transition_id as TransitionId;
changes_to_revert
}
/// Reverts each change up to and including any change that is part of `transition_id`.
///
/// The reverted changes are removed from this post-state, and their effects are reverted.
///
/// A new post-state containing the pre-revert state, as well as the reverted changes *only* is
/// returned.
///
/// This effectively splits the post state in two:
///
/// 1. This post-state has the changes reverted
/// 2. The returned post-state does *not* have the changes reverted, but only contains the
/// descriptions of the changes that were reverted in the first post-state.
pub fn split_at(&mut self, transition_id: TransitionId) -> Self {
// Clone ourselves
let mut non_reverted_state = self.clone();
// Revert the desired changes
let reverted_changes = self.revert_to(transition_id);
// Compute the new `current_transition_id` for `non_reverted_state`.
let new_transition_id =
reverted_changes.last().map(|c| c.transition_id()).unwrap_or_default();
non_reverted_state.changes = reverted_changes;
non_reverted_state.current_transition_id = new_transition_id + 1;
non_reverted_state
}
/// Add a newly created account to the post-state.
pub fn create_account(&mut self, address: Address, account: Account) {
self.add_and_apply(Change::AccountCreated {
id: self.current_transition_id,
address,
account,
});
}
/// Add a changed account to the post-state.
///
/// If the account also has changed storage values, [PostState::change_storage] should also be
/// called.
pub fn change_account(&mut self, address: Address, old: Account, new: Account) {
self.add_and_apply(Change::AccountChanged {
id: self.current_transition_id,
address,
old,
new,
});
}
/// Mark an account as destroyed.
pub fn destroy_account(&mut self, address: Address, account: Account) {
self.add_and_apply(Change::AccountDestroyed {
id: self.current_transition_id,
address,
old: account,
});
self.add_and_apply(Change::StorageWiped { id: self.current_transition_id, address });
}
/// Add changed storage values to the post-state.
pub fn change_storage(&mut self, address: Address, changeset: StorageChangeset) {
self.add_and_apply(Change::StorageChanged {
id: self.current_transition_id,
address,
changeset,
});
}
/// Add new bytecode to the post-state.
pub fn add_bytecode(&mut self, code_hash: H256, bytecode: Bytecode) {
// TODO: Is this faster than just doing `.insert`?
// Assumption: `insert` will override the value if present, but since the code hash for a
// given bytecode will always be the same, we are overriding with the same value.
//
// In other words: if this entry already exists, replacing the bytecode will replace with
// the same value, which is wasteful.
self.bytecode.entry(code_hash).or_insert(bytecode);
}
/// Add a transaction receipt to the post-state.
///
/// Transactions should always include their receipts in the post-state.
pub fn add_receipt(&mut self, receipt: Receipt) {
self.receipts.push(receipt);
}
/// Mark all prior changes as being part of one transition, and start a new one.
pub fn finish_transition(&mut self) {
self.current_transition_id += 1;
}
/// Add a new change, and apply its transformations to the current state
pub fn add_and_apply(&mut self, change: Change) {
match &change {
Change::AccountCreated { address, account, .. } |
Change::AccountChanged { address, new: account, .. } => {
self.accounts.insert(*address, Some(*account));
}
Change::AccountDestroyed { address, .. } => {
self.accounts.insert(*address, None);
}
Change::StorageChanged { address, changeset, .. } => {
let storage = self.storage.entry(*address).or_default();
for (slot, (_, current_value)) in changeset {
storage.storage.insert(*slot, *current_value);
}
}
Change::StorageWiped { address, .. } => {
let storage = self.storage.entry(*address).or_default();
storage.wiped = true;
storage.storage.clear();
}
}
self.changes.push(change);
}
/// Revert a change, applying the inverse of its transformations to the current state.
fn revert(&mut self, change: Change) {
match &change {
Change::AccountCreated { address, .. } => {
self.accounts.remove(address);
}
Change::AccountChanged { address, old, .. } => {
self.accounts.insert(*address, Some(*old));
}
Change::AccountDestroyed { address, old, .. } => {
self.accounts.insert(*address, Some(*old));
}
Change::StorageChanged { address, changeset, .. } => {
let storage = self.storage.entry(*address).or_default();
for (slot, (old_value, _)) in changeset {
storage.storage.insert(*slot, *old_value);
}
}
Change::StorageWiped { address, .. } => {
let storage = self.storage.entry(*address).or_default();
storage.wiped = false;
}
}
}
/// Write the post state to the database.
pub fn write_to_db<'a, TX: DbTxMut<'a> + DbTx<'a>>(
mut self,
tx: &TX,
first_transition_id: TransitionId,
) -> Result<(), DbError> {
// Collect and sort changesets by their key to improve write performance
let mut changesets = std::mem::take(&mut self.changes);
changesets
.sort_unstable_by_key(|changeset| (changeset.transition_id(), changeset.address()));
// Partition changesets into account and storage changes
let (account_changes, storage_changes): (Vec<Change>, Vec<Change>) =
changesets.into_iter().partition(|changeset| {
matches!(
changeset,
Change::AccountChanged { .. } |
Change::AccountCreated { .. } |
Change::AccountDestroyed { .. }
)
});
// Write account changes
tracing::trace!(target: "provider::post_state", len = account_changes.len(), "Writing account changes");
let mut account_changeset_cursor = tx.cursor_dup_write::<tables::AccountChangeSet>()?;
for changeset in account_changes.into_iter() {
match changeset {
Change::AccountDestroyed { id, address, old } |
Change::AccountChanged { id, address, old, .. } => {
let destroyed = matches!(changeset, Change::AccountDestroyed { .. });
let transition_id = first_transition_id + id;
tracing::trace!(target: "provider::post_state", transition_id, ?address, ?old, destroyed, "Account changed");
account_changeset_cursor
.append_dup(transition_id, AccountBeforeTx { address, info: Some(old) })?;
}
Change::AccountCreated { id, address, .. } => {
let transition_id = first_transition_id + id;
tracing::trace!(target: "provider::post_state", transition_id, ?address, "Account created");
account_changeset_cursor
.append_dup(transition_id, AccountBeforeTx { address, info: None })?;
}
_ => unreachable!(),
}
}
// Write storage changes
tracing::trace!(target: "provider::post_state", len = storage_changes.len(), "Writing storage changes");
let mut storages_cursor = tx.cursor_dup_write::<tables::PlainStorageState>()?;
let mut storage_changeset_cursor = tx.cursor_dup_write::<tables::StorageChangeSet>()?;
for changeset in storage_changes.into_iter() {
match changeset {
Change::StorageChanged { id, address, changeset } => {
let storage_id = TransitionIdAddress((first_transition_id + id, address));
for (key, (old_value, _)) in changeset {
tracing::trace!(target: "provider::post_state", ?storage_id, ?key, ?old_value, "Storage changed");
storage_changeset_cursor.append_dup(
storage_id,
StorageEntry { key: H256(key.to_be_bytes()), value: old_value },
)?;
}
}
Change::StorageWiped { id, address } => {
let storage_id = TransitionIdAddress((first_transition_id + id, address));
if let Some((_, entry)) = storages_cursor.seek_exact(address)? {
tracing::trace!(target: "provider::post_state", ?storage_id, key = ?entry.key, "Storage wiped");
storage_changeset_cursor.append_dup(storage_id, entry)?;
while let Some(entry) = storages_cursor.next_dup_val()? {
storage_changeset_cursor.append_dup(storage_id, entry)?;
}
}
}
_ => unreachable!(),
}
}
// Write new storage state
for (address, storage) in self.storage.into_iter() {
// If the storage was wiped, remove all previous entries from the database.
if storage.wiped {
tracing::trace!(target: "provider::post_state", ?address, "Wiping storage from plain state");
if storages_cursor.seek_exact(address)?.is_some() {
storages_cursor.delete_current_duplicates()?;
}
}
for (key, value) in storage.storage {
tracing::trace!(target: "provider::post_state", ?address, ?key, "Updating plain state storage");
let key = H256(key.to_be_bytes());
if let Some(entry) = storages_cursor.seek_by_key_subkey(address, key)? {
if entry.key == key {
storages_cursor.delete_current()?;
}
}
if value != U256::ZERO {
storages_cursor.upsert(address, StorageEntry { key, value })?;
}
}
}
// Write new account state
tracing::trace!(target: "provider::post_state", len = self.accounts.len(), "Writing new account state");
let mut accounts_cursor = tx.cursor_write::<tables::PlainAccountState>()?;
for (address, account) in self.accounts.into_iter() {
if let Some(account) = account {
tracing::trace!(target: "provider::post_state", ?address, "Updating plain state account");
accounts_cursor.upsert(address, account)?;
} else if accounts_cursor.seek_exact(address)?.is_some() {
tracing::trace!(target: "provider::post_state", ?address, "Deleting plain state account");
accounts_cursor.delete_current()?;
}
}
// Write bytecode
tracing::trace!(target: "provider::post_state", len = self.bytecode.len(), "Writing bytecods");
let mut bytecodes_cursor = tx.cursor_write::<tables::Bytecodes>()?;
for (hash, bytecode) in self.bytecode.into_iter() {
bytecodes_cursor.upsert(hash, bytecode)?;
}
// write the receipts of the transactions
let mut receipts_cursor = tx.cursor_write::<tables::Receipts>()?;
let mut next_tx_num =
if let Some(last_tx) = receipts_cursor.last()?.map(|(tx_num, _)| tx_num) {
last_tx + 1
} else {
// the very first tx
0
};
for receipt in self.receipts.into_iter() {
receipts_cursor.append(next_tx_num, receipt)?;
next_tx_num += 1;
}
Ok(())
}
}
impl Default for PostState {
fn default() -> Self {
Self {
current_transition_id: 0,
accounts: Default::default(),
storage: Default::default(),
changes: Vec::with_capacity(PREALLOC_CHANGES_SIZE),
bytecode: Default::default(),
receipts: vec![],
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use reth_db::{
database::Database,
mdbx::{test_utils, Env, EnvKind, WriteMap},
transaction::DbTx,
};
use std::sync::Arc;
// Ensure that the transition id is not incremented if postate is extended by another empty
// poststate.
#[test]
fn extend_empty() {
let mut a = PostState::new();
assert_eq!(a.current_transition_id, 0);
// Extend empty poststate with another empty poststate
a.extend(PostState::new());
assert_eq!(a.current_transition_id, 0);
// Add single transition and extend with empty poststate
a.create_account(Address::zero(), Account::default());
a.finish_transition();
let transition_id = a.current_transition_id;
a.extend(PostState::new());
assert_eq!(a.current_transition_id, transition_id);
}
#[test]
fn extend() {
let mut a = PostState::new();
a.create_account(Address::zero(), Account::default());
a.destroy_account(Address::zero(), Account::default());
a.finish_transition();
assert_eq!(a.transitions_count(), 1);
assert_eq!(a.changes().len(), 3);
let mut b = PostState::new();
b.create_account(Address::repeat_byte(0xff), Account::default());
b.finish_transition();
assert_eq!(b.transitions_count(), 1);
assert_eq!(b.changes.len(), 1);
let mut c = a.clone();
c.extend(b.clone());
assert_eq!(c.transitions_count(), 2);
assert_eq!(c.changes.len(), a.changes.len() + b.changes.len());
}
#[test]
fn write_to_db_account_info() {
let db: Arc<Env<WriteMap>> = test_utils::create_test_db(EnvKind::RW);
let tx = db.tx_mut().expect("Could not get database tx");
let mut post_state = PostState::new();
let address_a = Address::zero();
let address_b = Address::repeat_byte(0xff);
let account_a = Account { balance: U256::from(1), nonce: 1, bytecode_hash: None };
let account_b = Account { balance: U256::from(2), nonce: 2, bytecode_hash: None };
let account_b_changed = Account { balance: U256::from(3), nonce: 3, bytecode_hash: None };
// 0x00.. is created
post_state.create_account(address_a, account_a);
// 0x11.. is changed (balance + 1, nonce + 1)
post_state.change_account(address_b, account_b, account_b_changed);
post_state.write_to_db(&tx, 0).expect("Could not write post state to DB");
// Check plain state
assert_eq!(
tx.get::<tables::PlainAccountState>(address_a).expect("Could not read account state"),
Some(account_a),
"Account A state is wrong"
);
assert_eq!(
tx.get::<tables::PlainAccountState>(address_b).expect("Could not read account state"),
Some(account_b_changed),
"Account B state is wrong"
);
// Check change set
let mut changeset_cursor = tx
.cursor_dup_read::<tables::AccountChangeSet>()
.expect("Could not open changeset cursor");
assert_eq!(
changeset_cursor.seek_exact(0).expect("Could not read account change set"),
Some((0, AccountBeforeTx { address: address_a, info: None })),
"Account A changeset is wrong"
);
assert_eq!(
changeset_cursor.next_dup().expect("Changeset table is malformed"),
Some((0, AccountBeforeTx { address: address_b, info: Some(account_b) })),
"Account B changeset is wrong"
);
let mut post_state = PostState::new();
// 0x11.. is destroyed
post_state.destroy_account(address_b, account_b_changed);
post_state.write_to_db(&tx, 1).expect("Could not write second post state to DB");
// Check new plain state for account B
assert_eq!(
tx.get::<tables::PlainAccountState>(address_b).expect("Could not read account state"),
None,
"Account B should be deleted"
);
// Check change set
assert_eq!(
changeset_cursor.seek_exact(1).expect("Could not read account change set"),
Some((1, AccountBeforeTx { address: address_b, info: Some(account_b_changed) })),
"Account B changeset is wrong after deletion"
);
}
#[test]
fn write_to_db_storage() {
let db: Arc<Env<WriteMap>> = test_utils::create_test_db(EnvKind::RW);
let tx = db.tx_mut().expect("Could not get database tx");
let mut post_state = PostState::new();
let address_a = Address::zero();
let address_b = Address::repeat_byte(0xff);
// 0x00 => 0 => 1
// 0x01 => 0 => 2
let storage_a_changeset = BTreeMap::from([
(U256::from(0), (U256::from(0), U256::from(1))),
(U256::from(1), (U256::from(0), U256::from(2))),
]);
// 0x01 => 1 => 2
let storage_b_changeset = BTreeMap::from([(U256::from(1), (U256::from(1), U256::from(2)))]);
post_state.change_storage(address_a, storage_a_changeset);
post_state.change_storage(address_b, storage_b_changeset);
post_state.write_to_db(&tx, 0).expect("Could not write post state to DB");
// Check plain storage state
let mut storage_cursor = tx
.cursor_dup_read::<tables::PlainStorageState>()
.expect("Could not open plain storage state cursor");
assert_eq!(
storage_cursor.seek_exact(address_a).unwrap(),
Some((address_a, StorageEntry { key: H256::zero(), value: U256::from(1) })),
"Slot 0 for account A should be 1"
);
assert_eq!(
storage_cursor.next_dup().unwrap(),
Some((
address_a,
StorageEntry { key: H256::from(U256::from(1).to_be_bytes()), value: U256::from(2) }
)),
"Slot 1 for account A should be 2"
);
assert_eq!(
storage_cursor.next_dup().unwrap(),
None,
"Account A should only have 2 storage slots"
);
assert_eq!(
storage_cursor.seek_exact(address_b).unwrap(),
Some((
address_b,
StorageEntry { key: H256::from(U256::from(1).to_be_bytes()), value: U256::from(2) }
)),
"Slot 1 for account B should be 2"
);
assert_eq!(
storage_cursor.next_dup().unwrap(),
None,
"Account B should only have 1 storage slot"
);
// Check change set
let mut changeset_cursor = tx
.cursor_dup_read::<tables::StorageChangeSet>()
.expect("Could not open storage changeset cursor");
assert_eq!(
changeset_cursor.seek_exact(TransitionIdAddress((0, address_a))).unwrap(),
Some((
TransitionIdAddress((0, address_a)),
StorageEntry { key: H256::zero(), value: U256::from(0) }
)),
"Slot 0 for account A should have changed from 0"
);
assert_eq!(
changeset_cursor.next_dup().unwrap(),
Some((
TransitionIdAddress((0, address_a)),
StorageEntry { key: H256::from(U256::from(1).to_be_bytes()), value: U256::from(0) }
)),
"Slot 1 for account A should have changed from 0"
);
assert_eq!(
changeset_cursor.next_dup().unwrap(),
None,
"Account A should only be in the changeset 2 times"
);
assert_eq!(
changeset_cursor.seek_exact(TransitionIdAddress((0, address_b))).unwrap(),
Some((
TransitionIdAddress((0, address_b)),
StorageEntry { key: H256::from(U256::from(1).to_be_bytes()), value: U256::from(1) }
)),
"Slot 1 for account B should have changed from 1"
);
assert_eq!(
changeset_cursor.next_dup().unwrap(),
None,
"Account B should only be in the changeset 1 time"
);
// Delete account A
let mut post_state = PostState::new();
post_state.destroy_account(address_a, Account::default());
post_state.write_to_db(&tx, 1).expect("Could not write post state to DB");
assert_eq!(
storage_cursor.seek_exact(address_a).unwrap(),
None,
"Account A should have no storage slots after deletion"
);
assert_eq!(
changeset_cursor.seek_exact(TransitionIdAddress((1, address_a))).unwrap(),
Some((
TransitionIdAddress((1, address_a)),
StorageEntry { key: H256::zero(), value: U256::from(1) }
)),
"Slot 0 for account A should have changed from 1 on deletion"
);
assert_eq!(
changeset_cursor.next_dup().unwrap(),
Some((
TransitionIdAddress((1, address_a)),
StorageEntry { key: H256::from(U256::from(1).to_be_bytes()), value: U256::from(2) }
)),
"Slot 1 for account A should have changed from 2 on deletion"
);
assert_eq!(
changeset_cursor.next_dup().unwrap(),
None,
"Account A should only be in the changeset 2 times on deletion"
);
}
#[test]
fn reuse_selfdestructed_account() {
let address_a = Address::zero();
// 0x00 => 0 => 1
// 0x01 => 0 => 2
// 0x03 => 0 => 3
let storage_changeset_one = BTreeMap::from([
(U256::from(0), (U256::from(0), U256::from(1))),
(U256::from(1), (U256::from(0), U256::from(2))),
(U256::from(3), (U256::from(0), U256::from(3))),
]);
// 0x00 => 0 => 3
// 0x01 => 0 => 4
let storage_changeset_two = BTreeMap::from([
(U256::from(0), (U256::from(0), U256::from(3))),
(U256::from(2), (U256::from(0), U256::from(4))),
]);
let mut state = PostState::new();
// Create some storage for account A (simulates a contract deployment)
state.change_storage(address_a, storage_changeset_one);
state.finish_transition();
// Next transition destroys the account (selfdestruct)
state.destroy_account(address_a, Account::default());
state.finish_transition();
// Next transition recreates account A with some storage (simulates a contract deployment)
state.change_storage(address_a, storage_changeset_two);
state.finish_transition();
// All the storage of account A has to be deleted in the database (wiped)
assert!(
state.account_storage(&address_a).expect("Account A should have some storage").wiped,
"The wiped flag should be set to discard all pre-existing storage from the database"
);
// Then, we must ensure that *only* the storage from the last transition will be written
assert_eq!(
state.account_storage(&address_a).expect("Account A should have some storage").storage,
BTreeMap::from([(U256::from(0), U256::from(3)), (U256::from(2), U256::from(4))]),
"Account A's storage should only have slots 0 and 2, and they should have values 3 and 4, respectively."
);
}
#[test]
fn revert_to() {
let mut state = PostState::new();
state.create_account(
Address::repeat_byte(0),
Account { nonce: 1, balance: U256::from(1), bytecode_hash: None },
);
state.finish_transition();
let revert_to = state.current_transition_id;
state.create_account(
Address::repeat_byte(0xff),
Account { nonce: 2, balance: U256::from(2), bytecode_hash: None },
);
state.finish_transition();
assert_eq!(state.transitions_count(), 2);
assert_eq!(state.accounts().len(), 2);
let reverted_changes = state.revert_to(revert_to);
assert_eq!(state.accounts().len(), 1);
assert_eq!(state.transitions_count(), 1);
assert_eq!(reverted_changes.len(), 1);
}
}