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