From 855b89dec461e8e94754261dd4d940161993df8f Mon Sep 17 00:00:00 2001 From: Henry de Valence Date: Sat, 15 Aug 2020 00:14:07 -0700 Subject: [PATCH] chain: create a new work module for proof-of-work This extracts the `difficulty` module from `block` and the `equihash_solution` module from the crate root. The PoW calculations are significantly more complicated than the other block code and pretty dissimilar from it, so it makes more sense to create a common proof of work module. The `EquihashSolution` and `EQUIHASH_SOLUTION_SIZE` are renamed to `equihash::Solution` and `equihash::SOLUTION_SIZE` and imported that way, except in `block/header.rs`, to avoid a conflict with the `equihash` crate. In the future it would be better to encapsulate the equihash solution check into the `equihash::Solution` type so that callers only need to import our `work::equihash`. The test organization leaves a little to be desired but I think that this can be improved as we fill out the proof of work implementation. --- zebra-chain/src/block.rs | 1 - zebra-chain/src/block/header.rs | 8 +- zebra-chain/src/block/serialize.rs | 5 +- zebra-chain/src/block/tests.rs | 6 +- zebra-chain/src/equihash_solution.rs | 276 ------------------ zebra-chain/src/lib.rs | 2 +- zebra-chain/src/work.rs | 7 + zebra-chain/src/{block => work}/difficulty.rs | 0 .../src/{block => work}/difficulty/tests.rs | 2 - zebra-chain/src/work/equihash.rs | 111 +++++++ zebra-chain/src/work/tests.rs | 3 + zebra-chain/src/work/tests/arbitrary.rs | 19 ++ zebra-chain/src/work/tests/prop.rs | 119 ++++++++ zebra-chain/src/work/tests/vectors.rs | 37 +++ 14 files changed, 306 insertions(+), 290 deletions(-) delete mode 100644 zebra-chain/src/equihash_solution.rs create mode 100644 zebra-chain/src/work.rs rename zebra-chain/src/{block => work}/difficulty.rs (100%) rename zebra-chain/src/{block => work}/difficulty/tests.rs (99%) create mode 100644 zebra-chain/src/work/equihash.rs create mode 100644 zebra-chain/src/work/tests.rs create mode 100644 zebra-chain/src/work/tests/arbitrary.rs create mode 100644 zebra-chain/src/work/tests/prop.rs create mode 100644 zebra-chain/src/work/tests/vectors.rs diff --git a/zebra-chain/src/block.rs b/zebra-chain/src/block.rs index de0de8eb52e..88887150e24 100644 --- a/zebra-chain/src/block.rs +++ b/zebra-chain/src/block.rs @@ -1,7 +1,6 @@ //! Definitions of block datastructures. #![allow(clippy::unit_arg)] -mod difficulty; mod hash; mod header; mod height; diff --git a/zebra-chain/src/block/header.rs b/zebra-chain/src/block/header.rs index eea612168dc..9e1adc931f3 100644 --- a/zebra-chain/src/block/header.rs +++ b/zebra-chain/src/block/header.rs @@ -1,10 +1,10 @@ use chrono::{DateTime, Duration, Utc}; -use crate::equihash_solution::EquihashSolution; use crate::merkle_tree::MerkleTreeRootHash; use crate::serialization::ZcashSerialize; +use crate::work::{difficulty::CompactDifficulty, equihash::Solution}; -use super::{difficulty::CompactDifficulty, BlockHeaderHash, Error}; +use super::{BlockHeaderHash, Error}; /// Block header. /// @@ -65,7 +65,7 @@ pub struct BlockHeader { pub nonce: [u8; 32], /// The Equihash solution. - pub solution: EquihashSolution, + pub solution: Solution, } impl BlockHeader { @@ -80,7 +80,7 @@ impl BlockHeader { self.zcash_serialize(&mut input) .expect("serialization into a vec can't fail"); - let input = &input[0..EquihashSolution::INPUT_LENGTH]; + let input = &input[0..Solution::INPUT_LENGTH]; equihash::is_valid_solution(n, k, input, nonce, solution)?; diff --git a/zebra-chain/src/block/serialize.rs b/zebra-chain/src/block/serialize.rs index 1503533a644..771b4310ad7 100644 --- a/zebra-chain/src/block/serialize.rs +++ b/zebra-chain/src/block/serialize.rs @@ -2,11 +2,10 @@ use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; use chrono::{TimeZone, Utc}; use std::io; -use crate::block::difficulty::CompactDifficulty; -use crate::equihash_solution::EquihashSolution; use crate::merkle_tree::MerkleTreeRootHash; use crate::serialization::ZcashDeserializeInto; use crate::serialization::{ReadZcashExt, SerializationError, ZcashDeserialize, ZcashSerialize}; +use crate::work::{difficulty::CompactDifficulty, equihash}; use super::Block; use super::BlockHeader; @@ -71,7 +70,7 @@ impl ZcashDeserialize for BlockHeader { time: Utc.timestamp(reader.read_u32::()? as i64, 0), difficulty_threshold: CompactDifficulty(reader.read_u32::()?), nonce: reader.read_32_bytes()?, - solution: EquihashSolution::zcash_deserialize(reader)?, + solution: equihash::Solution::zcash_deserialize(reader)?, }) } } diff --git a/zebra-chain/src/block/tests.rs b/zebra-chain/src/block/tests.rs index 2d4c376ec36..72db135c13e 100644 --- a/zebra-chain/src/block/tests.rs +++ b/zebra-chain/src/block/tests.rs @@ -1,12 +1,12 @@ use super::*; -use crate::block::{difficulty::CompactDifficulty, light_client::LightClientRootHash}; -use crate::equihash_solution::EquihashSolution; +use crate::block::light_client::LightClientRootHash; use crate::merkle_tree::MerkleTreeRootHash; use crate::serialization::{ sha256d, SerializationError, ZcashDeserialize, ZcashDeserializeInto, ZcashSerialize, }; use crate::transaction::LockTime; +use crate::work::{difficulty::CompactDifficulty, equihash}; use crate::Network; use crate::test::generate; @@ -48,7 +48,7 @@ impl Arbitrary for BlockHeader { (0i64..(u32::MAX as i64)), any::(), any::<[u8; 32]>(), - any::(), + any::(), ) .prop_map( |( diff --git a/zebra-chain/src/equihash_solution.rs b/zebra-chain/src/equihash_solution.rs deleted file mode 100644 index b9ce5c869b5..00000000000 --- a/zebra-chain/src/equihash_solution.rs +++ /dev/null @@ -1,276 +0,0 @@ -//! Equihash Solution and related items. - -use crate::serialization::{ - serde_helpers, ReadZcashExt, SerializationError, WriteZcashExt, ZcashDeserialize, - ZcashSerialize, -}; -use std::{fmt, io}; - -/// The size of an Equihash solution in bytes (always 1344). -pub(crate) const EQUIHASH_SOLUTION_SIZE: usize = 1344; - -/// Equihash Solution. -/// -/// A wrapper around [u8; 1344] because Rust doesn't implement common -/// traits like `Debug`, `Clone`, etc for collections like array -/// beyond lengths 0 to 32. -/// -/// The size of an Equihash solution in bytes is always 1344 so the -/// length of this type is fixed. -#[derive(Deserialize, Serialize)] -pub struct EquihashSolution( - #[serde(with = "serde_helpers::BigArray")] pub [u8; EQUIHASH_SOLUTION_SIZE], -); - -impl EquihashSolution { - /// The length of the portion of the header used as input when verifying - /// equihash solutions, in bytes - pub const INPUT_LENGTH: usize = 4 + 32 * 3 + 4 * 2; -} - -impl PartialEq for EquihashSolution { - fn eq(&self, other: &EquihashSolution) -> bool { - self.0.as_ref() == other.0.as_ref() - } -} - -impl fmt::Debug for EquihashSolution { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_tuple("EquihashSolution") - .field(&hex::encode(&self.0[..])) - .finish() - } -} - -// These impls all only exist because of array length restrictions. - -impl Copy for EquihashSolution {} - -impl Clone for EquihashSolution { - fn clone(&self) -> Self { - let mut bytes = [0; EQUIHASH_SOLUTION_SIZE]; - bytes[..].copy_from_slice(&self.0[..]); - Self(bytes) - } -} - -impl Eq for EquihashSolution {} - -impl ZcashSerialize for EquihashSolution { - fn zcash_serialize(&self, mut writer: W) -> Result<(), io::Error> { - writer.write_compactsize(EQUIHASH_SOLUTION_SIZE as u64)?; - writer.write_all(&self.0[..])?; - Ok(()) - } -} - -impl ZcashDeserialize for EquihashSolution { - fn zcash_deserialize(mut reader: R) -> Result { - let solution_size = reader.read_compactsize()?; - if solution_size != (EQUIHASH_SOLUTION_SIZE as u64) { - return Err(SerializationError::Parse( - "incorrect equihash solution size", - )); - } - let mut bytes = [0; EQUIHASH_SOLUTION_SIZE]; - reader.read_exact(&mut bytes[..])?; - Ok(Self(bytes)) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::block::{Block, BlockHeader}; - use crate::serialization::ZcashDeserializeInto; - use proptest::{arbitrary::Arbitrary, collection::vec, prelude::*}; - - impl Arbitrary for EquihashSolution { - type Parameters = (); - - fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { - (vec(any::(), EQUIHASH_SOLUTION_SIZE)) - .prop_map(|v| { - let mut bytes = [0; EQUIHASH_SOLUTION_SIZE]; - bytes.copy_from_slice(v.as_slice()); - Self(bytes) - }) - .boxed() - } - - type Strategy = BoxedStrategy; - } - - #[test] - fn equihash_solution_roundtrip() { - proptest!(|(solution in any::())| { - let data = solution - .zcash_serialize_to_vec() - .expect("randomized EquihashSolution should serialize"); - let solution2 = data - .zcash_deserialize_into() - .expect("randomized EquihashSolution should deserialize"); - - prop_assert_eq![solution, solution2]; - }); - } - - const EQUIHASH_SOLUTION_BLOCK_OFFSET: usize = EquihashSolution::INPUT_LENGTH + 32; - - #[test] - fn equihash_solution_test_vector() { - zebra_test::init(); - let solution_bytes = - &zebra_test::vectors::HEADER_MAINNET_415000_BYTES[EQUIHASH_SOLUTION_BLOCK_OFFSET..]; - - let solution = solution_bytes - .zcash_deserialize_into::() - .expect("Test vector EquihashSolution should deserialize"); - - let mut data = Vec::new(); - solution - .zcash_serialize(&mut data) - .expect("Test vector EquihashSolution should serialize"); - - assert_eq!(solution_bytes, data.as_slice()); - } - - #[test] - fn equihash_solution_test_vector_is_valid() -> color_eyre::eyre::Result<()> { - zebra_test::init(); - - let block = Block::zcash_deserialize(&zebra_test::vectors::BLOCK_MAINNET_415000_BYTES[..]) - .expect("block test vector should deserialize"); - block.header.is_equihash_solution_valid()?; - - Ok(()) - } - - prop_compose! { - fn randomized_solutions(real_header: BlockHeader) - (fake_solution in any::() - .prop_filter("solution must not be the actual solution", move |s| { - s != &real_header.solution - }) - ) -> BlockHeader { - let mut fake_header = real_header; - fake_header.solution = fake_solution; - fake_header - } - } - - #[test] - fn equihash_prop_test_solution() -> color_eyre::eyre::Result<()> { - zebra_test::init(); - - for block_bytes in zebra_test::vectors::TEST_BLOCKS.iter() { - let block = crate::block::Block::zcash_deserialize(&block_bytes[..]) - .expect("block test vector should deserialize"); - block.header.is_equihash_solution_valid()?; - - proptest!(|(fake_header in randomized_solutions(block.header))| { - fake_header - .is_equihash_solution_valid() - .expect_err("block header should not validate on randomized solution"); - }); - } - - Ok(()) - } - - prop_compose! { - fn randomized_nonce(real_header: BlockHeader) - (fake_nonce in proptest::array::uniform32(any::()) - .prop_filter("nonce must not be the actual nonce", move |fake_nonce| { - fake_nonce != &real_header.nonce - }) - ) -> BlockHeader { - let mut fake_header = real_header; - fake_header.nonce = fake_nonce; - fake_header - } - } - - #[test] - fn equihash_prop_test_nonce() -> color_eyre::eyre::Result<()> { - zebra_test::init(); - - for block_bytes in zebra_test::vectors::TEST_BLOCKS.iter() { - let block = crate::block::Block::zcash_deserialize(&block_bytes[..]) - .expect("block test vector should deserialize"); - block.header.is_equihash_solution_valid()?; - - proptest!(|(fake_header in randomized_nonce(block.header))| { - fake_header - .is_equihash_solution_valid() - .expect_err("block header should not validate on randomized nonce"); - }); - } - - Ok(()) - } - - prop_compose! { - fn randomized_input(real_header: BlockHeader) - (fake_header in any::() - .prop_map(move |mut fake_header| { - fake_header.nonce = real_header.nonce; - fake_header.solution = real_header.solution; - fake_header - }) - .prop_filter("input must not be the actual input", move |fake_header| { - fake_header != &real_header - }) - ) -> BlockHeader { - fake_header - } - } - - #[test] - fn equihash_prop_test_input() -> color_eyre::eyre::Result<()> { - zebra_test::init(); - - for block_bytes in zebra_test::vectors::TEST_BLOCKS.iter() { - let block = crate::block::Block::zcash_deserialize(&block_bytes[..]) - .expect("block test vector should deserialize"); - block.header.is_equihash_solution_valid()?; - - proptest!(|(fake_header in randomized_input(block.header))| { - fake_header - .is_equihash_solution_valid() - .expect_err("equihash solution should not validate on randomized input"); - }); - } - - Ok(()) - } - - static EQUIHASH_SIZE_TESTS: &[u64] = &[ - 0, - 1, - (EQUIHASH_SOLUTION_SIZE - 1) as u64, - EQUIHASH_SOLUTION_SIZE as u64, - (EQUIHASH_SOLUTION_SIZE + 1) as u64, - u64::MAX - 1, - u64::MAX, - ]; - - #[test] - fn equihash_solution_size_field() { - zebra_test::init(); - - for size in EQUIHASH_SIZE_TESTS { - let mut data = Vec::new(); - data.write_compactsize(*size as u64) - .expect("Compact size should serialize"); - data.resize(data.len() + EQUIHASH_SOLUTION_SIZE, 0); - let result = EquihashSolution::zcash_deserialize(data.as_slice()); - if *size == (EQUIHASH_SOLUTION_SIZE as u64) { - result.expect("Correct size field in EquihashSolution should deserialize"); - } else { - result - .expect_err("Wrong size field in EquihashSolution should fail on deserialize"); - } - } - } -} diff --git a/zebra-chain/src/lib.rs b/zebra-chain/src/lib.rs index 48e3c0b362f..1d6a1a532b1 100644 --- a/zebra-chain/src/lib.rs +++ b/zebra-chain/src/lib.rs @@ -17,7 +17,6 @@ pub mod addresses; pub mod amount; pub mod block; pub mod commitments; -pub mod equihash_solution; pub mod keys; pub mod notes; pub mod parameters; @@ -26,6 +25,7 @@ pub mod serialization; pub mod transaction; pub mod treestate; pub mod types; +pub mod work; pub use ed25519_zebra; pub use redjubjub; diff --git a/zebra-chain/src/work.rs b/zebra-chain/src/work.rs new file mode 100644 index 00000000000..35e87c17201 --- /dev/null +++ b/zebra-chain/src/work.rs @@ -0,0 +1,7 @@ +//! Proof-of-work implementation. + +pub mod difficulty; +pub mod equihash; + +#[cfg(test)] +mod tests; diff --git a/zebra-chain/src/block/difficulty.rs b/zebra-chain/src/work/difficulty.rs similarity index 100% rename from zebra-chain/src/block/difficulty.rs rename to zebra-chain/src/work/difficulty.rs diff --git a/zebra-chain/src/block/difficulty/tests.rs b/zebra-chain/src/work/difficulty/tests.rs similarity index 99% rename from zebra-chain/src/block/difficulty/tests.rs rename to zebra-chain/src/work/difficulty/tests.rs index a57431260b7..d31a9f3df16 100644 --- a/zebra-chain/src/block/difficulty/tests.rs +++ b/zebra-chain/src/work/difficulty/tests.rs @@ -1,5 +1,3 @@ -//! Tests for difficulty and work - use super::*; use crate::block::Block; diff --git a/zebra-chain/src/work/equihash.rs b/zebra-chain/src/work/equihash.rs new file mode 100644 index 00000000000..c92306a0d46 --- /dev/null +++ b/zebra-chain/src/work/equihash.rs @@ -0,0 +1,111 @@ +//! Equihash Solution and related items. + +use crate::serialization::{ + serde_helpers, ReadZcashExt, SerializationError, WriteZcashExt, ZcashDeserialize, + ZcashSerialize, +}; +use std::{fmt, io}; + +/// The size of an Equihash solution in bytes (always 1344). +pub(crate) const SOLUTION_SIZE: usize = 1344; + +/// Equihash Solution. +/// +/// A wrapper around [u8; 1344] because Rust doesn't implement common +/// traits like `Debug`, `Clone`, etc for collections like array +/// beyond lengths 0 to 32. +/// +/// The size of an Equihash solution in bytes is always 1344 so the +/// length of this type is fixed. +#[derive(Deserialize, Serialize)] +pub struct Solution(#[serde(with = "serde_helpers::BigArray")] pub [u8; SOLUTION_SIZE]); + +impl Solution { + /// The length of the portion of the header used as input when verifying + /// equihash solutions, in bytes + pub const INPUT_LENGTH: usize = 4 + 32 * 3 + 4 * 2; +} + +impl PartialEq for Solution { + fn eq(&self, other: &Solution) -> bool { + self.0.as_ref() == other.0.as_ref() + } +} + +impl fmt::Debug for Solution { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_tuple("EquihashSolution") + .field(&hex::encode(&self.0[..])) + .finish() + } +} + +// These impls all only exist because of array length restrictions. + +impl Copy for Solution {} + +impl Clone for Solution { + fn clone(&self) -> Self { + let mut bytes = [0; SOLUTION_SIZE]; + bytes[..].copy_from_slice(&self.0[..]); + Self(bytes) + } +} + +impl Eq for Solution {} + +impl ZcashSerialize for Solution { + fn zcash_serialize(&self, mut writer: W) -> Result<(), io::Error> { + writer.write_compactsize(SOLUTION_SIZE as u64)?; + writer.write_all(&self.0[..])?; + Ok(()) + } +} + +impl ZcashDeserialize for Solution { + fn zcash_deserialize(mut reader: R) -> Result { + let solution_size = reader.read_compactsize()?; + if solution_size != (SOLUTION_SIZE as u64) { + return Err(SerializationError::Parse( + "incorrect equihash solution size", + )); + } + let mut bytes = [0; SOLUTION_SIZE]; + reader.read_exact(&mut bytes[..])?; + Ok(Self(bytes)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + static EQUIHASH_SIZE_TESTS: &[u64] = &[ + 0, + 1, + (SOLUTION_SIZE - 1) as u64, + SOLUTION_SIZE as u64, + (SOLUTION_SIZE + 1) as u64, + u64::MAX - 1, + u64::MAX, + ]; + + #[test] + fn equihash_solution_size_field() { + zebra_test::init(); + + for size in EQUIHASH_SIZE_TESTS { + let mut data = Vec::new(); + data.write_compactsize(*size as u64) + .expect("Compact size should serialize"); + data.resize(data.len() + SOLUTION_SIZE, 0); + let result = Solution::zcash_deserialize(data.as_slice()); + if *size == (SOLUTION_SIZE as u64) { + result.expect("Correct size field in EquihashSolution should deserialize"); + } else { + result + .expect_err("Wrong size field in EquihashSolution should fail on deserialize"); + } + } + } +} diff --git a/zebra-chain/src/work/tests.rs b/zebra-chain/src/work/tests.rs new file mode 100644 index 00000000000..263a67a8a08 --- /dev/null +++ b/zebra-chain/src/work/tests.rs @@ -0,0 +1,3 @@ +mod arbitrary; +mod prop; +mod vectors; diff --git a/zebra-chain/src/work/tests/arbitrary.rs b/zebra-chain/src/work/tests/arbitrary.rs new file mode 100644 index 00000000000..0332ee71de5 --- /dev/null +++ b/zebra-chain/src/work/tests/arbitrary.rs @@ -0,0 +1,19 @@ +use super::super::*; + +use proptest::{arbitrary::Arbitrary, collection::vec, prelude::*}; + +impl Arbitrary for equihash::Solution { + type Parameters = (); + + fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { + (vec(any::(), equihash::SOLUTION_SIZE)) + .prop_map(|v| { + let mut bytes = [0; equihash::SOLUTION_SIZE]; + bytes.copy_from_slice(v.as_slice()); + Self(bytes) + }) + .boxed() + } + + type Strategy = BoxedStrategy; +} diff --git a/zebra-chain/src/work/tests/prop.rs b/zebra-chain/src/work/tests/prop.rs new file mode 100644 index 00000000000..b7585e50154 --- /dev/null +++ b/zebra-chain/src/work/tests/prop.rs @@ -0,0 +1,119 @@ +use proptest::prelude::*; + +use crate::block::{Block, BlockHeader}; +use crate::serialization::{ZcashDeserialize, ZcashDeserializeInto, ZcashSerialize}; + +use super::super::*; + +#[test] +fn equihash_solution_roundtrip() { + proptest!(|(solution in any::())| { + let data = solution + .zcash_serialize_to_vec() + .expect("randomized EquihashSolution should serialize"); + let solution2 = data + .zcash_deserialize_into() + .expect("randomized EquihashSolution should deserialize"); + + prop_assert_eq![solution, solution2]; + }); +} + +prop_compose! { + fn randomized_solutions(real_header: BlockHeader) + (fake_solution in any::() + .prop_filter("solution must not be the actual solution", move |s| { + s != &real_header.solution + }) + ) -> BlockHeader { + let mut fake_header = real_header; + fake_header.solution = fake_solution; + fake_header + } +} + +#[test] +fn equihash_prop_test_solution() -> color_eyre::eyre::Result<()> { + zebra_test::init(); + + for block_bytes in zebra_test::vectors::TEST_BLOCKS.iter() { + let block = Block::zcash_deserialize(&block_bytes[..]) + .expect("block test vector should deserialize"); + block.header.is_equihash_solution_valid()?; + + proptest!(|(fake_header in randomized_solutions(block.header))| { + fake_header + .is_equihash_solution_valid() + .expect_err("block header should not validate on randomized solution"); + }); + } + + Ok(()) +} + +prop_compose! { + fn randomized_nonce(real_header: BlockHeader) + (fake_nonce in proptest::array::uniform32(any::()) + .prop_filter("nonce must not be the actual nonce", move |fake_nonce| { + fake_nonce != &real_header.nonce + }) + ) -> BlockHeader { + let mut fake_header = real_header; + fake_header.nonce = fake_nonce; + fake_header + } +} + +#[test] +fn equihash_prop_test_nonce() -> color_eyre::eyre::Result<()> { + zebra_test::init(); + + for block_bytes in zebra_test::vectors::TEST_BLOCKS.iter() { + let block = Block::zcash_deserialize(&block_bytes[..]) + .expect("block test vector should deserialize"); + block.header.is_equihash_solution_valid()?; + + proptest!(|(fake_header in randomized_nonce(block.header))| { + fake_header + .is_equihash_solution_valid() + .expect_err("block header should not validate on randomized nonce"); + }); + } + + Ok(()) +} + +prop_compose! { + fn randomized_input(real_header: BlockHeader) + (fake_header in any::() + .prop_map(move |mut fake_header| { + fake_header.nonce = real_header.nonce; + fake_header.solution = real_header.solution; + fake_header + }) + .prop_filter("input must not be the actual input", move |fake_header| { + fake_header != &real_header + }) + ) -> BlockHeader { + fake_header + } +} + +#[test] +fn equihash_prop_test_input() -> color_eyre::eyre::Result<()> { + zebra_test::init(); + + for block_bytes in zebra_test::vectors::TEST_BLOCKS.iter() { + let block = Block::zcash_deserialize(&block_bytes[..]) + .expect("block test vector should deserialize"); + block.header.is_equihash_solution_valid()?; + + proptest!(|(fake_header in randomized_input(block.header))| { + fake_header + .is_equihash_solution_valid() + .expect_err("equihash solution should not validate on randomized input"); + }); + } + + Ok(()) +} diff --git a/zebra-chain/src/work/tests/vectors.rs b/zebra-chain/src/work/tests/vectors.rs new file mode 100644 index 00000000000..3b5b8fbc1a7 --- /dev/null +++ b/zebra-chain/src/work/tests/vectors.rs @@ -0,0 +1,37 @@ +use super::super::*; + +use crate::{ + block::Block, + serialization::{ZcashDeserialize, ZcashDeserializeInto, ZcashSerialize}, +}; + +const EQUIHASH_SOLUTION_BLOCK_OFFSET: usize = equihash::Solution::INPUT_LENGTH + 32; + +#[test] +fn equihash_solution_test_vector() { + zebra_test::init(); + let solution_bytes = + &zebra_test::vectors::HEADER_MAINNET_415000_BYTES[EQUIHASH_SOLUTION_BLOCK_OFFSET..]; + + let solution = solution_bytes + .zcash_deserialize_into::() + .expect("Test vector EquihashSolution should deserialize"); + + let mut data = Vec::new(); + solution + .zcash_serialize(&mut data) + .expect("Test vector EquihashSolution should serialize"); + + assert_eq!(solution_bytes, data.as_slice()); +} + +#[test] +fn equihash_solution_test_vector_is_valid() -> color_eyre::eyre::Result<()> { + zebra_test::init(); + + let block = Block::zcash_deserialize(&zebra_test::vectors::BLOCK_MAINNET_415000_BYTES[..]) + .expect("block test vector should deserialize"); + block.header.is_equihash_solution_valid()?; + + Ok(()) +}