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_node(|node| {
373///                 node.with_name("node")
374///                     .with_command("command")
375///                     .validator(true)
376///             })
377///     })
378///     .with_parachain(|parachain| {
379///         parachain
380///             .with_id(1000)
381///             .with_chain("myparachain1")
382///             .with_initial_balance(100_000)
383///             .with_default_image("myimage:version")
384///             .with_collator(|collator| {
385///                 collator
386///                     .with_name("collator1")
387///                     .with_command("command1")
388///                     .validator(true)
389///             })
390///     })
391///     .with_parachain(|parachain| {
392///         parachain
393///             .with_id(2000)
394///             .with_chain("myparachain2")
395///             .with_initial_balance(50_0000)
396///             .with_collator(|collator| {
397///                 collator
398///                     .with_name("collator2")
399///                     .with_command("command2")
400///                     .validator(true)
401///             })
402///     })
403///     .with_hrmp_channel(|hrmp_channel1| {
404///         hrmp_channel1
405///             .with_sender(1)
406///             .with_recipient(2)
407///             .with_max_capacity(200)
408///             .with_max_message_size(500)
409///     })
410///     .with_hrmp_channel(|hrmp_channel2| {
411///         hrmp_channel2
412///             .with_sender(2)
413///             .with_recipient(1)
414///             .with_max_capacity(100)
415///             .with_max_message_size(250)
416///     })
417///     .with_global_settings(|global_settings| {
418///         global_settings
419///             .with_network_spawn_timeout(1200)
420///             .with_node_spawn_timeout(240)
421///     })
422///     .build();
423///
424/// assert!(network_config.is_ok())
425/// ```
426pub struct NetworkConfigBuilder<State> {
427    config: NetworkConfig,
428    validation_context: Rc<RefCell<ValidationContext>>,
429    errors: Vec<anyhow::Error>,
430    _state: PhantomData<State>,
431}
432
433impl Default for NetworkConfigBuilder<Initial> {
434    fn default() -> Self {
435        Self {
436            config: NetworkConfig {
437                global_settings: GlobalSettingsBuilder::new()
438                    .build()
439                    .expect(&format!("{NO_ERR_DEF_BUILDER}, {THIS_IS_A_BUG}")),
440                relaychain: None,
441                parachains: vec![],
442                hrmp_channels: vec![],
443                custom_processes: vec![],
444            },
445            validation_context: Default::default(),
446            errors: vec![],
447            _state: PhantomData,
448        }
449    }
450}
451
452impl<A> NetworkConfigBuilder<A> {
453    fn transition<B>(
454        config: NetworkConfig,
455        validation_context: Rc<RefCell<ValidationContext>>,
456        errors: Vec<anyhow::Error>,
457    ) -> NetworkConfigBuilder<B> {
458        NetworkConfigBuilder {
459            config,
460            errors,
461            validation_context,
462            _state: PhantomData,
463        }
464    }
465}
466
467impl NetworkConfigBuilder<Initial> {
468    pub fn new() -> NetworkConfigBuilder<Initial> {
469        Self::default()
470    }
471
472    /// uses the default options for both the relay chain and the validator nodes
473    /// the only required fields are the name of the validator nodes,
474    /// and the name of the relay chain ("rococo-local", "polkadot", etc.)
475    pub fn with_chain_and_nodes(
476        relay_name: &str,
477        node_names: Vec<String>,
478    ) -> NetworkConfigBuilder<WithRelaychain> {
479        let network_config = NetworkConfigBuilder::new().with_relaychain(|relaychain| {
480            let mut relaychain_with_node =
481                relaychain.with_chain(relay_name).with_validator(|node| {
482                    node.with_name(node_names.first().unwrap_or(&"".to_string()))
483                });
484
485            for node_name in node_names.iter().skip(1) {
486                relaychain_with_node = relaychain_with_node
487                    .with_validator(|node_builder| node_builder.with_name(node_name));
488            }
489            relaychain_with_node
490        });
491
492        Self::transition(
493            network_config.config,
494            network_config.validation_context,
495            network_config.errors,
496        )
497    }
498
499    /// Set the relay chain using a nested [`RelaychainConfigBuilder`].
500    pub fn with_relaychain(
501        self,
502        f: impl FnOnce(
503            RelaychainConfigBuilder<relaychain::Initial>,
504        ) -> RelaychainConfigBuilder<relaychain::WithAtLeastOneNode>,
505    ) -> NetworkConfigBuilder<WithRelaychain> {
506        match f(RelaychainConfigBuilder::new(
507            self.validation_context.clone(),
508        ))
509        .build()
510        {
511            Ok(relaychain) => Self::transition(
512                NetworkConfig {
513                    relaychain: Some(relaychain),
514                    ..self.config
515                },
516                self.validation_context,
517                self.errors,
518            ),
519            Err(errors) => Self::transition(self.config, self.validation_context, errors),
520        }
521    }
522}
523
524impl NetworkConfigBuilder<WithRelaychain> {
525    /// Set the global settings using a nested [`GlobalSettingsBuilder`].
526    pub fn with_global_settings(
527        self,
528        f: impl FnOnce(GlobalSettingsBuilder) -> GlobalSettingsBuilder,
529    ) -> Self {
530        match f(GlobalSettingsBuilder::new()).build() {
531            Ok(global_settings) => Self::transition(
532                NetworkConfig {
533                    global_settings,
534                    ..self.config
535                },
536                self.validation_context,
537                self.errors,
538            ),
539            Err(errors) => Self::transition(
540                self.config,
541                self.validation_context,
542                merge_errors_vecs(self.errors, errors),
543            ),
544        }
545    }
546
547    /// Add a parachain using a nested [`ParachainConfigBuilder`].
548    pub fn with_parachain(
549        self,
550        f: impl FnOnce(
551            ParachainConfigBuilder<parachain::states::Initial, parachain::states::Bootstrap>,
552        ) -> ParachainConfigBuilder<
553            parachain::states::WithAtLeastOneCollator,
554            parachain::states::Bootstrap,
555        >,
556    ) -> Self {
557        match f(ParachainConfigBuilder::new(self.validation_context.clone())).build() {
558            Ok(parachain) => Self::transition(
559                NetworkConfig {
560                    parachains: [self.config.parachains, vec![parachain]].concat(),
561                    ..self.config
562                },
563                self.validation_context,
564                self.errors,
565            ),
566            Err(errors) => Self::transition(
567                self.config,
568                self.validation_context,
569                merge_errors_vecs(self.errors, errors),
570            ),
571        }
572    }
573
574    /// uses default settings for setting for:
575    /// - the parachain,
576    /// - the global settings
577    /// - the hrmp channels
578    ///
579    /// the only required parameters are the names of the collators as a vector,
580    /// and the id of the parachain
581    pub fn with_parachain_id_and_collators(self, id: u32, collator_names: Vec<String>) -> Self {
582        if collator_names.is_empty() {
583            return Self::transition(
584                self.config,
585                self.validation_context,
586                merge_errors(
587                    self.errors,
588                    ConfigError::Parachain(id, ValidationError::CantBeEmpty().into()).into(),
589                ),
590            );
591        }
592
593        self.with_parachain(|parachain| {
594            let mut parachain_config = parachain.with_id(id).with_collator(|collator| {
595                collator
596                    .with_name(collator_names.first().unwrap_or(&"".to_string()))
597                    .validator(true)
598            });
599
600            for collator_name in collator_names.iter().skip(1) {
601                parachain_config = parachain_config
602                    .with_collator(|collator| collator.with_name(collator_name).validator(true));
603            }
604            parachain_config
605        })
606
607        // TODO: if need to set global settings and hrmp channels
608        // we can also do in here
609    }
610
611    /// Add an HRMP channel using a nested [`HrmpChannelConfigBuilder`].
612    pub fn with_hrmp_channel(
613        self,
614        f: impl FnOnce(
615            HrmpChannelConfigBuilder<hrmp_channel::Initial>,
616        ) -> HrmpChannelConfigBuilder<hrmp_channel::WithRecipient>,
617    ) -> Self {
618        let new_hrmp_channel = f(HrmpChannelConfigBuilder::new()).build();
619
620        Self::transition(
621            NetworkConfig {
622                hrmp_channels: [self.config.hrmp_channels, vec![new_hrmp_channel]].concat(),
623                ..self.config
624            },
625            self.validation_context,
626            self.errors,
627        )
628    }
629
630    /// Add a custom process using a nested [`CustomProcessBuilder`].
631    pub fn with_custom_process(
632        self,
633        f: impl FnOnce(
634            CustomProcessBuilder<custom_process::WithOutName, custom_process::WithOutCmd>,
635        )
636            -> CustomProcessBuilder<custom_process::WithName, custom_process::WithCmd>,
637    ) -> Self {
638        match f(CustomProcessBuilder::new()).build() {
639            Ok(custom_process) => Self::transition(
640                NetworkConfig {
641                    custom_processes: [self.config.custom_processes, vec![custom_process]].concat(),
642                    ..self.config
643                },
644                self.validation_context,
645                self.errors,
646            ),
647            Err((name, errors)) => Self::transition(
648                self.config,
649                self.validation_context,
650                merge_errors_vecs(
651                    self.errors,
652                    errors
653                        .into_iter()
654                        .map(|error| ConfigError::Node(name.clone(), error).into())
655                        .collect::<Vec<_>>(),
656                ),
657            ),
658        }
659    }
660
661    /// Seals the builder and returns a [`NetworkConfig`] if there are no validation errors, else returns errors.
662    pub fn build(self) -> Result<NetworkConfig, Vec<anyhow::Error>> {
663        let mut paras_to_register: HashSet<ParaId> = Default::default();
664        let mut errs: Vec<anyhow::Error> = self
665            .config
666            .parachains
667            .iter()
668            .filter_map(|para| {
669                if let Some(RegistrationStrategy::Manual) = para.registration_strategy() {
670                    return None;
671                };
672
673                if paras_to_register.insert(para.id()) {
674                    None
675                } else {
676                    // already in the set
677                    Some(anyhow!(
678                        "ParaId {} already set to be registered, only one should be.",
679                        para.id()
680                    ))
681                }
682            })
683            .collect();
684
685        if !self.errors.is_empty() || !errs.is_empty() {
686            let mut ret_errs = self.errors;
687            ret_errs.append(&mut errs);
688            return Err(ret_errs);
689        }
690
691        Ok(self.config)
692    }
693}
694
695#[cfg(test)]
696mod tests {
697    use std::path::PathBuf;
698
699    use super::*;
700    use crate::parachain::RegistrationStrategy;
701
702    #[test]
703    fn network_config_builder_should_succeeds_and_returns_a_network_config() {
704        let network_config = NetworkConfigBuilder::new()
705            .with_relaychain(|relaychain| {
706                relaychain
707                    .with_chain("polkadot")
708                    .with_random_nominators_count(10)
709                    .with_validator(|node| node.with_name("node").with_command("command"))
710            })
711            .with_parachain(|parachain| {
712                parachain
713                    .with_id(1)
714                    .with_chain("myparachain1")
715                    .with_initial_balance(100_000)
716                    .with_collator(|collator| {
717                        collator
718                            .with_name("collator1")
719                            .with_command("command1")
720                            .validator(true)
721                    })
722            })
723            .with_parachain(|parachain| {
724                parachain
725                    .with_id(2)
726                    .with_chain("myparachain2")
727                    .with_initial_balance(0)
728                    .with_collator(|collator| {
729                        collator
730                            .with_name("collator2")
731                            .with_command("command2")
732                            .validator(true)
733                    })
734            })
735            .with_hrmp_channel(|hrmp_channel1| {
736                hrmp_channel1
737                    .with_sender(1)
738                    .with_recipient(2)
739                    .with_max_capacity(200)
740                    .with_max_message_size(500)
741            })
742            .with_hrmp_channel(|hrmp_channel2| {
743                hrmp_channel2
744                    .with_sender(2)
745                    .with_recipient(1)
746                    .with_max_capacity(100)
747                    .with_max_message_size(250)
748            })
749            .with_global_settings(|global_settings| {
750                global_settings
751                    .with_network_spawn_timeout(1200)
752                    .with_node_spawn_timeout(240)
753            })
754            .build()
755            .unwrap();
756
757        // relaychain
758        assert_eq!(network_config.relaychain().chain().as_str(), "polkadot");
759        assert_eq!(network_config.relaychain().nodes().len(), 1);
760        let &node = network_config.relaychain().nodes().first().unwrap();
761        assert_eq!(node.name(), "node");
762        assert_eq!(node.command().unwrap().as_str(), "command");
763        assert!(node.is_validator());
764        assert_eq!(
765            network_config
766                .relaychain()
767                .random_nominators_count()
768                .unwrap(),
769            10
770        );
771
772        // parachains
773        assert_eq!(network_config.parachains().len(), 2);
774
775        // parachain1
776        let &parachain1 = network_config.parachains().first().unwrap();
777        assert_eq!(parachain1.id(), 1);
778        assert_eq!(parachain1.collators().len(), 1);
779        let &collator = parachain1.collators().first().unwrap();
780        assert_eq!(collator.name(), "collator1");
781        assert_eq!(collator.command().unwrap().as_str(), "command1");
782        assert!(collator.is_validator());
783        assert_eq!(parachain1.initial_balance(), 100_000);
784        assert_eq!(parachain1.unique_id(), "1");
785
786        // parachain2
787        let &parachain2 = network_config.parachains().last().unwrap();
788        assert_eq!(parachain2.id(), 2);
789        assert_eq!(parachain2.collators().len(), 1);
790        let &collator = parachain2.collators().first().unwrap();
791        assert_eq!(collator.name(), "collator2");
792        assert_eq!(collator.command().unwrap().as_str(), "command2");
793        assert!(collator.is_validator());
794        assert_eq!(parachain2.initial_balance(), 0);
795
796        // hrmp_channels
797        assert_eq!(network_config.hrmp_channels().len(), 2);
798
799        // hrmp_channel1
800        let &hrmp_channel1 = network_config.hrmp_channels().first().unwrap();
801        assert_eq!(hrmp_channel1.sender(), 1);
802        assert_eq!(hrmp_channel1.recipient(), 2);
803        assert_eq!(hrmp_channel1.max_capacity(), 200);
804        assert_eq!(hrmp_channel1.max_message_size(), 500);
805
806        // hrmp_channel2
807        let &hrmp_channel2 = network_config.hrmp_channels().last().unwrap();
808        assert_eq!(hrmp_channel2.sender(), 2);
809        assert_eq!(hrmp_channel2.recipient(), 1);
810        assert_eq!(hrmp_channel2.max_capacity(), 100);
811        assert_eq!(hrmp_channel2.max_message_size(), 250);
812
813        // global settings
814        assert_eq!(
815            network_config.global_settings().network_spawn_timeout(),
816            1200
817        );
818        assert_eq!(network_config.global_settings().node_spawn_timeout(), 240);
819        assert!(network_config.global_settings().tear_down_on_failure());
820    }
821
822    #[test]
823    fn network_config_builder_should_fails_and_returns_multiple_errors_if_relaychain_is_invalid() {
824        let errors = NetworkConfigBuilder::new()
825            .with_relaychain(|relaychain| {
826                relaychain
827                    .with_chain("polkadot")
828                    .with_random_nominators_count(10)
829                    .with_default_image("invalid.image")
830                    .with_validator(|node| node.with_name("node").with_command("invalid command"))
831            })
832            .with_parachain(|parachain| {
833                parachain
834                    .with_id(1)
835                    .with_chain("myparachain")
836                    .with_initial_balance(100_000)
837                    .with_collator(|collator| {
838                        collator
839                            .with_name("collator1")
840                            .with_command("command1")
841                            .validator(true)
842                    })
843            })
844            .build()
845            .unwrap_err();
846
847        assert_eq!(errors.len(), 2);
848        assert_eq!(
849            errors.first().unwrap().to_string(),
850            "relaychain.default_image: 'invalid.image' doesn't match regex '^([ip]|[hostname]/)?[tag_name]:[tag_version]?$'"
851        );
852        assert_eq!(
853            errors.get(1).unwrap().to_string(),
854            "relaychain.nodes['node'].command: 'invalid command' shouldn't contains whitespace"
855        );
856    }
857
858    #[test]
859    fn network_config_builder_should_fails_and_returns_multiple_errors_if_parachain_is_invalid() {
860        let errors = NetworkConfigBuilder::new()
861            .with_relaychain(|relaychain| {
862                relaychain
863                    .with_chain("polkadot")
864                    .with_random_nominators_count(10)
865                    .with_validator(|node| node.with_name("node").with_command("command"))
866            })
867            .with_parachain(|parachain| {
868                parachain
869                    .with_id(1000)
870                    .with_chain("myparachain")
871                    .with_initial_balance(100_000)
872                    .with_collator(|collator| {
873                        collator
874                            .with_name("collator1")
875                            .with_command("invalid command")
876                            .with_image("invalid.image")
877                            .validator(true)
878                    })
879            })
880            .build()
881            .unwrap_err();
882
883        assert_eq!(errors.len(), 2);
884        assert_eq!(
885            errors.first().unwrap().to_string(),
886            "parachain[1000].collators['collator1'].command: 'invalid command' shouldn't contains whitespace"
887        );
888        assert_eq!(
889            errors.get(1).unwrap().to_string(),
890            "parachain[1000].collators['collator1'].image: 'invalid.image' doesn't match regex '^([ip]|[hostname]/)?[tag_name]:[tag_version]?$'"
891        );
892    }
893
894    #[test]
895    fn network_config_builder_should_fails_and_returns_multiple_errors_if_multiple_parachains_are_invalid(
896    ) {
897        let errors = NetworkConfigBuilder::new()
898            .with_relaychain(|relaychain| {
899                relaychain
900                    .with_chain("polkadot")
901                    .with_random_nominators_count(10)
902                    .with_validator(|node| node.with_name("node").with_command("command"))
903            })
904            .with_parachain(|parachain| {
905                parachain
906                    .with_id(1000)
907                    .with_chain("myparachain1")
908                    .with_initial_balance(100_000)
909                    .with_collator(|collator| {
910                        collator
911                            .with_name("collator1")
912                            .with_command("invalid command")
913                    })
914            })
915            .with_parachain(|parachain| {
916                parachain
917                    .with_id(2000)
918                    .with_chain("myparachain2")
919                    .with_initial_balance(100_000)
920                    .with_collator(|collator| {
921                        collator
922                            .with_name("collator2")
923                            .validator(true)
924                            .with_resources(|resources| {
925                                resources
926                                    .with_limit_cpu("1000m")
927                                    .with_request_memory("1Gi")
928                                    .with_request_cpu("invalid")
929                            })
930                    })
931            })
932            .build()
933            .unwrap_err();
934
935        assert_eq!(errors.len(), 2);
936        assert_eq!(
937            errors.first().unwrap().to_string(),
938            "parachain[1000].collators['collator1'].command: 'invalid command' shouldn't contains whitespace"
939        );
940        assert_eq!(
941            errors.get(1).unwrap().to_string(),
942            "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)?$'"
943        );
944    }
945
946    #[test]
947    fn network_config_builder_should_fails_and_returns_multiple_errors_if_global_settings_is_invalid(
948    ) {
949        let errors = NetworkConfigBuilder::new()
950            .with_relaychain(|relaychain| {
951                relaychain
952                    .with_chain("polkadot")
953                    .with_random_nominators_count(10)
954                    .with_validator(|node| node.with_name("node").with_command("command"))
955            })
956            .with_parachain(|parachain| {
957                parachain
958                    .with_id(1000)
959                    .with_chain("myparachain")
960                    .with_initial_balance(100_000)
961                    .with_collator(|collator| {
962                        collator
963                            .with_name("collator")
964                            .with_command("command")
965                            .validator(true)
966                    })
967            })
968            .with_global_settings(|global_settings| {
969                global_settings
970                    .with_local_ip("127.0.0000.1")
971                    .with_raw_bootnodes_addresses(vec!["/ip4//tcp/45421"])
972            })
973            .build()
974            .unwrap_err();
975
976        assert_eq!(errors.len(), 2);
977        assert_eq!(
978            errors.first().unwrap().to_string(),
979            "global_settings.local_ip: invalid IP address syntax"
980        );
981        assert_eq!(
982            errors.get(1).unwrap().to_string(),
983            "global_settings.bootnodes_addresses[0]: '/ip4//tcp/45421' failed to parse: invalid IPv4 address syntax"
984        );
985    }
986
987    #[test]
988    fn network_config_builder_should_fails_and_returns_multiple_errors_if_multiple_fields_are_invalid(
989    ) {
990        let errors = NetworkConfigBuilder::new()
991            .with_relaychain(|relaychain| {
992                relaychain
993                    .with_chain("polkadot")
994                    .with_random_nominators_count(10)
995                    .with_validator(|node| node.with_name("node").with_command("invalid command"))
996            })
997            .with_parachain(|parachain| {
998                parachain
999                    .with_id(1000)
1000                    .with_chain("myparachain")
1001                    .with_initial_balance(100_000)
1002                    .with_collator(|collator| {
1003                        collator
1004                            .with_name("collator")
1005                            .with_command("command")
1006                            .with_image("invalid.image")
1007                    })
1008            })
1009            .with_global_settings(|global_settings| global_settings.with_local_ip("127.0.0000.1"))
1010            .build()
1011            .unwrap_err();
1012
1013        assert_eq!(errors.len(), 3);
1014        assert_eq!(
1015            errors.first().unwrap().to_string(),
1016            "relaychain.nodes['node'].command: 'invalid command' shouldn't contains whitespace"
1017        );
1018        assert_eq!(
1019            errors.get(1).unwrap().to_string(),
1020            "parachain[1000].collators['collator'].image: 'invalid.image' doesn't match regex '^([ip]|[hostname]/)?[tag_name]:[tag_version]?$'"
1021        );
1022        assert_eq!(
1023            errors.get(2).unwrap().to_string(),
1024            "global_settings.local_ip: invalid IP address syntax"
1025        );
1026    }
1027
1028    #[test]
1029    fn network_config_should_be_dumpable_to_a_toml_config_for_a_small_network() {
1030        let network_config = NetworkConfigBuilder::new()
1031            .with_relaychain(|relaychain| {
1032                relaychain
1033                    .with_chain("rococo-local")
1034                    .with_default_command("polkadot")
1035                    .with_default_image("docker.io/parity/polkadot:latest")
1036                    .with_default_args(vec![("-lparachain", "debug").into()])
1037                    .with_validator(|node| node.with_name("alice"))
1038                    .with_validator(|node| {
1039                        node.with_name("bob")
1040                            .invulnerable(false)
1041                            .bootnode(true)
1042                            .with_args(vec![("--database", "paritydb-experimental").into()])
1043                    })
1044            })
1045            .build()
1046            .unwrap();
1047
1048        let got = network_config.dump_to_toml().unwrap();
1049        let expected = fs::read_to_string("./testing/snapshots/0000-small-network.toml").unwrap();
1050        assert_eq!(got, expected);
1051    }
1052
1053    #[test]
1054    fn network_config_should_be_dumpable_to_a_toml_config_for_a_big_network() {
1055        let network_config = NetworkConfigBuilder::new()
1056            .with_relaychain(|relaychain| {
1057                relaychain
1058                    .with_chain("polkadot")
1059                    .with_default_command("polkadot")
1060                    .with_default_image("docker.io/parity/polkadot:latest")
1061                    .with_default_resources(|resources| {
1062                        resources
1063                            .with_request_cpu(100000)
1064                            .with_request_memory("500M")
1065                            .with_limit_cpu("10Gi")
1066                            .with_limit_memory("4000M")
1067                    })
1068                    .with_validator(|node| {
1069                        node.with_name("alice")
1070                            .with_initial_balance(1_000_000_000)
1071                            .bootnode(true)
1072                            .invulnerable(true)
1073                    })
1074                    .with_validator(|node| node.with_name("bob").invulnerable(true).bootnode(true))
1075            })
1076            .with_parachain(|parachain| {
1077                parachain
1078                    .with_id(1000)
1079                    .with_chain("myparachain")
1080                    .with_chain_spec_path("/path/to/my/chain/spec.json")
1081                    .with_registration_strategy(RegistrationStrategy::UsingExtrinsic)
1082                    .onboard_as_parachain(false)
1083                    .with_default_db_snapshot("https://storage.com/path/to/db_snapshot.tgz")
1084                    .with_collator(|collator| {
1085                        collator
1086                            .with_name("john")
1087                            .bootnode(true)
1088                            .invulnerable(true)
1089                            .with_initial_balance(5_000_000_000)
1090                    })
1091                    .with_fullnode(|collator| {
1092                        collator
1093                            .with_name("charles")
1094                            .bootnode(true)
1095                            .invulnerable(true)
1096                            .with_initial_balance(0)
1097                    })
1098                    .with_collator(|collator| {
1099                        collator
1100                            .with_name("frank")
1101                            .invulnerable(false)
1102                            .bootnode(true)
1103                            .with_initial_balance(1_000_000_000)
1104                    })
1105            })
1106            .with_parachain(|parachain| {
1107                parachain
1108                    .with_id(2000)
1109                    .with_chain("myotherparachain")
1110                    .with_chain_spec_path("/path/to/my/other/chain/spec.json")
1111                    .with_collator(|collator| {
1112                        collator
1113                            .with_name("mike")
1114                            .bootnode(true)
1115                            .invulnerable(true)
1116                            .with_initial_balance(5_000_000_000)
1117                    })
1118                    .with_fullnode(|collator| {
1119                        collator
1120                            .with_name("georges")
1121                            .bootnode(true)
1122                            .invulnerable(true)
1123                            .with_initial_balance(0)
1124                    })
1125                    .with_collator(|collator| {
1126                        collator
1127                            .with_name("victor")
1128                            .invulnerable(false)
1129                            .bootnode(true)
1130                            .with_initial_balance(1_000_000_000)
1131                    })
1132            })
1133            .with_hrmp_channel(|hrmp_channel| {
1134                hrmp_channel
1135                    .with_sender(1000)
1136                    .with_recipient(2000)
1137                    .with_max_capacity(150)
1138                    .with_max_message_size(5000)
1139            })
1140            .with_hrmp_channel(|hrmp_channel| {
1141                hrmp_channel
1142                    .with_sender(2000)
1143                    .with_recipient(1000)
1144                    .with_max_capacity(200)
1145                    .with_max_message_size(8000)
1146            })
1147            .with_custom_process(|c| c.with_name("eth-rpc").with_command("eth-rpc"))
1148            .build()
1149            .unwrap();
1150
1151        let got = network_config.dump_to_toml().unwrap();
1152        let expected = fs::read_to_string("./testing/snapshots/0001-big-network.toml").unwrap();
1153        assert_eq!(got, expected);
1154    }
1155
1156    #[test]
1157    fn network_config_builder_should_be_dumplable_to_a_toml_config_a_overrides_default_correctly() {
1158        let network_config = NetworkConfigBuilder::new()
1159            .with_relaychain(|relaychain| {
1160                relaychain
1161                    .with_chain("polkadot")
1162                    .with_default_command("polkadot")
1163                    .with_default_image("docker.io/parity/polkadot:latest")
1164                    .with_default_args(vec![("-name", "value").into(), "--flag".into()])
1165                    .with_default_db_snapshot("https://storage.com/path/to/db_snapshot.tgz")
1166                    .with_default_resources(|resources| {
1167                        resources
1168                            .with_request_cpu(100000)
1169                            .with_request_memory("500M")
1170                            .with_limit_cpu("10Gi")
1171                            .with_limit_memory("4000M")
1172                    })
1173                    .with_validator(|node| {
1174                        node.with_name("alice")
1175                            .with_initial_balance(1_000_000_000)
1176                            .bootnode(true)
1177                            .invulnerable(true)
1178                    })
1179                    .with_validator(|node| {
1180                        node.with_name("bob")
1181                            .invulnerable(true)
1182                            .bootnode(true)
1183                            .with_image("mycustomimage:latest")
1184                            .with_command("my-custom-command")
1185                            .with_db_snapshot("https://storage.com/path/to/other/db_snapshot.tgz")
1186                            .with_resources(|resources| {
1187                                resources
1188                                    .with_request_cpu(1000)
1189                                    .with_request_memory("250Mi")
1190                                    .with_limit_cpu("5Gi")
1191                                    .with_limit_memory("2Gi")
1192                            })
1193                            .with_args(vec![("-myothername", "value").into()])
1194                    })
1195            })
1196            .with_parachain(|parachain| {
1197                parachain
1198                    .with_id(1000)
1199                    .with_chain("myparachain")
1200                    .with_chain_spec_path("/path/to/my/chain/spec.json")
1201                    .with_default_db_snapshot("https://storage.com/path/to/other_snapshot.tgz")
1202                    .with_default_command("my-default-command")
1203                    .with_default_image("mydefaultimage:latest")
1204                    .with_collator(|collator| {
1205                        collator
1206                            .with_name("john")
1207                            .bootnode(true)
1208                            .invulnerable(true)
1209                            .with_initial_balance(5_000_000_000)
1210                            .with_command("my-non-default-command")
1211                            .with_image("anotherimage:latest")
1212                    })
1213                    .with_fullnode(|collator| {
1214                        collator
1215                            .with_name("charles")
1216                            .bootnode(true)
1217                            .invulnerable(true)
1218                            .with_initial_balance(0)
1219                    })
1220            })
1221            .build()
1222            .unwrap();
1223
1224        let got = network_config.dump_to_toml().unwrap();
1225        let expected =
1226            fs::read_to_string("./testing/snapshots/0002-overridden-defaults.toml").unwrap();
1227        assert_eq!(got, expected);
1228    }
1229
1230    #[test]
1231    fn the_toml_config_with_custom_settings() {
1232        let settings = GlobalSettingsBuilder::new()
1233            .with_base_dir("/tmp/test-demo")
1234            .build()
1235            .unwrap();
1236
1237        let load_from_toml = NetworkConfig::load_from_toml_with_settings(
1238            "./testing/snapshots/0000-small-network.toml",
1239            &settings,
1240        )
1241        .unwrap();
1242
1243        assert_eq!(
1244            Some(PathBuf::from("/tmp/test-demo").as_path()),
1245            load_from_toml.global_settings.base_dir()
1246        );
1247    }
1248
1249    #[test]
1250    fn the_toml_config_should_be_imported_and_match_a_network() {
1251        let load_from_toml =
1252            NetworkConfig::load_from_toml("./testing/snapshots/0000-small-network.toml").unwrap();
1253
1254        let expected = NetworkConfigBuilder::new()
1255            .with_relaychain(|relaychain| {
1256                relaychain
1257                    .with_chain("rococo-local")
1258                    .with_default_command("polkadot")
1259                    .with_default_image("docker.io/parity/polkadot:latest")
1260                    .with_default_args(vec![("-lparachain=debug").into()])
1261                    .with_validator(|node| {
1262                        node.with_name("alice")
1263                            .validator(true)
1264                            .invulnerable(true)
1265                            .bootnode(false)
1266                            .with_initial_balance(2000000000000)
1267                    })
1268                    .with_validator(|node| {
1269                        node.with_name("bob")
1270                            .with_args(vec![("--database", "paritydb-experimental").into()])
1271                            .invulnerable(false)
1272                            .bootnode(true)
1273                            .with_initial_balance(2000000000000)
1274                    })
1275            })
1276            .build()
1277            .unwrap();
1278
1279        // We need to assert parts of the network config separately because the expected one contains the chain default context which
1280        // is used for dumbing to tomp while the
1281        // while loaded
1282        assert_eq!(
1283            expected.relaychain().chain(),
1284            load_from_toml.relaychain().chain()
1285        );
1286        assert_eq!(
1287            expected.relaychain().default_args(),
1288            load_from_toml.relaychain().default_args()
1289        );
1290        assert_eq!(
1291            expected.relaychain().default_command(),
1292            load_from_toml.relaychain().default_command()
1293        );
1294        assert_eq!(
1295            expected.relaychain().default_image(),
1296            load_from_toml.relaychain().default_image()
1297        );
1298
1299        // Check the nodes without the Chain Default Context
1300        expected
1301            .relaychain()
1302            .nodes()
1303            .iter()
1304            .zip(load_from_toml.relaychain().nodes().iter())
1305            .for_each(|(expected_node, loaded_node)| {
1306                assert_eq!(expected_node.name(), loaded_node.name());
1307                assert_eq!(expected_node.command(), loaded_node.command());
1308                assert_eq!(expected_node.args(), loaded_node.args());
1309                assert_eq!(
1310                    expected_node.is_invulnerable(),
1311                    loaded_node.is_invulnerable()
1312                );
1313                assert_eq!(expected_node.is_validator(), loaded_node.is_validator());
1314                assert_eq!(expected_node.is_bootnode(), loaded_node.is_bootnode());
1315                assert_eq!(
1316                    expected_node.initial_balance(),
1317                    loaded_node.initial_balance()
1318                );
1319            });
1320    }
1321
1322    #[test]
1323    fn the_toml_config_without_settings_should_be_imported_and_match_a_network() {
1324        let load_from_toml = NetworkConfig::load_from_toml(
1325            "./testing/snapshots/0004-small-network-without-settings.toml",
1326        )
1327        .unwrap();
1328
1329        let expected = NetworkConfigBuilder::new()
1330            .with_relaychain(|relaychain| {
1331                relaychain
1332                    .with_chain("rococo-local")
1333                    .with_default_command("polkadot")
1334                    .with_validator(|node| node.with_name("alice"))
1335                    .with_validator(|node| node.with_name("bob"))
1336            })
1337            .build()
1338            .unwrap();
1339
1340        assert_eq!(
1341            load_from_toml.global_settings().network_spawn_timeout(),
1342            expected.global_settings().network_spawn_timeout()
1343        )
1344    }
1345
1346    #[test]
1347    fn the_toml_config_should_be_imported_and_match_a_network_with_parachains() {
1348        let load_from_toml =
1349            NetworkConfig::load_from_toml("./testing/snapshots/0001-big-network.toml").unwrap();
1350
1351        let expected = NetworkConfigBuilder::new()
1352            .with_relaychain(|relaychain| {
1353                relaychain
1354                    .with_chain("polkadot")
1355                    .with_default_command("polkadot")
1356                    .with_default_image("docker.io/parity/polkadot:latest")
1357                    .with_default_resources(|resources| {
1358                        resources
1359                            .with_request_cpu(100000)
1360                            .with_request_memory("500M")
1361                            .with_limit_cpu("10Gi")
1362                            .with_limit_memory("4000M")
1363                    })
1364                    .with_validator(|node| {
1365                        node.with_name("alice")
1366                            .with_initial_balance(1_000_000_000)
1367                            .bootnode(true)
1368                            .invulnerable(true)
1369                    })
1370                    .with_validator(|node| node.with_name("bob").invulnerable(true).bootnode(true))
1371            })
1372            .with_parachain(|parachain| {
1373                parachain
1374                    .with_id(1000)
1375                    .with_chain("myparachain")
1376                    .with_chain_spec_path("/path/to/my/chain/spec.json")
1377                    .with_registration_strategy(RegistrationStrategy::UsingExtrinsic)
1378                    .onboard_as_parachain(false)
1379                    .with_default_db_snapshot("https://storage.com/path/to/db_snapshot.tgz")
1380                    .with_collator(|collator| {
1381                        collator
1382                            .with_name("john")
1383                            .bootnode(true)
1384                            .invulnerable(true)
1385                            .with_initial_balance(5_000_000_000)
1386                    })
1387                    .with_fullnode(|collator| {
1388                        collator
1389                            .with_name("charles")
1390                            .bootnode(true)
1391                            .invulnerable(true)
1392                            .with_initial_balance(0)
1393                    })
1394                    .with_collator(|collator| {
1395                        collator
1396                            .with_name("frank")
1397                            .invulnerable(false)
1398                            .bootnode(true)
1399                            .with_initial_balance(1_000_000_000)
1400                    })
1401            })
1402            .with_parachain(|parachain| {
1403                parachain
1404                    .with_id(2000)
1405                    .with_chain("myotherparachain")
1406                    .with_chain_spec_path("/path/to/my/other/chain/spec.json")
1407                    .with_collator(|collator| {
1408                        collator
1409                            .with_name("mike")
1410                            .bootnode(true)
1411                            .invulnerable(true)
1412                            .with_initial_balance(5_000_000_000)
1413                    })
1414                    .with_fullnode(|collator| {
1415                        collator
1416                            .with_name("georges")
1417                            .bootnode(true)
1418                            .invulnerable(true)
1419                            .with_initial_balance(0)
1420                    })
1421                    .with_collator(|collator| {
1422                        collator
1423                            .with_name("victor")
1424                            .invulnerable(false)
1425                            .bootnode(true)
1426                            .with_initial_balance(1_000_000_000)
1427                    })
1428            })
1429            .with_hrmp_channel(|hrmp_channel| {
1430                hrmp_channel
1431                    .with_sender(1000)
1432                    .with_recipient(2000)
1433                    .with_max_capacity(150)
1434                    .with_max_message_size(5000)
1435            })
1436            .with_hrmp_channel(|hrmp_channel| {
1437                hrmp_channel
1438                    .with_sender(2000)
1439                    .with_recipient(1000)
1440                    .with_max_capacity(200)
1441                    .with_max_message_size(8000)
1442            })
1443            .build()
1444            .unwrap();
1445
1446        // Check the relay chain
1447        assert_eq!(
1448            expected.relaychain().default_resources(),
1449            load_from_toml.relaychain().default_resources()
1450        );
1451
1452        // Check the nodes without the Chain Default Context
1453        expected
1454            .relaychain()
1455            .nodes()
1456            .iter()
1457            .zip(load_from_toml.relaychain().nodes().iter())
1458            .for_each(|(expected_node, loaded_node)| {
1459                assert_eq!(expected_node.name(), loaded_node.name());
1460                assert_eq!(expected_node.command(), loaded_node.command());
1461                assert_eq!(expected_node.args(), loaded_node.args());
1462                assert_eq!(expected_node.is_validator(), loaded_node.is_validator());
1463                assert_eq!(expected_node.is_bootnode(), loaded_node.is_bootnode());
1464                assert_eq!(
1465                    expected_node.initial_balance(),
1466                    loaded_node.initial_balance()
1467                );
1468                assert_eq!(
1469                    expected_node.is_invulnerable(),
1470                    loaded_node.is_invulnerable()
1471                );
1472            });
1473
1474        expected
1475            .parachains()
1476            .iter()
1477            .zip(load_from_toml.parachains().iter())
1478            .for_each(|(expected_parachain, loaded_parachain)| {
1479                assert_eq!(expected_parachain.id(), loaded_parachain.id());
1480                assert_eq!(expected_parachain.chain(), loaded_parachain.chain());
1481                assert_eq!(
1482                    expected_parachain.chain_spec_path(),
1483                    loaded_parachain.chain_spec_path()
1484                );
1485                assert_eq!(
1486                    expected_parachain.registration_strategy(),
1487                    loaded_parachain.registration_strategy()
1488                );
1489                assert_eq!(
1490                    expected_parachain.onboard_as_parachain(),
1491                    loaded_parachain.onboard_as_parachain()
1492                );
1493                assert_eq!(
1494                    expected_parachain.default_db_snapshot(),
1495                    loaded_parachain.default_db_snapshot()
1496                );
1497                assert_eq!(
1498                    expected_parachain.default_command(),
1499                    loaded_parachain.default_command()
1500                );
1501                assert_eq!(
1502                    expected_parachain.default_image(),
1503                    loaded_parachain.default_image()
1504                );
1505                assert_eq!(
1506                    expected_parachain.collators().len(),
1507                    loaded_parachain.collators().len()
1508                );
1509                expected_parachain
1510                    .collators()
1511                    .iter()
1512                    .zip(loaded_parachain.collators().iter())
1513                    .for_each(|(expected_collator, loaded_collator)| {
1514                        assert_eq!(expected_collator.name(), loaded_collator.name());
1515                        assert_eq!(expected_collator.command(), loaded_collator.command());
1516                        assert_eq!(expected_collator.image(), loaded_collator.image());
1517                        assert_eq!(
1518                            expected_collator.is_validator(),
1519                            loaded_collator.is_validator()
1520                        );
1521                        assert_eq!(
1522                            expected_collator.is_bootnode(),
1523                            loaded_collator.is_bootnode()
1524                        );
1525                        assert_eq!(
1526                            expected_collator.is_invulnerable(),
1527                            loaded_collator.is_invulnerable()
1528                        );
1529                        assert_eq!(
1530                            expected_collator.initial_balance(),
1531                            loaded_collator.initial_balance()
1532                        );
1533                    });
1534            });
1535
1536        expected
1537            .hrmp_channels()
1538            .iter()
1539            .zip(load_from_toml.hrmp_channels().iter())
1540            .for_each(|(expected_hrmp_channel, loaded_hrmp_channel)| {
1541                assert_eq!(expected_hrmp_channel.sender(), loaded_hrmp_channel.sender());
1542                assert_eq!(
1543                    expected_hrmp_channel.recipient(),
1544                    loaded_hrmp_channel.recipient()
1545                );
1546                assert_eq!(
1547                    expected_hrmp_channel.max_capacity(),
1548                    loaded_hrmp_channel.max_capacity()
1549                );
1550                assert_eq!(
1551                    expected_hrmp_channel.max_message_size(),
1552                    loaded_hrmp_channel.max_message_size()
1553                );
1554            });
1555    }
1556
1557    #[test]
1558    fn the_toml_config_should_be_imported_and_match_a_network_with_overriden_defaults() {
1559        let load_from_toml =
1560            NetworkConfig::load_from_toml("./testing/snapshots/0002-overridden-defaults.toml")
1561                .unwrap();
1562
1563        let expected = NetworkConfigBuilder::new()
1564            .with_relaychain(|relaychain| {
1565                relaychain
1566                    .with_chain("polkadot")
1567                    .with_default_command("polkadot")
1568                    .with_default_image("docker.io/parity/polkadot:latest")
1569                    .with_default_args(vec![("-name", "value").into(), "--flag".into()])
1570                    .with_default_db_snapshot("https://storage.com/path/to/db_snapshot.tgz")
1571                    .with_default_resources(|resources| {
1572                        resources
1573                            .with_request_cpu(100000)
1574                            .with_request_memory("500M")
1575                            .with_limit_cpu("10Gi")
1576                            .with_limit_memory("4000M")
1577                    })
1578                    .with_validator(|node| {
1579                        node.with_name("alice")
1580                            .with_initial_balance(1_000_000_000)
1581                            .bootnode(true)
1582                            .invulnerable(true)
1583                    })
1584                    .with_validator(|node| {
1585                        node.with_name("bob")
1586                            .invulnerable(true)
1587                            .bootnode(true)
1588                            .with_image("mycustomimage:latest")
1589                            .with_command("my-custom-command")
1590                            .with_db_snapshot("https://storage.com/path/to/other/db_snapshot.tgz")
1591                            .with_resources(|resources| {
1592                                resources
1593                                    .with_request_cpu(1000)
1594                                    .with_request_memory("250Mi")
1595                                    .with_limit_cpu("5Gi")
1596                                    .with_limit_memory("2Gi")
1597                            })
1598                            .with_args(vec![("-myothername", "value").into()])
1599                    })
1600            })
1601            .with_parachain(|parachain| {
1602                parachain
1603                    .with_id(1000)
1604                    .with_chain("myparachain")
1605                    .with_chain_spec_path("/path/to/my/chain/spec.json")
1606                    .with_default_db_snapshot("https://storage.com/path/to/other_snapshot.tgz")
1607                    .with_default_command("my-default-command")
1608                    .with_default_image("mydefaultimage:latest")
1609                    .with_collator(|collator| {
1610                        collator
1611                            .with_name("john")
1612                            .bootnode(true)
1613                            .validator(true)
1614                            .invulnerable(true)
1615                            .with_initial_balance(5_000_000_000)
1616                            .with_command("my-non-default-command")
1617                            .with_image("anotherimage:latest")
1618                    })
1619                    .with_fullnode(|collator| {
1620                        collator
1621                            .with_name("charles")
1622                            .bootnode(true)
1623                            .invulnerable(true)
1624                            .with_initial_balance(0)
1625                    })
1626            })
1627            .build()
1628            .unwrap();
1629
1630        expected
1631            .parachains()
1632            .iter()
1633            .zip(load_from_toml.parachains().iter())
1634            .for_each(|(expected_parachain, loaded_parachain)| {
1635                assert_eq!(expected_parachain.id(), loaded_parachain.id());
1636                assert_eq!(expected_parachain.chain(), loaded_parachain.chain());
1637                assert_eq!(
1638                    expected_parachain.chain_spec_path(),
1639                    loaded_parachain.chain_spec_path()
1640                );
1641                assert_eq!(
1642                    expected_parachain.registration_strategy(),
1643                    loaded_parachain.registration_strategy()
1644                );
1645                assert_eq!(
1646                    expected_parachain.onboard_as_parachain(),
1647                    loaded_parachain.onboard_as_parachain()
1648                );
1649                assert_eq!(
1650                    expected_parachain.default_db_snapshot(),
1651                    loaded_parachain.default_db_snapshot()
1652                );
1653                assert_eq!(
1654                    expected_parachain.default_command(),
1655                    loaded_parachain.default_command()
1656                );
1657                assert_eq!(
1658                    expected_parachain.default_image(),
1659                    loaded_parachain.default_image()
1660                );
1661                assert_eq!(
1662                    expected_parachain.collators().len(),
1663                    loaded_parachain.collators().len()
1664                );
1665                expected_parachain
1666                    .collators()
1667                    .iter()
1668                    .zip(loaded_parachain.collators().iter())
1669                    .for_each(|(expected_collator, loaded_collator)| {
1670                        assert_eq!(expected_collator.name(), loaded_collator.name());
1671                        assert_eq!(expected_collator.command(), loaded_collator.command());
1672                        assert_eq!(expected_collator.image(), loaded_collator.image());
1673                        assert_eq!(
1674                            expected_collator.is_validator(),
1675                            loaded_collator.is_validator()
1676                        );
1677                        assert_eq!(
1678                            expected_collator.is_bootnode(),
1679                            loaded_collator.is_bootnode()
1680                        );
1681                        assert_eq!(
1682                            expected_collator.is_invulnerable(),
1683                            loaded_collator.is_invulnerable()
1684                        );
1685                        assert_eq!(
1686                            expected_collator.initial_balance(),
1687                            loaded_collator.initial_balance()
1688                        );
1689                    });
1690            });
1691    }
1692
1693    #[test]
1694    fn with_chain_and_nodes_works() {
1695        let network_config = NetworkConfigBuilder::with_chain_and_nodes(
1696            "rococo-local",
1697            vec!["alice".to_string(), "bob".to_string()],
1698        )
1699        .build()
1700        .unwrap();
1701
1702        // relaychain
1703        assert_eq!(network_config.relaychain().chain().as_str(), "rococo-local");
1704        assert_eq!(network_config.relaychain().nodes().len(), 2);
1705        let mut node_names = network_config.relaychain().nodes().into_iter();
1706        let node1 = node_names.next().unwrap().name();
1707        assert_eq!(node1, "alice");
1708        let node2 = node_names.next().unwrap().name();
1709        assert_eq!(node2, "bob");
1710
1711        // parachains
1712        assert_eq!(network_config.parachains().len(), 0);
1713    }
1714
1715    #[test]
1716    fn with_chain_and_nodes_should_fail_with_empty_relay_name() {
1717        let errors = NetworkConfigBuilder::with_chain_and_nodes("", vec!["alice".to_string()])
1718            .build()
1719            .unwrap_err();
1720
1721        assert_eq!(
1722            errors.first().unwrap().to_string(),
1723            "relaychain.chain: can't be empty"
1724        );
1725    }
1726
1727    #[test]
1728    fn with_chain_and_nodes_should_fail_with_empty_node_list() {
1729        let errors = NetworkConfigBuilder::with_chain_and_nodes("rococo-local", vec![])
1730            .build()
1731            .unwrap_err();
1732
1733        assert_eq!(
1734            errors.first().unwrap().to_string(),
1735            "relaychain.nodes[''].name: can't be empty"
1736        );
1737    }
1738
1739    #[test]
1740    fn with_chain_and_nodes_should_fail_with_empty_node_name() {
1741        let errors = NetworkConfigBuilder::with_chain_and_nodes(
1742            "rococo-local",
1743            vec!["alice".to_string(), "".to_string()],
1744        )
1745        .build()
1746        .unwrap_err();
1747
1748        assert_eq!(
1749            errors.first().unwrap().to_string(),
1750            "relaychain.nodes[''].name: can't be empty"
1751        );
1752    }
1753
1754    #[test]
1755    fn with_parachain_id_and_collators_works() {
1756        let network_config = NetworkConfigBuilder::with_chain_and_nodes(
1757            "rococo-local",
1758            vec!["alice".to_string(), "bob".to_string()],
1759        )
1760        .with_parachain_id_and_collators(
1761            100,
1762            vec!["collator1".to_string(), "collator2".to_string()],
1763        )
1764        .build()
1765        .unwrap();
1766
1767        // relaychain
1768        assert_eq!(network_config.relaychain().chain().as_str(), "rococo-local");
1769        assert_eq!(network_config.relaychain().nodes().len(), 2);
1770        let mut node_names = network_config.relaychain().nodes().into_iter();
1771        let node1 = node_names.next().unwrap().name();
1772        assert_eq!(node1, "alice");
1773        let node2 = node_names.next().unwrap().name();
1774        assert_eq!(node2, "bob");
1775
1776        // parachains
1777        assert_eq!(network_config.parachains().len(), 1);
1778        let &parachain1 = network_config.parachains().first().unwrap();
1779        assert_eq!(parachain1.id(), 100);
1780        assert_eq!(parachain1.collators().len(), 2);
1781        let mut collator_names = parachain1.collators().into_iter();
1782        let collator1 = collator_names.next().unwrap().name();
1783        assert_eq!(collator1, "collator1");
1784        let collator2 = collator_names.next().unwrap().name();
1785        assert_eq!(collator2, "collator2");
1786
1787        assert_eq!(parachain1.initial_balance(), 2_000_000_000_000);
1788    }
1789
1790    #[test]
1791    fn with_parachain_id_and_collators_should_fail_with_empty_collator_list() {
1792        let errors =
1793            NetworkConfigBuilder::with_chain_and_nodes("polkadot", vec!["alice".to_string()])
1794                .with_parachain_id_and_collators(1, vec![])
1795                .build()
1796                .unwrap_err();
1797
1798        assert_eq!(
1799            errors.first().unwrap().to_string(),
1800            "parachain[1].can't be empty"
1801        );
1802    }
1803
1804    #[test]
1805    fn with_parachain_id_and_collators_should_fail_with_empty_collator_name() {
1806        let errors =
1807            NetworkConfigBuilder::with_chain_and_nodes("polkadot", vec!["alice".to_string()])
1808                .with_parachain_id_and_collators(1, vec!["collator1".to_string(), "".to_string()])
1809                .build()
1810                .unwrap_err();
1811
1812        assert_eq!(
1813            errors.first().unwrap().to_string(),
1814            "parachain[1].collators[''].name: can't be empty"
1815        );
1816    }
1817
1818    #[test]
1819    fn wasm_override_in_toml_should_work() {
1820        let load_from_toml = NetworkConfig::load_from_toml(
1821            "./testing/snapshots/0005-small-networl-with-wasm-override.toml",
1822        )
1823        .unwrap();
1824
1825        let expected = NetworkConfigBuilder::new()
1826            .with_relaychain(|relaychain| {
1827                relaychain
1828                    .with_chain("rococo-local")
1829                    .with_default_command("polkadot")
1830                    .with_wasm_override("/some/path/runtime.wasm")
1831                    .with_validator(|node| node.with_name("alice"))
1832                    .with_validator(|node| node.with_name("bob"))
1833            })
1834            .with_parachain(|p| {
1835                p.with_id(1000)
1836                    .with_wasm_override("https://some.com/runtime.wasm")
1837                    .with_collator(|c| c.with_name("john"))
1838            })
1839            .build()
1840            .unwrap();
1841
1842        assert_eq!(
1843            load_from_toml.relaychain().wasm_override(),
1844            expected.relaychain().wasm_override()
1845        );
1846        assert_eq!(
1847            load_from_toml.parachains()[0].wasm_override(),
1848            expected.parachains()[0].wasm_override()
1849        );
1850    }
1851
1852    #[test]
1853    fn multiple_paras_with_same_id_should_work() {
1854        let network_config = NetworkConfigBuilder::new()
1855            .with_relaychain(|relaychain| {
1856                relaychain
1857                    .with_chain("polkadot")
1858                    .with_fullnode(|node| node.with_name("node").with_command("command"))
1859            })
1860            .with_parachain(|parachain| {
1861                parachain
1862                    .with_id(1)
1863                    .with_chain("myparachain1")
1864                    .with_collator(|collator| {
1865                        collator.with_name("collator1").with_command("command1")
1866                    })
1867            })
1868            .with_parachain(|parachain| {
1869                parachain
1870                    .with_id(1)
1871                    .with_chain("myparachain1")
1872                    .with_registration_strategy(RegistrationStrategy::Manual)
1873                    .with_collator(|collator| {
1874                        collator.with_name("collator2").with_command("command1")
1875                    })
1876            })
1877            .build()
1878            .unwrap();
1879
1880        let &parachain2 = network_config.parachains().last().unwrap();
1881        assert_eq!(parachain2.unique_id(), "1-1");
1882    }
1883
1884    #[test]
1885    fn multiple_paras_with_same_id_both_for_register_should_fail() {
1886        let errors = NetworkConfigBuilder::new()
1887            .with_relaychain(|relaychain| {
1888                relaychain
1889                    .with_chain("polkadot")
1890                    .with_fullnode(|node| node.with_name("node").with_command("command"))
1891            })
1892            .with_parachain(|parachain| {
1893                parachain
1894                    .with_id(1)
1895                    .with_chain("myparachain1")
1896                    .with_collator(|collator| {
1897                        collator.with_name("collator1").with_command("command1")
1898                    })
1899            })
1900            .with_parachain(|parachain| {
1901                parachain
1902                    .with_id(1)
1903                    .with_chain("myparachain1")
1904                    // .with_registration_strategy(RegistrationStrategy::UsingExtrinsic)
1905                    .with_collator(|collator| {
1906                        collator
1907                            .with_name("collator2")
1908                            .with_command("command1")
1909                    })
1910            })
1911            .build()
1912            .unwrap_err();
1913
1914        assert_eq!(
1915            errors.first().unwrap().to_string(),
1916            "ParaId 1 already set to be registered, only one should be."
1917        );
1918    }
1919
1920    #[test]
1921    fn network_config_should_work_from_toml_without_chain_name() {
1922        let loaded_from_toml =
1923            NetworkConfig::load_from_toml("./testing/snapshots/0006-without-rc-chain-name.toml")
1924                .unwrap();
1925
1926        assert_eq!(
1927            "rococo-local",
1928            loaded_from_toml.relaychain().chain().as_str()
1929        );
1930    }
1931
1932    #[test]
1933    fn network_config_should_work_from_toml_with_duplicate_name_between_collator_and_relay_node() {
1934        let loaded_from_toml = NetworkConfig::load_from_toml(
1935            "./testing/snapshots/0007-small-network_w_parachain_w_duplicate_node_names.toml",
1936        )
1937        .unwrap();
1938
1939        assert_eq!(
1940            loaded_from_toml
1941                .relaychain()
1942                .nodes()
1943                .iter()
1944                .filter(|n| n.name() == "alice")
1945                .count(),
1946            1
1947        );
1948        assert_eq!(
1949            loaded_from_toml
1950                .parachains()
1951                .iter()
1952                .flat_map(|para| para.collators())
1953                .filter(|n| n.name() == "alice-1")
1954                .count(),
1955            1
1956        );
1957    }
1958
1959    #[test]
1960    fn raw_spec_override_in_toml_should_work() {
1961        let load_from_toml = NetworkConfig::load_from_toml(
1962            "./testing/snapshots/0008-small-network-with-raw-spec-override.toml",
1963        )
1964        .unwrap();
1965
1966        let expected = NetworkConfigBuilder::new()
1967            .with_relaychain(|relaychain| {
1968                relaychain
1969                    .with_chain("rococo-local")
1970                    .with_default_command("polkadot")
1971                    .with_raw_spec_override("/some/path/raw_spec_override.json")
1972                    .with_validator(|node| node.with_name("alice"))
1973                    .with_validator(|node| node.with_name("bob"))
1974            })
1975            .with_parachain(|p| {
1976                p.with_id(1000)
1977                    .with_raw_spec_override("https://some.com/raw_spec_override.json")
1978                    .with_collator(|c| c.with_name("john"))
1979            })
1980            .build()
1981            .unwrap();
1982
1983        assert_eq!(
1984            load_from_toml.relaychain().raw_spec_override(),
1985            expected.relaychain().raw_spec_override()
1986        );
1987        assert_eq!(
1988            load_from_toml.parachains()[0].raw_spec_override(),
1989            expected.parachains()[0].raw_spec_override()
1990        );
1991    }
1992}