diff --git a/Cargo.lock b/Cargo.lock
index 4eabcf68bd13..2f642edbac89 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -7928,6 +7928,7 @@ dependencies = [
"metrics",
"once_cell",
"proptest",
+ "rayon",
"reth-db",
"reth-execution-errors",
"reth-metrics",
diff --git a/Cargo.toml b/Cargo.toml
index d73f2d29bb00..f6ae59338970 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -302,14 +302,8 @@ reth-trie = { path = "crates/trie/trie" }
reth-trie-parallel = { path = "crates/trie/parallel" }
# revm
-revm = { version = "9.0.0", features = [
- "std",
- "secp256k1",
- "blst",
-], default-features = false }
-revm-primitives = { version = "4.0.0", features = [
- "std",
-], default-features = false }
+revm = { version = "9.0.0", features = [ "std", "secp256k1", "blst", ], default-features = false }
+revm-primitives = { version = "4.0.0", features = [ "std", ], default-features = false }
revm-inspectors = { git = "https://github.com/paradigmxyz/evm-inspectors", rev = "2b6fff1" }
# eth
diff --git a/crates/trie/trie/Cargo.toml b/crates/trie/trie/Cargo.toml
index a09615d2203b..1842eae09d58 100644
--- a/crates/trie/trie/Cargo.toml
+++ b/crates/trie/trie/Cargo.toml
@@ -26,6 +26,7 @@ alloy-rlp.workspace = true
tracing.workspace = true
# misc
+rayon.workspace = true
derive_more.workspace = true
auto_impl.workspace = true
@@ -62,3 +63,7 @@ test-utils = ["triehash"]
[[bench]]
name = "prefix_set"
harness = false
+
+[[bench]]
+name = "hash_post_state"
+harness = false
diff --git a/crates/trie/trie/benches/hash_post_state.rs b/crates/trie/trie/benches/hash_post_state.rs
new file mode 100644
index 000000000000..48a71ba6ddb1
--- /dev/null
+++ b/crates/trie/trie/benches/hash_post_state.rs
@@ -0,0 +1,80 @@
+#![allow(missing_docs, unreachable_pub)]
+use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
+use proptest::{prelude::*, strategy::ValueTree, test_runner::TestRunner};
+use reth_primitives::{keccak256, revm::compat::into_reth_acc, Address, B256, U256};
+use reth_trie::{HashedPostState, HashedStorage};
+use revm::db::{states::BundleBuilder, BundleAccount};
+use std::collections::HashMap;
+
+pub fn hash_post_state(c: &mut Criterion) {
+ let mut group = c.benchmark_group("Hash Post State");
+ group.sample_size(20);
+
+ for size in [100, 1_000, 3_000, 5_000, 10_000] {
+ let state = generate_test_data(size);
+
+ // sequence
+ group.bench_function(BenchmarkId::new("sequence hashing", size), |b| {
+ b.iter(|| from_bundle_state_seq(&state))
+ });
+
+ // parallel
+ group.bench_function(BenchmarkId::new("parallel hashing", size), |b| {
+ b.iter(|| HashedPostState::from_bundle_state(&state))
+ });
+ }
+}
+
+fn from_bundle_state_seq(state: &HashMap
) -> HashedPostState {
+ let mut this = HashedPostState::default();
+
+ for (address, account) in state {
+ let hashed_address = keccak256(address);
+ this.accounts.insert(hashed_address, account.info.clone().map(into_reth_acc));
+
+ let hashed_storage = HashedStorage::from_iter(
+ account.status.was_destroyed(),
+ account
+ .storage
+ .iter()
+ .map(|(key, value)| (keccak256(B256::new(key.to_be_bytes())), value.present_value)),
+ );
+ this.storages.insert(hashed_address, hashed_storage);
+ }
+ this
+}
+
+fn generate_test_data(size: usize) -> HashMap {
+ let storage_size = 1_000;
+ let mut runner = TestRunner::new(ProptestConfig::default());
+
+ use proptest::collection::hash_map;
+ let state = hash_map(
+ any::(),
+ hash_map(
+ any::(), // slot
+ (
+ any::(), // old value
+ any::(), // new value
+ ),
+ storage_size,
+ ),
+ size,
+ )
+ .new_tree(&mut runner)
+ .unwrap()
+ .current();
+
+ let mut bundle_builder = BundleBuilder::default();
+
+ for (address, storage) in state.into_iter() {
+ bundle_builder = bundle_builder.state_storage(address, storage);
+ }
+
+ let bundle_state = bundle_builder.build();
+
+ bundle_state.state
+}
+
+criterion_group!(post_state, hash_post_state);
+criterion_main!(post_state);
diff --git a/crates/trie/trie/src/state.rs b/crates/trie/trie/src/state.rs
index 6637e9dd9fce..b7b145e8ec8f 100644
--- a/crates/trie/trie/src/state.rs
+++ b/crates/trie/trie/src/state.rs
@@ -4,6 +4,7 @@ use crate::{
updates::TrieUpdates,
StateRoot,
};
+use rayon::prelude::{IntoParallelIterator, ParallelIterator};
use reth_db::{
cursor::DbCursorRO,
models::{AccountBeforeTx, BlockNumberAddress},
@@ -36,22 +37,30 @@ impl HashedPostState {
/// Hashes all changed accounts and storage entries that are currently stored in the bundle
/// state.
pub fn from_bundle_state<'a>(
- state: impl IntoIterator- ,
+ state: impl IntoParallelIterator
- ,
) -> Self {
- let mut this = Self::default();
- for (address, account) in state {
- let hashed_address = keccak256(address);
- this.accounts.insert(hashed_address, account.info.clone().map(into_reth_acc));
-
- let hashed_storage = HashedStorage::from_iter(
- account.status.was_destroyed(),
- account.storage.iter().map(|(key, value)| {
- (keccak256(B256::new(key.to_be_bytes())), value.present_value)
- }),
- );
- this.storages.insert(hashed_address, hashed_storage);
+ let hashed = state
+ .into_par_iter()
+ .map(|(address, account)| {
+ let hashed_address = keccak256(address);
+ let hashed_account = account.info.clone().map(into_reth_acc);
+ let hashed_storage = HashedStorage::from_iter(
+ account.status.was_destroyed(),
+ account.storage.iter().map(|(key, value)| {
+ (keccak256(B256::new(key.to_be_bytes())), value.present_value)
+ }),
+ );
+ (hashed_address, (hashed_account, hashed_storage))
+ })
+ .collect::, HashedStorage))>>();
+
+ let mut accounts = HashMap::with_capacity(hashed.len());
+ let mut storages = HashMap::with_capacity(hashed.len());
+ for (address, (account, storage)) in hashed {
+ accounts.insert(address, account);
+ storages.insert(address, storage);
}
- this
+ Self { accounts, storages }
}
/// Initialize [`HashedPostState`] from revert range.
@@ -325,6 +334,12 @@ pub struct HashedStorageSorted {
#[cfg(test)]
mod tests {
use super::*;
+ use reth_db::{database::Database, test_utils::create_test_rw_db};
+ use reth_primitives::hex;
+ use revm::{
+ db::states::BundleState,
+ primitives::{AccountInfo, HashMap},
+ };
#[test]
fn hashed_state_wiped_extension() {
@@ -399,4 +414,34 @@ mod tests {
);
assert_eq!(account_storage.map(|st| st.wiped), Some(true));
}
+
+ #[test]
+ fn from_bundle_state_with_rayon() {
+ let address1 = Address::with_last_byte(1);
+ let address2 = Address::with_last_byte(2);
+ let slot1 = U256::from(1015);
+ let slot2 = U256::from(2015);
+
+ let account1 = AccountInfo { nonce: 1, ..Default::default() };
+ let account2 = AccountInfo { nonce: 2, ..Default::default() };
+
+ let bundle_state = BundleState::builder(2..=2)
+ .state_present_account_info(address1, account1)
+ .state_present_account_info(address2, account2)
+ .state_storage(address1, HashMap::from([(slot1, (U256::ZERO, U256::from(10)))]))
+ .state_storage(address2, HashMap::from([(slot2, (U256::ZERO, U256::from(20)))]))
+ .build();
+ assert_eq!(bundle_state.reverts.len(), 1);
+
+ let post_state = HashedPostState::from_bundle_state(&bundle_state.state);
+ assert_eq!(post_state.accounts.len(), 2);
+ assert_eq!(post_state.storages.len(), 2);
+
+ let db = create_test_rw_db();
+ let tx = db.tx().expect("failed to create transaction");
+ assert_eq!(
+ post_state.state_root(&tx).unwrap(),
+ hex!("b464525710cafcf5d4044ac85b72c08b1e76231b8d91f288fe438cc41d8eaafd")
+ );
+ }
}