1use alloy_primitives::{map::HashMap, B256};
2use reth_db::DatabaseError;
3use reth_trie::{
4 trie_cursor::{TrieCursor, TrieCursorFactory},
5 updates::{StorageTrieUpdates, TrieUpdates},
6 BranchNodeCompact, Nibbles,
7};
8use std::collections::BTreeSet;
9use tracing::warn;
10
11#[derive(Debug)]
12struct EntryDiff<T> {
13 task: T,
14 regular: T,
15 database: T,
16}
17
18#[derive(Debug, Default)]
19struct TrieUpdatesDiff {
20 account_nodes: HashMap<Nibbles, EntryDiff<Option<BranchNodeCompact>>>,
21 removed_nodes: HashMap<Nibbles, EntryDiff<bool>>,
22 storage_tries: HashMap<B256, StorageTrieUpdatesDiff>,
23}
24
25impl TrieUpdatesDiff {
26 fn has_differences(&self) -> bool {
27 !self.account_nodes.is_empty() ||
28 !self.removed_nodes.is_empty() ||
29 !self.storage_tries.is_empty()
30 }
31
32 pub(super) fn log_differences(mut self) {
33 if self.has_differences() {
34 for (path, EntryDiff { task, regular, database }) in &mut self.account_nodes {
35 warn!(target: "engine::tree", ?path, ?task, ?regular, ?database, "Difference in account trie updates");
36 }
37
38 for (
39 path,
40 EntryDiff {
41 task: task_removed,
42 regular: regular_removed,
43 database: database_not_exists,
44 },
45 ) in &self.removed_nodes
46 {
47 warn!(target: "engine::tree", ?path, ?task_removed, ?regular_removed, ?database_not_exists, "Difference in removed account trie nodes");
48 }
49
50 for (address, storage_diff) in self.storage_tries {
51 storage_diff.log_differences(address);
52 }
53 }
54 }
55}
56
57#[derive(Debug, Default)]
58struct StorageTrieUpdatesDiff {
59 is_deleted: Option<EntryDiff<bool>>,
60 storage_nodes: HashMap<Nibbles, EntryDiff<Option<BranchNodeCompact>>>,
61 removed_nodes: HashMap<Nibbles, EntryDiff<bool>>,
62}
63
64impl StorageTrieUpdatesDiff {
65 fn has_differences(&self) -> bool {
66 self.is_deleted.is_some() ||
67 !self.storage_nodes.is_empty() ||
68 !self.removed_nodes.is_empty()
69 }
70
71 fn log_differences(&self, address: B256) {
72 if let Some(EntryDiff {
73 task: task_deleted,
74 regular: regular_deleted,
75 database: database_not_exists,
76 }) = self.is_deleted
77 {
78 warn!(target: "engine::tree", ?address, ?task_deleted, ?regular_deleted, ?database_not_exists, "Difference in storage trie deletion");
79 }
80
81 for (path, EntryDiff { task, regular, database }) in &self.storage_nodes {
82 warn!(target: "engine::tree", ?address, ?path, ?task, ?regular, ?database, "Difference in storage trie updates");
83 }
84
85 for (
86 path,
87 EntryDiff {
88 task: task_removed,
89 regular: regular_removed,
90 database: database_not_exists,
91 },
92 ) in &self.removed_nodes
93 {
94 warn!(target: "engine::tree", ?address, ?path, ?task_removed, ?regular_removed, ?database_not_exists, "Difference in removed storage trie nodes");
95 }
96 }
97}
98
99pub(super) fn compare_trie_updates(
102 trie_cursor_factory: impl TrieCursorFactory,
103 task: TrieUpdates,
104 regular: TrieUpdates,
105) -> Result<(), DatabaseError> {
106 let mut task = adjust_trie_updates(task);
107 let mut regular = adjust_trie_updates(regular);
108
109 let mut diff = TrieUpdatesDiff::default();
110
111 let mut account_trie_cursor = trie_cursor_factory.account_trie_cursor()?;
113 for key in task
114 .account_nodes
115 .keys()
116 .chain(regular.account_nodes.keys())
117 .cloned()
118 .collect::<BTreeSet<_>>()
119 {
120 let (task, regular) = (task.account_nodes.remove(&key), regular.account_nodes.remove(&key));
121 let database = account_trie_cursor.seek_exact(key.clone())?.map(|x| x.1);
122
123 if !branch_nodes_equal(task.as_ref(), regular.as_ref(), database.as_ref())? {
124 diff.account_nodes.insert(key, EntryDiff { task, regular, database });
125 }
126 }
127
128 let mut account_trie_cursor = trie_cursor_factory.account_trie_cursor()?;
130 for key in task
131 .removed_nodes
132 .iter()
133 .chain(regular.removed_nodes.iter())
134 .cloned()
135 .collect::<BTreeSet<_>>()
136 {
137 let (task_removed, regular_removed) =
138 (task.removed_nodes.contains(&key), regular.removed_nodes.contains(&key));
139 let database_not_exists = account_trie_cursor.seek_exact(key.clone())?.is_none();
140 if task_removed != regular_removed && !database_not_exists {
143 diff.removed_nodes.insert(
144 key,
145 EntryDiff {
146 task: task_removed,
147 regular: regular_removed,
148 database: database_not_exists,
149 },
150 );
151 }
152 }
153
154 for key in task
156 .storage_tries
157 .keys()
158 .chain(regular.storage_tries.keys())
159 .copied()
160 .collect::<BTreeSet<_>>()
161 {
162 let (mut task, mut regular) =
163 (task.storage_tries.remove(&key), regular.storage_tries.remove(&key));
164 if task != regular {
165 #[allow(clippy::or_fun_call)]
166 let storage_diff = compare_storage_trie_updates(
167 || trie_cursor_factory.storage_trie_cursor(key),
168 task.as_mut().unwrap_or(&mut Default::default()),
170 regular.as_mut().unwrap_or(&mut Default::default()),
171 )?;
172 if storage_diff.has_differences() {
173 diff.storage_tries.insert(key, storage_diff);
174 }
175 }
176 }
177
178 diff.log_differences();
180
181 Ok(())
182}
183
184fn compare_storage_trie_updates<C: TrieCursor>(
185 trie_cursor: impl Fn() -> Result<C, DatabaseError>,
186 task: &mut StorageTrieUpdates,
187 regular: &mut StorageTrieUpdates,
188) -> Result<StorageTrieUpdatesDiff, DatabaseError> {
189 let database_not_exists = trie_cursor()?.next()?.is_none();
190 let mut diff = StorageTrieUpdatesDiff {
191 is_deleted: (task.is_deleted != regular.is_deleted && !database_not_exists).then_some(
194 EntryDiff {
195 task: task.is_deleted,
196 regular: regular.is_deleted,
197 database: database_not_exists,
198 },
199 ),
200 ..Default::default()
201 };
202
203 let mut storage_trie_cursor = trie_cursor()?;
205 for key in task
206 .storage_nodes
207 .keys()
208 .chain(regular.storage_nodes.keys())
209 .cloned()
210 .collect::<BTreeSet<_>>()
211 {
212 let (task, regular) = (task.storage_nodes.remove(&key), regular.storage_nodes.remove(&key));
213 let database = storage_trie_cursor.seek_exact(key.clone())?.map(|x| x.1);
214 if !branch_nodes_equal(task.as_ref(), regular.as_ref(), database.as_ref())? {
215 diff.storage_nodes.insert(key, EntryDiff { task, regular, database });
216 }
217 }
218
219 let mut storage_trie_cursor = trie_cursor()?;
221 for key in task
222 .removed_nodes
223 .iter()
224 .chain(regular.removed_nodes.iter())
225 .cloned()
226 .collect::<BTreeSet<_>>()
227 {
228 let (task_removed, regular_removed) =
229 (task.removed_nodes.contains(&key), regular.removed_nodes.contains(&key));
230 let database_not_exists =
231 storage_trie_cursor.seek_exact(key.clone())?.map(|x| x.1).is_none();
232 if task_removed != regular_removed && !database_not_exists {
235 diff.removed_nodes.insert(
236 key,
237 EntryDiff {
238 task: task_removed,
239 regular: regular_removed,
240 database: database_not_exists,
241 },
242 );
243 }
244 }
245
246 Ok(diff)
247}
248
249fn adjust_trie_updates(trie_updates: TrieUpdates) -> TrieUpdates {
252 TrieUpdates {
253 removed_nodes: trie_updates
254 .removed_nodes
255 .into_iter()
256 .filter(|key| !trie_updates.account_nodes.contains_key(key))
257 .collect(),
258 storage_tries: trie_updates
259 .storage_tries
260 .into_iter()
261 .map(|(address, updates)| {
262 (
263 address,
264 StorageTrieUpdates {
265 removed_nodes: updates
266 .removed_nodes
267 .into_iter()
268 .filter(|key| !updates.storage_nodes.contains_key(key))
269 .collect(),
270 ..updates
271 },
272 )
273 })
274 .collect(),
275 ..trie_updates
276 }
277}
278
279fn branch_nodes_equal(
286 task: Option<&BranchNodeCompact>,
287 regular: Option<&BranchNodeCompact>,
288 database: Option<&BranchNodeCompact>,
289) -> Result<bool, DatabaseError> {
290 Ok(match (task, regular) {
291 (Some(task), Some(regular)) => {
292 task.state_mask == regular.state_mask &&
293 task.tree_mask == regular.tree_mask &&
294 task.hash_mask == regular.hash_mask &&
295 task.hashes == regular.hashes &&
296 task.root_hash == regular.root_hash
297 }
298 (None, None) => true,
299 _ => {
300 if task.is_some() {
301 task == database
302 } else {
303 regular == database
304 }
305 }
306 })
307}