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
39    loop {
40        match epoch {
41            Some(epoch) => match surf_disco::Client::<
42                tide_disco::error::ServerError,
43                StaticVersion<0, 1>,
44            >::new(sequencer_url.clone())
45            .get::<Vec<PeerConfig<SeqTypes>>>(&format!("node/stake-table/{}", epoch.u64()))
46            .send()
47            .await
48            {
49                Ok(resp) => break Ok(resp.into()),
50                Err(e) => {
51                    let url = sequencer_url
52                        .join(&format!("node/stake-table/{}", epoch.u64()))
53                        .unwrap();
54                    tracing::error!(%url, "Failed to fetch the stake table: {e}");
55                    sleep(Duration::from_secs(5)).await;
56                },
57            },
58            None => {
59                let url = sequencer_url.join("config/hotshot").unwrap();
60                match reqwest::get(url.clone()).await {
61                    Ok(resp) => {
62                        let value: serde_json::Value = resp.json().await.with_context(|| {
63                            format!("Failed to parse the json object from url {url}")
64                        })?;
65                        let known_nodes_with_stake =
66                            serde_json::from_str::<Vec<PeerConfig<SeqTypes>>>(
67                                &value["config"]["known_nodes_with_stake"].to_string(),
68                            )
69                            .with_context(|| "Failed to parse the stake table")?;
70                        break Ok(known_nodes_with_stake.into());
71                    },
72                    Err(e) => {
73                        tracing::error!(%url, "Failed to fetch the network config: {e}");
74                        sleep(Duration::from_secs(5)).await;
75                    },
76                }
77            },
78        }
79    }
80}
81
82#[inline]
83/// derive the genesis light client state and stake table state from initial set of `PeerConfig`
84pub fn light_client_genesis_from_stake_table(
85    st: &HSStakeTable<SeqTypes>,
86    stake_table_capacity: usize,
87) -> anyhow::Result<(LightClientStateSol, StakeTableStateSol)> {
88    let st_state = st.commitment(stake_table_capacity)?;
89    Ok((
90        LightClientStateSol {
91            viewNum: 0,
92            blockHeight: 0,
93            blockCommRoot: U256::from(0u32),
94        },
95        StakeTableStateSol {
96            blsKeyComm: field_to_u256(st_state.bls_key_comm),
97            schnorrKeyComm: field_to_u256(st_state.schnorr_key_comm),
98            amountComm: field_to_u256(st_state.amount_comm),
99            threshold: field_to_u256(st_state.threshold),
100        },
101    ))
102}
103
104/// Get the epoch-related  from the sequencer's `PublicHotShotConfig` struct
105/// return (blocks_per_epoch, epoch_start_block)
106pub async fn fetch_epoch_config_from_sequencer(sequencer_url: &Url) -> anyhow::Result<(u64, u64)> {
107    // Request the configuration until it is successful
108    loop {
109        let url = sequencer_url.join("config/hotshot").unwrap();
110        match reqwest::get(url.clone()).await {
111            Ok(resp) => {
112                let value: serde_json::Value = resp
113                    .json()
114                    .await
115                    .with_context(|| format!("Failed to parse the json object from url {url}"))?;
116                let blocks_per_epoch =
117                    value["config"]["epoch_height"]
118                        .as_u64()
119                        .ok_or(anyhow::anyhow!(
120                            "Failed to parse epoch_height from hotshot config"
121                        ))?;
122                let epoch_start_block =
123                    value["config"]["epoch_start_block"]
124                        .as_u64()
125                        .ok_or(anyhow::anyhow!(
126                            "Failed to parse epoch_start_block from hotshot config"
127                        ))?;
128                break Ok((blocks_per_epoch, epoch_start_block));
129            },
130            Err(e) => {
131                tracing::error!(%url, "Failed to fetch the network config: {e}");
132                sleep(Duration::from_secs(5)).await;
133            },
134        }
135    }
136}