chore: deploy eth gateway via fluence cli (#418)

This commit is contained in:
folex 2023-02-08 17:07:38 +07:00 committed by GitHub
parent 6fd0ad6d93
commit 3c933c28fb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 409 additions and 186 deletions

View File

@ -4,3 +4,13 @@ target/
Cargo.lock
**/*.bk
**/*.bak
# recommended by Fluence Labs:
.idea
.fluence
**/node_modules
**/target/
.repl_history
.vscode/settings.json
src/ts/src/aqua
src/js/src/aqua

View File

@ -52,5 +52,22 @@
"logger_enabled": true
}
]
},
"eth-rpc": {
"modules": [
{
"name": "curl_adapter",
"path": "./artifacts/curl_adapter.wasm",
"max_heap_size": "2 MiB",
"mounted_binaries": [["curl", "/usr/bin/curl"]],
"logger_enabled": true
},
{
"name": "eth-rpc",
"path": "./artifacts/eth-rpc.wasm",
"logger_enabled": true,
"max_heap_size": "100 MiB"
}
]
}
}

View File

@ -0,0 +1,13 @@
# yaml-language-server: $schema=../../../.fluence/schemas/service.yaml.json
# Defines a [Marine service](https://fluence.dev/docs/build/concepts/#services), most importantly the modules that the service consists of. For Fluence CLI, **service** - is a directory which contains this config. You can use `fluence service new` command to generate a template for new service
# Documentation: https://github.com/fluencelabs/fluence-cli/tree/main/docs/configs/service.md
version: 0
name: eth_rpc
modules:
facade:
get: ../../../wasm-modules/eth-rpc
curl-adapter:
get: ../../../wasm-modules/curl-adapter

View File

@ -0,0 +1,13 @@
# yaml-language-server: $schema=../../../.fluence/schemas/service.yaml.json
# Defines a [Marine service](https://fluence.dev/docs/build/concepts/#services), most importantly the modules that the service consists of. For Fluence CLI, **service** - is a directory which contains this config. You can use `fluence service new` command to generate a template for new service
# Documentation: https://github.com/fluencelabs/fluence-cli/tree/main/docs/configs/service.md
version: 0
name: ipfs_package
modules:
facade:
get: ../../../wasm-modules/ipfs-cli
ipfs-adapter:
get: ../../../wasm-modules/ipfs-adapter

View File

@ -0,0 +1,13 @@
# yaml-language-server: $schema=../../../.fluence/schemas/service.yaml.json
# Defines a [Marine service](https://fluence.dev/docs/build/concepts/#services), most importantly the modules that the service consists of. For Fluence CLI, **service** - is a directory which contains this config. You can use `fluence service new` command to generate a template for new service
# Documentation: https://github.com/fluencelabs/fluence-cli/tree/main/docs/configs/service.md
version: 0
name: multi_provider_query
modules:
facade:
get: ../../../wasm-modules/multi-provider-query
curl-adapter:
get: ../../../wasm-modules/curl-adapter

View File

@ -0,0 +1,11 @@
# yaml-language-server: $schema=../../../.fluence/schemas/service.yaml.json
# Defines a [Marine service](https://fluence.dev/docs/build/concepts/#services), most importantly the modules that the service consists of. For Fluence CLI, **service** - is a directory which contains this config. You can use `fluence service new` command to generate a template for new service
# Documentation: https://github.com/fluencelabs/fluence-cli/tree/main/docs/configs/service.md
version: 0
name: simple_quorum
modules:
facade:
get: ../../../wasm-modules/simple-quorum

View File

@ -0,0 +1,11 @@
# yaml-language-server: $schema=../../../.fluence/schemas/service.yaml.json
# Defines a [Marine service](https://fluence.dev/docs/build/concepts/#services), most importantly the modules that the service consists of. For Fluence CLI, **service** - is a directory which contains this config. You can use `fluence service new` command to generate a template for new service
# Documentation: https://github.com/fluencelabs/fluence-cli/tree/main/docs/configs/service.md
version: 0
name: utilities
modules:
facade:
get: ../../../wasm-modules/utilities

View File

@ -0,0 +1,11 @@
# yaml-language-server: $schema=.fluence/schemas/fluence-lock.yaml.json
# Defines a lock file for Fluence Project dependencies. When dependencies are installed - their exact versions are saved here.
# Documentation: https://github.com/fluencelabs/fluence-cli/tree/main/docs/configs/fluence-lock.md
version: 0
cargo:
marine: 0.12.5
npm:
"@fluencelabs/aqua": 0.9.1-374

View File

@ -0,0 +1,37 @@
# yaml-language-server: $schema=.fluence/schemas/fluence.yaml.json
# Defines Fluence Project, most importantly - what exactly you want to deploy and how. You can use `fluence init` command to generate a template for new Fluence project
# Documentation: https://github.com/fluencelabs/fluence-cli/tree/main/docs/configs/fluence.md
version: 2
aquaInputPath: src/aqua/main.aqua
dependencies:
npm:
"@fluencelabs/aqua-lib": 0.6.0
"@fluencelabs/spell": 0.0.1
"@fluencelabs/aqua": 0.9.1-374
cargo:
marine: 0.12.5
mrepl: 0.18.8
services:
ipfs_package:
get: ./configs/services/ipfs-package
deploy:
- deployId: default
multi_provider_query:
get: configs/services/multi-provider-query
deploy:
- deployId: default
simple_quorum:
get: configs/services/simple-quorum
deploy:
- deployId: default
utilities:
get: configs/services/utilities
deploy:
- deployId: default
eth_rpc:
get: configs/services/eth-rpc
deploy:
- deployId: default

View File

@ -8,33 +8,31 @@ rm -f artifacts/*.wasm
cd wasm-modules
cd curl-adapter
cargo update --aggressive
marine build --release
cp target/wasm32-wasi/release/curl_adapter.wasm ../../artifacts/
cd ../ipfs-adapter
cargo update --aggressive
marine build --release
cp target/wasm32-wasi/release/ipfs_adapter.wasm ../../artifacts/
cd ../multi-provider-query
cargo update --aggressive
marine build --release
cp target/wasm32-wasi/release/multi_provider_query.wasm ../../artifacts/
cd ../simple-quorum
cargo update --aggressive
marine build --release
cp target/wasm32-wasi/release/simple_quorum.wasm ../../artifacts/
cd ../ipfs-cli
cargo update --aggressive
marine build --release
cp target/wasm32-wasi/release/ipfs_cli.wasm ../../artifacts/
cd ../utilities
cargo update --aggressive
marine build --release
cp target/wasm32-wasi/release/utilities.wasm ../../artifacts/
cd ../eth-rpc
marine build --release
cp target/wasm32-wasi/release/eth_rpc.wasm ../../artifacts/
cd ..

View File

@ -0,0 +1,11 @@
# yaml-language-server: $schema=../../.fluence/schemas/module.yaml.json
# Defines [Marine Module](https://fluence.dev/docs/build/concepts/#modules). For Fluence CLI, **module** - is a directory which contains this config and either a precompiled .wasm Marine module or a source code of the module written in Rust which can be compiled into a .wasm Marine module. You can use `fluence module new` command to generate a template for new module
# Documentation: https://github.com/fluencelabs/fluence-cli/tree/main/docs/configs/module.md
version: 0
type: rust
name: curl_adapter
mountedBinaries:
curl: /usr/bin/curl

View File

@ -1,10 +1,10 @@
[package]
name = "eth-rpc"
name = "eth_rpc"
version = "0.1.0"
edition = "2021"
[[bin]]
name = "eth-rpc"
name = "eth_rpc"
path = "src/main.rs"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

View File

@ -0,0 +1,9 @@
# yaml-language-server: $schema=../../.fluence/schemas/module.yaml.json
# Defines [Marine Module](https://fluence.dev/docs/build/concepts/#modules). For Fluence CLI, **module** - is a directory which contains this config and either a precompiled .wasm Marine module or a source code of the module written in Rust which can be compiled into a .wasm Marine module. You can use `fluence module new` command to generate a template for new module
# Documentation: https://github.com/fluencelabs/fluence-cli/tree/main/docs/configs/module.md
version: 0
type: rust
name: eth_rpc

View File

@ -84,9 +84,20 @@ impl Transport for CurlTransport {
// FIX: if there's a bad uri, the panic kicks in here.
let response: Output =
serde_json::from_value(serde_json::from_slice(response.stdout.as_slice())?)?;
if response.ret_code != 0 {
let stdout = String::from_utf8_lossy(&response.stdout);
let error = if response.error.is_empty() {
stdout.to_string()
} else {
format!("error: {}\nstdout: {stdout}", response.error)
};
return Err(web3::error::Error::Transport(
web3::error::TransportError::Message(format!("error: {}", error)),
));
}
let response = serde_json::from_slice(response.stdout.as_slice())?;
let response: Output = serde_json::from_value(response)?;
let result = match response {
Output::Success(jsonrpc_core::types::Success { result, .. }) => result,

View File

@ -1,3 +1,4 @@
use eyre::eyre;
use marine_rs_sdk::marine;
use serde::{Deserialize, Serialize};
use serde_json::Value;
@ -12,12 +13,13 @@ pub fn eth_call(uri: String, method: &str, json_args: Vec<String>) -> JsonString
let result: eyre::Result<Value> = try {
let rt = Builder::new_current_thread().build()?;
let args: Result<Vec<Value>, _> = json_args
.into_iter()
.map(|a| serde_json::from_str(&a))
.collect();
let args: Result<Vec<Value>, _> =
json_args.iter().map(|a| serde_json::from_str(a)).collect();
let args = args.map_err(|err| {
eyre!("Invalid arguments. Expected JSON serialized to string, got {json_args:?}: {err}")
})?;
let transport = CurlTransport::new(uri);
let result = rt.block_on(transport.execute(method, args?))?;
let result = rt.block_on(transport.execute(method, args))?;
result
};
@ -80,9 +82,6 @@ mod tests {
let accounts = rpc.eth_call(uri, method, json_args);
println!("bad method: {:?}", accounts);
// println!("accounts: {:?}", accounts);
// assert_eq!(accounts.len(), 0);
}
#[marine_test(
@ -100,4 +99,37 @@ mod tests {
// println!("accounts: {:?}", accounts);
// assert_eq!(accounts.len(), 0);
}
#[marine_test(
config_path = "../tests_artifacts/Config.toml",
modules_dir = "../tests_artifacts"
)]
fn get_transaction(rpc: marine_test_env::eth_rpc::ModuleInterface) {
use serde_json::json;
let uri: String = todo!("put Goerli ETH RPC URL here");
let method: String = "eth_getTransactionByHash".into();
let json_args: Vec<String> =
vec![
json!("0x3ffaa16b93ef90b9385b6f6a140d8297c43b6551bf8e8b804d9eecff7bc1509f")
.to_string(),
];
let result = rpc.eth_call(uri.clone(), method.clone(), json_args);
assert!(result.success, "{}", result.error);
assert_eq!(result.value, "null", "{}", result.value);
let json_args: Vec<String> =
vec!["0x3ffaa16b93ef90b9385b6f6a140d8297c43b6551bf8e8b804d9eecff7bc1509f".into()];
let result = rpc.eth_call(uri, method, json_args);
assert!(!result.success);
assert!(
result
.error
.starts_with("Invalid arguments. Expected JSON serialized to string"),
"{}",
result.error
);
}
}

View File

@ -72,21 +72,6 @@ extern "C" {
#[cfg(test)]
mod tests {
use marine_rs_sdk_test::marine_test;
// use web3::types::Address;
#[marine_test(
config_path = "../tests_artifacts/Config.toml",
modules_dir = "../tests_artifacts"
)]
fn get_accounts(rpc: marine_test_env::eth_rpc::ModuleInterface) {
let uri: String = std::fs::read_to_string("./infura_uri.txt").unwrap();
let accounts = rpc.call_get_accounts(uri);
// let addr: Address = "0x407d73d8a49eeb85d32cf465507dd71d507100c1"
// .parse()
// .unwrap();
// assert_eq!(accounts, vec![addr.as_bytes().to_vec()]);
assert_eq!(accounts.len(), 0);
}
#[marine_test(
config_path = "../tests_artifacts/Config.toml",
@ -99,6 +84,5 @@ mod tests {
let accounts = rpc.eth_call(uri, method, json_args);
println!("accounts: {:?}", accounts);
// assert_eq!(accounts.len(), 0);
}
}

View File

@ -29,7 +29,12 @@ pub fn block_number(uri: String) -> U64Value {
pub fn call(uri: String, req: String, block: u64) -> BytesValue {
let result: eyre::Result<Bytes> = try {
let req: CallRequest = serde_json::from_str(&req)?;
web3_call(uri, move |w| w.call(req, Some(BlockId::Number(BlockNumber::Number(block.into())))))?
web3_call(uri, move |w| {
w.call(
req,
Some(BlockId::Number(BlockNumber::Number(block.into()))),
)
})?
};
result.into()

View File

@ -5,9 +5,9 @@ use web3::types::U64;
#[marine]
pub struct JsonString {
value: String,
success: bool,
error: String,
pub value: String,
pub success: bool,
pub error: String,
}
impl From<eyre::Result<Value>> for JsonString {
@ -29,9 +29,9 @@ impl From<eyre::Result<Value>> for JsonString {
#[marine]
pub struct U64Value {
value: u64,
success: bool,
error: String,
pub value: u64,
pub success: bool,
pub error: String,
}
impl From<web3::error::Result<U64>> for U64Value {
@ -53,9 +53,9 @@ impl From<web3::error::Result<U64>> for U64Value {
#[marine]
pub struct BytesValue {
value: Vec<u8>,
success: bool,
error: String,
pub value: Vec<u8>,
pub success: bool,
pub error: String,
}
impl From<eyre::Result<Bytes>> for BytesValue {
@ -73,4 +73,4 @@ impl From<eyre::Result<Bytes>> for BytesValue {
},
}
}
}
}

View File

@ -18,6 +18,5 @@ logger_enabled = true
curl = "/usr/bin/curl"
[[module]]
name = "eth-rpc"
logger_enabled = true
name = "eth_rpc"
logger_enabled = true

View File

@ -0,0 +1,11 @@
# yaml-language-server: $schema=../../.fluence/schemas/module.yaml.json
# Defines [Marine Module](https://fluence.dev/docs/build/concepts/#modules). For Fluence CLI, **module** - is a directory which contains this config and either a precompiled .wasm Marine module or a source code of the module written in Rust which can be compiled into a .wasm Marine module. You can use `fluence module new` command to generate a template for new module
# Documentation: https://github.com/fluencelabs/fluence-cli/tree/main/docs/configs/module.md
version: 0
type: rust
name: ipfs_adapter
mountedBinaries:
ipfs: /usr/bin/ipfs

View File

@ -0,0 +1,9 @@
# yaml-language-server: $schema=../../.fluence/schemas/module.yaml.json
# Defines [Marine Module](https://fluence.dev/docs/build/concepts/#modules). For Fluence CLI, **module** - is a directory which contains this config and either a precompiled .wasm Marine module or a source code of the module written in Rust which can be compiled into a .wasm Marine module. You can use `fluence module new` command to generate a template for new module
# Documentation: https://github.com/fluencelabs/fluence-cli/tree/main/docs/configs/module.md
version: 0
type: rust
name: ipfs_cli

View File

@ -1,24 +1,14 @@
[package]
name = "multi_provider_query"
version = "0.1.5"
authors = ["boneyard93501 <4523011+boneyard93501@users.noreply.github.com>"]
name = "multi-provider-query"
version = "0.1.0"
edition = "2018"
description = "multi-provider-query, a Marine wasi module"
license = "Apache-2.0"
[[bin]]
name = "multi_provider_query"
name = "multi-provider-query"
path = "src/main.rs"
[dependencies]
marine-rs-sdk = { version = "0.7.1", features = ["logger"] }
log = "0.4.14"
serde_json = "1.0.81"
serde = "1.0.137"
marine-rs-sdk = "0.7.1"
[dev-dependencies]
marine-rs-sdk-test = "0.8.1"
[dev]
[profile.release]
opt-level = "s"

View File

@ -0,0 +1,9 @@
# yaml-language-server: $schema=../../.fluence/schemas/module.yaml.json
# Defines [Marine Module](https://fluence.dev/docs/build/concepts/#modules). For Fluence CLI, **module** - is a directory which contains this config and either a precompiled .wasm Marine module or a source code of the module written in Rust which can be compiled into a .wasm Marine module. You can use `fluence module new` command to generate a template for new module
# Documentation: https://github.com/fluencelabs/fluence-cli/tree/main/docs/configs/module.md
version: 0
type: rust
name: multi_provider_query

View File

@ -14,146 +14,146 @@
* limitations under the License.
*/
use marine_rs_sdk::{marine, module_manifest, MountedBinaryResult};
use serde::{Deserialize, Serialize};
use serde_json;
use std::sync::atomic::{AtomicUsize, Ordering};
use marine_rs_sdk::{marine, module_manifest, MountedBinaryResult};
use serde::{Deserialize, Serialize};
use serde_json;
use std::sync::atomic::{AtomicUsize, Ordering};
pub static NONCE_COUNTER: AtomicUsize = AtomicUsize::new(1);
pub static NONCE_COUNTER: AtomicUsize = AtomicUsize::new(1);
module_manifest!();
module_manifest!();
fn main() {}
fn main() {}
fn get_nonce() -> u64 {
NONCE_COUNTER.fetch_add(1, Ordering::SeqCst) as u64
}
fn get_nonce() -> u64 {
NONCE_COUNTER.fetch_add(1, Ordering::SeqCst) as u64
}
#[marine]
pub struct ProviderInfo {
pub url: String,
pub name: String,
}
#[marine]
pub struct ProviderInfo {
pub url: String,
pub name: String,
}
#[marine]
pub struct EVMResult {
pub provider: String,
pub stdout: String,
pub stderr: String,
}
#[marine]
pub struct EVMResult {
pub provider: String,
pub stdout: String,
pub stderr: String,
}
#[derive(Serialize, Deserialize)]
struct RpcData {
jsonrpc: String,
method: String,
params: Vec<String>,
id: u64,
}
#[derive(Serialize, Deserialize)]
struct RpcData {
jsonrpc: String,
method: String,
params: Vec<String>,
id: u64,
}
#[derive(Serialize, Deserialize, Debug)]
struct RpcResponseError {
code: i32,
message: String,
}
#[derive(Serialize, Deserialize, Debug)]
struct RpcResponse {
jsonrpc: String,
error: Option<RpcResponseError>,
result: Option<String>,
}
#[derive(Serialize, Deserialize, Debug)]
struct RpcResponseError {
code: i32,
message: String,
}
#[derive(Serialize, Deserialize, Debug)]
struct RpcResponse {
jsonrpc: String,
error: Option<RpcResponseError>,
result: Option<String>,
}
impl RpcData {
fn new(method: String, params: Vec<String>) -> Self {
let nonce = get_nonce();
RpcData {
jsonrpc: "2.0".to_owned(),
method: method,
params: params,
id: nonce,
}
}
}
impl RpcData {
fn new(method: String, params: Vec<String>) -> Self {
let nonce = get_nonce();
RpcData {
jsonrpc: "2.0".to_owned(),
method: method,
params: params,
id: nonce,
}
}
}
fn curl_cmd_builder(url: String, data: String) -> Vec<String> {
let curl_cmd: Vec<String> = vec![
url,
"-X".to_string(),
"POST".to_string(),
"-H".to_string(),
"Accept: application/json".to_string(),
"-H".to_string(),
"Content-Type: application/json".to_string(),
"-d".to_string(),
data,
];
fn curl_cmd_builder(url: String, data: String) -> Vec<String> {
let curl_cmd: Vec<String> = vec![
url,
"-X".to_string(),
"POST".to_string(),
"-H".to_string(),
"Accept: application/json".to_string(),
"-H".to_string(),
"Content-Type: application/json".to_string(),
"-d".to_string(),
data,
];
curl_cmd
}
curl_cmd
}
fn get_curl_response(curl_cmd: Vec<String>) -> RpcResponse {
let response = curl_request(curl_cmd);
let response = String::from_utf8(response.stdout).unwrap();
let response: Result<RpcResponse, _> = serde_json::from_str(&response);
match response {
Ok(r) => r,
Err(e) => RpcResponse {
jsonrpc: "".to_owned(),
error: Some(RpcResponseError {
code: -1,
message: e.to_string(),
}),
result: None,
},
}
}
fn get_curl_response(curl_cmd: Vec<String>) -> RpcResponse {
let response = curl_request(curl_cmd);
let response = String::from_utf8(response.stdout).unwrap();
let response: Result<RpcResponse, _> = serde_json::from_str(&response);
match response {
Ok(r) => r,
Err(e) => RpcResponse {
jsonrpc: "".to_owned(),
error: Some(RpcResponseError {
code: -1,
message: e.to_string(),
}),
result: None,
},
}
}
#[marine]
// see https://eth.wiki/json-rpc/API#eth_blocknumbers
fn get_block_number(provider: ProviderInfo) -> EVMResult {
let method = "eth_blockNumber";
let params: Vec<String> = vec![];
let url = provider.url;
#[marine]
// see https://eth.wiki/json-rpc/API#eth_blocknumbers
fn get_block_number(provider: ProviderInfo) -> EVMResult {
let method = "eth_blockNumber";
let params: Vec<String> = vec![];
let url = provider.url;
let data = RpcData::new(method.to_owned(), params);
let data = serde_json::to_string(&data).unwrap();
let data = RpcData::new(method.to_owned(), params);
let data = serde_json::to_string(&data).unwrap();
let curl_cmd = curl_cmd_builder(url, data);
let response = get_curl_response(curl_cmd);
let curl_cmd = curl_cmd_builder(url, data);
let response = get_curl_response(curl_cmd);
if response.error.is_none() {
let raw_response = response.result.unwrap();
let block_height = u64::from_str_radix(raw_response.trim_start_matches("0x"), 16);
if response.error.is_none() {
let raw_response = response.result.unwrap();
let block_height = u64::from_str_radix(raw_response.trim_start_matches("0x"), 16);
let result = match block_height {
Ok(r) => {
let j_res = serde_json::json!({ "block-height": r });
EVMResult {
provider: provider.name,
stdout: j_res.to_string(),
stderr: "".to_owned(),
}
}
Err(e) => {
let err = format!("unable to convert {} to u64 with error {}", raw_response, e);
EVMResult {
provider: provider.name,
stdout: "".to_owned(),
stderr: err,
}
}
};
return result;
}
let result = match block_height {
Ok(r) => {
let j_res = serde_json::json!({ "block-height": r });
EVMResult {
provider: provider.name,
stdout: j_res.to_string(),
stderr: "".to_owned(),
}
}
Err(e) => {
let err = format!("unable to convert {} to u64 with error {}", raw_response, e);
EVMResult {
provider: provider.name,
stdout: "".to_owned(),
stderr: err,
}
}
};
return result;
}
EVMResult {
provider: provider.name,
stdout: "".to_owned(),
stderr: serde_json::to_string(&response.error).unwrap(),
}
}
EVMResult {
provider: provider.name,
stdout: "".to_owned(),
stderr: serde_json::to_string(&response.error).unwrap(),
}
}
#[marine]
#[link(wasm_import_module = "curl_adapter")]
extern "C" {
pub fn curl_request(cmd: Vec<String>) -> MountedBinaryResult;
}
#[marine]
#[link(wasm_import_module = "curl_adapter")]
extern "C" {
pub fn curl_request(cmd: Vec<String>) -> MountedBinaryResult;
}

View File

@ -0,0 +1,9 @@
# yaml-language-server: $schema=../../.fluence/schemas/module.yaml.json
# Defines [Marine Module](https://fluence.dev/docs/build/concepts/#modules). For Fluence CLI, **module** - is a directory which contains this config and either a precompiled .wasm Marine module or a source code of the module written in Rust which can be compiled into a .wasm Marine module. You can use `fluence module new` command to generate a template for new module
# Documentation: https://github.com/fluencelabs/fluence-cli/tree/main/docs/configs/module.md
version: 0
type: rust
name: simple_quorum

View File

@ -47,6 +47,7 @@ fn mode<'a>(data: impl ExactSizeIterator<Item = &'a EVMResult>) -> (u32, u64) {
(*frequencies.get(&mode).unwrap(), *mode)
}
#[allow(unused)]
fn mean<'a>(data: impl ExactSizeIterator<Item = &'a u64>) -> Option<f64> {
let n = data.len() as u64;
if n < 1 {

View File

@ -0,0 +1,9 @@
# yaml-language-server: $schema=../../.fluence/schemas/module.yaml.json
# Defines [Marine Module](https://fluence.dev/docs/build/concepts/#modules). For Fluence CLI, **module** - is a directory which contains this config and either a precompiled .wasm Marine module or a source code of the module written in Rust which can be compiled into a .wasm Marine module. You can use `fluence module new` command to generate a template for new module
# Documentation: https://github.com/fluencelabs/fluence-cli/tree/main/docs/configs/module.md
version: 0
type: rust
name: utilities