2020-11-22 17:56:38 -08:00
|
|
|
|
//! Block difficulty adjustment calculations for contextual validation.
|
2020-12-02 19:57:47 -08:00
|
|
|
|
//!
|
|
|
|
|
//! This module supports the following consensus rule calculations:
|
|
|
|
|
//! * `ThresholdBits` from the Zcash Specification,
|
|
|
|
|
//! * the Testnet minimum difficulty adjustment from ZIPs 205 and 208, and
|
|
|
|
|
//! * `median-time-past`.
|
2020-11-22 17:56:38 -08:00
|
|
|
|
|
2020-11-18 17:56:01 -08:00
|
|
|
|
use std::{cmp::max, cmp::min, convert::TryInto};
|
2020-11-22 17:56:38 -08:00
|
|
|
|
|
2022-12-01 17:38:05 -08:00
|
|
|
|
use chrono::{DateTime, Duration, Utc};
|
|
|
|
|
|
2020-11-18 01:20:06 -08:00
|
|
|
|
use zebra_chain::{
|
2021-06-18 11:35:05 -07:00
|
|
|
|
block,
|
|
|
|
|
block::Block,
|
|
|
|
|
parameters::Network,
|
|
|
|
|
parameters::NetworkUpgrade,
|
|
|
|
|
parameters::POW_AVERAGING_WINDOW,
|
|
|
|
|
work::difficulty::{CompactDifficulty, U256},
|
2024-03-12 14:41:44 -07:00
|
|
|
|
work::difficulty::{ExpandedDifficulty, ParameterDifficulty as _},
|
2020-11-18 01:20:06 -08:00
|
|
|
|
};
|
2020-11-22 17:56:38 -08:00
|
|
|
|
|
|
|
|
|
/// The median block span for time median calculations.
|
|
|
|
|
///
|
|
|
|
|
/// `PoWMedianBlockSpan` in the Zcash specification.
|
|
|
|
|
pub const POW_MEDIAN_BLOCK_SPAN: usize = 11;
|
|
|
|
|
|
2022-12-01 17:38:05 -08:00
|
|
|
|
/// The overall block span used for adjusting Zcash block difficulty.
|
|
|
|
|
///
|
|
|
|
|
/// `PoWAveragingWindow + PoWMedianBlockSpan` in the Zcash specification.
|
|
|
|
|
pub const POW_ADJUSTMENT_BLOCK_SPAN: usize = POW_AVERAGING_WINDOW + POW_MEDIAN_BLOCK_SPAN;
|
|
|
|
|
|
2020-11-18 17:56:01 -08:00
|
|
|
|
/// The damping factor for median timespan variance.
|
|
|
|
|
///
|
|
|
|
|
/// `PoWDampingFactor` in the Zcash specification.
|
|
|
|
|
pub const POW_DAMPING_FACTOR: i32 = 4;
|
|
|
|
|
|
|
|
|
|
/// The maximum upward adjustment percentage for median timespan variance.
|
|
|
|
|
///
|
|
|
|
|
/// `PoWMaxAdjustUp * 100` in the Zcash specification.
|
|
|
|
|
pub const POW_MAX_ADJUST_UP_PERCENT: i32 = 16;
|
|
|
|
|
|
|
|
|
|
/// The maximum downward adjustment percentage for median timespan variance.
|
|
|
|
|
///
|
|
|
|
|
/// `PoWMaxAdjustDown * 100` in the Zcash specification.
|
|
|
|
|
pub const POW_MAX_ADJUST_DOWN_PERCENT: i32 = 32;
|
|
|
|
|
|
2020-12-03 16:33:21 -08:00
|
|
|
|
/// The maximum number of seconds between the `median-time-past` of a block,
|
2020-12-02 20:28:42 -08:00
|
|
|
|
/// and the block's `time` field.
|
|
|
|
|
///
|
|
|
|
|
/// Part of the block header consensus rules in the Zcash specification.
|
2022-12-13 13:25:04 -08:00
|
|
|
|
pub const BLOCK_MAX_TIME_SINCE_MEDIAN: u32 = 90 * 60;
|
2020-12-02 20:28:42 -08:00
|
|
|
|
|
2020-11-22 17:56:38 -08:00
|
|
|
|
/// Contains the context needed to calculate the adjusted difficulty for a block.
|
2022-11-28 01:06:32 -08:00
|
|
|
|
pub(crate) struct AdjustedDifficulty {
|
2020-11-22 17:56:38 -08:00
|
|
|
|
/// The `header.time` field from the candidate block
|
|
|
|
|
candidate_time: DateTime<Utc>,
|
|
|
|
|
/// The coinbase height from the candidate block
|
|
|
|
|
///
|
|
|
|
|
/// If we only have the header, this field is calculated from the previous
|
|
|
|
|
/// block height.
|
|
|
|
|
candidate_height: block::Height,
|
|
|
|
|
/// The configured network
|
|
|
|
|
network: Network,
|
|
|
|
|
/// The `header.difficulty_threshold`s from the previous
|
|
|
|
|
/// `PoWAveragingWindow + PoWMedianBlockSpan` (28) blocks, in reverse height
|
|
|
|
|
/// order.
|
2022-12-01 17:38:05 -08:00
|
|
|
|
relevant_difficulty_thresholds: [CompactDifficulty; POW_ADJUSTMENT_BLOCK_SPAN],
|
2020-11-22 17:56:38 -08:00
|
|
|
|
/// The `header.time`s from the previous
|
|
|
|
|
/// `PoWAveragingWindow + PoWMedianBlockSpan` (28) blocks, in reverse height
|
|
|
|
|
/// order.
|
|
|
|
|
///
|
|
|
|
|
/// Only the first and last `PoWMedianBlockSpan` times are used. Times
|
|
|
|
|
/// `11..=16` are ignored.
|
2022-12-01 17:38:05 -08:00
|
|
|
|
relevant_times: [DateTime<Utc>; POW_ADJUSTMENT_BLOCK_SPAN],
|
2020-11-22 17:56:38 -08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl AdjustedDifficulty {
|
|
|
|
|
/// Initialise and return a new `AdjustedDifficulty` using a `candidate_block`,
|
|
|
|
|
/// `network`, and a `context`.
|
|
|
|
|
///
|
|
|
|
|
/// The `context` contains the previous
|
|
|
|
|
/// `PoWAveragingWindow + PoWMedianBlockSpan` (28) `difficulty_threshold`s and
|
|
|
|
|
/// `time`s from the relevant chain for `candidate_block`, in reverse height
|
|
|
|
|
/// order, starting with the previous block.
|
|
|
|
|
///
|
|
|
|
|
/// Note that the `time`s might not be in reverse chronological order, because
|
|
|
|
|
/// block times are supplied by miners.
|
|
|
|
|
///
|
2020-11-25 17:55:24 -08:00
|
|
|
|
/// # Panics
|
|
|
|
|
///
|
2020-11-22 17:56:38 -08:00
|
|
|
|
/// If the `context` contains fewer than 28 items.
|
|
|
|
|
pub fn new_from_block<C>(
|
|
|
|
|
candidate_block: &Block,
|
|
|
|
|
network: Network,
|
|
|
|
|
context: C,
|
|
|
|
|
) -> AdjustedDifficulty
|
|
|
|
|
where
|
|
|
|
|
C: IntoIterator<Item = (CompactDifficulty, DateTime<Utc>)>,
|
|
|
|
|
{
|
|
|
|
|
let candidate_block_height = candidate_block
|
|
|
|
|
.coinbase_height()
|
|
|
|
|
.expect("semantically valid blocks have a coinbase height");
|
|
|
|
|
let previous_block_height = (candidate_block_height - 1)
|
|
|
|
|
.expect("contextual validation is never run on the genesis block");
|
|
|
|
|
|
2022-11-28 01:06:32 -08:00
|
|
|
|
AdjustedDifficulty::new_from_header_time(
|
|
|
|
|
candidate_block.header.time,
|
2020-11-22 17:56:38 -08:00
|
|
|
|
previous_block_height,
|
|
|
|
|
network,
|
|
|
|
|
context,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-13 18:22:16 -07:00
|
|
|
|
/// Initialise and return a new [`AdjustedDifficulty`] using a
|
2022-11-28 01:06:32 -08:00
|
|
|
|
/// `candidate_header_time`, `previous_block_height`, `network`, and a `context`.
|
2020-11-22 17:56:38 -08:00
|
|
|
|
///
|
|
|
|
|
/// Designed for use when validating block headers, where the full block has not
|
|
|
|
|
/// been downloaded yet.
|
|
|
|
|
///
|
2022-06-13 18:22:16 -07:00
|
|
|
|
/// See [`Self::new_from_block`] for detailed information about the `context`.
|
2020-11-22 17:56:38 -08:00
|
|
|
|
///
|
2020-11-25 17:55:24 -08:00
|
|
|
|
/// # Panics
|
|
|
|
|
///
|
2020-11-22 17:56:38 -08:00
|
|
|
|
/// If the context contains fewer than 28 items.
|
2022-11-28 01:06:32 -08:00
|
|
|
|
pub fn new_from_header_time<C>(
|
|
|
|
|
candidate_header_time: DateTime<Utc>,
|
2020-11-22 17:56:38 -08:00
|
|
|
|
previous_block_height: block::Height,
|
|
|
|
|
network: Network,
|
|
|
|
|
context: C,
|
|
|
|
|
) -> AdjustedDifficulty
|
|
|
|
|
where
|
|
|
|
|
C: IntoIterator<Item = (CompactDifficulty, DateTime<Utc>)>,
|
|
|
|
|
{
|
|
|
|
|
let candidate_height = (previous_block_height + 1).expect("next block height is valid");
|
|
|
|
|
|
2020-11-25 17:56:57 -08:00
|
|
|
|
let (relevant_difficulty_thresholds, relevant_times) = context
|
2020-11-22 17:56:38 -08:00
|
|
|
|
.into_iter()
|
2022-12-01 17:38:05 -08:00
|
|
|
|
.take(POW_ADJUSTMENT_BLOCK_SPAN)
|
2020-11-25 17:56:57 -08:00
|
|
|
|
.unzip::<_, _, Vec<_>, Vec<_>>();
|
|
|
|
|
|
|
|
|
|
let relevant_difficulty_thresholds = relevant_difficulty_thresholds
|
2020-11-22 17:56:38 -08:00
|
|
|
|
.try_into()
|
|
|
|
|
.expect("not enough context: difficulty adjustment needs at least 28 (PoWAveragingWindow + PoWMedianBlockSpan) headers");
|
2020-11-25 17:56:57 -08:00
|
|
|
|
let relevant_times = relevant_times
|
2020-11-22 17:56:38 -08:00
|
|
|
|
.try_into()
|
|
|
|
|
.expect("not enough context: difficulty adjustment needs at least 28 (PoWAveragingWindow + PoWMedianBlockSpan) headers");
|
|
|
|
|
|
|
|
|
|
AdjustedDifficulty {
|
2022-11-28 01:06:32 -08:00
|
|
|
|
candidate_time: candidate_header_time,
|
2020-11-22 17:56:38 -08:00
|
|
|
|
candidate_height,
|
|
|
|
|
network,
|
|
|
|
|
relevant_difficulty_thresholds,
|
|
|
|
|
relevant_times,
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-11-18 01:20:06 -08:00
|
|
|
|
|
2020-12-02 21:13:05 -08:00
|
|
|
|
/// Returns the candidate block's height.
|
|
|
|
|
pub fn candidate_height(&self) -> block::Height {
|
|
|
|
|
self.candidate_height
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-02 20:28:42 -08:00
|
|
|
|
/// Returns the candidate block's time field.
|
|
|
|
|
pub fn candidate_time(&self) -> DateTime<Utc> {
|
|
|
|
|
self.candidate_time
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-13 17:05:31 -08:00
|
|
|
|
/// Returns the configured network.
|
|
|
|
|
pub fn network(&self) -> Network {
|
|
|
|
|
self.network
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-18 01:20:06 -08:00
|
|
|
|
/// 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 {
|
2020-11-19 00:00:56 -08:00
|
|
|
|
if NetworkUpgrade::is_testnet_min_difficulty_block(
|
|
|
|
|
self.network,
|
|
|
|
|
self.candidate_height,
|
|
|
|
|
self.candidate_time,
|
|
|
|
|
self.relevant_times[0],
|
|
|
|
|
) {
|
|
|
|
|
assert_eq!(
|
|
|
|
|
self.network,
|
|
|
|
|
Network::Testnet,
|
|
|
|
|
"invalid network: the minimum difficulty rule only applies on testnet"
|
|
|
|
|
);
|
2024-03-12 14:41:44 -07:00
|
|
|
|
self.network.target_difficulty_limit().to_compact()
|
2020-11-19 00:00:56 -08:00
|
|
|
|
} else {
|
|
|
|
|
self.threshold_bits()
|
|
|
|
|
}
|
2020-11-18 01:20:06 -08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Calculate the `difficulty_threshold` for a candidate block, based on the
|
|
|
|
|
/// `candidate_height`, `network`, and the relevant `difficulty_threshold`s and
|
|
|
|
|
/// `time`s.
|
|
|
|
|
///
|
2022-06-13 18:22:16 -07:00
|
|
|
|
/// See [`Self::expected_difficulty_threshold`] for details.
|
2020-11-18 01:20:06 -08:00
|
|
|
|
///
|
|
|
|
|
/// Implements `ThresholdBits` from the Zcash specification. (Which excludes the
|
|
|
|
|
/// Testnet minimum difficulty adjustment.)
|
|
|
|
|
fn threshold_bits(&self) -> CompactDifficulty {
|
2020-11-18 18:24:47 -08:00
|
|
|
|
let averaging_window_timespan = NetworkUpgrade::averaging_window_timespan_for_height(
|
|
|
|
|
self.network,
|
|
|
|
|
self.candidate_height,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
let threshold = (self.mean_target_difficulty() / averaging_window_timespan.num_seconds())
|
|
|
|
|
* self.median_timespan_bounded().num_seconds();
|
2024-03-12 14:41:44 -07:00
|
|
|
|
let threshold = min(self.network.target_difficulty_limit(), threshold);
|
2020-11-18 18:24:47 -08:00
|
|
|
|
|
|
|
|
|
threshold.to_compact()
|
2020-11-18 01:20:06 -08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// 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 {
|
2021-03-14 17:39:47 -07:00
|
|
|
|
// In Zebra, contextual validation starts after Canopy activation, so we
|
2020-11-18 01:20:06 -08:00
|
|
|
|
// 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();
|
|
|
|
|
|
2020-11-25 18:06:15 -08:00
|
|
|
|
let divisor: U256 = POW_AVERAGING_WINDOW.into();
|
|
|
|
|
total / divisor
|
2020-11-18 01:20:06 -08:00
|
|
|
|
}
|
2020-11-18 16:56:05 -08:00
|
|
|
|
|
2020-11-18 18:00:10 -08:00
|
|
|
|
/// Calculate the bounded median timespan. The median timespan is the
|
|
|
|
|
/// difference of medians of the timespan times, which are the `time`s from
|
|
|
|
|
/// the previous `PoWAveragingWindow + PoWMedianBlockSpan` (28) blocks in the
|
|
|
|
|
/// relevant chain.
|
2020-11-18 16:56:05 -08:00
|
|
|
|
///
|
|
|
|
|
/// Uses the candidate block's `height' and `network` to calculate the
|
|
|
|
|
/// `AveragingWindowTimespan` for that block.
|
|
|
|
|
///
|
|
|
|
|
/// The median timespan is damped by the `PoWDampingFactor`, and bounded by
|
|
|
|
|
/// `PoWMaxAdjustDown` and `PoWMaxAdjustUp`.
|
|
|
|
|
///
|
|
|
|
|
/// Implements `ActualTimespanBounded` from the Zcash specification.
|
|
|
|
|
///
|
|
|
|
|
/// Note: This calculation only uses `PoWMedianBlockSpan` (11) times at the
|
|
|
|
|
/// start and end of the timespan times. timespan times `[11..=16]` are ignored.
|
2020-11-18 17:56:01 -08:00
|
|
|
|
fn median_timespan_bounded(&self) -> Duration {
|
|
|
|
|
let averaging_window_timespan = NetworkUpgrade::averaging_window_timespan_for_height(
|
|
|
|
|
self.network,
|
|
|
|
|
self.candidate_height,
|
|
|
|
|
);
|
|
|
|
|
// This value is exact, but we need to truncate its nanoseconds component
|
2020-11-18 18:00:10 -08:00
|
|
|
|
let damped_variance =
|
|
|
|
|
(self.median_timespan() - averaging_window_timespan) / POW_DAMPING_FACTOR;
|
2020-11-18 17:56:01 -08:00
|
|
|
|
// num_seconds truncates negative values towards zero, matching the Zcash specification
|
|
|
|
|
let damped_variance = Duration::seconds(damped_variance.num_seconds());
|
|
|
|
|
|
|
|
|
|
// `ActualTimespanDamped` in the Zcash specification
|
|
|
|
|
let median_timespan_damped = averaging_window_timespan + damped_variance;
|
|
|
|
|
|
|
|
|
|
// `MinActualTimespan` and `MaxActualTimespan` in the Zcash spec
|
|
|
|
|
let min_median_timespan =
|
|
|
|
|
averaging_window_timespan * (100 - POW_MAX_ADJUST_UP_PERCENT) / 100;
|
|
|
|
|
let max_median_timespan =
|
|
|
|
|
averaging_window_timespan * (100 + POW_MAX_ADJUST_DOWN_PERCENT) / 100;
|
|
|
|
|
|
|
|
|
|
// `ActualTimespanBounded` in the Zcash specification
|
|
|
|
|
max(
|
|
|
|
|
min_median_timespan,
|
|
|
|
|
min(max_median_timespan, median_timespan_damped),
|
|
|
|
|
)
|
2020-11-18 16:56:05 -08:00
|
|
|
|
}
|
|
|
|
|
|
2020-11-18 18:00:10 -08:00
|
|
|
|
/// Calculate the median timespan. The median timespan is the difference of
|
|
|
|
|
/// medians of the timespan times, which are the `time`s from the previous
|
|
|
|
|
/// `PoWAveragingWindow + PoWMedianBlockSpan` (28) blocks in the relevant chain.
|
|
|
|
|
///
|
|
|
|
|
/// Implements `ActualTimespan` from the Zcash specification.
|
|
|
|
|
///
|
2022-06-13 18:22:16 -07:00
|
|
|
|
/// See [`Self::median_timespan_bounded`] for details.
|
2020-11-18 18:00:10 -08:00
|
|
|
|
fn median_timespan(&self) -> Duration {
|
2020-12-02 19:58:12 -08:00
|
|
|
|
let newer_median = self.median_time_past();
|
2020-11-18 18:00:10 -08:00
|
|
|
|
|
|
|
|
|
let older_times: [DateTime<Utc>; POW_MEDIAN_BLOCK_SPAN] = self.relevant_times
|
|
|
|
|
[POW_AVERAGING_WINDOW..]
|
|
|
|
|
.try_into()
|
|
|
|
|
.expect("relevant times is the correct length");
|
|
|
|
|
let older_median = AdjustedDifficulty::median_time(older_times);
|
|
|
|
|
|
|
|
|
|
// `ActualTimespan` in the Zcash specification
|
|
|
|
|
newer_median - older_median
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-02 19:58:12 -08:00
|
|
|
|
/// Calculate the median of the `time`s from the previous
|
|
|
|
|
/// `PoWMedianBlockSpan` (11) blocks in the relevant chain.
|
|
|
|
|
///
|
|
|
|
|
/// Implements `median-time-past` and `MedianTime(candidate_height)` from the
|
|
|
|
|
/// Zcash specification. (These functions are identical, but they are
|
|
|
|
|
/// specified in slightly different ways.)
|
|
|
|
|
pub fn median_time_past(&self) -> DateTime<Utc> {
|
|
|
|
|
let median_times: [DateTime<Utc>; POW_MEDIAN_BLOCK_SPAN] = self.relevant_times
|
|
|
|
|
[0..POW_MEDIAN_BLOCK_SPAN]
|
|
|
|
|
.try_into()
|
|
|
|
|
.expect("relevant times is the correct length");
|
|
|
|
|
|
|
|
|
|
AdjustedDifficulty::median_time(median_times)
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-18 16:56:05 -08:00
|
|
|
|
/// Calculate the median of the `median_block_span_times`: the `time`s from a
|
|
|
|
|
/// slice of `PoWMedianBlockSpan` (11) blocks in the relevant chain.
|
|
|
|
|
///
|
|
|
|
|
/// Implements `MedianTime` from the Zcash specification.
|
2023-01-31 12:42:11 -08:00
|
|
|
|
pub(crate) fn median_time(
|
2020-11-18 17:06:28 -08:00
|
|
|
|
mut median_block_span_times: [DateTime<Utc>; POW_MEDIAN_BLOCK_SPAN],
|
|
|
|
|
) -> DateTime<Utc> {
|
2020-11-18 16:56:05 -08:00
|
|
|
|
median_block_span_times.sort_unstable();
|
|
|
|
|
median_block_span_times[POW_MEDIAN_BLOCK_SPAN / 2]
|
|
|
|
|
}
|
2020-11-22 17:56:38 -08:00
|
|
|
|
}
|