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