Skip to main content

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