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