From 741c44cd550fb00143554c4e89cb455a3bdb00b9 Mon Sep 17 00:00:00 2001 From: teor Date: Wed, 18 Nov 2020 19:20:06 +1000 Subject: [PATCH] Implement mean_target_difficulty And enough stub code to actually run it on the context. --- zebra-chain/src/work/difficulty.rs | 14 +++++ zebra-state/Cargo.toml | 1 + zebra-state/src/service/check.rs | 13 ++++- zebra-state/src/service/check/difficulty.rs | 65 ++++++++++++++++++++- 4 files changed, 89 insertions(+), 4 deletions(-) diff --git a/zebra-chain/src/work/difficulty.rs b/zebra-chain/src/work/difficulty.rs index 45e654a1..1057e13f 100644 --- a/zebra-chain/src/work/difficulty.rs +++ b/zebra-chain/src/work/difficulty.rs @@ -17,6 +17,8 @@ use std::{ cmp::{Ordering, PartialEq, PartialOrd}, convert::TryFrom, fmt, + iter::Sum, + ops::Add, }; use primitive_types::U256; @@ -385,6 +387,18 @@ impl From for ExpandedDifficulty { } } +impl From for U256 { + fn from(value: ExpandedDifficulty) -> Self { + value.0 + } +} + +impl Sum for ExpandedDifficulty { + fn sum>(iter: I) -> Self { + iter.map(|d| d.0).fold(U256::zero(), Add::add).into() + } +} + impl PartialEq for ExpandedDifficulty { /// Is `self` equal to `other`? /// diff --git a/zebra-state/Cargo.toml b/zebra-state/Cargo.toml index c16d04f1..dbe0e69b 100644 --- a/zebra-state/Cargo.toml +++ b/zebra-state/Cargo.toml @@ -27,6 +27,7 @@ displaydoc = "0.1.7" rocksdb = "0.15.0" tempdir = "0.3.7" chrono = "0.4.19" +primitive-types = "0.7.3" [dev-dependencies] zebra-chain = { path = "../zebra-chain", features = ["proptest-impl"] } diff --git a/zebra-state/src/service/check.rs b/zebra-state/src/service/check.rs index 23618d65..5847ab4b 100644 --- a/zebra-state/src/service/check.rs +++ b/zebra-state/src/service/check.rs @@ -113,10 +113,17 @@ fn height_one_more_than_parent_height( /// value, then compares that value to the `difficulty_threshold`. /// Returns `Ok(())` if the values are equal. fn difficulty_threshold_is_valid( - _difficulty_threshold: CompactDifficulty, - _expected_difficulty: AdjustedDifficulty, + difficulty_threshold: CompactDifficulty, + expected_difficulty: AdjustedDifficulty, ) -> Result<(), ValidateContextError> { - Ok(()) + // TODO: return an error on failure, after the calculation is implemented + #[allow(clippy::if_same_then_else)] + if difficulty_threshold == expected_difficulty.expected_difficulty_threshold() { + Ok(()) + } else { + // This is the error case + Ok(()) + } } #[cfg(test)] diff --git a/zebra-state/src/service/check/difficulty.rs b/zebra-state/src/service/check/difficulty.rs index 5aac11ea..c429cc6f 100644 --- a/zebra-state/src/service/check/difficulty.rs +++ b/zebra-state/src/service/check/difficulty.rs @@ -1,10 +1,14 @@ //! Block difficulty adjustment calculations for contextual validation. use chrono::{DateTime, Utc}; +use primitive_types::U256; use std::convert::TryInto; -use zebra_chain::{block, block::Block, parameters::Network, work::difficulty::CompactDifficulty}; +use zebra_chain::{ + block, block::Block, parameters::Network, work::difficulty::CompactDifficulty, + work::difficulty::ExpandedDifficulty, +}; /// The averaging window for difficulty threshold arithmetic mean calculations. /// @@ -124,4 +128,63 @@ impl AdjustedDifficulty { relevant_times, } } + + /// Calculate the expected `difficulty_threshold` for a candidate block, based + /// on the `candidate_time`, `candidate_height`, `network`, and the + /// `difficulty_threshold`s and `time`s from the previous + /// `PoWAveragingWindow + PoWMedianBlockSpan` (28) blocks in the relevant chain. + /// + /// Implements `ThresholdBits` from the Zcash specification, and the Testnet + /// minimum difficulty adjustment from ZIPs 205 and 208. + pub fn expected_difficulty_threshold(&self) -> CompactDifficulty { + // TODO: Testnet minimum difficulty + self.threshold_bits() + } + + /// Calculate the `difficulty_threshold` for a candidate block, based on the + /// `candidate_height`, `network`, and the relevant `difficulty_threshold`s and + /// `time`s. + /// + /// See `expected_difficulty_threshold` for details. + /// + /// Implements `ThresholdBits` from the Zcash specification. (Which excludes the + /// Testnet minimum difficulty adjustment.) + fn threshold_bits(&self) -> CompactDifficulty { + let mean_target = self.mean_target_difficulty(); + + // TODO: calculate the actual value + mean_target.to_compact() + } + + /// Calculate the arithmetic mean of the averaging window thresholds: the + /// expanded `difficulty_threshold`s from the previous `PoWAveragingWindow` (17) + /// blocks in the relevant chain. + /// + /// Implements `MeanTarget` from the Zcash specification. + fn mean_target_difficulty(&self) -> ExpandedDifficulty { + // In Zebra, contextual validation starts after Sapling activation, so we + // can assume that the relevant chain contains at least 17 blocks. + // Therefore, the `PoWLimit` case of `MeanTarget()` from the Zcash + // specification is unreachable. + + let averaging_window_thresholds = + &self.relevant_difficulty_thresholds[0..POW_AVERAGING_WINDOW]; + + // Since the PoWLimits are `2^251 − 1` for Testnet, and `2^243 − 1` for + // Mainnet, the sum of 17 `ExpandedDifficulty` will be less than or equal + // to: `(2^251 − 1) * 17 = 2^255 + 2^251 - 17`. Therefore, the sum can + // not overflow a u256 value. + let total: ExpandedDifficulty = averaging_window_thresholds + .iter() + .map(|compact| { + compact + .to_expanded() + .expect("difficulty thresholds in previously verified blocks are valid") + }) + .sum(); + let total: U256 = total.into(); + let divisor: U256 = POW_AVERAGING_WINDOW.into(); + + (total / divisor).into() + } }