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},
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 min_minutes_after_checkpoint_update = div_ceil(
94 i64::from(MIN_BLOCKS_MINED_AFTER_CHECKPOINT_UPDATE)
95 * NetworkUpgrade::target_spacing_for_height(
96 &network,
97 network.checkpoint_list().max_height(),
98 )
99 .num_seconds(),
100 60,
101 );
102
103 let mut last_state_change_time = Utc::now();
107 let mut last_state_change_instant = Instant::now();
108
109 let mut last_state_change_height = Height(0);
113
114 let mut last_log_time = Instant::now();
116
117 #[cfg(feature = "progress-bar")]
118 let block_bar = howudoin::new().label("Blocks");
119 let mut is_chain_metrics_chan_closed = false;
120
121 loop {
122 let now = Utc::now();
123 let instant_now = Instant::now();
124
125 let is_syncer_stopped = sync_status.is_close_to_tip();
126
127 if let Some(estimated_height) =
128 latest_chain_tip.estimate_network_chain_tip_height(&network, now)
129 {
130 let current_height = latest_chain_tip
133 .best_tip_height()
134 .expect("unexpected empty state: estimate requires a block height");
135 let network_upgrade = NetworkUpgrade::current(&network, current_height);
136
137 #[cfg(feature = "progress-bar")]
141 if matches!(howudoin::cancelled(), Some(true)) {
142 block_bar.close();
143 } else {
144 block_bar
145 .set_pos(current_height.0)
146 .set_len(u64::from(estimated_height.0));
147 }
148
149 let mut remaining_sync_blocks = estimated_height - current_height;
150
151 if remaining_sync_blocks < 0 {
152 remaining_sync_blocks = 0;
153 }
154
155 metrics::gauge!("sync.estimated_network_tip_height").set(estimated_height.0 as f64);
157 metrics::gauge!("sync.estimated_distance_to_tip").set(remaining_sync_blocks as f64);
158
159 if current_height > last_state_change_height {
163 last_state_change_height = current_height;
164 last_state_change_time = now;
165 last_state_change_instant = instant_now;
166 }
167
168 if !is_chain_metrics_chan_closed {
169 if let Err(err) = chain_tip_metrics_sender.send(ChainTipMetrics::new(
170 last_state_change_instant,
171 Some(remaining_sync_blocks),
172 )) {
173 tracing::warn!(?err, "chain tip metrics channel closed");
174 is_chain_metrics_chan_closed = true
175 };
176 }
177
178 let elapsed_since_log = instant_now.saturating_duration_since(last_log_time);
180 if elapsed_since_log < LOG_INTERVAL {
181 tokio::time::sleep(PROGRESS_BAR_INTERVAL).await;
182 continue;
183 } else {
184 last_log_time = instant_now;
185 }
186
187 let target_block_spacing =
189 NetworkUpgrade::target_spacing_for_height(&network, estimated_height);
190 let max_block_spacing =
191 NetworkUpgrade::minimum_difficulty_spacing_for_height(&network, estimated_height);
192
193 let min_state_block_interval =
200 max_block_spacing.unwrap_or(target_block_spacing * 4) * 2;
201
202 let target_block_spacing = humantime_seconds(
204 target_block_spacing
205 .to_std()
206 .expect("constant fits in std::Duration"),
207 );
208 let max_block_spacing = max_block_spacing
209 .map(|duration| {
210 humantime_seconds(duration.to_std().expect("constant fits in std::Duration"))
211 })
212 .unwrap_or_else(|| "None".to_string());
213
214 let sync_progress = f64::from(current_height.0) / f64::from(estimated_height.0);
218 let sync_percent = format!(
219 "{:.frac$}%",
220 sync_progress * 100.0,
221 frac = SYNC_PERCENT_FRAC_DIGITS,
222 );
223
224 let time_since_last_state_block_chrono =
225 now.signed_duration_since(last_state_change_time);
226 let time_since_last_state_block = humantime_seconds(
227 time_since_last_state_block_chrono
228 .to_std()
229 .unwrap_or_default(),
230 );
231
232 if time_since_last_state_block_chrono > min_state_block_interval {
233 warn!(
237 %sync_percent,
238 ?current_height,
239 ?network_upgrade,
240 %time_since_last_state_block,
241 %target_block_spacing,
242 %max_block_spacing,
243 ?is_syncer_stopped,
244 "chain updates have stalled, \
245 state height has not increased for {} minutes. \
246 Hint: check your network connection, \
247 and your computer clock and time zone",
248 time_since_last_state_block_chrono.num_minutes(),
249 );
250
251 #[cfg(feature = "progress-bar")]
253 block_bar.desc(format!("{network_upgrade}: sync has stalled"));
254 } else if is_syncer_stopped && remaining_sync_blocks > MIN_SYNC_WARNING_BLOCKS {
255 info!(
259 %sync_percent,
260 ?current_height,
261 ?network_upgrade,
262 ?remaining_sync_blocks,
263 ?after_checkpoint_height,
264 %time_since_last_state_block,
265 "initial sync is very slow, or estimated tip is wrong. \
266 Hint: check your network connection, \
267 and your computer clock and time zone",
268 );
269
270 #[cfg(feature = "progress-bar")]
271 block_bar.desc(format!(
272 "{network_upgrade}: sync is very slow, or estimated tip is wrong"
273 ));
274 } else if is_syncer_stopped && current_height <= after_checkpoint_height {
275 warn!(
278 %sync_percent,
279 ?current_height,
280 ?network_upgrade,
281 ?remaining_sync_blocks,
282 ?after_checkpoint_height,
283 %time_since_last_state_block,
284 "initial sync is very slow, and state is below the highest checkpoint. \
285 Hint: check your network connection, \
286 and your computer clock and time zone. \
287 Dev Hint: were the checkpoints updated in the last {} minutes?",
288 min_minutes_after_checkpoint_update,
289 );
290
291 #[cfg(feature = "progress-bar")]
292 block_bar.desc(format!("{network_upgrade}: sync is very slow"));
293 } else if is_syncer_stopped {
294 info!(
297 %sync_percent,
298 ?current_height,
299 ?network_upgrade,
300 ?remaining_sync_blocks,
301 %time_since_last_state_block,
302 "finished initial sync to chain tip, using gossiped blocks",
303 );
304
305 #[cfg(feature = "progress-bar")]
306 block_bar.desc(format!("{network_upgrade}: waiting for next block"));
307 } else if remaining_sync_blocks <= MAX_CLOSE_TO_TIP_BLOCKS {
308 info!(
311 %sync_percent,
312 ?current_height,
313 ?network_upgrade,
314 ?remaining_sync_blocks,
315 %time_since_last_state_block,
316 "close to finishing initial sync, \
317 confirming using syncer and gossiped blocks",
318 );
319
320 #[cfg(feature = "progress-bar")]
321 block_bar.desc(format!("{network_upgrade}: finishing initial sync"));
322 } else {
323 info!(
325 %sync_percent,
326 ?current_height,
327 ?network_upgrade,
328 ?remaining_sync_blocks,
329 %time_since_last_state_block,
330 "estimated progress to chain tip",
331 );
332
333 #[cfg(feature = "progress-bar")]
334 block_bar.desc(format!("{network_upgrade}: syncing blocks"));
335 }
336 } else {
337 let sync_percent = format!("{:.SYNC_PERCENT_FRAC_DIGITS$} %", 0.0f64,);
338 #[cfg(feature = "progress-bar")]
339 let network_upgrade = NetworkUpgrade::Genesis;
340
341 if is_syncer_stopped {
342 warn!(
345 %sync_percent,
346 current_height = %"None",
347 "initial sync can't download and verify the genesis block. \
348 Hint: check your network connection, \
349 and your computer clock and time zone",
350 );
351
352 #[cfg(feature = "progress-bar")]
353 block_bar.desc(format!("{network_upgrade}: can't download genesis block"));
354 } else {
355 info!(
358 %sync_percent,
359 current_height = %"None",
360 "initial sync is waiting to download the genesis block",
361 );
362
363 #[cfg(feature = "progress-bar")]
364 block_bar.desc(format!(
365 "{network_upgrade}: waiting to download genesis block"
366 ));
367 }
368 }
369
370 tokio::time::sleep(min(LOG_INTERVAL, PROGRESS_BAR_INTERVAL)).await;
371 }
372}