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