Compare commits
42 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8fb9c1d1fa | |||
| 6649cbb229 | |||
| 57b9ddec41 | |||
| c6959d3e2d | |||
| 2569deb080 | |||
| 5cefa7fdd4 | |||
| 80b590d50d | |||
| f9b363648f | |||
| b73561f1c9 | |||
| 09b68a8204 | |||
| 0374626960 | |||
| cf4fe5f875 | |||
| 9f8bf2d080 | |||
| b9d1fc40e7 | |||
| 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 |
@@ -1,55 +0,0 @@
|
||||
name: Build and upload Data observatory container to harbor.nymte.ch
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
WORKING_DIRECTORY: "nym-data-observatory"
|
||||
CONTAINER_NAME: "data-observatory"
|
||||
|
||||
jobs:
|
||||
build-container:
|
||||
runs-on: arc-ubuntu-22.04-dind
|
||||
steps:
|
||||
- name: Login to Harbor
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: harbor.nymte.ch
|
||||
username: ${{ secrets.HARBOR_ROBOT_USERNAME }}
|
||||
password: ${{ secrets.HARBOR_ROBOT_SECRET }}
|
||||
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Configure git identity
|
||||
run: |
|
||||
git config --global user.email "lawrence@nymtech.net"
|
||||
git config --global user.name "Lawrence Stalder"
|
||||
|
||||
- name: Get version from cargo.toml
|
||||
uses: mikefarah/yq@v4.44.3
|
||||
id: get_version
|
||||
with:
|
||||
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
|
||||
|
||||
- name: Check if tag exists
|
||||
run: |
|
||||
if git rev-parse ${{ steps.get_version.outputs.value }} >/dev/null 2>&1; then
|
||||
echo "Tag ${{ steps.get_version.outputs.value }} already exists"
|
||||
fi
|
||||
|
||||
- name: Remove existing tag if exists
|
||||
run: |
|
||||
if git rev-parse ${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }} >/dev/null 2>&1; then
|
||||
git push --delete origin ${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }}
|
||||
git tag -d ${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }}
|
||||
fi
|
||||
|
||||
- name: Create tag
|
||||
run: |
|
||||
git tag -a ${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }} -m "Version ${{ steps.get_version.outputs.result }}"
|
||||
git push origin ${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }}
|
||||
|
||||
- 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 push harbor.nymte.ch/nym/${{ env.CONTAINER_NAME }} --all-tags
|
||||
Generated
+469
-841
File diff suppressed because it is too large
Load Diff
+8
-8
@@ -54,7 +54,6 @@ members = [
|
||||
"common/exit-policy",
|
||||
"common/gateway-requests",
|
||||
"common/gateway-storage",
|
||||
"common/gateway-stats-storage",
|
||||
"common/http-api-client",
|
||||
"common/http-api-common",
|
||||
"common/inclusion-probability",
|
||||
@@ -190,9 +189,10 @@ aes = "0.8.1"
|
||||
aes-gcm = "0.10.1"
|
||||
aes-gcm-siv = "0.11.1"
|
||||
aead = "0.5.2"
|
||||
anyhow = "1.0.90"
|
||||
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"
|
||||
@@ -215,7 +215,7 @@ chacha20 = "0.9.0"
|
||||
chacha20poly1305 = "0.10.1"
|
||||
chrono = "0.4.31"
|
||||
cipher = "0.4.3"
|
||||
clap = "4.5.20"
|
||||
clap = "4.5.18"
|
||||
clap_complete = "4.5"
|
||||
clap_complete_fig = "4.5"
|
||||
colored = "2.0"
|
||||
@@ -285,7 +285,7 @@ opentelemetry-jaeger = "0.18.0"
|
||||
parking_lot = "0.12.3"
|
||||
pem = "0.8"
|
||||
petgraph = "0.6.5"
|
||||
pin-project = "1.1"
|
||||
pin-project = "1.0"
|
||||
pin-project-lite = "0.2.14"
|
||||
pretty_env_logger = "0.4.0"
|
||||
publicsuffix = "2.2.3"
|
||||
@@ -305,7 +305,7 @@ rocket_okapi = "0.8.0"
|
||||
safer-ffi = "0.1.13"
|
||||
schemars = "0.8.21"
|
||||
semver = "1.0.23"
|
||||
serde = "1.0.211"
|
||||
serde = "1.0.210"
|
||||
serde_bytes = "0.11.15"
|
||||
serde_derive = "1.0"
|
||||
serde_json = "1.0.132"
|
||||
@@ -402,10 +402,10 @@ indexed_db_futures = { git = "https://github.com/TiemenSch/rust-indexed-db", bra
|
||||
js-sys = "0.3.70"
|
||||
serde-wasm-bindgen = "0.6.5"
|
||||
tsify = "0.4.5"
|
||||
wasm-bindgen = "0.2.95"
|
||||
wasm-bindgen-futures = "0.4.45"
|
||||
wasm-bindgen = "0.2.93"
|
||||
wasm-bindgen-futures = "0.4.43"
|
||||
wasmtimer = "0.2.0"
|
||||
web-sys = "0.3.72"
|
||||
web-sys = "0.3.70"
|
||||
|
||||
|
||||
# Profile settings for individual crates
|
||||
|
||||
@@ -27,7 +27,7 @@ pub type HmacSha256 = Hmac<Sha256>;
|
||||
pub type Nonce = u64;
|
||||
pub type Taken = Option<SystemTime>;
|
||||
|
||||
pub const BANDWIDTH_CAP_PER_DAY: u64 = 1024 * 1024 * 1024; // 1 GB
|
||||
pub const BANDWIDTH_CAP_PER_DAY: u64 = 250 * 1024 * 1024 * 1024; // 250 GB
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct InitMessage {
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -18,8 +18,8 @@ use nym_api_requests::ecash::{
|
||||
PartialExpirationDateSignatureResponse, VerificationKeyResponse,
|
||||
};
|
||||
use nym_api_requests::models::{
|
||||
GatewayCoreStatusResponse, MixnodeCoreStatusResponse, MixnodeStatusResponse,
|
||||
RewardEstimationResponse, StakeSaturationResponse,
|
||||
ApiHealthResponse, GatewayCoreStatusResponse, MixnodeCoreStatusResponse, MixnodeStatusResponse,
|
||||
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>,
|
||||
}
|
||||
|
||||
@@ -190,6 +192,8 @@ impl<C, S> Client<C, S> {
|
||||
}
|
||||
|
||||
// validator-api wrappers
|
||||
// we have to allow the use of deprecated method here as they're calling the deprecated trait methods
|
||||
#[allow(deprecated)]
|
||||
impl<C, S> Client<C, S> {
|
||||
pub fn api_url(&self) -> &Url {
|
||||
self.nym_api.current_url()
|
||||
@@ -199,50 +203,102 @@ impl<C, S> Client<C, S> {
|
||||
self.nym_api.change_base_url(new_endpoint)
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
pub async fn get_cached_mixnodes(&self) -> Result<Vec<MixNodeDetails>, ValidatorClientError> {
|
||||
Ok(self.nym_api.get_mixnodes().await?)
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
pub async fn get_cached_mixnodes_detailed(
|
||||
&self,
|
||||
) -> Result<Vec<MixNodeBondAnnotated>, ValidatorClientError> {
|
||||
Ok(self.nym_api.get_mixnodes_detailed().await?)
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
pub async fn get_cached_mixnodes_detailed_unfiltered(
|
||||
&self,
|
||||
) -> Result<Vec<MixNodeBondAnnotated>, ValidatorClientError> {
|
||||
Ok(self.nym_api.get_mixnodes_detailed_unfiltered().await?)
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
pub async fn get_cached_rewarded_mixnodes(
|
||||
&self,
|
||||
) -> Result<Vec<MixNodeDetails>, ValidatorClientError> {
|
||||
Ok(self.nym_api.get_rewarded_mixnodes().await?)
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
pub async fn get_cached_rewarded_mixnodes_detailed(
|
||||
&self,
|
||||
) -> Result<Vec<MixNodeBondAnnotated>, ValidatorClientError> {
|
||||
Ok(self.nym_api.get_rewarded_mixnodes_detailed().await?)
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
pub async fn get_cached_active_mixnodes(
|
||||
&self,
|
||||
) -> Result<Vec<MixNodeDetails>, ValidatorClientError> {
|
||||
Ok(self.nym_api.get_active_mixnodes().await?)
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
pub async fn get_cached_active_mixnodes_detailed(
|
||||
&self,
|
||||
) -> Result<Vec<MixNodeBondAnnotated>, ValidatorClientError> {
|
||||
Ok(self.nym_api.get_active_mixnodes_detailed().await?)
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
pub async fn get_cached_gateways(&self) -> Result<Vec<GatewayBond>, ValidatorClientError> {
|
||||
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,
|
||||
@@ -258,6 +314,8 @@ pub struct NymApiClient {
|
||||
// we could re-implement the communication with the REST API on port 1317
|
||||
}
|
||||
|
||||
// we have to allow the use of deprecated method here as they're calling the deprecated trait methods
|
||||
#[allow(deprecated)]
|
||||
impl NymApiClient {
|
||||
pub fn new(api_url: Url) -> Self {
|
||||
let nym_api = nym_api::Client::new(api_url, None);
|
||||
@@ -290,7 +348,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>,
|
||||
@@ -327,7 +385,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),
|
||||
@@ -348,7 +406,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> {
|
||||
@@ -378,32 +436,142 @@ impl NymApiClient {
|
||||
Ok(nodes)
|
||||
}
|
||||
|
||||
/// retrieve basic information for nodes are capable of operating as a mixnode
|
||||
/// this includes legacy mixnodes and nym-nodes
|
||||
pub async fn get_all_basic_mixing_capable_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_mixing_capable_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)
|
||||
}
|
||||
|
||||
/// 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 health(&self) -> Result<ApiHealthResponse, ValidatorClientError> {
|
||||
Ok(self.nym_api.health().await?)
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
pub async fn get_cached_active_mixnodes(
|
||||
&self,
|
||||
) -> Result<Vec<MixNodeDetails>, ValidatorClientError> {
|
||||
Ok(self.nym_api.get_active_mixnodes().await?)
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
pub async fn get_cached_rewarded_mixnodes(
|
||||
&self,
|
||||
) -> Result<Vec<MixNodeDetails>, ValidatorClientError> {
|
||||
Ok(self.nym_api.get_rewarded_mixnodes().await?)
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
pub async fn get_cached_mixnodes(&self) -> Result<Vec<MixNodeDetails>, ValidatorClientError> {
|
||||
Ok(self.nym_api.get_mixnodes().await?)
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
pub async fn get_cached_gateways(&self) -> Result<Vec<GatewayBond>, ValidatorClientError> {
|
||||
Ok(self.nym_api.get_gateways().await?)
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
pub async fn get_cached_described_gateways(
|
||||
&self,
|
||||
) -> Result<Vec<LegacyDescribedGateway>, ValidatorClientError> {
|
||||
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)
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
pub async fn get_gateway_core_status_count(
|
||||
&self,
|
||||
identity: IdentityKeyRef<'_>,
|
||||
@@ -415,6 +583,7 @@ impl NymApiClient {
|
||||
.await?)
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
pub async fn get_mixnode_core_status_count(
|
||||
&self,
|
||||
mix_id: NodeId,
|
||||
@@ -426,6 +595,7 @@ impl NymApiClient {
|
||||
.await?)
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
pub async fn get_mixnode_status(
|
||||
&self,
|
||||
mix_id: NodeId,
|
||||
@@ -433,6 +603,7 @@ impl NymApiClient {
|
||||
Ok(self.nym_api.get_mixnode_status(mix_id).await?)
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
pub async fn get_mixnode_reward_estimation(
|
||||
&self,
|
||||
mix_id: NodeId,
|
||||
@@ -440,6 +611,7 @@ impl NymApiClient {
|
||||
Ok(self.nym_api.get_mixnode_reward_estimation(mix_id).await?)
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
pub async fn get_mixnode_stake_saturation(
|
||||
&self,
|
||||
mix_id: NodeId,
|
||||
@@ -471,6 +643,7 @@ impl NymApiClient {
|
||||
.await?)
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
pub async fn spent_credentials_filter(
|
||||
&self,
|
||||
) -> Result<SpentCredentialsResponse, ValidatorClientError> {
|
||||
|
||||
@@ -164,7 +164,7 @@ async fn test_nym_api_connection(
|
||||
) -> ConnectionResult {
|
||||
let result = match timeout(
|
||||
Duration::from_secs(CONNECTION_TEST_TIMEOUT_SEC),
|
||||
client.get_cached_mixnodes(),
|
||||
client.health(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
|
||||
@@ -11,9 +11,11 @@ use nym_api_requests::ecash::models::{
|
||||
};
|
||||
use nym_api_requests::ecash::VerificationKeyResponse;
|
||||
use nym_api_requests::models::{
|
||||
AnnotationResponse, LegacyDescribedMixNode, NodePerformanceResponse,
|
||||
AnnotationResponse, ApiHealthResponse, 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 +40,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;
|
||||
use tracing::instrument;
|
||||
@@ -53,12 +55,26 @@ pub fn rfc_3339_date() -> Vec<BorrowedFormatItem<'static>> {
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||
pub trait NymApiClientExt: ApiClient {
|
||||
async fn health(&self) -> Result<ApiHealthResponse, NymAPIError> {
|
||||
self.get_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::API_STATUS_ROUTES,
|
||||
routes::HEALTH,
|
||||
],
|
||||
NO_PARAMS,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_mixnodes(&self) -> Result<Vec<MixNodeDetails>, NymAPIError> {
|
||||
self.get_json(&[routes::API_VERSION, routes::MIXNODES], NO_PARAMS)
|
||||
.await
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_mixnodes_detailed(&self) -> Result<Vec<MixNodeBondAnnotated>, NymAPIError> {
|
||||
self.get_json(
|
||||
@@ -73,6 +89,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
.await
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_gateways_detailed(&self) -> Result<Vec<GatewayBondAnnotated>, NymAPIError> {
|
||||
self.get_json(
|
||||
@@ -87,6 +104,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
.await
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_mixnodes_detailed_unfiltered(
|
||||
&self,
|
||||
@@ -103,12 +121,14 @@ pub trait NymApiClientExt: ApiClient {
|
||||
.await
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_gateways(&self) -> Result<Vec<GatewayBond>, NymAPIError> {
|
||||
self.get_json(&[routes::API_VERSION, routes::GATEWAYS], NO_PARAMS)
|
||||
.await
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_gateways_described(&self) -> Result<Vec<LegacyDescribedGateway>, NymAPIError> {
|
||||
self.get_json(
|
||||
@@ -118,6 +138,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
.await
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_mixnodes_described(&self) -> Result<Vec<LegacyDescribedMixNode>, NymAPIError> {
|
||||
self.get_json(
|
||||
@@ -127,6 +148,47 @@ pub trait NymApiClientExt: ApiClient {
|
||||
.await
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
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
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
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
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
async fn get_basic_mixnodes(
|
||||
&self,
|
||||
@@ -151,6 +213,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
.await
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_basic_gateways(
|
||||
&self,
|
||||
@@ -178,7 +241,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
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_all_basic_entry_assigned_nodes(
|
||||
async fn get_basic_entry_assigned_nodes(
|
||||
&self,
|
||||
semver_compatibility: Option<String>,
|
||||
no_legacy: bool,
|
||||
@@ -259,6 +322,82 @@ pub trait NymApiClientExt: ApiClient {
|
||||
.await
|
||||
}
|
||||
|
||||
/// retrieve basic information for nodes that got assigned 'mixing' node in this epoch
|
||||
/// this includes legacy mixnodes and nym-nodes
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_basic_mixing_capable_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",
|
||||
"mixnodes",
|
||||
"all",
|
||||
],
|
||||
¶ms,
|
||||
)
|
||||
.await
|
||||
}
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_active_mixnodes(&self) -> Result<Vec<MixNodeDetails>, NymAPIError> {
|
||||
self.get_json(
|
||||
@@ -268,6 +407,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
.await
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_active_mixnodes_detailed(&self) -> Result<Vec<MixNodeBondAnnotated>, NymAPIError> {
|
||||
self.get_json(
|
||||
@@ -283,6 +423,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
.await
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_rewarded_mixnodes(&self) -> Result<Vec<MixNodeDetails>, NymAPIError> {
|
||||
self.get_json(
|
||||
@@ -292,6 +433,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
.await
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_mixnode_report(
|
||||
&self,
|
||||
@@ -310,6 +452,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
.await
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_gateway_report(
|
||||
&self,
|
||||
@@ -328,6 +471,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
.await
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_mixnode_history(
|
||||
&self,
|
||||
@@ -346,6 +490,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
.await
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_gateway_history(
|
||||
&self,
|
||||
@@ -364,6 +509,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
.await
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_rewarded_mixnodes_detailed(
|
||||
&self,
|
||||
@@ -381,6 +527,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
.await
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_gateway_core_status_count(
|
||||
&self,
|
||||
@@ -413,6 +560,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
}
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_mixnode_core_status_count(
|
||||
&self,
|
||||
@@ -446,6 +594,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
}
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_mixnode_status(
|
||||
&self,
|
||||
@@ -464,6 +613,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
.await
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_mixnode_reward_estimation(
|
||||
&self,
|
||||
@@ -482,6 +632,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
.await
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn compute_mixnode_reward_estimation(
|
||||
&self,
|
||||
@@ -502,6 +653,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
.await
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_mixnode_stake_saturation(
|
||||
&self,
|
||||
@@ -520,6 +672,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
.await
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_mixnode_inclusion_probability(
|
||||
&self,
|
||||
@@ -543,18 +696,35 @@ pub trait NymApiClientExt: ApiClient {
|
||||
&self,
|
||||
node_id: NodeId,
|
||||
) -> Result<NodePerformanceResponse, NymAPIError> {
|
||||
self.get_json_from(format!("/v1/nym-nodes/performance/{node_id}"))
|
||||
.await
|
||||
self.get_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
"nym-nodes",
|
||||
"performance",
|
||||
&node_id.to_string(),
|
||||
],
|
||||
NO_PARAMS,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn get_node_annotation(
|
||||
&self,
|
||||
node_id: NodeId,
|
||||
) -> Result<AnnotationResponse, NymAPIError> {
|
||||
self.get_json_from(format!("/v1/nym-nodes/annotation/{node_id}"))
|
||||
.await
|
||||
self.get_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
"nym-nodes",
|
||||
"annotation",
|
||||
&node_id.to_string(),
|
||||
],
|
||||
NO_PARAMS,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
async fn get_mixnode_avg_uptime(&self, mix_id: NodeId) -> Result<UptimeResponse, NymAPIError> {
|
||||
self.get_json(
|
||||
&[
|
||||
@@ -569,6 +739,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
.await
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_mixnodes_blacklisted(&self) -> Result<Vec<NodeId>, NymAPIError> {
|
||||
self.get_json(
|
||||
@@ -578,6 +749,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
.await
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_gateways_blacklisted(&self) -> Result<Vec<IdentityKey>, NymAPIError> {
|
||||
self.get_json(
|
||||
@@ -638,6 +810,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
.await
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn double_spending_filter_v1(&self) -> Result<SpentCredentialsResponse, NymAPIError> {
|
||||
self.get_json(
|
||||
|
||||
@@ -36,6 +36,8 @@ pub mod ecash {
|
||||
}
|
||||
|
||||
pub const STATUS_ROUTES: &str = "status";
|
||||
pub const API_STATUS_ROUTES: &str = "api-status";
|
||||
pub const HEALTH: &str = "health";
|
||||
pub const MIXNODE: &str = "mixnode";
|
||||
pub const GATEWAY: &str = "gateway";
|
||||
pub const NYM_NODES: &str = "nym-nodes";
|
||||
|
||||
@@ -10,10 +10,10 @@ use cosmrs::AccountId;
|
||||
use nym_contracts_common::signing::Nonce;
|
||||
use nym_mixnet_contract_common::gateway::{PreassignedGatewayIdsResponse, PreassignedId};
|
||||
use nym_mixnet_contract_common::nym_node::{
|
||||
EpochAssignmentResponse, NodeDetailsByIdentityResponse, NodeOwnershipResponse,
|
||||
NodeRewardingDetailsResponse, PagedNymNodeBondsResponse, PagedNymNodeDetailsResponse,
|
||||
PagedUnbondedNymNodesResponse, Role, RolesMetadataResponse, StakeSaturationResponse,
|
||||
UnbondedNodeResponse, UnbondedNymNode,
|
||||
EpochAssignmentResponse, NodeDetailsByIdentityResponse, NodeDetailsResponse,
|
||||
NodeOwnershipResponse, NodeRewardingDetailsResponse, PagedNymNodeBondsResponse,
|
||||
PagedNymNodeDetailsResponse, PagedUnbondedNymNodesResponse, Role, RolesMetadataResponse,
|
||||
StakeSaturationResponse, UnbondedNodeResponse, UnbondedNymNode,
|
||||
};
|
||||
use nym_mixnet_contract_common::reward_params::WorkFactor;
|
||||
use nym_mixnet_contract_common::{
|
||||
@@ -316,10 +316,7 @@ pub trait MixnetQueryClient {
|
||||
.await
|
||||
}
|
||||
|
||||
async fn get_nymnode_details(
|
||||
&self,
|
||||
node_id: NodeId,
|
||||
) -> Result<NodeOwnershipResponse, NyxdError> {
|
||||
async fn get_nymnode_details(&self, node_id: NodeId) -> Result<NodeDetailsResponse, NyxdError> {
|
||||
self.query_mixnet_contract(MixnetQueryMsg::GetNymNodeDetails { node_id })
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -2,10 +2,9 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::context::QueryClientWithNyxd;
|
||||
use crate::utils::{pretty_cosmwasm_coin, show_error};
|
||||
use crate::utils::show_error;
|
||||
use clap::Parser;
|
||||
use comfy_table::Table;
|
||||
use nym_validator_client::client::NymApiClientExt;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
@@ -15,12 +14,11 @@ pub struct Args {
|
||||
}
|
||||
|
||||
pub async fn query(args: Args, client: &QueryClientWithNyxd) {
|
||||
match client.nym_api.get_gateways().await {
|
||||
match client.get_all_cached_described_nodes().await {
|
||||
Ok(res) => match args.identity_key {
|
||||
Some(identity_key) => {
|
||||
let node = res.iter().find(|node| {
|
||||
node.gateway
|
||||
.identity_key
|
||||
node.ed25519_identity_key()
|
||||
.to_string()
|
||||
.eq_ignore_ascii_case(&identity_key)
|
||||
});
|
||||
@@ -32,14 +30,16 @@ pub async fn query(args: Args, client: &QueryClientWithNyxd) {
|
||||
None => {
|
||||
let mut table = Table::new();
|
||||
|
||||
table.set_header(vec!["Identity Key", "Owner", "Host", "Bond", "Version"]);
|
||||
for node in res {
|
||||
table.set_header(vec!["Node Id", "Identity Key", "Version", "Is Legacy"]);
|
||||
for node in res
|
||||
.into_iter()
|
||||
.filter(|node| node.description.declared_role.entry)
|
||||
{
|
||||
table.add_row(vec![
|
||||
node.gateway.identity_key.to_string(),
|
||||
node.owner.to_string(),
|
||||
node.gateway.host.to_string(),
|
||||
pretty_cosmwasm_coin(&node.pledge_amount),
|
||||
node.gateway.version.clone(),
|
||||
node.node_id.to_string(),
|
||||
node.ed25519_identity_key().to_base58_string(),
|
||||
node.description.build_information.build_version,
|
||||
(!node.contract_node_type.is_nym_node()).to_string(),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,10 +2,9 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::context::QueryClientWithNyxd;
|
||||
use crate::utils::{pretty_decimal_with_denom, show_error};
|
||||
use crate::utils::show_error;
|
||||
use clap::Parser;
|
||||
use comfy_table::Table;
|
||||
use nym_validator_client::client::NymApiClientExt;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
@@ -15,13 +14,11 @@ pub struct Args {
|
||||
}
|
||||
|
||||
pub async fn query(args: Args, client: &QueryClientWithNyxd) {
|
||||
match client.nym_api.get_mixnodes().await {
|
||||
match client.get_all_cached_described_nodes().await {
|
||||
Ok(res) => match args.identity_key {
|
||||
Some(identity_key) => {
|
||||
let node = res.iter().find(|node| {
|
||||
node.bond_information
|
||||
.mix_node
|
||||
.identity_key
|
||||
node.ed25519_identity_key()
|
||||
.to_string()
|
||||
.eq_ignore_ascii_case(&identity_key)
|
||||
});
|
||||
@@ -33,25 +30,16 @@ pub async fn query(args: Args, client: &QueryClientWithNyxd) {
|
||||
None => {
|
||||
let mut table = Table::new();
|
||||
|
||||
table.set_header(vec![
|
||||
"Mix id",
|
||||
"Identity Key",
|
||||
"Owner",
|
||||
"Host",
|
||||
"Bond",
|
||||
"Total Delegations",
|
||||
"Version",
|
||||
]);
|
||||
for node in res {
|
||||
let denom = &node.bond_information.original_pledge().denom;
|
||||
table.set_header(vec!["Node Id", "Identity Key", "Version", "Is Legacy"]);
|
||||
for node in res
|
||||
.into_iter()
|
||||
.filter(|node| node.description.declared_role.mixnode)
|
||||
{
|
||||
table.add_row(vec![
|
||||
node.mix_id().to_string(),
|
||||
node.bond_information.mix_node.identity_key.clone(),
|
||||
node.bond_information.owner.clone().into_string(),
|
||||
node.bond_information.mix_node.host.clone(),
|
||||
pretty_decimal_with_denom(node.rewarding_details.operator, denom),
|
||||
pretty_decimal_with_denom(node.rewarding_details.delegates, denom),
|
||||
node.bond_information.mix_node.version,
|
||||
node.node_id.to_string(),
|
||||
node.ed25519_identity_key().to_base58_string(),
|
||||
node.description.build_information.build_version,
|
||||
(!node.contract_node_type.is_nym_node()).to_string(),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -6,7 +6,7 @@ use std::sync::Arc;
|
||||
use time::{Date, OffsetDateTime};
|
||||
use tracing::*;
|
||||
|
||||
use nym_credentials::ecash::utils::{ecash_today, EcashTime};
|
||||
use nym_credentials::ecash::utils::{cred_exp_date, ecash_today, EcashTime};
|
||||
use nym_credentials_interface::{Bandwidth, ClientTicket, TicketType};
|
||||
use nym_gateway_requests::models::CredentialSpendingRequest;
|
||||
use nym_gateway_storage::Storage;
|
||||
@@ -131,7 +131,7 @@ impl<S: Storage + Clone + 'static> CredentialVerifier<S> {
|
||||
let bandwidth = Bandwidth::ticket_amount(credential_type.into());
|
||||
|
||||
self.bandwidth_storage_manager
|
||||
.increase_bandwidth(bandwidth, spend_date)
|
||||
.increase_bandwidth(bandwidth, cred_exp_date())
|
||||
.await?;
|
||||
|
||||
Ok(self
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
[package]
|
||||
name = "nym-gateway-stats-storage"
|
||||
version = "0.1.0"
|
||||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
homepage.workspace = true
|
||||
documentation.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
sqlx = { workspace = true, features = [
|
||||
"runtime-tokio-rustls",
|
||||
"sqlite",
|
||||
"macros",
|
||||
"migrate",
|
||||
"time",
|
||||
] }
|
||||
time = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
|
||||
nym-sphinx = { path = "../nymsphinx" }
|
||||
nym-credentials-interface = { path = "../credentials-interface" }
|
||||
|
||||
|
||||
[build-dependencies]
|
||||
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
|
||||
sqlx = { workspace = true, features = [
|
||||
"runtime-tokio-rustls",
|
||||
"sqlite",
|
||||
"macros",
|
||||
"migrate",
|
||||
] }
|
||||
@@ -1,28 +0,0 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use sqlx::{Connection, SqliteConnection};
|
||||
use std::env;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let out_dir = env::var("OUT_DIR").unwrap();
|
||||
let database_path = format!("{}/gateway-stats-example.sqlite", out_dir);
|
||||
|
||||
let mut conn = SqliteConnection::connect(&format!("sqlite://{}?mode=rwc", database_path))
|
||||
.await
|
||||
.expect("Failed to create SQLx database connection");
|
||||
|
||||
sqlx::migrate!("./migrations")
|
||||
.run(&mut conn)
|
||||
.await
|
||||
.expect("Failed to perform SQLx migrations");
|
||||
|
||||
#[cfg(target_family = "unix")]
|
||||
println!("cargo:rustc-env=DATABASE_URL=sqlite://{}", &database_path);
|
||||
|
||||
#[cfg(target_family = "windows")]
|
||||
// for some strange reason we need to add a leading `/` to the windows path even though it's
|
||||
// not a valid windows path... but hey, it works...
|
||||
println!("cargo:rustc-env=DATABASE_URL=sqlite:///{}", &database_path);
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
/*
|
||||
* Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
|
||||
CREATE TABLE sessions_active
|
||||
(
|
||||
client_address TEXT NOT NULL PRIMARY KEY UNIQUE,
|
||||
start_time TIMESTAMP WITHOUT TIME ZONE NOT NULL,
|
||||
typ TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE sessions_finished
|
||||
(
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
day DATE NOT NULL,
|
||||
duration_ms INTEGER NOT NULL,
|
||||
typ TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE sessions_unique_users
|
||||
(
|
||||
day DATE NOT NULL,
|
||||
client_address TEXT NOT NULL,
|
||||
PRIMARY KEY (day, client_address)
|
||||
);
|
||||
@@ -1,13 +0,0 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum StatsStorageError {
|
||||
#[error("Database experienced an internal error: {0}")]
|
||||
InternalDatabaseError(#[from] sqlx::Error),
|
||||
|
||||
#[error("Failed to perform database migration: {0}")]
|
||||
MigrationError(#[from] sqlx::migrate::MigrateError),
|
||||
}
|
||||
@@ -1,195 +0,0 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use error::StatsStorageError;
|
||||
use models::{ActiveSession, FinishedSession, SessionType, StoredFinishedSession};
|
||||
use nym_sphinx::DestinationAddressBytes;
|
||||
use sessions::SessionManager;
|
||||
use sqlx::ConnectOptions;
|
||||
use std::path::Path;
|
||||
use time::Date;
|
||||
use tracing::{debug, error};
|
||||
|
||||
pub mod error;
|
||||
pub mod models;
|
||||
mod sessions;
|
||||
|
||||
// note that clone here is fine as upon cloning the same underlying pool will be used
|
||||
#[derive(Clone)]
|
||||
pub struct PersistentStatsStorage {
|
||||
session_manager: SessionManager,
|
||||
}
|
||||
|
||||
impl PersistentStatsStorage {
|
||||
/// Initialises `PersistentStatsStorage` using the provided path.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `database_path`: path to the database.
|
||||
pub async fn init<P: AsRef<Path> + Send>(database_path: P) -> Result<Self, StatsStorageError> {
|
||||
debug!(
|
||||
"Attempting to connect to database {:?}",
|
||||
database_path.as_ref().as_os_str()
|
||||
);
|
||||
|
||||
// TODO: we can inject here more stuff based on our gateway global config
|
||||
// struct. Maybe different pool size or timeout intervals?
|
||||
let opts = sqlx::sqlite::SqliteConnectOptions::new()
|
||||
.filename(database_path)
|
||||
.create_if_missing(true)
|
||||
.disable_statement_logging();
|
||||
|
||||
// TODO: do we want auto_vacuum ?
|
||||
|
||||
let connection_pool = match sqlx::SqlitePool::connect_with(opts).await {
|
||||
Ok(db) => db,
|
||||
Err(err) => {
|
||||
error!("Failed to connect to SQLx database: {err}");
|
||||
return Err(err.into());
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(err) = sqlx::migrate!("./migrations").run(&connection_pool).await {
|
||||
error!("Failed to perform migration on the SQLx database: {err}");
|
||||
return Err(err.into());
|
||||
}
|
||||
|
||||
// the cloning here are cheap as connection pool is stored behind an Arc
|
||||
Ok(PersistentStatsStorage {
|
||||
session_manager: sessions::SessionManager::new(connection_pool),
|
||||
})
|
||||
}
|
||||
|
||||
//Sessions fn
|
||||
pub async fn insert_finished_session(
|
||||
&self,
|
||||
date: Date,
|
||||
session: FinishedSession,
|
||||
) -> Result<(), StatsStorageError> {
|
||||
Ok(self
|
||||
.session_manager
|
||||
.insert_finished_session(
|
||||
date,
|
||||
session.duration.whole_milliseconds() as i64,
|
||||
session.typ.to_string().into(),
|
||||
)
|
||||
.await?)
|
||||
}
|
||||
|
||||
pub async fn get_finished_sessions(
|
||||
&self,
|
||||
date: Date,
|
||||
) -> Result<Vec<StoredFinishedSession>, StatsStorageError> {
|
||||
Ok(self.session_manager.get_finished_sessions(date).await?)
|
||||
}
|
||||
|
||||
pub async fn delete_finished_sessions(
|
||||
&self,
|
||||
before_date: Date,
|
||||
) -> Result<(), StatsStorageError> {
|
||||
Ok(self
|
||||
.session_manager
|
||||
.delete_finished_sessions(before_date)
|
||||
.await?)
|
||||
}
|
||||
|
||||
pub async fn insert_unique_user(
|
||||
&self,
|
||||
date: Date,
|
||||
client_address_bs58: String,
|
||||
) -> Result<(), StatsStorageError> {
|
||||
Ok(self
|
||||
.session_manager
|
||||
.insert_unique_user(date, client_address_bs58)
|
||||
.await?)
|
||||
}
|
||||
|
||||
pub async fn get_unique_users_count(&self, date: Date) -> Result<i32, StatsStorageError> {
|
||||
Ok(self.session_manager.get_unique_users_count(date).await?)
|
||||
}
|
||||
|
||||
pub async fn delete_unique_users(&self, before_date: Date) -> Result<(), StatsStorageError> {
|
||||
Ok(self
|
||||
.session_manager
|
||||
.delete_unique_users(before_date)
|
||||
.await?)
|
||||
}
|
||||
|
||||
pub async fn insert_active_session(
|
||||
&self,
|
||||
client_address: DestinationAddressBytes,
|
||||
session: ActiveSession,
|
||||
) -> Result<(), StatsStorageError> {
|
||||
Ok(self
|
||||
.session_manager
|
||||
.insert_active_session(
|
||||
client_address.as_base58_string(),
|
||||
session.start,
|
||||
session.typ.to_string().into(),
|
||||
)
|
||||
.await?)
|
||||
}
|
||||
|
||||
pub async fn update_active_session_type(
|
||||
&self,
|
||||
client_address: DestinationAddressBytes,
|
||||
session_type: SessionType,
|
||||
) -> Result<(), StatsStorageError> {
|
||||
Ok(self
|
||||
.session_manager
|
||||
.update_active_session_type(
|
||||
client_address.as_base58_string(),
|
||||
session_type.to_string().into(),
|
||||
)
|
||||
.await?)
|
||||
}
|
||||
|
||||
pub async fn get_active_session(
|
||||
&self,
|
||||
client_address: DestinationAddressBytes,
|
||||
) -> Result<Option<ActiveSession>, StatsStorageError> {
|
||||
Ok(self
|
||||
.session_manager
|
||||
.get_active_session(client_address.as_base58_string())
|
||||
.await?
|
||||
.map(Into::into))
|
||||
}
|
||||
|
||||
pub async fn get_all_active_sessions(&self) -> Result<Vec<ActiveSession>, StatsStorageError> {
|
||||
Ok(self
|
||||
.session_manager
|
||||
.get_all_active_sessions()
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.collect())
|
||||
}
|
||||
|
||||
pub async fn get_started_sessions_count(
|
||||
&self,
|
||||
start_date: Date,
|
||||
) -> Result<i32, StatsStorageError> {
|
||||
Ok(self
|
||||
.session_manager
|
||||
.get_started_sessions_count(start_date)
|
||||
.await?)
|
||||
}
|
||||
|
||||
pub async fn get_active_users(&self) -> Result<Vec<String>, StatsStorageError> {
|
||||
Ok(self.session_manager.get_active_users().await?)
|
||||
}
|
||||
|
||||
pub async fn delete_active_session(
|
||||
&self,
|
||||
client_address: DestinationAddressBytes,
|
||||
) -> Result<(), StatsStorageError> {
|
||||
Ok(self
|
||||
.session_manager
|
||||
.delete_active_session(client_address.as_base58_string())
|
||||
.await?)
|
||||
}
|
||||
|
||||
pub async fn cleanup_active_sessions(&self) -> Result<(), StatsStorageError> {
|
||||
Ok(self.session_manager.cleanup_active_sessions().await?)
|
||||
}
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use nym_credentials_interface::TicketType;
|
||||
use sqlx::prelude::FromRow;
|
||||
use time::{Duration, OffsetDateTime};
|
||||
|
||||
#[derive(FromRow)]
|
||||
pub struct StoredFinishedSession {
|
||||
duration_ms: i64,
|
||||
typ: String,
|
||||
}
|
||||
|
||||
impl StoredFinishedSession {
|
||||
pub fn serialize(&self) -> (u64, String) {
|
||||
(
|
||||
self.duration_ms as u64, //we are sure that it fits in a u64, see `fn end_at`
|
||||
self.typ.clone(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FinishedSession {
|
||||
pub duration: Duration,
|
||||
pub typ: SessionType,
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
pub enum SessionType {
|
||||
Vpn,
|
||||
Mixnet,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl SessionType {
|
||||
pub fn to_string(&self) -> &str {
|
||||
match self {
|
||||
Self::Vpn => "vpn",
|
||||
Self::Mixnet => "mixnet",
|
||||
Self::Unknown => "unknown",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_string(s: &str) -> Self {
|
||||
match s {
|
||||
"vpn" => Self::Vpn,
|
||||
"mixnet" => Self::Mixnet,
|
||||
_ => Self::Unknown,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TicketType> for SessionType {
|
||||
fn from(value: TicketType) -> Self {
|
||||
match value {
|
||||
TicketType::V1MixnetEntry => Self::Mixnet,
|
||||
TicketType::V1MixnetExit => Self::Mixnet,
|
||||
TicketType::V1WireguardEntry => Self::Vpn,
|
||||
TicketType::V1WireguardExit => Self::Vpn,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(FromRow)]
|
||||
pub(crate) struct StoredActiveSession {
|
||||
start_time: OffsetDateTime,
|
||||
typ: String,
|
||||
}
|
||||
|
||||
pub struct ActiveSession {
|
||||
pub start: OffsetDateTime,
|
||||
pub typ: SessionType,
|
||||
}
|
||||
|
||||
impl ActiveSession {
|
||||
pub fn new(start_time: OffsetDateTime) -> Self {
|
||||
ActiveSession {
|
||||
start: start_time,
|
||||
typ: SessionType::Unknown,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_type(&mut self, ticket_type: TicketType) {
|
||||
self.typ = ticket_type.into();
|
||||
}
|
||||
|
||||
pub fn end_at(self, stop_time: OffsetDateTime) -> Option<FinishedSession> {
|
||||
let session_duration = stop_time - self.start;
|
||||
//ensure duration is positive to fit in a u64
|
||||
//u64::max milliseconds is 500k millenia so no overflow issue
|
||||
if session_duration > Duration::ZERO {
|
||||
Some(FinishedSession {
|
||||
duration: session_duration,
|
||||
typ: self.typ,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<StoredActiveSession> for ActiveSession {
|
||||
fn from(value: StoredActiveSession) -> Self {
|
||||
ActiveSession {
|
||||
start: value.start_time,
|
||||
typ: SessionType::from_string(&value.typ),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,177 +0,0 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use time::{Date, OffsetDateTime};
|
||||
|
||||
use crate::models::{StoredActiveSession, StoredFinishedSession};
|
||||
|
||||
pub(crate) type Result<T> = std::result::Result<T, sqlx::Error>;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct SessionManager {
|
||||
connection_pool: sqlx::SqlitePool,
|
||||
}
|
||||
|
||||
impl SessionManager {
|
||||
/// Creates new instance of the `SessionsManager` with the provided sqlite connection pool.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `connection_pool`: database connection pool to use.
|
||||
pub(crate) fn new(connection_pool: sqlx::SqlitePool) -> Self {
|
||||
SessionManager { connection_pool }
|
||||
}
|
||||
|
||||
pub(crate) async fn insert_finished_session(
|
||||
&self,
|
||||
date: Date,
|
||||
duration_ms: i64,
|
||||
typ: String,
|
||||
) -> Result<()> {
|
||||
sqlx::query!(
|
||||
"INSERT INTO sessions_finished (day, duration_ms, typ) VALUES (?, ?, ?)",
|
||||
date,
|
||||
duration_ms,
|
||||
typ
|
||||
)
|
||||
.execute(&self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn get_finished_sessions(
|
||||
&self,
|
||||
date: Date,
|
||||
) -> Result<Vec<StoredFinishedSession>> {
|
||||
sqlx::query_as("SELECT duration_ms, typ FROM sessions_finished WHERE day = ?")
|
||||
.bind(date)
|
||||
.fetch_all(&self.connection_pool)
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) async fn delete_finished_sessions(&self, before_date: Date) -> Result<()> {
|
||||
sqlx::query!("DELETE FROM sessions_finished WHERE day <= ? ", before_date)
|
||||
.execute(&self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn insert_unique_user(
|
||||
&self,
|
||||
date: Date,
|
||||
client_address_b58: String,
|
||||
) -> Result<()> {
|
||||
sqlx::query!(
|
||||
"INSERT OR IGNORE INTO sessions_unique_users (day, client_address) VALUES (?, ?)",
|
||||
date,
|
||||
client_address_b58,
|
||||
)
|
||||
.execute(&self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn get_unique_users_count(&self, date: Date) -> Result<i32> {
|
||||
Ok(sqlx::query!(
|
||||
"SELECT COUNT(*) as count FROM sessions_unique_users WHERE day = ?",
|
||||
date
|
||||
)
|
||||
.fetch_one(&self.connection_pool)
|
||||
.await?
|
||||
.count)
|
||||
}
|
||||
|
||||
pub(crate) async fn delete_unique_users(&self, before_date: Date) -> Result<()> {
|
||||
sqlx::query!(
|
||||
"DELETE FROM sessions_unique_users WHERE day <= ? ",
|
||||
before_date
|
||||
)
|
||||
.execute(&self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn insert_active_session(
|
||||
&self,
|
||||
client_address_b58: String,
|
||||
start_time: OffsetDateTime,
|
||||
typ: String,
|
||||
) -> Result<()> {
|
||||
sqlx::query!(
|
||||
"INSERT INTO sessions_active (client_address, start_time, typ) VALUES (?, ?, ?)",
|
||||
client_address_b58,
|
||||
start_time,
|
||||
typ
|
||||
)
|
||||
.execute(&self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn update_active_session_type(
|
||||
&self,
|
||||
client_address_b58: String,
|
||||
typ: String,
|
||||
) -> Result<()> {
|
||||
sqlx::query!(
|
||||
"UPDATE sessions_active SET typ = ? WHERE client_address = ?",
|
||||
typ,
|
||||
client_address_b58,
|
||||
)
|
||||
.execute(&self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn get_active_session(
|
||||
&self,
|
||||
client_address_b58: String,
|
||||
) -> Result<Option<StoredActiveSession>> {
|
||||
sqlx::query_as("SELECT start_time, typ FROM sessions_active WHERE client_address = ?")
|
||||
.bind(client_address_b58)
|
||||
.fetch_optional(&self.connection_pool)
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) async fn get_all_active_sessions(&self) -> Result<Vec<StoredActiveSession>> {
|
||||
sqlx::query_as("SELECT start_time, typ FROM sessions_active")
|
||||
.fetch_all(&self.connection_pool)
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) async fn get_started_sessions_count(&self, start_date: Date) -> Result<i32> {
|
||||
Ok(sqlx::query!(
|
||||
"SELECT COUNT(*) as count FROM sessions_active WHERE date(start_time) = ?",
|
||||
start_date
|
||||
)
|
||||
.fetch_one(&self.connection_pool)
|
||||
.await?
|
||||
.count)
|
||||
}
|
||||
|
||||
pub(crate) async fn get_active_users(&self) -> Result<Vec<String>> {
|
||||
Ok(sqlx::query!("SELECT client_address from sessions_active")
|
||||
.fetch_all(&self.connection_pool)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|record| record.client_address)
|
||||
.collect())
|
||||
}
|
||||
|
||||
pub(crate) async fn delete_active_session(&self, client_address_b58: String) -> Result<()> {
|
||||
sqlx::query!(
|
||||
"DELETE FROM sessions_active WHERE client_address = ?",
|
||||
client_address_b58
|
||||
)
|
||||
.execute(&self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn cleanup_active_sessions(&self) -> Result<()> {
|
||||
sqlx::query!("DELETE FROM sessions_active")
|
||||
.execute(&self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -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]
|
||||
@@ -312,6 +315,7 @@ impl Client {
|
||||
parse_response(res, true).await
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all)]
|
||||
pub async fn get_json_endpoint<T, S, E>(&self, endpoint: S) -> Result<T, HttpClientError<E>>
|
||||
where
|
||||
for<'a> T: Deserialize<'a>,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -14,7 +14,6 @@ use nym_task::TaskClient;
|
||||
use rand::seq::SliceRandom;
|
||||
use rand::thread_rng;
|
||||
use std::net::SocketAddr;
|
||||
use std::net::ToSocketAddrs;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tokio::task::JoinHandle;
|
||||
@@ -313,7 +312,7 @@ impl VerlocMeasurer {
|
||||
info!("Starting verloc measurements");
|
||||
// TODO: should we also measure gateways?
|
||||
|
||||
let all_mixes = match self.validator_client.get_cached_mixnodes().await {
|
||||
let all_mixes = match self.validator_client.get_all_described_nodes().await {
|
||||
Ok(nodes) => nodes,
|
||||
Err(err) => {
|
||||
error!(
|
||||
@@ -332,22 +331,14 @@ impl VerlocMeasurer {
|
||||
// we only care about address and identity
|
||||
let tested_nodes = all_mixes
|
||||
.into_iter()
|
||||
.filter(|n| n.description.declared_role.mixnode)
|
||||
.filter_map(|node| {
|
||||
let mix_node = node.bond_information.mix_node;
|
||||
// check if the node has sufficient version to be able to understand the packets
|
||||
let node_version = parse_version(&mix_node.version).ok()?;
|
||||
if node_version < self.config.minimum_compatible_node_version {
|
||||
return None;
|
||||
}
|
||||
|
||||
// try to parse the identity and host
|
||||
let node_identity =
|
||||
identity::PublicKey::from_base58_string(mix_node.identity_key).ok()?;
|
||||
let node_identity = node.ed25519_identity_key();
|
||||
|
||||
let verloc_host = (&*mix_node.host, mix_node.verloc_port)
|
||||
.to_socket_addrs()
|
||||
.ok()?
|
||||
.next()?;
|
||||
let ip = node.description.host_information.ip_address.first()?;
|
||||
let verloc_port = node.description.verloc_port();
|
||||
let verloc_host = SocketAddr::new(*ip, verloc_port);
|
||||
|
||||
// TODO: possible problem in the future, this does name resolution and theoretically
|
||||
// if a lot of nodes maliciously mis-configured themselves, it might take a while to resolve them all
|
||||
|
||||
@@ -2,7 +2,6 @@ use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct TestrunAssignment {
|
||||
/// has nothing to do with GW identity key. This is PK from `gateways` table
|
||||
pub testrun_id: i64,
|
||||
pub gateway_pk_id: i64,
|
||||
pub gateway_identity_key: String,
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ nym-sphinx-types = { path = "../types" }
|
||||
nym-topology = { path = "../../topology" }
|
||||
|
||||
[target."cfg(target_arch = \"wasm32\")".dependencies.wasm-bindgen]
|
||||
version = "0.2.95"
|
||||
version = "0.2.93"
|
||||
|
||||
[dev-dependencies]
|
||||
rand_chacha = { workspace = true }
|
||||
|
||||
@@ -42,32 +42,8 @@ impl PendingSync {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BlockProcessorConfig {
|
||||
pub pruning_options: PruningOptions,
|
||||
pub store_precommits: bool,
|
||||
}
|
||||
|
||||
impl Default for BlockProcessorConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
pruning_options: PruningOptions::nothing(),
|
||||
store_precommits: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BlockProcessorConfig {
|
||||
pub fn new(pruning_options: PruningOptions, store_precommits: bool) -> Self {
|
||||
Self {
|
||||
pruning_options,
|
||||
store_precommits,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct BlockProcessor {
|
||||
config: BlockProcessorConfig,
|
||||
pruning_options: PruningOptions,
|
||||
cancel: CancellationToken,
|
||||
synced: Arc<Notify>,
|
||||
last_processed_height: u32,
|
||||
@@ -89,10 +65,9 @@ pub struct BlockProcessor {
|
||||
msg_modules: Vec<Box<dyn MsgModule + Send>>,
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
impl BlockProcessor {
|
||||
pub async fn new(
|
||||
config: BlockProcessorConfig,
|
||||
pruning_options: PruningOptions,
|
||||
cancel: CancellationToken,
|
||||
synced: Arc<Notify>,
|
||||
incoming: UnboundedReceiver<BlockToProcess>,
|
||||
@@ -107,7 +82,7 @@ impl BlockProcessor {
|
||||
let last_pruned_height = last_pruned.try_into().unwrap_or_default();
|
||||
|
||||
Ok(BlockProcessor {
|
||||
config,
|
||||
pruning_options,
|
||||
cancel,
|
||||
synced,
|
||||
last_processed_height,
|
||||
@@ -126,7 +101,7 @@ impl BlockProcessor {
|
||||
}
|
||||
|
||||
pub fn with_pruning(mut self, pruning_options: PruningOptions) -> Self {
|
||||
self.config.pruning_options = pruning_options;
|
||||
self.pruning_options = pruning_options;
|
||||
self
|
||||
}
|
||||
|
||||
@@ -153,7 +128,7 @@ impl BlockProcessor {
|
||||
// we won't end up with a corrupted storage.
|
||||
let mut tx = self.storage.begin_processing_tx().await?;
|
||||
|
||||
persist_block(&full_info, &mut tx, self.config.store_precommits).await?;
|
||||
persist_block(&full_info, &mut tx).await?;
|
||||
|
||||
// let the modules do whatever they want
|
||||
// the ones wanting the full block:
|
||||
@@ -266,7 +241,7 @@ impl BlockProcessor {
|
||||
|
||||
#[instrument(skip(self))]
|
||||
async fn prune_storage(&mut self) -> Result<(), ScraperError> {
|
||||
let keep_recent = self.config.pruning_options.strategy_keep_recent();
|
||||
let keep_recent = self.pruning_options.strategy_keep_recent();
|
||||
let last_to_keep = self.last_processed_height - keep_recent;
|
||||
|
||||
info!(
|
||||
@@ -307,12 +282,12 @@ impl BlockProcessor {
|
||||
async fn maybe_prune_storage(&mut self) -> Result<(), ScraperError> {
|
||||
debug!("checking for storage pruning");
|
||||
|
||||
if self.config.pruning_options.strategy.is_nothing() {
|
||||
if self.pruning_options.strategy.is_nothing() {
|
||||
trace!("the current pruning strategy is 'nothing'");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let interval = self.config.pruning_options.strategy_interval();
|
||||
let interval = self.pruning_options.strategy_interval();
|
||||
if self.last_pruned_height + interval <= self.last_processed_height {
|
||||
self.prune_storage().await?;
|
||||
}
|
||||
@@ -396,7 +371,7 @@ impl BlockProcessor {
|
||||
if latest_block > self.last_processed_height && self.last_processed_height != 0 {
|
||||
// in case we were offline for a while,
|
||||
// make sure we don't request blocks we'd have to prune anyway
|
||||
let keep_recent = self.config.pruning_options.strategy_keep_recent();
|
||||
let keep_recent = self.pruning_options.strategy_keep_recent();
|
||||
let last_to_keep = latest_block - keep_recent;
|
||||
self.last_processed_height = max(self.last_processed_height, last_to_keep);
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::block_processor::types::BlockToProcess;
|
||||
use crate::block_processor::{BlockProcessor, BlockProcessorConfig};
|
||||
use crate::block_processor::BlockProcessor;
|
||||
use crate::block_requester::{BlockRequest, BlockRequester};
|
||||
use crate::error::ScraperError;
|
||||
use crate::modules::{BlockModule, MsgModule, TxModule};
|
||||
@@ -34,8 +34,6 @@ pub struct Config {
|
||||
pub database_path: PathBuf,
|
||||
|
||||
pub pruning_options: PruningOptions,
|
||||
|
||||
pub store_precommits: bool,
|
||||
}
|
||||
|
||||
pub struct NyxdScraperBuilder {
|
||||
@@ -62,14 +60,8 @@ impl NyxdScraperBuilder {
|
||||
req_rx,
|
||||
processing_tx.clone(),
|
||||
);
|
||||
|
||||
let block_processor_config = BlockProcessorConfig::new(
|
||||
scraper.config.pruning_options,
|
||||
scraper.config.store_precommits,
|
||||
);
|
||||
|
||||
let mut block_processor = BlockProcessor::new(
|
||||
block_processor_config,
|
||||
scraper.config.pruning_options,
|
||||
scraper.cancel_token.clone(),
|
||||
scraper.startup_sync.clone(),
|
||||
processing_rx,
|
||||
@@ -283,11 +275,8 @@ impl NyxdScraper {
|
||||
req_tx: Sender<BlockRequest>,
|
||||
processing_rx: UnboundedReceiver<BlockToProcess>,
|
||||
) -> Result<BlockProcessor, ScraperError> {
|
||||
let block_processor_config =
|
||||
BlockProcessorConfig::new(self.config.pruning_options, self.config.store_precommits);
|
||||
|
||||
BlockProcessor::new(
|
||||
block_processor_config,
|
||||
self.config.pruning_options,
|
||||
self.cancel_token.clone(),
|
||||
self.startup_sync.clone(),
|
||||
processing_rx,
|
||||
|
||||
@@ -212,7 +212,6 @@ impl ScraperStorage {
|
||||
pub async fn persist_block(
|
||||
block: &FullBlockInformation,
|
||||
tx: &mut StorageTransaction,
|
||||
store_precommits: bool,
|
||||
) -> Result<(), ScraperError> {
|
||||
let total_gas = crate::helpers::tx_gas_sum(&block.transactions);
|
||||
|
||||
@@ -225,12 +224,11 @@ pub async fn persist_block(
|
||||
// persist block data
|
||||
persist_block_data(&block.block, total_gas, tx).await?;
|
||||
|
||||
if store_precommits {
|
||||
if let Some(commit) = &block.block.last_commit {
|
||||
persist_commits(commit, &block.validators, tx).await?;
|
||||
} else {
|
||||
warn!("no commits for block {}", block.block.header.height)
|
||||
}
|
||||
// persist commits
|
||||
if let Some(commit) = &block.block.last_commit {
|
||||
persist_commits(commit, &block.validators, tx).await?;
|
||||
} else {
|
||||
warn!("no commits for block {}", block.block.header.height)
|
||||
}
|
||||
|
||||
// persist txs
|
||||
|
||||
@@ -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)?,
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::deprecated::DelegationEvent;
|
||||
use crate::error::TypesError;
|
||||
use crate::mixnode::NodeCostParams;
|
||||
use cosmwasm_std::Decimal;
|
||||
use nym_mixnet_contract_common::{Delegation as MixnetContractDelegation, NodeId};
|
||||
use nym_mixnet_contract_common::{Delegation as MixnetContractDelegation, NodeId, NodeRewarding};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -70,6 +70,14 @@ pub struct DelegationWithEverything {
|
||||
pub mixnode_is_unbonding: Option<bool>,
|
||||
}
|
||||
|
||||
pub struct NodeInformation {
|
||||
pub owner: String,
|
||||
pub mix_id: NodeId,
|
||||
pub node_identity: String,
|
||||
pub rewarding_details: NodeRewarding,
|
||||
pub is_unbonding: bool,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
feature = "generate-ts",
|
||||
|
||||
@@ -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?;
|
||||
|
||||
|
||||
@@ -99,12 +99,25 @@ pub async fn start_wireguard<St: nym_gateway_storage::Storage + Clone + 'static>
|
||||
let peers = all_peers
|
||||
.into_iter()
|
||||
.map(Peer::try_from)
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
.collect::<Result<Vec<_>, _>>()?
|
||||
.into_iter()
|
||||
.map(|mut peer| {
|
||||
// since WGApi doesn't set those values on init, let's set them to 0
|
||||
peer.rx_bytes = 0;
|
||||
peer.tx_bytes = 0;
|
||||
peer
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
for peer in peers.iter() {
|
||||
let bandwidth_manager =
|
||||
PeerController::generate_bandwidth_manager(storage.clone(), &peer.public_key)
|
||||
.await?
|
||||
.map(|bw_m| Arc::new(RwLock::new(bw_m)));
|
||||
// Update storage with *x_bytes set to 0, as in kernel peers we can't set those values
|
||||
// so we need to restart counting. Hopefully the bandwidth was counted in available_bandwidth
|
||||
storage
|
||||
.insert_wireguard_peer(peer, bandwidth_manager.is_some())
|
||||
.await?;
|
||||
peer_bandwidth_managers.insert(peer.public_key.clone(), bandwidth_manager);
|
||||
}
|
||||
wg_api.create_interface()?;
|
||||
|
||||
@@ -158,10 +158,13 @@ impl<St: Storage + Clone + 'static> PeerController<St> {
|
||||
.ok_or(Error::MissingClientBandwidthEntry)?
|
||||
.client_id
|
||||
{
|
||||
storage.create_bandwidth_entry(client_id).await?;
|
||||
let bandwidth = storage
|
||||
.get_available_bandwidth(client_id)
|
||||
.await?
|
||||
.ok_or(Error::MissingClientBandwidthEntry)?;
|
||||
Ok(Some(BandwidthStorageManager::new(
|
||||
storage,
|
||||
ClientBandwidth::new(Default::default()),
|
||||
ClientBandwidth::new(bandwidth.into()),
|
||||
client_id,
|
||||
BandwidthFlushingBehaviourConfig::default(),
|
||||
true,
|
||||
|
||||
@@ -6,7 +6,7 @@ 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;
|
||||
use nym_authenticator_requests::latest::registration::BANDWIDTH_CAP_PER_DAY;
|
||||
use nym_credential_verification::bandwidth_storage_manager::BandwidthStorageManager;
|
||||
use nym_gateway_storage::models::WireguardPeer;
|
||||
use nym_gateway_storage::Storage;
|
||||
@@ -18,7 +18,7 @@ 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
|
||||
const AUTO_REMOVE_AFTER: Duration = Duration::from_secs(60 * 60 * 24 * 30); // 30 days
|
||||
|
||||
pub struct PeerHandle<St> {
|
||||
storage: St,
|
||||
@@ -75,8 +75,8 @@ impl<St: Storage + Clone + 'static> PeerHandle<St> {
|
||||
|
||||
async fn active_peer(
|
||||
&mut self,
|
||||
storage_peer: WireguardPeer,
|
||||
kernel_peer: Peer,
|
||||
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)
|
||||
@@ -84,12 +84,13 @@ impl<St: Storage + Clone + 'static> PeerHandle<St> {
|
||||
.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_peer().await?;
|
||||
return Ok(!success);
|
||||
@@ -97,7 +98,7 @@ impl<St: Storage + Clone + 'static> PeerHandle<St> {
|
||||
} else {
|
||||
if SystemTime::now().duration_since(self.startup_timestamp)? >= AUTO_REMOVE_AFTER {
|
||||
log::debug!(
|
||||
"Peer {} has been present for 24 hours, removing it",
|
||||
"Peer {} has been present for 30 days, removing it",
|
||||
self.public_key.to_string()
|
||||
);
|
||||
let success = self.remove_peer().await?;
|
||||
@@ -135,9 +136,12 @@ impl<St: Storage + Clone + 'static> PeerHandle<St> {
|
||||
log::debug!("Peer {:?} not in storage anymore, shutting down handle", self.public_key);
|
||||
return Ok(());
|
||||
};
|
||||
if !self.active_peer(storage_peer, kernel_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(());
|
||||
} else {
|
||||
// Update storage values
|
||||
self.storage.insert_wireguard_peer(&kernel_peer, self.bandwidth_storage_manager.is_some()).await?;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,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";
|
||||
|
||||
@@ -96,6 +98,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;
|
||||
|
||||
@@ -8,6 +8,9 @@ use nym_contracts_common::truncate_decimal;
|
||||
use nym_mixnet_contract_common::NodeId;
|
||||
use nym_validator_client::client::NymApiClientExt;
|
||||
|
||||
// use deprecated method as hopefully this whole API will be sunset soon-enough...
|
||||
// and we're only getting info for legacy node so the relevant data should still exist
|
||||
#[allow(deprecated)]
|
||||
pub(crate) async fn retrieve_mixnode_econ_stats(
|
||||
client: &ThreadsafeValidatorClient,
|
||||
mix_id: NodeId,
|
||||
|
||||
@@ -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,22 +1,24 @@
|
||||
// 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,
|
||||
shutdown: TaskClient,
|
||||
}
|
||||
|
||||
// allow usage of deprecated methods here as we actually want to be explicitly querying for legacy data
|
||||
#[allow(deprecated)]
|
||||
impl ExplorerApiTasks {
|
||||
pub(crate) fn new(state: ExplorerApiStateContext, shutdown: TaskClient) -> Self {
|
||||
ExplorerApiTasks { state, shutdown }
|
||||
@@ -39,6 +41,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 +154,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 +194,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");
|
||||
|
||||
+2
-1
@@ -69,7 +69,6 @@ nym-credentials-interface = { path = "../common/credentials-interface" }
|
||||
nym-credential-verification = { path = "../common/credential-verification" }
|
||||
nym-crypto = { path = "../common/crypto" }
|
||||
nym-gateway-storage = { path = "../common/gateway-storage" }
|
||||
nym-gateway-stats-storage = { path = "../common/gateway-stats-storage" }
|
||||
nym-gateway-requests = { path = "../common/gateway-requests" }
|
||||
nym-mixnet-client = { path = "../common/client-libs/mixnet-client" }
|
||||
nym-mixnode-common = { path = "../common/mixnode-common" }
|
||||
@@ -77,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" }
|
||||
|
||||
@@ -12,7 +12,6 @@ pub const DEFAULT_PRIVATE_SPHINX_KEY_FILENAME: &str = "private_sphinx.pem";
|
||||
pub const DEFAULT_PUBLIC_SPHINX_KEY_FILENAME: &str = "public_sphinx.pem";
|
||||
|
||||
pub const DEFAULT_CLIENTS_STORAGE_FILENAME: &str = "db.sqlite";
|
||||
pub const DEFAULT_STATS_STORAGE_FILENAME: &str = "stats.sqlite";
|
||||
|
||||
pub const DEFAULT_NETWORK_REQUESTER_CONFIG_FILENAME: &str = "network_requester_config.toml";
|
||||
pub const DEFAULT_NETWORK_REQUESTER_DATA_DIR: &str = "network-requester-data";
|
||||
@@ -40,9 +39,6 @@ pub struct GatewayPaths {
|
||||
#[serde(alias = "persistent_storage")]
|
||||
pub clients_storage: PathBuf,
|
||||
|
||||
/// Path to sqlite database containing all persistent stats data.
|
||||
pub stats_storage: PathBuf,
|
||||
|
||||
/// Path to the configuration of the embedded network requester.
|
||||
#[serde(deserialize_with = "de_maybe_stringified")]
|
||||
pub network_requester_config: Option<PathBuf>,
|
||||
@@ -58,9 +54,7 @@ impl GatewayPaths {
|
||||
pub fn new_default<P: AsRef<Path>>(id: P) -> Self {
|
||||
GatewayPaths {
|
||||
keys: KeysPaths::new_default(id.as_ref()),
|
||||
clients_storage: default_data_directory(id.as_ref())
|
||||
.join(DEFAULT_CLIENTS_STORAGE_FILENAME),
|
||||
stats_storage: default_data_directory(id).join(DEFAULT_STATS_STORAGE_FILENAME),
|
||||
clients_storage: default_data_directory(id).join(DEFAULT_CLIENTS_STORAGE_FILENAME),
|
||||
// node_description: default_config_filepath(id).join(DEFAULT_DESCRIPTION_FILENAME),
|
||||
network_requester_config: None,
|
||||
ip_packet_router_config: None,
|
||||
@@ -76,7 +70,6 @@ impl GatewayPaths {
|
||||
public_sphinx_key_file: Default::default(),
|
||||
},
|
||||
clients_storage: Default::default(),
|
||||
stats_storage: Default::default(),
|
||||
network_requester_config: None,
|
||||
ip_packet_router_config: None,
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use nym_authenticator::error::AuthenticatorError;
|
||||
use nym_gateway_stats_storage::error::StatsStorageError;
|
||||
use nym_gateway_storage::error::StorageError;
|
||||
use nym_ip_packet_router::error::IpPacketRouterError;
|
||||
use nym_network_requester::error::{ClientCoreError, NetworkRequesterError};
|
||||
@@ -116,12 +115,6 @@ pub enum GatewayError {
|
||||
source: StorageError,
|
||||
},
|
||||
|
||||
#[error("stats storage failure: {source}")]
|
||||
StatsStorageError {
|
||||
#[from]
|
||||
source: StatsStorageError,
|
||||
},
|
||||
|
||||
#[error("Path to network requester configuration file hasn't been specified. Perhaps try to run `setup-network-requester`?")]
|
||||
UnspecifiedNetworkRequesterConfig,
|
||||
|
||||
|
||||
+60
-11
@@ -3,14 +3,18 @@
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::error::GatewayError;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use nym_crypto::asymmetric::encryption;
|
||||
use nym_gateway_stats_storage::PersistentStatsStorage;
|
||||
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,
|
||||
@@ -75,14 +79,6 @@ pub(crate) async fn initialise_main_storage(
|
||||
Ok(PersistentStorage::init(path, retrieval_limit).await?)
|
||||
}
|
||||
|
||||
pub(crate) async fn initialise_stats_storage(
|
||||
config: &Config,
|
||||
) -> Result<PersistentStatsStorage, GatewayError> {
|
||||
let path = &config.storage_paths.stats_storage;
|
||||
|
||||
Ok(PersistentStatsStorage::init(path).await?)
|
||||
}
|
||||
|
||||
pub fn load_keypair<T: PemStorableKeyPair>(
|
||||
paths: KeyPairPath,
|
||||
name: impl Into<String>,
|
||||
@@ -102,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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+67
-28
@@ -13,10 +13,11 @@ 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, initialise_stats_storage, load_network_requester_config,
|
||||
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,
|
||||
};
|
||||
@@ -27,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::*;
|
||||
@@ -43,7 +46,6 @@ pub(crate) mod helpers;
|
||||
pub(crate) mod mixnet_handling;
|
||||
pub(crate) mod statistics;
|
||||
|
||||
pub use nym_gateway_stats_storage::PersistentStatsStorage;
|
||||
pub use nym_gateway_storage::{PersistentStorage, Storage};
|
||||
|
||||
// TODO: should this struct live here?
|
||||
@@ -99,8 +101,6 @@ pub async fn create_gateway(
|
||||
|
||||
let storage = initialise_main_storage(&config).await?;
|
||||
|
||||
let stats_storage = initialise_stats_storage(&config).await?;
|
||||
|
||||
let nr_opts = network_requester_config.map(|config| LocalNetworkRequesterOpts {
|
||||
config: config.clone(),
|
||||
custom_mixnet_path: custom_mixnet.clone(),
|
||||
@@ -111,7 +111,7 @@ pub async fn create_gateway(
|
||||
custom_mixnet_path: custom_mixnet.clone(),
|
||||
});
|
||||
|
||||
Gateway::new(config, nr_opts, ip_opts, storage, stats_storage)
|
||||
Gateway::new(config, nr_opts, ip_opts, storage)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -152,9 +152,7 @@ pub struct Gateway<St = PersistentStorage> {
|
||||
/// x25519 keypair used for Diffie-Hellman. Currently only used for sphinx key derivation.
|
||||
sphinx_keypair: Arc<encryption::KeyPair>,
|
||||
|
||||
client_storage: St,
|
||||
|
||||
stats_storage: PersistentStatsStorage,
|
||||
storage: St,
|
||||
|
||||
wireguard_data: Option<nym_wireguard::WireguardData>,
|
||||
|
||||
@@ -170,12 +168,10 @@ impl<St> Gateway<St> {
|
||||
config: Config,
|
||||
network_requester_opts: Option<LocalNetworkRequesterOpts>,
|
||||
ip_packet_router_opts: Option<LocalIpPacketRouterOpts>,
|
||||
client_storage: St,
|
||||
stats_storage: PersistentStatsStorage,
|
||||
storage: St,
|
||||
) -> Result<Self, GatewayError> {
|
||||
Ok(Gateway {
|
||||
client_storage,
|
||||
stats_storage,
|
||||
storage,
|
||||
identity_keypair: Arc::new(load_identity_keys(&config)?),
|
||||
sphinx_keypair: Arc::new(helpers::load_sphinx_keys(&config)?),
|
||||
config,
|
||||
@@ -188,7 +184,7 @@ impl<St> Gateway<St> {
|
||||
task_client: None,
|
||||
})
|
||||
}
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
|
||||
pub fn new_loaded(
|
||||
config: Config,
|
||||
network_requester_opts: Option<LocalNetworkRequesterOpts>,
|
||||
@@ -196,8 +192,7 @@ impl<St> Gateway<St> {
|
||||
authenticator_opts: Option<LocalAuthenticatorOpts>,
|
||||
identity_keypair: Arc<identity::KeyPair>,
|
||||
sphinx_keypair: Arc<encryption::KeyPair>,
|
||||
client_storage: St,
|
||||
stats_storage: PersistentStatsStorage,
|
||||
storage: St,
|
||||
) -> Self {
|
||||
Gateway {
|
||||
config,
|
||||
@@ -206,8 +201,7 @@ impl<St> Gateway<St> {
|
||||
authenticator_opts,
|
||||
identity_keypair,
|
||||
sphinx_keypair,
|
||||
client_storage,
|
||||
stats_storage,
|
||||
storage,
|
||||
wireguard_data: None,
|
||||
session_stats: None,
|
||||
run_http_server: true,
|
||||
@@ -236,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,
|
||||
@@ -251,7 +278,7 @@ impl<St> Gateway<St> {
|
||||
|
||||
let connection_handler = ConnectionHandler::new(
|
||||
packet_processor,
|
||||
self.client_storage.clone(),
|
||||
self.storage.clone(),
|
||||
ack_sender,
|
||||
active_clients_store,
|
||||
);
|
||||
@@ -268,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>>
|
||||
@@ -286,7 +314,7 @@ impl<St> Gateway<St> {
|
||||
forwarding_channel,
|
||||
router_tx,
|
||||
);
|
||||
let all_peers = self.client_storage.get_all_wireguard_peers().await?;
|
||||
let all_peers = self.storage.get_all_wireguard_peers().await?;
|
||||
let used_private_network_ips = all_peers
|
||||
.iter()
|
||||
.cloned()
|
||||
@@ -315,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 {
|
||||
@@ -341,7 +370,7 @@ impl<St> Gateway<St> {
|
||||
.start_with_shutdown(router_shutdown);
|
||||
|
||||
let wg_api = nym_wireguard::start_wireguard(
|
||||
self.client_storage.clone(),
|
||||
self.storage.clone(),
|
||||
all_peers,
|
||||
shutdown,
|
||||
wireguard_data,
|
||||
@@ -363,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>> {
|
||||
@@ -388,7 +418,7 @@ impl<St> Gateway<St> {
|
||||
|
||||
let shared_state = websocket::CommonHandlerState {
|
||||
ecash_verifier,
|
||||
storage: self.client_storage.clone(),
|
||||
storage: self.storage.clone(),
|
||||
local_identity: Arc::clone(&self.identity_keypair),
|
||||
only_coconut_credentials: self.config.gateway.only_coconut_credentials,
|
||||
bandwidth_cfg: (&self.config).into(),
|
||||
@@ -426,7 +456,7 @@ impl<St> Gateway<St> {
|
||||
info!("Starting gateway stats collector...");
|
||||
|
||||
let (mut stats_collector, stats_event_sender) =
|
||||
GatewayStatisticsCollector::new(shared_session_stats, self.stats_storage.clone());
|
||||
GatewayStatisticsCollector::new(shared_session_stats);
|
||||
tokio::spawn(async move { stats_collector.run(shutdown).await });
|
||||
stats_event_sender
|
||||
}
|
||||
@@ -435,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...");
|
||||
@@ -462,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 {
|
||||
@@ -499,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...");
|
||||
@@ -527,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 {
|
||||
@@ -588,7 +622,7 @@ impl<St> Gateway<St> {
|
||||
// TODO: if anything, this should be getting data directly from the contract
|
||||
// as opposed to the validator API
|
||||
let validator_client = self.random_api_client()?;
|
||||
let existing_nodes = match validator_client.get_cached_gateways().await {
|
||||
let existing_nodes = match validator_client.get_all_basic_nodes(None).await {
|
||||
Ok(nodes) => nodes,
|
||||
Err(err) => {
|
||||
error!("failed to grab initial network gateways - {err}\n Please try to startup again in few minutes");
|
||||
@@ -596,9 +630,9 @@ impl<St> Gateway<St> {
|
||||
}
|
||||
};
|
||||
|
||||
Ok(existing_nodes.iter().any(|node| {
|
||||
node.gateway.identity_key == self.identity_keypair.public_key().to_base58_string()
|
||||
}))
|
||||
Ok(existing_nodes
|
||||
.iter()
|
||||
.any(|node| &node.ed25519_identity_pubkey == self.identity_keypair.public_key()))
|
||||
}
|
||||
|
||||
pub async fn run(mut self) -> Result<(), GatewayError>
|
||||
@@ -643,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
|
||||
@@ -665,7 +701,7 @@ impl<St> Gateway<St> {
|
||||
nyxd_client,
|
||||
self.identity_keypair.public_key().to_bytes(),
|
||||
shutdown.fork("EcashVerifier"),
|
||||
self.client_storage.clone(),
|
||||
self.storage.clone(),
|
||||
)
|
||||
.await?,
|
||||
);
|
||||
@@ -691,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?;
|
||||
@@ -706,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?;
|
||||
@@ -718,6 +756,7 @@ impl<St> Gateway<St> {
|
||||
let embedded_auth = self
|
||||
.start_authenticator(
|
||||
mix_forwarding_channel,
|
||||
topology_provider,
|
||||
shutdown.fork("authenticator"),
|
||||
ecash_verifier,
|
||||
)
|
||||
|
||||
@@ -2,14 +2,13 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use futures::{channel::mpsc, StreamExt};
|
||||
use nym_gateway_stats_storage::PersistentStatsStorage;
|
||||
use nym_node_http_api::state::metrics::SharedSessionStats;
|
||||
use nym_statistics_common::events::{StatsEvent, StatsEventReceiver, StatsEventSender};
|
||||
use nym_task::TaskClient;
|
||||
use sessions::SessionStatsHandler;
|
||||
use std::time::Duration;
|
||||
use time::OffsetDateTime;
|
||||
use tracing::{error, trace, warn};
|
||||
use tracing::trace;
|
||||
|
||||
pub mod sessions;
|
||||
|
||||
@@ -24,38 +23,21 @@ pub(crate) struct GatewayStatisticsCollector {
|
||||
impl GatewayStatisticsCollector {
|
||||
pub fn new(
|
||||
shared_session_stats: SharedSessionStats,
|
||||
stats_storage: PersistentStatsStorage,
|
||||
) -> (GatewayStatisticsCollector, StatsEventSender) {
|
||||
let (stats_event_tx, stats_event_rx) = mpsc::unbounded();
|
||||
|
||||
let session_stats = SessionStatsHandler::new(shared_session_stats, stats_storage);
|
||||
let collector = GatewayStatisticsCollector {
|
||||
stats_event_rx,
|
||||
session_stats,
|
||||
session_stats: SessionStatsHandler::new(shared_session_stats),
|
||||
};
|
||||
(collector, stats_event_tx)
|
||||
}
|
||||
|
||||
async fn update_shared_state(&mut self, update_time: OffsetDateTime) {
|
||||
if let Err(e) = self
|
||||
.session_stats
|
||||
.maybe_update_shared_state(update_time)
|
||||
.await
|
||||
{
|
||||
error!("Failed to update session stats - {e}");
|
||||
}
|
||||
self.session_stats.update_shared_state(update_time).await;
|
||||
//here goes additionnal stats handler update
|
||||
}
|
||||
|
||||
async fn on_start(&mut self) {
|
||||
if let Err(e) = self.session_stats.on_start().await {
|
||||
error!("Failed to cleanup session stats handler - {e}");
|
||||
}
|
||||
//here goes additionnal stats handler start cleanup
|
||||
}
|
||||
|
||||
pub async fn run(&mut self, mut shutdown: TaskClient) {
|
||||
self.on_start().await;
|
||||
let mut update_interval = tokio::time::interval(STATISTICS_UPDATE_TIMER_INTERVAL);
|
||||
while !shutdown.is_shutdown() {
|
||||
tokio::select! {
|
||||
@@ -71,10 +53,7 @@ impl GatewayStatisticsCollector {
|
||||
Some(stat_event) = self.stats_event_rx.next() => {
|
||||
//dispatching event to proper handler
|
||||
match stat_event {
|
||||
StatsEvent::SessionStatsEvent(event) => {
|
||||
if let Err(e) = self.session_stats.handle_event(event).await{
|
||||
warn!("Session event handling error - {e}");
|
||||
}},
|
||||
StatsEvent::SessionStatsEvent(event) => self.session_stats.handle_event(event),
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -2,158 +2,176 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use nym_credentials_interface::TicketType;
|
||||
use nym_gateway_stats_storage::models::FinishedSession;
|
||||
use nym_gateway_stats_storage::PersistentStatsStorage;
|
||||
use nym_gateway_stats_storage::{error::StatsStorageError, models::ActiveSession};
|
||||
use nym_node_http_api::state::metrics::SharedSessionStats;
|
||||
use nym_sphinx::DestinationAddressBytes;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use time::{Date, Duration, OffsetDateTime};
|
||||
|
||||
use nym_statistics_common::events::SessionEvent;
|
||||
|
||||
const FINISHED_SESSIONS_CAP: usize = 1_000_000; //to be on the safe side of memory blowups until persistent storage
|
||||
|
||||
#[derive(PartialEq)]
|
||||
enum SessionType {
|
||||
Vpn,
|
||||
Mixnet,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl SessionType {
|
||||
fn to_string(&self) -> &str {
|
||||
match self {
|
||||
Self::Vpn => "vpn",
|
||||
Self::Mixnet => "mixnet",
|
||||
Self::Unknown => "unknown",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TicketType> for SessionType {
|
||||
fn from(value: TicketType) -> Self {
|
||||
match value {
|
||||
TicketType::V1MixnetEntry => Self::Mixnet,
|
||||
TicketType::V1MixnetExit => Self::Mixnet,
|
||||
TicketType::V1WireguardEntry => Self::Vpn,
|
||||
TicketType::V1WireguardExit => Self::Vpn,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct FinishedSession {
|
||||
duration: Duration,
|
||||
typ: SessionType,
|
||||
}
|
||||
|
||||
impl FinishedSession {
|
||||
fn serialize(&self) -> (u64, String) {
|
||||
(
|
||||
self.duration.whole_milliseconds() as u64, //we are sure that it fits in a u64, see `fn end_at`
|
||||
self.typ.to_string().into(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
struct ActiveSession {
|
||||
start: OffsetDateTime,
|
||||
typ: SessionType,
|
||||
}
|
||||
|
||||
impl ActiveSession {
|
||||
fn new(start_time: OffsetDateTime) -> Self {
|
||||
ActiveSession {
|
||||
start: start_time,
|
||||
typ: SessionType::Unknown,
|
||||
}
|
||||
}
|
||||
|
||||
fn set_type(&mut self, ticket_type: TicketType) {
|
||||
self.typ = ticket_type.into();
|
||||
}
|
||||
|
||||
fn end_at(self, stop_time: OffsetDateTime) -> Option<FinishedSession> {
|
||||
let session_duration = stop_time - self.start;
|
||||
//ensure duration is positive to fit in a u64
|
||||
//u64::max milliseconds is 500k millenia so no overflow issue
|
||||
if session_duration > Duration::ZERO {
|
||||
Some(FinishedSession {
|
||||
duration: session_duration,
|
||||
typ: self.typ,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct SessionStatsHandler {
|
||||
storage: PersistentStatsStorage,
|
||||
current_day: Date,
|
||||
last_update_day: Date,
|
||||
|
||||
shared_session_stats: SharedSessionStats,
|
||||
active_sessions: HashMap<DestinationAddressBytes, ActiveSession>,
|
||||
unique_users: HashSet<DestinationAddressBytes>,
|
||||
sessions_started: u32,
|
||||
finished_sessions: Vec<FinishedSession>,
|
||||
}
|
||||
|
||||
impl SessionStatsHandler {
|
||||
pub fn new(shared_session_stats: SharedSessionStats, storage: PersistentStatsStorage) -> Self {
|
||||
pub fn new(shared_session_stats: SharedSessionStats) -> Self {
|
||||
SessionStatsHandler {
|
||||
storage,
|
||||
current_day: OffsetDateTime::now_utc().date(),
|
||||
last_update_day: OffsetDateTime::now_utc().date(),
|
||||
shared_session_stats,
|
||||
active_sessions: Default::default(),
|
||||
unique_users: Default::default(),
|
||||
sessions_started: 0,
|
||||
finished_sessions: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn handle_event(
|
||||
&mut self,
|
||||
event: SessionEvent,
|
||||
) -> Result<(), StatsStorageError> {
|
||||
pub(crate) fn handle_event(&mut self, event: SessionEvent) {
|
||||
match event {
|
||||
SessionEvent::SessionStart { start_time, client } => {
|
||||
self.handle_session_start(start_time, client).await
|
||||
self.handle_session_start(start_time, client);
|
||||
}
|
||||
|
||||
SessionEvent::SessionStop { stop_time, client } => {
|
||||
self.handle_session_stop(stop_time, client).await
|
||||
self.handle_session_stop(stop_time, client);
|
||||
}
|
||||
|
||||
SessionEvent::EcashTicket {
|
||||
ticket_type,
|
||||
client,
|
||||
} => self.handle_ecash_ticket(ticket_type, client).await,
|
||||
} => self.handle_ecash_ticket(ticket_type, client),
|
||||
}
|
||||
}
|
||||
async fn handle_session_start(
|
||||
fn handle_session_start(
|
||||
&mut self,
|
||||
start_time: OffsetDateTime,
|
||||
client: DestinationAddressBytes,
|
||||
) -> Result<(), StatsStorageError> {
|
||||
self.storage
|
||||
.insert_unique_user(self.current_day, client.as_base58_string())
|
||||
.await?;
|
||||
self.storage
|
||||
.insert_active_session(client, ActiveSession::new(start_time))
|
||||
.await?;
|
||||
Ok(())
|
||||
) {
|
||||
self.sessions_started += 1;
|
||||
self.unique_users.insert(client);
|
||||
self.active_sessions
|
||||
.insert(client, ActiveSession::new(start_time));
|
||||
}
|
||||
|
||||
async fn handle_session_stop(
|
||||
&mut self,
|
||||
stop_time: OffsetDateTime,
|
||||
client: DestinationAddressBytes,
|
||||
) -> Result<(), StatsStorageError> {
|
||||
if let Some(session) = self.storage.get_active_session(client).await? {
|
||||
fn handle_session_stop(&mut self, stop_time: OffsetDateTime, client: DestinationAddressBytes) {
|
||||
if let Some(session) = self.active_sessions.remove(&client) {
|
||||
if let Some(finished_session) = session.end_at(stop_time) {
|
||||
self.storage
|
||||
.insert_finished_session(self.current_day, finished_session)
|
||||
.await?;
|
||||
self.storage.delete_active_session(client).await?;
|
||||
if self.finished_sessions.len() < FINISHED_SESSIONS_CAP {
|
||||
self.finished_sessions.push(finished_session);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn handle_ecash_ticket(
|
||||
&mut self,
|
||||
ticket_type: TicketType,
|
||||
client: DestinationAddressBytes,
|
||||
) -> Result<(), StatsStorageError> {
|
||||
self.storage
|
||||
.update_active_session_type(client, ticket_type.into())
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn on_start(&mut self) -> Result<(), StatsStorageError> {
|
||||
let yesterday = OffsetDateTime::now_utc().date() - Duration::DAY;
|
||||
//publish yesterday's data if any
|
||||
self.publish_stats(yesterday).await?;
|
||||
//store "active" sessions as duration 0
|
||||
for active_session in self.storage.get_all_active_sessions().await? {
|
||||
self.storage
|
||||
.insert_finished_session(
|
||||
self.current_day,
|
||||
FinishedSession {
|
||||
duration: Duration::ZERO,
|
||||
typ: active_session.typ,
|
||||
},
|
||||
)
|
||||
.await?
|
||||
fn handle_ecash_ticket(&mut self, ticket_type: TicketType, client: DestinationAddressBytes) {
|
||||
if let Some(active_session) = self.active_sessions.get_mut(&client) {
|
||||
if active_session.typ == SessionType::Unknown {
|
||||
active_session.set_type(ticket_type);
|
||||
}
|
||||
}
|
||||
//cleanup active sessions
|
||||
self.storage.cleanup_active_sessions().await?;
|
||||
|
||||
//delete old entries
|
||||
self.delete_old_stats(yesterday - Duration::DAY).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
//update shared state once a day has passed, with data from the previous day
|
||||
async fn publish_stats(&mut self, stats_date: Date) -> Result<(), StatsStorageError> {
|
||||
let finished_sessions = self.storage.get_finished_sessions(stats_date).await?;
|
||||
let user_count = self.storage.get_unique_users_count(stats_date).await?;
|
||||
let session_started = self.storage.get_started_sessions_count(stats_date).await? as u32;
|
||||
{
|
||||
let mut shared_state = self.shared_session_stats.write().await;
|
||||
shared_state.update_time = stats_date;
|
||||
shared_state.unique_active_users = user_count as u32;
|
||||
shared_state.session_started = session_started;
|
||||
shared_state.sessions = finished_sessions.iter().map(|s| s.serialize()).collect();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
pub(crate) async fn maybe_update_shared_state(
|
||||
&mut self,
|
||||
update_time: OffsetDateTime,
|
||||
) -> Result<(), StatsStorageError> {
|
||||
pub(crate) async fn update_shared_state(&mut self, update_time: OffsetDateTime) {
|
||||
let update_date = update_time.date();
|
||||
if update_date != self.current_day {
|
||||
self.publish_stats(self.current_day).await?;
|
||||
self.delete_old_stats(self.current_day - Duration::DAY)
|
||||
.await?;
|
||||
self.reset_stats(update_date).await?;
|
||||
self.current_day = update_date;
|
||||
if update_date != self.last_update_day {
|
||||
{
|
||||
let mut shared_state = self.shared_session_stats.write().await;
|
||||
shared_state.update_time = self.last_update_day;
|
||||
shared_state.unique_active_users = self.unique_users.len() as u32;
|
||||
shared_state.session_started = self.sessions_started;
|
||||
shared_state.sessions = self
|
||||
.finished_sessions
|
||||
.iter()
|
||||
.map(|s| s.serialize())
|
||||
.collect();
|
||||
}
|
||||
self.reset_stats(update_date);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn reset_stats(&mut self, reset_day: Date) -> Result<(), StatsStorageError> {
|
||||
//active users reset
|
||||
let new_active_users = self.storage.get_active_users().await?;
|
||||
for user in new_active_users {
|
||||
self.storage.insert_unique_user(reset_day, user).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn delete_old_stats(&mut self, delete_before: Date) -> Result<(), StatsStorageError> {
|
||||
self.storage.delete_finished_sessions(delete_before).await?;
|
||||
self.storage.delete_unique_users(delete_before).await?;
|
||||
Ok(())
|
||||
fn reset_stats(&mut self, reset_day: Date) {
|
||||
self.last_update_day = reset_day;
|
||||
self.unique_users = self.active_sessions.keys().copied().collect();
|
||||
self.finished_sessions = Default::default();
|
||||
self.sessions_started = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -234,7 +234,7 @@ impl MixNode {
|
||||
// TODO: if anything, this should be getting data directly from the contract
|
||||
// as opposed to the validator API
|
||||
let validator_client = self.random_api_client();
|
||||
let existing_nodes = match validator_client.get_cached_mixnodes().await {
|
||||
let existing_nodes = match validator_client.get_all_basic_nodes(None).await {
|
||||
Ok(nodes) => nodes,
|
||||
Err(err) => {
|
||||
error!(
|
||||
@@ -245,10 +245,9 @@ impl MixNode {
|
||||
}
|
||||
};
|
||||
|
||||
existing_nodes.iter().any(|node| {
|
||||
node.bond_information.mix_node.identity_key
|
||||
== self.identity_keypair.public_key().to_base58_string()
|
||||
})
|
||||
existing_nodes
|
||||
.iter()
|
||||
.any(|node| &node.ed25519_identity_pubkey == self.identity_keypair.public_key())
|
||||
}
|
||||
|
||||
async fn wait_for_interrupt(&self, shutdown: TaskHandle) {
|
||||
|
||||
@@ -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,
|
||||
@@ -851,6 +897,12 @@ pub enum DescribedNodeType {
|
||||
NymNode,
|
||||
}
|
||||
|
||||
impl DescribedNodeType {
|
||||
pub fn is_nym_node(&self) -> bool {
|
||||
matches!(self, DescribedNodeType::NymNode)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, schemars::JsonSchema, ToSchema)]
|
||||
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
@@ -935,14 +987,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>> {
|
||||
|
||||
@@ -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,
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Generated
+34
-5
@@ -214,6 +214,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",
|
||||
"forwarded-header-value",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "axum-core"
|
||||
version = "0.4.5"
|
||||
@@ -1311,6 +1322,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 = "futures"
|
||||
version = "0.3.31"
|
||||
@@ -2147,6 +2168,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 = "nu-ansi-term"
|
||||
version = "0.46.0"
|
||||
@@ -2315,7 +2342,7 @@ dependencies = [
|
||||
"digest 0.9.0",
|
||||
"ff",
|
||||
"group",
|
||||
"itertools 0.12.1",
|
||||
"itertools 0.13.0",
|
||||
"nym-network-defaults",
|
||||
"nym-pemstore",
|
||||
"rand",
|
||||
@@ -2355,7 +2382,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-credential-proxy"
|
||||
version = "0.1.0"
|
||||
version = "0.1.3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -2540,6 +2567,7 @@ name = "nym-http-api-common"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"axum",
|
||||
"axum-client-ip",
|
||||
"bytes",
|
||||
"colored",
|
||||
"mime",
|
||||
@@ -2663,7 +2691,6 @@ dependencies = [
|
||||
"flate2",
|
||||
"futures",
|
||||
"itertools 0.13.0",
|
||||
"log",
|
||||
"nym-api-requests",
|
||||
"nym-coconut-bandwidth-contract-common",
|
||||
"nym-coconut-dkg-common",
|
||||
@@ -2687,6 +2714,7 @@ dependencies = [
|
||||
"thiserror",
|
||||
"time",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"url",
|
||||
"wasmtimer",
|
||||
"zeroize",
|
||||
@@ -3763,9 +3791,9 @@ dependencies = [
|
||||
|
||||
[[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",
|
||||
"memchr",
|
||||
@@ -4889,6 +4917,7 @@ dependencies = [
|
||||
"quote",
|
||||
"regex",
|
||||
"syn 2.0.79",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -10,7 +10,7 @@ use schemars::schema::Schema;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use time::Date;
|
||||
use time::{Date, OffsetDateTime};
|
||||
|
||||
#[cfg(feature = "query-types")]
|
||||
use nym_http_api_common::Output;
|
||||
@@ -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,
|
||||
@@ -234,22 +236,28 @@ pub struct TicketbookWalletSharesAsyncResponse {
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)]
|
||||
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct BlindedWalletSharesResponse {
|
||||
pub struct WebhookTicketbookWalletShares {
|
||||
pub id: i64,
|
||||
pub status: String,
|
||||
pub device_id: String,
|
||||
pub credential_id: String,
|
||||
pub data: Option<TicketbookWalletSharesResponse>,
|
||||
pub error_message: Option<String>,
|
||||
pub created: String,
|
||||
pub updated: String,
|
||||
|
||||
#[schemars(with = "String")]
|
||||
#[serde(with = "time::serde::rfc3339")]
|
||||
pub created: OffsetDateTime,
|
||||
|
||||
#[schemars(with = "String")]
|
||||
#[serde(with = "time::serde::rfc3339")]
|
||||
pub updated: OffsetDateTime,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)]
|
||||
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct WebhookBlindedSharesResponse {
|
||||
pub blinded_shares: BlindedWalletSharesResponse,
|
||||
pub struct WebhookTicketbookWalletSharesRequest {
|
||||
pub ticketbook_wallet_shares: WebhookTicketbookWalletShares,
|
||||
pub secret: String,
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-credential-proxy"
|
||||
version = "0.1.1"
|
||||
version = "0.1.3"
|
||||
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
|
||||
);
|
||||
@@ -3,10 +3,12 @@
|
||||
|
||||
use crate::error::VpnApiError;
|
||||
use crate::http::state::ApiState;
|
||||
use crate::storage::models::BlindedShares;
|
||||
use futures::{stream, StreamExt};
|
||||
use nym_credential_proxy_requests::api::v1::ticketbook::models::{
|
||||
TicketbookAsyncRequest, TicketbookObtainQueryParams, TicketbookRequest,
|
||||
TicketbookWalletSharesResponse, WalletShare,
|
||||
TicketbookWalletSharesResponse, WalletShare, WebhookTicketbookWalletShares,
|
||||
WebhookTicketbookWalletSharesRequest,
|
||||
};
|
||||
use nym_credentials::IssuanceTicketBook;
|
||||
use nym_credentials_interface::Base58;
|
||||
@@ -217,11 +219,13 @@ async fn try_obtain_blinded_ticketbook_async_inner(
|
||||
requested_on: OffsetDateTime,
|
||||
request_data: TicketbookAsyncRequest,
|
||||
params: TicketbookObtainQueryParams,
|
||||
pending: &BlindedShares,
|
||||
) -> Result<(), VpnApiError> {
|
||||
let epoch_id = state.current_epoch_id().await?;
|
||||
|
||||
let device_id = &request_data.device_id;
|
||||
let credential_id = &request_data.credential_id;
|
||||
let secret = request_data.secret.clone();
|
||||
|
||||
// 1. try to obtain global data
|
||||
let (
|
||||
@@ -259,19 +263,70 @@ async fn try_obtain_blinded_ticketbook_async_inner(
|
||||
error!(uuid = %request, "failed to update db with issued information: {err}")
|
||||
}
|
||||
|
||||
// 4. build the response
|
||||
let response = TicketbookWalletSharesResponse {
|
||||
// 4. build the webhook request body
|
||||
let data = Some(TicketbookWalletSharesResponse {
|
||||
epoch_id,
|
||||
shares,
|
||||
master_verification_key,
|
||||
aggregated_coin_index_signatures,
|
||||
aggregated_expiration_date_signatures,
|
||||
});
|
||||
|
||||
let ticketbook_wallet_shares = WebhookTicketbookWalletShares {
|
||||
id: pending.id,
|
||||
status: pending.status.to_string(),
|
||||
device_id: device_id.clone(),
|
||||
credential_id: credential_id.clone(),
|
||||
data,
|
||||
error_message: None,
|
||||
created: pending.created,
|
||||
updated: pending.updated,
|
||||
};
|
||||
|
||||
let webhook_request = WebhookTicketbookWalletSharesRequest {
|
||||
ticketbook_wallet_shares,
|
||||
secret,
|
||||
};
|
||||
|
||||
// 5. call the webhook
|
||||
state
|
||||
.zk_nym_web_hook()
|
||||
.try_trigger(request, &response)
|
||||
.try_trigger(request, &webhook_request)
|
||||
.await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn try_trigger_webhook_request_for_error(
|
||||
state: &ApiState,
|
||||
request: Uuid,
|
||||
request_data: TicketbookAsyncRequest,
|
||||
pending: &BlindedShares,
|
||||
error_message: String,
|
||||
) -> Result<(), VpnApiError> {
|
||||
let device_id = &request_data.device_id;
|
||||
let credential_id = &request_data.credential_id;
|
||||
let secret = request_data.secret.clone();
|
||||
|
||||
let ticketbook_wallet_shares = WebhookTicketbookWalletShares {
|
||||
id: pending.id,
|
||||
status: "error".to_string(),
|
||||
device_id: device_id.clone(),
|
||||
credential_id: credential_id.clone(),
|
||||
data: None,
|
||||
error_message: Some(error_message),
|
||||
created: pending.created,
|
||||
updated: pending.updated,
|
||||
};
|
||||
|
||||
let webhook_request = WebhookTicketbookWalletSharesRequest {
|
||||
ticketbook_wallet_shares,
|
||||
secret,
|
||||
};
|
||||
|
||||
state
|
||||
.zk_nym_web_hook()
|
||||
.try_trigger(request, &webhook_request)
|
||||
.await;
|
||||
|
||||
Ok(())
|
||||
@@ -285,16 +340,30 @@ pub(crate) async fn try_obtain_blinded_ticketbook_async(
|
||||
requested_on: OffsetDateTime,
|
||||
request_data: TicketbookAsyncRequest,
|
||||
params: TicketbookObtainQueryParams,
|
||||
pending: BlindedShares,
|
||||
) {
|
||||
if let Err(err) = try_obtain_blinded_ticketbook_async_inner(
|
||||
&state,
|
||||
request,
|
||||
requested_on,
|
||||
request_data,
|
||||
request_data.clone(),
|
||||
params,
|
||||
&pending,
|
||||
)
|
||||
.await
|
||||
{
|
||||
// post to the webhook to notify of errors on this side
|
||||
if let Err(webhook_err) = try_trigger_webhook_request_for_error(
|
||||
&state,
|
||||
request,
|
||||
request_data,
|
||||
&pending,
|
||||
format!("Failed to get ticketbook: {err}"),
|
||||
)
|
||||
.await
|
||||
{
|
||||
error!(uuid = %request, "failed to make webhook request to report error: {webhook_err}")
|
||||
}
|
||||
error!(uuid = %request, "failed to resolve the blinded ticketbook issuance: {err}")
|
||||
} else {
|
||||
info!(uuid = %request, "managed to resolve the blinded ticketbook issuance")
|
||||
|
||||
@@ -84,8 +84,8 @@ pub(crate) struct ApiDoc;
|
||||
api_requests::v1::ticketbook::models::WalletShare,
|
||||
api_requests::v1::ticketbook::models::TicketbookWalletSharesResponse,
|
||||
api_requests::v1::ticketbook::models::TicketbookWalletSharesAsyncResponse,
|
||||
api_requests::v1::ticketbook::models::BlindedWalletSharesResponse,
|
||||
api_requests::v1::ticketbook::models::WebhookBlindedSharesResponse,
|
||||
api_requests::v1::ticketbook::models::WebhookTicketbookWalletShares,
|
||||
api_requests::v1::ticketbook::models::WebhookTicketbookWalletSharesRequest,
|
||||
api_requests::v1::ticketbook::models::TicketbookObtainQueryParams,
|
||||
api_requests::v1::ticketbook::models::SharesQueryParams,
|
||||
api_requests::v1::ticketbook::models::PlaceholderJsonSchemaImpl,
|
||||
|
||||
@@ -192,6 +192,7 @@ pub(crate) async fn obtain_ticketbook_shares_async(
|
||||
}
|
||||
Ok(pending) => pending,
|
||||
};
|
||||
let id = pending.id;
|
||||
|
||||
// 3. try to spawn a new task attempting to resolve the request
|
||||
if state
|
||||
@@ -201,6 +202,7 @@ pub(crate) async fn obtain_ticketbook_shares_async(
|
||||
requested_on,
|
||||
payload,
|
||||
params,
|
||||
pending,
|
||||
))
|
||||
.is_none()
|
||||
{
|
||||
@@ -213,10 +215,7 @@ pub(crate) async fn obtain_ticketbook_shares_async(
|
||||
}
|
||||
|
||||
// 4. in the meantime, return the id to the user
|
||||
Ok(output.to_response(TicketbookWalletSharesAsyncResponse {
|
||||
id: pending.id,
|
||||
uuid,
|
||||
}))
|
||||
Ok(output.to_response(TicketbookWalletSharesAsyncResponse { id, uuid }))
|
||||
}
|
||||
|
||||
/// Obtain the current value of the bandwidth voucher deposit
|
||||
|
||||
+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
|
||||
|
||||
@@ -5,18 +5,6 @@ WORKDIR /usr/src/nym/nym-data-observatory
|
||||
|
||||
RUN cargo build --release
|
||||
|
||||
#-------------------------------------------------------------------
|
||||
# The following environment variables are required at runtime:
|
||||
#
|
||||
# NYM_DATA_OBSERVATORY_CONNECTION_URL
|
||||
#
|
||||
# And optionally:
|
||||
#
|
||||
# NYM_DATA_OBSERVATORY_HTTP_PORT
|
||||
#
|
||||
# see https://github.com/nymtech/nym/blob/develop/nym-data-observatory/src/main.rs for details
|
||||
#-------------------------------------------------------------------
|
||||
|
||||
FROM ubuntu:24.04
|
||||
|
||||
RUN apt update && apt install -yy curl ca-certificates
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user