reth_chain_state/
preserved_sparse_trie.rs1use alloy_primitives::B256;
4use reth_trie_sparse::SparseStateTrie;
5use std::{fmt, sync::mpsc::Receiver};
6use tracing::debug;
7
8pub type SparseTrie = SparseStateTrie;
10
11pub struct PreservedSparseTrie {
13 trie: PreservedSparseTrieInner,
15 block_hash: B256,
20 anchor_hash: B256,
22}
23
24impl fmt::Debug for PreservedSparseTrie {
25 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
26 f.debug_struct("PreservedSparseTrie")
27 .field("block_hash", &self.block_hash)
28 .field("anchor_hash", &self.anchor_hash)
29 .finish_non_exhaustive()
30 }
31}
32
33impl PreservedSparseTrie {
34 pub const fn anchored(trie: SparseTrie, block_hash: B256, anchor_hash: B256) -> Self {
39 Self { trie: PreservedSparseTrieInner::Ready(trie), block_hash, anchor_hash }
40 }
41
42 pub const fn pending(trie: Receiver<SparseTrie>, block_hash: B256, anchor_hash: B256) -> Self {
44 Self { trie: PreservedSparseTrieInner::Pending(trie), block_hash, anchor_hash }
45 }
46
47 pub const fn block_hash(&self) -> B256 {
49 self.block_hash
50 }
51
52 pub const fn anchor_hash(&self) -> B256 {
54 self.anchor_hash
55 }
56
57 pub fn into_trie_for(
62 self,
63 parent_hash: B256,
64 ) -> Result<Option<SparseTrie>, PreservedSparseTrieError> {
65 if self.block_hash == parent_hash {
66 let trie = match self.trie {
67 PreservedSparseTrieInner::Ready(trie) => trie,
68 PreservedSparseTrieInner::Pending(rx) => match rx.recv() {
69 Ok(trie) => trie,
70 Err(_) => {
71 return Err(PreservedSparseTrieError::ProducerDropped {
72 block_hash: self.block_hash,
73 })
74 }
75 },
76 };
77 debug!(
78 target: "engine::tree::payload_processor",
79 block_hash = %self.block_hash,
80 anchor_hash = %self.anchor_hash,
81 "Reusing anchored sparse trie for continuation payload"
82 );
83 Ok(Some(trie))
84 } else {
85 debug!(
86 target: "engine::tree::payload_processor",
87 block_hash = %self.block_hash,
88 anchor_hash = %self.anchor_hash,
89 %parent_hash,
90 "Dropping anchored sparse trie - parent hash mismatch"
91 );
92 Ok(None)
93 }
94 }
95}
96
97#[derive(Debug, Clone, Copy, PartialEq, Eq)]
99pub enum PreservedSparseTrieError {
100 ProducerDropped {
102 block_hash: B256,
104 },
105}
106
107impl fmt::Display for PreservedSparseTrieError {
108 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
109 match self {
110 Self::ProducerDropped { block_hash } => {
111 write!(f, "pending preserved sparse trie producer dropped for {block_hash}")
112 }
113 }
114 }
115}
116
117impl std::error::Error for PreservedSparseTrieError {}
118
119#[allow(clippy::large_enum_variant)]
120enum PreservedSparseTrieInner {
121 Ready(SparseTrie),
122 Pending(Receiver<SparseTrie>),
123}
124
125#[cfg(test)]
126mod tests {
127 use super::*;
128 use std::sync::mpsc;
129
130 #[test]
131 fn pending_trie_exposes_block_hash_before_completion() {
132 let block_hash = B256::with_last_byte(1);
133 let anchor_hash = B256::with_last_byte(2);
134 let (tx, rx) = mpsc::channel();
135 let preserved = PreservedSparseTrie::pending(rx, block_hash, anchor_hash);
136
137 assert_eq!(preserved.block_hash(), block_hash);
138 assert_eq!(preserved.anchor_hash(), anchor_hash);
139 tx.send(SparseTrie::default()).unwrap();
140 assert!(preserved.into_trie_for(block_hash).unwrap().is_some());
141 }
142
143 #[test]
144 fn pending_trie_with_mismatched_parent_does_not_wait() {
145 let block_hash = B256::with_last_byte(1);
146 let other_block_hash = B256::with_last_byte(2);
147 let anchor_hash = B256::with_last_byte(3);
148 let (tx, rx) = mpsc::channel();
149 let preserved = PreservedSparseTrie::pending(rx, block_hash, anchor_hash);
150
151 assert!(preserved.into_trie_for(other_block_hash).unwrap().is_none());
152 assert!(tx.send(SparseTrie::default()).is_err());
153 }
154}