test(trie): use DB for hash builder in sparse trie fuzz (#13754)

Co-authored-by: Roman Krasiuk <rokrassyuk@gmail.com>
This commit is contained in:
Alexey Shekhirin
2025-01-27 11:46:52 +00:00
committed by GitHub
parent 3538c53317
commit d858a9f87d
3 changed files with 97 additions and 32 deletions

View File

@ -28,9 +28,12 @@ thiserror.workspace = true
[dev-dependencies]
reth-primitives-traits = { workspace = true, features = ["arbitrary"] }
reth-provider = { workspace = true, features = ["test-utils"] }
reth-storage-api.workspace = true
reth-testing-utils.workspace = true
reth-trie = { workspace = true, features = ["test-utils"] }
reth-trie-common = { workspace = true, features = ["test-utils", "arbitrary"] }
reth-testing-utils.workspace = true
reth-trie-db = { workspace = true, features = ["test-utils"] }
arbitrary.workspace = true
assert_matches.workspace = true
@ -44,8 +47,10 @@ rand.workspace = true
[features]
test-utils = [
"reth-primitives-traits/test-utils",
"reth-trie/test-utils",
"reth-provider/test-utils",
"reth-trie-common/test-utils",
"reth-trie-db/test-utils",
"reth-trie/test-utils",
]
arbitrary = [
"reth-primitives-traits/arbitrary",

View File

@ -1401,10 +1401,7 @@ impl SparseTrieUpdates {
#[cfg(test)]
mod tests {
use super::*;
use alloy_primitives::{
map::{B256HashSet, HashSet},
U256,
};
use alloy_primitives::{map::B256HashSet, U256};
use alloy_rlp::Encodable;
use assert_matches::assert_matches;
use itertools::Itertools;
@ -1413,19 +1410,21 @@ mod tests {
use proptest_arbitrary_interop::arb;
use rand::seq::IteratorRandom;
use reth_primitives_traits::Account;
use reth_provider::{test_utils::create_test_provider_factory, TrieWriter};
use reth_trie::{
hashed_cursor::{noop::NoopHashedAccountCursor, HashedPostStateAccountCursor},
node_iter::{TrieElement, TrieNodeIter},
trie_cursor::noop::NoopAccountTrieCursor,
updates::TrieUpdates,
trie_cursor::{noop::NoopAccountTrieCursor, TrieCursor, TrieCursorFactory},
walker::TrieWalker,
BranchNode, ExtensionNode, HashedPostState, LeafNode,
};
use reth_trie_common::{
proof::{ProofNodes, ProofRetainer},
updates::TrieUpdates,
HashBuilder,
};
use std::collections::BTreeMap;
use reth_trie_db::DatabaseTrieCursorFactory;
use std::collections::{BTreeMap, BTreeSet};
/// Pad nibbles to the length of a B256 hash with zeros on the left.
fn pad_nibbles_left(nibbles: Nibbles) -> Nibbles {
@ -1447,6 +1446,7 @@ mod tests {
/// Returns the state root and the retained proof nodes.
fn run_hash_builder(
state: impl IntoIterator<Item = (Nibbles, Account)> + Clone,
trie_cursor: impl TrieCursor,
destroyed_accounts: B256HashSet,
proof_targets: impl IntoIterator<Item = Nibbles>,
) -> (B256, TrieUpdates, ProofNodes, HashMap<Nibbles, TrieMask>, HashMap<Nibbles, TrieMask>)
@ -1459,8 +1459,9 @@ mod tests {
let mut prefix_set = PrefixSetMut::default();
prefix_set.extend_keys(state.clone().into_iter().map(|(nibbles, _)| nibbles));
let walker = TrieWalker::new(NoopAccountTrieCursor::default(), prefix_set.freeze())
.with_deletions_retained(true);
prefix_set.extend_keys(destroyed_accounts.iter().map(Nibbles::unpack));
let walker =
TrieWalker::new(trie_cursor, prefix_set.freeze()).with_deletions_retained(true);
let hashed_post_state = HashedPostState::default()
.with_accounts(state.into_iter().map(|(nibbles, account)| {
(nibbles.pack().into_inner().unwrap().into(), Some(account))
@ -1551,7 +1552,11 @@ mod tests {
(_, SparseNode::Empty | SparseNode::Hash(_)) => continue,
_ => false,
};
assert!(equals, "proof node: {:?}, sparse node: {:?}", proof_node, sparse_node);
assert!(
equals,
"path: {:?}\nproof node: {:?}\nsparse node: {:?}",
proof_node_path, proof_node, sparse_node
);
}
}
@ -1572,7 +1577,12 @@ mod tests {
};
let (hash_builder_root, hash_builder_updates, hash_builder_proof_nodes, _, _) =
run_hash_builder([(key.clone(), value())], Default::default(), [key.clone()]);
run_hash_builder(
[(key.clone(), value())],
NoopAccountTrieCursor::default(),
Default::default(),
[key.clone()],
);
let mut sparse = RevealedSparseTrie::default().with_updates(true);
sparse.update_leaf(key, value_encoded()).unwrap();
@ -1599,6 +1609,7 @@ mod tests {
let (hash_builder_root, hash_builder_updates, hash_builder_proof_nodes, _, _) =
run_hash_builder(
paths.iter().cloned().zip(std::iter::repeat_with(value)),
NoopAccountTrieCursor::default(),
Default::default(),
paths.clone(),
);
@ -1628,6 +1639,7 @@ mod tests {
let (hash_builder_root, hash_builder_updates, hash_builder_proof_nodes, _, _) =
run_hash_builder(
paths.iter().cloned().zip(std::iter::repeat_with(value)),
NoopAccountTrieCursor::default(),
Default::default(),
paths.clone(),
);
@ -1665,6 +1677,7 @@ mod tests {
let (hash_builder_root, hash_builder_updates, hash_builder_proof_nodes, _, _) =
run_hash_builder(
paths.iter().sorted_unstable().cloned().zip(std::iter::repeat_with(value)),
NoopAccountTrieCursor::default(),
Default::default(),
paths.clone(),
);
@ -1703,6 +1716,7 @@ mod tests {
let (hash_builder_root, hash_builder_updates, hash_builder_proof_nodes, _, _) =
run_hash_builder(
paths.iter().cloned().zip(std::iter::repeat_with(|| old_value)),
NoopAccountTrieCursor::default(),
Default::default(),
paths.clone(),
);
@ -1721,6 +1735,7 @@ mod tests {
let (hash_builder_root, hash_builder_updates, hash_builder_proof_nodes, _, _) =
run_hash_builder(
paths.iter().cloned().zip(std::iter::repeat_with(|| new_value)),
NoopAccountTrieCursor::default(),
Default::default(),
paths.clone(),
);
@ -2050,9 +2065,10 @@ mod tests {
// to test the sparse trie updates.
const KEY_NIBBLES_LEN: usize = 3;
fn test(updates: Vec<(HashMap<Nibbles, Account>, HashSet<Nibbles>)>) {
fn test(updates: Vec<(BTreeMap<Nibbles, Account>, BTreeSet<Nibbles>)>) {
{
let mut state = BTreeMap::default();
let provider_factory = create_test_provider_factory();
let mut sparse = RevealedSparseTrie::default().with_updates(true);
for (update, keys_to_delete) in updates {
@ -2072,28 +2088,36 @@ mod tests {
// Insert state updates into the hash builder and calculate the root
state.extend(update);
let provider = provider_factory.provider().unwrap();
let trie_cursor = DatabaseTrieCursorFactory::new(provider.tx_ref());
let (hash_builder_root, hash_builder_updates, hash_builder_proof_nodes, _, _) =
run_hash_builder(
state.clone(),
trie_cursor.account_trie_cursor().unwrap(),
Default::default(),
state.keys().cloned().collect::<Vec<_>>(),
);
// Write trie updates to the database
let provider_rw = provider_factory.provider_rw().unwrap();
provider_rw.write_trie_updates(&hash_builder_updates).unwrap();
provider_rw.commit().unwrap();
// Assert that the sparse trie root matches the hash builder root
assert_eq!(sparse_root, hash_builder_root);
// Assert that the sparse trie updates match the hash builder updates
pretty_assertions::assert_eq!(
sparse_updates.updated_nodes,
hash_builder_updates.account_nodes
BTreeMap::from_iter(sparse_updates.updated_nodes),
BTreeMap::from_iter(hash_builder_updates.account_nodes)
);
// Assert that the sparse trie nodes match the hash builder proof nodes
assert_eq_sparse_trie_proof_nodes(&updated_sparse, hash_builder_proof_nodes);
// Delete some keys from both the hash builder and the sparse trie and check
// that the sparse trie root still matches the hash builder root
for key in keys_to_delete {
state.remove(&key).unwrap();
sparse.remove_leaf(&key).unwrap();
for key in &keys_to_delete {
state.remove(key).unwrap();
sparse.remove_leaf(key).unwrap();
}
// We need to clone the sparse trie, so that all updated branch nodes are
@ -2103,19 +2127,30 @@ mod tests {
let sparse_root = updated_sparse.root();
let sparse_updates = updated_sparse.take_updates();
let provider = provider_factory.provider().unwrap();
let trie_cursor = DatabaseTrieCursorFactory::new(provider.tx_ref());
let (hash_builder_root, hash_builder_updates, hash_builder_proof_nodes, _, _) =
run_hash_builder(
state.clone(),
Default::default(),
trie_cursor.account_trie_cursor().unwrap(),
keys_to_delete
.iter()
.map(|nibbles| B256::from_slice(&nibbles.pack()))
.collect(),
state.keys().cloned().collect::<Vec<_>>(),
);
// Write trie updates to the database
let provider_rw = provider_factory.provider_rw().unwrap();
provider_rw.write_trie_updates(&hash_builder_updates).unwrap();
provider_rw.commit().unwrap();
// Assert that the sparse trie root matches the hash builder root
assert_eq!(sparse_root, hash_builder_root);
// Assert that the sparse trie updates match the hash builder updates
pretty_assertions::assert_eq!(
sparse_updates.updated_nodes,
hash_builder_updates.account_nodes
BTreeMap::from_iter(sparse_updates.updated_nodes),
BTreeMap::from_iter(hash_builder_updates.account_nodes)
);
// Assert that the sparse trie nodes match the hash builder proof nodes
assert_eq_sparse_trie_proof_nodes(&updated_sparse, hash_builder_proof_nodes);
@ -2124,10 +2159,10 @@ mod tests {
}
fn transform_updates(
updates: Vec<HashMap<Nibbles, Account>>,
updates: Vec<BTreeMap<Nibbles, Account>>,
mut rng: impl Rng,
) -> Vec<(HashMap<Nibbles, Account>, HashSet<Nibbles>)> {
let mut keys = HashSet::new();
) -> Vec<(BTreeMap<Nibbles, Account>, BTreeSet<Nibbles>)> {
let mut keys = BTreeSet::new();
updates
.into_iter()
.map(|update| {
@ -2148,12 +2183,12 @@ mod tests {
proptest!(ProptestConfig::with_cases(10), |(
updates in proptest::collection::vec(
proptest::collection::hash_map(
proptest::collection::btree_map(
any_with::<Nibbles>(SizeRange::new(KEY_NIBBLES_LEN..=KEY_NIBBLES_LEN)).prop_map(pad_nibbles_right),
arb::<Account>(),
1..100,
).prop_map(HashMap::from_iter),
1..100,
1..50,
),
1..50,
).prop_perturb(transform_updates)
)| {
test(updates)
@ -2187,6 +2222,7 @@ mod tests {
let (_, _, hash_builder_proof_nodes, branch_node_hash_masks, branch_node_tree_masks) =
run_hash_builder(
[(key1(), value()), (key3(), value())],
NoopAccountTrieCursor::default(),
Default::default(),
[Nibbles::default()],
);
@ -2200,7 +2236,12 @@ mod tests {
// Generate the proof for the first key and reveal it in the sparse trie
let (_, _, hash_builder_proof_nodes, branch_node_hash_masks, branch_node_tree_masks) =
run_hash_builder([(key1(), value()), (key3(), value())], Default::default(), [key1()]);
run_hash_builder(
[(key1(), value()), (key3(), value())],
NoopAccountTrieCursor::default(),
Default::default(),
[key1()],
);
for (path, node) in hash_builder_proof_nodes.nodes_sorted() {
let hash_mask = branch_node_hash_masks.get(&path).copied();
let tree_mask = branch_node_tree_masks.get(&path).copied();
@ -2226,7 +2267,12 @@ mod tests {
// Generate the proof for the third key and reveal it in the sparse trie
let (_, _, hash_builder_proof_nodes, branch_node_hash_masks, branch_node_tree_masks) =
run_hash_builder([(key1(), value()), (key3(), value())], Default::default(), [key3()]);
run_hash_builder(
[(key1(), value()), (key3(), value())],
NoopAccountTrieCursor::default(),
Default::default(),
[key3()],
);
for (path, node) in hash_builder_proof_nodes.nodes_sorted() {
let hash_mask = branch_node_hash_masks.get(&path).copied();
let tree_mask = branch_node_tree_masks.get(&path).copied();
@ -2245,6 +2291,7 @@ mod tests {
// compare them to the sparse trie
let (_, _, hash_builder_proof_nodes, _, _) = run_hash_builder(
[(key1(), value()), (key2(), value()), (key3(), value())],
NoopAccountTrieCursor::default(),
Default::default(),
[key1(), key2(), key3()],
);
@ -2273,6 +2320,7 @@ mod tests {
let (_, _, hash_builder_proof_nodes, branch_node_hash_masks, branch_node_tree_masks) =
run_hash_builder(
[(key1(), value()), (key2(), value()), (key3(), value())],
NoopAccountTrieCursor::default(),
Default::default(),
[Nibbles::default()],
);
@ -2289,6 +2337,7 @@ mod tests {
let (_, _, hash_builder_proof_nodes, branch_node_hash_masks, branch_node_tree_masks) =
run_hash_builder(
[(key1(), value()), (key2(), value()), (key3(), value())],
NoopAccountTrieCursor::default(),
Default::default(),
[key1(), Nibbles::from_nibbles_unchecked([0x01])],
);
@ -2319,6 +2368,7 @@ mod tests {
let (_, _, hash_builder_proof_nodes, branch_node_hash_masks, branch_node_tree_masks) =
run_hash_builder(
[(key1(), value()), (key2(), value()), (key3(), value())],
NoopAccountTrieCursor::default(),
Default::default(),
[key2()],
);
@ -2361,6 +2411,7 @@ mod tests {
let (_, _, hash_builder_proof_nodes, branch_node_hash_masks, branch_node_tree_masks) =
run_hash_builder(
[(key1(), value()), (key2(), value())],
NoopAccountTrieCursor::default(),
Default::default(),
[Nibbles::default()],
);
@ -2389,7 +2440,12 @@ mod tests {
// Generate the proof for the first key and reveal it in the sparse trie
let (_, _, hash_builder_proof_nodes, branch_node_hash_masks, branch_node_tree_masks) =
run_hash_builder([(key1(), value()), (key2(), value())], Default::default(), [key1()]);
run_hash_builder(
[(key1(), value()), (key2(), value())],
NoopAccountTrieCursor::default(),
Default::default(),
[key1()],
);
for (path, node) in hash_builder_proof_nodes.nodes_sorted() {
let hash_mask = branch_node_hash_masks.get(&path).copied();
let tree_mask = branch_node_tree_masks.get(&path).copied();
@ -2491,6 +2547,7 @@ mod tests {
let (hash_builder_root, hash_builder_updates, _, _, _) = run_hash_builder(
[(key1(), value()), (key2(), value())],
NoopAccountTrieCursor::default(),
Default::default(),
[Nibbles::default()],
);