Skip to content

Commit 05211ba

Browse files
iovoidjrchatruc
andauthored
perf(l1): avoid validating node hash (#5167)
**Motivation** Currently when fetching a node during tree traversal, we compute it's hash to validate it. This isn't necessary when the trie is consistent. **Description** This adds a checked variant for use in snap sync (which requires reading from inconsistent tries) and removes hash validation from the others. --------- Co-authored-by: Javier Chatruc <[email protected]>
1 parent 9e37483 commit 05211ba

File tree

6 files changed

+54
-23
lines changed

6 files changed

+54
-23
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
## Perf
44

55
### 2025-11-03
6+
7+
- Avoid unnecessary hash validations [#5167](https://github.com/lambdaclass/ethrex/pull/5167)
68
- Merge execution with some post-execution validations [#5170](https://github.com/lambdaclass/ethrex/pull/5170)
79

810
### 2025-10-31

crates/common/trie/node.rs

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,29 @@ pub enum NodeRef {
2424

2525
impl NodeRef {
2626
/// Gets a shared reference to the inner node.
27+
/// Requires that the trie is in a consistent state, ie that all leaves being pointed are in the database.
28+
/// Outside of snapsync this should always be the case.
2729
pub fn get_node(&self, db: &dyn TrieDB, path: Nibbles) -> Result<Option<Arc<Node>>, TrieError> {
30+
match self {
31+
NodeRef::Node(node, _) => Ok(Some(node.clone())),
32+
NodeRef::Hash(hash @ NodeHash::Inline(_)) => {
33+
Ok(Some(Arc::new(Node::decode(hash.as_ref())?)))
34+
}
35+
NodeRef::Hash(_) => db
36+
.get(path)?
37+
.filter(|rlp| !rlp.is_empty())
38+
.map(|rlp| Ok(Arc::new(Node::decode(&rlp)?)))
39+
.transpose(),
40+
}
41+
}
42+
43+
/// Gets a shared reference to the inner node, checking it's hash.
44+
/// Returns `Ok(None)` if the hash is invalid.
45+
pub fn get_node_checked(
46+
&self,
47+
db: &dyn TrieDB,
48+
path: Nibbles,
49+
) -> Result<Option<Arc<Node>>, TrieError> {
2850
match self {
2951
NodeRef::Node(node, _) => Ok(Some(node.clone())),
3052
NodeRef::Hash(hash @ NodeHash::Inline(_)) => {
@@ -63,10 +85,7 @@ impl NodeRef {
6385
let Some(node) = db
6486
.get(path.clone())?
6587
.filter(|rlp| !rlp.is_empty())
66-
.and_then(|rlp| match Node::decode(&rlp) {
67-
Ok(node) => (node.compute_hash() == *hash).then_some(Ok(node)),
68-
Err(err) => Some(Err(TrieError::RLPDecode(err))),
69-
})
88+
.map(|rlp| Node::decode(&rlp).map_err(TrieError::RLPDecode))
7089
.transpose()?
7190
else {
7291
return Ok(None);

crates/common/trie/trie.rs

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -194,11 +194,13 @@ impl Trie {
194194
}
195195

196196
pub fn get_root_node(&self, path: Nibbles) -> Result<Arc<Node>, TrieError> {
197-
self.root.get_node(self.db.as_ref(), path)?.ok_or_else(|| {
198-
TrieError::InconsistentTree(Box::new(InconsistentTreeError::RootNotFound(
199-
self.root.compute_hash().finalize(),
200-
)))
201-
})
197+
self.root
198+
.get_node_checked(self.db.as_ref(), path)?
199+
.ok_or_else(|| {
200+
TrieError::InconsistentTree(Box::new(InconsistentTreeError::RootNotFound(
201+
self.root.compute_hash().finalize(),
202+
)))
203+
})
202204
}
203205

204206
/// Returns a list of changes in a TrieNode format since last root hash processed.
@@ -254,7 +256,10 @@ impl Trie {
254256
node_path.push(data[..len as usize].to_vec());
255257
}
256258

257-
let root = match self.root.get_node(self.db.as_ref(), Nibbles::default())? {
259+
let root = match self
260+
.root
261+
.get_node_checked(self.db.as_ref(), Nibbles::default())?
262+
{
258263
Some(x) => x,
259264
None => return Ok(Vec::new()),
260265
};
@@ -431,8 +436,9 @@ impl Trie {
431436
let child_ref = &branch_node.choices[idx];
432437
if child_ref.is_valid() {
433438
let child_path = current_path.append_new(idx as u8);
434-
let child_node =
435-
child_ref.get_node(db, child_path.clone())?.ok_or_else(|| {
439+
let child_node = child_ref
440+
.get_node_checked(db, child_path.clone())?
441+
.ok_or_else(|| {
436442
TrieError::InconsistentTree(Box::new(
437443
InconsistentTreeError::NodeNotFoundOnBranchNode(
438444
child_ref.compute_hash().finalize(),
@@ -455,7 +461,7 @@ impl Trie {
455461
let child_path = partial_path.concat(&extension_node.prefix);
456462
let child_node = extension_node
457463
.child
458-
.get_node(db, child_path.clone())?
464+
.get_node_checked(db, child_path.clone())?
459465
.ok_or_else(|| {
460466
TrieError::InconsistentTree(Box::new(
461467
InconsistentTreeError::ExtensionNodeChildNotFound(
@@ -500,7 +506,8 @@ impl Trie {
500506
if self.hash_no_commit() == *EMPTY_TRIE_HASH {
501507
return Ok(None);
502508
}
503-
self.root.get_node(self.db.as_ref(), Nibbles::default())
509+
self.root
510+
.get_node_checked(self.db.as_ref(), Nibbles::default())
504511
}
505512

506513
/// Creates a new Trie based on a temporary InMemory DB

crates/common/trie/trie_iter.rs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::cmp::Ordering;
1+
use std::{cmp::Ordering, sync::Arc};
22

33
use crate::{
44
PathRLP, Trie, TrieDB, TrieError, ValueRLP,
@@ -39,14 +39,17 @@ impl TrieIterator {
3939
db: &dyn TrieDB,
4040
prefix_nibbles: Nibbles,
4141
mut target_nibbles: Nibbles,
42-
mut node: NodeRef,
42+
node: NodeRef,
4343
new_stack: &mut Vec<(Nibbles, NodeRef)>,
4444
) -> Result<(), TrieError> {
45-
let Some(next_node) = node.get_node_mut(db, prefix_nibbles.clone()).ok().flatten()
45+
let Some(mut next_node) = node
46+
.get_node_checked(db, prefix_nibbles.clone())
47+
.ok()
48+
.flatten()
4649
else {
4750
return Ok(());
4851
};
49-
match &next_node {
52+
match Arc::make_mut(&mut next_node) {
5053
Node::Branch(branch_node) => {
5154
// Add all children to the stack (in reverse order so we process first child frist)
5255
let Some(choice) = target_nibbles.next_choice() else {
@@ -141,7 +144,7 @@ impl Iterator for TrieIterator {
141144
// Fetch the last node in the stack
142145
let (mut path, next_node_ref) = self.stack.pop()?;
143146
let next_node = next_node_ref
144-
.get_node(self.db.as_ref(), path.clone())
147+
.get_node_checked(self.db.as_ref(), path.clone())
145148
.ok()
146149
.flatten()?;
147150
match &(*next_node) {

crates/networking/p2p/sync/state_healing.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -414,7 +414,7 @@ pub fn node_missing_children(
414414
continue;
415415
}
416416
let validity = child
417-
.get_node(trie_state, child_path.clone())
417+
.get_node_checked(trie_state, child_path.clone())
418418
.inspect_err(|_| {
419419
debug!("Malformed data when doing get child of a branch node")
420420
})?
@@ -438,7 +438,7 @@ pub fn node_missing_children(
438438
}
439439
let validity = node
440440
.child
441-
.get_node(trie_state, child_path.clone())
441+
.get_node_checked(trie_state, child_path.clone())
442442
.inspect_err(|_| debug!("Malformed data when doing get child of a branch node"))?
443443
.is_some();
444444
if validity {

crates/networking/p2p/sync/storage_healing.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -617,7 +617,7 @@ pub fn determine_missing_children(
617617
continue;
618618
}
619619
let validity = child
620-
.get_node(trie_state, child_path.clone())
620+
.get_node_checked(trie_state, child_path.clone())
621621
.inspect_err(|_| {
622622
debug!("Malformed data when doing get child of a branch node")
623623
})?
@@ -643,7 +643,7 @@ pub fn determine_missing_children(
643643
}
644644
let validity = node
645645
.child
646-
.get_node(trie_state, child_path.clone())
646+
.get_node_checked(trie_state, child_path.clone())
647647
.inspect_err(|_| debug!("Malformed data when doing get child of a branch node"))?
648648
.is_some();
649649

0 commit comments

Comments
 (0)