1use super::{bal_prewarm_pool::BalPrewarmPool, StateRootHintStream, StateRootUpdateStream};
15use crate::tree::{
16 precompile_cache::{CachedPrecompile, PrecompileCacheMap},
17 CachedStateCacheMetrics, CachedStateMetrics, CachedStateProvider, ExecutionEnv,
18 PayloadExecutionCache, SavedCache, StateProviderBuilder,
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, DatabaseProviderFactory,
31 PruneCheckpointReader, StageCheckpointReader, StorageSettingsCache,
32 TryIntoHistoricalStateProvider,
33};
34use reth_revm::database::StateProviderDatabase;
35use reth_tasks::{pool::WorkerPool, Runtime};
36use reth_trie_common::MultiProofTargetsV2;
37use std::sync::{
38 atomic::{AtomicBool, AtomicUsize, Ordering},
39 mpsc::{self, channel, Receiver, Sender},
40 Arc,
41};
42use tokio::sync::oneshot;
43use tracing::{debug, debug_span, instrument, trace, trace_span, warn, Span};
44
45#[derive(Debug)]
50pub enum PrewarmMode<Tx> {
51 Transactions {
53 pending: Receiver<(usize, Tx)>,
55 hints: Option<StateRootHintStream>,
57 },
58 BlockAccessList {
60 bal: Arc<DecodedBal>,
62 updates: Option<StateRootUpdateStream>,
64 },
65 Skipped,
68}
69
70#[derive(Debug)]
75pub struct PrewarmCacheTask<N, P, Evm>
76where
77 N: NodePrimitives,
78 Evm: ConfigureEvm<Primitives = N>,
79{
80 executor: Runtime,
82 execution_cache: PayloadExecutionCache,
84 ctx: PrewarmContext<N, P, Evm>,
86 actions_rx: Receiver<PrewarmTaskEvent<N::Receipt>>,
88 parent_span: Span,
90}
91
92impl<N, P, Evm> PrewarmCacheTask<N, P, Evm>
93where
94 N: NodePrimitives,
95 P: DatabaseProviderFactory + Clone + 'static,
96 P::Provider: BlockNumReader
97 + PruneCheckpointReader
98 + StageCheckpointReader
99 + StorageSettingsCache
100 + TryIntoHistoricalStateProvider
101 + 'static,
102 Evm: ConfigureEvm<Primitives = N> + 'static,
103{
104 pub fn new(
106 executor: Runtime,
107 execution_cache: PayloadExecutionCache,
108 ctx: PrewarmContext<N, P, Evm>,
109 ) -> (Self, Sender<PrewarmTaskEvent<N::Receipt>>) {
110 let (actions_tx, actions_rx) = channel();
111
112 trace!(
113 target: "engine::tree::payload_processor::prewarm",
114 prewarming_threads = executor.prewarming_pool().current_num_threads(),
115 transaction_count = ctx.env.transaction_count,
116 "Initialized prewarm task"
117 );
118
119 (
120 Self { executor, execution_cache, ctx, actions_rx, parent_span: Span::current() },
121 actions_tx,
122 )
123 }
124
125 fn spawn_txs_prewarm<Tx>(
132 &self,
133 pending: mpsc::Receiver<(usize, Tx)>,
134 actions_tx: Sender<PrewarmTaskEvent<N::Receipt>>,
135 state_root_hint_stream: Option<StateRootHintStream>,
136 ) where
137 Tx: ExecutableTxFor<Evm> + Send + 'static,
138 {
139 let executor = self.executor.clone();
140 let ctx = self.ctx.clone();
141 let span = Span::current();
142
143 self.executor.spawn_blocking_named("prewarm-txs", move || {
144 let _enter = debug_span!(
145 target: "engine::tree::payload_processor::prewarm",
146 parent: &span,
147 "prewarm_txs"
148 )
149 .entered();
150
151 let ctx = &ctx;
152 let pool = executor.prewarming_pool();
153
154 let mut tx_count = 0usize;
155 let state_root_hint_stream = state_root_hint_stream.as_ref();
156 pool.in_place_scope(|s| {
157 s.spawn(|_| {
158 pool.init::<PrewarmEvmState<Evm>>(|_| ctx.evm_for_ctx());
159 });
160
161 while let Ok((index, tx)) = pending.recv() {
162 if ctx.should_stop() {
163 trace!(
164 target: "engine::tree::payload_processor::prewarm",
165 "Termination requested, stopping transaction distribution"
166 );
167 break;
168 }
169
170 if index < ctx.executed_tx_index.load(Ordering::Relaxed) {
172 continue;
173 }
174
175 tx_count += 1;
176 let parent_span = Span::current();
177 s.spawn(move |_| {
178 let _enter = trace_span!(
179 target: "engine::tree::payload_processor::prewarm",
180 parent: parent_span,
181 "prewarm_tx",
182 i = index,
183 )
184 .entered();
185 Self::transact_worker(ctx, index, tx, state_root_hint_stream);
186 });
187 }
188
189 if let Some(state_root_hint_stream) = state_root_hint_stream &&
191 let Some(withdrawals) = &ctx.env.withdrawals &&
192 !withdrawals.is_empty()
193 {
194 let targets = multiproof_targets_from_withdrawals(withdrawals);
195 state_root_hint_stream.on_access_hint(targets.into());
196 }
197 });
198
199 pool.clear();
201
202 let _ = actions_tx
203 .send(PrewarmTaskEvent::FinishedTxExecution { executed_transactions: tx_count });
204 });
205 }
206
207 fn transact_worker<Tx>(
212 ctx: &PrewarmContext<N, P, Evm>,
213 index: usize,
214 tx: Tx,
215 state_root_hint_stream: Option<&StateRootHintStream>,
216 ) where
217 Tx: ExecutableTxFor<Evm>,
218 {
219 WorkerPool::with_worker_mut(|worker| {
220 let Some(evm) =
221 worker.get_or_init::<PrewarmEvmState<Evm>>(|| ctx.evm_for_ctx()).as_mut()
222 else {
223 return;
224 };
225
226 if ctx.should_stop() {
227 return;
228 }
229
230 if index < ctx.executed_tx_index.load(Ordering::Relaxed) {
232 return;
233 }
234
235 let start = Instant::now();
236
237 let (tx_env, tx) = tx.into_parts();
238 let res = match evm.transact(tx_env) {
239 Ok(res) => res,
240 Err(err) => {
241 trace!(
242 target: "engine::tree::payload_processor::prewarm",
243 %err,
244 tx_hash=%tx.tx().tx_hash(),
245 sender=%tx.signer(),
246 "Error when executing prewarm transaction",
247 );
248 ctx.metrics.transaction_errors.increment(1);
249 return;
250 }
251 };
252 ctx.metrics.execution_duration.record(start.elapsed());
253
254 if ctx.should_stop() {
255 return;
256 }
257
258 if index > 0 {
259 let (targets, storage_targets) = MultiProofTargetsV2::from_state(res.state);
260 ctx.metrics.prefetch_storage_targets.record(storage_targets as f64);
261 if let Some(state_root_hint_stream) = state_root_hint_stream {
262 state_root_hint_stream.on_access_hint(targets.into());
263 }
264 }
265
266 ctx.metrics.total_runtime.record(start.elapsed());
267 });
268 }
269
270 #[instrument(level = "debug", target = "engine::tree::payload_processor::prewarm", skip_all)]
282 fn save_cache(
283 self,
284 execution_outcome: Arc<BlockExecutionOutput<N::Receipt>>,
285 valid_block_rx: mpsc::Receiver<()>,
286 ) {
287 let start = Instant::now();
288
289 let Self {
290 execution_cache,
291 ctx: PrewarmContext { env, metrics, cache_state_metrics, saved_cache, .. },
292 ..
293 } = self;
294 let hash = env.hash;
295
296 if let Some(saved_cache) = saved_cache {
297 debug!(target: "engine::caching", parent_hash=?hash, "Updating execution cache");
298 execution_cache.update_with_guard(|cached| {
299 let caches = saved_cache.cache().clone();
302 let new_cache = SavedCache::new(hash, caches);
303
304 if new_cache.cache().insert_state(&execution_outcome.state).is_err() {
307 *cached = None;
309 debug!(target: "engine::caching", "cleared execution cache on update error");
310 return;
311 }
312
313 new_cache.update_metrics(cache_state_metrics.as_ref());
314
315 if valid_block_rx.recv().is_ok() {
316 *cached = Some(new_cache);
319 } else {
320 *cached = None;
323 debug!(target: "engine::caching", "cleared execution cache on invalid block");
324 }
325 });
326
327 let elapsed = start.elapsed();
328 debug!(target: "engine::caching", parent_hash=?hash, elapsed=?elapsed, "Updated execution cache");
329
330 metrics.cache_saving_duration.set(elapsed.as_secs_f64());
331 }
332 }
333
334 #[instrument(level = "debug", target = "engine::tree::payload_processor::prewarm", skip_all)]
342 fn run_bal_prewarm(
343 &self,
344 decoded_bal: Arc<DecodedBal>,
345 actions_tx: Sender<PrewarmTaskEvent<N::Receipt>>,
346 hashed_update_stream: Option<StateRootUpdateStream>,
347 ) {
348 let bal = decoded_bal.as_bal();
349 if bal.is_empty() {
350 if let Some(hashed_update_stream) = hashed_update_stream {
351 hashed_update_stream.finish();
352 }
353 let _ =
354 actions_tx.send(PrewarmTaskEvent::FinishedTxExecution { executed_transactions: 0 });
355 return;
356 }
357
358 trace!(
359 target: "engine::tree::payload_processor::prewarm",
360 accounts = bal.len(),
361 "Starting BAL prewarm"
362 );
363
364 let ctx = self.ctx.clone();
365 let executor = self.executor.clone();
366 let parent_span = Span::current();
367 let stream_parent_span = parent_span;
368 let prefetch_bal = Arc::clone(&decoded_bal);
369 let stream_bal = Arc::clone(&decoded_bal);
370 let (stream_tx, stream_rx) = oneshot::channel();
371
372 if let Some(hashed_update_stream) = hashed_update_stream {
373 let ctx = ctx.clone();
374 executor.bal_streaming_pool().spawn(move || {
375 let branch_span = debug_span!(
376 target: "engine::tree::payload_processor::prewarm",
377 parent: &stream_parent_span,
378 "bal_hashed_state_stream",
379 bal_accounts = stream_bal.as_bal().len(),
380 );
381 let parent_span = branch_span.clone();
382 let _span = branch_span.entered();
383
384 stream_bal.as_bal().par_iter().for_each(|account_changes| {
385 WorkerPool::with_worker_mut(|worker| {
386 let provider =
387 worker.get_or_init::<Option<Box<dyn AccountReader>>>(|| None);
388 ctx.send_bal_hashed_state(
389 &parent_span,
390 provider,
391 account_changes,
392 &hashed_update_stream,
393 );
394 });
395 });
396
397 hashed_update_stream.finish();
398 let _ = stream_tx.send(());
399 });
400 } else {
401 let _ = stream_tx.send(());
402 }
403
404 if let Some(saved_cache) = ctx.saved_cache &&
405 !ctx.disable_bal_batch_io &&
406 let Some(pool) = ctx.bal_prewarm_pool.as_ref()
407 {
408 let caches = saved_cache.cache().clone();
421 let provider_builder = ctx.provider.clone();
422 let build = Arc::new(move || provider_builder.build());
423
424 pool.begin_block(build, caches, ctx.env.txpool_snapshot.clone());
425 for account in prefetch_bal.as_bal() {
426 pool.warm_account(account.address);
427 for change in &account.storage_changes {
428 pool.warm_storage(account.address, change.slot.into());
429 }
430 for &slot in &account.storage_reads {
431 pool.warm_storage(account.address, slot.into());
432 }
433 }
434 pool.end_block();
435 }
436
437 stream_rx
438 .blocking_recv()
439 .expect("BAL hashed-state streaming task dropped without signaling completion");
440
441 executor.bal_streaming_pool().clear();
443 executor.prewarming_pool().clear();
444
445 let _ = actions_tx.send(PrewarmTaskEvent::FinishedTxExecution { executed_transactions: 0 });
446 }
447
448 #[instrument(
453 parent = &self.parent_span,
454 level = "debug",
455 target = "engine::tree::payload_processor::prewarm",
456 name = "prewarm and caching",
457 skip_all
458 )]
459 pub fn run<Tx>(self, mode: PrewarmMode<Tx>, actions_tx: Sender<PrewarmTaskEvent<N::Receipt>>)
460 where
461 Tx: ExecutableTxFor<Evm> + Send + 'static,
462 {
463 match mode {
467 PrewarmMode::Transactions { pending, hints } => {
468 self.spawn_txs_prewarm(pending, actions_tx, hints);
469 }
470 PrewarmMode::BlockAccessList { bal, updates } => {
471 self.run_bal_prewarm(bal, actions_tx, updates);
472 }
473 PrewarmMode::Skipped => {
474 let _ = actions_tx
475 .send(PrewarmTaskEvent::FinishedTxExecution { executed_transactions: 0 });
476 }
477 }
478
479 let mut final_execution_outcome = None;
480 let mut finished_execution = false;
481 while let Ok(event) = self.actions_rx.recv() {
482 match event {
483 PrewarmTaskEvent::TerminateTransactionExecution => {
484 debug!(target: "engine::tree::prewarm", "Terminating prewarm execution");
486 self.ctx.stop();
487 }
488 PrewarmTaskEvent::Terminate { execution_outcome, valid_block_rx } => {
489 trace!(target: "engine::tree::payload_processor::prewarm", "Received termination signal");
490 self.ctx.stop();
493 final_execution_outcome =
494 Some(execution_outcome.map(|outcome| (outcome, valid_block_rx)));
495
496 if finished_execution {
497 break
499 }
500 }
501 PrewarmTaskEvent::FinishedTxExecution { executed_transactions } => {
502 trace!(target: "engine::tree::payload_processor::prewarm", "Finished prewarm execution signal");
503 self.ctx.metrics.transactions.set(executed_transactions as f64);
504 self.ctx.metrics.transactions_histogram.record(executed_transactions as f64);
505
506 finished_execution = true;
507
508 if final_execution_outcome.is_some() {
509 break
511 }
512 }
513 }
514 }
515
516 debug!(target: "engine::tree::payload_processor::prewarm", "Completed prewarm execution");
517
518 if let Some(Some((execution_outcome, valid_block_rx))) = final_execution_outcome {
520 self.save_cache(execution_outcome, valid_block_rx);
521 }
522 }
523}
524
525#[derive(Debug, Clone)]
527pub struct PrewarmContext<N, P, Evm>
528where
529 N: NodePrimitives,
530 Evm: ConfigureEvm<Primitives = N>,
531{
532 pub env: ExecutionEnv<Evm>,
534 pub evm_config: Evm,
536 pub saved_cache: Option<SavedCache>,
538 pub provider: StateProviderBuilder<N, P>,
540 pub(crate) bal_prewarm_pool: Option<Arc<BalPrewarmPool>>,
543 pub metrics: PrewarmMetrics,
545 pub cache_metrics: Option<CachedStateMetrics>,
548 pub cache_state_metrics: Option<CachedStateCacheMetrics>,
550 pub terminate_execution: Arc<AtomicBool>,
552 pub executed_tx_index: Arc<AtomicUsize>,
556 pub precompile_cache_disabled: bool,
558 pub precompile_cache_map: PrecompileCacheMap<SpecFor<Evm>>,
560 pub disable_bal_parallel_state_root: bool,
563 pub disable_bal_batch_io: bool,
565}
566
567type PrewarmEvmState<Evm> =
570 Option<EvmFor<Evm, StateProviderDatabase<reth_provider::StateProviderBox>>>;
571
572impl<N, P, Evm> PrewarmContext<N, P, Evm>
573where
574 N: NodePrimitives,
575 P: DatabaseProviderFactory,
576 P::Provider: BlockNumReader
577 + PruneCheckpointReader
578 + StageCheckpointReader
579 + StorageSettingsCache
580 + TryIntoHistoricalStateProvider
581 + 'static,
582 Evm: ConfigureEvm<Primitives = N> + 'static,
583{
584 #[instrument(level = "debug", target = "engine::tree::payload_processor::prewarm", skip_all)]
586 fn evm_for_ctx(&self) -> PrewarmEvmState<Evm> {
587 let mut state_provider = match self.provider.build() {
588 Ok(provider) => provider,
589 Err(err) => {
590 trace!(
591 target: "engine::tree::payload_processor::prewarm",
592 %err,
593 "Failed to build state provider in prewarm thread"
594 );
595 return None
596 }
597 };
598
599 if let Some(saved_cache) = &self.saved_cache {
601 let caches = saved_cache.cache().clone();
602 state_provider = Box::new(
603 CachedStateProvider::new_prewarm(state_provider, caches)
604 .with_txpool_snapshot(self.env.txpool_snapshot.clone()),
605 );
606 }
607
608 let state_provider = StateProviderDatabase::new(state_provider);
609
610 let mut evm_env = self.env.evm_env.clone();
611
612 evm_env.cfg_env.disable_nonce_check = true;
615
616 evm_env.cfg_env.disable_balance_check = true;
619
620 let spec_id = *evm_env.spec_id();
622 let mut evm = self.evm_config.evm_with_env(state_provider, evm_env);
623
624 if !self.precompile_cache_disabled {
625 evm.precompiles_mut().map_cacheable_precompiles(|address, precompile| {
627 CachedPrecompile::wrap(
628 precompile,
629 self.precompile_cache_map.cache_for_address(*address),
630 spec_id,
631 None, )
633 });
634 }
635
636 Some(evm)
637 }
638
639 #[inline]
641 pub fn should_stop(&self) -> bool {
642 self.terminate_execution.load(Ordering::Relaxed)
643 }
644
645 #[inline]
647 pub fn stop(&self) {
648 self.terminate_execution.store(true, Ordering::Relaxed);
649 }
650
651 fn send_bal_hashed_state(
661 &self,
662 parent_span: &Span,
663 provider: &mut Option<Box<dyn AccountReader>>,
664 account_changes: &alloy_eip7928::AccountChanges,
665 hashed_update_stream: &StateRootUpdateStream,
666 ) {
667 if self.disable_bal_parallel_state_root {
668 return;
669 }
670 let address = account_changes.address;
671 let mut hashed_address = None;
672 let account_fields = BalAccountStateFields::from_changes(account_changes);
673
674 if !bal_account_changes_state_root(account_changes, account_fields) {
675 return;
676 }
677
678 if !account_changes.storage_changes.is_empty() {
682 let hashed_address = *hashed_address.get_or_insert_with(|| keccak256(address));
683 let mut storage_map = reth_trie::HashedStorage::new(false);
684
685 for slot_changes in &account_changes.storage_changes {
686 let hashed_slot = keccak256(slot_changes.slot.to_be_bytes::<32>());
687 if let Some(last_change) = slot_changes.changes.last() {
688 storage_map.storage.insert(hashed_slot, last_change.new_value);
689 }
690 }
691
692 let mut hashed_state = reth_trie::HashedPostState::default();
693 hashed_state.storages.insert(hashed_address, storage_map);
694 hashed_update_stream.on_hashed_state_update(hashed_state);
695 }
696
697 let existing_account = if account_fields.needs_parent_account() {
698 if provider.is_none() {
699 let _span = debug_span!(
700 target: "engine::tree::payload_processor::prewarm",
701 parent: parent_span,
702 "bal_hashed_state_provider_init",
703 has_saved_cache = !self.disable_bal_batch_io && self.saved_cache.is_some(),
704 )
705 .entered();
706
707 let inner = match self.provider.build() {
708 Ok(p) => p,
709 Err(err) => {
710 warn!(
711 target: "engine::tree::payload_processor::prewarm",
712 ?err,
713 "Failed to build provider for BAL account reads"
714 );
715 return;
716 }
717 };
718 let boxed: Box<dyn AccountReader> =
719 match (self.disable_bal_batch_io, &self.saved_cache) {
720 (false, Some(saved)) => {
721 let caches = saved.cache().clone();
722 Box::new(
723 CachedStateProvider::new_prewarm(inner, caches)
724 .with_txpool_snapshot(self.env.txpool_snapshot.clone()),
725 )
726 }
727 _ => Box::new(inner),
728 };
729 *provider = Some(boxed);
730 }
731 let account_reader = provider.as_ref().expect("provider just initialized");
732 account_reader.basic_account(&address).ok().flatten()
733 } else {
734 None
735 };
736
737 let account = account_fields.into_account(existing_account);
738 let hashed_address = hashed_address.unwrap_or_else(|| keccak256(address));
739
740 let account = (!account.is_empty()).then_some(account);
752
753 let mut hashed_state = reth_trie::HashedPostState::default();
754 hashed_state.accounts.insert(hashed_address, account);
755 hashed_update_stream.on_hashed_state_update(hashed_state);
756 }
757}
758
759#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
760struct BalAccountStateFields {
761 balance: Option<U256>,
762 nonce: Option<u64>,
763 code_hash: Option<B256>,
764}
765
766impl BalAccountStateFields {
767 fn from_changes(account_changes: &alloy_eip7928::AccountChanges) -> Self {
768 Self {
769 balance: account_changes.balance_changes.last().map(|change| change.post_balance),
770 nonce: account_changes.nonce_changes.last().map(|change| change.new_nonce),
771 code_hash: account_changes.code_changes.last().map(|code_change| {
772 if code_change.new_code.is_empty() {
773 alloy_consensus::constants::KECCAK_EMPTY
774 } else {
775 keccak256(&code_change.new_code)
776 }
777 }),
778 }
779 }
780
781 const fn is_empty(self) -> bool {
782 self.balance.is_none() && self.nonce.is_none() && self.code_hash.is_none()
783 }
784
785 const fn needs_parent_account(self) -> bool {
786 self.balance.is_none() || self.nonce.is_none() || self.code_hash.is_none()
787 }
788
789 fn into_account(self, existing_account: Option<Account>) -> Account {
790 let existing_account = existing_account.as_ref();
791 Account {
792 balance: self.balance.unwrap_or_else(|| {
793 existing_account
794 .map(|account| account.balance)
795 .unwrap_or(alloy_primitives::U256::ZERO)
796 }),
797 nonce: self
798 .nonce
799 .unwrap_or_else(|| existing_account.map(|account| account.nonce).unwrap_or(0)),
800 bytecode_hash: self.code_hash.or_else(|| {
801 existing_account
802 .and_then(|account| account.bytecode_hash)
803 .or(Some(alloy_consensus::constants::KECCAK_EMPTY))
804 }),
805 }
806 }
807}
808
809const fn bal_account_changes_state_root(
810 account_changes: &alloy_eip7928::AccountChanges,
811 account_fields: BalAccountStateFields,
812) -> bool {
813 !account_fields.is_empty() || !account_changes.storage_changes.is_empty()
814}
815
816fn multiproof_targets_from_withdrawals(withdrawals: &[Withdrawal]) -> MultiProofTargetsV2 {
821 MultiProofTargetsV2 {
822 account_targets: withdrawals.iter().map(|w| keccak256(w.address).into()).collect(),
823 ..Default::default()
824 }
825}
826
827#[cfg(test)]
828mod tests {
829 use super::*;
830 use alloy_consensus::transaction::Recovered;
831 use alloy_eip7928::{
832 AccountChanges, BalanceChange, BlockAccessIndex, CodeChange, NonceChange, SlotChanges,
833 StorageChange,
834 };
835 use alloy_primitives::{address, bytes};
836 use reth_chainspec::ChainSpec;
837 use reth_ethereum_primitives::{EthPrimitives, TransactionSigned};
838 use reth_evm::{execute::WithTxEnv, TxEnvFor};
839 use reth_evm_ethereum::EthEvmConfig;
840 use reth_provider::test_utils::MockEthProvider;
841 use reth_storage_overlay::OverlayManager;
842
843 #[test]
844 fn terminate_event_stops_transaction_execution() {
845 let terminate_execution = Arc::new(AtomicBool::new(false));
846 let ctx = PrewarmContext {
847 env: ExecutionEnv::test_default(),
848 evm_config: EthEvmConfig::new(Arc::new(ChainSpec::default())),
849 saved_cache: None,
850 provider: StateProviderBuilder::<EthPrimitives, _>::new(
851 MockEthProvider::default(),
852 B256::ZERO,
853 OverlayManager::default(),
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}