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