1mod sparse_trie;
57
58use self::sparse_trie::{SparseTrieCacheTask, SparseTrieTaskMetrics};
59use crate::tree::{
60 metrics::BlockValidationMetrics, EngineApiTreeState, ExecutionEnv, StateProviderBuilder,
61 TreeConfig,
62};
63use alloy_primitives::B256;
64use crossbeam_channel::{Receiver as CrossbeamReceiver, Sender as CrossbeamSender};
65use reth_chain_state::{ExecutedBlock, PreservedSparseTrie};
66use reth_errors::ProviderResult;
67use reth_evm::{ConfigureEvm, OnStateHook};
68use reth_primitives_traits::{
69 AlloyBlockHeader, FastInstant as Instant, NodePrimitives, RecoveredBlock, SealedHeader,
70};
71use reth_provider::{
72 BlockExecutionOutput, BlockNumReader, DatabaseProviderFactory, DatabaseProviderROFactory,
73 HashedPostStateProvider, ProviderError, PruneCheckpointReader, StageCheckpointReader,
74 StateRootProvider, StorageSettingsCache, TryIntoHistoricalStateProvider,
75};
76use reth_storage_overlay::{OverlayManager, OverlayStateProviderFactory};
77use reth_tasks::utils::increase_thread_priority;
78use reth_trie::{
79 hashed_cursor::HashedCursorFactory, trie_cursor::TrieCursorFactory, updates::TrieUpdates,
80 HashedPostState,
81};
82use reth_trie_parallel::proof_task::{ProofResultMessage, ProofTaskCtx, ProofWorkerHandle};
83pub use reth_trie_parallel::{
84 error::StateRootTaskError,
85 state_root_task::{
86 evm_state_to_hashed_post_state, PayloadStateRootHandle, StateAccessHint,
87 StateRootComputeOutcome, StateRootHandle, StateRootHintStream, StateRootMessage,
88 StateRootSink, StateRootTaskCancelGuard, StateRootUpdateHook, StateRootUpdateStream,
89 },
90};
91#[cfg(feature = "trie-debug")]
92use reth_trie_sparse::debug_recorder::TrieDebugRecorder;
93use reth_trie_sparse::{
94 ArenaParallelSparseTrie, RevealableSparseTrie, SparseStateTrie, TrieNodeEpoch,
95};
96use std::{
97 fmt,
98 sync::{
99 mpsc::{self, RecvTimeoutError},
100 Arc,
101 },
102 time::Duration,
103};
104use tracing::{debug, debug_span, instrument, warn, Span};
105
106pub type LazyHashedPostState = reth_tasks::LazyHandle<Arc<HashedPostState>>;
108
109pub trait StateRootStrategy<N, P, Evm>: Send + Sync
111where
112 N: NodePrimitives,
113 Evm: ConfigureEvm<Primitives = N>,
114{
115 fn prepare(
120 &self,
121 ctx: StateRootJobContext<'_, N, P, Evm>,
122 ) -> ProviderResult<PreparedStateRootJob<N>>;
123
124 fn prepare_payload_builder(
131 &self,
132 _ctx: PayloadStateRootJobContext<'_, N, P>,
133 ) -> ProviderResult<Option<PayloadStateRootHandle>> {
134 Ok(None)
135 }
136}
137
138pub struct PayloadStateRootJobContext<'a, N, P>
140where
141 N: NodePrimitives,
142{
143 executor: &'a reth_tasks::Runtime,
144 overlay_manager: &'a OverlayManager<N>,
145 parent_hash: B256,
146 parent_header: &'a N::BlockHeader,
147 timestamp: u64,
148 state: &'a mut EngineApiTreeState<N>,
149 provider_builder: StateProviderBuilder<N, P>,
150 overlay_factory: OverlayStateProviderFactory<P, N>,
151 config: &'a TreeConfig,
152}
153
154impl<N, P> fmt::Debug for PayloadStateRootJobContext<'_, N, P>
155where
156 N: NodePrimitives,
157{
158 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
159 f.debug_struct("PayloadStateRootJobContext")
160 .field("parent_hash", &self.parent_hash)
161 .field("parent_state_root", &self.parent_state_root())
162 .field("timestamp", &self.timestamp)
163 .field("pending_sparse_trie_prune", &self.state.pending_sparse_trie_prune())
164 .finish_non_exhaustive()
165 }
166}
167
168impl<'a, N, P> PayloadStateRootJobContext<'a, N, P>
169where
170 N: NodePrimitives,
171{
172 #[expect(clippy::too_many_arguments)]
174 pub(crate) const fn new(
175 executor: &'a reth_tasks::Runtime,
176 overlay_manager: &'a OverlayManager<N>,
177 parent_hash: B256,
178 parent_header: &'a N::BlockHeader,
179 timestamp: u64,
180 state: &'a mut EngineApiTreeState<N>,
181 provider_builder: StateProviderBuilder<N, P>,
182 overlay_factory: OverlayStateProviderFactory<P, N>,
183 config: &'a TreeConfig,
184 ) -> Self {
185 Self {
186 executor,
187 overlay_manager,
188 parent_hash,
189 parent_header,
190 timestamp,
191 state,
192 provider_builder,
193 overlay_factory,
194 config,
195 }
196 }
197
198 pub const fn parent_hash(&self) -> B256 {
200 self.parent_hash
201 }
202
203 pub const fn parent_header(&self) -> &N::BlockHeader {
208 self.parent_header
209 }
210
211 pub fn parent_state_root(&self) -> B256 {
213 self.parent_header.state_root()
214 }
215
216 pub const fn timestamp(&self) -> u64 {
220 self.timestamp
221 }
222
223 pub const fn executor(&self) -> &reth_tasks::Runtime {
225 self.executor
226 }
227
228 pub fn provider_builder(&self) -> StateProviderBuilder<N, P>
230 where
231 P: Clone,
232 {
233 self.provider_builder.clone()
234 }
235
236 pub fn take_sparse_trie_prune_blocks(&mut self) -> Option<Vec<ExecutedBlock<N>>> {
241 self.state.take_sparse_trie_prune_blocks(self.parent_hash)
242 }
243}
244
245pub struct StateRootJobContext<'a, N, P, Evm>
247where
248 N: NodePrimitives,
249 Evm: ConfigureEvm<Primitives = N>,
250{
251 executor: &'a reth_tasks::Runtime,
252 overlay_manager: &'a OverlayManager<N>,
253 env: &'a ExecutionEnv<Evm>,
254 parent_header: &'a SealedHeader<N::BlockHeader>,
255 provider_builder: StateProviderBuilder<N, P>,
256 overlay_factory: OverlayStateProviderFactory<P, N>,
257 config: &'a TreeConfig,
258 parallel_bal_execution: bool,
259 state: &'a mut EngineApiTreeState<N>,
260}
261
262impl<N, P, Evm> fmt::Debug for StateRootJobContext<'_, N, P, Evm>
263where
264 N: NodePrimitives,
265 Evm: ConfigureEvm<Primitives = N>,
266{
267 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
268 f.debug_struct("StateRootJobContext")
269 .field("parallel_bal_execution", &self.parallel_bal_execution)
270 .field("has_pending_sparse_trie_prune", &self.state.pending_sparse_trie_prune())
271 .finish_non_exhaustive()
272 }
273}
274
275impl<'a, N, P, Evm> StateRootJobContext<'a, N, P, Evm>
276where
277 N: NodePrimitives,
278 Evm: ConfigureEvm<Primitives = N>,
279{
280 #[expect(clippy::too_many_arguments)]
282 pub(crate) const fn new(
283 executor: &'a reth_tasks::Runtime,
284 overlay_manager: &'a OverlayManager<N>,
285 env: &'a ExecutionEnv<Evm>,
286 parent_header: &'a SealedHeader<N::BlockHeader>,
287 provider_builder: StateProviderBuilder<N, P>,
288 overlay_factory: OverlayStateProviderFactory<P, N>,
289 config: &'a TreeConfig,
290 parallel_bal_execution: bool,
291 state: &'a mut EngineApiTreeState<N>,
292 ) -> Self {
293 Self {
294 executor,
295 overlay_manager,
296 env,
297 parent_header,
298 provider_builder,
299 overlay_factory,
300 config,
301 parallel_bal_execution,
302 state,
303 }
304 }
305
306 pub const fn env(&self) -> &ExecutionEnv<Evm> {
308 self.env
309 }
310
311 pub const fn parent_header(&self) -> &SealedHeader<N::BlockHeader> {
313 self.parent_header
314 }
315
316 pub const fn executor(&self) -> &reth_tasks::Runtime {
318 self.executor
319 }
320
321 pub const fn parallel_bal_execution(&self) -> bool {
323 self.parallel_bal_execution
324 }
325
326 pub fn provider_builder(&self) -> StateProviderBuilder<N, P>
328 where
329 P: Clone,
330 {
331 self.provider_builder.clone()
332 }
333
334 pub fn take_sparse_trie_prune_blocks(&mut self) -> Option<Vec<ExecutedBlock<N>>> {
339 self.state.take_sparse_trie_prune_blocks(self.env.parent_hash)
340 }
341}
342
343pub struct PreparedStateRootJob<N: NodePrimitives> {
350 job: Box<dyn StateRootJob<N>>,
351 execution_hook: Option<StateRootUpdateHook>,
352 hint_stream: Option<StateRootHintStream>,
353 hashed_update_stream: Option<StateRootUpdateStream>,
354 hashed_state_rx: Option<mpsc::Receiver<Arc<HashedPostState>>>,
355}
356
357impl<N: NodePrimitives> fmt::Debug for PreparedStateRootJob<N> {
358 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
359 f.debug_struct("PreparedStateRootJob")
360 .field("name", &self.job.name())
361 .field("has_execution_hook", &self.execution_hook.is_some())
362 .field("has_hint_stream", &self.hint_stream.is_some())
363 .field("has_hashed_update_stream", &self.hashed_update_stream.is_some())
364 .field("has_hashed_state_rx", &self.hashed_state_rx.is_some())
365 .finish()
366 }
367}
368
369impl<N: NodePrimitives> PreparedStateRootJob<N> {
370 pub const fn new(
372 job: Box<dyn StateRootJob<N>>,
373 hashed_state_rx: Option<mpsc::Receiver<Arc<HashedPostState>>>,
374 ) -> Self {
375 Self {
376 job,
377 execution_hook: None,
378 hint_stream: None,
379 hashed_update_stream: None,
380 hashed_state_rx,
381 }
382 }
383
384 pub fn with_execution_hook(mut self, hook: StateRootUpdateHook) -> Self {
386 self.execution_hook = Some(hook);
387 self
388 }
389
390 pub fn with_hint_stream(mut self, hint_stream: StateRootHintStream) -> Self {
392 self.hint_stream = Some(hint_stream);
393 self
394 }
395
396 pub fn with_hashed_update_stream(mut self, stream: StateRootUpdateStream) -> Self {
398 self.hashed_update_stream = Some(stream);
399 self
400 }
401
402 pub fn name(&self) -> &'static str {
404 self.job.name()
405 }
406
407 pub fn take_execution_hook(&mut self) -> Option<Box<dyn OnStateHook + 'static>> {
409 self.execution_hook.take().map(|hook| Box::new(hook) as Box<dyn OnStateHook + 'static>)
410 }
411
412 pub const fn take_hint_stream(&mut self) -> Option<StateRootHintStream> {
414 self.hint_stream.take()
415 }
416
417 pub const fn take_hashed_update_stream(&mut self) -> Option<StateRootUpdateStream> {
419 self.hashed_update_stream.take()
420 }
421
422 pub const fn take_hashed_state_rx(&mut self) -> Option<mpsc::Receiver<Arc<HashedPostState>>> {
428 self.hashed_state_rx.take()
429 }
430
431 pub fn finish(
433 &mut self,
434 block: &RecoveredBlock<N::Block>,
435 output: Arc<BlockExecutionOutput<N::Receipt>>,
436 hashed_state: &LazyHashedPostState,
437 ) -> ProviderResult<StateRootJobOutcome> {
438 self.job.finish(block, output, hashed_state)
439 }
440}
441
442pub trait StateRootJob<N: NodePrimitives>: Send {
444 fn name(&self) -> &'static str;
446
447 fn finish(
451 &mut self,
452 block: &RecoveredBlock<N::Block>,
453 output: Arc<BlockExecutionOutput<N::Receipt>>,
454 hashed_state: &LazyHashedPostState,
455 ) -> ProviderResult<StateRootJobOutcome>;
456}
457
458#[derive(Debug)]
460pub struct StateRootJobOutcome {
461 pub state_root: B256,
463 pub trie_updates: Arc<TrieUpdates>,
465 pub hashed_state: Option<Arc<HashedPostState>>,
470}
471
472impl StateRootJobOutcome {
473 pub const fn new(state_root: B256, trie_updates: Arc<TrieUpdates>) -> Self {
475 Self { state_root, trie_updates, hashed_state: None }
476 }
477
478 pub fn with_hashed_state(mut self, hashed_state: Option<Arc<HashedPostState>>) -> Self {
480 self.hashed_state = hashed_state;
481 self
482 }
483}
484
485type SerialFallbackRx = mpsc::Receiver<ProviderResult<(B256, TrieUpdates, Arc<HashedPostState>)>>;
488
489#[derive(Default)]
497pub struct DefaultStateRootStrategy {
498 metrics: SparseTrieTaskMetrics,
499}
500
501impl fmt::Debug for DefaultStateRootStrategy {
502 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
503 f.debug_struct("DefaultStateRootStrategy").finish_non_exhaustive()
504 }
505}
506
507impl DefaultStateRootStrategy {
508 const SMALL_BLOCK_PROOF_WORKER_TX_THRESHOLD: usize = 30;
511
512 #[instrument(level = "debug", target = "engine::tree::payload_processor", skip_all)]
518 fn spawn_state_root<N, F>(
519 &self,
520 executor: &reth_tasks::Runtime,
521 overlay_manager: &OverlayManager<N>,
522 multiproof_provider_factory: F,
523 options: StateRootTaskOptions<'_, N>,
524 ) -> StateRootHandle
525 where
526 N: NodePrimitives,
527 F: DatabaseProviderROFactory<Provider: TrieCursorFactory + HashedCursorFactory>
528 + Clone
529 + Send
530 + Sync
531 + 'static,
532 {
533 let StateRootTaskOptions {
534 parent_header,
535 preserved_sparse_trie,
536 transaction_count,
537 config,
538 pending_sparse_trie_prune_blocks,
539 } = options;
540 let (updates_tx, from_multi_proof) = crossbeam_channel::unbounded();
541 let (cancel_guard, cancel_rx) = StateRootTaskCancelGuard::channel();
542 let (proof_result_tx, proof_result_rx) =
543 crossbeam_channel::unbounded::<ProofResultMessage>();
544
545 let task_ctx = ProofTaskCtx::new(multiproof_provider_factory);
546 #[cfg(feature = "trie-debug")]
547 let task_ctx = task_ctx.with_proof_jitter(config.proof_jitter());
548 let halve_workers = transaction_count
549 .is_some_and(|count| count <= Self::SMALL_BLOCK_PROOF_WORKER_TX_THRESHOLD);
550 let proof_handle =
551 ProofWorkerHandle::new(executor, task_ctx, halve_workers, proof_result_tx.clone());
552
553 let (state_root_tx, state_root_rx) = mpsc::channel();
554 let (hashed_state_tx, hashed_state_rx) = mpsc::channel();
555 let parent_state_root = parent_header.state_root();
556
557 self.spawn_sparse_trie_task(
558 executor,
559 overlay_manager,
560 proof_handle,
561 proof_result_tx,
562 proof_result_rx,
563 state_root_tx,
564 hashed_state_tx,
565 from_multi_proof,
566 cancel_rx,
567 SparseTrieTaskOptions {
568 parent_header,
569 preserved_sparse_trie,
570 chunk_size: config.multiproof_chunk_size(),
571 pending_sparse_trie_prune_blocks: if config.disable_sparse_trie_cache_pruning() {
572 None
573 } else {
574 pending_sparse_trie_prune_blocks
575 },
576 },
577 );
578
579 StateRootHandle::new(
580 parent_state_root,
581 updates_tx,
582 cancel_guard,
583 state_root_rx,
584 hashed_state_rx,
585 )
586 }
587
588 #[expect(clippy::too_many_arguments)]
590 fn spawn_sparse_trie_task<N: NodePrimitives>(
591 &self,
592 executor: &reth_tasks::Runtime,
593 overlay_manager: &OverlayManager<N>,
594 proof_worker_handle: ProofWorkerHandle,
595 proof_result_tx: CrossbeamSender<ProofResultMessage>,
596 proof_result_rx: CrossbeamReceiver<ProofResultMessage>,
597 state_root_tx: mpsc::Sender<Result<StateRootComputeOutcome, StateRootTaskError>>,
598 hashed_state_tx: mpsc::Sender<Arc<HashedPostState>>,
599 from_multi_proof: CrossbeamReceiver<StateRootMessage>,
600 cancel_rx: CrossbeamReceiver<()>,
601 options: SparseTrieTaskOptions<N>,
602 ) {
603 let SparseTrieTaskOptions {
604 parent_header,
605 preserved_sparse_trie,
606 chunk_size,
607 pending_sparse_trie_prune_blocks,
608 } = options;
609 let overlay_manager = overlay_manager.clone();
610 let trie_metrics = self.metrics.clone();
611 let executor = executor.clone();
612
613 let parent_span = Span::current();
614 executor.clone().spawn_blocking_named("sparse-trie", move || {
615 reth_tasks::once!(increase_thread_priority);
616
617 let parent_hash = parent_header.hash();
618 let parent_state_root = parent_header.state_root();
619 let new_epoch = TrieNodeEpoch::new(parent_header.number().saturating_add(1));
620 let prune_before =
621 sparse_trie_prune_before(pending_sparse_trie_prune_blocks.as_deref(), new_epoch);
622
623 let _enter = debug_span!(
624 target: "engine::tree::payload_processor",
625 parent: parent_span,
626 "sparse_trie_task"
627 )
628 .entered();
629
630 let new_sparse_state_trie = || {
631 debug!(
632 target: "engine::tree::payload_processor",
633 "Creating new sparse trie - no preserved trie available"
634 );
635 let default_trie =
636 RevealableSparseTrie::blind_from(ArenaParallelSparseTrie::default());
637 SparseStateTrie::default()
638 .with_accounts_trie(default_trie.clone())
639 .with_default_storage_trie(default_trie)
640 .with_updates(true)
641 };
642
643 let mut sparse_trie_anchor_hash = parent_hash;
644 let mut reused_preserved_sparse_trie = false;
645 let sparse_state_trie = match preserved_sparse_trie {
646 Some(preserved) => {
647 let start = Instant::now();
648 let preserved_anchor_hash = preserved.anchor_hash();
649 let preserved = preserved.into_trie_for(parent_state_root);
650 trie_metrics
651 .sparse_trie_cache_wait_duration_histogram
652 .record(start.elapsed().as_secs_f64());
653
654 match preserved {
655 Ok(Some(trie)) => {
656 sparse_trie_anchor_hash = preserved_anchor_hash;
657 reused_preserved_sparse_trie = true;
658 trie
659 }
660 Ok(None) => new_sparse_state_trie(),
661 Err(err) => {
662 let _ =
663 state_root_tx.send(Err(StateRootTaskError::Other(err.to_string())));
664 return;
665 }
666 }
667 }
668 None => new_sparse_state_trie(),
669 };
670 let mut task = SparseTrieCacheTask::new_with_trie(
671 &executor,
672 from_multi_proof,
673 cancel_rx,
674 hashed_state_tx,
675 proof_worker_handle,
676 proof_result_tx,
677 proof_result_rx,
678 trie_metrics.clone(),
679 sparse_state_trie,
680 parent_state_root,
681 new_epoch,
682 chunk_size,
683 );
684
685 let result = task.run();
686 let task_result = result.as_ref().ok().cloned();
687
688 let pending_trie = if let Some(result) = &task_result {
691 let preserved_anchor_hash = published_sparse_trie_anchor_hash(
692 sparse_trie_anchor_hash,
693 reused_preserved_sparse_trie,
694 pending_sparse_trie_prune_blocks.as_deref(),
695 );
696 let (preserved, completer) =
697 PreservedSparseTrie::pending(result.state_root, preserved_anchor_hash);
698 overlay_manager.store_sparse_trie(preserved);
699 Some(completer)
700 } else {
701 overlay_manager.clear_sparse_trie();
702 None
703 };
704
705 if state_root_tx.send(result).is_err() {
706 debug!(
712 target: "engine::tree::payload_processor",
713 "State root receiver dropped, dropping trie"
714 );
715 let (trie, deferred) = task.into_cleared_trie();
716 overlay_manager.clear_sparse_trie();
717 executor.spawn_drop(trie);
718 executor.spawn_drop(deferred);
719 return;
720 }
721
722 let _enter =
723 debug_span!(target: "engine::tree::payload_processor", "preserve").entered();
724 let mut trie_to_drop = None;
725 let deferred = if task_result.is_some() {
726 let pending_trie =
727 pending_trie.expect("pending trie is created for successful task result");
728 let start = Instant::now();
729 let (mut trie, deferred) = task.into_trie_for_reuse();
730 if let Some(prune_before) = prune_before {
731 let prune_start = Instant::now();
732 trie.prune(prune_before);
733 trie_metrics
734 .sparse_trie_prune_duration_histogram
735 .record(prune_start.elapsed().as_secs_f64());
736 }
737 trie_metrics
738 .into_trie_for_reuse_duration_histogram
739 .record(start.elapsed().as_secs_f64());
740 trie_metrics
741 .sparse_trie_retained_storage_tries
742 .set(trie.retained_storage_tries_count() as f64);
743 if let Err(trie) = pending_trie.complete(trie) {
744 trie_to_drop = Some(trie);
745 }
746 deferred
747 } else {
748 debug!(
749 target: "engine::tree::payload_processor",
750 "State root computation failed, dropping trie"
751 );
752 let (trie, deferred) = task.into_cleared_trie();
753 trie_to_drop = Some(trie);
754 deferred
755 };
756 if let Some(trie) = trie_to_drop {
757 executor.spawn_drop(trie);
758 }
759 executor.spawn_drop(deferred);
760 });
761 }
762}
763
764struct SparseTrieTaskOptions<N: NodePrimitives> {
765 parent_header: SealedHeader<N::BlockHeader>,
766 preserved_sparse_trie: Option<PreservedSparseTrie>,
767 chunk_size: usize,
768 pending_sparse_trie_prune_blocks: Option<Vec<ExecutedBlock<N>>>,
770}
771
772struct StateRootTaskOptions<'a, N: NodePrimitives> {
773 parent_header: SealedHeader<N::BlockHeader>,
774 preserved_sparse_trie: Option<PreservedSparseTrie>,
775 transaction_count: Option<usize>,
776 config: &'a TreeConfig,
777 pending_sparse_trie_prune_blocks: Option<Vec<ExecutedBlock<N>>>,
778}
779
780fn sparse_trie_prune_before<N: NodePrimitives>(
781 pending_sparse_trie_prune_blocks: Option<&[ExecutedBlock<N>]>,
782 new_epoch: TrieNodeEpoch,
783) -> Option<TrieNodeEpoch> {
784 match pending_sparse_trie_prune_blocks {
787 None => None,
788 Some([]) => Some(new_epoch),
789 Some([.., oldest]) => Some(TrieNodeEpoch::new(oldest.recovered_block().number())),
790 }
791}
792
793fn published_sparse_trie_anchor_hash<N: NodePrimitives>(
794 sparse_trie_anchor_hash: B256,
795 reused_preserved_sparse_trie: bool,
796 pending_sparse_trie_prune_blocks: Option<&[ExecutedBlock<N>]>,
797) -> B256 {
798 if !reused_preserved_sparse_trie {
799 return sparse_trie_anchor_hash
800 }
801
802 let Some(prune_blocks) = pending_sparse_trie_prune_blocks else {
803 return sparse_trie_anchor_hash
804 };
805 let Some(oldest_prune_block) = prune_blocks.last() else { return sparse_trie_anchor_hash };
806
807 if prune_blocks.iter().any(|block| block.recovered_block().hash() == sparse_trie_anchor_hash) {
813 return sparse_trie_anchor_hash
814 }
815
816 oldest_prune_block.recovered_block().parent_hash()
817}
818
819impl<N, P, Evm> StateRootStrategy<N, P, Evm> for DefaultStateRootStrategy
820where
821 N: NodePrimitives,
822 P: DatabaseProviderFactory + Clone + 'static,
823 P::Provider: BlockNumReader
824 + PruneCheckpointReader
825 + StageCheckpointReader
826 + StorageSettingsCache
827 + TryIntoHistoricalStateProvider
828 + 'static,
829 OverlayStateProviderFactory<P, N>: DatabaseProviderROFactory<Provider: TrieCursorFactory + HashedCursorFactory>
830 + Clone
831 + 'static,
832 Evm: ConfigureEvm<Primitives = N> + 'static,
833{
834 fn prepare(
835 &self,
836 mut ctx: StateRootJobContext<'_, N, P, Evm>,
837 ) -> ProviderResult<PreparedStateRootJob<N>> {
838 if ctx.config.skip_state_root() {
839 return Ok(PreparedStateRootJob::new(Box::new(SkippedStateRootJob {}), None))
840 }
841
842 if !ctx.config.use_state_root_task() {
843 return Ok(PreparedStateRootJob::new(
844 Box::new(SynchronousStateRootJob { provider_builder: ctx.provider_builder }),
845 None,
846 ))
847 }
848
849 let pending_sparse_trie_prune_blocks = ctx.take_sparse_trie_prune_blocks();
850 let StateRootJobContext {
851 executor,
852 overlay_manager,
853 env,
854 parent_header,
855 provider_builder,
856 overlay_factory,
857 config,
858 parallel_bal_execution,
859 state: _,
860 } = ctx;
861
862 let preserved_sparse_trie = overlay_manager.take_sparse_trie();
863 let overlay_factory = if let Some(anchor_hash) = preserved_sparse_trie
864 .as_ref()
865 .filter(|trie| trie.state_root() == env.parent_state_root)
866 .map(|trie| trie.anchor_hash())
867 {
868 overlay_factory.with_skip_overlay_for_reused_sparse_trie(anchor_hash)
869 } else {
870 overlay_factory
871 };
872
873 let mut handle = self.spawn_state_root(
874 executor,
875 overlay_manager,
876 overlay_factory.clone(),
877 StateRootTaskOptions {
878 parent_header: parent_header.clone(),
879 preserved_sparse_trie,
880 transaction_count: Some(env.transaction_count),
881 config,
882 pending_sparse_trie_prune_blocks,
883 },
884 );
885
886 let (hashed_update_stream, execution_hook): (
890 Option<StateRootUpdateStream>,
891 Option<StateRootUpdateHook>,
892 ) = match parallel_bal_execution {
893 true => (Some(handle.take_hashed_update_stream()), None),
894 false => (None, Some(handle.take_execution_hook())),
895 };
896 let hint_stream = handle.take_hint_stream();
897
898 let hashed_state_rx = Some(handle.take_hashed_state_rx());
899
900 let mut prepared = PreparedStateRootJob::new(
901 Box::new(SparseTrieStateRootJob {
902 handle,
903 provider_builder,
904 overlay_factory,
905 executor: executor.clone(),
906 timeout: config.state_root_task_timeout(),
907 compare_trie_updates: config.always_compare_trie_updates(),
908 metrics: BlockValidationMetrics::default(),
909 }),
910 hashed_state_rx,
911 )
912 .with_hint_stream(hint_stream);
913 if let Some(hook) = execution_hook {
914 prepared = prepared.with_execution_hook(hook);
915 }
916 if let Some(stream) = hashed_update_stream {
917 prepared = prepared.with_hashed_update_stream(stream);
918 }
919 Ok(prepared)
920 }
921
922 fn prepare_payload_builder(
923 &self,
924 mut ctx: PayloadStateRootJobContext<'_, N, P>,
925 ) -> ProviderResult<Option<PayloadStateRootHandle>> {
926 if !ctx.config.share_sparse_trie_with_payload_builder() ||
929 ctx.config.skip_state_root() ||
930 !ctx.config.has_enough_parallelism()
931 {
932 return Ok(None)
933 }
934
935 let pending_sparse_trie_prune_blocks = ctx.take_sparse_trie_prune_blocks();
936 let parent_state_root = ctx.parent_state_root();
937 let parent_header = SealedHeader::new(ctx.parent_header().clone(), ctx.parent_hash());
938 let preserved_sparse_trie = ctx.overlay_manager.take_sparse_trie();
939 let overlay_factory = if let Some(anchor_hash) = preserved_sparse_trie
940 .as_ref()
941 .filter(|trie| trie.state_root() == parent_state_root)
942 .map(|trie| trie.anchor_hash())
943 {
944 ctx.overlay_factory.clone().with_skip_overlay_for_reused_sparse_trie(anchor_hash)
945 } else {
946 ctx.overlay_factory.clone()
947 };
948 Ok(Some(
949 self.spawn_state_root(
950 ctx.executor,
951 ctx.overlay_manager,
952 overlay_factory,
953 StateRootTaskOptions {
954 parent_header,
955 preserved_sparse_trie,
956 transaction_count: None,
958 config: ctx.config,
959 pending_sparse_trie_prune_blocks,
960 },
961 )
962 .into_payload_state_root_handle(),
963 ))
964 }
965}
966
967#[derive(Debug)]
968struct SkippedStateRootJob {}
969
970impl<N: NodePrimitives> StateRootJob<N> for SkippedStateRootJob {
971 fn name(&self) -> &'static str {
972 "skipped"
973 }
974
975 fn finish(
976 &mut self,
977 block: &RecoveredBlock<N::Block>,
978 _output: Arc<BlockExecutionOutput<N::Receipt>>,
979 _hashed_state: &LazyHashedPostState,
980 ) -> ProviderResult<StateRootJobOutcome> {
981 Ok(StateRootJobOutcome::new(block.header().state_root(), Arc::new(TrieUpdates::default())))
982 }
983}
984
985#[derive(Debug)]
986struct SynchronousStateRootJob<N: NodePrimitives, P> {
987 provider_builder: StateProviderBuilder<N, P>,
988}
989
990impl<N, P> StateRootJob<N> for SynchronousStateRootJob<N, P>
991where
992 N: NodePrimitives,
993 P: DatabaseProviderFactory + Clone + 'static,
994 P::Provider: BlockNumReader
995 + PruneCheckpointReader
996 + StageCheckpointReader
997 + StorageSettingsCache
998 + TryIntoHistoricalStateProvider
999 + 'static,
1000{
1001 fn name(&self) -> &'static str {
1002 "synchronous"
1003 }
1004
1005 fn finish(
1006 &mut self,
1007 _block: &RecoveredBlock<N::Block>,
1008 _output: Arc<BlockExecutionOutput<N::Receipt>>,
1009 hashed_state: &LazyHashedPostState,
1010 ) -> ProviderResult<StateRootJobOutcome> {
1011 let provider = self.provider_builder.clone().build()?;
1012 let (state_root, trie_updates) =
1013 provider.state_root_with_updates(hashed_state.get().as_ref().clone())?;
1014 Ok(StateRootJobOutcome::new(state_root, Arc::new(trie_updates)))
1015 }
1016}
1017
1018#[derive(Debug)]
1019struct SparseTrieStateRootJob<N: NodePrimitives, P> {
1020 handle: StateRootHandle,
1021 provider_builder: StateProviderBuilder<N, P>,
1022 overlay_factory: OverlayStateProviderFactory<P, N>,
1023 executor: reth_tasks::Runtime,
1024 timeout: Option<Duration>,
1025 compare_trie_updates: bool,
1026 metrics: BlockValidationMetrics,
1027}
1028
1029impl<N, P> SparseTrieStateRootJob<N, P>
1030where
1031 N: NodePrimitives,
1032 P: DatabaseProviderFactory + Clone + 'static,
1033 P::Provider: BlockNumReader
1034 + PruneCheckpointReader
1035 + StageCheckpointReader
1036 + StorageSettingsCache
1037 + TryIntoHistoricalStateProvider
1038 + 'static,
1039 OverlayStateProviderFactory<P, N>: DatabaseProviderROFactory<Provider: TrieCursorFactory + HashedCursorFactory>
1040 + Clone
1041 + 'static,
1042{
1043 fn serial_fallback(
1044 executor: &reth_tasks::Runtime,
1045 provider_builder: StateProviderBuilder<N, P>,
1046 output: Arc<BlockExecutionOutput<N::Receipt>>,
1047 ) -> ProviderResult<SerialFallbackRx> {
1048 let provider = provider_builder.build()?;
1049 let (fallback_tx, fallback_rx) = mpsc::channel();
1050 executor.spawn_blocking_named("serial-root", move || {
1051 let result = (|| {
1052 let hashed_state = Arc::new(provider.hashed_post_state(&output.state)?);
1053 let (root, updates) =
1054 provider.state_root_with_updates(hashed_state.as_ref().clone())?;
1055 Ok((root, updates, hashed_state))
1056 })();
1057 let _ = fallback_tx.send(result);
1058 });
1059
1060 Ok(fallback_rx)
1061 }
1062
1063 fn compute_serial(
1068 &self,
1069 output: &BlockExecutionOutput<N::Receipt>,
1070 ) -> ProviderResult<StateRootJobOutcome> {
1071 let provider = self.provider_builder.clone().build()?;
1072 let hashed_state = Arc::new(provider.hashed_post_state(&output.state)?);
1073 let (state_root, trie_updates) =
1074 provider.state_root_with_updates(hashed_state.as_ref().clone())?;
1075 self.metrics.state_root_task_fallback_success_total.increment(1);
1076 Ok(StateRootJobOutcome::new(state_root, Arc::new(trie_updates))
1077 .with_hashed_state(Some(hashed_state)))
1078 }
1079
1080 fn verified_sparse_outcome(
1085 &self,
1086 block: &RecoveredBlock<N::Block>,
1087 output: &BlockExecutionOutput<N::Receipt>,
1088 outcome: StateRootComputeOutcome,
1089 ) -> ProviderResult<StateRootJobOutcome> {
1090 let outcome = self.sparse_outcome(block, output, outcome);
1091 if outcome.state_root == block.header().state_root() {
1092 return Ok(outcome)
1093 }
1094 warn!(
1095 target: "engine::tree::state_root_strategy",
1096 state_root = ?outcome.state_root,
1097 block_state_root = ?block.header().state_root(),
1098 "State root task returned incorrect state root, recomputing serially"
1099 );
1100 self.compute_serial(output)
1101 }
1102
1103 fn sparse_outcome(
1104 &self,
1105 _block: &RecoveredBlock<N::Block>,
1106 output: &BlockExecutionOutput<N::Receipt>,
1107 outcome: StateRootComputeOutcome,
1108 ) -> StateRootJobOutcome {
1109 let StateRootComputeOutcome {
1110 state_root,
1111 trie_updates,
1112 hashed_state: _hashed_state,
1113 #[cfg(feature = "trie-debug")]
1114 debug_recorders,
1115 } = outcome;
1116
1117 if self.compare_trie_updates {
1118 let _has_diff = compare_trie_updates_with_serial(
1119 self.provider_builder.clone(),
1120 self.overlay_factory.clone(),
1121 output,
1122 trie_updates.as_ref().clone(),
1123 );
1124 #[cfg(feature = "trie-debug")]
1125 if _has_diff {
1126 write_trie_debug_recorders(_block.header().number(), &debug_recorders);
1127 }
1128 }
1129
1130 #[cfg(feature = "trie-debug")]
1131 if state_root != _block.header().state_root() {
1132 write_trie_debug_recorders(_block.header().number(), &debug_recorders);
1133 }
1134
1135 StateRootJobOutcome::new(state_root, trie_updates)
1136 }
1137}
1138
1139impl<N, P> StateRootJob<N> for SparseTrieStateRootJob<N, P>
1140where
1141 N: NodePrimitives,
1142 P: DatabaseProviderFactory + Clone + 'static,
1143 P::Provider: BlockNumReader
1144 + PruneCheckpointReader
1145 + StageCheckpointReader
1146 + StorageSettingsCache
1147 + TryIntoHistoricalStateProvider
1148 + 'static,
1149 OverlayStateProviderFactory<P, N>: DatabaseProviderROFactory<Provider: TrieCursorFactory + HashedCursorFactory>
1150 + Clone
1151 + 'static,
1152{
1153 fn name(&self) -> &'static str {
1154 "sparse-trie"
1155 }
1156
1157 fn finish(
1158 &mut self,
1159 block: &RecoveredBlock<N::Block>,
1160 output: Arc<BlockExecutionOutput<N::Receipt>>,
1161 _hashed_state: &LazyHashedPostState,
1162 ) -> ProviderResult<StateRootJobOutcome> {
1163 if self.timeout.is_none() {
1164 return match self.handle.state_root() {
1165 Ok(outcome) => self.verified_sparse_outcome(block, &output, outcome),
1166 Err(err) => {
1167 debug!(target: "engine::tree::state_root_strategy", %err, "State root task failed, falling back to serial root");
1168 self.compute_serial(&output)
1169 }
1170 }
1171 }
1172
1173 let timeout = self.timeout.expect("checked above");
1174 let task_rx = self.handle.take_state_root_rx();
1175 let fallback_rx = match task_rx.recv_timeout(timeout) {
1176 Ok(Ok(outcome)) => return self.verified_sparse_outcome(block, &output, outcome),
1177 Ok(Err(err)) => {
1178 debug!(target: "engine::tree::state_root_strategy", %err, "State root task failed, falling back to serial root");
1179 Self::serial_fallback(
1180 &self.executor,
1181 self.provider_builder.clone(),
1182 output.clone(),
1183 )?
1184 }
1185 Err(RecvTimeoutError::Timeout) => {
1186 warn!(target: "engine::tree::state_root_strategy", ?timeout, "State root task timed out, racing serial fallback");
1187 self.metrics.state_root_task_timeout_total.increment(1);
1188 Self::serial_fallback(
1189 &self.executor,
1190 self.provider_builder.clone(),
1191 output.clone(),
1192 )?
1193 }
1194 Err(RecvTimeoutError::Disconnected) => {
1195 debug!(target: "engine::tree::state_root_strategy", "State root task dropped, falling back to serial root");
1196 Self::serial_fallback(
1197 &self.executor,
1198 self.provider_builder.clone(),
1199 output.clone(),
1200 )?
1201 }
1202 };
1203
1204 loop {
1205 if let Ok(Ok(outcome)) = task_rx.try_recv() {
1206 let outcome = self.sparse_outcome(block, &output, outcome);
1207 if outcome.state_root == block.header().state_root() {
1208 return Ok(outcome)
1209 }
1210 warn!(
1212 target: "engine::tree::state_root_strategy",
1213 state_root = ?outcome.state_root,
1214 block_state_root = ?block.header().state_root(),
1215 "State root task returned incorrect state root, using serial fallback"
1216 );
1217 }
1218
1219 match fallback_rx.try_recv() {
1220 Ok(Ok((state_root, trie_updates, hashed_state))) => {
1221 self.metrics.state_root_task_fallback_success_total.increment(1);
1222 return Ok(StateRootJobOutcome::new(state_root, Arc::new(trie_updates))
1223 .with_hashed_state(Some(hashed_state)))
1224 }
1225 Ok(Err(err)) => return Err(err),
1226 Err(mpsc::TryRecvError::Empty) => {}
1227 Err(mpsc::TryRecvError::Disconnected) => {
1228 return Err(ProviderError::other(std::io::Error::other(
1229 "serial state root fallback task dropped",
1230 )))
1231 }
1232 }
1233
1234 std::thread::sleep(Duration::from_millis(1));
1235 }
1236 }
1237}
1238
1239fn compare_trie_updates_with_serial<N, P>(
1240 state_provider_builder: StateProviderBuilder<N, P>,
1241 overlay_factory: OverlayStateProviderFactory<P, N>,
1242 output: &BlockExecutionOutput<N::Receipt>,
1243 task_trie_updates: TrieUpdates,
1244) -> bool
1245where
1246 N: NodePrimitives,
1247 P: DatabaseProviderFactory,
1248 P::Provider: BlockNumReader
1249 + PruneCheckpointReader
1250 + StageCheckpointReader
1251 + StorageSettingsCache
1252 + TryIntoHistoricalStateProvider
1253 + 'static,
1254 OverlayStateProviderFactory<P, N>:
1255 DatabaseProviderROFactory<Provider: TrieCursorFactory + HashedCursorFactory>,
1256{
1257 debug!(target: "engine::tree::state_root_strategy", "Comparing trie updates with serial computation");
1258
1259 match state_provider_builder.build().and_then(|provider| {
1260 let hashed_state = provider.hashed_post_state(&output.state)?;
1261 provider.state_root_with_updates(hashed_state)
1262 }) {
1263 Ok((serial_root, serial_trie_updates)) => {
1264 debug!(
1265 target: "engine::tree::state_root_strategy",
1266 ?serial_root,
1267 "Serial state root computation finished for comparison"
1268 );
1269
1270 match overlay_factory.database_provider_ro() {
1271 Ok(provider) => match super::trie_updates::compare_trie_updates(
1272 &provider,
1273 task_trie_updates,
1274 serial_trie_updates,
1275 ) {
1276 Ok(has_diff) => return has_diff,
1277 Err(err) => {
1278 warn!(
1279 target: "engine::tree::state_root_strategy",
1280 %err,
1281 "Error comparing trie updates"
1282 );
1283 return true;
1284 }
1285 },
1286 Err(err) => {
1287 warn!(
1288 target: "engine::tree::state_root_strategy",
1289 %err,
1290 "Failed to get database provider for trie update comparison"
1291 );
1292 }
1293 }
1294 }
1295 Err(err) => {
1296 warn!(
1297 target: "engine::tree::state_root_strategy",
1298 %err,
1299 "Failed to compute serial state root for comparison"
1300 );
1301 }
1302 }
1303 false
1304}
1305
1306#[cfg(feature = "trie-debug")]
1310fn write_trie_debug_recorders(block_number: u64, recorders: &[(Option<B256>, TrieDebugRecorder)]) {
1311 let path = format!("trie_debug_block_{block_number}.json");
1312 match serde_json::to_string_pretty(recorders) {
1313 Ok(json) => match std::fs::write(&path, json) {
1314 Ok(()) => {
1315 warn!(
1316 target: "engine::tree::state_root_strategy",
1317 %path,
1318 "Wrote trie debug recorders to file"
1319 );
1320 }
1321 Err(err) => {
1322 warn!(
1323 target: "engine::tree::state_root_strategy",
1324 %err,
1325 %path,
1326 "Failed to write trie debug recorders"
1327 );
1328 }
1329 },
1330 Err(err) => {
1331 warn!(
1332 target: "engine::tree::state_root_strategy",
1333 %err,
1334 "Failed to serialize trie debug recorders"
1335 );
1336 }
1337 }
1338}
1339
1340#[cfg(test)]
1341mod tests {
1342 use super::*;
1343 use alloy_consensus::constants::KECCAK_EMPTY;
1344 use alloy_primitives::{map::HashMap, Address, U256};
1345 use rand::Rng;
1346 use reth_chain_state::test_utils::TestBlockBuilder;
1347 use reth_chainspec::ChainSpec;
1348 use reth_db_common::init::init_genesis;
1349 use reth_ethereum_primitives::EthPrimitives;
1350 use reth_evm::OnStateHook;
1351 use reth_evm_ethereum::EthEvmConfig;
1352 use reth_primitives_traits::{Account, StorageEntry};
1353 use reth_provider::{
1354 providers::BlockchainProvider, test_utils::create_test_provider_factory_with_chain_spec,
1355 HashingWriter,
1356 };
1357 use reth_storage_overlay::{OverlayManager, OverlayStateProviderFactory};
1358 use reth_testing_utils::generators;
1359 use reth_trie::test_utils::state_root;
1360 use revm::state::{AccountInfo, AccountStatus, EvmState, EvmStorageSlot, TransactionId};
1361
1362 #[test]
1363 fn sparse_trie_prune_before_uses_requested_range() {
1364 let new_epoch = TrieNodeEpoch::new(10);
1365 assert_eq!(sparse_trie_prune_before::<EthPrimitives>(None, new_epoch), None);
1366 assert_eq!(
1367 sparse_trie_prune_before::<EthPrimitives>(Some(&[]), new_epoch),
1368 Some(new_epoch)
1369 );
1370
1371 let mut blocks: Vec<_> = TestBlockBuilder::eth().get_executed_blocks(7..10).collect();
1372 blocks.reverse();
1373
1374 assert_eq!(sparse_trie_prune_before(Some(&blocks), new_epoch), Some(TrieNodeEpoch::new(7)));
1375 }
1376
1377 #[test]
1378 fn published_sparse_trie_anchor_advances_to_prune_anchor() {
1379 let blocks: Vec<_> = TestBlockBuilder::eth().get_executed_blocks(1..5).collect();
1380 let reused_anchor_hash = blocks[0].recovered_block().hash();
1381 let expected_prune_anchor = blocks[1].recovered_block().hash();
1382 let prune_blocks: Vec<_> = blocks.into_iter().skip(2).rev().collect();
1383
1384 assert_eq!(
1385 published_sparse_trie_anchor_hash(reused_anchor_hash, true, Some(&prune_blocks)),
1386 expected_prune_anchor
1387 );
1388 }
1389
1390 #[test]
1391 fn published_sparse_trie_anchor_does_not_move_backwards_when_anchor_is_in_prune_range() {
1392 let blocks: Vec<_> = TestBlockBuilder::eth().get_executed_blocks(1..5).collect();
1393 let reused_anchor_hash = blocks[2].recovered_block().hash();
1394 let mut prune_blocks = blocks;
1395 prune_blocks.reverse();
1396 let prune_anchor = prune_blocks.last().unwrap().recovered_block().parent_hash();
1397
1398 assert_ne!(reused_anchor_hash, prune_anchor);
1399 assert_eq!(
1400 published_sparse_trie_anchor_hash(reused_anchor_hash, true, Some(&prune_blocks)),
1401 reused_anchor_hash
1402 );
1403 }
1404
1405 #[test]
1406 fn published_sparse_trie_anchor_keeps_parent_for_fresh_trie() {
1407 let mut blocks: Vec<_> = TestBlockBuilder::eth().get_executed_blocks(1..3).collect();
1408 blocks.reverse();
1409 let parent_hash = B256::with_last_byte(0xaa);
1410
1411 assert_eq!(
1412 published_sparse_trie_anchor_hash(parent_hash, false, Some(&blocks)),
1413 parent_hash
1414 );
1415 }
1416
1417 fn create_mock_state_updates(num_accounts: usize, updates_per_account: usize) -> Vec<EvmState> {
1418 let mut rng = generators::rng();
1419 let all_addresses: Vec<Address> = (0..num_accounts).map(|_| rng.random()).collect();
1420 let mut updates = Vec::with_capacity(updates_per_account);
1421
1422 for _ in 0..updates_per_account {
1423 let num_accounts_in_update = rng.random_range(1..=num_accounts);
1424 let mut state_update = EvmState::default();
1425
1426 for &address in &all_addresses[0..num_accounts_in_update] {
1427 let mut storage = HashMap::default();
1428 if rng.random_bool(0.7) {
1429 for _ in 0..rng.random_range(1..10) {
1430 let slot = U256::from(rng.random::<u64>());
1431 storage.insert(
1432 slot,
1433 EvmStorageSlot::new_changed(
1434 U256::ZERO,
1435 U256::from(rng.random::<u64>()),
1436 TransactionId::ZERO,
1437 ),
1438 );
1439 }
1440 }
1441
1442 let mut account = revm::state::Account::default();
1443 account.info = AccountInfo {
1444 balance: U256::from(rng.random::<u64>()),
1445 nonce: rng.random::<u64>(),
1446 code_hash: KECCAK_EMPTY,
1447 code: Some(Default::default()),
1448 account_id: None,
1449 };
1450 account.storage = storage;
1451 account.status = AccountStatus::Touched;
1452 account.transaction_id = TransactionId::ZERO;
1453 state_update.insert(address, account);
1454 }
1455
1456 updates.push(state_update);
1457 }
1458
1459 updates
1460 }
1461
1462 #[test]
1463 fn state_root_task_matches_serial_root() {
1464 reth_tracing::init_test_tracing();
1465
1466 let factory = create_test_provider_factory_with_chain_spec(Arc::new(ChainSpec::default()));
1467 let genesis_hash = init_genesis(&factory).unwrap();
1468 let state_updates = create_mock_state_updates(10, 10);
1469 let mut accumulated_state: HashMap<Address, (Account, HashMap<B256, U256>)> =
1470 HashMap::default();
1471
1472 {
1473 let provider_rw = factory.provider_rw().expect("failed to get provider");
1474 for update in &state_updates {
1475 let account_updates = update.iter().map(|(address, account)| {
1476 (*address, Some(Account::from_revm_account(account)))
1477 });
1478 provider_rw
1479 .insert_account_for_hashing(account_updates)
1480 .expect("failed to insert accounts");
1481
1482 let storage_updates = update.iter().map(|(address, account)| {
1483 let storage_entries = account.storage.iter().map(|(slot, value)| {
1484 StorageEntry { key: B256::from(*slot), value: value.present_value }
1485 });
1486 (*address, storage_entries)
1487 });
1488 provider_rw
1489 .insert_storage_for_hashing(storage_updates)
1490 .expect("failed to insert storage");
1491 }
1492 provider_rw.commit().expect("failed to commit changes");
1493 }
1494
1495 for update in &state_updates {
1496 for (address, account) in update {
1497 let storage: HashMap<B256, U256> = account
1498 .storage
1499 .iter()
1500 .map(|(key, value)| (B256::from(*key), value.present_value))
1501 .collect();
1502 let entry = accumulated_state.entry(*address).or_default();
1503 entry.0 = Account::from_revm_account(account);
1504 entry.1.extend(storage);
1505 }
1506 }
1507
1508 let provider_factory = BlockchainProvider::new(factory).unwrap();
1509 let env: ExecutionEnv<EthEvmConfig> = ExecutionEnv::test_default();
1510 let runtime = reth_tasks::Runtime::test();
1511 let overlay_manager = OverlayManager::<EthPrimitives>::default();
1512 let mut state_root_handle = DefaultStateRootStrategy::default().spawn_state_root(
1513 &runtime,
1514 &overlay_manager,
1515 OverlayStateProviderFactory::new(
1516 provider_factory,
1517 overlay_manager.overlay_builder(genesis_hash),
1518 ),
1519 StateRootTaskOptions {
1520 parent_header: SealedHeader::new(Default::default(), genesis_hash),
1521 preserved_sparse_trie: None,
1522 transaction_count: Some(env.transaction_count),
1523 config: &TreeConfig::default(),
1524 pending_sparse_trie_prune_blocks: None,
1525 },
1526 );
1527
1528 let mut state_hook = state_root_handle.take_execution_hook();
1529 for update in state_updates {
1530 state_hook.on_state(update);
1531 }
1532 drop(state_hook);
1533
1534 let root_from_task = state_root_handle.state_root().expect("task failed").state_root;
1535 let root_from_regular = state_root(accumulated_state);
1536 assert_eq!(root_from_task, root_from_regular);
1537 }
1538}