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