feat: Vendor pezkuwi-subxt and pezkuwi-zombienet-sdk into monorepo
- Add pezkuwi-subxt crates to vendor/pezkuwi-subxt - Add pezkuwi-zombienet-sdk crates to vendor/pezkuwi-zombienet-sdk - Convert git dependencies to path dependencies - Add vendor crates to workspace members - Remove test/example crates from vendor (not needed for SDK) - Fix feature propagation issues detected by zepter - Fix workspace inheritance for internal dependencies - All 606 crates now in workspace - All 6919 internal dependency links verified correct - No git dependencies remaining
This commit is contained in:
+112
@@ -0,0 +1,112 @@
|
||||
# Mechanism to call Rust code from Javascript/Typescript
|
||||
|
||||
### Status: proposed | rejected | **accepted** | deprecated
|
||||
|
||||
### Deciders: [@pepoviola](https://github.com/pepoviola) [@wirednkod](https://github.com/wirednkod) [@l0r1s](https://github.com/l0r1s)
|
||||
|
||||
### Creation date: 18/05/2023
|
||||
|
||||
### Update date: -
|
||||
|
||||
---
|
||||
|
||||
## Context and Problem Statement
|
||||
|
||||
The `zombienet-sdk` will be developed in Rust. Our objective is make it easily integrable into existing Typescript/Javascript project. To achieve this goal, we need to find a way to call the Rust code from a Javascript/Typescript program.
|
||||
|
||||
Many mechanisms exists for this purpose like Wasm or N(ode)-API but some may or may not fit our use case, for example, executing async code.
|
||||
|
||||
---
|
||||
|
||||
## Decision drivers
|
||||
|
||||
- We can use the standard library (for filesystem or networking in providers).
|
||||
|
||||
- We can execute asynchronous code: our goal is not to make the program fully sequential as many operations (e.g: bootstrapping the relaychain nodes) can be done concurrently.
|
||||
|
||||
- Easy to package and deploy
|
||||
|
||||
---
|
||||
|
||||
## Considered Options
|
||||
|
||||
- #### WASM
|
||||
|
||||
- [wasm-pack](https://github.com/neon-bindings/neon)
|
||||
|
||||
- #### Native node modules (Node-API / V8 / libuv)
|
||||
- [napi-rs](https://github.com/napi-rs/napi-rs)
|
||||
|
||||
---
|
||||
|
||||
## Prototyping
|
||||
|
||||
To demonstrate and learn which options fit the best for our use case, we will create a small test program which will have the following functionalities:
|
||||
|
||||
- Has a function taking an arbitratry object and a callback as parameters in the Typescript code, calling the callback with the function result on Rust side.
|
||||
- Has a function taking an arbitrary object as parameter and a returning a promise in Typescript, signaling an asynchronous operation on Rust side.
|
||||
- Make an HTTP request asynchronously in the Rust code, using a dependency using the standard library.
|
||||
|
||||
The prototype assume versions of `rustc` and `cargo` to be `1.69.0`, use of `stable` channel and `Linux` on `amd64` architecture.
|
||||
|
||||
|
||||
- ### [Boilerplate app to execute prototype](boilerplate-app-prototype.md)
|
||||
|
||||
- ### [Wasm-pack prototype](wasm-prototype.md)
|
||||
|
||||
- ### [Napi-rs prototype](napi-prototype.md)
|
||||
|
||||
---
|
||||
|
||||
## Pros and cons of each options
|
||||
|
||||
- ### Napi-rs
|
||||
- Pros 👍
|
||||
- Support many types correctly including typed callback, typed array, class and all JS primitives types (Null, Undefined, Numbers, String, BigInt, ...)
|
||||
|
||||
- Support top level async function because it detects if it needs to be run inside an async runtime (tokio by default)
|
||||
|
||||
- Standard library can be used without limitations, including threading, networking, etc...
|
||||
|
||||
- Extremely well documented with examples
|
||||
|
||||
- Provide full Github action pipeline template to compile on all architecture easily
|
||||
|
||||
- Support complex use cases
|
||||
|
||||
- Used by many big names (Prisma, Parcel, Tailwind, Next.js, Bitwarden)
|
||||
|
||||
- Cons 👎
|
||||
- Node-API is not simple for complex use case
|
||||
|
||||
- Bound to NodeJS, if we want to expose the same logic to others languages (Go, C++, Python, ...) we need to wrap the Rust code inside a dynamic library and adapt to others languages primitives by creating a small adapter over the library
|
||||
|
||||
- Not universally compiled
|
||||
|
||||
|
||||
- ### Wasm-pack
|
||||
- Pros 👍
|
||||
- Rich ecosystem and developing fast
|
||||
|
||||
- Used in many places across web, backend (Docker supports WASM)
|
||||
|
||||
- Easy to use and distribute
|
||||
|
||||
- Universally compiled and used across languages (if they support WASM execution)
|
||||
|
||||
- Good for simple use case where you do pure function (taking input, returning output, without side effects like writing to filesystem or making networking calls)
|
||||
|
||||
- Cons 👎
|
||||
- Limited in the use of the standard library, can't access networking/filesystem primitives without having to use WASI which is inconsistent across languages/runtimes
|
||||
|
||||
- Only support 32 bits
|
||||
|
||||
- No support for concurrent programming (async/threads), even if we can returns Promise from WASM exposed functions but could see the light in few months (maybe?)
|
||||
|
||||
- wasm-bindgen types are too generic, for example, we return a JsValue but we would like to be more specific for the type
|
||||
|
||||
## Decision outcome
|
||||
|
||||
- ### **Napi-rs** for crates dependant on async, filesystem or networking: *support*, *orchestrator*, *test-runner*, *providers* from [schema](https://github.com/paritytech/zombienet-sdk/issues/22)
|
||||
|
||||
- ### **Wasm-pack** for the rest of the crates: *configuration* from [schema](https://github.com/paritytech/zombienet-sdk/issues/22)
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
## [Back](001-node-to-rust-foreign-function-interface.md)
|
||||
|
||||
## Boilerplate app to execute prototypes
|
||||
|
||||
1. Create the new node app :
|
||||
|
||||
```bash
|
||||
$ mkdir -p ffi-prototype/app && cd ffi-prototype/app && npm init -y
|
||||
```
|
||||
|
||||
2. Install required packages :
|
||||
|
||||
```bash
|
||||
[ffi-prototype/app]$ npm i -D @tsconfig/recommended ts-node typescript
|
||||
```
|
||||
|
||||
3. Add a new script :
|
||||
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"build+exec": "tsc && node ./index.js"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
4. Add tsconfig.json
|
||||
```json
|
||||
{
|
||||
"extends": "@tsconfig/recommended/tsconfig.json"
|
||||
}
|
||||
```
|
||||
Vendored
+142
@@ -0,0 +1,142 @@
|
||||
## [Back](001-node-to-rust-foreign-function-interface.md)
|
||||
|
||||
## Napi-rs prototype
|
||||
___
|
||||
|
||||
1. Install the napi CLI
|
||||
|
||||
```bash
|
||||
[ffi-prototype]$ npm install -g @napi-rs/cli
|
||||
```
|
||||
|
||||
2. Create a new napi project
|
||||
|
||||
```bash
|
||||
[ffi-prototype]$ napi new napi-prototype
|
||||
```
|
||||
|
||||
3. Install cargo dependencies
|
||||
|
||||
```bash
|
||||
[ffi-prototype/napi-prototype]$ cargo add tokio --features full
|
||||
[ffi-prototype/napi-prototype]$ cargo add reqwest --features blocking
|
||||
[ffi-prototype/napi-prototype]$ cargo add napi --no-default-features --features napi4,async
|
||||
```
|
||||
|
||||
4. Copy the following code to `napi-prototype/src/lib.rs`
|
||||
|
||||
```rust
|
||||
#![deny(clippy::all)]
|
||||
|
||||
use std::thread;
|
||||
|
||||
use napi::{
|
||||
bindgen_prelude::*,
|
||||
threadsafe_function::{
|
||||
ErrorStrategy, ThreadSafeCallContext, ThreadsafeFunction, ThreadsafeFunctionCallMode,
|
||||
},
|
||||
};
|
||||
use reqwest;
|
||||
|
||||
#[macro_use]
|
||||
extern crate napi_derive;
|
||||
|
||||
// native async with tokio is supported without annotating a main function
|
||||
#[napi]
|
||||
pub async fn fetch_promise() -> Result<String> {
|
||||
let body = reqwest::get("https://paritytech.github.io/zombienet/")
|
||||
.await
|
||||
.map_err(|_| napi::Error::from_reason("Error while fetching page"))?
|
||||
.text()
|
||||
.await
|
||||
.map_err(|_| napi::Error::from_reason("Error while extracting body"))?;
|
||||
|
||||
Ok(body)
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn fetch_callback(callback: JsFunction) -> Result<()> {
|
||||
// createa thread safe callback from the JsFunction
|
||||
let thread_safe_callback: ThreadsafeFunction<String, ErrorStrategy::CalleeHandled> = callback
|
||||
.create_threadsafe_function(0, |ctx: ThreadSafeCallContext<String>| {
|
||||
ctx.env.create_string(&ctx.value).map(|s| vec![s])
|
||||
})?;
|
||||
|
||||
// spawn a thread to execute our logic
|
||||
thread::spawn(move || {
|
||||
let response = reqwest::blocking::get("https://paritytech.github.io/zombienet/");
|
||||
|
||||
if response.is_err() {
|
||||
let response = response
|
||||
.map(|_| "".into())
|
||||
.map_err(|_| napi::Error::from_reason("Error while fetching page"));
|
||||
|
||||
// error are returned by calling the callback with an empty response and the error mapped
|
||||
return thread_safe_callback.call(response, ThreadsafeFunctionCallMode::Blocking);
|
||||
}
|
||||
|
||||
let body = response.unwrap().text();
|
||||
|
||||
if body.is_err() {
|
||||
let body = body
|
||||
.map(|_| "".into())
|
||||
.map_err(|_| napi::Error::from_reason("Error while extracting body"));
|
||||
|
||||
return thread_safe_callback.call(body, ThreadsafeFunctionCallMode::Blocking);
|
||||
}
|
||||
|
||||
// result is returned as a string
|
||||
thread_safe_callback.call(Ok(body.unwrap()), ThreadsafeFunctionCallMode::Blocking)
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
5. Build the project :
|
||||
```bash
|
||||
[ffi-prototype/napi-prototype]$ npm run build
|
||||
```
|
||||
|
||||
6. Copy artifacts :
|
||||
```bash
|
||||
[ffi-prorotype/napi-prototype]$ mv napi-prototype.linux-x64-gnu.node index.d.ts index.js npm/linux-x64-gnu
|
||||
```
|
||||
|
||||
7. Install package in ```ffi-prototype/app``` :
|
||||
```bash
|
||||
[ffi-prototype/app]$ npm i ../napi-prototype/npm/linux-x64-gnu/
|
||||
```
|
||||
|
||||
8. Copy the following code to the ```ffi-prototype/app/index.ts``` file :
|
||||
|
||||
```ts
|
||||
import { fetchCallback, fetchPromise } from "napi-prototype-linux-x64-gnu";
|
||||
|
||||
(async () => {
|
||||
fetchCallback((_err: any, result: string) => {
|
||||
console.log(`HTTP request through FFI with callback: ${result.length}`);
|
||||
});
|
||||
|
||||
console.log(
|
||||
`HTTP request through FFI with promise ${(await fetchPromise()).length}`
|
||||
);
|
||||
})();
|
||||
```
|
||||
|
||||
9. Build and execute the app :
|
||||
|
||||
```bash
|
||||
[ffi-prototype/app]$ npm run build+exec
|
||||
```
|
||||
|
||||
Expected output:
|
||||
```tty
|
||||
> app@1.0.0 build+exec
|
||||
> tsc && node ./index.js
|
||||
|
||||
HTTP request through FFI with promise 12057
|
||||
HTTP request through FFI with callback: 12057
|
||||
```
|
||||
|
||||
That's it !
|
||||
Vendored
+153
@@ -0,0 +1,153 @@
|
||||
## [Back](001-node-to-rust-foreign-function-interface.md)
|
||||
|
||||
## Wasm-pack prototype
|
||||
___
|
||||
|
||||
1. Install the wasm-pack CLI
|
||||
|
||||
```bash
|
||||
curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
|
||||
```
|
||||
|
||||
2. Create a new wasm-pack project
|
||||
|
||||
```bash
|
||||
[ffi-prototype]$ wasm-pack new wasm-prototype
|
||||
```
|
||||
|
||||
3. Install cargo dependencies
|
||||
```bash
|
||||
[ffi-prototype/wasm-prototype]$ cargo add tokio --features full
|
||||
[ffi-prototype/wasm-prototype]$ cargo add reqwest --features blocking
|
||||
[ffi-prototype/wasm-prototype]$ cargo add wasm-bindgen-futures
|
||||
cargo add js-sys
|
||||
```
|
||||
|
||||
4. Copy the following code to `wasm-prototype/src/lib.rs`
|
||||
```rust
|
||||
mod utils;
|
||||
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
// When the `wee_alloc` feature is enabled, use `wee_alloc` as the global
|
||||
// allocator.
|
||||
#[cfg(feature = "wee_alloc")]
|
||||
#[global_allocator]
|
||||
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub async fn fetch_promise() -> Result<String, JsError> {
|
||||
let body = reqwest::get("https://paritytech.github.io/zombienet/")
|
||||
.await
|
||||
.map_err(|_| JsError::new("Error while fetching page"))?
|
||||
.text()
|
||||
.await
|
||||
.map_err(|_| JsError::new("Error while extracting body"))?;
|
||||
|
||||
Ok(body)
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn fetch_callback(callback: &js_sys::Function) -> Result<JsValue, JsValue> {
|
||||
let this = JsValue::null();
|
||||
|
||||
let response = reqwest::blocking::get("https://paritytech.github.io/zombienet/");
|
||||
|
||||
if response.is_err() {
|
||||
return callback.call2(
|
||||
&this,
|
||||
&JsError::new("Error while fetching page").into(),
|
||||
&JsValue::null(),
|
||||
);
|
||||
}
|
||||
|
||||
let body = response.unwrap().text();
|
||||
|
||||
if body.is_err() {
|
||||
return callback.call2(
|
||||
&this,
|
||||
&JsError::new("Error while extracting body").into(),
|
||||
&JsValue::null(),
|
||||
);
|
||||
}
|
||||
|
||||
Ok(body.unwrap().into())
|
||||
}
|
||||
```
|
||||
|
||||
5. Build the project :
|
||||
```bash
|
||||
[ffi-prototype/wasm-prototype]$ wasm-pack build -t nodejs
|
||||
```
|
||||
|
||||
Error are shown, this is expected because WASM doesn't support networking primitives,
|
||||
as you can see, we removed the thread call from the fetch_callback function because ```JsValue```
|
||||
is using *const u8 under the hood and it's not ```Send``` so can't be passed safely across thread:
|
||||
|
||||
```bash
|
||||
[INFO]: 🎯 Checking for the Wasm target...
|
||||
[INFO]: 🌀 Compiling to Wasm...
|
||||
Compiling mio v0.8.6
|
||||
Compiling parking_lot v0.12.1
|
||||
Compiling serde_json v1.0.96
|
||||
Compiling url v2.3.1
|
||||
error[E0432]: unresolved import `crate::sys::IoSourceState`
|
||||
--> /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/mio-0.8.6/src/io_source.rs:12:5
|
||||
|
|
||||
12 | use crate::sys::IoSourceState;
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^ no `IoSourceState` in `sys`
|
||||
|
||||
error[E0432]: unresolved import `crate::sys::tcp`
|
||||
--> /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/mio-0.8.6/src/net/tcp/listener.rs:15:17
|
||||
|
|
||||
15 | use crate::sys::tcp::{bind, listen, new_for_addr};
|
||||
| ^^^ could not find `tcp` in `sys`
|
||||
|
||||
error[E0432]: unresolved import `crate::sys::tcp`
|
||||
--> /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/mio-0.8.6/src/net/tcp/stream.rs:13:17
|
||||
|
|
||||
13 | use crate::sys::tcp::{connect, new_for_addr};
|
||||
| ^^^ could not find `tcp` in `sys`
|
||||
|
||||
error[E0433]: failed to resolve: could not find `Selector` in `sys`
|
||||
--> /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/mio-0.8.6/src/poll.rs:301:18
|
||||
|
|
||||
301 | sys::Selector::new().map(|selector| Poll {
|
||||
| ^^^^^^^^ could not find `Selector` in `sys`
|
||||
|
||||
error[E0433]: failed to resolve: could not find `event` in `sys`
|
||||
--> /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/mio-0.8.6/src/event/event.rs:24:14
|
||||
|
|
||||
24 | sys::event::token(&self.inner)
|
||||
| ^^^^^ could not find `event` in `sys`
|
||||
|
||||
error[E0433]: failed to resolve: could not find `event` in `sys`
|
||||
--> /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/mio-0.8.6/src/event/event.rs:38:14
|
||||
|
|
||||
38 | sys::event::is_readable(&self.inner)
|
||||
| ^^^^^ could not find `event` in `sys`
|
||||
|
||||
error[E0433]: failed to resolve: could not find `event` in `sys`
|
||||
--> /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/mio-0.8.6/src/event/event.rs:43:14
|
||||
|
|
||||
43 | sys::event::is_writable(&self.inner)
|
||||
| ^^^^^ could not find `event` in `sys`
|
||||
|
||||
error[E0433]: failed to resolve: could not find `event` in `sys`
|
||||
--> /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/mio-0.8.6/src/event/event.rs:68:14
|
||||
|
|
||||
68 | sys::event::is_error(&self.inner)
|
||||
| ^^^^^ could not find `event` in `sys`
|
||||
|
||||
error[E0433]: failed to resolve: could not find `event` in `sys`
|
||||
--> /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/mio-0.8.6/src/event/event.rs:99:14
|
||||
|
|
||||
99 | sys::event::is_read_closed(&self.inner)
|
||||
| ^^^^^ could not find `event` in `sys`
|
||||
|
||||
error[E0433]: failed to resolve: could not find `event` in `sys`
|
||||
--> /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/mio-0.8.6/src/event/event.rs:129:14
|
||||
|
|
||||
129 | sys::event::is_write_closed(&self.inner)
|
||||
| ^^^^^ could not find `event` in `sys`
|
||||
```
|
||||
Reference in New Issue
Block a user