Skip to main content

reth_engine_tree/tree/payload_processor/
preserved_sparse_trie.rs

1//! Preserved sparse trie for reuse across payload validations.
2
3use alloy_primitives::B256;
4use parking_lot::Mutex;
5use reth_trie_sparse::SparseStateTrie;
6use std::{sync::Arc, time::Instant};
7use tracing::debug;
8
9/// Type alias for the sparse trie type used in preservation.
10pub(super) type SparseTrie = SparseStateTrie;
11
12/// Shared handle to a preserved sparse trie that can be reused across payload validations.
13///
14/// This is stored in [`PayloadProcessor`](super::PayloadProcessor) and cloned to pass to
15/// [`SparseTrieCacheTask`](super::sparse_trie::SparseTrieCacheTask) for trie reuse.
16#[derive(Debug, Default, Clone)]
17pub(super) struct SharedPreservedSparseTrie(Arc<Mutex<Option<PreservedSparseTrie>>>);
18
19impl SharedPreservedSparseTrie {
20    /// Takes the preserved trie if present, leaving `None` in its place.
21    pub(super) fn take(&self) -> Option<PreservedSparseTrie> {
22        self.0.lock().take()
23    }
24
25    /// Acquires a guard that blocks `take()` until dropped.
26    /// Use this before sending the state root result to ensure the next block
27    /// waits for the trie to be stored.
28    pub(super) fn lock(&self) -> PreservedTrieGuard<'_> {
29        PreservedTrieGuard(self.0.lock())
30    }
31
32    /// Waits until the sparse trie lock becomes available.
33    ///
34    /// This acquires and immediately releases the lock, ensuring that any
35    /// ongoing operations complete before returning. Useful for synchronization
36    /// before starting payload processing.
37    ///
38    /// Returns the time spent waiting for the lock.
39    pub(super) fn wait_for_availability(&self) -> std::time::Duration {
40        let start = Instant::now();
41        let _guard = self.0.lock();
42        let elapsed = start.elapsed();
43        if elapsed.as_millis() > 5 {
44            debug!(
45                target: "engine::tree::payload_processor",
46                blocked_for=?elapsed,
47                "Waited for preserved sparse trie to become available"
48            );
49        }
50        elapsed
51    }
52}
53
54/// Guard that holds the lock on the preserved trie.
55/// While held, `take()` will block. Call `store()` to save the trie before dropping.
56pub(super) struct PreservedTrieGuard<'a>(parking_lot::MutexGuard<'a, Option<PreservedSparseTrie>>);
57
58impl PreservedTrieGuard<'_> {
59    /// Stores a preserved trie for later reuse.
60    pub(super) fn store(&mut self, trie: PreservedSparseTrie) {
61        self.0.replace(trie);
62    }
63}
64
65/// A preserved sparse trie that can be reused across payload validations.
66///
67/// The trie exists in one of two states:
68/// - **Anchored**: Has a computed state root and can be reused for payloads whose parent state root
69///   matches the anchor.
70/// - **Cleared**: Trie data has been cleared but allocations are preserved for reuse.
71#[derive(Debug)]
72pub(super) enum PreservedSparseTrie {
73    /// Trie with a computed state root that can be reused for continuation payloads.
74    Anchored {
75        /// The sparse state trie (pruned after root computation).
76        trie: SparseTrie,
77        /// The state root this trie represents (computed from the previous block).
78        /// Used to verify continuity: new payload's `parent_state_root` must match this.
79        state_root: B256,
80    },
81    /// Cleared trie with preserved allocations, ready for fresh use.
82    Cleared {
83        /// The sparse state trie with cleared data but preserved allocations.
84        trie: SparseTrie,
85    },
86}
87
88impl PreservedSparseTrie {
89    /// Creates a new anchored preserved trie.
90    ///
91    /// The `state_root` is the computed state root from the trie, which becomes the
92    /// anchor for determining if subsequent payloads can reuse this trie.
93    pub(super) const fn anchored(trie: SparseTrie, state_root: B256) -> Self {
94        Self::Anchored { trie, state_root }
95    }
96
97    /// Creates a cleared preserved trie (allocations preserved, data cleared).
98    pub(super) const fn cleared(trie: SparseTrie) -> Self {
99        Self::Cleared { trie }
100    }
101
102    /// Consumes self and returns the trie for reuse.
103    ///
104    /// If the preserved trie is anchored and the parent state root matches, the pruned
105    /// trie structure is reused directly. Otherwise, the trie is cleared but allocations
106    /// are preserved to reduce memory overhead.
107    pub(super) fn into_trie_for(self, parent_state_root: B256) -> SparseTrie {
108        match self {
109            Self::Anchored { trie, state_root } if state_root == parent_state_root => {
110                debug!(
111                    target: "engine::tree::payload_processor",
112                    %state_root,
113                    "Reusing anchored sparse trie for continuation payload"
114                );
115                trie
116            }
117            Self::Anchored { mut trie, state_root } => {
118                debug!(
119                    target: "engine::tree::payload_processor",
120                    anchor_root = %state_root,
121                    %parent_state_root,
122                    "Clearing anchored sparse trie - parent state root mismatch"
123                );
124                trie.clear();
125                trie
126            }
127            Self::Cleared { trie } => {
128                debug!(
129                    target: "engine::tree::payload_processor",
130                    %parent_state_root,
131                    "Using cleared sparse trie with preserved allocations"
132                );
133                trie
134            }
135        }
136    }
137}