nym-api caching node self described information
This commit is contained in:
@@ -162,6 +162,7 @@ once_cell = "1.7.2"
|
||||
parking_lot = "0.12.1"
|
||||
rand = "0.8.5"
|
||||
reqwest = "0.11.18"
|
||||
schemars = "0.8.1"
|
||||
serde = "1.0.152"
|
||||
serde_json = "1.0.91"
|
||||
tap = "1.0.1"
|
||||
|
||||
@@ -148,7 +148,7 @@ impl Client<ReqwestRpcClient> {
|
||||
|
||||
impl<C> Client<C> {
|
||||
pub fn new_with_rpc_client(config: Config, rpc_client: C) -> Self {
|
||||
let nym_api_client = nym_api::Client::new(config.api_url.clone());
|
||||
let nym_api_client = nym_api::Client::new(config.api_url.clone(), None);
|
||||
|
||||
Client {
|
||||
nym_api: nym_api_client,
|
||||
@@ -162,7 +162,7 @@ impl<C, S> Client<C, S> {
|
||||
where
|
||||
S: OfflineSigner,
|
||||
{
|
||||
let nym_api_client = nym_api::Client::new(config.api_url.clone());
|
||||
let nym_api_client = nym_api::Client::new(config.api_url.clone(), None);
|
||||
|
||||
Client {
|
||||
nym_api: nym_api_client,
|
||||
@@ -242,7 +242,7 @@ pub struct NymApiClient {
|
||||
|
||||
impl NymApiClient {
|
||||
pub fn new(api_url: Url) -> Self {
|
||||
let nym_api = nym_api::Client::new(api_url);
|
||||
let nym_api = nym_api::Client::new(api_url, None);
|
||||
|
||||
NymApiClient { nym_api }
|
||||
}
|
||||
|
||||
@@ -17,3 +17,4 @@ url = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
@@ -6,9 +6,13 @@ use reqwest::{IntoUrl, Response, StatusCode};
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::Display;
|
||||
use std::time::Duration;
|
||||
use thiserror::Error;
|
||||
use tracing::warn;
|
||||
use url::Url;
|
||||
|
||||
pub const DEFAULT_TIMEOUT: Duration = Duration::from_secs(3);
|
||||
|
||||
pub type PathSegments<'a> = &'a [&'a str];
|
||||
pub type Params<'a, K, V> = &'a [(K, V)];
|
||||
|
||||
@@ -28,14 +32,20 @@ pub enum HttpClientError<E: Display = String> {
|
||||
source: url::ParseError,
|
||||
},
|
||||
|
||||
#[error("not found")]
|
||||
#[error("the requested resource could not be found")]
|
||||
NotFound,
|
||||
|
||||
#[error("request failed with error message: {0}")]
|
||||
GenericRequestFailure(String),
|
||||
|
||||
#[error("failed to resolve request. status code: {status}, additional error message: {error}")]
|
||||
EndpointFailure { status: u16, error: E },
|
||||
#[error("the request failed with status '{status}'. no additional error message provided")]
|
||||
RequestFailure { status: StatusCode },
|
||||
|
||||
#[error("the returned response was empty. status: '{status}'")]
|
||||
EmptyResponse { status: StatusCode },
|
||||
|
||||
#[error("failed to resolve request. status: '{status}', additional error message: {error}")]
|
||||
EndpointFailure { status: StatusCode, error: E },
|
||||
}
|
||||
|
||||
/// A simple extendable client wrapper for http request with extra url sanitization.
|
||||
@@ -46,19 +56,35 @@ pub struct Client {
|
||||
}
|
||||
|
||||
impl Client {
|
||||
pub fn new(base_url: Url) -> Self {
|
||||
pub fn new(base_url: Url, timeout: Option<Duration>) -> Self {
|
||||
Client {
|
||||
base_url,
|
||||
reqwest_client: reqwest::Client::new(),
|
||||
|
||||
// TODO: we should probably be propagating the error rather than panicking,
|
||||
// but that'd break bunch of things due to type changes
|
||||
reqwest_client: reqwest::ClientBuilder::new()
|
||||
.timeout(timeout.unwrap_or(DEFAULT_TIMEOUT))
|
||||
.build()
|
||||
.expect("Client::new()"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_url<U, E>(url: U) -> Result<Self, HttpClientError<E>>
|
||||
pub fn new_url<U, E>(url: U, timeout: Option<Duration>) -> Result<Self, HttpClientError<E>>
|
||||
where
|
||||
U: IntoUrl,
|
||||
E: Display,
|
||||
{
|
||||
Ok(Self::new(url.into_url()?))
|
||||
// a naive check: if the provided URL does not start with http(s), add that scheme
|
||||
let str_url = url.as_str();
|
||||
|
||||
if !str_url.starts_with("http") {
|
||||
let alt = format!("http://{str_url}");
|
||||
warn!("the provided url ('{str_url}') does not contain scheme information. Changing it to '{alt}' ...");
|
||||
// TODO: or should we maybe default to https?
|
||||
Self::new_url(alt, timeout)
|
||||
} else {
|
||||
Ok(Self::new(url.into_url()?, timeout))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn change_base_url(&mut self, new_url: Url) {
|
||||
@@ -111,7 +137,7 @@ impl Client {
|
||||
E: Display + DeserializeOwned,
|
||||
{
|
||||
let res = self.send_get_request(path, params).await?;
|
||||
parse_response(res).await
|
||||
parse_response(res, false).await
|
||||
}
|
||||
|
||||
pub async fn post_json<B, T, K, V, E>(
|
||||
@@ -128,7 +154,7 @@ impl Client {
|
||||
E: Display + DeserializeOwned,
|
||||
{
|
||||
let res = self.send_post_request(path, params, json_body).await?;
|
||||
parse_response(res).await
|
||||
parse_response(res, true).await
|
||||
}
|
||||
|
||||
pub async fn get_json_endpoint<T, S, E>(&self, endpoint: S) -> Result<T, HttpClientError<E>>
|
||||
@@ -142,7 +168,7 @@ impl Client {
|
||||
.get(self.base_url.join(endpoint.as_ref())?)
|
||||
.send()
|
||||
.await?;
|
||||
parse_response(res).await
|
||||
parse_response(res, false).await
|
||||
}
|
||||
|
||||
pub async fn post_json_endpoint<B, T, S, E>(
|
||||
@@ -162,7 +188,7 @@ impl Client {
|
||||
.json(json_body)
|
||||
.send()
|
||||
.await?;
|
||||
parse_response(res).await
|
||||
parse_response(res, true).await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -299,22 +325,31 @@ pub fn sanitize_url<K: AsRef<str>, V: AsRef<str>>(
|
||||
url
|
||||
}
|
||||
|
||||
async fn parse_response<T, E>(res: Response) -> Result<T, HttpClientError<E>>
|
||||
async fn parse_response<T, E>(res: Response, allow_empty: bool) -> Result<T, HttpClientError<E>>
|
||||
where
|
||||
T: DeserializeOwned,
|
||||
E: DeserializeOwned + Display,
|
||||
{
|
||||
let status = res.status();
|
||||
|
||||
if !allow_empty {
|
||||
if let Some(0) = res.content_length() {
|
||||
return Err(HttpClientError::EmptyResponse { status });
|
||||
}
|
||||
}
|
||||
|
||||
if res.status().is_success() {
|
||||
Ok(res.json().await?)
|
||||
} else if res.status() == StatusCode::NOT_FOUND {
|
||||
Err(HttpClientError::NotFound)
|
||||
} else {
|
||||
let plaintext = res.text().await?;
|
||||
let Ok(plaintext) = res.text().await else {
|
||||
return Err(HttpClientError::RequestFailure { status });
|
||||
};
|
||||
|
||||
if let Ok(request_error) = serde_json::from_str(&plaintext) {
|
||||
Err(HttpClientError::EndpointFailure {
|
||||
status: status.as_u16(),
|
||||
status,
|
||||
error: request_error,
|
||||
})
|
||||
} else {
|
||||
|
||||
@@ -12,7 +12,7 @@ cfg-if = { workspace = true }
|
||||
dotenvy = { workspace = true }
|
||||
hex-literal = "0.3.3"
|
||||
once_cell = { workspace = true }
|
||||
schemars = { version = "0.8", features = ["preserve_order"] }
|
||||
schemars = { workspace = true, features = ["preserve_order"] }
|
||||
serde = { workspace = true, features = ["derive"]}
|
||||
thiserror = { workspace = true }
|
||||
url = { workspace = true }
|
||||
|
||||
@@ -13,7 +13,7 @@ log = { workspace = true }
|
||||
pin-project = "1.0"
|
||||
rand = { version = "0.7.3", features = ["wasm-bindgen"] }
|
||||
reqwest = { workspace = true }
|
||||
schemars = { version = "0.8", features = ["preserve_order"] }
|
||||
schemars = { workspace = true, features = ["preserve_order"] }
|
||||
serde = { workspace = true, features = ["derive"] } # for config serialization/deserialization
|
||||
tap = "1.0.1"
|
||||
thiserror = { workspace = true }
|
||||
|
||||
@@ -24,7 +24,7 @@ reqwest = { workspace = true }
|
||||
rocket = { version = "0.5.0-rc.2", features = ["json"] }
|
||||
rocket_cors = { git="https://github.com/lawliet89/rocket_cors", rev="dfd3662c49e2f6fc37df35091cb94d82f7fb5915" }
|
||||
rocket_okapi = { version = "0.8.0-rc.2", features = ["swagger"] }
|
||||
schemars = { version = "0.8", features = ["preserve_order"] }
|
||||
schemars = { workspace = true, features = ["preserve_order"] }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
|
||||
@@ -7,6 +7,6 @@ edition = "2021"
|
||||
nym-contracts-common = { path = "../../common/cosmwasm-smart-contracts/contracts-common" }
|
||||
nym-mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-contract" }
|
||||
nym-api-requests = { path = "../../nym-api/nym-api-requests" }
|
||||
schemars = { version = "0.8", features = ["preserve_order"] }
|
||||
schemars = { workspace = true, features = ["preserve_order"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
ts-rs = { workspace = true, optional = true }
|
||||
|
||||
+5
-5
@@ -16,7 +16,7 @@ rust-version = "1.56"
|
||||
|
||||
[dependencies]
|
||||
async-trait = { workspace = true }
|
||||
bs58 = {version = "0.4.0" }
|
||||
bs58 = { version = "0.4.0" }
|
||||
bip39 = { workspace = true }
|
||||
cfg-if = "1.0"
|
||||
clap = { version = "4.0", features = ["cargo", "derive"] }
|
||||
@@ -27,7 +27,6 @@ humantime-serde = "1.0"
|
||||
lazy_static = "1.4.0"
|
||||
log = { workspace = true }
|
||||
pin-project = "1.0"
|
||||
pretty_env_logger = "0.4.0"
|
||||
rand = "0.8.5"
|
||||
rand-07 = { package = "rand", version = "0.7.3" } # required for compatibility
|
||||
reqwest = { workspace = true, features = ["json"] }
|
||||
@@ -49,7 +48,7 @@ url = { workspace = true }
|
||||
|
||||
ts-rs = { workspace = true, optional = true}
|
||||
|
||||
anyhow = "1.0"
|
||||
anyhow = { workspace = true }
|
||||
getset = "0.1.1"
|
||||
|
||||
sqlx = { version = "0.6.2", features = [
|
||||
@@ -61,7 +60,7 @@ sqlx = { version = "0.6.2", features = [
|
||||
|
||||
okapi = { version = "0.7.0-rc.1", features = ["impl_json_schema"] }
|
||||
rocket_okapi = { version = "0.8.0-rc.2", features = ["swagger"] }
|
||||
schemars = { version = "0.8", features = ["preserve_order"] }
|
||||
schemars = { workspace = true, features = ["preserve_order"] }
|
||||
zeroize = { workspace = true }
|
||||
|
||||
## ephemera-specific
|
||||
@@ -106,13 +105,14 @@ nym-api-requests = { path = "nym-api-requests" }
|
||||
nym-validator-client = { path = "../common/client-libs/validator-client" }
|
||||
nym-bin-common = { path = "../common/bin-common" }
|
||||
nym-node-tester-utils = { path = "../common/node-tester-utils" }
|
||||
nym-node-requests = { path = "../nym-node/nym-node-requests" }
|
||||
|
||||
[features]
|
||||
no-reward = []
|
||||
generate-ts = ["ts-rs"]
|
||||
|
||||
[build-dependencies]
|
||||
tokio = { version = "1.24.1", features = ["rt-multi-thread", "macros"] }
|
||||
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
|
||||
sqlx = { version = "0.6.2", features = [
|
||||
"runtime-tokio-rustls",
|
||||
"sqlite",
|
||||
|
||||
@@ -10,13 +10,14 @@ bs58 = "0.4.0"
|
||||
cosmrs = { workspace = true }
|
||||
cosmwasm-std = { workspace = true }
|
||||
getset = "0.1.1"
|
||||
schemars = { version = "0.8", features = ["preserve_order"] }
|
||||
schemars = { workspace = true, features = ["preserve_order"] }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
ts-rs = { workspace = true, optional = true }
|
||||
|
||||
nym-coconut-interface = { path = "../../common/coconut-interface" }
|
||||
nym-mixnet-contract-common = { path= "../../common/cosmwasm-smart-contracts/mixnet-contract" }
|
||||
nym-node-requests = { path = "../../nym-node/nym-node-requests", default-features = false }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
generate-ts = ["ts-rs"]
|
||||
generate-ts = ["ts-rs", "nym-mixnet-contract-common/generate-ts"]
|
||||
|
||||
@@ -9,6 +9,7 @@ use nym_mixnet_contract_common::rewarding::RewardEstimate;
|
||||
use nym_mixnet_contract_common::{
|
||||
GatewayBond, IdentityKey, Interval, MixId, MixNode, Percent, RewardedSetNodeStatus,
|
||||
};
|
||||
use nym_node_requests::api::v1::gateway::models::WebSockets;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::{Display, Formatter};
|
||||
@@ -142,6 +143,10 @@ impl MixNodeBondAnnotated {
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct GatewayBondAnnotated {
|
||||
pub gateway_bond: GatewayBond,
|
||||
|
||||
#[serde(default)]
|
||||
pub self_described: Option<GatewayDescription>,
|
||||
|
||||
// NOTE: the performance field is deprecated in favour of node_performance
|
||||
pub performance: Performance,
|
||||
pub node_performance: NodePerformance,
|
||||
@@ -158,6 +163,11 @@ impl GatewayBondAnnotated {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct GatewayDescription {
|
||||
// for now only expose what we need. this struct will evolve in the future (or be incorporated into nym-node properly)
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct ComputeRewardEstParam {
|
||||
pub performance: Option<Performance>,
|
||||
@@ -344,3 +354,24 @@ pub struct CirculatingSupplyResponse {
|
||||
pub vesting_tokens: Coin,
|
||||
pub circulating_supply: Coin,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, schemars::JsonSchema)]
|
||||
pub struct NymNodeDescription {
|
||||
// for now we only care about their ws/wss situation, nothing more
|
||||
pub mixnet_websockets: WebSockets,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, schemars::JsonSchema)]
|
||||
pub struct DescribedGateway {
|
||||
pub bond: GatewayBond,
|
||||
pub self_described: Option<NymNodeDescription>,
|
||||
}
|
||||
|
||||
impl From<GatewayBond> for DescribedGateway {
|
||||
fn from(bond: GatewayBond) -> Self {
|
||||
DescribedGateway {
|
||||
bond,
|
||||
self_described: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,9 @@ extern crate rocket;
|
||||
|
||||
use crate::epoch_operations::RewardedSetUpdater;
|
||||
use crate::network::models::NetworkDetails;
|
||||
use crate::node_describe_cache::DescribedNodes;
|
||||
use crate::node_status_api::uptime_updater::HistoricalUptimeUpdater;
|
||||
use crate::support::caching::cache::SharedCache;
|
||||
use crate::support::cli;
|
||||
use crate::support::cli::CliArgs;
|
||||
use crate::support::config::Config;
|
||||
@@ -34,8 +36,10 @@ mod ephemera;
|
||||
mod epoch_operations;
|
||||
pub(crate) mod network;
|
||||
mod network_monitor;
|
||||
pub(crate) mod node_describe_cache;
|
||||
pub(crate) mod node_status_api;
|
||||
pub(crate) mod nym_contract_cache;
|
||||
pub(crate) mod nym_nodes;
|
||||
pub(crate) mod support;
|
||||
|
||||
struct ShutdownHandles {
|
||||
@@ -90,6 +94,18 @@ async fn start_nym_api_tasks(
|
||||
let node_status_cache_state = rocket.state::<NodeStatusCache>().unwrap();
|
||||
let circulating_supply_cache_state = rocket.state::<CirculatingSupplyCache>().unwrap();
|
||||
let maybe_storage = rocket.state::<NymApiStorage>();
|
||||
let described_nodes_state = rocket.state::<SharedCache<DescribedNodes>>().unwrap();
|
||||
|
||||
// start note describe cache refresher
|
||||
// we should be doing the below, but can't due to our current startup structure
|
||||
// let refresher = node_describe_cache::new_refresher(&config.topology_cacher);
|
||||
// let cache = refresher.get_shared_cache();
|
||||
node_describe_cache::new_refresher_with_initial_value(
|
||||
&config.topology_cacher,
|
||||
described_nodes_state.to_owned(),
|
||||
)
|
||||
.named("node-self-described-data-refresher")
|
||||
.start(shutdown.subscribe_named("node-self-described-data-refresher"));
|
||||
|
||||
// start all the caches first
|
||||
let nym_contract_cache_listener = nym_contract_cache::start_refresher(
|
||||
@@ -98,6 +114,7 @@ async fn start_nym_api_tasks(
|
||||
nyxd_client.clone(),
|
||||
&shutdown,
|
||||
);
|
||||
|
||||
node_status_api::start_cache_refresh(
|
||||
&config.node_status_api,
|
||||
nym_contract_cache_state,
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::support::caching::cache::{SharedCache, UninitialisedCache};
|
||||
use crate::support::caching::refresher::{CacheItemProvider, CacheRefresher};
|
||||
use crate::support::config;
|
||||
use crate::support::config::DEFAULT_NODE_DESCRIBE_BATCH_SIZE;
|
||||
use futures_util::{stream, StreamExt};
|
||||
use nym_api_requests::models::NymNodeDescription;
|
||||
use nym_contracts_common::IdentityKey;
|
||||
use nym_mixnet_contract_common::{Gateway, GatewayBond};
|
||||
use nym_node_requests::api::client::{NymNodeApiClientError, NymNodeApiClientExt};
|
||||
use nym_node_requests::api::v1::gateway::models::WebSockets;
|
||||
use std::collections::HashMap;
|
||||
use std::ops::Deref;
|
||||
use thiserror::Error;
|
||||
|
||||
// type alias for ease of use
|
||||
pub type DescribedNodes = HashMap<IdentityKey, NymNodeDescription>;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum NodeDescribeCacheError {
|
||||
#[error("contract cache hasn't been initialised")]
|
||||
UninitialisedContractCache {
|
||||
#[from]
|
||||
source: UninitialisedCache,
|
||||
},
|
||||
|
||||
#[error("gateway {gateway} has provided malformed host information ({host}: {source}")]
|
||||
MalformedHost {
|
||||
host: String,
|
||||
|
||||
gateway: IdentityKey,
|
||||
|
||||
#[source]
|
||||
source: NymNodeApiClientError,
|
||||
},
|
||||
|
||||
#[error("failed to query gateway '{gateway}': {source}")]
|
||||
ApiFailure {
|
||||
gateway: IdentityKey,
|
||||
|
||||
#[source]
|
||||
source: NymNodeApiClientError,
|
||||
},
|
||||
}
|
||||
|
||||
pub struct NodeDescriptionProvider {
|
||||
// for now we only care about gateways, nothing more
|
||||
network_gateways: SharedCache<Vec<GatewayBond>>,
|
||||
|
||||
batch_size: usize,
|
||||
}
|
||||
|
||||
impl NodeDescriptionProvider {
|
||||
pub(crate) fn new() -> NodeDescriptionProvider {
|
||||
NodeDescriptionProvider {
|
||||
network_gateways: SharedCache::new(),
|
||||
batch_size: DEFAULT_NODE_DESCRIBE_BATCH_SIZE,
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub(crate) fn with_batch_size(mut self, batch_size: usize) -> Self {
|
||||
self.batch_size = batch_size;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_gateway_websocket_support(
|
||||
gateway: Gateway,
|
||||
) -> Result<(IdentityKey, WebSockets), NodeDescribeCacheError> {
|
||||
let client = match nym_node_requests::api::Client::new_url(&gateway.host, None) {
|
||||
Ok(client) => client,
|
||||
Err(err) => {
|
||||
return Err(NodeDescribeCacheError::MalformedHost {
|
||||
host: gateway.host,
|
||||
gateway: gateway.identity_key,
|
||||
source: err,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
let websockets =
|
||||
client
|
||||
.get_mixnet_websockets()
|
||||
.await
|
||||
.map_err(|err| NodeDescribeCacheError::ApiFailure {
|
||||
gateway: gateway.identity_key.clone(),
|
||||
source: err,
|
||||
})?;
|
||||
|
||||
Ok((gateway.identity_key, websockets))
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl CacheItemProvider for NodeDescriptionProvider {
|
||||
type Item = HashMap<IdentityKey, NymNodeDescription>;
|
||||
type Error = NodeDescribeCacheError;
|
||||
|
||||
async fn try_refresh(&self) -> Result<Self::Item, Self::Error> {
|
||||
let guard = self.network_gateways.get().await?;
|
||||
let gateways = &*guard;
|
||||
|
||||
// TODO: somehow bypass the 'higher-ranked lifetime error' and remove that redundant clone
|
||||
let websockets = stream::iter(
|
||||
gateways
|
||||
.deref()
|
||||
.clone()
|
||||
.into_iter()
|
||||
.map(|bond| bond.gateway)
|
||||
.map(get_gateway_websocket_support),
|
||||
)
|
||||
.buffer_unordered(self.batch_size)
|
||||
.filter_map(|res| async move {
|
||||
match res {
|
||||
Ok((identity, mixnet_websockets)) => {
|
||||
Some((identity, NymNodeDescription { mixnet_websockets }))
|
||||
}
|
||||
Err(err) => {
|
||||
// TODO: reduce it to trace/debug before PR
|
||||
warn!("{err}");
|
||||
None
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect::<HashMap<_, _>>()
|
||||
.await;
|
||||
|
||||
Ok(websockets)
|
||||
}
|
||||
}
|
||||
|
||||
// currently dead code : (
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn new_refresher(
|
||||
config: &config::TopologyCacher,
|
||||
) -> CacheRefresher<DescribedNodes, NodeDescribeCacheError> {
|
||||
CacheRefresher::new(
|
||||
Box::new(
|
||||
NodeDescriptionProvider::new().with_batch_size(config.debug.node_describe_batch_size),
|
||||
),
|
||||
config.debug.node_describe_caching_interval,
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn new_refresher_with_initial_value(
|
||||
config: &config::TopologyCacher,
|
||||
initial: SharedCache<DescribedNodes>,
|
||||
) -> CacheRefresher<DescribedNodes, NodeDescribeCacheError> {
|
||||
CacheRefresher::new_with_initial_value(
|
||||
Box::new(
|
||||
NodeDescriptionProvider::new().with_batch_size(config.debug.node_describe_batch_size),
|
||||
),
|
||||
config.debug.node_describe_caching_interval,
|
||||
initial,
|
||||
)
|
||||
}
|
||||
@@ -180,6 +180,7 @@ pub(crate) async fn annotate_gateways_with_details(
|
||||
annotated.push(GatewayBondAnnotated {
|
||||
blacklisted: blacklist.contains(&gateway_bond.gateway.identity_key),
|
||||
gateway_bond,
|
||||
self_described: None,
|
||||
performance,
|
||||
node_performance,
|
||||
});
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use self::cache::refresher::NodeStatusCacheRefresher;
|
||||
use crate::support::config;
|
||||
use crate::{
|
||||
nym_contract_cache::cache::NymContractCache,
|
||||
support::{self, storage},
|
||||
};
|
||||
pub(crate) use cache::NodeStatusCache;
|
||||
use nym_task::TaskManager;
|
||||
use okapi::openapi3::OpenApi;
|
||||
@@ -8,13 +14,6 @@ use rocket::Route;
|
||||
use rocket_okapi::{openapi_get_routes_spec, settings::OpenApiSettings};
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::support::config;
|
||||
use crate::{
|
||||
nym_contract_cache::cache::NymContractCache,
|
||||
support::{self, storage},
|
||||
};
|
||||
|
||||
use self::cache::refresher::NodeStatusCacheRefresher;
|
||||
pub(crate) mod cache;
|
||||
pub(crate) mod helpers;
|
||||
pub(crate) mod local_guard;
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use okapi::openapi3::OpenApi;
|
||||
use rocket::Route;
|
||||
use rocket_okapi::openapi_get_routes_spec;
|
||||
use rocket_okapi::settings::OpenApiSettings;
|
||||
|
||||
pub(crate) mod routes;
|
||||
|
||||
/// Merges the routes with http information and returns it to Rocket for serving
|
||||
pub(crate) fn nym_node_routes(settings: &OpenApiSettings) -> (Vec<Route>, OpenApi) {
|
||||
openapi_get_routes_spec![
|
||||
settings: routes::get_gateways_described
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::node_describe_cache::DescribedNodes;
|
||||
use crate::nym_contract_cache::cache::NymContractCache;
|
||||
use crate::support::caching::cache::SharedCache;
|
||||
use nym_api_requests::models::DescribedGateway;
|
||||
use rocket::serde::json::Json;
|
||||
use rocket::State;
|
||||
use rocket_okapi::openapi;
|
||||
use std::ops::Deref;
|
||||
|
||||
// obviously this should get refactored later on because gateways will go away.
|
||||
// unless maybe this will be filtering based on which nodes got assigned gateway role? TBD
|
||||
|
||||
#[openapi(tag = "Nym Nodes")]
|
||||
#[get("/gateways/described")]
|
||||
pub async fn get_gateways_described(
|
||||
contract_cache: &State<NymContractCache>,
|
||||
describe_cache: &State<SharedCache<DescribedNodes>>,
|
||||
) -> Json<Vec<DescribedGateway>> {
|
||||
let gateways = contract_cache.gateways_filtered().await;
|
||||
if gateways.is_empty() {
|
||||
return Json(Vec::new());
|
||||
}
|
||||
|
||||
// if the self describe cache is unavailable, well, don't attach describe data
|
||||
let Ok(self_descriptions) = describe_cache.get().await else {
|
||||
return Json(gateways.into_iter().map(Into::into).collect());
|
||||
};
|
||||
|
||||
// TODO: this is extremely inefficient, but given we don't have many gateways,
|
||||
// it shouldn't be too much of a problem until we go ahead with directory v3 / the smoosh 2: electric smoosharoo,
|
||||
// but at that point (I hope) the whole caching situation should get refactored
|
||||
Json(
|
||||
gateways
|
||||
.into_iter()
|
||||
.map(|bond| DescribedGateway {
|
||||
self_described: self_descriptions.deref().get(bond.identity()).cloned(),
|
||||
bond,
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
@@ -49,6 +49,11 @@ const DEFAULT_PER_NODE_TEST_PACKETS: usize = 3;
|
||||
const DEFAULT_TOPOLOGY_CACHE_INTERVAL: Duration = Duration::from_secs(30);
|
||||
const DEFAULT_NODE_STATUS_CACHE_INTERVAL: Duration = Duration::from_secs(120);
|
||||
const DEFAULT_CIRCULATING_SUPPLY_CACHE_INTERVAL: Duration = Duration::from_secs(3600);
|
||||
|
||||
pub(crate) const DEFAULT_NODE_DESCRIBE_CACHE_INTERVAL: Duration = Duration::from_secs(4500);
|
||||
pub(crate) const DEFAULT_NODE_DESCRIBE_BATCH_SIZE: usize = 50;
|
||||
|
||||
|
||||
const DEFAULT_MONITOR_THRESHOLD: u8 = 60;
|
||||
const DEFAULT_MIN_MIXNODE_RELIABILITY: u8 = 50;
|
||||
const DEFAULT_MIN_GATEWAY_RELIABILITY: u8 = 20;
|
||||
@@ -451,12 +456,19 @@ pub struct TopologyCacher {
|
||||
pub struct TopologyCacherDebug {
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub caching_interval: Duration,
|
||||
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub node_describe_caching_interval: Duration,
|
||||
|
||||
pub node_describe_batch_size: usize,
|
||||
}
|
||||
|
||||
impl Default for TopologyCacherDebug {
|
||||
fn default() -> Self {
|
||||
TopologyCacherDebug {
|
||||
caching_interval: DEFAULT_TOPOLOGY_CACHE_INTERVAL,
|
||||
node_describe_caching_interval: DEFAULT_NODE_DESCRIBE_CACHE_INTERVAL,
|
||||
node_describe_batch_size: DEFAULT_NODE_DESCRIBE_BATCH_SIZE,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,6 +126,7 @@ impl From<ConfigV1_1_21> for Config {
|
||||
topology_cacher: TopologyCacher {
|
||||
debug: TopologyCacherDebug {
|
||||
caching_interval: value.topology_cacher.caching_interval,
|
||||
..Default::default()
|
||||
},
|
||||
},
|
||||
circulating_supply_cacher: CirculatingSupplyCacher {
|
||||
|
||||
@@ -5,11 +5,13 @@ use crate::circulating_supply_api::cache::CirculatingSupplyCache;
|
||||
use crate::coconut::{self, comm::QueryCommunicationChannel, InternalSignRequest};
|
||||
use crate::network::models::NetworkDetails;
|
||||
use crate::network::network_routes;
|
||||
use crate::node_describe_cache::DescribedNodes;
|
||||
use crate::node_status_api::{self, NodeStatusCache};
|
||||
use crate::nym_contract_cache::cache::NymContractCache;
|
||||
use crate::support::caching::cache::SharedCache;
|
||||
use crate::support::config::Config;
|
||||
use crate::support::{nyxd, storage};
|
||||
use crate::{circulating_supply_api, nym_contract_cache};
|
||||
use crate::{circulating_supply_api, nym_contract_cache, nym_nodes::nym_node_routes};
|
||||
use anyhow::Result;
|
||||
use rocket::http::Method;
|
||||
use rocket::{Ignite, Rocket};
|
||||
@@ -39,10 +41,12 @@ pub(crate) async fn setup_rocket(
|
||||
"" => nym_contract_cache::nym_contract_cache_routes(&openapi_settings),
|
||||
"/status" => node_status_api::node_status_routes(&openapi_settings, config.network_monitor.enabled),
|
||||
"/network" => network_routes(&openapi_settings),
|
||||
"" => nym_node_routes(&openapi_settings),
|
||||
}
|
||||
|
||||
let rocket = rocket
|
||||
.manage(network_details)
|
||||
.manage(SharedCache::<DescribedNodes>::new())
|
||||
.mount("/swagger", make_swagger_ui(&openapi::get_docs()))
|
||||
.attach(setup_cors()?)
|
||||
.attach(NymContractCache::stage())
|
||||
|
||||
Generated
+14
@@ -2973,6 +2973,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
"tracing",
|
||||
"url",
|
||||
]
|
||||
|
||||
@@ -3886,6 +3887,7 @@ dependencies = [
|
||||
"getset",
|
||||
"nym-coconut-interface",
|
||||
"nym-mixnet-contract-common",
|
||||
"nym-node-requests",
|
||||
"schemars",
|
||||
"serde",
|
||||
]
|
||||
@@ -4322,6 +4324,18 @@ dependencies = [
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-node-requests"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"nym-bin-common",
|
||||
"nym-crypto",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-nonexhaustive-delayqueue"
|
||||
version = "0.1.0"
|
||||
|
||||
@@ -11,6 +11,7 @@ license.workspace = true
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
schemars = { workspace = true, features = ["preserve_order"] }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::api::v1::node::models::HostInformation;
|
||||
use crate::error::Error;
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use serde::Serialize;
|
||||
use std::ops::Deref;
|
||||
|
||||
#[cfg(feature = "openapi")]
|
||||
use crate::api::v1::node::models::HostInformation;
|
||||
|
||||
#[cfg(feature = "client")]
|
||||
pub mod client;
|
||||
pub mod v1;
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)]
|
||||
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
|
||||
pub struct Gateway {
|
||||
pub client_interfaces: ClientInterfaces,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)]
|
||||
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
|
||||
pub struct Wireguard {
|
||||
#[cfg_attr(feature = "openapi", schema(example = 51820, default = 51820))]
|
||||
@@ -18,7 +19,7 @@ pub struct Wireguard {
|
||||
pub public_key: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)]
|
||||
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
|
||||
pub struct ClientInterfaces {
|
||||
pub wireguard: Option<Wireguard>,
|
||||
@@ -27,7 +28,7 @@ pub struct ClientInterfaces {
|
||||
// pub mixnet_tcp:
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, JsonSchema)]
|
||||
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
|
||||
pub struct WebSockets {
|
||||
#[cfg_attr(feature = "openapi", schema(example = 9000, default = 9000))]
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)]
|
||||
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
|
||||
pub struct Mixnode {
|
||||
// /// Base58 encoded ed25519 EdDSA public key of the mixnode.
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)]
|
||||
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
|
||||
pub struct NetworkRequester {
|
||||
/// Base58 encoded ed25519 EdDSA public key of the network requester.
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[cfg(feature = "openapi")]
|
||||
pub use crate::api::SignedHostInformation;
|
||||
pub use nym_bin_common::build_information::BinaryBuildInformationOwned;
|
||||
|
||||
#[derive(Clone, Default, Debug, Copy, Serialize, Deserialize)]
|
||||
#[derive(Clone, Default, Debug, Copy, Serialize, Deserialize, JsonSchema)]
|
||||
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
|
||||
pub struct NodeRoles {
|
||||
pub mixnode_enabled: bool,
|
||||
@@ -15,7 +16,7 @@ pub struct NodeRoles {
|
||||
pub network_requester_enabled: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
|
||||
#[derive(Clone, Default, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
|
||||
pub struct HostInformation {
|
||||
/// Ip address(es) of this host, such as `1.1.1.1`.
|
||||
@@ -28,7 +29,7 @@ pub struct HostInformation {
|
||||
pub keys: HostKeys,
|
||||
}
|
||||
|
||||
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
|
||||
#[derive(Clone, Default, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
|
||||
pub struct HostKeys {
|
||||
/// Base58-encoded ed25519 public key of this node. Currently it corresponds to either mixnode's or gateway's identity.
|
||||
|
||||
Reference in New Issue
Block a user