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