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