Skip to content

Commit

Permalink
feat: put pjs-rs behind a feature-gate (#216)
Browse files Browse the repository at this point in the history
cc @sandreim thanks for the suggestion :)
  • Loading branch information
pepoviola authored May 16, 2024
1 parent 895735f commit a1374f1
Show file tree
Hide file tree
Showing 9 changed files with 137 additions and 124 deletions.
1 change: 0 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
2 changes: 1 addition & 1 deletion crates/examples/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
5 changes: 4 additions & 1 deletion crates/orchestrator/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,14 @@ 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
configuration = { workspace = true }
support = { workspace = true }
provider = { workspace = true }
prom-metrics-parser = { workspace = true }

[features]
pjs = ["dep:pjs-rs"]
5 changes: 4 additions & 1 deletion crates/orchestrator/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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 {
Expand Down
160 changes: 50 additions & 110 deletions crates/orchestrator/src/network/node.rs
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -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<str>,
args: Vec<serde_json::Value>,
user_types: Option<serde_json::Value>,
) -> Result<PjsResult, anyhow::Error> {
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<Path>,
args: Vec<serde_json::Value>,
user_types: Option<serde_json::Value>,
) -> Result<PjsResult, anyhow::Error> {
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> {
Expand Down Expand Up @@ -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<str>,
args: Vec<serde_json::Value>,
user_types: Option<serde_json::Value>,
) -> Result<PjsResult, anyhow::Error> {
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<std::path::Path>,
args: Vec<serde_json::Value>,
user_types: Option<serde_json::Value>,
) -> Result<PjsResult, anyhow::Error> {
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?)?;
Expand Down Expand Up @@ -227,64 +228,3 @@ impl std::fmt::Debug for NetworkNode {
.finish()
}
}

// Helper methods

fn pjs_build_template(
ws_uri: &str,
content: &str,
args: Vec<serde_json::Value>,
user_types: Option<serde_json::Value>,
) -> 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<ReturnValue, anyhow::Error> {
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"))?
}
72 changes: 72 additions & 0 deletions crates/orchestrator/src/pjs_helper.rs
Original file line number Diff line number Diff line change
@@ -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<serde_json::Value>,
user_types: Option<serde_json::Value>,
) -> 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<ReturnValue, anyhow::Error> {
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<serde_json::Value, String>;
9 changes: 0 additions & 9 deletions crates/orchestrator/src/shared/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<serde_json::Value, String>;
3 changes: 3 additions & 0 deletions crates/sdk/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
4 changes: 3 additions & 1 deletion crates/sdk/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down

0 comments on commit a1374f1

Please sign in to comment.