Skip to main content

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}