1use std::{
2 collections::{hash_map::Entry, HashMap},
3 sync::Arc,
4};
5
6use configuration::{GlobalSettings, HrmpChannelConfig, NetworkConfig};
7use futures::future::try_join_all;
8use provider::{DynNamespace, ProviderError, ProviderNamespace};
9use serde::{Deserialize, Serialize};
10use support::{constants::THIS_IS_A_BUG, fs::FileSystem};
11use tracing::{debug, trace};
12
13use crate::{errors::OrchestratorError, ScopedFilesystem};
14
15pub mod node;
16pub mod parachain;
17pub mod relaychain;
18
19use self::{node::NodeSpec, parachain::ParachainSpec, relaychain::RelaychainSpec};
20
21#[derive(Debug, Clone, Serialize, Deserialize)]
22pub struct NetworkSpec {
23 pub(crate) relaychain: RelaychainSpec,
25
26 pub(crate) parachains: Vec<ParachainSpec>,
28
29 pub(crate) hrmp_channels: Vec<HrmpChannelConfig>,
31
32 pub(crate) global_settings: GlobalSettings,
34}
35
36impl NetworkSpec {
37 pub async fn from_config(
38 network_config: &NetworkConfig,
39 ) -> Result<NetworkSpec, OrchestratorError> {
40 let mut errs = vec![];
41 let relaychain = RelaychainSpec::from_config(network_config.relaychain())?;
42 let mut parachains = vec![];
43
44 for para_config in network_config.parachains() {
46 match ParachainSpec::from_config(para_config) {
47 Ok(para) => parachains.push(para),
48 Err(err) => errs.push(err),
49 }
50 }
51
52 if errs.is_empty() {
53 Ok(NetworkSpec {
54 relaychain,
55 parachains,
56 hrmp_channels: network_config
57 .hrmp_channels()
58 .into_iter()
59 .cloned()
60 .collect(),
61 global_settings: network_config.global_settings().clone(),
62 })
63 } else {
64 let errs_str = errs
65 .into_iter()
66 .map(|e| e.to_string())
67 .collect::<Vec<String>>()
68 .join("\n");
69 Err(OrchestratorError::InvalidConfig(errs_str))
70 }
71 }
72
73 pub async fn populate_nodes_available_args(
74 &mut self,
75 ns: Arc<dyn ProviderNamespace + Send + Sync>,
76 ) -> Result<(), OrchestratorError> {
77 let network_nodes = self.collect_network_nodes();
78
79 let mut image_command_to_nodes_mapping =
80 Self::create_image_command_to_nodes_mapping(network_nodes);
81
82 let available_args_outputs =
83 Self::retrieve_all_nodes_available_args_output(ns, &image_command_to_nodes_mapping)
84 .await?;
85
86 Self::update_nodes_available_args_output(
87 &mut image_command_to_nodes_mapping,
88 available_args_outputs,
89 );
90
91 Ok(())
92 }
93
94 pub async fn node_available_args_output(
96 &self,
97 node_spec: &NodeSpec,
98 ns: Arc<dyn ProviderNamespace + Send + Sync>,
99 ) -> Result<String, ProviderError> {
100 let cmp_fn = |ad_hoc: &&NodeSpec| -> bool {
102 ad_hoc.image == node_spec.image && ad_hoc.command == node_spec.command
103 };
104
105 let node = self.relaychain.nodes.iter().find(cmp_fn);
107 let node = if let Some(node) = node {
108 Some(node)
109 } else {
110 let node = self
111 .parachains
112 .iter()
113 .find_map(|para| para.collators.iter().find(cmp_fn));
114
115 node
116 };
117
118 let output = if let Some(node) = node {
119 node.available_args_output.clone().expect(&format!(
120 "args_output should be set for running nodes {THIS_IS_A_BUG}"
121 ))
122 } else {
123 let image = node_spec
125 .image
126 .as_ref()
127 .map(|image| image.as_str().to_string());
128 let command = node_spec.command.as_str().to_string();
129
130 ns.get_node_available_args((command, image)).await?
131 };
132
133 Ok(output)
134 }
135
136 pub fn relaychain(&self) -> &RelaychainSpec {
137 &self.relaychain
138 }
139
140 pub fn relaychain_mut(&mut self) -> &mut RelaychainSpec {
141 &mut self.relaychain
142 }
143
144 pub fn parachains_iter(&self) -> impl Iterator<Item = &ParachainSpec> {
145 self.parachains.iter()
146 }
147
148 pub fn parachains_iter_mut(&mut self) -> impl Iterator<Item = &mut ParachainSpec> {
149 self.parachains.iter_mut()
150 }
151
152 pub fn set_global_settings(&mut self, global_settings: GlobalSettings) {
153 self.global_settings = global_settings;
154 }
155
156 pub async fn build_parachain_artifacts<'a, T: FileSystem>(
157 &mut self,
158 ns: DynNamespace,
159 scoped_fs: &ScopedFilesystem<'a, T>,
160 relaychain_id: &str,
161 base_dir_exists: bool,
162 ) -> Result<(), anyhow::Error> {
163 for para in self.parachains.iter_mut() {
164 let chain_spec_raw_path = para.build_chain_spec(relaychain_id, &ns, scoped_fs).await?;
165
166 trace!("creating dirs for {}", ¶.unique_id);
167 if base_dir_exists {
168 scoped_fs.create_dir_all(¶.unique_id).await?;
169 } else {
170 scoped_fs.create_dir(¶.unique_id).await?;
171 };
172 trace!("created dirs for {}", ¶.unique_id);
173
174 para.genesis_state
176 .build(
177 chain_spec_raw_path.clone(),
178 format!("{}/genesis-state", para.unique_id),
179 &ns,
180 scoped_fs,
181 )
182 .await?;
183 debug!("parachain genesis state built!");
184 para.genesis_wasm
185 .build(
186 chain_spec_raw_path,
187 format!("{}/genesis-wasm", para.unique_id),
188 &ns,
189 scoped_fs,
190 )
191 .await?;
192 debug!("parachain genesis wasm built!");
193 }
194
195 Ok(())
196 }
197
198 fn collect_network_nodes(&mut self) -> Vec<&mut NodeSpec> {
200 vec![
201 self.relaychain.nodes.iter_mut().collect::<Vec<_>>(),
202 self.parachains
203 .iter_mut()
204 .flat_map(|para| para.collators.iter_mut())
205 .collect(),
206 ]
207 .into_iter()
208 .flatten()
209 .collect::<Vec<_>>()
210 }
211
212 fn create_image_command_to_nodes_mapping(
214 network_nodes: Vec<&mut NodeSpec>,
215 ) -> HashMap<(Option<String>, String), Vec<&mut NodeSpec>> {
216 network_nodes.into_iter().fold(
217 HashMap::new(),
218 |mut acc: HashMap<(Option<String>, String), Vec<&mut node::NodeSpec>>, node| {
219 let key = node
221 .image
222 .as_ref()
223 .map(|image| {
224 (
225 Some(image.as_str().to_string()),
226 node.command.as_str().to_string(),
227 )
228 })
229 .unwrap_or_else(|| (None, node.command.as_str().to_string()));
230
231 if let Entry::Vacant(entry) = acc.entry(key.clone()) {
233 entry.insert(vec![node]);
234 } else {
235 acc.get_mut(&key).unwrap().push(node);
236 }
237
238 acc
239 },
240 )
241 }
242
243 async fn retrieve_all_nodes_available_args_output(
244 ns: Arc<dyn ProviderNamespace + Send + Sync>,
245 image_command_to_nodes_mapping: &HashMap<(Option<String>, String), Vec<&mut NodeSpec>>,
246 ) -> Result<Vec<(Option<String>, String, String)>, OrchestratorError> {
247 try_join_all(
248 image_command_to_nodes_mapping
249 .keys()
250 .cloned()
251 .map(|(image, command)| async {
252 let available_args = ns
254 .get_node_available_args((command.clone(), image.clone()))
255 .await?;
256 debug!(
257 "retrieved available args for image: {:?}, command: {}",
258 image, command
259 );
260
261 Ok::<_, OrchestratorError>((image, command, available_args))
263 })
264 .collect::<Vec<_>>(),
265 )
266 .await
267 }
268
269 fn update_nodes_available_args_output(
270 image_command_to_nodes_mapping: &mut HashMap<(Option<String>, String), Vec<&mut NodeSpec>>,
271 available_args_outputs: Vec<(Option<String>, String, String)>,
272 ) {
273 for (image, command, available_args_output) in available_args_outputs {
274 let nodes = image_command_to_nodes_mapping
275 .get_mut(&(image, command))
276 .expect(&format!(
277 "node image/command key should exist {THIS_IS_A_BUG}"
278 ));
279
280 for node in nodes {
281 node.available_args_output = Some(available_args_output.clone());
282 }
283 }
284 }
285}
286
287#[cfg(test)]
288mod tests {
289
290 #[tokio::test]
291 async fn small_network_config_get_spec() {
292 use configuration::NetworkConfigBuilder;
293
294 use super::*;
295
296 let config = NetworkConfigBuilder::new()
297 .with_relaychain(|r| {
298 r.with_chain("rococo-local")
299 .with_default_command("polkadot")
300 .with_node(|node| node.with_name("alice"))
301 .with_node(|node| {
302 node.with_name("bob")
303 .with_command("polkadot1")
304 .validator(false)
305 })
306 })
307 .with_parachain(|p| {
308 p.with_id(100)
309 .with_default_command("adder-collator")
310 .with_collator(|c| c.with_name("collator1"))
311 })
312 .build()
313 .unwrap();
314
315 let network_spec = NetworkSpec::from_config(&config).await.unwrap();
316 let alice = network_spec.relaychain.nodes.first().unwrap();
317 let bob = network_spec.relaychain.nodes.get(1).unwrap();
318 assert_eq!(alice.command.as_str(), "polkadot");
319 assert_eq!(bob.command.as_str(), "polkadot1");
320 assert!(alice.is_validator);
321 assert!(!bob.is_validator);
322
323 assert_eq!(network_spec.parachains.len(), 1);
325 let para_100 = network_spec.parachains.first().unwrap();
326 assert_eq!(para_100.id, 100);
327 }
328}