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