Skip to main content

tempo/
utils.rs

1//! Helper functions for the Tempo node entrypoint.
2
3use eyre::WrapErr as _;
4use std::time::Duration;
5
6/// Force-install the default crypto provider.
7///
8/// This is necessary in case there are more than one available backends enabled in rustls (ring,
9/// aws-lc-rs).
10///
11/// This should be called high in the main fn.
12///
13/// See also:
14///   <https://github.com/snapview/tokio-tungstenite/issues/353#issuecomment-2455100010>
15///   <https://github.com/awslabs/aws-sdk-rust/discussions/1257>
16pub(crate) fn install_crypto_provider() {
17    // https://github.com/snapview/tokio-tungstenite/issues/353
18    rustls::crypto::ring::default_provider()
19        .install_default()
20        .expect("Failed to install default rustls crypto provider");
21}
22
23pub(crate) fn block_on_consensus_public_key(
24    args: &tempo_consensus::Args,
25) -> eyre::Result<Option<commonware_cryptography::ed25519::PublicKey>> {
26    tokio::runtime::Builder::new_current_thread()
27        .enable_time()
28        .build()
29        .wrap_err("failed building runtime for consensus key parsing")?
30        .block_on(args.public_key())
31}
32
33/// Print installed extensions as a footer after root help output.
34/// Skips printing when help is for a subcommand (e.g. `tempo node --help`).
35pub(crate) fn print_extensions_footer() {
36    let is_subcommand_help = std::env::args()
37        .skip(1)
38        .any(|a| !a.starts_with('-') && a != "help");
39    if is_subcommand_help {
40        return;
41    }
42
43    let extensions = match tempo_ext::installed_extensions() {
44        Ok(e) => e,
45        Err(_) => return,
46    };
47    if extensions.is_empty() {
48        return;
49    }
50    let use_color = std::io::IsTerminal::is_terminal(&std::io::stdout());
51    let (b, bu, r) = if use_color {
52        ("\x1b[1m", "\x1b[1m\x1b[4m", "\x1b[0m")
53    } else {
54        ("", "", "")
55    };
56    println!("\n{bu}Extensions:{r}");
57    for (name, desc) in &extensions {
58        if desc.is_empty() {
59            println!("  {b}{name}{r}");
60        } else {
61            println!("  {b}{name:<22}{r} {desc}");
62        }
63    }
64}
65
66/// Fetches bootnodes from the given endpoint for the specified chain ID.
67///
68/// The endpoint must return JSON in the format:
69/// `{ "<chain_id>": ["enode://...", ...] }`
70pub(crate) async fn fetch_bootnodes(
71    endpoint: &str,
72    chain_id: u64,
73) -> eyre::Result<Vec<reth_network_peers::NodeRecord>> {
74    let client = reqwest::Client::builder()
75        .timeout(Duration::from_secs(5))
76        .build()
77        .wrap_err("failed to build HTTP client")?;
78
79    let resp: std::collections::HashMap<String, Vec<String>> = client
80        .get(endpoint)
81        .send()
82        .await
83        .wrap_err("request failed")?
84        .error_for_status()
85        .wrap_err("endpoint returned error status")?
86        .json()
87        .await
88        .wrap_err("failed to parse response as JSON")?;
89
90    Ok(resp
91        .get(&chain_id.to_string())
92        .map(reth_network_peers::parse_nodes)
93        .unwrap_or_default())
94}