1use crate::error::StateRootTaskError;
11use alloy_evm::block::OnStateHook;
12use alloy_primitives::{keccak256, map::B256Map, B256};
13use reth_trie::{
14 updates::TrieUpdates, HashedPostState, HashedStorage, MultiProofTargetsV2, ProofV2Target,
15};
16use revm::state::EvmState;
17use std::{fmt, sync::Arc};
18use tracing::trace;
19
20#[derive(Debug)]
22pub enum StateRootMessage {
23 PrefetchProofs(MultiProofTargetsV2),
25 StateUpdate(EvmState),
27 HashedStateUpdate(HashedPostState),
29 FinishedStateUpdates,
34}
35
36#[derive(Debug, Clone)]
39pub struct StateRootComputeOutcome {
40 pub state_root: B256,
42 pub trie_updates: Arc<TrieUpdates>,
44 pub hashed_state: Arc<HashedPostState>,
46 #[cfg(feature = "trie-debug")]
49 pub debug_recorders: Vec<(Option<B256>, reth_trie_sparse::debug_recorder::TrieDebugRecorder)>,
50}
51
52#[derive(Debug)]
60pub struct StateRootHandle {
61 cached_trie_state_root: B256,
63 hint: Option<StateRootHintStream>,
65 authoritative: Option<StateRootUpdateStream>,
72 cancel_guard: StateRootTaskCancelGuard,
74 state_root_rx:
76 Option<std::sync::mpsc::Receiver<Result<StateRootComputeOutcome, StateRootTaskError>>>,
77 hashed_state_rx: Option<std::sync::mpsc::Receiver<Arc<HashedPostState>>>,
79}
80
81impl StateRootHandle {
82 pub fn new(
84 cached_trie_state_root: B256,
85 updates_tx: crossbeam_channel::Sender<StateRootMessage>,
86 cancel_guard: StateRootTaskCancelGuard,
87 state_root_rx: std::sync::mpsc::Receiver<
88 Result<StateRootComputeOutcome, StateRootTaskError>,
89 >,
90 hashed_state_rx: std::sync::mpsc::Receiver<Arc<HashedPostState>>,
91 ) -> Self {
92 let sink: Arc<dyn StateRootSink> = Arc::new(SparseTrieStateRootSink::new(updates_tx));
93 Self {
94 cached_trie_state_root,
95 hint: Some(StateRootHintStream::new(Arc::clone(&sink))),
96 authoritative: Some(StateRootUpdateStream::new(sink)),
97 cancel_guard,
98 state_root_rx: Some(state_root_rx),
99 hashed_state_rx: Some(hashed_state_rx),
100 }
101 }
102
103 pub const fn cached_trie_state_root(&self) -> B256 {
105 self.cached_trie_state_root
106 }
107
108 pub const fn take_hint_stream(&mut self) -> StateRootHintStream {
114 self.hint.take().expect("hint stream already taken")
115 }
116
117 pub fn take_execution_hook(&mut self) -> StateRootUpdateHook {
126 self.take_hashed_update_stream().into_state_hook()
127 }
128
129 pub const fn take_hashed_update_stream(&mut self) -> StateRootUpdateStream {
139 self.authoritative.take().expect("authoritative update capability already taken")
140 }
141
142 pub fn state_root(&mut self) -> Result<StateRootComputeOutcome, StateRootTaskError> {
148 self.state_root_rx
149 .take()
150 .expect("state_root already taken")
151 .recv()
152 .map_err(|_| StateRootTaskError::Other("sparse trie task dropped".to_string()))?
153 }
154
155 pub const fn take_state_root_rx(
161 &mut self,
162 ) -> std::sync::mpsc::Receiver<Result<StateRootComputeOutcome, StateRootTaskError>> {
163 self.state_root_rx.take().expect("state_root already taken")
164 }
165
166 pub const fn take_hashed_state_rx(
172 &mut self,
173 ) -> std::sync::mpsc::Receiver<Arc<HashedPostState>> {
174 self.hashed_state_rx.take().expect("hashed_state already taken")
175 }
176
177 pub fn into_payload_state_root_handle(mut self) -> PayloadStateRootHandle {
182 let hook = self.take_execution_hook();
183 PayloadStateRootHandle {
184 name: "sparse-trie",
185 hook: Some(hook),
186 cancel_guard: Some(self.cancel_guard),
187 state_root_rx: self.state_root_rx.take(),
188 hashed_state_rx: self.hashed_state_rx.take(),
189 }
190 }
191}
192
193#[derive(Debug)]
199pub struct StateRootTaskCancelGuard(#[allow(dead_code)] crossbeam_channel::Sender<()>);
200
201impl StateRootTaskCancelGuard {
202 pub fn channel() -> (Self, crossbeam_channel::Receiver<()>) {
204 let (tx, rx) = crossbeam_channel::bounded(0);
205 (Self(tx), rx)
206 }
207}
208
209pub struct PayloadStateRootHandle {
211 name: &'static str,
212 hook: Option<StateRootUpdateHook>,
214 cancel_guard: Option<StateRootTaskCancelGuard>,
216 state_root_rx:
217 Option<std::sync::mpsc::Receiver<Result<StateRootComputeOutcome, StateRootTaskError>>>,
218 hashed_state_rx: Option<std::sync::mpsc::Receiver<Arc<HashedPostState>>>,
219}
220
221impl fmt::Debug for PayloadStateRootHandle {
222 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
223 f.debug_struct("PayloadStateRootHandle")
224 .field("name", &self.name)
225 .field("has_hook", &self.hook.is_some())
226 .field("has_cancel_guard", &self.cancel_guard.is_some())
227 .field("has_state_root_rx", &self.state_root_rx.is_some())
228 .field("has_hashed_state_rx", &self.hashed_state_rx.is_some())
229 .finish()
230 }
231}
232
233impl PayloadStateRootHandle {
234 pub const fn new(
239 name: &'static str,
240 hook: Option<StateRootUpdateHook>,
241 state_root_rx: std::sync::mpsc::Receiver<
242 Result<StateRootComputeOutcome, StateRootTaskError>,
243 >,
244 hashed_state_rx: Option<std::sync::mpsc::Receiver<Arc<HashedPostState>>>,
245 ) -> Self {
246 Self { name, hook, cancel_guard: None, state_root_rx: Some(state_root_rx), hashed_state_rx }
247 }
248
249 pub const fn name(&self) -> &'static str {
251 self.name
252 }
253
254 pub const fn take_state_hook(&mut self) -> StateRootUpdateHook {
260 self.hook.take().expect("payload state root task missing execution hook")
261 }
262
263 pub fn state_root(&mut self) -> Result<StateRootComputeOutcome, StateRootTaskError> {
269 self.state_root_rx
270 .take()
271 .expect("state_root already taken")
272 .recv()
273 .map_err(|_| StateRootTaskError::Other("state root task dropped".to_string()))?
274 }
275
276 pub const fn take_state_root_rx(
284 &mut self,
285 ) -> std::sync::mpsc::Receiver<Result<StateRootComputeOutcome, StateRootTaskError>> {
286 self.state_root_rx.take().expect("state_root already taken")
287 }
288
289 pub const fn try_take_hashed_state_rx(
292 &mut self,
293 ) -> Option<std::sync::mpsc::Receiver<Arc<HashedPostState>>> {
294 self.hashed_state_rx.take()
295 }
296}
297
298#[derive(Debug, Clone, Default)]
304pub struct StateAccessHint {
305 pub accounts: Vec<B256>,
307 pub storages: B256Map<Vec<B256>>,
309}
310
311impl From<MultiProofTargetsV2> for StateAccessHint {
312 fn from(targets: MultiProofTargetsV2) -> Self {
313 Self {
314 accounts: targets.account_targets.into_iter().map(|target| target.key()).collect(),
315 storages: targets
316 .storage_targets
317 .into_iter()
318 .map(|(account, slots)| {
319 (account, slots.into_iter().map(|target| target.key()).collect())
320 })
321 .collect(),
322 }
323 }
324}
325
326impl From<StateAccessHint> for MultiProofTargetsV2 {
327 fn from(hint: StateAccessHint) -> Self {
328 Self {
329 account_targets: hint.accounts.into_iter().map(ProofV2Target::from).collect(),
330 storage_targets: hint
331 .storages
332 .into_iter()
333 .map(|(account, slots)| {
334 (account, slots.into_iter().map(ProofV2Target::from).collect())
335 })
336 .collect(),
337 }
338 }
339}
340
341pub trait StateRootSink: Send + Sync + 'static {
343 fn on_access_hint(&self, _hint: StateAccessHint) {}
345
346 fn on_state_update(&self, state: EvmState);
348
349 fn on_hashed_state_update(&self, state: HashedPostState);
351
352 fn on_updates_finished(&self);
354}
355
356#[derive(Clone)]
358pub struct StateRootHintStream {
359 inner: Arc<dyn StateRootSink>,
360}
361
362impl fmt::Debug for StateRootHintStream {
363 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
364 f.debug_struct("StateRootHintStream").finish_non_exhaustive()
365 }
366}
367
368impl StateRootHintStream {
369 pub fn new(inner: Arc<dyn StateRootSink>) -> Self {
371 Self { inner }
372 }
373
374 pub fn on_access_hint(&self, hint: StateAccessHint) {
376 self.inner.on_access_hint(hint);
377 }
378}
379
380pub struct StateRootUpdateStream {
392 inner: Arc<dyn StateRootSink>,
393}
394
395impl fmt::Debug for StateRootUpdateStream {
396 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
397 f.debug_struct("StateRootUpdateStream").finish_non_exhaustive()
398 }
399}
400
401impl StateRootUpdateStream {
402 pub fn new(inner: Arc<dyn StateRootSink>) -> Self {
404 Self { inner }
405 }
406
407 pub fn on_hashed_state_update(&self, state: HashedPostState) {
409 self.inner.on_hashed_state_update(state);
410 }
411
412 pub fn finish(self) {
414 self.inner.on_updates_finished();
415 }
416
417 pub fn into_state_hook(self) -> StateRootUpdateHook {
422 StateRootUpdateHook { inner: self.inner }
423 }
424}
425
426pub struct StateRootUpdateHook {
439 inner: Arc<dyn StateRootSink>,
440}
441
442impl fmt::Debug for StateRootUpdateHook {
443 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
444 f.debug_struct("StateRootUpdateHook").finish_non_exhaustive()
445 }
446}
447
448impl OnStateHook for StateRootUpdateHook {
449 fn on_state(&mut self, state: EvmState) {
450 self.inner.on_state_update(state);
451 }
452}
453
454impl Drop for StateRootUpdateHook {
455 fn drop(&mut self) {
456 if std::thread::panicking() {
459 return;
460 }
461 self.inner.on_updates_finished();
462 }
463}
464
465#[derive(Debug, Clone)]
466struct SparseTrieStateRootSink {
467 sender: crossbeam_channel::Sender<StateRootMessage>,
468}
469
470impl SparseTrieStateRootSink {
471 const fn new(sender: crossbeam_channel::Sender<StateRootMessage>) -> Self {
472 Self { sender }
473 }
474}
475
476impl StateRootSink for SparseTrieStateRootSink {
477 fn on_access_hint(&self, hint: StateAccessHint) {
478 let _ = self.sender.send(StateRootMessage::PrefetchProofs(hint.into()));
479 }
480
481 fn on_state_update(&self, state: EvmState) {
482 let _ = self.sender.send(StateRootMessage::StateUpdate(state));
483 }
484
485 fn on_hashed_state_update(&self, state: HashedPostState) {
486 let _ = self.sender.send(StateRootMessage::HashedStateUpdate(state));
487 }
488
489 fn on_updates_finished(&self) {
490 let _ = self.sender.send(StateRootMessage::FinishedStateUpdates);
491 }
492}
493
494pub fn evm_state_to_hashed_post_state(update: EvmState) -> HashedPostState {
496 let mut hashed_state = HashedPostState::with_capacity(update.len());
497
498 for (address, account) in update {
499 if account.is_touched() {
500 let hashed_address = keccak256(address);
501 trace!(target: "trie::parallel::sparse", ?address, ?hashed_address, "Adding account to state update");
502
503 let destroyed = account.is_selfdestructed();
504 if account.info != account.original_info() {
505 let info = if destroyed { None } else { Some(account.info.into()) };
506 hashed_state.accounts.insert(hashed_address, info);
507 }
508
509 let mut changed_storage_iter = account
510 .storage
511 .into_iter()
512 .filter(|(_slot, value)| value.is_changed())
513 .map(|(slot, value)| (keccak256(B256::from(slot)), value.present_value))
514 .peekable();
515
516 if !destroyed && changed_storage_iter.peek().is_some() {
517 hashed_state
518 .storages
519 .insert(hashed_address, HashedStorage::from_iter(false, changed_storage_iter));
520 }
521 }
522 }
523
524 hashed_state
525}
526
527#[cfg(test)]
528mod tests {
529 use super::*;
530 use alloy_primitives::{Address, U256};
531 use revm::state::{Account, EvmStorageSlot, TransactionId};
532 use std::{
533 sync::atomic::{AtomicUsize, Ordering},
534 time::Duration,
535 };
536
537 #[test]
538 fn created_selfdestruct_does_not_emit_storage() {
539 let address = Address::repeat_byte(0x01);
540 let mut account = Account::default();
541 account.mark_touch();
542 assert!(account.mark_created_locally());
543 assert!(account.mark_selfdestructed_locally());
544 account.info.nonce = 1;
545 account.storage.insert(
546 U256::from(1),
547 EvmStorageSlot::new_changed(U256::ZERO, U256::from(2), TransactionId::ZERO),
548 );
549
550 let hashed_state =
551 evm_state_to_hashed_post_state(EvmState::from_iter([(address, account)]));
552 let hashed_address = keccak256(address);
553
554 assert_eq!(hashed_state.accounts.get(&hashed_address), Some(&None));
555 assert!(!hashed_state.storages.contains_key(&hashed_address));
556 }
557
558 #[test]
559 fn existing_selfdestruct_does_not_emit_storage() {
560 let address = Address::repeat_byte(0x02);
561 let mut account = Account::default();
562 account.info.nonce = 1;
563 account.set_current_info_as_original();
564 account.mark_touch();
565 assert!(account.mark_selfdestructed_locally());
566 account.selfdestruct();
567 account.storage.insert(
568 U256::from(1),
569 EvmStorageSlot::new_changed(U256::ZERO, U256::from(2), TransactionId::ZERO),
570 );
571
572 let hashed_state =
573 evm_state_to_hashed_post_state(EvmState::from_iter([(address, account)]));
574 let hashed_address = keccak256(address);
575
576 assert_eq!(hashed_state.accounts.get(&hashed_address), Some(&None));
577 assert!(!hashed_state.storages.contains_key(&hashed_address));
578 }
579
580 #[derive(Default)]
581 struct CountingSink {
582 access_hints: AtomicUsize,
583 state_updates: AtomicUsize,
584 hashed_state_updates: AtomicUsize,
585 finished_updates: AtomicUsize,
586 }
587
588 impl StateRootSink for CountingSink {
589 fn on_access_hint(&self, hint: StateAccessHint) {
590 assert_eq!(hint.accounts, vec![B256::repeat_byte(0x01)]);
591 assert_eq!(
592 hint.storages.get(&B256::repeat_byte(0x02)),
593 Some(&vec![B256::repeat_byte(0x03)])
594 );
595 self.access_hints.fetch_add(1, Ordering::Relaxed);
596 }
597
598 fn on_state_update(&self, state: EvmState) {
599 assert!(state.is_empty());
600 self.state_updates.fetch_add(1, Ordering::Relaxed);
601 }
602
603 fn on_hashed_state_update(&self, state: HashedPostState) {
604 assert!(state.accounts.is_empty());
605 assert!(state.storages.is_empty());
606 self.hashed_state_updates.fetch_add(1, Ordering::Relaxed);
607 }
608
609 fn on_updates_finished(&self) {
610 self.finished_updates.fetch_add(1, Ordering::Relaxed);
611 }
612 }
613
614 #[test]
615 fn state_access_hint_converts_to_sparse_targets() {
616 let account = B256::repeat_byte(0x01);
617 let storage_account = B256::repeat_byte(0x02);
618 let storage_slot = B256::repeat_byte(0x03);
619
620 let mut storages = B256Map::default();
621 storages.insert(storage_account, vec![storage_slot]);
622 let hint = StateAccessHint { accounts: vec![account], storages };
623
624 let targets = MultiProofTargetsV2::from(hint);
625 assert_eq!(targets.account_targets.len(), 1);
626 assert_eq!(targets.account_targets[0].key(), account);
627 assert_eq!(targets.storage_targets.len(), 1);
628 assert_eq!(targets.storage_targets[&storage_account].len(), 1);
629 assert_eq!(targets.storage_targets[&storage_account][0].key(), storage_slot);
630
631 let hint = StateAccessHint::from(targets);
632 assert_eq!(hint.accounts, vec![account]);
633 assert_eq!(hint.storages.len(), 1);
634 assert_eq!(hint.storages[&storage_account], vec![storage_slot]);
635 }
636
637 #[test]
638 fn state_root_capabilities_forward_to_sink() {
639 let sink = Arc::new(CountingSink::default());
640
641 let hint_stream = StateRootHintStream::new(sink.clone());
642 let mut storages = B256Map::default();
643 storages.insert(B256::repeat_byte(0x02), vec![B256::repeat_byte(0x03)]);
644 hint_stream
645 .on_access_hint(StateAccessHint { accounts: vec![B256::repeat_byte(0x01)], storages });
646
647 let updates = StateRootUpdateStream::new(sink.clone());
648 updates.on_hashed_state_update(HashedPostState::default());
649 updates.finish();
650
651 {
652 let mut hook = StateRootUpdateStream::new(sink.clone()).into_state_hook();
653 hook.on_state(EvmState::default());
654 }
655
656 assert_eq!(sink.access_hints.load(Ordering::Relaxed), 1);
657 assert_eq!(sink.state_updates.load(Ordering::Relaxed), 1);
658 assert_eq!(sink.hashed_state_updates.load(Ordering::Relaxed), 1);
659 assert_eq!(sink.finished_updates.load(Ordering::Relaxed), 2);
660 }
661
662 #[test]
665 fn hook_dropped_during_panic_does_not_finish_stream() {
666 let sink = Arc::new(CountingSink::default());
667 let hook = StateRootUpdateStream::new(sink.clone()).into_state_hook();
668
669 let result = std::thread::spawn(move || {
670 let _hook = hook;
671 panic!("execution died mid-block");
672 })
673 .join();
674
675 assert!(result.is_err());
676 assert_eq!(sink.finished_updates.load(Ordering::Relaxed), 0);
677 }
678
679 #[test]
682 #[should_panic(expected = "authoritative update capability already taken")]
683 fn authoritative_capability_can_only_be_taken_once() {
684 let (updates_tx, _updates_rx) = crossbeam_channel::unbounded();
685 let (cancel_guard, _cancel_rx) = StateRootTaskCancelGuard::channel();
686 let (_state_root_tx, state_root_rx) = std::sync::mpsc::channel();
687 let (_hashed_state_tx, hashed_state_rx) = std::sync::mpsc::channel();
688 let mut handle = StateRootHandle::new(
689 B256::ZERO,
690 updates_tx,
691 cancel_guard,
692 state_root_rx,
693 hashed_state_rx,
694 );
695
696 let _hook = handle.take_execution_hook();
697 let _ = handle.take_hashed_update_stream();
698 }
699
700 #[test]
705 fn payload_state_root_handle_lifecycle() {
706 let sink = Arc::new(CountingSink::default());
707 let hook = StateRootUpdateStream::new(sink.clone()).into_state_hook();
708
709 let (state_root_tx, state_root_rx) = std::sync::mpsc::channel();
710 let (hashed_state_tx, hashed_state_rx) = std::sync::mpsc::channel();
711 let mut handle =
712 PayloadStateRootHandle::new("test", Some(hook), state_root_rx, Some(hashed_state_rx));
713
714 assert_eq!(handle.name(), "test");
715
716 {
717 let mut hook = handle.take_state_hook();
718 hook.on_state(EvmState::default());
719 }
720 assert_eq!(sink.state_updates.load(Ordering::Relaxed), 1);
721 assert_eq!(sink.finished_updates.load(Ordering::Relaxed), 1);
722
723 hashed_state_tx.send(Arc::new(HashedPostState::default())).unwrap();
724 let rx = handle.try_take_hashed_state_rx().expect("first take returns the receiver");
725 assert!(rx.recv().is_ok());
726 assert!(handle.try_take_hashed_state_rx().is_none(), "second take returns None");
727
728 state_root_tx
729 .send(Ok(StateRootComputeOutcome {
730 state_root: B256::repeat_byte(0x42),
731 trie_updates: Arc::new(TrieUpdates::default()),
732 hashed_state: Arc::new(HashedPostState::default()),
733 #[cfg(feature = "trie-debug")]
734 debug_recorders: Vec::new(),
735 }))
736 .unwrap();
737 let outcome = handle.state_root().expect("outcome is delivered");
738 assert_eq!(outcome.state_root, B256::repeat_byte(0x42));
739 }
740
741 #[test]
742 #[should_panic(expected = "state_root already taken")]
743 fn payload_state_root_receiver_can_only_be_taken_once() {
744 let (_state_root_tx, state_root_rx) = std::sync::mpsc::channel();
745 let mut handle = PayloadStateRootHandle::new("test", None, state_root_rx, None);
746
747 let _state_root_rx = handle.take_state_root_rx();
748 let _ = handle.take_state_root_rx();
749 }
750
751 #[test]
752 fn payload_state_root_receiver_retains_cancellation() {
753 let (updates_tx, _updates_rx) = crossbeam_channel::unbounded();
754 let (cancel_guard, cancel_rx) = StateRootTaskCancelGuard::channel();
755 let (_state_root_tx, state_root_rx) = std::sync::mpsc::channel();
756 let (_hashed_state_tx, hashed_state_rx) = std::sync::mpsc::channel();
757 let mut handle = StateRootHandle::new(
758 B256::ZERO,
759 updates_tx,
760 cancel_guard,
761 state_root_rx,
762 hashed_state_rx,
763 )
764 .into_payload_state_root_handle();
765
766 let state_root_rx = handle.take_state_root_rx();
767 assert!(matches!(
768 state_root_rx.recv_timeout(Duration::ZERO),
769 Err(std::sync::mpsc::RecvTimeoutError::Timeout)
770 ));
771 assert!(matches!(cancel_rx.try_recv(), Err(crossbeam_channel::TryRecvError::Empty)));
772
773 drop(handle);
774 assert!(matches!(
775 cancel_rx.recv_timeout(Duration::from_secs(1)),
776 Err(crossbeam_channel::RecvTimeoutError::Disconnected)
777 ));
778 }
779}