diff --git a/Cargo.toml b/Cargo.toml index d34f63f0..d8951f2f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -57,8 +57,8 @@ toml_edit = { version = "0.22", features = ["serde"] } symlink = "0.1" serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } -zombienet-sdk = "0.2.2" -zombienet-support = "0.2.2" +zombienet-sdk = "0.2.5" +zombienet-support = "0.2.5" git2_credentials = "0.13.0" # pop-cli diff --git a/crates/pop-cli/src/commands/up/parachain.rs b/crates/pop-cli/src/commands/up/parachain.rs index 1106ec24..0766e570 100644 --- a/crates/pop-cli/src/commands/up/parachain.rs +++ b/crates/pop-cli/src/commands/up/parachain.rs @@ -17,14 +17,23 @@ pub(crate) struct ZombienetCommand { /// The Zombienet network configuration file to be used. #[arg(short, long)] file: String, - /// The version of Polkadot to be used for the relay chain, as per the release tag (e.g. - /// "v1.11.0"). + /// The version of the binary to be used for the relay chain, as per the release tag (e.g. "v1.13.0"). + /// See https://github.com/paritytech/polkadot-sdk/releases for more details. #[arg(short, long)] relay_chain: Option, - /// The version of Polkadot to be used for a system parachain, as per the release tag (e.g. - /// "v1.11.0"). Defaults to the relay chain version if not specified. + /// The version of the runtime to be used for the relay chain, as per the release tag (e.g. "v1.2.7"). + /// See https://github.com/polkadot-fellows/runtimes/releases for more details. + #[arg(short = 'R', long)] + relay_chain_runtime: Option, + /// The version of the binary to be used for system parachains, as per the release tag (e.g. "v1.13.0"). + /// Defaults to the relay chain version if not specified. + /// See https://github.com/paritytech/polkadot-sdk/releases for more details. #[arg(short, long)] system_parachain: Option, + /// The version of the runtime to be used for system parachains, as per the release tag (e.g. "v1.2.7"). + /// See https://github.com/polkadot-fellows/runtimes/releases for more details. + #[arg(short = 'S', long)] + system_parachain_runtime: Option, /// The url of the git repository of a parachain to be used, with branch/release tag/commit specified as #fragment (e.g. 'https://github.com/org/repository#ref'). /// A specific binary name can also be optionally specified via query string parameter (e.g. 'https://github.com/org/repository?binaryname#ref'), defaulting to the name of the repository when not specified. #[arg(short, long)] @@ -49,8 +58,10 @@ impl ZombienetCommand { let mut zombienet = match Zombienet::new( &cache, &self.file, - self.relay_chain.as_ref().map(|v| v.as_str()), - self.system_parachain.as_ref().map(|v| v.as_str()), + self.relay_chain.as_deref(), + self.relay_chain_runtime.as_deref(), + self.system_parachain.as_deref(), + self.system_parachain_runtime.as_deref(), self.parachain.as_ref(), ) .await @@ -167,7 +178,7 @@ impl ZombienetCommand { )) .dim() .to_string(); - log::warning(format!("⚠️ The following binaries specified in the network configuration file cannot be found locally:\n {list}"))?; + log::warning(format!("⚠️ The following binaries required to launch the network cannot be found locally:\n {list}"))?; // Prompt for automatic sourcing of binaries let list = style(format!( diff --git a/crates/pop-parachains/src/up/chain_specs.rs b/crates/pop-parachains/src/up/chain_specs.rs new file mode 100644 index 00000000..5ecedf37 --- /dev/null +++ b/crates/pop-parachains/src/up/chain_specs.rs @@ -0,0 +1,196 @@ +// SPDX-License-Identifier: GPL-3.0 + +use crate::{ + up::{ + sourcing::{ + self, + traits::{Source as _, *}, + GitHub::*, + Source, + }, + target, + }, + Binary, Error, +}; +use std::path::Path; +use strum::{EnumProperty as _, VariantArray as _}; +use strum_macros::{AsRefStr, EnumProperty, VariantArray}; + +/// A supported runtime. +#[derive(AsRefStr, Debug, EnumProperty, PartialEq, VariantArray)] +pub(super) enum Runtime { + /// Kusama. + #[strum(props( + Repository = "https://github.com/r0gue-io/polkadot-runtimes", + Binary = "chain-spec-generator", + Chain = "kusama-local", + Fallback = "v1.2.7" + ))] + Kusama, + /// Paseo. + #[strum(props( + Repository = "https://github.com/r0gue-io/paseo-runtimes", + Binary = "chain-spec-generator", + Chain = "paseo-local", + Fallback = "v1.2.4" + ))] + Paseo, + /// Polkadot. + #[strum(props( + Repository = "https://github.com/r0gue-io/polkadot-runtimes", + Binary = "chain-spec-generator", + Chain = "polkadot-local", + Fallback = "v1.2.7" + ))] + Polkadot, +} + +impl TryInto for &Runtime { + /// Attempt the conversion. + /// + /// # Arguments + /// * `tag` - If applicable, a tag used to determine a specific release. + /// * `latest` - If applicable, some specifier used to determine the latest source. + fn try_into(&self, tag: Option, latest: Option) -> Result { + Ok(match self { + _ => { + // Source from GitHub release asset + let repo = crate::GitHub::parse(self.repository())?; + let name = self.name().to_lowercase(); + let binary = self.binary(); + Source::GitHub(ReleaseArchive { + owner: repo.org, + repository: repo.name, + tag, + tag_format: self.tag_format().map(|t| t.into()), + archive: format!("{binary}-{}.tar.gz", target()?), + contents: vec![(binary, Some(format!("{name}-{binary}")))], + latest, + }) + }, + }) + } +} + +impl Runtime { + /// The chain spec identifier. + fn chain(&self) -> &'static str { + self.get_str("Chain").expect("expected specification of `Chain`") + } + + /// The name of the runtime. + fn name(&self) -> &str { + self.as_ref() + } +} + +impl sourcing::traits::Source for Runtime {} + +pub(super) async fn chain_spec_generator( + chain: &str, + version: Option<&str>, + cache: &Path, +) -> Result, Error> { + for runtime in Runtime::VARIANTS.iter().filter(|r| chain.to_lowercase().ends_with(r.chain())) { + let name = format!("{}-{}", runtime.name().to_lowercase(), runtime.binary()); + let releases = runtime.releases().await?; + let tag = Binary::resolve_version(&name, version, &releases, cache); + // Only set latest when caller has not explicitly specified a version to use + let latest = version + .is_none() + .then(|| releases.iter().nth(0).map(|v| v.to_string())) + .flatten(); + let binary = Binary::Source { + name: name.to_string(), + source: TryInto::try_into(&runtime, tag, latest)?, + cache: cache.to_path_buf(), + }; + return Ok(Some(binary)); + } + Ok(None) +} + +#[cfg(test)] +mod tests { + use super::*; + use tempfile::tempdir; + + #[tokio::test] + async fn kusama_works() -> anyhow::Result<()> { + let expected = Runtime::Kusama; + let version = "v1.2.7"; + let temp_dir = tempdir()?; + let binary = chain_spec_generator("kusama-local", Some(version), temp_dir.path()) + .await? + .unwrap(); + assert!(matches!(binary, Binary::Source { name, source, cache } + if name == format!("{}-{}", expected.name().to_lowercase(), expected.binary()) && + source == Source::GitHub(ReleaseArchive { + owner: "r0gue-io".to_string(), + repository: "polkadot-runtimes".to_string(), + tag: Some(version.to_string()), + tag_format: None, + archive: format!("chain-spec-generator-{}.tar.gz", target()?), + contents: ["chain-spec-generator"].map(|b| (b, Some(format!("kusama-{b}").to_string()))).to_vec(), + latest: binary.latest().map(|l| l.to_string()), + }) && + cache == temp_dir.path() + )); + Ok(()) + } + + #[tokio::test] + async fn paseo_works() -> anyhow::Result<()> { + let expected = Runtime::Paseo; + let version = "v1.2.4"; + let temp_dir = tempdir()?; + let binary = chain_spec_generator("paseo-local", Some(version), temp_dir.path()) + .await? + .unwrap(); + assert!(matches!(binary, Binary::Source { name, source, cache } + if name == format!("{}-{}", expected.name().to_lowercase(), expected.binary()) && + source == Source::GitHub(ReleaseArchive { + owner: "r0gue-io".to_string(), + repository: "paseo-runtimes".to_string(), + tag: Some(version.to_string()), + tag_format: None, + archive: format!("chain-spec-generator-{}.tar.gz", target()?), + contents: ["chain-spec-generator"].map(|b| (b, Some(format!("paseo-{b}").to_string()))).to_vec(), + latest: binary.latest().map(|l| l.to_string()), + }) && + cache == temp_dir.path() + )); + Ok(()) + } + + #[tokio::test] + async fn polkadot_works() -> anyhow::Result<()> { + let expected = Runtime::Polkadot; + let version = "v1.2.7"; + let temp_dir = tempdir()?; + let binary = chain_spec_generator("polkadot-local", Some(version), temp_dir.path()) + .await? + .unwrap(); + assert!(matches!(binary, Binary::Source { name, source, cache } + if name == format!("{}-{}", expected.name().to_lowercase(), expected.binary()) && + source == Source::GitHub(ReleaseArchive { + owner: "r0gue-io".to_string(), + repository: "polkadot-runtimes".to_string(), + tag: Some(version.to_string()), + tag_format: None, + archive: format!("chain-spec-generator-{}.tar.gz", target()?), + contents: ["chain-spec-generator"].map(|b| (b, Some(format!("polkadot-{b}").to_string()))).to_vec(), + latest: binary.latest().map(|l| l.to_string()), + }) && + cache == temp_dir.path() + )); + Ok(()) + } + + #[tokio::test] + async fn chain_spec_generator_returns_none_when_no_match() -> anyhow::Result<()> { + let temp_dir = tempdir()?; + assert_eq!(chain_spec_generator("rococo-local", None, temp_dir.path()).await?, None); + Ok(()) + } +} diff --git a/crates/pop-parachains/src/up/mod.rs b/crates/pop-parachains/src/up/mod.rs index 9563cf25..b8e99f1f 100644 --- a/crates/pop-parachains/src/up/mod.rs +++ b/crates/pop-parachains/src/up/mod.rs @@ -1,4 +1,5 @@ // SPDX-License-Identifier: GPL-3.0 + use crate::{errors::Error, utils::git::GitHub}; use glob::glob; use indexmap::IndexMap; @@ -16,6 +17,7 @@ use url::Url; use zombienet_sdk::{Network, NetworkConfig, NetworkConfigExt}; use zombienet_support::fs::local::LocalFileSystem; +mod chain_specs; mod parachains; mod relay; mod sourcing; @@ -36,20 +38,30 @@ impl Zombienet { /// # Arguments /// * `cache` - The location used for caching binaries. /// * `network_config` - The configuration file to be used to launch a network. - /// * `relay_chain_version` - The specific version used for the relay chain (`None` will use the latest available version). - /// * `system_parachain_version` - The specific version used for the system chain (`None` will use the latest available version). + /// * `relay_chain_version` - The specific binary version used for the relay chain (`None` will use the latest available version). + /// * `relay_chain_runtime_version` - The specific runtime version used for the relay chain runtime (`None` will use the latest available version). + /// * `system_parachain_version` - The specific binary version used for system parachains (`None` will use the latest available version). + /// * `system_parachain_runtime_version` - The specific runtime version used for system parachains (`None` will use the latest available version). /// * `parachains` - The parachain(s) specified. pub async fn new( cache: &Path, network_config: &str, relay_chain_version: Option<&str>, + relay_chain_runtime_version: Option<&str>, system_parachain_version: Option<&str>, + system_parachain_runtime_version: Option<&str>, parachains: Option<&Vec>, ) -> Result { // Parse network config let network_config = NetworkConfiguration::from(network_config)?; // Determine relay and parachain requirements based on arguments and config - let relay_chain = Self::relay_chain(relay_chain_version, &network_config, cache).await?; + let relay_chain = Self::relay_chain( + relay_chain_version, + relay_chain_runtime_version, + &network_config, + cache, + ) + .await?; let parachains = match parachains { Some(parachains) => Some( parachains @@ -62,6 +74,7 @@ impl Zombienet { let parachains = Self::parachains( &relay_chain, system_parachain_version, + system_parachain_runtime_version, parachains, &network_config, cache, @@ -72,21 +85,29 @@ impl Zombienet { /// The binaries required to launch the network. pub fn binaries(&mut self) -> impl Iterator { - once::<&mut Binary>(&mut self.relay_chain.binary) - .chain(self.parachains.values_mut().map(|p| &mut p.binary)) + once([Some(&mut self.relay_chain.binary), self.relay_chain.chain_spec_generator.as_mut()]) + .chain( + self.parachains + .values_mut() + .map(|p| [Some(&mut p.binary), p.chain_spec_generator.as_mut()]), + ) + .flatten() + .filter_map(|b| b) } /// Determine parachain configuration based on specified version and network configuration. /// /// # Arguments /// * `relay_chain` - The configuration required to launch the relay chain. - /// * `system_parachain_version` - The specific version used for the system chain (`None` will use the latest available version). + /// * `system_parachain_version` - The specific binary version used for system parachains (`None` will use the latest available version). + /// * `system_parachain_runtime_version` - The specific runtime version used for system parachains (`None` will use the latest available version). /// * `parachains` - The parachain repositories specified. /// * `network_config` - The network configuration to be used to launch a network. /// * `cache` - The location used for caching binaries. async fn parachains( relay_chain: &RelayChain, system_parachain_version: Option<&str>, + system_parachain_runtime_version: Option<&str>, parachains: Option>, network_config: &NetworkConfiguration, cache: &Path, @@ -103,6 +124,8 @@ impl Zombienet { .ok_or_else(|| Error::Config("expected `parachain` to have `id`".into()))? as u32; + let chain = table.get("chain").and_then(|i| i.as_str()); + let command = NetworkConfiguration::default_command(table) .cloned() .or_else(|| { @@ -134,7 +157,9 @@ impl Zombienet { id, &command, system_parachain_version, + system_parachain_runtime_version, &relay_chain.binary.version().expect("expected relay chain to have version"), + chain, cache, ) .await? @@ -150,7 +175,7 @@ impl Zombienet { .nth(0) .map(|v| v.as_str()) }); - if let Some(parachain) = parachains::from(id, &command, version, cache).await? { + if let Some(parachain) = parachains::from(id, &command, version, chain, cache).await? { paras.insert(id, parachain); continue; } @@ -158,14 +183,14 @@ impl Zombienet { // Check if parachain binary source specified as an argument if let Some(parachains) = parachains.as_ref() { for repo in parachains.iter().filter(|r| command == r.package) { - paras.insert(id, Parachain::from_repository(id, repo, cache)?); + paras.insert(id, Parachain::from_repository(id, repo, chain, cache)?); continue 'outer; } } // Check if command references a local binary if ["./", "../", "/"].iter().any(|p| command.starts_with(p)) { - paras.insert(id, Parachain::from_local(id, command.into())?); + paras.insert(id, Parachain::from_local(id, command.into(), chain)?); continue; } @@ -177,20 +202,24 @@ impl Zombienet { /// Determines relay chain configuration based on specified version and network configuration. /// /// # Arguments - /// * `version` - The specific version used for the relay chain (`None` will use the latest available version). + /// * `version` - The specific binary version used for the relay chain (`None` will use the latest available version). + /// * `runtime_version` - The specific runtime version used for the relay chain runtime (`None` will use the latest available version). /// * `network_config` - The network configuration to be used to launch a network. /// * `cache` - The location used for caching binaries. async fn relay_chain( version: Option<&str>, + runtime_version: Option<&str>, network_config: &NetworkConfiguration, cache: &Path, ) -> Result { - // Attempt to determine relay from default_command + // Attempt to determine relay from configuration let relay_chain = network_config.relay_chain()?; + let chain = relay_chain.get("chain").and_then(|i| i.as_str()); if let Some(default_command) = NetworkConfiguration::default_command(relay_chain).and_then(|c| c.as_str()) { - let relay = relay::from(default_command, version, cache).await?; + let relay = + relay::from(default_command, version, runtime_version, chain, cache).await?; // Validate any node config is supported if let Some(nodes) = NetworkConfiguration::nodes(relay_chain) { for node in nodes { @@ -222,7 +251,10 @@ impl Zombienet { } }, None => { - relay = Some(relay::from(command, version, cache).await?); + relay = Some( + relay::from(command, version, runtime_version, chain, cache) + .await?, + ); }, } } @@ -232,7 +264,7 @@ impl Zombienet { } } // Otherwise use default - return Ok(relay::default(version, cache).await?); + return Ok(relay::default(version, runtime_version, chain, cache).await?); } /// Launches the local network. @@ -376,6 +408,12 @@ impl NetworkConfiguration { } } } + // Configure chain spec generator + if let Some(path) = relay_chain.chain_spec_generator.as_ref().map(|b| b.path()) { + let command = format!("{} {}", Self::resolve_path(&path)?, "{{chainName}}"); + *relay_chain_config.entry("chain_spec_command").or_insert(value(&command)) = + value(&command); + } // Update parachain config if let Some(tables) = self.parachains_mut() { @@ -392,6 +430,12 @@ impl NetworkConfiguration { let path = Self::resolve_path(¶.binary.path())?; table.insert("default_command", value(&path)); + // Configure chain spec generator + if let Some(path) = para.chain_spec_generator.as_ref().map(|b| b.path()) { + let command = format!("{} {}", Self::resolve_path(&path)?, "{{chainName}}"); + *table.entry("chain_spec_command").or_insert(value(&command)) = value(&command); + } + // Resolve individual collator command to binary if let Some(collators) = table.get_mut("collators").and_then(|p| p.as_array_of_tables_mut()) @@ -436,6 +480,10 @@ struct RelayChain { binary: Binary, /// The additional workers required by the relay chain node. workers: [&'static str; 2], + /// The name of the chain. + chain: String, + /// If applicable, the binary used to generate a chain specification. + chain_spec_generator: Option, } /// The configuration required to launch a parachain. @@ -445,6 +493,10 @@ struct Parachain { id: u32, /// The binary used to launch a parachain node. binary: Binary, + /// The name of the chain. + chain: Option, + /// If applicable, the binary used to generate a chain specification. + chain_spec_generator: Option, } impl Parachain { @@ -453,7 +505,8 @@ impl Parachain { /// # Arguments /// * `id` - The parachain identifier on the local network. /// * `path` - The path to the local binary. - fn from_local(id: u32, path: PathBuf) -> Result { + /// * `chain` - The chain specified. + fn from_local(id: u32, path: PathBuf, chain: Option<&str>) -> Result { let name = path .file_name() .and_then(|f| f.to_str()) @@ -461,7 +514,12 @@ impl Parachain { .to_string(); // Check if package manifest can be found within path let manifest = resolve_manifest(&name, &path)?; - Ok(Parachain { id, binary: Binary::Local { name, path, manifest } }) + Ok(Parachain { + id, + binary: Binary::Local { name, path, manifest }, + chain: chain.map(|c| c.to_string()), + chain_spec_generator: None, + }) } /// Initializes the configuration required to launch a parachain using a binary sourced from the specified repository. @@ -469,8 +527,14 @@ impl Parachain { /// # Arguments /// * `id` - The parachain identifier on the local network. /// * `repo` - The repository to be used to source the binary. + /// * `chain` - The chain specified. /// * `cache` - The location used for caching binaries. - fn from_repository(id: u32, repo: &Repository, cache: &Path) -> Result { + fn from_repository( + id: u32, + repo: &Repository, + chain: Option<&str>, + cache: &Path, + ) -> Result { // Check for GitHub repository to be able to download source as an archive if repo.url.host_str().is_some_and(|h| h.to_lowercase() == "github.com") { let github = GitHub::parse(repo.url.as_str())?; @@ -489,6 +553,8 @@ impl Parachain { source, cache: cache.to_path_buf(), }, + chain: chain.map(|c| c.to_string()), + chain_spec_generator: None, }) } else { Ok(Parachain { @@ -504,6 +570,8 @@ impl Parachain { }, cache: cache.to_path_buf(), }, + chain: chain.map(|c| c.to_string()), + chain_spec_generator: None, }) } } @@ -545,7 +613,7 @@ impl Binary { Self::Local { .. } => None, Self::Source { source, .. } => { if let GitHub(ReleaseArchive { latest, .. }) = source { - latest.as_ref().map(|v| v.as_str()) + latest.as_deref() } else { None } @@ -840,9 +908,16 @@ chain = "rococo-local" )?; let version = "v1.12.0"; - let zombienet = - Zombienet::new(&cache, config.path().to_str().unwrap(), Some(version), None, None) - .await?; + let zombienet = Zombienet::new( + &cache, + config.path().to_str().unwrap(), + Some(version), + None, + None, + None, + None, + ) + .await?; let relay_chain = &zombienet.relay_chain.binary; assert_eq!(relay_chain.name(), "polkadot"); @@ -857,6 +932,48 @@ chain = "rococo-local" Ok(()) } + #[tokio::test] + async fn new_with_relay_chain_spec_generator_works() -> Result<()> { + let temp_dir = tempdir()?; + let cache = PathBuf::from(temp_dir.path()); + let config = Builder::new().suffix(".toml").tempfile()?; + writeln!( + config.as_file(), + r#" +[relaychain] +chain = "paseo-local" +"# + )?; + let version = "v1.2.7"; + + let zombienet = Zombienet::new( + &cache, + config.path().to_str().unwrap(), + None, + Some(version), + None, + None, + None, + ) + .await?; + + assert_eq!(zombienet.relay_chain.chain, "paseo-local"); + let chain_spec_generator = &zombienet.relay_chain.chain_spec_generator.unwrap(); + assert_eq!(chain_spec_generator.name(), "paseo-chain-spec-generator"); + assert_eq!( + chain_spec_generator.path(), + temp_dir.path().join(format!("paseo-chain-spec-generator-{version}")) + ); + assert_eq!(chain_spec_generator.version().unwrap(), version); + assert!(matches!( + chain_spec_generator, + Binary::Source { source: Source::GitHub(ReleaseArchive { tag, .. }), .. } + if *tag == Some(version.to_string()) + )); + assert!(zombienet.parachains.is_empty()); + Ok(()) + } + #[tokio::test] async fn new_with_default_command_works() -> Result<()> { let temp_dir = tempdir()?; @@ -872,9 +989,16 @@ default_command = "./bin-v1.6.0/polkadot" )?; let version = "v1.12.0"; - let zombienet = - Zombienet::new(&cache, config.path().to_str().unwrap(), Some(version), None, None) - .await?; + let zombienet = Zombienet::new( + &cache, + config.path().to_str().unwrap(), + Some(version), + None, + None, + None, + None, + ) + .await?; let relay_chain = &zombienet.relay_chain.binary; assert_eq!(relay_chain.name(), "polkadot"); @@ -908,9 +1032,16 @@ command = "polkadot" )?; let version = "v1.12.0"; - let zombienet = - Zombienet::new(&cache, config.path().to_str().unwrap(), Some(version), None, None) - .await?; + let zombienet = Zombienet::new( + &cache, + config.path().to_str().unwrap(), + Some(version), + None, + None, + None, + None, + ) + .await?; let relay_chain = &zombienet.relay_chain.binary; assert_eq!(relay_chain.name(), "polkadot"); @@ -949,7 +1080,7 @@ command = "polkadot-v1.12.0" )?; assert!(matches!( - Zombienet::new(&cache, config.path().to_str().unwrap(), None, None, None).await, + Zombienet::new(&cache, config.path().to_str().unwrap(), None, None, None, None, None).await, Err(Error::UnsupportedCommand(error)) if error == "the relay chain command is unsupported: polkadot-v1.12.0" )); @@ -976,7 +1107,7 @@ command = "polkadot-v1.12.0" )?; assert!(matches!( - Zombienet::new(&cache, config.path().to_str().unwrap(), None, None, None).await, + Zombienet::new(&cache, config.path().to_str().unwrap(), None, None, None, None, None).await, Err(Error::UnsupportedCommand(error)) if error == "the relay chain command is unsupported: polkadot-v1.12.0" )); @@ -1005,8 +1136,10 @@ chain = "asset-hub-rococo-local" &cache, config.path().to_str().unwrap(), Some("v1.11.0"), + None, Some(system_parachain_version), None, + None, ) .await?; @@ -1026,6 +1159,53 @@ chain = "asset-hub-rococo-local" Ok(()) } + #[tokio::test] + async fn new_with_system_chain_spec_generator_works() -> Result<()> { + let temp_dir = tempdir()?; + let cache = PathBuf::from(temp_dir.path()); + let config = Builder::new().suffix(".toml").tempfile()?; + writeln!( + config.as_file(), + r#" +[relaychain] +chain = "paseo-local" + +[[parachains]] +id = 1000 +chain = "asset-hub-paseo-local" +"# + )?; + let version = "v1.12.0"; + + let zombienet = Zombienet::new( + &cache, + config.path().to_str().unwrap(), + None, + None, + None, + Some(version), + None, + ) + .await?; + + assert_eq!(zombienet.parachains.len(), 1); + let system_parachain = &zombienet.parachains.get(&1000).unwrap(); + assert_eq!(system_parachain.chain.as_ref().unwrap(), "asset-hub-paseo-local"); + let chain_spec_generator = system_parachain.chain_spec_generator.as_ref().unwrap(); + assert_eq!(chain_spec_generator.name(), "paseo-chain-spec-generator"); + assert_eq!( + chain_spec_generator.path(), + temp_dir.path().join(format!("paseo-chain-spec-generator-{version}")) + ); + assert_eq!(chain_spec_generator.version().unwrap(), version); + assert!(matches!( + chain_spec_generator, + Binary::Source { source: Source::GitHub(ReleaseArchive { tag, .. }), .. } + if *tag == Some(version.to_string()) + )); + Ok(()) + } + #[tokio::test] async fn new_with_pop_works() -> Result<()> { let temp_dir = tempdir()?; @@ -1043,8 +1223,16 @@ default_command = "pop-node" "# )?; - let zombienet = - Zombienet::new(&cache, config.path().to_str().unwrap(), None, None, None).await?; + let zombienet = Zombienet::new( + &cache, + config.path().to_str().unwrap(), + None, + None, + None, + None, + None, + ) + .await?; assert_eq!(zombienet.parachains.len(), 1); let pop = &zombienet.parachains.get(&4385).unwrap().binary; @@ -1083,6 +1271,8 @@ default_command = "pop-node" config.path().to_str().unwrap(), None, None, + None, + None, Some(&vec![format!("https://github.com/r0gue-io/pop-node#{version}")]), ) .await?; @@ -1117,8 +1307,16 @@ default_command = "./target/release/parachain-template-node" "# )?; - let zombienet = - Zombienet::new(&cache, config.path().to_str().unwrap(), None, None, None).await?; + let zombienet = Zombienet::new( + &cache, + config.path().to_str().unwrap(), + None, + None, + None, + None, + None, + ) + .await?; assert_eq!(zombienet.parachains.len(), 1); let pop = &zombienet.parachains.get(&2000).unwrap().binary; @@ -1149,8 +1347,16 @@ command = "./target/release/parachain-template-node" "# )?; - let zombienet = - Zombienet::new(&cache, config.path().to_str().unwrap(), None, None, None).await?; + let zombienet = Zombienet::new( + &cache, + config.path().to_str().unwrap(), + None, + None, + None, + None, + None, + ) + .await?; assert_eq!(zombienet.parachains.len(), 1); let pop = &zombienet.parachains.get(&2000).unwrap().binary; @@ -1184,6 +1390,8 @@ default_command = "moonbeam" config.path().to_str().unwrap(), None, None, + None, + None, Some(&vec![format!("https://github.com/moonbeam-foundation/moonbeam#{version}")]), ) .await?; @@ -1217,7 +1425,7 @@ chain = "rococo-local" )?; assert!(matches!( - Zombienet::new(&cache, config.path().to_str().unwrap(), None, None, None).await, + Zombienet::new(&cache, config.path().to_str().unwrap(), None, None, None, None, None).await, Err(Error::Config(error)) if error == "expected `parachain` to have `id`" )); @@ -1242,7 +1450,7 @@ default_command = "missing-binary" )?; assert!(matches!( - Zombienet::new(&cache, config.path().to_str().unwrap(), None, None, None).await, + Zombienet::new(&cache, config.path().to_str().unwrap(), None, None, None, None, None).await, Err(Error::MissingBinary(command)) if command == "missing-binary" )); @@ -1274,8 +1482,47 @@ default_command = "pop-node" "# )?; - let mut zombienet = - Zombienet::new(&cache, config.path().to_str().unwrap(), None, None, None).await?; + let mut zombienet = Zombienet::new( + &cache, + config.path().to_str().unwrap(), + None, + None, + None, + None, + None, + ) + .await?; + assert_eq!(zombienet.binaries().count(), 4); + Ok(()) + } + + #[tokio::test] + async fn binaries_includes_chain_spec_generators() -> Result<()> { + let temp_dir = tempdir()?; + let cache = PathBuf::from(temp_dir.path()); + let config = Builder::new().suffix(".toml").tempfile()?; + writeln!( + config.as_file(), + r#" +[relaychain] +chain = "paseo-local" + +[[parachains]] +id = 1000 +chain = "asset-hub-paseo-local" +"# + )?; + + let mut zombienet = Zombienet::new( + &cache, + config.path().to_str().unwrap(), + None, + None, + None, + None, + None, + ) + .await?; assert_eq!(zombienet.binaries().count(), 4); Ok(()) } @@ -1293,8 +1540,16 @@ chain = "rococo-local" "# )?; - let mut zombienet = - Zombienet::new(&cache, config.path().to_str().unwrap(), None, None, None).await?; + let mut zombienet = Zombienet::new( + &cache, + config.path().to_str().unwrap(), + None, + None, + None, + None, + None, + ) + .await?; assert!(matches!( zombienet.spawn().await, Err(Error::MissingBinary(error)) @@ -1317,8 +1572,16 @@ chain = "rococo-local" )?; File::create(cache.join("polkadot"))?; - let mut zombienet = - Zombienet::new(&cache, config.path().to_str().unwrap(), None, None, None).await?; + let mut zombienet = Zombienet::new( + &cache, + config.path().to_str().unwrap(), + None, + None, + None, + None, + None, + ) + .await?; if let Binary::Source { source: Source::GitHub(ReleaseArchive { tag, .. }), .. } = &mut zombienet.relay_chain.binary { @@ -1349,8 +1612,16 @@ chain = "rococo-local" File::create(cache.join(format!("polkadot-execute-worker-{version}")))?; File::create(cache.join(format!("polkadot-prepare-worker-{version}")))?; - let mut zombienet = - Zombienet::new(&cache, config.path().to_str().unwrap(), None, None, None).await?; + let mut zombienet = Zombienet::new( + &cache, + config.path().to_str().unwrap(), + None, + None, + None, + None, + None, + ) + .await?; assert!(!cache.join("polkadot-execute-worker").exists()); assert!(!cache.join("polkadot-prepare-worker").exists()); let _ = zombienet.spawn().await; @@ -1377,8 +1648,16 @@ validator = true "# )?; - let mut zombienet = - Zombienet::new(&cache, config.path().to_str().unwrap(), None, None, None).await?; + let mut zombienet = Zombienet::new( + &cache, + config.path().to_str().unwrap(), + None, + None, + None, + None, + None, + ) + .await?; for b in zombienet.binaries() { b.source(true, &Output, true).await?; } @@ -1530,6 +1809,8 @@ command = "./target/release/parachain-template-node" manifest: None, }, workers: ["polkadot-execute-worker", ""], + chain: "rococo-local".to_string(), + chain_spec_generator: None, }, &[ ( @@ -1541,6 +1822,8 @@ command = "./target/release/parachain-template-node" path: system_chain.to_path_buf(), manifest: None, }, + chain: None, + chain_spec_generator: None, }, ), ( @@ -1552,6 +1835,8 @@ command = "./target/release/parachain-template-node" path: pop.to_path_buf(), manifest: None, }, + chain: None, + chain_spec_generator: None, }, ), ( @@ -1563,6 +1848,8 @@ command = "./target/release/parachain-template-node" path: parachain_template.to_path_buf(), manifest: None, }, + chain: None, + chain_spec_generator: None, }, ), ] @@ -1624,6 +1911,122 @@ node_spawn_timeout = 300 Ok(()) } + #[test] + fn configure_with_chain_spec_generator_works() -> Result<(), Error> { + let config = Builder::new().suffix(".toml").tempfile()?; + writeln!( + config.as_file(), + r#" +[relaychain] +chain = "paseo-local" + +[[relaychain.nodes]] +name = "alice" +command = "polkadot" + +[[parachains]] +id = 1000 +chain = "asset-hub-paseo-local" + +[[parachains.collators]] +name = "asset-hub" +command = "polkadot-parachain" + +"# + )?; + let mut network_config = NetworkConfiguration::from(config.path())?; + + let relay_chain_binary = Builder::new().tempfile()?; + let relay_chain = relay_chain_binary.path(); + File::create(&relay_chain)?; + let relay_chain_spec_generator = Builder::new().tempfile()?; + let relay_chain_spec_generator = relay_chain_spec_generator.path(); + File::create(&relay_chain_spec_generator)?; + let system_chain_binary = Builder::new().tempfile()?; + let system_chain = system_chain_binary.path(); + File::create(&system_chain)?; + let system_chain_spec_generator = Builder::new().tempfile()?; + let system_chain_spec_generator = system_chain_spec_generator.path(); + File::create(&system_chain_spec_generator)?; + + let mut configured = network_config.configure( + &RelayChain { + binary: Binary::Local { + name: "polkadot".to_string(), + path: relay_chain.to_path_buf(), + manifest: None, + }, + workers: ["polkadot-execute-worker", ""], + chain: "paseo-local".to_string(), + chain_spec_generator: Some(Binary::Local { + name: "paseo-chain-spec-generator".to_string(), + path: relay_chain_spec_generator.to_path_buf(), + manifest: None, + }), + }, + &[( + 1000, + Parachain { + id: 1000, + binary: Binary::Local { + name: "polkadot-parachain".to_string(), + path: system_chain.to_path_buf(), + manifest: None, + }, + chain: Some("asset-hub-paseo-local".to_string()), + chain_spec_generator: Some(Binary::Local { + name: "paseo-chain-spec-generator".to_string(), + path: system_chain_spec_generator.to_path_buf(), + manifest: None, + }), + }, + )] + .into(), + )?; + assert_eq!("toml", configured.path().extension().unwrap()); + + let mut contents = String::new(); + configured.read_to_string(&mut contents)?; + println!("{contents}"); + assert_eq!( + contents, + format!( + r#" +[relaychain] +chain = "paseo-local" +default_command = "{0}" +chain_spec_command = "{1} {2}" + +[[relaychain.nodes]] +name = "alice" +command = "{0}" + +[[parachains]] +id = 1000 +chain = "asset-hub-paseo-local" +default_command = "{3}" +chain_spec_command = "{4} {2}" + +[[parachains.collators]] +name = "asset-hub" +command = "{3}" + +[settings] +timeout = 1000 +node_spawn_timeout = 300 + + +"#, + relay_chain.canonicalize()?.to_str().unwrap(), + relay_chain_spec_generator.canonicalize()?.to_str().unwrap(), + "{{chainName}}", + system_chain.canonicalize()?.to_str().unwrap(), + system_chain_spec_generator.canonicalize()?.to_str().unwrap(), + ) + ); + Ok(()) + } + #[test] fn resolves_path() -> Result<(), Error> { let working_dir = tempdir()?; @@ -1654,10 +2057,12 @@ node_spawn_timeout = 300 let name = "parachain-template-node"; let command = PathBuf::from("./target/release").join(&name); assert_eq!( - Parachain::from_local(2000, command.clone())?, + Parachain::from_local(2000, command.clone(), Some("dev"))?, Parachain { id: 2000, - binary: Binary::Local { name: name.to_string(), path: command, manifest: None } + binary: Binary::Local { name: name.to_string(), path: command, manifest: None }, + chain: Some("dev".to_string()), + chain_spec_generator: None, } ); Ok(()) @@ -1668,14 +2073,16 @@ node_spawn_timeout = 300 let name = "pop-parachains"; let command = PathBuf::from("./target/release").join(&name); assert_eq!( - Parachain::from_local(2000, command.clone())?, + Parachain::from_local(2000, command.clone(), Some("dev"))?, Parachain { id: 2000, binary: Binary::Local { name: name.to_string(), path: command, manifest: Some(PathBuf::from("./Cargo.toml")) - } + }, + chain: Some("dev".to_string()), + chain_spec_generator: None, } ); Ok(()) @@ -1686,12 +2093,12 @@ node_spawn_timeout = 300 let repo = Repository::parse("https://git.com/r0gue-io/pop-node#v1.0")?; let cache = tempdir()?; assert_eq!( - Parachain::from_repository(2000, &repo, cache.path())?, + Parachain::from_repository(2000, &repo, Some("dev"), cache.path())?, Parachain { id: 2000, binary: Binary::Source { name: "pop-node".to_string(), - source: Source::Git { + source: Git { url: repo.url, reference: repo.reference, manifest: None, @@ -1699,7 +2106,9 @@ node_spawn_timeout = 300 artifacts: vec!["pop-node".to_string()], }, cache: cache.path().to_path_buf(), - } + }, + chain: Some("dev".to_string()), + chain_spec_generator: None, } ); Ok(()) @@ -1710,7 +2119,7 @@ node_spawn_timeout = 300 let repo = Repository::parse("https://github.com/r0gue-io/pop-node#v1.0")?; let cache = tempdir()?; assert_eq!( - Parachain::from_repository(2000, &repo, cache.path())?, + Parachain::from_repository(2000, &repo, Some("dev"), cache.path())?, Parachain { id: 2000, binary: Binary::Source { @@ -1724,8 +2133,10 @@ node_spawn_timeout = 300 artifacts: vec!["pop-node".to_string()], }), cache: cache.path().to_path_buf(), - } - } + }, + chain: Some("dev".to_string()), + chain_spec_generator: None, + }, ); Ok(()) } @@ -1898,7 +2309,7 @@ node_spawn_timeout = 300 tag: tag.clone(), tag_format: Some(tag_format.to_string()), archive: archive.clone(), - contents: contents.to_vec(), + contents: contents.into_iter().map(|b| (b, None)).collect(), latest: latest.clone(), }), cache: temp_dir.path().to_path_buf(), diff --git a/crates/pop-parachains/src/up/parachains.rs b/crates/pop-parachains/src/up/parachains.rs index 4cd426fb..d252d9e0 100644 --- a/crates/pop-parachains/src/up/parachains.rs +++ b/crates/pop-parachains/src/up/parachains.rs @@ -1,7 +1,9 @@ // SPDX-License-Identifier: GPL-3.0 + use super::{ - sourcing, + chain_specs::chain_spec_generator, sourcing::{ + self, traits::{Source as _, *}, GitHub::ReleaseArchive, Source, @@ -50,7 +52,7 @@ impl TryInto for Parachain { tag, tag_format: self.tag_format().map(|t| t.into()), archive: format!("{}-{}.tar.gz", self.binary(), target()?), - contents: vec![self.binary()], + contents: vec![(self.binary(), None)], latest, }) }, @@ -66,13 +68,17 @@ impl sourcing::traits::Source for Parachain {} /// * `id` - The parachain identifier. /// * `command` - The command specified. /// * `version` - The version of the parachain binary to be used. -/// * `version` - The version of the relay chain binary being used. +/// * `runtime_version` - The version of the runtime to be used. +/// * `relay_chain_version` - The version of the relay chain binary being used. +/// * `chain` - The chain specified. /// * `cache` - The cache to be used. pub(super) async fn system( id: u32, command: &str, version: Option<&str>, - relay_chain: &str, + runtime_version: Option<&str>, + relay_chain_version: &str, + chain: Option<&str>, cache: &Path, ) -> Result, Error> { let para = &Parachain::System; @@ -84,14 +90,22 @@ pub(super) async fn system( Some(version) => (Some(version.to_string()), None), None => { // Default to same version as relay chain when not explicitly specified - let version = relay_chain.to_string(); // Only set latest when caller has not explicitly specified a version to use - (Some(version), para.releases().await?.into_iter().nth(0)) + (Some(relay_chain_version.to_string()), para.releases().await?.into_iter().nth(0)) }, }; let source = TryInto::try_into(para, tag, latest)?; let binary = Binary::Source { name: name.to_string(), source, cache: cache.to_path_buf() }; - return Ok(Some(super::Parachain { id, binary })); + let chain_spec_generator = match chain { + Some(chain) => chain_spec_generator(chain, runtime_version, cache).await?, + None => None, + }; + return Ok(Some(super::Parachain { + id, + binary, + chain: chain.map(|c| c.to_string()), + chain_spec_generator, + })); } /// Initialises the configuration required to launch a parachain. @@ -100,11 +114,13 @@ pub(super) async fn system( /// * `id` - The parachain identifier. /// * `command` - The command specified. /// * `version` - The version of the parachain binary to be used. +/// * `chain` - The chain specified. /// * `cache` - The cache to be used. pub(super) async fn from( id: u32, command: &str, version: Option<&str>, + chain: Option<&str>, cache: &Path, ) -> Result, Error> { for para in Parachain::VARIANTS.iter().filter(|p| p.binary() == command) { @@ -120,7 +136,12 @@ pub(super) async fn from( source: TryInto::try_into(para, tag, latest)?, cache: cache.to_path_buf(), }; - return Ok(Some(super::Parachain { id, binary })); + return Ok(Some(super::Parachain { + id, + binary, + chain: chain.map(|c| c.to_string()), + chain_spec_generator: None, + })); } Ok(None) } @@ -128,11 +149,22 @@ pub(super) async fn from( #[cfg(test)] mod tests { use super::*; + use std::path::PathBuf; use tempfile::tempdir; #[tokio::test] async fn system_matches_command() -> anyhow::Result<()> { - assert!(system(1000, "polkadot", None, "v1.12.0", tempdir()?.path()).await?.is_none()); + assert!(system( + 1000, + "polkadot", + None, + None, + "v1.12.0", + Some("asset-hub-rococo-local"), + tempdir()?.path() + ) + .await? + .is_none()); Ok(()) } @@ -143,9 +175,10 @@ mod tests { let para_id = 1000; let temp_dir = tempdir()?; - let parachain = system(para_id, expected.binary(), None, version, temp_dir.path()) - .await? - .unwrap(); + let parachain = + system(para_id, expected.binary(), None, None, version, None, temp_dir.path()) + .await? + .unwrap(); assert_eq!(para_id, parachain.id); assert!(matches!(parachain.binary, Binary::Source { name, source, cache } if name == expected.binary() && source == Source::GitHub(ReleaseArchive { @@ -154,7 +187,7 @@ mod tests { tag: Some(version.to_string()), tag_format: Some("polkadot-{tag}".to_string()), archive: format!("{name}-{}.tar.gz", target()?), - contents: vec![expected.binary()], + contents: vec![(expected.binary(), None)], latest: parachain.binary.latest().map(|l| l.to_string()), }) && cache == temp_dir.path() )); @@ -168,9 +201,10 @@ mod tests { let para_id = 1000; let temp_dir = tempdir()?; - let parachain = system(para_id, expected.binary(), Some(version), version, temp_dir.path()) - .await? - .unwrap(); + let parachain = + system(para_id, expected.binary(), Some(version), None, version, None, temp_dir.path()) + .await? + .unwrap(); assert_eq!(para_id, parachain.id); assert!(matches!(parachain.binary, Binary::Source { name, source, cache } if name == expected.binary() && source == Source::GitHub(ReleaseArchive { @@ -179,13 +213,48 @@ mod tests { tag: Some(version.to_string()), tag_format: Some("polkadot-{tag}".to_string()), archive: format!("{name}-{}.tar.gz", target()?), - contents: vec![expected.binary()], + contents: vec![(expected.binary(), None)], latest: parachain.binary.latest().map(|l| l.to_string()), }) && cache == temp_dir.path() )); Ok(()) } + #[tokio::test] + async fn system_with_chain_spec_generator_works() -> anyhow::Result<()> { + let expected = Parachain::System; + let runtime_version = "v1.2.7"; + let para_id = 1000; + + let temp_dir = tempdir()?; + let parachain = system( + para_id, + expected.binary(), + None, + Some(runtime_version), + "v.13.0", + Some("asset-hub-paseo-local"), + temp_dir.path(), + ) + .await? + .unwrap(); + assert_eq!(parachain.id, para_id); + assert_eq!(parachain.chain.unwrap(), "asset-hub-paseo-local"); + let chain_spec_generator = parachain.chain_spec_generator.unwrap(); + assert!(matches!(chain_spec_generator, Binary::Source { name, source, cache } + if name == "paseo-chain-spec-generator" && source == Source::GitHub(ReleaseArchive { + owner: "r0gue-io".to_string(), + repository: "paseo-runtimes".to_string(), + tag: Some(runtime_version.to_string()), + tag_format: None, + archive: format!("chain-spec-generator-{}.tar.gz", target()?), + contents: [("chain-spec-generator", Some("paseo-chain-spec-generator".to_string()))].to_vec(), + latest: chain_spec_generator.latest().map(|l| l.to_string()), + }) && cache == temp_dir.path() + )); + Ok(()) + } + #[tokio::test] async fn pop_works() -> anyhow::Result<()> { let version = "v1.0"; @@ -193,8 +262,9 @@ mod tests { let para_id = 2000; let temp_dir = tempdir()?; - let parachain = - from(para_id, expected.binary(), Some(version), temp_dir.path()).await?.unwrap(); + let parachain = from(para_id, expected.binary(), Some(version), None, temp_dir.path()) + .await? + .unwrap(); assert_eq!(para_id, parachain.id); assert!(matches!(parachain.binary, Binary::Source { name, source, cache } if name == expected.binary() && source == Source::GitHub(ReleaseArchive { @@ -203,7 +273,7 @@ mod tests { tag: Some(version.to_string()), tag_format: None, archive: format!("{name}-{}.tar.gz", target()?), - contents: vec![expected.binary()], + contents: vec![(expected.binary(), None)], latest: parachain.binary.latest().map(|l| l.to_string()), }) && cache == temp_dir.path() )); @@ -212,7 +282,7 @@ mod tests { #[tokio::test] async fn from_handles_unsupported_command() -> anyhow::Result<()> { - assert!(from(2000, "none", None, tempdir()?.path()).await?.is_none()); + assert!(from(2000, "none", None, None, &PathBuf::default()).await?.is_none()); Ok(()) } } diff --git a/crates/pop-parachains/src/up/relay.rs b/crates/pop-parachains/src/up/relay.rs index 9b303842..af0b53e9 100644 --- a/crates/pop-parachains/src/up/relay.rs +++ b/crates/pop-parachains/src/up/relay.rs @@ -1,7 +1,9 @@ // SPDX-License-Identifier: GPL-3.0 + use super::{ - sourcing, + chain_specs::chain_spec_generator, sourcing::{ + self, traits::{Source as _, *}, GitHub::ReleaseArchive, Source, @@ -42,7 +44,10 @@ impl TryInto for &RelayChain { tag, tag_format: self.tag_format().map(|t| t.into()), archive: format!("{}-{}.tar.gz", self.binary(), target()?), - contents: once(self.binary()).chain(self.workers()).collect(), + contents: once(self.binary()) + .chain(self.workers()) + .map(|n| (n, None)) + .collect(), latest, }) }, @@ -63,12 +68,16 @@ impl sourcing::traits::Source for RelayChain {} /// /// # Arguments /// * `version` - The version of the relay chain binary to be used. +/// * `runtime_version` - The version of the runtime to be used. +/// * `chain` - The chain specified. /// * `cache` - The cache to be used. pub(super) async fn default( version: Option<&str>, + runtime_version: Option<&str>, + chain: Option<&str>, cache: &Path, ) -> Result { - from(RelayChain::Polkadot.binary(), version, cache).await + from(RelayChain::Polkadot.binary(), version, runtime_version, chain, cache).await } /// Initialises the configuration required to launch the relay chain using the specified command. @@ -76,10 +85,14 @@ pub(super) async fn default( /// # Arguments /// * `command` - The command specified. /// * `version` - The version of the binary to be used. +/// * `runtime_version` - The version of the runtime to be used. +/// * `chain` - The chain specified. /// * `cache` - The cache to be used. pub(super) async fn from( command: &str, version: Option<&str>, + runtime_version: Option<&str>, + chain: Option<&str>, cache: &Path, ) -> Result { for relay in RelayChain::VARIANTS @@ -99,11 +112,16 @@ pub(super) async fn from( source: TryInto::try_into(&relay, tag, latest)?, cache: cache.to_path_buf(), }; - return Ok(super::RelayChain { binary, workers: relay.workers() }); + let chain = chain.unwrap_or_else(|| "rococo-local"); + return Ok(super::RelayChain { + binary, + workers: relay.workers(), + chain: chain.into(), + chain_spec_generator: chain_spec_generator(chain, runtime_version, cache).await?, + }); } - return Err(Error::UnsupportedCommand(format!( - "the relay chain command is unsupported: {command}", - ))); + + Err(Error::UnsupportedCommand(format!("the relay chain command is unsupported: {command}"))) } #[cfg(test)] @@ -116,7 +134,7 @@ mod tests { let expected = RelayChain::Polkadot; let version = "v1.12.0"; let temp_dir = tempdir()?; - let relay = default(Some(version), temp_dir.path()).await?; + let relay = default(Some(version), None, None, temp_dir.path()).await?; assert!(matches!(relay.binary, Binary::Source { name, source, cache } if name == expected.binary() && source == Source::GitHub(ReleaseArchive { owner: "r0gue-io".to_string(), @@ -124,7 +142,7 @@ mod tests { tag: Some(version.to_string()), tag_format: Some("polkadot-{tag}".to_string()), archive: format!("{name}-{}.tar.gz", target()?), - contents: vec!["polkadot", "polkadot-execute-worker", "polkadot-prepare-worker"], + contents: ["polkadot", "polkadot-execute-worker", "polkadot-prepare-worker"].map(|b| (b, None)).to_vec(), latest: relay.binary.latest().map(|l| l.to_string()), }) && cache == temp_dir.path() )); @@ -132,10 +150,32 @@ mod tests { Ok(()) } + #[tokio::test] + async fn default_with_chain_spec_generator_works() -> anyhow::Result<()> { + let runtime_version = "v1.2.7"; + let temp_dir = tempdir()?; + let relay = + default(None, Some(runtime_version), Some("paseo-local"), temp_dir.path()).await?; + assert_eq!(relay.chain, "paseo-local"); + let chain_spec_generator = relay.chain_spec_generator.unwrap(); + assert!(matches!(chain_spec_generator, Binary::Source { name, source, cache } + if name == "paseo-chain-spec-generator" && source == Source::GitHub(ReleaseArchive { + owner: "r0gue-io".to_string(), + repository: "paseo-runtimes".to_string(), + tag: Some(runtime_version.to_string()), + tag_format: None, + archive: format!("chain-spec-generator-{}.tar.gz", target()?), + contents: [("chain-spec-generator", Some("paseo-chain-spec-generator".to_string()))].to_vec(), + latest: chain_spec_generator.latest().map(|l| l.to_string()), + }) && cache == temp_dir.path() + )); + Ok(()) + } + #[tokio::test] async fn from_handles_unsupported_command() -> anyhow::Result<()> { assert!( - matches!(from("none", None, tempdir()?.path()).await, Err(Error::UnsupportedCommand(e)) + matches!(from("none", None, None, None, tempdir()?.path()).await, Err(Error::UnsupportedCommand(e)) if e == "the relay chain command is unsupported: none") ); Ok(()) @@ -146,7 +186,8 @@ mod tests { let expected = RelayChain::Polkadot; let version = "v1.12.0"; let temp_dir = tempdir()?; - let relay = from("./bin-v1.6.0/polkadot", Some(version), temp_dir.path()).await?; + let relay = + from("./bin-v1.6.0/polkadot", Some(version), None, None, temp_dir.path()).await?; assert!(matches!(relay.binary, Binary::Source { name, source, cache } if name == expected.binary() && source == Source::GitHub(ReleaseArchive { owner: "r0gue-io".to_string(), @@ -154,7 +195,7 @@ mod tests { tag: Some(version.to_string()), tag_format: Some("polkadot-{tag}".to_string()), archive: format!("{name}-{}.tar.gz", target()?), - contents: vec!["polkadot", "polkadot-execute-worker", "polkadot-prepare-worker"], + contents: ["polkadot", "polkadot-execute-worker", "polkadot-prepare-worker"].map(|b| (b, None)).to_vec(), latest: relay.binary.latest().map(|l| l.to_string()), }) && cache == temp_dir.path() )); diff --git a/crates/pop-parachains/src/up/sourcing.rs b/crates/pop-parachains/src/up/sourcing.rs index 5e0ef8e5..845f9806 100644 --- a/crates/pop-parachains/src/up/sourcing.rs +++ b/crates/pop-parachains/src/up/sourcing.rs @@ -1,4 +1,5 @@ // SPDX-License-Identifier: GPL-3.0 + use crate::{Error, Git, Status, APP_USER_AGENT}; use duct::cmd; use flate2::read::GzDecoder; @@ -115,7 +116,8 @@ pub(crate) enum GitHub { /// The name of the archive (asset) to download. archive: String, /// The archive contents required, including the binary name. - contents: Vec<&'static str>, + /// The second parameter can be used to specify another name for the binary once extracted. + contents: Vec<(&'static str, Option)>, /// If applicable, the latest release tag available. latest: Option, }, @@ -169,9 +171,15 @@ impl GitHub { }; let contents: Vec<_> = contents .iter() - .map(|name| match tag.as_ref() { - Some(tag) => (*name, cache.join(&format!("{name}-{tag}"))), - None => (*name, cache.join(&name)), + .map(|(name, target)| match tag.as_ref() { + Some(tag) => ( + *name, + cache.join(&format!( + "{}-{tag}", + target.as_ref().map_or(*name, |t| t.as_str()) + )), + ), + None => (*name, cache.join(target.as_ref().map_or(*name, |t| t.as_str()))), }) .collect(); from_archive(&url, &contents, status).await @@ -536,7 +544,7 @@ pub(super) mod tests { tag: Some(tag.to_string()), tag_format, archive, - contents: contents.to_vec(), + contents: contents.map(|n| (n, None)).to_vec(), latest: None, }) .source(temp_dir.path(), true, &Output, true) @@ -547,6 +555,35 @@ pub(super) mod tests { Ok(()) } + #[tokio::test] + async fn sourcing_from_github_release_archive_maps_contents() -> anyhow::Result<()> { + let owner = "r0gue-io".to_string(); + let repository = "polkadot".to_string(); + let tag = "v1.12.0"; + let tag_format = Some("polkadot-{tag}".to_string()); + let name = "polkadot".to_string(); + let archive = format!("{name}-{}.tar.gz", target()?); + let contents = ["polkadot", "polkadot-execute-worker", "polkadot-prepare-worker"]; + let temp_dir = tempdir()?; + let prefix = "test"; + + Source::GitHub(ReleaseArchive { + owner, + repository, + tag: Some(tag.to_string()), + tag_format, + archive, + contents: contents.map(|n| (n, Some(format!("{prefix}-{n}")))).to_vec(), + latest: None, + }) + .source(temp_dir.path(), true, &Output, true) + .await?; + for item in contents { + assert!(temp_dir.path().join(format!("{prefix}-{item}-{tag}")).exists()); + } + Ok(()) + } + #[tokio::test] async fn sourcing_from_latest_github_release_archive_works() -> anyhow::Result<()> { let owner = "r0gue-io".to_string(); @@ -563,7 +600,7 @@ pub(super) mod tests { tag: None, tag_format, archive, - contents: contents.to_vec(), + contents: contents.map(|n| (n, None)).to_vec(), latest: None, }) .source(temp_dir.path(), true, &Output, true) @@ -759,6 +796,7 @@ pub(crate) mod traits { use crate::Error; use strum::EnumProperty; + /// The source of a binary. pub(crate) trait Source: EnumProperty { /// The name of the binary. fn binary(&self) -> &'static str { diff --git a/crates/pop-parachains/tests/parachain.rs b/crates/pop-parachains/tests/parachain.rs index da7dd1d5..751912e1 100644 --- a/crates/pop-parachains/tests/parachain.rs +++ b/crates/pop-parachains/tests/parachain.rs @@ -1,20 +1,166 @@ // SPDX-License-Identifier: GPL-3.0 + use anyhow::Result; use pop_parachains::Zombienet; -const CONFIG_FILE_PATH: &str = "../../tests/networks/pop.toml"; -const TESTING_POLKADOT_VERSION: &str = "v1.12.0"; +const BINARY_VERSION: &str = "v1.13.0"; + +#[tokio::test] +async fn launch_kusama() -> Result<()> { + let temp_dir = tempfile::tempdir()?; + let cache = temp_dir.path().to_path_buf(); + + let mut zombienet = Zombienet::new( + &cache, + "../../tests/networks/kusama.toml", + Some(BINARY_VERSION), + Some("v1.2.7"), + None, + None, + None, + ) + .await?; + + for binary in zombienet.binaries().filter(|b| !b.exists()) { + binary.source(true, &(), true).await?; + } + + zombienet.spawn().await?; + Ok(()) +} + +#[tokio::test] +async fn launch_paseo() -> Result<()> { + let temp_dir = tempfile::tempdir()?; + let cache = temp_dir.path().to_path_buf(); + + let mut zombienet = Zombienet::new( + &cache, + "../../tests/networks/paseo.toml", + Some(BINARY_VERSION), + Some("v1.2.4"), + None, + None, + None, + ) + .await?; + + for binary in zombienet.binaries().filter(|b| !b.exists()) { + binary.source(true, &(), true).await?; + } + + zombienet.spawn().await?; + Ok(()) +} + +#[tokio::test] +async fn launch_polkadot() -> Result<()> { + let temp_dir = tempfile::tempdir()?; + let cache = temp_dir.path().to_path_buf(); + + let mut zombienet = Zombienet::new( + &cache, + "../../tests/networks/polkadot.toml", + Some(BINARY_VERSION), + Some("v1.2.7"), + None, + None, + None, + ) + .await?; + + for binary in zombienet.binaries().filter(|b| !b.exists()) { + binary.source(true, &(), true).await?; + } + + zombienet.spawn().await?; + Ok(()) +} + +#[tokio::test] +async fn launch_polkadot_and_system_parachain() -> Result<()> { + let temp_dir = tempfile::tempdir()?; + let cache = temp_dir.path().to_path_buf(); + + let mut zombienet = Zombienet::new( + &cache, + "../../tests/networks/polkadot+collectives.toml", + Some(BINARY_VERSION), + Some("v1.2.7"), + Some(BINARY_VERSION), + Some("v1.2.7"), + None, + ) + .await?; + + for binary in zombienet.binaries().filter(|b| !b.exists()) { + binary.source(true, &(), true).await?; + } + + zombienet.spawn().await?; + Ok(()) +} + +#[tokio::test] +async fn launch_rococo() -> Result<()> { + let temp_dir = tempfile::tempdir()?; + let cache = temp_dir.path().to_path_buf(); + + let mut zombienet = Zombienet::new( + &cache, + "../../tests/networks/rococo.toml", + Some(BINARY_VERSION), + None, + None, + None, + None, + ) + .await?; + + for binary in zombienet.binaries().filter(|b| !b.exists()) { + binary.source(true, &(), true).await?; + } + + zombienet.spawn().await?; + Ok(()) +} + +#[tokio::test] +async fn launch_rococo_and_system_parachain() -> Result<()> { + let temp_dir = tempfile::tempdir()?; + let cache = temp_dir.path().to_path_buf(); + + let mut zombienet = Zombienet::new( + &cache, + "../../tests/networks/rococo+coretime.toml", + Some(BINARY_VERSION), + None, + Some(BINARY_VERSION), + None, + None, + ) + .await?; + + for binary in zombienet.binaries().filter(|b| !b.exists()) { + binary.source(true, &(), true).await?; + } + + zombienet.spawn().await?; + Ok(()) +} #[tokio::test] -async fn test_spawn_polkadot_and_two_parachains() -> Result<()> { +async fn launch_rococo_and_two_parachains() -> Result<()> { let temp_dir = tempfile::tempdir()?; let cache = temp_dir.path().to_path_buf(); let mut zombienet = Zombienet::new( &cache, - CONFIG_FILE_PATH, - Some(&TESTING_POLKADOT_VERSION.to_string()), - Some(&TESTING_POLKADOT_VERSION.to_string()), + "../../tests/networks/pop.toml", + Some(BINARY_VERSION), + None, + Some(BINARY_VERSION), + None, Some(&vec!["https://github.com/r0gue-io/pop-node".to_string()]), ) .await?; diff --git a/tests/networks/kusama.toml b/tests/networks/kusama.toml new file mode 100644 index 00000000..48b6c2e2 --- /dev/null +++ b/tests/networks/kusama.toml @@ -0,0 +1,12 @@ +# pop up parachain -f ./tests/networks/kusama.toml + +[relaychain] +chain = "kusama-local" + +[[relaychain.nodes]] +name = "alice" +validator = true + +[[relaychain.nodes]] +name = "bob" +validator = true \ No newline at end of file diff --git a/tests/networks/paseo.toml b/tests/networks/paseo.toml new file mode 100644 index 00000000..4f680034 --- /dev/null +++ b/tests/networks/paseo.toml @@ -0,0 +1,12 @@ +# pop up parachain -f ./tests/networks/paseo.toml + +[relaychain] +chain = "paseo-local" + +[[relaychain.nodes]] +name = "alice" +validator = true + +[[relaychain.nodes]] +name = "bob" +validator = true \ No newline at end of file diff --git a/tests/networks/polkadot+collectives.toml b/tests/networks/polkadot+collectives.toml new file mode 100644 index 00000000..64d7d580 --- /dev/null +++ b/tests/networks/polkadot+collectives.toml @@ -0,0 +1,19 @@ +# pop up parachain -f ./tests/networks/polkadot.toml + +[relaychain] +chain = "polkadot-local" + +[[relaychain.nodes]] +name = "alice" +validator = true + +[[relaychain.nodes]] +name = "bob" +validator = true + +[[parachains]] +id = 1000 +chain = "collectives-polkadot-local" + +[[parachains.collators]] +name = "collectives-01" \ No newline at end of file diff --git a/tests/networks/polkadot.toml b/tests/networks/polkadot.toml new file mode 100644 index 00000000..4171ed65 --- /dev/null +++ b/tests/networks/polkadot.toml @@ -0,0 +1,12 @@ +# pop up parachain -f ./tests/networks/polkadot.toml + +[relaychain] +chain = "polkadot-local" + +[[relaychain.nodes]] +name = "alice" +validator = true + +[[relaychain.nodes]] +name = "bob" +validator = true \ No newline at end of file