Skip to main content

espresso_contract_deployer/
network_config.rs

1//! Fetching hotshot network config
2
3use std::time::Duration;
4
5use alloy::{
6    primitives::U256,
7    transports::http::reqwest::{self, Url},
8};
9use anyhow::{Context, Result};
10use espresso_types::SeqTypes;
11use hotshot_contract_adapter::{
12    field_to_u256,
13    sol_types::{LightClientStateSol, StakeTableStateSol},
14};
15use hotshot_types::{PeerConfig, data::EpochNumber, stake_table::HSStakeTable};
16use tokio::time::sleep;
17use vbs::version::StaticVersion;
18
19/// Returns both genesis light client state and stake table state
20pub async fn light_client_genesis(
21    sequencer_url: &Url,
22    stake_table_capacity: usize,
23) -> anyhow::Result<(LightClientStateSol, StakeTableStateSol)> {
24    let st = fetch_stake_table_from_sequencer(sequencer_url, None)
25        .await
26        .with_context(|| "Failed to initialize stake table")?;
27    light_client_genesis_from_stake_table(&st, stake_table_capacity)
28}
29
30/// Fetch the stake table from a sequencer node given the epoch number
31///
32/// Does not error, runs until the stake table is provided.
33pub async fn fetch_stake_table_from_sequencer(
34    sequencer_url: &Url,
35    epoch: Option<EpochNumber>,
36) -> Result<HSStakeTable<SeqTypes>> {
37    tracing::info!("Initializing stake table from node for epoch {epoch:?}");
38    const NUM_RETRIES: usize = 5;
39
40    for i in 0..NUM_RETRIES {
41        match epoch {
42            Some(epoch) => match surf_disco::Client::<
43                tide_disco::error::ServerError,
44                StaticVersion<0, 1>,
45            >::new(sequencer_url.clone())
46            .get::<Vec<PeerConfig<SeqTypes>>>(&format!("node/stake-table/{}", epoch.u64()))
47            .send()
48            .await
49            {
50                Ok(resp) => return Ok(resp.into()),
51                Err(e) => {
52                    let url = sequencer_url
53                        .join(&format!("node/stake-table/{}", epoch.u64()))
54                        .unwrap();
55                    tracing::warn!(%url, "Failed to fetch the stake table: {e}, num_retries left: {}", NUM_RETRIES - i - 1);
56                    if NUM_RETRIES - i > 1 {
57                        sleep(Duration::from_secs(5)).await;
58                    }
59                },
60            },
61            None => {
62                let url = sequencer_url.join("config/hotshot").unwrap();
63                match reqwest::get(url.clone()).await {
64                    Ok(resp) => {
65                        let value: serde_json::Value = resp.json().await.with_context(|| {
66                            format!("Failed to parse the json object from url {url}")
67                        })?;
68                        let known_nodes_with_stake =
69                            serde_json::from_str::<Vec<PeerConfig<SeqTypes>>>(
70                                &value["config"]["known_nodes_with_stake"].to_string(),
71                            )
72                            .with_context(|| "Failed to parse the stake table")?;
73                        return Ok(known_nodes_with_stake.into());
74                    },
75                    Err(e) => {
76                        tracing::warn!(%url, "Failed to fetch the network config: {e}, num_retries left: {}", NUM_RETRIES - i - 1);
77                        if NUM_RETRIES - i > 1 {
78                            sleep(Duration::from_secs(5)).await;
79                        }
80                    },
81                }
82            },
83        }
84    }
85    Err(anyhow::anyhow!(
86        "Failed to fetch the stake table after {NUM_RETRIES} attempts"
87    ))
88}
89
90#[inline]
91/// derive the genesis light client state and stake table state from initial set of `PeerConfig`
92pub fn light_client_genesis_from_stake_table(
93    st: &HSStakeTable<SeqTypes>,
94    stake_table_capacity: usize,
95) -> anyhow::Result<(LightClientStateSol, StakeTableStateSol)> {
96    let st_state = st.commitment(stake_table_capacity)?;
97    Ok((
98        LightClientStateSol {
99            viewNum: 0,
100            blockHeight: 0,
101            blockCommRoot: U256::from(0u32),
102        },
103        StakeTableStateSol {
104            blsKeyComm: field_to_u256(st_state.bls_key_comm),
105            schnorrKeyComm: field_to_u256(st_state.schnorr_key_comm),
106            amountComm: field_to_u256(st_state.amount_comm),
107            threshold: field_to_u256(st_state.threshold),
108        },
109    ))
110}
111
112/// Get the epoch-related  from the sequencer's `PublicHotShotConfig` struct
113/// return (blocks_per_epoch, epoch_start_block)
114pub async fn fetch_epoch_config_from_sequencer(sequencer_url: &Url) -> anyhow::Result<(u64, u64)> {
115    // Request the configuration until it is successful
116    loop {
117        let url = sequencer_url.join("config/hotshot").unwrap();
118        match reqwest::get(url.clone()).await {
119            Ok(resp) => {
120                let value: serde_json::Value = resp
121                    .json()
122                    .await
123                    .with_context(|| format!("Failed to parse the json object from url {url}"))?;
124                let blocks_per_epoch =
125                    value["config"]["epoch_height"]
126                        .as_u64()
127                        .ok_or(anyhow::anyhow!(
128                            "Failed to parse epoch_height from hotshot config"
129                        ))?;
130                let epoch_start_block =
131                    value["config"]["epoch_start_block"]
132                        .as_u64()
133                        .ok_or(anyhow::anyhow!(
134                            "Failed to parse epoch_start_block from hotshot config"
135                        ))?;
136                break Ok((blocks_per_epoch, epoch_start_block));
137            },
138            Err(e) => {
139                tracing::error!(%url, "Failed to fetch the network config: {e}");
140                sleep(Duration::from_secs(5)).await;
141            },
142        }
143    }
144}