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}