diff --git a/zcash_history/Cargo.toml b/zcash_history/Cargo.toml index 0050ac7b4..d28e41511 100644 --- a/zcash_history/Cargo.toml +++ b/zcash_history/Cargo.toml @@ -11,12 +11,16 @@ categories = ["cryptography::cryptocurrencies"] [dev-dependencies] assert_matches = "1.3.0" -quickcheck = "0.9" +proptest = "1.0.0" [dependencies] primitive-types = "0.11" byteorder = "1" blake2 = { package = "blake2b_simd", version = "1" } +proptest = { version = "1.0.0", optional = true } + +[features] +test-dependencies = ["proptest"] [lib] bench = false diff --git a/zcash_history/src/node_data.rs b/zcash_history/src/node_data.rs index 315f3fabb..def91e326 100644 --- a/zcash_history/src/node_data.rs +++ b/zcash_history/src/node_data.rs @@ -216,39 +216,57 @@ impl V2 { } } -#[cfg(test)] -impl quickcheck::Arbitrary for NodeData { - fn arbitrary(gen: &mut G) -> Self { - let mut node_data = NodeData { - consensus_branch_id: 0, - ..Default::default() - }; - gen.fill_bytes(&mut node_data.subtree_commitment[..]); - node_data.start_time = gen.next_u32(); - node_data.end_time = gen.next_u32(); - node_data.start_target = gen.next_u32(); - node_data.end_target = gen.next_u32(); - gen.fill_bytes(&mut node_data.start_sapling_root[..]); - gen.fill_bytes(&mut node_data.end_sapling_root[..]); - let mut number = [0u8; 32]; - gen.fill_bytes(&mut number[..]); - node_data.subtree_total_work = U256::from_little_endian(&number[..]); - node_data.start_height = gen.next_u64(); - node_data.end_height = gen.next_u64(); - node_data.sapling_tx = gen.next_u64(); +#[cfg(any(test, feature = "test-dependencies"))] +pub mod testing { + use primitive_types::U256; + use proptest::array::uniform32; + use proptest::prelude::{any, prop_compose}; - node_data + use super::NodeData; + + prop_compose! { + pub fn arb_node_data()( + subtree_commitment in uniform32(any::()), + start_time in any::(), + end_time in any::(), + start_target in any::(), + end_target in any::(), + start_sapling_root in uniform32(any::()), + end_sapling_root in uniform32(any::()), + subtree_total_work in uniform32(any::()), + start_height in any::(), + end_height in any::(), + sapling_tx in any::(), + ) -> NodeData { + NodeData { + consensus_branch_id: 0, + subtree_commitment, + start_time, + end_time, + start_target, + end_target, + start_sapling_root, + end_sapling_root, + subtree_total_work: U256::from_little_endian(&subtree_total_work[..]), + start_height, + end_height, + sapling_tx + } + } } } #[cfg(test)] mod tests { - use super::NodeData; - use quickcheck::{quickcheck, TestResult}; + use super::testing::arb_node_data; + use proptest::prelude::*; - quickcheck! { - fn serialization_round_trip(node_data: NodeData) -> TestResult { - TestResult::from_bool(NodeData::from_bytes(0, node_data.to_bytes()).unwrap() == node_data) + use super::NodeData; + + proptest! { + #[test] + fn serialization_round_trip(node_data in arb_node_data()) { + assert_eq!(NodeData::from_bytes(0, node_data.to_bytes()).unwrap(), node_data); } } } diff --git a/zcash_history/src/tree.rs b/zcash_history/src/tree.rs index 85d1db8e4..67b9c2fd3 100644 --- a/zcash_history/src/tree.rs +++ b/zcash_history/src/tree.rs @@ -325,12 +325,11 @@ fn combine_nodes<'a, V: Version>(left: IndexedNode<'a, V>, right: IndexedNode<'a #[cfg(test)] mod tests { - use super::{Entry, EntryKind, EntryLink, Tree}; use crate::{node_data, NodeData, Version, V2}; use assert_matches::assert_matches; - use quickcheck::{quickcheck, TestResult}; + use proptest::prelude::*; fn leaf(height: u32) -> node_data::V2 { node_data::V2 { @@ -640,100 +639,90 @@ mod tests { assert_eq!(tree.len(), 4083); // 4095 - log2(4096) } - quickcheck! { - fn there_and_back(number: u32) -> TestResult { - if number > 1024*1024 { - TestResult::discard() - } else { - let mut tree = initial(); - for i in 0..number { - tree.append_leaf(leaf(i+3)).expect("Failed to append"); - } - for _ in 0..number { - tree.truncate_leaf().expect("Failed to truncate"); - } + proptest! { + #![proptest_config(ProptestConfig::with_cases(10))] - TestResult::from_bool(matches!(tree.root(), EntryLink::Stored(2))) + #[test] + fn prop_there_and_back(number in 0u32..=(1024*1024)) { + let mut tree = initial(); + for i in 0..number { + tree.append_leaf(leaf(i+3)).expect("Failed to append"); + } + for _ in 0..number { + tree.truncate_leaf().expect("Failed to truncate"); + } + + assert_matches!(tree.root(), EntryLink::Stored(2)); + } + + #[test] + fn prop_leaf_count(number in 3u32..=(1024*1024)) { + let mut tree = initial(); + for i in 1..(number-1) { + tree.append_leaf(leaf(i+2)).expect("Failed to append"); + } + + assert_eq!(tree.root_node().expect("no root").node.leaf_count(), number as u64); + } + + #[test] + fn prop_parity(number in 3u32..=(2048*2048)) { + let mut tree = initial(); + for i in 1..(number-1) { + tree.append_leaf(leaf(i+2)).expect("Failed to append"); + } + + if number & (number - 1) == 0 { + assert_matches!(tree.root(), EntryLink::Stored(_)); + } else { + assert_matches!(tree.root(), EntryLink::Generated(_)); } } - fn leaf_count(number: u32) -> TestResult { - if !(3..=1024 * 1024).contains(&number) { - TestResult::discard() - } else { - let mut tree = initial(); - for i in 1..(number-1) { - tree.append_leaf(leaf(i+2)).expect("Failed to append"); - } - - TestResult::from_bool( - tree.root_node().expect("no root").node.leaf_count() == number as u64 - ) - } - } - - fn parity(number: u32) -> TestResult { - if !(3..=2048 * 2048).contains(&number) { - TestResult::discard() - } else { - let mut tree = initial(); - for i in 1..(number-1) { - tree.append_leaf(leaf(i+2)).expect("Failed to append"); - } - - TestResult::from_bool( - if number & (number - 1) == 0 { - matches!(tree.root(), EntryLink::Stored(_)) - } else { - matches!(tree.root(), EntryLink::Generated(_)) - } - ) - } - } - - fn parity_with_truncate(add: u32, delete: u32) -> TestResult { + #[test] + fn prop_parity_with_truncate( + add_and_delete in (0u32..=(2048*2048)).prop_flat_map( + |add| (Just(add), 0..=add) + ) + ) { + let (add, delete) = add_and_delete; // First we add `add` number of leaves, then delete `delete` number of leaves // What is left should be consistent with generated-stored structure - if add > 2048 * 2048 || add < delete { - TestResult::discard() + let mut tree = initial(); + for i in 0..add { + tree.append_leaf(leaf(i+3)).expect("Failed to append"); + } + for _ in 0..delete { + tree.truncate_leaf().expect("Failed to truncate"); + } + + let total = add - delete + 2; + + if total & (total - 1) == 0 { + assert_matches!(tree.root(), EntryLink::Stored(_)); } else { - let mut tree = initial(); - for i in 0..add { - tree.append_leaf(leaf(i+3)).expect("Failed to append"); - } - for _ in 0..delete { - tree.truncate_leaf().expect("Failed to truncate"); - } - - let total = add - delete + 2; - - TestResult::from_bool( - if total & (total - 1) == 0 { - matches!(tree.root(), EntryLink::Stored(_)) - } else { - matches!(tree.root(), EntryLink::Generated(_)) - } - ) + assert_matches!(tree.root(), EntryLink::Generated(_)); } } - // Length of tree is always less than number of leaves squared - fn stored_length(add: u32, delete: u32) -> TestResult { - if add > 2048 * 2048 || add < delete { - TestResult::discard() - } else { - let mut tree = initial(); - for i in 0..add { - tree.append_leaf(leaf(i+3)).expect("Failed to append"); - } - for _ in 0..delete { - tree.truncate_leaf().expect("Failed to truncate"); - } - - let total = add - delete + 2; - - TestResult::from_bool(total * total > tree.len()) + #[test] + fn prop_stored_length( + add_and_delete in (0u32..=(2048*2048)).prop_flat_map( + |add| (Just(add), 0..=add) + ) + ) { + let (add, delete) = add_and_delete; + let mut tree = initial(); + for i in 0..add { + tree.append_leaf(leaf(i+3)).expect("Failed to append"); } + for _ in 0..delete { + tree.truncate_leaf().expect("Failed to truncate"); + } + + let total = add - delete + 2; + + assert!(total * total > tree.len()) } } }