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