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