zebra_state/service/read/
difficulty.rs1use 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
34pub 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 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#[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 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 let last_header = header_iter.peek()?;
121
122 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 total_work -= last_work;
141
142 let work_duration = (max_time - min_time).num_seconds();
143
144 if work_duration <= 0 {
147 return None;
148 }
149
150 Some(total_work.as_u128() / work_duration as u128)
151}
152
153fn 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 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
222fn 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 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 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 let cur_time = if network.is_regtest() {
271 min_time
272 } else {
273 now.clamp(min_time, max_time)
274 };
275
276 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
301fn 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 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 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 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 if result.cur_time <= std_difficulty_max_time {
379 result.max_time = std_difficulty_max_time.clamp(result.min_time, result.max_time);
384
385 result.cur_time = result.cur_time.clamp(result.min_time, result.max_time);
388 } else {
389 result.min_time = min_difficulty_min_time.clamp(result.min_time, result.max_time);
394
395 result.cur_time = result.cur_time.clamp(result.min_time, result.max_time);
397
398 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 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 const ACTIVE_HEIGHT: Height = Height(2_000_000);
425 const PREV: u32 = 1_600_000_000;
426 const GAP: u32 = 6 * 75;
427
428 fn recent_block_data(network: &Network) -> Vec<(CompactDifficulty, DateTime<Utc>)> {
432 let threshold = network.target_difficulty_limit().to_compact();
433 (0..MAX_POW_ADJUSTMENT_BLOCK_SPAN)
436 .map(|i| (threshold, DateTime32::from(PREV - i as u32).into()))
437 .collect()
438 }
439
440 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 #[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 assert_eq!(result.expected_difficulty, CompactDifficulty::default());
474 assert_eq!(result.cur_time, DateTime32::from(cur));
476 assert_eq!(result.max_time, DateTime32::from(PREV + GAP));
478 }
479
480 #[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 assert_eq!(
496 result.expected_difficulty,
497 network.target_difficulty_limit().to_compact()
498 );
499 assert_eq!(result.min_time, DateTime32::from(PREV + GAP + 1));
501 assert_eq!(result.cur_time, DateTime32::from(cur));
502 }
503
504 #[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 #[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 #[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}