//! Benchmark for `StateRootTask` complete workflow, including sending state //! updates using the incoming messages sender and waiting for the final result. #![allow(missing_docs)] use alloy_consensus::constants::KECCAK_EMPTY; use alloy_evm::block::StateChangeSource; use alloy_primitives::{Address, B256}; use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; use proptest::test_runner::TestRunner; use rand::Rng; use reth_chain_state::EthPrimitives; use reth_chainspec::ChainSpec; use reth_db_common::init::init_genesis; use reth_engine_tree::tree::{ executor::WorkloadExecutor, precompile_cache::PrecompileCacheMap, PayloadProcessor, StateProviderBuilder, TreeConfig, }; use reth_evm::OnStateHook; use reth_evm_ethereum::EthEvmConfig; use reth_primitives_traits::{Account as RethAccount, StorageEntry}; use reth_provider::{ providers::{BlockchainProvider, ConsistentDbView}, test_utils::{create_test_provider_factory_with_chain_spec, MockNodeTypesWithDB}, AccountReader, ChainSpecProvider, HashingWriter, ProviderFactory, }; use reth_trie::TrieInput; use revm_primitives::{HashMap, U256}; use revm_state::{Account as RevmAccount, AccountInfo, AccountStatus, EvmState, EvmStorageSlot}; use std::{hint::black_box, sync::Arc}; #[derive(Debug, Clone)] struct BenchParams { num_accounts: usize, updates_per_account: usize, storage_slots_per_account: usize, selfdestructs_per_update: usize, } /// Generates a series of random state updates with configurable accounts, /// storage, and self-destructs fn create_bench_state_updates(params: &BenchParams) -> Vec { let mut runner = TestRunner::deterministic(); let mut rng = runner.rng().clone(); let all_addresses: Vec
= (0..params.num_accounts) .map(|_| { // TODO: rand08 Address::random() }) .collect(); let mut updates = Vec::new(); for _ in 0..params.updates_per_account { let mut state_update = EvmState::default(); let num_accounts_in_update = rng.random_range(1..=params.num_accounts); // regular updates for randomly selected accounts for &address in &all_addresses[0..num_accounts_in_update] { // randomly choose to self-destruct with probability // (selfdestructs/accounts) let is_selfdestruct = rng .random_bool(params.selfdestructs_per_update as f64 / params.num_accounts as f64); let account = if is_selfdestruct { RevmAccount { info: AccountInfo::default(), storage: HashMap::default(), status: AccountStatus::SelfDestructed, transaction_id: 0, } } else { RevmAccount { info: AccountInfo { balance: U256::from(rng.random::()), nonce: rng.random::(), code_hash: KECCAK_EMPTY, code: Some(Default::default()), }, storage: (0..rng.random_range(0..=params.storage_slots_per_account)) .map(|_| { ( U256::from(rng.random::()), EvmStorageSlot::new_changed( U256::ZERO, U256::from(rng.random::()), 0, ), ) }) .collect(), status: AccountStatus::Touched, transaction_id: 0, } }; state_update.insert(address, account); } updates.push(state_update); } updates } fn convert_revm_to_reth_account(revm_account: &RevmAccount) -> Option { match revm_account.status { AccountStatus::SelfDestructed => None, _ => Some(RethAccount { balance: revm_account.info.balance, nonce: revm_account.info.nonce, bytecode_hash: if revm_account.info.code_hash == KECCAK_EMPTY { None } else { Some(revm_account.info.code_hash) }, }), } } /// Applies state updates to the provider, ensuring self-destructs only affect /// existing accounts fn setup_provider( factory: &ProviderFactory, state_updates: &[EvmState], ) -> Result<(), Box> { for update in state_updates { let provider_rw = factory.provider_rw()?; let mut account_updates = Vec::new(); for (address, account) in update { // only process self-destructs if account exists, always process // other updates let should_process = match account.status { AccountStatus::SelfDestructed => { provider_rw.basic_account(address).ok().flatten().is_some() } _ => true, }; if should_process { account_updates.push(( *address, convert_revm_to_reth_account(account), (account.status == AccountStatus::Touched).then(|| { account .storage .iter() .map(|(slot, value)| StorageEntry { key: B256::from(*slot), value: value.present_value, }) .collect::>() }), )); } } // update in the provider account and its storage (if available) for (address, account, maybe_storage) in account_updates { provider_rw.insert_account_for_hashing(std::iter::once((address, account)))?; if let Some(storage) = maybe_storage { provider_rw .insert_storage_for_hashing(std::iter::once((address, storage.into_iter())))?; } } provider_rw.commit()?; } Ok(()) } fn bench_state_root(c: &mut Criterion) { reth_tracing::init_test_tracing(); let mut group = c.benchmark_group("state_root"); let scenarios = vec![ BenchParams { num_accounts: 100, updates_per_account: 5, storage_slots_per_account: 10, selfdestructs_per_update: 2, }, BenchParams { num_accounts: 1000, updates_per_account: 10, storage_slots_per_account: 20, selfdestructs_per_update: 5, }, BenchParams { num_accounts: 500, updates_per_account: 8, storage_slots_per_account: 15, selfdestructs_per_update: 20, }, ]; for params in scenarios { group.bench_with_input( BenchmarkId::new( "state_root_task", format!( "accounts_{}_updates_{}_slots_{}_selfdestructs_{}", params.num_accounts, params.updates_per_account, params.storage_slots_per_account, params.selfdestructs_per_update ), ), ¶ms, |b, params| { b.iter_with_setup( || { let factory = create_test_provider_factory_with_chain_spec(Arc::new( ChainSpec::default(), )); let genesis_hash = init_genesis(&factory).unwrap(); let state_updates = create_bench_state_updates(params); setup_provider(&factory, &state_updates).expect("failed to setup provider"); let payload_processor = PayloadProcessor::::new( WorkloadExecutor::default(), EthEvmConfig::new(factory.chain_spec()), &TreeConfig::default(), PrecompileCacheMap::default(), ); let provider = BlockchainProvider::new(factory).unwrap(); (genesis_hash, payload_processor, provider, state_updates) }, |(genesis_hash, mut payload_processor, provider, state_updates)| { black_box({ let mut handle = payload_processor.spawn( Default::default(), Default::default(), StateProviderBuilder::new(provider.clone(), genesis_hash, None), ConsistentDbView::new_with_latest_tip(provider).unwrap(), TrieInput::default(), &TreeConfig::default(), ); let mut state_hook = handle.state_hook(); for (i, update) in state_updates.into_iter().enumerate() { state_hook.on_state(StateChangeSource::Transaction(i), &update); } drop(state_hook); handle.state_root().expect("task failed") }); }, ) }, ); } group.finish(); } criterion_group!(benches, bench_state_root); criterion_main!(benches);