zombienet_configuration/
network.rs

1use std::{cell::RefCell, collections::HashSet, fs, marker::PhantomData, rc::Rc};
2
3use anyhow::anyhow;
4use regex::Regex;
5use serde::{Deserialize, Serialize};
6use support::{
7    constants::{NO_ERR_DEF_BUILDER, RELAY_NOT_NONE, THIS_IS_A_BUG, VALIDATION_CHECK, VALID_REGEX},
8    replacer::apply_env_replacements,
9};
10use tracing::trace;
11
12use crate::{
13    custom_process,
14    global_settings::{GlobalSettings, GlobalSettingsBuilder},
15    hrmp_channel::{self, HrmpChannelConfig, HrmpChannelConfigBuilder},
16    parachain::{self, ParachainConfig, ParachainConfigBuilder},
17    relaychain::{self, RelaychainConfig, RelaychainConfigBuilder},
18    shared::{
19        errors::{ConfigError, ValidationError},
20        helpers::{generate_unique_node_name_from_names, merge_errors, merge_errors_vecs},
21        macros::states,
22        node::{GroupNodeConfig, NodeConfig},
23        types::{Arg, AssetLocation, Chain, Command, Image, ValidationContext},
24    },
25    types::ParaId,
26    CustomProcess, CustomProcessBuilder, RegistrationStrategy,
27};
28
29/// A network configuration, composed of a relaychain, parachains and HRMP channels.
30#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
31pub struct NetworkConfig {
32    #[serde(rename = "settings", default = "GlobalSettings::default")]
33    global_settings: GlobalSettings,
34    relaychain: Option<RelaychainConfig>,
35    #[serde(skip_serializing_if = "std::vec::Vec::is_empty", default)]
36    parachains: Vec<ParachainConfig>,
37    #[serde(skip_serializing_if = "std::vec::Vec::is_empty", default)]
38    hrmp_channels: Vec<HrmpChannelConfig>,
39    #[serde(skip_serializing_if = "std::vec::Vec::is_empty", default)]
40    custom_processes: Vec<CustomProcess>,
41}
42
43impl NetworkConfig {
44    /// The global settings of the network.
45    pub fn global_settings(&self) -> &GlobalSettings {
46        &self.global_settings
47    }
48
49    /// The relay chain of the network.
50    pub fn relaychain(&self) -> &RelaychainConfig {
51        self.relaychain
52            .as_ref()
53            .expect(&format!("{RELAY_NOT_NONE}, {THIS_IS_A_BUG}"))
54    }
55
56    /// The parachains of the network.
57    pub fn parachains(&self) -> Vec<&ParachainConfig> {
58        self.parachains.iter().collect::<Vec<_>>()
59    }
60
61    /// The HRMP channels of the network.
62    pub fn hrmp_channels(&self) -> Vec<&HrmpChannelConfig> {
63        self.hrmp_channels.iter().collect::<Vec<_>>()
64    }
65
66    /// The Custom process to spawn
67    pub fn custom_processes(&self) -> Vec<&CustomProcess> {
68        self.custom_processes.iter().collect::<Vec<_>>()
69    }
70
71    /// Set the parachain.
72    fn set_parachains(&mut self, parachains: Vec<ParachainConfig>) {
73        self.parachains = parachains;
74    }
75
76    /// A helper function to dump the network configuration to a TOML string.
77    pub fn dump_to_toml(&self) -> Result<String, toml::ser::Error> {
78        // This regex is used to replace the "" enclosed u128 value to a raw u128 because u128 is not supported for TOML serialization/deserialization.
79        let re = Regex::new(r#""U128%(?<u128_value>\d+)""#)
80            .expect(&format!("{VALID_REGEX} {THIS_IS_A_BUG}"));
81        let toml_string = toml::to_string_pretty(&self)?;
82
83        Ok(re.replace_all(&toml_string, "$u128_value").to_string())
84    }
85
86    /// A helper function to load a network configuration from a TOML file.
87    pub fn load_from_toml_with_settings(
88        path: &str,
89        settings: &GlobalSettings,
90    ) -> Result<NetworkConfig, anyhow::Error> {
91        let mut network_config = NetworkConfig::load_from_toml(path)?;
92        network_config.global_settings = settings.clone();
93        Ok(network_config)
94    }
95
96    /// A helper function to load a network configuration from a TOML file.
97    pub fn load_from_toml(path: &str) -> Result<NetworkConfig, anyhow::Error> {
98        let file_str = fs::read_to_string(path)
99            .map_err(|e| anyhow!("Failed to read configuration file '{}': {}", path, e))?;
100        let re: Regex = Regex::new(r"(?<field_name>(initial_)?balance)\s+=\s+(?<u128_value>\d+)")
101            .expect(&format!("{VALID_REGEX} {THIS_IS_A_BUG}"));
102
103        let toml_text = re.replace_all(&file_str, "$field_name = \"$u128_value\"");
104        trace!("toml text to parse: {}", toml_text);
105        // apply replacements from env in toml
106        let toml_text = apply_env_replacements(&toml_text);
107        trace!("toml text after replacements: {}", toml_text);
108        let mut network_config: NetworkConfig = toml::from_str(&toml_text)?;
109        trace!("parsed config {network_config:#?}");
110
111        // All unwraps below are safe, because we ensure that the relaychain is not None at this point
112        if network_config.relaychain.is_none() {
113            Err(anyhow!("Relay chain does not exist."))?
114        }
115
116        // retrieve the defaults relaychain for assigning to nodes if needed
117        let mut relaychain_default_command: Option<Command> =
118            network_config.relaychain().default_command().cloned();
119
120        if relaychain_default_command.is_none() {
121            relaychain_default_command = network_config.relaychain().command().cloned();
122        }
123        let relaychain_default_image: Option<Image> =
124            network_config.relaychain().default_image().cloned();
125
126        let relaychain_default_db_snapshot: Option<AssetLocation> =
127            network_config.relaychain().default_db_snapshot().cloned();
128
129        let default_args: Vec<Arg> = network_config
130            .relaychain()
131            .default_args()
132            .into_iter()
133            .cloned()
134            .collect();
135
136        let mut nodes: Vec<NodeConfig> = network_config
137            .relaychain()
138            .nodes()
139            .into_iter()
140            .cloned()
141            .collect();
142
143        let group_nodes: Vec<GroupNodeConfig> = network_config
144            .relaychain()
145            .group_node_configs()
146            .into_iter()
147            .cloned()
148            .collect();
149
150        if let Some(group) = group_nodes.iter().find(|n| n.count == 0) {
151            return Err(anyhow!(
152                "Group node '{}' must have a count greater than 0.",
153                group.base_config.name()
154            ));
155        }
156
157        let mut parachains: Vec<ParachainConfig> =
158            network_config.parachains().into_iter().cloned().collect();
159
160        // Validation checks for relay
161        TryInto::<Chain>::try_into(network_config.relaychain().chain().as_str())?;
162        if relaychain_default_image.is_some() {
163            TryInto::<Image>::try_into(relaychain_default_image.clone().expect(VALIDATION_CHECK))?;
164        }
165        if relaychain_default_command.is_some() {
166            TryInto::<Command>::try_into(
167                relaychain_default_command.clone().expect(VALIDATION_CHECK),
168            )?;
169        }
170
171        // Keep track of node names to ensure uniqueness
172        let mut names = HashSet::new();
173
174        for node in nodes.iter_mut() {
175            if relaychain_default_command.is_some() {
176                // we modify only nodes which don't already have a command
177                if node.command.is_none() {
178                    node.command.clone_from(&relaychain_default_command);
179                }
180            }
181
182            if relaychain_default_image.is_some() && node.image.is_none() {
183                node.image.clone_from(&relaychain_default_image);
184            }
185
186            if relaychain_default_db_snapshot.is_some() && node.db_snapshot.is_none() {
187                node.db_snapshot.clone_from(&relaychain_default_db_snapshot);
188            }
189
190            if !default_args.is_empty() && node.args().is_empty() {
191                node.set_args(default_args.clone());
192            }
193
194            let unique_name = generate_unique_node_name_from_names(node.name(), &mut names);
195            node.name = unique_name;
196        }
197
198        for para in parachains.iter_mut() {
199            // retrieve the defaults parachain for assigning to collators if needed
200            let parachain_default_command: Option<Command> = para.default_command().cloned();
201
202            let parachain_default_image: Option<Image> = para.default_image().cloned();
203
204            let parachain_default_db_snapshot: Option<AssetLocation> =
205                para.default_db_snapshot().cloned();
206
207            let default_args: Vec<Arg> = para.default_args().into_iter().cloned().collect();
208
209            let group_collators: Vec<GroupNodeConfig> = para
210                .group_collators_configs()
211                .into_iter()
212                .cloned()
213                .collect();
214
215            if let Some(group) = group_collators.iter().find(|n| n.count == 0) {
216                return Err(anyhow!(
217                    "Group node '{}' must have a count greater than 0.",
218                    group.base_config.name()
219                ));
220            }
221
222            let mut collators: Vec<NodeConfig> = para.collators.clone();
223
224            for collator in collators.iter_mut() {
225                populate_collator_with_defaults(
226                    collator,
227                    &parachain_default_command,
228                    &parachain_default_image,
229                    &parachain_default_db_snapshot,
230                    &default_args,
231                );
232                let unique_name = generate_unique_node_name_from_names(collator.name(), &mut names);
233                collator.name = unique_name;
234            }
235
236            para.collators = collators;
237
238            if para.collator.is_some() {
239                let mut collator = para.collator.clone().unwrap();
240                populate_collator_with_defaults(
241                    &mut collator,
242                    &parachain_default_command,
243                    &parachain_default_image,
244                    &parachain_default_db_snapshot,
245                    &default_args,
246                );
247                let unique_name = generate_unique_node_name_from_names(collator.name(), &mut names);
248                collator.name = unique_name;
249                para.collator = Some(collator);
250            }
251        }
252
253        network_config
254            .relaychain
255            .as_mut()
256            .expect(&format!("{NO_ERR_DEF_BUILDER}, {THIS_IS_A_BUG}"))
257            .set_nodes(nodes);
258
259        network_config.set_parachains(parachains);
260
261        // Validation checks for parachains
262        network_config.parachains().iter().for_each(|parachain| {
263            if parachain.default_image().is_some() {
264                let _ = TryInto::<Image>::try_into(parachain.default_image().unwrap().as_str());
265            }
266            if parachain.default_command().is_some() {
267                let _ = TryInto::<Command>::try_into(parachain.default_command().unwrap().as_str());
268            }
269        });
270        Ok(network_config)
271    }
272}
273
274fn populate_collator_with_defaults(
275    collator: &mut NodeConfig,
276    parachain_default_command: &Option<Command>,
277    parachain_default_image: &Option<Image>,
278    parachain_default_db_snapshot: &Option<AssetLocation>,
279    default_args: &[Arg],
280) {
281    if parachain_default_command.is_some() {
282        // we modify only nodes which don't already have a command
283        if collator.command.is_none() {
284            collator.command.clone_from(parachain_default_command);
285        }
286    }
287
288    if parachain_default_image.is_some() && collator.image.is_none() {
289        collator.image.clone_from(parachain_default_image);
290    }
291
292    if parachain_default_db_snapshot.is_some() && collator.db_snapshot.is_none() {
293        collator
294            .db_snapshot
295            .clone_from(parachain_default_db_snapshot);
296    }
297
298    if !default_args.is_empty() && collator.args().is_empty() {
299        collator.set_args(default_args.to_owned());
300    }
301}
302
303states! {
304    Initial,
305    WithRelaychain
306}
307
308/// A network configuration builder, used to build a [`NetworkConfig`] declaratively with fields validation.
309///
310/// # Example:
311///
312/// ```
313/// use zombienet_configuration::NetworkConfigBuilder;
314///
315/// let network_config = NetworkConfigBuilder::new()
316///     .with_relaychain(|relaychain| {
317///         relaychain
318///             .with_chain("polkadot")
319///             .with_random_nominators_count(10)
320///             .with_default_resources(|resources| {
321///                 resources
322///                     .with_limit_cpu("1000m")
323///                     .with_request_memory("1Gi")
324///                     .with_request_cpu(100_000)
325///             })
326///             .with_node(|node| {
327///                 node.with_name("node")
328///                     .with_command("command")
329///                     .validator(true)
330///             })
331///     })
332///     .with_parachain(|parachain| {
333///         parachain
334///             .with_id(1000)
335///             .with_chain("myparachain1")
336///             .with_initial_balance(100_000)
337///             .with_default_image("myimage:version")
338///             .with_collator(|collator| {
339///                 collator
340///                     .with_name("collator1")
341///                     .with_command("command1")
342///                     .validator(true)
343///             })
344///     })
345///     .with_parachain(|parachain| {
346///         parachain
347///             .with_id(2000)
348///             .with_chain("myparachain2")
349///             .with_initial_balance(50_0000)
350///             .with_collator(|collator| {
351///                 collator
352///                     .with_name("collator2")
353///                     .with_command("command2")
354///                     .validator(true)
355///             })
356///     })
357///     .with_hrmp_channel(|hrmp_channel1| {
358///         hrmp_channel1
359///             .with_sender(1)
360///             .with_recipient(2)
361///             .with_max_capacity(200)
362///             .with_max_message_size(500)
363///     })
364///     .with_hrmp_channel(|hrmp_channel2| {
365///         hrmp_channel2
366///             .with_sender(2)
367///             .with_recipient(1)
368///             .with_max_capacity(100)
369///             .with_max_message_size(250)
370///     })
371///     .with_global_settings(|global_settings| {
372///         global_settings
373///             .with_network_spawn_timeout(1200)
374///             .with_node_spawn_timeout(240)
375///     })
376///     .build();
377///
378/// assert!(network_config.is_ok())
379/// ```
380pub struct NetworkConfigBuilder<State> {
381    config: NetworkConfig,
382    validation_context: Rc<RefCell<ValidationContext>>,
383    errors: Vec<anyhow::Error>,
384    _state: PhantomData<State>,
385}
386
387impl Default for NetworkConfigBuilder<Initial> {
388    fn default() -> Self {
389        Self {
390            config: NetworkConfig {
391                global_settings: GlobalSettingsBuilder::new()
392                    .build()
393                    .expect(&format!("{NO_ERR_DEF_BUILDER}, {THIS_IS_A_BUG}")),
394                relaychain: None,
395                parachains: vec![],
396                hrmp_channels: vec![],
397                custom_processes: vec![],
398            },
399            validation_context: Default::default(),
400            errors: vec![],
401            _state: PhantomData,
402        }
403    }
404}
405
406impl<A> NetworkConfigBuilder<A> {
407    fn transition<B>(
408        config: NetworkConfig,
409        validation_context: Rc<RefCell<ValidationContext>>,
410        errors: Vec<anyhow::Error>,
411    ) -> NetworkConfigBuilder<B> {
412        NetworkConfigBuilder {
413            config,
414            errors,
415            validation_context,
416            _state: PhantomData,
417        }
418    }
419}
420
421impl NetworkConfigBuilder<Initial> {
422    pub fn new() -> NetworkConfigBuilder<Initial> {
423        Self::default()
424    }
425
426    /// uses the default options for both the relay chain and the validator nodes
427    /// the only required fields are the name of the validator nodes,
428    /// and the name of the relay chain ("rococo-local", "polkadot", etc.)
429    pub fn with_chain_and_nodes(
430        relay_name: &str,
431        node_names: Vec<String>,
432    ) -> NetworkConfigBuilder<WithRelaychain> {
433        let network_config = NetworkConfigBuilder::new().with_relaychain(|relaychain| {
434            let mut relaychain_with_node =
435                relaychain.with_chain(relay_name).with_validator(|node| {
436                    node.with_name(node_names.first().unwrap_or(&"".to_string()))
437                });
438
439            for node_name in node_names.iter().skip(1) {
440                relaychain_with_node = relaychain_with_node
441                    .with_validator(|node_builder| node_builder.with_name(node_name));
442            }
443            relaychain_with_node
444        });
445
446        Self::transition(
447            network_config.config,
448            network_config.validation_context,
449            network_config.errors,
450        )
451    }
452
453    /// Set the relay chain using a nested [`RelaychainConfigBuilder`].
454    pub fn with_relaychain(
455        self,
456        f: impl FnOnce(
457            RelaychainConfigBuilder<relaychain::Initial>,
458        ) -> RelaychainConfigBuilder<relaychain::WithAtLeastOneNode>,
459    ) -> NetworkConfigBuilder<WithRelaychain> {
460        match f(RelaychainConfigBuilder::new(
461            self.validation_context.clone(),
462        ))
463        .build()
464        {
465            Ok(relaychain) => Self::transition(
466                NetworkConfig {
467                    relaychain: Some(relaychain),
468                    ..self.config
469                },
470                self.validation_context,
471                self.errors,
472            ),
473            Err(errors) => Self::transition(self.config, self.validation_context, errors),
474        }
475    }
476}
477
478impl NetworkConfigBuilder<WithRelaychain> {
479    /// Set the global settings using a nested [`GlobalSettingsBuilder`].
480    pub fn with_global_settings(
481        self,
482        f: impl FnOnce(GlobalSettingsBuilder) -> GlobalSettingsBuilder,
483    ) -> Self {
484        match f(GlobalSettingsBuilder::new()).build() {
485            Ok(global_settings) => Self::transition(
486                NetworkConfig {
487                    global_settings,
488                    ..self.config
489                },
490                self.validation_context,
491                self.errors,
492            ),
493            Err(errors) => Self::transition(
494                self.config,
495                self.validation_context,
496                merge_errors_vecs(self.errors, errors),
497            ),
498        }
499    }
500
501    /// Add a parachain using a nested [`ParachainConfigBuilder`].
502    pub fn with_parachain(
503        self,
504        f: impl FnOnce(
505            ParachainConfigBuilder<parachain::states::Initial, parachain::states::Bootstrap>,
506        ) -> ParachainConfigBuilder<
507            parachain::states::WithAtLeastOneCollator,
508            parachain::states::Bootstrap,
509        >,
510    ) -> Self {
511        match f(ParachainConfigBuilder::new(self.validation_context.clone())).build() {
512            Ok(parachain) => Self::transition(
513                NetworkConfig {
514                    parachains: [self.config.parachains, vec![parachain]].concat(),
515                    ..self.config
516                },
517                self.validation_context,
518                self.errors,
519            ),
520            Err(errors) => Self::transition(
521                self.config,
522                self.validation_context,
523                merge_errors_vecs(self.errors, errors),
524            ),
525        }
526    }
527
528    /// uses default settings for setting for:
529    /// - the parachain,
530    /// - the global settings
531    /// - the hrmp channels
532    ///
533    /// the only required parameters are the names of the collators as a vector,
534    /// and the id of the parachain
535    pub fn with_parachain_id_and_collators(self, id: u32, collator_names: Vec<String>) -> Self {
536        if collator_names.is_empty() {
537            return Self::transition(
538                self.config,
539                self.validation_context,
540                merge_errors(
541                    self.errors,
542                    ConfigError::Parachain(id, ValidationError::CantBeEmpty().into()).into(),
543                ),
544            );
545        }
546
547        self.with_parachain(|parachain| {
548            let mut parachain_config = parachain.with_id(id).with_collator(|collator| {
549                collator
550                    .with_name(collator_names.first().unwrap_or(&"".to_string()))
551                    .validator(true)
552            });
553
554            for collator_name in collator_names.iter().skip(1) {
555                parachain_config = parachain_config
556                    .with_collator(|collator| collator.with_name(collator_name).validator(true));
557            }
558            parachain_config
559        })
560
561        // TODO: if need to set global settings and hrmp channels
562        // we can also do in here
563    }
564
565    /// Add an HRMP channel using a nested [`HrmpChannelConfigBuilder`].
566    pub fn with_hrmp_channel(
567        self,
568        f: impl FnOnce(
569            HrmpChannelConfigBuilder<hrmp_channel::Initial>,
570        ) -> HrmpChannelConfigBuilder<hrmp_channel::WithRecipient>,
571    ) -> Self {
572        let new_hrmp_channel = f(HrmpChannelConfigBuilder::new()).build();
573
574        Self::transition(
575            NetworkConfig {
576                hrmp_channels: [self.config.hrmp_channels, vec![new_hrmp_channel]].concat(),
577                ..self.config
578            },
579            self.validation_context,
580            self.errors,
581        )
582    }
583
584    /// Add a custom process using a nested [`CustomProcessBuilder`].
585    pub fn with_custom_process(
586        self,
587        f: impl FnOnce(
588            CustomProcessBuilder<custom_process::WithOutName, custom_process::WithOutCmd>,
589        )
590            -> CustomProcessBuilder<custom_process::WithName, custom_process::WithCmd>,
591    ) -> Self {
592        match f(CustomProcessBuilder::new()).build() {
593            Ok(custom_process) => Self::transition(
594                NetworkConfig {
595                    custom_processes: [self.config.custom_processes, vec![custom_process]].concat(),
596                    ..self.config
597                },
598                self.validation_context,
599                self.errors,
600            ),
601            Err((name, errors)) => Self::transition(
602                self.config,
603                self.validation_context,
604                merge_errors_vecs(
605                    self.errors,
606                    errors
607                        .into_iter()
608                        .map(|error| ConfigError::Node(name.clone(), error).into())
609                        .collect::<Vec<_>>(),
610                ),
611            ),
612        }
613    }
614
615    /// Seals the builder and returns a [`NetworkConfig`] if there are no validation errors, else returns errors.
616    pub fn build(self) -> Result<NetworkConfig, Vec<anyhow::Error>> {
617        let mut paras_to_register: HashSet<ParaId> = Default::default();
618        let mut errs: Vec<anyhow::Error> = self
619            .config
620            .parachains
621            .iter()
622            .filter_map(|para| {
623                if let Some(RegistrationStrategy::Manual) = para.registration_strategy() {
624                    return None;
625                };
626
627                if paras_to_register.insert(para.id()) {
628                    None
629                } else {
630                    // already in the set
631                    Some(anyhow!(
632                        "ParaId {} already set to be registered, only one should be.",
633                        para.id()
634                    ))
635                }
636            })
637            .collect();
638
639        if !self.errors.is_empty() || !errs.is_empty() {
640            let mut ret_errs = self.errors;
641            ret_errs.append(&mut errs);
642            return Err(ret_errs);
643        }
644
645        Ok(self.config)
646    }
647}
648
649#[cfg(test)]
650mod tests {
651    use std::path::PathBuf;
652
653    use super::*;
654    use crate::parachain::RegistrationStrategy;
655
656    #[test]
657    fn network_config_builder_should_succeeds_and_returns_a_network_config() {
658        let network_config = NetworkConfigBuilder::new()
659            .with_relaychain(|relaychain| {
660                relaychain
661                    .with_chain("polkadot")
662                    .with_random_nominators_count(10)
663                    .with_validator(|node| node.with_name("node").with_command("command"))
664            })
665            .with_parachain(|parachain| {
666                parachain
667                    .with_id(1)
668                    .with_chain("myparachain1")
669                    .with_initial_balance(100_000)
670                    .with_collator(|collator| {
671                        collator
672                            .with_name("collator1")
673                            .with_command("command1")
674                            .validator(true)
675                    })
676            })
677            .with_parachain(|parachain| {
678                parachain
679                    .with_id(2)
680                    .with_chain("myparachain2")
681                    .with_initial_balance(0)
682                    .with_collator(|collator| {
683                        collator
684                            .with_name("collator2")
685                            .with_command("command2")
686                            .validator(true)
687                    })
688            })
689            .with_hrmp_channel(|hrmp_channel1| {
690                hrmp_channel1
691                    .with_sender(1)
692                    .with_recipient(2)
693                    .with_max_capacity(200)
694                    .with_max_message_size(500)
695            })
696            .with_hrmp_channel(|hrmp_channel2| {
697                hrmp_channel2
698                    .with_sender(2)
699                    .with_recipient(1)
700                    .with_max_capacity(100)
701                    .with_max_message_size(250)
702            })
703            .with_global_settings(|global_settings| {
704                global_settings
705                    .with_network_spawn_timeout(1200)
706                    .with_node_spawn_timeout(240)
707            })
708            .build()
709            .unwrap();
710
711        // relaychain
712        assert_eq!(network_config.relaychain().chain().as_str(), "polkadot");
713        assert_eq!(network_config.relaychain().nodes().len(), 1);
714        let &node = network_config.relaychain().nodes().first().unwrap();
715        assert_eq!(node.name(), "node");
716        assert_eq!(node.command().unwrap().as_str(), "command");
717        assert!(node.is_validator());
718        assert_eq!(
719            network_config
720                .relaychain()
721                .random_nominators_count()
722                .unwrap(),
723            10
724        );
725
726        // parachains
727        assert_eq!(network_config.parachains().len(), 2);
728
729        // parachain1
730        let &parachain1 = network_config.parachains().first().unwrap();
731        assert_eq!(parachain1.id(), 1);
732        assert_eq!(parachain1.collators().len(), 1);
733        let &collator = parachain1.collators().first().unwrap();
734        assert_eq!(collator.name(), "collator1");
735        assert_eq!(collator.command().unwrap().as_str(), "command1");
736        assert!(collator.is_validator());
737        assert_eq!(parachain1.initial_balance(), 100_000);
738        assert_eq!(parachain1.unique_id(), "1");
739
740        // parachain2
741        let &parachain2 = network_config.parachains().last().unwrap();
742        assert_eq!(parachain2.id(), 2);
743        assert_eq!(parachain2.collators().len(), 1);
744        let &collator = parachain2.collators().first().unwrap();
745        assert_eq!(collator.name(), "collator2");
746        assert_eq!(collator.command().unwrap().as_str(), "command2");
747        assert!(collator.is_validator());
748        assert_eq!(parachain2.initial_balance(), 0);
749
750        // hrmp_channels
751        assert_eq!(network_config.hrmp_channels().len(), 2);
752
753        // hrmp_channel1
754        let &hrmp_channel1 = network_config.hrmp_channels().first().unwrap();
755        assert_eq!(hrmp_channel1.sender(), 1);
756        assert_eq!(hrmp_channel1.recipient(), 2);
757        assert_eq!(hrmp_channel1.max_capacity(), 200);
758        assert_eq!(hrmp_channel1.max_message_size(), 500);
759
760        // hrmp_channel2
761        let &hrmp_channel2 = network_config.hrmp_channels().last().unwrap();
762        assert_eq!(hrmp_channel2.sender(), 2);
763        assert_eq!(hrmp_channel2.recipient(), 1);
764        assert_eq!(hrmp_channel2.max_capacity(), 100);
765        assert_eq!(hrmp_channel2.max_message_size(), 250);
766
767        // global settings
768        assert_eq!(
769            network_config.global_settings().network_spawn_timeout(),
770            1200
771        );
772        assert_eq!(network_config.global_settings().node_spawn_timeout(), 240);
773        assert!(network_config.global_settings().tear_down_on_failure());
774    }
775
776    #[test]
777    fn network_config_builder_should_fails_and_returns_multiple_errors_if_relaychain_is_invalid() {
778        let errors = NetworkConfigBuilder::new()
779            .with_relaychain(|relaychain| {
780                relaychain
781                    .with_chain("polkadot")
782                    .with_random_nominators_count(10)
783                    .with_default_image("invalid.image")
784                    .with_validator(|node| node.with_name("node").with_command("invalid command"))
785            })
786            .with_parachain(|parachain| {
787                parachain
788                    .with_id(1)
789                    .with_chain("myparachain")
790                    .with_initial_balance(100_000)
791                    .with_collator(|collator| {
792                        collator
793                            .with_name("collator1")
794                            .with_command("command1")
795                            .validator(true)
796                    })
797            })
798            .build()
799            .unwrap_err();
800
801        assert_eq!(errors.len(), 2);
802        assert_eq!(
803            errors.first().unwrap().to_string(),
804            "relaychain.default_image: 'invalid.image' doesn't match regex '^([ip]|[hostname]/)?[tag_name]:[tag_version]?$'"
805        );
806        assert_eq!(
807            errors.get(1).unwrap().to_string(),
808            "relaychain.nodes['node'].command: 'invalid command' shouldn't contains whitespace"
809        );
810    }
811
812    #[test]
813    fn network_config_builder_should_fails_and_returns_multiple_errors_if_parachain_is_invalid() {
814        let errors = NetworkConfigBuilder::new()
815            .with_relaychain(|relaychain| {
816                relaychain
817                    .with_chain("polkadot")
818                    .with_random_nominators_count(10)
819                    .with_validator(|node| node.with_name("node").with_command("command"))
820            })
821            .with_parachain(|parachain| {
822                parachain
823                    .with_id(1000)
824                    .with_chain("myparachain")
825                    .with_initial_balance(100_000)
826                    .with_collator(|collator| {
827                        collator
828                            .with_name("collator1")
829                            .with_command("invalid command")
830                            .with_image("invalid.image")
831                            .validator(true)
832                    })
833            })
834            .build()
835            .unwrap_err();
836
837        assert_eq!(errors.len(), 2);
838        assert_eq!(
839            errors.first().unwrap().to_string(),
840            "parachain[1000].collators['collator1'].command: 'invalid command' shouldn't contains whitespace"
841        );
842        assert_eq!(
843            errors.get(1).unwrap().to_string(),
844            "parachain[1000].collators['collator1'].image: 'invalid.image' doesn't match regex '^([ip]|[hostname]/)?[tag_name]:[tag_version]?$'"
845        );
846    }
847
848    #[test]
849    fn network_config_builder_should_fails_and_returns_multiple_errors_if_multiple_parachains_are_invalid(
850    ) {
851        let errors = NetworkConfigBuilder::new()
852            .with_relaychain(|relaychain| {
853                relaychain
854                    .with_chain("polkadot")
855                    .with_random_nominators_count(10)
856                    .with_validator(|node| node.with_name("node").with_command("command"))
857            })
858            .with_parachain(|parachain| {
859                parachain
860                    .with_id(1000)
861                    .with_chain("myparachain1")
862                    .with_initial_balance(100_000)
863                    .with_collator(|collator| {
864                        collator
865                            .with_name("collator1")
866                            .with_command("invalid command")
867                    })
868            })
869            .with_parachain(|parachain| {
870                parachain
871                    .with_id(2000)
872                    .with_chain("myparachain2")
873                    .with_initial_balance(100_000)
874                    .with_collator(|collator| {
875                        collator
876                            .with_name("collator2")
877                            .validator(true)
878                            .with_resources(|resources| {
879                                resources
880                                    .with_limit_cpu("1000m")
881                                    .with_request_memory("1Gi")
882                                    .with_request_cpu("invalid")
883                            })
884                    })
885            })
886            .build()
887            .unwrap_err();
888
889        assert_eq!(errors.len(), 2);
890        assert_eq!(
891            errors.first().unwrap().to_string(),
892            "parachain[1000].collators['collator1'].command: 'invalid command' shouldn't contains whitespace"
893        );
894        assert_eq!(
895            errors.get(1).unwrap().to_string(),
896            "parachain[2000].collators['collator2'].resources.request_cpu: 'invalid' doesn't match regex '^\\d+(.\\d+)?(m|K|M|G|T|P|E|Ki|Mi|Gi|Ti|Pi|Ei)?$'"
897        );
898    }
899
900    #[test]
901    fn network_config_builder_should_fails_and_returns_multiple_errors_if_global_settings_is_invalid(
902    ) {
903        let errors = NetworkConfigBuilder::new()
904            .with_relaychain(|relaychain| {
905                relaychain
906                    .with_chain("polkadot")
907                    .with_random_nominators_count(10)
908                    .with_validator(|node| node.with_name("node").with_command("command"))
909            })
910            .with_parachain(|parachain| {
911                parachain
912                    .with_id(1000)
913                    .with_chain("myparachain")
914                    .with_initial_balance(100_000)
915                    .with_collator(|collator| {
916                        collator
917                            .with_name("collator")
918                            .with_command("command")
919                            .validator(true)
920                    })
921            })
922            .with_global_settings(|global_settings| {
923                global_settings
924                    .with_local_ip("127.0.0000.1")
925                    .with_raw_bootnodes_addresses(vec!["/ip4//tcp/45421"])
926            })
927            .build()
928            .unwrap_err();
929
930        assert_eq!(errors.len(), 2);
931        assert_eq!(
932            errors.first().unwrap().to_string(),
933            "global_settings.local_ip: invalid IP address syntax"
934        );
935        assert_eq!(
936            errors.get(1).unwrap().to_string(),
937            "global_settings.bootnodes_addresses[0]: '/ip4//tcp/45421' failed to parse: invalid IPv4 address syntax"
938        );
939    }
940
941    #[test]
942    fn network_config_builder_should_fails_and_returns_multiple_errors_if_multiple_fields_are_invalid(
943    ) {
944        let errors = NetworkConfigBuilder::new()
945            .with_relaychain(|relaychain| {
946                relaychain
947                    .with_chain("polkadot")
948                    .with_random_nominators_count(10)
949                    .with_validator(|node| node.with_name("node").with_command("invalid command"))
950            })
951            .with_parachain(|parachain| {
952                parachain
953                    .with_id(1000)
954                    .with_chain("myparachain")
955                    .with_initial_balance(100_000)
956                    .with_collator(|collator| {
957                        collator
958                            .with_name("collator")
959                            .with_command("command")
960                            .with_image("invalid.image")
961                    })
962            })
963            .with_global_settings(|global_settings| global_settings.with_local_ip("127.0.0000.1"))
964            .build()
965            .unwrap_err();
966
967        assert_eq!(errors.len(), 3);
968        assert_eq!(
969            errors.first().unwrap().to_string(),
970            "relaychain.nodes['node'].command: 'invalid command' shouldn't contains whitespace"
971        );
972        assert_eq!(
973            errors.get(1).unwrap().to_string(),
974            "parachain[1000].collators['collator'].image: 'invalid.image' doesn't match regex '^([ip]|[hostname]/)?[tag_name]:[tag_version]?$'"
975        );
976        assert_eq!(
977            errors.get(2).unwrap().to_string(),
978            "global_settings.local_ip: invalid IP address syntax"
979        );
980    }
981
982    #[test]
983    fn network_config_should_be_dumpable_to_a_toml_config_for_a_small_network() {
984        let network_config = NetworkConfigBuilder::new()
985            .with_relaychain(|relaychain| {
986                relaychain
987                    .with_chain("rococo-local")
988                    .with_default_command("polkadot")
989                    .with_default_image("docker.io/parity/polkadot:latest")
990                    .with_default_args(vec![("-lparachain", "debug").into()])
991                    .with_validator(|node| node.with_name("alice"))
992                    .with_validator(|node| {
993                        node.with_name("bob")
994                            .invulnerable(false)
995                            .bootnode(true)
996                            .with_args(vec![("--database", "paritydb-experimental").into()])
997                    })
998            })
999            .build()
1000            .unwrap();
1001
1002        let got = network_config.dump_to_toml().unwrap();
1003        let expected = fs::read_to_string("./testing/snapshots/0000-small-network.toml").unwrap();
1004        assert_eq!(got, expected);
1005    }
1006
1007    #[test]
1008    fn network_config_should_be_dumpable_to_a_toml_config_for_a_big_network() {
1009        let network_config = NetworkConfigBuilder::new()
1010            .with_relaychain(|relaychain| {
1011                relaychain
1012                    .with_chain("polkadot")
1013                    .with_default_command("polkadot")
1014                    .with_default_image("docker.io/parity/polkadot:latest")
1015                    .with_default_resources(|resources| {
1016                        resources
1017                            .with_request_cpu(100000)
1018                            .with_request_memory("500M")
1019                            .with_limit_cpu("10Gi")
1020                            .with_limit_memory("4000M")
1021                    })
1022                    .with_validator(|node| {
1023                        node.with_name("alice")
1024                            .with_initial_balance(1_000_000_000)
1025                            .bootnode(true)
1026                            .invulnerable(true)
1027                    })
1028                    .with_validator(|node| node.with_name("bob").invulnerable(true).bootnode(true))
1029            })
1030            .with_parachain(|parachain| {
1031                parachain
1032                    .with_id(1000)
1033                    .with_chain("myparachain")
1034                    .with_chain_spec_path("/path/to/my/chain/spec.json")
1035                    .with_registration_strategy(RegistrationStrategy::UsingExtrinsic)
1036                    .onboard_as_parachain(false)
1037                    .with_default_db_snapshot("https://storage.com/path/to/db_snapshot.tgz")
1038                    .with_collator(|collator| {
1039                        collator
1040                            .with_name("john")
1041                            .bootnode(true)
1042                            .invulnerable(true)
1043                            .with_initial_balance(5_000_000_000)
1044                    })
1045                    .with_fullnode(|collator| {
1046                        collator
1047                            .with_name("charles")
1048                            .bootnode(true)
1049                            .invulnerable(true)
1050                            .with_initial_balance(0)
1051                    })
1052                    .with_collator(|collator| {
1053                        collator
1054                            .with_name("frank")
1055                            .invulnerable(false)
1056                            .bootnode(true)
1057                            .with_initial_balance(1_000_000_000)
1058                    })
1059            })
1060            .with_parachain(|parachain| {
1061                parachain
1062                    .with_id(2000)
1063                    .with_chain("myotherparachain")
1064                    .with_chain_spec_path("/path/to/my/other/chain/spec.json")
1065                    .with_collator(|collator| {
1066                        collator
1067                            .with_name("mike")
1068                            .bootnode(true)
1069                            .invulnerable(true)
1070                            .with_initial_balance(5_000_000_000)
1071                    })
1072                    .with_fullnode(|collator| {
1073                        collator
1074                            .with_name("georges")
1075                            .bootnode(true)
1076                            .invulnerable(true)
1077                            .with_initial_balance(0)
1078                    })
1079                    .with_collator(|collator| {
1080                        collator
1081                            .with_name("victor")
1082                            .invulnerable(false)
1083                            .bootnode(true)
1084                            .with_initial_balance(1_000_000_000)
1085                    })
1086            })
1087            .with_hrmp_channel(|hrmp_channel| {
1088                hrmp_channel
1089                    .with_sender(1000)
1090                    .with_recipient(2000)
1091                    .with_max_capacity(150)
1092                    .with_max_message_size(5000)
1093            })
1094            .with_hrmp_channel(|hrmp_channel| {
1095                hrmp_channel
1096                    .with_sender(2000)
1097                    .with_recipient(1000)
1098                    .with_max_capacity(200)
1099                    .with_max_message_size(8000)
1100            })
1101            .with_custom_process(|c| c.with_name("eth-rpc").with_command("eth-rpc"))
1102            .build()
1103            .unwrap();
1104
1105        let got = network_config.dump_to_toml().unwrap();
1106        let expected = fs::read_to_string("./testing/snapshots/0001-big-network.toml").unwrap();
1107        assert_eq!(got, expected);
1108    }
1109
1110    #[test]
1111    fn network_config_builder_should_be_dumplable_to_a_toml_config_a_overrides_default_correctly() {
1112        let network_config = NetworkConfigBuilder::new()
1113            .with_relaychain(|relaychain| {
1114                relaychain
1115                    .with_chain("polkadot")
1116                    .with_default_command("polkadot")
1117                    .with_default_image("docker.io/parity/polkadot:latest")
1118                    .with_default_args(vec![("-name", "value").into(), "--flag".into()])
1119                    .with_default_db_snapshot("https://storage.com/path/to/db_snapshot.tgz")
1120                    .with_default_resources(|resources| {
1121                        resources
1122                            .with_request_cpu(100000)
1123                            .with_request_memory("500M")
1124                            .with_limit_cpu("10Gi")
1125                            .with_limit_memory("4000M")
1126                    })
1127                    .with_validator(|node| {
1128                        node.with_name("alice")
1129                            .with_initial_balance(1_000_000_000)
1130                            .bootnode(true)
1131                            .invulnerable(true)
1132                    })
1133                    .with_validator(|node| {
1134                        node.with_name("bob")
1135                            .invulnerable(true)
1136                            .bootnode(true)
1137                            .with_image("mycustomimage:latest")
1138                            .with_command("my-custom-command")
1139                            .with_db_snapshot("https://storage.com/path/to/other/db_snapshot.tgz")
1140                            .with_resources(|resources| {
1141                                resources
1142                                    .with_request_cpu(1000)
1143                                    .with_request_memory("250Mi")
1144                                    .with_limit_cpu("5Gi")
1145                                    .with_limit_memory("2Gi")
1146                            })
1147                            .with_args(vec![("-myothername", "value").into()])
1148                    })
1149            })
1150            .with_parachain(|parachain| {
1151                parachain
1152                    .with_id(1000)
1153                    .with_chain("myparachain")
1154                    .with_chain_spec_path("/path/to/my/chain/spec.json")
1155                    .with_default_db_snapshot("https://storage.com/path/to/other_snapshot.tgz")
1156                    .with_default_command("my-default-command")
1157                    .with_default_image("mydefaultimage:latest")
1158                    .with_collator(|collator| {
1159                        collator
1160                            .with_name("john")
1161                            .bootnode(true)
1162                            .invulnerable(true)
1163                            .with_initial_balance(5_000_000_000)
1164                            .with_command("my-non-default-command")
1165                            .with_image("anotherimage:latest")
1166                    })
1167                    .with_fullnode(|collator| {
1168                        collator
1169                            .with_name("charles")
1170                            .bootnode(true)
1171                            .invulnerable(true)
1172                            .with_initial_balance(0)
1173                    })
1174            })
1175            .build()
1176            .unwrap();
1177
1178        let got = network_config.dump_to_toml().unwrap();
1179        let expected =
1180            fs::read_to_string("./testing/snapshots/0002-overridden-defaults.toml").unwrap();
1181        assert_eq!(got, expected);
1182    }
1183
1184    #[test]
1185    fn the_toml_config_with_custom_settings() {
1186        let settings = GlobalSettingsBuilder::new()
1187            .with_base_dir("/tmp/test-demo")
1188            .build()
1189            .unwrap();
1190
1191        let load_from_toml = NetworkConfig::load_from_toml_with_settings(
1192            "./testing/snapshots/0000-small-network.toml",
1193            &settings,
1194        )
1195        .unwrap();
1196
1197        assert_eq!(
1198            Some(PathBuf::from("/tmp/test-demo").as_path()),
1199            load_from_toml.global_settings.base_dir()
1200        );
1201    }
1202
1203    #[test]
1204    fn the_toml_config_should_be_imported_and_match_a_network() {
1205        let load_from_toml =
1206            NetworkConfig::load_from_toml("./testing/snapshots/0000-small-network.toml").unwrap();
1207
1208        let expected = NetworkConfigBuilder::new()
1209            .with_relaychain(|relaychain| {
1210                relaychain
1211                    .with_chain("rococo-local")
1212                    .with_default_command("polkadot")
1213                    .with_default_image("docker.io/parity/polkadot:latest")
1214                    .with_default_args(vec![("-lparachain=debug").into()])
1215                    .with_validator(|node| {
1216                        node.with_name("alice")
1217                            .validator(true)
1218                            .invulnerable(true)
1219                            .bootnode(false)
1220                            .with_initial_balance(2000000000000)
1221                    })
1222                    .with_validator(|node| {
1223                        node.with_name("bob")
1224                            .with_args(vec![("--database", "paritydb-experimental").into()])
1225                            .invulnerable(false)
1226                            .bootnode(true)
1227                            .with_initial_balance(2000000000000)
1228                    })
1229            })
1230            .build()
1231            .unwrap();
1232
1233        // We need to assert parts of the network config separately because the expected one contains the chain default context which
1234        // is used for dumbing to tomp while the
1235        // while loaded
1236        assert_eq!(
1237            expected.relaychain().chain(),
1238            load_from_toml.relaychain().chain()
1239        );
1240        assert_eq!(
1241            expected.relaychain().default_args(),
1242            load_from_toml.relaychain().default_args()
1243        );
1244        assert_eq!(
1245            expected.relaychain().default_command(),
1246            load_from_toml.relaychain().default_command()
1247        );
1248        assert_eq!(
1249            expected.relaychain().default_image(),
1250            load_from_toml.relaychain().default_image()
1251        );
1252
1253        // Check the nodes without the Chain Default Context
1254        expected
1255            .relaychain()
1256            .nodes()
1257            .iter()
1258            .zip(load_from_toml.relaychain().nodes().iter())
1259            .for_each(|(expected_node, loaded_node)| {
1260                assert_eq!(expected_node.name(), loaded_node.name());
1261                assert_eq!(expected_node.command(), loaded_node.command());
1262                assert_eq!(expected_node.args(), loaded_node.args());
1263                assert_eq!(
1264                    expected_node.is_invulnerable(),
1265                    loaded_node.is_invulnerable()
1266                );
1267                assert_eq!(expected_node.is_validator(), loaded_node.is_validator());
1268                assert_eq!(expected_node.is_bootnode(), loaded_node.is_bootnode());
1269                assert_eq!(
1270                    expected_node.initial_balance(),
1271                    loaded_node.initial_balance()
1272                );
1273            });
1274    }
1275
1276    #[test]
1277    fn the_toml_config_without_settings_should_be_imported_and_match_a_network() {
1278        let load_from_toml = NetworkConfig::load_from_toml(
1279            "./testing/snapshots/0004-small-network-without-settings.toml",
1280        )
1281        .unwrap();
1282
1283        let expected = NetworkConfigBuilder::new()
1284            .with_relaychain(|relaychain| {
1285                relaychain
1286                    .with_chain("rococo-local")
1287                    .with_default_command("polkadot")
1288                    .with_validator(|node| node.with_name("alice"))
1289                    .with_validator(|node| node.with_name("bob"))
1290            })
1291            .build()
1292            .unwrap();
1293
1294        assert_eq!(
1295            load_from_toml.global_settings().network_spawn_timeout(),
1296            expected.global_settings().network_spawn_timeout()
1297        )
1298    }
1299
1300    #[test]
1301    fn the_toml_config_should_be_imported_and_match_a_network_with_parachains() {
1302        let load_from_toml =
1303            NetworkConfig::load_from_toml("./testing/snapshots/0001-big-network.toml").unwrap();
1304
1305        let expected = NetworkConfigBuilder::new()
1306            .with_relaychain(|relaychain| {
1307                relaychain
1308                    .with_chain("polkadot")
1309                    .with_default_command("polkadot")
1310                    .with_default_image("docker.io/parity/polkadot:latest")
1311                    .with_default_resources(|resources| {
1312                        resources
1313                            .with_request_cpu(100000)
1314                            .with_request_memory("500M")
1315                            .with_limit_cpu("10Gi")
1316                            .with_limit_memory("4000M")
1317                    })
1318                    .with_validator(|node| {
1319                        node.with_name("alice")
1320                            .with_initial_balance(1_000_000_000)
1321                            .bootnode(true)
1322                            .invulnerable(true)
1323                    })
1324                    .with_validator(|node| node.with_name("bob").invulnerable(true).bootnode(true))
1325            })
1326            .with_parachain(|parachain| {
1327                parachain
1328                    .with_id(1000)
1329                    .with_chain("myparachain")
1330                    .with_chain_spec_path("/path/to/my/chain/spec.json")
1331                    .with_registration_strategy(RegistrationStrategy::UsingExtrinsic)
1332                    .onboard_as_parachain(false)
1333                    .with_default_db_snapshot("https://storage.com/path/to/db_snapshot.tgz")
1334                    .with_collator(|collator| {
1335                        collator
1336                            .with_name("john")
1337                            .bootnode(true)
1338                            .invulnerable(true)
1339                            .with_initial_balance(5_000_000_000)
1340                    })
1341                    .with_fullnode(|collator| {
1342                        collator
1343                            .with_name("charles")
1344                            .bootnode(true)
1345                            .invulnerable(true)
1346                            .with_initial_balance(0)
1347                    })
1348                    .with_collator(|collator| {
1349                        collator
1350                            .with_name("frank")
1351                            .invulnerable(false)
1352                            .bootnode(true)
1353                            .with_initial_balance(1_000_000_000)
1354                    })
1355            })
1356            .with_parachain(|parachain| {
1357                parachain
1358                    .with_id(2000)
1359                    .with_chain("myotherparachain")
1360                    .with_chain_spec_path("/path/to/my/other/chain/spec.json")
1361                    .with_collator(|collator| {
1362                        collator
1363                            .with_name("mike")
1364                            .bootnode(true)
1365                            .invulnerable(true)
1366                            .with_initial_balance(5_000_000_000)
1367                    })
1368                    .with_fullnode(|collator| {
1369                        collator
1370                            .with_name("georges")
1371                            .bootnode(true)
1372                            .invulnerable(true)
1373                            .with_initial_balance(0)
1374                    })
1375                    .with_collator(|collator| {
1376                        collator
1377                            .with_name("victor")
1378                            .invulnerable(false)
1379                            .bootnode(true)
1380                            .with_initial_balance(1_000_000_000)
1381                    })
1382            })
1383            .with_hrmp_channel(|hrmp_channel| {
1384                hrmp_channel
1385                    .with_sender(1000)
1386                    .with_recipient(2000)
1387                    .with_max_capacity(150)
1388                    .with_max_message_size(5000)
1389            })
1390            .with_hrmp_channel(|hrmp_channel| {
1391                hrmp_channel
1392                    .with_sender(2000)
1393                    .with_recipient(1000)
1394                    .with_max_capacity(200)
1395                    .with_max_message_size(8000)
1396            })
1397            .build()
1398            .unwrap();
1399
1400        // Check the relay chain
1401        assert_eq!(
1402            expected.relaychain().default_resources(),
1403            load_from_toml.relaychain().default_resources()
1404        );
1405
1406        // Check the nodes without the Chain Default Context
1407        expected
1408            .relaychain()
1409            .nodes()
1410            .iter()
1411            .zip(load_from_toml.relaychain().nodes().iter())
1412            .for_each(|(expected_node, loaded_node)| {
1413                assert_eq!(expected_node.name(), loaded_node.name());
1414                assert_eq!(expected_node.command(), loaded_node.command());
1415                assert_eq!(expected_node.args(), loaded_node.args());
1416                assert_eq!(expected_node.is_validator(), loaded_node.is_validator());
1417                assert_eq!(expected_node.is_bootnode(), loaded_node.is_bootnode());
1418                assert_eq!(
1419                    expected_node.initial_balance(),
1420                    loaded_node.initial_balance()
1421                );
1422                assert_eq!(
1423                    expected_node.is_invulnerable(),
1424                    loaded_node.is_invulnerable()
1425                );
1426            });
1427
1428        expected
1429            .parachains()
1430            .iter()
1431            .zip(load_from_toml.parachains().iter())
1432            .for_each(|(expected_parachain, loaded_parachain)| {
1433                assert_eq!(expected_parachain.id(), loaded_parachain.id());
1434                assert_eq!(expected_parachain.chain(), loaded_parachain.chain());
1435                assert_eq!(
1436                    expected_parachain.chain_spec_path(),
1437                    loaded_parachain.chain_spec_path()
1438                );
1439                assert_eq!(
1440                    expected_parachain.registration_strategy(),
1441                    loaded_parachain.registration_strategy()
1442                );
1443                assert_eq!(
1444                    expected_parachain.onboard_as_parachain(),
1445                    loaded_parachain.onboard_as_parachain()
1446                );
1447                assert_eq!(
1448                    expected_parachain.default_db_snapshot(),
1449                    loaded_parachain.default_db_snapshot()
1450                );
1451                assert_eq!(
1452                    expected_parachain.default_command(),
1453                    loaded_parachain.default_command()
1454                );
1455                assert_eq!(
1456                    expected_parachain.default_image(),
1457                    loaded_parachain.default_image()
1458                );
1459                assert_eq!(
1460                    expected_parachain.collators().len(),
1461                    loaded_parachain.collators().len()
1462                );
1463                expected_parachain
1464                    .collators()
1465                    .iter()
1466                    .zip(loaded_parachain.collators().iter())
1467                    .for_each(|(expected_collator, loaded_collator)| {
1468                        assert_eq!(expected_collator.name(), loaded_collator.name());
1469                        assert_eq!(expected_collator.command(), loaded_collator.command());
1470                        assert_eq!(expected_collator.image(), loaded_collator.image());
1471                        assert_eq!(
1472                            expected_collator.is_validator(),
1473                            loaded_collator.is_validator()
1474                        );
1475                        assert_eq!(
1476                            expected_collator.is_bootnode(),
1477                            loaded_collator.is_bootnode()
1478                        );
1479                        assert_eq!(
1480                            expected_collator.is_invulnerable(),
1481                            loaded_collator.is_invulnerable()
1482                        );
1483                        assert_eq!(
1484                            expected_collator.initial_balance(),
1485                            loaded_collator.initial_balance()
1486                        );
1487                    });
1488            });
1489
1490        expected
1491            .hrmp_channels()
1492            .iter()
1493            .zip(load_from_toml.hrmp_channels().iter())
1494            .for_each(|(expected_hrmp_channel, loaded_hrmp_channel)| {
1495                assert_eq!(expected_hrmp_channel.sender(), loaded_hrmp_channel.sender());
1496                assert_eq!(
1497                    expected_hrmp_channel.recipient(),
1498                    loaded_hrmp_channel.recipient()
1499                );
1500                assert_eq!(
1501                    expected_hrmp_channel.max_capacity(),
1502                    loaded_hrmp_channel.max_capacity()
1503                );
1504                assert_eq!(
1505                    expected_hrmp_channel.max_message_size(),
1506                    loaded_hrmp_channel.max_message_size()
1507                );
1508            });
1509    }
1510
1511    #[test]
1512    fn the_toml_config_should_be_imported_and_match_a_network_with_overriden_defaults() {
1513        let load_from_toml =
1514            NetworkConfig::load_from_toml("./testing/snapshots/0002-overridden-defaults.toml")
1515                .unwrap();
1516
1517        let expected = NetworkConfigBuilder::new()
1518            .with_relaychain(|relaychain| {
1519                relaychain
1520                    .with_chain("polkadot")
1521                    .with_default_command("polkadot")
1522                    .with_default_image("docker.io/parity/polkadot:latest")
1523                    .with_default_args(vec![("-name", "value").into(), "--flag".into()])
1524                    .with_default_db_snapshot("https://storage.com/path/to/db_snapshot.tgz")
1525                    .with_default_resources(|resources| {
1526                        resources
1527                            .with_request_cpu(100000)
1528                            .with_request_memory("500M")
1529                            .with_limit_cpu("10Gi")
1530                            .with_limit_memory("4000M")
1531                    })
1532                    .with_validator(|node| {
1533                        node.with_name("alice")
1534                            .with_initial_balance(1_000_000_000)
1535                            .bootnode(true)
1536                            .invulnerable(true)
1537                    })
1538                    .with_validator(|node| {
1539                        node.with_name("bob")
1540                            .invulnerable(true)
1541                            .bootnode(true)
1542                            .with_image("mycustomimage:latest")
1543                            .with_command("my-custom-command")
1544                            .with_db_snapshot("https://storage.com/path/to/other/db_snapshot.tgz")
1545                            .with_resources(|resources| {
1546                                resources
1547                                    .with_request_cpu(1000)
1548                                    .with_request_memory("250Mi")
1549                                    .with_limit_cpu("5Gi")
1550                                    .with_limit_memory("2Gi")
1551                            })
1552                            .with_args(vec![("-myothername", "value").into()])
1553                    })
1554            })
1555            .with_parachain(|parachain| {
1556                parachain
1557                    .with_id(1000)
1558                    .with_chain("myparachain")
1559                    .with_chain_spec_path("/path/to/my/chain/spec.json")
1560                    .with_default_db_snapshot("https://storage.com/path/to/other_snapshot.tgz")
1561                    .with_default_command("my-default-command")
1562                    .with_default_image("mydefaultimage:latest")
1563                    .with_collator(|collator| {
1564                        collator
1565                            .with_name("john")
1566                            .bootnode(true)
1567                            .validator(true)
1568                            .invulnerable(true)
1569                            .with_initial_balance(5_000_000_000)
1570                            .with_command("my-non-default-command")
1571                            .with_image("anotherimage:latest")
1572                    })
1573                    .with_fullnode(|collator| {
1574                        collator
1575                            .with_name("charles")
1576                            .bootnode(true)
1577                            .invulnerable(true)
1578                            .with_initial_balance(0)
1579                    })
1580            })
1581            .build()
1582            .unwrap();
1583
1584        expected
1585            .parachains()
1586            .iter()
1587            .zip(load_from_toml.parachains().iter())
1588            .for_each(|(expected_parachain, loaded_parachain)| {
1589                assert_eq!(expected_parachain.id(), loaded_parachain.id());
1590                assert_eq!(expected_parachain.chain(), loaded_parachain.chain());
1591                assert_eq!(
1592                    expected_parachain.chain_spec_path(),
1593                    loaded_parachain.chain_spec_path()
1594                );
1595                assert_eq!(
1596                    expected_parachain.registration_strategy(),
1597                    loaded_parachain.registration_strategy()
1598                );
1599                assert_eq!(
1600                    expected_parachain.onboard_as_parachain(),
1601                    loaded_parachain.onboard_as_parachain()
1602                );
1603                assert_eq!(
1604                    expected_parachain.default_db_snapshot(),
1605                    loaded_parachain.default_db_snapshot()
1606                );
1607                assert_eq!(
1608                    expected_parachain.default_command(),
1609                    loaded_parachain.default_command()
1610                );
1611                assert_eq!(
1612                    expected_parachain.default_image(),
1613                    loaded_parachain.default_image()
1614                );
1615                assert_eq!(
1616                    expected_parachain.collators().len(),
1617                    loaded_parachain.collators().len()
1618                );
1619                expected_parachain
1620                    .collators()
1621                    .iter()
1622                    .zip(loaded_parachain.collators().iter())
1623                    .for_each(|(expected_collator, loaded_collator)| {
1624                        assert_eq!(expected_collator.name(), loaded_collator.name());
1625                        assert_eq!(expected_collator.command(), loaded_collator.command());
1626                        assert_eq!(expected_collator.image(), loaded_collator.image());
1627                        assert_eq!(
1628                            expected_collator.is_validator(),
1629                            loaded_collator.is_validator()
1630                        );
1631                        assert_eq!(
1632                            expected_collator.is_bootnode(),
1633                            loaded_collator.is_bootnode()
1634                        );
1635                        assert_eq!(
1636                            expected_collator.is_invulnerable(),
1637                            loaded_collator.is_invulnerable()
1638                        );
1639                        assert_eq!(
1640                            expected_collator.initial_balance(),
1641                            loaded_collator.initial_balance()
1642                        );
1643                    });
1644            });
1645    }
1646
1647    #[test]
1648    fn with_chain_and_nodes_works() {
1649        let network_config = NetworkConfigBuilder::with_chain_and_nodes(
1650            "rococo-local",
1651            vec!["alice".to_string(), "bob".to_string()],
1652        )
1653        .build()
1654        .unwrap();
1655
1656        // relaychain
1657        assert_eq!(network_config.relaychain().chain().as_str(), "rococo-local");
1658        assert_eq!(network_config.relaychain().nodes().len(), 2);
1659        let mut node_names = network_config.relaychain().nodes().into_iter();
1660        let node1 = node_names.next().unwrap().name();
1661        assert_eq!(node1, "alice");
1662        let node2 = node_names.next().unwrap().name();
1663        assert_eq!(node2, "bob");
1664
1665        // parachains
1666        assert_eq!(network_config.parachains().len(), 0);
1667    }
1668
1669    #[test]
1670    fn with_chain_and_nodes_should_fail_with_empty_relay_name() {
1671        let errors = NetworkConfigBuilder::with_chain_and_nodes("", vec!["alice".to_string()])
1672            .build()
1673            .unwrap_err();
1674
1675        assert_eq!(
1676            errors.first().unwrap().to_string(),
1677            "relaychain.chain: can't be empty"
1678        );
1679    }
1680
1681    #[test]
1682    fn with_chain_and_nodes_should_fail_with_empty_node_list() {
1683        let errors = NetworkConfigBuilder::with_chain_and_nodes("rococo-local", vec![])
1684            .build()
1685            .unwrap_err();
1686
1687        assert_eq!(
1688            errors.first().unwrap().to_string(),
1689            "relaychain.nodes[''].name: can't be empty"
1690        );
1691    }
1692
1693    #[test]
1694    fn with_chain_and_nodes_should_fail_with_empty_node_name() {
1695        let errors = NetworkConfigBuilder::with_chain_and_nodes(
1696            "rococo-local",
1697            vec!["alice".to_string(), "".to_string()],
1698        )
1699        .build()
1700        .unwrap_err();
1701
1702        assert_eq!(
1703            errors.first().unwrap().to_string(),
1704            "relaychain.nodes[''].name: can't be empty"
1705        );
1706    }
1707
1708    #[test]
1709    fn with_parachain_id_and_collators_works() {
1710        let network_config = NetworkConfigBuilder::with_chain_and_nodes(
1711            "rococo-local",
1712            vec!["alice".to_string(), "bob".to_string()],
1713        )
1714        .with_parachain_id_and_collators(
1715            100,
1716            vec!["collator1".to_string(), "collator2".to_string()],
1717        )
1718        .build()
1719        .unwrap();
1720
1721        // relaychain
1722        assert_eq!(network_config.relaychain().chain().as_str(), "rococo-local");
1723        assert_eq!(network_config.relaychain().nodes().len(), 2);
1724        let mut node_names = network_config.relaychain().nodes().into_iter();
1725        let node1 = node_names.next().unwrap().name();
1726        assert_eq!(node1, "alice");
1727        let node2 = node_names.next().unwrap().name();
1728        assert_eq!(node2, "bob");
1729
1730        // parachains
1731        assert_eq!(network_config.parachains().len(), 1);
1732        let &parachain1 = network_config.parachains().first().unwrap();
1733        assert_eq!(parachain1.id(), 100);
1734        assert_eq!(parachain1.collators().len(), 2);
1735        let mut collator_names = parachain1.collators().into_iter();
1736        let collator1 = collator_names.next().unwrap().name();
1737        assert_eq!(collator1, "collator1");
1738        let collator2 = collator_names.next().unwrap().name();
1739        assert_eq!(collator2, "collator2");
1740
1741        assert_eq!(parachain1.initial_balance(), 2_000_000_000_000);
1742    }
1743
1744    #[test]
1745    fn with_parachain_id_and_collators_should_fail_with_empty_collator_list() {
1746        let errors =
1747            NetworkConfigBuilder::with_chain_and_nodes("polkadot", vec!["alice".to_string()])
1748                .with_parachain_id_and_collators(1, vec![])
1749                .build()
1750                .unwrap_err();
1751
1752        assert_eq!(
1753            errors.first().unwrap().to_string(),
1754            "parachain[1].can't be empty"
1755        );
1756    }
1757
1758    #[test]
1759    fn with_parachain_id_and_collators_should_fail_with_empty_collator_name() {
1760        let errors =
1761            NetworkConfigBuilder::with_chain_and_nodes("polkadot", vec!["alice".to_string()])
1762                .with_parachain_id_and_collators(1, vec!["collator1".to_string(), "".to_string()])
1763                .build()
1764                .unwrap_err();
1765
1766        assert_eq!(
1767            errors.first().unwrap().to_string(),
1768            "parachain[1].collators[''].name: can't be empty"
1769        );
1770    }
1771
1772    #[test]
1773    fn wasm_override_in_toml_should_work() {
1774        let load_from_toml = NetworkConfig::load_from_toml(
1775            "./testing/snapshots/0005-small-networl-with-wasm-override.toml",
1776        )
1777        .unwrap();
1778
1779        let expected = NetworkConfigBuilder::new()
1780            .with_relaychain(|relaychain| {
1781                relaychain
1782                    .with_chain("rococo-local")
1783                    .with_default_command("polkadot")
1784                    .with_wasm_override("/some/path/runtime.wasm")
1785                    .with_validator(|node| node.with_name("alice"))
1786                    .with_validator(|node| node.with_name("bob"))
1787            })
1788            .with_parachain(|p| {
1789                p.with_id(1000)
1790                    .with_wasm_override("https://some.com/runtime.wasm")
1791                    .with_collator(|c| c.with_name("john"))
1792            })
1793            .build()
1794            .unwrap();
1795
1796        assert_eq!(
1797            load_from_toml.relaychain().wasm_override(),
1798            expected.relaychain().wasm_override()
1799        );
1800        assert_eq!(
1801            load_from_toml.parachains()[0].wasm_override(),
1802            expected.parachains()[0].wasm_override()
1803        );
1804    }
1805
1806    #[test]
1807    fn multiple_paras_with_same_id_should_work() {
1808        let network_config = NetworkConfigBuilder::new()
1809            .with_relaychain(|relaychain| {
1810                relaychain
1811                    .with_chain("polkadot")
1812                    .with_fullnode(|node| node.with_name("node").with_command("command"))
1813            })
1814            .with_parachain(|parachain| {
1815                parachain
1816                    .with_id(1)
1817                    .with_chain("myparachain1")
1818                    .with_collator(|collator| {
1819                        collator.with_name("collator1").with_command("command1")
1820                    })
1821            })
1822            .with_parachain(|parachain| {
1823                parachain
1824                    .with_id(1)
1825                    .with_chain("myparachain1")
1826                    .with_registration_strategy(RegistrationStrategy::Manual)
1827                    .with_collator(|collator| {
1828                        collator.with_name("collator2").with_command("command1")
1829                    })
1830            })
1831            .build()
1832            .unwrap();
1833
1834        let &parachain2 = network_config.parachains().last().unwrap();
1835        assert_eq!(parachain2.unique_id(), "1-1");
1836    }
1837
1838    #[test]
1839    fn multiple_paras_with_same_id_both_for_register_should_fail() {
1840        let errors = NetworkConfigBuilder::new()
1841            .with_relaychain(|relaychain| {
1842                relaychain
1843                    .with_chain("polkadot")
1844                    .with_fullnode(|node| node.with_name("node").with_command("command"))
1845            })
1846            .with_parachain(|parachain| {
1847                parachain
1848                    .with_id(1)
1849                    .with_chain("myparachain1")
1850                    .with_collator(|collator| {
1851                        collator.with_name("collator1").with_command("command1")
1852                    })
1853            })
1854            .with_parachain(|parachain| {
1855                parachain
1856                    .with_id(1)
1857                    .with_chain("myparachain1")
1858                    // .with_registration_strategy(RegistrationStrategy::UsingExtrinsic)
1859                    .with_collator(|collator| {
1860                        collator
1861                            .with_name("collator2")
1862                            .with_command("command1")
1863                    })
1864            })
1865            .build()
1866            .unwrap_err();
1867
1868        assert_eq!(
1869            errors.first().unwrap().to_string(),
1870            "ParaId 1 already set to be registered, only one should be."
1871        );
1872    }
1873
1874    #[test]
1875    fn network_config_should_work_from_toml_without_chain_name() {
1876        let loaded_from_toml =
1877            NetworkConfig::load_from_toml("./testing/snapshots/0006-without-rc-chain-name.toml")
1878                .unwrap();
1879
1880        assert_eq!(
1881            "rococo-local",
1882            loaded_from_toml.relaychain().chain().as_str()
1883        );
1884    }
1885
1886    #[test]
1887    fn network_config_should_work_from_toml_with_duplicate_name_between_collator_and_relay_node() {
1888        let loaded_from_toml = NetworkConfig::load_from_toml(
1889            "./testing/snapshots/0007-small-network_w_parachain_w_duplicate_node_names.toml",
1890        )
1891        .unwrap();
1892
1893        assert_eq!(
1894            loaded_from_toml
1895                .relaychain()
1896                .nodes()
1897                .iter()
1898                .filter(|n| n.name() == "alice")
1899                .count(),
1900            1
1901        );
1902        assert_eq!(
1903            loaded_from_toml
1904                .parachains()
1905                .iter()
1906                .flat_map(|para| para.collators())
1907                .filter(|n| n.name() == "alice-1")
1908                .count(),
1909            1
1910        );
1911    }
1912
1913    #[test]
1914    fn raw_spec_override_in_toml_should_work() {
1915        let load_from_toml = NetworkConfig::load_from_toml(
1916            "./testing/snapshots/0008-small-network-with-raw-spec-override.toml",
1917        )
1918        .unwrap();
1919
1920        let expected = NetworkConfigBuilder::new()
1921            .with_relaychain(|relaychain| {
1922                relaychain
1923                    .with_chain("rococo-local")
1924                    .with_default_command("polkadot")
1925                    .with_raw_spec_override("/some/path/raw_spec_override.json")
1926                    .with_validator(|node| node.with_name("alice"))
1927                    .with_validator(|node| node.with_name("bob"))
1928            })
1929            .with_parachain(|p| {
1930                p.with_id(1000)
1931                    .with_raw_spec_override("https://some.com/raw_spec_override.json")
1932                    .with_collator(|c| c.with_name("john"))
1933            })
1934            .build()
1935            .unwrap();
1936
1937        assert_eq!(
1938            load_from_toml.relaychain().raw_spec_override(),
1939            expected.relaychain().raw_spec_override()
1940        );
1941        assert_eq!(
1942            load_from_toml.parachains()[0].raw_spec_override(),
1943            expected.parachains()[0].raw_spec_override()
1944        );
1945    }
1946}