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}