mirror of
https://github.com/paradigmxyz/reth.git
synced 2026-04-30 03:01:58 -04:00
fix(trie): Fix branch collapse edge-cases in ArenaParallelSparseTrie (#23053)
Signed-off-by: Delweng <delweng@gmail.com> Co-authored-by: Amp <amp@ampcode.com> Co-authored-by: stevencartavia <112043913+stevencartavia@users.noreply.github.com> Co-authored-by: Derek Cofausper <256792747+decofe@users.noreply.github.com> Co-authored-by: Alexey Shekhirin <5773434+shekhirin@users.noreply.github.com> Co-authored-by: MagicJoshh <subhshubham398@gmail.com> Co-authored-by: Delweng <delweng@gmail.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: github-merge-queue <118344674+github-merge-queue@users.noreply.github.com> Co-authored-by: Huber <HuberyJulianay@gmail.com> Co-authored-by: Sergei Shulepov <2205845+pepyakin@users.noreply.github.com> Co-authored-by: Olivier Dupont <olivierdupontvier@gmail.com> Co-authored-by: YK <chiayongkang@hotmail.com> Co-authored-by: Crypto Nomad <cryptonomadkripto@gmail.com> Co-authored-by: ligt <me@ligt.dev> Co-authored-by: Sergei Shulepov <pep@tempo.xyz>
This commit is contained in:
5
.changelog/dull-seals-laugh.md
Normal file
5
.changelog/dull-seals-laugh.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
reth-trie-sparse: minor
|
||||
---
|
||||
|
||||
Fixed a bug in `ArenaParallelSparseTrie` where subtrie updates that would completely empty a subtrie were incorrectly dispatched to parallel workers instead of being processed inline, preventing correct branch collapse detection when blinded siblings are present. Refactored the `SparseTrie` test suite to accept a `fn() -> T` factory instead of requiring `T: Default`, enabling a new `arena_parallel_sparse_trie_always_parallel` test variant that exercises all tests with parallelism thresholds set to 1. Added `test_branch_collapse_multi_empty_subtries_blinded_remaining` to cover the case where removing multiple revealed leaves empties their subtries and leaves a single blinded sibling requiring a proof.
|
||||
@@ -2817,7 +2817,24 @@ impl SparseTrie for ArenaParallelSparseTrie {
|
||||
|
||||
let num_subtrie_updates = update_idx - subtrie_start;
|
||||
|
||||
if num_subtrie_updates >= threshold {
|
||||
// If all updates are removals and could empty the subtrie,
|
||||
// force inline processing so the upper-arena collapse logic
|
||||
// can detect blinded siblings and request proofs.
|
||||
let all_removals = subtrie_updates
|
||||
.iter()
|
||||
// Filter out Touched, as they don't affect the structure of the trie. So an
|
||||
// update set with 2 removals and one Touched could still result in an empty
|
||||
// sub trie.
|
||||
.filter(|(_, _, u)| matches!(u, LeafUpdate::Changed(_)))
|
||||
.all(|(_, _, u)| matches!(u, LeafUpdate::Changed(v) if v.is_empty()));
|
||||
let subtrie_num_leaves = match &self.upper_arena[child_idx] {
|
||||
ArenaSparseNode::Subtrie(s) => s.num_leaves,
|
||||
_ => 0,
|
||||
};
|
||||
let might_empty_subtrie =
|
||||
all_removals && num_subtrie_updates as u64 >= subtrie_num_leaves;
|
||||
|
||||
if num_subtrie_updates >= threshold && !might_empty_subtrie {
|
||||
// Take subtrie for parallel update.
|
||||
trace!(target: TRACE_TARGET, ?subtrie_root_path, num_subtrie_updates, "Taking subtrie for parallel update");
|
||||
let ArenaSparseNode::Subtrie(subtrie) = mem::replace(
|
||||
@@ -2978,7 +2995,10 @@ impl SparseTrie for ArenaParallelSparseTrie {
|
||||
}
|
||||
|
||||
// Navigate to each taken subtrie via seek to propagate dirty state
|
||||
// through intermediate branches, and collapse any EmptyRoot subtries.
|
||||
// through intermediate branches. Taken subtries are guaranteed not to
|
||||
// become EmptyRoot (the would-empty-subtrie check above forces those
|
||||
// inline), so we only need to handle sibling collapses that may have
|
||||
// occurred during inline processing while this subtrie was taken.
|
||||
{
|
||||
let mut cursor = mem::take(&mut self.buffers.cursor);
|
||||
cursor.reset(&self.upper_arena, self.root, Nibbles::default());
|
||||
@@ -2987,13 +3007,17 @@ impl SparseTrie for ArenaParallelSparseTrie {
|
||||
let find_result = cursor.seek(&mut self.upper_arena, path);
|
||||
match find_result {
|
||||
SeekResult::RevealedSubtrie => {
|
||||
let head_idx = cursor.head().expect("cursor is non-empty").index;
|
||||
if let ArenaSparseNode::Subtrie(s) = &self.upper_arena[head_idx] &&
|
||||
matches!(s.arena[s.root], ArenaSparseNode::EmptyRoot)
|
||||
{
|
||||
self.maybe_unwrap_subtrie(&mut cursor);
|
||||
continue;
|
||||
}
|
||||
debug_assert!(
|
||||
{
|
||||
let head_idx = cursor.head().expect("cursor is non-empty").index;
|
||||
!matches!(
|
||||
&self.upper_arena[head_idx],
|
||||
ArenaSparseNode::Subtrie(s) if matches!(s.arena[s.root], ArenaSparseNode::EmptyRoot)
|
||||
)
|
||||
},
|
||||
"taken subtrie became EmptyRoot — should have been forced inline"
|
||||
);
|
||||
|
||||
cursor.pop(&mut self.upper_arena);
|
||||
|
||||
// The parent branch (now at cursor top) may have had a sibling
|
||||
|
||||
@@ -5,7 +5,7 @@ use super::*;
|
||||
/// After calling `commit_updates` with taken updates, a subsequent mutation + `root()` +
|
||||
/// `take_updates()` should only report the delta from the new baseline — it must NOT
|
||||
/// re-report branch nodes from the first round.
|
||||
pub(super) fn test_commit_updates_syncs_branch_masks<T: SparseTrie + Default>() {
|
||||
pub(super) fn test_commit_updates_syncs_branch_masks<T: SparseTrie>(new_trie: fn() -> T) {
|
||||
// 5 leaves spread across different subtrie regions.
|
||||
let mut key_a = B256::ZERO;
|
||||
key_a.0[0] = 0x10;
|
||||
@@ -27,7 +27,7 @@ pub(super) fn test_commit_updates_syncs_branch_masks<T: SparseTrie + Default>()
|
||||
]);
|
||||
|
||||
let harness = SuiteTestHarness::new(storage);
|
||||
let mut trie: T = harness.init_trie_fully_revealed(true);
|
||||
let mut trie: T = harness.init_trie_fully_revealed(true, new_trie);
|
||||
|
||||
// Cache initial hashes.
|
||||
let _ = trie.root();
|
||||
@@ -79,7 +79,7 @@ pub(super) fn test_commit_updates_syncs_branch_masks<T: SparseTrie + Default>()
|
||||
/// Committing empty updated/removed sets should not change trie behavior.
|
||||
///
|
||||
/// Build a trie, compute root, then commit empty updates. Root should be unchanged.
|
||||
pub(super) fn test_commit_updates_empty_is_noop<T: SparseTrie + Default>() {
|
||||
pub(super) fn test_commit_updates_empty_is_noop<T: SparseTrie>(new_trie: fn() -> T) {
|
||||
let mut key_a = B256::ZERO;
|
||||
key_a.0[0] = 0x10;
|
||||
let mut key_b = B256::ZERO;
|
||||
@@ -90,7 +90,7 @@ pub(super) fn test_commit_updates_empty_is_noop<T: SparseTrie + Default>() {
|
||||
BTreeMap::from([(key_a, U256::from(1)), (key_b, U256::from(2)), (key_c, U256::from(3))]);
|
||||
|
||||
let harness = SuiteTestHarness::new(storage);
|
||||
let mut trie: T = harness.init_trie_fully_revealed(true);
|
||||
let mut trie: T = harness.init_trie_fully_revealed(true, new_trie);
|
||||
|
||||
let hash1 = trie.root();
|
||||
assert_eq!(hash1, harness.original_root());
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use super::*;
|
||||
|
||||
pub(super) fn test_find_leaf_exists<T: SparseTrie + Default>() {
|
||||
pub(super) fn test_find_leaf_exists<T: SparseTrie>(new_trie: fn() -> T) {
|
||||
let key1 = B256::with_last_byte(0x10);
|
||||
let key2 = B256::with_last_byte(0x20);
|
||||
let key3 = B256::with_last_byte(0x30);
|
||||
@@ -9,7 +9,7 @@ pub(super) fn test_find_leaf_exists<T: SparseTrie + Default>() {
|
||||
[(key1, U256::from(1)), (key2, U256::from(2)), (key3, U256::from(3))].into_iter().collect();
|
||||
|
||||
let harness = SuiteTestHarness::new(base_storage);
|
||||
let trie: T = harness.init_trie_fully_revealed(false);
|
||||
let trie: T = harness.init_trie_fully_revealed(false, new_trie);
|
||||
|
||||
let key2_nibbles = Nibbles::unpack(key2);
|
||||
|
||||
@@ -29,7 +29,7 @@ pub(super) fn test_find_leaf_exists<T: SparseTrie + Default>() {
|
||||
);
|
||||
}
|
||||
|
||||
pub(super) fn test_find_leaf_nonexistent<T: SparseTrie + Default>() {
|
||||
pub(super) fn test_find_leaf_nonexistent<T: SparseTrie>(new_trie: fn() -> T) {
|
||||
let key1 = B256::with_last_byte(0x10);
|
||||
let key2 = B256::with_last_byte(0x20);
|
||||
let key3 = B256::with_last_byte(0x30);
|
||||
@@ -38,7 +38,7 @@ pub(super) fn test_find_leaf_nonexistent<T: SparseTrie + Default>() {
|
||||
[(key1, U256::from(1)), (key2, U256::from(2)), (key3, U256::from(3))].into_iter().collect();
|
||||
|
||||
let harness = SuiteTestHarness::new(base_storage);
|
||||
let trie: T = harness.init_trie_fully_revealed(false);
|
||||
let trie: T = harness.init_trie_fully_revealed(false, new_trie);
|
||||
|
||||
let nonexistent_key = B256::with_last_byte(0x99);
|
||||
let nonexistent_nibbles = Nibbles::unpack(nonexistent_key);
|
||||
@@ -52,7 +52,7 @@ pub(super) fn test_find_leaf_nonexistent<T: SparseTrie + Default>() {
|
||||
|
||||
/// `find_leaf` on a path that traverses a blinded node returns
|
||||
/// `Err(LeafLookupError::BlindedNode)`.
|
||||
pub(super) fn test_find_leaf_blinded<T: SparseTrie + Default>() {
|
||||
pub(super) fn test_find_leaf_blinded<T: SparseTrie>(new_trie: fn() -> T) {
|
||||
// Use ≥16 keys per nibble group so branch children become hash nodes (>32 bytes RLP),
|
||||
// ensuring partial reveal leaves blinded subtries.
|
||||
let mut base_storage = BTreeMap::new();
|
||||
@@ -78,7 +78,7 @@ pub(super) fn test_find_leaf_blinded<T: SparseTrie + Default>() {
|
||||
let harness = SuiteTestHarness::new(base_storage);
|
||||
|
||||
// Reveal only group_a keys, leaving group_b's subtrie blinded.
|
||||
let trie: T = harness.init_trie_with_targets(&group_a_keys, false);
|
||||
let trie: T = harness.init_trie_with_targets(&group_a_keys, false, new_trie);
|
||||
|
||||
// Look up a key in group_b — should hit a blinded node.
|
||||
let blinded_key = group_b_keys[0];
|
||||
@@ -93,7 +93,7 @@ pub(super) fn test_find_leaf_blinded<T: SparseTrie + Default>() {
|
||||
|
||||
/// `find_leaf` with an expected value that doesn't match the actual leaf value
|
||||
/// returns `Err(LeafLookupError::ValueMismatch)`.
|
||||
pub(super) fn test_find_leaf_value_mismatch<T: SparseTrie + Default>() {
|
||||
pub(super) fn test_find_leaf_value_mismatch<T: SparseTrie>(new_trie: fn() -> T) {
|
||||
let key1 = B256::with_last_byte(0x10);
|
||||
let key2 = B256::with_last_byte(0x20);
|
||||
let key3 = B256::with_last_byte(0x30);
|
||||
@@ -102,7 +102,7 @@ pub(super) fn test_find_leaf_value_mismatch<T: SparseTrie + Default>() {
|
||||
[(key1, U256::from(1)), (key2, U256::from(2)), (key3, U256::from(3))].into_iter().collect();
|
||||
|
||||
let harness = SuiteTestHarness::new(base_storage);
|
||||
let trie: T = harness.init_trie_fully_revealed(false);
|
||||
let trie: T = harness.init_trie_fully_revealed(false, new_trie);
|
||||
|
||||
let key2_nibbles = Nibbles::unpack(key2);
|
||||
let wrong_value = encode_fixed_size(&U256::from(999)).to_vec();
|
||||
@@ -119,7 +119,7 @@ pub(super) fn test_find_leaf_value_mismatch<T: SparseTrie + Default>() {
|
||||
///
|
||||
/// Two leaves sharing prefix 0x12 create a branch with children at nibbles 3 and 5.
|
||||
/// Searching for a key with nibble 7 at that branch position should return `NonExistent`.
|
||||
pub(super) fn test_find_leaf_nonexistent_branch_divergence<T: SparseTrie + Default>() {
|
||||
pub(super) fn test_find_leaf_nonexistent_branch_divergence<T: SparseTrie>(new_trie: fn() -> T) {
|
||||
// key1 nibbles: 1,2,3,4,0,0,... → B256 = 0x12340000...
|
||||
let mut key1 = B256::ZERO;
|
||||
key1.0[0] = 0x12;
|
||||
@@ -134,7 +134,7 @@ pub(super) fn test_find_leaf_nonexistent_branch_divergence<T: SparseTrie + Defau
|
||||
[(key1, U256::from(1)), (key2, U256::from(2))].into_iter().collect();
|
||||
|
||||
let harness = SuiteTestHarness::new(base_storage);
|
||||
let trie: T = harness.init_trie_fully_revealed(false);
|
||||
let trie: T = harness.init_trie_fully_revealed(false, new_trie);
|
||||
|
||||
// search_path nibbles: 1,2,7,8,0,0,... → B256 = 0x12780000...
|
||||
// Nibble 7 is unset at the branch (only 3 and 5 are set).
|
||||
@@ -155,7 +155,7 @@ pub(super) fn test_find_leaf_nonexistent_branch_divergence<T: SparseTrie + Defau
|
||||
///
|
||||
/// A single leaf at nibbles [1,2,3,4,5,6,...] creates an extension root with key 0x12.
|
||||
/// Searching for a key at nibbles [1,2,7,8,...] diverges from that extension.
|
||||
pub(super) fn test_find_leaf_nonexistent_extension_divergence<T: SparseTrie + Default>() {
|
||||
pub(super) fn test_find_leaf_nonexistent_extension_divergence<T: SparseTrie>(new_trie: fn() -> T) {
|
||||
// Single leaf: nibbles [1,2,3,4,5,6,0,0,...] → B256 = 0x12345600...
|
||||
let mut key1 = B256::ZERO;
|
||||
key1.0[0] = 0x12;
|
||||
@@ -165,7 +165,7 @@ pub(super) fn test_find_leaf_nonexistent_extension_divergence<T: SparseTrie + De
|
||||
let base_storage: BTreeMap<B256, U256> = once((key1, U256::from(1))).collect();
|
||||
|
||||
let harness = SuiteTestHarness::new(base_storage);
|
||||
let trie: T = harness.init_trie_fully_revealed(false);
|
||||
let trie: T = harness.init_trie_fully_revealed(false, new_trie);
|
||||
|
||||
// Search path diverges from the extension: nibbles [1,2,7,8,0,0,...] → B256 = 0x12780000...
|
||||
let mut search_key = B256::ZERO;
|
||||
@@ -185,7 +185,7 @@ pub(super) fn test_find_leaf_nonexistent_extension_divergence<T: SparseTrie + De
|
||||
///
|
||||
/// A single leaf at nibbles [1,2,3,4,0,0,...] exists. Searching for a key at nibbles
|
||||
/// [1,2,3,4,5,6,0,0,...] extends past the existing leaf — it should return `NonExistent`.
|
||||
pub(super) fn test_find_leaf_nonexistent_leaf_divergence<T: SparseTrie + Default>() {
|
||||
pub(super) fn test_find_leaf_nonexistent_leaf_divergence<T: SparseTrie>(new_trie: fn() -> T) {
|
||||
// Existing leaf at short path: nibbles [1,2,3,4,0,0,...] → B256 = 0x12340000...
|
||||
let mut existing_key = B256::ZERO;
|
||||
existing_key.0[0] = 0x12;
|
||||
@@ -194,7 +194,7 @@ pub(super) fn test_find_leaf_nonexistent_leaf_divergence<T: SparseTrie + Default
|
||||
let base_storage: BTreeMap<B256, U256> = once((existing_key, U256::from(1))).collect();
|
||||
|
||||
let harness = SuiteTestHarness::new(base_storage);
|
||||
let trie: T = harness.init_trie_fully_revealed(false);
|
||||
let trie: T = harness.init_trie_fully_revealed(false, new_trie);
|
||||
|
||||
// Search path extends past the existing leaf: nibbles [1,2,3,4,5,6,0,0,...]
|
||||
// → B256 = 0x12345600...
|
||||
|
||||
@@ -2,7 +2,7 @@ use super::*;
|
||||
|
||||
/// After inserting or updating a leaf via `update_leaves`, `get_leaf_value`
|
||||
/// should return the new value.
|
||||
pub(super) fn test_get_leaf_value_after_update<T: SparseTrie + Default>() {
|
||||
pub(super) fn test_get_leaf_value_after_update<T: SparseTrie>(new_trie: fn() -> T) {
|
||||
let key1 = B256::with_last_byte(0x10);
|
||||
let key2 = B256::with_last_byte(0x20);
|
||||
let key3 = B256::with_last_byte(0x30);
|
||||
@@ -11,7 +11,7 @@ pub(super) fn test_get_leaf_value_after_update<T: SparseTrie + Default>() {
|
||||
[(key1, U256::from(1)), (key2, U256::from(2)), (key3, U256::from(3))].into_iter().collect();
|
||||
|
||||
let harness = SuiteTestHarness::new(base_storage);
|
||||
let mut trie: T = harness.init_trie_fully_revealed(false);
|
||||
let mut trie: T = harness.init_trie_fully_revealed(false, new_trie);
|
||||
|
||||
// Insert a new leaf (key4) with value 42.
|
||||
let key4 = B256::with_last_byte(0x40);
|
||||
@@ -42,7 +42,7 @@ pub(super) fn test_get_leaf_value_after_update<T: SparseTrie + Default>() {
|
||||
}
|
||||
|
||||
/// After removing a leaf via `update_leaves`, `get_leaf_value` should return `None`.
|
||||
pub(super) fn test_get_leaf_value_after_removal<T: SparseTrie + Default>() {
|
||||
pub(super) fn test_get_leaf_value_after_removal<T: SparseTrie>(new_trie: fn() -> T) {
|
||||
let key1 = B256::with_last_byte(0x10);
|
||||
let key2 = B256::with_last_byte(0x20);
|
||||
let key3 = B256::with_last_byte(0x30);
|
||||
@@ -51,7 +51,7 @@ pub(super) fn test_get_leaf_value_after_removal<T: SparseTrie + Default>() {
|
||||
[(key1, U256::from(1)), (key2, U256::from(2)), (key3, U256::from(3))].into_iter().collect();
|
||||
|
||||
let harness = SuiteTestHarness::new(base_storage);
|
||||
let mut trie: T = harness.init_trie_fully_revealed(false);
|
||||
let mut trie: T = harness.init_trie_fully_revealed(false, new_trie);
|
||||
|
||||
let key2_nibbles = Nibbles::unpack(key2);
|
||||
let expected_value_rlp = encode_fixed_size(&U256::from(2)).to_vec();
|
||||
|
||||
@@ -5,7 +5,7 @@ use super::*;
|
||||
/// Build a trie with enough leaves to produce hashed branch children (≥16 per subtrie),
|
||||
/// insert 1 new leaf + modify 1 existing, compute root, take updates, commit, then verify
|
||||
/// root is unchanged (cache hit) and updates are non-empty.
|
||||
pub(super) fn test_full_lifecycle_update_root_take_commit<T: SparseTrie + Default>() {
|
||||
pub(super) fn test_full_lifecycle_update_root_take_commit<T: SparseTrie>(new_trie: fn() -> T) {
|
||||
// 16 leaves sharing prefix [1,0] to produce hashed branch children.
|
||||
let mut storage: BTreeMap<B256, U256> = BTreeMap::new();
|
||||
for i in 0u8..16 {
|
||||
@@ -20,7 +20,7 @@ pub(super) fn test_full_lifecycle_update_root_take_commit<T: SparseTrie + Defaul
|
||||
storage.insert(key_extra, U256::from(100));
|
||||
|
||||
let harness = SuiteTestHarness::new(storage.clone());
|
||||
let mut trie: T = harness.init_trie_fully_revealed(true);
|
||||
let mut trie: T = harness.init_trie_fully_revealed(true, new_trie);
|
||||
|
||||
// Cache initial hashes.
|
||||
let _ = trie.root();
|
||||
@@ -66,7 +66,7 @@ pub(super) fn test_full_lifecycle_update_root_take_commit<T: SparseTrie + Defaul
|
||||
|
||||
/// Multiple rounds of (update → root → `take_updates` → `commit_updates`), followed by
|
||||
/// a prune, simulating block processing.
|
||||
pub(super) fn test_multi_round_update_commit_prune_cycle<T: SparseTrie + Default>() {
|
||||
pub(super) fn test_multi_round_update_commit_prune_cycle<T: SparseTrie>(new_trie: fn() -> T) {
|
||||
// Build a trie with 10 leaves.
|
||||
let mut storage: BTreeMap<B256, U256> = BTreeMap::new();
|
||||
let mut keys = Vec::new();
|
||||
@@ -78,7 +78,7 @@ pub(super) fn test_multi_round_update_commit_prune_cycle<T: SparseTrie + Default
|
||||
}
|
||||
|
||||
let mut harness = SuiteTestHarness::new(storage.clone());
|
||||
let mut trie: T = harness.init_trie_fully_revealed(true);
|
||||
let mut trie: T = harness.init_trie_fully_revealed(true, new_trie);
|
||||
|
||||
// Cache initial hashes.
|
||||
let _ = trie.root();
|
||||
@@ -146,7 +146,7 @@ pub(super) fn test_multi_round_update_commit_prune_cycle<T: SparseTrie + Default
|
||||
///
|
||||
/// Build a 5-leaf trie, reveal all proofs, apply 2 modifications + 1 removal,
|
||||
/// and verify root matches the reference trie with the same mutations.
|
||||
pub(super) fn test_reveal_update_root_basic_lifecycle<T: SparseTrie + Default>() {
|
||||
pub(super) fn test_reveal_update_root_basic_lifecycle<T: SparseTrie>(new_trie: fn() -> T) {
|
||||
let mut keys = Vec::new();
|
||||
let mut storage: BTreeMap<B256, U256> = BTreeMap::new();
|
||||
for i in 0u8..5 {
|
||||
@@ -157,7 +157,7 @@ pub(super) fn test_reveal_update_root_basic_lifecycle<T: SparseTrie + Default>()
|
||||
}
|
||||
|
||||
let harness = SuiteTestHarness::new(storage.clone());
|
||||
let mut trie: T = harness.init_trie_fully_revealed(true);
|
||||
let mut trie: T = harness.init_trie_fully_revealed(true, new_trie);
|
||||
|
||||
// Apply 2 modifications and 1 removal.
|
||||
let mut changeset: BTreeMap<B256, U256> = BTreeMap::new();
|
||||
@@ -185,7 +185,7 @@ pub(super) fn test_reveal_update_root_basic_lifecycle<T: SparseTrie + Default>()
|
||||
|
||||
/// Incremental reveal and update with retry loop.
|
||||
/// Partial proof → `update_leaves` hits blinded nodes → reveal more → retry succeeds.
|
||||
pub(super) fn test_incremental_reveal_and_update_with_retry<T: SparseTrie + Default>() {
|
||||
pub(super) fn test_incremental_reveal_and_update_with_retry<T: SparseTrie>(new_trie: fn() -> T) {
|
||||
// Build 10 leaves across multiple subtries so partial reveal leaves some blinded.
|
||||
// Use 16 keys per group so branch children become hash nodes (>32 bytes RLP).
|
||||
let mut base_storage = BTreeMap::new();
|
||||
@@ -211,7 +211,7 @@ pub(super) fn test_incremental_reveal_and_update_with_retry<T: SparseTrie + Defa
|
||||
let harness = SuiteTestHarness::new(base_storage.clone());
|
||||
|
||||
// Reveal only group A keys, leaving group B's subtrie blinded.
|
||||
let mut trie: T = harness.init_trie_with_targets(&group_a_keys, true);
|
||||
let mut trie: T = harness.init_trie_with_targets(&group_a_keys, true, new_trie);
|
||||
|
||||
// Prepare updates for 5 keys: 3 from group A (covered) + 2 from group B (blinded).
|
||||
let mut changeset: BTreeMap<B256, U256> = BTreeMap::new();
|
||||
@@ -269,7 +269,7 @@ pub(super) fn test_incremental_reveal_and_update_with_retry<T: SparseTrie + Defa
|
||||
/// Simulates a complete block processing cycle: receive state updates → apply to storage
|
||||
/// tries → compute storage roots → promote to account trie → compute state root → take
|
||||
/// updates → commit → prune for next block.
|
||||
pub(super) fn test_full_block_processing_lifecycle<T: SparseTrie + Default>() {
|
||||
pub(super) fn test_full_block_processing_lifecycle<T: SparseTrie>(new_trie: fn() -> T) {
|
||||
// --- Setup: Build account trie with 5 accounts ---
|
||||
// A1 storage: 5 slots, A2 storage: 3 slots, A3-A5: empty storage.
|
||||
// Account trie leaf values = RLP-encoded storage roots.
|
||||
@@ -318,9 +318,9 @@ pub(super) fn test_full_block_processing_lifecycle<T: SparseTrie + Default>() {
|
||||
let mut acct_harness = SuiteTestHarness::new(acct_storage.clone());
|
||||
|
||||
// Initialize all tries fully revealed with update tracking.
|
||||
let mut a1_trie: T = a1_harness.init_trie_fully_revealed(true);
|
||||
let mut a2_trie: T = a2_harness.init_trie_fully_revealed(true);
|
||||
let mut acct_trie: T = acct_harness.init_trie_fully_revealed(true);
|
||||
let mut a1_trie: T = a1_harness.init_trie_fully_revealed(true, new_trie);
|
||||
let mut a2_trie: T = a2_harness.init_trie_fully_revealed(true, new_trie);
|
||||
let mut acct_trie: T = acct_harness.init_trie_fully_revealed(true, new_trie);
|
||||
|
||||
// Cache initial hashes for all tries.
|
||||
let _ = a1_trie.root();
|
||||
@@ -406,7 +406,7 @@ pub(super) fn test_full_block_processing_lifecycle<T: SparseTrie + Default>() {
|
||||
/// `Touched` is used to prewarm accounts/slots before actual state changes arrive.
|
||||
/// When the real `Changed` update arrives, it overwrites the `Touched` entry.
|
||||
/// This test verifies that prewarming followed by mutation works correctly.
|
||||
pub(super) fn test_touched_prewarm_then_changed_update<T: SparseTrie + Default>() {
|
||||
pub(super) fn test_touched_prewarm_then_changed_update<T: SparseTrie>(new_trie: fn() -> T) {
|
||||
let key1 = B256::with_last_byte(0x10);
|
||||
let key2 = B256::with_last_byte(0x20);
|
||||
let key3 = B256::with_last_byte(0x30);
|
||||
@@ -424,7 +424,7 @@ pub(super) fn test_touched_prewarm_then_changed_update<T: SparseTrie + Default>(
|
||||
.collect();
|
||||
|
||||
let mut harness = SuiteTestHarness::new(base_storage);
|
||||
let mut trie: T = harness.init_trie_fully_revealed(false);
|
||||
let mut trie: T = harness.init_trie_fully_revealed(false, new_trie);
|
||||
|
||||
// Step 1: Prewarm 3 keys with Touched — all should be drained (paths are revealed).
|
||||
let mut leaf_updates: B256Map<LeafUpdate> =
|
||||
@@ -468,9 +468,9 @@ pub(super) fn test_touched_prewarm_then_changed_update<T: SparseTrie + Default>(
|
||||
/// A `Touched` update hits a blinded node, triggering a proof request. After the proof is
|
||||
/// revealed, a `Changed` update for the same key succeeds. This is the prewarm-miss →
|
||||
/// reveal → update sequence.
|
||||
pub(super) fn test_touched_on_blinded_triggers_proof_then_changed_succeeds<
|
||||
T: SparseTrie + Default,
|
||||
>() {
|
||||
pub(super) fn test_touched_on_blinded_triggers_proof_then_changed_succeeds<T: SparseTrie>(
|
||||
new_trie: fn() -> T,
|
||||
) {
|
||||
// Two groups of 16 keys each to create blinded subtries.
|
||||
let mut base_storage = BTreeMap::new();
|
||||
|
||||
@@ -493,7 +493,7 @@ pub(super) fn test_touched_on_blinded_triggers_proof_then_changed_succeeds<
|
||||
let mut harness = SuiteTestHarness::new(base_storage);
|
||||
|
||||
// Reveal only group A — group B's subtrie is blinded.
|
||||
let mut trie: T = harness.init_trie_with_targets(&group_a_keys, false);
|
||||
let mut trie: T = harness.init_trie_with_targets(&group_a_keys, false, new_trie);
|
||||
|
||||
// Step 1: Touched on a key in group B's blinded subtrie → callback fires.
|
||||
let target_key = group_b_keys[0];
|
||||
@@ -540,7 +540,7 @@ pub(super) fn test_touched_on_blinded_triggers_proof_then_changed_succeeds<
|
||||
/// Simulates the `SparseStateTrie::update_account` pattern: read existing leaf via
|
||||
/// `get_leaf_value`, decode, modify (change one field while preserving another), re-encode,
|
||||
/// and update. Verifies that root matches reference.
|
||||
pub(super) fn test_get_leaf_value_for_storage_root_lookup<T: SparseTrie + Default>() {
|
||||
pub(super) fn test_get_leaf_value_for_storage_root_lookup<T: SparseTrie>(new_trie: fn() -> T) {
|
||||
let key1 = B256::with_last_byte(0x10);
|
||||
let key2 = B256::with_last_byte(0x20);
|
||||
let key3 = B256::with_last_byte(0x30);
|
||||
@@ -551,7 +551,7 @@ pub(super) fn test_get_leaf_value_for_storage_root_lookup<T: SparseTrie + Defaul
|
||||
.collect();
|
||||
|
||||
let mut harness = SuiteTestHarness::new(base_storage);
|
||||
let mut trie: T = harness.init_trie_fully_revealed(false);
|
||||
let mut trie: T = harness.init_trie_fully_revealed(false, new_trie);
|
||||
|
||||
// Step 1: Read existing leaf value via get_leaf_value.
|
||||
let key1_nibbles = Nibbles::unpack(key1);
|
||||
@@ -589,7 +589,7 @@ pub(super) fn test_get_leaf_value_for_storage_root_lookup<T: SparseTrie + Defaul
|
||||
|
||||
/// Before updating, check existence via `find_leaf`, then
|
||||
/// insert/modify, and verify `find_leaf` reflects the new state.
|
||||
pub(super) fn test_find_leaf_before_update_to_check_existence<T: SparseTrie + Default>() {
|
||||
pub(super) fn test_find_leaf_before_update_to_check_existence<T: SparseTrie>(new_trie: fn() -> T) {
|
||||
let key1 = B256::with_last_byte(0x10);
|
||||
let key2 = B256::with_last_byte(0x20);
|
||||
let key3 = B256::with_last_byte(0x30);
|
||||
@@ -599,7 +599,7 @@ pub(super) fn test_find_leaf_before_update_to_check_existence<T: SparseTrie + De
|
||||
[(key1, U256::from(1)), (key2, U256::from(2)), (key3, U256::from(3))].into_iter().collect();
|
||||
|
||||
let mut harness = SuiteTestHarness::new(base_storage);
|
||||
let mut trie: T = harness.init_trie_fully_revealed(false);
|
||||
let mut trie: T = harness.init_trie_fully_revealed(false, new_trie);
|
||||
|
||||
let key2_nibbles = Nibbles::unpack(key2);
|
||||
let nonexistent_nibbles = Nibbles::unpack(nonexistent_key);
|
||||
@@ -650,7 +650,7 @@ pub(super) fn test_find_leaf_before_update_to_check_existence<T: SparseTrie + De
|
||||
///
|
||||
/// Build 10-leaf trie, do Block 1 (update K1,K2,K3 → commit → prune retaining K1,K2),
|
||||
/// then Block 2: update K1 (hot, works immediately), update K5 (cold, needs re-reveal).
|
||||
pub(super) fn test_prune_then_reuse_for_next_block<T: SparseTrie + Default>() {
|
||||
pub(super) fn test_prune_then_reuse_for_next_block<T: SparseTrie>(new_trie: fn() -> T) {
|
||||
// Build a trie with 10 leaves.
|
||||
let mut storage: BTreeMap<B256, U256> = BTreeMap::new();
|
||||
let mut keys = Vec::new();
|
||||
@@ -662,7 +662,7 @@ pub(super) fn test_prune_then_reuse_for_next_block<T: SparseTrie + Default>() {
|
||||
}
|
||||
|
||||
let mut harness = SuiteTestHarness::new(storage.clone());
|
||||
let mut trie: T = harness.init_trie_fully_revealed(true);
|
||||
let mut trie: T = harness.init_trie_fully_revealed(true, new_trie);
|
||||
|
||||
// Cache initial hashes.
|
||||
let _ = trie.root();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//! Generic `SparseTrie` test suite.
|
||||
//!
|
||||
//! Tests are written as generic functions `test_foo<T: SparseTrie + Default>()` and stamped out
|
||||
//! for every concrete implementation via the [`sparse_trie_tests`] macro.
|
||||
//! Tests are written as generic functions `test_foo<T: SparseTrie>(new_trie: fn() -> T)` and
|
||||
//! stamped out for every concrete implementation via the [`sparse_trie_tests`] macro.
|
||||
//!
|
||||
//! Tests are organized into modules by which `SparseTrie` method is the most likely root cause
|
||||
//! of failure for each test case:
|
||||
@@ -117,13 +117,14 @@ impl SuiteTestHarness {
|
||||
|
||||
/// Initializes a trie with the harness root node and reveals all proof nodes for the
|
||||
/// given target keys. Returns the initialized trie.
|
||||
fn init_trie_with_targets<T: SparseTrie + Default>(
|
||||
fn init_trie_with_targets<T: SparseTrie>(
|
||||
&self,
|
||||
target_keys: &[B256],
|
||||
retain_updates: bool,
|
||||
new_trie: fn() -> T,
|
||||
) -> T {
|
||||
let root_node = self.root_node();
|
||||
let mut trie = T::default();
|
||||
let mut trie = (new_trie)();
|
||||
trie.set_root(root_node.node, root_node.masks, retain_updates)
|
||||
.expect("set_root should succeed");
|
||||
|
||||
@@ -138,9 +139,13 @@ impl SuiteTestHarness {
|
||||
}
|
||||
|
||||
/// Initializes a trie and reveals proofs for all keys in the base storage.
|
||||
fn init_trie_fully_revealed<T: SparseTrie + Default>(&self, retain_updates: bool) -> T {
|
||||
fn init_trie_fully_revealed<T: SparseTrie>(
|
||||
&self,
|
||||
retain_updates: bool,
|
||||
new_trie: fn() -> T,
|
||||
) -> T {
|
||||
let keys: Vec<B256> = self.storage().keys().copied().collect();
|
||||
self.init_trie_with_targets(&keys, retain_updates)
|
||||
self.init_trie_with_targets(&keys, retain_updates, new_trie)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,7 +163,7 @@ macro_rules! sparse_trie_tests {
|
||||
$(
|
||||
#[test]
|
||||
fn $test_fn() {
|
||||
super::$test_fn::<ParallelSparseTrie>();
|
||||
super::$test_fn(ParallelSparseTrie::default);
|
||||
}
|
||||
)*
|
||||
}
|
||||
@@ -169,7 +174,27 @@ macro_rules! sparse_trie_tests {
|
||||
$(
|
||||
#[test]
|
||||
fn $test_fn() {
|
||||
super::$test_fn::<ArenaParallelSparseTrie>();
|
||||
super::$test_fn(ArenaParallelSparseTrie::default);
|
||||
}
|
||||
)*
|
||||
}
|
||||
|
||||
mod arena_parallel_sparse_trie_always_parallel {
|
||||
use reth_trie_sparse::{ArenaParallelSparseTrie, ArenaParallelismThresholds};
|
||||
|
||||
$(
|
||||
#[test]
|
||||
fn $test_fn() {
|
||||
super::$test_fn(|| {
|
||||
ArenaParallelSparseTrie::default().with_parallelism_thresholds(
|
||||
ArenaParallelismThresholds {
|
||||
min_dirty_leaves: 1,
|
||||
min_revealed_nodes: 1,
|
||||
min_updates: 1,
|
||||
min_leaves_for_prune: 1,
|
||||
},
|
||||
)
|
||||
});
|
||||
}
|
||||
)*
|
||||
}
|
||||
@@ -245,6 +270,8 @@ sparse_trie_tests! {
|
||||
test_orphaned_value_update_falls_through_to_full_insertion,
|
||||
test_branch_collapse_updates_leaf_key_len_across_subtries,
|
||||
test_remove_leaf_does_not_reveal_blind_subtries,
|
||||
test_branch_collapse_multi_empty_subtries_blinded_remaining,
|
||||
test_subtrie_emptied_by_deletes_with_touched,
|
||||
|
||||
// root
|
||||
test_root_empty_trie,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use super::*;
|
||||
|
||||
pub(super) fn test_prune_retains_specified_leaves<T: SparseTrie + Default>() {
|
||||
pub(super) fn test_prune_retains_specified_leaves<T: SparseTrie>(new_trie: fn() -> T) {
|
||||
let mut key_a = B256::ZERO;
|
||||
key_a.0[0] = 0x10;
|
||||
let mut key_b = B256::ZERO;
|
||||
@@ -21,7 +21,7 @@ pub(super) fn test_prune_retains_specified_leaves<T: SparseTrie + Default>() {
|
||||
]);
|
||||
|
||||
let harness = SuiteTestHarness::new(storage);
|
||||
let mut trie: T = harness.init_trie_fully_revealed(false);
|
||||
let mut trie: T = harness.init_trie_fully_revealed(false, new_trie);
|
||||
|
||||
// Compute root before prune.
|
||||
let hash1 = trie.root();
|
||||
@@ -47,7 +47,7 @@ pub(super) fn test_prune_retains_specified_leaves<T: SparseTrie + Default>() {
|
||||
/// Build a trie with 10+ leaves spread across multiple subtries, fully reveal
|
||||
/// and compute root. Then prune retaining only 1 leaf. `size_hint()` must
|
||||
/// decrease and `prune` must return > 0.
|
||||
pub(super) fn test_prune_reduces_node_count<T: SparseTrie + Default>() {
|
||||
pub(super) fn test_prune_reduces_node_count<T: SparseTrie>(new_trie: fn() -> T) {
|
||||
// Create 16 keys with different first nibbles to spread across subtries.
|
||||
let keys: Vec<B256> = (0u8..16)
|
||||
.map(|i| {
|
||||
@@ -61,7 +61,7 @@ pub(super) fn test_prune_reduces_node_count<T: SparseTrie + Default>() {
|
||||
keys.iter().enumerate().map(|(i, k)| (*k, U256::from(i + 1))).collect();
|
||||
|
||||
let harness = SuiteTestHarness::new(storage);
|
||||
let mut trie: T = harness.init_trie_fully_revealed(false);
|
||||
let mut trie: T = harness.init_trie_fully_revealed(false, new_trie);
|
||||
|
||||
// Compute root to cache hashes (required for pruning).
|
||||
let _root = trie.root();
|
||||
@@ -83,7 +83,7 @@ pub(super) fn test_prune_reduces_node_count<T: SparseTrie + Default>() {
|
||||
|
||||
/// Pruning with an empty retained set should convert all subtrees to
|
||||
/// hash stubs (maximum pruning). Root hash must be unchanged.
|
||||
pub(super) fn test_prune_empty_retained_set<T: SparseTrie + Default>() {
|
||||
pub(super) fn test_prune_empty_retained_set<T: SparseTrie>(new_trie: fn() -> T) {
|
||||
let keys: Vec<B256> = (0u8..16)
|
||||
.map(|i| {
|
||||
let mut k = B256::ZERO;
|
||||
@@ -96,7 +96,7 @@ pub(super) fn test_prune_empty_retained_set<T: SparseTrie + Default>() {
|
||||
keys.iter().enumerate().map(|(i, k)| (*k, U256::from(i + 1))).collect();
|
||||
|
||||
let harness = SuiteTestHarness::new(storage);
|
||||
let mut trie: T = harness.init_trie_fully_revealed(false);
|
||||
let mut trie: T = harness.init_trie_fully_revealed(false, new_trie);
|
||||
|
||||
let hash_before = trie.root();
|
||||
|
||||
@@ -116,7 +116,7 @@ pub(super) fn test_prune_empty_retained_set<T: SparseTrie + Default>() {
|
||||
);
|
||||
}
|
||||
|
||||
pub(super) fn test_prune_requires_computed_hashes<T: SparseTrie + Default>() {
|
||||
pub(super) fn test_prune_requires_computed_hashes<T: SparseTrie>(new_trie: fn() -> T) {
|
||||
let keys: Vec<B256> = (0u8..5)
|
||||
.map(|i| {
|
||||
let mut k = B256::ZERO;
|
||||
@@ -129,7 +129,7 @@ pub(super) fn test_prune_requires_computed_hashes<T: SparseTrie + Default>() {
|
||||
keys.iter().enumerate().map(|(i, k)| (*k, U256::from(i + 1))).collect();
|
||||
|
||||
let harness = SuiteTestHarness::new(storage);
|
||||
let mut trie: T = harness.init_trie_fully_revealed(false);
|
||||
let mut trie: T = harness.init_trie_fully_revealed(false, new_trie);
|
||||
|
||||
// Dirty the trie by updating a leaf — do NOT call root() to compute hashes.
|
||||
let mut leaf_updates: B256Map<LeafUpdate> = B256Map::default();
|
||||
@@ -142,7 +142,7 @@ pub(super) fn test_prune_requires_computed_hashes<T: SparseTrie + Default>() {
|
||||
|
||||
// Compare against pruning after root() is called (clean state).
|
||||
// With dirty nodes, pruning is limited because dirty subtrees lack cached hashes.
|
||||
let mut trie_clean: T = harness.init_trie_fully_revealed(false);
|
||||
let mut trie_clean: T = harness.init_trie_fully_revealed(false, new_trie);
|
||||
trie_clean.root();
|
||||
let clean_pruned = trie_clean.prune(&retained);
|
||||
|
||||
@@ -152,7 +152,7 @@ pub(super) fn test_prune_requires_computed_hashes<T: SparseTrie + Default>() {
|
||||
);
|
||||
}
|
||||
|
||||
pub(super) fn test_prune_then_update_and_recompute_root<T: SparseTrie + Default>() {
|
||||
pub(super) fn test_prune_then_update_and_recompute_root<T: SparseTrie>(new_trie: fn() -> T) {
|
||||
let keys: Vec<B256> = (0u8..5)
|
||||
.map(|i| {
|
||||
let mut k = B256::ZERO;
|
||||
@@ -165,7 +165,7 @@ pub(super) fn test_prune_then_update_and_recompute_root<T: SparseTrie + Default>
|
||||
keys.iter().enumerate().map(|(i, k)| (*k, U256::from(i + 1))).collect();
|
||||
|
||||
let harness = SuiteTestHarness::new(storage.clone());
|
||||
let mut trie: T = harness.init_trie_fully_revealed(false);
|
||||
let mut trie: T = harness.init_trie_fully_revealed(false, new_trie);
|
||||
|
||||
trie.root();
|
||||
|
||||
@@ -187,7 +187,7 @@ pub(super) fn test_prune_then_update_and_recompute_root<T: SparseTrie + Default>
|
||||
assert_eq!(root_after, expected_root, "root after prune + update should match reference trie");
|
||||
}
|
||||
|
||||
pub(super) fn test_prune_then_reveal_pruned_subtree<T: SparseTrie + Default>() {
|
||||
pub(super) fn test_prune_then_reveal_pruned_subtree<T: SparseTrie>(new_trie: fn() -> T) {
|
||||
let keys: Vec<B256> = (0u8..5)
|
||||
.map(|i| {
|
||||
let mut k = B256::ZERO;
|
||||
@@ -200,7 +200,7 @@ pub(super) fn test_prune_then_reveal_pruned_subtree<T: SparseTrie + Default>() {
|
||||
keys.iter().enumerate().map(|(i, k)| (*k, U256::from(i + 1))).collect();
|
||||
|
||||
let harness = SuiteTestHarness::new(storage.clone());
|
||||
let mut trie: T = harness.init_trie_fully_revealed(false);
|
||||
let mut trie: T = harness.init_trie_fully_revealed(false, new_trie);
|
||||
|
||||
trie.root();
|
||||
|
||||
@@ -227,7 +227,7 @@ pub(super) fn test_prune_then_reveal_pruned_subtree<T: SparseTrie + Default>() {
|
||||
|
||||
/// Pruning a trie with both large (hashed) and small (embedded) node values
|
||||
/// should preserve the root hash.
|
||||
pub(super) fn test_prune_mixed_embedded_and_hashed_nodes<T: SparseTrie + Default>() {
|
||||
pub(super) fn test_prune_mixed_embedded_and_hashed_nodes<T: SparseTrie>(new_trie: fn() -> T) {
|
||||
let mut storage = BTreeMap::new();
|
||||
|
||||
// 4 keys with large values (produce hashed nodes: RLP ≥ 32 bytes)
|
||||
@@ -243,7 +243,7 @@ pub(super) fn test_prune_mixed_embedded_and_hashed_nodes<T: SparseTrie + Default
|
||||
storage.insert(key, U256::from(1));
|
||||
}
|
||||
|
||||
let mut trie = T::default();
|
||||
let mut trie = (new_trie)();
|
||||
let mut leaf_updates = SuiteTestHarness::leaf_updates(&storage);
|
||||
trie.update_leaves(&mut leaf_updates, |_, _| {
|
||||
panic!("no proof callback expected on empty trie");
|
||||
@@ -259,7 +259,7 @@ pub(super) fn test_prune_mixed_embedded_and_hashed_nodes<T: SparseTrie + Default
|
||||
|
||||
/// After pruning, inserting a new leaf at a
|
||||
/// previously-unrevealed path should not panic.
|
||||
pub(super) fn test_prune_then_update_no_panic<T: SparseTrie + Default>() {
|
||||
pub(super) fn test_prune_then_update_no_panic<T: SparseTrie>(new_trie: fn() -> T) {
|
||||
// Build a trie with 64 leaves (16 keys × 4 first-nibble groups).
|
||||
let mut storage = BTreeMap::new();
|
||||
for group in 0..4u8 {
|
||||
@@ -271,7 +271,7 @@ pub(super) fn test_prune_then_update_no_panic<T: SparseTrie + Default>() {
|
||||
}
|
||||
|
||||
let harness = SuiteTestHarness::new(storage.clone());
|
||||
let mut trie: T = harness.init_trie_fully_revealed(false);
|
||||
let mut trie: T = harness.init_trie_fully_revealed(false, new_trie);
|
||||
|
||||
let root_before_prune = trie.root();
|
||||
|
||||
@@ -297,19 +297,19 @@ pub(super) fn test_prune_then_update_no_panic<T: SparseTrie + Default>() {
|
||||
|
||||
/// When the root is not a branch (e.g., a single
|
||||
/// leaf or empty root), `prune` should immediately return 0 without walking.
|
||||
pub(super) fn test_prune_only_descends_into_branch_root<T: SparseTrie + Default>() {
|
||||
pub(super) fn test_prune_only_descends_into_branch_root<T: SparseTrie>(new_trie: fn() -> T) {
|
||||
// Single-leaf trie: root is a leaf node, not a branch.
|
||||
let storage: BTreeMap<B256, U256> =
|
||||
BTreeMap::from([(B256::with_last_byte(0x10), U256::from(1))]);
|
||||
let harness = SuiteTestHarness::new(storage);
|
||||
let mut trie: T = harness.init_trie_fully_revealed(false);
|
||||
let mut trie: T = harness.init_trie_fully_revealed(false, new_trie);
|
||||
|
||||
let _root = trie.root();
|
||||
let pruned = trie.prune(&[]);
|
||||
assert_eq!(pruned, 0, "non-branch root should not prune any nodes");
|
||||
|
||||
// Empty root: also not a branch.
|
||||
let mut empty_trie = T::default();
|
||||
let mut empty_trie = (new_trie)();
|
||||
let pruned_empty = empty_trie.prune(&[]);
|
||||
assert_eq!(pruned_empty, 0, "empty root should not prune any nodes");
|
||||
}
|
||||
@@ -317,7 +317,7 @@ pub(super) fn test_prune_only_descends_into_branch_root<T: SparseTrie + Default>
|
||||
/// Small subtrie root nodes (RLP < 32 bytes) are
|
||||
/// handled correctly during prune. After `root()` + `prune()`, a subsequent `root()`
|
||||
/// still returns the same hash.
|
||||
pub(super) fn test_prune_handles_small_subtrie_root_nodes<T: SparseTrie + Default>() {
|
||||
pub(super) fn test_prune_handles_small_subtrie_root_nodes<T: SparseTrie>(new_trie: fn() -> T) {
|
||||
// Build a trie with two groups of leaves to create a branch root with mixed
|
||||
// subtrie sizes:
|
||||
// - Group A (nibble 0x1): 16 leaves with large values → hashable subtrie root (RLP ≥ 32 bytes)
|
||||
@@ -335,7 +335,7 @@ pub(super) fn test_prune_handles_small_subtrie_root_nodes<T: SparseTrie + Defaul
|
||||
storage.insert(small_key, U256::from(1));
|
||||
|
||||
let harness = SuiteTestHarness::new(storage);
|
||||
let mut trie: T = harness.init_trie_fully_revealed(false);
|
||||
let mut trie: T = harness.init_trie_fully_revealed(false, new_trie);
|
||||
|
||||
let root_before = trie.root();
|
||||
assert_eq!(root_before, harness.original_root());
|
||||
|
||||
@@ -4,7 +4,7 @@ use super::*;
|
||||
///
|
||||
/// Calling `reveal_nodes` with an empty slice should return `Ok(())` and leave
|
||||
/// the trie state unchanged.
|
||||
pub(super) fn test_reveal_nodes_empty_slice<T: SparseTrie + Default>() {
|
||||
pub(super) fn test_reveal_nodes_empty_slice<T: SparseTrie>(new_trie: fn() -> T) {
|
||||
// Set up a trie with a root node.
|
||||
let mut key_a = B256::ZERO;
|
||||
key_a.0[0] = 0x10;
|
||||
@@ -15,7 +15,7 @@ pub(super) fn test_reveal_nodes_empty_slice<T: SparseTrie + Default>() {
|
||||
|
||||
let harness = SuiteTestHarness::new(storage);
|
||||
let root_node = harness.root_node();
|
||||
let mut trie = T::default();
|
||||
let mut trie = (new_trie)();
|
||||
trie.set_root(root_node.node, root_node.masks, true).expect("set_root should succeed");
|
||||
|
||||
let root_before = trie.root();
|
||||
@@ -31,7 +31,7 @@ pub(super) fn test_reveal_nodes_empty_slice<T: SparseTrie + Default>() {
|
||||
///
|
||||
/// Revealing a single leaf node within a branch should make it accessible and
|
||||
/// produce correct root hashes.
|
||||
pub(super) fn test_reveal_nodes_single_leaf<T: SparseTrie + Default>() {
|
||||
pub(super) fn test_reveal_nodes_single_leaf<T: SparseTrie>(new_trie: fn() -> T) {
|
||||
let mut key_a = B256::ZERO;
|
||||
key_a.0[0] = 0x10;
|
||||
let mut key_b = B256::ZERO;
|
||||
@@ -44,7 +44,7 @@ pub(super) fn test_reveal_nodes_single_leaf<T: SparseTrie + Default>() {
|
||||
let harness = SuiteTestHarness::new(storage);
|
||||
|
||||
// Set root and reveal only one leaf's proof.
|
||||
let mut trie: T = harness.init_trie_with_targets(&[key_a], true);
|
||||
let mut trie: T = harness.init_trie_with_targets(&[key_a], true, new_trie);
|
||||
let root = trie.root();
|
||||
assert_eq!(root, harness.original_root());
|
||||
}
|
||||
@@ -53,7 +53,7 @@ pub(super) fn test_reveal_nodes_single_leaf<T: SparseTrie + Default>() {
|
||||
///
|
||||
/// Revealing the same proof nodes twice should not corrupt the trie or change
|
||||
/// the root hash. The second reveal is a no-op.
|
||||
pub(super) fn test_reveal_nodes_idempotent<T: SparseTrie + Default>() {
|
||||
pub(super) fn test_reveal_nodes_idempotent<T: SparseTrie>(new_trie: fn() -> T) {
|
||||
let mut key_a = B256::ZERO;
|
||||
key_a.0[0] = 0x10;
|
||||
let mut key_b = B256::ZERO;
|
||||
@@ -66,7 +66,7 @@ pub(super) fn test_reveal_nodes_idempotent<T: SparseTrie + Default>() {
|
||||
let harness = SuiteTestHarness::new(storage);
|
||||
|
||||
// First reveal: set root and reveal all proof nodes.
|
||||
let mut trie: T = harness.init_trie_fully_revealed(true);
|
||||
let mut trie: T = harness.init_trie_fully_revealed(true, new_trie);
|
||||
let root_first = trie.root();
|
||||
assert_eq!(root_first, harness.original_root());
|
||||
|
||||
@@ -85,7 +85,7 @@ pub(super) fn test_reveal_nodes_idempotent<T: SparseTrie + Default>() {
|
||||
/// Branch node masks provided during reveal should be stored and used for update tracking.
|
||||
/// After modifying a leaf and computing the root, `take_updates()` should contain entries
|
||||
/// reflecting which branch nodes were updated vs removed, guided by the stored masks.
|
||||
pub(super) fn test_reveal_nodes_with_branch_masks<T: SparseTrie + Default>() {
|
||||
pub(super) fn test_reveal_nodes_with_branch_masks<T: SparseTrie>(new_trie: fn() -> T) {
|
||||
// Build a trie with 16 leaves sharing first nibble 0x1 to produce non-root branch nodes
|
||||
// with hashed children (needed for masks to produce InsertUpdated actions).
|
||||
let mut storage: BTreeMap<B256, U256> = BTreeMap::new();
|
||||
@@ -99,7 +99,7 @@ pub(super) fn test_reveal_nodes_with_branch_masks<T: SparseTrie + Default>() {
|
||||
let harness = SuiteTestHarness::new(storage);
|
||||
|
||||
// Initialize trie with masks (from proofs) and retain_updates=true.
|
||||
let mut trie: T = harness.init_trie_fully_revealed(true);
|
||||
let mut trie: T = harness.init_trie_fully_revealed(true, new_trie);
|
||||
|
||||
// Compute root to cache initial branch hashes.
|
||||
let _ = trie.root();
|
||||
@@ -129,7 +129,7 @@ pub(super) fn test_reveal_nodes_with_branch_masks<T: SparseTrie + Default>() {
|
||||
///
|
||||
/// Calling `reveal_nodes` when the root is `EmptyRoot` should return `Ok(())` without
|
||||
/// modifying trie state, even when non-empty proof nodes are provided.
|
||||
pub(super) fn test_reveal_nodes_skips_on_empty_root<T: SparseTrie + Default>() {
|
||||
pub(super) fn test_reveal_nodes_skips_on_empty_root<T: SparseTrie>(new_trie: fn() -> T) {
|
||||
// Build a harness with real data so we can obtain non-trivial proof nodes.
|
||||
let storage: BTreeMap<B256, U256> = BTreeMap::from([
|
||||
(B256::with_last_byte(1), U256::from(10)),
|
||||
@@ -142,7 +142,7 @@ pub(super) fn test_reveal_nodes_skips_on_empty_root<T: SparseTrie + Default>() {
|
||||
let (mut proof_nodes, _) = harness.proof_v2(&mut targets);
|
||||
|
||||
// Create a trie with an empty root.
|
||||
let mut trie = T::default();
|
||||
let mut trie = (new_trie)();
|
||||
trie.set_root(TrieNodeV2::EmptyRoot, None, true).expect("set_root EmptyRoot should succeed");
|
||||
|
||||
// Reveal non-empty proof nodes — should be a no-op on an empty root.
|
||||
@@ -161,7 +161,9 @@ pub(super) fn test_reveal_nodes_skips_on_empty_root<T: SparseTrie + Default>() {
|
||||
/// When `reveal_nodes` receives proof nodes that include entries not reachable from the
|
||||
/// current trie root (e.g., boundary leaves for unrelated subtries), those nodes should
|
||||
/// be silently skipped without corrupting state.
|
||||
pub(super) fn test_reveal_nodes_filters_unreachable_boundary_leaves<T: SparseTrie + Default>() {
|
||||
pub(super) fn test_reveal_nodes_filters_unreachable_boundary_leaves<T: SparseTrie>(
|
||||
new_trie: fn() -> T,
|
||||
) {
|
||||
// Create a trie with two groups of keys under different first nibbles.
|
||||
// Group A: 3 keys under nibble 0x1
|
||||
// Group B: 3 keys under nibble 0x2
|
||||
@@ -192,7 +194,7 @@ pub(super) fn test_reveal_nodes_filters_unreachable_boundary_leaves<T: SparseTri
|
||||
|
||||
// Initialize trie with root and reveal ONLY group A keys.
|
||||
let root_node = harness.root_node();
|
||||
let mut trie = T::default();
|
||||
let mut trie = (new_trie)();
|
||||
trie.set_root(root_node.node, root_node.masks, false).expect("set_root should succeed");
|
||||
|
||||
let mut targets_a: Vec<ProofV2Target> = keys_a.iter().map(|k| ProofV2Target::new(*k)).collect();
|
||||
@@ -237,7 +239,7 @@ pub(super) fn test_reveal_nodes_filters_unreachable_boundary_leaves<T: SparseTri
|
||||
/// When proofs from a 2-leaf trie are revealed, then a 3rd leaf is inserted, then another
|
||||
/// proof from the original 2-leaf trie is revealed, the branch node should not be overwritten
|
||||
/// by the stale proof. The root must match a reference trie with all 3 keys.
|
||||
pub(super) fn test_reveal_insert_reveal_preserves_branch_state<T: SparseTrie + Default>() {
|
||||
pub(super) fn test_reveal_insert_reveal_preserves_branch_state<T: SparseTrie>(new_trie: fn() -> T) {
|
||||
// Two original keys and one to insert.
|
||||
let key_a = B256::with_last_byte(0x00);
|
||||
let key_b = B256::with_last_byte(0x01);
|
||||
@@ -249,7 +251,7 @@ pub(super) fn test_reveal_insert_reveal_preserves_branch_state<T: SparseTrie + D
|
||||
let harness = SuiteTestHarness::new(original_storage);
|
||||
|
||||
// Initialize trie with root, reveal proof for key_a only.
|
||||
let mut trie: T = harness.init_trie_with_targets(&[key_a], false);
|
||||
let mut trie: T = harness.init_trie_with_targets(&[key_a], false, new_trie);
|
||||
|
||||
// Insert key_b via update_leaves.
|
||||
let insert_value = U256::from(2);
|
||||
@@ -277,7 +279,9 @@ pub(super) fn test_reveal_insert_reveal_preserves_branch_state<T: SparseTrie + D
|
||||
/// After removing a leaf that collapses a branch into an
|
||||
/// extension, revealing a stale proof (which had a branch at root) should not overwrite the
|
||||
/// extension node.
|
||||
pub(super) fn test_remove_then_reveal_does_not_overwrite_collapsed_node<T: SparseTrie + Default>() {
|
||||
pub(super) fn test_remove_then_reveal_does_not_overwrite_collapsed_node<T: SparseTrie>(
|
||||
new_trie: fn() -> T,
|
||||
) {
|
||||
// Nibbles [0,0,..], [1,1,..], [1,2,..] — root branch has children at nibbles 0 and 1.
|
||||
// Packed into B256 keys: byte 0x00 → nibbles [0,0], byte 0x11 → nibbles [1,1], etc.
|
||||
let key_a = {
|
||||
@@ -302,7 +306,7 @@ pub(super) fn test_remove_then_reveal_does_not_overwrite_collapsed_node<T: Spars
|
||||
let harness = SuiteTestHarness::new(original_storage);
|
||||
|
||||
// Initialize trie with root and reveal proofs for all keys.
|
||||
let mut trie: T = harness.init_trie_with_targets(&[key_a, key_b, key_c], false);
|
||||
let mut trie: T = harness.init_trie_with_targets(&[key_a, key_b, key_c], false, new_trie);
|
||||
|
||||
// Remove key_a (0x0000..) — should collapse root branch into extension (shared prefix 0x01).
|
||||
let removals: BTreeMap<B256, U256> = BTreeMap::from([(key_a, U256::ZERO)]);
|
||||
@@ -329,7 +333,9 @@ pub(super) fn test_remove_then_reveal_does_not_overwrite_collapsed_node<T: Spars
|
||||
/// After inserting a leaf that converts an extension root into
|
||||
/// a branch, revealing a stale proof from the original trie (which has an extension at root)
|
||||
/// should not overwrite the branch.
|
||||
pub(super) fn test_insert_then_reveal_does_not_overwrite_branch<T: SparseTrie + Default>() {
|
||||
pub(super) fn test_insert_then_reveal_does_not_overwrite_branch<T: SparseTrie>(
|
||||
new_trie: fn() -> T,
|
||||
) {
|
||||
// Original trie: keys 0x0001.. and 0x0002.. share prefix 0x00 → extension root.
|
||||
let key_a = {
|
||||
let mut k = B256::ZERO;
|
||||
@@ -350,7 +356,7 @@ pub(super) fn test_insert_then_reveal_does_not_overwrite_branch<T: SparseTrie +
|
||||
let harness = SuiteTestHarness::new(original_storage);
|
||||
|
||||
// Initialize trie with root, reveal all proofs.
|
||||
let mut trie: T = harness.init_trie_with_targets(&[key_a, key_b], false);
|
||||
let mut trie: T = harness.init_trie_with_targets(&[key_a, key_b], false, new_trie);
|
||||
|
||||
// Insert key_c at 0x0100.. — different first nibble, forces extension→branch conversion.
|
||||
let key_c = {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use super::*;
|
||||
|
||||
/// Calling `root()` on a fresh, empty trie returns `EMPTY_ROOT_HASH`.
|
||||
pub(super) fn test_root_empty_trie<T: SparseTrie + Default>() {
|
||||
let mut trie = T::default();
|
||||
pub(super) fn test_root_empty_trie<T: SparseTrie>(new_trie: fn() -> T) {
|
||||
let mut trie = (new_trie)();
|
||||
assert_eq!(trie.root(), EMPTY_ROOT_HASH, "empty trie should return EMPTY_ROOT_HASH");
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ pub(super) fn test_root_empty_trie<T: SparseTrie + Default>() {
|
||||
///
|
||||
/// After fully revealing and computing root once, calling `root()` again without
|
||||
/// mutations should return the same hash and `is_root_cached()` should be true.
|
||||
pub(super) fn test_root_cached_returns_without_recomputation<T: SparseTrie + Default>() {
|
||||
pub(super) fn test_root_cached_returns_without_recomputation<T: SparseTrie>(new_trie: fn() -> T) {
|
||||
let mut key_a = B256::ZERO;
|
||||
key_a.0[0] = 0x10;
|
||||
let mut key_b = B256::ZERO;
|
||||
@@ -21,7 +21,7 @@ pub(super) fn test_root_cached_returns_without_recomputation<T: SparseTrie + Def
|
||||
BTreeMap::from([(key_a, U256::from(1)), (key_b, U256::from(2)), (key_c, U256::from(3))]);
|
||||
|
||||
let harness = SuiteTestHarness::new(storage);
|
||||
let mut trie: T = harness.init_trie_fully_revealed(true);
|
||||
let mut trie: T = harness.init_trie_fully_revealed(true, new_trie);
|
||||
|
||||
let root1 = trie.root();
|
||||
assert_eq!(root1, harness.original_root(), "first root should match reference");
|
||||
@@ -36,7 +36,7 @@ pub(super) fn test_root_cached_returns_without_recomputation<T: SparseTrie + Def
|
||||
///
|
||||
/// After modifying one leaf's value, `root()` should differ from the original and
|
||||
/// match the reference trie with the updated value.
|
||||
pub(super) fn test_root_after_single_leaf_update<T: SparseTrie + Default>() {
|
||||
pub(super) fn test_root_after_single_leaf_update<T: SparseTrie>(new_trie: fn() -> T) {
|
||||
let mut key_a = B256::ZERO;
|
||||
key_a.0[0] = 0x10;
|
||||
let mut key_b = B256::ZERO;
|
||||
@@ -47,7 +47,7 @@ pub(super) fn test_root_after_single_leaf_update<T: SparseTrie + Default>() {
|
||||
BTreeMap::from([(key_a, U256::from(1)), (key_b, U256::from(2)), (key_c, U256::from(3))]);
|
||||
|
||||
let harness = SuiteTestHarness::new(storage);
|
||||
let mut trie: T = harness.init_trie_fully_revealed(false);
|
||||
let mut trie: T = harness.init_trie_fully_revealed(false, new_trie);
|
||||
|
||||
let original_root = trie.root();
|
||||
assert_eq!(original_root, harness.original_root(), "initial root should match reference");
|
||||
@@ -75,7 +75,7 @@ pub(super) fn test_root_after_single_leaf_update<T: SparseTrie + Default>() {
|
||||
///
|
||||
/// Two tries built from the same 5 key-value pairs inserted in different orders
|
||||
/// must produce the same root hash.
|
||||
pub(super) fn test_root_deterministic_across_update_orders<T: SparseTrie + Default>() {
|
||||
pub(super) fn test_root_deterministic_across_update_orders<T: SparseTrie>(new_trie: fn() -> T) {
|
||||
// Define 5 key-value pairs spread across different subtrie regions.
|
||||
let mut k1 = B256::ZERO;
|
||||
k1.0[0] = 0x10;
|
||||
@@ -99,7 +99,7 @@ pub(super) fn test_root_deterministic_across_update_orders<T: SparseTrie + Defau
|
||||
// Build trie A: insert keys in order 1,2,3,4,5.
|
||||
let order_a = [pairs[0], pairs[1], pairs[2], pairs[3], pairs[4]];
|
||||
let root_a = {
|
||||
let mut trie = T::default();
|
||||
let mut trie = (new_trie)();
|
||||
for (key, value) in &order_a {
|
||||
let mut leaf_updates =
|
||||
SuiteTestHarness::leaf_updates(&BTreeMap::from([(*key, *value)]));
|
||||
@@ -111,7 +111,7 @@ pub(super) fn test_root_deterministic_across_update_orders<T: SparseTrie + Defau
|
||||
// Build trie B: insert keys in order 5,3,1,4,2.
|
||||
let order_b = [pairs[4], pairs[2], pairs[0], pairs[3], pairs[1]];
|
||||
let root_b = {
|
||||
let mut trie = T::default();
|
||||
let mut trie = (new_trie)();
|
||||
for (key, value) in &order_b {
|
||||
let mut leaf_updates =
|
||||
SuiteTestHarness::leaf_updates(&BTreeMap::from([(*key, *value)]));
|
||||
@@ -132,7 +132,7 @@ pub(super) fn test_root_deterministic_across_update_orders<T: SparseTrie + Defau
|
||||
///
|
||||
/// When the root node's RLP encoding is smaller than 32 bytes, `root()` must still return the
|
||||
/// correct hash. A second `root()` call should use the cached result without panic.
|
||||
pub(super) fn test_root_handles_small_root_node_without_hash<T: SparseTrie + Default>() {
|
||||
pub(super) fn test_root_handles_small_root_node_without_hash<T: SparseTrie>(new_trie: fn() -> T) {
|
||||
// A single small leaf produces a root node whose RLP is < 32 bytes.
|
||||
let key = B256::with_last_byte(1);
|
||||
let value = U256::from(1);
|
||||
@@ -140,7 +140,7 @@ pub(super) fn test_root_handles_small_root_node_without_hash<T: SparseTrie + Def
|
||||
let storage: BTreeMap<B256, U256> = BTreeMap::from([(key, value)]);
|
||||
let harness = SuiteTestHarness::new(storage);
|
||||
|
||||
let mut trie: T = harness.init_trie_fully_revealed(false);
|
||||
let mut trie: T = harness.init_trie_fully_revealed(false, new_trie);
|
||||
|
||||
let root1 = trie.root();
|
||||
assert_eq!(root1, harness.original_root(), "first root() should match reference trie");
|
||||
|
||||
@@ -4,7 +4,7 @@ use super::*;
|
||||
///
|
||||
/// Two leaves whose first nibbles differ produce a branch root node.
|
||||
/// After `set_root` + `reveal_nodes`, `root()` must match the reference hash.
|
||||
pub(super) fn test_set_root_with_branch_node<T: SparseTrie + Default>() {
|
||||
pub(super) fn test_set_root_with_branch_node<T: SparseTrie>(new_trie: fn() -> T) {
|
||||
// Keys whose first nibbles differ → branch at root.
|
||||
let mut key_a = B256::ZERO;
|
||||
key_a.0[0] = 0x10; // first nibble = 1
|
||||
@@ -14,7 +14,7 @@ pub(super) fn test_set_root_with_branch_node<T: SparseTrie + Default>() {
|
||||
BTreeMap::from([(key_a, U256::from(100)), (key_b, U256::from(200))]);
|
||||
|
||||
let harness = SuiteTestHarness::new(storage);
|
||||
let mut trie: T = harness.init_trie_fully_revealed(true);
|
||||
let mut trie: T = harness.init_trie_fully_revealed(true, new_trie);
|
||||
let root = trie.root();
|
||||
assert_eq!(root, harness.original_root());
|
||||
}
|
||||
@@ -23,12 +23,12 @@ pub(super) fn test_set_root_with_branch_node<T: SparseTrie + Default>() {
|
||||
///
|
||||
/// A single key-value pair produces a leaf root node. After `set_root` + `root()`,
|
||||
/// the hash must match the reference trie.
|
||||
pub(super) fn test_set_root_with_leaf_node<T: SparseTrie + Default>() {
|
||||
pub(super) fn test_set_root_with_leaf_node<T: SparseTrie>(new_trie: fn() -> T) {
|
||||
let storage: BTreeMap<B256, U256> = BTreeMap::from([(B256::ZERO, U256::from(42))]);
|
||||
|
||||
let harness = SuiteTestHarness::new(storage);
|
||||
let root_node = harness.root_node();
|
||||
let mut trie = T::default();
|
||||
let mut trie = (new_trie)();
|
||||
trie.set_root(root_node.node, root_node.masks, true).expect("set_root should succeed");
|
||||
let root = trie.root();
|
||||
assert_eq!(root, harness.original_root());
|
||||
@@ -38,7 +38,7 @@ pub(super) fn test_set_root_with_leaf_node<T: SparseTrie + Default>() {
|
||||
///
|
||||
/// Two keys sharing a long common prefix produce an extension root node.
|
||||
/// After `set_root` + `reveal_nodes`, `root()` must match the reference hash.
|
||||
pub(super) fn test_set_root_with_extension_node<T: SparseTrie + Default>() {
|
||||
pub(super) fn test_set_root_with_extension_node<T: SparseTrie>(new_trie: fn() -> T) {
|
||||
// Keys that share first byte 0xAB → extension root.
|
||||
let mut key_a = B256::ZERO;
|
||||
key_a.0[0] = 0xAB;
|
||||
@@ -49,7 +49,7 @@ pub(super) fn test_set_root_with_extension_node<T: SparseTrie + Default>() {
|
||||
BTreeMap::from([(key_a, U256::from(100)), (key_b, U256::from(200))]);
|
||||
|
||||
let harness = SuiteTestHarness::new(storage);
|
||||
let mut trie: T = harness.init_trie_fully_revealed(true);
|
||||
let mut trie: T = harness.init_trie_fully_revealed(true, new_trie);
|
||||
let root = trie.root();
|
||||
assert_eq!(root, harness.original_root());
|
||||
}
|
||||
@@ -58,7 +58,7 @@ pub(super) fn test_set_root_with_extension_node<T: SparseTrie + Default>() {
|
||||
///
|
||||
/// When `set_root` is called with `retain_updates = true`, subsequent mutations
|
||||
/// should be tracked and `take_updates()` should return non-empty results.
|
||||
pub(super) fn test_set_root_retains_updates_when_requested<T: SparseTrie + Default>() {
|
||||
pub(super) fn test_set_root_retains_updates_when_requested<T: SparseTrie>(new_trie: fn() -> T) {
|
||||
// Build a trie with enough leaves to produce non-root branch nodes with hash children.
|
||||
// We need leaves sharing a prefix nibble so that intermediate branch nodes are created,
|
||||
// and enough entries that children are hashed (RLP ≥ 32 bytes).
|
||||
@@ -71,7 +71,7 @@ pub(super) fn test_set_root_retains_updates_when_requested<T: SparseTrie + Defau
|
||||
}
|
||||
|
||||
let harness = SuiteTestHarness::new(storage);
|
||||
let mut trie: T = harness.init_trie_fully_revealed(true);
|
||||
let mut trie: T = harness.init_trie_fully_revealed(true, new_trie);
|
||||
|
||||
// Compute root once so branch hashes are cached.
|
||||
let _ = trie.root();
|
||||
@@ -101,7 +101,9 @@ pub(super) fn test_set_root_retains_updates_when_requested<T: SparseTrie + Defau
|
||||
///
|
||||
/// When `set_root` is called with `retain_updates = false`, `take_updates()` should
|
||||
/// return an empty `SparseTrieUpdates` even after mutations.
|
||||
pub(super) fn test_set_root_does_not_retain_updates_when_not_requested<T: SparseTrie + Default>() {
|
||||
pub(super) fn test_set_root_does_not_retain_updates_when_not_requested<T: SparseTrie>(
|
||||
new_trie: fn() -> T,
|
||||
) {
|
||||
let mut key_a = B256::ZERO;
|
||||
key_a.0[0] = 0x10;
|
||||
let mut key_b = B256::ZERO;
|
||||
@@ -113,7 +115,7 @@ pub(super) fn test_set_root_does_not_retain_updates_when_not_requested<T: Sparse
|
||||
|
||||
let harness = SuiteTestHarness::new(storage);
|
||||
// retain_updates = false
|
||||
let mut trie: T = harness.init_trie_fully_revealed(false);
|
||||
let mut trie: T = harness.init_trie_fully_revealed(false, new_trie);
|
||||
|
||||
// Modify a leaf.
|
||||
let changeset: BTreeMap<B256, U256> = BTreeMap::from([(key_a, U256::from(99))]);
|
||||
@@ -135,8 +137,8 @@ pub(super) fn test_set_root_does_not_retain_updates_when_not_requested<T: Sparse
|
||||
///
|
||||
/// Setting the root to `TrieNodeV2::EmptyRoot` should leave the trie in its initial
|
||||
/// empty state, returning `EMPTY_ROOT_HASH` from `root()`.
|
||||
pub(super) fn test_set_root_with_empty_root<T: SparseTrie + Default>() {
|
||||
let mut trie = T::default();
|
||||
pub(super) fn test_set_root_with_empty_root<T: SparseTrie>(new_trie: fn() -> T) {
|
||||
let mut trie = (new_trie)();
|
||||
trie.set_root(TrieNodeV2::EmptyRoot, None, true).expect("set_root should succeed");
|
||||
assert_eq!(trie.root(), EMPTY_ROOT_HASH);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ use super::*;
|
||||
/// Builds a 5-leaf trie, records `size_hint`, adds 2 leaves, records again,
|
||||
/// removes 1 leaf, records again. Asserts s2 > s1 and s3 < s2 (monotonic
|
||||
/// relative to leaf count changes).
|
||||
pub(super) fn test_size_hint_reflects_leaf_count<T: SparseTrie + Default>() {
|
||||
pub(super) fn test_size_hint_reflects_leaf_count<T: SparseTrie>(new_trie: fn() -> T) {
|
||||
let key1 = B256::with_last_byte(0x10);
|
||||
let key2 = B256::with_last_byte(0x20);
|
||||
let key3 = B256::with_last_byte(0x30);
|
||||
@@ -26,7 +26,7 @@ pub(super) fn test_size_hint_reflects_leaf_count<T: SparseTrie + Default>() {
|
||||
|
||||
// Include new key targets so proofs cover them.
|
||||
let all_targets = vec![key1, key2, key3, key4, key5, new_key1, new_key2];
|
||||
let mut trie: T = harness.init_trie_with_targets(&all_targets, false);
|
||||
let mut trie: T = harness.init_trie_with_targets(&all_targets, false, new_trie);
|
||||
|
||||
let s1 = trie.size_hint();
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use super::*;
|
||||
|
||||
pub(super) fn test_take_updates_returns_empty_when_not_tracking<T: SparseTrie + Default>() {
|
||||
pub(super) fn test_take_updates_returns_empty_when_not_tracking<T: SparseTrie>(
|
||||
new_trie: fn() -> T,
|
||||
) {
|
||||
let mut key_a = B256::ZERO;
|
||||
key_a.0[0] = 0x10;
|
||||
let mut key_b = B256::ZERO;
|
||||
@@ -9,7 +11,7 @@ pub(super) fn test_take_updates_returns_empty_when_not_tracking<T: SparseTrie +
|
||||
BTreeMap::from([(key_a, U256::from(1)), (key_b, U256::from(2))]);
|
||||
|
||||
let harness = SuiteTestHarness::new(storage);
|
||||
let mut trie: T = harness.init_trie_fully_revealed(false);
|
||||
let mut trie: T = harness.init_trie_fully_revealed(false, new_trie);
|
||||
|
||||
let updates = trie.take_updates();
|
||||
assert!(updates.updated_nodes.is_empty(), "updated_nodes should be empty when not tracking");
|
||||
@@ -20,7 +22,7 @@ pub(super) fn test_take_updates_returns_empty_when_not_tracking<T: SparseTrie +
|
||||
///
|
||||
/// After `take_updates()`, subsequent updates should be tracked independently in a fresh
|
||||
/// accumulator. updates1 reflects only the A mutation, updates2 reflects only the B mutation.
|
||||
pub(super) fn test_take_updates_resets_after_take<T: SparseTrie + Default>() {
|
||||
pub(super) fn test_take_updates_resets_after_take<T: SparseTrie>(new_trie: fn() -> T) {
|
||||
let mut storage: BTreeMap<B256, U256> = BTreeMap::new();
|
||||
for i in 0u8..16 {
|
||||
let mut key = B256::ZERO;
|
||||
@@ -30,7 +32,7 @@ pub(super) fn test_take_updates_resets_after_take<T: SparseTrie + Default>() {
|
||||
}
|
||||
|
||||
let harness = SuiteTestHarness::new(storage);
|
||||
let mut trie: T = harness.init_trie_fully_revealed(true);
|
||||
let mut trie: T = harness.init_trie_fully_revealed(true, new_trie);
|
||||
|
||||
// Cache initial branch hashes.
|
||||
let _ = trie.root();
|
||||
@@ -80,7 +82,9 @@ pub(super) fn test_take_updates_resets_after_take<T: SparseTrie + Default>() {
|
||||
/// (non-empty `BranchNodeMasks`). After removing one group entirely and modifying the
|
||||
/// other, `take_updates` should report real branches in `removed_nodes` and modified
|
||||
/// branches in `updated_nodes`, with the two sets mutually exclusive.
|
||||
pub(super) fn test_take_updates_contains_updated_and_removed_nodes<T: SparseTrie + Default>() {
|
||||
pub(super) fn test_take_updates_contains_updated_and_removed_nodes<T: SparseTrie>(
|
||||
new_trie: fn() -> T,
|
||||
) {
|
||||
// 3-level branching under two groups:
|
||||
//
|
||||
// Group 0x1 (survives, gets modified):
|
||||
@@ -122,7 +126,7 @@ pub(super) fn test_take_updates_contains_updated_and_removed_nodes<T: SparseTrie
|
||||
}
|
||||
|
||||
let harness = SuiteTestHarness::new(storage);
|
||||
let mut trie: T = harness.init_trie_fully_revealed(true);
|
||||
let mut trie: T = harness.init_trie_fully_revealed(true, new_trie);
|
||||
|
||||
// Cache initial branch hashes.
|
||||
let _ = trie.root();
|
||||
@@ -200,7 +204,9 @@ pub(super) fn test_take_updates_contains_updated_and_removed_nodes<T: SparseTrie
|
||||
/// nibble 4, creating branch `[A,A,1,0]` (no short key) as a child of branch `[A,A,1]`. This
|
||||
/// gives `[A,A,1]` non-empty `hash_mask`.
|
||||
/// - Changeset 2 removes both, collapsing `[A,A,1]` back to a leaf with empty masks.
|
||||
pub(super) fn test_take_updates_cross_cancellation_across_root_calls<T: SparseTrie + Default>() {
|
||||
pub(super) fn test_take_updates_cross_cancellation_across_root_calls<T: SparseTrie>(
|
||||
new_trie: fn() -> T,
|
||||
) {
|
||||
let val = U256::from(1u64);
|
||||
|
||||
let mut key_existing = B256::ZERO;
|
||||
@@ -227,7 +233,7 @@ pub(super) fn test_take_updates_cross_cancellation_across_root_calls<T: SparseTr
|
||||
[(key_existing, val), (key_b, val), (key_other, val)].into_iter().collect();
|
||||
|
||||
let harness = SuiteTestHarness::new(initial);
|
||||
let mut trie: T = harness.init_trie_fully_revealed(true);
|
||||
let mut trie: T = harness.init_trie_fully_revealed(true, new_trie);
|
||||
|
||||
// Cache initial branch hashes.
|
||||
let _ = trie.root();
|
||||
@@ -270,7 +276,9 @@ pub(super) fn test_take_updates_cross_cancellation_across_root_calls<T: SparseTr
|
||||
/// When a branch collapses (leaf removal) and then a new branch is created at the same path
|
||||
/// (leaf insertion), `take_updates` must not report the same path in both `updated_nodes` and
|
||||
/// `removed_nodes`. The insertion must win.
|
||||
pub(super) fn test_take_updates_no_duplicate_updated_and_removed_nodes<T: SparseTrie + Default>() {
|
||||
pub(super) fn test_take_updates_no_duplicate_updated_and_removed_nodes<T: SparseTrie>(
|
||||
new_trie: fn() -> T,
|
||||
) {
|
||||
// 3 leaves sharing the first nibble → branch at nibble 0x0.
|
||||
let mut key_a = B256::ZERO;
|
||||
key_a.0[0] = 0x00;
|
||||
@@ -283,7 +291,7 @@ pub(super) fn test_take_updates_no_duplicate_updated_and_removed_nodes<T: Sparse
|
||||
BTreeMap::from([(key_a, U256::from(1)), (key_b, U256::from(2)), (key_c, U256::from(3))]);
|
||||
|
||||
let harness = SuiteTestHarness::new(storage);
|
||||
let mut trie: T = harness.init_trie_fully_revealed(true);
|
||||
let mut trie: T = harness.init_trie_fully_revealed(true, new_trie);
|
||||
|
||||
// Cache initial hashes.
|
||||
let _ = trie.root();
|
||||
|
||||
@@ -4,7 +4,7 @@ use super::*;
|
||||
///
|
||||
/// Starting from a 3-leaf trie, inserting a 4th key via `update_leaves` should produce
|
||||
/// a root hash matching a reference trie containing all 4 leaves.
|
||||
pub(super) fn test_update_leaves_insert_new_leaf<T: SparseTrie + Default>() {
|
||||
pub(super) fn test_update_leaves_insert_new_leaf<T: SparseTrie>(new_trie: fn() -> T) {
|
||||
let key1 = B256::with_last_byte(0x10);
|
||||
let key2 = B256::with_last_byte(0x20);
|
||||
let key3 = B256::with_last_byte(0x30);
|
||||
@@ -17,7 +17,7 @@ pub(super) fn test_update_leaves_insert_new_leaf<T: SparseTrie + Default>() {
|
||||
|
||||
// Initialize trie with all 3 existing keys revealed, plus the new key target.
|
||||
let all_targets = vec![key1, key2, key3, new_key];
|
||||
let mut trie: T = harness.init_trie_with_targets(&all_targets, true);
|
||||
let mut trie: T = harness.init_trie_with_targets(&all_targets, true, new_trie);
|
||||
|
||||
// Insert the new leaf.
|
||||
let new_value = U256::from(4);
|
||||
@@ -48,7 +48,7 @@ pub(super) fn test_update_leaves_insert_new_leaf<T: SparseTrie + Default>() {
|
||||
///
|
||||
/// Starting from a 3-leaf trie, changing one leaf's value via `update_leaves` should
|
||||
/// produce a root hash matching a reference trie with the updated value.
|
||||
pub(super) fn test_update_leaves_modify_existing_leaf<T: SparseTrie + Default>() {
|
||||
pub(super) fn test_update_leaves_modify_existing_leaf<T: SparseTrie>(new_trie: fn() -> T) {
|
||||
let key1 = B256::with_last_byte(0x10);
|
||||
let key2 = B256::with_last_byte(0x20);
|
||||
let key3 = B256::with_last_byte(0x30);
|
||||
@@ -57,7 +57,7 @@ pub(super) fn test_update_leaves_modify_existing_leaf<T: SparseTrie + Default>()
|
||||
BTreeMap::from([(key1, U256::from(1)), (key2, U256::from(2)), (key3, U256::from(3))]);
|
||||
|
||||
let harness = SuiteTestHarness::new(base_storage);
|
||||
let mut trie: T = harness.init_trie_fully_revealed(true);
|
||||
let mut trie: T = harness.init_trie_fully_revealed(true, new_trie);
|
||||
|
||||
// Modify an existing leaf with a new value.
|
||||
let new_value = U256::from(999);
|
||||
@@ -83,11 +83,11 @@ pub(super) fn test_update_leaves_modify_existing_leaf<T: SparseTrie + Default>()
|
||||
///
|
||||
/// Calling `update_leaves` with one key on a default (empty) trie should produce a root
|
||||
/// hash matching a reference trie with that single leaf.
|
||||
pub(super) fn test_insert_single_leaf_into_empty_trie<T: SparseTrie + Default>() {
|
||||
pub(super) fn test_insert_single_leaf_into_empty_trie<T: SparseTrie>(new_trie: fn() -> T) {
|
||||
let key = B256::with_last_byte(42);
|
||||
let value = U256::from(1);
|
||||
|
||||
let mut trie = T::default();
|
||||
let mut trie = (new_trie)();
|
||||
let mut leaf_updates = SuiteTestHarness::leaf_updates(&BTreeMap::from([(key, value)]));
|
||||
|
||||
// Empty trie has no blinded nodes, so update_leaves should succeed in one call.
|
||||
@@ -112,7 +112,7 @@ pub(super) fn test_insert_single_leaf_into_empty_trie<T: SparseTrie + Default>()
|
||||
///
|
||||
/// All 256 keys are inserted in a single `update_leaves` call. The root must match
|
||||
/// a reference trie and `take_updates()` must return non-empty results.
|
||||
pub(super) fn test_insert_multiple_leaves_into_empty_trie<T: SparseTrie + Default>() {
|
||||
pub(super) fn test_insert_multiple_leaves_into_empty_trie<T: SparseTrie>(new_trie: fn() -> T) {
|
||||
// Build 256 keys with alternating prefix patterns (matching original test).
|
||||
let storage: BTreeMap<B256, U256> = (0..=255u8)
|
||||
.map(|b| {
|
||||
@@ -123,7 +123,7 @@ pub(super) fn test_insert_multiple_leaves_into_empty_trie<T: SparseTrie + Defaul
|
||||
|
||||
let expected_harness = SuiteTestHarness::new(storage.clone());
|
||||
|
||||
let mut trie = T::default();
|
||||
let mut trie = (new_trie)();
|
||||
trie.set_updates(true);
|
||||
|
||||
let mut leaf_updates = SuiteTestHarness::leaf_updates(&storage);
|
||||
@@ -151,7 +151,7 @@ pub(super) fn test_insert_multiple_leaves_into_empty_trie<T: SparseTrie + Defaul
|
||||
/// Insert 256 keys with old values, compute root (hash1). Then update all 256 keys with
|
||||
/// new values, compute root (hash2). Both must match their respective reference tries,
|
||||
/// and hash1 ≠ hash2.
|
||||
pub(super) fn test_update_all_leaves_with_new_values<T: SparseTrie + Default>() {
|
||||
pub(super) fn test_update_all_leaves_with_new_values<T: SparseTrie>(new_trie: fn() -> T) {
|
||||
// Build 256 keys with alternating prefix patterns.
|
||||
let keys: Vec<B256> = (0..=255u8)
|
||||
.map(|b| if b % 2 == 0 { B256::repeat_byte(b) } else { B256::with_last_byte(b) })
|
||||
@@ -164,7 +164,7 @@ pub(super) fn test_update_all_leaves_with_new_values<T: SparseTrie + Default>()
|
||||
let expected_old = SuiteTestHarness::new(old_storage.clone());
|
||||
let expected_new = SuiteTestHarness::new(new_storage.clone());
|
||||
|
||||
let mut trie = T::default();
|
||||
let mut trie = (new_trie)();
|
||||
trie.set_updates(true);
|
||||
|
||||
// Insert all 256 keys with old values.
|
||||
@@ -194,14 +194,16 @@ pub(super) fn test_update_all_leaves_with_new_values<T: SparseTrie + Default>()
|
||||
/// Insert key `0x50..` then key `0x51..` (adjacent first-byte keys that share first nibble `5`),
|
||||
/// computing root after each. The final root must match the reference trie with both keys.
|
||||
/// `take_updates()` should return empty since no branch masks were set.
|
||||
pub(super) fn test_two_leaves_at_adjacent_keys_root_correctness<T: SparseTrie + Default>() {
|
||||
pub(super) fn test_two_leaves_at_adjacent_keys_root_correctness<T: SparseTrie>(
|
||||
new_trie: fn() -> T,
|
||||
) {
|
||||
let mut key_50 = B256::ZERO;
|
||||
key_50.0[0] = 0x50;
|
||||
let mut key_51 = B256::ZERO;
|
||||
key_51.0[0] = 0x51;
|
||||
let value = U256::from(1);
|
||||
|
||||
let mut trie = T::default();
|
||||
let mut trie = (new_trie)();
|
||||
trie.set_updates(true);
|
||||
|
||||
// Insert first leaf and compute root.
|
||||
@@ -234,7 +236,7 @@ pub(super) fn test_two_leaves_at_adjacent_keys_root_correctness<T: SparseTrie +
|
||||
///
|
||||
/// Starting from a 3-leaf trie, removing one key should produce a root hash
|
||||
/// matching a reference trie containing only the remaining 2 leaves.
|
||||
pub(super) fn test_update_leaves_remove_leaf<T: SparseTrie + Default>() {
|
||||
pub(super) fn test_update_leaves_remove_leaf<T: SparseTrie>(new_trie: fn() -> T) {
|
||||
let key1 = B256::with_last_byte(0x10);
|
||||
let key2 = B256::with_last_byte(0x20);
|
||||
let key3 = B256::with_last_byte(0x30);
|
||||
@@ -243,7 +245,7 @@ pub(super) fn test_update_leaves_remove_leaf<T: SparseTrie + Default>() {
|
||||
BTreeMap::from([(key1, U256::from(1)), (key2, U256::from(2)), (key3, U256::from(3))]);
|
||||
|
||||
let harness = SuiteTestHarness::new(base_storage);
|
||||
let mut trie: T = harness.init_trie_fully_revealed(true);
|
||||
let mut trie: T = harness.init_trie_fully_revealed(true, new_trie);
|
||||
|
||||
// Remove key2 by setting its value to U256::ZERO (produces LeafUpdate::Changed(vec![])).
|
||||
let mut leaf_updates = SuiteTestHarness::leaf_updates(&BTreeMap::from([(key2, U256::ZERO)]));
|
||||
@@ -267,7 +269,7 @@ pub(super) fn test_update_leaves_remove_leaf<T: SparseTrie + Default>() {
|
||||
/// extension. Three leaves sharing prefix `0x5` create a branch at nibble 5; removing one
|
||||
/// child should collapse the structure. The root hash must match a reference trie with the
|
||||
/// remaining two leaves.
|
||||
pub(super) fn test_remove_leaf_branch_collapses_to_extension<T: SparseTrie + Default>() {
|
||||
pub(super) fn test_remove_leaf_branch_collapses_to_extension<T: SparseTrie>(new_trie: fn() -> T) {
|
||||
// Keys sharing prefix 0x5: two share 0x50 (children at 0x502..) and one at 0x53.
|
||||
// This creates a branch at nibble 5 with children at nibbles 0 and 3.
|
||||
let mut key_50231 = B256::ZERO;
|
||||
@@ -291,7 +293,7 @@ pub(super) fn test_remove_leaf_branch_collapses_to_extension<T: SparseTrie + Def
|
||||
]);
|
||||
|
||||
let harness = SuiteTestHarness::new(base_storage);
|
||||
let mut trie: T = harness.init_trie_fully_revealed(false);
|
||||
let mut trie: T = harness.init_trie_fully_revealed(false, new_trie);
|
||||
|
||||
// Remove the leaf at key_537 — this collapses the branch at 0x5.
|
||||
let mut leaf_updates = SuiteTestHarness::leaf_updates(&BTreeMap::from([(key_537, U256::ZERO)]));
|
||||
@@ -312,7 +314,7 @@ pub(super) fn test_remove_leaf_branch_collapses_to_extension<T: SparseTrie + Def
|
||||
/// Removing one of two leaves from a branch should collapse the
|
||||
/// branch into a leaf. Update tracking should report the root branch as removed and NOT as
|
||||
/// updated.
|
||||
pub(super) fn test_remove_leaf_branch_collapses_to_leaf<T: SparseTrie + Default>() {
|
||||
pub(super) fn test_remove_leaf_branch_collapses_to_leaf<T: SparseTrie>(new_trie: fn() -> T) {
|
||||
// Two leaves with different first nibbles → branch root.
|
||||
let key_a = B256::with_last_byte(0x10); // first nibble = 1
|
||||
let key_b = B256::with_last_byte(0x20); // first nibble = 2
|
||||
@@ -321,7 +323,7 @@ pub(super) fn test_remove_leaf_branch_collapses_to_leaf<T: SparseTrie + Default>
|
||||
BTreeMap::from([(key_a, U256::from(100)), (key_b, U256::from(200))]);
|
||||
|
||||
let harness = SuiteTestHarness::new(base_storage);
|
||||
let mut trie: T = harness.init_trie_fully_revealed(true);
|
||||
let mut trie: T = harness.init_trie_fully_revealed(true, new_trie);
|
||||
|
||||
// Compute root to cache hashes, take and commit updates to establish baseline masks.
|
||||
let _ = trie.root();
|
||||
@@ -357,12 +359,12 @@ pub(super) fn test_remove_leaf_branch_collapses_to_leaf<T: SparseTrie + Default>
|
||||
|
||||
/// Removing the only leaf in a trie should produce
|
||||
/// `EMPTY_ROOT_HASH`.
|
||||
pub(super) fn test_remove_last_leaf_produces_empty_root<T: SparseTrie + Default>() {
|
||||
pub(super) fn test_remove_last_leaf_produces_empty_root<T: SparseTrie>(new_trie: fn() -> T) {
|
||||
let key = B256::with_last_byte(0x12);
|
||||
let base_storage: BTreeMap<B256, U256> = BTreeMap::from([(key, U256::from(1))]);
|
||||
|
||||
let harness = SuiteTestHarness::new(base_storage);
|
||||
let mut trie: T = harness.init_trie_fully_revealed(false);
|
||||
let mut trie: T = harness.init_trie_fully_revealed(false, new_trie);
|
||||
|
||||
// Remove the only leaf.
|
||||
let mut leaf_updates = SuiteTestHarness::leaf_updates(&BTreeMap::from([(key, U256::ZERO)]));
|
||||
@@ -374,7 +376,7 @@ pub(super) fn test_remove_last_leaf_produces_empty_root<T: SparseTrie + Default>
|
||||
|
||||
/// Build 6 leaves then remove one-by-one, verifying root at each
|
||||
/// step against a reference trie. Final removal produces `EMPTY_ROOT_HASH`.
|
||||
pub(super) fn test_insert_then_remove_sequence<T: SparseTrie + Default>() {
|
||||
pub(super) fn test_insert_then_remove_sequence<T: SparseTrie>(new_trie: fn() -> T) {
|
||||
// Helper: build a B256 key from a nibble prefix, zero-padded.
|
||||
let key_from_nibbles = |nibbles: &[u8]| -> B256 {
|
||||
let mut bytes = [0u8; 32];
|
||||
@@ -402,7 +404,7 @@ pub(super) fn test_insert_then_remove_sequence<T: SparseTrie + Default>() {
|
||||
let base_storage: BTreeMap<B256, U256> = all_keys.iter().map(|&k| (k, val)).collect();
|
||||
|
||||
let mut harness = SuiteTestHarness::new(base_storage.clone());
|
||||
let mut trie = T::default();
|
||||
let mut trie = (new_trie)();
|
||||
let mut leaf_updates = SuiteTestHarness::leaf_updates(&base_storage);
|
||||
harness.reveal_and_update(&mut trie, &mut leaf_updates);
|
||||
|
||||
@@ -436,7 +438,7 @@ pub(super) fn test_insert_then_remove_sequence<T: SparseTrie + Default>() {
|
||||
/// After computing `root()` (which caches hashes on all nodes), attempting to remove a key
|
||||
/// that doesn't exist should leave the cache intact so the next `root()` call returns the
|
||||
/// same hash without recomputation.
|
||||
pub(super) fn test_remove_nonexistent_leaf_preserves_hashes<T: SparseTrie + Default>() {
|
||||
pub(super) fn test_remove_nonexistent_leaf_preserves_hashes<T: SparseTrie>(new_trie: fn() -> T) {
|
||||
let key_a = B256::with_last_byte(0x10);
|
||||
let key_b = B256::with_last_byte(0x20);
|
||||
let key_c = B256::with_last_byte(0x30);
|
||||
@@ -445,7 +447,7 @@ pub(super) fn test_remove_nonexistent_leaf_preserves_hashes<T: SparseTrie + Defa
|
||||
BTreeMap::from([(key_a, U256::from(1)), (key_b, U256::from(2)), (key_c, U256::from(3))]);
|
||||
|
||||
let harness = SuiteTestHarness::new(base_storage);
|
||||
let mut trie: T = harness.init_trie_fully_revealed(false);
|
||||
let mut trie: T = harness.init_trie_fully_revealed(false, new_trie);
|
||||
|
||||
// Compute root to cache hashes on all nodes.
|
||||
let root_before = trie.root();
|
||||
@@ -468,7 +470,7 @@ pub(super) fn test_remove_nonexistent_leaf_preserves_hashes<T: SparseTrie + Defa
|
||||
/// When `update_leaves` encounters a blinded node (insufficient
|
||||
/// proof data), it should invoke the `proof_required_fn` callback with the correct target key
|
||||
/// and minimum depth, and leave the key in the updates map for retry.
|
||||
pub(super) fn test_update_leaves_blinded_node_requests_proof<T: SparseTrie + Default>() {
|
||||
pub(super) fn test_update_leaves_blinded_node_requests_proof<T: SparseTrie>(new_trie: fn() -> T) {
|
||||
// Use enough keys under two different first nibbles so that branch children become
|
||||
// hash nodes (>32 bytes RLP). This ensures partial reveal leaves blinded subtries.
|
||||
let mut base_storage = BTreeMap::new();
|
||||
@@ -494,7 +496,7 @@ pub(super) fn test_update_leaves_blinded_node_requests_proof<T: SparseTrie + Def
|
||||
let harness = SuiteTestHarness::new(base_storage);
|
||||
|
||||
// Reveal only group_a keys, leaving group_b's subtrie blinded.
|
||||
let mut trie: T = harness.init_trie_with_targets(&group_a_keys, false);
|
||||
let mut trie: T = harness.init_trie_with_targets(&group_a_keys, false, new_trie);
|
||||
|
||||
// Try to modify a key in group_b's blinded subtrie.
|
||||
let target_key = group_b_keys[0];
|
||||
@@ -518,7 +520,7 @@ pub(super) fn test_update_leaves_blinded_node_requests_proof<T: SparseTrie + Def
|
||||
);
|
||||
}
|
||||
|
||||
pub(super) fn test_update_leaves_retry_after_reveal<T: SparseTrie + Default>() {
|
||||
pub(super) fn test_update_leaves_retry_after_reveal<T: SparseTrie>(new_trie: fn() -> T) {
|
||||
// Same setup as blinded_node_requests_proof: two groups of 16 keys each under
|
||||
// different first nibbles, so branch children become hash nodes.
|
||||
let mut base_storage = BTreeMap::new();
|
||||
@@ -542,7 +544,7 @@ pub(super) fn test_update_leaves_retry_after_reveal<T: SparseTrie + Default>() {
|
||||
let harness = SuiteTestHarness::new(base_storage.clone());
|
||||
|
||||
// Reveal only group_a keys, leaving group_b's subtrie blinded.
|
||||
let mut trie: T = harness.init_trie_with_targets(&group_a_keys, false);
|
||||
let mut trie: T = harness.init_trie_with_targets(&group_a_keys, false, new_trie);
|
||||
|
||||
// Modify a key in group_b's blinded subtrie.
|
||||
let target_key = group_b_keys[0];
|
||||
@@ -585,7 +587,7 @@ pub(super) fn test_update_leaves_retry_after_reveal<T: SparseTrie + Default>() {
|
||||
);
|
||||
}
|
||||
|
||||
pub(super) fn test_remove_leaf_blinded_sibling_requires_reveal<T: SparseTrie + Default>() {
|
||||
pub(super) fn test_remove_leaf_blinded_sibling_requires_reveal<T: SparseTrie>(new_trie: fn() -> T) {
|
||||
// Build a branch with two children: one revealed leaf at nibble 0x1, and a blinded
|
||||
// subtrie at nibble 0x2 (16 keys so it becomes a hash node > 32 bytes).
|
||||
let mut base_storage = BTreeMap::new();
|
||||
@@ -608,7 +610,7 @@ pub(super) fn test_remove_leaf_blinded_sibling_requires_reveal<T: SparseTrie + D
|
||||
let harness = SuiteTestHarness::new(base_storage.clone());
|
||||
|
||||
// Reveal only the single key at nibble 0x1, leaving nibble 0x2's subtrie blinded.
|
||||
let mut trie: T = harness.init_trie_with_targets(&[revealed_key], false);
|
||||
let mut trie: T = harness.init_trie_with_targets(&[revealed_key], false, new_trie);
|
||||
|
||||
// Try to remove the revealed leaf. Branch collapse requires the blinded sibling.
|
||||
let mut leaf_updates =
|
||||
@@ -649,9 +651,9 @@ pub(super) fn test_remove_leaf_blinded_sibling_requires_reveal<T: SparseTrie + D
|
||||
///
|
||||
/// Same scenario as the value-preservation test, but additionally checks that
|
||||
/// `size_hint` (node count) is unchanged and the update remains in the map.
|
||||
pub(super) fn test_update_leaves_removal_branch_collapse_blinded_sibling<
|
||||
T: SparseTrie + Default,
|
||||
>() {
|
||||
pub(super) fn test_update_leaves_removal_branch_collapse_blinded_sibling<T: SparseTrie>(
|
||||
new_trie: fn() -> T,
|
||||
) {
|
||||
// Branch: nibble 0x1 = one revealed leaf, nibble 0x2 = 16 blinded keys (hash node).
|
||||
let mut base_storage = BTreeMap::new();
|
||||
|
||||
@@ -671,7 +673,7 @@ pub(super) fn test_update_leaves_removal_branch_collapse_blinded_sibling<
|
||||
let harness = SuiteTestHarness::new(base_storage);
|
||||
|
||||
// Reveal only the leaf at nibble 0x1, leaving nibble 0x2 blinded.
|
||||
let mut trie: T = harness.init_trie_with_targets(&[revealed_key], false);
|
||||
let mut trie: T = harness.init_trie_with_targets(&[revealed_key], false, new_trie);
|
||||
|
||||
// Snapshot state before the removal attempt.
|
||||
let revealed_path = Nibbles::unpack(revealed_key);
|
||||
@@ -712,7 +714,9 @@ pub(super) fn test_update_leaves_removal_branch_collapse_blinded_sibling<
|
||||
/// When removals in a subtrie would empty it and collapse the parent branch onto
|
||||
/// a blinded sibling, `update_leaves` should detect this and request a proof for
|
||||
/// the blinded sibling via the callback, deferring the updates.
|
||||
pub(super) fn test_update_leaves_subtrie_collapse_requests_proof<T: SparseTrie + Default>() {
|
||||
pub(super) fn test_update_leaves_subtrie_collapse_requests_proof<T: SparseTrie>(
|
||||
new_trie: fn() -> T,
|
||||
) {
|
||||
// Build a branch with two children:
|
||||
// nibble 0x1 → a subtrie with 2 revealed leaves
|
||||
// nibble 0x2 → 16 blinded keys (hash node > 32 bytes)
|
||||
@@ -742,7 +746,8 @@ pub(super) fn test_update_leaves_subtrie_collapse_requests_proof<T: SparseTrie +
|
||||
let harness = SuiteTestHarness::new(base_storage);
|
||||
|
||||
// Reveal only the two subtrie keys at nibble 0x1, leaving nibble 0x2 blinded.
|
||||
let mut trie: T = harness.init_trie_with_targets(&[subtrie_key_a, subtrie_key_b], false);
|
||||
let mut trie: T =
|
||||
harness.init_trie_with_targets(&[subtrie_key_a, subtrie_key_b], false, new_trie);
|
||||
|
||||
// Remove both leaves in the subtrie — this would empty the subtrie and
|
||||
// require collapsing the parent branch onto the blinded sibling at nibble 0x2.
|
||||
@@ -770,7 +775,9 @@ pub(super) fn test_update_leaves_subtrie_collapse_requests_proof<T: SparseTrie +
|
||||
///
|
||||
/// When multiple keys in the update map all route through the same blinded node,
|
||||
/// the callback should be invoked once per key (not deduplicated).
|
||||
pub(super) fn test_update_leaves_multiple_keys_same_blinded_node<T: SparseTrie + Default>() {
|
||||
pub(super) fn test_update_leaves_multiple_keys_same_blinded_node<T: SparseTrie>(
|
||||
new_trie: fn() -> T,
|
||||
) {
|
||||
// Branch: nibble 0x1 = 16 revealed keys (hash node), nibble 0x2 = 16 blinded keys.
|
||||
let mut base_storage = BTreeMap::new();
|
||||
|
||||
@@ -791,7 +798,7 @@ pub(super) fn test_update_leaves_multiple_keys_same_blinded_node<T: SparseTrie +
|
||||
let harness = SuiteTestHarness::new(base_storage);
|
||||
|
||||
// Reveal only group_a, leaving nibble 0x2 blinded.
|
||||
let mut trie: T = harness.init_trie_with_targets(&group_a_keys, false);
|
||||
let mut trie: T = harness.init_trie_with_targets(&group_a_keys, false, new_trie);
|
||||
|
||||
// Submit 3 keys that all start with nibble 0x2 — they all hit the same blinded node.
|
||||
let blinded_keys: BTreeMap<B256, U256> = (0u8..3)
|
||||
@@ -816,7 +823,7 @@ pub(super) fn test_update_leaves_multiple_keys_same_blinded_node<T: SparseTrie +
|
||||
}
|
||||
|
||||
/// `LeafUpdate::Touched` on a fully revealed path should be a no-op.
|
||||
pub(super) fn test_update_leaves_touched_fully_revealed<T: SparseTrie + Default>() {
|
||||
pub(super) fn test_update_leaves_touched_fully_revealed<T: SparseTrie>(new_trie: fn() -> T) {
|
||||
let key1 = B256::with_last_byte(0x10);
|
||||
let key2 = B256::with_last_byte(0x20);
|
||||
let key3 = B256::with_last_byte(0x30);
|
||||
@@ -825,7 +832,7 @@ pub(super) fn test_update_leaves_touched_fully_revealed<T: SparseTrie + Default>
|
||||
[(key1, U256::from(1)), (key2, U256::from(2)), (key3, U256::from(3))].into_iter().collect();
|
||||
|
||||
let harness = SuiteTestHarness::new(base_storage);
|
||||
let mut trie: T = harness.init_trie_fully_revealed(false);
|
||||
let mut trie: T = harness.init_trie_fully_revealed(false, new_trie);
|
||||
|
||||
let root_before = trie.root();
|
||||
|
||||
@@ -848,7 +855,9 @@ pub(super) fn test_update_leaves_touched_fully_revealed<T: SparseTrie + Default>
|
||||
|
||||
/// `LeafUpdate::Touched` on a path with a blinded node should
|
||||
/// invoke the callback and keep the key in the updates map. No trie mutation should occur.
|
||||
pub(super) fn test_update_leaves_touched_blinded_requests_proof<T: SparseTrie + Default>() {
|
||||
pub(super) fn test_update_leaves_touched_blinded_requests_proof<T: SparseTrie>(
|
||||
new_trie: fn() -> T,
|
||||
) {
|
||||
// Two groups of 16 keys each under different first nibbles so that branch children
|
||||
// become hash nodes (>32 bytes RLP). Partial reveal leaves one subtrie blinded.
|
||||
let mut base_storage = BTreeMap::new();
|
||||
@@ -870,7 +879,7 @@ pub(super) fn test_update_leaves_touched_blinded_requests_proof<T: SparseTrie +
|
||||
let harness = SuiteTestHarness::new(base_storage);
|
||||
|
||||
// Reveal only group_a keys, leaving group_b's subtrie blinded.
|
||||
let mut trie: T = harness.init_trie_with_targets(&group_a_keys, false);
|
||||
let mut trie: T = harness.init_trie_with_targets(&group_a_keys, false, new_trie);
|
||||
|
||||
let root_before = trie.root();
|
||||
|
||||
@@ -901,8 +910,8 @@ pub(super) fn test_update_leaves_touched_blinded_requests_proof<T: SparseTrie +
|
||||
/// An empty (default) trie has an Empty root — all paths are accessible (no blinded nodes).
|
||||
/// `Touched` on a key that doesn't exist should be drained from the map without any
|
||||
/// callback invocation or mutation.
|
||||
pub(super) fn test_update_leaves_touched_nonexistent_key<T: SparseTrie + Default>() {
|
||||
let mut trie = T::default();
|
||||
pub(super) fn test_update_leaves_touched_nonexistent_key<T: SparseTrie>(new_trie: fn() -> T) {
|
||||
let mut trie = (new_trie)();
|
||||
|
||||
let target_key = B256::with_last_byte(42);
|
||||
let mut leaf_updates: B256Map<LeafUpdate> = once((target_key, LeafUpdate::Touched)).collect();
|
||||
@@ -924,7 +933,9 @@ pub(super) fn test_update_leaves_touched_nonexistent_key<T: SparseTrie + Default
|
||||
|
||||
/// `LeafUpdate::Touched` on a nonexistent key in a fully
|
||||
/// revealed, populated trie should be a no-op — no callback, key drained, trie unchanged.
|
||||
pub(super) fn test_update_leaves_touched_nonexistent_in_populated_trie<T: SparseTrie + Default>() {
|
||||
pub(super) fn test_update_leaves_touched_nonexistent_in_populated_trie<T: SparseTrie>(
|
||||
new_trie: fn() -> T,
|
||||
) {
|
||||
let key1 = B256::with_last_byte(0x10);
|
||||
let key2 = B256::with_last_byte(0x20);
|
||||
let key3 = B256::with_last_byte(0x30);
|
||||
@@ -933,7 +944,7 @@ pub(super) fn test_update_leaves_touched_nonexistent_in_populated_trie<T: Sparse
|
||||
[(key1, U256::from(1)), (key2, U256::from(2)), (key3, U256::from(3))].into_iter().collect();
|
||||
|
||||
let harness = SuiteTestHarness::new(base_storage);
|
||||
let mut trie: T = harness.init_trie_fully_revealed(false);
|
||||
let mut trie: T = harness.init_trie_fully_revealed(false, new_trie);
|
||||
|
||||
let root_before = trie.root();
|
||||
|
||||
@@ -960,7 +971,7 @@ pub(super) fn test_update_leaves_touched_nonexistent_in_populated_trie<T: Sparse
|
||||
|
||||
/// A single `update_leaves` call with a mix of inserts,
|
||||
/// modifications, removals, and touched entries should process all correctly.
|
||||
pub(super) fn test_update_leaves_multiple_mixed_updates<T: SparseTrie + Default>() {
|
||||
pub(super) fn test_update_leaves_multiple_mixed_updates<T: SparseTrie>(new_trie: fn() -> T) {
|
||||
let key_a = B256::with_last_byte(0x10); // will be inserted (new key)
|
||||
let key_b = B256::with_last_byte(0x20); // will be modified
|
||||
let key_c = B256::with_last_byte(0x30); // will be removed
|
||||
@@ -980,7 +991,7 @@ pub(super) fn test_update_leaves_multiple_mixed_updates<T: SparseTrie + Default>
|
||||
|
||||
// Fully reveal existing trie, plus proof for key_a (new key to be inserted).
|
||||
let all_keys = vec![key_a, key_b, key_c, key_d, key_e];
|
||||
let mut trie: T = harness.init_trie_with_targets(&all_keys, false);
|
||||
let mut trie: T = harness.init_trie_with_targets(&all_keys, false, new_trie);
|
||||
|
||||
// Build mixed leaf updates.
|
||||
let new_value_a = U256::from(100);
|
||||
@@ -1027,7 +1038,9 @@ pub(super) fn test_update_leaves_multiple_mixed_updates<T: SparseTrie + Default>
|
||||
/// not just those that previously had a cached hash. This test inserts leaves without
|
||||
/// calling `root()` (so no hashes are cached), then removes a leaf and verifies
|
||||
/// `root()` returns the correct hash.
|
||||
pub(super) fn test_remove_leaf_marks_ancestors_dirty_unconditionally<T: SparseTrie + Default>() {
|
||||
pub(super) fn test_remove_leaf_marks_ancestors_dirty_unconditionally<T: SparseTrie>(
|
||||
new_trie: fn() -> T,
|
||||
) {
|
||||
// Create a trie with 5 leaves.
|
||||
let mut keys = Vec::new();
|
||||
let mut storage: BTreeMap<B256, U256> = BTreeMap::new();
|
||||
@@ -1042,7 +1055,7 @@ pub(super) fn test_remove_leaf_marks_ancestors_dirty_unconditionally<T: SparseTr
|
||||
|
||||
// Initialize trie: set root and reveal all proofs.
|
||||
let root_node = harness.root_node();
|
||||
let mut trie = T::default();
|
||||
let mut trie = (new_trie)();
|
||||
trie.set_root(root_node.node, root_node.masks, false).expect("set_root should succeed");
|
||||
|
||||
let mut targets: Vec<ProofV2Target> = keys.iter().map(|k| ProofV2Target::new(*k)).collect();
|
||||
@@ -1082,9 +1095,9 @@ pub(super) fn test_remove_leaf_marks_ancestors_dirty_unconditionally<T: SparseTr
|
||||
/// every key with a value must remain findable via `find_leaf` and updatable
|
||||
/// via `update_leaves`. This verifies the invariant that structural consistency
|
||||
/// is maintained even when branch collapses could orphan value entries.
|
||||
pub(super) fn test_orphaned_value_update_falls_through_to_full_insertion<
|
||||
T: SparseTrie + Default,
|
||||
>() {
|
||||
pub(super) fn test_orphaned_value_update_falls_through_to_full_insertion<T: SparseTrie>(
|
||||
new_trie: fn() -> T,
|
||||
) {
|
||||
// Create a trie with 3 leaves sharing a branch prefix, plus 2 additional leaves
|
||||
// in different subtries. Keys chosen so removal of key_c collapses the branch
|
||||
// at the shared prefix.
|
||||
@@ -1128,7 +1141,7 @@ pub(super) fn test_orphaned_value_update_falls_through_to_full_insertion<
|
||||
.collect();
|
||||
|
||||
let mut harness = SuiteTestHarness::new(initial_storage.clone());
|
||||
let mut trie: T = harness.init_trie_fully_revealed(false);
|
||||
let mut trie: T = harness.init_trie_fully_revealed(false, new_trie);
|
||||
|
||||
// Insert all leaves.
|
||||
let mut insert_updates = SuiteTestHarness::leaf_updates(&initial_storage);
|
||||
@@ -1185,7 +1198,9 @@ pub(super) fn test_orphaned_value_update_falls_through_to_full_insertion<
|
||||
/// When removing a leaf causes a branch to collapse at a subtrie
|
||||
/// boundary, the remaining sibling leaf's `key_len` metadata must be updated. After the
|
||||
/// collapse the remaining leaf must be findable, updatable, and contribute to the correct root.
|
||||
pub(super) fn test_branch_collapse_updates_leaf_key_len_across_subtries<T: SparseTrie + Default>() {
|
||||
pub(super) fn test_branch_collapse_updates_leaf_key_len_across_subtries<T: SparseTrie>(
|
||||
new_trie: fn() -> T,
|
||||
) {
|
||||
// Create two leaves that share a branch at a subtrie boundary.
|
||||
// Keys share the same first nibble (0x1) so they form a branch one level down,
|
||||
// which is a subtrie boundary for the parallel sparse trie.
|
||||
@@ -1196,7 +1211,7 @@ pub(super) fn test_branch_collapse_updates_leaf_key_len_across_subtries<T: Spars
|
||||
BTreeMap::from([(key_a, U256::from(100)), (key_b, U256::from(200))]);
|
||||
|
||||
let mut harness = SuiteTestHarness::new(base_storage);
|
||||
let mut trie: T = harness.init_trie_fully_revealed(false);
|
||||
let mut trie: T = harness.init_trie_fully_revealed(false, new_trie);
|
||||
|
||||
// Step 1: Remove key_a → branch collapses, key_b becomes the sole child.
|
||||
let removal: BTreeMap<B256, U256> = once((key_a, U256::ZERO)).collect();
|
||||
@@ -1239,7 +1254,7 @@ pub(super) fn test_branch_collapse_updates_leaf_key_len_across_subtries<T: Spars
|
||||
/// When a branch collapses during leaf removal and the remaining child's value needs to be
|
||||
/// moved between subtries, the operation must not reveal (initialize) blind/unloaded subtries.
|
||||
/// Either the removal succeeds cleanly or it requests proofs for the blinded area.
|
||||
pub(super) fn test_remove_leaf_does_not_reveal_blind_subtries<T: SparseTrie + Default>() {
|
||||
pub(super) fn test_remove_leaf_does_not_reveal_blind_subtries<T: SparseTrie>(new_trie: fn() -> T) {
|
||||
// Create a trie with 10 leaves across different first-nibble subtries.
|
||||
// We'll prune some subtries, then remove a leaf whose branch collapse
|
||||
// involves a sibling in a pruned (blinded) subtrie.
|
||||
@@ -1253,7 +1268,7 @@ pub(super) fn test_remove_leaf_does_not_reveal_blind_subtries<T: SparseTrie + De
|
||||
}
|
||||
|
||||
let mut harness = SuiteTestHarness::new(storage.clone());
|
||||
let mut trie: T = harness.init_trie_fully_revealed(true);
|
||||
let mut trie: T = harness.init_trie_fully_revealed(true, new_trie);
|
||||
|
||||
// Compute initial root and commit to establish baseline.
|
||||
let _ = trie.root();
|
||||
@@ -1307,3 +1322,148 @@ pub(super) fn test_remove_leaf_does_not_reveal_blind_subtries<T: SparseTrie + De
|
||||
"root after modifying retained leaf should match reference"
|
||||
);
|
||||
}
|
||||
|
||||
/// Branch collapse across multiple emptied subtries with blinded remaining child.
|
||||
///
|
||||
/// A branch at nibble `0xd` has 3 children: two revealed subtries (at `0xd7` and `0xdd`)
|
||||
/// each containing a single leaf, and one blinded subtrie (at `0xd8`). Removing both
|
||||
/// revealed leaves empties their subtries, leaving the branch with a single blinded child
|
||||
/// that cannot be collapsed without a proof.
|
||||
///
|
||||
/// ```text
|
||||
/// root (branch)
|
||||
/// └─ 0xd (branch, 3 children)
|
||||
/// ├─ 0xd7 → Leaf (revealed)
|
||||
/// ├─ 0xd8 → Leaf (BLINDED)
|
||||
/// └─ 0xdd → Leaf (revealed)
|
||||
///
|
||||
/// After removing 0xd7 and 0xdd:
|
||||
/// 0xd branch has single child (0xd8), must collapse,
|
||||
/// but 0xd8 is blinded → needs proof
|
||||
/// ```
|
||||
pub(super) fn test_branch_collapse_multi_empty_subtries_blinded_remaining<T: SparseTrie>(
|
||||
new_trie: fn() -> T,
|
||||
) {
|
||||
// Three keys sharing first nibble 0xd, differing at second nibble.
|
||||
let key_d7 = {
|
||||
let mut key = B256::ZERO;
|
||||
key.0[0] = 0xd7;
|
||||
key
|
||||
};
|
||||
let key_d8 = {
|
||||
let mut key = B256::ZERO;
|
||||
key.0[0] = 0xd8;
|
||||
key
|
||||
};
|
||||
let key_dd = {
|
||||
let mut key = B256::ZERO;
|
||||
key.0[0] = 0xdd;
|
||||
key
|
||||
};
|
||||
|
||||
let base_storage: BTreeMap<B256, U256> =
|
||||
BTreeMap::from([(key_d7, U256::from(1)), (key_d8, U256::from(2)), (key_dd, U256::from(3))]);
|
||||
|
||||
let harness = SuiteTestHarness::new(base_storage);
|
||||
|
||||
// Reveal only 0xd7 and 0xdd, leaving 0xd8's subtrie blinded.
|
||||
let mut trie: T = harness.init_trie_with_targets(&[key_d7, key_dd], false, new_trie);
|
||||
|
||||
// Remove both revealed leaves — their subtries empty, branch collapses to
|
||||
// single child (0xd8) which is blinded.
|
||||
let mut leaf_updates = SuiteTestHarness::leaf_updates(&BTreeMap::from([
|
||||
(key_d7, U256::ZERO),
|
||||
(key_dd, U256::ZERO),
|
||||
]));
|
||||
|
||||
let mut targets: Vec<ProofV2Target> = Vec::new();
|
||||
trie.update_leaves(&mut leaf_updates, |key, min_len| {
|
||||
targets.push(ProofV2Target::new(key).with_min_len(min_len));
|
||||
})
|
||||
.expect("update_leaves should succeed");
|
||||
|
||||
// Callback should fire for the blinded child at 0xd8.
|
||||
assert!(!targets.is_empty(), "callback should fire for blinded child during branch collapse");
|
||||
// Removal keys should remain in the map for retry.
|
||||
assert!(!leaf_updates.is_empty(), "removal keys should remain in map after blinded hit");
|
||||
|
||||
// Reveal the blinded subtrie.
|
||||
let (mut proof_nodes, _) = harness.proof_v2(&mut targets);
|
||||
trie.reveal_nodes(&mut proof_nodes).expect("reveal_nodes should succeed");
|
||||
|
||||
// Retry — now the sibling is revealed, branch can collapse.
|
||||
trie.update_leaves(&mut leaf_updates, |_, _| {})
|
||||
.expect("update_leaves should succeed on retry");
|
||||
assert!(leaf_updates.is_empty(), "keys should be drained after successful retry");
|
||||
|
||||
// Root should match reference trie with only key_d8.
|
||||
let expected_harness = SuiteTestHarness::new(BTreeMap::from([(key_d8, U256::from(2))]));
|
||||
let root = trie.root();
|
||||
assert_eq!(
|
||||
root,
|
||||
expected_harness.original_root(),
|
||||
"root should match trie with only the previously-blinded leaf"
|
||||
);
|
||||
}
|
||||
|
||||
/// Regression: subtrie emptied by deletes mixed with `LeafUpdate::Touched`.
|
||||
///
|
||||
/// When all `Changed` updates in a subtrie are removals and they would empty the subtrie,
|
||||
/// the `might_empty_subtrie` guard must still trigger even if `Touched` entries are present.
|
||||
/// `Touched` is a no-op that doesn't prevent the subtrie from being emptied.
|
||||
pub(super) fn test_subtrie_emptied_by_deletes_with_touched<T: SparseTrie>(new_trie: fn() -> T) {
|
||||
// Two leaves under prefix 0xAB (the target subtrie), one under 0xAC (sibling at
|
||||
// depth 1 to force the 0xAB child into a subtrie at depth 2), one under 0xCD
|
||||
// (sibling at depth 0 to force a branch at the root).
|
||||
let mut key_ab1 = B256::ZERO;
|
||||
key_ab1[0] = 0xAB;
|
||||
key_ab1[31] = 0x11;
|
||||
let mut key_ab2 = B256::ZERO;
|
||||
key_ab2[0] = 0xAB;
|
||||
key_ab2[31] = 0x22;
|
||||
let mut key_ab3 = B256::ZERO;
|
||||
key_ab3[0] = 0xAB;
|
||||
key_ab3[31] = 0x33;
|
||||
let mut key_ac1 = B256::ZERO;
|
||||
key_ac1[0] = 0xAC;
|
||||
key_ac1[31] = 0x44;
|
||||
let mut key_cd1 = B256::ZERO;
|
||||
key_cd1[0] = 0xCD;
|
||||
key_cd1[31] = 0x01;
|
||||
|
||||
let value = U256::from(1u64);
|
||||
|
||||
let base_storage: BTreeMap<B256, U256> =
|
||||
[(key_ab1, value), (key_ab2, value), (key_ac1, value), (key_cd1, value)]
|
||||
.into_iter()
|
||||
.collect();
|
||||
|
||||
let harness = SuiteTestHarness::new(base_storage.clone());
|
||||
let all_keys = vec![key_ab1, key_ab2, key_ac1, key_cd1];
|
||||
let mut trie: T = harness.init_trie_with_targets(&all_keys, false, new_trie);
|
||||
|
||||
// Verify initial root matches.
|
||||
let root = trie.root();
|
||||
assert_eq!(root, harness.original_root(), "initial root mismatch");
|
||||
|
||||
// Delete both 0xAB leaves + Touched on a third 0xAB key (not in the trie).
|
||||
// Touched is a no-op but must not prevent the might_empty_subtrie guard.
|
||||
let mut leaf_updates: B256Map<LeafUpdate> = [
|
||||
(key_ab1, LeafUpdate::Changed(Vec::new())),
|
||||
(key_ab2, LeafUpdate::Changed(Vec::new())),
|
||||
(key_ab3, LeafUpdate::Touched),
|
||||
]
|
||||
.into_iter()
|
||||
.collect();
|
||||
|
||||
harness.reveal_and_update(&mut trie, &mut leaf_updates);
|
||||
|
||||
// Root should match reference trie with ab1 and ab2 removed.
|
||||
let mut expected_storage = base_storage;
|
||||
expected_storage.remove(&key_ab1);
|
||||
expected_storage.remove(&key_ab2);
|
||||
let expected_harness = SuiteTestHarness::new(expected_storage);
|
||||
|
||||
let actual_root = trie.root();
|
||||
assert_eq!(actual_root, expected_harness.original_root(), "post-delete root mismatch");
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ use super::*;
|
||||
|
||||
/// Calling `wipe()` resets the trie so that
|
||||
/// `root()` returns `EMPTY_ROOT_HASH`.
|
||||
pub(super) fn test_wipe_resets_to_empty_root<T: SparseTrie + Default>() {
|
||||
pub(super) fn test_wipe_resets_to_empty_root<T: SparseTrie>(new_trie: fn() -> T) {
|
||||
let storage: BTreeMap<B256, U256> = BTreeMap::from([
|
||||
(B256::with_last_byte(0x10), U256::from(1)),
|
||||
(B256::with_last_byte(0x20), U256::from(2)),
|
||||
@@ -12,7 +12,7 @@ pub(super) fn test_wipe_resets_to_empty_root<T: SparseTrie + Default>() {
|
||||
]);
|
||||
|
||||
let harness = SuiteTestHarness::new(storage);
|
||||
let mut trie: T = harness.init_trie_fully_revealed(false);
|
||||
let mut trie: T = harness.init_trie_fully_revealed(false, new_trie);
|
||||
|
||||
// Compute root to confirm the trie is populated.
|
||||
let root_before = trie.root();
|
||||
@@ -28,7 +28,9 @@ pub(super) fn test_wipe_resets_to_empty_root<T: SparseTrie + Default>() {
|
||||
/// `clear()` resets the trie to empty but preserves
|
||||
/// update tracking mode. After clear, `root()` returns `EMPTY_ROOT_HASH` and
|
||||
/// `take_updates()` returns empty (non-wiped) updates.
|
||||
pub(super) fn test_clear_resets_trie_but_preserves_update_tracking<T: SparseTrie + Default>() {
|
||||
pub(super) fn test_clear_resets_trie_but_preserves_update_tracking<T: SparseTrie>(
|
||||
new_trie: fn() -> T,
|
||||
) {
|
||||
let storage: BTreeMap<B256, U256> = BTreeMap::from([
|
||||
(B256::with_last_byte(0x10), U256::from(1)),
|
||||
(B256::with_last_byte(0x20), U256::from(2)),
|
||||
@@ -37,7 +39,7 @@ pub(super) fn test_clear_resets_trie_but_preserves_update_tracking<T: SparseTrie
|
||||
|
||||
let harness = SuiteTestHarness::new(storage);
|
||||
// retain_updates = true so update tracking is active
|
||||
let mut trie: T = harness.init_trie_fully_revealed(true);
|
||||
let mut trie: T = harness.init_trie_fully_revealed(true, new_trie);
|
||||
|
||||
// Compute root to populate the trie fully.
|
||||
let root_before = trie.root();
|
||||
@@ -59,7 +61,7 @@ pub(super) fn test_clear_resets_trie_but_preserves_update_tracking<T: SparseTrie
|
||||
/// `wipe()` produces special "wiped" updates distinct
|
||||
/// from normal empty updates. After wipe, `take_updates()` returns updates with
|
||||
/// the wiped flag set and `root()` returns `EMPTY_ROOT_HASH`.
|
||||
pub(super) fn test_wipe_produces_wiped_updates<T: SparseTrie + Default>() {
|
||||
pub(super) fn test_wipe_produces_wiped_updates<T: SparseTrie>(new_trie: fn() -> T) {
|
||||
let storage: BTreeMap<B256, U256> = BTreeMap::from([
|
||||
(B256::with_last_byte(0x10), U256::from(1)),
|
||||
(B256::with_last_byte(0x20), U256::from(2)),
|
||||
@@ -68,7 +70,7 @@ pub(super) fn test_wipe_produces_wiped_updates<T: SparseTrie + Default>() {
|
||||
|
||||
let harness = SuiteTestHarness::new(storage);
|
||||
// retain_updates = true so update tracking is active
|
||||
let mut trie: T = harness.init_trie_fully_revealed(true);
|
||||
let mut trie: T = harness.init_trie_fully_revealed(true, new_trie);
|
||||
|
||||
// Compute root to populate the trie fully.
|
||||
let root_before = trie.root();
|
||||
@@ -90,7 +92,7 @@ pub(super) fn test_wipe_produces_wiped_updates<T: SparseTrie + Default>() {
|
||||
/// A cleared trie can be fully re-initialized and used
|
||||
/// normally. After `clear()`, set a new root from a different dataset, reveal
|
||||
/// nodes, insert a leaf, and verify `root()` matches the reference.
|
||||
pub(super) fn test_clear_then_reuse_trie<T: SparseTrie + Default>() {
|
||||
pub(super) fn test_clear_then_reuse_trie<T: SparseTrie>(new_trie: fn() -> T) {
|
||||
// Phase 1: build a trie with 5 leaves and compute root.
|
||||
let storage_1: BTreeMap<B256, U256> = BTreeMap::from([
|
||||
(B256::with_last_byte(0x10), U256::from(1)),
|
||||
@@ -100,7 +102,7 @@ pub(super) fn test_clear_then_reuse_trie<T: SparseTrie + Default>() {
|
||||
(B256::with_last_byte(0x50), U256::from(5)),
|
||||
]);
|
||||
let harness_1 = SuiteTestHarness::new(storage_1);
|
||||
let mut trie: T = harness_1.init_trie_fully_revealed(false);
|
||||
let mut trie: T = harness_1.init_trie_fully_revealed(false, new_trie);
|
||||
|
||||
let root_1 = trie.root();
|
||||
assert_eq!(root_1, harness_1.original_root());
|
||||
|
||||
Reference in New Issue
Block a user