Skip to main content

reth_trie_common/
lazy_hashed_state.rs

1//! Sorted hashed state published by a background task.
2
3use crate::{HashedPostState, HashedPostStateSorted};
4use alloc::sync::Arc;
5use core::fmt;
6use reth_primitives_traits::sync::OnceLock;
7
8/// Shared sorted hashed state, available immediately or published by a background task.
9/// Clones share the same result and wait for the producer when it is still pending.
10#[derive(Clone)]
11pub struct LazyHashedPostStateSorted {
12    value: Arc<OnceLock<Arc<HashedPostStateSorted>>>,
13}
14
15impl LazyHashedPostStateSorted {
16    /// Creates a handle to already sorted hashed state.
17    pub fn ready(state: Arc<HashedPostStateSorted>) -> Self {
18        Self { value: Arc::new(OnceLock::from(state)) }
19    }
20
21    /// Creates a handle and a producer that sorts and publishes the hashed state.
22    #[cfg(feature = "std")]
23    pub fn pending(hashed_state: Arc<HashedPostState>) -> (Self, HashedPostStateSortedProducer) {
24        let value = Arc::new(OnceLock::new());
25        (Self { value: Arc::clone(&value) }, HashedPostStateSortedProducer { value, hashed_state })
26    }
27
28    /// Returns the sorted hashed state, waiting for its producer if it is still pending.
29    pub fn get(&self) -> &Arc<HashedPostStateSorted> {
30        #[cfg(feature = "std")]
31        {
32            self.value.wait()
33        }
34        #[cfg(not(feature = "std"))]
35        {
36            self.value.get().expect("hashed state must be initialized")
37        }
38    }
39}
40
41impl fmt::Debug for LazyHashedPostStateSorted {
42    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
43        f.debug_struct("LazyHashedPostStateSorted")
44            .field("initialized", &self.value.get().is_some())
45            .finish()
46    }
47}
48
49impl PartialEq for LazyHashedPostStateSorted {
50    fn eq(&self, other: &Self) -> bool {
51        self.get() == other.get()
52    }
53}
54
55impl Eq for LazyHashedPostStateSorted {}
56
57#[cfg(feature = "serde")]
58impl serde::Serialize for LazyHashedPostStateSorted {
59    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
60    where
61        S: serde::Serializer,
62    {
63        self.get().serialize(serializer)
64    }
65}
66
67#[cfg(feature = "serde")]
68impl<'de> serde::Deserialize<'de> for LazyHashedPostStateSorted {
69    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
70    where
71        D: serde::Deserializer<'de>,
72    {
73        Arc::deserialize(deserializer).map(Self::ready)
74    }
75}
76
77/// Sorts hashed state and publishes it to waiting handles.
78#[derive(Debug)]
79#[must_use = "call compute_and_publish to wake hashed state waiters"]
80pub struct HashedPostStateSortedProducer {
81    value: Arc<OnceLock<Arc<HashedPostStateSorted>>>,
82    hashed_state: Arc<HashedPostState>,
83}
84
85impl HashedPostStateSortedProducer {
86    /// Sorts hashed state, publishes it to waiters, and returns the shared result.
87    pub fn compute_and_publish(self) -> Arc<HashedPostStateSorted> {
88        let sorted = Arc::new(match Arc::try_unwrap(self.hashed_state) {
89            Ok(state) => state.into_sorted(),
90            Err(state) => state.clone_into_sorted(),
91        });
92        let _ = self.value.set(Arc::clone(&sorted));
93        sorted
94    }
95}
96
97#[cfg(test)]
98mod tests {
99    use crate::HashedStorage;
100
101    use super::*;
102    use alloy_primitives::{map::B256Map, B256, U256};
103    use reth_primitives_traits::Account;
104    use std::{
105        thread,
106        time::{Duration, Instant},
107    };
108
109    fn empty_pending() -> (LazyHashedPostStateSorted, HashedPostStateSortedProducer) {
110        LazyHashedPostStateSorted::pending(Arc::new(HashedPostState::default()))
111    }
112
113    #[test]
114    fn test_lazy_ready_is_initialized() {
115        let lazy = LazyHashedPostStateSorted::ready(Arc::default());
116        let _ = lazy.get();
117    }
118
119    #[test]
120    fn test_lazy_clone_shares_state() {
121        let lazy1 = LazyHashedPostStateSorted::ready(Arc::default());
122        let lazy2 = lazy1.clone();
123
124        // Both point to the same data
125        assert!(Arc::ptr_eq(lazy1.get(), lazy2.get()));
126    }
127
128    #[test]
129    fn ready_returns_immediately() {
130        let bundle = Arc::new(HashedPostStateSorted::default());
131        let deferred = LazyHashedPostStateSorted::ready(bundle.clone());
132
133        let result = deferred.get();
134
135        assert_eq!(result.total_len(), bundle.total_len());
136    }
137
138    #[test]
139    fn pending_waits_for_task_and_caches_result() {
140        let (deferred, task) = empty_pending();
141        let published = task.compute_and_publish();
142        let first = deferred.get();
143        let second = deferred.get();
144
145        assert!(Arc::ptr_eq(&published, first));
146        assert!(Arc::ptr_eq(first, second));
147    }
148
149    #[test]
150    fn pending_wait_blocks_until_task_publishes() {
151        let (deferred, task) = empty_pending();
152
153        let handle = thread::spawn(move || deferred.get().clone());
154        thread::sleep(Duration::from_millis(20));
155        assert!(!handle.is_finished());
156
157        let published = task.compute_and_publish();
158        let result = handle.join().unwrap();
159
160        assert!(Arc::ptr_eq(&published, &result));
161    }
162
163    #[test]
164    fn concurrent_waits_share_published_result() {
165        let (deferred, task) = empty_pending();
166        let deferred2 = deferred.clone();
167
168        let handle = thread::spawn(move || deferred2.get().clone());
169        let published = task.compute_and_publish();
170        let result1 = deferred.get().clone();
171        let result2 = handle.join().unwrap();
172
173        assert!(Arc::ptr_eq(&published, &result1));
174        assert!(Arc::ptr_eq(&result1, &result2));
175    }
176
177    #[test]
178    fn sorts_non_empty_inputs() {
179        let hashed_address = B256::with_last_byte(1);
180        let hashed_slot = B256::with_last_byte(2);
181        let hashed_state = HashedPostState::default()
182            .with_accounts([(hashed_address, Some(Account::default()))])
183            .with_storages([(
184                hashed_address,
185                HashedStorage::from_iter([(hashed_slot, U256::from(1))]),
186            )]);
187
188        let (deferred, task) = LazyHashedPostStateSorted::pending(Arc::new(hashed_state));
189        let _ = task.compute_and_publish();
190        let result = deferred.get().clone();
191
192        assert_eq!(result.total_len(), 2);
193    }
194
195    #[test]
196    fn wait_does_not_block_after_first_compute() {
197        let mut accounts = B256Map::default();
198        for i in 0..100 {
199            accounts.insert(B256::with_last_byte(i), Some(Account::default()));
200        }
201        let (deferred, task) = LazyHashedPostStateSorted::pending(Arc::new(HashedPostState {
202            accounts,
203            storages: Default::default(),
204        }));
205
206        let _ = task.compute_and_publish();
207        let _ = deferred.get().clone();
208        let start = Instant::now();
209        let _ = deferred.get().clone();
210
211        assert!(start.elapsed() < Duration::from_millis(10));
212    }
213}