1use crate::{
4 errors::Error, omni_node::PolkadotOmniNodeCli::PolkadotOmniNode,
5 registry::traits::Chain as ChainT, up::chain_specs::Runtime,
6};
7pub use chain_specs::Runtime as Relay;
8use glob::glob;
9use indexmap::IndexMap;
10use pop_common::sourcing::traits::{Source as _, enums::Source as _};
11pub use pop_common::{
12 Profile,
13 git::{GitHub, Repository},
14 sourcing::{Binary, GitHub::*, Source, Source::*},
15};
16use std::{
17 collections::BTreeSet,
18 fmt::Debug,
19 iter::once,
20 path::{Path, PathBuf},
21};
22use strum::VariantArray;
23use symlink::{remove_symlink_file, symlink_file};
24use toml_edit::DocumentMut;
25use zombienet_configuration::{
26 NodeConfig,
27 shared::node::{Buildable, Initial, NodeConfigBuilder},
28};
29pub use zombienet_sdk::NetworkConfigBuilder;
30use zombienet_sdk::{
31 AttachToLive, AttachToLiveNetwork, LocalFileSystem, Network, NetworkConfig, NetworkConfigExt,
32};
33
34mod chain_specs;
35pub mod chains;
37mod relay;
38
39const VALIDATORS: [&str; 6] = ["alice", "bob", "charlie", "dave", "eve", "ferdie"];
40
41pub struct Zombienet {
43 network_config: NetworkConfiguration,
45 relay_chain: RelayChain,
47 parachains: IndexMap<u32, Chain>,
49 hrmp_channels: bool,
51}
52
53pub async fn destroy_network(zombie_json: &Path) -> Result<(), Error> {
55 let network = AttachToLiveNetwork::attach_native(zombie_json.to_path_buf()).await?;
56 network.destroy().await.map_err(anyhow::Error::from)?;
57 Ok(())
58}
59
60impl Zombienet {
61 pub async fn new(
76 cache: &Path,
77 network_config: NetworkConfiguration,
78 relay_chain_version: Option<&str>,
79 relay_chain_runtime_version: Option<&str>,
80 system_parachain_version: Option<&str>,
81 system_parachain_runtime_version: Option<&str>,
82 parachains: Option<&Vec<String>>,
83 ) -> Result<Self, Error> {
84 let relay_chain = Self::init_relay_chain(
86 relay_chain_version,
87 relay_chain_runtime_version,
88 &network_config,
89 cache,
90 )
91 .await?;
92 let parachains = match parachains {
93 Some(parachains) => {
94 let mut repos = Vec::new();
95 for parachain in parachains {
96 if parachain.contains("://") {
97 repos.push(Repository::parse(parachain)?);
98 }
99 }
100 (!repos.is_empty()).then_some(repos)
101 },
102 None => None,
103 };
104 let parachains = Self::parachains(
105 &relay_chain,
106 system_parachain_version,
107 system_parachain_runtime_version.or(relay_chain_runtime_version),
108 parachains,
109 &network_config,
110 cache,
111 )
112 .await?;
113 let hrmp_channels = !network_config.0.hrmp_channels().is_empty();
114 Ok(Self { network_config, relay_chain, parachains, hrmp_channels })
115 }
116
117 pub fn binaries(&mut self) -> impl Iterator<Item = &mut Binary> {
119 once([Some(&mut self.relay_chain.binary), self.relay_chain.chain_spec_generator.as_mut()])
120 .chain(
121 self.parachains
122 .values_mut()
123 .map(|p| [Some(&mut p.binary), p.chain_spec_generator.as_mut()]),
124 )
125 .flatten()
126 .flatten()
127 }
128
129 async fn parachains(
141 relay_chain: &RelayChain,
142 system_parachain_version: Option<&str>,
143 system_parachain_runtime_version: Option<&str>,
144 parachains: Option<Vec<Repository>>,
145 network_config: &NetworkConfiguration,
146 cache: &Path,
147 ) -> Result<IndexMap<u32, Chain>, Error> {
148 let mut paras: IndexMap<u32, Chain> = IndexMap::new();
149 'outer: for parachain in network_config.0.parachains() {
150 let id = parachain.id();
151 let chain = parachain.chain().map(|c| c.as_str());
152
153 let command = parachain
154 .default_command()
155 .map(|c| c.as_str())
156 .or_else(|| {
157 for collator in parachain.collators() {
159 if let Some(command) = collator.command().map(|i| i.as_str()) {
160 return Some(command);
161 }
162 }
163
164 Some("polkadot-parachain")
166 })
167 .expect("missing default_command set above")
168 .to_lowercase();
169
170 if let Some(parachain) = chains::system(
172 id,
173 &command,
174 system_parachain_version,
175 system_parachain_runtime_version,
176 relay_chain.binary.version().expect("expected relay chain to have version"),
177 chain,
178 cache,
179 )
180 .await?
181 {
182 paras.insert(id, parachain);
183 continue;
184 }
185
186 let version = parachains.as_ref().and_then(|r| {
188 r.iter()
189 .filter_map(|r| {
190 (r.package == command).then_some(r.reference.as_ref()).flatten()
191 })
192 .nth(0)
193 .map(|v| v.as_str())
194 });
195 if let Some(parachain) =
196 chains::from(&relay_chain.runtime, id, &command, version, chain, cache).await?
197 {
198 paras.insert(id, parachain);
199 continue;
200 }
201
202 if let Some(parachains) = parachains.as_ref() &&
204 let Some(repo) = parachains.iter().find(|r| command == r.package)
205 {
206 paras.insert(id, Chain::from_repository(id, repo, chain, cache)?);
207 continue 'outer;
208 }
209
210 if ["./", "../", "/"].iter().any(|p| command.starts_with(p)) {
212 paras.insert(id, Chain::from_local(id, command.into(), chain)?);
213 continue;
214 }
215
216 if ["parachain-template-node", "substrate-contracts-node"].contains(&command.as_str()) {
219 for profile in Profile::VARIANTS {
220 let binary_path = profile.target_directory(Path::new("./")).join(&command);
221 if binary_path.exists() {
222 paras.insert(id, Chain::from_local(id, binary_path, chain)?);
223 continue 'outer;
224 }
225 }
226 return Err(Error::MissingBinary(command));
227 }
228
229 if command.starts_with(PolkadotOmniNode.binary()) {
230 paras.insert(id, Chain::from_omni_node(id, cache)?);
231 continue 'outer;
232 }
233
234 return Err(Error::MissingBinary(command));
235 }
236 Ok(paras)
237 }
238
239 async fn init_relay_chain(
249 version: Option<&str>,
250 runtime_version: Option<&str>,
251 network_config: &NetworkConfiguration,
252 cache: &Path,
253 ) -> Result<RelayChain, Error> {
254 let relay_chain = network_config.0.relaychain();
256 let chain = relay_chain.chain().as_str();
257 if let Some(default_command) = relay_chain.default_command().map(|c| c.as_str()) {
258 let relay =
259 relay::from(default_command, version, runtime_version, chain, cache).await?;
260 for node in relay_chain.nodes() {
262 if let Some(command) = node.command().map(|c| c.as_str()) &&
263 command.to_lowercase() != relay.binary.name()
264 {
265 return Err(Error::UnsupportedCommand(format!(
266 "the relay chain command is unsupported: {command}",
267 )));
268 }
269 }
270 return Ok(relay);
271 }
272 let mut relay: Option<RelayChain> = None;
274 for node in relay_chain.nodes() {
275 if let Some(command) = node.command().map(|c| c.as_str()) {
276 match &relay {
277 Some(relay) =>
278 if command.to_lowercase() != relay.binary.name() {
279 return Err(Error::UnsupportedCommand(format!(
280 "the relay chain command is unsupported: {command}",
281 )));
282 },
283 None => {
284 relay = Some(
285 relay::from(command, version, runtime_version, chain, cache).await?,
286 );
287 },
288 }
289 }
290 }
291 if let Some(relay) = relay {
292 return Ok(relay);
293 }
294 relay::default(version, runtime_version, chain, cache).await
296 }
297
298 pub fn relay_chain(&self) -> &str {
300 &self.relay_chain.chain
301 }
302
303 pub fn hrmp_channels(&self) -> bool {
305 self.hrmp_channels
306 }
307
308 pub async fn spawn(&mut self) -> Result<Network<LocalFileSystem>, Error> {
310 let relay_chain_binary_path = self.relay_chain.binary.path();
312 if !relay_chain_binary_path.exists() {
313 return Err(Error::MissingBinary(self.relay_chain.binary.name().to_string()));
314 }
315 let cache = relay_chain_binary_path
316 .parent()
317 .expect("expected relay chain binary path to exist");
318 let version = self.relay_chain.binary.version().ok_or_else(|| {
319 Error::MissingBinary(format!(
320 "Could not determine version for `{}` binary",
321 self.relay_chain.binary.name()
322 ))
323 })?;
324 for worker in &self.relay_chain.workers {
325 let dest = cache.join(worker);
326 if dest.exists() {
327 remove_symlink_file(&dest)?;
328 }
329 symlink_file(cache.join(format!("{worker}-{version}")), dest)?;
330 }
331
332 let network_config = self.network_config.adapt(&self.relay_chain, &self.parachains)?;
334 Ok(network_config.spawn_native().await?)
335 }
336}
337
338#[derive(Debug, PartialEq)]
342pub struct NetworkConfiguration(NetworkConfig, BTreeSet<u32>);
343
344impl NetworkConfiguration {
345 pub fn build(
352 relay_chain: Relay,
353 port: Option<u16>,
354 chains: Option<&[Box<dyn ChainT>]>,
355 ) -> Result<Self, Error> {
356 let validators: Vec<_> = VALIDATORS
357 .into_iter()
358 .take(chains.as_ref().map(|v| v.len()).unwrap_or_default().max(2))
359 .map(String::from)
360 .collect();
361
362 let mut builder = NetworkConfigBuilder::new().with_relaychain(|builder| {
363 let mut builder = builder.with_chain(relay_chain.chain()).with_validator(|builder| {
364 let mut builder = builder
365 .with_name(validators.first().expect("at least two validators defined above"));
366 if let Some(port) = port {
367 builder = builder.with_rpc_port(port)
368 }
369 builder
370 });
371
372 for validator in validators.iter().skip(1) {
373 builder = builder.with_validator(|builder| builder.with_name(validator))
374 }
375 builder
376 });
377
378 if let Some(chains) = chains {
379 let mut dependencies =
380 chains.iter().filter_map(|p| p.requires()).flatten().collect::<Vec<_>>();
381
382 for chain in chains {
383 builder = builder.with_parachain(|builder| {
384 let mut builder = builder
385 .with_id(chain.id())
386 .with_chain(chain.chain())
387 .with_default_command(chain.binary());
388
389 let mut genesis_overrides = serde_json::Map::new();
391 if let Some(mut r#override) = chain.genesis_overrides() {
392 r#override(&mut genesis_overrides);
393 }
394 for (_, r#override) in
395 dependencies.iter_mut().filter(|(t, _)| t == &chain.as_any().type_id())
396 {
397 r#override(&mut genesis_overrides);
398 }
399 if !genesis_overrides.is_empty() {
400 builder = builder.with_genesis_overrides(genesis_overrides);
401 }
402
403 builder.with_collator(|builder| {
404 let mut builder =
405 builder.with_name(&format!("{}-collator", chain.name())).with_args(
406 chain
407 .args()
408 .map(|args| args.into_iter().map(|arg| arg.into()).collect())
409 .unwrap_or_default(),
410 );
411 if let Some(port) = chain.port() {
412 builder = builder.with_rpc_port(*port)
413 }
414 builder
415 })
416 })
417 }
418
419 let chains = || chains.iter().map(|p| p.id());
421 for (sender, recipient) in
422 chains().flat_map(|s| chains().filter(move |r| s != *r).map(move |r| (s, r)))
423 {
424 builder = builder.with_hrmp_channel(|channel| {
425 channel
426 .with_sender(sender)
427 .with_recipient(recipient)
428 .with_max_capacity(1_000)
429 .with_max_message_size(8_000)
430 })
431 }
432 }
433
434 Ok(NetworkConfiguration(
435 builder.build().map_err(Error::NetworkConfigurationError)?,
436 Default::default(),
437 ))
438 }
439
440 fn adapt(
447 &self,
448 relay_chain: &RelayChain,
449 parachains: &IndexMap<u32, Chain>,
450 ) -> Result<NetworkConfig, Error> {
451 let binary_path = NetworkConfiguration::resolve_path(&relay_chain.binary.path())?;
453 let chain_spec_generator = match &relay_chain.chain_spec_generator {
454 None => None,
455 Some(path) => Some(format!(
456 "{} {}",
457 NetworkConfiguration::resolve_path(&path.path())?,
458 "{{chainName}}"
459 )),
460 };
461
462 let mut builder = NetworkConfigBuilder::new()
464 .with_relaychain(|relay| {
465 let source = self.0.relaychain();
466 let nodes = source.nodes();
467
468 let mut builder = relay
469 .with_chain(source.chain().as_str())
470 .with_default_args(source.default_args().into_iter().cloned().collect())
471 .with_default_command(binary_path.as_str());
473
474 if let Some(command) = source.chain_spec_command() {
476 builder = builder.with_chain_spec_command(command);
477 }
478 if source.chain_spec_command_is_local() {
479 builder = builder.chain_spec_command_is_local(true);
480 }
481 if let Some(location) = source.chain_spec_path() {
482 builder = builder.with_chain_spec_path(location.clone());
483 }
484 if let Some(chain_spec_command_output_path) =
485 source.chain_spec_command_output_path()
486 {
487 builder =
488 builder.with_chain_spec_command_output_path(chain_spec_command_output_path);
489 }
490 if let Some(command) = chain_spec_generator {
492 builder = builder.with_chain_spec_command(command);
493 }
494 if let Some(genesis) = source.runtime_genesis_patch() {
496 builder = builder.with_genesis_overrides(genesis.clone());
497 }
498 if let Some(location) = source.wasm_override() {
499 builder = builder.with_wasm_override(location.clone());
500 }
501
502 let mut builder = builder.with_validator(|builder| {
504 let source = nodes.first().expect("expected at least one node");
505 Self::build_node_from_source(builder, source, binary_path.as_str())
506 });
507 for source in nodes.iter().skip(1) {
508 builder = builder.with_validator(|builder| {
509 Self::build_node_from_source(builder, source, binary_path.as_str())
510 });
511 }
512
513 builder
514 })
515 .with_global_settings(|settings| {
517 settings.with_network_spawn_timeout(1_000).with_node_spawn_timeout(300)
518 });
519
520 let parachains = ¶chains;
522 for source in self.0.parachains() {
523 let id = source.id();
524 let collators = source.collators();
525 let para =
526 parachains.get(&id).expect("expected parachain existence due to preprocessing");
527
528 let binary_path = NetworkConfiguration::resolve_path(¶.binary.path())?;
530 let mut chain_spec_generator = match ¶.chain_spec_generator {
531 None => None,
532 Some(path) => Some(format!(
533 "{} {}",
534 NetworkConfiguration::resolve_path(&path.path())?,
535 "{{chainName}}"
536 )),
537 };
538
539 builder = builder.with_parachain(|builder| {
540 let mut builder = builder
541 .with_id(id)
542 .with_default_args(source.default_args().into_iter().cloned().collect())
543 .with_default_command(binary_path.as_str());
545
546 if let Some(chain) = source.chain() {
548 builder = builder.with_chain(chain.as_str());
549 if chain.as_str().contains("passet-hub") {
552 let chain_spec = crate::get_passet_hub_spec_content();
553 let temp_dir = std::env::temp_dir();
554 let spec_path = temp_dir.join("passet-hub-spec.json");
555 std::fs::write(&spec_path, chain_spec)
556 .expect("Failed to write passet-hub chain spec");
557 builder = builder.with_chain_spec_path(spec_path);
558 chain_spec_generator = None;
559 }
560 }
561 if let Some(command) = source.chain_spec_command() {
562 builder = builder.with_chain_spec_command(command);
563 }
564 if source.chain_spec_command_is_local() {
565 builder = builder.chain_spec_command_is_local(true);
566 }
567 if let Some(chain_spec_command_output_path) =
568 source.chain_spec_command_output_path()
569 {
570 builder =
571 builder.with_chain_spec_command_output_path(chain_spec_command_output_path)
572 }
573 if let Some(location) = source.chain_spec_path() {
574 builder = builder.with_chain_spec_path(location.clone());
575 }
576 if let Some(command) = chain_spec_generator {
578 builder = builder.with_chain_spec_command(command);
579 }
580 if let Some(genesis) = source.genesis_overrides() {
582 builder = builder.with_genesis_overrides(genesis.clone());
583 }
584 if let Some(location) = source.wasm_override() {
585 builder = builder.with_wasm_override(location.clone());
586 }
587 builder = builder.evm_based(self.1.contains(&id) || source.is_evm_based());
589
590 let mut builder = builder.with_collator(|builder| {
592 let source = collators.first().expect("expected at least one collator");
593 Self::build_node_from_source(builder, source, binary_path.as_str())
594 });
595 for source in collators.iter().skip(1) {
596 builder = builder.with_collator(|builder| {
597 Self::build_node_from_source(builder, source, binary_path.as_str())
598 });
599 }
600
601 builder
602 });
603 }
604
605 for source in self.0.hrmp_channels() {
607 builder = builder.with_hrmp_channel(|channel| {
608 channel
609 .with_sender(source.sender())
610 .with_recipient(source.recipient())
611 .with_max_capacity(source.max_capacity())
612 .with_max_message_size(source.max_message_size())
613 })
614 }
615
616 builder
617 .build()
618 .map_err(|e| Error::Config(format!("could not configure network {:?}", e)))
619 }
620
621 fn build_node_from_source(
623 builder: NodeConfigBuilder<Initial>,
624 source: &NodeConfig,
625 binary_path: &str,
626 ) -> NodeConfigBuilder<Buildable> {
627 let mut builder = builder
628 .with_name(source.name())
629 .bootnode(source.is_bootnode())
630 .invulnerable(source.is_invulnerable())
631 .validator(source.is_validator())
632 .with_args(source.args().into_iter().cloned().collect())
633 .with_command(binary_path)
634 .with_env(source.env().into_iter().cloned().collect());
635 if let Some(command) = source.subcommand() {
636 builder = builder.with_subcommand(command.clone())
637 }
638 if let Some(port) = source.rpc_port() {
639 builder = builder.with_rpc_port(port)
640 }
641 if let Some(port) = source.ws_port() {
642 builder = builder.with_ws_port(port)
643 }
644 builder
645 }
646
647 fn resolve_path(path: &Path) -> Result<String, Error> {
652 path.canonicalize()
653 .map_err(|_| {
654 Error::Config(format!("the canonical path of {:?} could not be resolved", path))
655 })
656 .map(|p| p.to_str().map(|p| p.to_string()))?
657 .ok_or_else(|| Error::Config("the path is invalid".into()))
658 }
659}
660
661impl TryFrom<&Path> for NetworkConfiguration {
662 type Error = Error;
663
664 fn try_from(file: &Path) -> Result<Self, Self::Error> {
665 if !file.exists() {
666 return Err(Error::Config(format!("The {file:?} configuration file was not found")));
667 }
668
669 let contents = std::fs::read_to_string(file)?;
671 let config = contents.parse::<DocumentMut>().map_err(|err| Error::TomlError(err.into()))?;
672 let evm_based = config
673 .get("parachains")
674 .and_then(|p| p.as_array_of_tables())
675 .map(|tables| {
676 tables
677 .iter()
678 .filter_map(|table| {
679 table
680 .get("force_decorator")
681 .and_then(|i| i.as_str())
682 .filter(|v| *v == "generic-evm")
683 .and_then(|_| table.get("id"))
684 .and_then(|i| i.as_integer())
685 .map(|id| id as u32)
686 })
687 .collect()
688 })
689 .unwrap_or_default();
690
691 Ok(NetworkConfiguration(
692 NetworkConfig::load_from_toml(
693 file.to_str().expect("expected file path to be convertible to string"),
694 )
695 .map_err(|e| Error::Config(e.to_string()))?,
696 evm_based,
697 ))
698 }
699}
700
701impl TryFrom<NetworkConfig> for NetworkConfiguration {
702 type Error = ();
703
704 fn try_from(value: NetworkConfig) -> Result<Self, Self::Error> {
705 Ok(NetworkConfiguration(value, Default::default()))
706 }
707}
708
709struct RelayChain {
711 runtime: Runtime,
713 binary: Binary,
715 workers: [&'static str; 2],
717 #[allow(dead_code)]
719 chain: String,
720 chain_spec_generator: Option<Binary>,
722}
723
724#[derive(Debug, PartialEq)]
726struct Chain {
727 id: u32,
729 binary: Binary,
731 chain: Option<String>,
733 chain_spec_generator: Option<Binary>,
735}
736
737impl Chain {
738 fn from_local(id: u32, path: PathBuf, chain: Option<&str>) -> Result<Chain, Error> {
745 let name = path
746 .file_name()
747 .and_then(|f| f.to_str())
748 .ok_or_else(|| Error::Config(format!("unable to determine file name for {path:?}")))?
749 .to_string();
750 let manifest = resolve_manifest(&name, &path)?;
752 Ok(Chain {
753 id,
754 binary: Binary::Local { name, path, manifest },
755 chain: chain.map(|c| c.to_string()),
756 chain_spec_generator: None,
757 })
758 }
759
760 fn from_repository(
769 id: u32,
770 repo: &Repository,
771 chain: Option<&str>,
772 cache: &Path,
773 ) -> Result<Chain, Error> {
774 if repo.url.host_str().is_some_and(|h| h.to_lowercase() == "github.com") {
776 let github = GitHub::parse(repo.url.as_str())?;
777 let source = Source::GitHub(SourceCodeArchive {
778 owner: github.org,
779 repository: github.name,
780 reference: repo.reference.clone(),
781 manifest: None,
782 package: repo.package.clone(),
783 artifacts: vec![repo.package.clone()],
784 })
785 .into();
786 Ok(Chain {
787 id,
788 binary: Binary::Source {
789 name: repo.package.clone(),
790 source,
791 cache: cache.to_path_buf(),
792 },
793 chain: chain.map(|c| c.to_string()),
794 chain_spec_generator: None,
795 })
796 } else {
797 Ok(Chain {
798 id,
799 binary: Binary::Source {
800 name: repo.package.clone(),
801 source: Git {
802 url: repo.url.clone(),
803 reference: repo.reference.clone(),
804 manifest: None,
805 package: repo.package.clone(),
806 artifacts: vec![repo.package.clone()],
807 }
808 .into(),
809 cache: cache.to_path_buf(),
810 },
811 chain: chain.map(|c| c.to_string()),
812 chain_spec_generator: None,
813 })
814 }
815 }
816
817 fn from_omni_node(id: u32, cache: &Path) -> Result<Chain, Error> {
818 Ok(Chain {
819 id,
820 binary: Binary::Source {
821 name: PolkadotOmniNode.binary().to_string(),
822 source: Box::new(PolkadotOmniNode.source()?),
823 cache: cache.to_path_buf(),
824 },
825 chain: None,
826 chain_spec_generator: None,
827 })
828 }
829}
830
831fn resolve_manifest(package: &str, path: &Path) -> Result<Option<PathBuf>, Error> {
837 let matches_package = |config: &DocumentMut| {
838 config
839 .get("package")
840 .and_then(|i| i.as_table())
841 .and_then(|t| t.get("name"))
842 .and_then(|i| i.as_str()) ==
843 Some(package)
844 };
845
846 let mut manifest = Some(path);
847 'outer: while let Some(path) = manifest {
848 let manifest_path = path.join("Cargo.toml");
849 if !manifest_path.exists() {
850 manifest = path.parent();
851 continue;
852 }
853 let contents = std::fs::read_to_string(&manifest_path)?;
854 let config = contents.parse::<DocumentMut>().map_err(|err| Error::TomlError(err.into()))?;
855 if matches_package(&config) {
857 break 'outer;
858 }
859 if let Some(members) = config
861 .get("workspace")
862 .and_then(|i| i.as_table())
863 .and_then(|t| t.get("members"))
864 .and_then(|m| m.as_array())
865 .map(|a| a.iter().filter_map(|v| v.as_str()))
866 {
867 for member in members {
869 let member_path = path.join(member);
870 for entry in glob(member_path.to_string_lossy().as_ref())
871 .expect("expected valid glob for workspace member")
872 .filter_map(Result::ok)
873 {
874 let manifest_path = entry.join("Cargo.toml");
875 if manifest_path.exists() {
876 let contents = std::fs::read_to_string(&manifest_path)?;
877 let config = contents
878 .parse::<DocumentMut>()
879 .map_err(|err| Error::TomlError(err.into()))?;
880 if matches_package(&config) {
881 break 'outer;
882 }
883 }
884 }
885 }
886 };
887 manifest = path.parent();
888 }
889 Ok(manifest.map(|p| p.join("Cargo.toml")))
890}
891
892#[cfg(test)]
893mod tests {
894 use super::*;
895 use anyhow::Result;
896 use std::{
897 env::current_dir,
898 fs::{File, create_dir_all, remove_dir, remove_file},
899 io::Write,
900 };
901 use tempfile::{Builder, tempdir};
902
903 pub(crate) const FALLBACK: &str = "stable2512";
904 pub(crate) const RELAY_BINARY_VERSION: &str = "stable2512";
905 pub(crate) const SYSTEM_PARA_BINARY_VERSION: &str = "stable2512";
906 const SYSTEM_PARA_RUNTIME_VERSION: &str = "v1.4.1";
907
908 mod zombienet {
909 use super::*;
910 use pop_common::{Status, helpers::with_current_dir_async};
911
912 pub(crate) struct Output;
913 impl Status for Output {
914 fn update(&self, status: &str) {
915 println!("{status}")
916 }
917 }
918
919 #[tokio::test]
920 async fn new_with_relay_only_works() -> Result<()> {
921 let temp_dir = tempdir()?;
922 let cache = PathBuf::from(temp_dir.path());
923 let config = Builder::new().suffix(".toml").tempfile()?;
924 writeln!(
925 config.as_file(),
926 r#"
927[relaychain]
928chain = "paseo-local"
929"#
930 )?;
931
932 let zombienet = Zombienet::new(
933 &cache,
934 config.path().try_into()?,
935 Some(RELAY_BINARY_VERSION),
936 None,
937 None,
938 None,
939 None,
940 )
941 .await?;
942
943 let relay_chain = &zombienet.relay_chain.binary;
944 assert_eq!(relay_chain.name(), "polkadot");
945 assert_eq!(
946 relay_chain.path(),
947 temp_dir.path().join(format!("polkadot-{RELAY_BINARY_VERSION}"))
948 );
949 assert_eq!(relay_chain.version().unwrap(), RELAY_BINARY_VERSION);
950 assert!(matches!(
951 relay_chain,
952 Binary::Source { source, .. }
953 if matches!(source.as_ref(), Source::GitHub(ReleaseArchive { tag, .. })
954 if *tag == Some(format!("polkadot-{RELAY_BINARY_VERSION}"))
955 )
956 ));
957 assert!(zombienet.parachains.is_empty());
958 assert_eq!(zombienet.relay_chain(), "paseo-local");
959 assert!(!zombienet.hrmp_channels());
960 Ok(())
961 }
962
963 #[tokio::test]
964 async fn new_with_relay_only_from_network_config_works() -> Result<()> {
965 let temp_dir = tempdir()?;
966 let cache = PathBuf::from(temp_dir.path());
967 let config = NetworkConfigBuilder::new()
968 .with_relaychain(|b| {
969 b.with_chain("paseo-local").with_validator(|b| b.with_name("alice"))
970 })
971 .build()
972 .unwrap();
973
974 let zombienet = Zombienet::new(
975 &cache,
976 config.try_into().unwrap(),
977 Some(RELAY_BINARY_VERSION),
978 None,
979 None,
980 None,
981 None,
982 )
983 .await?;
984
985 let relay_chain = &zombienet.relay_chain.binary;
986 assert_eq!(relay_chain.name(), "polkadot");
987 assert_eq!(
988 relay_chain.path(),
989 temp_dir.path().join(format!("polkadot-{RELAY_BINARY_VERSION}"))
990 );
991 assert_eq!(relay_chain.version().unwrap(), RELAY_BINARY_VERSION);
992 assert!(matches!(
993 relay_chain,
994 Binary::Source { source, .. }
995 if matches!(source.as_ref(), Source::GitHub(ReleaseArchive { tag, .. })
996 if *tag == Some(format!("polkadot-{RELAY_BINARY_VERSION}"))
997 )
998 ));
999 assert!(zombienet.parachains.is_empty());
1000 assert_eq!(zombienet.relay_chain(), "paseo-local");
1001 assert!(!zombienet.hrmp_channels());
1002 Ok(())
1003 }
1004
1005 #[tokio::test]
1006 async fn new_with_relay_chain_spec_generator_works() -> Result<()> {
1007 let temp_dir = tempdir()?;
1008 let cache = PathBuf::from(temp_dir.path());
1009 let config = Builder::new().suffix(".toml").tempfile()?;
1010 writeln!(
1011 config.as_file(),
1012 r#"
1013[relaychain]
1014chain = "paseo-local"
1015"#
1016 )?;
1017 let version = "v1.3.3";
1018
1019 let zombienet = Zombienet::new(
1020 &cache,
1021 config.path().try_into()?,
1022 None,
1023 Some(version),
1024 None,
1025 None,
1026 None,
1027 )
1028 .await?;
1029
1030 assert_eq!(zombienet.relay_chain.chain, "paseo-local");
1031 let chain_spec_generator = &zombienet.relay_chain.chain_spec_generator.unwrap();
1032 assert_eq!(chain_spec_generator.name(), "paseo-chain-spec-generator");
1033 assert_eq!(
1034 chain_spec_generator.path(),
1035 temp_dir.path().join(format!("paseo-chain-spec-generator-{version}"))
1036 );
1037 assert_eq!(chain_spec_generator.version().unwrap(), version);
1038 assert!(matches!(
1039 chain_spec_generator,
1040 Binary::Source { source, .. }
1041 if matches!(source.as_ref(), Source::GitHub(ReleaseArchive { tag, .. })
1042 if *tag == Some(version.to_string())
1043 )
1044 ));
1045 assert!(zombienet.parachains.is_empty());
1046 Ok(())
1047 }
1048
1049 #[tokio::test]
1050 async fn new_with_default_command_works() -> Result<()> {
1051 let temp_dir = tempdir()?;
1052 let cache = PathBuf::from(temp_dir.path());
1053 let config = Builder::new().suffix(".toml").tempfile()?;
1054 writeln!(
1055 config.as_file(),
1056 r#"
1057[relaychain]
1058chain = "paseo-local"
1059default_command = "./bin-stable2512/polkadot"
1060"#
1061 )?;
1062
1063 let zombienet = Zombienet::new(
1064 &cache,
1065 config.path().try_into()?,
1066 Some(RELAY_BINARY_VERSION),
1067 None,
1068 None,
1069 None,
1070 None,
1071 )
1072 .await?;
1073
1074 let relay_chain = &zombienet.relay_chain.binary;
1075 assert_eq!(relay_chain.name(), "polkadot");
1076 assert_eq!(
1077 relay_chain.path(),
1078 temp_dir.path().join(format!("polkadot-{RELAY_BINARY_VERSION}"))
1079 );
1080 assert_eq!(relay_chain.version().unwrap(), RELAY_BINARY_VERSION);
1081 assert!(matches!(
1082 relay_chain,
1083 Binary::Source { source, ..}
1084 if matches!(source.as_ref(), Source::GitHub(ReleaseArchive { tag, .. })
1085 if *tag == Some(format!("polkadot-{RELAY_BINARY_VERSION}"))
1086 )
1087 ));
1088 assert!(zombienet.parachains.is_empty());
1089 Ok(())
1090 }
1091
1092 #[tokio::test]
1093 async fn new_accepts_parachain_names_without_repo_urls() -> Result<()> {
1094 let temp_dir = tempdir()?;
1095 let cache = PathBuf::from(temp_dir.path());
1096 let config = Builder::new().suffix(".toml").tempfile()?;
1097 writeln!(
1098 config.as_file(),
1099 r#"
1100[relaychain]
1101chain = "paseo-local"
1102"#
1103 )?;
1104
1105 let zombienet = Zombienet::new(
1106 &cache,
1107 config.path().try_into()?,
1108 Some(RELAY_BINARY_VERSION),
1109 None,
1110 None,
1111 None,
1112 Some(&vec!["asset-hub".to_string(), "coretime".to_string()]),
1113 )
1114 .await?;
1115
1116 assert!(zombienet.parachains.is_empty());
1117 Ok(())
1118 }
1119
1120 #[tokio::test]
1121 async fn new_with_node_command_works() -> Result<()> {
1122 let temp_dir = tempdir()?;
1123 let cache = PathBuf::from(temp_dir.path());
1124 let config = Builder::new().suffix(".toml").tempfile()?;
1125 writeln!(
1126 config.as_file(),
1127 r#"
1128[relaychain]
1129chain = "paseo-local"
1130
1131[[relaychain.nodes]]
1132name = "alice"
1133validator = true
1134command = "polkadot"
1135"#
1136 )?;
1137
1138 let zombienet = Zombienet::new(
1139 &cache,
1140 config.path().try_into()?,
1141 Some(RELAY_BINARY_VERSION),
1142 None,
1143 None,
1144 None,
1145 None,
1146 )
1147 .await?;
1148
1149 let relay_chain = &zombienet.relay_chain.binary;
1150 assert_eq!(relay_chain.name(), "polkadot");
1151 assert_eq!(
1152 relay_chain.path(),
1153 temp_dir.path().join(format!("polkadot-{RELAY_BINARY_VERSION}"))
1154 );
1155 assert_eq!(relay_chain.version().unwrap(), RELAY_BINARY_VERSION);
1156 assert!(matches!(
1157 relay_chain,
1158 Binary::Source { source, .. }
1159 if matches!(source.as_ref(), Source::GitHub(ReleaseArchive { tag, .. })
1160 if *tag == Some(format!("polkadot-{RELAY_BINARY_VERSION}"))
1161 )
1162 ));
1163 assert!(zombienet.parachains.is_empty());
1164 Ok(())
1165 }
1166
1167 #[tokio::test]
1168 async fn new_with_node_command_from_network_config_works() -> Result<()> {
1169 let temp_dir = tempdir()?;
1170 let cache = PathBuf::from(temp_dir.path());
1171 let config = NetworkConfigBuilder::new()
1172 .with_relaychain(|b| {
1173 b.with_chain("paseo-local")
1174 .with_validator(|b| b.with_name("alice").with_command("polkadot"))
1175 })
1176 .build()
1177 .unwrap();
1178
1179 let zombienet = Zombienet::new(
1180 &cache,
1181 config.try_into().unwrap(),
1182 Some(RELAY_BINARY_VERSION),
1183 None,
1184 None,
1185 None,
1186 None,
1187 )
1188 .await?;
1189
1190 let relay_chain = &zombienet.relay_chain.binary;
1191 assert_eq!(relay_chain.name(), "polkadot");
1192 assert_eq!(
1193 relay_chain.path(),
1194 temp_dir.path().join(format!("polkadot-{RELAY_BINARY_VERSION}"))
1195 );
1196 assert_eq!(relay_chain.version().unwrap(), RELAY_BINARY_VERSION);
1197 assert!(matches!(
1198 relay_chain,
1199 Binary::Source { source, .. }
1200 if matches!(source.as_ref(), Source::GitHub(ReleaseArchive { tag, .. })
1201 if *tag == Some(format!("polkadot-{RELAY_BINARY_VERSION}"))
1202 )
1203 ));
1204 assert!(zombienet.parachains.is_empty());
1205 Ok(())
1206 }
1207
1208 #[tokio::test]
1209 async fn new_ensures_node_commands_valid() -> Result<()> {
1210 let temp_dir = tempdir()?;
1211 let cache = PathBuf::from(temp_dir.path());
1212 let config = Builder::new().suffix(".toml").tempfile()?;
1213 writeln!(
1214 config.as_file(),
1215 r#"
1216[relaychain]
1217chain = "paseo-local"
1218
1219[[relaychain.nodes]]
1220name = "alice"
1221validator = true
1222command = "polkadot"
1223
1224[[relaychain.nodes]]
1225name = "bob"
1226validator = true
1227command = "polkadot-stable2512"
1228"#
1229 )?;
1230
1231 assert!(matches!(
1232 Zombienet::new(&cache, config.path().try_into()?, None, None, None, None, None).await,
1233 Err(Error::UnsupportedCommand(error))
1234 if error == "the relay chain command is unsupported: polkadot-stable2512"
1235 ));
1236 Ok(())
1237 }
1238
1239 #[tokio::test]
1240 async fn new_ensures_node_command_valid() -> Result<()> {
1241 let temp_dir = tempdir()?;
1242 let cache = PathBuf::from(temp_dir.path());
1243 let config = Builder::new().suffix(".toml").tempfile()?;
1244 writeln!(
1245 config.as_file(),
1246 r#"
1247[relaychain]
1248chain = "paseo-local"
1249default_command = "polkadot"
1250
1251[[relaychain.nodes]]
1252name = "alice"
1253validator = true
1254command = "polkadot-stable2512"
1255"#
1256 )?;
1257
1258 assert!(matches!(
1259 Zombienet::new(&cache, config.path().try_into()?, None, None, None, None, None).await,
1260 Err(Error::UnsupportedCommand(error))
1261 if error == "the relay chain command is unsupported: polkadot-stable2512"
1262 ));
1263 Ok(())
1264 }
1265
1266 #[tokio::test]
1267 async fn new_ensures_node_command_from_network_config_valid() -> Result<()> {
1268 let temp_dir = tempdir()?;
1269 let cache = PathBuf::from(temp_dir.path());
1270 let config = NetworkConfigBuilder::new()
1271 .with_relaychain(|b| {
1272 b.with_chain("paseo-local")
1273 .with_validator(|b| b.with_name("alice").with_command("polkadot"))
1274 .with_validator(|b| b.with_name("bob").with_command("polkadot"))
1275 .with_validator(|b| b.with_name("charlie").with_command("p0lk4d0t"))
1276 })
1277 .build()
1278 .unwrap();
1279
1280 assert!(matches!(
1281 Zombienet::new(&cache, config.try_into().unwrap(), None, None, None, None,None).await,
1282 Err(Error::UnsupportedCommand(error))
1283 if error == "the relay chain command is unsupported: p0lk4d0t"
1284 ));
1285 Ok(())
1286 }
1287
1288 #[tokio::test]
1289 async fn new_with_system_chain_works() -> Result<()> {
1290 let temp_dir = tempdir()?;
1291 let cache = PathBuf::from(temp_dir.path());
1292 let config = Builder::new().suffix(".toml").tempfile()?;
1293 writeln!(
1294 config.as_file(),
1295 r#"
1296[relaychain]
1297chain = "paseo-local"
1298
1299[[parachains]]
1300id = 1000
1301chain = "asset-hub-paseo-local"
1302"#
1303 )?;
1304
1305 let zombienet = Zombienet::new(
1306 &cache,
1307 config.path().try_into()?,
1308 Some(RELAY_BINARY_VERSION),
1309 None,
1310 Some(SYSTEM_PARA_BINARY_VERSION),
1311 None,
1312 None,
1313 )
1314 .await?;
1315
1316 assert_eq!(zombienet.parachains.len(), 1);
1317 let system_parachain = &zombienet.parachains.get(&1000).unwrap().binary;
1318 assert_eq!(system_parachain.name(), "polkadot-parachain");
1319 assert_eq!(
1320 system_parachain.path(),
1321 temp_dir.path().join(format!("polkadot-parachain-{SYSTEM_PARA_BINARY_VERSION}"))
1322 );
1323 assert_eq!(system_parachain.version().unwrap(), SYSTEM_PARA_BINARY_VERSION);
1324 assert!(matches!(
1325 system_parachain,
1326 Binary::Source { source, .. }
1327 if matches!(source.as_ref(), Source::GitHub(ReleaseArchive { tag, .. })
1328 if *tag == Some(format!("polkadot-{SYSTEM_PARA_BINARY_VERSION}"))
1329 )
1330 ));
1331 Ok(())
1332 }
1333
1334 #[tokio::test]
1335 async fn new_with_system_chain_spec_generator_works() -> Result<()> {
1336 let temp_dir = tempdir()?;
1337 let cache = PathBuf::from(temp_dir.path());
1338 let config = Builder::new().suffix(".toml").tempfile()?;
1339 writeln!(
1340 config.as_file(),
1341 r#"
1342[relaychain]
1343chain = "paseo-local"
1344
1345[[parachains]]
1346id = 1000
1347chain = "asset-hub-paseo-local"
1348"#
1349 )?;
1350
1351 let zombienet = Zombienet::new(
1352 &cache,
1353 config.path().try_into()?,
1354 None,
1355 None,
1356 None,
1357 Some(SYSTEM_PARA_RUNTIME_VERSION),
1358 None,
1359 )
1360 .await?;
1361
1362 assert_eq!(zombienet.parachains.len(), 1);
1363 let system_parachain = &zombienet.parachains.get(&1000).unwrap();
1364 assert_eq!(system_parachain.chain.as_ref().unwrap(), "asset-hub-paseo-local");
1365 let chain_spec_generator = system_parachain.chain_spec_generator.as_ref().unwrap();
1366 assert_eq!(chain_spec_generator.name(), "paseo-chain-spec-generator");
1367 assert_eq!(
1368 chain_spec_generator.path(),
1369 temp_dir
1370 .path()
1371 .join(format!("paseo-chain-spec-generator-{SYSTEM_PARA_RUNTIME_VERSION}"))
1372 );
1373 assert_eq!(chain_spec_generator.version().unwrap(), SYSTEM_PARA_RUNTIME_VERSION);
1374 assert!(matches!(
1375 chain_spec_generator,
1376 Binary::Source { source, .. }
1377 if matches!(source.as_ref(), Source::GitHub(ReleaseArchive { tag, .. })
1378 if *tag == Some(SYSTEM_PARA_RUNTIME_VERSION.to_string())
1379 )
1380 ));
1381 Ok(())
1382 }
1383
1384 #[tokio::test]
1385 async fn new_with_pop_works() -> Result<()> {
1386 let temp_dir = tempdir()?;
1387 let cache = PathBuf::from(temp_dir.path());
1388 let config = Builder::new().suffix(".toml").tempfile()?;
1389 writeln!(
1390 config.as_file(),
1391 r#"
1392[relaychain]
1393chain = "paseo-local"
1394
1395[[parachains]]
1396id = 4385
1397default_command = "pop-node"
1398"#
1399 )?;
1400
1401 let zombienet =
1402 Zombienet::new(&cache, config.path().try_into()?, None, None, None, None, None)
1403 .await?;
1404
1405 assert_eq!(zombienet.parachains.len(), 1);
1406 let pop = &zombienet.parachains.get(&4385).unwrap().binary;
1407 let version = pop.latest().unwrap();
1408 assert_eq!(pop.name(), "pop-node");
1409 assert_eq!(pop.path(), temp_dir.path().join(format!("pop-node-{version}")));
1410 assert_eq!(pop.version().unwrap(), version);
1411 assert!(matches!(
1412 pop,
1413 Binary::Source { source, .. }
1414 if matches!(source.as_ref(), Source::GitHub(ReleaseArchive { tag, .. })
1415 if *tag == Some(format!("node-{version}"))
1416 )
1417 ));
1418 Ok(())
1419 }
1420
1421 #[tokio::test]
1422 async fn new_with_pop_version_works() -> Result<()> {
1423 let temp_dir = tempdir()?;
1424 let cache = PathBuf::from(temp_dir.path());
1425 let config = Builder::new().suffix(".toml").tempfile()?;
1426 writeln!(
1427 config.as_file(),
1428 r#"
1429[relaychain]
1430chain = "paseo-local"
1431
1432[[parachains]]
1433id = 4385
1434default_command = "pop-node"
1435"#
1436 )?;
1437 let version = "v1.0";
1438
1439 let zombienet = Zombienet::new(
1440 &cache,
1441 config.path().try_into()?,
1442 None,
1443 None,
1444 None,
1445 None,
1446 Some(&vec![format!("https://github.com/r0gue-io/pop-node#{version}")]),
1447 )
1448 .await?;
1449
1450 assert_eq!(zombienet.parachains.len(), 1);
1451 let pop = &zombienet.parachains.get(&4385).unwrap().binary;
1452 assert_eq!(pop.name(), "pop-node");
1453 assert_eq!(pop.path(), temp_dir.path().join(format!("pop-node-{version}")));
1454 assert_eq!(pop.version().unwrap(), version);
1455 assert!(matches!(
1456 pop,
1457 Binary::Source { source, .. }
1458 if matches!(source.as_ref(), Source::GitHub(ReleaseArchive { tag, .. })
1459 if *tag == Some(format!("node-{version}"))
1460 )
1461 ));
1462 Ok(())
1463 }
1464
1465 #[tokio::test]
1466 async fn new_with_local_parachain_works() -> Result<()> {
1467 let temp_dir = tempdir()?;
1468 let cache = PathBuf::from(temp_dir.path());
1469 let config = Builder::new().suffix(".toml").tempfile()?;
1470 writeln!(
1471 config.as_file(),
1472 r#"
1473[relaychain]
1474chain = "paseo-local"
1475
1476[[parachains]]
1477id = 2000
1478default_command = "./target/release/parachain-template-node"
1479"#
1480 )?;
1481
1482 let zombienet =
1483 Zombienet::new(&cache, config.path().try_into()?, None, None, None, None, None)
1484 .await?;
1485
1486 assert_eq!(zombienet.parachains.len(), 1);
1487 let pop = &zombienet.parachains.get(&2000).unwrap().binary;
1488 assert_eq!(pop.name(), "parachain-template-node");
1489 assert_eq!(pop.path(), Path::new("./target/release/parachain-template-node"));
1490 assert_eq!(pop.version(), None);
1491 assert!(matches!(pop, Binary::Local { .. }));
1492 Ok(())
1493 }
1494
1495 #[tokio::test]
1496 async fn new_with_local_parachain_without_path_works() -> Result<()> {
1497 let temp_dir = tempdir()?;
1498 let cache = PathBuf::from(temp_dir.path());
1499 let config = Builder::new().suffix(".toml").tempfile()?;
1500 writeln!(
1501 config.as_file(),
1502 r#"
1503[relaychain]
1504chain = "paseo-local"
1505
1506[[parachains]]
1507id = 1000
1508
1509[parachains.collator]
1510name = "collator"
1511command = "parachain-template-node"
1512
1513[[parachains]]
1514id = 2000
1515
1516[parachains.collator]
1517name = "collator"
1518command = "substrate-contracts-node"
1519"#
1520 )?;
1521 let temp_workspace = tempdir()?;
1522 with_current_dir_async(temp_workspace.path(), async || {
1523 assert!(matches!(
1526 Zombienet::new(&cache, config.path().try_into()?, None, None, None, None, None).await,
1527 Err(Error::MissingBinary(command))
1528 if command == "parachain-template-node"
1529 ));
1530 let parachain_template = PathBuf::from("target/release/parachain-template-node");
1532 create_dir_all(parachain_template.parent().unwrap())?;
1533 File::create(¶chain_template)?;
1534 let parachain_contracts_template =
1536 PathBuf::from("target/debug/substrate-contracts-node");
1537 create_dir_all(parachain_contracts_template.parent().unwrap())?;
1538 File::create(¶chain_contracts_template)?;
1539
1540 let zombienet =
1541 Zombienet::new(&cache, config.path().try_into()?, None, None, None, None, None)
1542 .await?;
1543 remove_file(¶chain_template)?;
1546 remove_file(¶chain_contracts_template)?;
1547 remove_dir(parachain_template.parent().unwrap())?;
1548 remove_dir(parachain_contracts_template.parent().unwrap())?;
1549
1550 assert_eq!(zombienet.parachains.len(), 2);
1551 let parachain = &zombienet.parachains.get(&1000).unwrap().binary;
1552 assert_eq!(parachain.name(), "parachain-template-node");
1553 assert_eq!(parachain.path(), Path::new("./target/release/parachain-template-node"));
1554 assert_eq!(parachain.version(), None);
1555 assert!(matches!(parachain, Binary::Local { .. }));
1556 let contract_parachain = &zombienet.parachains.get(&2000).unwrap().binary;
1557 assert_eq!(contract_parachain.name(), "substrate-contracts-node");
1558 assert_eq!(
1559 contract_parachain.path(),
1560 Path::new("./target/debug/substrate-contracts-node")
1561 );
1562 assert_eq!(contract_parachain.version(), None);
1563 assert!(matches!(contract_parachain, Binary::Local { .. }));
1564 Ok(())
1565 })
1566 .await
1567 }
1568
1569 #[tokio::test]
1570 async fn new_with_collator_command_works() -> Result<()> {
1571 let temp_dir = tempdir()?;
1572 let cache = PathBuf::from(temp_dir.path());
1573 let config = Builder::new().suffix(".toml").tempfile()?;
1574 writeln!(
1575 config.as_file(),
1576 r#"
1577[relaychain]
1578chain = "paseo-local"
1579
1580[[parachains]]
1581id = 2000
1582
1583[[parachains.collators]]
1584name = "collator-01"
1585command = "./target/release/parachain-template-node"
1586"#
1587 )?;
1588
1589 let zombienet =
1590 Zombienet::new(&cache, config.path().try_into()?, None, None, None, None, None)
1591 .await?;
1592
1593 assert_eq!(zombienet.parachains.len(), 1);
1594 let pop = &zombienet.parachains.get(&2000).unwrap().binary;
1595 assert_eq!(pop.name(), "parachain-template-node");
1596 assert_eq!(pop.path(), Path::new("./target/release/parachain-template-node"));
1597 assert_eq!(pop.version(), None);
1598 assert!(matches!(pop, Binary::Local { .. }));
1599 Ok(())
1600 }
1601
1602 #[tokio::test]
1603 async fn new_with_moonbeam_works() -> Result<()> {
1604 let temp_dir = tempdir()?;
1605 let cache = PathBuf::from(temp_dir.path());
1606 let config = Builder::new().suffix(".toml").tempfile()?;
1607 writeln!(
1608 config.as_file(),
1609 r#"
1610[relaychain]
1611chain = "paseo-local"
1612
1613[[parachains]]
1614id = 2000
1615default_command = "moonbeam"
1616"#
1617 )?;
1618 let version = "v0.38.0";
1619
1620 let zombienet = Zombienet::new(
1621 &cache,
1622 config.path().try_into()?,
1623 None,
1624 None,
1625 None,
1626 None,
1627 Some(&vec![format!("https://github.com/moonbeam-foundation/moonbeam#{version}")]),
1628 )
1629 .await?;
1630
1631 assert_eq!(zombienet.parachains.len(), 1);
1632 let pop = &zombienet.parachains.get(&2000).unwrap().binary;
1633 assert_eq!(pop.name(), "moonbeam");
1634 assert_eq!(pop.path(), temp_dir.path().join(format!("moonbeam-{version}")));
1635 assert_eq!(pop.version().unwrap(), version);
1636 assert!(matches!(
1637 pop,
1638 Binary::Source { source, .. }
1639 if matches!(source.as_ref(), Source::GitHub(SourceCodeArchive { reference, .. })
1640 if *reference == Some(version.to_string())
1641 )
1642 ));
1643 Ok(())
1644 }
1645
1646 #[tokio::test]
1647 async fn new_with_hrmp_channels_works() -> Result<()> {
1648 let temp_dir = tempdir()?;
1649 let cache = PathBuf::from(temp_dir.path());
1650 let config = Builder::new().suffix(".toml").tempfile()?;
1651 writeln!(
1652 config.as_file(),
1653 r#"
1654[relaychain]
1655chain = "paseo-local"
1656
1657[[parachains]]
1658id = 1000
1659chain = "asset-hub-paseo-local"
1660
1661[[parachains]]
1662id = 4385
1663default_command = "pop-node"
1664
1665[[hrmp_channels]]
1666sender = 4385
1667recipient = 1000
1668max_capacity = 1000
1669max_message_size = 8000
1670"#
1671 )?;
1672
1673 let zombienet =
1674 Zombienet::new(&cache, config.path().try_into()?, None, None, None, None, None)
1675 .await?;
1676
1677 assert!(zombienet.hrmp_channels());
1678 Ok(())
1679 }
1680
1681 #[tokio::test]
1682 async fn new_handles_missing_binary() -> Result<()> {
1683 let temp_dir = tempdir()?;
1684 let cache = PathBuf::from(temp_dir.path());
1685 let config = Builder::new().suffix(".toml").tempfile()?;
1686 writeln!(
1687 config.as_file(),
1688 r#"
1689[relaychain]
1690chain = "paseo-local"
1691
1692[[parachains]]
1693id = 404
1694default_command = "missing-binary"
1695"#
1696 )?;
1697
1698 assert!(matches!(
1699 Zombienet::new(&cache, config.path().try_into()?, None, None, None, None, None).await,
1700 Err(Error::MissingBinary(command))
1701 if command == "missing-binary"
1702 ));
1703 Ok(())
1704 }
1705
1706 #[tokio::test]
1707 async fn binaries_works() -> Result<()> {
1708 let temp_dir = tempdir()?;
1709 let cache = PathBuf::from(temp_dir.path());
1710 let config = Builder::new().suffix(".toml").tempfile()?;
1711 writeln!(
1712 config.as_file(),
1713 r#"
1714[relaychain]
1715chain = "paseo-local"
1716
1717[[parachains]]
1718id = 1000
1719chain = "asset-hub-paseo-local"
1720
1721[[parachains]]
1722id = 2000
1723default_command = "./target/release/parachain-template-node"
1724
1725[[parachains]]
1726id = 4385
1727default_command = "pop-node"
1728"#
1729 )?;
1730
1731 let mut zombienet =
1732 Zombienet::new(&cache, config.path().try_into()?, None, None, None, None, None)
1733 .await?;
1734 assert_eq!(zombienet.binaries().count(), 6);
1735 Ok(())
1736 }
1737
1738 #[tokio::test]
1739 async fn binaries_includes_chain_spec_generators() -> Result<()> {
1740 let temp_dir = tempdir()?;
1741 let cache = PathBuf::from(temp_dir.path());
1742 let config = Builder::new().suffix(".toml").tempfile()?;
1743 writeln!(
1744 config.as_file(),
1745 r#"
1746[relaychain]
1747chain = "paseo-local"
1748
1749[[parachains]]
1750id = 1000
1751chain = "asset-hub-paseo-local"
1752"#
1753 )?;
1754
1755 let mut zombienet =
1756 Zombienet::new(&cache, config.path().try_into()?, None, None, None, None, None)
1757 .await?;
1758 assert_eq!(zombienet.binaries().count(), 4);
1759 Ok(())
1760 }
1761
1762 #[tokio::test]
1763 async fn spawn_ensures_relay_chain_binary_exists() -> Result<()> {
1764 let temp_dir = tempdir()?;
1765 let cache = PathBuf::from(temp_dir.path());
1766 let config = Builder::new().suffix(".toml").tempfile()?;
1767 writeln!(
1768 config.as_file(),
1769 r#"
1770[relaychain]
1771chain = "paseo-local"
1772"#
1773 )?;
1774
1775 let mut zombienet =
1776 Zombienet::new(&cache, config.path().try_into()?, None, None, None, None, None)
1777 .await?;
1778 assert!(matches!(
1779 zombienet.spawn().await,
1780 Err(Error::MissingBinary(error))
1781 if error == "polkadot"
1782 ));
1783 Ok(())
1784 }
1785
1786 #[tokio::test]
1787 async fn spawn_ensures_relay_chain_version_set() -> Result<()> {
1788 let temp_dir = tempdir()?;
1789 let cache = PathBuf::from(temp_dir.path());
1790 let config = Builder::new().suffix(".toml").tempfile()?;
1791 writeln!(
1792 config.as_file(),
1793 r#"
1794[relaychain]
1795chain = "paseo-local"
1796"#
1797 )?;
1798 File::create(cache.join("polkadot"))?;
1799
1800 let mut zombienet =
1801 Zombienet::new(&cache, config.path().try_into()?, None, None, None, None, None)
1802 .await?;
1803 let Binary::Source { source, .. } = &mut zombienet.relay_chain.binary else {
1804 panic!("expected binary which needs to be sourced")
1805 };
1806 if let Source::GitHub(ReleaseArchive { tag, .. }) = source.as_mut() {
1807 *tag = None
1808 }
1809 assert!(matches!(
1810 zombienet.spawn().await,
1811 Err(Error::MissingBinary(error))
1812 if error == "Could not determine version for `polkadot` binary",
1813 ));
1814 Ok(())
1815 }
1816
1817 #[tokio::test]
1818 async fn spawn_symlinks_workers() -> Result<()> {
1819 let temp_dir = tempdir()?;
1820 let cache = PathBuf::from(temp_dir.path());
1821 let config = Builder::new().suffix(".toml").tempfile()?;
1822 writeln!(
1823 config.as_file(),
1824 r#"
1825[relaychain]
1826chain = "paseo-local"
1827"#
1828 )?;
1829 File::create(cache.join(format!("polkadot-{RELAY_BINARY_VERSION}")))?;
1830 File::create(cache.join(format!("polkadot-execute-worker-{RELAY_BINARY_VERSION}")))?;
1831 File::create(cache.join(format!("polkadot-prepare-worker-{RELAY_BINARY_VERSION}")))?;
1832
1833 let mut zombienet = Zombienet::new(
1834 &cache,
1835 config.path().try_into()?,
1836 Some(RELAY_BINARY_VERSION),
1837 None,
1838 None,
1839 None,
1840 None,
1841 )
1842 .await?;
1843 assert!(!cache.join("polkadot-execute-worker").exists());
1844 assert!(!cache.join("polkadot-prepare-worker").exists());
1845 let _ = zombienet.spawn().await;
1846 assert!(cache.join("polkadot-execute-worker").exists());
1847 assert!(cache.join("polkadot-prepare-worker").exists());
1848 let _ = zombienet.spawn().await;
1849 Ok(())
1850 }
1851
1852 #[tokio::test]
1853 async fn spawn_works() -> Result<()> {
1854 let temp_dir = tempdir()?;
1855 let cache = PathBuf::from(temp_dir.path());
1856 let config = Builder::new().suffix(".toml").tempfile()?;
1857 writeln!(
1858 config.as_file(),
1859 r#"
1860[relaychain]
1861chain = "paseo-local"
1862
1863[[relaychain.nodes]]
1864name = "alice"
1865validator = true
1866"#
1867 )?;
1868
1869 let mut zombienet =
1870 Zombienet::new(&cache, config.path().try_into()?, None, None, None, None, None)
1871 .await?;
1872 for b in zombienet.binaries() {
1873 b.source(true, &Output, true).await?;
1874 }
1875
1876 zombienet.spawn().await?;
1877 Ok(())
1878 }
1879 }
1880
1881 mod network_config {
1882 use super::{Relay::*, *};
1883 use crate::registry::chains;
1884 use std::{
1885 fs::{File, create_dir_all},
1886 io::Write,
1887 path::PathBuf,
1888 };
1889 use tempfile::{Builder, tempdir};
1890
1891 #[test]
1892 fn initializing_from_file_fails_when_missing() {
1893 assert!(NetworkConfiguration::try_from(PathBuf::new().as_path()).is_err());
1894 }
1895
1896 #[test]
1897 fn initializing_from_file_fails_when_malformed() -> Result<(), Error> {
1898 let config = Builder::new().suffix(".toml").tempfile()?;
1899 writeln!(config.as_file(), "[")?;
1900 assert!(matches!(
1901 NetworkConfiguration::try_from(config.path()),
1902 Err(Error::TomlError(..))
1903 ));
1904 Ok(())
1905 }
1906
1907 #[test]
1908 fn initializing_from_file_fails_when_relaychain_missing() -> Result<(), Error> {
1909 let config = Builder::new().suffix(".toml").tempfile()?;
1910 assert!(matches!(
1911 NetworkConfiguration::try_from(config.path()),
1912 Err(Error::Config(error)) if error == "Relay chain does not exist."
1913 ));
1914 Ok(())
1915 }
1916
1917 #[tokio::test]
1918 async fn initializing_from_file_fails_when_parachain_id_missing() -> Result<()> {
1919 let config = Builder::new().suffix(".toml").tempfile()?;
1920 writeln!(
1921 config.as_file(),
1922 r#"
1923[relaychain]
1924chain = "paseo-local"
1925
1926[[parachains]]
1927"#
1928 )?;
1929
1930 assert!(matches!(
1931 <&Path as TryInto<NetworkConfiguration>>::try_into(config.path()),
1932 Err(Error::Config(error))
1933 if error == "TOML parse error at line 5, column 1\n |\n5 | [[parachains]]\n | ^^^^^^^^^^^^^^\nmissing field `id`\n"
1934 ));
1935 Ok(())
1936 }
1937
1938 #[test]
1939 fn initializes_relay_from_file() -> Result<(), Error> {
1940 let config = Builder::new().suffix(".toml").tempfile()?;
1941 writeln!(
1942 config.as_file(),
1943 r#"
1944 [relaychain]
1945 chain = "paseo-local"
1946 default_command = "polkadot"
1947 [[relaychain.nodes]]
1948 name = "alice"
1949 "#
1950 )?;
1951 let network_config = NetworkConfiguration::try_from(config.path())?;
1952 let relay_chain = network_config.0.relaychain();
1953 assert_eq!("paseo-local", relay_chain.chain().as_str());
1954 assert_eq!(Some("polkadot"), relay_chain.default_command().map(|c| c.as_str()));
1955 let nodes = relay_chain.nodes();
1956 assert_eq!("alice", nodes.first().unwrap().name());
1957 assert!(network_config.0.parachains().is_empty());
1958 Ok(())
1959 }
1960
1961 #[test]
1962 fn initializes_parachains_from_file() -> Result<(), Error> {
1963 let config = Builder::new().suffix(".toml").tempfile()?;
1964 writeln!(
1965 config.as_file(),
1966 r#"
1967 [relaychain]
1968 chain = "paseo-local"
1969 [[parachains]]
1970 id = 2000
1971 default_command = "node"
1972 "#
1973 )?;
1974 let network_config = NetworkConfiguration::try_from(config.path())?;
1975 let parachains = network_config.0.parachains();
1976 let para_2000 = parachains.first().unwrap();
1977 assert_eq!(2000, para_2000.id());
1978 assert_eq!(Some("node"), para_2000.default_command().map(|c| c.as_str()));
1979 Ok(())
1980 }
1981
1982 #[test]
1983 fn initializing_from_network_config_works() -> Result<(), Error> {
1984 let network_config = NetworkConfigBuilder::new()
1985 .with_relaychain(|b| {
1986 b.with_chain("paseo-local").with_validator(|b| b.with_name("alice"))
1987 })
1988 .build()
1989 .unwrap();
1990 let config = NetworkConfiguration::try_from(network_config.clone()).unwrap();
1991 assert_eq!(config, NetworkConfiguration(network_config, Default::default()));
1992 Ok(())
1993 }
1994
1995 #[test]
1996 fn build_works() -> Result<(), Error> {
1997 let port = 9944;
1998 for relay in [Paseo, Kusama, Polkadot, Westend] {
1999 let mut chains: Vec<_> = chains(&relay).to_vec();
2000 chains
2001 .iter_mut()
2002 .enumerate()
2003 .for_each(|(i, chain)| chain.set_port(port + i as u16 + 1));
2004 let relay_chain = relay.chain();
2005
2006 let config =
2007 NetworkConfiguration::build(relay, Some(port), Some(chains.as_slice()))?;
2008
2009 let relay_config = config.0.relaychain();
2010 assert_eq!(relay_config.chain().as_str(), relay_chain);
2011 assert_eq!(
2015 relay_config.nodes().iter().map(|n| n.name()).collect::<Vec<_>>(),
2016 VALIDATORS.into_iter().take(relay_config.nodes().len()).collect::<Vec<_>>()
2017 );
2018 assert_eq!(relay_config.nodes().first().unwrap().rpc_port().unwrap(), port);
2019
2020 let parachains = config.0.parachains();
2021 assert_eq!(parachains.len(), chains.len());
2022 for (i, chain) in chains.iter().enumerate() {
2023 let parachain = parachains.iter().find(|p| p.id() == chain.id()).unwrap();
2024 assert_eq!(parachain.chain().unwrap().as_str(), chain.chain());
2025 assert_eq!(parachain.default_command().unwrap().as_str(), chain.binary());
2026 println!("{} {}", relay_chain, chain.name());
2027 assert_eq!(
2028 parachain.genesis_overrides().is_some(),
2029 chain.genesis_overrides().is_some() ||
2030 chains.iter().any(|r| r
2031 .requires()
2032 .map(|r| r.contains_key(&chain.as_any().type_id()))
2033 .unwrap_or_default())
2034 );
2035 let collators = parachain.collators();
2036 assert_eq!(collators.len(), 1);
2037 let collator = collators.first().unwrap();
2038 assert_eq!(collator.name(), &format!("{}-collator", chain.name()));
2039 assert_eq!(
2040 collator.args().len(),
2041 chain.args().map(|a| a.len()).unwrap_or_default()
2042 );
2043 assert_eq!(collator.rpc_port(), Some(port + i as u16 + 1));
2044 }
2045
2046 let channels = config.0.hrmp_channels();
2048 assert_eq!(channels.len(), chains.len() * (chains.len() - 1));
2049 for chain in chains.iter() {
2050 for other in chains.iter().filter(|r| r.id() != chain.id()) {
2051 assert!(
2052 channels
2053 .iter()
2054 .any(|c| c.sender() == chain.id() && c.recipient() == other.id())
2055 );
2056 assert!(
2057 channels
2058 .iter()
2059 .any(|c| c.sender() == other.id() && c.recipient() == chain.id())
2060 );
2061 }
2062 }
2063 assert!(
2064 channels
2065 .iter()
2066 .all(|c| c.max_capacity() == 1000 && c.max_message_size() == 8000)
2067 );
2068 }
2069 Ok(())
2070 }
2071
2072 #[test]
2073 fn adapt_works() -> Result<(), Error> {
2074 let config = Builder::new().suffix(".toml").tempfile()?;
2075 writeln!(
2076 config.as_file(),
2077 r#"
2078[relaychain]
2079chain = "paseo-local"
2080
2081[[relaychain.nodes]]
2082name = "alice"
2083command = "polkadot"
2084
2085[[relaychain.nodes]]
2086name = "bob"
2087
2088[[parachains]]
2089id = 1000
2090chain = "asset-hub-paseo-local"
2091
2092[[parachains.collators]]
2093name = "asset-hub-1"
2094command = "polkadot-parachain"
2095
2096[[parachains.collators]]
2097name = "asset-hub-2"
2098
2099[[parachains]]
2100id = 2000
2101default_command = "pop-node"
2102
2103[[parachains.collators]]
2104name = "pop"
2105command = "pop-node"
2106
2107[[parachains]]
2108id = 2001
2109default_command = "./target/release/parachain-template-node"
2110
2111[[parachains.collators]]
2112name = "collator-2001"
2113command = "./target/release/parachain-template-node"
2114
2115[[parachains]]
2116id = 2002
2117default_command = "./target/release/parachain-template-node"
2118
2119[parachains.collator]
2120name = "collator-2002"
2121command = "./target/release/parachain-template-node"
2122subcommand = "test"
2123ws_port = 9945
2124rpc_port = 9944
2125"#
2126 )?;
2127 let network_config = NetworkConfiguration::try_from(config.path())?;
2128
2129 let relay_chain_binary = Builder::new().tempfile()?;
2130 let relay_chain = relay_chain_binary.path();
2131 File::create(relay_chain)?;
2132 let system_chain_binary = Builder::new().tempfile()?;
2133 let system_chain = system_chain_binary.path();
2134 File::create(system_chain)?;
2135 let pop_binary = Builder::new().tempfile()?;
2136 let pop = pop_binary.path();
2137 File::create(pop)?;
2138 let parachain_template_node = Builder::new().tempfile()?;
2139 let parachain_template = parachain_template_node.path();
2140 create_dir_all(parachain_template.parent().unwrap())?;
2141 File::create(parachain_template)?;
2142
2143 let adapted = network_config.adapt(
2144 &RelayChain {
2145 runtime: Paseo,
2146 binary: Binary::Local {
2147 name: "polkadot".to_string(),
2148 path: relay_chain.to_path_buf(),
2149 manifest: None,
2150 },
2151 workers: ["polkadot-execute-worker", ""],
2152 chain: "paseo-local".to_string(),
2153 chain_spec_generator: None,
2154 },
2155 &[
2156 (
2157 1000,
2158 Chain {
2159 id: 1000,
2160 binary: Binary::Local {
2161 name: "polkadot-parachain".to_string(),
2162 path: system_chain.to_path_buf(),
2163 manifest: None,
2164 },
2165 chain: None,
2166 chain_spec_generator: None,
2167 },
2168 ),
2169 (
2170 2000,
2171 Chain {
2172 id: 2000,
2173 binary: Binary::Local {
2174 name: "pop-node".to_string(),
2175 path: pop.to_path_buf(),
2176 manifest: None,
2177 },
2178 chain: None,
2179 chain_spec_generator: None,
2180 },
2181 ),
2182 (
2183 2001,
2184 Chain {
2185 id: 2001,
2186 binary: Binary::Local {
2187 name: "parachain-template-node".to_string(),
2188 path: parachain_template.to_path_buf(),
2189 manifest: None,
2190 },
2191 chain: None,
2192 chain_spec_generator: None,
2193 },
2194 ),
2195 (
2196 2002,
2197 Chain {
2198 id: 2002,
2199 binary: Binary::Local {
2200 name: "parachain-template-node".to_string(),
2201 path: parachain_template.to_path_buf(),
2202 manifest: None,
2203 },
2204 chain: None,
2205 chain_spec_generator: None,
2206 },
2207 ),
2208 ]
2209 .into(),
2210 )?;
2211
2212 let contents = adapted.dump_to_toml().unwrap();
2213 assert_eq!(
2214 contents,
2215 format!(
2216 r#"[settings]
2217timeout = 1000
2218node_spawn_timeout = 300
2219tear_down_on_failure = true
2220
2221[relaychain]
2222chain = "paseo-local"
2223default_command = "{0}"
2224
2225[[relaychain.nodes]]
2226name = "alice"
2227validator = true
2228invulnerable = true
2229bootnode = false
2230balance = 2000000000000
2231
2232[[relaychain.nodes]]
2233name = "bob"
2234validator = true
2235invulnerable = true
2236bootnode = false
2237balance = 2000000000000
2238
2239[[parachains]]
2240id = 1000
2241chain = "asset-hub-paseo-local"
2242add_to_genesis = true
2243balance = 2000000000000
2244default_command = "{1}"
2245cumulus_based = true
2246evm_based = false
2247
2248[[parachains.collators]]
2249name = "asset-hub-1"
2250validator = true
2251invulnerable = true
2252bootnode = false
2253balance = 2000000000000
2254
2255[[parachains.collators]]
2256name = "asset-hub-2"
2257validator = true
2258invulnerable = true
2259bootnode = false
2260balance = 2000000000000
2261
2262[[parachains]]
2263id = 2000
2264add_to_genesis = true
2265balance = 2000000000000
2266default_command = "{2}"
2267cumulus_based = true
2268evm_based = false
2269
2270[[parachains.collators]]
2271name = "pop"
2272validator = true
2273invulnerable = true
2274bootnode = false
2275balance = 2000000000000
2276
2277[[parachains]]
2278id = 2001
2279add_to_genesis = true
2280balance = 2000000000000
2281default_command = "{3}"
2282cumulus_based = true
2283evm_based = false
2284
2285[[parachains.collators]]
2286name = "collator-2001"
2287validator = true
2288invulnerable = true
2289bootnode = false
2290balance = 2000000000000
2291
2292[[parachains]]
2293id = 2002
2294add_to_genesis = true
2295balance = 2000000000000
2296default_command = "{3}"
2297cumulus_based = true
2298evm_based = false
2299
2300[[parachains.collators]]
2301name = "collator-2002"
2302subcommand = "test"
2303validator = true
2304invulnerable = true
2305bootnode = false
2306balance = 2000000000000
2307ws_port = 9945
2308rpc_port = 9944
2309"#,
2310 relay_chain.canonicalize()?.to_str().unwrap(),
2311 system_chain.canonicalize()?.to_str().unwrap(),
2312 pop.canonicalize()?.to_str().unwrap(),
2313 parachain_template.canonicalize()?.to_str().unwrap()
2314 )
2315 );
2316 Ok(())
2317 }
2318
2319 #[test]
2320 fn adapt_with_chain_spec_generator_works() -> Result<(), Error> {
2321 let config = Builder::new().suffix(".toml").tempfile()?;
2322 writeln!(
2323 config.as_file(),
2324 r#"
2325[relaychain]
2326chain = "paseo-local"
2327
2328[[relaychain.nodes]]
2329name = "alice"
2330command = "polkadot"
2331
2332[[parachains]]
2333id = 1000
2334chain = "asset-hub-paseo-local"
2335
2336[[parachains.collators]]
2337name = "asset-hub"
2338command = "polkadot-parachain"
2339
2340"#
2341 )?;
2342 let network_config = NetworkConfiguration::try_from(config.path())?;
2343
2344 let relay_chain_binary = Builder::new().tempfile()?;
2345 let relay_chain = relay_chain_binary.path();
2346 File::create(relay_chain)?;
2347 let relay_chain_spec_generator = Builder::new().tempfile()?;
2348 let relay_chain_spec_generator = relay_chain_spec_generator.path();
2349 File::create(relay_chain_spec_generator)?;
2350 let system_chain_binary = Builder::new().tempfile()?;
2351 let system_chain = system_chain_binary.path();
2352 File::create(system_chain)?;
2353 let system_chain_spec_generator = Builder::new().tempfile()?;
2354 let system_chain_spec_generator = system_chain_spec_generator.path();
2355 File::create(system_chain_spec_generator)?;
2356
2357 let adapted = network_config.adapt(
2358 &RelayChain {
2359 runtime: Paseo,
2360 binary: Binary::Local {
2361 name: "polkadot".to_string(),
2362 path: relay_chain.to_path_buf(),
2363 manifest: None,
2364 },
2365 workers: ["polkadot-execute-worker", ""],
2366 chain: "paseo-local".to_string(),
2367 chain_spec_generator: Some(Binary::Local {
2368 name: "paseo-chain-spec-generator".to_string(),
2369 path: relay_chain_spec_generator.to_path_buf(),
2370 manifest: None,
2371 }),
2372 },
2373 &[(
2374 1000,
2375 Chain {
2376 id: 1000,
2377 binary: Binary::Local {
2378 name: "polkadot-parachain".to_string(),
2379 path: system_chain.to_path_buf(),
2380 manifest: None,
2381 },
2382 chain: Some("asset-hub-paseo-local".to_string()),
2383 chain_spec_generator: Some(Binary::Local {
2384 name: "paseo-chain-spec-generator".to_string(),
2385 path: system_chain_spec_generator.to_path_buf(),
2386 manifest: None,
2387 }),
2388 },
2389 )]
2390 .into(),
2391 )?;
2392
2393 let contents = adapted.dump_to_toml().unwrap();
2394 assert_eq!(
2395 contents,
2396 format!(
2397 r#"[settings]
2398timeout = 1000
2399node_spawn_timeout = 300
2400tear_down_on_failure = true
2401
2402[relaychain]
2403chain = "paseo-local"
2404default_command = "{0}"
2405chain_spec_command = "{1} {2}"
2406
2407[[relaychain.nodes]]
2408name = "alice"
2409validator = true
2410invulnerable = true
2411bootnode = false
2412balance = 2000000000000
2413
2414[[parachains]]
2415id = 1000
2416chain = "asset-hub-paseo-local"
2417add_to_genesis = true
2418balance = 2000000000000
2419default_command = "{3}"
2420chain_spec_command = "{4} {2}"
2421cumulus_based = true
2422evm_based = false
2423
2424[[parachains.collators]]
2425name = "asset-hub"
2426validator = true
2427invulnerable = true
2428bootnode = false
2429balance = 2000000000000
2430"#,
2431 relay_chain.canonicalize()?.to_str().unwrap(),
2432 relay_chain_spec_generator.canonicalize()?.to_str().unwrap(),
2433 "{{chainName}}",
2434 system_chain.canonicalize()?.to_str().unwrap(),
2435 system_chain_spec_generator.canonicalize()?.to_str().unwrap(),
2436 )
2437 );
2438 Ok(())
2439 }
2440
2441 #[test]
2442 fn adapt_with_hrmp_channels_works() -> Result<(), Error> {
2443 let config = Builder::new().suffix(".toml").tempfile()?;
2444 writeln!(
2445 config.as_file(),
2446 r#"
2447[relaychain]
2448chain = "paseo-local"
2449
2450[[relaychain.nodes]]
2451name = "alice"
2452
2453[[parachains]]
2454id = 1000
2455chain = "asset-hub-paseo-local"
2456
2457[[parachains.collators]]
2458name = "asset-hub"
2459
2460[[parachains]]
2461id = 2000
2462default_command = "pop-node"
2463
2464[[parachains.collators]]
2465name = "pop"
2466
2467[[hrmp_channels]]
2468sender = 1000
2469recipient = 2000
2470max_capacity = 1000
2471max_message_size = 5000
2472
2473[[hrmp_channels]]
2474sender = 2000
2475recipient = 1000
2476max_capacity = 1000
2477max_message_size = 8000
2478
2479"#
2480 )?;
2481 let network_config = NetworkConfiguration::try_from(config.path())?;
2482
2483 let relay_chain_binary = Builder::new().tempfile()?;
2484 let relay_chain = relay_chain_binary.path();
2485 File::create(relay_chain)?;
2486 let system_chain_binary = Builder::new().tempfile()?;
2487 let system_chain = system_chain_binary.path();
2488 File::create(system_chain)?;
2489 let pop_binary = Builder::new().tempfile()?;
2490 let pop = pop_binary.path();
2491 File::create(pop)?;
2492
2493 let adapted = network_config.adapt(
2494 &RelayChain {
2495 runtime: Paseo,
2496 binary: Binary::Local {
2497 name: "polkadot".to_string(),
2498 path: relay_chain.to_path_buf(),
2499 manifest: None,
2500 },
2501 workers: ["polkadot-execute-worker", ""],
2502 chain: "paseo-local".to_string(),
2503 chain_spec_generator: None,
2504 },
2505 &[
2506 (
2507 1000,
2508 Chain {
2509 id: 1000,
2510 binary: Binary::Local {
2511 name: "polkadot-parachain".to_string(),
2512 path: system_chain.to_path_buf(),
2513 manifest: None,
2514 },
2515 chain: Some("asset-hub-paseo-local".to_string()),
2516 chain_spec_generator: None,
2517 },
2518 ),
2519 (
2520 2000,
2521 Chain {
2522 id: 2000,
2523 binary: Binary::Local {
2524 name: "pop-node".to_string(),
2525 path: pop.to_path_buf(),
2526 manifest: None,
2527 },
2528 chain: None,
2529 chain_spec_generator: None,
2530 },
2531 ),
2532 ]
2533 .into(),
2534 )?;
2535
2536 let contents = adapted.dump_to_toml().unwrap();
2537 assert_eq!(
2538 contents,
2539 format!(
2540 r#"[settings]
2541timeout = 1000
2542node_spawn_timeout = 300
2543tear_down_on_failure = true
2544
2545[relaychain]
2546chain = "paseo-local"
2547default_command = "{0}"
2548
2549[[relaychain.nodes]]
2550name = "alice"
2551validator = true
2552invulnerable = true
2553bootnode = false
2554balance = 2000000000000
2555
2556[[parachains]]
2557id = 1000
2558chain = "asset-hub-paseo-local"
2559add_to_genesis = true
2560balance = 2000000000000
2561default_command = "{1}"
2562cumulus_based = true
2563evm_based = false
2564
2565[[parachains.collators]]
2566name = "asset-hub"
2567validator = true
2568invulnerable = true
2569bootnode = false
2570balance = 2000000000000
2571
2572[[parachains]]
2573id = 2000
2574add_to_genesis = true
2575balance = 2000000000000
2576default_command = "{2}"
2577cumulus_based = true
2578evm_based = false
2579
2580[[parachains.collators]]
2581name = "pop"
2582validator = true
2583invulnerable = true
2584bootnode = false
2585balance = 2000000000000
2586
2587[[hrmp_channels]]
2588sender = 1000
2589recipient = 2000
2590max_capacity = 1000
2591max_message_size = 5000
2592
2593[[hrmp_channels]]
2594sender = 2000
2595recipient = 1000
2596max_capacity = 1000
2597max_message_size = 8000
2598"#,
2599 relay_chain.canonicalize()?.to_str().unwrap(),
2600 system_chain.canonicalize()?.to_str().unwrap(),
2601 pop.canonicalize()?.to_str().unwrap(),
2602 )
2603 );
2604 Ok(())
2605 }
2606
2607 #[test]
2608 fn adapt_with_chain_spec_works() -> Result<(), Error> {
2609 let config = Builder::new().suffix(".toml").tempfile()?;
2610 writeln!(
2611 config.as_file(),
2612 r#"
2613[relaychain]
2614chain = "paseo-local"
2615chain_spec_command = "cmd_template"
2616chain_spec_command_is_local = true
2617chain_spec_path = "./path/to/paseo-local.spec.json"
2618
2619[[relaychain.nodes]]
2620name = "alice"
2621
2622[[parachains]]
2623id = 1000
2624chain = "asset-hub-paseo-local"
2625chain_spec_command = "cmd_template"
2626chain_spec_command_is_local = true
2627chain_spec_path = "./path/to/asset-hub-paseo-local.spec.json"
2628
2629[[parachains.collators]]
2630name = "asset-hub"
2631"#
2632 )?;
2633 let network_config = NetworkConfiguration::try_from(config.path())?;
2634
2635 let relay_chain_binary = Builder::new().tempfile()?;
2636 let relay_chain = relay_chain_binary.path();
2637 File::create(relay_chain)?;
2638 let system_chain_binary = Builder::new().tempfile()?;
2639 let system_chain = system_chain_binary.path();
2640 File::create(system_chain)?;
2641 let pop_binary = Builder::new().tempfile()?;
2642 let pop = pop_binary.path();
2643 File::create(pop)?;
2644
2645 let adapted = network_config.adapt(
2646 &RelayChain {
2647 runtime: Paseo,
2648 binary: Binary::Local {
2649 name: "polkadot".to_string(),
2650 path: relay_chain.to_path_buf(),
2651 manifest: None,
2652 },
2653 workers: ["polkadot-execute-worker", ""],
2654 chain: "paseo-local".to_string(),
2655 chain_spec_generator: None,
2656 },
2657 &[(
2658 1000,
2659 Chain {
2660 id: 1000,
2661 binary: Binary::Local {
2662 name: "polkadot-parachain".to_string(),
2663 path: system_chain.to_path_buf(),
2664 manifest: None,
2665 },
2666 chain: Some("asset-hub-paseo-local".to_string()),
2667 chain_spec_generator: None,
2668 },
2669 )]
2670 .into(),
2671 )?;
2672
2673 let contents = adapted.dump_to_toml().unwrap();
2674 assert_eq!(
2675 contents,
2676 format!(
2677 r#"[settings]
2678timeout = 1000
2679node_spawn_timeout = 300
2680tear_down_on_failure = true
2681
2682[relaychain]
2683chain = "paseo-local"
2684default_command = "{0}"
2685chain_spec_path = "./path/to/paseo-local.spec.json"
2686chain_spec_command = "cmd_template"
2687chain_spec_command_is_local = true
2688
2689[[relaychain.nodes]]
2690name = "alice"
2691validator = true
2692invulnerable = true
2693bootnode = false
2694balance = 2000000000000
2695
2696[[parachains]]
2697id = 1000
2698chain = "asset-hub-paseo-local"
2699add_to_genesis = true
2700balance = 2000000000000
2701default_command = "{1}"
2702chain_spec_path = "./path/to/asset-hub-paseo-local.spec.json"
2703chain_spec_command = "cmd_template"
2704chain_spec_command_is_local = true
2705cumulus_based = true
2706evm_based = false
2707
2708[[parachains.collators]]
2709name = "asset-hub"
2710validator = true
2711invulnerable = true
2712bootnode = false
2713balance = 2000000000000
2714"#,
2715 relay_chain.canonicalize()?.to_str().unwrap(),
2716 system_chain.canonicalize()?.to_str().unwrap(),
2717 )
2718 );
2719 Ok(())
2720 }
2721
2722 #[test]
2723 fn adapt_with_overrides_works() -> Result<(), Error> {
2724 let config = Builder::new().suffix(".toml").tempfile()?;
2725 writeln!(
2726 config.as_file(),
2727 r#"
2728[relaychain]
2729chain = "paseo-local"
2730wasm_override = "./path/to/paseo-local.wasm"
2731
2732[[relaychain.nodes]]
2733name = "alice"
2734
2735[relaychain.genesis.balances]
2736balances = [["5Ec4AhPKXY9B4ayGshkz2wFMh7N8gP7XKfAvtt1cigpG9FkJ", 420000000000]]
2737
2738[[parachains]]
2739id = 1000
2740chain = "asset-hub-paseo-local"
2741wasm_override = "./path/to/asset-hub-paseo-local.wasm"
2742
2743[[parachains.collators]]
2744name = "asset-hub"
2745
2746[parachains.genesis.balances]
2747balances = [["5Ec4AhPKXY9B4ayGshkz2wFMh7N8gP7XKfAvtt1cigpG9FkJ", 420000000000]]
2748
2749"#
2750 )?;
2751 let network_config = NetworkConfiguration::try_from(config.path())?;
2752
2753 let relay_chain_binary = Builder::new().tempfile()?;
2754 let relay_chain = relay_chain_binary.path();
2755 File::create(relay_chain)?;
2756 let system_chain_binary = Builder::new().tempfile()?;
2757 let system_chain = system_chain_binary.path();
2758 File::create(system_chain)?;
2759 let pop_binary = Builder::new().tempfile()?;
2760 let pop = pop_binary.path();
2761 File::create(pop)?;
2762
2763 let adapted = network_config.adapt(
2764 &RelayChain {
2765 runtime: Paseo,
2766 binary: Binary::Local {
2767 name: "polkadot".to_string(),
2768 path: relay_chain.to_path_buf(),
2769 manifest: None,
2770 },
2771 workers: ["polkadot-execute-worker", ""],
2772 chain: "paseo-local".to_string(),
2773 chain_spec_generator: None,
2774 },
2775 &[(
2776 1000,
2777 Chain {
2778 id: 1000,
2779 binary: Binary::Local {
2780 name: "polkadot-parachain".to_string(),
2781 path: system_chain.to_path_buf(),
2782 manifest: None,
2783 },
2784 chain: Some("asset-hub-paseo-local".to_string()),
2785 chain_spec_generator: None,
2786 },
2787 )]
2788 .into(),
2789 )?;
2790
2791 let contents = adapted.dump_to_toml().unwrap();
2792 assert_eq!(
2793 contents,
2794 format!(
2795 r#"[settings]
2796timeout = 1000
2797node_spawn_timeout = 300
2798tear_down_on_failure = true
2799
2800[relaychain]
2801chain = "paseo-local"
2802default_command = "{0}"
2803wasm_override = "./path/to/paseo-local.wasm"
2804
2805[[relaychain.nodes]]
2806name = "alice"
2807validator = true
2808invulnerable = true
2809bootnode = false
2810balance = 2000000000000
2811
2812[relaychain.genesis.balances]
2813balances = [[
2814 "5Ec4AhPKXY9B4ayGshkz2wFMh7N8gP7XKfAvtt1cigpG9FkJ",
2815 {{ "$serde_json::private::Number" = "420000000000" }},
2816]]
2817
2818[[parachains]]
2819id = 1000
2820chain = "asset-hub-paseo-local"
2821add_to_genesis = true
2822balance = 2000000000000
2823default_command = "{1}"
2824wasm_override = "./path/to/asset-hub-paseo-local.wasm"
2825cumulus_based = true
2826evm_based = false
2827
2828[parachains.genesis.balances]
2829balances = [[
2830 "5Ec4AhPKXY9B4ayGshkz2wFMh7N8gP7XKfAvtt1cigpG9FkJ",
2831 {{ "$serde_json::private::Number" = "420000000000" }},
2832]]
2833
2834[[parachains.collators]]
2835name = "asset-hub"
2836validator = true
2837invulnerable = true
2838bootnode = false
2839balance = 2000000000000
2840"#,
2841 relay_chain.canonicalize()?.to_str().unwrap(),
2842 system_chain.canonicalize()?.to_str().unwrap(),
2843 )
2844 );
2845 Ok(())
2846 }
2847
2848 #[test]
2849 fn resolves_path() -> Result<(), Error> {
2850 let working_dir = tempdir()?;
2851 let path = working_dir.path().join("./target/release/node");
2852 assert!(
2853 matches!(NetworkConfiguration::resolve_path(&path), Err(Error::Config(message))
2854 if message == format!("the canonical path of {:?} could not be resolved", path)
2855 )
2856 );
2857
2858 create_dir_all(path.parent().unwrap())?;
2859 File::create(&path)?;
2860 assert_eq!(
2861 NetworkConfiguration::resolve_path(&path)?,
2862 path.canonicalize()?.to_str().unwrap().to_string()
2863 );
2864 Ok(())
2865 }
2866 }
2867
2868 mod parachain {
2869 use super::*;
2870 use pop_common::sourcing::GitHub::SourceCodeArchive;
2871 use std::path::PathBuf;
2872
2873 #[test]
2874 fn initializes_from_local_binary() -> Result<(), Error> {
2875 let name = "parachain-template-node";
2876 let command = PathBuf::from("./target/release").join(name);
2877 assert_eq!(
2878 Chain::from_local(2000, command.clone(), Some("dev"))?,
2879 Chain {
2880 id: 2000,
2881 binary: Binary::Local { name: name.to_string(), path: command, manifest: None },
2882 chain: Some("dev".to_string()),
2883 chain_spec_generator: None,
2884 }
2885 );
2886 Ok(())
2887 }
2888
2889 #[test]
2890 fn initializes_from_local_package() -> Result<(), Error> {
2891 let name = "pop-chains";
2892 let command = PathBuf::from("./target/release").join(name);
2893 assert_eq!(
2894 Chain::from_local(2000, command.clone(), Some("dev"))?,
2895 Chain {
2896 id: 2000,
2897 binary: Binary::Local {
2898 name: name.to_string(),
2899 path: command,
2900 manifest: Some(PathBuf::from("./Cargo.toml"))
2901 },
2902 chain: Some("dev".to_string()),
2903 chain_spec_generator: None,
2904 }
2905 );
2906 Ok(())
2907 }
2908
2909 #[test]
2910 fn initializes_from_git() -> Result<(), Error> {
2911 let repo = Repository::parse("https://git.com/r0gue-io/pop-node#v1.0")?;
2912 let cache = tempdir()?;
2913 assert_eq!(
2914 Chain::from_repository(2000, &repo, Some("dev"), cache.path())?,
2915 Chain {
2916 id: 2000,
2917 binary: Binary::Source {
2918 name: "pop-node".to_string(),
2919 source: Git {
2920 url: repo.url,
2921 reference: repo.reference,
2922 manifest: None,
2923 package: "pop-node".to_string(),
2924 artifacts: vec!["pop-node".to_string()],
2925 }
2926 .into(),
2927 cache: cache.path().to_path_buf(),
2928 },
2929 chain: Some("dev".to_string()),
2930 chain_spec_generator: None,
2931 }
2932 );
2933 Ok(())
2934 }
2935
2936 #[test]
2937 fn initializes_from_github() -> Result<(), Error> {
2938 let repo = Repository::parse("https://github.com/r0gue-io/pop-node#v1.0")?;
2939 let cache = tempdir()?;
2940 assert_eq!(
2941 Chain::from_repository(2000, &repo, Some("dev"), cache.path())?,
2942 Chain {
2943 id: 2000,
2944 binary: Binary::Source {
2945 name: "pop-node".to_string(),
2946 source: Source::GitHub(SourceCodeArchive {
2947 owner: "r0gue-io".to_string(),
2948 repository: "pop-node".to_string(),
2949 reference: Some("v1.0".to_string()),
2950 manifest: None,
2951 package: "pop-node".to_string(),
2952 artifacts: vec!["pop-node".to_string()],
2953 })
2954 .into(),
2955 cache: cache.path().to_path_buf(),
2956 },
2957 chain: Some("dev".to_string()),
2958 chain_spec_generator: None,
2959 },
2960 );
2961 Ok(())
2962 }
2963 }
2964
2965 #[test]
2966 fn resolve_manifest_works() -> Result<()> {
2967 let current_dir = current_dir()?;
2968 assert_eq!(
2970 current_dir.join("Cargo.toml"),
2971 resolve_manifest("pop-chains", ¤t_dir)?.unwrap()
2972 );
2973 assert_eq!(
2975 current_dir.join("../../Cargo.toml").canonicalize()?,
2976 resolve_manifest("pop-cli", ¤t_dir)?.unwrap()
2977 );
2978 Ok(())
2979 }
2980}