Compare commits
51 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ac22533ecd | |||
| bdc0b875a4 | |||
| d7b67c1408 | |||
| 606e29ebb0 | |||
| 21e3c1538d | |||
| 0fc7cc657d | |||
| 23a7f01c05 | |||
| 3a21cfa1ab | |||
| 1d2e6d916c | |||
| 4c2bf3642e | |||
| 70e2e32385 | |||
| 68a192daa3 | |||
| d6aacae14e | |||
| 6f00023d09 | |||
| 982ec56874 | |||
| 5dcc1ed6dc | |||
| d62bc0a10b | |||
| 882003c08c | |||
| b71a491872 | |||
| 8f48ae08c4 | |||
| 6d90ffdd2c | |||
| 9550934d1f | |||
| ff91d4619e | |||
| 9d01474277 | |||
| 8d10552d7c | |||
| 04fd197f5a | |||
| 4eadaf8292 | |||
| 32e39ebc6b | |||
| 117eb83a0b | |||
| c964c137f4 | |||
| 35b43d5b20 | |||
| bf88b34898 | |||
| 93140a1aa7 | |||
| f594bfc9ab | |||
| 4327e2945a | |||
| 8670693952 | |||
| 57c38ef222 | |||
| 8e05386a0b | |||
| 13cfa55e6c | |||
| 18e628acde | |||
| e67b2b020a | |||
| a0daabab03 | |||
| b0a5b60945 | |||
| 01c7ea72dd | |||
| dfd1df5706 | |||
| 5f06414a12 | |||
| 656838811a | |||
| 7b8458630a | |||
| cf2ab08b4d | |||
| 2466112829 | |||
| e5306908e4 |
@@ -8,17 +8,18 @@ on:
|
||||
- 'explorer-api/**'
|
||||
- 'gateway/**'
|
||||
- 'integrations/**'
|
||||
- 'mixnode/**'
|
||||
- 'nym-api/**'
|
||||
- 'nym-data-observatory/**'
|
||||
- 'nym-credential-proxy/**'
|
||||
- 'nym-network-monitor/**'
|
||||
- 'nym-node/**'
|
||||
- 'nym-node-status-api/**'
|
||||
- 'nym-outfox/**'
|
||||
- 'nym-validator-rewarder/**'
|
||||
- 'sdk/lib/**'
|
||||
- 'nyx-chain-watcher/**'
|
||||
- 'sdk/ffi/**'
|
||||
- 'sdk/rust/**'
|
||||
- 'service-providers/**'
|
||||
- 'nym-browser-extension/storage/**'
|
||||
- 'tools/**'
|
||||
- 'wasm/**'
|
||||
- 'Cargo.toml'
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
name: ci-sdk-wasm
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'wasm/**'
|
||||
|
||||
+11
-1
@@ -4,8 +4,13 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [2025.2-hu] (2025-01-28)
|
||||
## [2025.2-hu] (2025-02-04)
|
||||
|
||||
- Feature/remove double spending bloomfilter ([#5417])
|
||||
- HU - Downgrade harmless log message from info to debug ([#5405])
|
||||
- lower default ticket verification quorum to 0.7 ([#5404])
|
||||
- Downgrade harmless log message from info to debug ([#5403])
|
||||
- Redirect from mixnode page to nodes page ([#5397])
|
||||
- chore :update version of chain watcher and validator rewarder ([#5394])
|
||||
- bugfix: correctly handle ingore epoch roles flag ([#5390])
|
||||
- bugfix: terminate mixnet socket listener on shutdown ([#5389])
|
||||
@@ -40,6 +45,11 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
- NS API: add mixnet scraper ([#5200])
|
||||
- build(deps): bump criterion from 0.4.0 to 0.5.1 ([#4911])
|
||||
|
||||
[#5417]: https://github.com/nymtech/nym/pull/5417
|
||||
[#5405]: https://github.com/nymtech/nym/pull/5405
|
||||
[#5404]: https://github.com/nymtech/nym/pull/5404
|
||||
[#5403]: https://github.com/nymtech/nym/pull/5403
|
||||
[#5397]: https://github.com/nymtech/nym/pull/5397
|
||||
[#5394]: https://github.com/nymtech/nym/pull/5394
|
||||
[#5390]: https://github.com/nymtech/nym/pull/5390
|
||||
[#5389]: https://github.com/nymtech/nym/pull/5389
|
||||
|
||||
Generated
+282
-165
File diff suppressed because it is too large
Load Diff
+34
-35
@@ -48,13 +48,12 @@ members = [
|
||||
"common/credentials-interface",
|
||||
"common/crypto",
|
||||
"common/dkg",
|
||||
"common/ecash-double-spending",
|
||||
"common/ecash-time",
|
||||
"common/execute",
|
||||
"common/exit-policy",
|
||||
"common/gateway-requests",
|
||||
"common/gateway-storage",
|
||||
"common/gateway-stats-storage",
|
||||
"common/gateway-storage",
|
||||
"common/http-api-client",
|
||||
"common/http-api-common",
|
||||
"common/inclusion-probability",
|
||||
@@ -93,6 +92,7 @@ members = [
|
||||
"common/topology",
|
||||
"common/tun",
|
||||
"common/types",
|
||||
"common/verloc",
|
||||
"common/wasm/client-core",
|
||||
"common/wasm/storage",
|
||||
"common/wasm/utils",
|
||||
@@ -104,6 +104,22 @@ members = [
|
||||
"explorer-api/explorer-client",
|
||||
"gateway",
|
||||
"integrations/bity",
|
||||
"nym-api",
|
||||
"nym-api/nym-api-requests",
|
||||
"nym-browser-extension/storage",
|
||||
"nym-credential-proxy/nym-credential-proxy",
|
||||
"nym-credential-proxy/nym-credential-proxy-requests",
|
||||
"nym-credential-proxy/vpn-api-lib-wasm",
|
||||
"nym-network-monitor",
|
||||
"nym-node",
|
||||
"nym-node-status-api/nym-node-status-agent",
|
||||
"nym-node-status-api/nym-node-status-api",
|
||||
"nym-node-status-api/nym-node-status-client",
|
||||
"nym-node/nym-node-metrics",
|
||||
"nym-node/nym-node-requests",
|
||||
"nym-outfox",
|
||||
"nym-validator-rewarder",
|
||||
"nyx-chain-watcher",
|
||||
"sdk/ffi/cpp",
|
||||
"sdk/ffi/go",
|
||||
"sdk/ffi/shared",
|
||||
@@ -112,26 +128,16 @@ members = [
|
||||
"service-providers/common",
|
||||
"service-providers/ip-packet-router",
|
||||
"service-providers/network-requester",
|
||||
"nym-api",
|
||||
"nym-api/nym-api-requests",
|
||||
"nym-browser-extension/storage",
|
||||
"nym-credential-proxy/nym-credential-proxy",
|
||||
"nym-credential-proxy/nym-credential-proxy-requests",
|
||||
"nym-credential-proxy/vpn-api-lib-wasm",
|
||||
"nym-network-monitor",
|
||||
"nyx-chain-watcher",
|
||||
"nym-node",
|
||||
"nym-node/nym-node-requests",
|
||||
"nym-node/nym-node-metrics",
|
||||
"nym-node-status-api/nym-node-status-agent",
|
||||
"nym-node-status-api/nym-node-status-api",
|
||||
"nym-node-status-api/nym-node-status-client",
|
||||
"nym-outfox",
|
||||
"nym-validator-rewarder",
|
||||
"tools/echo-server",
|
||||
"tools/internal/ssl-inject",
|
||||
"tools/echo-server",
|
||||
"tools/internal/contract-state-importer/importer-cli",
|
||||
"tools/internal/contract-state-importer/importer-contract",
|
||||
"tools/internal/mixnet-connectivity-check",
|
||||
# "tools/internal/sdk-version-bump",
|
||||
"tools/internal/ssl-inject",
|
||||
"tools/internal/testnet-manager",
|
||||
"tools/internal/testnet-manager",
|
||||
"tools/internal/testnet-manager/dkg-bypass-contract",
|
||||
"tools/internal/testnet-manager/dkg-bypass-contract",
|
||||
"tools/nym-cli",
|
||||
"tools/nym-id-cli",
|
||||
@@ -143,13 +149,6 @@ members = [
|
||||
"wasm/mix-fetch",
|
||||
"wasm/node-tester",
|
||||
"wasm/zknym-lib",
|
||||
"tools/echo-server",
|
||||
"tools/internal/contract-state-importer/importer-cli",
|
||||
"tools/internal/contract-state-importer/importer-contract",
|
||||
"tools/internal/testnet-manager",
|
||||
"tools/internal/testnet-manager/dkg-bypass-contract",
|
||||
"common/verloc",
|
||||
"tools/internal/mixnet-connectivity-check",
|
||||
]
|
||||
|
||||
default-members = [
|
||||
@@ -173,7 +172,6 @@ exclude = [
|
||||
"explorer",
|
||||
"contracts",
|
||||
"nym-wallet",
|
||||
"nym-vpn/ui/src-tauri",
|
||||
"cpu-cycles",
|
||||
]
|
||||
|
||||
@@ -217,7 +215,7 @@ chacha20 = "0.9.0"
|
||||
chacha20poly1305 = "0.10.1"
|
||||
chrono = "0.4.39"
|
||||
cipher = "0.4.3"
|
||||
clap = "4.5.26"
|
||||
clap = "4.5.27"
|
||||
clap_complete = "4.5"
|
||||
clap_complete_fig = "4.5"
|
||||
colored = "2.0"
|
||||
@@ -250,11 +248,12 @@ futures = "0.3.31"
|
||||
futures-util = "0.3"
|
||||
generic-array = "0.14.7"
|
||||
getrandom = "0.2.10"
|
||||
getset = "0.1.3"
|
||||
getset = "0.1.4"
|
||||
handlebars = "3.5.5"
|
||||
headers = "0.4.0"
|
||||
hex = "0.4.3"
|
||||
hex-literal = "0.3.3"
|
||||
hickory-resolver = "0.24.2"
|
||||
hkdf = "0.12.3"
|
||||
hmac = "0.12.1"
|
||||
http = "1"
|
||||
@@ -265,7 +264,7 @@ humantime-serde = "1.1.1"
|
||||
human-repr = "1.1.0"
|
||||
hyper = "1.4.1"
|
||||
hyper-util = "0.1"
|
||||
indicatif = "0.17.9"
|
||||
indicatif = "0.17.11"
|
||||
inquire = "0.6.2"
|
||||
ip_network = "0.4.1"
|
||||
ipnetwork = "0.20"
|
||||
@@ -308,11 +307,11 @@ rocket_cors = "0.6.0"
|
||||
rocket_okapi = "0.8.0"
|
||||
safer-ffi = "0.1.13"
|
||||
schemars = "0.8.21"
|
||||
semver = "1.0.24"
|
||||
semver = "1.0.25"
|
||||
serde = "1.0.217"
|
||||
serde_bytes = "0.11.15"
|
||||
serde_derive = "1.0"
|
||||
serde_json = "1.0.135"
|
||||
serde_json = "1.0.138"
|
||||
serde_json_path = "0.7.1"
|
||||
serde_repr = "0.1"
|
||||
serde_with = "3.9.0"
|
||||
@@ -329,9 +328,9 @@ sysinfo = "0.33.0"
|
||||
tap = "1.0.1"
|
||||
tar = "0.4.43"
|
||||
tempfile = "3.15"
|
||||
thiserror = "1.0.64"
|
||||
thiserror = "2.0"
|
||||
time = "0.3.37"
|
||||
tokio = "1.39"
|
||||
tokio = "1.43"
|
||||
tokio-stream = "0.1.17"
|
||||
tokio-test = "0.4.4"
|
||||
tokio-tun = "0.11.5"
|
||||
@@ -385,7 +384,7 @@ cw4 = { version = "=1.1.2" }
|
||||
cw-controllers = { version = "=1.1.0" }
|
||||
|
||||
# cosmrs-related
|
||||
bip32 = { version = "0.5.2", default-features = false }
|
||||
bip32 = { version = "0.5.3", default-features = false }
|
||||
|
||||
|
||||
cosmrs = { version = "0.21.0" }
|
||||
|
||||
@@ -13,7 +13,7 @@ The platform is composed of multiple Rust crates. Top-level executable binary cr
|
||||
* `nym-client` - an executable which you can build into your own applications. Use it for interacting with Nym nodes.
|
||||
* `nym-socks5-client` - a Socks5 proxy you can run on your machine and use with existing applications.
|
||||
* `nym-explorer` - a (projected) block explorer and (existing) mixnet viewer.
|
||||
* `nym-wallet` - a desktop wallet implemented using the [Tauri](https://tauri.studio/en/docs/about/intro) framework.
|
||||
* `nym-wallet` - a desktop wallet implemented using the [Tauri](https://tauri.app)) framework.
|
||||
* `nym-cli` - a tool for interacting with the network from the CLI.
|
||||
<!-- coming soon
|
||||
* `nym-network-monitor` - sends packets through the full system to check that they are working as expected, and stores node uptime histories as the basis of a rewards system ("mixmining" or "proof-of-mixing").
|
||||
@@ -66,4 +66,4 @@ As a general approach, licensing is as follows this pattern:
|
||||
- libraries and components are Apache 2.0 or MIT
|
||||
- documentation is Apache 2.0 or CC0-1.0
|
||||
|
||||
Nym Node Operators and Validators Temrs and Conditions can be found [here](https://nym.com/terms-and-conditions/operators/v1.0.0).
|
||||
Nym Node Operators and Validators Terms and Conditions can be found [here](https://nym.com/operators-validators-terms).
|
||||
|
||||
@@ -40,6 +40,7 @@ nym-crypto = { path = "../crypto" }
|
||||
nym-explorer-client = { path = "../../explorer-api/explorer-client" }
|
||||
nym-gateway-client = { path = "../client-libs/gateway-client" }
|
||||
nym-gateway-requests = { path = "../gateway-requests" }
|
||||
nym-http-api-client = { path = "../http-api-client" }
|
||||
nym-metrics = { path = "../nym-metrics" }
|
||||
nym-nonexhaustive-delayqueue = { path = "../nonexhaustive-delayqueue" }
|
||||
nym-sphinx = { path = "../nymsphinx" }
|
||||
|
||||
@@ -15,6 +15,7 @@ pub mod error;
|
||||
mod manager;
|
||||
mod models;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct OnDiskGatewaysDetails {
|
||||
manager: StorageManager,
|
||||
}
|
||||
|
||||
@@ -20,12 +20,12 @@ pub enum InMemStorageError {
|
||||
MalformedGateway(#[from] BadGateway),
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct InMemGatewaysDetails {
|
||||
inner: Arc<RwLock<InMemStorageInner>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
#[derive(Clone, Debug, Default)]
|
||||
struct InMemStorageInner {
|
||||
active_gateway: Option<String>,
|
||||
gateways: HashMap<String, GatewayRegistration>,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use super::mix_traffic::ClientRequestSender;
|
||||
use super::received_buffer::ReceivedBufferMessage;
|
||||
use super::statistics_control::StatisticsControl;
|
||||
use crate::client::base_client::storage::helpers::store_client_keys;
|
||||
@@ -645,13 +646,12 @@ where
|
||||
fn start_mix_traffic_controller(
|
||||
gateway_transceiver: Box<dyn GatewayTransceiver + Send>,
|
||||
shutdown: TaskClient,
|
||||
forget_me: ForgetMe,
|
||||
) -> BatchMixMessageSender {
|
||||
) -> (BatchMixMessageSender, ClientRequestSender) {
|
||||
info!("Starting mix traffic controller...");
|
||||
let (mix_traffic_controller, mix_tx) =
|
||||
MixTrafficController::new(gateway_transceiver, forget_me);
|
||||
let (mix_traffic_controller, mix_tx, client_tx) =
|
||||
MixTrafficController::new(gateway_transceiver);
|
||||
mix_traffic_controller.start_with_shutdown(shutdown);
|
||||
mix_tx
|
||||
(mix_tx, client_tx)
|
||||
}
|
||||
|
||||
// TODO: rename it as it implies the data is persistent whilst one can use InMemBackend
|
||||
@@ -833,10 +833,9 @@ where
|
||||
// traffic stream.
|
||||
// The MixTrafficController then sends the actual traffic
|
||||
|
||||
let message_sender = Self::start_mix_traffic_controller(
|
||||
let (message_sender, client_request_sender) = Self::start_mix_traffic_controller(
|
||||
gateway_transceiver,
|
||||
shutdown.fork("mix_traffic_controller"),
|
||||
self.forget_me,
|
||||
);
|
||||
|
||||
// Channels that the websocket listener can use to signal downstream to the real traffic
|
||||
@@ -911,6 +910,8 @@ where
|
||||
},
|
||||
stats_reporter,
|
||||
task_handle: shutdown,
|
||||
client_request_sender,
|
||||
forget_me: self.forget_me,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -922,6 +923,7 @@ pub struct BaseClient {
|
||||
pub client_output: ClientOutputStatus,
|
||||
pub client_state: ClientState,
|
||||
pub stats_reporter: ClientStatsSender,
|
||||
|
||||
pub client_request_sender: ClientRequestSender,
|
||||
pub task_handle: TaskHandle,
|
||||
pub forget_me: ForgetMe,
|
||||
}
|
||||
|
||||
@@ -65,6 +65,7 @@ pub trait MixnetClientStorage {
|
||||
fn gateway_details_store(&self) -> &Self::GatewaysDetailsStore;
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Ephemeral {
|
||||
key_store: InMemEphemeralKeys,
|
||||
reply_store: reply_storage::Empty,
|
||||
@@ -120,6 +121,7 @@ impl MixnetClientStorage for Ephemeral {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[cfg(all(
|
||||
not(target_arch = "wasm32"),
|
||||
feature = "fs-surb-storage",
|
||||
|
||||
@@ -5,6 +5,7 @@ use crate::client::key_manager::ClientKeys;
|
||||
use async_trait::async_trait;
|
||||
use rand::{CryptoRng, RngCore};
|
||||
use std::error::Error;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
@@ -65,6 +66,7 @@ pub enum OnDiskKeysError {
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub struct OnDiskKeys {
|
||||
paths: ClientKeysPaths,
|
||||
@@ -194,8 +196,9 @@ impl KeyStore for OnDiskKeys {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct InMemEphemeralKeys {
|
||||
keys: Mutex<ClientKeys>,
|
||||
keys: Arc<Mutex<ClientKeys>>,
|
||||
}
|
||||
|
||||
impl InMemEphemeralKeys {
|
||||
@@ -204,7 +207,7 @@ impl InMemEphemeralKeys {
|
||||
R: RngCore + CryptoRng,
|
||||
{
|
||||
InMemEphemeralKeys {
|
||||
keys: Mutex::new(ClientKeys::generate_new(rng)),
|
||||
keys: Arc::new(Mutex::new(ClientKeys::generate_new(rng))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
use crate::client::mix_traffic::transceiver::GatewayTransceiver;
|
||||
use crate::error::ClientCoreError;
|
||||
use crate::{spawn_future, ForgetMe};
|
||||
use crate::spawn_future;
|
||||
use log::*;
|
||||
use nym_gateway_requests::ClientRequest;
|
||||
use nym_sphinx::forwarding::packet::MixPacket;
|
||||
@@ -11,6 +11,8 @@ use transceiver::ErasedGatewayError;
|
||||
|
||||
pub type BatchMixMessageSender = tokio::sync::mpsc::Sender<Vec<MixPacket>>;
|
||||
pub type BatchMixMessageReceiver = tokio::sync::mpsc::Receiver<Vec<MixPacket>>;
|
||||
pub type ClientRequestReceiver = tokio::sync::mpsc::Receiver<ClientRequest>;
|
||||
pub type ClientRequestSender = tokio::sync::mpsc::Sender<ClientRequest>;
|
||||
|
||||
pub mod transceiver;
|
||||
|
||||
@@ -25,48 +27,60 @@ pub struct MixTrafficController {
|
||||
gateway_transceiver: Box<dyn GatewayTransceiver + Send>,
|
||||
|
||||
mix_rx: BatchMixMessageReceiver,
|
||||
client_rx: ClientRequestReceiver,
|
||||
|
||||
// TODO: this is temporary work-around.
|
||||
// in long run `gateway_client` will be moved away from `MixTrafficController` anyway.
|
||||
consecutive_gateway_failure_count: usize,
|
||||
forget_me: ForgetMe,
|
||||
}
|
||||
|
||||
impl MixTrafficController {
|
||||
pub fn new<T>(
|
||||
gateway_transceiver: T,
|
||||
forget_me: ForgetMe,
|
||||
) -> (MixTrafficController, BatchMixMessageSender)
|
||||
) -> (
|
||||
MixTrafficController,
|
||||
BatchMixMessageSender,
|
||||
ClientRequestSender,
|
||||
)
|
||||
where
|
||||
T: GatewayTransceiver + Send + 'static,
|
||||
{
|
||||
let (message_sender, message_receiver) =
|
||||
tokio::sync::mpsc::channel(MIX_MESSAGE_RECEIVER_BUFFER_SIZE);
|
||||
|
||||
let (client_sender, client_receiver) = tokio::sync::mpsc::channel(1);
|
||||
|
||||
(
|
||||
MixTrafficController {
|
||||
gateway_transceiver: Box::new(gateway_transceiver),
|
||||
mix_rx: message_receiver,
|
||||
client_rx: client_receiver,
|
||||
consecutive_gateway_failure_count: 0,
|
||||
forget_me,
|
||||
},
|
||||
message_sender,
|
||||
client_sender,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_dynamic(
|
||||
gateway_transceiver: Box<dyn GatewayTransceiver + Send>,
|
||||
forget_me: ForgetMe,
|
||||
) -> (MixTrafficController, BatchMixMessageSender) {
|
||||
) -> (
|
||||
MixTrafficController,
|
||||
BatchMixMessageSender,
|
||||
ClientRequestSender,
|
||||
) {
|
||||
let (message_sender, message_receiver) =
|
||||
tokio::sync::mpsc::channel(MIX_MESSAGE_RECEIVER_BUFFER_SIZE);
|
||||
let (client_sender, client_receiver) = tokio::sync::mpsc::channel(1);
|
||||
(
|
||||
MixTrafficController {
|
||||
gateway_transceiver,
|
||||
mix_rx: message_receiver,
|
||||
client_rx: client_receiver,
|
||||
consecutive_gateway_failure_count: 0,
|
||||
forget_me,
|
||||
},
|
||||
message_sender,
|
||||
client_sender,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -121,6 +135,17 @@ impl MixTrafficController {
|
||||
break;
|
||||
}
|
||||
},
|
||||
client_request = self.client_rx.recv() => match client_request {
|
||||
Some(client_request) => {
|
||||
match self.gateway_transceiver.send_client_request(client_request).await {
|
||||
Ok(_) => (),
|
||||
Err(e) => error!("Failed to send client request: {}", e),
|
||||
};
|
||||
},
|
||||
None => {
|
||||
log::trace!("MixTrafficController, client request channel closed");
|
||||
}
|
||||
},
|
||||
_ = shutdown.recv_with_delay() => {
|
||||
log::trace!("MixTrafficController: Received shutdown");
|
||||
break;
|
||||
@@ -129,25 +154,6 @@ impl MixTrafficController {
|
||||
}
|
||||
shutdown.recv_timeout().await;
|
||||
|
||||
if self.forget_me.any() {
|
||||
log::info!("Sending forget me request to the gateway");
|
||||
match self
|
||||
.gateway_transceiver
|
||||
.send_client_request(ClientRequest::ForgetMe {
|
||||
client: self.forget_me.client(),
|
||||
stats: self.forget_me.stats(),
|
||||
})
|
||||
.await
|
||||
{
|
||||
Ok(_) => {
|
||||
log::info!("Successfully sent forget me request to the gateway");
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!("Failed to send forget me request to the gateway: {err}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log::debug!("MixTrafficController: Exiting");
|
||||
});
|
||||
}
|
||||
|
||||
@@ -86,7 +86,9 @@ impl<G: GatewayTransceiver + ?Sized + Send> GatewayTransceiver for Box<G> {
|
||||
&mut self,
|
||||
message: ClientRequest,
|
||||
) -> Result<(), GatewayClientError> {
|
||||
(**self).send_client_request(message).await
|
||||
let _ = (**self).send_client_request(message.clone()).await?;
|
||||
log::debug!("Sent client request: {:?}", message);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,14 +145,7 @@ where
|
||||
&mut self,
|
||||
message: ClientRequest,
|
||||
) -> Result<(), GatewayClientError> {
|
||||
if let Some(shared_key) = self.gateway_client.shared_key() {
|
||||
self.gateway_client
|
||||
.send_websocket_message(message.encrypt(&*shared_key)?)
|
||||
.await?;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(GatewayClientError::ConnectionInInvalidState)
|
||||
}
|
||||
self.gateway_client.send_client_request(message).await
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,13 @@ pub enum ClientCoreError {
|
||||
#[error("no gateway with id: {0}")]
|
||||
NoGatewayWithId(String),
|
||||
|
||||
#[error("Invalid URL: {0}")]
|
||||
InvalidUrl(String),
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[error("resolution failed: {0}")]
|
||||
ResolutionFailed(#[from] nym_http_api_client::HickoryDnsError),
|
||||
|
||||
#[error("no gateways on network")]
|
||||
NoGatewaysOnNetwork,
|
||||
|
||||
|
||||
@@ -15,6 +15,9 @@ use std::{sync::Arc, time::Duration};
|
||||
use tungstenite::Message;
|
||||
use url::Url;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use crate::init::websockets::connect_async;
|
||||
|
||||
use nym_topology::NodeId;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use tokio::net::TcpStream;
|
||||
@@ -23,8 +26,6 @@ use tokio::time::sleep;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use tokio::time::Instant;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use tokio_tungstenite::connect_async;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use tokio_tungstenite::{MaybeTlsStream, WebSocketStream};
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use wasm_utils::websocket::JSWebsocket;
|
||||
@@ -132,7 +133,7 @@ pub async fn gateways_for_init<R: Rng>(
|
||||
async fn connect(endpoint: &str) -> Result<WsConn, ClientCoreError> {
|
||||
match tokio::time::timeout(CONN_TIMEOUT, connect_async(endpoint)).await {
|
||||
Err(_elapsed) => Err(ClientCoreError::GatewayConnectionTimeout),
|
||||
Ok(Err(conn_failure)) => Err(conn_failure.into()),
|
||||
Ok(Err(conn_failure)) => Err(conn_failure),
|
||||
Ok(Ok((stream, _))) => Ok(stream),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,8 @@ use serde::Serialize;
|
||||
|
||||
pub mod helpers;
|
||||
pub mod types;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub(crate) mod websockets;
|
||||
|
||||
// helpers for error wrapping
|
||||
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
use crate::error::ClientCoreError;
|
||||
|
||||
use nym_http_api_client::HickoryDnsResolver;
|
||||
use tokio::net::TcpStream;
|
||||
use tokio_tungstenite::{MaybeTlsStream, WebSocketStream};
|
||||
use tungstenite::handshake::client::Response;
|
||||
use url::{Host, Url};
|
||||
|
||||
use std::net::SocketAddr;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub(crate) async fn connect_async(
|
||||
endpoint: &str,
|
||||
) -> Result<(WebSocketStream<MaybeTlsStream<TcpStream>>, Response), ClientCoreError> {
|
||||
let resolver = HickoryDnsResolver::default();
|
||||
let uri = Url::parse(endpoint).map_err(|_| ClientCoreError::InvalidUrl(endpoint.to_owned()))?;
|
||||
let port: u16 = uri.port_or_known_default().unwrap_or(443);
|
||||
|
||||
let host = uri
|
||||
.host()
|
||||
.ok_or(ClientCoreError::InvalidUrl(endpoint.to_owned()))?;
|
||||
|
||||
// Get address for tcp connection, if a domain is provided use our preferred resolver rather than
|
||||
// the default std resolve
|
||||
let sock_addrs: Vec<SocketAddr> = match host {
|
||||
Host::Ipv4(addr) => vec![SocketAddr::new(addr.into(), port)],
|
||||
Host::Ipv6(addr) => vec![SocketAddr::new(addr.into(), port)],
|
||||
Host::Domain(domain) => {
|
||||
// Do a DNS lookup for the domain using our custom DNS resolver
|
||||
resolver
|
||||
.resolve_str(domain)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|a| SocketAddr::new(a, port))
|
||||
.collect()
|
||||
}
|
||||
};
|
||||
|
||||
let stream = TcpStream::connect(&sock_addrs[..]).await?;
|
||||
|
||||
tokio_tungstenite::client_async_tls(endpoint, stream)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
@@ -22,7 +22,7 @@ mod error;
|
||||
mod manager;
|
||||
mod models;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Backend {
|
||||
temporary_old_path: Option<PathBuf>,
|
||||
database_path: PathBuf,
|
||||
|
||||
@@ -19,7 +19,7 @@ pub mod fs_backend;
|
||||
#[error("no information provided")]
|
||||
pub struct UndefinedError;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Empty {
|
||||
// we need to keep 'basic' metadata here to "load" the CombinedReplyStorage
|
||||
pub min_surb_threshold: usize,
|
||||
|
||||
@@ -27,6 +27,7 @@ nym-credential-storage = { path = "../../credential-storage" }
|
||||
nym-credentials-interface = { path = "../../credentials-interface" }
|
||||
nym-crypto = { path = "../../crypto" }
|
||||
nym-gateway-requests = { path = "../../gateway-requests" }
|
||||
nym-http-api-client = { path = "../../http-api-client" }
|
||||
nym-network-defaults = { path = "../../network-defaults" }
|
||||
nym-sphinx = { path = "../../nymsphinx" }
|
||||
nym-statistics-common = { path = "../../statistics" }
|
||||
|
||||
@@ -40,8 +40,6 @@ use url::Url;
|
||||
use std::os::fd::RawFd;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use tokio::time::sleep;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use tokio_tungstenite::connect_async;
|
||||
|
||||
#[cfg(not(unix))]
|
||||
use std::os::raw::c_int as RawFd;
|
||||
@@ -53,6 +51,11 @@ use zeroize::Zeroizing;
|
||||
|
||||
pub mod config;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub(crate) mod websockets;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use websockets::connect_async;
|
||||
|
||||
pub struct GatewayConfig {
|
||||
pub gateway_identity: identity::PublicKey,
|
||||
|
||||
@@ -201,15 +204,7 @@ impl<C, St> GatewayClient<C, St> {
|
||||
"Attemting to establish connection to gateway at: {}",
|
||||
self.gateway_address
|
||||
);
|
||||
let ws_stream = match connect_async(&self.gateway_address).await {
|
||||
Ok((ws_stream, _)) => ws_stream,
|
||||
Err(error) => {
|
||||
return Err(GatewayClientError::NetworkConnectionFailed {
|
||||
address: self.gateway_address.clone(),
|
||||
source: error,
|
||||
})
|
||||
}
|
||||
};
|
||||
let (ws_stream, _) = connect_async(&self.gateway_address).await?;
|
||||
|
||||
self.connection = SocketState::Available(Box::new(ws_stream));
|
||||
|
||||
@@ -271,6 +266,19 @@ impl<C, St> GatewayClient<C, St> {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn send_client_request(
|
||||
&mut self,
|
||||
message: ClientRequest,
|
||||
) -> Result<(), GatewayClientError> {
|
||||
if let Some(shared_key) = self.shared_key() {
|
||||
let encrypted = message.encrypt(&*shared_key)?;
|
||||
Box::pin(self.send_websocket_message(encrypted)).await?;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(GatewayClientError::ConnectionInInvalidState)
|
||||
}
|
||||
}
|
||||
|
||||
async fn read_control_response(&mut self) -> Result<ServerResponse, GatewayClientError> {
|
||||
// we use the fact that all request responses are Message::Text and only pushed
|
||||
// sphinx packets are Message::Binary
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
use crate::error::GatewayClientError;
|
||||
|
||||
use nym_http_api_client::HickoryDnsResolver;
|
||||
use tokio::net::TcpStream;
|
||||
use tokio_tungstenite::{MaybeTlsStream, WebSocketStream};
|
||||
use tungstenite::handshake::client::Response;
|
||||
use url::{Host, Url};
|
||||
|
||||
use std::net::SocketAddr;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub(crate) async fn connect_async(
|
||||
endpoint: &str,
|
||||
) -> Result<(WebSocketStream<MaybeTlsStream<TcpStream>>, Response), GatewayClientError> {
|
||||
let resolver = HickoryDnsResolver::default();
|
||||
let uri =
|
||||
Url::parse(endpoint).map_err(|_| GatewayClientError::InvalidUrl(endpoint.to_owned()))?;
|
||||
let port: u16 = uri.port_or_known_default().unwrap_or(443);
|
||||
|
||||
let host = uri
|
||||
.host()
|
||||
.ok_or(GatewayClientError::InvalidUrl(endpoint.to_owned()))?;
|
||||
|
||||
// Get address for tcp connection, if a domain is provided use our preferred resolver rather than
|
||||
// the default std resolve
|
||||
let sock_addrs: Vec<SocketAddr> = match host {
|
||||
Host::Ipv4(addr) => vec![SocketAddr::new(addr.into(), port)],
|
||||
Host::Ipv6(addr) => vec![SocketAddr::new(addr.into(), port)],
|
||||
Host::Domain(domain) => {
|
||||
// Do a DNS lookup for the domain using our custom DNS resolver
|
||||
resolver
|
||||
.resolve_str(domain)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|a| SocketAddr::new(a, port))
|
||||
.collect()
|
||||
}
|
||||
};
|
||||
|
||||
let stream = TcpStream::connect(&sock_addrs[..]).await.map_err(|error| {
|
||||
GatewayClientError::NetworkConnectionFailed {
|
||||
address: endpoint.to_owned(),
|
||||
source: error.into(),
|
||||
}
|
||||
})?;
|
||||
|
||||
tokio_tungstenite::client_async_tls(endpoint, stream)
|
||||
.await
|
||||
.map_err(|error| GatewayClientError::NetworkConnectionFailed {
|
||||
address: endpoint.to_owned(),
|
||||
source: error,
|
||||
})
|
||||
}
|
||||
@@ -44,7 +44,11 @@ pub enum GatewayClientError {
|
||||
NetworkConnectionFailed { address: String, source: WsError },
|
||||
|
||||
#[error("Invalid URL: {0}")]
|
||||
InvalidURL(String),
|
||||
InvalidUrl(String),
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[error("resolution failed: {0}")]
|
||||
ResolutionFailed(#[from] nym_http_api_client::HickoryDnsError),
|
||||
|
||||
#[error("No shared key was provided or obtained")]
|
||||
NoSharedKeyAvailable,
|
||||
|
||||
@@ -11,8 +11,7 @@ use crate::{
|
||||
use nym_api_requests::ecash::models::{
|
||||
AggregatedCoinIndicesSignatureResponse, AggregatedExpirationDateSignatureResponse,
|
||||
BatchRedeemTicketsBody, EcashBatchTicketRedemptionResponse, EcashTicketVerificationResponse,
|
||||
IssuedTicketbooksChallengeResponse, IssuedTicketbooksForResponse, SpentCredentialsResponse,
|
||||
VerifyEcashTicketBody,
|
||||
IssuedTicketbooksChallengeResponse, IssuedTicketbooksForResponse, VerifyEcashTicketBody,
|
||||
};
|
||||
use nym_api_requests::ecash::{
|
||||
BlindSignRequestBody, BlindedSignatureResponse, PartialCoinIndicesSignatureResponse,
|
||||
@@ -647,13 +646,6 @@ impl NymApiClient {
|
||||
.await?)
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
pub async fn spent_credentials_filter(
|
||||
&self,
|
||||
) -> Result<SpentCredentialsResponse, ValidatorClientError> {
|
||||
Ok(self.nym_api.double_spending_filter_v1().await?)
|
||||
}
|
||||
|
||||
pub async fn partial_expiration_date_signatures(
|
||||
&self,
|
||||
expiration_date: Option<Date>,
|
||||
|
||||
@@ -31,6 +31,7 @@ pub use nym_api_requests::{
|
||||
StakeSaturationResponse, UptimeResponse,
|
||||
},
|
||||
nym_nodes::{CachedNodesResponse, SkimmedNode},
|
||||
NymNetworkDetailsResponse,
|
||||
};
|
||||
pub use nym_coconut_dkg_common::types::EpochId;
|
||||
use nym_contracts_common::IdentityKey;
|
||||
@@ -849,20 +850,6 @@ pub trait NymApiClientExt: ApiClient {
|
||||
.await
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn double_spending_filter_v1(&self) -> Result<SpentCredentialsResponse, NymAPIError> {
|
||||
self.get_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::ECASH_ROUTES,
|
||||
routes::DOUBLE_SPENDING_FILTER_V1,
|
||||
],
|
||||
NO_PARAMS,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn partial_expiration_date_signatures(
|
||||
&self,
|
||||
@@ -1027,6 +1014,15 @@ pub trait NymApiClientExt: ApiClient {
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_network_details(&self) -> Result<NymNetworkDetailsResponse, NymAPIError> {
|
||||
self.get_json(
|
||||
&[routes::API_VERSION, routes::NETWORK, routes::DETAILS],
|
||||
NO_PARAMS,
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
|
||||
@@ -13,8 +13,6 @@ pub const DETAILED: &str = "detailed";
|
||||
pub const DETAILED_UNFILTERED: &str = "detailed-unfiltered";
|
||||
pub const ACTIVE: &str = "active";
|
||||
pub const REWARDED: &str = "rewarded";
|
||||
pub const DOUBLE_SPENDING_FILTER_V1: &str = "double-spending-filter-v1";
|
||||
|
||||
pub const ECASH_ROUTES: &str = "ecash";
|
||||
|
||||
pub use ecash::*;
|
||||
@@ -67,6 +65,8 @@ 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 PERFORMANCE: &str = "performance";
|
||||
|
||||
pub const SERVICE_PROVIDERS: &str = "services";
|
||||
|
||||
pub const DETAILS: &str = "details";
|
||||
pub const NETWORK: &str = "network";
|
||||
|
||||
@@ -8,81 +8,81 @@ use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug, PartialEq)]
|
||||
pub enum VestingContractError {
|
||||
#[error("VESTING ({}): {0}", line!())]
|
||||
#[error("VESTING ({l}): {0}", l = line!())]
|
||||
Std(#[from] StdError),
|
||||
|
||||
#[error("VESTING: {0}")]
|
||||
OverflowError(#[from] OverflowError),
|
||||
|
||||
#[error("VESTING ({}): Account does not exist - {0}", line!())]
|
||||
#[error("VESTING ({l}): Account does not exist - {0}", l = line!())]
|
||||
NoAccountForAddress(String),
|
||||
|
||||
#[error("VESTING ({}): Only admin can perform this action, {0} is not admin", line!())]
|
||||
#[error("VESTING ({l}): Only admin can perform this action, {0} is not admin", l = line!())]
|
||||
NotAdmin(String),
|
||||
|
||||
#[error("VESTING ({}): Balance not found for existing account ({0}), this is a bug", line!())]
|
||||
#[error("VESTING ({l}): Balance not found for existing account ({0}), this is a bug", l = line!())]
|
||||
NoBalanceForAddress(String),
|
||||
|
||||
#[error("VESTING ({}): Insufficient balance for address {0} -> {1}", line!())]
|
||||
#[error("VESTING ({l}): Insufficient balance for address {0} -> {1}", l = line!())]
|
||||
InsufficientBalance(String, u128),
|
||||
|
||||
#[error("VESTING ({}): Insufficient spendable balance for address {0} -> {1}", line!())]
|
||||
#[error("VESTING ({l}): Insufficient spendable balance for address {0} -> {1}", l = line!())]
|
||||
InsufficientSpendable(String, u128),
|
||||
|
||||
#[error(
|
||||
"VESTING ({}):Only delegation owner can perform delegation actions, {0} is not the delegation owner"
|
||||
, line!())]
|
||||
"VESTING ({l}):Only delegation owner can perform delegation actions, {0} is not the delegation owner"
|
||||
, l = line!())]
|
||||
NotDelegate(String),
|
||||
|
||||
#[error("VESTING ({}): Total vesting amount is inprobably low -> {0}, this is likely an error", line!())]
|
||||
#[error("VESTING ({l}): Total vesting amount is inprobably low -> {0}, this is likely an error", l = line!())]
|
||||
ImprobableVestingAmount(u128),
|
||||
|
||||
#[error("VESTING ({}): Address {0} has already bonded a node", line!())]
|
||||
#[error("VESTING ({l}): Address {0} has already bonded a node", l = line!())]
|
||||
AlreadyBonded(String),
|
||||
|
||||
#[error("VESTING ({}): Received empty funds vector", line!())]
|
||||
#[error("VESTING ({l}): Received empty funds vector", l = line!())]
|
||||
EmptyFunds,
|
||||
|
||||
#[error("VESTING ({}): Received wrong denom: {0}, expected {1}", line!())]
|
||||
#[error("VESTING ({l}): Received wrong denom: {0}, expected {1}", l = line!())]
|
||||
WrongDenom(String, String),
|
||||
|
||||
#[error("VESTING ({}): Received multiple denoms, expected 1", line!())]
|
||||
#[error("VESTING ({l}): Received multiple denoms, expected 1", l = line!())]
|
||||
MultipleDenoms,
|
||||
|
||||
#[error("VESTING ({}): No delegations found for account {0}, mix_identity {1}", line!())]
|
||||
#[error("VESTING ({l}): No delegations found for account {0}, mix_identity {1}", l = line!())]
|
||||
NoSuchDelegation(Addr, NodeId),
|
||||
|
||||
#[error("VESTING ({}): Only mixnet contract can perform this operation, got {0}", line!())]
|
||||
#[error("VESTING ({l}): Only mixnet contract can perform this operation, got {0}", l = line!())]
|
||||
NotMixnetContract(Addr),
|
||||
|
||||
#[error("VESTING ({}): Calculation underflowed", line!())]
|
||||
#[error("VESTING ({l}): Calculation underflowed", l = line!())]
|
||||
Underflow,
|
||||
|
||||
#[error("VESTING ({}): No bond found for account {0}", line!())]
|
||||
#[error("VESTING ({l}): No bond found for account {0}", l = line!())]
|
||||
NoBondFound(String),
|
||||
|
||||
#[error("VESTING: Attempted to reduce mixnode bond pledge below zero! The current pledge is {current} and we attempted to reduce it by {decrease_by}.")]
|
||||
InvalidBondPledgeReduction { current: Coin, decrease_by: Coin },
|
||||
|
||||
#[error("VESTING ({}): Action can only be executed by account owner -> {0}", line!())]
|
||||
#[error("VESTING ({l}): Action can only be executed by account owner -> {0}", l = line!())]
|
||||
NotOwner(String),
|
||||
|
||||
#[error("VESTING ({}): Invalid address: {0}", line!())]
|
||||
#[error("VESTING ({l}): Invalid address: {0}", l = line!())]
|
||||
InvalidAddress(String),
|
||||
|
||||
#[error("VESTING ({}): Account already exists: {0}", line!())]
|
||||
#[error("VESTING ({l}): Account already exists: {0}", l = line!())]
|
||||
AccountAlreadyExists(String),
|
||||
|
||||
#[error("VESTING ({}): Staking account already exists: {0}", line!())]
|
||||
#[error("VESTING ({l}): Staking account already exists: {0}", l = line!())]
|
||||
StakingAccountAlreadyExists(String),
|
||||
|
||||
#[error("VESTING ({}): Too few coins sent for vesting account creation, sent {sent}, need at least {need}", line!())]
|
||||
#[error("VESTING ({l}): Too few coins sent for vesting account creation, sent {sent}, need at least {need}", l = line!())]
|
||||
MinVestingFunds { sent: u128, need: u128 },
|
||||
|
||||
#[error("VESTING ({}): Maximum amount of locked coins has already been pledged: {current}, cap is {cap}", line!())]
|
||||
#[error("VESTING ({l}): Maximum amount of locked coins has already been pledged: {current}, cap is {cap}", l = line!())]
|
||||
LockedPledgeCapReached { current: Uint128, cap: Uint128 },
|
||||
|
||||
#[error("VESTING: ({}: Account owned by {owner} has unpopulated vesting periods!", line!())]
|
||||
#[error("VESTING: ({l}: Account owned by {owner} has unpopulated vesting periods!", l = line!())]
|
||||
UnpopulatedVestingPeriods { owner: Addr },
|
||||
|
||||
#[error("VESTING: Vesting account associated with {0} already exists, only addresses with not existing vesting accounts can be added as staking addresses")]
|
||||
|
||||
@@ -19,7 +19,7 @@ use std::error::Error;
|
||||
// `SELECT total_tickets, used_tickets FROM ecash_ticketbook WHERE expiration_date >= ?`, today_date
|
||||
// then for each calculate the diff total_tickets - used_tickets and multiply the result by the size of the ticket
|
||||
#[async_trait]
|
||||
pub trait Storage: Send + Sync {
|
||||
pub trait Storage: Clone + Send + Sync {
|
||||
type StorageError: Error;
|
||||
|
||||
async fn close(&self);
|
||||
|
||||
@@ -26,7 +26,6 @@ nym-api-requests = { path = "../../nym-api/nym-api-requests" }
|
||||
nym-credentials = { path = "../credentials" }
|
||||
nym-credentials-interface = { path = "../credentials-interface" }
|
||||
nym-ecash-contract-common = { path = "../cosmwasm-smart-contracts/ecash-contract" }
|
||||
nym-ecash-double-spending = { path = "../ecash-double-spending" }
|
||||
nym-gateway-requests = { path = "../gateway-requests" }
|
||||
nym-gateway-storage = { path = "../gateway-storage" }
|
||||
nym-task = { path = "../task" }
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
[package]
|
||||
name = "nym-ecash-double-spending"
|
||||
version = "0.1.0"
|
||||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
homepage.workspace = true
|
||||
documentation.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
bit-vec = { workspace = true }
|
||||
bloomfilter = { workspace = true }
|
||||
nym-network-defaults = { path = "../network-defaults" }
|
||||
@@ -1,136 +0,0 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use bit_vec::BitVec;
|
||||
use bloomfilter::Bloom;
|
||||
use nym_network_defaults::{BloomfilterParameters, ECASH_DS_BLOOMFILTER_PARAMS};
|
||||
|
||||
pub struct DoubleSpendingFilter {
|
||||
params: BloomfilterParameters,
|
||||
inner: Bloom<Vec<u8>>,
|
||||
}
|
||||
|
||||
impl Default for DoubleSpendingFilter {
|
||||
fn default() -> Self {
|
||||
DoubleSpendingFilter::new_empty_ecash()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bloom_from_params<T>(params: &BloomfilterParameters, bitvec: BitVec) -> Bloom<Vec<T>> {
|
||||
assert_eq!(params.bitmap_size, bitvec.len() as u64);
|
||||
|
||||
Bloom::from_bit_vec(
|
||||
bitvec,
|
||||
params.bitmap_size,
|
||||
params.num_hashes,
|
||||
params.sip_keys,
|
||||
)
|
||||
}
|
||||
|
||||
impl DoubleSpendingFilter {
|
||||
pub fn new_empty(params: BloomfilterParameters) -> Self {
|
||||
let bitvec = BitVec::from_elem(params.bitmap_size as usize, false);
|
||||
DoubleSpendingFilter {
|
||||
inner: bloom_from_params(¶ms, bitvec),
|
||||
params,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn params(&self) -> BloomfilterParameters {
|
||||
self.params
|
||||
}
|
||||
|
||||
pub fn rebuild(&self) -> DoubleSpendingFilterBuilder {
|
||||
DoubleSpendingFilterBuilder::new(self.params)
|
||||
}
|
||||
|
||||
pub fn reset(&mut self) {
|
||||
self.inner.clear()
|
||||
}
|
||||
|
||||
pub fn new_empty_ecash() -> Self {
|
||||
DoubleSpendingFilter::new_empty(ECASH_DS_BLOOMFILTER_PARAMS)
|
||||
}
|
||||
|
||||
pub fn builder(params: BloomfilterParameters) -> DoubleSpendingFilterBuilder {
|
||||
DoubleSpendingFilterBuilder::new(params)
|
||||
}
|
||||
|
||||
pub fn from_bytes(params: BloomfilterParameters, bitmap: &[u8]) -> Self {
|
||||
DoubleSpendingFilter {
|
||||
inner: bloom_from_params(¶ms, BitVec::from_bytes(bitmap)),
|
||||
params,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn replace_bitvec(&mut self, new: BitVec) {
|
||||
self.inner = bloom_from_params(&self.params, new)
|
||||
}
|
||||
|
||||
pub fn dump_bitmap(&self) -> Vec<u8> {
|
||||
self.inner.bitmap()
|
||||
}
|
||||
|
||||
pub fn set(&mut self, b: &Vec<u8>) {
|
||||
self.inner.set(b);
|
||||
}
|
||||
|
||||
pub fn check(&self, b: &Vec<u8>) -> bool {
|
||||
self.inner.check(b)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DoubleSpendingFilterBuilder {
|
||||
params: BloomfilterParameters,
|
||||
bit_vec_builder: Option<BitVecBuilder>,
|
||||
}
|
||||
|
||||
impl DoubleSpendingFilterBuilder {
|
||||
pub fn new(params: BloomfilterParameters) -> Self {
|
||||
DoubleSpendingFilterBuilder {
|
||||
params,
|
||||
bit_vec_builder: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_bytes(&mut self, b: &[u8]) -> bool {
|
||||
match &mut self.bit_vec_builder {
|
||||
None => {
|
||||
self.bit_vec_builder = Some(BitVecBuilder::new(b));
|
||||
true
|
||||
}
|
||||
Some(builder) => builder.add_bytes(b),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build(self) -> DoubleSpendingFilter {
|
||||
match self.bit_vec_builder {
|
||||
None => DoubleSpendingFilter::new_empty(self.params),
|
||||
Some(builder) => DoubleSpendingFilter {
|
||||
inner: bloom_from_params(&self.params, builder.finish()),
|
||||
params: self.params,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct BitVecBuilder(BitVec);
|
||||
|
||||
impl BitVecBuilder {
|
||||
pub fn new(initial_bitmap: &[u8]) -> Self {
|
||||
BitVecBuilder(BitVec::from_bytes(initial_bitmap))
|
||||
}
|
||||
|
||||
pub fn add_bytes(&mut self, b: &[u8]) -> bool {
|
||||
let add = BitVec::from_bytes(b);
|
||||
if self.0.len() != add.len() {
|
||||
return false;
|
||||
}
|
||||
self.0.or(&add);
|
||||
true
|
||||
}
|
||||
|
||||
pub fn finish(self) -> BitVec {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
@@ -59,12 +59,12 @@ pub enum GatewayRequestsError {
|
||||
source: NymNodeRoutingAddressError,
|
||||
},
|
||||
|
||||
#[error("received request had invalid size. (actual: {0}, but expected one of: {} (ACK), {} (REGULAR), {}, {}, {} (EXTENDED))",
|
||||
PacketSize::AckPacket.size(),
|
||||
PacketSize::RegularPacket.size(),
|
||||
PacketSize::ExtendedPacket8.size(),
|
||||
PacketSize::ExtendedPacket16.size(),
|
||||
PacketSize::ExtendedPacket32.size())
|
||||
#[error("received request had invalid size. (actual: {0}, but expected one of: {a} (ACK), {r} (REGULAR), {e8}, {e16}, {e32} (EXTENDED))",
|
||||
a = PacketSize::AckPacket.size(),
|
||||
r = PacketSize::RegularPacket.size(),
|
||||
e8 = PacketSize::ExtendedPacket8.size(),
|
||||
e16 = PacketSize::ExtendedPacket16.size(),
|
||||
e32 = PacketSize::ExtendedPacket32.size())
|
||||
]
|
||||
RequestOfInvalidSize(usize),
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ use std::str::FromStr;
|
||||
use tungstenite::Message;
|
||||
|
||||
// wrapper for all encrypted requests for ease of use
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
#[non_exhaustive]
|
||||
pub enum ClientRequest {
|
||||
UpgradeKey {
|
||||
|
||||
@@ -15,6 +15,7 @@ async-trait = { workspace = true }
|
||||
reqwest = { workspace = true, features = ["json"] }
|
||||
http.workspace = true
|
||||
url = { workspace = true }
|
||||
once_cell = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
@@ -22,7 +23,13 @@ tracing = { workspace = true }
|
||||
|
||||
nym-bin-common = { path = "../bin-common" }
|
||||
|
||||
[target."cfg(not(target_arch = \"wasm32\"))".dependencies]
|
||||
hickory-resolver = { workspace = true, features = ["dns-over-https-rustls", "webpki-roots"] }
|
||||
|
||||
# for request timeout until https://github.com/seanmonstar/reqwest/issues/1135 is fixed
|
||||
[target."cfg(target_arch = \"wasm32\")".dependencies.wasmtimer]
|
||||
workspace = true
|
||||
features = ["tokio"]
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { workspace = true, features=["rt", "macros"] }
|
||||
|
||||
@@ -0,0 +1,178 @@
|
||||
//! DNS resolver configuration for internal lookups.
|
||||
//!
|
||||
//! The resolver itself is the set combination of the google, cloudflare, and quad9 endpoints
|
||||
//! supporting DoH and DoT.
|
||||
//!
|
||||
//! This resolver implements a fallback mechanism where, should the DNS-over-TLS resolution fail, a
|
||||
//! followup resolution will be done using the hosts configured default (e.g. `/etc/resolve.conf` on
|
||||
//! linux).
|
||||
//!
|
||||
//! Requires the `dns-over-https-rustls`, `webpki-roots` feature for the
|
||||
//! `hickory-resolver` crate
|
||||
#![deny(missing_docs)]
|
||||
|
||||
use crate::ClientBuilder;
|
||||
|
||||
use std::{net::SocketAddr, sync::Arc};
|
||||
|
||||
use hickory_resolver::lookup_ip::LookupIp;
|
||||
use hickory_resolver::{
|
||||
config::{LookupIpStrategy, NameServerConfigGroup, ResolverConfig, ResolverOpts},
|
||||
error::ResolveError,
|
||||
lookup_ip::LookupIpIntoIter,
|
||||
TokioAsyncResolver,
|
||||
};
|
||||
use once_cell::sync::OnceCell;
|
||||
use reqwest::dns::{Addrs, Name, Resolve, Resolving};
|
||||
use tracing::warn;
|
||||
|
||||
impl ClientBuilder {
|
||||
/// Override the DNS resolver implementation used by the underlying http client.
|
||||
pub fn dns_resolver<R: Resolve + 'static>(mut self, resolver: Arc<R>) -> Self {
|
||||
self.reqwest_client_builder = self.reqwest_client_builder.dns_resolver(resolver);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
struct SocketAddrs {
|
||||
iter: LookupIpIntoIter,
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[error("hickory-dns resolver error: {hickory_error}")]
|
||||
/// Error occurring while resolving a hostname into an IP address.
|
||||
pub struct HickoryDnsError {
|
||||
#[from]
|
||||
hickory_error: ResolveError,
|
||||
}
|
||||
|
||||
/// Wrapper around an `AsyncResolver`, which implements the `Resolve` trait.
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct HickoryDnsResolver {
|
||||
/// Since we might not have been called in the context of a
|
||||
/// Tokio Runtime in initialization, so we must delay the actual
|
||||
/// construction of the resolver.
|
||||
state: Arc<OnceCell<TokioAsyncResolver>>,
|
||||
fallback: Arc<OnceCell<TokioAsyncResolver>>,
|
||||
}
|
||||
|
||||
impl Resolve for HickoryDnsResolver {
|
||||
fn resolve(&self, name: Name) -> Resolving {
|
||||
let resolver = self.state.clone();
|
||||
let fallback = self.fallback.clone();
|
||||
Box::pin(async move {
|
||||
let resolver = resolver.get_or_try_init(new_resolver)?;
|
||||
|
||||
// try the primary DNS resolver that we set up (DoH or DoT or whatever)
|
||||
let lookup = match resolver.lookup_ip(name.as_str()).await {
|
||||
Ok(res) => res,
|
||||
Err(e) => {
|
||||
// on failure use the fall back system configured DNS resolver
|
||||
warn!("primary DNS failed w/ error {e}: using system fallback");
|
||||
let resolver = fallback.get_or_try_init(new_resolver_system)?;
|
||||
resolver.lookup_ip(name.as_str()).await?
|
||||
}
|
||||
};
|
||||
|
||||
let addrs: Addrs = Box::new(SocketAddrs {
|
||||
iter: lookup.into_iter(),
|
||||
});
|
||||
Ok(addrs)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for SocketAddrs {
|
||||
type Item = SocketAddr;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.iter.next().map(|ip_addr| SocketAddr::new(ip_addr, 0))
|
||||
}
|
||||
}
|
||||
|
||||
impl HickoryDnsResolver {
|
||||
/// Attempt to resolve a domain name to a set of ['IpAddr']s
|
||||
pub async fn resolve_str(&self, name: &str) -> Result<LookupIp, HickoryDnsError> {
|
||||
let resolver = self.state.get_or_try_init(new_resolver)?;
|
||||
|
||||
// try the primary DNS resolver that we set up (DoH or DoT or whatever)
|
||||
let lookup = match resolver.lookup_ip(name).await {
|
||||
Ok(res) => res,
|
||||
Err(e) => {
|
||||
// on failure use the fall back system configured DNS resolver
|
||||
warn!("primary DNS failed w/ error {e}: using system fallback");
|
||||
let resolver = self.fallback.get_or_try_init(new_resolver_system)?;
|
||||
resolver.lookup_ip(name).await?
|
||||
}
|
||||
};
|
||||
|
||||
Ok(lookup)
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new resolver with a custom DoT based configuration. The options are overridden to look
|
||||
/// up for both IPv4 and IPv6 addresses to work with "happy eyeballs" algorithm.
|
||||
fn new_resolver() -> Result<TokioAsyncResolver, HickoryDnsError> {
|
||||
let mut name_servers = NameServerConfigGroup::google_tls();
|
||||
name_servers.merge(NameServerConfigGroup::google_https());
|
||||
// name_servers.merge(NameServerConfigGroup::google_h3());
|
||||
name_servers.merge(NameServerConfigGroup::quad9_tls());
|
||||
name_servers.merge(NameServerConfigGroup::quad9_https());
|
||||
name_servers.merge(NameServerConfigGroup::cloudflare_tls());
|
||||
name_servers.merge(NameServerConfigGroup::cloudflare_https());
|
||||
|
||||
let config = ResolverConfig::from_parts(None, Vec::new(), name_servers);
|
||||
|
||||
let mut opts = ResolverOpts::default();
|
||||
opts.ip_strategy = LookupIpStrategy::Ipv4AndIpv6;
|
||||
// Would like to enable this when 0.25 stabilizes
|
||||
// opts.server_ordering_strategy = ServerOrderingStrategy::RoundRobin;
|
||||
|
||||
Ok(TokioAsyncResolver::tokio(config, opts))
|
||||
}
|
||||
|
||||
/// Create a new resolver with the default configuration, which reads from the system DNS config
|
||||
/// (i.e. `/etc/resolve.conf` in unix). The options are overridden to look up for both IPv4 and IPv6
|
||||
/// addresses to work with "happy eyeballs" algorithm.
|
||||
fn new_resolver_system() -> Result<TokioAsyncResolver, HickoryDnsError> {
|
||||
let (config, mut opts) = hickory_resolver::system_conf::read_system_conf()?;
|
||||
opts.ip_strategy = LookupIpStrategy::Ipv4AndIpv6;
|
||||
Ok(TokioAsyncResolver::tokio(config, opts))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn reqwest_hickory_doh() {
|
||||
let resolver = HickoryDnsResolver::default();
|
||||
let client = reqwest::ClientBuilder::new()
|
||||
.dns_resolver(resolver.into())
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let resp = client
|
||||
.get("http://ifconfig.me:80")
|
||||
.send()
|
||||
.await
|
||||
.unwrap()
|
||||
.bytes()
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert!(!resp.is_empty());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn dns_lookup() -> Result<(), HickoryDnsError> {
|
||||
let resolver = HickoryDnsResolver::default();
|
||||
|
||||
let domain = "ifconfig.me";
|
||||
let addrs = resolver.resolve_str(domain).await?;
|
||||
|
||||
assert!(addrs.into_iter().next().is_some());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -6,17 +6,23 @@ use reqwest::header::HeaderValue;
|
||||
use reqwest::{RequestBuilder, Response, StatusCode};
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::Display;
|
||||
use std::time::Duration;
|
||||
use thiserror::Error;
|
||||
use tracing::{instrument, warn};
|
||||
use url::Url;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use std::sync::Arc;
|
||||
use std::{fmt::Display, time::Duration};
|
||||
|
||||
pub use reqwest::IntoUrl;
|
||||
|
||||
mod user_agent;
|
||||
pub use user_agent::UserAgent;
|
||||
|
||||
mod user_agent;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
mod dns;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub use dns::{HickoryDnsError, HickoryDnsResolver};
|
||||
|
||||
// The timeout is relatively high as we are often making requests over the mixnet, where latency is
|
||||
// high and chatty protocols take a while to complete.
|
||||
@@ -86,11 +92,18 @@ impl ClientBuilder {
|
||||
// TODO: or should we maybe default to https?
|
||||
Self::new(alt)
|
||||
} else {
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let reqwest_client_builder = reqwest::ClientBuilder::new();
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let reqwest_client_builder =
|
||||
reqwest::ClientBuilder::new().dns_resolver(Arc::new(HickoryDnsResolver::default()));
|
||||
|
||||
Ok(ClientBuilder {
|
||||
url: url.into_url()?,
|
||||
timeout: None,
|
||||
custom_user_agent: false,
|
||||
reqwest_client_builder: reqwest::ClientBuilder::new(),
|
||||
reqwest_client_builder,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -315,7 +315,7 @@ impl TaskClient {
|
||||
const MAX_NAME_LENGTH: usize = 128;
|
||||
const OVERFLOW_NAME: &'static str = "reached maximum TaskClient children name depth";
|
||||
|
||||
const SHUTDOWN_TIMEOUT_WAITING_FOR_SIGNAL_ON_EXIT: Duration = Duration::from_secs(5);
|
||||
const SHUTDOWN_TIMEOUT_WAITING_FOR_SIGNAL_ON_EXIT: Duration = Duration::from_secs(10);
|
||||
|
||||
fn new(
|
||||
notify: watch::Receiver<()>,
|
||||
|
||||
@@ -11,7 +11,7 @@ use std::borrow::Borrow;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::fmt::Display;
|
||||
use std::net::IpAddr;
|
||||
use tracing::{debug, warn};
|
||||
use tracing::{debug, trace, warn};
|
||||
|
||||
pub use crate::node::{EntryDetails, RoutingNode, SupportedRoles};
|
||||
pub use error::NymTopologyError;
|
||||
@@ -293,7 +293,7 @@ impl NymTopology {
|
||||
let has_exit_gateways = !self.rewarded_set.exit_gateways.is_empty();
|
||||
let has_entry_gateways = !self.rewarded_set.entry_gateways.is_empty();
|
||||
|
||||
debug!(
|
||||
trace!(
|
||||
has_layer1 = %has_layer1,
|
||||
has_layer2 = %has_layer2,
|
||||
has_layer3 = %has_layer3,
|
||||
|
||||
+1
@@ -163,3 +163,4 @@ Another command is `version_count` where at least one `nym-node` version is requ
|
||||
```sh
|
||||
./node_api_check version_count 1.1.0 1.1.1 1.1.2 1.1.3 --markdown
|
||||
```
|
||||
|
||||
@@ -11,22 +11,38 @@ Tor community created a very helpful table called [*Good Bad ISPs*](https://comm
|
||||
|
||||
<div>
|
||||
<Tabs items={[
|
||||
<code>nym-node</code>,
|
||||
<><code>nym-node</code> - dedicated server</>,
|
||||
<><code>nym-node</code> - VPS</>,
|
||||
<code>validator</code>,
|
||||
]} defaultIndex="0">
|
||||
<MyTab>
|
||||
#### `nym-node`
|
||||
Before we conclude the testing with exact results, these are the rough specs:
|
||||
#### `nym-node` - dedicated server
|
||||
If you are about to start a new Nym Node, look for a server with this specification:
|
||||
|
||||
| **Hardware** | **Minimum Specification** |
|
||||
| :--- | ---: |
|
||||
| CPU Cores | 4 |
|
||||
| Memory | 8 GB RAM |
|
||||
| Storage | 80 GB |
|
||||
| Connectivity | IPv4, IPv6, TCP/IP, UDP |
|
||||
| Bandwidth | > 1Tb |
|
||||
| Port speed | 1Gbps |
|
||||
| **Hardware** | **Minimum Specification** |
|
||||
| :--- | ---: |
|
||||
| Type of server | Dedicated or bare metal |
|
||||
| CPU Cores | 4 |
|
||||
| Memory | 8-16 GB RAM |
|
||||
| Storage | 50 GB (preferably 80 GB) |
|
||||
| Connectivity | IPv4, IPv6, TCP/IP, UDP |
|
||||
| Bandwidth | Unmetered if possible, or \> 20Tb |
|
||||
| Port speed | 1Gbps |
|
||||
|
||||
</MyTab>
|
||||
<MyTab>
|
||||
#### `nym-node` - VPS
|
||||
If you are about to start a new Nym Node, look for a server with this specification:
|
||||
|
||||
| **Hardware** | **Minimum Specification** |
|
||||
| :--- | ---: |
|
||||
| Type of server | VPS |
|
||||
| CPU Cores | 8 |
|
||||
| Memory | 16-32 GB RAM |
|
||||
| Storage | 50 GB (preferably 80 GB) |
|
||||
| Connectivity | IPv4, IPv6, TCP/IP, UDP |
|
||||
| Bandwidth | Unmetered if possible, or \> 40Tb |
|
||||
| Port speed | 10Gbps |
|
||||
</MyTab>
|
||||
<MyTab>
|
||||
#### Nyx validator
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
```
|
||||
Thank you for forwarding me the notice you received from <COPYRIGHT_CLAIMANT> regarding <CONTENT>. I would like to assure you that I am not hosting the claimed infringing materials, and furthermore, the Digital Millennium Copyright Act's ("DMCA") safe harbors likely protect you from liability arising from this complaint. The notice is likely based upon misunderstandings about the law and about some of the software I run.
|
||||
|
||||
As you know, the DMCA creates four "safe harbors" for service providers to protect them from copyright liability for the acts of their users, when the ISPs fulfill certain requirements. (17 U.S.C. 512)
|
||||
|
||||
The DMCA's requirements vary depending on the ISP's role. You may be familiar with the "notice and takedown" provisions of section 512(c) of the DMCA; however, those do not apply when an ISP merely acts as a conduit.
|
||||
Instead, the "conduit" safe harbor of section 512(a) of the DMCA has different and less burdensome eligibility requirements, as the D.C. Circuit Court of Appeals held in RIAA v. Verizon (see https://scholar.google.com/scholar_case?case=15815830240179540527) and the Eighth Circuit Court of Appeals confirmed in RIAA v. Charter (see https://scholar.google.com/scholar_case?case=11547531128234336420).
|
||||
|
||||
Under DMCA 512(a), service providers like you are typically protected from damages for copyright infringement claims if you also maintain "a policy that provides for termination in appropriate circumstances of subscribers and account holders of the service provider's system or network who are repeat infringers."
|
||||
|
||||
If you have and implement such a policy, and you otherwise qualify for the safe harbor, you should be free from fear of copyright damages.
|
||||
|
||||
The copyright notice you received was likely triggered by a program I run called Nym. Nym is a network software that helps users to enhance their privacy, security, and safety online. It does not host any content. Rather, it is part of a network of nodes on the Internet that simply pass packets among themselves before sending them to their destinations, just as any Internet intermediary does.
|
||||
|
||||
The difference is that Nym tunnels the connections such that no hop can learn both the source and destination of the packets, giving users protection from nefarious snooping on network traffic. The result is that, unlike most other Internet traffic, the final IP address that the recipient receives is not the IP address of the sender. Nym protects users against hazards such as harassment, spam, and identity theft.
|
||||
|
||||
Nym was conceived during the EU-funded project 'Panoramix' R&D project (Horizon 2020 programme) with the goal of protecting communication privacy by building a comprehensive mixnet infrastructure. This project has received funding from the European Union’s Horizon 2020 research and innovation programme under the Grant Agreement No 653497, "Privacy and Accountability in Networks via Optimized Randomized Mix-nets (Panoramix)”. (For more on Nym, see https://www.nym.com/, For more on Panoramix, see https://panoramix.me/ ). Nym has continued this work by building a decentralized routing, authentication and payment protocol. Nym will enable developers to build their own sustainable privacy-enhanced services on top of its platform.
|
||||
|
||||
I hope, as an organization committed to protecting the privacy of its customers, you'll agree that this is a valuable technology.
|
||||
|
||||
While the Nym node that I run may appear to be the source of material that is alleged to be copyright-infringing, I do not host that material.
|
||||
|
||||
I do not select the material transmitted through the Nym node that I run, and I have no practical means of either identifying the source of such material or preventing its transmission. I do nothing to encourage or promote the use of the Nym network for copyright infringement.
|
||||
|
||||
For these reasons, I am not an infringer of copyright in any materials that are transmitted through the Nym node that I run, either directly or under a theory of contributory or vicarious liability.
|
||||
|
||||
Therefore, you should continue to be protected under the DMCA 512(a) safe harbor without taking any further action.
|
||||
|
||||
Thank you for working with me on this matter. As a loyal subscriber, I appreciate your notifying me of this issue and hope that the protections of DMCA 512 put any concerns you may have to rest.
|
||||
|
||||
If not, please contact me with any further questions.
|
||||
|
||||
Very truly yours,
|
||||
|
||||
Your customer, <YOUR_NAME/PSEUDONYM>
|
||||
```
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
```
|
||||
I am reaching out to introduce myself. I am about to spin up a machine with you to run what is called a “nym node” - think of it as somewhat similar to a Tor exit node. You can always recognize a nym node by our domain names: nym-exit
|
||||
|
||||
Nym node runners are a decentralized community all over the world. We provide secure internet traffic routing services and privacy protection to ordinary people and businesses via the Nym platform and NymVPN app.
|
||||
|
||||
The Nym traffic pattern is somewhat unique, as we route traffic using the “sphinx” packet format (similar to Tor 's onion encryption), which makes all traffic look uniform. I wanted to introduce myself and the machines I am running just so that this unique traffic pattern doesn’t raise any flags or issues with you!
|
||||
|
||||
We have the highest security standards, which also means I simply relay traffic and do not know the end-destination nor its content. I can not be selective about the material transmitted through the Nym node that I run, and I have no practical means of either identifying the source of such material or preventing its transmission. More information can be found at https://nym.com.
|
||||
|
||||
Feel free to ask any questions.
|
||||
|
||||
Many thanks,
|
||||
|
||||
<YOUR_NAME/PSEUDONYM>
|
||||
```
|
||||
+1
-1
@@ -1 +1 @@
|
||||
805_903_308
|
||||
807_251_217
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
44.811
|
||||
44.332
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
1_022_821
|
||||
1_025_628
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
402_951_654
|
||||
403_625_608
|
||||
|
||||
+4
-4
@@ -1,7 +1,7 @@
|
||||
| **Item** | **Description** | **Amount in NYM** |
|
||||
|:-------------------|:------------------------------------------------------|--------------------:|
|
||||
| Total Supply | Maximum amount of NYM token in existence | 1_000_000_000 |
|
||||
| Mixmining Reserve | Tokens releasing for operators rewards | 194_096_191 |
|
||||
| Vesting Tokens | Tokens locked outside of cicrulation for future claim | 500 |
|
||||
| Circulating Supply | Amount of unlocked tokens | 805_903_308 |
|
||||
| Stake Saturation | Optimal size of node self-bond + delegation | 1_022_821 |
|
||||
| Mixmining Reserve | Tokens releasing for operators rewards | 192_748_782 |
|
||||
| Vesting Tokens | Tokens locked outside of cicrulation for future claim | 0 |
|
||||
| Circulating Supply | Amount of unlocked tokens | 807_251_217 |
|
||||
| Stake Saturation | Optimal size of node self-bond + delegation | 1_025_628 |
|
||||
|
||||
@@ -1 +1 @@
|
||||
Thursday, January 16th 2025, 09:57:52 UTC
|
||||
Monday, February 3rd 2025, 13:47:19 UTC
|
||||
|
||||
@@ -497,13 +497,6 @@ const config = {
|
||||
permanent: true,
|
||||
basePath: false,
|
||||
},
|
||||
{
|
||||
source: "/operators/testing/node-api-check.html",
|
||||
destination:
|
||||
"/docs/operators/nodes/performance-and-testing/node-api-check",
|
||||
permanent: true,
|
||||
basePath: false,
|
||||
},
|
||||
{
|
||||
source: "/operators/testing/prometheus-grafana.html",
|
||||
destination:
|
||||
@@ -663,13 +656,6 @@ const config = {
|
||||
permanent: true,
|
||||
basePath: false,
|
||||
},
|
||||
{
|
||||
source: "/docs/operators/testing/node-api-check",
|
||||
destination:
|
||||
"/docs/operators/nodes/performance-and-testing/node-api-check",
|
||||
permanent: true,
|
||||
basePath: false,
|
||||
},
|
||||
{
|
||||
source: "/docs/operators/nodes/proxy-configuration",
|
||||
destination:
|
||||
|
||||
@@ -4,6 +4,8 @@ The information below is generated with [Redoc](https://redocly.com/docs/redoc)
|
||||
|
||||
There is also an overview of other Validator endpoints at [https://cosmos.directory/nyx](https://cosmos.directory/nyx).
|
||||
|
||||
<br /><br />
|
||||
|
||||
<RedocStandalone
|
||||
specUrl="https://api.nymtech.net/swagger/swagger.yaml"
|
||||
options={{
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { RedocStandalone } from 'redoc';
|
||||
|
||||
The information below is generated with [Redoc](https://redocly.com/docs/redoc) consuming the OpenAPI spec found at []() which is also used to generate the Swagger docs deployed at []().
|
||||
The information below is generated with [Redoc](https://redocly.com/docs/redoc) consuming the OpenAPI spec found at [https://api.sandbox.nymtech.net/swagger/swagger.yaml](https://api.sandbox.nymtech.net/swagger/swagger.yaml).
|
||||
|
||||
<br /><br />
|
||||
|
||||
<RedocStandalone
|
||||
specUrl=""
|
||||
specUrl="https://api.sandbox.nymtech.net/swagger/swagger.yaml"
|
||||
options={{
|
||||
nativeScrollbars: true,
|
||||
theme: {
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
|
||||
The Explorer API is the backend for the [Mixnet Explorer](https://explorer.nymtech.net/).
|
||||
|
||||
**This will soon be deprecated in favour of the [Node Status API](../ns-api).**
|
||||
**This will soon be deprecated in favour of the [Node Status API](ns-api.mdx).**
|
||||
|
||||
The code for this service can be found [in our monorepo](https://github.com/nymtech/nym/tree/develop/explorer).
|
||||
|
||||
@@ -3,6 +3,8 @@ import { RedocStandalone } from 'redoc';
|
||||
|
||||
The information below is generated with [Redoc](https://redocly.com/docs/redoc) consuming the OpenAPI spec found at [https://explorer.nymtech.net/api/v1/openapi.json](https://explorer.nymtech.net/api/v1/openapi.json) which is also used to generate the Swagger docs deployed at [https://explorer.nymtech.net/api/swagger/index.html](https://explorer.nymtech.net/api/swagger/index.html).
|
||||
|
||||
<br /><br />
|
||||
|
||||
<RedocStandalone
|
||||
specUrl="https://sandbox-explorer.nymtech.net/api/v1/openapi.json"
|
||||
options={{
|
||||
|
||||
@@ -2,6 +2,8 @@ import { RedocStandalone } from 'redoc';
|
||||
|
||||
The information below is generated with [Redoc](https://redocly.com/docs/redoc) consuming the OpenAPI spec found at [https://sandbox-explorer.nymtech.net/api/v1/openapi.json](https://sandbox-explorer.nymtech.net/api/v1/openapi.json) which is also used to generate the Swagger docs deployed at [https://sandbox-explorer.nymtech.net/api/swagger/index.html](https://sandbox-explorer.nymtech.net/api/swagger/index.html).
|
||||
|
||||
<br /><br />
|
||||
|
||||
<RedocStandalone
|
||||
specUrl="https://sandbox-explorer.nymtech.net/api/v1/openapi.json"
|
||||
options={{
|
||||
|
||||
@@ -2,6 +2,8 @@ import { RedocStandalone } from 'redoc';
|
||||
|
||||
The information below is generated with [Redoc](https://redocly.com/docs/redoc) consuming the OpenAPI spec found at [https://mainnet-node-status-api.nymtech.cc/api-docs/openapi.json](https://mainnet-node-status-api.nymtech.cc/api-docs/openapi.json) which is also used to generate the Swagger docs deployed at [https://mainnet-node-status-api.nymtech.cc/swagger/](https://mainnet-node-status-api.nymtech.cc/swagger/).
|
||||
|
||||
<br /><br />
|
||||
|
||||
<RedocStandalone
|
||||
specUrl="https://mainnet-node-status-api.nymtech.cc/api-docs/openapi.json"
|
||||
options={{
|
||||
|
||||
@@ -2,6 +2,8 @@ import { RedocStandalone } from 'redoc';
|
||||
|
||||
The information below is generated with [Redoc](https://redocly.com/docs/redoc) consuming the OpenAPI spec found at [https://sandbox-node-status-api.nymte.ch/api-docs/openapi.json](https://sandbox-node-status-api.nymte.ch/api-docs/openapi.json) which is also used to generate the Swagger docs deployed at [https://sandbox-node-status-api.nymte.ch/swagger/](https://sandbox-node-status-api.nymte.ch/swagger/).
|
||||
|
||||
<br /><br />
|
||||
|
||||
<RedocStandalone
|
||||
specUrl="https://sandbox-node-status-api.nymte.ch/api-docs/openapi.json"
|
||||
options={{
|
||||
|
||||
@@ -1,10 +1,27 @@
|
||||
import APITable from 'components/api-table.tsx';
|
||||
import { RedocStandalone } from 'redoc';
|
||||
import { Callout } from 'nextra/components'
|
||||
|
||||
You can find the OpenAPI spec found at [https://validator.nymtech.net/api-docs/openapi.json](https://validator.nymtech.net/api-docs/openapi.json) which is also used to generate the Swagger docs deployed at [https://validator.nymtech.net/api/swagger/index.html](https://validator.nymtech.net/api/swagger/index.html).
|
||||
|
||||
<Callout type="info" emoji="ℹ️">
|
||||
You can find the Swagger endpoints of other NymAPI instances at the following endpoints:
|
||||
<APITable endpoint="https://api.nymtech.net/cosmwasm/wasm/v1/contract/n19604yflqggs9mk2z26mqygq43q2kr3n932egxx630svywd5mpxjsztfpvx/smart/eyJnZXRfY3VycmVudF9kZWFsZXJzIjogeyJsaW1pdCI6IDMwfX0=" />
|
||||
|
||||
There is also an overview of endpoints at [https://cosmos.directory/nyx](https://cosmos.directory/nyx).
|
||||
</Callout>
|
||||
|
||||
> Interactive Redoc component coming soon
|
||||
<br /><br />
|
||||
|
||||
<RedocStandalone
|
||||
specUrl="https://validator.nymtech.net/api-docs/openapi.json"
|
||||
options={{
|
||||
nativeScrollbars: true,
|
||||
theme: {
|
||||
sidebar: {
|
||||
backgroundColor: '#273239',
|
||||
textColor: '#FCFDFE'
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -16,7 +16,7 @@ You can check that your binaries are properly compiled with:
|
||||
You can check the necessary parameters for the available commands by running:
|
||||
|
||||
```
|
||||
./nym-client <COMMAND> --help
|
||||
./nym-socks5-client <COMMAND> --help
|
||||
```
|
||||
|
||||
## Initialising a new client instance
|
||||
|
||||
@@ -2,6 +2,3 @@
|
||||
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.
|
||||
|
||||
### Generate Crate Docs
|
||||
In order to generate the crate docs run `cargo doc --open` from `nym/sdk/rust/nym-sdk/`
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
The `nym-cli` binary can be built by running `cargo build --release` in the `nym/tools/nym-cli` directory.
|
||||
|
||||
## Usage
|
||||
See the [commands](./nym-cli/commands.mdx) page for an overview of all command options.
|
||||
See the [commands](commands.mdx) page for an overview of all command options.
|
||||
|
||||
## Staking on someone's behalf (for custodians)
|
||||
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import { Callout } from 'nextra/components'
|
||||
|
||||
# Cosmos Kit
|
||||
|
||||
<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
|
||||
Nym. These include:
|
||||
|
||||
@@ -17,7 +23,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
|
||||
```
|
||||
|
||||
@@ -57,7 +63,7 @@ export default defineConfig({
|
||||
Your components have to be wrapped into a [ChainProvider](https://docs.cosmoskit.com/chain-provider),
|
||||
in order to use the `useChain('nyx')` hook. The nyx chain is provided in the 'chain-registry' NPM package by default.
|
||||
|
||||
Now, go to the `src` folder and open your `App.tsx` file to replace all the code with the following, which will allow you to connect and disconnect a Ledger or Keplr wallet to Nyx:
|
||||
Now, go to the `src` folder and open your `App.tsx` file to replace all the code with the following, which will allow you to connect and disconnect a Ledger or Keplr wallet to Nyx:
|
||||
|
||||
```ts
|
||||
import "./App.css";
|
||||
@@ -68,7 +74,7 @@ import { wallets as ledger } from '@cosmos-kit/ledger';
|
||||
import { wallets as keplr } from '@cosmos-kit/keplr';
|
||||
import { AminoMsg, makeSignDoc } from '@cosmjs/amino';
|
||||
import { MsgSend } from 'cosmjs-types/cosmos/bank/v1beta1/tx';
|
||||
|
||||
|
||||
export const getDoc = (address: string) => {
|
||||
const chainId = 'nyx';
|
||||
const msg: AminoMsg = {
|
||||
@@ -89,28 +95,28 @@ export const getDoc = (address: string) => {
|
||||
const doc = makeSignDoc([msg], fee, chainId, memo, accountNumber, sequence);
|
||||
return doc
|
||||
};
|
||||
|
||||
|
||||
function MyComponent() {
|
||||
const {wallet, address, connect, disconnect, getOfflineSignerAmino } =
|
||||
useChain('nyx');
|
||||
|
||||
|
||||
React.useEffect(() => {
|
||||
connect();
|
||||
disconnect();
|
||||
}, []);
|
||||
|
||||
|
||||
const sign = async () => {
|
||||
if (!address) return
|
||||
const doc = getDoc(address);
|
||||
return getOfflineSignerAmino().signAmino(address, doc);
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div>
|
||||
<div>
|
||||
{wallet &&
|
||||
<div>
|
||||
<div>Connected to {wallet?.prettyName} </div>
|
||||
{wallet &&
|
||||
<div>
|
||||
<div>Connected to {wallet?.prettyName} </div>
|
||||
<div>Address: <code>{address}</code></div>
|
||||
</div>}
|
||||
</div>
|
||||
@@ -126,7 +132,7 @@ function MyComponent() {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
export default function App() {
|
||||
const assetsFixedUp = React.useMemo(() => {
|
||||
const nyx = assets.find((a) => a.chain_name === 'nyx');
|
||||
@@ -139,7 +145,7 @@ export default function App() {
|
||||
}
|
||||
return assets;
|
||||
}, [assets]);
|
||||
|
||||
|
||||
return (
|
||||
<ChainProvider
|
||||
chains={[chains.find((c) => c.chain_id === 'nyx')!]}
|
||||
@@ -149,10 +155,10 @@ export default function App() {
|
||||
preferredSignType: () => 'amino',
|
||||
}}
|
||||
>
|
||||
|
||||
|
||||
<MyComponent/>
|
||||
</ChainProvider>
|
||||
|
||||
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { Callout } from 'nextra/components';
|
||||
|
||||
# `mixFetch`
|
||||
|
||||
<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.
|
||||
@@ -15,8 +17,6 @@ Sounds great, are there any catches? Well, there are a few (for now):
|
||||
|
||||
- For now, `mixFetch` cannot deal with concurrent requests with the same base URL.
|
||||
|
||||
Read [this article](https://blog.nym.com/mixfetch-like-the-fetch-api-but-via-the-mixnet-82acfd435c62) to learn more.
|
||||
|
||||
<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.
|
||||
|
||||
@@ -35,9 +35,9 @@ curl -X 'GET' \
|
||||
import type { SetupMixFetchOps } from '@nymproject/mix-fetch';
|
||||
|
||||
const mixFetchOptions: SetupMixFetchOps = {
|
||||
preferredGateway: '6Gb7ftQdKveMjPyrxDXeAtfYAX7Zg5mVZHtnRC5MmZ1B', // with WSS
|
||||
preferredGateway: '23A7CSaBSA2L67PWuFTPXUnYrCdyVcB7ATYsjUsfdftb', // with WSS
|
||||
preferredNetworkRequester:
|
||||
'8rRGWy54oC8drFL9DepMegBt2DLrsqQwCoHMXt9nsnTo.2XjCPVbb4FpQ9hNRcXwb9mTzEAVVk1zf1tcch3wdtNEA@6Gb7ftQdKveMjPyrxDXeAtfYAX7Zg5mVZHtnRC5MmZ1B',
|
||||
'HuNL1pFprNSKW6jdqppibXP5KNKCNJxDh7ivpYcoULN9.C62NahRTUf6kqpNtDVHXoVriQr6yyaU5LtxdgpbsGrtA@23A7CSaBSA2L67PWuFTPXUnYrCdyVcB7ATYsjUsfdftb',
|
||||
mixFetchOverride: {
|
||||
requestTimeoutMs: 60_000,
|
||||
},
|
||||
@@ -90,9 +90,9 @@ import { mixFetch, SetupMixFetchOps } from '@nymproject/mix-fetch-full-fat';
|
||||
import React from 'react';
|
||||
|
||||
const mixFetchOptions: SetupMixFetchOps = {
|
||||
preferredGateway: '6Gb7ftQdKveMjPyrxDXeAtfYAX7Zg5mVZHtnRC5MmZ1B', // with WSS
|
||||
preferredGateway: '23A7CSaBSA2L67PWuFTPXUnYrCdyVcB7ATYsjUsfdftb', // with WSS
|
||||
preferredNetworkRequester:
|
||||
'8rRGWy54oC8drFL9DepMegBt2DLrsqQwCoHMXt9nsnTo.2XjCPVbb4FpQ9hNRcXwb9mTzEAVVk1zf1tcch3wdtNEA@6Gb7ftQdKveMjPyrxDXeAtfYAX7Zg5mVZHtnRC5MmZ1B',
|
||||
'HuNL1pFprNSKW6jdqppibXP5KNKCNJxDh7ivpYcoULN9.C62NahRTUf6kqpNtDVHXoVriQr6yyaU5LtxdgpbsGrtA@23A7CSaBSA2L67PWuFTPXUnYrCdyVcB7ATYsjUsfdftb',
|
||||
mixFetchOverride: {
|
||||
requestTimeoutMs: 60_000,
|
||||
},
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { Callout } from 'nextra/components'
|
||||
|
||||
# Mixnet Client
|
||||
|
||||
<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.
|
||||
|
||||
This client is message based - it can only send a one-way message to another client's address.
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { Callout } from 'nextra/components'
|
||||
|
||||
# Nym Smart Contract Clients
|
||||
|
||||
<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.
|
||||
|
||||
##### Contract Clients list
|
||||
@@ -29,14 +31,14 @@ 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
|
||||
```
|
||||
|
||||
##### Installation
|
||||
Install the packages and their dependencies if you don't already have them:
|
||||
```bash
|
||||
npm install @nymproject/contract-clients @cosmjs/cosmwasm-stargate
|
||||
npm install @nymproject/contract-clients @cosmjs/cosmwasm-stargate
|
||||
```
|
||||
|
||||
## Query clients
|
||||
@@ -143,7 +145,7 @@ import { SigningCosmWasmClient } from "@cosmjs/cosmwasm-stargate";
|
||||
import { DirectSecp256k1HdWallet } from "@cosmjs/proto-signing";
|
||||
import { GasPrice } from "@cosmjs/stargate";
|
||||
import { settings } from "./settings";
|
||||
|
||||
|
||||
export default function Exec() {
|
||||
let signer: DirectSecp256k1HdWallet;
|
||||
let signerMixnetClient: any;
|
||||
@@ -153,7 +155,7 @@ export default function Exec() {
|
||||
let nodeAddress: string;
|
||||
let amountToSend: string;
|
||||
let delegations: any;
|
||||
|
||||
|
||||
async function ExecuteOnNyx() {
|
||||
// Cosmos client
|
||||
signer = await DirectSecp256k1HdWallet.fromMnemonic(settings.mnemonic, {
|
||||
@@ -168,7 +170,7 @@ export default function Exec() {
|
||||
);
|
||||
// Save globally
|
||||
cosmWasmSigningClient = cosmWasmClient;
|
||||
|
||||
|
||||
// Nym client
|
||||
const mixnetClient = new contracts.Mixnet.MixnetClient(
|
||||
cosmWasmSigningClient,
|
||||
@@ -177,9 +179,9 @@ export default function Exec() {
|
||||
);
|
||||
// Save globally
|
||||
signerMixnetClient = mixnetClient;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
// Get delegations
|
||||
const getDelegations = async () => {
|
||||
if (!signerMixnetClient) {
|
||||
@@ -190,7 +192,7 @@ export default function Exec() {
|
||||
});
|
||||
delegations = delegationsObject;
|
||||
};
|
||||
|
||||
|
||||
// Make delegation
|
||||
const doDelegation = async () => {
|
||||
if (!signerMixnetClient) {
|
||||
@@ -204,7 +206,7 @@ export default function Exec() {
|
||||
);
|
||||
console.log(res);
|
||||
};
|
||||
|
||||
|
||||
// Undelegate all
|
||||
const doUndelegateAll = async () => {
|
||||
for (const delegation of delegations.delegations) {
|
||||
@@ -214,7 +216,7 @@ export default function Exec() {
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// Sending tokens
|
||||
const doSendTokens = async () => {
|
||||
const memo = "test sending tokens";
|
||||
@@ -227,10 +229,10 @@ export default function Exec() {
|
||||
);
|
||||
console.log(res);
|
||||
};
|
||||
|
||||
|
||||
ExecuteOnNyx();
|
||||
setTimeout(() => getDelegations(), 1000);
|
||||
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p>Exec</p>
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
import "@interchain-ui/react/styles"
|
||||
import { CosmosKit } from "../../../../components/cosmos-kit";
|
||||
import { Callout } from 'nextra/components'
|
||||
import FormattedCosmoskitExampleCode from '../../../../code-examples/sdk/typescript/cosmoskit-example-code.mdx';
|
||||
|
||||
# Cosmos Kit
|
||||
|
||||
import { Callout } from 'nextra/components'
|
||||
|
||||
<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
|
||||
[Ledger hardware wallet](https://www.ledger.com/) to this page:
|
||||
|
||||
|
||||
@@ -5,6 +5,10 @@ import Box from '@mui/material/Box';
|
||||
import FormattedMixFetchExampleCode from '../../../../code-examples/sdk/typescript/mixfetch-example-code.mdx';
|
||||
import { Callout } from 'nextra/components'
|
||||
|
||||
<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.
|
||||
|
||||
|
||||
@@ -3,6 +3,11 @@
|
||||
import { Mixnodes } from '../../../../components/mixnodes';
|
||||
import Box from '@mui/material/Box';
|
||||
import FormattedExampleCode from '../../../../code-examples/sdk/typescript/mixnodes-example-code.mdx';
|
||||
import { Callout } from 'nextra/components'
|
||||
|
||||
<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,6 +4,9 @@ import { Traffic } from '../../../../components/traffic';
|
||||
import Box from '@mui/material/Box';
|
||||
import FormattedTrafficExampleCode from '../../../../code-examples/sdk/typescript/traffic-example-code.mdx';
|
||||
|
||||
<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!
|
||||
|
||||
|
||||
@@ -10,6 +10,13 @@ import FormattedWalletConnectCode from '../../../../code-examples/sdk/typescript
|
||||
import FormattedWalletSendTokensCode from '../../../../code-examples/sdk/typescript/wallet-sendTokens-code.mdx';
|
||||
import FormattedWalletDelegationsCode from '../../../../code-examples/sdk/typescript/wallet-delegations-code.mdx';
|
||||
|
||||
|
||||
import { Callout } from 'nextra/components'
|
||||
|
||||
<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!
|
||||
|
||||
<WalletContextProvider>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# Network Components
|
||||
|
||||
Core components:
|
||||
* A **Mixnet**, which mixes Sphinx packet traffic so that it cannot be determined who is communicating with whom. Our mixnet is based on a modified version of the [**Loopix** design](concepts/loopix). This is made up of [Nym nodes](architecture/mixnet/nodes) runnning on servers around the world maintained by a decentralised group of Operators.
|
||||
* Various [**Nym clients**](architecture/mixnet/clients) which manage sending and receiving Sphinx packets, encrypting/decrypting traffic, and providing [cover traffic](./concepts/cover-traffic) to hide 'real' traffic timing.
|
||||
* A **Mixnet**, which mixes Sphinx packet traffic so that it cannot be determined who is communicating with whom. Our mixnet is based on a modified version of the [**Loopix** design](concepts/loopix). This is made up of [Nym nodes](architecture/mixnet#nodes) runnning on servers around the world maintained by a decentralised group of Operators.
|
||||
* Various [**Nym clients**](architecture/mixnet#nym-clients) which manage sending and receiving Sphinx packets, encrypting/decrypting traffic, and providing [cover traffic](./concepts/cover-traffic) to hide 'real' traffic timing.
|
||||
* A CosmWasm-enabled blockchain called [**Nyx**](architecture/nyx), the home of the various smart contracts used by the mixnet. A subset of Nyx Validators run [NymAPI](./architecture/nyx#nymapi) instances, taking part in also producing and verifying [zk-nym credentials](cryptography/zk-nym).
|
||||
|
||||

|
||||
|
||||
@@ -66,7 +66,7 @@ The Vesting contract has multiple functions:
|
||||
* Interacting with the Mixnet using vesting (i.e. non-transferable) tokens, allowing users to delegate their unvested tokens.
|
||||
|
||||
### Multisig Contract
|
||||
The multisig contract used by the [NymAPI Quroum](../cryptography/zk-nym/zk-nym-overview) - a subset of the Nyx Validator set taking on the additional work of generating and validating zk-nyms - to execute certain actions in the [zk-nym](./ecash.md) contract.
|
||||
The multisig contract used by the [NymAPI Quroum](../cryptography/zk-nym/zk-nym-overview) - a subset of the Nyx Validator set taking on the additional work of generating and validating zk-nyms - to execute certain actions in the [zk-nym](../cryptography/zk-nym) contract.
|
||||
|
||||
It is essentially an instance of the [canonical](https://github.com/CosmWasm/cw-plus/tree/main/contracts) `cw3-flex-multisig` using the `cw4-group` contract, with one minor change to restrict the addresses allowed to submit proposals.
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { Steps } from 'nextra/components'
|
||||
import { Steps } from 'nextra/components';
|
||||
import { VarInfo } from 'components/variable-info.tsx';
|
||||
import { Tabs } from 'nextra/components';
|
||||
import { MyTab } from 'components/generic-tabs.tsx';
|
||||
|
||||
# Pre-built Binaries
|
||||
|
||||
@@ -18,20 +20,62 @@ If the pre-built binaries don't work or are unavailable for your system, you wil
|
||||
- Download from your terminal using `curl` or `wget` tool:
|
||||
```sh
|
||||
# using curl
|
||||
curl -L <LINK> -o <PATH>/nym-node
|
||||
curl -L <LINK> -o <PATH>
|
||||
|
||||
# using wget
|
||||
wget <LINK>
|
||||
```
|
||||
In case you want to download binary to your current working directory, drop `<PATH>` from the command
|
||||
|
||||
In case you want to download binary to your current working directory, drop `<PATH>` from the command
|
||||
###### 2. Verify the binary `sha256sum` hash
|
||||
|
||||
###### 2. Make the binary executable
|
||||
<div>
|
||||
<Tabs items={[
|
||||
<strong>Manual check</strong>,
|
||||
<strong>One-liner</strong>,
|
||||
]} defaultIndex="0">
|
||||
<MyTab>
|
||||
To see your binary `sha256sum` hash, run:
|
||||
```sh
|
||||
sha256sum <BINARY>
|
||||
```
|
||||
```sh
|
||||
# for example
|
||||
# sha256sum ./nym-wallet_1.2.15_amd64.AppImage
|
||||
# or
|
||||
# sha256sum ./nym-node
|
||||
```
|
||||
- Download [`hashes.json`](https://github.com/nymtech/nym/releases) file from the same same *Assets* drop down like your binary
|
||||
- Open it with your text editor or print its content with `cat hashes.json`
|
||||
- Check it if your binary `sha256sum` output is in `hashes.json` by using the `sha256sum` and searching for it or using `grep` command:
|
||||
```
|
||||
grep -i <SHA_HASH>
|
||||
```
|
||||
</MyTab>
|
||||
<MyTab>
|
||||
- Download [`hashes.json`](https://github.com/nymtech/nym/releases) file from the same same *Assets* drop down like your binary
|
||||
- Run this command, substituting `<BINARY>` with your the one you want to check:
|
||||
```sh
|
||||
sha256sum <BINARY> | awk '{print $1}' | grep -qF "$(jq -r '.assets | to_entries | .[].value.sha256' hashes.json)" && echo "Hash matches an asset in the JSON file." || echo "Hash does not match any asset in the JSON file."
|
||||
```
|
||||
```sh
|
||||
# for example
|
||||
# sha256sum ./nym-node | awk '{print $1}' | grep -qF "$(jq -r '.assets | to_entries | .[].value.sha256' hashes.json)" && echo "Hash matches an asset in the JSON file." || echo "Hash does not match any asset in the JSON file."
|
||||
```
|
||||
</MyTab>
|
||||
</Tabs>
|
||||
</div>
|
||||
|
||||
- If your have to extract the binary (it would look like like `<BINARY>.tar.gz`) do it:
|
||||
```sh
|
||||
tar -xvzf <BINARY>.tar.gz
|
||||
```
|
||||
|
||||
###### 3. Make the binary executable
|
||||
- Open terminal in the same directory and run:
|
||||
```sh
|
||||
chmod +x <BINARY>
|
||||
# for example: chmod +x nym-mixnode
|
||||
chmod u+x <BINARY>
|
||||
# for example: chmod u+x nym-node
|
||||
```
|
||||
|
||||
</Steps>
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
import { Callout } from 'nextra/components';
|
||||
import { Tabs } from 'nextra/components';
|
||||
import { MyTab } from 'components/generic-tabs.tsx';
|
||||
import { RunTabs } from 'components/operators/nodes/node-run-command-tabs';
|
||||
import { VarInfo } from 'components/variable-info.tsx';
|
||||
import { AccordionTemplate } from 'components/accordion-template.tsx';
|
||||
import { Steps } from 'nextra/components';
|
||||
import AbuseResponse from 'components/operators/templates/dmca_response.md';
|
||||
import OperatorIntroduction from 'components/operators/templates/provider_introduction.md';
|
||||
|
||||
|
||||
export const TestingSteps = () => (
|
||||
<div>
|
||||
@@ -29,6 +33,11 @@ export const TunnelManagerCommands = () => (
|
||||
</div>
|
||||
);
|
||||
|
||||
export const LoadEndpointInfo = () => (
|
||||
<div>
|
||||
Developer notes behind <code>/load</code> endpoint
|
||||
</div>
|
||||
);
|
||||
|
||||
# Changelog
|
||||
|
||||
@@ -38,18 +47,418 @@ This page displays a full list of all the changes during our release cycle from
|
||||
|
||||
<VarInfo />
|
||||
|
||||
## `v2025.1-reeses`
|
||||
## `v2025.2-hu`
|
||||
|
||||
- [Release Binaries](https://github.com/nymtech/nym/releases/tag/nym-binaries-v2025.1-reeses)
|
||||
- [`nym-node`](nodes/nym-node.mdx) version `1.3.0`
|
||||
- [Release Binaries](https://github.com/nymtech/nym/releases/tag/nym-binaries-v2025.2-hu)
|
||||
- [`nym-node`](nodes/nym-node.mdx) version `1.4.0`
|
||||
|
||||
```
|
||||
nym-node
|
||||
Binary Name: nym-node
|
||||
Build Timestamp: 2025-01-15T09:50:54.404428047Z
|
||||
Build Version: 1.3.0
|
||||
Commit SHA: c202e2d598fabda5fc16a6c1e110255857a2b1ca
|
||||
Commit Date: 2025-01-15T10:27:39.000000000+01:00
|
||||
Build Timestamp: 2025-02-04T09:35:42.399220545Z
|
||||
Build Version: 1.4.0
|
||||
Commit SHA: 4c2bf3642e8eec0d55c7753e14429d73ac2d0e99
|
||||
Commit Date: 2025-02-04T10:29:48.000000000+01:00
|
||||
Commit Branch: HEAD
|
||||
rustc Version: 1.84.1
|
||||
rustc Channel: stable
|
||||
cargo Profile: release
|
||||
```
|
||||
|
||||
### Operators Updates & Tools
|
||||
|
||||
<Callout type="info">
|
||||
From `nym-node v1.3.0` operators can technically choose multiple functionalities for their `nym-node` binary (flagged as `--mode`).
|
||||
|
||||
**However, the clients are yet to be developed to be able to make a proper selection for multi-mode nodes!**
|
||||
|
||||
**WE ASK OPERATORS TO ASSIGN ONLY ONE FUNCTIONALITY TO `--mode` OPTION PER NODE. PLEASE SELECT ONE MODE ARGUMENT OUT OF: `mixnode` or `entry-gateway` or `exit-gateway`!**
|
||||
</Callout>
|
||||
|
||||
- Updated maintenance guides to [backup](nodes/maintenance#backup-a-node), [restore](nodes/maintenance#restoring-a-node) and [move](nodes/maintenance#moving-a-node) a node, containing a new and important commands to backup and restore `clients.sqlite` database.
|
||||
|
||||
- [New explanation of `nym-node` functionalities](nodes/nym-node/setup#functionality-mode) describing how Gateways get selected in Mixnet mode and Wireguard mode.
|
||||
|
||||
<Callout type="warning">
|
||||
Wireguard nodes route data directly to the open internet. Therefore it exposes IP of operators server (VPS) to abuse complains. Before you decide to run a node with active wireguard routing, please read our [Community Counsel pages](community-counsel/exit-gateway) containing more information and some legal content.
|
||||
|
||||
**Wireguard mode has no exit policy right now - we are working on the implementation.**
|
||||
</Callout>
|
||||
|
||||
#### Legal support
|
||||
|
||||
We have been notified that a handful of nodes have been taken down by abuse reports. We created new pages with [legal suggestions](community-counsel/legal) and email [templates](community-counsel/templates). Here are some useful points on legal support:
|
||||
|
||||
- Here is the first version of a response template tailored for Nym operators.
|
||||
<AccordionTemplate name="Email template: responding to DMCA take-down notices">
|
||||
<AbuseResponse />
|
||||
</AccordionTemplate>
|
||||
|
||||
- We are starting Operators Legal Clinic with Alexis Roussel, every Wednesday, 14:30 UTC (60 min) in our [Operator Legal Forum channel on Matrix](https://matrix.to/#/!YfoUFsJjsXbWmijbPG:nymtech.chat?via=nymtech.chat&via=matrix.org&via=matrix.su4ka.icu). Come and share your findigs and questions with the rest of the operators.
|
||||
|
||||
- Join [Operator Legal Forum channel](https://matrix.to/#/!YfoUFsJjsXbWmijbPG:nymtech.chat?via=nymtech.chat&via=matrix.org&via=matrix.su4ka.icu) and share as much as possible (like screen prints, provider, location etc).
|
||||
|
||||
- Join [Community legal counsel](https://nym.com/docs/operators/community-counsel) - our collective knowledge hub. Add your findings by opening a [Pull Request](https://nym.com/docs/operators/add-content)
|
||||
|
||||
- While we are working on a new list of more friendly providers, consider to move away from these provides as soon as possible:
|
||||
|
||||
- Servinga / VPS2day (AS39378)
|
||||
- Frantech / Ponynet / BuyVM (AS53667)
|
||||
- OVH SAS / OVHcloud (AS16276)
|
||||
- Online S.A.S. / Scaleway (AS12876)
|
||||
- Hetzner Online GmbH (AS24940)
|
||||
- IONOS SE (AS8560)
|
||||
- Psychz Networks (AS40676)
|
||||
- 1337 Services GmbH / RDP.sh (AS210558)
|
||||
|
||||
- Backup your nodes to have access to `.nym` directory locally. Follow [node](nodes/maintenance#backup-a-node) and [proxy configuration](nodes/maintenance#backup-proxy-configuration) backup guides to be able to [restore your node](nodes/maintenance#restoring-a-node) later on on another machine, without losing your delegation.
|
||||
|
||||
- We would like to ask operators who use reverse proxy and a domain (required for Gateways) to start using a common convention starting with `nym-exit` for their nodes URL. The entire address should have this new format:
|
||||
```
|
||||
nym-exit.<CUSTOM>.<DOMAIN>.<TLD>
|
||||
```
|
||||
|
||||
For example:
|
||||
|
||||
```
|
||||
# for squads running multiple nodes a format can look like this:
|
||||
nym-exit.ch-node1.mysquad.org
|
||||
|
||||
# or like this
|
||||
nym-exit.3-jamaica.mysquad.org
|
||||
|
||||
# for operators having one node per location, the format can look like this:
|
||||
nym-exit.brazil.mysquad.org
|
||||
|
||||
# or if operators decide to not have any custom, they can simply have this format:
|
||||
nym-exit.mysquad.org
|
||||
```
|
||||
|
||||
**The `NYM-EXIT` part in the beginning is what's important.**
|
||||
|
||||
- When registering a domain, check [Top Level Domain (TLD)](https://www.techopedia.com/definition/1348/top-level-domain-tld) terms and conditions. For example `.icu` is a no go. Having a wrong TLD may lead to your domain being taken away from you when facing a DMCA report.
|
||||
|
||||
- Write a message to your provider and introduce your intention to run a Nym Node on their service
|
||||
<AccordionTemplate name="Email template: Introduce yourself to your VPS provider">
|
||||
<OperatorIntroduction />
|
||||
</AccordionTemplate>
|
||||
|
||||
- New page with [templates for VPS provider](community-counsel/templates)
|
||||
|
||||
#### Delegation Program
|
||||
|
||||
**If you are interested to sign up to delegation program que, message Merve on Element for the time being as we are working on CRM upgrade. We review nodes dynamically and delegate to new ones once a month.** New operators interested to join DP must follow the rules specified [here](https://delegations.explorenym.net/), run one of two latest binary version and have [Terms & Conditions](nodes/nym-node/setup#terms--conditions) accepted.
|
||||
|
||||
Note: Due to the token price we allow operators to have Operators cost 1000 NYM, the profit margin maximum remains 20%.
|
||||
|
||||
**Delegation and undelegation in January**
|
||||
|
||||
Delegated to all these nodes:
|
||||
<AccordionTemplate name="New delegated nodes">
|
||||
```
|
||||
AMnDNd1Xgw7Em9R5vehP7r12ZNWUZ3jmitDHya6gpvGR
|
||||
DLkKyYcA5feq43rqZdx6nJBNZvQsdX7kb2f1f6ED4cs2
|
||||
26ZmTxTVBKHZg8MTKwypHkXZVJhDC7QHuv3BdsyRyTuk
|
||||
5ZWdDN9pQ18vYkYYs5ZERh4P4JLtMiijscZ6FvwSfVxR
|
||||
3uBgUJR393acoCRysu6SiLsximiwAMM85QFxq9WD8puC
|
||||
5rXcNe2a44vXisK3uqLHCzpzvEwcnsijDMU7hg4fcYk8
|
||||
nuMerN7ahqsptK8zDUZhnxMyDqePza8vWDf8k171EEs
|
||||
86cNnnRxNpGdEtSX9UP1GpT6xrNvNuxFdHNkZcK7pAJg
|
||||
9ngFADaYpT54F9i8iYFZLYR7SB7hrubsPArwpqe1RXQb
|
||||
FQBbq1crAkCrjVBnEN85VqgZgGRMLJV65NJk8bPADdw
|
||||
FUH3E3dghXjC9hdt3jeAhSsxwozsw1yzBXkoaFqkL4ci
|
||||
5wF5wN7T2UuSsiUcQyL77NwdnXwAXxFttpZ1dR1nu4kR
|
||||
4pVq4QSCcq4zNCnBNCfyugPjVhVyGDCBDSW5rghg8oZS
|
||||
8XW1WWN1PuuAYz2TXQ9iWRekMFEJXjdEq4asMyE2qCCN
|
||||
CxgjcTTAnRJzDxMbqtX73vEbWwLQmVoW8zS8RZEicgfT
|
||||
7fwyXd4Cmh92d4piEPHmdFm1Wz1QCFQDf6bnDGNT3M6P
|
||||
```
|
||||
</AccordionTemplate>
|
||||
|
||||
Undelegated from 8 nodes for not meeting the rules (being offline):
|
||||
<AccordionTemplate name="Undelegated nodes">
|
||||
```
|
||||
# Gateways
|
||||
DLxLKsd3LTnfudSSmHanPaZACsh1x4MGEzfJS4jQibir
|
||||
DiciBkjHovXzTDE2EFJKPNj3TGw2oQjr7HPSas2YQPiQ
|
||||
ADjpymCgjFsE5m7YvnZFxLMscg85dCUUesR5g8yN3Mzz
|
||||
C9dEABjtFRMD1x4ZnbqnsELGJgutT73bPCfYzqBe7sHB
|
||||
|
||||
|
||||
# Mixnodes
|
||||
2FGgY5zWq6JP1BvpnLPbedWRYYAZELFCT9rMybNhnxpo
|
||||
4nKkwPSbkbtkw3yrRKQVAVdviNgFgcNskgcSGdT2Sucy
|
||||
DjY3T7n6VoHGcETMnmtZKmUU7NZP6AhCs8CoRsSSbViC
|
||||
FAKhiQ8nW5sAWAxks1WB8u1MAWsapToCSE3KmF9LuGRQ
|
||||
```
|
||||
</AccordionTemplate>
|
||||
|
||||
Undelegated due to high saturation:
|
||||
```
|
||||
5omopSZy59UyNPpx9P97vjBnN6PwPw9x5MVUx5kuNaXt
|
||||
```
|
||||
|
||||
#### Service Grant Program v2
|
||||
|
||||
Aside from delegating on top of nodes, Nym runs a Service Grant Program (SGP) to support Exit Gateway operators before they will be rewarded by collecting [zk-nym tickets](../network/cryptography/zk-nym) from users subscription. Operators included in SGP are long term active community members with the highest requirements on the technical setup and upgrading pace. We are about to start a second iteration of SGP very soon (SGPv2). The final slots and locations are yet to be concluded. Priority to participate in SGPv2 will be given to the current operators in SGP. Based on the number of slots, we will then determine how many more operators can sign up.
|
||||
|
||||
##### Rules of SGPv2
|
||||
|
||||
<Callout type="info">
|
||||
**We will share more info soon in the channels. The rules are not set in stone and could potentially be altered or updated in the future! Do *not* purchase new servers neither migrate your nodes just yet.**
|
||||
</Callout>
|
||||
|
||||
As we finalising last details of *"Project Smoosh"*, where one binary - `nym-node` - can run as an `entry-gateway`, `mixnode` or `exit-gateway` in Mixnet mode as well as `entry-gateway` or `exit-gateway` in Wireguard mode, we plan to step up the game. SGPv2 grants will be higher if operators can meet new requirements.
|
||||
|
||||
**Minimum Specs & Requirements**
|
||||
|
||||
These are minimum requirements to become a part of SGPv2. We aim to have nodes on dedicated servers, with exceptions for much stronger VPS to meet the needed criteria while sharing server with other users.
|
||||
|
||||
<div>
|
||||
<Tabs items={[
|
||||
<><code>nym-node</code> - dedicated server</>,
|
||||
<><code>nym-node</code> - VPS</>,
|
||||
]} defaultIndex="0">
|
||||
<MyTab>
|
||||
#### `nym-node` - dedicated server
|
||||
|
||||
| **Hardware** | **Minimum Specification** |
|
||||
| :--- | ---: |
|
||||
| Type of server | Dedicated or bare metal |
|
||||
| CPU Cores | 4 |
|
||||
| Memory | 8-16 GB RAM |
|
||||
| Storage | 50 GB (preferably 80 GB) |
|
||||
| Connectivity | IPv4, IPv6, TCP/IP, UDP |
|
||||
| Bandwidth | Unmetered if possible, or {" >"} 20Tb |
|
||||
| Port speed | 1Gbps |
|
||||
|
||||
- Exit Gateways only
|
||||
- [Terms & Conditions](nodes/nym-node/setup#terms--conditions) accepted
|
||||
- Nodes are [bonded](nodes/nym-node/bonding#migrate-to-nym-node-in-mixnet-smart-contract) as `nym-node` not as a legacy node
|
||||
- Timely upgrades without direct message notifications: Only Latest version, and the two preceding are accepted
|
||||
- Dedicated (or bare metal) machine is to ensure that the numbers above are *dedicated* for `nym-node` operation
|
||||
- Unmetered bandwidth is to ensure smooth user experience without data allowance limitation. 20Tb is a minimum for locations where unlimited bandwidth is not an option.
|
||||
- 1Gbps is an expected speed of Nym network in order to meet expectations of NymVPN users
|
||||
- Squads operating more than 2 nodes are expected to run larger servers and divide them for multiple nodes. We will share a how-to guide soon.
|
||||
- Operators run [reverse proxy](nodes/nym-node/configuration) with landing page URL starting with `nym-exit` and [Web Secure Socket](nodes/nym-node/configuration)
|
||||
- Operators must write their providers upfront that they will run a Nym Exit Gateway on their servers, using [this template](community-counsel/templates#introduction-to-server-provider)
|
||||
- If a node remains offline for more than 5 days for any reason, including abuse reports, and the operator doesn't resolve it neither doesn't communicate the blockers, they will be removed from the program.
|
||||
</MyTab>
|
||||
<MyTab>
|
||||
#### `nym-node` - VPS
|
||||
|
||||
| **Hardware** | **Minimum Specification** |
|
||||
| :--- | ---: |
|
||||
| Type of server | VPS |
|
||||
| CPU Cores | 8 |
|
||||
| Memory | 16-32 GB RAM |
|
||||
| Storage | 50 GB (preferably 80 GB) |
|
||||
| Connectivity | IPv4, IPv6, TCP/IP, UDP |
|
||||
| Bandwidth | Unmetered if possible, or {" >"} 40Tb |
|
||||
| Port speed | 10Gbps |
|
||||
|
||||
- Exit Gateways only
|
||||
- [Terms & Conditions](nodes/nym-node/setup#terms--conditions) accepted
|
||||
- Nodes are [bonded](nodes/nym-node/bonding#migrate-to-nym-node-in-mixnet-smart-contract) as `nym-node` not as a legacy node
|
||||
- Timely upgrades without direct message notifications: Only Latest version, and the two preceding are accepted
|
||||
- Stronger specs for VPS are to ensure that the `nym-node` operation will remain solid even when sharing a server with other users
|
||||
- Unmetered bandwidth is to ensure smooth user experience without data allowance limitation. 40Tb is a minimum for locations where unlimited bandwidth is not an option.
|
||||
- 10Gbps is to ensure that the 1Gbps expected speed of Nym network is met even when sharing a server with other users
|
||||
- Operators run [reverse proxy](nodes/nym-node/configuration) with landing page URL starting with `nym-exit` and [Web Secure Socket](nodes/nym-node/configuration)
|
||||
- Operators must write their providers upfront that they will run a Nym Exit Gateway on their servers, using [this template](community-counsel/templates#introduction-to-server-provider)
|
||||
- If a node remains offline for more than 5 days for any reason, including abuse reports, and the operator doesn't resolve it neither doesn't communicate the blockers, they will be removed from the program.
|
||||
</MyTab>
|
||||
</Tabs>
|
||||
</div>
|
||||
|
||||
**Operators interested in joining SGPv2 can start by searching for servers that meet the above criteria, where they may eventually migrate their nodes, and then share their findings by submitting a form which will be shared shortly. We will be first reaching to the current participants of Service Grant Program. Everything will be announced, sending DMs to devrel will not speed up the process!**
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
- [build(deps): bump criterion from `0.4.0` to `0.5.1`](https://github.com/nymtech/nym/pull/4911): Bumps [criterion](https://github.com/bheisler/criterion.rs) from `0.4.0` to `0.5.1`.
|
||||
|
||||
- [NS API: add mixnet scraper](https://github.com/nymtech/nym/pull/5200)
|
||||
|
||||
- [build(deps): bump http from `1.1.0` to `1.2.0`](https://github.com/nymtech/nym/pull/5228): Bumps [http](https://github.com/hyperium/http) from `1.1.0` to `1.2.0`.
|
||||
|
||||
- [`http-api-client`: deduplicate code](https://github.com/nymtech/nym/pull/5267): After adding PATCH support in [\#5260](https://github.com/nymtech/nym/pull/5260), it's now time to de-duplicate some of the code
|
||||
|
||||
- [Add windows to CI builds](https://github.com/nymtech/nym/pull/5269)
|
||||
|
||||
- [nym topology revamp](https://github.com/nymtech/nym/pull/5271): This PR changes the internals of the `NymTopology` to blur the lines between explicit mixnodes and gateways so that what used to be considered a "mixnode" could be a valid egress point of the network. `NymTopology` is no longer divided into `BTreeMap<MixLayer, Vec<mix::Node>>` and `Vec<gateway::Node>`. instead there's information about the current rewarded set (to support future VRF work) and a simple map of `HashMap<NodeId, RoutingNode>`. The new features are mostly controlled via 2 new flags/config values:
|
||||
- `use_extended_topology` that tells the client to retrieve **all** network nodes rather than the ones that got assigned "active" mixnode role (or support being a gateway)
|
||||
- `ignore_egress_epoch_role` that tells the client it's fine to construct egress packets to nodes that are **not** assigned entry or exit gateway role
|
||||
|
||||
- [Nyx Chain Watcher](https://github.com/nymtech/nym/pull/5274)
|
||||
|
||||
- [Include `IPINFO_API_TOKEN` in nightly CI](https://github.com/nymtech/nym/pull/5285)
|
||||
|
||||
- [Move tun constants to network defaults](https://github.com/nymtech/nym/pull/5286):
|
||||
<AccordionTemplate name={<TestingSteps/>}>
|
||||
1. **Regression Testing**:
|
||||
- Verified no issues arose when running tests for the affected files.
|
||||
- Tested TUN behaviour with new nym-nodes in the hu branch.
|
||||
|
||||
**Results**:
|
||||
|
||||
- **No bugs detected**.
|
||||
- Tunnels are functioning as expected, with traffic routing and IP generation working seamlessly.
|
||||
</AccordionTemplate>
|
||||
|
||||
- [Add dependabot assignes for the root cargo ecosystem](https://github.com/nymtech/nym/pull/5297)
|
||||
|
||||
- [build(deps): bump the patch-updates group across 1 directory with 35 updates](https://github.com/nymtech/nym/pull/5310): Bumps the `patch-updates` group with 33 updates
|
||||
|
||||
- [Periodically remove stale gateway messages](https://github.com/nymtech/nym/pull/5312): This PR introduces a simple task that removes gateway messages that haven't been retrieved in (by default) 24h.
|
||||
<AccordionTemplate name={<TestingSteps/>}>
|
||||
**Automation Script for Data Cleanup Validation**
|
||||
|
||||
Test Objective: Validate that the stale message cleanup mechanism in the database correctly removes records older than the configured threshold (24 hours).
|
||||
|
||||
Test Setup:
|
||||
1. Environment:
|
||||
* SQLite database
|
||||
* Bash script: used to insert data.
|
||||
|
||||
Steps Performed:
|
||||
1. Ran the insert_data.sh script to populate the database with test data:
|
||||
* Recent records inserted successfully.
|
||||
2. Verified the database content post-insertion: sqlite3 clients.sqlite "SELECT * FROM message_store;"
|
||||
3. Confirmed that all 20 records (10 recent + 10 stale) were present.
|
||||
4. Allowed the system to run for 24 hours to trigger the cleanup mechanism.
|
||||
5. Queried the database again after 24 hours: sqlite3 gateway_storage.db "SELECT * FROM message_store;"
|
||||
6.
|
||||
|
||||
Expected Result:
|
||||
* All stale records (older than 24 hours) should be removed.
|
||||
* Recent records should remain in the database.
|
||||
Actual Result:
|
||||
* Query after 24 hours showed only the 10 recent records.
|
||||
* All 10 stale records were successfully removed.
|
||||
</AccordionTemplate>
|
||||
|
||||
- [Use expect in geodata test to give error message on failure](https://github.com/nymtech/nym/pull/5314): Keep hitting this error on CI, from what I think is network hickup. But it's hard to tell form the log since the error is swallowed. Explicitly unwrap the result so we get a more detailed error output.
|
||||
<AccordionTemplate name={<TestingSteps/>}>
|
||||
**Quick Code Review**
|
||||
|
||||
**Summary**
|
||||
1. **CI Workflow**: Adjusted paths to optimise build triggers, avoiding unnecessary CI runs while ensuring coverage for key directories
|
||||
2. **Geolocation Test**: Improved error handling by replacing assertions with `.expect` for clearer debugging in API regression tests
|
||||
|
||||
**Conclusion**
|
||||
|
||||
Regression testing confirms everything works as intended. **Approved**.
|
||||
</AccordionTemplate>
|
||||
|
||||
- [`CancellationToken`-based shutdowns](https://github.com/nymtech/nym/pull/5325): This PR introduces scaffolding for using `CancellationToken` and `TaskTracker` for our graceful shutdowns rather than the existing `TaskClient` and `TaskManager`.
|
||||
|
||||
- [Introduce `/load` endpoint for self-reported quantised Nym Node load](https://github.com/nymtech/nym/pull/5326): This PR introduces a new `/load` endpoint on a `NymNode` to return its current load. It returns the following data:
|
||||
```rust
|
||||
pub struct NodeLoad {
|
||||
pub total: Load,
|
||||
pub machine: Load,
|
||||
pub network: Load,
|
||||
}
|
||||
```
|
||||
Where `Load` is quantised into the following buckets / tiers:
|
||||
```rust
|
||||
pub enum Load {
|
||||
Negligible, // 0 - 0.1
|
||||
Low, // 0.1 - 0.3
|
||||
Medium, // 0.3 - 0.6
|
||||
High, // 0.6 - 0.8
|
||||
VeryHigh, // 0.8 - 0.95
|
||||
AtCapacity, // >= 0.95
|
||||
}
|
||||
```
|
||||
<AccordionTemplate name={<LoadEndpointInfo/>}>
|
||||
The actual values for`NodeLoad` are determined as follows:
|
||||
- For network we approximate current rx/tx rates on all `eth` interfaces and scale them to the range of 0-1Gbps (for this initial iteration we assume the maximum network speed is 1Gbps which would be treated as fully saturated). So for example, if the node is sending at 0.5Gbps, it would get a `Load` of 0.5 and thus value of `Load::Medium`, 0.9Gbps would get `Load` of 0.9 and value of `Load::VeryHigh`, etc. we take the bigger value between rx and tx
|
||||
- For machine load there's a bit more logic in there:
|
||||
- Firstly we determine what I call a "base load" of the machine. we do this by taking the average load from the last 5min (via `getloadavg`) and dividing it by the number of CPUs in the machine. For example if the average load of the machine in the last 5min was `1.23` and it has 2 CPUs, then it's `Load` would be `Load::High` (`1.23 / 2 = 0.615`)
|
||||
- However, whilst CPU utilisation is one of the most important factors, it does not tell the whole story. I tried to also take memory/swap utilisation into consideration (whilst not making it the main factor)
|
||||
- Thus we calculate two additional auxiliary `Load` values, for memory usage and swap usage, i.e.: `used_memory / total_memory` and `used_swap / total_swap` respectively.
|
||||
- Then we check whether either of the `MemoryLoad` or `SwapLoad` is bigger than the current base `Load` of the machine we have determined, if so, it's increased by one tier / bucket. For example, say the current machine load is `Load::Low`, but the memory usage is at 90% (`Load::VeryHigh`). that would result in the reported `Load` being bumped up to `Load::Medium` instead. The same logic applies with swap load, **however, only if the total swap > 1GB**. this is to prevent weird edge cases where the machine has hardly any swap.
|
||||
- Finally, the `.total` `Load` uses the same "tier bumping" behaviour using the `.total` and `.network` loads, i.e. `if network > machine`, then `total = machine + 1`. for example if `machine` `Load` is `Load::Low`, but `network` `Load` is `Load::Medium`, then the `total` `Load` is set to `Load::Medium` instead.
|
||||
</AccordionTemplate>
|
||||
|
||||
- [Bump the `patch-updates` group with 8 updates](https://github.com/nymtech/nym/pull/5336)
|
||||
|
||||
- [Bump `tempfile` from `3.14.0` to `3.15.0`](https://github.com/nymtech/nym/pull/5337)
|
||||
|
||||
- [Bump `ts-rs` from `10.0.0` to `10.1.0`](https://github.com/nymtech/nym/pull/5338)
|
||||
|
||||
- [Updated `cosmrs` and `tendermint-rpc` to their most recent versions](https://github.com/nymtech/nym/pull/5339)
|
||||
|
||||
- [Bump mikefarah/yq from `4.44.6` to `4.45.1`](https://github.com/nymtech/nym/pull/5342)
|
||||
|
||||
- [Update `indexed_db_futures`](https://github.com/nymtech/nym/pull/5347): Updates the `indexed_db_futures` dependency to stop relying on the fork.
|
||||
|
||||
- [Refresh wasm sdk](https://github.com/nymtech/nym/pull/5353): This PR refreshes the wasm clients to make them usable in the current network
|
||||
|
||||
- [Client gateway selection](https://github.com/nymtech/nym/pull/5358): Changed how gateway is selected.
|
||||
- For init the target gateway can't support mixing
|
||||
- For egress, unless `ignore_epoch_roles` is specified, the gateway can't be currently assigned to a mixing layer. But it can be standby or inactive
|
||||
|
||||
- [Exposed `NymApiClient` method for obtaining node performance history](https://github.com/nymtech/nym/pull/5360)
|
||||
|
||||
- [Bind to `[::]` on `nym-node` for both IP versions](https://github.com/nymtech/nym/pull/5361)
|
||||
<AccordionTemplate name={<TestingSteps/>}>
|
||||
**IPv4 Configuration and Migration Testing**
|
||||
|
||||
Testing Steps:
|
||||
- Initiated and ran a nym-node with version 1.3.1 on an IPv4-only machine, then updated it to the new 'hu', 1.4.0 version.
|
||||
- Initiated and ran a new nym-node with 'hu', 1.4.0 always on a machine with only IPv4.
|
||||
- Initiated and ran a new nym-node with 'hu', 1.4.0 on a machine with both IPv4 and IPv6 for regression testing.
|
||||
|
||||
Results:
|
||||
- No functional issues during version updates.
|
||||
- Logged message on all versions: "no registered client for destination: ff02::2"
|
||||
|
||||
Status: Pass
|
||||
</ AccordionTemplate>
|
||||
|
||||
- [Remove empty ephemeral keys](https://github.com/nymtech/nym/pull/5376)
|
||||
|
||||
- [Handle ecash network errors differently](https://github.com/nymtech/nym/pull/5378): For errors that could be caused by network problems, we should mark the ticket as still pending, not as bad. This won't fix the underlying problem, but should not assume anymore that the client is the culprit and penalise its bandwidth quota.
|
||||
|
||||
- [Make client ignore dual mode nodes by default](https://github.com/nymtech/nym/pull/5388)
|
||||
|
||||
<AccordionTemplate name={<TestingSteps/>}>
|
||||
**Testing Steps:**
|
||||
- Ensured a client is able to select a node which aside from a gateway, can also act as a mixnode
|
||||
- Verified 'ignore_epoch_roles' is the default mode
|
||||
- _note; this is most likely not going to be a permanent solution_
|
||||
|
||||
**Results:**
|
||||
- Clients and topology are behaving correctly
|
||||
|
||||
Status: Pass
|
||||
</ AccordionTemplate>
|
||||
|
||||
- [Update version of chain watcher and validator rewarder](https://github.com/nymtech/nym/pull/5394)
|
||||
|
||||
### Bugfix
|
||||
|
||||
- [Remove unnecessary arguments for nym-api swagger endpoints](https://github.com/nymtech/nym/pull/5272)
|
||||
- [Fixed sql migration for adding default message timestamp](https://github.com/nymtech/nym/pull/5374)
|
||||
- [Terminate mixnet socket listener on shutdown](https://github.com/nymtech/nym/pull/5389)
|
||||
- [Correctly handle ingore epoch roles flag](https://github.com/nymtech/nym/pull/5390)
|
||||
|
||||
|
||||
## `v2025.1-reeses`
|
||||
|
||||
- [Release Binaries](https://github.com/nymtech/nym/releases/tag/nym-binaries-v2025.1-reeses)
|
||||
- [`nym-node`](nodes/nym-node.mdx) version `1.3.1`
|
||||
|
||||
```
|
||||
nym-node
|
||||
Binary Name: nym-node
|
||||
Build Timestamp: 2025-01-16T11:54:17.079662337Z
|
||||
Build Version: 1.3.1
|
||||
Commit SHA: 5ab164d229f85bd2dd27ec6e38292c281df2f678
|
||||
Commit Date: 2025-01-16T12:51:53.000000000+01:00
|
||||
Commit Branch: HEAD
|
||||
rustc Version: 1.84.0
|
||||
rustc Channel: stable
|
||||
@@ -525,7 +934,7 @@ cargo Profile: release
|
||||
|
||||
- `nym-node` has now implemented [IPv6 support for wireguard](https://github.com/nymtech/nym/pull/5059)
|
||||
|
||||
- [`network_tunnel_manager.sh` updated](network): run the commands below to make sure
|
||||
- [`network_tunnel_manager.sh` updated](https://raw.githubusercontent.com/nymtech/nym/refs/heads/develop/scripts/network_tunnel_manager.sh): run the commands below to make sure
|
||||
|
||||
<br />
|
||||
|
||||
@@ -659,7 +1068,7 @@ systemctl daemon-reload && service nym-node restart
|
||||
|
||||
- Since `v2024.13-magura`, operators do not update their node version in the wallet. [Manual upgrading steps](nodes/maintenance/manual-upgrade.mdx) has been updated accordingly.
|
||||
|
||||
- CLI tool [`node_api_check.py`](nodes/performance-and-testing/node-api-check.mdx), helping operators to collect all API values about their nodes locally, is not up to date with the API changes introduced with `v2024.13-magura` release version. Please treat it as unstable before we fix it.
|
||||
- CLI tool `node_api_check.py`, helping operators to collect all API values about their nodes locally, is not up to date with the API changes introduced with `v2024.13-magura` release version. Please treat it as unstable before we fix it.
|
||||
|
||||
#### Error Log
|
||||
|
||||
@@ -967,7 +1376,7 @@ Confirm that the deployment workflows work through manual testing
|
||||
### Operators Updates & Tooling
|
||||
|
||||
<Callout type="warning" emoji="⚠️">
|
||||
**Every operator has to make sure that their nodes [self-described endpoint works](nodes/performance-and-testing/node-api-check#basic-api-usage), otherwise the node will be un-routable and thus won't get any rewards!**
|
||||
**Every operator has to make sure that their nodes self-described endpoint works, otherwise the node will be un-routable and thus won't get any rewards!**
|
||||
</Callout>
|
||||
|
||||
- **New technical documentation:** All Nym documentation starts from a new entry page [nymtech.net/docs](https://nymtech.net/docs/operators/introduction). To run locally or propose collaboration, start in our [repository](https://github.com/nymtech/nym/tree/develop/documentation)
|
||||
@@ -1165,7 +1574,7 @@ Some endpoints got purposely deprecated without any equivalent reimplemented sin
|
||||
##### Mixnet Contract
|
||||
|
||||
<Callout type="warning" emoji="">
|
||||
**Every operator has to make sure that their nodes [self-described endpoint works](nodes/performance-and-testing/node-api-check#basic-api-usage), otherwise the node will be un-routable and thus won't get any rewards!**
|
||||
**Every operator has to make sure that their nodes self-described endpoint works, otherwise the node will be un-routable and thus won't get any rewards!**
|
||||
</Callout>
|
||||
|
||||
###### High Level Changes
|
||||
@@ -1979,7 +2388,7 @@ done
|
||||
- Vesting contract functionalities have been purged, users can only remove tokens from vesting
|
||||
- Migrating from `mixnode` or `gateway` smart contracts to a new unifying `nym-node` smart contract will be available soon using Nym desktop wallet, just like you are used to for bonding and node settings. After this migration all `nym-nodes` will be able to receive delegation and rewards. We will share a step by step guide once this migration will be deployed. No action needed now.
|
||||
|
||||
- [Nym API Check CLI](testing/node-api-check.md) is upgraded according to the latest API endpoints, output is cleaner and more concise.
|
||||
- Nym API Check CLI is upgraded according to the latest API endpoints, output is cleaner and more concise.
|
||||
|
||||
|
||||
#### Operators Tasks
|
||||
@@ -2868,7 +3277,7 @@ warning: /home/alice/src/nym/nym/common/dkg/Cargo.toml: `default-features` is ig
|
||||
- Flow [chart](release-cycle.md#release-flow)
|
||||
- [Sandbox testnet](sandbox.md) guide: teaching Nym node operators how to run their nodes in Nym Sandbox testnet environment.
|
||||
- [Terms & Conditions flag](nodes/setup.md#terms--conditions)
|
||||
- [Node API Check CLI](testing/node-api-check.md)
|
||||
- Node API Check CLI
|
||||
- [Pruning VPS `syslog` scripts](troubleshooting/vps-isp.md#pruning-logs)
|
||||
- [Black-xit: Exiting the blacklist](troubleshooting/nodes.md#my-gateway-is-blacklisted)
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
{
|
||||
"exit-gateway": "Exit Gateway",
|
||||
"legal": "Legal Counsel",
|
||||
"templates": "Templates",
|
||||
"jurisdictions": "Jurisdictions",
|
||||
"isp-list": "ISP List",
|
||||
"landing-pages": "Landing Pages",
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
import { Callout } from 'nextra/components';
|
||||
import { Tabs } from 'nextra/components';
|
||||
import { RunTabs } from 'components/operators/nodes/node-run-command-tabs';
|
||||
import { VarInfo } from 'components/variable-info.tsx';
|
||||
import { AccordionTemplate } from 'components/accordion-template.tsx';
|
||||
import { Steps } from 'nextra/components';
|
||||
import AbuseResponse from 'components/operators/templates/dmca_response.md';
|
||||
import OperatorIntroduction from 'components/operators/templates/provider_introduction.md';
|
||||
|
||||
# Community Counsel: General Legal Considerations
|
||||
|
||||
**This page contains some tips & tricks to support operators running Exit Gateway (`nym-node` in mode `exit-gateway`) gathered by the operators community. Have a look on the points below to strengthen your legal protection.**
|
||||
|
||||
<VarInfo />
|
||||
|
||||
## Useful Suggestions
|
||||
|
||||
#### Introduce Nym Node to your provider
|
||||
Write a message to your provider telling them about your intention to run a `nym-node` on their infrastructure. You can use this template for it.
|
||||
|
||||
<br/>
|
||||
<AccordionTemplate name="Email template: Introduce yourself to your VPS provider">
|
||||
<OperatorIntroduction />
|
||||
</AccordionTemplate>
|
||||
|
||||
#### Join Operators Legal Forum
|
||||
This [Matrix channel]((https://matrix.to/#/!YfoUFsJjsXbWmijbPG:nymtech.chat?via=nymtech.chat&via=matrix.org&via=matrix.su4ka.icu)) is the best place to ask questions and share your experience with others. You can share screen prints of abuse reports and ask for support.
|
||||
|
||||
#### Join Operators Legal Clinic
|
||||
Do you have any questions directed for lawyers? Come and chat with Nym COO Alexis Roussel, every Wednesday 14:30 UTC for 60min in our [Operator Legal Forum channel on Matrix](https://matrix.to/#/!YfoUFsJjsXbWmijbPG:nymtech.chat?via=nymtech.chat&via=matrix.org&via=matrix.su4ka.icu).
|
||||
|
||||
#### Use a friendly provider
|
||||
Nym operators community shares their experience with different ISPs on [this page](isp-list). At the same time, consider to move away from these provides:
|
||||
|
||||
- Servinga / VPS2day (AS39378)
|
||||
- Frantech / Ponynet / BuyVM (AS53667)
|
||||
- OVH SAS / OVHcloud (AS16276)
|
||||
- Online S.A.S. / Scaleway (AS12876)
|
||||
- Hetzner Online GmbH (AS24940)
|
||||
- IONOS SE (AS8560)
|
||||
- Psychz Networks (AS40676)
|
||||
- 1337 Services GmbH / RDP.sh (AS210558)
|
||||
|
||||
#### Backup your nodes
|
||||
Your only way to restore your node is when you have an access to `.nym` directory locally. Follow [node](../nodes/maintenance#backup-a-node) and [proxy configuration](../nodes/maintenance#backup-proxy-configuration) backup guides to be able to [restore your node](../nodes/maintenance#restoring-a-node) on another machine later on, without losing your delegation.
|
||||
|
||||
#### Use `nym-exit` prefix on your landing page URL
|
||||
We would like to ask operators to use [reverse proxy](../nodes/nym-node/configuration/proxy-configuration) with a [landing page](landing-pages). When assigning a domain please use a common convention with `nym-exit` in the beginning of the the page URL as this will create a reputation and reference. The entire address should have this new format:
|
||||
```
|
||||
nym-exit.<CUSTOM>.<DOMAIN>.<TLD>
|
||||
```
|
||||
|
||||
For example:
|
||||
|
||||
```
|
||||
# for squads running multiple nodes a format can look like this:
|
||||
nym-exit.ch-node1.mysquad.org
|
||||
|
||||
# or like this
|
||||
nym-exit.3-jamaica.mysquad.org
|
||||
|
||||
# for operators having one node per location, the format can look like this:
|
||||
nym-exit.brazil.mysquad.org
|
||||
|
||||
# or if operators decide to not have any custom, they can simply have this format:
|
||||
nym-exit.mysquad.org
|
||||
```
|
||||
|
||||
**The `NYM-EXIT` part in the beginning is what's important.**
|
||||
|
||||
#### Chose the right TLD
|
||||
When registering a domain, check [Top Level Domain (TLD)](https://www.techopedia.com/definition/1348/top-level-domain-tld) terms and conditions. For example `.icu` is a no go. Having a wrong TLD may lead to your domain being taken away from you when facing a DMCA report.
|
||||
|
||||
#### Respond to abuse reports
|
||||
Make sure to read notifications from your account provider and if you receive an abuse report, respond to it in time. Here is a template explaining the essential legal background of Nym Node Exit Gateway. Don't forget to adjust the variables.
|
||||
|
||||
<br/>
|
||||
<AccordionTemplate name="Email template: responding to DMCA take-down notices">
|
||||
<AbuseResponse />
|
||||
</AccordionTemplate>
|
||||
|
||||
#### Help us to improve these pages
|
||||
Add your findings by opening a [Pull Request](add-content).
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import { Callout } from 'nextra/components';
|
||||
import { Tabs } from 'nextra/components';
|
||||
import { RunTabs } from 'components/operators/nodes/node-run-command-tabs';
|
||||
import { VarInfo } from 'components/variable-info.tsx';
|
||||
import { AccordionTemplate } from 'components/accordion-template.tsx';
|
||||
import { Steps } from 'nextra/components';
|
||||
import AbuseResponse from 'components/operators/templates/dmca_response.md';
|
||||
import OperatorIntroduction from 'components/operators/templates/provider_introduction.md';
|
||||
|
||||
# Templates for Providers
|
||||
|
||||
Communication with your provider is an essential part of legal defense, as more often than not, terminating a server is based on the lack of knowledge about Nym Node and it's legal bases. Use the following templates to introduce your intention to run a Nym Node or as a response to abuse report.
|
||||
|
||||
<VarInfo/ >
|
||||
|
||||
## Email Templates
|
||||
|
||||
### Introduction to server provider
|
||||
|
||||
<OperatorIntroduction />
|
||||
|
||||
### DMCA abuse report response
|
||||
<AbuseResponse />
|
||||
@@ -35,9 +35,7 @@ Accepting T&Cs is done via a flag `--accept-operator-terms-and-conditions` added
|
||||
This is a summary of all needed steps for node operators to setup and configure a `nym-node` and register it to Nym Network:
|
||||
|
||||
|
||||
1. **Start with [Preliminary Steps](nodes/preliminary-steps.mdx), preparing:**
|
||||
- [VPS](nodes/preliminary-steps/vps-setup.mdx)
|
||||
- [Nym wallet](nodes/preliminary-steps/wallet-preparation)
|
||||
1. **Start with [Preliminary Steps](nodes/preliminary-steps.mdx), preparing [VPS](nodes/preliminary-steps/vps-setup.mdx) and [Nym wallet](nodes/preliminary-steps/wallet-preparation)**
|
||||
|
||||
2. **[Setup](nodes/nym-node/setup.mdx) the node**
|
||||
|
||||
|
||||
@@ -101,66 +101,178 @@ For more information about your node's port configuration, check the [port refer
|
||||
|
||||
Anything can happen to the server on which your node is running. To back up your `nym-node` keys and configuration protects the operators against the negative impact of unexpected events. To restart your node on another server, two essential pieces are needed:
|
||||
|
||||
1. Node keys to initialise the same node on a new VPS
|
||||
2. Access to the bonding Nym account (wallet seeds) to edit the IP on smart contract
|
||||
1. **Node keys to initialise the same node on a new VPS**
|
||||
2. **Access to the bonding Nyx account (wallet seeds) to update host on the Mixnet smart contract**
|
||||
|
||||
Assuming that everyone access their wallets from local machine and does *not* store their seeds on VPS, point 2. should be a given.
|
||||
To backup your `nym-node` keys and configuration in the easiest way possible, copy the entire config directory `.nym` from your VPS to your local desktop, using a special copy command `scp`:
|
||||
Optionally, it can be useful to have a backup your configuration directories to setup [reverse proxy and WSS](nym-node/configuration/proxy-configuration.mdx) quickly as well.
|
||||
|
||||
Assuming that everyone access their wallets from local machine and does *not* store their seeds on VPS, point \#2 should be a given.
|
||||
|
||||
<Callout type="warning" emoji="⚠️">
|
||||
Never store your mnemonic seed anywhere online nor do *not* share it with anyone!
|
||||
</Callout>
|
||||
|
||||
To backup your `nym-node` keys and configuration in the easiest way possible, copy the entire config directory `.nym` from your VPS to your local machine, using a special copy command `scp`:
|
||||
|
||||
<Steps>
|
||||
|
||||
###### 1. Create a directory where you want to store your backup
|
||||
###### 1. Create a local directory where you want to store your backup
|
||||
```sh
|
||||
mkdir -pv <PATH_TO_TARGET_DIRECTORY>
|
||||
```
|
||||
|
||||
###### 2. Copy configuration folder `.nym` from your VPS to your newly created backup directory
|
||||
```sh
|
||||
# for example
|
||||
# mkdir -pv $HOME/backup/my_nym_node/.nym
|
||||
```
|
||||
|
||||
###### 2. Backup `clients.sqlite` database
|
||||
|
||||
- Install `sqlite3`
|
||||
```sh
|
||||
apt install sqlite3
|
||||
```
|
||||
- Open sqlite CLI shell inside `clients.sqlite` database
|
||||
```sh
|
||||
sqlite3 ~/.nym/nym-nodes/default-nym-node/data/clients.sqlite
|
||||
```
|
||||
- Create backup called `clients_backup.sqlite`
|
||||
```sh
|
||||
.backup .nym/nym-nodes/default-nym-node/data/clients_backup.sqlite
|
||||
```
|
||||
- Exit sqlite CLI shell
|
||||
```sh
|
||||
.exit
|
||||
```
|
||||
|
||||
###### 3. Copy configuration folder `.nym` from your VPS to your newly created backup directory
|
||||
|
||||
```sh
|
||||
scp -r <SOURCE_USER_NAME>@<SOURCE_HOST_ADDRESS>:~/.nym/nym-nodes/<ID> <PATH_TO_TARGET_DIRECTORY>
|
||||
```
|
||||
|
||||
###### 4. Verify the success of the backup
|
||||
|
||||
The `scp` command should print logs, an operator can see directly whether it was successful or if it encountered any error. However, double check that all your needed configuration is in the backup target directory.
|
||||
|
||||
</Steps>
|
||||
|
||||
Now you have everything needed to restore your `nym-node` on another server. If you are in a need of doing so, follow the steps in [*Restoring a node*](#restoring-a-node) chapter below.
|
||||
|
||||
### Backup proxy configuration
|
||||
|
||||
If you run your node behind a [reverse proxy and WSS](nym-node/configuration/proxy-configuration.mdx) (suggested for all Exit Gateways), you may find it useful to backup configuration directories for that as well. It's done by following the same logic, using `scp` command for remote copy:
|
||||
|
||||
<Callout>
|
||||
Given that servers hosting a `nym-node` are expected to be a single purpose machines, we assume in this guide, that there are no other `<HOSTNAME>` sub-directories in `/var/www` and `/etc/nginx/sites-available` then the ones relevant to `nym-node`. In case you prefer to only backup configuration for a particular hostname, just append your `<HOSTNAME>` to the path in the commands below.
|
||||
</ Callout>
|
||||
|
||||
<Steps>
|
||||
|
||||
###### 1. Create local sub-directories where you want to store your backup
|
||||
|
||||
```sh
|
||||
mkdir -pv <PATH_TO_TARGET_DIRECTORY>
|
||||
mkdir -pv <PATH_TO_TARGET_DIRECTORY>
|
||||
```
|
||||
|
||||
```sh
|
||||
# for example
|
||||
# mkdir -pv $HOME/backup/my_nym_node/var/www
|
||||
# mkdir -pv $HOME/backup/my_nym_node/etc/nginx/sites-available
|
||||
```
|
||||
|
||||
###### 1. Backup `/var/www` directory
|
||||
|
||||
```sh
|
||||
scp -r <SOURCE_USER_NAME>@<SOURCE_HOST_ADDRESS>:/var/www <PATH_TO_TARGET_DIRECTORY>
|
||||
```
|
||||
|
||||
###### 2. Backup `/etc/nginx/sites-available` directory
|
||||
|
||||
```sh
|
||||
scp -r <SOURCE_USER_NAME>@<SOURCE_HOST_ADDRESS>:/etc/nginx/sites-available <PATH_TO_TARGET_DIRECTORY>
|
||||
```
|
||||
|
||||
###### 3. Verify the success of the backup
|
||||
|
||||
The `scp` command should print logs, an operator can see directly whether it was successful or if it encountered any error. However, double check that all your needed configuration is in the backup target directory.
|
||||
|
||||
</Steps>
|
||||
|
||||
<Callout type="info">
|
||||
To copy files from the root directory (paths starting with `/`), you need root permissions on the remote machine. If your server is not running as the root user, the `scp` command won't work directly, as using the `sudo` prefix would apply to your local machine instead of the remote server.
|
||||
|
||||
To resolve this, you need to SSH into the remote server first, and there use the `sudo cp -r <SOURCE_PATH> <TARGET_PATH>` command to copy the necessary directories to a location outside the root directory. After that, you can run `scp` with the correct source path to transfer the files.
|
||||
</Callout>
|
||||
|
||||
## Restoring a node
|
||||
|
||||
In case your VPS shut down and you have a [backup](#backup-a-node) of your node keys and access to your bonding wallet, you can easily restore your node on another server without losing your delegation.
|
||||
In case your VPS was terminated and you have a [backup](#backup-a-node) of your node keys and access to your [bonding](nym-node/bonding) wallet, you can easily restore your node on another server without losing your delegation.
|
||||
|
||||
<Steps>
|
||||
|
||||
###### 1. Prepare new VPS
|
||||
|
||||
- On VPS: Do all [preliminary steps](preliminary-steps.mdx) needed to run a `nym-node`.
|
||||
SSH into your new VPS and start with:
|
||||
|
||||
- On VPS: Create a `.nym/nym-nodes` configuration folder:
|
||||
- Do all [preliminary steps](preliminary-steps.mdx) needed to run a `nym-node`.
|
||||
|
||||
- Create a `.nym/nym-nodes` configuration folder:
|
||||
```sh
|
||||
mkdir -pv ~/.nym/nym-nodes
|
||||
```
|
||||
|
||||
###### 2. Restore your node configuration
|
||||
|
||||
From machine where your node is backed up (usually local desktop): Copy the folder with your node keys and configuration to the newly created folder on your VPS using `scp` command. Make sure to grab the entire `nym-node` configuration folder, which is called after your local `nym-node` identifier (`<ID>`), the `-r` (recursive) flag will take care of all sub-directories and their content:
|
||||
Copy the folder with your node configuration and keys from your local machine to the newly created folder on your VPS using `scp` command. Make sure to grab the entire `nym-node` configuration folder, which is called after your local `nym-node` identifier (`<ID>`), the `-r` (recursive) flag will take care of all sub-directories and their content:
|
||||
|
||||
```sh
|
||||
scp -r <PATH_TO_LOCAL_NODE_CONFIGURATION_FOLDER> <VPS_USER_NAME>@<VPS_HOST_ADDRESS>:~/.nym/nym-nodes/
|
||||
```
|
||||
```sh
|
||||
# for example:
|
||||
scp -r $HOME/backup/my_nym_node/.nym/nym-nodes/default-nym-node root@my-nym-node:~/.nym/nym-nodes/
|
||||
```
|
||||
|
||||
###### 3. Verify the success of the backup
|
||||
|
||||
The `scp` command should print logs, an operator can see directly whether it was successful or if it encountered any error. However, double check that all your needed configuration is in the target directory `.nym/nym-nodes` on your VPS.
|
||||
|
||||
|
||||
###### 3. Configure your node on the new VPS
|
||||
###### 4. Configure your node on the new VPS
|
||||
|
||||
* Edit `~/.nym/nym-nodes/<ID>/config/config.toml` config with the new listening address IP - it's the one under the header `[host]`, called `public_ips = [<PUBLIC_IPS>,]` and add your new location (field `location = <LOCATION>`, formats like: 'Jamaica', or two-letter alpha2 (e.g. 'JM'), three-letter alpha3 (e.g. 'JAM') or three-digit numeric-3 (e.g. '388') can be provided). You can see your IP by running a command `echo "$(curl -4 https://ifconfig.me)"`.
|
||||
* SSH to your VPS
|
||||
* Run a command `echo "$(curl -4 https://ifconfig.me)"` to see your public IPv4
|
||||
* Edit config file located at `~/.nym/nym-nodes/<ID>/config/config.toml` correct IP - it's the field under the header `[host]`, called `public_ips = [<PUBLIC_IPS>,]`
|
||||
* Add your new location field `location = <LOCATION>`, (formats like: 'Jamaica', or two-letter alpha2 (e.g. 'JM'), three-letter alpha3 (e.g. 'JAM') or three-digit numeric-3 (e.g. '388') can be provided).
|
||||
|
||||
* Try to run the node and see if everything works.
|
||||
###### 5. Restore `clients.sqlite` database
|
||||
|
||||
- Install `sqlite3`
|
||||
```sh
|
||||
apt install sqlite3
|
||||
```
|
||||
- Open sqlite CLI shell inside `clients.sqlite` database
|
||||
```sh
|
||||
sqlite3 ~/.nym/nym-nodes/default-nym-node/data/clients.sqlite
|
||||
```
|
||||
- Restore your backup called `clients_backup.sqlite`
|
||||
```sh
|
||||
.restore .nym/nym-nodes/default-nym-node/data/clients_backup.sqlite
|
||||
```
|
||||
- Exit sqlite CLI shell
|
||||
```sh
|
||||
.exit
|
||||
```
|
||||
- Check integrity
|
||||
```sh
|
||||
sqlite3 ~/.nym/nym-nodes/default-nym-node/data/clients.sqlite "PRAGMA integrity_check;"
|
||||
```
|
||||
The result should return `ok` if the database is intact.
|
||||
|
||||
###### 6. Test & validate your setup
|
||||
* Dry run the node and see if everything works.
|
||||
* Setup the [systemd](nym-node/configuration.mdx#systemd) automation (don't forget to add the [terms and conditions flag](nym-node/setup.mdx#terms--conditions)) to `ExecStart` command, reload the daemon and run the service.
|
||||
|
||||
###### 4. Change the node smart contract info via the wallet interface
|
||||
@@ -169,6 +281,63 @@ Open Nym Wallet, go to *Bonding*, open *Settings* and change *Host* value to the
|
||||
|
||||
</Steps>
|
||||
|
||||
Everything should work now. If not, have a look through the `config.toml` file and ensure that there are no details from the past VPS, things like WSS port, landing page asset etc. If you had a configuration with reverse proxy and WSS, continue to set that up as well. If you had it backed up, you can follow the steps to [restore proxy configuration](#restore-proxy-configuration) below, or you can start to [configure proxy from scratch](nym-node/configuration/proxy-configuration.mdx).
|
||||
|
||||
### Restoring proxy configuration
|
||||
|
||||
If operators moving to a new server had their [proxy configuration backed up](#backup-proxy-configuration), it's possible to simply restore it, following these steps:
|
||||
|
||||
<Steps>
|
||||
###### 1. Copy your backed up directory `/var/www` for landing page to the server
|
||||
- Start setting up reverse proxy and WSS following [this guide](nym-node/configuration/proxy-configuration.mdx) but instead of creating a new `/var/www/<HOSTNAME>` directory, simply copy there the one from the old server, using `scp` command
|
||||
|
||||
```sh
|
||||
scp -r <PATH_TO_LOCAL_WWW_CONFIGURATION_BACKUP> <VPS_USER_NAME>@<VPS_HOST_ADDRESS>:/var/www/
|
||||
```
|
||||
```sh
|
||||
# for example:
|
||||
scp -r $HOME/backup/my_nym_node/var/www root@my-nym-node:/var/www
|
||||
```
|
||||
###### 2. Make sure that domain is correctly setup
|
||||
As you see in the guide you use a domain (same like your `<HOSTNAME>`), make sure you have it registered as and redirected to the new IP by logging into your DNS provider and editing the dashboard of the domain.
|
||||
|
||||
###### 3. Proceed with all needed `node` configuration
|
||||
- In the guide follow the steps to [configure your node reverse proxy](nym-node/configuration/proxy-configuration#reverse-proxy-configuration), like installing Nginx, opening the needed ports etc
|
||||
|
||||
###### 4. Restore Nginx config
|
||||
- When you arrive to the point to configure your [`/etc/nginx/sites-available/<HOSTNAME>`](nym-node/configuration/proxy-configuration#2-add-your-endpoint-configuration-to-nginx-by-creating-a-config-file), use again `scp` instead of creating a new one
|
||||
|
||||
```sh
|
||||
scp -r <PATH_TO_LOCAL_NGINX_CONFIGURATION_BACKUP> <VPS_USER_NAME>@<VPS_HOST_ADDRESS>:/etc/nginx/sites-available
|
||||
```
|
||||
```sh
|
||||
# for example:
|
||||
scp -r $HOME/backup/my_nym_node/etc/nginx/sites-available root@my-nym-node:/etc/nginx/sites-available
|
||||
```
|
||||
###### 5. Edit Nginx configuration file with correct hostname
|
||||
- Open the Nginx config file on your server and see if a domain name needs to be changed to new one. To open the file, use for example nano text editor:
|
||||
```sh
|
||||
nano /etc/nginx/sites-available/<HOSTNAME>
|
||||
```
|
||||
###### 6. Continue with the Nginx setup
|
||||
- Go back to the [proxy setup guide](nym-node/configuration/proxy-configuration#3-activate-and-test-nginx-configuration) and continue from the point [\#3](nym-node/configuration/proxy-configuration#3-activate-and-test-nginx-configuration)
|
||||
|
||||
###### 7. Edit WSS configuration with the correct hostname
|
||||
- Open the WSS config file on your server and see if a domain name needs to be changed to new one. To open the file, use for example nano text editor:
|
||||
```sh
|
||||
nano /etc/nginx/sites-available/wss-config-nym
|
||||
```
|
||||
###### 8. Finish and test the setup
|
||||
|
||||
Continue with the [activating and testing](nym-node/configuration/proxy-configuration#2-activate-and-test-nginx-wss-configuration) your reverse proxy and WSS until the end of the guide.
|
||||
</ Steps>
|
||||
|
||||
<Callout type="info">
|
||||
To copy files to the root directory (paths starting with `/`), you need root permissions on the remote machine. If your server is not running as the root user, the `scp` command won't work directly, as using the `sudo` prefix would apply to your local machine instead of the remote server.
|
||||
|
||||
To resolve this, you can SSH into the remote server and create the configuration files manually using the prefix `sudo` and then copy their content from your local backup.
|
||||
</Callout>
|
||||
|
||||
## Moving a node
|
||||
|
||||
In case of a need to move a Nym Node from one machine to another and avoiding to lose the delegation, here are few steps how to do it.
|
||||
@@ -189,21 +358,71 @@ mkdir ~/.nym
|
||||
#in case no `nym-node` was initialized previously
|
||||
mkdir ~/.nym/nym-nodes
|
||||
```
|
||||
###### 2. Backup `clients.sqlite` database
|
||||
|
||||
###### 2. Move the node data and keys to the new machine
|
||||
- Stop your node
|
||||
```sh
|
||||
service nym-node stop
|
||||
```
|
||||
- Install `sqlite3`
|
||||
```sh
|
||||
apt install sqlite3
|
||||
```
|
||||
- Open sqlite CLI shell inside `clients.sqlite` database
|
||||
```sh
|
||||
sqlite3 ~/.nym/nym-nodes/default-nym-node/data/clients.sqlite
|
||||
```
|
||||
- Create backup called `clients_backup.sqlite`
|
||||
```sh
|
||||
.backup .nym/nym-nodes/default-nym-node/data/clients_backup.sqlite
|
||||
```
|
||||
- Exit sqlite CLI shell
|
||||
```sh
|
||||
.exit
|
||||
```
|
||||
|
||||
###### 3. Move the node data and keys to the new machine
|
||||
|
||||
* Open your **local terminal** (as that one's ssh key is authorized in both of the VPS) and run:
|
||||
```sh
|
||||
scp -r -3 <SOURCE_USER_NAME>@<SOURCE_HOST_ADDRESS>:~/.nym/nym-nodes <TARGET_USER_NAME>@<TARGET_HOST_ADDRESS>:~/.nym/nym-nodes/
|
||||
```
|
||||
|
||||
###### 3. Open new/target VPS terminal and configure the node
|
||||
###### 4. Open new/target VPS terminal and configure the node
|
||||
|
||||
* Edit `~/.nym/nym-nodes/<ID>/config/config.toml` config with the new listening address IP - it's the one under the header `[host]`, called `public_ips = [<PUBLIC_IPS>,]` and add your new location (field `location = <LOCATION>`, formats like: 'Jamaica', or two-letter alpha2 (e.g. 'JM'), three-letter alpha3 (e.g. 'JAM') or three-digit numeric-3 (e.g. '388') can be provided). You can see your IP by running a command `echo "$(curl -4 https://ifconfig.me)"`.
|
||||
|
||||
* Try to run the node and see if everything works.
|
||||
|
||||
* Setup the [systemd](nym-node/configuration.mdx#systemd) automation (don't forget to add the [terms and conditions flag](nym-node/setup.mdx#terms--conditions)) to `ExecStart` command, reload the daemon and run the service. If you want to use the exact same service config file, you can also copy it from one VPS to another following the same logic by opening your **local terminal** (as that one's ssh key is authorized in both of the VPS) and running:
|
||||
###### 5. Restore `clients.sqlite` database
|
||||
|
||||
- Install `sqlite3`
|
||||
```sh
|
||||
apt install sqlite3
|
||||
```
|
||||
- Open sqlite CLI shell inside `clients.sqlite` database
|
||||
```sh
|
||||
sqlite3 ~/.nym/nym-nodes/default-nym-node/data/clients.sqlite
|
||||
```
|
||||
- Restore your backup called `clients_backup.sqlite`
|
||||
```sh
|
||||
.restore .nym/nym-nodes/default-nym-node/data/clients_backup.sqlite
|
||||
```
|
||||
- Exit sqlite CLI shell
|
||||
```sh
|
||||
.exit
|
||||
```
|
||||
- Check integrity
|
||||
```sh
|
||||
sqlite3 ~/.nym/nym-nodes/default-nym-node/data/clients.sqlite "PRAGMA integrity_check;"
|
||||
```
|
||||
The result should return `ok` if the database is intact.
|
||||
|
||||
###### 6. Test & validate your setup
|
||||
* Dry run the node and see if everything works.
|
||||
* Setup the [systemd](nym-node/configuration.mdx#systemd) automation (don't forget to add the [terms and conditions flag](nym-node/setup.mdx#terms--conditions)) to `ExecStart` command, reload the daemon and run the service.
|
||||
|
||||
|
||||
* If you want to use the exact same service config file, you can also copy it from one VPS to another following the same logic by opening your **local terminal** (as that one's ssh key is authorized in both of the VPS) and running:
|
||||
```sh
|
||||
scp -r -3 <SOURCE_USER_NAME>@<SOURCE_HOST_ADDRESS>:/etc/systemd/system/nym-node.service <TARGET_USER_NAME>@<TARGET_HOST_ADDRESS>:/etc/systemd/system/nym-node.service
|
||||
```
|
||||
|
||||
@@ -28,7 +28,7 @@ Do not bond your node to the API if the previous steps weren't finished. Bad con
|
||||
|
||||
Any new bonded node will provide only the bare minimum information: host, identity key and optionally custom port of its HTTP API - we highly recommend to set that one up to `8080`. Everything else will be discovered via the self-described API for maximum flexibility. This also includes the sphinx key, meaning if the API is not exposed, the node will be unable to route any traffic.
|
||||
|
||||
**Every operator has to make sure that their nodes [self-described endpoint works](nodes/performance-and-testing/node-api-check#basic-api-usage), otherwise the node will be un-routable and thus won't get any rewards!**
|
||||
**Every operator has to make sure that their nodes [self-described endpoint works](../performance-and-testing#functionality--performance-check), otherwise the node will be un-routable and thus won't get any rewards!**
|
||||
|
||||
<Callout type="warning" emoji="⚠️">
|
||||
**Reveal your menominc phrase only in areas out of surveillance of other people and never share it with others. Nym team will never ask you for your mnemonic phrase - in case you were asked by someone it's a scam, do *not* reply to it!**
|
||||
@@ -36,7 +36,7 @@ Any new bonded node will provide only the bare minimum information: host, identi
|
||||
|
||||
## Bond via the Desktop wallet (recommended)
|
||||
|
||||
You can bond your `nym-node` via the Desktop wallet.
|
||||
You can bond your `nym-node` via the Desktop wallet. Nym wallet, just like any Nym binary can be downloaded, verified and made executable following [these simple steps](../../binaries/pre-built-binaries#setup-binaries).
|
||||
|
||||
<Steps>
|
||||
|
||||
@@ -120,18 +120,13 @@ From `nym-wallet` version `1.2.15` onward the application allows and prompts ope
|
||||
|
||||
<Steps>
|
||||
|
||||
###### 1. Download the latest wallet from [the release page](https://github.com/nymtech/nym/releases)
|
||||
###### 1. Download the latest wallet from [the release page](https://github.com/nymtech/nym/releases), verify and make executable
|
||||
|
||||
###### 2. Verify the binary and extract it if needed
|
||||
Nym wallet, just like any Nym binary can be downloaded, verified and made executable following [these simple steps](../../binaries/pre-built-binaries#setup-binaries).
|
||||
|
||||
- Download [`hashes.json`](https://github.com/nymtech/nym/releases/download/nym-wallet-v1.2.15/hashes.json)
|
||||
- Open it with your text editor or print it's content with `cat hashes.json`
|
||||
- Run `sha256sum <WALLET_BINARY>` for example `sha256sum ./nym-wallet_1.2.15_amd64.AppImage`
|
||||
- If your have to extract it (like `.tar.gz`) do it
|
||||
###### 2. Open the wallet and sign in
|
||||
|
||||
###### 3. Open the wallet and sign in
|
||||
|
||||
###### 4. Migrate!
|
||||
###### 3. Migrate!
|
||||
|
||||
- Go to Bonding and you will be prompted with such message:
|
||||
|
||||
@@ -161,9 +156,9 @@ If you want to bond your Mix Node via the CLI, then check out the [relevant sect
|
||||
This is not relevant for operators running exclusively `mixnode` functionality. For any type of gateway functionality this is a preparation requirement for the upcoming [ticket rewarding](../../tokenomics/mixnet-rewards#roadmap).
|
||||
</ Callout>
|
||||
|
||||
Every `nym-node` client contains a mnemonic of a Nyx account, generated with node initialisation (first `run` command creating all configuration and data files). This mnemonic is located in `$HOME/.nym/nym-nodes/<ID>/data/cosmos_mnemonic`. **This is *not* the same account as the one used for bondng!**
|
||||
Every `nym-node` client contains a mnemonic of a Nyx account, generated with node initialisation (first `run` command creating all configuration and data files). This mnemonic is located in `$HOME/.nym/nym-nodes/<ID>/data/cosmos_mnemonic`. **This is *not* the same account as the one used for bonding!**
|
||||
|
||||
This client account will be used for the process of redemption of tickets collected by nodes running as `entry-gateway` and `exit-gateway` as the redemption requires gateway to create a multisig proposal on the chain for which the client (node) needs to pay transaction fee.
|
||||
This client account will be used for the process of redemption of [zk-nym tickets](../../../network/cryptography/zk-nym) collected by nodes running as `entry-gateway` and `exit-gateway` as the redemption requires gateway to create a multisig proposal on the chain for which the client (node) needs to pay transaction fee.
|
||||
|
||||
Giving the low transaction cost on Cosmos, funding your client Nyx account with 25 NYM tokens should be more than enough. To do so, follow these steps:
|
||||
|
||||
|
||||
@@ -20,12 +20,12 @@ This documentation page provides a guide on how to set up and run a [NYM NODE](.
|
||||
```sh
|
||||
nym-node
|
||||
Binary Name: nym-node
|
||||
Build Timestamp: 2025-01-15T09:50:54.404428047Z
|
||||
Build Version: 1.3.0
|
||||
Commit SHA: c202e2d598fabda5fc16a6c1e110255857a2b1ca
|
||||
Commit Date: 2025-01-15T10:27:39.000000000+01:00
|
||||
Build Timestamp: 2025-02-04T09:35:42.399220545Z
|
||||
Build Version: 1.4.0
|
||||
Commit SHA: 4c2bf3642e8eec0d55c7753e14429d73ac2d0e99
|
||||
Commit Date: 2025-02-04T10:29:48.000000000+01:00
|
||||
Commit Branch: HEAD
|
||||
rustc Version: 1.84.0
|
||||
rustc Version: 1.84.1
|
||||
rustc Channel: stable
|
||||
cargo Profile: release
|
||||
```
|
||||
@@ -39,21 +39,35 @@ cargo Profile: release
|
||||
<Callout type="info">
|
||||
From `nym-node v1.3.0` operators can choose multiple functionalities for their `nym-node` binary (flagged as `--mode`).
|
||||
|
||||
**However, the clients are yet to be developed to be able to make a proper selection for multi-mode nodes and therefore we ask operators to sign only one functionality to `--mode` option at a time. Please chose out of: `mixnode` or `entry-gateway` or `exit-gateway`.**
|
||||
|
||||
We are developing a design where operators can enable multiple modes, and let the Nym API to position the node according the network's needs in the beginning of each epoch.
|
||||
**However, the clients are yet to be developed to be able to make a proper selection for multi-mode nodes and therefore we ask operators to assign only one functionality to `--mode` option at a time. Please chose out of: `mixnode` or `entry-gateway` or `exit-gateway`. Chosing multiple at once will make your node non-routable!**
|
||||
</Callout>
|
||||
|
||||
Nym Node has three functionalities in the network: `entry-gateway`, `mixnode` and `exit-gateway`.
|
||||
### Mixnet Routing
|
||||
|
||||
- **Entry Gateway (`--mode entry-gateway`)**: A the node to which local client connects. It checks the bandwidth allowance, using [zk-nyms](../../../network/cryptography/zk-nym) and either sends [Sphinx packets](../../../network/cryptography/sphinx) through the mixnet or directly to Exit Gateway in case of dVPN (2-hop) routing. This node also recieves replies and sends them back to users local client.
|
||||
***Mixnet mode (5-hop) is the full anonymising option of NymVPN. Read more about the Mixnet architecture [here](http://localhost:3000/docs/network/architecture)***
|
||||
|
||||
- **Mixnode (`--mode mixnode`)**: Nodes organized in three layers, randmoly selected every epoch (60 minutes), mixing Sphinx packets, adding a slight latency to defend users agains time correlation attacks and sending them further to the next layer or to the Exit Gateway
|
||||
Nym Node has three functionalities in the Mixnet: `entry-gateway`, `mixnode` and `exit-gateway`. These are selected with a flag `--mode <MODE>` alongside `nym-node` command `run` .
|
||||
|
||||
- **Entry Gateway (`--mode entry-gateway`)**: A node to which clients connect. It checks the bandwidth allowance, using [zk-nyms](../../../network/cryptography/zk-nym) and either sends [Sphinx packets](../../../network/cryptography/sphinx) through the mixnet or directly to Exit Gateway in case of dVPN (2-hop) routing. This node also receives replies and sends them back to users local client.
|
||||
|
||||
- **Mixnode (`--mode mixnode`)**: Nodes organized in three layers, randomly selected every epoch (60 minutes), mixing Sphinx packets, adding a slight latency to defend users agains time correlation attacks and sending them further to the next layer or to the Exit Gateway
|
||||
|
||||
- **Exit Gateway (`--mode exit-gateway`)**: The final node in the mixnet. It puts all packets together and using inbuilt Network requester and IP packet router, it sends traffic to the open internet. This node also recieves replies and sends them back to the user client.
|
||||
|
||||
<Callout type="warning">
|
||||
Exit Gateway is the only node routing data directly to the open internet. Therefore it exposes IP of operators server (VPS) to abuse complains. Before you decide to run an Exit Gateway, please read our [Community Counsel pages](../../community-counsel/exit-gateway) containing more information and some legal content.
|
||||
Exit Gateway is the only mode routing data directly to the open internet. Therefore it exposes IP of operators server (VPS) to abuse complains. Before you decide to run an Exit Gateway, please read our [Community Counsel pages](../../community-counsel/exit-gateway) containing more information and some legal content.
|
||||
</Callout>
|
||||
|
||||
### dVPN Routing
|
||||
|
||||
***dVPN routing (2-hop) is the Fast option of NymVPN. It runs a wireguard tunnel inside another wireguard tunnel. dVPN uses 2 Gateway layers (entry and exit) and no Mixnode layers. If an operator announces and [correctly configure](configuration#connectivity-test-and-configuration) their node to route wireguard, the node can be chosen as entry or exit by any client at any time.***
|
||||
|
||||
To activate wireguard routing, operators need to add `--wireguard-enabled true` alongside `nym-node` command `run`.
|
||||
|
||||
<Callout type="warning">
|
||||
Wireguard nodes route data directly to the open internet. Therefore it exposes IP of operators server (VPS) to abuse complains. Before you decide to run a node with active wireguard routing, please read our [Community Counsel pages](../../community-counsel/exit-gateway) containing more information and some legal content.
|
||||
|
||||
**Wireguard mode has no exit policy right now - we are working on the implementation.**
|
||||
</Callout>
|
||||
|
||||
Everything essential for each mode exists on `nym-node` by default. For instance, if you run a Mixnode, you'll find that a NR (Network Requester) and IPR (IP Packet Router) addresses exist, but they will be ignored in `mixnode` mode.
|
||||
@@ -178,7 +192,7 @@ http://<IPv4_ADDRESS>/api/v1/swagger/#/
|
||||
https://<HOSTNAME>/api/v1/swagger/#/
|
||||
```
|
||||
|
||||
For example to determine which mode your node is running, you can check the `:8080/api/v1/roles` endpoint. For example:
|
||||
For example to determine which mode your node is running, you can check the `:8080/api/v1/roles` endpoint:
|
||||
```sh
|
||||
# sustitude <IPv4_ADDRESS> or <HOSTNAME> with the one corresponding to your node
|
||||
# for http
|
||||
|
||||
@@ -8,6 +8,34 @@ As developers we need to be constantly improving the software. Operators have as
|
||||
|
||||
Therefore [monitoring](#monitoring) and [testing](#testing) are essential pieces of our common work. We call out all Nym operators to join the efforts!
|
||||
|
||||
## Functionality & Performance Check
|
||||
|
||||
If you want to just quickly check your nodes performance, connectivity and much more, visit [harbourmaster.nymtech.net](https://harbourmaster.nymtech.net/).
|
||||
|
||||
For more information about available endpoints and their status, you can refer to:
|
||||
```sh
|
||||
# sustitude <IPv4_ADDRESS> or <HOSTNAME> with the one corresponding to your node
|
||||
# for http
|
||||
http://<IPv4_ADDRESS>:8080/api/v1/swagger/#/
|
||||
# or
|
||||
http://<IPv4_ADDRESS>/api/v1/swagger/#/
|
||||
|
||||
# for reversed proxy/WSS
|
||||
https://<HOSTNAME>/api/v1/swagger/#/
|
||||
```
|
||||
|
||||
For example to determine which mode your node is running, you can check the `:8080/api/v1/roles` endpoint:
|
||||
```sh
|
||||
# sustitude <IPv4_ADDRESS> or <HOSTNAME> with the one corresponding to your node
|
||||
# for http
|
||||
http://<IPv4_ADDRESS>:8080/api/v1/roles
|
||||
# or
|
||||
http://<IPv4_ADDRESS>/api/v1/roles
|
||||
|
||||
# for reversed proxy/WSS
|
||||
https://<HOSTNAME>/api/v1/roles
|
||||
```
|
||||
|
||||
## Monitoring
|
||||
|
||||
There are multiple ways to monitor performance of nodes and the machines on which they run. For the purpose of maximal privacy and decentralisation of the data - preventing Nym Mixnet from any global adversary takeover - we created these pages as a source of mutual empowerment, a place where operators can share and learn new skills to **setup metrics monitors on their own infrastructure**.
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
{
|
||||
"gateway-probe": "Gateway Probe",
|
||||
"node-api-check": "Node API Check",
|
||||
"prometheus-grafana": "Prometheus & Grafana"
|
||||
}
|
||||
|
||||
@@ -146,7 +146,7 @@ Besides these values, the API is also looking whether the node is bonded in Mixn
|
||||
**The node score is calculated with this formula:**
|
||||
|
||||
<Callout type="info" emoji="📌">
|
||||
> **active_set_selection_probability = total_stake \* ( config_score \* node_performance ) ^ 20 )**
|
||||
> **active_set_selection_probability = total_stake \* (( config_score \* node_performance ) ^ 20 )**
|
||||
</Callout>
|
||||
|
||||
Note that the score helps prioritize some nodes over others. If all available nodes have the same score, then the selection is done uniformly at random. By raising the config and performance components to 20, values of these parameters that are below one incur a heavy penalization for the node’s selection chances.
|
||||
|
||||
@@ -113,8 +113,6 @@ There are a few community explorers as well.
|
||||
|
||||
Enter your **identity key** to find your node. Check the contents of the `Mixnode stats` and `Routing score` sections.
|
||||
|
||||
You can run [Node API Check CLI](../testing/node-api-check.mdx) to query all API endpoints of your node at once.
|
||||
|
||||
[Here](https://github.com/cosmos/chain-registry/blob/master/nyx/chain.json#L158-L187) is a dictionary with Nyx chain registry entry regarding all explorers.
|
||||
|
||||
If you want more information, or if your node isn't showing up on the explorer of your choice and you want to double-check, here are some examples on how to check if the node is configured properly.
|
||||
@@ -345,7 +343,7 @@ In case your Gateway appeared on the [blacklist](https://validator.nymtech.net/a
|
||||
|
||||
**What to do**
|
||||
|
||||
Begin with a sanity check by opening [harbourmaster.nymtech.net](https://harbourmaster.nymtech.net) and check your node there. To query all API endpoints of your node at once, you can run [Node API Check CLI](../testing/node-api-check.mdx). To see IPv4 and IPv6 routing in real time (harbourmaster can have a cache up to 90 min), run [Gateway Probe CLI](../testing/gateway-probe.mdx).
|
||||
Begin with a sanity check by opening [harbourmaster.nymtech.net](https://harbourmaster.nymtech.net) and check your node there. To see IPv4 and IPv6 routing in real time (harbourmaster can have a cache up to 90 min), run [Gateway Probe CLI](../testing/gateway-probe.mdx).
|
||||
|
||||
Then follow these steps:
|
||||
|
||||
|
||||
@@ -1,62 +1,62 @@
|
||||
'use client'
|
||||
"use client";
|
||||
|
||||
import * as React from 'react'
|
||||
import { Alert, AlertTitle, Box, CircularProgress, Grid } from '@mui/material'
|
||||
import { useParams } from 'next/navigation'
|
||||
import { ColumnsType, DetailTable } from '@/app/components/DetailTable'
|
||||
import { ComponentError } from '@/app/components/ComponentError'
|
||||
import { ContentCard } from '@/app/components/ContentCard'
|
||||
import { UptimeChart } from '@/app/components/UptimeChart'
|
||||
import {
|
||||
NymNodeContextProvider,
|
||||
useNymNodeContext,
|
||||
} from '@/app/context/node'
|
||||
import { useMainContext } from '@/app/context/main'
|
||||
import { Title } from '@/app/components/Title'
|
||||
import * as React from "react";
|
||||
import { Alert, AlertTitle, Box, CircularProgress, Grid } from "@mui/material";
|
||||
import { useParams } from "next/navigation";
|
||||
import { ColumnsType, DetailTable } from "@/app/components/DetailTable";
|
||||
import { ComponentError } from "@/app/components/ComponentError";
|
||||
import { ContentCard } from "@/app/components/ContentCard";
|
||||
import { UptimeChart } from "@/app/components/UptimeChart";
|
||||
import { NymNodeContextProvider, useNymNodeContext } from "@/app/context/node";
|
||||
import { useMainContext } from "@/app/context/main";
|
||||
import { Title } from "@/app/components/Title";
|
||||
import Paper from "@mui/material/Paper";
|
||||
import Table from '@mui/material/Table';
|
||||
import TableBody from '@mui/material/TableBody';
|
||||
import TableCell from '@mui/material/TableCell';
|
||||
import TableContainer from '@mui/material/TableContainer';
|
||||
import TableRow from '@mui/material/TableRow';
|
||||
import {humanReadableCurrencyToString} from "@/app/utils/currency";
|
||||
import {DeclaredRole} from "@/app/network-components/nodes/DeclaredRole";
|
||||
import {NodeDelegationsTable, VestingDelegationWarning} from "@/app/network-components/nodes/[id]/NodeDelegationsTable";
|
||||
import Table from "@mui/material/Table";
|
||||
import TableBody from "@mui/material/TableBody";
|
||||
import TableCell from "@mui/material/TableCell";
|
||||
import TableContainer from "@mui/material/TableContainer";
|
||||
import TableRow from "@mui/material/TableRow";
|
||||
import { humanReadableCurrencyToString } from "@/app/utils/currency";
|
||||
import { DeclaredRole } from "@/app/network-components/nodes/DeclaredRole";
|
||||
import {
|
||||
NodeDelegationsTable,
|
||||
VestingDelegationWarning,
|
||||
} from "@/app/network-components/nodes/[id]/NodeDelegationsTable";
|
||||
|
||||
const columns: ColumnsType[] = [
|
||||
{
|
||||
field: 'identity_key',
|
||||
title: 'Identity Key',
|
||||
headerAlign: 'left',
|
||||
field: "identity_key",
|
||||
title: "Identity Key",
|
||||
headerAlign: "left",
|
||||
width: 230,
|
||||
},
|
||||
{
|
||||
field: 'bond',
|
||||
title: 'Bond',
|
||||
headerAlign: 'left',
|
||||
field: "bond",
|
||||
title: "Bond",
|
||||
headerAlign: "left",
|
||||
},
|
||||
{
|
||||
field: 'host',
|
||||
title: 'IP',
|
||||
headerAlign: 'left',
|
||||
field: "host",
|
||||
title: "IP",
|
||||
headerAlign: "left",
|
||||
width: 99,
|
||||
},
|
||||
{
|
||||
field: 'location',
|
||||
title: 'Location',
|
||||
headerAlign: 'left',
|
||||
field: "location",
|
||||
title: "Location",
|
||||
headerAlign: "left",
|
||||
},
|
||||
{
|
||||
field: 'owner',
|
||||
title: 'Owner',
|
||||
headerAlign: 'left',
|
||||
field: "owner",
|
||||
title: "Owner",
|
||||
headerAlign: "left",
|
||||
},
|
||||
{
|
||||
field: 'version',
|
||||
title: 'Version',
|
||||
headerAlign: 'left',
|
||||
field: "version",
|
||||
title: "Version",
|
||||
headerAlign: "left",
|
||||
},
|
||||
]
|
||||
];
|
||||
|
||||
interface NodeEnrichedRowType {
|
||||
node_id: number;
|
||||
@@ -71,28 +71,33 @@ interface NodeEnrichedRowType {
|
||||
function nodeEnrichedToGridRow(node: any): NodeEnrichedRowType {
|
||||
return {
|
||||
node_id: node.node_id,
|
||||
owner: node.bond_information?.owner || '',
|
||||
identity_key: node.bond_information?.node?.identity_key || '',
|
||||
location: node.location?.country_name || '',
|
||||
owner: node.bond_information?.owner || "",
|
||||
identity_key: node.bond_information?.node?.identity_key || "",
|
||||
location: node.location?.country_name || "",
|
||||
bond: node.bond_information?.original_pledge.amount || 0, // TODO: format
|
||||
host: node.bond_information?.node?.host || '',
|
||||
version: node.description?.build_information?.build_version || '',
|
||||
host: node.bond_information?.node?.host || "",
|
||||
version: node.description?.build_information?.build_version || "",
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Shows nym node details
|
||||
*/
|
||||
const PageNymNodeDetailsWithState = ({
|
||||
selectedNymNode,
|
||||
}: {
|
||||
selectedNymNode?: any
|
||||
selectedNymNode?: any;
|
||||
}) => {
|
||||
const { uptimeHistory } = useNymNodeContext()
|
||||
const enrichedData = React.useMemo(() => selectedNymNode ? [nodeEnrichedToGridRow(selectedNymNode)] : [], []);
|
||||
const { uptimeHistory } = useNymNodeContext();
|
||||
const enrichedData = React.useMemo(
|
||||
() => (selectedNymNode ? [nodeEnrichedToGridRow(selectedNymNode)] : []),
|
||||
[]
|
||||
);
|
||||
|
||||
const hasVestingContractDelegations = React.useMemo(() => selectedNymNode?.delegations?.filter((d: any) => d.proxy)?.length, [selectedNymNode]);
|
||||
const hasVestingContractDelegations = React.useMemo(
|
||||
() => selectedNymNode?.delegations?.filter((d: any) => d.proxy)?.length,
|
||||
[selectedNymNode]
|
||||
);
|
||||
|
||||
return (
|
||||
<Box component="main">
|
||||
@@ -109,81 +114,128 @@ const PageNymNodeDetailsWithState = ({
|
||||
</Grid>
|
||||
|
||||
<Grid container mt={2} spacing={2}>
|
||||
{selectedNymNode.rewarding_details &&
|
||||
<Grid item xs={12} md={4}>
|
||||
<TableContainer component={Paper}>
|
||||
<Table>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableCell colSpan={2}>
|
||||
Delegations and Rewards
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell component="th" scope="row" sx={{ color: "inherit" }}>
|
||||
<strong>Operator</strong>
|
||||
</TableCell>
|
||||
<TableCell align="right">
|
||||
{humanReadableCurrencyToString({ amount : selectedNymNode.rewarding_details.operator.split('.')[0], denom: "unym" })}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell component="th" scope="row" sx={{ color: "inherit" }}>
|
||||
<strong>
|
||||
{hasVestingContractDelegations ?
|
||||
<VestingDelegationWarning plural={true}>
|
||||
Delegates ({selectedNymNode.rewarding_details.unique_delegations} delegates)
|
||||
</VestingDelegationWarning> :
|
||||
<>Delegates ({selectedNymNode.rewarding_details.unique_delegations} delegates)</>
|
||||
}
|
||||
</strong>
|
||||
</TableCell>
|
||||
<TableCell align="right">
|
||||
{humanReadableCurrencyToString({ amount : selectedNymNode.rewarding_details.delegates.split('.')[0], denom: "unym" })}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell component="th" scope="row" sx={{ color: "inherit" }}>
|
||||
<strong>Profit margin</strong>
|
||||
</TableCell>
|
||||
<TableCell align="right">
|
||||
{selectedNymNode.rewarding_details.cost_params.profit_margin_percent * 100}%
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell component="th" scope="row" sx={{ color: "inherit" }}>
|
||||
<strong>Operator costs</strong>
|
||||
</TableCell>
|
||||
<TableCell align="right">
|
||||
{humanReadableCurrencyToString(selectedNymNode.rewarding_details.cost_params.interval_operating_cost)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</Grid>}
|
||||
{selectedNymNode.rewarding_details && (
|
||||
<Grid item xs={12} md={4}>
|
||||
<TableContainer component={Paper}>
|
||||
<Table>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableCell colSpan={2}>Delegations and Rewards</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell
|
||||
component="th"
|
||||
scope="row"
|
||||
sx={{ color: "inherit" }}
|
||||
>
|
||||
<strong>Operator</strong>
|
||||
</TableCell>
|
||||
<TableCell align="right">
|
||||
{humanReadableCurrencyToString({
|
||||
amount:
|
||||
selectedNymNode.rewarding_details.operator.split(
|
||||
"."
|
||||
)[0],
|
||||
denom: "unym",
|
||||
})}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell
|
||||
component="th"
|
||||
scope="row"
|
||||
sx={{ color: "inherit" }}
|
||||
>
|
||||
<strong>
|
||||
{hasVestingContractDelegations ? (
|
||||
<VestingDelegationWarning plural={true}>
|
||||
Delegates (
|
||||
{
|
||||
selectedNymNode.rewarding_details
|
||||
.unique_delegations
|
||||
}{" "}
|
||||
delegates)
|
||||
</VestingDelegationWarning>
|
||||
) : (
|
||||
<>
|
||||
Delegates (
|
||||
{
|
||||
selectedNymNode.rewarding_details
|
||||
.unique_delegations
|
||||
}{" "}
|
||||
delegates)
|
||||
</>
|
||||
)}
|
||||
</strong>
|
||||
</TableCell>
|
||||
<TableCell align="right">
|
||||
{humanReadableCurrencyToString({
|
||||
amount:
|
||||
selectedNymNode.rewarding_details.delegates.split(
|
||||
"."
|
||||
)[0],
|
||||
denom: "unym",
|
||||
})}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell
|
||||
component="th"
|
||||
scope="row"
|
||||
sx={{ color: "inherit" }}
|
||||
>
|
||||
<strong>Profit margin</strong>
|
||||
</TableCell>
|
||||
<TableCell align="right">
|
||||
{selectedNymNode.rewarding_details.cost_params
|
||||
.profit_margin_percent * 100}
|
||||
%
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell
|
||||
component="th"
|
||||
scope="row"
|
||||
sx={{ color: "inherit" }}
|
||||
>
|
||||
<strong>Operator costs</strong>
|
||||
</TableCell>
|
||||
<TableCell align="right">
|
||||
{humanReadableCurrencyToString(
|
||||
selectedNymNode.rewarding_details.cost_params
|
||||
.interval_operating_cost
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</Grid>
|
||||
)}
|
||||
|
||||
{selectedNymNode.description?.declared_role && <Grid item xs={12} md={4}>
|
||||
<TableContainer component={Paper}>
|
||||
<Table>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableCell colSpan={2}>
|
||||
Node roles
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
Self declared roles
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<DeclaredRole declared_role={selectedNymNode.description?.declared_role}/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</Grid>}
|
||||
{selectedNymNode.description?.declared_role && (
|
||||
<Grid item xs={12} md={4}>
|
||||
<TableContainer component={Paper}>
|
||||
<Table>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableCell colSpan={2}>Node roles</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>Self declared roles</TableCell>
|
||||
<TableCell>
|
||||
<DeclaredRole
|
||||
declared_role={
|
||||
selectedNymNode.description?.declared_role
|
||||
}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</Grid>
|
||||
)}
|
||||
</Grid>
|
||||
|
||||
<Grid container spacing={2} mt={2}>
|
||||
@@ -205,42 +257,41 @@ const PageNymNodeDetailsWithState = ({
|
||||
</Grid>
|
||||
|
||||
<Box mt={2}>
|
||||
<NodeDelegationsTable node={selectedNymNode}/>
|
||||
<NodeDelegationsTable node={selectedNymNode} />
|
||||
</Box>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Guard component to handle loading and not found states
|
||||
*/
|
||||
const PageNymNodeDetailGuard = () => {
|
||||
const [selectedNode, setSelectedNode] = React.useState<any>()
|
||||
const [selectedNode, setSelectedNode] = React.useState<any>();
|
||||
const [isLoading, setLoading] = React.useState<boolean>(true);
|
||||
const [error, setError] = React.useState<string>();
|
||||
const { fetchNodeById } = useMainContext()
|
||||
const { id } = useParams()
|
||||
const { fetchNodeById } = useMainContext();
|
||||
const { id } = useParams();
|
||||
|
||||
React.useEffect(() => {
|
||||
setSelectedNode(undefined);
|
||||
setLoading(true);
|
||||
(async () => {
|
||||
if(typeof(id) === "string") {
|
||||
if (typeof id === "string") {
|
||||
try {
|
||||
const res = await fetchNodeById(Number.parseInt(id));
|
||||
setSelectedNode(res);
|
||||
} catch(e: any) {
|
||||
} catch (e: any) {
|
||||
setError(e.message);
|
||||
}
|
||||
finally {
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
})();
|
||||
}, [id])
|
||||
}, [id, fetchNodeById]);
|
||||
|
||||
if (isLoading) {
|
||||
return <CircularProgress />
|
||||
return <CircularProgress />;
|
||||
}
|
||||
|
||||
// loaded, but not found
|
||||
@@ -248,31 +299,38 @@ const PageNymNodeDetailGuard = () => {
|
||||
return (
|
||||
<Alert severity="warning">
|
||||
<AlertTitle>Nym node not found</AlertTitle>
|
||||
Sorry, we could not find a node with id <code>{id || ''}</code>
|
||||
Sorry, we could not find a node with id <code>{id || ""}</code>
|
||||
</Alert>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return <PageNymNodeDetailsWithState selectedNymNode={selectedNode} />
|
||||
}
|
||||
if (!selectedNode) {
|
||||
return (
|
||||
<Alert severity="warning">
|
||||
<AlertTitle>Legacy Node</AlertTitle>
|
||||
Unable to load details for the selected Nym node.
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
|
||||
return <PageNymNodeDetailsWithState selectedNymNode={selectedNode} />;
|
||||
};
|
||||
|
||||
/**
|
||||
* Wrapper component that adds the node content based on the `id` in the address URL
|
||||
*/
|
||||
const PageNymNodeDetail = () => {
|
||||
const { id } = useParams()
|
||||
const { id } = useParams();
|
||||
|
||||
if (!id || typeof id !== 'string') {
|
||||
return (
|
||||
<Alert severity="error">Oh no! Could not find that node</Alert>
|
||||
)
|
||||
if (!id || typeof id !== "string") {
|
||||
return <Alert severity="error">Oh no! Could not find that node</Alert>;
|
||||
}
|
||||
|
||||
return (
|
||||
<NymNodeContextProvider nymNodeId={id}>
|
||||
<PageNymNodeDetailGuard />
|
||||
</NymNodeContextProvider>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default PageNymNodeDetail
|
||||
export default PageNymNodeDetail;
|
||||
|
||||
@@ -1,4 +1,14 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {};
|
||||
const nextConfig = {
|
||||
async redirects() {
|
||||
return [
|
||||
{
|
||||
source: '/network-components/mixnode/:id', // Match the old URL
|
||||
destination: '/network-components/nodes/:id', // Redirect to the new URL
|
||||
permanent: true,
|
||||
},
|
||||
];
|
||||
},
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
|
||||
@@ -28,7 +28,8 @@
|
||||
"../assets/*"
|
||||
]
|
||||
},
|
||||
"moduleResolution": "node"
|
||||
"moduleResolution": "node",
|
||||
"target": "ES2017"
|
||||
},
|
||||
"include": [
|
||||
"next-env.d.ts",
|
||||
|
||||
@@ -7,7 +7,7 @@ use tracing::error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum BandwidthError {
|
||||
#[error("Provided bandwidth credential asks for more bandwidth than it is supported to add at once (credential value: {0}, supported: {}). Try to split it before attempting again", i64::MAX)]
|
||||
#[error("Provided bandwidth credential asks for more bandwidth than it is supported to add at once (credential value: {0}, supported: {s}). Try to split it before attempting again", s = i64::MAX)]
|
||||
UnsupportedBandwidthValue(u64),
|
||||
|
||||
#[error("failed to parse the bandwidth voucher value: {source}")]
|
||||
|
||||
@@ -16,7 +16,6 @@ async-trait = { workspace = true }
|
||||
bs58 = { workspace = true }
|
||||
bip39 = { workspace = true }
|
||||
bincode.workspace = true
|
||||
bloomfilter = { workspace = true }
|
||||
cfg-if = { workspace = true }
|
||||
clap = { workspace = true, features = ["cargo", "derive", "env"] }
|
||||
console-subscriber = { workspace = true, optional = true } # validator-api needs to be built with RUSTFLAGS="--cfg tokio_unstable"
|
||||
@@ -84,7 +83,6 @@ tracing = { workspace = true }
|
||||
#ephemera = { path = "../ephemera" }
|
||||
nym-bandwidth-controller = { path = "../common/bandwidth-controller" }
|
||||
nym-ecash-contract-common = { path = "../common/cosmwasm-smart-contracts/ecash-contract" }
|
||||
nym-ecash-double-spending = { path = "../common/ecash-double-spending" }
|
||||
nym-ecash-time = { path = "../common/ecash-time", features = ["expiration"] }
|
||||
nym-coconut-dkg-common = { path = "../common/cosmwasm-smart-contracts/coconut-dkg" }
|
||||
nym-compact-ecash = { path = "../common/nym_offline_compact_ecash" }
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
/*
|
||||
* Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
|
||||
DROP TABLE partial_bloomfilter;
|
||||
DROP TABLE bloomfilter_parameters;
|
||||
@@ -28,6 +28,7 @@ nym-serde-helpers = { path = "../../common/serde-helpers", features = ["bs58", "
|
||||
nym-credentials-interface = { path = "../../common/credentials-interface" }
|
||||
nym-crypto = { path = "../../common/crypto", features = ["serde", "asymmetric"] }
|
||||
|
||||
nym-config = { path = "../../common/config" }
|
||||
nym-ecash-time = { path = "../../common/ecash-time" }
|
||||
nym-compact-ecash = { path = "../../common/nym_offline_compact_ecash" }
|
||||
nym-contracts-common = { path = "../../common/cosmwasm-smart-contracts/contracts-common", features = ["naive_float"] }
|
||||
|
||||
@@ -12,6 +12,12 @@ pub mod models;
|
||||
pub mod nym_nodes;
|
||||
pub mod pagination;
|
||||
|
||||
// The response type we fetch from the network details endpoint.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct NymNetworkDetailsResponse {
|
||||
pub network: nym_config::defaults::NymNetworkDetails,
|
||||
}
|
||||
|
||||
pub trait Deprecatable {
|
||||
fn deprecate(self) -> Deprecated<Self>
|
||||
where
|
||||
|
||||
@@ -6,6 +6,7 @@ use crate::ecash::state::EcashState;
|
||||
use crate::node_status_api::models::{AxumErrorResponse, AxumResult};
|
||||
use crate::support::http::state::AppState;
|
||||
use axum::extract::State;
|
||||
use axum::http::StatusCode;
|
||||
use axum::{Json, Router};
|
||||
use nym_api_requests::constants::MIN_BATCH_REDEMPTION_DELAY;
|
||||
use nym_api_requests::ecash::models::{
|
||||
@@ -89,12 +90,6 @@ async fn verify_ticket(
|
||||
});
|
||||
}
|
||||
|
||||
// check the bloomfilter for obvious double-spending so that we wouldn't need to waste time on crypto verification
|
||||
// TODO: when blacklisting is implemented, this should get removed
|
||||
if state.check_bloomfilter(sn).await {
|
||||
return reject_ticket(EcashTicketVerificationRejection::ReplayedTicket);
|
||||
}
|
||||
|
||||
// actual double spend detection with storage
|
||||
if let Some(previous_payment) = state.get_ticket_data_by_serial_number(sn).await? {
|
||||
match nym_compact_ecash::identify::identify(
|
||||
@@ -127,27 +122,17 @@ async fn verify_ticket(
|
||||
return reject_ticket(EcashTicketVerificationRejection::InvalidTicket);
|
||||
}
|
||||
|
||||
// finally get EXCLUSIVE lock on the bloomfilter, check if for the final time and insert the SN
|
||||
let was_present = state
|
||||
.update_bloomfilter(sn, spend_date, today_ecash)
|
||||
// store credential and check whether it wasn't already there (due to a parallel request)
|
||||
let was_inserted = state
|
||||
.store_verified_ticket(credential_data, gateway_cosmos_addr)
|
||||
.await?;
|
||||
if was_present {
|
||||
if !was_inserted {
|
||||
return reject_ticket(EcashTicketVerificationRejection::ReplayedTicket);
|
||||
}
|
||||
|
||||
//store credential
|
||||
state
|
||||
.store_verified_ticket(credential_data, gateway_cosmos_addr)
|
||||
.await?;
|
||||
|
||||
Ok(Json(EcashTicketVerificationResponse { verified: Ok(()) }))
|
||||
}
|
||||
|
||||
// // for particular SN returns what gateway has submitted it and whether it has been verified correctly
|
||||
// async fn credential_status() -> ! {
|
||||
// todo!()
|
||||
// }
|
||||
|
||||
#[utoipa::path(
|
||||
tag = "Ecash",
|
||||
post,
|
||||
@@ -240,5 +225,8 @@ async fn batch_redeem_tickets(
|
||||
)]
|
||||
#[deprecated]
|
||||
async fn double_spending_filter_v1() -> AxumResult<Json<SpentCredentialsResponse>> {
|
||||
AxumResult::Err(AxumErrorResponse::internal_msg("permanently restricted"))
|
||||
AxumResult::Err(AxumErrorResponse::new(
|
||||
"permanently restricted",
|
||||
StatusCode::GONE,
|
||||
))
|
||||
}
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
@@ -0,0 +1,89 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::ecash::storage::EcashStorageExt;
|
||||
use crate::node_status_api::models::NymApiStorageError;
|
||||
use crate::support::config::Config;
|
||||
use crate::support::storage::NymApiStorage;
|
||||
use nym_ecash_time::ecash_today_date;
|
||||
use nym_task::TaskClient;
|
||||
use std::time::Duration;
|
||||
use time::Date;
|
||||
use tokio::task::JoinHandle;
|
||||
use tracing::{debug, error, trace};
|
||||
|
||||
/// Task responsible for clearing out the database from stale ecash data,
|
||||
/// such as verified tickets or issued partial ticketbooks.
|
||||
pub struct EcashBackgroundStateCleaner {
|
||||
run_interval: Duration,
|
||||
issued_ticketbooks_retention_period_days: u32,
|
||||
verified_tickets_retention_period_days: u32,
|
||||
|
||||
storage: NymApiStorage,
|
||||
task_client: TaskClient,
|
||||
}
|
||||
|
||||
impl EcashBackgroundStateCleaner {
|
||||
pub fn new(global_config: &Config, storage: NymApiStorage, task_client: TaskClient) -> Self {
|
||||
EcashBackgroundStateCleaner {
|
||||
run_interval: global_config.ecash_signer.debug.stale_data_cleaner_interval,
|
||||
issued_ticketbooks_retention_period_days: global_config
|
||||
.ecash_signer
|
||||
.debug
|
||||
.issued_ticketbooks_retention_period_days,
|
||||
verified_tickets_retention_period_days: global_config
|
||||
.ecash_signer
|
||||
.debug
|
||||
.verified_tickets_retention_period_days,
|
||||
storage,
|
||||
task_client,
|
||||
}
|
||||
}
|
||||
|
||||
fn ticketbook_retention_cutoff(&self) -> Date {
|
||||
ecash_today_date()
|
||||
- time::Duration::days(self.issued_ticketbooks_retention_period_days as i64)
|
||||
}
|
||||
|
||||
fn verified_tickets_retention_cutoff(&self) -> Date {
|
||||
ecash_today_date()
|
||||
- time::Duration::days(self.verified_tickets_retention_period_days as i64)
|
||||
}
|
||||
|
||||
async fn clean_stale_data(&self) -> Result<(), NymApiStorageError> {
|
||||
// 1. remove old verified tickets
|
||||
self.storage
|
||||
.remove_expired_verified_tickets(self.verified_tickets_retention_cutoff())
|
||||
.await?;
|
||||
|
||||
// 2. remove old issued partial ticketbooks
|
||||
self.storage
|
||||
.remove_old_issued_ticketbooks(self.ticketbook_retention_cutoff())
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn run(&mut self) {
|
||||
let mut ticker = tokio::time::interval(self.run_interval);
|
||||
loop {
|
||||
tokio::select! {
|
||||
_ = self.task_client.recv() => {
|
||||
trace!("EcashBackgroundStateCleaner: Received shutdown");
|
||||
break;
|
||||
}
|
||||
_ = ticker.tick() => {
|
||||
if let Err(err) = self.clean_stale_data().await {
|
||||
error!("failed to clear out stale data: {err}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
debug!("EcashBackgroundStateCleaner: exiting");
|
||||
}
|
||||
|
||||
pub(crate) fn start(mut self) -> JoinHandle<()> {
|
||||
tokio::spawn(async move { self.run().await })
|
||||
}
|
||||
}
|
||||
@@ -2,106 +2,14 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::ecash::error::EcashError;
|
||||
use crate::ecash::state::local::TicketDoubleSpendingFilter;
|
||||
use crate::ecash::storage::EcashStorageExt;
|
||||
use crate::support::storage::NymApiStorage;
|
||||
use futures::{stream, StreamExt};
|
||||
use nym_compact_ecash::constants;
|
||||
use nym_config::defaults::BloomfilterParameters;
|
||||
use nym_dkg::Threshold;
|
||||
use nym_ecash_double_spending::{DoubleSpendingFilter, DoubleSpendingFilterBuilder};
|
||||
use nym_ecash_time::{cred_exp_date, ecash_today};
|
||||
use nym_validator_client::EcashApiClient;
|
||||
use std::future::Future;
|
||||
use time::ext::NumericalDuration;
|
||||
use time::Date;
|
||||
use tokio::sync::Mutex;
|
||||
use tracing::{debug, error, info, warn};
|
||||
|
||||
// attempt to completely rebuild the bloomfilter data for given day
|
||||
async fn try_rebuild_today_bloomfilter(
|
||||
today: Date,
|
||||
params: BloomfilterParameters,
|
||||
storage: &NymApiStorage,
|
||||
) -> Result<DoubleSpendingFilter, EcashError> {
|
||||
info!("rebuilding bloomfilter for {today}");
|
||||
|
||||
let tickets = storage.get_all_spent_tickets_on(today).await?;
|
||||
debug!(
|
||||
"there are {} tickets to insert into the filter",
|
||||
tickets.len()
|
||||
);
|
||||
|
||||
let mut filter = DoubleSpendingFilter::new_empty(params);
|
||||
for ticket in tickets {
|
||||
filter.set(&ticket.serial_number)
|
||||
}
|
||||
Ok(filter)
|
||||
}
|
||||
|
||||
pub(crate) async fn prepare_partial_bloomfilter_builder(
|
||||
storage: &NymApiStorage,
|
||||
params: BloomfilterParameters,
|
||||
params_id: i64,
|
||||
start: Date,
|
||||
days: i64,
|
||||
) -> Result<DoubleSpendingFilterBuilder, EcashError> {
|
||||
info!(
|
||||
"attempting to rebuild partial bloomfilter starting at {start} which includes {days} days"
|
||||
);
|
||||
|
||||
let mut filter_builder = DoubleSpendingFilter::builder(params);
|
||||
for i in 0..days {
|
||||
let date = start - i.days();
|
||||
let Some(bitmap) = storage
|
||||
.try_load_partial_bloomfilter_bitmap(date, params_id)
|
||||
.await?
|
||||
else {
|
||||
warn!("missing double spending bloomfilter bitmap for {date} (if this API hasn't been running for at least {days} day(s) since 'ecash'-based zk-nyms were introduced this is expected)");
|
||||
continue;
|
||||
};
|
||||
if !filter_builder.add_bytes(&bitmap) {
|
||||
error!(
|
||||
"failed to add bitmap from {date} to the global bloomfilter. it may be malformed!"
|
||||
);
|
||||
}
|
||||
}
|
||||
Ok(filter_builder)
|
||||
}
|
||||
|
||||
pub(super) async fn try_rebuild_bloomfilter(
|
||||
storage: &NymApiStorage,
|
||||
) -> Result<TicketDoubleSpendingFilter, EcashError> {
|
||||
info!("attempting to rebuild the double spending bloomfilter...");
|
||||
let today = ecash_today().date();
|
||||
|
||||
let (params_id, params) = storage.get_double_spending_filter_params().await?;
|
||||
info!("will use the following parameters: {params:?}");
|
||||
|
||||
// we're never going to have persisted data for 'today'. we need to rebuild it from scratch
|
||||
let today_filter = try_rebuild_today_bloomfilter(today, params, storage).await?;
|
||||
|
||||
info!("attempting to rebuild the global filter");
|
||||
let mut global_filter = prepare_partial_bloomfilter_builder(
|
||||
storage,
|
||||
params,
|
||||
params_id,
|
||||
today.previous_day().unwrap(),
|
||||
constants::CRED_VALIDITY_PERIOD_DAYS as i64 - 1,
|
||||
)
|
||||
.await?;
|
||||
|
||||
if !global_filter.add_bytes(&today_filter.dump_bitmap()) {
|
||||
error!("failed to add bitmap from {today} to the global bloomfilter. it may be malformed!");
|
||||
}
|
||||
|
||||
Ok(TicketDoubleSpendingFilter::new(
|
||||
today,
|
||||
params_id,
|
||||
global_filter.build(),
|
||||
today_filter,
|
||||
))
|
||||
}
|
||||
use tracing::warn;
|
||||
|
||||
pub(crate) fn ensure_sane_expiration_date(expiration_date: Date) -> Result<(), EcashError> {
|
||||
let today = ecash_today();
|
||||
|
||||
@@ -9,9 +9,7 @@ use crate::ecash::helpers::{
|
||||
use crate::ecash::keys::KeyPair;
|
||||
use crate::ecash::storage::models::IssuedHash;
|
||||
use nym_api_requests::ecash::models::{CommitedDeposit, DepositId};
|
||||
use nym_config::defaults::BloomfilterParameters;
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use nym_ecash_double_spending::DoubleSpendingFilter;
|
||||
use nym_ticketbooks_merkle::{
|
||||
IssuedTicketbook, IssuedTicketbooksFullMerkleProof, IssuedTicketbooksMerkleTree, MerkleLeaf,
|
||||
};
|
||||
@@ -21,69 +19,6 @@ use time::Date;
|
||||
use tokio::sync::RwLock;
|
||||
use tracing::error;
|
||||
|
||||
pub(crate) struct TicketDoubleSpendingFilter {
|
||||
built_on: Date,
|
||||
params_id: i64,
|
||||
|
||||
today_filter: DoubleSpendingFilter,
|
||||
global_filter: DoubleSpendingFilter,
|
||||
}
|
||||
|
||||
impl TicketDoubleSpendingFilter {
|
||||
pub(crate) fn new(
|
||||
built_on: Date,
|
||||
params_id: i64,
|
||||
global_filter: DoubleSpendingFilter,
|
||||
today_filter: DoubleSpendingFilter,
|
||||
) -> TicketDoubleSpendingFilter {
|
||||
TicketDoubleSpendingFilter {
|
||||
built_on,
|
||||
params_id,
|
||||
today_filter,
|
||||
global_filter,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn built_on(&self) -> Date {
|
||||
self.built_on
|
||||
}
|
||||
|
||||
pub(crate) fn params(&self) -> BloomfilterParameters {
|
||||
self.today_filter.params()
|
||||
}
|
||||
|
||||
pub(crate) fn params_id(&self) -> i64 {
|
||||
self.params_id
|
||||
}
|
||||
|
||||
pub(crate) fn check(&self, sn: &Vec<u8>) -> bool {
|
||||
self.global_filter.check(sn)
|
||||
}
|
||||
|
||||
/// Returns boolean to indicate if the entry was already present
|
||||
pub(crate) fn insert_both(&mut self, sn: &Vec<u8>) -> bool {
|
||||
self.today_filter.set(sn);
|
||||
self.insert_global_only(sn)
|
||||
}
|
||||
|
||||
/// Returns boolean to indicate if the entry was already present
|
||||
pub(crate) fn insert_global_only(&mut self, sn: &Vec<u8>) -> bool {
|
||||
let existed = self.global_filter.check(sn);
|
||||
self.global_filter.set(sn);
|
||||
existed
|
||||
}
|
||||
|
||||
pub(crate) fn export_today_bitmap(&self) -> Vec<u8> {
|
||||
self.today_filter.dump_bitmap()
|
||||
}
|
||||
|
||||
pub(crate) fn advance_day(&mut self, date: Date, new_global: DoubleSpendingFilter) {
|
||||
self.built_on = date;
|
||||
self.global_filter = new_global;
|
||||
self.today_filter.reset();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct DailyMerkleTree {
|
||||
pub(crate) merkle_tree: IssuedTicketbooksMerkleTree,
|
||||
@@ -207,9 +142,6 @@ pub(crate) struct LocalEcashState {
|
||||
pub(crate) partial_expiration_date_signatures:
|
||||
CachedImmutableItems<Date, IssuedExpirationDateSignatures>,
|
||||
|
||||
// the actual, up to date, bloomfilter
|
||||
pub(crate) double_spending_filter: Arc<RwLock<TicketDoubleSpendingFilter>>,
|
||||
|
||||
// merkle trees for ticketbooks issued for particular expiration dates
|
||||
pub(crate) issued_merkle_trees: Arc<RwLock<HashMap<Date, DailyMerkleTree>>>,
|
||||
}
|
||||
@@ -218,7 +150,6 @@ impl LocalEcashState {
|
||||
pub(crate) fn new(
|
||||
ecash_keypair: KeyPair,
|
||||
identity_keypair: identity::KeyPair,
|
||||
double_spending_filter: TicketDoubleSpendingFilter,
|
||||
explicitly_disabled: bool,
|
||||
) -> Self {
|
||||
LocalEcashState {
|
||||
@@ -228,7 +159,6 @@ impl LocalEcashState {
|
||||
active_signer: Default::default(),
|
||||
partial_coin_index_signatures: Default::default(),
|
||||
partial_expiration_date_signatures: Default::default(),
|
||||
double_spending_filter: Arc::new(RwLock::new(double_spending_filter)),
|
||||
issued_merkle_trees: Arc::new(Default::default()),
|
||||
}
|
||||
}
|
||||
|
||||
+51
-172
@@ -8,11 +8,9 @@ use crate::ecash::error::{EcashError, RedemptionError, Result};
|
||||
use crate::ecash::helpers::{IssuedCoinIndicesSignatures, IssuedExpirationDateSignatures};
|
||||
use crate::ecash::keys::KeyPair;
|
||||
use crate::ecash::state::auxiliary::AuxiliaryEcashState;
|
||||
use crate::ecash::state::cleaner::EcashBackgroundStateCleaner;
|
||||
use crate::ecash::state::global::GlobalEcachState;
|
||||
use crate::ecash::state::helpers::{
|
||||
ensure_sane_expiration_date, prepare_partial_bloomfilter_builder, query_all_threshold_apis,
|
||||
try_rebuild_bloomfilter,
|
||||
};
|
||||
use crate::ecash::state::helpers::{ensure_sane_expiration_date, query_all_threshold_apis};
|
||||
use crate::ecash::state::local::{DailyMerkleTree, LocalEcashState};
|
||||
use crate::ecash::storage::models::{SerialNumberWrapper, TicketProvider};
|
||||
use crate::ecash::storage::EcashStorageExt;
|
||||
@@ -33,31 +31,30 @@ use nym_compact_ecash::scheme::expiration_date_signatures::{
|
||||
aggregate_annotated_expiration_signatures, ExpirationDateSignatureShare,
|
||||
};
|
||||
use nym_compact_ecash::{
|
||||
constants, scheme::expiration_date_signatures::sign_expiration_date, BlindedSignature, Bytable,
|
||||
scheme::expiration_date_signatures::sign_expiration_date, BlindedSignature, Bytable,
|
||||
SecretKeyAuth, VerificationKeyAuth,
|
||||
};
|
||||
use nym_config::defaults::BloomfilterParameters;
|
||||
use nym_credentials::ecash::utils::EcashTime;
|
||||
use nym_credentials::{aggregate_verification_keys, CredentialSpendingData};
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use nym_ecash_contract_common::deposit::{Deposit, DepositId};
|
||||
use nym_ecash_contract_common::msg::ExecuteMsg;
|
||||
use nym_ecash_contract_common::redeem_credential::BATCH_REDEMPTION_PROPOSAL_TITLE;
|
||||
use nym_ecash_double_spending::DoubleSpendingFilter;
|
||||
use nym_ecash_time::{cred_exp_date, ecash_today_date};
|
||||
use nym_ecash_time::{ecash_default_expiration_date, ecash_today_date};
|
||||
use nym_task::TaskClient;
|
||||
use nym_ticketbooks_merkle::{IssuedTicketbook, IssuedTicketbooksFullMerkleProof, MerkleLeaf};
|
||||
use nym_validator_client::nyxd::AccountId;
|
||||
use nym_validator_client::EcashApiClient;
|
||||
use rand::{thread_rng, RngCore};
|
||||
use std::collections::HashMap;
|
||||
use std::ops::Deref;
|
||||
use time::ext::NumericalDuration;
|
||||
use time::{Date, OffsetDateTime};
|
||||
use tokio::sync::{RwLockReadGuard, RwLockWriteGuard};
|
||||
use tokio::task::JoinHandle;
|
||||
use tracing::{debug, error, info, warn};
|
||||
|
||||
pub(crate) mod auxiliary;
|
||||
pub(crate) mod bloom;
|
||||
mod cleaner;
|
||||
pub(crate) mod global;
|
||||
mod helpers;
|
||||
pub(crate) mod local;
|
||||
@@ -84,10 +81,24 @@ impl EcashStateConfig {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) enum BackgroundCleanerState {
|
||||
WaitingStartup(EcashBackgroundStateCleaner),
|
||||
Running {
|
||||
_handle: JoinHandle<()>,
|
||||
},
|
||||
|
||||
// an ephemeral state so that we could swap between the other two
|
||||
#[default]
|
||||
Invalid,
|
||||
}
|
||||
|
||||
pub struct EcashState {
|
||||
// additional global config parameters
|
||||
pub(crate) config: EcashStateConfig,
|
||||
|
||||
pub(crate) background_cleaner_state: BackgroundCleanerState,
|
||||
|
||||
// state global to the system, like aggregated keys, addresses, etc.
|
||||
pub(crate) global: GlobalEcachState,
|
||||
|
||||
@@ -99,7 +110,8 @@ pub struct EcashState {
|
||||
}
|
||||
|
||||
impl EcashState {
|
||||
pub(crate) async fn new<C, D>(
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn new<C, D>(
|
||||
global_config: &Config,
|
||||
contract_address: AccountId,
|
||||
client: C,
|
||||
@@ -107,24 +119,38 @@ impl EcashState {
|
||||
key_pair: KeyPair,
|
||||
comm_channel: D,
|
||||
storage: NymApiStorage,
|
||||
) -> Result<Self>
|
||||
task_client: TaskClient,
|
||||
) -> Self
|
||||
where
|
||||
C: LocalClient + Send + Sync + 'static,
|
||||
D: APICommunicationChannel + Send + Sync + 'static,
|
||||
{
|
||||
let double_spending_filter = try_rebuild_bloomfilter(&storage).await?;
|
||||
|
||||
Ok(Self {
|
||||
Self {
|
||||
config: EcashStateConfig::new(global_config),
|
||||
background_cleaner_state: BackgroundCleanerState::WaitingStartup(
|
||||
EcashBackgroundStateCleaner::new(global_config, storage.clone(), task_client),
|
||||
),
|
||||
global: GlobalEcachState::new(contract_address),
|
||||
local: LocalEcashState::new(
|
||||
key_pair,
|
||||
identity_keypair,
|
||||
double_spending_filter,
|
||||
!global_config.ecash_signer.enabled,
|
||||
),
|
||||
aux: AuxiliaryEcashState::new(client, comm_channel, storage),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn spawn_background_cleaner(&mut self) {
|
||||
match std::mem::take(&mut self.background_cleaner_state) {
|
||||
BackgroundCleanerState::WaitingStartup(cleaner) => {
|
||||
self.background_cleaner_state = BackgroundCleanerState::Running {
|
||||
_handle: cleaner.start(),
|
||||
}
|
||||
}
|
||||
// whilst we normally don't want to panic, this one would only occur at startup,
|
||||
// if some logical invariants got broken (which have to be fixed in code anyway)
|
||||
_ => panic!("attempted to spawn background cleaner more than once"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Ensures that this nym-api is one of ecash signers for the current epoch
|
||||
@@ -766,12 +792,6 @@ impl EcashState {
|
||||
// remove the in-memory merkle tree
|
||||
map.remove(&date);
|
||||
}
|
||||
|
||||
// remove data from the storage
|
||||
self.aux
|
||||
.storage
|
||||
.remove_old_issued_ticketbooks(cutoff)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -807,6 +827,12 @@ impl EcashState {
|
||||
return Err(EcashError::ExpirationDateTooEarly);
|
||||
}
|
||||
|
||||
if challenge.expiration_date > ecash_default_expiration_date() {
|
||||
// we wouldn't have issued any credentials for that expiration date so no point
|
||||
// in attempting to construct an ultimately empty response
|
||||
return Err(EcashError::ExpirationDateTooLate);
|
||||
}
|
||||
|
||||
let merkle_proof = self
|
||||
.get_merkle_proof(challenge.expiration_date, &challenge.deposits)
|
||||
.await?;
|
||||
@@ -863,18 +889,17 @@ impl EcashState {
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns a boolean to indicate whether the ticket has actually been inserted
|
||||
pub async fn store_verified_ticket(
|
||||
&self,
|
||||
ticket_data: &CredentialSpendingData,
|
||||
gateway_addr: &AccountId,
|
||||
) -> Result<()> {
|
||||
) -> Result<bool> {
|
||||
self.aux
|
||||
.storage
|
||||
.store_verified_ticket(ticket_data, gateway_addr)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
|
||||
// TODO UNIMPLEMENTED: we should probably also be removing old tickets here
|
||||
}
|
||||
|
||||
pub async fn get_ticket_provider(
|
||||
@@ -921,150 +946,4 @@ impl EcashState {
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
pub async fn check_bloomfilter(&self, serial_number: &Vec<u8>) -> bool {
|
||||
self.local
|
||||
.double_spending_filter
|
||||
.read()
|
||||
.await
|
||||
.check(serial_number)
|
||||
}
|
||||
|
||||
async fn update_archived_partial_bloomfilter(
|
||||
&self,
|
||||
date: Date,
|
||||
params_id: i64,
|
||||
params: BloomfilterParameters,
|
||||
sn: &Vec<u8>,
|
||||
) -> Result<(), EcashError> {
|
||||
let mut filter = match self
|
||||
.aux
|
||||
.storage
|
||||
.try_load_partial_bloomfilter_bitmap(date, params_id)
|
||||
.await?
|
||||
{
|
||||
Some(bitmap) => DoubleSpendingFilter::from_bytes(params, &bitmap),
|
||||
None => {
|
||||
warn!("no existing partial bloomfilter for {date}");
|
||||
DoubleSpendingFilter::new_empty(params)
|
||||
}
|
||||
};
|
||||
filter.set(sn);
|
||||
let updated_bitmap = filter.dump_bitmap();
|
||||
self.aux
|
||||
.storage
|
||||
.update_archived_partial_bloomfilter(date, &updated_bitmap)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Attempt to insert the provided serial number into the bloomfilter.
|
||||
/// Furthermore, attempt to rotate the filter if we have advanced into a next day.
|
||||
pub async fn update_bloomfilter(
|
||||
&self,
|
||||
serial_number: &Vec<u8>,
|
||||
spending_date: Date,
|
||||
today: Date,
|
||||
) -> Result<bool, EcashError> {
|
||||
let mut guard = self.local.double_spending_filter.write().await;
|
||||
|
||||
let filter_date = guard.built_on();
|
||||
let yesterday = today.previous_day().unwrap();
|
||||
|
||||
let params_id = guard.params_id();
|
||||
let params = guard.params();
|
||||
|
||||
// if the filter is up-to-date, we just insert the entry and call it a day
|
||||
if filter_date == today {
|
||||
if spending_date == today {
|
||||
return Ok(guard.insert_both(serial_number));
|
||||
}
|
||||
// sanity check because this should NEVER happen,
|
||||
// but when it inevitably does, we don't want to crash
|
||||
if spending_date != yesterday {
|
||||
error!("attempted to insert a ticket with spending date of {spending_date} while it's {today} today!!");
|
||||
}
|
||||
|
||||
// this shouldn't be happening too often, so it's fine to interact with the storage
|
||||
warn!("updating archived partial bloomfilter for {spending_date}. those logs have to be closely controlled to make sure they're not too frequent");
|
||||
self.update_archived_partial_bloomfilter(
|
||||
spending_date,
|
||||
params_id,
|
||||
params,
|
||||
serial_number,
|
||||
)
|
||||
.await?;
|
||||
|
||||
return Ok(guard.insert_global_only(serial_number));
|
||||
}
|
||||
|
||||
info!("we need to advance our bloomfilter");
|
||||
let previous_bitmap = guard.export_today_bitmap();
|
||||
|
||||
// archive the BF for today's date
|
||||
self.aux
|
||||
.storage
|
||||
.insert_partial_bloomfilter(filter_date, params_id, &previous_bitmap)
|
||||
.await?;
|
||||
|
||||
let new_global_filter = if filter_date == yesterday {
|
||||
// normal case when we update filter daily
|
||||
let two_days_ago = yesterday.previous_day().unwrap();
|
||||
let mut filter_builder = prepare_partial_bloomfilter_builder(
|
||||
&self.aux.storage,
|
||||
params,
|
||||
params_id,
|
||||
two_days_ago,
|
||||
constants::CRED_VALIDITY_PERIOD_DAYS as i64 - 2,
|
||||
)
|
||||
.await?;
|
||||
// add the bitmap from 'old today', i.e. yesterday
|
||||
// (we have it on hand so no point in retrieving it from storage)
|
||||
filter_builder.add_bytes(&previous_bitmap);
|
||||
filter_builder.build()
|
||||
} else {
|
||||
// initial deployment case when we don't even get tickets daily
|
||||
prepare_partial_bloomfilter_builder(
|
||||
&self.aux.storage,
|
||||
params,
|
||||
params_id,
|
||||
yesterday,
|
||||
constants::CRED_VALIDITY_PERIOD_DAYS as i64 - 1,
|
||||
)
|
||||
.await?
|
||||
.build()
|
||||
};
|
||||
|
||||
guard.advance_day(today, new_global_filter);
|
||||
|
||||
// drop guard so other tasks could read the filter already whilst we clean-up the storage
|
||||
let res = if spending_date == today {
|
||||
Ok(guard.insert_both(serial_number))
|
||||
} else {
|
||||
Ok(guard.insert_global_only(serial_number))
|
||||
};
|
||||
drop(guard);
|
||||
|
||||
let cutoff = cred_exp_date().ecash_date();
|
||||
|
||||
// sanity check:
|
||||
assert_eq!(
|
||||
cutoff,
|
||||
today + (constants::CRED_VALIDITY_PERIOD_DAYS as i64 - 1).days()
|
||||
);
|
||||
|
||||
// remove the data we no longer need to hold, i.e. partial bloomfilters beyond max credential validity
|
||||
// and the ticket data for those
|
||||
self.aux
|
||||
.storage
|
||||
.remove_old_partial_bloomfilters(cutoff)
|
||||
.await?;
|
||||
self.aux
|
||||
.storage
|
||||
.remove_expired_verified_tickets(cutoff)
|
||||
.await?;
|
||||
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
use crate::ecash::storage::models::{
|
||||
IssuedHash, RawExpirationDateSignatures, RawIssuedTicketbook, SerialNumberWrapper,
|
||||
StoredBloomfilterParams, TicketProvider, VerifiedTicket,
|
||||
TicketProvider, VerifiedTicket,
|
||||
};
|
||||
use crate::support::storage::manager::StorageManager;
|
||||
use async_trait::async_trait;
|
||||
@@ -65,6 +65,7 @@ pub trait EcashStorageManagerExt {
|
||||
gateway_address: &str,
|
||||
) -> Result<Option<TicketProvider>, sqlx::Error>;
|
||||
|
||||
/// Returns a boolean to indicate whether the ticket has actually been inserted
|
||||
async fn insert_verified_ticket(
|
||||
&self,
|
||||
provider_id: i64,
|
||||
@@ -72,7 +73,7 @@ pub trait EcashStorageManagerExt {
|
||||
verified_at: OffsetDateTime,
|
||||
ticket_data: Vec<u8>,
|
||||
serial_number: Vec<u8>,
|
||||
) -> Result<(), sqlx::Error>;
|
||||
) -> Result<bool, sqlx::Error>;
|
||||
|
||||
async fn get_ticket(&self, serial_number: &[u8])
|
||||
-> Result<Option<VerifiedTicket>, sqlx::Error>;
|
||||
@@ -145,41 +146,6 @@ pub trait EcashStorageManagerExt {
|
||||
data: &[u8],
|
||||
) -> Result<(), sqlx::Error>;
|
||||
|
||||
async fn insert_double_spending_filter_params(
|
||||
&self,
|
||||
num_hashes: u32,
|
||||
bitmap_size: u32,
|
||||
sip0_key0: &[u8],
|
||||
sip0_key1: &[u8],
|
||||
sip1_key0: &[u8],
|
||||
sip1_key1: &[u8],
|
||||
) -> Result<i64, sqlx::Error>;
|
||||
|
||||
async fn get_latest_double_spending_filter_params(
|
||||
&self,
|
||||
) -> Result<Option<StoredBloomfilterParams>, sqlx::Error>;
|
||||
|
||||
async fn update_archived_partial_bloomfilter(
|
||||
&self,
|
||||
date: Date,
|
||||
new_bitmap: &[u8],
|
||||
) -> Result<(), sqlx::Error>;
|
||||
|
||||
async fn try_load_partial_bloomfilter_bitmap(
|
||||
&self,
|
||||
date: Date,
|
||||
params_id: i64,
|
||||
) -> Result<Option<Vec<u8>>, sqlx::Error>;
|
||||
|
||||
async fn insert_partial_bloomfilter(
|
||||
&self,
|
||||
date: Date,
|
||||
params_id: i64,
|
||||
bitmap: &[u8],
|
||||
) -> Result<(), sqlx::Error>;
|
||||
|
||||
async fn remove_old_partial_bloomfilters(&self, cutoff: Date) -> Result<(), sqlx::Error>;
|
||||
|
||||
async fn remove_expired_verified_tickets(&self, cutoff: Date) -> Result<(), sqlx::Error>;
|
||||
}
|
||||
|
||||
@@ -330,6 +296,8 @@ impl EcashStorageManagerExt for StorageManager {
|
||||
.fetch_optional(&self.connection_pool)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Returns a boolean to indicate whether the ticket has actually been inserted
|
||||
async fn insert_verified_ticket(
|
||||
&self,
|
||||
provider_id: i64,
|
||||
@@ -337,10 +305,10 @@ impl EcashStorageManagerExt for StorageManager {
|
||||
verified_at: OffsetDateTime,
|
||||
ticket_data: Vec<u8>,
|
||||
serial_number: Vec<u8>,
|
||||
) -> Result<(), sqlx::Error> {
|
||||
sqlx::query!(
|
||||
) -> Result<bool, sqlx::Error> {
|
||||
let affected = sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO verified_tickets(ticket_data, serial_number, spending_date, verified_at, gateway_id)
|
||||
INSERT OR IGNORE INTO verified_tickets(ticket_data, serial_number, spending_date, verified_at, gateway_id)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
"#,
|
||||
ticket_data,
|
||||
@@ -350,9 +318,9 @@ impl EcashStorageManagerExt for StorageManager {
|
||||
provider_id
|
||||
)
|
||||
.execute(&self.connection_pool)
|
||||
.await?;
|
||||
.await?.rows_affected();
|
||||
|
||||
Ok(())
|
||||
Ok(affected == 1)
|
||||
}
|
||||
|
||||
async fn get_ticket(
|
||||
@@ -573,95 +541,9 @@ impl EcashStorageManagerExt for StorageManager {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn insert_double_spending_filter_params(
|
||||
&self,
|
||||
num_hashes: u32,
|
||||
bitmap_size: u32,
|
||||
sip0_key0: &[u8],
|
||||
sip0_key1: &[u8],
|
||||
sip1_key0: &[u8],
|
||||
sip1_key1: &[u8],
|
||||
) -> Result<i64, sqlx::Error> {
|
||||
let row_id = sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO bloomfilter_parameters(num_hashes, bitmap_size,sip0_key0, sip0_key1, sip1_key0, sip1_key1)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
"#,
|
||||
num_hashes,
|
||||
bitmap_size,
|
||||
sip0_key0,
|
||||
sip0_key1,
|
||||
sip1_key0,
|
||||
sip1_key1
|
||||
).execute(&self.connection_pool).await?.last_insert_rowid();
|
||||
Ok(row_id)
|
||||
}
|
||||
|
||||
async fn get_latest_double_spending_filter_params(
|
||||
&self,
|
||||
) -> Result<Option<StoredBloomfilterParams>, sqlx::Error> {
|
||||
sqlx::query_as("SELECT * FROM bloomfilter_parameters ORDER BY id DESC LIMIT 1")
|
||||
.fetch_optional(&self.connection_pool)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn update_archived_partial_bloomfilter(
|
||||
&self,
|
||||
date: Date,
|
||||
new_bitmap: &[u8],
|
||||
) -> Result<(), sqlx::Error> {
|
||||
sqlx::query!(
|
||||
"UPDATE partial_bloomfilter SET bitmap = ? WHERE date = ?",
|
||||
new_bitmap,
|
||||
date
|
||||
)
|
||||
.execute(&self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn try_load_partial_bloomfilter_bitmap(
|
||||
&self,
|
||||
date: Date,
|
||||
params_id: i64,
|
||||
) -> Result<Option<Vec<u8>>, sqlx::Error> {
|
||||
sqlx::query!(
|
||||
"SELECT bitmap FROM partial_bloomfilter WHERE date = ? AND parameters = ?",
|
||||
date,
|
||||
params_id
|
||||
)
|
||||
.fetch_optional(&self.connection_pool)
|
||||
.await
|
||||
.map(|maybe_record| maybe_record.map(|r| r.bitmap))
|
||||
}
|
||||
|
||||
async fn insert_partial_bloomfilter(
|
||||
&self,
|
||||
date: Date,
|
||||
params_id: i64,
|
||||
bitmap: &[u8],
|
||||
) -> Result<(), sqlx::Error> {
|
||||
sqlx::query!(
|
||||
"INSERT INTO partial_bloomfilter(date, parameters, bitmap) VALUES (?, ?, ?)",
|
||||
date,
|
||||
params_id,
|
||||
bitmap
|
||||
)
|
||||
.execute(&self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn remove_old_partial_bloomfilters(&self, cutoff: Date) -> Result<(), sqlx::Error> {
|
||||
sqlx::query!("DELETE FROM partial_bloomfilter WHERE date > ?", cutoff)
|
||||
.execute(&self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn remove_expired_verified_tickets(&self, cutoff: Date) -> Result<(), sqlx::Error> {
|
||||
sqlx::query!(
|
||||
"DELETE FROM verified_tickets WHERE spending_date > ?",
|
||||
"DELETE FROM verified_tickets WHERE spending_date < ?",
|
||||
cutoff
|
||||
)
|
||||
.execute(&self.connection_pool)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user