//! Equihash Solution and related items. use std::{fmt, io}; use serde_big_array::BigArray; use crate::{ block::Header, serialization::{ zcash_serialize_bytes, SerializationError, ZcashDeserialize, ZcashDeserializeInto, ZcashSerialize, }, }; #[cfg(feature = "internal-miner")] use crate::serialization::AtLeastOne; /// The error type for Equihash validation. #[non_exhaustive] #[derive(Debug, thiserror::Error)] #[error("invalid equihash solution for BlockHeader")] pub struct Error(#[from] equihash::Error); /// The error type for Equihash solving. #[derive(Copy, Clone, Debug, Eq, PartialEq, thiserror::Error)] #[error("solver was cancelled")] pub struct SolverCancelled; /// The size of an Equihash solution in bytes (always 1344). pub(crate) const SOLUTION_SIZE: usize = 1344; /// The size of an Equihash solution in bytes on Regtest (always 36). pub(crate) const REGTEST_SOLUTION_SIZE: usize = 36; /// Equihash Solution in compressed format. /// /// A wrapper around `[u8; n]` where `n` is the solution size because /// Rust doesn't implement common traits like `Debug`, `Clone`, etc. /// for collections like arrays beyond lengths 0 to 32. /// /// The size of an Equihash solution in bytes is always 1344 on Mainnet and Testnet, and /// is always 36 on Regtest so the length of this type is fixed. #[derive(Deserialize, Serialize)] // It's okay to use the extra space on Regtest #[allow(clippy::large_enum_variant)] pub enum Solution { /// Equihash solution on Mainnet or Testnet Common(#[serde(with = "BigArray")] [u8; SOLUTION_SIZE]), /// Equihash solution on Regtest Regtest(#[serde(with = "BigArray")] [u8; REGTEST_SOLUTION_SIZE]), } impl Solution { /// The length of the portion of the header used as input when verifying /// equihash solutions, in bytes. /// /// Excludes the 32-byte nonce, which is passed as a separate argument /// to the verification function. pub const INPUT_LENGTH: usize = 4 + 32 * 3 + 4 * 2; /// Returns the inner value of the [`Solution`] as a byte slice. fn value(&self) -> &[u8] { match self { Solution::Common(solution) => solution.as_slice(), Solution::Regtest(solution) => solution.as_slice(), } } /// Returns `Ok(())` if `EquihashSolution` is valid for `header` #[allow(clippy::unwrap_in_result)] pub fn check(&self, header: &Header) -> Result<(), Error> { let nonce = &header.nonce; let (solution, n, k) = match self { Solution::Common(solution) => (solution.as_slice(), 200, 9), Solution::Regtest(solution) => (solution.as_slice(), 48, 5), }; let mut input = Vec::new(); header .zcash_serialize(&mut input) .expect("serialization into a vec can't fail"); // The part of the header before the nonce and solution. // This data is kept constant during solver runs, so the verifier API takes it separately. let input = &input[0..Solution::INPUT_LENGTH]; equihash::is_valid_solution(n, k, input, nonce.as_ref(), solution)?; Ok(()) } /// Returns a [`Solution`] containing the bytes from `solution`. /// Returns an error if `solution` is the wrong length. pub fn from_bytes(solution: &[u8]) -> Result { match solution.len() { // Won't panic, because we just checked the length. SOLUTION_SIZE => { let mut bytes = [0; SOLUTION_SIZE]; bytes.copy_from_slice(solution); Ok(Self::Common(bytes)) } REGTEST_SOLUTION_SIZE => { let mut bytes = [0; REGTEST_SOLUTION_SIZE]; bytes.copy_from_slice(solution); Ok(Self::Regtest(bytes)) } _unexpected_len => Err(SerializationError::Parse( "incorrect equihash solution size", )), } } /// Returns a [`Solution`] of `[0; SOLUTION_SIZE]` to be used in block proposals. #[cfg(feature = "getblocktemplate-rpcs")] pub fn for_proposal() -> Self { // TODO: Accept network as an argument, and if it's Regtest, return the shorter null solution. Self::Common([0; SOLUTION_SIZE]) } /// Mines and returns one or more [`Solution`]s based on a template `header`. /// The returned header contains a valid `nonce` and `solution`. /// /// If `cancel_fn()` returns an error, returns early with `Err(SolverCancelled)`. /// /// The `nonce` in the header template is taken as the starting nonce. If you are running multiple /// solvers at the same time, start them with different nonces. /// The `solution` in the header template is ignored. /// /// This method is CPU and memory-intensive. It uses 144 MB of RAM and one CPU core while running. /// It can run for minutes or hours if the network difficulty is high. #[cfg(feature = "internal-miner")] #[allow(clippy::unwrap_in_result)] pub fn solve( mut _header: Header, mut _cancel_fn: F, ) -> Result, SolverCancelled> where F: FnMut() -> Result<(), SolverCancelled>, { // TODO: Function code was removed as part of https://github.com/ZcashFoundation/zebra/issues/8180 // Find the removed code at https://github.com/ZcashFoundation/zebra/blob/v1.5.1/zebra-chain/src/work/equihash.rs#L115-L166 // Restore the code when conditions are met. https://github.com/ZcashFoundation/zebra/issues/8183 Err(SolverCancelled) } // TODO: Some methods were removed as part of https://github.com/ZcashFoundation/zebra/issues/8180 // Find the removed code at https://github.com/ZcashFoundation/zebra/blob/v1.5.1/zebra-chain/src/work/equihash.rs#L171-L196 // Restore the code when conditions are met. https://github.com/ZcashFoundation/zebra/issues/8183 } impl PartialEq for Solution { fn eq(&self, other: &Solution) -> bool { self.value() == other.value() } } impl fmt::Debug for Solution { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_tuple("EquihashSolution") .field(&hex::encode(self.value())) .finish() } } // These impls all only exist because of array length restrictions. impl Copy for Solution {} impl Clone for Solution { fn clone(&self) -> Self { *self } } impl Eq for Solution {} #[cfg(any(test, feature = "proptest-impl"))] impl Default for Solution { fn default() -> Self { Self::Common([0; SOLUTION_SIZE]) } } impl ZcashSerialize for Solution { fn zcash_serialize(&self, writer: W) -> Result<(), io::Error> { zcash_serialize_bytes(&self.value().to_vec(), writer) } } impl ZcashDeserialize for Solution { fn zcash_deserialize(mut reader: R) -> Result { let solution: Vec = (&mut reader).zcash_deserialize_into()?; Self::from_bytes(&solution) } }