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