diff --git a/crates/configuration/src/network.rs b/crates/configuration/src/network.rs index f6cb355f6a718995b47a8c0749e68bc6df733984..41f113eb6c748cb0f1b13ce457fe7eb2400fa020 100644 --- a/crates/configuration/src/network.rs +++ b/crates/configuration/src/network.rs @@ -17,8 +17,7 @@ use crate::{ helpers::merge_errors_vecs, macros::states, node::NodeConfig, - resources::Resources, - types::{Arg, AssetLocation, Chain, Command, Image, ValidationContext}, + types::{Arg, Chain, Command, Image, ValidationContext}, }, }; @@ -82,34 +81,32 @@ impl NetworkConfig { Err(anyhow!("Relay chain does not exist."))? } + // println!("+++++++++++++++++++++++++++"); + // println!("{:?}", network_config); + // println!("+++++++++++++++++++++++++++"); + // retrieve the defaults relaychain for assigning to nodes if needed - let relaychain_default_command = network_config + let relaychain_default_command: Option<Command> = network_config .relaychain .as_ref() .unwrap() .default_command() .cloned(); - let relaychain_default_image = network_config + let relaychain_default_image: Option<Image> = network_config .relaychain .as_ref() .unwrap() .default_image() .cloned(); - let relaychain_default_resources = network_config - .relaychain - .as_ref() - .unwrap() - .default_resources() - .cloned(); - - let relaychain_default_db_snapshot = network_config - .relaychain - .as_ref() - .unwrap() - .default_db_snapshot() - .cloned(); + let relaychain_default_db_snapshot: Option<crate::shared::types::AssetLocation> = + network_config + .relaychain + .as_ref() + .unwrap() + .default_db_snapshot() + .cloned(); let default_args: Vec<Arg> = network_config .relaychain() @@ -149,10 +146,6 @@ impl NetworkConfig { node.image = relaychain_default_image.clone(); } - if relaychain_default_resources.is_some() && node.resources.is_none() { - node.resources = relaychain_default_resources.clone(); - } - if relaychain_default_db_snapshot.is_some() && node.db_snapshot.is_none() { node.db_snapshot = relaychain_default_db_snapshot.clone(); } @@ -168,6 +161,9 @@ impl NetworkConfig { .expect(&format!("{}, {}", NO_ERR_DEF_BUILDER, THIS_IS_A_BUG)) .set_nodes(nodes); + // Validation checks for parachains + // TryInto::<Chain>::try_into(network_config.parachains().first().unwrap().chain().as_str())?; + Ok(network_config) } } @@ -1045,86 +1041,223 @@ mod tests { }); } + #[test] + fn the_toml_config_should_be_imported_and_match_a_network_with_parachains() { + 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_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_parachain(|parachain| { - // parachain - // .with_id(1000) - // .with_chain("myparachain") - // .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) - // .invulnerable(true) - // .with_initial_balance(0) - // }) - // }) - // .build() - // .unwrap(); - - // assert_eq!(load_from_toml, expected); - // } + 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_parachain(|parachain| { + parachain + .with_id(1000) + .with_chain("myparachain") + .with_chain_spec_path("/path/to/my/chain/spec.json") + .with_registration_strategy(RegistrationStrategy::UsingExtrinsic) + .onboard_as_parachain(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) + .invulnerable(true) + .with_initial_balance(0) + }) + .with_collator(|collator| { + collator + .with_name("frank") + .validator(true) + .bootnode(true) + .with_initial_balance(1_000_000_000) + }) + }) + .with_parachain(|parachain| { + parachain + .with_id(2000) + .with_chain("myotherparachain") + .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) + .invulnerable(true) + .with_initial_balance(0) + }) + .with_collator(|collator| { + collator + .with_name("victor") + .validator(true) + .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(); - #[test] - fn dumb_test() { - // let file_str = fs::read_to_string("./testing/snapshots/0002-overridden-defaults.toml").unwrap(); - // let converted_toml = toml::from_str::<toml::Value>(file_str.as_str()).unwrap(); + // Check the relay chain + assert_eq!( + expected.relaychain().default_resources(), + load_from_toml.relaychain().default_resources() + ); - let config = - NetworkConfig::load_from_toml("./testing/snapshots/0000-small-network.toml").unwrap(); - println!("{:?}", config); + // 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 + .parachains() + .iter() + .zip(load_from_toml.parachains().iter()) + .for_each(|(expected_parachain, loaded_parachain)| { + assert_eq!(expected_parachain.id(), loaded_parachain.id()); + assert_eq!(expected_parachain.chain(), loaded_parachain.chain()); + assert_eq!( + expected_parachain.chain_spec_path(), + loaded_parachain.chain_spec_path() + ); + // TODO: Test this + // assert_eq!( + // expected_parachain.registration_strategy(), + // loaded_parachain.registration_strategy() + // ); + assert_eq!( + expected_parachain.onboard_as_parachain(), + loaded_parachain.onboard_as_parachain() + ); + assert_eq!( + expected_parachain.default_db_snapshot(), + loaded_parachain.default_db_snapshot() + ); + assert_eq!( + expected_parachain.default_command(), + loaded_parachain.default_command() + ); + assert_eq!( + expected_parachain.default_image(), + loaded_parachain.default_image() + ); + assert_eq!( + expected_parachain.collators().len(), + loaded_parachain.collators().len() + ); + expected_parachain + .collators() + .iter() + .zip(loaded_parachain.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() + ); + }); } } diff --git a/crates/configuration/src/parachain.rs b/crates/configuration/src/parachain.rs index d8e1f486789573e66c6c37cd0f34bc6a14b76785..4f2ee2a20f2ffc2944fec91321cadbd4ae5beb8b 100644 --- a/crates/configuration/src/parachain.rs +++ b/crates/configuration/src/parachain.rs @@ -43,7 +43,10 @@ pub struct ParachainConfig { chain: Option<Chain>, #[serde(flatten)] registration_strategy: Option<RegistrationStrategy>, - #[serde(skip_serializing_if = "super::utils::is_true")] + #[serde( + skip_serializing_if = "super::utils::is_true", + default = "default_as_true" + )] onboard_as_parachain: bool, #[serde(rename = "balance")] initial_balance: U128, @@ -51,7 +54,7 @@ pub struct ParachainConfig { default_image: Option<Image>, default_resources: Option<Resources>, default_db_snapshot: Option<AssetLocation>, - #[serde(skip_serializing_if = "std::vec::Vec::is_empty")] + #[serde(skip_serializing_if = "std::vec::Vec::is_empty", default)] default_args: Vec<Arg>, genesis_wasm_path: Option<AssetLocation>, genesis_wasm_generator: Option<Command>, @@ -60,12 +63,16 @@ pub struct ParachainConfig { chain_spec_path: Option<AssetLocation>, #[serde(rename = "cumulus_based")] is_cumulus_based: bool, - #[serde(skip_serializing_if = "std::vec::Vec::is_empty")] + #[serde(skip_serializing_if = "std::vec::Vec::is_empty", default)] bootnodes_addresses: Vec<Multiaddr>, - #[serde(skip_serializing_if = "std::vec::Vec::is_empty")] + #[serde(skip_serializing_if = "std::vec::Vec::is_empty", default)] collators: Vec<NodeConfig>, } +fn default_as_true() -> bool { + true +} + impl ParachainConfig { /// The parachain ID. pub fn id(&self) -> u32 { diff --git a/crates/configuration/src/relaychain.rs b/crates/configuration/src/relaychain.rs index 53ab45a74af49e0cdb4997fcc4b449f8d3438bef..b365d0cb3cb09914874c250c53123b11aede901f 100644 --- a/crates/configuration/src/relaychain.rs +++ b/crates/configuration/src/relaychain.rs @@ -25,7 +25,7 @@ pub struct RelaychainConfig { chain_spec_path: Option<AssetLocation>, random_nominators_count: Option<u32>, max_nominations: Option<u8>, - #[serde(skip_serializing_if = "std::vec::Vec::is_empty")] + #[serde(skip_serializing_if = "std::vec::Vec::is_empty", default)] nodes: Vec<NodeConfig>, } @@ -60,11 +60,6 @@ impl RelaychainConfig { self.default_args.iter().collect::<Vec<&Arg>>() } - /// Set the default arguments that will be used to launch the node command. - pub(crate) fn set_default_args(&mut self, args: Vec<Arg>) { - self.default_args = args; - } - /// The location of an pre-existing chain specification for the relay chain. pub fn chain_spec_path(&self) -> Option<&AssetLocation> { self.chain_spec_path.as_ref() diff --git a/crates/configuration/src/shared/constants.rs b/crates/configuration/src/shared/constants.rs index aeb8d4755569d73964ace8ce4e2fc811d658419e..0b288389a050dd7142833e4b8d7cf8f57375b39f 100644 --- a/crates/configuration/src/shared/constants.rs +++ b/crates/configuration/src/shared/constants.rs @@ -1,6 +1,7 @@ pub const VALID_REGEX: &str = "Regex should be valid. "; pub const BORROWABLE: &str = "Must be borrowable as mutable. "; -pub const RELAY_NOT_NONE: &str = "Typestate should ensure the relaychain isn't None at this point. "; +pub const RELAY_NOT_NONE: &str = + "Typestate should ensure the relaychain isn't None at this point. "; pub const SHOULD_COMPILE: &str = "Should compile with success. "; pub const INFAILABLE: &str = "Infaillible. "; pub const NO_ERR_DEF_BUILDER: &str = "Should have no errors for default builder. "; diff --git a/crates/configuration/src/shared/resources.rs b/crates/configuration/src/shared/resources.rs index 3887fe863c033435f6234e60a94bb8d4ad5ed66d..41d716508266574af949826446b72a8fbdfbb54e 100644 --- a/crates/configuration/src/shared/resources.rs +++ b/crates/configuration/src/shared/resources.rs @@ -2,7 +2,11 @@ use std::error::Error; use lazy_static::lazy_static; use regex::Regex; -use serde::{de::{self}, ser::SerializeStruct, Deserialize, Serialize}; +use serde::{ + de::{self}, + ser::SerializeStruct, + Deserialize, Serialize, +}; use super::{ errors::{ConversionError, FieldError}, @@ -116,9 +120,9 @@ impl Serialize for Resources { impl<'de> Deserialize<'de> for Resources { fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> - where - D: serde::Deserializer<'de> { - + where + D: serde::Deserializer<'de>, + { struct ResourcesVisitor; #[derive(Deserialize)] @@ -135,28 +139,28 @@ impl<'de> Deserialize<'de> for Resources { } fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error> - where - A: de::MapAccess<'de>, + where + A: de::MapAccess<'de>, { let mut resources: Resources = Resources::default(); - + while let Some((key, value)) = map.next_entry::<String, ResourcesField>()? { match key.as_str() { "requests" => { resources.request_memory = value.memory; resources.request_cpu = value.cpu; - } + }, "limits" => { - resources.limit_memory = value.memory; + resources.limit_memory = value.memory; resources.limit_cpu = value.cpu; - } + }, _ => { return Err(de::Error::unknown_field( &key, &["requests", "limits", "cpu", "memory"], )) - } - } + }, + } } Ok(resources) } diff --git a/crates/configuration/src/shared/types.rs b/crates/configuration/src/shared/types.rs index a9faf73bc8a8d9a9018a67bfbbce39e8eaf63bab..4f0451af18c3a6a2d31b5f138778f7a8f0b7223b 100644 --- a/crates/configuration/src/shared/types.rs +++ b/crates/configuration/src/shared/types.rs @@ -222,7 +222,7 @@ impl Command { /// assert!(matches!(path_location, AssetLocation::FilePath(value) if value.to_str().unwrap() == "/tmp/path/to/my/file")); /// assert!(matches!(path_location2, AssetLocation::FilePath(value) if value.to_str().unwrap() == "/tmp/path/to/my/file")); /// ``` -#[derive(Debug, Clone, PartialEq, Deserialize)] +#[derive(Debug, Clone, PartialEq)] pub enum AssetLocation { Url(Url), FilePath(PathBuf), @@ -270,6 +270,32 @@ impl Serialize for AssetLocation { } } +impl<'de> Deserialize<'de> for AssetLocation { + fn deserialize<D>(deserializer: D) -> Result<AssetLocation, D::Error> + where + D: Deserializer<'de>, + { + struct AssetLocationVisitor; + + impl<'de> de::Visitor<'de> for AssetLocationVisitor { + type Value = AssetLocation; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a string") + } + + fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> + where + E: de::Error, + { + Ok(AssetLocation::from(v)) + } + } + + deserializer.deserialize_any(AssetLocationVisitor) + } +} + /// A CLI argument passed to an executed command, can be an option with an assigned value or a simple flag to enable/disable a feature. /// A flag arg can be constructed from a `&str` and a option arg can be constructed from a `(&str, &str)`. ///