Skip to content

Commit 6fc33d2

Browse files
author
Kristoffer Ström
committed
Model testing of v1 and v2 tries against a HasMap model
1 parent 88df572 commit 6fc33d2

File tree

2 files changed

+107
-86
lines changed

2 files changed

+107
-86
lines changed
Lines changed: 107 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,17 @@
1-
use crate::v2::trie::Trie;
1+
use crate::v1::sparse_mpt::{DeletionError as DeletionErrorV1, DiffTrie};
2+
use crate::v2::trie::{DeletionError as DeletionErrorV2, Trie};
3+
4+
use alloy_primitives::{hex, keccak256, Bytes, FixedBytes};
25
use quickcheck::{quickcheck, Arbitrary, Gen};
36
use std::collections::HashMap;
47

5-
// The maximum key size. keeping it relatively
6-
// small increases the chance of multiple
7-
// operations being executed against the same
8-
// key, which will tease out more bugs.
9-
const KEY_SPACE: u8 = 16;
10-
118
#[derive(Clone, Debug)]
129
enum Op {
13-
Insert(Vec<u8>, Vec<u8>),
14-
Get(Vec<u8>),
10+
Insert(FixedKey, Vec<u8>),
11+
Delete(FixedKey),
1512
}
1613

14+
// helper trait to extend `choose` with exception handling
1715
trait ChooseNonempty {
1816
fn one_of<'a, T>(&'a mut self, entries: &'a [T]) -> &'a T;
1917
}
@@ -24,46 +22,129 @@ impl ChooseNonempty for Gen {
2422
}
2523
}
2624

27-
// Arbitrary lets you create randomized instances
28-
// of types that you're interested in testing
29-
// properties with. QuickCheck will look for
30-
// this trait for things that are the arguments
31-
// to properties that it is testing.
25+
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
26+
struct FixedKey(FixedBytes<32>);
27+
28+
impl FixedKey {
29+
fn as_slice(&self) -> &[u8; 32] {
30+
&self.0
31+
}
32+
33+
fn from_string(s: &str) -> Self {
34+
Self(keccak256("a"))
35+
}
36+
37+
fn from_bytes(bytes: [u8; 32]) -> Self {
38+
Self(FixedBytes::new(bytes))
39+
}
40+
41+
fn into_bytes(self) -> Bytes {
42+
Bytes::from(self.0)
43+
}
44+
}
45+
46+
impl From<Bytes> for FixedKey {
47+
fn from(bytes: Bytes) -> FixedKey {
48+
let fbytes = FixedBytes::from_slice(bytes.as_ref());
49+
FixedKey(fbytes)
50+
}
51+
}
52+
53+
// We chose a small number of keys, to make sure our error cases handle key collisions,
54+
// as well as shared prefixes, properties that would be very unlikely for random keys
55+
impl Arbitrary for FixedKey {
56+
fn arbitrary(g: &mut Gen) -> Self {
57+
let keys = [
58+
FixedKey::from_bytes(hex!(
59+
"0000000000000000000000000000000000000000000000000000000000000000"
60+
)),
61+
FixedKey::from_bytes(hex!(
62+
"0000000000000000000000000000000000000000000000000000000000000001"
63+
)),
64+
FixedKey::from_bytes(hex!(
65+
"0000000000000000000000000000001000000000000000000000000000000001"
66+
)),
67+
FixedKey::from_string("0"),
68+
FixedKey::from_string("1"),
69+
FixedKey::from_string("2"),
70+
FixedKey::from_string("3"),
71+
FixedKey::from_string("4"),
72+
FixedKey::from_string("5"),
73+
FixedKey::from_string("6"),
74+
FixedKey::from_string("7"),
75+
];
76+
*g.one_of(&keys)
77+
}
78+
}
79+
3280
impl Arbitrary for Op {
3381
fn arbitrary(g: &mut Gen) -> Self {
3482
// pick a random key to perform an operation on
35-
let key = g
36-
.one_of(&["key00", "key01", "odd", "key010"])
37-
.as_bytes()
38-
.to_owned();
83+
let key = FixedKey::arbitrary(g);
3984

4085
if *g.one_of(&[true, false]) {
4186
Op::Insert(key, "value".into())
4287
} else {
43-
Op::Get(key)
88+
Op::Delete(key)
4489
}
4590
}
4691
}
4792

93+
/// This test fails, since the Trie is designed for fixed key sizes
94+
#[ignore]
95+
#[test]
96+
fn crash_example_v2() {
97+
let mut trie = Trie::new_empty();
98+
trie.insert(b"00aeee", b"ok").unwrap();
99+
trie.insert(b"00ae", b"ok").unwrap();
100+
}
101+
48102
quickcheck! {
49-
fn model_test_v2(ops: Vec<Op>) -> bool {
103+
fn model_test_v1_map(ops: Vec<Op>) -> bool {
104+
let mut model = HashMap::new();
105+
let mut implementation = DiffTrie::new_empty();
106+
107+
for op in ops {
108+
match op {
109+
Op::Insert(key, value) => {
110+
implementation.insert(key.into_bytes(), Bytes::from(value.clone())).unwrap();
111+
model.insert(key, value);
112+
}
113+
Op::Delete(key) => {
114+
match (implementation.delete(key.into_bytes()), model.remove(&key)) {
115+
(Err(DeletionErrorV1::KeyNotFound), None) => (),
116+
(Err(err), _) => panic!("Implementation error {err:?}"),
117+
(Ok(_), Some(_)) => (),
118+
(Ok(returned), None) => panic!("Implementation returned {returned:?} on delete"),
119+
}
120+
}
121+
}
122+
}
123+
true
124+
}
125+
}
126+
127+
quickcheck! {
128+
fn model_test_v2_map(ops: Vec<Op>) -> bool {
50129
let mut model = HashMap::new();
51130
let mut implementation = Trie::new_empty();
52131

53132
for op in ops {
54133
match op {
55134
Op::Insert(k, v) => {
56-
implementation.insert(k.as_slice(), v.as_slice());
135+
implementation.insert(k.as_slice(), v.as_slice()).unwrap();
57136
model.insert(k, v);
58137
}
59-
Op::Get(k) => {
60-
// if implementation.get(&k) != model.get(&k).map(AsRef::as_ref) {
61-
// return false;
62-
// }
138+
Op::Delete(k) => {
139+
match (implementation.delete(k.as_slice()), model.remove(&k)) {
140+
(Err(DeletionErrorV2::KeyNotFound), None) => (),
141+
(Err(e), _) => panic!("Implementation error {e:?}"),
142+
(Ok(_), Some(_)) => (),
143+
(Ok(a), None) => panic!("Implementation returned {a:?} on delete"),
144+
}
63145
}
64146
}
65147
}
66-
67148
true
68149
}
69150
}

crates/eth-sparse-mpt/src/v2/trie/mod.rs

Lines changed: 0 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -361,66 +361,6 @@ impl Trie {
361361
self.delete_nibbles_key(&n)
362362
}
363363

364-
pub fn get(&mut self, key: &[u8]) -> Option<&[u8]> {
365-
let n = Nibbles::unpack(key);
366-
self.get_nibbles_key(&n)
367-
}
368-
369-
pub fn get_nibbles_key(&mut self, nibbles_key: &Nibbles) -> Option<&[u8]> {
370-
let get_key = nibbles_key.as_slice();
371-
self.walk_path.clear();
372-
373-
let mut current_node = 0;
374-
let mut path_walked = 0;
375-
376-
loop {
377-
let node = self.nodes.get(current_node)?;
378-
379-
match node {
380-
DiffTrieNode::Branch { children } => {
381-
// deleting from branch, key not found
382-
if get_key.len() == path_walked {
383-
return None;
384-
}
385-
386-
let children = *children;
387-
388-
let n = get_key[path_walked];
389-
self.walk_path.push((current_node, n));
390-
path_walked += 1;
391-
392-
if let Some(child_ptr) = self.branch_node_children[children][n as usize] {
393-
current_node = child_ptr.local_ptr()?;
394-
continue;
395-
} else {
396-
return None;
397-
}
398-
}
399-
DiffTrieNode::Extension { key, next_node } => {
400-
let key = key.clone();
401-
let next_node = *next_node;
402-
403-
if get_key[path_walked..].starts_with(&self.keys[key.clone()]) {
404-
self.walk_path.push((current_node, 0));
405-
path_walked += key.len();
406-
current_node = next_node.local_ptr()?;
407-
continue;
408-
}
409-
410-
return None;
411-
}
412-
DiffTrieNode::Leaf { key, value } => {
413-
if self.keys[key.clone()] == get_key[path_walked..] {
414-
self.walk_path.push((current_node, 0));
415-
return Some(b"test");
416-
}
417-
return None;
418-
}
419-
DiffTrieNode::Null => return None,
420-
}
421-
}
422-
}
423-
424364
pub fn delete_nibbles_key(
425365
&mut self,
426366
nibbles_key: &Nibbles,

0 commit comments

Comments
 (0)