Skip to main content

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 std::{
6    fmt,
7    sync::mpsc::{self, Receiver, Sender},
8};
9use tracing::debug;
10
11/// Type alias for the sparse trie type used in preservation.
12pub type SparseTrie = SparseStateTrie;
13
14/// A preserved sparse trie that can be reused across payload validations.
15pub struct PreservedSparseTrie {
16    /// The preserved sparse state trie, or a handle to wait for it.
17    trie: PreservedSparseTrieInner,
18    /// The state root this trie represents.
19    ///
20    /// Used to verify continuity: a new payload's `parent_state_root` must match this before the
21    /// existing sparse trie nodes can be reused.
22    state_root: B256,
23    /// Parent block hash of the earliest overlay state covered by this trie.
24    anchor_hash: B256,
25}
26
27impl fmt::Debug for PreservedSparseTrie {
28    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
29        f.debug_struct("PreservedSparseTrie")
30            .field("state_root", &self.state_root)
31            .field("anchor_hash", &self.anchor_hash)
32            .finish_non_exhaustive()
33    }
34}
35
36impl PreservedSparseTrie {
37    /// Creates a new anchored preserved trie.
38    ///
39    /// The `state_root` is the computed state root from the trie. The `anchor_hash` is the parent
40    /// block hash of the earliest overlay state covered by the trie.
41    pub const fn anchored(trie: SparseTrie, state_root: B256, anchor_hash: B256) -> Self {
42        Self { trie: PreservedSparseTrieInner::Ready(trie), state_root, anchor_hash }
43    }
44
45    /// Creates a pending preserved trie and a completer that will publish the trie later.
46    pub fn pending(state_root: B256, anchor_hash: B256) -> (Self, PreservedSparseTrieCompleter) {
47        let (tx, rx) = mpsc::channel();
48        (
49            Self { trie: PreservedSparseTrieInner::Pending(rx), state_root, anchor_hash },
50            PreservedSparseTrieCompleter { tx },
51        )
52    }
53
54    /// Returns the state root this trie is anchored to.
55    pub const fn state_root(&self) -> B256 {
56        self.state_root
57    }
58
59    /// Returns the parent block hash of the earliest overlay state covered by this trie.
60    pub const fn anchor_hash(&self) -> B256 {
61        self.anchor_hash
62    }
63
64    /// Consumes self and returns the trie if it can be reused for the parent state root.
65    ///
66    /// If the parent state root does not match the preserved trie's state root, this drops the trie
67    /// and returns `None` so the caller can create a fresh sparse trie.
68    pub fn into_trie_for(
69        self,
70        parent_state_root: B256,
71    ) -> Result<Option<SparseTrie>, PreservedSparseTrieError> {
72        if self.state_root == parent_state_root {
73            let trie = match self.trie {
74                PreservedSparseTrieInner::Ready(trie) => trie,
75                PreservedSparseTrieInner::Pending(rx) => match rx.recv() {
76                    Ok(trie) => trie,
77                    Err(_) => {
78                        return Err(PreservedSparseTrieError::ProducerDropped {
79                            state_root: self.state_root,
80                        })
81                    }
82                },
83            };
84            debug!(
85                target: "engine::tree::payload_processor",
86                state_root = %self.state_root,
87                anchor_hash = %self.anchor_hash,
88                "Reusing anchored sparse trie for continuation payload"
89            );
90            Ok(Some(trie))
91        } else {
92            debug!(
93                target: "engine::tree::payload_processor",
94                anchor_root = %self.state_root,
95                anchor_hash = %self.anchor_hash,
96                %parent_state_root,
97                "Dropping anchored sparse trie - parent state root mismatch"
98            );
99            Ok(None)
100        }
101    }
102}
103
104/// Error returned when consuming a preserved sparse trie.
105#[derive(Debug, Clone, Copy, PartialEq, Eq)]
106pub enum PreservedSparseTrieError {
107    /// The producer of a pending preserved sparse trie dropped before publishing it.
108    ProducerDropped {
109        /// The state root the pending trie was expected to represent.
110        state_root: B256,
111    },
112}
113
114impl fmt::Display for PreservedSparseTrieError {
115    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
116        match self {
117            Self::ProducerDropped { state_root } => {
118                write!(f, "pending preserved sparse trie producer dropped for {state_root}")
119            }
120        }
121    }
122}
123
124impl std::error::Error for PreservedSparseTrieError {}
125
126#[allow(clippy::large_enum_variant)]
127enum PreservedSparseTrieInner {
128    Ready(SparseTrie),
129    Pending(Receiver<SparseTrie>),
130}
131
132/// Completes a pending preserved sparse trie.
133#[derive(Debug)]
134pub struct PreservedSparseTrieCompleter {
135    tx: Sender<SparseTrie>,
136}
137
138impl PreservedSparseTrieCompleter {
139    /// Publishes the trie for a pending preserved sparse trie.
140    pub fn complete(self, trie: SparseTrie) -> Result<(), SparseTrie> {
141        self.tx.send(trie).map_err(|err| err.0)
142    }
143}
144
145#[cfg(test)]
146mod tests {
147    use super::*;
148
149    #[test]
150    fn pending_trie_exposes_state_root_before_completion() {
151        let state_root = B256::with_last_byte(1);
152        let anchor_hash = B256::with_last_byte(2);
153        let (preserved, completer) = PreservedSparseTrie::pending(state_root, anchor_hash);
154
155        assert_eq!(preserved.state_root(), state_root);
156        assert_eq!(preserved.anchor_hash(), anchor_hash);
157        completer.complete(SparseTrie::default()).unwrap();
158        assert!(preserved.into_trie_for(state_root).unwrap().is_some());
159    }
160
161    #[test]
162    fn pending_trie_with_mismatched_root_does_not_wait() {
163        let state_root = B256::with_last_byte(1);
164        let other_state_root = B256::with_last_byte(2);
165        let anchor_hash = B256::with_last_byte(3);
166        let (preserved, completer) = PreservedSparseTrie::pending(state_root, anchor_hash);
167
168        assert!(preserved.into_trie_for(other_state_root).unwrap().is_none());
169        assert!(completer.complete(SparseTrie::default()).is_err());
170    }
171}