mirror of
https://github.com/paradigmxyz/reth.git
synced 2026-02-16 09:56:17 -05:00
1079 lines
39 KiB
Rust
1079 lines
39 KiB
Rust
use crate::{
|
|
providers::StaticFileProviderRWRefMut, DatabaseProviderRW, StateChanges, StateReverts,
|
|
StateWriter,
|
|
};
|
|
use reth_db::{tables, Database};
|
|
use reth_db_api::{
|
|
cursor::{DbCursorRO, DbCursorRW},
|
|
transaction::{DbTx, DbTxMut},
|
|
};
|
|
pub use reth_execution_types::*;
|
|
use reth_primitives::StaticFileSegment;
|
|
use reth_storage_errors::provider::{ProviderError, ProviderResult};
|
|
pub use revm::db::states::OriginalValuesKnown;
|
|
|
|
impl StateWriter for ExecutionOutcome {
|
|
fn write_to_storage<DB>(
|
|
self,
|
|
provider_rw: &DatabaseProviderRW<DB>,
|
|
mut static_file_producer: Option<StaticFileProviderRWRefMut<'_>>,
|
|
is_value_known: OriginalValuesKnown,
|
|
) -> ProviderResult<()>
|
|
where
|
|
DB: Database,
|
|
{
|
|
let tx = provider_rw.tx_ref();
|
|
let (plain_state, reverts) = self.bundle.into_plain_state_and_reverts(is_value_known);
|
|
|
|
StateReverts(reverts).write_to_db(tx, self.first_block)?;
|
|
|
|
// write receipts
|
|
let mut bodies_cursor = tx.cursor_read::<tables::BlockBodyIndices>()?;
|
|
let mut receipts_cursor = tx.cursor_write::<tables::Receipts>()?;
|
|
|
|
// ATTENTION: Any potential future refactor or change to how this loop works should keep in
|
|
// mind that the static file producer must always call `increment_block` even if the block
|
|
// has no receipts. Keeping track of the exact block range of the segment is needed for
|
|
// consistency, querying and file range segmentation.
|
|
let blocks = self.receipts.into_iter().enumerate();
|
|
for (idx, receipts) in blocks {
|
|
let block_number = self.first_block + idx as u64;
|
|
let first_tx_index = bodies_cursor
|
|
.seek_exact(block_number)?
|
|
.map(|(_, indices)| indices.first_tx_num())
|
|
.ok_or_else(|| ProviderError::BlockBodyIndicesNotFound(block_number))?;
|
|
|
|
if let Some(static_file_producer) = &mut static_file_producer {
|
|
// Increment block on static file header.
|
|
static_file_producer.increment_block(StaticFileSegment::Receipts, block_number)?;
|
|
let receipts = receipts.into_iter().enumerate().map(|(tx_idx, receipt)| {
|
|
Ok((
|
|
first_tx_index + tx_idx as u64,
|
|
receipt
|
|
.expect("receipt should not be filtered when saving to static files."),
|
|
))
|
|
});
|
|
static_file_producer.append_receipts(receipts)?;
|
|
} else if !receipts.is_empty() {
|
|
for (tx_idx, receipt) in receipts.into_iter().enumerate() {
|
|
if let Some(receipt) = receipt {
|
|
receipts_cursor.append(first_tx_index + tx_idx as u64, receipt)?;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
StateChanges(plain_state).write_to_db(tx)?;
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use crate::{test_utils::create_test_provider_factory, AccountReader};
|
|
use reth_db::test_utils::create_test_rw_db;
|
|
use reth_db_api::{
|
|
cursor::DbDupCursorRO,
|
|
database::Database,
|
|
models::{AccountBeforeTx, BlockNumberAddress},
|
|
};
|
|
use reth_primitives::{
|
|
keccak256, Account, Address, Receipt, Receipts, StorageEntry, B256, U256,
|
|
};
|
|
use reth_trie::{test_utils::state_root, StateRoot};
|
|
use revm::{
|
|
db::{
|
|
states::{
|
|
bundle_state::BundleRetention, changes::PlainStorageRevert, PlainStorageChangeset,
|
|
},
|
|
BundleState, EmptyDB,
|
|
},
|
|
primitives::{
|
|
Account as RevmAccount, AccountInfo as RevmAccountInfo, AccountStatus, EvmStorageSlot,
|
|
},
|
|
DatabaseCommit, State,
|
|
};
|
|
use std::collections::{BTreeMap, HashMap};
|
|
|
|
#[test]
|
|
fn write_to_db_account_info() {
|
|
let factory = create_test_provider_factory();
|
|
let provider = factory.provider_rw().unwrap();
|
|
|
|
let address_a = Address::ZERO;
|
|
let address_b = Address::repeat_byte(0xff);
|
|
|
|
let account_a = RevmAccountInfo { balance: U256::from(1), nonce: 1, ..Default::default() };
|
|
let account_b = RevmAccountInfo { balance: U256::from(2), nonce: 2, ..Default::default() };
|
|
let account_b_changed =
|
|
RevmAccountInfo { balance: U256::from(3), nonce: 3, ..Default::default() };
|
|
|
|
let mut state = State::builder().with_bundle_update().build();
|
|
state.insert_not_existing(address_a);
|
|
state.insert_account(address_b, account_b.clone());
|
|
|
|
// 0x00.. is created
|
|
state.commit(HashMap::from([(
|
|
address_a,
|
|
RevmAccount {
|
|
info: account_a.clone(),
|
|
status: AccountStatus::Touched | AccountStatus::Created,
|
|
storage: HashMap::default(),
|
|
},
|
|
)]));
|
|
|
|
// 0xff.. is changed (balance + 1, nonce + 1)
|
|
state.commit(HashMap::from([(
|
|
address_b,
|
|
RevmAccount {
|
|
info: account_b_changed.clone(),
|
|
status: AccountStatus::Touched,
|
|
storage: HashMap::default(),
|
|
},
|
|
)]));
|
|
|
|
state.merge_transitions(BundleRetention::Reverts);
|
|
let mut revm_bundle_state = state.take_bundle();
|
|
|
|
// Write plain state and reverts separately.
|
|
let reverts = revm_bundle_state.take_all_reverts().into_plain_state_reverts();
|
|
let plain_state = revm_bundle_state.into_plain_state(OriginalValuesKnown::Yes);
|
|
assert!(plain_state.storage.is_empty());
|
|
assert!(plain_state.contracts.is_empty());
|
|
StateChanges(plain_state)
|
|
.write_to_db(provider.tx_ref())
|
|
.expect("Could not write plain state to DB");
|
|
|
|
assert_eq!(reverts.storage, [[]]);
|
|
StateReverts(reverts)
|
|
.write_to_db(provider.tx_ref(), 1)
|
|
.expect("Could not write reverts to DB");
|
|
|
|
let reth_account_a = account_a.into();
|
|
let reth_account_b = account_b.into();
|
|
let reth_account_b_changed = account_b_changed.clone().into();
|
|
|
|
// Check plain state
|
|
assert_eq!(
|
|
provider.basic_account(address_a).expect("Could not read account state"),
|
|
Some(reth_account_a),
|
|
"Account A state is wrong"
|
|
);
|
|
assert_eq!(
|
|
provider.basic_account(address_b).expect("Could not read account state"),
|
|
Some(reth_account_b_changed),
|
|
"Account B state is wrong"
|
|
);
|
|
|
|
// Check change set
|
|
let mut changeset_cursor = provider
|
|
.tx_ref()
|
|
.cursor_dup_read::<tables::AccountChangeSets>()
|
|
.expect("Could not open changeset cursor");
|
|
assert_eq!(
|
|
changeset_cursor.seek_exact(1).expect("Could not read account change set"),
|
|
Some((1, AccountBeforeTx { address: address_a, info: None })),
|
|
"Account A changeset is wrong"
|
|
);
|
|
assert_eq!(
|
|
changeset_cursor.next_dup().expect("Changeset table is malformed"),
|
|
Some((1, AccountBeforeTx { address: address_b, info: Some(reth_account_b) })),
|
|
"Account B changeset is wrong"
|
|
);
|
|
|
|
let mut state = State::builder().with_bundle_update().build();
|
|
state.insert_account(address_b, account_b_changed.clone());
|
|
|
|
// 0xff.. is destroyed
|
|
state.commit(HashMap::from([(
|
|
address_b,
|
|
RevmAccount {
|
|
status: AccountStatus::Touched | AccountStatus::SelfDestructed,
|
|
info: account_b_changed,
|
|
storage: HashMap::default(),
|
|
},
|
|
)]));
|
|
|
|
state.merge_transitions(BundleRetention::Reverts);
|
|
let mut revm_bundle_state = state.take_bundle();
|
|
|
|
// Write plain state and reverts separately.
|
|
let reverts = revm_bundle_state.take_all_reverts().into_plain_state_reverts();
|
|
let plain_state = revm_bundle_state.into_plain_state(OriginalValuesKnown::Yes);
|
|
// Account B selfdestructed so flag for it should be present.
|
|
assert_eq!(
|
|
plain_state.storage,
|
|
[PlainStorageChangeset { address: address_b, wipe_storage: true, storage: vec![] }]
|
|
);
|
|
assert!(plain_state.contracts.is_empty());
|
|
StateChanges(plain_state)
|
|
.write_to_db(provider.tx_ref())
|
|
.expect("Could not write plain state to DB");
|
|
|
|
assert_eq!(
|
|
reverts.storage,
|
|
[[PlainStorageRevert { address: address_b, wiped: true, storage_revert: vec![] }]]
|
|
);
|
|
StateReverts(reverts)
|
|
.write_to_db(provider.tx_ref(), 2)
|
|
.expect("Could not write reverts to DB");
|
|
|
|
// Check new plain state for account B
|
|
assert_eq!(
|
|
provider.basic_account(address_b).expect("Could not read account state"),
|
|
None,
|
|
"Account B should be deleted"
|
|
);
|
|
|
|
// Check change set
|
|
assert_eq!(
|
|
changeset_cursor.seek_exact(2).expect("Could not read account change set"),
|
|
Some((2, AccountBeforeTx { address: address_b, info: Some(reth_account_b_changed) })),
|
|
"Account B changeset is wrong after deletion"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn write_to_db_storage() {
|
|
let factory = create_test_provider_factory();
|
|
let provider = factory.provider_rw().unwrap();
|
|
|
|
let address_a = Address::ZERO;
|
|
let address_b = Address::repeat_byte(0xff);
|
|
|
|
let account_b = RevmAccountInfo { balance: U256::from(2), nonce: 2, ..Default::default() };
|
|
|
|
let mut state = State::builder().with_bundle_update().build();
|
|
state.insert_not_existing(address_a);
|
|
state.insert_account_with_storage(
|
|
address_b,
|
|
account_b.clone(),
|
|
HashMap::from([(U256::from(1), U256::from(1))]),
|
|
);
|
|
|
|
state.commit(HashMap::from([
|
|
(
|
|
address_a,
|
|
RevmAccount {
|
|
status: AccountStatus::Touched | AccountStatus::Created,
|
|
info: RevmAccountInfo::default(),
|
|
// 0x00 => 0 => 1
|
|
// 0x01 => 0 => 2
|
|
storage: HashMap::from([
|
|
(
|
|
U256::from(0),
|
|
EvmStorageSlot { present_value: U256::from(1), ..Default::default() },
|
|
),
|
|
(
|
|
U256::from(1),
|
|
EvmStorageSlot { present_value: U256::from(2), ..Default::default() },
|
|
),
|
|
]),
|
|
},
|
|
),
|
|
(
|
|
address_b,
|
|
RevmAccount {
|
|
status: AccountStatus::Touched,
|
|
info: account_b,
|
|
// 0x01 => 1 => 2
|
|
storage: HashMap::from([(
|
|
U256::from(1),
|
|
EvmStorageSlot {
|
|
present_value: U256::from(2),
|
|
original_value: U256::from(1),
|
|
..Default::default()
|
|
},
|
|
)]),
|
|
},
|
|
),
|
|
]));
|
|
|
|
state.merge_transitions(BundleRetention::Reverts);
|
|
|
|
ExecutionOutcome::new(state.take_bundle(), Receipts::default(), 1, Vec::new())
|
|
.write_to_storage(&provider, None, OriginalValuesKnown::Yes)
|
|
.expect("Could not write bundle state to DB");
|
|
|
|
// Check plain storage state
|
|
let mut storage_cursor = provider
|
|
.tx_ref()
|
|
.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: B256::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: B256::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: B256::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 = provider
|
|
.tx_ref()
|
|
.cursor_dup_read::<tables::StorageChangeSets>()
|
|
.expect("Could not open storage changeset cursor");
|
|
assert_eq!(
|
|
changeset_cursor.seek_exact(BlockNumberAddress((1, address_a))).unwrap(),
|
|
Some((
|
|
BlockNumberAddress((1, address_a)),
|
|
StorageEntry { key: B256::ZERO, value: U256::from(0) }
|
|
)),
|
|
"Slot 0 for account A should have changed from 0"
|
|
);
|
|
assert_eq!(
|
|
changeset_cursor.next_dup().unwrap(),
|
|
Some((
|
|
BlockNumberAddress((1, address_a)),
|
|
StorageEntry { key: B256::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(BlockNumberAddress((1, address_b))).unwrap(),
|
|
Some((
|
|
BlockNumberAddress((1, address_b)),
|
|
StorageEntry { key: B256::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 state = State::builder().with_bundle_update().build();
|
|
state.insert_account(address_a, RevmAccountInfo::default());
|
|
|
|
state.commit(HashMap::from([(
|
|
address_a,
|
|
RevmAccount {
|
|
status: AccountStatus::Touched | AccountStatus::SelfDestructed,
|
|
info: RevmAccountInfo::default(),
|
|
storage: HashMap::default(),
|
|
},
|
|
)]));
|
|
|
|
state.merge_transitions(BundleRetention::Reverts);
|
|
ExecutionOutcome::new(state.take_bundle(), Receipts::default(), 2, Vec::new())
|
|
.write_to_storage(&provider, None, OriginalValuesKnown::Yes)
|
|
.expect("Could not write bundle 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(BlockNumberAddress((2, address_a))).unwrap(),
|
|
Some((
|
|
BlockNumberAddress((2, address_a)),
|
|
StorageEntry { key: B256::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((
|
|
BlockNumberAddress((2, address_a)),
|
|
StorageEntry { key: B256::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 write_to_db_multiple_selfdestructs() {
|
|
let factory = create_test_provider_factory();
|
|
let provider = factory.provider_rw().unwrap();
|
|
|
|
let address1 = Address::random();
|
|
let account_info = RevmAccountInfo { nonce: 1, ..Default::default() };
|
|
|
|
// Block #0: initial state.
|
|
let mut init_state = State::builder().with_bundle_update().build();
|
|
init_state.insert_not_existing(address1);
|
|
init_state.commit(HashMap::from([(
|
|
address1,
|
|
RevmAccount {
|
|
info: account_info.clone(),
|
|
status: AccountStatus::Touched | AccountStatus::Created,
|
|
// 0x00 => 0 => 1
|
|
// 0x01 => 0 => 2
|
|
storage: HashMap::from([
|
|
(
|
|
U256::ZERO,
|
|
EvmStorageSlot { present_value: U256::from(1), ..Default::default() },
|
|
),
|
|
(
|
|
U256::from(1),
|
|
EvmStorageSlot { present_value: U256::from(2), ..Default::default() },
|
|
),
|
|
]),
|
|
},
|
|
)]));
|
|
init_state.merge_transitions(BundleRetention::Reverts);
|
|
ExecutionOutcome::new(init_state.take_bundle(), Receipts::default(), 0, Vec::new())
|
|
.write_to_storage(&provider, None, OriginalValuesKnown::Yes)
|
|
.expect("Could not write init bundle state to DB");
|
|
|
|
let mut state = State::builder().with_bundle_update().build();
|
|
state.insert_account_with_storage(
|
|
address1,
|
|
account_info.clone(),
|
|
HashMap::from([(U256::ZERO, U256::from(1)), (U256::from(1), U256::from(2))]),
|
|
);
|
|
|
|
// Block #1: change storage.
|
|
state.commit(HashMap::from([(
|
|
address1,
|
|
RevmAccount {
|
|
status: AccountStatus::Touched,
|
|
info: account_info.clone(),
|
|
// 0x00 => 1 => 2
|
|
storage: HashMap::from([(
|
|
U256::ZERO,
|
|
EvmStorageSlot {
|
|
original_value: U256::from(1),
|
|
present_value: U256::from(2),
|
|
..Default::default()
|
|
},
|
|
)]),
|
|
},
|
|
)]));
|
|
state.merge_transitions(BundleRetention::Reverts);
|
|
|
|
// Block #2: destroy account.
|
|
state.commit(HashMap::from([(
|
|
address1,
|
|
RevmAccount {
|
|
status: AccountStatus::Touched | AccountStatus::SelfDestructed,
|
|
info: account_info.clone(),
|
|
storage: HashMap::default(),
|
|
},
|
|
)]));
|
|
state.merge_transitions(BundleRetention::Reverts);
|
|
|
|
// Block #3: re-create account and change storage.
|
|
state.commit(HashMap::from([(
|
|
address1,
|
|
RevmAccount {
|
|
status: AccountStatus::Touched | AccountStatus::Created,
|
|
info: account_info.clone(),
|
|
storage: HashMap::default(),
|
|
},
|
|
)]));
|
|
state.merge_transitions(BundleRetention::Reverts);
|
|
|
|
// Block #4: change storage.
|
|
state.commit(HashMap::from([(
|
|
address1,
|
|
RevmAccount {
|
|
status: AccountStatus::Touched,
|
|
info: account_info.clone(),
|
|
// 0x00 => 0 => 2
|
|
// 0x02 => 0 => 4
|
|
// 0x06 => 0 => 6
|
|
storage: HashMap::from([
|
|
(
|
|
U256::ZERO,
|
|
EvmStorageSlot { present_value: U256::from(2), ..Default::default() },
|
|
),
|
|
(
|
|
U256::from(2),
|
|
EvmStorageSlot { present_value: U256::from(4), ..Default::default() },
|
|
),
|
|
(
|
|
U256::from(6),
|
|
EvmStorageSlot { present_value: U256::from(6), ..Default::default() },
|
|
),
|
|
]),
|
|
},
|
|
)]));
|
|
state.merge_transitions(BundleRetention::Reverts);
|
|
|
|
// Block #5: Destroy account again.
|
|
state.commit(HashMap::from([(
|
|
address1,
|
|
RevmAccount {
|
|
status: AccountStatus::Touched | AccountStatus::SelfDestructed,
|
|
info: account_info.clone(),
|
|
storage: HashMap::default(),
|
|
},
|
|
)]));
|
|
state.merge_transitions(BundleRetention::Reverts);
|
|
|
|
// Block #6: Create, change, destroy and re-create in the same block.
|
|
state.commit(HashMap::from([(
|
|
address1,
|
|
RevmAccount {
|
|
status: AccountStatus::Touched | AccountStatus::Created,
|
|
info: account_info.clone(),
|
|
storage: HashMap::default(),
|
|
},
|
|
)]));
|
|
state.commit(HashMap::from([(
|
|
address1,
|
|
RevmAccount {
|
|
status: AccountStatus::Touched,
|
|
info: account_info.clone(),
|
|
// 0x00 => 0 => 2
|
|
storage: HashMap::from([(
|
|
U256::ZERO,
|
|
EvmStorageSlot { present_value: U256::from(2), ..Default::default() },
|
|
)]),
|
|
},
|
|
)]));
|
|
state.commit(HashMap::from([(
|
|
address1,
|
|
RevmAccount {
|
|
status: AccountStatus::Touched | AccountStatus::SelfDestructed,
|
|
info: account_info.clone(),
|
|
storage: HashMap::default(),
|
|
},
|
|
)]));
|
|
state.commit(HashMap::from([(
|
|
address1,
|
|
RevmAccount {
|
|
status: AccountStatus::Touched | AccountStatus::Created,
|
|
info: account_info.clone(),
|
|
storage: HashMap::default(),
|
|
},
|
|
)]));
|
|
state.merge_transitions(BundleRetention::Reverts);
|
|
|
|
// Block #7: Change storage.
|
|
state.commit(HashMap::from([(
|
|
address1,
|
|
RevmAccount {
|
|
status: AccountStatus::Touched,
|
|
info: account_info,
|
|
// 0x00 => 0 => 9
|
|
storage: HashMap::from([(
|
|
U256::ZERO,
|
|
EvmStorageSlot { present_value: U256::from(9), ..Default::default() },
|
|
)]),
|
|
},
|
|
)]));
|
|
state.merge_transitions(BundleRetention::Reverts);
|
|
|
|
let bundle = state.take_bundle();
|
|
|
|
ExecutionOutcome::new(bundle, Receipts::default(), 1, Vec::new())
|
|
.write_to_storage(&provider, None, OriginalValuesKnown::Yes)
|
|
.expect("Could not write bundle state to DB");
|
|
|
|
let mut storage_changeset_cursor = provider
|
|
.tx_ref()
|
|
.cursor_dup_read::<tables::StorageChangeSets>()
|
|
.expect("Could not open plain storage state cursor");
|
|
let mut storage_changes = storage_changeset_cursor.walk_range(..).unwrap();
|
|
|
|
// Iterate through all storage changes
|
|
|
|
// Block <number>
|
|
// <slot>: <expected value before>
|
|
// ...
|
|
|
|
// Block #0
|
|
// 0x00: 0
|
|
// 0x01: 0
|
|
assert_eq!(
|
|
storage_changes.next(),
|
|
Some(Ok((
|
|
BlockNumberAddress((0, address1)),
|
|
StorageEntry { key: B256::with_last_byte(0), value: U256::ZERO }
|
|
)))
|
|
);
|
|
assert_eq!(
|
|
storage_changes.next(),
|
|
Some(Ok((
|
|
BlockNumberAddress((0, address1)),
|
|
StorageEntry { key: B256::with_last_byte(1), value: U256::ZERO }
|
|
)))
|
|
);
|
|
|
|
// Block #1
|
|
// 0x00: 1
|
|
assert_eq!(
|
|
storage_changes.next(),
|
|
Some(Ok((
|
|
BlockNumberAddress((1, address1)),
|
|
StorageEntry { key: B256::with_last_byte(0), value: U256::from(1) }
|
|
)))
|
|
);
|
|
|
|
// Block #2 (destroyed)
|
|
// 0x00: 2
|
|
// 0x01: 2
|
|
assert_eq!(
|
|
storage_changes.next(),
|
|
Some(Ok((
|
|
BlockNumberAddress((2, address1)),
|
|
StorageEntry { key: B256::with_last_byte(0), value: U256::from(2) }
|
|
)))
|
|
);
|
|
assert_eq!(
|
|
storage_changes.next(),
|
|
Some(Ok((
|
|
BlockNumberAddress((2, address1)),
|
|
StorageEntry { key: B256::with_last_byte(1), value: U256::from(2) }
|
|
)))
|
|
);
|
|
|
|
// Block #3
|
|
// no storage changes
|
|
|
|
// Block #4
|
|
// 0x00: 0
|
|
// 0x02: 0
|
|
// 0x06: 0
|
|
assert_eq!(
|
|
storage_changes.next(),
|
|
Some(Ok((
|
|
BlockNumberAddress((4, address1)),
|
|
StorageEntry { key: B256::with_last_byte(0), value: U256::ZERO }
|
|
)))
|
|
);
|
|
assert_eq!(
|
|
storage_changes.next(),
|
|
Some(Ok((
|
|
BlockNumberAddress((4, address1)),
|
|
StorageEntry { key: B256::with_last_byte(2), value: U256::ZERO }
|
|
)))
|
|
);
|
|
assert_eq!(
|
|
storage_changes.next(),
|
|
Some(Ok((
|
|
BlockNumberAddress((4, address1)),
|
|
StorageEntry { key: B256::with_last_byte(6), value: U256::ZERO }
|
|
)))
|
|
);
|
|
|
|
// Block #5 (destroyed)
|
|
// 0x00: 2
|
|
// 0x02: 4
|
|
// 0x06: 6
|
|
assert_eq!(
|
|
storage_changes.next(),
|
|
Some(Ok((
|
|
BlockNumberAddress((5, address1)),
|
|
StorageEntry { key: B256::with_last_byte(0), value: U256::from(2) }
|
|
)))
|
|
);
|
|
assert_eq!(
|
|
storage_changes.next(),
|
|
Some(Ok((
|
|
BlockNumberAddress((5, address1)),
|
|
StorageEntry { key: B256::with_last_byte(2), value: U256::from(4) }
|
|
)))
|
|
);
|
|
assert_eq!(
|
|
storage_changes.next(),
|
|
Some(Ok((
|
|
BlockNumberAddress((5, address1)),
|
|
StorageEntry { key: B256::with_last_byte(6), value: U256::from(6) }
|
|
)))
|
|
);
|
|
|
|
// Block #6
|
|
// no storage changes (only inter block changes)
|
|
|
|
// Block #7
|
|
// 0x00: 0
|
|
assert_eq!(
|
|
storage_changes.next(),
|
|
Some(Ok((
|
|
BlockNumberAddress((7, address1)),
|
|
StorageEntry { key: B256::with_last_byte(0), value: U256::ZERO }
|
|
)))
|
|
);
|
|
assert_eq!(storage_changes.next(), None);
|
|
}
|
|
|
|
#[test]
|
|
fn storage_change_after_selfdestruct_within_block() {
|
|
let factory = create_test_provider_factory();
|
|
let provider = factory.provider_rw().unwrap();
|
|
|
|
let address1 = Address::random();
|
|
let account1 = RevmAccountInfo { nonce: 1, ..Default::default() };
|
|
|
|
// Block #0: initial state.
|
|
let mut init_state = State::builder().with_bundle_update().build();
|
|
init_state.insert_not_existing(address1);
|
|
init_state.commit(HashMap::from([(
|
|
address1,
|
|
RevmAccount {
|
|
info: account1.clone(),
|
|
status: AccountStatus::Touched | AccountStatus::Created,
|
|
// 0x00 => 0 => 1
|
|
// 0x01 => 0 => 2
|
|
storage: HashMap::from([
|
|
(
|
|
U256::ZERO,
|
|
EvmStorageSlot { present_value: U256::from(1), ..Default::default() },
|
|
),
|
|
(
|
|
U256::from(1),
|
|
EvmStorageSlot { present_value: U256::from(2), ..Default::default() },
|
|
),
|
|
]),
|
|
},
|
|
)]));
|
|
init_state.merge_transitions(BundleRetention::Reverts);
|
|
ExecutionOutcome::new(init_state.take_bundle(), Receipts::default(), 0, Vec::new())
|
|
.write_to_storage(&provider, None, OriginalValuesKnown::Yes)
|
|
.expect("Could not write init bundle state to DB");
|
|
|
|
let mut state = State::builder().with_bundle_update().build();
|
|
state.insert_account_with_storage(
|
|
address1,
|
|
account1.clone(),
|
|
HashMap::from([(U256::ZERO, U256::from(1)), (U256::from(1), U256::from(2))]),
|
|
);
|
|
|
|
// Block #1: Destroy, re-create, change storage.
|
|
state.commit(HashMap::from([(
|
|
address1,
|
|
RevmAccount {
|
|
status: AccountStatus::Touched | AccountStatus::SelfDestructed,
|
|
info: account1.clone(),
|
|
storage: HashMap::default(),
|
|
},
|
|
)]));
|
|
|
|
state.commit(HashMap::from([(
|
|
address1,
|
|
RevmAccount {
|
|
status: AccountStatus::Touched | AccountStatus::Created,
|
|
info: account1.clone(),
|
|
storage: HashMap::default(),
|
|
},
|
|
)]));
|
|
|
|
state.commit(HashMap::from([(
|
|
address1,
|
|
RevmAccount {
|
|
status: AccountStatus::Touched,
|
|
info: account1,
|
|
// 0x01 => 0 => 5
|
|
storage: HashMap::from([(
|
|
U256::from(1),
|
|
EvmStorageSlot { present_value: U256::from(5), ..Default::default() },
|
|
)]),
|
|
},
|
|
)]));
|
|
|
|
// Commit block #1 changes to the database.
|
|
state.merge_transitions(BundleRetention::Reverts);
|
|
ExecutionOutcome::new(state.take_bundle(), Receipts::default(), 1, Vec::new())
|
|
.write_to_storage(&provider, None, OriginalValuesKnown::Yes)
|
|
.expect("Could not write bundle state to DB");
|
|
|
|
let mut storage_changeset_cursor = provider
|
|
.tx_ref()
|
|
.cursor_dup_read::<tables::StorageChangeSets>()
|
|
.expect("Could not open plain storage state cursor");
|
|
let range = BlockNumberAddress::range(1..=1);
|
|
let mut storage_changes = storage_changeset_cursor.walk_range(range).unwrap();
|
|
|
|
assert_eq!(
|
|
storage_changes.next(),
|
|
Some(Ok((
|
|
BlockNumberAddress((1, address1)),
|
|
StorageEntry { key: B256::with_last_byte(0), value: U256::from(1) }
|
|
)))
|
|
);
|
|
assert_eq!(
|
|
storage_changes.next(),
|
|
Some(Ok((
|
|
BlockNumberAddress((1, address1)),
|
|
StorageEntry { key: B256::with_last_byte(1), value: U256::from(2) }
|
|
)))
|
|
);
|
|
assert_eq!(storage_changes.next(), None);
|
|
}
|
|
|
|
#[test]
|
|
fn revert_to_indices() {
|
|
let base = ExecutionOutcome {
|
|
bundle: BundleState::default(),
|
|
receipts: vec![vec![Some(Receipt::default()); 2]; 7].into(),
|
|
first_block: 10,
|
|
requests: Vec::new(),
|
|
};
|
|
|
|
let mut this = base.clone();
|
|
assert!(this.revert_to(10));
|
|
assert_eq!(this.receipts.len(), 1);
|
|
|
|
let mut this = base.clone();
|
|
assert!(!this.revert_to(9));
|
|
assert_eq!(this.receipts.len(), 7);
|
|
|
|
let mut this = base.clone();
|
|
assert!(this.revert_to(15));
|
|
assert_eq!(this.receipts.len(), 6);
|
|
|
|
let mut this = base.clone();
|
|
assert!(this.revert_to(16));
|
|
assert_eq!(this.receipts.len(), 7);
|
|
|
|
let mut this = base;
|
|
assert!(!this.revert_to(17));
|
|
assert_eq!(this.receipts.len(), 7);
|
|
}
|
|
|
|
#[test]
|
|
fn bundle_state_state_root() {
|
|
type PreState = BTreeMap<Address, (Account, BTreeMap<B256, U256>)>;
|
|
let mut prestate: PreState = (0..10)
|
|
.map(|key| {
|
|
let account = Account { nonce: 1, balance: U256::from(key), bytecode_hash: None };
|
|
let storage =
|
|
(1..11).map(|key| (B256::with_last_byte(key), U256::from(key))).collect();
|
|
(Address::with_last_byte(key), (account, storage))
|
|
})
|
|
.collect();
|
|
|
|
let db = create_test_rw_db();
|
|
|
|
// insert initial state to the database
|
|
db.update(|tx| {
|
|
for (address, (account, storage)) in &prestate {
|
|
let hashed_address = keccak256(address);
|
|
tx.put::<tables::HashedAccounts>(hashed_address, *account).unwrap();
|
|
for (slot, value) in storage {
|
|
tx.put::<tables::HashedStorages>(
|
|
hashed_address,
|
|
StorageEntry { key: keccak256(slot), value: *value },
|
|
)
|
|
.unwrap();
|
|
}
|
|
}
|
|
|
|
let (_, updates) = StateRoot::from_tx(tx).root_with_updates().unwrap();
|
|
updates.write_to_database(tx).unwrap();
|
|
})
|
|
.unwrap();
|
|
|
|
let tx = db.tx().unwrap();
|
|
let mut state = State::builder().with_bundle_update().build();
|
|
|
|
let assert_state_root = |state: &State<EmptyDB>, expected: &PreState, msg| {
|
|
assert_eq!(
|
|
ExecutionOutcome::new(
|
|
state.bundle_state.clone(),
|
|
Receipts::default(),
|
|
0,
|
|
Vec::new()
|
|
)
|
|
.hash_state_slow()
|
|
.state_root(&tx)
|
|
.unwrap(),
|
|
state_root(expected.clone().into_iter().map(|(address, (account, storage))| (
|
|
address,
|
|
(account, storage.into_iter())
|
|
))),
|
|
"{msg}"
|
|
);
|
|
};
|
|
|
|
// database only state root is correct
|
|
assert_state_root(&state, &prestate, "empty");
|
|
|
|
// destroy account 1
|
|
let address1 = Address::with_last_byte(1);
|
|
let account1_old = prestate.remove(&address1).unwrap();
|
|
state.insert_account(address1, account1_old.0.into());
|
|
state.commit(HashMap::from([(
|
|
address1,
|
|
RevmAccount {
|
|
status: AccountStatus::Touched | AccountStatus::SelfDestructed,
|
|
info: RevmAccountInfo::default(),
|
|
storage: HashMap::default(),
|
|
},
|
|
)]));
|
|
state.merge_transitions(BundleRetention::PlainState);
|
|
assert_state_root(&state, &prestate, "destroyed account");
|
|
|
|
// change slot 2 in account 2
|
|
let address2 = Address::with_last_byte(2);
|
|
let slot2 = U256::from(2);
|
|
let slot2_key = B256::from(slot2);
|
|
let account2 = prestate.get_mut(&address2).unwrap();
|
|
let account2_slot2_old_value = *account2.1.get(&slot2_key).unwrap();
|
|
state.insert_account_with_storage(
|
|
address2,
|
|
account2.0.into(),
|
|
HashMap::from([(slot2, account2_slot2_old_value)]),
|
|
);
|
|
|
|
let account2_slot2_new_value = U256::from(100);
|
|
account2.1.insert(slot2_key, account2_slot2_new_value);
|
|
state.commit(HashMap::from([(
|
|
address2,
|
|
RevmAccount {
|
|
status: AccountStatus::Touched,
|
|
info: account2.0.into(),
|
|
storage: HashMap::from_iter([(
|
|
slot2,
|
|
EvmStorageSlot::new_changed(account2_slot2_old_value, account2_slot2_new_value),
|
|
)]),
|
|
},
|
|
)]));
|
|
state.merge_transitions(BundleRetention::PlainState);
|
|
assert_state_root(&state, &prestate, "changed storage");
|
|
|
|
// change balance of account 3
|
|
let address3 = Address::with_last_byte(3);
|
|
let account3 = prestate.get_mut(&address3).unwrap();
|
|
state.insert_account(address3, account3.0.into());
|
|
|
|
account3.0.balance = U256::from(24);
|
|
state.commit(HashMap::from([(
|
|
address3,
|
|
RevmAccount {
|
|
status: AccountStatus::Touched,
|
|
info: account3.0.into(),
|
|
storage: HashMap::default(),
|
|
},
|
|
)]));
|
|
state.merge_transitions(BundleRetention::PlainState);
|
|
assert_state_root(&state, &prestate, "changed balance");
|
|
|
|
// change nonce of account 4
|
|
let address4 = Address::with_last_byte(4);
|
|
let account4 = prestate.get_mut(&address4).unwrap();
|
|
state.insert_account(address4, account4.0.into());
|
|
|
|
account4.0.nonce = 128;
|
|
state.commit(HashMap::from([(
|
|
address4,
|
|
RevmAccount {
|
|
status: AccountStatus::Touched,
|
|
info: account4.0.into(),
|
|
storage: HashMap::default(),
|
|
},
|
|
)]));
|
|
state.merge_transitions(BundleRetention::PlainState);
|
|
assert_state_root(&state, &prestate, "changed nonce");
|
|
|
|
// recreate account 1
|
|
let account1_new =
|
|
Account { nonce: 56, balance: U256::from(123), bytecode_hash: Some(B256::random()) };
|
|
prestate.insert(address1, (account1_new, BTreeMap::default()));
|
|
state.commit(HashMap::from([(
|
|
address1,
|
|
RevmAccount {
|
|
status: AccountStatus::Touched | AccountStatus::Created,
|
|
info: account1_new.into(),
|
|
storage: HashMap::default(),
|
|
},
|
|
)]));
|
|
state.merge_transitions(BundleRetention::PlainState);
|
|
assert_state_root(&state, &prestate, "recreated");
|
|
|
|
// update storage for account 1
|
|
let slot20 = U256::from(20);
|
|
let slot20_key = B256::from(slot20);
|
|
let account1_slot20_value = U256::from(12345);
|
|
prestate.get_mut(&address1).unwrap().1.insert(slot20_key, account1_slot20_value);
|
|
state.commit(HashMap::from([(
|
|
address1,
|
|
RevmAccount {
|
|
status: AccountStatus::Touched | AccountStatus::Created,
|
|
info: account1_new.into(),
|
|
storage: HashMap::from_iter([(
|
|
slot20,
|
|
EvmStorageSlot::new_changed(U256::ZERO, account1_slot20_value),
|
|
)]),
|
|
},
|
|
)]));
|
|
state.merge_transitions(BundleRetention::PlainState);
|
|
assert_state_root(&state, &prestate, "recreated changed storage");
|
|
}
|
|
|
|
#[test]
|
|
fn prepend_state() {
|
|
let address1 = Address::random();
|
|
let address2 = Address::random();
|
|
|
|
let account1 = RevmAccountInfo { nonce: 1, ..Default::default() };
|
|
let account1_changed = RevmAccountInfo { nonce: 1, ..Default::default() };
|
|
let account2 = RevmAccountInfo { nonce: 1, ..Default::default() };
|
|
|
|
let present_state = BundleState::builder(2..=2)
|
|
.state_present_account_info(address1, account1_changed.clone())
|
|
.build();
|
|
assert_eq!(present_state.reverts.len(), 1);
|
|
let previous_state = BundleState::builder(1..=1)
|
|
.state_present_account_info(address1, account1)
|
|
.state_present_account_info(address2, account2.clone())
|
|
.build();
|
|
assert_eq!(previous_state.reverts.len(), 1);
|
|
|
|
let mut test = ExecutionOutcome {
|
|
bundle: present_state,
|
|
receipts: vec![vec![Some(Receipt::default()); 2]; 1].into(),
|
|
first_block: 2,
|
|
requests: Vec::new(),
|
|
};
|
|
|
|
test.prepend_state(previous_state);
|
|
|
|
assert_eq!(test.receipts.len(), 1);
|
|
let end_state = test.state();
|
|
assert_eq!(end_state.state.len(), 2);
|
|
// reverts num should stay the same.
|
|
assert_eq!(end_state.reverts.len(), 1);
|
|
// account1 is not overwritten.
|
|
assert_eq!(end_state.state.get(&address1).unwrap().info, Some(account1_changed));
|
|
// account2 got inserted
|
|
assert_eq!(end_state.state.get(&address2).unwrap().info, Some(account2));
|
|
}
|
|
}
|