Implement the block header time consensus rules

This commit is contained in:
teor 2020-12-03 14:28:42 +10:00
parent 0bac2dafcc
commit 23e07a94cf
3 changed files with 47 additions and 12 deletions

View File

@ -43,6 +43,14 @@ pub enum ValidateContextError {
#[non_exhaustive] #[non_exhaustive]
NonSequentialBlock, 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 /// block.header.difficulty_threshold is not equal to the adjusted difficulty for the block
#[non_exhaustive] #[non_exhaustive]
InvalidDifficultyThreshold, InvalidDifficultyThreshold,

View File

@ -2,6 +2,7 @@
use std::borrow::Borrow; use std::borrow::Borrow;
use chrono::Duration;
use zebra_chain::{ use zebra_chain::{
block::{self, Block}, block::{self, Block},
parameters::Network, parameters::Network,
@ -74,11 +75,11 @@ where
block.borrow().header.time, block.borrow().header.time,
) )
}); });
let expected_difficulty = let difficulty_adjustment =
AdjustedDifficulty::new_from_block(&prepared.block, network, relevant_data); AdjustedDifficulty::new_from_block(&prepared.block, network, relevant_data);
check::difficulty_threshold_is_valid( check::difficulty_threshold_is_valid(
prepared.block.header.difficulty_threshold, prepared.block.header.difficulty_threshold,
expected_difficulty, difficulty_adjustment,
)?; )?;
// TODO: other contextual validation design and implementation // 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 /// Validate the `time` and `difficulty_threshold` from a candidate block's
/// on an `expected_difficulty` for that block. /// header.
/// ///
/// Uses `expected_difficulty` to calculate the expected difficulty value, then /// Uses the `difficulty_adjustment` context for the block to:
/// compares that value to the `difficulty_threshold`. /// * 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( fn difficulty_threshold_is_valid(
difficulty_threshold: CompactDifficulty, difficulty_threshold: CompactDifficulty,
expected_difficulty: AdjustedDifficulty, difficulty_adjustment: AdjustedDifficulty,
) -> Result<(), ValidateContextError> { ) -> Result<(), ValidateContextError> {
if difficulty_threshold == expected_difficulty.expected_difficulty_threshold() { // Check the block header time consensus rules from the Zcash specification
Ok(()) let median_time_past = difficulty_adjustment.median_time_past();
} else { let block_max_time_since_median = Duration::seconds(difficulty::BLOCK_MAX_TIME_SINCE_MEDIAN);
Err(ValidateContextError::InvalidDifficultyThreshold) 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)] #[cfg(test)]

View File

@ -36,6 +36,12 @@ pub const POW_MAX_ADJUST_UP_PERCENT: i32 = 16;
/// `PoWMaxAdjustDown * 100` in the Zcash specification. /// `PoWMaxAdjustDown * 100` in the Zcash specification.
pub const POW_MAX_ADJUST_DOWN_PERCENT: i32 = 32; 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. /// Contains the context needed to calculate the adjusted difficulty for a block.
pub(super) struct AdjustedDifficulty { pub(super) struct AdjustedDifficulty {
/// The `header.time` field from the candidate block /// 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<Utc> {
self.candidate_time
}
/// Calculate the expected `difficulty_threshold` for a candidate block, based /// Calculate the expected `difficulty_threshold` for a candidate block, based
/// on the `candidate_time`, `candidate_height`, `network`, and the /// on the `candidate_time`, `candidate_height`, `network`, and the
/// `difficulty_threshold`s and `time`s from the previous /// `difficulty_threshold`s and `time`s from the previous