zebra_state/service/read/
difficulty.rs1use 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, POST_BLOSSOM_POW_TARGET_SPACING},
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
35pub const EXTRA_TIME_TO_MINE_A_BLOCK: u32 = POST_BLOSSOM_POW_TARGET_SPACING * 2;
39
40pub fn get_block_template_chain_info(
46 non_finalized_state: &NonFinalizedState,
47 db: &ZebraDb,
48 network: &Network,
49) -> Result<GetBlockTemplateChainInfo, BoxError> {
50 let mut best_relevant_chain_and_history_tree_result =
51 best_relevant_chain_and_history_tree(non_finalized_state, db);
52
53 for _ in 0..FINALIZED_STATE_QUERY_RETRIES {
57 if best_relevant_chain_and_history_tree_result.is_ok() {
58 break;
59 }
60
61 best_relevant_chain_and_history_tree_result =
62 best_relevant_chain_and_history_tree(non_finalized_state, db);
63 }
64
65 let (best_tip_height, best_tip_hash, best_relevant_chain, best_tip_history_tree) =
66 best_relevant_chain_and_history_tree_result?;
67
68 Ok(difficulty_time_and_history_tree(
69 best_relevant_chain,
70 best_tip_height,
71 best_tip_hash,
72 network,
73 best_tip_history_tree,
74 ))
75}
76
77#[allow(unused)]
86pub fn solution_rate(
87 non_finalized_state: &NonFinalizedState,
88 db: &ZebraDb,
89 num_blocks: usize,
90 start_hash: Hash,
91) -> Option<u128> {
92 let mut header_iter =
98 any_chain_ancestor_iter::<block::Header>(non_finalized_state, db, start_hash)
99 .take(num_blocks.checked_add(1).unwrap_or(num_blocks))
100 .peekable();
101
102 let get_work = |header: &block::Header| {
103 header
104 .difficulty_threshold
105 .to_work()
106 .expect("work has already been validated")
107 };
108
109 let last_header = header_iter.peek()?;
111
112 let mut min_time = last_header.time;
114 let mut max_time = last_header.time;
115
116 let mut last_work = Work::zero();
117 let mut total_work = PartialCumulativeWork::zero();
118
119 for header in header_iter {
120 min_time = min_time.min(header.time);
121 max_time = max_time.max(header.time);
122
123 last_work = get_work(&header);
124 total_work += last_work;
125 }
126
127 total_work -= last_work;
131
132 let work_duration = (max_time - min_time).num_seconds();
133
134 if work_duration <= 0 {
137 return None;
138 }
139
140 Some(total_work.as_u128() / work_duration as u128)
141}
142
143fn best_relevant_chain_and_history_tree(
154 non_finalized_state: &NonFinalizedState,
155 db: &ZebraDb,
156) -> Result<(Height, block::Hash, Vec<Arc<Block>>, Arc<HistoryTree>), BoxError> {
157 let state_tip_before_queries = read::best_tip(non_finalized_state, db).ok_or_else(|| {
158 BoxError::from("Zebra's state is empty, wait until it syncs to the chain tip")
159 })?;
160
161 let best_relevant_chain =
162 any_ancestor_blocks(non_finalized_state, db, state_tip_before_queries.1);
163 let best_relevant_chain: Vec<_> = best_relevant_chain
164 .into_iter()
165 .take(POW_ADJUSTMENT_BLOCK_SPAN)
166 .collect();
167
168 if best_relevant_chain.is_empty() {
169 return Err("missing genesis block, wait until it is committed".into());
170 };
171
172 let history_tree = history_tree(
173 non_finalized_state.best_chain(),
174 db,
175 state_tip_before_queries.into(),
176 )
177 .expect("tip hash should exist in the chain");
178
179 let state_tip_after_queries =
180 read::best_tip(non_finalized_state, db).expect("already checked for an empty tip");
181
182 if state_tip_before_queries != state_tip_after_queries {
183 return Err("Zebra is committing too many blocks to the state, \
184 wait until it syncs to the chain tip"
185 .into());
186 }
187
188 Ok((
189 state_tip_before_queries.0,
190 state_tip_before_queries.1,
191 best_relevant_chain,
192 history_tree,
193 ))
194}
195
196fn difficulty_time_and_history_tree(
203 relevant_chain: Vec<Arc<Block>>,
204 tip_height: Height,
205 tip_hash: block::Hash,
206 network: &Network,
207 history_tree: Arc<HistoryTree>,
208) -> GetBlockTemplateChainInfo {
209 let relevant_data: Vec<(CompactDifficulty, DateTime<Utc>)> = relevant_chain
210 .iter()
211 .map(|block| (block.header.difficulty_threshold, block.header.time))
212 .collect();
213
214 let cur_time = DateTime32::now();
215
216 let median_time_past = calculate_median_time_past(
220 relevant_chain
221 .iter()
222 .take(POW_MEDIAN_BLOCK_SPAN)
223 .cloned()
224 .collect(),
225 );
226
227 let min_time = median_time_past
228 .checked_add(Duration32::from_seconds(1))
229 .expect("a valid block time plus a small constant is in-range");
230
231 let max_time = median_time_past
236 .checked_add(Duration32::from_seconds(BLOCK_MAX_TIME_SINCE_MEDIAN))
237 .expect("a valid block time plus a small constant is in-range");
238
239 let cur_time = cur_time.clamp(min_time, max_time);
240
241 let difficulty_adjustment = AdjustedDifficulty::new_from_header_time(
243 cur_time.into(),
244 tip_height,
245 network,
246 relevant_data.iter().cloned(),
247 );
248 let expected_difficulty = difficulty_adjustment.expected_difficulty_threshold();
249
250 let mut result = GetBlockTemplateChainInfo {
251 tip_hash,
252 tip_height,
253 chain_history_root: history_tree.hash(),
254 expected_difficulty,
255 cur_time,
256 min_time,
257 max_time,
258 };
259
260 adjust_difficulty_and_time_for_testnet(&mut result, network, tip_height, relevant_data);
261
262 result
263}
264
265fn adjust_difficulty_and_time_for_testnet(
269 result: &mut GetBlockTemplateChainInfo,
270 network: &Network,
271 previous_block_height: Height,
272 relevant_data: Vec<(CompactDifficulty, DateTime<Utc>)>,
273) {
274 if network == &Network::Mainnet {
275 return;
276 }
277
278 let previous_block_time = relevant_data.first().expect("has at least one block").1;
301 let previous_block_time: DateTime32 = previous_block_time
302 .try_into()
303 .expect("valid blocks have in-range times");
304
305 let Some(minimum_difficulty_spacing) =
306 NetworkUpgrade::minimum_difficulty_spacing_for_height(network, previous_block_height)
307 else {
308 return;
310 };
311
312 let minimum_difficulty_spacing: Duration32 = minimum_difficulty_spacing
313 .try_into()
314 .expect("small positive values are in-range");
315
316 let std_difficulty_max_time = previous_block_time
318 .checked_add(minimum_difficulty_spacing)
319 .expect("a valid block time plus a small constant is in-range");
320 let min_difficulty_min_time = std_difficulty_max_time
321 .checked_add(Duration32::from_seconds(1))
322 .expect("a valid block time plus a small constant is in-range");
323
324 let local_std_difficulty_limit = std_difficulty_max_time
335 .checked_sub(Duration32::from_seconds(EXTRA_TIME_TO_MINE_A_BLOCK))
336 .expect("a valid block time minus a small constant is in-range");
337
338 if result.cur_time <= local_std_difficulty_limit {
339 result.max_time = std_difficulty_max_time.clamp(result.min_time, result.max_time);
344
345 result.cur_time = result.cur_time.clamp(result.min_time, result.max_time);
348 } else {
349 result.min_time = min_difficulty_min_time.clamp(result.min_time, result.max_time);
354
355 result.cur_time = result.cur_time.clamp(result.min_time, result.max_time);
357
358 result.expected_difficulty = AdjustedDifficulty::new_from_header_time(
360 result.cur_time.into(),
361 previous_block_height,
362 network,
363 relevant_data.iter().cloned(),
364 )
365 .expected_difficulty_threshold();
366 }
367}