reth_chain_state/preserved_sparse_trie.rs
1//! Preserved sparse trie for reuse across payload validations.
2
3use alloy_primitives::B256;
4use reth_trie_sparse::SparseStateTrie;
5use tracing::debug;
6
7/// Type alias for the sparse trie type used in preservation.
8pub type SparseTrie = SparseStateTrie;
9
10/// Guard that holds the lock on the preserved trie.
11/// While held, the next trie take will block. Call `store()` to save the trie before dropping.
12#[derive(Debug)]
13pub struct PreservedTrieGuard<'a>(parking_lot::MutexGuard<'a, Option<PreservedSparseTrie>>);
14
15impl<'a> PreservedTrieGuard<'a> {
16 /// Creates a new guard from the preserved trie lock.
17 pub(crate) const fn new(
18 guard: parking_lot::MutexGuard<'a, Option<PreservedSparseTrie>>,
19 ) -> Self {
20 PreservedTrieGuard(guard)
21 }
22
23 /// Stores a preserved trie for later reuse.
24 pub fn store(&mut self, trie: PreservedSparseTrie) {
25 self.0.replace(trie);
26 }
27}
28
29/// A preserved sparse trie that can be reused across payload validations.
30///
31/// The trie exists in one of two states:
32/// - **Anchored**: Has a computed state root and can be reused for payloads whose parent state root
33/// matches the anchor.
34/// - **Cleared**: Trie data has been cleared but allocations are preserved for reuse.
35#[derive(Debug)]
36pub enum PreservedSparseTrie {
37 /// Trie with a computed state root that can be reused for continuation payloads.
38 Anchored {
39 /// The sparse state trie anchored to the computed state root.
40 trie: SparseTrie,
41 /// The state root this trie represents (computed from the previous block).
42 /// Used to verify continuity: new payload's `parent_state_root` must match this.
43 state_root: B256,
44 },
45 /// Cleared trie with preserved allocations, ready for fresh use.
46 Cleared {
47 /// The sparse state trie with cleared data but preserved allocations.
48 trie: SparseTrie,
49 },
50}
51
52impl PreservedSparseTrie {
53 /// Creates a new anchored preserved trie.
54 ///
55 /// The `state_root` is the computed state root from the trie, which becomes the
56 /// anchor for determining if subsequent payloads can reuse this trie.
57 pub const fn anchored(trie: SparseTrie, state_root: B256) -> Self {
58 Self::Anchored { trie, state_root }
59 }
60
61 /// Creates a cleared preserved trie (allocations preserved, data cleared).
62 pub const fn cleared(trie: SparseTrie) -> Self {
63 Self::Cleared { trie }
64 }
65
66 /// Consumes self and returns the trie for reuse.
67 ///
68 /// If the preserved trie is anchored and the parent state root matches, the preserved
69 /// trie structure is reused directly. Otherwise, the trie is cleared but allocations
70 /// are preserved to reduce memory overhead.
71 pub fn into_trie_for(self, parent_state_root: B256) -> SparseTrie {
72 match self {
73 Self::Anchored { trie, state_root } if state_root == parent_state_root => {
74 debug!(
75 target: "engine::tree::payload_processor",
76 %state_root,
77 "Reusing anchored sparse trie for continuation payload"
78 );
79 trie
80 }
81 Self::Anchored { mut trie, state_root } => {
82 debug!(
83 target: "engine::tree::payload_processor",
84 anchor_root = %state_root,
85 %parent_state_root,
86 "Clearing anchored sparse trie - parent state root mismatch"
87 );
88 trie.clear();
89 trie
90 }
91 Self::Cleared { trie } => {
92 debug!(
93 target: "engine::tree::payload_processor",
94 %parent_state_root,
95 "Using cleared sparse trie with preserved allocations"
96 );
97 trie
98 }
99 }
100 }
101}