1use std::{
4 cmp::min,
5 ops::Add,
6 time::{Duration, Instant},
7};
8
9use chrono::Utc;
10use num_integer::div_ceil;
11
12use zebra_chain::{
13 block::{Height, HeightDiff},
14 chain_sync_status::ChainSyncStatus,
15 chain_tip::ChainTip,
16 fmt::humantime_seconds,
17 parameters::{Network, NetworkUpgrade, POST_BLOSSOM_POW_TARGET_SPACING},
18};
19use zebra_state::MAX_BLOCK_REORG_HEIGHT;
20
21use crate::components::sync::SyncStatus;
22
23const LOG_INTERVAL: Duration = Duration::from_secs(60);
25
26const PROGRESS_BAR_INTERVAL: Duration = Duration::from_secs(5);
28
29const MAX_CLOSE_TO_TIP_BLOCKS: HeightDiff = 1;
33
34const MIN_SYNC_WARNING_BLOCKS: HeightDiff = 60;
40
41const SYNC_PERCENT_FRAC_DIGITS: usize = 3;
43
44const MIN_BLOCKS_MINED_AFTER_CHECKPOINT_UPDATE: u32 = 10;
61
62pub async fn show_block_chain_progress(
69 network: Network,
70 latest_chain_tip: impl ChainTip,
71 sync_status: SyncStatus,
72) -> ! {
73 let min_after_checkpoint_blocks =
78 MAX_BLOCK_REORG_HEIGHT + MIN_BLOCKS_MINED_AFTER_CHECKPOINT_UPDATE;
79 let min_after_checkpoint_blocks: HeightDiff = min_after_checkpoint_blocks.into();
80
81 let after_checkpoint_height = network
85 .checkpoint_list()
86 .max_height()
87 .add(min_after_checkpoint_blocks)
88 .expect("hard-coded checkpoint height is far below Height::MAX");
89
90 let target_block_spacing = NetworkUpgrade::target_spacing_for_height(&network, Height::MAX);
91 let max_block_spacing =
92 NetworkUpgrade::minimum_difficulty_spacing_for_height(&network, Height::MAX);
93
94 let min_state_block_interval = max_block_spacing.unwrap_or(target_block_spacing * 4) * 2;
101
102 let target_block_spacing = humantime_seconds(
104 target_block_spacing
105 .to_std()
106 .expect("constant fits in std::Duration"),
107 );
108 let max_block_spacing = max_block_spacing
109 .map(|duration| {
110 humantime_seconds(duration.to_std().expect("constant fits in std::Duration"))
111 })
112 .unwrap_or_else(|| "None".to_string());
113
114 let mut last_state_change_time = Utc::now();
118
119 let mut last_state_change_height = Height(0);
123
124 let mut last_log_time = Instant::now();
126
127 #[cfg(feature = "progress-bar")]
128 let block_bar = howudoin::new().label("Blocks");
129
130 loop {
131 let now = Utc::now();
132 let instant_now = Instant::now();
133
134 let is_syncer_stopped = sync_status.is_close_to_tip();
135
136 if let Some(estimated_height) =
137 latest_chain_tip.estimate_network_chain_tip_height(&network, now)
138 {
139 let current_height = latest_chain_tip
142 .best_tip_height()
143 .expect("unexpected empty state: estimate requires a block height");
144 let network_upgrade = NetworkUpgrade::current(&network, current_height);
145
146 #[cfg(feature = "progress-bar")]
150 if matches!(howudoin::cancelled(), Some(true)) {
151 block_bar.close();
152 } else {
153 block_bar
154 .set_pos(current_height.0)
155 .set_len(u64::from(estimated_height.0));
156 }
157
158 let elapsed_since_log = instant_now.saturating_duration_since(last_log_time);
160 if elapsed_since_log < LOG_INTERVAL {
161 tokio::time::sleep(PROGRESS_BAR_INTERVAL).await;
162 continue;
163 } else {
164 last_log_time = instant_now;
165 }
166
167 let sync_progress = f64::from(current_height.0) / f64::from(estimated_height.0);
171 let sync_percent = format!(
172 "{:.frac$}%",
173 sync_progress * 100.0,
174 frac = SYNC_PERCENT_FRAC_DIGITS,
175 );
176
177 let mut remaining_sync_blocks = estimated_height - current_height;
178 if remaining_sync_blocks < 0 {
179 remaining_sync_blocks = 0;
180 }
181
182 if current_height > last_state_change_height {
186 last_state_change_height = current_height;
187 last_state_change_time = now;
188 }
189
190 let time_since_last_state_block_chrono =
191 now.signed_duration_since(last_state_change_time);
192 let time_since_last_state_block = humantime_seconds(
193 time_since_last_state_block_chrono
194 .to_std()
195 .unwrap_or_default(),
196 );
197
198 if time_since_last_state_block_chrono > min_state_block_interval {
199 warn!(
203 %sync_percent,
204 ?current_height,
205 ?network_upgrade,
206 %time_since_last_state_block,
207 %target_block_spacing,
208 %max_block_spacing,
209 ?is_syncer_stopped,
210 "chain updates have stalled, \
211 state height has not increased for {} minutes. \
212 Hint: check your network connection, \
213 and your computer clock and time zone",
214 time_since_last_state_block_chrono.num_minutes(),
215 );
216
217 #[cfg(feature = "progress-bar")]
219 block_bar.desc(format!("{network_upgrade}: sync has stalled"));
220 } else if is_syncer_stopped && remaining_sync_blocks > MIN_SYNC_WARNING_BLOCKS {
221 info!(
225 %sync_percent,
226 ?current_height,
227 ?network_upgrade,
228 ?remaining_sync_blocks,
229 ?after_checkpoint_height,
230 %time_since_last_state_block,
231 "initial sync is very slow, or estimated tip is wrong. \
232 Hint: check your network connection, \
233 and your computer clock and time zone",
234 );
235
236 #[cfg(feature = "progress-bar")]
237 block_bar.desc(format!(
238 "{network_upgrade}: sync is very slow, or estimated tip is wrong"
239 ));
240 } else if is_syncer_stopped && current_height <= after_checkpoint_height {
241 let min_minutes_after_checkpoint_update = div_ceil(
244 MIN_BLOCKS_MINED_AFTER_CHECKPOINT_UPDATE * POST_BLOSSOM_POW_TARGET_SPACING,
245 60,
246 );
247
248 warn!(
249 %sync_percent,
250 ?current_height,
251 ?network_upgrade,
252 ?remaining_sync_blocks,
253 ?after_checkpoint_height,
254 %time_since_last_state_block,
255 "initial sync is very slow, and state is below the highest checkpoint. \
256 Hint: check your network connection, \
257 and your computer clock and time zone. \
258 Dev Hint: were the checkpoints updated in the last {} minutes?",
259 min_minutes_after_checkpoint_update,
260 );
261
262 #[cfg(feature = "progress-bar")]
263 block_bar.desc(format!("{network_upgrade}: sync is very slow"));
264 } else if is_syncer_stopped {
265 info!(
268 %sync_percent,
269 ?current_height,
270 ?network_upgrade,
271 ?remaining_sync_blocks,
272 %time_since_last_state_block,
273 "finished initial sync to chain tip, using gossiped blocks",
274 );
275
276 #[cfg(feature = "progress-bar")]
277 block_bar.desc(format!("{network_upgrade}: waiting for next block"));
278 } else if remaining_sync_blocks <= MAX_CLOSE_TO_TIP_BLOCKS {
279 info!(
282 %sync_percent,
283 ?current_height,
284 ?network_upgrade,
285 ?remaining_sync_blocks,
286 %time_since_last_state_block,
287 "close to finishing initial sync, \
288 confirming using syncer and gossiped blocks",
289 );
290
291 #[cfg(feature = "progress-bar")]
292 block_bar.desc(format!("{network_upgrade}: finishing initial sync"));
293 } else {
294 info!(
296 %sync_percent,
297 ?current_height,
298 ?network_upgrade,
299 ?remaining_sync_blocks,
300 %time_since_last_state_block,
301 "estimated progress to chain tip",
302 );
303
304 #[cfg(feature = "progress-bar")]
305 block_bar.desc(format!("{network_upgrade}: syncing blocks"));
306 }
307 } else {
308 let sync_percent = format!("{:.SYNC_PERCENT_FRAC_DIGITS$} %", 0.0f64,);
309 #[cfg(feature = "progress-bar")]
310 let network_upgrade = NetworkUpgrade::Genesis;
311
312 if is_syncer_stopped {
313 warn!(
316 %sync_percent,
317 current_height = %"None",
318 "initial sync can't download and verify the genesis block. \
319 Hint: check your network connection, \
320 and your computer clock and time zone",
321 );
322
323 #[cfg(feature = "progress-bar")]
324 block_bar.desc(format!("{network_upgrade}: can't download genesis block"));
325 } else {
326 info!(
329 %sync_percent,
330 current_height = %"None",
331 "initial sync is waiting to download the genesis block",
332 );
333
334 #[cfg(feature = "progress-bar")]
335 block_bar.desc(format!(
336 "{network_upgrade}: waiting to download genesis block"
337 ));
338 }
339 }
340
341 tokio::time::sleep(min(LOG_INTERVAL, PROGRESS_BAR_INTERVAL)).await;
342 }
343}