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