Add error context for contextual validation
This commit is contained in:
parent
23e07a94cf
commit
207ded6889
|
|
@ -1,6 +1,10 @@
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use chrono::{DateTime, Utc};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
|
use zebra_chain::{block, work::difficulty::CompactDifficulty};
|
||||||
|
|
||||||
/// A wrapper for type erased errors that is itself clonable and implements the
|
/// A wrapper for type erased errors that is itself clonable and implements the
|
||||||
/// Error trait
|
/// Error trait
|
||||||
#[derive(Debug, Error, Clone)]
|
#[derive(Debug, Error, Clone)]
|
||||||
|
|
@ -34,24 +38,40 @@ pub struct CommitBlockError(#[from] ValidateContextError);
|
||||||
/// An error describing why a block failed contextual validation.
|
/// An error describing why a block failed contextual validation.
|
||||||
#[derive(displaydoc::Display, Debug, Error)]
|
#[derive(displaydoc::Display, Debug, Error)]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
|
#[allow(missing_docs)]
|
||||||
pub enum ValidateContextError {
|
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]
|
#[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]
|
#[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]
|
#[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]
|
#[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]
|
#[non_exhaustive]
|
||||||
InvalidDifficultyThreshold,
|
InvalidDifficultyThreshold {
|
||||||
|
difficulty_threshold: CompactDifficulty,
|
||||||
|
expected_difficulty: CompactDifficulty,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -90,10 +90,13 @@ where
|
||||||
/// block is less than or equal to the finalized tip height.
|
/// block is less than or equal to the finalized tip height.
|
||||||
fn block_is_not_orphaned(
|
fn block_is_not_orphaned(
|
||||||
finalized_tip_height: block::Height,
|
finalized_tip_height: block::Height,
|
||||||
height: block::Height,
|
candidate_height: block::Height,
|
||||||
) -> Result<(), ValidateContextError> {
|
) -> Result<(), ValidateContextError> {
|
||||||
if height <= finalized_tip_height {
|
if candidate_height <= finalized_tip_height {
|
||||||
Err(ValidateContextError::OrphanedBlock)
|
Err(ValidateContextError::OrphanedBlock {
|
||||||
|
candidate_height,
|
||||||
|
finalized_tip_height,
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -103,10 +106,13 @@ fn block_is_not_orphaned(
|
||||||
/// equal to the parent_height+1.
|
/// equal to the parent_height+1.
|
||||||
fn height_one_more_than_parent_height(
|
fn height_one_more_than_parent_height(
|
||||||
parent_height: block::Height,
|
parent_height: block::Height,
|
||||||
height: block::Height,
|
candidate_height: block::Height,
|
||||||
) -> Result<(), ValidateContextError> {
|
) -> Result<(), ValidateContextError> {
|
||||||
if parent_height + 1 != Some(height) {
|
if parent_height + 1 != Some(candidate_height) {
|
||||||
Err(ValidateContextError::NonSequentialBlock)
|
Err(ValidateContextError::NonSequentialBlock {
|
||||||
|
candidate_height,
|
||||||
|
parent_height,
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -127,19 +133,28 @@ fn difficulty_threshold_is_valid(
|
||||||
difficulty_adjustment: AdjustedDifficulty,
|
difficulty_adjustment: AdjustedDifficulty,
|
||||||
) -> Result<(), ValidateContextError> {
|
) -> Result<(), ValidateContextError> {
|
||||||
// Check the block header time consensus rules from the Zcash specification
|
// 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 median_time_past = difficulty_adjustment.median_time_past();
|
||||||
let block_max_time_since_median = Duration::seconds(difficulty::BLOCK_MAX_TIME_SINCE_MEDIAN);
|
let block_time_max =
|
||||||
if difficulty_adjustment.candidate_time() <= median_time_past {
|
median_time_past + Duration::seconds(difficulty::BLOCK_MAX_TIME_SINCE_MEDIAN);
|
||||||
Err(ValidateContextError::TimeTooEarly)?
|
if candidate_time <= median_time_past {
|
||||||
} else if difficulty_adjustment.candidate_time()
|
Err(ValidateContextError::TimeTooEarly {
|
||||||
> median_time_past + block_max_time_since_median
|
candidate_time,
|
||||||
{
|
median_time_past,
|
||||||
Err(ValidateContextError::TimeTooLate)?
|
})?
|
||||||
|
} else if candidate_time > block_time_max {
|
||||||
|
Err(ValidateContextError::TimeTooLate {
|
||||||
|
candidate_time,
|
||||||
|
block_time_max,
|
||||||
|
})?
|
||||||
}
|
}
|
||||||
|
|
||||||
let expected_difficulty = difficulty_adjustment.expected_difficulty_threshold();
|
let expected_difficulty = difficulty_adjustment.expected_difficulty_threshold();
|
||||||
if difficulty_threshold != expected_difficulty {
|
if difficulty_threshold != expected_difficulty {
|
||||||
Err(ValidateContextError::InvalidDifficultyThreshold)?
|
Err(ValidateContextError::InvalidDifficultyThreshold {
|
||||||
|
difficulty_threshold,
|
||||||
|
expected_difficulty,
|
||||||
|
})?
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue