Merge remote-tracking branch 'origin/develop' into jon/autoinherit
This commit is contained in:
@@ -9,7 +9,11 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Dependencies (Linux)
|
||||
run: sudo apt-get update && sudo apt-get install -y build-essential curl wget libssl-dev libudev-dev squashfs-tools protobuf-compiler git
|
||||
run: sudo apt-get update && sudo apt-get install -y build-essential curl wget libssl-dev libudev-dev squashfs-tools protobuf-compiler git python3 && sudo apt-get update --fix-missing
|
||||
- name: Install pip3
|
||||
run: sudo apt install -y python3-pip
|
||||
- name: Install Python3 modules
|
||||
run: sudo pip3 install pandas tabulate
|
||||
- name: Install rsync
|
||||
run: sudo apt-get install rsync
|
||||
- uses: rlespinasse/github-slug-action@v3.x
|
||||
|
||||
@@ -13,7 +13,11 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Dependencies (Linux)
|
||||
run: sudo apt-get update && sudo apt-get install -y build-essential curl wget libssl-dev libudev-dev squashfs-tools protobuf-compiler git
|
||||
run: sudo apt-get update && sudo apt-get install -y build-essential curl wget libssl-dev libudev-dev squashfs-tools protobuf-compiler git python3 && sudo apt-get update --fix-missing
|
||||
- name: Install pip3
|
||||
run: sudo apt install -y python3-pip
|
||||
- name: Install Python3 modules
|
||||
run: sudo pip3 install pandas tabulate
|
||||
- name: Install rsync
|
||||
run: sudo apt-get install rsync
|
||||
- uses: rlespinasse/github-slug-action@v3.x
|
||||
|
||||
Generated
+159
-19
@@ -8,6 +8,18 @@ version = "0.11.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3"
|
||||
|
||||
[[package]]
|
||||
name = "accessory"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "850bb534b9dc04744fbbb71d30ad6d25a7e4cf6dc33e223c81ef3a92ebab4e0b"
|
||||
dependencies = [
|
||||
"macroific",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.63",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "addr"
|
||||
version = "0.15.6"
|
||||
@@ -714,9 +726,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck"
|
||||
version = "1.15.0"
|
||||
version = "1.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d6d68c57235a3a081186990eca2867354726650f42f7516ca50c28d6281fd15"
|
||||
checksum = "78834c15cb5d5efe3452d58b1e8ba890dd62d21907f867f383358198e56ebca5"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
@@ -1821,6 +1833,18 @@ dependencies = [
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "delegate-display"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "98a85201f233142ac819bbf6226e36d0b5e129a47bd325084674261c82d4cd66"
|
||||
dependencies = [
|
||||
"macroific",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.63",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "der"
|
||||
version = "0.7.9"
|
||||
@@ -2203,7 +2227,7 @@ dependencies = [
|
||||
"bip39",
|
||||
"console_error_panic_hook",
|
||||
"js-sys",
|
||||
"serde-wasm-bindgen",
|
||||
"serde-wasm-bindgen 0.6.5",
|
||||
"thiserror",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
@@ -2231,6 +2255,18 @@ dependencies = [
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fancy_constructor"
|
||||
version = "1.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f71f317e4af73b2f8f608fac190c52eac4b1879d2145df1db2fe48881ca69435"
|
||||
dependencies = [
|
||||
"macroific",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.63",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "1.9.0"
|
||||
@@ -2586,14 +2622,14 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
|
||||
|
||||
[[package]]
|
||||
name = "gloo-net"
|
||||
version = "0.3.1"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a66b4e3c7d9ed8d315fd6b97c8b1f74a7c6ecbbc2320e65ae7ed38b7068cc620"
|
||||
checksum = "43aaa242d1239a8822c15c645f02166398da4f8b5c4bae795c1f5b44e9eee173"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"gloo-utils",
|
||||
"gloo-utils 0.2.0",
|
||||
"http 0.2.12",
|
||||
"js-sys",
|
||||
"pin-project",
|
||||
@@ -2630,6 +2666,19 @@ dependencies = [
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gloo-utils"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b5555354113b18c547c1d3a98fbf7fb32a9ff4f6fa112ce823a21641a0ba3aa"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "group"
|
||||
version = "0.13.0"
|
||||
@@ -3123,11 +3172,14 @@ checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683"
|
||||
|
||||
[[package]]
|
||||
name = "indexed_db_futures"
|
||||
version = "0.3.0"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bfbcff6ae46750b15cc594bfd277b188cbddcfdc1817848f97f03f26f8625b9e"
|
||||
checksum = "6cc2083760572ee02385ab8b7c02c20925d2dd1f97a1a25a8737a238608f1152"
|
||||
dependencies = [
|
||||
"accessory",
|
||||
"cfg-if",
|
||||
"delegate-display",
|
||||
"fancy_constructor",
|
||||
"js-sys",
|
||||
"uuid 1.6.1",
|
||||
"wasm-bindgen",
|
||||
@@ -3578,6 +3630,53 @@ version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "58093314a45e00c77d5c508f76e77c3396afbbc0d01506e7fae47b018bac2b1d"
|
||||
|
||||
[[package]]
|
||||
name = "macroific"
|
||||
version = "1.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f05c00ac596022625d01047c421a0d97d7f09a18e429187b341c201cb631b9dd"
|
||||
dependencies = [
|
||||
"macroific_attr_parse",
|
||||
"macroific_core",
|
||||
"macroific_macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "macroific_attr_parse"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd94d5da95b30ae6e10621ad02340909346ad91661f3f8c0f2b62345e46a2f67"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.63",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "macroific_core"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13198c120864097a565ccb3ff947672d969932b7975ebd4085732c9f09435e55"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.63",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "macroific_macro"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b0c9853143cbed7f1e41dc39fee95f9b361bec65c8dc2a01bf609be01b61f5ae"
|
||||
dependencies = [
|
||||
"macroific_attr_parse",
|
||||
"macroific_core",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.63",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "matchers"
|
||||
version = "0.1.0"
|
||||
@@ -3677,7 +3776,7 @@ dependencies = [
|
||||
"nym-socks5-requests",
|
||||
"rand 0.7.3",
|
||||
"serde",
|
||||
"serde-wasm-bindgen",
|
||||
"serde-wasm-bindgen 0.6.5",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tsify",
|
||||
@@ -4312,7 +4411,7 @@ dependencies = [
|
||||
"nym-node-tester-wasm",
|
||||
"rand 0.7.3",
|
||||
"serde",
|
||||
"serde-wasm-bindgen",
|
||||
"serde-wasm-bindgen 0.6.5",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
"tsify",
|
||||
@@ -4635,7 +4734,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"futures",
|
||||
"getrandom 0.2.15",
|
||||
"gloo-utils",
|
||||
"gloo-utils 0.2.0",
|
||||
"log",
|
||||
"nym-bandwidth-controller",
|
||||
"nym-credential-storage",
|
||||
@@ -4702,6 +4801,7 @@ name = "nym-http-api-client"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"http 1.1.0",
|
||||
"reqwest 0.12.4",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -4770,6 +4870,7 @@ dependencies = [
|
||||
"rand 0.8.5",
|
||||
"serde",
|
||||
"thiserror",
|
||||
"time",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
]
|
||||
@@ -5183,7 +5284,7 @@ dependencies = [
|
||||
"nym-node-tester-utils",
|
||||
"rand 0.7.3",
|
||||
"serde",
|
||||
"serde-wasm-bindgen",
|
||||
"serde-wasm-bindgen 0.6.5",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tsify",
|
||||
@@ -6575,9 +6676,9 @@ checksum = "106dd99e98437432fed6519dedecfade6a06a73bb7b2a1e019fdd2bee5778d94"
|
||||
|
||||
[[package]]
|
||||
name = "psl"
|
||||
version = "2.1.38"
|
||||
version = "2.1.39"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a66783ea3ba6140ffd59462fa46585d7cc0797893fbf3835ad2da832319964c"
|
||||
checksum = "7b320cda4ad7e8f4269fa415754418f83b38c666a5e2e99ea48825b274a373f3"
|
||||
dependencies = [
|
||||
"psl-types",
|
||||
]
|
||||
@@ -7605,6 +7706,17 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde-wasm-bindgen"
|
||||
version = "0.6.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8302e169f0eddcc139c70f139d19d6467353af16f9fce27e8c30158036a1e16b"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"serde",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_bytes"
|
||||
version = "0.11.14"
|
||||
@@ -9026,9 +9138,9 @@ version = "0.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6b26cf145f2f3b9ff84e182c448eaf05468e247f148cf3d2a7d67d78ff023a0"
|
||||
dependencies = [
|
||||
"gloo-utils",
|
||||
"gloo-utils 0.1.7",
|
||||
"serde",
|
||||
"serde-wasm-bindgen",
|
||||
"serde-wasm-bindgen 0.5.0",
|
||||
"serde_json",
|
||||
"tsify-macros",
|
||||
"wasm-bindgen",
|
||||
@@ -9289,6 +9401,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e395fcf16a7a3d8127ec99782007af141946b4795001f876d54fb0d55978560"
|
||||
dependencies = [
|
||||
"getrandom 0.2.15",
|
||||
"serde",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
@@ -9470,7 +9583,7 @@ dependencies = [
|
||||
"nym-validator-client",
|
||||
"rand 0.7.3",
|
||||
"serde",
|
||||
"serde-wasm-bindgen",
|
||||
"serde-wasm-bindgen 0.6.5",
|
||||
"thiserror",
|
||||
"time",
|
||||
"tsify",
|
||||
@@ -9492,7 +9605,7 @@ dependencies = [
|
||||
"js-sys",
|
||||
"nym-store-cipher",
|
||||
"serde",
|
||||
"serde-wasm-bindgen",
|
||||
"serde-wasm-bindgen 0.6.5",
|
||||
"thiserror",
|
||||
"wasm-bindgen",
|
||||
"wasm-utils",
|
||||
@@ -9518,7 +9631,7 @@ dependencies = [
|
||||
"futures",
|
||||
"getrandom 0.2.15",
|
||||
"gloo-net",
|
||||
"gloo-utils",
|
||||
"gloo-utils 0.2.0",
|
||||
"js-sys",
|
||||
"tungstenite",
|
||||
"wasm-bindgen",
|
||||
@@ -10019,3 +10132,30 @@ dependencies = [
|
||||
"crossbeam-utils",
|
||||
"flate2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zknym-lib"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"bs58 0.5.1",
|
||||
"getrandom 0.2.15",
|
||||
"js-sys",
|
||||
"nym-bin-common",
|
||||
"nym-coconut",
|
||||
"nym-credentials",
|
||||
"nym-crypto",
|
||||
"nym-http-api-client",
|
||||
"rand 0.7.3",
|
||||
"reqwest 0.12.4",
|
||||
"serde",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tsify",
|
||||
"uuid 1.6.1",
|
||||
"wasm-bindgen",
|
||||
"wasm-utils",
|
||||
"wasmtimer",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
+9
-6
@@ -122,6 +122,7 @@ members = [
|
||||
# "wasm/full-nym-wasm",
|
||||
"wasm/mix-fetch",
|
||||
"wasm/node-tester",
|
||||
"wasm/zknym-lib",
|
||||
]
|
||||
|
||||
default-members = [
|
||||
@@ -219,6 +220,7 @@ http = "0.2.9"
|
||||
httpcodec = "0.2.3"
|
||||
humantime = "2.1.0"
|
||||
humantime-serde = "1.1.1"
|
||||
http = "1"
|
||||
hyper = "1.3.1"
|
||||
indexed_db_futures = "0.3.0"
|
||||
inquire = "0.6.2"
|
||||
@@ -333,14 +335,15 @@ tendermint-rpc = "0.34" # same version as used by cosmrs
|
||||
prost = "0.12"
|
||||
|
||||
# wasm-related dependencies
|
||||
gloo-utils = "0.1.7"
|
||||
js-sys = "0.3.63"
|
||||
serde-wasm-bindgen = "0.5.0"
|
||||
gloo-utils = "0.2.0"
|
||||
gloo-net = "0.5.0"
|
||||
js-sys = "0.3.69"
|
||||
serde-wasm-bindgen = "0.6.5"
|
||||
tsify = "0.4.5"
|
||||
wasm-bindgen = "0.2.86"
|
||||
wasm-bindgen-futures = "0.4.37"
|
||||
wasm-bindgen = "0.2.92"
|
||||
wasm-bindgen-futures = "0.4.39"
|
||||
wasmtimer = "0.2.0"
|
||||
web-sys = "0.3.63"
|
||||
web-sys = "0.3.69"
|
||||
|
||||
|
||||
# Profile settings for individual crates
|
||||
|
||||
@@ -105,6 +105,7 @@ sdk-wasm-build:
|
||||
$(MAKE) -C wasm/client
|
||||
$(MAKE) -C wasm/node-tester
|
||||
$(MAKE) -C wasm/mix-fetch
|
||||
$(MAKE) -C wasm/zknym-lib
|
||||
#$(MAKE) -C wasm/full-nym-wasm
|
||||
|
||||
# run this from npm/yarn to ensure tools are in the path, e.g. yarn build:sdk from root of repo
|
||||
@@ -115,7 +116,7 @@ sdk-typescript-build:
|
||||
yarn --cwd sdk/typescript/codegen/contract-clients build
|
||||
|
||||
# NOTE: These targets are part of the main workspace (but not as wasm32-unknown-unknown)
|
||||
WASM_CRATES = extension-storage nym-client-wasm nym-node-tester-wasm
|
||||
WASM_CRATES = extension-storage nym-client-wasm nym-node-tester-wasm zknym-lib
|
||||
|
||||
sdk-wasm-test:
|
||||
#cargo test $(addprefix -p , $(WASM_CRATES)) --target wasm32-unknown-unknown -- -Dwarnings
|
||||
|
||||
@@ -11,11 +11,8 @@ use nym_validator_client::nyxd::contract_traits::{
|
||||
};
|
||||
use nym_validator_client::nyxd::Coin;
|
||||
use std::path::PathBuf;
|
||||
use std::process::exit;
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
||||
const SAFETY_BUFFER_SECS: u64 = 60; // 1 minute
|
||||
|
||||
pub async fn issue_credential<C>(
|
||||
client: &C,
|
||||
amount: Coin,
|
||||
@@ -92,13 +89,6 @@ where
|
||||
.as_secs();
|
||||
|
||||
if epoch.state.is_final() {
|
||||
if let Some(finish_timestamp) = epoch.deadline {
|
||||
if current_timestamp_secs + SAFETY_BUFFER_SECS >= finish_timestamp.seconds() {
|
||||
info!("In the next {} minute(s), a transition will take place in the coconut system. Deposits should be halted in this time for safety reasons.", SAFETY_BUFFER_SECS / 60);
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
} else if let Some(final_timestamp) = epoch.final_timestamp_secs() {
|
||||
// Use 1 additional second to not start the next iteration immediately and spam get_current_epoch queries
|
||||
|
||||
@@ -155,11 +155,6 @@ impl IssuedBandwidthCredential {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn randomise_signature(&mut self) {
|
||||
let signature_prime = self.signature.randomise(bandwidth_credential_params());
|
||||
self.signature = signature_prime.0
|
||||
}
|
||||
|
||||
pub fn default_parameters() -> Parameters {
|
||||
IssuanceBandwidthCredential::default_parameters()
|
||||
}
|
||||
|
||||
@@ -30,6 +30,10 @@ impl<'a> From<&'a BandwidthVoucherIssuanceData> for BandwidthVoucherIssuedData {
|
||||
}
|
||||
|
||||
impl BandwidthVoucherIssuedData {
|
||||
pub fn new(value: Coin) -> Self {
|
||||
BandwidthVoucherIssuedData { value }
|
||||
}
|
||||
|
||||
pub fn value(&self) -> &Coin {
|
||||
&self.value
|
||||
}
|
||||
|
||||
@@ -200,6 +200,14 @@ impl<'a> From<&'a PrivateKey> for PublicKey {
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for PrivateKey {
|
||||
type Err = KeyRecoveryError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
PrivateKey::from_base58_string(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl PrivateKey {
|
||||
#[cfg(feature = "rand")]
|
||||
pub fn new<R: RngCore + CryptoRng>(rng: &mut R) -> Self {
|
||||
|
||||
@@ -227,6 +227,14 @@ impl<'a> From<&'a PrivateKey> for PublicKey {
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for PrivateKey {
|
||||
type Err = Ed25519RecoveryError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
PrivateKey::from_base58_string(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl PrivateKey {
|
||||
#[cfg(feature = "rand")]
|
||||
pub fn new<R: RngCore + CryptoRng>(rng: &mut R) -> Self {
|
||||
|
||||
@@ -13,6 +13,7 @@ license.workspace = true
|
||||
[dependencies]
|
||||
async-trait = { workspace = true }
|
||||
reqwest = { workspace = true, features = ["json"] }
|
||||
http.workspace = true
|
||||
url = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use async_trait::async_trait;
|
||||
use reqwest::{IntoUrl, Response, StatusCode};
|
||||
use reqwest::header::HeaderValue;
|
||||
use reqwest::{RequestBuilder, Response, StatusCode};
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::Display;
|
||||
@@ -11,6 +12,8 @@ use thiserror::Error;
|
||||
use tracing::warn;
|
||||
use url::Url;
|
||||
|
||||
pub use reqwest::IntoUrl;
|
||||
|
||||
pub const DEFAULT_TIMEOUT: Duration = Duration::from_secs(10);
|
||||
|
||||
pub type PathSegments<'a> = &'a [&'a str];
|
||||
@@ -52,6 +55,88 @@ pub enum HttpClientError<E: Display = String> {
|
||||
RequestTimeout,
|
||||
}
|
||||
|
||||
pub struct ClientBuilder {
|
||||
url: Url,
|
||||
timeout: Option<Duration>,
|
||||
custom_user_agent: bool,
|
||||
reqwest_client_builder: reqwest::ClientBuilder,
|
||||
}
|
||||
|
||||
impl ClientBuilder {
|
||||
pub fn new<U, E>(url: U) -> Result<Self, HttpClientError<E>>
|
||||
where
|
||||
U: IntoUrl,
|
||||
E: Display,
|
||||
{
|
||||
// a naive check: if the provided URL does not start with http(s), add that scheme
|
||||
let str_url = url.as_str();
|
||||
|
||||
if !str_url.starts_with("http") {
|
||||
let alt = format!("http://{str_url}");
|
||||
warn!("the provided url ('{str_url}') does not contain scheme information. Changing it to '{alt}' ...");
|
||||
// TODO: or should we maybe default to https?
|
||||
Self::new(alt)
|
||||
} else {
|
||||
Ok(ClientBuilder {
|
||||
url: url.into_url()?,
|
||||
timeout: None,
|
||||
custom_user_agent: false,
|
||||
reqwest_client_builder: reqwest::ClientBuilder::new(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_timeout(mut self, timeout: Duration) -> Self {
|
||||
self.timeout = Some(timeout);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_reqwest_builder(mut self, reqwest_builder: reqwest::ClientBuilder) -> Self {
|
||||
self.reqwest_client_builder = reqwest_builder;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_user_agent<V>(mut self, value: V) -> Self
|
||||
where
|
||||
V: TryInto<HeaderValue>,
|
||||
V::Error: Into<http::Error>,
|
||||
{
|
||||
self.custom_user_agent = true;
|
||||
self.reqwest_client_builder = self.reqwest_client_builder.user_agent(value);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build<E>(self) -> Result<Client, HttpClientError<E>>
|
||||
where
|
||||
E: Display,
|
||||
{
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let reqwest_client = self.reqwest_client_builder.build()?;
|
||||
|
||||
// TODO: we should probably be propagating the error rather than panicking,
|
||||
// but that'd break bunch of things due to type changes
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let reqwest_client = {
|
||||
let mut builder = self
|
||||
.reqwest_client_builder
|
||||
.timeout(self.timeout.unwrap_or(DEFAULT_TIMEOUT));
|
||||
if !self.custom_user_agent {
|
||||
builder =
|
||||
builder.user_agent(format!("nym-http-api-client/{}", env!("CARGO_PKG_VERSION")))
|
||||
}
|
||||
builder.build()?
|
||||
};
|
||||
|
||||
Ok(Client {
|
||||
base_url: self.url,
|
||||
reqwest_client,
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
request_timeout: self.timeout.unwrap_or(DEFAULT_TIMEOUT),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// A simple extendable client wrapper for http request with extra url sanitization.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Client {
|
||||
@@ -65,25 +150,9 @@ pub struct Client {
|
||||
impl Client {
|
||||
// no timeout until https://github.com/seanmonstar/reqwest/issues/1135 is fixed
|
||||
pub fn new(base_url: Url, timeout: Option<Duration>) -> Self {
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let reqwest_client = reqwest::Client::new();
|
||||
|
||||
// TODO: we should probably be propagating the error rather than panicking,
|
||||
// but that'd break bunch of things due to type changes
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let reqwest_client = reqwest::ClientBuilder::new()
|
||||
.timeout(timeout.unwrap_or(DEFAULT_TIMEOUT))
|
||||
.user_agent(format!("nym-http-api-client/{}", env!("CARGO_PKG_VERSION")))
|
||||
.build()
|
||||
.expect("Client::new()");
|
||||
|
||||
Client {
|
||||
base_url,
|
||||
reqwest_client,
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
request_timeout: timeout.unwrap_or(DEFAULT_TIMEOUT),
|
||||
}
|
||||
Self::new_url::<_, String>(base_url, timeout).expect(
|
||||
"we provided valid url and we were unwrapping previous construction errors anyway",
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_url<U, E>(url: U, timeout: Option<Duration>) -> Result<Self, HttpClientError<E>>
|
||||
@@ -91,19 +160,21 @@ impl Client {
|
||||
U: IntoUrl,
|
||||
E: Display,
|
||||
{
|
||||
// a naive check: if the provided URL does not start with http(s), add that scheme
|
||||
let str_url = url.as_str();
|
||||
|
||||
if !str_url.starts_with("http") {
|
||||
let alt = format!("http://{str_url}");
|
||||
warn!("the provided url ('{str_url}') does not contain scheme information. Changing it to '{alt}' ...");
|
||||
// TODO: or should we maybe default to https?
|
||||
Self::new_url(alt, timeout)
|
||||
} else {
|
||||
Ok(Self::new(url.into_url()?, timeout))
|
||||
let builder = Self::builder(url)?;
|
||||
match timeout {
|
||||
Some(timeout) => builder.with_timeout(timeout).build(),
|
||||
None => builder.build(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn builder<U, E>(url: U) -> Result<ClientBuilder, HttpClientError<E>>
|
||||
where
|
||||
U: IntoUrl,
|
||||
E: Display,
|
||||
{
|
||||
ClientBuilder::new(url)
|
||||
}
|
||||
|
||||
pub fn change_base_url(&mut self, new_url: Url) {
|
||||
self.base_url = new_url
|
||||
}
|
||||
@@ -112,6 +183,19 @@ impl Client {
|
||||
&self.base_url
|
||||
}
|
||||
|
||||
pub fn create_get_request<K, V>(
|
||||
&self,
|
||||
path: PathSegments<'_>,
|
||||
params: Params<'_, K, V>,
|
||||
) -> RequestBuilder
|
||||
where
|
||||
K: AsRef<str>,
|
||||
V: AsRef<str>,
|
||||
{
|
||||
let url = sanitize_url(&self.base_url, path, params);
|
||||
self.reqwest_client.get(url)
|
||||
}
|
||||
|
||||
async fn send_get_request<K, V, E>(
|
||||
&self,
|
||||
path: PathSegments<'_>,
|
||||
@@ -142,6 +226,21 @@ impl Client {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_post_request<B, K, V>(
|
||||
&self,
|
||||
path: PathSegments<'_>,
|
||||
params: Params<'_, K, V>,
|
||||
json_body: &B,
|
||||
) -> RequestBuilder
|
||||
where
|
||||
B: Serialize + ?Sized,
|
||||
K: AsRef<str>,
|
||||
V: AsRef<str>,
|
||||
{
|
||||
let url = sanitize_url(&self.base_url, path, params);
|
||||
self.reqwest_client.post(url).json(json_body)
|
||||
}
|
||||
|
||||
async fn send_post_request<B, K, V, E>(
|
||||
&self,
|
||||
path: PathSegments<'_>,
|
||||
@@ -407,7 +506,7 @@ pub fn sanitize_url<K: AsRef<str>, V: AsRef<str>>(
|
||||
url
|
||||
}
|
||||
|
||||
async fn parse_response<T, E>(res: Response, allow_empty: bool) -> Result<T, HttpClientError<E>>
|
||||
pub async fn parse_response<T, E>(res: Response, allow_empty: bool) -> Result<T, HttpClientError<E>>
|
||||
where
|
||||
T: DeserializeOwned,
|
||||
E: DeserializeOwned + Display,
|
||||
|
||||
@@ -16,5 +16,6 @@ nym-sphinx = { path = "../nymsphinx" }
|
||||
rand = "0.8.5"
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
thiserror = { workspace = true }
|
||||
time = { workspace = true }
|
||||
tokio = { workspace = true, features = ["time"] }
|
||||
tokio-util = { workspace = true, features = ["codec"] }
|
||||
|
||||
@@ -12,11 +12,13 @@ pub use v6::response;
|
||||
|
||||
pub mod codec;
|
||||
pub mod v6;
|
||||
pub mod v7;
|
||||
|
||||
// version 3: initial version
|
||||
// version 4: IPv6 support
|
||||
// version 5: Add severity level to info response
|
||||
// version 6: Increase the available IPs
|
||||
// version 7: Add signature support (for the future)
|
||||
pub const CURRENT_VERSION: u8 = 6;
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
pub mod request;
|
||||
pub mod response;
|
||||
@@ -0,0 +1,395 @@
|
||||
use nym_sphinx::addressing::clients::Recipient;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use time::OffsetDateTime;
|
||||
|
||||
use crate::{make_bincode_serializer, IpPair, CURRENT_VERSION};
|
||||
|
||||
fn generate_random() -> u64 {
|
||||
use rand::RngCore;
|
||||
let mut rng = rand::rngs::OsRng;
|
||||
rng.next_u64()
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct IpPacketRequest {
|
||||
pub version: u8,
|
||||
pub data: IpPacketRequestData,
|
||||
}
|
||||
|
||||
impl IpPacketRequest {
|
||||
pub fn new_static_connect_request(
|
||||
ips: IpPair,
|
||||
reply_to: Recipient,
|
||||
reply_to_hops: Option<u8>,
|
||||
reply_to_avg_mix_delays: Option<f64>,
|
||||
buffer_timeout: Option<u64>,
|
||||
) -> (Self, u64) {
|
||||
let request_id = generate_random();
|
||||
(
|
||||
Self {
|
||||
version: CURRENT_VERSION,
|
||||
data: IpPacketRequestData::StaticConnect(SignedStaticConnectRequest {
|
||||
request: StaticConnectRequest {
|
||||
request_id,
|
||||
ips,
|
||||
reply_to,
|
||||
reply_to_hops,
|
||||
reply_to_avg_mix_delays,
|
||||
buffer_timeout,
|
||||
timestamp: OffsetDateTime::now_utc(),
|
||||
},
|
||||
signature: None,
|
||||
}),
|
||||
},
|
||||
request_id,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_dynamic_connect_request(
|
||||
reply_to: Recipient,
|
||||
reply_to_hops: Option<u8>,
|
||||
reply_to_avg_mix_delays: Option<f64>,
|
||||
buffer_timeout: Option<u64>,
|
||||
) -> (Self, u64) {
|
||||
let request_id = generate_random();
|
||||
(
|
||||
Self {
|
||||
version: CURRENT_VERSION,
|
||||
data: IpPacketRequestData::DynamicConnect(SignedDynamicConnectRequest {
|
||||
request: DynamicConnectRequest {
|
||||
request_id,
|
||||
reply_to,
|
||||
reply_to_hops,
|
||||
reply_to_avg_mix_delays,
|
||||
buffer_timeout,
|
||||
timestamp: OffsetDateTime::now_utc(),
|
||||
},
|
||||
signature: None,
|
||||
}),
|
||||
},
|
||||
request_id,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_disconnect_request(reply_to: Recipient) -> (Self, u64) {
|
||||
let request_id = generate_random();
|
||||
(
|
||||
Self {
|
||||
version: CURRENT_VERSION,
|
||||
data: IpPacketRequestData::Disconnect(SignedDisconnectRequest {
|
||||
request: DisconnectRequest {
|
||||
request_id,
|
||||
reply_to,
|
||||
timestamp: OffsetDateTime::now_utc(),
|
||||
},
|
||||
signature: None,
|
||||
}),
|
||||
},
|
||||
request_id,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_data_request(ip_packets: bytes::Bytes) -> Self {
|
||||
Self {
|
||||
version: CURRENT_VERSION,
|
||||
data: IpPacketRequestData::Data(DataRequest { ip_packets }),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_ping(reply_to: Recipient) -> (Self, u64) {
|
||||
let request_id = generate_random();
|
||||
(
|
||||
Self {
|
||||
version: CURRENT_VERSION,
|
||||
data: IpPacketRequestData::Ping(PingRequest {
|
||||
request_id,
|
||||
reply_to,
|
||||
timestamp: OffsetDateTime::now_utc(),
|
||||
}),
|
||||
},
|
||||
request_id,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_health_request(reply_to: Recipient) -> (Self, u64) {
|
||||
let request_id = generate_random();
|
||||
(
|
||||
Self {
|
||||
version: CURRENT_VERSION,
|
||||
data: IpPacketRequestData::Health(HealthRequest {
|
||||
request_id,
|
||||
reply_to,
|
||||
timestamp: OffsetDateTime::now_utc(),
|
||||
}),
|
||||
},
|
||||
request_id,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn id(&self) -> Option<u64> {
|
||||
match &self.data {
|
||||
IpPacketRequestData::StaticConnect(request) => Some(request.request.request_id),
|
||||
IpPacketRequestData::DynamicConnect(request) => Some(request.request.request_id),
|
||||
IpPacketRequestData::Disconnect(request) => Some(request.request.request_id),
|
||||
IpPacketRequestData::Data(_) => None,
|
||||
IpPacketRequestData::Ping(request) => Some(request.request_id),
|
||||
IpPacketRequestData::Health(request) => Some(request.request_id),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn recipient(&self) -> Option<&Recipient> {
|
||||
match &self.data {
|
||||
IpPacketRequestData::StaticConnect(request) => Some(&request.request.reply_to),
|
||||
IpPacketRequestData::DynamicConnect(request) => Some(&request.request.reply_to),
|
||||
IpPacketRequestData::Disconnect(request) => Some(&request.request.reply_to),
|
||||
IpPacketRequestData::Data(_) => None,
|
||||
IpPacketRequestData::Ping(request) => Some(&request.reply_to),
|
||||
IpPacketRequestData::Health(request) => Some(&request.reply_to),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_bytes(&self) -> Result<Vec<u8>, bincode::Error> {
|
||||
use bincode::Options;
|
||||
make_bincode_serializer().serialize(self)
|
||||
}
|
||||
|
||||
pub fn from_reconstructed_message(
|
||||
message: &nym_sphinx::receiver::ReconstructedMessage,
|
||||
) -> Result<Self, bincode::Error> {
|
||||
use bincode::Options;
|
||||
make_bincode_serializer().deserialize(&message.message)
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub enum IpPacketRequestData {
|
||||
StaticConnect(SignedStaticConnectRequest),
|
||||
DynamicConnect(SignedDynamicConnectRequest),
|
||||
Disconnect(SignedDisconnectRequest),
|
||||
Data(DataRequest),
|
||||
Ping(PingRequest),
|
||||
Health(HealthRequest),
|
||||
}
|
||||
|
||||
impl IpPacketRequestData {
|
||||
pub fn add_signature(&mut self, signature: Vec<u8>) -> Option<Vec<u8>> {
|
||||
match self {
|
||||
IpPacketRequestData::StaticConnect(request) => {
|
||||
request.signature = Some(signature);
|
||||
request.signature.clone()
|
||||
}
|
||||
IpPacketRequestData::DynamicConnect(request) => {
|
||||
request.signature = Some(signature);
|
||||
request.signature.clone()
|
||||
}
|
||||
IpPacketRequestData::Disconnect(request) => {
|
||||
request.signature = Some(signature);
|
||||
request.signature.clone()
|
||||
}
|
||||
IpPacketRequestData::Data(_)
|
||||
| IpPacketRequestData::Ping(_)
|
||||
| IpPacketRequestData::Health(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A static connect request is when the client provides the internal IP address it will use on the
|
||||
// ip packet router.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct StaticConnectRequest {
|
||||
pub request_id: u64,
|
||||
|
||||
pub ips: IpPair,
|
||||
|
||||
// The nym-address the response should be sent back to
|
||||
pub reply_to: Recipient,
|
||||
|
||||
// The number of mix node hops that responses should take, in addition to the entry and exit
|
||||
// node. Zero means only client -> entry -> exit -> client.
|
||||
pub reply_to_hops: Option<u8>,
|
||||
|
||||
// The average delay at each mix node, in milliseconds. Currently this is not supported by the
|
||||
// ip packet router.
|
||||
pub reply_to_avg_mix_delays: Option<f64>,
|
||||
|
||||
// The maximum time in milliseconds the IPR should wait when filling up a mix packet
|
||||
// with ip packets.
|
||||
pub buffer_timeout: Option<u64>,
|
||||
|
||||
// Timestamp of when the request was sent by the client.
|
||||
pub timestamp: OffsetDateTime,
|
||||
}
|
||||
|
||||
impl StaticConnectRequest {
|
||||
pub fn to_bytes(&self) -> Result<Vec<u8>, bincode::Error> {
|
||||
use bincode::Options;
|
||||
make_bincode_serializer().serialize(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct SignedStaticConnectRequest {
|
||||
pub request: StaticConnectRequest,
|
||||
pub signature: Option<Vec<u8>>,
|
||||
}
|
||||
|
||||
// A dynamic connect request is when the client does not provide the internal IP address it will use
|
||||
// on the ip packet router, and instead requests one to be assigned to it.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct DynamicConnectRequest {
|
||||
pub request_id: u64,
|
||||
|
||||
// The nym-address the response should be sent back to
|
||||
pub reply_to: Recipient,
|
||||
|
||||
// The number of mix node hops that responses should take, in addition to the entry and exit
|
||||
// node. Zero means only client -> entry -> exit -> client.
|
||||
pub reply_to_hops: Option<u8>,
|
||||
|
||||
// The average delay at each mix node, in milliseconds. Currently this is not supported by the
|
||||
// ip packet router.
|
||||
pub reply_to_avg_mix_delays: Option<f64>,
|
||||
|
||||
// The maximum time in milliseconds the IPR should wait when filling up a mix packet
|
||||
// with ip packets.
|
||||
pub buffer_timeout: Option<u64>,
|
||||
|
||||
// Timestamp of when the request was sent by the client.
|
||||
pub timestamp: OffsetDateTime,
|
||||
}
|
||||
|
||||
impl DynamicConnectRequest {
|
||||
pub fn to_bytes(&self) -> Result<Vec<u8>, bincode::Error> {
|
||||
use bincode::Options;
|
||||
make_bincode_serializer().serialize(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct SignedDynamicConnectRequest {
|
||||
pub request: DynamicConnectRequest,
|
||||
pub signature: Option<Vec<u8>>,
|
||||
}
|
||||
|
||||
// A disconnect request is when the client wants to disconnect from the ip packet router and free
|
||||
// up the allocated IP address.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct DisconnectRequest {
|
||||
pub request_id: u64,
|
||||
|
||||
// The nym-address the response should be sent back to
|
||||
pub reply_to: Recipient,
|
||||
|
||||
// Timestamp of when the request was sent by the client.
|
||||
pub timestamp: OffsetDateTime,
|
||||
}
|
||||
|
||||
impl DisconnectRequest {
|
||||
pub fn to_bytes(&self) -> Result<Vec<u8>, bincode::Error> {
|
||||
use bincode::Options;
|
||||
make_bincode_serializer().serialize(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct SignedDisconnectRequest {
|
||||
pub request: DisconnectRequest,
|
||||
pub signature: Option<Vec<u8>>,
|
||||
}
|
||||
|
||||
// A data request is when the client wants to send an IP packet to a destination.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct DataRequest {
|
||||
pub ip_packets: bytes::Bytes,
|
||||
}
|
||||
|
||||
// A ping request is when the client wants to check if the ip packet router is still alive.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct PingRequest {
|
||||
pub request_id: u64,
|
||||
|
||||
// The nym-address the response should be sent back to
|
||||
pub reply_to: Recipient,
|
||||
|
||||
// Timestamp of when the request was sent by the client.
|
||||
pub timestamp: OffsetDateTime,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct HealthRequest {
|
||||
pub request_id: u64,
|
||||
|
||||
// The nym-address the response should be sent back to
|
||||
pub reply_to: Recipient,
|
||||
|
||||
// Timestamp of when the request was sent by the client.
|
||||
pub timestamp: OffsetDateTime,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::net::{Ipv4Addr, Ipv6Addr};
|
||||
use std::str::FromStr;
|
||||
|
||||
#[test]
|
||||
fn check_size_of_request() {
|
||||
let connect = IpPacketRequest {
|
||||
version: 4,
|
||||
data: IpPacketRequestData::StaticConnect(
|
||||
SignedStaticConnectRequest {
|
||||
request: StaticConnectRequest {
|
||||
request_id: 123,
|
||||
ips: IpPair::new(Ipv4Addr::from_str("10.0.0.1").unwrap(), Ipv6Addr::from_str("2001:db8:a160::1").unwrap()),
|
||||
reply_to: Recipient::try_from_base58_string("D1rrpsysCGCYXy9saP8y3kmNpGtJZUXN9SvFoUcqAsM9.9Ssso1ea5NfkbMASdiseDSjTN1fSWda5SgEVjdSN4CvV@GJqd3ZxpXWSNxTfx7B1pPtswpetH4LnJdFeLeuY5KUuN").unwrap(),
|
||||
reply_to_hops: None,
|
||||
reply_to_avg_mix_delays: None,
|
||||
buffer_timeout: None,
|
||||
timestamp: OffsetDateTime::now_utc(),
|
||||
},
|
||||
signature: None,
|
||||
}
|
||||
),
|
||||
};
|
||||
assert_eq!(connect.to_bytes().unwrap().len(), 139);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_size_of_data() {
|
||||
let data = IpPacketRequest {
|
||||
version: 4,
|
||||
data: IpPacketRequestData::Data(DataRequest {
|
||||
ip_packets: bytes::Bytes::from(vec![1u8; 32]),
|
||||
}),
|
||||
};
|
||||
assert_eq!(data.to_bytes().unwrap().len(), 35);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize_and_deserialize_data_request() {
|
||||
let data = IpPacketRequest {
|
||||
version: 4,
|
||||
data: IpPacketRequestData::Data(DataRequest {
|
||||
ip_packets: bytes::Bytes::from(vec![1, 2, 4, 2, 5]),
|
||||
}),
|
||||
};
|
||||
|
||||
let serialized = data.to_bytes().unwrap();
|
||||
let deserialized = IpPacketRequest::from_reconstructed_message(
|
||||
&nym_sphinx::receiver::ReconstructedMessage {
|
||||
message: serialized,
|
||||
sender_tag: None,
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(deserialized.version, 4);
|
||||
assert_eq!(
|
||||
deserialized.data,
|
||||
IpPacketRequestData::Data(DataRequest {
|
||||
ip_packets: bytes::Bytes::from(vec![1, 2, 4, 2, 5]),
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,410 @@
|
||||
use nym_sphinx::addressing::clients::Recipient;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{make_bincode_serializer, IpPair, CURRENT_VERSION};
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct IpPacketResponse {
|
||||
pub version: u8,
|
||||
pub data: IpPacketResponseData,
|
||||
}
|
||||
|
||||
impl IpPacketResponse {
|
||||
pub fn new_static_connect_success(request_id: u64, reply_to: Recipient) -> Self {
|
||||
Self {
|
||||
version: CURRENT_VERSION,
|
||||
data: IpPacketResponseData::StaticConnect(StaticConnectResponse {
|
||||
request_id,
|
||||
reply_to,
|
||||
reply: StaticConnectResponseReply::Success,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_static_connect_failure(
|
||||
request_id: u64,
|
||||
reply_to: Recipient,
|
||||
reason: StaticConnectFailureReason,
|
||||
) -> Self {
|
||||
Self {
|
||||
version: CURRENT_VERSION,
|
||||
data: IpPacketResponseData::StaticConnect(StaticConnectResponse {
|
||||
request_id,
|
||||
reply_to,
|
||||
reply: StaticConnectResponseReply::Failure(reason),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_dynamic_connect_success(request_id: u64, reply_to: Recipient, ips: IpPair) -> Self {
|
||||
Self {
|
||||
version: CURRENT_VERSION,
|
||||
data: IpPacketResponseData::DynamicConnect(DynamicConnectResponse {
|
||||
request_id,
|
||||
reply_to,
|
||||
reply: DynamicConnectResponseReply::Success(DynamicConnectSuccess { ips }),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_dynamic_connect_failure(
|
||||
request_id: u64,
|
||||
reply_to: Recipient,
|
||||
reason: DynamicConnectFailureReason,
|
||||
) -> Self {
|
||||
Self {
|
||||
version: CURRENT_VERSION,
|
||||
data: IpPacketResponseData::DynamicConnect(DynamicConnectResponse {
|
||||
request_id,
|
||||
reply_to,
|
||||
reply: DynamicConnectResponseReply::Failure(reason),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_disconnect_success(request_id: u64, reply_to: Recipient) -> Self {
|
||||
Self {
|
||||
version: CURRENT_VERSION,
|
||||
data: IpPacketResponseData::Disconnect(DisconnectResponse {
|
||||
request_id,
|
||||
reply_to,
|
||||
reply: DisconnectResponseReply::Success,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_disconnect_failure(
|
||||
request_id: u64,
|
||||
reply_to: Recipient,
|
||||
reason: DisconnectFailureReason,
|
||||
) -> Self {
|
||||
Self {
|
||||
version: CURRENT_VERSION,
|
||||
data: IpPacketResponseData::Disconnect(DisconnectResponse {
|
||||
request_id,
|
||||
reply_to,
|
||||
reply: DisconnectResponseReply::Failure(reason),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_unrequested_disconnect(
|
||||
reply_to: Recipient,
|
||||
reason: UnrequestedDisconnectReason,
|
||||
) -> Self {
|
||||
Self {
|
||||
version: CURRENT_VERSION,
|
||||
data: IpPacketResponseData::UnrequestedDisconnect(UnrequestedDisconnect {
|
||||
reply_to,
|
||||
reason,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_ip_packet(ip_packet: bytes::Bytes) -> Self {
|
||||
Self {
|
||||
version: CURRENT_VERSION,
|
||||
data: IpPacketResponseData::Data(DataResponse { ip_packet }),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_version_mismatch(
|
||||
request_id: u64,
|
||||
reply_to: Recipient,
|
||||
request_version: u8,
|
||||
our_version: u8,
|
||||
) -> Self {
|
||||
Self {
|
||||
version: CURRENT_VERSION,
|
||||
data: IpPacketResponseData::Info(InfoResponse {
|
||||
request_id,
|
||||
reply_to,
|
||||
reply: InfoResponseReply::VersionMismatch {
|
||||
request_version,
|
||||
response_version: our_version,
|
||||
},
|
||||
level: InfoLevel::Error,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_data_info_response(
|
||||
reply_to: Recipient,
|
||||
reply: InfoResponseReply,
|
||||
level: InfoLevel,
|
||||
) -> Self {
|
||||
Self {
|
||||
version: CURRENT_VERSION,
|
||||
data: IpPacketResponseData::Info(InfoResponse {
|
||||
request_id: 0,
|
||||
reply_to,
|
||||
reply,
|
||||
level,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_pong(request_id: u64, reply_to: Recipient) -> Self {
|
||||
Self {
|
||||
version: CURRENT_VERSION,
|
||||
data: IpPacketResponseData::Pong(PongResponse {
|
||||
request_id,
|
||||
reply_to,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_health_response(
|
||||
request_id: u64,
|
||||
reply_to: Recipient,
|
||||
build_info: nym_bin_common::build_information::BinaryBuildInformationOwned,
|
||||
routable: Option<bool>,
|
||||
) -> Self {
|
||||
Self {
|
||||
version: CURRENT_VERSION,
|
||||
data: IpPacketResponseData::Health(HealthResponse {
|
||||
request_id,
|
||||
reply_to,
|
||||
reply: HealthResponseReply {
|
||||
build_info,
|
||||
routable,
|
||||
},
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn id(&self) -> Option<u64> {
|
||||
match &self.data {
|
||||
IpPacketResponseData::StaticConnect(response) => Some(response.request_id),
|
||||
IpPacketResponseData::DynamicConnect(response) => Some(response.request_id),
|
||||
IpPacketResponseData::Disconnect(response) => Some(response.request_id),
|
||||
IpPacketResponseData::UnrequestedDisconnect(_) => None,
|
||||
IpPacketResponseData::Data(_) => None,
|
||||
IpPacketResponseData::Pong(response) => Some(response.request_id),
|
||||
IpPacketResponseData::Health(response) => Some(response.request_id),
|
||||
IpPacketResponseData::Info(response) => Some(response.request_id),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn recipient(&self) -> Option<&Recipient> {
|
||||
match &self.data {
|
||||
IpPacketResponseData::StaticConnect(response) => Some(&response.reply_to),
|
||||
IpPacketResponseData::DynamicConnect(response) => Some(&response.reply_to),
|
||||
IpPacketResponseData::Disconnect(response) => Some(&response.reply_to),
|
||||
IpPacketResponseData::UnrequestedDisconnect(response) => Some(&response.reply_to),
|
||||
IpPacketResponseData::Data(_) => None,
|
||||
IpPacketResponseData::Pong(response) => Some(&response.reply_to),
|
||||
IpPacketResponseData::Health(response) => Some(&response.reply_to),
|
||||
IpPacketResponseData::Info(response) => Some(&response.reply_to),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_bytes(&self) -> Result<Vec<u8>, bincode::Error> {
|
||||
use bincode::Options;
|
||||
make_bincode_serializer().serialize(self)
|
||||
}
|
||||
|
||||
pub fn from_reconstructed_message(
|
||||
message: &nym_sphinx::receiver::ReconstructedMessage,
|
||||
) -> Result<Self, bincode::Error> {
|
||||
use bincode::Options;
|
||||
make_bincode_serializer().deserialize(&message.message)
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum IpPacketResponseData {
|
||||
// Response for a static connect request
|
||||
StaticConnect(StaticConnectResponse),
|
||||
|
||||
// Response for a dynamic connect request
|
||||
DynamicConnect(DynamicConnectResponse),
|
||||
|
||||
// Response for a disconnect initiqated by the client
|
||||
Disconnect(DisconnectResponse),
|
||||
|
||||
// Message from the server that the client got disconnected without the client initiating it
|
||||
UnrequestedDisconnect(UnrequestedDisconnect),
|
||||
|
||||
// Response to a data request
|
||||
Data(DataResponse),
|
||||
|
||||
// Response to ping request
|
||||
Pong(PongResponse),
|
||||
|
||||
// Response for a health request
|
||||
Health(HealthResponse),
|
||||
|
||||
// Info response. This can be anything from informative messages to errors
|
||||
Info(InfoResponse),
|
||||
}
|
||||
|
||||
impl IpPacketResponseData {
|
||||
pub fn to_bytes(&self) -> Result<Vec<u8>, bincode::Error> {
|
||||
use bincode::Options;
|
||||
make_bincode_serializer().serialize(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct StaticConnectResponse {
|
||||
pub request_id: u64,
|
||||
pub reply_to: Recipient,
|
||||
pub reply: StaticConnectResponseReply,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum StaticConnectResponseReply {
|
||||
Success,
|
||||
Failure(StaticConnectFailureReason),
|
||||
}
|
||||
|
||||
impl StaticConnectResponseReply {
|
||||
pub fn is_success(&self) -> bool {
|
||||
match self {
|
||||
StaticConnectResponseReply::Success => true,
|
||||
StaticConnectResponseReply::Failure(_) => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, thiserror::Error)]
|
||||
pub enum StaticConnectFailureReason {
|
||||
#[error("requested ip address is already in use")]
|
||||
RequestedIpAlreadyInUse,
|
||||
#[error("requested nym-address is already in use")]
|
||||
RequestedNymAddressAlreadyInUse,
|
||||
#[error("{0}")]
|
||||
Other(String),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct DynamicConnectResponse {
|
||||
pub request_id: u64,
|
||||
pub reply_to: Recipient,
|
||||
pub reply: DynamicConnectResponseReply,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum DynamicConnectResponseReply {
|
||||
Success(DynamicConnectSuccess),
|
||||
Failure(DynamicConnectFailureReason),
|
||||
}
|
||||
|
||||
impl DynamicConnectResponseReply {
|
||||
pub fn is_success(&self) -> bool {
|
||||
match self {
|
||||
DynamicConnectResponseReply::Success(_) => true,
|
||||
DynamicConnectResponseReply::Failure(_) => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct DynamicConnectSuccess {
|
||||
pub ips: IpPair,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, thiserror::Error)]
|
||||
pub enum DynamicConnectFailureReason {
|
||||
#[error("requested nym-address is already in use")]
|
||||
RequestedNymAddressAlreadyInUse,
|
||||
#[error("no available ip address")]
|
||||
NoAvailableIp,
|
||||
#[error("{0}")]
|
||||
Other(String),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct DisconnectResponse {
|
||||
pub request_id: u64,
|
||||
pub reply_to: Recipient,
|
||||
pub reply: DisconnectResponseReply,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum DisconnectResponseReply {
|
||||
Success,
|
||||
Failure(DisconnectFailureReason),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, thiserror::Error)]
|
||||
pub enum DisconnectFailureReason {
|
||||
#[error("requested nym-address is not currently connected")]
|
||||
RequestedNymAddressNotConnected,
|
||||
#[error("{0}")]
|
||||
Other(String),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct UnrequestedDisconnect {
|
||||
pub reply_to: Recipient,
|
||||
pub reason: UnrequestedDisconnectReason,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, thiserror::Error)]
|
||||
pub enum UnrequestedDisconnectReason {
|
||||
#[error("client mixnet traffic timeout")]
|
||||
ClientMixnetTrafficTimeout,
|
||||
#[error("client tun traffic timeout")]
|
||||
ClientTunTrafficTimeout,
|
||||
#[error("{0}")]
|
||||
Other(String),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct DataResponse {
|
||||
pub ip_packet: bytes::Bytes,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct PongResponse {
|
||||
pub request_id: u64,
|
||||
pub reply_to: Recipient,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct HealthResponse {
|
||||
pub request_id: u64,
|
||||
pub reply_to: Recipient,
|
||||
pub reply: HealthResponseReply,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct HealthResponseReply {
|
||||
// Return the binary build information of the IPR
|
||||
pub build_info: nym_bin_common::build_information::BinaryBuildInformationOwned,
|
||||
// Return if the IPR has performed a successful routing test.
|
||||
pub routable: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct InfoResponse {
|
||||
pub request_id: u64,
|
||||
pub reply_to: Recipient,
|
||||
pub reply: InfoResponseReply,
|
||||
pub level: InfoLevel,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, thiserror::Error)]
|
||||
pub enum InfoResponseReply {
|
||||
#[error("{msg}")]
|
||||
Generic { msg: String },
|
||||
#[error(
|
||||
"version mismatch: response is v{request_version} and response is v{response_version}"
|
||||
)]
|
||||
VersionMismatch {
|
||||
request_version: u8,
|
||||
response_version: u8,
|
||||
},
|
||||
#[error("destination failed exit policy filter check: {dst}")]
|
||||
ExitPolicyFilterCheckFailed { dst: String },
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum InfoLevel {
|
||||
Info,
|
||||
Warn,
|
||||
Error,
|
||||
}
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
use crate::storage::wasm_client_traits::WasmClientStorageError;
|
||||
use crate::topology::WasmTopologyError;
|
||||
use js_sys::Promise;
|
||||
use nym_client_core::client::base_client::storage::gateways_storage::BadGateway;
|
||||
use nym_client_core::error::ClientCoreError;
|
||||
use nym_crypto::asymmetric::identity::Ed25519RecoveryError;
|
||||
|
||||
@@ -32,17 +32,4 @@ pub use nym_validator_client::{DirectSigningReqwestRpcNyxdClient, QueryReqwestRp
|
||||
pub use nym_validator_client::client::IdentityKey;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[wasm_bindgen]
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub fn set_panic_hook() {
|
||||
// When the `console_error_panic_hook` feature is enabled, we can call the
|
||||
// `set_panic_hook` function at least once during initialization, and then
|
||||
// we will get better error messages if our code ever panics.
|
||||
//
|
||||
// For more details see
|
||||
// https://github.com/rustwasm/console_error_panic_hook#readme
|
||||
#[cfg(feature = "console_error_panic_hook")]
|
||||
console_error_panic_hook::set_once();
|
||||
}
|
||||
pub use wasm_utils::set_panic_hook;
|
||||
|
||||
@@ -12,7 +12,7 @@ futures = { workspace = true }
|
||||
js-sys = { workspace = true }
|
||||
wasm-bindgen = { workspace = true }
|
||||
wasm-bindgen-futures = { workspace = true }
|
||||
getrandom = { workspace = true, features=["js"], optional = true }
|
||||
getrandom = { workspace = true, features = ["js"], optional = true }
|
||||
gloo-utils = { workspace = true }
|
||||
gloo-net = { workspace = true, features = ["websocket"], optional = true }
|
||||
#gloo-net = { path = "../../../../gloo/crates/net", features = ["websocket"], optional = true }
|
||||
|
||||
@@ -21,7 +21,7 @@ macro_rules! wasm_error {
|
||||
|
||||
impl From<$struct> for js_sys::Promise {
|
||||
fn from(value: $struct) -> Self {
|
||||
Promise::reject(&value.into())
|
||||
js_sys::Promise::reject(&value.into())
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -41,6 +41,18 @@ macro_rules! console_error {
|
||||
($($t:tt)*) => ($crate::error(&format_args!($($t)*).to_string()))
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn set_panic_hook() {
|
||||
// When the `console_error_panic_hook` feature is enabled, we can call the
|
||||
// `set_panic_hook` function at least once during initialization, and then
|
||||
// we will get better error messages if our code ever panics.
|
||||
//
|
||||
// For more details see
|
||||
// https://github.com/rustwasm/console_error_panic_hook#readme
|
||||
#[cfg(feature = "console_error_panic_hook")]
|
||||
console_error_panic_hook::set_once();
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
#[wasm_bindgen(js_namespace = console)]
|
||||
|
||||
@@ -27,13 +27,15 @@
|
||||
- [Maintenance](nodes/maintenance.md)
|
||||
- [Manual Node Upgrade](nodes/manual-upgrade.md)
|
||||
- [Automatic Node Upgrade: Nymvisor Setup and Usage](nodes/nymvisor-upgrade.md)
|
||||
- [Performance Testing](testing/performance.md)
|
||||
- [Node Setup](testing/node-setup.md)
|
||||
- [Metrics Monitoring](testing/templates.md)
|
||||
- [Performance Monitoring & Testing](testing/performance.md)
|
||||
<!--- [Node Setup](testing/node-setup.md)-->
|
||||
- [Gateway Probe](testing/gateway-probe.md)
|
||||
- [Prometheus & Grafana](testing/prometheus-grafana.md)
|
||||
- [ExploreNYM scripts](testing/explorenym-scripts.md)
|
||||
<!-- - [Run in a Docker](testing/docker-monitor.md) -->
|
||||
|
||||
|
||||
|
||||
# Troubleshooting
|
||||
|
||||
- [VPS Setup](troubleshooting/vps-isp.md)
|
||||
@@ -56,6 +58,7 @@
|
||||
|
||||
- [Exit Gateway](legal/exit-gateway.md)
|
||||
- [Community Counsel](legal/community-counsel.md)
|
||||
- [ISP List](legal/isp-list.md)
|
||||
- [Jurisdictions](legal/jurisdictions.md)
|
||||
- [Switzerland](legal/swiss.md)
|
||||
- [United States](legal/united-states.md)
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
**ISP**,**Locations**,**Public IPv6**,**Crypto Payments**,**Comments**,**Last Updated**
|
||||
[Flokinet](https://flokinet.is),"Netherlands, Iceland, Romania,France","Yes, needs a ticket and custom setup","yes, including XMR","Very slow customer support","05/2024"
|
||||
[BitLaunch](https://bitlaunch.io),"Canada, USA, UK","No","Yes","Expensive. Digial Ocean through BitLanch has IPv6","05/2024"
|
||||
[Hostinger](https://hostinger.com),"France, Lithuania, India, USA, Brazil","Yes, out of the box","Yes","Crypto payments must be done per each server monthly or annually.","05/2024"
|
||||
[Linode](https://linode.com),"USA, Canada, Japan, India, Indonesia, Sweden, Netherlands, Germany, Brazil, France, UK, Australia, Italy","Yes out of the box","No, only through [BitLAunch](https://bitlaunch.io)","IPv6 sometimes need to be re-added in Networking tab, no reboot needed","05/2024"
|
||||
[Cherry Servers](https://www.cherryservers.com),"Lithuania, Netherlands, USA, Singapore","No","Yes","Issued IP doesn’t match the location offered by the provider.","05/2024"
|
||||
[Njalla](https://nja.la),"Sweden","Yes","Yes","Privacy vandguards! The biggest VPS 45 is 3 cores only, but it works better than many “larger” servers on the market.","05/2024"
|
||||
[HostSailor](https://hostsailor.com),"USA","Yes, based on ticket","Yes","The IPv6 setup needs custom research and is not documented","05/2024"
|
||||
|
@@ -0,0 +1,25 @@
|
||||
# Where to host your `nym-node`?
|
||||
|
||||
```admonish info
|
||||
The entire content of this page is under [Creative Commons Attribution 4.0 International Public License](https://creativecommons.org/licenses/by/4.0/).
|
||||
```
|
||||
|
||||
Inspired by a valuable resource, done by Tor community - [*Good Bad ISPs*](https://community.torproject.org/relay/community-resources/good-bad-isps/), LunarDAO squad initiated a table customised for Nym Exit Gateways operators.
|
||||
|
||||
This ISP list is fully managed by Nym operator community and it serves as a space to share their experience of running Exit Gateways on various Internet Service Providers (ISPs). The ISPs greatly differ in regards to services they offer as well as to their openess of hosting exit routing software.
|
||||
|
||||
Please share any experiences running a node like policies, complains, legal issues and solutions, discrepancy between offers and reality (bandwidth, IP range, locations) or anything regarding pricing or customer support.
|
||||
|
||||
If you came across any legal findings, please share them in our [list of jurisdictions](jurisdictions.md).
|
||||
|
||||
While we trust that Nym node operators are honest, we would like to ask everyone to do your own research.
|
||||
|
||||
```admonish caution title=""
|
||||
To edit or add information to the ISP list, make changes to the csv file located [here](https://github.com/nymtech/nym/blob/develop/documentation/operators/src/data/isp-sheet.csv) and submit your edits as a pull request according to [this guide](add-content.md).
|
||||
```
|
||||
|
||||
```admonish note title=""
|
||||
As of now the list is quite short. When it grows, we can divide it according the localities of the listed ISPs.
|
||||
```
|
||||
|
||||
<!--cmdrun python3 ../../../scripts/csv2md.py ../data/isp-sheet.csv -s 0 -->
|
||||
@@ -10,9 +10,9 @@ A suboptimally configured VPS often results in a non-functional node. To follow
|
||||
|
||||
You will need to rent a VPS to run your node on. One key reason for this is that your node **must be able to send TCP data using both IPv4 and IPv6** (as other nodes you talk to may use either protocol).
|
||||
|
||||
Tor community created a very helpful table called [*Good Bad ISPs*](https://community.torproject.org/relay/community-resources/good-bad-isps/), use that one as a guideline for your choice of ISP for your VPS.
|
||||
Tor community created a very helpful table called [*Good Bad ISPs*](https://community.torproject.org/relay/community-resources/good-bad-isps/), you can use that one as a guideline for your choice of ISP for your VPS.
|
||||
|
||||
Currently we run [performance testing](../testing/performance.md) events to find out the best optimization. Sphinx packet decryption is CPU-bound, so more fast cores the better throughput.
|
||||
**Update:** Nym community started an ISP table called [*Where to host your nym node?*](../legal/isp-list.md), check it out and add your findings!
|
||||
|
||||
### `nym-node`
|
||||
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
# Nym Gateway Probe
|
||||
|
||||
Nym Node operators running Gateway functionality are already familiar with the monitoring tool [Harbourmaster.nymtech.net](https://harbourmaster.nymtech.net). Under the hood of Nym Harbourmaster runs iterations of `nym-gateway-probe` doing various checks and displaying the results on the interface. Operators don't have to rely on the probe ran by Nym and wait for the data to refresh. With `nym-gateway-probe` everyone can check any Gateway's networking status from their own computer at any time. In one command the client queries data from:
|
||||
|
||||
- [`nym-api`](https://validator.nymtech.net/api/)
|
||||
- [`explorer-api`](https://explorer.nymtech.net/api/)
|
||||
- [`harbour-master`](https://harbourmaster.nymtech.net/)
|
||||
|
||||
|
||||
## Preparation
|
||||
|
||||
We recommend to have install all [the prerequisites](../binaries/building-nym.md#prerequisites) needed to build `nym-node` from source including latest [Rust Toolchain](https://www.rust-lang.org/tools/install).
|
||||
|
||||
## Installation
|
||||
|
||||
`nym-gateway-probe` source code is in [`nym-vpn-client`](https://github.com/nymtech/nym-vpn-client) repository. The client needs to be build from source.
|
||||
|
||||
1. Clone the repository:
|
||||
|
||||
```sh
|
||||
git clone https://github.com/nymtech/nym-vpn-client.git
|
||||
```
|
||||
|
||||
2. Build `nym-gateway-probe`:
|
||||
|
||||
```sh
|
||||
cd nym-vpn-client
|
||||
|
||||
cargo build --release -p nym-gateway-probe
|
||||
```
|
||||
|
||||
## Running the client
|
||||
|
||||
```sh
|
||||
./target/release/nym-gateway-probe --help
|
||||
```
|
||||
~~~admonish collapsible=true
|
||||
```
|
||||
Usage: nym-gateway-probe [OPTIONS]
|
||||
|
||||
Options:
|
||||
-c, --config-env-file <CONFIG_ENV_FILE> Path pointing to an env file describing the network
|
||||
-g, --gateway <GATEWAY>
|
||||
-n, --no-log
|
||||
-h, --help Print help
|
||||
-V, --version Print version
|
||||
|
||||
```
|
||||
~~~
|
||||
|
||||
To run the client, simply add a flag `--gateway` with a targeted gateway identity key.
|
||||
|
||||
```sh
|
||||
./target/release/nym-gateway-probe --gateway <GATEWAY_IDENTITY_KEY>
|
||||
```
|
||||
|
||||
For any `nym-node --mode exit-gateway` the aim is to have this outcome:
|
||||
```sh
|
||||
{
|
||||
"gateway": "<GATEWAY_IDENTITY_KEY>",
|
||||
"outcome": {
|
||||
"as_entry": {
|
||||
"can_connect": true,
|
||||
"can_route": true
|
||||
},
|
||||
"as_exit": {
|
||||
"can_connect": true,
|
||||
"can_route_ip_v4": true,
|
||||
"can_route_ip_external_v4": true,
|
||||
"can_route_ip_v6": true,
|
||||
"can_route_ip_external_v6": true
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If you don't provide a `--gateway` flag it will pick a random one to test.
|
||||
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
# Node Setup for Performance Testing Event
|
||||
|
||||
```admonish info
|
||||
For the moment we paused Fast and Furious `perf` environment. Nym Mainnet environment will be used for future tests, please wait for further instructions.
|
||||
```
|
||||
|
||||
To join the [Performance testing event]({{performance_testing_webpage}}) node operators need to do proceed with the following tasks:
|
||||
|
||||
1. **[Sign their node]({{performance_testing_webpage}}) into the testing environment**
|
||||
2. **[Configure their node](#node-configuration) for the test**
|
||||
3. (*Not mandatory*) [Setup metric monitoring system](templates.md) to observe node performance at any time
|
||||
3. (*Not mandatory*) [Setup metric monitoring system](performance.md#monitoring) to observe node performance at any time
|
||||
|
||||
## Node Configuration
|
||||
|
||||
|
||||
@@ -1,8 +1,34 @@
|
||||
# Performance Testing
|
||||
# Performance Monitoring & Testing
|
||||
|
||||
> To configure your node for a testing event, visit [node setup page](node-setup.md).
|
||||
Nym Mixnet has been running on mainnet for quite some time. There is still work to be done in order for the network to meet its full potential - mass adoption of privacy through fully distributed Mixnet.
|
||||
|
||||
Nym Mixnet has been running on mainnet for quite some time. There is still work to be done in order for the network to meet its full potential - mass adoption of privacy through fully distributed Mixnet.
|
||||
As developers we need to be constantly improving the software. Operators have as much important role, keep their nodes up to date, monitor their performance and share their feedback with the rest of the community and core developers.
|
||||
|
||||
Therefore [monitoring](#monitoring) and [testing](#testing) are essential pieces of our common work. We call out all Nym operators to join the efforts!
|
||||
|
||||
## Monitoring
|
||||
|
||||
There are multiple ways to monitor performance of nodes and the machines on which they run. For the purpose of maximal privacy and decentralisation of the data - preventing Nym Mixnet from any global adversary takeover - we created these pages as a source of mutual empowerment, a place where operators can share and learn new skills to **setup metrics monitors on their own infrastructure**.
|
||||
|
||||
### Guides to Setup Own Metrics
|
||||
|
||||
A list of different scripts, templates and guides for easier navigation:
|
||||
|
||||
* [`nym-gateway-probe`](gateway-probe.md) - a useful tool used under the hood of [harbourmaster.nymtech.net](https://harbourmaster.nymtech.net)
|
||||
* [Prometheus and Grafana](prometheus-grafana.md) self-hosted setup
|
||||
* [Nym-node CPU cron service](https://gist.github.com/tommyv1987/97e939a7adf491333d686a8eaa68d4bd) - an easy bash script by Nym core developer [@tommy1987](https://gist.github.com/tommyv1987), designed to monitor a CPU usage of your node, running locally
|
||||
* Nym's script [`prom_targets.py`](https://github.com/nymtech/nym/blob/develop/scripts/prom_targets.py) - a useful python program to request data from API and can be run on its own or plugged to more sophisticated flows
|
||||
|
||||
### Collecting Testing Metrics
|
||||
|
||||
For the purpose of the performance testing Nym core developers plan to run instances of Prometheus and Grafana connected to Node explorer in the house. The network overall key insights we seek from these tests are primarily internal. We're focused on pinpointing bottlenecks, capacity loads, and monitoring cpu usage on the nodes' machines.
|
||||
|
||||
|
||||
## Testing
|
||||
|
||||
```admonish info
|
||||
For the moment we paused Fast and Furious `perf` environment. Nym Mainnet environment will be used for future tests, please wait for further instructions.
|
||||
```
|
||||
|
||||
Nym asks its decentralised community of operators to join a series of performance testing events in order to **increase the overall quality of the Mixnet**. The main takeaways of such event are:
|
||||
|
||||
@@ -21,7 +47,7 @@ Visit [Fast and Furious web page]({{performance_testing_webpage}}) and [Nym Harb
|
||||
|
||||
* Nym runs a paralel network environment [validator.performance.nymte.ch]({{performance_validator}}) with a chain ID `perf`
|
||||
* Operators of Nym Nodes join by following easy steps on [performance testing web page]({{performance_testing_webpage}}), including simplified node authentication signature (while keep running their nodes on the mainnet)
|
||||
* Once signed in, operators will be asked to swap their binary for the modified version with metrics endpoint to be able to connect their own [monitoring system](templates.md)
|
||||
* Once signed in, operators will be asked to swap their binary for the modified version with metrics endpoint to be able to connect their own [monitoring system](#monitoring)
|
||||
* Core node data will be fed to a unique mixnet contract for the `perf` side chain
|
||||
* Nym starts a new API and start packet transition in high load through these nodes in both settings
|
||||
* Nym tracks packet flow using Prometheus and Grafana
|
||||
@@ -31,4 +57,5 @@ Visit [Fast and Furious web page]({{performance_testing_webpage}}) and [Nym Harb
|
||||
## More Information
|
||||
|
||||
* What happens after the test or what operators get for participating is shared up to date on the [performance testing web page]({{performance_testing_webpage}})
|
||||
* Visit our guides to [setup metrics template](templates.md) and learn how to operate them in self-custodial way
|
||||
|
||||
|
||||
|
||||
@@ -16,6 +16,8 @@ Begin with the steps listed in [*Connectivity Test and Configuration*](../nodes/
|
||||
2. Checkout your VPS dashboard and make sure your IPv6-public enabled.
|
||||
3. If you are able to add IPv6 address `/64` range, do it.
|
||||
|
||||
**Update:** Nym community started an ISP table called [*Where to host your nym node?*](../legal/isp-list.md), check it out and add your findings!
|
||||
|
||||

|
||||
|
||||
4. Search or ask your ISP for additional documentation related to IPv6 routing and ask them to provide you with `IPv6 IP address` and `IPv6 IP gateway address`
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
"""CLI to display .csv files as markdown"""
|
||||
|
||||
import argparse
|
||||
import pandas as pd
|
||||
import sys
|
||||
import csv
|
||||
|
||||
def create_table(args):
|
||||
"""Imports csv and creates a table"""
|
||||
file = args.file
|
||||
csv = pd.read_csv(file)
|
||||
if args.sort != None:
|
||||
csv = csv.sort_values(csv.columns[args.sort])
|
||||
if args.table:
|
||||
table = csv.to_markdown(tablefmt="grid", index=args.index)
|
||||
else:
|
||||
table = csv.to_markdown(index=args.index)
|
||||
return table
|
||||
|
||||
def display_file(args):
|
||||
"""Display csv file as a table"""
|
||||
table = create_table(args)
|
||||
print(table)
|
||||
|
||||
def panic(msg):
|
||||
"""Error message print"""
|
||||
print(f"error: {msg}", file=sys.stderr)
|
||||
sys.exit(-1)
|
||||
|
||||
def parser_main():
|
||||
"""Main function initializing ArgumentParser, storing arguments and executing commands."""
|
||||
# Top level parser
|
||||
parser = argparse.ArgumentParser(
|
||||
prog='CSV2MD',
|
||||
description='''Displays .csv files in markdown''',
|
||||
epilog='''Code is power!'''
|
||||
)
|
||||
|
||||
# Parser arguments
|
||||
parser.add_argument("-V","--version", action="version", version='%(prog)s 1.1.0')
|
||||
parser.add_argument("file", help="path/to/file.csv")
|
||||
parser.add_argument("-t","--table", default=False, action="store_true", help="output with a tabulate option for terminal reading - does not render in mdbook")
|
||||
parser.add_argument("-i","--index", default=False, action="store_true", help="output with an index column")
|
||||
parser.add_argument("-s","--sort", type=int, help="supply with column index to sort your output accordingly (ascending way)")
|
||||
|
||||
parser.set_defaults(func=display_file)
|
||||
args = parser.parse_args()
|
||||
|
||||
try:
|
||||
args.func(args)
|
||||
except AttributeError as e:
|
||||
msg = f"{e}.\nPlease run with --help or read the error message in case your .csv file is corrupted."
|
||||
panic(msg)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser_main()
|
||||
+2
-2
@@ -11,7 +11,7 @@ authors = [
|
||||
"Drazen Urch <durch@users.noreply.github.com>",
|
||||
]
|
||||
edition = "2021"
|
||||
rust-version = "1.70.0"
|
||||
rust-version = "1.76.0"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@@ -43,7 +43,7 @@ tokio = { workspace = true, features = ["rt-multi-thread", "macros", "signal", "
|
||||
tokio-stream = { workspace = true }
|
||||
url = { workspace = true }
|
||||
|
||||
ts-rs = { workspace = true, optional = true}
|
||||
ts-rs = { workspace = true, optional = true }
|
||||
|
||||
anyhow = { workspace = true }
|
||||
getset = { workspace = true }
|
||||
|
||||
@@ -6,6 +6,7 @@ use serde::{Deserialize, Serialize};
|
||||
|
||||
pub mod coconut;
|
||||
pub mod models;
|
||||
pub mod pagination;
|
||||
|
||||
pub trait Deprecatable {
|
||||
fn deprecate(self) -> Deprecated<Self>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::pagination::PaginatedResponse;
|
||||
use cosmwasm_std::{Addr, Coin, Decimal};
|
||||
use nym_mixnet_contract_common::families::FamilyHead;
|
||||
use nym_mixnet_contract_common::mixnode::MixNodeDetails;
|
||||
@@ -569,3 +570,28 @@ pub struct SignerInformationResponse {
|
||||
|
||||
pub verification_key: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, Default)]
|
||||
pub struct TestNode {
|
||||
pub node_id: Option<u32>,
|
||||
pub identity_key: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema)]
|
||||
pub struct TestRoute {
|
||||
pub gateway: TestNode,
|
||||
pub layer1: TestNode,
|
||||
pub layer2: TestNode,
|
||||
pub layer3: TestNode,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema)]
|
||||
pub struct PartialTestResult {
|
||||
pub monitor_run_id: i64,
|
||||
pub timestamp: i64,
|
||||
pub overall_reliability_for_all_routes_in_monitor_run: Option<u8>,
|
||||
pub test_routes: TestRoute,
|
||||
}
|
||||
|
||||
pub type MixnodeTestResultResponse = PaginatedResponse<PartialTestResult>;
|
||||
pub type GatewayTestResultResponse = PaginatedResponse<PartialTestResult>;
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize, JsonSchema)]
|
||||
pub struct Pagination {
|
||||
pub total: usize,
|
||||
pub page: u32,
|
||||
pub size: usize,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, JsonSchema)]
|
||||
pub struct PaginatedResponse<T> {
|
||||
pub pagination: Pagination,
|
||||
pub data: Vec<T>,
|
||||
}
|
||||
@@ -1,12 +1,15 @@
|
||||
// Copyright 2023-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::coconut::api_routes::helpers::build_credentials_response;
|
||||
use crate::coconut::error::{CoconutError, Result};
|
||||
use crate::coconut::helpers::{accepted_vote_err, blind_sign};
|
||||
use crate::coconut::state::State;
|
||||
use crate::coconut::storage::CoconutStorageExt;
|
||||
use std::ops::Deref;
|
||||
|
||||
use k256::ecdsa::signature::Verifier;
|
||||
use rand::rngs::OsRng;
|
||||
use rand::RngCore;
|
||||
use rocket::serde::json::Json;
|
||||
use rocket::State as RocketState;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
use nym_api_requests::coconut::models::{
|
||||
CredentialsRequestBody, EpochCredentialsResponse, FreePassNonceResponse, FreePassRequest,
|
||||
IssuedCredentialResponse, IssuedCredentialsResponse,
|
||||
@@ -23,12 +26,12 @@ use nym_credentials::coconut::bandwidth::{
|
||||
bandwidth_credential_params, CredentialType, IssuanceBandwidthCredential,
|
||||
};
|
||||
use nym_validator_client::nyxd::Coin;
|
||||
use rand::rngs::OsRng;
|
||||
use rand::RngCore;
|
||||
use rocket::serde::json::Json;
|
||||
use rocket::State as RocketState;
|
||||
use std::ops::Deref;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
use crate::coconut::api_routes::helpers::build_credentials_response;
|
||||
use crate::coconut::error::{CoconutError, Result};
|
||||
use crate::coconut::helpers::{accepted_vote_err, blind_sign};
|
||||
use crate::coconut::state::State;
|
||||
use crate::coconut::storage::CoconutStorageExt;
|
||||
|
||||
mod helpers;
|
||||
|
||||
@@ -98,17 +101,30 @@ pub async fn post_free_pass(
|
||||
|
||||
validate_freepass_public_attributes(&freepass_request_body)?;
|
||||
|
||||
// grab the admin of the bandwidth contract
|
||||
let Some(authorised_admin) = state.get_bandwidth_contract_admin().await? else {
|
||||
error!("our bandwidth contract does not have an admin set! We won't be able to migrate the contract! We should redeploy it ASAP");
|
||||
return Err(CoconutError::MissingBandwidthContractAdmin);
|
||||
// check for explicit admin
|
||||
let explicit_admin = state.get_authorised_freepass_requester().await;
|
||||
|
||||
// otherwise fallback to bandwidth contract admin
|
||||
let bandwidth_contract_admin = state
|
||||
.get_bandwidth_contract_admin()
|
||||
.await
|
||||
.cloned()
|
||||
.inspect_err(|_| error!("our bandwidth contract does not have an admin set! We won't be able to migrate the contract! We should redeploy it ASAP"))
|
||||
.ok()
|
||||
.flatten();
|
||||
|
||||
// extract account prefix
|
||||
let prefix = match (&explicit_admin, &bandwidth_contract_admin) {
|
||||
(None, None) => {
|
||||
error!("neither explicit admin nor bandwidth contract admin has been set!");
|
||||
return Err(CoconutError::MissingBandwidthContractAddress);
|
||||
}
|
||||
(Some(addr), _) => addr.prefix(),
|
||||
(None, Some(addr)) => addr.prefix(),
|
||||
};
|
||||
|
||||
// derive the address out of the provided pubkey
|
||||
let requester = match freepass_request_body
|
||||
.cosmos_pubkey
|
||||
.account_id(authorised_admin.prefix())
|
||||
{
|
||||
let requester = match freepass_request_body.cosmos_pubkey.account_id(prefix) {
|
||||
Ok(address) => address,
|
||||
Err(err) => {
|
||||
return Err(CoconutError::AdminAccountDerivationFailure {
|
||||
@@ -116,12 +132,16 @@ pub async fn post_free_pass(
|
||||
})
|
||||
}
|
||||
};
|
||||
debug!("derived the following address out of the provided public key: {requester}. Going to check it against the authorised admin ({authorised_admin})");
|
||||
debug!("derived the following address out of the provided public key: {requester}. Going to check it against the authorised admin ({explicit_admin:?}) and fallback to bandwidth contract admin: {bandwidth_contract_admin:?}");
|
||||
|
||||
if &requester != authorised_admin {
|
||||
// check if request matches any address
|
||||
if Some(&requester) != explicit_admin.as_ref()
|
||||
&& Some(&requester) != bandwidth_contract_admin.as_ref()
|
||||
{
|
||||
return Err(CoconutError::UnauthorisedFreePassAccount {
|
||||
requester,
|
||||
authorised_admin: authorised_admin.clone(),
|
||||
explicit_admin,
|
||||
bandwidth_contract_admin,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -37,10 +37,20 @@ pub enum CoconutError {
|
||||
#[error("failed to derive the admin account from the provided public key: {formatted_source}")]
|
||||
AdminAccountDerivationFailure { formatted_source: String },
|
||||
|
||||
#[error("the requester of the free pass ({requester}) is not authorised. the only allowed account is {authorised_admin}.")]
|
||||
#[error("failed to query for the authorised freepass requester address: {source}")]
|
||||
FreepassAuthorisedFreepassRequesterQueryFailure {
|
||||
#[from]
|
||||
source: reqwest::Error,
|
||||
},
|
||||
|
||||
#[error("the provided authorised freepass requester address ({address}) is not a valid cosmos address")]
|
||||
MalformedAuthorisedFreepassRequesterAddress { address: String },
|
||||
|
||||
#[error("the requester of the free pass ({requester}) is not authorised. the only allowed account is {explicit_admin:?} or {bandwidth_contract_admin:?}.")]
|
||||
UnauthorisedFreePassAccount {
|
||||
requester: AccountId,
|
||||
authorised_admin: AccountId,
|
||||
explicit_admin: Option<AccountId>,
|
||||
bandwidth_contract_admin: Option<AccountId>,
|
||||
},
|
||||
|
||||
#[error("failed to verify signature on the provided free pass request")]
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
use crate::coconut::client::Client as LocalClient;
|
||||
use crate::coconut::comm::APICommunicationChannel;
|
||||
use crate::coconut::deposit::validate_deposit_tx;
|
||||
use crate::coconut::error::Result;
|
||||
use crate::coconut::error::{CoconutError, Result};
|
||||
use crate::coconut::keys::KeyPair;
|
||||
use crate::coconut::storage::CoconutStorageExt;
|
||||
use crate::support::storage::NymApiStorage;
|
||||
@@ -17,6 +17,7 @@ use nym_validator_client::nyxd::{AccountId, Hash, TxResponse};
|
||||
use rand::rngs::OsRng;
|
||||
use rand::RngCore;
|
||||
use std::sync::Arc;
|
||||
use time::{Duration, OffsetDateTime};
|
||||
use tokio::sync::{OnceCell, RwLock};
|
||||
|
||||
pub use nym_credentials::coconut::bandwidth::bandwidth_credential_params;
|
||||
@@ -30,6 +31,25 @@ pub struct State {
|
||||
pub(crate) comm_channel: Arc<dyn APICommunicationChannel + Send + Sync>,
|
||||
pub(crate) storage: NymApiStorage,
|
||||
pub(crate) freepass_nonce: Arc<RwLock<[u8; 16]>>,
|
||||
pub(crate) authorised_freepass_requester: Arc<RwLock<AuthorisedFreepassRequester>>,
|
||||
}
|
||||
|
||||
const FREEPASS_REQUESTER_TTL: Duration = Duration::hours(1);
|
||||
const AUTHORISED_FREEPASS_REQUESTER_ENDPOINT: &str =
|
||||
"https://nymtech.net/.wellknown/authorised-freepass-requester.txt";
|
||||
|
||||
pub struct AuthorisedFreepassRequester {
|
||||
address: Option<AccountId>,
|
||||
refreshed_at: OffsetDateTime,
|
||||
}
|
||||
|
||||
impl Default for AuthorisedFreepassRequester {
|
||||
fn default() -> Self {
|
||||
AuthorisedFreepassRequester {
|
||||
address: None,
|
||||
refreshed_at: OffsetDateTime::UNIX_EPOCH,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl State {
|
||||
@@ -60,6 +80,7 @@ impl State {
|
||||
comm_channel,
|
||||
storage,
|
||||
freepass_nonce: Arc::new(RwLock::new(nonce)),
|
||||
authorised_freepass_requester: Arc::new(Default::default()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,6 +104,58 @@ impl State {
|
||||
.await
|
||||
}
|
||||
|
||||
async fn try_get_authorised_freepass_requester(&self) -> Result<AccountId> {
|
||||
let address = reqwest::Client::builder()
|
||||
.user_agent(format!(
|
||||
"nym-api / {} identity: {}",
|
||||
env!("CARGO_PKG_VERSION"),
|
||||
self.identity_keypair.public_key().to_base58_string()
|
||||
))
|
||||
.build()?
|
||||
.get(AUTHORISED_FREEPASS_REQUESTER_ENDPOINT)
|
||||
.send()
|
||||
.await?
|
||||
.text()
|
||||
.await?;
|
||||
let trimmed = address.trim();
|
||||
|
||||
address.parse().map_err(
|
||||
|_| CoconutError::MalformedAuthorisedFreepassRequesterAddress {
|
||||
address: trimmed.to_string(),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub async fn get_authorised_freepass_requester(&self) -> Option<AccountId> {
|
||||
{
|
||||
let cached = self.authorised_freepass_requester.read().await;
|
||||
|
||||
// the entry hasn't expired
|
||||
if cached.refreshed_at + FREEPASS_REQUESTER_TTL >= OffsetDateTime::now_utc() {
|
||||
if let Some(cached_address) = cached.address.as_ref() {
|
||||
return Some(cached_address.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// refresh cache
|
||||
let mut cache = self.authorised_freepass_requester.write().await;
|
||||
|
||||
// whatever happens, update refresh time
|
||||
cache.refreshed_at = OffsetDateTime::now_utc();
|
||||
|
||||
let refreshed = match self.try_get_authorised_freepass_requester().await {
|
||||
Ok(upstream) => upstream,
|
||||
Err(err) => {
|
||||
warn!("failed to obtain authorised freepass requester address: {err}");
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
cache.address = Some(refreshed.clone());
|
||||
Some(refreshed)
|
||||
}
|
||||
|
||||
pub async fn validate_request(
|
||||
&self,
|
||||
request: &BlindSignRequestBody,
|
||||
|
||||
@@ -53,6 +53,8 @@ pub(crate) fn node_status_routes(
|
||||
routes::get_active_set_detailed,
|
||||
routes::get_gateways_detailed,
|
||||
routes::get_gateways_detailed_unfiltered,
|
||||
routes::unstable::mixnode_test_results,
|
||||
routes::unstable::gateway_test_results,
|
||||
]
|
||||
} else {
|
||||
// in the minimal variant we would not have access to endpoints relying on existence
|
||||
|
||||
@@ -1,6 +1,19 @@
|
||||
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2021-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use nym_api_requests::models::{
|
||||
AllInclusionProbabilitiesResponse, ComputeRewardEstParam, GatewayBondAnnotated,
|
||||
GatewayCoreStatusResponse, GatewayStatusReportResponse, GatewayUptimeHistoryResponse,
|
||||
GatewayUptimeResponse, InclusionProbabilityResponse, MixNodeBondAnnotated,
|
||||
MixnodeCoreStatusResponse, MixnodeStatusReportResponse, MixnodeStatusResponse,
|
||||
MixnodeUptimeHistoryResponse, RewardEstimationResponse, StakeSaturationResponse,
|
||||
UptimeResponse,
|
||||
};
|
||||
use nym_mixnet_contract_common::MixId;
|
||||
use rocket::serde::json::Json;
|
||||
use rocket::State;
|
||||
use rocket_okapi::openapi;
|
||||
|
||||
use super::helpers::_get_gateways_detailed;
|
||||
use super::NodeStatusCache;
|
||||
use crate::node_status_api::helpers::{
|
||||
@@ -15,18 +28,6 @@ use crate::node_status_api::helpers::{
|
||||
use crate::node_status_api::models::ErrorResponse;
|
||||
use crate::storage::NymApiStorage;
|
||||
use crate::NymContractCache;
|
||||
use nym_api_requests::models::{
|
||||
AllInclusionProbabilitiesResponse, ComputeRewardEstParam, GatewayBondAnnotated,
|
||||
GatewayCoreStatusResponse, GatewayStatusReportResponse, GatewayUptimeHistoryResponse,
|
||||
GatewayUptimeResponse, InclusionProbabilityResponse, MixNodeBondAnnotated,
|
||||
MixnodeCoreStatusResponse, MixnodeStatusReportResponse, MixnodeStatusResponse,
|
||||
MixnodeUptimeHistoryResponse, RewardEstimationResponse, StakeSaturationResponse,
|
||||
UptimeResponse,
|
||||
};
|
||||
use nym_mixnet_contract_common::MixId;
|
||||
use rocket::serde::json::Json;
|
||||
use rocket::State;
|
||||
use rocket_okapi::openapi;
|
||||
|
||||
#[openapi(tag = "status")]
|
||||
#[get("/gateway/<identity>/report")]
|
||||
@@ -227,3 +228,278 @@ pub async fn get_gateways_detailed_unfiltered(
|
||||
) -> Json<Vec<GatewayBondAnnotated>> {
|
||||
Json(_get_gateways_detailed_unfiltered(cache).await)
|
||||
}
|
||||
|
||||
pub mod unstable {
|
||||
use crate::node_status_api::models::ErrorResponse;
|
||||
use crate::support::http::helpers::PaginationRequest;
|
||||
use crate::support::storage::NymApiStorage;
|
||||
use nym_api_requests::models::{
|
||||
GatewayTestResultResponse, MixnodeTestResultResponse, PartialTestResult, TestNode,
|
||||
TestRoute,
|
||||
};
|
||||
use nym_api_requests::pagination::Pagination;
|
||||
use nym_mixnet_contract_common::MixId;
|
||||
use rocket::http::Status;
|
||||
use rocket::serde::json::Json;
|
||||
use rocket::State;
|
||||
use rocket_okapi::openapi;
|
||||
use std::cmp::min;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
pub type DbId = i64;
|
||||
|
||||
// a simply in-memory cache of node details
|
||||
#[derive(Debug, Default)]
|
||||
pub struct NodeInfoCache {
|
||||
inner: Arc<RwLock<NodeInfoCacheInner>>,
|
||||
}
|
||||
|
||||
impl NodeInfoCache {
|
||||
async fn get_mix_node_details(&self, db_id: DbId, storage: &NymApiStorage) -> TestNode {
|
||||
{
|
||||
let read_guard = self.inner.read().await;
|
||||
if let Some(cached) = read_guard.mixnodes.get(&db_id) {
|
||||
trace!("cache hit for mixnode {db_id}");
|
||||
return cached.clone();
|
||||
}
|
||||
}
|
||||
trace!("cache miss for mixnode {db_id}");
|
||||
|
||||
let mut write_guard = self.inner.write().await;
|
||||
// double-check the cache in case somebody already updated it while we were waiting for the lock
|
||||
if let Some(cached) = write_guard.mixnodes.get(&db_id) {
|
||||
return cached.clone();
|
||||
}
|
||||
|
||||
let details = match storage.get_mixnode_details_by_db_id(db_id).await {
|
||||
Ok(Some(details)) => details.into(),
|
||||
Ok(None) => {
|
||||
error!("somebody has been messing with the database! details for mixnode with database id {db_id} have been removed!");
|
||||
TestNode::default()
|
||||
}
|
||||
Err(err) => {
|
||||
// don't insert into the cache in case another request is successful
|
||||
error!("failed to retrieve details for mixnode {db_id}: {err}");
|
||||
return TestNode::default();
|
||||
}
|
||||
};
|
||||
|
||||
write_guard.mixnodes.insert(db_id, details.clone());
|
||||
details
|
||||
}
|
||||
|
||||
async fn get_gateway_details(&self, db_id: DbId, storage: &NymApiStorage) -> TestNode {
|
||||
{
|
||||
let read_guard = self.inner.read().await;
|
||||
if let Some(cached) = read_guard.gateways.get(&db_id) {
|
||||
trace!("cache hit for gateway {db_id}");
|
||||
return cached.clone();
|
||||
}
|
||||
}
|
||||
trace!("cache miss for gateway {db_id}");
|
||||
|
||||
let mut write_guard = self.inner.write().await;
|
||||
// double-check the cache in case somebody already updated it while we were waiting for the lock
|
||||
if let Some(cached) = write_guard.gateways.get(&db_id) {
|
||||
return cached.clone();
|
||||
}
|
||||
|
||||
let details = match storage.get_gateway_details_by_db_id(db_id).await {
|
||||
Ok(Some(details)) => details.into(),
|
||||
Ok(None) => {
|
||||
error!("somebody has been messing with the database! details for gateway with database id {db_id} have been removed!");
|
||||
TestNode::default()
|
||||
}
|
||||
Err(err) => {
|
||||
// don't insert into the cache in case another request is successful
|
||||
error!("failed to retrieve details for gateway {db_id}: {err}");
|
||||
return TestNode::default();
|
||||
}
|
||||
};
|
||||
|
||||
write_guard.gateways.insert(db_id, details.clone());
|
||||
details
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct NodeInfoCacheInner {
|
||||
mixnodes: HashMap<DbId, TestNode>,
|
||||
gateways: HashMap<DbId, TestNode>,
|
||||
}
|
||||
|
||||
const MAX_TEST_RESULTS_PAGE_SIZE: u32 = 100;
|
||||
const DEFAULT_TEST_RESULTS_PAGE_SIZE: u32 = 50;
|
||||
|
||||
async fn _mixnode_test_results(
|
||||
mix_id: MixId,
|
||||
page: u32,
|
||||
per_page: u32,
|
||||
info_cache: &State<NodeInfoCache>,
|
||||
storage: &State<NymApiStorage>,
|
||||
) -> anyhow::Result<MixnodeTestResultResponse> {
|
||||
// convert to db offset
|
||||
// we're paging from page 0 like civilised people,
|
||||
// so we have to skip (page * per_page) results
|
||||
let offset = page * per_page;
|
||||
let limit = per_page;
|
||||
|
||||
let raw_results = storage
|
||||
.get_mixnode_detailed_statuses(mix_id, limit, offset)
|
||||
.await?;
|
||||
let total = match raw_results.first() {
|
||||
None => 0,
|
||||
Some(r) => storage.get_mixnode_detailed_statuses_count(r.db_id).await?,
|
||||
};
|
||||
|
||||
let mut partial_results = Vec::new();
|
||||
for result in raw_results {
|
||||
let gateway = info_cache
|
||||
.get_gateway_details(result.gateway_id, storage)
|
||||
.await;
|
||||
let layer1 = info_cache
|
||||
.get_mix_node_details(result.layer1_mix_id, storage)
|
||||
.await;
|
||||
let layer2 = info_cache
|
||||
.get_mix_node_details(result.layer2_mix_id, storage)
|
||||
.await;
|
||||
let layer3 = info_cache
|
||||
.get_mix_node_details(result.layer3_mix_id, storage)
|
||||
.await;
|
||||
|
||||
partial_results.push(PartialTestResult {
|
||||
monitor_run_id: result.monitor_run_id,
|
||||
timestamp: result.timestamp,
|
||||
overall_reliability_for_all_routes_in_monitor_run: result.reliability,
|
||||
test_routes: TestRoute {
|
||||
gateway,
|
||||
layer1,
|
||||
layer2,
|
||||
layer3,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
Ok(MixnodeTestResultResponse {
|
||||
pagination: Pagination {
|
||||
total,
|
||||
page,
|
||||
size: partial_results.len(),
|
||||
},
|
||||
data: partial_results,
|
||||
})
|
||||
}
|
||||
|
||||
#[openapi(tag = "UNSTABLE - DO **NOT** USE")]
|
||||
#[get("/mixnodes/unstable/<mix_id>/test-results?<pagination..>")]
|
||||
pub async fn mixnode_test_results(
|
||||
mix_id: MixId,
|
||||
pagination: PaginationRequest,
|
||||
info_cache: &State<NodeInfoCache>,
|
||||
storage: &State<NymApiStorage>,
|
||||
) -> Result<Json<MixnodeTestResultResponse>, ErrorResponse> {
|
||||
let page = pagination.page.unwrap_or_default();
|
||||
let per_page = min(
|
||||
pagination
|
||||
.per_page
|
||||
.unwrap_or(DEFAULT_TEST_RESULTS_PAGE_SIZE),
|
||||
MAX_TEST_RESULTS_PAGE_SIZE,
|
||||
);
|
||||
|
||||
match _mixnode_test_results(mix_id, page, per_page, info_cache, storage).await {
|
||||
Ok(res) => Ok(Json(res)),
|
||||
Err(err) => Err(ErrorResponse::new(
|
||||
format!("failed to retrieve mixnode test results for node {mix_id}: {err}"),
|
||||
Status::InternalServerError,
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
async fn _gateway_test_results(
|
||||
gateway_identity: &str,
|
||||
page: u32,
|
||||
per_page: u32,
|
||||
info_cache: &State<NodeInfoCache>,
|
||||
storage: &State<NymApiStorage>,
|
||||
) -> anyhow::Result<GatewayTestResultResponse> {
|
||||
// convert to db offset
|
||||
// we're paging from page 0 like civilised people,
|
||||
// so we have to skip (page * per_page) results
|
||||
let offset = page * per_page;
|
||||
let limit = per_page;
|
||||
|
||||
let raw_results = storage
|
||||
.get_gateway_detailed_statuses(gateway_identity, limit, offset)
|
||||
.await?;
|
||||
let total = match raw_results.first() {
|
||||
None => 0,
|
||||
Some(r) => storage.get_gateway_detailed_statuses_count(r.db_id).await?,
|
||||
};
|
||||
|
||||
let mut partial_results = Vec::new();
|
||||
for result in raw_results {
|
||||
let gateway = info_cache
|
||||
.get_gateway_details(result.gateway_id, storage)
|
||||
.await;
|
||||
let layer1 = info_cache
|
||||
.get_mix_node_details(result.layer1_mix_id, storage)
|
||||
.await;
|
||||
let layer2 = info_cache
|
||||
.get_mix_node_details(result.layer2_mix_id, storage)
|
||||
.await;
|
||||
let layer3 = info_cache
|
||||
.get_mix_node_details(result.layer3_mix_id, storage)
|
||||
.await;
|
||||
|
||||
partial_results.push(PartialTestResult {
|
||||
monitor_run_id: result.monitor_run_id,
|
||||
timestamp: result.timestamp,
|
||||
overall_reliability_for_all_routes_in_monitor_run: result.reliability,
|
||||
test_routes: TestRoute {
|
||||
gateway,
|
||||
layer1,
|
||||
layer2,
|
||||
layer3,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
Ok(GatewayTestResultResponse {
|
||||
pagination: Pagination {
|
||||
total,
|
||||
page,
|
||||
size: partial_results.len(),
|
||||
},
|
||||
data: partial_results,
|
||||
})
|
||||
}
|
||||
|
||||
#[openapi(tag = "UNSTABLE - DO **NOT** USE")]
|
||||
#[get("/gateways/unstable/<gateway_identity>/test-results?<pagination..>")]
|
||||
pub async fn gateway_test_results(
|
||||
gateway_identity: &str,
|
||||
pagination: PaginationRequest,
|
||||
info_cache: &State<NodeInfoCache>,
|
||||
storage: &State<NymApiStorage>,
|
||||
) -> Result<Json<GatewayTestResultResponse>, ErrorResponse> {
|
||||
let page = pagination.page.unwrap_or_default();
|
||||
let per_page = min(
|
||||
pagination
|
||||
.per_page
|
||||
.unwrap_or(DEFAULT_TEST_RESULTS_PAGE_SIZE),
|
||||
MAX_TEST_RESULTS_PAGE_SIZE,
|
||||
);
|
||||
|
||||
match _gateway_test_results(gateway_identity, page, per_page, info_cache, storage).await {
|
||||
Ok(res) => Ok(Json(res)),
|
||||
Err(err) => Err(ErrorResponse::new(
|
||||
format!(
|
||||
"failed to retrieve mixnode test results for gateway {gateway_identity}: {err}"
|
||||
),
|
||||
Status::InternalServerError,
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize, FromForm, Debug, JsonSchema)]
|
||||
pub struct PaginationRequest {
|
||||
pub page: Option<u32>,
|
||||
pub per_page: Option<u32>,
|
||||
}
|
||||
@@ -7,6 +7,7 @@ use crate::coconut::{self, comm::QueryCommunicationChannel};
|
||||
use crate::network::models::NetworkDetails;
|
||||
use crate::network::network_routes;
|
||||
use crate::node_describe_cache::DescribedNodes;
|
||||
use crate::node_status_api::routes::unstable;
|
||||
use crate::node_status_api::{self, NodeStatusCache};
|
||||
use crate::nym_contract_cache::cache::NymContractCache;
|
||||
use crate::status::{api_status_routes, ApiStatusState, SignerState};
|
||||
@@ -23,6 +24,7 @@ use rocket_cors::{AllowedHeaders, AllowedOrigins, Cors};
|
||||
use rocket_okapi::mount_endpoints_and_merged_docs;
|
||||
use rocket_okapi::swagger_ui::make_swagger_ui;
|
||||
|
||||
pub(crate) mod helpers;
|
||||
pub(crate) mod openapi;
|
||||
|
||||
pub(crate) async fn setup_rocket(
|
||||
@@ -57,7 +59,8 @@ pub(crate) async fn setup_rocket(
|
||||
.attach(setup_cors()?)
|
||||
.attach(NymContractCache::stage())
|
||||
.attach(NodeStatusCache::stage())
|
||||
.attach(CirculatingSupplyCache::stage(mix_denom.clone()));
|
||||
.attach(CirculatingSupplyCache::stage(mix_denom.clone()))
|
||||
.manage(unstable::NodeInfoCache::default());
|
||||
|
||||
// This is not a very nice approach. A lazy value would be more suitable, but that's still
|
||||
// a nightly feature: https://github.com/rust-lang/rust/issues/74465
|
||||
|
||||
@@ -4,7 +4,8 @@ use crate::network_monitor::monitor::summary_producer::{GatewayResult, MixnodeRe
|
||||
use crate::node_status_api::models::{HistoricalUptime, Uptime};
|
||||
use crate::node_status_api::utils::{ActiveGatewayStatuses, ActiveMixnodeStatuses};
|
||||
use crate::support::storage::models::{
|
||||
ActiveGateway, ActiveMixnode, NodeStatus, RewardingReport, TestingRoute,
|
||||
ActiveGateway, ActiveMixnode, GatewayDetails, MixnodeDetails, NodeStatus, RewardingReport,
|
||||
TestedGatewayStatus, TestedMixnodeStatus, TestingRoute,
|
||||
};
|
||||
use nym_mixnet_contract_common::{EpochId, IdentityKey, MixId};
|
||||
|
||||
@@ -971,4 +972,133 @@ impl StorageManager {
|
||||
|
||||
Ok(active_day_statuses)
|
||||
}
|
||||
|
||||
pub(crate) async fn get_mixnode_details_by_db_id(
|
||||
&self,
|
||||
id: i64,
|
||||
) -> Result<Option<MixnodeDetails>, sqlx::Error> {
|
||||
sqlx::query_as!(
|
||||
MixnodeDetails,
|
||||
"SELECT * FROM mixnode_details WHERE id = ?",
|
||||
id
|
||||
)
|
||||
.fetch_optional(&self.connection_pool)
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) async fn get_gateway_details_by_db_id(
|
||||
&self,
|
||||
id: i64,
|
||||
) -> Result<Option<GatewayDetails>, sqlx::Error> {
|
||||
sqlx::query_as!(
|
||||
GatewayDetails,
|
||||
"SELECT * FROM gateway_details WHERE id = ?",
|
||||
id
|
||||
)
|
||||
.fetch_optional(&self.connection_pool)
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) async fn get_mixnode_statuses_count(&self, db_id: i64) -> Result<i32, sqlx::Error> {
|
||||
sqlx::query!(
|
||||
r#"
|
||||
SELECT COUNT(*) as count
|
||||
FROM mixnode_status
|
||||
JOIN monitor_run ON mixnode_status.timestamp = monitor_run.timestamp
|
||||
JOIN testing_route ON monitor_run.id = testing_route.monitor_run_id
|
||||
WHERE mixnode_details_id = ?
|
||||
"#,
|
||||
db_id
|
||||
)
|
||||
.fetch_one(&self.connection_pool)
|
||||
.await
|
||||
.map(|record| record.count)
|
||||
}
|
||||
|
||||
pub(crate) async fn get_mixnode_statuses(
|
||||
&self,
|
||||
mix_id: MixId,
|
||||
limit: u32,
|
||||
offset: u32,
|
||||
) -> Result<Vec<TestedMixnodeStatus>, sqlx::Error> {
|
||||
sqlx::query_as!(
|
||||
TestedMixnodeStatus,
|
||||
r#"
|
||||
SELECT
|
||||
mixnode_details.id as "db_id",
|
||||
mix_id as "mix_id!",
|
||||
identity_key,
|
||||
reliability as "reliability: u8",
|
||||
monitor_run.timestamp as "timestamp!",
|
||||
gateway_id as "gateway_id!",
|
||||
layer1_mix_id as "layer1_mix_id!",
|
||||
layer2_mix_id as "layer2_mix_id!",
|
||||
layer3_mix_id as "layer3_mix_id!",
|
||||
monitor_run_id as "monitor_run_id!"
|
||||
FROM mixnode_status
|
||||
JOIN mixnode_details ON mixnode_status.mixnode_details_id = mixnode_details.id
|
||||
JOIN monitor_run ON mixnode_status.timestamp = monitor_run.timestamp
|
||||
JOIN testing_route ON monitor_run.id = testing_route.monitor_run_id
|
||||
WHERE mix_id = ?
|
||||
ORDER BY mixnode_status.timestamp DESC
|
||||
LIMIT ? OFFSET ?
|
||||
"#,
|
||||
mix_id,
|
||||
limit,
|
||||
offset
|
||||
)
|
||||
.fetch_all(&self.connection_pool)
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) async fn get_gateway_statuses_count(&self, db_id: i64) -> Result<i32, sqlx::Error> {
|
||||
sqlx::query!(
|
||||
r#"
|
||||
SELECT COUNT(*) as count
|
||||
FROM gateway_status
|
||||
JOIN monitor_run ON gateway_status.timestamp = monitor_run.timestamp
|
||||
JOIN testing_route ON monitor_run.id = testing_route.monitor_run_id
|
||||
WHERE gateway_details_id = ?
|
||||
"#,
|
||||
db_id
|
||||
)
|
||||
.fetch_one(&self.connection_pool)
|
||||
.await
|
||||
.map(|record| record.count)
|
||||
}
|
||||
|
||||
pub(crate) async fn get_gateway_statuses(
|
||||
&self,
|
||||
gateway_identity: &str,
|
||||
limit: u32,
|
||||
offset: u32,
|
||||
) -> Result<Vec<TestedGatewayStatus>, sqlx::Error> {
|
||||
sqlx::query_as!(
|
||||
TestedGatewayStatus,
|
||||
r#"
|
||||
SELECT
|
||||
gateway_details.id as "db_id",
|
||||
identity as "identity_key",
|
||||
reliability as "reliability: u8",
|
||||
monitor_run.timestamp as "timestamp!",
|
||||
gateway_id as "gateway_id!",
|
||||
layer1_mix_id as "layer1_mix_id!",
|
||||
layer2_mix_id as "layer2_mix_id!",
|
||||
layer3_mix_id as "layer3_mix_id!",
|
||||
monitor_run_id as "monitor_run_id!"
|
||||
FROM gateway_status
|
||||
JOIN gateway_details ON gateway_status.gateway_details_id = gateway_details.id
|
||||
JOIN monitor_run ON gateway_status.timestamp = monitor_run.timestamp
|
||||
JOIN testing_route ON monitor_run.id = testing_route.monitor_run_id
|
||||
WHERE identity = ?
|
||||
ORDER BY gateway_status.timestamp DESC
|
||||
LIMIT ? OFFSET ?
|
||||
"#,
|
||||
gateway_identity,
|
||||
limit,
|
||||
offset
|
||||
)
|
||||
.fetch_all(&self.connection_pool)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,9 @@ use crate::node_status_api::models::{
|
||||
use crate::node_status_api::{ONE_DAY, ONE_HOUR};
|
||||
use crate::storage::manager::StorageManager;
|
||||
use crate::storage::models::{NodeStatus, TestingRoute};
|
||||
use crate::support::storage::models::{
|
||||
GatewayDetails, MixnodeDetails, TestedGatewayStatus, TestedMixnodeStatus,
|
||||
};
|
||||
use nym_mixnet_contract_common::MixId;
|
||||
use rocket::fairing::AdHoc;
|
||||
use sqlx::ConnectOptions;
|
||||
@@ -716,4 +719,66 @@ impl NymApiStorage {
|
||||
.await
|
||||
.map_err(|err| err.into())
|
||||
}
|
||||
|
||||
pub(crate) async fn get_mixnode_details_by_db_id(
|
||||
&self,
|
||||
id: i64,
|
||||
) -> Result<Option<MixnodeDetails>, NymApiStorageError> {
|
||||
Ok(self.manager.get_mixnode_details_by_db_id(id).await?)
|
||||
}
|
||||
|
||||
pub(crate) async fn get_gateway_details_by_db_id(
|
||||
&self,
|
||||
id: i64,
|
||||
) -> Result<Option<GatewayDetails>, NymApiStorageError> {
|
||||
Ok(self.manager.get_gateway_details_by_db_id(id).await?)
|
||||
}
|
||||
|
||||
pub(crate) async fn get_mixnode_detailed_statuses_count(
|
||||
&self,
|
||||
db_id: i64,
|
||||
) -> Result<usize, NymApiStorageError> {
|
||||
Ok(self
|
||||
.manager
|
||||
.get_mixnode_statuses_count(db_id)
|
||||
.await?
|
||||
.try_into()
|
||||
.unwrap_or(usize::MAX))
|
||||
}
|
||||
|
||||
pub(crate) async fn get_mixnode_detailed_statuses(
|
||||
&self,
|
||||
mix_id: MixId,
|
||||
limit: u32,
|
||||
offset: u32,
|
||||
) -> Result<Vec<TestedMixnodeStatus>, NymApiStorageError> {
|
||||
Ok(self
|
||||
.manager
|
||||
.get_mixnode_statuses(mix_id, limit, offset)
|
||||
.await?)
|
||||
}
|
||||
|
||||
pub(crate) async fn get_gateway_detailed_statuses_count(
|
||||
&self,
|
||||
db_id: i64,
|
||||
) -> Result<usize, NymApiStorageError> {
|
||||
Ok(self
|
||||
.manager
|
||||
.get_gateway_statuses_count(db_id)
|
||||
.await?
|
||||
.try_into()
|
||||
.unwrap_or(usize::MAX))
|
||||
}
|
||||
|
||||
pub(crate) async fn get_gateway_detailed_statuses(
|
||||
&self,
|
||||
gateway_identity: &str,
|
||||
limit: u32,
|
||||
offset: u32,
|
||||
) -> Result<Vec<TestedGatewayStatus>, NymApiStorageError> {
|
||||
Ok(self
|
||||
.manager
|
||||
.get_gateway_statuses(gateway_identity, limit, offset)
|
||||
.await?)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use nym_api_requests::models::TestNode;
|
||||
use nym_mixnet_contract_common::MixId;
|
||||
|
||||
// Internally used struct to catch results from the database to calculate uptimes for given mixnode/gateway
|
||||
@@ -49,3 +50,61 @@ pub(crate) struct RewardingReport {
|
||||
|
||||
pub(crate) eligible_mixnodes: u32,
|
||||
}
|
||||
|
||||
pub struct MixnodeDetails {
|
||||
pub id: i64,
|
||||
pub mix_id: i64,
|
||||
pub owner: String,
|
||||
pub identity_key: String,
|
||||
}
|
||||
|
||||
impl From<MixnodeDetails> for TestNode {
|
||||
fn from(value: MixnodeDetails) -> Self {
|
||||
TestNode {
|
||||
node_id: Some(value.mix_id.try_into().unwrap_or(u32::MAX)),
|
||||
identity_key: Some(value.identity_key),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct GatewayDetails {
|
||||
pub id: i64,
|
||||
pub owner: String,
|
||||
pub identity: String,
|
||||
}
|
||||
|
||||
impl From<GatewayDetails> for TestNode {
|
||||
fn from(value: GatewayDetails) -> Self {
|
||||
TestNode {
|
||||
node_id: None,
|
||||
identity_key: Some(value.identity),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TestedMixnodeStatus {
|
||||
pub db_id: i64,
|
||||
pub mix_id: i64,
|
||||
pub identity_key: String,
|
||||
pub reliability: Option<u8>,
|
||||
pub timestamp: i64,
|
||||
|
||||
pub gateway_id: i64,
|
||||
pub layer1_mix_id: i64,
|
||||
pub layer2_mix_id: i64,
|
||||
pub layer3_mix_id: i64,
|
||||
pub monitor_run_id: i64,
|
||||
}
|
||||
|
||||
pub struct TestedGatewayStatus {
|
||||
pub db_id: i64,
|
||||
pub identity_key: String,
|
||||
pub reliability: Option<u8>,
|
||||
pub timestamp: i64,
|
||||
|
||||
pub gateway_id: i64,
|
||||
pub layer1_mix_id: i64,
|
||||
pub layer2_mix_id: i64,
|
||||
pub layer3_mix_id: i64,
|
||||
pub monitor_run_id: i64,
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use js_sys::Promise;
|
||||
use thiserror::Error;
|
||||
use wasm_storage::error::StorageError;
|
||||
use wasm_utils::wasm_error;
|
||||
|
||||
@@ -14,17 +14,4 @@ pub use error::ExtensionStorageError;
|
||||
pub use storage::ExtensionStorage;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[wasm_bindgen]
|
||||
pub fn set_panic_hook() {
|
||||
// When the `console_error_panic_hook` feature is enabled, we can call the
|
||||
// `set_panic_hook` function at least once during initialization, and then
|
||||
// we will get better error messages if our code ever panics.
|
||||
//
|
||||
// For more details see
|
||||
// https://github.com/rustwasm/console_error_panic_hook#readme
|
||||
#[cfg(feature = "console_error_panic_hook")]
|
||||
console_error_panic_hook::set_once();
|
||||
}
|
||||
pub use wasm_utils::set_panic_hook;
|
||||
|
||||
@@ -5,7 +5,7 @@ use crate::router::api;
|
||||
use crate::router::types::{ErrorResponse, RequestError};
|
||||
use axum::Router;
|
||||
use nym_node_requests::api as api_requests;
|
||||
use nym_node_requests::routes::api::v1;
|
||||
use nym_node_requests::routes::api::{v1, v1_absolute};
|
||||
use utoipa::openapi::security::{Http, HttpAuthScheme};
|
||||
use utoipa::{openapi::security::SecurityScheme, Modify, OpenApi};
|
||||
use utoipa_swagger_ui::SwaggerUi;
|
||||
@@ -97,7 +97,8 @@ impl Modify for SecurityAddon {
|
||||
|
||||
pub(crate) fn route<S: Send + Sync + 'static + Clone>() -> Router<S> {
|
||||
// provide absolute path to the openapi.json
|
||||
let config = utoipa_swagger_ui::Config::from("/api/v1/api-docs/openapi.json");
|
||||
let config =
|
||||
utoipa_swagger_ui::Config::from(format!("{}/api-docs/openapi.json", v1_absolute()));
|
||||
SwaggerUi::new(v1::SWAGGER)
|
||||
.url("/api-docs/openapi.json", ApiDoc::openapi())
|
||||
.config(config)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use js_sys::Promise;
|
||||
use thiserror::Error;
|
||||
use wasm_client_core::error::WasmCoreError;
|
||||
use wasm_client_core::topology::WasmTopologyError;
|
||||
|
||||
@@ -25,7 +25,7 @@ use wasm_bindgen::prelude::*;
|
||||
pub fn main() {
|
||||
wasm_utils::console_log!("[rust main]: rust module loaded");
|
||||
wasm_utils::console_log!(
|
||||
"wasm client version used: {:#?}",
|
||||
nym_bin_common::bin_info!()
|
||||
"wasm client version used: {}",
|
||||
nym_bin_common::bin_info_owned!()
|
||||
);
|
||||
}
|
||||
|
||||
+1
-26
@@ -1,16 +1,7 @@
|
||||
# Default target
|
||||
all: build build-node
|
||||
|
||||
# --- non-nodejs ---
|
||||
|
||||
build: build-go-opt build-rust build-package-json
|
||||
|
||||
check-fmt: check-fmt-go check-fmt-rust
|
||||
|
||||
build-debug-dev:
|
||||
wasm-pack build --scope nymproject --target no-modules
|
||||
$(MAKE) -C go-mix-conn build-debug-dev
|
||||
|
||||
build-go:
|
||||
$(MAKE) -C go-mix-conn build-go
|
||||
|
||||
@@ -29,20 +20,4 @@ check-fmt-go:
|
||||
|
||||
check-fmt-rust:
|
||||
cargo fmt --check
|
||||
cargo clippy --target wasm32-unknown-unknown -- -Dwarnings
|
||||
|
||||
# --- nodejs ---
|
||||
|
||||
build-rust-node:
|
||||
wasm-pack build --scope nymproject --target nodejs --out-dir ../../dist/node/wasm/mix-fetch
|
||||
wasm-opt -Oz -o ../../dist/node/wasm/mix-fetch/mix_fetch_wasm_bg.wasm ../../dist/node/wasm/mix-fetch/mix_fetch_wasm_bg.wasm
|
||||
|
||||
build-package-json-node:
|
||||
node build-node.mjs
|
||||
|
||||
copy-go-conn:
|
||||
mkdir -p ../../dist/node/wasm/mix-fetch
|
||||
cp ../../dist/wasm/mix-fetch/go_conn.wasm ../../dist/node/wasm/mix-fetch/go_conn.wasm
|
||||
cp ../../dist/wasm/mix-fetch/wasm_exec.js ../../dist/node/wasm/mix-fetch/wasm_exec.js
|
||||
|
||||
build-node: build-go-opt copy-go-conn build-rust-node build-package-json-node
|
||||
cargo clippy --target wasm32-unknown-unknown -- -Dwarnings
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
use crate::harbourmaster::HarbourMasterApiError;
|
||||
use crate::RequestId;
|
||||
use js_sys::Promise;
|
||||
use nym_ordered_buffer::OrderedMessageError;
|
||||
use nym_socks5_requests::ConnectionError;
|
||||
use thiserror::Error;
|
||||
|
||||
@@ -31,5 +31,8 @@ use wasm_bindgen::prelude::*;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub fn main() {
|
||||
wasm_utils::console_log!("[rust main]: rust module loaded");
|
||||
wasm_utils::console_log!("mix fetch version used: {:#?}", nym_bin_common::bin_info!());
|
||||
wasm_utils::console_log!(
|
||||
"mix fetch version used: {}",
|
||||
nym_bin_common::bin_info_owned!()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use js_sys::Promise;
|
||||
use nym_node_tester_utils::error::NetworkTestingError;
|
||||
use thiserror::Error;
|
||||
use wasm_client_core::error::WasmCoreError;
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
[build]
|
||||
target = "wasm32-unknown-unknown"
|
||||
target_arch = "wasm32"
|
||||
@@ -0,0 +1,46 @@
|
||||
[package]
|
||||
name = "zknym-lib"
|
||||
version = "0.1.0"
|
||||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
homepage.workspace = true
|
||||
documentation.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
async-trait.workspace = true
|
||||
bs58.workspace = true
|
||||
getrandom = { version = "0.2", features = ["js"] }
|
||||
js-sys.workspace = true
|
||||
wasm-bindgen.workspace = true
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
thiserror.workspace = true
|
||||
tsify = { workspace = true, features = ["js"] }
|
||||
uuid = { version = "*", features = ["serde"] }
|
||||
reqwest = { workspace = true }
|
||||
wasmtimer = { workspace = true }
|
||||
zeroize.workspace = true
|
||||
|
||||
rand = { version = "0.7.3", features = ["wasm-bindgen"] }
|
||||
|
||||
|
||||
nym-bin-common = { path = "../../common/bin-common" }
|
||||
nym-coconut = { path = "../../common/nymcoconut", features = ["key-zeroize"] }
|
||||
nym-credentials = { path = "../../common/credentials" }
|
||||
nym-crypto = { path = "../../common/crypto", features = ["asymmetric", "rand"] }
|
||||
nym-http-api-client = { path = "../../common/http-api-client" }
|
||||
wasm-utils = { path = "../../common/wasm/utils" }
|
||||
|
||||
[dev-dependencies]
|
||||
anyhow = { workspace = true }
|
||||
tokio = { workspace = true, features = ["full"] }
|
||||
|
||||
|
||||
[package.metadata.wasm-pack.profile.release]
|
||||
wasm-opt = false
|
||||
@@ -0,0 +1,18 @@
|
||||
all: build build-node
|
||||
|
||||
|
||||
build:
|
||||
wasm-pack build --scope nymproject --target web --out-dir ../../dist/wasm/zknym-lib
|
||||
wasm-opt -Oz -o ../../dist/wasm/zknym-lib/zknym_lib_bg.wasm ../../dist/wasm/zknym-lib/zknym_lib_bg.wasm
|
||||
|
||||
build-debug-dev:
|
||||
wasm-pack build --scope nymproject --target no-modules
|
||||
|
||||
build-rust-node:
|
||||
wasm-pack build --scope nymproject --target nodejs --out-dir ../../dist/node/wasm/zknym-lib
|
||||
wasm-opt -Oz -o ../../dist/node/wasm/zknym-lib/zknym_lib_bg.wasm ../../dist/node/wasm/zknym-lib/zknym_lib_bg.wasm
|
||||
|
||||
#build-package-json-node:
|
||||
# node build-node.mjs
|
||||
|
||||
build-node: # build-rust-node build-package-json-node
|
||||
@@ -0,0 +1,2 @@
|
||||
node_modules
|
||||
dist
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
// A dependency graph that contains any wasm must all be imported
|
||||
// asynchronously. This `bootstrap.js` file does the single async import, so
|
||||
// that no one else needs to worry about it again.
|
||||
import('./index.js')
|
||||
.catch(e => console.error('Error importing `index.js`:', e));
|
||||
@@ -0,0 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>zknym-lib internal dev</title>
|
||||
<script src="bootstrap.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1> Internal dev of zknym-lib</h1>
|
||||
|
||||
|
||||
<hr>
|
||||
<p>
|
||||
<span id="output"></span>
|
||||
</p>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,36 @@
|
||||
// Copyright 2024 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
|
||||
class WebWorkerClient {
|
||||
worker = null;
|
||||
|
||||
constructor() {
|
||||
this.worker = new Worker('./worker.js');
|
||||
|
||||
this.worker.onmessage = (ev) => {
|
||||
console.log("message:", ev)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
let client = null;
|
||||
|
||||
async function main() {
|
||||
client = new WebWorkerClient();
|
||||
|
||||
}
|
||||
|
||||
|
||||
main();
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"name": "create-wasm-app",
|
||||
"version": "0.1.0",
|
||||
"description": "create an app to consume rust-generated wasm packages",
|
||||
"main": "index.js",
|
||||
"bin": {
|
||||
"create-wasm-app": ".bin/create-wasm-app.js"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "webpack --config webpack.config.js",
|
||||
"build:wasm": "cd ../ && make wasm-build",
|
||||
"start": "webpack-dev-server --port 8001"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/rustwasm/create-wasm-app.git"
|
||||
},
|
||||
"keywords": [
|
||||
"webassembly",
|
||||
"wasm",
|
||||
"rust",
|
||||
"webpack"
|
||||
],
|
||||
"author": "Dave Hrycyszyn <futurechimp@users.noreply.github.com>",
|
||||
"license": "Apache-2.0",
|
||||
"bugs": {
|
||||
"url": "https://github.com/nymtech/nym/issues"
|
||||
},
|
||||
"homepage": "https://nymtech.net/docs",
|
||||
"devDependencies": {
|
||||
"copy-webpack-plugin": "^11.0.0",
|
||||
"hello-wasm-pack": "^0.1.0",
|
||||
"webpack": "^5.70.0",
|
||||
"webpack-cli": "^4.9.2",
|
||||
"webpack-dev-server": "^4.7.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nymproject/nym-zknym-lib": "file:../pkg"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||
const path = require('path');
|
||||
|
||||
module.exports = {
|
||||
performance: {
|
||||
hints: false,
|
||||
maxEntrypointSize: 512000,
|
||||
maxAssetSize: 512000
|
||||
},
|
||||
entry: {
|
||||
bootstrap: './bootstrap.js',
|
||||
worker: './worker.js',
|
||||
},
|
||||
output: {
|
||||
path: path.resolve(__dirname, 'dist'),
|
||||
filename: '[name].js',
|
||||
},
|
||||
mode: 'development',
|
||||
// mode: 'production',
|
||||
plugins: [
|
||||
new CopyWebpackPlugin({
|
||||
patterns: [
|
||||
'index.html',
|
||||
{
|
||||
from: '../pkg/*.(js|wasm)',
|
||||
to: '[name][ext]',
|
||||
},
|
||||
],
|
||||
}),
|
||||
|
||||
],
|
||||
experiments: { syncWebAssembly: true },
|
||||
};
|
||||
@@ -0,0 +1,193 @@
|
||||
// Copyright 2020-2023 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
const RUST_WASM_URL = "zknym_lib_bg.wasm"
|
||||
|
||||
importScripts('zknym_lib.js');
|
||||
|
||||
console.log('Initializing worker');
|
||||
|
||||
// wasm_bindgen creates a global variable (with the exports attached) that is in scope after `importScripts`
|
||||
const {
|
||||
set_panic_hook,
|
||||
prepareBlindSign,
|
||||
blindSign,
|
||||
setup,
|
||||
signSimple,
|
||||
verifySimple,
|
||||
keygen,
|
||||
simpleRandomiseCredential,
|
||||
ttpKeygen,
|
||||
aggregateVerificationKeyShares,
|
||||
aggregateSignatureShares,
|
||||
proveBandwidthCredential,
|
||||
verifyCredential,
|
||||
NymIssuanceBandwidthVoucher
|
||||
} = wasm_bindgen;
|
||||
|
||||
|
||||
// I've made it before starting on VPN API so didn't know exact requirements
|
||||
function simpleGenericUsage() {
|
||||
// SIMPLE SETUP: no threshold, no private attributes
|
||||
console.log(">>> simple example...")
|
||||
setup({numAttributes: 4, setGlobal: true})
|
||||
let keypair = keygen()
|
||||
|
||||
let vk = keypair.verificationKey();
|
||||
|
||||
let goodAttributes = ["foomp", "foo", "bar"]
|
||||
let badAttributes = ["I", "didn't", "sign", "that"]
|
||||
|
||||
let sig_simple = signSimple(goodAttributes, keypair)
|
||||
console.log("signature:", sig_simple.stringify());
|
||||
|
||||
let verified = verifySimple(vk, goodAttributes, sig_simple)
|
||||
let verified2 = verifySimple(vk, badAttributes, sig_simple)
|
||||
|
||||
let randomised = simpleRandomiseCredential(sig_simple);
|
||||
let verified3 = verifySimple(vk, goodAttributes, randomised)
|
||||
|
||||
console.log(`verified1: ${verified} (good attributes), verified2: ${verified2} (bad attributes), verified3: ${verified3} (good attributes + randomised)`)
|
||||
|
||||
|
||||
// 'NORMAL' SETUP: threshold with private attributes:
|
||||
console.log(">>> full example...")
|
||||
let t3_5_keys = ttpKeygen({threshold: 3, authorities: 5})
|
||||
|
||||
// currently they HAVE TO correspond to our bandwidth credential attributes,
|
||||
// i.e. serial number and binding number, because it seems we removed generic verification methods
|
||||
// ¯\_(ツ)_/¯
|
||||
let serialNumber = "credential-serial-number"
|
||||
let bindingNumber = "credential-binding-number"
|
||||
let privateAttributes = [serialNumber, bindingNumber]
|
||||
let publicAttributes = ["foo", "bar"]
|
||||
|
||||
// sign the attributes
|
||||
console.log("creating partial credentials...");
|
||||
let blindSignRequestData = prepareBlindSign(privateAttributes, publicAttributes)
|
||||
let blindSignRequest = blindSignRequestData.blindSignRequest()
|
||||
let blindPartial1 = blindSign(t3_5_keys[0], blindSignRequest, publicAttributes)
|
||||
let blindPartial2 = blindSign(t3_5_keys[1], blindSignRequest, publicAttributes)
|
||||
let blindPartial3 = blindSign(t3_5_keys[2], blindSignRequest, publicAttributes)
|
||||
let blindPartial4 = blindSign(t3_5_keys[3], blindSignRequest, publicAttributes)
|
||||
let blindPartial5 = blindSign(t3_5_keys[4], blindSignRequest, publicAttributes)
|
||||
|
||||
console.log("unblinding signatures...");
|
||||
let pedersenOpenings = blindSignRequestData.pedersenCommitmentsOpenings()
|
||||
let partialSig1 = blindPartial1.unblind(t3_5_keys[0].verificationKey(), pedersenOpenings)
|
||||
let partialSig2 = blindPartial2.unblind(t3_5_keys[1].verificationKey(), pedersenOpenings)
|
||||
let partialSig3 = blindPartial3.unblind(t3_5_keys[2].verificationKey(), pedersenOpenings)
|
||||
let partialSig4 = blindPartial4.unblind(t3_5_keys[3].verificationKey(), pedersenOpenings)
|
||||
let partialSig5 = blindPartial5.unblind(t3_5_keys[4].verificationKey(), pedersenOpenings)
|
||||
|
||||
// aggregate signature:
|
||||
console.log("aggregating signatures...")
|
||||
let sigShare1 = partialSig1.intoShare(t3_5_keys[0].index())
|
||||
let sigShare2 = partialSig2.intoShare(t3_5_keys[1].index())
|
||||
let sigShare3 = partialSig3.intoShare(t3_5_keys[2].index())
|
||||
let sigShare4 = partialSig4.intoShare(t3_5_keys[3].index())
|
||||
let sigShare5 = partialSig5.intoShare(t3_5_keys[4].index())
|
||||
let masterCred1 = aggregateSignatureShares([sigShare1.cloneDataPointer(), sigShare3, sigShare4])
|
||||
let masterCred2 = aggregateSignatureShares([sigShare1, sigShare2, sigShare5])
|
||||
|
||||
// key shares:
|
||||
console.log("aggregating verification keys...");
|
||||
let vk1 = t3_5_keys[0].verificationKeyShare()
|
||||
let vk2 = t3_5_keys[1].verificationKeyShare()
|
||||
let vk3 = t3_5_keys[2].verificationKeyShare()
|
||||
let vk4 = t3_5_keys[3].verificationKeyShare()
|
||||
let vk5 = t3_5_keys[4].verificationKeyShare()
|
||||
|
||||
// master verification key:
|
||||
let masterVk1 = aggregateVerificationKeyShares([vk1, vk2.cloneDataPointer(), vk3])
|
||||
let masterVk2 = aggregateVerificationKeyShares([vk2, vk4, vk5])
|
||||
|
||||
// attempt to 'spend'/'prove' the credential (note that the master keys and credentials should be cryptographically identical):
|
||||
console.log("attempting to spend the credential...");
|
||||
let verifyReq1 = proveBandwidthCredential(masterVk1, masterCred2, serialNumber, bindingNumber)
|
||||
let verifyReq2 = proveBandwidthCredential(masterVk2, masterCred1, serialNumber, bindingNumber)
|
||||
|
||||
console.log("verifying the credential...");
|
||||
let verifiedMaster1 = verifyCredential(masterVk1, verifyReq1, publicAttributes)
|
||||
let verifiedMaster2 = verifyCredential(masterVk1, verifyReq2, publicAttributes)
|
||||
|
||||
console.log(`verified1: ${verifiedMaster1}, verified2: ${verifiedMaster2}`)
|
||||
}
|
||||
|
||||
async function frontendSimulation() {
|
||||
console.log("getting opts");
|
||||
const res = await fetch("http://localhost:8080/api/v1/bandwidth-voucher/prehashed-public-attributes", {
|
||||
headers: new Headers({'Authorization': 'Bearer foomp'})
|
||||
});
|
||||
const opts = await res.json()
|
||||
|
||||
const issuanceVoucher = new NymIssuanceBandwidthVoucher(opts)
|
||||
const blindSignRequest = issuanceVoucher.getBlindSignRequest()
|
||||
|
||||
console.log("getting partial vks");
|
||||
const partialVksRes = await fetch("http://localhost:8080/api/v1/bandwidth-voucher/partial-verification-keys", {
|
||||
headers: new Headers({'Authorization': 'Bearer foomp'})
|
||||
});
|
||||
const partialVks = await partialVksRes.json()
|
||||
|
||||
console.log("getting master vk")
|
||||
const masterVkRes = await fetch("http://localhost:8080/api/v1/bandwidth-voucher/master-verification-key", {
|
||||
headers: new Headers({'Authorization': 'Bearer foomp'})
|
||||
});
|
||||
const masterVk = await masterVkRes.json()
|
||||
|
||||
console.log("getting blinded shares")
|
||||
const sharesRes = await fetch("http://localhost:8080/api/v1/bandwidth-voucher/obtain", {
|
||||
method: "POST",
|
||||
headers: new Headers(
|
||||
{
|
||||
'Authorization': 'Bearer foomp',
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
),
|
||||
body: JSON.stringify({
|
||||
blindSignRequest
|
||||
})
|
||||
});
|
||||
|
||||
const credentialShares = await sharesRes.json()
|
||||
|
||||
console.log("unblinding shares");
|
||||
const bandwidthVoucher = issuanceVoucher.unblindShares(credentialShares, partialVks)
|
||||
console.log("is valid: ", bandwidthVoucher.ensureIsValid(masterVk.bs58EncodedKey))
|
||||
|
||||
const serialised = bandwidthVoucher.serialise();
|
||||
console.log("serialised:\n", serialised)
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log(">>>>>>>>>>>>>>>>>>>>> JS WORKER MAIN START");
|
||||
|
||||
// load rust WASM package
|
||||
await wasm_bindgen(RUST_WASM_URL);
|
||||
console.log('Loaded RUST WASM');
|
||||
|
||||
// sets up better stack traces in case of in-rust panics
|
||||
set_panic_hook();
|
||||
|
||||
|
||||
await frontendSimulation()
|
||||
|
||||
|
||||
console.log(">>>>>>>>>>>>>>>>>>>>> JS WORKER MAIN END")
|
||||
}
|
||||
|
||||
|
||||
// Let's get started!
|
||||
main();
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,229 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::error::ZkNymError;
|
||||
use crate::generic_scheme::get_params;
|
||||
use crate::types::{CredentialWrapper, ParametersWrapper, UnblindableShare};
|
||||
use crate::vpn_api_client::types::{
|
||||
AttributesResponse, BandwidthVoucherResponse, PartialVerificationKeysResponse,
|
||||
};
|
||||
use nym_coconut::{
|
||||
Base58, BlindSignRequest, BlindedSignature, PrivateAttribute, PublicAttribute, Scalar,
|
||||
SignatureShare, VerificationKey,
|
||||
};
|
||||
use nym_credentials::coconut::bandwidth::issuance::Coin;
|
||||
use nym_credentials::coconut::bandwidth::issued::BandwidthCredentialIssuedDataVariant;
|
||||
use nym_credentials::coconut::bandwidth::voucher::BandwidthVoucherIssuedData;
|
||||
use nym_credentials::IssuedBandwidthCredential;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use tsify::Tsify;
|
||||
use wasm_bindgen::prelude::wasm_bindgen;
|
||||
use wasm_utils::console_error;
|
||||
use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
|
||||
|
||||
// tiny 'hacks' to just allow passing responses from vpn-api queries
|
||||
pub type NymIssuanceBandwidthVoucherOpts = AttributesResponse;
|
||||
pub type VoucherShares = BandwidthVoucherResponse;
|
||||
pub type VoucherIssuers = PartialVerificationKeysResponse;
|
||||
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug)]
|
||||
#[allow(dead_code)]
|
||||
pub struct NymIssuanceBandwidthVoucher {
|
||||
serial_number: PrivateAttribute,
|
||||
|
||||
binding_number: PrivateAttribute,
|
||||
|
||||
credential_amount: Coin,
|
||||
|
||||
prehashed_amount: PublicAttribute,
|
||||
prehashed_type: PublicAttribute,
|
||||
|
||||
blind_sign_request: BlindSignRequest,
|
||||
pedersen_commitments_openings: Zeroizing<Vec<Scalar>>,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl NymIssuanceBandwidthVoucher {
|
||||
#[wasm_bindgen(js_name = "prepareNew", constructor)]
|
||||
pub fn prepare_new(
|
||||
opts: NymIssuanceBandwidthVoucherOpts,
|
||||
parameters: Option<ParametersWrapper>,
|
||||
) -> Result<NymIssuanceBandwidthVoucher, ZkNymError> {
|
||||
let deposit_amount: u128 = opts
|
||||
.credential_amount_string
|
||||
.parse()
|
||||
.map_err(|_| ZkNymError::InvalidDepositAmount)?;
|
||||
let credential_amount = Coin::new(deposit_amount, opts.credential_amount_denom);
|
||||
|
||||
let params = get_params(¶meters);
|
||||
let serial_number = params.random_scalar();
|
||||
let binding_number = params.random_scalar();
|
||||
|
||||
let prehashed_amount = Scalar::try_from_bs58(opts.bs58_prehashed_amount)?;
|
||||
let prehashed_type = Scalar::try_from_bs58(opts.bs58_prehashed_type)?;
|
||||
|
||||
let public_attributes = vec![&prehashed_amount, &prehashed_type];
|
||||
let private_attributes = vec![&serial_number, &binding_number];
|
||||
|
||||
let (pedersen_commitments_openings, blind_sign_request) =
|
||||
nym_coconut::prepare_blind_sign(params, &private_attributes, &public_attributes)?;
|
||||
|
||||
Ok(NymIssuanceBandwidthVoucher {
|
||||
serial_number,
|
||||
binding_number,
|
||||
credential_amount,
|
||||
prehashed_amount,
|
||||
prehashed_type,
|
||||
blind_sign_request,
|
||||
pedersen_commitments_openings: Zeroizing::new(pedersen_commitments_openings),
|
||||
})
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "getBlindSignRequest")]
|
||||
pub fn get_blind_sign_request(&self) -> String {
|
||||
self.blind_sign_request.to_bs58()
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "unblindShare")]
|
||||
pub fn unblind_share(&self, share: UnblindableShare) -> Result<CredentialWrapper, ZkNymError> {
|
||||
let blinded_sig = BlindedSignature::try_from_bs58(share.blinded_share_bs58)?;
|
||||
let vk = VerificationKey::try_from_bs58(share.issuer_key_bs58)?;
|
||||
|
||||
Ok(blinded_sig
|
||||
.unblind(&vk, &self.pedersen_commitments_openings)
|
||||
.into())
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "unblindShares")]
|
||||
pub fn unblind_shares(
|
||||
self,
|
||||
shares: VoucherShares,
|
||||
issuers: VoucherIssuers,
|
||||
) -> Result<NymIssuedBandwidthVoucher, ZkNymError> {
|
||||
if shares.epoch_id != issuers.epoch_id {
|
||||
console_error!(
|
||||
"the provided shares and issuers are not from the same epoch! {} and {}",
|
||||
shares.epoch_id,
|
||||
issuers.epoch_id
|
||||
);
|
||||
return Err(ZkNymError::InconsistentEpochId {
|
||||
shares: shares.epoch_id,
|
||||
issuers: issuers.epoch_id,
|
||||
});
|
||||
}
|
||||
|
||||
let mut decoded_keys = HashMap::new();
|
||||
for key in issuers.keys {
|
||||
let vk = VerificationKey::try_from_bs58(key.bs58_encoded_key)?;
|
||||
decoded_keys.insert(key.node_index, vk);
|
||||
}
|
||||
|
||||
let mut credential_shares = Vec::new();
|
||||
for share in shares.shares {
|
||||
let blinded_sig = BlindedSignature::try_from_bs58(share.bs58_encoded_share)?;
|
||||
let Some(vk) = decoded_keys.get(&share.node_index) else {
|
||||
console_error!("received a share from issuer {} but did not receive a corresponding verification key!", share.node_index);
|
||||
continue;
|
||||
};
|
||||
let unblinded_sig = blinded_sig.unblind(vk, &self.pedersen_commitments_openings);
|
||||
credential_shares.push(SignatureShare::new(unblinded_sig, share.node_index));
|
||||
}
|
||||
|
||||
let signature = nym_coconut::aggregate_signature_shares(&credential_shares)?;
|
||||
|
||||
let voucher_data = BandwidthCredentialIssuedDataVariant::Voucher(
|
||||
BandwidthVoucherIssuedData::new(self.credential_amount),
|
||||
);
|
||||
|
||||
Ok(NymIssuedBandwidthVoucher {
|
||||
inner: IssuedBandwidthCredential::new(
|
||||
self.serial_number,
|
||||
self.binding_number,
|
||||
signature,
|
||||
voucher_data,
|
||||
self.prehashed_type,
|
||||
shares.epoch_id,
|
||||
),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub struct NymIssuedBandwidthVoucher {
|
||||
inner: IssuedBandwidthCredential,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl NymIssuedBandwidthVoucher {
|
||||
#[wasm_bindgen(js_name = "ensureIsValid")]
|
||||
pub fn ensure_is_valid(
|
||||
&self,
|
||||
master_vk: String,
|
||||
parameters: Option<ParametersWrapper>,
|
||||
) -> bool {
|
||||
let params = get_params(¶meters);
|
||||
let Ok(master_vk) = VerificationKey::try_from_bs58(master_vk) else {
|
||||
console_error!("malformed master verification key");
|
||||
return false;
|
||||
};
|
||||
|
||||
let spending_req = match self.inner.prepare_for_spending(&master_vk) {
|
||||
Ok(req) => req,
|
||||
Err(err) => {
|
||||
console_error!("failed to prepare spending request: {err}");
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
spending_req.verify(params, &master_vk)
|
||||
}
|
||||
|
||||
pub fn serialise(self) -> SerialisedNymIssuedBandwidthVoucher {
|
||||
SerialisedNymIssuedBandwidthVoucher {
|
||||
serialisation_revision:
|
||||
nym_credentials::coconut::bandwidth::issued::CURRENT_SERIALIZATION_REVISION,
|
||||
bs58_encoded_data: bs58::encode(&self.inner.pack_v1()).into_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Tsify, Serialize, Deserialize, Debug, PartialEq, Eq, Zeroize, ZeroizeOnDrop)]
|
||||
#[tsify(into_wasm_abi, from_wasm_abi)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SerialisedNymIssuedBandwidthVoucher {
|
||||
pub serialisation_revision: u8,
|
||||
pub bs58_encoded_data: String,
|
||||
}
|
||||
|
||||
// #[cfg(test)]
|
||||
// mod tests {
|
||||
// use super::*;
|
||||
// use crate::vpn_api_client::client::{new_client, NymVpnApiClient};
|
||||
//
|
||||
// #[ignore]
|
||||
// #[tokio::test]
|
||||
// async fn end_to_end() -> anyhow::Result<()> {
|
||||
// let client = new_client("http://0.0.0.0:8080", "foomp")?;
|
||||
// let opts = client.get_prehashed_public_attributes().await?;
|
||||
// let issuance = NymIssuanceBandwidthVoucher::prepare_new(opts, None)?;
|
||||
//
|
||||
// let shares = client
|
||||
// .get_bandwidth_voucher_blinded_shares(issuance.blind_sign_request.clone())
|
||||
// .await?;
|
||||
// let keys = client.get_partial_verification_keys().await?;
|
||||
// let master_key = client.get_master_verification_key().await?;
|
||||
//
|
||||
// let voucher = issuance.unblind_shares(shares, keys)?;
|
||||
//
|
||||
// println!(
|
||||
// "valid: {}",
|
||||
// voucher.ensure_is_valid(master_key.bs58_encoded_key, None)
|
||||
// );
|
||||
// let serialised = voucher.serialise();
|
||||
// println!("final: {serialised:#?}");
|
||||
//
|
||||
// Ok(())
|
||||
// }
|
||||
// }
|
||||
@@ -0,0 +1,43 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::vpn_api_client::NymVpnApiClientError;
|
||||
use thiserror::Error;
|
||||
use wasm_utils::wasm_error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ZkNymError {
|
||||
#[error("cryptographic failure: {source}")]
|
||||
CoconutFailure {
|
||||
#[from]
|
||||
source: nym_coconut::CoconutError,
|
||||
},
|
||||
|
||||
#[error("failed to contact the vpn api")]
|
||||
HttpClientFailure {
|
||||
#[from]
|
||||
source: NymVpnApiClientError,
|
||||
},
|
||||
#[error("the provided shares and issuers are not from the same epoch! {shares} and {issuers}")]
|
||||
InconsistentEpochId { shares: u64, issuers: u64 },
|
||||
|
||||
#[error("the provided deposit amount is malformed")]
|
||||
InvalidDepositAmount,
|
||||
|
||||
#[error("global parameters have already been set before")]
|
||||
GlobalParamsAlreadySet,
|
||||
|
||||
#[error("no parameters were provided - they need to be provided either explicitly or a global ones need to be set")]
|
||||
NoParametersProvided,
|
||||
|
||||
#[error("failed to recover ed25519 private key from its base58 representation: {0}")]
|
||||
MalformedEd25519Key(String),
|
||||
|
||||
#[error("failed to recover x25519 private key from its base58 representation: {0}")]
|
||||
MalformedX25519Key(String),
|
||||
|
||||
#[error("failed to recover the deposit transaction hash from its [hex] representation: {0}")]
|
||||
MalformedTransactionHash(String),
|
||||
}
|
||||
|
||||
wasm_error!(ZkNymError);
|
||||
@@ -0,0 +1,340 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::error::ZkNymError;
|
||||
use crate::types::{
|
||||
BlindSignRequestData, BlindSignRequestWrapper, BlindedCredentialWrapper,
|
||||
CredentialShareWrapper, CredentialWrapper, KeyPairWrapper, ParametersWrapper, ScalarsWrapper,
|
||||
VerificationKeyShareWrapper, VerificationKeyWrapper, VerifyCredentialRequestWrapper,
|
||||
};
|
||||
use crate::GLOBAL_PARAMS;
|
||||
use nym_coconut::{hash_to_scalar, Parameters, SignerIndex};
|
||||
use nym_credentials::IssuanceBandwidthCredential;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::sync::OnceLock;
|
||||
use tsify::Tsify;
|
||||
use wasm_bindgen::prelude::wasm_bindgen;
|
||||
|
||||
// works under the assumption of having 4 attributes in the underlying credential(s)
|
||||
pub fn default_bandwidth_credential_params() -> &'static Parameters {
|
||||
static BANDWIDTH_CREDENTIAL_PARAMS: OnceLock<Parameters> = OnceLock::new();
|
||||
BANDWIDTH_CREDENTIAL_PARAMS.get_or_init(IssuanceBandwidthCredential::default_parameters)
|
||||
}
|
||||
|
||||
// attempt to extract appropriate system parameters in the following order:
|
||||
// 1. attempt to get explicit provided value
|
||||
// 2. then try a globally set value
|
||||
// 3. finally fallback to sane default: the bandwidth credential params
|
||||
pub(crate) fn get_params(explicit_params: &Option<ParametersWrapper>) -> &Parameters {
|
||||
if let Some(explicit) = explicit_params.as_ref() {
|
||||
return explicit;
|
||||
}
|
||||
|
||||
if let Some(global) = GLOBAL_PARAMS.get() {
|
||||
return global;
|
||||
}
|
||||
|
||||
default_bandwidth_credential_params()
|
||||
}
|
||||
|
||||
#[derive(Tsify, Debug, Copy, Clone, Serialize, Deserialize, Default)]
|
||||
#[tsify(into_wasm_abi, from_wasm_abi)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SetupOpts {
|
||||
#[tsify(optional)]
|
||||
pub num_attributes: Option<u32>,
|
||||
|
||||
#[tsify(optional)]
|
||||
pub set_global: Option<bool>,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn setup(opts: SetupOpts) -> Result<ParametersWrapper, ZkNymError> {
|
||||
let num_attributes = opts
|
||||
.num_attributes
|
||||
.unwrap_or(IssuanceBandwidthCredential::ENCODED_ATTRIBUTES);
|
||||
|
||||
let params = nym_coconut::setup(num_attributes)?;
|
||||
|
||||
if let Some(true) = opts.set_global {
|
||||
GLOBAL_PARAMS
|
||||
.set(params.clone())
|
||||
.map_err(|_| ZkNymError::GlobalParamsAlreadySet)?;
|
||||
}
|
||||
|
||||
Ok(params.into())
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn keygen(parameters: Option<ParametersWrapper>) -> KeyPairWrapper {
|
||||
let params = get_params(¶meters);
|
||||
nym_coconut::keygen(params).into()
|
||||
}
|
||||
|
||||
#[derive(Tsify, Debug, Clone, Copy, Serialize, Deserialize)]
|
||||
#[tsify(into_wasm_abi, from_wasm_abi)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct TtpKeygenOpts {
|
||||
pub threshold: u64,
|
||||
|
||||
pub authorities: u64,
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "ttpKeygen")]
|
||||
pub fn ttp_keygen(
|
||||
opts: TtpKeygenOpts,
|
||||
parameters: Option<ParametersWrapper>,
|
||||
) -> Result<Vec<KeyPairWrapper>, ZkNymError> {
|
||||
let params = get_params(¶meters);
|
||||
|
||||
let keys = nym_coconut::ttp_keygen(params, opts.threshold, opts.authorities)?;
|
||||
Ok(keys.into_iter().map(Into::into).collect())
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "signSimple")]
|
||||
pub fn sign_simple(
|
||||
attributes: Vec<String>,
|
||||
keys: &KeyPairWrapper,
|
||||
parameters: Option<ParametersWrapper>,
|
||||
) -> Result<CredentialWrapper, ZkNymError> {
|
||||
let params = get_params(¶meters);
|
||||
|
||||
let public_attributes = attributes
|
||||
.into_iter()
|
||||
.map(hash_to_scalar)
|
||||
.collect::<Vec<_>>();
|
||||
let attributes_ref = public_attributes.iter().collect::<Vec<_>>();
|
||||
|
||||
nym_coconut::sign(params, keys.secret_key(), &attributes_ref)
|
||||
.map(Into::into)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "prepareBlindSign")]
|
||||
pub fn prepare_blind_sign(
|
||||
private_attributes: Vec<String>,
|
||||
public_attributes: Vec<String>,
|
||||
parameters: Option<ParametersWrapper>,
|
||||
) -> Result<BlindSignRequestData, ZkNymError> {
|
||||
let params = get_params(¶meters);
|
||||
|
||||
let public_attributes = public_attributes
|
||||
.into_iter()
|
||||
.map(hash_to_scalar)
|
||||
.collect::<Vec<_>>();
|
||||
let public_attributes_ref = public_attributes.iter().collect::<Vec<_>>();
|
||||
|
||||
let private_attributes = private_attributes
|
||||
.into_iter()
|
||||
.map(hash_to_scalar)
|
||||
.collect::<Vec<_>>();
|
||||
let private_attributes_ref = private_attributes.iter().collect::<Vec<_>>();
|
||||
let (pedersen_commitments_openings, blind_sign_request) =
|
||||
nym_coconut::prepare_blind_sign(params, &private_attributes_ref, &public_attributes_ref)?;
|
||||
|
||||
Ok(BlindSignRequestData {
|
||||
blind_sign_request,
|
||||
pedersen_commitments_openings,
|
||||
})
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "blindSign")]
|
||||
pub fn blind_sign(
|
||||
keys: &KeyPairWrapper,
|
||||
blind_sign_request: &BlindSignRequestWrapper,
|
||||
public_attributes: Vec<String>,
|
||||
parameters: Option<ParametersWrapper>,
|
||||
) -> Result<BlindedCredentialWrapper, ZkNymError> {
|
||||
let params = get_params(¶meters);
|
||||
|
||||
let public_attributes = public_attributes
|
||||
.into_iter()
|
||||
.map(hash_to_scalar)
|
||||
.collect::<Vec<_>>();
|
||||
let public_attributes_ref = public_attributes.iter().collect::<Vec<_>>();
|
||||
|
||||
nym_coconut::blind_sign(
|
||||
params,
|
||||
keys.secret_key(),
|
||||
blind_sign_request,
|
||||
&public_attributes_ref,
|
||||
)
|
||||
.map(Into::into)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "unblindSignatureShare")]
|
||||
pub fn unblind_signature_share(
|
||||
blinded_signature: &BlindedCredentialWrapper,
|
||||
partial_verification_key: &VerificationKeyWrapper,
|
||||
pedersen_commitments_openings: &ScalarsWrapper,
|
||||
) -> CredentialWrapper {
|
||||
BlindedCredentialWrapper::unblind(
|
||||
blinded_signature,
|
||||
partial_verification_key,
|
||||
pedersen_commitments_openings,
|
||||
)
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "unblindAndVerifySignatureShare")]
|
||||
pub fn unblind_and_verify_signature_share(
|
||||
blinded_signature: &BlindedCredentialWrapper,
|
||||
partial_verification_key: &VerificationKeyWrapper,
|
||||
request: &BlindSignRequestData,
|
||||
private_attributes: Vec<String>,
|
||||
public_attributes: Vec<String>,
|
||||
parameters: Option<ParametersWrapper>,
|
||||
) -> Result<CredentialWrapper, ZkNymError> {
|
||||
BlindedCredentialWrapper::unblind_and_verify(
|
||||
blinded_signature,
|
||||
partial_verification_key,
|
||||
request,
|
||||
private_attributes,
|
||||
public_attributes,
|
||||
parameters,
|
||||
)
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "aggregateSignatureShares")]
|
||||
pub fn aggregate_signature_shares(
|
||||
shares: Vec<CredentialShareWrapper>,
|
||||
) -> Result<CredentialWrapper, ZkNymError> {
|
||||
let shares = shares.into_iter().map(Into::into).collect::<Vec<_>>();
|
||||
|
||||
nym_coconut::aggregate_signature_shares(&shares)
|
||||
.map(Into::into)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "aggregateSignatureSharesAndVerify")]
|
||||
pub fn aggregate_signature_shares_and_verify(
|
||||
verification_key: &VerificationKeyWrapper,
|
||||
parameters: Option<ParametersWrapper>,
|
||||
private_attributes: Vec<String>,
|
||||
public_attributes: Vec<String>,
|
||||
shares: Vec<CredentialShareWrapper>,
|
||||
) -> Result<CredentialWrapper, ZkNymError> {
|
||||
let params = get_params(¶meters);
|
||||
|
||||
let public_attributes = public_attributes
|
||||
.into_iter()
|
||||
.map(hash_to_scalar)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let private_attributes = private_attributes
|
||||
.into_iter()
|
||||
.map(hash_to_scalar)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let attributes = private_attributes
|
||||
.iter()
|
||||
.chain(public_attributes.iter())
|
||||
.collect::<Vec<_>>();
|
||||
let shares = shares.into_iter().map(Into::into).collect::<Vec<_>>();
|
||||
|
||||
nym_coconut::aggregate_signature_shares_and_verify(
|
||||
params,
|
||||
verification_key,
|
||||
&attributes,
|
||||
&shares,
|
||||
)
|
||||
.map(Into::into)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "aggregateVerificationKeyShares")]
|
||||
pub fn aggregate_verification_key_shares(
|
||||
shares: Vec<VerificationKeyShareWrapper>,
|
||||
) -> Result<VerificationKeyWrapper, ZkNymError> {
|
||||
let shares = shares.into_iter().map(Into::into).collect::<Vec<_>>();
|
||||
|
||||
nym_coconut::aggregate_key_shares(&shares)
|
||||
.map(Into::into)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "aggregateVerificationKeys")]
|
||||
pub fn aggregate_verification_keys(
|
||||
keys: Vec<VerificationKeyWrapper>,
|
||||
indices: Vec<SignerIndex>,
|
||||
) -> Result<VerificationKeyWrapper, ZkNymError> {
|
||||
let keys = keys.into_iter().map(Into::into).collect::<Vec<_>>();
|
||||
|
||||
nym_coconut::aggregate_verification_keys(&keys, Some(&indices))
|
||||
.map(Into::into)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "proveBandwidthCredential")]
|
||||
pub fn prove_bandwidth_credential(
|
||||
verification_key: &VerificationKeyWrapper,
|
||||
credential: &CredentialWrapper,
|
||||
serial_number: String,
|
||||
binding_number: String,
|
||||
parameters: Option<ParametersWrapper>,
|
||||
) -> Result<VerifyCredentialRequestWrapper, ZkNymError> {
|
||||
let params = get_params(¶meters);
|
||||
|
||||
nym_coconut::prove_bandwidth_credential(
|
||||
params,
|
||||
verification_key,
|
||||
credential,
|
||||
&hash_to_scalar(serial_number),
|
||||
&hash_to_scalar(binding_number),
|
||||
)
|
||||
.map(Into::into)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "verifyCredential")]
|
||||
pub fn verify_credential(
|
||||
verification_key: &VerificationKeyWrapper,
|
||||
verification_request: &VerifyCredentialRequestWrapper,
|
||||
public_attributes: Vec<String>,
|
||||
parameters: Option<ParametersWrapper>,
|
||||
) -> bool {
|
||||
let params = get_params(¶meters);
|
||||
|
||||
let public_attributes = public_attributes
|
||||
.into_iter()
|
||||
.map(hash_to_scalar)
|
||||
.collect::<Vec<_>>();
|
||||
let attributes_ref = public_attributes.iter().collect::<Vec<_>>();
|
||||
|
||||
nym_coconut::verify_credential(
|
||||
params,
|
||||
verification_key,
|
||||
verification_request,
|
||||
&attributes_ref,
|
||||
)
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "verifySimple")]
|
||||
pub fn verify_simple(
|
||||
verification_key: &VerificationKeyWrapper,
|
||||
attributes: Vec<String>,
|
||||
credential: &CredentialWrapper,
|
||||
parameters: Option<ParametersWrapper>,
|
||||
) -> bool {
|
||||
let params = get_params(¶meters);
|
||||
|
||||
let public_attributes = attributes
|
||||
.into_iter()
|
||||
.map(hash_to_scalar)
|
||||
.collect::<Vec<_>>();
|
||||
let attributes_ref = public_attributes.iter().collect::<Vec<_>>();
|
||||
|
||||
nym_coconut::verify(params, verification_key, &attributes_ref, credential)
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "simpleRandomiseCredential")]
|
||||
pub fn simple_randomise_credential(
|
||||
credential: &CredentialWrapper,
|
||||
parameters: Option<ParametersWrapper>,
|
||||
) -> CredentialWrapper {
|
||||
let params = get_params(¶meters);
|
||||
|
||||
CredentialWrapper {
|
||||
inner: credential.inner.randomise_simple(params),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// due to the code generated by Tsify
|
||||
#![allow(clippy::empty_docs)]
|
||||
|
||||
use nym_coconut::Parameters;
|
||||
use std::sync::OnceLock;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
pub mod bandwidth_voucher;
|
||||
pub mod error;
|
||||
pub mod generic_scheme;
|
||||
pub mod types;
|
||||
|
||||
// keep in internal to the crate since I'm not sure how temporary this thing is going to be
|
||||
// I mostly got it, so I could test the whole thing end to end
|
||||
pub(crate) mod vpn_api_client;
|
||||
|
||||
pub(crate) static GLOBAL_PARAMS: OnceLock<Parameters> = OnceLock::new();
|
||||
|
||||
#[wasm_bindgen(start)]
|
||||
// #[cfg(target_arch = "wasm32")]
|
||||
pub fn main() {
|
||||
wasm_utils::console_log!("[rust main]: rust module loaded");
|
||||
wasm_utils::console_log!(
|
||||
"wasm zk-nym version used: {}",
|
||||
nym_bin_common::bin_info_owned!()
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::error::ZkNymError;
|
||||
use crate::generic_scheme::get_params;
|
||||
use nym_coconut::{
|
||||
hash_to_scalar, Base58, BlindSignRequest, BlindedSignature, KeyPair, Parameters, Scalar,
|
||||
SecretKey, Signature, SignatureShare, SignerIndex, VerificationKey, VerificationKeyShare,
|
||||
VerifyCredentialRequest,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::ops::Deref;
|
||||
use std::str::FromStr;
|
||||
use tsify::Tsify;
|
||||
use wasm_bindgen::prelude::wasm_bindgen;
|
||||
use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||
|
||||
macro_rules! wasm_wrapper {
|
||||
($base:ident, $wrapper:ident) => {
|
||||
#[wasm_bindgen]
|
||||
pub struct $wrapper {
|
||||
pub(crate) inner: $base,
|
||||
}
|
||||
|
||||
impl Deref for $wrapper {
|
||||
type Target = $base;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl From<$base> for $wrapper {
|
||||
fn from(inner: $base) -> Self {
|
||||
$wrapper { inner }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<$wrapper> for $base {
|
||||
fn from(value: $wrapper) -> Self {
|
||||
value.inner
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! data_pointer_clone {
|
||||
($wrapper:ident) => {
|
||||
#[wasm_bindgen]
|
||||
impl $wrapper {
|
||||
#[wasm_bindgen(js_name = "cloneDataPointer")]
|
||||
pub fn clone_data_pointer(&self) -> Self {
|
||||
Self {
|
||||
inner: self.inner.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! wasm_wrapper_bs58 {
|
||||
($base:ident, $wrapper:ident) => {
|
||||
wasm_wrapper!($base, $wrapper);
|
||||
|
||||
impl std::fmt::Display for $wrapper {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.inner.to_bs58().fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for $wrapper {
|
||||
type Err = ZkNymError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok($base::try_from_bs58(s)?.into())
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl $wrapper {
|
||||
pub fn stringify(&self) -> String {
|
||||
self.to_string()
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "fromString")]
|
||||
pub fn from_string(raw: String) -> Result<$wrapper, ZkNymError> {
|
||||
raw.parse()
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
wasm_wrapper!(Parameters, ParametersWrapper);
|
||||
wasm_wrapper_bs58!(Signature, CredentialWrapper);
|
||||
wasm_wrapper_bs58!(BlindedSignature, BlindedCredentialWrapper);
|
||||
wasm_wrapper!(SignatureShare, CredentialShareWrapper);
|
||||
wasm_wrapper_bs58!(Scalar, ScalarWrapper);
|
||||
|
||||
wasm_wrapper!(KeyPair, KeyPairWrapper);
|
||||
wasm_wrapper!(SecretKey, SecretKeyWrapper);
|
||||
wasm_wrapper!(BlindSignRequest, BlindSignRequestWrapper);
|
||||
wasm_wrapper_bs58!(VerificationKey, VerificationKeyWrapper);
|
||||
wasm_wrapper_bs58!(VerifyCredentialRequest, VerifyCredentialRequestWrapper);
|
||||
wasm_wrapper!(VerificationKeyShare, VerificationKeyShareWrapper);
|
||||
|
||||
data_pointer_clone!(VerificationKeyShareWrapper);
|
||||
data_pointer_clone!(CredentialShareWrapper);
|
||||
data_pointer_clone!(BlindSignRequestWrapper);
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl BlindedCredentialWrapper {
|
||||
pub fn unblind(
|
||||
&self,
|
||||
partial_verification_key: &VerificationKeyWrapper,
|
||||
pedersen_commitments_openings: &ScalarsWrapper,
|
||||
) -> CredentialWrapper {
|
||||
self.inner
|
||||
.unblind(partial_verification_key, pedersen_commitments_openings)
|
||||
.into()
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "unblindAndVerify")]
|
||||
pub fn unblind_and_verify(
|
||||
&self,
|
||||
partial_verification_key: &VerificationKeyWrapper,
|
||||
request: &BlindSignRequestData,
|
||||
private_attributes: Vec<String>,
|
||||
public_attributes: Vec<String>,
|
||||
parameters: Option<ParametersWrapper>,
|
||||
) -> Result<CredentialWrapper, ZkNymError> {
|
||||
let params = get_params(¶meters);
|
||||
|
||||
let public_attributes = public_attributes
|
||||
.into_iter()
|
||||
.map(hash_to_scalar)
|
||||
.collect::<Vec<_>>();
|
||||
let public_attributes_ref = public_attributes.iter().collect::<Vec<_>>();
|
||||
|
||||
let private_attributes = private_attributes
|
||||
.into_iter()
|
||||
.map(hash_to_scalar)
|
||||
.collect::<Vec<_>>();
|
||||
let private_attributes_ref = private_attributes.iter().collect::<Vec<_>>();
|
||||
|
||||
let unblinded_signature = self.inner.unblind_and_verify(
|
||||
params,
|
||||
partial_verification_key,
|
||||
&private_attributes_ref,
|
||||
&public_attributes_ref,
|
||||
&request.blind_sign_request.get_commitment_hash(),
|
||||
&request.pedersen_commitments_openings,
|
||||
)?;
|
||||
|
||||
Ok(unblinded_signature.into())
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl CredentialWrapper {
|
||||
#[wasm_bindgen(js_name = "intoShare")]
|
||||
pub fn into_share(self, index: SignerIndex) -> CredentialShareWrapper {
|
||||
CredentialShareWrapper {
|
||||
inner: SignatureShare::new(self.inner, index),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl KeyPairWrapper {
|
||||
#[wasm_bindgen(js_name = "verificationKey")]
|
||||
pub fn verification_key(&self) -> VerificationKeyWrapper {
|
||||
self.inner.verification_key().clone().into()
|
||||
}
|
||||
|
||||
pub fn index(&self) -> Option<SignerIndex> {
|
||||
self.inner.index
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "verificationKeyShare")]
|
||||
pub fn verification_key_share(&self) -> Option<VerificationKeyShareWrapper> {
|
||||
self.inner.to_verification_key_share().map(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub struct BlindSignRequestData {
|
||||
pub(crate) blind_sign_request: BlindSignRequest,
|
||||
pub(crate) pedersen_commitments_openings: Vec<Scalar>,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl BlindSignRequestData {
|
||||
#[wasm_bindgen(js_name = "blindSignRequest")]
|
||||
pub fn blind_sign_request(&self) -> BlindSignRequestWrapper {
|
||||
self.blind_sign_request.clone().into()
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "pedersenCommitmentsOpenings")]
|
||||
pub fn pedersen_commitments_openings(&self) -> ScalarsWrapper {
|
||||
ScalarsWrapper(self.pedersen_commitments_openings.clone())
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
#[derive(Zeroize, ZeroizeOnDrop)]
|
||||
pub struct ScalarsWrapper(pub(crate) Vec<Scalar>);
|
||||
|
||||
impl Deref for ScalarsWrapper {
|
||||
type Target = Vec<Scalar>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Tsify, Serialize, Deserialize, Clone, Default, Debug, PartialEq, Eq, Zeroize, ZeroizeOnDrop,
|
||||
)]
|
||||
#[tsify(into_wasm_abi, from_wasm_abi)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct KeypairWrapper {
|
||||
pub private_key: String,
|
||||
pub public_key: String,
|
||||
}
|
||||
|
||||
#[derive(Tsify, Serialize, Deserialize, Clone, Default, Debug, PartialEq, Eq)]
|
||||
#[tsify(into_wasm_abi, from_wasm_abi)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UnblindableShare {
|
||||
pub issuer_index: u64,
|
||||
pub issuer_key_bs58: String,
|
||||
pub blinded_share_bs58: String,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl UnblindableShare {
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(issuer_index: u64, issuer_key_bs58: String, blinded_share_bs58: String) -> Self {
|
||||
UnblindableShare {
|
||||
issuer_index,
|
||||
issuer_key_bs58,
|
||||
blinded_share_bs58,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use super::NymVpnApiClientError;
|
||||
use crate::error::ZkNymError;
|
||||
use crate::vpn_api_client::types::{
|
||||
AttributesResponse, BandwidthVoucherRequest, BandwidthVoucherResponse,
|
||||
MasterVerificationKeyResponse, PartialVerificationKeysResponse,
|
||||
};
|
||||
use async_trait::async_trait;
|
||||
use nym_coconut::BlindSignRequest;
|
||||
pub use nym_http_api_client::Client;
|
||||
use nym_http_api_client::{parse_response, PathSegments, NO_PARAMS};
|
||||
use reqwest::IntoUrl;
|
||||
use serde::de::DeserializeOwned;
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub struct VpnApiClient {
|
||||
inner: Client,
|
||||
bearer_token: String,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn new_client(
|
||||
base_url: impl IntoUrl,
|
||||
bearer_token: impl Into<String>,
|
||||
) -> Result<VpnApiClient, ZkNymError> {
|
||||
Ok(VpnApiClient {
|
||||
inner: Client::builder(base_url)?
|
||||
.with_user_agent(format!("nym-wasm-znym-lib/{}", env!("CARGO_PKG_VERSION")))
|
||||
.build()?,
|
||||
bearer_token: bearer_token.into(),
|
||||
})
|
||||
}
|
||||
|
||||
// TODO: do it properly by implementing auth headers on `ApiClient` trait
|
||||
#[allow(dead_code)]
|
||||
#[async_trait(?Send)]
|
||||
pub trait NymVpnApiClient {
|
||||
async fn simple_get<T>(&self, path: PathSegments<'_>) -> Result<T, NymVpnApiClientError>
|
||||
where
|
||||
T: DeserializeOwned;
|
||||
|
||||
async fn get_prehashed_public_attributes(
|
||||
&self,
|
||||
) -> Result<AttributesResponse, NymVpnApiClientError> {
|
||||
self.simple_get(&[
|
||||
"/api",
|
||||
"/v1",
|
||||
"/bandwidth-voucher",
|
||||
"/prehashed-public-attributes",
|
||||
])
|
||||
.await
|
||||
}
|
||||
|
||||
async fn get_partial_verification_keys(
|
||||
&self,
|
||||
) -> Result<PartialVerificationKeysResponse, NymVpnApiClientError> {
|
||||
self.simple_get(&[
|
||||
"/api",
|
||||
"/v1",
|
||||
"/bandwidth-voucher",
|
||||
"/partial-verification-keys",
|
||||
])
|
||||
.await
|
||||
}
|
||||
|
||||
async fn get_master_verification_key(
|
||||
&self,
|
||||
) -> Result<MasterVerificationKeyResponse, NymVpnApiClientError> {
|
||||
self.simple_get(&[
|
||||
"/api",
|
||||
"/v1",
|
||||
"/bandwidth-voucher",
|
||||
"/master-verification-key",
|
||||
])
|
||||
.await
|
||||
}
|
||||
|
||||
async fn get_bandwidth_voucher_blinded_shares(
|
||||
&self,
|
||||
blind_sign_request: BlindSignRequest,
|
||||
) -> Result<BandwidthVoucherResponse, NymVpnApiClientError>;
|
||||
}
|
||||
|
||||
#[async_trait(?Send)]
|
||||
impl NymVpnApiClient for VpnApiClient {
|
||||
async fn simple_get<T>(&self, path: PathSegments<'_>) -> Result<T, NymVpnApiClientError>
|
||||
where
|
||||
T: DeserializeOwned,
|
||||
{
|
||||
let req = self
|
||||
.inner
|
||||
.create_get_request(path, NO_PARAMS)
|
||||
.bearer_auth(&self.bearer_token)
|
||||
.send();
|
||||
|
||||
// the only reason for that target lock is so that I could call this method from an ephemeral test
|
||||
// running in non-wasm mode (since I wanted to use tokio)
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let res = wasmtimer::tokio::timeout(std::time::Duration::from_secs(5), req)
|
||||
.await
|
||||
.map_err(|_timeout| HttpClientError::RequestTimeout)??;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let res = req.await?;
|
||||
|
||||
parse_response(res, false).await
|
||||
}
|
||||
|
||||
async fn get_bandwidth_voucher_blinded_shares(
|
||||
&self,
|
||||
blind_sign_request: BlindSignRequest,
|
||||
) -> Result<BandwidthVoucherResponse, NymVpnApiClientError> {
|
||||
let req = self.inner.create_post_request(
|
||||
&["/api", "/v1", "/bandwidth-voucher", "/obtain"],
|
||||
NO_PARAMS,
|
||||
&BandwidthVoucherRequest { blind_sign_request },
|
||||
);
|
||||
|
||||
let fut = req.bearer_auth(&self.bearer_token).send();
|
||||
|
||||
// the only reason for that target lock is so that I could call this method from an ephemeral test
|
||||
// running in non-wasm mode (since I wanted to use tokio)
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let res = wasmtimer::tokio::timeout(std::time::Duration::from_secs(5), fut)
|
||||
.await
|
||||
.map_err(|_timeout| HttpClientError::RequestTimeout)??;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let res = fut.await?;
|
||||
|
||||
parse_response(res, false).await
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::vpn_api_client::types::ErrorResponse;
|
||||
use nym_http_api_client::HttpClientError;
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) mod client;
|
||||
|
||||
pub mod types;
|
||||
|
||||
pub type NymVpnApiClientError = HttpClientError<ErrorResponse>;
|
||||
@@ -0,0 +1,109 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// just copied over from dot com repo
|
||||
|
||||
use nym_coconut::BlindSignRequest;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::{Display, Formatter};
|
||||
use tsify::Tsify;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct BandwidthVoucherRequest {
|
||||
/// base58 encoded blind sign request
|
||||
pub blind_sign_request: BlindSignRequest,
|
||||
}
|
||||
|
||||
#[derive(Tsify, Serialize, Deserialize, Clone, Default, Debug, PartialEq, Eq)]
|
||||
#[tsify(into_wasm_abi, from_wasm_abi)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct BandwidthVoucherResponse {
|
||||
pub epoch_id: u64,
|
||||
pub shares: Vec<CredentialShare>,
|
||||
}
|
||||
|
||||
#[derive(Tsify, Serialize, Deserialize, Clone, Default, Debug, PartialEq, Eq)]
|
||||
#[tsify(into_wasm_abi, from_wasm_abi)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PartialVerificationKeysResponse {
|
||||
pub epoch_id: u64,
|
||||
pub keys: Vec<PartialVerificationKey>,
|
||||
}
|
||||
|
||||
#[derive(Tsify, Serialize, Deserialize, Clone, Default, Debug, PartialEq, Eq)]
|
||||
#[tsify(into_wasm_abi, from_wasm_abi)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CurrentEpochResponse {
|
||||
pub epoch_id: u64,
|
||||
}
|
||||
|
||||
#[derive(Tsify, Serialize, Deserialize, Clone, Default, Debug, PartialEq, Eq)]
|
||||
#[tsify(into_wasm_abi, from_wasm_abi)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CredentialShare {
|
||||
pub node_index: u64,
|
||||
pub bs58_encoded_share: String,
|
||||
}
|
||||
|
||||
#[derive(Tsify, Serialize, Deserialize, Clone, Default, Debug, PartialEq, Eq)]
|
||||
#[tsify(into_wasm_abi, from_wasm_abi)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PartialVerificationKey {
|
||||
pub node_index: u64,
|
||||
pub bs58_encoded_key: String,
|
||||
}
|
||||
|
||||
#[derive(Tsify, Serialize, Deserialize, Clone, Default, Debug, PartialEq, Eq)]
|
||||
#[tsify(into_wasm_abi, from_wasm_abi)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct MasterVerificationKeyResponse {
|
||||
pub epoch_id: u64,
|
||||
pub bs58_encoded_key: String,
|
||||
}
|
||||
|
||||
#[derive(Tsify, Serialize, Deserialize, Clone, Default, Debug, PartialEq, Eq)]
|
||||
#[tsify(into_wasm_abi, from_wasm_abi)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DepositResponse {
|
||||
pub current_deposit_amount: u128,
|
||||
pub current_deposit_denom: String,
|
||||
}
|
||||
|
||||
#[derive(Tsify, Serialize, Deserialize, Clone, Default, Debug, PartialEq, Eq)]
|
||||
#[tsify(into_wasm_abi, from_wasm_abi)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AttributesResponse {
|
||||
pub credential_type_string: String,
|
||||
pub credential_amount_string: String,
|
||||
pub credential_amount_denom: String,
|
||||
|
||||
pub bs58_prehashed_type: String,
|
||||
pub bs58_prehashed_amount: String,
|
||||
}
|
||||
|
||||
#[derive(Tsify, Serialize, Deserialize, Clone, Default, Debug, PartialEq, Eq)]
|
||||
#[tsify(into_wasm_abi, from_wasm_abi)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct FreepassCredentialResponse {
|
||||
pub bs58_encoded_value: String,
|
||||
}
|
||||
|
||||
#[derive(Tsify, Serialize, Deserialize, Clone, Default, Debug, PartialEq, Eq)]
|
||||
#[tsify(into_wasm_abi, from_wasm_abi)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ErrorResponse {
|
||||
pub uuid: Option<Uuid>,
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
impl Display for ErrorResponse {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.message)?;
|
||||
if let Some(uuid) = self.uuid {
|
||||
write!(f, ". request uuid: {uuid}")?
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user