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