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::{dashmap::DashMap, 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 let cached_storage_roots = Arc::<DashMap<_, _>>::default();
185
186 let divisor = if halve_workers { 2 } else { 1 };
187 let storage_worker_count =
188 runtime.proof_storage_worker_pool().current_num_threads() / divisor;
189 let account_worker_count =
190 runtime.proof_account_worker_pool().current_num_threads() / divisor;
191
192 let storage_availability = Arc::new(AvailabilitySheet::new(storage_worker_count));
193 let account_availability = Arc::new(AvailabilitySheet::new(account_worker_count));
194
195 debug!(
196 target: "trie::proof_task",
197 storage_worker_count,
198 account_worker_count,
199 halve_workers,
200 "Spawning proof worker pools"
201 );
202
203 let storage_rt = runtime.clone();
206 let storage_task_ctx = task_ctx.clone();
207 let storage_avail = storage_availability.clone();
208 let storage_roots = cached_storage_roots.clone();
209 let storage_result_tx = proof_result_tx.clone();
210 let storage_parent_span = tracing::Span::current();
211 runtime.spawn_blocking_named("storage-workers", move || {
212 let worker_id = AtomicUsize::new(0);
213 storage_rt.proof_storage_worker_pool().broadcast(storage_worker_count, |_| {
214 let worker_id = worker_id.fetch_add(1, Ordering::Relaxed);
215 let span = debug_span!(target: "trie::proof_task", parent: storage_parent_span.clone(), "storage_worker", ?worker_id);
216 let _guard = span.enter();
217
218 #[cfg(feature = "metrics")]
219 let metrics = ProofTaskTrieMetrics::default();
220 #[cfg(feature = "metrics")]
221 let cursor_metrics = ProofTaskCursorMetrics::new();
222
223 let worker = StorageProofWorker::new(
224 storage_task_ctx.clone(),
225 storage_work_rx.clone(),
226 worker_id,
227 storage_avail.clone(),
228 storage_roots.clone(),
229 #[cfg(feature = "metrics")]
230 metrics,
231 #[cfg(feature = "metrics")]
232 cursor_metrics,
233 );
234 if let Err(error) = worker.run() {
235 error!(
236 target: "trie::proof_task",
237 worker_id,
238 ?error,
239 "Storage worker failed"
240 );
241 let _ = storage_result_tx.send(ProofResultMessage {
242 result: Err(StateRootTaskError::ProofWorker(format!(
243 "storage worker {worker_id}: {error}"
244 ))),
245 elapsed: Duration::ZERO,
246 state: Default::default(),
247 });
248 }
249 });
250 });
251
252 let account_rt = runtime.clone();
253 let account_tx = storage_work_tx.clone();
254 let account_avail = account_availability.clone();
255 let account_result_tx = proof_result_tx;
256 let account_parent_span = tracing::Span::current();
257 runtime.spawn_blocking_named("account-workers", move || {
258 let worker_id = AtomicUsize::new(0);
259 account_rt.proof_account_worker_pool().broadcast(account_worker_count, |_| {
260 let worker_id = worker_id.fetch_add(1, Ordering::Relaxed);
261 let span = debug_span!(target: "trie::proof_task", parent: account_parent_span.clone(), "account_worker", ?worker_id);
262 let _guard = span.enter();
263
264 #[cfg(feature = "metrics")]
265 let metrics = ProofTaskTrieMetrics::default();
266 #[cfg(feature = "metrics")]
267 let cursor_metrics = ProofTaskCursorMetrics::new();
268
269 let worker = AccountProofWorker::new(
270 task_ctx.clone(),
271 account_work_rx.clone(),
272 worker_id,
273 account_tx.clone(),
274 account_avail.clone(),
275 cached_storage_roots.clone(),
276 #[cfg(feature = "metrics")]
277 metrics,
278 #[cfg(feature = "metrics")]
279 cursor_metrics,
280 );
281 if let Err(error) = worker.run() {
282 error!(
283 target: "trie::proof_task",
284 worker_id,
285 ?error,
286 "Account worker failed"
287 );
288 let _ = account_result_tx.send(ProofResultMessage {
289 result: Err(StateRootTaskError::ProofWorker(format!(
290 "account worker {worker_id}: {error}"
291 ))),
292 elapsed: Duration::ZERO,
293 state: Default::default(),
294 });
295 }
296 });
297 });
298
299 Self {
300 storage_work_tx,
301 account_work_tx,
302 storage_availability,
303 account_availability,
304 storage_worker_count,
305 account_worker_count,
306 }
307 }
308
309 pub fn has_multiple_idle_storage_workers(&self) -> bool {
311 self.storage_availability.has_multiple_idle()
312 }
313
314 pub fn has_multiple_idle_account_workers(&self) -> bool {
316 self.account_availability.has_multiple_idle()
317 }
318
319 pub fn pending_storage_tasks(&self) -> usize {
321 self.storage_work_tx.len()
322 }
323
324 pub fn pending_account_tasks(&self) -> usize {
326 self.account_work_tx.len()
327 }
328
329 pub const fn total_storage_workers(&self) -> usize {
331 self.storage_worker_count
332 }
333
334 pub const fn total_account_workers(&self) -> usize {
336 self.account_worker_count
337 }
338
339 pub fn dispatch_storage_proof(
343 &self,
344 input: StorageProofInput,
345 proof_result_sender: CrossbeamSender<StorageProofResultMessage>,
346 ) -> Result<(), ProviderError> {
347 let hashed_address = input.hashed_address;
348 self.storage_work_tx
349 .send(StorageWorkerJob::StorageProof { input, proof_result_sender })
350 .map_err(|err| {
351 let StorageWorkerJob::StorageProof { proof_result_sender, .. } = err.0;
352 let _ = proof_result_sender.send(StorageProofResultMessage {
353 hashed_address,
354 result: Err(
355 DatabaseError::Other("storage workers unavailable".to_string()).into()
356 ),
357 });
358
359 ProviderError::other(std::io::Error::other("storage workers unavailable"))
360 })
361 }
362
363 pub fn dispatch_account_multiproof(
367 &self,
368 input: AccountMultiproofInput,
369 ) -> Result<(), ProviderError> {
370 self.account_work_tx
371 .send(AccountWorkerJob::AccountMultiproof { input: Box::new(input) })
372 .map_err(|err| {
373 let error =
374 ProviderError::other(std::io::Error::other("account workers unavailable"));
375
376 let AccountWorkerJob::AccountMultiproof { input } = err.0;
377 let ProofResultContext { sender: result_tx, state, start_time: start } =
378 input.into_proof_result_sender();
379
380 let _ = result_tx.send(ProofResultMessage {
381 result: Err(StateRootTaskError::ProofDispatch(error.clone())),
382 elapsed: start.elapsed(),
383 state,
384 });
385
386 error
387 })
388 }
389}
390
391#[derive(Clone, Debug)]
393pub struct ProofTaskCtx<Factory> {
394 factory: Factory,
396 #[cfg(feature = "trie-debug")]
398 proof_jitter: Option<Duration>,
399}
400
401impl<Factory> ProofTaskCtx<Factory> {
402 pub const fn new(factory: Factory) -> Self {
404 Self {
405 factory,
406 #[cfg(feature = "trie-debug")]
407 proof_jitter: None,
408 }
409 }
410
411 #[cfg(feature = "trie-debug")]
413 pub const fn with_proof_jitter(mut self, jitter: Option<Duration>) -> Self {
414 self.proof_jitter = jitter;
415 self
416 }
417}
418
419#[derive(Debug)]
421pub struct ProofTaskTx<Provider> {
422 provider: Provider,
424
425 id: usize,
427}
428
429impl<Provider> ProofTaskTx<Provider> {
430 const fn new(provider: Provider, id: usize) -> Self {
432 Self { provider, id }
433 }
434}
435
436impl<Provider> ProofTaskTx<Provider>
437where
438 Provider: TrieCursorFactory + HashedCursorFactory,
439{
440 fn compute_v2_storage_proof<TC, HC>(
441 &self,
442 input: StorageProofInput,
443 calculator: &mut proof_v2::StorageProofCalculator<TC, HC>,
444 ) -> Result<StorageProofResult, StateProofError>
445 where
446 TC: TrieStorageCursor,
447 HC: HashedStorageCursor<Value = U256>,
448 {
449 let StorageProofInput { hashed_address, mut targets, needs_root } = input;
450
451 let span = debug_span!(
452 target: "trie::proof_task",
453 "Storage proof calculation",
454 n = %targets.len(),
455 );
456 let _span_guard = span.enter();
457
458 let proof_start = Instant::now();
459
460 let (proof, root) = if targets.is_empty() {
462 let root_node = calculator.storage_root_node(hashed_address)?;
463 let root = calculator.compute_root_hash(core::slice::from_ref(&root_node))?;
464 (vec![root_node], root)
465 } else {
466 let root = if needs_root && targets.iter().all(|target| target.parent.is_known()) {
470 let root_node = calculator.storage_root_node(hashed_address)?;
471 calculator.compute_root_hash(core::slice::from_ref(&root_node))?
472 } else {
473 None
474 };
475
476 let proof = calculator.storage_proof(hashed_address, &mut targets)?;
477 let root = if root.is_some() { root } else { calculator.compute_root_hash(&proof)? };
478 (proof, root)
479 };
480
481 trace!(
482 target: "trie::proof_task",
483 hashed_address = ?hashed_address,
484 proof_time_us = proof_start.elapsed().as_micros(),
485 ?root,
486 worker_id = self.id,
487 "Completed V2 storage proof calculation"
488 );
489
490 Ok(StorageProofResult { proof, root })
491 }
492}
493
494pub type ProofResultSender = CrossbeamSender<ProofResultMessage>;
500
501#[derive(Debug)]
507pub struct ProofResultMessage {
508 pub result: Result<DecodedMultiProofV2, StateRootTaskError>,
510 pub elapsed: Duration,
512 pub state: HashedPostState,
514}
515
516#[derive(Debug, Clone)]
521pub struct ProofResultContext {
522 pub sender: ProofResultSender,
524 pub state: HashedPostState,
526 pub start_time: Instant,
528}
529
530impl ProofResultContext {
531 pub const fn new(
533 sender: ProofResultSender,
534 state: HashedPostState,
535 start_time: Instant,
536 ) -> Self {
537 Self { sender, state, start_time }
538 }
539}
540
541#[derive(Debug)]
543pub(crate) struct StorageProofResult {
544 pub proof: Vec<ProofTrieNodeV2>,
546 pub root: Option<B256>,
548}
549
550impl StorageProofResult {
551 const fn root(&self) -> Option<B256> {
553 self.root
554 }
555}
556
557#[derive(Debug)]
559pub struct StorageProofResultMessage {
560 #[allow(dead_code)]
562 pub(crate) hashed_address: B256,
563 pub(crate) result: Result<StorageProofResult, StateProofError>,
565}
566
567#[derive(Debug)]
569pub(crate) enum StorageWorkerJob {
570 StorageProof {
572 input: StorageProofInput,
574 proof_result_sender: CrossbeamSender<StorageProofResultMessage>,
576 },
577}
578
579struct StorageProofWorker<Factory> {
584 task_ctx: ProofTaskCtx<Factory>,
586 work_rx: CrossbeamReceiver<StorageWorkerJob>,
588 worker_id: usize,
590 availability: Arc<AvailabilitySheet>,
592 cached_storage_roots: Arc<DashMap<B256, B256>>,
594 #[cfg(feature = "metrics")]
596 metrics: ProofTaskTrieMetrics,
597 #[cfg(feature = "metrics")]
599 cursor_metrics: ProofTaskCursorMetrics,
600}
601
602impl<Factory> StorageProofWorker<Factory>
603where
604 Factory: DatabaseProviderROFactory<Provider: TrieCursorFactory + HashedCursorFactory>,
605{
606 const fn new(
608 task_ctx: ProofTaskCtx<Factory>,
609 work_rx: CrossbeamReceiver<StorageWorkerJob>,
610 worker_id: usize,
611 availability: Arc<AvailabilitySheet>,
612 cached_storage_roots: Arc<DashMap<B256, B256>>,
613 #[cfg(feature = "metrics")] metrics: ProofTaskTrieMetrics,
614 #[cfg(feature = "metrics")] cursor_metrics: ProofTaskCursorMetrics,
615 ) -> Self {
616 Self {
617 task_ctx,
618 work_rx,
619 worker_id,
620 availability,
621 cached_storage_roots,
622 #[cfg(feature = "metrics")]
623 metrics,
624 #[cfg(feature = "metrics")]
625 cursor_metrics,
626 }
627 }
628
629 fn run(mut self) -> ProviderResult<()> {
647 let provider = self.task_ctx.factory.database_provider_ro()?;
649 let proof_tx = ProofTaskTx::new(provider, self.worker_id);
650
651 trace!(
652 target: "trie::proof_task",
653 worker_id = self.worker_id,
654 "Storage worker started"
655 );
656
657 let mut storage_proofs_processed = 0u64;
658 let mut cursor_metrics_cache = ProofTaskCursorMetricsCache::default();
659 let trie_cursor = proof_tx.provider.storage_trie_cursor(B256::ZERO)?;
660 let hashed_cursor = proof_tx.provider.hashed_storage_cursor(B256::ZERO)?;
661 let instrumented_trie_cursor =
662 InstrumentedTrieCursor::new(trie_cursor, &mut cursor_metrics_cache.storage_trie_cursor);
663 let instrumented_hashed_cursor = InstrumentedHashedCursor::new(
664 hashed_cursor,
665 &mut cursor_metrics_cache.storage_hashed_cursor,
666 );
667 let mut v2_calculator = proof_v2::StorageProofCalculator::new_storage(
668 instrumented_trie_cursor,
669 instrumented_hashed_cursor,
670 );
671
672 self.availability.mark_idle(self.worker_id);
674
675 let mut total_idle_time = Duration::ZERO;
676 let mut idle_start = Instant::now();
677
678 while let Ok(job) = self.work_rx.recv() {
679 total_idle_time += idle_start.elapsed();
680
681 self.availability.mark_busy(self.worker_id);
683
684 #[cfg(feature = "trie-debug")]
685 if let Some(max_jitter) = self.task_ctx.proof_jitter {
686 let jitter =
687 Duration::from_nanos(rand::random_range(0..=max_jitter.as_nanos() as u64));
688 trace!(
689 target: "trie::proof_task",
690 worker_id = self.worker_id,
691 jitter_us = jitter.as_micros(),
692 "Storage worker applying proof jitter"
693 );
694 std::thread::sleep(jitter);
695 }
696
697 match job {
698 StorageWorkerJob::StorageProof { input, proof_result_sender } => {
699 self.process_storage_proof(
700 &proof_tx,
701 &mut v2_calculator,
702 input,
703 proof_result_sender,
704 &mut storage_proofs_processed,
705 );
706 }
707 }
708
709 self.availability.mark_idle(self.worker_id);
711
712 idle_start = Instant::now();
713 }
714
715 drop(v2_calculator);
717
718 trace!(
719 target: "trie::proof_task",
720 worker_id = self.worker_id,
721 storage_proofs_processed,
722 total_idle_time_us = total_idle_time.as_micros(),
723 "Storage worker shutting down"
724 );
725
726 #[cfg(feature = "metrics")]
727 {
728 self.metrics.record_storage_worker_idle_time(total_idle_time);
729 self.cursor_metrics.record(&mut cursor_metrics_cache);
730 }
731
732 Ok(())
733 }
734
735 fn process_storage_proof<Provider, TC, HC>(
737 &self,
738 proof_tx: &ProofTaskTx<Provider>,
739 v2_calculator: &mut proof_v2::StorageProofCalculator<TC, HC>,
740 input: StorageProofInput,
741 proof_result_sender: CrossbeamSender<StorageProofResultMessage>,
742 storage_proofs_processed: &mut u64,
743 ) where
744 Provider: TrieCursorFactory + HashedCursorFactory,
745 TC: TrieStorageCursor,
746 HC: HashedStorageCursor<Value = U256>,
747 {
748 let hashed_address = input.hashed_address;
749 let proof_start = Instant::now();
750
751 trace!(
752 target: "trie::proof_task",
753 worker_id = self.worker_id,
754 hashed_address = ?hashed_address,
755 targets_len = input.targets.len(),
756 "Processing V2 storage proof"
757 );
758
759 let result = proof_tx.compute_v2_storage_proof(input, v2_calculator);
760
761 let proof_elapsed = proof_start.elapsed();
762 *storage_proofs_processed += 1;
763
764 let root = result.as_ref().ok().and_then(|result| result.root());
765
766 if proof_result_sender.send(StorageProofResultMessage { hashed_address, result }).is_err() {
767 trace!(
768 target: "trie::proof_task",
769 worker_id = self.worker_id,
770 hashed_address = ?hashed_address,
771 storage_proofs_processed,
772 "Proof result receiver dropped, discarding result"
773 );
774 }
775
776 if let Some(root) = root {
777 self.cached_storage_roots.insert(hashed_address, root);
778 }
779
780 trace!(
781 target: "trie::proof_task",
782 worker_id = self.worker_id,
783 hashed_address = ?hashed_address,
784 proof_time_us = proof_elapsed.as_micros(),
785 total_processed = storage_proofs_processed,
786 ?root,
787 "Storage proof completed"
788 );
789 }
790}
791
792struct AccountProofWorker<Factory> {
797 task_ctx: ProofTaskCtx<Factory>,
799 work_rx: CrossbeamReceiver<AccountWorkerJob>,
801 worker_id: usize,
803 storage_work_tx: CrossbeamSender<StorageWorkerJob>,
805 availability: Arc<AvailabilitySheet>,
807 cached_storage_roots: Arc<DashMap<B256, B256>>,
809 #[cfg(feature = "metrics")]
811 metrics: ProofTaskTrieMetrics,
812 #[cfg(feature = "metrics")]
814 cursor_metrics: ProofTaskCursorMetrics,
815}
816
817impl<Factory> AccountProofWorker<Factory>
818where
819 Factory: DatabaseProviderROFactory<Provider: TrieCursorFactory + HashedCursorFactory>,
820{
821 #[expect(clippy::too_many_arguments)]
823 const fn new(
824 task_ctx: ProofTaskCtx<Factory>,
825 work_rx: CrossbeamReceiver<AccountWorkerJob>,
826 worker_id: usize,
827 storage_work_tx: CrossbeamSender<StorageWorkerJob>,
828 availability: Arc<AvailabilitySheet>,
829 cached_storage_roots: Arc<DashMap<B256, B256>>,
830 #[cfg(feature = "metrics")] metrics: ProofTaskTrieMetrics,
831 #[cfg(feature = "metrics")] cursor_metrics: ProofTaskCursorMetrics,
832 ) -> Self {
833 Self {
834 task_ctx,
835 work_rx,
836 worker_id,
837 storage_work_tx,
838 availability,
839 cached_storage_roots,
840 #[cfg(feature = "metrics")]
841 metrics,
842 #[cfg(feature = "metrics")]
843 cursor_metrics,
844 }
845 }
846
847 fn run(mut self) -> ProviderResult<()> {
865 let provider = self.task_ctx.factory.database_provider_ro()?;
866
867 trace!(
868 target: "trie::proof_task",
869 worker_id=self.worker_id,
870 "Account worker started"
871 );
872
873 let mut account_proofs_processed = 0u64;
874 let mut cursor_metrics_cache = ProofTaskCursorMetricsCache::default();
875
876 let account_trie_cursor = provider.account_trie_cursor()?;
879 let account_hashed_cursor = provider.hashed_account_cursor()?;
880
881 let storage_trie_cursor = provider.storage_trie_cursor(B256::ZERO)?;
882 let storage_hashed_cursor = provider.hashed_storage_cursor(B256::ZERO)?;
883
884 let instrumented_account_trie_cursor = InstrumentedTrieCursor::new(
885 account_trie_cursor,
886 &mut cursor_metrics_cache.account_trie_cursor,
887 );
888 let instrumented_account_hashed_cursor = InstrumentedHashedCursor::new(
889 account_hashed_cursor,
890 &mut cursor_metrics_cache.account_hashed_cursor,
891 );
892 let instrumented_storage_trie_cursor = InstrumentedTrieCursor::new(
893 storage_trie_cursor,
894 &mut cursor_metrics_cache.storage_trie_cursor,
895 );
896 let instrumented_storage_hashed_cursor = InstrumentedHashedCursor::new(
897 storage_hashed_cursor,
898 &mut cursor_metrics_cache.storage_hashed_cursor,
899 );
900
901 let mut v2_account_calculator =
902 proof_v2::ProofCalculator::<
903 _,
904 _,
905 AsyncAccountValueEncoder<
906 InstrumentedTrieCursor<
907 '_,
908 <Factory::Provider as TrieCursorFactory>::StorageTrieCursor<'_>,
909 >,
910 InstrumentedHashedCursor<
911 '_,
912 <Factory::Provider as HashedCursorFactory>::StorageCursor<'_>,
913 >,
914 >,
915 >::new(instrumented_account_trie_cursor, instrumented_account_hashed_cursor);
916 let v2_storage_calculator =
917 Rc::new(RefCell::new(proof_v2::StorageProofCalculator::new_storage(
918 instrumented_storage_trie_cursor,
919 instrumented_storage_hashed_cursor,
920 )));
921
922 self.availability.mark_idle(self.worker_id);
924
925 let mut total_idle_time = Duration::ZERO;
926 let mut idle_start = Instant::now();
927 let mut value_encoder_stats_cache = ValueEncoderStats::default();
928
929 while let Ok(job) = self.work_rx.recv() {
930 total_idle_time += idle_start.elapsed();
931
932 self.availability.mark_busy(self.worker_id);
934
935 #[cfg(feature = "trie-debug")]
936 if let Some(max_jitter) = self.task_ctx.proof_jitter {
937 let jitter =
938 Duration::from_nanos(rand::random_range(0..=max_jitter.as_nanos() as u64));
939 trace!(
940 target: "trie::proof_task",
941 worker_id = self.worker_id,
942 jitter_us = jitter.as_micros(),
943 "Account worker applying proof jitter"
944 );
945 std::thread::sleep(jitter);
946 }
947
948 match job {
949 AccountWorkerJob::AccountMultiproof { input } => {
950 let value_encoder_stats = self.process_account_multiproof::<Factory::Provider>(
951 &mut v2_account_calculator,
952 v2_storage_calculator.clone(),
953 *input,
954 &mut account_proofs_processed,
955 );
956 total_idle_time += value_encoder_stats.storage_wait_time;
957 value_encoder_stats_cache.extend(&value_encoder_stats);
958 }
959 }
960
961 self.availability.mark_idle(self.worker_id);
963
964 idle_start = Instant::now();
965 }
966
967 drop(v2_account_calculator);
969 drop(v2_storage_calculator);
970
971 trace!(
972 target: "trie::proof_task",
973 worker_id=self.worker_id,
974 account_proofs_processed,
975 total_idle_time_us = total_idle_time.as_micros(),
976 "Account worker shutting down"
977 );
978
979 #[cfg(feature = "metrics")]
980 {
981 self.metrics.record_account_worker_idle_time(total_idle_time);
982 self.cursor_metrics.record(&mut cursor_metrics_cache);
983 self.metrics.record_value_encoder_stats(&value_encoder_stats_cache);
984 }
985
986 Ok(())
987 }
988
989 fn compute_v2_account_multiproof<'a, Provider>(
990 &self,
991 v2_account_calculator: &mut V2AccountProofCalculator<'a, Provider>,
992 v2_storage_calculator: Rc<RefCell<V2StorageProofCalculator<'a, Provider>>>,
993 targets: MultiProofTargetsV2,
994 ) -> Result<(DecodedMultiProofV2, ValueEncoderStats), StateRootTaskError>
995 where
996 Provider: TrieCursorFactory + HashedCursorFactory + 'a,
997 {
998 let MultiProofTargetsV2 { mut account_targets, storage_targets } = targets;
999
1000 let span = debug_span!(
1001 target: "trie::proof_task",
1002 "Account multiproof calculation",
1003 account_targets = account_targets.len(),
1004 storage_targets = storage_targets.values().map(|t| t.len()).sum::<usize>(),
1005 );
1006 let _span_guard = span.enter();
1007
1008 trace!(target: "trie::proof_task", "Processing V2 account multiproof");
1009
1010 let storage_proof_receivers =
1011 dispatch_v2_storage_proofs(&self.storage_work_tx, &account_targets, storage_targets)?;
1012
1013 let mut value_encoder = AsyncAccountValueEncoder::new(
1014 storage_proof_receivers,
1015 self.cached_storage_roots.clone(),
1016 v2_storage_calculator,
1017 );
1018
1019 let account_proofs =
1020 v2_account_calculator.proof(&mut value_encoder, &mut account_targets)?;
1021
1022 let (storage_proofs, value_encoder_stats) = value_encoder.finalize()?;
1023
1024 let proof = DecodedMultiProofV2 { account_proofs, storage_proofs };
1025
1026 Ok((proof, value_encoder_stats))
1027 }
1028
1029 fn process_account_multiproof<'a, Provider>(
1033 &self,
1034 v2_account_calculator: &mut V2AccountProofCalculator<'a, Provider>,
1035 v2_storage_calculator: Rc<RefCell<V2StorageProofCalculator<'a, Provider>>>,
1036 input: AccountMultiproofInput,
1037 account_proofs_processed: &mut u64,
1038 ) -> ValueEncoderStats
1039 where
1040 Provider: TrieCursorFactory + HashedCursorFactory + 'a,
1041 {
1042 let proof_start = Instant::now();
1043
1044 let AccountMultiproofInput { targets, proof_result_sender } = input;
1045 let (result, value_encoder_stats) = match self.compute_v2_account_multiproof::<Provider>(
1046 v2_account_calculator,
1047 v2_storage_calculator,
1048 targets,
1049 ) {
1050 Ok((proof, stats)) => (Ok(proof), stats),
1051 Err(e) => (Err(e), ValueEncoderStats::default()),
1052 };
1053
1054 let ProofResultContext { sender: result_tx, state, start_time: start } =
1055 proof_result_sender;
1056
1057 let proof_elapsed = proof_start.elapsed();
1058 let total_elapsed = start.elapsed();
1059 *account_proofs_processed += 1;
1060
1061 if result_tx.send(ProofResultMessage { result, elapsed: total_elapsed, state }).is_err() {
1063 trace!(
1064 target: "trie::proof_task",
1065 worker_id=self.worker_id,
1066 account_proofs_processed,
1067 "Account multiproof receiver dropped, discarding result"
1068 );
1069 }
1070
1071 trace!(
1072 target: "trie::proof_task",
1073 proof_time_us = proof_elapsed.as_micros(),
1074 total_elapsed_us = total_elapsed.as_micros(),
1075 total_processed = account_proofs_processed,
1076 "Account multiproof completed"
1077 );
1078
1079 value_encoder_stats
1080 }
1081}
1082
1083fn dispatch_v2_storage_proofs(
1091 storage_work_tx: &CrossbeamSender<StorageWorkerJob>,
1092 account_targets: &[ProofV2Target],
1093 storage_targets: B256Map<Vec<ProofV2Target>>,
1094) -> Result<B256Map<CrossbeamReceiver<StorageProofResultMessage>>, StateRootTaskError> {
1095 if storage_targets.is_empty() {
1096 return Ok(B256Map::default())
1097 }
1098
1099 let mut storage_proof_receivers =
1100 B256Map::with_capacity_and_hasher(storage_targets.len(), Default::default());
1101
1102 let account_target_addresses: B256Set = account_targets.iter().map(|t| t.key()).collect();
1104
1105 let mut sorted_storage_targets: Vec<_> = storage_targets.into_iter().collect();
1109 sorted_storage_targets.sort_unstable_by_key(|(addr, _)| *addr);
1110
1111 for (hashed_address, targets) in sorted_storage_targets {
1113 let (result_tx, result_rx) = crossbeam_channel::unbounded();
1115 let needs_root = account_target_addresses.contains(&hashed_address);
1116 let input = StorageProofInput::new(hashed_address, targets, needs_root);
1117
1118 storage_work_tx
1119 .send(StorageWorkerJob::StorageProof { input, proof_result_sender: result_tx })
1120 .map_err(|_| {
1121 StateRootTaskError::Other(format!(
1122 "Failed to queue storage proof for {hashed_address:?}: storage worker pool unavailable",
1123 ))
1124 })?;
1125
1126 storage_proof_receivers.insert(hashed_address, result_rx);
1127 }
1128
1129 Ok(storage_proof_receivers)
1130}
1131
1132#[derive(Debug)]
1134pub struct StorageProofInput {
1135 pub hashed_address: B256,
1137 pub targets: Vec<ProofV2Target>,
1139 pub needs_root: bool,
1141}
1142
1143impl StorageProofInput {
1144 pub const fn new(hashed_address: B256, targets: Vec<ProofV2Target>, needs_root: bool) -> Self {
1146 Self { hashed_address, targets, needs_root }
1147 }
1148}
1149
1150#[derive(Debug)]
1152pub struct AccountMultiproofInput {
1153 pub targets: MultiProofTargetsV2,
1155 pub proof_result_sender: ProofResultContext,
1157}
1158
1159impl AccountMultiproofInput {
1160 fn into_proof_result_sender(self) -> ProofResultContext {
1162 self.proof_result_sender
1163 }
1164}
1165
1166#[derive(Debug)]
1168enum AccountWorkerJob {
1169 AccountMultiproof {
1171 input: Box<AccountMultiproofInput>,
1173 },
1174}
1175
1176#[cfg(test)]
1177mod tests {
1178 use super::*;
1179 use reth_chainspec::ChainSpec;
1180 use reth_provider::test_utils::create_test_provider_factory_with_chain_spec;
1181 use std::sync::Arc;
1182
1183 fn test_ctx<Factory>(factory: Factory) -> ProofTaskCtx<Factory> {
1184 ProofTaskCtx::new(factory)
1185 }
1186
1187 #[test]
1189 fn spawn_proof_workers_creates_handle() {
1190 let chain_spec = Arc::new(ChainSpec::default());
1191 let anchor_hash = chain_spec.genesis_hash();
1192 let provider_factory = create_test_provider_factory_with_chain_spec(chain_spec);
1193 let factory = reth_provider::providers::OverlayStateProviderFactory::new(
1194 provider_factory,
1195 reth_storage_overlay::OverlayManager::<
1196 reth_ethereum_primitives::EthPrimitives,
1197 >::default()
1198 .overlay_builder(anchor_hash),
1199 );
1200 let ctx = test_ctx(factory);
1201
1202 let runtime = reth_tasks::Runtime::test();
1203 let (proof_result_tx, _) = unbounded();
1204 let proof_handle = ProofWorkerHandle::new(&runtime, ctx, false, proof_result_tx);
1205
1206 let _cloned_handle = proof_handle.clone();
1208
1209 drop(proof_handle);
1211 }
1212}