Skip to main content

zombienet_configuration/
parachain.rs

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