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}
47
48#[derive(Debug)]
56pub struct StateRootHandle {
57 cached_trie_state_root: B256,
59 hint: Option<StateRootHintStream>,
61 authoritative: Option<StateRootUpdateStream>,
68 cancel_guard: StateRootTaskCancelGuard,
70 state_root_rx:
72 Option<std::sync::mpsc::Receiver<Result<StateRootComputeOutcome, StateRootTaskError>>>,
73 hashed_state_rx: Option<std::sync::mpsc::Receiver<Arc<HashedPostState>>>,
75}
76
77impl StateRootHandle {
78 pub fn new(
80 cached_trie_state_root: B256,
81 updates_tx: crossbeam_channel::Sender<StateRootMessage>,
82 cancel_guard: StateRootTaskCancelGuard,
83 state_root_rx: std::sync::mpsc::Receiver<
84 Result<StateRootComputeOutcome, StateRootTaskError>,
85 >,
86 hashed_state_rx: std::sync::mpsc::Receiver<Arc<HashedPostState>>,
87 ) -> Self {
88 let sink: Arc<dyn StateRootSink> = Arc::new(SparseTrieStateRootSink::new(updates_tx));
89 Self {
90 cached_trie_state_root,
91 hint: Some(StateRootHintStream::new(Arc::clone(&sink))),
92 authoritative: Some(StateRootUpdateStream::new(sink)),
93 cancel_guard,
94 state_root_rx: Some(state_root_rx),
95 hashed_state_rx: Some(hashed_state_rx),
96 }
97 }
98
99 pub const fn cached_trie_state_root(&self) -> B256 {
101 self.cached_trie_state_root
102 }
103
104 pub const fn take_hint_stream(&mut self) -> StateRootHintStream {
110 self.hint.take().expect("hint stream already taken")
111 }
112
113 pub fn take_execution_hook(&mut self) -> StateRootUpdateHook {
122 self.take_hashed_update_stream().into_state_hook()
123 }
124
125 pub const fn take_hashed_update_stream(&mut self) -> StateRootUpdateStream {
135 self.authoritative.take().expect("authoritative update capability already taken")
136 }
137
138 pub fn state_root(&mut self) -> Result<StateRootComputeOutcome, StateRootTaskError> {
144 self.state_root_rx
145 .take()
146 .expect("state_root already taken")
147 .recv()
148 .map_err(|_| StateRootTaskError::Other("sparse trie task dropped".to_string()))?
149 }
150
151 pub const fn take_state_root_rx(
157 &mut self,
158 ) -> std::sync::mpsc::Receiver<Result<StateRootComputeOutcome, StateRootTaskError>> {
159 self.state_root_rx.take().expect("state_root already taken")
160 }
161
162 pub const fn take_hashed_state_rx(
168 &mut self,
169 ) -> std::sync::mpsc::Receiver<Arc<HashedPostState>> {
170 self.hashed_state_rx.take().expect("hashed_state already taken")
171 }
172
173 pub fn into_payload_state_root_handle(mut self) -> PayloadStateRootHandle {
178 let hook = self.take_execution_hook();
179 PayloadStateRootHandle {
180 name: "sparse-trie",
181 hook: Some(hook),
182 cancel_guard: Some(self.cancel_guard),
183 state_root_rx: self.state_root_rx.take(),
184 hashed_state_rx: self.hashed_state_rx.take(),
185 }
186 }
187}
188
189#[derive(Debug)]
195pub struct StateRootTaskCancelGuard(#[allow(dead_code)] crossbeam_channel::Sender<()>);
196
197impl StateRootTaskCancelGuard {
198 pub fn channel() -> (Self, crossbeam_channel::Receiver<()>) {
200 let (tx, rx) = crossbeam_channel::bounded(0);
201 (Self(tx), rx)
202 }
203}
204
205pub struct PayloadStateRootHandle {
207 name: &'static str,
208 hook: Option<StateRootUpdateHook>,
210 cancel_guard: Option<StateRootTaskCancelGuard>,
212 state_root_rx:
213 Option<std::sync::mpsc::Receiver<Result<StateRootComputeOutcome, StateRootTaskError>>>,
214 hashed_state_rx: Option<std::sync::mpsc::Receiver<Arc<HashedPostState>>>,
215}
216
217impl fmt::Debug for PayloadStateRootHandle {
218 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
219 f.debug_struct("PayloadStateRootHandle")
220 .field("name", &self.name)
221 .field("has_hook", &self.hook.is_some())
222 .field("has_cancel_guard", &self.cancel_guard.is_some())
223 .field("has_state_root_rx", &self.state_root_rx.is_some())
224 .field("has_hashed_state_rx", &self.hashed_state_rx.is_some())
225 .finish()
226 }
227}
228
229impl PayloadStateRootHandle {
230 pub const fn new(
235 name: &'static str,
236 hook: Option<StateRootUpdateHook>,
237 state_root_rx: std::sync::mpsc::Receiver<
238 Result<StateRootComputeOutcome, StateRootTaskError>,
239 >,
240 hashed_state_rx: Option<std::sync::mpsc::Receiver<Arc<HashedPostState>>>,
241 ) -> Self {
242 Self { name, hook, cancel_guard: None, state_root_rx: Some(state_root_rx), hashed_state_rx }
243 }
244
245 pub const fn name(&self) -> &'static str {
247 self.name
248 }
249
250 pub const fn take_state_hook(&mut self) -> StateRootUpdateHook {
256 self.hook.take().expect("payload state root task missing execution hook")
257 }
258
259 pub fn state_root(&mut self) -> Result<StateRootComputeOutcome, StateRootTaskError> {
265 self.state_root_rx
266 .take()
267 .expect("state_root already taken")
268 .recv()
269 .map_err(|_| StateRootTaskError::Other("state root task dropped".to_string()))?
270 }
271
272 pub const fn take_state_root_rx(
280 &mut self,
281 ) -> std::sync::mpsc::Receiver<Result<StateRootComputeOutcome, StateRootTaskError>> {
282 self.state_root_rx.take().expect("state_root already taken")
283 }
284
285 pub const fn try_take_hashed_state_rx(
288 &mut self,
289 ) -> Option<std::sync::mpsc::Receiver<Arc<HashedPostState>>> {
290 self.hashed_state_rx.take()
291 }
292}
293
294#[derive(Debug, Clone, Default)]
300pub struct StateAccessHint {
301 pub accounts: Vec<B256>,
303 pub storages: B256Map<Vec<B256>>,
305}
306
307impl From<MultiProofTargetsV2> for StateAccessHint {
308 fn from(targets: MultiProofTargetsV2) -> Self {
309 Self {
310 accounts: targets.account_targets.into_iter().map(|target| target.key()).collect(),
311 storages: targets
312 .storage_targets
313 .into_iter()
314 .map(|(account, slots)| {
315 (account, slots.into_iter().map(|target| target.key()).collect())
316 })
317 .collect(),
318 }
319 }
320}
321
322impl From<StateAccessHint> for MultiProofTargetsV2 {
323 fn from(hint: StateAccessHint) -> Self {
324 Self {
325 account_targets: hint.accounts.into_iter().map(ProofV2Target::from).collect(),
326 storage_targets: hint
327 .storages
328 .into_iter()
329 .map(|(account, slots)| {
330 (account, slots.into_iter().map(ProofV2Target::from).collect())
331 })
332 .collect(),
333 }
334 }
335}
336
337pub trait StateRootSink: Send + Sync + 'static {
339 fn on_access_hint(&self, _hint: StateAccessHint) {}
341
342 fn on_state_update(&self, state: EvmState);
344
345 fn on_hashed_state_update(&self, state: HashedPostState);
347
348 fn on_updates_finished(&self);
350}
351
352#[derive(Clone)]
354pub struct StateRootHintStream {
355 inner: Arc<dyn StateRootSink>,
356}
357
358impl fmt::Debug for StateRootHintStream {
359 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
360 f.debug_struct("StateRootHintStream").finish_non_exhaustive()
361 }
362}
363
364impl StateRootHintStream {
365 pub fn new(inner: Arc<dyn StateRootSink>) -> Self {
367 Self { inner }
368 }
369
370 pub fn on_access_hint(&self, hint: StateAccessHint) {
372 self.inner.on_access_hint(hint);
373 }
374}
375
376pub struct StateRootUpdateStream {
388 inner: Arc<dyn StateRootSink>,
389}
390
391impl fmt::Debug for StateRootUpdateStream {
392 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
393 f.debug_struct("StateRootUpdateStream").finish_non_exhaustive()
394 }
395}
396
397impl StateRootUpdateStream {
398 pub fn new(inner: Arc<dyn StateRootSink>) -> Self {
400 Self { inner }
401 }
402
403 pub fn on_hashed_state_update(&self, state: HashedPostState) {
405 self.inner.on_hashed_state_update(state);
406 }
407
408 pub fn finish(self) {
410 self.inner.on_updates_finished();
411 }
412
413 pub fn into_state_hook(self) -> StateRootUpdateHook {
418 StateRootUpdateHook { inner: self.inner }
419 }
420}
421
422pub struct StateRootUpdateHook {
435 inner: Arc<dyn StateRootSink>,
436}
437
438impl fmt::Debug for StateRootUpdateHook {
439 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
440 f.debug_struct("StateRootUpdateHook").finish_non_exhaustive()
441 }
442}
443
444impl OnStateHook for StateRootUpdateHook {
445 fn on_state(&mut self, state: EvmState) {
446 self.inner.on_state_update(state);
447 }
448}
449
450impl Drop for StateRootUpdateHook {
451 fn drop(&mut self) {
452 if std::thread::panicking() {
455 return;
456 }
457 self.inner.on_updates_finished();
458 }
459}
460
461#[derive(Debug, Clone)]
462struct SparseTrieStateRootSink {
463 sender: crossbeam_channel::Sender<StateRootMessage>,
464}
465
466impl SparseTrieStateRootSink {
467 const fn new(sender: crossbeam_channel::Sender<StateRootMessage>) -> Self {
468 Self { sender }
469 }
470}
471
472impl StateRootSink for SparseTrieStateRootSink {
473 fn on_access_hint(&self, hint: StateAccessHint) {
474 let _ = self.sender.send(StateRootMessage::PrefetchProofs(hint.into()));
475 }
476
477 fn on_state_update(&self, state: EvmState) {
478 let _ = self.sender.send(StateRootMessage::StateUpdate(state));
479 }
480
481 fn on_hashed_state_update(&self, state: HashedPostState) {
482 let _ = self.sender.send(StateRootMessage::HashedStateUpdate(state));
483 }
484
485 fn on_updates_finished(&self) {
486 let _ = self.sender.send(StateRootMessage::FinishedStateUpdates);
487 }
488}
489
490pub fn evm_state_to_hashed_post_state(update: EvmState) -> HashedPostState {
492 let mut hashed_state = HashedPostState::with_capacity(update.len());
493
494 for (address, account) in update {
495 if account.is_touched() {
496 let hashed_address = keccak256(address);
497 trace!(target: "trie::parallel::sparse", ?address, ?hashed_address, "Adding account to state update");
498
499 let destroyed = account.is_selfdestructed();
500 if account.info != account.original_info() {
501 let info = if destroyed { None } else { Some(account.info.into()) };
502 hashed_state.accounts.insert(hashed_address, info);
503 }
504
505 let mut changed_storage_iter = account
506 .storage
507 .into_iter()
508 .filter(|(_slot, value)| value.is_changed())
509 .map(|(slot, value)| (keccak256(B256::from(slot)), value.present_value))
510 .peekable();
511
512 if !destroyed && changed_storage_iter.peek().is_some() {
513 hashed_state
514 .storages
515 .insert(hashed_address, HashedStorage::from_iter(changed_storage_iter));
516 }
517 }
518 }
519
520 hashed_state
521}
522
523#[cfg(test)]
524mod tests {
525 use super::*;
526 use alloy_primitives::{Address, U256};
527 use revm::state::{Account, EvmStorageSlot, TransactionId};
528 use std::{
529 sync::atomic::{AtomicUsize, Ordering},
530 time::Duration,
531 };
532
533 #[test]
534 fn created_selfdestruct_does_not_emit_storage() {
535 let address = Address::repeat_byte(0x01);
536 let mut account = Account::default();
537 account.mark_touch();
538 assert!(account.mark_created_locally());
539 assert!(account.mark_selfdestructed_locally());
540 account.info.nonce = 1;
541 account.storage.insert(
542 U256::from(1),
543 EvmStorageSlot::new_changed(U256::ZERO, U256::from(2), TransactionId::ZERO),
544 );
545
546 let hashed_state =
547 evm_state_to_hashed_post_state(EvmState::from_iter([(address, account)]));
548 let hashed_address = keccak256(address);
549
550 assert_eq!(hashed_state.accounts.get(&hashed_address), Some(&None));
551 assert!(!hashed_state.storages.contains_key(&hashed_address));
552 }
553
554 #[test]
555 fn existing_selfdestruct_does_not_emit_storage() {
556 let address = Address::repeat_byte(0x02);
557 let mut account = Account::default();
558 account.info.nonce = 1;
559 account.set_current_info_as_original();
560 account.mark_touch();
561 assert!(account.mark_selfdestructed_locally());
562 account.selfdestruct();
563 account.storage.insert(
564 U256::from(1),
565 EvmStorageSlot::new_changed(U256::ZERO, U256::from(2), TransactionId::ZERO),
566 );
567
568 let hashed_state =
569 evm_state_to_hashed_post_state(EvmState::from_iter([(address, account)]));
570 let hashed_address = keccak256(address);
571
572 assert_eq!(hashed_state.accounts.get(&hashed_address), Some(&None));
573 assert!(!hashed_state.storages.contains_key(&hashed_address));
574 }
575
576 #[derive(Default)]
577 struct CountingSink {
578 access_hints: AtomicUsize,
579 state_updates: AtomicUsize,
580 hashed_state_updates: AtomicUsize,
581 finished_updates: AtomicUsize,
582 }
583
584 impl StateRootSink for CountingSink {
585 fn on_access_hint(&self, hint: StateAccessHint) {
586 assert_eq!(hint.accounts, vec![B256::repeat_byte(0x01)]);
587 assert_eq!(
588 hint.storages.get(&B256::repeat_byte(0x02)),
589 Some(&vec![B256::repeat_byte(0x03)])
590 );
591 self.access_hints.fetch_add(1, Ordering::Relaxed);
592 }
593
594 fn on_state_update(&self, state: EvmState) {
595 assert!(state.is_empty());
596 self.state_updates.fetch_add(1, Ordering::Relaxed);
597 }
598
599 fn on_hashed_state_update(&self, state: HashedPostState) {
600 assert!(state.accounts.is_empty());
601 assert!(state.storages.is_empty());
602 self.hashed_state_updates.fetch_add(1, Ordering::Relaxed);
603 }
604
605 fn on_updates_finished(&self) {
606 self.finished_updates.fetch_add(1, Ordering::Relaxed);
607 }
608 }
609
610 #[test]
611 fn state_access_hint_converts_to_sparse_targets() {
612 let account = B256::repeat_byte(0x01);
613 let storage_account = B256::repeat_byte(0x02);
614 let storage_slot = B256::repeat_byte(0x03);
615
616 let mut storages = B256Map::default();
617 storages.insert(storage_account, vec![storage_slot]);
618 let hint = StateAccessHint { accounts: vec![account], storages };
619
620 let targets = MultiProofTargetsV2::from(hint);
621 assert_eq!(targets.account_targets.len(), 1);
622 assert_eq!(targets.account_targets[0].key(), account);
623 assert_eq!(targets.storage_targets.len(), 1);
624 assert_eq!(targets.storage_targets[&storage_account].len(), 1);
625 assert_eq!(targets.storage_targets[&storage_account][0].key(), storage_slot);
626
627 let hint = StateAccessHint::from(targets);
628 assert_eq!(hint.accounts, vec![account]);
629 assert_eq!(hint.storages.len(), 1);
630 assert_eq!(hint.storages[&storage_account], vec![storage_slot]);
631 }
632
633 #[test]
634 fn state_root_capabilities_forward_to_sink() {
635 let sink = Arc::new(CountingSink::default());
636
637 let hint_stream = StateRootHintStream::new(sink.clone());
638 let mut storages = B256Map::default();
639 storages.insert(B256::repeat_byte(0x02), vec![B256::repeat_byte(0x03)]);
640 hint_stream
641 .on_access_hint(StateAccessHint { accounts: vec![B256::repeat_byte(0x01)], storages });
642
643 let updates = StateRootUpdateStream::new(sink.clone());
644 updates.on_hashed_state_update(HashedPostState::default());
645 updates.finish();
646
647 {
648 let mut hook = StateRootUpdateStream::new(sink.clone()).into_state_hook();
649 hook.on_state(EvmState::default());
650 }
651
652 assert_eq!(sink.access_hints.load(Ordering::Relaxed), 1);
653 assert_eq!(sink.state_updates.load(Ordering::Relaxed), 1);
654 assert_eq!(sink.hashed_state_updates.load(Ordering::Relaxed), 1);
655 assert_eq!(sink.finished_updates.load(Ordering::Relaxed), 2);
656 }
657
658 #[test]
661 fn hook_dropped_during_panic_does_not_finish_stream() {
662 let sink = Arc::new(CountingSink::default());
663 let hook = StateRootUpdateStream::new(sink.clone()).into_state_hook();
664
665 let result = std::thread::spawn(move || {
666 let _hook = hook;
667 panic!("execution died mid-block");
668 })
669 .join();
670
671 assert!(result.is_err());
672 assert_eq!(sink.finished_updates.load(Ordering::Relaxed), 0);
673 }
674
675 #[test]
678 #[should_panic(expected = "authoritative update capability already taken")]
679 fn authoritative_capability_can_only_be_taken_once() {
680 let (updates_tx, _updates_rx) = crossbeam_channel::unbounded();
681 let (cancel_guard, _cancel_rx) = StateRootTaskCancelGuard::channel();
682 let (_state_root_tx, state_root_rx) = std::sync::mpsc::channel();
683 let (_hashed_state_tx, hashed_state_rx) = std::sync::mpsc::channel();
684 let mut handle = StateRootHandle::new(
685 B256::ZERO,
686 updates_tx,
687 cancel_guard,
688 state_root_rx,
689 hashed_state_rx,
690 );
691
692 let _hook = handle.take_execution_hook();
693 let _ = handle.take_hashed_update_stream();
694 }
695
696 #[test]
701 fn payload_state_root_handle_lifecycle() {
702 let sink = Arc::new(CountingSink::default());
703 let hook = StateRootUpdateStream::new(sink.clone()).into_state_hook();
704
705 let (state_root_tx, state_root_rx) = std::sync::mpsc::channel();
706 let (hashed_state_tx, hashed_state_rx) = std::sync::mpsc::channel();
707 let mut handle =
708 PayloadStateRootHandle::new("test", Some(hook), state_root_rx, Some(hashed_state_rx));
709
710 assert_eq!(handle.name(), "test");
711
712 {
713 let mut hook = handle.take_state_hook();
714 hook.on_state(EvmState::default());
715 }
716 assert_eq!(sink.state_updates.load(Ordering::Relaxed), 1);
717 assert_eq!(sink.finished_updates.load(Ordering::Relaxed), 1);
718
719 hashed_state_tx.send(Arc::new(HashedPostState::default())).unwrap();
720 let rx = handle.try_take_hashed_state_rx().expect("first take returns the receiver");
721 assert!(rx.recv().is_ok());
722 assert!(handle.try_take_hashed_state_rx().is_none(), "second take returns None");
723
724 state_root_tx
725 .send(Ok(StateRootComputeOutcome {
726 state_root: B256::repeat_byte(0x42),
727 trie_updates: Arc::new(TrieUpdates::default()),
728 hashed_state: Arc::new(HashedPostState::default()),
729 }))
730 .unwrap();
731 let outcome = handle.state_root().expect("outcome is delivered");
732 assert_eq!(outcome.state_root, B256::repeat_byte(0x42));
733 }
734
735 #[test]
736 #[should_panic(expected = "state_root already taken")]
737 fn payload_state_root_receiver_can_only_be_taken_once() {
738 let (_state_root_tx, state_root_rx) = std::sync::mpsc::channel();
739 let mut handle = PayloadStateRootHandle::new("test", None, state_root_rx, None);
740
741 let _state_root_rx = handle.take_state_root_rx();
742 let _ = handle.take_state_root_rx();
743 }
744
745 #[test]
746 fn payload_state_root_receiver_retains_cancellation() {
747 let (updates_tx, _updates_rx) = crossbeam_channel::unbounded();
748 let (cancel_guard, cancel_rx) = StateRootTaskCancelGuard::channel();
749 let (_state_root_tx, state_root_rx) = std::sync::mpsc::channel();
750 let (_hashed_state_tx, hashed_state_rx) = std::sync::mpsc::channel();
751 let mut handle = StateRootHandle::new(
752 B256::ZERO,
753 updates_tx,
754 cancel_guard,
755 state_root_rx,
756 hashed_state_rx,
757 )
758 .into_payload_state_root_handle();
759
760 let state_root_rx = handle.take_state_root_rx();
761 assert!(matches!(
762 state_root_rx.recv_timeout(Duration::ZERO),
763 Err(std::sync::mpsc::RecvTimeoutError::Timeout)
764 ));
765 assert!(matches!(cancel_rx.try_recv(), Err(crossbeam_channel::TryRecvError::Empty)));
766
767 drop(handle);
768 assert!(matches!(
769 cancel_rx.recv_timeout(Duration::from_secs(1)),
770 Err(crossbeam_channel::RecvTimeoutError::Disconnected)
771 ));
772 }
773}