Skip to main content

espresso_types/v0/
config.rs

1use std::{num::NonZeroUsize, time::Duration};
2
3use hotshot_types::{
4    HotShotConfig, PeerConfig, ValidatorConfig, VersionedDaCommittee,
5    addr::NetAddr,
6    network::{BuilderType, NetworkConfig, RandomBuilderConfig},
7    x25519,
8};
9use serde::{Deserialize, Serialize};
10use url::Url;
11use vec1::Vec1;
12
13use crate::{PubKey, SeqTypes};
14
15/// This struct defines the public Hotshot validator configuration.
16/// Private key and state key pairs are excluded for security reasons.
17#[derive(Clone, Debug, Deserialize, Serialize)]
18pub struct PublicValidatorConfig {
19    public_key: PubKey,
20    stake_value: u64,
21    is_da: bool,
22    private_key: String,
23    state_public_key: String,
24    state_key_pair: String,
25    x25519_key: Option<x25519::PublicKey>,
26    p2p_addr: Option<NetAddr>,
27}
28
29impl From<ValidatorConfig<SeqTypes>> for PublicValidatorConfig {
30    fn from(v: ValidatorConfig<SeqTypes>) -> Self {
31        let ValidatorConfig::<SeqTypes> {
32            public_key,
33            private_key: _,
34            stake_value,
35            state_public_key,
36            state_private_key: _,
37            is_da,
38            p2p_addr,
39            x25519_keypair,
40        } = v;
41
42        Self {
43            public_key,
44            stake_value: stake_value.to::<u64>(),
45            is_da,
46            state_public_key: state_public_key.to_string(),
47            private_key: "*****".into(),
48            state_key_pair: "*****".into(),
49            x25519_key: x25519_keypair.map(|x| x.public_key()),
50            p2p_addr,
51        }
52    }
53}
54
55/// This struct defines the public Hotshot configuration parameters.
56/// Our config module features a GET endpoint accessible via the route `/hotshot` to display the hotshot config parameters.
57/// Hotshot config has sensitive information like private keys and such fields are excluded from this struct.
58#[derive(Clone, Debug, Deserialize, Serialize)]
59pub struct PublicHotShotConfig {
60    start_threshold: (u64, u64),
61    num_nodes_with_stake: NonZeroUsize,
62    known_nodes_with_stake: Vec<PeerConfig<SeqTypes>>,
63    known_da_nodes: Vec<PeerConfig<SeqTypes>>,
64    #[serde(default)]
65    da_committees: Vec<VersionedDaCommittee<SeqTypes>>,
66    da_staked_committee_size: usize,
67    fixed_leader_for_gpuvid: usize,
68    next_view_timeout: u64,
69    view_sync_timeout: Duration,
70    num_bootstrap: usize,
71    builder_timeout: Duration,
72    data_request_delay: Duration,
73    builder_urls: Vec1<Url>,
74    start_proposing_view: u64,
75    stop_proposing_view: u64,
76    start_voting_view: u64,
77    stop_voting_view: u64,
78    start_proposing_time: u64,
79    stop_proposing_time: u64,
80    start_voting_time: u64,
81    stop_voting_time: u64,
82    epoch_height: u64,
83    epoch_start_block: u64,
84    #[serde(default = "default_stake_table_capacity")]
85    stake_table_capacity: usize,
86    #[serde(default = "default_drb_difficulty")]
87    drb_difficulty: u64,
88    #[serde(default = "default_drb_upgrade_difficulty")]
89    drb_upgrade_difficulty: u64,
90}
91
92fn default_stake_table_capacity() -> usize {
93    hotshot_types::light_client::DEFAULT_STAKE_TABLE_CAPACITY
94}
95
96/// Default DRB difficulty, set to 0 (intended to be overwritten)
97fn default_drb_difficulty() -> u64 {
98    0
99}
100
101/// Default DRB upgrade difficulty, set to 0 (intended to be overwritten)
102fn default_drb_upgrade_difficulty() -> u64 {
103    0
104}
105
106impl From<HotShotConfig<SeqTypes>> for PublicHotShotConfig {
107    fn from(v: HotShotConfig<SeqTypes>) -> Self {
108        // Destructure all fields from HotShotConfig to return an error
109        // if new fields are added to HotShotConfig. This makes sure that we handle
110        // all fields appropriately and do not miss any updates.
111        let HotShotConfig::<SeqTypes> {
112            start_threshold,
113            num_nodes_with_stake,
114            known_nodes_with_stake,
115            known_da_nodes,
116            da_committees,
117            da_staked_committee_size,
118            fixed_leader_for_gpuvid,
119            next_view_timeout,
120            view_sync_timeout,
121            num_bootstrap,
122            builder_timeout,
123            data_request_delay,
124            builder_urls,
125            start_proposing_view,
126            stop_proposing_view,
127            start_voting_view,
128            stop_voting_view,
129            start_proposing_time,
130            stop_proposing_time,
131            start_voting_time,
132            stop_voting_time,
133            epoch_height,
134            epoch_start_block,
135            stake_table_capacity,
136            drb_difficulty,
137            drb_upgrade_difficulty,
138        } = v;
139
140        Self {
141            start_threshold,
142            num_nodes_with_stake,
143            known_nodes_with_stake,
144            known_da_nodes,
145            da_committees,
146            da_staked_committee_size,
147            fixed_leader_for_gpuvid,
148            next_view_timeout,
149            view_sync_timeout,
150            num_bootstrap,
151            builder_timeout,
152            data_request_delay,
153            builder_urls,
154            start_proposing_view,
155            stop_proposing_view,
156            start_voting_view,
157            stop_voting_view,
158            start_proposing_time,
159            stop_proposing_time,
160            start_voting_time,
161            stop_voting_time,
162            epoch_height,
163            epoch_start_block,
164            stake_table_capacity,
165            drb_difficulty,
166            drb_upgrade_difficulty,
167        }
168    }
169}
170
171impl PublicHotShotConfig {
172    pub fn into_hotshot_config(self) -> HotShotConfig<SeqTypes> {
173        HotShotConfig {
174            start_threshold: self.start_threshold,
175            num_nodes_with_stake: self.num_nodes_with_stake,
176            known_nodes_with_stake: self.known_nodes_with_stake,
177            known_da_nodes: self.known_da_nodes,
178            da_committees: self.da_committees,
179            da_staked_committee_size: self.da_staked_committee_size,
180            fixed_leader_for_gpuvid: self.fixed_leader_for_gpuvid,
181            next_view_timeout: self.next_view_timeout,
182            view_sync_timeout: self.view_sync_timeout,
183            num_bootstrap: self.num_bootstrap,
184            builder_timeout: self.builder_timeout,
185            data_request_delay: self.data_request_delay,
186            builder_urls: self.builder_urls,
187            start_proposing_view: self.start_proposing_view,
188            stop_proposing_view: self.stop_proposing_view,
189            start_voting_view: self.start_voting_view,
190            stop_voting_view: self.stop_voting_view,
191            start_proposing_time: self.start_proposing_time,
192            stop_proposing_time: self.stop_proposing_time,
193            start_voting_time: self.start_voting_time,
194            stop_voting_time: self.stop_voting_time,
195            epoch_height: self.epoch_height,
196            epoch_start_block: self.epoch_start_block,
197            stake_table_capacity: self.stake_table_capacity,
198            drb_difficulty: self.drb_difficulty,
199            drb_upgrade_difficulty: self.drb_upgrade_difficulty,
200        }
201    }
202
203    pub fn known_nodes_with_stake(&self) -> Vec<PeerConfig<SeqTypes>> {
204        self.known_nodes_with_stake.clone()
205    }
206
207    pub fn known_da_nodes(&self) -> Vec<PeerConfig<SeqTypes>> {
208        self.known_da_nodes.clone()
209    }
210
211    pub fn blocks_per_epoch(&self) -> u64 {
212        self.epoch_height
213    }
214
215    pub fn epoch_start_block(&self) -> u64 {
216        self.epoch_start_block
217    }
218}
219
220#[derive(Clone, Debug, Deserialize, Serialize)]
221pub struct PublicNetworkConfig {
222    rounds: usize,
223    indexed_da: bool,
224    transactions_per_round: usize,
225    manual_start_password: Option<String>,
226    num_bootrap: usize,
227    next_view_timeout: u64,
228    view_sync_timeout: Duration,
229    builder_timeout: Duration,
230    data_request_delay: Duration,
231    node_index: u64,
232    seed: [u8; 32],
233    transaction_size: usize,
234    key_type_name: String,
235    config: PublicHotShotConfig,
236    /// Served for peers running a release before this one, which refuse to start without a
237    /// libp2p section in the config they fetch; they never read its bootstrap nodes.
238    #[serde(default, skip_deserializing)]
239    libp2p_config: LegacyLibp2pConfig,
240    commit_sha: String,
241    builder: BuilderType,
242    random_builder: Option<RandomBuilderConfig>,
243}
244
245/// Always `{"bootstrap_nodes": []}` on the wire.
246#[derive(Clone, Debug, Default, Serialize)]
247struct LegacyLibp2pConfig {
248    bootstrap_nodes: Vec<()>,
249}
250
251impl From<NetworkConfig<SeqTypes>> for PublicNetworkConfig {
252    fn from(cfg: NetworkConfig<SeqTypes>) -> Self {
253        Self {
254            rounds: cfg.rounds,
255            indexed_da: cfg.indexed_da,
256            transactions_per_round: cfg.transactions_per_round,
257            manual_start_password: Some("*****".into()),
258            num_bootrap: cfg.num_bootrap,
259            next_view_timeout: cfg.next_view_timeout,
260            view_sync_timeout: cfg.view_sync_timeout,
261            builder_timeout: cfg.builder_timeout,
262            data_request_delay: cfg.data_request_delay,
263            node_index: cfg.node_index,
264            seed: cfg.seed,
265            transaction_size: cfg.transaction_size,
266            key_type_name: cfg.key_type_name,
267            config: cfg.config.into(),
268            libp2p_config: LegacyLibp2pConfig::default(),
269            commit_sha: cfg.commit_sha,
270            builder: cfg.builder,
271            random_builder: cfg.random_builder,
272        }
273    }
274}
275
276impl PublicNetworkConfig {
277    pub fn into_network_config(
278        self,
279        my_own_validator_config: ValidatorConfig<SeqTypes>,
280    ) -> anyhow::Result<NetworkConfig<SeqTypes>> {
281        let node_index = self
282            .config
283            .known_nodes_with_stake
284            .iter()
285            .position(|peer| peer.stake_table_entry.stake_key == my_own_validator_config.public_key)
286            .unwrap_or(0) as u64;
287
288        Ok(NetworkConfig {
289            rounds: self.rounds,
290            indexed_da: self.indexed_da,
291            transactions_per_round: self.transactions_per_round,
292            manual_start_password: self.manual_start_password,
293            num_bootrap: self.num_bootrap,
294            next_view_timeout: self.next_view_timeout,
295            view_sync_timeout: self.view_sync_timeout,
296            builder_timeout: self.builder_timeout,
297            data_request_delay: self.data_request_delay,
298            node_index,
299            seed: self.seed,
300            transaction_size: self.transaction_size,
301            key_type_name: self.key_type_name,
302            config: self.config.into_hotshot_config(),
303            commit_sha: self.commit_sha,
304            builder: self.builder,
305            random_builder: self.random_builder,
306            public_keys: Vec::new(),
307        })
308    }
309
310    pub fn hotshot_config(&self) -> PublicHotShotConfig {
311        self.config.clone()
312    }
313
314    // Read-only accessors rather than public fields: `manual_start_password` is masked when a
315    // `PublicNetworkConfig` is built, and a public constructor would let a real one through.
316
317    pub fn commit_sha(&self) -> &str {
318        &self.commit_sha
319    }
320
321    pub fn indexed_da(&self) -> bool {
322        self.indexed_da
323    }
324
325    pub fn builder(&self) -> BuilderType {
326        self.builder
327    }
328}
329
330#[cfg(test)]
331mod tests {
332    use super::{NetworkConfig, PublicNetworkConfig};
333    use crate::SeqTypes;
334
335    /// `/v2/config/hotshot` renders this struct from the api crate, where these fields are
336    /// private and cannot be destructured. Adding one below fails to compile until it is either
337    /// served there or listed among the drops in `proto/v2/config.proto`.
338    #[test]
339    fn every_field_is_accounted_for_by_the_v2_config_api() {
340        let PublicNetworkConfig {
341            // Served.
342            config: _,
343            commit_sha: _,
344            indexed_da: _,
345            builder: _,
346            // Dropped: orchestrator run parameters, the masked password, a Rust type name, the
347            // timings and bootstrap count repeated here with stale values, and the empty libp2p
348            // section served only to older peers.
349            libp2p_config: _,
350            rounds: _,
351            transactions_per_round: _,
352            transaction_size: _,
353            node_index: _,
354            seed: _,
355            random_builder: _,
356            manual_start_password: _,
357            key_type_name: _,
358            num_bootrap: _,
359            next_view_timeout: _,
360            view_sync_timeout: _,
361            builder_timeout: _,
362            data_request_delay: _,
363        } = PublicNetworkConfig::from(NetworkConfig::<SeqTypes>::default());
364    }
365
366    /// A node from a release before this one fetches its config from peers and fails to start
367    /// unless the JSON carries a libp2p section, so the key is served with no bootstrap nodes.
368    #[test]
369    fn serves_an_empty_libp2p_section_for_older_peers() {
370        let config = PublicNetworkConfig::from(NetworkConfig::<SeqTypes>::default());
371        let json = serde_json::to_value(&config).unwrap();
372        assert_eq!(
373            json["libp2p_config"],
374            serde_json::json!({ "bootstrap_nodes": [] })
375        );
376        // Incoming values, including the non-empty lists older peers serve, are ignored.
377        let mut with_peers = json.clone();
378        with_peers["libp2p_config"] =
379            serde_json::json!({ "bootstrap_nodes": [["12D3KooW", "/ip4/10.0.0.1/tcp/1769"]] });
380        let parsed: PublicNetworkConfig = serde_json::from_value(with_peers).unwrap();
381        assert!(parsed.libp2p_config.bootstrap_nodes.is_empty());
382    }
383
384    #[test]
385    fn test_deserialize_from_old_config() {
386        // pulled from decaf node
387        let json_str = r#"
388        {
389  "rounds": 100,
390  "indexed_da": false,
391  "transactions_per_round": 10,
392  "manual_start_password": "*****",
393  "num_bootrap": 5,
394  "next_view_timeout": 10,
395  "view_sync_timeout": {
396    "secs": 2,
397    "nanos": 0
398  },
399  "builder_timeout": {
400    "secs": 10,
401    "nanos": 0
402  },
403  "data_request_delay": {
404    "secs": 2,
405    "nanos": 500000000
406  },
407  "node_index": 1,
408  "seed": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
409  "transaction_size": 100,
410  "key_type_name": "jf_signature::bls_over_bn254::VerKey",
411  "libp2p_config": {
412    "bootstrap_nodes": []
413  },
414  "config": {
415    "start_threshold": [100, 100],
416    "num_nodes_with_stake": 100,
417    "known_nodes_with_stake": [],
418    "known_da_nodes": [],
419    "da_staked_committee_size": 100,
420    "fixed_leader_for_gpuvid": 1,
421    "next_view_timeout": 12000,
422    "view_sync_timeout": {
423      "secs": 1,
424      "nanos": 0
425    },
426    "num_bootstrap": 5,
427    "builder_timeout": {
428      "secs": 8,
429      "nanos": 0
430    },
431    "data_request_delay": {
432      "secs": 5,
433      "nanos": 0
434    },
435    "builder_urls": [
436      "https://builder.decaf.testnet.espresso.network/"
437    ],
438    "start_proposing_view": 0,
439    "stop_proposing_view": 0,
440    "start_voting_view": 0,
441    "stop_voting_view": 0,
442    "start_proposing_time": 0,
443    "stop_proposing_time": 0,
444    "start_voting_time": 0,
445    "stop_voting_time": 0,
446    "epoch_height": 3000,
447    "epoch_start_block": 3160636
448  },
449  "cdn_marshal_address": null,
450  "combined_network_config": null,
451  "commit_sha": "",
452  "builder": "Simple",
453  "random_builder": null
454}
455        "#;
456        let _public_config: PublicNetworkConfig = serde_json::from_str(json_str).unwrap();
457    }
458}