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_optional_default_db_snapshot(
702 self,
703 location: Option<impl Into<AssetLocation>>,
704 ) -> Self {
705 match location {
706 Some(location) => self.with_default_db_snapshot(location),
707 None => self,
708 }
709 }
710
711 pub fn with_default_args(self, args: Vec<Arg>) -> Self {
713 Self::transition(
714 ParachainConfig {
715 default_args: args,
716 ..self.config
717 },
718 self.validation_context,
719 self.errors,
720 )
721 }
722
723 pub fn with_genesis_wasm_path(self, location: impl Into<AssetLocation>) -> Self {
725 Self::transition(
726 ParachainConfig {
727 genesis_wasm_path: Some(location.into()),
728 ..self.config
729 },
730 self.validation_context,
731 self.errors,
732 )
733 }
734
735 pub fn with_genesis_wasm_generator<T>(self, command: T) -> Self
737 where
738 T: TryInto<Command>,
739 T::Error: Error + Send + Sync + 'static,
740 {
741 match command.try_into() {
742 Ok(command) => Self::transition(
743 ParachainConfig {
744 genesis_wasm_generator: Some(command),
745 ..self.config
746 },
747 self.validation_context,
748 self.errors,
749 ),
750 Err(error) => Self::transition(
751 self.config,
752 self.validation_context,
753 merge_errors(
754 self.errors,
755 FieldError::GenesisWasmGenerator(error.into()).into(),
756 ),
757 ),
758 }
759 }
760
761 pub fn with_genesis_state_path(self, location: impl Into<AssetLocation>) -> Self {
763 Self::transition(
764 ParachainConfig {
765 genesis_state_path: Some(location.into()),
766 ..self.config
767 },
768 self.validation_context,
769 self.errors,
770 )
771 }
772
773 pub fn with_genesis_state_generator<T>(self, command: T) -> Self
775 where
776 T: TryInto<CommandWithCustomArgs>,
777 T::Error: Error + Send + Sync + 'static,
778 {
779 match command.try_into() {
780 Ok(command) => Self::transition(
781 ParachainConfig {
782 genesis_state_generator: Some(command),
783 ..self.config
784 },
785 self.validation_context,
786 self.errors,
787 ),
788 Err(error) => Self::transition(
789 self.config,
790 self.validation_context,
791 merge_errors(
792 self.errors,
793 FieldError::GenesisStateGenerator(error.into()).into(),
794 ),
795 ),
796 }
797 }
798
799 pub fn with_genesis_overrides(self, genesis_overrides: impl Into<serde_json::Value>) -> Self {
801 Self::transition(
802 ParachainConfig {
803 genesis_overrides: Some(genesis_overrides.into()),
804 ..self.config
805 },
806 self.validation_context,
807 self.errors,
808 )
809 }
810
811 pub fn with_chain_spec_path(self, location: impl Into<AssetLocation>) -> Self {
813 Self::transition(
814 ParachainConfig {
815 chain_spec_path: Some(location.into()),
816 ..self.config
817 },
818 self.validation_context,
819 self.errors,
820 )
821 }
822
823 pub fn with_chain_spec_command(self, cmd_template: impl Into<String>) -> Self {
825 Self::transition(
826 ParachainConfig {
827 chain_spec_command: Some(cmd_template.into()),
828 ..self.config
829 },
830 self.validation_context,
831 self.errors,
832 )
833 }
834
835 pub fn with_chain_spec_runtime(
839 self,
840 location: impl Into<AssetLocation>,
841 preset: Option<&str>,
842 ) -> Self {
843 let chain_spec_runtime = if let Some(preset) = preset {
844 ChainSpecRuntime::with_preset(location.into(), preset.to_string())
845 } else {
846 ChainSpecRuntime::new(location.into())
847 };
848 Self::transition(
849 ParachainConfig {
850 chain_spec_runtime: Some(chain_spec_runtime),
851 ..self.config
852 },
853 self.validation_context,
854 self.errors,
855 )
856 }
857
858 pub fn with_wasm_override(self, location: impl Into<AssetLocation>) -> Self {
860 Self::transition(
861 ParachainConfig {
862 wasm_override: Some(location.into()),
863 ..self.config
864 },
865 self.validation_context,
866 self.errors,
867 )
868 }
869
870 pub fn chain_spec_command_is_local(self, choice: bool) -> Self {
872 Self::transition(
873 ParachainConfig {
874 chain_spec_command_is_local: choice,
875 ..self.config
876 },
877 self.validation_context,
878 self.errors,
879 )
880 }
881
882 pub fn with_chain_spec_command_output_path(self, output_path: &str) -> Self {
884 Self::transition(
885 ParachainConfig {
886 chain_spec_command_output_path: Some(output_path.to_string()),
887 ..self.config
888 },
889 self.validation_context,
890 self.errors,
891 )
892 }
893
894 pub fn cumulus_based(self, choice: bool) -> Self {
896 Self::transition(
897 ParachainConfig {
898 is_cumulus_based: choice,
899 ..self.config
900 },
901 self.validation_context,
902 self.errors,
903 )
904 }
905
906 pub fn evm_based(self, choice: bool) -> Self {
908 Self::transition(
909 ParachainConfig {
910 is_evm_based: choice,
911 ..self.config
912 },
913 self.validation_context,
914 self.errors,
915 )
916 }
917
918 pub fn with_raw_bootnodes_addresses<T>(self, bootnodes_addresses: Vec<T>) -> Self
923 where
924 T: TryInto<Multiaddr> + Display + Copy,
925 T::Error: Error + Send + Sync + 'static,
926 {
927 let mut addrs = vec![];
928 let mut errors = vec![];
929
930 for (index, addr) in bootnodes_addresses.into_iter().enumerate() {
931 match addr.try_into() {
932 Ok(addr) => addrs.push(addr),
933 Err(error) => errors.push(
934 FieldError::BootnodesAddress(index, addr.to_string(), error.into()).into(),
935 ),
936 }
937 }
938
939 Self::transition(
940 ParachainConfig {
941 bootnodes_addresses: addrs,
942 ..self.config
943 },
944 self.validation_context,
945 merge_errors_vecs(self.errors, errors),
946 )
947 }
948
949 pub fn without_default_bootnodes(self) -> Self {
951 Self::transition(
952 ParachainConfig {
953 no_default_bootnodes: true,
954 ..self.config
955 },
956 self.validation_context,
957 self.errors,
958 )
959 }
960
961 pub fn with_collator(
963 self,
964 f: impl FnOnce(NodeConfigBuilder<node::Initial>) -> NodeConfigBuilder<node::Buildable>,
965 ) -> ParachainConfigBuilder<WithAtLeastOneCollator, C> {
966 match self.create_node_builder(f).validator(true).build() {
967 Ok(collator) => Self::transition(
968 ParachainConfig {
969 collators: vec![collator],
970 ..self.config
971 },
972 self.validation_context,
973 self.errors,
974 ),
975 Err((name, errors)) => Self::transition(
976 self.config,
977 self.validation_context,
978 merge_errors_vecs(
979 self.errors,
980 errors
981 .into_iter()
982 .map(|error| ConfigError::Collator(name.clone(), error).into())
983 .collect::<Vec<_>>(),
984 ),
985 ),
986 }
987 }
988
989 pub fn with_fullnode(
992 self,
993 f: impl FnOnce(NodeConfigBuilder<node::Initial>) -> NodeConfigBuilder<node::Buildable>,
994 ) -> ParachainConfigBuilder<WithAtLeastOneCollator, C> {
995 match self.create_node_builder(f).validator(false).build() {
996 Ok(node) => Self::transition(
997 ParachainConfig {
998 collators: vec![node],
999 ..self.config
1000 },
1001 self.validation_context,
1002 self.errors,
1003 ),
1004 Err((name, errors)) => Self::transition(
1005 self.config,
1006 self.validation_context,
1007 merge_errors_vecs(
1008 self.errors,
1009 errors
1010 .into_iter()
1011 .map(|error| ConfigError::Collator(name.clone(), error).into())
1012 .collect::<Vec<_>>(),
1013 ),
1014 ),
1015 }
1016 }
1017
1018 #[deprecated(
1022 since = "0.4.0",
1023 note = "Use `with_collator()` for collator nodes or `with_fullnode()` for full nodes instead"
1024 )]
1025 pub fn with_node(
1026 self,
1027 f: impl FnOnce(NodeConfigBuilder<node::Initial>) -> NodeConfigBuilder<node::Buildable>,
1028 ) -> ParachainConfigBuilder<WithAtLeastOneCollator, C> {
1029 match self.create_node_builder(f).build() {
1030 Ok(node) => Self::transition(
1031 ParachainConfig {
1032 collators: vec![node],
1033 ..self.config
1034 },
1035 self.validation_context,
1036 self.errors,
1037 ),
1038 Err((name, errors)) => Self::transition(
1039 self.config,
1040 self.validation_context,
1041 merge_errors_vecs(
1042 self.errors,
1043 errors
1044 .into_iter()
1045 .map(|error| ConfigError::Collator(name.clone(), error).into())
1046 .collect::<Vec<_>>(),
1047 ),
1048 ),
1049 }
1050 }
1051
1052 pub fn with_collator_group(
1054 self,
1055 f: impl FnOnce(GroupNodeConfigBuilder<node::Initial>) -> GroupNodeConfigBuilder<node::Buildable>,
1056 ) -> ParachainConfigBuilder<WithAtLeastOneCollator, C> {
1057 match f(GroupNodeConfigBuilder::new(
1058 self.default_chain_context(),
1059 self.validation_context.clone(),
1060 ))
1061 .build()
1062 {
1063 Ok(group) => Self::transition(
1064 ParachainConfig {
1065 collator_groups: [self.config.collator_groups, vec![group]].concat(),
1066 ..self.config
1067 },
1068 self.validation_context,
1069 self.errors,
1070 ),
1071 Err((name, errors)) => Self::transition(
1072 self.config,
1073 self.validation_context,
1074 merge_errors_vecs(
1075 self.errors,
1076 errors
1077 .into_iter()
1078 .map(|error| ConfigError::Collator(name.clone(), error).into())
1079 .collect::<Vec<_>>(),
1080 ),
1081 ),
1082 }
1083 }
1084
1085 pub fn with_raw_spec_override(self, overrides: impl Into<JsonOverrides>) -> Self {
1087 Self::transition(
1088 ParachainConfig {
1089 raw_spec_override: Some(overrides.into()),
1090 ..self.config
1091 },
1092 self.validation_context,
1093 self.errors,
1094 )
1095 }
1096}
1097
1098impl<C: Context> ParachainConfigBuilder<WithAtLeastOneCollator, C> {
1099 pub fn with_collator(
1101 self,
1102 f: impl FnOnce(NodeConfigBuilder<node::Initial>) -> NodeConfigBuilder<node::Buildable>,
1103 ) -> Self {
1104 match self.create_node_builder(f).validator(true).build() {
1105 Ok(collator) => Self::transition(
1106 ParachainConfig {
1107 collators: [self.config.collators, vec![collator]].concat(),
1108 ..self.config
1109 },
1110 self.validation_context,
1111 self.errors,
1112 ),
1113 Err((name, errors)) => Self::transition(
1114 self.config,
1115 self.validation_context,
1116 merge_errors_vecs(
1117 self.errors,
1118 errors
1119 .into_iter()
1120 .map(|error| ConfigError::Collator(name.clone(), error).into())
1121 .collect::<Vec<_>>(),
1122 ),
1123 ),
1124 }
1125 }
1126
1127 pub fn with_fullnode(
1130 self,
1131 f: impl FnOnce(NodeConfigBuilder<node::Initial>) -> NodeConfigBuilder<node::Buildable>,
1132 ) -> Self {
1133 match self.create_node_builder(f).validator(false).build() {
1134 Ok(node) => Self::transition(
1135 ParachainConfig {
1136 collators: [self.config.collators, vec![node]].concat(),
1137 ..self.config
1138 },
1139 self.validation_context,
1140 self.errors,
1141 ),
1142 Err((name, errors)) => Self::transition(
1143 self.config,
1144 self.validation_context,
1145 merge_errors_vecs(
1146 self.errors,
1147 errors
1148 .into_iter()
1149 .map(|error| ConfigError::Collator(name.clone(), error).into())
1150 .collect::<Vec<_>>(),
1151 ),
1152 ),
1153 }
1154 }
1155
1156 #[deprecated(
1160 since = "0.4.0",
1161 note = "Use `with_collator()` for collator nodes or `with_fullnode()` for full nodes instead"
1162 )]
1163 pub fn with_node(
1164 self,
1165 f: impl FnOnce(NodeConfigBuilder<node::Initial>) -> NodeConfigBuilder<node::Buildable>,
1166 ) -> Self {
1167 match self.create_node_builder(f).build() {
1168 Ok(node) => Self::transition(
1169 ParachainConfig {
1170 collators: [self.config.collators, vec![node]].concat(),
1171 ..self.config
1172 },
1173 self.validation_context,
1174 self.errors,
1175 ),
1176 Err((name, errors)) => Self::transition(
1177 self.config,
1178 self.validation_context,
1179 merge_errors_vecs(
1180 self.errors,
1181 errors
1182 .into_iter()
1183 .map(|error| ConfigError::Collator(name.clone(), error).into())
1184 .collect::<Vec<_>>(),
1185 ),
1186 ),
1187 }
1188 }
1189
1190 pub fn with_collator_group(
1192 self,
1193 f: impl FnOnce(GroupNodeConfigBuilder<node::Initial>) -> GroupNodeConfigBuilder<node::Buildable>,
1194 ) -> Self {
1195 match f(GroupNodeConfigBuilder::new(
1196 self.default_chain_context(),
1197 self.validation_context.clone(),
1198 ))
1199 .build()
1200 {
1201 Ok(group) => Self::transition(
1202 ParachainConfig {
1203 collator_groups: [self.config.collator_groups, vec![group]].concat(),
1204 ..self.config
1205 },
1206 self.validation_context,
1207 self.errors,
1208 ),
1209 Err((name, errors)) => Self::transition(
1210 self.config,
1211 self.validation_context,
1212 merge_errors_vecs(
1213 self.errors,
1214 errors
1215 .into_iter()
1216 .map(|error| ConfigError::Collator(name.clone(), error).into())
1217 .collect::<Vec<_>>(),
1218 ),
1219 ),
1220 }
1221 }
1222
1223 pub fn build(self) -> Result<ParachainConfig, Vec<anyhow::Error>> {
1225 if !self.errors.is_empty() {
1226 return Err(self
1227 .errors
1228 .into_iter()
1229 .map(|error| ConfigError::Parachain(self.config.id, error).into())
1230 .collect::<Vec<_>>());
1231 }
1232
1233 Ok(self.config)
1234 }
1235}
1236
1237#[cfg(test)]
1238mod tests {
1239 use super::*;
1240 use crate::NetworkConfig;
1241
1242 #[test]
1243 fn parachain_config_builder_should_succeeds_and_returns_a_new_parachain_config() {
1244 let parachain_config = ParachainConfigBuilder::new(Default::default())
1245 .with_id(1000)
1246 .with_chain("mychainname")
1247 .with_registration_strategy(RegistrationStrategy::UsingExtrinsic)
1248 .with_num_cores(3)
1249 .onboard_as_parachain(false)
1250 .with_initial_balance(100_000_042)
1251 .with_default_image("myrepo:myimage")
1252 .with_default_command("default_command")
1253 .with_default_resources(|resources| {
1254 resources
1255 .with_limit_cpu("500M")
1256 .with_limit_memory("1G")
1257 .with_request_cpu("250M")
1258 })
1259 .with_default_db_snapshot("https://www.urltomysnapshot.com/file.tgz")
1260 .with_default_args(vec![("--arg1", "value1").into(), "--option2".into()])
1261 .with_genesis_wasm_path("https://www.backupsite.com/my/wasm/file.tgz")
1262 .with_genesis_wasm_generator("generator_wasm")
1263 .with_genesis_state_path("./path/to/genesis/state")
1264 .with_genesis_state_generator(
1265 "undying-collator export-genesis-state --pov-size=10000 --pvf-complexity=1",
1266 )
1267 .with_chain_spec_path("./path/to/chain/spec.json")
1268 .with_chain_spec_runtime("./path/to/runtime.wasm", Some("dev"))
1269 .with_wasm_override("./path/to/override/runtime.wasm")
1270 .with_raw_spec_override("./path/to/override/rawspec.json")
1271 .cumulus_based(false)
1272 .evm_based(false)
1273 .with_raw_bootnodes_addresses(vec![
1274 "/ip4/10.41.122.55/tcp/45421",
1275 "/ip4/51.144.222.10/tcp/2333",
1276 ])
1277 .without_default_bootnodes()
1278 .with_collator(|collator| {
1279 collator
1280 .with_name("collator1")
1281 .with_command("command1")
1282 .bootnode(true)
1283 })
1284 .with_collator(|collator| {
1285 collator
1286 .with_name("collator2")
1287 .with_command("command2")
1288 .validator(true)
1289 })
1290 .build()
1291 .unwrap();
1292
1293 assert_eq!(parachain_config.id(), 1000);
1294 assert_eq!(parachain_config.num_cores(), Some(3));
1295 assert_eq!(parachain_config.collators().len(), 2);
1296 let &collator1 = parachain_config.collators().first().unwrap();
1297 assert_eq!(collator1.name(), "collator1");
1298 assert_eq!(collator1.command().unwrap().as_str(), "command1");
1299 assert!(collator1.is_bootnode());
1300 let &collator2 = parachain_config.collators().last().unwrap();
1301 assert_eq!(collator2.name(), "collator2");
1302 assert_eq!(collator2.command().unwrap().as_str(), "command2");
1303 assert!(collator2.is_validator());
1304 assert_eq!(parachain_config.chain().unwrap().as_str(), "mychainname");
1305
1306 assert_eq!(
1307 parachain_config.registration_strategy().unwrap(),
1308 &RegistrationStrategy::UsingExtrinsic
1309 );
1310 assert!(!parachain_config.onboard_as_parachain());
1311 assert_eq!(parachain_config.initial_balance(), 100_000_042);
1312 assert_eq!(
1313 parachain_config.default_command().unwrap().as_str(),
1314 "default_command"
1315 );
1316 assert_eq!(
1317 parachain_config.default_image().unwrap().as_str(),
1318 "myrepo:myimage"
1319 );
1320 let default_resources = parachain_config.default_resources().unwrap();
1321 assert_eq!(default_resources.limit_cpu().unwrap().as_str(), "500M");
1322 assert_eq!(default_resources.limit_memory().unwrap().as_str(), "1G");
1323 assert_eq!(default_resources.request_cpu().unwrap().as_str(), "250M");
1324 assert!(matches!(
1325 parachain_config.default_db_snapshot().unwrap(),
1326 AssetLocation::Url(value) if value.as_str() == "https://www.urltomysnapshot.com/file.tgz",
1327 ));
1328 assert!(matches!(
1329 parachain_config.chain_spec_path().unwrap(),
1330 AssetLocation::FilePath(value) if value.to_str().unwrap() == "./path/to/chain/spec.json"
1331 ));
1332 assert!(matches!(
1333 parachain_config.wasm_override().unwrap(),
1334 AssetLocation::FilePath(value) if value.to_str().unwrap() == "./path/to/override/runtime.wasm"
1335 ));
1336 assert!(matches!(
1337 ¶chain_config.chain_spec_runtime().unwrap().location,
1338 AssetLocation::FilePath(value) if value.to_str().unwrap() == "./path/to/runtime.wasm"
1339 ));
1340 assert_eq!(
1341 parachain_config
1342 .chain_spec_runtime()
1343 .unwrap()
1344 .preset
1345 .as_deref(),
1346 Some("dev")
1347 );
1348
1349 let args: Vec<Arg> = vec![("--arg1", "value1").into(), "--option2".into()];
1350 assert_eq!(
1351 parachain_config.default_args(),
1352 args.iter().collect::<Vec<_>>()
1353 );
1354 assert!(matches!(
1355 parachain_config.genesis_wasm_path().unwrap(),
1356 AssetLocation::Url(value) if value.as_str() == "https://www.backupsite.com/my/wasm/file.tgz"
1357 ));
1358 assert_eq!(
1359 parachain_config.genesis_wasm_generator().unwrap().as_str(),
1360 "generator_wasm"
1361 );
1362 assert!(matches!(
1363 parachain_config.genesis_state_path().unwrap(),
1364 AssetLocation::FilePath(value) if value.to_str().unwrap() == "./path/to/genesis/state"
1365 ));
1366 assert_eq!(
1367 parachain_config
1368 .genesis_state_generator()
1369 .unwrap()
1370 .cmd()
1371 .as_str(),
1372 "undying-collator"
1373 );
1374
1375 assert_eq!(
1376 parachain_config.genesis_state_generator().unwrap().args(),
1377 &vec![
1378 "export-genesis-state".into(),
1379 ("--pov-size", "10000").into(),
1380 ("--pvf-complexity", "1").into()
1381 ]
1382 );
1383
1384 assert!(matches!(
1385 parachain_config.chain_spec_path().unwrap(),
1386 AssetLocation::FilePath(value) if value.to_str().unwrap() == "./path/to/chain/spec.json"
1387 ));
1388 assert!(!parachain_config.is_cumulus_based());
1389 let bootnodes_addresses: Vec<Multiaddr> = vec![
1390 "/ip4/10.41.122.55/tcp/45421".try_into().unwrap(),
1391 "/ip4/51.144.222.10/tcp/2333".try_into().unwrap(),
1392 ];
1393 assert!(parachain_config.no_default_bootnodes());
1394 assert_eq!(
1395 parachain_config.bootnodes_addresses(),
1396 bootnodes_addresses.iter().collect::<Vec<_>>()
1397 );
1398 assert!(!parachain_config.is_evm_based());
1399 assert!(matches!(
1400 parachain_config.raw_spec_override().unwrap(),
1401 JsonOverrides::Location(AssetLocation::FilePath(value)) if value.to_str().unwrap() == "./path/to/override/rawspec.json"
1402 ));
1403 }
1404
1405 #[test]
1406 fn parachain_config_builder_should_works_when_genesis_state_generator_contains_args() {
1407 let parachain_config = ParachainConfigBuilder::new(Default::default())
1408 .with_id(1000)
1409 .with_chain("myparachain")
1410 .with_genesis_state_generator("generator_state --simple-flag --flag=value")
1411 .with_collator(|collator| {
1412 collator
1413 .with_name("collator")
1414 .with_command("command")
1415 .validator(true)
1416 })
1417 .build()
1418 .unwrap();
1419
1420 assert_eq!(
1421 parachain_config
1422 .genesis_state_generator()
1423 .unwrap()
1424 .cmd()
1425 .as_str(),
1426 "generator_state"
1427 );
1428
1429 assert_eq!(
1430 parachain_config
1431 .genesis_state_generator()
1432 .unwrap()
1433 .args()
1434 .len(),
1435 2
1436 );
1437
1438 let args = parachain_config.genesis_state_generator().unwrap().args();
1439
1440 assert_eq!(
1441 args,
1442 &vec![
1443 Arg::Flag("--simple-flag".into()),
1444 Arg::Option("--flag".into(), "value".into())
1445 ]
1446 );
1447 }
1448
1449 #[test]
1450 fn parachain_config_builder_should_fails_and_returns_an_error_if_chain_is_invalid() {
1451 let errors = ParachainConfigBuilder::new(Default::default())
1452 .with_id(1000)
1453 .with_chain("invalid chain")
1454 .with_collator(|collator| {
1455 collator
1456 .with_name("collator")
1457 .with_command("command")
1458 .validator(true)
1459 })
1460 .build()
1461 .unwrap_err();
1462
1463 assert_eq!(errors.len(), 1);
1464 assert_eq!(
1465 errors.first().unwrap().to_string(),
1466 "parachain[1000].chain: 'invalid chain' shouldn't contains whitespace"
1467 );
1468 }
1469
1470 #[test]
1471 fn parachain_config_builder_should_fails_and_returns_an_error_if_default_command_is_invalid() {
1472 let errors = ParachainConfigBuilder::new(Default::default())
1473 .with_id(1000)
1474 .with_chain("chain")
1475 .with_default_command("invalid command")
1476 .with_collator(|collator| {
1477 collator
1478 .with_name("node")
1479 .with_command("command")
1480 .validator(true)
1481 })
1482 .build()
1483 .unwrap_err();
1484
1485 assert_eq!(errors.len(), 1);
1486 assert_eq!(
1487 errors.first().unwrap().to_string(),
1488 "parachain[1000].default_command: 'invalid command' shouldn't contains whitespace"
1489 );
1490 }
1491
1492 #[test]
1493 fn parachain_config_builder_should_fails_and_returns_an_error_if_default_image_is_invalid() {
1494 let errors = ParachainConfigBuilder::new(Default::default())
1495 .with_id(1000)
1496 .with_chain("chain")
1497 .with_default_image("invalid image")
1498 .with_collator(|collator| {
1499 collator
1500 .with_name("node")
1501 .with_command("command")
1502 .validator(true)
1503 })
1504 .build()
1505 .unwrap_err();
1506
1507 assert_eq!(errors.len(), 1);
1508 assert_eq!(
1509 errors.first().unwrap().to_string(),
1510 r"parachain[1000].default_image: 'invalid image' doesn't match regex '^([ip]|[hostname]/)?[tag_name]:[tag_version]?$'"
1511 );
1512 }
1513
1514 #[test]
1515 fn parachain_config_builder_should_fails_and_returns_an_error_if_default_resources_are_invalid()
1516 {
1517 let errors = ParachainConfigBuilder::new(Default::default())
1518 .with_id(1000)
1519 .with_chain("chain")
1520 .with_default_resources(|default_resources| {
1521 default_resources
1522 .with_limit_memory("100m")
1523 .with_request_cpu("invalid")
1524 })
1525 .with_collator(|collator| {
1526 collator
1527 .with_name("node")
1528 .with_command("command")
1529 .validator(true)
1530 })
1531 .build()
1532 .unwrap_err();
1533
1534 assert_eq!(errors.len(), 1);
1535 assert_eq!(
1536 errors.first().unwrap().to_string(),
1537 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)?$'"
1538 );
1539 }
1540
1541 #[test]
1542 fn parachain_config_builder_should_fails_and_returns_an_error_if_genesis_wasm_generator_is_invalid(
1543 ) {
1544 let errors = ParachainConfigBuilder::new(Default::default())
1545 .with_id(2000)
1546 .with_chain("myparachain")
1547 .with_genesis_wasm_generator("invalid command")
1548 .with_collator(|collator| {
1549 collator
1550 .with_name("collator")
1551 .with_command("command")
1552 .validator(true)
1553 })
1554 .build()
1555 .unwrap_err();
1556
1557 assert_eq!(errors.len(), 1);
1558 assert_eq!(
1559 errors.first().unwrap().to_string(),
1560 "parachain[2000].genesis_wasm_generator: 'invalid command' shouldn't contains whitespace"
1561 );
1562 }
1563
1564 #[test]
1565 fn parachain_config_builder_should_fails_and_returns_an_error_if_bootnodes_addresses_are_invalid(
1566 ) {
1567 let errors = ParachainConfigBuilder::new(Default::default())
1568 .with_id(2000)
1569 .with_chain("myparachain")
1570 .with_raw_bootnodes_addresses(vec!["/ip4//tcp/45421", "//10.42.153.10/tcp/43111"])
1571 .with_collator(|collator| {
1572 collator
1573 .with_name("collator")
1574 .with_command("command")
1575 .validator(true)
1576 })
1577 .build()
1578 .unwrap_err();
1579
1580 assert_eq!(errors.len(), 2);
1581 assert_eq!(
1582 errors.first().unwrap().to_string(),
1583 "parachain[2000].bootnodes_addresses[0]: '/ip4//tcp/45421' failed to parse: invalid IPv4 address syntax"
1584 );
1585 assert_eq!(
1586 errors.get(1).unwrap().to_string(),
1587 "parachain[2000].bootnodes_addresses[1]: '//10.42.153.10/tcp/43111' unknown protocol string: "
1588 );
1589 }
1590
1591 #[test]
1592 fn parachain_config_builder_should_fails_and_returns_an_error_if_first_collator_is_invalid() {
1593 let errors = ParachainConfigBuilder::new(Default::default())
1594 .with_id(1000)
1595 .with_chain("myparachain")
1596 .with_collator(|collator| {
1597 collator
1598 .with_name("collator")
1599 .with_command("invalid command")
1600 })
1601 .build()
1602 .unwrap_err();
1603
1604 assert_eq!(errors.len(), 1);
1605 assert_eq!(
1606 errors.first().unwrap().to_string(),
1607 "parachain[1000].collators['collator'].command: 'invalid command' shouldn't contains whitespace"
1608 );
1609 }
1610
1611 #[test]
1612 fn parachain_config_builder_with_at_least_one_collator_should_fails_and_returns_an_error_if_second_collator_is_invalid(
1613 ) {
1614 let errors = ParachainConfigBuilder::new(Default::default())
1615 .with_id(2000)
1616 .with_chain("myparachain")
1617 .with_collator(|collator| {
1618 collator
1619 .with_name("collator1")
1620 .with_command("command1")
1621 .invulnerable(true)
1622 .bootnode(true)
1623 })
1624 .with_collator(|collator| {
1625 collator
1626 .with_name("collator2")
1627 .with_command("invalid command")
1628 .with_initial_balance(20000000)
1629 })
1630 .build()
1631 .unwrap_err();
1632
1633 assert_eq!(errors.len(), 1);
1634 assert_eq!(
1635 errors.first().unwrap().to_string(),
1636 "parachain[2000].collators['collator2'].command: 'invalid command' shouldn't contains whitespace"
1637 );
1638 }
1639
1640 #[test]
1641 fn parachain_config_builder_should_fails_and_returns_multiple_errors_if_multiple_fields_are_invalid(
1642 ) {
1643 let errors = ParachainConfigBuilder::new(Default::default())
1644 .with_id(2000)
1645 .with_chain("myparachain")
1646 .with_raw_bootnodes_addresses(vec!["/ip4//tcp/45421", "//10.42.153.10/tcp/43111"])
1647 .with_collator(|collator| {
1648 collator
1649 .with_name("collator1")
1650 .with_command("invalid command")
1651 .invulnerable(true)
1652 .bootnode(true)
1653 .with_resources(|resources| {
1654 resources
1655 .with_limit_cpu("invalid")
1656 .with_request_memory("1G")
1657 })
1658 })
1659 .with_collator(|collator| {
1660 collator
1661 .with_name("collator2")
1662 .with_command("command2")
1663 .with_image("invalid.image")
1664 .with_initial_balance(20000000)
1665 })
1666 .build()
1667 .unwrap_err();
1668
1669 assert_eq!(errors.len(), 5);
1670 assert_eq!(
1671 errors.first().unwrap().to_string(),
1672 "parachain[2000].bootnodes_addresses[0]: '/ip4//tcp/45421' failed to parse: invalid IPv4 address syntax"
1673 );
1674 assert_eq!(
1675 errors.get(1).unwrap().to_string(),
1676 "parachain[2000].bootnodes_addresses[1]: '//10.42.153.10/tcp/43111' unknown protocol string: "
1677 );
1678 assert_eq!(
1679 errors.get(2).unwrap().to_string(),
1680 "parachain[2000].collators['collator1'].command: 'invalid command' shouldn't contains whitespace"
1681 );
1682 assert_eq!(
1683 errors.get(3).unwrap().to_string(),
1684 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)?$'",
1685 );
1686 assert_eq!(
1687 errors.get(4).unwrap().to_string(),
1688 "parachain[2000].collators['collator2'].image: 'invalid.image' doesn't match regex '^([ip]|[hostname]/)?[tag_name]:[tag_version]?$'"
1689 );
1690 }
1691
1692 #[test]
1693 fn import_toml_registration_strategy_should_deserialize() {
1694 let load_from_toml =
1695 NetworkConfig::load_from_toml("./testing/snapshots/0001-big-network.toml").unwrap();
1696
1697 for parachain in load_from_toml.parachains().iter() {
1698 if parachain.id() == 1000 {
1699 assert_eq!(
1700 parachain.registration_strategy(),
1701 Some(&RegistrationStrategy::UsingExtrinsic)
1702 );
1703 }
1704 if parachain.id() == 2000 {
1705 assert_eq!(
1706 parachain.registration_strategy(),
1707 Some(&RegistrationStrategy::InGenesis)
1708 );
1709 }
1710 }
1711
1712 let load_from_toml_small = NetworkConfig::load_from_toml(
1713 "./testing/snapshots/0003-small-network_w_parachain.toml",
1714 )
1715 .unwrap();
1716
1717 let parachain = load_from_toml_small.parachains()[0];
1718 let parachain_evm = load_from_toml_small.parachains()[1];
1719
1720 assert_eq!(parachain.registration_strategy(), None);
1721 assert!(!parachain.is_evm_based());
1722 assert_eq!(parachain.collators().len(), 1);
1723 assert!(parachain_evm.is_evm_based());
1724 }
1725
1726 #[test]
1727 fn with_optional_default_db_snapshot_applies_when_some() {
1728 let config = ParachainConfigBuilder::new(Default::default())
1729 .with_id(2000)
1730 .with_chain("myparachain")
1731 .with_optional_default_db_snapshot(Some("https://example.com/snap.tgz"))
1732 .with_collator(|collator| collator.with_name("collator"))
1733 .build()
1734 .unwrap();
1735 assert!(matches!(
1736 config.default_db_snapshot().unwrap(),
1737 AssetLocation::Url(value) if value.as_str() == "https://example.com/snap.tgz"
1738 ));
1739 }
1740
1741 #[test]
1742 fn with_optional_default_db_snapshot_is_noop_when_none() {
1743 let config = ParachainConfigBuilder::new(Default::default())
1744 .with_id(2000)
1745 .with_chain("myparachain")
1746 .with_optional_default_db_snapshot(None::<&str>)
1747 .with_collator(|collator| collator.with_name("collator"))
1748 .build()
1749 .unwrap();
1750 assert!(config.default_db_snapshot().is_none());
1751 }
1752
1753 #[test]
1754 fn onboard_as_parachain_should_default_to_true() {
1755 let config = ParachainConfigBuilder::new(Default::default())
1756 .with_id(2000)
1757 .with_chain("myparachain")
1758 .with_collator(|collator| collator.with_name("collator"))
1759 .build()
1760 .unwrap();
1761
1762 assert!(config.onboard_as_parachain());
1763 }
1764
1765 #[test]
1766 fn evm_based_default_to_false() {
1767 let config = ParachainConfigBuilder::new(Default::default())
1768 .with_id(2000)
1769 .with_chain("myparachain")
1770 .with_collator(|collator| collator.with_name("collator"))
1771 .build()
1772 .unwrap();
1773
1774 assert!(!config.is_evm_based());
1775 }
1776
1777 #[test]
1778 fn evm_based() {
1779 let config = ParachainConfigBuilder::new(Default::default())
1780 .with_id(2000)
1781 .with_chain("myparachain")
1782 .evm_based(true)
1783 .with_collator(|collator| collator.with_name("collator"))
1784 .build()
1785 .unwrap();
1786
1787 assert!(config.is_evm_based());
1788 }
1789
1790 #[test]
1791 fn build_config_in_running_context() {
1792 let config = ParachainConfigBuilder::new_with_running(Default::default())
1793 .with_id(2000)
1794 .with_chain("myparachain")
1795 .with_collator(|collator| collator.with_name("collator"))
1796 .build()
1797 .unwrap();
1798
1799 assert_eq!(
1800 config.registration_strategy(),
1801 Some(&RegistrationStrategy::UsingExtrinsic)
1802 );
1803 }
1804
1805 #[test]
1806 fn parachain_config_builder_should_works_with_chain_spec_command() {
1807 const CMD_TPL: &str = "./bin/chain-spec-generator {% raw %} {{chainName}} {% endraw %}";
1808 let config = ParachainConfigBuilder::new(Default::default())
1809 .with_id(2000)
1810 .with_chain("some-chain")
1811 .with_default_image("myrepo:myimage")
1812 .with_default_command("default_command")
1813 .with_chain_spec_command(CMD_TPL)
1814 .with_collator(|collator| collator.with_name("collator"))
1815 .build()
1816 .unwrap();
1817
1818 assert_eq!(config.chain_spec_command(), Some(CMD_TPL));
1819 assert!(!config.chain_spec_command_is_local());
1820 }
1821
1822 #[test]
1823 fn parachain_config_builder_should_works_with_chain_spec_command_and_local() {
1824 const CMD_TPL: &str = "./bin/chain-spec-generator {% raw %} {{chainName}} {% endraw %}";
1825 let config = ParachainConfigBuilder::new(Default::default())
1826 .with_id(2000)
1827 .with_chain("some-chain")
1828 .with_default_image("myrepo:myimage")
1829 .with_default_command("default_command")
1830 .with_chain_spec_command(CMD_TPL)
1831 .chain_spec_command_is_local(true)
1832 .with_collator(|collator| collator.with_name("collator"))
1833 .build()
1834 .unwrap();
1835
1836 assert_eq!(config.chain_spec_command(), Some(CMD_TPL));
1837 assert!(config.chain_spec_command_is_local());
1838 }
1839
1840 #[test]
1841 fn parachain_with_group_config_builder_should_succeeds_and_returns_a_new_parachain_config() {
1842 let parachain_config = ParachainConfigBuilder::new(Default::default())
1843 .with_id(1000)
1844 .with_chain("mychainname")
1845 .with_registration_strategy(RegistrationStrategy::UsingExtrinsic)
1846 .onboard_as_parachain(false)
1847 .with_initial_balance(100_000_042)
1848 .with_default_image("myrepo:myimage")
1849 .with_default_command("default_command")
1850 .without_default_bootnodes()
1851 .with_collator(|collator| {
1852 collator
1853 .with_name("collator1")
1854 .with_command("command1")
1855 .bootnode(true)
1856 })
1857 .with_collator_group(|group| {
1858 group.with_count(2).with_base_node(|base| {
1859 base.with_name("collator_group1")
1860 .with_command("group_command1")
1861 .bootnode(true)
1862 })
1863 })
1864 .with_collator_group(|group| {
1865 group.with_count(3).with_base_node(|base| {
1866 base.with_name("collator_group2")
1867 .with_command("group_command2")
1868 .bootnode(false)
1869 })
1870 })
1871 .build()
1872 .unwrap();
1873
1874 assert_eq!(parachain_config.id(), 1000);
1875 assert_eq!(parachain_config.collators().len(), 1);
1876 assert_eq!(parachain_config.group_collators_configs().len(), 2);
1877
1878 let group_collator1 = parachain_config.group_collators_configs()[0].clone();
1879 assert_eq!(group_collator1.count, 2);
1880 let base_config1 = group_collator1.base_config;
1881 assert_eq!(base_config1.name(), "collator_group1");
1882 assert_eq!(base_config1.command().unwrap().as_str(), "group_command1");
1883 assert!(base_config1.is_bootnode());
1884
1885 let group_collator2 = parachain_config.group_collators_configs()[1].clone();
1886 assert_eq!(group_collator2.count, 3);
1887 let base_config2 = group_collator2.base_config;
1888 assert_eq!(base_config2.name(), "collator_group2");
1889 assert_eq!(base_config2.command().unwrap().as_str(), "group_command2");
1890 assert!(!base_config2.is_bootnode());
1891 }
1892
1893 #[test]
1894 fn parachain_with_group_count_0_config_builder_should_fail() {
1895 let parachain_config = ParachainConfigBuilder::new(Default::default())
1896 .with_id(1000)
1897 .with_chain("mychainname")
1898 .with_registration_strategy(RegistrationStrategy::UsingExtrinsic)
1899 .onboard_as_parachain(false)
1900 .with_initial_balance(100_000_042)
1901 .with_default_image("myrepo:myimage")
1902 .with_default_command("default_command")
1903 .without_default_bootnodes()
1904 .with_collator(|collator| {
1905 collator
1906 .with_name("collator1")
1907 .with_command("command1")
1908 .bootnode(true)
1909 })
1910 .with_collator_group(|group| {
1911 group.with_count(2).with_base_node(|base| {
1912 base.with_name("collator_group1")
1913 .with_command("group_command1")
1914 .bootnode(true)
1915 })
1916 })
1917 .with_collator_group(|group| {
1918 group.with_count(0).with_base_node(|base| {
1919 base.with_name("collator_group2")
1920 .with_command("group_command2")
1921 .bootnode(false)
1922 })
1923 })
1924 .build();
1925
1926 let errors: Vec<anyhow::Error> = match parachain_config {
1927 Ok(_) => vec![],
1928 Err(errs) => errs,
1929 };
1930
1931 assert_eq!(errors.len(), 1);
1932 assert_eq!(
1933 errors.first().unwrap().to_string(),
1934 "parachain[1000].collators['collator_group2'].Count cannot be zero"
1935 );
1936 }
1937}