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