1use crate::tree::{
15 cached_state::{CachedStateProvider, SavedCache},
16 payload_processor::{
17 bal::{self, total_slots, BALSlotIter},
18 multiproof::{MultiProofMessage, VersionedMultiProofTargets},
19 PayloadExecutionCache,
20 },
21 precompile_cache::{CachedPrecompile, PrecompileCacheMap},
22 ExecutionEnv, StateProviderBuilder,
23};
24use alloy_consensus::transaction::TxHashRef;
25use alloy_eip7928::BlockAccessList;
26use alloy_eips::eip4895::Withdrawal;
27use alloy_evm::Database;
28use alloy_primitives::{keccak256, map::B256Set, B256};
29use crossbeam_channel::{Receiver as CrossbeamReceiver, Sender as CrossbeamSender};
30use metrics::{Counter, Gauge, Histogram};
31use reth_evm::{execute::ExecutableTxFor, ConfigureEvm, Evm, EvmFor, RecoveredTx, SpecFor};
32use reth_metrics::Metrics;
33use reth_primitives_traits::NodePrimitives;
34use reth_provider::{
35 AccountReader, BlockExecutionOutput, BlockReader, StateProvider, StateProviderFactory,
36 StateReader,
37};
38use reth_revm::{database::StateProviderDatabase, state::EvmState};
39use reth_tasks::Runtime;
40use reth_trie::MultiProofTargets;
41use std::{
42 ops::Range,
43 sync::{
44 atomic::{AtomicBool, Ordering},
45 mpsc::{self, channel, Receiver, Sender},
46 Arc,
47 },
48 time::Instant,
49};
50use tracing::{debug, debug_span, instrument, trace, warn, Span};
51
52#[derive(Debug)]
54pub enum PrewarmMode<Tx> {
55 Transactions(Receiver<Tx>),
57 BlockAccessList(Arc<BlockAccessList>),
59 Skipped,
62}
63
64#[derive(Clone)]
66struct IndexedTransaction<Tx> {
67 index: usize,
69 tx: Tx,
71}
72
73#[derive(Debug)]
78pub struct PrewarmCacheTask<N, P, Evm>
79where
80 N: NodePrimitives,
81 Evm: ConfigureEvm<Primitives = N>,
82{
83 executor: Runtime,
85 execution_cache: PayloadExecutionCache,
87 ctx: PrewarmContext<N, P, Evm>,
89 max_concurrency: usize,
91 to_multi_proof: Option<CrossbeamSender<MultiProofMessage>>,
93 actions_rx: Receiver<PrewarmTaskEvent<N::Receipt>>,
95 parent_span: Span,
97}
98
99impl<N, P, Evm> PrewarmCacheTask<N, P, Evm>
100where
101 N: NodePrimitives,
102 P: BlockReader + StateProviderFactory + StateReader + Clone + 'static,
103 Evm: ConfigureEvm<Primitives = N> + 'static,
104{
105 pub fn new(
107 executor: Runtime,
108 execution_cache: PayloadExecutionCache,
109 ctx: PrewarmContext<N, P, Evm>,
110 to_multi_proof: Option<CrossbeamSender<MultiProofMessage>>,
111 max_concurrency: usize,
112 ) -> (Self, Sender<PrewarmTaskEvent<N::Receipt>>) {
113 let (actions_tx, actions_rx) = channel();
114
115 trace!(
116 target: "engine::tree::payload_processor::prewarm",
117 max_concurrency,
118 transaction_count = ctx.env.transaction_count,
119 "Initialized prewarm task"
120 );
121
122 (
123 Self {
124 executor,
125 execution_cache,
126 ctx,
127 max_concurrency,
128 to_multi_proof,
129 actions_rx,
130 parent_span: Span::current(),
131 },
132 actions_tx,
133 )
134 }
135
136 fn spawn_all<Tx>(
142 &self,
143 pending: mpsc::Receiver<Tx>,
144 actions_tx: Sender<PrewarmTaskEvent<N::Receipt>>,
145 to_multi_proof: Option<CrossbeamSender<MultiProofMessage>>,
146 ) where
147 Tx: ExecutableTxFor<Evm> + Clone + Send + 'static,
148 {
149 let executor = self.executor.clone();
150 let ctx = self.ctx.clone();
151 let max_concurrency = self.max_concurrency;
152 let span = Span::current();
153
154 self.executor.spawn_blocking(move || {
155 let _enter = debug_span!(target: "engine::tree::payload_processor::prewarm", parent: span, "spawn_all").entered();
156
157 let (done_tx, done_rx) = mpsc::channel();
158
159 let transaction_count = ctx.env.transaction_count;
163 let workers_needed = if transaction_count == 0 {
164 max_concurrency
165 } else {
166 transaction_count.min(max_concurrency)
167 };
168
169 let tx_sender = ctx.clone().spawn_workers(workers_needed, &executor, to_multi_proof.clone(), done_tx.clone());
171
172 let mut tx_index = 0usize;
174 while let Ok(tx) = pending.recv() {
175 if ctx.terminate_execution.load(Ordering::Relaxed) {
177 trace!(
178 target: "engine::tree::payload_processor::prewarm",
179 "Termination requested, stopping transaction distribution"
180 );
181 break;
182 }
183
184 let indexed_tx = IndexedTransaction { index: tx_index, tx };
185
186 let _ = tx_sender.send(indexed_tx);
190
191 tx_index += 1;
192 }
193
194 if let Some(to_multi_proof) = to_multi_proof
196 && let Some(withdrawals) = &ctx.env.withdrawals
197 && !withdrawals.is_empty()
198 {
199 let targets =
200 multiproof_targets_from_withdrawals(withdrawals, ctx.v2_proofs_enabled);
201 let _ = to_multi_proof.send(MultiProofMessage::PrefetchProofs(targets));
202 }
203
204 drop(done_tx);
206 drop(tx_sender);
207 while done_rx.recv().is_ok() {}
208
209 let _ = actions_tx
210 .send(PrewarmTaskEvent::FinishedTxExecution { executed_transactions: tx_index });
211 });
212 }
213
214 #[instrument(level = "debug", target = "engine::tree::payload_processor::prewarm", skip_all)]
226 fn save_cache(
227 self,
228 execution_outcome: Arc<BlockExecutionOutput<N::Receipt>>,
229 valid_block_rx: mpsc::Receiver<()>,
230 ) {
231 let start = Instant::now();
232
233 let Self { execution_cache, ctx: PrewarmContext { env, metrics, saved_cache, .. }, .. } =
234 self;
235 let hash = env.hash;
236
237 if let Some(saved_cache) = saved_cache {
238 debug!(target: "engine::caching", parent_hash=?hash, "Updating execution cache");
239 execution_cache.update_with_guard(|cached| {
241 let (caches, cache_metrics, disable_cache_metrics) = saved_cache.split();
244 let new_cache = SavedCache::new(hash, caches, cache_metrics)
245 .with_disable_cache_metrics(disable_cache_metrics);
246
247 if new_cache.cache().insert_state(&execution_outcome.state).is_err() {
250 *cached = None;
252 debug!(target: "engine::caching", "cleared execution cache on update error");
253 return;
254 }
255
256 new_cache.update_metrics();
257
258 if valid_block_rx.recv().is_ok() {
259 *cached = Some(new_cache);
262 } else {
263 *cached = None;
266 debug!(target: "engine::caching", "cleared execution cache on invalid block");
267 }
268 });
269
270 let elapsed = start.elapsed();
271 debug!(target: "engine::caching", parent_hash=?hash, elapsed=?elapsed, "Updated execution cache");
272
273 metrics.cache_saving_duration.set(elapsed.as_secs_f64());
274 }
275 }
276
277 #[instrument(level = "debug", target = "engine::tree::payload_processor::prewarm", skip_all)]
282 fn run_bal_prewarm(
283 &self,
284 bal: Arc<BlockAccessList>,
285 actions_tx: Sender<PrewarmTaskEvent<N::Receipt>>,
286 ) {
287 if self.ctx.saved_cache.is_none() {
289 trace!(
290 target: "engine::tree::payload_processor::prewarm",
291 "Skipping BAL prewarm - no cache available"
292 );
293 self.send_bal_hashed_state(&bal);
294 let _ =
295 actions_tx.send(PrewarmTaskEvent::FinishedTxExecution { executed_transactions: 0 });
296 return;
297 }
298
299 let total_slots = total_slots(&bal);
300
301 trace!(
302 target: "engine::tree::payload_processor::prewarm",
303 total_slots,
304 max_concurrency = self.max_concurrency,
305 "Starting BAL prewarm"
306 );
307
308 if total_slots == 0 {
309 self.send_bal_hashed_state(&bal);
310 let _ =
311 actions_tx.send(PrewarmTaskEvent::FinishedTxExecution { executed_transactions: 0 });
312 return;
313 }
314
315 let (done_tx, done_rx) = mpsc::channel();
316
317 let workers_needed = total_slots.min(self.max_concurrency);
319
320 let slots_per_worker = total_slots / workers_needed;
322 let remainder = total_slots % workers_needed;
323
324 for i in 0..workers_needed {
326 let start = i * slots_per_worker + i.min(remainder);
327 let extra = if i < remainder { 1 } else { 0 };
328 let end = start + slots_per_worker + extra;
329
330 self.ctx.spawn_bal_worker(
331 i,
332 &self.executor,
333 Arc::clone(&bal),
334 start..end,
335 done_tx.clone(),
336 );
337 }
338
339 drop(done_tx);
341
342 let mut completed_workers = 0;
344 while done_rx.recv().is_ok() {
345 completed_workers += 1;
346 }
347
348 trace!(
349 target: "engine::tree::payload_processor::prewarm",
350 completed_workers,
351 "All BAL prewarm workers completed"
352 );
353
354 self.send_bal_hashed_state(&bal);
356
357 let _ = actions_tx.send(PrewarmTaskEvent::FinishedTxExecution { executed_transactions: 0 });
359 }
360
361 fn send_bal_hashed_state(&self, bal: &BlockAccessList) {
364 let Some(to_multi_proof) = &self.to_multi_proof else { return };
365
366 let provider = match self.ctx.provider.build() {
367 Ok(provider) => provider,
368 Err(err) => {
369 warn!(
370 target: "engine::tree::payload_processor::prewarm",
371 ?err,
372 "Failed to build provider for BAL hashed state conversion"
373 );
374 return;
375 }
376 };
377
378 match bal::bal_to_hashed_post_state(bal, &provider) {
379 Ok(hashed_state) => {
380 debug!(
381 target: "engine::tree::payload_processor::prewarm",
382 accounts = hashed_state.accounts.len(),
383 storages = hashed_state.storages.len(),
384 "Converted BAL to hashed post state"
385 );
386 let _ = to_multi_proof.send(MultiProofMessage::HashedStateUpdate(hashed_state));
387 let _ = to_multi_proof.send(MultiProofMessage::FinishedStateUpdates);
388 }
389 Err(err) => {
390 warn!(
391 target: "engine::tree::payload_processor::prewarm",
392 ?err,
393 "Failed to convert BAL to hashed state"
394 );
395 }
396 }
397 }
398
399 #[instrument(
404 parent = &self.parent_span,
405 level = "debug",
406 target = "engine::tree::payload_processor::prewarm",
407 name = "prewarm and caching",
408 skip_all
409 )]
410 pub fn run<Tx>(self, mode: PrewarmMode<Tx>, actions_tx: Sender<PrewarmTaskEvent<N::Receipt>>)
411 where
412 Tx: ExecutableTxFor<Evm> + Clone + Send + 'static,
413 {
414 match mode {
416 PrewarmMode::Transactions(pending) => {
417 self.spawn_all(pending, actions_tx, self.to_multi_proof.clone());
418 }
419 PrewarmMode::BlockAccessList(bal) => {
420 self.run_bal_prewarm(bal, actions_tx);
421 }
422 PrewarmMode::Skipped => {
423 let _ = actions_tx
424 .send(PrewarmTaskEvent::FinishedTxExecution { executed_transactions: 0 });
425 }
426 }
427
428 let mut final_execution_outcome = None;
429 let mut finished_execution = false;
430 while let Ok(event) = self.actions_rx.recv() {
431 match event {
432 PrewarmTaskEvent::TerminateTransactionExecution => {
433 debug!(target: "engine::tree::prewarm", "Terminating prewarm execution");
435 self.ctx.terminate_execution.store(true, Ordering::Relaxed);
436 }
437 PrewarmTaskEvent::Terminate { execution_outcome, valid_block_rx } => {
438 trace!(target: "engine::tree::payload_processor::prewarm", "Received termination signal");
439 final_execution_outcome =
440 Some(execution_outcome.map(|outcome| (outcome, valid_block_rx)));
441
442 if finished_execution {
443 break
445 }
446 }
447 PrewarmTaskEvent::FinishedTxExecution { executed_transactions } => {
448 trace!(target: "engine::tree::payload_processor::prewarm", "Finished prewarm execution signal");
449 self.ctx.metrics.transactions.set(executed_transactions as f64);
450 self.ctx.metrics.transactions_histogram.record(executed_transactions as f64);
451
452 finished_execution = true;
453
454 if final_execution_outcome.is_some() {
455 break
457 }
458 }
459 }
460 }
461
462 debug!(target: "engine::tree::payload_processor::prewarm", "Completed prewarm execution");
463
464 if let Some(Some((execution_outcome, valid_block_rx))) = final_execution_outcome {
466 self.save_cache(execution_outcome, valid_block_rx);
467 }
468 }
469}
470
471#[derive(Debug, Clone)]
473pub struct PrewarmContext<N, P, Evm>
474where
475 N: NodePrimitives,
476 Evm: ConfigureEvm<Primitives = N>,
477{
478 pub env: ExecutionEnv<Evm>,
480 pub evm_config: Evm,
482 pub saved_cache: Option<SavedCache>,
484 pub provider: StateProviderBuilder<N, P>,
486 pub metrics: PrewarmMetrics,
488 pub terminate_execution: Arc<AtomicBool>,
490 pub precompile_cache_disabled: bool,
492 pub precompile_cache_map: PrecompileCacheMap<SpecFor<Evm>>,
494 pub v2_proofs_enabled: bool,
496}
497
498impl<N, P, Evm> PrewarmContext<N, P, Evm>
499where
500 N: NodePrimitives,
501 P: BlockReader + StateProviderFactory + StateReader + Clone + 'static,
502 Evm: ConfigureEvm<Primitives = N> + 'static,
503{
504 #[instrument(level = "debug", target = "engine::tree::payload_processor::prewarm", skip_all)]
507 fn evm_for_ctx(
508 self,
509 ) -> Option<(EvmFor<Evm, impl Database>, PrewarmMetrics, Arc<AtomicBool>, bool)> {
510 let Self {
511 env,
512 evm_config,
513 saved_cache,
514 provider,
515 metrics,
516 terminate_execution,
517 precompile_cache_disabled,
518 precompile_cache_map,
519 v2_proofs_enabled,
520 } = self;
521
522 let mut state_provider = match provider.build() {
523 Ok(provider) => provider,
524 Err(err) => {
525 trace!(
526 target: "engine::tree::payload_processor::prewarm",
527 %err,
528 "Failed to build state provider in prewarm thread"
529 );
530 return None
531 }
532 };
533
534 if let Some(saved_cache) = saved_cache {
536 let caches = saved_cache.cache().clone();
537 let cache_metrics = saved_cache.metrics().clone();
538 state_provider =
539 Box::new(CachedStateProvider::new_prewarm(state_provider, caches, cache_metrics));
540 }
541
542 let state_provider = StateProviderDatabase::new(state_provider);
543
544 let mut evm_env = env.evm_env;
545
546 evm_env.cfg_env.disable_nonce_check = true;
549
550 evm_env.cfg_env.disable_balance_check = true;
553
554 let spec_id = *evm_env.spec_id();
556 let mut evm = evm_config.evm_with_env(state_provider, evm_env);
557
558 if !precompile_cache_disabled {
559 evm.precompiles_mut().map_pure_precompiles(|address, precompile| {
561 CachedPrecompile::wrap(
562 precompile,
563 precompile_cache_map.cache_for_address(*address),
564 spec_id,
565 None, )
567 });
568 }
569
570 Some((evm, metrics, terminate_execution, v2_proofs_enabled))
571 }
572
573 #[instrument(level = "debug", target = "engine::tree::payload_processor::prewarm", skip_all)]
584 fn transact_batch<Tx>(
585 self,
586 txs: CrossbeamReceiver<IndexedTransaction<Tx>>,
587 to_multi_proof: Option<CrossbeamSender<MultiProofMessage>>,
588 done_tx: Sender<()>,
589 ) where
590 Tx: ExecutableTxFor<Evm>,
591 {
592 let Some((mut evm, metrics, terminate_execution, v2_proofs_enabled)) = self.evm_for_ctx()
593 else {
594 return
595 };
596
597 while let Ok(IndexedTransaction { index, tx }) = txs.recv() {
598 let _enter = debug_span!(
599 target: "engine::tree::payload_processor::prewarm",
600 "prewarm tx",
601 index,
602 )
603 .entered();
604
605 let start = Instant::now();
607
608 if terminate_execution.load(Ordering::Relaxed) {
610 break
611 }
612
613 let (tx_env, tx) = tx.into_parts();
614 let res = match evm.transact(tx_env) {
615 Ok(res) => res,
616 Err(err) => {
617 trace!(
618 target: "engine::tree::payload_processor::prewarm",
619 %err,
620 tx_hash=%tx.tx().tx_hash(),
621 sender=%tx.signer(),
622 "Error when executing prewarm transaction",
623 );
624 metrics.transaction_errors.increment(1);
626 continue
628 }
629 };
630 metrics.execution_duration.record(start.elapsed());
631
632 if terminate_execution.load(Ordering::Relaxed) {
634 break
635 }
636
637 if index > 0 {
640 let (targets, storage_targets) =
641 multiproof_targets_from_state(res.state, v2_proofs_enabled);
642 metrics.prefetch_storage_targets.record(storage_targets as f64);
643 if let Some(to_multi_proof) = &to_multi_proof {
644 let _ = to_multi_proof.send(MultiProofMessage::PrefetchProofs(targets));
645 }
646 }
647
648 metrics.total_runtime.record(start.elapsed());
649 }
650
651 let _ = done_tx.send(());
653 }
654
655 fn spawn_workers<Tx>(
659 self,
660 workers_needed: usize,
661 task_executor: &Runtime,
662 to_multi_proof: Option<CrossbeamSender<MultiProofMessage>>,
663 done_tx: Sender<()>,
664 ) -> CrossbeamSender<IndexedTransaction<Tx>>
665 where
666 Tx: ExecutableTxFor<Evm> + Send + 'static,
667 {
668 let (tx_sender, tx_receiver) = crossbeam_channel::unbounded();
669
670 let executor = task_executor.clone();
672 let span = Span::current();
673 task_executor.spawn_blocking(move || {
674 let _enter = span.entered();
675 for idx in 0..workers_needed {
676 let ctx = self.clone();
677 let to_multi_proof = to_multi_proof.clone();
678 let done_tx = done_tx.clone();
679 let rx = tx_receiver.clone();
680 let span = debug_span!(target: "engine::tree::payload_processor::prewarm", "prewarm worker", idx);
681 executor.spawn_blocking(move || {
682 let _enter = span.entered();
683 ctx.transact_batch(rx, to_multi_proof, done_tx);
684 });
685 }
686 });
687
688 tx_sender
689 }
690
691 fn spawn_bal_worker(
696 &self,
697 idx: usize,
698 executor: &Runtime,
699 bal: Arc<BlockAccessList>,
700 range: Range<usize>,
701 done_tx: Sender<()>,
702 ) {
703 let ctx = self.clone();
704 let span = debug_span!(
705 target: "engine::tree::payload_processor::prewarm",
706 "bal prewarm worker",
707 idx,
708 range_start = range.start,
709 range_end = range.end
710 );
711
712 executor.spawn_blocking(move || {
713 let _enter = span.entered();
714 ctx.prefetch_bal_slots(bal, range, done_tx);
715 });
716 }
717
718 #[instrument(level = "debug", target = "engine::tree::payload_processor::prewarm", skip_all)]
723 fn prefetch_bal_slots(
724 self,
725 bal: Arc<BlockAccessList>,
726 range: Range<usize>,
727 done_tx: Sender<()>,
728 ) {
729 let Self { saved_cache, provider, metrics, .. } = self;
730
731 let state_provider = match provider.build() {
733 Ok(provider) => provider,
734 Err(err) => {
735 trace!(
736 target: "engine::tree::payload_processor::prewarm",
737 %err,
738 "Failed to build state provider in BAL prewarm thread"
739 );
740 let _ = done_tx.send(());
741 return;
742 }
743 };
744
745 let saved_cache = saved_cache.expect("BAL prewarm should only run with cache");
747 let caches = saved_cache.cache().clone();
748 let cache_metrics = saved_cache.metrics().clone();
749 let state_provider = CachedStateProvider::new(state_provider, caches, cache_metrics);
750
751 let start = Instant::now();
752
753 let mut last_address = None;
755
756 for (address, slot) in BALSlotIter::new(&bal, range.clone()) {
758 if last_address != Some(address) {
760 let _ = state_provider.basic_account(&address);
761 last_address = Some(address);
762 }
763
764 let _ = state_provider.storage(address, slot);
766 }
767
768 let elapsed = start.elapsed();
769
770 trace!(
771 target: "engine::tree::payload_processor::prewarm",
772 ?range,
773 elapsed_ms = elapsed.as_millis(),
774 "BAL prewarm worker completed"
775 );
776
777 let _ = done_tx.send(());
779 metrics.bal_slot_iteration_duration.record(elapsed.as_secs_f64());
780 }
781}
782
783fn multiproof_targets_from_state(
786 state: EvmState,
787 v2_enabled: bool,
788) -> (VersionedMultiProofTargets, usize) {
789 if v2_enabled {
790 multiproof_targets_v2_from_state(state)
791 } else {
792 multiproof_targets_legacy_from_state(state)
793 }
794}
795
796fn multiproof_targets_legacy_from_state(state: EvmState) -> (VersionedMultiProofTargets, usize) {
799 let mut targets = MultiProofTargets::with_capacity(state.len());
800 let mut storage_targets = 0;
801 for (addr, account) in state {
802 if !account.is_touched() || account.is_selfdestructed() {
810 continue
811 }
812
813 let mut storage_set =
814 B256Set::with_capacity_and_hasher(account.storage.len(), Default::default());
815 for (key, slot) in account.storage {
816 if !slot.is_changed() {
818 continue
819 }
820
821 storage_set.insert(keccak256(B256::new(key.to_be_bytes())));
822 }
823
824 storage_targets += storage_set.len();
825 targets.insert(keccak256(addr), storage_set);
826 }
827
828 (VersionedMultiProofTargets::Legacy(targets), storage_targets)
829}
830
831fn multiproof_targets_v2_from_state(state: EvmState) -> (VersionedMultiProofTargets, usize) {
834 use reth_trie::proof_v2;
835 use reth_trie_parallel::targets_v2::MultiProofTargetsV2;
836
837 let mut targets = MultiProofTargetsV2::default();
838 let mut storage_target_count = 0;
839 for (addr, account) in state {
840 if !account.is_touched() || account.is_selfdestructed() {
848 continue
849 }
850
851 let hashed_address = keccak256(addr);
852 targets.account_targets.push(hashed_address.into());
853
854 let mut storage_slots = Vec::with_capacity(account.storage.len());
855 for (key, slot) in account.storage {
856 if !slot.is_changed() {
858 continue
859 }
860
861 let hashed_slot = keccak256(B256::new(key.to_be_bytes()));
862 storage_slots.push(proof_v2::Target::from(hashed_slot));
863 }
864
865 storage_target_count += storage_slots.len();
866 if !storage_slots.is_empty() {
867 targets.storage_targets.insert(hashed_address, storage_slots);
868 }
869 }
870
871 (VersionedMultiProofTargets::V2(targets), storage_target_count)
872}
873
874fn multiproof_targets_from_withdrawals(
879 withdrawals: &[Withdrawal],
880 v2_enabled: bool,
881) -> VersionedMultiProofTargets {
882 use reth_trie_parallel::targets_v2::MultiProofTargetsV2;
883 if v2_enabled {
884 VersionedMultiProofTargets::V2(MultiProofTargetsV2 {
885 account_targets: withdrawals.iter().map(|w| keccak256(w.address).into()).collect(),
886 ..Default::default()
887 })
888 } else {
889 VersionedMultiProofTargets::Legacy(
890 withdrawals.iter().map(|w| (keccak256(w.address), Default::default())).collect(),
891 )
892 }
893}
894
895#[derive(Debug)]
900pub enum PrewarmTaskEvent<R> {
901 TerminateTransactionExecution,
903 Terminate {
906 execution_outcome: Option<Arc<BlockExecutionOutput<R>>>,
909 valid_block_rx: mpsc::Receiver<()>,
914 },
915 FinishedTxExecution {
917 executed_transactions: usize,
919 },
920}
921
922#[derive(Metrics, Clone)]
924#[metrics(scope = "sync.prewarm")]
925pub struct PrewarmMetrics {
926 pub(crate) transactions: Gauge,
928 pub(crate) transactions_histogram: Histogram,
930 pub(crate) total_runtime: Histogram,
932 pub(crate) execution_duration: Histogram,
934 pub(crate) prefetch_storage_targets: Histogram,
936 pub(crate) cache_saving_duration: Gauge,
938 pub(crate) transaction_errors: Counter,
940 pub(crate) bal_slot_iteration_duration: Histogram,
942}