Skip to content
Snippets Groups Projects
parachain.rs 45.5 KiB
Newer Older
use std::{cell::RefCell, error::Error, fmt::Display, marker::PhantomData, rc::Rc};
Nikos Kontakis's avatar
Nikos Kontakis committed
use serde::{
    de::{self, Visitor},
Nikos Kontakis's avatar
Nikos Kontakis committed
    ser::SerializeStruct,
    Deserialize, Serialize,
Nikos Kontakis's avatar
Nikos Kontakis committed
};
Nikos Kontakis's avatar
Nikos Kontakis committed
use crate::{
    shared::{
        errors::{ConfigError, FieldError},
        helpers::{merge_errors, merge_errors_vecs},
        node::{self, NodeConfig, NodeConfigBuilder},
        resources::{Resources, ResourcesBuilder},
        types::{
            Arg, AssetLocation, Chain, ChainDefaultContext, Command, Image, ValidationContext, U128,
        },
    utils::{default_as_true, default_initial_balance, is_false},
/// The registration strategy that will be used for the parachain.
Nikos Kontakis's avatar
Nikos Kontakis committed
#[derive(Debug, Clone, PartialEq)]
    /// The parachain will be added to the genesis before spawning.
    /// The parachain will be registered using an extrinsic after spawning.
    /// The parachaing will not be registered and the user can doit after spawning manually.
impl Serialize for RegistrationStrategy {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        let mut state = serializer.serialize_struct("RegistrationStrategy", 1)?;

        match self {
            Self::InGenesis => state.serialize_field("add_to_genesis", &true)?,
            Self::UsingExtrinsic => state.serialize_field("register_para", &true)?,
            Self::Manual => {
                state.serialize_field("add_to_genesis", &false)?;
                state.serialize_field("register_para", &false)?;
            },
Nikos Kontakis's avatar
Nikos Kontakis committed
struct RegistrationStrategyVisitor;

impl<'de> Visitor<'de> for RegistrationStrategyVisitor {
    type Value = RegistrationStrategy;

    fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
Nikos Kontakis's avatar
Nikos Kontakis committed
        formatter.write_str("struct RegistrationStrategy")
    }

    fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
Nikos Kontakis's avatar
Nikos Kontakis committed
    where
        A: serde::de::MapAccess<'de>,
Nikos Kontakis's avatar
Nikos Kontakis committed
    {
        let mut add_to_genesis = false;
        let mut register_para = false;

        while let Some(key) = map.next_key::<String>()? {
            match key.as_str() {
                "addToGenesis" | "add_to_genesis" => add_to_genesis = map.next_value()?,
                "registerPara" | "register_para" => register_para = map.next_value()?,
Nikos Kontakis's avatar
Nikos Kontakis committed
                _ => {
                    return Err(de::Error::unknown_field(
                        &key,
                        &["add_to_genesis", "register_para"],
        match (add_to_genesis, register_para) {
            (true, false) => Ok(RegistrationStrategy::InGenesis),
            (false, true) => Ok(RegistrationStrategy::UsingExtrinsic),
            _ => Err(de::Error::missing_field("add_to_genesis or register_para")),
Nikos Kontakis's avatar
Nikos Kontakis committed
        }
    }
}

impl<'de> Deserialize<'de> for RegistrationStrategy {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
Nikos Kontakis's avatar
Nikos Kontakis committed
    {
        deserializer.deserialize_struct(
            "RegistrationStrategy",
            &["add_to_genesis", "register_para"],
            RegistrationStrategyVisitor,
        )
/// A parachain configuration, composed of collators and fine-grained configuration options.
Nikos Kontakis's avatar
Nikos Kontakis committed
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ParachainConfig {
    registration_strategy: Option<RegistrationStrategy>,
    #[serde(
        skip_serializing_if = "super::utils::is_true",
        default = "default_as_true"
    )]
Nikos Kontakis's avatar
Nikos Kontakis committed
    #[serde(rename = "balance", default = "default_initial_balance")]
    default_command: Option<Command>,
    default_image: Option<Image>,
    default_resources: Option<Resources>,
    default_db_snapshot: Option<AssetLocation>,
    #[serde(skip_serializing_if = "std::vec::Vec::is_empty", default)]
    default_args: Vec<Arg>,
    genesis_wasm_path: Option<AssetLocation>,
    genesis_wasm_generator: Option<Command>,
    genesis_state_path: Option<AssetLocation>,
    genesis_state_generator: Option<Command>,
    // Full _template_ command, will be rendered using [tera]
    // and executed for generate the chain-spec.
    // available tokens {{chainName}} / {{disableBootnodes}}
    chain_spec_command: Option<String>,
    // Does the chain_spec_command needs to be run locally
    #[serde(skip_serializing_if = "is_false", default)]
    chain_spec_command_is_local: bool,
    #[serde(rename = "cumulus_based", default = "default_as_true")]
    is_cumulus_based: bool,
    #[serde(skip_serializing_if = "std::vec::Vec::is_empty", default)]
    genesis_overrides: Option<serde_json::Value>,
    #[serde(skip_serializing_if = "std::vec::Vec::is_empty", default)]
    pub(crate) collators: Vec<NodeConfig>,
    // Single collator config, added for backward compatibility
    // with `toml` networks definitions from v1.
    // This field can only be set loading an old `toml` definition
    // with `[parachain.collator]` key.
    // NOTE: if the file also contains multiple collators defined in
    // `[[parachain.collators]], the single configuration will be added to the bottom.
    collator: Option<NodeConfig>,
    /// The parachain ID.
    pub fn chain(&self) -> Option<&Chain> {
        self.chain.as_ref()
    /// The registration strategy for the parachain.
    pub fn registration_strategy(&self) -> Option<&RegistrationStrategy> {
        self.registration_strategy.as_ref()
    }

    /// Whether the parachain should be onboarded or stay a parathread
    pub fn onboard_as_parachain(&self) -> bool {
        self.onboard_as_parachain
    }

    /// The initial balance of the parachain account.
    /// The default command used for collators.
    pub fn default_command(&self) -> Option<&Command> {
        self.default_command.as_ref()
    }

    /// The default container image used for collators.
    pub fn default_image(&self) -> Option<&Image> {
        self.default_image.as_ref()
    }

    /// The default resources limits used for collators.
    pub fn default_resources(&self) -> Option<&Resources> {
        self.default_resources.as_ref()
    }

    /// The default database snapshot location that will be used for state.
    pub fn default_db_snapshot(&self) -> Option<&AssetLocation> {
        self.default_db_snapshot.as_ref()
    }

    /// The default arguments that will be used to execute the collator command.
    pub fn default_args(&self) -> Vec<&Arg> {
        self.default_args.iter().collect::<Vec<&Arg>>()
    }

    /// The location of a pre-existing genesis WASM runtime blob of the parachain.
    pub fn genesis_wasm_path(&self) -> Option<&AssetLocation> {
        self.genesis_wasm_path.as_ref()
    }

    /// The generator command used to create the genesis WASM runtime blob of the parachain.
    pub fn genesis_wasm_generator(&self) -> Option<&Command> {
        self.genesis_wasm_generator.as_ref()
    /// The location of a pre-existing genesis state of the parachain.
    pub fn genesis_state_path(&self) -> Option<&AssetLocation> {
        self.genesis_state_path.as_ref()
    }

    /// The generator command used to create the genesis state of the parachain.
    pub fn genesis_state_generator(&self) -> Option<&Command> {
        self.genesis_state_generator.as_ref()
    /// The genesis overrides as a JSON value.
    pub fn genesis_overrides(&self) -> Option<&serde_json::Value> {
        self.genesis_overrides.as_ref()
    }

    /// The location of a pre-existing chain specification for the parachain.
    pub fn chain_spec_path(&self) -> Option<&AssetLocation> {
        self.chain_spec_path.as_ref()
    }

    /// The full _template_ command to genera the chain-spec
    pub fn chain_spec_command(&self) -> Option<&str> {
        self.chain_spec_command.as_deref()
    }

    /// Does the chain_spec_command needs to be run locally
    pub fn chain_spec_command_is_local(&self) -> bool {
        self.chain_spec_command_is_local
    }

    /// Whether the parachain is based on cumulus.
    /// The bootnodes addresses the collators will connect to.
    pub fn bootnodes_addresses(&self) -> Vec<&Multiaddr> {
    /// The collators of the parachain.
    pub fn collators(&self) -> Vec<&NodeConfig> {
        let mut cols = self.collators.iter().collect::<Vec<_>>();
        if let Some(col) = self.collator.as_ref() {
            cols.push(col);
        }
        cols
pub mod states {
    use crate::shared::macros::states;

    states! {
        Initial,
        WithId,
        WithAtLeastOneCollator
    }

    states! {
        Bootstrap,
        Running
    }

    pub trait Context {}
    impl Context for Bootstrap {}
    impl Context for Running {}
use states::{Bootstrap, Context, Initial, Running, WithAtLeastOneCollator, WithId};
/// A parachain configuration builder, used to build a [`ParachainConfig`] declaratively with fields validation.
pub struct ParachainConfigBuilder<S, C> {
    validation_context: Rc<RefCell<ValidationContext>>,
    _context: PhantomData<C>,
impl<C: Context> Default for ParachainConfigBuilder<Initial, C> {
                id: 100,
                registration_strategy: Some(RegistrationStrategy::InGenesis),
                default_command: None,
                default_image: None,
                default_resources: None,
                default_db_snapshot: None,
                default_args: vec![],
                genesis_wasm_path: None,
                genesis_wasm_generator: None,
                genesis_state_path: None,
                genesis_overrides: None,
                chain_spec_path: None,
                chain_spec_command_is_local: false, // remote by default
                bootnodes_addresses: vec![],
                collators: vec![],
            validation_context: Default::default(),
            _context: PhantomData,
impl<A, C> ParachainConfigBuilder<A, C> {
    fn transition<B>(
        config: ParachainConfig,
        validation_context: Rc<RefCell<ValidationContext>>,
    ) -> ParachainConfigBuilder<B, C> {
            _context: PhantomData,
    fn default_chain_context(&self) -> ChainDefaultContext {
        ChainDefaultContext {
            default_command: self.config.default_command.clone(),
            default_image: self.config.default_image.clone(),
            default_resources: self.config.default_resources.clone(),
            default_db_snapshot: self.config.default_db_snapshot.clone(),
            default_args: self.config.default_args.clone(),
impl ParachainConfigBuilder<Initial, Bootstrap> {
    /// Instantiate a new builder that can be used to build a [`ParachainConfig`] during the bootstrap phase.
    pub fn new(
        validation_context: Rc<RefCell<ValidationContext>>,
    ) -> ParachainConfigBuilder<Initial, Bootstrap> {
        Self {
            validation_context,
            ..Self::default()
        }
impl ParachainConfigBuilder<WithId, Bootstrap> {
    /// Set the registration strategy for the parachain, could be Manual (no registered by zombienet) or automatic
    /// using an extrinsic or in genesis.
    pub fn with_registration_strategy(self, strategy: RegistrationStrategy) -> Self {
        Self::transition(
            ParachainConfig {
                registration_strategy: Some(strategy),
                ..self.config
            },
            self.validation_context,
            self.errors,
        )
    }
}

impl ParachainConfigBuilder<WithId, Running> {
    /// Set the registration strategy for the parachain, could be Manual (no registered by zombienet) or automatic
    /// Using an extrinsic. Genesis option is not allowed in `Running` context.
    pub fn with_registration_strategy(self, strategy: RegistrationStrategy) -> Self {
        match strategy {
            RegistrationStrategy::InGenesis => Self::transition(
                self.config,
                self.validation_context,
                merge_errors(
                    self.errors,
                    FieldError::RegistrationStrategy(anyhow!(
                        "Can be set to InGenesis in Running context"
                    ))
                    .into(),
                ),
            ),
            RegistrationStrategy::Manual | RegistrationStrategy::UsingExtrinsic => {
                Self::transition(
                    ParachainConfig {
                        registration_strategy: Some(strategy),
                        ..self.config
                    },
                    self.validation_context,
                    self.errors,
                )
            },
        }
    }
}

impl ParachainConfigBuilder<Initial, Running> {
    /// Start a new builder in the context of a running network
    pub fn new_with_running(
        validation_context: Rc<RefCell<ValidationContext>>,
    ) -> ParachainConfigBuilder<Initial, Running> {
        let mut builder = Self {
            validation_context,
            ..Self::default()
        };

        // override the registration strategy
        builder.config.registration_strategy = Some(RegistrationStrategy::UsingExtrinsic);
        builder
    }
}

impl<C: Context> ParachainConfigBuilder<Initial, C> {
    /// Set the parachain ID (should be unique).
    // TODO: handle unique validation
    pub fn with_id(self, id: u32) -> ParachainConfigBuilder<WithId, C> {
        Self::transition(
            ParachainConfig { id, ..self.config },
            self.validation_context,
            self.errors,
        )
impl<C: Context> ParachainConfigBuilder<WithId, C> {
    /// Set the chain name (e.g. rococo-local).
    /// Use [`None`], if you are running adder-collator or undying-collator).
    pub fn with_chain<T>(self, chain: T) -> Self
    where
        T: TryInto<Chain>,
        T::Error: Error + Send + Sync + 'static,
    {
        match chain.try_into() {
            Ok(chain) => Self::transition(
            Err(error) => Self::transition(
                merge_errors(self.errors, FieldError::Chain(error.into()).into()),
    /// Set whether the parachain should be onboarded or stay a parathread. Default is ```true```.
    pub fn onboard_as_parachain(self, choice: bool) -> Self {
        Self::transition(
            ParachainConfig {
                onboard_as_parachain: choice,
                ..self.config
            },
            self.validation_context,
            self.errors,
        )
    }

    /// Set the initial balance of the parachain account.
    pub fn with_initial_balance(self, initial_balance: u128) -> Self {
        Self::transition(
            ParachainConfig {
    /// Set the default command used for collators. Can be overridden.
    pub fn with_default_command<T>(self, command: T) -> Self
        T::Error: Error + Send + Sync + 'static,
        match command.try_into() {
            Ok(command) => Self::transition(
                ParachainConfig {
                    default_command: Some(command),
            Err(error) => Self::transition(
                merge_errors(self.errors, FieldError::DefaultCommand(error.into()).into()),
    /// Set the default container image used for collators. Can be overridden.
    pub fn with_default_image<T>(self, image: T) -> Self
    where
        T: TryInto<Image>,
        T::Error: Error + Send + Sync + 'static,
    {
        match image.try_into() {
            Ok(image) => Self::transition(
                ParachainConfig {
                    default_image: Some(image),
                    ..self.config
                },
                self.errors,
            ),
            Err(error) => Self::transition(
                self.config,
                merge_errors(self.errors, FieldError::DefaultImage(error.into()).into()),
            ),
        }
    }

    /// Set the default resources limits used for collators. Can be overridden.
    pub fn with_default_resources(
        self,
        f: impl FnOnce(ResourcesBuilder) -> ResourcesBuilder,
    ) -> Self {
        match f(ResourcesBuilder::new()).build() {
            Ok(default_resources) => Self::transition(
                ParachainConfig {
                    default_resources: Some(default_resources),
                    ..self.config
                },
                self.errors,
            ),
            Err(errors) => Self::transition(
                self.config,
                merge_errors_vecs(
                    self.errors,
                    errors
                        .into_iter()
                        .map(|error| FieldError::DefaultResources(error).into())
                        .collect::<Vec<_>>(),
                ),
            ),
        }
    }

    /// Set the default database snapshot location that will be used for state. Can be overridden.
    pub fn with_default_db_snapshot(self, location: impl Into<AssetLocation>) -> Self {
                default_db_snapshot: Some(location.into()),
    /// Set the default arguments that will be used to execute the collator command. Can be overridden.
    pub fn with_default_args(self, args: Vec<Arg>) -> Self {
    /// Set the location of a pre-existing genesis WASM runtime blob of the parachain.
    pub fn with_genesis_wasm_path(self, location: impl Into<AssetLocation>) -> Self {
        Self::transition(
            ParachainConfig {
                genesis_wasm_path: Some(location.into()),
                ..self.config
            },
    /// Set the generator command used to create the genesis WASM runtime blob of the parachain.
    pub fn with_genesis_wasm_generator<T>(self, command: T) -> Self
    where
        T: TryInto<Command>,
        T::Error: Error + Send + Sync + 'static,
        match command.try_into() {
            Ok(command) => Self::transition(
                    genesis_wasm_generator: Some(command),
            Err(error) => Self::transition(
                merge_errors(
                    self.errors,
                    FieldError::GenesisWasmGenerator(error.into()).into(),
                ),
    /// Set the location of a pre-existing genesis state of the parachain.
    pub fn with_genesis_state_path(self, location: impl Into<AssetLocation>) -> Self {
        Self::transition(
            ParachainConfig {
                genesis_state_path: Some(location.into()),
                ..self.config
            },
    /// Set the generator command used to create the genesis state of the parachain.
    pub fn with_genesis_state_generator<T>(self, command: T) -> Self
    where
        T: TryInto<Command>,
        T::Error: Error + Send + Sync + 'static,
        match command.try_into() {
            Ok(command) => Self::transition(
                    genesis_state_generator: Some(command),
            Err(error) => Self::transition(
                merge_errors(
                    self.errors,
                    FieldError::GenesisStateGenerator(error.into()).into(),
                ),
    /// Set the genesis overrides as a JSON object.
    pub fn with_genesis_overrides(self, genesis_overrides: impl Into<serde_json::Value>) -> Self {
        Self::transition(
            ParachainConfig {
                genesis_overrides: Some(genesis_overrides.into()),
                ..self.config
            },
            self.validation_context,
            self.errors,
        )
    }

    /// Set the location of a pre-existing chain specification for the parachain.
    pub fn with_chain_spec_path(self, location: impl Into<AssetLocation>) -> Self {
        Self::transition(
            ParachainConfig {
                chain_spec_path: Some(location.into()),
                ..self.config
            },
    /// Set the chain-spec command _template_ for the relay chain.
    pub fn with_chain_spec_command(self, cmd_template: impl Into<String>) -> Self {
        Self::transition(
            ParachainConfig {
                chain_spec_command: Some(cmd_template.into()),
                ..self.config
            },
            self.validation_context,
            self.errors,
        )
    }

    /// Set if the chain-spec command needs to be run locally or not (false by default)
    pub fn chain_spec_command_is_local(self, choice: bool) -> Self {
        Self::transition(
            ParachainConfig {
                chain_spec_command_is_local: choice,
                ..self.config
            },
            self.validation_context,
            self.errors,
        )
    }

    /// Set whether the parachain is based on cumulus (true in a majority of case, except adder or undying collators).
    pub fn cumulus_based(self, choice: bool) -> Self {
Loris Moulin's avatar
Loris Moulin committed

    /// Set the bootnodes addresses the collators will connect to.
    pub fn with_bootnodes_addresses<T>(self, bootnodes_addresses: Vec<T>) -> Self
    where
        T: TryInto<Multiaddr> + Display + Copy,
        T::Error: Error + Send + Sync + 'static,
        for (index, addr) in bootnodes_addresses.into_iter().enumerate() {
                Err(error) => errors.push(
                    FieldError::BootnodesAddress(index, addr.to_string(), error.into()).into(),
                ),
            }
        }

        Self::transition(
            ParachainConfig {
                bootnodes_addresses: addrs,
                ..self.config
            },
            merge_errors_vecs(self.errors, errors),
    /// Add a new collator using a nested [`NodeConfigBuilder`].
        f: impl FnOnce(NodeConfigBuilder<node::Initial>) -> NodeConfigBuilder<node::Buildable>,
    ) -> ParachainConfigBuilder<WithAtLeastOneCollator, C> {
        match f(NodeConfigBuilder::new(
            self.default_chain_context(),
            self.validation_context.clone(),
        ))
        .build()
        {
            Ok(collator) => Self::transition(
                ParachainConfig {
                    collators: vec![collator],
                    ..self.config
                },
                self.errors,
            ),
            Err((name, errors)) => Self::transition(
                self.config,
                merge_errors_vecs(
                    self.errors,
                    errors
                        .into_iter()
                        .map(|error| ConfigError::Collator(name.clone(), error).into())
                        .collect::<Vec<_>>(),
                ),
            ),
        }
    }
}

impl<C: Context> ParachainConfigBuilder<WithAtLeastOneCollator, C> {
    /// Add a new collator using a nested [`NodeConfigBuilder`].
        f: impl FnOnce(NodeConfigBuilder<node::Initial>) -> NodeConfigBuilder<node::Buildable>,
        match f(NodeConfigBuilder::new(
            ChainDefaultContext::default(),
            self.validation_context.clone(),
        ))
        .build()
        {
            Ok(collator) => Self::transition(
                ParachainConfig {
muraca's avatar
muraca committed
                    collators: [self.config.collators, vec![collator]].concat(),
                self.errors,
            ),
            Err((name, errors)) => Self::transition(
                self.config,
                merge_errors_vecs(
                    self.errors,
                    errors
                        .into_iter()
                        .map(|error| ConfigError::Collator(name.clone(), error).into())
                        .collect::<Vec<_>>(),
                ),
            ),
        }
    /// Seals the builder and returns a [`ParachainConfig`] if there are no validation errors, else returns errors.
    pub fn build(self) -> Result<ParachainConfig, Vec<anyhow::Error>> {
            return Err(self
                .errors
                .into_iter()
                .map(|error| ConfigError::Parachain(self.config.id, error).into())
                .collect::<Vec<_>>());
Nikos Kontakis's avatar
Nikos Kontakis committed
    use crate::NetworkConfig;
    fn parachain_config_builder_should_succeeds_and_returns_a_new_parachain_config() {
        let parachain_config = ParachainConfigBuilder::new(Default::default())
            .with_id(1000)
            .with_chain("mychainname")
            .with_registration_strategy(RegistrationStrategy::UsingExtrinsic)
            .with_initial_balance(100_000_042)
            .with_default_image("myrepo:myimage")
            .with_default_command("default_command")
            .with_default_resources(|resources| {
                resources
                    .with_limit_cpu("500M")
                    .with_limit_memory("1G")
                    .with_request_cpu("250M")
            })
            .with_default_db_snapshot("https://www.urltomysnapshot.com/file.tgz")
            .with_default_args(vec![("--arg1", "value1").into(), "--option2".into()])
            .with_genesis_wasm_path("https://www.backupsite.com/my/wasm/file.tgz")
            .with_genesis_wasm_generator("generator_wasm")
            .with_genesis_state_path("./path/to/genesis/state")
            .with_genesis_state_generator("generator_state")
            .cumulus_based(false)
            .with_bootnodes_addresses(vec![
                "/ip4/10.41.122.55/tcp/45421",
                "/ip4/51.144.222.10/tcp/2333",
Loris Moulin's avatar
Loris Moulin committed
            .with_collator(|collator| {
                collator
                    .with_name("collator1")
                    .with_command("command1")
                    .bootnode(true)
            })
Loris Moulin's avatar
Loris Moulin committed
            .with_collator(|collator| {
                collator
                    .with_name("collator2")
                    .with_command("command2")
                    .validator(true)
            })

        assert_eq!(parachain_config.id(), 1000);
        assert_eq!(parachain_config.collators().len(), 2);
        let &collator1 = parachain_config.collators().first().unwrap();
        assert_eq!(collator1.name(), "collator1");
        assert_eq!(collator1.command().unwrap().as_str(), "command1");
        assert!(collator1.is_bootnode());
        let &collator2 = parachain_config.collators().last().unwrap();
        assert_eq!(collator2.name(), "collator2");
        assert_eq!(collator2.command().unwrap().as_str(), "command2");
        assert!(collator2.is_validator());
        assert_eq!(parachain_config.chain().unwrap().as_str(), "mychainname");
        assert_eq!(
            parachain_config.registration_strategy().unwrap(),
            &RegistrationStrategy::UsingExtrinsic
        );
        assert!(!parachain_config.onboard_as_parachain());
        assert_eq!(parachain_config.initial_balance(), 100_000_042);
        assert_eq!(
            parachain_config.default_command().unwrap().as_str(),
            "default_command"
        );
        assert_eq!(
            parachain_config.default_image().unwrap().as_str(),
            "myrepo:myimage"
        );
        let default_resources = parachain_config.default_resources().unwrap();
        assert_eq!(default_resources.limit_cpu().unwrap().as_str(), "500M");
        assert_eq!(default_resources.limit_memory().unwrap().as_str(), "1G");
        assert_eq!(default_resources.request_cpu().unwrap().as_str(), "250M");
        assert!(matches!(
            parachain_config.default_db_snapshot().unwrap(),
            AssetLocation::Url(value) if value.as_str() == "https://www.urltomysnapshot.com/file.tgz",
        ));
        assert!(matches!(
            parachain_config.chain_spec_path().unwrap(),
            AssetLocation::FilePath(value) if value.to_str().unwrap() == "./path/to/chain/spec.json"
        ));
        let args: Vec<Arg> = vec![("--arg1", "value1").into(), "--option2".into()];
        assert_eq!(
            parachain_config.default_args(),
            args.iter().collect::<Vec<_>>()
        );
        assert!(matches!(
            parachain_config.genesis_wasm_path().unwrap(),
            AssetLocation::Url(value) if value.as_str() == "https://www.backupsite.com/my/wasm/file.tgz"
            parachain_config.genesis_wasm_generator().unwrap().as_str(),
            "generator_wasm"
        );
        assert!(matches!(
            parachain_config.genesis_state_path().unwrap(),
            AssetLocation::FilePath(value) if value.to_str().unwrap() == "./path/to/genesis/state"
            parachain_config.genesis_state_generator().unwrap().as_str(),
            "generator_state"
        );
        assert!(matches!(
            parachain_config.chain_spec_path().unwrap(),
            AssetLocation::FilePath(value) if value.to_str().unwrap() == "./path/to/chain/spec.json"
        ));
        assert!(!parachain_config.is_cumulus_based());
        let bootnodes_addresses: Vec<Multiaddr> = vec![
            "/ip4/10.41.122.55/tcp/45421".try_into().unwrap(),
            "/ip4/51.144.222.10/tcp/2333".try_into().unwrap(),
        assert_eq!(
            parachain_config.bootnodes_addresses(),
            bootnodes_addresses.iter().collect::<Vec<_>>()
        );
    fn parachain_config_builder_should_fails_and_returns_an_error_if_chain_is_invalid() {
        let errors = ParachainConfigBuilder::new(Default::default())
            .with_collator(|collator| {
                collator
                    .with_name("collator")
            })
            .build()
            .unwrap_err();

        assert_eq!(errors.len(), 1);
        assert_eq!(
            errors.first().unwrap().to_string(),
            "parachain[1000].chain: 'invalid chain' shouldn't contains whitespace"
    fn parachain_config_builder_should_fails_and_returns_an_error_if_default_command_is_invalid() {
        let errors = ParachainConfigBuilder::new(Default::default())
            .with_chain("chain")
            .with_default_command("invalid command")
            .with_collator(|collator| {
                collator
                    .with_command("command")
                    .validator(true)
            })
            .build()
            .unwrap_err();

        assert_eq!(errors.len(), 1);
        assert_eq!(
            errors.first().unwrap().to_string(),
            "parachain[1000].default_command: 'invalid command' shouldn't contains whitespace"
    fn parachain_config_builder_should_fails_and_returns_an_error_if_default_image_is_invalid() {
        let errors = ParachainConfigBuilder::new(Default::default())