Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Now prunes tries properly (and more aggressively!) without hack #97

Merged
merged 10 commits into from
Mar 11, 2024
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Fix generation inputs logging pre-transaction execution ([#89](https://github.com/0xPolygonZero/zk_evm/pull/89))
- Reduce state trie size for dummy payloads ([#88](https://github.com/0xPolygonZero/zk_evm/pull/88))
- Fix post-txn trie debugging output for multi-logs receipts ([#86](https://github.com/0xPolygonZero/zk_evm/pull/86))
- Fixed *most* failing blocks caused by the merged in aggressive pruning changes ([#97](https://github.com/0xPolygonZero/zk_evm/pull/97))

## [0.1.1] - 2024-03-01

Expand Down
2 changes: 1 addition & 1 deletion mpt_trie/src/debug_tools/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ pub struct DebugQueryOutput {
k: Nibbles,

/// The nodes hit during the query.
pub node_path: TriePath,
node_path: TriePath,
extra_node_info: Vec<Option<ExtraNodeSegmentInfo>>,
node_found: bool,
params: DebugQueryParams,
Expand Down
51 changes: 32 additions & 19 deletions mpt_trie/src/nibbles.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ pub type Nibble = u8;
/// Used for the internal representation of a sequence of nibbles.
pub type NibblesIntern = U512;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just noticed it now, but why do we need U512? For the 65-Nibbles edge case? We probably would want to refactor it at some point, seems a bit wasteful to leverage U512 everywhere here (though out of scope for this PR).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah exactly. It's for the 65 Nibble edge case.

I don't think I have a Github issue open for this (there was one in Jira), so I'll create one. Also worth having a discussion on the best way to implement this. If we care about performance, using a byte array might perform worse. I know H256 uses a [u8; 32], but U256s use [u64; 4]. If I had to guess, since H256 don't support common operators (eg. +, -), they probably found that working with u64s allowed them to implement a lot of operations with 64-bit instructions (vs. doing a bunch of operations to individual bytes). Probably worth talking to @dvdplm about this, as IIRC he worked on ethereum-types a good amount.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Created an issue for this (#104).


const MULTIPLE_NIBBLES_APPEND_ASSERT_ERR_MSG: &str =
"Attempted to create a nibbles sequence longer than 64!";
const SINGLE_NIBBLE_APPEND_ASSERT_ERR_MSG: &str =
"Attempted to append a single nibble that was greater than 15!";

/// Because there are two different ways to convert to `Nibbles`, we don't want
/// to rely on `From`. Instead, we'll define a new trait that defines both
/// conversions.
Expand Down Expand Up @@ -776,12 +781,20 @@ impl Nibbles {
}

fn nibble_append_safety_asserts(&self, n: Nibble) {
assert!(self.count < 64);
assert!(n < 16);
assert!(
self.count < 64,
"{}",
MULTIPLE_NIBBLES_APPEND_ASSERT_ERR_MSG
);
assert!(n < 16, "{}", SINGLE_NIBBLE_APPEND_ASSERT_ERR_MSG);
}

fn nibbles_append_safety_asserts(&self, new_count: usize) {
assert!(new_count <= 64);
assert!(
new_count <= 64,
"{}",
MULTIPLE_NIBBLES_APPEND_ASSERT_ERR_MSG
);
}

// TODO: REMOVE BEFORE NEXT CRATE VERSION! THIS IS A TEMP HACK!
Expand All @@ -806,8 +819,12 @@ mod tests {
use super::{Nibble, Nibbles, ToNibbles};
use crate::nibbles::FromHexPrefixError;

const LONG_ZERO_NIBS_STR_LEN_63: &str =
"0x000000000000000000000000000000000000000000000000000000000000000";
const ZERO_NIBS_63: &str = "0x000000000000000000000000000000000000000000000000000000000000000";
const ZERO_NIBS_64: &str = "0x0000000000000000000000000000000000000000000000000000000000000000";
const ZERO_NIBS_64_LEADING_1: &str =
"0x1000000000000000000000000000000000000000000000000000000000000000";
const ZERO_NIBS_64_TRAILING_1: &str =
"0x0000000000000000000000000000000000000000000000000000000000000001";

#[test]
fn get_nibble_works() {
Expand Down Expand Up @@ -925,9 +942,8 @@ mod tests {
test_and_assert_nib_push_func(Nibbles::default(), 0x1, |n| n.push_nibble_front(0x1));
test_and_assert_nib_push_func(0x1, 0x21, |n| n.push_nibble_front(0x2));
test_and_assert_nib_push_func(
Nibbles::from_str(LONG_ZERO_NIBS_STR_LEN_63).unwrap(),
Nibbles::from_str("0x1000000000000000000000000000000000000000000000000000000000000000")
.unwrap(),
Nibbles::from_str(ZERO_NIBS_63).unwrap(),
Nibbles::from_str(ZERO_NIBS_64_LEADING_1).unwrap(),
|n| n.push_nibble_front(0x1),
);
}
Expand All @@ -937,9 +953,8 @@ mod tests {
test_and_assert_nib_push_func(Nibbles::default(), 0x1, |n| n.push_nibble_back(0x1));
test_and_assert_nib_push_func(0x1, 0x12, |n| n.push_nibble_back(0x2));
test_and_assert_nib_push_func(
Nibbles::from_str(LONG_ZERO_NIBS_STR_LEN_63).unwrap(),
Nibbles::from_str("0x0000000000000000000000000000000000000000000000000000000000000001")
.unwrap(),
Nibbles::from_str(ZERO_NIBS_63).unwrap(),
Nibbles::from_str(ZERO_NIBS_64_TRAILING_1).unwrap(),
|n| n.push_nibble_back(0x1),
);
}
Expand All @@ -951,9 +966,8 @@ mod tests {
});
test_and_assert_nib_push_func(0x1234, 0x5671234, |n| n.push_nibbles_front(&0x567.into()));
test_and_assert_nib_push_func(
Nibbles::from_str(LONG_ZERO_NIBS_STR_LEN_63).unwrap(),
Nibbles::from_str("0x1000000000000000000000000000000000000000000000000000000000000000")
.unwrap(),
Nibbles::from_str(ZERO_NIBS_63).unwrap(),
Nibbles::from_str(ZERO_NIBS_64_LEADING_1).unwrap(),
|n| n.push_nibbles_front(&0x1.into()),
);
}
Expand All @@ -965,9 +979,8 @@ mod tests {
});
test_and_assert_nib_push_func(0x1234, 0x1234567, |n| n.push_nibbles_back(&0x567.into()));
test_and_assert_nib_push_func(
Nibbles::from_str(LONG_ZERO_NIBS_STR_LEN_63).unwrap(),
Nibbles::from_str("0x0000000000000000000000000000000000000000000000000000000000000001")
.unwrap(),
Nibbles::from_str(ZERO_NIBS_63).unwrap(),
Nibbles::from_str(ZERO_NIBS_64_TRAILING_1).unwrap(),
|n| n.push_nibbles_back(&0x1.into()),
);
}
Expand Down Expand Up @@ -1264,15 +1277,15 @@ mod tests {
fn nibbles_from_h256_works() {
assert_eq!(
format!("{:x}", Nibbles::from_h256_be(H256::from_low_u64_be(0))),
"0x0000000000000000000000000000000000000000000000000000000000000000",
ZERO_NIBS_64,
);
assert_eq!(
format!("{:x}", Nibbles::from_h256_be(H256::from_low_u64_be(2048))),
"0x0000000000000000000000000000000000000000000000000000000000000800"
);
assert_eq!(
format!("{:x}", Nibbles::from_h256_le(H256::from_low_u64_be(0))),
"0x0000000000000000000000000000000000000000000000000000000000000000"
ZERO_NIBS_64
);

// Note that the first bit of the `Nibbles` changes if the count is odd.
Expand Down
74 changes: 67 additions & 7 deletions mpt_trie/src/special_query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ pub struct TriePathIter<N: PartialTrie> {
// Although wrapping `curr_node` in an option might be more "Rust like", the logic is a lot
// cleaner with a bool.
terminated: bool,

/// Always include the final node we encounter even if the key does not
/// match.
always_include_final_node_if_possible: bool,
}

impl<T: PartialTrie> Iterator for TriePathIter<T> {
Expand All @@ -39,9 +43,12 @@ impl<T: PartialTrie> Iterator for TriePathIter<T> {
Some(TrieSegment::Hash)
}
Node::Branch { children, .. } => {
// Our query key has ended. Stop here.
if self.curr_key.is_empty() {
// Our query key has ended. Stop here.
self.terminated = true;

// In this case even if `always_include_final_node` is set, we have no
// information on which branch to take, so we can't add in the last node.
return None;
}

Expand All @@ -58,7 +65,8 @@ impl<T: PartialTrie> Iterator for TriePathIter<T> {
false => {
// Only a partial match. Stop.
self.terminated = true;
None
self.always_include_final_node_if_possible
.then_some(TrieSegment::Extension(*nibbles))
}
true => {
pop_nibbles_clamped(&mut self.curr_key, nibbles.count);
Expand All @@ -72,7 +80,7 @@ impl<T: PartialTrie> Iterator for TriePathIter<T> {
Node::Leaf { nibbles, .. } => {
self.terminated = true;

match self.curr_key == *nibbles {
match self.curr_key == *nibbles || self.always_include_final_node_if_possible {
false => None,
true => Some(TrieSegment::Leaf(*nibbles)),
}
Expand All @@ -93,14 +101,19 @@ fn pop_nibbles_clamped(nibbles: &mut Nibbles, n: usize) -> Nibbles {
/// Note that if the key does not match the entire key of a node (eg. the
/// remaining key is `0x34` but the next key is a leaf with the key `0x3456`),
/// then the leaf will not appear in the query output.
pub fn path_for_query<K, T: PartialTrie>(trie: &Node<T>, k: K) -> TriePathIter<T>
pub fn path_for_query<K, T: PartialTrie>(
trie: &Node<T>,
k: K,
always_include_final_node_if_possible: bool,
) -> TriePathIter<T>
where
K: Into<Nibbles>,
{
TriePathIter {
curr_node: trie.clone().into(),
curr_key: k.into(),
terminated: false,
always_include_final_node_if_possible,
}
}

Expand All @@ -109,10 +122,15 @@ mod test {
use std::str::FromStr;

use super::path_for_query;
use crate::{nibbles::Nibbles, testing_utils::handmade_trie_1, utils::TrieSegment};
use crate::{
nibbles::Nibbles,
testing_utils::{common_setup, handmade_trie_1},
utils::TrieSegment,
};

#[test]
fn query_iter_works() {
fn query_iter_works_no_last_node() {
common_setup();
let (trie, ks) = handmade_trie_1();

// ks --> vec![0x1234, 0x1324, 0x132400005_u64, 0x2001, 0x2002];
Expand Down Expand Up @@ -149,8 +167,50 @@ mod test {
];

for (q, expected) in ks.into_iter().zip(res.into_iter()) {
let res: Vec<_> = path_for_query(&trie.node, q).collect();
let res: Vec<_> = path_for_query(&trie.node, q, false).collect();
assert_eq!(res, expected)
}
}

#[test]
fn query_iter_works_with_last_node() {
common_setup();
let (trie, _) = handmade_trie_1();

let extension_expected = vec![
TrieSegment::Branch(1),
TrieSegment::Branch(3),
TrieSegment::Extension(0x24.into()),
];

assert_eq!(
path_for_query(&trie, 0x13, true).collect::<Vec<_>>(),
extension_expected
);
assert_eq!(
path_for_query(&trie, 0x132, true).collect::<Vec<_>>(),
extension_expected
);

// The last node is a branch here, but we don't include it because a TrieSegment
// requires us to state which nibble we took in the branch, and we don't have
// this information.
assert_eq!(
path_for_query(&trie, 0x1324, true).collect::<Vec<_>>(),
extension_expected
);

let mut leaf_expected = extension_expected.clone();
leaf_expected.push(TrieSegment::Branch(0));
leaf_expected.push(TrieSegment::Leaf(Nibbles::from_str("0x0005").unwrap()));

assert_eq!(
path_for_query(&trie, 0x13240, true).collect::<Vec<_>>(),
leaf_expected
);
assert_eq!(
path_for_query(&trie, 0x132400, true).collect::<Vec<_>>(),
leaf_expected
);
}
}
14 changes: 10 additions & 4 deletions mpt_trie/src/trie_subsets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -307,11 +307,17 @@ fn mark_nodes_that_are_needed<N: PartialTrie>(
return mark_nodes_that_are_needed(&mut children[nib as usize], curr_nibbles);
}
TrackedNodeIntern::Extension(child) => {
let nibbles = trie.info.get_nibbles_expected();
let r = curr_nibbles.pop_nibbles_front(nibbles.count);
// If we hit an extension node, we always must include it unless we exhausted
// our key.
if curr_nibbles.is_empty() {
return Ok(());
}

if r.nibbles_are_identical_up_to_smallest_count(nibbles) {
trie.info.touched = true;
trie.info.touched = true;

let nibbles: &Nibbles = trie.info.get_nibbles_expected();
if curr_nibbles.nibbles_are_identical_up_to_smallest_count(nibbles) {
curr_nibbles.pop_nibbles_front(nibbles.count);
return mark_nodes_that_are_needed(child, curr_nibbles);
}
}
Expand Down
4 changes: 2 additions & 2 deletions mpt_trie/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ impl TrieSegment {
}

/// Extracts the key piece used by the segment (if applicable).
pub fn get_key_piece_from_seg_if_present(&self) -> Option<Nibbles> {
pub(crate) fn get_key_piece_from_seg_if_present(&self) -> Option<Nibbles> {
match self {
TrieSegment::Empty | TrieSegment::Hash => None,
TrieSegment::Branch(nib) => Some(Nibbles::from_nibble(*nib)),
Expand All @@ -230,7 +230,7 @@ impl TrieSegment {
/// This function is intended to be used during a trie query as we are
/// traversing down a trie. Depending on the current node, we pop off nibbles
/// and use these to create `TrieSegment`s.
pub fn get_segment_from_node_and_key_piece<T: PartialTrie>(
pub(crate) fn get_segment_from_node_and_key_piece<T: PartialTrie>(
n: &Node<T>,
k_piece: &Nibbles,
) -> TrieSegment {
Expand Down
Loading