diff --git a/zebra-chain/src/work/difficulty.rs b/zebra-chain/src/work/difficulty.rs index 98b43f28d..86f57defd 100644 --- a/zebra-chain/src/work/difficulty.rs +++ b/zebra-chain/src/work/difficulty.rs @@ -11,7 +11,7 @@ //! the actual work represented by the block header hash. #![allow(clippy::unit_arg)] -use crate::block; +use crate::{block, parameters::Network}; use std::cmp::{Ordering, PartialEq, PartialOrd}; use std::{fmt, ops::Add, ops::AddAssign}; @@ -257,6 +257,21 @@ impl ExpandedDifficulty { /// users of this module should avoid converting hashes into difficulties. fn from_hash(hash: &block::Hash) -> ExpandedDifficulty { U256::from_little_endian(&hash.0).into() + } + + /// Returns the easiest target difficulty allowed on `network`. + /// + /// See `PoWLimit` in the Zcash specification. + pub fn target_difficulty_limit(network: Network) -> ExpandedDifficulty { + let limit: U256 = match network { + /* 2^243 - 1 */ + Network::Mainnet => (U256::one() << 243) - 1, + /* 2^251 - 1 */ + Network::Testnet => (U256::one() << 251) - 1, + }; + + limit.into() + } } impl From for ExpandedDifficulty { @@ -277,6 +292,9 @@ impl PartialEq for ExpandedDifficulty { impl PartialOrd for ExpandedDifficulty { /// `block::Hash`es are compared with `ExpandedDifficulty` thresholds by /// converting the hash to a 256-bit integer in little-endian order. + /// + /// Greater values represent *less* work. This matches the convention in + /// zcashd and bitcoin. fn partial_cmp(&self, other: &block::Hash) -> Option { self.partial_cmp(&ExpandedDifficulty::from_hash(other)) } diff --git a/zebra-consensus/src/block.rs b/zebra-consensus/src/block.rs index ccc8c6a8b..9ad4963e9 100644 --- a/zebra-consensus/src/block.rs +++ b/zebra-consensus/src/block.rs @@ -140,7 +140,7 @@ where // Do the difficulty checks first, to raise the threshold for // attacks that use any other fields. - check::difficulty_is_valid(&block.header, &height, &hash)?; + check::difficulty_is_valid(&block.header, network, &height, &hash)?; check::equihash_solution_is_valid(&block.header)?; // Since errors cause an early exit, try to do the diff --git a/zebra-consensus/src/block/check.rs b/zebra-consensus/src/block/check.rs index 9386a35c8..eb1ef1bb3 100644 --- a/zebra-consensus/src/block/check.rs +++ b/zebra-consensus/src/block/check.rs @@ -7,7 +7,7 @@ use zebra_chain::{ block::Height, block::{Block, Header}, parameters::{Network, NetworkUpgrade}, - work::equihash, + work::{difficulty::ExpandedDifficulty, equihash}, }; use crate::{error::*, parameters::SLOW_START_INTERVAL}; @@ -38,23 +38,38 @@ pub fn coinbase_is_first(block: &Block) -> Result<(), BlockError> { Ok(()) } -/// Returns `Ok(())` if `hash` passes the difficulty filter and PoW limit, +/// Returns `Ok(())` if `hash` passes: +/// - the target difficulty limit for `network` (PoWLimit), and +/// - the difficulty filter, /// based on the fields in `header`. /// /// If the block is invalid, returns an error containing `height` and `hash`. pub fn difficulty_is_valid( header: &Header, + network: Network, height: &Height, hash: &Hash, ) -> Result<(), BlockError> { - // TODO: - // - PoW limit - let difficulty_threshold = header .difficulty_threshold .to_expanded() .ok_or(BlockError::InvalidDifficulty(*height, *hash))?; + // Note: the comparisons in this function are u256 integer comparisons, like + // zcashd and bitcoin. Greater values represent *less* work. + + // The PowLimit check is part of `Threshold()` in the spec, but it doesn't + // actually depend on any previous blocks. + if difficulty_threshold > ExpandedDifficulty::target_difficulty_limit(network) { + Err(BlockError::TargetDifficultyLimit( + *height, + *hash, + difficulty_threshold, + network, + ExpandedDifficulty::target_difficulty_limit(network), + ))?; + } + // Difficulty filter if hash > &difficulty_threshold { Err(BlockError::DifficultyFilter( diff --git a/zebra-consensus/src/error.rs b/zebra-consensus/src/error.rs index 6e9b04d90..25a32adb2 100644 --- a/zebra-consensus/src/error.rs +++ b/zebra-consensus/src/error.rs @@ -53,7 +53,16 @@ pub enum BlockError { #[error("invalid difficulty threshold in block header {0:?} {1:?}")] InvalidDifficulty(zebra_chain::block::Height, zebra_chain::block::Hash), - #[error("block {0:?} failed the difficulty filter: hash {1:?} must be less than or equal to the difficulty threshold {2:?}")] + #[error("block {0:?} failed the difficulty limit: the difficulty threshold {2:?} must be at least as difficult as the {3:?} difficulty limit {4:?}, block hash {1:?}")] + TargetDifficultyLimit( + zebra_chain::block::Height, + zebra_chain::block::Hash, + zebra_chain::work::difficulty::ExpandedDifficulty, + zebra_chain::parameters::Network, + zebra_chain::work::difficulty::ExpandedDifficulty, + ), + + #[error("block {0:?} failed the difficulty filter: hash {1:?} must be at least as difficult as the difficulty threshold {2:?}")] DifficultyFilter( zebra_chain::block::Height, zebra_chain::block::Hash,