1use std::{
4 cmp::min,
5 ops::Add,
6 time::{Duration, Instant},
7};
8
9use chrono::Utc;
10use num_integer::div_ceil;
11
12use tokio::sync::watch;
13use zebra_chain::{
14 block::{Height, HeightDiff},
15 chain_sync_status::ChainSyncStatus,
16 chain_tip::ChainTip,
17 fmt::humantime_seconds,
18 parameters::{Network, NetworkUpgrade, POST_BLOSSOM_POW_TARGET_SPACING},
19};
20use zebra_state::MAX_BLOCK_REORG_HEIGHT;
21
22use crate::components::{health::ChainTipMetrics, sync::SyncStatus};
23
24const LOG_INTERVAL: Duration = Duration::from_secs(60);
26
27const PROGRESS_BAR_INTERVAL: Duration = Duration::from_secs(5);
29
30const MAX_CLOSE_TO_TIP_BLOCKS: HeightDiff = 1;
34
35const MIN_SYNC_WARNING_BLOCKS: HeightDiff = 60;
41
42const SYNC_PERCENT_FRAC_DIGITS: usize = 3;
44
45const MIN_BLOCKS_MINED_AFTER_CHECKPOINT_UPDATE: u32 = 10;
62
63pub async fn show_block_chain_progress(
70 network: Network,
71 latest_chain_tip: impl ChainTip,
72 sync_status: SyncStatus,
73 chain_tip_metrics_sender: watch::Sender<ChainTipMetrics>,
74) -> ! {
75 let min_after_checkpoint_blocks =
80 MAX_BLOCK_REORG_HEIGHT + MIN_BLOCKS_MINED_AFTER_CHECKPOINT_UPDATE;
81 let min_after_checkpoint_blocks: HeightDiff = min_after_checkpoint_blocks.into();
82
83 let after_checkpoint_height = network
87 .checkpoint_list()
88 .max_height()
89 .add(min_after_checkpoint_blocks)
90 .expect("hard-coded checkpoint height is far below Height::MAX");
91
92 let target_block_spacing = NetworkUpgrade::target_spacing_for_height(&network, Height::MAX);
93 let max_block_spacing =
94 NetworkUpgrade::minimum_difficulty_spacing_for_height(&network, Height::MAX);
95
96 let min_state_block_interval = max_block_spacing.unwrap_or(target_block_spacing * 4) * 2;
103
104 let target_block_spacing = humantime_seconds(
106 target_block_spacing
107 .to_std()
108 .expect("constant fits in std::Duration"),
109 );
110 let max_block_spacing = max_block_spacing
111 .map(|duration| {
112 humantime_seconds(duration.to_std().expect("constant fits in std::Duration"))
113 })
114 .unwrap_or_else(|| "None".to_string());
115
116 let mut last_state_change_time = Utc::now();
120 let mut last_state_change_instant = Instant::now();
121
122 let mut last_state_change_height = Height(0);
126
127 let mut last_log_time = Instant::now();
129
130 #[cfg(feature = "progress-bar")]
131 let block_bar = howudoin::new().label("Blocks");
132 let mut is_chain_metrics_chan_closed = false;
133
134 loop {
135 let now = Utc::now();
136 let instant_now = Instant::now();
137
138 let is_syncer_stopped = sync_status.is_close_to_tip();
139
140 if let Some(estimated_height) =
141 latest_chain_tip.estimate_network_chain_tip_height(&network, now)
142 {
143 let current_height = latest_chain_tip
146 .best_tip_height()
147 .expect("unexpected empty state: estimate requires a block height");
148 let network_upgrade = NetworkUpgrade::current(&network, current_height);
149
150 #[cfg(feature = "progress-bar")]
154 if matches!(howudoin::cancelled(), Some(true)) {
155 block_bar.close();
156 } else {
157 block_bar
158 .set_pos(current_height.0)
159 .set_len(u64::from(estimated_height.0));
160 }
161
162 let mut remaining_sync_blocks = estimated_height - current_height;
163
164 if remaining_sync_blocks < 0 {
165 remaining_sync_blocks = 0;
166 }
167
168 metrics::gauge!("sync.estimated_network_tip_height").set(estimated_height.0 as f64);
170 metrics::gauge!("sync.estimated_distance_to_tip").set(remaining_sync_blocks as f64);
171
172 if current_height > last_state_change_height {
176 last_state_change_height = current_height;
177 last_state_change_time = now;
178 last_state_change_instant = instant_now;
179 }
180
181 if !is_chain_metrics_chan_closed {
182 if let Err(err) = chain_tip_metrics_sender.send(ChainTipMetrics::new(
183 last_state_change_instant,
184 Some(remaining_sync_blocks),
185 )) {
186 tracing::warn!(?err, "chain tip metrics channel closed");
187 is_chain_metrics_chan_closed = true
188 };
189 }
190
191 let elapsed_since_log = instant_now.saturating_duration_since(last_log_time);
193 if elapsed_since_log < LOG_INTERVAL {
194 tokio::time::sleep(PROGRESS_BAR_INTERVAL).await;
195 continue;
196 } else {
197 last_log_time = instant_now;
198 }
199
200 let sync_progress = f64::from(current_height.0) / f64::from(estimated_height.0);
204 let sync_percent = format!(
205 "{:.frac$}%",
206 sync_progress * 100.0,
207 frac = SYNC_PERCENT_FRAC_DIGITS,
208 );
209
210 let time_since_last_state_block_chrono =
211 now.signed_duration_since(last_state_change_time);
212 let time_since_last_state_block = humantime_seconds(
213 time_since_last_state_block_chrono
214 .to_std()
215 .unwrap_or_default(),
216 );
217
218 if time_since_last_state_block_chrono > min_state_block_interval {
219 warn!(
223 %sync_percent,
224 ?current_height,
225 ?network_upgrade,
226 %time_since_last_state_block,
227 %target_block_spacing,
228 %max_block_spacing,
229 ?is_syncer_stopped,
230 "chain updates have stalled, \
231 state height has not increased for {} minutes. \
232 Hint: check your network connection, \
233 and your computer clock and time zone",
234 time_since_last_state_block_chrono.num_minutes(),
235 );
236
237 #[cfg(feature = "progress-bar")]
239 block_bar.desc(format!("{network_upgrade}: sync has stalled"));
240 } else if is_syncer_stopped && remaining_sync_blocks > MIN_SYNC_WARNING_BLOCKS {
241 info!(
245 %sync_percent,
246 ?current_height,
247 ?network_upgrade,
248 ?remaining_sync_blocks,
249 ?after_checkpoint_height,
250 %time_since_last_state_block,
251 "initial sync is very slow, or estimated tip is wrong. \
252 Hint: check your network connection, \
253 and your computer clock and time zone",
254 );
255
256 #[cfg(feature = "progress-bar")]
257 block_bar.desc(format!(
258 "{network_upgrade}: sync is very slow, or estimated tip is wrong"
259 ));
260 } else if is_syncer_stopped && current_height <= after_checkpoint_height {
261 let min_minutes_after_checkpoint_update = div_ceil(
264 MIN_BLOCKS_MINED_AFTER_CHECKPOINT_UPDATE * POST_BLOSSOM_POW_TARGET_SPACING,
265 60,
266 );
267
268 warn!(
269 %sync_percent,
270 ?current_height,
271 ?network_upgrade,
272 ?remaining_sync_blocks,
273 ?after_checkpoint_height,
274 %time_since_last_state_block,
275 "initial sync is very slow, and state is below the highest checkpoint. \
276 Hint: check your network connection, \
277 and your computer clock and time zone. \
278 Dev Hint: were the checkpoints updated in the last {} minutes?",
279 min_minutes_after_checkpoint_update,
280 );
281
282 #[cfg(feature = "progress-bar")]
283 block_bar.desc(format!("{network_upgrade}: sync is very slow"));
284 } else if is_syncer_stopped {
285 info!(
288 %sync_percent,
289 ?current_height,
290 ?network_upgrade,
291 ?remaining_sync_blocks,
292 %time_since_last_state_block,
293 "finished initial sync to chain tip, using gossiped blocks",
294 );
295
296 #[cfg(feature = "progress-bar")]
297 block_bar.desc(format!("{network_upgrade}: waiting for next block"));
298 } else if remaining_sync_blocks <= MAX_CLOSE_TO_TIP_BLOCKS {
299 info!(
302 %sync_percent,
303 ?current_height,
304 ?network_upgrade,
305 ?remaining_sync_blocks,
306 %time_since_last_state_block,
307 "close to finishing initial sync, \
308 confirming using syncer and gossiped blocks",
309 );
310
311 #[cfg(feature = "progress-bar")]
312 block_bar.desc(format!("{network_upgrade}: finishing initial sync"));
313 } else {
314 info!(
316 %sync_percent,
317 ?current_height,
318 ?network_upgrade,
319 ?remaining_sync_blocks,
320 %time_since_last_state_block,
321 "estimated progress to chain tip",
322 );
323
324 #[cfg(feature = "progress-bar")]
325 block_bar.desc(format!("{network_upgrade}: syncing blocks"));
326 }
327 } else {
328 let sync_percent = format!("{:.SYNC_PERCENT_FRAC_DIGITS$} %", 0.0f64,);
329 #[cfg(feature = "progress-bar")]
330 let network_upgrade = NetworkUpgrade::Genesis;
331
332 if is_syncer_stopped {
333 warn!(
336 %sync_percent,
337 current_height = %"None",
338 "initial sync can't download and verify the genesis block. \
339 Hint: check your network connection, \
340 and your computer clock and time zone",
341 );
342
343 #[cfg(feature = "progress-bar")]
344 block_bar.desc(format!("{network_upgrade}: can't download genesis block"));
345 } else {
346 info!(
349 %sync_percent,
350 current_height = %"None",
351 "initial sync is waiting to download the genesis block",
352 );
353
354 #[cfg(feature = "progress-bar")]
355 block_bar.desc(format!(
356 "{network_upgrade}: waiting to download genesis block"
357 ));
358 }
359 }
360
361 tokio::time::sleep(min(LOG_INTERVAL, PROGRESS_BAR_INTERVAL)).await;
362 }
363}