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