Skip to content

Commit fa072ee

Browse files
authored
chore(l1): fast validate (#5713)
**Motivation** We want to validate the tree quicker and consume less memory. **Description** - Made validate state and storage root not recompute the entire tree, instead just traverse the trie to found the missing nodes.
1 parent c83c0cb commit fa072ee

File tree

2 files changed

+69
-47
lines changed

2 files changed

+69
-47
lines changed

crates/common/trie/trie.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -528,6 +528,38 @@ impl Trie {
528528
trie.root = root;
529529
trie
530530
}
531+
532+
/// Validates that the Trie isn't missing any nodes expected in the branches
533+
///
534+
/// This is used internally with debug assertions to check the status of the trie
535+
/// after syncing operations.
536+
/// Note: this operation validates the hashes because the iterator uses
537+
/// get_node_checked. We shouldn't downgrade that to the unchecked version
538+
pub fn validate(self) -> Result<(), TrieError> {
539+
let mut expected_count = if self.root.is_valid() { 1 } else { 0 };
540+
for (_, node) in self.into_iter() {
541+
expected_count -= 1;
542+
match node {
543+
Node::Branch(branch_node) => {
544+
expected_count += branch_node
545+
.choices
546+
.iter()
547+
.filter(|child| child.is_valid())
548+
.count();
549+
}
550+
Node::Extension(_) => {
551+
expected_count += 1;
552+
}
553+
Node::Leaf(_) => {}
554+
}
555+
}
556+
if expected_count != 0 {
557+
return Err(TrieError::Verify(format!(
558+
"Node count mismatch, expected {expected_count} more"
559+
)));
560+
}
561+
Ok(())
562+
}
531563
}
532564

533565
impl IntoIterator for Trie {

crates/networking/p2p/sync.rs

Lines changed: 37 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,12 @@ use ethrex_common::{
2525
constants::{EMPTY_KECCACK_HASH, EMPTY_TRIE_HASH},
2626
types::{AccountState, Block, BlockHeader},
2727
};
28-
use ethrex_rlp::{decode::RLPDecode, encode::RLPEncode, error::RLPDecodeError};
28+
use ethrex_rlp::{decode::RLPDecode, error::RLPDecodeError};
2929
use ethrex_storage::{Store, error::StoreError};
30+
#[cfg(feature = "rocksdb")]
31+
use ethrex_trie::Trie;
32+
use ethrex_trie::TrieError;
3033
use ethrex_trie::trie_sorted::TrieGenerationError;
31-
use ethrex_trie::{Trie, TrieError};
3234
use rayon::iter::{ParallelBridge, ParallelIterator};
3335
use std::collections::{BTreeMap, BTreeSet, HashSet};
3436
use std::path::{Path, PathBuf};
@@ -870,10 +872,7 @@ impl Syncer {
870872
store.generate_flatkeyvalue()?;
871873

872874
debug_assert!(validate_state_root(store.clone(), pivot_header.state_root).await);
873-
debug_assert!(validate_storage_root(
874-
store.clone(),
875-
pivot_header.state_root
876-
));
875+
debug_assert!(validate_storage_root(store.clone(), pivot_header.state_root).await);
877876

878877
info!("Finished healing");
879878

@@ -992,6 +991,9 @@ impl Syncer {
992991
}
993992
}
994993

994+
#[cfg(not(feature = "rocksdb"))]
995+
use ethrex_rlp::encode::RLPEncode;
996+
995997
#[cfg(not(feature = "rocksdb"))]
996998
type StorageRoots = (H256, Vec<(ethrex_trie::Nibbles, Vec<u8>)>);
997999

@@ -1216,62 +1218,50 @@ impl<T> From<SendError<T>> for SyncError {
12161218

12171219
pub async fn validate_state_root(store: Store, state_root: H256) -> bool {
12181220
info!("Starting validate_state_root");
1219-
let computed_state_root = tokio::task::spawn_blocking(move || {
1220-
Trie::compute_hash_from_unsorted_iter(
1221-
store
1222-
.iter_accounts(state_root)
1223-
.expect("we couldn't iterate over accounts")
1224-
.map(|(hash, state)| (hash.0.to_vec(), state.encode_to_vec())),
1225-
)
1221+
let validated = tokio::task::spawn_blocking(move || {
1222+
store
1223+
.open_locked_state_trie(state_root)
1224+
.expect("couldn't open trie")
1225+
.validate()
12261226
})
12271227
.await
12281228
.expect("We should be able to create threads");
12291229

1230-
let tree_validated = state_root == computed_state_root;
1231-
if tree_validated {
1230+
if validated.is_ok() {
12321231
info!("Succesfully validated tree, {state_root} found");
12331232
} else {
1234-
error!(
1235-
"We have failed the validation of the state tree {state_root} expected but {computed_state_root} found"
1236-
);
1233+
error!("We have failed the validation of the state tree");
1234+
std::process::exit(1);
12371235
}
1238-
tree_validated
1236+
validated.is_ok()
12391237
}
12401238

1241-
pub fn validate_storage_root(store: Store, state_root: H256) -> bool {
1239+
pub async fn validate_storage_root(store: Store, state_root: H256) -> bool {
12421240
info!("Starting validate_storage_root");
1243-
let is_valid = store
1244-
.clone()
1245-
.iter_accounts(state_root)
1246-
.expect("We should be able to open the store")
1247-
.par_bridge()
1248-
.map(|(hashed_address, account_state)|
1249-
{
1250-
let store_clone = store.clone();
1251-
let computed_storage_root = Trie::compute_hash_from_unsorted_iter(
1241+
let is_valid = tokio::task::spawn_blocking(move || {
1242+
store
1243+
.iter_accounts(state_root)
1244+
.expect("couldn't iterate accounts")
1245+
.par_bridge()
1246+
.try_for_each(|(hashed_address, account_state)| {
1247+
let store_clone = store.clone();
12521248
store_clone
1253-
.iter_storage(state_root, hashed_address)
1254-
.expect("we couldn't iterate over accounts")
1255-
.expect("This address should be valid")
1256-
.map(|(hash, state)| (hash.0.to_vec(), state.encode_to_vec())),
1257-
);
1258-
1259-
let tree_validated = account_state.storage_root == computed_storage_root;
1260-
if !tree_validated {
1261-
error!(
1262-
"We have failed the validation of the storage tree {:x} expected but {computed_storage_root:x} found for the account {:x}",
1263-
account_state.storage_root,
1264-
hashed_address
1265-
);
1266-
}
1267-
tree_validated
1249+
.open_locked_storage_trie(
1250+
hashed_address,
1251+
state_root,
1252+
account_state.storage_root,
1253+
)
1254+
.expect("couldn't open storage trie")
1255+
.validate()
1256+
})
12681257
})
1269-
.all(|valid| valid);
1258+
.await
1259+
.expect("We should be able to create threads");
12701260
info!("Finished validate_storage_root");
1271-
if !is_valid {
1261+
if is_valid.is_err() {
12721262
std::process::exit(1);
12731263
}
1274-
is_valid
1264+
is_valid.is_ok()
12751265
}
12761266

12771267
pub fn validate_bytecodes(store: Store, state_root: H256) -> bool {

0 commit comments

Comments
 (0)