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