zombienet_configuration/
network.rs

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