From 78b5bf5e9adc68ec8586daec337f554a3c19ba4e Mon Sep 17 00:00:00 2001 From: teor Date: Mon, 3 Aug 2020 17:47:32 +1000 Subject: [PATCH] refactor: Move the difficulty tests into their own file --- zebra-chain/src/block/difficulty.rs | 278 +-------------------- zebra-chain/src/block/difficulty/tests.rs | 281 ++++++++++++++++++++++ 2 files changed, 283 insertions(+), 276 deletions(-) create mode 100644 zebra-chain/src/block/difficulty/tests.rs diff --git a/zebra-chain/src/block/difficulty.rs b/zebra-chain/src/block/difficulty.rs index 332cee6d..819b99f3 100644 --- a/zebra-chain/src/block/difficulty.rs +++ b/zebra-chain/src/block/difficulty.rs @@ -17,10 +17,10 @@ use std::fmt; use primitive_types::U256; -#[cfg(test)] -use proptest::prelude::*; #[cfg(test)] use proptest_derive::Arbitrary; +#[cfg(test)] +mod tests; /// A 32-bit "compact bits" value, which represents the difficulty threshold for /// a block header. @@ -243,277 +243,3 @@ impl PartialOrd for BlockHeaderHash { } } } - -#[cfg(test)] -impl Arbitrary for ExpandedDifficulty { - type Parameters = (); - - fn arbitrary_with(_args: ()) -> Self::Strategy { - (any::<[u8; 32]>()) - .prop_map(|v| ExpandedDifficulty(U256::from_little_endian(&v))) - .boxed() - } - - type Strategy = BoxedStrategy; -} - -#[cfg(test)] -mod tests { - use super::*; - - use color_eyre::eyre::Report; - use std::sync::Arc; - - use crate::block::Block; - use crate::serialization::ZcashDeserialize; - - // Alias the struct constants here, so the code is easier to read. - const PRECISION: u32 = CompactDifficulty::PRECISION; - const SIGN_BIT: u32 = CompactDifficulty::SIGN_BIT; - const UNSIGNED_MANTISSA_MASK: u32 = CompactDifficulty::UNSIGNED_MANTISSA_MASK; - const OFFSET: i32 = CompactDifficulty::OFFSET; - - /// Test debug formatting. - #[test] - fn debug_format() { - zebra_test::init(); - - assert_eq!( - format!("{:?}", CompactDifficulty(0)), - "CompactDifficulty(0x00000000)" - ); - assert_eq!( - format!("{:?}", CompactDifficulty(1)), - "CompactDifficulty(0x00000001)" - ); - assert_eq!( - format!("{:?}", CompactDifficulty(u32::MAX)), - "CompactDifficulty(0xffffffff)" - ); - - assert_eq!(format!("{:?}", ExpandedDifficulty(U256::zero())), "ExpandedDifficulty(\"0000000000000000000000000000000000000000000000000000000000000000\")"); - assert_eq!(format!("{:?}", ExpandedDifficulty(U256::one())), "ExpandedDifficulty(\"0100000000000000000000000000000000000000000000000000000000000000\")"); - assert_eq!(format!("{:?}", ExpandedDifficulty(U256::MAX)), "ExpandedDifficulty(\"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\")"); - } - - /// Test zero values for CompactDifficulty. - #[test] - fn compact_zero() { - zebra_test::init(); - - let natural_zero = CompactDifficulty(0); - assert_eq!(natural_zero.to_expanded(), None); - - // Small value zeroes - let small_zero_1 = CompactDifficulty(1); - assert_eq!(small_zero_1.to_expanded(), None); - let small_zero_max = CompactDifficulty(UNSIGNED_MANTISSA_MASK); - assert_eq!(small_zero_max.to_expanded(), None); - - // Special-cased zeroes, negative in the floating-point representation - let sc_zero = CompactDifficulty(SIGN_BIT); - assert_eq!(sc_zero.to_expanded(), None); - let sc_zero_next = CompactDifficulty(SIGN_BIT + 1); - assert_eq!(sc_zero_next.to_expanded(), None); - let sc_zero_high = CompactDifficulty((1 << PRECISION) - 1); - assert_eq!(sc_zero_high.to_expanded(), None); - let sc_zero_max = CompactDifficulty(u32::MAX); - assert_eq!(sc_zero_max.to_expanded(), None); - } - - /// Test extreme values for CompactDifficulty. - #[test] - fn compact_extremes() { - zebra_test::init(); - - // Values equal to one - let expanded_one = Some(ExpandedDifficulty(U256::one())); - - let one = CompactDifficulty(OFFSET as u32 * (1 << PRECISION) + 1); - assert_eq!(one.to_expanded(), expanded_one); - let another_one = CompactDifficulty((1 << PRECISION) + (1 << 16)); - assert_eq!(another_one.to_expanded(), expanded_one); - - // Maximum mantissa - let expanded_mant = Some(ExpandedDifficulty(UNSIGNED_MANTISSA_MASK.into())); - - let mant = CompactDifficulty(OFFSET as u32 * (1 << PRECISION) + UNSIGNED_MANTISSA_MASK); - assert_eq!(mant.to_expanded(), expanded_mant); - - // Maximum valid exponent - let exponent: U256 = (31 * 8).into(); - let expanded_exp = Some(ExpandedDifficulty(U256::from(2).pow(exponent))); - - let exp = CompactDifficulty((31 + OFFSET as u32) * (1 << PRECISION) + 1); - assert_eq!(exp.to_expanded(), expanded_exp); - - // Maximum valid mantissa and exponent - let exponent: U256 = (29 * 8).into(); - let expanded_me = U256::from(UNSIGNED_MANTISSA_MASK) * U256::from(2).pow(exponent); - let expanded_me = Some(ExpandedDifficulty(expanded_me)); - - let me = CompactDifficulty((31 + 1) * (1 << PRECISION) + UNSIGNED_MANTISSA_MASK); - assert_eq!(me.to_expanded(), expanded_me); - - // Maximum value, at least according to the spec - // - // According to ToTarget() in the spec, this value is - // `(2^23 - 1) * 256^253`, which is larger than the maximum expanded - // value. Therefore, a block can never pass with this threshold. - // - // zcashd rejects these blocks without comparing the hash. - let difficulty_max = CompactDifficulty(u32::MAX & !SIGN_BIT); - assert_eq!(difficulty_max.to_expanded(), None); - } - - /// Test blocks using CompactDifficulty. - #[test] - #[spandoc::spandoc] - fn block_difficulty() -> Result<(), Report> { - zebra_test::init(); - - let mut blockchain = Vec::new(); - for b in &[ - &zebra_test::vectors::BLOCK_MAINNET_GENESIS_BYTES[..], - &zebra_test::vectors::BLOCK_MAINNET_1_BYTES[..], - &zebra_test::vectors::BLOCK_MAINNET_2_BYTES[..], - &zebra_test::vectors::BLOCK_MAINNET_3_BYTES[..], - &zebra_test::vectors::BLOCK_MAINNET_4_BYTES[..], - &zebra_test::vectors::BLOCK_MAINNET_5_BYTES[..], - &zebra_test::vectors::BLOCK_MAINNET_6_BYTES[..], - &zebra_test::vectors::BLOCK_MAINNET_7_BYTES[..], - &zebra_test::vectors::BLOCK_MAINNET_8_BYTES[..], - &zebra_test::vectors::BLOCK_MAINNET_9_BYTES[..], - &zebra_test::vectors::BLOCK_MAINNET_10_BYTES[..], - ] { - let block = Arc::::zcash_deserialize(*b)?; - let hash: BlockHeaderHash = block.as_ref().into(); - blockchain.push((block.clone(), block.coinbase_height().unwrap(), hash)); - } - - let zero = ExpandedDifficulty(U256::zero()); - let one = ExpandedDifficulty(U256::one()); - let max_value = ExpandedDifficulty(U256::MAX); - for (block, height, hash) in blockchain { - /// SPANDOC: Calculate the threshold for mainnet block {?height} - let threshold = block - .header - .difficulty_threshold - .to_expanded() - .expect("Chain blocks have valid difficulty thresholds."); - - /// SPANDOC: Check the difficulty for mainnet block {?height, ?threshold, ?hash} - { - assert!(hash <= threshold); - // also check the comparison operators work - assert!(hash > zero); - assert!(hash > one); - assert!(hash < max_value); - } - } - - Ok(()) - } - - /// Test ExpandedDifficulty ordering - #[test] - #[spandoc::spandoc] - #[allow(clippy::eq_op)] - fn expanded_order() -> Result<(), Report> { - zebra_test::init(); - - let zero = ExpandedDifficulty(U256::zero()); - let one = ExpandedDifficulty(U256::one()); - let max_value = ExpandedDifficulty(U256::MAX); - - assert!(zero < one); - assert!(zero < max_value); - assert!(one < max_value); - - assert_eq!(zero, zero); - assert!(zero <= one); - assert!(one >= zero); - assert!(one > zero); - - Ok(()) - } - - /// Test ExpandedDifficulty and BlockHeaderHash ordering - #[test] - #[spandoc::spandoc] - fn expanded_hash_order() -> Result<(), Report> { - zebra_test::init(); - - let ex_zero = ExpandedDifficulty(U256::zero()); - let ex_one = ExpandedDifficulty(U256::one()); - let ex_max = ExpandedDifficulty(U256::MAX); - let hash_zero = BlockHeaderHash([0; 32]); - let hash_max = BlockHeaderHash([0xff; 32]); - - assert_eq!(hash_zero, ex_zero); - assert!(hash_zero < ex_one); - assert!(hash_zero < ex_max); - - assert!(hash_max > ex_zero); - assert!(hash_max > ex_one); - assert_eq!(hash_max, ex_max); - - assert!(ex_one > hash_zero); - assert!(ex_one < hash_max); - - assert!(hash_zero >= ex_zero); - assert!(ex_zero >= hash_zero); - assert!(hash_zero <= ex_zero); - assert!(ex_zero <= hash_zero); - - Ok(()) - } - - proptest! { - /// Check that CompactDifficulty expands without panicking, and compares - /// correctly. - #[test] - fn prop_compact_expand(compact in any::()) { - // TODO: round-trip test, once we have ExpandedDifficulty::to_compact() - let expanded = compact.to_expanded(); - - let hash_zero = BlockHeaderHash([0; 32]); - let hash_max = BlockHeaderHash([0xff; 32]); - - if let Some(expanded) = expanded { - prop_assert!(expanded >= hash_zero); - prop_assert!(expanded <= hash_max); - } - } - - /// Check that a random ExpandedDifficulty compares correctly with fixed BlockHeaderHashes. - #[test] - fn prop_expanded_order(expanded in any::()) { - // TODO: round-trip test, once we have ExpandedDifficulty::to_compact() - let hash_zero = BlockHeaderHash([0; 32]); - let hash_max = BlockHeaderHash([0xff; 32]); - - prop_assert!(expanded >= hash_zero); - prop_assert!(expanded <= hash_max); - } - - /// Check that ExpandedDifficulty compares correctly with a random BlockHeaderHash. - #[test] - fn prop_hash_order(hash in any::()) { - let ex_zero = ExpandedDifficulty(U256::zero()); - let ex_one = ExpandedDifficulty(U256::one()); - let ex_max = ExpandedDifficulty(U256::MAX); - - prop_assert!(hash >= ex_zero); - prop_assert!(hash <= ex_max); - prop_assert!(hash >= ex_one || hash == ex_zero); - } - - /// Check that a random ExpandedDifficulty and BlockHeaderHash compare correctly. - #[test] - #[allow(clippy::double_comparisons)] - fn prop_expanded_hash_order(expanded in any::(), hash in any::()) { - prop_assert!(expanded < hash || expanded > hash || expanded == hash); - } - } -} diff --git a/zebra-chain/src/block/difficulty/tests.rs b/zebra-chain/src/block/difficulty/tests.rs new file mode 100644 index 00000000..45d44784 --- /dev/null +++ b/zebra-chain/src/block/difficulty/tests.rs @@ -0,0 +1,281 @@ +//! Tests for difficulty and work + +use super::*; + +use crate::block::Block; +use crate::serialization::ZcashDeserialize; + +use color_eyre::eyre::Report; +use proptest::prelude::*; +use std::sync::Arc; + +// Alias the struct constants here, so the code is easier to read. +const PRECISION: u32 = CompactDifficulty::PRECISION; +const SIGN_BIT: u32 = CompactDifficulty::SIGN_BIT; +const UNSIGNED_MANTISSA_MASK: u32 = CompactDifficulty::UNSIGNED_MANTISSA_MASK; +const OFFSET: i32 = CompactDifficulty::OFFSET; + +impl Arbitrary for ExpandedDifficulty { + type Parameters = (); + + fn arbitrary_with(_args: ()) -> Self::Strategy { + (any::<[u8; 32]>()) + .prop_map(|v| ExpandedDifficulty(U256::from_little_endian(&v))) + .boxed() + } + + type Strategy = BoxedStrategy; +} + +/// Test debug formatting. +#[test] +fn debug_format() { + zebra_test::init(); + + assert_eq!( + format!("{:?}", CompactDifficulty(0)), + "CompactDifficulty(0x00000000)" + ); + assert_eq!( + format!("{:?}", CompactDifficulty(1)), + "CompactDifficulty(0x00000001)" + ); + assert_eq!( + format!("{:?}", CompactDifficulty(u32::MAX)), + "CompactDifficulty(0xffffffff)" + ); + + assert_eq!( + format!("{:?}", ExpandedDifficulty(U256::zero())), + "ExpandedDifficulty(\"0000000000000000000000000000000000000000000000000000000000000000\")" + ); + assert_eq!( + format!("{:?}", ExpandedDifficulty(U256::one())), + "ExpandedDifficulty(\"0100000000000000000000000000000000000000000000000000000000000000\")" + ); + assert_eq!( + format!("{:?}", ExpandedDifficulty(U256::MAX)), + "ExpandedDifficulty(\"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\")" + ); +} + +/// Test zero values for CompactDifficulty. +#[test] +fn compact_zero() { + zebra_test::init(); + + let natural_zero = CompactDifficulty(0); + assert_eq!(natural_zero.to_expanded(), None); + + // Small value zeroes + let small_zero_1 = CompactDifficulty(1); + assert_eq!(small_zero_1.to_expanded(), None); + let small_zero_max = CompactDifficulty(UNSIGNED_MANTISSA_MASK); + assert_eq!(small_zero_max.to_expanded(), None); + + // Special-cased zeroes, negative in the floating-point representation + let sc_zero = CompactDifficulty(SIGN_BIT); + assert_eq!(sc_zero.to_expanded(), None); + let sc_zero_next = CompactDifficulty(SIGN_BIT + 1); + assert_eq!(sc_zero_next.to_expanded(), None); + let sc_zero_high = CompactDifficulty((1 << PRECISION) - 1); + assert_eq!(sc_zero_high.to_expanded(), None); + let sc_zero_max = CompactDifficulty(u32::MAX); + assert_eq!(sc_zero_max.to_expanded(), None); +} + +/// Test extreme values for CompactDifficulty. +#[test] +fn compact_extremes() { + zebra_test::init(); + + // Values equal to one + let expanded_one = Some(ExpandedDifficulty(U256::one())); + + let one = CompactDifficulty(OFFSET as u32 * (1 << PRECISION) + 1); + assert_eq!(one.to_expanded(), expanded_one); + let another_one = CompactDifficulty((1 << PRECISION) + (1 << 16)); + assert_eq!(another_one.to_expanded(), expanded_one); + + // Maximum mantissa + let expanded_mant = Some(ExpandedDifficulty(UNSIGNED_MANTISSA_MASK.into())); + + let mant = CompactDifficulty(OFFSET as u32 * (1 << PRECISION) + UNSIGNED_MANTISSA_MASK); + assert_eq!(mant.to_expanded(), expanded_mant); + + // Maximum valid exponent + let exponent: U256 = (31 * 8).into(); + let expanded_exp = Some(ExpandedDifficulty(U256::from(2).pow(exponent))); + + let exp = CompactDifficulty((31 + OFFSET as u32) * (1 << PRECISION) + 1); + assert_eq!(exp.to_expanded(), expanded_exp); + + // Maximum valid mantissa and exponent + let exponent: U256 = (29 * 8).into(); + let expanded_me = U256::from(UNSIGNED_MANTISSA_MASK) * U256::from(2).pow(exponent); + let expanded_me = Some(ExpandedDifficulty(expanded_me)); + + let me = CompactDifficulty((31 + 1) * (1 << PRECISION) + UNSIGNED_MANTISSA_MASK); + assert_eq!(me.to_expanded(), expanded_me); + + // Maximum value, at least according to the spec + // + // According to ToTarget() in the spec, this value is + // `(2^23 - 1) * 256^253`, which is larger than the maximum expanded + // value. Therefore, a block can never pass with this threshold. + // + // zcashd rejects these blocks without comparing the hash. + let difficulty_max = CompactDifficulty(u32::MAX & !SIGN_BIT); + assert_eq!(difficulty_max.to_expanded(), None); +} + +/// Test blocks using CompactDifficulty. +#[test] +#[spandoc::spandoc] +fn block_difficulty() -> Result<(), Report> { + zebra_test::init(); + + let mut blockchain = Vec::new(); + for b in &[ + &zebra_test::vectors::BLOCK_MAINNET_GENESIS_BYTES[..], + &zebra_test::vectors::BLOCK_MAINNET_1_BYTES[..], + &zebra_test::vectors::BLOCK_MAINNET_2_BYTES[..], + &zebra_test::vectors::BLOCK_MAINNET_3_BYTES[..], + &zebra_test::vectors::BLOCK_MAINNET_4_BYTES[..], + &zebra_test::vectors::BLOCK_MAINNET_5_BYTES[..], + &zebra_test::vectors::BLOCK_MAINNET_6_BYTES[..], + &zebra_test::vectors::BLOCK_MAINNET_7_BYTES[..], + &zebra_test::vectors::BLOCK_MAINNET_8_BYTES[..], + &zebra_test::vectors::BLOCK_MAINNET_9_BYTES[..], + &zebra_test::vectors::BLOCK_MAINNET_10_BYTES[..], + ] { + let block = Arc::::zcash_deserialize(*b)?; + let hash: BlockHeaderHash = block.as_ref().into(); + blockchain.push((block.clone(), block.coinbase_height().unwrap(), hash)); + } + + let zero = ExpandedDifficulty(U256::zero()); + let one = ExpandedDifficulty(U256::one()); + let max_value = ExpandedDifficulty(U256::MAX); + for (block, height, hash) in blockchain { + /// SPANDOC: Calculate the threshold for mainnet block {?height} + let threshold = block + .header + .difficulty_threshold + .to_expanded() + .expect("Chain blocks have valid difficulty thresholds."); + + /// SPANDOC: Check the difficulty for mainnet block {?height, ?threshold, ?hash} + { + assert!(hash <= threshold); + // also check the comparison operators work + assert!(hash > zero); + assert!(hash > one); + assert!(hash < max_value); + } + } + + Ok(()) +} + +/// Test ExpandedDifficulty ordering +#[test] +#[spandoc::spandoc] +#[allow(clippy::eq_op)] +fn expanded_order() -> Result<(), Report> { + zebra_test::init(); + + let zero = ExpandedDifficulty(U256::zero()); + let one = ExpandedDifficulty(U256::one()); + let max_value = ExpandedDifficulty(U256::MAX); + + assert!(zero < one); + assert!(zero < max_value); + assert!(one < max_value); + + assert_eq!(zero, zero); + assert!(zero <= one); + assert!(one >= zero); + assert!(one > zero); + + Ok(()) +} + +/// Test ExpandedDifficulty and BlockHeaderHash ordering +#[test] +#[spandoc::spandoc] +fn expanded_hash_order() -> Result<(), Report> { + zebra_test::init(); + + let ex_zero = ExpandedDifficulty(U256::zero()); + let ex_one = ExpandedDifficulty(U256::one()); + let ex_max = ExpandedDifficulty(U256::MAX); + let hash_zero = BlockHeaderHash([0; 32]); + let hash_max = BlockHeaderHash([0xff; 32]); + + assert_eq!(hash_zero, ex_zero); + assert!(hash_zero < ex_one); + assert!(hash_zero < ex_max); + + assert!(hash_max > ex_zero); + assert!(hash_max > ex_one); + assert_eq!(hash_max, ex_max); + + assert!(ex_one > hash_zero); + assert!(ex_one < hash_max); + + assert!(hash_zero >= ex_zero); + assert!(ex_zero >= hash_zero); + assert!(hash_zero <= ex_zero); + assert!(ex_zero <= hash_zero); + + Ok(()) +} + +proptest! { + /// Check that CompactDifficulty expands without panicking, and compares + /// correctly. + #[test] + fn prop_compact_expand(compact in any::()) { + // TODO: round-trip test, once we have ExpandedDifficulty::to_compact() + let expanded = compact.to_expanded(); + + let hash_zero = BlockHeaderHash([0; 32]); + let hash_max = BlockHeaderHash([0xff; 32]); + + if let Some(expanded) = expanded { + prop_assert!(expanded >= hash_zero); + prop_assert!(expanded <= hash_max); + } + } + + /// Check that a random ExpandedDifficulty compares correctly with fixed BlockHeaderHashes. + #[test] + fn prop_expanded_order(expanded in any::()) { + // TODO: round-trip test, once we have ExpandedDifficulty::to_compact() + let hash_zero = BlockHeaderHash([0; 32]); + let hash_max = BlockHeaderHash([0xff; 32]); + + prop_assert!(expanded >= hash_zero); + prop_assert!(expanded <= hash_max); + } + + /// Check that ExpandedDifficulty compares correctly with a random BlockHeaderHash. + #[test] + fn prop_hash_order(hash in any::()) { + let ex_zero = ExpandedDifficulty(U256::zero()); + let ex_one = ExpandedDifficulty(U256::one()); + let ex_max = ExpandedDifficulty(U256::MAX); + + prop_assert!(hash >= ex_zero); + prop_assert!(hash <= ex_max); + prop_assert!(hash >= ex_one || hash == ex_zero); + } + + /// Check that a random ExpandedDifficulty and BlockHeaderHash compare correctly. + #[test] + #[allow(clippy::double_comparisons)] + fn prop_expanded_hash_order(expanded in any::(), hash in any::()) { + prop_assert!(expanded < hash || expanded > hash || expanded == hash); + } +}