From 23e07a94cf4c49c514cdb2d652730ed797f56597 Mon Sep 17 00:00:00 2001 From: teor Date: Thu, 3 Dec 2020 14:28:42 +1000 Subject: [PATCH] Implement the block header time consensus rules --- zebra-state/src/error.rs | 8 +++++ zebra-state/src/service/check.rs | 40 ++++++++++++++------- zebra-state/src/service/check/difficulty.rs | 11 ++++++ 3 files changed, 47 insertions(+), 12 deletions(-) diff --git a/zebra-state/src/error.rs b/zebra-state/src/error.rs index 67d17f27..e7425bf9 100644 --- a/zebra-state/src/error.rs +++ b/zebra-state/src/error.rs @@ -43,6 +43,14 @@ pub enum ValidateContextError { #[non_exhaustive] NonSequentialBlock, + /// block.header.time is less than or equal to the median-time-past for the block + #[non_exhaustive] + TimeTooEarly, + + /// block.header.time is greater than the median-time-past for the block plus 90 minutes + #[non_exhaustive] + TimeTooLate, + /// block.header.difficulty_threshold is not equal to the adjusted difficulty for the block #[non_exhaustive] InvalidDifficultyThreshold, diff --git a/zebra-state/src/service/check.rs b/zebra-state/src/service/check.rs index 103ae1c7..1e6b2225 100644 --- a/zebra-state/src/service/check.rs +++ b/zebra-state/src/service/check.rs @@ -2,6 +2,7 @@ use std::borrow::Borrow; +use chrono::Duration; use zebra_chain::{ block::{self, Block}, parameters::Network, @@ -74,11 +75,11 @@ where block.borrow().header.time, ) }); - let expected_difficulty = + let difficulty_adjustment = AdjustedDifficulty::new_from_block(&prepared.block, network, relevant_data); check::difficulty_threshold_is_valid( prepared.block.header.difficulty_threshold, - expected_difficulty, + difficulty_adjustment, )?; // TODO: other contextual validation design and implementation @@ -111,22 +112,37 @@ fn height_one_more_than_parent_height( } } -/// Validate the `difficulty_threshold` from a candidate block's header, based -/// on an `expected_difficulty` for that block. +/// Validate the `time` and `difficulty_threshold` from a candidate block's +/// header. /// -/// Uses `expected_difficulty` to calculate the expected difficulty value, then -/// compares that value to the `difficulty_threshold`. +/// Uses the `difficulty_adjustment` context for the block to: +/// * check that the the `time` field is within the valid range, and +/// * check that the expected difficulty is equal to the block's +/// `difficulty_threshold`. /// -/// The check passes if the values are equal. +/// These checks are performed together, because the time field is used to +/// calculate the expected difficulty adjustment. fn difficulty_threshold_is_valid( difficulty_threshold: CompactDifficulty, - expected_difficulty: AdjustedDifficulty, + difficulty_adjustment: AdjustedDifficulty, ) -> Result<(), ValidateContextError> { - if difficulty_threshold == expected_difficulty.expected_difficulty_threshold() { - Ok(()) - } else { - Err(ValidateContextError::InvalidDifficultyThreshold) + // Check the block header time consensus rules from the Zcash specification + let median_time_past = difficulty_adjustment.median_time_past(); + let block_max_time_since_median = Duration::seconds(difficulty::BLOCK_MAX_TIME_SINCE_MEDIAN); + if difficulty_adjustment.candidate_time() <= median_time_past { + Err(ValidateContextError::TimeTooEarly)? + } else if difficulty_adjustment.candidate_time() + > median_time_past + block_max_time_since_median + { + Err(ValidateContextError::TimeTooLate)? } + + let expected_difficulty = difficulty_adjustment.expected_difficulty_threshold(); + if difficulty_threshold != expected_difficulty { + Err(ValidateContextError::InvalidDifficultyThreshold)? + } + + Ok(()) } #[cfg(test)] diff --git a/zebra-state/src/service/check/difficulty.rs b/zebra-state/src/service/check/difficulty.rs index 94875450..617acfec 100644 --- a/zebra-state/src/service/check/difficulty.rs +++ b/zebra-state/src/service/check/difficulty.rs @@ -36,6 +36,12 @@ pub const POW_MAX_ADJUST_UP_PERCENT: i32 = 16; /// `PoWMaxAdjustDown * 100` in the Zcash specification. pub const POW_MAX_ADJUST_DOWN_PERCENT: i32 = 32; +/// The maximum number of seconds between the `meadian-time-past` of a block, +/// and the block's `time` field. +/// +/// Part of the block header consensus rules in the Zcash specification. +pub const BLOCK_MAX_TIME_SINCE_MEDIAN: i64 = 90 * 60; + /// Contains the context needed to calculate the adjusted difficulty for a block. pub(super) struct AdjustedDifficulty { /// The `header.time` field from the candidate block @@ -141,6 +147,11 @@ impl AdjustedDifficulty { } } + /// Returns the candidate block's time field. + pub fn candidate_time(&self) -> DateTime { + self.candidate_time + } + /// 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