diff --git a/Cargo.lock b/Cargo.lock index 43d37f76..5215d346 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4712,6 +4712,8 @@ dependencies = [ "serde_json", "strum 0.26.2", "strum_macros 0.26.4", + "subxt 0.37.0", + "subxt-signer 0.37.0", "symlink", "tar", "tempfile", diff --git a/crates/pop-cli/src/commands/up/parachain.rs b/crates/pop-cli/src/commands/up/parachain.rs index ae9be03e..0d3f4bd9 100644 --- a/crates/pop-cli/src/commands/up/parachain.rs +++ b/crates/pop-cli/src/commands/up/parachain.rs @@ -9,7 +9,7 @@ use cliclack::{ use console::{Emoji, Style, Term}; use duct::cmd; use pop_common::Status; -use pop_parachains::{clear_dmpq, Error, IndexSet, NetworkNode, Zombienet}; +use pop_parachains::{clear_dmpq, Error, IndexSet, NetworkNode, RelayChain, Zombienet}; use std::{path::PathBuf, time::Duration}; use tokio::time::sleep; @@ -149,25 +149,36 @@ impl ZombienetCommand { spinner.stop(result); - // Check for any HRMP specified channels + // Check for any specified channels if zombienet.hrmp_channels() { - let spinner = cliclack::spinner(); - spinner.start("Readying channels..."); - // Allow relay node time to start - tokio::time::sleep(Duration::from_secs(10)).await; - let relay_endpoint = network.relaychain().nodes()[0].client().await?; - let para_ids: Vec<_> = - network.parachains().iter().map(|p| p.para_id()).collect(); - tokio::spawn(async move { - if let Err(e) = clear_dmpq(relay_endpoint, ¶_ids).await { - spinner.stop(""); - outro_cancel(format!("{e}"))?; - } - spinner.stop("Channels ready for initialization."); - tokio::time::sleep(Duration::from_secs(2)).await; - Term::stderr().clear_last_lines(2)?; - Ok::<(), Error>(()) - }); + let relay_chain = zombienet.relay_chain(); + match RelayChain::from(relay_chain) { + None => { + log::error(format!("🚫 Using `{relay_chain}` with HRMP channels is currently unsupported. Please use `paseo-local` or `rococo-local`."))?; + }, + Some(relay_chain) => { + use tokio::time::sleep; + let spinner = cliclack::spinner(); + spinner.start("Connecting to relay chain to prepare channels..."); + // Allow relay node time to start + sleep(Duration::from_secs(10)).await; + spinner.start("Preparing channels..."); + let relay_endpoint = network.relaychain().nodes()[0].client().await?; + let para_ids: Vec<_> = + network.parachains().iter().map(|p| p.para_id()).collect(); + tokio::spawn(async move { + if let Err(e) = + clear_dmpq(relay_chain, relay_endpoint, ¶_ids).await + { + spinner.stop(""); + log::error(format!("🚫 Could not prepare channels: {e}"))?; + return Ok::<(), Error>(()); + } + spinner.stop("Channels successfully prepared for initialization."); + Ok::<(), Error>(()) + }); + }, + } } tokio::signal::ctrl_c().await?; diff --git a/crates/pop-parachains/src/errors.rs b/crates/pop-parachains/src/errors.rs index a97c5eff..11416d51 100644 --- a/crates/pop-parachains/src/errors.rs +++ b/crates/pop-parachains/src/errors.rs @@ -35,6 +35,8 @@ pub enum Error { RustfmtError(std::io::Error), #[error("Template error: {0}")] SourcingError(#[from] pop_common::sourcing::Error), + #[error("Subxt error: {0}")] + SubXtError(#[from] subxt::Error), #[error("Toml error: {0}")] TomlError(#[from] toml_edit::de::Error), #[error("Unsupported command: {0}")] diff --git a/crates/pop-parachains/src/lib.rs b/crates/pop-parachains/src/lib.rs index 5a8a56e2..b7e4699d 100644 --- a/crates/pop-parachains/src/lib.rs +++ b/crates/pop-parachains/src/lib.rs @@ -6,6 +6,7 @@ mod errors; mod generator; mod new_pallet; mod new_parachain; +mod relay; mod templates; mod up; mod utils; @@ -18,9 +19,10 @@ pub use errors::Error; pub use indexmap::IndexSet; pub use new_pallet::{create_pallet_template, TemplatePalletConfig}; pub use new_parachain::instantiate_template_dir; +pub use relay::{clear_dmpq, RelayChain}; pub use templates::{Config, Parachain, Provider}; pub use up::Zombienet; -pub use utils::helpers::{clear_dmpq, is_initial_endowment_valid}; +pub use utils::helpers::is_initial_endowment_valid; pub use utils::pallet_helpers::resolve_pallet_path; /// Information about the Node. External export from Zombienet-SDK. pub use zombienet_sdk::NetworkNode; diff --git a/crates/pop-parachains/src/relay.rs b/crates/pop-parachains/src/relay.rs new file mode 100644 index 00000000..d9111fe3 --- /dev/null +++ b/crates/pop-parachains/src/relay.rs @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: GPL-3.0 + +use crate::Error; +use sp_core::twox_128; +use subxt::{config::BlockHash, ext::sp_core, OnlineClient, PolkadotConfig}; + +/// Clears the DMPQ state for the given parachain IDs. +/// +/// # Arguments +/// * `relay_chain` - The relay chain. +/// * `client` - Client for the network which state is to be modified. +/// * `para_ids` - List of ids to build the keys that will be mutated. +pub async fn clear_dmpq( + relay_chain: RelayChain, + client: OnlineClient, + para_ids: &[u32], +) -> Result { + // Wait for blocks to be produced. + let mut sub = client.blocks().subscribe_finalized().await?; + for _ in 0..2 { + sub.next().await; + } + + // Generate storage keys to be removed + let dmp = twox_128("Dmp".as_bytes()); + let dmp_queues = twox_128("DownwardMessageQueues".as_bytes()); + let dmp_queue_heads = twox_128("DownwardMessageQueueHeads".as_bytes()); + let mut clear_dmq_keys = Vec::>::new(); + for id in para_ids { + let id = id.to_le_bytes(); + // DMP Queue Head + let mut key = dmp.to_vec(); + key.extend(&dmp_queue_heads); + key.extend(sp_core::twox_64(&id)); + key.extend(id); + clear_dmq_keys.push(key); + // DMP Queue + let mut key = dmp.to_vec(); + key.extend(&dmp_queues); + key.extend(sp_core::twox_64(&id)); + key.extend(id); + clear_dmq_keys.push(key); + } + + // Submit calls to remove specified keys + let sudo = subxt_signer::sr25519::dev::alice(); + match relay_chain { + RelayChain::PaseoLocal => { + use paseo_local::{ + runtime_types::paseo_runtime::RuntimeCall::System, system::Call, tx, + }; + let sudo_call = tx().sudo().sudo(System(Call::kill_storage { keys: clear_dmq_keys })); + Ok(client.tx().sign_and_submit_default(&sudo_call, &sudo).await?) + }, + RelayChain::RococoLocal => { + use rococo_local::{ + runtime_types::rococo_runtime::RuntimeCall::System, system::Call, tx, + }; + let sudo_call = tx().sudo().sudo(System(Call::kill_storage { keys: clear_dmq_keys })); + Ok(client.tx().sign_and_submit_default(&sudo_call, &sudo).await?) + }, + } +} + +/// A supported relay chain. +pub enum RelayChain { + /// Paseo. + PaseoLocal, + /// Rococo. + RococoLocal, +} + +impl RelayChain { + /// Attempts to convert a chain identifier into a supported `RelayChain` variant. + /// + /// # Arguments + /// * `id` - The relay chain identifier. + pub fn from(id: &str) -> Option { + match id { + "paseo-local" => Some(RelayChain::PaseoLocal), + "rococo-local" => Some(RelayChain::RococoLocal), + _ => None, + } + } +} + +// subxt metadata --url ws://127.0.0.1:58774 --pallets System,Sudo > paseo-local.scale +#[subxt::subxt(runtime_metadata_path = "./src/utils/artifacts/paseo-local.scale")] +mod paseo_local {} + +// subxt metadata --url ws://127.0.0.1:58774 --pallets System,Sudo > rococo-local.scale +#[subxt::subxt(runtime_metadata_path = "./src/utils/artifacts/rococo-local.scale")] +mod rococo_local {} diff --git a/crates/pop-parachains/src/up/mod.rs b/crates/pop-parachains/src/up/mod.rs index 3dbde95b..4f332ddb 100644 --- a/crates/pop-parachains/src/up/mod.rs +++ b/crates/pop-parachains/src/up/mod.rs @@ -58,7 +58,7 @@ impl Zombienet { // Parse network config let network_config = NetworkConfiguration::from(network_config)?; // Determine relay and parachain requirements based on arguments and config - let relay_chain = Self::relay_chain( + let relay_chain = Self::_relay_chain( relay_chain_version, relay_chain_runtime_version, &network_config, @@ -209,7 +209,7 @@ impl Zombienet { /// * `runtime_version` - The specific runtime version used for the relay chain runtime (`None` will use the latest available version). /// * `network_config` - The network configuration to be used to launch a network. /// * `cache` - The location used for caching binaries. - async fn relay_chain( + async fn _relay_chain( version: Option<&str>, runtime_version: Option<&str>, network_config: &NetworkConfiguration, @@ -267,7 +267,12 @@ impl Zombienet { } } // Otherwise use default - return Ok(relay::default(version, runtime_version, chain, cache).await?); + Ok(relay::default(version, runtime_version, chain, cache).await?) + } + + /// The name of the relay chain. + pub fn relay_chain(&self) -> &str { + &self.relay_chain.chain } /// Whether any HRMP channels are to be pre-opened. diff --git a/crates/pop-parachains/src/utils/helpers.rs b/crates/pop-parachains/src/utils/helpers.rs index 7b20e0cb..26945633 100644 --- a/crates/pop-parachains/src/utils/helpers.rs +++ b/crates/pop-parachains/src/utils/helpers.rs @@ -6,7 +6,6 @@ use std::{ io::{self, stdin, stdout, Write}, path::Path, }; -use subxt::{ext::sp_core, OnlineClient, PolkadotConfig}; pub(crate) fn sanitize(target: &Path) -> Result<(), Error> { if target.exists() { @@ -25,64 +24,6 @@ pub(crate) fn sanitize(target: &Path) -> Result<(), Error> { Ok(()) } -/// Clears the DMPQ state for the given chains. -/// Assumes pallet-sudo is present in the runtime. -/// -/// # Arguments -/// -/// * `client` - Client for the network which state is to be modified. -/// * `para_ids` - List of ids to build the keys that will be mutated. -pub async fn clear_dmpq( - client: OnlineClient, - para_ids: &[u32], -) -> Result<(), Box> { - use subxt_signer::sr25519::dev; - - #[subxt::subxt(runtime_metadata_path = "./src/utils/artifacts/paseo-local.scale")] - mod paseo_local {} - type RuntimeCall = paseo_local::runtime_types::paseo_runtime::RuntimeCall; - - let sudo = dev::alice(); - - // Wait for blocks to be produced. - let mut sub = client.blocks().subscribe_finalized().await.unwrap(); - for _ in 0..2 { - sub.next().await; - } - - let dmp = sp_core::twox_128("Dmp".as_bytes()); - let dmp_queues = sp_core::twox_128("DownwardMessageQueues".as_bytes()); - let dmp_queue_heads = sp_core::twox_128("DownwardMessageQueueHeads".as_bytes()); - - let mut clear_dmq_keys = Vec::>::new(); - for id in para_ids { - let id = id.to_le_bytes(); - // DMP Queue Head - let mut key = dmp.to_vec(); - key.extend(&dmp_queue_heads); - key.extend(sp_core::twox_64(&id)); - key.extend(id); - clear_dmq_keys.push(key); - // DMP Queue - let mut key = dmp.to_vec(); - key.extend(&dmp_queues); - key.extend(sp_core::twox_64(&id)); - key.extend(id); - clear_dmq_keys.push(key); - } - - // Craft calls to dispatch - let kill_storage = - RuntimeCall::System(paseo_local::system::Call::kill_storage { keys: clear_dmq_keys }); - let sudo_call = paseo_local::tx().sudo().sudo(kill_storage); - - // Dispatch and watch tx - let _sudo_call_events = - client.tx().sign_and_submit_then_watch_default(&sudo_call, &sudo).await?; - - Ok(()) -} - /// Check if the initial endowment input by the user is a valid balance. /// /// # Arguments @@ -92,7 +33,8 @@ pub fn is_initial_endowment_valid(initial_endowment: &str) -> bool { initial_endowment.parse::().is_ok() || is_valid_bitwise_left_shift(initial_endowment).is_ok() } -// Auxiliar method to check if the endowment input with a shift left (1u64 << 60) format is valid. + +// Auxiliary method to check if the endowment input with a shift left (1u64 << 60) format is valid. // Parse the self << rhs format and check the shift left operation is valid. fn is_valid_bitwise_left_shift(initial_endowment: &str) -> Result { let v: Vec<&str> = initial_endowment.split(" << ").collect();