diff --git a/Cargo.toml b/Cargo.toml index 149f7cc60..a141138bc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,7 +48,6 @@ libp2p = { version = "0.52" } subxt = { version = "0.35.3", features = ["substrate-compat"] } subxt-signer = { version = "0.35.3", features = ["subxt"] } tracing = "0.1.35" -pjs-rs = "0.1.2" kube = "0.87.1" k8s-openapi = "0.20.0" tar = "0.4" diff --git a/crates/examples/Cargo.toml b/crates/examples/Cargo.toml index 40fcf727a..a11ef2746 100644 --- a/crates/examples/Cargo.toml +++ b/crates/examples/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -zombienet-sdk = {workspace = true } +zombienet-sdk = {workspace = true, features = ["pjs"]} tokio = { workspace = true } futures = { workspace = true } subxt = { workspace = true } diff --git a/crates/orchestrator/Cargo.toml b/crates/orchestrator/Cargo.toml index c674ab318..d1bb614f5 100644 --- a/crates/orchestrator/Cargo.toml +++ b/crates/orchestrator/Cargo.toml @@ -28,7 +28,7 @@ subxt = { workspace = true } subxt-signer = { workspace = true } reqwest = { workspace = true } tracing = { workspace = true } -pjs-rs = { workspace = true } +pjs-rs = { version = "0.1.2", optional = true } uuid = { workspace = true } # Zombienet deps @@ -36,3 +36,6 @@ configuration = { workspace = true } support = { workspace = true } provider = { workspace = true } prom-metrics-parser = { workspace = true } + +[features] +pjs = ["dep:pjs-rs"] diff --git a/crates/orchestrator/src/lib.rs b/crates/orchestrator/src/lib.rs index 03e63d115..999d4ce78 100644 --- a/crates/orchestrator/src/lib.rs +++ b/crates/orchestrator/src/lib.rs @@ -6,6 +6,8 @@ mod generators; pub mod network; mod network_helper; mod network_spec; +#[cfg(feature = "pjs")] +pub mod pjs_helper; mod shared; mod spawner; @@ -494,7 +496,8 @@ pub enum ZombieRole { // re-export pub use network::{AddCollatorOptions, AddNodeOptions}; -pub use shared::types::PjsResult; +#[cfg(feature = "pjs")] +pub use pjs_helper::PjsResult; #[cfg(test)] mod tests { diff --git a/crates/orchestrator/src/network/node.rs b/crates/orchestrator/src/network/node.rs index b00666f00..a38cc11d3 100644 --- a/crates/orchestrator/src/network/node.rs +++ b/crates/orchestrator/src/network/node.rs @@ -1,15 +1,14 @@ -use std::{path::Path, sync::Arc, time::Duration}; +use std::{sync::Arc, time::Duration}; use anyhow::anyhow; -use pjs_rs::ReturnValue; use prom_metrics_parser::MetricMap; use provider::DynNode; -use serde_json::json; use subxt::{backend::rpc::RpcClient, OnlineClient}; use tokio::sync::RwLock; -use tracing::trace; -use crate::{network_spec::node::NodeSpec, shared::types::PjsResult}; +use crate::network_spec::node::NodeSpec; +#[cfg(feature = "pjs")] +use crate::pjs_helper::{pjs_build_template, pjs_exec, PjsResult, ReturnValue}; #[derive(Clone)] pub struct NetworkNode { @@ -81,50 +80,6 @@ impl NetworkNode { } } - /// Execute js/ts code inside [pjs_rs] custom runtime. - /// - /// The code will be run in a wrapper similar to the `javascript` developer tab - /// of polkadot.js apps. The returning value is represented as [PjsResult] enum, to allow - /// to communicate that the execution was successful but the returning value can be deserialized as [serde_json::Value]. - pub async fn pjs( - &self, - code: impl AsRef, - args: Vec, - user_types: Option, - ) -> Result { - let code = pjs_build_template(self.ws_uri(), code.as_ref(), args, user_types); - trace!("Code to execute: {code}"); - let value = match pjs_inner(code)? { - ReturnValue::Deserialized(val) => Ok(val), - ReturnValue::CantDeserialize(msg) => Err(msg), - }; - - Ok(value) - } - - /// Execute js/ts file inside [pjs_rs] custom runtime. - /// - /// The content of the file will be run in a wrapper similar to the `javascript` developer tab - /// of polkadot.js apps. The returning value is represented as [PjsResult] enum, to allow - /// to communicate that the execution was successful but the returning value can be deserialized as [serde_json::Value]. - pub async fn pjs_file( - &self, - file: impl AsRef, - args: Vec, - user_types: Option, - ) -> Result { - let content = std::fs::read_to_string(file)?; - let code = pjs_build_template(self.ws_uri(), content.as_ref(), args, user_types); - trace!("Code to execute: {code}"); - - let value = match pjs_inner(code)? { - ReturnValue::Deserialized(val) => Ok(val), - ReturnValue::CantDeserialize(msg) => Err(msg), - }; - - Ok(value) - } - /// Resume the node, this is implemented by resuming the /// actual process (e.g polkadot) with sending `SIGCONT` signal pub async fn resume(&self) -> Result<(), anyhow::Error> { @@ -192,6 +147,52 @@ impl NetworkNode { Ok(self.inner.logs().await?) } + #[cfg(feature = "pjs")] + /// Execute js/ts code inside [pjs_rs] custom runtime. + /// + /// The code will be run in a wrapper similar to the `javascript` developer tab + /// of polkadot.js apps. The returning value is represented as [PjsResult] enum, to allow + /// to communicate that the execution was successful but the returning value can be deserialized as [serde_json::Value]. + pub async fn pjs( + &self, + code: impl AsRef, + args: Vec, + user_types: Option, + ) -> Result { + let code = pjs_build_template(self.ws_uri(), code.as_ref(), args, user_types); + tracing::trace!("Code to execute: {code}"); + let value = match pjs_exec(code)? { + ReturnValue::Deserialized(val) => Ok(val), + ReturnValue::CantDeserialize(msg) => Err(msg), + }; + + Ok(value) + } + + #[cfg(feature = "pjs")] + /// Execute js/ts file inside [pjs_rs] custom runtime. + /// + /// The content of the file will be run in a wrapper similar to the `javascript` developer tab + /// of polkadot.js apps. The returning value is represented as [PjsResult] enum, to allow + /// to communicate that the execution was successful but the returning value can be deserialized as [serde_json::Value]. + pub async fn pjs_file( + &self, + file: impl AsRef, + args: Vec, + user_types: Option, + ) -> Result { + let content = std::fs::read_to_string(file)?; + let code = pjs_build_template(self.ws_uri(), content.as_ref(), args, user_types); + tracing::trace!("Code to execute: {code}"); + + let value = match pjs_exec(code)? { + ReturnValue::Deserialized(val) => Ok(val), + ReturnValue::CantDeserialize(msg) => Err(msg), + }; + + Ok(value) + } + async fn fetch_metrics(&self) -> Result<(), anyhow::Error> { let response = reqwest::get(&self.prometheus_uri).await?; let metrics = prom_metrics_parser::parse(&response.text().await?)?; @@ -227,64 +228,3 @@ impl std::fmt::Debug for NetworkNode { .finish() } } - -// Helper methods - -fn pjs_build_template( - ws_uri: &str, - content: &str, - args: Vec, - user_types: Option, -) -> String { - let types = if let Some(user_types) = user_types { - if let Some(types) = user_types.pointer("/types") { - // if the user_types includes the `types` key use the inner value - types.clone() - } else { - user_types.clone() - } - } else { - // No custom types, just an emtpy json - json!({}) - }; - - let tmpl = format!( - r#" - const {{ util, utilCrypto, keyring, types }} = pjs; - ( async () => {{ - const api = await pjs.api.ApiPromise.create({{ - provider: new pjs.api.WsProvider('{}'), - types: {} - }}); - const _run = async (api, hashing, keyring, types, util, arguments) => {{ - {} - }}; - return await _run(api, utilCrypto, keyring, types, util, {}); - }})() - "#, - ws_uri, - types, - content, - json!(args), - ); - trace!(tmpl = tmpl, "code to execute"); - tmpl -} - -// Since pjs-rs run a custom javascript runtime (using deno_core) we need to -// execute in an isolated thread. -fn pjs_inner(code: String) -> Result { - let rt = tokio::runtime::Builder::new_current_thread() - .enable_all() - .build()?; - - std::thread::spawn(move || { - rt.block_on(async move { - let value = pjs_rs::run_ts_code(code, None).await; - trace!("ts_code return: {:?}", value); - value - }) - }) - .join() - .map_err(|_| anyhow!("[pjs] Thread panicked"))? -} diff --git a/crates/orchestrator/src/pjs_helper.rs b/crates/orchestrator/src/pjs_helper.rs new file mode 100644 index 000000000..ffe7d82db --- /dev/null +++ b/crates/orchestrator/src/pjs_helper.rs @@ -0,0 +1,72 @@ +use anyhow::anyhow; +pub use pjs_rs::ReturnValue; +use serde_json::json; +use tracing::trace; + +pub fn pjs_build_template( + ws_uri: &str, + content: &str, + args: Vec, + user_types: Option, +) -> String { + let types = if let Some(user_types) = user_types { + if let Some(types) = user_types.pointer("/types") { + // if the user_types includes the `types` key use the inner value + types.clone() + } else { + user_types.clone() + } + } else { + // No custom types, just an emtpy json + json!({}) + }; + + let tmpl = format!( + r#" + const {{ util, utilCrypto, keyring, types }} = pjs; + ( async () => {{ + const api = await pjs.api.ApiPromise.create({{ + provider: new pjs.api.WsProvider('{}'), + types: {} + }}); + const _run = async (api, hashing, keyring, types, util, arguments) => {{ + {} + }}; + return await _run(api, utilCrypto, keyring, types, util, {}); + }})() + "#, + ws_uri, + types, + content, + json!(args), + ); + trace!(tmpl = tmpl, "code to execute"); + tmpl +} + +// Since pjs-rs run a custom javascript runtime (using deno_core) we need to +// execute in an isolated thread. +pub fn pjs_exec(code: String) -> Result { + let rt = tokio::runtime::Builder::new_current_thread() + .enable_all() + .build()?; + + std::thread::spawn(move || { + rt.block_on(async move { + let value = pjs_rs::run_ts_code(code, None).await; + trace!("ts_code return: {:?}", value); + value + }) + }) + .join() + .map_err(|_| anyhow!("[pjs] Thread panicked"))? +} + +/// pjs-rs success [Result] type +/// +/// Represent the possible states returned from a succefully call to pjs-rs +/// +/// Ok(value) -> Deserialized return value into a [serde_json::Value] +/// Err(msg) -> Execution of the script finish Ok, but the returned value +/// can't be deserialize into a [serde_json::Value] +pub type PjsResult = Result; diff --git a/crates/orchestrator/src/shared/types.rs b/crates/orchestrator/src/shared/types.rs index 015170f90..a0bc2b367 100644 --- a/crates/orchestrator/src/shared/types.rs +++ b/crates/orchestrator/src/shared/types.rs @@ -75,12 +75,3 @@ pub struct ParachainGenesisArgs { pub validation_code: String, pub parachain: bool, } - -/// pjs-rs success [Result] type -/// -/// Represent the possible states returned from a succefully call to pjs-rs -/// -/// Ok(value) -> Deserialized return value into a [serde_json::Value] -/// Err(msg) -> Execution of the script finish Ok, but the returned value -/// can't be deserialize into a [serde_json::Value] -pub type PjsResult = Result; diff --git a/crates/sdk/Cargo.toml b/crates/sdk/Cargo.toml index 1cf6901db..414a46188 100644 --- a/crates/sdk/Cargo.toml +++ b/crates/sdk/Cargo.toml @@ -30,3 +30,6 @@ tracing-subscriber = "0.3" kube = { workspace = true, features = ["ws", "runtime"] } k8s-openapi = { workspace = true, features = ["v1_27"] } serde_json = {workspace = true } + +[features] +pjs = ["orchestrator/pjs"] diff --git a/crates/sdk/src/lib.rs b/crates/sdk/src/lib.rs index 0e4afe92f..fd61db5ce 100644 --- a/crates/sdk/src/lib.rs +++ b/crates/sdk/src/lib.rs @@ -1,9 +1,11 @@ use async_trait::async_trait; pub use configuration::{NetworkConfig, NetworkConfigBuilder, RegistrationStrategy}; +#[cfg(feature = "pjs")] +pub use orchestrator::pjs_helper::PjsResult; pub use orchestrator::{ errors::OrchestratorError, network::{node::NetworkNode, Network}, - AddCollatorOptions, AddNodeOptions, Orchestrator, PjsResult, + AddCollatorOptions, AddNodeOptions, Orchestrator, }; use provider::{DockerProvider, KubernetesProvider, NativeProvider}; pub use support::fs::local::LocalFileSystem;