1use std::{cell::RefCell, error::Error, fmt::Debug, marker::PhantomData, rc::Rc};
2
3use serde::{Deserialize, Serialize};
4use support::constants::{DEFAULT_TYPESTATE, THIS_IS_A_BUG};
5
6use crate::{
7 shared::{
8 errors::{ConfigError, FieldError},
9 helpers::{merge_errors, merge_errors_vecs},
10 macros::states,
11 node::{self, GroupNodeConfig, GroupNodeConfigBuilder, NodeConfig, NodeConfigBuilder},
12 resources::{Resources, ResourcesBuilder},
13 types::{
14 Arg, AssetLocation, Chain, ChainDefaultContext, Command, Image, ValidationContext,
15 },
16 },
17 types::{ChainSpecRuntime, JsonOverrides},
18 utils::{default_command_polkadot, default_relaychain_chain, is_false},
19};
20
21#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
23pub struct RelaychainConfig {
24 #[serde(default = "default_relaychain_chain")]
25 chain: Chain,
26 #[serde(default = "default_command_polkadot")]
27 default_command: Option<Command>,
28 default_image: Option<Image>,
29 default_resources: Option<Resources>,
30 default_db_snapshot: Option<AssetLocation>,
31 #[serde(skip_serializing_if = "std::vec::Vec::is_empty", default)]
32 default_args: Vec<Arg>,
33 chain_spec_path: Option<AssetLocation>,
35 chain_spec_command: Option<String>,
39 chain_spec_runtime: Option<ChainSpecRuntime>,
42 #[serde(skip_serializing_if = "is_false", default)]
43 chain_spec_command_is_local: bool,
44 chain_spec_command_output_path: Option<String>,
45 random_nominators_count: Option<u32>,
46 max_nominations: Option<u8>,
47 #[serde(skip_serializing_if = "std::vec::Vec::is_empty", default)]
48 nodes: Vec<NodeConfig>,
49 #[serde(skip_serializing_if = "std::vec::Vec::is_empty", default)]
50 node_groups: Vec<GroupNodeConfig>,
51 #[serde(rename = "genesis", skip_serializing_if = "Option::is_none")]
52 runtime_genesis_patch: Option<serde_json::Value>,
53 wasm_override: Option<AssetLocation>,
55 command: Option<Command>,
56 raw_spec_override: Option<JsonOverrides>,
58 #[serde(skip_serializing_if = "Option::is_none")]
60 post_process_script: Option<String>,
61 #[serde(skip_serializing_if = "is_false", default)]
62 override_session_0: bool,
63}
64
65impl RelaychainConfig {
66 pub fn chain(&self) -> &Chain {
68 &self.chain
69 }
70
71 pub fn default_command(&self) -> Option<&Command> {
73 self.default_command.as_ref()
74 }
75
76 pub fn default_image(&self) -> Option<&Image> {
78 self.default_image.as_ref()
79 }
80
81 pub fn default_resources(&self) -> Option<&Resources> {
83 self.default_resources.as_ref()
84 }
85
86 pub fn default_db_snapshot(&self) -> Option<&AssetLocation> {
88 self.default_db_snapshot.as_ref()
89 }
90
91 pub fn default_args(&self) -> Vec<&Arg> {
93 self.default_args.iter().collect::<Vec<&Arg>>()
94 }
95
96 pub fn chain_spec_path(&self) -> Option<&AssetLocation> {
98 self.chain_spec_path.as_ref()
99 }
100
101 pub fn wasm_override(&self) -> Option<&AssetLocation> {
103 self.wasm_override.as_ref()
104 }
105
106 pub fn chain_spec_command(&self) -> Option<&str> {
108 self.chain_spec_command.as_deref()
109 }
110
111 pub fn chain_spec_command_is_local(&self) -> bool {
113 self.chain_spec_command_is_local
114 }
115
116 pub fn override_session_0(&self) -> bool {
117 self.override_session_0
118 }
119
120 pub fn chain_spec_command_output_path(&self) -> Option<&str> {
123 self.chain_spec_command_output_path.as_deref()
124 }
125
126 pub fn command(&self) -> Option<&Command> {
128 self.command.as_ref()
129 }
130
131 pub fn random_nominators_count(&self) -> Option<u32> {
133 self.random_nominators_count
134 }
135
136 pub fn max_nominations(&self) -> Option<u8> {
138 self.max_nominations
139 }
140
141 pub fn runtime_genesis_patch(&self) -> Option<&serde_json::Value> {
143 self.runtime_genesis_patch.as_ref()
144 }
145
146 pub fn nodes(&self) -> Vec<&NodeConfig> {
148 self.nodes.iter().collect::<Vec<&NodeConfig>>()
149 }
150
151 pub fn group_node_configs(&self) -> Vec<&GroupNodeConfig> {
153 self.node_groups.iter().collect::<Vec<&GroupNodeConfig>>()
154 }
155
156 pub fn raw_spec_override(&self) -> Option<&JsonOverrides> {
158 self.raw_spec_override.as_ref()
159 }
160
161 pub fn post_process_script(&self) -> Option<&str> {
163 self.post_process_script.as_deref()
164 }
165
166 pub(crate) fn set_nodes(&mut self, nodes: Vec<NodeConfig>) {
168 self.nodes = nodes;
169 }
170
171 pub fn chain_spec_runtime(&self) -> Option<&ChainSpecRuntime> {
173 self.chain_spec_runtime.as_ref()
174 }
175}
176
177states! {
178 Initial,
179 WithChain,
180 WithAtLeastOneNode
181}
182
183pub struct RelaychainConfigBuilder<State> {
185 config: RelaychainConfig,
186 validation_context: Rc<RefCell<ValidationContext>>,
187 errors: Vec<anyhow::Error>,
188 _state: PhantomData<State>,
189}
190
191impl Default for RelaychainConfigBuilder<Initial> {
192 fn default() -> Self {
193 Self {
194 config: RelaychainConfig {
195 chain: "default"
196 .try_into()
197 .expect(&format!("{DEFAULT_TYPESTATE} {THIS_IS_A_BUG}")),
198 default_command: None,
199 default_image: None,
200 default_resources: None,
201 default_db_snapshot: None,
202 default_args: vec![],
203 chain_spec_path: None,
204 chain_spec_command: None,
205 chain_spec_command_output_path: None,
206 chain_spec_runtime: None,
207 wasm_override: None,
208 chain_spec_command_is_local: false, command: None,
210 random_nominators_count: None,
211 max_nominations: None,
212 runtime_genesis_patch: None,
213 nodes: vec![],
214 node_groups: vec![],
215 raw_spec_override: None,
216 post_process_script: None,
217 override_session_0: false,
218 },
219 validation_context: Default::default(),
220 errors: vec![],
221 _state: PhantomData,
222 }
223 }
224}
225
226impl<A> RelaychainConfigBuilder<A> {
227 fn transition<B>(
228 config: RelaychainConfig,
229 validation_context: Rc<RefCell<ValidationContext>>,
230 errors: Vec<anyhow::Error>,
231 ) -> RelaychainConfigBuilder<B> {
232 RelaychainConfigBuilder {
233 config,
234 validation_context,
235 errors,
236 _state: PhantomData,
237 }
238 }
239
240 fn default_chain_context(&self) -> ChainDefaultContext {
241 ChainDefaultContext {
242 default_command: self.config.default_command.clone(),
243 default_image: self.config.default_image.clone(),
244 default_resources: self.config.default_resources.clone(),
245 default_db_snapshot: self.config.default_db_snapshot.clone(),
246 default_args: self.config.default_args.clone(),
247 }
248 }
249
250 fn create_node_builder<F>(&self, f: F) -> NodeConfigBuilder<node::Buildable>
251 where
252 F: FnOnce(NodeConfigBuilder<node::Initial>) -> NodeConfigBuilder<node::Buildable>,
253 {
254 f(NodeConfigBuilder::new(
255 self.default_chain_context(),
256 self.validation_context.clone(),
257 ))
258 }
259
260 pub fn with_post_process_script(mut self, script: impl Into<String>) -> Self {
262 self.config.post_process_script = Some(script.into());
263 self
264 }
265}
266
267impl RelaychainConfigBuilder<Initial> {
268 pub fn new(
269 validation_context: Rc<RefCell<ValidationContext>>,
270 ) -> RelaychainConfigBuilder<Initial> {
271 Self {
272 validation_context,
273 ..Self::default()
274 }
275 }
276
277 pub fn with_chain<T>(self, chain: T) -> RelaychainConfigBuilder<WithChain>
279 where
280 T: TryInto<Chain>,
281 T::Error: Error + Send + Sync + 'static,
282 {
283 match chain.try_into() {
284 Ok(chain) => Self::transition(
285 RelaychainConfig {
286 chain,
287 ..self.config
288 },
289 self.validation_context,
290 self.errors,
291 ),
292 Err(error) => Self::transition(
293 self.config,
294 self.validation_context,
295 merge_errors(self.errors, FieldError::Chain(error.into()).into()),
296 ),
297 }
298 }
299}
300
301impl RelaychainConfigBuilder<WithChain> {
302 pub fn with_default_command<T>(self, command: T) -> Self
304 where
305 T: TryInto<Command>,
306 T::Error: Error + Send + Sync + 'static,
307 {
308 match command.try_into() {
309 Ok(command) => Self::transition(
310 RelaychainConfig {
311 default_command: Some(command),
312 ..self.config
313 },
314 self.validation_context,
315 self.errors,
316 ),
317 Err(error) => Self::transition(
318 self.config,
319 self.validation_context,
320 merge_errors(self.errors, FieldError::DefaultCommand(error.into()).into()),
321 ),
322 }
323 }
324
325 pub fn with_default_image<T>(self, image: T) -> Self
327 where
328 T: TryInto<Image>,
329 T::Error: Error + Send + Sync + 'static,
330 {
331 match image.try_into() {
332 Ok(image) => Self::transition(
333 RelaychainConfig {
334 default_image: Some(image),
335 ..self.config
336 },
337 self.validation_context,
338 self.errors,
339 ),
340 Err(error) => Self::transition(
341 self.config,
342 self.validation_context,
343 merge_errors(self.errors, FieldError::DefaultImage(error.into()).into()),
344 ),
345 }
346 }
347
348 pub fn with_default_resources(
350 self,
351 f: impl FnOnce(ResourcesBuilder) -> ResourcesBuilder,
352 ) -> Self {
353 match f(ResourcesBuilder::new()).build() {
354 Ok(default_resources) => Self::transition(
355 RelaychainConfig {
356 default_resources: Some(default_resources),
357 ..self.config
358 },
359 self.validation_context,
360 self.errors,
361 ),
362 Err(errors) => Self::transition(
363 self.config,
364 self.validation_context,
365 merge_errors_vecs(
366 self.errors,
367 errors
368 .into_iter()
369 .map(|error| FieldError::DefaultResources(error).into())
370 .collect::<Vec<_>>(),
371 ),
372 ),
373 }
374 }
375
376 pub fn with_default_db_snapshot(self, location: impl Into<AssetLocation>) -> Self {
378 Self::transition(
379 RelaychainConfig {
380 default_db_snapshot: Some(location.into()),
381 ..self.config
382 },
383 self.validation_context,
384 self.errors,
385 )
386 }
387
388 pub fn with_default_args(self, args: Vec<Arg>) -> Self {
390 Self::transition(
391 RelaychainConfig {
392 default_args: args,
393 ..self.config
394 },
395 self.validation_context,
396 self.errors,
397 )
398 }
399
400 pub fn with_chain_spec_path(self, location: impl Into<AssetLocation>) -> Self {
402 Self::transition(
403 RelaychainConfig {
404 chain_spec_path: Some(location.into()),
405 ..self.config
406 },
407 self.validation_context,
408 self.errors,
409 )
410 }
411
412 pub fn with_wasm_override(self, location: impl Into<AssetLocation>) -> Self {
414 Self::transition(
415 RelaychainConfig {
416 wasm_override: Some(location.into()),
417 ..self.config
418 },
419 self.validation_context,
420 self.errors,
421 )
422 }
423
424 pub fn with_chain_spec_command(self, cmd_template: impl Into<String>) -> Self {
426 Self::transition(
427 RelaychainConfig {
428 chain_spec_command: Some(cmd_template.into()),
429 ..self.config
430 },
431 self.validation_context,
432 self.errors,
433 )
434 }
435
436 pub fn with_chain_spec_runtime(
440 self,
441 location: impl Into<AssetLocation>,
442 preset: Option<&str>,
443 ) -> Self {
444 let chain_spec_runtime = if let Some(preset) = preset {
445 ChainSpecRuntime::with_preset(location.into(), preset.to_string())
446 } else {
447 ChainSpecRuntime::new(location.into())
448 };
449 Self::transition(
450 RelaychainConfig {
451 chain_spec_runtime: Some(chain_spec_runtime),
452 ..self.config
453 },
454 self.validation_context,
455 self.errors,
456 )
457 }
458
459 pub fn chain_spec_command_is_local(self, choice: bool) -> Self {
461 Self::transition(
462 RelaychainConfig {
463 chain_spec_command_is_local: choice,
464 ..self.config
465 },
466 self.validation_context,
467 self.errors,
468 )
469 }
470
471 pub fn with_override_session_0(self, choice: bool) -> Self {
474 Self::transition(
475 RelaychainConfig {
476 override_session_0: choice,
477 ..self.config
478 },
479 self.validation_context,
480 self.errors,
481 )
482 }
483
484 pub fn with_chain_spec_command_output_path(self, output_path: &str) -> Self {
486 Self::transition(
487 RelaychainConfig {
488 chain_spec_command_output_path: Some(output_path.to_string()),
489 ..self.config
490 },
491 self.validation_context,
492 self.errors,
493 )
494 }
495
496 pub fn with_random_nominators_count(self, random_nominators_count: u32) -> Self {
498 Self::transition(
499 RelaychainConfig {
500 random_nominators_count: Some(random_nominators_count),
501 ..self.config
502 },
503 self.validation_context,
504 self.errors,
505 )
506 }
507
508 pub fn with_max_nominations(self, max_nominations: u8) -> Self {
510 Self::transition(
511 RelaychainConfig {
512 max_nominations: Some(max_nominations),
513 ..self.config
514 },
515 self.validation_context,
516 self.errors,
517 )
518 }
519
520 pub fn with_genesis_overrides(self, genesis_overrides: impl Into<serde_json::Value>) -> Self {
522 Self::transition(
523 RelaychainConfig {
524 runtime_genesis_patch: Some(genesis_overrides.into()),
525 ..self.config
526 },
527 self.validation_context,
528 self.errors,
529 )
530 }
531
532 pub fn with_validator(
535 self,
536 f: impl FnOnce(NodeConfigBuilder<node::Initial>) -> NodeConfigBuilder<node::Buildable>,
537 ) -> RelaychainConfigBuilder<WithAtLeastOneNode> {
538 match self.create_node_builder(f).validator(true).build() {
539 Ok(node) => Self::transition(
540 RelaychainConfig {
541 nodes: [self.config.nodes, vec![node]].concat(),
542 ..self.config
543 },
544 self.validation_context,
545 self.errors,
546 ),
547 Err((name, errors)) => Self::transition(
548 self.config,
549 self.validation_context,
550 merge_errors_vecs(
551 self.errors,
552 errors
553 .into_iter()
554 .map(|error| ConfigError::Node(name.clone(), error).into())
555 .collect::<Vec<_>>(),
556 ),
557 ),
558 }
559 }
560
561 pub fn with_fullnode(
564 self,
565 f: impl FnOnce(NodeConfigBuilder<node::Initial>) -> NodeConfigBuilder<node::Buildable>,
566 ) -> RelaychainConfigBuilder<WithAtLeastOneNode> {
567 match self.create_node_builder(f).validator(false).build() {
568 Ok(node) => Self::transition(
569 RelaychainConfig {
570 nodes: [self.config.nodes, vec![node]].concat(),
571 ..self.config
572 },
573 self.validation_context,
574 self.errors,
575 ),
576 Err((name, errors)) => Self::transition(
577 self.config,
578 self.validation_context,
579 merge_errors_vecs(
580 self.errors,
581 errors
582 .into_iter()
583 .map(|error| ConfigError::Node(name.clone(), error).into())
584 .collect::<Vec<_>>(),
585 ),
586 ),
587 }
588 }
589
590 #[deprecated(
594 since = "0.4.0",
595 note = "Use `with_validator()` for validator nodes or `with_fullnode()` for full nodes instead"
596 )]
597 pub fn with_node(
598 self,
599 f: impl FnOnce(NodeConfigBuilder<node::Initial>) -> NodeConfigBuilder<node::Buildable>,
600 ) -> RelaychainConfigBuilder<WithAtLeastOneNode> {
601 match self.create_node_builder(f).build() {
602 Ok(node) => Self::transition(
603 RelaychainConfig {
604 nodes: vec![node],
605 ..self.config
606 },
607 self.validation_context,
608 self.errors,
609 ),
610 Err((name, errors)) => Self::transition(
611 self.config,
612 self.validation_context,
613 merge_errors_vecs(
614 self.errors,
615 errors
616 .into_iter()
617 .map(|error| ConfigError::Node(name.clone(), error).into())
618 .collect::<Vec<_>>(),
619 ),
620 ),
621 }
622 }
623
624 pub fn with_node_group(
626 self,
627 f: impl FnOnce(GroupNodeConfigBuilder<node::Initial>) -> GroupNodeConfigBuilder<node::Buildable>,
628 ) -> RelaychainConfigBuilder<WithAtLeastOneNode> {
629 match f(GroupNodeConfigBuilder::new(
630 self.default_chain_context(),
631 self.validation_context.clone(),
632 ))
633 .build()
634 {
635 Ok(group_node) => Self::transition(
636 RelaychainConfig {
637 node_groups: vec![group_node],
638 ..self.config
639 },
640 self.validation_context,
641 self.errors,
642 ),
643 Err((name, errors)) => Self::transition(
644 self.config,
645 self.validation_context,
646 merge_errors_vecs(
647 self.errors,
648 errors
649 .into_iter()
650 .map(|error| ConfigError::Node(name.clone(), error).into())
651 .collect::<Vec<_>>(),
652 ),
653 ),
654 }
655 }
656
657 pub fn with_raw_spec_override(self, overrides: impl Into<JsonOverrides>) -> Self {
659 Self::transition(
660 RelaychainConfig {
661 raw_spec_override: Some(overrides.into()),
662 ..self.config
663 },
664 self.validation_context,
665 self.errors,
666 )
667 }
668}
669
670impl RelaychainConfigBuilder<WithAtLeastOneNode> {
671 pub fn with_validator(
674 self,
675 f: impl FnOnce(NodeConfigBuilder<node::Initial>) -> NodeConfigBuilder<node::Buildable>,
676 ) -> RelaychainConfigBuilder<WithAtLeastOneNode> {
677 match self.create_node_builder(f).validator(true).build() {
678 Ok(node) => Self::transition(
679 RelaychainConfig {
680 nodes: [self.config.nodes, vec![node]].concat(),
681 ..self.config
682 },
683 self.validation_context,
684 self.errors,
685 ),
686 Err((name, errors)) => Self::transition(
687 self.config,
688 self.validation_context,
689 merge_errors_vecs(
690 self.errors,
691 errors
692 .into_iter()
693 .map(|error| ConfigError::Node(name.clone(), error).into())
694 .collect::<Vec<_>>(),
695 ),
696 ),
697 }
698 }
699
700 pub fn with_fullnode(
703 self,
704 f: impl FnOnce(NodeConfigBuilder<node::Initial>) -> NodeConfigBuilder<node::Buildable>,
705 ) -> Self {
706 match self.create_node_builder(f).validator(false).build() {
707 Ok(node) => Self::transition(
708 RelaychainConfig {
709 nodes: [self.config.nodes, vec![node]].concat(),
710 ..self.config
711 },
712 self.validation_context,
713 self.errors,
714 ),
715 Err((name, errors)) => Self::transition(
716 self.config,
717 self.validation_context,
718 merge_errors_vecs(
719 self.errors,
720 errors
721 .into_iter()
722 .map(|error| ConfigError::Node(name.clone(), error).into())
723 .collect::<Vec<_>>(),
724 ),
725 ),
726 }
727 }
728
729 #[deprecated(
733 since = "0.4.0",
734 note = "Use `with_validator()` for validator nodes or `with_fullnode()` for full nodes instead"
735 )]
736 pub fn with_node(
737 self,
738 f: impl FnOnce(NodeConfigBuilder<node::Initial>) -> NodeConfigBuilder<node::Buildable>,
739 ) -> Self {
740 match self.create_node_builder(f).build() {
741 Ok(node) => Self::transition(
742 RelaychainConfig {
743 nodes: [self.config.nodes, vec![node]].concat(),
744 ..self.config
745 },
746 self.validation_context,
747 self.errors,
748 ),
749 Err((name, errors)) => Self::transition(
750 self.config,
751 self.validation_context,
752 merge_errors_vecs(
753 self.errors,
754 errors
755 .into_iter()
756 .map(|error| ConfigError::Node(name.clone(), error).into())
757 .collect::<Vec<_>>(),
758 ),
759 ),
760 }
761 }
762
763 pub fn with_node_group(
765 self,
766 f: impl FnOnce(GroupNodeConfigBuilder<node::Initial>) -> GroupNodeConfigBuilder<node::Buildable>,
767 ) -> Self {
768 match f(GroupNodeConfigBuilder::new(
769 self.default_chain_context(),
770 self.validation_context.clone(),
771 ))
772 .build()
773 {
774 Ok(group_node) => Self::transition(
775 RelaychainConfig {
776 node_groups: [self.config.node_groups, vec![group_node]].concat(),
777 ..self.config
778 },
779 self.validation_context,
780 self.errors,
781 ),
782 Err((name, errors)) => Self::transition(
783 self.config,
784 self.validation_context,
785 merge_errors_vecs(
786 self.errors,
787 errors
788 .into_iter()
789 .map(|error| ConfigError::Node(name.clone(), error).into())
790 .collect::<Vec<_>>(),
791 ),
792 ),
793 }
794 }
795
796 pub fn build(self) -> Result<RelaychainConfig, Vec<anyhow::Error>> {
798 if !self.errors.is_empty() {
799 return Err(self
800 .errors
801 .into_iter()
802 .map(|error| ConfigError::Relaychain(error).into())
803 .collect::<Vec<_>>());
804 }
805
806 Ok(self.config)
807 }
808}
809
810#[cfg(test)]
811mod tests {
812 use super::*;
813
814 #[test]
815 fn relaychain_config_builder_should_succeeds_and_returns_a_relaychain_config() {
816 let relaychain_config = RelaychainConfigBuilder::new(Default::default())
817 .with_chain("polkadot")
818 .with_default_image("myrepo:myimage")
819 .with_default_command("default_command")
820 .with_default_resources(|resources| {
821 resources
822 .with_limit_cpu("500M")
823 .with_limit_memory("1G")
824 .with_request_cpu("250M")
825 })
826 .with_default_db_snapshot("https://www.urltomysnapshot.com/file.tgz")
827 .with_chain_spec_path("./path/to/chain/spec.json")
828 .with_chain_spec_runtime("./path/to/runtime.wasm", Some("local_testnet"))
829 .with_wasm_override("./path/to/override/runtime.wasm")
830 .with_raw_spec_override(serde_json::json!({"some_override_key": "some_override_val"}))
831 .with_default_args(vec![("--arg1", "value1").into(), "--option2".into()])
832 .with_random_nominators_count(42)
833 .with_max_nominations(5)
834 .with_fullnode(|node| node.with_name("node1").bootnode(true))
835 .with_validator(|node| node.with_name("node2").with_command("command2"))
836 .build()
837 .unwrap();
838
839 assert_eq!(relaychain_config.chain().as_str(), "polkadot");
840 assert_eq!(relaychain_config.nodes().len(), 2);
841 let &node1 = relaychain_config.nodes().first().unwrap();
842 assert_eq!(node1.name(), "node1");
843 assert_eq!(node1.command().unwrap().as_str(), "default_command");
844 assert!(node1.is_bootnode());
845 let &node2 = relaychain_config.nodes().last().unwrap();
846 assert_eq!(node2.name(), "node2");
847 assert_eq!(node2.command().unwrap().as_str(), "command2");
848 assert!(node2.is_validator());
849 assert_eq!(
850 relaychain_config.default_command().unwrap().as_str(),
851 "default_command"
852 );
853 assert_eq!(
854 relaychain_config.default_image().unwrap().as_str(),
855 "myrepo:myimage"
856 );
857 let default_resources = relaychain_config.default_resources().unwrap();
858 assert_eq!(default_resources.limit_cpu().unwrap().as_str(), "500M");
859 assert_eq!(default_resources.limit_memory().unwrap().as_str(), "1G");
860 assert_eq!(default_resources.request_cpu().unwrap().as_str(), "250M");
861 assert!(matches!(
862 relaychain_config.default_db_snapshot().unwrap(),
863 AssetLocation::Url(value) if value.as_str() == "https://www.urltomysnapshot.com/file.tgz",
864 ));
865 assert!(matches!(
866 relaychain_config.chain_spec_path().unwrap(),
867 AssetLocation::FilePath(value) if value.to_str().unwrap() == "./path/to/chain/spec.json"
868 ));
869 assert!(matches!(
870 &relaychain_config.chain_spec_runtime().unwrap().location,
871 AssetLocation::FilePath(value) if value.to_str().unwrap() == "./path/to/runtime.wasm"
872 ));
873 assert_eq!(
874 relaychain_config
875 .chain_spec_runtime()
876 .unwrap()
877 .preset
878 .as_deref(),
879 Some("local_testnet")
880 );
881 assert!(matches!(
882 relaychain_config.wasm_override().unwrap(),
883 AssetLocation::FilePath(value) if value.to_str().unwrap() == "./path/to/override/runtime.wasm"
884 ));
885 let args: Vec<Arg> = vec![("--arg1", "value1").into(), "--option2".into()];
886 assert_eq!(
887 relaychain_config.default_args(),
888 args.iter().collect::<Vec<_>>()
889 );
890 assert_eq!(relaychain_config.random_nominators_count().unwrap(), 42);
891 assert_eq!(relaychain_config.max_nominations().unwrap(), 5);
892
893 assert!(matches!(
894 relaychain_config.raw_spec_override().unwrap(),
895 JsonOverrides::Json(value) if *value == serde_json::json!({"some_override_key": "some_override_val"})
896 ));
897 }
898
899 #[test]
900 fn relaychain_config_builder_should_fails_and_returns_an_error_if_chain_is_invalid() {
901 let errors = RelaychainConfigBuilder::new(Default::default())
902 .with_chain("invalid chain")
903 .with_validator(|node| node.with_name("node").with_command("command"))
904 .build()
905 .unwrap_err();
906
907 assert_eq!(errors.len(), 1);
908 assert_eq!(
909 errors.first().unwrap().to_string(),
910 "relaychain.chain: 'invalid chain' shouldn't contains whitespace"
911 );
912 }
913
914 #[test]
915 fn relaychain_config_builder_should_fails_and_returns_an_error_if_default_command_is_invalid() {
916 let errors = RelaychainConfigBuilder::new(Default::default())
917 .with_chain("chain")
918 .with_default_command("invalid command")
919 .with_validator(|node| node.with_name("node").with_command("command"))
920 .build()
921 .unwrap_err();
922
923 assert_eq!(errors.len(), 1);
924 assert_eq!(
925 errors.first().unwrap().to_string(),
926 "relaychain.default_command: 'invalid command' shouldn't contains whitespace"
927 );
928 }
929
930 #[test]
931 fn relaychain_config_builder_should_fails_and_returns_an_error_if_default_image_is_invalid() {
932 let errors = RelaychainConfigBuilder::new(Default::default())
933 .with_chain("chain")
934 .with_default_image("invalid image")
935 .with_validator(|node| node.with_name("node").with_command("command"))
936 .build()
937 .unwrap_err();
938
939 assert_eq!(errors.len(), 1);
940 assert_eq!(
941 errors.first().unwrap().to_string(),
942 r"relaychain.default_image: 'invalid image' doesn't match regex '^([ip]|[hostname]/)?[tag_name]:[tag_version]?$'"
943 );
944 }
945
946 #[test]
947 fn relaychain_config_builder_should_fails_and_returns_an_error_if_default_resources_are_invalid(
948 ) {
949 let errors = RelaychainConfigBuilder::new(Default::default())
950 .with_chain("chain")
951 .with_default_resources(|default_resources| {
952 default_resources
953 .with_limit_memory("100m")
954 .with_request_cpu("invalid")
955 })
956 .with_validator(|node| node.with_name("node").with_command("command"))
957 .build()
958 .unwrap_err();
959
960 assert_eq!(errors.len(), 1);
961 assert_eq!(
962 errors.first().unwrap().to_string(),
963 r"relaychain.default_resources.request_cpu: 'invalid' doesn't match regex '^\d+(.\d+)?(m|K|M|G|T|P|E|Ki|Mi|Gi|Ti|Pi|Ei)?$'"
964 );
965 }
966
967 #[test]
968 fn relaychain_config_builder_should_fails_and_returns_an_error_if_first_node_is_invalid() {
969 let errors = RelaychainConfigBuilder::new(Default::default())
970 .with_chain("chain")
971 .with_validator(|node| node.with_name("node").with_command("invalid command"))
972 .build()
973 .unwrap_err();
974
975 assert_eq!(errors.len(), 1);
976 assert_eq!(
977 errors.first().unwrap().to_string(),
978 "relaychain.nodes['node'].command: 'invalid command' shouldn't contains whitespace"
979 );
980 }
981
982 #[test]
983 fn relaychain_config_builder_with_at_least_one_node_should_fails_and_returns_an_error_if_second_node_is_invalid(
984 ) {
985 let errors = RelaychainConfigBuilder::new(Default::default())
986 .with_chain("chain")
987 .with_validator(|node| node.with_name("node1").with_command("command1"))
988 .with_validator(|node| node.with_name("node2").with_command("invalid command"))
989 .build()
990 .unwrap_err();
991
992 assert_eq!(errors.len(), 1);
993 assert_eq!(
994 errors.first().unwrap().to_string(),
995 "relaychain.nodes['node2'].command: 'invalid command' shouldn't contains whitespace"
996 );
997 }
998
999 #[test]
1000 fn relaychain_config_builder_should_fails_returns_multiple_errors_if_a_node_and_default_resources_are_invalid(
1001 ) {
1002 let errors = RelaychainConfigBuilder::new(Default::default())
1003 .with_chain("chain")
1004 .with_default_resources(|resources| {
1005 resources
1006 .with_request_cpu("100Mi")
1007 .with_limit_memory("1Gi")
1008 .with_limit_cpu("invalid")
1009 })
1010 .with_validator(|node| node.with_name("node").with_image("invalid image"))
1011 .build()
1012 .unwrap_err();
1013
1014 assert_eq!(errors.len(), 2);
1015 assert_eq!(
1016 errors.first().unwrap().to_string(),
1017 "relaychain.default_resources.limit_cpu: 'invalid' doesn't match regex '^\\d+(.\\d+)?(m|K|M|G|T|P|E|Ki|Mi|Gi|Ti|Pi|Ei)?$'"
1018 );
1019 assert_eq!(
1020 errors.get(1).unwrap().to_string(),
1021 "relaychain.nodes['node'].image: 'invalid image' doesn't match regex '^([ip]|[hostname]/)?[tag_name]:[tag_version]?$'"
1022 );
1023 }
1024
1025 #[test]
1026 fn relaychain_config_builder_should_works_with_chain_spec_command() {
1027 const CMD_TPL: &str = "./bin/chain-spec-generator {% raw %} {{chainName}} {% endraw %}";
1028 let config = RelaychainConfigBuilder::new(Default::default())
1029 .with_chain("polkadot")
1030 .with_default_image("myrepo:myimage")
1031 .with_default_command("default_command")
1032 .with_chain_spec_command(CMD_TPL)
1033 .with_fullnode(|node| node.with_name("node1").bootnode(true))
1034 .build()
1035 .unwrap();
1036
1037 assert_eq!(config.chain_spec_command(), Some(CMD_TPL));
1038 assert!(!config.chain_spec_command_is_local());
1039 }
1040
1041 #[test]
1042 fn relaychain_config_builder_should_works_with_chain_spec_command_locally() {
1043 const CMD_TPL: &str = "./bin/chain-spec-generator {% raw %} {{chainName}} {% endraw %}";
1044 let config = RelaychainConfigBuilder::new(Default::default())
1045 .with_chain("polkadot")
1046 .with_default_image("myrepo:myimage")
1047 .with_default_command("default_command")
1048 .with_chain_spec_command(CMD_TPL)
1049 .chain_spec_command_is_local(true)
1050 .with_fullnode(|node| node.with_name("node1").bootnode(true))
1051 .build()
1052 .unwrap();
1053
1054 assert_eq!(config.chain_spec_command(), Some(CMD_TPL));
1055 assert!(config.chain_spec_command_is_local());
1056 }
1057
1058 #[test]
1059 fn relaychain_with_group_config_should_succeeds_and_returns_a_relaychain_config() {
1060 let relaychain_config = RelaychainConfigBuilder::new(Default::default())
1061 .with_chain("chain")
1062 .with_default_command("command")
1063 .with_validator(|node| node.with_name("node").with_command("node_command"))
1064 .with_node_group(|group| {
1065 group.with_count(2).with_base_node(|base| {
1066 base.with_name("group_node")
1067 .with_command("some_command")
1068 .with_image("repo:image")
1069 .validator(true)
1070 })
1071 })
1072 .build()
1073 .unwrap();
1074
1075 assert_eq!(relaychain_config.chain().as_str(), "chain");
1076 assert_eq!(relaychain_config.nodes().len(), 1);
1077 assert_eq!(relaychain_config.group_node_configs().len(), 1);
1078 assert_eq!(
1079 relaychain_config
1080 .group_node_configs()
1081 .first()
1082 .unwrap()
1083 .count,
1084 2
1085 );
1086 let &node = relaychain_config.nodes().first().unwrap();
1087 assert_eq!(node.name(), "node");
1088 assert_eq!(node.command().unwrap().as_str(), "node_command");
1089
1090 let group_nodes = relaychain_config.group_node_configs();
1091 let group_base_node = group_nodes.first().unwrap();
1092 assert_eq!(group_base_node.base_config.name(), "group_node");
1093 assert_eq!(
1094 group_base_node.base_config.command().unwrap().as_str(),
1095 "some_command"
1096 );
1097 assert_eq!(
1098 group_base_node.base_config.image().unwrap().as_str(),
1099 "repo:image"
1100 );
1101 assert!(group_base_node.base_config.is_validator());
1102 }
1103
1104 #[test]
1105 fn relaychain_with_group_count_0_config_should_fail() {
1106 let relaychain_config = RelaychainConfigBuilder::new(Default::default())
1107 .with_chain("chain")
1108 .with_default_command("command")
1109 .with_validator(|node| node.with_name("node").with_command("node_command"))
1110 .with_node_group(|group| {
1111 group.with_count(0).with_base_node(|base| {
1112 base.with_name("group_node")
1113 .with_command("some_command")
1114 .with_image("repo:image")
1115 .validator(true)
1116 })
1117 })
1118 .build();
1119
1120 let errors: Vec<anyhow::Error> = match relaychain_config {
1121 Ok(_) => vec![],
1122 Err(errs) => errs,
1123 };
1124
1125 assert_eq!(errors.len(), 1);
1126 assert_eq!(
1127 errors.first().unwrap().to_string(),
1128 "relaychain.nodes['group_node'].Count cannot be zero"
1129 );
1130 }
1131}