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