referrerpolicy=no-referrer-when-downgrade

sc_informant/
lib.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
5
6// This program is free software: you can redistribute it and/or modify
7// it under the terms of the GNU General Public License as published by
8// the Free Software Foundation, either version 3 of the License, or
9// (at your option) any later version.
10
11// This program is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14// GNU General Public License for more details.
15
16// You should have received a copy of the GNU General Public License
17// along with this program. If not, see <https://www.gnu.org/licenses/>.
18
19//! Console informant. Prints sync progress and block events. Runs on the calling thread.
20
21use console::style;
22use futures::prelude::*;
23use futures_timer::Delay;
24use log::{debug, info, log_enabled, trace};
25use sc_client_api::{BlockchainEvents, UsageProvider};
26use sc_network::NetworkStatusProvider;
27use sc_network_sync::{SyncStatusProvider, SyncingService};
28use sp_blockchain::HeaderMetadata;
29use sp_runtime::traits::{Block as BlockT, Header};
30use std::{
31	collections::VecDeque,
32	fmt::{Debug, Display},
33	sync::Arc,
34	time::Duration,
35};
36
37mod display;
38
39/// Creates a stream that returns a new value every `duration`.
40fn interval(duration: Duration) -> impl Stream<Item = ()> + Unpin {
41	futures::stream::unfold((), move |_| Delay::new(duration).map(|_| Some(((), ())))).map(drop)
42}
43
44/// Builds the informant and returns a `Future` that drives the informant.
45pub async fn build<B: BlockT, C, N>(client: Arc<C>, network: N, syncing: Arc<SyncingService<B>>)
46where
47	N: NetworkStatusProvider,
48	C: UsageProvider<B> + HeaderMetadata<B> + BlockchainEvents<B>,
49	<C as HeaderMetadata<B>>::Error: Display,
50{
51	let mut display = display::InformantDisplay::new();
52
53	let client_1 = client.clone();
54
55	let display_notifications = interval(Duration::from_millis(5000))
56		.filter_map(|_| async {
57			let net_status = network.status().await;
58			let sync_status = syncing.status().await;
59			let num_connected_peers = syncing.num_connected_peers();
60
61			match (net_status, sync_status) {
62				(Ok(net), Ok(sync)) => Some((net, sync, num_connected_peers)),
63				_ => None,
64			}
65		})
66		.for_each(move |(net_status, sync_status, num_connected_peers)| {
67			let info = client_1.usage_info();
68			if let Some(ref usage) = info.usage {
69				trace!(target: "usage", "Usage statistics: {}", usage);
70			} else {
71				trace!(
72					target: "usage",
73					"Usage statistics not displayed as backend does not provide it",
74				)
75			}
76			display.display(&info, net_status, sync_status, num_connected_peers);
77			future::ready(())
78		});
79
80	futures::select! {
81		() = display_notifications.fuse() => (),
82		() = display_block_import(client).fuse() => (),
83	};
84}
85
86/// Print the full hash when debug logging is enabled.
87struct PrintFullHashOnDebugLogging<'a, H>(&'a H);
88
89impl<H: Debug + Display> Display for PrintFullHashOnDebugLogging<'_, H> {
90	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
91		if log_enabled!(log::Level::Debug) {
92			Debug::fmt(&self.0, f)
93		} else {
94			Display::fmt(&self.0, f)
95		}
96	}
97}
98
99async fn display_block_import<B: BlockT, C>(client: Arc<C>)
100where
101	C: UsageProvider<B> + HeaderMetadata<B> + BlockchainEvents<B>,
102	<C as HeaderMetadata<B>>::Error: Display,
103{
104	let mut last_best = {
105		let info = client.usage_info();
106		Some((info.chain.best_number, info.chain.best_hash))
107	};
108
109	// Hashes of the last blocks we have seen at import.
110	let mut last_blocks = VecDeque::new();
111	let max_blocks_to_track = 100;
112	let mut notifications = client.import_notification_stream();
113
114	while let Some(n) = notifications.next().await {
115		// detect and log reorganizations.
116		if let Some((ref last_num, ref last_hash)) = last_best {
117			if n.header.parent_hash() != last_hash && n.is_new_best {
118				let maybe_ancestor =
119					sp_blockchain::lowest_common_ancestor(&*client, *last_hash, n.hash);
120
121				match maybe_ancestor {
122					Ok(ref ancestor) if ancestor.hash != *last_hash => info!(
123						"โ™ป๏ธ  Reorg on #{},{} to #{},{}, common ancestor #{},{}",
124						style(last_num).red().bold(),
125						PrintFullHashOnDebugLogging(&last_hash),
126						style(n.header.number()).green().bold(),
127						PrintFullHashOnDebugLogging(&n.hash),
128						style(ancestor.number).white().bold(),
129						ancestor.hash,
130					),
131					Ok(_) => {},
132					Err(e) => debug!("Error computing tree route: {}", e),
133				}
134			}
135		}
136
137		if n.is_new_best {
138			last_best = Some((*n.header.number(), n.hash));
139		}
140
141		// If we already printed a message for a given block recently,
142		// we should not print it again.
143		if !last_blocks.contains(&n.hash) {
144			last_blocks.push_back(n.hash);
145
146			if last_blocks.len() > max_blocks_to_track {
147				last_blocks.pop_front();
148			}
149
150			let best_indicator = if n.is_new_best { "๐Ÿ†" } else { "๐Ÿ†•" };
151			info!(
152				target: "substrate",
153				"{best_indicator} Imported #{} ({} โ†’ {})",
154				style(n.header.number()).white().bold(),
155				PrintFullHashOnDebugLogging(n.header.parent_hash()),
156				PrintFullHashOnDebugLogging(&n.hash),
157			);
158		}
159	}
160}