1use crate::{
33 error::StateRootTaskError,
34 value_encoder::{AsyncAccountValueEncoder, ValueEncoderStats},
35};
36use alloy_primitives::{
37 map::{B256Map, B256Set},
38 B256, U256,
39};
40use crossbeam_channel::{unbounded, Receiver as CrossbeamReceiver, Sender as CrossbeamSender};
41use reth_execution_errors::StateProofError;
42use reth_primitives_traits::FastInstant as Instant;
43use reth_provider::{DatabaseProviderROFactory, ProviderError, ProviderResult};
44use reth_storage_errors::db::DatabaseError;
45use reth_tasks::Runtime;
46use reth_trie::{
47 hashed_cursor::{HashedCursorFactory, HashedStorageCursor, InstrumentedHashedCursor},
48 proof_v2,
49 trie_cursor::{InstrumentedTrieCursor, TrieCursorFactory, TrieStorageCursor},
50 DecodedMultiProofV2, HashedPostState, MultiProofTargetsV2, ProofTrieNodeV2, ProofV2Target,
51};
52use std::{
53 cell::RefCell,
54 rc::Rc,
55 sync::{
56 atomic::{AtomicBool, AtomicUsize, Ordering},
57 Arc,
58 },
59 time::Duration,
60};
61use tracing::{debug, debug_span, error, instrument, trace};
62
63#[cfg(feature = "metrics")]
64use crate::proof_task_metrics::{
65 ProofTaskCursorMetrics, ProofTaskCursorMetricsCache, ProofTaskTrieMetrics,
66};
67
68type V2AccountProofCalculator<'a, Provider> = proof_v2::ProofCalculator<
70 InstrumentedTrieCursor<'a, <Provider as TrieCursorFactory>::AccountTrieCursor<'a>>,
71 InstrumentedHashedCursor<'a, <Provider as HashedCursorFactory>::AccountCursor<'a>>,
72 AsyncAccountValueEncoder<
73 InstrumentedTrieCursor<'a, <Provider as TrieCursorFactory>::StorageTrieCursor<'a>>,
74 InstrumentedHashedCursor<'a, <Provider as HashedCursorFactory>::StorageCursor<'a>>,
75 >,
76>;
77
78type V2StorageProofCalculator<'a, Provider> = proof_v2::StorageProofCalculator<
80 InstrumentedTrieCursor<'a, <Provider as TrieCursorFactory>::StorageTrieCursor<'a>>,
81 InstrumentedHashedCursor<'a, <Provider as HashedCursorFactory>::StorageCursor<'a>>,
82>;
83
84#[derive(Debug)]
88struct AvailabilitySheet {
89 flags: Vec<crossbeam_utils::CachePadded<AtomicBool>>,
92}
93
94impl AvailabilitySheet {
95 fn new(count: usize) -> Self {
97 let flags =
98 (0..count).map(|_| crossbeam_utils::CachePadded::new(AtomicBool::new(false))).collect();
99 Self { flags }
100 }
101
102 fn has_multiple_idle(&self) -> bool {
107 let mut idle = 0u32;
108 for flag in &self.flags {
109 if flag.load(Ordering::Relaxed) {
110 idle += 1;
111 if idle > 1 {
112 return true;
113 }
114 }
115 }
116 false
117 }
118
119 fn mark_idle(&self, worker_id: usize) {
121 self.flags[worker_id].store(true, Ordering::Relaxed);
122 }
123
124 fn mark_busy(&self, worker_id: usize) {
126 self.flags[worker_id].store(false, Ordering::Relaxed);
127 }
128}
129
130#[derive(Debug, Clone)]
136pub struct ProofWorkerHandle {
137 storage_work_tx: CrossbeamSender<StorageWorkerJob>,
139 account_work_tx: CrossbeamSender<AccountWorkerJob>,
141 storage_availability: Arc<AvailabilitySheet>,
144 account_availability: Arc<AvailabilitySheet>,
147 storage_worker_count: usize,
149 account_worker_count: usize,
151}
152
153impl ProofWorkerHandle {
154 #[instrument(
164 name = "ProofWorkerHandle::new",
165 level = "debug",
166 target = "trie::proof_task",
167 skip_all
168 )]
169 pub fn new<Factory>(
170 runtime: &Runtime,
171 task_ctx: ProofTaskCtx<Factory>,
172 halve_workers: bool,
173 proof_result_tx: ProofResultSender,
174 ) -> Self
175 where
176 Factory: DatabaseProviderROFactory<Provider: TrieCursorFactory + HashedCursorFactory>
177 + Clone
178 + Send
179 + Sync
180 + 'static,
181 {
182 let (storage_work_tx, storage_work_rx) = unbounded::<StorageWorkerJob>();
183 let (account_work_tx, account_work_rx) = unbounded::<AccountWorkerJob>();
184
185 let divisor = if halve_workers { 2 } else { 1 };
186 let storage_worker_count =
187 runtime.proof_storage_worker_pool().current_num_threads() / divisor;
188 let account_worker_count =
189 runtime.proof_account_worker_pool().current_num_threads() / divisor;
190
191 let storage_availability = Arc::new(AvailabilitySheet::new(storage_worker_count));
192 let account_availability = Arc::new(AvailabilitySheet::new(account_worker_count));
193
194 debug!(
195 target: "trie::proof_task",
196 storage_worker_count,
197 account_worker_count,
198 halve_workers,
199 "Spawning proof worker pools"
200 );
201
202 let storage_rt = runtime.clone();
205 let storage_task_ctx = task_ctx.clone();
206 let storage_avail = storage_availability.clone();
207 let storage_result_tx = proof_result_tx.clone();
208 let storage_parent_span = tracing::Span::current();
209 runtime.spawn_blocking_named("storage-workers", move || {
210 let worker_id = AtomicUsize::new(0);
211 storage_rt.proof_storage_worker_pool().broadcast(storage_worker_count, |_| {
212 let worker_id = worker_id.fetch_add(1, Ordering::Relaxed);
213 let span = debug_span!(target: "trie::proof_task", parent: storage_parent_span.clone(), "storage_worker", ?worker_id);
214 let _guard = span.enter();
215
216 #[cfg(feature = "metrics")]
217 let metrics = ProofTaskTrieMetrics::default();
218 #[cfg(feature = "metrics")]
219 let cursor_metrics = ProofTaskCursorMetrics::new();
220
221 let worker = StorageProofWorker::new(
222 storage_task_ctx.clone(),
223 storage_work_rx.clone(),
224 worker_id,
225 storage_avail.clone(),
226 #[cfg(feature = "metrics")]
227 metrics,
228 #[cfg(feature = "metrics")]
229 cursor_metrics,
230 );
231 if let Err(error) = worker.run() {
232 error!(
233 target: "trie::proof_task",
234 worker_id,
235 ?error,
236 "Storage worker failed"
237 );
238 let _ = storage_result_tx.send(ProofResultMessage {
239 result: Err(StateRootTaskError::ProofWorker(format!(
240 "storage worker {worker_id}: {error}"
241 ))),
242 elapsed: Duration::ZERO,
243 state: Default::default(),
244 });
245 }
246 });
247 });
248
249 let account_rt = runtime.clone();
250 let account_tx = storage_work_tx.clone();
251 let account_avail = account_availability.clone();
252 let account_result_tx = proof_result_tx;
253 let account_parent_span = tracing::Span::current();
254 runtime.spawn_blocking_named("account-workers", move || {
255 let worker_id = AtomicUsize::new(0);
256 account_rt.proof_account_worker_pool().broadcast(account_worker_count, |_| {
257 let worker_id = worker_id.fetch_add(1, Ordering::Relaxed);
258 let span = debug_span!(target: "trie::proof_task", parent: account_parent_span.clone(), "account_worker", ?worker_id);
259 let _guard = span.enter();
260
261 #[cfg(feature = "metrics")]
262 let metrics = ProofTaskTrieMetrics::default();
263 #[cfg(feature = "metrics")]
264 let cursor_metrics = ProofTaskCursorMetrics::new();
265
266 let worker = AccountProofWorker::new(
267 task_ctx.clone(),
268 account_work_rx.clone(),
269 worker_id,
270 account_tx.clone(),
271 account_avail.clone(),
272 #[cfg(feature = "metrics")]
273 metrics,
274 #[cfg(feature = "metrics")]
275 cursor_metrics,
276 );
277 if let Err(error) = worker.run() {
278 error!(
279 target: "trie::proof_task",
280 worker_id,
281 ?error,
282 "Account worker failed"
283 );
284 let _ = account_result_tx.send(ProofResultMessage {
285 result: Err(StateRootTaskError::ProofWorker(format!(
286 "account worker {worker_id}: {error}"
287 ))),
288 elapsed: Duration::ZERO,
289 state: Default::default(),
290 });
291 }
292 });
293 });
294
295 Self {
296 storage_work_tx,
297 account_work_tx,
298 storage_availability,
299 account_availability,
300 storage_worker_count,
301 account_worker_count,
302 }
303 }
304
305 pub fn has_multiple_idle_storage_workers(&self) -> bool {
307 self.storage_availability.has_multiple_idle()
308 }
309
310 pub fn has_multiple_idle_account_workers(&self) -> bool {
312 self.account_availability.has_multiple_idle()
313 }
314
315 pub fn pending_storage_tasks(&self) -> usize {
317 self.storage_work_tx.len()
318 }
319
320 pub fn pending_account_tasks(&self) -> usize {
322 self.account_work_tx.len()
323 }
324
325 pub const fn total_storage_workers(&self) -> usize {
327 self.storage_worker_count
328 }
329
330 pub const fn total_account_workers(&self) -> usize {
332 self.account_worker_count
333 }
334
335 pub fn dispatch_storage_proof(
339 &self,
340 input: StorageProofInput,
341 proof_result_sender: CrossbeamSender<StorageProofResultMessage>,
342 ) -> Result<(), ProviderError> {
343 let hashed_address = input.hashed_address;
344 self.storage_work_tx
345 .send(StorageWorkerJob::StorageProof { input, proof_result_sender })
346 .map_err(|err| {
347 let StorageWorkerJob::StorageProof { proof_result_sender, .. } = err.0;
348 let _ = proof_result_sender.send(StorageProofResultMessage {
349 hashed_address,
350 result: Err(
351 DatabaseError::Other("storage workers unavailable".to_string()).into()
352 ),
353 });
354
355 ProviderError::other(std::io::Error::other("storage workers unavailable"))
356 })
357 }
358
359 pub fn dispatch_account_multiproof(
363 &self,
364 input: AccountMultiproofInput,
365 ) -> Result<(), ProviderError> {
366 self.account_work_tx
367 .send(AccountWorkerJob::AccountMultiproof { input: Box::new(input) })
368 .map_err(|err| {
369 let error =
370 ProviderError::other(std::io::Error::other("account workers unavailable"));
371
372 let AccountWorkerJob::AccountMultiproof { input } = err.0;
373 let ProofResultContext { sender: result_tx, state, start_time: start } =
374 input.into_proof_result_sender();
375
376 let _ = result_tx.send(ProofResultMessage {
377 result: Err(StateRootTaskError::ProofDispatch(error.clone())),
378 elapsed: start.elapsed(),
379 state,
380 });
381
382 error
383 })
384 }
385}
386
387#[derive(Clone, Debug)]
389pub struct ProofTaskCtx<Factory> {
390 factory: Factory,
392 #[cfg(feature = "trie-debug")]
394 proof_jitter: Option<Duration>,
395}
396
397impl<Factory> ProofTaskCtx<Factory> {
398 pub const fn new(factory: Factory) -> Self {
400 Self {
401 factory,
402 #[cfg(feature = "trie-debug")]
403 proof_jitter: None,
404 }
405 }
406
407 #[cfg(feature = "trie-debug")]
409 pub const fn with_proof_jitter(mut self, jitter: Option<Duration>) -> Self {
410 self.proof_jitter = jitter;
411 self
412 }
413}
414
415#[derive(Debug)]
417pub struct ProofTaskTx<Provider> {
418 provider: Provider,
420
421 id: usize,
423}
424
425impl<Provider> ProofTaskTx<Provider> {
426 const fn new(provider: Provider, id: usize) -> Self {
428 Self { provider, id }
429 }
430}
431
432impl<Provider> ProofTaskTx<Provider>
433where
434 Provider: TrieCursorFactory + HashedCursorFactory,
435{
436 fn compute_v2_storage_proof<TC, HC>(
437 &self,
438 input: StorageProofInput,
439 calculator: &mut proof_v2::StorageProofCalculator<TC, HC>,
440 ) -> Result<StorageProofResult, StateProofError>
441 where
442 TC: TrieStorageCursor,
443 HC: HashedStorageCursor<Value = U256>,
444 {
445 let StorageProofInput { hashed_address, mut targets, needs_root } = input;
446
447 let span = debug_span!(
448 target: "trie::proof_task",
449 "Storage proof calculation",
450 n = %targets.len(),
451 );
452 let _span_guard = span.enter();
453
454 let proof_start = Instant::now();
455
456 let (proof, root) = if targets.is_empty() {
458 let root_node = calculator.storage_root_node(hashed_address)?;
459 let root = calculator.compute_root_hash(core::slice::from_ref(&root_node))?;
460 (vec![root_node], root)
461 } else {
462 let root = if needs_root && targets.iter().all(|target| target.parent.is_known()) {
466 let root_node = calculator.storage_root_node(hashed_address)?;
467 calculator.compute_root_hash(core::slice::from_ref(&root_node))?
468 } else {
469 None
470 };
471
472 let proof = calculator.storage_proof(hashed_address, &mut targets)?;
473 let root = if root.is_some() { root } else { calculator.compute_root_hash(&proof)? };
474 (proof, root)
475 };
476
477 trace!(
478 target: "trie::proof_task",
479 hashed_address = ?hashed_address,
480 proof_time_us = proof_start.elapsed().as_micros(),
481 ?root,
482 worker_id = self.id,
483 "Completed V2 storage proof calculation"
484 );
485
486 Ok(StorageProofResult { proof, root })
487 }
488}
489
490pub type ProofResultSender = CrossbeamSender<ProofResultMessage>;
496
497#[derive(Debug)]
503pub struct ProofResultMessage {
504 pub result: Result<DecodedMultiProofV2, StateRootTaskError>,
506 pub elapsed: Duration,
508 pub state: HashedPostState,
510}
511
512#[derive(Debug, Clone)]
517pub struct ProofResultContext {
518 pub sender: ProofResultSender,
520 pub state: HashedPostState,
522 pub start_time: Instant,
524}
525
526impl ProofResultContext {
527 pub const fn new(
529 sender: ProofResultSender,
530 state: HashedPostState,
531 start_time: Instant,
532 ) -> Self {
533 Self { sender, state, start_time }
534 }
535}
536
537#[derive(Debug)]
539pub(crate) struct StorageProofResult {
540 pub proof: Vec<ProofTrieNodeV2>,
542 pub root: Option<B256>,
544}
545
546impl StorageProofResult {
547 const fn root(&self) -> Option<B256> {
549 self.root
550 }
551}
552
553#[derive(Debug)]
555pub struct StorageProofResultMessage {
556 #[allow(dead_code)]
558 pub(crate) hashed_address: B256,
559 pub(crate) result: Result<StorageProofResult, StateProofError>,
561}
562
563#[derive(Debug)]
565pub(crate) enum StorageWorkerJob {
566 StorageProof {
568 input: StorageProofInput,
570 proof_result_sender: CrossbeamSender<StorageProofResultMessage>,
572 },
573}
574
575struct StorageProofWorker<Factory> {
580 task_ctx: ProofTaskCtx<Factory>,
582 work_rx: CrossbeamReceiver<StorageWorkerJob>,
584 worker_id: usize,
586 availability: Arc<AvailabilitySheet>,
588 #[cfg(feature = "metrics")]
590 metrics: ProofTaskTrieMetrics,
591 #[cfg(feature = "metrics")]
593 cursor_metrics: ProofTaskCursorMetrics,
594}
595
596impl<Factory> StorageProofWorker<Factory>
597where
598 Factory: DatabaseProviderROFactory<Provider: TrieCursorFactory + HashedCursorFactory>,
599{
600 const fn new(
602 task_ctx: ProofTaskCtx<Factory>,
603 work_rx: CrossbeamReceiver<StorageWorkerJob>,
604 worker_id: usize,
605 availability: Arc<AvailabilitySheet>,
606 #[cfg(feature = "metrics")] metrics: ProofTaskTrieMetrics,
607 #[cfg(feature = "metrics")] cursor_metrics: ProofTaskCursorMetrics,
608 ) -> Self {
609 Self {
610 task_ctx,
611 work_rx,
612 worker_id,
613 availability,
614 #[cfg(feature = "metrics")]
615 metrics,
616 #[cfg(feature = "metrics")]
617 cursor_metrics,
618 }
619 }
620
621 fn run(mut self) -> ProviderResult<()> {
639 let provider = self.task_ctx.factory.database_provider_ro()?;
641 let proof_tx = ProofTaskTx::new(provider, self.worker_id);
642
643 trace!(
644 target: "trie::proof_task",
645 worker_id = self.worker_id,
646 "Storage worker started"
647 );
648
649 let mut storage_proofs_processed = 0u64;
650 let mut cursor_metrics_cache = ProofTaskCursorMetricsCache::default();
651 let trie_cursor = proof_tx.provider.storage_trie_cursor(B256::ZERO)?;
652 let hashed_cursor = proof_tx.provider.hashed_storage_cursor(B256::ZERO)?;
653 let instrumented_trie_cursor =
654 InstrumentedTrieCursor::new(trie_cursor, &mut cursor_metrics_cache.storage_trie_cursor);
655 let instrumented_hashed_cursor = InstrumentedHashedCursor::new(
656 hashed_cursor,
657 &mut cursor_metrics_cache.storage_hashed_cursor,
658 );
659 let mut v2_calculator = proof_v2::StorageProofCalculator::new_storage(
660 instrumented_trie_cursor,
661 instrumented_hashed_cursor,
662 );
663
664 self.availability.mark_idle(self.worker_id);
666
667 let mut total_idle_time = Duration::ZERO;
668 let mut idle_start = Instant::now();
669
670 while let Ok(job) = self.work_rx.recv() {
671 total_idle_time += idle_start.elapsed();
672
673 self.availability.mark_busy(self.worker_id);
675
676 #[cfg(feature = "trie-debug")]
677 if let Some(max_jitter) = self.task_ctx.proof_jitter {
678 let jitter =
679 Duration::from_nanos(rand::random_range(0..=max_jitter.as_nanos() as u64));
680 trace!(
681 target: "trie::proof_task",
682 worker_id = self.worker_id,
683 jitter_us = jitter.as_micros(),
684 "Storage worker applying proof jitter"
685 );
686 std::thread::sleep(jitter);
687 }
688
689 match job {
690 StorageWorkerJob::StorageProof { input, proof_result_sender } => {
691 self.process_storage_proof(
692 &proof_tx,
693 &mut v2_calculator,
694 input,
695 proof_result_sender,
696 &mut storage_proofs_processed,
697 );
698 }
699 }
700
701 self.availability.mark_idle(self.worker_id);
703
704 idle_start = Instant::now();
705 }
706
707 drop(v2_calculator);
709
710 trace!(
711 target: "trie::proof_task",
712 worker_id = self.worker_id,
713 storage_proofs_processed,
714 total_idle_time_us = total_idle_time.as_micros(),
715 "Storage worker shutting down"
716 );
717
718 #[cfg(feature = "metrics")]
719 {
720 self.metrics.record_storage_worker_idle_time(total_idle_time);
721 self.cursor_metrics.record(&mut cursor_metrics_cache);
722 }
723
724 Ok(())
725 }
726
727 fn process_storage_proof<Provider, TC, HC>(
729 &self,
730 proof_tx: &ProofTaskTx<Provider>,
731 v2_calculator: &mut proof_v2::StorageProofCalculator<TC, HC>,
732 input: StorageProofInput,
733 proof_result_sender: CrossbeamSender<StorageProofResultMessage>,
734 storage_proofs_processed: &mut u64,
735 ) where
736 Provider: TrieCursorFactory + HashedCursorFactory,
737 TC: TrieStorageCursor,
738 HC: HashedStorageCursor<Value = U256>,
739 {
740 let hashed_address = input.hashed_address;
741 let proof_start = Instant::now();
742
743 trace!(
744 target: "trie::proof_task",
745 worker_id = self.worker_id,
746 hashed_address = ?hashed_address,
747 targets_len = input.targets.len(),
748 "Processing V2 storage proof"
749 );
750
751 let result = proof_tx.compute_v2_storage_proof(input, v2_calculator);
752
753 let proof_elapsed = proof_start.elapsed();
754 *storage_proofs_processed += 1;
755
756 let root = result.as_ref().ok().and_then(|result| result.root());
757
758 if proof_result_sender.send(StorageProofResultMessage { hashed_address, result }).is_err() {
759 trace!(
760 target: "trie::proof_task",
761 worker_id = self.worker_id,
762 hashed_address = ?hashed_address,
763 storage_proofs_processed,
764 "Proof result receiver dropped, discarding result"
765 );
766 }
767
768 trace!(
769 target: "trie::proof_task",
770 worker_id = self.worker_id,
771 hashed_address = ?hashed_address,
772 proof_time_us = proof_elapsed.as_micros(),
773 total_processed = storage_proofs_processed,
774 ?root,
775 "Storage proof completed"
776 );
777 }
778}
779
780struct AccountProofWorker<Factory> {
785 task_ctx: ProofTaskCtx<Factory>,
787 work_rx: CrossbeamReceiver<AccountWorkerJob>,
789 worker_id: usize,
791 storage_work_tx: CrossbeamSender<StorageWorkerJob>,
793 availability: Arc<AvailabilitySheet>,
795 #[cfg(feature = "metrics")]
797 metrics: ProofTaskTrieMetrics,
798 #[cfg(feature = "metrics")]
800 cursor_metrics: ProofTaskCursorMetrics,
801}
802
803impl<Factory> AccountProofWorker<Factory>
804where
805 Factory: DatabaseProviderROFactory<Provider: TrieCursorFactory + HashedCursorFactory>,
806{
807 const fn new(
809 task_ctx: ProofTaskCtx<Factory>,
810 work_rx: CrossbeamReceiver<AccountWorkerJob>,
811 worker_id: usize,
812 storage_work_tx: CrossbeamSender<StorageWorkerJob>,
813 availability: Arc<AvailabilitySheet>,
814 #[cfg(feature = "metrics")] metrics: ProofTaskTrieMetrics,
815 #[cfg(feature = "metrics")] cursor_metrics: ProofTaskCursorMetrics,
816 ) -> Self {
817 Self {
818 task_ctx,
819 work_rx,
820 worker_id,
821 storage_work_tx,
822 availability,
823 #[cfg(feature = "metrics")]
824 metrics,
825 #[cfg(feature = "metrics")]
826 cursor_metrics,
827 }
828 }
829
830 fn run(mut self) -> ProviderResult<()> {
848 let provider = self.task_ctx.factory.database_provider_ro()?;
849
850 trace!(
851 target: "trie::proof_task",
852 worker_id=self.worker_id,
853 "Account worker started"
854 );
855
856 let mut account_proofs_processed = 0u64;
857 let mut cursor_metrics_cache = ProofTaskCursorMetricsCache::default();
858
859 let account_trie_cursor = provider.account_trie_cursor()?;
862 let account_hashed_cursor = provider.hashed_account_cursor()?;
863
864 let storage_trie_cursor = provider.storage_trie_cursor(B256::ZERO)?;
865 let storage_hashed_cursor = provider.hashed_storage_cursor(B256::ZERO)?;
866
867 let instrumented_account_trie_cursor = InstrumentedTrieCursor::new(
868 account_trie_cursor,
869 &mut cursor_metrics_cache.account_trie_cursor,
870 );
871 let instrumented_account_hashed_cursor = InstrumentedHashedCursor::new(
872 account_hashed_cursor,
873 &mut cursor_metrics_cache.account_hashed_cursor,
874 );
875 let instrumented_storage_trie_cursor = InstrumentedTrieCursor::new(
876 storage_trie_cursor,
877 &mut cursor_metrics_cache.storage_trie_cursor,
878 );
879 let instrumented_storage_hashed_cursor = InstrumentedHashedCursor::new(
880 storage_hashed_cursor,
881 &mut cursor_metrics_cache.storage_hashed_cursor,
882 );
883
884 let mut v2_account_calculator =
885 proof_v2::ProofCalculator::<
886 _,
887 _,
888 AsyncAccountValueEncoder<
889 InstrumentedTrieCursor<
890 '_,
891 <Factory::Provider as TrieCursorFactory>::StorageTrieCursor<'_>,
892 >,
893 InstrumentedHashedCursor<
894 '_,
895 <Factory::Provider as HashedCursorFactory>::StorageCursor<'_>,
896 >,
897 >,
898 >::new(instrumented_account_trie_cursor, instrumented_account_hashed_cursor);
899 let v2_storage_calculator =
900 Rc::new(RefCell::new(proof_v2::StorageProofCalculator::new_storage(
901 instrumented_storage_trie_cursor,
902 instrumented_storage_hashed_cursor,
903 )));
904
905 self.availability.mark_idle(self.worker_id);
907
908 let mut total_idle_time = Duration::ZERO;
909 let mut idle_start = Instant::now();
910 let mut value_encoder_stats_cache = ValueEncoderStats::default();
911
912 while let Ok(job) = self.work_rx.recv() {
913 total_idle_time += idle_start.elapsed();
914
915 self.availability.mark_busy(self.worker_id);
917
918 #[cfg(feature = "trie-debug")]
919 if let Some(max_jitter) = self.task_ctx.proof_jitter {
920 let jitter =
921 Duration::from_nanos(rand::random_range(0..=max_jitter.as_nanos() as u64));
922 trace!(
923 target: "trie::proof_task",
924 worker_id = self.worker_id,
925 jitter_us = jitter.as_micros(),
926 "Account worker applying proof jitter"
927 );
928 std::thread::sleep(jitter);
929 }
930
931 match job {
932 AccountWorkerJob::AccountMultiproof { input } => {
933 let value_encoder_stats = self.process_account_multiproof::<Factory::Provider>(
934 &mut v2_account_calculator,
935 v2_storage_calculator.clone(),
936 *input,
937 &mut account_proofs_processed,
938 );
939 total_idle_time += value_encoder_stats.storage_wait_time;
940 value_encoder_stats_cache.extend(&value_encoder_stats);
941 }
942 }
943
944 self.availability.mark_idle(self.worker_id);
946
947 idle_start = Instant::now();
948 }
949
950 drop(v2_account_calculator);
952 drop(v2_storage_calculator);
953
954 trace!(
955 target: "trie::proof_task",
956 worker_id=self.worker_id,
957 account_proofs_processed,
958 total_idle_time_us = total_idle_time.as_micros(),
959 "Account worker shutting down"
960 );
961
962 #[cfg(feature = "metrics")]
963 {
964 self.metrics.record_account_worker_idle_time(total_idle_time);
965 self.cursor_metrics.record(&mut cursor_metrics_cache);
966 self.metrics.record_value_encoder_stats(&value_encoder_stats_cache);
967 }
968
969 Ok(())
970 }
971
972 fn compute_v2_account_multiproof<'a, Provider>(
973 &self,
974 v2_account_calculator: &mut V2AccountProofCalculator<'a, Provider>,
975 v2_storage_calculator: Rc<RefCell<V2StorageProofCalculator<'a, Provider>>>,
976 targets: MultiProofTargetsV2,
977 ) -> Result<(DecodedMultiProofV2, ValueEncoderStats), StateRootTaskError>
978 where
979 Provider: TrieCursorFactory + HashedCursorFactory + 'a,
980 {
981 let MultiProofTargetsV2 { mut account_targets, storage_targets } = targets;
982
983 let span = debug_span!(
984 target: "trie::proof_task",
985 "Account multiproof calculation",
986 account_targets = account_targets.len(),
987 storage_targets = storage_targets.values().map(|t| t.len()).sum::<usize>(),
988 );
989 let _span_guard = span.enter();
990
991 trace!(target: "trie::proof_task", "Processing V2 account multiproof");
992
993 let storage_proof_receivers =
994 dispatch_v2_storage_proofs(&self.storage_work_tx, &account_targets, storage_targets)?;
995
996 let mut value_encoder =
997 AsyncAccountValueEncoder::new(storage_proof_receivers, v2_storage_calculator);
998
999 let account_proofs =
1000 v2_account_calculator.proof(&mut value_encoder, &mut account_targets)?;
1001
1002 let (storage_proofs, value_encoder_stats) = value_encoder.finalize()?;
1003
1004 let proof = DecodedMultiProofV2 { account_proofs, storage_proofs };
1005
1006 Ok((proof, value_encoder_stats))
1007 }
1008
1009 fn process_account_multiproof<'a, Provider>(
1013 &self,
1014 v2_account_calculator: &mut V2AccountProofCalculator<'a, Provider>,
1015 v2_storage_calculator: Rc<RefCell<V2StorageProofCalculator<'a, Provider>>>,
1016 input: AccountMultiproofInput,
1017 account_proofs_processed: &mut u64,
1018 ) -> ValueEncoderStats
1019 where
1020 Provider: TrieCursorFactory + HashedCursorFactory + 'a,
1021 {
1022 let proof_start = Instant::now();
1023
1024 let AccountMultiproofInput { targets, proof_result_sender } = input;
1025 let (result, value_encoder_stats) = match self.compute_v2_account_multiproof::<Provider>(
1026 v2_account_calculator,
1027 v2_storage_calculator,
1028 targets,
1029 ) {
1030 Ok((proof, stats)) => (Ok(proof), stats),
1031 Err(e) => (Err(e), ValueEncoderStats::default()),
1032 };
1033
1034 let ProofResultContext { sender: result_tx, state, start_time: start } =
1035 proof_result_sender;
1036
1037 let proof_elapsed = proof_start.elapsed();
1038 let total_elapsed = start.elapsed();
1039 *account_proofs_processed += 1;
1040
1041 if result_tx.send(ProofResultMessage { result, elapsed: total_elapsed, state }).is_err() {
1043 trace!(
1044 target: "trie::proof_task",
1045 worker_id=self.worker_id,
1046 account_proofs_processed,
1047 "Account multiproof receiver dropped, discarding result"
1048 );
1049 }
1050
1051 trace!(
1052 target: "trie::proof_task",
1053 proof_time_us = proof_elapsed.as_micros(),
1054 total_elapsed_us = total_elapsed.as_micros(),
1055 total_processed = account_proofs_processed,
1056 "Account multiproof completed"
1057 );
1058
1059 value_encoder_stats
1060 }
1061}
1062
1063fn dispatch_v2_storage_proofs(
1071 storage_work_tx: &CrossbeamSender<StorageWorkerJob>,
1072 account_targets: &[ProofV2Target],
1073 storage_targets: B256Map<Vec<ProofV2Target>>,
1074) -> Result<B256Map<CrossbeamReceiver<StorageProofResultMessage>>, StateRootTaskError> {
1075 if storage_targets.is_empty() {
1076 return Ok(B256Map::default())
1077 }
1078
1079 let mut storage_proof_receivers =
1080 B256Map::with_capacity_and_hasher(storage_targets.len(), Default::default());
1081
1082 let account_target_addresses: B256Set = account_targets.iter().map(|t| t.key()).collect();
1084
1085 let mut sorted_storage_targets: Vec<_> = storage_targets.into_iter().collect();
1089 sorted_storage_targets.sort_unstable_by_key(|(addr, _)| *addr);
1090
1091 for (hashed_address, targets) in sorted_storage_targets {
1093 let (result_tx, result_rx) = crossbeam_channel::unbounded();
1095 let needs_root = account_target_addresses.contains(&hashed_address);
1096 let input = StorageProofInput::new(hashed_address, targets, needs_root);
1097
1098 storage_work_tx
1099 .send(StorageWorkerJob::StorageProof { input, proof_result_sender: result_tx })
1100 .map_err(|_| {
1101 StateRootTaskError::Other(format!(
1102 "Failed to queue storage proof for {hashed_address:?}: storage worker pool unavailable",
1103 ))
1104 })?;
1105
1106 storage_proof_receivers.insert(hashed_address, result_rx);
1107 }
1108
1109 Ok(storage_proof_receivers)
1110}
1111
1112#[derive(Debug)]
1114pub struct StorageProofInput {
1115 pub hashed_address: B256,
1117 pub targets: Vec<ProofV2Target>,
1119 pub needs_root: bool,
1121}
1122
1123impl StorageProofInput {
1124 pub const fn new(hashed_address: B256, targets: Vec<ProofV2Target>, needs_root: bool) -> Self {
1126 Self { hashed_address, targets, needs_root }
1127 }
1128}
1129
1130#[derive(Debug)]
1132pub struct AccountMultiproofInput {
1133 pub targets: MultiProofTargetsV2,
1135 pub proof_result_sender: ProofResultContext,
1137}
1138
1139impl AccountMultiproofInput {
1140 fn into_proof_result_sender(self) -> ProofResultContext {
1142 self.proof_result_sender
1143 }
1144}
1145
1146#[derive(Debug)]
1148enum AccountWorkerJob {
1149 AccountMultiproof {
1151 input: Box<AccountMultiproofInput>,
1153 },
1154}
1155
1156#[cfg(test)]
1157mod tests {
1158 use super::*;
1159 use reth_chainspec::ChainSpec;
1160 use reth_provider::test_utils::create_test_provider_factory_with_chain_spec;
1161 use std::sync::Arc;
1162
1163 fn test_ctx<Factory>(factory: Factory) -> ProofTaskCtx<Factory> {
1164 ProofTaskCtx::new(factory)
1165 }
1166
1167 #[test]
1169 fn spawn_proof_workers_creates_handle() {
1170 let chain_spec = Arc::new(ChainSpec::default());
1171 let anchor_hash = chain_spec.genesis_hash();
1172 let provider_factory = create_test_provider_factory_with_chain_spec(chain_spec);
1173 let factory = reth_storage_overlay::OverlayStateProviderFactory::new(
1174 provider_factory,
1175 reth_storage_overlay::OverlayManager::<
1176 reth_ethereum_primitives::EthPrimitives,
1177 >::default()
1178 .overlay_builder(anchor_hash),
1179 );
1180 let ctx = test_ctx(factory);
1181
1182 let runtime = reth_tasks::Runtime::test();
1183 let (proof_result_tx, _) = unbounded();
1184 let proof_handle = ProofWorkerHandle::new(&runtime, ctx, false, proof_result_tx);
1185
1186 let _cloned_handle = proof_handle.clone();
1188
1189 drop(proof_handle);
1191 }
1192}