1mod display;
6
7use bitcoin::BlockHash;
8use console::style;
9use futures::prelude::*;
10use futures_timer::Delay;
11use sc_client_api::{AuxStore, BlockchainEvents, ClientInfo, UsageProvider};
12use sp_blockchain::{HeaderBackend, HeaderMetadata};
13use sp_runtime::traits::{Block as BlockT, Header};
14use std::collections::VecDeque;
15use std::fmt::Display;
16use std::ops::Deref;
17use std::sync::Arc;
18use std::time::Duration;
19use subcoin_network::NetworkApi;
20use subcoin_primitives::BackendExt;
21use tracing::{debug, info, trace};
22
23#[derive(Debug)]
25struct ClientInfoExt<Block: BlockT> {
26 info: ClientInfo<Block>,
27 best_bitcoin_hash: BlockHash,
28 finalized_bitcoin_hash: BlockHash,
29}
30
31impl<Block: BlockT> Deref for ClientInfoExt<Block> {
32 type Target = ClientInfo<Block>;
33 fn deref(&self) -> &Self::Target {
34 &self.info
35 }
36}
37
38fn interval(duration: Duration) -> impl Stream<Item = ()> + Unpin {
40 futures::stream::unfold((), move |_| Delay::new(duration).map(|_| Some(((), ())))).map(drop)
41}
42
43pub async fn build<B: BlockT, C>(client: Arc<C>, network_api: Arc<dyn NetworkApi>)
45where
46 C: UsageProvider<B> + HeaderMetadata<B> + BlockchainEvents<B> + HeaderBackend<B> + AuxStore,
47 <C as HeaderMetadata<B>>::Error: Display,
48{
49 let mut display = display::InformantDisplay::new();
50
51 let net_client = client.clone();
52
53 let display_notifications = interval(Duration::from_millis(5000))
54 .filter_map(|_| async { network_api.status().await })
55 .for_each({
56 move |net_status| {
57 let info = net_client.usage_info();
58 if let Some(ref usage) = info.usage {
59 trace!(target: "usage", "Usage statistics: {}", usage);
60 } else {
61 trace!(
62 target: "usage",
63 "Usage statistics not displayed as backend does not provide it",
64 )
65 }
66
67 let best_bitcoin_hash = net_client
68 .bitcoin_block_hash_for(info.chain.best_hash)
69 .expect("Best bitcoin hash must exist; qed");
70
71 let finalized_bitcoin_hash = net_client
72 .bitcoin_block_hash_for(info.chain.finalized_hash)
73 .expect("Finalized bitcoin hash must exist; qed");
74
75 let client_info_ext = ClientInfoExt {
76 info,
77 best_bitcoin_hash,
78 finalized_bitcoin_hash,
79 };
80
81 display.display(client_info_ext, net_status);
82 future::ready(())
83 }
84 });
85
86 futures::select! {
87 () = display_notifications.fuse() => (),
88 () = display_block_import(client, &network_api).fuse() => (),
89 };
90}
91
92async fn display_block_import<B: BlockT, C>(client: Arc<C>, network_api: &Arc<dyn NetworkApi>)
93where
94 C: UsageProvider<B> + HeaderMetadata<B> + BlockchainEvents<B>,
95 <C as HeaderMetadata<B>>::Error: Display,
96{
97 let mut last_best = {
98 let info = client.usage_info();
99 Some((info.chain.best_number, info.chain.best_hash))
100 };
101
102 let mut last_blocks = VecDeque::new();
104 let max_blocks_to_track = 100;
105
106 while let Some(n) = client.import_notification_stream().next().await {
107 if let Some((ref last_num, ref last_hash)) = last_best {
109 if n.header.parent_hash() != last_hash && n.is_new_best {
110 let maybe_ancestor =
111 sp_blockchain::lowest_common_ancestor(&*client, *last_hash, n.hash);
112
113 match maybe_ancestor {
114 Ok(ref ancestor) if ancestor.hash != *last_hash => info!(
115 "♻️ Reorg on #{},{} to #{},{}, common ancestor #{},{}",
116 style(last_num).red().bold(),
117 last_hash,
118 style(n.header.number()).green().bold(),
119 n.hash,
120 style(ancestor.number).white().bold(),
121 ancestor.hash,
122 ),
123 Ok(_) => {}
124 Err(e) => debug!("Error computing tree route: {}", e),
125 }
126 }
127 }
128
129 if network_api.is_major_syncing() {
130 continue;
131 }
132
133 if n.is_new_best {
134 last_best = Some((*n.header.number(), n.hash));
135 }
136
137 if !last_blocks.contains(&n.hash) {
140 last_blocks.push_back(n.hash);
141
142 if last_blocks.len() > max_blocks_to_track {
143 last_blocks.pop_front();
144 }
145
146 let best_indicator = if n.is_new_best { "🏆" } else { "🆕" };
147 info!(
148 target: "subcoin",
149 "{best_indicator} Imported #{} ({} → {})",
150 style(n.header.number()).white().bold(),
151 n.header.parent_hash(),
152 n.hash,
153 );
154 }
155 }
156}