mirror of
https://github.com/paradigmxyz/reth.git
synced 2026-01-30 01:28:21 -05:00
refactor(trie): hashed post state (#6066)
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -6858,6 +6858,7 @@ dependencies = [
|
||||
"reth-interfaces",
|
||||
"reth-primitives",
|
||||
"reth-provider",
|
||||
"revm",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
|
||||
@@ -306,8 +306,8 @@ impl Command {
|
||||
|
||||
let hashed_state = state.hash_state_slow();
|
||||
let (state_root, trie_updates) = state
|
||||
.state_root_calculator(provider_factory.provider()?.tx_ref(), &hashed_state)
|
||||
.root_with_updates()?;
|
||||
.hash_state_slow()
|
||||
.state_root_with_updates(provider_factory.provider()?.tx_ref())?;
|
||||
|
||||
if state_root != block_with_senders.state_root {
|
||||
eyre::bail!(
|
||||
|
||||
@@ -1174,12 +1174,9 @@ impl<DB: Database, EF: ExecutorFactory> BlockchainTree<DB, EF> {
|
||||
}
|
||||
None => {
|
||||
debug!(target: "blockchain_tree", blocks = ?block_hash_numbers, "Recomputing state root for insert");
|
||||
let (state_root, trie_updates) = state
|
||||
.state_root_calculator(
|
||||
self.externals.provider_factory.provider()?.tx_ref(),
|
||||
&hashed_state,
|
||||
)
|
||||
.root_with_updates()
|
||||
let provider = self.externals.provider_factory.provider()?;
|
||||
let (state_root, trie_updates) = hashed_state
|
||||
.state_root_with_updates(provider.tx_ref())
|
||||
.map_err(Into::<DatabaseError>::into)?;
|
||||
let tip = blocks.tip();
|
||||
if state_root != tip.state_root {
|
||||
|
||||
@@ -6,16 +6,12 @@ use reth_db::{
|
||||
};
|
||||
use reth_interfaces::db::DatabaseError;
|
||||
use reth_primitives::{
|
||||
keccak256, logs_bloom,
|
||||
logs_bloom,
|
||||
revm::compat::{into_reth_acc, into_revm_acc},
|
||||
Account, Address, BlockNumber, Bloom, Bytecode, Log, Receipt, Receipts, StorageEntry, B256,
|
||||
U256,
|
||||
};
|
||||
use reth_trie::{
|
||||
hashed_cursor::{HashedPostState, HashedPostStateCursorFactory, HashedStorage},
|
||||
updates::TrieUpdates,
|
||||
StateRoot, StateRootError,
|
||||
};
|
||||
use reth_trie::HashedPostState;
|
||||
use revm::{
|
||||
db::{states::BundleState, BundleAccount},
|
||||
primitives::AccountInfo,
|
||||
@@ -135,106 +131,10 @@ impl BundleStateWithReceipts {
|
||||
self.bundle.bytecode(code_hash).map(Bytecode)
|
||||
}
|
||||
|
||||
/// Hash all changed accounts and storage entries that are currently stored in the post state.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// The hashed post state.
|
||||
/// Returns [HashedPostState] for this bundle state.
|
||||
/// See [HashedPostState::from_bundle_state] for more info.
|
||||
pub fn hash_state_slow(&self) -> HashedPostState {
|
||||
let mut hashed_state = HashedPostState::default();
|
||||
|
||||
for (address, account) in self.bundle.state() {
|
||||
let hashed_address = keccak256(address);
|
||||
if let Some(account) = &account.info {
|
||||
hashed_state.insert_account(hashed_address, into_reth_acc(account.clone()))
|
||||
} else {
|
||||
hashed_state.insert_destroyed_account(hashed_address);
|
||||
}
|
||||
|
||||
// insert storage.
|
||||
let mut hashed_storage = HashedStorage::new(account.status.was_destroyed());
|
||||
|
||||
for (key, value) in account.storage.iter() {
|
||||
let hashed_key = keccak256(B256::new(key.to_be_bytes()));
|
||||
if value.present_value.is_zero() {
|
||||
hashed_storage.insert_zero_valued_slot(hashed_key);
|
||||
} else {
|
||||
hashed_storage.insert_non_zero_valued_storage(hashed_key, value.present_value);
|
||||
}
|
||||
}
|
||||
hashed_state.insert_hashed_storage(hashed_address, hashed_storage)
|
||||
}
|
||||
hashed_state.sorted()
|
||||
}
|
||||
|
||||
/// Returns [StateRoot] calculator based on database and in-memory state.
|
||||
pub fn state_root_calculator<'a, 'b, TX: DbTx>(
|
||||
&self,
|
||||
tx: &'a TX,
|
||||
hashed_post_state: &'b HashedPostState,
|
||||
) -> StateRoot<&'a TX, HashedPostStateCursorFactory<'a, 'b, TX>> {
|
||||
let (account_prefix_set, storage_prefix_set) = hashed_post_state.construct_prefix_sets();
|
||||
let hashed_cursor_factory = HashedPostStateCursorFactory::new(tx, hashed_post_state);
|
||||
StateRoot::from_tx(tx)
|
||||
.with_hashed_cursor_factory(hashed_cursor_factory)
|
||||
.with_changed_account_prefixes(account_prefix_set)
|
||||
.with_changed_storage_prefixes(storage_prefix_set)
|
||||
.with_destroyed_accounts(hashed_post_state.destroyed_accounts())
|
||||
}
|
||||
|
||||
/// Calculate the state root for this [BundleState].
|
||||
/// Internally, function calls [Self::hash_state_slow] to obtain the [HashedPostState].
|
||||
/// Afterwards, it retrieves the prefixsets from the [HashedPostState] and uses them to
|
||||
/// calculate the incremental state root.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use reth_db::{database::Database, test_utils::create_test_rw_db};
|
||||
/// use reth_primitives::{Account, Receipts, U256};
|
||||
/// use reth_provider::BundleStateWithReceipts;
|
||||
/// use std::collections::HashMap;
|
||||
///
|
||||
/// // Initialize the database
|
||||
/// let db = create_test_rw_db();
|
||||
///
|
||||
/// // Initialize the bundle state
|
||||
/// let bundle = BundleStateWithReceipts::new_init(
|
||||
/// HashMap::from([(
|
||||
/// [0x11; 20].into(),
|
||||
/// (
|
||||
/// None,
|
||||
/// Some(Account { nonce: 1, balance: U256::from(10), bytecode_hash: None }),
|
||||
/// HashMap::from([]),
|
||||
/// ),
|
||||
/// )]),
|
||||
/// HashMap::from([]),
|
||||
/// vec![],
|
||||
/// Receipts::new(),
|
||||
/// 0,
|
||||
/// );
|
||||
///
|
||||
/// // Calculate the state root
|
||||
/// let tx = db.tx().expect("failed to create transaction");
|
||||
/// let state_root = bundle.state_root_slow(&tx);
|
||||
/// ```
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// The state root for this [BundleState].
|
||||
pub fn state_root_slow<TX: DbTx>(&self, tx: &TX) -> Result<B256, StateRootError> {
|
||||
let hashed_post_state = self.hash_state_slow();
|
||||
self.state_root_calculator(tx, &hashed_post_state).root()
|
||||
}
|
||||
|
||||
/// Calculates the state root for this [BundleState] and returns it alongside trie updates.
|
||||
/// See [Self::state_root_slow] for more info.
|
||||
pub fn state_root_slow_with_updates<TX: DbTx>(
|
||||
&self,
|
||||
tx: &TX,
|
||||
) -> Result<(B256, TrieUpdates), StateRootError> {
|
||||
let hashed_post_state = self.hash_state_slow();
|
||||
self.state_root_calculator(tx, &hashed_post_state).root_with_updates()
|
||||
HashedPostState::from_bundle_state(&self.bundle.state)
|
||||
}
|
||||
|
||||
/// Transform block number to the index of block.
|
||||
@@ -439,9 +339,10 @@ mod tests {
|
||||
transaction::DbTx,
|
||||
};
|
||||
use reth_primitives::{
|
||||
revm::compat::into_reth_acc, Address, Receipt, Receipts, StorageEntry, B256, U256,
|
||||
keccak256, revm::compat::into_reth_acc, Address, Receipt, Receipts, StorageEntry, B256,
|
||||
U256,
|
||||
};
|
||||
use reth_trie::test_utils::state_root;
|
||||
use reth_trie::{test_utils::state_root, StateRoot};
|
||||
use revm::{
|
||||
db::{
|
||||
states::{
|
||||
@@ -1265,7 +1166,8 @@ mod tests {
|
||||
let assert_state_root = |state: &State<EmptyDB>, expected: &PreState, msg| {
|
||||
assert_eq!(
|
||||
BundleStateWithReceipts::new(state.bundle_state.clone(), Receipts::default(), 0)
|
||||
.state_root_slow(&tx)
|
||||
.hash_state_slow()
|
||||
.state_root(&tx)
|
||||
.unwrap(),
|
||||
state_root(expected.clone().into_iter().map(|(address, (account, storage))| (
|
||||
address,
|
||||
|
||||
@@ -5,7 +5,7 @@ use reth_db::{
|
||||
DatabaseError,
|
||||
};
|
||||
use reth_primitives::{Account, StorageEntry, B256, U256};
|
||||
use reth_trie::hashed_cursor::HashedPostState;
|
||||
use reth_trie::HashedPostState;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
/// Changes to the hashed state.
|
||||
@@ -73,7 +73,7 @@ mod tests {
|
||||
use super::*;
|
||||
use crate::test_utils::create_test_provider_factory;
|
||||
use reth_primitives::{keccak256, Address};
|
||||
use reth_trie::hashed_cursor::HashedStorage;
|
||||
use reth_trie::HashedStorage;
|
||||
|
||||
#[test]
|
||||
fn wiped_entries_are_removed() {
|
||||
|
||||
@@ -45,9 +45,7 @@ use reth_primitives::{
|
||||
StorageEntry, TransactionMeta, TransactionSigned, TransactionSignedEcRecovered,
|
||||
TransactionSignedNoHash, TxHash, TxNumber, Withdrawal, B256, U256,
|
||||
};
|
||||
use reth_trie::{
|
||||
hashed_cursor::HashedPostState, prefix_set::PrefixSetMut, updates::TrieUpdates, StateRoot,
|
||||
};
|
||||
use reth_trie::{prefix_set::PrefixSetMut, updates::TrieUpdates, HashedPostState, StateRoot};
|
||||
use revm::primitives::{BlockEnv, CfgEnv, SpecId};
|
||||
use std::{
|
||||
collections::{hash_map, BTreeMap, BTreeSet, HashMap},
|
||||
|
||||
@@ -60,7 +60,10 @@ impl<'b, TX: DbTx> BlockHashReader for LatestStateProviderRef<'b, TX> {
|
||||
|
||||
impl<'b, TX: DbTx> StateRootProvider for LatestStateProviderRef<'b, TX> {
|
||||
fn state_root(&self, bundle_state: &BundleStateWithReceipts) -> ProviderResult<B256> {
|
||||
bundle_state.state_root_slow(self.db).map_err(|err| ProviderError::Database(err.into()))
|
||||
bundle_state
|
||||
.hash_state_slow()
|
||||
.state_root(self.db)
|
||||
.map_err(|err| ProviderError::Database(err.into()))
|
||||
}
|
||||
|
||||
fn state_root_with_updates(
|
||||
@@ -68,7 +71,8 @@ impl<'b, TX: DbTx> StateRootProvider for LatestStateProviderRef<'b, TX> {
|
||||
bundle_state: &BundleStateWithReceipts,
|
||||
) -> ProviderResult<(B256, TrieUpdates)> {
|
||||
bundle_state
|
||||
.state_root_slow_with_updates(self.db)
|
||||
.hash_state_slow()
|
||||
.state_root_with_updates(self.db)
|
||||
.map_err(|err| ProviderError::Database(err.into()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ use reth_primitives::{
|
||||
Block, BlockHashOrNumber, BlockId, BlockNumber, BlockNumberOrTag, BlockWithSenders, ChainSpec,
|
||||
Header, PruneModes, Receipt, SealedBlock, SealedBlockWithSenders, SealedHeader, B256,
|
||||
};
|
||||
use reth_trie::{hashed_cursor::HashedPostState, updates::TrieUpdates};
|
||||
use reth_trie::{updates::TrieUpdates, HashedPostState};
|
||||
use std::ops::RangeInclusive;
|
||||
|
||||
/// Enum to control transaction hash inclusion.
|
||||
|
||||
@@ -16,9 +16,12 @@ workspace = true
|
||||
reth-primitives.workspace = true
|
||||
reth-interfaces.workspace = true
|
||||
reth-db.workspace = true
|
||||
revm.workspace = true
|
||||
|
||||
# alloy
|
||||
alloy-rlp.workspace = true
|
||||
alloy-chains.workspace = true
|
||||
|
||||
# tokio
|
||||
tokio = { workspace = true, default-features = false, features = ["sync"] }
|
||||
|
||||
|
||||
@@ -1,183 +1,11 @@
|
||||
use super::{HashedAccountCursor, HashedCursorFactory, HashedStorageCursor};
|
||||
use crate::prefix_set::{PrefixSet, PrefixSetMut};
|
||||
use ahash::{AHashMap, AHashSet};
|
||||
use crate::state::HashedPostState;
|
||||
use reth_db::{
|
||||
cursor::{DbCursorRO, DbDupCursorRO},
|
||||
tables,
|
||||
transaction::DbTx,
|
||||
};
|
||||
use reth_primitives::{trie::Nibbles, Account, StorageEntry, B256, U256};
|
||||
|
||||
/// The post state account storage with hashed slots.
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct HashedStorage {
|
||||
/// Hashed storage slots with non-zero.
|
||||
non_zero_valued_storage: Vec<(B256, U256)>,
|
||||
/// Slots that have been zero valued.
|
||||
zero_valued_slots: AHashSet<B256>,
|
||||
/// Whether the storage was wiped or not.
|
||||
wiped: bool,
|
||||
/// Whether the storage entries were sorted or not.
|
||||
sorted: bool,
|
||||
}
|
||||
|
||||
impl HashedStorage {
|
||||
/// Create new instance of [HashedStorage].
|
||||
pub fn new(wiped: bool) -> Self {
|
||||
Self {
|
||||
non_zero_valued_storage: Vec::new(),
|
||||
zero_valued_slots: AHashSet::new(),
|
||||
wiped,
|
||||
sorted: true, // empty is sorted
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if the storage was wiped.
|
||||
pub fn wiped(&self) -> bool {
|
||||
self.wiped
|
||||
}
|
||||
|
||||
/// Returns all storage slots.
|
||||
pub fn storage_slots(&self) -> impl Iterator<Item = (B256, U256)> + '_ {
|
||||
self.zero_valued_slots
|
||||
.iter()
|
||||
.map(|slot| (*slot, U256::ZERO))
|
||||
.chain(self.non_zero_valued_storage.iter().cloned())
|
||||
}
|
||||
|
||||
/// Sorts the non zero value storage entries.
|
||||
pub fn sort_storage(&mut self) {
|
||||
if !self.sorted {
|
||||
self.non_zero_valued_storage.sort_unstable_by_key(|(slot, _)| *slot);
|
||||
self.sorted = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// Insert non zero-valued storage entry.
|
||||
pub fn insert_non_zero_valued_storage(&mut self, slot: B256, value: U256) {
|
||||
debug_assert!(value != U256::ZERO, "value cannot be zero");
|
||||
self.non_zero_valued_storage.push((slot, value));
|
||||
self.sorted = false;
|
||||
}
|
||||
|
||||
/// Insert zero-valued storage slot.
|
||||
pub fn insert_zero_valued_slot(&mut self, slot: B256) {
|
||||
self.zero_valued_slots.insert(slot);
|
||||
}
|
||||
}
|
||||
|
||||
/// The post state with hashed addresses as keys.
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct HashedPostState {
|
||||
/// Map of hashed addresses to account info.
|
||||
accounts: Vec<(B256, Account)>,
|
||||
/// Set of destroyed accounts.
|
||||
destroyed_accounts: AHashSet<B256>,
|
||||
/// Map of hashed addresses to hashed storage.
|
||||
storages: AHashMap<B256, HashedStorage>,
|
||||
/// Whether the account and storage entries were sorted or not.
|
||||
sorted: bool,
|
||||
}
|
||||
|
||||
impl Default for HashedPostState {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
accounts: Vec::new(),
|
||||
destroyed_accounts: AHashSet::new(),
|
||||
storages: AHashMap::new(),
|
||||
sorted: true, // empty is sorted
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl HashedPostState {
|
||||
/// Sort and return self.
|
||||
pub fn sorted(mut self) -> Self {
|
||||
self.sort();
|
||||
self
|
||||
}
|
||||
|
||||
/// Returns all accounts with their state.
|
||||
pub fn accounts(&self) -> impl Iterator<Item = (B256, Option<Account>)> + '_ {
|
||||
self.destroyed_accounts.iter().map(|hashed_address| (*hashed_address, None)).chain(
|
||||
self.accounts.iter().map(|(hashed_address, account)| (*hashed_address, Some(*account))),
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns all account storages.
|
||||
pub fn storages(&self) -> impl Iterator<Item = (&B256, &HashedStorage)> {
|
||||
self.storages.iter()
|
||||
}
|
||||
|
||||
/// Sort account and storage entries.
|
||||
pub fn sort(&mut self) {
|
||||
if !self.sorted {
|
||||
for (_, storage) in self.storages.iter_mut() {
|
||||
storage.sort_storage();
|
||||
}
|
||||
|
||||
self.accounts.sort_unstable_by_key(|(address, _)| *address);
|
||||
self.sorted = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// Insert non-empty account info.
|
||||
pub fn insert_account(&mut self, hashed_address: B256, account: Account) {
|
||||
self.accounts.push((hashed_address, account));
|
||||
self.sorted = false;
|
||||
}
|
||||
|
||||
/// Insert destroyed hashed account key.
|
||||
pub fn insert_destroyed_account(&mut self, hashed_address: B256) {
|
||||
self.destroyed_accounts.insert(hashed_address);
|
||||
}
|
||||
|
||||
/// Insert hashed storage entry.
|
||||
pub fn insert_hashed_storage(&mut self, hashed_address: B256, hashed_storage: HashedStorage) {
|
||||
self.sorted &= hashed_storage.sorted;
|
||||
self.storages.insert(hashed_address, hashed_storage);
|
||||
}
|
||||
|
||||
/// Returns all destroyed accounts.
|
||||
pub fn destroyed_accounts(&self) -> AHashSet<B256> {
|
||||
self.destroyed_accounts.clone()
|
||||
}
|
||||
|
||||
/// Construct (PrefixSet)[PrefixSet] from hashed post state.
|
||||
/// The prefix sets contain the hashed account and storage keys that have been changed in the
|
||||
/// post state.
|
||||
pub fn construct_prefix_sets(&self) -> (PrefixSet, AHashMap<B256, PrefixSet>) {
|
||||
// Initialize prefix sets.
|
||||
let mut account_prefix_set = PrefixSetMut::default();
|
||||
let mut storage_prefix_set: AHashMap<B256, PrefixSetMut> = AHashMap::default();
|
||||
|
||||
// Populate account prefix set.
|
||||
for (hashed_address, _) in &self.accounts {
|
||||
account_prefix_set.insert(Nibbles::unpack(hashed_address));
|
||||
}
|
||||
for hashed_address in &self.destroyed_accounts {
|
||||
account_prefix_set.insert(Nibbles::unpack(hashed_address));
|
||||
}
|
||||
|
||||
// Populate storage prefix sets.
|
||||
for (hashed_address, hashed_storage) in self.storages.iter() {
|
||||
account_prefix_set.insert(Nibbles::unpack(hashed_address));
|
||||
|
||||
let storage_prefix_set_entry = storage_prefix_set.entry(*hashed_address).or_default();
|
||||
for (hashed_slot, _) in &hashed_storage.non_zero_valued_storage {
|
||||
storage_prefix_set_entry.insert(Nibbles::unpack(hashed_slot));
|
||||
}
|
||||
for hashed_slot in &hashed_storage.zero_valued_slots {
|
||||
storage_prefix_set_entry.insert(Nibbles::unpack(hashed_slot));
|
||||
}
|
||||
}
|
||||
|
||||
(
|
||||
account_prefix_set.freeze(),
|
||||
storage_prefix_set.into_iter().map(|(k, v)| (k, v.freeze())).collect(),
|
||||
)
|
||||
}
|
||||
}
|
||||
use reth_primitives::{Account, StorageEntry, B256, U256};
|
||||
|
||||
/// The hashed cursor factory for the post state.
|
||||
#[derive(Debug)]
|
||||
@@ -573,6 +401,8 @@ where
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::HashedStorage;
|
||||
|
||||
use super::*;
|
||||
use proptest::prelude::*;
|
||||
use reth_db::{database::Database, test_utils::create_test_rw_db, transaction::DbTxMut};
|
||||
|
||||
@@ -32,6 +32,10 @@ pub use errors::*;
|
||||
// The iterators for traversing existing intermediate hashes and updated trie leaves.
|
||||
pub(crate) mod node_iter;
|
||||
|
||||
/// In-memory hashed state.
|
||||
mod state;
|
||||
pub use state::{HashedPostState, HashedStorage};
|
||||
|
||||
/// Merkle proof generation.
|
||||
pub mod proof;
|
||||
|
||||
|
||||
272
crates/trie/src/state.rs
Normal file
272
crates/trie/src/state.rs
Normal file
@@ -0,0 +1,272 @@
|
||||
use crate::{
|
||||
hashed_cursor::HashedPostStateCursorFactory,
|
||||
prefix_set::{PrefixSet, PrefixSetMut},
|
||||
updates::TrieUpdates,
|
||||
StateRoot, StateRootError,
|
||||
};
|
||||
use ahash::{AHashMap, AHashSet};
|
||||
use reth_db::transaction::DbTx;
|
||||
use reth_primitives::{
|
||||
keccak256, revm::compat::into_reth_acc, trie::Nibbles, Account, Address, B256, U256,
|
||||
};
|
||||
use revm::db::BundleAccount;
|
||||
|
||||
/// The post state with hashed addresses as keys.
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct HashedPostState {
|
||||
/// Collection of hashed addresses and their account info.
|
||||
pub(crate) accounts: Vec<(B256, Account)>,
|
||||
/// Set of destroyed account keys.
|
||||
pub(crate) destroyed_accounts: AHashSet<B256>,
|
||||
/// Map of hashed addresses to hashed storage.
|
||||
pub(crate) storages: AHashMap<B256, HashedStorage>,
|
||||
/// Flag indicating whether the account and storage entries were sorted.
|
||||
pub(crate) sorted: bool,
|
||||
}
|
||||
|
||||
impl Default for HashedPostState {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
accounts: Vec::new(),
|
||||
destroyed_accounts: AHashSet::new(),
|
||||
storages: AHashMap::new(),
|
||||
sorted: true, // empty is sorted
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl HashedPostState {
|
||||
/// Initialize [HashedPostState] from bundle state.
|
||||
/// Hashes all changed accounts and storage entries that are currently stored in the bundle
|
||||
/// state.
|
||||
pub fn from_bundle_state<'a>(
|
||||
state: impl IntoIterator<Item = (&'a Address, &'a BundleAccount)>,
|
||||
) -> Self {
|
||||
let mut hashed_state = Self::default();
|
||||
|
||||
for (address, account) in state {
|
||||
let hashed_address = keccak256(address);
|
||||
if let Some(account) = &account.info {
|
||||
hashed_state.insert_account(hashed_address, into_reth_acc(account.clone()))
|
||||
} else {
|
||||
hashed_state.insert_destroyed_account(hashed_address);
|
||||
}
|
||||
|
||||
// insert storage.
|
||||
let mut hashed_storage = HashedStorage::new(account.status.was_destroyed());
|
||||
|
||||
for (key, value) in account.storage.iter() {
|
||||
let hashed_key = keccak256(B256::new(key.to_be_bytes()));
|
||||
if value.present_value.is_zero() {
|
||||
hashed_storage.insert_zero_valued_slot(hashed_key);
|
||||
} else {
|
||||
hashed_storage.insert_non_zero_valued_storage(hashed_key, value.present_value);
|
||||
}
|
||||
}
|
||||
hashed_state.insert_hashed_storage(hashed_address, hashed_storage)
|
||||
}
|
||||
hashed_state.sorted()
|
||||
}
|
||||
|
||||
/// Sort and return self.
|
||||
pub fn sorted(mut self) -> Self {
|
||||
self.sort();
|
||||
self
|
||||
}
|
||||
|
||||
/// Returns all accounts with their state.
|
||||
pub fn accounts(&self) -> impl Iterator<Item = (B256, Option<Account>)> + '_ {
|
||||
self.destroyed_accounts.iter().map(|hashed_address| (*hashed_address, None)).chain(
|
||||
self.accounts.iter().map(|(hashed_address, account)| (*hashed_address, Some(*account))),
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns all account storages.
|
||||
pub fn storages(&self) -> impl Iterator<Item = (&B256, &HashedStorage)> {
|
||||
self.storages.iter()
|
||||
}
|
||||
|
||||
/// Sort account and storage entries.
|
||||
pub fn sort(&mut self) {
|
||||
if !self.sorted {
|
||||
for (_, storage) in self.storages.iter_mut() {
|
||||
storage.sort_storage();
|
||||
}
|
||||
|
||||
self.accounts.sort_unstable_by_key(|(address, _)| *address);
|
||||
self.sorted = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// Insert non-empty account info.
|
||||
pub fn insert_account(&mut self, hashed_address: B256, account: Account) {
|
||||
self.accounts.push((hashed_address, account));
|
||||
self.sorted = false;
|
||||
}
|
||||
|
||||
/// Insert destroyed hashed account key.
|
||||
pub fn insert_destroyed_account(&mut self, hashed_address: B256) {
|
||||
self.destroyed_accounts.insert(hashed_address);
|
||||
}
|
||||
|
||||
/// Insert hashed storage entry.
|
||||
pub fn insert_hashed_storage(&mut self, hashed_address: B256, hashed_storage: HashedStorage) {
|
||||
self.sorted &= hashed_storage.sorted;
|
||||
self.storages.insert(hashed_address, hashed_storage);
|
||||
}
|
||||
|
||||
/// Returns all destroyed accounts.
|
||||
pub fn destroyed_accounts(&self) -> AHashSet<B256> {
|
||||
self.destroyed_accounts.clone()
|
||||
}
|
||||
|
||||
/// Construct [PrefixSet] from hashed post state.
|
||||
/// The prefix sets contain the hashed account and storage keys that have been changed in the
|
||||
/// post state.
|
||||
pub fn construct_prefix_sets(&self) -> (PrefixSet, AHashMap<B256, PrefixSet>) {
|
||||
// Initialize prefix sets.
|
||||
let mut account_prefix_set = PrefixSetMut::default();
|
||||
let mut storage_prefix_set: AHashMap<B256, PrefixSetMut> = AHashMap::default();
|
||||
|
||||
// Populate account prefix set.
|
||||
for (hashed_address, _) in &self.accounts {
|
||||
account_prefix_set.insert(Nibbles::unpack(hashed_address));
|
||||
}
|
||||
for hashed_address in &self.destroyed_accounts {
|
||||
account_prefix_set.insert(Nibbles::unpack(hashed_address));
|
||||
}
|
||||
|
||||
// Populate storage prefix sets.
|
||||
for (hashed_address, hashed_storage) in self.storages.iter() {
|
||||
account_prefix_set.insert(Nibbles::unpack(hashed_address));
|
||||
|
||||
let storage_prefix_set_entry = storage_prefix_set.entry(*hashed_address).or_default();
|
||||
for (hashed_slot, _) in &hashed_storage.non_zero_valued_storage {
|
||||
storage_prefix_set_entry.insert(Nibbles::unpack(hashed_slot));
|
||||
}
|
||||
for hashed_slot in &hashed_storage.zero_valued_slots {
|
||||
storage_prefix_set_entry.insert(Nibbles::unpack(hashed_slot));
|
||||
}
|
||||
}
|
||||
|
||||
(
|
||||
account_prefix_set.freeze(),
|
||||
storage_prefix_set.into_iter().map(|(k, v)| (k, v.freeze())).collect(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns [StateRoot] calculator based on database and in-memory state.
|
||||
fn state_root_calculator<'a, TX: DbTx>(
|
||||
&self,
|
||||
tx: &'a TX,
|
||||
) -> StateRoot<&'a TX, HashedPostStateCursorFactory<'a, '_, TX>> {
|
||||
let (account_prefix_set, storage_prefix_set) = self.construct_prefix_sets();
|
||||
let hashed_cursor_factory = HashedPostStateCursorFactory::new(tx, self);
|
||||
StateRoot::from_tx(tx)
|
||||
.with_hashed_cursor_factory(hashed_cursor_factory)
|
||||
.with_changed_account_prefixes(account_prefix_set)
|
||||
.with_changed_storage_prefixes(storage_prefix_set)
|
||||
.with_destroyed_accounts(self.destroyed_accounts())
|
||||
}
|
||||
|
||||
/// Calculate the state root for this [HashedPostState].
|
||||
/// Internally, this method retrieves prefixsets and uses them
|
||||
/// to calculate incremental state root.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use reth_db::{database::Database, test_utils::create_test_rw_db};
|
||||
/// use reth_primitives::{Account, U256};
|
||||
/// use reth_trie::HashedPostState;
|
||||
///
|
||||
/// // Initialize the database
|
||||
/// let db = create_test_rw_db();
|
||||
///
|
||||
/// // Initialize hashed post state
|
||||
/// let mut hashed_state = HashedPostState::default();
|
||||
/// hashed_state.insert_account(
|
||||
/// [0x11; 32].into(),
|
||||
/// Account { nonce: 1, balance: U256::from(10), bytecode_hash: None },
|
||||
/// );
|
||||
/// hashed_state.sort();
|
||||
///
|
||||
/// // Calculate the state root
|
||||
/// let tx = db.tx().expect("failed to create transaction");
|
||||
/// let state_root = hashed_state.state_root(&tx);
|
||||
/// ```
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// The state root for this [HashedPostState].
|
||||
pub fn state_root<TX: DbTx>(&self, tx: &TX) -> Result<B256, StateRootError> {
|
||||
self.state_root_calculator(tx).root()
|
||||
}
|
||||
|
||||
/// Calculates the state root for this [HashedPostState] and returns it alongside trie updates.
|
||||
/// See [Self::state_root] for more info.
|
||||
pub fn state_root_with_updates<TX: DbTx>(
|
||||
&self,
|
||||
tx: &TX,
|
||||
) -> Result<(B256, TrieUpdates), StateRootError> {
|
||||
self.state_root_calculator(tx).root_with_updates()
|
||||
}
|
||||
}
|
||||
|
||||
/// The post state account storage with hashed slots.
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct HashedStorage {
|
||||
/// Hashed storage slots with non-zero.
|
||||
pub(crate) non_zero_valued_storage: Vec<(B256, U256)>,
|
||||
/// Slots that have been zero valued.
|
||||
pub(crate) zero_valued_slots: AHashSet<B256>,
|
||||
/// Whether the storage was wiped or not.
|
||||
pub(crate) wiped: bool,
|
||||
/// Whether the storage entries were sorted or not.
|
||||
pub(crate) sorted: bool,
|
||||
}
|
||||
|
||||
impl HashedStorage {
|
||||
/// Create new instance of [HashedStorage].
|
||||
pub fn new(wiped: bool) -> Self {
|
||||
Self {
|
||||
non_zero_valued_storage: Vec::new(),
|
||||
zero_valued_slots: AHashSet::new(),
|
||||
wiped,
|
||||
sorted: true, // empty is sorted
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if the storage was wiped.
|
||||
pub fn wiped(&self) -> bool {
|
||||
self.wiped
|
||||
}
|
||||
|
||||
/// Returns all storage slots.
|
||||
pub fn storage_slots(&self) -> impl Iterator<Item = (B256, U256)> + '_ {
|
||||
self.zero_valued_slots
|
||||
.iter()
|
||||
.map(|slot| (*slot, U256::ZERO))
|
||||
.chain(self.non_zero_valued_storage.iter().cloned())
|
||||
}
|
||||
|
||||
/// Sorts the non zero value storage entries.
|
||||
pub fn sort_storage(&mut self) {
|
||||
if !self.sorted {
|
||||
self.non_zero_valued_storage.sort_unstable_by_key(|(slot, _)| *slot);
|
||||
self.sorted = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// Insert non zero-valued storage entry.
|
||||
pub fn insert_non_zero_valued_storage(&mut self, slot: B256, value: U256) {
|
||||
debug_assert!(value != U256::ZERO, "value cannot be zero");
|
||||
self.non_zero_valued_storage.push((slot, value));
|
||||
self.sorted = false;
|
||||
}
|
||||
|
||||
/// Insert zero-valued storage slot.
|
||||
pub fn insert_zero_valued_slot(&mut self, slot: B256) {
|
||||
self.zero_valued_slots.insert(slot);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user