Add error context for contextual validation

This commit is contained in:
teor 2020-12-03 14:48:34 +10:00
parent 23e07a94cf
commit 207ded6889
2 changed files with 59 additions and 24 deletions

View File

@ -1,6 +1,10 @@
use std::sync::Arc;
use chrono::{DateTime, Utc};
use thiserror::Error;
use zebra_chain::{block, work::difficulty::CompactDifficulty};
/// A wrapper for type erased errors that is itself clonable and implements the
/// Error trait
#[derive(Debug, Error, Clone)]
@ -34,24 +38,40 @@ pub struct CommitBlockError(#[from] ValidateContextError);
/// An error describing why a block failed contextual validation.
#[derive(displaydoc::Display, Debug, Error)]
#[non_exhaustive]
#[allow(missing_docs)]
pub enum ValidateContextError {
/// block.height is lower than the current finalized height
/// block height {candidate_height:?} is lower than the current finalized height {finalized_tip_height:?}
#[non_exhaustive]
OrphanedBlock,
OrphanedBlock {
candidate_height: block::Height,
finalized_tip_height: block::Height,
},
/// block.height is not one greater than its parent block's height
/// block height {candidate_height:?} is not one greater than its parent block's height {parent_height:?}
#[non_exhaustive]
NonSequentialBlock,
NonSequentialBlock {
candidate_height: block::Height,
parent_height: block::Height,
},
/// block.header.time is less than or equal to the median-time-past for the block
/// block time {candidate_time:?} is less than or equal to the median-time-past for the block {median_time_past:?}
#[non_exhaustive]
TimeTooEarly,
TimeTooEarly {
candidate_time: DateTime<Utc>,
median_time_past: DateTime<Utc>,
},
/// block.header.time is greater than the median-time-past for the block plus 90 minutes
/// block time {candidate_time:?} is greater than the median-time-past for the block plus 90 minutes {block_time_max:?}
#[non_exhaustive]
TimeTooLate,
TimeTooLate {
candidate_time: DateTime<Utc>,
block_time_max: DateTime<Utc>,
},
/// block.header.difficulty_threshold is not equal to the adjusted difficulty for the block
/// block difficulty threshold {difficulty_threshold:?} is not equal to the expected difficulty for the block {expected_difficulty:?}
#[non_exhaustive]
InvalidDifficultyThreshold,
InvalidDifficultyThreshold {
difficulty_threshold: CompactDifficulty,
expected_difficulty: CompactDifficulty,
},
}

View File

@ -90,10 +90,13 @@ where
/// block is less than or equal to the finalized tip height.
fn block_is_not_orphaned(
finalized_tip_height: block::Height,
height: block::Height,
candidate_height: block::Height,
) -> Result<(), ValidateContextError> {
if height <= finalized_tip_height {
Err(ValidateContextError::OrphanedBlock)
if candidate_height <= finalized_tip_height {
Err(ValidateContextError::OrphanedBlock {
candidate_height,
finalized_tip_height,
})
} else {
Ok(())
}
@ -103,10 +106,13 @@ fn block_is_not_orphaned(
/// equal to the parent_height+1.
fn height_one_more_than_parent_height(
parent_height: block::Height,
height: block::Height,
candidate_height: block::Height,
) -> Result<(), ValidateContextError> {
if parent_height + 1 != Some(height) {
Err(ValidateContextError::NonSequentialBlock)
if parent_height + 1 != Some(candidate_height) {
Err(ValidateContextError::NonSequentialBlock {
candidate_height,
parent_height,
})
} else {
Ok(())
}
@ -127,19 +133,28 @@ fn difficulty_threshold_is_valid(
difficulty_adjustment: AdjustedDifficulty,
) -> Result<(), ValidateContextError> {
// Check the block header time consensus rules from the Zcash specification
let candidate_time = difficulty_adjustment.candidate_time();
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 block_time_max =
median_time_past + Duration::seconds(difficulty::BLOCK_MAX_TIME_SINCE_MEDIAN);
if candidate_time <= median_time_past {
Err(ValidateContextError::TimeTooEarly {
candidate_time,
median_time_past,
})?
} else if candidate_time > block_time_max {
Err(ValidateContextError::TimeTooLate {
candidate_time,
block_time_max,
})?
}
let expected_difficulty = difficulty_adjustment.expected_difficulty_threshold();
if difficulty_threshold != expected_difficulty {
Err(ValidateContextError::InvalidDifficultyThreshold)?
Err(ValidateContextError::InvalidDifficultyThreshold {
difficulty_threshold,
expected_difficulty,
})?
}
Ok(())