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    prefix_set::TriePrefixSetsMut,
8    updates::{TrieUpdates, TrieUpdatesSorted},
9    HashedPostState, HashedPostStateSorted,
10};
11use alloc::sync::Arc;
12use core::fmt;
13use reth_primitives_traits::sync::OnceLock;
14
15/// Container for sorted trie data: hashed state and trie updates.
16///
17/// This bundles both [`HashedPostStateSorted`] and [`TrieUpdatesSorted`] together
18/// for convenient passing and storage.
19#[derive(Clone, Debug, Default, PartialEq, Eq)]
20#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
21pub struct SortedTrieData {
22    /// Sorted hashed post-state produced by execution.
23    pub hashed_state: Arc<HashedPostStateSorted>,
24    /// Sorted trie updates produced by state root computation.
25    pub trie_updates: Arc<TrieUpdatesSorted>,
26}
27
28impl SortedTrieData {
29    /// Creates a new [`SortedTrieData`] with the given values.
30    pub const fn new(
31        hashed_state: Arc<HashedPostStateSorted>,
32        trie_updates: Arc<TrieUpdatesSorted>,
33    ) -> Self {
34        Self { hashed_state, trie_updates }
35    }
36}
37
38/// Container for sorted trie data that also includes `changed_paths`.
39#[derive(Clone, Debug, Default, PartialEq, Eq)]
40pub struct ComputedTrieData {
41    /// Sorted trie data: hashed state and trie updates.
42    pub sorted: SortedTrieData,
43    /// Changed trie node base paths produced by state root computation.
44    pub changed_paths: Option<Arc<TriePrefixSetsMut>>,
45}
46
47impl ComputedTrieData {
48    /// Construct sorted trie data for one block.
49    pub const fn new(
50        hashed_state: Arc<HashedPostStateSorted>,
51        trie_updates: Arc<TrieUpdatesSorted>,
52    ) -> Self {
53        Self::new_with_changed_paths(hashed_state, trie_updates, None)
54    }
55
56    /// Construct sorted trie data with changed trie node base paths for one block.
57    pub const fn new_with_changed_paths(
58        hashed_state: Arc<HashedPostStateSorted>,
59        trie_updates: Arc<TrieUpdatesSorted>,
60        changed_paths: Option<Arc<TriePrefixSetsMut>>,
61    ) -> Self {
62        Self { sorted: SortedTrieData::new(hashed_state, trie_updates), changed_paths }
63    }
64}
65
66/// Lazily initialized trie data containing sorted hashed state and trie updates.
67///
68/// This is a no-std compatible wrapper that supports three modes:
69/// 1. **Ready mode**: Data is available immediately (created via `ready()`)
70/// 2. **Deferred mode**: Data is computed on first access (created via `deferred()`)
71/// 3. **Pending mode**: Data is computed in background task, callers wait for that result (created
72///    via `pending()`).
73///
74/// In deferred mode, the computation runs on the first call to `get()`, `hashed_state()`,
75/// or `trie_updates()`, and results are cached for subsequent calls.
76///
77/// Cloning is cheap (Arc clone) and clones share the cached state.
78pub struct LazyTrieData {
79    /// Cached sorted trie data, computed on first access.
80    data: Arc<OnceLock<ComputedTrieData>>,
81    // /// Optional deferred computation function.
82    // compute: Option<Arc<dyn Fn() -> SortedTrieData + Send + Sync>>,
83    /// Lazy mode.
84    mode: LazyTrieDataMode,
85}
86
87impl Clone for LazyTrieData {
88    fn clone(&self) -> Self {
89        Self { data: Arc::clone(&self.data), mode: self.mode.clone() }
90    }
91}
92
93impl fmt::Debug for LazyTrieData {
94    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
95        f.debug_struct("LazyTrieData")
96            .field("data", &if self.data.get().is_some() { "initialized" } else { "pending" })
97            .finish()
98    }
99}
100
101impl PartialEq for LazyTrieData {
102    fn eq(&self, other: &Self) -> bool {
103        self.get() == other.get()
104    }
105}
106
107impl Eq for LazyTrieData {}
108
109impl LazyTrieData {
110    /// Creates a new [`LazyTrieData`] that is already initialized with the given values.
111    pub fn ready(sorted: ComputedTrieData) -> Self {
112        Self { data: Arc::new(OnceLock::from(sorted)), mode: LazyTrieDataMode::Ready }
113    }
114
115    /// Creates a new [`LazyTrieData`] with a deferred computation function.
116    ///
117    /// The computation will run on the first call to `get()`, `hashed_state()`,
118    /// or `trie_updates()`. Results are cached for subsequent calls.
119    pub fn deferred(compute: impl Fn() -> ComputedTrieData + Send + Sync + 'static) -> Self {
120        Self {
121            data: Arc::new(OnceLock::new()),
122            mode: LazyTrieDataMode::Deferred(Arc::new(compute)),
123        }
124    }
125
126    /// Creates a new [`LazyTrieData`] with a spawned task to compute sorted trie data.
127    #[cfg(feature = "std")]
128    pub fn pending(
129        hashed_state: Arc<HashedPostState>,
130        trie_updates: Arc<TrieUpdates>,
131        changed_paths: Option<Arc<TriePrefixSetsMut>>,
132    ) -> (Self, LazyTrieDataProducer) {
133        let value = Arc::new(OnceLock::new());
134        (
135            Self { data: Arc::clone(&value), mode: LazyTrieDataMode::Pending },
136            LazyTrieDataProducer {
137                value,
138                inputs: PendingInputs { hashed_state, trie_updates, changed_paths },
139            },
140        )
141    }
142
143    /// Returns a reference to the sorted trie data, computing or waiting for result if necessary.
144    ///
145    /// # Panics
146    ///
147    /// Panics if in ready state, but value has not been initialized.
148    pub fn get(&self) -> &ComputedTrieData {
149        match &self.mode {
150            LazyTrieDataMode::Ready => self.data.get().expect("LazyTrieData must be initialized"),
151            LazyTrieDataMode::Deferred(compute) => self.data.get_or_init(|| compute.as_ref()()),
152            #[cfg(feature = "std")]
153            LazyTrieDataMode::Pending => self.data.wait(),
154        }
155    }
156
157    /// Returns a clone of the hashed state Arc.
158    ///
159    /// If not initialized, computes from the deferred source or panics.
160    pub fn hashed_state(&self) -> Arc<HashedPostStateSorted> {
161        Arc::clone(&self.get().sorted.hashed_state)
162    }
163
164    /// Returns a clone of the trie updates Arc.
165    ///
166    /// If not initialized, computes from the deferred source or panics.
167    pub fn trie_updates(&self) -> Arc<TrieUpdatesSorted> {
168        Arc::clone(&self.get().sorted.trie_updates)
169    }
170
171    /// Returns a clone of the [`SortedTrieData`].
172    ///
173    /// If not initialized, computes from the deferred source or panics.
174    pub fn sorted_trie_data(&self) -> SortedTrieData {
175        self.get().sorted.clone()
176    }
177}
178
179#[cfg(feature = "serde")]
180impl serde::Serialize for LazyTrieData {
181    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
182    where
183        S: serde::Serializer,
184    {
185        self.get().sorted.serialize(serializer)
186    }
187}
188
189#[cfg(feature = "serde")]
190impl<'de> serde::Deserialize<'de> for LazyTrieData {
191    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
192    where
193        D: serde::Deserializer<'de>,
194    {
195        let data = SortedTrieData::deserialize(deserializer)?;
196        Ok(Self::ready(ComputedTrieData::new(data.hashed_state, data.trie_updates)))
197    }
198}
199
200#[derive(Clone)]
201enum LazyTrieDataMode {
202    Ready,
203    Deferred(Arc<dyn Fn() -> ComputedTrieData + Send + Sync>),
204    #[cfg(feature = "std")]
205    Pending,
206}
207
208impl fmt::Debug for LazyTrieDataMode {
209    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
210        match self {
211            Self::Ready => write!(f, "Ready"),
212            Self::Deferred(_) => write!(f, "Deferred(..)"),
213            #[cfg(feature = "std")]
214            Self::Pending => write!(f, "Pending"),
215        }
216    }
217}
218
219/// Producer consumed by a spawned task to compute sorted trie data for a [`LazyTrieData`] handle.
220#[must_use = "LazyTrieDataProducer must be consumed with compute_and_publish to wake trie data waiters"]
221pub struct LazyTrieDataProducer {
222    /// Shared result initialized exactly once by this producer.
223    value: Arc<OnceLock<ComputedTrieData>>,
224    /// Unsorted inputs consumed when the producer computes trie data.
225    inputs: PendingInputs,
226}
227
228impl fmt::Debug for LazyTrieDataProducer {
229    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
230        f.debug_struct("LazyTrieDataProducer").field("inputs", &self.inputs).finish_non_exhaustive()
231    }
232}
233
234impl LazyTrieDataProducer {
235    /// Computes sorted trie data, publishes it to waiters, and returns it to the task owner.
236    pub fn compute_and_publish(self) -> ComputedTrieData {
237        let Self { value, inputs } = self;
238        let computed = Self::sort(inputs.hashed_state, inputs.trie_updates, inputs.changed_paths);
239        let _ = value.set(computed.clone());
240        computed
241    }
242
243    /// Sorts block execution outputs.
244    pub fn sort(
245        hashed_state: Arc<HashedPostState>,
246        trie_updates: Arc<TrieUpdates>,
247        changed_paths: Option<Arc<TriePrefixSetsMut>>,
248    ) -> ComputedTrieData {
249        #[cfg(feature = "rayon")]
250        let (sorted_hashed_state, sorted_trie_updates) = rayon::join(
251            || match Arc::try_unwrap(hashed_state) {
252                Ok(state) => state.into_sorted(),
253                Err(arc) => arc.clone_into_sorted(),
254            },
255            || match Arc::try_unwrap(trie_updates) {
256                Ok(updates) => updates.into_sorted(),
257                Err(arc) => arc.clone_into_sorted(),
258            },
259        );
260
261        #[cfg(not(feature = "rayon"))]
262        let (sorted_hashed_state, sorted_trie_updates) = (
263            match Arc::try_unwrap(hashed_state) {
264                Ok(state) => state.into_sorted(),
265                Err(arc) => arc.clone_into_sorted(),
266            },
267            match Arc::try_unwrap(trie_updates) {
268                Ok(updates) => updates.into_sorted(),
269                Err(arc) => arc.clone_into_sorted(),
270            },
271        );
272
273        ComputedTrieData::new_with_changed_paths(
274            Arc::new(sorted_hashed_state),
275            Arc::new(sorted_trie_updates),
276            changed_paths,
277        )
278    }
279}
280
281/// Inputs kept while a deferred trie computation is pending.
282#[derive(Clone, Debug)]
283struct PendingInputs {
284    /// Unsorted hashed post-state from execution.
285    hashed_state: Arc<HashedPostState>,
286    /// Unsorted trie updates from state root computation.
287    trie_updates: Arc<TrieUpdates>,
288    /// Changed trie node base paths from state root computation.
289    changed_paths: Option<Arc<TriePrefixSetsMut>>,
290}
291
292#[cfg(test)]
293mod tests {
294    use crate::HashedStorage;
295
296    use super::*;
297    use alloy_primitives::{B256, U256};
298    use reth_primitives_traits::Account;
299    use revm::primitives::B256Map;
300    use std::{
301        thread,
302        time::{Duration, Instant},
303    };
304
305    fn empty_pending() -> (LazyTrieData, LazyTrieDataProducer) {
306        LazyTrieData::pending(
307            Arc::new(HashedPostState::default()),
308            Arc::new(TrieUpdates::default()),
309            None,
310        )
311    }
312
313    fn assert_changed_paths_ptr_eq(
314        left: &Option<Arc<TriePrefixSetsMut>>,
315        right: &Option<Arc<TriePrefixSetsMut>>,
316    ) {
317        match (left, right) {
318            (Some(left), Some(right)) => assert!(Arc::ptr_eq(left, right)),
319            (None, None) => {}
320            _ => panic!("changed paths presence mismatch"),
321        }
322    }
323
324    #[test]
325    fn test_lazy_ready_is_initialized() {
326        let lazy = LazyTrieData::ready(ComputedTrieData::default());
327        let _ = lazy.hashed_state();
328        let _ = lazy.trie_updates();
329    }
330
331    #[test]
332    fn test_lazy_clone_shares_state() {
333        let lazy1 = LazyTrieData::ready(ComputedTrieData::default());
334        let lazy2 = lazy1.clone();
335
336        // Both point to the same data
337        assert!(Arc::ptr_eq(&lazy1.hashed_state(), &lazy2.hashed_state()));
338        assert!(Arc::ptr_eq(&lazy1.trie_updates(), &lazy2.trie_updates()));
339    }
340
341    #[test]
342    fn test_lazy_deferred() {
343        let lazy = LazyTrieData::deferred(ComputedTrieData::default);
344        assert!(lazy.hashed_state().is_empty());
345        assert!(lazy.trie_updates().is_empty());
346    }
347
348    #[test]
349    fn ready_returns_immediately() {
350        let bundle = ComputedTrieData::default();
351        let deferred = LazyTrieData::ready(bundle.clone());
352
353        let result = deferred.get();
354
355        assert_eq!(result.sorted.hashed_state.total_len(), bundle.sorted.hashed_state.total_len());
356        assert_eq!(result.sorted.trie_updates.total_len(), bundle.sorted.trie_updates.total_len());
357    }
358
359    #[test]
360    fn pending_waits_for_task_and_caches_result() {
361        let (deferred, task) = empty_pending();
362
363        let published = task.compute_and_publish();
364        let first = deferred.get();
365        let second = deferred.get();
366
367        assert!(Arc::ptr_eq(&published.sorted.hashed_state, &first.sorted.hashed_state));
368        assert!(Arc::ptr_eq(&published.sorted.trie_updates, &first.sorted.trie_updates));
369        assert_changed_paths_ptr_eq(&published.changed_paths, &first.changed_paths);
370        assert!(Arc::ptr_eq(&first.sorted.hashed_state, &second.sorted.hashed_state));
371        assert!(Arc::ptr_eq(&first.sorted.trie_updates, &second.sorted.trie_updates));
372        assert_changed_paths_ptr_eq(&first.changed_paths, &second.changed_paths);
373    }
374
375    #[test]
376    fn pending_wait_blocks_until_task_publishes() {
377        let (deferred, task) = empty_pending();
378
379        let handle = thread::spawn(move || deferred.get().clone());
380        thread::sleep(Duration::from_millis(20));
381        assert!(!handle.is_finished());
382
383        let published = task.compute_and_publish();
384        let result = handle.join().unwrap();
385
386        assert!(Arc::ptr_eq(&published.sorted.hashed_state, &result.sorted.hashed_state));
387        assert!(Arc::ptr_eq(&published.sorted.trie_updates, &result.sorted.trie_updates));
388        assert_changed_paths_ptr_eq(&published.changed_paths, &result.changed_paths);
389    }
390
391    #[test]
392    fn concurrent_waits_share_published_result() {
393        let (deferred, task) = empty_pending();
394        let deferred2 = deferred.clone();
395
396        let handle = thread::spawn(move || deferred2.get().clone());
397        let published = task.compute_and_publish();
398        let result1 = deferred.get().clone();
399        let result2 = handle.join().unwrap();
400
401        assert!(Arc::ptr_eq(&published.sorted.hashed_state, &result1.sorted.hashed_state));
402        assert!(Arc::ptr_eq(&published.sorted.trie_updates, &result1.sorted.trie_updates));
403        assert_changed_paths_ptr_eq(&published.changed_paths, &result1.changed_paths);
404        assert!(Arc::ptr_eq(&result1.sorted.hashed_state, &result2.sorted.hashed_state));
405        assert!(Arc::ptr_eq(&result1.sorted.trie_updates, &result2.sorted.trie_updates));
406        assert_changed_paths_ptr_eq(&result1.changed_paths, &result2.changed_paths);
407    }
408
409    #[test]
410    fn sorts_non_empty_inputs() {
411        let hashed_address = B256::with_last_byte(1);
412        let hashed_slot = B256::with_last_byte(2);
413        let hashed_state = HashedPostState::default()
414            .with_accounts([(hashed_address, Some(Account::default()))])
415            .with_storages([(
416                hashed_address,
417                HashedStorage::from_iter(false, [(hashed_slot, U256::from(1))]),
418            )]);
419
420        let (deferred, task) =
421            LazyTrieData::pending(Arc::new(hashed_state), Arc::new(TrieUpdates::default()), None);
422        let _ = task.compute_and_publish();
423        let result = deferred.get().clone();
424
425        assert_eq!(result.sorted.hashed_state.total_len(), 2);
426        assert_eq!(result.sorted.trie_updates.total_len(), 0);
427    }
428
429    #[test]
430    fn wait_does_not_block_after_first_compute() {
431        let mut accounts = B256Map::default();
432        for i in 0..100 {
433            accounts.insert(B256::with_last_byte(i), Some(Account::default()));
434        }
435        let (deferred, task) = LazyTrieData::pending(
436            Arc::new(HashedPostState { accounts, storages: Default::default() }),
437            Arc::new(TrieUpdates::default()),
438            None,
439        );
440
441        let _ = task.compute_and_publish();
442        let _ = deferred.get().clone();
443        let start = Instant::now();
444        let _ = deferred.get().clone();
445
446        assert!(start.elapsed() < Duration::from_millis(10));
447    }
448}