Files
pezkuwi-sdk/vendor/zombienet-configuration/src/network.rs
T

1910 lines
73 KiB
Rust

use std::{cell::RefCell, collections::HashSet, fs, marker::PhantomData, rc::Rc};
use anyhow::anyhow;
use regex::Regex;
use serde::{Deserialize, Serialize};
use support::{
constants::{
NO_ERR_DEF_BUILDER, RELAY_NOT_NONE, RW_FAILED, THIS_IS_A_BUG, VALIDATION_CHECK, VALID_REGEX,
},
replacer::apply_env_replacements,
};
use tracing::trace;
use crate::{
global_settings::{GlobalSettings, GlobalSettingsBuilder},
hrmp_channel::{self, HrmpChannelConfig, HrmpChannelConfigBuilder},
teyrchain::{self, TeyrchainConfig, TeyrchainConfigBuilder},
relaychain::{self, RelaychainConfig, RelaychainConfigBuilder},
shared::{
errors::{ConfigError, ValidationError},
helpers::{generate_unique_node_name_from_names, merge_errors, merge_errors_vecs},
macros::states,
node::NodeConfig,
types::{Arg, AssetLocation, Chain, Command, Image, ValidationContext},
},
types::ParaId,
RegistrationStrategy,
};
/// A network configuration, composed of a relaychain, teyrchains and HRMP channels.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct NetworkConfig {
#[serde(rename = "settings", default = "GlobalSettings::default")]
global_settings: GlobalSettings,
relaychain: Option<RelaychainConfig>,
// Pezkuwi SDK: Added "teyrchains" alias for Pezkuwi terminology
#[serde(alias = "teyrchains", skip_serializing_if = "std::vec::Vec::is_empty", default)]
teyrchains: Vec<TeyrchainConfig>,
#[serde(skip_serializing_if = "std::vec::Vec::is_empty", default)]
hrmp_channels: Vec<HrmpChannelConfig>,
}
impl NetworkConfig {
/// The global settings of the network.
pub fn global_settings(&self) -> &GlobalSettings {
&self.global_settings
}
/// The relay chain of the network.
pub fn relaychain(&self) -> &RelaychainConfig {
self.relaychain
.as_ref()
.expect(&format!("{RELAY_NOT_NONE}, {THIS_IS_A_BUG}"))
}
/// The teyrchains of the network.
pub fn teyrchains(&self) -> Vec<&TeyrchainConfig> {
self.teyrchains.iter().collect::<Vec<_>>()
}
/// Backward compatibility alias for teyrchains() - for external crates using Polkadot SDK terminology.
pub fn parachains(&self) -> Vec<&TeyrchainConfig> {
self.teyrchains()
}
/// The HRMP channels of the network.
pub fn hrmp_channels(&self) -> Vec<&HrmpChannelConfig> {
self.hrmp_channels.iter().collect::<Vec<_>>()
}
fn set_teyrchains(&mut self, teyrchains: Vec<TeyrchainConfig>) {
self.teyrchains = teyrchains;
}
/// A helper function to dump the network configuration to a TOML string.
pub fn dump_to_toml(&self) -> Result<String, toml::ser::Error> {
// This regex is used to replace the "" enclosed u128 value to a raw u128 because u128 is not supported for TOML serialization/deserialization.
let re = Regex::new(r#""U128%(?<u128_value>\d+)""#)
.expect(&format!("{VALID_REGEX} {THIS_IS_A_BUG}"));
let toml_string = toml::to_string_pretty(&self)?;
Ok(re.replace_all(&toml_string, "$u128_value").to_string())
}
/// A helper function to load a network configuration from a TOML file.
pub fn load_from_toml_with_settings(
path: &str,
settings: &GlobalSettings,
) -> Result<NetworkConfig, anyhow::Error> {
let mut network_config = NetworkConfig::load_from_toml(path)?;
network_config.global_settings = settings.clone();
Ok(network_config)
}
/// A helper function to load a network configuration from a TOML file.
pub fn load_from_toml(path: &str) -> Result<NetworkConfig, anyhow::Error> {
let file_str = fs::read_to_string(path).expect(&format!("{RW_FAILED} {THIS_IS_A_BUG}"));
let re: Regex = Regex::new(r"(?<field_name>(initial_)?balance)\s+=\s+(?<u128_value>\d+)")
.expect(&format!("{VALID_REGEX} {THIS_IS_A_BUG}"));
let toml_text = re.replace_all(&file_str, "$field_name = \"$u128_value\"");
trace!("toml text to parse: {}", toml_text);
// apply replacements from env in toml
let toml_text = apply_env_replacements(&toml_text);
trace!("toml text after replacements: {}", toml_text);
let mut network_config: NetworkConfig = toml::from_str(&toml_text)?;
trace!("parsed config {network_config:#?}");
// All unwraps below are safe, because we ensure that the relaychain is not None at this point
if network_config.relaychain.is_none() {
Err(anyhow!("Relay chain does not exist."))?
}
// retrieve the defaults relaychain for assigning to nodes if needed
let mut relaychain_default_command: Option<Command> =
network_config.relaychain().default_command().cloned();
if relaychain_default_command.is_none() {
relaychain_default_command = network_config.relaychain().command().cloned();
}
let relaychain_default_image: Option<Image> =
network_config.relaychain().default_image().cloned();
let relaychain_default_db_snapshot: Option<AssetLocation> =
network_config.relaychain().default_db_snapshot().cloned();
let default_args: Vec<Arg> = network_config
.relaychain()
.default_args()
.into_iter()
.cloned()
.collect();
let mut nodes: Vec<NodeConfig> = network_config
.relaychain()
.nodes()
.into_iter()
.cloned()
.collect();
let mut teyrchains: Vec<TeyrchainConfig> =
network_config.teyrchains().into_iter().cloned().collect();
// Validation checks for relay
TryInto::<Chain>::try_into(network_config.relaychain().chain().as_str())?;
if relaychain_default_image.is_some() {
TryInto::<Image>::try_into(relaychain_default_image.clone().expect(VALIDATION_CHECK))?;
}
if relaychain_default_command.is_some() {
TryInto::<Command>::try_into(
relaychain_default_command.clone().expect(VALIDATION_CHECK),
)?;
}
// Keep track of node names to ensure uniqueness
let mut names = HashSet::new();
for node in nodes.iter_mut() {
if relaychain_default_command.is_some() {
// we modify only nodes which don't already have a command
if node.command.is_none() {
node.command.clone_from(&relaychain_default_command);
}
}
if relaychain_default_image.is_some() && node.image.is_none() {
node.image.clone_from(&relaychain_default_image);
}
if relaychain_default_db_snapshot.is_some() && node.db_snapshot.is_none() {
node.db_snapshot.clone_from(&relaychain_default_db_snapshot);
}
if !default_args.is_empty() && node.args().is_empty() {
node.set_args(default_args.clone());
}
let unique_name = generate_unique_node_name_from_names(node.name(), &mut names);
node.name = unique_name;
}
for para in teyrchains.iter_mut() {
// retrieve the defaults teyrchain for assigning to collators if needed
let teyrchain_default_command: Option<Command> = para.default_command().cloned();
let teyrchain_default_image: Option<Image> = para.default_image().cloned();
let teyrchain_default_db_snapshot: Option<AssetLocation> =
para.default_db_snapshot().cloned();
let default_args: Vec<Arg> = para.default_args().into_iter().cloned().collect();
let mut collators: Vec<NodeConfig> = para.collators.clone();
for collator in collators.iter_mut() {
populate_collator_with_defaults(
collator,
&teyrchain_default_command,
&teyrchain_default_image,
&teyrchain_default_db_snapshot,
&default_args,
);
let unique_name = generate_unique_node_name_from_names(collator.name(), &mut names);
collator.name = unique_name;
}
para.collators = collators;
if para.collator.is_some() {
let mut collator = para.collator.clone().unwrap();
populate_collator_with_defaults(
&mut collator,
&teyrchain_default_command,
&teyrchain_default_image,
&teyrchain_default_db_snapshot,
&default_args,
);
let unique_name = generate_unique_node_name_from_names(collator.name(), &mut names);
collator.name = unique_name;
para.collator = Some(collator);
}
}
network_config
.relaychain
.as_mut()
.expect(&format!("{NO_ERR_DEF_BUILDER}, {THIS_IS_A_BUG}"))
.set_nodes(nodes);
network_config.set_teyrchains(teyrchains);
// Validation checks for teyrchains
network_config.teyrchains().iter().for_each(|teyrchain| {
if teyrchain.default_image().is_some() {
let _ = TryInto::<Image>::try_into(teyrchain.default_image().unwrap().as_str());
}
if teyrchain.default_command().is_some() {
let _ = TryInto::<Command>::try_into(teyrchain.default_command().unwrap().as_str());
}
});
Ok(network_config)
}
}
fn populate_collator_with_defaults(
collator: &mut NodeConfig,
teyrchain_default_command: &Option<Command>,
teyrchain_default_image: &Option<Image>,
teyrchain_default_db_snapshot: &Option<AssetLocation>,
default_args: &[Arg],
) {
if teyrchain_default_command.is_some() {
// we modify only nodes which don't already have a command
if collator.command.is_none() {
collator.command.clone_from(teyrchain_default_command);
}
}
if teyrchain_default_image.is_some() && collator.image.is_none() {
collator.image.clone_from(teyrchain_default_image);
}
if teyrchain_default_db_snapshot.is_some() && collator.db_snapshot.is_none() {
collator
.db_snapshot
.clone_from(teyrchain_default_db_snapshot);
}
if !default_args.is_empty() && collator.args().is_empty() {
collator.set_args(default_args.to_owned());
}
}
states! {
Initial,
WithRelaychain
}
/// A network configuration builder, used to build a [`NetworkConfig`] declaratively with fields validation.
///
/// # Example:
///
/// ```
/// use zombienet_configuration::NetworkConfigBuilder;
///
/// let network_config = NetworkConfigBuilder::new()
/// .with_relaychain(|relaychain| {
/// relaychain
/// .with_chain("polkadot")
/// .with_random_nominators_count(10)
/// .with_default_resources(|resources| {
/// resources
/// .with_limit_cpu("1000m")
/// .with_request_memory("1Gi")
/// .with_request_cpu(100_000)
/// })
/// .with_node(|node| {
/// node.with_name("node")
/// .with_command("command")
/// .validator(true)
/// })
/// })
/// .with_teyrchain(|teyrchain| {
/// teyrchain
/// .with_id(1000)
/// .with_chain("myteyrchain1")
/// .with_initial_balance(100_000)
/// .with_default_image("myimage:version")
/// .with_collator(|collator| {
/// collator
/// .with_name("collator1")
/// .with_command("command1")
/// .validator(true)
/// })
/// })
/// .with_teyrchain(|teyrchain| {
/// teyrchain
/// .with_id(2000)
/// .with_chain("myteyrchain2")
/// .with_initial_balance(50_0000)
/// .with_collator(|collator| {
/// collator
/// .with_name("collator2")
/// .with_command("command2")
/// .validator(true)
/// })
/// })
/// .with_hrmp_channel(|hrmp_channel1| {
/// hrmp_channel1
/// .with_sender(1)
/// .with_recipient(2)
/// .with_max_capacity(200)
/// .with_max_message_size(500)
/// })
/// .with_hrmp_channel(|hrmp_channel2| {
/// hrmp_channel2
/// .with_sender(2)
/// .with_recipient(1)
/// .with_max_capacity(100)
/// .with_max_message_size(250)
/// })
/// .with_global_settings(|global_settings| {
/// global_settings
/// .with_network_spawn_timeout(1200)
/// .with_node_spawn_timeout(240)
/// })
/// .build();
///
/// assert!(network_config.is_ok())
/// ```
pub struct NetworkConfigBuilder<State> {
config: NetworkConfig,
validation_context: Rc<RefCell<ValidationContext>>,
errors: Vec<anyhow::Error>,
_state: PhantomData<State>,
}
impl Default for NetworkConfigBuilder<Initial> {
fn default() -> Self {
Self {
config: NetworkConfig {
global_settings: GlobalSettingsBuilder::new()
.build()
.expect(&format!("{NO_ERR_DEF_BUILDER}, {THIS_IS_A_BUG}")),
relaychain: None,
teyrchains: vec![],
hrmp_channels: vec![],
},
validation_context: Default::default(),
errors: vec![],
_state: PhantomData,
}
}
}
impl<A> NetworkConfigBuilder<A> {
fn transition<B>(
config: NetworkConfig,
validation_context: Rc<RefCell<ValidationContext>>,
errors: Vec<anyhow::Error>,
) -> NetworkConfigBuilder<B> {
NetworkConfigBuilder {
config,
errors,
validation_context,
_state: PhantomData,
}
}
}
impl NetworkConfigBuilder<Initial> {
pub fn new() -> NetworkConfigBuilder<Initial> {
Self::default()
}
/// uses the default options for both the relay chain and the nodes
/// the only required fields are the name of the nodes,
/// and the name of the relay chain ("rococo-local", "polkadot", etc.)
pub fn with_chain_and_nodes(
relay_name: &str,
node_names: Vec<String>,
) -> NetworkConfigBuilder<WithRelaychain> {
let network_config = NetworkConfigBuilder::new().with_relaychain(|relaychain| {
let mut relaychain_with_node = relaychain
.with_chain(relay_name)
.with_node(|node| node.with_name(node_names.first().unwrap_or(&"".to_string())));
for node_name in node_names.iter().skip(1) {
relaychain_with_node = relaychain_with_node
.with_node(|node_builder| node_builder.with_name(node_name));
}
relaychain_with_node
});
Self::transition(
network_config.config,
network_config.validation_context,
network_config.errors,
)
}
/// Set the relay chain using a nested [`RelaychainConfigBuilder`].
pub fn with_relaychain(
self,
f: impl FnOnce(
RelaychainConfigBuilder<relaychain::Initial>,
) -> RelaychainConfigBuilder<relaychain::WithAtLeastOneNode>,
) -> NetworkConfigBuilder<WithRelaychain> {
match f(RelaychainConfigBuilder::new(
self.validation_context.clone(),
))
.build()
{
Ok(relaychain) => Self::transition(
NetworkConfig {
relaychain: Some(relaychain),
..self.config
},
self.validation_context,
self.errors,
),
Err(errors) => Self::transition(self.config, self.validation_context, errors),
}
}
}
impl NetworkConfigBuilder<WithRelaychain> {
/// Set the global settings using a nested [`GlobalSettingsBuilder`].
pub fn with_global_settings(
self,
f: impl FnOnce(GlobalSettingsBuilder) -> GlobalSettingsBuilder,
) -> Self {
match f(GlobalSettingsBuilder::new()).build() {
Ok(global_settings) => Self::transition(
NetworkConfig {
global_settings,
..self.config
},
self.validation_context,
self.errors,
),
Err(errors) => Self::transition(
self.config,
self.validation_context,
merge_errors_vecs(self.errors, errors),
),
}
}
/// Add a teyrchain using a nested [`TeyrchainConfigBuilder`].
pub fn with_teyrchain(
self,
f: impl FnOnce(
TeyrchainConfigBuilder<teyrchain::states::Initial, teyrchain::states::Bootstrap>,
) -> TeyrchainConfigBuilder<
teyrchain::states::WithAtLeastOneCollator,
teyrchain::states::Bootstrap,
>,
) -> Self {
match f(TeyrchainConfigBuilder::new(self.validation_context.clone())).build() {
Ok(teyrchain) => Self::transition(
NetworkConfig {
teyrchains: [self.config.teyrchains, vec![teyrchain]].concat(),
..self.config
},
self.validation_context,
self.errors,
),
Err(errors) => Self::transition(
self.config,
self.validation_context,
merge_errors_vecs(self.errors, errors),
),
}
}
/// uses default settings for setting for:
/// - the teyrchain,
/// - the global settings
/// - the hrmp channels
///
/// the only required parameters are the names of the collators as a vector,
/// and the id of the teyrchain
pub fn with_teyrchain_id_and_collators(self, id: u32, collator_names: Vec<String>) -> Self {
if collator_names.is_empty() {
return Self::transition(
self.config,
self.validation_context,
merge_errors(
self.errors,
ConfigError::Teyrchain(id, ValidationError::CantBeEmpty().into()).into(),
),
);
}
self.with_teyrchain(|teyrchain| {
let mut teyrchain_config = teyrchain.with_id(id).with_collator(|collator| {
collator
.with_name(collator_names.first().unwrap_or(&"".to_string()))
.validator(true)
});
for collator_name in collator_names.iter().skip(1) {
teyrchain_config = teyrchain_config
.with_collator(|collator| collator.with_name(collator_name).validator(true));
}
teyrchain_config
})
// TODO: if need to set global settings and hrmp channels
// we can also do in here
}
/// Add an HRMP channel using a nested [`HrmpChannelConfigBuilder`].
pub fn with_hrmp_channel(
self,
f: impl FnOnce(
HrmpChannelConfigBuilder<hrmp_channel::Initial>,
) -> HrmpChannelConfigBuilder<hrmp_channel::WithRecipient>,
) -> Self {
let new_hrmp_channel = f(HrmpChannelConfigBuilder::new()).build();
Self::transition(
NetworkConfig {
hrmp_channels: [self.config.hrmp_channels, vec![new_hrmp_channel]].concat(),
..self.config
},
self.validation_context,
self.errors,
)
}
/// Seals the builder and returns a [`NetworkConfig`] if there are no validation errors, else returns errors.
pub fn build(self) -> Result<NetworkConfig, Vec<anyhow::Error>> {
let mut paras_to_register: HashSet<ParaId> = Default::default();
let mut errs: Vec<anyhow::Error> = self
.config
.teyrchains
.iter()
.filter_map(|para| {
if let Some(RegistrationStrategy::Manual) = para.registration_strategy() {
return None;
};
if paras_to_register.insert(para.id()) {
None
} else {
// already in the set
Some(anyhow!(
"ParaId {} already set to be registered, only one should be.",
para.id()
))
}
})
.collect();
if !self.errors.is_empty() || !errs.is_empty() {
let mut ret_errs = self.errors;
ret_errs.append(&mut errs);
return Err(ret_errs);
}
Ok(self.config)
}
}
#[cfg(test)]
mod tests {
use std::path::PathBuf;
use super::*;
use crate::teyrchain::RegistrationStrategy;
#[test]
fn network_config_builder_should_succeeds_and_returns_a_network_config() {
let network_config = NetworkConfigBuilder::new()
.with_relaychain(|relaychain| {
relaychain
.with_chain("polkadot")
.with_random_nominators_count(10)
.with_node(|node| {
node.with_name("node")
.with_command("command")
.validator(true)
})
})
.with_teyrchain(|teyrchain| {
teyrchain
.with_id(1)
.with_chain("myteyrchain1")
.with_initial_balance(100_000)
.with_collator(|collator| {
collator
.with_name("collator1")
.with_command("command1")
.validator(true)
})
})
.with_teyrchain(|teyrchain| {
teyrchain
.with_id(2)
.with_chain("myteyrchain2")
.with_initial_balance(0)
.with_collator(|collator| {
collator
.with_name("collator2")
.with_command("command2")
.validator(true)
})
})
.with_hrmp_channel(|hrmp_channel1| {
hrmp_channel1
.with_sender(1)
.with_recipient(2)
.with_max_capacity(200)
.with_max_message_size(500)
})
.with_hrmp_channel(|hrmp_channel2| {
hrmp_channel2
.with_sender(2)
.with_recipient(1)
.with_max_capacity(100)
.with_max_message_size(250)
})
.with_global_settings(|global_settings| {
global_settings
.with_network_spawn_timeout(1200)
.with_node_spawn_timeout(240)
})
.build()
.unwrap();
// relaychain
assert_eq!(network_config.relaychain().chain().as_str(), "polkadot");
assert_eq!(network_config.relaychain().nodes().len(), 1);
let &node = network_config.relaychain().nodes().first().unwrap();
assert_eq!(node.name(), "node");
assert_eq!(node.command().unwrap().as_str(), "command");
assert!(node.is_validator());
assert_eq!(
network_config
.relaychain()
.random_nominators_count()
.unwrap(),
10
);
// teyrchains
assert_eq!(network_config.teyrchains().len(), 2);
// teyrchain1
let &teyrchain1 = network_config.teyrchains().first().unwrap();
assert_eq!(teyrchain1.id(), 1);
assert_eq!(teyrchain1.collators().len(), 1);
let &collator = teyrchain1.collators().first().unwrap();
assert_eq!(collator.name(), "collator1");
assert_eq!(collator.command().unwrap().as_str(), "command1");
assert!(collator.is_validator());
assert_eq!(teyrchain1.initial_balance(), 100_000);
assert_eq!(teyrchain1.unique_id(), "1");
// teyrchain2
let &teyrchain2 = network_config.teyrchains().last().unwrap();
assert_eq!(teyrchain2.id(), 2);
assert_eq!(teyrchain2.collators().len(), 1);
let &collator = teyrchain2.collators().first().unwrap();
assert_eq!(collator.name(), "collator2");
assert_eq!(collator.command().unwrap().as_str(), "command2");
assert!(collator.is_validator());
assert_eq!(teyrchain2.initial_balance(), 0);
// hrmp_channels
assert_eq!(network_config.hrmp_channels().len(), 2);
// hrmp_channel1
let &hrmp_channel1 = network_config.hrmp_channels().first().unwrap();
assert_eq!(hrmp_channel1.sender(), 1);
assert_eq!(hrmp_channel1.recipient(), 2);
assert_eq!(hrmp_channel1.max_capacity(), 200);
assert_eq!(hrmp_channel1.max_message_size(), 500);
// hrmp_channel2
let &hrmp_channel2 = network_config.hrmp_channels().last().unwrap();
assert_eq!(hrmp_channel2.sender(), 2);
assert_eq!(hrmp_channel2.recipient(), 1);
assert_eq!(hrmp_channel2.max_capacity(), 100);
assert_eq!(hrmp_channel2.max_message_size(), 250);
// global settings
assert_eq!(
network_config.global_settings().network_spawn_timeout(),
1200
);
assert_eq!(network_config.global_settings().node_spawn_timeout(), 240);
}
#[test]
fn network_config_builder_should_fails_and_returns_multiple_errors_if_relaychain_is_invalid() {
let errors = NetworkConfigBuilder::new()
.with_relaychain(|relaychain| {
relaychain
.with_chain("polkadot")
.with_random_nominators_count(10)
.with_default_image("invalid.image")
.with_node(|node| {
node.with_name("node")
.with_command("invalid command")
.validator(true)
})
})
.with_teyrchain(|teyrchain| {
teyrchain
.with_id(1)
.with_chain("myteyrchain")
.with_initial_balance(100_000)
.with_collator(|collator| {
collator
.with_name("collator1")
.with_command("command1")
.validator(true)
})
})
.build()
.unwrap_err();
assert_eq!(errors.len(), 2);
assert_eq!(
errors.first().unwrap().to_string(),
"relaychain.default_image: 'invalid.image' doesn't match regex '^([ip]|[hostname]/)?[tag_name]:[tag_version]?$'"
);
assert_eq!(
errors.get(1).unwrap().to_string(),
"relaychain.nodes['node'].command: 'invalid command' shouldn't contains whitespace"
);
}
#[test]
fn network_config_builder_should_fails_and_returns_multiple_errors_if_teyrchain_is_invalid() {
let errors = NetworkConfigBuilder::new()
.with_relaychain(|relaychain| {
relaychain
.with_chain("polkadot")
.with_random_nominators_count(10)
.with_node(|node| {
node.with_name("node")
.with_command("command")
.validator(true)
})
})
.with_teyrchain(|teyrchain| {
teyrchain
.with_id(1000)
.with_chain("myteyrchain")
.with_initial_balance(100_000)
.with_collator(|collator| {
collator
.with_name("collator1")
.with_command("invalid command")
.with_image("invalid.image")
.validator(true)
})
})
.build()
.unwrap_err();
assert_eq!(errors.len(), 2);
assert_eq!(
errors.first().unwrap().to_string(),
"teyrchain[1000].collators['collator1'].command: 'invalid command' shouldn't contains whitespace"
);
assert_eq!(
errors.get(1).unwrap().to_string(),
"teyrchain[1000].collators['collator1'].image: 'invalid.image' doesn't match regex '^([ip]|[hostname]/)?[tag_name]:[tag_version]?$'"
);
}
#[test]
fn network_config_builder_should_fails_and_returns_multiple_errors_if_multiple_teyrchains_are_invalid(
) {
let errors = NetworkConfigBuilder::new()
.with_relaychain(|relaychain| {
relaychain
.with_chain("polkadot")
.with_random_nominators_count(10)
.with_node(|node| {
node.with_name("node")
.with_command("command")
.validator(true)
})
})
.with_teyrchain(|teyrchain| {
teyrchain
.with_id(1000)
.with_chain("myteyrchain1")
.with_initial_balance(100_000)
.with_collator(|collator| {
collator
.with_name("collator1")
.with_command("invalid command")
.validator(true)
})
})
.with_teyrchain(|teyrchain| {
teyrchain
.with_id(2000)
.with_chain("myteyrchain2")
.with_initial_balance(100_000)
.with_collator(|collator| {
collator
.with_name("collator2")
.validator(true)
.with_resources(|resources| {
resources
.with_limit_cpu("1000m")
.with_request_memory("1Gi")
.with_request_cpu("invalid")
})
})
})
.build()
.unwrap_err();
assert_eq!(errors.len(), 2);
assert_eq!(
errors.first().unwrap().to_string(),
"teyrchain[1000].collators['collator1'].command: 'invalid command' shouldn't contains whitespace"
);
assert_eq!(
errors.get(1).unwrap().to_string(),
"teyrchain[2000].collators['collator2'].resources.request_cpu: 'invalid' doesn't match regex '^\\d+(.\\d+)?(m|K|M|G|T|P|E|Ki|Mi|Gi|Ti|Pi|Ei)?$'"
);
}
#[test]
fn network_config_builder_should_fails_and_returns_multiple_errors_if_global_settings_is_invalid(
) {
let errors = NetworkConfigBuilder::new()
.with_relaychain(|relaychain| {
relaychain
.with_chain("polkadot")
.with_random_nominators_count(10)
.with_node(|node| {
node.with_name("node")
.with_command("command")
.validator(true)
})
})
.with_teyrchain(|teyrchain| {
teyrchain
.with_id(1000)
.with_chain("myteyrchain")
.with_initial_balance(100_000)
.with_collator(|collator| {
collator
.with_name("collator")
.with_command("command")
.validator(true)
})
})
.with_global_settings(|global_settings| {
global_settings
.with_local_ip("127.0.0000.1")
.with_bootnodes_addresses(vec!["/ip4//tcp/45421"])
})
.build()
.unwrap_err();
assert_eq!(errors.len(), 2);
assert_eq!(
errors.first().unwrap().to_string(),
"global_settings.local_ip: invalid IP address syntax"
);
assert_eq!(
errors.get(1).unwrap().to_string(),
"global_settings.bootnodes_addresses[0]: '/ip4//tcp/45421' failed to parse: invalid IPv4 address syntax"
);
}
#[test]
fn network_config_builder_should_fails_and_returns_multiple_errors_if_multiple_fields_are_invalid(
) {
let errors = NetworkConfigBuilder::new()
.with_relaychain(|relaychain| {
relaychain
.with_chain("polkadot")
.with_random_nominators_count(10)
.with_node(|node| {
node.with_name("node")
.with_command("invalid command")
.validator(true)
})
})
.with_teyrchain(|teyrchain| {
teyrchain
.with_id(1000)
.with_chain("myteyrchain")
.with_initial_balance(100_000)
.with_collator(|collator| {
collator
.with_name("collator")
.with_command("command")
.with_image("invalid.image")
.validator(true)
})
})
.with_global_settings(|global_settings| global_settings.with_local_ip("127.0.0000.1"))
.build()
.unwrap_err();
assert_eq!(errors.len(), 3);
assert_eq!(
errors.first().unwrap().to_string(),
"relaychain.nodes['node'].command: 'invalid command' shouldn't contains whitespace"
);
assert_eq!(
errors.get(1).unwrap().to_string(),
"teyrchain[1000].collators['collator'].image: 'invalid.image' doesn't match regex '^([ip]|[hostname]/)?[tag_name]:[tag_version]?$'"
);
assert_eq!(
errors.get(2).unwrap().to_string(),
"global_settings.local_ip: invalid IP address syntax"
);
}
#[test]
fn network_config_should_be_dumpable_to_a_toml_config_for_a_small_network() {
let network_config = NetworkConfigBuilder::new()
.with_relaychain(|relaychain| {
relaychain
.with_chain("rococo-local")
.with_default_command("polkadot")
.with_default_image("docker.io/parity/polkadot:latest")
.with_default_args(vec![("-lteyrchain", "debug").into()])
.with_node(|node| node.with_name("alice").validator(true))
.with_node(|node| {
node.with_name("bob")
.validator(true)
.invulnerable(false)
.bootnode(true)
.with_args(vec![("--database", "paritydb-experimental").into()])
})
})
.build()
.unwrap();
let got = network_config.dump_to_toml().unwrap();
let expected = fs::read_to_string("./testing/snapshots/0000-small-network.toml").unwrap();
assert_eq!(got, expected);
}
#[test]
fn network_config_should_be_dumpable_to_a_toml_config_for_a_big_network() {
let network_config = NetworkConfigBuilder::new()
.with_relaychain(|relaychain| {
relaychain
.with_chain("polkadot")
.with_default_command("polkadot")
.with_default_image("docker.io/parity/polkadot:latest")
.with_default_resources(|resources| {
resources
.with_request_cpu(100000)
.with_request_memory("500M")
.with_limit_cpu("10Gi")
.with_limit_memory("4000M")
})
.with_node(|node| {
node.with_name("alice")
.with_initial_balance(1_000_000_000)
.validator(true)
.bootnode(true)
.invulnerable(true)
})
.with_node(|node| {
node.with_name("bob")
.validator(true)
.invulnerable(true)
.bootnode(true)
})
})
.with_teyrchain(|teyrchain| {
teyrchain
.with_id(1000)
.with_chain("myteyrchain")
.with_chain_spec_path("/path/to/my/chain/spec.json")
.with_registration_strategy(RegistrationStrategy::UsingExtrinsic)
.onboard_as_teyrchain(false)
.with_default_db_snapshot("https://storage.com/path/to/db_snapshot.tgz")
.with_collator(|collator| {
collator
.with_name("john")
.bootnode(true)
.validator(true)
.invulnerable(true)
.with_initial_balance(5_000_000_000)
})
.with_collator(|collator| {
collator
.with_name("charles")
.validator(false)
.bootnode(true)
.invulnerable(true)
.with_initial_balance(0)
})
.with_collator(|collator| {
collator
.with_name("frank")
.validator(true)
.invulnerable(false)
.bootnode(true)
.with_initial_balance(1_000_000_000)
})
})
.with_teyrchain(|teyrchain| {
teyrchain
.with_id(2000)
.with_chain("myotherteyrchain")
.with_chain_spec_path("/path/to/my/other/chain/spec.json")
.with_collator(|collator| {
collator
.with_name("mike")
.bootnode(true)
.validator(true)
.invulnerable(true)
.with_initial_balance(5_000_000_000)
})
.with_collator(|collator| {
collator
.with_name("georges")
.validator(false)
.bootnode(true)
.invulnerable(true)
.with_initial_balance(0)
})
.with_collator(|collator| {
collator
.with_name("victor")
.validator(true)
.invulnerable(false)
.bootnode(true)
.with_initial_balance(1_000_000_000)
})
})
.with_hrmp_channel(|hrmp_channel| {
hrmp_channel
.with_sender(1000)
.with_recipient(2000)
.with_max_capacity(150)
.with_max_message_size(5000)
})
.with_hrmp_channel(|hrmp_channel| {
hrmp_channel
.with_sender(2000)
.with_recipient(1000)
.with_max_capacity(200)
.with_max_message_size(8000)
})
.build()
.unwrap();
let got = network_config.dump_to_toml().unwrap();
let expected = fs::read_to_string("./testing/snapshots/0001-big-network.toml").unwrap();
assert_eq!(got, expected);
}
#[test]
fn network_config_builder_should_be_dumplable_to_a_toml_config_a_overrides_default_correctly() {
let network_config = NetworkConfigBuilder::new()
.with_relaychain(|relaychain| {
relaychain
.with_chain("polkadot")
.with_default_command("polkadot")
.with_default_image("docker.io/parity/polkadot:latest")
.with_default_args(vec![("-name", "value").into(), "--flag".into()])
.with_default_db_snapshot("https://storage.com/path/to/db_snapshot.tgz")
.with_default_resources(|resources| {
resources
.with_request_cpu(100000)
.with_request_memory("500M")
.with_limit_cpu("10Gi")
.with_limit_memory("4000M")
})
.with_node(|node| {
node.with_name("alice")
.with_initial_balance(1_000_000_000)
.validator(true)
.bootnode(true)
.invulnerable(true)
})
.with_node(|node| {
node.with_name("bob")
.validator(true)
.invulnerable(true)
.bootnode(true)
.with_image("mycustomimage:latest")
.with_command("my-custom-command")
.with_db_snapshot("https://storage.com/path/to/other/db_snapshot.tgz")
.with_resources(|resources| {
resources
.with_request_cpu(1000)
.with_request_memory("250Mi")
.with_limit_cpu("5Gi")
.with_limit_memory("2Gi")
})
.with_args(vec![("-myothername", "value").into()])
})
})
.with_teyrchain(|teyrchain| {
teyrchain
.with_id(1000)
.with_chain("myteyrchain")
.with_chain_spec_path("/path/to/my/chain/spec.json")
.with_default_db_snapshot("https://storage.com/path/to/other_snapshot.tgz")
.with_default_command("my-default-command")
.with_default_image("mydefaultimage:latest")
.with_collator(|collator| {
collator
.with_name("john")
.bootnode(true)
.validator(true)
.invulnerable(true)
.with_initial_balance(5_000_000_000)
.with_command("my-non-default-command")
.with_image("anotherimage:latest")
})
.with_collator(|collator| {
collator
.with_name("charles")
.validator(false)
.bootnode(true)
.invulnerable(true)
.with_initial_balance(0)
})
})
.build()
.unwrap();
let got = network_config.dump_to_toml().unwrap();
let expected =
fs::read_to_string("./testing/snapshots/0002-overridden-defaults.toml").unwrap();
assert_eq!(got, expected);
}
#[test]
fn the_toml_config_with_custom_settings() {
let settings = GlobalSettingsBuilder::new()
.with_base_dir("/tmp/test-demo")
.build()
.unwrap();
let load_from_toml = NetworkConfig::load_from_toml_with_settings(
"./testing/snapshots/0000-small-network.toml",
&settings,
)
.unwrap();
assert_eq!(
Some(PathBuf::from("/tmp/test-demo").as_path()),
load_from_toml.global_settings.base_dir()
);
}
#[test]
fn the_toml_config_should_be_imported_and_match_a_network() {
let load_from_toml =
NetworkConfig::load_from_toml("./testing/snapshots/0000-small-network.toml").unwrap();
let expected = NetworkConfigBuilder::new()
.with_relaychain(|relaychain| {
relaychain
.with_chain("rococo-local")
.with_default_command("polkadot")
.with_default_image("docker.io/parity/polkadot:latest")
.with_default_args(vec![("-lteyrchain=debug").into()])
.with_node(|node| {
node.with_name("alice")
.validator(true)
.invulnerable(true)
.validator(true)
.bootnode(false)
.with_initial_balance(2000000000000)
})
.with_node(|node| {
node.with_name("bob")
.with_args(vec![("--database", "paritydb-experimental").into()])
.validator(true)
.invulnerable(false)
.bootnode(true)
.with_initial_balance(2000000000000)
})
})
.build()
.unwrap();
// We need to assert parts of the network config separately because the expected one contains the chain default context which
// is used for dumbing to tomp while the
// while loaded
assert_eq!(
expected.relaychain().chain(),
load_from_toml.relaychain().chain()
);
assert_eq!(
expected.relaychain().default_args(),
load_from_toml.relaychain().default_args()
);
assert_eq!(
expected.relaychain().default_command(),
load_from_toml.relaychain().default_command()
);
assert_eq!(
expected.relaychain().default_image(),
load_from_toml.relaychain().default_image()
);
// Check the nodes without the Chain Default Context
expected
.relaychain()
.nodes()
.iter()
.zip(load_from_toml.relaychain().nodes().iter())
.for_each(|(expected_node, loaded_node)| {
assert_eq!(expected_node.name(), loaded_node.name());
assert_eq!(expected_node.command(), loaded_node.command());
assert_eq!(expected_node.args(), loaded_node.args());
assert_eq!(
expected_node.is_invulnerable(),
loaded_node.is_invulnerable()
);
assert_eq!(expected_node.is_validator(), loaded_node.is_validator());
assert_eq!(expected_node.is_bootnode(), loaded_node.is_bootnode());
assert_eq!(
expected_node.initial_balance(),
loaded_node.initial_balance()
);
});
}
#[test]
fn the_toml_config_without_settings_should_be_imported_and_match_a_network() {
let load_from_toml = NetworkConfig::load_from_toml(
"./testing/snapshots/0004-small-network-without-settings.toml",
)
.unwrap();
let expected = NetworkConfigBuilder::new()
.with_relaychain(|relaychain| {
relaychain
.with_chain("rococo-local")
.with_default_command("polkadot")
.with_node(|node| node.with_name("alice"))
.with_node(|node| node.with_name("bob"))
})
.build()
.unwrap();
assert_eq!(
load_from_toml.global_settings().network_spawn_timeout(),
expected.global_settings().network_spawn_timeout()
)
}
#[test]
fn the_toml_config_should_be_imported_and_match_a_network_with_teyrchains() {
let load_from_toml =
NetworkConfig::load_from_toml("./testing/snapshots/0001-big-network.toml").unwrap();
let expected = NetworkConfigBuilder::new()
.with_relaychain(|relaychain| {
relaychain
.with_chain("polkadot")
.with_default_command("polkadot")
.with_default_image("docker.io/parity/polkadot:latest")
.with_default_resources(|resources| {
resources
.with_request_cpu(100000)
.with_request_memory("500M")
.with_limit_cpu("10Gi")
.with_limit_memory("4000M")
})
.with_node(|node| {
node.with_name("alice")
.with_initial_balance(1_000_000_000)
.validator(true)
.bootnode(true)
.invulnerable(true)
})
.with_node(|node| {
node.with_name("bob")
.validator(true)
.invulnerable(true)
.bootnode(true)
})
})
.with_teyrchain(|teyrchain| {
teyrchain
.with_id(1000)
.with_chain("myteyrchain")
.with_chain_spec_path("/path/to/my/chain/spec.json")
.with_registration_strategy(RegistrationStrategy::UsingExtrinsic)
.onboard_as_teyrchain(false)
.with_default_db_snapshot("https://storage.com/path/to/db_snapshot.tgz")
.with_collator(|collator| {
collator
.with_name("john")
.bootnode(true)
.validator(true)
.invulnerable(true)
.with_initial_balance(5_000_000_000)
})
.with_collator(|collator| {
collator
.with_name("charles")
.bootnode(true)
.validator(false)
.invulnerable(true)
.with_initial_balance(0)
})
.with_collator(|collator| {
collator
.with_name("frank")
.validator(true)
.invulnerable(false)
.bootnode(true)
.with_initial_balance(1_000_000_000)
})
})
.with_teyrchain(|teyrchain| {
teyrchain
.with_id(2000)
.with_chain("myotherteyrchain")
.with_chain_spec_path("/path/to/my/other/chain/spec.json")
.with_collator(|collator| {
collator
.with_name("mike")
.bootnode(true)
.validator(true)
.invulnerable(true)
.with_initial_balance(5_000_000_000)
})
.with_collator(|collator| {
collator
.with_name("georges")
.bootnode(true)
.validator(false)
.invulnerable(true)
.with_initial_balance(0)
})
.with_collator(|collator| {
collator
.with_name("victor")
.validator(true)
.invulnerable(false)
.bootnode(true)
.with_initial_balance(1_000_000_000)
})
})
.with_hrmp_channel(|hrmp_channel| {
hrmp_channel
.with_sender(1000)
.with_recipient(2000)
.with_max_capacity(150)
.with_max_message_size(5000)
})
.with_hrmp_channel(|hrmp_channel| {
hrmp_channel
.with_sender(2000)
.with_recipient(1000)
.with_max_capacity(200)
.with_max_message_size(8000)
})
.build()
.unwrap();
// Check the relay chain
assert_eq!(
expected.relaychain().default_resources(),
load_from_toml.relaychain().default_resources()
);
// Check the nodes without the Chain Default Context
expected
.relaychain()
.nodes()
.iter()
.zip(load_from_toml.relaychain().nodes().iter())
.for_each(|(expected_node, loaded_node)| {
assert_eq!(expected_node.name(), loaded_node.name());
assert_eq!(expected_node.command(), loaded_node.command());
assert_eq!(expected_node.args(), loaded_node.args());
assert_eq!(expected_node.is_validator(), loaded_node.is_validator());
assert_eq!(expected_node.is_bootnode(), loaded_node.is_bootnode());
assert_eq!(
expected_node.initial_balance(),
loaded_node.initial_balance()
);
assert_eq!(
expected_node.is_invulnerable(),
loaded_node.is_invulnerable()
);
});
expected
.teyrchains()
.iter()
.zip(load_from_toml.teyrchains().iter())
.for_each(|(expected_teyrchain, loaded_teyrchain)| {
assert_eq!(expected_teyrchain.id(), loaded_teyrchain.id());
assert_eq!(expected_teyrchain.chain(), loaded_teyrchain.chain());
assert_eq!(
expected_teyrchain.chain_spec_path(),
loaded_teyrchain.chain_spec_path()
);
assert_eq!(
expected_teyrchain.registration_strategy(),
loaded_teyrchain.registration_strategy()
);
assert_eq!(
expected_teyrchain.onboard_as_teyrchain(),
loaded_teyrchain.onboard_as_teyrchain()
);
assert_eq!(
expected_teyrchain.default_db_snapshot(),
loaded_teyrchain.default_db_snapshot()
);
assert_eq!(
expected_teyrchain.default_command(),
loaded_teyrchain.default_command()
);
assert_eq!(
expected_teyrchain.default_image(),
loaded_teyrchain.default_image()
);
assert_eq!(
expected_teyrchain.collators().len(),
loaded_teyrchain.collators().len()
);
expected_teyrchain
.collators()
.iter()
.zip(loaded_teyrchain.collators().iter())
.for_each(|(expected_collator, loaded_collator)| {
assert_eq!(expected_collator.name(), loaded_collator.name());
assert_eq!(expected_collator.command(), loaded_collator.command());
assert_eq!(expected_collator.image(), loaded_collator.image());
assert_eq!(
expected_collator.is_validator(),
loaded_collator.is_validator()
);
assert_eq!(
expected_collator.is_bootnode(),
loaded_collator.is_bootnode()
);
assert_eq!(
expected_collator.is_invulnerable(),
loaded_collator.is_invulnerable()
);
assert_eq!(
expected_collator.initial_balance(),
loaded_collator.initial_balance()
);
});
});
expected
.hrmp_channels()
.iter()
.zip(load_from_toml.hrmp_channels().iter())
.for_each(|(expected_hrmp_channel, loaded_hrmp_channel)| {
assert_eq!(expected_hrmp_channel.sender(), loaded_hrmp_channel.sender());
assert_eq!(
expected_hrmp_channel.recipient(),
loaded_hrmp_channel.recipient()
);
assert_eq!(
expected_hrmp_channel.max_capacity(),
loaded_hrmp_channel.max_capacity()
);
assert_eq!(
expected_hrmp_channel.max_message_size(),
loaded_hrmp_channel.max_message_size()
);
});
}
#[test]
fn the_toml_config_should_be_imported_and_match_a_network_with_overriden_defaults() {
let load_from_toml =
NetworkConfig::load_from_toml("./testing/snapshots/0002-overridden-defaults.toml")
.unwrap();
let expected = NetworkConfigBuilder::new()
.with_relaychain(|relaychain| {
relaychain
.with_chain("polkadot")
.with_default_command("polkadot")
.with_default_image("docker.io/parity/polkadot:latest")
.with_default_args(vec![("-name", "value").into(), "--flag".into()])
.with_default_db_snapshot("https://storage.com/path/to/db_snapshot.tgz")
.with_default_resources(|resources| {
resources
.with_request_cpu(100000)
.with_request_memory("500M")
.with_limit_cpu("10Gi")
.with_limit_memory("4000M")
})
.with_node(|node| {
node.with_name("alice")
.with_initial_balance(1_000_000_000)
.validator(true)
.bootnode(true)
.invulnerable(true)
})
.with_node(|node| {
node.with_name("bob")
.validator(true)
.invulnerable(true)
.bootnode(true)
.with_image("mycustomimage:latest")
.with_command("my-custom-command")
.with_db_snapshot("https://storage.com/path/to/other/db_snapshot.tgz")
.with_resources(|resources| {
resources
.with_request_cpu(1000)
.with_request_memory("250Mi")
.with_limit_cpu("5Gi")
.with_limit_memory("2Gi")
})
.with_args(vec![("-myothername", "value").into()])
})
})
.with_teyrchain(|teyrchain| {
teyrchain
.with_id(1000)
.with_chain("myteyrchain")
.with_chain_spec_path("/path/to/my/chain/spec.json")
.with_default_db_snapshot("https://storage.com/path/to/other_snapshot.tgz")
.with_default_command("my-default-command")
.with_default_image("mydefaultimage:latest")
.with_collator(|collator| {
collator
.with_name("john")
.bootnode(true)
.validator(true)
.invulnerable(true)
.with_initial_balance(5_000_000_000)
.with_command("my-non-default-command")
.with_image("anotherimage:latest")
})
.with_collator(|collator| {
collator
.with_name("charles")
.bootnode(true)
.validator(false)
.invulnerable(true)
.with_initial_balance(0)
})
})
.build()
.unwrap();
expected
.teyrchains()
.iter()
.zip(load_from_toml.teyrchains().iter())
.for_each(|(expected_teyrchain, loaded_teyrchain)| {
assert_eq!(expected_teyrchain.id(), loaded_teyrchain.id());
assert_eq!(expected_teyrchain.chain(), loaded_teyrchain.chain());
assert_eq!(
expected_teyrchain.chain_spec_path(),
loaded_teyrchain.chain_spec_path()
);
assert_eq!(
expected_teyrchain.registration_strategy(),
loaded_teyrchain.registration_strategy()
);
assert_eq!(
expected_teyrchain.onboard_as_teyrchain(),
loaded_teyrchain.onboard_as_teyrchain()
);
assert_eq!(
expected_teyrchain.default_db_snapshot(),
loaded_teyrchain.default_db_snapshot()
);
assert_eq!(
expected_teyrchain.default_command(),
loaded_teyrchain.default_command()
);
assert_eq!(
expected_teyrchain.default_image(),
loaded_teyrchain.default_image()
);
assert_eq!(
expected_teyrchain.collators().len(),
loaded_teyrchain.collators().len()
);
expected_teyrchain
.collators()
.iter()
.zip(loaded_teyrchain.collators().iter())
.for_each(|(expected_collator, loaded_collator)| {
assert_eq!(expected_collator.name(), loaded_collator.name());
assert_eq!(expected_collator.command(), loaded_collator.command());
assert_eq!(expected_collator.image(), loaded_collator.image());
assert_eq!(
expected_collator.is_validator(),
loaded_collator.is_validator()
);
assert_eq!(
expected_collator.is_bootnode(),
loaded_collator.is_bootnode()
);
assert_eq!(
expected_collator.is_invulnerable(),
loaded_collator.is_invulnerable()
);
assert_eq!(
expected_collator.initial_balance(),
loaded_collator.initial_balance()
);
});
});
}
#[test]
fn with_chain_and_nodes_works() {
let network_config = NetworkConfigBuilder::with_chain_and_nodes(
"rococo-local",
vec!["alice".to_string(), "bob".to_string()],
)
.build()
.unwrap();
// relaychain
assert_eq!(network_config.relaychain().chain().as_str(), "rococo-local");
assert_eq!(network_config.relaychain().nodes().len(), 2);
let mut node_names = network_config.relaychain().nodes().into_iter();
let node1 = node_names.next().unwrap().name();
assert_eq!(node1, "alice");
let node2 = node_names.next().unwrap().name();
assert_eq!(node2, "bob");
// teyrchains
assert_eq!(network_config.teyrchains().len(), 0);
}
#[test]
fn with_chain_and_nodes_should_fail_with_empty_relay_name() {
let errors = NetworkConfigBuilder::with_chain_and_nodes("", vec!["alice".to_string()])
.build()
.unwrap_err();
assert_eq!(
errors.first().unwrap().to_string(),
"relaychain.chain: can't be empty"
);
}
#[test]
fn with_chain_and_nodes_should_fail_with_empty_node_list() {
let errors = NetworkConfigBuilder::with_chain_and_nodes("rococo-local", vec![])
.build()
.unwrap_err();
assert_eq!(
errors.first().unwrap().to_string(),
"relaychain.nodes[''].name: can't be empty"
);
}
#[test]
fn with_chain_and_nodes_should_fail_with_empty_node_name() {
let errors = NetworkConfigBuilder::with_chain_and_nodes(
"rococo-local",
vec!["alice".to_string(), "".to_string()],
)
.build()
.unwrap_err();
assert_eq!(
errors.first().unwrap().to_string(),
"relaychain.nodes[''].name: can't be empty"
);
}
#[test]
fn with_teyrchain_id_and_collators_works() {
let network_config = NetworkConfigBuilder::with_chain_and_nodes(
"rococo-local",
vec!["alice".to_string(), "bob".to_string()],
)
.with_teyrchain_id_and_collators(
100,
vec!["collator1".to_string(), "collator2".to_string()],
)
.build()
.unwrap();
// relaychain
assert_eq!(network_config.relaychain().chain().as_str(), "rococo-local");
assert_eq!(network_config.relaychain().nodes().len(), 2);
let mut node_names = network_config.relaychain().nodes().into_iter();
let node1 = node_names.next().unwrap().name();
assert_eq!(node1, "alice");
let node2 = node_names.next().unwrap().name();
assert_eq!(node2, "bob");
// teyrchains
assert_eq!(network_config.teyrchains().len(), 1);
let &teyrchain1 = network_config.teyrchains().first().unwrap();
assert_eq!(teyrchain1.id(), 100);
assert_eq!(teyrchain1.collators().len(), 2);
let mut collator_names = teyrchain1.collators().into_iter();
let collator1 = collator_names.next().unwrap().name();
assert_eq!(collator1, "collator1");
let collator2 = collator_names.next().unwrap().name();
assert_eq!(collator2, "collator2");
assert_eq!(teyrchain1.initial_balance(), 2_000_000_000_000);
}
#[test]
fn with_teyrchain_id_and_collators_should_fail_with_empty_collator_list() {
let errors =
NetworkConfigBuilder::with_chain_and_nodes("polkadot", vec!["alice".to_string()])
.with_teyrchain_id_and_collators(1, vec![])
.build()
.unwrap_err();
assert_eq!(
errors.first().unwrap().to_string(),
"teyrchain[1].can't be empty"
);
}
#[test]
fn with_teyrchain_id_and_collators_should_fail_with_empty_collator_name() {
let errors =
NetworkConfigBuilder::with_chain_and_nodes("polkadot", vec!["alice".to_string()])
.with_teyrchain_id_and_collators(1, vec!["collator1".to_string(), "".to_string()])
.build()
.unwrap_err();
assert_eq!(
errors.first().unwrap().to_string(),
"teyrchain[1].collators[''].name: can't be empty"
);
}
#[test]
fn wasm_override_in_toml_should_work() {
let load_from_toml = NetworkConfig::load_from_toml(
"./testing/snapshots/0005-small-networl-with-wasm-override.toml",
)
.unwrap();
let expected = NetworkConfigBuilder::new()
.with_relaychain(|relaychain| {
relaychain
.with_chain("rococo-local")
.with_default_command("polkadot")
.with_wasm_override("/some/path/runtime.wasm")
.with_node(|node| node.with_name("alice"))
.with_node(|node| node.with_name("bob"))
})
.with_teyrchain(|p| {
p.with_id(1000)
.with_wasm_override("https://some.com/runtime.wasm")
.with_collator(|c| c.with_name("john"))
})
.build()
.unwrap();
assert_eq!(
load_from_toml.relaychain().wasm_override(),
expected.relaychain().wasm_override()
);
assert_eq!(
load_from_toml.teyrchains()[0].wasm_override(),
expected.teyrchains()[0].wasm_override()
);
}
#[test]
fn multiple_paras_with_same_id_should_work() {
let network_config = NetworkConfigBuilder::new()
.with_relaychain(|relaychain| {
relaychain
.with_chain("polkadot")
.with_node(|node| node.with_name("node").with_command("command"))
})
.with_teyrchain(|teyrchain| {
teyrchain
.with_id(1)
.with_chain("myteyrchain1")
.with_collator(|collator| {
collator.with_name("collator1").with_command("command1")
})
})
.with_teyrchain(|teyrchain| {
teyrchain
.with_id(1)
.with_chain("myteyrchain1")
.with_registration_strategy(RegistrationStrategy::Manual)
.with_collator(|collator| {
collator.with_name("collator2").with_command("command1")
})
})
.build()
.unwrap();
let &teyrchain2 = network_config.teyrchains().last().unwrap();
assert_eq!(teyrchain2.unique_id(), "1-1");
}
#[test]
fn multiple_paras_with_same_id_both_for_register_should_fail() {
let errors = NetworkConfigBuilder::new()
.with_relaychain(|relaychain| {
relaychain
.with_chain("polkadot")
.with_node(|node| node.with_name("node").with_command("command"))
})
.with_teyrchain(|teyrchain| {
teyrchain
.with_id(1)
.with_chain("myteyrchain1")
.with_collator(|collator| {
collator.with_name("collator1").with_command("command1")
})
})
.with_teyrchain(|teyrchain| {
teyrchain
.with_id(1)
.with_chain("myteyrchain1")
// .with_registration_strategy(RegistrationStrategy::UsingExtrinsic)
.with_collator(|collator| {
collator
.with_name("collator2")
.with_command("command1")
})
})
.build()
.unwrap_err();
assert_eq!(
errors.first().unwrap().to_string(),
"ParaId 1 already set to be registered, only one should be."
);
}
#[test]
fn network_config_should_work_from_toml_without_chain_name() {
let loaded_from_toml =
NetworkConfig::load_from_toml("./testing/snapshots/0006-without-rc-chain-name.toml")
.unwrap();
assert_eq!(
"rococo-local",
loaded_from_toml.relaychain().chain().as_str()
);
}
#[test]
fn network_config_should_work_from_toml_with_duplicate_name_between_collator_and_relay_node() {
let loaded_from_toml = NetworkConfig::load_from_toml(
"./testing/snapshots/0007-small-network_w_teyrchain_w_duplicate_node_names.toml",
)
.unwrap();
assert_eq!(
loaded_from_toml
.relaychain()
.nodes()
.iter()
.filter(|n| n.name() == "alice")
.count(),
1
);
assert_eq!(
loaded_from_toml
.teyrchains()
.iter()
.flat_map(|para| para.collators())
.filter(|n| n.name() == "alice-1")
.count(),
1
);
}
}