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)]
53pub struct StateRootHandle {
54 cached_trie_state_root: B256,
56 hint: Option<StateRootHintStream>,
58 authoritative: Option<StateRootUpdateStream>,
65 cancel_guard: StateRootTaskCancelGuard,
67 state_root_rx:
69 Option<std::sync::mpsc::Receiver<Result<StateRootComputeOutcome, StateRootTaskError>>>,
70 hashed_state_rx: Option<std::sync::mpsc::Receiver<Arc<HashedPostState>>>,
72}
73
74impl StateRootHandle {
75 pub fn new(
77 cached_trie_state_root: B256,
78 updates_tx: crossbeam_channel::Sender<StateRootMessage>,
79 cancel_guard: StateRootTaskCancelGuard,
80 state_root_rx: std::sync::mpsc::Receiver<
81 Result<StateRootComputeOutcome, StateRootTaskError>,
82 >,
83 hashed_state_rx: std::sync::mpsc::Receiver<Arc<HashedPostState>>,
84 ) -> Self {
85 let sink: Arc<dyn StateRootSink> = Arc::new(SparseTrieStateRootSink::new(updates_tx));
86 Self {
87 cached_trie_state_root,
88 hint: Some(StateRootHintStream::new(Arc::clone(&sink))),
89 authoritative: Some(StateRootUpdateStream::new(sink)),
90 cancel_guard,
91 state_root_rx: Some(state_root_rx),
92 hashed_state_rx: Some(hashed_state_rx),
93 }
94 }
95
96 pub const fn cached_trie_state_root(&self) -> B256 {
98 self.cached_trie_state_root
99 }
100
101 pub const fn take_hint_stream(&mut self) -> StateRootHintStream {
107 self.hint.take().expect("hint stream already taken")
108 }
109
110 pub fn take_execution_hook(&mut self) -> StateRootUpdateHook {
119 self.take_hashed_update_stream().into_state_hook()
120 }
121
122 pub const fn take_hashed_update_stream(&mut self) -> StateRootUpdateStream {
132 self.authoritative.take().expect("authoritative update capability already taken")
133 }
134
135 pub fn state_root(&mut self) -> Result<StateRootComputeOutcome, StateRootTaskError> {
141 self.state_root_rx
142 .take()
143 .expect("state_root already taken")
144 .recv()
145 .map_err(|_| StateRootTaskError::Other("sparse trie task dropped".into()))?
146 }
147
148 pub const fn take_state_root_rx(
154 &mut self,
155 ) -> std::sync::mpsc::Receiver<Result<StateRootComputeOutcome, StateRootTaskError>> {
156 self.state_root_rx.take().expect("state_root already taken")
157 }
158
159 pub const fn take_hashed_state_rx(
165 &mut self,
166 ) -> std::sync::mpsc::Receiver<Arc<HashedPostState>> {
167 self.hashed_state_rx.take().expect("hashed_state already taken")
168 }
169
170 pub fn into_payload_state_root_handle(mut self) -> PayloadStateRootHandle {
175 let hook = self.take_execution_hook();
176 PayloadStateRootHandle {
177 name: "sparse-trie",
178 hook: Some(hook),
179 cancel_guard: Some(self.cancel_guard),
180 state_root_rx: self.state_root_rx.take(),
181 hashed_state_rx: self.hashed_state_rx.take(),
182 on_payload_built: None,
183 }
184 }
185}
186
187#[derive(Debug)]
193pub struct StateRootTaskCancelGuard(#[allow(dead_code)] crossbeam_channel::Sender<()>);
194
195impl StateRootTaskCancelGuard {
196 pub fn channel() -> (Self, crossbeam_channel::Receiver<()>) {
198 let (tx, rx) = crossbeam_channel::bounded(0);
199 (Self(tx), rx)
200 }
201}
202
203pub struct PayloadStateRootHandle {
205 name: &'static str,
206 hook: Option<StateRootUpdateHook>,
208 cancel_guard: Option<StateRootTaskCancelGuard>,
210 state_root_rx:
211 Option<std::sync::mpsc::Receiver<Result<StateRootComputeOutcome, StateRootTaskError>>>,
212 hashed_state_rx: Option<std::sync::mpsc::Receiver<Arc<HashedPostState>>>,
213 on_payload_built: Option<Box<dyn FnOnce(B256, B256) + Send>>,
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 {
243 name,
244 hook,
245 cancel_guard: None,
246 state_root_rx: Some(state_root_rx),
247 hashed_state_rx,
248 on_payload_built: None,
249 }
250 }
251
252 pub fn with_on_payload_built(
256 mut self,
257 callback: impl FnOnce(B256, B256) + Send + 'static,
258 ) -> Self {
259 self.on_payload_built = Some(Box::new(callback));
260 self
261 }
262
263 pub fn take_on_payload_built(&mut self) -> Option<Box<dyn FnOnce(B256, B256) + Send>> {
266 self.on_payload_built.take()
267 }
268
269 pub const fn name(&self) -> &'static str {
271 self.name
272 }
273
274 pub const fn take_state_hook(&mut self) -> StateRootUpdateHook {
280 self.hook.take().expect("payload state root task missing execution hook")
281 }
282
283 pub fn state_root(&mut self) -> Result<StateRootComputeOutcome, StateRootTaskError> {
289 self.state_root_rx
290 .take()
291 .expect("state_root already taken")
292 .recv()
293 .map_err(|_| StateRootTaskError::Other("state root task dropped".into()))?
294 }
295
296 pub const fn take_state_root_rx(
304 &mut self,
305 ) -> std::sync::mpsc::Receiver<Result<StateRootComputeOutcome, StateRootTaskError>> {
306 self.state_root_rx.take().expect("state_root already taken")
307 }
308
309 pub const fn try_take_hashed_state_rx(
312 &mut self,
313 ) -> Option<std::sync::mpsc::Receiver<Arc<HashedPostState>>> {
314 self.hashed_state_rx.take()
315 }
316}
317
318#[derive(Debug, Clone, Default)]
323pub struct StateAccessHint {
324 pub accounts: Vec<B256>,
326 pub storages: B256Map<Vec<B256>>,
328}
329
330impl From<MultiProofTargetsV2> for StateAccessHint {
331 fn from(targets: MultiProofTargetsV2) -> Self {
332 Self {
333 accounts: targets.account_targets.into_iter().map(|target| target.key()).collect(),
334 storages: targets
335 .storage_targets
336 .into_iter()
337 .map(|(account, slots)| {
338 (account, slots.into_iter().map(|target| target.key()).collect())
339 })
340 .collect(),
341 }
342 }
343}
344
345impl From<StateAccessHint> for MultiProofTargetsV2 {
346 fn from(hint: StateAccessHint) -> Self {
347 Self {
348 account_targets: hint.accounts.into_iter().map(ProofV2Target::from).collect(),
349 storage_targets: hint
350 .storages
351 .into_iter()
352 .map(|(account, slots)| {
353 (account, slots.into_iter().map(ProofV2Target::from).collect())
354 })
355 .collect(),
356 }
357 }
358}
359
360trait StateRootSink: Send + Sync + 'static {
365 fn on_access_hint(&self, _hint: StateAccessHint) {}
367
368 fn on_state_update(&self, state: EvmState);
370
371 fn on_hashed_state_update(&self, state: HashedPostState);
373
374 fn on_updates_finished(&self);
376}
377
378#[derive(Clone)]
380pub struct StateRootHintStream {
381 inner: Arc<dyn StateRootSink>,
382}
383
384impl fmt::Debug for StateRootHintStream {
385 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
386 f.debug_struct("StateRootHintStream").finish_non_exhaustive()
387 }
388}
389
390impl StateRootHintStream {
391 fn new(inner: Arc<dyn StateRootSink>) -> Self {
393 Self { inner }
394 }
395
396 pub fn on_access_hint(&self, hint: StateAccessHint) {
398 self.inner.on_access_hint(hint);
399 }
400}
401
402pub struct StateRootUpdateStream {
414 inner: Arc<dyn StateRootSink>,
415}
416
417impl fmt::Debug for StateRootUpdateStream {
418 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
419 f.debug_struct("StateRootUpdateStream").finish_non_exhaustive()
420 }
421}
422
423impl StateRootUpdateStream {
424 fn new(inner: Arc<dyn StateRootSink>) -> Self {
426 Self { inner }
427 }
428
429 pub fn on_hashed_state_update(&self, state: HashedPostState) {
431 self.inner.on_hashed_state_update(state);
432 }
433
434 pub fn finish(self) {
436 self.inner.on_updates_finished();
437 }
438
439 pub fn into_state_hook(self) -> StateRootUpdateHook {
444 StateRootUpdateHook { inner: self.inner }
445 }
446}
447
448pub struct StateRootUpdateHook {
461 inner: Arc<dyn StateRootSink>,
462}
463
464impl fmt::Debug for StateRootUpdateHook {
465 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
466 f.debug_struct("StateRootUpdateHook").finish_non_exhaustive()
467 }
468}
469
470impl OnStateHook for StateRootUpdateHook {
471 fn on_state(&mut self, state: EvmState) {
472 self.inner.on_state_update(state);
473 }
474}
475
476impl Drop for StateRootUpdateHook {
477 fn drop(&mut self) {
478 if std::thread::panicking() {
481 return;
482 }
483 self.inner.on_updates_finished();
484 }
485}
486
487#[derive(Debug, Clone)]
488struct SparseTrieStateRootSink {
489 sender: crossbeam_channel::Sender<StateRootMessage>,
490}
491
492impl SparseTrieStateRootSink {
493 const fn new(sender: crossbeam_channel::Sender<StateRootMessage>) -> Self {
494 Self { sender }
495 }
496}
497
498impl StateRootSink for SparseTrieStateRootSink {
503 fn on_access_hint(&self, hint: StateAccessHint) {
504 let _ = self.sender.send(StateRootMessage::PrefetchProofs(hint.into()));
505 }
506
507 fn on_state_update(&self, state: EvmState) {
508 let _ = self.sender.send(StateRootMessage::StateUpdate(state));
509 }
510
511 fn on_hashed_state_update(&self, state: HashedPostState) {
512 let _ = self.sender.send(StateRootMessage::HashedStateUpdate(state));
513 }
514
515 fn on_updates_finished(&self) {
516 let _ = self.sender.send(StateRootMessage::FinishedStateUpdates);
517 }
518}
519
520pub fn evm_state_to_hashed_post_state(update: EvmState) -> HashedPostState {
522 let mut hashed_state = HashedPostState::with_capacity(update.len());
523
524 for (address, account) in update {
525 if account.is_touched() {
526 let hashed_address = keccak256(address);
527 trace!(target: "trie::parallel::sparse", ?address, ?hashed_address, "Adding account to state update");
528
529 let destroyed = account.is_selfdestructed();
530 let deleted = destroyed || (account.is_empty() && !account.is_loaded_as_not_existing());
538 if deleted {
539 hashed_state.accounts.insert(hashed_address, None);
540 } else if account.info != account.original_info() {
541 hashed_state.accounts.insert(hashed_address, Some(account.info.into()));
543 }
544
545 let mut changed_storage_iter = account
546 .storage
547 .into_iter()
548 .filter(|(_slot, value)| value.is_changed())
549 .map(|(slot, value)| (keccak256(B256::from(slot)), value.present_value))
550 .peekable();
551
552 if !destroyed && changed_storage_iter.peek().is_some() {
553 hashed_state
554 .storages
555 .insert(hashed_address, HashedStorage::from_iter(changed_storage_iter));
556 }
557 }
558 }
559
560 hashed_state
561}
562
563#[cfg(test)]
564mod tests {
565 use super::*;
566 use alloy_primitives::{Address, U256};
567 use revm::state::{Account, EvmStorageSlot, TransactionId};
568 use std::{
569 sync::atomic::{AtomicUsize, Ordering},
570 time::Duration,
571 };
572
573 #[test]
574 fn created_selfdestruct_does_not_emit_storage() {
575 let address = Address::repeat_byte(0x01);
576 let mut account = Account::new_not_existing(TransactionId::ZERO);
577 account.mark_touch();
578 assert!(account.mark_created_locally());
579 assert!(account.mark_selfdestructed_locally());
580 account.info.nonce = 1;
581 account.storage.insert(
582 U256::ONE,
583 EvmStorageSlot::new_changed(U256::ZERO, U256::from(2), TransactionId::ZERO),
584 );
585
586 let hashed_state =
587 evm_state_to_hashed_post_state(EvmState::from_iter([(address, account)]));
588 let hashed_address = keccak256(address);
589
590 assert_eq!(hashed_state.accounts.get(&hashed_address), Some(&None));
591 assert!(!hashed_state.storages.contains_key(&hashed_address));
592 }
593
594 #[test]
595 fn existing_selfdestruct_does_not_emit_storage() {
596 let address = Address::repeat_byte(0x02);
597 let mut account = Account::default();
598 account.info.nonce = 1;
599 account.set_current_info_as_original();
600 account.mark_touch();
601 assert!(account.mark_selfdestructed_locally());
602 account.selfdestruct();
603 account.storage.insert(
604 U256::ONE,
605 EvmStorageSlot::new_changed(U256::ZERO, U256::from(2), TransactionId::ZERO),
606 );
607
608 let hashed_state =
609 evm_state_to_hashed_post_state(EvmState::from_iter([(address, account)]));
610 let hashed_address = keccak256(address);
611
612 assert_eq!(hashed_state.accounts.get(&hashed_address), Some(&None));
613 assert!(!hashed_state.storages.contains_key(&hashed_address));
614 }
615
616 #[test]
621 fn emptied_account_is_deleted() {
622 let address = Address::repeat_byte(0x05);
623 let mut account = Account::default();
624 account.info.balance = U256::ONE;
626 account.set_current_info_as_original();
627 account.mark_touch();
629 account.info.balance = U256::ZERO;
630 assert!(account.is_empty(), "the drained account must be EIP-161-empty");
631 assert!(!account.is_selfdestructed());
632
633 let hashed_state =
634 evm_state_to_hashed_post_state(EvmState::from_iter([(address, account)]));
635
636 assert_eq!(hashed_state.accounts.get(&keccak256(address)), Some(&None));
637 }
638
639 #[test]
644 fn touched_preexisting_empty_account_is_deleted() {
645 let address = Address::repeat_byte(0x06);
646 let mut account = Account::default();
648 account.set_current_info_as_original();
649 account.mark_touch();
650 assert!(account.is_empty());
651
652 let hashed_state =
653 evm_state_to_hashed_post_state(EvmState::from_iter([(address, account)]));
654
655 assert_eq!(hashed_state.accounts.get(&keccak256(address)), Some(&None));
656 }
657
658 #[test]
668 fn matches_bundle_state_for_emptied_account() {
669 use revm::{
670 database::{states::bundle_state::BundleRetention, State},
671 state::AccountInfo,
672 DatabaseCommit,
673 };
674
675 let address = Address::repeat_byte(0x07);
676 let pre = AccountInfo { balance: U256::ONE, ..Default::default() };
677
678 let mut account = Account::from(pre.clone());
680 account.mark_touch();
681 account.info.balance = U256::ZERO;
682 let evm_state = EvmState::from_iter([(address, account)]);
683
684 let mut db = State::builder().with_bundle_update().build();
686 db.insert_account(address, pre);
687 db.commit(evm_state.clone());
688 db.merge_transitions(BundleRetention::PlainState);
689 let bundle = db.take_bundle();
690
691 let from_bundle =
692 HashedPostState::from_bundle_state::<reth_trie::KeccakKeyHasher>(bundle.state.iter());
693 let from_hook = evm_state_to_hashed_post_state(evm_state);
694
695 assert_eq!(
696 from_hook.accounts, from_bundle.accounts,
697 "state-hook and bundle producers disagree about durable account state"
698 );
699 }
700
701 #[test]
704 fn created_empty_account_matches_bundle_state() {
705 use revm::{
706 database::{states::bundle_state::BundleRetention, State},
707 DatabaseCommit,
708 };
709
710 let address = Address::repeat_byte(0x08);
711 let mut account = Account::default();
712 account.mark_touch();
713 assert!(account.mark_created_locally());
714 assert!(account.is_empty());
715 let evm_state = EvmState::from_iter([(address, account)]);
716
717 let mut db = State::builder().with_bundle_update().build();
718 db.commit(evm_state.clone());
719 db.merge_transitions(BundleRetention::PlainState);
720 let bundle = db.take_bundle();
721
722 let from_bundle =
723 HashedPostState::from_bundle_state::<reth_trie::KeccakKeyHasher>(bundle.state.iter());
724 let from_hook = evm_state_to_hashed_post_state(evm_state);
725
726 assert_eq!(
727 from_hook.accounts.get(&keccak256(address)).and_then(Option::as_ref),
728 from_bundle.accounts.get(&keccak256(address)).and_then(Option::as_ref),
729 "state-hook and bundle producers disagree about a created-empty account"
730 );
731 }
732
733 #[test]
738 fn touched_never_existing_account_matches_bundle_state() {
739 use revm::{
740 database::{states::bundle_state::BundleRetention, State},
741 DatabaseCommit,
742 };
743
744 let address = Address::repeat_byte(0x09);
745 let mut account = Account::new_not_existing(TransactionId::default());
746 account.mark_touch();
747 let evm_state = EvmState::from_iter([(address, account)]);
748
749 let mut db = State::builder().with_bundle_update().build();
750 db.commit(evm_state.clone());
751 db.merge_transitions(BundleRetention::PlainState);
752 let bundle = db.take_bundle();
753
754 let from_bundle =
755 HashedPostState::from_bundle_state::<reth_trie::KeccakKeyHasher>(bundle.state.iter());
756 let from_hook = evm_state_to_hashed_post_state(evm_state);
757
758 assert_eq!(
759 from_hook.accounts, from_bundle.accounts,
760 "state-hook and bundle producers disagree about a never-existing touched account"
761 );
762 }
763
764 #[test]
768 fn touched_unchanged_account_matches_bundle_state() {
769 use revm::{
770 database::{states::bundle_state::BundleRetention, State},
771 state::AccountInfo,
772 DatabaseCommit,
773 };
774
775 let address = Address::repeat_byte(0x0a);
776 let pre = AccountInfo { balance: U256::from(7), nonce: 1, ..Default::default() };
777 let mut account = Account::from(pre.clone());
778 account.mark_touch();
779 assert!(!account.is_empty());
780 let evm_state = EvmState::from_iter([(address, account)]);
781
782 let mut db = State::builder().with_bundle_update().build();
783 db.insert_account(address, pre);
784 db.commit(evm_state.clone());
785 db.merge_transitions(BundleRetention::PlainState);
786 let bundle = db.take_bundle();
787
788 let from_bundle =
789 HashedPostState::from_bundle_state::<reth_trie::KeccakKeyHasher>(bundle.state.iter());
790 let from_hook = evm_state_to_hashed_post_state(evm_state);
791
792 assert_eq!(
793 from_hook.accounts, from_bundle.accounts,
794 "state-hook and bundle producers disagree about a touched but unchanged account"
795 );
796 }
797
798 #[derive(Default)]
799 struct CountingSink {
800 state_updates: AtomicUsize,
801 finished_updates: AtomicUsize,
802 }
803
804 impl StateRootSink for CountingSink {
805 fn on_state_update(&self, state: EvmState) {
806 assert!(state.is_empty());
807 self.state_updates.fetch_add(1, Ordering::Relaxed);
808 }
809
810 fn on_hashed_state_update(&self, _state: HashedPostState) {}
811
812 fn on_updates_finished(&self) {
813 self.finished_updates.fetch_add(1, Ordering::Relaxed);
814 }
815 }
816
817 #[test]
818 fn state_access_hint_converts_to_sparse_targets() {
819 let account = B256::repeat_byte(0x01);
820 let storage_account = B256::repeat_byte(0x02);
821 let storage_slot = B256::repeat_byte(0x03);
822
823 let mut storages = B256Map::default();
824 storages.insert(storage_account, vec![storage_slot]);
825 let hint = StateAccessHint { accounts: vec![account], storages };
826
827 let targets = MultiProofTargetsV2::from(hint);
828 assert_eq!(targets.account_targets.len(), 1);
829 assert_eq!(targets.account_targets[0].key(), account);
830 assert_eq!(targets.storage_targets.len(), 1);
831 assert_eq!(targets.storage_targets[&storage_account].len(), 1);
832 assert_eq!(targets.storage_targets[&storage_account][0].key(), storage_slot);
833
834 let hint = StateAccessHint::from(targets);
835 assert_eq!(hint.accounts, vec![account]);
836 assert_eq!(hint.storages.len(), 1);
837 assert_eq!(hint.storages[&storage_account], vec![storage_slot]);
838 }
839
840 #[test]
843 fn hook_dropped_during_panic_does_not_finish_stream() {
844 let sink = Arc::new(CountingSink::default());
845 let hook = StateRootUpdateStream::new(sink.clone()).into_state_hook();
846
847 let result = std::thread::spawn(move || {
848 let _hook = hook;
849 panic!("execution died mid-block");
850 })
851 .join();
852
853 assert!(result.is_err());
854 assert_eq!(sink.finished_updates.load(Ordering::Relaxed), 0);
855 }
856
857 #[test]
860 #[should_panic(expected = "authoritative update capability already taken")]
861 fn authoritative_capability_can_only_be_taken_once() {
862 let (updates_tx, _updates_rx) = crossbeam_channel::unbounded();
863 let (cancel_guard, _cancel_rx) = StateRootTaskCancelGuard::channel();
864 let (_state_root_tx, state_root_rx) = std::sync::mpsc::channel();
865 let (_hashed_state_tx, hashed_state_rx) = std::sync::mpsc::channel();
866 let mut handle = StateRootHandle::new(
867 B256::ZERO,
868 updates_tx,
869 cancel_guard,
870 state_root_rx,
871 hashed_state_rx,
872 );
873
874 let _hook = handle.take_execution_hook();
875 let _ = handle.take_hashed_update_stream();
876 }
877
878 #[test]
883 fn payload_state_root_handle_lifecycle() {
884 let sink = Arc::new(CountingSink::default());
885 let hook = StateRootUpdateStream::new(sink.clone()).into_state_hook();
886
887 let (state_root_tx, state_root_rx) = std::sync::mpsc::channel();
888 let (hashed_state_tx, hashed_state_rx) = std::sync::mpsc::channel();
889 let mut handle =
890 PayloadStateRootHandle::new("test", Some(hook), state_root_rx, Some(hashed_state_rx));
891
892 assert_eq!(handle.name(), "test");
893
894 {
895 let mut hook = handle.take_state_hook();
896 hook.on_state(EvmState::default());
897 }
898 assert_eq!(sink.state_updates.load(Ordering::Relaxed), 1);
899 assert_eq!(sink.finished_updates.load(Ordering::Relaxed), 1);
900
901 hashed_state_tx.send(Arc::new(HashedPostState::default())).unwrap();
902 let rx = handle.try_take_hashed_state_rx().expect("first take returns the receiver");
903 assert!(rx.recv().is_ok());
904 assert!(handle.try_take_hashed_state_rx().is_none(), "second take returns None");
905
906 state_root_tx
907 .send(Ok(StateRootComputeOutcome {
908 state_root: B256::repeat_byte(0x42),
909 trie_updates: Arc::new(TrieUpdates::default()),
910 hashed_state: Arc::new(HashedPostState::default()),
911 }))
912 .unwrap();
913 let outcome = handle.state_root().expect("outcome is delivered");
914 assert_eq!(outcome.state_root, B256::repeat_byte(0x42));
915 }
916
917 #[test]
918 fn payload_state_root_receiver_retains_cancellation() {
919 let (updates_tx, _updates_rx) = crossbeam_channel::unbounded();
920 let (cancel_guard, cancel_rx) = StateRootTaskCancelGuard::channel();
921 let (_state_root_tx, state_root_rx) = std::sync::mpsc::channel();
922 let (_hashed_state_tx, hashed_state_rx) = std::sync::mpsc::channel();
923 let mut handle = StateRootHandle::new(
924 B256::ZERO,
925 updates_tx,
926 cancel_guard,
927 state_root_rx,
928 hashed_state_rx,
929 )
930 .into_payload_state_root_handle();
931
932 let state_root_rx = handle.take_state_root_rx();
933 assert!(matches!(
934 state_root_rx.recv_timeout(Duration::ZERO),
935 Err(std::sync::mpsc::RecvTimeoutError::Timeout)
936 ));
937 assert!(matches!(cancel_rx.try_recv(), Err(crossbeam_channel::TryRecvError::Empty)));
938
939 drop(handle);
940 assert!(matches!(
941 cancel_rx.recv_timeout(Duration::from_secs(1)),
942 Err(crossbeam_channel::RecvTimeoutError::Disconnected)
943 ));
944 }
945}