1use super::{bal_prewarm_pool::BalPrewarmPool, StateRootHintStream, StateRootUpdateStream};
15use crate::tree::{
16 precompile_cache::{CachedPrecompile, PrecompileCacheMap},
17 CachedStateCacheMetrics, CachedStateMetrics, CachedStateProvider, ExecutionEnv,
18 PayloadExecutionCache, SavedCache,
19};
20use alloy_consensus::transaction::TxHashRef;
21use alloy_eip7928::bal::DecodedBal;
22use alloy_eips::eip4895::Withdrawal;
23use alloy_primitives::{keccak256, B256, U256};
24use metrics::{Counter, Gauge, Histogram};
25use rayon::prelude::*;
26use reth_evm::{execute::ExecutableTxFor, ConfigureEvm, Evm, EvmFor, RecoveredTx, SpecFor};
27use reth_metrics::Metrics;
28use reth_primitives_traits::{Account, FastInstant as Instant, NodePrimitives};
29use reth_provider::{
30 AccountReader, BlockExecutionOutput, BlockNumReader, ChangeSetReader, DatabaseProviderFactory,
31 DatabaseProviderROFactory, HistoryReader, PruneCheckpointReader, StageCheckpointReader,
32 StateProviderBox, StorageChangeSetReader, StorageSettingsCache,
33};
34use reth_revm::database::StateProviderDatabase;
35use reth_storage_overlay::OverlayStateProviderFactory;
36use reth_tasks::{pool::WorkerPool, Runtime};
37use reth_trie_common::MultiProofTargetsV2;
38use std::sync::{
39 atomic::{AtomicBool, AtomicUsize, Ordering},
40 mpsc::{self, channel, Receiver, Sender},
41 Arc,
42};
43use tokio::sync::oneshot;
44use tracing::{debug, debug_span, instrument, trace, trace_span, warn, Span};
45
46#[derive(Debug)]
51pub enum PrewarmMode<Tx> {
52 Transactions {
54 pending: Receiver<(usize, Tx)>,
56 hints: Option<StateRootHintStream>,
58 },
59 BlockAccessList {
61 bal: Arc<DecodedBal>,
63 updates: Option<StateRootUpdateStream>,
65 },
66 Skipped,
69}
70
71#[derive(Debug)]
76pub struct PrewarmCacheTask<N, P, Evm>
77where
78 N: NodePrimitives,
79 Evm: ConfigureEvm<Primitives = N>,
80{
81 executor: Runtime,
83 execution_cache: PayloadExecutionCache,
85 ctx: PrewarmContext<N, P, Evm>,
87 actions_rx: Receiver<PrewarmTaskEvent<N::Receipt>>,
89 parent_span: Span,
91}
92
93impl<N, P, Evm> PrewarmCacheTask<N, P, Evm>
94where
95 N: NodePrimitives,
96 P: DatabaseProviderFactory + Clone + 'static,
97 P::Provider: BlockNumReader
98 + PruneCheckpointReader
99 + StageCheckpointReader
100 + ChangeSetReader
101 + StorageChangeSetReader
102 + StorageSettingsCache
103 + HistoryReader
104 + 'static,
105 Evm: ConfigureEvm<Primitives = N> + 'static,
106{
107 pub fn new(
109 executor: Runtime,
110 execution_cache: PayloadExecutionCache,
111 ctx: PrewarmContext<N, P, Evm>,
112 ) -> (Self, Sender<PrewarmTaskEvent<N::Receipt>>) {
113 let (actions_tx, actions_rx) = channel();
114
115 trace!(
116 target: "engine::tree::payload_processor::prewarm",
117 prewarming_threads = executor.prewarming_pool().current_num_threads(),
118 transaction_count = ctx.env.transaction_count,
119 "Initialized prewarm task"
120 );
121
122 (
123 Self { executor, execution_cache, ctx, actions_rx, parent_span: Span::current() },
124 actions_tx,
125 )
126 }
127
128 fn spawn_txs_prewarm<Tx>(
135 &self,
136 pending: mpsc::Receiver<(usize, Tx)>,
137 actions_tx: Sender<PrewarmTaskEvent<N::Receipt>>,
138 state_root_hint_stream: Option<StateRootHintStream>,
139 ) where
140 Tx: ExecutableTxFor<Evm> + Send + 'static,
141 {
142 let executor = self.executor.clone();
143 let ctx = self.ctx.clone();
144 let span = Span::current();
145
146 self.executor.spawn_blocking_named("prewarm-txs", move || {
147 let _enter = debug_span!(
148 target: "engine::tree::payload_processor::prewarm",
149 parent: &span,
150 "prewarm_txs"
151 )
152 .entered();
153
154 let ctx = &ctx;
155 let pool = executor.prewarming_pool();
156
157 let mut tx_count = 0usize;
158 let state_root_hint_stream = state_root_hint_stream.as_ref();
159 pool.in_place_scope(|s| {
160 s.spawn(|_| {
161 pool.init::<PrewarmEvmState<Evm>>(|_| ctx.evm_for_ctx());
162 });
163
164 while let Ok((index, tx)) = pending.recv() {
165 if ctx.should_stop() {
166 trace!(
167 target: "engine::tree::payload_processor::prewarm",
168 "Termination requested, stopping transaction distribution"
169 );
170 break;
171 }
172
173 if index < ctx.executed_tx_index.load(Ordering::Relaxed) {
175 continue;
176 }
177
178 tx_count += 1;
179 let parent_span = Span::current();
180 s.spawn(move |_| {
181 let _enter = trace_span!(
182 target: "engine::tree::payload_processor::prewarm",
183 parent: parent_span,
184 "prewarm_tx",
185 i = index,
186 )
187 .entered();
188 Self::transact_worker(ctx, index, tx, state_root_hint_stream);
189 });
190 }
191
192 if let Some(state_root_hint_stream) = state_root_hint_stream &&
194 let Some(withdrawals) = &ctx.env.withdrawals &&
195 !withdrawals.is_empty()
196 {
197 let targets = multiproof_targets_from_withdrawals(withdrawals);
198 state_root_hint_stream.on_access_hint(targets.into());
199 }
200 });
201
202 pool.clear();
204
205 let _ = actions_tx
206 .send(PrewarmTaskEvent::FinishedTxExecution { executed_transactions: tx_count });
207 });
208 }
209
210 fn transact_worker<Tx>(
215 ctx: &PrewarmContext<N, P, Evm>,
216 index: usize,
217 tx: Tx,
218 state_root_hint_stream: Option<&StateRootHintStream>,
219 ) where
220 Tx: ExecutableTxFor<Evm>,
221 {
222 WorkerPool::with_worker_mut(|worker| {
223 let Some(evm) =
224 worker.get_or_init::<PrewarmEvmState<Evm>>(|| ctx.evm_for_ctx()).as_mut()
225 else {
226 return;
227 };
228
229 if ctx.should_stop() {
230 return;
231 }
232
233 if index < ctx.executed_tx_index.load(Ordering::Relaxed) {
235 return;
236 }
237
238 let start = Instant::now();
239
240 let (tx_env, tx) = tx.into_parts();
241 let res = match evm.transact(tx_env) {
242 Ok(res) => res,
243 Err(err) => {
244 trace!(
245 target: "engine::tree::payload_processor::prewarm",
246 %err,
247 tx_hash=%tx.tx().tx_hash(),
248 sender=%tx.signer(),
249 "Error when executing prewarm transaction",
250 );
251 ctx.metrics.transaction_errors.increment(1);
252 return;
253 }
254 };
255 ctx.metrics.execution_duration.record(start.elapsed());
256
257 if ctx.should_stop() {
258 return;
259 }
260
261 if index > 0 {
262 let (targets, storage_targets) = MultiProofTargetsV2::from_state(res.state);
263 ctx.metrics.prefetch_storage_targets.record(storage_targets as f64);
264 if let Some(state_root_hint_stream) = state_root_hint_stream {
265 state_root_hint_stream.on_access_hint(targets.into());
266 }
267 }
268
269 ctx.metrics.total_runtime.record(start.elapsed());
270 });
271 }
272
273 #[instrument(level = "debug", target = "engine::tree::payload_processor::prewarm", skip_all)]
285 fn save_cache(
286 self,
287 execution_outcome: Arc<BlockExecutionOutput<N::Receipt>>,
288 valid_block_rx: mpsc::Receiver<()>,
289 ) {
290 let start = Instant::now();
291
292 let Self {
293 execution_cache,
294 ctx: PrewarmContext { env, metrics, cache_state_metrics, saved_cache, .. },
295 ..
296 } = self;
297 let hash = env.hash;
298
299 if let Some(saved_cache) = saved_cache {
300 debug!(target: "engine::caching", parent_hash=?hash, "Updating execution cache");
301 execution_cache.update_with_guard(|cached| {
302 let caches = saved_cache.cache().clone();
305 let new_cache = SavedCache::new(hash, caches);
306
307 if new_cache.cache().insert_state(&execution_outcome.state).is_err() {
310 *cached = None;
312 debug!(target: "engine::caching", "cleared execution cache on update error");
313 return;
314 }
315
316 new_cache.update_metrics(cache_state_metrics.as_ref());
317
318 if valid_block_rx.recv().is_ok() {
319 *cached = Some(new_cache);
322 } else {
323 *cached = None;
326 debug!(target: "engine::caching", "cleared execution cache on invalid block");
327 }
328 });
329
330 let elapsed = start.elapsed();
331 debug!(target: "engine::caching", parent_hash=?hash, elapsed=?elapsed, "Updated execution cache");
332
333 metrics.cache_saving_duration.set(elapsed.as_secs_f64());
334 }
335 }
336
337 #[instrument(level = "debug", target = "engine::tree::payload_processor::prewarm", skip_all)]
345 fn run_bal_prewarm(
346 &self,
347 decoded_bal: Arc<DecodedBal>,
348 actions_tx: Sender<PrewarmTaskEvent<N::Receipt>>,
349 hashed_update_stream: Option<StateRootUpdateStream>,
350 ) {
351 let bal = decoded_bal.as_bal();
352 if bal.is_empty() {
353 if let Some(hashed_update_stream) = hashed_update_stream {
354 hashed_update_stream.finish();
355 }
356 let _ =
357 actions_tx.send(PrewarmTaskEvent::FinishedTxExecution { executed_transactions: 0 });
358 return;
359 }
360
361 trace!(
362 target: "engine::tree::payload_processor::prewarm",
363 accounts = bal.len(),
364 "Starting BAL prewarm"
365 );
366
367 let ctx = self.ctx.clone();
368 let executor = self.executor.clone();
369 let parent_span = Span::current();
370 let stream_parent_span = parent_span;
371 let prefetch_bal = Arc::clone(&decoded_bal);
372 let stream_bal = Arc::clone(&decoded_bal);
373 let (stream_tx, stream_rx) = oneshot::channel();
374
375 if let Some(hashed_update_stream) = hashed_update_stream {
376 let ctx = ctx.clone();
377 executor.bal_streaming_pool().spawn(move || {
378 let branch_span = debug_span!(
379 target: "engine::tree::payload_processor::prewarm",
380 parent: &stream_parent_span,
381 "bal_hashed_state_stream",
382 bal_accounts = stream_bal.as_bal().len(),
383 );
384 let parent_span = branch_span.clone();
385 let _span = branch_span.entered();
386
387 stream_bal.as_bal().par_iter().for_each(|account_changes| {
388 WorkerPool::with_worker_mut(|worker| {
389 let provider =
390 worker.get_or_init::<Option<Box<dyn AccountReader>>>(|| None);
391 ctx.send_bal_hashed_state(
392 &parent_span,
393 provider,
394 account_changes,
395 &hashed_update_stream,
396 );
397 });
398 });
399
400 hashed_update_stream.finish();
401 let _ = stream_tx.send(());
402 });
403 } else {
404 let _ = stream_tx.send(());
405 }
406
407 if let Some(saved_cache) = ctx.saved_cache &&
408 !ctx.disable_bal_batch_io &&
409 let Some(pool) = ctx.bal_prewarm_pool.as_ref()
410 {
411 let caches = saved_cache.cache().clone();
424 let state_provider_factory = ctx.provider.clone();
425 let build = Arc::new(move || {
426 state_provider_factory
427 .database_provider_ro()
428 .map(|provider| Box::new(provider) as _)
429 });
430
431 pool.begin_block(build, caches, ctx.env.txpool_snapshot.clone());
432 let dispatch_start = Instant::now();
433 for account in prefetch_bal.as_bal() {
434 pool.warm_account(account.address, account.storage_slots().map(Into::into));
435 }
436 ctx.metrics.bal_slot_iteration_duration.record(dispatch_start.elapsed());
437 pool.end_block();
438 }
439
440 stream_rx
441 .blocking_recv()
442 .expect("BAL hashed-state streaming task dropped without signaling completion");
443
444 executor.bal_streaming_pool().clear();
446
447 let _ = actions_tx.send(PrewarmTaskEvent::FinishedTxExecution { executed_transactions: 0 });
448 }
449
450 #[instrument(
455 parent = &self.parent_span,
456 level = "debug",
457 target = "engine::tree::payload_processor::prewarm",
458 name = "prewarm and caching",
459 skip_all
460 )]
461 pub fn run<Tx>(self, mode: PrewarmMode<Tx>, actions_tx: Sender<PrewarmTaskEvent<N::Receipt>>)
462 where
463 Tx: ExecutableTxFor<Evm> + Send + 'static,
464 {
465 match mode {
469 PrewarmMode::Transactions { pending, hints } => {
470 self.spawn_txs_prewarm(pending, actions_tx, hints);
471 }
472 PrewarmMode::BlockAccessList { bal, updates } => {
473 self.run_bal_prewarm(bal, actions_tx, updates);
474 }
475 PrewarmMode::Skipped => {
476 let _ = actions_tx
477 .send(PrewarmTaskEvent::FinishedTxExecution { executed_transactions: 0 });
478 }
479 }
480
481 let mut final_execution_outcome = None;
482 let mut finished_execution = false;
483 while let Ok(event) = self.actions_rx.recv() {
484 match event {
485 PrewarmTaskEvent::TerminateTransactionExecution => {
486 debug!(target: "engine::tree::prewarm", "Terminating prewarm execution");
488 self.ctx.stop();
489 }
490 PrewarmTaskEvent::Terminate { execution_outcome, valid_block_rx } => {
491 trace!(target: "engine::tree::payload_processor::prewarm", "Received termination signal");
492 self.ctx.stop();
495 final_execution_outcome =
496 Some(execution_outcome.map(|outcome| (outcome, valid_block_rx)));
497
498 if finished_execution {
499 break
501 }
502 }
503 PrewarmTaskEvent::FinishedTxExecution { executed_transactions } => {
504 trace!(target: "engine::tree::payload_processor::prewarm", "Finished prewarm execution signal");
505 self.ctx.metrics.transactions.set(executed_transactions as f64);
506 self.ctx.metrics.transactions_histogram.record(executed_transactions as f64);
507
508 finished_execution = true;
509
510 if final_execution_outcome.is_some() {
511 break
513 }
514 }
515 }
516 }
517
518 debug!(target: "engine::tree::payload_processor::prewarm", "Completed prewarm execution");
519
520 if let Some(Some((execution_outcome, valid_block_rx))) = final_execution_outcome {
522 self.save_cache(execution_outcome, valid_block_rx);
523 }
524 }
525}
526
527#[derive(Debug, Clone)]
529pub struct PrewarmContext<N, P, Evm>
530where
531 N: NodePrimitives,
532 Evm: ConfigureEvm<Primitives = N>,
533{
534 pub env: ExecutionEnv<Evm>,
536 pub evm_config: Evm,
538 pub saved_cache: Option<SavedCache>,
540 pub provider: OverlayStateProviderFactory<P, N>,
542 pub(crate) bal_prewarm_pool: Option<Arc<BalPrewarmPool>>,
545 pub metrics: PrewarmMetrics,
547 pub cache_metrics: Option<CachedStateMetrics>,
550 pub cache_state_metrics: Option<CachedStateCacheMetrics>,
552 pub terminate_execution: Arc<AtomicBool>,
554 pub executed_tx_index: Arc<AtomicUsize>,
558 pub precompile_cache_disabled: bool,
560 pub precompile_cache_map: PrecompileCacheMap<SpecFor<Evm>>,
562 pub disable_bal_parallel_state_root: bool,
565 pub disable_bal_batch_io: bool,
567}
568
569type PrewarmEvmState<Evm> =
572 Option<EvmFor<Evm, StateProviderDatabase<reth_provider::StateProviderBox>>>;
573
574impl<N, P, Evm> PrewarmContext<N, P, Evm>
575where
576 N: NodePrimitives,
577 P: DatabaseProviderFactory,
578 P::Provider: BlockNumReader
579 + PruneCheckpointReader
580 + StageCheckpointReader
581 + ChangeSetReader
582 + StorageChangeSetReader
583 + StorageSettingsCache
584 + HistoryReader
585 + 'static,
586 Evm: ConfigureEvm<Primitives = N> + 'static,
587{
588 #[instrument(level = "debug", target = "engine::tree::payload_processor::prewarm", skip_all)]
590 fn evm_for_ctx(&self) -> PrewarmEvmState<Evm> {
591 let mut state_provider: StateProviderBox = match self.provider.database_provider_ro() {
592 Ok(provider) => Box::new(provider),
593 Err(err) => {
594 trace!(
595 target: "engine::tree::payload_processor::prewarm",
596 %err,
597 "Failed to build state provider in prewarm thread"
598 );
599 return None
600 }
601 };
602
603 if let Some(saved_cache) = &self.saved_cache {
605 let caches = saved_cache.cache().clone();
606 state_provider = Box::new(
607 CachedStateProvider::new_prewarm(state_provider, caches)
608 .with_txpool_snapshot(self.env.txpool_snapshot.clone()),
609 );
610 }
611
612 let state_provider = StateProviderDatabase::new(state_provider);
613
614 let mut evm_env = self.env.evm_env.clone();
615
616 evm_env.cfg_env.disable_nonce_check = true;
619
620 evm_env.cfg_env.disable_balance_check = true;
623
624 let spec_id = *evm_env.spec_id();
626 let mut evm = self.evm_config.evm_with_env(state_provider, evm_env);
627
628 if !self.precompile_cache_disabled {
629 evm.precompiles_mut().map_cacheable_precompiles(|address, precompile| {
631 CachedPrecompile::wrap(
632 precompile,
633 self.precompile_cache_map.cache_for_address(*address),
634 spec_id,
635 None, )
637 });
638 }
639
640 Some(evm)
641 }
642
643 #[inline]
645 pub fn should_stop(&self) -> bool {
646 self.terminate_execution.load(Ordering::Relaxed)
647 }
648
649 #[inline]
651 pub fn stop(&self) {
652 self.terminate_execution.store(true, Ordering::Relaxed);
653 }
654
655 fn send_bal_hashed_state(
665 &self,
666 parent_span: &Span,
667 provider: &mut Option<Box<dyn AccountReader>>,
668 account_changes: &alloy_eip7928::AccountChanges,
669 hashed_update_stream: &StateRootUpdateStream,
670 ) {
671 if self.disable_bal_parallel_state_root {
672 return;
673 }
674 let address = account_changes.address;
675 let mut hashed_address = None;
676 let account_fields = BalAccountStateFields::from_changes(account_changes);
677
678 if !bal_account_changes_state_root(account_changes, account_fields) {
679 return;
680 }
681
682 if !account_changes.storage_changes.is_empty() {
686 let hashed_address = *hashed_address.get_or_insert_with(|| keccak256(address));
687 let storage_map = reth_trie::HashedStorage::from_iter(
688 account_changes
689 .storage_post_states()
690 .map(|(slot, value)| (keccak256(slot.to_be_bytes::<32>()), value)),
691 );
692
693 let mut hashed_state = reth_trie::HashedPostState::default();
694 hashed_state.storages.insert(hashed_address, storage_map);
695 hashed_update_stream.on_hashed_state_update(hashed_state);
696 }
697
698 let existing_account = if account_fields.needs_parent_account() {
699 if provider.is_none() {
700 let _span = debug_span!(
701 target: "engine::tree::payload_processor::prewarm",
702 parent: parent_span,
703 "bal_hashed_state_provider_init",
704 has_saved_cache = !self.disable_bal_batch_io && self.saved_cache.is_some(),
705 )
706 .entered();
707
708 let inner = match self.provider.database_provider_ro() {
709 Ok(p) => p,
710 Err(err) => {
711 warn!(
712 target: "engine::tree::payload_processor::prewarm",
713 ?err,
714 "Failed to build provider for BAL account reads"
715 );
716 return;
717 }
718 };
719 let boxed: Box<dyn AccountReader> =
720 match (self.disable_bal_batch_io, &self.saved_cache) {
721 (false, Some(saved)) => {
722 let caches = saved.cache().clone();
723 Box::new(
724 CachedStateProvider::new_prewarm(inner, caches)
725 .with_txpool_snapshot(self.env.txpool_snapshot.clone()),
726 )
727 }
728 _ => Box::new(inner),
729 };
730 *provider = Some(boxed);
731 }
732 let account_reader = provider.as_ref().expect("provider just initialized");
733 account_reader.basic_account(&address).ok().flatten()
734 } else {
735 None
736 };
737
738 let account = account_fields.into_account(existing_account);
739 let hashed_address = hashed_address.unwrap_or_else(|| keccak256(address));
740
741 let account = (!account.is_empty()).then_some(account);
753
754 let mut hashed_state = reth_trie::HashedPostState::default();
755 hashed_state.accounts.insert(hashed_address, account);
756 hashed_update_stream.on_hashed_state_update(hashed_state);
757 }
758}
759
760#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
761struct BalAccountStateFields {
762 balance: Option<U256>,
763 nonce: Option<u64>,
764 code_hash: Option<B256>,
765}
766
767impl BalAccountStateFields {
768 fn from_changes(account_changes: &alloy_eip7928::AccountChanges) -> Self {
769 Self {
770 balance: account_changes.balance_post_state(),
771 nonce: account_changes.nonce_post_state(),
772 code_hash: account_changes.code_post_state().map(|code| {
773 if code.is_empty() {
774 alloy_consensus::constants::KECCAK_EMPTY
775 } else {
776 keccak256(code)
777 }
778 }),
779 }
780 }
781
782 const fn is_empty(self) -> bool {
783 self.balance.is_none() && self.nonce.is_none() && self.code_hash.is_none()
784 }
785
786 const fn needs_parent_account(self) -> bool {
787 self.balance.is_none() || self.nonce.is_none() || self.code_hash.is_none()
788 }
789
790 fn into_account(self, existing_account: Option<Account>) -> Account {
791 let existing_account = existing_account.as_ref();
792 Account {
793 balance: self.balance.unwrap_or_else(|| {
794 existing_account
795 .map(|account| account.balance)
796 .unwrap_or(alloy_primitives::U256::ZERO)
797 }),
798 nonce: self
799 .nonce
800 .unwrap_or_else(|| existing_account.map(|account| account.nonce).unwrap_or(0)),
801 bytecode_hash: self.code_hash.or_else(|| {
802 existing_account
803 .and_then(|account| account.bytecode_hash)
804 .or(Some(alloy_consensus::constants::KECCAK_EMPTY))
805 }),
806 }
807 }
808}
809
810const fn bal_account_changes_state_root(
811 account_changes: &alloy_eip7928::AccountChanges,
812 account_fields: BalAccountStateFields,
813) -> bool {
814 !account_fields.is_empty() || !account_changes.storage_changes.is_empty()
815}
816
817fn multiproof_targets_from_withdrawals(withdrawals: &[Withdrawal]) -> MultiProofTargetsV2 {
822 MultiProofTargetsV2 {
823 account_targets: withdrawals.iter().map(|w| keccak256(w.address).into()).collect(),
824 ..Default::default()
825 }
826}
827
828#[cfg(test)]
829mod tests {
830 use super::*;
831 use alloy_consensus::transaction::Recovered;
832 use alloy_eip7928::{
833 AccountChanges, BalanceChange, BlockAccessIndex, CodeChange, NonceChange, SlotChanges,
834 StorageChange,
835 };
836 use alloy_primitives::{address, bytes};
837 use reth_chainspec::ChainSpec;
838 use reth_ethereum_primitives::TransactionSigned;
839 use reth_evm::{execute::WithTxEnv, TxEnvFor};
840 use reth_evm_ethereum::EthEvmConfig;
841 use reth_provider::test_utils::MockEthProvider;
842 use reth_storage_overlay::OverlayManager;
843
844 #[test]
845 fn terminate_event_stops_transaction_execution() {
846 let terminate_execution = Arc::new(AtomicBool::new(false));
847 let ctx = PrewarmContext {
848 env: ExecutionEnv::test_default(),
849 evm_config: EthEvmConfig::new(Arc::new(ChainSpec::default())),
850 saved_cache: None,
851 provider: OverlayStateProviderFactory::new(
852 MockEthProvider::default(),
853 OverlayManager::default().overlay_builder(B256::ZERO),
854 ),
855 bal_prewarm_pool: None,
856 metrics: PrewarmMetrics::default(),
857 cache_metrics: None,
858 cache_state_metrics: None,
859 terminate_execution: Arc::clone(&terminate_execution),
860 executed_tx_index: Arc::new(AtomicUsize::new(0)),
861 precompile_cache_disabled: false,
862 precompile_cache_map: PrecompileCacheMap::default(),
863 disable_bal_parallel_state_root: false,
864 disable_bal_batch_io: false,
865 };
866 let (task, actions_tx) =
867 PrewarmCacheTask::new(Runtime::test(), PayloadExecutionCache::default(), ctx);
868 actions_tx
869 .send(PrewarmTaskEvent::Terminate {
870 execution_outcome: None,
871 valid_block_rx: mpsc::channel().1,
872 })
873 .unwrap();
874
875 task.run::<WithTxEnv<TxEnvFor<EthEvmConfig>, Recovered<TransactionSigned>>>(
876 PrewarmMode::Skipped,
877 actions_tx,
878 );
879
880 assert!(terminate_execution.load(Ordering::Relaxed));
881 }
882
883 #[test]
884 fn bal_read_only_account_does_not_change_state_root() {
885 let changes = AccountChanges::new(address!("0000000000000000000000000000000000000001"))
886 .with_storage_read(U256::from(1));
887 let fields = BalAccountStateFields::from_changes(&changes);
888
889 assert!(fields.is_empty());
890 assert!(!bal_account_changes_state_root(&changes, fields));
891 }
892
893 #[test]
894 fn bal_account_with_all_leaf_fields_does_not_need_parent_account() {
895 let changes = AccountChanges::new(address!("0000000000000000000000000000000000000001"))
896 .with_balance_change(BalanceChange::new(BlockAccessIndex::new(1), U256::from(10)))
897 .with_nonce_change(NonceChange::new(BlockAccessIndex::new(1), 7))
898 .with_code_change(CodeChange::new(BlockAccessIndex::new(1), bytes!("6001600155")));
899 let fields = BalAccountStateFields::from_changes(&changes);
900
901 assert!(bal_account_changes_state_root(&changes, fields));
902 assert!(!fields.needs_parent_account());
903 }
904
905 #[test]
906 fn bal_storage_change_needs_parent_account_when_leaf_fields_missing() {
907 let changes = AccountChanges::new(address!("0000000000000000000000000000000000000001"))
908 .with_storage_change(SlotChanges::new(
909 U256::from(1),
910 vec![StorageChange::new(BlockAccessIndex::new(1), U256::from(2))],
911 ));
912 let fields = BalAccountStateFields::from_changes(&changes);
913
914 assert!(bal_account_changes_state_root(&changes, fields));
915 assert!(fields.needs_parent_account());
916 }
917
918 #[test]
919 fn bal_account_uses_existing_fields_only_when_missing() {
920 let changes = AccountChanges::new(address!("0000000000000000000000000000000000000001"))
921 .with_balance_change(BalanceChange::new(BlockAccessIndex::new(1), U256::from(10)));
922 let fields = BalAccountStateFields::from_changes(&changes);
923 let account = fields.into_account(Some(Account {
924 balance: U256::from(1),
925 nonce: 3,
926 bytecode_hash: Some(B256::repeat_byte(0xaa)),
927 }));
928
929 assert_eq!(account.balance, U256::from(10));
930 assert_eq!(account.nonce, 3);
931 assert_eq!(account.bytecode_hash, Some(B256::repeat_byte(0xaa)));
932 }
933}
934
935#[derive(Debug)]
940pub enum PrewarmTaskEvent<R> {
941 TerminateTransactionExecution,
947 Terminate {
955 execution_outcome: Option<Arc<BlockExecutionOutput<R>>>,
959 valid_block_rx: mpsc::Receiver<()>,
964 },
965 FinishedTxExecution {
968 executed_transactions: usize,
970 },
971}
972
973#[derive(Metrics, Clone)]
975#[metrics(scope = "sync.prewarm")]
976pub struct PrewarmMetrics {
977 pub(crate) transactions: Gauge,
979 pub(crate) transactions_histogram: Histogram,
981 pub(crate) total_runtime: Histogram,
983 pub(crate) execution_duration: Histogram,
985 pub(crate) prefetch_storage_targets: Histogram,
987 pub(crate) cache_saving_duration: Gauge,
989 pub(crate) transaction_errors: Counter,
991 pub(crate) bal_slot_iteration_duration: Histogram,
993}