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 providers::OverlayStateProviderFactory, BlockExecutionOutput, BlockReader,
73 DatabaseProviderFactory, DatabaseProviderROFactory, HashedPostStateProvider, ProviderError,
74 StateProviderFactory, StateReader, StateRootProvider,
75};
76use reth_storage_overlay::OverlayManager;
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
823 + BlockReader<Header = N::BlockHeader>
824 + StateProviderFactory
825 + StateReader
826 + Clone
827 + 'static,
828 OverlayStateProviderFactory<P, N>: DatabaseProviderROFactory<Provider: TrieCursorFactory + HashedCursorFactory>
829 + Clone
830 + Send
831 + Sync
832 + 'static,
833 Evm: ConfigureEvm<Primitives = N> + 'static,
834{
835 fn prepare(
836 &self,
837 mut ctx: StateRootJobContext<'_, N, P, Evm>,
838 ) -> ProviderResult<PreparedStateRootJob<N>> {
839 if ctx.config.skip_state_root() {
840 return Ok(PreparedStateRootJob::new(Box::new(SkippedStateRootJob {}), None))
841 }
842
843 if !ctx.config.use_state_root_task() {
844 return Ok(PreparedStateRootJob::new(
845 Box::new(SynchronousStateRootJob { provider_builder: ctx.provider_builder }),
846 None,
847 ))
848 }
849
850 let pending_sparse_trie_prune_blocks = ctx.take_sparse_trie_prune_blocks();
851 let StateRootJobContext {
852 executor,
853 overlay_manager,
854 env,
855 parent_header,
856 provider_builder,
857 overlay_factory,
858 config,
859 parallel_bal_execution,
860 state: _,
861 } = ctx;
862
863 let preserved_sparse_trie = overlay_manager.take_sparse_trie();
864 let overlay_factory = if let Some(anchor_hash) = preserved_sparse_trie
865 .as_ref()
866 .filter(|trie| trie.state_root() == env.parent_state_root)
867 .map(|trie| trie.anchor_hash())
868 {
869 overlay_factory.with_skip_overlay_for_reused_sparse_trie(anchor_hash)
870 } else {
871 overlay_factory
872 };
873
874 let mut handle = self.spawn_state_root(
875 executor,
876 overlay_manager,
877 overlay_factory.clone(),
878 StateRootTaskOptions {
879 parent_header: parent_header.clone(),
880 preserved_sparse_trie,
881 transaction_count: Some(env.transaction_count),
882 config,
883 pending_sparse_trie_prune_blocks,
884 },
885 );
886
887 let (hashed_update_stream, execution_hook): (
891 Option<StateRootUpdateStream>,
892 Option<StateRootUpdateHook>,
893 ) = match parallel_bal_execution {
894 true => (Some(handle.take_hashed_update_stream()), None),
895 false => (None, Some(handle.take_execution_hook())),
896 };
897 let hint_stream = handle.take_hint_stream();
898
899 let hashed_state_rx = Some(handle.take_hashed_state_rx());
900
901 let mut prepared = PreparedStateRootJob::new(
902 Box::new(SparseTrieStateRootJob {
903 handle,
904 provider_builder,
905 overlay_factory,
906 executor: executor.clone(),
907 timeout: config.state_root_task_timeout(),
908 compare_trie_updates: config.always_compare_trie_updates(),
909 metrics: BlockValidationMetrics::default(),
910 }),
911 hashed_state_rx,
912 )
913 .with_hint_stream(hint_stream);
914 if let Some(hook) = execution_hook {
915 prepared = prepared.with_execution_hook(hook);
916 }
917 if let Some(stream) = hashed_update_stream {
918 prepared = prepared.with_hashed_update_stream(stream);
919 }
920 Ok(prepared)
921 }
922
923 fn prepare_payload_builder(
924 &self,
925 mut ctx: PayloadStateRootJobContext<'_, N, P>,
926 ) -> ProviderResult<Option<PayloadStateRootHandle>> {
927 if !ctx.config.share_sparse_trie_with_payload_builder() ||
930 ctx.config.skip_state_root() ||
931 !ctx.config.has_enough_parallelism()
932 {
933 return Ok(None)
934 }
935
936 let pending_sparse_trie_prune_blocks = ctx.take_sparse_trie_prune_blocks();
937 let parent_state_root = ctx.parent_state_root();
938 let parent_header = SealedHeader::new(ctx.parent_header().clone(), ctx.parent_hash());
939 let preserved_sparse_trie = ctx.overlay_manager.take_sparse_trie();
940 let overlay_factory = if let Some(anchor_hash) = preserved_sparse_trie
941 .as_ref()
942 .filter(|trie| trie.state_root() == parent_state_root)
943 .map(|trie| trie.anchor_hash())
944 {
945 ctx.overlay_factory.clone().with_skip_overlay_for_reused_sparse_trie(anchor_hash)
946 } else {
947 ctx.overlay_factory.clone()
948 };
949 Ok(Some(
950 self.spawn_state_root(
951 ctx.executor,
952 ctx.overlay_manager,
953 overlay_factory,
954 StateRootTaskOptions {
955 parent_header,
956 preserved_sparse_trie,
957 transaction_count: None,
959 config: ctx.config,
960 pending_sparse_trie_prune_blocks,
961 },
962 )
963 .into_payload_state_root_handle(),
964 ))
965 }
966}
967
968#[derive(Debug)]
969struct SkippedStateRootJob {}
970
971impl<N: NodePrimitives> StateRootJob<N> for SkippedStateRootJob {
972 fn name(&self) -> &'static str {
973 "skipped"
974 }
975
976 fn finish(
977 &mut self,
978 block: &RecoveredBlock<N::Block>,
979 _output: Arc<BlockExecutionOutput<N::Receipt>>,
980 _hashed_state: &LazyHashedPostState,
981 ) -> ProviderResult<StateRootJobOutcome> {
982 Ok(StateRootJobOutcome::new(block.header().state_root(), Arc::new(TrieUpdates::default())))
983 }
984}
985
986#[derive(Debug)]
987struct SynchronousStateRootJob<N: NodePrimitives, P> {
988 provider_builder: StateProviderBuilder<N, P>,
989}
990
991impl<N, P> StateRootJob<N> for SynchronousStateRootJob<N, P>
992where
993 N: NodePrimitives,
994 P: BlockReader + StateProviderFactory + StateReader + Clone + Send + Sync + 'static,
995{
996 fn name(&self) -> &'static str {
997 "synchronous"
998 }
999
1000 fn finish(
1001 &mut self,
1002 _block: &RecoveredBlock<N::Block>,
1003 _output: Arc<BlockExecutionOutput<N::Receipt>>,
1004 hashed_state: &LazyHashedPostState,
1005 ) -> ProviderResult<StateRootJobOutcome> {
1006 let provider = self.provider_builder.clone().build()?;
1007 let (state_root, trie_updates) =
1008 provider.state_root_with_updates(hashed_state.get().as_ref().clone())?;
1009 Ok(StateRootJobOutcome::new(state_root, Arc::new(trie_updates)))
1010 }
1011}
1012
1013#[derive(Debug)]
1014struct SparseTrieStateRootJob<N: NodePrimitives, P> {
1015 handle: StateRootHandle,
1016 provider_builder: StateProviderBuilder<N, P>,
1017 overlay_factory: OverlayStateProviderFactory<P, N>,
1018 executor: reth_tasks::Runtime,
1019 timeout: Option<Duration>,
1020 compare_trie_updates: bool,
1021 metrics: BlockValidationMetrics,
1022}
1023
1024impl<N, P> SparseTrieStateRootJob<N, P>
1025where
1026 N: NodePrimitives,
1027 P: StateProviderFactory + Clone + Send + Sync + 'static,
1028 P: BlockReader + StateReader,
1029 OverlayStateProviderFactory<P, N>: DatabaseProviderROFactory<Provider: TrieCursorFactory + HashedCursorFactory>
1030 + Clone
1031 + Send
1032 + Sync
1033 + 'static,
1034{
1035 fn serial_fallback(
1036 executor: &reth_tasks::Runtime,
1037 provider_builder: StateProviderBuilder<N, P>,
1038 output: Arc<BlockExecutionOutput<N::Receipt>>,
1039 ) -> ProviderResult<SerialFallbackRx> {
1040 let provider = provider_builder.build()?;
1041 let (fallback_tx, fallback_rx) = mpsc::channel();
1042 executor.spawn_blocking_named("serial-root", move || {
1043 let result = (|| {
1044 let hashed_state = Arc::new(provider.hashed_post_state(&output.state));
1045 let (root, updates) =
1046 provider.state_root_with_updates(hashed_state.as_ref().clone())?;
1047 Ok((root, updates, hashed_state))
1048 })();
1049 let _ = fallback_tx.send(result);
1050 });
1051
1052 Ok(fallback_rx)
1053 }
1054
1055 fn compute_serial(
1060 &self,
1061 output: &BlockExecutionOutput<N::Receipt>,
1062 ) -> ProviderResult<StateRootJobOutcome> {
1063 let provider = self.provider_builder.clone().build()?;
1064 let hashed_state = Arc::new(provider.hashed_post_state(&output.state));
1065 let (state_root, trie_updates) =
1066 provider.state_root_with_updates(hashed_state.as_ref().clone())?;
1067 self.metrics.state_root_task_fallback_success_total.increment(1);
1068 Ok(StateRootJobOutcome::new(state_root, Arc::new(trie_updates))
1069 .with_hashed_state(Some(hashed_state)))
1070 }
1071
1072 fn verified_sparse_outcome(
1077 &self,
1078 block: &RecoveredBlock<N::Block>,
1079 output: &BlockExecutionOutput<N::Receipt>,
1080 outcome: StateRootComputeOutcome,
1081 ) -> ProviderResult<StateRootJobOutcome> {
1082 let outcome = self.sparse_outcome(block, output, outcome);
1083 if outcome.state_root == block.header().state_root() {
1084 return Ok(outcome)
1085 }
1086 warn!(
1087 target: "engine::tree::state_root_strategy",
1088 state_root = ?outcome.state_root,
1089 block_state_root = ?block.header().state_root(),
1090 "State root task returned incorrect state root, recomputing serially"
1091 );
1092 self.compute_serial(output)
1093 }
1094
1095 fn sparse_outcome(
1096 &self,
1097 _block: &RecoveredBlock<N::Block>,
1098 output: &BlockExecutionOutput<N::Receipt>,
1099 outcome: StateRootComputeOutcome,
1100 ) -> StateRootJobOutcome {
1101 let StateRootComputeOutcome {
1102 state_root,
1103 trie_updates,
1104 hashed_state: _hashed_state,
1105 #[cfg(feature = "trie-debug")]
1106 debug_recorders,
1107 } = outcome;
1108
1109 if self.compare_trie_updates {
1110 let _has_diff = compare_trie_updates_with_serial(
1111 self.provider_builder.clone(),
1112 self.overlay_factory.clone(),
1113 output,
1114 trie_updates.as_ref().clone(),
1115 );
1116 #[cfg(feature = "trie-debug")]
1117 if _has_diff {
1118 write_trie_debug_recorders(_block.header().number(), &debug_recorders);
1119 }
1120 }
1121
1122 #[cfg(feature = "trie-debug")]
1123 if state_root != _block.header().state_root() {
1124 write_trie_debug_recorders(_block.header().number(), &debug_recorders);
1125 }
1126
1127 StateRootJobOutcome::new(state_root, trie_updates)
1128 }
1129}
1130
1131impl<N, P> StateRootJob<N> for SparseTrieStateRootJob<N, P>
1132where
1133 N: NodePrimitives,
1134 P: BlockReader + StateProviderFactory + StateReader + Clone + Send + Sync + 'static,
1135 OverlayStateProviderFactory<P, N>: DatabaseProviderROFactory<Provider: TrieCursorFactory + HashedCursorFactory>
1136 + Clone
1137 + Send
1138 + Sync
1139 + 'static,
1140{
1141 fn name(&self) -> &'static str {
1142 "sparse-trie"
1143 }
1144
1145 fn finish(
1146 &mut self,
1147 block: &RecoveredBlock<N::Block>,
1148 output: Arc<BlockExecutionOutput<N::Receipt>>,
1149 _hashed_state: &LazyHashedPostState,
1150 ) -> ProviderResult<StateRootJobOutcome> {
1151 if self.timeout.is_none() {
1152 return match self.handle.state_root() {
1153 Ok(outcome) => self.verified_sparse_outcome(block, &output, outcome),
1154 Err(err) => {
1155 debug!(target: "engine::tree::state_root_strategy", %err, "State root task failed, falling back to serial root");
1156 self.compute_serial(&output)
1157 }
1158 }
1159 }
1160
1161 let timeout = self.timeout.expect("checked above");
1162 let task_rx = self.handle.take_state_root_rx();
1163 let fallback_rx = match task_rx.recv_timeout(timeout) {
1164 Ok(Ok(outcome)) => return self.verified_sparse_outcome(block, &output, outcome),
1165 Ok(Err(err)) => {
1166 debug!(target: "engine::tree::state_root_strategy", %err, "State root task failed, falling back to serial root");
1167 Self::serial_fallback(
1168 &self.executor,
1169 self.provider_builder.clone(),
1170 output.clone(),
1171 )?
1172 }
1173 Err(RecvTimeoutError::Timeout) => {
1174 warn!(target: "engine::tree::state_root_strategy", ?timeout, "State root task timed out, racing serial fallback");
1175 self.metrics.state_root_task_timeout_total.increment(1);
1176 Self::serial_fallback(
1177 &self.executor,
1178 self.provider_builder.clone(),
1179 output.clone(),
1180 )?
1181 }
1182 Err(RecvTimeoutError::Disconnected) => {
1183 debug!(target: "engine::tree::state_root_strategy", "State root task dropped, falling back to serial root");
1184 Self::serial_fallback(
1185 &self.executor,
1186 self.provider_builder.clone(),
1187 output.clone(),
1188 )?
1189 }
1190 };
1191
1192 loop {
1193 if let Ok(Ok(outcome)) = task_rx.try_recv() {
1194 let outcome = self.sparse_outcome(block, &output, outcome);
1195 if outcome.state_root == block.header().state_root() {
1196 return Ok(outcome)
1197 }
1198 warn!(
1200 target: "engine::tree::state_root_strategy",
1201 state_root = ?outcome.state_root,
1202 block_state_root = ?block.header().state_root(),
1203 "State root task returned incorrect state root, using serial fallback"
1204 );
1205 }
1206
1207 match fallback_rx.try_recv() {
1208 Ok(Ok((state_root, trie_updates, hashed_state))) => {
1209 self.metrics.state_root_task_fallback_success_total.increment(1);
1210 return Ok(StateRootJobOutcome::new(state_root, Arc::new(trie_updates))
1211 .with_hashed_state(Some(hashed_state)))
1212 }
1213 Ok(Err(err)) => return Err(err),
1214 Err(mpsc::TryRecvError::Empty) => {}
1215 Err(mpsc::TryRecvError::Disconnected) => {
1216 return Err(ProviderError::other(std::io::Error::other(
1217 "serial state root fallback task dropped",
1218 )))
1219 }
1220 }
1221
1222 std::thread::sleep(Duration::from_millis(1));
1223 }
1224 }
1225}
1226
1227fn compare_trie_updates_with_serial<N, P>(
1228 state_provider_builder: StateProviderBuilder<N, P>,
1229 overlay_factory: OverlayStateProviderFactory<P, N>,
1230 output: &BlockExecutionOutput<N::Receipt>,
1231 task_trie_updates: TrieUpdates,
1232) -> bool
1233where
1234 N: NodePrimitives,
1235 P: BlockReader + StateProviderFactory + StateReader + Clone,
1236 OverlayStateProviderFactory<P, N>:
1237 DatabaseProviderROFactory<Provider: TrieCursorFactory + HashedCursorFactory>,
1238{
1239 debug!(target: "engine::tree::state_root_strategy", "Comparing trie updates with serial computation");
1240
1241 match state_provider_builder.build().and_then(|provider| {
1242 let hashed_state = provider.hashed_post_state(&output.state);
1243 provider.state_root_with_updates(hashed_state)
1244 }) {
1245 Ok((serial_root, serial_trie_updates)) => {
1246 debug!(
1247 target: "engine::tree::state_root_strategy",
1248 ?serial_root,
1249 "Serial state root computation finished for comparison"
1250 );
1251
1252 match overlay_factory.database_provider_ro() {
1253 Ok(provider) => match super::trie_updates::compare_trie_updates(
1254 &provider,
1255 task_trie_updates,
1256 serial_trie_updates,
1257 ) {
1258 Ok(has_diff) => return has_diff,
1259 Err(err) => {
1260 warn!(
1261 target: "engine::tree::state_root_strategy",
1262 %err,
1263 "Error comparing trie updates"
1264 );
1265 return true;
1266 }
1267 },
1268 Err(err) => {
1269 warn!(
1270 target: "engine::tree::state_root_strategy",
1271 %err,
1272 "Failed to get database provider for trie update comparison"
1273 );
1274 }
1275 }
1276 }
1277 Err(err) => {
1278 warn!(
1279 target: "engine::tree::state_root_strategy",
1280 %err,
1281 "Failed to compute serial state root for comparison"
1282 );
1283 }
1284 }
1285 false
1286}
1287
1288#[cfg(feature = "trie-debug")]
1292fn write_trie_debug_recorders(block_number: u64, recorders: &[(Option<B256>, TrieDebugRecorder)]) {
1293 let path = format!("trie_debug_block_{block_number}.json");
1294 match serde_json::to_string_pretty(recorders) {
1295 Ok(json) => match std::fs::write(&path, json) {
1296 Ok(()) => {
1297 warn!(
1298 target: "engine::tree::state_root_strategy",
1299 %path,
1300 "Wrote trie debug recorders to file"
1301 );
1302 }
1303 Err(err) => {
1304 warn!(
1305 target: "engine::tree::state_root_strategy",
1306 %err,
1307 %path,
1308 "Failed to write trie debug recorders"
1309 );
1310 }
1311 },
1312 Err(err) => {
1313 warn!(
1314 target: "engine::tree::state_root_strategy",
1315 %err,
1316 "Failed to serialize trie debug recorders"
1317 );
1318 }
1319 }
1320}
1321
1322#[cfg(test)]
1323mod tests {
1324 use super::*;
1325 use alloy_consensus::constants::KECCAK_EMPTY;
1326 use alloy_primitives::{map::HashMap, Address, U256};
1327 use rand::Rng;
1328 use reth_chain_state::test_utils::TestBlockBuilder;
1329 use reth_chainspec::ChainSpec;
1330 use reth_db_common::init::init_genesis;
1331 use reth_ethereum_primitives::EthPrimitives;
1332 use reth_evm::OnStateHook;
1333 use reth_evm_ethereum::EthEvmConfig;
1334 use reth_primitives_traits::{Account, StorageEntry};
1335 use reth_provider::{
1336 providers::{BlockchainProvider, OverlayStateProviderFactory},
1337 test_utils::create_test_provider_factory_with_chain_spec,
1338 HashingWriter,
1339 };
1340 use reth_storage_overlay::OverlayManager;
1341 use reth_testing_utils::generators;
1342 use reth_trie::test_utils::state_root;
1343 use revm::state::{AccountInfo, AccountStatus, EvmState, EvmStorageSlot, TransactionId};
1344
1345 #[test]
1346 fn sparse_trie_prune_before_uses_requested_range() {
1347 let new_epoch = TrieNodeEpoch::new(10);
1348 assert_eq!(sparse_trie_prune_before::<EthPrimitives>(None, new_epoch), None);
1349 assert_eq!(
1350 sparse_trie_prune_before::<EthPrimitives>(Some(&[]), new_epoch),
1351 Some(new_epoch)
1352 );
1353
1354 let mut blocks: Vec<_> = TestBlockBuilder::eth().get_executed_blocks(7..10).collect();
1355 blocks.reverse();
1356
1357 assert_eq!(sparse_trie_prune_before(Some(&blocks), new_epoch), Some(TrieNodeEpoch::new(7)));
1358 }
1359
1360 #[test]
1361 fn published_sparse_trie_anchor_advances_to_prune_anchor() {
1362 let blocks: Vec<_> = TestBlockBuilder::eth().get_executed_blocks(1..5).collect();
1363 let reused_anchor_hash = blocks[0].recovered_block().hash();
1364 let expected_prune_anchor = blocks[1].recovered_block().hash();
1365 let prune_blocks: Vec<_> = blocks.into_iter().skip(2).rev().collect();
1366
1367 assert_eq!(
1368 published_sparse_trie_anchor_hash(reused_anchor_hash, true, Some(&prune_blocks)),
1369 expected_prune_anchor
1370 );
1371 }
1372
1373 #[test]
1374 fn published_sparse_trie_anchor_does_not_move_backwards_when_anchor_is_in_prune_range() {
1375 let blocks: Vec<_> = TestBlockBuilder::eth().get_executed_blocks(1..5).collect();
1376 let reused_anchor_hash = blocks[2].recovered_block().hash();
1377 let mut prune_blocks = blocks;
1378 prune_blocks.reverse();
1379 let prune_anchor = prune_blocks.last().unwrap().recovered_block().parent_hash();
1380
1381 assert_ne!(reused_anchor_hash, prune_anchor);
1382 assert_eq!(
1383 published_sparse_trie_anchor_hash(reused_anchor_hash, true, Some(&prune_blocks)),
1384 reused_anchor_hash
1385 );
1386 }
1387
1388 #[test]
1389 fn published_sparse_trie_anchor_keeps_parent_for_fresh_trie() {
1390 let mut blocks: Vec<_> = TestBlockBuilder::eth().get_executed_blocks(1..3).collect();
1391 blocks.reverse();
1392 let parent_hash = B256::with_last_byte(0xaa);
1393
1394 assert_eq!(
1395 published_sparse_trie_anchor_hash(parent_hash, false, Some(&blocks)),
1396 parent_hash
1397 );
1398 }
1399
1400 fn create_mock_state_updates(num_accounts: usize, updates_per_account: usize) -> Vec<EvmState> {
1401 let mut rng = generators::rng();
1402 let all_addresses: Vec<Address> = (0..num_accounts).map(|_| rng.random()).collect();
1403 let mut updates = Vec::with_capacity(updates_per_account);
1404
1405 for _ in 0..updates_per_account {
1406 let num_accounts_in_update = rng.random_range(1..=num_accounts);
1407 let mut state_update = EvmState::default();
1408
1409 for &address in &all_addresses[0..num_accounts_in_update] {
1410 let mut storage = HashMap::default();
1411 if rng.random_bool(0.7) {
1412 for _ in 0..rng.random_range(1..10) {
1413 let slot = U256::from(rng.random::<u64>());
1414 storage.insert(
1415 slot,
1416 EvmStorageSlot::new_changed(
1417 U256::ZERO,
1418 U256::from(rng.random::<u64>()),
1419 TransactionId::ZERO,
1420 ),
1421 );
1422 }
1423 }
1424
1425 let mut account = revm::state::Account::default();
1426 account.info = AccountInfo {
1427 balance: U256::from(rng.random::<u64>()),
1428 nonce: rng.random::<u64>(),
1429 code_hash: KECCAK_EMPTY,
1430 code: Some(Default::default()),
1431 account_id: None,
1432 };
1433 account.storage = storage;
1434 account.status = AccountStatus::Touched;
1435 account.transaction_id = TransactionId::ZERO;
1436 state_update.insert(address, account);
1437 }
1438
1439 updates.push(state_update);
1440 }
1441
1442 updates
1443 }
1444
1445 #[test]
1446 fn state_root_task_matches_serial_root() {
1447 reth_tracing::init_test_tracing();
1448
1449 let factory = create_test_provider_factory_with_chain_spec(Arc::new(ChainSpec::default()));
1450 let genesis_hash = init_genesis(&factory).unwrap();
1451 let state_updates = create_mock_state_updates(10, 10);
1452 let mut accumulated_state: HashMap<Address, (Account, HashMap<B256, U256>)> =
1453 HashMap::default();
1454
1455 {
1456 let provider_rw = factory.provider_rw().expect("failed to get provider");
1457 for update in &state_updates {
1458 let account_updates = update.iter().map(|(address, account)| {
1459 (*address, Some(Account::from_revm_account(account)))
1460 });
1461 provider_rw
1462 .insert_account_for_hashing(account_updates)
1463 .expect("failed to insert accounts");
1464
1465 let storage_updates = update.iter().map(|(address, account)| {
1466 let storage_entries = account.storage.iter().map(|(slot, value)| {
1467 StorageEntry { key: B256::from(*slot), value: value.present_value }
1468 });
1469 (*address, storage_entries)
1470 });
1471 provider_rw
1472 .insert_storage_for_hashing(storage_updates)
1473 .expect("failed to insert storage");
1474 }
1475 provider_rw.commit().expect("failed to commit changes");
1476 }
1477
1478 for update in &state_updates {
1479 for (address, account) in update {
1480 let storage: HashMap<B256, U256> = account
1481 .storage
1482 .iter()
1483 .map(|(key, value)| (B256::from(*key), value.present_value))
1484 .collect();
1485 let entry = accumulated_state.entry(*address).or_default();
1486 entry.0 = Account::from_revm_account(account);
1487 entry.1.extend(storage);
1488 }
1489 }
1490
1491 let provider_factory = BlockchainProvider::new(factory).unwrap();
1492 let env: ExecutionEnv<EthEvmConfig> = ExecutionEnv::test_default();
1493 let runtime = reth_tasks::Runtime::test();
1494 let overlay_manager = OverlayManager::<EthPrimitives>::default();
1495 let mut state_root_handle = DefaultStateRootStrategy::default().spawn_state_root(
1496 &runtime,
1497 &overlay_manager,
1498 OverlayStateProviderFactory::new(
1499 provider_factory,
1500 overlay_manager.overlay_builder(genesis_hash),
1501 ),
1502 StateRootTaskOptions {
1503 parent_header: SealedHeader::new(Default::default(), genesis_hash),
1504 preserved_sparse_trie: None,
1505 transaction_count: Some(env.transaction_count),
1506 config: &TreeConfig::default(),
1507 pending_sparse_trie_prune_blocks: None,
1508 },
1509 );
1510
1511 let mut state_hook = state_root_handle.take_execution_hook();
1512 for update in state_updates {
1513 state_hook.on_state(update);
1514 }
1515 drop(state_hook);
1516
1517 let root_from_task = state_root_handle.state_root().expect("task failed").state_root;
1518 let root_from_regular = state_root(accumulated_state);
1519 assert_eq!(root_from_task, root_from_regular);
1520 }
1521}