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}