iota_swarm_config/
network_config.rs1use std::path::Path;
6
7use anyhow::{Context, Result, bail, ensure};
8use fastcrypto::encoding::{Base64, Encoding};
9use iota_config::{Config, IOTA_NETWORK_CONFIG, NodeConfig, genesis, node};
10use iota_multiaddr::Multiaddr;
11use iota_sdk_crypto::ToFromBytes as _;
12use iota_types::{committee::CommitteeWithNetworkMetadata, crypto::AccountPrivateKey};
13use serde::{Deserialize, Deserializer, Serialize, Serializer};
14use serde_with::{DeserializeAs, SerializeAs, serde_as};
15
16use crate::{genesis_config::GenesisConfig, node_config_builder::ValidatorConfigBuilder};
17
18struct AccountPrivateKeyBase64;
21
22impl SerializeAs<AccountPrivateKey> for AccountPrivateKeyBase64 {
23 fn serialize_as<S: Serializer>(key: &AccountPrivateKey, s: S) -> Result<S::Ok, S::Error> {
24 Base64::encode(key.to_bytes()).serialize(s)
25 }
26}
27
28impl<'de> DeserializeAs<'de, AccountPrivateKey> for AccountPrivateKeyBase64 {
29 fn deserialize_as<D: Deserializer<'de>>(d: D) -> Result<AccountPrivateKey, D::Error> {
30 let bytes = Base64::decode(&String::deserialize(d)?).map_err(serde::de::Error::custom)?;
31 AccountPrivateKey::from_bytes(&bytes).map_err(serde::de::Error::custom)
32 }
33}
34
35#[serde_as]
38#[derive(Debug, Deserialize, Serialize)]
39pub struct NetworkConfig {
40 pub validator_configs: Vec<NodeConfig>,
41 #[serde_as(as = "Vec<AccountPrivateKeyBase64>")]
42 pub account_keys: Vec<AccountPrivateKey>,
43 pub genesis: genesis::Genesis,
44}
45
46impl Config for NetworkConfig {}
47
48impl NetworkConfig {
49 pub fn validator_configs(&self) -> &[NodeConfig] {
50 &self.validator_configs
51 }
52
53 pub fn net_addresses(&self) -> Vec<Multiaddr> {
54 self.genesis
55 .committee_with_network()
56 .validators()
57 .values()
58 .map(|(_, n)| n.network_address.clone())
59 .collect()
60 }
61
62 pub fn committee_with_network(&self) -> CommitteeWithNetworkMetadata {
63 self.genesis.committee_with_network()
64 }
65
66 pub fn into_validator_configs(self) -> Vec<NodeConfig> {
67 self.validator_configs
68 }
69
70 pub fn get_validator_genesis(&self) -> Option<&node::Genesis> {
73 self.validator_configs
74 .first()
75 .as_ref()
76 .map(|validator| &validator.genesis)
77 }
78}
79
80#[serde_as]
87#[derive(Deserialize, Serialize)]
88pub struct PersistedNetworkConfig {
89 pub version: u32,
92 pub genesis_config: GenesisConfig,
93 #[serde_as(as = "Vec<AccountPrivateKeyBase64>")]
94 pub account_keys: Vec<AccountPrivateKey>,
95}
96
97impl Config for PersistedNetworkConfig {}
98
99#[derive(Deserialize)]
102struct NetworkConfigFormatVersion {
103 #[serde(default)]
104 version: Option<u32>,
105}
106
107impl PersistedNetworkConfig {
108 pub const VERSION: u32 = 1;
110
111 pub fn read(config_directory: &Path) -> Result<Self> {
119 let path = config_directory.join(IOTA_NETWORK_CONFIG);
120 let contents = std::fs::read_to_string(&path)
121 .with_context(|| format!("cannot open the IOTA network config file at {path:?}"))?;
122 let format_version: NetworkConfigFormatVersion = serde_yaml::from_str(&contents)
123 .with_context(|| format!("cannot read the IOTA network config file at {path:?}"))?;
124 if format_version.version != Some(Self::VERSION) {
125 if format_version.version > Some(Self::VERSION) {
128 bail!(
129 "the configuration in {} was created by a newer version of iota-localnet and \
130 cannot be read. Update iota-localnet.",
131 config_directory.display()
132 );
133 }
134 bail!(
135 "the configuration in {} was created by an older version of iota-localnet and \
136 cannot be read. Re-create it with `iota-localnet genesis --force`.",
137 config_directory.display()
138 );
139 }
140 serde_yaml::from_str(&contents)
141 .with_context(|| format!("cannot read the IOTA network config file at {path:?}"))
142 }
143
144 pub fn into_network_config(
153 self,
154 config_directory: &Path,
155 genesis: node::Genesis,
156 ) -> Result<NetworkConfig> {
157 let validators = self
158 .genesis_config
159 .validator_config_info
160 .unwrap_or_default();
161 ensure!(
162 !validators.is_empty(),
163 "the IOTA network config must contain at least one validator"
164 );
165 let validator_configs = validators
166 .into_iter()
167 .map(|validator| {
168 let mut config = ValidatorConfigBuilder::new()
169 .with_config_directory(config_directory.to_path_buf())
170 .build_without_genesis(validator);
171 config.genesis = genesis.clone();
172 config
173 })
174 .collect();
175 Ok(NetworkConfig {
176 validator_configs,
177 account_keys: self.account_keys,
178 genesis: genesis.genesis()?.clone(),
179 })
180 }
181}
182
183#[serde_as]
186#[derive(Debug, Deserialize, Serialize)]
187pub struct NetworkConfigLight {
188 pub validator_configs: Vec<NodeConfig>,
189 #[serde_as(as = "Vec<AccountPrivateKeyBase64>")]
190 pub account_keys: Vec<AccountPrivateKey>,
191 pub committee_with_network: CommitteeWithNetworkMetadata,
192}
193
194impl Config for NetworkConfigLight {}
195
196impl NetworkConfigLight {
197 pub fn new(
198 validator_configs: Vec<NodeConfig>,
199 account_keys: Vec<AccountPrivateKey>,
200 genesis: &genesis::Genesis,
201 ) -> Self {
202 Self {
203 validator_configs,
204 account_keys,
205 committee_with_network: genesis.committee_with_network(),
206 }
207 }
208
209 pub fn validator_configs(&self) -> &[NodeConfig] {
210 &self.validator_configs
211 }
212
213 pub fn net_addresses(&self) -> Vec<Multiaddr> {
214 self.committee_with_network
215 .validators()
216 .values()
217 .map(|(_, n)| n.network_address.clone())
218 .collect()
219 }
220
221 pub fn into_validator_configs(self) -> Vec<NodeConfig> {
222 self.validator_configs
223 }
224
225 pub fn get_validator_genesis(&self) -> Option<&node::Genesis> {
228 self.validator_configs
229 .first()
230 .as_ref()
231 .map(|validator| &validator.genesis)
232 }
233}
234
235#[cfg(test)]
236mod tests {
237 use super::*;
238
239 #[test]
242 fn a_persisted_config_without_validators_is_rejected() {
243 let directory = tempfile::tempdir().unwrap();
244 let persisted = PersistedNetworkConfig {
245 version: PersistedNetworkConfig::VERSION,
246 genesis_config: GenesisConfig::for_local_testing(),
247 account_keys: vec![],
248 };
249
250 let err = persisted
251 .into_network_config(directory.path(), node::Genesis::new_empty())
252 .unwrap_err();
253
254 assert!(err.to_string().contains("at least one validator"), "{err}");
255 }
256}