Skip to main content

reth_trie_common/
trie_data.rs

1//! Lazy initialization wrapper for trie data.
2//!
3//! Provides a no-std compatible [`LazyTrieData`] type for lazily initialized
4//! trie-related data containing sorted hashed state and trie updates.
5
6use crate::{
7    updates::{TrieUpdates, TrieUpdatesSorted},
8    HashedPostState, HashedPostStateSorted,
9};
10use alloc::sync::Arc;
11use core::fmt;
12use reth_primitives_traits::sync::OnceLock;
13
14/// Container for sorted trie data: hashed state and trie updates.
15///
16/// This bundles both [`HashedPostStateSorted`] and [`TrieUpdatesSorted`] together
17/// for convenient passing and storage.
18#[derive(Clone, Debug, Default, PartialEq, Eq)]
19#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
20pub struct SortedTrieData {
21    /// Sorted hashed post-state produced by execution.
22    pub hashed_state: Arc<HashedPostStateSorted>,
23    /// Sorted trie updates produced by state root computation.
24    pub trie_updates: Arc<TrieUpdatesSorted>,
25}
26
27impl SortedTrieData {
28    /// Creates a new [`SortedTrieData`] with the given values.
29    pub const fn new(
30        hashed_state: Arc<HashedPostStateSorted>,
31        trie_updates: Arc<TrieUpdatesSorted>,
32    ) -> Self {
33        Self { hashed_state, trie_updates }
34    }
35}
36
37/// Lazily initialized trie data containing sorted hashed state and trie updates.
38///
39/// This is a no-std compatible wrapper that supports three modes:
40/// 1. **Ready mode**: Data is available immediately (created via `ready()`)
41/// 2. **Deferred mode**: Data is computed on first access (created via `deferred()`)
42/// 3. **Pending mode**: Access blocks until the caller-owned producer publishes the data (created
43///    via `pending()`). No task is spawned automatically.
44///
45/// In deferred mode, the computation runs on the first call to `get()`, `hashed_state()`,
46/// or `trie_updates()`, and results are cached for subsequent calls.
47///
48/// Cloning is cheap (Arc clone) and clones share the cached state.
49pub struct LazyTrieData {
50    /// Cached sorted trie data, computed on first access.
51    data: Arc<OnceLock<SortedTrieData>>,
52    // /// Optional deferred computation function.
53    // compute: Option<Arc<dyn Fn() -> SortedTrieData + Send + Sync>>,
54    /// Lazy mode.
55    mode: LazyTrieDataMode,
56}
57
58impl Clone for LazyTrieData {
59    fn clone(&self) -> Self {
60        Self { data: Arc::clone(&self.data), mode: self.mode.clone() }
61    }
62}
63
64impl fmt::Debug for LazyTrieData {
65    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
66        f.debug_struct("LazyTrieData")
67            .field("data", &if self.data.get().is_some() { "initialized" } else { "pending" })
68            .finish()
69    }
70}
71
72impl PartialEq for LazyTrieData {
73    fn eq(&self, other: &Self) -> bool {
74        self.get() == other.get()
75    }
76}
77
78impl Eq for LazyTrieData {}
79
80impl LazyTrieData {
81    /// Creates a new [`LazyTrieData`] that is already initialized with the given values.
82    pub fn ready(sorted: SortedTrieData) -> Self {
83        Self { data: Arc::new(OnceLock::from(sorted)), mode: LazyTrieDataMode::Ready }
84    }
85
86    /// Creates a new [`LazyTrieData`] with a deferred computation function.
87    ///
88    /// The computation will run on the first call to `get()`, `hashed_state()`,
89    /// or `trie_updates()`. Results are cached for subsequent calls.
90    pub fn deferred(compute: impl Fn() -> SortedTrieData + Send + Sync + 'static) -> Self {
91        Self {
92            data: Arc::new(OnceLock::new()),
93            mode: LazyTrieDataMode::Deferred(Arc::new(compute)),
94        }
95    }
96
97    /// Creates a pending [`LazyTrieData`] and its producer without spawning a task.
98    ///
99    /// The caller must arrange for `LazyTrieDataProducer::compute_and_publish` to run before or
100    /// concurrently with access to the handle. Access blocks until publication; dropping the
101    /// producer without publishing leaves access blocked indefinitely.
102    #[cfg(feature = "std")]
103    pub fn pending(
104        hashed_state: Arc<HashedPostState>,
105        trie_updates: Arc<TrieUpdates>,
106    ) -> (Self, LazyTrieDataProducer) {
107        let value = Arc::new(OnceLock::new());
108        (
109            Self { data: Arc::clone(&value), mode: LazyTrieDataMode::Pending },
110            LazyTrieDataProducer { value, inputs: PendingInputs { hashed_state, trie_updates } },
111        )
112    }
113
114    /// Returns a reference to the sorted trie data.
115    ///
116    /// Deferred mode computes on first access. Pending mode blocks until the producer publishes
117    /// the result and can block indefinitely if publication never occurs.
118    ///
119    /// # Panics
120    ///
121    /// Panics if in ready state, but value has not been initialized.
122    pub fn get(&self) -> &SortedTrieData {
123        match &self.mode {
124            LazyTrieDataMode::Ready => self.data.get().expect("LazyTrieData must be initialized"),
125            LazyTrieDataMode::Deferred(compute) => self.data.get_or_init(|| compute.as_ref()()),
126            #[cfg(feature = "std")]
127            LazyTrieDataMode::Pending => self.data.wait(),
128        }
129    }
130
131    /// Returns a clone of the hashed state Arc.
132    ///
133    /// Calls [`Self::get`], computing deferred data or blocking until pending data is published.
134    pub fn hashed_state(&self) -> Arc<HashedPostStateSorted> {
135        Arc::clone(&self.get().hashed_state)
136    }
137
138    /// Returns a clone of the trie updates Arc.
139    ///
140    /// Calls [`Self::get`], computing deferred data or blocking until pending data is published.
141    pub fn trie_updates(&self) -> Arc<TrieUpdatesSorted> {
142        Arc::clone(&self.get().trie_updates)
143    }
144
145    /// Returns a clone of the [`SortedTrieData`].
146    ///
147    /// Calls [`Self::get`], computing deferred data or blocking until pending data is published.
148    pub fn sorted_trie_data(&self) -> SortedTrieData {
149        self.get().clone()
150    }
151}
152
153#[cfg(feature = "serde")]
154impl serde::Serialize for LazyTrieData {
155    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
156    where
157        S: serde::Serializer,
158    {
159        self.get().serialize(serializer)
160    }
161}
162
163#[cfg(feature = "serde")]
164impl<'de> serde::Deserialize<'de> for LazyTrieData {
165    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
166    where
167        D: serde::Deserializer<'de>,
168    {
169        let data = SortedTrieData::deserialize(deserializer)?;
170        Ok(Self::ready(data))
171    }
172}
173
174#[derive(Clone)]
175enum LazyTrieDataMode {
176    Ready,
177    Deferred(Arc<dyn Fn() -> SortedTrieData + Send + Sync>),
178    #[cfg(feature = "std")]
179    Pending,
180}
181
182impl fmt::Debug for LazyTrieDataMode {
183    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
184        match self {
185            Self::Ready => write!(f, "Ready"),
186            Self::Deferred(_) => write!(f, "Deferred(..)"),
187            #[cfg(feature = "std")]
188            Self::Pending => write!(f, "Pending"),
189        }
190    }
191}
192
193/// Producer that computes and publishes sorted trie data for a [`LazyTrieData`] handle.
194///
195/// The caller is responsible for running [`Self::compute_and_publish`]. Dropping the producer
196/// without publishing leaves pending accessors blocked indefinitely.
197#[must_use = "LazyTrieDataProducer must be consumed with compute_and_publish to wake trie data waiters"]
198pub struct LazyTrieDataProducer {
199    /// Shared result initialized exactly once by this producer.
200    value: Arc<OnceLock<SortedTrieData>>,
201    /// Unsorted inputs consumed when the producer computes trie data.
202    inputs: PendingInputs,
203}
204
205impl fmt::Debug for LazyTrieDataProducer {
206    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
207        f.debug_struct("LazyTrieDataProducer").field("inputs", &self.inputs).finish_non_exhaustive()
208    }
209}
210
211impl LazyTrieDataProducer {
212    /// Computes sorted trie data, publishes it to waiters, and returns it to the task owner.
213    pub fn compute_and_publish(self) -> SortedTrieData {
214        let Self { value, inputs } = self;
215        let computed = Self::sort(inputs.hashed_state, inputs.trie_updates);
216        let _ = value.set(computed.clone());
217        computed
218    }
219
220    /// Sorts block execution outputs.
221    pub fn sort(
222        hashed_state: Arc<HashedPostState>,
223        trie_updates: Arc<TrieUpdates>,
224    ) -> SortedTrieData {
225        #[cfg(feature = "rayon")]
226        let (sorted_hashed_state, sorted_trie_updates) = rayon::join(
227            || match Arc::try_unwrap(hashed_state) {
228                Ok(state) => state.into_sorted(),
229                Err(arc) => arc.clone_into_sorted(),
230            },
231            || match Arc::try_unwrap(trie_updates) {
232                Ok(updates) => updates.into_sorted(),
233                Err(arc) => arc.clone_into_sorted(),
234            },
235        );
236
237        #[cfg(not(feature = "rayon"))]
238        let (sorted_hashed_state, sorted_trie_updates) = (
239            match Arc::try_unwrap(hashed_state) {
240                Ok(state) => state.into_sorted(),
241                Err(arc) => arc.clone_into_sorted(),
242            },
243            match Arc::try_unwrap(trie_updates) {
244                Ok(updates) => updates.into_sorted(),
245                Err(arc) => arc.clone_into_sorted(),
246            },
247        );
248
249        SortedTrieData::new(Arc::new(sorted_hashed_state), Arc::new(sorted_trie_updates))
250    }
251}
252
253/// Inputs kept while a deferred trie computation is pending.
254#[derive(Clone, Debug)]
255struct PendingInputs {
256    /// Unsorted hashed post-state from execution.
257    hashed_state: Arc<HashedPostState>,
258    /// Unsorted trie updates from state root computation.
259    trie_updates: Arc<TrieUpdates>,
260}
261
262#[cfg(test)]
263mod tests {
264    use crate::HashedStorage;
265
266    use super::*;
267    use alloy_primitives::{B256, U256};
268    use reth_primitives_traits::Account;
269    use std::{thread, time::Duration};
270
271    fn empty_pending() -> (LazyTrieData, LazyTrieDataProducer) {
272        LazyTrieData::pending(
273            Arc::new(HashedPostState::default()),
274            Arc::new(TrieUpdates::default()),
275        )
276    }
277
278    #[test]
279    fn test_lazy_clone_shares_state() {
280        let lazy1 = LazyTrieData::ready(SortedTrieData::default());
281        let lazy2 = lazy1.clone();
282
283        // Both point to the same data
284        assert!(Arc::ptr_eq(&lazy1.hashed_state(), &lazy2.hashed_state()));
285        assert!(Arc::ptr_eq(&lazy1.trie_updates(), &lazy2.trie_updates()));
286    }
287
288    #[test]
289    fn test_lazy_deferred() {
290        let lazy = LazyTrieData::deferred(SortedTrieData::default);
291        assert!(lazy.hashed_state().is_empty());
292        assert!(lazy.trie_updates().is_empty());
293    }
294
295    #[test]
296    fn pending_wait_blocks_until_task_publishes() {
297        let (deferred, task) = empty_pending();
298
299        let handle = thread::spawn(move || deferred.get().clone());
300        thread::sleep(Duration::from_millis(20));
301        assert!(!handle.is_finished());
302
303        let published = task.compute_and_publish();
304        let result = handle.join().unwrap();
305
306        assert!(Arc::ptr_eq(&published.hashed_state, &result.hashed_state));
307        assert!(Arc::ptr_eq(&published.trie_updates, &result.trie_updates));
308    }
309
310    #[test]
311    fn concurrent_waits_share_published_result() {
312        let (deferred, task) = empty_pending();
313        let deferred2 = deferred.clone();
314
315        let handle = thread::spawn(move || deferred2.get().clone());
316        let published = task.compute_and_publish();
317        let result1 = deferred.get().clone();
318        let result2 = handle.join().unwrap();
319
320        assert!(Arc::ptr_eq(&published.hashed_state, &result1.hashed_state));
321        assert!(Arc::ptr_eq(&published.trie_updates, &result1.trie_updates));
322        assert!(Arc::ptr_eq(&result1.hashed_state, &result2.hashed_state));
323        assert!(Arc::ptr_eq(&result1.trie_updates, &result2.trie_updates));
324    }
325
326    #[test]
327    fn sorts_non_empty_inputs() {
328        let hashed_address = B256::with_last_byte(1);
329        let hashed_slot = B256::with_last_byte(2);
330        let hashed_state = HashedPostState::default()
331            .with_accounts([(hashed_address, Some(Account::default()))])
332            .with_storages([(
333                hashed_address,
334                HashedStorage::from_iter([(hashed_slot, U256::ONE)]),
335            )]);
336
337        let (deferred, task) =
338            LazyTrieData::pending(Arc::new(hashed_state), Arc::new(TrieUpdates::default()));
339        let _ = task.compute_and_publish();
340        let result = deferred.get().clone();
341
342        assert_eq!(result.hashed_state.total_len(), 2);
343        assert_eq!(result.trie_updates.total_len(), 0);
344    }
345}