- Updates median time past and difficulty checks to use fewer than 11/17/28 blocks
This commit is contained in:
parent
088e4e4904
commit
5564c38a81
|
@ -699,7 +699,7 @@ impl ParameterDifficulty for Network {
|
|||
/* 2^243 - 1 */
|
||||
Network::Mainnet => (U256::one() << 243) - 1,
|
||||
/* 2^251 - 1 */
|
||||
// TODO: Add a `target_difficulty_limit` field to `testnet::Parameters` to return here.
|
||||
// TODO: Add a `target_difficulty_limit` field to `testnet::Parameters` to return here. (`U256::from_big_endian(&[0x0f].repeat(8))` for Regtest)
|
||||
Network::Testnet(_params) => (U256::one() << 251) - 1,
|
||||
};
|
||||
|
||||
|
|
|
@ -638,7 +638,9 @@ where
|
|||
//
|
||||
// Optional TODO:
|
||||
// - add `async changed()` method to ChainSyncStatus (like `ChainTip`)
|
||||
// TODO: Add a `disable_peers` field to `Network` to check instead of `disable_pow()` (#8361)
|
||||
// TODO:
|
||||
// - Add a `disable_peers` field to `Network` to check instead of `disable_pow()` (#8361)
|
||||
// - Check the field in `sync_status` so it applies to the mempool as well.
|
||||
if !network.disable_pow() {
|
||||
check_synced_to_tip(&network, latest_chain_tip.clone(), sync_status.clone())?;
|
||||
}
|
||||
|
|
|
@ -64,14 +64,14 @@ pub(crate) struct AdjustedDifficulty {
|
|||
/// The `header.difficulty_threshold`s from the previous
|
||||
/// `PoWAveragingWindow + PoWMedianBlockSpan` (28) blocks, in reverse height
|
||||
/// order.
|
||||
relevant_difficulty_thresholds: [CompactDifficulty; POW_ADJUSTMENT_BLOCK_SPAN],
|
||||
relevant_difficulty_thresholds: Vec<CompactDifficulty>,
|
||||
/// 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.
|
||||
relevant_times: [DateTime<Utc>; POW_ADJUSTMENT_BLOCK_SPAN],
|
||||
relevant_times: Vec<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
impl AdjustedDifficulty {
|
||||
|
@ -138,13 +138,6 @@ impl AdjustedDifficulty {
|
|||
.take(POW_ADJUSTMENT_BLOCK_SPAN)
|
||||
.unzip::<_, _, Vec<_>, Vec<_>>();
|
||||
|
||||
let relevant_difficulty_thresholds = relevant_difficulty_thresholds
|
||||
.try_into()
|
||||
.expect("not enough context: difficulty adjustment needs at least 28 (PoWAveragingWindow + PoWMedianBlockSpan) headers");
|
||||
let relevant_times = relevant_times
|
||||
.try_into()
|
||||
.expect("not enough context: difficulty adjustment needs at least 28 (PoWAveragingWindow + PoWMedianBlockSpan) headers");
|
||||
|
||||
AdjustedDifficulty {
|
||||
candidate_time: candidate_header_time,
|
||||
candidate_height,
|
||||
|
@ -226,7 +219,11 @@ impl AdjustedDifficulty {
|
|||
// specification is unreachable.
|
||||
|
||||
let averaging_window_thresholds =
|
||||
&self.relevant_difficulty_thresholds[0..POW_AVERAGING_WINDOW];
|
||||
if self.relevant_difficulty_thresholds.len() >= POW_AVERAGING_WINDOW {
|
||||
&self.relevant_difficulty_thresholds[0..POW_AVERAGING_WINDOW]
|
||||
} else {
|
||||
return self.network.target_difficulty_limit();
|
||||
};
|
||||
|
||||
// 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
|
||||
|
@ -297,10 +294,14 @@ impl AdjustedDifficulty {
|
|||
fn median_timespan(&self) -> Duration {
|
||||
let newer_median = self.median_time_past();
|
||||
|
||||
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_times: Vec<_> = self
|
||||
.relevant_times
|
||||
.iter()
|
||||
.rev()
|
||||
.cloned()
|
||||
.take(POW_MEDIAN_BLOCK_SPAN)
|
||||
.collect();
|
||||
|
||||
let older_median = AdjustedDifficulty::median_time(older_times);
|
||||
|
||||
// `ActualTimespan` in the Zcash specification
|
||||
|
@ -314,22 +315,30 @@ impl AdjustedDifficulty {
|
|||
/// 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");
|
||||
let median_times: Vec<DateTime<Utc>> = self
|
||||
.relevant_times
|
||||
.iter()
|
||||
.take(POW_MEDIAN_BLOCK_SPAN)
|
||||
.cloned()
|
||||
.collect();
|
||||
|
||||
AdjustedDifficulty::median_time(median_times)
|
||||
}
|
||||
|
||||
/// Calculate the median of the `median_block_span_times`: the `time`s from a
|
||||
/// slice of `PoWMedianBlockSpan` (11) blocks in the relevant chain.
|
||||
/// Vec of `PoWMedianBlockSpan` (11) or fewer blocks in the relevant chain.
|
||||
///
|
||||
/// Implements `MedianTime` from the Zcash specification.
|
||||
pub(crate) fn median_time(
|
||||
mut median_block_span_times: [DateTime<Utc>; POW_MEDIAN_BLOCK_SPAN],
|
||||
) -> DateTime<Utc> {
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// If provided an empty Vec
|
||||
pub(crate) fn median_time(mut median_block_span_times: Vec<DateTime<Utc>>) -> DateTime<Utc> {
|
||||
median_block_span_times.sort_unstable();
|
||||
median_block_span_times[POW_MEDIAN_BLOCK_SPAN / 2]
|
||||
|
||||
// > median(𝑆) := sorted(𝑆)ceiling((length(𝑆)+1)/2)
|
||||
// <https://zips.z.cash/protocol/protocol.pdf>, section 7.7.3, Difficulty Adjustment (p. 132)
|
||||
let median_idx = (POW_MEDIAN_BLOCK_SPAN / 2).min(median_block_span_times.len() / 2);
|
||||
median_block_span_times[median_idx]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -152,15 +152,7 @@ pub fn solution_rate(
|
|||
fn best_relevant_chain_and_history_tree(
|
||||
non_finalized_state: &NonFinalizedState,
|
||||
db: &ZebraDb,
|
||||
) -> Result<
|
||||
(
|
||||
Height,
|
||||
block::Hash,
|
||||
[Arc<Block>; POW_ADJUSTMENT_BLOCK_SPAN],
|
||||
Arc<HistoryTree>,
|
||||
),
|
||||
BoxError,
|
||||
> {
|
||||
) -> Result<(Height, block::Hash, Vec<Arc<Block>>, Arc<HistoryTree>), BoxError> {
|
||||
let state_tip_before_queries = read::best_tip(non_finalized_state, db).ok_or_else(|| {
|
||||
BoxError::from("Zebra's state is empty, wait until it syncs to the chain tip")
|
||||
})?;
|
||||
|
@ -171,9 +163,10 @@ fn best_relevant_chain_and_history_tree(
|
|||
.into_iter()
|
||||
.take(POW_ADJUSTMENT_BLOCK_SPAN)
|
||||
.collect();
|
||||
let best_relevant_chain = best_relevant_chain.try_into().map_err(|_error| {
|
||||
"Zebra's state only has a few blocks, wait until it syncs to the chain tip"
|
||||
})?;
|
||||
|
||||
if best_relevant_chain.is_empty() {
|
||||
return Err("missing genesis block, wait until it is committed".into());
|
||||
};
|
||||
|
||||
let history_tree = history_tree(
|
||||
non_finalized_state.best_chain(),
|
||||
|
@ -206,7 +199,7 @@ fn best_relevant_chain_and_history_tree(
|
|||
///
|
||||
/// See [`get_block_template_chain_info()`] for details.
|
||||
fn difficulty_time_and_history_tree(
|
||||
relevant_chain: [Arc<Block>; POW_ADJUSTMENT_BLOCK_SPAN],
|
||||
relevant_chain: Vec<Arc<Block>>,
|
||||
tip_height: Height,
|
||||
tip_hash: block::Hash,
|
||||
network: &Network,
|
||||
|
@ -223,10 +216,11 @@ fn difficulty_time_and_history_tree(
|
|||
// > the median-time-past of that block.
|
||||
// https://zips.z.cash/protocol/protocol.pdf#blockheader
|
||||
let median_time_past = calculate_median_time_past(
|
||||
relevant_chain[0..POW_MEDIAN_BLOCK_SPAN]
|
||||
.to_vec()
|
||||
.try_into()
|
||||
.expect("slice is correct size"),
|
||||
relevant_chain
|
||||
.iter()
|
||||
.take(POW_MEDIAN_BLOCK_SPAN)
|
||||
.cloned()
|
||||
.collect(),
|
||||
);
|
||||
|
||||
let min_time = median_time_past
|
||||
|
@ -307,9 +301,13 @@ fn adjust_difficulty_and_time_for_testnet(
|
|||
.try_into()
|
||||
.expect("valid blocks have in-range times");
|
||||
|
||||
let minimum_difficulty_spacing =
|
||||
let Some(minimum_difficulty_spacing) =
|
||||
NetworkUpgrade::minimum_difficulty_spacing_for_height(network, previous_block_height)
|
||||
.expect("just checked testnet, and the RPC returns an error for low heights");
|
||||
else {
|
||||
// Returns early if the testnet minimum difficulty consensus rule is not active
|
||||
return;
|
||||
};
|
||||
|
||||
let minimum_difficulty_spacing: Duration32 = minimum_difficulty_spacing
|
||||
.try_into()
|
||||
.expect("small positive values are in-range");
|
||||
|
|
|
@ -596,7 +596,7 @@ pub fn next_median_time_past(
|
|||
fn best_relevant_chain(
|
||||
non_finalized_state: &NonFinalizedState,
|
||||
db: &ZebraDb,
|
||||
) -> Result<[Arc<Block>; POW_MEDIAN_BLOCK_SPAN], BoxError> {
|
||||
) -> Result<Vec<Arc<Block>>, BoxError> {
|
||||
let state_tip_before_queries = read::best_tip(non_finalized_state, db).ok_or_else(|| {
|
||||
BoxError::from("Zebra's state is empty, wait until it syncs to the chain tip")
|
||||
})?;
|
||||
|
@ -607,9 +607,10 @@ fn best_relevant_chain(
|
|||
.into_iter()
|
||||
.take(POW_MEDIAN_BLOCK_SPAN)
|
||||
.collect();
|
||||
let best_relevant_chain = best_relevant_chain.try_into().map_err(|_error| {
|
||||
"Zebra's state only has a few blocks, wait until it syncs to the chain tip"
|
||||
})?;
|
||||
|
||||
if best_relevant_chain.is_empty() {
|
||||
return Err("missing genesis block, wait until it is committed".into());
|
||||
};
|
||||
|
||||
let state_tip_after_queries =
|
||||
read::best_tip(non_finalized_state, db).expect("already checked for an empty tip");
|
||||
|
@ -628,9 +629,7 @@ fn best_relevant_chain(
|
|||
/// The `relevant_chain` has blocks in reverse height order.
|
||||
///
|
||||
/// See [`next_median_time_past()`] for details.
|
||||
pub(crate) fn calculate_median_time_past(
|
||||
relevant_chain: [Arc<Block>; POW_MEDIAN_BLOCK_SPAN],
|
||||
) -> DateTime32 {
|
||||
pub(crate) fn calculate_median_time_past(relevant_chain: Vec<Arc<Block>>) -> DateTime32 {
|
||||
let relevant_data: Vec<DateTime<Utc>> = relevant_chain
|
||||
.iter()
|
||||
.map(|block| block.header.time)
|
||||
|
@ -640,11 +639,7 @@ pub(crate) fn calculate_median_time_past(
|
|||
// > preceding PoWMedianBlockSpan blocks (or all preceding blocks if there are fewer than
|
||||
// > PoWMedianBlockSpan). The median-time-past of a genesis block is not defined.
|
||||
// https://zips.z.cash/protocol/protocol.pdf#blockheader
|
||||
let median_time_past = AdjustedDifficulty::median_time(
|
||||
relevant_data
|
||||
.try_into()
|
||||
.expect("always has the correct length due to function argument type"),
|
||||
);
|
||||
let median_time_past = AdjustedDifficulty::median_time(relevant_data);
|
||||
|
||||
DateTime32::try_from(median_time_past).expect("valid blocks have in-range times")
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@ pub(crate) async fn submit_blocks_test() -> Result<()> {
|
|||
|
||||
let network = Network::new_regtest(None);
|
||||
let mut config = random_known_rpc_port_config(false, &network)?;
|
||||
config.mempool.debug_enable_at_height = Some(0);
|
||||
let rpc_address = config.rpc.listen_addr.unwrap();
|
||||
|
||||
let mut zebrad = testdir()?
|
||||
|
|
Loading…
Reference in New Issue