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