Compare commits
52 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| be67234093 | |||
| 8b0b70a727 | |||
| c90ebf0a6a | |||
| 07ff2639ec | |||
| 753a21f8ca | |||
| 76da4ab532 | |||
| 317f7fffa9 | |||
| 4396def133 | |||
| a56a318a7f | |||
| 4d08047c57 | |||
| cb13be27f8 | |||
| fa392169c1 | |||
| 3167fb34e6 | |||
| 9ca6301e1c | |||
| e16a73338e | |||
| bfa3825d70 | |||
| d626e7689f | |||
| 9234474565 | |||
| 29f8386b50 | |||
| 0edb9631a6 | |||
| 4b0153f5f2 | |||
| c09a17b66d | |||
| d18ddcdc11 | |||
| d2df542280 | |||
| 6fafd8c03a | |||
| 38e66f6ddf | |||
| b9fbe0b8f3 | |||
| daafb5cae4 | |||
| 0ec2514edf | |||
| d6435a8270 | |||
| 9efc50e067 | |||
| 1532547e2b | |||
| 0cb11632e6 | |||
| f71ea52d5d | |||
| 338835698c | |||
| e65e261cd3 | |||
| 2d78f6939e | |||
| 9a45de5874 | |||
| 2f894b9be3 | |||
| d36ea20366 | |||
| 18c6fd3e3e | |||
| d75c7eaaaf | |||
| 99cf7d1eec | |||
| f786dbeaa7 | |||
| eae76cce10 | |||
| 9341db5d08 | |||
| 75a5192c6d | |||
| 25ad0920cf | |||
| a1e75e1dff | |||
| e59a9a59b6 | |||
| 4c51a8975c | |||
| a4c6f51fe0 |
@@ -29,7 +29,7 @@ jobs:
|
||||
uses: mikefarah/yq@v4.44.3
|
||||
id: get_version
|
||||
with:
|
||||
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
|
||||
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/nym-credential-proxy/Cargo.toml
|
||||
|
||||
- name: Check if tag exists
|
||||
run: |
|
||||
@@ -51,5 +51,5 @@ jobs:
|
||||
|
||||
- name: BuildAndPushImageOnHarbor
|
||||
run: |
|
||||
docker build -f ${{ env.WORKING_DIRECTORY }}/Dockerfile . -t harbor.nymte.ch/nym/${{ env.CONTAINER_NAME }}:${{ steps.get_version.outputs.result }} -t harbor.nymte.ch/nym/${{ env.CONTAINER_NAME }}:latest
|
||||
docker build -f ${{ env.WORKING_DIRECTORY }}/nym-credential-proxy/Dockerfile . -t harbor.nymte.ch/nym/${{ env.CONTAINER_NAME }}:${{ steps.get_version.outputs.result }} -t harbor.nymte.ch/nym/${{ env.CONTAINER_NAME }}:latest
|
||||
docker push harbor.nymte.ch/nym/${{ env.CONTAINER_NAME }} --all-tags
|
||||
|
||||
@@ -4,6 +4,80 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [2024.12-aero] (2024-10-17)
|
||||
|
||||
- nym-node: don't use bloomfilters for double spending checks ([#4960])
|
||||
- bugfix: replace unreachable macro with an error return ([#4958])
|
||||
- [DOCs:/operators]: Update FAQ sphinx size ([#4946])
|
||||
- [DOCs/operators]: Release notes v2024.11-wedel ([#4939])
|
||||
- Fix handle drop ([#4934])
|
||||
- Assume offline mode ([#4926])
|
||||
- Make ip-packet-request VERSION pub ([#4925])
|
||||
- Expose error type ([#4924])
|
||||
- Fix argument to cargo-deny action ([#4922])
|
||||
- Fix nymvpn.com url in mainnet defaults ([#4920])
|
||||
- Check both version and type in message header ([#4918])
|
||||
- Bump http-api-client default timeout to 30 sec ([#4917])
|
||||
- Max/proxy ffi ([#4906])
|
||||
- Data Observatory stub ([#4905])
|
||||
- Fix missing duplication of modified tables ([#4904])
|
||||
- Update cargo deny ([#4901])
|
||||
- docs: add hostname instructions for wss ([#4900])
|
||||
- build(deps): bump the patch-updates group across 1 directory with 9 updates ([#4898])
|
||||
- Fix clippy for beta toolchain ([#4897])
|
||||
- Remove clippy github PR annotations ([#4896])
|
||||
- Fix apt install in ci-build-upload-binaries.yml ([#4894])
|
||||
- Update network monitor entrypoint ([#4893])
|
||||
- Update nym-vpn metapackage and replace nymvpn-x with nym-vpn-app ([#4889])
|
||||
- Entry wireguard tickets ([#4888])
|
||||
- Build and Push CI ([#4887])
|
||||
- Feature/updated gateway registration ([#4885])
|
||||
- Few fixes to NNM pre deploy ([#4883])
|
||||
- Fix sql serde with enum ([#4875])
|
||||
- allow clients to send stateless gateway requests without prior registration ([#4873])
|
||||
- chore: remove queued migration for adding explicit admin ([#4871])
|
||||
- Gateway database modifications for different modes ([#4868])
|
||||
- build(deps): bump strum from 0.25.0 to 0.26.3 ([#4848])
|
||||
- Use serde from workspace ([#4833])
|
||||
- build(deps): bump toml from 0.5.11 to 0.8.14 ([#4805])
|
||||
- Max/rust sdk stream abstraction ([#4743])
|
||||
|
||||
[#4960]: https://github.com/nymtech/nym/pull/4960
|
||||
[#4958]: https://github.com/nymtech/nym/pull/4958
|
||||
[#4946]: https://github.com/nymtech/nym/pull/4946
|
||||
[#4939]: https://github.com/nymtech/nym/pull/4939
|
||||
[#4934]: https://github.com/nymtech/nym/pull/4934
|
||||
[#4926]: https://github.com/nymtech/nym/pull/4926
|
||||
[#4925]: https://github.com/nymtech/nym/pull/4925
|
||||
[#4924]: https://github.com/nymtech/nym/pull/4924
|
||||
[#4922]: https://github.com/nymtech/nym/pull/4922
|
||||
[#4920]: https://github.com/nymtech/nym/pull/4920
|
||||
[#4918]: https://github.com/nymtech/nym/pull/4918
|
||||
[#4917]: https://github.com/nymtech/nym/pull/4917
|
||||
[#4906]: https://github.com/nymtech/nym/pull/4906
|
||||
[#4905]: https://github.com/nymtech/nym/pull/4905
|
||||
[#4904]: https://github.com/nymtech/nym/pull/4904
|
||||
[#4901]: https://github.com/nymtech/nym/pull/4901
|
||||
[#4900]: https://github.com/nymtech/nym/pull/4900
|
||||
[#4898]: https://github.com/nymtech/nym/pull/4898
|
||||
[#4897]: https://github.com/nymtech/nym/pull/4897
|
||||
[#4896]: https://github.com/nymtech/nym/pull/4896
|
||||
[#4894]: https://github.com/nymtech/nym/pull/4894
|
||||
[#4893]: https://github.com/nymtech/nym/pull/4893
|
||||
[#4889]: https://github.com/nymtech/nym/pull/4889
|
||||
[#4888]: https://github.com/nymtech/nym/pull/4888
|
||||
[#4887]: https://github.com/nymtech/nym/pull/4887
|
||||
[#4885]: https://github.com/nymtech/nym/pull/4885
|
||||
[#4883]: https://github.com/nymtech/nym/pull/4883
|
||||
[#4875]: https://github.com/nymtech/nym/pull/4875
|
||||
[#4873]: https://github.com/nymtech/nym/pull/4873
|
||||
[#4871]: https://github.com/nymtech/nym/pull/4871
|
||||
[#4868]: https://github.com/nymtech/nym/pull/4868
|
||||
[#4848]: https://github.com/nymtech/nym/pull/4848
|
||||
[#4833]: https://github.com/nymtech/nym/pull/4833
|
||||
[#4805]: https://github.com/nymtech/nym/pull/4805
|
||||
[#4743]: https://github.com/nymtech/nym/pull/4743
|
||||
|
||||
## [2024.11-wedel] (2024-09-23)
|
||||
|
||||
- Backport #4894 to fix ci ([#4899])
|
||||
|
||||
Generated
+38
-8
@@ -481,6 +481,17 @@ dependencies = [
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "axum-client-ip"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9eefda7e2b27e1bda4d6fa8a06b50803b8793769045918bc37ad062d48a6efac"
|
||||
dependencies = [
|
||||
"axum 0.7.7",
|
||||
"forwarded-header-value",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "axum-core"
|
||||
version = "0.3.4"
|
||||
@@ -2364,7 +2375,7 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
|
||||
|
||||
[[package]]
|
||||
name = "explorer-api"
|
||||
version = "1.1.40"
|
||||
version = "1.1.41"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"clap 4.5.18",
|
||||
@@ -2585,6 +2596,16 @@ version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c8cbd1169bd7b4a0a20d92b9af7a7e0422888bd38a6f5ec29c1fd8c1558a272e"
|
||||
|
||||
[[package]]
|
||||
name = "forwarded-header-value"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8835f84f38484cc86f110a805655697908257fb9a7af005234060891557198e9"
|
||||
dependencies = [
|
||||
"nonempty",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fs-err"
|
||||
version = "2.11.0"
|
||||
@@ -4198,6 +4219,12 @@ dependencies = [
|
||||
"minimal-lexical",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nonempty"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e9e591e719385e6ebaeb5ce5d3887f7d5676fceca6411d1925ccc95745f3d6f7"
|
||||
|
||||
[[package]]
|
||||
name = "notify"
|
||||
version = "5.2.0"
|
||||
@@ -4347,7 +4374,7 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
|
||||
|
||||
[[package]]
|
||||
name = "nym-api"
|
||||
version = "1.1.44"
|
||||
version = "1.1.45"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -4588,7 +4615,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-cli"
|
||||
version = "1.1.42"
|
||||
version = "1.1.43"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64 0.22.1",
|
||||
@@ -4669,7 +4696,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-client"
|
||||
version = "1.1.41"
|
||||
version = "1.1.42"
|
||||
dependencies = [
|
||||
"bs58",
|
||||
"clap 4.5.18",
|
||||
@@ -5266,9 +5293,11 @@ dependencies = [
|
||||
"nym-network-requester",
|
||||
"nym-node-http-api",
|
||||
"nym-pemstore",
|
||||
"nym-sdk",
|
||||
"nym-sphinx",
|
||||
"nym-statistics-common",
|
||||
"nym-task",
|
||||
"nym-topology",
|
||||
"nym-types",
|
||||
"nym-validator-client",
|
||||
"nym-wireguard",
|
||||
@@ -5419,6 +5448,7 @@ name = "nym-http-api-common"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"axum 0.7.7",
|
||||
"axum-client-ip",
|
||||
"bytes",
|
||||
"colored",
|
||||
"mime",
|
||||
@@ -5714,7 +5744,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-network-requester"
|
||||
version = "1.1.42"
|
||||
version = "1.1.43"
|
||||
dependencies = [
|
||||
"addr",
|
||||
"anyhow",
|
||||
@@ -5765,7 +5795,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-node"
|
||||
version = "1.1.8"
|
||||
version = "1.1.9"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bip39",
|
||||
@@ -6055,7 +6085,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-socks5-client"
|
||||
version = "1.1.41"
|
||||
version = "1.1.42"
|
||||
dependencies = [
|
||||
"bs58",
|
||||
"clap 4.5.18",
|
||||
@@ -6600,7 +6630,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nymvisor"
|
||||
version = "0.1.7"
|
||||
version = "0.1.8"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
|
||||
@@ -186,6 +186,7 @@ aead = "0.5.2"
|
||||
anyhow = "1.0.89"
|
||||
argon2 = "0.5.0"
|
||||
async-trait = "0.1.83"
|
||||
axum-client-ip = "0.6.1"
|
||||
axum = "0.7.5"
|
||||
axum-extra = "0.9.4"
|
||||
base64 = "0.22.1"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-client"
|
||||
version = "1.1.41"
|
||||
version = "1.1.42"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
|
||||
description = "Implementation of the Nym Client"
|
||||
edition = "2021"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-socks5-client"
|
||||
version = "1.1.41"
|
||||
version = "1.1.42"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
|
||||
description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address"
|
||||
edition = "2021"
|
||||
|
||||
@@ -112,7 +112,7 @@ impl GeoAwareTopologyProvider {
|
||||
async fn get_topology(&self) -> Option<NymTopology> {
|
||||
let mixnodes = match self
|
||||
.validator_client
|
||||
.get_basic_active_mixing_assigned_nodes(Some(self.client_version.clone()))
|
||||
.get_all_basic_active_mixing_assigned_nodes(Some(self.client_version.clone()))
|
||||
.await
|
||||
{
|
||||
Err(err) => {
|
||||
|
||||
@@ -6,7 +6,6 @@ pub(crate) use accessor::{TopologyAccessor, TopologyReadPermit};
|
||||
use futures::StreamExt;
|
||||
use log::*;
|
||||
use nym_sphinx::addressing::nodes::NodeIdentity;
|
||||
use nym_topology::provider_trait::TopologyProvider;
|
||||
use nym_topology::NymTopologyError;
|
||||
use std::time::Duration;
|
||||
|
||||
@@ -18,7 +17,11 @@ use wasmtimer::tokio::sleep;
|
||||
|
||||
mod accessor;
|
||||
pub mod geo_aware_provider;
|
||||
pub(crate) mod nym_api_provider;
|
||||
pub mod nym_api_provider;
|
||||
|
||||
pub use geo_aware_provider::GeoAwareTopologyProvider;
|
||||
pub use nym_api_provider::{Config as NymApiTopologyProviderConfig, NymApiTopologyProvider};
|
||||
pub use nym_topology::provider_trait::TopologyProvider;
|
||||
|
||||
// TODO: move it to config later
|
||||
const MAX_FAILURE_COUNT: usize = 10;
|
||||
|
||||
@@ -14,9 +14,10 @@ use url::Url;
|
||||
pub const DEFAULT_MIN_MIXNODE_PERFORMANCE: u8 = 50;
|
||||
pub const DEFAULT_MIN_GATEWAY_PERFORMANCE: u8 = 50;
|
||||
|
||||
pub(crate) struct Config {
|
||||
pub(crate) min_mixnode_performance: u8,
|
||||
pub(crate) min_gateway_performance: u8,
|
||||
#[derive(Debug)]
|
||||
pub struct Config {
|
||||
pub min_mixnode_performance: u8,
|
||||
pub min_gateway_performance: u8,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
@@ -29,7 +30,7 @@ impl Default for Config {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct NymApiTopologyProvider {
|
||||
pub struct NymApiTopologyProvider {
|
||||
config: Config,
|
||||
|
||||
validator_client: nym_validator_client::client::NymApiClient,
|
||||
@@ -40,7 +41,7 @@ pub(crate) struct NymApiTopologyProvider {
|
||||
}
|
||||
|
||||
impl NymApiTopologyProvider {
|
||||
pub(crate) fn new(
|
||||
pub fn new(
|
||||
config: Config,
|
||||
mut nym_api_urls: Vec<Url>,
|
||||
client_version: String,
|
||||
@@ -98,7 +99,7 @@ impl NymApiTopologyProvider {
|
||||
async fn get_current_compatible_topology(&mut self) -> Option<NymTopology> {
|
||||
let mixnodes = match self
|
||||
.validator_client
|
||||
.get_basic_active_mixing_assigned_nodes(Some(self.client_version.clone()))
|
||||
.get_all_basic_active_mixing_assigned_nodes(Some(self.client_version.clone()))
|
||||
.await
|
||||
{
|
||||
Err(err) => {
|
||||
|
||||
@@ -121,7 +121,9 @@ pub async fn current_mixnodes<R: Rng>(
|
||||
|
||||
log::trace!("Fetching list of mixnodes from: {nym_api}");
|
||||
|
||||
let mixnodes = client.get_basic_active_mixing_assigned_nodes(None).await?;
|
||||
let mixnodes = client
|
||||
.get_all_basic_active_mixing_assigned_nodes(None)
|
||||
.await?;
|
||||
let valid_mixnodes = mixnodes
|
||||
.iter()
|
||||
.filter_map(|mixnode| mixnode.try_into().ok())
|
||||
|
||||
@@ -19,7 +19,7 @@ use nym_api_requests::ecash::{
|
||||
};
|
||||
use nym_api_requests::models::{
|
||||
GatewayCoreStatusResponse, MixnodeCoreStatusResponse, MixnodeStatusResponse,
|
||||
RewardEstimationResponse, StakeSaturationResponse,
|
||||
NymNodeDescription, RewardEstimationResponse, StakeSaturationResponse,
|
||||
};
|
||||
use nym_api_requests::models::{LegacyDescribedGateway, MixNodeBondAnnotated};
|
||||
use nym_api_requests::nym_nodes::SkimmedNode;
|
||||
@@ -30,10 +30,10 @@ use time::Date;
|
||||
use url::Url;
|
||||
|
||||
pub use crate::nym_api::NymApiClientExt;
|
||||
use nym_mixnet_contract_common::NymNodeDetails;
|
||||
pub use nym_mixnet_contract_common::{
|
||||
mixnode::MixNodeDetails, GatewayBond, IdentityKey, IdentityKeyRef, NodeId,
|
||||
};
|
||||
|
||||
// re-export the type to not break existing imports
|
||||
pub use crate::coconut::EcashApiClient;
|
||||
|
||||
@@ -106,7 +106,9 @@ impl Config {
|
||||
|
||||
pub struct Client<C, S = NoSigner> {
|
||||
// ideally they would have been read-only, but unfortunately rust doesn't have such features
|
||||
// #[deprecated(note = "please use `nym_api_client` instead")]
|
||||
pub nym_api: nym_api::Client,
|
||||
// pub nym_api_client: NymApiClient,
|
||||
pub nyxd: NyxdClient<C, S>,
|
||||
}
|
||||
|
||||
@@ -243,6 +245,50 @@ impl<C, S> Client<C, S> {
|
||||
Ok(self.nym_api.get_gateways().await?)
|
||||
}
|
||||
|
||||
// TODO: combine with NymApiClient...
|
||||
pub async fn get_all_cached_described_nodes(
|
||||
&self,
|
||||
) -> Result<Vec<NymNodeDescription>, ValidatorClientError> {
|
||||
// TODO: deal with paging in macro or some helper function or something, because it's the same pattern everywhere
|
||||
let mut page = 0;
|
||||
let mut descriptions = Vec::new();
|
||||
|
||||
loop {
|
||||
let mut res = self.nym_api.get_nodes_described(Some(page), None).await?;
|
||||
|
||||
descriptions.append(&mut res.data);
|
||||
if descriptions.len() < res.pagination.total {
|
||||
page += 1
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(descriptions)
|
||||
}
|
||||
|
||||
// TODO: combine with NymApiClient...
|
||||
pub async fn get_all_cached_bonded_nym_nodes(
|
||||
&self,
|
||||
) -> Result<Vec<NymNodeDetails>, ValidatorClientError> {
|
||||
// TODO: deal with paging in macro or some helper function or something, because it's the same pattern everywhere
|
||||
let mut page = 0;
|
||||
let mut bonds = Vec::new();
|
||||
|
||||
loop {
|
||||
let mut res = self.nym_api.get_nym_nodes(Some(page), None).await?;
|
||||
|
||||
bonds.append(&mut res.data);
|
||||
if bonds.len() < res.pagination.total {
|
||||
page += 1
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(bonds)
|
||||
}
|
||||
|
||||
pub async fn blind_sign(
|
||||
&self,
|
||||
request_body: &BlindSignRequestBody,
|
||||
@@ -283,7 +329,7 @@ impl NymApiClient {
|
||||
self.nym_api.change_base_url(new_endpoint);
|
||||
}
|
||||
|
||||
#[deprecated(note = "use get_basic_active_mixing_assigned_nodes instead")]
|
||||
#[deprecated(note = "use get_all_basic_active_mixing_assigned_nodes instead")]
|
||||
pub async fn get_basic_mixnodes(
|
||||
&self,
|
||||
semver_compatibility: Option<String>,
|
||||
@@ -320,7 +366,7 @@ impl NymApiClient {
|
||||
loop {
|
||||
let mut res = self
|
||||
.nym_api
|
||||
.get_all_basic_entry_assigned_nodes(
|
||||
.get_basic_entry_assigned_nodes(
|
||||
semver_compatibility.clone(),
|
||||
false,
|
||||
Some(page),
|
||||
@@ -341,7 +387,7 @@ impl NymApiClient {
|
||||
|
||||
/// retrieve basic information for nodes that got assigned 'mixing' node in this epoch
|
||||
/// this includes legacy mixnodes and nym-nodes
|
||||
pub async fn get_basic_active_mixing_assigned_nodes(
|
||||
pub async fn get_all_basic_active_mixing_assigned_nodes(
|
||||
&self,
|
||||
semver_compatibility: Option<String>,
|
||||
) -> Result<Vec<SkimmedNode>, ValidatorClientError> {
|
||||
@@ -371,6 +417,32 @@ impl NymApiClient {
|
||||
Ok(nodes)
|
||||
}
|
||||
|
||||
/// retrieve basic information for all bonded nodes on the network
|
||||
pub async fn get_all_basic_nodes(
|
||||
&self,
|
||||
semver_compatibility: Option<String>,
|
||||
) -> Result<Vec<SkimmedNode>, ValidatorClientError> {
|
||||
// TODO: deal with paging in macro or some helper function or something, because it's the same pattern everywhere
|
||||
let mut page = 0;
|
||||
let mut nodes = Vec::new();
|
||||
|
||||
loop {
|
||||
let mut res = self
|
||||
.nym_api
|
||||
.get_basic_nodes(semver_compatibility.clone(), false, Some(page), None)
|
||||
.await?;
|
||||
|
||||
nodes.append(&mut res.nodes.data);
|
||||
if nodes.len() < res.nodes.pagination.total {
|
||||
page += 1
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(nodes)
|
||||
}
|
||||
|
||||
pub async fn get_cached_active_mixnodes(
|
||||
&self,
|
||||
) -> Result<Vec<MixNodeDetails>, ValidatorClientError> {
|
||||
@@ -397,6 +469,48 @@ impl NymApiClient {
|
||||
Ok(self.nym_api.get_gateways_described().await?)
|
||||
}
|
||||
|
||||
pub async fn get_all_described_nodes(
|
||||
&self,
|
||||
) -> Result<Vec<NymNodeDescription>, ValidatorClientError> {
|
||||
// TODO: deal with paging in macro or some helper function or something, because it's the same pattern everywhere
|
||||
let mut page = 0;
|
||||
let mut descriptions = Vec::new();
|
||||
|
||||
loop {
|
||||
let mut res = self.nym_api.get_nodes_described(Some(page), None).await?;
|
||||
|
||||
descriptions.append(&mut res.data);
|
||||
if descriptions.len() < res.pagination.total {
|
||||
page += 1
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(descriptions)
|
||||
}
|
||||
|
||||
pub async fn get_all_bonded_nym_nodes(
|
||||
&self,
|
||||
) -> Result<Vec<NymNodeDetails>, ValidatorClientError> {
|
||||
// TODO: deal with paging in macro or some helper function or something, because it's the same pattern everywhere
|
||||
let mut page = 0;
|
||||
let mut bonds = Vec::new();
|
||||
|
||||
loop {
|
||||
let mut res = self.nym_api.get_nym_nodes(Some(page), None).await?;
|
||||
|
||||
bonds.append(&mut res.data);
|
||||
if bonds.len() < res.pagination.total {
|
||||
page += 1
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(bonds)
|
||||
}
|
||||
|
||||
pub async fn get_gateway_core_status_count(
|
||||
&self,
|
||||
identity: IdentityKeyRef<'_>,
|
||||
|
||||
@@ -11,9 +11,10 @@ use nym_api_requests::ecash::models::{
|
||||
};
|
||||
use nym_api_requests::ecash::VerificationKeyResponse;
|
||||
use nym_api_requests::models::{
|
||||
AnnotationResponse, LegacyDescribedMixNode, NodePerformanceResponse,
|
||||
AnnotationResponse, LegacyDescribedMixNode, NodePerformanceResponse, NymNodeDescription,
|
||||
};
|
||||
use nym_api_requests::nym_nodes::PaginatedCachedNodesResponse;
|
||||
use nym_api_requests::pagination::PaginatedResponse;
|
||||
pub use nym_api_requests::{
|
||||
ecash::{
|
||||
models::{
|
||||
@@ -38,7 +39,7 @@ use nym_contracts_common::IdentityKey;
|
||||
pub use nym_http_api_client::Client;
|
||||
use nym_http_api_client::{ApiClient, NO_PARAMS};
|
||||
use nym_mixnet_contract_common::mixnode::MixNodeDetails;
|
||||
use nym_mixnet_contract_common::{GatewayBond, IdentityKeyRef, NodeId};
|
||||
use nym_mixnet_contract_common::{GatewayBond, IdentityKeyRef, NodeId, NymNodeDetails};
|
||||
use time::format_description::BorrowedFormatItem;
|
||||
use time::Date;
|
||||
|
||||
@@ -119,6 +120,44 @@ pub trait NymApiClientExt: ApiClient {
|
||||
.await
|
||||
}
|
||||
|
||||
async fn get_nodes_described(
|
||||
&self,
|
||||
page: Option<u32>,
|
||||
per_page: Option<u32>,
|
||||
) -> Result<PaginatedResponse<NymNodeDescription>, NymAPIError> {
|
||||
let mut params = Vec::new();
|
||||
|
||||
if let Some(page) = page {
|
||||
params.push(("page", page.to_string()))
|
||||
}
|
||||
|
||||
if let Some(per_page) = per_page {
|
||||
params.push(("per_page", per_page.to_string()))
|
||||
}
|
||||
|
||||
self.get_json(&[routes::API_VERSION, "nym-nodes", "described"], ¶ms)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn get_nym_nodes(
|
||||
&self,
|
||||
page: Option<u32>,
|
||||
per_page: Option<u32>,
|
||||
) -> Result<PaginatedResponse<NymNodeDetails>, NymAPIError> {
|
||||
let mut params = Vec::new();
|
||||
|
||||
if let Some(page) = page {
|
||||
params.push(("page", page.to_string()))
|
||||
}
|
||||
|
||||
if let Some(per_page) = per_page {
|
||||
params.push(("per_page", per_page.to_string()))
|
||||
}
|
||||
|
||||
self.get_json(&[routes::API_VERSION, "nym-nodes", "bonded"], ¶ms)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn get_basic_mixnodes(
|
||||
&self,
|
||||
semver_compatibility: Option<String>,
|
||||
@@ -167,7 +206,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
|
||||
/// retrieve basic information for nodes are capable of operating as an entry gateway
|
||||
/// this includes legacy gateways and nym-nodes
|
||||
async fn get_all_basic_entry_assigned_nodes(
|
||||
async fn get_basic_entry_assigned_nodes(
|
||||
&self,
|
||||
semver_compatibility: Option<String>,
|
||||
no_legacy: bool,
|
||||
@@ -247,6 +286,38 @@ pub trait NymApiClientExt: ApiClient {
|
||||
.await
|
||||
}
|
||||
|
||||
async fn get_basic_nodes(
|
||||
&self,
|
||||
semver_compatibility: Option<String>,
|
||||
no_legacy: bool,
|
||||
page: Option<u32>,
|
||||
per_page: Option<u32>,
|
||||
) -> Result<PaginatedCachedNodesResponse<SkimmedNode>, NymAPIError> {
|
||||
let mut params = Vec::new();
|
||||
|
||||
if let Some(arg) = &semver_compatibility {
|
||||
params.push(("semver_compatibility", arg.clone()))
|
||||
}
|
||||
|
||||
if no_legacy {
|
||||
params.push(("no_legacy", "true".to_string()))
|
||||
}
|
||||
|
||||
if let Some(page) = page {
|
||||
params.push(("page", page.to_string()))
|
||||
}
|
||||
|
||||
if let Some(per_page) = per_page {
|
||||
params.push(("per_page", per_page.to_string()))
|
||||
}
|
||||
|
||||
self.get_json(
|
||||
&[routes::API_VERSION, "unstable", "nym-nodes", "skimmed"],
|
||||
¶ms,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn get_active_mixnodes(&self) -> Result<Vec<MixNodeDetails>, NymAPIError> {
|
||||
self.get_json(
|
||||
&[routes::API_VERSION, routes::MIXNODES, routes::ACTIVE],
|
||||
|
||||
@@ -17,6 +17,7 @@ use crate::{
|
||||
use cosmwasm_schema::cw_serde;
|
||||
use cosmwasm_std::{Addr, Coin, Decimal, StdResult, Uint128};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_repr::{Deserialize_repr, Serialize_repr};
|
||||
|
||||
/// Full details associated with given mixnode.
|
||||
@@ -647,14 +648,39 @@ impl From<LegacyMixLayer> for u8 {
|
||||
export_to = "ts-packages/types/src/types/rust/PendingMixnodeChanges.ts"
|
||||
)
|
||||
)]
|
||||
#[cw_serde]
|
||||
#[derive(Default, Copy)]
|
||||
// note: we had to remove `#[cw_serde]` as it enforces `#[serde(deny_unknown_fields)]` which we do not want
|
||||
// with the addition of .cost_params_change field
|
||||
#[derive(
|
||||
::cosmwasm_schema::serde::Serialize,
|
||||
::cosmwasm_schema::serde::Deserialize,
|
||||
::std::clone::Clone,
|
||||
::std::fmt::Debug,
|
||||
::std::cmp::PartialEq,
|
||||
::cosmwasm_schema::schemars::JsonSchema,
|
||||
Default,
|
||||
Copy,
|
||||
)]
|
||||
#[schemars(crate = "::cosmwasm_schema::schemars")]
|
||||
pub struct PendingMixNodeChanges {
|
||||
pub pledge_change: Option<EpochEventId>,
|
||||
|
||||
#[serde(default)]
|
||||
pub cost_params_change: Option<IntervalEventId>,
|
||||
}
|
||||
|
||||
#[derive(Default, Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct LegacyPendingMixNodeChanges {
|
||||
pub pledge_change: Option<EpochEventId>,
|
||||
}
|
||||
|
||||
impl From<PendingMixNodeChanges> for LegacyPendingMixNodeChanges {
|
||||
fn from(value: PendingMixNodeChanges) -> Self {
|
||||
LegacyPendingMixNodeChanges {
|
||||
pledge_change: value.pledge_change,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PendingMixNodeChanges {
|
||||
pub fn new_empty() -> PendingMixNodeChanges {
|
||||
PendingMixNodeChanges {
|
||||
|
||||
@@ -1,100 +0,0 @@
|
||||
// Copyright 2022-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::ecash::error::EcashTicketError;
|
||||
use crate::ecash::state::SharedState;
|
||||
use nym_ecash_double_spending::DoubleSpendingFilter;
|
||||
use nym_gateway_storage::Storage;
|
||||
use nym_task::TaskClient;
|
||||
use nym_validator_client::client::NymApiClientExt;
|
||||
use nym_validator_client::EcashApiClient;
|
||||
use rand::prelude::SliceRandom;
|
||||
use rand::thread_rng;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::{RwLock, RwLockReadGuard};
|
||||
use tokio::time::{interval, Duration};
|
||||
use tracing::{info, trace, warn};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct DoubleSpendingDetector<S> {
|
||||
spent_serial_numbers: Arc<RwLock<DoubleSpendingFilter>>,
|
||||
shared_state: SharedState<S>,
|
||||
}
|
||||
|
||||
impl<S> DoubleSpendingDetector<S>
|
||||
where
|
||||
S: Storage + Clone + Send + Sync + 'static,
|
||||
{
|
||||
pub(crate) fn new(shared_state: SharedState<S>) -> Self {
|
||||
DoubleSpendingDetector {
|
||||
spent_serial_numbers: Arc::new(RwLock::new(DoubleSpendingFilter::new_empty_ecash())),
|
||||
shared_state,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn check(&self, serial_number: &Vec<u8>) -> bool {
|
||||
self.spent_serial_numbers.read().await.check(serial_number)
|
||||
}
|
||||
|
||||
async fn latest_api_endpoints(
|
||||
&self,
|
||||
) -> Result<RwLockReadGuard<Vec<EcashApiClient>>, EcashTicketError> {
|
||||
let epoch_id = self.shared_state.current_epoch_id().await?;
|
||||
self.shared_state.api_clients(epoch_id).await
|
||||
}
|
||||
|
||||
async fn refresh_bloomfilter(&self) {
|
||||
let mut filter_builder = self.spent_serial_numbers.read().await.rebuild();
|
||||
|
||||
let api_clients = match self.latest_api_endpoints().await {
|
||||
Ok(clients) => clients,
|
||||
Err(err) => {
|
||||
warn!("failed to obtain current api clients: {err}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let mut clients = api_clients
|
||||
.iter()
|
||||
.map(|c| c.api_client.clone())
|
||||
.collect::<Vec<_>>();
|
||||
clients.shuffle(&mut thread_rng());
|
||||
|
||||
for client in clients {
|
||||
match client.nym_api.double_spending_filter_v1().await {
|
||||
Ok(response) => {
|
||||
// due to relative big size of the filter, query only one api since all of them should contain
|
||||
// roughly the same data anyway.
|
||||
filter_builder.add_bytes(&response.bitmap);
|
||||
*self.spent_serial_numbers.write().await = filter_builder.build();
|
||||
return;
|
||||
}
|
||||
Err(err) => {
|
||||
warn!("Validator @ {} could not be reached. There might be a problem with the ecash endpoint: {err}", client.api_url());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
warn!("none of the validators could be reached. the bloomfilter will remain unchanged.");
|
||||
}
|
||||
|
||||
async fn run(&self, mut shutdown: TaskClient) {
|
||||
info!("Starting Ecash DoubleSpendingDetector");
|
||||
let mut interval = interval(Duration::from_secs(600));
|
||||
|
||||
while !shutdown.is_shutdown() {
|
||||
tokio::select! {
|
||||
biased;
|
||||
_ = shutdown.recv() => {
|
||||
trace!("ecash_verifier::DoubleSpendingDetector : received shutdown");
|
||||
},
|
||||
_ = interval.tick() => self.refresh_bloomfilter().await,
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn start(self, shutdown: nym_task::TaskClient) {
|
||||
tokio::spawn(async move { self.run(shutdown).await });
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,6 @@
|
||||
use crate::Error;
|
||||
use credential_sender::CredentialHandler;
|
||||
use credential_sender::CredentialHandlerConfig;
|
||||
use double_spending::DoubleSpendingDetector;
|
||||
use error::EcashTicketError;
|
||||
use futures::channel::mpsc::{self, UnboundedSender};
|
||||
use nym_credentials::CredentialSpendingData;
|
||||
@@ -18,7 +17,6 @@ use tokio::sync::{Mutex, RwLockReadGuard};
|
||||
use tracing::error;
|
||||
|
||||
pub mod credential_sender;
|
||||
pub(crate) mod double_spending;
|
||||
pub mod error;
|
||||
mod helpers;
|
||||
mod state;
|
||||
@@ -31,7 +29,6 @@ pub struct EcashManager<S> {
|
||||
pk_bytes: [u8; 32], // bytes representation of a pub key representing the verifier
|
||||
pay_infos: Mutex<Vec<NymPayInfo>>,
|
||||
cred_sender: UnboundedSender<ClientTicket>,
|
||||
double_spend_detector: DoubleSpendingDetector<S>,
|
||||
}
|
||||
|
||||
impl<S> EcashManager<S>
|
||||
@@ -47,9 +44,6 @@ where
|
||||
) -> Result<Self, Error> {
|
||||
let shared_state = SharedState::new(nyxd_client, storage).await?;
|
||||
|
||||
let double_spend_detector = DoubleSpendingDetector::new(shared_state.clone());
|
||||
double_spend_detector.clone().start(shutdown.clone());
|
||||
|
||||
let (cred_sender, cred_receiver) = mpsc::unbounded();
|
||||
|
||||
let cs =
|
||||
@@ -62,7 +56,6 @@ where
|
||||
pk_bytes,
|
||||
pay_infos: Default::default(),
|
||||
cred_sender,
|
||||
double_spend_detector,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -163,10 +156,6 @@ where
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn check_double_spend(&self, serial_number: &Vec<u8>) -> bool {
|
||||
self.double_spend_detector.check(serial_number).await
|
||||
}
|
||||
|
||||
pub fn async_verify(&self, ticket: ClientTicket) {
|
||||
// TODO: I guess do something for shutdowns
|
||||
let _ = self
|
||||
|
||||
@@ -53,18 +53,6 @@ impl<S: Storage + Clone + 'static> CredentialVerifier<S> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn check_bloomfilter(&self, serial_number: &Vec<u8>) -> Result<()> {
|
||||
trace!("checking the bloomfilter...");
|
||||
|
||||
let spent = self.ecash_verifier.check_double_spend(serial_number).await;
|
||||
|
||||
if spent {
|
||||
trace!("the credential has already been spent before at some gateway before (bloomfilter failure)");
|
||||
return Err(Error::BandwidthCredentialAlreadySpent);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn check_local_db_for_double_spending(&self, serial_number: &[u8]) -> Result<()> {
|
||||
trace!("checking local db for double spending...");
|
||||
|
||||
@@ -128,7 +116,6 @@ impl<S: Storage + Clone + 'static> CredentialVerifier<S> {
|
||||
}
|
||||
|
||||
self.check_credential_spending_date(spend_date.ecash_date())?;
|
||||
self.check_bloomfilter(&serial_number).await?;
|
||||
self.check_local_db_for_double_spending(&serial_number)
|
||||
.await?;
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use nym_pemstore::traits::{PemStorableKey, PemStorableKeyPair};
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
use std::fmt::{self, Debug, Display, Formatter};
|
||||
use std::str::FromStr;
|
||||
use thiserror::Error;
|
||||
use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||
@@ -112,12 +112,18 @@ impl PemStorableKeyPair for KeyPair {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)]
|
||||
#[derive(PartialEq, Eq, Hash, Copy, Clone)]
|
||||
pub struct PublicKey(x25519_dalek::PublicKey);
|
||||
|
||||
impl Display for PublicKey {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.to_base58_string())
|
||||
Display::fmt(&self.to_base58_string(), f)
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for PublicKey {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
Debug::fmt(&self.to_base58_string(), f)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,8 +31,16 @@ pub mod option_bs58_x25519_pubkey {
|
||||
pub fn deserialize<'de, D: Deserializer<'de>>(
|
||||
deserializer: D,
|
||||
) -> Result<Option<PublicKey>, D::Error> {
|
||||
let s = Option::<String>::deserialize(deserializer)?;
|
||||
s.map(|s| PublicKey::from_base58_string(&s).map_err(serde::de::Error::custom))
|
||||
.transpose()
|
||||
match Option::<String>::deserialize(deserializer)? {
|
||||
None => Ok(None),
|
||||
Some(s) => {
|
||||
if s.is_empty() {
|
||||
Ok(None)
|
||||
} else {
|
||||
Some(PublicKey::from_base58_string(&s).map_err(serde::de::Error::custom))
|
||||
.transpose()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ pub use ed25519_dalek::SignatureError;
|
||||
use ed25519_dalek::{Signer, SigningKey};
|
||||
pub use ed25519_dalek::{Verifier, PUBLIC_KEY_LENGTH, SECRET_KEY_LENGTH, SIGNATURE_LENGTH};
|
||||
use nym_pemstore::traits::{PemStorableKey, PemStorableKeyPair};
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
use std::fmt::{self, Debug, Display, Formatter};
|
||||
use std::str::FromStr;
|
||||
use thiserror::Error;
|
||||
use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||
@@ -119,12 +119,18 @@ impl PemStorableKeyPair for KeyPair {
|
||||
}
|
||||
|
||||
/// ed25519 EdDSA Public Key
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||
pub struct PublicKey(ed25519_dalek::VerifyingKey);
|
||||
|
||||
impl Display for PublicKey {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.to_base58_string())
|
||||
Display::fmt(&self.to_base58_string(), f)
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for PublicKey {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
Debug::fmt(&self.to_base58_string(), f)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,6 +35,9 @@ pub enum HttpClientError<E: Display = String> {
|
||||
source: reqwest::Error,
|
||||
},
|
||||
|
||||
#[error("failed to deserialise received response: {source}")]
|
||||
ResponseDeserialisationFailure { source: serde_json::Error },
|
||||
|
||||
#[error("provided url is malformed: {source}")]
|
||||
MalformedUrl {
|
||||
#[from]
|
||||
@@ -526,7 +529,11 @@ where
|
||||
}
|
||||
|
||||
if res.status().is_success() {
|
||||
Ok(res.json().await?)
|
||||
let text = res.text().await?;
|
||||
match serde_json::from_str(&text) {
|
||||
Ok(res) => Ok(res),
|
||||
Err(source) => Err(HttpClientError::ResponseDeserialisationFailure { source }),
|
||||
}
|
||||
} else if res.status() == StatusCode::NOT_FOUND {
|
||||
Err(HttpClientError::NotFound)
|
||||
} else {
|
||||
|
||||
@@ -11,6 +11,7 @@ license.workspace = true
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
axum-client-ip.workspace = true
|
||||
axum.workspace = true
|
||||
bytes = { workspace = true }
|
||||
colored.workspace = true
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use axum::extract::{ConnectInfo, Request};
|
||||
use axum::extract::Request;
|
||||
use axum::http::header::{HOST, USER_AGENT};
|
||||
use axum::http::HeaderValue;
|
||||
use axum::middleware::Next;
|
||||
use axum::response::IntoResponse;
|
||||
use axum_client_ip::InsecureClientIp;
|
||||
use colored::Colorize;
|
||||
use std::net::SocketAddr;
|
||||
use std::time::Instant;
|
||||
use tracing::info;
|
||||
|
||||
pub async fn logger(
|
||||
ConnectInfo(addr): ConnectInfo<SocketAddr>,
|
||||
InsecureClientIp(addr): InsecureClientIp,
|
||||
request: Request,
|
||||
next: Next,
|
||||
) -> impl IntoResponse {
|
||||
|
||||
@@ -19,10 +19,10 @@ pub enum TicketTypeRepr {
|
||||
}
|
||||
|
||||
impl TicketTypeRepr {
|
||||
pub const WIREGUARD_ENTRY_TICKET_SIZE: u64 = 512 * 1024 * 1024; // 512 MB
|
||||
pub const WIREGUARD_EXIT_TICKET_SIZE: u64 = 512 * 1024 * 1024; // 512 MB
|
||||
pub const MIXNET_ENTRY_TICKET_SIZE: u64 = 128 * 1024 * 1024; // 128 MB
|
||||
pub const MIXNET_EXIT_TICKET_SIZE: u64 = 128 * 1024 * 1024; // 128 MB
|
||||
pub const WIREGUARD_ENTRY_TICKET_SIZE: u64 = 500 * 1000 * 1000; // 500 MB
|
||||
pub const WIREGUARD_EXIT_TICKET_SIZE: u64 = 500 * 1000 * 1000; // 500 MB
|
||||
pub const MIXNET_ENTRY_TICKET_SIZE: u64 = 200 * 1000 * 1000; // 200 MB
|
||||
pub const MIXNET_EXIT_TICKET_SIZE: u64 = 100 * 1000 * 1000; // 100 MB
|
||||
|
||||
/// How much bandwidth (in bytes) one ticket can grant
|
||||
pub const fn bandwidth_value(&self) -> u64 {
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use crate::fragment::{linked_fragment_payload_max_len, unlinked_fragment_payload_max_len};
|
||||
use dashmap::DashMap;
|
||||
use fragment::{Fragment, FragmentHeader};
|
||||
use fragment::FragmentHeader;
|
||||
use nym_crypto::asymmetric::ed25519::PublicKey;
|
||||
use serde::Serialize;
|
||||
pub use set::split_into_sets;
|
||||
@@ -29,6 +26,59 @@ pub mod fragment;
|
||||
pub mod reconstruction;
|
||||
pub mod set;
|
||||
|
||||
pub mod monitoring {
|
||||
use crate::fragment::Fragment;
|
||||
use crate::{ReceivedFragment, SentFragment};
|
||||
use dashmap::DashMap;
|
||||
use nym_crypto::asymmetric::ed25519::PublicKey;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::LazyLock;
|
||||
|
||||
pub static ENABLED: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
pub static FRAGMENTS_RECEIVED: LazyLock<DashMap<i32, Vec<ReceivedFragment>>> =
|
||||
LazyLock::new(DashMap::new);
|
||||
|
||||
pub static FRAGMENTS_SENT: LazyLock<DashMap<i32, Vec<SentFragment>>> =
|
||||
LazyLock::new(DashMap::new);
|
||||
|
||||
pub fn enable() {
|
||||
ENABLED.store(true, Ordering::Relaxed)
|
||||
}
|
||||
|
||||
pub fn enabled() -> bool {
|
||||
ENABLED.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! now {
|
||||
() => {
|
||||
match std::time::SystemTime::now().duration_since(std::time::SystemTime::UNIX_EPOCH) {
|
||||
Ok(n) => n.as_secs(),
|
||||
Err(_) => 0,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn fragment_received(fragment: &Fragment) {
|
||||
if enabled() {
|
||||
let id = fragment.fragment_identifier().set_id();
|
||||
let mut entry = FRAGMENTS_RECEIVED.entry(id).or_default();
|
||||
let r = ReceivedFragment::new(fragment.header(), now!());
|
||||
entry.push(r);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fragment_sent(fragment: &Fragment, client_nonce: i32, destination: PublicKey, hops: u8) {
|
||||
if enabled() {
|
||||
let id = fragment.fragment_identifier().set_id();
|
||||
let mut entry = FRAGMENTS_SENT.entry(id).or_default();
|
||||
let s = SentFragment::new(fragment.header(), now!(), client_nonce, destination, hops);
|
||||
entry.push(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FragmentMixParams {
|
||||
destination: PublicKey,
|
||||
@@ -112,35 +162,6 @@ impl ReceivedFragment {
|
||||
}
|
||||
}
|
||||
|
||||
pub static FRAGMENTS_RECEIVED: LazyLock<DashMap<i32, Vec<ReceivedFragment>>> =
|
||||
LazyLock::new(DashMap::new);
|
||||
|
||||
pub static FRAGMENTS_SENT: LazyLock<DashMap<i32, Vec<SentFragment>>> = LazyLock::new(DashMap::new);
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! now {
|
||||
() => {
|
||||
match std::time::SystemTime::now().duration_since(std::time::SystemTime::UNIX_EPOCH) {
|
||||
Ok(n) => n.as_secs(),
|
||||
Err(_) => 0,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn fragment_received(fragment: &Fragment) {
|
||||
let id = fragment.fragment_identifier().set_id();
|
||||
let mut entry = FRAGMENTS_RECEIVED.entry(id).or_default();
|
||||
let r = ReceivedFragment::new(fragment.header(), now!());
|
||||
entry.push(r);
|
||||
}
|
||||
|
||||
pub fn fragment_sent(fragment: &Fragment, client_nonce: i32, destination: PublicKey, hops: u8) {
|
||||
let id = fragment.fragment_identifier().set_id();
|
||||
let mut entry = FRAGMENTS_SENT.entry(id).or_default();
|
||||
let s = SentFragment::new(fragment.header(), now!(), client_nonce, destination, hops);
|
||||
entry.push(s);
|
||||
}
|
||||
|
||||
/// The idea behind the process of chunking is to incur as little data overhead as possible due
|
||||
/// to very computationally costly sphinx encapsulation procedure.
|
||||
///
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
use crate::fragment::Fragment;
|
||||
use crate::{fragment_received, ChunkingError};
|
||||
use crate::{monitoring, ChunkingError};
|
||||
use log::*;
|
||||
use std::collections::HashMap;
|
||||
|
||||
@@ -110,7 +110,7 @@ impl ReconstructionBuffer {
|
||||
}
|
||||
});
|
||||
|
||||
fragment_received(&fragment);
|
||||
monitoring::fragment_received(&fragment);
|
||||
|
||||
let fragment_index = fragment.current_fragment() as usize - 1;
|
||||
if self.fragments[fragment_index].is_some() {
|
||||
|
||||
@@ -12,7 +12,6 @@ use nym_sphinx_addressing::clients::Recipient;
|
||||
use nym_sphinx_addressing::nodes::NymNodeRoutingAddress;
|
||||
use nym_sphinx_anonymous_replies::reply_surb::ReplySurb;
|
||||
use nym_sphinx_chunking::fragment::{Fragment, FragmentIdentifier};
|
||||
use nym_sphinx_chunking::fragment_sent;
|
||||
use nym_sphinx_forwarding::packet::MixPacket;
|
||||
use nym_sphinx_params::packet_sizes::PacketSize;
|
||||
use nym_sphinx_params::{PacketType, ReplySurbKeyDigestAlgorithm, DEFAULT_NUM_MIX_HOPS};
|
||||
@@ -21,6 +20,7 @@ use nym_topology::{NymTopology, NymTopologyError};
|
||||
use rand::{CryptoRng, Rng, SeedableRng};
|
||||
use rand_chacha::ChaCha20Rng;
|
||||
|
||||
use nym_sphinx_chunking::monitoring;
|
||||
use std::time::Duration;
|
||||
|
||||
pub(crate) mod payload;
|
||||
@@ -206,7 +206,7 @@ pub trait FragmentPreparer {
|
||||
|
||||
let destination = packet_recipient.gateway();
|
||||
let hops = mix_hops.unwrap_or(self.num_mix_hops());
|
||||
fragment_sent(&fragment, self.nonce(), *destination, hops);
|
||||
monitoring::fragment_sent(&fragment, self.nonce(), *destination, hops);
|
||||
|
||||
let non_reply_overhead = encryption::PUBLIC_KEY_SIZE;
|
||||
let expected_plaintext = match packet_type {
|
||||
|
||||
@@ -286,6 +286,10 @@ impl NymTopology {
|
||||
self.get_gateway(gateway_identity).is_some()
|
||||
}
|
||||
|
||||
pub fn insert_gateway(&mut self, gateway: gateway::LegacyNode) {
|
||||
self.gateways.push(gateway)
|
||||
}
|
||||
|
||||
pub fn set_gateways(&mut self, gateways: Vec<gateway::LegacyNode>) {
|
||||
self.gateways = gateways
|
||||
}
|
||||
|
||||
@@ -116,7 +116,7 @@ impl<'a> TryFrom<&'a SkimmedNode> for LegacyNode {
|
||||
});
|
||||
}
|
||||
|
||||
let layer = match value.epoch_role {
|
||||
let layer = match value.role {
|
||||
NodeRole::Mixnode { layer } => layer
|
||||
.try_into()
|
||||
.map_err(|_| MixnodeConversionError::InvalidLayer)?,
|
||||
|
||||
@@ -68,7 +68,7 @@ pub async fn current_network_topology_async(
|
||||
|
||||
let api_client = NymApiClient::new(url);
|
||||
let mixnodes = api_client
|
||||
.get_basic_active_mixing_assigned_nodes(None)
|
||||
.get_all_basic_active_mixing_assigned_nodes(None)
|
||||
.await?;
|
||||
let gateways = api_client.get_all_basic_entry_assigned_nodes(None).await?;
|
||||
|
||||
|
||||
@@ -3,9 +3,6 @@
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
#[error("peers in wireguard don't match with in-memory ")]
|
||||
PeerMismatch,
|
||||
|
||||
#[error("traffic byte data needs to be increasing")]
|
||||
InconsistentConsumedBytes,
|
||||
|
||||
@@ -20,4 +17,7 @@ pub enum Error {
|
||||
|
||||
#[error("{0}")]
|
||||
GatewayStorage(#[from] nym_gateway_storage::error::StorageError),
|
||||
|
||||
#[error("{0}")]
|
||||
SystemTime(#[from] std::time::SystemTimeError),
|
||||
}
|
||||
|
||||
@@ -226,14 +226,10 @@ impl<St: Storage + Clone + 'static> PeerController<St> {
|
||||
.available_bandwidth()
|
||||
.await
|
||||
} else {
|
||||
let peer = self
|
||||
.host_information
|
||||
.read()
|
||||
.await
|
||||
.peers
|
||||
.get(key)
|
||||
.ok_or(Error::PeerMismatch)?
|
||||
.clone();
|
||||
let Some(peer) = self.host_information.read().await.peers.get(key).cloned() else {
|
||||
// host information not updated yet
|
||||
return Ok(None);
|
||||
};
|
||||
BANDWIDTH_CAP_PER_DAY.saturating_sub((peer.rx_bytes + peer.tx_bytes) as i64)
|
||||
};
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
use crate::error::Error;
|
||||
use crate::peer_controller::PeerControlRequest;
|
||||
use defguard_wireguard_rs::host::Peer;
|
||||
use defguard_wireguard_rs::{host::Host, key::Key};
|
||||
use futures::channel::oneshot;
|
||||
use nym_authenticator_requests::v2::registration::BANDWIDTH_CAP_PER_DAY;
|
||||
@@ -12,10 +13,12 @@ use nym_gateway_storage::Storage;
|
||||
use nym_task::TaskClient;
|
||||
use nym_wireguard_types::DEFAULT_PEER_TIMEOUT_CHECK;
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, SystemTime};
|
||||
use tokio::sync::{mpsc, RwLock};
|
||||
use tokio_stream::{wrappers::IntervalStream, StreamExt};
|
||||
|
||||
pub(crate) type SharedBandwidthStorageManager<St> = Arc<RwLock<BandwidthStorageManager<St>>>;
|
||||
const AUTO_REMOVE_AFTER: Duration = Duration::from_secs(60 * 60 * 24); // 24 hours
|
||||
|
||||
pub struct PeerHandle<St> {
|
||||
storage: St,
|
||||
@@ -25,6 +28,7 @@ pub struct PeerHandle<St> {
|
||||
request_tx: mpsc::Sender<PeerControlRequest>,
|
||||
timeout_check_interval: IntervalStream,
|
||||
task_client: TaskClient,
|
||||
startup_timestamp: SystemTime,
|
||||
}
|
||||
|
||||
impl<St: Storage + Clone + 'static> PeerHandle<St> {
|
||||
@@ -39,7 +43,8 @@ impl<St: Storage + Clone + 'static> PeerHandle<St> {
|
||||
let timeout_check_interval = tokio_stream::wrappers::IntervalStream::new(
|
||||
tokio::time::interval(DEFAULT_PEER_TIMEOUT_CHECK),
|
||||
);
|
||||
let task_client = task_client.fork(format!("peer{public_key}"));
|
||||
let mut task_client = task_client.fork(format!("peer-{public_key}"));
|
||||
task_client.disarm();
|
||||
PeerHandle {
|
||||
storage,
|
||||
public_key,
|
||||
@@ -48,14 +53,11 @@ impl<St: Storage + Clone + 'static> PeerHandle<St> {
|
||||
request_tx,
|
||||
timeout_check_interval,
|
||||
task_client,
|
||||
startup_timestamp: SystemTime::now(),
|
||||
}
|
||||
}
|
||||
|
||||
async fn remove_depleted_peer(&self) -> Result<bool, Error> {
|
||||
log::debug!(
|
||||
"Peer {} doesn't have bandwidth anymore, removing it",
|
||||
self.public_key.to_string()
|
||||
);
|
||||
async fn remove_peer(&self) -> Result<bool, Error> {
|
||||
let (response_tx, response_rx) = oneshot::channel();
|
||||
self.request_tx
|
||||
.send(PeerControlRequest::RemovePeer {
|
||||
@@ -71,35 +73,44 @@ impl<St: Storage + Clone + 'static> PeerHandle<St> {
|
||||
Ok(success)
|
||||
}
|
||||
|
||||
async fn active_peer(&mut self, storage_peer: WireguardPeer) -> Result<bool, Error> {
|
||||
let kernel_peer = self
|
||||
.host_information
|
||||
.read()
|
||||
.await
|
||||
.peers
|
||||
.get(&self.public_key)
|
||||
.ok_or(Error::PeerMismatch)?
|
||||
.clone();
|
||||
async fn active_peer(
|
||||
&mut self,
|
||||
storage_peer: WireguardPeer,
|
||||
kernel_peer: Peer,
|
||||
) -> Result<bool, Error> {
|
||||
if let Some(bandwidth_manager) = &self.bandwidth_storage_manager {
|
||||
let spent_bandwidth = (kernel_peer.rx_bytes + kernel_peer.tx_bytes)
|
||||
.checked_sub(storage_peer.rx_bytes as u64 + storage_peer.tx_bytes as u64)
|
||||
.ok_or(Error::InconsistentConsumedBytes)?
|
||||
.try_into()
|
||||
.map_err(|_| Error::InconsistentConsumedBytes)?;
|
||||
if bandwidth_manager
|
||||
.write()
|
||||
.await
|
||||
.try_use_bandwidth(spent_bandwidth)
|
||||
.await
|
||||
.is_err()
|
||||
if spent_bandwidth > 0
|
||||
&& bandwidth_manager
|
||||
.write()
|
||||
.await
|
||||
.try_use_bandwidth(spent_bandwidth)
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
let success = self.remove_depleted_peer().await?;
|
||||
let success = self.remove_peer().await?;
|
||||
return Ok(!success);
|
||||
}
|
||||
} else {
|
||||
if SystemTime::now().duration_since(self.startup_timestamp)? >= AUTO_REMOVE_AFTER {
|
||||
log::debug!(
|
||||
"Peer {} has been present for 24 hours, removing it",
|
||||
self.public_key.to_string()
|
||||
);
|
||||
let success = self.remove_peer().await?;
|
||||
return Ok(!success);
|
||||
}
|
||||
let spent_bandwidth = kernel_peer.rx_bytes + kernel_peer.tx_bytes;
|
||||
if spent_bandwidth >= BANDWIDTH_CAP_PER_DAY {
|
||||
let success = self.remove_depleted_peer().await?;
|
||||
log::debug!(
|
||||
"Peer {} doesn't have bandwidth anymore, removing it",
|
||||
self.public_key.to_string()
|
||||
);
|
||||
let success = self.remove_peer().await?;
|
||||
return Ok(!success);
|
||||
}
|
||||
}
|
||||
@@ -111,11 +122,21 @@ impl<St: Storage + Clone + 'static> PeerHandle<St> {
|
||||
while !self.task_client.is_shutdown() {
|
||||
tokio::select! {
|
||||
_ = self.timeout_check_interval.next() => {
|
||||
let Some(peer) = self.storage.get_wireguard_peer(&self.public_key.to_string()).await? else {
|
||||
let Some(kernel_peer) = self
|
||||
.host_information
|
||||
.read()
|
||||
.await
|
||||
.peers
|
||||
.get(&self.public_key)
|
||||
.cloned() else {
|
||||
// the host information hasn't beed updated yet
|
||||
continue;
|
||||
};
|
||||
let Some(storage_peer) = self.storage.get_wireguard_peer(&self.public_key.to_string()).await? else {
|
||||
log::debug!("Peer {:?} not in storage anymore, shutting down handle", self.public_key);
|
||||
return Ok(());
|
||||
};
|
||||
if !self.active_peer(peer).await? {
|
||||
if !self.active_peer(storage_peer, kernel_peer).await? {
|
||||
log::debug!("Peer {:?} doesn't have bandwidth anymore, shutting down handle", self.public_key);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@@ -3689,8 +3689,7 @@
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"Percent": {
|
||||
"description": "Percent represents a value between 0 and 100% (i.e. between 0.0 and 1.0)",
|
||||
@@ -5239,8 +5238,7 @@
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"Percent": {
|
||||
"description": "Percent represents a value between 0 and 100% (i.e. between 0.0 and 1.0)",
|
||||
@@ -5575,8 +5573,7 @@
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"Percent": {
|
||||
"description": "Percent represents a value between 0 and 100% (i.e. between 0.0 and 1.0)",
|
||||
@@ -7595,8 +7592,7 @@
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"Percent": {
|
||||
"description": "Percent represents a value between 0 and 100% (i.e. between 0.0 and 1.0)",
|
||||
|
||||
@@ -315,8 +315,7 @@
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"Percent": {
|
||||
"description": "Percent represents a value between 0 and 100% (i.e. between 0.0 and 1.0)",
|
||||
|
||||
@@ -323,8 +323,7 @@
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"Percent": {
|
||||
"description": "Percent represents a value between 0 and 100% (i.e. between 0.0 and 1.0)",
|
||||
|
||||
@@ -317,8 +317,7 @@
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"Percent": {
|
||||
"description": "Percent represents a value between 0 and 100% (i.e. between 0.0 and 1.0)",
|
||||
|
||||
@@ -319,8 +319,7 @@
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"Percent": {
|
||||
"description": "Percent represents a value between 0 and 100% (i.e. between 0.0 and 1.0)",
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
use super::helpers::must_get_gateway_bond_by_owner;
|
||||
use super::storage;
|
||||
use crate::constants::default_node_costs;
|
||||
use crate::interval::storage as interval_storage;
|
||||
use crate::mixnet_contract_settings::storage as mixnet_params_storage;
|
||||
use crate::nodes::helpers::save_new_nymnode_with_id;
|
||||
use crate::nodes::transactions::add_nym_node_inner;
|
||||
@@ -115,6 +116,10 @@ pub fn try_migrate_to_nymnode(
|
||||
comment: "legacy gateway did not have a pre-assigned node id".to_string(),
|
||||
})?;
|
||||
|
||||
let current_epoch =
|
||||
interval_storage::current_interval(deps.storage)?.current_epoch_absolute_id();
|
||||
let previous_epoch = current_epoch.saturating_sub(1);
|
||||
|
||||
// create nym-node entry
|
||||
// for gateways it's quite straightforward as there are no delegations or rewards to worry about
|
||||
save_new_nymnode_with_id(
|
||||
@@ -125,6 +130,7 @@ pub fn try_migrate_to_nymnode(
|
||||
cost_params,
|
||||
info.sender.clone(),
|
||||
gateway_bond.pledge_amount,
|
||||
previous_epoch,
|
||||
)?;
|
||||
|
||||
storage::PREASSIGNED_LEGACY_IDS.remove(deps.storage, gateway_identity.clone());
|
||||
|
||||
@@ -22,6 +22,8 @@ pub(crate) fn save_new_nymnode(
|
||||
pledge: Coin,
|
||||
) -> Result<NodeId, MixnetContractError> {
|
||||
let node_id = next_nymnode_id_counter(storage)?;
|
||||
let current_epoch = interval_storage::current_interval(storage)?.current_epoch_absolute_id();
|
||||
|
||||
save_new_nymnode_with_id(
|
||||
storage,
|
||||
node_id,
|
||||
@@ -30,11 +32,13 @@ pub(crate) fn save_new_nymnode(
|
||||
cost_params,
|
||||
owner,
|
||||
pledge,
|
||||
current_epoch,
|
||||
)?;
|
||||
|
||||
Ok(node_id)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn save_new_nymnode_with_id(
|
||||
storage: &mut dyn Storage,
|
||||
node_id: NodeId,
|
||||
@@ -43,10 +47,9 @@ pub(crate) fn save_new_nymnode_with_id(
|
||||
cost_params: NodeCostParams,
|
||||
owner: Addr,
|
||||
pledge: Coin,
|
||||
last_rewarding_epoch: u32,
|
||||
) -> Result<(), MixnetContractError> {
|
||||
let current_epoch = interval_storage::current_interval(storage)?.current_epoch_absolute_id();
|
||||
|
||||
let node_rewarding = NodeRewarding::initialise_new(cost_params, &pledge, current_epoch)?;
|
||||
let node_rewarding = NodeRewarding::initialise_new(cost_params, &pledge, last_rewarding_epoch)?;
|
||||
let node_bond = NymNodeBond::new(node_id, owner, pledge, node, bonding_height);
|
||||
|
||||
// save node bond data
|
||||
|
||||
@@ -52,8 +52,8 @@ pub(crate) fn save_assignment(
|
||||
|
||||
// update metadata
|
||||
let mut metadata = ROLES_METADATA.load(storage, inactive)?;
|
||||
let last = assignment.nodes.last().copied().unwrap_or_default();
|
||||
metadata.set_highest_id(last, assignment.role);
|
||||
let highest_id = assignment.nodes.iter().max().copied().unwrap_or_default();
|
||||
metadata.set_highest_id(highest_id, assignment.role);
|
||||
metadata.set_role_count(assignment.role, assignment.nodes.len() as u32);
|
||||
if assignment.is_final_assignment() {
|
||||
metadata.fully_assigned = true
|
||||
@@ -140,6 +140,7 @@ pub(crate) fn initialise_storage(storage: &mut dyn Storage) -> Result<(), Mixnet
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::support::tests::test_helpers;
|
||||
use crate::support::tests::test_helpers::TestSetup;
|
||||
|
||||
#[test]
|
||||
fn next_id() {
|
||||
@@ -149,4 +150,33 @@ mod tests {
|
||||
assert_eq!(i, next_nymnode_id_counter(deps.as_mut().storage).unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assigning_role_uses_highest_id_even_if_not_sorted() {
|
||||
let mut test = TestSetup::new();
|
||||
let deps = test.deps_mut();
|
||||
|
||||
let sorted = RoleAssignment {
|
||||
role: Role::EntryGateway,
|
||||
nodes: vec![1, 2, 3],
|
||||
};
|
||||
|
||||
let unsorted = RoleAssignment {
|
||||
role: Role::Layer1,
|
||||
nodes: vec![8, 5, 4],
|
||||
};
|
||||
|
||||
save_assignment(deps.storage, sorted).unwrap();
|
||||
save_assignment(deps.storage, unsorted).unwrap();
|
||||
|
||||
let storage = deps.as_ref().storage;
|
||||
|
||||
let active_bucket = ACTIVE_ROLES_BUCKET.load(storage).unwrap();
|
||||
let inactive = active_bucket.other() as u8;
|
||||
let metadata = ROLES_METADATA.load(storage, inactive).unwrap();
|
||||
|
||||
assert_eq!(metadata.entry_gateway_metadata.highest_id, 3);
|
||||
assert_eq!(metadata.layer1_metadata.highest_id, 8);
|
||||
assert_eq!(metadata.highest_rewarded_id(), 8)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "explorer-api"
|
||||
version = "1.1.40"
|
||||
version = "1.1.41"
|
||||
edition = "2021"
|
||||
license.workspace = true
|
||||
|
||||
|
||||
@@ -12,6 +12,8 @@ pub use nym_explorer_api_requests::{
|
||||
|
||||
// Paths
|
||||
const API_VERSION: &str = "v1";
|
||||
const TMP: &str = "tmp";
|
||||
const UNSTABLE: &str = "unstable";
|
||||
const MIXNODES: &str = "mix-nodes";
|
||||
const GATEWAYS: &str = "gateways";
|
||||
|
||||
@@ -53,6 +55,12 @@ impl ExplorerClient {
|
||||
Ok(Self { client, url })
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn new_with_timeout(url: url::Url, timeout: Duration) -> Result<Self, ExplorerApiError> {
|
||||
let client = reqwest::Client::builder().timeout(timeout).build()?;
|
||||
Ok(Self { client, url })
|
||||
}
|
||||
|
||||
async fn send_get_request(
|
||||
&self,
|
||||
paths: &[&str],
|
||||
@@ -86,6 +94,13 @@ impl ExplorerClient {
|
||||
pub async fn get_gateways(&self) -> Result<Vec<PrettyDetailedGatewayBond>, ExplorerApiError> {
|
||||
self.query_explorer_api(&[API_VERSION, GATEWAYS]).await
|
||||
}
|
||||
|
||||
pub async fn unstable_get_gateways(
|
||||
&self,
|
||||
) -> Result<Vec<PrettyDetailedGatewayBond>, ExplorerApiError> {
|
||||
self.query_explorer_api(&[API_VERSION, TMP, UNSTABLE, GATEWAYS])
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
fn combine_url(mut base_url: Url, paths: &[&str]) -> Result<Url, ExplorerApiError> {
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
use crate::state::ExplorerApiStateContext;
|
||||
use log::{info, warn};
|
||||
use nym_explorer_api_requests::Location;
|
||||
use nym_network_defaults::DEFAULT_NYM_NODE_HTTP_PORT;
|
||||
use nym_task::TaskClient;
|
||||
|
||||
pub(crate) struct GeoLocateTask {
|
||||
@@ -25,6 +26,7 @@ impl GeoLocateTask {
|
||||
_ = interval_timer.tick() => {
|
||||
self.locate_mix_nodes().await;
|
||||
self.locate_gateways().await;
|
||||
self.locate_nym_nodes().await;
|
||||
}
|
||||
_ = self.shutdown.recv() => {
|
||||
trace!("Listener: Received shutdown");
|
||||
@@ -109,6 +111,83 @@ impl GeoLocateTask {
|
||||
trace!("All mix nodes located");
|
||||
}
|
||||
|
||||
async fn locate_nym_nodes(&mut self) {
|
||||
// I'm unwrapping to the default value to get rid of an extra indentation level from the `if let Some(...) = ...`
|
||||
// If the value is None, we'll unwrap to an empty hashmap and the `values()` loop won't do any work anyway
|
||||
let nym_nodes = self.state.inner.nymnodes.get_bonded_nymnodes().await;
|
||||
|
||||
let geo_ip = self.state.inner.geo_ip.0.clone();
|
||||
|
||||
for (i, cache_item) in nym_nodes.values().enumerate() {
|
||||
if self
|
||||
.state
|
||||
.inner
|
||||
.nymnodes
|
||||
.is_location_valid(cache_item.node_id())
|
||||
.await
|
||||
{
|
||||
// when the cached location is valid, don't locate and continue to next mix node
|
||||
continue;
|
||||
}
|
||||
|
||||
let bonded_host = &cache_item.bond_information.node.host;
|
||||
|
||||
match geo_ip.query(
|
||||
bonded_host,
|
||||
Some(
|
||||
cache_item
|
||||
.bond_information
|
||||
.node
|
||||
.custom_http_port
|
||||
.unwrap_or(DEFAULT_NYM_NODE_HTTP_PORT),
|
||||
),
|
||||
) {
|
||||
Ok(opt) => match opt {
|
||||
Some(location) => {
|
||||
let location: Location = location.into();
|
||||
|
||||
trace!(
|
||||
"{} mix nodes already located. host {} is located in {:#?}",
|
||||
i,
|
||||
bonded_host,
|
||||
location.three_letter_iso_country_code,
|
||||
);
|
||||
|
||||
if i > 0 && (i % 100) == 0 {
|
||||
info!("Located {} nym-nodes...", i + 1,);
|
||||
}
|
||||
|
||||
self.state
|
||||
.inner
|
||||
.nymnodes
|
||||
.set_location(cache_item.node_id(), Some(location))
|
||||
.await;
|
||||
|
||||
// one node has been located, so return out of the loop
|
||||
return;
|
||||
}
|
||||
None => {
|
||||
warn!("❌ Location for {bonded_host} not found.");
|
||||
self.state
|
||||
.inner
|
||||
.nymnodes
|
||||
.set_location(cache_item.node_id(), None)
|
||||
.await;
|
||||
}
|
||||
},
|
||||
Err(_e) => {
|
||||
// warn!(
|
||||
// "❌ Oh no! Location for {} failed. Error: {:#?}",
|
||||
// cache_item.mix_node().host,
|
||||
// e
|
||||
// );
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
trace!("All nym-nodes nodes located");
|
||||
}
|
||||
|
||||
async fn locate_gateways(&mut self) {
|
||||
let gateways = self.state.inner.gateways.get_gateways().await;
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ use crate::gateways::http::gateways_make_default_routes;
|
||||
use crate::http::swagger::get_docs;
|
||||
use crate::mix_node::http::mix_node_make_default_routes;
|
||||
use crate::mix_nodes::http::mix_nodes_make_default_routes;
|
||||
use crate::nym_nodes::http::unstable_temp_nymnodes_make_default_routes;
|
||||
use crate::overview::http::overview_make_default_routes;
|
||||
use crate::ping::http::ping_make_default_routes;
|
||||
use crate::service_providers::http::service_providers_make_default_routes;
|
||||
@@ -58,6 +59,7 @@ fn configure_rocket(state: ExplorerApiStateContext) -> Rocket<Build> {
|
||||
"/ping" => ping_make_default_routes(&openapi_settings),
|
||||
"/validators" => validators_make_default_routes(&openapi_settings),
|
||||
"/service-providers" => service_providers_make_default_routes(&openapi_settings),
|
||||
"/tmp/unstable" => unstable_temp_nymnodes_make_default_routes(&openapi_settings),
|
||||
};
|
||||
|
||||
building_rocket
|
||||
|
||||
@@ -22,6 +22,7 @@ mod http;
|
||||
mod location;
|
||||
mod mix_node;
|
||||
pub(crate) mod mix_nodes;
|
||||
mod nym_nodes;
|
||||
mod overview;
|
||||
mod ping;
|
||||
pub(crate) mod service_providers;
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::state::ExplorerApiStateContext;
|
||||
use nym_explorer_api_requests::PrettyDetailedGatewayBond;
|
||||
use okapi::openapi3::OpenApi;
|
||||
use rocket::serde::json::Json;
|
||||
use rocket::{Route, State};
|
||||
use rocket_okapi::settings::OpenApiSettings;
|
||||
|
||||
pub fn unstable_temp_nymnodes_make_default_routes(
|
||||
settings: &OpenApiSettings,
|
||||
) -> (Vec<Route>, OpenApi) {
|
||||
openapi_get_routes_spec![settings: all_gateways]
|
||||
}
|
||||
|
||||
#[openapi(tag = "UNSTABLE")]
|
||||
#[get("/gateways")]
|
||||
pub(crate) async fn all_gateways(
|
||||
state: &State<ExplorerApiStateContext>,
|
||||
) -> Json<Vec<PrettyDetailedGatewayBond>> {
|
||||
let mut gateways = state.inner.gateways.get_detailed_gateways().await;
|
||||
gateways.append(&mut state.inner.nymnodes.pretty_gateways().await);
|
||||
|
||||
Json(gateways)
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use nym_mixnet_contract_common::NodeId;
|
||||
|
||||
use crate::location::LocationCache;
|
||||
|
||||
pub(crate) type NymNodeLocationCache = LocationCache<NodeId>;
|
||||
@@ -0,0 +1,10 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
pub(crate) mod http;
|
||||
pub(crate) mod location;
|
||||
pub(crate) mod models;
|
||||
|
||||
pub(crate) const CACHE_ENTRY_TTL: Duration = Duration::from_secs(1200);
|
||||
@@ -0,0 +1,154 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::location::{LocationCache, LocationCacheItem};
|
||||
use crate::nym_nodes::location::NymNodeLocationCache;
|
||||
use crate::nym_nodes::CACHE_ENTRY_TTL;
|
||||
use nym_explorer_api_requests::{Location, PrettyDetailedGatewayBond};
|
||||
use nym_mixnet_contract_common::{Gateway, NodeId, NymNodeDetails};
|
||||
use nym_validator_client::models::NymNodeDescription;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, SystemTime};
|
||||
use tokio::sync::{RwLock, RwLockReadGuard};
|
||||
|
||||
pub(crate) struct NymNodesCache {
|
||||
pub(crate) valid_until: SystemTime,
|
||||
pub(crate) bonded_nym_nodes: HashMap<NodeId, NymNodeDetails>,
|
||||
pub(crate) described_nodes: HashMap<NodeId, NymNodeDescription>,
|
||||
}
|
||||
|
||||
impl NymNodesCache {
|
||||
fn new() -> Self {
|
||||
NymNodesCache {
|
||||
valid_until: SystemTime::now() - Duration::from_secs(60), // in the past
|
||||
bonded_nym_nodes: Default::default(),
|
||||
described_nodes: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
// fn is_valid(&self) -> bool {
|
||||
// self.valid_until >= SystemTime::now()
|
||||
// }
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct ThreadSafeNymNodesCache {
|
||||
nymnodes: Arc<RwLock<NymNodesCache>>,
|
||||
locations: Arc<RwLock<LocationCache<NodeId>>>,
|
||||
}
|
||||
|
||||
impl ThreadSafeNymNodesCache {
|
||||
pub(crate) fn new() -> Self {
|
||||
ThreadSafeNymNodesCache {
|
||||
nymnodes: Arc::new(RwLock::new(NymNodesCache::new())),
|
||||
locations: Arc::new(RwLock::new(NymNodeLocationCache::new())),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn new_with_location_cache(locations: NymNodeLocationCache) -> Self {
|
||||
ThreadSafeNymNodesCache {
|
||||
nymnodes: Arc::new(RwLock::new(NymNodesCache::new())),
|
||||
locations: Arc::new(RwLock::new(locations)),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn is_location_valid(&self, node_id: NodeId) -> bool {
|
||||
self.locations
|
||||
.read()
|
||||
.await
|
||||
.get(&node_id)
|
||||
.map_or(false, |cache_item| {
|
||||
cache_item.valid_until > SystemTime::now()
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) async fn get_bonded_nymnodes(
|
||||
&self,
|
||||
) -> RwLockReadGuard<HashMap<NodeId, NymNodeDetails>> {
|
||||
let guard = self.nymnodes.read().await;
|
||||
RwLockReadGuard::map(guard, |n| &n.bonded_nym_nodes)
|
||||
}
|
||||
|
||||
pub(crate) async fn get_locations(&self) -> NymNodeLocationCache {
|
||||
self.locations.read().await.clone()
|
||||
}
|
||||
|
||||
pub(crate) async fn set_location(&self, node_id: NodeId, location: Option<Location>) {
|
||||
// cache the location for this mix node so that it can be used when the mix node list is refreshed
|
||||
self.locations
|
||||
.write()
|
||||
.await
|
||||
.insert(node_id, LocationCacheItem::new_from_location(location));
|
||||
}
|
||||
|
||||
pub(crate) async fn update_cache(
|
||||
&self,
|
||||
all_bonds: Vec<NymNodeDetails>,
|
||||
descriptions: Vec<NymNodeDescription>,
|
||||
) {
|
||||
let mut guard = self.nymnodes.write().await;
|
||||
guard.bonded_nym_nodes = all_bonds
|
||||
.into_iter()
|
||||
.map(|details| (details.node_id(), details))
|
||||
.collect();
|
||||
guard.described_nodes = descriptions
|
||||
.into_iter()
|
||||
.map(|description| (description.node_id, description))
|
||||
.collect();
|
||||
|
||||
guard.valid_until = SystemTime::now() + CACHE_ENTRY_TTL;
|
||||
}
|
||||
|
||||
pub(crate) async fn pretty_gateways(&self) -> Vec<PrettyDetailedGatewayBond> {
|
||||
let nodes_guard = self.nymnodes.read().await;
|
||||
let location_guard = self.locations.read().await;
|
||||
|
||||
let mut pretty_gateways = vec![];
|
||||
|
||||
for (node_id, native_nymnode) in &nodes_guard.bonded_nym_nodes {
|
||||
let Some(description) = nodes_guard.described_nodes.get(node_id) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if description.description.declared_role.entry {
|
||||
let location = location_guard.get(node_id);
|
||||
let bond = &native_nymnode.bond_information;
|
||||
|
||||
pretty_gateways.push(PrettyDetailedGatewayBond {
|
||||
pledge_amount: bond.original_pledge.clone(),
|
||||
owner: bond.owner.clone(),
|
||||
block_height: bond.bonding_height,
|
||||
gateway: Gateway {
|
||||
host: bond.node.host.clone(),
|
||||
mix_port: description.description.mix_port(),
|
||||
clients_port: description.description.mixnet_websockets.ws_port,
|
||||
location: description
|
||||
.description
|
||||
.auxiliary_details
|
||||
.location
|
||||
.as_ref()
|
||||
.map(|l| l.to_string())
|
||||
.unwrap_or_default(),
|
||||
sphinx_key: description
|
||||
.description
|
||||
.host_information
|
||||
.keys
|
||||
.x25519
|
||||
.to_base58_string(),
|
||||
identity_key: bond.node.identity_key.clone(),
|
||||
version: description
|
||||
.description
|
||||
.build_information
|
||||
.build_version
|
||||
.clone(),
|
||||
},
|
||||
proxy: None,
|
||||
location: location.and_then(|l| l.location.clone()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pretty_gateways
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,8 @@ use crate::gateways::models::ThreadsafeGatewayCache;
|
||||
use crate::mix_node::models::ThreadsafeMixNodeCache;
|
||||
use crate::mix_nodes::location::MixnodeLocationCache;
|
||||
use crate::mix_nodes::models::ThreadsafeMixNodesCache;
|
||||
use crate::nym_nodes::location::NymNodeLocationCache;
|
||||
use crate::nym_nodes::models::ThreadSafeNymNodesCache;
|
||||
use crate::ping::models::ThreadsafePingCache;
|
||||
use crate::validators::models::ThreadsafeValidatorCache;
|
||||
|
||||
@@ -30,6 +32,7 @@ pub struct ExplorerApiState {
|
||||
pub(crate) gateways: ThreadsafeGatewayCache,
|
||||
pub(crate) mixnode: ThreadsafeMixNodeCache,
|
||||
pub(crate) mixnodes: ThreadsafeMixNodesCache,
|
||||
pub(crate) nymnodes: ThreadSafeNymNodesCache,
|
||||
pub(crate) ping: ThreadsafePingCache,
|
||||
pub(crate) validators: ThreadsafeValidatorCache,
|
||||
pub(crate) geo_ip: ThreadsafeGeoIp,
|
||||
@@ -49,6 +52,7 @@ pub struct ExplorerApiStateOnDisk {
|
||||
pub(crate) country_node_distribution: CountryNodesDistribution,
|
||||
pub(crate) mixnode_location_cache: MixnodeLocationCache,
|
||||
pub(crate) gateway_location_cache: GatewayLocationCache,
|
||||
pub(crate) nymnode_location_cache: NymNodeLocationCache,
|
||||
pub(crate) as_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
@@ -85,6 +89,9 @@ impl ExplorerApiStateContext {
|
||||
mixnodes: ThreadsafeMixNodesCache::new_with_location_cache(
|
||||
state.mixnode_location_cache,
|
||||
),
|
||||
nymnodes: ThreadSafeNymNodesCache::new_with_location_cache(
|
||||
state.nymnode_location_cache,
|
||||
),
|
||||
ping: ThreadsafePingCache::new(),
|
||||
validators: ThreadsafeValidatorCache::new(),
|
||||
validator_client: ThreadsafeValidatorClient::new(),
|
||||
@@ -101,6 +108,7 @@ impl ExplorerApiStateContext {
|
||||
gateways: ThreadsafeGatewayCache::new(),
|
||||
mixnode: ThreadsafeMixNodeCache::new(),
|
||||
mixnodes: ThreadsafeMixNodesCache::new(),
|
||||
nymnodes: ThreadSafeNymNodesCache::new(),
|
||||
ping: ThreadsafePingCache::new(),
|
||||
validators: ThreadsafeValidatorCache::new(),
|
||||
validator_client: ThreadsafeValidatorClient::new(),
|
||||
@@ -117,6 +125,7 @@ impl ExplorerApiStateContext {
|
||||
country_node_distribution: self.inner.country_node_distribution.get_all().await,
|
||||
mixnode_location_cache: self.inner.mixnodes.get_locations().await,
|
||||
gateway_location_cache: self.inner.gateways.get_locations().await,
|
||||
nymnode_location_cache: self.inner.nymnodes.get_locations().await,
|
||||
as_at: Utc::now(),
|
||||
};
|
||||
serde_json::to_writer(file, &state).expect("error writing state to disk");
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use nym_mixnet_contract_common::GatewayBond;
|
||||
use crate::mix_nodes::CACHE_REFRESH_RATE;
|
||||
use crate::state::ExplorerApiStateContext;
|
||||
use nym_mixnet_contract_common::{GatewayBond, NymNodeDetails};
|
||||
use nym_task::TaskClient;
|
||||
use nym_validator_client::models::MixNodeBondAnnotated;
|
||||
use nym_validator_client::models::{MixNodeBondAnnotated, NymNodeDescription};
|
||||
use nym_validator_client::nyxd::error::NyxdError;
|
||||
use nym_validator_client::nyxd::{Paging, TendermintRpcClient, ValidatorResponse};
|
||||
use nym_validator_client::{QueryHttpRpcValidatorClient, ValidatorClientError};
|
||||
use std::future::Future;
|
||||
|
||||
use crate::mix_nodes::CACHE_REFRESH_RATE;
|
||||
use crate::state::ExplorerApiStateContext;
|
||||
use tokio::time::MissedTickBehavior;
|
||||
|
||||
pub(crate) struct ExplorerApiTasks {
|
||||
state: ExplorerApiStateContext,
|
||||
@@ -39,6 +39,28 @@ impl ExplorerApiTasks {
|
||||
bonds
|
||||
}
|
||||
|
||||
async fn retrieve_bonded_nymnodes(&self) -> Result<Vec<NymNodeDetails>, ValidatorClientError> {
|
||||
info!("About to retrieve all nymnode bonds...");
|
||||
self.state
|
||||
.inner
|
||||
.validator_client
|
||||
.0
|
||||
.get_all_cached_bonded_nym_nodes()
|
||||
.await
|
||||
}
|
||||
|
||||
async fn retrieve_node_descriptions(
|
||||
&self,
|
||||
) -> Result<Vec<NymNodeDescription>, ValidatorClientError> {
|
||||
info!("About to retrieve node descriptions...");
|
||||
self.state
|
||||
.inner
|
||||
.validator_client
|
||||
.0
|
||||
.get_all_cached_described_nodes()
|
||||
.await
|
||||
}
|
||||
|
||||
async fn retrieve_all_mixnodes(&self) -> Vec<MixNodeBondAnnotated> {
|
||||
info!("About to retrieve all mixnode bonds...");
|
||||
self.retrieve_mixnodes(
|
||||
@@ -130,10 +152,33 @@ impl ExplorerApiTasks {
|
||||
}
|
||||
}
|
||||
|
||||
async fn update_nymnodes_cache(&self) {
|
||||
let nym_node_bonds = self.retrieve_bonded_nymnodes().await.unwrap_or_else(|err| {
|
||||
error!("failed to retrieve nym node bonds: {err}");
|
||||
Vec::new()
|
||||
});
|
||||
|
||||
let all_descriptions = self
|
||||
.retrieve_node_descriptions()
|
||||
.await
|
||||
.unwrap_or_else(|err| {
|
||||
error!("failed to retrieve node descriptions: {err}");
|
||||
Vec::new()
|
||||
});
|
||||
|
||||
self.state
|
||||
.inner
|
||||
.nymnodes
|
||||
.update_cache(nym_node_bonds, all_descriptions)
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) fn start(mut self) {
|
||||
info!("Spawning mix nodes task runner...");
|
||||
tokio::spawn(async move {
|
||||
let mut interval_timer = tokio::time::interval(CACHE_REFRESH_RATE);
|
||||
interval_timer.set_missed_tick_behavior(MissedTickBehavior::Skip);
|
||||
|
||||
while !self.shutdown.is_shutdown() {
|
||||
tokio::select! {
|
||||
_ = interval_timer.tick() => {
|
||||
@@ -147,6 +192,10 @@ impl ExplorerApiTasks {
|
||||
|
||||
info!("Updating mix node cache...");
|
||||
self.update_mixnode_cache().await;
|
||||
|
||||
info!("Updating nymnode cache...");
|
||||
self.update_nymnodes_cache().await;
|
||||
info!("Done");
|
||||
}
|
||||
_ = self.shutdown.recv() => {
|
||||
trace!("Listener: Received shutdown");
|
||||
|
||||
@@ -76,9 +76,11 @@ nym-network-defaults = { path = "../common/network-defaults" }
|
||||
nym-network-requester = { path = "../service-providers/network-requester" }
|
||||
nym-node-http-api = { path = "../nym-node/nym-node-http-api" }
|
||||
nym-pemstore = { path = "../common/pemstore" }
|
||||
nym-sdk = { path = "../sdk/rust/nym-sdk" }
|
||||
nym-sphinx = { path = "../common/nymsphinx" }
|
||||
nym-statistics-common = { path = "../common/statistics" }
|
||||
nym-task = { path = "../common/task" }
|
||||
nym-topology = { path = "../common/topology" }
|
||||
nym-types = { path = "../common/types" }
|
||||
nym-validator-client = { path = "../common/client-libs/validator-client" }
|
||||
nym-ip-packet-router = { path = "../service-providers/ip-packet-router" }
|
||||
|
||||
@@ -73,6 +73,9 @@ pub(crate) enum InitialAuthenticationError {
|
||||
#[error("Only 'Register' or 'Authenticate' requests are allowed")]
|
||||
InvalidRequest,
|
||||
|
||||
#[error("received a Message of type {typ} which was not expected in this context")]
|
||||
UnexpectedMessageType { typ: String },
|
||||
|
||||
#[error("Experienced connection error: {0}")]
|
||||
ConnectionError(#[from] WsError),
|
||||
|
||||
@@ -861,9 +864,27 @@ where
|
||||
Message::Binary(_) => {
|
||||
return Err(InitialAuthenticationError::BinaryRequestWithoutAuthentication);
|
||||
}
|
||||
_ => unreachable!(
|
||||
"the underlying tunsgenite stream should be handling other message types"
|
||||
),
|
||||
other => {
|
||||
if other.is_ping() {
|
||||
debug!("unexpected ping message!");
|
||||
return Err(InitialAuthenticationError::UnexpectedMessageType {
|
||||
typ: "ping".to_string(),
|
||||
});
|
||||
} else if other.is_pong() {
|
||||
debug!("unexpected pong message!");
|
||||
return Err(InitialAuthenticationError::UnexpectedMessageType {
|
||||
typ: "pong".to_string(),
|
||||
});
|
||||
} else if other.is_close() {
|
||||
debug!("unexpected close message!");
|
||||
return Err(InitialAuthenticationError::UnexpectedMessageType {
|
||||
typ: "close".to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
// at this point this is definitely unreachable, but just in case, let's not panic...
|
||||
return Err(InitialAuthenticationError::InvalidRequest);
|
||||
}
|
||||
};
|
||||
|
||||
text.parse()
|
||||
|
||||
@@ -3,13 +3,18 @@
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::error::GatewayError;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use nym_crypto::asymmetric::encryption;
|
||||
use nym_gateway_storage::PersistentStorage;
|
||||
use nym_pemstore::traits::PemStorableKeyPair;
|
||||
use nym_pemstore::KeyPairPath;
|
||||
|
||||
use nym_sdk::{NymApiTopologyProvider, NymApiTopologyProviderConfig, UserAgent};
|
||||
use nym_topology::{gateway, NymTopology, TopologyProvider};
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::Mutex;
|
||||
use tracing::debug;
|
||||
use url::Url;
|
||||
|
||||
pub async fn load_network_requester_config<P: AsRef<Path>>(
|
||||
id: &str,
|
||||
@@ -93,3 +98,56 @@ pub(crate) fn load_sphinx_keys(config: &Config) -> Result<encryption::KeyPair, G
|
||||
);
|
||||
load_keypair(sphinx_paths, "gateway sphinx")
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct GatewayTopologyProvider {
|
||||
inner: Arc<Mutex<GatewayTopologyProviderInner>>,
|
||||
}
|
||||
|
||||
impl GatewayTopologyProvider {
|
||||
pub fn new(
|
||||
gateway_node: gateway::LegacyNode,
|
||||
user_agent: UserAgent,
|
||||
nym_api_url: Vec<Url>,
|
||||
) -> GatewayTopologyProvider {
|
||||
GatewayTopologyProvider {
|
||||
inner: Arc::new(Mutex::new(GatewayTopologyProviderInner {
|
||||
inner: NymApiTopologyProvider::new(
|
||||
NymApiTopologyProviderConfig {
|
||||
min_mixnode_performance: 50,
|
||||
min_gateway_performance: 0,
|
||||
},
|
||||
nym_api_url,
|
||||
env!("CARGO_PKG_VERSION").to_string(),
|
||||
Some(user_agent),
|
||||
),
|
||||
gateway_node,
|
||||
})),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct GatewayTopologyProviderInner {
|
||||
inner: NymApiTopologyProvider,
|
||||
gateway_node: gateway::LegacyNode,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl TopologyProvider for GatewayTopologyProvider {
|
||||
async fn get_new_topology(&mut self) -> Option<NymTopology> {
|
||||
let mut guard = self.inner.lock().await;
|
||||
match guard.inner.get_new_topology().await {
|
||||
None => None,
|
||||
Some(mut base) => {
|
||||
if !base.gateway_exists(&guard.gateway_node.identity_key) {
|
||||
debug!(
|
||||
"{} didn't exist in topology. inserting it.",
|
||||
guard.gateway_node.identity_key
|
||||
);
|
||||
base.insert_gateway(guard.gateway_node.clone());
|
||||
}
|
||||
Some(base)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+52
-2
@@ -12,9 +12,12 @@ use crate::http::HttpApiBuilder;
|
||||
use crate::node::client_handling::active_clients::ActiveClientsStore;
|
||||
use crate::node::client_handling::embedded_clients::{LocalEmbeddedClientHandle, MessageRouter};
|
||||
use crate::node::client_handling::websocket;
|
||||
use crate::node::helpers::{initialise_main_storage, load_network_requester_config};
|
||||
use crate::node::helpers::{
|
||||
initialise_main_storage, load_network_requester_config, GatewayTopologyProvider,
|
||||
};
|
||||
use crate::node::mixnet_handling::receiver::connection_handler::ConnectionHandler;
|
||||
use futures::channel::{mpsc, oneshot};
|
||||
use nym_bin_common::bin_info;
|
||||
use nym_credential_verification::ecash::{
|
||||
credential_sender::CredentialHandlerConfig, EcashManager,
|
||||
};
|
||||
@@ -25,13 +28,15 @@ use nym_network_requester::{LocalGateway, NRServiceProviderBuilder, RequestFilte
|
||||
use nym_node_http_api::state::metrics::SharedSessionStats;
|
||||
use nym_statistics_common::events::{self, StatsEventSender};
|
||||
use nym_task::{TaskClient, TaskHandle, TaskManager};
|
||||
use nym_topology::NetworkAddress;
|
||||
use nym_types::gateway::GatewayNodeDetailsResponse;
|
||||
use nym_validator_client::client::NodeId;
|
||||
use nym_validator_client::nyxd::{Coin, CosmWasmClient};
|
||||
use nym_validator_client::{nyxd, DirectSigningHttpRpcNyxdClient};
|
||||
use rand::seq::SliceRandom;
|
||||
use rand::thread_rng;
|
||||
use statistics::GatewayStatisticsCollector;
|
||||
use std::net::SocketAddr;
|
||||
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use tracing::*;
|
||||
@@ -225,6 +230,39 @@ impl<St> Gateway<St> {
|
||||
crate::helpers::node_details(&self.config).await
|
||||
}
|
||||
|
||||
fn gateway_topology_provider(&self) -> GatewayTopologyProvider {
|
||||
GatewayTopologyProvider::new(
|
||||
self.as_topology_node(),
|
||||
bin_info!().into(),
|
||||
self.config.gateway.nym_api_urls.clone(),
|
||||
)
|
||||
}
|
||||
|
||||
fn as_topology_node(&self) -> nym_topology::gateway::LegacyNode {
|
||||
let ip = self
|
||||
.config
|
||||
.host
|
||||
.public_ips
|
||||
.first()
|
||||
.copied()
|
||||
.unwrap_or(IpAddr::V4(Ipv4Addr::LOCALHOST));
|
||||
let mix_host = SocketAddr::new(ip, self.config.gateway.mix_port);
|
||||
|
||||
nym_topology::gateway::LegacyNode {
|
||||
// those fields are irrelevant for the purposes of routing so it's fine if they're inaccurate.
|
||||
// the only thing that matters is the identity key (and maybe version)
|
||||
node_id: NodeId::MAX,
|
||||
mix_host,
|
||||
host: NetworkAddress::IpAddr(ip),
|
||||
clients_ws_port: self.config.gateway.clients_port,
|
||||
clients_wss_port: self.config.gateway.clients_wss_port,
|
||||
sphinx_key: *self.sphinx_keypair.public_key(),
|
||||
|
||||
identity_key: *self.identity_keypair.public_key(),
|
||||
version: env!("CARGO_PKG_VERSION").into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn start_mix_socket_listener(
|
||||
&self,
|
||||
ack_sender: MixForwardingSender,
|
||||
@@ -257,6 +295,7 @@ impl<St> Gateway<St> {
|
||||
async fn start_authenticator(
|
||||
&mut self,
|
||||
forwarding_channel: MixForwardingSender,
|
||||
topology_provider: GatewayTopologyProvider,
|
||||
shutdown: TaskClient,
|
||||
ecash_verifier: Arc<EcashManager<St>>,
|
||||
) -> Result<StartedAuthenticator, Box<dyn std::error::Error + Send + Sync>>
|
||||
@@ -304,6 +343,7 @@ impl<St> Gateway<St> {
|
||||
.with_shutdown(shutdown.fork("authenticator"))
|
||||
.with_wait_for_gateway(true)
|
||||
.with_minimum_gateway_performance(0)
|
||||
.with_custom_topology_provider(Box::new(topology_provider))
|
||||
.with_on_start(on_start_tx);
|
||||
|
||||
if let Some(custom_mixnet) = &opts.custom_mixnet_path {
|
||||
@@ -352,6 +392,7 @@ impl<St> Gateway<St> {
|
||||
async fn start_authenticator(
|
||||
&self,
|
||||
_forwarding_channel: MixForwardingSender,
|
||||
_topology_provider: GatewayTopologyProvider,
|
||||
_shutdown: TaskClient,
|
||||
_ecash_verifier: Arc<EcashManager<St>>,
|
||||
) -> Result<StartedAuthenticator, Box<dyn std::error::Error + Send + Sync>> {
|
||||
@@ -424,6 +465,7 @@ impl<St> Gateway<St> {
|
||||
async fn start_network_requester(
|
||||
&self,
|
||||
forwarding_channel: MixForwardingSender,
|
||||
topology_provider: GatewayTopologyProvider,
|
||||
shutdown: TaskClient,
|
||||
) -> Result<StartedNetworkRequester, GatewayError> {
|
||||
info!("Starting network requester...");
|
||||
@@ -451,6 +493,7 @@ impl<St> Gateway<St> {
|
||||
.with_custom_gateway_transceiver(Box::new(transceiver))
|
||||
.with_wait_for_gateway(true)
|
||||
.with_minimum_gateway_performance(0)
|
||||
.with_custom_topology_provider(Box::new(topology_provider))
|
||||
.with_on_start(on_start_tx);
|
||||
|
||||
if let Some(custom_mixnet) = &nr_opts.custom_mixnet_path {
|
||||
@@ -488,6 +531,7 @@ impl<St> Gateway<St> {
|
||||
async fn start_ip_packet_router(
|
||||
&self,
|
||||
forwarding_channel: MixForwardingSender,
|
||||
topology_provider: GatewayTopologyProvider,
|
||||
shutdown: TaskClient,
|
||||
) -> Result<LocalEmbeddedClientHandle, GatewayError> {
|
||||
info!("Starting IP packet provider...");
|
||||
@@ -516,6 +560,7 @@ impl<St> Gateway<St> {
|
||||
.with_custom_gateway_transceiver(Box::new(transceiver))
|
||||
.with_wait_for_gateway(true)
|
||||
.with_minimum_gateway_performance(0)
|
||||
.with_custom_topology_provider(Box::new(topology_provider))
|
||||
.with_on_start(on_start_tx);
|
||||
|
||||
if let Some(custom_mixnet) = &ip_opts.custom_mixnet_path {
|
||||
@@ -632,6 +677,8 @@ impl<St> Gateway<St> {
|
||||
shutdown.fork("statistics::GatewayStatisticsCollector"),
|
||||
);
|
||||
|
||||
let topology_provider = self.gateway_topology_provider();
|
||||
|
||||
let handler_config = CredentialHandlerConfig {
|
||||
revocation_bandwidth_penalty: self
|
||||
.config
|
||||
@@ -680,6 +727,7 @@ impl<St> Gateway<St> {
|
||||
let embedded_nr = self
|
||||
.start_network_requester(
|
||||
mix_forwarding_channel.clone(),
|
||||
topology_provider.clone(),
|
||||
shutdown.fork("NetworkRequester"),
|
||||
)
|
||||
.await?;
|
||||
@@ -695,6 +743,7 @@ impl<St> Gateway<St> {
|
||||
let embedded_ip_sp = self
|
||||
.start_ip_packet_router(
|
||||
mix_forwarding_channel.clone(),
|
||||
topology_provider.clone(),
|
||||
shutdown.fork("ip_service_provider"),
|
||||
)
|
||||
.await?;
|
||||
@@ -707,6 +756,7 @@ impl<St> Gateway<St> {
|
||||
let embedded_auth = self
|
||||
.start_authenticator(
|
||||
mix_forwarding_channel,
|
||||
topology_provider,
|
||||
shutdown.fork("authenticator"),
|
||||
ecash_verifier,
|
||||
)
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
FROM rust:latest AS builder
|
||||
|
||||
COPY ./ /usr/src/nym
|
||||
WORKDIR /usr/src/nym/nym-api
|
||||
RUN cargo build --release
|
||||
|
||||
ENTRYPOINT ["/usr/src/nym/nym-api/entrypoint.sh"]
|
||||
+2
-2
@@ -4,7 +4,7 @@
|
||||
[package]
|
||||
name = "nym-api"
|
||||
license = "GPL-3.0"
|
||||
version = "1.1.44"
|
||||
version = "1.1.45"
|
||||
authors.workspace = true
|
||||
edition = "2021"
|
||||
rust-version.workspace = true
|
||||
@@ -18,7 +18,7 @@ bip39 = { workspace = true }
|
||||
bincode.workspace = true
|
||||
bloomfilter = { workspace = true }
|
||||
cfg-if = { workspace = true }
|
||||
clap = { workspace = true, features = ["cargo", "derive"] }
|
||||
clap = { workspace = true, features = ["cargo", "derive", "env"] }
|
||||
console-subscriber = { workspace = true, optional = true } # validator-api needs to be built with RUSTFLAGS="--cfg tokio_unstable"
|
||||
dirs = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
|
||||
Executable
+5
@@ -0,0 +1,5 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
/usr/src/nym/target/release/nym-api init && /usr/src/nym/target/release/nym-api run
|
||||
@@ -2,10 +2,8 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use cosmwasm_std::Decimal;
|
||||
use nym_mixnet_contract_common::mixnode::PendingMixNodeChanges;
|
||||
use nym_mixnet_contract_common::{
|
||||
GatewayBond, LegacyMixLayer, MixNodeBond, MixNodeDetails, NodeId, NodeRewarding,
|
||||
};
|
||||
use nym_mixnet_contract_common::mixnode::LegacyPendingMixNodeChanges;
|
||||
use nym_mixnet_contract_common::{GatewayBond, LegacyMixLayer, MixNodeBond, NodeId, NodeRewarding};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::ops::Deref;
|
||||
@@ -64,7 +62,7 @@ pub struct LegacyMixNodeDetailsWithLayer {
|
||||
|
||||
/// Adjustments to the mixnode that are ought to happen during future epoch transitions.
|
||||
#[serde(default)]
|
||||
pub pending_changes: PendingMixNodeChanges,
|
||||
pub pending_changes: LegacyPendingMixNodeChanges,
|
||||
}
|
||||
|
||||
impl LegacyMixNodeDetailsWithLayer {
|
||||
@@ -80,13 +78,3 @@ impl LegacyMixNodeDetailsWithLayer {
|
||||
self.bond_information.is_unbonding
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LegacyMixNodeDetailsWithLayer> for MixNodeDetails {
|
||||
fn from(value: LegacyMixNodeDetailsWithLayer) -> Self {
|
||||
MixNodeDetails {
|
||||
bond_information: value.bond_information.into(),
|
||||
rewarding_details: value.rewarding_details,
|
||||
pending_changes: value.pending_changes,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ use nym_crypto::asymmetric::x25519::{
|
||||
use nym_mixnet_contract_common::nym_node::Role;
|
||||
use nym_mixnet_contract_common::reward_params::{Performance, RewardingParams};
|
||||
use nym_mixnet_contract_common::rewarding::RewardEstimate;
|
||||
use nym_mixnet_contract_common::{IdentityKey, Interval, MixNode, NodeId, Percent};
|
||||
use nym_mixnet_contract_common::{GatewayBond, IdentityKey, Interval, MixNode, NodeId, Percent};
|
||||
use nym_network_defaults::{DEFAULT_MIX_LISTENING_PORT, DEFAULT_VERLOC_LISTENING_PORT};
|
||||
use nym_node_requests::api::v1::authenticator::models::Authenticator;
|
||||
use nym_node_requests::api::v1::gateway::models::Wireguard;
|
||||
@@ -138,6 +138,48 @@ pub struct NodePerformance {
|
||||
pub last_24h: Performance,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, Hash, JsonSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
feature = "generate-ts",
|
||||
ts(export, export_to = "ts-packages/types/src/types/rust/DisplayRole.ts")
|
||||
)]
|
||||
pub enum DisplayRole {
|
||||
EntryGateway,
|
||||
Layer1,
|
||||
Layer2,
|
||||
Layer3,
|
||||
ExitGateway,
|
||||
Standby,
|
||||
}
|
||||
|
||||
impl From<Role> for DisplayRole {
|
||||
fn from(role: Role) -> Self {
|
||||
match role {
|
||||
Role::EntryGateway => DisplayRole::EntryGateway,
|
||||
Role::Layer1 => DisplayRole::Layer1,
|
||||
Role::Layer2 => DisplayRole::Layer2,
|
||||
Role::Layer3 => DisplayRole::Layer3,
|
||||
Role::ExitGateway => DisplayRole::ExitGateway,
|
||||
Role::Standby => DisplayRole::Standby,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DisplayRole> for Role {
|
||||
fn from(role: DisplayRole) -> Self {
|
||||
match role {
|
||||
DisplayRole::EntryGateway => Role::EntryGateway,
|
||||
DisplayRole::Layer1 => Role::Layer1,
|
||||
DisplayRole::Layer2 => Role::Layer2,
|
||||
DisplayRole::Layer3 => Role::Layer3,
|
||||
DisplayRole::ExitGateway => Role::ExitGateway,
|
||||
DisplayRole::Standby => Role::Standby,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// imo for now there's no point in exposing more than that,
|
||||
// nym-api shouldn't be calculating apy or stake saturation for you.
|
||||
// it should just return its own metrics (performance) and then you can do with it as you wish
|
||||
@@ -153,7 +195,7 @@ pub struct NodePerformance {
|
||||
pub struct NodeAnnotation {
|
||||
#[cfg_attr(feature = "generate-ts", ts(type = "string"))]
|
||||
pub last_24h_performance: Performance,
|
||||
pub current_role: Option<Role>,
|
||||
pub current_role: Option<DisplayRole>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, ToSchema)]
|
||||
@@ -286,7 +328,7 @@ impl MixNodeBondAnnotated {
|
||||
.sphinx_key
|
||||
.parse()
|
||||
.map_err(|_| MalformedNodeBond::InvalidX25519Key)?,
|
||||
epoch_role: role,
|
||||
role,
|
||||
supported_roles: DeclaredRoles {
|
||||
mixnode: true,
|
||||
entry: false,
|
||||
@@ -345,7 +387,7 @@ impl GatewayBondAnnotated {
|
||||
.sphinx_key
|
||||
.parse()
|
||||
.map_err(|_| MalformedNodeBond::InvalidX25519Key)?,
|
||||
epoch_role: role,
|
||||
role,
|
||||
supported_roles: DeclaredRoles {
|
||||
mixnode: false,
|
||||
entry: true,
|
||||
@@ -810,6 +852,10 @@ impl NymNodeDescription {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ed25519_identity_key(&self) -> ed25519::PublicKey {
|
||||
self.description.host_information.keys.ed25519
|
||||
}
|
||||
|
||||
pub fn to_skimmed_node(&self, role: NodeRole, performance: Performance) -> SkimmedNode {
|
||||
let keys = &self.description.host_information.keys;
|
||||
let entry = if self.description.declared_role.entry {
|
||||
@@ -827,7 +873,7 @@ impl NymNodeDescription {
|
||||
// we can't use the declared roles, we have to take whatever was provided in the contract.
|
||||
// why? say this node COULD operate as an exit, but it might be the case the contract decided
|
||||
// to assign it an ENTRY role only. we have to use that one instead.
|
||||
epoch_role: role,
|
||||
role,
|
||||
supported_roles: self.description.declared_role,
|
||||
entry,
|
||||
performance,
|
||||
@@ -935,14 +981,14 @@ impl NymNodeData {
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)]
|
||||
pub struct LegacyDescribedGateway {
|
||||
pub bond: LegacyGatewayBondWithId,
|
||||
pub bond: GatewayBond,
|
||||
pub self_described: Option<NymNodeData>,
|
||||
}
|
||||
|
||||
impl From<LegacyGatewayBondWithId> for LegacyDescribedGateway {
|
||||
fn from(bond: LegacyGatewayBondWithId) -> Self {
|
||||
LegacyDescribedGateway {
|
||||
bond,
|
||||
bond: bond.bond,
|
||||
self_described: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,8 +141,8 @@ pub struct SkimmedNode {
|
||||
#[schemars(with = "String")]
|
||||
pub x25519_sphinx_pubkey: x25519::PublicKey,
|
||||
|
||||
#[serde(alias = "role")]
|
||||
pub epoch_role: NodeRole,
|
||||
#[serde(alias = "epoch_role")]
|
||||
pub role: NodeRole,
|
||||
|
||||
// needed for the purposes of sending appropriate test packets
|
||||
#[serde(default)]
|
||||
@@ -157,7 +157,7 @@ pub struct SkimmedNode {
|
||||
|
||||
impl SkimmedNode {
|
||||
pub fn get_mix_layer(&self) -> Option<u8> {
|
||||
match self.epoch_role {
|
||||
match self.role {
|
||||
NodeRole::Mixnode { layer } => Some(layer),
|
||||
_ => None,
|
||||
}
|
||||
|
||||
@@ -24,21 +24,21 @@ use utoipa::IntoParams;
|
||||
pub(crate) fn aggregation_routes(ecash_state: Arc<EcashState>) -> Router<AppState> {
|
||||
Router::new()
|
||||
.route(
|
||||
"/master-verification-key:epoch_id",
|
||||
"/master-verification-key",
|
||||
axum::routing::get({
|
||||
let ecash_state = Arc::clone(&ecash_state);
|
||||
|epoch_id| master_verification_key(epoch_id, ecash_state)
|
||||
}),
|
||||
)
|
||||
.route(
|
||||
"/aggregated-expiration-date-signatures:expiration_date",
|
||||
"/aggregated-expiration-date-signatures",
|
||||
axum::routing::get({
|
||||
let ecash_state = Arc::clone(&ecash_state);
|
||||
|expiration_date| expiration_date_signatures(expiration_date, ecash_state)
|
||||
}),
|
||||
)
|
||||
.route(
|
||||
"/aggregated-coin-indices-signatures:epoch_id",
|
||||
"/aggregated-coin-indices-signatures",
|
||||
axum::routing::get({
|
||||
let ecash_state = Arc::clone(&ecash_state);
|
||||
|epoch_id| coin_indices_signatures(epoch_id, ecash_state)
|
||||
@@ -52,7 +52,7 @@ pub(crate) fn aggregation_routes(ecash_state: Arc<EcashState>) -> Router<AppStat
|
||||
params(
|
||||
EpochIdParam
|
||||
),
|
||||
path = "/v1/ecash/master-verification-key/{epoch_id}",
|
||||
path = "/v1/ecash/master-verification-key",
|
||||
responses(
|
||||
(status = 200, body = VerificationKeyResponse)
|
||||
)
|
||||
@@ -83,7 +83,7 @@ struct ExpirationDateParam {
|
||||
params(
|
||||
ExpirationDateParam
|
||||
),
|
||||
path = "/v1/ecash/aggregated-expiration-date-signatures/{epoch_id}",
|
||||
path = "/v1/ecash/aggregated-expiration-date-signatures",
|
||||
responses(
|
||||
(status = 200, body = AggregatedExpirationDateSignatureResponse)
|
||||
)
|
||||
@@ -120,7 +120,7 @@ async fn expiration_date_signatures(
|
||||
params(
|
||||
EpochIdParam
|
||||
),
|
||||
path = "/v1/ecash/aggregated-coin-indices-signatures/{epoch_id}",
|
||||
path = "/v1/ecash/aggregated-coin-indices-signatures",
|
||||
responses(
|
||||
(status = 200, body = AggregatedCoinIndicesSignatureResponse)
|
||||
)
|
||||
|
||||
@@ -7,7 +7,7 @@ use crate::ecash::helpers::blind_sign;
|
||||
use crate::ecash::state::EcashState;
|
||||
use crate::node_status_api::models::AxumResult;
|
||||
use crate::support::http::state::AppState;
|
||||
use axum::extract::Path;
|
||||
use axum::extract::Query;
|
||||
use axum::{Json, Router};
|
||||
use nym_api_requests::ecash::{
|
||||
BlindSignRequestBody, BlindedSignatureResponse, PartialCoinIndicesSignatureResponse,
|
||||
@@ -32,14 +32,14 @@ pub(crate) fn partial_signing_routes(ecash_state: Arc<EcashState>) -> Router<App
|
||||
}),
|
||||
)
|
||||
.route(
|
||||
"/partial-expiration-date-signatures:expiration_date",
|
||||
"/partial-expiration-date-signatures",
|
||||
axum::routing::get({
|
||||
let ecash_state = Arc::clone(&ecash_state);
|
||||
|expiration_date| partial_expiration_date_signatures(expiration_date, ecash_state)
|
||||
}),
|
||||
)
|
||||
.route(
|
||||
"/partial-coin-indices-signatures:epoch_id",
|
||||
"/partial-coin-indices-signatures",
|
||||
axum::routing::get({
|
||||
let ecash_state = Arc::clone(&ecash_state);
|
||||
|epoch_id| partial_coin_indices_signatures(epoch_id, ecash_state)
|
||||
@@ -127,14 +127,14 @@ struct ExpirationDateParam {
|
||||
params(
|
||||
ExpirationDateParam
|
||||
),
|
||||
path = "/v1/ecash/partial-expiration-date-signatures/{expiration_date}",
|
||||
path = "/v1/ecash/partial-expiration-date-signatures",
|
||||
responses(
|
||||
(status = 200, body = PartialExpirationDateSignatureResponse),
|
||||
(status = 400, body = ErrorResponse, description = "this nym-api is not an ecash signer in the current epoch"),
|
||||
)
|
||||
)]
|
||||
async fn partial_expiration_date_signatures(
|
||||
Path(ExpirationDateParam { expiration_date }): Path<ExpirationDateParam>,
|
||||
Query(ExpirationDateParam { expiration_date }): Query<ExpirationDateParam>,
|
||||
state: Arc<EcashState>,
|
||||
) -> AxumResult<Json<PartialExpirationDateSignatureResponse>> {
|
||||
state.ensure_signer().await?;
|
||||
@@ -165,14 +165,14 @@ async fn partial_expiration_date_signatures(
|
||||
params(
|
||||
EpochIdParam
|
||||
),
|
||||
path = "/v1/ecash/partial-coin-indices-signatures/{epoch_id}",
|
||||
path = "/v1/ecash/partial-coin-indices-signatures",
|
||||
responses(
|
||||
(status = 200, body = PartialExpirationDateSignatureResponse),
|
||||
(status = 400, body = ErrorResponse, description = "this nym-api is not an ecash signer in the current epoch"),
|
||||
)
|
||||
)]
|
||||
async fn partial_coin_indices_signatures(
|
||||
Path(EpochIdParam { epoch_id }): Path<EpochIdParam>,
|
||||
Query(EpochIdParam { epoch_id }): Query<EpochIdParam>,
|
||||
state: Arc<EcashState>,
|
||||
) -> AxumResult<Json<PartialCoinIndicesSignatureResponse>> {
|
||||
state.ensure_signer().await?;
|
||||
|
||||
@@ -20,6 +20,7 @@ use time::macros::time;
|
||||
use time::{OffsetDateTime, Time};
|
||||
use tracing::{error, warn};
|
||||
|
||||
#[allow(deprecated)]
|
||||
pub(crate) fn spending_routes(ecash_state: Arc<EcashState>) -> Router<AppState> {
|
||||
Router::new()
|
||||
.route(
|
||||
@@ -242,6 +243,7 @@ async fn batch_redeem_tickets(
|
||||
(status = 500, body = ErrorResponse, description = "bloomfilters got disabled"),
|
||||
)
|
||||
)]
|
||||
#[deprecated]
|
||||
async fn double_spending_filter_v1(
|
||||
_state: Arc<EcashState>,
|
||||
) -> AxumResult<Json<SpentCredentialsResponse>> {
|
||||
|
||||
@@ -24,6 +24,9 @@ pub type Result<T, E = EcashError> = std::result::Result<T, E>;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum EcashError {
|
||||
#[error("permanently restricted")]
|
||||
Restricted,
|
||||
|
||||
#[error(transparent)]
|
||||
IOError(#[from] std::io::Error),
|
||||
|
||||
|
||||
@@ -169,7 +169,7 @@ impl EpochAdvancer {
|
||||
let standby_eligible = all_choices
|
||||
.iter()
|
||||
.filter(|node| {
|
||||
exit_gateways.contains(&node.0.node_id)
|
||||
!exit_gateways.contains(&node.0.node_id)
|
||||
&& !entry_gateways.contains(&node.0.node_id)
|
||||
&& !mixnodes.contains(&node.0.node_id)
|
||||
})
|
||||
@@ -228,14 +228,24 @@ impl EpochAdvancer {
|
||||
)
|
||||
}
|
||||
|
||||
Ok(RewardedSet {
|
||||
let mut rewarded_set = RewardedSet {
|
||||
entry_gateways: entry_gateways.into_iter().collect(),
|
||||
exit_gateways: exit_gateways.into_iter().collect(),
|
||||
layer1,
|
||||
layer2,
|
||||
layer3,
|
||||
standby,
|
||||
})
|
||||
};
|
||||
|
||||
// make sure to sort the rewarded set values
|
||||
rewarded_set.entry_gateways.sort();
|
||||
rewarded_set.exit_gateways.sort();
|
||||
rewarded_set.layer1.sort();
|
||||
rewarded_set.layer2.sort();
|
||||
rewarded_set.layer3.sort();
|
||||
rewarded_set.standby.sort();
|
||||
|
||||
Ok(rewarded_set)
|
||||
}
|
||||
|
||||
async fn attach_performance_to_eligible_nodes(
|
||||
|
||||
@@ -274,8 +274,8 @@ impl<R: MessageReceiver + Send> Monitor<R> {
|
||||
info!("Received {}/{} packets", total_received, total_sent);
|
||||
|
||||
let summary = self.summary_producer.produce_summary(
|
||||
prepared_packets.tested_mixnodes,
|
||||
prepared_packets.tested_gateways,
|
||||
prepared_packets.mixnodes_under_test,
|
||||
prepared_packets.gateways_under_test,
|
||||
received,
|
||||
prepared_packets.invalid_mixnodes,
|
||||
prepared_packets.invalid_gateways,
|
||||
|
||||
@@ -25,7 +25,7 @@ use std::collections::{HashMap, HashSet};
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tracing::{error, info, trace};
|
||||
use tracing::{debug, error, info, trace};
|
||||
|
||||
const DEFAULT_AVERAGE_PACKET_DELAY: Duration = Duration::from_millis(200);
|
||||
const DEFAULT_AVERAGE_ACK_DELAY: Duration = Duration::from_millis(200);
|
||||
@@ -59,10 +59,10 @@ pub(crate) struct PreparedPackets {
|
||||
pub(super) packets: Vec<GatewayPackets>,
|
||||
|
||||
/// Vector containing list of public keys and owners of all nodes mixnodes being tested.
|
||||
pub(super) tested_mixnodes: Vec<TestableNode>,
|
||||
pub(super) mixnodes_under_test: Vec<TestableNode>,
|
||||
|
||||
/// Vector containing list of public keys and owners of all gateways being tested.
|
||||
pub(super) tested_gateways: Vec<TestableNode>,
|
||||
pub(super) gateways_under_test: Vec<TestableNode>,
|
||||
|
||||
/// All mixnodes that failed to get parsed correctly or were not version compatible.
|
||||
/// They will be marked to the validator as being down for the test.
|
||||
@@ -151,34 +151,38 @@ impl PacketPreparer {
|
||||
self.contract_cache.wait_for_initial_values().await;
|
||||
self.described_cache.naive_wait_for_initial_values().await;
|
||||
|
||||
let described_nodes = self
|
||||
.described_cache
|
||||
.get()
|
||||
.await
|
||||
.expect("the self-describe cache should have been initialised!");
|
||||
|
||||
// now wait for at least `minimum_full_routes` mixnodes per layer and `minimum_full_routes` gateway to be online
|
||||
info!("Waiting for minimal topology to be online");
|
||||
let initialisation_backoff = Duration::from_secs(30);
|
||||
loop {
|
||||
let gateways = self.contract_cache.legacy_gateways_all().await;
|
||||
let mixnodes = self.contract_cache.legacy_mixnodes_all_basic().await;
|
||||
let nym_nodes = self.contract_cache.nym_nodes().await;
|
||||
|
||||
if gateways.len() < minimum_full_routes {
|
||||
self.topology_wait_backoff(initialisation_backoff).await;
|
||||
continue;
|
||||
}
|
||||
let mut gateways_count = gateways.len();
|
||||
let mut mixnodes_count = mixnodes.len();
|
||||
|
||||
let mut layer1_count = 0;
|
||||
let mut layer2_count = 0;
|
||||
let mut layer3_count = 0;
|
||||
|
||||
for mix in mixnodes {
|
||||
match mix.layer {
|
||||
LegacyMixLayer::One => layer1_count += 1,
|
||||
LegacyMixLayer::Two => layer2_count += 1,
|
||||
LegacyMixLayer::Three => layer3_count += 1,
|
||||
for nym_node in nym_nodes {
|
||||
if let Some(described) = described_nodes.get_description(&nym_node.node_id()) {
|
||||
if described.declared_role.mixnode {
|
||||
mixnodes_count += 1;
|
||||
} else if described.declared_role.entry {
|
||||
gateways_count += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if layer1_count >= minimum_full_routes
|
||||
&& layer2_count >= minimum_full_routes
|
||||
&& layer3_count >= minimum_full_routes
|
||||
{
|
||||
debug!(
|
||||
"we have {mixnodes_count} possible mixnodes and {gateways_count} possible gateways"
|
||||
);
|
||||
|
||||
if gateways_count >= minimum_full_routes && mixnodes_count * 3 >= minimum_full_routes {
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -529,30 +533,41 @@ impl PacketPreparer {
|
||||
let mixing_nym_nodes = descriptions.mixing_nym_nodes();
|
||||
let gateway_capable_nym_nodes = descriptions.entry_capable_nym_nodes();
|
||||
|
||||
let (mixnodes, invalid_mixnodes) = self.filter_outdated_and_malformed_mixnodes(mixnodes);
|
||||
let (gateways, invalid_gateways) = self.filter_outdated_and_malformed_gateways(gateways);
|
||||
let (mut mixnodes_to_test_details, invalid_mixnodes) =
|
||||
self.filter_outdated_and_malformed_mixnodes(mixnodes);
|
||||
let (mut gateways_to_test_details, invalid_gateways) =
|
||||
self.filter_outdated_and_malformed_gateways(gateways);
|
||||
|
||||
let mut tested_mixnodes = mixnodes.iter().map(|node| node.into()).collect::<Vec<_>>();
|
||||
let mut tested_gateways = gateways.iter().map(|node| node.into()).collect::<Vec<_>>();
|
||||
// summary of nodes that got tested
|
||||
let mut mixnodes_under_test = mixnodes_to_test_details
|
||||
.iter()
|
||||
.map(|node| node.into())
|
||||
.collect::<Vec<_>>();
|
||||
let mut gateways_under_test = gateways_to_test_details
|
||||
.iter()
|
||||
.map(|node| node.into())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// try to add nym-nodes into the fold
|
||||
if let Some(rewarded_set) = rewarded_set {
|
||||
let mut rng = thread_rng();
|
||||
for mix in mixing_nym_nodes {
|
||||
if let Some(parsed) = self.nym_node_to_legacy_mix(&mut rng, &rewarded_set, mix) {
|
||||
tested_mixnodes.push(TestableNode::from(&parsed));
|
||||
mixnodes_under_test.push(TestableNode::from(&parsed));
|
||||
mixnodes_to_test_details.push(parsed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for gateway in gateway_capable_nym_nodes {
|
||||
if let Some(parsed) = self.nym_node_to_legacy_gateway(gateway) {
|
||||
tested_gateways.push((&parsed, gateway.node_id).into())
|
||||
gateways_under_test.push((&parsed, gateway.node_id).into());
|
||||
gateways_to_test_details.push((parsed, gateway.node_id));
|
||||
}
|
||||
}
|
||||
|
||||
let packets_to_create = (test_routes.len() * self.per_node_test_packets)
|
||||
* (tested_mixnodes.len() + tested_gateways.len());
|
||||
* (mixnodes_under_test.len() + gateways_under_test.len());
|
||||
info!("Need to create {} mix packets", packets_to_create);
|
||||
|
||||
let mut all_gateway_packets = HashMap::new();
|
||||
@@ -574,7 +589,7 @@ impl PacketPreparer {
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let mixnode_test_packets = mix_tester
|
||||
.mixnodes_test_packets(
|
||||
&mixnodes,
|
||||
&mixnodes_to_test_details,
|
||||
route_ext,
|
||||
self.per_node_test_packets as u32,
|
||||
None,
|
||||
@@ -588,7 +603,7 @@ impl PacketPreparer {
|
||||
gateway_packets.push_packets(mix_packets);
|
||||
|
||||
// and generate test packets for gateways (note the variable recipient)
|
||||
for (gateway, node_id) in &gateways {
|
||||
for (gateway, node_id) in &gateways_to_test_details {
|
||||
let recipient = self.create_packet_sender(gateway);
|
||||
let gateway_identity = gateway.identity_key;
|
||||
let gateway_address = gateway.clients_address();
|
||||
@@ -624,8 +639,8 @@ impl PacketPreparer {
|
||||
|
||||
PreparedPackets {
|
||||
packets,
|
||||
tested_mixnodes,
|
||||
tested_gateways,
|
||||
mixnodes_under_test,
|
||||
gateways_under_test,
|
||||
invalid_mixnodes,
|
||||
invalid_gateways,
|
||||
}
|
||||
|
||||
@@ -145,6 +145,7 @@ impl NodeDescriptionTopologyExt for NymNodeDescription {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DescribedNodes {
|
||||
nodes: HashMap<NodeId, NymNodeDescription>,
|
||||
}
|
||||
@@ -290,6 +291,7 @@ async fn try_get_description(
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct RefreshData {
|
||||
host: String,
|
||||
node_id: NodeId,
|
||||
|
||||
@@ -210,6 +210,7 @@ impl ResolvedNodeDescribedInfo {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct UnwrappedResolvedNodeDescribedInfo {
|
||||
pub(crate) build_info: BinaryBuildInformationOwned,
|
||||
pub(crate) roles: DeclaredRoles,
|
||||
|
||||
+3
-3
@@ -209,7 +209,7 @@ pub(crate) async fn produce_node_annotations(
|
||||
legacy_mix.mix_id(),
|
||||
NodeAnnotation {
|
||||
last_24h_performance: perf,
|
||||
current_role: rewarded_set.role(legacy_mix.mix_id()),
|
||||
current_role: rewarded_set.role(legacy_mix.mix_id()).map(|r| r.into()),
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -229,7 +229,7 @@ pub(crate) async fn produce_node_annotations(
|
||||
legacy_gateway.node_id,
|
||||
NodeAnnotation {
|
||||
last_24h_performance: perf,
|
||||
current_role: rewarded_set.role(legacy_gateway.node_id),
|
||||
current_role: rewarded_set.role(legacy_gateway.node_id).map(|r| r.into()),
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -249,7 +249,7 @@ pub(crate) async fn produce_node_annotations(
|
||||
nym_node.node_id(),
|
||||
NodeAnnotation {
|
||||
last_24h_performance: perf,
|
||||
current_role: rewarded_set.role(nym_node.node_id()),
|
||||
current_role: rewarded_set.role(nym_node.node_id()).map(|r| r.into()),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ use nym_serde_helpers::date::DATE_FORMAT;
|
||||
use reqwest::StatusCode;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::Error;
|
||||
use std::fmt::Display;
|
||||
use thiserror::Error;
|
||||
use time::{Date, OffsetDateTime};
|
||||
@@ -438,7 +439,7 @@ pub enum NymApiStorageError {
|
||||
|
||||
// I don't think we want to expose errors to the user about what really happened
|
||||
#[error("experienced internal database error")]
|
||||
InternalDatabaseError(#[from] sqlx::Error),
|
||||
InternalDatabaseError(sqlx::Error),
|
||||
|
||||
// the same is true here (also note that the message is subtly different so we would be able to distinguish them)
|
||||
#[error("experienced internal storage error")]
|
||||
@@ -449,6 +450,14 @@ pub enum NymApiStorageError {
|
||||
StartupMigrationFailure(#[from] sqlx::migrate::MigrateError),
|
||||
}
|
||||
|
||||
impl From<sqlx::Error> for NymApiStorageError {
|
||||
fn from(err: Error) -> Self {
|
||||
// those should realistically never be happening so an `error!` is warranted
|
||||
error!("storage failure: {err}");
|
||||
NymApiStorageError::InternalDatabaseError(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl NymApiStorageError {
|
||||
pub fn database_inconsistency<S: Into<String>>(reason: S) -> NymApiStorageError {
|
||||
NymApiStorageError::DatabaseInconsistency {
|
||||
|
||||
+1
-1
@@ -173,7 +173,7 @@ impl NymContractCacheRefresher {
|
||||
layer,
|
||||
},
|
||||
rewarding_details: detail.rewarding_details,
|
||||
pending_changes: detail.pending_changes,
|
||||
pending_changes: detail.pending_changes.into(),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ async fn get_gateways_described(
|
||||
.into_iter()
|
||||
.map(|bond| LegacyDescribedGateway {
|
||||
self_described: self_descriptions.get_description(&bond.node_id).cloned(),
|
||||
bond,
|
||||
bond: bond.bond,
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
|
||||
@@ -9,7 +9,7 @@ use axum::routing::get;
|
||||
use axum::{Json, Router};
|
||||
use nym_api_requests::models::{
|
||||
AnnotationResponse, NodeDatePerformanceResponse, NodePerformanceResponse, NoiseDetails,
|
||||
NymNodeData, PerformanceHistoryResponse, UptimeHistoryResponse,
|
||||
NymNodeDescription, PerformanceHistoryResponse, UptimeHistoryResponse,
|
||||
};
|
||||
use nym_api_requests::pagination::{PaginatedResponse, Pagination};
|
||||
use nym_contracts_common::NaiveFloat;
|
||||
@@ -125,32 +125,27 @@ async fn get_bonded_nodes(
|
||||
path = "/described",
|
||||
context_path = "/v1/nym-nodes",
|
||||
responses(
|
||||
(status = 200, body = PaginatedResponse<NymNodeData>)
|
||||
(status = 200, body = PaginatedResponse<NymNodeDescription>)
|
||||
),
|
||||
params(PaginationRequest)
|
||||
)]
|
||||
async fn get_described_nodes(
|
||||
State(state): State<AppState>,
|
||||
Query(pagination): Query<PaginationRequest>,
|
||||
) -> AxumResult<Json<PaginatedResponse<NymNodeData>>> {
|
||||
) -> AxumResult<Json<PaginatedResponse<NymNodeDescription>>> {
|
||||
// TODO: implement it
|
||||
let _ = pagination;
|
||||
|
||||
let cache = state.described_nodes_cache.get().await?;
|
||||
let descriptions = cache.all_nodes();
|
||||
|
||||
let data = descriptions
|
||||
.map(|n| &n.description)
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
let descriptions = cache.all_nodes().cloned().collect::<Vec<_>>();
|
||||
|
||||
Ok(Json(PaginatedResponse {
|
||||
pagination: Pagination {
|
||||
total: data.len(),
|
||||
total: descriptions.len(),
|
||||
page: 0,
|
||||
size: data.len(),
|
||||
size: descriptions.len(),
|
||||
},
|
||||
data,
|
||||
data: descriptions,
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
@@ -10,27 +10,33 @@ use std::net::SocketAddr;
|
||||
pub(crate) struct Args {
|
||||
/// Id of the nym-api we want to initialise. if unspecified, a default value will be used.
|
||||
/// default: "default"
|
||||
#[clap(long, default_value = "default")]
|
||||
#[clap(long, default_value = "default", env = "NYMAPI_ID_ARG")]
|
||||
pub(crate) id: String,
|
||||
|
||||
/// Specifies whether network monitoring is enabled on this API
|
||||
/// default: false
|
||||
#[clap(short = 'm', long)]
|
||||
#[clap(short = 'm', long, env = "NYMAPI_ENABLE_MONITOR_ARG")]
|
||||
pub(crate) enable_monitor: bool,
|
||||
|
||||
/// Specifies whether network rewarding is enabled on this API
|
||||
/// default: false
|
||||
#[clap(short = 'r', long, requires = "enable_monitor", requires = "mnemonic")]
|
||||
#[clap(
|
||||
short = 'r',
|
||||
long,
|
||||
requires = "enable_monitor",
|
||||
requires = "mnemonic",
|
||||
env = "NYMAPI_ENABLE_REWARDING_ARG"
|
||||
)]
|
||||
pub(crate) enable_rewarding: bool,
|
||||
|
||||
/// Endpoint to nyxd instance used for contract information.
|
||||
/// default: http://localhost:26657
|
||||
#[clap(long)]
|
||||
#[clap(long, env = "NYMAPI_NYXD_VALIDATOR_ARG")]
|
||||
pub(crate) nyxd_validator: Option<url::Url>,
|
||||
|
||||
/// Mnemonic of the network monitor used for sending rewarding and zk-nyms transactions
|
||||
/// default: None
|
||||
#[clap(long)]
|
||||
#[clap(long, env = "NYMAPI_MNEMONIC_ARG")]
|
||||
pub(crate) mnemonic: Option<bip39::Mnemonic>,
|
||||
|
||||
/// Flag to indicate whether credential signer authority is enabled on this API
|
||||
@@ -39,18 +45,23 @@ pub(crate) struct Args {
|
||||
long,
|
||||
requires = "mnemonic",
|
||||
requires = "announce_address",
|
||||
alias = "enable_coconut"
|
||||
alias = "enable_coconut",
|
||||
env = "NYMAPI_ENABLE_ZK_NYM_ARG"
|
||||
)]
|
||||
pub(crate) enable_zk_nym: bool,
|
||||
|
||||
/// Announced address that is going to be put in the DKG contract where zk-nym clients will connect
|
||||
/// to obtain their credentials
|
||||
/// default: None
|
||||
#[clap(long)]
|
||||
#[clap(long, env = "NYMAPI_ANNOUNCE_ADDRESS_NYM_ARG")]
|
||||
pub(crate) announce_address: Option<url::Url>,
|
||||
|
||||
/// Set this nym api to work in a enabled credentials that would attempt to use gateway with the bandwidth credential requirement
|
||||
#[clap(long, requires = "enable_monitor")]
|
||||
#[clap(
|
||||
long,
|
||||
requires = "enable_monitor",
|
||||
env = "NYMAPI_MONITOR_CREDENTIALS_MODE_ARG"
|
||||
)]
|
||||
pub(crate) monitor_credentials_mode: bool,
|
||||
|
||||
/// Socket address this api will use for binding its http API.
|
||||
|
||||
@@ -20,11 +20,11 @@ fn pretty_build_info_static() -> &'static str {
|
||||
#[clap(author = "Nymtech", version, long_version = pretty_build_info_static(), about)]
|
||||
pub(crate) struct Cli {
|
||||
/// Path pointing to an env file that configures the Nym API.
|
||||
#[clap(short, long)]
|
||||
#[clap(short, long, env = "NYMAPI_CONFIG_ENV_FILE_ARG")]
|
||||
pub(crate) config_env_file: Option<std::path::PathBuf>,
|
||||
|
||||
/// A no-op flag included for consistency with other binaries (and compatibility with nymvisor, oops)
|
||||
#[clap(long)]
|
||||
#[clap(long, env = "NYMAPI_NO_BANNER_ARG")]
|
||||
pub(crate) no_banner: bool,
|
||||
|
||||
#[clap(subcommand)]
|
||||
|
||||
@@ -44,27 +44,33 @@ use tracing::{error, info};
|
||||
pub(crate) struct Args {
|
||||
/// Id of the nym-api we want to run.if unspecified, a default value will be used.
|
||||
/// default: "default"
|
||||
#[clap(long, default_value = "default")]
|
||||
#[clap(long, default_value = "default", env = "NYMAPI_ID_ARG")]
|
||||
pub(crate) id: String,
|
||||
|
||||
/// Specifies whether network monitoring is enabled on this API
|
||||
/// default: None - config value will be used instead
|
||||
#[clap(short = 'm', long)]
|
||||
#[clap(short = 'm', long, env = "NYMAPI_ENABLE_MONITOR_ARG")]
|
||||
pub(crate) enable_monitor: Option<bool>,
|
||||
|
||||
/// Specifies whether network rewarding is enabled on this API
|
||||
/// default: None - config value will be used instead
|
||||
#[clap(short = 'r', long, requires = "enable_monitor", requires = "mnemonic")]
|
||||
#[clap(
|
||||
short = 'r',
|
||||
long,
|
||||
requires = "enable_monitor",
|
||||
requires = "mnemonic",
|
||||
env = "NYMAPI_ENABLE_REWARDING_ARG"
|
||||
)]
|
||||
pub(crate) enable_rewarding: Option<bool>,
|
||||
|
||||
/// Endpoint to nyxd instance used for contract information.
|
||||
/// default: None - config value will be used instead
|
||||
#[clap(long)]
|
||||
#[clap(long, env = "NYMAPI_NYXD_VALIDATOR_ARG")]
|
||||
pub(crate) nyxd_validator: Option<url::Url>,
|
||||
|
||||
/// Mnemonic of the network monitor used for sending rewarding and zk-nyms transactions
|
||||
/// default: None - config value will be used instead
|
||||
#[clap(long)]
|
||||
#[clap(long, env = "NYMAPI_MNEMONIC_ARG")]
|
||||
pub(crate) mnemonic: Option<bip39::Mnemonic>,
|
||||
|
||||
/// Flag to indicate whether coconut signer authority is enabled on this API
|
||||
@@ -73,19 +79,20 @@ pub(crate) struct Args {
|
||||
long,
|
||||
requires = "mnemonic",
|
||||
requires = "announce_address",
|
||||
alias = "enable_coconut"
|
||||
alias = "enable_coconut",
|
||||
env = "NYMAPI_ENABLE_ZK_NYM_ARG"
|
||||
)]
|
||||
pub(crate) enable_zk_nym: Option<bool>,
|
||||
|
||||
/// Announced address that is going to be put in the DKG contract where zk-nym clients will connect
|
||||
/// to obtain their credentials
|
||||
/// default: None - config value will be used instead
|
||||
#[clap(long)]
|
||||
#[clap(long, env = "NYMAPI_ANNOUNCE_ADDRESS_ARG")]
|
||||
pub(crate) announce_address: Option<url::Url>,
|
||||
|
||||
/// Set this nym api to work in a enabled credentials that would attempt to use gateway with the bandwidth credential requirement
|
||||
/// default: None - config value will be used instead
|
||||
#[clap(long)]
|
||||
#[clap(long, env = "NYMAPI_MONITOR_CREDENTIALS_MODE_ARG")]
|
||||
pub(crate) monitor_credentials_mode: Option<bool>,
|
||||
|
||||
/// Socket address this api will use for binding its http API.
|
||||
|
||||
@@ -234,9 +234,9 @@ impl Config {
|
||||
fn default_http_socket_addr() -> SocketAddr {
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(debug_assertions)] {
|
||||
SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 8080)
|
||||
SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 8000)
|
||||
} else {
|
||||
SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 8080)
|
||||
SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 8000)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,9 +119,8 @@ impl StorageManager {
|
||||
start_ts_secs: i64,
|
||||
end_ts_secs: i64,
|
||||
) -> Result<Vec<AvgGatewayReliability>, sqlx::Error> {
|
||||
// we can't use `query_as!` macro because we don't apply all required table changes during sqlx migrations.
|
||||
// some (like v3 directory) happens at runtime
|
||||
let result = sqlx::query_as(
|
||||
let result = sqlx::query_as!(
|
||||
AvgGatewayReliability,
|
||||
r#"
|
||||
SELECT
|
||||
d.node_id as "node_id: NodeId",
|
||||
@@ -135,9 +134,9 @@ impl StorageManager {
|
||||
timestamp <= ?
|
||||
GROUP BY 1
|
||||
"#,
|
||||
start_ts_secs,
|
||||
end_ts_secs
|
||||
)
|
||||
.bind(start_ts_secs)
|
||||
.bind(end_ts_secs)
|
||||
.fetch_all(&self.connection_pool)
|
||||
.await?;
|
||||
Ok(result)
|
||||
|
||||
@@ -30,10 +30,6 @@ pub async fn migrate_v3_database(
|
||||
let contract_gateways = nyxd_client.get_gateways().await?;
|
||||
let nym_nodes = nyxd_client.get_nymnodes().await?;
|
||||
|
||||
if preassigned_ids.len() != contract_gateways.len() {
|
||||
bail!("CONTRACT DATA CORRUPTION: THE NUMBER OF PREASSIGNED GATEWAY IDS IS DIFFERENT THAN THE NUMBER OF GATEWAYS")
|
||||
}
|
||||
|
||||
// assign node_id to every gateway
|
||||
let all_known = storage.get_all_known_gateways().await?;
|
||||
for gateway in all_known {
|
||||
|
||||
@@ -22,12 +22,12 @@ reqwest = { workspace = true, features = ["json"] }
|
||||
wasm-bindgen = { workspace = true, optional = true }
|
||||
|
||||
## openapi:
|
||||
utoipa = { workspace = true, optional = true }
|
||||
utoipa = { workspace = true, optional = true, features = ["uuid"] }
|
||||
|
||||
nym-credentials = { path = "../../common/credentials" }
|
||||
nym-credentials-interface = { path = "../../common/credentials-interface" }
|
||||
nym-http-api-common = { path = "../../common/http-api-common", optional = true }
|
||||
nym-http-api-client = { path = "../../common/http-api-client" }
|
||||
nym-http-api-client = { path = "../../common/http-api-client" }
|
||||
nym-serde-helpers = { path = "../../common/serde-helpers", features = ["bs58"] }
|
||||
|
||||
[target."cfg(target_arch = \"wasm32\")".dependencies.wasmtimer]
|
||||
|
||||
@@ -40,6 +40,7 @@ pub struct TicketbookRequest {
|
||||
/// you **MUST** provide a valid value otherwise blacklisting won't work
|
||||
#[schemars(with = "String")]
|
||||
#[serde(with = "bs58_ecash")]
|
||||
#[cfg_attr(feature = "openapi", schema(value_type = String))]
|
||||
pub ecash_pubkey: PublicKeyUser,
|
||||
|
||||
// needs to be explicit in case user creates request at 23:59:59.999, but it reaches vpn-api at 00:00:00.001
|
||||
@@ -48,6 +49,7 @@ pub struct TicketbookRequest {
|
||||
pub expiration_date: Date,
|
||||
|
||||
#[schemars(with = "String")]
|
||||
#[cfg_attr(feature = "openapi", schema(value_type = String))]
|
||||
pub ticketbook_type: TicketType,
|
||||
|
||||
pub is_freepass_request: bool,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-credential-proxy"
|
||||
version = "0.1.0"
|
||||
version = "0.1.2"
|
||||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
homepage.workspace = true
|
||||
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
|
||||
DROP TABLE blinded_shares;
|
||||
CREATE TABLE blinded_shares
|
||||
(
|
||||
id INTEGER NOT NULL PRIMARY KEY,
|
||||
-- removed reference to `ticketbook_deposit` as the deposit wouldn't actually have been made before the pending share is inserted
|
||||
request_uuid TEXT NOT NULL,
|
||||
status TEXT NOT NULL,
|
||||
device_id TEXT NOT NULL,
|
||||
credential_id TEXT NOT NULL,
|
||||
available_shares INTEGER NOT NULL DEFAULT 0,
|
||||
error_message TEXT DEFAULT NULL,
|
||||
created TIMESTAMP WITHOUT TIME ZONE NOT NULL,
|
||||
updated TIMESTAMP WITHOUT TIME ZONE NOT NULL
|
||||
);
|
||||
+1
-1
@@ -149,7 +149,7 @@ pub(crate) async fn query_for_shares_by_id(
|
||||
(status = 401, description = "authentication token is missing or is invalid"),
|
||||
(status = 500, body = ErrorResponse, description = "failed to query for bandwidth blinded shares"),
|
||||
),
|
||||
params(OutputParams),
|
||||
params(SharesQueryParams),
|
||||
security(
|
||||
("auth_token" = [])
|
||||
)
|
||||
|
||||
@@ -82,7 +82,11 @@ impl SqliteStorageManager {
|
||||
sqlx::query_as!(
|
||||
MinimalWalletShare,
|
||||
r#"
|
||||
SELECT t1.node_id, t1.blinded_signature, t1.epoch_id, t1.expiration_date
|
||||
SELECT
|
||||
t1.node_id as "node_id!",
|
||||
t1.blinded_signature as "blinded_signature!",
|
||||
t1.epoch_id as "epoch_id!",
|
||||
t1.expiration_date as "expiration_date!"
|
||||
FROM partial_blinded_wallet as t1
|
||||
JOIN ticketbook_deposit as t2
|
||||
on t1.corresponding_deposit = t2.deposit_id
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::collections::{HashMap, HashSet};
|
||||
use anyhow::Result;
|
||||
use futures::{stream::FuturesUnordered, StreamExt};
|
||||
use log::{debug, info};
|
||||
use nym_sphinx::chunking::{SentFragment, FRAGMENTS_RECEIVED, FRAGMENTS_SENT};
|
||||
use nym_sphinx::chunking::{monitoring, SentFragment};
|
||||
use nym_topology::{gateway, mix, NymTopology};
|
||||
use nym_types::monitoring::{MonitorMessage, NodeResult};
|
||||
use nym_validator_client::nym_api::routes::{API_VERSION, STATUS, SUBMIT_GATEWAY, SUBMIT_NODE};
|
||||
@@ -115,8 +115,8 @@ impl NetworkAccount {
|
||||
}
|
||||
|
||||
pub fn empty_buffers() {
|
||||
FRAGMENTS_SENT.clear();
|
||||
FRAGMENTS_RECEIVED.clear();
|
||||
monitoring::FRAGMENTS_SENT.clear();
|
||||
monitoring::FRAGMENTS_RECEIVED.clear();
|
||||
}
|
||||
|
||||
fn new() -> Self {
|
||||
@@ -125,7 +125,7 @@ impl NetworkAccount {
|
||||
topology,
|
||||
..Default::default()
|
||||
};
|
||||
for fragment_set in FRAGMENTS_SENT.iter() {
|
||||
for fragment_set in monitoring::FRAGMENTS_SENT.iter() {
|
||||
let sent_fragments = fragment_set
|
||||
.value()
|
||||
.first()
|
||||
@@ -138,7 +138,7 @@ impl NetworkAccount {
|
||||
sent_fragments
|
||||
);
|
||||
|
||||
let recv = FRAGMENTS_RECEIVED.get(fragment_set.key());
|
||||
let recv = monitoring::FRAGMENTS_RECEIVED.get(fragment_set.key());
|
||||
let recv_fragments = recv.as_ref().map(|r| r.value().len()).unwrap_or(0);
|
||||
debug!(
|
||||
"RECV Fragment set {} has {} fragments",
|
||||
@@ -170,7 +170,7 @@ impl NetworkAccount {
|
||||
}
|
||||
|
||||
fn hydrate_all_fragments(&mut self) -> Result<()> {
|
||||
for fragment_set in FRAGMENTS_SENT.iter() {
|
||||
for fragment_set in monitoring::FRAGMENTS_SENT.iter() {
|
||||
let fragment_set_id = fragment_set.key();
|
||||
for fragment in fragment_set.value() {
|
||||
let route = self.hydrate_route(fragment.clone())?;
|
||||
@@ -205,7 +205,7 @@ impl NetworkAccount {
|
||||
fn find_missing_fragments(&mut self) {
|
||||
let mut missing_fragments_map = HashMap::new();
|
||||
for fragment_set_id in &self.incomplete_fragment_sets {
|
||||
if let Some(fragment_ref) = FRAGMENTS_RECEIVED.get(fragment_set_id) {
|
||||
if let Some(fragment_ref) = monitoring::FRAGMENTS_RECEIVED.get(fragment_set_id) {
|
||||
if let Some(ref_fragment) = fragment_ref.value().first() {
|
||||
let ref_header = ref_fragment.header();
|
||||
let ref_id_set = (0..ref_header.total_fragments()).collect::<HashSet<u8>>();
|
||||
|
||||
@@ -6,7 +6,7 @@ use axum::{
|
||||
use futures::StreamExt;
|
||||
use log::{debug, error, warn};
|
||||
use nym_sdk::mixnet::MixnetMessageSender;
|
||||
use nym_sphinx::chunking::{ReceivedFragment, SentFragment, FRAGMENTS_RECEIVED, FRAGMENTS_SENT};
|
||||
use nym_sphinx::chunking::{monitoring, ReceivedFragment, SentFragment};
|
||||
use petgraph::{dot::Dot, Graph};
|
||||
use rand::{distributions::Alphanumeric, seq::SliceRandom, Rng};
|
||||
use serde::Serialize;
|
||||
@@ -113,7 +113,7 @@ pub async fn graph_handler() -> Result<String, StatusCode> {
|
||||
)]
|
||||
pub async fn sent_handler() -> Json<FragmentsSent> {
|
||||
Json(FragmentsSent(
|
||||
(*FRAGMENTS_SENT)
|
||||
(*monitoring::FRAGMENTS_SENT)
|
||||
.clone()
|
||||
.into_iter()
|
||||
.collect::<HashMap<_, _>>(),
|
||||
@@ -129,7 +129,7 @@ pub async fn sent_handler() -> Json<FragmentsSent> {
|
||||
)]
|
||||
pub async fn recv_handler() -> Json<FragmentsReceived> {
|
||||
Json(FragmentsReceived(
|
||||
(*FRAGMENTS_RECEIVED)
|
||||
(*monitoring::FRAGMENTS_RECEIVED)
|
||||
.clone()
|
||||
.into_iter()
|
||||
.collect::<HashMap<_, _>>(),
|
||||
|
||||
@@ -7,6 +7,7 @@ use nym_crypto::asymmetric::ed25519::PrivateKey;
|
||||
use nym_network_defaults::setup_env;
|
||||
use nym_network_defaults::var_names::NYM_API;
|
||||
use nym_sdk::mixnet::{self, MixnetClient};
|
||||
use nym_sphinx::chunking::monitoring;
|
||||
use nym_topology::{HardcodedTopologyProvider, NymTopology};
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
@@ -154,6 +155,9 @@ async fn main() -> Result<()> {
|
||||
|
||||
setup_env(args.env); // Defaults to mainnet if empty
|
||||
|
||||
// enable monitoring client-side
|
||||
monitoring::enable();
|
||||
|
||||
let cancel_token = CancellationToken::new();
|
||||
let server_cancel_token = cancel_token.clone();
|
||||
let clients = Arc::new(RwLock::new(VecDeque::with_capacity(args.n_clients)));
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
|
||||
[package]
|
||||
name = "nym-node"
|
||||
version = "1.1.8"
|
||||
version = "1.1.9"
|
||||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
homepage.workspace = true
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
FROM rust:latest AS builder
|
||||
|
||||
COPY ./ /usr/src/nym
|
||||
WORKDIR /usr/src/nym/nym-node
|
||||
|
||||
RUN cargo build --release
|
||||
|
||||
FROM ubuntu:24.04
|
||||
|
||||
WORKDIR /nym
|
||||
|
||||
COPY --from=builder /usr/src/nym/target/release/nym-node ./
|
||||
ENTRYPOINT [ "/nym/nym-node" ]
|
||||
@@ -1,7 +1,9 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::api::v1::node::models::{HostInformation, LegacyHostInformation};
|
||||
use crate::api::v1::node::models::{
|
||||
HostInformation, LegacyHostInformation, LegacyHostInformationV2,
|
||||
};
|
||||
use crate::error::Error;
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use schemars::JsonSchema;
|
||||
@@ -61,9 +63,18 @@ impl SignedHostInformation {
|
||||
return true;
|
||||
}
|
||||
|
||||
// attempt to verify legacy signature
|
||||
// attempt to verify legacy signatures
|
||||
let legacy_v2 = SignedData {
|
||||
data: LegacyHostInformationV2::from(self.data.clone()),
|
||||
signature: self.signature.clone(),
|
||||
};
|
||||
|
||||
if legacy_v2.verify(&self.keys.ed25519_identity) {
|
||||
return true;
|
||||
}
|
||||
|
||||
SignedData {
|
||||
data: LegacyHostInformation::from(self.data.clone()),
|
||||
data: LegacyHostInformation::from(legacy_v2.data),
|
||||
signature: self.signature.clone(),
|
||||
}
|
||||
.verify(&self.keys.ed25519_identity)
|
||||
@@ -133,6 +144,81 @@ mod tests {
|
||||
assert!(signed_info.verify_host_information());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dummy_legacy_v2_signed_host_verification() {
|
||||
let mut rng = rand_chacha::ChaCha20Rng::from_seed([0u8; 32]);
|
||||
let ed22519 = ed25519::KeyPair::new(&mut rng);
|
||||
let x25519_sphinx = x25519::KeyPair::new(&mut rng);
|
||||
let x25519_noise = x25519::KeyPair::new(&mut rng);
|
||||
|
||||
let legacy_info_no_noise = crate::api::v1::node::models::LegacyHostInformationV2 {
|
||||
ip_address: vec!["1.1.1.1".parse().unwrap()],
|
||||
hostname: Some("foomp.com".to_string()),
|
||||
keys: crate::api::v1::node::models::LegacyHostKeysV2 {
|
||||
ed25519_identity: ed22519.public_key().to_base58_string(),
|
||||
x25519_sphinx: x25519_sphinx.public_key().to_base58_string(),
|
||||
x25519_noise: "".to_string(),
|
||||
},
|
||||
};
|
||||
|
||||
let legacy_info_noise = crate::api::v1::node::models::LegacyHostInformationV2 {
|
||||
ip_address: vec!["1.1.1.1".parse().unwrap()],
|
||||
hostname: Some("foomp.com".to_string()),
|
||||
keys: crate::api::v1::node::models::LegacyHostKeysV2 {
|
||||
ed25519_identity: ed22519.public_key().to_base58_string(),
|
||||
x25519_sphinx: x25519_sphinx.public_key().to_base58_string(),
|
||||
x25519_noise: x25519_noise.public_key().to_base58_string(),
|
||||
},
|
||||
};
|
||||
|
||||
let host_info_no_noise = crate::api::v1::node::models::HostInformation {
|
||||
ip_address: legacy_info_no_noise.ip_address.clone(),
|
||||
hostname: legacy_info_no_noise.hostname.clone(),
|
||||
keys: crate::api::v1::node::models::HostKeys {
|
||||
ed25519_identity: legacy_info_no_noise.keys.ed25519_identity.parse().unwrap(),
|
||||
x25519_sphinx: legacy_info_no_noise.keys.x25519_sphinx.parse().unwrap(),
|
||||
x25519_noise: None,
|
||||
},
|
||||
};
|
||||
|
||||
let host_info_noise = crate::api::v1::node::models::HostInformation {
|
||||
ip_address: legacy_info_noise.ip_address.clone(),
|
||||
hostname: legacy_info_noise.hostname.clone(),
|
||||
keys: crate::api::v1::node::models::HostKeys {
|
||||
ed25519_identity: legacy_info_noise.keys.ed25519_identity.parse().unwrap(),
|
||||
x25519_sphinx: legacy_info_noise.keys.x25519_sphinx.parse().unwrap(),
|
||||
x25519_noise: Some(legacy_info_noise.keys.x25519_noise.parse().unwrap()),
|
||||
},
|
||||
};
|
||||
|
||||
// signature on legacy data
|
||||
let signature_no_noise = SignedData::new(legacy_info_no_noise, ed22519.private_key())
|
||||
.unwrap()
|
||||
.signature;
|
||||
|
||||
let signature_noise = SignedData::new(legacy_info_noise, ed22519.private_key())
|
||||
.unwrap()
|
||||
.signature;
|
||||
|
||||
// signed blob with the 'current' structure
|
||||
let current_struct_no_noise = SignedData {
|
||||
data: host_info_no_noise,
|
||||
signature: signature_no_noise,
|
||||
};
|
||||
|
||||
let current_struct_noise = SignedData {
|
||||
data: host_info_noise,
|
||||
signature: signature_noise,
|
||||
};
|
||||
|
||||
assert!(!current_struct_no_noise.verify(ed22519.public_key()));
|
||||
assert!(current_struct_no_noise.verify_host_information());
|
||||
|
||||
// if noise key is present, the signature is actually valid
|
||||
assert!(current_struct_noise.verify(ed22519.public_key()));
|
||||
assert!(current_struct_noise.verify_host_information())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dummy_legacy_signed_host_verification() {
|
||||
let mut rng = rand_chacha::ChaCha20Rng::from_seed([0u8; 32]);
|
||||
|
||||
@@ -59,6 +59,13 @@ pub struct HostInformation {
|
||||
pub keys: HostKeys,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct LegacyHostInformationV2 {
|
||||
pub ip_address: Vec<IpAddr>,
|
||||
pub hostname: Option<String>,
|
||||
pub keys: LegacyHostKeysV2,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct LegacyHostInformation {
|
||||
pub ip_address: Vec<IpAddr>,
|
||||
@@ -66,8 +73,18 @@ pub struct LegacyHostInformation {
|
||||
pub keys: LegacyHostKeys,
|
||||
}
|
||||
|
||||
impl From<HostInformation> for LegacyHostInformation {
|
||||
impl From<HostInformation> for LegacyHostInformationV2 {
|
||||
fn from(value: HostInformation) -> Self {
|
||||
LegacyHostInformationV2 {
|
||||
ip_address: value.ip_address,
|
||||
hostname: value.hostname,
|
||||
keys: value.keys.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LegacyHostInformationV2> for LegacyHostInformation {
|
||||
fn from(value: LegacyHostInformationV2) -> Self {
|
||||
LegacyHostInformation {
|
||||
ip_address: value.ip_address,
|
||||
hostname: value.hostname,
|
||||
@@ -99,13 +116,11 @@ pub struct HostKeys {
|
||||
pub x25519_noise: Option<x25519::PublicKey>,
|
||||
}
|
||||
|
||||
impl From<HostKeys> for LegacyHostKeys {
|
||||
fn from(value: HostKeys) -> Self {
|
||||
LegacyHostKeys {
|
||||
ed25519: value.ed25519_identity.to_base58_string(),
|
||||
x25519: value.x25519_sphinx.to_base58_string(),
|
||||
}
|
||||
}
|
||||
#[derive(Serialize)]
|
||||
pub struct LegacyHostKeysV2 {
|
||||
pub ed25519_identity: String,
|
||||
pub x25519_sphinx: String,
|
||||
pub x25519_noise: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
@@ -114,6 +129,28 @@ pub struct LegacyHostKeys {
|
||||
pub x25519: String,
|
||||
}
|
||||
|
||||
impl From<HostKeys> for LegacyHostKeysV2 {
|
||||
fn from(value: HostKeys) -> Self {
|
||||
LegacyHostKeysV2 {
|
||||
ed25519_identity: value.ed25519_identity.to_base58_string(),
|
||||
x25519_sphinx: value.x25519_sphinx.to_base58_string(),
|
||||
x25519_noise: value
|
||||
.x25519_noise
|
||||
.map(|k| k.to_base58_string())
|
||||
.unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LegacyHostKeysV2> for LegacyHostKeys {
|
||||
fn from(value: LegacyHostKeysV2) -> Self {
|
||||
LegacyHostKeys {
|
||||
ed25519: value.ed25519_identity,
|
||||
x25519: value.x25519_sphinx,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Default, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
|
||||
pub struct HostSystem {
|
||||
|
||||
@@ -154,7 +154,7 @@ pub fn ephemeral_entry_gateway_config(
|
||||
config: Config,
|
||||
mnemonic: &bip39::Mnemonic,
|
||||
) -> Result<EphemeralConfig, EntryGatewayError> {
|
||||
let auth_opts = LocalAuthenticatorOpts {
|
||||
let mut auth_opts = LocalAuthenticatorOpts {
|
||||
config: nym_authenticator::Config {
|
||||
base: nym_client_core_config_types::Config {
|
||||
client: base_client_config(&config),
|
||||
@@ -173,6 +173,10 @@ pub fn ephemeral_entry_gateway_config(
|
||||
custom_mixnet_path: None,
|
||||
};
|
||||
|
||||
if config.authenticator.debug.disable_poisson_rate {
|
||||
auth_opts.config.base.set_no_poisson_process();
|
||||
}
|
||||
|
||||
let wg_opts = LocalWireguardOpts {
|
||||
config: super::Wireguard {
|
||||
enabled: config.wireguard.enabled,
|
||||
|
||||
@@ -243,7 +243,7 @@ pub fn ephemeral_exit_gateway_config(
|
||||
ipr_opts.config.base.set_no_poisson_process()
|
||||
}
|
||||
|
||||
let auth_opts = LocalAuthenticatorOpts {
|
||||
let mut auth_opts = LocalAuthenticatorOpts {
|
||||
config: nym_authenticator::Config {
|
||||
base: nym_client_core_config_types::Config {
|
||||
client: base_client_config(&config),
|
||||
@@ -262,6 +262,10 @@ pub fn ephemeral_exit_gateway_config(
|
||||
custom_mixnet_path: None,
|
||||
};
|
||||
|
||||
if config.authenticator.debug.disable_poisson_rate {
|
||||
auth_opts.config.base.set_no_poisson_process();
|
||||
}
|
||||
|
||||
let pub_id_path = config
|
||||
.storage_paths
|
||||
.keys
|
||||
|
||||
@@ -46,6 +46,7 @@ pub struct Verloc {
|
||||
/// will use.
|
||||
/// Useful when the node is behind a proxy.
|
||||
#[serde(deserialize_with = "de_maybe_port")]
|
||||
#[serde(default)]
|
||||
pub announce_port: Option<u16>,
|
||||
|
||||
#[serde(default)]
|
||||
|
||||
@@ -421,6 +421,7 @@ pub struct Mixnet {
|
||||
/// will use.
|
||||
/// Useful when the node is behind a proxy.
|
||||
#[serde(deserialize_with = "de_maybe_port")]
|
||||
#[serde(default)]
|
||||
pub announce_port: Option<u16>,
|
||||
|
||||
/// Addresses to nym APIs from which the node gets the view of the network.
|
||||
|
||||
Generated
+34
-34
@@ -173,7 +173,7 @@ checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.55",
|
||||
"syn 2.0.85",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -658,7 +658,7 @@ dependencies = [
|
||||
"heck 0.4.1",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.55",
|
||||
"syn 2.0.85",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1022,7 +1022,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn 2.0.55",
|
||||
"syn 2.0.85",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1083,7 +1083,7 @@ checksum = "83fdaf97f4804dcebfa5862639bc9ce4121e82140bec2a987ac5140294865b5b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.55",
|
||||
"syn 2.0.85",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1751,7 +1751,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.55",
|
||||
"syn 2.0.85",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1931,7 +1931,7 @@ dependencies = [
|
||||
"proc-macro-error2",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.55",
|
||||
"syn 2.0.85",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3667,7 +3667,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.55",
|
||||
"syn 2.0.85",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3854,7 +3854,7 @@ dependencies = [
|
||||
"pest_meta",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.55",
|
||||
"syn 2.0.85",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3983,7 +3983,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.55",
|
||||
"syn 2.0.85",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4140,7 +4140,7 @@ dependencies = [
|
||||
"proc-macro-error-attr2",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.55",
|
||||
"syn 2.0.85",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4151,9 +4151,9 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.79"
|
||||
version = "1.0.89"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e"
|
||||
checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
@@ -4178,7 +4178,7 @@ dependencies = [
|
||||
"itertools 0.11.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.55",
|
||||
"syn 2.0.85",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4704,7 +4704,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde_derive_internals",
|
||||
"syn 2.0.55",
|
||||
"syn 2.0.85",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4798,9 +4798,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.210"
|
||||
version = "1.0.214"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a"
|
||||
checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
@@ -4825,13 +4825,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.210"
|
||||
version = "1.0.214"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f"
|
||||
checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.55",
|
||||
"syn 2.0.85",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4842,14 +4842,14 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.55",
|
||||
"syn 2.0.85",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.128"
|
||||
version = "1.0.132"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8"
|
||||
checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03"
|
||||
dependencies = [
|
||||
"itoa 1.0.9",
|
||||
"memchr",
|
||||
@@ -4865,7 +4865,7 @@ checksum = "8725e1dfadb3a50f7e5ce0b1a540466f6ed3fe7a0fca2ac2b8b831d31316bd00"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.55",
|
||||
"syn 2.0.85",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5219,7 +5219,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustversion",
|
||||
"syn 2.0.55",
|
||||
"syn 2.0.85",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5262,9 +5262,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.55"
|
||||
version = "2.0.85"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "002a1b3dbf967edfafc32655d0f377ab0bb7b994aa1d32c8cc7e9b8bf3ebb8f0"
|
||||
checksum = "5023162dfcd14ef8f32034d8bcd4cc5ddc61ef7a247c024a33e24e1f24d21b56"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -5769,7 +5769,7 @@ checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.55",
|
||||
"syn 2.0.85",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5856,7 +5856,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.55",
|
||||
"syn 2.0.85",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6021,7 +6021,7 @@ checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.55",
|
||||
"syn 2.0.85",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6097,7 +6097,7 @@ checksum = "0ea0b99e8ec44abd6f94a18f28f7934437809dd062820797c52401298116f70e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.55",
|
||||
"syn 2.0.85",
|
||||
"termcolor",
|
||||
]
|
||||
|
||||
@@ -6201,7 +6201,7 @@ dependencies = [
|
||||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.55",
|
||||
"syn 2.0.85",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6316,7 +6316,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.55",
|
||||
"syn 2.0.85",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
@@ -6350,7 +6350,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.55",
|
||||
"syn 2.0.85",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
@@ -6979,7 +6979,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.55",
|
||||
"syn 2.0.85",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -4,13 +4,12 @@
|
||||
use crate::api_client;
|
||||
use crate::error::BackendError;
|
||||
use crate::state::WalletState;
|
||||
use nym_mixnet_contract_common::nym_node::Role;
|
||||
use nym_mixnet_contract_common::{
|
||||
reward_params::Performance, Coin, IdentityKeyRef, NodeId, Percent,
|
||||
};
|
||||
use nym_validator_client::client::NymApiClientExt;
|
||||
use nym_validator_client::models::{
|
||||
AnnotationResponse, ComputeRewardEstParam, GatewayCoreStatusResponse,
|
||||
AnnotationResponse, ComputeRewardEstParam, DisplayRole, GatewayCoreStatusResponse,
|
||||
GatewayStatusReportResponse, InclusionProbabilityResponse, MixnodeCoreStatusResponse,
|
||||
MixnodeStatusResponse, RewardEstimationResponse, StakeSaturationResponse,
|
||||
};
|
||||
@@ -110,7 +109,7 @@ pub async fn mixnode_inclusion_probability(
|
||||
pub async fn get_nymnode_role(
|
||||
node_id: NodeId,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<Option<Role>, BackendError> {
|
||||
) -> Result<Option<DisplayRole>, BackendError> {
|
||||
let annotation = get_nymnode_annotation(node_id, state).await?;
|
||||
Ok(annotation.annotation.and_then(|n| n.current_role))
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user