1use std::sync::Arc;
4
5use super::{evm_state_to_hashed_post_state, StateRootComputeOutcome, StateRootMessage};
6use alloy_primitives::{
7 map::{hash_map::Entry, B256Map},
8 B256,
9};
10use alloy_rlp::{Decodable, Encodable};
11use crossbeam_channel::{Receiver as CrossbeamReceiver, Sender as CrossbeamSender};
12use metrics::{Gauge, Histogram};
13use rayon::iter::{IntoParallelIterator, ParallelIterator};
14use reth_metrics::Metrics;
15use reth_primitives_traits::{Account, FastInstant as Instant};
16use reth_tasks::Runtime;
17use reth_trie::{
18 updates::TrieUpdates, DecodedMultiProofV2, HashedPostState, TrieAccount, EMPTY_ROOT_HASH,
19 TRIE_ACCOUNT_RLP_MAX_SIZE,
20};
21use reth_trie_common::{MultiProofTargetsV2, ProofV2Target, ProofV2TargetParent};
22use reth_trie_parallel::{
23 error::StateRootTaskError,
24 proof_task::{
25 AccountMultiproofInput, ProofResultContext, ProofResultMessage, ProofResultSender,
26 ProofWorkerHandle,
27 },
28};
29use reth_trie_sparse::{
30 errors::{SparseStateTrieErrorKind, SparseTrieErrorKind, SparseTrieResult},
31 ArenaParallelSparseTrie, DeferredDrops, LeafUpdate, RevealableSparseTrie, SparseStateTrie,
32 SparseTrie, TrieNodeEpoch,
33};
34use tracing::{debug, debug_span, error, instrument, trace_span};
35
36pub(super) struct SparseTrieCacheTask<A = ArenaParallelSparseTrie, S = ArenaParallelSparseTrie> {
38 proof_result_tx: ProofResultSender,
40 proof_result_rx: CrossbeamReceiver<ProofResultMessage>,
42 updates: CrossbeamReceiver<SparseTrieTaskMessage>,
44 cancel_rx: CrossbeamReceiver<()>,
48 final_hashed_state_tx: Option<std::sync::mpsc::Sender<Arc<HashedPostState>>>,
50 trie: SparseStateTrie<A, S>,
52 parent_state_root: B256,
54 new_epoch: TrieNodeEpoch,
56 proof_worker_handle: ProofWorkerHandle,
58
59 chunk_size: usize,
62 max_targets_for_chunking: usize,
66
67 account_updates: B256Map<LeafUpdate>,
69 storage_updates: B256Map<B256Map<LeafUpdate>>,
71
72 new_account_updates: B256Map<LeafUpdate>,
74 new_storage_updates: B256Map<B256Map<LeafUpdate>>,
76 pending_account_updates: B256Map<Option<Option<Account>>>,
90 fetched_account_targets: B256Map<ProofV2TargetParent>,
94 fetched_storage_targets: B256Map<B256Map<ProofV2TargetParent>>,
97 account_rlp_buf: Vec<u8>,
99 finished_state_updates: bool,
101 account_cache_hits: u64,
103 account_cache_misses: u64,
105 storage_cache_hits: u64,
107 storage_cache_misses: u64,
109 pending_targets: PendingTargets,
111 in_flight_proof_batches: usize,
113 pending_updates: usize,
116 initial_updates_applied: bool,
118 final_hashed_state: HashedPostState,
124
125 metrics: SparseTrieTaskMetrics,
127}
128
129impl<A, S> SparseTrieCacheTask<A, S>
130where
131 A: SparseTrie + Default,
132 S: SparseTrie + Default + Clone,
133{
134 #[expect(clippy::too_many_arguments)]
136 pub(super) fn new_with_trie(
137 executor: &Runtime,
138 updates: CrossbeamReceiver<StateRootMessage>,
139 cancel_rx: CrossbeamReceiver<()>,
140 final_hashed_state_tx: std::sync::mpsc::Sender<Arc<HashedPostState>>,
141 proof_worker_handle: ProofWorkerHandle,
142 proof_result_tx: ProofResultSender,
143 proof_result_rx: CrossbeamReceiver<ProofResultMessage>,
144 metrics: SparseTrieTaskMetrics,
145 trie: SparseStateTrie<A, S>,
146 parent_state_root: B256,
147 new_epoch: TrieNodeEpoch,
148 chunk_size: usize,
149 ) -> Self {
150 let (hashed_state_tx, hashed_state_rx) = crossbeam_channel::unbounded();
151
152 let parent_span = tracing::Span::current();
153 let hashing_metrics = metrics.clone();
154 executor.spawn_blocking_named("trie-hashing", move || {
155 let _span = trace_span!(parent: parent_span, "run_hashing_task").entered();
156 Self::run_hashing_task(updates, hashed_state_tx, hashing_metrics)
157 });
158
159 Self {
160 proof_result_tx,
161 proof_result_rx,
162 updates: hashed_state_rx,
163 cancel_rx,
164 proof_worker_handle,
165 final_hashed_state_tx: Some(final_hashed_state_tx),
166 trie,
167 parent_state_root,
168 new_epoch,
169 chunk_size,
170 max_targets_for_chunking: DEFAULT_MAX_TARGETS_FOR_CHUNKING,
171 account_updates: Default::default(),
172 storage_updates: Default::default(),
173 new_account_updates: Default::default(),
174 new_storage_updates: Default::default(),
175 pending_account_updates: Default::default(),
176 fetched_account_targets: Default::default(),
177 fetched_storage_targets: Default::default(),
178 account_rlp_buf: Vec::with_capacity(TRIE_ACCOUNT_RLP_MAX_SIZE),
179 finished_state_updates: Default::default(),
180 account_cache_hits: 0,
181 account_cache_misses: 0,
182 storage_cache_hits: 0,
183 storage_cache_misses: 0,
184 pending_targets: Default::default(),
185 in_flight_proof_batches: 0,
186 pending_updates: Default::default(),
187 initial_updates_applied: false,
188 final_hashed_state: Default::default(),
189 metrics,
190 }
191 }
192
193 fn run_hashing_task(
196 updates: CrossbeamReceiver<StateRootMessage>,
197 hashed_state_tx: CrossbeamSender<SparseTrieTaskMessage>,
198 metrics: SparseTrieTaskMetrics,
199 ) {
200 let mut total_idle_time = std::time::Duration::ZERO;
201 let mut idle_start = Instant::now();
202
203 while let Ok(message) = updates.recv() {
204 total_idle_time += idle_start.elapsed();
205
206 let msg = match message {
207 StateRootMessage::PrefetchProofs(targets) => {
208 SparseTrieTaskMessage::PrefetchProofs(targets)
209 }
210 StateRootMessage::StateUpdate(state) => {
211 let _span = trace_span!(target: "engine::tree::payload_processor::sparse_trie", "hashing_state_update", n = state.len()).entered();
212 let hashed = evm_state_to_hashed_post_state(state);
213 SparseTrieTaskMessage::HashedState(hashed)
214 }
215 StateRootMessage::FinishedStateUpdates => {
216 SparseTrieTaskMessage::FinishedStateUpdates
217 }
218 StateRootMessage::HashedStateUpdate(state) => {
219 SparseTrieTaskMessage::HashedState(state)
220 }
221 };
222 if hashed_state_tx.send(msg).is_err() {
223 break;
224 }
225
226 idle_start = Instant::now();
227 }
228
229 metrics.hashing_task_idle_time_seconds.record(total_idle_time.as_secs_f64());
230 }
231
232 pub(super) fn into_trie_for_reuse(self) -> (SparseStateTrie<A, S>, DeferredDrops) {
236 let Self { mut trie, .. } = self;
237 let deferred = trie.take_deferred_drops();
238 (trie, deferred)
239 }
240
241 pub(super) fn into_cleared_trie(self) -> (SparseStateTrie<A, S>, DeferredDrops) {
246 let Self { mut trie, .. } = self;
247 trie.clear();
248 let deferred = trie.take_deferred_drops();
249 (trie, deferred)
250 }
251
252 #[instrument(
259 name = "SparseTrieCacheTask::run",
260 level = "debug",
261 target = "engine::tree::payload_processor::sparse_trie",
262 skip_all
263 )]
264 pub(super) fn run(&mut self) -> Result<StateRootComputeOutcome, StateRootTaskError> {
265 let now = Instant::now();
266
267 let mut total_idle_time = std::time::Duration::ZERO;
268 let mut idle_start = Instant::now();
269 let mut done = false;
270 let mut finalized_hashed_state = None;
271
272 while !self.finished_state_updates {
276 let mut t = Instant::now();
277 crossbeam_channel::select_biased! {
278 recv(self.updates) -> message => {
279 let wake = Instant::now();
280 total_idle_time += wake.duration_since(idle_start);
281 self.metrics
282 .sparse_trie_channel_wait_duration_histogram
283 .record(wake.duration_since(t));
284
285 let update = message.map_err(|_| StateRootTaskError::Other(
286 "updates channel disconnected before state root calculation".to_string(),
287 ))?;
288 if let Some(hashed_state) = self.on_message(update) {
289 finalized_hashed_state = Some(hashed_state);
290 }
291 self.pending_updates += 1;
292 }
293 recv(self.proof_result_rx) -> message => {
294 let wake = Instant::now();
295 total_idle_time += wake.duration_since(idle_start);
296 self.metrics
297 .sparse_trie_channel_wait_duration_histogram
298 .record(wake.duration_since(t));
299 t = wake;
300
301 let Ok(result) = message else {
302 unreachable!("we own the sender half")
303 };
304 self.on_proof_results(result, &mut t)?;
305 },
306 recv(self.cancel_rx) -> _ => return Err(StateRootTaskError::Canceled),
307 }
308
309 done = self.make_progress()?;
310 idle_start = Instant::now();
311 }
312
313 while !done {
318 let mut t = Instant::now();
319 crossbeam_channel::select_biased! {
320 recv(self.proof_result_rx) -> message => {
321 let wake = Instant::now();
322 total_idle_time += wake.duration_since(idle_start);
323 self.metrics
324 .sparse_trie_channel_wait_duration_histogram
325 .record(wake.duration_since(t));
326 t = wake;
327
328 let Ok(result) = message else {
329 unreachable!("we own the sender half")
330 };
331 self.on_proof_results(result, &mut t)?;
332 },
333 recv(self.cancel_rx) -> _ => return Err(StateRootTaskError::Canceled),
334 }
335
336 done = self.make_progress()?;
337 idle_start = Instant::now();
338 }
339
340 self.metrics.sparse_trie_idle_time_seconds.record(total_idle_time.as_secs_f64());
341
342 debug!(target: "engine::root", "All proofs processed, ending calculation");
343
344 let start = Instant::now();
345 let (state_root, trie_updates) = match self.trie.root_with_updates(self.new_epoch) {
346 Ok(result) => result,
347 Err(err)
348 if matches!(
349 err.kind(),
350 SparseStateTrieErrorKind::Sparse(SparseTrieErrorKind::Blind)
351 ) =>
352 {
353 (self.parent_state_root, TrieUpdates::default())
357 }
358 Err(err) => {
359 return Err(StateRootTaskError::Other(format!(
360 "could not calculate state root: {err:?}"
361 )))
362 }
363 };
364
365 let end = Instant::now();
366 self.metrics.sparse_trie_final_update_duration_histogram.record(end.duration_since(start));
367 self.metrics.sparse_trie_total_duration_histogram.record(end.duration_since(now));
368
369 self.metrics.sparse_trie_account_cache_hits.record(self.account_cache_hits as f64);
370 self.metrics.sparse_trie_account_cache_misses.record(self.account_cache_misses as f64);
371 self.metrics.sparse_trie_storage_cache_hits.record(self.storage_cache_hits as f64);
372 self.metrics.sparse_trie_storage_cache_misses.record(self.storage_cache_misses as f64);
373 self.account_cache_hits = 0;
374 self.account_cache_misses = 0;
375 self.storage_cache_hits = 0;
376 self.storage_cache_misses = 0;
377
378 Ok(StateRootComputeOutcome {
379 state_root,
380 trie_updates: Arc::new(trie_updates),
381 hashed_state: finalized_hashed_state
382 .expect("finished state updates publish the hashed post state"),
383 })
384 }
385
386 fn on_proof_results(
389 &mut self,
390 message: ProofResultMessage,
391 t: &mut Instant,
392 ) -> Result<(), StateRootTaskError> {
393 let mut result = self.on_proof_result_message(message)?;
394 while let Ok(next) = self.proof_result_rx.try_recv() {
395 let res = self.on_proof_result_message(next)?;
396 result.extend(res);
397 }
398
399 let phase_end = Instant::now();
400 self.metrics
401 .sparse_trie_proof_coalesce_duration_histogram
402 .record(phase_end.duration_since(*t));
403 *t = phase_end;
404
405 self.on_proof_result(result)?;
406 self.metrics.sparse_trie_reveal_multiproof_duration_histogram.record(t.elapsed());
407 Ok(())
408 }
409
410 fn make_progress(&mut self) -> Result<bool, StateRootTaskError> {
415 let updates_queued = !self.finished_state_updates && !self.updates.is_empty();
416
417 if !updates_queued && self.proof_result_rx.is_empty() {
418 self.dispatch_pending_targets()?;
421 let t = Instant::now();
422 self.process_new_updates()?;
423 self.promote_pending_account_updates()?;
424 self.metrics.sparse_trie_process_updates_duration_histogram.record(t.elapsed());
425
426 if self.finished_state_updates && !self.has_pending_sparse_trie_updates() {
427 return Ok(true);
428 }
429
430 self.dispatch_pending_targets()?;
431 self.ensure_not_stalled(updates_queued)?;
432
433 if self.proof_result_rx.is_empty() {
436 self.trie.calculate_subtries(self.new_epoch);
437 }
438 } else if !updates_queued {
439 let t = Instant::now();
441 self.process_new_updates()?;
442 self.metrics.sparse_trie_process_updates_duration_histogram.record(t.elapsed());
443 self.dispatch_pending_targets()?;
444 } else if !self.initial_updates_applied && self.pending_updates >= INITIAL_UPDATE_BATCH_SIZE
445 {
446 let t = Instant::now();
449 self.process_new_updates()?;
450 self.metrics.sparse_trie_process_updates_duration_histogram.record(t.elapsed());
451 self.dispatch_pending_targets()?;
452 } else if self.pending_targets.len() > self.chunk_size {
453 self.dispatch_pending_targets()?;
455 }
456 Ok(false)
457 }
458
459 fn on_message(&mut self, message: SparseTrieTaskMessage) -> Option<Arc<HashedPostState>> {
461 match message {
462 SparseTrieTaskMessage::PrefetchProofs(targets) => {
463 self.on_prewarm_targets(targets);
464 None
465 }
466 SparseTrieTaskMessage::HashedState(hashed_state) => {
467 self.on_hashed_state_update(hashed_state);
468 None
469 }
470 SparseTrieTaskMessage::FinishedStateUpdates => {
471 let hashed_state = Arc::new(core::mem::take(&mut self.final_hashed_state));
472 let _ = self.final_hashed_state_tx.take().unwrap().send(Arc::clone(&hashed_state));
473 self.finished_state_updates = true;
474 Some(hashed_state)
475 }
476 }
477 }
478
479 #[instrument(
480 level = "trace",
481 target = "engine::tree::payload_processor::sparse_trie",
482 skip_all
483 )]
484 fn on_prewarm_targets(&mut self, targets: MultiProofTargetsV2) {
485 for target in targets.account_targets {
486 self.new_account_updates.entry(target.key()).or_insert(LeafUpdate::Touched);
488 }
489
490 for (address, slots) in targets.storage_targets {
491 if !slots.is_empty() {
492 let new_updates = self.new_storage_updates.entry(address).or_default();
494 for slot in slots {
495 new_updates.entry(slot.key()).or_insert(LeafUpdate::Touched);
497 }
498 }
499
500 self.new_account_updates.entry(address).or_insert(LeafUpdate::Touched);
503 }
504 }
505
506 #[instrument(
508 level = "trace",
509 target = "engine::tree::payload_processor::sparse_trie",
510 skip_all
511 )]
512 fn on_hashed_state_update(&mut self, hashed_state_update: HashedPostState) {
513 for (&address, storage) in &hashed_state_update.storages {
514 if !storage.storage.is_empty() {
515 let new_updates = self.new_storage_updates.entry(address).or_default();
517 let mut existing_updates = self.storage_updates.get_mut(&address);
518
519 for (&slot, &value) in &storage.storage {
520 let encoded = if value.is_zero() {
521 Vec::new()
522 } else {
523 alloy_rlp::encode_fixed_size(&value).to_vec()
524 };
525 new_updates.insert(slot, LeafUpdate::Changed(encoded));
526
527 if let Some(ref mut existing) = existing_updates {
529 existing.remove(&slot);
530 }
531 }
532 }
533
534 self.new_account_updates.entry(address).or_insert(LeafUpdate::Touched);
537
538 self.pending_account_updates.entry(address).or_insert(None);
541 }
542
543 for (&address, &account) in &hashed_state_update.accounts {
544 self.new_account_updates.insert(address, LeafUpdate::Touched);
549
550 self.pending_account_updates.insert(address, Some(account));
553 }
554
555 self.final_hashed_state.extend(hashed_state_update);
556 }
557
558 fn on_proof_result(&mut self, result: DecodedMultiProofV2) -> Result<(), StateRootTaskError> {
559 self.trie
560 .reveal_decoded_multiproof_v2(result)
561 .map_err(|e| StateRootTaskError::Other(format!("could not reveal multiproof: {e:?}")))
562 }
563
564 fn on_proof_result_message(
565 &mut self,
566 message: ProofResultMessage,
567 ) -> Result<DecodedMultiProofV2, StateRootTaskError> {
568 let result = message.result?;
569 debug_assert!(
570 self.in_flight_proof_batches > 0,
571 "received proof result without an in-flight proof batch"
572 );
573 self.in_flight_proof_batches = self.in_flight_proof_batches.saturating_sub(1);
574 Ok(result)
575 }
576
577 fn process_new_updates(&mut self) -> SparseTrieResult<()> {
578 if self.pending_updates == 0 {
579 return Ok(());
580 }
581
582 let _span = debug_span!("process_new_updates").entered();
583 self.pending_updates = 0;
584 self.initial_updates_applied = true;
585
586 self.process_leaf_updates(true)?;
588
589 for (address, mut new) in self.new_storage_updates.drain() {
590 match self.storage_updates.entry(address) {
591 Entry::Vacant(entry) => {
592 entry.insert(new); }
594 Entry::Occupied(mut entry) => {
595 let updates = entry.get_mut();
596 for (slot, new) in new.drain() {
597 match updates.entry(slot) {
598 Entry::Occupied(mut slot_entry) => {
599 if new.is_changed() {
600 slot_entry.insert(new);
601 }
602 }
603 Entry::Vacant(slot_entry) => {
604 slot_entry.insert(new);
605 }
606 }
607 }
608 }
609 }
610 }
611
612 for (address, new) in self.new_account_updates.drain() {
613 match self.account_updates.entry(address) {
614 Entry::Occupied(mut entry) => {
615 if new.is_changed() {
616 entry.insert(new);
617 }
618 }
619 Entry::Vacant(entry) => {
620 entry.insert(new);
621 }
622 }
623 }
624
625 Ok(())
626 }
627
628 #[instrument(
631 level = "trace",
632 target = "engine::tree::payload_processor::sparse_trie",
633 skip_all
634 )]
635 fn process_leaf_updates(&mut self, new: bool) -> SparseTrieResult<()> {
636 let storage_updates =
637 if new { &mut self.new_storage_updates } else { &mut self.storage_updates };
638
639 let span = trace_span!("process_storage_leaf_updates").entered();
641 for (address, updates) in storage_updates {
642 if updates.is_empty() {
643 continue;
644 }
645 let _enter = trace_span!(target: "engine::tree::payload_processor::sparse_trie", parent: &span, "storage_trie_leaf_updates", a=%address).entered();
646
647 let trie = self.trie.get_or_create_storage_trie_mut(*address);
648 let fetched = self.fetched_storage_targets.entry(*address).or_default();
649 let mut targets = Vec::new();
650
651 let updates_len_before = updates.len();
652 trie.update_leaves(updates, |path, parent| match fetched.entry(path) {
653 Entry::Occupied(mut entry) => {
654 if parent < *entry.get() {
655 entry.insert(parent);
656 targets.push(ProofV2Target::new(path).with_parent(parent));
657 }
658 }
659 Entry::Vacant(entry) => {
660 entry.insert(parent);
661 targets.push(ProofV2Target::new(path).with_parent(parent));
662 }
663 })?;
664 let updates_len_after = updates.len();
665 self.storage_cache_hits += (updates_len_before - updates_len_after) as u64;
666 self.storage_cache_misses += updates_len_after as u64;
667
668 if !targets.is_empty() {
669 self.pending_targets.extend_storage_targets(address, targets);
670 }
671 }
672
673 drop(span);
674
675 self.process_account_leaf_updates(new)?;
677
678 Ok(())
679 }
680
681 #[instrument(
685 level = "trace",
686 target = "engine::tree::payload_processor::sparse_trie",
687 skip_all
688 )]
689 fn process_account_leaf_updates(&mut self, new: bool) -> SparseTrieResult<bool> {
690 let account_updates =
691 if new { &mut self.new_account_updates } else { &mut self.account_updates };
692
693 let updates_len_before = account_updates.len();
694
695 self.trie.trie_mut().update_leaves(account_updates, |target, parent| {
696 match self.fetched_account_targets.entry(target) {
697 Entry::Occupied(mut entry) => {
698 if parent < *entry.get() {
699 entry.insert(parent);
700 self.pending_targets
701 .push_account_target(ProofV2Target::new(target).with_parent(parent));
702 }
703 }
704 Entry::Vacant(entry) => {
705 entry.insert(parent);
706 self.pending_targets
707 .push_account_target(ProofV2Target::new(target).with_parent(parent));
708 }
709 }
710 })?;
711
712 let updates_len_after = account_updates.len();
713 self.account_cache_hits += (updates_len_before - updates_len_after) as u64;
714 self.account_cache_misses += updates_len_after as u64;
715
716 Ok(updates_len_after < updates_len_before)
717 }
718
719 fn compute_drained_storage_roots(&mut self) {
728 struct SendStorageTriePtr<S>(*mut RevealableSparseTrie<S>);
729 unsafe impl<S: Send> Send for SendStorageTriePtr<S> {}
732
733 let mut tries_to_compute_roots: Vec<(B256, SendStorageTriePtr<S>)> = Vec::new();
734 for (address, updates) in &self.storage_updates {
735 if updates.is_empty() &&
736 let Some(trie) = self.trie.storage_tries_mut().get_mut(address) &&
737 !trie.is_root_cached()
738 {
739 tries_to_compute_roots.push((*address, SendStorageTriePtr(trie)));
740 }
741 }
742
743 if tries_to_compute_roots.is_empty() {
744 return;
745 }
746
747 let parent_span =
748 debug_span!("compute_drained_storage_roots", n = tries_to_compute_roots.len());
749 let new_epoch = self.new_epoch;
750 tries_to_compute_roots.into_par_iter().for_each(|(address, SendStorageTriePtr(trie))| {
751 let span = if tracing::enabled!(tracing::Level::TRACE) {
752 debug_span!(
753 target: "engine::tree::payload_processor::sparse_trie",
754 parent: &parent_span,
755 "storage_root",
756 ?address
757 )
758 } else {
759 debug_span!(
760 target: "engine::tree::payload_processor::sparse_trie",
761 parent: &parent_span,
762 "storage_root",
763 )
764 };
765 let _enter = span.entered();
766 unsafe {
773 (*trie)
774 .root(new_epoch)
775 .expect("updates are drained, trie should be revealed by now")
776 };
777 });
778 }
779
780 #[instrument(
784 level = "trace",
785 target = "engine::tree::payload_processor::sparse_trie",
786 skip_all
787 )]
788 fn promote_pending_account_updates(&mut self) -> SparseTrieResult<()> {
789 self.process_leaf_updates(false)?;
790
791 if self.pending_account_updates.is_empty() {
792 return Ok(());
793 }
794
795 self.compute_drained_storage_roots();
796
797 loop {
798 let span = trace_span!("promote_updates", promoted = tracing::field::Empty).entered();
799 let account_rlp_buf = &mut self.account_rlp_buf;
801 let mut num_promoted = 0;
802 self.pending_account_updates.retain(|addr, account| {
803 if let Some(updates) = self.storage_updates.get(addr) {
804 if !updates.is_empty() {
805 return true;
807 } else if let Some(account) = account.take() {
808 let storage_root = self.trie.storage_root(addr, self.new_epoch).expect("updates are drained, storage trie should be revealed by now");
809 let encoded = encode_account_leaf_value(account, storage_root, account_rlp_buf);
810 self.account_updates.insert(*addr, LeafUpdate::Changed(encoded));
811 num_promoted += 1;
812 return false;
813 }
814 }
815
816 let trie_account = match self.account_updates.get(addr) {
818 Some(LeafUpdate::Changed(encoded)) => {
819 Some(encoded).filter(|encoded| !encoded.is_empty())
820 }
821 Some(LeafUpdate::Touched) => return true,
823 None => self.trie.get_account_value(addr),
824 };
825
826 let trie_account = trie_account.map(|value| TrieAccount::decode(&mut &value[..]).expect("invalid account RLP"));
827
828 let (account, storage_root) = if let Some(account) = account.take() {
829 let storage_root = trie_account.map(|account| account.storage_root).unwrap_or(EMPTY_ROOT_HASH);
834
835 (account, storage_root)
836 } else {
837 (trie_account.map(Into::into), self.trie.storage_root(addr, self.new_epoch).expect("account had storage updates that were applied to its trie, storage root must be revealed by now"))
838 };
839
840 let encoded = encode_account_leaf_value(account, storage_root, account_rlp_buf);
841 self.account_updates.insert(*addr, LeafUpdate::Changed(encoded));
842 num_promoted += 1;
843
844 false
845 });
846 span.record("promoted", num_promoted);
847 drop(span);
848
849 if num_promoted == 0 || !self.process_account_leaf_updates(false)? {
854 break
855 }
856 }
857
858 Ok(())
859 }
860
861 fn dispatch_pending_targets(&mut self) -> Result<(), StateRootTaskError> {
862 if self.pending_targets.is_empty() {
863 return Ok(())
864 }
865
866 let _span = trace_span!("dispatch_pending_targets").entered();
867 let (targets, chunking_length) = self.pending_targets.take();
868 let mut dispatch_error = None;
869 dispatch_with_chunking(
870 targets,
871 chunking_length,
872 self.chunk_size,
873 self.max_targets_for_chunking,
874 self.proof_worker_handle.has_multiple_idle_account_workers(),
875 self.proof_worker_handle.has_multiple_idle_storage_workers(),
876 MultiProofTargetsV2::chunks,
877 |proof_targets| {
878 if dispatch_error.is_some() {
879 return;
880 }
881
882 match self.proof_worker_handle.dispatch_account_multiproof(AccountMultiproofInput {
883 targets: proof_targets,
884 proof_result_sender: ProofResultContext::new(
885 self.proof_result_tx.clone(),
886 HashedPostState::default(),
887 Instant::now(),
888 ),
889 }) {
890 Ok(()) => {
891 self.in_flight_proof_batches += 1;
892 }
893 Err(e) => {
894 error!("failed to dispatch account multiproof: {e:?}");
895 dispatch_error = Some(StateRootTaskError::ProofDispatch(e));
896 }
897 }
898 },
899 );
900
901 if let Some(error) = dispatch_error {
902 return Err(error)
903 }
904
905 Ok(())
906 }
907
908 fn has_pending_sparse_trie_updates(&self) -> bool {
909 !self.account_updates.is_empty() ||
910 self.storage_updates.values().any(|updates| !updates.is_empty()) ||
911 !self.pending_account_updates.is_empty()
912 }
913
914 fn ensure_not_stalled(&self, updates_queued: bool) -> Result<(), StateRootTaskError> {
922 if self.finished_state_updates &&
923 !updates_queued &&
924 self.pending_updates == 0 &&
925 self.pending_targets.is_empty() &&
926 self.in_flight_proof_batches == 0 &&
927 self.proof_result_rx.is_empty() &&
928 self.has_pending_sparse_trie_updates()
929 {
930 const MAX_STALLED_PROOF_TARGETS_TO_LOG: usize = 5;
931
932 let mut account_targets = self
933 .account_updates
934 .keys()
935 .map(|target| (*target, self.fetched_account_targets.get(target).copied()))
936 .collect::<Vec<_>>();
937 account_targets.sort_unstable();
938 let account_targets_truncated =
939 account_targets.len().saturating_sub(MAX_STALLED_PROOF_TARGETS_TO_LOG);
940 account_targets.truncate(MAX_STALLED_PROOF_TARGETS_TO_LOG);
941
942 let mut storage_targets = self
943 .storage_updates
944 .iter()
945 .flat_map(|(address, updates)| {
946 let fetched_targets = self.fetched_storage_targets.get(address);
947 updates.keys().map(move |target| {
948 (
949 *address,
950 *target,
951 fetched_targets.and_then(|targets| targets.get(target)).copied(),
952 )
953 })
954 })
955 .collect::<Vec<_>>();
956 storage_targets.sort_unstable();
957 let storage_targets_truncated =
958 storage_targets.len().saturating_sub(MAX_STALLED_PROOF_TARGETS_TO_LOG);
959 storage_targets.truncate(MAX_STALLED_PROOF_TARGETS_TO_LOG);
960
961 error!(
962 ?account_targets,
963 account_targets_truncated,
964 ?storage_targets,
965 storage_targets_truncated,
966 "sparse trie task stalled: pending updates remain but no proof targets are queued or in flight"
967 );
968
969 return Err(StateRootTaskError::Stalled)
970 }
971
972 Ok(())
973 }
974}
975
976#[derive(Metrics, Clone)]
978#[metrics(scope = "tree.root")]
979pub(super) struct SparseTrieTaskMetrics {
980 pub(super) sparse_trie_reveal_multiproof_duration_histogram: Histogram,
982 pub(super) sparse_trie_proof_coalesce_duration_histogram: Histogram,
984 pub(super) sparse_trie_channel_wait_duration_histogram: Histogram,
986 pub(super) sparse_trie_process_updates_duration_histogram: Histogram,
988 pub(super) sparse_trie_final_update_duration_histogram: Histogram,
990 pub(super) sparse_trie_total_duration_histogram: Histogram,
992 pub(super) into_trie_for_reuse_duration_histogram: Histogram,
994 pub(super) sparse_trie_prune_duration_histogram: Histogram,
996 pub(super) sparse_trie_cache_wait_duration_histogram: Histogram,
998 pub(super) sparse_trie_idle_time_seconds: Histogram,
1001 pub(super) hashing_task_idle_time_seconds: Histogram,
1004
1005 pub(super) sparse_trie_account_cache_hits: Histogram,
1007 pub(super) sparse_trie_account_cache_misses: Histogram,
1009 pub(super) sparse_trie_storage_cache_hits: Histogram,
1011 pub(super) sparse_trie_storage_cache_misses: Histogram,
1013
1014 pub(super) sparse_trie_retained_storage_tries: Gauge,
1016}
1017
1018const DEFAULT_MAX_TARGETS_FOR_CHUNKING: usize = 300;
1021
1022const INITIAL_UPDATE_BATCH_SIZE: usize = 64;
1024
1025#[expect(clippy::too_many_arguments)]
1028fn dispatch_with_chunking<T, I>(
1029 items: T,
1030 chunking_len: usize,
1031 chunk_size: usize,
1032 max_targets_for_chunking: usize,
1033 has_multiple_idle_account_workers: bool,
1034 has_multiple_idle_storage_workers: bool,
1035 chunker: impl FnOnce(T, usize) -> I,
1036 mut dispatch: impl FnMut(T),
1037) where
1038 I: IntoIterator<Item = T>,
1039{
1040 let has_full_chunks = chunking_len >= chunk_size.saturating_mul(2);
1041 let should_chunk = chunking_len > max_targets_for_chunking ||
1042 (has_full_chunks &&
1043 (has_multiple_idle_account_workers || has_multiple_idle_storage_workers));
1044
1045 if should_chunk && chunking_len > chunk_size {
1046 for chunk in chunker(items, chunk_size) {
1047 dispatch(chunk);
1048 }
1049 return;
1050 }
1051
1052 dispatch(items);
1053}
1054
1055fn encode_account_leaf_value(
1062 account: Option<Account>,
1063 storage_root: B256,
1064 account_rlp_buf: &mut Vec<u8>,
1065) -> Vec<u8> {
1066 if account.is_none_or(|account| account.is_empty()) && storage_root == EMPTY_ROOT_HASH {
1067 return Vec::new();
1068 }
1069
1070 account_rlp_buf.clear();
1071 account.unwrap_or_default().into_trie_account(storage_root).encode(account_rlp_buf);
1072 account_rlp_buf.clone()
1073}
1074
1075#[derive(Default)]
1077struct PendingTargets {
1078 targets: MultiProofTargetsV2,
1080 len: usize,
1082}
1083
1084impl PendingTargets {
1085 const fn len(&self) -> usize {
1087 self.len
1088 }
1089
1090 const fn is_empty(&self) -> bool {
1092 self.len == 0
1093 }
1094
1095 fn take(&mut self) -> (MultiProofTargetsV2, usize) {
1097 (std::mem::take(&mut self.targets), std::mem::take(&mut self.len))
1098 }
1099
1100 fn push_account_target(&mut self, target: ProofV2Target) {
1102 self.targets.account_targets.push(target);
1103 self.len += 1;
1104 }
1105
1106 fn extend_storage_targets(&mut self, address: &B256, targets: Vec<ProofV2Target>) {
1108 self.len += targets.len();
1109 self.targets.storage_targets.entry(*address).or_default().extend(targets);
1110 }
1111}
1112
1113enum SparseTrieTaskMessage {
1115 HashedState(HashedPostState),
1117 PrefetchProofs(MultiProofTargetsV2),
1119 FinishedStateUpdates,
1121}
1122
1123#[cfg(test)]
1124mod tests {
1125 use super::*;
1126 use alloy_primitives::{keccak256, Address, B256, U256};
1127 use reth_db_common::init::init_genesis;
1128 use reth_provider::test_utils::create_test_provider_factory;
1129 use reth_storage_overlay::{OverlayManager, OverlayStateProviderFactory};
1130 use reth_trie_parallel::proof_task::ProofTaskCtx;
1131 use reth_trie_sparse::ArenaParallelSparseTrie;
1132
1133 fn drain_sparse_trie_tasks(runtime: &Runtime) {
1134 for task_name in ["trie-hashing", "storage-workers", "account-workers"] {
1135 runtime.spawn_blocking_named(task_name, || {}).get();
1136 }
1137 }
1138
1139 #[test]
1140 fn test_run_hashing_task_hashed_state_update_forwards() {
1141 let (updates_tx, updates_rx) = crossbeam_channel::unbounded();
1142 let (hashed_state_tx, hashed_state_rx) = crossbeam_channel::unbounded();
1143
1144 let address = keccak256(Address::random());
1145 let slot = keccak256(U256::from(42).to_be_bytes::<32>());
1146 let value = U256::from(999);
1147
1148 let mut hashed_state = HashedPostState::default();
1149 hashed_state.accounts.insert(
1150 address,
1151 Some(Account { balance: U256::from(100), nonce: 1, bytecode_hash: None }),
1152 );
1153 let mut storage = reth_trie::HashedStorage::default();
1154 storage.storage.insert(slot, value);
1155 hashed_state.storages.insert(address, storage);
1156
1157 let expected_state = hashed_state.clone();
1158
1159 let handle = std::thread::spawn(move || {
1160 SparseTrieCacheTask::<ArenaParallelSparseTrie, ArenaParallelSparseTrie>::run_hashing_task(
1161 updates_rx,
1162 hashed_state_tx,
1163 SparseTrieTaskMetrics::default(),
1164 );
1165 });
1166
1167 updates_tx.send(StateRootMessage::HashedStateUpdate(hashed_state)).unwrap();
1168 updates_tx.send(StateRootMessage::FinishedStateUpdates).unwrap();
1169 drop(updates_tx);
1170
1171 let SparseTrieTaskMessage::HashedState(received) = hashed_state_rx.recv().unwrap() else {
1172 panic!("expected HashedState message");
1173 };
1174
1175 let account = received.accounts.get(&address).unwrap().unwrap();
1176 assert_eq!(account.balance, expected_state.accounts[&address].unwrap().balance);
1177 assert_eq!(account.nonce, expected_state.accounts[&address].unwrap().nonce);
1178
1179 let storage = received.storages.get(&address).unwrap();
1180 assert_eq!(*storage.storage.get(&slot).unwrap(), value);
1181
1182 let second = hashed_state_rx.recv().unwrap();
1183 assert!(matches!(second, SparseTrieTaskMessage::FinishedStateUpdates));
1184
1185 assert!(hashed_state_rx.recv().is_err());
1186 handle.join().unwrap();
1187 }
1188
1189 #[test]
1190 fn test_encode_account_leaf_value_deletion_and_empty_root_is_empty() {
1191 let mut account_rlp_buf = vec![0xAB];
1192 let encoded = encode_account_leaf_value(None, EMPTY_ROOT_HASH, &mut account_rlp_buf);
1193
1194 assert!(encoded.is_empty());
1195 assert_eq!(account_rlp_buf, vec![0xAB]);
1197 }
1198
1199 #[test]
1200 fn test_encode_account_leaf_value_empty_account_and_empty_root_is_empty() {
1201 let mut account_rlp_buf = vec![0xAB];
1202 let encoded = encode_account_leaf_value(
1203 Some(Account::default()),
1204 EMPTY_ROOT_HASH,
1205 &mut account_rlp_buf,
1206 );
1207
1208 assert!(encoded.is_empty());
1209 assert_eq!(account_rlp_buf, vec![0xAB]);
1211 }
1212
1213 #[test]
1214 fn test_encode_account_leaf_value_non_empty_account_is_rlp() {
1215 let storage_root = B256::from([0x99; 32]);
1216 let account = Some(Account {
1217 nonce: 7,
1218 balance: U256::from(42),
1219 bytecode_hash: Some(B256::from([0xAA; 32])),
1220 });
1221 let mut account_rlp_buf = vec![0x00, 0x01];
1222
1223 let encoded = encode_account_leaf_value(account, storage_root, &mut account_rlp_buf);
1224 let decoded = TrieAccount::decode(&mut &encoded[..]).expect("valid account RLP");
1225
1226 assert_eq!(decoded.nonce, 7);
1227 assert_eq!(decoded.balance, U256::from(42));
1228 assert_eq!(decoded.storage_root, storage_root);
1229 assert_eq!(account_rlp_buf, encoded);
1230 }
1231
1232 #[test]
1233 fn first_leaf_batch_starts_proofs_before_input_queue_drains() {
1234 let runtime = reth_tasks::Runtime::test();
1235 let provider_factory = create_test_provider_factory();
1236 let anchor_hash = init_genesis(&provider_factory).expect("failed to initialize genesis");
1237 let state_provider_factory = OverlayStateProviderFactory::new(
1238 provider_factory,
1239 OverlayManager::<reth_chain_state::EthPrimitives>::default()
1240 .overlay_builder(anchor_hash),
1241 );
1242 let (proof_result_tx, proof_result_rx) = crossbeam_channel::unbounded();
1243 let proof_worker_handle = ProofWorkerHandle::new(
1244 &runtime,
1245 ProofTaskCtx::new(state_provider_factory),
1246 false,
1247 proof_result_tx.clone(),
1248 );
1249
1250 let default_trie = RevealableSparseTrie::blind_from(ArenaParallelSparseTrie::default());
1251 let trie = SparseStateTrie::default()
1252 .with_accounts_trie(default_trie.clone())
1253 .with_default_storage_trie(default_trie)
1254 .with_updates(true);
1255
1256 let parent_state_root = B256::from([0x55; 32]);
1257 let (updates_tx, updates_rx) = crossbeam_channel::unbounded();
1258 let (_cancel_guard, cancel_rx) = crossbeam_channel::bounded::<()>(0);
1259 let mut task = SparseTrieCacheTask::new_with_trie(
1260 &runtime,
1261 updates_rx,
1262 cancel_rx,
1263 std::sync::mpsc::channel().0,
1264 proof_worker_handle,
1265 proof_result_tx,
1266 proof_result_rx,
1267 SparseTrieTaskMetrics::default(),
1268 trie,
1269 parent_state_root,
1270 TrieNodeEpoch::UNMODIFIED,
1271 1,
1272 );
1273
1274 updates_tx.send(StateRootMessage::PrefetchProofs(Default::default())).unwrap();
1276 let deadline = std::time::Instant::now();
1277 while task.updates.is_empty() {
1278 assert!(deadline.elapsed() < std::time::Duration::from_secs(1));
1279 std::thread::yield_now();
1280 }
1281 for index in 0..INITIAL_UPDATE_BATCH_SIZE {
1282 let mut state = HashedPostState::default();
1283 state.accounts.insert(
1284 B256::repeat_byte(index as u8),
1285 Some(Account { nonce: 1, ..Default::default() }),
1286 );
1287 task.on_hashed_state_update(state);
1288 task.pending_updates += 1;
1289 assert!(!task.make_progress().unwrap());
1290 if index + 1 < INITIAL_UPDATE_BATCH_SIZE {
1291 assert_eq!(task.in_flight_proof_batches, 0);
1292 }
1293 }
1294 assert!(task.in_flight_proof_batches > 0, "proof work must start before the queue drains");
1295 assert_eq!(task.pending_updates, 0);
1296
1297 for index in INITIAL_UPDATE_BATCH_SIZE..INITIAL_UPDATE_BATCH_SIZE * 2 {
1300 let mut state = HashedPostState::default();
1301 state.accounts.insert(
1302 B256::repeat_byte(index as u8),
1303 Some(Account { nonce: 1, ..Default::default() }),
1304 );
1305 task.on_hashed_state_update(state);
1306 task.pending_updates += 1;
1307 assert!(!task.make_progress().unwrap());
1308 }
1309 assert_eq!(task.pending_updates, INITIAL_UPDATE_BATCH_SIZE);
1310 assert_eq!(task.new_account_updates.len(), INITIAL_UPDATE_BATCH_SIZE);
1311 drop(updates_tx);
1312 drop(task);
1313 drain_sparse_trie_tasks(&runtime);
1314 }
1315
1316 #[test]
1317 fn run_returns_parent_root_without_revealing_blind_trie_when_no_state_updates() {
1318 let runtime = reth_tasks::Runtime::test();
1319 let provider_factory = create_test_provider_factory();
1320 let anchor_hash = init_genesis(&provider_factory).expect("failed to initialize genesis");
1321 let state_provider_factory = OverlayStateProviderFactory::new(
1322 provider_factory,
1323 OverlayManager::<reth_chain_state::EthPrimitives>::default()
1324 .overlay_builder(anchor_hash),
1325 );
1326 let (proof_result_tx, proof_result_rx) = crossbeam_channel::unbounded();
1327 let proof_worker_handle = ProofWorkerHandle::new(
1328 &runtime,
1329 ProofTaskCtx::new(state_provider_factory),
1330 false,
1331 proof_result_tx.clone(),
1332 );
1333
1334 let default_trie = RevealableSparseTrie::blind_from(ArenaParallelSparseTrie::default());
1335 let trie = SparseStateTrie::default()
1336 .with_accounts_trie(default_trie.clone())
1337 .with_default_storage_trie(default_trie)
1338 .with_updates(true);
1339
1340 let parent_state_root = B256::from([0x55; 32]);
1341 let (updates_tx, updates_rx) = crossbeam_channel::unbounded();
1342 let (_cancel_guard, cancel_rx) = crossbeam_channel::bounded::<()>(0);
1343 let mut task = SparseTrieCacheTask::new_with_trie(
1344 &runtime,
1345 updates_rx,
1346 cancel_rx,
1347 std::sync::mpsc::channel().0,
1348 proof_worker_handle,
1349 proof_result_tx,
1350 proof_result_rx,
1351 SparseTrieTaskMetrics::default(),
1352 trie,
1353 parent_state_root,
1354 TrieNodeEpoch::UNMODIFIED,
1355 1,
1356 );
1357
1358 updates_tx.send(StateRootMessage::FinishedStateUpdates).unwrap();
1359 drop(updates_tx);
1360
1361 let outcome = task.run().expect("state root computation should succeed");
1362
1363 assert_eq!(outcome.state_root, parent_state_root);
1364 assert!(outcome.trie_updates.is_empty());
1365 assert!(task.trie.state_trie_ref().is_none(), "blind trie should not be revealed");
1366
1367 drop(task);
1368 drain_sparse_trie_tasks(&runtime);
1369 }
1370
1371 #[test]
1372 fn stall_check_waits_for_in_flight_proofs_then_reports_pending_updates() {
1373 let runtime = reth_tasks::Runtime::test();
1374 let provider_factory = create_test_provider_factory();
1375 let anchor_hash = init_genesis(&provider_factory).expect("failed to initialize genesis");
1376 let state_provider_factory = OverlayStateProviderFactory::new(
1377 provider_factory,
1378 OverlayManager::<reth_chain_state::EthPrimitives>::default()
1379 .overlay_builder(anchor_hash),
1380 );
1381 let (proof_result_tx, proof_result_rx) = crossbeam_channel::unbounded();
1382 let proof_worker_handle = ProofWorkerHandle::new(
1383 &runtime,
1384 ProofTaskCtx::new(state_provider_factory),
1385 false,
1386 proof_result_tx.clone(),
1387 );
1388
1389 let default_trie = RevealableSparseTrie::blind_from(ArenaParallelSparseTrie::default());
1390 let trie = SparseStateTrie::default()
1391 .with_accounts_trie(default_trie.clone())
1392 .with_default_storage_trie(default_trie)
1393 .with_updates(true);
1394
1395 let (updates_tx, updates_rx) = crossbeam_channel::unbounded();
1396 let (_cancel_guard, cancel_rx) = crossbeam_channel::bounded::<()>(0);
1397 let mut task = SparseTrieCacheTask::new_with_trie(
1398 &runtime,
1399 updates_rx,
1400 cancel_rx,
1401 std::sync::mpsc::channel().0,
1402 proof_worker_handle,
1403 proof_result_tx,
1404 proof_result_rx,
1405 SparseTrieTaskMetrics::default(),
1406 trie,
1407 B256::from([0x55; 32]),
1408 TrieNodeEpoch::UNMODIFIED,
1409 1,
1410 );
1411
1412 drop(updates_tx);
1413
1414 let account = B256::from([0x11; 32]);
1415 let slot = B256::from([0x22; 32]);
1416 let account_target = B256::from([0x33; 32]);
1417 let storage_target = B256::from([0x44; 32]);
1418
1419 task.finished_state_updates = true;
1420 task.account_updates.insert(account, LeafUpdate::Touched);
1421 task.storage_updates.entry(account).or_default().insert(slot, LeafUpdate::Touched);
1422 task.pending_account_updates.insert(account, None);
1423 task.fetched_account_targets.insert(account_target, ProofV2TargetParent::NONE);
1424 task.fetched_storage_targets
1425 .entry(account)
1426 .or_default()
1427 .insert(storage_target, ProofV2TargetParent::new(11));
1428 task.in_flight_proof_batches = 1;
1429
1430 assert!(task.ensure_not_stalled(false).is_ok());
1431
1432 let result = ProofResultMessage {
1433 result: Ok(DecodedMultiProofV2::default()),
1434 elapsed: std::time::Duration::ZERO,
1435 state: HashedPostState::default(),
1436 };
1437 task.on_proof_result_message(result).expect("proof result should be ok");
1438
1439 assert_eq!(task.in_flight_proof_batches, 0);
1440 let error = task.ensure_not_stalled(false).expect_err("task should be stalled");
1441 assert!(matches!(error, StateRootTaskError::Stalled));
1442 let error = error.to_string();
1443
1444 assert!(error.contains("sparse trie task stalled"));
1445 assert!(!error.contains("account_targets"));
1446 assert!(!error.contains("storage_targets"));
1447 assert!(!error.contains(&format!("{account:?}")));
1448 assert!(!error.contains(&format!("{account_target:?}")));
1449 assert!(!error.contains(&format!("{storage_target:?}")));
1450 assert!(!error.contains("pending_account_leaves"));
1451 assert!(!error.contains("pending_storage_leaves"));
1452 assert!(!error.contains("pending_account_updates"));
1453 assert!(!error.contains(&format!("{slot:?}")));
1454
1455 drop(task);
1456 drain_sparse_trie_tasks(&runtime);
1457 }
1458
1459 #[test]
1460 fn run_errors_when_cancel_guard_drops_before_updates_finish() {
1461 let runtime = reth_tasks::Runtime::test();
1462 let provider_factory = create_test_provider_factory();
1463 let anchor_hash = init_genesis(&provider_factory).expect("failed to initialize genesis");
1464 let state_provider_factory = OverlayStateProviderFactory::new(
1465 provider_factory,
1466 OverlayManager::<reth_chain_state::EthPrimitives>::default()
1467 .overlay_builder(anchor_hash),
1468 );
1469 let (proof_result_tx, proof_result_rx) = crossbeam_channel::unbounded();
1470 let proof_worker_handle = ProofWorkerHandle::new(
1471 &runtime,
1472 ProofTaskCtx::new(state_provider_factory),
1473 false,
1474 proof_result_tx.clone(),
1475 );
1476
1477 let default_trie = RevealableSparseTrie::blind_from(ArenaParallelSparseTrie::default());
1478 let trie = SparseStateTrie::default()
1479 .with_accounts_trie(default_trie.clone())
1480 .with_default_storage_trie(default_trie)
1481 .with_updates(true);
1482
1483 let (updates_tx, updates_rx) = crossbeam_channel::unbounded();
1484 let (cancel_guard, cancel_rx) = crossbeam_channel::bounded::<()>(0);
1485 let mut task = SparseTrieCacheTask::new_with_trie(
1486 &runtime,
1487 updates_rx,
1488 cancel_rx,
1489 std::sync::mpsc::channel().0,
1490 proof_worker_handle,
1491 proof_result_tx,
1492 proof_result_rx,
1493 SparseTrieTaskMetrics::default(),
1494 trie,
1495 B256::from([0x55; 32]),
1496 TrieNodeEpoch::UNMODIFIED,
1497 1,
1498 );
1499
1500 drop(cancel_guard);
1503
1504 let error = task.run().expect_err("canceled task must return an error");
1505 assert!(matches!(error, StateRootTaskError::Canceled));
1506
1507 drop(updates_tx);
1508 drop(task);
1509 drain_sparse_trie_tasks(&runtime);
1510 }
1511
1512 #[test]
1513 fn run_ignores_hints_queued_after_updates_finish() {
1514 let runtime = reth_tasks::Runtime::test();
1515 let provider_factory = create_test_provider_factory();
1516 let anchor_hash = init_genesis(&provider_factory).expect("failed to initialize genesis");
1517 let state_provider_factory = OverlayStateProviderFactory::new(
1518 provider_factory,
1519 OverlayManager::<reth_chain_state::EthPrimitives>::default()
1520 .overlay_builder(anchor_hash),
1521 );
1522 let (proof_result_tx, proof_result_rx) = crossbeam_channel::unbounded();
1523 let proof_worker_handle = ProofWorkerHandle::new(
1524 &runtime,
1525 ProofTaskCtx::new(state_provider_factory),
1526 false,
1527 proof_result_tx.clone(),
1528 );
1529
1530 let default_trie = RevealableSparseTrie::blind_from(ArenaParallelSparseTrie::default());
1531 let trie = SparseStateTrie::default()
1532 .with_accounts_trie(default_trie.clone())
1533 .with_default_storage_trie(default_trie)
1534 .with_updates(true);
1535
1536 let (updates_tx, updates_rx) = crossbeam_channel::unbounded();
1537 let (cancel_guard, cancel_rx) = crossbeam_channel::bounded::<()>(0);
1538 let mut task = SparseTrieCacheTask::new_with_trie(
1539 &runtime,
1540 updates_rx,
1541 cancel_rx,
1542 std::sync::mpsc::channel().0,
1543 proof_worker_handle,
1544 proof_result_tx,
1545 proof_result_rx,
1546 SparseTrieTaskMetrics::default(),
1547 trie,
1548 B256::from([0x55; 32]),
1549 TrieNodeEpoch::UNMODIFIED,
1550 1,
1551 );
1552
1553 updates_tx.send(StateRootMessage::FinishedStateUpdates).unwrap();
1554 updates_tx.send(StateRootMessage::PrefetchProofs(Default::default())).unwrap();
1555
1556 let wait_start = std::time::Instant::now();
1557 while task.updates.len() < 2 {
1558 assert!(
1559 wait_start.elapsed() < std::time::Duration::from_secs(1),
1560 "hashing task did not queue the test messages"
1561 );
1562 std::thread::yield_now();
1563 }
1564
1565 let (result_tx, result_rx) = std::sync::mpsc::channel();
1566 let handle = std::thread::spawn(move || {
1567 let _ = result_tx.send(task.run());
1568 });
1569
1570 let result = result_rx.recv_timeout(std::time::Duration::from_secs(1));
1571 drop(cancel_guard);
1572 handle.join().unwrap();
1573
1574 assert!(result.expect("state root task stalled on a late hint").is_ok());
1575
1576 drop(updates_tx);
1577 drain_sparse_trie_tasks(&runtime);
1578 }
1579}