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_optional_default_db_snapshot(
392 self,
393 location: Option<impl Into<AssetLocation>>,
394 ) -> Self {
395 match location {
396 Some(location) => self.with_default_db_snapshot(location),
397 None => self,
398 }
399 }
400
401 pub fn with_default_args(self, args: Vec<Arg>) -> Self {
403 Self::transition(
404 RelaychainConfig {
405 default_args: args,
406 ..self.config
407 },
408 self.validation_context,
409 self.errors,
410 )
411 }
412
413 pub fn with_chain_spec_path(self, location: impl Into<AssetLocation>) -> Self {
415 Self::transition(
416 RelaychainConfig {
417 chain_spec_path: Some(location.into()),
418 ..self.config
419 },
420 self.validation_context,
421 self.errors,
422 )
423 }
424
425 pub fn with_wasm_override(self, location: impl Into<AssetLocation>) -> Self {
427 Self::transition(
428 RelaychainConfig {
429 wasm_override: Some(location.into()),
430 ..self.config
431 },
432 self.validation_context,
433 self.errors,
434 )
435 }
436
437 pub fn with_chain_spec_command(self, cmd_template: impl Into<String>) -> Self {
439 Self::transition(
440 RelaychainConfig {
441 chain_spec_command: Some(cmd_template.into()),
442 ..self.config
443 },
444 self.validation_context,
445 self.errors,
446 )
447 }
448
449 pub fn with_chain_spec_runtime(
453 self,
454 location: impl Into<AssetLocation>,
455 preset: Option<&str>,
456 ) -> Self {
457 let chain_spec_runtime = if let Some(preset) = preset {
458 ChainSpecRuntime::with_preset(location.into(), preset.to_string())
459 } else {
460 ChainSpecRuntime::new(location.into())
461 };
462 Self::transition(
463 RelaychainConfig {
464 chain_spec_runtime: Some(chain_spec_runtime),
465 ..self.config
466 },
467 self.validation_context,
468 self.errors,
469 )
470 }
471
472 pub fn chain_spec_command_is_local(self, choice: bool) -> Self {
474 Self::transition(
475 RelaychainConfig {
476 chain_spec_command_is_local: choice,
477 ..self.config
478 },
479 self.validation_context,
480 self.errors,
481 )
482 }
483
484 pub fn with_override_session_0(self, choice: bool) -> Self {
487 Self::transition(
488 RelaychainConfig {
489 override_session_0: choice,
490 ..self.config
491 },
492 self.validation_context,
493 self.errors,
494 )
495 }
496
497 pub fn with_chain_spec_command_output_path(self, output_path: &str) -> Self {
499 Self::transition(
500 RelaychainConfig {
501 chain_spec_command_output_path: Some(output_path.to_string()),
502 ..self.config
503 },
504 self.validation_context,
505 self.errors,
506 )
507 }
508
509 pub fn with_random_nominators_count(self, random_nominators_count: u32) -> Self {
511 Self::transition(
512 RelaychainConfig {
513 random_nominators_count: Some(random_nominators_count),
514 ..self.config
515 },
516 self.validation_context,
517 self.errors,
518 )
519 }
520
521 pub fn with_max_nominations(self, max_nominations: u8) -> Self {
523 Self::transition(
524 RelaychainConfig {
525 max_nominations: Some(max_nominations),
526 ..self.config
527 },
528 self.validation_context,
529 self.errors,
530 )
531 }
532
533 pub fn with_genesis_overrides(self, genesis_overrides: impl Into<serde_json::Value>) -> Self {
535 Self::transition(
536 RelaychainConfig {
537 runtime_genesis_patch: Some(genesis_overrides.into()),
538 ..self.config
539 },
540 self.validation_context,
541 self.errors,
542 )
543 }
544
545 pub fn with_validator(
548 self,
549 f: impl FnOnce(NodeConfigBuilder<node::Initial>) -> NodeConfigBuilder<node::Buildable>,
550 ) -> RelaychainConfigBuilder<WithAtLeastOneNode> {
551 match self.create_node_builder(f).validator(true).build() {
552 Ok(node) => Self::transition(
553 RelaychainConfig {
554 nodes: [self.config.nodes, vec![node]].concat(),
555 ..self.config
556 },
557 self.validation_context,
558 self.errors,
559 ),
560 Err((name, errors)) => Self::transition(
561 self.config,
562 self.validation_context,
563 merge_errors_vecs(
564 self.errors,
565 errors
566 .into_iter()
567 .map(|error| ConfigError::Node(name.clone(), error).into())
568 .collect::<Vec<_>>(),
569 ),
570 ),
571 }
572 }
573
574 pub fn with_fullnode(
577 self,
578 f: impl FnOnce(NodeConfigBuilder<node::Initial>) -> NodeConfigBuilder<node::Buildable>,
579 ) -> RelaychainConfigBuilder<WithAtLeastOneNode> {
580 match self.create_node_builder(f).validator(false).build() {
581 Ok(node) => Self::transition(
582 RelaychainConfig {
583 nodes: [self.config.nodes, vec![node]].concat(),
584 ..self.config
585 },
586 self.validation_context,
587 self.errors,
588 ),
589 Err((name, errors)) => Self::transition(
590 self.config,
591 self.validation_context,
592 merge_errors_vecs(
593 self.errors,
594 errors
595 .into_iter()
596 .map(|error| ConfigError::Node(name.clone(), error).into())
597 .collect::<Vec<_>>(),
598 ),
599 ),
600 }
601 }
602
603 #[deprecated(
607 since = "0.4.0",
608 note = "Use `with_validator()` for validator nodes or `with_fullnode()` for full nodes instead"
609 )]
610 pub fn with_node(
611 self,
612 f: impl FnOnce(NodeConfigBuilder<node::Initial>) -> NodeConfigBuilder<node::Buildable>,
613 ) -> RelaychainConfigBuilder<WithAtLeastOneNode> {
614 match self.create_node_builder(f).build() {
615 Ok(node) => Self::transition(
616 RelaychainConfig {
617 nodes: vec![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_node_group(
639 self,
640 f: impl FnOnce(GroupNodeConfigBuilder<node::Initial>) -> GroupNodeConfigBuilder<node::Buildable>,
641 ) -> RelaychainConfigBuilder<WithAtLeastOneNode> {
642 match f(GroupNodeConfigBuilder::new(
643 self.default_chain_context(),
644 self.validation_context.clone(),
645 ))
646 .build()
647 {
648 Ok(group_node) => Self::transition(
649 RelaychainConfig {
650 node_groups: vec![group_node],
651 ..self.config
652 },
653 self.validation_context,
654 self.errors,
655 ),
656 Err((name, errors)) => Self::transition(
657 self.config,
658 self.validation_context,
659 merge_errors_vecs(
660 self.errors,
661 errors
662 .into_iter()
663 .map(|error| ConfigError::Node(name.clone(), error).into())
664 .collect::<Vec<_>>(),
665 ),
666 ),
667 }
668 }
669
670 pub fn with_raw_spec_override(self, overrides: impl Into<JsonOverrides>) -> Self {
672 Self::transition(
673 RelaychainConfig {
674 raw_spec_override: Some(overrides.into()),
675 ..self.config
676 },
677 self.validation_context,
678 self.errors,
679 )
680 }
681}
682
683impl RelaychainConfigBuilder<WithAtLeastOneNode> {
684 pub fn with_validator(
687 self,
688 f: impl FnOnce(NodeConfigBuilder<node::Initial>) -> NodeConfigBuilder<node::Buildable>,
689 ) -> RelaychainConfigBuilder<WithAtLeastOneNode> {
690 match self.create_node_builder(f).validator(true).build() {
691 Ok(node) => Self::transition(
692 RelaychainConfig {
693 nodes: [self.config.nodes, vec![node]].concat(),
694 ..self.config
695 },
696 self.validation_context,
697 self.errors,
698 ),
699 Err((name, errors)) => Self::transition(
700 self.config,
701 self.validation_context,
702 merge_errors_vecs(
703 self.errors,
704 errors
705 .into_iter()
706 .map(|error| ConfigError::Node(name.clone(), error).into())
707 .collect::<Vec<_>>(),
708 ),
709 ),
710 }
711 }
712
713 pub fn with_fullnode(
716 self,
717 f: impl FnOnce(NodeConfigBuilder<node::Initial>) -> NodeConfigBuilder<node::Buildable>,
718 ) -> Self {
719 match self.create_node_builder(f).validator(false).build() {
720 Ok(node) => Self::transition(
721 RelaychainConfig {
722 nodes: [self.config.nodes, vec![node]].concat(),
723 ..self.config
724 },
725 self.validation_context,
726 self.errors,
727 ),
728 Err((name, errors)) => Self::transition(
729 self.config,
730 self.validation_context,
731 merge_errors_vecs(
732 self.errors,
733 errors
734 .into_iter()
735 .map(|error| ConfigError::Node(name.clone(), error).into())
736 .collect::<Vec<_>>(),
737 ),
738 ),
739 }
740 }
741
742 #[deprecated(
746 since = "0.4.0",
747 note = "Use `with_validator()` for validator nodes or `with_fullnode()` for full nodes instead"
748 )]
749 pub fn with_node(
750 self,
751 f: impl FnOnce(NodeConfigBuilder<node::Initial>) -> NodeConfigBuilder<node::Buildable>,
752 ) -> Self {
753 match self.create_node_builder(f).build() {
754 Ok(node) => Self::transition(
755 RelaychainConfig {
756 nodes: [self.config.nodes, vec![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 with_node_group(
778 self,
779 f: impl FnOnce(GroupNodeConfigBuilder<node::Initial>) -> GroupNodeConfigBuilder<node::Buildable>,
780 ) -> Self {
781 match f(GroupNodeConfigBuilder::new(
782 self.default_chain_context(),
783 self.validation_context.clone(),
784 ))
785 .build()
786 {
787 Ok(group_node) => Self::transition(
788 RelaychainConfig {
789 node_groups: [self.config.node_groups, vec![group_node]].concat(),
790 ..self.config
791 },
792 self.validation_context,
793 self.errors,
794 ),
795 Err((name, errors)) => Self::transition(
796 self.config,
797 self.validation_context,
798 merge_errors_vecs(
799 self.errors,
800 errors
801 .into_iter()
802 .map(|error| ConfigError::Node(name.clone(), error).into())
803 .collect::<Vec<_>>(),
804 ),
805 ),
806 }
807 }
808
809 pub fn build(self) -> Result<RelaychainConfig, Vec<anyhow::Error>> {
811 if !self.errors.is_empty() {
812 return Err(self
813 .errors
814 .into_iter()
815 .map(|error| ConfigError::Relaychain(error).into())
816 .collect::<Vec<_>>());
817 }
818
819 Ok(self.config)
820 }
821}
822
823#[cfg(test)]
824mod tests {
825 use super::*;
826
827 #[test]
828 fn relaychain_config_builder_should_succeeds_and_returns_a_relaychain_config() {
829 let relaychain_config = RelaychainConfigBuilder::new(Default::default())
830 .with_chain("polkadot")
831 .with_default_image("myrepo:myimage")
832 .with_default_command("default_command")
833 .with_default_resources(|resources| {
834 resources
835 .with_limit_cpu("500M")
836 .with_limit_memory("1G")
837 .with_request_cpu("250M")
838 })
839 .with_default_db_snapshot("https://www.urltomysnapshot.com/file.tgz")
840 .with_chain_spec_path("./path/to/chain/spec.json")
841 .with_chain_spec_runtime("./path/to/runtime.wasm", Some("local_testnet"))
842 .with_wasm_override("./path/to/override/runtime.wasm")
843 .with_raw_spec_override(serde_json::json!({"some_override_key": "some_override_val"}))
844 .with_default_args(vec![("--arg1", "value1").into(), "--option2".into()])
845 .with_random_nominators_count(42)
846 .with_max_nominations(5)
847 .with_fullnode(|node| node.with_name("node1").bootnode(true))
848 .with_validator(|node| node.with_name("node2").with_command("command2"))
849 .build()
850 .unwrap();
851
852 assert_eq!(relaychain_config.chain().as_str(), "polkadot");
853 assert_eq!(relaychain_config.nodes().len(), 2);
854 let &node1 = relaychain_config.nodes().first().unwrap();
855 assert_eq!(node1.name(), "node1");
856 assert_eq!(node1.command().unwrap().as_str(), "default_command");
857 assert!(node1.is_bootnode());
858 let &node2 = relaychain_config.nodes().last().unwrap();
859 assert_eq!(node2.name(), "node2");
860 assert_eq!(node2.command().unwrap().as_str(), "command2");
861 assert!(node2.is_validator());
862 assert_eq!(
863 relaychain_config.default_command().unwrap().as_str(),
864 "default_command"
865 );
866 assert_eq!(
867 relaychain_config.default_image().unwrap().as_str(),
868 "myrepo:myimage"
869 );
870 let default_resources = relaychain_config.default_resources().unwrap();
871 assert_eq!(default_resources.limit_cpu().unwrap().as_str(), "500M");
872 assert_eq!(default_resources.limit_memory().unwrap().as_str(), "1G");
873 assert_eq!(default_resources.request_cpu().unwrap().as_str(), "250M");
874 assert!(matches!(
875 relaychain_config.default_db_snapshot().unwrap(),
876 AssetLocation::Url(value) if value.as_str() == "https://www.urltomysnapshot.com/file.tgz",
877 ));
878 assert!(matches!(
879 relaychain_config.chain_spec_path().unwrap(),
880 AssetLocation::FilePath(value) if value.to_str().unwrap() == "./path/to/chain/spec.json"
881 ));
882 assert!(matches!(
883 &relaychain_config.chain_spec_runtime().unwrap().location,
884 AssetLocation::FilePath(value) if value.to_str().unwrap() == "./path/to/runtime.wasm"
885 ));
886 assert_eq!(
887 relaychain_config
888 .chain_spec_runtime()
889 .unwrap()
890 .preset
891 .as_deref(),
892 Some("local_testnet")
893 );
894 assert!(matches!(
895 relaychain_config.wasm_override().unwrap(),
896 AssetLocation::FilePath(value) if value.to_str().unwrap() == "./path/to/override/runtime.wasm"
897 ));
898 let args: Vec<Arg> = vec![("--arg1", "value1").into(), "--option2".into()];
899 assert_eq!(
900 relaychain_config.default_args(),
901 args.iter().collect::<Vec<_>>()
902 );
903 assert_eq!(relaychain_config.random_nominators_count().unwrap(), 42);
904 assert_eq!(relaychain_config.max_nominations().unwrap(), 5);
905
906 assert!(matches!(
907 relaychain_config.raw_spec_override().unwrap(),
908 JsonOverrides::Json(value) if *value == serde_json::json!({"some_override_key": "some_override_val"})
909 ));
910 }
911
912 #[test]
913 fn with_optional_default_db_snapshot_applies_when_some() {
914 let config = RelaychainConfigBuilder::new(Default::default())
915 .with_chain("polkadot")
916 .with_default_command("cmd")
917 .with_optional_default_db_snapshot(Some("https://example.com/snap.tgz"))
918 .with_validator(|node| node.with_name("alice"))
919 .build()
920 .unwrap();
921 assert!(matches!(
922 config.default_db_snapshot().unwrap(),
923 AssetLocation::Url(value) if value.as_str() == "https://example.com/snap.tgz"
924 ));
925 }
926
927 #[test]
928 fn with_optional_default_db_snapshot_is_noop_when_none() {
929 let config = RelaychainConfigBuilder::new(Default::default())
930 .with_chain("polkadot")
931 .with_default_command("cmd")
932 .with_optional_default_db_snapshot(None::<&str>)
933 .with_validator(|node| node.with_name("alice"))
934 .build()
935 .unwrap();
936 assert!(config.default_db_snapshot().is_none());
937 }
938
939 #[test]
940 fn relaychain_config_builder_should_fails_and_returns_an_error_if_chain_is_invalid() {
941 let errors = RelaychainConfigBuilder::new(Default::default())
942 .with_chain("invalid chain")
943 .with_validator(|node| node.with_name("node").with_command("command"))
944 .build()
945 .unwrap_err();
946
947 assert_eq!(errors.len(), 1);
948 assert_eq!(
949 errors.first().unwrap().to_string(),
950 "relaychain.chain: 'invalid chain' shouldn't contains whitespace"
951 );
952 }
953
954 #[test]
955 fn relaychain_config_builder_should_fails_and_returns_an_error_if_default_command_is_invalid() {
956 let errors = RelaychainConfigBuilder::new(Default::default())
957 .with_chain("chain")
958 .with_default_command("invalid command")
959 .with_validator(|node| node.with_name("node").with_command("command"))
960 .build()
961 .unwrap_err();
962
963 assert_eq!(errors.len(), 1);
964 assert_eq!(
965 errors.first().unwrap().to_string(),
966 "relaychain.default_command: 'invalid command' shouldn't contains whitespace"
967 );
968 }
969
970 #[test]
971 fn relaychain_config_builder_should_fails_and_returns_an_error_if_default_image_is_invalid() {
972 let errors = RelaychainConfigBuilder::new(Default::default())
973 .with_chain("chain")
974 .with_default_image("invalid image")
975 .with_validator(|node| node.with_name("node").with_command("command"))
976 .build()
977 .unwrap_err();
978
979 assert_eq!(errors.len(), 1);
980 assert_eq!(
981 errors.first().unwrap().to_string(),
982 r"relaychain.default_image: 'invalid image' doesn't match regex '^([ip]|[hostname]/)?[tag_name]:[tag_version]?$'"
983 );
984 }
985
986 #[test]
987 fn relaychain_config_builder_should_fails_and_returns_an_error_if_default_resources_are_invalid(
988 ) {
989 let errors = RelaychainConfigBuilder::new(Default::default())
990 .with_chain("chain")
991 .with_default_resources(|default_resources| {
992 default_resources
993 .with_limit_memory("100m")
994 .with_request_cpu("invalid")
995 })
996 .with_validator(|node| node.with_name("node").with_command("command"))
997 .build()
998 .unwrap_err();
999
1000 assert_eq!(errors.len(), 1);
1001 assert_eq!(
1002 errors.first().unwrap().to_string(),
1003 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)?$'"
1004 );
1005 }
1006
1007 #[test]
1008 fn relaychain_config_builder_should_fails_and_returns_an_error_if_first_node_is_invalid() {
1009 let errors = RelaychainConfigBuilder::new(Default::default())
1010 .with_chain("chain")
1011 .with_validator(|node| node.with_name("node").with_command("invalid command"))
1012 .build()
1013 .unwrap_err();
1014
1015 assert_eq!(errors.len(), 1);
1016 assert_eq!(
1017 errors.first().unwrap().to_string(),
1018 "relaychain.nodes['node'].command: 'invalid command' shouldn't contains whitespace"
1019 );
1020 }
1021
1022 #[test]
1023 fn relaychain_config_builder_with_at_least_one_node_should_fails_and_returns_an_error_if_second_node_is_invalid(
1024 ) {
1025 let errors = RelaychainConfigBuilder::new(Default::default())
1026 .with_chain("chain")
1027 .with_validator(|node| node.with_name("node1").with_command("command1"))
1028 .with_validator(|node| node.with_name("node2").with_command("invalid command"))
1029 .build()
1030 .unwrap_err();
1031
1032 assert_eq!(errors.len(), 1);
1033 assert_eq!(
1034 errors.first().unwrap().to_string(),
1035 "relaychain.nodes['node2'].command: 'invalid command' shouldn't contains whitespace"
1036 );
1037 }
1038
1039 #[test]
1040 fn relaychain_config_builder_should_fails_returns_multiple_errors_if_a_node_and_default_resources_are_invalid(
1041 ) {
1042 let errors = RelaychainConfigBuilder::new(Default::default())
1043 .with_chain("chain")
1044 .with_default_resources(|resources| {
1045 resources
1046 .with_request_cpu("100Mi")
1047 .with_limit_memory("1Gi")
1048 .with_limit_cpu("invalid")
1049 })
1050 .with_validator(|node| node.with_name("node").with_image("invalid image"))
1051 .build()
1052 .unwrap_err();
1053
1054 assert_eq!(errors.len(), 2);
1055 assert_eq!(
1056 errors.first().unwrap().to_string(),
1057 "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)?$'"
1058 );
1059 assert_eq!(
1060 errors.get(1).unwrap().to_string(),
1061 "relaychain.nodes['node'].image: 'invalid image' doesn't match regex '^([ip]|[hostname]/)?[tag_name]:[tag_version]?$'"
1062 );
1063 }
1064
1065 #[test]
1066 fn relaychain_config_builder_should_works_with_chain_spec_command() {
1067 const CMD_TPL: &str = "./bin/chain-spec-generator {% raw %} {{chainName}} {% endraw %}";
1068 let config = RelaychainConfigBuilder::new(Default::default())
1069 .with_chain("polkadot")
1070 .with_default_image("myrepo:myimage")
1071 .with_default_command("default_command")
1072 .with_chain_spec_command(CMD_TPL)
1073 .with_fullnode(|node| node.with_name("node1").bootnode(true))
1074 .build()
1075 .unwrap();
1076
1077 assert_eq!(config.chain_spec_command(), Some(CMD_TPL));
1078 assert!(!config.chain_spec_command_is_local());
1079 }
1080
1081 #[test]
1082 fn relaychain_config_builder_should_works_with_chain_spec_command_locally() {
1083 const CMD_TPL: &str = "./bin/chain-spec-generator {% raw %} {{chainName}} {% endraw %}";
1084 let config = RelaychainConfigBuilder::new(Default::default())
1085 .with_chain("polkadot")
1086 .with_default_image("myrepo:myimage")
1087 .with_default_command("default_command")
1088 .with_chain_spec_command(CMD_TPL)
1089 .chain_spec_command_is_local(true)
1090 .with_fullnode(|node| node.with_name("node1").bootnode(true))
1091 .build()
1092 .unwrap();
1093
1094 assert_eq!(config.chain_spec_command(), Some(CMD_TPL));
1095 assert!(config.chain_spec_command_is_local());
1096 }
1097
1098 #[test]
1099 fn relaychain_with_group_config_should_succeeds_and_returns_a_relaychain_config() {
1100 let relaychain_config = RelaychainConfigBuilder::new(Default::default())
1101 .with_chain("chain")
1102 .with_default_command("command")
1103 .with_validator(|node| node.with_name("node").with_command("node_command"))
1104 .with_node_group(|group| {
1105 group.with_count(2).with_base_node(|base| {
1106 base.with_name("group_node")
1107 .with_command("some_command")
1108 .with_image("repo:image")
1109 .validator(true)
1110 })
1111 })
1112 .build()
1113 .unwrap();
1114
1115 assert_eq!(relaychain_config.chain().as_str(), "chain");
1116 assert_eq!(relaychain_config.nodes().len(), 1);
1117 assert_eq!(relaychain_config.group_node_configs().len(), 1);
1118 assert_eq!(
1119 relaychain_config
1120 .group_node_configs()
1121 .first()
1122 .unwrap()
1123 .count,
1124 2
1125 );
1126 let &node = relaychain_config.nodes().first().unwrap();
1127 assert_eq!(node.name(), "node");
1128 assert_eq!(node.command().unwrap().as_str(), "node_command");
1129
1130 let group_nodes = relaychain_config.group_node_configs();
1131 let group_base_node = group_nodes.first().unwrap();
1132 assert_eq!(group_base_node.base_config.name(), "group_node");
1133 assert_eq!(
1134 group_base_node.base_config.command().unwrap().as_str(),
1135 "some_command"
1136 );
1137 assert_eq!(
1138 group_base_node.base_config.image().unwrap().as_str(),
1139 "repo:image"
1140 );
1141 assert!(group_base_node.base_config.is_validator());
1142 }
1143
1144 #[test]
1145 fn relaychain_with_group_count_0_config_should_fail() {
1146 let relaychain_config = RelaychainConfigBuilder::new(Default::default())
1147 .with_chain("chain")
1148 .with_default_command("command")
1149 .with_validator(|node| node.with_name("node").with_command("node_command"))
1150 .with_node_group(|group| {
1151 group.with_count(0).with_base_node(|base| {
1152 base.with_name("group_node")
1153 .with_command("some_command")
1154 .with_image("repo:image")
1155 .validator(true)
1156 })
1157 })
1158 .build();
1159
1160 let errors: Vec<anyhow::Error> = match relaychain_config {
1161 Ok(_) => vec![],
1162 Err(errs) => errs,
1163 };
1164
1165 assert_eq!(errors.len(), 1);
1166 assert_eq!(
1167 errors.first().unwrap().to_string(),
1168 "relaychain.nodes['group_node'].Count cannot be zero"
1169 );
1170 }
1171}