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, BlockReader, StateProviderFactory, StateReader,
31};
32use reth_revm::database::StateProviderDatabase;
33use reth_tasks::{pool::WorkerPool, Runtime};
34use reth_trie_common::MultiProofTargetsV2;
35use std::sync::{
36 atomic::{AtomicBool, AtomicUsize, Ordering},
37 mpsc::{self, channel, Receiver, Sender},
38 Arc,
39};
40use tokio::sync::oneshot;
41use tracing::{debug, debug_span, instrument, trace, trace_span, warn, Span};
42
43#[derive(Debug)]
48pub enum PrewarmMode<Tx> {
49 Transactions {
51 pending: Receiver<(usize, Tx)>,
53 hints: Option<StateRootHintStream>,
55 },
56 BlockAccessList {
58 bal: Arc<DecodedBal>,
60 updates: Option<StateRootUpdateStream>,
62 },
63 Skipped,
66}
67
68#[derive(Debug)]
73pub struct PrewarmCacheTask<N, P, Evm>
74where
75 N: NodePrimitives,
76 Evm: ConfigureEvm<Primitives = N>,
77{
78 executor: Runtime,
80 execution_cache: PayloadExecutionCache,
82 ctx: PrewarmContext<N, P, Evm>,
84 actions_rx: Receiver<PrewarmTaskEvent<N::Receipt>>,
86 parent_span: Span,
88}
89
90impl<N, P, Evm> PrewarmCacheTask<N, P, Evm>
91where
92 N: NodePrimitives,
93 P: BlockReader + StateProviderFactory + StateReader + Clone + 'static,
94 Evm: ConfigureEvm<Primitives = N> + 'static,
95{
96 pub fn new(
98 executor: Runtime,
99 execution_cache: PayloadExecutionCache,
100 ctx: PrewarmContext<N, P, Evm>,
101 ) -> (Self, Sender<PrewarmTaskEvent<N::Receipt>>) {
102 let (actions_tx, actions_rx) = channel();
103
104 trace!(
105 target: "engine::tree::payload_processor::prewarm",
106 prewarming_threads = executor.prewarming_pool().current_num_threads(),
107 transaction_count = ctx.env.transaction_count,
108 "Initialized prewarm task"
109 );
110
111 (
112 Self { executor, execution_cache, ctx, actions_rx, parent_span: Span::current() },
113 actions_tx,
114 )
115 }
116
117 fn spawn_txs_prewarm<Tx>(
124 &self,
125 pending: mpsc::Receiver<(usize, Tx)>,
126 actions_tx: Sender<PrewarmTaskEvent<N::Receipt>>,
127 state_root_hint_stream: Option<StateRootHintStream>,
128 ) where
129 Tx: ExecutableTxFor<Evm> + Send + 'static,
130 {
131 let executor = self.executor.clone();
132 let ctx = self.ctx.clone();
133 let span = Span::current();
134
135 self.executor.spawn_blocking_named("prewarm-txs", move || {
136 let _enter = debug_span!(
137 target: "engine::tree::payload_processor::prewarm",
138 parent: &span,
139 "prewarm_txs"
140 )
141 .entered();
142
143 let ctx = &ctx;
144 let pool = executor.prewarming_pool();
145
146 let mut tx_count = 0usize;
147 let state_root_hint_stream = state_root_hint_stream.as_ref();
148 pool.in_place_scope(|s| {
149 s.spawn(|_| {
150 pool.init::<PrewarmEvmState<Evm>>(|_| ctx.evm_for_ctx());
151 });
152
153 while let Ok((index, tx)) = pending.recv() {
154 if ctx.should_stop() {
155 trace!(
156 target: "engine::tree::payload_processor::prewarm",
157 "Termination requested, stopping transaction distribution"
158 );
159 break;
160 }
161
162 if index < ctx.executed_tx_index.load(Ordering::Relaxed) {
164 continue;
165 }
166
167 tx_count += 1;
168 let parent_span = Span::current();
169 s.spawn(move |_| {
170 let _enter = trace_span!(
171 target: "engine::tree::payload_processor::prewarm",
172 parent: parent_span,
173 "prewarm_tx",
174 i = index,
175 )
176 .entered();
177 Self::transact_worker(ctx, index, tx, state_root_hint_stream);
178 });
179 }
180
181 if let Some(state_root_hint_stream) = state_root_hint_stream &&
183 let Some(withdrawals) = &ctx.env.withdrawals &&
184 !withdrawals.is_empty()
185 {
186 let targets = multiproof_targets_from_withdrawals(withdrawals);
187 state_root_hint_stream.on_access_hint(targets.into());
188 }
189 });
190
191 pool.clear();
193
194 let _ = actions_tx
195 .send(PrewarmTaskEvent::FinishedTxExecution { executed_transactions: tx_count });
196 });
197 }
198
199 fn transact_worker<Tx>(
204 ctx: &PrewarmContext<N, P, Evm>,
205 index: usize,
206 tx: Tx,
207 state_root_hint_stream: Option<&StateRootHintStream>,
208 ) where
209 Tx: ExecutableTxFor<Evm>,
210 {
211 WorkerPool::with_worker_mut(|worker| {
212 let Some(evm) =
213 worker.get_or_init::<PrewarmEvmState<Evm>>(|| ctx.evm_for_ctx()).as_mut()
214 else {
215 return;
216 };
217
218 if ctx.should_stop() {
219 return;
220 }
221
222 if index < ctx.executed_tx_index.load(Ordering::Relaxed) {
224 return;
225 }
226
227 let start = Instant::now();
228
229 let (tx_env, tx) = tx.into_parts();
230 let res = match evm.transact(tx_env) {
231 Ok(res) => res,
232 Err(err) => {
233 trace!(
234 target: "engine::tree::payload_processor::prewarm",
235 %err,
236 tx_hash=%tx.tx().tx_hash(),
237 sender=%tx.signer(),
238 "Error when executing prewarm transaction",
239 );
240 ctx.metrics.transaction_errors.increment(1);
241 return;
242 }
243 };
244 ctx.metrics.execution_duration.record(start.elapsed());
245
246 if ctx.should_stop() {
247 return;
248 }
249
250 if index > 0 {
251 let (targets, storage_targets) = MultiProofTargetsV2::from_state(res.state);
252 ctx.metrics.prefetch_storage_targets.record(storage_targets as f64);
253 if let Some(state_root_hint_stream) = state_root_hint_stream {
254 state_root_hint_stream.on_access_hint(targets.into());
255 }
256 }
257
258 ctx.metrics.total_runtime.record(start.elapsed());
259 });
260 }
261
262 #[instrument(level = "debug", target = "engine::tree::payload_processor::prewarm", skip_all)]
274 fn save_cache(
275 self,
276 execution_outcome: Arc<BlockExecutionOutput<N::Receipt>>,
277 valid_block_rx: mpsc::Receiver<()>,
278 ) {
279 let start = Instant::now();
280
281 let Self {
282 execution_cache,
283 ctx: PrewarmContext { env, metrics, cache_state_metrics, saved_cache, .. },
284 ..
285 } = self;
286 let hash = env.hash;
287
288 if let Some(saved_cache) = saved_cache {
289 debug!(target: "engine::caching", parent_hash=?hash, "Updating execution cache");
290 execution_cache.update_with_guard(|cached| {
291 let caches = saved_cache.cache().clone();
294 let new_cache = SavedCache::new(hash, caches);
295
296 if new_cache.cache().insert_state(&execution_outcome.state).is_err() {
299 *cached = None;
301 debug!(target: "engine::caching", "cleared execution cache on update error");
302 return;
303 }
304
305 new_cache.update_metrics(cache_state_metrics.as_ref());
306
307 if valid_block_rx.recv().is_ok() {
308 *cached = Some(new_cache);
311 } else {
312 *cached = None;
315 debug!(target: "engine::caching", "cleared execution cache on invalid block");
316 }
317 });
318
319 let elapsed = start.elapsed();
320 debug!(target: "engine::caching", parent_hash=?hash, elapsed=?elapsed, "Updated execution cache");
321
322 metrics.cache_saving_duration.set(elapsed.as_secs_f64());
323 }
324 }
325
326 #[instrument(level = "debug", target = "engine::tree::payload_processor::prewarm", skip_all)]
334 fn run_bal_prewarm(
335 &self,
336 decoded_bal: Arc<DecodedBal>,
337 actions_tx: Sender<PrewarmTaskEvent<N::Receipt>>,
338 hashed_update_stream: Option<StateRootUpdateStream>,
339 ) {
340 let bal = decoded_bal.as_bal();
341 if bal.is_empty() {
342 if let Some(hashed_update_stream) = hashed_update_stream {
343 hashed_update_stream.finish();
344 }
345 let _ =
346 actions_tx.send(PrewarmTaskEvent::FinishedTxExecution { executed_transactions: 0 });
347 return;
348 }
349
350 trace!(
351 target: "engine::tree::payload_processor::prewarm",
352 accounts = bal.len(),
353 "Starting BAL prewarm"
354 );
355
356 let ctx = self.ctx.clone();
357 let executor = self.executor.clone();
358 let parent_span = Span::current();
359 let stream_parent_span = parent_span;
360 let prefetch_bal = Arc::clone(&decoded_bal);
361 let stream_bal = Arc::clone(&decoded_bal);
362 let (stream_tx, stream_rx) = oneshot::channel();
363
364 if let Some(hashed_update_stream) = hashed_update_stream {
365 let ctx = ctx.clone();
366 executor.bal_streaming_pool().spawn(move || {
367 let branch_span = debug_span!(
368 target: "engine::tree::payload_processor::prewarm",
369 parent: &stream_parent_span,
370 "bal_hashed_state_stream",
371 bal_accounts = stream_bal.as_bal().len(),
372 );
373 let parent_span = branch_span.clone();
374 let _span = branch_span.entered();
375
376 stream_bal.as_bal().par_iter().for_each(|account_changes| {
377 WorkerPool::with_worker_mut(|worker| {
378 let provider =
379 worker.get_or_init::<Option<Box<dyn AccountReader>>>(|| None);
380 ctx.send_bal_hashed_state(
381 &parent_span,
382 provider,
383 account_changes,
384 &hashed_update_stream,
385 );
386 });
387 });
388
389 hashed_update_stream.finish();
390 let _ = stream_tx.send(());
391 });
392 } else {
393 let _ = stream_tx.send(());
394 }
395
396 if let Some(saved_cache) = ctx.saved_cache &&
397 !ctx.disable_bal_batch_io &&
398 let Some(pool) = ctx.bal_prewarm_pool.as_ref()
399 {
400 let caches = saved_cache.cache().clone();
413 let provider_builder = ctx.provider.clone();
414 let build = Arc::new(move || provider_builder.build());
415
416 pool.begin_block(build, caches);
417 for account in prefetch_bal.as_bal() {
418 pool.warm_account(account.address);
419 for change in &account.storage_changes {
420 pool.warm_storage(account.address, change.slot.into());
421 }
422 for &slot in &account.storage_reads {
423 pool.warm_storage(account.address, slot.into());
424 }
425 }
426 pool.end_block();
427 }
428
429 stream_rx
430 .blocking_recv()
431 .expect("BAL hashed-state streaming task dropped without signaling completion");
432
433 executor.bal_streaming_pool().clear();
435 executor.prewarming_pool().clear();
436
437 let _ = actions_tx.send(PrewarmTaskEvent::FinishedTxExecution { executed_transactions: 0 });
438 }
439
440 #[instrument(
445 parent = &self.parent_span,
446 level = "debug",
447 target = "engine::tree::payload_processor::prewarm",
448 name = "prewarm and caching",
449 skip_all
450 )]
451 pub fn run<Tx>(self, mode: PrewarmMode<Tx>, actions_tx: Sender<PrewarmTaskEvent<N::Receipt>>)
452 where
453 Tx: ExecutableTxFor<Evm> + Send + 'static,
454 {
455 match mode {
459 PrewarmMode::Transactions { pending, hints } => {
460 self.spawn_txs_prewarm(pending, actions_tx, hints);
461 }
462 PrewarmMode::BlockAccessList { bal, updates } => {
463 self.run_bal_prewarm(bal, actions_tx, updates);
464 }
465 PrewarmMode::Skipped => {
466 let _ = actions_tx
467 .send(PrewarmTaskEvent::FinishedTxExecution { executed_transactions: 0 });
468 }
469 }
470
471 let mut final_execution_outcome = None;
472 let mut finished_execution = false;
473 while let Ok(event) = self.actions_rx.recv() {
474 match event {
475 PrewarmTaskEvent::TerminateTransactionExecution => {
476 debug!(target: "engine::tree::prewarm", "Terminating prewarm execution");
478 self.ctx.stop();
479 }
480 PrewarmTaskEvent::Terminate { execution_outcome, valid_block_rx } => {
481 trace!(target: "engine::tree::payload_processor::prewarm", "Received termination signal");
482 final_execution_outcome =
483 Some(execution_outcome.map(|outcome| (outcome, valid_block_rx)));
484
485 if finished_execution {
486 break
488 }
489 }
490 PrewarmTaskEvent::FinishedTxExecution { executed_transactions } => {
491 trace!(target: "engine::tree::payload_processor::prewarm", "Finished prewarm execution signal");
492 self.ctx.metrics.transactions.set(executed_transactions as f64);
493 self.ctx.metrics.transactions_histogram.record(executed_transactions as f64);
494
495 finished_execution = true;
496
497 if final_execution_outcome.is_some() {
498 break
500 }
501 }
502 }
503 }
504
505 debug!(target: "engine::tree::payload_processor::prewarm", "Completed prewarm execution");
506
507 if let Some(Some((execution_outcome, valid_block_rx))) = final_execution_outcome {
509 self.save_cache(execution_outcome, valid_block_rx);
510 }
511 }
512}
513
514#[derive(Debug, Clone)]
516pub struct PrewarmContext<N, P, Evm>
517where
518 N: NodePrimitives,
519 Evm: ConfigureEvm<Primitives = N>,
520{
521 pub env: ExecutionEnv<Evm>,
523 pub evm_config: Evm,
525 pub saved_cache: Option<SavedCache>,
527 pub provider: StateProviderBuilder<N, P>,
529 pub(crate) bal_prewarm_pool: Option<Arc<BalPrewarmPool>>,
532 pub metrics: PrewarmMetrics,
534 pub cache_metrics: Option<CachedStateMetrics>,
537 pub cache_state_metrics: Option<CachedStateCacheMetrics>,
539 pub terminate_execution: Arc<AtomicBool>,
541 pub executed_tx_index: Arc<AtomicUsize>,
545 pub precompile_cache_disabled: bool,
547 pub precompile_cache_map: PrecompileCacheMap<SpecFor<Evm>>,
549 pub disable_bal_parallel_state_root: bool,
552 pub disable_bal_batch_io: bool,
554}
555
556type PrewarmEvmState<Evm> =
559 Option<EvmFor<Evm, StateProviderDatabase<reth_provider::StateProviderBox>>>;
560
561impl<N, P, Evm> PrewarmContext<N, P, Evm>
562where
563 N: NodePrimitives,
564 P: BlockReader + StateProviderFactory + StateReader + Clone + 'static,
565 Evm: ConfigureEvm<Primitives = N> + 'static,
566{
567 #[instrument(level = "debug", target = "engine::tree::payload_processor::prewarm", skip_all)]
569 fn evm_for_ctx(&self) -> PrewarmEvmState<Evm> {
570 let mut state_provider = match self.provider.build() {
571 Ok(provider) => provider,
572 Err(err) => {
573 trace!(
574 target: "engine::tree::payload_processor::prewarm",
575 %err,
576 "Failed to build state provider in prewarm thread"
577 );
578 return None
579 }
580 };
581
582 if let Some(saved_cache) = &self.saved_cache {
584 let caches = saved_cache.cache().clone();
585 state_provider = Box::new(CachedStateProvider::new_prewarm(state_provider, caches));
586 }
587
588 let state_provider = StateProviderDatabase::new(state_provider);
589
590 let mut evm_env = self.env.evm_env.clone();
591
592 evm_env.cfg_env.disable_nonce_check = true;
595
596 evm_env.cfg_env.disable_balance_check = true;
599
600 let spec_id = *evm_env.spec_id();
602 let mut evm = self.evm_config.evm_with_env(state_provider, evm_env);
603
604 if !self.precompile_cache_disabled {
605 evm.precompiles_mut().map_cacheable_precompiles(|address, precompile| {
607 CachedPrecompile::wrap(
608 precompile,
609 self.precompile_cache_map.cache_for_address(*address),
610 spec_id,
611 None, )
613 });
614 }
615
616 Some(evm)
617 }
618
619 #[inline]
621 pub fn should_stop(&self) -> bool {
622 self.terminate_execution.load(Ordering::Relaxed)
623 }
624
625 #[inline]
627 pub fn stop(&self) {
628 self.terminate_execution.store(true, Ordering::Relaxed);
629 }
630
631 fn send_bal_hashed_state(
641 &self,
642 parent_span: &Span,
643 provider: &mut Option<Box<dyn AccountReader>>,
644 account_changes: &alloy_eip7928::AccountChanges,
645 hashed_update_stream: &StateRootUpdateStream,
646 ) {
647 if self.disable_bal_parallel_state_root {
648 return;
649 }
650 let address = account_changes.address;
651 let mut hashed_address = None;
652 let account_fields = BalAccountStateFields::from_changes(account_changes);
653
654 if !bal_account_changes_state_root(account_changes, account_fields) {
655 return;
656 }
657
658 if !account_changes.storage_changes.is_empty() {
659 let hashed_address = *hashed_address.get_or_insert_with(|| keccak256(address));
660 let mut storage_map = reth_trie::HashedStorage::new(false);
661
662 for slot_changes in &account_changes.storage_changes {
663 let hashed_slot = keccak256(slot_changes.slot.to_be_bytes::<32>());
664 if let Some(last_change) = slot_changes.changes.last() {
665 storage_map.storage.insert(hashed_slot, last_change.new_value);
666 }
667 }
668
669 let mut hashed_state = reth_trie::HashedPostState::default();
670 hashed_state.storages.insert(hashed_address, storage_map);
671 hashed_update_stream.on_hashed_state_update(hashed_state);
672 }
673
674 let existing_account = if account_fields.needs_parent_account() {
675 if provider.is_none() {
676 let _span = debug_span!(
677 target: "engine::tree::payload_processor::prewarm",
678 parent: parent_span,
679 "bal_hashed_state_provider_init",
680 has_saved_cache = !self.disable_bal_batch_io && self.saved_cache.is_some(),
681 )
682 .entered();
683
684 let inner = match self.provider.build() {
685 Ok(p) => p,
686 Err(err) => {
687 warn!(
688 target: "engine::tree::payload_processor::prewarm",
689 ?err,
690 "Failed to build provider for BAL account reads"
691 );
692 return;
693 }
694 };
695 let boxed: Box<dyn AccountReader> =
696 match (self.disable_bal_batch_io, &self.saved_cache) {
697 (false, Some(saved)) => {
698 let caches = saved.cache().clone();
699 Box::new(CachedStateProvider::new_prewarm(inner, caches))
700 }
701 _ => Box::new(inner),
702 };
703 *provider = Some(boxed);
704 }
705 let account_reader = provider.as_ref().expect("provider just initialized");
706 account_reader.basic_account(&address).ok().flatten()
707 } else {
708 None
709 };
710
711 let account = account_fields.into_account(existing_account);
712
713 let hashed_address = hashed_address.unwrap_or_else(|| keccak256(address));
714 let mut hashed_state = reth_trie::HashedPostState::default();
715 hashed_state.accounts.insert(hashed_address, Some(account));
716
717 hashed_update_stream.on_hashed_state_update(hashed_state);
718 }
719}
720
721#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
722struct BalAccountStateFields {
723 balance: Option<U256>,
724 nonce: Option<u64>,
725 code_hash: Option<B256>,
726}
727
728impl BalAccountStateFields {
729 fn from_changes(account_changes: &alloy_eip7928::AccountChanges) -> Self {
730 Self {
731 balance: account_changes.balance_changes.last().map(|change| change.post_balance),
732 nonce: account_changes.nonce_changes.last().map(|change| change.new_nonce),
733 code_hash: account_changes.code_changes.last().map(|code_change| {
734 if code_change.new_code.is_empty() {
735 alloy_consensus::constants::KECCAK_EMPTY
736 } else {
737 keccak256(&code_change.new_code)
738 }
739 }),
740 }
741 }
742
743 const fn is_empty(self) -> bool {
744 self.balance.is_none() && self.nonce.is_none() && self.code_hash.is_none()
745 }
746
747 const fn needs_parent_account(self) -> bool {
748 self.balance.is_none() || self.nonce.is_none() || self.code_hash.is_none()
749 }
750
751 fn into_account(self, existing_account: Option<Account>) -> Account {
752 let existing_account = existing_account.as_ref();
753 Account {
754 balance: self.balance.unwrap_or_else(|| {
755 existing_account
756 .map(|account| account.balance)
757 .unwrap_or(alloy_primitives::U256::ZERO)
758 }),
759 nonce: self
760 .nonce
761 .unwrap_or_else(|| existing_account.map(|account| account.nonce).unwrap_or(0)),
762 bytecode_hash: self.code_hash.or_else(|| {
763 existing_account
764 .and_then(|account| account.bytecode_hash)
765 .or(Some(alloy_consensus::constants::KECCAK_EMPTY))
766 }),
767 }
768 }
769}
770
771const fn bal_account_changes_state_root(
772 account_changes: &alloy_eip7928::AccountChanges,
773 account_fields: BalAccountStateFields,
774) -> bool {
775 !account_fields.is_empty() || !account_changes.storage_changes.is_empty()
776}
777
778fn multiproof_targets_from_withdrawals(withdrawals: &[Withdrawal]) -> MultiProofTargetsV2 {
783 MultiProofTargetsV2 {
784 account_targets: withdrawals.iter().map(|w| keccak256(w.address).into()).collect(),
785 ..Default::default()
786 }
787}
788
789#[cfg(test)]
790mod tests {
791 use super::*;
792 use alloy_eip7928::{
793 AccountChanges, BalanceChange, BlockAccessIndex, CodeChange, NonceChange, SlotChanges,
794 StorageChange,
795 };
796 use alloy_primitives::{address, bytes};
797
798 #[test]
799 fn bal_read_only_account_does_not_change_state_root() {
800 let changes = AccountChanges::new(address!("0000000000000000000000000000000000000001"))
801 .with_storage_read(U256::from(1));
802 let fields = BalAccountStateFields::from_changes(&changes);
803
804 assert!(fields.is_empty());
805 assert!(!bal_account_changes_state_root(&changes, fields));
806 }
807
808 #[test]
809 fn bal_account_with_all_leaf_fields_does_not_need_parent_account() {
810 let changes = AccountChanges::new(address!("0000000000000000000000000000000000000001"))
811 .with_balance_change(BalanceChange::new(BlockAccessIndex::new(1), U256::from(10)))
812 .with_nonce_change(NonceChange::new(BlockAccessIndex::new(1), 7))
813 .with_code_change(CodeChange::new(BlockAccessIndex::new(1), bytes!("6001600155")));
814 let fields = BalAccountStateFields::from_changes(&changes);
815
816 assert!(bal_account_changes_state_root(&changes, fields));
817 assert!(!fields.needs_parent_account());
818 }
819
820 #[test]
821 fn bal_storage_change_needs_parent_account_when_leaf_fields_missing() {
822 let changes = AccountChanges::new(address!("0000000000000000000000000000000000000001"))
823 .with_storage_change(SlotChanges::new(
824 U256::from(1),
825 vec![StorageChange::new(BlockAccessIndex::new(1), U256::from(2))],
826 ));
827 let fields = BalAccountStateFields::from_changes(&changes);
828
829 assert!(bal_account_changes_state_root(&changes, fields));
830 assert!(fields.needs_parent_account());
831 }
832
833 #[test]
834 fn bal_account_uses_existing_fields_only_when_missing() {
835 let changes = AccountChanges::new(address!("0000000000000000000000000000000000000001"))
836 .with_balance_change(BalanceChange::new(BlockAccessIndex::new(1), U256::from(10)));
837 let fields = BalAccountStateFields::from_changes(&changes);
838 let account = fields.into_account(Some(Account {
839 balance: U256::from(1),
840 nonce: 3,
841 bytecode_hash: Some(B256::repeat_byte(0xaa)),
842 }));
843
844 assert_eq!(account.balance, U256::from(10));
845 assert_eq!(account.nonce, 3);
846 assert_eq!(account.bytecode_hash, Some(B256::repeat_byte(0xaa)));
847 }
848}
849
850#[derive(Debug)]
855pub enum PrewarmTaskEvent<R> {
856 TerminateTransactionExecution,
858 Terminate {
861 execution_outcome: Option<Arc<BlockExecutionOutput<R>>>,
864 valid_block_rx: mpsc::Receiver<()>,
869 },
870 FinishedTxExecution {
872 executed_transactions: usize,
874 },
875}
876
877#[derive(Metrics, Clone)]
879#[metrics(scope = "sync.prewarm")]
880pub struct PrewarmMetrics {
881 pub(crate) transactions: Gauge,
883 pub(crate) transactions_histogram: Histogram,
885 pub(crate) total_runtime: Histogram,
887 pub(crate) execution_duration: Histogram,
889 pub(crate) prefetch_storage_targets: Histogram,
891 pub(crate) cache_saving_duration: Gauge,
893 pub(crate) transaction_errors: Counter,
895 pub(crate) bal_slot_iteration_duration: Histogram,
897}