Compare commits
34 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| aeb3c775a0 | |||
| b92999fb77 | |||
| 8ef4c667bf | |||
| 8d2cd48da5 | |||
| f2eb97514a | |||
| 735535d902 | |||
| f7603a9973 | |||
| 693b8d5519 | |||
| f96103ab97 | |||
| b599ededf3 | |||
| d2114d3c2e | |||
| 0356f0c682 | |||
| 365b12c069 | |||
| 032281dc00 | |||
| 9cc57f8f63 | |||
| 64c940a12a | |||
| d6f0d50760 | |||
| f6f361299c | |||
| 3c5677b4ff | |||
| b243062695 | |||
| 946b10cc30 | |||
| 1c8831ec17 | |||
| 3e606be545 | |||
| 3f8c2c096b | |||
| 3ede03e1d1 | |||
| ac12455f97 | |||
| 1c6db86259 | |||
| e126c1f7f1 | |||
| 31772019cd | |||
| 5369e5eab9 | |||
| 2e634c59a7 | |||
| d7383d74f3 | |||
| e98d60d7ce | |||
| f47650d6c8 |
+11
-1
@@ -14,6 +14,7 @@
|
||||
# contracts
|
||||
/contracts/mixnet @durch @jstuczyn
|
||||
/contracts/vesting @durch @jstuczyn
|
||||
/contracts/service-provider-directory @octol
|
||||
|
||||
# crypto code
|
||||
/common/crypto/ @jstuczyn
|
||||
@@ -21,5 +22,14 @@
|
||||
/common/dkg/ @jstuczyn
|
||||
/common/nymsphinx/ @jstuczyn
|
||||
|
||||
# rust sdk
|
||||
/sdk/rust/ @octol
|
||||
|
||||
# nym-connect (rust)
|
||||
/nym-connect/desktop/src-tauri/ @octol
|
||||
|
||||
# nym-wallet (rust)
|
||||
/nym-wallet/src-tauri/ @octol
|
||||
|
||||
# documentation
|
||||
/documentation @mfahampshire
|
||||
/documentation @mfahampshire
|
||||
@@ -27,7 +27,8 @@ jobs:
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
# pinned due to issues building contracts
|
||||
toolchain: 1.86.0
|
||||
target: wasm32-unknown-unknown
|
||||
override: true
|
||||
components: rustfmt, clippy
|
||||
|
||||
@@ -52,7 +52,7 @@ jobs:
|
||||
- name: Install Rust stable
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
toolchain: 1.86.0
|
||||
override: true
|
||||
|
||||
- name: Build all binaries
|
||||
|
||||
@@ -57,5 +57,5 @@ jobs:
|
||||
|
||||
- name: BuildAndPushImageOnHarbor
|
||||
run: |
|
||||
docker build --build-arg GIT_REF=${{ github.event.inputs.gateway_probe_git_ref }} -f ${{ env.WORKING_DIRECTORY }}/Dockerfile . -t harbor.nymte.ch/nym/${{ env.CONTAINER_NAME }}:${{ steps.get_version.outputs.result }}-${{ steps.cleanup_gateway_probe_ref.outputs.git_ref }}
|
||||
docker build --build-arg GIT_REF=${{ github.event.inputs.gateway_probe_git_ref }} --build-arg MNEMONIC="${{ secrets.CANARY_PROBE_MNEMONIC }}" -f ${{ env.WORKING_DIRECTORY }}/Dockerfile . -t harbor.nymte.ch/nym/${{ env.CONTAINER_NAME }}:${{ steps.get_version.outputs.result }}-${{ steps.cleanup_gateway_probe_ref.outputs.git_ref }}
|
||||
docker push harbor.nymte.ch/nym/${{ env.CONTAINER_NAME }} --all-tags
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
name: Build and upload Nym APU container to harbor.nymte.ch
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
WORKING_DIRECTORY: "."
|
||||
CONTAINER_NAME: "nym-api"
|
||||
|
||||
jobs:
|
||||
build-container:
|
||||
runs-on: arc-ubuntu-22.04-dind
|
||||
steps:
|
||||
- name: Login to Harbor
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: harbor.nymte.ch
|
||||
username: ${{ secrets.HARBOR_ROBOT_USERNAME }}
|
||||
password: ${{ secrets.HARBOR_ROBOT_SECRET }}
|
||||
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Configure git identity
|
||||
run: |
|
||||
git config --global user.email "lawrence@nymtech.net"
|
||||
git config --global user.name "Lawrence Stalder"
|
||||
|
||||
- name: Get version from cargo.toml
|
||||
uses: mikefarah/yq@v4.45.4
|
||||
id: get_version
|
||||
with:
|
||||
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/nym-api/Cargo.toml
|
||||
|
||||
- name: Remove existing tag if exists
|
||||
run: |
|
||||
echo "Checking if tag ${{ env.CONTAINER_NAME }}-${{ steps.get_version.outputs.result }} exists..."
|
||||
if git rev-parse ${{ env.CONTAINER_NAME }}-${{ steps.get_version.outputs.result }} >/dev/null 2>&1; then
|
||||
echo "Tag ${{ env.CONTAINER_NAME }}-${{ steps.get_version.outputs.result }} already exists"
|
||||
git push --delete origin ${{ env.CONTAINER_NAME }}-${{ steps.get_version.outputs.result }}
|
||||
git tag -d ${{ env.CONTAINER_NAME }}-${{ steps.get_version.outputs.result }}
|
||||
fi
|
||||
|
||||
- name: Create tag
|
||||
run: |
|
||||
git tag -a ${{ env.CONTAINER_NAME }}-${{ steps.get_version.outputs.result }} -m "Version ${{ steps.get_version.outputs.result }}"
|
||||
git push origin ${{ env.CONTAINER_NAME }}-${{ steps.get_version.outputs.result }}
|
||||
|
||||
- name: BuildAndPushImageOnHarbor
|
||||
run: |
|
||||
docker build -f nym-api.dockerfile ${{ env.WORKING_DIRECTORY }} -t harbor.nymte.ch/nym/${{ env.CONTAINER_NAME }}:${{ steps.get_version.outputs.result }} -t harbor.nymte.ch/nym/${{ env.CONTAINER_NAME }}:latest
|
||||
docker push harbor.nymte.ch/nym/${{ env.CONTAINER_NAME }} --all-tags
|
||||
@@ -62,4 +62,3 @@ nym-api/redocly/formatted-openapi.json
|
||||
|
||||
**/settings.sql
|
||||
**/enter_db.sh
|
||||
CLAUDE.md
|
||||
@@ -4,6 +4,58 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [2025.10-brie] (2025-05-27)
|
||||
|
||||
- Backport PR 5779 ([#5801])
|
||||
- Expanded Accept Encoding for `reqwest` ([#5779])
|
||||
- Teach HttpClientError how to report its status code and timeout ([#5770])
|
||||
- Skip refreshing the topology on startup as we already have an initial set ([#5768])
|
||||
- Fetch the topology from the nym-api concurrently ([#5767])
|
||||
- feat: use bincode by default in NymApiClient + remove feature-lock ([#5761])
|
||||
- Instrument create_request ([#5760])
|
||||
- Add node_bonded field to delegations ([#5759])
|
||||
- build(deps): bump mikefarah/yq from 4.45.1 to 4.45.4 ([#5758])
|
||||
- Raw route submissions ([#5756])
|
||||
- feat: expires header for `/active` nym-api responses ([#5755])
|
||||
- Decrease default average packet delay to 15 ms ([#5754])
|
||||
- build(deps): bump the patch-updates group across 1 directory with 12 updates ([#5753])
|
||||
- Remove pretty_env_logger and switch remaining crates to use tracing ([#5749])
|
||||
- Update pretty_env_logger to latest to not depend on unmaintained crate atty ([#5748])
|
||||
- Upgrade prometheus crate to fix security warning ([#5747])
|
||||
- Downgrade deranged crate to 0.4.0 ([#5746])
|
||||
- feat: nym-api bincode + yaml support ([#5745])
|
||||
- fix parallel feature in ecash crate with send + sync ([#5744])
|
||||
- Remove old test directory - Update validator docker ([#5743])
|
||||
- [Feature] `RememberMe` is the new don't `ForgetMe` ([#5742])
|
||||
- build(deps): bump ammonia from 4.0.0 to 4.1.0 ([#5739])
|
||||
- build(deps): bump base-x from 3.0.9 to 3.0.11 in /testnet-faucet ([#5737])
|
||||
- build(deps): bump http-proxy-middleware from 2.0.8 to 2.0.9 ([#5730])
|
||||
|
||||
[#5801]: https://github.com/nymtech/nym/pull/5801
|
||||
[#5779]: https://github.com/nymtech/nym/pull/5779
|
||||
[#5770]: https://github.com/nymtech/nym/pull/5770
|
||||
[#5768]: https://github.com/nymtech/nym/pull/5768
|
||||
[#5767]: https://github.com/nymtech/nym/pull/5767
|
||||
[#5761]: https://github.com/nymtech/nym/pull/5761
|
||||
[#5760]: https://github.com/nymtech/nym/pull/5760
|
||||
[#5759]: https://github.com/nymtech/nym/pull/5759
|
||||
[#5758]: https://github.com/nymtech/nym/pull/5758
|
||||
[#5756]: https://github.com/nymtech/nym/pull/5756
|
||||
[#5755]: https://github.com/nymtech/nym/pull/5755
|
||||
[#5754]: https://github.com/nymtech/nym/pull/5754
|
||||
[#5753]: https://github.com/nymtech/nym/pull/5753
|
||||
[#5749]: https://github.com/nymtech/nym/pull/5749
|
||||
[#5748]: https://github.com/nymtech/nym/pull/5748
|
||||
[#5747]: https://github.com/nymtech/nym/pull/5747
|
||||
[#5746]: https://github.com/nymtech/nym/pull/5746
|
||||
[#5745]: https://github.com/nymtech/nym/pull/5745
|
||||
[#5744]: https://github.com/nymtech/nym/pull/5744
|
||||
[#5743]: https://github.com/nymtech/nym/pull/5743
|
||||
[#5742]: https://github.com/nymtech/nym/pull/5742
|
||||
[#5739]: https://github.com/nymtech/nym/pull/5739
|
||||
[#5737]: https://github.com/nymtech/nym/pull/5737
|
||||
[#5730]: https://github.com/nymtech/nym/pull/5730
|
||||
|
||||
## [2025.9-appenzeller] (2025-05-13)
|
||||
|
||||
- build(deps): bump clap from 4.5.36 to 4.5.37 in the patch-updates group ([#5722])
|
||||
|
||||
Generated
+640
-488
File diff suppressed because it is too large
Load Diff
@@ -122,6 +122,7 @@ members = [
|
||||
"service-providers/common",
|
||||
"service-providers/ip-packet-router",
|
||||
"service-providers/network-requester",
|
||||
"sqlx-pool-guard",
|
||||
"tools/echo-server",
|
||||
"tools/internal/contract-state-importer/importer-cli",
|
||||
"tools/internal/contract-state-importer/importer-contract",
|
||||
@@ -281,6 +282,7 @@ petgraph = "0.6.5"
|
||||
pin-project = "1.1"
|
||||
pin-project-lite = "0.2.16"
|
||||
publicsuffix = "2.3.0"
|
||||
proc_pidinfo = "0.1.3"
|
||||
quote = "1"
|
||||
rand = "0.8.5"
|
||||
rand_chacha = "0.3"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-client"
|
||||
version = "1.1.55"
|
||||
version = "1.1.56"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
|
||||
description = "Implementation of the Nym Client"
|
||||
edition = "2021"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-socks5-client"
|
||||
version = "1.1.55"
|
||||
version = "1.1.56"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
|
||||
description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address"
|
||||
edition = "2021"
|
||||
|
||||
@@ -2,8 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::BadGateway;
|
||||
use std::io;
|
||||
use std::path::PathBuf;
|
||||
use std::{io, path::PathBuf};
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
@@ -19,7 +18,6 @@ pub enum StorageError {
|
||||
|
||||
#[error("failed to perform sqlx migration: {source}")]
|
||||
MigrationError {
|
||||
#[source]
|
||||
#[from]
|
||||
source: sqlx::migrate::MigrateError,
|
||||
},
|
||||
@@ -32,7 +30,6 @@ pub enum StorageError {
|
||||
|
||||
#[error("failed to run the SQL query: {source}")]
|
||||
QueryError {
|
||||
#[source]
|
||||
#[from]
|
||||
source: sqlx::error::Error,
|
||||
},
|
||||
|
||||
@@ -1,20 +1,18 @@
|
||||
// Copyright 2022-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::client::replies::reply_storage::{
|
||||
fs_backend, CombinedReplyStorage, ReplyStorageBackend,
|
||||
use crate::{
|
||||
client::replies::reply_storage::{fs_backend, CombinedReplyStorage, ReplyStorageBackend},
|
||||
config,
|
||||
config::Config,
|
||||
error::ClientCoreError,
|
||||
};
|
||||
use crate::config;
|
||||
use crate::config::Config;
|
||||
use crate::error::ClientCoreError;
|
||||
use log::{error, info, trace};
|
||||
use nym_bandwidth_controller::BandwidthController;
|
||||
use nym_client_core_gateways_storage::OnDiskGatewaysDetails;
|
||||
use nym_credential_storage::storage::Storage as CredentialStorage;
|
||||
use nym_validator_client::nyxd;
|
||||
use nym_validator_client::QueryHttpRpcNyxdClient;
|
||||
use std::path::Path;
|
||||
use std::{fs, io};
|
||||
use nym_validator_client::{nyxd, QueryHttpRpcNyxdClient};
|
||||
use std::{io, path::Path};
|
||||
use time::OffsetDateTime;
|
||||
use url::Url;
|
||||
|
||||
@@ -22,11 +20,11 @@ async fn setup_fresh_backend<P: AsRef<Path>>(
|
||||
db_path: P,
|
||||
surb_config: &config::ReplySurbs,
|
||||
) -> Result<fs_backend::Backend, ClientCoreError> {
|
||||
info!("creating fresh surb database");
|
||||
info!("Creating fresh surb database");
|
||||
let mut storage_backend = match fs_backend::Backend::init(db_path).await {
|
||||
Ok(backend) => backend,
|
||||
Err(err) => {
|
||||
error!("failed to setup persistent storage backend for our reply needs: {err}");
|
||||
error!("setup_fresh_backend: Failed to setup persistent storage backend for our reply needs: {err}");
|
||||
return Err(ClientCoreError::SurbStorageError {
|
||||
source: Box::new(err),
|
||||
});
|
||||
@@ -40,14 +38,15 @@ async fn setup_fresh_backend<P: AsRef<Path>>(
|
||||
surb_config.minimum_reply_surb_storage_threshold,
|
||||
surb_config.maximum_reply_surb_storage_threshold,
|
||||
);
|
||||
storage_backend
|
||||
.init_fresh(&mem_store)
|
||||
.await
|
||||
.map_err(|err| ClientCoreError::SurbStorageError {
|
||||
source: Box::new(err),
|
||||
})?;
|
||||
|
||||
Ok(storage_backend)
|
||||
match storage_backend.init_fresh(&mem_store).await {
|
||||
Ok(()) => Ok(storage_backend),
|
||||
Err(err) => {
|
||||
storage_backend.shutdown().await;
|
||||
Err(ClientCoreError::SurbStorageError {
|
||||
source: Box::new(err),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// fn setup_inactive_backend(surb_config: &config::ReplySurbs) -> fs_backend::Backend {
|
||||
@@ -58,12 +57,11 @@ async fn setup_fresh_backend<P: AsRef<Path>>(
|
||||
// )
|
||||
// }
|
||||
|
||||
fn archive_corrupted_database<P: AsRef<Path>>(db_path: P) -> io::Result<()> {
|
||||
async fn archive_corrupted_database<P: AsRef<Path>>(db_path: P) -> io::Result<()> {
|
||||
let db_path = db_path.as_ref();
|
||||
debug_assert!(db_path.exists());
|
||||
|
||||
let now = OffsetDateTime::now_utc().unix_timestamp();
|
||||
|
||||
let suffix = format!("_{now}.corrupted");
|
||||
|
||||
let new_extension =
|
||||
@@ -72,11 +70,15 @@ fn archive_corrupted_database<P: AsRef<Path>>(db_path: P) -> io::Result<()> {
|
||||
} else {
|
||||
suffix
|
||||
};
|
||||
let renamed = db_path.with_extension(new_extension);
|
||||
|
||||
let mut renamed = db_path.to_owned();
|
||||
renamed.set_extension(new_extension);
|
||||
|
||||
fs::rename(db_path, renamed)
|
||||
tokio::fs::rename(db_path, &renamed).await.inspect_err(|_| {
|
||||
error!(
|
||||
"Failed to rename corrupt database file: {} to {}",
|
||||
db_path.display(),
|
||||
renamed.display()
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn setup_fs_reply_surb_backend<P: AsRef<Path>>(
|
||||
@@ -87,13 +89,12 @@ pub async fn setup_fs_reply_surb_backend<P: AsRef<Path>>(
|
||||
// the existing one
|
||||
let db_path = db_path.as_ref();
|
||||
if db_path.exists() {
|
||||
info!("loading existing surb database");
|
||||
info!("Loading existing surb database");
|
||||
match fs_backend::Backend::try_load(db_path, surb_config.fresh_sender_tags).await {
|
||||
Ok(backend) => Ok(backend),
|
||||
Err(err) => {
|
||||
error!("failed to setup persistent storage backend for our reply needs: {err}. We're going to create a fresh database instead. This behaviour might change in the future");
|
||||
|
||||
archive_corrupted_database(db_path)?;
|
||||
error!("setup_fs_reply_surb_backend: Failed to setup persistent storage backend for our reply needs: {err}. We're going to create a fresh database instead. This behaviour might change in the future");
|
||||
archive_corrupted_database(db_path).await?;
|
||||
setup_fresh_backend(db_path, surb_config).await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,15 +17,26 @@ nym-crypto = { path = "../../crypto", optional = true, default-features = false
|
||||
nym-sphinx = { path = "../../nymsphinx" }
|
||||
nym-task = { path = "../../task" }
|
||||
|
||||
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio]
|
||||
workspace = true
|
||||
features = ["fs"]
|
||||
|
||||
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.sqlx]
|
||||
workspace = true
|
||||
features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"]
|
||||
optional = true
|
||||
|
||||
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.sqlx-pool-guard]
|
||||
path = "../../../sqlx-pool-guard"
|
||||
|
||||
[build-dependencies]
|
||||
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
|
||||
sqlx = { workspace = true, features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"] }
|
||||
sqlx = { workspace = true, features = [
|
||||
"runtime-tokio-rustls",
|
||||
"sqlite",
|
||||
"macros",
|
||||
"migrate",
|
||||
] }
|
||||
|
||||
[features]
|
||||
fs-surb-storage = ["sqlx", "nym-crypto", "nym-crypto/hashing"]
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::io;
|
||||
use std::path::PathBuf;
|
||||
use std::{io, path::PathBuf};
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
@@ -30,7 +29,6 @@ pub enum StorageError {
|
||||
|
||||
#[error("failed to perform sqlx migration: {source}")]
|
||||
MigrationError {
|
||||
#[source]
|
||||
#[from]
|
||||
source: sqlx::migrate::MigrateError,
|
||||
},
|
||||
@@ -43,7 +41,6 @@ pub enum StorageError {
|
||||
|
||||
#[error("failed to run the SQL query: {source}")]
|
||||
QueryError {
|
||||
#[source]
|
||||
#[from]
|
||||
source: sqlx::error::Error,
|
||||
},
|
||||
|
||||
@@ -15,9 +15,11 @@ use sqlx::{
|
||||
};
|
||||
use std::path::Path;
|
||||
|
||||
use sqlx_pool_guard::SqlitePoolGuard;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct StorageManager {
|
||||
pub connection_pool: sqlx::SqlitePool,
|
||||
connection_pool: SqlitePoolGuard,
|
||||
}
|
||||
|
||||
// all SQL goes here
|
||||
@@ -37,7 +39,7 @@ impl StorageManager {
|
||||
.journal_mode(sqlx::sqlite::SqliteJournalMode::Wal)
|
||||
.synchronous(SqliteSynchronous::Normal)
|
||||
.auto_vacuum(SqliteAutoVacuum::Incremental)
|
||||
.filename(database_path)
|
||||
.filename(&database_path)
|
||||
.create_if_missing(fresh)
|
||||
.disable_statement_logging();
|
||||
|
||||
@@ -49,11 +51,15 @@ impl StorageManager {
|
||||
}
|
||||
};
|
||||
|
||||
let connection_pool =
|
||||
SqlitePoolGuard::new(database_path.as_ref().to_path_buf(), connection_pool);
|
||||
|
||||
if let Err(err) = sqlx::migrate!("./fs_surbs_migrations")
|
||||
.run(&connection_pool)
|
||||
.run(&*connection_pool)
|
||||
.await
|
||||
{
|
||||
error!("Failed to initialize SQLx database: {err}");
|
||||
connection_pool.close().await;
|
||||
return Err(err.into());
|
||||
}
|
||||
|
||||
@@ -61,38 +67,43 @@ impl StorageManager {
|
||||
Ok(StorageManager { connection_pool })
|
||||
}
|
||||
|
||||
/// Close connection pool waiting for all connections to be closed.
|
||||
pub async fn close_pool(&self) {
|
||||
self.connection_pool.close().await;
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub async fn status_table_exists(&self) -> Result<bool, sqlx::Error> {
|
||||
sqlx::query!("SELECT name FROM sqlite_master WHERE type='table' AND name='status'")
|
||||
.fetch_optional(&self.connection_pool)
|
||||
.fetch_optional(&*self.connection_pool)
|
||||
.await
|
||||
.map(|r| r.is_some())
|
||||
}
|
||||
|
||||
pub async fn create_status_table(&self) -> Result<(), sqlx::Error> {
|
||||
sqlx::query!("INSERT INTO status(flush_in_progress, previous_flush_timestamp, client_in_use) VALUES (0, 0, 1)")
|
||||
.execute(&self.connection_pool)
|
||||
.execute(&*self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_flush_status(&self) -> Result<bool, sqlx::Error> {
|
||||
sqlx::query!("SELECT flush_in_progress FROM status;")
|
||||
.fetch_one(&self.connection_pool)
|
||||
.fetch_one(&*self.connection_pool)
|
||||
.await
|
||||
.map(|r| r.flush_in_progress > 0)
|
||||
}
|
||||
|
||||
pub async fn set_previous_flush_timestamp(&self, timestamp: i64) -> Result<(), sqlx::Error> {
|
||||
sqlx::query!("UPDATE status SET previous_flush_timestamp = ?", timestamp)
|
||||
.execute(&self.connection_pool)
|
||||
.execute(&*self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_previous_flush_timestamp(&self) -> Result<i64, sqlx::Error> {
|
||||
sqlx::query!("SELECT previous_flush_timestamp FROM status;")
|
||||
.fetch_one(&self.connection_pool)
|
||||
.fetch_one(&*self.connection_pool)
|
||||
.await
|
||||
.map(|r| r.previous_flush_timestamp)
|
||||
}
|
||||
@@ -100,14 +111,14 @@ impl StorageManager {
|
||||
pub async fn set_flush_status(&self, in_progress: bool) -> Result<(), sqlx::Error> {
|
||||
let in_progress_int = i64::from(in_progress);
|
||||
sqlx::query!("UPDATE status SET flush_in_progress = ?", in_progress_int)
|
||||
.execute(&self.connection_pool)
|
||||
.execute(&*self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_client_in_use_status(&self) -> Result<bool, sqlx::Error> {
|
||||
sqlx::query!("SELECT client_in_use FROM status;")
|
||||
.fetch_one(&self.connection_pool)
|
||||
.fetch_one(&*self.connection_pool)
|
||||
.await
|
||||
.map(|r| r.client_in_use > 0)
|
||||
}
|
||||
@@ -115,21 +126,21 @@ impl StorageManager {
|
||||
pub async fn set_client_in_use_status(&self, in_use: bool) -> Result<(), sqlx::Error> {
|
||||
let in_use_int = i64::from(in_use);
|
||||
sqlx::query!("UPDATE status SET client_in_use = ?", in_use_int)
|
||||
.execute(&self.connection_pool)
|
||||
.execute(&*self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn delete_all_tags(&self) -> Result<(), sqlx::Error> {
|
||||
sqlx::query!("DELETE FROM sender_tag;")
|
||||
.execute(&self.connection_pool)
|
||||
.execute(&*self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_tags(&self) -> Result<Vec<StoredSenderTag>, sqlx::Error> {
|
||||
sqlx::query_as!(StoredSenderTag, "SELECT * FROM sender_tag;",)
|
||||
.fetch_all(&self.connection_pool)
|
||||
.fetch_all(&*self.connection_pool)
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -141,21 +152,21 @@ impl StorageManager {
|
||||
stored_tag.recipient,
|
||||
stored_tag.tag
|
||||
)
|
||||
.execute(&self.connection_pool)
|
||||
.execute(&*self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn delete_all_reply_keys(&self) -> Result<(), sqlx::Error> {
|
||||
sqlx::query!("DELETE FROM reply_key;")
|
||||
.execute(&self.connection_pool)
|
||||
.execute(&*self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_reply_keys(&self) -> Result<Vec<StoredReplyKey>, sqlx::Error> {
|
||||
sqlx::query_as!(StoredReplyKey, "SELECT * FROM reply_key;",)
|
||||
.fetch_all(&self.connection_pool)
|
||||
.fetch_all(&*self.connection_pool)
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -171,14 +182,14 @@ impl StorageManager {
|
||||
stored_reply_key.reply_key,
|
||||
stored_reply_key.sent_at_timestamp
|
||||
)
|
||||
.execute(&self.connection_pool)
|
||||
.execute(&*self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_surb_senders(&self) -> Result<Vec<StoredSurbSender>, sqlx::Error> {
|
||||
sqlx::query_as!(StoredSurbSender, "SELECT * FROM reply_surb_sender;",)
|
||||
.fetch_all(&self.connection_pool)
|
||||
.fetch_all(&*self.connection_pool)
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -193,7 +204,7 @@ impl StorageManager {
|
||||
stored_surb_sender.tag,
|
||||
stored_surb_sender.last_sent_timestamp
|
||||
)
|
||||
.execute(&self.connection_pool)
|
||||
.execute(&*self.connection_pool)
|
||||
.await?
|
||||
.last_insert_rowid();
|
||||
Ok(id)
|
||||
@@ -208,17 +219,17 @@ impl StorageManager {
|
||||
"SELECT * FROM reply_surb WHERE reply_surb_sender_id = ?",
|
||||
sender_id
|
||||
)
|
||||
.fetch_all(&self.connection_pool)
|
||||
.fetch_all(&*self.connection_pool)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn delete_all_reply_surb_data(&self) -> Result<(), sqlx::Error> {
|
||||
sqlx::query!("DELETE FROM reply_surb;")
|
||||
.execute(&self.connection_pool)
|
||||
.execute(&*self.connection_pool)
|
||||
.await?;
|
||||
|
||||
sqlx::query!("DELETE FROM reply_surb_sender;")
|
||||
.execute(&self.connection_pool)
|
||||
.execute(&*self.connection_pool)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
@@ -235,7 +246,7 @@ impl StorageManager {
|
||||
stored_reply_surb.reply_surb_sender_id,
|
||||
stored_reply_surb.reply_surb
|
||||
)
|
||||
.execute(&self.connection_pool)
|
||||
.execute(&*self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -249,7 +260,7 @@ impl StorageManager {
|
||||
SELECT min_reply_surb_threshold as "min_reply_surb_threshold: u32", max_reply_surb_threshold as "max_reply_surb_threshold: u32" FROM reply_surb_storage_metadata;
|
||||
"#,
|
||||
)
|
||||
.fetch_one(&self.connection_pool)
|
||||
.fetch_one(&*self.connection_pool)
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -263,7 +274,7 @@ impl StorageManager {
|
||||
"#,
|
||||
metadata.min_reply_surb_threshold,
|
||||
metadata.max_reply_surb_threshold,
|
||||
).execute(&self.connection_pool).await?;
|
||||
).execute(&*self.connection_pool).await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::backend::fs_backend::manager::StorageManager;
|
||||
use crate::backend::fs_backend::models::{
|
||||
ReplySurbStorageMetadata, StoredReplyKey, StoredReplySurb, StoredSenderTag, StoredSurbSender,
|
||||
};
|
||||
use crate::surb_storage::ReceivedReplySurbs;
|
||||
use crate::{
|
||||
CombinedReplyStorage, ReceivedReplySurbsMap, ReplyStorageBackend, SentReplyKeys, UsedSenderTags,
|
||||
backend::fs_backend::{
|
||||
manager::StorageManager,
|
||||
models::{
|
||||
ReplySurbStorageMetadata, StoredReplyKey, StoredReplySurb, StoredSenderTag,
|
||||
StoredSurbSender,
|
||||
},
|
||||
},
|
||||
surb_storage::ReceivedReplySurbs,
|
||||
CombinedReplyStorage, ReceivedReplySurbsMap, ReplyStorageBackend, SentReplyKeys,
|
||||
UsedSenderTags,
|
||||
};
|
||||
use async_trait::async_trait;
|
||||
use log::{debug, error, info, warn};
|
||||
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
use time::OffsetDateTime;
|
||||
|
||||
@@ -41,15 +44,17 @@ impl Backend {
|
||||
}
|
||||
|
||||
let manager = StorageManager::init(database_path, true).await?;
|
||||
manager.create_status_table().await?;
|
||||
|
||||
let backend = Backend {
|
||||
temporary_old_path: None,
|
||||
database_path: owned_path,
|
||||
manager,
|
||||
};
|
||||
|
||||
Ok(backend)
|
||||
match manager.create_status_table().await {
|
||||
Ok(()) => Ok(Backend {
|
||||
temporary_old_path: None,
|
||||
database_path: owned_path,
|
||||
manager,
|
||||
}),
|
||||
Err(err) => {
|
||||
manager.close_pool().await;
|
||||
Err(err.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn try_load<P: AsRef<Path>>(
|
||||
@@ -64,7 +69,28 @@ impl Backend {
|
||||
}
|
||||
|
||||
let manager = StorageManager::init(database_path, false).await?;
|
||||
match Self::try_load_inner(&manager, fresh_sender_tags).await {
|
||||
Ok(()) => Ok(Backend {
|
||||
temporary_old_path: None,
|
||||
database_path: owned_path,
|
||||
manager,
|
||||
}),
|
||||
Err(e) => {
|
||||
manager.close_pool().await;
|
||||
Err(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Gracefully close sqlite connection pool and drop backend.
|
||||
pub async fn shutdown(self) {
|
||||
self.manager.close_pool().await
|
||||
}
|
||||
|
||||
async fn try_load_inner(
|
||||
manager: &StorageManager,
|
||||
fresh_sender_tags: bool,
|
||||
) -> Result<(), StorageError> {
|
||||
// the database flush wasn't fully finished and thus the data is in inconsistent state
|
||||
// (we don't really know what's properly saved or what's not)
|
||||
if manager.get_flush_status().await? {
|
||||
@@ -126,20 +152,11 @@ impl Backend {
|
||||
manager.delete_all_tags().await?;
|
||||
}
|
||||
|
||||
Ok(Backend {
|
||||
temporary_old_path: None,
|
||||
database_path: owned_path,
|
||||
// manager: StorageManagerState::Storage(manager),
|
||||
manager,
|
||||
})
|
||||
}
|
||||
|
||||
async fn close_pool(&mut self) {
|
||||
self.manager.connection_pool.close().await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn rotate(&mut self) -> Result<(), StorageError> {
|
||||
self.close_pool().await;
|
||||
self.manager.close_pool().await;
|
||||
|
||||
let new_extension = if let Some(existing_extension) =
|
||||
self.database_path.extension().and_then(|ext| ext.to_str())
|
||||
@@ -152,7 +169,8 @@ impl Backend {
|
||||
let mut temp_old = self.database_path.clone();
|
||||
temp_old.set_extension(new_extension);
|
||||
|
||||
fs::rename(&self.database_path, &temp_old)
|
||||
tokio::fs::rename(&self.database_path, &temp_old)
|
||||
.await
|
||||
.map_err(|err| StorageError::DatabaseRenameError { source: err })?;
|
||||
self.manager = StorageManager::init(&self.database_path, true).await?;
|
||||
self.manager.create_status_table().await?;
|
||||
@@ -161,9 +179,10 @@ impl Backend {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn remove_old(&mut self) -> Result<(), StorageError> {
|
||||
async fn remove_old(&mut self) -> Result<(), StorageError> {
|
||||
if let Some(old_path) = self.temporary_old_path.take() {
|
||||
fs::remove_file(old_path)
|
||||
tokio::fs::remove_file(old_path)
|
||||
.await
|
||||
.map_err(|err| StorageError::DatabaseOldFileRemoveError { source: err })
|
||||
} else {
|
||||
warn!("the old database file doesn't seem to exist!");
|
||||
@@ -335,7 +354,7 @@ impl ReplyStorageBackend for Backend {
|
||||
self.dump_reply_surb_storage_metadata(surbs_ref).await?;
|
||||
self.dump_reply_surbs(surbs_ref).await?;
|
||||
|
||||
self.remove_old()?;
|
||||
self.remove_old().await?;
|
||||
self.end_storage_flush().await
|
||||
}
|
||||
|
||||
|
||||
@@ -73,7 +73,6 @@ pub const STAKE_SATURATION: &str = "stake-saturation";
|
||||
pub const INCLUSION_CHANCE: &str = "inclusion-probability";
|
||||
pub const SUBMIT_GATEWAY: &str = "submit-gateway-monitoring-results";
|
||||
pub const SUBMIT_NODE: &str = "submit-node-monitoring-results";
|
||||
pub const SUBMIT_ROUTE: &str = "submit-route-monitoring-results";
|
||||
|
||||
pub const SERVICE_PROVIDERS: &str = "services";
|
||||
|
||||
|
||||
@@ -20,6 +20,8 @@ nym-credentials = { path = "../credentials" }
|
||||
nym-compact-ecash = { path = "../nym_offline_compact_ecash" }
|
||||
nym-ecash-time = { path = "../ecash-time" }
|
||||
|
||||
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.sqlx-pool-guard]
|
||||
path = "../../sqlx-pool-guard"
|
||||
|
||||
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.sqlx]
|
||||
workspace = true
|
||||
@@ -31,8 +33,13 @@ features = ["rt-multi-thread", "net", "signal", "fs"]
|
||||
|
||||
|
||||
[build-dependencies]
|
||||
sqlx = { workspace = true, features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"] }
|
||||
sqlx = { workspace = true, features = [
|
||||
"runtime-tokio-rustls",
|
||||
"sqlite",
|
||||
"macros",
|
||||
"migrate",
|
||||
] }
|
||||
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
|
||||
|
||||
[features]
|
||||
persistent-storage = ["bincode", "serde"]
|
||||
persistent-storage = ["bincode", "serde"]
|
||||
|
||||
@@ -7,10 +7,11 @@ use crate::models::{
|
||||
};
|
||||
use nym_ecash_time::Date;
|
||||
use sqlx::{Executor, Sqlite, Transaction};
|
||||
use sqlx_pool_guard::SqlitePoolGuard;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SqliteEcashTicketbookManager {
|
||||
connection_pool: sqlx::SqlitePool,
|
||||
connection_pool: SqlitePoolGuard,
|
||||
}
|
||||
|
||||
impl SqliteEcashTicketbookManager {
|
||||
@@ -19,7 +20,7 @@ impl SqliteEcashTicketbookManager {
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `connection_pool`: database connection pool to use.
|
||||
pub fn new(connection_pool: sqlx::SqlitePool) -> Self {
|
||||
pub fn new(connection_pool: SqlitePoolGuard) -> Self {
|
||||
SqliteEcashTicketbookManager { connection_pool }
|
||||
}
|
||||
|
||||
@@ -33,7 +34,7 @@ impl SqliteEcashTicketbookManager {
|
||||
"DELETE FROM ecash_ticketbook WHERE expiration_date <= ?",
|
||||
deadline
|
||||
)
|
||||
.execute(&self.connection_pool)
|
||||
.execute(&*self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -60,7 +61,7 @@ impl SqliteEcashTicketbookManager {
|
||||
data,
|
||||
expiration_date,
|
||||
)
|
||||
.execute(&self.connection_pool)
|
||||
.execute(&*self.connection_pool)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
@@ -90,7 +91,7 @@ impl SqliteEcashTicketbookManager {
|
||||
epoch_id,
|
||||
total_tickets,
|
||||
used_tickets,
|
||||
).execute(&self.connection_pool).await?;
|
||||
).execute(&*self.connection_pool).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -98,14 +99,15 @@ impl SqliteEcashTicketbookManager {
|
||||
pub(crate) async fn contains_ticketbook_data(&self, data: &[u8]) -> Result<bool, sqlx::Error> {
|
||||
let exists = sqlx::query(
|
||||
r#"
|
||||
SELECT 1
|
||||
FROM ecash_ticketbook
|
||||
WHERE ticketbook_data = ?
|
||||
|
||||
SELECT EXISTS (
|
||||
SELECT 1
|
||||
FROM ecash_ticketbook
|
||||
WHERE ticketbook_data = ?
|
||||
)
|
||||
"#,
|
||||
)
|
||||
.bind(data)
|
||||
.fetch_optional(&self.connection_pool)
|
||||
.fetch_optional(&*self.connection_pool)
|
||||
.await?
|
||||
.is_some();
|
||||
|
||||
@@ -121,7 +123,7 @@ impl SqliteEcashTicketbookManager {
|
||||
FROM ecash_ticketbook
|
||||
"#,
|
||||
)
|
||||
.fetch_all(&self.connection_pool)
|
||||
.fetch_all(&*self.connection_pool)
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -143,7 +145,7 @@ impl SqliteEcashTicketbookManager {
|
||||
ticketbook_id,
|
||||
expected_current_total_spent
|
||||
)
|
||||
.execute(&self.connection_pool)
|
||||
.execute(&*self.connection_pool)
|
||||
.await?
|
||||
.rows_affected();
|
||||
Ok(affected > 0)
|
||||
@@ -153,7 +155,7 @@ impl SqliteEcashTicketbookManager {
|
||||
&self,
|
||||
) -> Result<Vec<StoredPendingTicketbook>, sqlx::Error> {
|
||||
sqlx::query_as("SELECT * FROM pending_issuance")
|
||||
.fetch_all(&self.connection_pool)
|
||||
.fetch_all(&*self.connection_pool)
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -165,7 +167,7 @@ impl SqliteEcashTicketbookManager {
|
||||
"DELETE FROM pending_issuance WHERE deposit_id = ?",
|
||||
pending_id
|
||||
)
|
||||
.execute(&self.connection_pool)
|
||||
.execute(&*self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -182,7 +184,7 @@ impl SqliteEcashTicketbookManager {
|
||||
"#,
|
||||
epoch_id
|
||||
)
|
||||
.fetch_optional(&self.connection_pool)
|
||||
.fetch_optional(&*self.connection_pool)
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -208,7 +210,7 @@ impl SqliteEcashTicketbookManager {
|
||||
serialisation_revision,
|
||||
epoch_id
|
||||
)
|
||||
.execute(&self.connection_pool)
|
||||
.execute(&*self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -225,7 +227,7 @@ impl SqliteEcashTicketbookManager {
|
||||
"#,
|
||||
epoch_id
|
||||
)
|
||||
.fetch_optional(&self.connection_pool)
|
||||
.fetch_optional(&*self.connection_pool)
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -251,7 +253,7 @@ impl SqliteEcashTicketbookManager {
|
||||
serialisation_revision,
|
||||
epoch_id,
|
||||
)
|
||||
.execute(&self.connection_pool)
|
||||
.execute(&*self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -269,7 +271,7 @@ impl SqliteEcashTicketbookManager {
|
||||
"#,
|
||||
expiration_date
|
||||
)
|
||||
.fetch_optional(&self.connection_pool)
|
||||
.fetch_optional(&*self.connection_pool)
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -298,7 +300,7 @@ impl SqliteEcashTicketbookManager {
|
||||
serialisation_revision,
|
||||
expiration_date
|
||||
)
|
||||
.execute(&self.connection_pool)
|
||||
.execute(&*self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ use sqlx::{
|
||||
sqlite::{SqliteAutoVacuum, SqliteSynchronous},
|
||||
ConnectOptions,
|
||||
};
|
||||
use sqlx_pool_guard::SqlitePoolGuard;
|
||||
use std::path::Path;
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
@@ -62,7 +63,7 @@ impl PersistentStorage {
|
||||
.journal_mode(sqlx::sqlite::SqliteJournalMode::Wal)
|
||||
.synchronous(SqliteSynchronous::Normal)
|
||||
.auto_vacuum(SqliteAutoVacuum::Incremental)
|
||||
.filename(database_path)
|
||||
.filename(&database_path)
|
||||
.create_if_missing(true)
|
||||
.disable_statement_logging();
|
||||
|
||||
@@ -74,13 +75,17 @@ impl PersistentStorage {
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(err) = sqlx::migrate!("./migrations").run(&connection_pool).await {
|
||||
let connection_pool =
|
||||
SqlitePoolGuard::new(database_path.as_ref().to_path_buf(), connection_pool);
|
||||
|
||||
if let Err(err) = sqlx::migrate!("./migrations").run(&*connection_pool).await {
|
||||
error!("Failed to perform migration on the SQLx database: {err}");
|
||||
connection_pool.close().await;
|
||||
return Err(err.into());
|
||||
}
|
||||
|
||||
Ok(PersistentStorage {
|
||||
storage_manager: SqliteEcashTicketbookManager::new(connection_pool.clone()),
|
||||
storage_manager: SqliteEcashTicketbookManager::new(connection_pool),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,7 +124,7 @@ impl ReconstructionBuffer {
|
||||
// TODO: what to do in that case? give up on the message? overwrite it? panic?
|
||||
// it *might* be due to lock ack-packet, but let's keep the `warn` level in case
|
||||
// it could be somehow exploited
|
||||
debug!(
|
||||
warn!(
|
||||
"duplicate fragment received! - frag - {} (set id: {})",
|
||||
fragment.current_fragment(),
|
||||
fragment.id()
|
||||
|
||||
@@ -224,10 +224,6 @@ impl NymTopology {
|
||||
serde_json::from_reader(file).map_err(Into::into)
|
||||
}
|
||||
|
||||
pub fn node_details(&self) -> &HashMap<NodeId, RoutingNode> {
|
||||
&self.node_details
|
||||
}
|
||||
|
||||
pub fn add_skimmed_nodes(&mut self, nodes: &[SkimmedNode]) {
|
||||
self.add_additional_nodes(nodes.iter())
|
||||
}
|
||||
|
||||
@@ -11,27 +11,6 @@ static NETWORK_MONITORS: LazyLock<HashSet<String>> = LazyLock::new(|| {
|
||||
nm
|
||||
});
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, JsonSchema, Clone, ToSchema)]
|
||||
pub struct RouteResult {
|
||||
pub layer1: u32,
|
||||
pub layer2: u32,
|
||||
pub layer3: u32,
|
||||
pub gw: u32,
|
||||
pub success: bool,
|
||||
}
|
||||
|
||||
impl RouteResult {
|
||||
pub fn new(layer1: u32, layer2: u32, layer3: u32, gw: u32, success: bool) -> Self {
|
||||
RouteResult {
|
||||
layer1,
|
||||
layer2,
|
||||
layer3,
|
||||
gw,
|
||||
success,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, JsonSchema, Clone, ToSchema)]
|
||||
pub struct NodeResult {
|
||||
#[schema(value_type = u32)]
|
||||
@@ -50,23 +29,23 @@ impl NodeResult {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, JsonSchema, ToSchema)]
|
||||
#[derive(Serialize, Deserialize, JsonSchema)]
|
||||
#[serde(untagged)]
|
||||
pub enum MonitorResults {
|
||||
Node(Vec<NodeResult>),
|
||||
Route(Vec<RouteResult>),
|
||||
Mixnode(Vec<NodeResult>),
|
||||
Gateway(Vec<NodeResult>),
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, JsonSchema, ToSchema)]
|
||||
pub struct MonitorMessage {
|
||||
results: MonitorResults,
|
||||
results: Vec<NodeResult>,
|
||||
signature: String,
|
||||
signer: String,
|
||||
timestamp: i64,
|
||||
}
|
||||
|
||||
impl MonitorMessage {
|
||||
fn message_to_sign(results: &MonitorResults, timestamp: i64) -> Vec<u8> {
|
||||
fn message_to_sign(results: &[NodeResult], timestamp: i64) -> Vec<u8> {
|
||||
let mut msg = serde_json::to_vec(results).unwrap_or_default();
|
||||
msg.extend_from_slice(×tamp.to_le_bytes());
|
||||
msg
|
||||
@@ -81,7 +60,7 @@ impl MonitorMessage {
|
||||
now - self.timestamp < 5
|
||||
}
|
||||
|
||||
pub fn new(results: MonitorResults, private_key: &PrivateKey) -> Self {
|
||||
pub fn new(results: Vec<NodeResult>, private_key: &PrivateKey) -> Self {
|
||||
let timestamp = SystemTime::now()
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.expect("Time went backwards")
|
||||
@@ -103,7 +82,7 @@ impl MonitorMessage {
|
||||
NETWORK_MONITORS.contains(&self.signer)
|
||||
}
|
||||
|
||||
pub fn results(&self) -> &MonitorResults {
|
||||
pub fn results(&self) -> &[NodeResult] {
|
||||
&self.results
|
||||
}
|
||||
|
||||
|
||||
+5
-14
@@ -1,27 +1,17 @@
|
||||
import React, { useState } from 'react'
|
||||
import CirculatingSupply from 'components/outputs/api-scraping-outputs/circulating-supply.json'
|
||||
import RewardParams from 'components/outputs/api-scraping-outputs/reward-params.json'
|
||||
|
||||
|
||||
export default function RewardsCalculator() {
|
||||
const [a, setA] = useState(
|
||||
Number(
|
||||
(Number(RewardParams.interval.epoch_reward_budget) / 1_000_000).toFixed(6)
|
||||
)
|
||||
)
|
||||
const [a, setA] = useState(0)
|
||||
const [b, setB] = useState(0)
|
||||
const [c, setC] = useState(0)
|
||||
const [d, setD] = useState(0)
|
||||
const [e, setE] = useState(
|
||||
Number(
|
||||
(Number(RewardParams.interval.stake_saturation_point) / 1_000_000).toFixed(6)
|
||||
)
|
||||
)
|
||||
const [e, setE] = useState(0)
|
||||
|
||||
const result =
|
||||
e !== 0
|
||||
? `${(
|
||||
a * b * c * ((1 / 240) + 0.3 * ((d / e) / 240)) * 1 / (1 + 0.3)
|
||||
).toFixed(6)} NYM`
|
||||
).toFixed(4)} NYM`
|
||||
: '—'
|
||||
|
||||
return (
|
||||
@@ -127,3 +117,4 @@ export default function RewardsCalculator() {
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import StakeSaturation from 'components/outputs/api-scraping-outputs/nyx-outputs
|
||||
import CirculatingSupply from 'components/outputs/api-scraping-outputs/nyx-outputs/circulating-supply.md';
|
||||
import { Callout } from 'nextra/components';
|
||||
import StakingSupply from 'components/outputs/api-scraping-outputs/nyx-outputs/staking_supply.md';
|
||||
|
||||
|
||||
Stake saturation is a node reputation done in a form of self bond or stakers delegation. Optimal stake saturation level is calculated as:
|
||||
|
||||
<Callout type="info" borderColor="#008080" backgroundColor="#20b2aa" emoji="📌">
|
||||
@@ -14,10 +14,10 @@ Stake saturation is a node reputation done in a form of self bond or stakers del
|
||||
</Callout>
|
||||
|
||||
{/*
|
||||
With current circulating supply of <span style={{display: 'inline-block'}}><CirculatingSupply /></span> NYM, staking target of <span style={{display: 'inline-block'}}><StakingTarget /></span> NYM, divided by the sum of nodes in the [rewarded set](https://validator.nymtech.net/api/v1/epoch/reward_params), <b>the stake saturation level is <span style={{display: 'inline-block'}}><StakeSaturation /></span> NYM per node.</b>
|
||||
With current circulating supply of <span style={{display: 'inline-block'}}><CirculatingSupply /></span> NYM, staking target of <span style={{display: 'inline-block'}}><StakingTarget /></span> NYM, divided by the sum of nodes in the [rewarded set](https://validator.nymtech.net/api/v1/epoch/reward_params), <b>the stake saturation level is <span style={{display: 'inline-block'}}><StakeSaturation /></span> NYM per node.</b>
|
||||
*/}
|
||||
|
||||
With current circulating supply of <span style={{display: 'inline-block'}}><CirculatingSupply /></span> NYM, staking target of <span style={{display: 'inline-block'}}><StakingSupply /></span> NYM, divided by the sum of nodes in the [rewarded set](https://validator.nymtech.net/api/v1/epoch/reward_params), <b>the stake saturation level is <span style={{display: 'inline-block'}}><StakeSaturation /></span> NYM per node.</b>
|
||||
With current circulating supply of <span style={{display: 'inline-block'}}><CirculatingSupply /></span> NYM, staking target of <span style={{display: 'inline-block'}}><StakingSupply /></span> NYM, divided by the sum of nodes in the [rewarded set](https://validator.nymtech.net/api/v1/epoch/reward_params), <b>the stake saturation level is <span style={{display: 'inline-block'}}><StakeSaturation /></span> NYM per node.</b>
|
||||
|
||||
Node stake saturation is a value between `0` and `1` following this logic.
|
||||
|
||||
@@ -33,6 +33,6 @@ There is a caveat that the maximum value can be `1`. In practice it means that:
|
||||
|
||||
2. If `node_total_stake = stake_saturation_level` then `node_stake_saturation` will be `1`
|
||||
|
||||
3. If `node_total_stake > stake_saturation_level` then `node_stake_saturation` will be `1` due the capping function working as anti-whale prevention.
|
||||
- This results in a smaller <abbr title="Return on Investment (ROI) is a financial metric used to evaluate the profitability of an investment by comparing the net income with the cost of the investment.">ROI</abbr> per every staked (self bond or delegation) NYM token on that node, as the maximum rewards is capped and in this case distributed in between more staked tokens.
|
||||
- For example if `node_total_stake = 2 * stake_saturation_level` then the reward per staked token will be 50% in comparison to a case where `node_total_stake = stake_saturation_level`, in other words with 100% *over-saturation*, <abbr title="Return on Investment (ROI) is a financial metric used to evaluate the profitability of an investment by comparing the net income with the cost of the investment.">ROI</abbr> is half the maximum.
|
||||
3. If `node_total_stake > stake_saturation_level` then `node_stake_saturation` will be `1` due the capping function working as anti-whale prevention.
|
||||
- This results in smaller % APY per every staked (self bond or delegation) NYM token on that node, as the maximum rewards is capped and in this case distributed in between more staked tokens.
|
||||
- For example if `node_total_stake = 2 * stake_saturation_level` then the reward per staked token will be 50% in comparison to a case where `node_total_stake = stake_saturation_level`, in other words with 100% *over-saturation*, APY is half the maximum.
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"total_supply": {
|
||||
"denom": "unym",
|
||||
"amount": "1000000000000000"
|
||||
},
|
||||
"mixmining_reserve": {
|
||||
"denom": "unym",
|
||||
"amount": "188691142067690"
|
||||
},
|
||||
"vesting_tokens": {
|
||||
"denom": "unym",
|
||||
"amount": "0"
|
||||
},
|
||||
"circulating_supply": {
|
||||
"denom": "unym",
|
||||
"amount": "811308857932310"
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"interval": {
|
||||
"reward_pool": "188691142067690.265566584946684804",
|
||||
"staking_supply": "248179643769563.241524591796100959",
|
||||
"staking_supply_scale_factor": "0.5",
|
||||
"epoch_reward_budget": "5241420612.991396265738470741",
|
||||
"stake_saturation_point": "1034081849039.84683968579915042",
|
||||
"sybil_resistance": "0.3",
|
||||
"active_set_work_factor": "10",
|
||||
"interval_pool_emission": "0.02"
|
||||
},
|
||||
"rewarded_set": {
|
||||
"entry_gateways": 50,
|
||||
"exit_gateways": 70,
|
||||
"mixnodes": 120,
|
||||
"standby": 0
|
||||
}
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
Wednesday, May 21st 2025, 13:50:05 UTC
|
||||
Thursday, May 15th 2025, 13:46:20 UTC
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
# Introduction
|
||||
The Rust SDK allows developers building applications in Rust to import and interact with Nym clients as they would any other dependency, instead of running the client as a separate process on their machine.
|
||||
|
||||
Check the [development status](./rust/development-status) page to see the various modules that make up the SDK, and the [FFI](./rust/ffi) page for Go/C++ developers.
|
||||
@@ -1,16 +0,0 @@
|
||||
# Introduction
|
||||
|
||||
|
||||
import { Callout } from 'nextra/components';
|
||||
|
||||
<Callout type="warning">
|
||||
There will be a breaking SDK upgrade in the coming months. This upgrade will make the SDK a lot easier to build with.
|
||||
|
||||
This upgrade will affect the interface of the SDK dramatically, and will be coupled with a protocol change - stay tuned for information on early access to the new protocol testnet.
|
||||
|
||||
It will also be coupled with the documentation of the SDK on [crates.io](https://crates.io/).
|
||||
</Callout>
|
||||
|
||||
The Rust SDK allows developers building applications in Rust to import and interact with Nym clients as they would any other dependency, instead of running the client as a separate process on their machine.
|
||||
|
||||
Check the [development status](./rust/development-status) page to see the various modules that make up the SDK, and the [FFI](./rust/ffi) page for Go/C++ developers.
|
||||
@@ -4,5 +4,6 @@
|
||||
"mixnet": "Mixnet Module",
|
||||
"tcpproxy": "TcpProxy Module",
|
||||
"client-pool": "Client Pool",
|
||||
"ffi": "FFI"
|
||||
"ffi": "FFI",
|
||||
"tutorials": "Tutorials (Coming Soon)"
|
||||
}
|
||||
|
||||
@@ -1,16 +1,5 @@
|
||||
# Client Pool
|
||||
|
||||
import { Callout } from 'nextra/components';
|
||||
|
||||
<Callout type="warning">
|
||||
There will be a breaking SDK upgrade in the coming months. This upgrade will make the SDK a lot easier to build with.
|
||||
|
||||
This upgrade will affect the interface of the SDK dramatically, and will be coupled with a protocol change - stay tuned for information on early access to the new protocol testnet.
|
||||
|
||||
|
||||
It will also be coupled with the documentation of the SDK on [crates.io](https://crates.io/).
|
||||
</Callout>
|
||||
|
||||
We have a configurable-size Client Pool for processes that require multiple clients in quick succession (this is used by default by the [`TcpProxyClient`](./tcpproxy) for instance)
|
||||
|
||||
This will be useful for developers looking to build connection logic, or just are using raw SDK clients in a sitatuation where there are multiple connections with a lot of churn.
|
||||
|
||||
@@ -1,17 +1,5 @@
|
||||
# Client Pool Architecture
|
||||
|
||||
|
||||
import { Callout } from 'nextra/components';
|
||||
|
||||
<Callout type="warning">
|
||||
There will be a breaking SDK upgrade in the coming months. This upgrade will make the SDK a lot easier to build with.
|
||||
|
||||
This upgrade will affect the interface of the SDK dramatically, and will be coupled with a protocol change - stay tuned for information on early access to the new protocol testnet.
|
||||
|
||||
It will also be coupled with the documentation of the SDK on [crates.io](https://crates.io/).
|
||||
</Callout>
|
||||
|
||||
|
||||
## Motivations
|
||||
In situations where multiple connections are expected, and the number of connections can vary greatly, the Client Pool reduces time spent waiting for the creation of a Mixnet Client blocking your code sending traffic through the Mixnet. Instead, a configurable number of Clients can be generated and run in the background which can be very quickly grabbed, used, and disconnected.
|
||||
|
||||
|
||||
-10
@@ -1,15 +1,5 @@
|
||||
# Client Pool Example
|
||||
|
||||
import { Callout } from 'nextra/components';
|
||||
|
||||
<Callout type="warning">
|
||||
There will be a breaking SDK upgrade in the coming months. This upgrade will make the SDK a lot easier to build with.
|
||||
|
||||
This upgrade will affect the interface of the SDK dramatically, and will be coupled with a protocol change - stay tuned for information on early access to the new protocol testnet.
|
||||
|
||||
It will also be coupled with the documentation of the SDK on [crates.io](https://crates.io/).
|
||||
</Callout>
|
||||
|
||||
> You can find this code [here](https://github.com/nymtech/nym/blob/develop/sdk/rust/nym-sdk/examples/client_pool.rs)
|
||||
|
||||
```rust
|
||||
-12
@@ -1,16 +1,4 @@
|
||||
# Development status
|
||||
|
||||
|
||||
import { Callout } from 'nextra/components';
|
||||
|
||||
<Callout type="warning">
|
||||
There will be a breaking SDK upgrade in the coming months. This upgrade will make the SDK a lot easier to build with.
|
||||
|
||||
This upgrade will affect the interface of the SDK dramatically, and will be coupled with a protocol change - stay tuned for information on early access to the new protocol testnet.
|
||||
|
||||
It will also be coupled with the documentation of the SDK on [crates.io](https://crates.io/).
|
||||
</Callout>
|
||||
|
||||
The SDK is still somewhat a work in progress: interfaces are fairly stable but still may change in subsequent releases.
|
||||
|
||||
In the future the SDK will be made up of several modules, each of which will allow developers to interact with different parts of Nym infrastructure.
|
||||
@@ -1,14 +1,7 @@
|
||||
import { Callout } from 'nextra/components'
|
||||
|
||||
# FFI Bindings
|
||||
import { Callout } from 'nextra/components';
|
||||
|
||||
<Callout type="warning">
|
||||
There will be a breaking SDK upgrade in the coming months. This upgrade will make the SDK a lot easier to build with.
|
||||
|
||||
This upgrade will affect the interface of the SDK dramatically, and will be coupled with a protocol change - stay tuned for information on early access to the new protocol testnet.
|
||||
|
||||
It will also be coupled with the documentation of the SDK on [crates.io](https://crates.io/).
|
||||
</Callout>
|
||||
<Callout type="info" emoji="ℹ️">
|
||||
We are working on the intitial versions of the FFI code to allow developers to experiment and get feedback. Please get in touch if you think the FFI bindings are lacking certain functionality.
|
||||
</Callout>
|
||||
|
||||
-10
@@ -1,14 +1,4 @@
|
||||
# Installation
|
||||
import { Callout } from 'nextra/components';
|
||||
|
||||
<Callout type="warning">
|
||||
There will be a breaking SDK upgrade in the coming months. This upgrade will make the SDK a lot easier to build with.
|
||||
|
||||
This upgrade will affect the interface of the SDK dramatically, and will be coupled with a protocol change - stay tuned for information on early access to the new protocol testnet.
|
||||
|
||||
It will also be coupled with the documentation of the SDK on [crates.io](https://crates.io/).
|
||||
</Callout>
|
||||
|
||||
The `nym-sdk` crate is **not yet available via [crates.io](https://crates.io)**. As such, in order to import the crate you must specify the Nym monorepo in your `Cargo.toml` file. Since the `HEAD` of `master` is always the most recent release, we recommend developers use that for their imports, unless they have a reason to pull in a specific historic version of the code.
|
||||
|
||||
```toml
|
||||
@@ -1,13 +1,4 @@
|
||||
# Mixnet Module
|
||||
import { Callout } from 'nextra/components';
|
||||
|
||||
<Callout type="warning">
|
||||
There will be a breaking SDK upgrade in the coming months. This upgrade will make the SDK a lot easier to build with.
|
||||
|
||||
This upgrade will affect the interface of the SDK dramatically, and will be coupled with a protocol change - stay tuned for information on early access to the new protocol testnet.
|
||||
|
||||
It will also be coupled with the documentation of the SDK on [crates.io](https://crates.io/).
|
||||
</Callout>
|
||||
|
||||
This module exposes the logic of creating and interacting with clients and Mixnet messages. This is recommended for those wanting to either start playing around with the Mixnet and how it works, or build connection logic.
|
||||
|
||||
|
||||
-10
@@ -1,15 +1,5 @@
|
||||
# Examples
|
||||
|
||||
import { Callout } from 'nextra/components';
|
||||
|
||||
<Callout type="warning">
|
||||
There will be a breaking SDK upgrade in the coming months. This upgrade will make the SDK a lot easier to build with.
|
||||
|
||||
This upgrade will affect the interface of the SDK dramatically, and will be coupled with a protocol change - stay tuned for information on early access to the new protocol testnet.
|
||||
|
||||
It will also be coupled with the documentation of the SDK on [crates.io](https://crates.io/).
|
||||
</Callout>
|
||||
|
||||
All the following examples can be found in the `nym-sdk` [examples directory](https://github.com/nymtech/nym/tree/master/sdk/rust/nym-sdk/examples) in the monorepo. Just navigate to `nym/sdk/rust/nym-sdk/examples/` and run the files from there with:
|
||||
|
||||
```sh
|
||||
@@ -0,0 +1,3 @@
|
||||
# Builder Patterns
|
||||
|
||||
Since there are two ways of creating an SDK client - ephemeral and with-storage - then there are two ways of applying the Builder Pattern to client creation.
|
||||
@@ -1,11 +0,0 @@
|
||||
# Builder Patterns
|
||||
import { Callout } from 'nextra/components';
|
||||
|
||||
<Callout type="warning">
|
||||
There will be a breaking SDK upgrade in the coming months. This upgrade will make the SDK a lot easier to build with.
|
||||
|
||||
This upgrade will affect the interface of the SDK dramatically, and will be coupled with a protocol change - stay tuned for information on early access to the new protocol testnet.
|
||||
|
||||
It will also be coupled with the documentation of the SDK on [crates.io](https://crates.io/).
|
||||
</Callout>
|
||||
Since there are two ways of creating an SDK client - ephemeral and with-storage - then there are two ways of applying the Builder Pattern to client creation.
|
||||
-8
@@ -1,13 +1,5 @@
|
||||
# Mixnet Client Builder with Storage
|
||||
import { Callout } from 'nextra/components';
|
||||
|
||||
<Callout type="warning">
|
||||
There will be a breaking SDK upgrade in the coming months. This upgrade will make the SDK a lot easier to build with.
|
||||
|
||||
This upgrade will affect the interface of the SDK dramatically, and will be coupled with a protocol change - stay tuned for information on early access to the new protocol testnet.
|
||||
|
||||
It will also be coupled with the documentation of the SDK on [crates.io](https://crates.io/).
|
||||
</Callout>
|
||||
The previous example involves ephemeral keys - if we want to create and then maintain a client identity over time, our code becomes a little more complex as we need to create, store, and conditionally load these keys.
|
||||
|
||||
> You can find this code [here](https://github.com/nymtech/nym/blob/master/sdk/rust/nym-sdk/examples/builder_with_storage.rs).
|
||||
-8
@@ -1,13 +1,5 @@
|
||||
# Mixnet Client Builder
|
||||
import { Callout } from 'nextra/components';
|
||||
|
||||
<Callout type="warning">
|
||||
There will be a breaking SDK upgrade in the coming months. This upgrade will make the SDK a lot easier to build with.
|
||||
|
||||
This upgrade will affect the interface of the SDK dramatically, and will be coupled with a protocol change - stay tuned for information on early access to the new protocol testnet.
|
||||
|
||||
It will also be coupled with the documentation of the SDK on [crates.io](https://crates.io/).
|
||||
</Callout>
|
||||
You can spin up an ephemeral client like so. This client will not have a persistent identity and its keys will be dropped on restart. Since there is currently no way of reconnecting a client that has been disconnected after use, then treat disconnecting a client the same as dropping its keys entirely.
|
||||
|
||||
> You can find this code [here](https://github.com/nymtech/nym/blob/master/sdk/rust/nym-sdk/examples/builder.rs).
|
||||
@@ -2,14 +2,6 @@
|
||||
|
||||
import { Callout } from 'nextra/components'
|
||||
|
||||
<Callout type="warning">
|
||||
There will be a breaking SDK upgrade in the coming months. This upgrade will make the SDK a lot easier to build with.
|
||||
|
||||
This upgrade will affect the interface of the SDK dramatically, and will be coupled with a protocol change - stay tuned for information on early access to the new protocol testnet.
|
||||
|
||||
It will also be coupled with the documentation of the SDK on [crates.io](https://crates.io/).
|
||||
</Callout>
|
||||
|
||||
<Callout type="warning" emoji="⚠️">
|
||||
These examples are **not** the same as using a configurable network: these functions define a subset of nodes to use on a given network, whereas the [testnet](./testnet) example is an example of switching to use a different network entirely. The two can be combined, but if you are looking for how to connect your client to a testnet, see the `testnet` file.
|
||||
</Callout>
|
||||
|
||||
-8
@@ -1,13 +1,5 @@
|
||||
# Custom Topology Provider
|
||||
import { Callout } from 'nextra/components';
|
||||
|
||||
<Callout type="warning">
|
||||
There will be a breaking SDK upgrade in the coming months. This upgrade will make the SDK a lot easier to build with.
|
||||
|
||||
This upgrade will affect the interface of the SDK dramatically, and will be coupled with a protocol change - stay tuned for information on early access to the new protocol testnet.
|
||||
|
||||
It will also be coupled with the documentation of the SDK on [crates.io](https://crates.io/).
|
||||
</Callout>
|
||||
If you are also running a Validator and Nym API for your network, you can specify that endpoint as such and interact with it as clients usually do (under the hood).
|
||||
|
||||
> You can find this code [here](https://github.com/nymtech/nym/blob/master/sdk/rust/nym-sdk/examples/custom_topology_provider.rs)
|
||||
-8
@@ -1,13 +1,5 @@
|
||||
# Manually Overwrite Topology
|
||||
import { Callout } from 'nextra/components';
|
||||
|
||||
<Callout type="warning">
|
||||
There will be a breaking SDK upgrade in the coming months. This upgrade will make the SDK a lot easier to build with.
|
||||
|
||||
This upgrade will affect the interface of the SDK dramatically, and will be coupled with a protocol change - stay tuned for information on early access to the new protocol testnet.
|
||||
|
||||
It will also be coupled with the documentation of the SDK on [crates.io](https://crates.io/).
|
||||
</Callout>
|
||||
If you aren't running a Validator and Nym API, and just want to import a specific sub-set of mix nodes, you can simply overwrite the grabbed topology manually.
|
||||
|
||||
> You can find this code [here](https://github.com/nymtech/nym/blob/master/sdk/rust/nym-sdk/examples/manually_overwrite_topology.rs)
|
||||
-10
@@ -1,14 +1,4 @@
|
||||
# Simple Send
|
||||
|
||||
import { Callout } from 'nextra/components'
|
||||
|
||||
<Callout type="warning">
|
||||
There will be a breaking SDK upgrade in the coming months. This upgrade will make the SDK a lot easier to build with.
|
||||
|
||||
This upgrade will affect the interface of the SDK dramatically, and will be coupled with a protocol change - stay tuned for information on early access to the new protocol testnet.
|
||||
|
||||
It will also be coupled with the documentation of the SDK on [crates.io](https://crates.io/).
|
||||
</Callout>
|
||||
Lets look at a very simple example of how you can import and use the websocket client in a piece of Rust code.
|
||||
|
||||
Simply importing the `nym_sdk` crate into your project allows you to create a client and send traffic through the mixnet.
|
||||
-9
@@ -1,14 +1,5 @@
|
||||
# Socks Proxy
|
||||
|
||||
import { Callout } from 'nextra/components'
|
||||
|
||||
<Callout type="warning">
|
||||
There will be a breaking SDK upgrade in the coming months. This upgrade will make the SDK a lot easier to build with.
|
||||
|
||||
This upgrade will affect the interface of the SDK dramatically, and will be coupled with a protocol change - stay tuned for information on early access to the new protocol testnet.
|
||||
|
||||
It will also be coupled with the documentation of the SDK on [crates.io](https://crates.io/).
|
||||
</Callout>
|
||||
If you are looking at implementing Nym as a transport layer for a crypto wallet or desktop app, this is probably the best place to start if they can speak SOCKS5, 4a, or 4.
|
||||
|
||||
> You can find this code [here](https://github.com/nymtech/nym/blob/master/sdk/rust/nym-sdk/examples/socks5.rs)
|
||||
-10
@@ -1,14 +1,4 @@
|
||||
# Send and Receive in Different Tasks
|
||||
|
||||
import { Callout } from 'nextra/components'
|
||||
|
||||
<Callout type="warning">
|
||||
There will be a breaking SDK upgrade in the coming months. This upgrade will make the SDK a lot easier to build with.
|
||||
|
||||
This upgrade will affect the interface of the SDK dramatically, and will be coupled with a protocol change - stay tuned for information on early access to the new protocol testnet.
|
||||
|
||||
It will also be coupled with the documentation of the SDK on [crates.io](https://crates.io/).
|
||||
</Callout>
|
||||
If you need to split the different actions of your client across different tasks, you can do so like this. You can think of this analogously to spliting a Tcp Stream into read/write. This functionality is also useful for embedding a sending and receiving client into different tasks.
|
||||
|
||||
> You can find this code [here](https://github.com/nymtech/nym/blob/master/sdk/rust/nym-sdk/examples/parallel_sending_and_receiving.rs)
|
||||
-10
@@ -1,14 +1,4 @@
|
||||
# Manually Handle Storage
|
||||
|
||||
import { Callout } from 'nextra/components'
|
||||
|
||||
<Callout type="warning">
|
||||
There will be a breaking SDK upgrade in the coming months. This upgrade will make the SDK a lot easier to build with.
|
||||
|
||||
This upgrade will affect the interface of the SDK dramatically, and will be coupled with a protocol change - stay tuned for information on early access to the new protocol testnet.
|
||||
|
||||
It will also be coupled with the documentation of the SDK on [crates.io](https://crates.io/).
|
||||
</Callout>
|
||||
If you're integrating mixnet functionality into an existing app and want to integrate saving client configs and keys into your existing storage logic, you can manually perform these actions.
|
||||
|
||||
> You can find this code [here](https://github.com/nymtech/nym/blob/master/sdk/rust/nym-sdk/examples/manually_handle_storage.rs)
|
||||
-10
@@ -1,14 +1,4 @@
|
||||
# Anonymous Replies with SURBs (Single Use Reply Blocks)
|
||||
|
||||
import { Callout } from 'nextra/components'
|
||||
|
||||
<Callout type="warning">
|
||||
There will be a breaking SDK upgrade in the coming months. This upgrade will make the SDK a lot easier to build with.
|
||||
|
||||
This upgrade will affect the interface of the SDK dramatically, and will be coupled with a protocol change - stay tuned for information on early access to the new protocol testnet.
|
||||
|
||||
It will also be coupled with the documentation of the SDK on [crates.io](https://crates.io/).
|
||||
</Callout>
|
||||
Both functions used to send messages through the mixnet (`send_message` and `send_plain_message`) send a pre-determined number of SURBs along with their messages by default.
|
||||
|
||||
You can read more about how SURBs function under the hood [here](../../../../network/traffic/anonymous-replies).
|
||||
@@ -1,13 +1,5 @@
|
||||
# Configurable Network
|
||||
import { Callout } from 'nextra/components'
|
||||
|
||||
<Callout type="warning">
|
||||
There will be a breaking SDK upgrade in the coming months. This upgrade will make the SDK a lot easier to build with.
|
||||
|
||||
This upgrade will affect the interface of the SDK dramatically, and will be coupled with a protocol change - stay tuned for information on early access to the new protocol testnet.
|
||||
|
||||
It will also be coupled with the documentation of the SDK on [crates.io](https://crates.io/).
|
||||
</Callout>
|
||||
If you want to connect your Mixnet client to a different network than Mainnet, simply pull in a file from [`nym/envs`](https://github.com/nymtech/nym/tree/master/envs) as such:
|
||||
|
||||
```rust
|
||||
|
||||
-10
@@ -1,15 +1,5 @@
|
||||
# Message Helpers
|
||||
|
||||
import { Callout } from 'nextra/components';
|
||||
|
||||
<Callout type="warning">
|
||||
There will be a breaking SDK upgrade in the coming months. This upgrade will make the SDK a lot easier to build with.
|
||||
|
||||
This upgrade will affect the interface of the SDK dramatically, and will be coupled with a protocol change - stay tuned for information on early access to the new protocol testnet.
|
||||
|
||||
It will also be coupled with the documentation of the SDK on [crates.io](https://crates.io/).
|
||||
</Callout>
|
||||
|
||||
## Handling incoming messages
|
||||
When listening out for a response to a sent message (e.g. if you have sent a request to a service, and are awaiting the response) you will want to await [non-empty messages (if you don't know why, read the info on this here)](./troubleshooting#client-receives-empty-messages-when-listening-for-response). This can be done with something like the helper functions here:
|
||||
|
||||
-10
@@ -1,15 +1,5 @@
|
||||
# Message Types
|
||||
|
||||
import { Callout } from 'nextra/components';
|
||||
|
||||
<Callout type="warning">
|
||||
There will be a breaking SDK upgrade in the coming months. This upgrade will make the SDK a lot easier to build with.
|
||||
|
||||
This upgrade will affect the interface of the SDK dramatically, and will be coupled with a protocol change - stay tuned for information on early access to the new protocol testnet.
|
||||
|
||||
It will also be coupled with the documentation of the SDK on [crates.io](https://crates.io/).
|
||||
</Callout>
|
||||
|
||||
There are several functions used to send outgoing messages through the Mixnet, each with a different level of customisation:
|
||||
|
||||
- `send(&self, message: InputMessage) -> Result<()>`
|
||||
-13
@@ -1,17 +1,4 @@
|
||||
# Troubleshooting
|
||||
|
||||
import { Callout } from 'nextra/components';
|
||||
|
||||
<Callout type="warning">
|
||||
There will be a breaking SDK upgrade in the coming months. This upgrade will make the SDK a lot easier to build with.
|
||||
|
||||
This upgrade will affect the interface of the SDK dramatically, and will be coupled with a protocol change - stay tuned for information on early access to the new protocol testnet.
|
||||
|
||||
|
||||
It will also be coupled with the documentation of the SDK on [crates.io](https://crates.io/).
|
||||
</Callout>
|
||||
|
||||
|
||||
Below are several common issues or questions you may have.
|
||||
|
||||
If you come across something that isn't explained here, [PRs are welcome](https://github.com/nymtech/nym/issues/new/choose).
|
||||
@@ -1,13 +1,5 @@
|
||||
# TcpProxy Module
|
||||
import { Callout } from 'nextra/components';
|
||||
|
||||
<Callout type="warning">
|
||||
There will be a breaking SDK upgrade in the coming months. This upgrade will make the SDK a lot easier to build with.
|
||||
|
||||
This upgrade will affect the interface of the SDK dramatically, and will be coupled with a protocol change - stay tuned for information on early access to the new protocol testnet.
|
||||
|
||||
It will also be coupled with the documentation of the SDK on [crates.io](https://crates.io/).
|
||||
</Callout>
|
||||
This module exposes the `TcpProxyClient` and the `TcpProxyServer` which can be used to proxy traffic through the Mixnet in a way that is more familiar to developers than the methods exposed by the [`Mixnet` module](./mixnet).
|
||||
|
||||
Both `Client` and `Server` are intended to be initialised and then run in a background thread, exposing a configurable `localhost` socket which developers can read/write/stream to without having to worry about the [message-based](../concepts/messages) nature of sending and receiving traffic to/from the Mixnet.
|
||||
|
||||
@@ -1,15 +1,6 @@
|
||||
|
||||
# Architecture
|
||||
|
||||
import { Callout } from 'nextra/components'
|
||||
|
||||
<Callout type="warning">
|
||||
There will be a breaking SDK upgrade in the coming months. This upgrade will make the SDK a lot easier to build with.
|
||||
|
||||
This upgrade will affect the interface of the SDK dramatically, and will be coupled with a protocol change - stay tuned for information on early access to the new protocol testnet.
|
||||
|
||||
It will also be coupled with the documentation of the SDK on [crates.io](https://crates.io/).
|
||||
</Callout>
|
||||
# Architecture
|
||||
|
||||
## Motivations
|
||||
The motivation behind the creation of the `TcpProxy` module is to allow developers to interact with the Mixnet in a way that is far more familiar to them: simply setting up a connection with a transport, being returned a socket, and then being able to stream data to/from it, similar to something like the Tor [`arti`](https://gitlab.torproject.org/tpo/core/arti/-/tree/main/crates/arti-client) client.
|
||||
|
||||
@@ -1,13 +1,5 @@
|
||||
# Multi Connection Example
|
||||
import { Callout } from 'nextra/components'
|
||||
|
||||
<Callout type="warning">
|
||||
There will be a breaking SDK upgrade in the coming months. This upgrade will make the SDK a lot easier to build with.
|
||||
|
||||
This upgrade will affect the interface of the SDK dramatically, and will be coupled with a protocol change - stay tuned for information on early access to the new protocol testnet.
|
||||
|
||||
It will also be coupled with the documentation of the SDK on [crates.io](https://crates.io/).
|
||||
</Callout>
|
||||
This example starts off several Tcp connections on a loop to a remote endpoint: in this case the `TcpListener` behind the `NymProxyServer` instance on the echo server found in
|
||||
[`nym/tools/echo-server/`](https://github.com/nymtech/nym/tree/develop/tools/echo-server). It pipes a few messages to it, logs the replies, and keeps track of the number of replies received per connection.
|
||||
|
||||
|
||||
@@ -1,13 +1,5 @@
|
||||
# Single Connection Example
|
||||
import { Callout } from 'nextra/components'
|
||||
|
||||
<Callout type="warning">
|
||||
There will be a breaking SDK upgrade in the coming months. This upgrade will make the SDK a lot easier to build with.
|
||||
|
||||
This upgrade will affect the interface of the SDK dramatically, and will be coupled with a protocol change - stay tuned for information on early access to the new protocol testnet.
|
||||
|
||||
It will also be coupled with the documentation of the SDK on [crates.io](https://crates.io/).
|
||||
</Callout>
|
||||
This is a basic example which opens a single TCP connection and writes a bunch of messages between a client and some 'echo server' logic, so only uses a single session under the hood and doesn't really show off the message ordering capabilities; this is mainly just a quick introductory illustration on how:
|
||||
- the mixnet does message ordering
|
||||
- the NymProxyClient and NymProxyServer can be hooked into and used to communicate between two otherwise pretty vanilla TcpStreams
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
# Troubleshooting
|
||||
|
||||
## Lots of `duplicate fragment received` messages
|
||||
You might see a lot of `WARN` level logs about duplicate fragments in your logs, depending on the log level you're using. This occurs when a packet is retransmitted somewhere in the Mixnet, but then the original makes it to the destination client as well. This is not something to do with your client logic, but instead the state of the Mixnet.
|
||||
@@ -1,12 +0,0 @@
|
||||
# Troubleshooting
|
||||
import { Callout } from 'nextra/components'
|
||||
|
||||
<Callout type="warning">
|
||||
There will be a breaking SDK upgrade in the coming months. This upgrade will make the SDK a lot easier to build with.
|
||||
|
||||
This upgrade will affect the interface of the SDK dramatically, and will be coupled with a protocol change - stay tuned for information on early access to the new protocol testnet.
|
||||
|
||||
It will also be coupled with the documentation of the SDK on [crates.io](https://crates.io/).
|
||||
</Callout>
|
||||
## Lots of `duplicate fragment received` messages
|
||||
You might see a lot of `WARN` level logs about duplicate fragments in your logs, depending on the log level you're using. This occurs when a packet is retransmitted somewhere in the Mixnet, but then the original makes it to the destination client as well. This is not something to do with your client logic, but instead the state of the Mixnet.
|
||||
@@ -1,16 +1,5 @@
|
||||
|
||||
# Introduction
|
||||
|
||||
import { Callout } from 'nextra/components';
|
||||
|
||||
<Callout type="error">
|
||||
The TypeScript SDK is currently not avaliable to use: a network upgrade elsewhere has caused a problem which is not currently fixed. TS SDK Clients are not able to connect to the network.
|
||||
|
||||
When the issue is resolved, this will be reflected in the documentation.
|
||||
|
||||
Thanks for your patience!
|
||||
</Callout>
|
||||
|
||||
Welcome to the documentation for Nym's TypeScript SDK!
|
||||
|
||||
This guide contains information about the various TypeScript SDK modules that facilitate interaction with different components of the Nym stack, including the Nym mixnet, the Nyx blockchain, and Coconut credentials.
|
||||
This comprehensive guide contains information about the various TypeScript SDK modules that facilitate interaction with different components of the Nym stack, including the Nym mixnet, the Nyx blockchain, and Coconut credentials.
|
||||
|
||||
@@ -1,13 +1,4 @@
|
||||
# TS SDK FAQ
|
||||
import { Callout } from 'nextra/components'
|
||||
|
||||
<Callout type="error">
|
||||
The TypeScript SDK is currently not avaliable to use: a network upgrade elsewhere has caused a problem which is not currently fixed. TS SDK Clients are not able to connect to the network.
|
||||
|
||||
When the issue is resolved, this will be reflected in the documentation.
|
||||
|
||||
Thanks for your patience!
|
||||
</Callout>
|
||||
|
||||
## Why and when does the mixnet client complain about insufficient topology?
|
||||
|
||||
|
||||
@@ -1,15 +1,5 @@
|
||||
# Troubleshooting bundling
|
||||
|
||||
import { Callout } from 'nextra/components';
|
||||
|
||||
<Callout type="error">
|
||||
The TypeScript SDK is currently not avaliable to use: a network upgrade elsewhere has caused a problem which is not currently fixed. TS SDK Clients are not able to connect to the network.
|
||||
|
||||
When the issue is resolved, this will be reflected in the documentation.
|
||||
|
||||
Thanks for your patience!
|
||||
</Callout>
|
||||
|
||||
You might need some help bundling packages from the Nym Typescript SDK into your package.
|
||||
|
||||
Here are some things that could go wrong:
|
||||
|
||||
@@ -2,14 +2,6 @@ import { Callout } from 'nextra/components';
|
||||
|
||||
# Troubleshooting bundling with ESbuild
|
||||
|
||||
<Callout type="error">
|
||||
The TypeScript SDK is currently not avaliable to use: a network upgrade elsewhere has caused a problem which is not currently fixed. TS SDK Clients are not able to connect to the network.
|
||||
|
||||
When the issue is resolved, this will be reflected in the documentation.
|
||||
|
||||
Thanks for your patience!
|
||||
</Callout>
|
||||
|
||||
If you've been following the steps outlined in the Examples section, your development environment should be configured as follows:
|
||||
|
||||
#### Environment Setup
|
||||
@@ -22,7 +14,7 @@ npm create vite@latest
|
||||
During the environment setup, choose React and subsequently opt for Typescript if you want your application to function smoothly following this tutorial. Next, navigate to your application directory and run the following commands:
|
||||
```bash
|
||||
cd < YOUR_APP >
|
||||
npm i
|
||||
npm i
|
||||
npm run dev
|
||||
```
|
||||
|
||||
@@ -37,3 +29,4 @@ npm install @nymproject/< PACKAGE_NAME >
|
||||
</Callout>
|
||||
|
||||
By implementing the provided code for the various components in the step-by-step examples section, you should be able to set-up and run your application without encountering any bundling challenges!
|
||||
|
||||
|
||||
@@ -2,15 +2,6 @@ import { Callout } from 'nextra/components';
|
||||
|
||||
# Troubleshooting bundling with Webpack
|
||||
|
||||
|
||||
<Callout type="error">
|
||||
The TypeScript SDK is currently not avaliable to use: a network upgrade elsewhere has caused a problem which is not currently fixed. TS SDK Clients are not able to connect to the network.
|
||||
|
||||
When the issue is resolved, this will be reflected in the documentation.
|
||||
|
||||
Thanks for your patience!
|
||||
</Callout>
|
||||
|
||||
## Webpack > 5 ESM
|
||||
|
||||
For any project using Webpack, you´ll need the following rule in your `webpack.config.js` above version 5:
|
||||
@@ -33,7 +24,7 @@ If you wish to use Webpack for your app with the code provided in the step-by-st
|
||||
npx create-react-app nymapp --template typescript
|
||||
cd nymapp
|
||||
```
|
||||
You'll then need to install the needed dependencies, head to your app's `App.tsx` file and paste the code provided in the step-by-step section.
|
||||
You'll then need to install the needed dependencies, head to your app's `App.tsx` file and paste the code provided in the step-by-step section.
|
||||
|
||||
#### Contract client
|
||||
|
||||
@@ -43,7 +34,7 @@ You'll then need to install the needed dependencies, head to your app's `App.tsx
|
||||
|
||||
##### Install contract-clients dependencies
|
||||
```bash
|
||||
npm install @nymproject/contract-clients @cosmjs/cosmwasm-stargate @cosmjs/proto-signing
|
||||
npm install @nymproject/contract-clients @cosmjs/cosmwasm-stargate @cosmjs/proto-signing
|
||||
```
|
||||
|
||||
Head to you app's `App.tsx` file and replace the code by the one provided in the step-by-step examples section.
|
||||
@@ -85,7 +76,7 @@ module.exports = function override(config) {
|
||||
}
|
||||
}
|
||||
])
|
||||
return config;
|
||||
return config;
|
||||
}
|
||||
EOF
|
||||
```
|
||||
@@ -99,4 +90,4 @@ EOF
|
||||
"test": "react-app-rewired test",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
```
|
||||
```
|
||||
@@ -2,12 +2,8 @@ import { Callout } from 'nextra/components'
|
||||
|
||||
# Cosmos Kit
|
||||
|
||||
<Callout type="error">
|
||||
The TypeScript SDK is currently not avaliable: a network upgrade elsewhere has caused a problem which is not currently fixed. TS SDK Clients are not able to connect to the network.
|
||||
|
||||
When the issue is resolved, this will be reflected in the documentation.
|
||||
|
||||
Thanks for your patience!
|
||||
<Callout type="warning">
|
||||
The Typescript SDK is currently undergoing maintenance: a network upgrade elsewhere has temporarily caused a problem. These docs are likely to change after we upgrade the SDK.
|
||||
</Callout>
|
||||
|
||||
The wonderful people of Cosmology have made some [fantastic components](https://cosmoskit.com/) that can be used with
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
import { Callout } from 'nextra/components';
|
||||
|
||||
# `mixFetch`
|
||||
<Callout type="error">
|
||||
The TypeScript SDK is currently not avaliable to use: a network upgrade elsewhere has caused a problem which is not currently fixed. TS SDK Clients are not able to connect to the network.
|
||||
|
||||
When the issue is resolved, this will be reflected in the documentation.
|
||||
|
||||
Thanks for your patience!
|
||||
<Callout type="warning">
|
||||
The Typescript SDK is currently undergoing maintenance: a network upgrade elsewhere has temporarily caused a problem. These docs are likely to change after we upgrade the SDK.
|
||||
</Callout>
|
||||
|
||||
An easy way to secure parts or all of your web app is to replace calls to [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch) with `mixFetch`:
|
||||
|
||||
MixFetch works the same as vanilla `fetch` as it's a proxied wrapper around the original function.
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
import { Callout } from 'nextra/components'
|
||||
|
||||
# Mixnet Client
|
||||
<Callout type="error">
|
||||
The TypeScript SDK is currently not avaliable to use: a network upgrade elsewhere has caused a problem which is not currently fixed. TS SDK Clients are not able to connect to the network.
|
||||
|
||||
When the issue is resolved, this will be reflected in the documentation.
|
||||
|
||||
Thanks for your patience!
|
||||
<Callout type="warning">
|
||||
The Typescript SDK is currently undergoing maintenance: a network upgrade elsewhere has temporarily caused a problem. These docs are likely to change after we upgrade the SDK.
|
||||
</Callout>
|
||||
As you know by now, in order to send or receive messages over the mixnet, you'll need to use the [`SDK Client`](https://www.npmjs.com/package/@nymproject/sdk), which will allow you to create apps that can use the Nym mixnet and Coconut credentials.
|
||||
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
import { Callout } from 'nextra/components'
|
||||
|
||||
# Nym Smart Contract Clients
|
||||
<Callout type="error">
|
||||
The TypeScript SDK is currently not avaliable to use: a network upgrade elsewhere has caused a problem which is not currently fixed. TS SDK Clients are not able to connect to the network.
|
||||
|
||||
When the issue is resolved, this will be reflected in the documentation.
|
||||
|
||||
Thanks for your patience!
|
||||
<Callout type="warning">
|
||||
The Typescript SDK is currently undergoing maintenance: a network upgrade elsewhere has temporarily caused a problem. These docs are likely to change after we upgrade the SDK.
|
||||
</Callout>
|
||||
As previously mentioned, to query or execute on any of the Nym contracts, you'll need to use one of the [`Contract Clients`](https://www.npmjs.com/package/@nymproject/contract-clients), which contains read-only query and signing clients for all of Nym's smart contracts.
|
||||
|
||||
|
||||
@@ -4,14 +4,6 @@ import { Callout } from 'nextra/components'
|
||||
|
||||
The different modules in the Typescript SDK allow developers to start building browser-based applications quickly. Simply import the SDK module of your choice – depending on the component from the Nym architecture you want to use – into your code via NPM, as you would any other TypeScript library.
|
||||
|
||||
<Callout type="error">
|
||||
The TypeScript SDK is currently not avaliable to use: a network upgrade elsewhere has caused a problem which is not currently fixed. TS SDK Clients are not able to connect to the network.
|
||||
|
||||
When the issue is resolved, this will be reflected in the documentation.
|
||||
|
||||
Thanks for your patience!
|
||||
</Callout>
|
||||
|
||||
<Callout type="info" emoji="ℹ️">
|
||||
Other than the `Contract Clients`, SDK modules come in four different flavours (ESM, CJS and full-fat for ESM and CJS).
|
||||
This documentation focuses on examples using the `full-fat` versions.
|
||||
|
||||
@@ -3,16 +3,6 @@ import { TableContainer, Table, TableBody, TableCell, TableRow, Paper } from '@m
|
||||
import { NPMLink } from '../../../components/npm';
|
||||
|
||||
## SDK overview
|
||||
|
||||
|
||||
<Callout type="error">
|
||||
The TypeScript SDK is currently not avaliable to use: a network upgrade elsewhere has caused a problem which is not currently fixed. TS SDK Clients are not able to connect to the network.
|
||||
|
||||
When the issue is resolved, this will be reflected in the documentation.
|
||||
|
||||
Thanks for your patience!
|
||||
</Callout>
|
||||
|
||||
The Typescript SDK allows developers to start building browser-based Nym-based applications quickly, by simply importing the SDK modules into their code via NPM as they would any other Typescript library.
|
||||
|
||||
Currently developers can use different packages from the Typescript SDK to run the following entirely in browser:
|
||||
|
||||
@@ -6,12 +6,8 @@ import FormattedCosmoskitExampleCode from '../../../../code-examples/sdk/typescr
|
||||
|
||||
import { Callout } from 'nextra/components'
|
||||
|
||||
<Callout type="error">
|
||||
The TypeScript SDK is currently not avaliable to use: a network upgrade elsewhere has caused a problem which is not currently fixed. TS SDK Clients are not able to connect to the network.
|
||||
|
||||
When the issue is resolved, this will be reflected in the documentation.
|
||||
|
||||
Thanks for your patience!
|
||||
<Callout type="warning">
|
||||
The Typescript SDK is currently undergoing maintenance: a network upgrade elsewhere has temporarily caused a problem. These docs are likely to change after we upgrade the SDK.
|
||||
</Callout>
|
||||
|
||||
Below is an example that uses [CosmosKit](https://cosmoskit.com/) to connect and sign a fake transaction with your [Keplr wallet](https://www.keplr.app/) or
|
||||
|
||||
@@ -5,16 +5,23 @@ import Box from '@mui/material/Box';
|
||||
import FormattedMixFetchExampleCode from '../../../../code-examples/sdk/typescript/mixfetch-example-code.mdx';
|
||||
import { Callout } from 'nextra/components'
|
||||
|
||||
|
||||
<Callout type="error">
|
||||
The TypeScript SDK is currently not avaliable to use: a network upgrade elsewhere has caused a problem which is not currently fixed. TS SDK Clients are not able to connect to the network.
|
||||
|
||||
When the issue is resolved, this will be reflected in the documentation.
|
||||
|
||||
Thanks for your patience!
|
||||
<Callout type="warning">
|
||||
The Typescript SDK is currently undergoing maintenance: a network upgrade elsewhere has temporarily caused a problem. These docs are likely to change after we upgrade the SDK.
|
||||
</Callout>
|
||||
|
||||
<Callout type="info" emoji="ℹ️">
|
||||
Right now Gateways are not required to run a Secure Websocket (WSS) listener, so only a subset of nodes running in Gateway mode have configured their nodes to do so.
|
||||
|
||||
For the moment you have to select a Gateway that has WSS enabled from [this list](https://harbourmaster.nymtech.net/v1/services?wss=true).
|
||||
|
||||
You can also find WSS-enabled nodes by querying the `gateways/described` endpoint on the Nym API and filtering for `wss_port`, either via the [Swagger webpage](https://validator.nymtech.net/api/swagger/index.html) or with `curl`:
|
||||
|
||||
```
|
||||
curl -X 'GET' \
|
||||
'https://validator.nymtech.net/api/v1/gateways/described' \
|
||||
-H 'accept: application/json'
|
||||
```
|
||||
</Callout>
|
||||
|
||||
|
||||
<MixFetch />
|
||||
|
||||
@@ -5,14 +5,8 @@ import Box from '@mui/material/Box';
|
||||
import FormattedExampleCode from '../../../../code-examples/sdk/typescript/mixnodes-example-code.mdx';
|
||||
import { Callout } from 'nextra/components'
|
||||
|
||||
|
||||
|
||||
<Callout type="error">
|
||||
The TypeScript SDK is currently not avaliable to use: a network upgrade elsewhere has caused a problem which is not currently fixed. TS SDK Clients are not able to connect to the network.
|
||||
|
||||
When the issue is resolved, this will be reflected in the documentation.
|
||||
|
||||
Thanks for your patience!
|
||||
<Callout type="warning">
|
||||
The Typescript SDK is currently undergoing maintenance: a network upgrade elsewhere has temporarily caused a problem. These docs are likely to change after we upgrade the SDK.
|
||||
</Callout>
|
||||
|
||||
The Nym Mixnet contract keeps a directory of all mixnodes that can be used to mix traffic.
|
||||
|
||||
@@ -4,14 +4,10 @@ import { Traffic } from '../../../../components/traffic';
|
||||
import Box from '@mui/material/Box';
|
||||
import FormattedTrafficExampleCode from '../../../../code-examples/sdk/typescript/traffic-example-code.mdx';
|
||||
|
||||
|
||||
<Callout type="error">
|
||||
The TypeScript SDK is currently not avaliable to use: a network upgrade elsewhere has caused a problem which is not currently fixed. TS SDK Clients are not able to connect to the network.
|
||||
|
||||
When the issue is resolved, this will be reflected in the documentation.
|
||||
|
||||
Thanks for your patience!
|
||||
<Callout type="warning">
|
||||
The Typescript SDK is currently undergoing maintenance: a network upgrade elsewhere has temporarily caused a problem. These docs are likely to change after we upgrade the SDK.
|
||||
</Callout>
|
||||
|
||||
Use this tool to experiment with the mixnet: send and receive messages!
|
||||
|
||||
<Traffic />
|
||||
|
||||
@@ -13,12 +13,8 @@ import FormattedWalletDelegationsCode from '../../../../code-examples/sdk/typesc
|
||||
|
||||
import { Callout } from 'nextra/components'
|
||||
|
||||
<Callout type="error">
|
||||
The TypeScript SDK is currently not avaliable to use: a network upgrade elsewhere has caused a problem which is not currently fixed. TS SDK Clients are not able to connect to the network.
|
||||
|
||||
When the issue is resolved, this will be reflected in the documentation.
|
||||
|
||||
Thanks for your patience!
|
||||
<Callout type="warning">
|
||||
The Typescript SDK is currently undergoing maintenance: a network upgrade elsewhere has temporarily caused a problem. These docs are likely to change after we upgrade the SDK.
|
||||
</Callout>
|
||||
|
||||
Here's a small wallet example using testnet for you to test out!
|
||||
|
||||
@@ -3,14 +3,6 @@ import { Callout } from 'nextra/components'
|
||||
|
||||
## MixFetch
|
||||
|
||||
<Callout type="error">
|
||||
The TypeScript SDK is currently not avaliable to use: a network upgrade elsewhere has caused a problem which is not currently fixed. TS SDK Clients are not able to connect to the network.
|
||||
|
||||
When the issue is resolved, this will be reflected in the documentation.
|
||||
|
||||
Thanks for your patience!
|
||||
</Callout>
|
||||
|
||||
Use the [`mixFetch`](https://www.npmjs.com/package/@nymproject/mix-fetch) package as a drop-in replacement for `fetch`to send HTTP requests over the Nym mixnet:
|
||||
|
||||
```ts
|
||||
|
||||
@@ -57,11 +57,11 @@ Nodes bonded with vesting tokens are [not allowed to join rewarded set](https://
|
||||
|
||||
## Rewards Logic & Overview
|
||||
|
||||
This is a quick summary, to understand the logic behind fundamentals like [rewarded set selection](#rewarded-set-selection), [node performance](#node-performance-calculation), [stake saturation](#stake-saturation), or [rewards calculation](#rewards-calculation), please read the chapters below.
|
||||
This is a quick overview, to understand the logic behind fundamentals like [rewarded set selection](#rewarded-set-selection), [node performance](#node-performance-calculation), [stake saturation](#stake-saturation), or [rewards calculation](#rewards-calculation), please read the chapters below.
|
||||
|
||||
* **The current reward system is called [*Naive rewarding*](#naive-rewarding) - an intermediate step - where the operators of `nym-node` get rewarded from [Mixmining pool](https://validator.nymtech.net/api/v1/epoch/reward_params), which emits <span style={{display: 'inline-block'}}><EpochRewardBudget /></span> NYM per hour**
|
||||
* **Only nodes selected to [rewarded set](../tokenomics.mdx#active-set) of Mixnet receive rewards**
|
||||
* The [rewarded set](../tokenomics.mdx#active-set) of the Mixnet is currently **240 nodes in total and it's selected for each new epoch (60 min)**, from all the nodes registered (bonded) in the network
|
||||
* The [rewarded set](../tokenomics.mdx#active-set) of the Mixnet is currently **240 nodes in total and it's selected for each new epoch (60 min)**, from all the nodes registered (bonded) in the network
|
||||
* Each node gets the same proportion of work factor because of the *naive* distribution of work
|
||||
* In the [final model](#roadmap), nodes will get rewarded based on their layer position and the work they do (collected user tickets), where the work factor distribution per layer will be according to a [decision made by the operators](https://forum.nymtech.net/t/poll-what-should-be-the-split-of-mixmining-rewards-among-the-layers-of-the-nym-mixnet/407) as [listed below](#nym-network-rewarded-set-distribution)
|
||||
* If a node is selected to the rewarded set, it will be rewarded in the end of the epoch, based on this reward calculation formula:
|
||||
@@ -73,7 +73,7 @@ This is a quick summary, to understand the logic behind fundamentals like [rewar
|
||||
> **[total_epoch_reward_budget](https://validator.nymtech.net/api/v1/epoch/reward_params) = <span style={{display: 'inline-block'}}><EpochRewardBudget /></span>** <br/>
|
||||
> **<abbr title="In Naive rewarding the node work fraction is same for all nodes in the active set">node_work_fraction</abbr> = 1 / active_set_size** <br/>
|
||||
> **[active_set_size](https://validator.nymtech.net/api/v1/epoch/reward_params) = 240**
|
||||
>
|
||||
>
|
||||
> Therefore: <br/>
|
||||
> **node_epoch_rewards = <span style={{display: 'inline-block'}}><EpochRewardBudget /></span> \* 1 / 240 \* [node_stake_saturation](#stake-saturation) \* [node_performance](#node-performance-calculation)**
|
||||
</Callout>
|
||||
@@ -81,7 +81,7 @@ This is a quick summary, to understand the logic behind fundamentals like [rewar
|
||||
In reality there is a an additional value called **α**, giving a premium to nodes with a higher self bond. And additionally an operator gets more rewards based on [*Operators cost*](#rewards-distribution) and [*Profit margin*](#rewards-distribution) size. **Read chapter [Rewards calculation](#rewards-calculation) to be able to navigate in all the details relevant for operators and delegators.**
|
||||
|
||||
<Callout type="info" emoji="ℹ️">
|
||||
**In the current intermediate model we use one active set to reward all nodes and they are assigned same work factor of 1 / 240**, whether they work as Mixnode or Gateway of any kind, in both 2-hop and 5-hop mode (hence *naive rewarding*).
|
||||
**In the current intermediate model we use one active set to reward all nodes and they are assigned same work factor of 1 / 240**, whether they work as Mixnode or Gateway of any kind, in both 2-hop and 5-hop mode (hence *naive rewarding*).
|
||||
|
||||
**In reality it means that all nodes are rewarded within the [Mixnet (5-hop) reward set](#rewarded-set-selection) only.**
|
||||
|
||||
@@ -159,15 +159,14 @@ In reality there is a an additional value called **α**, giving a premium t
|
||||
|
||||
## Rewarded Set Selection
|
||||
|
||||
For a node to be rewarded, the node must be part of a [Rewarded set](https://validator.nymtech.net/api/v1/epoch/reward_params) (which currently = active set) in the first place. The Rewarded set is freshly selected at the start of each epoch (every 60 min), and it consists of 240 Nym nodes that are probabilistically chosen from all the available nodes. These 240 nodes are composed of 120 gateways (50 entry and 70 exit) and 120 mixnodes (40 for each of 3 mixnet layers).
|
||||
For a node to be rewarded, the node must be part of a [Rewarded set](https://validator.nymtech.net/api/v1/epoch/reward_params) (which currently = active set) in the first place. The Rewarded set is freshly selected at the start of each epoch (every 60 min), and it consists of 240 Nym nodes that are probabilistically chosen from all the available nodes. These 240 nodes include 120 gateways and 120 mixnodes (40 for each of 3 mixnet layers).
|
||||
|
||||
Nodes selected into the rewarded set are chosen probabilisticaly, and their selection chances increase the larger nodes weight is. Weight value is always between `0` and `1` and it's calculated by multiplying these parameters, each of them also having a value between `0` and `1` (some are floats, some are binary):
|
||||
Nodes selected into the rewarded set are chosen probabilisticaly are randomly selected, and their selection chances increase the larger nodes weight is. Weight value is always between `0` and `1` and it's calculated from these parameters, each of them having a value between `0` and `1` (some are floats, some are binary):
|
||||
|
||||
**1. [Performance](#node-performance-calculation):** This value consists of:
|
||||
1. [Performance](#node-performance-calculation): This value consists of:
|
||||
- [Config score](#config-score-calculation): highest (`1`) when the node is running the latest version of the software, has [T&C's accepted](../nodes/nym-node/setup.mdx#terms--conditions) and self described API endpoint available
|
||||
- [Routing ](#routing-score-calculation): highest (`1`) when the node is consistently online and correctly processes all the received traffic (100% of time)
|
||||
|
||||
**2. [Stake saturation](#stake-saturation):** combining bond and delegated stake (a float number between `0` and `1` representing percentage)
|
||||
2. [Stake saturation](#stake-saturation): combining bond and delegated stake (a float number representing percentage)
|
||||
|
||||
**Node weight is calculated with this formula:**
|
||||
|
||||
@@ -177,7 +176,7 @@ Nodes selected into the rewarded set are chosen probabilisticaly, and their sele
|
||||
|
||||
For the rewarded set selection weight, good [performance](#node-performance-calculation) is much more essential than [stake saturation](#stake-saturation), because it's lifted to 20th power in the selection algorhitm.
|
||||
|
||||
For a comparison we made an example with 5 nodes, where first number is node performance and second stake saturation (assuming all of them [`config_score`](#config-score-calculation) = `1` for simplification):
|
||||
For a comparison we made an example with 5 nodes, where first number is node performance and second stake saturation (assuming all of them [`config_score`](#config-score-calculation)) = 1 for simplification):
|
||||
|
||||
<br />
|
||||
<AccordionTemplate name="✏️ Calculation examples: performance ^ 20 * node_stake_saturation">
|
||||
@@ -325,7 +324,7 @@ Besides these values, the API also checks whether the node is bonded in Mixnet s
|
||||
|
||||
#### Versions Behind Calculation
|
||||
|
||||
From release `2024.14-crunch` (`nym-node v1.2.0`), the `config_score` parameter takes into account also nodes version (denoted as `versions_behind`). The "current version" is the one marked as `Latest` in the [repository](https://github.com/nymtech/nym/releases/). The parameter `versions_behind` indicates the number of versions between the `Latest` version and the version run by the node, and it is factored into the config score with this formula:
|
||||
From release `2024.14-crunch` (`nym-node v1.2.0`), the `config_score` parameter takes into account also nodes version (denoted as `versions_behind`). The "current version" is the one marked as `Latest` in our repository. The parameter `versions_behind` indicates the number of versions between the `Latest` version and the version run by the node, and it is factored into the config score with this formula:
|
||||
|
||||
<Callout type="info" emoji="📌">
|
||||
> **0.995 ^ ( ( X * versions_behind ) ^ 1.65 )**
|
||||
@@ -386,7 +385,7 @@ If a node is active in the rewarded set, it will receive rewards in the end of t
|
||||
> **[rewarded_set_size](https://validator.nymtech.net/api/v1/epoch/reward_params) = 240** <br/>
|
||||
> **[stake_saturation_level](https://validator.nymtech.net/api/v1/epoch/reward_params) = <span style={{display: 'inline-block'}}><StakeSaturation /></span>** <br/>
|
||||
> **α = <abbr title="α is a constant (0.3) working as a premium for nodes with higher self bond">0.3</abbr>**
|
||||
>
|
||||
>
|
||||
> Therefore: <br/>
|
||||
> **node_epoch_rewards = <span style={{display: 'inline-block'}}><EpochRewardBudget /></span> \* [node_performance](#node-performance-calculation) \* [node_stake_saturation](#stake-saturation) \* ( <abbr title="In Naive rewarding the node work fraction is same for all nodes in the active set">( 1 / 240 )</abbr> + <abbr title="α is a constant (0.3) working as a premium for nodes with higher self bond">0.3</abbr> \* ( ( <abbr title="The actual number of tokens in the bond. Node bond size is capped at stake saturation level.">node_bond_size</abbr> / <span style={{display: 'inline-block'}}><StakeSaturation /></span> ) / 240 ) ) \* 1 / ( 1 + <abbr title="α is a constant (0.3) working as a premium for nodes with higher self bond">0.3</abbr> )**
|
||||
</Callout>
|
||||
@@ -427,7 +426,7 @@ node2_epoch_rewards = 17.2275 NYM
|
||||
node3_epoch_rewards = 20.8333 NYM
|
||||
```
|
||||
|
||||
Difference between the smallest possible bond 100 NYM and a maximum bond 1mm NYM (equal full stake saturation point) is about 23% of increase in epoch rewards.
|
||||
Difference between the smallest possible bond 100 NYM and a maximum bond 1mm NYM (equal full stake saturation point) is about 23% of increase in epoch rewards.
|
||||
</ AccordionTemplate>
|
||||
|
||||
**Try to calculate rewards yourself**
|
||||
|
||||
@@ -29,10 +29,6 @@ python api_targets.py time_now > ../../docs/components/outputs/api-scraping-outp
|
||||
|
||||
python api_targets.py calculate --staking_target --separator _ > ../../docs/components/outputs/api-scraping-outputs/nyx-outputs/staking-target.md &&
|
||||
|
||||
curl -L https://validator.nymtech.net/api/v1/circulating-supply | jq > ../../docs/components/outputs/api-scraping-outputs/circulating-supply.json &&
|
||||
|
||||
curl -L https://validator.nymtech.net/api/v1/epoch/reward_params | jq > ../../docs/components/outputs/api-scraping-outputs/reward-params.json &&
|
||||
|
||||
cd ../../../scripts &&
|
||||
echo '```python' > ../documentation/docs/components/outputs/command-outputs/node-api-check-query-help.md &&
|
||||
python node_api_check.py query_stats --help >> ../documentation/docs/components/outputs/command-outputs/node-api-check-query-help.md &&
|
||||
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n INSERT OR IGNORE INTO routes (layer1, layer2, layer3, gw, success) VALUES (?, ?, ?, ?, ?);\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 5
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "66109c1d856e1ca2b5126e4bf4c58c7a27b8c303bfa079cf74909354202dcc49"
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n SELECT\n d.node_id as \"node_id: NodeId\",\n CASE WHEN count(*) > 3 THEN AVG(reliability) ELSE 100 END as \"value: f32\"\n FROM\n gateway_details d\n JOIN\n gateway_status s on d.id = s.gateway_details_id\n WHERE\n timestamp >= ? AND\n timestamp <= ?\n GROUP BY 1\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "node_id: NodeId",
|
||||
"ordinal": 0,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "value: f32",
|
||||
"ordinal": 1,
|
||||
"type_info": "Int"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 2
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "676299beb2004ab89f7b38cf21ffb84ab5e7d7435297573523e2532560c2e302"
|
||||
}
|
||||
-44
@@ -1,44 +0,0 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n SELECT\n layer1 as \"layer1\",\n layer2 as \"layer2\",\n layer3 as \"layer3\",\n gw as \"gw\",\n success\n FROM routes\n WHERE timestamp >= ? AND timestamp <= ?\n ORDER BY timestamp ASC\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "layer1",
|
||||
"ordinal": 0,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "layer2",
|
||||
"ordinal": 1,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "layer3",
|
||||
"ordinal": 2,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "gw",
|
||||
"ordinal": 3,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "success",
|
||||
"ordinal": 4,
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 2
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "6b2479c02cf1ef5ae674ce0ab4d027595b91739f3579e1f289b0c722ea91bbcc"
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n SELECT\n d.mix_id as \"mix_id: NodeId\",\n AVG(s.reliability) as \"value: f32\"\n FROM\n mixnode_details d\n JOIN\n mixnode_status s on d.id = s.mixnode_details_id\n WHERE\n timestamp >= ? AND\n timestamp <= ?\n GROUP BY 1\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "mix_id: NodeId",
|
||||
"ordinal": 0,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "value: f32",
|
||||
"ordinal": 1,
|
||||
"type_info": "Int64"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 2
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "c19e1b3768bf2929407599e6e8783ead09f4d7319b7997fa2a9bb628f9404166"
|
||||
}
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
[package]
|
||||
name = "nym-api"
|
||||
license = "GPL-3.0"
|
||||
version = "1.1.60"
|
||||
version = "1.1.59"
|
||||
authors.workspace = true
|
||||
edition = "2021"
|
||||
rust-version.workspace = true
|
||||
|
||||
@@ -2,5 +2,4 @@
|
||||
|
||||
set -e
|
||||
|
||||
# Init can fail if the mounted volume already has a config
|
||||
/usr/src/nym/target/release/nym-api init --mnemonic "$MNEMONIC" || true && /usr/src/nym/target/release/nym-api run --mnemonic "$MNEMONIC"
|
||||
/usr/src/nym/target/release/nym-api init && /usr/src/nym/target/release/nym-api run
|
||||
@@ -1,17 +0,0 @@
|
||||
-- Add routes table for storing route metrics data
|
||||
CREATE TABLE IF NOT EXISTS routes (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
layer1 INTEGER NOT NULL, -- NodeId of layer 1 mixnode
|
||||
layer2 INTEGER NOT NULL, -- NodeId of layer 2 mixnode
|
||||
layer3 INTEGER NOT NULL, -- NodeId of layer 3 mixnode
|
||||
gw INTEGER NOT NULL, -- NodeId of gateway
|
||||
success BOOLEAN NOT NULL, -- Whether the packet was delivered successfully
|
||||
timestamp INTEGER NOT NULL DEFAULT (unixepoch()) -- When the measurement was taken
|
||||
);
|
||||
|
||||
-- Add indexes for efficient querying
|
||||
CREATE INDEX IF NOT EXISTS idx_routes_timestamp ON routes(timestamp);
|
||||
CREATE INDEX IF NOT EXISTS idx_routes_layer1 ON routes(layer1);
|
||||
CREATE INDEX IF NOT EXISTS idx_routes_layer2 ON routes(layer2);
|
||||
CREATE INDEX IF NOT EXISTS idx_routes_layer3 ON routes(layer3);
|
||||
CREATE INDEX IF NOT EXISTS idx_routes_gw ON routes(gw);
|
||||
@@ -20,7 +20,7 @@ use nym_api_requests::models::{
|
||||
};
|
||||
use nym_http_api_common::{FormattedResponse, OutputParams};
|
||||
use nym_mixnet_contract_common::NodeId;
|
||||
use nym_types::monitoring::{MonitorMessage, MonitorResults};
|
||||
use nym_types::monitoring::MonitorMessage;
|
||||
use tracing::error;
|
||||
|
||||
pub(super) fn mandatory_routes() -> Router<AppState> {
|
||||
@@ -33,10 +33,6 @@ pub(super) fn mandatory_routes() -> Router<AppState> {
|
||||
"/submit-node-monitoring-results",
|
||||
post(submit_node_monitoring_results),
|
||||
)
|
||||
.route(
|
||||
"/submit-route-monitoring-results",
|
||||
post(submit_route_monitoring_results),
|
||||
)
|
||||
.nest(
|
||||
"/mixnode/:mix_id",
|
||||
Router::new()
|
||||
@@ -62,53 +58,6 @@ pub(super) fn mandatory_routes() -> Router<AppState> {
|
||||
)
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
tag = "status",
|
||||
post,
|
||||
path = "/v1/status/submit-route-monitoring-results",
|
||||
responses(
|
||||
(status = 200),
|
||||
(status = 400, body = String, description = "TBD"),
|
||||
(status = 403, body = String, description = "TBD"),
|
||||
(status = 500, body = String, description = "TBD"),
|
||||
),
|
||||
)]
|
||||
pub(crate) async fn submit_route_monitoring_results(
|
||||
State(state): State<AppState>,
|
||||
Json(message): Json<MonitorMessage>,
|
||||
) -> AxumResult<()> {
|
||||
if !message.is_in_allowed() {
|
||||
return Err(AxumErrorResponse::forbidden(
|
||||
"Monitor not registered to submit results",
|
||||
));
|
||||
}
|
||||
|
||||
if !message.timely() {
|
||||
return Err(AxumErrorResponse::bad_request("Message is too old"));
|
||||
}
|
||||
|
||||
if !message.verify() {
|
||||
return Err(AxumErrorResponse::bad_request("invalid signature"));
|
||||
}
|
||||
|
||||
match message.results() {
|
||||
MonitorResults::Route(results) => {
|
||||
match state.storage.submit_route_monitoring_results(results).await {
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) => {
|
||||
error!("failed to submit node monitoring results: {err}");
|
||||
Err(AxumErrorResponse::internal_msg(
|
||||
"failed to submit node monitoring results",
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
MonitorResults::Node(_results) => Err(AxumErrorResponse::bad_request(
|
||||
"Node monitoring results not supported for this endpoint",
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
tag = "status",
|
||||
post,
|
||||
@@ -138,21 +87,18 @@ pub(crate) async fn submit_gateway_monitoring_results(
|
||||
return Err(AxumErrorResponse::bad_request("invalid signature"));
|
||||
}
|
||||
|
||||
match message.results() {
|
||||
MonitorResults::Node(results) => {
|
||||
match state.storage.submit_gateway_statuses_v2(results).await {
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) => {
|
||||
error!("failed to submit node monitoring results: {err}");
|
||||
Err(AxumErrorResponse::internal_msg(
|
||||
"failed to submit node monitoring results",
|
||||
))
|
||||
}
|
||||
}
|
||||
match state
|
||||
.storage
|
||||
.submit_gateway_statuses_v2(message.results())
|
||||
.await
|
||||
{
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) => {
|
||||
error!("failed to submit gateway monitoring results: {err}");
|
||||
Err(AxumErrorResponse::internal_msg(
|
||||
"failed to submit gateway monitoring results",
|
||||
))
|
||||
}
|
||||
MonitorResults::Route(_results) => Err(AxumErrorResponse::bad_request(
|
||||
"Gateway monitoring results not supported for this endpoint",
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,21 +131,18 @@ pub(crate) async fn submit_node_monitoring_results(
|
||||
return Err(AxumErrorResponse::bad_request("invalid signature"));
|
||||
}
|
||||
|
||||
match message.results() {
|
||||
MonitorResults::Node(results) => {
|
||||
match state.storage.submit_mixnode_statuses_v2(results).await {
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) => {
|
||||
error!("failed to submit node monitoring results: {err}");
|
||||
Err(AxumErrorResponse::internal_msg(
|
||||
"failed to submit node monitoring results",
|
||||
))
|
||||
}
|
||||
}
|
||||
match state
|
||||
.storage
|
||||
.submit_mixnode_statuses_v2(message.results())
|
||||
.await
|
||||
{
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) => {
|
||||
error!("failed to submit node monitoring results: {err}");
|
||||
Err(AxumErrorResponse::internal_msg(
|
||||
"failed to submit node monitoring results",
|
||||
))
|
||||
}
|
||||
MonitorResults::Route(_results) => Err(AxumErrorResponse::bad_request(
|
||||
"Node monitoring results not supported for this endpoint",
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,9 +10,8 @@ use crate::support::storage::models::{
|
||||
};
|
||||
use crate::support::storage::DbIdCache;
|
||||
use nym_mixnet_contract_common::{EpochId, IdentityKey, NodeId};
|
||||
use nym_types::monitoring::{NodeResult, RouteResult};
|
||||
use nym_types::monitoring::NodeResult;
|
||||
use sqlx::FromRow;
|
||||
use std::collections::HashMap;
|
||||
use time::{Date, OffsetDateTime};
|
||||
use tracing::info;
|
||||
|
||||
@@ -52,25 +51,6 @@ impl AvgGatewayReliability {
|
||||
}
|
||||
}
|
||||
|
||||
// Helper struct for in-memory state during calculation for an interval
|
||||
#[derive(Debug, Default, Clone)]
|
||||
struct NodeCorrectionIntervalState {
|
||||
pos_samples: u32,
|
||||
neg_samples: u32,
|
||||
fail_seq: u32,
|
||||
}
|
||||
|
||||
// Output struct for the calculated corrected reliability for an interval
|
||||
#[derive(Debug, serde::Serialize)] // Add utoipa::ToSchema if this struct is directly exposed via API
|
||||
pub struct CorrectedNodeIntervalReliability {
|
||||
pub node_id: NodeId, // nym_mixnet_contract_common::NodeId (typically u32)
|
||||
pub identity: Option<String>, // Base58 public key
|
||||
pub reliability: f64,
|
||||
pub pos_samples_in_interval: u32,
|
||||
pub neg_samples_in_interval: u32,
|
||||
pub final_fail_seq_in_interval: u32,
|
||||
}
|
||||
|
||||
// all SQL goes here
|
||||
impl StorageManager {
|
||||
pub(super) async fn get_all_avg_mix_reliability_in_last_24hr(
|
||||
@@ -96,29 +76,27 @@ impl StorageManager {
|
||||
start_ts_secs: i64,
|
||||
end_ts_secs: i64,
|
||||
) -> Result<Vec<AvgMixnodeReliability>, sqlx::Error> {
|
||||
let corrected_reliabilities = self
|
||||
.calculate_corrected_node_reliabilities_for_interval(start_ts_secs, end_ts_secs)
|
||||
.await
|
||||
.map_err(|_e| sqlx::Error::PoolClosed)?; // Example: map anyhow::Error to sqlx::Error; adjust as needed
|
||||
|
||||
let mut avg_mix_reliabilities = Vec::new();
|
||||
|
||||
for corrected_node_info in corrected_reliabilities {
|
||||
// Check if this node_id is a mixnode by attempting to fetch its identity key as a mixnode.
|
||||
// This relies on get_mixnode_identity_key returning Some for mixnodes and None (or error) for non-mixnodes.
|
||||
if self
|
||||
.get_mixnode_identity_key(corrected_node_info.node_id)
|
||||
.await?
|
||||
.is_some()
|
||||
{
|
||||
avg_mix_reliabilities.push(AvgMixnodeReliability {
|
||||
mix_id: corrected_node_info.node_id,
|
||||
value: Some(corrected_node_info.reliability as f32),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Ok(avg_mix_reliabilities)
|
||||
let result = sqlx::query_as!(
|
||||
AvgMixnodeReliability,
|
||||
r#"
|
||||
SELECT
|
||||
d.mix_id as "mix_id: NodeId",
|
||||
AVG(s.reliability) as "value: f32"
|
||||
FROM
|
||||
mixnode_details d
|
||||
JOIN
|
||||
mixnode_status s on d.id = s.mixnode_details_id
|
||||
WHERE
|
||||
timestamp >= ? AND
|
||||
timestamp <= ?
|
||||
GROUP BY 1
|
||||
"#,
|
||||
start_ts_secs,
|
||||
end_ts_secs
|
||||
)
|
||||
.fetch_all(&self.connection_pool)
|
||||
.await?;
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub(super) async fn get_all_avg_gateway_reliability_in_interval(
|
||||
@@ -126,35 +104,27 @@ impl StorageManager {
|
||||
start_ts_secs: i64,
|
||||
end_ts_secs: i64,
|
||||
) -> Result<Vec<AvgGatewayReliability>, sqlx::Error> {
|
||||
let corrected_reliabilities = self
|
||||
.calculate_corrected_node_reliabilities_for_interval(start_ts_secs, end_ts_secs)
|
||||
.await
|
||||
.map_err(|_e| sqlx::Error::PoolClosed)?; // Example: map anyhow::Error to sqlx::Error; adjust as needed
|
||||
|
||||
let mut avg_gateway_reliabilities = Vec::new();
|
||||
|
||||
for corrected_node_info in corrected_reliabilities {
|
||||
// Check if this node_id is a gateway.
|
||||
if self
|
||||
.get_gateway_identity_key(corrected_node_info.node_id)
|
||||
.await?
|
||||
.is_some()
|
||||
{
|
||||
let total_samples = corrected_node_info.pos_samples_in_interval
|
||||
+ corrected_node_info.neg_samples_in_interval;
|
||||
let reliability_value = if total_samples <= 3 {
|
||||
100.0 // Default to 100% if 3 or fewer samples
|
||||
} else {
|
||||
corrected_node_info.reliability as f32
|
||||
};
|
||||
|
||||
avg_gateway_reliabilities.push(AvgGatewayReliability {
|
||||
node_id: corrected_node_info.node_id, // AvgGatewayReliability uses node_id
|
||||
value: Some(reliability_value),
|
||||
});
|
||||
}
|
||||
}
|
||||
Ok(avg_gateway_reliabilities)
|
||||
let result = sqlx::query_as!(
|
||||
AvgGatewayReliability,
|
||||
r#"
|
||||
SELECT
|
||||
d.node_id as "node_id: NodeId",
|
||||
CASE WHEN count(*) > 3 THEN AVG(reliability) ELSE 100 END as "value: f32"
|
||||
FROM
|
||||
gateway_details d
|
||||
JOIN
|
||||
gateway_status s on d.id = s.gateway_details_id
|
||||
WHERE
|
||||
timestamp >= ? AND
|
||||
timestamp <= ?
|
||||
GROUP BY 1
|
||||
"#,
|
||||
start_ts_secs,
|
||||
end_ts_secs
|
||||
)
|
||||
.fetch_all(&self.connection_pool)
|
||||
.await?;
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// Tries to obtain row id of given mixnode given its identity.
|
||||
@@ -694,33 +664,6 @@ impl StorageManager {
|
||||
tx.commit().await
|
||||
}
|
||||
|
||||
pub(super) async fn submit_route_monitoring_results(
|
||||
&self,
|
||||
route_results: &[RouteResult],
|
||||
) -> Result<(), sqlx::Error> {
|
||||
info!("Inserting {} route monitoring results", route_results.len());
|
||||
// insert it all in a transaction to make sure all nodes are updated at the same time
|
||||
// (plus it's a nice guard against new nodes)
|
||||
let mut tx = self.connection_pool.begin().await?;
|
||||
|
||||
for route_result in route_results {
|
||||
sqlx::query!(
|
||||
r#"
|
||||
INSERT OR IGNORE INTO routes (layer1, layer2, layer3, gw, success) VALUES (?, ?, ?, ?, ?);
|
||||
"#,
|
||||
route_result.layer1,
|
||||
route_result.layer2,
|
||||
route_result.layer3,
|
||||
route_result.gw,
|
||||
route_result.success,
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
}
|
||||
|
||||
tx.commit().await
|
||||
}
|
||||
|
||||
pub(super) async fn submit_gateway_statuses_v2(
|
||||
&self,
|
||||
gateway_results: &[NodeResult],
|
||||
@@ -1386,146 +1329,6 @@ impl StorageManager {
|
||||
.fetch_all(&self.connection_pool)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Fetches raw route results from the database for a given time interval.
|
||||
/// Assumes the 'routes' table has layer1, layer2, layer3, gw, success, and a timestamp.
|
||||
async fn get_raw_routes_in_interval(
|
||||
&self,
|
||||
start_ts_secs: i64,
|
||||
end_ts_secs: i64,
|
||||
) -> Result<Vec<(NodeId, NodeId, NodeId, NodeId, bool)>, sqlx::Error> {
|
||||
// Temporary struct to match the expected columns from the 'routes' table
|
||||
// NodeId here is assumed to be compatible with how layer1, etc. are stored (e.g. u32/i32/i64)
|
||||
struct RawRouteData {
|
||||
layer1: i64,
|
||||
layer2: i64,
|
||||
layer3: i64,
|
||||
gw: i64,
|
||||
success: Option<bool>,
|
||||
// timestamp: i64, // Not explicitly selected into struct, but used in WHERE and ORDER BY
|
||||
}
|
||||
|
||||
// Ensure your 'routes' table has:
|
||||
// layer1 (NodeId type), layer2 (NodeId type), layer3 (NodeId type),
|
||||
// gw (NodeId type), success (bool/INTEGER), timestamp (BIGINT/INTEGER)
|
||||
// The "NodeId:" type hint for sqlx::query_as! helps map to nym_mixnet_contract_common::NodeId
|
||||
let db_routes = sqlx::query_as!(
|
||||
RawRouteData,
|
||||
r#"
|
||||
SELECT
|
||||
layer1 as "layer1",
|
||||
layer2 as "layer2",
|
||||
layer3 as "layer3",
|
||||
gw as "gw",
|
||||
success
|
||||
FROM routes
|
||||
WHERE timestamp >= ? AND timestamp <= ?
|
||||
ORDER BY timestamp ASC
|
||||
"#,
|
||||
start_ts_secs,
|
||||
end_ts_secs
|
||||
)
|
||||
.fetch_all(&self.connection_pool)
|
||||
.await?;
|
||||
|
||||
Ok(db_routes
|
||||
.into_iter()
|
||||
.map(|r| {
|
||||
(
|
||||
r.layer1 as NodeId,
|
||||
r.layer2 as NodeId,
|
||||
r.layer3 as NodeId,
|
||||
r.gw as NodeId,
|
||||
r.success.unwrap_or_default(),
|
||||
)
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
pub async fn calculate_corrected_node_reliabilities_for_interval(
|
||||
&self,
|
||||
start_ts_secs: i64,
|
||||
end_ts_secs: i64,
|
||||
) -> Result<Vec<CorrectedNodeIntervalReliability>, anyhow::Error> {
|
||||
let raw_routes = self
|
||||
.get_raw_routes_in_interval(start_ts_secs, end_ts_secs)
|
||||
.await?;
|
||||
|
||||
let mut node_states: HashMap<NodeId, NodeCorrectionIntervalState> = HashMap::new();
|
||||
|
||||
for (l1, l2, l3, gw, success) in raw_routes {
|
||||
let path_node_ids = [l1, l2, l3, gw];
|
||||
|
||||
if success {
|
||||
for &node_id in &path_node_ids {
|
||||
let state = node_states.entry(node_id).or_default();
|
||||
state.pos_samples += 1;
|
||||
state.fail_seq = 0;
|
||||
}
|
||||
} else {
|
||||
// Path test failed
|
||||
let mut current_path_node_ids_for_blame: Vec<NodeId> = Vec::new();
|
||||
for &node_id in &path_node_ids {
|
||||
let state = node_states.entry(node_id).or_default();
|
||||
state.fail_seq += 1;
|
||||
current_path_node_ids_for_blame.push(node_id);
|
||||
}
|
||||
|
||||
let mut guilty_nodes_in_path: Vec<NodeId> = Vec::new();
|
||||
for &node_id in ¤t_path_node_ids_for_blame {
|
||||
if let Some(state_after_update) = node_states.get(&node_id) {
|
||||
if state_after_update.fail_seq > 2 {
|
||||
guilty_nodes_in_path.push(node_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !guilty_nodes_in_path.is_empty() {
|
||||
for &guilty_node_id in &guilty_nodes_in_path {
|
||||
if let Some(state) = node_states.get_mut(&guilty_node_id) {
|
||||
state.neg_samples += 1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// No single guilty party, distribute blame
|
||||
for &node_id_in_path in ¤t_path_node_ids_for_blame {
|
||||
if let Some(state) = node_states.get_mut(&node_id_in_path) {
|
||||
state.neg_samples += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut final_reliabilities = Vec::new();
|
||||
for (node_id, state) in node_states {
|
||||
let total_samples = state.pos_samples + state.neg_samples;
|
||||
let reliability = if total_samples == 0 {
|
||||
0.0 // Default for no samples in this interval. Consider Option<f64> or filtering.
|
||||
} else {
|
||||
state.pos_samples as f64 / total_samples as f64
|
||||
};
|
||||
|
||||
// Attempt to fetch identity, first as mixnode, then as gateway if not found.
|
||||
// This assumes get_mixnode_identity_key and get_gateway_identity_key return Result<Option<String>, sqlx::Error>
|
||||
let mut identity: Option<String> =
|
||||
self.get_mixnode_identity_key(node_id).await.unwrap_or(None);
|
||||
if identity.is_none() {
|
||||
identity = self.get_gateway_identity_key(node_id).await.unwrap_or(None);
|
||||
}
|
||||
|
||||
final_reliabilities.push(CorrectedNodeIntervalReliability {
|
||||
node_id,
|
||||
identity,
|
||||
reliability,
|
||||
pos_samples_in_interval: state.pos_samples,
|
||||
neg_samples_in_interval: state.neg_samples,
|
||||
final_fail_seq_in_interval: state.fail_seq,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(final_reliabilities)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) mod v3_migration {
|
||||
|
||||
@@ -17,7 +17,7 @@ use crate::support::storage::models::{
|
||||
};
|
||||
use dashmap::DashMap;
|
||||
use nym_mixnet_contract_common::NodeId;
|
||||
use nym_types::monitoring::{NodeResult, RouteResult};
|
||||
use nym_types::monitoring::NodeResult;
|
||||
use sqlx::sqlite::{SqliteAutoVacuum, SqliteSynchronous};
|
||||
use sqlx::ConnectOptions;
|
||||
use std::path::Path;
|
||||
@@ -835,16 +835,6 @@ impl NymApiStorage {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn submit_route_monitoring_results(
|
||||
&self,
|
||||
route_results: &[RouteResult],
|
||||
) -> Result<(), NymApiStorageError> {
|
||||
self.manager
|
||||
.submit_route_monitoring_results(route_results)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Obtains number of network monitor test runs that have occurred within the specified interval.
|
||||
///
|
||||
/// # Arguments
|
||||
|
||||
@@ -108,13 +108,13 @@ impl AddressInfoCache {
|
||||
address: account_id.to_string(),
|
||||
balance: balance.into(),
|
||||
delegations: delegation_data
|
||||
.delegations()
|
||||
.into_iter()
|
||||
.map(|d| NyxAccountDelegationDetails {
|
||||
delegated: d.amount,
|
||||
height: d.height,
|
||||
node_id: d.node_id,
|
||||
proxy: d.proxy,
|
||||
delegated: d.details().amount.clone(),
|
||||
height: d.details().height,
|
||||
node_id: d.details().node_id,
|
||||
proxy: d.details().proxy.clone(),
|
||||
node_bonded: d.is_node_bonded(),
|
||||
})
|
||||
.collect(),
|
||||
accumulated_rewards,
|
||||
|
||||
@@ -8,10 +8,9 @@ use crate::{
|
||||
};
|
||||
use cosmwasm_std::{Coin, Decimal};
|
||||
use nym_mixnet_contract_common::NodeRewarding;
|
||||
use nym_topology::NodeId;
|
||||
use nym_validator_client::nyxd::AccountId;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use tracing::warn;
|
||||
use tracing::error;
|
||||
|
||||
pub(crate) struct AddressDataCollector {
|
||||
nyxd_client: crate::nyxd::Client,
|
||||
@@ -56,18 +55,18 @@ impl AddressDataCollector {
|
||||
Ok(balance)
|
||||
}
|
||||
|
||||
pub(crate) async fn get_delegations(&mut self) -> AxumResult<AddressDelegationInfo> {
|
||||
pub(crate) async fn get_delegations(&mut self) -> AxumResult<Vec<AddressDelegationInfo>> {
|
||||
let og_delegations = self
|
||||
.nyxd_client
|
||||
.get_all_delegator_delegations(&self.account_id)
|
||||
.await?;
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|delegation| (delegation.node_id, delegation))
|
||||
.collect::<HashMap<_, _>>();
|
||||
|
||||
let delegated_to_nodes = og_delegations
|
||||
.iter()
|
||||
.map(|d| d.node_id)
|
||||
.collect::<HashSet<_>>();
|
||||
let mut node_delegation_info = Vec::new();
|
||||
|
||||
let nym_nodes = self
|
||||
let delegated_to_nodes_bonded = self
|
||||
.nym_contract_cache
|
||||
.all_cached_nym_nodes()
|
||||
.await
|
||||
@@ -85,61 +84,75 @@ impl AddressDataCollector {
|
||||
// add to totals
|
||||
self.total_value += pending_operator_reward;
|
||||
}
|
||||
if delegated_to_nodes.contains(&node_details.node_id()) {
|
||||
Some((
|
||||
node_details.node_id(),
|
||||
// avoid cloning node data which we don't need
|
||||
(
|
||||
node_details.rewarding_details.clone(),
|
||||
node_details.is_unbonding(),
|
||||
),
|
||||
))
|
||||
if let Some(delegation) = og_delegations.get(&node_details.node_id()) {
|
||||
node_delegation_info.push(AddressDelegationInfo {
|
||||
details: delegation.clone(),
|
||||
node_reward_info: NodeBondStatus::Bonded {
|
||||
rewarding_info: node_details.rewarding_details.to_owned(),
|
||||
unbonding: node_details.is_unbonding(),
|
||||
},
|
||||
});
|
||||
|
||||
Some(node_details.node_id())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<HashMap<_, _>>();
|
||||
.collect::<HashSet<_>>();
|
||||
|
||||
Ok(AddressDelegationInfo {
|
||||
delegations: og_delegations,
|
||||
delegated_to_nodes: nym_nodes,
|
||||
})
|
||||
for (node_id, delegation) in og_delegations {
|
||||
if !delegated_to_nodes_bonded.contains(&node_id) {
|
||||
node_delegation_info.push(AddressDelegationInfo {
|
||||
details: delegation.clone(),
|
||||
node_reward_info: NodeBondStatus::UnBonded,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Ok(node_delegation_info)
|
||||
}
|
||||
|
||||
pub(crate) async fn calculate_rewards(
|
||||
&mut self,
|
||||
delegation_data: &AddressDelegationInfo,
|
||||
delegation_data: &Vec<AddressDelegationInfo>,
|
||||
) -> AxumResult<Vec<NyxAccountDelegationRewardDetails>> {
|
||||
let mut accumulated_rewards = Vec::new();
|
||||
for delegation in delegation_data.delegations.iter() {
|
||||
let node_id = &delegation.node_id;
|
||||
for delegation in delegation_data {
|
||||
let node_id = delegation.details.node_id;
|
||||
|
||||
if let Some((rewarding_details, is_unbonding)) =
|
||||
delegation_data.delegated_to_nodes.get(node_id)
|
||||
{
|
||||
match rewarding_details.determine_delegation_reward(delegation) {
|
||||
Ok(delegation_reward) => {
|
||||
let reward = NyxAccountDelegationRewardDetails {
|
||||
node_id: delegation.node_id,
|
||||
rewards: decimal_to_coin(delegation_reward, &self.base_denom),
|
||||
amount_staked: delegation.amount.clone(),
|
||||
node_still_fully_bonded: !is_unbonding,
|
||||
};
|
||||
// 4. sum the rewards and delegations
|
||||
self.total_delegations += delegation.amount.amount.u128();
|
||||
self.total_value += delegation.amount.amount.u128();
|
||||
self.total_value += reward.rewards.amount.u128();
|
||||
self.claimable_rewards += reward.rewards.amount.u128();
|
||||
match &delegation.node_reward_info {
|
||||
NodeBondStatus::Bonded {
|
||||
rewarding_info,
|
||||
unbonding,
|
||||
} => {
|
||||
match rewarding_info.determine_delegation_reward(&delegation.details) {
|
||||
Ok(delegation_reward) => {
|
||||
let reward = NyxAccountDelegationRewardDetails {
|
||||
node_id,
|
||||
rewards: decimal_to_coin(delegation_reward, &self.base_denom),
|
||||
amount_staked: delegation.details.amount.clone(),
|
||||
node_still_fully_bonded: !unbonding,
|
||||
};
|
||||
// 4. sum the rewards and delegations
|
||||
self.total_delegations += delegation.details.amount.amount.u128();
|
||||
self.total_value += delegation.details.amount.amount.u128();
|
||||
self.total_value += reward.rewards.amount.u128();
|
||||
self.claimable_rewards += reward.rewards.amount.u128();
|
||||
|
||||
accumulated_rewards.push(reward);
|
||||
}
|
||||
Err(err) => {
|
||||
warn!(
|
||||
"Couldn't determine delegations for {} on node {}: {}",
|
||||
&self.account_id, node_id, err
|
||||
)
|
||||
accumulated_rewards.push(reward);
|
||||
}
|
||||
Err(err) => {
|
||||
error!(
|
||||
"Couldn't determine delegations for {} on node {}: {}",
|
||||
&self.account_id, node_id, err
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
NodeBondStatus::UnBonded => {
|
||||
// directory cache doesn't store node details required to
|
||||
// calculate rewarding for unbonded nodes
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,17 +184,27 @@ impl AddressDataCollector {
|
||||
}
|
||||
|
||||
pub(crate) struct AddressDelegationInfo {
|
||||
delegations: Vec<nym_mixnet_contract_common::Delegation>,
|
||||
delegated_to_nodes: HashMap<NodeId, RewardAndBondInfo>,
|
||||
details: nym_mixnet_contract_common::Delegation,
|
||||
node_reward_info: NodeBondStatus,
|
||||
}
|
||||
|
||||
impl AddressDelegationInfo {
|
||||
pub(crate) fn delegations(self) -> Vec<nym_mixnet_contract_common::Delegation> {
|
||||
self.delegations
|
||||
pub(crate) fn details(&self) -> &nym_mixnet_contract_common::Delegation {
|
||||
&self.details
|
||||
}
|
||||
|
||||
pub(crate) fn is_node_bonded(&self) -> bool {
|
||||
matches!(self.node_reward_info, NodeBondStatus::Bonded { .. })
|
||||
}
|
||||
}
|
||||
|
||||
type RewardAndBondInfo = (NodeRewarding, bool);
|
||||
pub(crate) enum NodeBondStatus {
|
||||
Bonded {
|
||||
rewarding_info: NodeRewarding,
|
||||
unbonding: bool,
|
||||
},
|
||||
UnBonded,
|
||||
}
|
||||
|
||||
fn decimal_to_coin(decimal: Decimal, denom: impl Into<String>) -> Coin {
|
||||
Coin::new(decimal.to_uint_floor(), denom)
|
||||
@@ -189,6 +212,7 @@ fn decimal_to_coin(decimal: Decimal, denom: impl Into<String>) -> Coin {
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
|
||||
@@ -21,6 +21,7 @@ pub struct NyxAccountDelegationDetails {
|
||||
pub height: u64,
|
||||
#[schema(value_type = Option<String>)]
|
||||
pub proxy: Option<Addr>,
|
||||
pub node_bonded: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, utoipa::ToSchema, utoipa::ToResponse)]
|
||||
@@ -41,6 +42,9 @@ pub struct NyxAccountDetails {
|
||||
#[schema(value_type = CoinSchema)]
|
||||
pub total_value: Coin,
|
||||
pub delegations: Vec<NyxAccountDelegationDetails>,
|
||||
/// Shows rewards from delegations to **currently** bonded nodes.
|
||||
/// Rewards from nodes that user delegated to, but were later unbonded,
|
||||
/// are claimable, but not shown here.
|
||||
pub accumulated_rewards: Vec<NyxAccountDelegationRewardDetails>,
|
||||
#[schema(value_type = String)]
|
||||
pub total_delegations: Coin,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-network-monitor"
|
||||
version = "1.1.3"
|
||||
version = "1.0.2"
|
||||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
homepage.workspace = true
|
||||
|
||||
@@ -4,14 +4,6 @@ Monitors the Nym network by sending itself packages across the mixnet.
|
||||
|
||||
Network monitor is running two tokio tasks, one manages mixnet clients and another manages monitoring itself. Monitor is designed to be driven externally, via an HTTP api. This means that it does not do any monitoring unless driven by something like [`locust`](https://locust.io/). This allows us to tailor the load externally, potentially distributing it across multiple monitors.
|
||||
|
||||
## Features
|
||||
|
||||
- **Continuous Monitoring**: Periodically sends test packets through the network
|
||||
- **Node Performance**: Tracks individual node reliability metrics
|
||||
- **Route Performance**: Records route-level success rates through specific node combinations
|
||||
- **Multi-API Submission**: Capable of submitting metrics to multiple API endpoints (fanout)
|
||||
- **Force Routing**: Can force packets through all mixnet nodes for comprehensive testing
|
||||
|
||||
### Client manager
|
||||
|
||||
On start network monitor will spawn `C` clients, with 10 being the default. Random client is dropped every `T`, defaults to 60 seconds, and a new one is created. Clients chose a random gateway to connect to the mixnet. Meaning that on average all gateways will be tested in `NUMBER_OF_GATEWAYS/N*T`, assuming at least one request per client per T.
|
||||
@@ -48,87 +40,8 @@ Options:
|
||||
-m, --mixnet-timeout <MIXNET_TIMEOUT> [default: 10]
|
||||
--generate-key-pair
|
||||
--private-key <PRIVATE_KEY>
|
||||
--database-url <DATABASE_URL> SQLite database URL
|
||||
--nym-apis <NYM_APIS> Comma-separated list of Nym API URLs
|
||||
-h, --help Print help
|
||||
-V, --version Print version
|
||||
```
|
||||
|
||||
## Metrics Collection & Reporting
|
||||
|
||||
### Node Metrics
|
||||
|
||||
The Network Monitor tracks performance metrics for individual nodes:
|
||||
|
||||
- **Reliability**: Percentage of successful packet handling
|
||||
- **Failure Sequences**: Tracking consecutive failures
|
||||
- **Volume**: Number of packets handled
|
||||
|
||||
### Route Metrics
|
||||
|
||||
Since version 1.1.0, the Network Monitor also tracks route-level metrics:
|
||||
|
||||
- **Route Success Rates**: Tracking which specific combinations of nodes have successful packet delivery
|
||||
- **Layer Analysis**: Identifying weak points in specific network layers
|
||||
- **Path Correction**: Improved algorithms for attributing failures to specific nodes
|
||||
|
||||
### Metrics Fanout
|
||||
|
||||
The Network Monitor can submit metrics to multiple API endpoints simultaneously:
|
||||
|
||||
1. Metrics are collected during each monitoring cycle
|
||||
2. The collected metrics are submitted to each configured API endpoint
|
||||
3. This provides redundancy and allows for distributed metrics collection
|
||||
|
||||
To enable metrics fanout, use the `--nym-apis` parameter with a comma-separated list of API URLs:
|
||||
|
||||
```bash
|
||||
cargo run -p nym-network-monitor -- --nym-apis https://api1.example.com,https://api2.example.com
|
||||
```
|
||||
|
||||
## Route Data Structure
|
||||
|
||||
Route metrics use the following data structure:
|
||||
|
||||
```rust
|
||||
// Route performance data
|
||||
pub struct RouteResult {
|
||||
pub layer1: u32, // NodeId of layer 1 mixnode
|
||||
pub layer2: u32, // NodeId of layer 2 mixnode
|
||||
pub layer3: u32, // NodeId of layer 3 mixnode
|
||||
pub gw: u32, // NodeId of gateway
|
||||
pub success: bool, // Whether the packet was successfully delivered
|
||||
}
|
||||
```
|
||||
|
||||
## Forced Routing
|
||||
|
||||
To ensure comprehensive testing of all nodes in the network, the Monitor supports forcing packets through all available nodes:
|
||||
|
||||
- Each node is assigned to a specific layer (1, 2, or 3) deterministically
|
||||
- This ensures all nodes participate in route testing
|
||||
- The routing algorithm cycles through all possible node combinations
|
||||
|
||||
Since version 1.1.0, Network Monitor automatically forces all available nodes to be active and distributes them evenly across the three layers (Layer 1, Layer 2, and Layer 3). This ensures every node in the network participates in testing, providing more comprehensive coverage and better metrics for all nodes, not just the popular ones.
|
||||
|
||||
## Node Performance Calculation
|
||||
|
||||
The Network Monitor uses a sophisticated algorithm for attributing failures to specific nodes:
|
||||
|
||||
1. For successful packet deliveries, all nodes in the path receive a positive sample
|
||||
2. For failed deliveries:
|
||||
- Nodes with more than 2 consecutive failures are considered "guilty"
|
||||
- If no node is clearly guilty, all nodes in the path receive negative samples
|
||||
3. Final node reliability is calculated as: positive_samples / (positive_samples + negative_samples)
|
||||
|
||||
## Changelog
|
||||
|
||||
### Version 1.1.0
|
||||
- Added route-level metrics tracking and submission
|
||||
- Implemented metrics fanout to multiple API endpoints
|
||||
- Forced routing through all available nodes for comprehensive testing
|
||||
- Improved reliability corrections with consecutive failure tracking
|
||||
- Updated data structures for better metrics organization
|
||||
|
||||
### Version 1.0.2
|
||||
- Initial public release with basic monitoring capabilities
|
||||
@@ -5,13 +5,11 @@ use std::{
|
||||
|
||||
use anyhow::Result;
|
||||
use futures::{pin_mut, stream::FuturesUnordered, StreamExt};
|
||||
use log::{debug, error, info, warn};
|
||||
use log::{debug, error, info};
|
||||
use nym_sphinx::chunking::{monitoring, SentFragment};
|
||||
use nym_topology::{NymRouteProvider, RoutingNode};
|
||||
use nym_types::monitoring::{MonitorMessage, MonitorResults, NodeResult, RouteResult};
|
||||
use nym_validator_client::nym_api::routes::{
|
||||
API_VERSION, STATUS, SUBMIT_GATEWAY, SUBMIT_NODE, SUBMIT_ROUTE,
|
||||
};
|
||||
use nym_types::monitoring::{MonitorMessage, NodeResult};
|
||||
use nym_validator_client::nym_api::routes::{API_VERSION, STATUS, SUBMIT_GATEWAY, SUBMIT_NODE};
|
||||
use rand::SeedableRng;
|
||||
use rand_chacha::ChaCha8Rng;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -19,7 +17,7 @@ use tokio::task::JoinHandle;
|
||||
use tokio_postgres::{binary_copy::BinaryCopyInWriter, types::Type, Client, NoTls};
|
||||
use utoipa::ToSchema;
|
||||
|
||||
use crate::{NYM_API_URLS, PRIVATE_KEY, TOPOLOGY};
|
||||
use crate::{NYM_API_URL, PRIVATE_KEY, TOPOLOGY};
|
||||
|
||||
struct HydratedRoute {
|
||||
mix_nodes: Vec<RoutingNode>,
|
||||
@@ -441,7 +439,6 @@ async fn db_connection(database_url: Option<&String>) -> Result<Option<(Client,
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn submit_metrics_to_db(database_url: Option<&String>) -> anyhow::Result<()> {
|
||||
if let Some((client, handle)) = db_connection(database_url).await? {
|
||||
let client = Arc::new(client);
|
||||
@@ -494,100 +491,49 @@ pub async fn submit_metrics(database_url: Option<&String>) -> anyhow::Result<()>
|
||||
}
|
||||
|
||||
if let Some(private_key) = PRIVATE_KEY.get() {
|
||||
if let Some(nym_api_urls) = NYM_API_URLS.get() {
|
||||
info!("Submitting metrics to {} nym apis", nym_api_urls.len());
|
||||
for nym_api_url in nym_api_urls {
|
||||
info!("Submitting metrics to {}", nym_api_url);
|
||||
let node_stats = monitor_mixnode_results().await?;
|
||||
let gateway_stats = monitor_gateway_results().await?;
|
||||
let client = reqwest::Client::new();
|
||||
let node_stats = monitor_mixnode_results().await?;
|
||||
let gateway_stats = monitor_gateway_results().await?;
|
||||
|
||||
let node_submit_url =
|
||||
format!("{}/{API_VERSION}/{STATUS}/{SUBMIT_NODE}", nym_api_url);
|
||||
let gateway_submit_url = format!(
|
||||
"{}/{API_VERSION}/{STATUS}/{SUBMIT_GATEWAY}",
|
||||
nym_api_url
|
||||
info!("Submitting metrics to {}", *NYM_API_URL);
|
||||
let client = reqwest::Client::new();
|
||||
|
||||
let node_submit_url = format!("{}/{API_VERSION}/{STATUS}/{SUBMIT_NODE}", &*NYM_API_URL);
|
||||
let gateway_submit_url =
|
||||
format!("{}/{API_VERSION}/{STATUS}/{SUBMIT_GATEWAY}", &*NYM_API_URL);
|
||||
|
||||
info!("Submitting {} mixnode measurements", node_stats.len());
|
||||
|
||||
node_stats
|
||||
.chunks(10)
|
||||
.map(|chunk| {
|
||||
let monitor_message = MonitorMessage::new(chunk.to_vec(), private_key);
|
||||
client.post(&node_submit_url).json(&monitor_message).send()
|
||||
})
|
||||
.collect::<FuturesUnordered<_>>()
|
||||
.collect::<Vec<Result<_, _>>>()
|
||||
.await
|
||||
.into_iter()
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
info!("Submitting {} gateway measurements", gateway_stats.len());
|
||||
|
||||
gateway_stats
|
||||
.chunks(10)
|
||||
.map(|chunk| {
|
||||
let monitor_message = MonitorMessage::new(
|
||||
chunk.to_vec(),
|
||||
PRIVATE_KEY.get().expect("We've set this!"),
|
||||
);
|
||||
let route_submit_url =
|
||||
format!("{}/{API_VERSION}/{STATUS}/{SUBMIT_ROUTE}", nym_api_url);
|
||||
|
||||
info!("Submitting {} mixnode measurements", node_stats.len());
|
||||
|
||||
node_stats
|
||||
.chunks(10)
|
||||
.map(|chunk| {
|
||||
let monitor_results = MonitorResults::Node(chunk.to_vec());
|
||||
let monitor_message = MonitorMessage::new(monitor_results, private_key);
|
||||
client.post(&node_submit_url).json(&monitor_message).send()
|
||||
})
|
||||
.collect::<FuturesUnordered<_>>()
|
||||
.collect::<Vec<Result<_, _>>>()
|
||||
.await
|
||||
.into_iter()
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
info!("Submitting {} gateway measurements", gateway_stats.len());
|
||||
|
||||
gateway_stats
|
||||
.chunks(10)
|
||||
.map(|chunk| {
|
||||
let monitor_results = MonitorResults::Node(chunk.to_vec());
|
||||
let monitor_message = MonitorMessage::new(
|
||||
monitor_results,
|
||||
PRIVATE_KEY.get().expect("We've set this!"),
|
||||
);
|
||||
client
|
||||
.post(&gateway_submit_url)
|
||||
.json(&monitor_message)
|
||||
.send()
|
||||
})
|
||||
.collect::<FuturesUnordered<_>>()
|
||||
.collect::<Vec<Result<_, _>>>()
|
||||
.await
|
||||
.into_iter()
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
let network_account = NetworkAccount::finalize()?;
|
||||
let accounting_routes = network_account.accounting_routes;
|
||||
info!("Submitting {} accounting routes", accounting_routes.len());
|
||||
match accounting_routes
|
||||
.chunks(10)
|
||||
.map(|chunk| {
|
||||
let route_results = chunk
|
||||
.iter()
|
||||
.map(|route| {
|
||||
RouteResult::new(
|
||||
route.mix_nodes.0,
|
||||
route.mix_nodes.1,
|
||||
route.mix_nodes.2,
|
||||
route.gateway_node,
|
||||
route.success,
|
||||
)
|
||||
})
|
||||
.collect::<Vec<RouteResult>>();
|
||||
let monitor_results = MonitorResults::Route(route_results);
|
||||
let monitor_message = MonitorMessage::new(monitor_results, private_key);
|
||||
client.post(&route_submit_url).json(&monitor_message).send()
|
||||
})
|
||||
.collect::<FuturesUnordered<_>>()
|
||||
.collect::<Vec<Result<_, _>>>()
|
||||
.await
|
||||
.into_iter()
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
{
|
||||
Ok(_) => info!(
|
||||
"Successfully submitted accounting routes to {}",
|
||||
nym_api_url
|
||||
),
|
||||
Err(e) => error!(
|
||||
"Error submitting accounting routes to {}: {}",
|
||||
nym_api_url, e
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
||||
} else {
|
||||
warn!("No private key or nym api urls found");
|
||||
client
|
||||
.post(&gateway_submit_url)
|
||||
.json(&monitor_message)
|
||||
.send()
|
||||
})
|
||||
.collect::<FuturesUnordered<_>>()
|
||||
.collect::<Vec<Result<_, _>>>()
|
||||
.await
|
||||
.into_iter()
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
}
|
||||
|
||||
NetworkAccount::empty_buffers();
|
||||
|
||||
@@ -10,9 +10,10 @@ use nym_network_defaults::setup_env;
|
||||
use nym_network_defaults::var_names::NYM_API;
|
||||
use nym_sdk::mixnet::{self, MixnetClient};
|
||||
use nym_sphinx::chunking::monitoring;
|
||||
use nym_topology::{HardcodedTopologyProvider, NymTopology, Role};
|
||||
use nym_topology::{HardcodedTopologyProvider, NymTopology};
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::sync::LazyLock;
|
||||
use std::time::Duration;
|
||||
use std::{
|
||||
collections::VecDeque,
|
||||
@@ -24,7 +25,9 @@ use tokio::sync::OnceCell;
|
||||
use tokio::{signal::ctrl_c, sync::RwLock};
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
static NYM_API_URLS: OnceCell<Vec<String>> = OnceCell::const_new();
|
||||
static NYM_API_URL: LazyLock<String> = LazyLock::new(|| {
|
||||
std::env::var(NYM_API).unwrap_or_else(|_| panic!("{} env var not set", NYM_API))
|
||||
});
|
||||
|
||||
static MIXNET_TIMEOUT: OnceCell<u64> = OnceCell::const_new();
|
||||
static TOPOLOGY: OnceCell<NymTopology> = OnceCell::const_new();
|
||||
@@ -135,9 +138,6 @@ struct Args {
|
||||
|
||||
#[arg(long, env = "DATABASE_URL")]
|
||||
database_url: Option<String>,
|
||||
|
||||
#[arg(long, env = "NYM_APIS", value_delimiter = ',')]
|
||||
nym_apis: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
fn generate_key_pair() -> Result<()> {
|
||||
@@ -155,7 +155,7 @@ fn generate_key_pair() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn nym_topology_forced_all_from_env() -> anyhow::Result<NymTopology> {
|
||||
async fn nym_topology_from_env() -> anyhow::Result<NymTopology> {
|
||||
let api_url = std::env::var(NYM_API)?;
|
||||
|
||||
info!("Generating topology from {api_url}");
|
||||
@@ -172,23 +172,6 @@ async fn nym_topology_forced_all_from_env() -> anyhow::Result<NymTopology> {
|
||||
let mut topology = NymTopology::new_empty(rewarded_set);
|
||||
topology.add_skimmed_nodes(&nodes);
|
||||
|
||||
let node_ids = topology
|
||||
.node_details()
|
||||
.iter()
|
||||
.filter(|(_node_id, node)| node.supported_roles.mixnode)
|
||||
.map(|(node_id, _)| *node_id)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Force all nodes to active to participate in route selection
|
||||
for (idx, node_id) in node_ids.iter().enumerate() {
|
||||
match idx % 3 {
|
||||
0 => topology.force_set_active(*node_id, Role::Layer1),
|
||||
1 => topology.force_set_active(*node_id, Role::Layer2),
|
||||
2 => topology.force_set_active(*node_id, Role::Layer3),
|
||||
_ => unreachable!(), // Unreachable since idx % 3 can only be 0, 1, or 2
|
||||
}
|
||||
}
|
||||
|
||||
Ok(topology)
|
||||
}
|
||||
|
||||
@@ -217,16 +200,11 @@ async fn main() -> Result<()> {
|
||||
PRIVATE_KEY.set(pk).ok();
|
||||
}
|
||||
|
||||
if let Some(nym_apis) = args.nym_apis {
|
||||
info!("Using nym apis: {:?}", nym_apis);
|
||||
NYM_API_URLS.set(nym_apis).ok();
|
||||
}
|
||||
|
||||
TOPOLOGY
|
||||
.set(if let Some(topology_file) = args.topology {
|
||||
NymTopology::new_from_file(topology_file)?
|
||||
} else {
|
||||
nym_topology_forced_all_from_env().await?
|
||||
nym_topology_from_env().await?
|
||||
})
|
||||
.ok();
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
FROM harbor.nymte.ch/dockerhub/rust:latest AS builder
|
||||
|
||||
ARG GIT_REF=main
|
||||
ARG MNEMONIC
|
||||
ENV MNEMONIC=${MNEMONIC}
|
||||
|
||||
RUN apt update && apt install -yy libdbus-1-dev pkg-config libclang-dev
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ export NODE_STATUS_AGENT_SERVER_ADDRESS="http://127.0.0.1"
|
||||
export NODE_STATUS_AGENT_SERVER_PORT="8000"
|
||||
export NODE_STATUS_AGENT_PROBE_PATH="$crate_root/nym-gateway-probe"
|
||||
export NODE_STATUS_AGENT_AUTH_KEY="BjyC9SsHAZUzPRkQR4sPTvVrp4GgaquTh5YfSJksvvWT"
|
||||
export NODE_STATUS_AGENT_PROBE_EXTRA_ARGS="netstack-download-timeout-sec=30,netstack-num-ping=2,netstack-send-timeout-sec=1,netstack-recv-timeout-sec=1"
|
||||
export NODE_STATUS_AGENT_PROBE_EXTRA_ARGS="netstack-download-timeout-sec=30,netstack-num-ping=2,netstack-send-timeout-sec=1,netstack-recv-timeout-sec=1,mnemonic=\"${MNEMONIC}\""
|
||||
|
||||
workers=${1:-1}
|
||||
echo "Running $workers workers in parallel"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user