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 = "std::vec::Vec::is_empty", default)]
168 pub(crate) collators: Vec<NodeConfig>,
169 pub(crate) collator: Option<NodeConfig>,
176 #[serde(skip_serializing_if = "std::vec::Vec::is_empty", default)]
177 pub(crate) collator_groups: Vec<GroupNodeConfig>,
178 raw_spec_override: Option<JsonOverrides>,
180}
181
182impl ParachainConfig {
183 pub fn id(&self) -> u32 {
185 self.id
186 }
187
188 pub fn unique_id(&self) -> &str {
190 &self.unique_id
191 }
192
193 pub fn chain(&self) -> Option<&Chain> {
195 self.chain.as_ref()
196 }
197
198 pub fn registration_strategy(&self) -> Option<&RegistrationStrategy> {
200 self.registration_strategy.as_ref()
201 }
202
203 pub fn onboard_as_parachain(&self) -> bool {
205 self.onboard_as_parachain
206 }
207
208 pub fn num_cores(&self) -> Option<u32> {
210 self.num_cores
211 }
212
213 pub fn initial_balance(&self) -> u128 {
215 self.initial_balance.0
216 }
217
218 pub fn default_command(&self) -> Option<&Command> {
220 self.default_command.as_ref()
221 }
222
223 pub fn default_image(&self) -> Option<&Image> {
225 self.default_image.as_ref()
226 }
227
228 pub fn default_resources(&self) -> Option<&Resources> {
230 self.default_resources.as_ref()
231 }
232
233 pub fn default_db_snapshot(&self) -> Option<&AssetLocation> {
235 self.default_db_snapshot.as_ref()
236 }
237
238 pub fn default_args(&self) -> Vec<&Arg> {
240 self.default_args.iter().collect::<Vec<&Arg>>()
241 }
242
243 pub fn genesis_wasm_path(&self) -> Option<&AssetLocation> {
245 self.genesis_wasm_path.as_ref()
246 }
247
248 pub fn genesis_wasm_generator(&self) -> Option<&Command> {
250 self.genesis_wasm_generator.as_ref()
251 }
252
253 pub fn genesis_state_path(&self) -> Option<&AssetLocation> {
255 self.genesis_state_path.as_ref()
256 }
257
258 pub fn genesis_state_generator(&self) -> Option<&CommandWithCustomArgs> {
260 self.genesis_state_generator.as_ref()
261 }
262
263 pub fn genesis_overrides(&self) -> Option<&serde_json::Value> {
265 self.genesis_overrides.as_ref()
266 }
267
268 pub fn chain_spec_path(&self) -> Option<&AssetLocation> {
270 self.chain_spec_path.as_ref()
271 }
272
273 pub fn chain_spec_command(&self) -> Option<&str> {
275 self.chain_spec_command.as_deref()
276 }
277
278 pub fn chain_spec_command_is_local(&self) -> bool {
280 self.chain_spec_command_is_local
281 }
282
283 pub fn chain_spec_command_output_path(&self) -> Option<&str> {
286 self.chain_spec_command_output_path.as_deref()
287 }
288
289 pub fn post_process_script(&self) -> Option<&str> {
291 self.post_process_script.as_deref()
292 }
293
294 pub fn is_cumulus_based(&self) -> bool {
296 self.is_cumulus_based
297 }
298
299 pub fn is_evm_based(&self) -> bool {
301 self.is_evm_based
302 }
303
304 pub fn bootnodes_addresses(&self) -> Vec<&Multiaddr> {
306 self.bootnodes_addresses.iter().collect::<Vec<_>>()
307 }
308
309 pub fn no_default_bootnodes(&self) -> bool {
312 self.no_default_bootnodes
313 }
314
315 pub fn collators(&self) -> Vec<&NodeConfig> {
317 let mut cols = self.collators.iter().collect::<Vec<_>>();
318 if let Some(col) = self.collator.as_ref() {
319 cols.push(col);
320 }
321 cols
322 }
323
324 pub fn group_collators_configs(&self) -> Vec<&GroupNodeConfig> {
326 self.collator_groups.iter().collect::<Vec<_>>()
327 }
328
329 pub fn wasm_override(&self) -> Option<&AssetLocation> {
331 self.wasm_override.as_ref()
332 }
333
334 pub fn raw_spec_override(&self) -> Option<&JsonOverrides> {
336 self.raw_spec_override.as_ref()
337 }
338
339 pub fn chain_spec_runtime(&self) -> Option<&ChainSpecRuntime> {
341 self.chain_spec_runtime.as_ref()
342 }
343}
344
345pub mod states {
346 use crate::shared::macros::states;
347
348 states! {
349 Initial,
350 WithId,
351 WithAtLeastOneCollator
352 }
353
354 states! {
355 Bootstrap,
356 Running
357 }
358
359 pub trait Context {}
360 impl Context for Bootstrap {}
361 impl Context for Running {}
362}
363
364use states::{Bootstrap, Context, Initial, Running, WithAtLeastOneCollator, WithId};
365pub struct ParachainConfigBuilder<S, C> {
367 config: ParachainConfig,
368 validation_context: Rc<RefCell<ValidationContext>>,
369 errors: Vec<anyhow::Error>,
370 _state: PhantomData<S>,
371 _context: PhantomData<C>,
372}
373
374impl<C: Context> Default for ParachainConfigBuilder<Initial, C> {
375 fn default() -> Self {
376 Self {
377 config: ParachainConfig {
378 id: 100,
379 unique_id: String::from("100"),
380 chain: None,
381 registration_strategy: Some(RegistrationStrategy::InGenesis),
382 onboard_as_parachain: true,
383 num_cores: None,
384 initial_balance: 2_000_000_000_000.into(),
385 default_command: None,
386 default_image: None,
387 default_resources: None,
388 default_db_snapshot: None,
389 default_args: vec![],
390 genesis_wasm_path: None,
391 genesis_wasm_generator: None,
392 genesis_state_path: None,
393 genesis_state_generator: None,
394 genesis_overrides: None,
395 chain_spec_path: None,
396 chain_spec_runtime: None,
397 chain_spec_command: None,
398 post_process_script: None,
399 chain_spec_command_output_path: None,
400 wasm_override: None,
401 chain_spec_command_is_local: false, is_cumulus_based: true,
403 is_evm_based: false,
404 bootnodes_addresses: vec![],
405 no_default_bootnodes: false,
406 collators: vec![],
407 collator: None,
408 collator_groups: vec![],
409 raw_spec_override: None,
410 },
411 validation_context: Default::default(),
412 errors: vec![],
413 _state: PhantomData,
414 _context: PhantomData,
415 }
416 }
417}
418
419impl<A, C> ParachainConfigBuilder<A, C> {
420 fn transition<B>(
421 config: ParachainConfig,
422 validation_context: Rc<RefCell<ValidationContext>>,
423 errors: Vec<anyhow::Error>,
424 ) -> ParachainConfigBuilder<B, C> {
425 ParachainConfigBuilder {
426 config,
427 validation_context,
428 errors,
429 _state: PhantomData,
430 _context: PhantomData,
431 }
432 }
433
434 fn default_chain_context(&self) -> ChainDefaultContext {
435 ChainDefaultContext {
436 default_command: self.config.default_command.clone(),
437 default_image: self.config.default_image.clone(),
438 default_resources: self.config.default_resources.clone(),
439 default_db_snapshot: self.config.default_db_snapshot.clone(),
440 default_args: self.config.default_args.clone(),
441 }
442 }
443
444 fn create_node_builder<F>(&self, f: F) -> NodeConfigBuilder<node::Buildable>
445 where
446 F: FnOnce(NodeConfigBuilder<node::Initial>) -> NodeConfigBuilder<node::Buildable>,
447 {
448 f(NodeConfigBuilder::new(
449 self.default_chain_context(),
450 self.validation_context.clone(),
451 ))
452 }
453
454 pub fn with_post_process_script(mut self, script: impl Into<String>) -> Self {
456 self.config.post_process_script = Some(script.into());
457 self
458 }
459}
460
461impl ParachainConfigBuilder<Initial, Bootstrap> {
462 pub fn new(
464 validation_context: Rc<RefCell<ValidationContext>>,
465 ) -> ParachainConfigBuilder<Initial, Bootstrap> {
466 Self {
467 validation_context,
468 ..Self::default()
469 }
470 }
471}
472
473impl ParachainConfigBuilder<WithId, Bootstrap> {
474 pub fn with_registration_strategy(self, strategy: RegistrationStrategy) -> Self {
477 Self::transition(
478 ParachainConfig {
479 registration_strategy: Some(strategy),
480 ..self.config
481 },
482 self.validation_context,
483 self.errors,
484 )
485 }
486}
487
488impl ParachainConfigBuilder<WithId, Running> {
489 pub fn with_registration_strategy(self, strategy: RegistrationStrategy) -> Self {
492 match strategy {
493 RegistrationStrategy::InGenesis => Self::transition(
494 self.config,
495 self.validation_context,
496 merge_errors(
497 self.errors,
498 FieldError::RegistrationStrategy(anyhow!(
499 "Can be set to InGenesis in Running context"
500 ))
501 .into(),
502 ),
503 ),
504 RegistrationStrategy::Manual | RegistrationStrategy::UsingExtrinsic => {
505 Self::transition(
506 ParachainConfig {
507 registration_strategy: Some(strategy),
508 ..self.config
509 },
510 self.validation_context,
511 self.errors,
512 )
513 },
514 }
515 }
516}
517
518impl ParachainConfigBuilder<Initial, Running> {
519 pub fn new_with_running(
521 validation_context: Rc<RefCell<ValidationContext>>,
522 ) -> ParachainConfigBuilder<Initial, Running> {
523 let mut builder = Self {
524 validation_context,
525 ..Self::default()
526 };
527
528 builder.config.registration_strategy = Some(RegistrationStrategy::UsingExtrinsic);
530 builder
531 }
532}
533
534impl<C: Context> ParachainConfigBuilder<Initial, C> {
535 pub fn with_id(self, id: u32) -> ParachainConfigBuilder<WithId, C> {
537 let unique_id = generate_unique_para_id(id, self.validation_context.clone());
538 Self::transition(
539 ParachainConfig {
540 id,
541 unique_id,
542 ..self.config
543 },
544 self.validation_context,
545 self.errors,
546 )
547 }
548}
549
550impl<C: Context> ParachainConfigBuilder<WithId, C> {
551 pub fn with_chain<T>(self, chain: T) -> Self
554 where
555 T: TryInto<Chain>,
556 T::Error: Error + Send + Sync + 'static,
557 {
558 match chain.try_into() {
559 Ok(chain) => Self::transition(
560 ParachainConfig {
561 chain: Some(chain),
562 ..self.config
563 },
564 self.validation_context,
565 self.errors,
566 ),
567 Err(error) => Self::transition(
568 self.config,
569 self.validation_context,
570 merge_errors(self.errors, FieldError::Chain(error.into()).into()),
571 ),
572 }
573 }
574
575 pub fn onboard_as_parachain(self, choice: bool) -> Self {
577 Self::transition(
578 ParachainConfig {
579 onboard_as_parachain: choice,
580 ..self.config
581 },
582 self.validation_context,
583 self.errors,
584 )
585 }
586
587 pub fn with_num_cores(self, cores: u32) -> Self {
590 Self::transition(
591 ParachainConfig {
592 num_cores: Some(cores),
593 ..self.config
594 },
595 self.validation_context,
596 self.errors,
597 )
598 }
599
600 pub fn with_initial_balance(self, initial_balance: u128) -> Self {
602 Self::transition(
603 ParachainConfig {
604 initial_balance: initial_balance.into(),
605 ..self.config
606 },
607 self.validation_context,
608 self.errors,
609 )
610 }
611
612 pub fn with_default_command<T>(self, command: T) -> Self
614 where
615 T: TryInto<Command>,
616 T::Error: Error + Send + Sync + 'static,
617 {
618 match command.try_into() {
619 Ok(command) => Self::transition(
620 ParachainConfig {
621 default_command: Some(command),
622 ..self.config
623 },
624 self.validation_context,
625 self.errors,
626 ),
627 Err(error) => Self::transition(
628 self.config,
629 self.validation_context,
630 merge_errors(self.errors, FieldError::DefaultCommand(error.into()).into()),
631 ),
632 }
633 }
634
635 pub fn with_default_image<T>(self, image: T) -> Self
637 where
638 T: TryInto<Image>,
639 T::Error: Error + Send + Sync + 'static,
640 {
641 match image.try_into() {
642 Ok(image) => Self::transition(
643 ParachainConfig {
644 default_image: Some(image),
645 ..self.config
646 },
647 self.validation_context,
648 self.errors,
649 ),
650 Err(error) => Self::transition(
651 self.config,
652 self.validation_context,
653 merge_errors(self.errors, FieldError::DefaultImage(error.into()).into()),
654 ),
655 }
656 }
657
658 pub fn with_default_resources(
660 self,
661 f: impl FnOnce(ResourcesBuilder) -> ResourcesBuilder,
662 ) -> Self {
663 match f(ResourcesBuilder::new()).build() {
664 Ok(default_resources) => Self::transition(
665 ParachainConfig {
666 default_resources: Some(default_resources),
667 ..self.config
668 },
669 self.validation_context,
670 self.errors,
671 ),
672 Err(errors) => Self::transition(
673 self.config,
674 self.validation_context,
675 merge_errors_vecs(
676 self.errors,
677 errors
678 .into_iter()
679 .map(|error| FieldError::DefaultResources(error).into())
680 .collect::<Vec<_>>(),
681 ),
682 ),
683 }
684 }
685
686 pub fn with_default_db_snapshot(self, location: impl Into<AssetLocation>) -> Self {
688 Self::transition(
689 ParachainConfig {
690 default_db_snapshot: Some(location.into()),
691 ..self.config
692 },
693 self.validation_context,
694 self.errors,
695 )
696 }
697
698 pub fn with_default_args(self, args: Vec<Arg>) -> Self {
700 Self::transition(
701 ParachainConfig {
702 default_args: args,
703 ..self.config
704 },
705 self.validation_context,
706 self.errors,
707 )
708 }
709
710 pub fn with_genesis_wasm_path(self, location: impl Into<AssetLocation>) -> Self {
712 Self::transition(
713 ParachainConfig {
714 genesis_wasm_path: Some(location.into()),
715 ..self.config
716 },
717 self.validation_context,
718 self.errors,
719 )
720 }
721
722 pub fn with_genesis_wasm_generator<T>(self, command: T) -> Self
724 where
725 T: TryInto<Command>,
726 T::Error: Error + Send + Sync + 'static,
727 {
728 match command.try_into() {
729 Ok(command) => Self::transition(
730 ParachainConfig {
731 genesis_wasm_generator: Some(command),
732 ..self.config
733 },
734 self.validation_context,
735 self.errors,
736 ),
737 Err(error) => Self::transition(
738 self.config,
739 self.validation_context,
740 merge_errors(
741 self.errors,
742 FieldError::GenesisWasmGenerator(error.into()).into(),
743 ),
744 ),
745 }
746 }
747
748 pub fn with_genesis_state_path(self, location: impl Into<AssetLocation>) -> Self {
750 Self::transition(
751 ParachainConfig {
752 genesis_state_path: Some(location.into()),
753 ..self.config
754 },
755 self.validation_context,
756 self.errors,
757 )
758 }
759
760 pub fn with_genesis_state_generator<T>(self, command: T) -> Self
762 where
763 T: TryInto<CommandWithCustomArgs>,
764 T::Error: Error + Send + Sync + 'static,
765 {
766 match command.try_into() {
767 Ok(command) => Self::transition(
768 ParachainConfig {
769 genesis_state_generator: Some(command),
770 ..self.config
771 },
772 self.validation_context,
773 self.errors,
774 ),
775 Err(error) => Self::transition(
776 self.config,
777 self.validation_context,
778 merge_errors(
779 self.errors,
780 FieldError::GenesisStateGenerator(error.into()).into(),
781 ),
782 ),
783 }
784 }
785
786 pub fn with_genesis_overrides(self, genesis_overrides: impl Into<serde_json::Value>) -> Self {
788 Self::transition(
789 ParachainConfig {
790 genesis_overrides: Some(genesis_overrides.into()),
791 ..self.config
792 },
793 self.validation_context,
794 self.errors,
795 )
796 }
797
798 pub fn with_chain_spec_path(self, location: impl Into<AssetLocation>) -> Self {
800 Self::transition(
801 ParachainConfig {
802 chain_spec_path: Some(location.into()),
803 ..self.config
804 },
805 self.validation_context,
806 self.errors,
807 )
808 }
809
810 pub fn with_chain_spec_command(self, cmd_template: impl Into<String>) -> Self {
812 Self::transition(
813 ParachainConfig {
814 chain_spec_command: Some(cmd_template.into()),
815 ..self.config
816 },
817 self.validation_context,
818 self.errors,
819 )
820 }
821
822 pub fn with_chain_spec_runtime(
826 self,
827 location: impl Into<AssetLocation>,
828 preset: Option<&str>,
829 ) -> Self {
830 let chain_spec_runtime = if let Some(preset) = preset {
831 ChainSpecRuntime::with_preset(location.into(), preset.to_string())
832 } else {
833 ChainSpecRuntime::new(location.into())
834 };
835 Self::transition(
836 ParachainConfig {
837 chain_spec_runtime: Some(chain_spec_runtime),
838 ..self.config
839 },
840 self.validation_context,
841 self.errors,
842 )
843 }
844
845 pub fn with_wasm_override(self, location: impl Into<AssetLocation>) -> Self {
847 Self::transition(
848 ParachainConfig {
849 wasm_override: Some(location.into()),
850 ..self.config
851 },
852 self.validation_context,
853 self.errors,
854 )
855 }
856
857 pub fn chain_spec_command_is_local(self, choice: bool) -> Self {
859 Self::transition(
860 ParachainConfig {
861 chain_spec_command_is_local: choice,
862 ..self.config
863 },
864 self.validation_context,
865 self.errors,
866 )
867 }
868
869 pub fn with_chain_spec_command_output_path(self, output_path: &str) -> Self {
871 Self::transition(
872 ParachainConfig {
873 chain_spec_command_output_path: Some(output_path.to_string()),
874 ..self.config
875 },
876 self.validation_context,
877 self.errors,
878 )
879 }
880
881 pub fn cumulus_based(self, choice: bool) -> Self {
883 Self::transition(
884 ParachainConfig {
885 is_cumulus_based: choice,
886 ..self.config
887 },
888 self.validation_context,
889 self.errors,
890 )
891 }
892
893 pub fn evm_based(self, choice: bool) -> Self {
895 Self::transition(
896 ParachainConfig {
897 is_evm_based: choice,
898 ..self.config
899 },
900 self.validation_context,
901 self.errors,
902 )
903 }
904
905 pub fn with_raw_bootnodes_addresses<T>(self, bootnodes_addresses: Vec<T>) -> Self
910 where
911 T: TryInto<Multiaddr> + Display + Copy,
912 T::Error: Error + Send + Sync + 'static,
913 {
914 let mut addrs = vec![];
915 let mut errors = vec![];
916
917 for (index, addr) in bootnodes_addresses.into_iter().enumerate() {
918 match addr.try_into() {
919 Ok(addr) => addrs.push(addr),
920 Err(error) => errors.push(
921 FieldError::BootnodesAddress(index, addr.to_string(), error.into()).into(),
922 ),
923 }
924 }
925
926 Self::transition(
927 ParachainConfig {
928 bootnodes_addresses: addrs,
929 ..self.config
930 },
931 self.validation_context,
932 merge_errors_vecs(self.errors, errors),
933 )
934 }
935
936 pub fn without_default_bootnodes(self) -> Self {
938 Self::transition(
939 ParachainConfig {
940 no_default_bootnodes: true,
941 ..self.config
942 },
943 self.validation_context,
944 self.errors,
945 )
946 }
947
948 pub fn with_collator(
950 self,
951 f: impl FnOnce(NodeConfigBuilder<node::Initial>) -> NodeConfigBuilder<node::Buildable>,
952 ) -> ParachainConfigBuilder<WithAtLeastOneCollator, C> {
953 match self.create_node_builder(f).validator(true).build() {
954 Ok(collator) => Self::transition(
955 ParachainConfig {
956 collators: vec![collator],
957 ..self.config
958 },
959 self.validation_context,
960 self.errors,
961 ),
962 Err((name, errors)) => Self::transition(
963 self.config,
964 self.validation_context,
965 merge_errors_vecs(
966 self.errors,
967 errors
968 .into_iter()
969 .map(|error| ConfigError::Collator(name.clone(), error).into())
970 .collect::<Vec<_>>(),
971 ),
972 ),
973 }
974 }
975
976 pub fn with_fullnode(
979 self,
980 f: impl FnOnce(NodeConfigBuilder<node::Initial>) -> NodeConfigBuilder<node::Buildable>,
981 ) -> ParachainConfigBuilder<WithAtLeastOneCollator, C> {
982 match self.create_node_builder(f).validator(false).build() {
983 Ok(node) => Self::transition(
984 ParachainConfig {
985 collators: vec![node],
986 ..self.config
987 },
988 self.validation_context,
989 self.errors,
990 ),
991 Err((name, errors)) => Self::transition(
992 self.config,
993 self.validation_context,
994 merge_errors_vecs(
995 self.errors,
996 errors
997 .into_iter()
998 .map(|error| ConfigError::Collator(name.clone(), error).into())
999 .collect::<Vec<_>>(),
1000 ),
1001 ),
1002 }
1003 }
1004
1005 #[deprecated(
1009 since = "0.4.0",
1010 note = "Use `with_collator()` for collator nodes or `with_fullnode()` for full nodes instead"
1011 )]
1012 pub fn with_node(
1013 self,
1014 f: impl FnOnce(NodeConfigBuilder<node::Initial>) -> NodeConfigBuilder<node::Buildable>,
1015 ) -> ParachainConfigBuilder<WithAtLeastOneCollator, C> {
1016 match self.create_node_builder(f).build() {
1017 Ok(node) => Self::transition(
1018 ParachainConfig {
1019 collators: vec![node],
1020 ..self.config
1021 },
1022 self.validation_context,
1023 self.errors,
1024 ),
1025 Err((name, errors)) => Self::transition(
1026 self.config,
1027 self.validation_context,
1028 merge_errors_vecs(
1029 self.errors,
1030 errors
1031 .into_iter()
1032 .map(|error| ConfigError::Collator(name.clone(), error).into())
1033 .collect::<Vec<_>>(),
1034 ),
1035 ),
1036 }
1037 }
1038
1039 pub fn with_collator_group(
1041 self,
1042 f: impl FnOnce(GroupNodeConfigBuilder<node::Initial>) -> GroupNodeConfigBuilder<node::Buildable>,
1043 ) -> ParachainConfigBuilder<WithAtLeastOneCollator, C> {
1044 match f(GroupNodeConfigBuilder::new(
1045 self.default_chain_context(),
1046 self.validation_context.clone(),
1047 ))
1048 .build()
1049 {
1050 Ok(group) => Self::transition(
1051 ParachainConfig {
1052 collator_groups: [self.config.collator_groups, vec![group]].concat(),
1053 ..self.config
1054 },
1055 self.validation_context,
1056 self.errors,
1057 ),
1058 Err((name, errors)) => Self::transition(
1059 self.config,
1060 self.validation_context,
1061 merge_errors_vecs(
1062 self.errors,
1063 errors
1064 .into_iter()
1065 .map(|error| ConfigError::Collator(name.clone(), error).into())
1066 .collect::<Vec<_>>(),
1067 ),
1068 ),
1069 }
1070 }
1071
1072 pub fn with_raw_spec_override(self, overrides: impl Into<JsonOverrides>) -> Self {
1074 Self::transition(
1075 ParachainConfig {
1076 raw_spec_override: Some(overrides.into()),
1077 ..self.config
1078 },
1079 self.validation_context,
1080 self.errors,
1081 )
1082 }
1083}
1084
1085impl<C: Context> ParachainConfigBuilder<WithAtLeastOneCollator, C> {
1086 pub fn with_collator(
1088 self,
1089 f: impl FnOnce(NodeConfigBuilder<node::Initial>) -> NodeConfigBuilder<node::Buildable>,
1090 ) -> Self {
1091 match self.create_node_builder(f).validator(true).build() {
1092 Ok(collator) => Self::transition(
1093 ParachainConfig {
1094 collators: [self.config.collators, vec![collator]].concat(),
1095 ..self.config
1096 },
1097 self.validation_context,
1098 self.errors,
1099 ),
1100 Err((name, errors)) => Self::transition(
1101 self.config,
1102 self.validation_context,
1103 merge_errors_vecs(
1104 self.errors,
1105 errors
1106 .into_iter()
1107 .map(|error| ConfigError::Collator(name.clone(), error).into())
1108 .collect::<Vec<_>>(),
1109 ),
1110 ),
1111 }
1112 }
1113
1114 pub fn with_fullnode(
1117 self,
1118 f: impl FnOnce(NodeConfigBuilder<node::Initial>) -> NodeConfigBuilder<node::Buildable>,
1119 ) -> Self {
1120 match self.create_node_builder(f).validator(false).build() {
1121 Ok(node) => Self::transition(
1122 ParachainConfig {
1123 collators: [self.config.collators, vec![node]].concat(),
1124 ..self.config
1125 },
1126 self.validation_context,
1127 self.errors,
1128 ),
1129 Err((name, errors)) => Self::transition(
1130 self.config,
1131 self.validation_context,
1132 merge_errors_vecs(
1133 self.errors,
1134 errors
1135 .into_iter()
1136 .map(|error| ConfigError::Collator(name.clone(), error).into())
1137 .collect::<Vec<_>>(),
1138 ),
1139 ),
1140 }
1141 }
1142
1143 #[deprecated(
1147 since = "0.4.0",
1148 note = "Use `with_collator()` for collator nodes or `with_fullnode()` for full nodes instead"
1149 )]
1150 pub fn with_node(
1151 self,
1152 f: impl FnOnce(NodeConfigBuilder<node::Initial>) -> NodeConfigBuilder<node::Buildable>,
1153 ) -> Self {
1154 match self.create_node_builder(f).build() {
1155 Ok(node) => Self::transition(
1156 ParachainConfig {
1157 collators: [self.config.collators, vec![node]].concat(),
1158 ..self.config
1159 },
1160 self.validation_context,
1161 self.errors,
1162 ),
1163 Err((name, errors)) => Self::transition(
1164 self.config,
1165 self.validation_context,
1166 merge_errors_vecs(
1167 self.errors,
1168 errors
1169 .into_iter()
1170 .map(|error| ConfigError::Collator(name.clone(), error).into())
1171 .collect::<Vec<_>>(),
1172 ),
1173 ),
1174 }
1175 }
1176
1177 pub fn with_collator_group(
1179 self,
1180 f: impl FnOnce(GroupNodeConfigBuilder<node::Initial>) -> GroupNodeConfigBuilder<node::Buildable>,
1181 ) -> Self {
1182 match f(GroupNodeConfigBuilder::new(
1183 self.default_chain_context(),
1184 self.validation_context.clone(),
1185 ))
1186 .build()
1187 {
1188 Ok(group) => Self::transition(
1189 ParachainConfig {
1190 collator_groups: [self.config.collator_groups, vec![group]].concat(),
1191 ..self.config
1192 },
1193 self.validation_context,
1194 self.errors,
1195 ),
1196 Err((name, errors)) => Self::transition(
1197 self.config,
1198 self.validation_context,
1199 merge_errors_vecs(
1200 self.errors,
1201 errors
1202 .into_iter()
1203 .map(|error| ConfigError::Collator(name.clone(), error).into())
1204 .collect::<Vec<_>>(),
1205 ),
1206 ),
1207 }
1208 }
1209
1210 pub fn build(self) -> Result<ParachainConfig, Vec<anyhow::Error>> {
1212 if !self.errors.is_empty() {
1213 return Err(self
1214 .errors
1215 .into_iter()
1216 .map(|error| ConfigError::Parachain(self.config.id, error).into())
1217 .collect::<Vec<_>>());
1218 }
1219
1220 Ok(self.config)
1221 }
1222}
1223
1224#[cfg(test)]
1225mod tests {
1226 use super::*;
1227 use crate::NetworkConfig;
1228
1229 #[test]
1230 fn parachain_config_builder_should_succeeds_and_returns_a_new_parachain_config() {
1231 let parachain_config = ParachainConfigBuilder::new(Default::default())
1232 .with_id(1000)
1233 .with_chain("mychainname")
1234 .with_registration_strategy(RegistrationStrategy::UsingExtrinsic)
1235 .with_num_cores(3)
1236 .onboard_as_parachain(false)
1237 .with_initial_balance(100_000_042)
1238 .with_default_image("myrepo:myimage")
1239 .with_default_command("default_command")
1240 .with_default_resources(|resources| {
1241 resources
1242 .with_limit_cpu("500M")
1243 .with_limit_memory("1G")
1244 .with_request_cpu("250M")
1245 })
1246 .with_default_db_snapshot("https://www.urltomysnapshot.com/file.tgz")
1247 .with_default_args(vec![("--arg1", "value1").into(), "--option2".into()])
1248 .with_genesis_wasm_path("https://www.backupsite.com/my/wasm/file.tgz")
1249 .with_genesis_wasm_generator("generator_wasm")
1250 .with_genesis_state_path("./path/to/genesis/state")
1251 .with_genesis_state_generator(
1252 "undying-collator export-genesis-state --pov-size=10000 --pvf-complexity=1",
1253 )
1254 .with_chain_spec_path("./path/to/chain/spec.json")
1255 .with_chain_spec_runtime("./path/to/runtime.wasm", Some("dev"))
1256 .with_wasm_override("./path/to/override/runtime.wasm")
1257 .with_raw_spec_override("./path/to/override/rawspec.json")
1258 .cumulus_based(false)
1259 .evm_based(false)
1260 .with_raw_bootnodes_addresses(vec![
1261 "/ip4/10.41.122.55/tcp/45421",
1262 "/ip4/51.144.222.10/tcp/2333",
1263 ])
1264 .without_default_bootnodes()
1265 .with_collator(|collator| {
1266 collator
1267 .with_name("collator1")
1268 .with_command("command1")
1269 .bootnode(true)
1270 })
1271 .with_collator(|collator| {
1272 collator
1273 .with_name("collator2")
1274 .with_command("command2")
1275 .validator(true)
1276 })
1277 .build()
1278 .unwrap();
1279
1280 assert_eq!(parachain_config.id(), 1000);
1281 assert_eq!(parachain_config.num_cores(), Some(3));
1282 assert_eq!(parachain_config.collators().len(), 2);
1283 let &collator1 = parachain_config.collators().first().unwrap();
1284 assert_eq!(collator1.name(), "collator1");
1285 assert_eq!(collator1.command().unwrap().as_str(), "command1");
1286 assert!(collator1.is_bootnode());
1287 let &collator2 = parachain_config.collators().last().unwrap();
1288 assert_eq!(collator2.name(), "collator2");
1289 assert_eq!(collator2.command().unwrap().as_str(), "command2");
1290 assert!(collator2.is_validator());
1291 assert_eq!(parachain_config.chain().unwrap().as_str(), "mychainname");
1292
1293 assert_eq!(
1294 parachain_config.registration_strategy().unwrap(),
1295 &RegistrationStrategy::UsingExtrinsic
1296 );
1297 assert!(!parachain_config.onboard_as_parachain());
1298 assert_eq!(parachain_config.initial_balance(), 100_000_042);
1299 assert_eq!(
1300 parachain_config.default_command().unwrap().as_str(),
1301 "default_command"
1302 );
1303 assert_eq!(
1304 parachain_config.default_image().unwrap().as_str(),
1305 "myrepo:myimage"
1306 );
1307 let default_resources = parachain_config.default_resources().unwrap();
1308 assert_eq!(default_resources.limit_cpu().unwrap().as_str(), "500M");
1309 assert_eq!(default_resources.limit_memory().unwrap().as_str(), "1G");
1310 assert_eq!(default_resources.request_cpu().unwrap().as_str(), "250M");
1311 assert!(matches!(
1312 parachain_config.default_db_snapshot().unwrap(),
1313 AssetLocation::Url(value) if value.as_str() == "https://www.urltomysnapshot.com/file.tgz",
1314 ));
1315 assert!(matches!(
1316 parachain_config.chain_spec_path().unwrap(),
1317 AssetLocation::FilePath(value) if value.to_str().unwrap() == "./path/to/chain/spec.json"
1318 ));
1319 assert!(matches!(
1320 parachain_config.wasm_override().unwrap(),
1321 AssetLocation::FilePath(value) if value.to_str().unwrap() == "./path/to/override/runtime.wasm"
1322 ));
1323 assert!(matches!(
1324 ¶chain_config.chain_spec_runtime().unwrap().location,
1325 AssetLocation::FilePath(value) if value.to_str().unwrap() == "./path/to/runtime.wasm"
1326 ));
1327 assert_eq!(
1328 parachain_config
1329 .chain_spec_runtime()
1330 .unwrap()
1331 .preset
1332 .as_deref(),
1333 Some("dev")
1334 );
1335
1336 let args: Vec<Arg> = vec![("--arg1", "value1").into(), "--option2".into()];
1337 assert_eq!(
1338 parachain_config.default_args(),
1339 args.iter().collect::<Vec<_>>()
1340 );
1341 assert!(matches!(
1342 parachain_config.genesis_wasm_path().unwrap(),
1343 AssetLocation::Url(value) if value.as_str() == "https://www.backupsite.com/my/wasm/file.tgz"
1344 ));
1345 assert_eq!(
1346 parachain_config.genesis_wasm_generator().unwrap().as_str(),
1347 "generator_wasm"
1348 );
1349 assert!(matches!(
1350 parachain_config.genesis_state_path().unwrap(),
1351 AssetLocation::FilePath(value) if value.to_str().unwrap() == "./path/to/genesis/state"
1352 ));
1353 assert_eq!(
1354 parachain_config
1355 .genesis_state_generator()
1356 .unwrap()
1357 .cmd()
1358 .as_str(),
1359 "undying-collator"
1360 );
1361
1362 assert_eq!(
1363 parachain_config.genesis_state_generator().unwrap().args(),
1364 &vec![
1365 "export-genesis-state".into(),
1366 ("--pov-size", "10000").into(),
1367 ("--pvf-complexity", "1").into()
1368 ]
1369 );
1370
1371 assert!(matches!(
1372 parachain_config.chain_spec_path().unwrap(),
1373 AssetLocation::FilePath(value) if value.to_str().unwrap() == "./path/to/chain/spec.json"
1374 ));
1375 assert!(!parachain_config.is_cumulus_based());
1376 let bootnodes_addresses: Vec<Multiaddr> = vec![
1377 "/ip4/10.41.122.55/tcp/45421".try_into().unwrap(),
1378 "/ip4/51.144.222.10/tcp/2333".try_into().unwrap(),
1379 ];
1380 assert!(parachain_config.no_default_bootnodes());
1381 assert_eq!(
1382 parachain_config.bootnodes_addresses(),
1383 bootnodes_addresses.iter().collect::<Vec<_>>()
1384 );
1385 assert!(!parachain_config.is_evm_based());
1386 assert!(matches!(
1387 parachain_config.raw_spec_override().unwrap(),
1388 JsonOverrides::Location(AssetLocation::FilePath(value)) if value.to_str().unwrap() == "./path/to/override/rawspec.json"
1389 ));
1390 }
1391
1392 #[test]
1393 fn parachain_config_builder_should_works_when_genesis_state_generator_contains_args() {
1394 let parachain_config = ParachainConfigBuilder::new(Default::default())
1395 .with_id(1000)
1396 .with_chain("myparachain")
1397 .with_genesis_state_generator("generator_state --simple-flag --flag=value")
1398 .with_collator(|collator| {
1399 collator
1400 .with_name("collator")
1401 .with_command("command")
1402 .validator(true)
1403 })
1404 .build()
1405 .unwrap();
1406
1407 assert_eq!(
1408 parachain_config
1409 .genesis_state_generator()
1410 .unwrap()
1411 .cmd()
1412 .as_str(),
1413 "generator_state"
1414 );
1415
1416 assert_eq!(
1417 parachain_config
1418 .genesis_state_generator()
1419 .unwrap()
1420 .args()
1421 .len(),
1422 2
1423 );
1424
1425 let args = parachain_config.genesis_state_generator().unwrap().args();
1426
1427 assert_eq!(
1428 args,
1429 &vec![
1430 Arg::Flag("--simple-flag".into()),
1431 Arg::Option("--flag".into(), "value".into())
1432 ]
1433 );
1434 }
1435
1436 #[test]
1437 fn parachain_config_builder_should_fails_and_returns_an_error_if_chain_is_invalid() {
1438 let errors = ParachainConfigBuilder::new(Default::default())
1439 .with_id(1000)
1440 .with_chain("invalid chain")
1441 .with_collator(|collator| {
1442 collator
1443 .with_name("collator")
1444 .with_command("command")
1445 .validator(true)
1446 })
1447 .build()
1448 .unwrap_err();
1449
1450 assert_eq!(errors.len(), 1);
1451 assert_eq!(
1452 errors.first().unwrap().to_string(),
1453 "parachain[1000].chain: 'invalid chain' shouldn't contains whitespace"
1454 );
1455 }
1456
1457 #[test]
1458 fn parachain_config_builder_should_fails_and_returns_an_error_if_default_command_is_invalid() {
1459 let errors = ParachainConfigBuilder::new(Default::default())
1460 .with_id(1000)
1461 .with_chain("chain")
1462 .with_default_command("invalid command")
1463 .with_collator(|collator| {
1464 collator
1465 .with_name("node")
1466 .with_command("command")
1467 .validator(true)
1468 })
1469 .build()
1470 .unwrap_err();
1471
1472 assert_eq!(errors.len(), 1);
1473 assert_eq!(
1474 errors.first().unwrap().to_string(),
1475 "parachain[1000].default_command: 'invalid command' shouldn't contains whitespace"
1476 );
1477 }
1478
1479 #[test]
1480 fn parachain_config_builder_should_fails_and_returns_an_error_if_default_image_is_invalid() {
1481 let errors = ParachainConfigBuilder::new(Default::default())
1482 .with_id(1000)
1483 .with_chain("chain")
1484 .with_default_image("invalid image")
1485 .with_collator(|collator| {
1486 collator
1487 .with_name("node")
1488 .with_command("command")
1489 .validator(true)
1490 })
1491 .build()
1492 .unwrap_err();
1493
1494 assert_eq!(errors.len(), 1);
1495 assert_eq!(
1496 errors.first().unwrap().to_string(),
1497 r"parachain[1000].default_image: 'invalid image' doesn't match regex '^([ip]|[hostname]/)?[tag_name]:[tag_version]?$'"
1498 );
1499 }
1500
1501 #[test]
1502 fn parachain_config_builder_should_fails_and_returns_an_error_if_default_resources_are_invalid()
1503 {
1504 let errors = ParachainConfigBuilder::new(Default::default())
1505 .with_id(1000)
1506 .with_chain("chain")
1507 .with_default_resources(|default_resources| {
1508 default_resources
1509 .with_limit_memory("100m")
1510 .with_request_cpu("invalid")
1511 })
1512 .with_collator(|collator| {
1513 collator
1514 .with_name("node")
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 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)?$'"
1525 );
1526 }
1527
1528 #[test]
1529 fn parachain_config_builder_should_fails_and_returns_an_error_if_genesis_wasm_generator_is_invalid(
1530 ) {
1531 let errors = ParachainConfigBuilder::new(Default::default())
1532 .with_id(2000)
1533 .with_chain("myparachain")
1534 .with_genesis_wasm_generator("invalid command")
1535 .with_collator(|collator| {
1536 collator
1537 .with_name("collator")
1538 .with_command("command")
1539 .validator(true)
1540 })
1541 .build()
1542 .unwrap_err();
1543
1544 assert_eq!(errors.len(), 1);
1545 assert_eq!(
1546 errors.first().unwrap().to_string(),
1547 "parachain[2000].genesis_wasm_generator: 'invalid command' shouldn't contains whitespace"
1548 );
1549 }
1550
1551 #[test]
1552 fn parachain_config_builder_should_fails_and_returns_an_error_if_bootnodes_addresses_are_invalid(
1553 ) {
1554 let errors = ParachainConfigBuilder::new(Default::default())
1555 .with_id(2000)
1556 .with_chain("myparachain")
1557 .with_raw_bootnodes_addresses(vec!["/ip4//tcp/45421", "//10.42.153.10/tcp/43111"])
1558 .with_collator(|collator| {
1559 collator
1560 .with_name("collator")
1561 .with_command("command")
1562 .validator(true)
1563 })
1564 .build()
1565 .unwrap_err();
1566
1567 assert_eq!(errors.len(), 2);
1568 assert_eq!(
1569 errors.first().unwrap().to_string(),
1570 "parachain[2000].bootnodes_addresses[0]: '/ip4//tcp/45421' failed to parse: invalid IPv4 address syntax"
1571 );
1572 assert_eq!(
1573 errors.get(1).unwrap().to_string(),
1574 "parachain[2000].bootnodes_addresses[1]: '//10.42.153.10/tcp/43111' unknown protocol string: "
1575 );
1576 }
1577
1578 #[test]
1579 fn parachain_config_builder_should_fails_and_returns_an_error_if_first_collator_is_invalid() {
1580 let errors = ParachainConfigBuilder::new(Default::default())
1581 .with_id(1000)
1582 .with_chain("myparachain")
1583 .with_collator(|collator| {
1584 collator
1585 .with_name("collator")
1586 .with_command("invalid command")
1587 })
1588 .build()
1589 .unwrap_err();
1590
1591 assert_eq!(errors.len(), 1);
1592 assert_eq!(
1593 errors.first().unwrap().to_string(),
1594 "parachain[1000].collators['collator'].command: 'invalid command' shouldn't contains whitespace"
1595 );
1596 }
1597
1598 #[test]
1599 fn parachain_config_builder_with_at_least_one_collator_should_fails_and_returns_an_error_if_second_collator_is_invalid(
1600 ) {
1601 let errors = ParachainConfigBuilder::new(Default::default())
1602 .with_id(2000)
1603 .with_chain("myparachain")
1604 .with_collator(|collator| {
1605 collator
1606 .with_name("collator1")
1607 .with_command("command1")
1608 .invulnerable(true)
1609 .bootnode(true)
1610 })
1611 .with_collator(|collator| {
1612 collator
1613 .with_name("collator2")
1614 .with_command("invalid command")
1615 .with_initial_balance(20000000)
1616 })
1617 .build()
1618 .unwrap_err();
1619
1620 assert_eq!(errors.len(), 1);
1621 assert_eq!(
1622 errors.first().unwrap().to_string(),
1623 "parachain[2000].collators['collator2'].command: 'invalid command' shouldn't contains whitespace"
1624 );
1625 }
1626
1627 #[test]
1628 fn parachain_config_builder_should_fails_and_returns_multiple_errors_if_multiple_fields_are_invalid(
1629 ) {
1630 let errors = ParachainConfigBuilder::new(Default::default())
1631 .with_id(2000)
1632 .with_chain("myparachain")
1633 .with_raw_bootnodes_addresses(vec!["/ip4//tcp/45421", "//10.42.153.10/tcp/43111"])
1634 .with_collator(|collator| {
1635 collator
1636 .with_name("collator1")
1637 .with_command("invalid command")
1638 .invulnerable(true)
1639 .bootnode(true)
1640 .with_resources(|resources| {
1641 resources
1642 .with_limit_cpu("invalid")
1643 .with_request_memory("1G")
1644 })
1645 })
1646 .with_collator(|collator| {
1647 collator
1648 .with_name("collator2")
1649 .with_command("command2")
1650 .with_image("invalid.image")
1651 .with_initial_balance(20000000)
1652 })
1653 .build()
1654 .unwrap_err();
1655
1656 assert_eq!(errors.len(), 5);
1657 assert_eq!(
1658 errors.first().unwrap().to_string(),
1659 "parachain[2000].bootnodes_addresses[0]: '/ip4//tcp/45421' failed to parse: invalid IPv4 address syntax"
1660 );
1661 assert_eq!(
1662 errors.get(1).unwrap().to_string(),
1663 "parachain[2000].bootnodes_addresses[1]: '//10.42.153.10/tcp/43111' unknown protocol string: "
1664 );
1665 assert_eq!(
1666 errors.get(2).unwrap().to_string(),
1667 "parachain[2000].collators['collator1'].command: 'invalid command' shouldn't contains whitespace"
1668 );
1669 assert_eq!(
1670 errors.get(3).unwrap().to_string(),
1671 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)?$'",
1672 );
1673 assert_eq!(
1674 errors.get(4).unwrap().to_string(),
1675 "parachain[2000].collators['collator2'].image: 'invalid.image' doesn't match regex '^([ip]|[hostname]/)?[tag_name]:[tag_version]?$'"
1676 );
1677 }
1678
1679 #[test]
1680 fn import_toml_registration_strategy_should_deserialize() {
1681 let load_from_toml =
1682 NetworkConfig::load_from_toml("./testing/snapshots/0001-big-network.toml").unwrap();
1683
1684 for parachain in load_from_toml.parachains().iter() {
1685 if parachain.id() == 1000 {
1686 assert_eq!(
1687 parachain.registration_strategy(),
1688 Some(&RegistrationStrategy::UsingExtrinsic)
1689 );
1690 }
1691 if parachain.id() == 2000 {
1692 assert_eq!(
1693 parachain.registration_strategy(),
1694 Some(&RegistrationStrategy::InGenesis)
1695 );
1696 }
1697 }
1698
1699 let load_from_toml_small = NetworkConfig::load_from_toml(
1700 "./testing/snapshots/0003-small-network_w_parachain.toml",
1701 )
1702 .unwrap();
1703
1704 let parachain = load_from_toml_small.parachains()[0];
1705 let parachain_evm = load_from_toml_small.parachains()[1];
1706
1707 assert_eq!(parachain.registration_strategy(), None);
1708 assert!(!parachain.is_evm_based());
1709 assert_eq!(parachain.collators().len(), 1);
1710 assert!(parachain_evm.is_evm_based());
1711 }
1712
1713 #[test]
1714 fn onboard_as_parachain_should_default_to_true() {
1715 let config = ParachainConfigBuilder::new(Default::default())
1716 .with_id(2000)
1717 .with_chain("myparachain")
1718 .with_collator(|collator| collator.with_name("collator"))
1719 .build()
1720 .unwrap();
1721
1722 assert!(config.onboard_as_parachain());
1723 }
1724
1725 #[test]
1726 fn evm_based_default_to_false() {
1727 let config = ParachainConfigBuilder::new(Default::default())
1728 .with_id(2000)
1729 .with_chain("myparachain")
1730 .with_collator(|collator| collator.with_name("collator"))
1731 .build()
1732 .unwrap();
1733
1734 assert!(!config.is_evm_based());
1735 }
1736
1737 #[test]
1738 fn evm_based() {
1739 let config = ParachainConfigBuilder::new(Default::default())
1740 .with_id(2000)
1741 .with_chain("myparachain")
1742 .evm_based(true)
1743 .with_collator(|collator| collator.with_name("collator"))
1744 .build()
1745 .unwrap();
1746
1747 assert!(config.is_evm_based());
1748 }
1749
1750 #[test]
1751 fn build_config_in_running_context() {
1752 let config = ParachainConfigBuilder::new_with_running(Default::default())
1753 .with_id(2000)
1754 .with_chain("myparachain")
1755 .with_collator(|collator| collator.with_name("collator"))
1756 .build()
1757 .unwrap();
1758
1759 assert_eq!(
1760 config.registration_strategy(),
1761 Some(&RegistrationStrategy::UsingExtrinsic)
1762 );
1763 }
1764
1765 #[test]
1766 fn parachain_config_builder_should_works_with_chain_spec_command() {
1767 const CMD_TPL: &str = "./bin/chain-spec-generator {% raw %} {{chainName}} {% endraw %}";
1768 let config = ParachainConfigBuilder::new(Default::default())
1769 .with_id(2000)
1770 .with_chain("some-chain")
1771 .with_default_image("myrepo:myimage")
1772 .with_default_command("default_command")
1773 .with_chain_spec_command(CMD_TPL)
1774 .with_collator(|collator| collator.with_name("collator"))
1775 .build()
1776 .unwrap();
1777
1778 assert_eq!(config.chain_spec_command(), Some(CMD_TPL));
1779 assert!(!config.chain_spec_command_is_local());
1780 }
1781
1782 #[test]
1783 fn parachain_config_builder_should_works_with_chain_spec_command_and_local() {
1784 const CMD_TPL: &str = "./bin/chain-spec-generator {% raw %} {{chainName}} {% endraw %}";
1785 let config = ParachainConfigBuilder::new(Default::default())
1786 .with_id(2000)
1787 .with_chain("some-chain")
1788 .with_default_image("myrepo:myimage")
1789 .with_default_command("default_command")
1790 .with_chain_spec_command(CMD_TPL)
1791 .chain_spec_command_is_local(true)
1792 .with_collator(|collator| collator.with_name("collator"))
1793 .build()
1794 .unwrap();
1795
1796 assert_eq!(config.chain_spec_command(), Some(CMD_TPL));
1797 assert!(config.chain_spec_command_is_local());
1798 }
1799
1800 #[test]
1801 fn parachain_with_group_config_builder_should_succeeds_and_returns_a_new_parachain_config() {
1802 let parachain_config = ParachainConfigBuilder::new(Default::default())
1803 .with_id(1000)
1804 .with_chain("mychainname")
1805 .with_registration_strategy(RegistrationStrategy::UsingExtrinsic)
1806 .onboard_as_parachain(false)
1807 .with_initial_balance(100_000_042)
1808 .with_default_image("myrepo:myimage")
1809 .with_default_command("default_command")
1810 .without_default_bootnodes()
1811 .with_collator(|collator| {
1812 collator
1813 .with_name("collator1")
1814 .with_command("command1")
1815 .bootnode(true)
1816 })
1817 .with_collator_group(|group| {
1818 group.with_count(2).with_base_node(|base| {
1819 base.with_name("collator_group1")
1820 .with_command("group_command1")
1821 .bootnode(true)
1822 })
1823 })
1824 .with_collator_group(|group| {
1825 group.with_count(3).with_base_node(|base| {
1826 base.with_name("collator_group2")
1827 .with_command("group_command2")
1828 .bootnode(false)
1829 })
1830 })
1831 .build()
1832 .unwrap();
1833
1834 assert_eq!(parachain_config.id(), 1000);
1835 assert_eq!(parachain_config.collators().len(), 1);
1836 assert_eq!(parachain_config.group_collators_configs().len(), 2);
1837
1838 let group_collator1 = parachain_config.group_collators_configs()[0].clone();
1839 assert_eq!(group_collator1.count, 2);
1840 let base_config1 = group_collator1.base_config;
1841 assert_eq!(base_config1.name(), "collator_group1");
1842 assert_eq!(base_config1.command().unwrap().as_str(), "group_command1");
1843 assert!(base_config1.is_bootnode());
1844
1845 let group_collator2 = parachain_config.group_collators_configs()[1].clone();
1846 assert_eq!(group_collator2.count, 3);
1847 let base_config2 = group_collator2.base_config;
1848 assert_eq!(base_config2.name(), "collator_group2");
1849 assert_eq!(base_config2.command().unwrap().as_str(), "group_command2");
1850 assert!(!base_config2.is_bootnode());
1851 }
1852
1853 #[test]
1854 fn parachain_with_group_count_0_config_builder_should_fail() {
1855 let parachain_config = ParachainConfigBuilder::new(Default::default())
1856 .with_id(1000)
1857 .with_chain("mychainname")
1858 .with_registration_strategy(RegistrationStrategy::UsingExtrinsic)
1859 .onboard_as_parachain(false)
1860 .with_initial_balance(100_000_042)
1861 .with_default_image("myrepo:myimage")
1862 .with_default_command("default_command")
1863 .without_default_bootnodes()
1864 .with_collator(|collator| {
1865 collator
1866 .with_name("collator1")
1867 .with_command("command1")
1868 .bootnode(true)
1869 })
1870 .with_collator_group(|group| {
1871 group.with_count(2).with_base_node(|base| {
1872 base.with_name("collator_group1")
1873 .with_command("group_command1")
1874 .bootnode(true)
1875 })
1876 })
1877 .with_collator_group(|group| {
1878 group.with_count(0).with_base_node(|base| {
1879 base.with_name("collator_group2")
1880 .with_command("group_command2")
1881 .bootnode(false)
1882 })
1883 })
1884 .build();
1885
1886 let errors: Vec<anyhow::Error> = match parachain_config {
1887 Ok(_) => vec![],
1888 Err(errs) => errs,
1889 };
1890
1891 assert_eq!(errors.len(), 1);
1892 assert_eq!(
1893 errors.first().unwrap().to_string(),
1894 "parachain[1000].collators['collator_group2'].Count cannot be zero"
1895 );
1896 }
1897}