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};
22use reth_trie_parallel::{
23 error::StateRootTaskError,
24 proof_task::{
25 AccountMultiproofInput, ProofResultContext, ProofResultMessage, ProofWorkerHandle,
26 },
27};
28use reth_trie_sparse::{
29 errors::{SparseStateTrieErrorKind, SparseTrieErrorKind, SparseTrieResult},
30 ArenaParallelSparseTrie, DeferredDrops, LeafUpdate, RevealableSparseTrie, SparseStateTrie,
31 SparseTrie,
32};
33use tracing::{debug, debug_span, error, instrument, trace_span};
34
35pub(super) struct SparseTrieCacheTask<A = ArenaParallelSparseTrie, S = ArenaParallelSparseTrie> {
37 proof_result_tx: CrossbeamSender<ProofResultMessage>,
39 proof_result_rx: CrossbeamReceiver<ProofResultMessage>,
41 updates: CrossbeamReceiver<SparseTrieTaskMessage>,
43 cancel_rx: CrossbeamReceiver<()>,
47 final_hashed_state_tx: Option<std::sync::mpsc::Sender<HashedPostState>>,
49 trie: SparseStateTrie<A, S>,
51 parent_state_root: B256,
53 proof_worker_handle: ProofWorkerHandle,
55
56 chunk_size: usize,
59 max_targets_for_chunking: usize,
63
64 account_updates: B256Map<LeafUpdate>,
66 storage_updates: B256Map<B256Map<LeafUpdate>>,
68
69 new_account_updates: B256Map<LeafUpdate>,
71 new_storage_updates: B256Map<B256Map<LeafUpdate>>,
73 pending_account_updates: B256Map<Option<Option<Account>>>,
87 fetched_account_targets: B256Map<u8>,
90 fetched_storage_targets: B256Map<B256Map<u8>>,
93 account_rlp_buf: Vec<u8>,
95 finished_state_updates: bool,
97 account_cache_hits: u64,
99 account_cache_misses: u64,
101 storage_cache_hits: u64,
103 storage_cache_misses: u64,
105 pending_targets: PendingTargets,
107 in_flight_proof_batches: usize,
109 pending_updates: usize,
112 final_hashed_state: HashedPostState,
118
119 metrics: SparseTrieTaskMetrics,
121}
122
123impl<A, S> SparseTrieCacheTask<A, S>
124where
125 A: SparseTrie + Default,
126 S: SparseTrie + Default + Clone,
127{
128 #[expect(clippy::too_many_arguments)]
130 pub(super) fn new_with_trie(
131 executor: &Runtime,
132 updates: CrossbeamReceiver<StateRootMessage>,
133 cancel_rx: CrossbeamReceiver<()>,
134 final_hashed_state_tx: std::sync::mpsc::Sender<HashedPostState>,
135 proof_worker_handle: ProofWorkerHandle,
136 metrics: SparseTrieTaskMetrics,
137 trie: SparseStateTrie<A, S>,
138 parent_state_root: B256,
139 chunk_size: usize,
140 ) -> Self {
141 let (proof_result_tx, proof_result_rx) = crossbeam_channel::unbounded();
142 let (hashed_state_tx, hashed_state_rx) = crossbeam_channel::unbounded();
143
144 let parent_span = tracing::Span::current();
145 let hashing_metrics = metrics.clone();
146 executor.spawn_blocking_named("trie-hashing", move || {
147 let _span = trace_span!(parent: parent_span, "run_hashing_task").entered();
148 Self::run_hashing_task(updates, hashed_state_tx, hashing_metrics)
149 });
150
151 Self {
152 proof_result_tx,
153 proof_result_rx,
154 updates: hashed_state_rx,
155 cancel_rx,
156 proof_worker_handle,
157 final_hashed_state_tx: Some(final_hashed_state_tx),
158 trie,
159 parent_state_root,
160 chunk_size,
161 max_targets_for_chunking: DEFAULT_MAX_TARGETS_FOR_CHUNKING,
162 account_updates: Default::default(),
163 storage_updates: Default::default(),
164 new_account_updates: Default::default(),
165 new_storage_updates: Default::default(),
166 pending_account_updates: Default::default(),
167 fetched_account_targets: Default::default(),
168 fetched_storage_targets: Default::default(),
169 account_rlp_buf: Vec::with_capacity(TRIE_ACCOUNT_RLP_MAX_SIZE),
170 finished_state_updates: Default::default(),
171 account_cache_hits: 0,
172 account_cache_misses: 0,
173 storage_cache_hits: 0,
174 storage_cache_misses: 0,
175 pending_targets: Default::default(),
176 in_flight_proof_batches: 0,
177 pending_updates: Default::default(),
178 final_hashed_state: Default::default(),
179 metrics,
180 }
181 }
182
183 fn run_hashing_task(
186 updates: CrossbeamReceiver<StateRootMessage>,
187 hashed_state_tx: CrossbeamSender<SparseTrieTaskMessage>,
188 metrics: SparseTrieTaskMetrics,
189 ) {
190 let mut total_idle_time = std::time::Duration::ZERO;
191 let mut idle_start = Instant::now();
192
193 while let Ok(message) = updates.recv() {
194 total_idle_time += idle_start.elapsed();
195
196 let msg = match message {
197 StateRootMessage::PrefetchProofs(targets) => {
198 SparseTrieTaskMessage::PrefetchProofs(targets)
199 }
200 StateRootMessage::StateUpdate(state) => {
201 let _span = trace_span!(target: "engine::tree::payload_processor::sparse_trie", "hashing_state_update", n = state.len()).entered();
202 let hashed = evm_state_to_hashed_post_state(state);
203 SparseTrieTaskMessage::HashedState(hashed)
204 }
205 StateRootMessage::FinishedStateUpdates => {
206 SparseTrieTaskMessage::FinishedStateUpdates
207 }
208 StateRootMessage::HashedStateUpdate(state) => {
209 SparseTrieTaskMessage::HashedState(state)
210 }
211 };
212 if hashed_state_tx.send(msg).is_err() {
213 break;
214 }
215
216 idle_start = Instant::now();
217 }
218
219 metrics.hashing_task_idle_time_seconds.record(total_idle_time.as_secs_f64());
220 }
221
222 pub(super) fn into_trie_for_reuse(self) -> (SparseStateTrie<A, S>, DeferredDrops) {
226 let Self { mut trie, .. } = self;
227 let deferred = trie.take_deferred_drops();
228 (trie, deferred)
229 }
230
231 pub(super) fn into_cleared_trie(self) -> (SparseStateTrie<A, S>, DeferredDrops) {
236 let Self { mut trie, .. } = self;
237 trie.clear();
238 let deferred = trie.take_deferred_drops();
239 (trie, deferred)
240 }
241
242 #[instrument(
249 name = "SparseTrieCacheTask::run",
250 level = "debug",
251 target = "engine::tree::payload_processor::sparse_trie",
252 skip_all
253 )]
254 pub(super) fn run(&mut self) -> Result<StateRootComputeOutcome, StateRootTaskError> {
255 let now = Instant::now();
256
257 let mut total_idle_time = std::time::Duration::ZERO;
258 let mut idle_start = Instant::now();
259 let mut done = false;
260
261 while !self.finished_state_updates {
265 let mut t = Instant::now();
266 crossbeam_channel::select_biased! {
267 recv(self.updates) -> message => {
268 let wake = Instant::now();
269 total_idle_time += wake.duration_since(idle_start);
270 self.metrics
271 .sparse_trie_channel_wait_duration_histogram
272 .record(wake.duration_since(t));
273
274 let update = message.map_err(|_| StateRootTaskError::Other(
275 "updates channel disconnected before state root calculation".to_string(),
276 ))?;
277 self.on_message(update);
278 self.pending_updates += 1;
279 }
280 recv(self.proof_result_rx) -> message => {
281 let wake = Instant::now();
282 total_idle_time += wake.duration_since(idle_start);
283 self.metrics
284 .sparse_trie_channel_wait_duration_histogram
285 .record(wake.duration_since(t));
286 t = wake;
287
288 let Ok(result) = message else {
289 unreachable!("we own the sender half")
290 };
291 self.on_proof_results(result, &mut t)?;
292 },
293 recv(self.cancel_rx) -> _ => return Err(StateRootTaskError::Canceled),
294 }
295
296 done = self.make_progress()?;
297 idle_start = Instant::now();
298 }
299
300 while !done {
305 let mut t = Instant::now();
306 crossbeam_channel::select_biased! {
307 recv(self.proof_result_rx) -> message => {
308 let wake = Instant::now();
309 total_idle_time += wake.duration_since(idle_start);
310 self.metrics
311 .sparse_trie_channel_wait_duration_histogram
312 .record(wake.duration_since(t));
313 t = wake;
314
315 let Ok(result) = message else {
316 unreachable!("we own the sender half")
317 };
318 self.on_proof_results(result, &mut t)?;
319 },
320 recv(self.cancel_rx) -> _ => return Err(StateRootTaskError::Canceled),
321 }
322
323 done = self.make_progress()?;
324 idle_start = Instant::now();
325 }
326
327 self.metrics.sparse_trie_idle_time_seconds.record(total_idle_time.as_secs_f64());
328
329 debug!(target: "engine::root", "All proofs processed, ending calculation");
330
331 let start = Instant::now();
332 let (state_root, trie_updates) = match self.trie.root_with_updates() {
333 Ok(result) => result,
334 Err(err)
335 if matches!(
336 err.kind(),
337 SparseStateTrieErrorKind::Sparse(SparseTrieErrorKind::Blind)
338 ) =>
339 {
340 (self.parent_state_root, TrieUpdates::default())
344 }
345 Err(err) => {
346 return Err(StateRootTaskError::Other(format!(
347 "could not calculate state root: {err:?}"
348 )))
349 }
350 };
351
352 #[cfg(feature = "trie-debug")]
353 let debug_recorders = self.trie.take_debug_recorders();
354 let changed_paths = Some(Arc::new(self.trie.take_changed_paths().unwrap_or_default()));
355
356 let end = Instant::now();
357 self.metrics.sparse_trie_final_update_duration_histogram.record(end.duration_since(start));
358 self.metrics.sparse_trie_total_duration_histogram.record(end.duration_since(now));
359
360 self.metrics.sparse_trie_account_cache_hits.record(self.account_cache_hits as f64);
361 self.metrics.sparse_trie_account_cache_misses.record(self.account_cache_misses as f64);
362 self.metrics.sparse_trie_storage_cache_hits.record(self.storage_cache_hits as f64);
363 self.metrics.sparse_trie_storage_cache_misses.record(self.storage_cache_misses as f64);
364 self.account_cache_hits = 0;
365 self.account_cache_misses = 0;
366 self.storage_cache_hits = 0;
367 self.storage_cache_misses = 0;
368
369 Ok(StateRootComputeOutcome {
370 state_root,
371 trie_updates: Arc::new(trie_updates),
372 changed_paths,
373 #[cfg(feature = "trie-debug")]
374 debug_recorders,
375 })
376 }
377
378 fn on_proof_results(
381 &mut self,
382 message: ProofResultMessage,
383 t: &mut Instant,
384 ) -> Result<(), StateRootTaskError> {
385 let mut result = self.on_proof_result_message(message)?;
386 while let Ok(next) = self.proof_result_rx.try_recv() {
387 let res = self.on_proof_result_message(next)?;
388 result.extend(res);
389 }
390
391 let phase_end = Instant::now();
392 self.metrics
393 .sparse_trie_proof_coalesce_duration_histogram
394 .record(phase_end.duration_since(*t));
395 *t = phase_end;
396
397 self.on_proof_result(result)?;
398 self.metrics.sparse_trie_reveal_multiproof_duration_histogram.record(t.elapsed());
399 Ok(())
400 }
401
402 fn make_progress(&mut self) -> Result<bool, StateRootTaskError> {
407 let updates_queued = !self.finished_state_updates && !self.updates.is_empty();
408
409 if !updates_queued && self.proof_result_rx.is_empty() {
410 self.dispatch_pending_targets()?;
413 let t = Instant::now();
414 self.process_new_updates()?;
415 self.promote_pending_account_updates()?;
416 self.metrics.sparse_trie_process_updates_duration_histogram.record(t.elapsed());
417
418 if self.finished_state_updates && !self.has_pending_sparse_trie_updates() {
419 return Ok(true);
420 }
421
422 self.dispatch_pending_targets()?;
423 self.ensure_not_stalled(updates_queued)?;
424
425 if self.proof_result_rx.is_empty() {
428 self.trie.calculate_subtries();
429 }
430 } else if !updates_queued {
431 let t = Instant::now();
433 self.process_new_updates()?;
434 self.metrics.sparse_trie_process_updates_duration_histogram.record(t.elapsed());
435 self.dispatch_pending_targets()?;
436 } else if self.pending_targets.len() > self.chunk_size {
437 self.dispatch_pending_targets()?;
439 }
440 Ok(false)
441 }
442
443 fn on_message(&mut self, message: SparseTrieTaskMessage) {
445 match message {
446 SparseTrieTaskMessage::PrefetchProofs(targets) => self.on_prewarm_targets(targets),
447 SparseTrieTaskMessage::HashedState(hashed_state) => {
448 self.on_hashed_state_update(hashed_state)
449 }
450 SparseTrieTaskMessage::FinishedStateUpdates => {
451 let _ = self
452 .final_hashed_state_tx
453 .take()
454 .unwrap()
455 .send(core::mem::take(&mut self.final_hashed_state));
456 self.finished_state_updates = true
457 }
458 }
459 }
460
461 #[instrument(
462 level = "trace",
463 target = "engine::tree::payload_processor::sparse_trie",
464 skip_all
465 )]
466 fn on_prewarm_targets(&mut self, targets: MultiProofTargetsV2) {
467 for target in targets.account_targets {
468 self.new_account_updates.entry(target.key()).or_insert(LeafUpdate::Touched);
470 }
471
472 for (address, slots) in targets.storage_targets {
473 if !slots.is_empty() {
474 let new_updates = self.new_storage_updates.entry(address).or_default();
476 for slot in slots {
477 new_updates.entry(slot.key()).or_insert(LeafUpdate::Touched);
479 }
480 }
481
482 self.new_account_updates.entry(address).or_insert(LeafUpdate::Touched);
485 }
486 }
487
488 #[instrument(
490 level = "trace",
491 target = "engine::tree::payload_processor::sparse_trie",
492 skip_all
493 )]
494 fn on_hashed_state_update(&mut self, hashed_state_update: HashedPostState) {
495 for (&address, storage) in &hashed_state_update.storages {
496 if !storage.storage.is_empty() {
497 let new_updates = self.new_storage_updates.entry(address).or_default();
499 let mut existing_updates = self.storage_updates.get_mut(&address);
500
501 for (&slot, &value) in &storage.storage {
502 self.trie.record_slot_touch(address, slot);
503
504 let encoded = if value.is_zero() {
505 Vec::new()
506 } else {
507 alloy_rlp::encode_fixed_size(&value).to_vec()
508 };
509 new_updates.insert(slot, LeafUpdate::Changed(encoded));
510
511 if let Some(ref mut existing) = existing_updates {
513 existing.remove(&slot);
514 }
515 }
516 }
517
518 self.new_account_updates.entry(address).or_insert(LeafUpdate::Touched);
521
522 self.pending_account_updates.entry(address).or_insert(None);
525 }
526
527 for (&address, &account) in &hashed_state_update.accounts {
528 self.trie.record_account_touch(address);
529
530 self.new_account_updates.insert(address, LeafUpdate::Touched);
535
536 self.pending_account_updates.insert(address, Some(account));
539 }
540
541 self.final_hashed_state.extend(hashed_state_update);
542 }
543
544 fn on_proof_result(&mut self, result: DecodedMultiProofV2) -> Result<(), StateRootTaskError> {
545 self.trie
546 .reveal_decoded_multiproof_v2(result)
547 .map_err(|e| StateRootTaskError::Other(format!("could not reveal multiproof: {e:?}")))
548 }
549
550 fn on_proof_result_message(
551 &mut self,
552 message: ProofResultMessage,
553 ) -> Result<DecodedMultiProofV2, StateRootTaskError> {
554 debug_assert!(
555 self.in_flight_proof_batches > 0,
556 "received proof result without an in-flight proof batch"
557 );
558 self.in_flight_proof_batches = self.in_flight_proof_batches.saturating_sub(1);
559 message.result
560 }
561
562 fn process_new_updates(&mut self) -> SparseTrieResult<()> {
563 if self.pending_updates == 0 {
564 return Ok(());
565 }
566
567 let _span = debug_span!("process_new_updates").entered();
568 self.pending_updates = 0;
569
570 self.process_leaf_updates(true)?;
572
573 for (address, mut new) in self.new_storage_updates.drain() {
574 match self.storage_updates.entry(address) {
575 Entry::Vacant(entry) => {
576 entry.insert(new); }
578 Entry::Occupied(mut entry) => {
579 let updates = entry.get_mut();
580 for (slot, new) in new.drain() {
581 match updates.entry(slot) {
582 Entry::Occupied(mut slot_entry) => {
583 if new.is_changed() {
584 slot_entry.insert(new);
585 }
586 }
587 Entry::Vacant(slot_entry) => {
588 slot_entry.insert(new);
589 }
590 }
591 }
592 }
593 }
594 }
595
596 for (address, new) in self.new_account_updates.drain() {
597 match self.account_updates.entry(address) {
598 Entry::Occupied(mut entry) => {
599 if new.is_changed() {
600 entry.insert(new);
601 }
602 }
603 Entry::Vacant(entry) => {
604 entry.insert(new);
605 }
606 }
607 }
608
609 Ok(())
610 }
611
612 #[instrument(
615 level = "trace",
616 target = "engine::tree::payload_processor::sparse_trie",
617 skip_all
618 )]
619 fn process_leaf_updates(&mut self, new: bool) -> SparseTrieResult<()> {
620 let storage_updates =
621 if new { &mut self.new_storage_updates } else { &mut self.storage_updates };
622
623 let span = trace_span!("process_storage_leaf_updates").entered();
625 for (address, updates) in storage_updates {
626 if updates.is_empty() {
627 continue;
628 }
629 let _enter = trace_span!(target: "engine::tree::payload_processor::sparse_trie", parent: &span, "storage_trie_leaf_updates", a=%address).entered();
630
631 let trie = self.trie.get_or_create_storage_trie_mut(*address);
632 let fetched = self.fetched_storage_targets.entry(*address).or_default();
633 let mut targets = Vec::new();
634
635 let updates_len_before = updates.len();
636 trie.update_leaves(updates, |path, min_len| match fetched.entry(path) {
637 Entry::Occupied(mut entry) => {
638 if min_len < *entry.get() {
639 entry.insert(min_len);
640 targets.push(ProofV2Target::new(path).with_min_len(min_len));
641 }
642 }
643 Entry::Vacant(entry) => {
644 entry.insert(min_len);
645 targets.push(ProofV2Target::new(path).with_min_len(min_len));
646 }
647 })?;
648 let updates_len_after = updates.len();
649 self.storage_cache_hits += (updates_len_before - updates_len_after) as u64;
650 self.storage_cache_misses += updates_len_after as u64;
651
652 if !targets.is_empty() {
653 self.pending_targets.extend_storage_targets(address, targets);
654 }
655 }
656
657 drop(span);
658
659 self.process_account_leaf_updates(new)?;
661
662 Ok(())
663 }
664
665 #[instrument(
669 level = "trace",
670 target = "engine::tree::payload_processor::sparse_trie",
671 skip_all
672 )]
673 fn process_account_leaf_updates(&mut self, new: bool) -> SparseTrieResult<bool> {
674 let account_updates =
675 if new { &mut self.new_account_updates } else { &mut self.account_updates };
676
677 let updates_len_before = account_updates.len();
678
679 self.trie.trie_mut().update_leaves(account_updates, |target, min_len| {
680 match self.fetched_account_targets.entry(target) {
681 Entry::Occupied(mut entry) => {
682 if min_len < *entry.get() {
683 entry.insert(min_len);
684 self.pending_targets
685 .push_account_target(ProofV2Target::new(target).with_min_len(min_len));
686 }
687 }
688 Entry::Vacant(entry) => {
689 entry.insert(min_len);
690 self.pending_targets
691 .push_account_target(ProofV2Target::new(target).with_min_len(min_len));
692 }
693 }
694 })?;
695
696 let updates_len_after = account_updates.len();
697 self.account_cache_hits += (updates_len_before - updates_len_after) as u64;
698 self.account_cache_misses += updates_len_after as u64;
699
700 Ok(updates_len_after < updates_len_before)
701 }
702
703 fn compute_drained_storage_roots(&mut self) {
712 let addresses_to_compute_roots: Vec<_> = self
713 .storage_updates
714 .iter()
715 .filter_map(|(address, updates)| updates.is_empty().then_some(*address))
716 .collect();
717
718 struct SendStorageTriePtr<S>(*mut RevealableSparseTrie<S>);
719 unsafe impl<S: Send> Send for SendStorageTriePtr<S> {}
722
723 let mut tries_to_compute_roots: Vec<(B256, SendStorageTriePtr<S>)> =
724 Vec::with_capacity(addresses_to_compute_roots.len());
725 for address in addresses_to_compute_roots {
726 if let Some(trie) = self.trie.storage_tries_mut().get_mut(&address) &&
727 !trie.is_root_cached()
728 {
729 tries_to_compute_roots.push((address, SendStorageTriePtr(trie)));
730 }
731 }
732
733 if tries_to_compute_roots.is_empty() {
734 return;
735 }
736
737 let parent_span =
738 debug_span!("compute_drained_storage_roots", n = tries_to_compute_roots.len());
739 tries_to_compute_roots.into_par_iter().for_each(|(address, SendStorageTriePtr(trie))| {
740 let span = if tracing::enabled!(tracing::Level::TRACE) {
741 debug_span!(
742 target: "engine::tree::payload_processor::sparse_trie",
743 parent: &parent_span,
744 "storage_root",
745 ?address
746 )
747 } else {
748 debug_span!(
749 target: "engine::tree::payload_processor::sparse_trie",
750 parent: &parent_span,
751 "storage_root",
752 )
753 };
754 let _enter = span.entered();
755 unsafe { (*trie).root().expect("updates are drained, trie should be revealed by now") };
762 });
763 }
764
765 #[instrument(
769 level = "trace",
770 target = "engine::tree::payload_processor::sparse_trie",
771 skip_all
772 )]
773 fn promote_pending_account_updates(&mut self) -> SparseTrieResult<()> {
774 self.process_leaf_updates(false)?;
775
776 if self.pending_account_updates.is_empty() {
777 return Ok(());
778 }
779
780 self.compute_drained_storage_roots();
781
782 loop {
783 let span = trace_span!("promote_updates", promoted = tracing::field::Empty).entered();
784 let account_rlp_buf = &mut self.account_rlp_buf;
786 let mut num_promoted = 0;
787 self.pending_account_updates.retain(|addr, account| {
788 if let Some(updates) = self.storage_updates.get(addr) {
789 if !updates.is_empty() {
790 return true;
792 } else if let Some(account) = account.take() {
793 let storage_root = self.trie.storage_root(addr).expect("updates are drained, storage trie should be revealed by now");
794 let encoded = encode_account_leaf_value(account, storage_root, account_rlp_buf);
795 self.account_updates.insert(*addr, LeafUpdate::Changed(encoded));
796 num_promoted += 1;
797 return false;
798 }
799 }
800
801 let trie_account = match self.account_updates.get(addr) {
803 Some(LeafUpdate::Changed(encoded)) => {
804 Some(encoded).filter(|encoded| !encoded.is_empty())
805 }
806 Some(LeafUpdate::Touched) => return true,
808 None => self.trie.get_account_value(addr),
809 };
810
811 let trie_account = trie_account.map(|value| TrieAccount::decode(&mut &value[..]).expect("invalid account RLP"));
812
813 let (account, storage_root) = if let Some(account) = account.take() {
814 let storage_root = trie_account.map(|account| account.storage_root).unwrap_or(EMPTY_ROOT_HASH);
819
820 (account, storage_root)
821 } else {
822 (trie_account.map(Into::into), self.trie.storage_root(addr).expect("account had storage updates that were applied to its trie, storage root must be revealed by now"))
823 };
824
825 let encoded = encode_account_leaf_value(account, storage_root, account_rlp_buf);
826 self.account_updates.insert(*addr, LeafUpdate::Changed(encoded));
827 num_promoted += 1;
828
829 false
830 });
831 span.record("promoted", num_promoted);
832 drop(span);
833
834 if num_promoted == 0 || !self.process_account_leaf_updates(false)? {
839 break
840 }
841 }
842
843 Ok(())
844 }
845
846 fn dispatch_pending_targets(&mut self) -> Result<(), StateRootTaskError> {
847 if self.pending_targets.is_empty() {
848 return Ok(())
849 }
850
851 let _span = trace_span!("dispatch_pending_targets").entered();
852 let (targets, chunking_length) = self.pending_targets.take();
853 let mut dispatch_error = None;
854 dispatch_with_chunking(
855 targets,
856 chunking_length,
857 self.chunk_size,
858 self.max_targets_for_chunking,
859 self.proof_worker_handle.has_multiple_idle_account_workers(),
860 self.proof_worker_handle.has_multiple_idle_storage_workers(),
861 MultiProofTargetsV2::chunks,
862 |proof_targets| {
863 if dispatch_error.is_some() {
864 return;
865 }
866
867 match self.proof_worker_handle.dispatch_account_multiproof(AccountMultiproofInput {
868 targets: proof_targets,
869 proof_result_sender: ProofResultContext::new(
870 self.proof_result_tx.clone(),
871 HashedPostState::default(),
872 Instant::now(),
873 ),
874 }) {
875 Ok(()) => {
876 self.in_flight_proof_batches += 1;
877 }
878 Err(e) => {
879 error!("failed to dispatch account multiproof: {e:?}");
880 dispatch_error = Some(StateRootTaskError::ProofDispatch(e));
881 }
882 }
883 },
884 );
885
886 if let Some(error) = dispatch_error {
887 return Err(error)
888 }
889
890 Ok(())
891 }
892
893 fn has_pending_sparse_trie_updates(&self) -> bool {
894 !self.account_updates.is_empty() ||
895 self.storage_updates.values().any(|updates| !updates.is_empty()) ||
896 !self.pending_account_updates.is_empty()
897 }
898
899 fn ensure_not_stalled(&self, updates_queued: bool) -> Result<(), StateRootTaskError> {
907 if self.finished_state_updates &&
908 !updates_queued &&
909 self.pending_updates == 0 &&
910 self.pending_targets.is_empty() &&
911 self.in_flight_proof_batches == 0 &&
912 self.proof_result_rx.is_empty() &&
913 self.has_pending_sparse_trie_updates()
914 {
915 const MAX_STALLED_PROOF_TARGETS_TO_LOG: usize = 5;
916
917 let mut account_targets = self
918 .account_updates
919 .keys()
920 .map(|target| (*target, self.fetched_account_targets.get(target).copied()))
921 .collect::<Vec<_>>();
922 account_targets.sort_unstable();
923 let account_targets_truncated =
924 account_targets.len().saturating_sub(MAX_STALLED_PROOF_TARGETS_TO_LOG);
925 account_targets.truncate(MAX_STALLED_PROOF_TARGETS_TO_LOG);
926
927 let mut storage_targets = self
928 .storage_updates
929 .iter()
930 .flat_map(|(address, updates)| {
931 let fetched_targets = self.fetched_storage_targets.get(address);
932 updates.keys().map(move |target| {
933 (
934 *address,
935 *target,
936 fetched_targets.and_then(|targets| targets.get(target)).copied(),
937 )
938 })
939 })
940 .collect::<Vec<_>>();
941 storage_targets.sort_unstable();
942 let storage_targets_truncated =
943 storage_targets.len().saturating_sub(MAX_STALLED_PROOF_TARGETS_TO_LOG);
944 storage_targets.truncate(MAX_STALLED_PROOF_TARGETS_TO_LOG);
945
946 error!(
947 ?account_targets,
948 account_targets_truncated,
949 ?storage_targets,
950 storage_targets_truncated,
951 "sparse trie task stalled: pending updates remain but no proof targets are queued or in flight"
952 );
953
954 return Err(StateRootTaskError::Stalled)
955 }
956
957 Ok(())
958 }
959}
960
961#[derive(Metrics, Clone)]
963#[metrics(scope = "tree.root")]
964pub(super) struct SparseTrieTaskMetrics {
965 pub(super) sparse_trie_reveal_multiproof_duration_histogram: Histogram,
967 pub(super) sparse_trie_proof_coalesce_duration_histogram: Histogram,
969 pub(super) sparse_trie_channel_wait_duration_histogram: Histogram,
971 pub(super) sparse_trie_process_updates_duration_histogram: Histogram,
973 pub(super) sparse_trie_final_update_duration_histogram: Histogram,
975 pub(super) sparse_trie_total_duration_histogram: Histogram,
977 pub(super) into_trie_for_reuse_duration_histogram: Histogram,
979 pub(super) sparse_trie_cache_wait_duration_histogram: Histogram,
981 pub(super) sparse_trie_idle_time_seconds: Histogram,
984 pub(super) hashing_task_idle_time_seconds: Histogram,
987
988 pub(super) sparse_trie_account_cache_hits: Histogram,
990 pub(super) sparse_trie_account_cache_misses: Histogram,
992 pub(super) sparse_trie_storage_cache_hits: Histogram,
994 pub(super) sparse_trie_storage_cache_misses: Histogram,
996
997 pub(super) sparse_trie_retained_memory_bytes: Gauge,
999 pub(super) sparse_trie_retained_storage_tries: Gauge,
1001}
1002
1003const DEFAULT_MAX_TARGETS_FOR_CHUNKING: usize = 300;
1006
1007#[expect(clippy::too_many_arguments)]
1010fn dispatch_with_chunking<T, I>(
1011 items: T,
1012 chunking_len: usize,
1013 chunk_size: usize,
1014 max_targets_for_chunking: usize,
1015 has_multiple_idle_account_workers: bool,
1016 has_multiple_idle_storage_workers: bool,
1017 chunker: impl FnOnce(T, usize) -> I,
1018 mut dispatch: impl FnMut(T),
1019) where
1020 I: IntoIterator<Item = T>,
1021{
1022 let has_full_chunks = chunking_len >= chunk_size.saturating_mul(2);
1023 let should_chunk = chunking_len > max_targets_for_chunking ||
1024 (has_full_chunks &&
1025 (has_multiple_idle_account_workers || has_multiple_idle_storage_workers));
1026
1027 if should_chunk && chunking_len > chunk_size {
1028 for chunk in chunker(items, chunk_size) {
1029 dispatch(chunk);
1030 }
1031 return;
1032 }
1033
1034 dispatch(items);
1035}
1036
1037fn encode_account_leaf_value(
1039 account: Option<Account>,
1040 storage_root: B256,
1041 account_rlp_buf: &mut Vec<u8>,
1042) -> Vec<u8> {
1043 if account.is_none_or(|account| account.is_empty()) && storage_root == EMPTY_ROOT_HASH {
1044 return Vec::new();
1045 }
1046
1047 account_rlp_buf.clear();
1048 account.unwrap_or_default().into_trie_account(storage_root).encode(account_rlp_buf);
1049 account_rlp_buf.clone()
1050}
1051
1052#[derive(Default)]
1054struct PendingTargets {
1055 targets: MultiProofTargetsV2,
1057 len: usize,
1059}
1060
1061impl PendingTargets {
1062 const fn len(&self) -> usize {
1064 self.len
1065 }
1066
1067 const fn is_empty(&self) -> bool {
1069 self.len == 0
1070 }
1071
1072 fn take(&mut self) -> (MultiProofTargetsV2, usize) {
1074 (std::mem::take(&mut self.targets), std::mem::take(&mut self.len))
1075 }
1076
1077 fn push_account_target(&mut self, target: ProofV2Target) {
1079 self.targets.account_targets.push(target);
1080 self.len += 1;
1081 }
1082
1083 fn extend_storage_targets(&mut self, address: &B256, targets: Vec<ProofV2Target>) {
1085 self.len += targets.len();
1086 self.targets.storage_targets.entry(*address).or_default().extend(targets);
1087 }
1088}
1089
1090enum SparseTrieTaskMessage {
1092 HashedState(HashedPostState),
1094 PrefetchProofs(MultiProofTargetsV2),
1096 FinishedStateUpdates,
1098}
1099
1100#[cfg(test)]
1101mod tests {
1102 use super::*;
1103 use alloy_primitives::{keccak256, Address, B256, U256};
1104 use reth_provider::{
1105 providers::{OverlayBuilder, OverlayStateProviderFactory},
1106 test_utils::create_test_provider_factory,
1107 ChainSpecProvider,
1108 };
1109 use reth_trie_db::ChangesetCache;
1110 use reth_trie_parallel::proof_task::ProofTaskCtx;
1111 use reth_trie_sparse::ArenaParallelSparseTrie;
1112
1113 #[test]
1114 fn test_run_hashing_task_hashed_state_update_forwards() {
1115 let (updates_tx, updates_rx) = crossbeam_channel::unbounded();
1116 let (hashed_state_tx, hashed_state_rx) = crossbeam_channel::unbounded();
1117
1118 let address = keccak256(Address::random());
1119 let slot = keccak256(U256::from(42).to_be_bytes::<32>());
1120 let value = U256::from(999);
1121
1122 let mut hashed_state = HashedPostState::default();
1123 hashed_state.accounts.insert(
1124 address,
1125 Some(Account { balance: U256::from(100), nonce: 1, bytecode_hash: None }),
1126 );
1127 let mut storage = reth_trie::HashedStorage::new(false);
1128 storage.storage.insert(slot, value);
1129 hashed_state.storages.insert(address, storage);
1130
1131 let expected_state = hashed_state.clone();
1132
1133 let handle = std::thread::spawn(move || {
1134 SparseTrieCacheTask::<ArenaParallelSparseTrie, ArenaParallelSparseTrie>::run_hashing_task(
1135 updates_rx,
1136 hashed_state_tx,
1137 SparseTrieTaskMetrics::default(),
1138 );
1139 });
1140
1141 updates_tx.send(StateRootMessage::HashedStateUpdate(hashed_state)).unwrap();
1142 updates_tx.send(StateRootMessage::FinishedStateUpdates).unwrap();
1143 drop(updates_tx);
1144
1145 let SparseTrieTaskMessage::HashedState(received) = hashed_state_rx.recv().unwrap() else {
1146 panic!("expected HashedState message");
1147 };
1148
1149 let account = received.accounts.get(&address).unwrap().unwrap();
1150 assert_eq!(account.balance, expected_state.accounts[&address].unwrap().balance);
1151 assert_eq!(account.nonce, expected_state.accounts[&address].unwrap().nonce);
1152
1153 let storage = received.storages.get(&address).unwrap();
1154 assert_eq!(*storage.storage.get(&slot).unwrap(), value);
1155
1156 let second = hashed_state_rx.recv().unwrap();
1157 assert!(matches!(second, SparseTrieTaskMessage::FinishedStateUpdates));
1158
1159 assert!(hashed_state_rx.recv().is_err());
1160 handle.join().unwrap();
1161 }
1162
1163 #[test]
1164 fn test_encode_account_leaf_value_empty_account_and_empty_root_is_empty() {
1165 let mut account_rlp_buf = vec![0xAB];
1166 let encoded = encode_account_leaf_value(None, EMPTY_ROOT_HASH, &mut account_rlp_buf);
1167
1168 assert!(encoded.is_empty());
1169 assert_eq!(account_rlp_buf, vec![0xAB]);
1171 }
1172
1173 #[test]
1174 fn test_encode_account_leaf_value_non_empty_account_is_rlp() {
1175 let storage_root = B256::from([0x99; 32]);
1176 let account = Some(Account {
1177 nonce: 7,
1178 balance: U256::from(42),
1179 bytecode_hash: Some(B256::from([0xAA; 32])),
1180 });
1181 let mut account_rlp_buf = vec![0x00, 0x01];
1182
1183 let encoded = encode_account_leaf_value(account, storage_root, &mut account_rlp_buf);
1184 let decoded = TrieAccount::decode(&mut &encoded[..]).expect("valid account RLP");
1185
1186 assert_eq!(decoded.nonce, 7);
1187 assert_eq!(decoded.balance, U256::from(42));
1188 assert_eq!(decoded.storage_root, storage_root);
1189 assert_eq!(account_rlp_buf, encoded);
1190 }
1191
1192 #[test]
1193 fn run_returns_parent_root_without_revealing_blind_trie_when_no_state_updates() {
1194 let runtime = reth_tasks::Runtime::test();
1195 let provider_factory = create_test_provider_factory();
1196 let anchor_hash = provider_factory.chain_spec().genesis_hash();
1197 let overlay_factory = OverlayStateProviderFactory::new(
1198 provider_factory,
1199 OverlayBuilder::<reth_chain_state::EthPrimitives>::new(
1200 anchor_hash,
1201 ChangesetCache::new(),
1202 ),
1203 );
1204 let proof_worker_handle =
1205 ProofWorkerHandle::new(&runtime, ProofTaskCtx::new(overlay_factory), false);
1206
1207 let default_trie = RevealableSparseTrie::blind_from(ArenaParallelSparseTrie::default());
1208 let trie = SparseStateTrie::default()
1209 .with_accounts_trie(default_trie.clone())
1210 .with_default_storage_trie(default_trie)
1211 .with_updates(true);
1212
1213 let parent_state_root = B256::from([0x55; 32]);
1214 let (updates_tx, updates_rx) = crossbeam_channel::unbounded();
1215 let (_cancel_guard, cancel_rx) = crossbeam_channel::bounded::<()>(0);
1216 let mut task = SparseTrieCacheTask::new_with_trie(
1217 &runtime,
1218 updates_rx,
1219 cancel_rx,
1220 std::sync::mpsc::channel().0,
1221 proof_worker_handle,
1222 SparseTrieTaskMetrics::default(),
1223 trie,
1224 parent_state_root,
1225 1,
1226 );
1227
1228 updates_tx.send(StateRootMessage::FinishedStateUpdates).unwrap();
1229 drop(updates_tx);
1230
1231 let outcome = task.run().expect("state root computation should succeed");
1232
1233 assert_eq!(outcome.state_root, parent_state_root);
1234 assert!(outcome.trie_updates.is_empty());
1235 assert!(task.trie.state_trie_ref().is_none(), "blind trie should not be revealed");
1236 }
1237
1238 #[test]
1239 fn stall_check_waits_for_in_flight_proofs_then_reports_pending_updates() {
1240 let runtime = reth_tasks::Runtime::test();
1241 let provider_factory = create_test_provider_factory();
1242 let anchor_hash = provider_factory.chain_spec().genesis_hash();
1243 let overlay_factory = OverlayStateProviderFactory::new(
1244 provider_factory,
1245 OverlayBuilder::<reth_chain_state::EthPrimitives>::new(
1246 anchor_hash,
1247 ChangesetCache::new(),
1248 ),
1249 );
1250 let proof_worker_handle =
1251 ProofWorkerHandle::new(&runtime, ProofTaskCtx::new(overlay_factory), false);
1252
1253 let default_trie = RevealableSparseTrie::blind_from(ArenaParallelSparseTrie::default());
1254 let trie = SparseStateTrie::default()
1255 .with_accounts_trie(default_trie.clone())
1256 .with_default_storage_trie(default_trie)
1257 .with_updates(true);
1258
1259 let (updates_tx, updates_rx) = crossbeam_channel::unbounded();
1260 let (_cancel_guard, cancel_rx) = crossbeam_channel::bounded::<()>(0);
1261 let mut task = SparseTrieCacheTask::new_with_trie(
1262 &runtime,
1263 updates_rx,
1264 cancel_rx,
1265 std::sync::mpsc::channel().0,
1266 proof_worker_handle,
1267 SparseTrieTaskMetrics::default(),
1268 trie,
1269 B256::from([0x55; 32]),
1270 1,
1271 );
1272
1273 drop(updates_tx);
1274
1275 let account = B256::from([0x11; 32]);
1276 let slot = B256::from([0x22; 32]);
1277 let account_target = B256::from([0x33; 32]);
1278 let storage_target = B256::from([0x44; 32]);
1279
1280 task.finished_state_updates = true;
1281 task.account_updates.insert(account, LeafUpdate::Touched);
1282 task.storage_updates.entry(account).or_default().insert(slot, LeafUpdate::Touched);
1283 task.pending_account_updates.insert(account, None);
1284 task.fetched_account_targets.insert(account_target, 0);
1285 task.fetched_storage_targets.entry(account).or_default().insert(storage_target, 12);
1286 task.in_flight_proof_batches = 1;
1287
1288 assert!(task.ensure_not_stalled(false).is_ok());
1289
1290 let result = ProofResultMessage {
1291 result: Ok(DecodedMultiProofV2::default()),
1292 elapsed: std::time::Duration::ZERO,
1293 state: HashedPostState::default(),
1294 };
1295 task.on_proof_result_message(result).expect("proof result should be ok");
1296
1297 assert_eq!(task.in_flight_proof_batches, 0);
1298 let error = task.ensure_not_stalled(false).expect_err("task should be stalled");
1299 assert!(matches!(error, StateRootTaskError::Stalled));
1300 let error = error.to_string();
1301
1302 assert!(error.contains("sparse trie task stalled"));
1303 assert!(!error.contains("account_targets"));
1304 assert!(!error.contains("storage_targets"));
1305 assert!(!error.contains(&format!("{account:?}")));
1306 assert!(!error.contains(&format!("{account_target:?}")));
1307 assert!(!error.contains(&format!("{storage_target:?}")));
1308 assert!(!error.contains("pending_account_leaves"));
1309 assert!(!error.contains("pending_storage_leaves"));
1310 assert!(!error.contains("pending_account_updates"));
1311 assert!(!error.contains(&format!("{slot:?}")));
1312 }
1313
1314 #[test]
1315 fn run_errors_when_cancel_guard_drops_before_updates_finish() {
1316 let runtime = reth_tasks::Runtime::test();
1317 let provider_factory = create_test_provider_factory();
1318 let anchor_hash = provider_factory.chain_spec().genesis_hash();
1319 let overlay_factory = OverlayStateProviderFactory::new(
1320 provider_factory,
1321 OverlayBuilder::<reth_chain_state::EthPrimitives>::new(
1322 anchor_hash,
1323 ChangesetCache::new(),
1324 ),
1325 );
1326 let proof_worker_handle =
1327 ProofWorkerHandle::new(&runtime, ProofTaskCtx::new(overlay_factory), false);
1328
1329 let default_trie = RevealableSparseTrie::blind_from(ArenaParallelSparseTrie::default());
1330 let trie = SparseStateTrie::default()
1331 .with_accounts_trie(default_trie.clone())
1332 .with_default_storage_trie(default_trie)
1333 .with_updates(true);
1334
1335 let (updates_tx, updates_rx) = crossbeam_channel::unbounded();
1336 let (cancel_guard, cancel_rx) = crossbeam_channel::bounded::<()>(0);
1337 let mut task = SparseTrieCacheTask::new_with_trie(
1338 &runtime,
1339 updates_rx,
1340 cancel_rx,
1341 std::sync::mpsc::channel().0,
1342 proof_worker_handle,
1343 SparseTrieTaskMetrics::default(),
1344 trie,
1345 B256::from([0x55; 32]),
1346 1,
1347 );
1348
1349 drop(cancel_guard);
1352
1353 let error = task.run().expect_err("canceled task must return an error");
1354 assert!(matches!(error, StateRootTaskError::Canceled));
1355
1356 drop(updates_tx);
1357 }
1358
1359 #[test]
1360 fn run_ignores_hints_queued_after_updates_finish() {
1361 let runtime = reth_tasks::Runtime::test();
1362 let provider_factory = create_test_provider_factory();
1363 let anchor_hash = provider_factory.chain_spec().genesis_hash();
1364 let overlay_factory = OverlayStateProviderFactory::new(
1365 provider_factory,
1366 OverlayBuilder::<reth_chain_state::EthPrimitives>::new(
1367 anchor_hash,
1368 ChangesetCache::new(),
1369 ),
1370 );
1371 let proof_worker_handle =
1372 ProofWorkerHandle::new(&runtime, ProofTaskCtx::new(overlay_factory), false);
1373
1374 let default_trie = RevealableSparseTrie::blind_from(ArenaParallelSparseTrie::default());
1375 let trie = SparseStateTrie::default()
1376 .with_accounts_trie(default_trie.clone())
1377 .with_default_storage_trie(default_trie)
1378 .with_updates(true);
1379
1380 let (updates_tx, updates_rx) = crossbeam_channel::unbounded();
1381 let (cancel_guard, cancel_rx) = crossbeam_channel::bounded::<()>(0);
1382 let mut task = SparseTrieCacheTask::new_with_trie(
1383 &runtime,
1384 updates_rx,
1385 cancel_rx,
1386 std::sync::mpsc::channel().0,
1387 proof_worker_handle,
1388 SparseTrieTaskMetrics::default(),
1389 trie,
1390 B256::from([0x55; 32]),
1391 1,
1392 );
1393
1394 updates_tx.send(StateRootMessage::FinishedStateUpdates).unwrap();
1395 updates_tx.send(StateRootMessage::PrefetchProofs(Default::default())).unwrap();
1396
1397 let wait_start = std::time::Instant::now();
1398 while task.updates.len() < 2 {
1399 assert!(
1400 wait_start.elapsed() < std::time::Duration::from_secs(1),
1401 "hashing task did not queue the test messages"
1402 );
1403 std::thread::yield_now();
1404 }
1405
1406 let (result_tx, result_rx) = std::sync::mpsc::channel();
1407 let handle = std::thread::spawn(move || {
1408 let _ = result_tx.send(task.run());
1409 });
1410
1411 let result = result_rx.recv_timeout(std::time::Duration::from_secs(1));
1412 drop(cancel_guard);
1413 handle.join().unwrap();
1414
1415 assert!(result.expect("state root task stalled on a late hint").is_ok());
1416 }
1417}