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