1use crate::error::StateRootTaskError;
11use alloy_evm::block::OnStateHook;
12use alloy_primitives::{keccak256, map::B256Map, B256};
13use reth_trie::{
14 updates::TrieUpdatesSorted, 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<TrieUpdatesSorted>,
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 on_payload_built: None,
186 }
187 }
188}
189
190#[derive(Debug)]
196pub struct StateRootTaskCancelGuard(#[allow(dead_code)] crossbeam_channel::Sender<()>);
197
198impl StateRootTaskCancelGuard {
199 pub fn channel() -> (Self, crossbeam_channel::Receiver<()>) {
201 let (tx, rx) = crossbeam_channel::bounded(0);
202 (Self(tx), rx)
203 }
204}
205
206pub struct PayloadStateRootHandle {
208 name: &'static str,
209 hook: Option<StateRootUpdateHook>,
211 cancel_guard: Option<StateRootTaskCancelGuard>,
213 state_root_rx:
214 Option<std::sync::mpsc::Receiver<Result<StateRootComputeOutcome, StateRootTaskError>>>,
215 hashed_state_rx: Option<std::sync::mpsc::Receiver<Arc<HashedPostState>>>,
216 on_payload_built: Option<Box<dyn FnOnce(B256, B256) + Send>>,
218}
219
220impl fmt::Debug for PayloadStateRootHandle {
221 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
222 f.debug_struct("PayloadStateRootHandle")
223 .field("name", &self.name)
224 .field("has_hook", &self.hook.is_some())
225 .field("has_cancel_guard", &self.cancel_guard.is_some())
226 .field("has_state_root_rx", &self.state_root_rx.is_some())
227 .field("has_hashed_state_rx", &self.hashed_state_rx.is_some())
228 .finish()
229 }
230}
231
232impl PayloadStateRootHandle {
233 pub const fn new(
238 name: &'static str,
239 hook: Option<StateRootUpdateHook>,
240 state_root_rx: std::sync::mpsc::Receiver<
241 Result<StateRootComputeOutcome, StateRootTaskError>,
242 >,
243 hashed_state_rx: Option<std::sync::mpsc::Receiver<Arc<HashedPostState>>>,
244 ) -> Self {
245 Self {
246 name,
247 hook,
248 cancel_guard: None,
249 state_root_rx: Some(state_root_rx),
250 hashed_state_rx,
251 on_payload_built: None,
252 }
253 }
254
255 pub fn with_on_payload_built(
259 mut self,
260 callback: impl FnOnce(B256, B256) + Send + 'static,
261 ) -> Self {
262 self.on_payload_built = Some(Box::new(callback));
263 self
264 }
265
266 pub fn take_on_payload_built(&mut self) -> Option<Box<dyn FnOnce(B256, B256) + Send>> {
269 self.on_payload_built.take()
270 }
271
272 pub const fn name(&self) -> &'static str {
274 self.name
275 }
276
277 pub const fn take_state_hook(&mut self) -> StateRootUpdateHook {
283 self.hook.take().expect("payload state root task missing execution hook")
284 }
285
286 pub fn state_root(&mut self) -> Result<StateRootComputeOutcome, StateRootTaskError> {
292 self.state_root_rx
293 .take()
294 .expect("state_root already taken")
295 .recv()
296 .map_err(|_| StateRootTaskError::Other("state root task dropped".to_string()))?
297 }
298
299 pub const fn take_state_root_rx(
307 &mut self,
308 ) -> std::sync::mpsc::Receiver<Result<StateRootComputeOutcome, StateRootTaskError>> {
309 self.state_root_rx.take().expect("state_root already taken")
310 }
311
312 pub const fn try_take_hashed_state_rx(
315 &mut self,
316 ) -> Option<std::sync::mpsc::Receiver<Arc<HashedPostState>>> {
317 self.hashed_state_rx.take()
318 }
319}
320
321#[derive(Debug, Clone, Default)]
327pub struct StateAccessHint {
328 pub accounts: Vec<B256>,
330 pub storages: B256Map<Vec<B256>>,
332}
333
334impl From<MultiProofTargetsV2> for StateAccessHint {
335 fn from(targets: MultiProofTargetsV2) -> Self {
336 Self {
337 accounts: targets.account_targets.into_iter().map(|target| target.key()).collect(),
338 storages: targets
339 .storage_targets
340 .into_iter()
341 .map(|(account, slots)| {
342 (account, slots.into_iter().map(|target| target.key()).collect())
343 })
344 .collect(),
345 }
346 }
347}
348
349impl From<StateAccessHint> for MultiProofTargetsV2 {
350 fn from(hint: StateAccessHint) -> Self {
351 Self {
352 account_targets: hint.accounts.into_iter().map(ProofV2Target::from).collect(),
353 storage_targets: hint
354 .storages
355 .into_iter()
356 .map(|(account, slots)| {
357 (account, slots.into_iter().map(ProofV2Target::from).collect())
358 })
359 .collect(),
360 }
361 }
362}
363
364pub trait StateRootSink: Send + Sync + 'static {
366 fn on_access_hint(&self, _hint: StateAccessHint) {}
368
369 fn on_state_update(&self, state: EvmState);
371
372 fn on_hashed_state_update(&self, state: HashedPostState);
374
375 fn on_updates_finished(&self);
377}
378
379#[derive(Clone)]
381pub struct StateRootHintStream {
382 inner: Arc<dyn StateRootSink>,
383}
384
385impl fmt::Debug for StateRootHintStream {
386 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
387 f.debug_struct("StateRootHintStream").finish_non_exhaustive()
388 }
389}
390
391impl StateRootHintStream {
392 pub fn new(inner: Arc<dyn StateRootSink>) -> Self {
394 Self { inner }
395 }
396
397 pub fn on_access_hint(&self, hint: StateAccessHint) {
399 self.inner.on_access_hint(hint);
400 }
401}
402
403pub struct StateRootUpdateStream {
415 inner: Arc<dyn StateRootSink>,
416}
417
418impl fmt::Debug for StateRootUpdateStream {
419 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
420 f.debug_struct("StateRootUpdateStream").finish_non_exhaustive()
421 }
422}
423
424impl StateRootUpdateStream {
425 pub fn new(inner: Arc<dyn StateRootSink>) -> Self {
427 Self { inner }
428 }
429
430 pub fn on_hashed_state_update(&self, state: HashedPostState) {
432 self.inner.on_hashed_state_update(state);
433 }
434
435 pub fn finish(self) {
437 self.inner.on_updates_finished();
438 }
439
440 pub fn into_state_hook(self) -> StateRootUpdateHook {
445 StateRootUpdateHook { inner: self.inner }
446 }
447}
448
449pub struct StateRootUpdateHook {
462 inner: Arc<dyn StateRootSink>,
463}
464
465impl fmt::Debug for StateRootUpdateHook {
466 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
467 f.debug_struct("StateRootUpdateHook").finish_non_exhaustive()
468 }
469}
470
471impl OnStateHook for StateRootUpdateHook {
472 fn on_state(&mut self, state: EvmState) {
473 self.inner.on_state_update(state);
474 }
475}
476
477impl Drop for StateRootUpdateHook {
478 fn drop(&mut self) {
479 if std::thread::panicking() {
482 return;
483 }
484 self.inner.on_updates_finished();
485 }
486}
487
488#[derive(Debug, Clone)]
489struct SparseTrieStateRootSink {
490 sender: crossbeam_channel::Sender<StateRootMessage>,
491}
492
493impl SparseTrieStateRootSink {
494 const fn new(sender: crossbeam_channel::Sender<StateRootMessage>) -> Self {
495 Self { sender }
496 }
497}
498
499impl StateRootSink for SparseTrieStateRootSink {
500 fn on_access_hint(&self, hint: StateAccessHint) {
501 let _ = self.sender.send(StateRootMessage::PrefetchProofs(hint.into()));
502 }
503
504 fn on_state_update(&self, state: EvmState) {
505 let _ = self.sender.send(StateRootMessage::StateUpdate(state));
506 }
507
508 fn on_hashed_state_update(&self, state: HashedPostState) {
509 let _ = self.sender.send(StateRootMessage::HashedStateUpdate(state));
510 }
511
512 fn on_updates_finished(&self) {
513 let _ = self.sender.send(StateRootMessage::FinishedStateUpdates);
514 }
515}
516
517pub fn evm_state_to_hashed_post_state(update: EvmState) -> HashedPostState {
519 let mut hashed_state = HashedPostState::with_capacity(update.len());
520
521 for (address, account) in update {
522 if account.is_touched() {
523 let hashed_address = keccak256(address);
524 trace!(target: "trie::parallel::sparse", ?address, ?hashed_address, "Adding account to state update");
525
526 let destroyed = account.is_selfdestructed();
527 let deleted = destroyed || (account.is_empty() && !account.is_loaded_as_not_existing());
535 if deleted {
536 hashed_state.accounts.insert(hashed_address, None);
537 } else if account.info != account.original_info() {
538 hashed_state.accounts.insert(hashed_address, Some(account.info.into()));
540 }
541
542 let mut changed_storage_iter = account
543 .storage
544 .into_iter()
545 .filter(|(_slot, value)| value.is_changed())
546 .map(|(slot, value)| (keccak256(B256::from(slot)), value.present_value))
547 .peekable();
548
549 if !destroyed && changed_storage_iter.peek().is_some() {
550 hashed_state
551 .storages
552 .insert(hashed_address, HashedStorage::from_iter(changed_storage_iter));
553 }
554 }
555 }
556
557 hashed_state
558}
559
560#[cfg(test)]
561mod tests {
562 use super::*;
563 use alloy_primitives::{Address, U256};
564 use revm::state::{Account, EvmStorageSlot, TransactionId};
565 use std::{
566 sync::atomic::{AtomicUsize, Ordering},
567 time::Duration,
568 };
569
570 #[test]
571 fn created_selfdestruct_does_not_emit_storage() {
572 let address = Address::repeat_byte(0x01);
573 let mut account = Account::new_not_existing(TransactionId::ZERO);
574 account.mark_touch();
575 assert!(account.mark_created_locally());
576 assert!(account.mark_selfdestructed_locally());
577 account.info.nonce = 1;
578 account.storage.insert(
579 U256::from(1),
580 EvmStorageSlot::new_changed(U256::ZERO, U256::from(2), TransactionId::ZERO),
581 );
582
583 let hashed_state =
584 evm_state_to_hashed_post_state(EvmState::from_iter([(address, account)]));
585 let hashed_address = keccak256(address);
586
587 assert_eq!(hashed_state.accounts.get(&hashed_address), Some(&None));
588 assert!(!hashed_state.storages.contains_key(&hashed_address));
589 }
590
591 #[test]
592 fn existing_selfdestruct_does_not_emit_storage() {
593 let address = Address::repeat_byte(0x02);
594 let mut account = Account::default();
595 account.info.nonce = 1;
596 account.set_current_info_as_original();
597 account.mark_touch();
598 assert!(account.mark_selfdestructed_locally());
599 account.selfdestruct();
600 account.storage.insert(
601 U256::from(1),
602 EvmStorageSlot::new_changed(U256::ZERO, U256::from(2), TransactionId::ZERO),
603 );
604
605 let hashed_state =
606 evm_state_to_hashed_post_state(EvmState::from_iter([(address, account)]));
607 let hashed_address = keccak256(address);
608
609 assert_eq!(hashed_state.accounts.get(&hashed_address), Some(&None));
610 assert!(!hashed_state.storages.contains_key(&hashed_address));
611 }
612
613 #[test]
618 fn emptied_account_is_deleted() {
619 let address = Address::repeat_byte(0x05);
620 let mut account = Account::default();
621 account.info.balance = U256::from(1);
623 account.set_current_info_as_original();
624 account.mark_touch();
626 account.info.balance = U256::ZERO;
627 assert!(account.is_empty(), "the drained account must be EIP-161-empty");
628 assert!(!account.is_selfdestructed());
629
630 let hashed_state =
631 evm_state_to_hashed_post_state(EvmState::from_iter([(address, account)]));
632
633 assert_eq!(hashed_state.accounts.get(&keccak256(address)), Some(&None));
634 }
635
636 #[test]
641 fn touched_preexisting_empty_account_is_deleted() {
642 let address = Address::repeat_byte(0x06);
643 let mut account = Account::default();
645 account.set_current_info_as_original();
646 account.mark_touch();
647 assert!(account.is_empty());
648
649 let hashed_state =
650 evm_state_to_hashed_post_state(EvmState::from_iter([(address, account)]));
651
652 assert_eq!(hashed_state.accounts.get(&keccak256(address)), Some(&None));
653 }
654
655 #[test]
665 fn matches_bundle_state_for_emptied_account() {
666 use revm::{
667 database::{states::bundle_state::BundleRetention, State},
668 state::AccountInfo,
669 DatabaseCommit,
670 };
671
672 let address = Address::repeat_byte(0x07);
673 let pre = AccountInfo { balance: U256::from(1), ..Default::default() };
674
675 let mut account = Account::from(pre.clone());
677 account.mark_touch();
678 account.info.balance = U256::ZERO;
679 let evm_state = EvmState::from_iter([(address, account)]);
680
681 let mut db = State::builder().with_bundle_update().build();
683 db.insert_account(address, pre);
684 db.commit(evm_state.clone());
685 db.merge_transitions(BundleRetention::PlainState);
686 let bundle = db.take_bundle();
687
688 let from_bundle =
689 HashedPostState::from_bundle_state::<reth_trie::KeccakKeyHasher>(bundle.state.iter());
690 let from_hook = evm_state_to_hashed_post_state(evm_state);
691
692 assert_eq!(
693 from_hook.accounts, from_bundle.accounts,
694 "state-hook and bundle producers disagree about durable account state"
695 );
696 }
697
698 #[test]
701 fn created_empty_account_matches_bundle_state() {
702 use revm::{
703 database::{states::bundle_state::BundleRetention, State},
704 DatabaseCommit,
705 };
706
707 let address = Address::repeat_byte(0x08);
708 let mut account = Account::default();
709 account.mark_touch();
710 assert!(account.mark_created_locally());
711 assert!(account.is_empty());
712 let evm_state = EvmState::from_iter([(address, account)]);
713
714 let mut db = State::builder().with_bundle_update().build();
715 db.commit(evm_state.clone());
716 db.merge_transitions(BundleRetention::PlainState);
717 let bundle = db.take_bundle();
718
719 let from_bundle =
720 HashedPostState::from_bundle_state::<reth_trie::KeccakKeyHasher>(bundle.state.iter());
721 let from_hook = evm_state_to_hashed_post_state(evm_state);
722
723 assert_eq!(
724 from_hook.accounts.get(&keccak256(address)).and_then(Option::as_ref),
725 from_bundle.accounts.get(&keccak256(address)).and_then(Option::as_ref),
726 "state-hook and bundle producers disagree about a created-empty account"
727 );
728 }
729
730 #[test]
735 fn touched_never_existing_account_matches_bundle_state() {
736 use revm::{
737 database::{states::bundle_state::BundleRetention, State},
738 DatabaseCommit,
739 };
740
741 let address = Address::repeat_byte(0x09);
742 let mut account = Account::new_not_existing(TransactionId::default());
743 account.mark_touch();
744 let evm_state = EvmState::from_iter([(address, account)]);
745
746 let mut db = State::builder().with_bundle_update().build();
747 db.commit(evm_state.clone());
748 db.merge_transitions(BundleRetention::PlainState);
749 let bundle = db.take_bundle();
750
751 let from_bundle =
752 HashedPostState::from_bundle_state::<reth_trie::KeccakKeyHasher>(bundle.state.iter());
753 let from_hook = evm_state_to_hashed_post_state(evm_state);
754
755 assert_eq!(
756 from_hook.accounts, from_bundle.accounts,
757 "state-hook and bundle producers disagree about a never-existing touched account"
758 );
759 }
760
761 #[test]
765 fn touched_unchanged_account_matches_bundle_state() {
766 use revm::{
767 database::{states::bundle_state::BundleRetention, State},
768 state::AccountInfo,
769 DatabaseCommit,
770 };
771
772 let address = Address::repeat_byte(0x0a);
773 let pre = AccountInfo { balance: U256::from(7), nonce: 1, ..Default::default() };
774 let mut account = Account::from(pre.clone());
775 account.mark_touch();
776 assert!(!account.is_empty());
777 let evm_state = EvmState::from_iter([(address, account)]);
778
779 let mut db = State::builder().with_bundle_update().build();
780 db.insert_account(address, pre);
781 db.commit(evm_state.clone());
782 db.merge_transitions(BundleRetention::PlainState);
783 let bundle = db.take_bundle();
784
785 let from_bundle =
786 HashedPostState::from_bundle_state::<reth_trie::KeccakKeyHasher>(bundle.state.iter());
787 let from_hook = evm_state_to_hashed_post_state(evm_state);
788
789 assert_eq!(
790 from_hook.accounts, from_bundle.accounts,
791 "state-hook and bundle producers disagree about a touched but unchanged account"
792 );
793 }
794
795 #[derive(Default)]
796 struct CountingSink {
797 access_hints: AtomicUsize,
798 state_updates: AtomicUsize,
799 hashed_state_updates: AtomicUsize,
800 finished_updates: AtomicUsize,
801 }
802
803 impl StateRootSink for CountingSink {
804 fn on_access_hint(&self, hint: StateAccessHint) {
805 assert_eq!(hint.accounts, vec![B256::repeat_byte(0x01)]);
806 assert_eq!(
807 hint.storages.get(&B256::repeat_byte(0x02)),
808 Some(&vec![B256::repeat_byte(0x03)])
809 );
810 self.access_hints.fetch_add(1, Ordering::Relaxed);
811 }
812
813 fn on_state_update(&self, state: EvmState) {
814 assert!(state.is_empty());
815 self.state_updates.fetch_add(1, Ordering::Relaxed);
816 }
817
818 fn on_hashed_state_update(&self, state: HashedPostState) {
819 assert!(state.accounts.is_empty());
820 assert!(state.storages.is_empty());
821 self.hashed_state_updates.fetch_add(1, Ordering::Relaxed);
822 }
823
824 fn on_updates_finished(&self) {
825 self.finished_updates.fetch_add(1, Ordering::Relaxed);
826 }
827 }
828
829 #[test]
830 fn state_access_hint_converts_to_sparse_targets() {
831 let account = B256::repeat_byte(0x01);
832 let storage_account = B256::repeat_byte(0x02);
833 let storage_slot = B256::repeat_byte(0x03);
834
835 let mut storages = B256Map::default();
836 storages.insert(storage_account, vec![storage_slot]);
837 let hint = StateAccessHint { accounts: vec![account], storages };
838
839 let targets = MultiProofTargetsV2::from(hint);
840 assert_eq!(targets.account_targets.len(), 1);
841 assert_eq!(targets.account_targets[0].key(), account);
842 assert_eq!(targets.storage_targets.len(), 1);
843 assert_eq!(targets.storage_targets[&storage_account].len(), 1);
844 assert_eq!(targets.storage_targets[&storage_account][0].key(), storage_slot);
845
846 let hint = StateAccessHint::from(targets);
847 assert_eq!(hint.accounts, vec![account]);
848 assert_eq!(hint.storages.len(), 1);
849 assert_eq!(hint.storages[&storage_account], vec![storage_slot]);
850 }
851
852 #[test]
853 fn state_root_capabilities_forward_to_sink() {
854 let sink = Arc::new(CountingSink::default());
855
856 let hint_stream = StateRootHintStream::new(sink.clone());
857 let mut storages = B256Map::default();
858 storages.insert(B256::repeat_byte(0x02), vec![B256::repeat_byte(0x03)]);
859 hint_stream
860 .on_access_hint(StateAccessHint { accounts: vec![B256::repeat_byte(0x01)], storages });
861
862 let updates = StateRootUpdateStream::new(sink.clone());
863 updates.on_hashed_state_update(HashedPostState::default());
864 updates.finish();
865
866 {
867 let mut hook = StateRootUpdateStream::new(sink.clone()).into_state_hook();
868 hook.on_state(EvmState::default());
869 }
870
871 assert_eq!(sink.access_hints.load(Ordering::Relaxed), 1);
872 assert_eq!(sink.state_updates.load(Ordering::Relaxed), 1);
873 assert_eq!(sink.hashed_state_updates.load(Ordering::Relaxed), 1);
874 assert_eq!(sink.finished_updates.load(Ordering::Relaxed), 2);
875 }
876
877 #[test]
880 fn hook_dropped_during_panic_does_not_finish_stream() {
881 let sink = Arc::new(CountingSink::default());
882 let hook = StateRootUpdateStream::new(sink.clone()).into_state_hook();
883
884 let result = std::thread::spawn(move || {
885 let _hook = hook;
886 panic!("execution died mid-block");
887 })
888 .join();
889
890 assert!(result.is_err());
891 assert_eq!(sink.finished_updates.load(Ordering::Relaxed), 0);
892 }
893
894 #[test]
897 #[should_panic(expected = "authoritative update capability already taken")]
898 fn authoritative_capability_can_only_be_taken_once() {
899 let (updates_tx, _updates_rx) = crossbeam_channel::unbounded();
900 let (cancel_guard, _cancel_rx) = StateRootTaskCancelGuard::channel();
901 let (_state_root_tx, state_root_rx) = std::sync::mpsc::channel();
902 let (_hashed_state_tx, hashed_state_rx) = std::sync::mpsc::channel();
903 let mut handle = StateRootHandle::new(
904 B256::ZERO,
905 updates_tx,
906 cancel_guard,
907 state_root_rx,
908 hashed_state_rx,
909 );
910
911 let _hook = handle.take_execution_hook();
912 let _ = handle.take_hashed_update_stream();
913 }
914
915 #[test]
920 fn payload_state_root_handle_lifecycle() {
921 let sink = Arc::new(CountingSink::default());
922 let hook = StateRootUpdateStream::new(sink.clone()).into_state_hook();
923
924 let (state_root_tx, state_root_rx) = std::sync::mpsc::channel();
925 let (hashed_state_tx, hashed_state_rx) = std::sync::mpsc::channel();
926 let mut handle =
927 PayloadStateRootHandle::new("test", Some(hook), state_root_rx, Some(hashed_state_rx));
928
929 assert_eq!(handle.name(), "test");
930
931 {
932 let mut hook = handle.take_state_hook();
933 hook.on_state(EvmState::default());
934 }
935 assert_eq!(sink.state_updates.load(Ordering::Relaxed), 1);
936 assert_eq!(sink.finished_updates.load(Ordering::Relaxed), 1);
937
938 hashed_state_tx.send(Arc::new(HashedPostState::default())).unwrap();
939 let rx = handle.try_take_hashed_state_rx().expect("first take returns the receiver");
940 assert!(rx.recv().is_ok());
941 assert!(handle.try_take_hashed_state_rx().is_none(), "second take returns None");
942
943 state_root_tx
944 .send(Ok(StateRootComputeOutcome {
945 state_root: B256::repeat_byte(0x42),
946 trie_updates: Arc::new(TrieUpdatesSorted::default()),
947 hashed_state: Arc::new(HashedPostState::default()),
948 }))
949 .unwrap();
950 let outcome = handle.state_root().expect("outcome is delivered");
951 assert_eq!(outcome.state_root, B256::repeat_byte(0x42));
952 }
953
954 #[test]
955 #[should_panic(expected = "state_root already taken")]
956 fn payload_state_root_receiver_can_only_be_taken_once() {
957 let (_state_root_tx, state_root_rx) = std::sync::mpsc::channel();
958 let mut handle = PayloadStateRootHandle::new("test", None, state_root_rx, None);
959
960 let _state_root_rx = handle.take_state_root_rx();
961 let _ = handle.take_state_root_rx();
962 }
963
964 #[test]
965 fn payload_state_root_receiver_retains_cancellation() {
966 let (updates_tx, _updates_rx) = crossbeam_channel::unbounded();
967 let (cancel_guard, cancel_rx) = StateRootTaskCancelGuard::channel();
968 let (_state_root_tx, state_root_rx) = std::sync::mpsc::channel();
969 let (_hashed_state_tx, hashed_state_rx) = std::sync::mpsc::channel();
970 let mut handle = StateRootHandle::new(
971 B256::ZERO,
972 updates_tx,
973 cancel_guard,
974 state_root_rx,
975 hashed_state_rx,
976 )
977 .into_payload_state_root_handle();
978
979 let state_root_rx = handle.take_state_root_rx();
980 assert!(matches!(
981 state_root_rx.recv_timeout(Duration::ZERO),
982 Err(std::sync::mpsc::RecvTimeoutError::Timeout)
983 ));
984 assert!(matches!(cancel_rx.try_recv(), Err(crossbeam_channel::TryRecvError::Empty)));
985
986 drop(handle);
987 assert!(matches!(
988 cancel_rx.recv_timeout(Duration::from_secs(1)),
989 Err(crossbeam_channel::RecvTimeoutError::Disconnected)
990 ));
991 }
992}