1use std::{cell::RefCell, error::Error, fmt::Display, marker::PhantomData, rc::Rc};
2
3use anyhow::anyhow;
4use multiaddr::Multiaddr;
5use serde::{
6 de::{self, Visitor},
7 ser::SerializeStruct,
8 Deserialize, Serialize,
9};
10
11use crate::{
12 shared::{
13 errors::{ConfigError, FieldError},
14 helpers::{generate_unique_para_id, merge_errors, merge_errors_vecs},
15 node::{self, GroupNodeConfig, GroupNodeConfigBuilder, NodeConfig, NodeConfigBuilder},
16 resources::{Resources, ResourcesBuilder},
17 types::{
18 Arg, AssetLocation, Chain, ChainDefaultContext, Command, Image, ValidationContext, U128,
19 },
20 },
21 types::{ChainSpecRuntime, CommandWithCustomArgs, JsonOverrides},
22 utils::{default_as_false, default_as_true, default_initial_balance, is_false},
23};
24
25#[derive(Debug, Clone, PartialEq)]
27pub enum RegistrationStrategy {
28 InGenesis,
30 UsingExtrinsic,
32 Manual,
34}
35
36impl Serialize for RegistrationStrategy {
37 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
38 where
39 S: serde::Serializer,
40 {
41 let mut state = serializer.serialize_struct("RegistrationStrategy", 1)?;
42
43 match self {
44 Self::InGenesis => state.serialize_field("add_to_genesis", &true)?,
45 Self::UsingExtrinsic => state.serialize_field("register_para", &true)?,
46 Self::Manual => {
47 state.serialize_field("add_to_genesis", &false)?;
48 state.serialize_field("register_para", &false)?;
49 },
50 }
51
52 state.end()
53 }
54}
55
56struct RegistrationStrategyVisitor;
57
58impl<'de> Visitor<'de> for RegistrationStrategyVisitor {
59 type Value = RegistrationStrategy;
60
61 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
62 formatter.write_str("struct RegistrationStrategy")
63 }
64
65 fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
66 where
67 A: serde::de::MapAccess<'de>,
68 {
69 let mut add_to_genesis = false;
70 let mut register_para = false;
71
72 while let Some(key) = map.next_key::<String>()? {
73 match key.as_str() {
74 "addToGenesis" | "add_to_genesis" => add_to_genesis = map.next_value()?,
75 "registerPara" | "register_para" => register_para = map.next_value()?,
76 _ => {
77 return Err(de::Error::unknown_field(
78 &key,
79 &["add_to_genesis", "register_para"],
80 ))
81 },
82 }
83 }
84
85 match (add_to_genesis, register_para) {
86 (true, false) => Ok(RegistrationStrategy::InGenesis),
87 (false, true) => Ok(RegistrationStrategy::UsingExtrinsic),
88 _ => Err(de::Error::missing_field("add_to_genesis or register_para")),
89 }
90 }
91}
92
93impl<'de> Deserialize<'de> for RegistrationStrategy {
94 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
95 where
96 D: serde::Deserializer<'de>,
97 {
98 deserializer.deserialize_struct(
99 "RegistrationStrategy",
100 &["add_to_genesis", "register_para"],
101 RegistrationStrategyVisitor,
102 )
103 }
104}
105
106#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
108pub struct ParachainConfig {
109 id: u32,
110 #[serde(skip)]
111 unique_id: String,
114 chain: Option<Chain>,
115 #[serde(flatten)]
116 registration_strategy: Option<RegistrationStrategy>,
117 #[serde(
118 skip_serializing_if = "super::utils::is_true",
119 default = "default_as_true"
120 )]
121 onboard_as_parachain: bool,
122 num_cores: Option<u32>,
125 #[serde(rename = "balance", default = "default_initial_balance")]
126 initial_balance: U128,
127 default_command: Option<Command>,
128 default_image: Option<Image>,
129 default_resources: Option<Resources>,
130 default_db_snapshot: Option<AssetLocation>,
131 #[serde(skip_serializing_if = "std::vec::Vec::is_empty", default)]
132 default_args: Vec<Arg>,
133 genesis_wasm_path: Option<AssetLocation>,
134 genesis_wasm_generator: Option<Command>,
135 genesis_state_path: Option<AssetLocation>,
136 genesis_state_generator: Option<CommandWithCustomArgs>,
137 chain_spec_path: Option<AssetLocation>,
139 chain_spec_runtime: Option<ChainSpecRuntime>,
142 wasm_override: Option<AssetLocation>,
144 chain_spec_command: Option<String>,
148 #[serde(skip_serializing_if = "Option::is_none")]
150 post_process_script: Option<String>,
151 #[serde(skip_serializing_if = "is_false", default)]
153 chain_spec_command_is_local: bool,
154 chain_spec_command_output_path: Option<String>,
157 #[serde(rename = "cumulus_based", default = "default_as_true")]
158 is_cumulus_based: bool,
159 #[serde(rename = "evm_based", default = "default_as_false")]
160 is_evm_based: bool,
161 #[serde(skip_serializing_if = "std::vec::Vec::is_empty", default)]
162 bootnodes_addresses: Vec<Multiaddr>,
163 #[serde(skip_serializing_if = "is_false", default)]
164 no_default_bootnodes: bool,
165 #[serde(rename = "genesis", skip_serializing_if = "Option::is_none")]
166 genesis_overrides: Option<serde_json::Value>,
167 #[serde(skip_serializing_if = "Option::is_none")]
169 genesis_patch: Option<json_patch::Patch>,
170 #[serde(skip_serializing_if = "std::vec::Vec::is_empty", default)]
171 pub(crate) collators: Vec<NodeConfig>,
172 pub(crate) collator: Option<NodeConfig>,
179 #[serde(skip_serializing_if = "std::vec::Vec::is_empty", default)]
180 pub(crate) collator_groups: Vec<GroupNodeConfig>,
181 raw_spec_override: Option<JsonOverrides>,
183}
184
185impl ParachainConfig {
186 pub fn id(&self) -> u32 {
188 self.id
189 }
190
191 pub fn unique_id(&self) -> &str {
193 &self.unique_id
194 }
195
196 pub fn chain(&self) -> Option<&Chain> {
198 self.chain.as_ref()
199 }
200
201 pub fn registration_strategy(&self) -> Option<&RegistrationStrategy> {
203 self.registration_strategy.as_ref()
204 }
205
206 pub fn onboard_as_parachain(&self) -> bool {
208 self.onboard_as_parachain
209 }
210
211 pub fn num_cores(&self) -> Option<u32> {
213 self.num_cores
214 }
215
216 pub fn initial_balance(&self) -> u128 {
218 self.initial_balance.0
219 }
220
221 pub fn default_command(&self) -> Option<&Command> {
223 self.default_command.as_ref()
224 }
225
226 pub fn default_image(&self) -> Option<&Image> {
228 self.default_image.as_ref()
229 }
230
231 pub fn default_resources(&self) -> Option<&Resources> {
233 self.default_resources.as_ref()
234 }
235
236 pub fn default_db_snapshot(&self) -> Option<&AssetLocation> {
238 self.default_db_snapshot.as_ref()
239 }
240
241 pub fn default_args(&self) -> Vec<&Arg> {
243 self.default_args.iter().collect::<Vec<&Arg>>()
244 }
245
246 pub fn genesis_wasm_path(&self) -> Option<&AssetLocation> {
248 self.genesis_wasm_path.as_ref()
249 }
250
251 pub fn genesis_wasm_generator(&self) -> Option<&Command> {
253 self.genesis_wasm_generator.as_ref()
254 }
255
256 pub fn genesis_state_path(&self) -> Option<&AssetLocation> {
258 self.genesis_state_path.as_ref()
259 }
260
261 pub fn genesis_state_generator(&self) -> Option<&CommandWithCustomArgs> {
263 self.genesis_state_generator.as_ref()
264 }
265
266 pub fn genesis_overrides(&self) -> Option<&serde_json::Value> {
268 self.genesis_overrides.as_ref()
269 }
270
271 pub fn genesis_patch(&self) -> Option<&json_patch::Patch> {
273 self.genesis_patch.as_ref()
274 }
275
276 pub fn chain_spec_path(&self) -> Option<&AssetLocation> {
278 self.chain_spec_path.as_ref()
279 }
280
281 pub fn chain_spec_command(&self) -> Option<&str> {
283 self.chain_spec_command.as_deref()
284 }
285
286 pub fn chain_spec_command_is_local(&self) -> bool {
288 self.chain_spec_command_is_local
289 }
290
291 pub fn chain_spec_command_output_path(&self) -> Option<&str> {
294 self.chain_spec_command_output_path.as_deref()
295 }
296
297 pub fn post_process_script(&self) -> Option<&str> {
299 self.post_process_script.as_deref()
300 }
301
302 pub fn is_cumulus_based(&self) -> bool {
304 self.is_cumulus_based
305 }
306
307 pub fn is_evm_based(&self) -> bool {
309 self.is_evm_based
310 }
311
312 pub fn bootnodes_addresses(&self) -> Vec<&Multiaddr> {
314 self.bootnodes_addresses.iter().collect::<Vec<_>>()
315 }
316
317 pub fn no_default_bootnodes(&self) -> bool {
320 self.no_default_bootnodes
321 }
322
323 pub fn collators(&self) -> Vec<&NodeConfig> {
325 let mut cols = self.collators.iter().collect::<Vec<_>>();
326 if let Some(col) = self.collator.as_ref() {
327 cols.push(col);
328 }
329 cols
330 }
331
332 pub fn group_collators_configs(&self) -> Vec<&GroupNodeConfig> {
334 self.collator_groups.iter().collect::<Vec<_>>()
335 }
336
337 pub fn wasm_override(&self) -> Option<&AssetLocation> {
339 self.wasm_override.as_ref()
340 }
341
342 pub fn raw_spec_override(&self) -> Option<&JsonOverrides> {
344 self.raw_spec_override.as_ref()
345 }
346
347 pub fn chain_spec_runtime(&self) -> Option<&ChainSpecRuntime> {
349 self.chain_spec_runtime.as_ref()
350 }
351}
352
353pub mod states {
354 use crate::shared::macros::states;
355
356 states! {
357 Initial,
358 WithId,
359 WithAtLeastOneCollator
360 }
361
362 states! {
363 Bootstrap,
364 Running
365 }
366
367 pub trait Context {}
368 impl Context for Bootstrap {}
369 impl Context for Running {}
370}
371
372use states::{Bootstrap, Context, Initial, Running, WithAtLeastOneCollator, WithId};
373pub struct ParachainConfigBuilder<S, C> {
375 config: ParachainConfig,
376 validation_context: Rc<RefCell<ValidationContext>>,
377 errors: Vec<anyhow::Error>,
378 _state: PhantomData<S>,
379 _context: PhantomData<C>,
380}
381
382impl<C: Context> Default for ParachainConfigBuilder<Initial, C> {
383 fn default() -> Self {
384 Self {
385 config: ParachainConfig {
386 id: 100,
387 unique_id: String::from("100"),
388 chain: None,
389 registration_strategy: Some(RegistrationStrategy::InGenesis),
390 onboard_as_parachain: true,
391 num_cores: None,
392 initial_balance: 2_000_000_000_000.into(),
393 default_command: None,
394 default_image: None,
395 default_resources: None,
396 default_db_snapshot: None,
397 default_args: vec![],
398 genesis_wasm_path: None,
399 genesis_wasm_generator: None,
400 genesis_state_path: None,
401 genesis_state_generator: None,
402 genesis_overrides: None,
403 genesis_patch: None,
404 chain_spec_path: None,
405 chain_spec_runtime: None,
406 chain_spec_command: None,
407 post_process_script: None,
408 chain_spec_command_output_path: None,
409 wasm_override: None,
410 chain_spec_command_is_local: false, is_cumulus_based: true,
412 is_evm_based: false,
413 bootnodes_addresses: vec![],
414 no_default_bootnodes: false,
415 collators: vec![],
416 collator: None,
417 collator_groups: vec![],
418 raw_spec_override: None,
419 },
420 validation_context: Default::default(),
421 errors: vec![],
422 _state: PhantomData,
423 _context: PhantomData,
424 }
425 }
426}
427
428impl<A, C> ParachainConfigBuilder<A, C> {
429 fn transition<B>(
430 config: ParachainConfig,
431 validation_context: Rc<RefCell<ValidationContext>>,
432 errors: Vec<anyhow::Error>,
433 ) -> ParachainConfigBuilder<B, C> {
434 ParachainConfigBuilder {
435 config,
436 validation_context,
437 errors,
438 _state: PhantomData,
439 _context: PhantomData,
440 }
441 }
442
443 fn default_chain_context(&self) -> ChainDefaultContext {
444 ChainDefaultContext {
445 default_command: self.config.default_command.clone(),
446 default_image: self.config.default_image.clone(),
447 default_resources: self.config.default_resources.clone(),
448 default_db_snapshot: self.config.default_db_snapshot.clone(),
449 default_args: self.config.default_args.clone(),
450 }
451 }
452
453 fn create_node_builder<F>(&self, f: F) -> NodeConfigBuilder<node::Buildable>
454 where
455 F: FnOnce(NodeConfigBuilder<node::Initial>) -> NodeConfigBuilder<node::Buildable>,
456 {
457 f(NodeConfigBuilder::new(
458 self.default_chain_context(),
459 self.validation_context.clone(),
460 ))
461 }
462
463 pub fn with_post_process_script(mut self, script: impl Into<String>) -> Self {
465 self.config.post_process_script = Some(script.into());
466 self
467 }
468}
469
470impl ParachainConfigBuilder<Initial, Bootstrap> {
471 pub fn new(
473 validation_context: Rc<RefCell<ValidationContext>>,
474 ) -> ParachainConfigBuilder<Initial, Bootstrap> {
475 Self {
476 validation_context,
477 ..Self::default()
478 }
479 }
480}
481
482impl ParachainConfigBuilder<WithId, Bootstrap> {
483 pub fn with_registration_strategy(self, strategy: RegistrationStrategy) -> Self {
486 Self::transition(
487 ParachainConfig {
488 registration_strategy: Some(strategy),
489 ..self.config
490 },
491 self.validation_context,
492 self.errors,
493 )
494 }
495}
496
497impl ParachainConfigBuilder<WithId, Running> {
498 pub fn with_registration_strategy(self, strategy: RegistrationStrategy) -> Self {
501 match strategy {
502 RegistrationStrategy::InGenesis => Self::transition(
503 self.config,
504 self.validation_context,
505 merge_errors(
506 self.errors,
507 FieldError::RegistrationStrategy(anyhow!(
508 "Can be set to InGenesis in Running context"
509 ))
510 .into(),
511 ),
512 ),
513 RegistrationStrategy::Manual | RegistrationStrategy::UsingExtrinsic => {
514 Self::transition(
515 ParachainConfig {
516 registration_strategy: Some(strategy),
517 ..self.config
518 },
519 self.validation_context,
520 self.errors,
521 )
522 },
523 }
524 }
525}
526
527impl ParachainConfigBuilder<Initial, Running> {
528 pub fn new_with_running(
530 validation_context: Rc<RefCell<ValidationContext>>,
531 ) -> ParachainConfigBuilder<Initial, Running> {
532 let mut builder = Self {
533 validation_context,
534 ..Self::default()
535 };
536
537 builder.config.registration_strategy = Some(RegistrationStrategy::UsingExtrinsic);
539 builder
540 }
541}
542
543impl<C: Context> ParachainConfigBuilder<Initial, C> {
544 pub fn with_id(self, id: u32) -> ParachainConfigBuilder<WithId, C> {
546 let unique_id = generate_unique_para_id(id, self.validation_context.clone());
547 Self::transition(
548 ParachainConfig {
549 id,
550 unique_id,
551 ..self.config
552 },
553 self.validation_context,
554 self.errors,
555 )
556 }
557}
558
559impl<C: Context> ParachainConfigBuilder<WithId, C> {
560 pub fn with_chain<T>(self, chain: T) -> Self
563 where
564 T: TryInto<Chain>,
565 T::Error: Error + Send + Sync + 'static,
566 {
567 match chain.try_into() {
568 Ok(chain) => Self::transition(
569 ParachainConfig {
570 chain: Some(chain),
571 ..self.config
572 },
573 self.validation_context,
574 self.errors,
575 ),
576 Err(error) => Self::transition(
577 self.config,
578 self.validation_context,
579 merge_errors(self.errors, FieldError::Chain(error.into()).into()),
580 ),
581 }
582 }
583
584 pub fn onboard_as_parachain(self, choice: bool) -> Self {
586 Self::transition(
587 ParachainConfig {
588 onboard_as_parachain: choice,
589 ..self.config
590 },
591 self.validation_context,
592 self.errors,
593 )
594 }
595
596 pub fn with_num_cores(self, cores: u32) -> Self {
599 Self::transition(
600 ParachainConfig {
601 num_cores: Some(cores),
602 ..self.config
603 },
604 self.validation_context,
605 self.errors,
606 )
607 }
608
609 pub fn with_initial_balance(self, initial_balance: u128) -> Self {
611 Self::transition(
612 ParachainConfig {
613 initial_balance: initial_balance.into(),
614 ..self.config
615 },
616 self.validation_context,
617 self.errors,
618 )
619 }
620
621 pub fn with_default_command<T>(self, command: T) -> Self
623 where
624 T: TryInto<Command>,
625 T::Error: Error + Send + Sync + 'static,
626 {
627 match command.try_into() {
628 Ok(command) => Self::transition(
629 ParachainConfig {
630 default_command: Some(command),
631 ..self.config
632 },
633 self.validation_context,
634 self.errors,
635 ),
636 Err(error) => Self::transition(
637 self.config,
638 self.validation_context,
639 merge_errors(self.errors, FieldError::DefaultCommand(error.into()).into()),
640 ),
641 }
642 }
643
644 pub fn with_default_image<T>(self, image: T) -> Self
646 where
647 T: TryInto<Image>,
648 T::Error: Error + Send + Sync + 'static,
649 {
650 match image.try_into() {
651 Ok(image) => Self::transition(
652 ParachainConfig {
653 default_image: Some(image),
654 ..self.config
655 },
656 self.validation_context,
657 self.errors,
658 ),
659 Err(error) => Self::transition(
660 self.config,
661 self.validation_context,
662 merge_errors(self.errors, FieldError::DefaultImage(error.into()).into()),
663 ),
664 }
665 }
666
667 pub fn with_default_resources(
669 self,
670 f: impl FnOnce(ResourcesBuilder) -> ResourcesBuilder,
671 ) -> Self {
672 match f(ResourcesBuilder::new()).build() {
673 Ok(default_resources) => Self::transition(
674 ParachainConfig {
675 default_resources: Some(default_resources),
676 ..self.config
677 },
678 self.validation_context,
679 self.errors,
680 ),
681 Err(errors) => Self::transition(
682 self.config,
683 self.validation_context,
684 merge_errors_vecs(
685 self.errors,
686 errors
687 .into_iter()
688 .map(|error| FieldError::DefaultResources(error).into())
689 .collect::<Vec<_>>(),
690 ),
691 ),
692 }
693 }
694
695 pub fn with_default_db_snapshot(self, location: impl Into<AssetLocation>) -> Self {
697 Self::transition(
698 ParachainConfig {
699 default_db_snapshot: Some(location.into()),
700 ..self.config
701 },
702 self.validation_context,
703 self.errors,
704 )
705 }
706
707 pub fn with_optional_default_db_snapshot(
711 self,
712 location: Option<impl Into<AssetLocation>>,
713 ) -> Self {
714 match location {
715 Some(location) => self.with_default_db_snapshot(location),
716 None => self,
717 }
718 }
719
720 pub fn with_default_args(self, args: Vec<Arg>) -> Self {
722 Self::transition(
723 ParachainConfig {
724 default_args: args,
725 ..self.config
726 },
727 self.validation_context,
728 self.errors,
729 )
730 }
731
732 pub fn with_genesis_wasm_path(self, location: impl Into<AssetLocation>) -> Self {
734 Self::transition(
735 ParachainConfig {
736 genesis_wasm_path: Some(location.into()),
737 ..self.config
738 },
739 self.validation_context,
740 self.errors,
741 )
742 }
743
744 pub fn with_genesis_wasm_generator<T>(self, command: T) -> Self
746 where
747 T: TryInto<Command>,
748 T::Error: Error + Send + Sync + 'static,
749 {
750 match command.try_into() {
751 Ok(command) => Self::transition(
752 ParachainConfig {
753 genesis_wasm_generator: Some(command),
754 ..self.config
755 },
756 self.validation_context,
757 self.errors,
758 ),
759 Err(error) => Self::transition(
760 self.config,
761 self.validation_context,
762 merge_errors(
763 self.errors,
764 FieldError::GenesisWasmGenerator(error.into()).into(),
765 ),
766 ),
767 }
768 }
769
770 pub fn with_genesis_state_path(self, location: impl Into<AssetLocation>) -> Self {
772 Self::transition(
773 ParachainConfig {
774 genesis_state_path: Some(location.into()),
775 ..self.config
776 },
777 self.validation_context,
778 self.errors,
779 )
780 }
781
782 pub fn with_genesis_state_generator<T>(self, command: T) -> Self
784 where
785 T: TryInto<CommandWithCustomArgs>,
786 T::Error: Error + Send + Sync + 'static,
787 {
788 match command.try_into() {
789 Ok(command) => Self::transition(
790 ParachainConfig {
791 genesis_state_generator: Some(command),
792 ..self.config
793 },
794 self.validation_context,
795 self.errors,
796 ),
797 Err(error) => Self::transition(
798 self.config,
799 self.validation_context,
800 merge_errors(
801 self.errors,
802 FieldError::GenesisStateGenerator(error.into()).into(),
803 ),
804 ),
805 }
806 }
807
808 pub fn with_genesis_overrides(self, genesis_overrides: impl Into<serde_json::Value>) -> Self {
810 Self::transition(
811 ParachainConfig {
812 genesis_overrides: Some(genesis_overrides.into()),
813 ..self.config
814 },
815 self.validation_context,
816 self.errors,
817 )
818 }
819
820 pub fn with_genesis_patch(self, patch: impl Into<serde_json::Value>) -> Self {
824 match serde_json::from_value::<json_patch::Patch>(patch.into()) {
825 Ok(patch) => Self::transition(
826 ParachainConfig {
827 genesis_patch: Some(patch),
828 ..self.config
829 },
830 self.validation_context,
831 self.errors,
832 ),
833 Err(error) => Self::transition(
834 self.config,
835 self.validation_context,
836 merge_errors(self.errors, FieldError::GenesisPatch(error.into()).into()),
837 ),
838 }
839 }
840
841 pub fn with_chain_spec_path(self, location: impl Into<AssetLocation>) -> Self {
843 Self::transition(
844 ParachainConfig {
845 chain_spec_path: Some(location.into()),
846 ..self.config
847 },
848 self.validation_context,
849 self.errors,
850 )
851 }
852
853 pub fn with_chain_spec_command(self, cmd_template: impl Into<String>) -> Self {
855 Self::transition(
856 ParachainConfig {
857 chain_spec_command: Some(cmd_template.into()),
858 ..self.config
859 },
860 self.validation_context,
861 self.errors,
862 )
863 }
864
865 pub fn with_chain_spec_runtime(
869 self,
870 location: impl Into<AssetLocation>,
871 preset: Option<&str>,
872 ) -> Self {
873 let chain_spec_runtime = if let Some(preset) = preset {
874 ChainSpecRuntime::with_preset(location.into(), preset.to_string())
875 } else {
876 ChainSpecRuntime::new(location.into())
877 };
878 Self::transition(
879 ParachainConfig {
880 chain_spec_runtime: Some(chain_spec_runtime),
881 ..self.config
882 },
883 self.validation_context,
884 self.errors,
885 )
886 }
887
888 pub fn with_wasm_override(self, location: impl Into<AssetLocation>) -> Self {
890 Self::transition(
891 ParachainConfig {
892 wasm_override: Some(location.into()),
893 ..self.config
894 },
895 self.validation_context,
896 self.errors,
897 )
898 }
899
900 pub fn chain_spec_command_is_local(self, choice: bool) -> Self {
902 Self::transition(
903 ParachainConfig {
904 chain_spec_command_is_local: choice,
905 ..self.config
906 },
907 self.validation_context,
908 self.errors,
909 )
910 }
911
912 pub fn with_chain_spec_command_output_path(self, output_path: &str) -> Self {
914 Self::transition(
915 ParachainConfig {
916 chain_spec_command_output_path: Some(output_path.to_string()),
917 ..self.config
918 },
919 self.validation_context,
920 self.errors,
921 )
922 }
923
924 pub fn cumulus_based(self, choice: bool) -> Self {
926 Self::transition(
927 ParachainConfig {
928 is_cumulus_based: choice,
929 ..self.config
930 },
931 self.validation_context,
932 self.errors,
933 )
934 }
935
936 pub fn evm_based(self, choice: bool) -> Self {
938 Self::transition(
939 ParachainConfig {
940 is_evm_based: choice,
941 ..self.config
942 },
943 self.validation_context,
944 self.errors,
945 )
946 }
947
948 pub fn with_raw_bootnodes_addresses<T>(self, bootnodes_addresses: Vec<T>) -> Self
953 where
954 T: TryInto<Multiaddr> + Display + Copy,
955 T::Error: Error + Send + Sync + 'static,
956 {
957 let mut addrs = vec![];
958 let mut errors = vec![];
959
960 for (index, addr) in bootnodes_addresses.into_iter().enumerate() {
961 match addr.try_into() {
962 Ok(addr) => addrs.push(addr),
963 Err(error) => errors.push(
964 FieldError::BootnodesAddress(index, addr.to_string(), error.into()).into(),
965 ),
966 }
967 }
968
969 Self::transition(
970 ParachainConfig {
971 bootnodes_addresses: addrs,
972 ..self.config
973 },
974 self.validation_context,
975 merge_errors_vecs(self.errors, errors),
976 )
977 }
978
979 pub fn without_default_bootnodes(self) -> Self {
981 Self::transition(
982 ParachainConfig {
983 no_default_bootnodes: true,
984 ..self.config
985 },
986 self.validation_context,
987 self.errors,
988 )
989 }
990
991 pub fn with_collator(
993 self,
994 f: impl FnOnce(NodeConfigBuilder<node::Initial>) -> NodeConfigBuilder<node::Buildable>,
995 ) -> ParachainConfigBuilder<WithAtLeastOneCollator, C> {
996 match self.create_node_builder(f).validator(true).build() {
997 Ok(collator) => Self::transition(
998 ParachainConfig {
999 collators: vec![collator],
1000 ..self.config
1001 },
1002 self.validation_context,
1003 self.errors,
1004 ),
1005 Err((name, errors)) => Self::transition(
1006 self.config,
1007 self.validation_context,
1008 merge_errors_vecs(
1009 self.errors,
1010 errors
1011 .into_iter()
1012 .map(|error| ConfigError::Collator(name.clone(), error).into())
1013 .collect::<Vec<_>>(),
1014 ),
1015 ),
1016 }
1017 }
1018
1019 pub fn with_fullnode(
1022 self,
1023 f: impl FnOnce(NodeConfigBuilder<node::Initial>) -> NodeConfigBuilder<node::Buildable>,
1024 ) -> ParachainConfigBuilder<WithAtLeastOneCollator, C> {
1025 match self.create_node_builder(f).validator(false).build() {
1026 Ok(node) => Self::transition(
1027 ParachainConfig {
1028 collators: vec![node],
1029 ..self.config
1030 },
1031 self.validation_context,
1032 self.errors,
1033 ),
1034 Err((name, errors)) => Self::transition(
1035 self.config,
1036 self.validation_context,
1037 merge_errors_vecs(
1038 self.errors,
1039 errors
1040 .into_iter()
1041 .map(|error| ConfigError::Collator(name.clone(), error).into())
1042 .collect::<Vec<_>>(),
1043 ),
1044 ),
1045 }
1046 }
1047
1048 #[deprecated(
1052 since = "0.4.0",
1053 note = "Use `with_collator()` for collator nodes or `with_fullnode()` for full nodes instead"
1054 )]
1055 pub fn with_node(
1056 self,
1057 f: impl FnOnce(NodeConfigBuilder<node::Initial>) -> NodeConfigBuilder<node::Buildable>,
1058 ) -> ParachainConfigBuilder<WithAtLeastOneCollator, C> {
1059 match self.create_node_builder(f).build() {
1060 Ok(node) => Self::transition(
1061 ParachainConfig {
1062 collators: vec![node],
1063 ..self.config
1064 },
1065 self.validation_context,
1066 self.errors,
1067 ),
1068 Err((name, errors)) => Self::transition(
1069 self.config,
1070 self.validation_context,
1071 merge_errors_vecs(
1072 self.errors,
1073 errors
1074 .into_iter()
1075 .map(|error| ConfigError::Collator(name.clone(), error).into())
1076 .collect::<Vec<_>>(),
1077 ),
1078 ),
1079 }
1080 }
1081
1082 pub fn with_collator_group(
1084 self,
1085 f: impl FnOnce(GroupNodeConfigBuilder<node::Initial>) -> GroupNodeConfigBuilder<node::Buildable>,
1086 ) -> ParachainConfigBuilder<WithAtLeastOneCollator, C> {
1087 match f(GroupNodeConfigBuilder::new(
1088 self.default_chain_context(),
1089 self.validation_context.clone(),
1090 ))
1091 .build()
1092 {
1093 Ok(group) => Self::transition(
1094 ParachainConfig {
1095 collator_groups: [self.config.collator_groups, vec![group]].concat(),
1096 ..self.config
1097 },
1098 self.validation_context,
1099 self.errors,
1100 ),
1101 Err((name, errors)) => Self::transition(
1102 self.config,
1103 self.validation_context,
1104 merge_errors_vecs(
1105 self.errors,
1106 errors
1107 .into_iter()
1108 .map(|error| ConfigError::Collator(name.clone(), error).into())
1109 .collect::<Vec<_>>(),
1110 ),
1111 ),
1112 }
1113 }
1114
1115 pub fn with_raw_spec_override(self, overrides: impl Into<JsonOverrides>) -> Self {
1117 Self::transition(
1118 ParachainConfig {
1119 raw_spec_override: Some(overrides.into()),
1120 ..self.config
1121 },
1122 self.validation_context,
1123 self.errors,
1124 )
1125 }
1126}
1127
1128impl<C: Context> ParachainConfigBuilder<WithAtLeastOneCollator, C> {
1129 pub fn with_collator(
1131 self,
1132 f: impl FnOnce(NodeConfigBuilder<node::Initial>) -> NodeConfigBuilder<node::Buildable>,
1133 ) -> Self {
1134 match self.create_node_builder(f).validator(true).build() {
1135 Ok(collator) => Self::transition(
1136 ParachainConfig {
1137 collators: [self.config.collators, vec![collator]].concat(),
1138 ..self.config
1139 },
1140 self.validation_context,
1141 self.errors,
1142 ),
1143 Err((name, errors)) => Self::transition(
1144 self.config,
1145 self.validation_context,
1146 merge_errors_vecs(
1147 self.errors,
1148 errors
1149 .into_iter()
1150 .map(|error| ConfigError::Collator(name.clone(), error).into())
1151 .collect::<Vec<_>>(),
1152 ),
1153 ),
1154 }
1155 }
1156
1157 pub fn with_fullnode(
1160 self,
1161 f: impl FnOnce(NodeConfigBuilder<node::Initial>) -> NodeConfigBuilder<node::Buildable>,
1162 ) -> Self {
1163 match self.create_node_builder(f).validator(false).build() {
1164 Ok(node) => Self::transition(
1165 ParachainConfig {
1166 collators: [self.config.collators, vec![node]].concat(),
1167 ..self.config
1168 },
1169 self.validation_context,
1170 self.errors,
1171 ),
1172 Err((name, errors)) => Self::transition(
1173 self.config,
1174 self.validation_context,
1175 merge_errors_vecs(
1176 self.errors,
1177 errors
1178 .into_iter()
1179 .map(|error| ConfigError::Collator(name.clone(), error).into())
1180 .collect::<Vec<_>>(),
1181 ),
1182 ),
1183 }
1184 }
1185
1186 #[deprecated(
1190 since = "0.4.0",
1191 note = "Use `with_collator()` for collator nodes or `with_fullnode()` for full nodes instead"
1192 )]
1193 pub fn with_node(
1194 self,
1195 f: impl FnOnce(NodeConfigBuilder<node::Initial>) -> NodeConfigBuilder<node::Buildable>,
1196 ) -> Self {
1197 match self.create_node_builder(f).build() {
1198 Ok(node) => Self::transition(
1199 ParachainConfig {
1200 collators: [self.config.collators, vec![node]].concat(),
1201 ..self.config
1202 },
1203 self.validation_context,
1204 self.errors,
1205 ),
1206 Err((name, errors)) => Self::transition(
1207 self.config,
1208 self.validation_context,
1209 merge_errors_vecs(
1210 self.errors,
1211 errors
1212 .into_iter()
1213 .map(|error| ConfigError::Collator(name.clone(), error).into())
1214 .collect::<Vec<_>>(),
1215 ),
1216 ),
1217 }
1218 }
1219
1220 pub fn with_collator_group(
1222 self,
1223 f: impl FnOnce(GroupNodeConfigBuilder<node::Initial>) -> GroupNodeConfigBuilder<node::Buildable>,
1224 ) -> Self {
1225 match f(GroupNodeConfigBuilder::new(
1226 self.default_chain_context(),
1227 self.validation_context.clone(),
1228 ))
1229 .build()
1230 {
1231 Ok(group) => Self::transition(
1232 ParachainConfig {
1233 collator_groups: [self.config.collator_groups, vec![group]].concat(),
1234 ..self.config
1235 },
1236 self.validation_context,
1237 self.errors,
1238 ),
1239 Err((name, errors)) => Self::transition(
1240 self.config,
1241 self.validation_context,
1242 merge_errors_vecs(
1243 self.errors,
1244 errors
1245 .into_iter()
1246 .map(|error| ConfigError::Collator(name.clone(), error).into())
1247 .collect::<Vec<_>>(),
1248 ),
1249 ),
1250 }
1251 }
1252
1253 pub fn build(self) -> Result<ParachainConfig, Vec<anyhow::Error>> {
1255 if !self.errors.is_empty() {
1256 return Err(self
1257 .errors
1258 .into_iter()
1259 .map(|error| ConfigError::Parachain(self.config.id, error).into())
1260 .collect::<Vec<_>>());
1261 }
1262
1263 Ok(self.config)
1264 }
1265}
1266
1267#[cfg(test)]
1268mod tests {
1269 use super::*;
1270 use crate::NetworkConfig;
1271
1272 #[test]
1273 fn parachain_config_builder_should_succeeds_and_returns_a_new_parachain_config() {
1274 let parachain_config = ParachainConfigBuilder::new(Default::default())
1275 .with_id(1000)
1276 .with_chain("mychainname")
1277 .with_registration_strategy(RegistrationStrategy::UsingExtrinsic)
1278 .with_num_cores(3)
1279 .onboard_as_parachain(false)
1280 .with_initial_balance(100_000_042)
1281 .with_default_image("myrepo:myimage")
1282 .with_default_command("default_command")
1283 .with_default_resources(|resources| {
1284 resources
1285 .with_limit_cpu("500M")
1286 .with_limit_memory("1G")
1287 .with_request_cpu("250M")
1288 })
1289 .with_default_db_snapshot("https://www.urltomysnapshot.com/file.tgz")
1290 .with_default_args(vec![("--arg1", "value1").into(), "--option2".into()])
1291 .with_genesis_wasm_path("https://www.backupsite.com/my/wasm/file.tgz")
1292 .with_genesis_wasm_generator("generator_wasm")
1293 .with_genesis_state_path("./path/to/genesis/state")
1294 .with_genesis_state_generator(
1295 "undying-collator export-genesis-state --pov-size=10000 --pvf-complexity=1",
1296 )
1297 .with_chain_spec_path("./path/to/chain/spec.json")
1298 .with_chain_spec_runtime("./path/to/runtime.wasm", Some("dev"))
1299 .with_wasm_override("./path/to/override/runtime.wasm")
1300 .with_raw_spec_override("./path/to/override/rawspec.json")
1301 .cumulus_based(false)
1302 .evm_based(false)
1303 .with_raw_bootnodes_addresses(vec![
1304 "/ip4/10.41.122.55/tcp/45421",
1305 "/ip4/51.144.222.10/tcp/2333",
1306 ])
1307 .without_default_bootnodes()
1308 .with_collator(|collator| {
1309 collator
1310 .with_name("collator1")
1311 .with_command("command1")
1312 .bootnode(true)
1313 })
1314 .with_collator(|collator| {
1315 collator
1316 .with_name("collator2")
1317 .with_command("command2")
1318 .validator(true)
1319 })
1320 .build()
1321 .unwrap();
1322
1323 assert_eq!(parachain_config.id(), 1000);
1324 assert_eq!(parachain_config.num_cores(), Some(3));
1325 assert_eq!(parachain_config.collators().len(), 2);
1326 let &collator1 = parachain_config.collators().first().unwrap();
1327 assert_eq!(collator1.name(), "collator1");
1328 assert_eq!(collator1.command().unwrap().as_str(), "command1");
1329 assert!(collator1.is_bootnode());
1330 let &collator2 = parachain_config.collators().last().unwrap();
1331 assert_eq!(collator2.name(), "collator2");
1332 assert_eq!(collator2.command().unwrap().as_str(), "command2");
1333 assert!(collator2.is_validator());
1334 assert_eq!(parachain_config.chain().unwrap().as_str(), "mychainname");
1335
1336 assert_eq!(
1337 parachain_config.registration_strategy().unwrap(),
1338 &RegistrationStrategy::UsingExtrinsic
1339 );
1340 assert!(!parachain_config.onboard_as_parachain());
1341 assert_eq!(parachain_config.initial_balance(), 100_000_042);
1342 assert_eq!(
1343 parachain_config.default_command().unwrap().as_str(),
1344 "default_command"
1345 );
1346 assert_eq!(
1347 parachain_config.default_image().unwrap().as_str(),
1348 "myrepo:myimage"
1349 );
1350 let default_resources = parachain_config.default_resources().unwrap();
1351 assert_eq!(default_resources.limit_cpu().unwrap().as_str(), "500M");
1352 assert_eq!(default_resources.limit_memory().unwrap().as_str(), "1G");
1353 assert_eq!(default_resources.request_cpu().unwrap().as_str(), "250M");
1354 assert!(matches!(
1355 parachain_config.default_db_snapshot().unwrap(),
1356 AssetLocation::Url(value) if value.as_str() == "https://www.urltomysnapshot.com/file.tgz",
1357 ));
1358 assert!(matches!(
1359 parachain_config.chain_spec_path().unwrap(),
1360 AssetLocation::FilePath(value) if value.to_str().unwrap() == "./path/to/chain/spec.json"
1361 ));
1362 assert!(matches!(
1363 parachain_config.wasm_override().unwrap(),
1364 AssetLocation::FilePath(value) if value.to_str().unwrap() == "./path/to/override/runtime.wasm"
1365 ));
1366 assert!(matches!(
1367 ¶chain_config.chain_spec_runtime().unwrap().location,
1368 AssetLocation::FilePath(value) if value.to_str().unwrap() == "./path/to/runtime.wasm"
1369 ));
1370 assert_eq!(
1371 parachain_config
1372 .chain_spec_runtime()
1373 .unwrap()
1374 .preset
1375 .as_deref(),
1376 Some("dev")
1377 );
1378
1379 let args: Vec<Arg> = vec![("--arg1", "value1").into(), "--option2".into()];
1380 assert_eq!(
1381 parachain_config.default_args(),
1382 args.iter().collect::<Vec<_>>()
1383 );
1384 assert!(matches!(
1385 parachain_config.genesis_wasm_path().unwrap(),
1386 AssetLocation::Url(value) if value.as_str() == "https://www.backupsite.com/my/wasm/file.tgz"
1387 ));
1388 assert_eq!(
1389 parachain_config.genesis_wasm_generator().unwrap().as_str(),
1390 "generator_wasm"
1391 );
1392 assert!(matches!(
1393 parachain_config.genesis_state_path().unwrap(),
1394 AssetLocation::FilePath(value) if value.to_str().unwrap() == "./path/to/genesis/state"
1395 ));
1396 assert_eq!(
1397 parachain_config
1398 .genesis_state_generator()
1399 .unwrap()
1400 .cmd()
1401 .as_str(),
1402 "undying-collator"
1403 );
1404
1405 assert_eq!(
1406 parachain_config.genesis_state_generator().unwrap().args(),
1407 &vec![
1408 "export-genesis-state".into(),
1409 ("--pov-size", "10000").into(),
1410 ("--pvf-complexity", "1").into()
1411 ]
1412 );
1413
1414 assert!(matches!(
1415 parachain_config.chain_spec_path().unwrap(),
1416 AssetLocation::FilePath(value) if value.to_str().unwrap() == "./path/to/chain/spec.json"
1417 ));
1418 assert!(!parachain_config.is_cumulus_based());
1419 let bootnodes_addresses: Vec<Multiaddr> = vec![
1420 "/ip4/10.41.122.55/tcp/45421".try_into().unwrap(),
1421 "/ip4/51.144.222.10/tcp/2333".try_into().unwrap(),
1422 ];
1423 assert!(parachain_config.no_default_bootnodes());
1424 assert_eq!(
1425 parachain_config.bootnodes_addresses(),
1426 bootnodes_addresses.iter().collect::<Vec<_>>()
1427 );
1428 assert!(!parachain_config.is_evm_based());
1429 assert!(matches!(
1430 parachain_config.raw_spec_override().unwrap(),
1431 JsonOverrides::Location(AssetLocation::FilePath(value)) if value.to_str().unwrap() == "./path/to/override/rawspec.json"
1432 ));
1433 }
1434
1435 #[test]
1436 fn parachain_config_builder_should_works_when_genesis_state_generator_contains_args() {
1437 let parachain_config = ParachainConfigBuilder::new(Default::default())
1438 .with_id(1000)
1439 .with_chain("myparachain")
1440 .with_genesis_state_generator("generator_state --simple-flag --flag=value")
1441 .with_collator(|collator| {
1442 collator
1443 .with_name("collator")
1444 .with_command("command")
1445 .validator(true)
1446 })
1447 .build()
1448 .unwrap();
1449
1450 assert_eq!(
1451 parachain_config
1452 .genesis_state_generator()
1453 .unwrap()
1454 .cmd()
1455 .as_str(),
1456 "generator_state"
1457 );
1458
1459 assert_eq!(
1460 parachain_config
1461 .genesis_state_generator()
1462 .unwrap()
1463 .args()
1464 .len(),
1465 2
1466 );
1467
1468 let args = parachain_config.genesis_state_generator().unwrap().args();
1469
1470 assert_eq!(
1471 args,
1472 &vec![
1473 Arg::Flag("--simple-flag".into()),
1474 Arg::Option("--flag".into(), "value".into())
1475 ]
1476 );
1477 }
1478
1479 #[test]
1480 fn genesis_patch_is_kept_when_valid_and_reported_when_not() {
1481 let patch = serde_json::json!([{"op": "remove", "path": "/sudo"}]);
1482 let config = ParachainConfigBuilder::new(Default::default())
1483 .with_id(1000)
1484 .with_genesis_patch(patch.clone())
1485 .with_collator(|collator| collator.with_name("collator").with_command("command"))
1486 .build()
1487 .unwrap();
1488 assert_eq!(
1489 serde_json::to_value(config.genesis_patch().unwrap()).unwrap(),
1490 patch
1491 );
1492
1493 let errors = ParachainConfigBuilder::new(Default::default())
1494 .with_id(1000)
1495 .with_genesis_patch(serde_json::json!([{"op": "add", "path": "no-slash", "value": 1}]))
1496 .with_collator(|collator| collator.with_name("collator").with_command("command"))
1497 .build()
1498 .unwrap_err();
1499 assert_eq!(errors.len(), 1);
1500 let error = errors.first().unwrap().to_string();
1501 assert!(
1502 error.starts_with("parachain[1000].genesis_patch: "),
1503 "{error}"
1504 );
1505 }
1506
1507 #[test]
1508 fn parachain_config_builder_should_fails_and_returns_an_error_if_chain_is_invalid() {
1509 let errors = ParachainConfigBuilder::new(Default::default())
1510 .with_id(1000)
1511 .with_chain("invalid chain")
1512 .with_collator(|collator| {
1513 collator
1514 .with_name("collator")
1515 .with_command("command")
1516 .validator(true)
1517 })
1518 .build()
1519 .unwrap_err();
1520
1521 assert_eq!(errors.len(), 1);
1522 assert_eq!(
1523 errors.first().unwrap().to_string(),
1524 "parachain[1000].chain: 'invalid chain' shouldn't contains whitespace"
1525 );
1526 }
1527
1528 #[test]
1529 fn parachain_config_builder_should_fails_and_returns_an_error_if_default_command_is_invalid() {
1530 let errors = ParachainConfigBuilder::new(Default::default())
1531 .with_id(1000)
1532 .with_chain("chain")
1533 .with_default_command("invalid command")
1534 .with_collator(|collator| {
1535 collator
1536 .with_name("node")
1537 .with_command("command")
1538 .validator(true)
1539 })
1540 .build()
1541 .unwrap_err();
1542
1543 assert_eq!(errors.len(), 1);
1544 assert_eq!(
1545 errors.first().unwrap().to_string(),
1546 "parachain[1000].default_command: 'invalid command' shouldn't contains whitespace"
1547 );
1548 }
1549
1550 #[test]
1551 fn parachain_config_builder_should_fails_and_returns_an_error_if_default_image_is_invalid() {
1552 let errors = ParachainConfigBuilder::new(Default::default())
1553 .with_id(1000)
1554 .with_chain("chain")
1555 .with_default_image("invalid image")
1556 .with_collator(|collator| {
1557 collator
1558 .with_name("node")
1559 .with_command("command")
1560 .validator(true)
1561 })
1562 .build()
1563 .unwrap_err();
1564
1565 assert_eq!(errors.len(), 1);
1566 assert_eq!(
1567 errors.first().unwrap().to_string(),
1568 r"parachain[1000].default_image: 'invalid image' doesn't match regex '^([ip]|[hostname]/)?[tag_name]:[tag_version]?$'"
1569 );
1570 }
1571
1572 #[test]
1573 fn parachain_config_builder_should_fails_and_returns_an_error_if_default_resources_are_invalid()
1574 {
1575 let errors = ParachainConfigBuilder::new(Default::default())
1576 .with_id(1000)
1577 .with_chain("chain")
1578 .with_default_resources(|default_resources| {
1579 default_resources
1580 .with_limit_memory("100m")
1581 .with_request_cpu("invalid")
1582 })
1583 .with_collator(|collator| {
1584 collator
1585 .with_name("node")
1586 .with_command("command")
1587 .validator(true)
1588 })
1589 .build()
1590 .unwrap_err();
1591
1592 assert_eq!(errors.len(), 1);
1593 assert_eq!(
1594 errors.first().unwrap().to_string(),
1595 r"parachain[1000].default_resources.request_cpu: 'invalid' doesn't match regex '^\d+(.\d+)?(m|K|M|G|T|P|E|Ki|Mi|Gi|Ti|Pi|Ei)?$'"
1596 );
1597 }
1598
1599 #[test]
1600 fn parachain_config_builder_should_fails_and_returns_an_error_if_genesis_wasm_generator_is_invalid(
1601 ) {
1602 let errors = ParachainConfigBuilder::new(Default::default())
1603 .with_id(2000)
1604 .with_chain("myparachain")
1605 .with_genesis_wasm_generator("invalid command")
1606 .with_collator(|collator| {
1607 collator
1608 .with_name("collator")
1609 .with_command("command")
1610 .validator(true)
1611 })
1612 .build()
1613 .unwrap_err();
1614
1615 assert_eq!(errors.len(), 1);
1616 assert_eq!(
1617 errors.first().unwrap().to_string(),
1618 "parachain[2000].genesis_wasm_generator: 'invalid command' shouldn't contains whitespace"
1619 );
1620 }
1621
1622 #[test]
1623 fn parachain_config_builder_should_fails_and_returns_an_error_if_bootnodes_addresses_are_invalid(
1624 ) {
1625 let errors = ParachainConfigBuilder::new(Default::default())
1626 .with_id(2000)
1627 .with_chain("myparachain")
1628 .with_raw_bootnodes_addresses(vec!["/ip4//tcp/45421", "//10.42.153.10/tcp/43111"])
1629 .with_collator(|collator| {
1630 collator
1631 .with_name("collator")
1632 .with_command("command")
1633 .validator(true)
1634 })
1635 .build()
1636 .unwrap_err();
1637
1638 assert_eq!(errors.len(), 2);
1639 assert_eq!(
1640 errors.first().unwrap().to_string(),
1641 "parachain[2000].bootnodes_addresses[0]: '/ip4//tcp/45421' failed to parse: invalid IPv4 address syntax"
1642 );
1643 assert_eq!(
1644 errors.get(1).unwrap().to_string(),
1645 "parachain[2000].bootnodes_addresses[1]: '//10.42.153.10/tcp/43111' unknown protocol string: "
1646 );
1647 }
1648
1649 #[test]
1650 fn parachain_config_builder_should_fails_and_returns_an_error_if_first_collator_is_invalid() {
1651 let errors = ParachainConfigBuilder::new(Default::default())
1652 .with_id(1000)
1653 .with_chain("myparachain")
1654 .with_collator(|collator| {
1655 collator
1656 .with_name("collator")
1657 .with_command("invalid command")
1658 })
1659 .build()
1660 .unwrap_err();
1661
1662 assert_eq!(errors.len(), 1);
1663 assert_eq!(
1664 errors.first().unwrap().to_string(),
1665 "parachain[1000].collators['collator'].command: 'invalid command' shouldn't contains whitespace"
1666 );
1667 }
1668
1669 #[test]
1670 fn parachain_config_builder_with_at_least_one_collator_should_fails_and_returns_an_error_if_second_collator_is_invalid(
1671 ) {
1672 let errors = ParachainConfigBuilder::new(Default::default())
1673 .with_id(2000)
1674 .with_chain("myparachain")
1675 .with_collator(|collator| {
1676 collator
1677 .with_name("collator1")
1678 .with_command("command1")
1679 .invulnerable(true)
1680 .bootnode(true)
1681 })
1682 .with_collator(|collator| {
1683 collator
1684 .with_name("collator2")
1685 .with_command("invalid command")
1686 .with_initial_balance(20000000)
1687 })
1688 .build()
1689 .unwrap_err();
1690
1691 assert_eq!(errors.len(), 1);
1692 assert_eq!(
1693 errors.first().unwrap().to_string(),
1694 "parachain[2000].collators['collator2'].command: 'invalid command' shouldn't contains whitespace"
1695 );
1696 }
1697
1698 #[test]
1699 fn parachain_config_builder_should_fails_and_returns_multiple_errors_if_multiple_fields_are_invalid(
1700 ) {
1701 let errors = ParachainConfigBuilder::new(Default::default())
1702 .with_id(2000)
1703 .with_chain("myparachain")
1704 .with_raw_bootnodes_addresses(vec!["/ip4//tcp/45421", "//10.42.153.10/tcp/43111"])
1705 .with_collator(|collator| {
1706 collator
1707 .with_name("collator1")
1708 .with_command("invalid command")
1709 .invulnerable(true)
1710 .bootnode(true)
1711 .with_resources(|resources| {
1712 resources
1713 .with_limit_cpu("invalid")
1714 .with_request_memory("1G")
1715 })
1716 })
1717 .with_collator(|collator| {
1718 collator
1719 .with_name("collator2")
1720 .with_command("command2")
1721 .with_image("invalid.image")
1722 .with_initial_balance(20000000)
1723 })
1724 .build()
1725 .unwrap_err();
1726
1727 assert_eq!(errors.len(), 5);
1728 assert_eq!(
1729 errors.first().unwrap().to_string(),
1730 "parachain[2000].bootnodes_addresses[0]: '/ip4//tcp/45421' failed to parse: invalid IPv4 address syntax"
1731 );
1732 assert_eq!(
1733 errors.get(1).unwrap().to_string(),
1734 "parachain[2000].bootnodes_addresses[1]: '//10.42.153.10/tcp/43111' unknown protocol string: "
1735 );
1736 assert_eq!(
1737 errors.get(2).unwrap().to_string(),
1738 "parachain[2000].collators['collator1'].command: 'invalid command' shouldn't contains whitespace"
1739 );
1740 assert_eq!(
1741 errors.get(3).unwrap().to_string(),
1742 r"parachain[2000].collators['collator1'].resources.limit_cpu: 'invalid' doesn't match regex '^\d+(.\d+)?(m|K|M|G|T|P|E|Ki|Mi|Gi|Ti|Pi|Ei)?$'",
1743 );
1744 assert_eq!(
1745 errors.get(4).unwrap().to_string(),
1746 "parachain[2000].collators['collator2'].image: 'invalid.image' doesn't match regex '^([ip]|[hostname]/)?[tag_name]:[tag_version]?$'"
1747 );
1748 }
1749
1750 #[test]
1751 fn import_toml_registration_strategy_should_deserialize() {
1752 let load_from_toml =
1753 NetworkConfig::load_from_toml("./testing/snapshots/0001-big-network.toml").unwrap();
1754
1755 for parachain in load_from_toml.parachains().iter() {
1756 if parachain.id() == 1000 {
1757 assert_eq!(
1758 parachain.registration_strategy(),
1759 Some(&RegistrationStrategy::UsingExtrinsic)
1760 );
1761 }
1762 if parachain.id() == 2000 {
1763 assert_eq!(
1764 parachain.registration_strategy(),
1765 Some(&RegistrationStrategy::InGenesis)
1766 );
1767 }
1768 }
1769
1770 let load_from_toml_small = NetworkConfig::load_from_toml(
1771 "./testing/snapshots/0003-small-network_w_parachain.toml",
1772 )
1773 .unwrap();
1774
1775 let parachain = load_from_toml_small.parachains()[0];
1776 let parachain_evm = load_from_toml_small.parachains()[1];
1777
1778 assert_eq!(parachain.registration_strategy(), None);
1779 assert!(!parachain.is_evm_based());
1780 assert_eq!(parachain.collators().len(), 1);
1781 assert!(parachain_evm.is_evm_based());
1782 }
1783
1784 #[test]
1785 fn with_optional_default_db_snapshot_applies_when_some() {
1786 let config = ParachainConfigBuilder::new(Default::default())
1787 .with_id(2000)
1788 .with_chain("myparachain")
1789 .with_optional_default_db_snapshot(Some("https://example.com/snap.tgz"))
1790 .with_collator(|collator| collator.with_name("collator"))
1791 .build()
1792 .unwrap();
1793 assert!(matches!(
1794 config.default_db_snapshot().unwrap(),
1795 AssetLocation::Url(value) if value.as_str() == "https://example.com/snap.tgz"
1796 ));
1797 }
1798
1799 #[test]
1800 fn with_optional_default_db_snapshot_is_noop_when_none() {
1801 let config = ParachainConfigBuilder::new(Default::default())
1802 .with_id(2000)
1803 .with_chain("myparachain")
1804 .with_optional_default_db_snapshot(None::<&str>)
1805 .with_collator(|collator| collator.with_name("collator"))
1806 .build()
1807 .unwrap();
1808 assert!(config.default_db_snapshot().is_none());
1809 }
1810
1811 #[test]
1812 fn onboard_as_parachain_should_default_to_true() {
1813 let config = ParachainConfigBuilder::new(Default::default())
1814 .with_id(2000)
1815 .with_chain("myparachain")
1816 .with_collator(|collator| collator.with_name("collator"))
1817 .build()
1818 .unwrap();
1819
1820 assert!(config.onboard_as_parachain());
1821 }
1822
1823 #[test]
1824 fn evm_based_default_to_false() {
1825 let config = ParachainConfigBuilder::new(Default::default())
1826 .with_id(2000)
1827 .with_chain("myparachain")
1828 .with_collator(|collator| collator.with_name("collator"))
1829 .build()
1830 .unwrap();
1831
1832 assert!(!config.is_evm_based());
1833 }
1834
1835 #[test]
1836 fn evm_based() {
1837 let config = ParachainConfigBuilder::new(Default::default())
1838 .with_id(2000)
1839 .with_chain("myparachain")
1840 .evm_based(true)
1841 .with_collator(|collator| collator.with_name("collator"))
1842 .build()
1843 .unwrap();
1844
1845 assert!(config.is_evm_based());
1846 }
1847
1848 #[test]
1849 fn build_config_in_running_context() {
1850 let config = ParachainConfigBuilder::new_with_running(Default::default())
1851 .with_id(2000)
1852 .with_chain("myparachain")
1853 .with_collator(|collator| collator.with_name("collator"))
1854 .build()
1855 .unwrap();
1856
1857 assert_eq!(
1858 config.registration_strategy(),
1859 Some(&RegistrationStrategy::UsingExtrinsic)
1860 );
1861 }
1862
1863 #[test]
1864 fn parachain_config_builder_should_works_with_chain_spec_command() {
1865 const CMD_TPL: &str = "./bin/chain-spec-generator {% raw %} {{chainName}} {% endraw %}";
1866 let config = ParachainConfigBuilder::new(Default::default())
1867 .with_id(2000)
1868 .with_chain("some-chain")
1869 .with_default_image("myrepo:myimage")
1870 .with_default_command("default_command")
1871 .with_chain_spec_command(CMD_TPL)
1872 .with_collator(|collator| collator.with_name("collator"))
1873 .build()
1874 .unwrap();
1875
1876 assert_eq!(config.chain_spec_command(), Some(CMD_TPL));
1877 assert!(!config.chain_spec_command_is_local());
1878 }
1879
1880 #[test]
1881 fn parachain_config_builder_should_works_with_chain_spec_command_and_local() {
1882 const CMD_TPL: &str = "./bin/chain-spec-generator {% raw %} {{chainName}} {% endraw %}";
1883 let config = ParachainConfigBuilder::new(Default::default())
1884 .with_id(2000)
1885 .with_chain("some-chain")
1886 .with_default_image("myrepo:myimage")
1887 .with_default_command("default_command")
1888 .with_chain_spec_command(CMD_TPL)
1889 .chain_spec_command_is_local(true)
1890 .with_collator(|collator| collator.with_name("collator"))
1891 .build()
1892 .unwrap();
1893
1894 assert_eq!(config.chain_spec_command(), Some(CMD_TPL));
1895 assert!(config.chain_spec_command_is_local());
1896 }
1897
1898 #[test]
1899 fn parachain_with_group_config_builder_should_succeeds_and_returns_a_new_parachain_config() {
1900 let parachain_config = ParachainConfigBuilder::new(Default::default())
1901 .with_id(1000)
1902 .with_chain("mychainname")
1903 .with_registration_strategy(RegistrationStrategy::UsingExtrinsic)
1904 .onboard_as_parachain(false)
1905 .with_initial_balance(100_000_042)
1906 .with_default_image("myrepo:myimage")
1907 .with_default_command("default_command")
1908 .without_default_bootnodes()
1909 .with_collator(|collator| {
1910 collator
1911 .with_name("collator1")
1912 .with_command("command1")
1913 .bootnode(true)
1914 })
1915 .with_collator_group(|group| {
1916 group.with_count(2).with_base_node(|base| {
1917 base.with_name("collator_group1")
1918 .with_command("group_command1")
1919 .bootnode(true)
1920 })
1921 })
1922 .with_collator_group(|group| {
1923 group.with_count(3).with_base_node(|base| {
1924 base.with_name("collator_group2")
1925 .with_command("group_command2")
1926 .bootnode(false)
1927 })
1928 })
1929 .build()
1930 .unwrap();
1931
1932 assert_eq!(parachain_config.id(), 1000);
1933 assert_eq!(parachain_config.collators().len(), 1);
1934 assert_eq!(parachain_config.group_collators_configs().len(), 2);
1935
1936 let group_collator1 = parachain_config.group_collators_configs()[0].clone();
1937 assert_eq!(group_collator1.count, 2);
1938 let base_config1 = group_collator1.base_config;
1939 assert_eq!(base_config1.name(), "collator-group1");
1940 assert_eq!(base_config1.command().unwrap().as_str(), "group_command1");
1941 assert!(base_config1.is_bootnode());
1942
1943 let group_collator2 = parachain_config.group_collators_configs()[1].clone();
1944 assert_eq!(group_collator2.count, 3);
1945 let base_config2 = group_collator2.base_config;
1946 assert_eq!(base_config2.name(), "collator-group2");
1947 assert_eq!(base_config2.command().unwrap().as_str(), "group_command2");
1948 assert!(!base_config2.is_bootnode());
1949 }
1950
1951 #[test]
1952 fn parachain_with_group_count_0_config_builder_should_fail() {
1953 let parachain_config = ParachainConfigBuilder::new(Default::default())
1954 .with_id(1000)
1955 .with_chain("mychainname")
1956 .with_registration_strategy(RegistrationStrategy::UsingExtrinsic)
1957 .onboard_as_parachain(false)
1958 .with_initial_balance(100_000_042)
1959 .with_default_image("myrepo:myimage")
1960 .with_default_command("default_command")
1961 .without_default_bootnodes()
1962 .with_collator(|collator| {
1963 collator
1964 .with_name("collator1")
1965 .with_command("command1")
1966 .bootnode(true)
1967 })
1968 .with_collator_group(|group| {
1969 group.with_count(2).with_base_node(|base| {
1970 base.with_name("collator_group1")
1971 .with_command("group_command1")
1972 .bootnode(true)
1973 })
1974 })
1975 .with_collator_group(|group| {
1976 group.with_count(0).with_base_node(|base| {
1977 base.with_name("collator_group2")
1978 .with_command("group_command2")
1979 .bootnode(false)
1980 })
1981 })
1982 .build();
1983
1984 let errors: Vec<anyhow::Error> = match parachain_config {
1985 Ok(_) => vec![],
1986 Err(errs) => errs,
1987 };
1988
1989 assert_eq!(errors.len(), 1);
1990 assert_eq!(
1991 errors.first().unwrap().to_string(),
1992 "parachain[1000].collators['collator-group2'].Count cannot be zero"
1993 );
1994 }
1995}