Skip to main content

reth_chain_state/
preserved_sparse_trie.rs

1//! Preserved sparse trie for reuse across payload validations.
2
3use alloy_primitives::{map::B256Map, B256};
4use reth_trie_sparse::SparseStateTrie;
5use std::{fmt, sync::mpsc::Receiver};
6use tracing::debug;
7
8/// A preserved sparse trie that can be reused across payload validations.
9pub struct PreservedSparseTrie {
10    /// The finalized sparse trie and its revealed storage roots, which may still be pending.
11    trie: Receiver<SparseTrieWithStorageRoots>,
12    /// Hash of the block whose post-state this trie represents.
13    ///
14    /// Used to verify continuity: a new payload's parent hash must match this before the existing
15    /// sparse trie nodes can be reused.
16    block_hash: B256,
17    /// Parent block hash of the earliest overlay state covered by this trie.
18    anchor_hash: B256,
19}
20
21impl fmt::Debug for PreservedSparseTrie {
22    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
23        f.debug_struct("PreservedSparseTrie")
24            .field("block_hash", &self.block_hash)
25            .field("anchor_hash", &self.anchor_hash)
26            .finish_non_exhaustive()
27    }
28}
29
30impl PreservedSparseTrie {
31    /// Associates a trie still being finalized with its completed block and pruning anchor.
32    pub const fn pending(
33        trie: Receiver<SparseTrieWithStorageRoots>,
34        block_hash: B256,
35        anchor_hash: B256,
36    ) -> Self {
37        Self { trie, block_hash, anchor_hash }
38    }
39
40    /// Returns the hash of the block whose post-state this trie represents.
41    pub const fn block_hash(&self) -> B256 {
42        self.block_hash
43    }
44
45    /// Returns the parent block hash of the earliest overlay state covered by this trie.
46    pub const fn anchor_hash(&self) -> B256 {
47        self.anchor_hash
48    }
49
50    /// Consumes self and returns the sparse trie and its revealed storage roots if reusable for
51    /// the parent block.
52    ///
53    /// If the parent block hash does not match the preserved trie's block hash, this drops the
54    /// trie and returns `None` so the caller can create a fresh sparse trie.
55    pub fn into_trie_for(
56        self,
57        parent_hash: B256,
58    ) -> Result<Option<SparseTrieWithStorageRoots>, PreservedSparseTrieError> {
59        if self.block_hash == parent_hash {
60            let trie = self.trie.recv().map_err(|_| PreservedSparseTrieError::ProducerDropped {
61                block_hash: self.block_hash,
62            })?;
63            debug!(
64                target: "engine::tree::payload_processor",
65                block_hash = %self.block_hash,
66                anchor_hash = %self.anchor_hash,
67                "Reusing anchored sparse trie for continuation payload"
68            );
69            Ok(Some(trie))
70        } else {
71            debug!(
72                target: "engine::tree::payload_processor",
73                block_hash = %self.block_hash,
74                anchor_hash = %self.anchor_hash,
75                %parent_hash,
76                "Dropping anchored sparse trie - parent hash mismatch"
77            );
78            Ok(None)
79        }
80    }
81}
82
83/// Error returned when consuming a preserved sparse trie.
84#[derive(Debug, Clone, Copy, PartialEq, Eq)]
85pub enum PreservedSparseTrieError {
86    /// The producer of a pending preserved sparse trie dropped before publishing it.
87    ProducerDropped {
88        /// The block hash the pending trie was expected to represent.
89        block_hash: B256,
90    },
91}
92
93impl fmt::Display for PreservedSparseTrieError {
94    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
95        match self {
96            Self::ProducerDropped { block_hash } => {
97                write!(f, "pending preserved sparse trie producer dropped for {block_hash}")
98            }
99        }
100    }
101}
102
103impl std::error::Error for PreservedSparseTrieError {}
104
105/// A finalized sparse trie and its revealed storage roots for the next block's proof workers.
106#[derive(Debug, Default)]
107pub struct SparseTrieWithStorageRoots {
108    /// The sparse trie after finalization and pruning.
109    pub trie: SparseStateTrie,
110    /// Roots of all revealed storage tries before pruning, keyed by hashed account address.
111    pub storage_roots: B256Map<B256>,
112}
113
114#[cfg(test)]
115mod tests {
116    use super::*;
117    use std::sync::mpsc;
118
119    #[test]
120    fn pending_trie_exposes_block_hash_before_completion() {
121        let block_hash = B256::with_last_byte(1);
122        let anchor_hash = B256::with_last_byte(2);
123        let (tx, rx) = mpsc::channel();
124        let preserved = PreservedSparseTrie::pending(rx, block_hash, anchor_hash);
125
126        assert_eq!(preserved.block_hash(), block_hash);
127        assert_eq!(preserved.anchor_hash(), anchor_hash);
128        tx.send(SparseTrieWithStorageRoots::default()).unwrap();
129        assert!(preserved.into_trie_for(block_hash).unwrap().is_some());
130    }
131
132    #[test]
133    fn pending_trie_with_mismatched_parent_does_not_wait() {
134        let block_hash = B256::with_last_byte(1);
135        let other_block_hash = B256::with_last_byte(2);
136        let anchor_hash = B256::with_last_byte(3);
137        let (tx, rx) = mpsc::channel();
138        let preserved = PreservedSparseTrie::pending(rx, block_hash, anchor_hash);
139
140        assert!(preserved.into_trie_for(other_block_hash).unwrap().is_none());
141        assert!(tx.send(SparseTrieWithStorageRoots::default()).is_err());
142    }
143}