Skip to main content

hotshot_types/
network.rs

1// Copyright (c) 2021-2024 Espresso Systems (espressosys.com)
2// This file is part of the HotShot repository.
3
4// You should have received a copy of the MIT License
5// along with the HotShot repository. If not, see <https://mit-license.org/>.
6
7use std::{fs, ops::Range, path::Path, time::Duration, vec};
8
9use alloy_primitives::U256;
10use clap::ValueEnum;
11use serde_inline_default::serde_inline_default;
12use thiserror::Error;
13
14use crate::{
15    HotShotConfig, NodeType, PeerConnectInfo, ValidatorConfig,
16    constants::{
17        ORCHESTRATOR_DEFAULT_NUM_ROUNDS, ORCHESTRATOR_DEFAULT_TRANSACTION_SIZE,
18        ORCHESTRATOR_DEFAULT_TRANSACTIONS_PER_ROUND, REQUEST_DATA_DELAY,
19    },
20    hotshot_config_file::HotShotConfigFile,
21};
22
23/// a network configuration error
24#[derive(Error, Debug)]
25pub enum NetworkConfigError {
26    /// Failed to read NetworkConfig from file
27    #[error("Failed to read NetworkConfig from file")]
28    ReadFromFileError(std::io::Error),
29    /// Failed to deserialize loaded NetworkConfig
30    #[error("Failed to deserialize loaded NetworkConfig")]
31    DeserializeError(serde_json::Error),
32    /// Failed to write NetworkConfig to file
33    #[error("Failed to write NetworkConfig to file")]
34    WriteToFileError(std::io::Error),
35    /// Failed to serialize NetworkConfig
36    #[error("Failed to serialize NetworkConfig")]
37    SerializeError(serde_json::Error),
38    /// Failed to recursively create path to NetworkConfig
39    #[error("Failed to recursively create path to NetworkConfig")]
40    FailedToCreatePath(std::io::Error),
41}
42
43#[derive(Clone, Copy, Debug, serde::Serialize, serde::Deserialize, Default, ValueEnum)]
44/// configuration for builder type to use
45pub enum BuilderType {
46    /// Use external builder, [config.builder_url] must be
47    /// set to correct builder address
48    External,
49    #[default]
50    /// Simple integrated builder will be started and used by each hotshot node
51    Simple,
52    /// Random integrated builder will be started and used by each hotshot node
53    Random,
54}
55
56/// Node PeerConfig keys
57#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq)]
58#[serde(bound(deserialize = ""))]
59pub struct PeerConfigKeys<TYPES: NodeType> {
60    /// The peer's public key
61    pub stake_table_key: TYPES::SignatureKey,
62    /// the peer's state public key
63    pub state_ver_key: TYPES::StateSignatureKey,
64    /// the peer's stake
65    pub stake: u64,
66    /// whether the node is a DA node
67    pub da: bool,
68    pub connect_info: Option<PeerConnectInfo>,
69}
70
71/// Options controlling how the random builder generates blocks
72#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
73pub struct RandomBuilderConfig {
74    /// How many transactions to include in a block
75    pub txn_in_block: u64,
76    /// How many blocks to generate per second
77    pub blocks_per_second: u32,
78    /// Range of how big a transaction can be (in bytes)
79    pub txn_size: Range<u32>,
80}
81
82impl Default for RandomBuilderConfig {
83    fn default() -> Self {
84        Self {
85            txn_in_block: 100,
86            blocks_per_second: 1,
87            txn_size: 20..100,
88        }
89    }
90}
91
92/// a network configuration
93#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
94#[serde(bound(deserialize = ""))]
95pub struct NetworkConfig<TYPES: NodeType> {
96    /// number of views to run
97    pub rounds: usize,
98    /// whether DA membership is determined by index.
99    /// if true, the first k nodes to register form the DA committee
100    /// if false, DA membership is requested by the nodes
101    pub indexed_da: bool,
102    /// number of transactions per view
103    pub transactions_per_round: usize,
104    /// password to have the orchestrator start the network,
105    /// regardless of the number of nodes connected.
106    pub manual_start_password: Option<String>,
107    /// number of bootstrap nodes
108    pub num_bootrap: usize,
109    /// timeout before starting the next view
110    pub next_view_timeout: u64,
111    /// timeout before starting next view sync round
112    pub view_sync_timeout: Duration,
113    /// The maximum amount of time a leader can wait to get a block from a builder
114    pub builder_timeout: Duration,
115    /// time to wait until we request data associated with a proposal
116    pub data_request_delay: Duration,
117    /// global index of node (for testing purposes a uid)
118    pub node_index: u64,
119    /// unique seed (for randomness? TODO)
120    pub seed: [u8; 32],
121    /// size of transactions
122    pub transaction_size: usize,
123    /// name of the key type (for debugging)
124    pub key_type_name: String,
125    /// the hotshot config
126    pub config: HotShotConfig<TYPES>,
127    /// the commit this run is based on
128    pub commit_sha: String,
129    /// builder to use
130    pub builder: BuilderType,
131    /// random builder config
132    pub random_builder: Option<RandomBuilderConfig>,
133    /// The list of public keys that are allowed to connect to the orchestrator
134    pub public_keys: Vec<PeerConfigKeys<TYPES>>,
135}
136
137/// the source of the network config
138pub enum NetworkConfigSource {
139    /// we source the network configuration from the orchestrator
140    Orchestrator,
141    /// we source the network configuration from a config file on disk
142    File,
143}
144
145impl<TYPES: NodeType> NetworkConfig<TYPES> {
146    /// Get a temporary node index for generating a validator config
147    pub fn generate_init_test_validator_config(
148        cur_node_index: u16,
149        is_da: bool,
150    ) -> ValidatorConfig<TYPES> {
151        // This cur_node_index is only used for key pair generation, it's not bound with the node,
152        // later the node with the generated key pair will get a new node_index from orchestrator.
153        ValidatorConfig::generated_from_seed_indexed(
154            [0u8; 32],
155            cur_node_index.into(),
156            U256::from(1),
157            is_da,
158        )
159    }
160
161    /// Loads a `NetworkConfig` from a file.
162    ///
163    /// This function takes a file path as a string, reads the file, and then deserializes the contents into a `NetworkConfig`.
164    ///
165    /// # Arguments
166    ///
167    /// * `file` - A string representing the path to the file from which to load the `NetworkConfig`.
168    ///
169    /// # Returns
170    ///
171    /// This function returns a `Result` that contains a `NetworkConfig` if the file was successfully read and deserialized, or a `NetworkConfigError` if an error occurred.
172    ///
173    /// # Errors
174    ///
175    /// This function will return an error if the file cannot be read or if the contents cannot be deserialized into a `NetworkConfig`.
176    ///
177    /// # Examples
178    ///
179    /// ```ignore
180    /// # use hotshot_orchestrator::config::NetworkConfig;
181    /// // # use hotshot::traits::election::static_committee::StaticElectionConfig;
182    /// let file = "/path/to/my/config".to_string();
183    /// // NOTE: broken due to staticelectionconfig not being importable
184    /// // cannot import staticelectionconfig from hotshot without creating circular dependency
185    /// // making this work probably involves the `types` crate implementing a dummy
186    /// // electionconfigtype just to make this example work
187    /// let config = NetworkConfig::<TYPES, StaticElectionConfig>::from_file(file).unwrap();
188    /// ```
189    pub fn from_file(file: String) -> Result<Self, NetworkConfigError> {
190        // read from file
191        let data = match fs::read(file) {
192            Ok(data) => data,
193            Err(e) => {
194                return Err(NetworkConfigError::ReadFromFileError(e));
195            },
196        };
197
198        // deserialize
199        match serde_json::from_slice(&data) {
200            Ok(data) => Ok(data),
201            Err(e) => Err(NetworkConfigError::DeserializeError(e)),
202        }
203    }
204
205    /// Serializes the `NetworkConfig` and writes it to a file.
206    ///
207    /// This function takes a file path as a string, serializes the `NetworkConfig` into JSON format using `serde_json` and then writes the serialized data to the file.
208    ///
209    /// # Arguments
210    ///
211    /// * `file` - A string representing the path to the file where the `NetworkConfig` should be saved.
212    ///
213    /// # Returns
214    ///
215    /// This function returns a `Result` that contains `()` if the `NetworkConfig` was successfully serialized and written to the file, or a `NetworkConfigError` if an error occurred.
216    ///
217    /// # Errors
218    ///
219    /// This function will return an error if the `NetworkConfig` cannot be serialized or if the file cannot be written.
220    ///
221    /// # Examples
222    ///
223    /// ```ignore
224    /// # use hotshot_orchestrator::config::NetworkConfig;
225    /// let file = "/path/to/my/config".to_string();
226    /// let config = NetworkConfig::from_file(file);
227    /// config.to_file(file).unwrap();
228    /// ```
229    pub fn to_file(&self, file: String) -> Result<(), NetworkConfigError> {
230        // ensure the directory containing the config file exists
231        if let Some(dir) = Path::new(&file).parent()
232            && let Err(e) = fs::create_dir_all(dir)
233        {
234            return Err(NetworkConfigError::FailedToCreatePath(e));
235        }
236
237        // serialize
238        let serialized = match serde_json::to_string_pretty(self) {
239            Ok(data) => data,
240            Err(e) => {
241                return Err(NetworkConfigError::SerializeError(e));
242            },
243        };
244
245        // write to file
246        match fs::write(file, serialized) {
247            Ok(()) => Ok(()),
248            Err(e) => Err(NetworkConfigError::WriteToFileError(e)),
249        }
250    }
251}
252
253impl<TYPES: NodeType> Default for NetworkConfig<TYPES> {
254    fn default() -> Self {
255        Self {
256            rounds: ORCHESTRATOR_DEFAULT_NUM_ROUNDS,
257            indexed_da: true,
258            transactions_per_round: ORCHESTRATOR_DEFAULT_TRANSACTIONS_PER_ROUND,
259            node_index: 0,
260            seed: [0u8; 32],
261            transaction_size: ORCHESTRATOR_DEFAULT_TRANSACTION_SIZE,
262            manual_start_password: None,
263            config: HotShotConfigFile::hotshot_config_5_nodes_10_da().into(),
264            key_type_name: std::any::type_name::<TYPES>().to_string(),
265            next_view_timeout: 10,
266            view_sync_timeout: Duration::from_secs(2),
267            num_bootrap: 5,
268            builder_timeout: Duration::from_secs(10),
269            data_request_delay: Duration::from_millis(2500),
270            commit_sha: String::new(),
271            builder: BuilderType::default(),
272            random_builder: None,
273            public_keys: vec![],
274        }
275    }
276}
277
278/// a network config stored in a file
279#[serde_inline_default]
280#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
281#[serde(bound(deserialize = ""))]
282pub struct PublicKeysFile<TYPES: NodeType> {
283    /// The list of public keys that are allowed to connect to the orchestrator
284    ///
285    /// If nonempty, this list becomes the stake table and is used to determine DA membership (ignoring the node's request).
286    #[serde(default)]
287    pub public_keys: Vec<PeerConfigKeys<TYPES>>,
288}
289
290/// a network config stored in a file
291#[serde_inline_default]
292#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
293#[serde(bound(deserialize = ""))]
294pub struct NetworkConfigFile<TYPES: NodeType> {
295    /// number of views to run
296    #[serde_inline_default(ORCHESTRATOR_DEFAULT_NUM_ROUNDS)]
297    pub rounds: usize,
298    /// number of views to run
299    #[serde(default)]
300    pub indexed_da: bool,
301    /// number of transactions per view
302    #[serde_inline_default(ORCHESTRATOR_DEFAULT_TRANSACTIONS_PER_ROUND)]
303    pub transactions_per_round: usize,
304    /// password to have the orchestrator start the network,
305    /// regardless of the number of nodes connected.
306    #[serde(default)]
307    pub manual_start_password: Option<String>,
308    /// global index of node (for testing purposes a uid)
309    #[serde(default)]
310    pub node_index: u64,
311    /// unique seed (for randomness? TODO)
312    #[serde(default)]
313    pub seed: [u8; 32],
314    /// size of transactions
315    #[serde_inline_default(ORCHESTRATOR_DEFAULT_TRANSACTION_SIZE)]
316    pub transaction_size: usize,
317    /// the hotshot config file
318    #[serde(default = "HotShotConfigFile::hotshot_config_5_nodes_10_da")]
319    pub config: HotShotConfigFile<TYPES>,
320    /// builder to use
321    #[serde(default)]
322    pub builder: BuilderType,
323    /// random builder configuration
324    #[serde(default)]
325    pub random_builder: Option<RandomBuilderConfig>,
326    /// The list of public keys that are allowed to connect to the orchestrator
327    ///
328    /// If nonempty, this list becomes the stake table and is used to determine DA membership (ignoring the node's request).
329    #[serde(default)]
330    pub public_keys: Vec<PeerConfigKeys<TYPES>>,
331}
332
333impl<TYPES: NodeType> From<NetworkConfigFile<TYPES>> for NetworkConfig<TYPES> {
334    fn from(val: NetworkConfigFile<TYPES>) -> Self {
335        NetworkConfig {
336            rounds: val.rounds,
337            indexed_da: val.indexed_da,
338            transactions_per_round: val.transactions_per_round,
339            node_index: 0,
340            num_bootrap: val.config.num_bootstrap,
341            manual_start_password: val.manual_start_password,
342            next_view_timeout: val.config.next_view_timeout,
343            view_sync_timeout: val.config.view_sync_timeout,
344            builder_timeout: val.config.builder_timeout,
345            data_request_delay: val
346                .config
347                .data_request_delay
348                .unwrap_or(Duration::from_millis(REQUEST_DATA_DELAY)),
349            seed: val.seed,
350            transaction_size: val.transaction_size,
351            config: val.config.into(),
352            key_type_name: std::any::type_name::<TYPES>().to_string(),
353            commit_sha: String::new(),
354            builder: val.builder,
355            random_builder: val.random_builder,
356            public_keys: val.public_keys,
357        }
358    }
359}