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