/__w/smoldot/smoldot/repo/full-node/bin/main.rs
Line | Count | Source (jump to first uncovered line) |
1 | | // Smoldot |
2 | | // Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. |
3 | | // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 |
4 | | |
5 | | // This program is free software: you can redistribute it and/or modify |
6 | | // it under the terms of the GNU General Public License as published by |
7 | | // the Free Software Foundation, either version 3 of the License, or |
8 | | // (at your option) any later version. |
9 | | |
10 | | // This program is distributed in the hope that it will be useful, |
11 | | // but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
13 | | // GNU General Public License for more details. |
14 | | |
15 | | // You should have received a copy of the GNU General Public License |
16 | | // along with this program. If not, see <http://www.gnu.org/licenses/>. |
17 | | |
18 | | #![deny(rustdoc::broken_intra_doc_links)] |
19 | | // TODO: #![deny(unused_crate_dependencies)] doesn't work because some deps are used only by the library, figure if this can be fixed? |
20 | | |
21 | | use std::{ |
22 | | fs, io, |
23 | | sync::Arc, |
24 | | thread, |
25 | | time::{Duration, SystemTime, UNIX_EPOCH}, |
26 | | }; |
27 | | |
28 | | mod cli; |
29 | | |
30 | 0 | fn main() { |
31 | 0 | smol::block_on(async_main()) |
32 | 0 | } |
33 | | |
34 | 0 | async fn async_main() { |
35 | 0 | match <cli::CliOptions as clap::Parser>::parse().command { |
36 | 0 | cli::CliOptionsCommand::Run(r) => run(*r).await, |
37 | 0 | cli::CliOptionsCommand::Blake264BitsHash(opt) => { |
38 | 0 | let hash = blake2_rfc::blake2b::blake2b(8, &[], opt.payload.as_bytes()); |
39 | 0 | println!("0x{}", hex::encode(hash)); |
40 | 0 | } |
41 | 0 | cli::CliOptionsCommand::Blake2256BitsHash(opt) => { |
42 | 0 | let content = fs::read(opt.file).expect("Failed to read file content"); |
43 | 0 | let hash = blake2_rfc::blake2b::blake2b(32, &[], &content); |
44 | 0 | println!("0x{}", hex::encode(hash)); |
45 | 0 | } |
46 | | } |
47 | 0 | } |
48 | | |
49 | 0 | async fn run(cli_options: cli::CliOptionsRun) { |
50 | | // Determine the actual CLI output by replacing `Auto` with the actual value. |
51 | 0 | let cli_output = if let cli::Output::Auto = cli_options.output { |
52 | 0 | if io::IsTerminal::is_terminal(&io::stderr()) && cli_options.log_level.is_none() { |
53 | 0 | cli::Output::Informant |
54 | | } else { |
55 | 0 | cli::Output::Logs |
56 | | } |
57 | | } else { |
58 | 0 | cli_options.output |
59 | | }; |
60 | 0 | debug_assert!(!matches!(cli_output, cli::Output::Auto)); |
61 | | |
62 | | // Setup the logging system of the binary. |
63 | 0 | let log_callback: Arc<dyn smoldot_full_node::LogCallback + Send + Sync> = match cli_output { |
64 | 0 | cli::Output::None => Arc::new(|_level, _message| {}), |
65 | | cli::Output::Informant | cli::Output::Logs => { |
66 | 0 | let color_choice = cli_options.color.clone(); |
67 | 0 | let log_level = cli_options.log_level.clone().unwrap_or( |
68 | 0 | if matches!(cli_output, cli::Output::Informant) { |
69 | 0 | cli::LogLevel::Info |
70 | | } else { |
71 | 0 | cli::LogLevel::Debug |
72 | | }, |
73 | | ); |
74 | | |
75 | 0 | Arc::new(move |level, message| { |
76 | 0 | match (&level, &log_level) { |
77 | 0 | (_, cli::LogLevel::Off) => return, |
78 | | ( |
79 | | smoldot_full_node::LogLevel::Warn |
80 | | | smoldot_full_node::LogLevel::Info |
81 | | | smoldot_full_node::LogLevel::Debug |
82 | | | smoldot_full_node::LogLevel::Trace, |
83 | | cli::LogLevel::Error, |
84 | 0 | ) => return, |
85 | | ( |
86 | | smoldot_full_node::LogLevel::Info |
87 | | | smoldot_full_node::LogLevel::Debug |
88 | | | smoldot_full_node::LogLevel::Trace, |
89 | | cli::LogLevel::Warn, |
90 | 0 | ) => return, |
91 | | ( |
92 | | smoldot_full_node::LogLevel::Debug | smoldot_full_node::LogLevel::Trace, |
93 | | cli::LogLevel::Info, |
94 | 0 | ) => return, |
95 | 0 | (smoldot_full_node::LogLevel::Trace, cli::LogLevel::Debug) => return, |
96 | 0 | _ => {} |
97 | 0 | } |
98 | 0 |
|
99 | 0 | let when = humantime::format_rfc3339_millis(SystemTime::now()); |
100 | | |
101 | 0 | let level_str = match (level, &color_choice) { |
102 | 0 | (smoldot_full_node::LogLevel::Trace, cli::ColorChoice::Never) => "trace", |
103 | | (smoldot_full_node::LogLevel::Trace, cli::ColorChoice::Always) => { |
104 | 0 | "\x1b[36mtrace\x1b[0m" |
105 | | } |
106 | 0 | (smoldot_full_node::LogLevel::Debug, cli::ColorChoice::Never) => "debug", |
107 | | (smoldot_full_node::LogLevel::Debug, cli::ColorChoice::Always) => { |
108 | 0 | "\x1b[34mdebug\x1b[0m" |
109 | | } |
110 | 0 | (smoldot_full_node::LogLevel::Info, cli::ColorChoice::Never) => "info", |
111 | | (smoldot_full_node::LogLevel::Info, cli::ColorChoice::Always) => { |
112 | 0 | "\x1b[32minfo\x1b[0m" |
113 | | } |
114 | 0 | (smoldot_full_node::LogLevel::Warn, cli::ColorChoice::Never) => "warn", |
115 | | (smoldot_full_node::LogLevel::Warn, cli::ColorChoice::Always) => { |
116 | 0 | "\x1b[33;1mwarn\x1b[0m" |
117 | | } |
118 | 0 | (smoldot_full_node::LogLevel::Error, cli::ColorChoice::Never) => "error", |
119 | | (smoldot_full_node::LogLevel::Error, cli::ColorChoice::Always) => { |
120 | 0 | "\x1b[31;1merror\x1b[0m" |
121 | | } |
122 | | }; |
123 | | |
124 | 0 | eprintln!("[{}] [{}] {}", when, level_str, message); |
125 | 0 | }) as Arc<dyn smoldot_full_node::LogCallback + Send + Sync> |
126 | | } |
127 | | cli::Output::LogsJson => { |
128 | 0 | let log_level = cli_options |
129 | 0 | .log_level |
130 | 0 | .clone() |
131 | 0 | .unwrap_or(cli::LogLevel::Debug); |
132 | 0 | Arc::new(move |level, message| { |
133 | 0 | match (&level, &log_level) { |
134 | 0 | (_, cli::LogLevel::Off) => return, |
135 | | ( |
136 | | smoldot_full_node::LogLevel::Warn |
137 | | | smoldot_full_node::LogLevel::Info |
138 | | | smoldot_full_node::LogLevel::Debug |
139 | | | smoldot_full_node::LogLevel::Trace, |
140 | | cli::LogLevel::Error, |
141 | 0 | ) => return, |
142 | | ( |
143 | | smoldot_full_node::LogLevel::Info |
144 | | | smoldot_full_node::LogLevel::Debug |
145 | | | smoldot_full_node::LogLevel::Trace, |
146 | | cli::LogLevel::Warn, |
147 | 0 | ) => return, |
148 | | ( |
149 | | smoldot_full_node::LogLevel::Debug | smoldot_full_node::LogLevel::Trace, |
150 | | cli::LogLevel::Info, |
151 | 0 | ) => return, |
152 | 0 | (smoldot_full_node::LogLevel::Trace, cli::LogLevel::Debug) => return, |
153 | 0 | _ => {} |
154 | 0 | } |
155 | 0 |
|
156 | 0 | #[derive(serde::Serialize)] |
157 | 0 | struct Record { |
158 | 0 | timestamp: u128, |
159 | 0 | level: &'static str, |
160 | 0 | message: String, |
161 | 0 | } |
162 | 0 |
|
163 | 0 | let mut lock = std::io::stderr().lock(); |
164 | 0 | if serde_json::to_writer( |
165 | 0 | &mut lock, |
166 | 0 | &Record { |
167 | 0 | timestamp: SystemTime::now() |
168 | 0 | .duration_since(UNIX_EPOCH) |
169 | 0 | .map(|d| d.as_millis()) |
170 | 0 | .unwrap_or(0), |
171 | 0 | level: match level { |
172 | 0 | smoldot_full_node::LogLevel::Trace => "trace", |
173 | 0 | smoldot_full_node::LogLevel::Debug => "debug", |
174 | 0 | smoldot_full_node::LogLevel::Info => "info", |
175 | 0 | smoldot_full_node::LogLevel::Warn => "warn", |
176 | 0 | smoldot_full_node::LogLevel::Error => "error", |
177 | | }, |
178 | 0 | message, |
179 | 0 | }, |
180 | 0 | ) |
181 | 0 | .is_ok() |
182 | 0 | { |
183 | 0 | let _ = io::Write::write_all(&mut lock, b"\n"); |
184 | 0 | } |
185 | 0 | }) |
186 | | } |
187 | 0 | cli::Output::Auto => unreachable!(), // Handled above. |
188 | | }; |
189 | | |
190 | 0 | let chain_spec = |
191 | 0 | fs::read(&cli_options.path_to_chain_spec).expect("Failed to read chain specification"); |
192 | 0 | let parsed_chain_spec = { |
193 | 0 | smoldot::chain_spec::ChainSpec::from_json_bytes(&chain_spec) |
194 | 0 | .expect("Failed to decode chain specification") |
195 | | }; |
196 | | |
197 | | // Directory where we will store everything on the disk, such as the database, secret keys, |
198 | | // etc. |
199 | 0 | let base_storage_directory = if cli_options.tmp { |
200 | 0 | None |
201 | 0 | } else if let Some(base) = directories::ProjectDirs::from("io", "smoldot", "smoldot") { |
202 | 0 | Some(base.data_dir().to_owned()) |
203 | | } else { |
204 | 0 | log_callback.log( |
205 | 0 | smoldot_full_node::LogLevel::Warn, |
206 | 0 | "Failed to fetch $HOME directory. Falling back to storing everything in memory, \ |
207 | 0 | meaning that everything will be lost when the node stops. If this is intended, \ |
208 | 0 | please make this explicit by passing the `--tmp` flag instead." |
209 | 0 | .to_string(), |
210 | 0 | ); |
211 | 0 | None |
212 | | }; |
213 | | |
214 | | // Create the directory if necessary. |
215 | 0 | if let Some(base_storage_directory) = base_storage_directory.as_ref() { |
216 | 0 | fs::create_dir_all(base_storage_directory.join(parsed_chain_spec.id())).unwrap(); |
217 | 0 | } |
218 | | // Directory supposed to contain the database. |
219 | 0 | let sqlite_database_path = base_storage_directory |
220 | 0 | .as_ref() |
221 | 0 | .map(|d| d.join(parsed_chain_spec.id()).join("database")); |
222 | 0 | // Directory supposed to contain the keystore. |
223 | 0 | let keystore_path = base_storage_directory |
224 | 0 | .as_ref() |
225 | 0 | .map(|path| path.join(parsed_chain_spec.id()).join("keys")); |
226 | | |
227 | | // Build the relay chain information if relevant. |
228 | 0 | let (relay_chain, relay_chain_name) = |
229 | 0 | if let Some((relay_chain_name, _parachain_id)) = parsed_chain_spec.relay_chain() { |
230 | 0 | let spec_json = { |
231 | 0 | let relay_chain_path = cli_options |
232 | 0 | .path_to_chain_spec |
233 | 0 | .parent() |
234 | 0 | .unwrap() |
235 | 0 | .join(format!("{relay_chain_name}.json")); |
236 | 0 | fs::read(&relay_chain_path).expect("Failed to read relay chain specification") |
237 | 0 | }; |
238 | 0 |
|
239 | 0 | let parsed_relay_spec = smoldot::chain_spec::ChainSpec::from_json_bytes(&spec_json) |
240 | 0 | .expect("Failed to decode relay chain chain specs"); |
241 | 0 |
|
242 | 0 | // Make sure we're not accidentally opening the same chain twice, otherwise weird |
243 | 0 | // interactions will happen. |
244 | 0 | assert_ne!(parsed_relay_spec.id(), parsed_chain_spec.id()); |
245 | | |
246 | | // Create the directory if necessary. |
247 | 0 | if let Some(base_storage_directory) = base_storage_directory.as_ref() { |
248 | 0 | fs::create_dir_all(base_storage_directory.join(parsed_relay_spec.id())).unwrap(); |
249 | 0 | } |
250 | | |
251 | 0 | let cfg = smoldot_full_node::ChainConfig { |
252 | 0 | chain_spec: spec_json.into(), |
253 | 0 | additional_bootnodes: Vec::new(), |
254 | 0 | keystore_memory: Vec::new(), |
255 | 0 | sqlite_database_path: base_storage_directory.as_ref().map(|d| { |
256 | 0 | d.join(parsed_relay_spec.id()) |
257 | 0 | .join("database") |
258 | 0 | .join("database.sqlite") |
259 | 0 | }), |
260 | 0 | sqlite_cache_size: cli_options.relay_chain_database_cache_size.0, |
261 | 0 | keystore_path: base_storage_directory |
262 | 0 | .as_ref() |
263 | 0 | .map(|path| path.join(parsed_relay_spec.id()).join("keys")), |
264 | 0 | json_rpc_listen: None, |
265 | 0 | }; |
266 | 0 |
|
267 | 0 | (Some(cfg), Some(relay_chain_name.to_owned())) |
268 | | } else { |
269 | 0 | (None, None) |
270 | | }; |
271 | | |
272 | | // Determine which networking key to use. |
273 | | // |
274 | | // This is either passed as a CLI option, loaded from disk, or generated randomly. |
275 | | // TODO: move this code to `/lib/src/identity`? |
276 | 0 | let libp2p_key = if let Some(node_key) = cli_options.libp2p_key { |
277 | 0 | node_key |
278 | 0 | } else if let Some(dir) = base_storage_directory.as_ref() { |
279 | 0 | let path = dir.join("libp2p_ed25519_secret_key.secret"); |
280 | 0 | let libp2p_key = if path.exists() { |
281 | 0 | let file_content = zeroize::Zeroizing::new( |
282 | 0 | fs::read_to_string(&path).expect("failed to read libp2p secret key file content"), |
283 | 0 | ); |
284 | 0 | let mut hex_decoded = Box::new([0u8; 32]); |
285 | 0 | hex::decode_to_slice(file_content, &mut *hex_decoded) |
286 | 0 | .expect("invalid libp2p secret key file content"); |
287 | 0 | hex_decoded |
288 | | } else { |
289 | 0 | let mut actual_key = Box::new([0u8; 32]); |
290 | 0 | rand::Fill::try_fill(&mut *actual_key, &mut rand::thread_rng()).unwrap(); |
291 | 0 | let mut hex_encoded = Box::new([0; 64]); |
292 | 0 | hex::encode_to_slice(*actual_key, &mut *hex_encoded).unwrap(); |
293 | 0 | fs::write(&path, *hex_encoded).expect("failed to write libp2p secret key file"); |
294 | 0 | zeroize::Zeroize::zeroize(&mut *hex_encoded); |
295 | 0 | actual_key |
296 | | }; |
297 | | // On Unix platforms, set the permission as 0o400 (only reading and by owner is permitted). |
298 | | // TODO: do something equivalent on Windows |
299 | | #[cfg(unix)] |
300 | 0 | let _ = fs::set_permissions(&path, std::os::unix::fs::PermissionsExt::from_mode(0o400)); |
301 | 0 | libp2p_key |
302 | | } else { |
303 | 0 | let mut key = Box::new([0u8; 32]); |
304 | 0 | rand::Fill::try_fill(&mut *key, &mut rand::thread_rng()).unwrap(); |
305 | 0 | key |
306 | | }; |
307 | | |
308 | | // Create an executor where tasks are going to be spawned onto. |
309 | 0 | let executor = Arc::new(smol::Executor::new()); |
310 | 0 | for n in 0..thread::available_parallelism() |
311 | 0 | .map(|n| n.get() - 1) |
312 | 0 | .unwrap_or(3) |
313 | | { |
314 | 0 | let executor = executor.clone(); |
315 | 0 |
|
316 | 0 | let spawn_result = thread::Builder::new() |
317 | 0 | .name(format!("tasks-pool-{}", n)) |
318 | 0 | .spawn(move || smol::block_on(executor.run(smol::future::pending::<()>()))); |
319 | | |
320 | | // Ignore a failure to spawn a thread, as we're going to run tasks on the current thread |
321 | | // later down this function. |
322 | 0 | if let Err(err) = spawn_result { |
323 | 0 | log_callback.log( |
324 | 0 | smoldot_full_node::LogLevel::Warn, |
325 | 0 | format!("tasks-pool-thread-spawn-failure; err={err}"), |
326 | 0 | ); |
327 | 0 | } |
328 | | } |
329 | | |
330 | | // Print some general information. |
331 | 0 | log_callback.log( |
332 | 0 | smoldot_full_node::LogLevel::Info, |
333 | 0 | "smoldot full node".to_string(), |
334 | 0 | ); |
335 | 0 | log_callback.log( |
336 | 0 | smoldot_full_node::LogLevel::Info, |
337 | 0 | "Copyright (C) 2019-2022 Parity Technologies (UK) Ltd.".to_string(), |
338 | 0 | ); |
339 | 0 | log_callback.log( |
340 | 0 | smoldot_full_node::LogLevel::Info, |
341 | 0 | "Copyright (C) 2023 Pierre Krieger.".to_string(), |
342 | 0 | ); |
343 | 0 | log_callback.log( |
344 | 0 | smoldot_full_node::LogLevel::Info, |
345 | 0 | "This program comes with ABSOLUTELY NO WARRANTY.".to_string(), |
346 | 0 | ); |
347 | 0 | log_callback.log( |
348 | 0 | smoldot_full_node::LogLevel::Info, |
349 | 0 | "This is free software, and you are welcome to redistribute it under certain conditions." |
350 | 0 | .to_string(), |
351 | 0 | ); |
352 | 0 |
|
353 | 0 | // This warning message should be removed if/when the full node becomes mature. |
354 | 0 | log_callback.log( |
355 | 0 | smoldot_full_node::LogLevel::Warn, |
356 | 0 | "Please note that this full node is experimental. It is not feature complete and is \ |
357 | 0 | known to panic often. Please report any panic you might encounter to \ |
358 | 0 | <https://github.com/smol-dot/smoldot/issues>." |
359 | 0 | .to_string(), |
360 | 0 | ); |
361 | | |
362 | 0 | let client_init_result = smoldot_full_node::start(smoldot_full_node::Config { |
363 | | chain: smoldot_full_node::ChainConfig { |
364 | 0 | chain_spec: chain_spec.into(), |
365 | 0 | additional_bootnodes: cli_options |
366 | 0 | .additional_bootnode |
367 | 0 | .iter() |
368 | 0 | .map(|cli::Bootnode { address, peer_id }| (peer_id.clone(), address.clone())) |
369 | 0 | .collect(), |
370 | 0 | keystore_memory: cli_options.keystore_memory, |
371 | 0 | sqlite_database_path, |
372 | 0 | sqlite_cache_size: cli_options.database_cache_size.0, |
373 | 0 | keystore_path, |
374 | 0 | json_rpc_listen: if let Some(address) = cli_options.json_rpc_address.0 { |
375 | 0 | Some(smoldot_full_node::JsonRpcListenConfig { |
376 | 0 | address, |
377 | 0 | max_json_rpc_clients: cli_options.json_rpc_max_clients, |
378 | 0 | }) |
379 | | } else { |
380 | 0 | None |
381 | | }, |
382 | | }, |
383 | 0 | relay_chain, |
384 | 0 | libp2p_key, |
385 | 0 | listen_addresses: cli_options.listen_addr, |
386 | 0 | tasks_executor: { |
387 | 0 | let executor = executor.clone(); |
388 | 0 | Arc::new(move |task| executor.spawn(task).detach()) |
389 | 0 | }, |
390 | 0 | log_callback: log_callback.clone(), |
391 | 0 | jaeger_agent: cli_options.jaeger, |
392 | | }) |
393 | 0 | .await; |
394 | | |
395 | 0 | let client = match client_init_result { |
396 | 0 | Ok(c) => c, |
397 | 0 | Err(err) => { |
398 | 0 | log_callback.log( |
399 | 0 | smoldot_full_node::LogLevel::Error, |
400 | 0 | format!("Failed to initialize client: {}", err), |
401 | 0 | ); |
402 | 0 | panic!("Failed to initialize client: {}", err); |
403 | | } |
404 | | }; |
405 | | |
406 | 0 | if let Some(addr) = client.json_rpc_server_addr() { |
407 | 0 | log_callback.log( |
408 | 0 | smoldot_full_node::LogLevel::Info, |
409 | 0 | format!( |
410 | 0 | "JSON-RPC server listening on {addr}. Visit \ |
411 | 0 | <https://cloudflare-ipfs.com/ipns/dotapps.io/?rpc=ws%3A%2F%2F{addr}> in order to \ |
412 | 0 | interact with the node." |
413 | 0 | ), |
414 | 0 | ); |
415 | 0 | } |
416 | | |
417 | | // Starting from here, a SIGINT (or equivalent) handler is set up. If the user does Ctrl+C, |
418 | | // an event will be triggered on `ctrlc_detected`. |
419 | | // This should be performed after all the expensive initialization is done, as otherwise these |
420 | | // expensive initializations aren't interrupted by Ctrl+C, which could be frustrating for the |
421 | | // user. |
422 | 0 | let ctrlc_detected = { |
423 | 0 | let event = event_listener::Event::new(); |
424 | 0 | let listen = event.listen(); |
425 | 0 | if let Err(err) = ctrlc::set_handler(move || { |
426 | 0 | event.notify(usize::MAX); |
427 | 0 | }) { |
428 | 0 | // It is not critical to fail to setup the Ctrl-C handler. |
429 | 0 | log_callback.log( |
430 | 0 | smoldot_full_node::LogLevel::Warn, |
431 | 0 | format!("ctrlc-handler-setup-fail; err={err}"), |
432 | 0 | ); |
433 | 0 | } |
434 | 0 | listen |
435 | | }; |
436 | | |
437 | | // Spawn a task that prints the informant at a regular interval. |
438 | | // The interval is fast enough that the informant should be visible roughly at any time, |
439 | | // even if the terminal is filled with logs. |
440 | | // Note that this task also holds the smoldot `client` alive, and thus we spawn it even if |
441 | | // the informant is disabled. |
442 | 0 | let main_task = executor.spawn({ |
443 | 0 | let show_informant = matches!(cli_output, cli::Output::Informant); |
444 | 0 | let informant_colors = match cli_options.color { |
445 | 0 | cli::ColorChoice::Always => true, |
446 | 0 | cli::ColorChoice::Never => false, |
447 | | }; |
448 | | |
449 | 0 | async move { |
450 | 0 | let mut informant_timer = if show_informant { |
451 | 0 | smol::Timer::after(Duration::new(0, 0)) |
452 | | } else { |
453 | 0 | smol::Timer::never() |
454 | | }; |
455 | | |
456 | | loop { |
457 | 0 | informant_timer = |
458 | 0 | smol::Timer::at(informant_timer.await + Duration::from_millis(100)); |
459 | | |
460 | | // We end the informant line with a `\r` so that it overwrites itself |
461 | | // every time. If any other line gets printed, it will overwrite the |
462 | | // informant, and the informant will then print itself below, which is |
463 | | // a fine behaviour. |
464 | 0 | let sync_state = client.sync_state().await; |
465 | 0 | eprint!( |
466 | 0 | "{}\r", |
467 | 0 | smoldot::informant::InformantLine { |
468 | 0 | enable_colors: informant_colors, |
469 | 0 | chain_name: parsed_chain_spec.name(), |
470 | 0 | relay_chain: client.relay_chain_sync_state().await.map( |
471 | 0 | |relay_sync_state| smoldot::informant::RelayChain { |
472 | 0 | chain_name: relay_chain_name.as_ref().unwrap(), |
473 | 0 | best_number: relay_sync_state.best_block_number, |
474 | 0 | } |
475 | 0 | ), |
476 | 0 | max_line_width: terminal_size::terminal_size() |
477 | 0 | .map_or(80, |(w, _)| w.0.into()), |
478 | 0 | num_peers: client.num_peers().await, |
479 | 0 | num_network_connections: client.num_network_connections().await, |
480 | 0 | best_number: sync_state.best_block_number, |
481 | 0 | finalized_number: sync_state.finalized_block_number, |
482 | 0 | best_hash: &sync_state.best_block_hash, |
483 | 0 | finalized_hash: &sync_state.finalized_block_hash, |
484 | 0 | network_known_best: client.network_known_best().await, |
485 | | } |
486 | | ); |
487 | | } |
488 | | } |
489 | | }); |
490 | | |
491 | | // Now run all the tasks that have been spawned. |
492 | 0 | executor.run(ctrlc_detected).await; |
493 | | |
494 | | // Add a new line after the informant so that the user's shell doesn't |
495 | | // overwrite it. |
496 | 0 | if matches!(cli_output, cli::Output::Informant) { |
497 | 0 | eprintln!(); |
498 | 0 | } |
499 | | |
500 | | // After `ctrlc_detected` has triggered, we destroy `main_task`, which cancels it and destroys |
501 | | // the smoldot client. |
502 | 0 | drop::<smol::Task<_>>(main_task); |
503 | 0 |
|
504 | 0 | // TODO: consider running the executor until all tasks shut down gracefully; unfortunately this currently hangs |
505 | 0 | } |