zebra_state/service/read/difficulty.rs
1//! Get context and calculate difficulty for the next block.
2
3use std::sync::Arc;
4
5use chrono::{DateTime, Utc};
6
7use zebra_chain::{
8 block::{self, Block, Hash, Height},
9 history_tree::HistoryTree,
10 parameters::{Network, NetworkUpgrade},
11 serialization::{DateTime32, Duration32},
12 work::difficulty::{CompactDifficulty, PartialCumulativeWork, Work},
13};
14
15use crate::{
16 service::{
17 any_ancestor_blocks,
18 block_iter::any_chain_ancestor_iter,
19 check::{
20 difficulty::{
21 BLOCK_MAX_TIME_SINCE_MEDIAN, POW_ADJUSTMENT_BLOCK_SPAN, POW_MEDIAN_BLOCK_SPAN,
22 },
23 AdjustedDifficulty,
24 },
25 finalized_state::ZebraDb,
26 read::{
27 self, find::calculate_median_time_past, tree::history_tree,
28 FINALIZED_STATE_QUERY_RETRIES,
29 },
30 NonFinalizedState,
31 },
32 BoxError, GetBlockTemplateChainInfo,
33};
34
35/// Returns the [`GetBlockTemplateChainInfo`] for the current best chain.
36///
37/// # Panics
38///
39/// - If we don't have enough blocks in the state.
40pub fn get_block_template_chain_info(
41 non_finalized_state: &NonFinalizedState,
42 db: &ZebraDb,
43 network: &Network,
44) -> Result<GetBlockTemplateChainInfo, BoxError> {
45 let mut best_relevant_chain_and_history_tree_result =
46 best_relevant_chain_and_history_tree(non_finalized_state, db);
47
48 // Retry the finalized state query if it was interrupted by a finalizing block.
49 //
50 // TODO: refactor this into a generic retry(finalized_closure, process_and_check_closure) fn
51 for _ in 0..FINALIZED_STATE_QUERY_RETRIES {
52 if best_relevant_chain_and_history_tree_result.is_ok() {
53 break;
54 }
55
56 best_relevant_chain_and_history_tree_result =
57 best_relevant_chain_and_history_tree(non_finalized_state, db);
58 }
59
60 let (best_tip_height, best_tip_hash, best_relevant_chain, best_tip_history_tree) =
61 best_relevant_chain_and_history_tree_result?;
62
63 Ok(difficulty_time_and_history_tree(
64 best_relevant_chain,
65 best_tip_height,
66 best_tip_hash,
67 network,
68 best_tip_history_tree,
69 ))
70}
71
72/// Accepts a `non_finalized_state`, [`ZebraDb`], `num_blocks`, and a block hash to start at.
73///
74/// Iterates over up to the last `num_blocks` blocks, summing up their total work.
75/// Divides that total by the number of seconds between the timestamp of the
76/// first block in the iteration and 1 block below the last block.
77///
78/// Returns the solution rate per second for the current best chain, or `None` if
79/// the `start_hash` and at least 1 block below it are not found in the chain.
80#[allow(unused)]
81pub fn solution_rate(
82 non_finalized_state: &NonFinalizedState,
83 db: &ZebraDb,
84 num_blocks: usize,
85 start_hash: Hash,
86) -> Option<u128> {
87 // Take 1 extra header for calculating the number of seconds between when mining on the first
88 // block likely started. The work for the extra header is not added to `total_work`.
89 //
90 // Since we can't take more headers than are actually in the chain, this automatically limits
91 // `num_blocks` to the chain length, like `zcashd` does.
92 let mut header_iter =
93 any_chain_ancestor_iter::<block::Header>(non_finalized_state, db, start_hash)
94 .take(num_blocks.checked_add(1).unwrap_or(num_blocks))
95 .peekable();
96
97 let get_work = |header: &block::Header| {
98 header
99 .difficulty_threshold
100 .to_work()
101 .expect("work has already been validated")
102 };
103
104 // If there are no blocks in the range, we can't return a useful result.
105 let last_header = header_iter.peek()?;
106
107 // Initialize the cumulative variables.
108 let mut min_time = last_header.time;
109 let mut max_time = last_header.time;
110
111 let mut last_work = Work::zero();
112 let mut total_work = PartialCumulativeWork::zero();
113
114 for header in header_iter {
115 min_time = min_time.min(header.time);
116 max_time = max_time.max(header.time);
117
118 last_work = get_work(&header);
119 total_work += last_work;
120 }
121
122 // We added an extra header so we could estimate when mining on the first block
123 // in the window of `num_blocks` likely started. But we don't want to add the work
124 // for that header.
125 total_work -= last_work;
126
127 let work_duration = (max_time - min_time).num_seconds();
128
129 // Avoid division by zero errors and negative average work.
130 // This also handles the case where there's only one block in the range.
131 if work_duration <= 0 {
132 return None;
133 }
134
135 Some(total_work.as_u128() / work_duration as u128)
136}
137
138/// Do a consistency check by checking the finalized tip before and after all other database
139/// queries.
140///
141/// Returns the best chain tip, recent blocks in reverse height order from the tip,
142/// and the tip history tree.
143/// Returns an error if the tip obtained before and after is not the same.
144///
145/// # Panics
146///
147/// - If we don't have enough blocks in the state.
148fn best_relevant_chain_and_history_tree(
149 non_finalized_state: &NonFinalizedState,
150 db: &ZebraDb,
151) -> Result<(Height, block::Hash, Vec<Arc<Block>>, Arc<HistoryTree>), BoxError> {
152 let state_tip_before_queries = read::best_tip(non_finalized_state, db).ok_or_else(|| {
153 BoxError::from("Zebra's state is empty, wait until it syncs to the chain tip")
154 })?;
155
156 let best_relevant_chain =
157 any_ancestor_blocks(non_finalized_state, db, state_tip_before_queries.1);
158 let best_relevant_chain: Vec<_> = best_relevant_chain
159 .into_iter()
160 .take(POW_ADJUSTMENT_BLOCK_SPAN)
161 .collect();
162
163 if best_relevant_chain.is_empty() {
164 return Err("missing genesis block, wait until it is committed".into());
165 };
166
167 let history_tree = history_tree(
168 non_finalized_state.best_chain(),
169 db,
170 state_tip_before_queries.into(),
171 )
172 .expect("tip hash should exist in the chain");
173
174 let state_tip_after_queries =
175 read::best_tip(non_finalized_state, db).expect("already checked for an empty tip");
176
177 if state_tip_before_queries != state_tip_after_queries {
178 return Err("Zebra is committing too many blocks to the state, \
179 wait until it syncs to the chain tip"
180 .into());
181 }
182
183 Ok((
184 state_tip_before_queries.0,
185 state_tip_before_queries.1,
186 best_relevant_chain,
187 history_tree,
188 ))
189}
190
191/// Returns the [`GetBlockTemplateChainInfo`] for the supplied `relevant_chain`, tip, `network`,
192/// and `history_tree`.
193///
194/// The `relevant_chain` has recent blocks in reverse height order from the tip.
195///
196/// See [`get_block_template_chain_info()`] for details.
197fn difficulty_time_and_history_tree(
198 relevant_chain: Vec<Arc<Block>>,
199 tip_height: Height,
200 tip_hash: block::Hash,
201 network: &Network,
202 history_tree: Arc<HistoryTree>,
203) -> GetBlockTemplateChainInfo {
204 let relevant_data: Vec<(CompactDifficulty, DateTime<Utc>)> = relevant_chain
205 .iter()
206 .map(|block| (block.header.difficulty_threshold, block.header.time))
207 .collect();
208
209 let cur_time = DateTime32::now();
210
211 // > For each block other than the genesis block , nTime MUST be strictly greater than
212 // > the median-time-past of that block.
213 // https://zips.z.cash/protocol/protocol.pdf#blockheader
214 let median_time_past = calculate_median_time_past(
215 relevant_chain
216 .iter()
217 .take(POW_MEDIAN_BLOCK_SPAN)
218 .cloned()
219 .collect(),
220 );
221
222 let min_time = median_time_past
223 .checked_add(Duration32::from_seconds(1))
224 .expect("a valid block time plus a small constant is in-range");
225
226 // > For each block at block height 2 or greater on Mainnet, or block height 653606 or greater on Testnet, nTime
227 // > MUST be less than or equal to the median-time-past of that block plus 90 * 60 seconds.
228 //
229 // We ignore the height as we are checkpointing on Canopy or higher in Mainnet and Testnet.
230 let max_time = median_time_past
231 .checked_add(Duration32::from_seconds(BLOCK_MAX_TIME_SINCE_MEDIAN))
232 .expect("a valid block time plus a small constant is in-range");
233
234 // On Regtest, real time is far ahead of the chain's median-time-past (a fresh
235 // chain starts from the 2011-era genesis), so clamping `now()` to the valid
236 // range pins every mined block to `max_time` (median-time-past + 90 minutes),
237 // making chain time race ~90 minutes per block. That quickly outruns a
238 // following zcashd sidecar's acceptable block-time window and stalls its sync.
239 // Match zcashd's regtest behaviour by advancing block time minimally instead,
240 // keeping mined timestamps tightly clustered just above the median-time-past.
241 let cur_time = if network.is_regtest() {
242 min_time
243 } else {
244 cur_time.clamp(min_time, max_time)
245 };
246
247 // Now that we have a valid time, get the difficulty for that time.
248 let difficulty_adjustment = AdjustedDifficulty::new_from_header_time(
249 cur_time.into(),
250 tip_height,
251 network,
252 relevant_data.iter().cloned(),
253 );
254 let expected_difficulty = difficulty_adjustment.expected_difficulty_threshold();
255
256 let mut result = GetBlockTemplateChainInfo {
257 tip_hash,
258 tip_height,
259 chain_history_root: history_tree.hash(),
260 expected_difficulty,
261 cur_time,
262 min_time,
263 max_time,
264 };
265
266 adjust_difficulty_and_time_for_testnet(&mut result, network, tip_height, relevant_data);
267
268 result
269}
270
271/// Adjust the difficulty and time for the testnet minimum difficulty rule.
272///
273/// The `relevant_data` has recent block difficulties and times in reverse order from the tip.
274fn adjust_difficulty_and_time_for_testnet(
275 result: &mut GetBlockTemplateChainInfo,
276 network: &Network,
277 previous_block_height: Height,
278 relevant_data: Vec<(CompactDifficulty, DateTime<Utc>)>,
279) {
280 if network == &Network::Mainnet {
281 return;
282 }
283
284 // On testnet, changing the block time can also change the difficulty,
285 // due to the minimum difficulty consensus rule:
286 // > if the block time of a block at height `height ≥ 299188`
287 // > is greater than 6 * PoWTargetSpacing(height) seconds after that of the preceding block,
288 // > then the block is a minimum-difficulty block.
289 //
290 // The max time is always a minimum difficulty block, because the minimum difficulty
291 // gap is 7.5 minutes, but the maximum gap is 90 minutes. This means that testnet blocks
292 // have two valid time ranges with different difficulties:
293 // * 1s - 7m30s: standard difficulty
294 // * 7m31s - 90m: minimum difficulty
295 //
296 // In rare cases, this could make some testnet miners produce invalid blocks,
297 // if they use the full 90 minute time gap in the consensus rules.
298 // (The zcashd getblocktemplate RPC reference doesn't have a max_time field,
299 // so there is no standard way of telling miners that the max_time is smaller.)
300 //
301 // So Zebra adjusts the min or max times to produce a valid time range for the difficulty.
302 // There is still a small chance that miners will produce an invalid block, if they are
303 // just below the max time, and don't check it.
304
305 // The tip is the first relevant data block, because they are in reverse order.
306 let previous_block_time = relevant_data.first().expect("has at least one block").1;
307 let previous_block_time: DateTime32 = previous_block_time
308 .try_into()
309 .expect("valid blocks have in-range times");
310
311 let Some(minimum_difficulty_spacing) =
312 NetworkUpgrade::minimum_difficulty_spacing_for_height(network, previous_block_height)
313 else {
314 // Returns early if the testnet minimum difficulty consensus rule is not active
315 return;
316 };
317
318 let minimum_difficulty_spacing: Duration32 = minimum_difficulty_spacing
319 .try_into()
320 .expect("small positive values are in-range");
321
322 // The first minimum difficulty time is strictly greater than the spacing.
323 let std_difficulty_max_time = previous_block_time
324 .checked_add(minimum_difficulty_spacing)
325 .expect("a valid block time plus a small constant is in-range");
326 let min_difficulty_min_time = std_difficulty_max_time
327 .checked_add(Duration32::from_seconds(1))
328 .expect("a valid block time plus a small constant is in-range");
329
330 // Offer a minimum-difficulty template only once `cur_time` is strictly past
331 // `previous_block_time + 6 * PoWTargetSpacing` (the latest time a standard-difficulty
332 // block may use; a minimum-difficulty block's time must be strictly greater).
333 //
334 // A minimum-difficulty block is only consensus-valid if its time is more than
335 // `6 * PoWTargetSpacing` after the previous block. Switching to a minimum-difficulty template
336 // before `cur_time` has reached that point would require clamping `cur_time` up to `min_time`,
337 // i.e. future-dating the block timestamp ahead of real time purely to obtain minimum difficulty.
338 //
339 // Zebra used to do this, switching 150 seconds early (`2 * PoWTargetSpacing` after Blossom),
340 // via a former `EXTRA_TIME_TO_MINE_A_BLOCK` constant. On a chain with any sustained hashrate,
341 // that behaviour produced a stream of future-dated minimum-difficulty blocks, each of which
342 // sharply reduced the difficulty (the difficulty average is over targets and includes
343 // minimum-difficulty blocks), depressing the difficulty far below equilibrium. See
344 // <https://github.com/zcash/zips/issues/1321>.
345 //
346 // This does not change the underlying difficulty averaging: a genuine gap of more than
347 // `6 * PoWTargetSpacing` still yields a minimum-difficulty block that reduces the difficulty.
348 // It only stops Zebra from proactively generating such blocks by future-dating timestamps.
349 //
350 // `cur_time` was already clamped into `[min_time, max_time]` by the caller
351 // (`difficulty_time_and_history_tree`); we don't restore the un-clamped `now` before
352 // choosing standard vs. minimum difficulty below, because whichever way that clamp
353 // moved `cur_time`, the outcome is benign:
354 // - clamped up to `min_time`: more likely a minimum-difficulty block, which makes
355 // mining easier;
356 // - clamped down to `max_time`: almost always a minimum-difficulty block.
357 if result.cur_time <= std_difficulty_max_time {
358 // Standard difficulty: the cur and max time need to exclude min difficulty blocks
359
360 // The maximum time can only be decreased, and only as far as min_time.
361 // The old minimum is still required by other consensus rules.
362 result.max_time = std_difficulty_max_time.clamp(result.min_time, result.max_time);
363
364 // The current time only needs to be decreased if the max_time decreased past it.
365 // Decreasing the current time can't change the difficulty.
366 result.cur_time = result.cur_time.clamp(result.min_time, result.max_time);
367 } else {
368 // Minimum difficulty: the min and cur time need to exclude std difficulty blocks
369
370 // The minimum time can only be increased, and only as far as max_time.
371 // The old maximum is still required by other consensus rules.
372 result.min_time = min_difficulty_min_time.clamp(result.min_time, result.max_time);
373
374 // The current time only needs to be increased if the min_time increased past it.
375 result.cur_time = result.cur_time.clamp(result.min_time, result.max_time);
376
377 // And then the difficulty needs to be updated for cur_time.
378 result.expected_difficulty = AdjustedDifficulty::new_from_header_time(
379 result.cur_time.into(),
380 previous_block_height,
381 network,
382 relevant_data.iter().cloned(),
383 )
384 .expected_difficulty_threshold();
385 }
386}
387
388#[cfg(test)]
389mod tests {
390 //! Unit tests for the Testnet minimum-difficulty template adjustment. They construct the
391 //! [`GetBlockTemplateChainInfo`] and block context with fixed times, exercising
392 //! `adjust_difficulty_and_time_for_testnet` deterministically without reading the real
393 //! clock (the `DateTime32::now()` call lives only in its caller).
394
395 use super::*;
396 use zebra_chain::work::difficulty::ParameterDifficulty as _;
397
398 // A Testnet height at which the minimum-difficulty rule is active (>= 299188) and the
399 // target spacing is the post-Blossom 75 s, so the minimum-difficulty gap is 6 * 75 = 450 s.
400 const ACTIVE_HEIGHT: Height = Height(2_000_000);
401 const PREV: u32 = 1_600_000_000;
402 const GAP: u32 = 6 * 75;
403
404 /// Recent block difficulties and times in reverse order from the tip, with the most
405 /// recent (first) block time equal to `PREV`. The difficulty thresholds are irrelevant
406 /// to the minimum-difficulty rule under test.
407 fn recent_block_data(network: &Network) -> Vec<(CompactDifficulty, DateTime<Utc>)> {
408 let threshold = network.target_difficulty_limit().to_compact();
409 (0..POW_ADJUSTMENT_BLOCK_SPAN)
410 .map(|i| (threshold, DateTime32::from(PREV - i as u32).into()))
411 .collect()
412 }
413
414 /// A [`GetBlockTemplateChainInfo`] with candidate time `cur_time` and a wide
415 /// `[min_time, max_time]` window around `PREV`, so the only clamping under test comes
416 /// from the minimum-difficulty adjustment. `expected_difficulty` starts at a sentinel.
417 fn chain_info(cur_time: u32) -> GetBlockTemplateChainInfo {
418 GetBlockTemplateChainInfo {
419 tip_hash: Hash([0; 32]),
420 tip_height: Height(0),
421 chain_history_root: None,
422 expected_difficulty: CompactDifficulty::default(),
423 cur_time: DateTime32::from(cur_time),
424 min_time: DateTime32::from(PREV - 100),
425 max_time: DateTime32::from(PREV + BLOCK_MAX_TIME_SINCE_MEDIAN),
426 }
427 }
428
429 /// A candidate time inside the old "eager" window (`PREV + 300 .. PREV + 450`) must stay
430 /// standard-difficulty and must not be future-dated. Zebra used to switch this to a
431 /// minimum-difficulty template with `cur_time` clamped up to `PREV + 451`.
432 #[test]
433 fn eager_window_stays_standard_difficulty_and_is_not_future_dated() {
434 let network = Network::new_default_testnet();
435 let cur = PREV + 400;
436 let mut result = chain_info(cur);
437
438 adjust_difficulty_and_time_for_testnet(
439 &mut result,
440 &network,
441 ACTIVE_HEIGHT,
442 recent_block_data(&network),
443 );
444
445 // Still standard difficulty: expected_difficulty left untouched (sentinel unchanged).
446 assert_eq!(result.expected_difficulty, CompactDifficulty::default());
447 // Not future-dated: cur_time unchanged, still before the consensus threshold.
448 assert_eq!(result.cur_time, DateTime32::from(cur));
449 // max_time clamped down to the standard-difficulty boundary (PREV + 450).
450 assert_eq!(result.max_time, DateTime32::from(PREV + GAP));
451 }
452
453 /// A candidate time strictly past `PREV + 450` switches to a minimum-difficulty template.
454 #[test]
455 fn past_the_threshold_switches_to_minimum_difficulty() {
456 let network = Network::new_default_testnet();
457 let cur = PREV + 500;
458 let mut result = chain_info(cur);
459
460 adjust_difficulty_and_time_for_testnet(
461 &mut result,
462 &network,
463 ACTIVE_HEIGHT,
464 recent_block_data(&network),
465 );
466
467 // Minimum difficulty: expected_difficulty recomputed to the PoWLimit.
468 assert_eq!(
469 result.expected_difficulty,
470 network.target_difficulty_limit().to_compact()
471 );
472 // min_time raised to PREV + 451; cur_time is already past it, so it is unchanged.
473 assert_eq!(result.min_time, DateTime32::from(PREV + GAP + 1));
474 assert_eq!(result.cur_time, DateTime32::from(cur));
475 }
476
477 /// The gap is a strict `>`: exactly `PREV + 450` is standard, `PREV + 451` is minimum.
478 #[test]
479 fn threshold_is_inclusive_for_standard_difficulty() {
480 let network = Network::new_default_testnet();
481
482 let mut at_threshold = chain_info(PREV + GAP);
483 adjust_difficulty_and_time_for_testnet(
484 &mut at_threshold,
485 &network,
486 ACTIVE_HEIGHT,
487 recent_block_data(&network),
488 );
489 assert_eq!(
490 at_threshold.expected_difficulty,
491 CompactDifficulty::default()
492 );
493
494 let mut past_threshold = chain_info(PREV + GAP + 1);
495 adjust_difficulty_and_time_for_testnet(
496 &mut past_threshold,
497 &network,
498 ACTIVE_HEIGHT,
499 recent_block_data(&network),
500 );
501 assert_eq!(
502 past_threshold.expected_difficulty,
503 network.target_difficulty_limit().to_compact()
504 );
505 }
506
507 /// The adjustment only applies to test networks: on Mainnet it is a no-op.
508 #[test]
509 fn mainnet_is_a_no_op() {
510 let network = Network::Mainnet;
511 let cur = PREV + 400;
512 let mut result = chain_info(cur);
513
514 adjust_difficulty_and_time_for_testnet(
515 &mut result,
516 &network,
517 ACTIVE_HEIGHT,
518 recent_block_data(&network),
519 );
520
521 assert_eq!(result.expected_difficulty, CompactDifficulty::default());
522 assert_eq!(result.cur_time, DateTime32::from(cur));
523 assert_eq!(result.min_time, DateTime32::from(PREV - 100));
524 assert_eq!(
525 result.max_time,
526 DateTime32::from(PREV + BLOCK_MAX_TIME_SINCE_MEDIAN)
527 );
528 }
529
530 /// Below `TESTNET_MINIMUM_DIFFICULTY_START_HEIGHT` (299188) the rule is inactive, so the
531 /// adjustment is a no-op even on Testnet.
532 #[test]
533 fn below_start_height_is_a_no_op() {
534 let network = Network::new_default_testnet();
535 let cur = PREV + 400;
536 let mut result = chain_info(cur);
537
538 adjust_difficulty_and_time_for_testnet(
539 &mut result,
540 &network,
541 Height(100_000),
542 recent_block_data(&network),
543 );
544
545 assert_eq!(result.expected_difficulty, CompactDifficulty::default());
546 assert_eq!(result.cur_time, DateTime32::from(cur));
547 assert_eq!(
548 result.max_time,
549 DateTime32::from(PREV + BLOCK_MAX_TIME_SINCE_MEDIAN)
550 );
551 }
552}