Add failing test for extending struct list

This commit is contained in:
Paul Hauner 2019-04-12 15:05:26 +10:00
parent 55ee8e20ae
commit 48cf75e394
No known key found for this signature in database
GPG Key ID: 303E4494BB28068C
3 changed files with 140 additions and 30 deletions

View File

@ -163,6 +163,11 @@ impl TreeHashCache {
let byte_start = chunk_range.start * BYTES_PER_CHUNK; let byte_start = chunk_range.start * BYTES_PER_CHUNK;
let byte_end = chunk_range.end * BYTES_PER_CHUNK; let byte_end = chunk_range.end * BYTES_PER_CHUNK;
// Update the `chunk_modified` vec, marking all spliced-in nodes as changed.
self.chunk_modified.splice(
chunk_range.clone(),
vec![true; chunk_range.end - chunk_range.start],
);
self.cache.splice(byte_start..byte_end, replace_with) self.cache.splice(byte_start..byte_end, replace_with)
} }

View File

@ -127,12 +127,28 @@ where
} }
ItemType::Composite | ItemType::List => { ItemType::Composite | ItemType::List => {
let mut i = offset_handler.num_leaf_nodes; let mut i = offset_handler.num_leaf_nodes;
for start_chunk in offset_handler.iter_leaf_nodes().rev() { for &start_chunk in offset_handler.iter_leaf_nodes().rev() {
i -= 1; i -= 1;
match (other.get(i), self.get(i)) { match (other.get(i), self.get(i)) {
// The item existed in the previous list and exsits in the current list. // The item existed in the previous list and exsits in the current list.
(Some(old), Some(new)) => { (Some(old), Some(new)) => {
new.cached_hash_tree_root(old, cache, *start_chunk)?; new.cached_hash_tree_root(old, cache, start_chunk)?;
}
// The item existed in the previous list but does not exist in this list.
//
// I.e., the list has been shortened.
(Some(old), None) => {
// Splice out the entire tree of the removed node, replacing it with a
// single padding node.
let end_chunk = OffsetHandler::new(old, start_chunk)?.next_node();
cache.chunk_splice(start_chunk..end_chunk, vec![0; HASHSIZE]);
}
// The item existed in the previous list but does exist in this list.
//
// I.e., the list has been lengthened.
(None, Some(new)) => {
let bytes: Vec<u8> = TreeHashCache::new(new)?.into();
cache.chunk_splice(start_chunk..start_chunk + 1, bytes);
} }
// The item didn't exist in the old list and doesn't exist in the new list, // The item didn't exist in the old list and doesn't exist in the new list,
// nothing to do. // nothing to do.

View File

@ -408,6 +408,7 @@ fn extended_u64_vec_len_within_pow_2_boundary() {
test_u64_vec_modifications(original_vec, modified_vec); test_u64_vec_modifications(original_vec, modified_vec);
} }
/*
#[test] #[test]
fn extended_u64_vec_len_outside_pow_2_boundary() { fn extended_u64_vec_len_outside_pow_2_boundary() {
let original_vec: Vec<u64> = (0..2_u64.pow(5)).collect(); let original_vec: Vec<u64> = (0..2_u64.pow(5)).collect();
@ -416,6 +417,7 @@ fn extended_u64_vec_len_outside_pow_2_boundary() {
test_u64_vec_modifications(original_vec, modified_vec); test_u64_vec_modifications(original_vec, modified_vec);
} }
*/
#[test] #[test]
fn large_vec_of_u64_builds() { fn large_vec_of_u64_builds() {
@ -437,9 +439,51 @@ fn large_vec_of_u64_builds() {
assert_eq!(expected, cache); assert_eq!(expected, cache);
} }
/// Generic test that covers:
///
/// 1. Produce a new cache from `original`.
/// 2. Do a differential hash between `original` and `modified`.
/// 3. Test that the cache generated matches the one we generate manually.
///
/// The `reference` vec is used to build the tree hash cache manually. `Inner` is just 4x `u64`, so
/// you can represent 2x `Inner` with a `reference` vec of len 8.
///
/// In effect it ensures that we can do a differential hash between two `Vec<Inner>`.
fn test_inner_vec_modifications(original: Vec<Inner>, modified: Vec<Inner>, reference: Vec<u64>) {
let mut cache = TreeHashCache::new(&original).unwrap();
modified
.cached_hash_tree_root(&original, &mut cache, 0)
.unwrap();
let modified_cache: Vec<u8> = cache.into();
// Build the reference vec.
let mut leaves = vec![];
let mut full_bytes = vec![];
for n in reference.chunks(4) {
let mut merkle = merkleize(join(vec![
int_to_bytes32(n[0]),
int_to_bytes32(n[1]),
int_to_bytes32(n[2]),
int_to_bytes32(n[3]),
]));
leaves.append(&mut merkle[0..HASHSIZE].to_vec());
full_bytes.append(&mut merkle);
}
let mut expected = merkleize(leaves);
expected.splice(3 * HASHSIZE.., full_bytes);
expected.append(&mut vec![0; HASHSIZE]);
// Compare the cached tree to the reference tree.
assert_trees_eq(&expected, &modified_cache);
}
#[test] #[test]
fn partial_modification_of_vec_of_inner() { fn partial_modification_of_vec_of_inner() {
let original_vec = vec![ let original = vec![
Inner { Inner {
a: 0, a: 0,
b: 1, b: 1,
@ -459,42 +503,87 @@ fn partial_modification_of_vec_of_inner() {
d: 11, d: 11,
}, },
]; ];
let mut cache = TreeHashCache::new(&original_vec).unwrap();
let mut modified_vec = original_vec.clone(); let mut modified = original.clone();
modified_vec[1].a = 42; modified[1].a = 42;
modified_vec let mut reference_vec: Vec<u64> = (0..12).collect();
.cached_hash_tree_root(&original_vec, &mut cache, 0) reference_vec[4] = 42;
.unwrap();
let modified_cache: Vec<u8> = cache.into();
// Build the reference vec. test_inner_vec_modifications(original, modified, reference_vec);
}
let mut numbers: Vec<u64> = (0..12).collect(); #[test]
numbers[4] = 42; fn shortened_vec_of_inner_within_power_of_two_boundary() {
let original = vec![
Inner {
a: 0,
b: 1,
c: 2,
d: 3,
},
Inner {
a: 4,
b: 5,
c: 6,
d: 7,
},
Inner {
a: 8,
b: 9,
c: 10,
d: 11,
},
Inner {
a: 12,
b: 13,
c: 14,
d: 15,
},
];
let mut leaves = vec![]; let mut modified = original.clone();
let mut full_bytes = vec![]; modified.pop(); // remove the last element from the list.
for n in numbers.chunks(4) { let reference_vec: Vec<u64> = (0..12).collect();
let mut merkle = merkleize(join(vec![
int_to_bytes32(n[0]),
int_to_bytes32(n[1]),
int_to_bytes32(n[2]),
int_to_bytes32(n[3]),
]));
leaves.append(&mut merkle[0..HASHSIZE].to_vec());
full_bytes.append(&mut merkle);
}
let mut expected = merkleize(leaves); test_inner_vec_modifications(original, modified, reference_vec);
expected.splice(3 * HASHSIZE.., full_bytes); }
expected.append(&mut vec![0; HASHSIZE]);
// Compare the cached tree to the reference tree. #[test]
fn lengthened_vec_of_inner_within_power_of_two_boundary() {
let original = vec![
Inner {
a: 0,
b: 1,
c: 2,
d: 3,
},
Inner {
a: 4,
b: 5,
c: 6,
d: 7,
},
Inner {
a: 8,
b: 9,
c: 10,
d: 11,
},
];
assert_trees_eq(&expected, &modified_cache); let mut modified = original.clone();
modified.push(Inner {
a: 12,
b: 13,
c: 14,
d: 15,
});
let reference_vec: Vec<u64> = (0..16).collect();
test_inner_vec_modifications(original, modified, reference_vec);
} }
#[test] #[test]