zombienet_configuration/
network.rs

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