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