diff --git a/crates/pop-cli/src/commands/up/parachain.rs b/crates/pop-cli/src/commands/up/parachain.rs index e68d20a1..510d638a 100644 --- a/crates/pop-cli/src/commands/up/parachain.rs +++ b/crates/pop-cli/src/commands/up/parachain.rs @@ -89,6 +89,12 @@ impl ZombienetCommand { if Self::source_binaries(&mut zombienet, &cache, self.verbose, self.skip_confirm).await? { return Ok(()); } + // For container chains, build the specs before spawn the network + // TODO: Is there a better way to identifiy it than to check if there is a hardcoded + // tanssi-node + if zombienet.binaries().any(|b| b.name() == "tanssi-node") { + zombienet.build_container_chain_specs("tanssi-node")?; + } // Finally spawn network and wait for signal to terminate let spinner = cliclack::spinner(); diff --git a/crates/pop-common/src/git.rs b/crates/pop-common/src/git.rs index f93fbc14..b6b582c6 100644 --- a/crates/pop-common/src/git.rs +++ b/crates/pop-common/src/git.rs @@ -67,29 +67,37 @@ impl Git { )?, }; - if let Some(tag_version) = tag_version { - let (object, reference) = repo.revparse_ext(&tag_version).expect("Object not found"); - repo.checkout_tree(&object, None).expect("Failed to checkout"); - match reference { - // gref is an actual reference like branches or tags - Some(gref) => repo.set_head(gref.name().unwrap()), - // this is a commit, not a reference - None => repo.set_head_detached(object.id()), - } - .expect("Failed to set HEAD"); - - let git_dir = repo.path(); - fs::remove_dir_all(&git_dir)?; - return Ok(Some(tag_version)); + match tag_version { + Some(tag) => Self::git_checkout(&repo, tag), + None => { + // Fetch latest release. + let release = Self::fetch_latest_tag(&repo); + if let Some(r) = release { + return Self::git_checkout(&repo, r); + } + // If there are no releases, use main. + let git_dir = repo.path(); + fs::remove_dir_all(&git_dir)?; + Ok(release) + }, } + } - // fetch tags from remote - let release = Self::fetch_latest_tag(&repo); + // Checkout to a specific tag or commit from a Git repository. + fn git_checkout(repo: &GitRepository, tag_version: String) -> Result> { + let (object, reference) = repo.revparse_ext(&tag_version).expect("Object not found"); + repo.checkout_tree(&object, None).expect("Failed to checkout"); + match reference { + // gref is an actual reference like branches or tags + Some(gref) => repo.set_head(gref.name().unwrap()), + // this is a commit, not a reference + None => repo.set_head_detached(object.id()), + } + .expect("Failed to set HEAD"); let git_dir = repo.path(); fs::remove_dir_all(&git_dir)?; - // Or by default the last one - Ok(release) + Ok(Some(tag_version)) } /// For users that have ssh configuration for cloning repositories. diff --git a/crates/pop-common/src/templates/extractor.rs b/crates/pop-common/src/templates/extractor.rs index 5f801999..95ee2c86 100644 --- a/crates/pop-common/src/templates/extractor.rs +++ b/crates/pop-common/src/templates/extractor.rs @@ -17,14 +17,24 @@ pub fn extract_template_files( target_directory: &Path, ignore_directories: Option>, ) -> Result<()> { - let template_directory = repo_directory.join(template_name); - // Recursively copy all directories and files within. Ignores the specified ones. - copy_dir_all( - &template_directory, - target_directory, - &ignore_directories.unwrap_or_else(|| vec![]), - )?; - Ok(()) + let template_directory = repo_directory.join(&template_name); + if template_directory.is_dir() { + // Recursively copy all directories and files within. Ignores the specified ones. + copy_dir_all( + &template_directory, + target_directory, + &ignore_directories.unwrap_or_else(|| vec![]), + )?; + return Ok(()); + } else { + // If not a dir, just copy the file. + let dst = target_directory.join(&template_name); + // In case the first file being pulled is not a directory, + // Make sure the target directory exists. + fs::create_dir_all(&target_directory)?; + fs::copy(template_directory, &dst)?; + Ok(()) + } } /// Recursively copy a directory and its files. @@ -42,8 +52,8 @@ fn copy_dir_all( for entry in fs::read_dir(src)? { let entry = entry?; let ty = entry.file_type()?; - if ty.is_dir() && - ignore_directories.contains(&entry.file_name().to_string_lossy().to_string()) + if ty.is_dir() + && ignore_directories.contains(&entry.file_name().to_string_lossy().to_string()) { continue; } else if ty.is_dir() { diff --git a/crates/pop-parachains/src/build.rs b/crates/pop-parachains/src/build.rs index 38d760c5..2b18fd33 100644 --- a/crates/pop-parachains/src/build.rs +++ b/crates/pop-parachains/src/build.rs @@ -121,6 +121,49 @@ pub fn generate_raw_chain_spec( Ok(raw_chain_spec) } +/// Generates a raw chain specification file for container chain. +/// +/// # Arguments +/// * `binary_path` - The path to the node binary executable that contains the `build-spec` command. +/// * `chain` - An optional chain to be used. This should be provided if generating a spec for a +/// parachain. +/// * `container_chains` - An optional vector of container chain specification files. These should +/// be provided if generating a spec for a parachain. +/// * `id` - The parachain id for which the chain specification is being generated. +/// * `invulnerables` - An optional vector of invulnerable nodes. These should be provided if +/// generating a spec for a parachain. +/// * `raw_chain_spec` - The path where the generated raw chain specification file will be +/// generated. +pub fn generate_raw_chain_spec_container_chain( + binary_path: &Path, + chain: Option<&str>, + container_chains: Option>, + id: &str, + invulnerables: Option>, + raw_chain_spec: &Path, +) -> Result { + check_command_exists(&binary_path, "build-spec")?; + let mut args = vec!["build-spec", "--parachain-id", id]; + // Parachain + if let (Some(chain), Some(invulnerables), Some(container_chains)) = + (chain, invulnerables, container_chains) + { + args.extend(&["--chain", chain]); + for container_chain in container_chains { + args.extend(&["--add-container-chain", container_chain]); + } + for invulnerable in invulnerables { + args.extend(&["--invulnerable", invulnerable]); + } + } + // Container Chain + else { + args.extend(["--disable-default-bootnode", "--raw"]); + } + cmd(binary_path, args).stdout_path(raw_chain_spec).stderr_null().run()?; + Ok(raw_chain_spec.to_path_buf()) +} + /// Export the WebAssembly runtime for the parachain. /// /// # Arguments @@ -317,8 +360,9 @@ impl ChainSpec { mod tests { use super::*; use crate::{ - new_parachain::instantiate_standard_template, templates::Parachain, Config, Error, - Zombienet, + new_parachain::{instantiate_standard_template, instantiate_tanssi_template}, + templates::Parachain, + Config, Error, Zombienet, }; use anyhow::Result; use pop_common::manifest::Dependency; @@ -492,6 +536,89 @@ default_command = "pop-node" Ok(()) } + #[tokio::test] + async fn generate_raw_chain_spec_container_chain_works() -> Result<()> { + let temp_dir = tempdir().expect("Failed to create temp dir"); + instantiate_tanssi_template(&Parachain::TanssiSimple, temp_dir.path(), None)?; + let config = Builder::new().suffix(".toml").tempfile()?; + // Get Zombienet config and fet binary for generate specs + writeln!( + config.as_file(), + "{}", + format!( + r#" +[relaychain] +chain = "paseo-local" + +[[parachains]] +id = 1000 +chain_spec_path = "{}" +chain = "dancebox-local" +default_command = "tanssi-node" + +[[parachains.collators]] +name = "FullNode-1000" + +[[parachains.collators]] +name = "Collator1000-01" + +[[parachains.collators]] +name = "Collator1000-02" + +[[parachains.collators]] +name = "Collator2000-01" + +[[parachains.collators]] +name = "Collator2000-02" +"#, + &temp_dir.path().join("tanssi-1000.json").display().to_string() + ) + )?; + let mut zombienet = Zombienet::new( + &temp_dir.path(), + config.path().to_str().unwrap(), + None, + None, + None, + None, + None, + ) + .await?; + // Fetch the tanssi-node binary + let mut binary_name: String = "".to_string(); + for binary in zombienet.binaries().filter(|b| !b.exists() && b.name() == "tanssi-node") { + binary_name = format!("{}-{}", binary.name(), binary.latest().unwrap()); + binary.source(true, &(), true).await?; + } + + let raw_chain_spec_container_chain = generate_raw_chain_spec_container_chain( + &temp_dir.path().join(binary_name.clone()), + None, + None, + &"2000", + None, + &temp_dir.path().join("raw-container-chain-chainspec.json"), + )?; + assert!(raw_chain_spec_container_chain.exists()); + + let raw_chain_spec = generate_raw_chain_spec_container_chain( + &temp_dir.path().join(binary_name), + Some("dancebox-local"), + Some(vec![&temp_dir + .path() + .join("raw-container-chain-chainspec.json") + .display() + .to_string()]), + &"1000", + Some(vec!["Collator1000-01", "Collator1000-02", "Collator2000-01", "Collator2000-02"]), + &temp_dir.path().join("raw-parachain-chainspec.json"), + )?; + assert!(raw_chain_spec.exists()); + let content = fs::read_to_string(raw_chain_spec.clone()).expect("Could not read file"); + assert!(content.contains("\"para_id\": 2000")); + Ok(()) + } + #[test] fn raw_chain_spec_fails_wrong_chain_spec() -> Result<()> { assert!(matches!( diff --git a/crates/pop-parachains/src/errors.rs b/crates/pop-parachains/src/errors.rs index 3eff7bbf..85e0b15c 100644 --- a/crates/pop-parachains/src/errors.rs +++ b/crates/pop-parachains/src/errors.rs @@ -9,6 +9,8 @@ pub enum Error { Aborted, #[error("Anyhow error: {0}")] AnyhowError(#[from] anyhow::Error), + #[error("Missing chain {0}")] + ChainNotFound(String), #[error("{0}")] CommonError(#[from] pop_common::Error), #[error("Configuration error: {0}")] diff --git a/crates/pop-parachains/src/new_parachain.rs b/crates/pop-parachains/src/new_parachain.rs index 33a9c305..4c4bdf92 100644 --- a/crates/pop-parachains/src/new_parachain.rs +++ b/crates/pop-parachains/src/new_parachain.rs @@ -94,7 +94,7 @@ pub fn instantiate_standard_template( /// * `template` - template to generate the container-chain from. /// * `target` - location where the parachain will be created. /// * `tag_version` - version to use (`None` to use latest). -fn instantiate_tanssi_template( +pub fn instantiate_tanssi_template( template: &ContainerChain, target: &Path, tag_version: Option, diff --git a/crates/pop-parachains/src/templates.rs b/crates/pop-parachains/src/templates.rs index a59d0082..484ba63d 100644 --- a/crates/pop-parachains/src/templates.rs +++ b/crates/pop-parachains/src/templates.rs @@ -188,23 +188,6 @@ pub enum Parachain { ) )] TanssiSimple, - /// Tanssi EVM enabled container chain template. - #[strum( - serialize = "frontier", - message = "EVM", - detailed_message = "EVM enabled container chain template.", - props( - Provider = "Tanssi", - Repository = "https://github.com/moondance-labs/tanssi", - Network = "./network.toml", - NodeDirectory = "container-chains/nodes", - RuntimeDirectory = "container-chains/runtime-templates", - SupportedVersions = "master", - ExtractableFiles = ".rustfmt.toml,Cargo.toml,Cargo.lock,LICENSE,README.md,rust-toolchain", - ) - )] - TanssiFrontier, - // Templates for unit tests below. #[cfg(test)] #[strum( @@ -303,7 +286,6 @@ mod tests { ("fpt".to_string(), ParityFPT), // Tanssi. ("simple".to_string(), TanssiSimple), - ("frontier".to_string(), TanssiFrontier), // Test. ("test_01".to_string(), TestTemplate01), ("test_02".to_string(), TestTemplate02), @@ -327,7 +309,6 @@ mod tests { ("fpt".to_string(), "https://github.com/paritytech/frontier-parachain-template"), // Tanssi. ("simple".to_string(), "https://github.com/moondance-labs/tanssi"), - ("frontier".to_string(), "https://github.com/moondance-labs/tanssi"), // Test. ("test_01".to_string(), ""), ("test_02".to_string(), ""), @@ -348,7 +329,6 @@ mod tests { (ParityFPT, Some("./zombienet-config.toml")), // Tanssi. (TanssiSimple, Some("./network.toml")), - (TanssiFrontier, Some("./network.toml")), // Test. (TestTemplate01, Some("")), (TestTemplate02, Some("")), @@ -392,7 +372,7 @@ mod tests { assert_eq!(Provider::OpenZeppelin.provides(&template), true); assert_eq!(Provider::Tanssi.provides(&template), false); } - if matches!(template, TanssiSimple | TanssiFrontier) { + if matches!(template, TanssiSimple) { assert_eq!(Provider::Pop.provides(&template), false); assert_eq!(Provider::Parity.provides(&template), false); assert_eq!(Provider::OpenZeppelin.provides(&template), false); diff --git a/crates/pop-parachains/src/up/mod.rs b/crates/pop-parachains/src/up/mod.rs index 192da7ff..71738c05 100644 --- a/crates/pop-parachains/src/up/mod.rs +++ b/crates/pop-parachains/src/up/mod.rs @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-3.0 -use crate::errors::Error; +use crate::{build::generate_raw_chain_spec_container_chain, errors::Error}; use glob::glob; use indexmap::IndexMap; pub use pop_common::{ @@ -100,6 +100,59 @@ impl Zombienet { .filter_map(|b| b) } + /// Build the needed specs before spawn the network containing the container chains. + pub fn build_container_chain_specs(&mut self, parachain_name: &str) -> Result { + let parachain_id = self + .parachains + .iter() + .find(|(_, parachain)| parachain.binary.name() == parachain_name) + .ok_or(Error::ChainNotFound(format!("with name: {parachain_name}")))? + .0; + // This order is important, first container chains that can be more than one, then the + // parachain + let mut container_chains: Vec = Vec::new(); + self.parachains.iter().filter(|(&id, _)| &id > parachain_id).try_for_each( + |(id, parachain)| -> Result<(), Error> { + container_chains.push( + generate_raw_chain_spec_container_chain( + parachain.binary.path().as_path(), + None, + None, + &id.to_string(), + None, + parachain + .chain_spec_path + .as_deref() + .unwrap_or(Path::new("./chain-spec.json")), + )? + .display() + .to_string(), + ); + Ok(()) + }, + )?; + let tanssi_node = self + .parachains + .get(parachain_id) + .ok_or(Error::ChainNotFound(format!("with id: {parachain_id}")))?; + let raw_spec_path = generate_raw_chain_spec_container_chain( + tanssi_node.binary.path().as_path(), + tanssi_node.chain.as_deref(), + Some(container_chains.iter().map(|s| s.as_str()).collect()), + ¶chain_id.to_string(), + Some( + tanssi_node + .collators_names + .iter() + .filter(|s| s.starts_with("Collator")) + .map(|s| s.as_str()) + .collect(), + ), + tanssi_node.chain_spec_path.as_deref().unwrap_or(Path::new("./chain-spec.json")), + )?; + Ok(raw_spec_path) + } + /// Determine parachain configuration based on specified version and network configuration. /// /// # Arguments @@ -132,7 +185,7 @@ impl Zombienet { as u32; let chain = table.get("chain").and_then(|i| i.as_str()); - + let chain_spec_path = table.get("chain_spec_path").and_then(|i| i.as_str()); let command = NetworkConfiguration::default_command(table) .cloned() .or_else(|| { @@ -175,6 +228,18 @@ impl Zombienet { continue; } + let mut collators_names: Vec<&str> = Vec::new(); + if let Some(collators) = table.get("collators").and_then(|p| p.as_array_of_tables()) { + for collator in collators.iter() { + if let Some(name) = + NetworkConfiguration::name(collator).and_then(|i| i.as_str()) + { + //let n = Some(Item::Value(Value::String(Formatted::new(name.into())))); + collators_names.push(name); + } + } + } + // Check if known parachain let version = parachains.as_ref().and_then(|r| { r.iter() @@ -182,7 +247,17 @@ impl Zombienet { .nth(0) .map(|v| v.as_str()) }); - if let Some(parachain) = parachains::from(id, &command, version, chain, cache).await? { + if let Some(parachain) = parachains::from( + id, + &command, + version, + chain, + cache, + chain_spec_path, + collators_names.clone(), + ) + .await? + { paras.insert(id, parachain); continue; } @@ -190,14 +265,33 @@ 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, chain, cache)?); + paras.insert( + id, + Parachain::from_repository( + id, + repo, + chain, + cache, + chain_spec_path, + collators_names, + )?, + ); 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(), chain)?); + paras.insert( + id, + Parachain::from_local( + id, + command.into(), + chain, + chain_spec_path, + collators_names, + )?, + ); continue; } @@ -368,6 +462,11 @@ impl NetworkConfiguration { config.get("default_command") } + /// Returns the `name` configuration. + fn name(config: &Table) -> Option<&Item> { + config.get("name") + } + /// Returns the `nodes` configuration. fn nodes(relay_chain: &Table) -> Option<&ArrayOfTables> { relay_chain.get("nodes").and_then(|i| i.as_array_of_tables()) @@ -506,6 +605,10 @@ struct Parachain { chain: Option, /// If applicable, the binary used to generate a chain specification. chain_spec_generator: Option, + /// If applicable, the path to the chain specification. + chain_spec_path: Option, + /// List of collators names. + collators_names: Vec, } impl Parachain { @@ -515,7 +618,13 @@ impl Parachain { /// * `id` - The parachain identifier on the local network. /// * `path` - The path to the local binary. /// * `chain` - The chain specified. - fn from_local(id: u32, path: PathBuf, chain: Option<&str>) -> Result { + fn from_local( + id: u32, + path: PathBuf, + chain: Option<&str>, + chain_spec_path: Option<&str>, + collators_names: Vec<&str>, + ) -> Result { let name = path .file_name() .and_then(|f| f.to_str()) @@ -528,6 +637,8 @@ impl Parachain { binary: Binary::Local { name, path, manifest }, chain: chain.map(|c| c.to_string()), chain_spec_generator: None, + chain_spec_path: chain_spec_path.map(PathBuf::from), + collators_names: collators_names.iter().map(|&s| s.to_string()).collect(), }) } @@ -544,6 +655,8 @@ impl Parachain { repo: &Repository, chain: Option<&str>, cache: &Path, + chain_spec_path: Option<&str>, + collators_names: Vec<&str>, ) -> 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") { @@ -565,6 +678,8 @@ impl Parachain { }, chain: chain.map(|c| c.to_string()), chain_spec_generator: None, + chain_spec_path: chain_spec_path.map(PathBuf::from), + collators_names: collators_names.iter().map(|&s| s.to_string()).collect(), }) } else { Ok(Parachain { @@ -582,6 +697,8 @@ impl Parachain { }, chain: chain.map(|c| c.to_string()), chain_spec_generator: None, + chain_spec_path: chain_spec_path.map(PathBuf::from), + collators_names: collators_names.iter().map(|&s| s.to_string()).collect(), }) } } @@ -651,11 +768,16 @@ fn resolve_manifest(package: &str, path: &Path) -> Result, Error #[cfg(test)] mod tests { use super::*; + use crate::{ + new_parachain::instantiate_tanssi_template, templates::Parachain as Template, Error, + }; use anyhow::Result; use std::{env::current_dir, fs::File, io::Write}; use tempfile::tempdir; mod zombienet { + use std::fs; + use super::*; use pop_common::Status; @@ -1299,6 +1421,89 @@ chain = "asset-hub-paseo-local" Ok(()) } + #[tokio::test] + async fn build_container_chain_specs_works() -> Result<()> { + let temp_dir = tempdir().expect("Failed to create temp dir"); + instantiate_tanssi_template(&Template::TanssiSimple, temp_dir.path(), None)?; + let config = Builder::new().suffix(".toml").tempfile()?; + // Get Zombienet config and fet binary for generate specs + writeln!( + config.as_file(), + "{}", + format!( + r#" +[relaychain] +chain = "paseo-local" + +[[parachains]] +id = 1000 +chain_spec_path = "{}" +chain = "dancebox-local" +default_command = "tanssi-node" + +[[parachains.collators]] +name = "FullNode-1000" + +[[parachains.collators]] +name = "Collator1000-01" + +[[parachains.collators]] +name = "Collator1000-02" + +[[parachains.collators]] +name = "Collator2000-01" + +[[parachains.collators]] +name = "Collator2000-02" +"#, + &temp_dir.path().join("tanssi-1000.json").display().to_string() + ) + )?; + let mut zombienet = Zombienet::new( + &temp_dir.path(), + config.path().to_str().unwrap(), + None, + None, + None, + None, + None, + ) + .await?; + // Fetch the tanssi-node binary + for binary in zombienet.binaries().filter(|b| !b.exists() && b.name() == "tanssi-node") + { + binary.source(true, &(), true).await?; + } + let raw_chain_spec = zombienet.build_container_chain_specs("tanssi-node")?; + assert!(raw_chain_spec.exists()); + let content = fs::read_to_string(raw_chain_spec.clone()).expect("Could not read file"); + assert!(content.contains("\"para_id\": 1000")); + assert!(content.contains("\"id\": \"dancebox_local\"")); + Ok(()) + } + + #[tokio::test] + async fn build_container_chain_specs_fails_chain_not_found() -> Result<()> { + let temp_dir = tempdir().expect("Failed to create temp dir"); + instantiate_tanssi_template(&Template::TanssiSimple, temp_dir.path(), None)?; + let mut zombienet = Zombienet::new( + &temp_dir.path(), + &temp_dir.path().join("network.toml").display().to_string(), + None, + None, + None, + None, + None, + ) + .await?; + assert!(matches!( + zombienet.build_container_chain_specs("bad-name"), + Err(Error::ChainNotFound(error)) + if error == "with name: bad-name" + )); + Ok(()) + } + #[tokio::test] async fn spawn_ensures_relay_chain_binary_exists() -> Result<()> { let temp_dir = tempdir()?; @@ -1445,6 +1650,7 @@ validator = true fs::{create_dir_all, File}, io::{Read, Write}, path::PathBuf, + vec, }; use tempfile::{tempdir, Builder}; @@ -1595,6 +1801,8 @@ command = "./target/release/parachain-template-node" }, chain: None, chain_spec_generator: None, + chain_spec_path: None, + collators_names: vec!["asset-hub".to_string()], }, ), ( @@ -1608,6 +1816,8 @@ command = "./target/release/parachain-template-node" }, chain: None, chain_spec_generator: None, + chain_spec_path: None, + collators_names: vec!["pop".to_string()], }, ), ( @@ -1621,6 +1831,8 @@ command = "./target/release/parachain-template-node" }, chain: None, chain_spec_generator: None, + chain_spec_path: None, + collators_names: vec!["collator".to_string()], }, ), ] @@ -1750,6 +1962,8 @@ command = "polkadot-parachain" path: system_chain_spec_generator.to_path_buf(), manifest: None, }), + chain_spec_path: None, + collators_names: vec!["asset-hub".to_string()], }, )] .into(), @@ -1828,12 +2042,20 @@ 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(), Some("dev"))?, + Parachain::from_local( + 2000, + command.clone(), + Some("dev"), + Some("/path"), + vec![name] + )?, Parachain { id: 2000, binary: Binary::Local { name: name.to_string(), path: command, manifest: None }, chain: Some("dev".to_string()), chain_spec_generator: None, + chain_spec_path: Some(PathBuf::from("/path")), + collators_names: vec![name.to_string()], } ); Ok(()) @@ -1844,7 +2066,13 @@ node_spawn_timeout = 300 let name = "pop-parachains"; let command = PathBuf::from("./target/release").join(&name); assert_eq!( - Parachain::from_local(2000, command.clone(), Some("dev"))?, + Parachain::from_local( + 2000, + command.clone(), + Some("dev"), + Some("/path"), + vec![name] + )?, Parachain { id: 2000, binary: Binary::Local { @@ -1854,6 +2082,8 @@ node_spawn_timeout = 300 }, chain: Some("dev".to_string()), chain_spec_generator: None, + chain_spec_path: Some(PathBuf::from("/path")), + collators_names: vec![name.to_string()], } ); Ok(()) @@ -1864,7 +2094,14 @@ 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, Some("dev"), cache.path())?, + Parachain::from_repository( + 2000, + &repo, + Some("dev"), + cache.path(), + Some("/path"), + vec!["pop-node"] + )?, Parachain { id: 2000, binary: Binary::Source { @@ -1880,6 +2117,8 @@ node_spawn_timeout = 300 }, chain: Some("dev".to_string()), chain_spec_generator: None, + chain_spec_path: Some(PathBuf::from("/path")), + collators_names: vec!["pop-node".to_string()], } ); Ok(()) @@ -1890,7 +2129,14 @@ 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, Some("dev"), cache.path())?, + Parachain::from_repository( + 2000, + &repo, + Some("dev"), + cache.path(), + Some("/path"), + vec!["pop-node"] + )?, Parachain { id: 2000, binary: Binary::Source { @@ -1907,6 +2153,8 @@ node_spawn_timeout = 300 }, chain: Some("dev".to_string()), chain_spec_generator: None, + chain_spec_path: Some(PathBuf::from("/path")), + collators_names: vec!["pop-node".to_string()], }, ); Ok(()) diff --git a/crates/pop-parachains/src/up/parachains.rs b/crates/pop-parachains/src/up/parachains.rs index 3bea2cba..5cbc8230 100644 --- a/crates/pop-parachains/src/up/parachains.rs +++ b/crates/pop-parachains/src/up/parachains.rs @@ -9,7 +9,7 @@ use pop_common::{ }, target, Error, GitHub, }; -use std::path::Path; +use std::path::{Path, PathBuf}; use strum::VariantArray as _; use strum_macros::{EnumProperty, VariantArray}; @@ -32,6 +32,13 @@ pub(super) enum Parachain { Fallback = "v0.1.0-alpha2" ))] Pop, + /// Swift and effortless deployment of application-specific blockchains. + #[strum(props( + Repository = "https://github.com/r0gue-io/tanssi", + Binary = "tanssi-node", + Fallback = "v0.8.1" + ))] + Tanssi, } impl TryInto for Parachain { @@ -42,7 +49,7 @@ impl TryInto for Parachain { /// * `latest` - If applicable, some specifier used to determine the latest source. fn try_into(&self, tag: Option, latest: Option) -> Result { Ok(match self { - Parachain::System | Parachain::Pop => { + Parachain::System | Parachain::Pop | Parachain::Tanssi => { // Source from GitHub release asset let repo = GitHub::parse(self.repository())?; Source::GitHub(ReleaseArchive { @@ -104,6 +111,8 @@ pub(super) async fn system( binary, chain: chain.map(|c| c.to_string()), chain_spec_generator, + chain_spec_path: None, + collators_names: Vec::new(), })); } @@ -121,6 +130,8 @@ pub(super) async fn from( version: Option<&str>, chain: Option<&str>, cache: &Path, + chain_spec_path: Option<&str>, + collators_names: Vec<&str>, ) -> Result, Error> { for para in Parachain::VARIANTS.iter().filter(|p| p.binary() == command) { let releases = para.releases().await?; @@ -140,6 +151,8 @@ pub(super) async fn from( binary, chain: chain.map(|c| c.to_string()), chain_spec_generator: None, + chain_spec_path: chain_spec_path.map(PathBuf::from), + collators_names: collators_names.iter().map(|&s| s.to_string()).collect(), })); } Ok(None) @@ -261,9 +274,17 @@ mod tests { let para_id = 2000; let temp_dir = tempdir()?; - let parachain = from(para_id, expected.binary(), Some(version), None, temp_dir.path()) - .await? - .unwrap(); + let parachain = from( + para_id, + expected.binary(), + Some(version), + None, + temp_dir.path(), + Some("/path"), + vec!["pop-node"], + ) + .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 { @@ -281,7 +302,9 @@ mod tests { #[tokio::test] async fn from_handles_unsupported_command() -> anyhow::Result<()> { - assert!(from(2000, "none", None, None, &PathBuf::default()).await?.is_none()); + assert!(from(2000, "none", None, None, &PathBuf::default(), None, vec!["pop-node"]) + .await? + .is_none()); Ok(()) } } diff --git a/crates/pop-parachains/templates/container/network.templ b/crates/pop-parachains/templates/container/network.templ index e495c281..fc69b942 100644 --- a/crates/pop-parachains/templates/container/network.templ +++ b/crates/pop-parachains/templates/container/network.templ @@ -20,26 +20,31 @@ validator = true [[parachains]] id = 1000 chain = "dancebox-local" +chain_spec_path = "tanssi-1000.json" default_command = "tanssi-node" [[parachains.collators]] -name = "collator-01" +name = "FullNode-1000" [[parachains.collators]] -name = "collator-02" +name = "Collator1000-01" [[parachains.collators]] -name = "collator-03" +name = "Collator1000-02" [[parachains.collators]] -name = "collator-04" +name = "Collator2000-01" + +[[parachains.collators]] +name = "Collator2000-02" [[parachains]] id = 2000 +chain_spec_path = "template-container-2000.json" default_command = "./target/release/^^node^^" [[parachains.collators]] -name = "full-node-01" +name = "FullNode-2000" [[parachains.collators]] -name = "full-node-02" +name = "FullNode-2000-2"