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