Compare commits

...

6 Commits

Author SHA1 Message Date
Bogdan-Ștefan Neacşu 080f28d626 Fix contains ticketbook function that always returned true 2025-05-20 16:58:48 +03:00
Andrej Mihajlov ae7f29313e Merge pull request #5780 from nymtech/am/cherry-pick-http-fix
Cherry-pick: more relaxed usage of reqwest accept-encoding (#5779)
2025-05-16 21:21:13 +02:00
Jack Wampler ab7b88d091 more relaxed usage of reqwest accept-encoding (#5779) 2025-05-16 21:16:37 +02:00
Jędrzej Stuczyński 57cd4896d9 feat: use bincode by default in NymApiClient + remove feature-lock (#5761) 2025-05-14 17:33:32 +01:00
Jędrzej Stuczyński 193baf13b4 feat: expires header for /active nym-api responses (#5755)
* refactor FormattedResponse to allow attaching additional headers

* helper method for including expiration headers

* add expires header for /active nodes responses

* added additional 'with_expires_header_delta' builder to FormattedResponse to allow setting expiration header with time delta
2025-05-13 16:05:12 +01:00
Jędrzej Stuczyński 5742a67014 feat: nym-api bincode + yaml support (#5745)
* introduce 'Bincode' variant for FormattedResponse

* allow nym-api to return responses in bincode (and also yaml)

* client parsing support

* cargo fmt

* missing changes to nym-api tests

* fixed node status api build + adjusted NymApiClient construction

* NMv2 fixes + further api changes

* feature-locking http-api-common to fix wasm build
2025-05-13 16:04:58 +01:00
40 changed files with 1722 additions and 671 deletions
Generated
+4 -1
View File
@@ -5785,12 +5785,14 @@ name = "nym-http-api-client"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"bincode",
"bytes", "bytes",
"encoding_rs", "encoding_rs",
"hickory-resolver", "hickory-resolver",
"http 1.3.1", "http 1.3.1",
"mime", "mime",
"nym-bin-common", "nym-bin-common",
"nym-http-api-common",
"once_cell", "once_cell",
"reqwest 0.12.15", "reqwest 0.12.15",
"serde", "serde",
@@ -5808,14 +5810,15 @@ version = "0.1.0"
dependencies = [ dependencies = [
"axum 0.7.9", "axum 0.7.9",
"axum-client-ip", "axum-client-ip",
"bincode",
"bytes", "bytes",
"colored", "colored",
"futures", "futures",
"mime", "mime",
"serde", "serde",
"serde_json",
"serde_yaml", "serde_yaml",
"subtle 2.6.1", "subtle 2.6.1",
"time",
"tower 0.5.2", "tower 0.5.2",
"tracing", "tracing",
"utoipa", "utoipa",
@@ -70,6 +70,10 @@ impl NymApiTopologyProvider {
} }
} }
pub fn disable_bincode(&mut self) {
self.validator_client.use_bincode = false;
}
fn use_next_nym_api(&mut self) { fn use_next_nym_api(&mut self) {
if self.nym_api_urls.len() == 1 { if self.nym_api_urls.len() == 1 {
warn!("There's only a single nym API available - it won't be possible to use a different one"); warn!("There's only a single nym API available - it won't be possible to use a different one");
@@ -345,25 +345,47 @@ impl<C, S> Client<C, S> {
#[derive(Clone)] #[derive(Clone)]
pub struct NymApiClient { pub struct NymApiClient {
pub use_bincode: bool,
pub nym_api: nym_api::Client, pub nym_api: nym_api::Client,
// TODO: perhaps if we really need it at some (currently I don't see any reasons for it) // TODO: perhaps if we really need it at some (currently I don't see any reasons for it)
// we could re-implement the communication with the REST API on port 1317 // we could re-implement the communication with the REST API on port 1317
} }
impl From<nym_api::Client> for NymApiClient {
fn from(nym_api: nym_api::Client) -> Self {
NymApiClient {
use_bincode: false,
nym_api,
}
}
}
// we have to allow the use of deprecated method here as they're calling the deprecated trait methods // we have to allow the use of deprecated method here as they're calling the deprecated trait methods
#[allow(deprecated)] #[allow(deprecated)]
impl NymApiClient { impl NymApiClient {
pub fn new(api_url: Url) -> Self { pub fn new(api_url: Url) -> Self {
let nym_api = nym_api::Client::new(api_url, None); let nym_api = nym_api::Client::new(api_url, None);
NymApiClient { nym_api } NymApiClient {
use_bincode: true,
nym_api,
}
} }
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
pub fn new_with_timeout(api_url: Url, timeout: std::time::Duration) -> Self { pub fn new_with_timeout(api_url: Url, timeout: std::time::Duration) -> Self {
let nym_api = nym_api::Client::new(api_url, Some(timeout)); let nym_api = nym_api::Client::new(api_url, Some(timeout));
NymApiClient { nym_api } NymApiClient {
use_bincode: true,
nym_api,
}
}
#[must_use]
pub fn with_bincode(mut self, use_bincode: bool) -> Self {
self.use_bincode = use_bincode;
self
} }
pub fn new_with_user_agent(api_url: Url, user_agent: impl Into<UserAgent>) -> Self { pub fn new_with_user_agent(api_url: Url, user_agent: impl Into<UserAgent>) -> Self {
@@ -373,7 +395,10 @@ impl NymApiClient {
.build::<ValidatorClientError>() .build::<ValidatorClientError>()
.expect("failed to build nym api client"); .expect("failed to build nym api client");
NymApiClient { nym_api } NymApiClient {
use_bincode: false,
nym_api,
}
} }
pub fn api_url(&self) -> &Url { pub fn api_url(&self) -> &Url {
@@ -410,7 +435,7 @@ impl NymApiClient {
loop { loop {
let mut res = self let mut res = self
.nym_api .nym_api
.get_basic_entry_assigned_nodes(false, Some(page), None) .get_basic_entry_assigned_nodes(false, Some(page), None, self.use_bincode)
.await?; .await?;
nodes.append(&mut res.nodes.data); nodes.append(&mut res.nodes.data);
@@ -436,7 +461,7 @@ impl NymApiClient {
loop { loop {
let mut res = self let mut res = self
.nym_api .nym_api
.get_basic_active_mixing_assigned_nodes(false, Some(page), None) .get_basic_active_mixing_assigned_nodes(false, Some(page), None, self.use_bincode)
.await?; .await?;
nodes.append(&mut res.nodes.data); nodes.append(&mut res.nodes.data);
@@ -462,7 +487,7 @@ impl NymApiClient {
loop { loop {
let mut res = self let mut res = self
.nym_api .nym_api
.get_basic_mixing_capable_nodes(false, Some(page), None) .get_basic_mixing_capable_nodes(false, Some(page), None, self.use_bincode)
.await?; .await?;
nodes.append(&mut res.nodes.data); nodes.append(&mut res.nodes.data);
@@ -485,7 +510,7 @@ impl NymApiClient {
loop { loop {
let mut res = self let mut res = self
.nym_api .nym_api
.get_basic_nodes(false, Some(page), None) .get_basic_nodes(false, Some(page), None, self.use_bincode)
.await?; .await?;
nodes.append(&mut res.nodes.data); nodes.append(&mut res.nodes.data);
@@ -318,6 +318,7 @@ pub trait NymApiClientExt: ApiClient {
no_legacy: bool, no_legacy: bool,
page: Option<u32>, page: Option<u32>,
per_page: Option<u32>, per_page: Option<u32>,
use_bincode: bool,
) -> Result<PaginatedCachedNodesResponse<SkimmedNode>, NymAPIError> { ) -> Result<PaginatedCachedNodesResponse<SkimmedNode>, NymAPIError> {
let mut params = Vec::new(); let mut params = Vec::new();
@@ -333,7 +334,11 @@ pub trait NymApiClientExt: ApiClient {
params.push(("per_page", per_page.to_string())) params.push(("per_page", per_page.to_string()))
} }
self.get_json( if use_bincode {
params.push(("output", "bincode".to_string()))
}
self.get_response(
&[ &[
routes::API_VERSION, routes::API_VERSION,
"unstable", "unstable",
@@ -355,6 +360,7 @@ pub trait NymApiClientExt: ApiClient {
no_legacy: bool, no_legacy: bool,
page: Option<u32>, page: Option<u32>,
per_page: Option<u32>, per_page: Option<u32>,
use_bincode: bool,
) -> Result<PaginatedCachedNodesResponse<SkimmedNode>, NymAPIError> { ) -> Result<PaginatedCachedNodesResponse<SkimmedNode>, NymAPIError> {
let mut params = Vec::new(); let mut params = Vec::new();
@@ -370,7 +376,11 @@ pub trait NymApiClientExt: ApiClient {
params.push(("per_page", per_page.to_string())) params.push(("per_page", per_page.to_string()))
} }
self.get_json( if use_bincode {
params.push(("output", "bincode".to_string()))
}
self.get_response(
&[ &[
routes::API_VERSION, routes::API_VERSION,
"unstable", "unstable",
@@ -392,6 +402,7 @@ pub trait NymApiClientExt: ApiClient {
no_legacy: bool, no_legacy: bool,
page: Option<u32>, page: Option<u32>,
per_page: Option<u32>, per_page: Option<u32>,
use_bincode: bool,
) -> Result<PaginatedCachedNodesResponse<SkimmedNode>, NymAPIError> { ) -> Result<PaginatedCachedNodesResponse<SkimmedNode>, NymAPIError> {
let mut params = Vec::new(); let mut params = Vec::new();
@@ -407,7 +418,11 @@ pub trait NymApiClientExt: ApiClient {
params.push(("per_page", per_page.to_string())) params.push(("per_page", per_page.to_string()))
} }
self.get_json( if use_bincode {
params.push(("output", "bincode".to_string()))
}
self.get_response(
&[ &[
routes::API_VERSION, routes::API_VERSION,
"unstable", "unstable",
@@ -427,6 +442,7 @@ pub trait NymApiClientExt: ApiClient {
no_legacy: bool, no_legacy: bool,
page: Option<u32>, page: Option<u32>,
per_page: Option<u32>, per_page: Option<u32>,
use_bincode: bool,
) -> Result<PaginatedCachedNodesResponse<SkimmedNode>, NymAPIError> { ) -> Result<PaginatedCachedNodesResponse<SkimmedNode>, NymAPIError> {
let mut params = Vec::new(); let mut params = Vec::new();
@@ -442,7 +458,11 @@ pub trait NymApiClientExt: ApiClient {
params.push(("per_page", per_page.to_string())) params.push(("per_page", per_page.to_string()))
} }
self.get_json( if use_bincode {
params.push(("output", "bincode".to_string()))
}
self.get_response(
&[ &[
routes::API_VERSION, routes::API_VERSION,
"unstable", "unstable",
@@ -98,11 +98,10 @@ impl SqliteEcashTicketbookManager {
pub(crate) async fn contains_ticketbook_data(&self, data: &[u8]) -> Result<bool, sqlx::Error> { pub(crate) async fn contains_ticketbook_data(&self, data: &[u8]) -> Result<bool, sqlx::Error> {
let exists = sqlx::query( let exists = sqlx::query(
r#" r#"
SELECT EXISTS ( SELECT 1
SELECT 1 FROM ecash_ticketbook
FROM ecash_ticketbook WHERE ticketbook_data = ?
WHERE ticketbook_data = ?
)
"#, "#,
) )
.bind(data) .bind(data)
+4 -2
View File
@@ -12,7 +12,8 @@ license.workspace = true
[dependencies] [dependencies]
async-trait = { workspace = true } async-trait = { workspace = true }
reqwest = { workspace = true, features = ["json", "gzip"] } bincode = { workspace = true }
reqwest = { workspace = true, features = ["json", "gzip", "deflate", "brotli", "zstd"] }
http.workspace = true http.workspace = true
url = { workspace = true } url = { workspace = true }
once_cell = { workspace = true } once_cell = { workspace = true }
@@ -26,7 +27,7 @@ bytes = { workspace = true }
encoding_rs = { workspace = true } encoding_rs = { workspace = true }
mime = { workspace = true } mime = { workspace = true }
nym-http-api-common = { path = "../http-api-common", default-features = false }
nym-bin-common = { path = "../bin-common" } nym-bin-common = { path = "../bin-common" }
[target."cfg(not(target_arch = \"wasm32\"))".dependencies] [target."cfg(not(target_arch = \"wasm32\"))".dependencies]
@@ -39,3 +40,4 @@ features = ["tokio"]
[dev-dependencies] [dev-dependencies]
tokio = { workspace = true, features = ["rt", "macros"] } tokio = { workspace = true, features = ["rt", "macros"] }
+104 -35
View File
@@ -144,10 +144,13 @@ use serde::{Deserialize, Serialize};
use std::fmt::Display; use std::fmt::Display;
use std::time::Duration; use std::time::Duration;
use thiserror::Error; use thiserror::Error;
use tracing::{instrument, warn}; use tracing::{debug, instrument, warn};
use url::Url; use url::Url;
use bytes::Bytes;
use http::header::CONTENT_TYPE;
use http::HeaderMap; use http::HeaderMap;
use mime::Mime;
pub use reqwest::IntoUrl; pub use reqwest::IntoUrl;
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
use std::net::SocketAddr; use std::net::SocketAddr;
@@ -210,11 +213,8 @@ pub enum HttpClientError<E: Display = String> {
#[error("failed to resolve request. status: '{status}', additional error message: {error}")] #[error("failed to resolve request. status: '{status}', additional error message: {error}")]
EndpointFailure { status: StatusCode, error: E }, EndpointFailure { status: StatusCode, error: E },
#[error("failed to decode response body: {source} from {content}")] #[error("failed to decode response body: {message} from {content}")]
ResponseDecodeFailure { ResponseDecodeFailure { message: String, content: String },
source: serde_json::Error,
content: String,
},
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
#[error("the request has timed out")] #[error("the request has timed out")]
@@ -265,14 +265,18 @@ impl ClientBuilder {
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
let reqwest_client_builder = { let reqwest_client_builder = {
let r = reqwest::ClientBuilder::new(); // Note: I believe the manual enable calls for the compression methods are extra
// as the various compression features for `reqwest` crate should be enabled
// Note this is extra as the `gzip` feature for `reqwest` crate should be enabled which // just by including the feature which:
// `"Enable[s] auto gzip decompression by checking the Content-Encoding response header."` // `"Enable[s] auto decompression by checking the Content-Encoding response header."`
// //
// I am going to leave it here anyways so that gzip decompression is attempted even if // I am going to leave these here anyways so that removing a decompression method
// that feature is removed. // from the features list will throw an error if it is not also removed here.
r.gzip(true) reqwest::ClientBuilder::new()
.gzip(true)
.deflate(true)
.brotli(true)
.zstd(true)
}; };
ClientBuilder { ClientBuilder {
@@ -535,11 +539,6 @@ impl ApiClientCore for Client {
let mut request = self.reqwest_client.request(method.clone(), url); let mut request = self.reqwest_client.request(method.clone(), url);
// Indicate that compressed responses are preferred, but if not supported other encodings are fine.
// TODO: Down the road we can be more selective about adding this, but it's inclusion here guarantees
// that we use compression when available.
request = request.header(reqwest::header::ACCEPT_ENCODING, "gzip;q=1.0, *;q=0.5");
if let Some(body) = json_body { if let Some(body) = json_body {
request = request.json(body); request = request.json(body);
} }
@@ -698,11 +697,29 @@ pub trait ApiClient: ApiClientCore {
/// defined key-value parameters, e.g. `[("since", "12345")]`. Attempt to parse the response /// defined key-value parameters, e.g. `[("since", "12345")]`. Attempt to parse the response
/// into the provided type `T`. /// into the provided type `T`.
#[instrument(level = "debug", skip_all)] #[instrument(level = "debug", skip_all)]
// TODO: deprecate in favour of get_response that works based on mime type in the response
async fn get_json<T, K, V, E>( async fn get_json<T, K, V, E>(
&self, &self,
path: PathSegments<'_>, path: PathSegments<'_>,
params: Params<'_, K, V>, params: Params<'_, K, V>,
) -> Result<T, HttpClientError<E>> ) -> Result<T, HttpClientError<E>>
where
for<'a> T: Deserialize<'a>,
K: AsRef<str> + Sync,
V: AsRef<str> + Sync,
E: Display + DeserializeOwned,
{
self.get_response(path, params).await
}
/// 'get' data from the segment-defined path, e.g. `["api", "v1", "mixnodes"]`, with tuple
/// defined key-value parameters, e.g. `[("since", "12345")]`. Attempt to parse the response
/// into the provided type `T` based on the content type header
async fn get_response<T, K, V, E>(
&self,
path: PathSegments<'_>,
params: Params<'_, K, V>,
) -> Result<T, HttpClientError<E>>
where where
for<'a> T: Deserialize<'a>, for<'a> T: Deserialize<'a>,
K: AsRef<str> + Sync, K: AsRef<str> + Sync,
@@ -877,14 +894,10 @@ fn sanitize_url<K: AsRef<str>, V: AsRef<str>>(
url url
} }
fn decode_as_text(bytes: &bytes::Bytes, headers: HeaderMap) -> String { fn decode_as_text(bytes: &bytes::Bytes, headers: &HeaderMap) -> String {
use encoding_rs::{Encoding, UTF_8}; use encoding_rs::{Encoding, UTF_8};
use mime::Mime;
let content_type = headers let content_type = try_get_mime_type(headers);
.get(http::header::CONTENT_TYPE)
.and_then(|value| value.to_str().ok())
.and_then(|value| value.parse::<Mime>().ok());
let encoding_name = content_type let encoding_name = content_type
.as_ref() .as_ref()
@@ -897,7 +910,7 @@ fn decode_as_text(bytes: &bytes::Bytes, headers: HeaderMap) -> String {
text.into_owned() text.into_owned()
} }
/// Attempt to parse a json object from an HTTP response /// Attempt to parse a response object from an HTTP response
#[instrument(level = "debug", skip_all)] #[instrument(level = "debug", skip_all)]
pub async fn parse_response<T, E>(res: Response, allow_empty: bool) -> Result<T, HttpClientError<E>> pub async fn parse_response<T, E>(res: Response, allow_empty: bool) -> Result<T, HttpClientError<E>>
where where
@@ -919,16 +932,7 @@ where
// internally reqwest is first retrieving bytes and then performing parsing via serde_json // internally reqwest is first retrieving bytes and then performing parsing via serde_json
// (and similarly does the same thing for text()) // (and similarly does the same thing for text())
let full = res.bytes().await?; let full = res.bytes().await?;
match serde_json::from_slice(&full) { decode_raw_response(&headers, full)
Ok(data) => Ok(data),
Err(err) => {
let content = decode_as_text(&full, headers);
Err(HttpClientError::ResponseDecodeFailure {
source: err,
content,
})
}
}
} else if res.status() == StatusCode::NOT_FOUND { } else if res.status() == StatusCode::NOT_FOUND {
Err(HttpClientError::NotFound) Err(HttpClientError::NotFound)
} else { } else {
@@ -947,6 +951,71 @@ where
} }
} }
fn decode_as_json<T, E>(headers: &HeaderMap, content: Bytes) -> Result<T, HttpClientError<E>>
where
T: DeserializeOwned,
E: DeserializeOwned + Display,
{
match serde_json::from_slice(&content) {
Ok(data) => Ok(data),
Err(err) => {
let content = decode_as_text(&content, headers);
Err(HttpClientError::ResponseDecodeFailure {
message: err.to_string(),
content,
})
}
}
}
fn decode_as_bincode<T, E>(headers: &HeaderMap, content: Bytes) -> Result<T, HttpClientError<E>>
where
T: DeserializeOwned,
E: DeserializeOwned + Display,
{
use bincode::Options;
let opts = nym_http_api_common::make_bincode_serializer();
match opts.deserialize(&content) {
Ok(data) => Ok(data),
Err(err) => {
let content = decode_as_text(&content, headers);
Err(HttpClientError::ResponseDecodeFailure {
message: err.to_string(),
content,
})
}
}
}
fn decode_raw_response<T, E>(headers: &HeaderMap, content: Bytes) -> Result<T, HttpClientError<E>>
where
T: DeserializeOwned,
E: DeserializeOwned + Display,
{
// if content type header is missing, fallback to our old default, json
let mime = try_get_mime_type(headers).unwrap_or(mime::APPLICATION_JSON);
debug!("attempting to parse response as {mime}");
// unfortunately we can't use stronger typing for subtype as "bincode" is not a defined mime type
match (mime.type_(), mime.subtype().as_str()) {
(mime::APPLICATION, "json") => decode_as_json(headers, content),
(mime::APPLICATION, "bincode") => decode_as_bincode(headers, content),
(_, _) => {
debug!("unrecognised mime type {mime}. falling back to json decoding...");
decode_as_json(headers, content)
}
}
}
fn try_get_mime_type(headers: &HeaderMap) -> Option<Mime> {
headers
.get(CONTENT_TYPE)
.and_then(|value| value.to_str().ok())
.and_then(|value| value.parse::<Mime>().ok())
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
+35 -11
View File
@@ -11,20 +11,44 @@ license.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
axum-client-ip.workspace = true axum = { workspace = true, optional = true }
axum.workspace = true axum-client-ip = { workspace = true, optional = true }
bytes = { workspace = true } bincode = { workspace = true }
colored.workspace = true bytes = { workspace = true, optional = true }
futures = { workspace = true } colored = { workspace = true, optional = true }
mime = { workspace = true } futures = { workspace = true, optional = true }
mime = { workspace = true, optional = true }
serde = { workspace = true, features = ["derive"] } serde = { workspace = true, features = ["derive"] }
serde_json.workspace = true serde_yaml = { workspace = true, optional = true }
serde_yaml = { workspace = true } subtle = { workspace = true, optional = true }
subtle.workspace = true time = { workspace = true, optional = true, features = ["macros"] }
tower = { workspace = true } tower = { workspace = true, optional = true }
tracing.workspace = true tracing.workspace = true
utoipa = { workspace = true, optional = true } utoipa = { workspace = true, optional = true }
zeroize = { workspace = true } zeroize = { workspace = true, optional = true }
[features] [features]
default = []
output = [
"axum",
"bytes",
"mime",
"serde_yaml",
"time",
"time/formatting"
]
middleware = [
"axum",
"axum-client-ip",
"colored",
"futures",
"subtle",
"tower",
"zeroize"
]
utoipa = ["dep:utoipa"] utoipa = ["dep:utoipa"]
[lints]
workspace = true
+13 -85
View File
@@ -1,92 +1,20 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net> // Copyright 2023-2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use axum::http::{header, HeaderValue, StatusCode}; #[cfg(feature = "middleware")]
use axum::response::{IntoResponse, Response};
use axum::Json;
use bytes::{BufMut, BytesMut};
use serde::{Deserialize, Serialize};
pub mod middleware; pub mod middleware;
#[derive(Debug, Clone)] #[cfg(feature = "output")]
pub enum FormattedResponse<T> { pub mod response;
Json(Json<T>),
Yaml(Yaml<T>),
}
impl<T> IntoResponse for FormattedResponse<T> // don't break existing imports
where #[cfg(feature = "output")]
T: Serialize, pub use response::*;
{
fn into_response(self) -> Response {
match self {
FormattedResponse::Json(json_response) => json_response.into_response(),
FormattedResponse::Yaml(yaml_response) => yaml_response.into_response(),
}
}
}
#[derive(Default, Debug, Serialize, Deserialize, Copy, Clone)] // be explicit about those values because bincode uses different defaults in different places
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] pub fn make_bincode_serializer() -> impl ::bincode::Options {
#[serde(rename_all = "lowercase")] use ::bincode::Options;
pub enum Output { ::bincode::DefaultOptions::new()
#[default] .with_little_endian()
Json, .with_varint_encoding()
Yaml,
}
#[derive(Default, Debug, Serialize, Deserialize, Copy, Clone)]
#[cfg_attr(feature = "utoipa", derive(utoipa::IntoParams, utoipa::ToSchema))]
#[serde(default)]
pub struct OutputParams {
pub output: Option<Output>,
}
impl Output {
pub fn to_response<T: Serialize>(self, data: T) -> FormattedResponse<T> {
match self {
Output::Json => FormattedResponse::Json(Json(data)),
Output::Yaml => FormattedResponse::Yaml(Yaml(data)),
}
}
}
#[derive(Debug, Clone, Copy, Default)]
#[must_use]
pub struct Yaml<T>(pub T);
impl<T> From<T> for Yaml<T> {
fn from(inner: T) -> Self {
Self(inner)
}
}
impl<T> IntoResponse for Yaml<T>
where
T: Serialize,
{
// replicates axum's Json
fn into_response(self) -> Response {
let mut buf = BytesMut::with_capacity(128).writer();
match serde_yaml::to_writer(&mut buf, &self.0) {
Ok(()) => (
[(
header::CONTENT_TYPE,
HeaderValue::from_static("application/yaml"),
)],
buf.into_inner().freeze(),
)
.into_response(),
Err(err) => (
StatusCode::INTERNAL_SERVER_ERROR,
[(
header::CONTENT_TYPE,
HeaderValue::from_static(mime::TEXT_PLAIN_UTF_8.as_ref()),
)],
err.to_string(),
)
.into_response(),
}
}
} }
@@ -0,0 +1,49 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::response::{error_response, ResponseWrapper};
use axum::http::header::IntoHeaderName;
use axum::http::{header, HeaderValue};
use axum::response::{IntoResponse, Response};
use bytes::{BufMut, BytesMut};
use serde::Serialize;
#[derive(Debug, Clone, Default)]
#[must_use]
pub struct Bincode<T>(pub(crate) ResponseWrapper<T>);
impl<T> From<T> for Bincode<T> {
fn from(response: T) -> Self {
Bincode(ResponseWrapper::new(response).with_header(
header::CONTENT_TYPE,
HeaderValue::from_static("application/bincode"),
))
}
}
impl<T> Bincode<T> {
pub(crate) fn with_header(
mut self,
name: impl IntoHeaderName,
value: impl Into<HeaderValue>,
) -> Self {
self.0.headers.insert(name, value.into());
self
}
}
impl<T> IntoResponse for Bincode<T>
where
T: Serialize,
{
// replicates axum's Json
fn into_response(self) -> Response {
use bincode::Options;
let mut buf = BytesMut::with_capacity(128).writer();
match crate::make_bincode_serializer().serialize_into(&mut buf, &self.0.data) {
Ok(()) => (self.0.headers, buf.into_inner().freeze()).into_response(),
Err(err) => error_response(err),
}
}
}
@@ -0,0 +1,49 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::response::{error_response, ResponseWrapper};
use axum::http::header::IntoHeaderName;
use axum::http::{header, HeaderValue};
use axum::response::{IntoResponse, Response};
use bytes::{BufMut, BytesMut};
use serde::Serialize;
use utoipa::gen::serde_json;
// don't use axum's Json directly as we need to be able to define custom headers
#[derive(Debug, Clone, Default)]
#[must_use]
pub struct Json<T>(pub(crate) ResponseWrapper<T>);
impl<T> From<T> for Json<T> {
fn from(response: T) -> Self {
Json(ResponseWrapper::new(response).with_header(
header::CONTENT_TYPE,
HeaderValue::from_static(mime::APPLICATION_JSON.as_ref()),
))
}
}
impl<T> Json<T> {
pub(crate) fn with_header(
mut self,
name: impl IntoHeaderName,
value: impl Into<HeaderValue>,
) -> Self {
self.0.headers.insert(name, value.into());
self
}
}
impl<T> IntoResponse for Json<T>
where
T: Serialize,
{
fn into_response(self) -> Response {
let mut buf = BytesMut::with_capacity(128).writer();
match serde_json::to_writer(&mut buf, &self.0.data) {
Ok(()) => (self.0.headers, buf.into_inner().freeze()).into_response(),
Err(err) => error_response(err),
}
}
}
+190
View File
@@ -0,0 +1,190 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use axum::http::header::IntoHeaderName;
use axum::http::{header, HeaderMap, HeaderValue, StatusCode};
use axum::response::{IntoResponse, Response};
use serde::{Deserialize, Serialize};
use std::time::Duration;
use time::format_description::BorrowedFormatItem;
use time::macros::{format_description, offset};
use time::OffsetDateTime;
pub mod bincode;
pub mod json;
pub mod yaml;
pub use json::Json;
pub use yaml::Yaml;
pub use bincode::Bincode;
#[derive(Debug, Clone, Default)]
pub(crate) struct ResponseWrapper<T> {
data: T,
headers: HeaderMap,
}
impl<T> ResponseWrapper<T> {
pub(crate) fn new(response: T) -> ResponseWrapper<T> {
ResponseWrapper {
data: response,
headers: Default::default(),
}
}
#[must_use]
pub(crate) fn with_header(
mut self,
name: impl IntoHeaderName,
value: impl Into<HeaderValue>,
) -> Self {
self.headers.insert(name, value.into());
self
}
}
#[derive(Debug, Clone)]
pub enum FormattedResponse<T> {
Json(Json<T>),
Yaml(Yaml<T>),
Bincode(Bincode<T>),
}
impl<T> FormattedResponse<T> {
pub fn into_inner(self) -> T {
match self {
FormattedResponse::Json(inner) => inner.0.data,
FormattedResponse::Yaml(inner) => inner.0.data,
FormattedResponse::Bincode(inner) => inner.0.data,
}
}
#[must_use]
pub fn with_header(
self,
name: impl IntoHeaderName,
value: impl Into<HeaderValue>,
) -> FormattedResponse<T> {
match self {
FormattedResponse::Json(inner) => {
FormattedResponse::Json(inner.with_header(name, value))
}
FormattedResponse::Yaml(inner) => {
FormattedResponse::Yaml(inner.with_header(name, value))
}
FormattedResponse::Bincode(inner) => {
FormattedResponse::Bincode(inner.with_header(name, value))
}
}
}
/// Set the `expires` header on the response to the provided expiration.
/// Internally it will perform conversions to make sure the value is set in GMT offset,
/// e.g. `Expires: Wed, 21 Oct 2015 07:28:00 GMT`
#[must_use]
pub fn with_expires_header(self, expiration: OffsetDateTime) -> FormattedResponse<T> {
// as per RFC-7234 (section 5.3) EXPIRES header has to use value formatted
// as defined in RFC-7231 (section 7.1.1.1)
// (preferred format (IMF-fixdate) uses RFC-5322 (section 3.3)
let formatted = format_rfc5352(expiration);
// SAFETY: our formatted datetime doesn't contain forbidden characters
#[allow(clippy::unwrap_used)]
self.with_header(header::EXPIRES, HeaderValue::try_from(formatted).unwrap())
}
/// Work similarly to `with_expires_header`, but rather than setting explicit expiration value,
/// it adds the provided time delta to the current time instead.
#[must_use]
pub fn with_expires_header_delta(self, expires_in: Duration) -> FormattedResponse<T> {
self.with_expires_header(OffsetDateTime::now_utc() + expires_in)
}
}
impl<T> IntoResponse for FormattedResponse<T>
where
T: Serialize,
{
fn into_response(self) -> Response {
match self {
FormattedResponse::Json(json_response) => json_response.into_response(),
FormattedResponse::Yaml(yaml_response) => yaml_response.into_response(),
FormattedResponse::Bincode(bincode_response) => bincode_response.into_response(),
}
}
}
#[derive(Default, Debug, Serialize, Deserialize, Copy, Clone)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[serde(rename_all = "lowercase")]
pub enum Output {
#[default]
Json,
Yaml,
Bincode,
}
#[derive(Default, Debug, Serialize, Deserialize, Copy, Clone)]
#[cfg_attr(feature = "utoipa", derive(utoipa::IntoParams, utoipa::ToSchema))]
#[serde(default)]
pub struct OutputParams {
pub output: Option<Output>,
}
impl Output {
pub fn to_response<T: Serialize>(self, data: T) -> FormattedResponse<T> {
match self {
Output::Json => FormattedResponse::Json(Json::from(data)),
Output::Yaml => FormattedResponse::Yaml(Yaml::from(data)),
Output::Bincode => FormattedResponse::Bincode(Bincode::from(data)),
}
}
}
pub(crate) fn error_response<E: ToString>(err: E) -> Response {
(
StatusCode::INTERNAL_SERVER_ERROR,
[(
header::CONTENT_TYPE,
HeaderValue::from_static(mime::TEXT_PLAIN_UTF_8.as_ref()),
)],
err.to_string(),
)
.into_response()
}
// SAFETY: this hardcoded datetime formatter is valid
#[allow(clippy::unwrap_used)]
fn format_rfc5352(datetime: OffsetDateTime) -> String {
// the time must be using GMT (UTC) offset
let normalised = datetime.to_offset(offset!(UTC));
normalised.format(&rfc5322()).unwrap()
}
// NOTE: this function is purposely not made public as it cannot guarantee caller
// has correctly ensured their date is using correct GMT offset
fn rfc5322() -> &'static [BorrowedFormatItem<'static>] {
// D, d M Y H:i:s T
format_description!(
"[weekday repr:short], [day] [month repr:short] [year] [hour]:[minute]:[second] GMT"
)
}
#[cfg(test)]
mod tests {
use crate::response::format_rfc5352;
use time::macros::datetime;
#[test]
fn rfc5322_formatting() {
let utc_date = datetime!(2021-08-23 12:13:14 UTC);
let non_utc_date = datetime!(2021-08-23 12:13:14 -1);
assert_eq!("Mon, 23 Aug 2021 12:13:14 GMT", format_rfc5352(utc_date));
assert_eq!(
"Mon, 23 Aug 2021 13:13:14 GMT",
format_rfc5352(non_utc_date)
);
}
}
@@ -0,0 +1,47 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::response::{error_response, ResponseWrapper};
use axum::http::header::IntoHeaderName;
use axum::http::{header, HeaderValue};
use axum::response::{IntoResponse, Response};
use bytes::{BufMut, BytesMut};
use serde::Serialize;
#[derive(Debug, Clone, Default)]
#[must_use]
pub struct Yaml<T>(pub(crate) ResponseWrapper<T>);
impl<T> From<T> for Yaml<T> {
fn from(response: T) -> Self {
Yaml(ResponseWrapper::new(response).with_header(
header::CONTENT_TYPE,
HeaderValue::from_static("application/yaml"),
))
}
}
impl<T> Yaml<T> {
pub(crate) fn with_header(
mut self,
name: impl IntoHeaderName,
value: impl Into<HeaderValue>,
) -> Self {
self.0.headers.insert(name, value.into());
self
}
}
impl<T> IntoResponse for Yaml<T>
where
T: Serialize,
{
// replicates axum's Json
fn into_response(self) -> Response {
let mut buf = BytesMut::with_capacity(128).writer();
match serde_yaml::to_writer(&mut buf, &self.0.data) {
Ok(()) => (self.0.headers, buf.into_inner().freeze()).into_response(),
Err(err) => error_response(err),
}
}
}
+1 -1
View File
@@ -116,7 +116,7 @@ nym-bin-common = { path = "../common/bin-common", features = ["output_format", "
nym-node-tester-utils = { path = "../common/node-tester-utils" } nym-node-tester-utils = { path = "../common/node-tester-utils" }
nym-node-requests = { path = "../nym-node/nym-node-requests" } nym-node-requests = { path = "../nym-node/nym-node-requests" }
nym-types = { path = "../common/types" } nym-types = { path = "../common/types" }
nym-http-api-common = { path = "../common/http-api-common", features = ["utoipa"] } nym-http-api-common = { path = "../common/http-api-common", features = ["utoipa", "output", "middleware"] }
nym-serde-helpers = { path = "../common/serde-helpers", features = ["date"] } nym-serde-helpers = { path = "../common/serde-helpers", features = ["date"] }
nym-ticketbooks-merkle = { path = "../common/ticketbooks-merkle" } nym-ticketbooks-merkle = { path = "../common/ticketbooks-merkle" }
nym-statistics-common = { path = "../common/statistics" } nym-statistics-common = { path = "../common/statistics" }
+42 -12
View File
@@ -3,8 +3,10 @@
use crate::node_status_api::models::{AxumErrorResponse, AxumResult}; use crate::node_status_api::models::{AxumErrorResponse, AxumResult};
use crate::support::http::state::AppState; use crate::support::http::state::AppState;
use axum::extract::Query;
use axum::{extract, Router}; use axum::{extract, Router};
use nym_api_requests::models::CirculatingSupplyResponse; use nym_api_requests::models::CirculatingSupplyResponse;
use nym_http_api_common::{FormattedResponse, OutputParams};
use nym_validator_client::nyxd::Coin; use nym_validator_client::nyxd::Coin;
pub(crate) fn circulating_supply_routes() -> Router<AppState> { pub(crate) fn circulating_supply_routes() -> Router<AppState> {
@@ -25,18 +27,26 @@ pub(crate) fn circulating_supply_routes() -> Router<AppState> {
get, get,
path = "/v1/circulating-supply", path = "/v1/circulating-supply",
responses( responses(
(status = 200, body = CirculatingSupplyResponse) (status = 200, content(
) (CirculatingSupplyResponse = "application/json"),
(CirculatingSupplyResponse = "application/yaml"),
(CirculatingSupplyResponse = "application/bincode")
))
),
params(OutputParams)
)] )]
async fn get_full_circulating_supply( async fn get_full_circulating_supply(
Query(output): Query<OutputParams>,
extract::State(state): extract::State<AppState>, extract::State(state): extract::State<AppState>,
) -> AxumResult<axum::Json<CirculatingSupplyResponse>> { ) -> AxumResult<FormattedResponse<CirculatingSupplyResponse>> {
let output = output.output.unwrap_or_default();
match state match state
.circulating_supply_cache() .circulating_supply_cache()
.get_circulating_supply() .get_circulating_supply()
.await .await
{ {
Some(value) => Ok(value.into()), Some(value) => Ok(output.to_response(value)),
None => Err(AxumErrorResponse::internal_msg("unavailable")), None => Err(AxumErrorResponse::internal_msg("unavailable")),
} }
} }
@@ -46,12 +56,19 @@ async fn get_full_circulating_supply(
get, get,
path = "/v1/circulating-supply/total-supply-value", path = "/v1/circulating-supply/total-supply-value",
responses( responses(
(status = 200, body = [f64]) (status = 200, content(
) (f64 = "application/json"),
(f64 = "application/yaml"),
(f64 = "application/bincode")
))
),
params(OutputParams)
)] )]
async fn get_total_supply( async fn get_total_supply(
Query(output): Query<OutputParams>,
extract::State(state): extract::State<AppState>, extract::State(state): extract::State<AppState>,
) -> AxumResult<axum::Json<f64>> { ) -> AxumResult<FormattedResponse<f64>> {
let output = output.output.unwrap_or_default();
let full_circulating_supply = match state let full_circulating_supply = match state
.circulating_supply_cache() .circulating_supply_cache()
.get_circulating_supply() .get_circulating_supply()
@@ -61,7 +78,9 @@ async fn get_total_supply(
None => return Err(AxumErrorResponse::internal_msg("unavailable")), None => return Err(AxumErrorResponse::internal_msg("unavailable")),
}; };
Ok(unym_coin_to_float_unym(full_circulating_supply.total_supply.into()).into()) let total_supply = unym_coin_to_float_unym(full_circulating_supply.total_supply.into());
Ok(output.to_response(total_supply))
} }
#[utoipa::path( #[utoipa::path(
@@ -69,12 +88,20 @@ async fn get_total_supply(
get, get,
path = "/v1/circulating-supply/circulating-supply-value", path = "/v1/circulating-supply/circulating-supply-value",
responses( responses(
(status = 200, body = [f64]) (status = 200, content(
) (f64 = "application/json"),
(f64 = "application/yaml"),
(f64 = "application/bincode")
))
),
params(OutputParams)
)] )]
async fn get_circulating_supply( async fn get_circulating_supply(
Query(output): Query<OutputParams>,
extract::State(state): extract::State<AppState>, extract::State(state): extract::State<AppState>,
) -> AxumResult<axum::Json<f64>> { ) -> AxumResult<FormattedResponse<f64>> {
let output = output.output.unwrap_or_default();
let full_circulating_supply = match state let full_circulating_supply = match state
.circulating_supply_cache() .circulating_supply_cache()
.get_circulating_supply() .get_circulating_supply()
@@ -84,7 +111,10 @@ async fn get_circulating_supply(
None => return Err(AxumErrorResponse::internal_msg("unavailable")), None => return Err(AxumErrorResponse::internal_msg("unavailable")),
}; };
Ok(unym_coin_to_float_unym(full_circulating_supply.circulating_supply.into()).into()) let circulating_supply =
unym_coin_to_float_unym(full_circulating_supply.circulating_supply.into());
Ok(output.to_response(circulating_supply))
} }
// TODO: this is not the best place to put it, it should be more centralised, // TODO: this is not the best place to put it, it should be more centralised,
+42 -20
View File
@@ -7,12 +7,13 @@ use crate::ecash::state::EcashState;
use crate::node_status_api::models::AxumResult; use crate::node_status_api::models::AxumResult;
use crate::support::http::state::AppState; use crate::support::http::state::AppState;
use axum::extract::{Query, State}; use axum::extract::{Query, State};
use axum::{Json, Router}; use axum::Router;
use nym_api_requests::ecash::models::{ use nym_api_requests::ecash::models::{
AggregatedCoinIndicesSignatureResponse, AggregatedExpirationDateSignatureResponse, AggregatedCoinIndicesSignatureResponse, AggregatedExpirationDateSignatureResponse,
}; };
use nym_api_requests::ecash::VerificationKeyResponse; use nym_api_requests::ecash::VerificationKeyResponse;
use nym_ecash_time::{cred_exp_date, EcashTime}; use nym_ecash_time::{cred_exp_date, EcashTime};
use nym_http_api_common::{FormattedResponse, Output};
use nym_validator_client::nym_api::rfc_3339_date; use nym_validator_client::nym_api::rfc_3339_date;
use serde::Deserialize; use serde::Deserialize;
use std::sync::Arc; use std::sync::Arc;
@@ -45,26 +46,32 @@ pub(crate) fn aggregation_routes() -> Router<AppState> {
), ),
path = "/v1/ecash/master-verification-key", path = "/v1/ecash/master-verification-key",
responses( responses(
(status = 200, body = VerificationKeyResponse) (status = 200, content(
) (VerificationKeyResponse = "application/json"),
(VerificationKeyResponse = "application/yaml"),
(VerificationKeyResponse = "application/bincode")
))
),
)] )]
async fn master_verification_key( async fn master_verification_key(
State(state): State<Arc<EcashState>>, State(state): State<Arc<EcashState>>,
Query(EpochIdParam { epoch_id }): Query<EpochIdParam>, Query(EpochIdParam { epoch_id, output }): Query<EpochIdParam>,
) -> AxumResult<Json<VerificationKeyResponse>> { ) -> AxumResult<FormattedResponse<VerificationKeyResponse>> {
trace!("aggregated_verification_key request"); trace!("aggregated_verification_key request");
let output = output.unwrap_or_default();
// see if we're not in the middle of new dkg // see if we're not in the middle of new dkg
state.ensure_dkg_not_in_progress().await?; state.ensure_dkg_not_in_progress().await?;
let key = state.master_verification_key(epoch_id).await?; let key = state.master_verification_key(epoch_id).await?;
Ok(Json(VerificationKeyResponse::new(key.clone()))) Ok(output.to_response(VerificationKeyResponse::new(key.clone())))
} }
#[derive(Deserialize, IntoParams)] #[derive(Deserialize, IntoParams)]
struct ExpirationDateParam { struct ExpirationDateParam {
expiration_date: Option<String>, expiration_date: Option<String>,
output: Option<Output>,
} }
#[utoipa::path( #[utoipa::path(
@@ -75,14 +82,22 @@ struct ExpirationDateParam {
), ),
path = "/v1/ecash/aggregated-expiration-date-signatures", path = "/v1/ecash/aggregated-expiration-date-signatures",
responses( responses(
(status = 200, body = AggregatedExpirationDateSignatureResponse) (status = 200, content(
) (AggregatedExpirationDateSignatureResponse = "application/json"),
(AggregatedExpirationDateSignatureResponse = "application/yaml"),
(AggregatedExpirationDateSignatureResponse = "application/bincode")
))
),
)] )]
async fn expiration_date_signatures( async fn expiration_date_signatures(
State(state): State<Arc<EcashState>>, State(state): State<Arc<EcashState>>,
Query(ExpirationDateParam { expiration_date }): Query<ExpirationDateParam>, Query(ExpirationDateParam {
) -> AxumResult<Json<AggregatedExpirationDateSignatureResponse>> { expiration_date,
output,
}): Query<ExpirationDateParam>,
) -> AxumResult<FormattedResponse<AggregatedExpirationDateSignatureResponse>> {
trace!("aggregated_expiration_date_signatures request"); trace!("aggregated_expiration_date_signatures request");
let output = output.unwrap_or_default();
let expiration_date = match expiration_date { let expiration_date = match expiration_date {
None => cred_exp_date().ecash_date(), None => cred_exp_date().ecash_date(),
@@ -97,11 +112,13 @@ async fn expiration_date_signatures(
.master_expiration_date_signatures(expiration_date) .master_expiration_date_signatures(expiration_date)
.await?; .await?;
Ok(Json(AggregatedExpirationDateSignatureResponse { Ok(
epoch_id: expiration_date_signatures.epoch_id, output.to_response(AggregatedExpirationDateSignatureResponse {
expiration_date, epoch_id: expiration_date_signatures.epoch_id,
signatures: expiration_date_signatures.signatures.clone(), expiration_date,
})) signatures: expiration_date_signatures.signatures.clone(),
}),
)
} }
#[utoipa::path( #[utoipa::path(
@@ -112,21 +129,26 @@ async fn expiration_date_signatures(
), ),
path = "/v1/ecash/aggregated-coin-indices-signatures", path = "/v1/ecash/aggregated-coin-indices-signatures",
responses( responses(
(status = 200, body = AggregatedCoinIndicesSignatureResponse) (status = 200, content(
) (AggregatedCoinIndicesSignatureResponse = "application/json"),
(AggregatedCoinIndicesSignatureResponse = "application/yaml"),
(AggregatedCoinIndicesSignatureResponse = "application/bincode")
))
),
)] )]
async fn coin_indices_signatures( async fn coin_indices_signatures(
Query(EpochIdParam { epoch_id }): Query<EpochIdParam>, Query(EpochIdParam { epoch_id, output }): Query<EpochIdParam>,
State(state): State<Arc<EcashState>>, State(state): State<Arc<EcashState>>,
) -> AxumResult<Json<AggregatedCoinIndicesSignatureResponse>> { ) -> AxumResult<FormattedResponse<AggregatedCoinIndicesSignatureResponse>> {
trace!("aggregated_coin_indices_signatures request"); trace!("aggregated_coin_indices_signatures request");
let output = output.unwrap_or_default();
// see if we're not in the middle of new dkg // see if we're not in the middle of new dkg
state.ensure_dkg_not_in_progress().await?; state.ensure_dkg_not_in_progress().await?;
let coin_indices_signatures = state.master_coin_index_signatures(epoch_id).await?; let coin_indices_signatures = state.master_coin_index_signatures(epoch_id).await?;
Ok(Json(AggregatedCoinIndicesSignatureResponse { Ok(output.to_response(AggregatedCoinIndicesSignatureResponse {
epoch_id: coin_indices_signatures.epoch_id, epoch_id: coin_indices_signatures.epoch_id,
signatures: coin_indices_signatures.signatures.clone(), signatures: coin_indices_signatures.signatures.clone(),
})) }))
+3
View File
@@ -1,7 +1,10 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net> // Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
use nym_http_api_common::Output;
#[derive(serde::Deserialize, utoipa::IntoParams)] #[derive(serde::Deserialize, utoipa::IntoParams)]
pub(super) struct EpochIdParam { pub(super) struct EpochIdParam {
pub(super) epoch_id: Option<u64>, pub(super) epoch_id: Option<u64>,
pub(super) output: Option<Output>,
} }
+61 -27
View File
@@ -13,6 +13,7 @@ use nym_api_requests::ecash::models::{
IssuedTicketbooksForCountResponse, IssuedTicketbooksForResponse, IssuedTicketbooksForCountResponse, IssuedTicketbooksForResponse,
IssuedTicketbooksOnCountResponse, SignableMessageBody, IssuedTicketbooksOnCountResponse, SignableMessageBody,
}; };
use nym_http_api_common::{FormattedResponse, OutputParams};
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::cmp::min; use std::cmp::min;
@@ -80,15 +81,20 @@ pub(crate) struct IssuanceDatePathParam {
path = "/issued-ticketbooks-count", path = "/issued-ticketbooks-count",
context_path = "/v1/ecash", context_path = "/v1/ecash",
responses( responses(
(status = 200, body = IssuedTicketbooksCountResponse), (status = 200, content(
(IssuedTicketbooksCountResponse = "application/json"),
(IssuedTicketbooksCountResponse = "application/yaml"),
(IssuedTicketbooksCountResponse = "application/bincode")
)),
(status = 400, body = String, description = "this nym-api is not an ecash signer in the current epoch"), (status = 400, body = String, description = "this nym-api is not an ecash signer in the current epoch"),
) )
)] )]
async fn issued_ticketbooks_count( async fn issued_ticketbooks_count(
Query(pagination): Query<PaginationRequest>, Query(pagination): Query<PaginationRequest>,
State(state): State<Arc<EcashState>>, State(state): State<Arc<EcashState>>,
) -> AxumResult<Json<IssuedTicketbooksCountResponse>> { ) -> AxumResult<FormattedResponse<IssuedTicketbooksCountResponse>> {
state.ensure_signer().await?; state.ensure_signer().await?;
let output = pagination.output.unwrap_or_default();
let page = pagination.page.unwrap_or_default(); let page = pagination.page.unwrap_or_default();
let per_page = min( let per_page = min(
@@ -98,9 +104,7 @@ async fn issued_ticketbooks_count(
MAX_ISSUANCE_COUNT_PAGE_SIZE, MAX_ISSUANCE_COUNT_PAGE_SIZE,
); );
Ok(Json( Ok(output.to_response(state.get_issued_ticketbooks_count(page, per_page).await?))
state.get_issued_ticketbooks_count(page, per_page).await?,
))
} }
/// Returns number of issued ticketbooks for particular expiration date. /// Returns number of issued ticketbooks for particular expiration date.
@@ -109,22 +113,28 @@ async fn issued_ticketbooks_count(
tag = "Ecash", tag = "Ecash",
get, get,
params( params(
ExpirationDatePathParam ExpirationDatePathParam, OutputParams
), ),
path = "/issued-ticketbooks-for-count/{expiration_date}", path = "/issued-ticketbooks-for-count/{expiration_date}",
context_path = "/v1/ecash", context_path = "/v1/ecash",
responses( responses(
(status = 200, body = IssuedTicketbooksForCountResponse), (status = 200, content(
(IssuedTicketbooksForCountResponse = "application/json"),
(IssuedTicketbooksForCountResponse = "application/yaml"),
(IssuedTicketbooksForCountResponse = "application/bincode")
)),
(status = 400, body = String, description = "this nym-api is not an ecash signer in the current epoch"), (status = 400, body = String, description = "this nym-api is not an ecash signer in the current epoch"),
) )
)] )]
async fn issued_ticketbooks_for_count( async fn issued_ticketbooks_for_count(
Query(output): Query<OutputParams>,
Path(ExpirationDatePathParam { expiration_date }): Path<ExpirationDatePathParam>, Path(ExpirationDatePathParam { expiration_date }): Path<ExpirationDatePathParam>,
State(state): State<Arc<EcashState>>, State(state): State<Arc<EcashState>>,
) -> AxumResult<Json<IssuedTicketbooksForCountResponse>> { ) -> AxumResult<FormattedResponse<IssuedTicketbooksForCountResponse>> {
state.ensure_signer().await?; state.ensure_signer().await?;
let output = output.output.unwrap_or_default();
Ok(Json( Ok(output.to_response(
state state
.get_issued_ticketbooks_for_count(expiration_date) .get_issued_ticketbooks_for_count(expiration_date)
.await?, .await?,
@@ -137,46 +147,56 @@ async fn issued_ticketbooks_for_count(
tag = "Ecash", tag = "Ecash",
get, get,
params( params(
IssuanceDatePathParam IssuanceDatePathParam, OutputParams
), ),
path = "/issued-ticketbooks-on-count/{issuance_date}", path = "/issued-ticketbooks-on-count/{issuance_date}",
context_path = "/v1/ecash", context_path = "/v1/ecash",
responses( responses(
(status = 200, body = IssuedTicketbooksOnCountResponse), (status = 200, content(
(IssuedTicketbooksOnCountResponse = "application/json"),
(IssuedTicketbooksOnCountResponse = "application/yaml"),
(IssuedTicketbooksOnCountResponse = "application/bincode")
)),
(status = 400, body = String, description = "this nym-api is not an ecash signer in the current epoch"), (status = 400, body = String, description = "this nym-api is not an ecash signer in the current epoch"),
) )
)] )]
async fn issued_ticketbooks_on_count( async fn issued_ticketbooks_on_count(
Query(output): Query<OutputParams>,
Path(IssuanceDatePathParam { issuance_date }): Path<IssuanceDatePathParam>, Path(IssuanceDatePathParam { issuance_date }): Path<IssuanceDatePathParam>,
State(state): State<Arc<EcashState>>, State(state): State<Arc<EcashState>>,
) -> AxumResult<Json<IssuedTicketbooksOnCountResponse>> { ) -> AxumResult<FormattedResponse<IssuedTicketbooksOnCountResponse>> {
state.ensure_signer().await?; state.ensure_signer().await?;
let output = output.output.unwrap_or_default();
Ok(Json( Ok(output.to_response(state.get_issued_ticketbooks_on_count(issuance_date).await?))
state.get_issued_ticketbooks_on_count(issuance_date).await?,
))
} }
#[utoipa::path( #[utoipa::path(
tag = "Ecash", tag = "Ecash",
get, get,
params( params(
ExpirationDatePathParam ExpirationDatePathParam, OutputParams
), ),
path = "/issued-ticketbooks-for/{expiration_date}", path = "/issued-ticketbooks-for/{expiration_date}",
context_path = "/v1/ecash", context_path = "/v1/ecash",
responses( responses(
(status = 200, body = IssuedTicketbooksForResponse), (status = 200, content(
(IssuedTicketbooksForResponse = "application/json"),
(IssuedTicketbooksForResponse = "application/yaml"),
(IssuedTicketbooksForResponse = "application/bincode")
)),
(status = 400, body = String, description = "this nym-api is not an ecash signer in the current epoch"), (status = 400, body = String, description = "this nym-api is not an ecash signer in the current epoch"),
) )
)] )]
async fn issued_ticketbooks_for( async fn issued_ticketbooks_for(
Query(output): Query<OutputParams>,
State(state): State<Arc<EcashState>>, State(state): State<Arc<EcashState>>,
Path(ExpirationDatePathParam { expiration_date }): Path<ExpirationDatePathParam>, Path(ExpirationDatePathParam { expiration_date }): Path<ExpirationDatePathParam>,
) -> AxumResult<Json<IssuedTicketbooksForResponse>> { ) -> AxumResult<FormattedResponse<IssuedTicketbooksForResponse>> {
state.ensure_signer().await?; state.ensure_signer().await?;
let output = output.output.unwrap_or_default();
Ok(Json( Ok(output.to_response(
state state
.get_issued_ticketbooks_deposits_on(expiration_date) .get_issued_ticketbooks_deposits_on(expiration_date)
.await? .await?
@@ -191,17 +211,24 @@ async fn issued_ticketbooks_for(
path = "/issued-ticketbooks-challenge-commitment", path = "/issued-ticketbooks-challenge-commitment",
context_path = "/v1/ecash", context_path = "/v1/ecash",
responses( responses(
(status = 200, body = IssuedTicketbooksChallengeCommitmentResponse), (status = 200, content(
(IssuedTicketbooksChallengeCommitmentResponse = "application/json"),
(IssuedTicketbooksChallengeCommitmentResponse = "application/yaml"),
(IssuedTicketbooksChallengeCommitmentResponse = "application/bincode")
)),
(status = 400, body = String, description = "this nym-api is not an ecash signer in the current epoch"), (status = 400, body = String, description = "this nym-api is not an ecash signer in the current epoch"),
) ),
params(OutputParams)
)] )]
async fn issued_ticketbooks_challenge_commitment( async fn issued_ticketbooks_challenge_commitment(
Query(output): Query<OutputParams>,
State(state): State<Arc<EcashState>>, State(state): State<Arc<EcashState>>,
Json(request): Json<IssuedTicketbooksChallengeCommitmentRequest>, Json(request): Json<IssuedTicketbooksChallengeCommitmentRequest>,
) -> AxumResult<Json<IssuedTicketbooksChallengeCommitmentResponse>> { ) -> AxumResult<FormattedResponse<IssuedTicketbooksChallengeCommitmentResponse>> {
state.ensure_signer().await?; state.ensure_signer().await?;
let output = output.output.unwrap_or_default();
Ok(Json( Ok(output.to_response(
state state
.get_issued_ticketbooks_challenge_commitment(request) .get_issued_ticketbooks_challenge_commitment(request)
.await? .await?
@@ -216,17 +243,24 @@ async fn issued_ticketbooks_challenge_commitment(
path = "/issued-ticketbooks-data", path = "/issued-ticketbooks-data",
context_path = "/v1/ecash", context_path = "/v1/ecash",
responses( responses(
(status = 200, body = IssuedTicketbooksDataResponse), (status = 200, content(
(IssuedTicketbooksDataResponse = "application/json"),
(IssuedTicketbooksDataResponse = "application/yaml"),
(IssuedTicketbooksDataResponse = "application/bincode")
)),
(status = 400, body = String, description = "this nym-api is not an ecash signer in the current epoch"), (status = 400, body = String, description = "this nym-api is not an ecash signer in the current epoch"),
) ),
params(OutputParams)
)] )]
async fn issued_ticketbooks_data( async fn issued_ticketbooks_data(
Query(output): Query<OutputParams>,
State(state): State<Arc<EcashState>>, State(state): State<Arc<EcashState>>,
Json(request): Json<IssuedTicketbooksDataRequest>, Json(request): Json<IssuedTicketbooksDataRequest>,
) -> AxumResult<Json<IssuedTicketbooksDataResponse>> { ) -> AxumResult<FormattedResponse<IssuedTicketbooksDataResponse>> {
state.ensure_signer().await?; state.ensure_signer().await?;
let output = output.output.unwrap_or_default();
Ok(Json( Ok(output.to_response(
state state
.get_issued_ticketbooks_data(request) .get_issued_ticketbooks_data(request)
.await? .await?
+39 -17
View File
@@ -14,6 +14,7 @@ use nym_api_requests::ecash::{
PartialExpirationDateSignatureResponse, PartialExpirationDateSignatureResponse,
}; };
use nym_ecash_time::{cred_exp_date, EcashTime}; use nym_ecash_time::{cred_exp_date, EcashTime};
use nym_http_api_common::{FormattedResponse, Output, OutputParams};
use nym_validator_client::nym_api::rfc_3339_date; use nym_validator_client::nym_api::rfc_3339_date;
use serde::Deserialize; use serde::Deserialize;
use std::ops::Deref; use std::ops::Deref;
@@ -41,16 +42,22 @@ pub(crate) fn partial_signing_routes() -> Router<AppState> {
request_body = BlindSignRequestBody, request_body = BlindSignRequestBody,
path = "/v1/ecash/blind-sign", path = "/v1/ecash/blind-sign",
responses( responses(
(status = 200, body = BlindedSignatureResponse), (status = 200, content(
(BlindedSignatureResponse = "application/json"),
(BlindedSignatureResponse = "application/yaml"),
(BlindedSignatureResponse = "application/bincode")
)),
(status = 400, body = String, description = "this nym-api is not an ecash signer in the current epoch"), (status = 400, body = String, description = "this nym-api is not an ecash signer in the current epoch"),
),
) params(OutputParams)
)] )]
async fn post_blind_sign( async fn post_blind_sign(
Query(output): Query<OutputParams>,
State(state): State<Arc<EcashState>>, State(state): State<Arc<EcashState>>,
Json(blind_sign_request_body): Json<BlindSignRequestBody>, Json(blind_sign_request_body): Json<BlindSignRequestBody>,
) -> AxumResult<Json<BlindedSignatureResponse>> { ) -> AxumResult<FormattedResponse<BlindedSignatureResponse>> {
state.ensure_signer().await?; state.ensure_signer().await?;
let output = output.output.unwrap_or_default();
debug!("Received blind sign request"); debug!("Received blind sign request");
trace!("body: {:?}", blind_sign_request_body); trace!("body: {:?}", blind_sign_request_body);
@@ -73,7 +80,7 @@ async fn post_blind_sign(
"checking if we have already issued credential for this deposit (deposit_id: {deposit_id})", "checking if we have already issued credential for this deposit (deposit_id: {deposit_id})",
); );
if let Some(blinded_signature) = state.already_issued(deposit_id).await? { if let Some(blinded_signature) = state.already_issued(deposit_id).await? {
return Ok(Json(BlindedSignatureResponse { blinded_signature })); return Ok(output.to_response(BlindedSignatureResponse { blinded_signature }));
} }
//check if account was blacklisted //check if account was blacklisted
@@ -101,12 +108,13 @@ async fn post_blind_sign(
.await?; .await?;
// finally return the credential to the client // finally return the credential to the client
Ok(Json(BlindedSignatureResponse { blinded_signature })) Ok(output.to_response(BlindedSignatureResponse { blinded_signature }))
} }
#[derive(Deserialize, IntoParams)] #[derive(Deserialize, IntoParams)]
struct ExpirationDateParam { struct ExpirationDateParam {
expiration_date: Option<String>, expiration_date: Option<String>,
output: Option<Output>,
} }
#[utoipa::path( #[utoipa::path(
@@ -117,15 +125,23 @@ struct ExpirationDateParam {
), ),
path = "/v1/ecash/partial-expiration-date-signatures", path = "/v1/ecash/partial-expiration-date-signatures",
responses( responses(
(status = 200, body = PartialExpirationDateSignatureResponse), (status = 200, content(
(PartialExpirationDateSignatureResponse = "application/json"),
(PartialExpirationDateSignatureResponse = "application/yaml"),
(PartialExpirationDateSignatureResponse = "application/bincode")
)),
(status = 400, body = String, description = "this nym-api is not an ecash signer in the current epoch"), (status = 400, body = String, description = "this nym-api is not an ecash signer in the current epoch"),
) )
)] )]
async fn partial_expiration_date_signatures( async fn partial_expiration_date_signatures(
State(state): State<Arc<EcashState>>, State(state): State<Arc<EcashState>>,
Query(ExpirationDateParam { expiration_date }): Query<ExpirationDateParam>, Query(ExpirationDateParam {
) -> AxumResult<Json<PartialExpirationDateSignatureResponse>> { expiration_date,
output,
}): Query<ExpirationDateParam>,
) -> AxumResult<FormattedResponse<PartialExpirationDateSignatureResponse>> {
state.ensure_signer().await?; state.ensure_signer().await?;
let output = output.unwrap_or_default();
let expiration_date = match expiration_date { let expiration_date = match expiration_date {
None => cred_exp_date().ecash_date(), None => cred_exp_date().ecash_date(),
@@ -140,7 +156,7 @@ async fn partial_expiration_date_signatures(
.partial_expiration_date_signatures(expiration_date) .partial_expiration_date_signatures(expiration_date)
.await?; .await?;
Ok(Json(PartialExpirationDateSignatureResponse { Ok(output.to_response(PartialExpirationDateSignatureResponse {
epoch_id: expiration_date_signatures.epoch_id, epoch_id: expiration_date_signatures.epoch_id,
expiration_date, expiration_date,
signatures: expiration_date_signatures.signatures.clone(), signatures: expiration_date_signatures.signatures.clone(),
@@ -155,14 +171,18 @@ async fn partial_expiration_date_signatures(
), ),
path = "/v1/ecash/partial-coin-indices-signatures", path = "/v1/ecash/partial-coin-indices-signatures",
responses( responses(
(status = 200, body = PartialExpirationDateSignatureResponse), (status = 200, content(
(PartialCoinIndicesSignatureResponse = "application/json"),
(PartialCoinIndicesSignatureResponse = "application/yaml"),
(PartialCoinIndicesSignatureResponse = "application/bincode")
)),
(status = 400, body = String, description = "this nym-api is not an ecash signer in the current epoch"), (status = 400, body = String, description = "this nym-api is not an ecash signer in the current epoch"),
) )
)] )]
async fn partial_coin_indices_signatures( async fn partial_coin_indices_signatures(
State(state): State<Arc<EcashState>>, State(state): State<Arc<EcashState>>,
Query(EpochIdParam { epoch_id }): Query<EpochIdParam>, Query(EpochIdParam { epoch_id, output }): Query<EpochIdParam>,
) -> AxumResult<Json<PartialCoinIndicesSignatureResponse>> { ) -> AxumResult<FormattedResponse<PartialCoinIndicesSignatureResponse>> {
state.ensure_signer().await?; state.ensure_signer().await?;
// see if we're not in the middle of new dkg // see if we're not in the middle of new dkg
@@ -170,8 +190,10 @@ async fn partial_coin_indices_signatures(
let coin_indices_signatures = state.partial_coin_index_signatures(epoch_id).await?; let coin_indices_signatures = state.partial_coin_index_signatures(epoch_id).await?;
Ok(Json(PartialCoinIndicesSignatureResponse { Ok(output
epoch_id: coin_indices_signatures.epoch_id, .unwrap_or_default()
signatures: coin_indices_signatures.signatures.clone(), .to_response(PartialCoinIndicesSignatureResponse {
})) epoch_id: coin_indices_signatures.epoch_id,
signatures: coin_indices_signatures.signatures.clone(),
}))
} }
+39 -21
View File
@@ -5,7 +5,7 @@ use crate::ecash::error::EcashError;
use crate::ecash::state::EcashState; use crate::ecash::state::EcashState;
use crate::node_status_api::models::{AxumErrorResponse, AxumResult}; use crate::node_status_api::models::{AxumErrorResponse, AxumResult};
use crate::support::http::state::AppState; use crate::support::http::state::AppState;
use axum::extract::State; use axum::extract::{Query, State};
use axum::http::StatusCode; use axum::http::StatusCode;
use axum::{Json, Router}; use axum::{Json, Router};
use nym_api_requests::constants::MIN_BATCH_REDEMPTION_DELAY; use nym_api_requests::constants::MIN_BATCH_REDEMPTION_DELAY;
@@ -15,6 +15,7 @@ use nym_api_requests::ecash::models::{
}; };
use nym_compact_ecash::identify::IdentifyResult; use nym_compact_ecash::identify::IdentifyResult;
use nym_ecash_time::EcashTime; use nym_ecash_time::EcashTime;
use nym_http_api_common::{FormattedResponse, Output, OutputParams};
use std::collections::HashSet; use std::collections::HashSet;
use std::ops::Deref; use std::ops::Deref;
use std::sync::Arc; use std::sync::Arc;
@@ -39,9 +40,10 @@ pub(crate) fn spending_routes() -> Router<AppState> {
const ONE_AM: Time = time!(1:00); const ONE_AM: Time = time!(1:00);
fn reject_ticket( fn reject_ticket(
output: Output,
reason: EcashTicketVerificationRejection, reason: EcashTicketVerificationRejection,
) -> AxumResult<Json<EcashTicketVerificationResponse>> { ) -> AxumResult<FormattedResponse<EcashTicketVerificationResponse>> {
Ok(Json(EcashTicketVerificationResponse::reject(reason))) Ok(output.to_response(EcashTicketVerificationResponse::reject(reason)))
} }
// TODO: optimise it; for now it's just dummy split of the original `verify_offline_credential` // TODO: optimise it; for now it's just dummy split of the original `verify_offline_credential`
@@ -52,23 +54,29 @@ fn reject_ticket(
request_body = VerifyEcashTicketBody, request_body = VerifyEcashTicketBody,
path = "/v1/ecash/verify-ecash-ticket", path = "/v1/ecash/verify-ecash-ticket",
responses( responses(
(status = 200, body = EcashTicketVerificationResponse), (status = 200, content(
(EcashTicketVerificationResponse = "application/json"),
(EcashTicketVerificationResponse = "application/yaml"),
(EcashTicketVerificationResponse = "application/bincode")
)),
(status = 400, body = String, description = "this nym-api is not an ecash signer in the current epoch"), (status = 400, body = String, description = "this nym-api is not an ecash signer in the current epoch"),
) )
)] )]
async fn verify_ticket( async fn verify_ticket(
Query(output): Query<OutputParams>,
State(state): State<Arc<EcashState>>, State(state): State<Arc<EcashState>>,
// TODO in the future: make it send binary data rather than json // TODO in the future: make it send binary data rather than json
Json(verify_ticket_body): Json<VerifyEcashTicketBody>, Json(verify_ticket_body): Json<VerifyEcashTicketBody>,
) -> AxumResult<Json<EcashTicketVerificationResponse>> { ) -> AxumResult<FormattedResponse<EcashTicketVerificationResponse>> {
state.ensure_signer().await?; state.ensure_signer().await?;
let output = output.output.unwrap_or_default();
let credential_data = &verify_ticket_body.credential; let credential_data = &verify_ticket_body.credential;
let gateway_cosmos_addr = &verify_ticket_body.gateway_cosmos_addr; let gateway_cosmos_addr = &verify_ticket_body.gateway_cosmos_addr;
// easy check: is there only a single payment attached? // easy check: is there only a single payment attached?
if credential_data.payment.spend_value != 1 { if credential_data.payment.spend_value != 1 {
return reject_ticket(EcashTicketVerificationRejection::MultipleTickets); return reject_ticket(output, EcashTicketVerificationRejection::MultipleTickets);
} }
let sn = &credential_data.encoded_serial_number(); let sn = &credential_data.encoded_serial_number();
@@ -83,11 +91,14 @@ async fn verify_ticket(
// only accept yesterday date if we're near the day transition, i.e. before 1AM UTC // only accept yesterday date if we're near the day transition, i.e. before 1AM UTC
if spend_date != today_ecash && now.time() > ONE_AM && spend_date != yesterday_ecash { if spend_date != today_ecash && now.time() > ONE_AM && spend_date != yesterday_ecash {
return reject_ticket(EcashTicketVerificationRejection::InvalidSpentDate { return reject_ticket(
today: today_ecash, output,
yesterday: yesterday_ecash, EcashTicketVerificationRejection::InvalidSpentDate {
received: spend_date, today: today_ecash,
}); yesterday: yesterday_ecash,
received: spend_date,
},
);
} }
// actual double spend detection with storage // actual double spend detection with storage
@@ -101,7 +112,7 @@ async fn verify_ticket(
IdentifyResult::NotADuplicatePayment => {} //SW NOTE This should never happen, quick message? IdentifyResult::NotADuplicatePayment => {} //SW NOTE This should never happen, quick message?
IdentifyResult::DuplicatePayInfo(_) => { IdentifyResult::DuplicatePayInfo(_) => {
warn!("Identical payInfo"); warn!("Identical payInfo");
return reject_ticket(EcashTicketVerificationRejection::ReplayedTicket); return reject_ticket(output, EcashTicketVerificationRejection::ReplayedTicket);
} }
IdentifyResult::DoubleSpendingPublicKeys(pub_key) => { IdentifyResult::DoubleSpendingPublicKeys(pub_key) => {
//Actual double spending //Actual double spending
@@ -110,7 +121,7 @@ async fn verify_ticket(
pub_key.to_base58_string() pub_key.to_base58_string()
); );
error!("UNIMPLEMENTED: blacklisting the double spend key"); error!("UNIMPLEMENTED: blacklisting the double spend key");
return reject_ticket(EcashTicketVerificationRejection::DoubleSpend); return reject_ticket(output, EcashTicketVerificationRejection::DoubleSpend);
} }
} }
} }
@@ -119,7 +130,7 @@ async fn verify_ticket(
// perform actual crypto verification // perform actual crypto verification
if credential_data.verify(&verification_key).is_err() { if credential_data.verify(&verification_key).is_err() {
return reject_ticket(EcashTicketVerificationRejection::InvalidTicket); return reject_ticket(output, EcashTicketVerificationRejection::InvalidTicket);
} }
// store credential and check whether it wasn't already there (due to a parallel request) // store credential and check whether it wasn't already there (due to a parallel request)
@@ -127,10 +138,10 @@ async fn verify_ticket(
.store_verified_ticket(credential_data, gateway_cosmos_addr) .store_verified_ticket(credential_data, gateway_cosmos_addr)
.await?; .await?;
if !was_inserted { if !was_inserted {
return reject_ticket(EcashTicketVerificationRejection::ReplayedTicket); return reject_ticket(output, EcashTicketVerificationRejection::ReplayedTicket);
} }
Ok(Json(EcashTicketVerificationResponse { verified: Ok(()) })) Ok(output.to_response(EcashTicketVerificationResponse { verified: Ok(()) }))
} }
#[utoipa::path( #[utoipa::path(
@@ -139,16 +150,23 @@ async fn verify_ticket(
request_body = BatchRedeemTicketsBody, request_body = BatchRedeemTicketsBody,
path = "/v1/ecash/batch-redeem-ecash-tickets", path = "/v1/ecash/batch-redeem-ecash-tickets",
responses( responses(
(status = 200, body = EcashBatchTicketRedemptionResponse), (status = 200, content(
(EcashBatchTicketRedemptionResponse = "application/json"),
(EcashBatchTicketRedemptionResponse = "application/yaml"),
(EcashBatchTicketRedemptionResponse = "application/bincode")
)),
(status = 400, body = String, description = "this nym-api is not an ecash signer in the current epoch"), (status = 400, body = String, description = "this nym-api is not an ecash signer in the current epoch"),
) ),
params(OutputParams)
)] )]
async fn batch_redeem_tickets( async fn batch_redeem_tickets(
Query(output): Query<OutputParams>,
State(state): State<Arc<EcashState>>, State(state): State<Arc<EcashState>>,
// TODO in the future: make it send binary data rather than json // TODO in the future: make it send binary data rather than json
Json(batch_redeem_credentials_body): Json<BatchRedeemTicketsBody>, Json(batch_redeem_credentials_body): Json<BatchRedeemTicketsBody>,
) -> AxumResult<Json<EcashBatchTicketRedemptionResponse>> { ) -> AxumResult<FormattedResponse<EcashBatchTicketRedemptionResponse>> {
state.ensure_signer().await?; state.ensure_signer().await?;
let output = output.output.unwrap_or_default();
// 1. see if that gateway has even submitted any tickets // 1. see if that gateway has even submitted any tickets
let Some(provider_info) = state let Some(provider_info) = state
@@ -208,7 +226,7 @@ async fn batch_redeem_tickets(
// 7. update the time of the last verification for this provider // 7. update the time of the last verification for this provider
state.update_last_batch_verification(&provider_info).await?; state.update_last_batch_verification(&provider_info).await?;
Ok(Json(EcashBatchTicketRedemptionResponse { Ok(output.to_response(EcashBatchTicketRedemptionResponse {
proposal_accepted: true, proposal_accepted: true,
})) }))
} }
@@ -224,7 +242,7 @@ async fn batch_redeem_tickets(
) )
)] )]
#[deprecated] #[deprecated]
async fn double_spending_filter_v1() -> AxumResult<Json<SpentCredentialsResponse>> { async fn double_spending_filter_v1() -> AxumResult<FormattedResponse<SpentCredentialsResponse>> {
AxumResult::Err(AxumErrorResponse::new( AxumResult::Err(AxumErrorResponse::new(
"permanently restricted", "permanently restricted",
StatusCode::GONE, StatusCode::GONE,
+77 -40
View File
@@ -4,10 +4,11 @@
use crate::network::models::{ContractInformation, NetworkDetails}; use crate::network::models::{ContractInformation, NetworkDetails};
use crate::node_status_api::models::AxumResult; use crate::node_status_api::models::AxumResult;
use crate::support::http::state::AppState; use crate::support::http::state::AppState;
use axum::extract::State; use axum::extract::{Query, State};
use axum::{extract, Json, Router}; use axum::{extract, Router};
use nym_api_requests::models::ChainStatusResponse; use nym_api_requests::models::ChainStatusResponse;
use nym_contracts_common::ContractBuildInformation; use nym_contracts_common::ContractBuildInformation;
use nym_http_api_common::{FormattedResponse, OutputParams};
use std::collections::HashMap; use std::collections::HashMap;
use tower_http::compression::CompressionLayer; use tower_http::compression::CompressionLayer;
use utoipa::ToSchema; use utoipa::ToSchema;
@@ -29,13 +30,21 @@ pub(crate) fn nym_network_routes() -> Router<AppState> {
get, get,
path = "/v1/network/details", path = "/v1/network/details",
responses( responses(
(status = 200, body = NetworkDetails) (status = 200, content(
) (NetworkDetails = "application/json"),
(NetworkDetails = "application/yaml"),
(NetworkDetails = "application/bincode")
))
),
params(OutputParams)
)] )]
async fn network_details( async fn network_details(
Query(output): Query<OutputParams>,
extract::State(state): extract::State<AppState>, extract::State(state): extract::State<AppState>,
) -> axum::Json<NetworkDetails> { ) -> FormattedResponse<NetworkDetails> {
state.network_details().to_owned().into() let output = output.output.unwrap_or_default();
output.to_response(state.network_details().to_owned())
} }
#[utoipa::path( #[utoipa::path(
@@ -43,10 +52,20 @@ async fn network_details(
get, get,
path = "/v1/network/chain-status", path = "/v1/network/chain-status",
responses( responses(
(status = 200, body = ChainStatusResponse) (status = 200, content(
) (ChainStatusResponse = "application/json"),
(ChainStatusResponse = "application/yaml"),
(ChainStatusResponse = "application/bincode")
))
),
params(OutputParams)
)] )]
async fn chain_status(State(state): State<AppState>) -> AxumResult<Json<ChainStatusResponse>> { async fn chain_status(
Query(output): Query<OutputParams>,
State(state): State<AppState>,
) -> AxumResult<FormattedResponse<ChainStatusResponse>> {
let output = output.output.unwrap_or_default();
let chain_status = state let chain_status = state
.chain_status_cache .chain_status_cache
.get_or_refresh(&state.nyxd_client) .get_or_refresh(&state.nyxd_client)
@@ -54,7 +73,7 @@ async fn chain_status(State(state): State<AppState>) -> AxumResult<Json<ChainSta
let connected_nyxd = state.network_details.connected_nyxd; let connected_nyxd = state.network_details.connected_nyxd;
Ok(Json(ChainStatusResponse { Ok(output.to_response(ChainStatusResponse {
connected_nyxd, connected_nyxd,
status: chain_status, status: chain_status,
})) }))
@@ -87,25 +106,34 @@ pub struct ContractInformationContractVersion {
get, get,
path = "/v1/network/nym-contracts", path = "/v1/network/nym-contracts",
responses( responses(
(status = 200, body = HashMap<String, ContractInformationContractVersion>) (status = 200, content(
) (HashMap<String, ContractInformationContractVersion> = "application/json"),
(HashMap<String, ContractInformationContractVersion> = "application/yaml"),
(HashMap<String, ContractInformationContractVersion> = "application/bincode")
))
),
params(OutputParams)
)] )]
async fn nym_contracts( async fn nym_contracts(
Query(output): Query<OutputParams>,
extract::State(state): extract::State<AppState>, extract::State(state): extract::State<AppState>,
) -> axum::Json<HashMap<String, ContractInformation<cw2::ContractVersion>>> { ) -> FormattedResponse<HashMap<String, ContractInformation<cw2::ContractVersion>>> {
let output = output.output.unwrap_or_default();
let info = state.nym_contract_cache().contract_details().await; let info = state.nym_contract_cache().contract_details().await;
info.iter() output.to_response(
.map(|(contract, info)| { info.iter()
( .map(|(contract, info)| {
contract.to_owned(), (
ContractInformation { contract.to_owned(),
address: info.address.as_ref().map(|a| a.to_string()), ContractInformation {
details: info.base.clone(), address: info.address.as_ref().map(|a| a.to_string()),
}, details: info.base.clone(),
) },
}) )
.collect::<HashMap<_, _>>() })
.into() .collect::<HashMap<_, _>>(),
)
} }
#[allow(dead_code)] // not dead, used in OpenAPI docs #[allow(dead_code)] // not dead, used in OpenAPI docs
@@ -120,23 +148,32 @@ pub struct ContractInformationBuildInformation {
get, get,
path = "/v1/network/nym-contracts-detailed", path = "/v1/network/nym-contracts-detailed",
responses( responses(
(status = 200, body = HashMap<String, ContractInformationBuildInformation>) (status = 200, content(
) (HashMap<String, ContractInformationBuildInformation> = "application/json"),
(HashMap<String, ContractInformationBuildInformation> = "application/yaml"),
(HashMap<String, ContractInformationBuildInformation> = "application/bincode")
))
),
params(OutputParams)
)] )]
async fn nym_contracts_detailed( async fn nym_contracts_detailed(
Query(output): Query<OutputParams>,
extract::State(state): extract::State<AppState>, extract::State(state): extract::State<AppState>,
) -> axum::Json<HashMap<String, ContractInformation<ContractBuildInformation>>> { ) -> FormattedResponse<HashMap<String, ContractInformation<ContractBuildInformation>>> {
let output = output.output.unwrap_or_default();
let info = state.nym_contract_cache().contract_details().await; let info = state.nym_contract_cache().contract_details().await;
info.iter() output.to_response(
.map(|(contract, info)| { info.iter()
( .map(|(contract, info)| {
contract.to_owned(), (
ContractInformation { contract.to_owned(),
address: info.address.as_ref().map(|a| a.to_string()), ContractInformation {
details: info.detailed.clone(), address: info.address.as_ref().map(|a| a.to_string()),
}, details: info.detailed.clone(),
) },
}) )
.collect::<HashMap<_, _>>() })
.into() .collect::<HashMap<_, _>>(),
)
} }
+14 -5
View File
@@ -4,10 +4,11 @@
use crate::node_status_api::models::AxumResult; use crate::node_status_api::models::AxumResult;
use crate::support::caching::cache::UninitialisedCache; use crate::support::caching::cache::UninitialisedCache;
use crate::support::http::state::AppState; use crate::support::http::state::AppState;
use axum::extract::State; use axum::extract::{Query, State};
use axum::routing::get; use axum::routing::get;
use axum::{Json, Router}; use axum::Router;
use nym_api_requests::models::ConfigScoreDataResponse; use nym_api_requests::models::ConfigScoreDataResponse;
use nym_http_api_common::{FormattedResponse, OutputParams};
use nym_mixnet_contract_common::NodeId; use nym_mixnet_contract_common::NodeId;
use serde::Deserialize; use serde::Deserialize;
use utoipa::IntoParams; use utoipa::IntoParams;
@@ -43,17 +44,25 @@ struct MixIdParam {
path = "/config-score-details", path = "/config-score-details",
context_path = "/v1/status", context_path = "/v1/status",
responses( responses(
(status = 200, body = ConfigScoreDataResponse) (status = 200, content(
(ConfigScoreDataResponse = "application/json"),
(ConfigScoreDataResponse = "application/yaml"),
(ConfigScoreDataResponse = "application/bincode")
))
), ),
params(OutputParams)
)] )]
async fn config_score_details( async fn config_score_details(
Query(output): Query<OutputParams>,
State(state): State<AppState>, State(state): State<AppState>,
) -> AxumResult<Json<ConfigScoreDataResponse>> { ) -> AxumResult<FormattedResponse<ConfigScoreDataResponse>> {
let output = output.output.unwrap_or_default();
let data = state let data = state
.nym_contract_cache() .nym_contract_cache()
.maybe_config_score_data_owned() .maybe_config_score_data_owned()
.await .await
.ok_or(UninitialisedCache)?; .ok_or(UninitialisedCache)?;
Ok(Json(data.into_inner().into())) Ok(output.to_response(data.into_inner().into()))
} }
@@ -22,6 +22,7 @@ use nym_api_requests::models::{
MixNodeBondAnnotated, MixnodeCoreStatusResponse, MixnodeStatusReportResponse, MixNodeBondAnnotated, MixnodeCoreStatusResponse, MixnodeStatusReportResponse,
MixnodeUptimeHistoryResponse, RewardEstimationResponse, UptimeResponse, MixnodeUptimeHistoryResponse, RewardEstimationResponse, UptimeResponse,
}; };
use nym_http_api_common::{FormattedResponse, Output, OutputParams};
use serde::Deserialize; use serde::Deserialize;
use utoipa::IntoParams; use utoipa::IntoParams;
@@ -103,17 +104,23 @@ pub(super) fn network_monitor_routes() -> Router<AppState> {
get, get,
path = "/v1/status/gateway/{identity}/report", path = "/v1/status/gateway/{identity}/report",
responses( responses(
(status = 200, body = GatewayStatusReportResponse) (status = 200, content(
) (GatewayStatusReportResponse = "application/json"),
(GatewayStatusReportResponse = "application/yaml"),
(GatewayStatusReportResponse = "application/bincode")
))
),
params(OutputParams)
)] )]
#[deprecated] #[deprecated]
async fn gateway_report( async fn gateway_report(
Path(identity): Path<String>, Path(identity): Path<String>,
Query(output): Query<OutputParams>,
State(state): State<AppState>, State(state): State<AppState>,
) -> AxumResult<Json<GatewayStatusReportResponse>> { ) -> AxumResult<FormattedResponse<GatewayStatusReportResponse>> {
Ok(Json( let output = output.output.unwrap_or_default();
_gateway_report(state.node_status_cache(), &identity).await?,
)) Ok(output.to_response(_gateway_report(state.node_status_cache(), &identity).await?))
} }
#[utoipa::path( #[utoipa::path(
@@ -121,15 +128,23 @@ async fn gateway_report(
get, get,
path = "/v1/status/gateway/{identity}/history", path = "/v1/status/gateway/{identity}/history",
responses( responses(
(status = 200, body = GatewayUptimeHistoryResponse) (status = 200, content(
) (GatewayUptimeHistoryResponse = "application/json"),
(GatewayUptimeHistoryResponse = "application/yaml"),
(GatewayUptimeHistoryResponse = "application/bincode")
))
),
params(OutputParams)
)] )]
#[deprecated] #[deprecated]
async fn gateway_uptime_history( async fn gateway_uptime_history(
Path(identity): Path<String>, Path(identity): Path<String>,
Query(output): Query<OutputParams>,
State(state): State<AppState>, State(state): State<AppState>,
) -> AxumResult<Json<GatewayUptimeHistoryResponse>> { ) -> AxumResult<FormattedResponse<GatewayUptimeHistoryResponse>> {
Ok(Json( let output = output.output.unwrap_or_default();
Ok(output.to_response(
_gateway_uptime_history(state.storage(), state.nym_contract_cache(), &identity).await?, _gateway_uptime_history(state.storage(), state.nym_contract_cache(), &identity).await?,
)) ))
} }
@@ -138,6 +153,7 @@ async fn gateway_uptime_history(
#[into_params(parameter_in = Query)] #[into_params(parameter_in = Query)]
struct SinceQueryParams { struct SinceQueryParams {
since: Option<i64>, since: Option<i64>,
output: Option<Output>,
} }
#[utoipa::path( #[utoipa::path(
@@ -148,18 +164,22 @@ struct SinceQueryParams {
), ),
path = "/v1/status/gateway/{identity}/core-status-count", path = "/v1/status/gateway/{identity}/core-status-count",
responses( responses(
(status = 200, body = GatewayCoreStatusResponse) (status = 200, content(
) (GatewayCoreStatusResponse = "application/json"),
(GatewayCoreStatusResponse = "application/yaml"),
(GatewayCoreStatusResponse = "application/bincode")
))
),
)] )]
#[deprecated] #[deprecated]
async fn gateway_core_status_count( async fn gateway_core_status_count(
Path(identity): Path<String>, Path(identity): Path<String>,
Query(SinceQueryParams { since }): Query<SinceQueryParams>, Query(SinceQueryParams { since, output }): Query<SinceQueryParams>,
State(state): State<AppState>, State(state): State<AppState>,
) -> AxumResult<Json<GatewayCoreStatusResponse>> { ) -> AxumResult<FormattedResponse<GatewayCoreStatusResponse>> {
Ok(Json( Ok(output
_gateway_core_status_count(state.storage(), &identity, since).await?, .unwrap_or_default()
)) .to_response(_gateway_core_status_count(state.storage(), &identity, since).await?))
} }
#[utoipa::path( #[utoipa::path(
@@ -167,57 +187,71 @@ async fn gateway_core_status_count(
get, get,
path = "/v1/status/gateway/{identity}/avg_uptime", path = "/v1/status/gateway/{identity}/avg_uptime",
responses( responses(
(status = 200, body = GatewayUptimeResponse) (status = 200, content(
) (GatewayUptimeResponse = "application/json"),
(GatewayUptimeResponse = "application/yaml"),
(GatewayUptimeResponse = "application/bincode")
))
),
params(OutputParams)
)] )]
#[deprecated] #[deprecated]
async fn get_gateway_avg_uptime( async fn get_gateway_avg_uptime(
Path(identity): Path<String>, Path(identity): Path<String>,
Query(output): Query<OutputParams>,
State(state): State<AppState>, State(state): State<AppState>,
) -> AxumResult<Json<GatewayUptimeResponse>> { ) -> AxumResult<FormattedResponse<GatewayUptimeResponse>> {
Ok(Json( let output = output.output.unwrap_or_default();
_get_gateway_avg_uptime(state.node_status_cache(), &identity).await?,
)) Ok(output.to_response(_get_gateway_avg_uptime(state.node_status_cache(), &identity).await?))
} }
#[utoipa::path( #[utoipa::path(
tag = "network-monitor-status", tag = "network-monitor-status",
get, get,
params(
MixIdParam
),
path = "/v1/status/mixnode/{mix_id}/report", path = "/v1/status/mixnode/{mix_id}/report",
responses( responses(
(status = 200, body = MixnodeStatusReportResponse) (status = 200, content(
) (MixnodeStatusReportResponse = "application/json"),
(MixnodeStatusReportResponse = "application/yaml"),
(MixnodeStatusReportResponse = "application/bincode")
))
),
params(OutputParams, MixIdParam)
)] )]
#[deprecated] #[deprecated]
async fn mixnode_report( async fn mixnode_report(
Path(MixIdParam { mix_id }): Path<MixIdParam>, Path(MixIdParam { mix_id }): Path<MixIdParam>,
Query(output): Query<OutputParams>,
State(state): State<AppState>, State(state): State<AppState>,
) -> AxumResult<Json<MixnodeStatusReportResponse>> { ) -> AxumResult<FormattedResponse<MixnodeStatusReportResponse>> {
Ok(Json( let output = output.output.unwrap_or_default();
_mixnode_report(state.node_status_cache(), mix_id).await?,
)) Ok(output.to_response(_mixnode_report(state.node_status_cache(), mix_id).await?))
} }
#[utoipa::path( #[utoipa::path(
tag = "network-monitor-status", tag = "network-monitor-status",
get, get,
params(
MixIdParam
),
path = "/v1/status/mixnode/{mix_id}/history", path = "/v1/status/mixnode/{mix_id}/history",
responses( responses(
(status = 200, body = MixnodeUptimeHistoryResponse) (status = 200, content(
) (MixnodeUptimeHistoryResponse = "application/json"),
(MixnodeUptimeHistoryResponse = "application/yaml"),
(MixnodeUptimeHistoryResponse = "application/bincode")
))
),
params(MixIdParam, OutputParams)
)] )]
#[deprecated] #[deprecated]
async fn mixnode_uptime_history( async fn mixnode_uptime_history(
Path(MixIdParam { mix_id }): Path<MixIdParam>, Path(MixIdParam { mix_id }): Path<MixIdParam>,
Query(output): Query<OutputParams>,
State(state): State<AppState>, State(state): State<AppState>,
) -> AxumResult<Json<MixnodeUptimeHistoryResponse>> { ) -> AxumResult<FormattedResponse<MixnodeUptimeHistoryResponse>> {
Ok(Json( let output = output.output.unwrap_or_default();
Ok(output.to_response(
_mixnode_uptime_history(state.storage(), state.nym_contract_cache(), mix_id).await?, _mixnode_uptime_history(state.storage(), state.nym_contract_cache(), mix_id).await?,
)) ))
} }
@@ -230,37 +264,48 @@ async fn mixnode_uptime_history(
), ),
path = "/v1/status/mixnode/{mix_id}/core-status-count", path = "/v1/status/mixnode/{mix_id}/core-status-count",
responses( responses(
(status = 200, body = MixnodeCoreStatusResponse) (status = 200, content(
) (MixnodeCoreStatusResponse = "application/json"),
(MixnodeCoreStatusResponse = "application/yaml"),
(MixnodeCoreStatusResponse = "application/bincode")
))
),
)] )]
#[deprecated] #[deprecated]
async fn mixnode_core_status_count( async fn mixnode_core_status_count(
Path(MixIdParam { mix_id }): Path<MixIdParam>, Path(MixIdParam { mix_id }): Path<MixIdParam>,
Query(SinceQueryParams { since }): Query<SinceQueryParams>, Query(SinceQueryParams { since, output }): Query<SinceQueryParams>,
State(state): State<AppState>, State(state): State<AppState>,
) -> AxumResult<Json<MixnodeCoreStatusResponse>> { ) -> AxumResult<FormattedResponse<MixnodeCoreStatusResponse>> {
Ok(Json( let output = output.unwrap_or_default();
_mixnode_core_status_count(state.storage(), mix_id, since).await?,
)) Ok(output.to_response(_mixnode_core_status_count(state.storage(), mix_id, since).await?))
} }
#[utoipa::path( #[utoipa::path(
tag = "network-monitor-status", tag = "network-monitor-status",
get, get,
params( params(
MixIdParam MixIdParam, OutputParams
), ),
path = "/v1/status/mixnode/{mix_id}/reward-estimation", path = "/v1/status/mixnode/{mix_id}/reward-estimation",
responses( responses(
(status = 200, body = RewardEstimationResponse) (status = 200, content(
) (RewardEstimationResponse = "application/json"),
(RewardEstimationResponse = "application/yaml"),
(RewardEstimationResponse = "application/bincode")
))
),
)] )]
#[deprecated] #[deprecated]
async fn get_mixnode_reward_estimation( async fn get_mixnode_reward_estimation(
Path(MixIdParam { mix_id }): Path<MixIdParam>, Path(MixIdParam { mix_id }): Path<MixIdParam>,
Query(output): Query<OutputParams>,
State(state): State<AppState>, State(state): State<AppState>,
) -> AxumResult<Json<RewardEstimationResponse>> { ) -> AxumResult<FormattedResponse<RewardEstimationResponse>> {
Ok(Json( let output = output.output.unwrap_or_default();
Ok(output.to_response(
_get_mixnode_reward_estimation( _get_mixnode_reward_estimation(
state.node_status_cache(), state.node_status_cache(),
state.nym_contract_cache(), state.nym_contract_cache(),
@@ -274,21 +319,28 @@ async fn get_mixnode_reward_estimation(
tag = "network-monitor-status", tag = "network-monitor-status",
post, post,
params( params(
ComputeRewardEstParam, MixIdParam OutputParams, MixIdParam
), ),
path = "/v1/status/mixnode/{mix_id}/compute-reward-estimation", path = "/v1/status/mixnode/{mix_id}/compute-reward-estimation",
request_body = ComputeRewardEstParam, request_body = ComputeRewardEstParam,
responses( responses(
(status = 200, body = RewardEstimationResponse) (status = 200, content(
) (RewardEstimationResponse = "application/json"),
(RewardEstimationResponse = "application/yaml"),
(RewardEstimationResponse = "application/bincode")
))
),
)] )]
#[deprecated] #[deprecated]
async fn compute_mixnode_reward_estimation( async fn compute_mixnode_reward_estimation(
Path(MixIdParam { mix_id }): Path<MixIdParam>, Path(MixIdParam { mix_id }): Path<MixIdParam>,
Query(output): Query<OutputParams>,
State(state): State<AppState>, State(state): State<AppState>,
Json(user_reward_param): Json<ComputeRewardEstParam>, Json(user_reward_param): Json<ComputeRewardEstParam>,
) -> AxumResult<Json<RewardEstimationResponse>> { ) -> AxumResult<FormattedResponse<RewardEstimationResponse>> {
Ok(Json( let output = output.output.unwrap_or_default();
Ok(output.to_response(
_compute_mixnode_reward_estimation( _compute_mixnode_reward_estimation(
&user_reward_param, &user_reward_param,
state.node_status_cache(), state.node_status_cache(),
@@ -303,21 +355,26 @@ async fn compute_mixnode_reward_estimation(
tag = "network-monitor-status", tag = "network-monitor-status",
get, get,
params( params(
MixIdParam MixIdParam, OutputParams
), ),
path = "/v1/status/mixnode/{mix_id}/avg_uptime", path = "/v1/status/mixnode/{mix_id}/avg_uptime",
responses( responses(
(status = 200, body = UptimeResponse) (status = 200, content(
) (UptimeResponse = "application/json"),
(UptimeResponse = "application/yaml"),
(UptimeResponse = "application/bincode")
))
),
)] )]
#[deprecated] #[deprecated]
async fn get_mixnode_avg_uptime( async fn get_mixnode_avg_uptime(
Path(MixIdParam { mix_id }): Path<MixIdParam>, Path(MixIdParam { mix_id }): Path<MixIdParam>,
Query(output): Query<OutputParams>,
State(state): State<AppState>, State(state): State<AppState>,
) -> AxumResult<Json<UptimeResponse>> { ) -> AxumResult<FormattedResponse<UptimeResponse>> {
Ok(Json( let output = output.output.unwrap_or_default();
_get_mixnode_avg_uptime(state.node_status_cache(), mix_id).await?,
)) Ok(output.to_response(_get_mixnode_avg_uptime(state.node_status_cache(), mix_id).await?))
} }
#[utoipa::path( #[utoipa::path(
@@ -325,14 +382,22 @@ async fn get_mixnode_avg_uptime(
get, get,
path = "/v1/status/mixnodes/detailed-unfiltered", path = "/v1/status/mixnodes/detailed-unfiltered",
responses( responses(
(status = 200, body = MixNodeBondAnnotated) (status = 200, content(
) (MixNodeBondAnnotated = "application/json"),
(MixNodeBondAnnotated = "application/yaml"),
(MixNodeBondAnnotated = "application/bincode")
))
),
params(OutputParams)
)] )]
#[deprecated] #[deprecated]
pub async fn get_mixnodes_detailed_unfiltered( pub async fn get_mixnodes_detailed_unfiltered(
Query(output): Query<OutputParams>,
State(state): State<AppState>, State(state): State<AppState>,
) -> Json<Vec<MixNodeBondAnnotated>> { ) -> FormattedResponse<Vec<MixNodeBondAnnotated>> {
Json(_get_mixnodes_detailed_unfiltered(state.node_status_cache()).await) let output = output.output.unwrap_or_default();
output.to_response(_get_mixnodes_detailed_unfiltered(state.node_status_cache()).await)
} }
#[utoipa::path( #[utoipa::path(
@@ -340,14 +405,22 @@ pub async fn get_mixnodes_detailed_unfiltered(
get, get,
path = "/v1/status/gateways/detailed", path = "/v1/status/gateways/detailed",
responses( responses(
(status = 200, body = GatewayBondAnnotated) (status = 200, content(
) (GatewayBondAnnotated = "application/json"),
(GatewayBondAnnotated = "application/yaml"),
(GatewayBondAnnotated = "application/bincode")
))
),
params(OutputParams)
)] )]
#[deprecated] #[deprecated]
pub async fn get_gateways_detailed( pub async fn get_gateways_detailed(
Query(output): Query<OutputParams>,
State(state): State<AppState>, State(state): State<AppState>,
) -> Json<Vec<GatewayBondAnnotated>> { ) -> FormattedResponse<Vec<GatewayBondAnnotated>> {
Json(_get_legacy_gateways_detailed(state.node_status_cache()).await) let output = output.output.unwrap_or_default();
output.to_response(_get_legacy_gateways_detailed(state.node_status_cache()).await)
} }
#[utoipa::path( #[utoipa::path(
@@ -355,12 +428,20 @@ pub async fn get_gateways_detailed(
get, get,
path = "/v1/status/gateways/detailed-unfiltered", path = "/v1/status/gateways/detailed-unfiltered",
responses( responses(
(status = 200, body = GatewayBondAnnotated) (status = 200, content(
) (GatewayBondAnnotated = "application/json"),
(GatewayBondAnnotated = "application/yaml"),
(GatewayBondAnnotated = "application/bincode")
))
),
params(OutputParams)
)] )]
#[deprecated] #[deprecated]
pub async fn get_gateways_detailed_unfiltered( pub async fn get_gateways_detailed_unfiltered(
Query(output): Query<OutputParams>,
State(state): State<AppState>, State(state): State<AppState>,
) -> Json<Vec<GatewayBondAnnotated>> { ) -> FormattedResponse<Vec<GatewayBondAnnotated>> {
Json(_get_legacy_gateways_detailed_unfiltered(state.node_status_cache()).await) let output = output.output.unwrap_or_default();
output.to_response(_get_legacy_gateways_detailed_unfiltered(state.node_status_cache()).await)
} }
@@ -8,12 +8,12 @@ use crate::support::http::state::AppState;
use crate::support::storage::NymApiStorage; use crate::support::storage::NymApiStorage;
use anyhow::bail; use anyhow::bail;
use axum::extract::{Path, Query, State}; use axum::extract::{Path, Query, State};
use axum::Json;
use nym_api_requests::models::{ use nym_api_requests::models::{
GatewayTestResultResponse, MixnodeTestResultResponse, NetworkMonitorRunDetailsResponse, GatewayTestResultResponse, MixnodeTestResultResponse, NetworkMonitorRunDetailsResponse,
PartialTestResult, TestNode, TestRoute, PartialTestResult, TestNode, TestRoute,
}; };
use nym_api_requests::pagination::Pagination; use nym_api_requests::pagination::Pagination;
use nym_http_api_common::{FormattedResponse, OutputParams};
use nym_mixnet_contract_common::NodeId; use nym_mixnet_contract_common::NodeId;
use std::cmp::min; use std::cmp::min;
use std::collections::{BTreeMap, HashMap}; use std::collections::{BTreeMap, HashMap};
@@ -173,14 +173,18 @@ async fn _mixnode_test_results(
), ),
path = "/v1/status/mixnodes/unstable/{mix_id}/test-results", path = "/v1/status/mixnodes/unstable/{mix_id}/test-results",
responses( responses(
(status = 200, body = MixnodeTestResultResponse) (status = 200, content(
) (MixnodeTestResultResponse = "application/json"),
(MixnodeTestResultResponse = "application/yaml"),
(MixnodeTestResultResponse = "application/bincode")
))
),
)] )]
pub async fn mixnode_test_results( pub async fn mixnode_test_results(
Path(mix_id): Path<NodeId>, Path(mix_id): Path<NodeId>,
Query(pagination): Query<PaginationRequest>, Query(pagination): Query<PaginationRequest>,
State(state): State<AppState>, State(state): State<AppState>,
) -> AxumResult<Json<MixnodeTestResultResponse>> { ) -> AxumResult<FormattedResponse<MixnodeTestResultResponse>> {
let page = pagination.page.unwrap_or_default(); let page = pagination.page.unwrap_or_default();
let per_page = min( let per_page = min(
pagination pagination
@@ -188,6 +192,7 @@ pub async fn mixnode_test_results(
.unwrap_or(DEFAULT_TEST_RESULTS_PAGE_SIZE), .unwrap_or(DEFAULT_TEST_RESULTS_PAGE_SIZE),
MAX_TEST_RESULTS_PAGE_SIZE, MAX_TEST_RESULTS_PAGE_SIZE,
); );
let output = pagination.output.unwrap_or_default();
match _mixnode_test_results( match _mixnode_test_results(
mix_id, mix_id,
@@ -198,7 +203,7 @@ pub async fn mixnode_test_results(
) )
.await .await
{ {
Ok(res) => Ok(Json(res)), Ok(res) => Ok(output.to_response(res)),
Err(err) => Err(AxumErrorResponse::internal_msg(format!( Err(err) => Err(AxumErrorResponse::internal_msg(format!(
"failed to retrieve mixnode test results for node {mix_id}: {err}" "failed to retrieve mixnode test results for node {mix_id}: {err}"
))), ))),
@@ -272,14 +277,18 @@ async fn _gateway_test_results(
), ),
path = "/v1/status/gateways/unstable/{identity}/test-results", path = "/v1/status/gateways/unstable/{identity}/test-results",
responses( responses(
(status = 200, body = GatewayTestResultResponse) (status = 200, content(
) (GatewayTestResultResponse = "application/json"),
(GatewayTestResultResponse = "application/yaml"),
(GatewayTestResultResponse = "application/bincode")
))
),
)] )]
pub async fn gateway_test_results( pub async fn gateway_test_results(
Path(gateway_identity): Path<String>, Path(gateway_identity): Path<String>,
Query(pagination): Query<PaginationRequest>, Query(pagination): Query<PaginationRequest>,
State(state): State<AppState>, State(state): State<AppState>,
) -> AxumResult<Json<GatewayTestResultResponse>> { ) -> AxumResult<FormattedResponse<GatewayTestResultResponse>> {
let page = pagination.page.unwrap_or_default(); let page = pagination.page.unwrap_or_default();
let per_page = min( let per_page = min(
pagination pagination
@@ -287,6 +296,7 @@ pub async fn gateway_test_results(
.unwrap_or(DEFAULT_TEST_RESULTS_PAGE_SIZE), .unwrap_or(DEFAULT_TEST_RESULTS_PAGE_SIZE),
MAX_TEST_RESULTS_PAGE_SIZE, MAX_TEST_RESULTS_PAGE_SIZE,
); );
let output = pagination.output.unwrap_or_default();
match _gateway_test_results( match _gateway_test_results(
&gateway_identity, &gateway_identity,
@@ -297,7 +307,7 @@ pub async fn gateway_test_results(
) )
.await .await
{ {
Ok(res) => Ok(Json(res)), Ok(res) => Ok(output.to_response(res)),
Err(err) => Err(AxumErrorResponse::internal_msg(format!( Err(err) => Err(AxumErrorResponse::internal_msg(format!(
"failed to retrieve mixnode test results for gateway {gateway_identity}: {err}" "failed to retrieve mixnode test results for gateway {gateway_identity}: {err}"
))), ))),
@@ -349,15 +359,23 @@ async fn _latest_monitor_run_report(
get, get,
path = "/v1/status/network-monitor/unstable/run/{monitor_run_id}/details", path = "/v1/status/network-monitor/unstable/run/{monitor_run_id}/details",
responses( responses(
(status = 200, body = NetworkMonitorRunDetailsResponse) (status = 200, content(
) (NetworkMonitorRunDetailsResponse = "application/json"),
(NetworkMonitorRunDetailsResponse = "application/yaml"),
(NetworkMonitorRunDetailsResponse = "application/bincode")
))
),
params(OutputParams)
)] )]
pub async fn monitor_run_report( pub async fn monitor_run_report(
Path(monitor_run_id): Path<i64>, Path(monitor_run_id): Path<i64>,
Query(output): Query<OutputParams>,
State(state): State<AppState>, State(state): State<AppState>,
) -> AxumResult<Json<NetworkMonitorRunDetailsResponse>> { ) -> AxumResult<FormattedResponse<NetworkMonitorRunDetailsResponse>> {
let output = output.output.unwrap_or_default();
match _monitor_run_report(monitor_run_id, state.storage()).await { match _monitor_run_report(monitor_run_id, state.storage()).await {
Ok(res) => Ok(Json(res)), Ok(res) => Ok(output.to_response(res)),
Err(err) => Err(AxumErrorResponse::internal_msg(format!( Err(err) => Err(AxumErrorResponse::internal_msg(format!(
"failed to retrieve monitor run report for run {monitor_run_id}: {err}" "failed to retrieve monitor run report for run {monitor_run_id}: {err}"
))), ))),
@@ -369,14 +387,22 @@ pub async fn monitor_run_report(
get, get,
path = "/v1/status/network-monitor/unstable/run/latest/details", path = "/v1/status/network-monitor/unstable/run/latest/details",
responses( responses(
(status = 200, body = NetworkMonitorRunDetailsResponse) (status = 200, content(
) (NetworkMonitorRunDetailsResponse = "application/json"),
(NetworkMonitorRunDetailsResponse = "application/yaml"),
(NetworkMonitorRunDetailsResponse = "application/bincode")
))
),
params(OutputParams)
)] )]
pub async fn latest_monitor_run_report( pub async fn latest_monitor_run_report(
Query(output): Query<OutputParams>,
State(state): State<AppState>, State(state): State<AppState>,
) -> AxumResult<Json<NetworkMonitorRunDetailsResponse>> { ) -> AxumResult<FormattedResponse<NetworkMonitorRunDetailsResponse>> {
let output = output.output.unwrap_or_default();
match _latest_monitor_run_report(state.storage()).await { match _latest_monitor_run_report(state.storage()).await {
Ok(res) => Ok(Json(res)), Ok(res) => Ok(output.to_response(res)),
Err(err) => Err(AxumErrorResponse::internal_msg(format!( Err(err) => Err(AxumErrorResponse::internal_msg(format!(
"failed to retrieve the latest monitor run report: {err}" "failed to retrieve the latest monitor run report: {err}"
))), ))),
@@ -11,13 +11,14 @@ use crate::node_status_api::helpers::{
}; };
use crate::node_status_api::models::{AxumErrorResponse, AxumResult}; use crate::node_status_api::models::{AxumErrorResponse, AxumResult};
use crate::support::http::state::AppState; use crate::support::http::state::AppState;
use axum::extract::{Path, State}; use axum::extract::{Path, Query, State};
use axum::routing::{get, post}; use axum::routing::{get, post};
use axum::Json; use axum::Json;
use axum::Router; use axum::Router;
use nym_api_requests::models::{ use nym_api_requests::models::{
MixNodeBondAnnotated, MixnodeStatusResponse, StakeSaturationResponse, MixNodeBondAnnotated, MixnodeStatusResponse, StakeSaturationResponse,
}; };
use nym_http_api_common::{FormattedResponse, OutputParams};
use nym_mixnet_contract_common::NodeId; use nym_mixnet_contract_common::NodeId;
use nym_types::monitoring::MonitorMessage; use nym_types::monitoring::MonitorMessage;
use tracing::error; use tracing::error;
@@ -153,15 +154,22 @@ pub(crate) async fn submit_node_monitoring_results(
), ),
path = "/v1/status/mixnode/{mix_id}/status", path = "/v1/status/mixnode/{mix_id}/status",
responses( responses(
(status = 200, body = MixnodeStatusResponse) (status = 200, content(
) (MixnodeStatusResponse = "application/json"),
(MixnodeStatusResponse = "application/yaml"),
(MixnodeStatusResponse = "application/bincode")
))
),
params(OutputParams)
)] )]
#[deprecated] #[deprecated]
async fn get_mixnode_status( async fn get_mixnode_status(
Path(MixIdParam { mix_id }): Path<MixIdParam>, Path(MixIdParam { mix_id }): Path<MixIdParam>,
Query(output): Query<OutputParams>,
State(state): State<AppState>, State(state): State<AppState>,
) -> Json<MixnodeStatusResponse> { ) -> FormattedResponse<MixnodeStatusResponse> {
Json(_get_mixnode_status(state.nym_contract_cache(), mix_id).await) let output = output.output.unwrap_or_default();
output.to_response(_get_mixnode_status(state.nym_contract_cache(), mix_id).await)
} }
#[utoipa::path( #[utoipa::path(
@@ -172,15 +180,23 @@ async fn get_mixnode_status(
), ),
path = "/v1/status/mixnode/{mix_id}/stake-saturation", path = "/v1/status/mixnode/{mix_id}/stake-saturation",
responses( responses(
(status = 200, body = StakeSaturationResponse) (status = 200, content(
) (StakeSaturationResponse = "application/json"),
(StakeSaturationResponse = "application/yaml"),
(StakeSaturationResponse = "application/bincode")
))
),
params(OutputParams)
)] )]
#[deprecated] #[deprecated]
async fn get_mixnode_stake_saturation( async fn get_mixnode_stake_saturation(
Path(mix_id): Path<NodeId>, Path(mix_id): Path<NodeId>,
Query(output): Query<OutputParams>,
State(state): State<AppState>, State(state): State<AppState>,
) -> AxumResult<Json<StakeSaturationResponse>> { ) -> AxumResult<FormattedResponse<StakeSaturationResponse>> {
Ok(Json( let output = output.output.unwrap_or_default();
Ok(output.to_response(
_get_mixnode_stake_saturation( _get_mixnode_stake_saturation(
state.node_status_cache(), state.node_status_cache(),
state.nym_contract_cache(), state.nym_contract_cache(),
@@ -194,22 +210,28 @@ async fn get_mixnode_stake_saturation(
tag = "status", tag = "status",
get, get,
params( params(
MixIdParam MixIdParam, OutputParams
), ),
path = "/v1/status/mixnode/{mix_id}/inclusion-probability", path = "/v1/status/mixnode/{mix_id}/inclusion-probability",
responses( responses(
(status = 200, body = nym_api_requests::models::InclusionProbabilityResponse) (status = 200, content(
) (nym_api_requests::models::InclusionProbabilityResponse = "application/json"),
(nym_api_requests::models::InclusionProbabilityResponse = "application/yaml"),
(nym_api_requests::models::InclusionProbabilityResponse = "application/bincode")
))
),
)] )]
#[deprecated] #[deprecated]
#[allow(deprecated)] #[allow(deprecated)]
async fn get_mixnode_inclusion_probability( async fn get_mixnode_inclusion_probability(
Path(mix_id): Path<NodeId>, Path(mix_id): Path<NodeId>,
Query(output): Query<OutputParams>,
State(state): State<AppState>, State(state): State<AppState>,
) -> AxumResult<Json<nym_api_requests::models::InclusionProbabilityResponse>> { ) -> AxumResult<FormattedResponse<nym_api_requests::models::InclusionProbabilityResponse>> {
Ok(Json( let output = output.output.unwrap_or_default();
_get_mixnode_inclusion_probability(state.node_status_cache(), mix_id).await?,
)) Ok(output
.to_response(_get_mixnode_inclusion_probability(state.node_status_cache(), mix_id).await?))
} }
#[utoipa::path( #[utoipa::path(
@@ -217,17 +239,23 @@ async fn get_mixnode_inclusion_probability(
get, get,
path = "/v1/status/mixnodes/inclusion-probability", path = "/v1/status/mixnodes/inclusion-probability",
responses( responses(
(status = 200, body = nym_api_requests::models::AllInclusionProbabilitiesResponse) (status = 200, content(
) (nym_api_requests::models::AllInclusionProbabilitiesResponse = "application/json"),
(nym_api_requests::models::AllInclusionProbabilitiesResponse = "application/yaml"),
(nym_api_requests::models::AllInclusionProbabilitiesResponse = "application/bincode")
))
),
params(OutputParams)
)] )]
#[deprecated] #[deprecated]
#[allow(deprecated)] #[allow(deprecated)]
async fn get_mixnode_inclusion_probabilities( async fn get_mixnode_inclusion_probabilities(
Query(output): Query<OutputParams>,
State(state): State<AppState>, State(state): State<AppState>,
) -> AxumResult<Json<nym_api_requests::models::AllInclusionProbabilitiesResponse>> { ) -> AxumResult<FormattedResponse<nym_api_requests::models::AllInclusionProbabilitiesResponse>> {
Ok(Json( let output = output.output.unwrap_or_default();
_get_mixnode_inclusion_probabilities(state.node_status_cache()).await?,
)) Ok(output.to_response(_get_mixnode_inclusion_probabilities(state.node_status_cache()).await?))
} }
#[utoipa::path( #[utoipa::path(
@@ -235,14 +263,22 @@ async fn get_mixnode_inclusion_probabilities(
get, get,
path = "/v1/status/mixnodes/detailed", path = "/v1/status/mixnodes/detailed",
responses( responses(
(status = 200, body = MixNodeBondAnnotated) (status = 200, content(
) (MixNodeBondAnnotated = "application/json"),
(MixNodeBondAnnotated = "application/yaml"),
(MixNodeBondAnnotated = "application/bincode")
))
),
params(OutputParams)
)] )]
#[deprecated] #[deprecated]
pub async fn get_mixnodes_detailed( pub async fn get_mixnodes_detailed(
Query(output): Query<OutputParams>,
State(state): State<AppState>, State(state): State<AppState>,
) -> Json<Vec<MixNodeBondAnnotated>> { ) -> FormattedResponse<Vec<MixNodeBondAnnotated>> {
Json(_get_legacy_mixnodes_detailed(state.node_status_cache()).await) let output = output.output.unwrap_or_default();
output.to_response(_get_legacy_mixnodes_detailed(state.node_status_cache()).await)
} }
#[utoipa::path( #[utoipa::path(
@@ -250,14 +286,22 @@ pub async fn get_mixnodes_detailed(
get, get,
path = "/v1/status/mixnodes/rewarded/detailed", path = "/v1/status/mixnodes/rewarded/detailed",
responses( responses(
(status = 200, body = MixNodeBondAnnotated) (status = 200, content(
) (MixNodeBondAnnotated = "application/json"),
(MixNodeBondAnnotated = "application/yaml"),
(MixNodeBondAnnotated = "application/bincode")
))
),
params(OutputParams)
)] )]
#[deprecated] #[deprecated]
pub async fn get_rewarded_set_detailed( pub async fn get_rewarded_set_detailed(
Query(output): Query<OutputParams>,
State(state): State<AppState>, State(state): State<AppState>,
) -> Json<Vec<MixNodeBondAnnotated>> { ) -> FormattedResponse<Vec<MixNodeBondAnnotated>> {
Json( let output = output.output.unwrap_or_default();
output.to_response(
_get_rewarded_set_legacy_mixnodes_detailed( _get_rewarded_set_legacy_mixnodes_detailed(
state.node_status_cache(), state.node_status_cache(),
state.nym_contract_cache(), state.nym_contract_cache(),
@@ -271,14 +315,22 @@ pub async fn get_rewarded_set_detailed(
get, get,
path = "/v1/status/mixnodes/active/detailed", path = "/v1/status/mixnodes/active/detailed",
responses( responses(
(status = 200, body = MixNodeBondAnnotated) (status = 200, content(
) (MixNodeBondAnnotated = "application/json"),
(MixNodeBondAnnotated = "application/yaml"),
(MixNodeBondAnnotated = "application/bincode")
))
),
params(OutputParams)
)] )]
#[deprecated] #[deprecated]
pub async fn get_active_set_detailed( pub async fn get_active_set_detailed(
Query(output): Query<OutputParams>,
State(state): State<AppState>, State(state): State<AppState>,
) -> Json<Vec<MixNodeBondAnnotated>> { ) -> FormattedResponse<Vec<MixNodeBondAnnotated>> {
Json( let output = output.output.unwrap_or_default();
output.to_response(
_get_active_set_legacy_mixnodes_detailed( _get_active_set_legacy_mixnodes_detailed(
state.node_status_cache(), state.node_status_cache(),
state.nym_contract_cache(), state.nym_contract_cache(),
+185 -77
View File
@@ -7,10 +7,11 @@ use crate::node_status_api::helpers::{
}; };
use crate::support::http::state::AppState; use crate::support::http::state::AppState;
use crate::support::legacy_helpers::{to_legacy_gateway, to_legacy_mixnode}; use crate::support::legacy_helpers::{to_legacy_gateway, to_legacy_mixnode};
use axum::extract::State; use axum::extract::{Query, State};
use axum::{Json, Router}; use axum::Router;
use nym_api_requests::legacy::LegacyMixNodeDetailsWithLayer; use nym_api_requests::legacy::LegacyMixNodeDetailsWithLayer;
use nym_api_requests::models::MixNodeBondAnnotated; use nym_api_requests::models::MixNodeBondAnnotated;
use nym_http_api_common::{FormattedResponse, OutputParams};
use nym_mixnet_contract_common::reward_params::Performance; use nym_mixnet_contract_common::reward_params::Performance;
use nym_mixnet_contract_common::{reward_params::RewardingParams, GatewayBond, Interval, NodeId}; use nym_mixnet_contract_common::{reward_params::RewardingParams, GatewayBond, Interval, NodeId};
use std::collections::HashSet; use std::collections::HashSet;
@@ -55,23 +56,32 @@ pub(crate) fn nym_contract_cache_routes() -> Router<AppState> {
get, get,
path = "/v1/mixnodes", path = "/v1/mixnodes",
responses( responses(
(status = 200, body = Vec<LegacyMixNodeDetailsWithLayer>) (status = 200, content(
) (Vec<LegacyMixNodeDetailsWithLayer> = "application/json"),
(Vec<LegacyMixNodeDetailsWithLayer> = "application/yaml"),
(Vec<LegacyMixNodeDetailsWithLayer> = "application/bincode")
))
),
params(OutputParams)
)] )]
#[deprecated] #[deprecated]
async fn get_mixnodes(State(state): State<AppState>) -> Json<Vec<LegacyMixNodeDetailsWithLayer>> { async fn get_mixnodes(
Query(output): Query<OutputParams>,
State(state): State<AppState>,
) -> FormattedResponse<Vec<LegacyMixNodeDetailsWithLayer>> {
let output = output.output.unwrap_or_default();
let mut out = state.nym_contract_cache().legacy_mixnodes_filtered().await; let mut out = state.nym_contract_cache().legacy_mixnodes_filtered().await;
let Ok(describe_cache) = state.described_nodes_cache.get().await else { let Ok(describe_cache) = state.described_nodes_cache.get().await else {
return Json(out); return output.to_response(out);
}; };
let Some(migrated_nymnodes) = state.nym_contract_cache().all_cached_nym_nodes().await else { let Some(migrated_nymnodes) = state.nym_contract_cache().all_cached_nym_nodes().await else {
return Json(out); return output.to_response(out);
}; };
let Ok(annotations) = state.node_annotations().await else { let Ok(annotations) = state.node_annotations().await else {
return Json(out); return output.to_response(out);
}; };
// safety: valid percentage value // safety: valid percentage value
@@ -100,7 +110,7 @@ async fn get_mixnodes(State(state): State<AppState>) -> Json<Vec<LegacyMixNodeDe
out.push(node); out.push(node);
} }
Json(out) output.to_response(out)
} }
// DEPRECATED: this endpoint now lives in `node_status_api`. Once all consumers are updated, // DEPRECATED: this endpoint now lives in `node_status_api`. Once all consumers are updated,
@@ -115,14 +125,22 @@ async fn get_mixnodes(State(state): State<AppState>) -> Json<Vec<LegacyMixNodeDe
get, get,
path = "/v1/mixnodes/detailed", path = "/v1/mixnodes/detailed",
responses( responses(
(status = 200, body = Vec<MixNodeBondAnnotated>) (status = 200, content(
) (Vec<MixNodeBondAnnotated> = "application/json"),
(Vec<MixNodeBondAnnotated> = "application/yaml"),
(Vec<MixNodeBondAnnotated> = "application/bincode")
))
),
params(OutputParams)
)] )]
#[deprecated] #[deprecated]
async fn get_mixnodes_detailed(State(state): State<AppState>) -> Json<Vec<MixNodeBondAnnotated>> { async fn get_mixnodes_detailed(
_get_legacy_mixnodes_detailed(state.node_status_cache()) Query(output): Query<OutputParams>,
.await State(state): State<AppState>,
.into() ) -> FormattedResponse<Vec<MixNodeBondAnnotated>> {
let output = output.output.unwrap_or_default();
output.to_response(_get_legacy_mixnodes_detailed(state.node_status_cache()).await)
} }
#[utoipa::path( #[utoipa::path(
@@ -130,11 +148,21 @@ async fn get_mixnodes_detailed(State(state): State<AppState>) -> Json<Vec<MixNod
get, get,
path = "/v1/gateways", path = "/v1/gateways",
responses( responses(
(status = 200, body = Vec<GatewayBond>) (status = 200, content(
) (Vec<GatewayBond> = "application/json"),
(Vec<GatewayBond> = "application/yaml"),
(Vec<GatewayBond> = "application/bincode")
))
),
params(OutputParams)
)] )]
#[deprecated] #[deprecated]
async fn get_gateways(State(state): State<AppState>) -> Json<Vec<GatewayBond>> { async fn get_gateways(
Query(output): Query<OutputParams>,
State(state): State<AppState>,
) -> FormattedResponse<Vec<GatewayBond>> {
let output = output.output.unwrap_or_default();
// legacy // legacy
let mut out: Vec<GatewayBond> = state let mut out: Vec<GatewayBond> = state
.nym_contract_cache() .nym_contract_cache()
@@ -145,15 +173,15 @@ async fn get_gateways(State(state): State<AppState>) -> Json<Vec<GatewayBond>> {
.collect(); .collect();
let Ok(describe_cache) = state.described_nodes_cache.get().await else { let Ok(describe_cache) = state.described_nodes_cache.get().await else {
return Json(out); return output.to_response(out);
}; };
let Some(migrated_nymnodes) = state.nym_contract_cache().all_cached_nym_nodes().await else { let Some(migrated_nymnodes) = state.nym_contract_cache().all_cached_nym_nodes().await else {
return Json(out); return output.to_response(out);
}; };
let Ok(annotations) = state.node_annotations().await else { let Ok(annotations) = state.node_annotations().await else {
return Json(out); return output.to_response(out);
}; };
// safety: valid percentage value // safety: valid percentage value
@@ -182,7 +210,7 @@ async fn get_gateways(State(state): State<AppState>) -> Json<Vec<GatewayBond>> {
out.push(node); out.push(node);
} }
Json(out) output.to_response(out)
} }
#[utoipa::path( #[utoipa::path(
@@ -190,14 +218,22 @@ async fn get_gateways(State(state): State<AppState>) -> Json<Vec<GatewayBond>> {
get, get,
path = "/v1/mixnodes/rewarded", path = "/v1/mixnodes/rewarded",
responses( responses(
(status = 200, body = Vec<LegacyMixNodeDetailsWithLayer>) (status = 200, content(
) (Vec<LegacyMixNodeDetailsWithLayer> = "application/json"),
(Vec<LegacyMixNodeDetailsWithLayer> = "application/yaml"),
(Vec<LegacyMixNodeDetailsWithLayer> = "application/bincode")
))
),
params(OutputParams)
)] )]
#[deprecated] #[deprecated]
async fn get_rewarded_set( async fn get_rewarded_set(
Query(output): Query<OutputParams>,
State(state): State<AppState>, State(state): State<AppState>,
) -> Json<Vec<LegacyMixNodeDetailsWithLayer>> { ) -> FormattedResponse<Vec<LegacyMixNodeDetailsWithLayer>> {
Json( let output = output.output.unwrap_or_default();
output.to_response(
state state
.nym_contract_cache() .nym_contract_cache()
.legacy_v1_rewarded_set_mixnodes() .legacy_v1_rewarded_set_mixnodes()
@@ -218,19 +254,28 @@ async fn get_rewarded_set(
get, get,
path = "/v1/mixnodes/rewarded/detailed", path = "/v1/mixnodes/rewarded/detailed",
responses( responses(
(status = 200, body = Vec<MixNodeBondAnnotated>) (status = 200, content(
) (Vec<MixNodeBondAnnotated> = "application/json"),
(Vec<MixNodeBondAnnotated> = "application/yaml"),
(Vec<MixNodeBondAnnotated> = "application/bincode")
))
),
params(OutputParams)
)] )]
#[deprecated] #[deprecated]
async fn get_rewarded_set_detailed( async fn get_rewarded_set_detailed(
Query(output): Query<OutputParams>,
State(state): State<AppState>, State(state): State<AppState>,
) -> Json<Vec<MixNodeBondAnnotated>> { ) -> FormattedResponse<Vec<MixNodeBondAnnotated>> {
_get_rewarded_set_legacy_mixnodes_detailed( let output = output.output.unwrap_or_default();
state.node_status_cache(),
state.nym_contract_cache(), output.to_response(
_get_rewarded_set_legacy_mixnodes_detailed(
state.node_status_cache(),
state.nym_contract_cache(),
)
.await,
) )
.await
.into()
} }
#[utoipa::path( #[utoipa::path(
@@ -238,11 +283,21 @@ async fn get_rewarded_set_detailed(
get, get,
path = "/v1/mixnodes/active", path = "/v1/mixnodes/active",
responses( responses(
(status = 200, body = Vec<LegacyMixNodeDetailsWithLayer>) (status = 200, content(
) (Vec<LegacyMixNodeDetailsWithLayer> = "application/json"),
(Vec<LegacyMixNodeDetailsWithLayer> = "application/yaml"),
(Vec<LegacyMixNodeDetailsWithLayer> = "application/bincode")
))
),
params(OutputParams)
)] )]
#[deprecated] #[deprecated]
async fn get_active_set(State(state): State<AppState>) -> Json<Vec<LegacyMixNodeDetailsWithLayer>> { async fn get_active_set(
Query(output): Query<OutputParams>,
State(state): State<AppState>,
) -> FormattedResponse<Vec<LegacyMixNodeDetailsWithLayer>> {
let output = output.output.unwrap_or_default();
let mut out = state let mut out = state
.nym_contract_cache() .nym_contract_cache()
.legacy_v1_active_set_mixnodes() .legacy_v1_active_set_mixnodes()
@@ -250,19 +305,19 @@ async fn get_active_set(State(state): State<AppState>) -> Json<Vec<LegacyMixNode
.clone(); .clone();
let Some(rewarded_set) = state.nym_contract_cache().rewarded_set().await else { let Some(rewarded_set) = state.nym_contract_cache().rewarded_set().await else {
return Json(out); return output.to_response(out);
}; };
let Ok(describe_cache) = state.described_nodes_cache.get().await else { let Ok(describe_cache) = state.described_nodes_cache.get().await else {
return Json(out); return output.to_response(out);
}; };
let Some(migrated_nymnodes) = state.nym_contract_cache().all_cached_nym_nodes().await else { let Some(migrated_nymnodes) = state.nym_contract_cache().all_cached_nym_nodes().await else {
return Json(out); return output.to_response(out);
}; };
let Ok(annotations) = state.node_annotations().await else { let Ok(annotations) = state.node_annotations().await else {
return Json(out); return output.to_response(out);
}; };
// safety: valid percentage value // safety: valid percentage value
@@ -295,7 +350,7 @@ async fn get_active_set(State(state): State<AppState>) -> Json<Vec<LegacyMixNode
out.push(node); out.push(node);
} }
Json(out) output.to_response(out)
} }
// DEPRECATED: this endpoint now lives in `node_status_api`. Once all consumers are updated, // DEPRECATED: this endpoint now lives in `node_status_api`. Once all consumers are updated,
@@ -311,14 +366,28 @@ async fn get_active_set(State(state): State<AppState>) -> Json<Vec<LegacyMixNode
get, get,
path = "/v1/mixnodes/active/detailed", path = "/v1/mixnodes/active/detailed",
responses( responses(
(status = 200, body = Vec<MixNodeBondAnnotated>) (status = 200, content(
) (Vec<MixNodeBondAnnotated> = "application/json"),
(Vec<MixNodeBondAnnotated> = "application/yaml"),
(Vec<MixNodeBondAnnotated> = "application/bincode")
))
),
params(OutputParams)
)] )]
#[deprecated] #[deprecated]
async fn get_active_set_detailed(State(state): State<AppState>) -> Json<Vec<MixNodeBondAnnotated>> { async fn get_active_set_detailed(
_get_active_set_legacy_mixnodes_detailed(state.node_status_cache(), state.nym_contract_cache()) Query(output): Query<OutputParams>,
.await State(state): State<AppState>,
.into() ) -> FormattedResponse<Vec<MixNodeBondAnnotated>> {
let output = output.output.unwrap_or_default();
output.to_response(
_get_active_set_legacy_mixnodes_detailed(
state.node_status_cache(),
state.nym_contract_cache(),
)
.await,
)
} }
#[utoipa::path( #[utoipa::path(
@@ -326,22 +395,31 @@ async fn get_active_set_detailed(State(state): State<AppState>) -> Json<Vec<MixN
get, get,
path = "/v1/mixnodes/blacklisted", path = "/v1/mixnodes/blacklisted",
responses( responses(
(status = 200, body = Option<HashSet<NodeId>>) (status = 200, content(
) (Option<HashSet<NodeId>> = "application/json"),
(Option<HashSet<NodeId>> = "application/yaml"),
(Option<HashSet<NodeId>> = "application/bincode")
))
),
params(OutputParams)
)] )]
#[deprecated] #[deprecated]
async fn get_blacklisted_mixnodes(State(state): State<AppState>) -> Json<Option<HashSet<NodeId>>> { async fn get_blacklisted_mixnodes(
Query(output): Query<OutputParams>,
State(state): State<AppState>,
) -> FormattedResponse<Option<HashSet<NodeId>>> {
let output = output.output.unwrap_or_default();
let blacklist = state let blacklist = state
.nym_contract_cache() .nym_contract_cache()
.mixnodes_blacklist() .mixnodes_blacklist()
.await .await
.to_owned(); .to_owned();
if blacklist.is_empty() { if blacklist.is_empty() {
None output.to_response(None)
} else { } else {
Some(blacklist) output.to_response(Some(blacklist))
} }
.into()
} }
#[utoipa::path( #[utoipa::path(
@@ -349,18 +427,28 @@ async fn get_blacklisted_mixnodes(State(state): State<AppState>) -> Json<Option<
get, get,
path = "/v1/gateways/blacklisted", path = "/v1/gateways/blacklisted",
responses( responses(
(status = 200, body = Option<HashSet<String>>) (status = 200, content(
) (Option<HashSet<NodeId>> = "application/json"),
(Option<HashSet<NodeId>> = "application/yaml"),
(Option<HashSet<NodeId>> = "application/bincode")
))
),
params(OutputParams)
)] )]
#[deprecated] #[deprecated]
async fn get_blacklisted_gateways(State(state): State<AppState>) -> Json<Option<HashSet<String>>> { async fn get_blacklisted_gateways(
Query(output): Query<OutputParams>,
State(state): State<AppState>,
) -> FormattedResponse<Option<HashSet<String>>> {
let output = output.output.unwrap_or_default();
let cache = state.nym_contract_cache(); let cache = state.nym_contract_cache();
let blacklist = cache.gateways_blacklist().await.clone(); let blacklist = cache.gateways_blacklist().await.clone();
if blacklist.is_empty() { if blacklist.is_empty() {
Json(None) output.to_response(None)
} else { } else {
let gateways = cache.legacy_gateways_all().await; let gateways = cache.legacy_gateways_all().await;
Json(Some( output.to_response(Some(
gateways gateways
.into_iter() .into_iter()
.filter(|g| blacklist.contains(&g.node_id)) .filter(|g| blacklist.contains(&g.node_id))
@@ -375,18 +463,27 @@ async fn get_blacklisted_gateways(State(state): State<AppState>) -> Json<Option<
get, get,
path = "/v1/epoch/reward_params", path = "/v1/epoch/reward_params",
responses( responses(
(status = 200, body = Option<RewardingParams>) (status = 200, content(
) (Option<RewardingParams> = "application/json"),
(Option<RewardingParams> = "application/yaml"),
(Option<RewardingParams> = "application/bincode")
))
),
params(OutputParams)
)] )]
async fn get_interval_reward_params( async fn get_interval_reward_params(
Query(output): Query<OutputParams>,
State(state): State<AppState>, State(state): State<AppState>,
) -> Json<Option<RewardingParams>> { ) -> FormattedResponse<Option<RewardingParams>> {
state let output = output.output.unwrap_or_default();
.nym_contract_cache()
.interval_reward_params() output.to_response(
.await state
.to_owned() .nym_contract_cache()
.into() .interval_reward_params()
.await
.to_owned(),
)
} }
#[utoipa::path( #[utoipa::path(
@@ -394,14 +491,25 @@ async fn get_interval_reward_params(
get, get,
path = "/v1/epoch/current", path = "/v1/epoch/current",
responses( responses(
(status = 200, body = Option<Interval>) (status = 200, content(
) (Option<Interval> = "application/json"),
(Option<Interval> = "application/yaml"),
(Option<Interval> = "application/bincode")
))
),
params(OutputParams)
)] )]
async fn get_current_epoch(State(state): State<AppState>) -> Json<Option<Interval>> { async fn get_current_epoch(
state Query(output): Query<OutputParams>,
.nym_contract_cache() State(state): State<AppState>,
.current_interval() ) -> FormattedResponse<Option<Interval>> {
.await let output = output.output.unwrap_or_default();
.to_owned()
.into() output.to_response(
state
.nym_contract_cache()
.current_interval()
.await
.to_owned(),
)
} }
+27 -12
View File
@@ -3,10 +3,11 @@
use crate::support::http::state::AppState; use crate::support::http::state::AppState;
use crate::support::legacy_helpers::{to_legacy_gateway, to_legacy_mixnode}; use crate::support::legacy_helpers::{to_legacy_gateway, to_legacy_mixnode};
use axum::extract::State; use axum::extract::{Query, State};
use axum::{Json, Router}; use axum::Router;
use nym_api_requests::legacy::LegacyMixNodeBondWithLayer; use nym_api_requests::legacy::LegacyMixNodeBondWithLayer;
use nym_api_requests::models::{LegacyDescribedGateway, LegacyDescribedMixNode}; use nym_api_requests::models::{LegacyDescribedGateway, LegacyDescribedMixNode};
use nym_http_api_common::{FormattedResponse, OutputParams};
use tower_http::compression::CompressionLayer; use tower_http::compression::CompressionLayer;
// we want to mark the routes as deprecated in swagger, but still expose them // we want to mark the routes as deprecated in swagger, but still expose them
@@ -29,22 +30,29 @@ pub(crate) fn legacy_nym_node_routes() -> Router<AppState> {
get, get,
path = "/v1/gateways/described", path = "/v1/gateways/described",
responses( responses(
(status = 200, body = Vec<LegacyDescribedGateway>) (status = 200, content(
) (Vec<LegacyDescribedGateway> = "application/json"),
(Vec<LegacyDescribedGateway> = "application/yaml"),
(Vec<LegacyDescribedGateway> = "application/bincode")
))
),
params(OutputParams)
)] )]
#[deprecated] #[deprecated]
async fn get_gateways_described( async fn get_gateways_described(
Query(output): Query<OutputParams>,
State(state): State<AppState>, State(state): State<AppState>,
) -> Json<Vec<LegacyDescribedGateway>> { ) -> FormattedResponse<Vec<LegacyDescribedGateway>> {
let contract_cache = state.nym_contract_cache(); let contract_cache = state.nym_contract_cache();
let describe_cache = state.described_nodes_cache(); let describe_cache = state.described_nodes_cache();
let output = output.output.unwrap_or_default();
// legacy // legacy
let legacy = contract_cache.legacy_gateways_filtered().await; let legacy = contract_cache.legacy_gateways_filtered().await;
// if the self describe cache is unavailable, well, don't attach describe data and only return legacy gateways // if the self describe cache is unavailable, well, don't attach describe data and only return legacy gateways
let Ok(describe_cache) = describe_cache.get().await else { let Ok(describe_cache) = describe_cache.get().await else {
return Json(legacy.into_iter().map(Into::into).collect()); return output.to_response(legacy.into_iter().map(Into::into).collect());
}; };
let migrated_nymnodes = state.nym_contract_cache().nym_nodes().await; let migrated_nymnodes = state.nym_contract_cache().nym_nodes().await;
@@ -75,7 +83,7 @@ async fn get_gateways_described(
}) })
} }
Json(out) output.to_response(out)
} }
#[utoipa::path( #[utoipa::path(
@@ -83,15 +91,22 @@ async fn get_gateways_described(
get, get,
path = "/v1/mixnodes/described", path = "/v1/mixnodes/described",
responses( responses(
(status = 200, body = Vec<LegacyDescribedMixNode>) (status = 200, content(
) (Vec<LegacyDescribedMixNode> = "application/json"),
(Vec<LegacyDescribedMixNode> = "application/yaml"),
(Vec<LegacyDescribedMixNode> = "application/bincode")
))
),
params(OutputParams)
)] )]
#[deprecated] #[deprecated]
async fn get_mixnodes_described( async fn get_mixnodes_described(
Query(output): Query<OutputParams>,
State(state): State<AppState>, State(state): State<AppState>,
) -> Json<Vec<LegacyDescribedMixNode>> { ) -> FormattedResponse<Vec<LegacyDescribedMixNode>> {
let contract_cache = state.nym_contract_cache(); let contract_cache = state.nym_contract_cache();
let describe_cache = state.described_nodes_cache(); let describe_cache = state.described_nodes_cache();
let output = output.output.unwrap_or_default();
let legacy: Vec<LegacyMixNodeBondWithLayer> = contract_cache let legacy: Vec<LegacyMixNodeBondWithLayer> = contract_cache
.legacy_mixnodes_filtered() .legacy_mixnodes_filtered()
@@ -102,7 +117,7 @@ async fn get_mixnodes_described(
// if the self describe cache is unavailable, well, don't attach describe data and only return legacy mixnodes // if the self describe cache is unavailable, well, don't attach describe data and only return legacy mixnodes
let Ok(describe_cache) = describe_cache.get().await else { let Ok(describe_cache) = describe_cache.get().await else {
return Json(legacy.into_iter().map(Into::into).collect()); return output.to_response(legacy.into_iter().map(Into::into).collect());
}; };
let migrated_nymnodes = state.nym_contract_cache().nym_nodes().await; let migrated_nymnodes = state.nym_contract_cache().nym_nodes().await;
@@ -131,5 +146,5 @@ async fn get_mixnodes_described(
}) })
} }
Json(out) output.to_response(out)
} }
+89 -33
View File
@@ -15,9 +15,9 @@ use nym_api_requests::models::{
}; };
use nym_api_requests::pagination::{PaginatedResponse, Pagination}; use nym_api_requests::pagination::{PaginatedResponse, Pagination};
use nym_contracts_common::NaiveFloat; use nym_contracts_common::NaiveFloat;
use nym_http_api_common::{FormattedResponse, Output, OutputParams};
use nym_mixnet_contract_common::reward_params::Performance; use nym_mixnet_contract_common::reward_params::Performance;
use nym_mixnet_contract_common::NymNodeDetails; use nym_mixnet_contract_common::NymNodeDetails;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::time::Duration; use std::time::Duration;
use time::{Date, OffsetDateTime}; use time::{Date, OffsetDateTime};
@@ -55,10 +55,20 @@ pub(crate) fn nym_node_routes() -> Router<AppState> {
path = "/rewarded-set", path = "/rewarded-set",
context_path = "/v1/nym-nodes", context_path = "/v1/nym-nodes",
responses( responses(
(status = 200, body = RewardedSetResponse) (status = 200, content(
(RewardedSetResponse = "application/json"),
(RewardedSetResponse = "application/yaml"),
(RewardedSetResponse = "application/bincode")
))
), ),
params(OutputParams)
)] )]
async fn rewarded_set(State(state): State<AppState>) -> AxumResult<Json<RewardedSetResponse>> { async fn rewarded_set(
Query(output): Query<OutputParams>,
State(state): State<AppState>,
) -> AxumResult<FormattedResponse<RewardedSetResponse>> {
let output = output.output.unwrap_or_default();
let cached_rewarded_set = state let cached_rewarded_set = state
.nym_contract_cache() .nym_contract_cache()
.rewarded_set() .rewarded_set()
@@ -67,7 +77,7 @@ async fn rewarded_set(State(state): State<AppState>) -> AxumResult<Json<Rewarded
.ok_or(UninitialisedCache)? .ok_or(UninitialisedCache)?
.into_inner(); .into_inner();
Ok(Json( Ok(output.to_response(
nym_mixnet_contract_common::EpochRewardedSet::from(cached_rewarded_set).into(), nym_mixnet_contract_common::EpochRewardedSet::from(cached_rewarded_set).into(),
)) ))
} }
@@ -136,16 +146,21 @@ async fn refresh_described(
path = "/noise", path = "/noise",
context_path = "/v1/nym-nodes", context_path = "/v1/nym-nodes",
responses( responses(
(status = 200, body = PaginatedResponse<NoiseDetails>) (status = 200, content(
(PaginatedResponse<NoiseDetails> = "application/json"),
(PaginatedResponse<NoiseDetails> = "application/yaml"),
(PaginatedResponse<NoiseDetails> = "application/bincode")
))
), ),
params(PaginationRequest) params(PaginationRequest)
)] )]
async fn nodes_noise( async fn nodes_noise(
State(state): State<AppState>, State(state): State<AppState>,
Query(pagination): Query<PaginationRequest>, Query(pagination): Query<PaginationRequest>,
) -> AxumResult<Json<PaginatedResponse<NoiseDetails>>> { ) -> AxumResult<FormattedResponse<PaginatedResponse<NoiseDetails>>> {
// TODO: implement it // TODO: implement it
let _ = pagination; let _ = pagination;
let output = pagination.output.unwrap_or_default();
let describe_cache = state.describe_nodes_cache_data().await?; let describe_cache = state.describe_nodes_cache_data().await?;
@@ -167,7 +182,7 @@ async fn nodes_noise(
let total = nodes.len(); let total = nodes.len();
Ok(Json(PaginatedResponse { Ok(output.to_response(PaginatedResponse {
pagination: Pagination { pagination: Pagination {
total, total,
page: 0, page: 0,
@@ -183,21 +198,26 @@ async fn nodes_noise(
path = "/bonded", path = "/bonded",
context_path = "/v1/nym-nodes", context_path = "/v1/nym-nodes",
responses( responses(
(status = 200, body = PaginatedResponse<NymNodeDetails>) (status = 200, content(
(PaginatedResponse<NymNodeDetails> = "application/json"),
(PaginatedResponse<NymNodeDetails> = "application/yaml"),
(PaginatedResponse<NymNodeDetails> = "application/bincode")
))
), ),
params(PaginationRequest) params(PaginationRequest)
)] )]
async fn get_bonded_nodes( async fn get_bonded_nodes(
State(state): State<AppState>, State(state): State<AppState>,
Query(pagination): Query<PaginationRequest>, Query(pagination): Query<PaginationRequest>,
) -> Json<PaginatedResponse<NymNodeDetails>> { ) -> FormattedResponse<PaginatedResponse<NymNodeDetails>> {
// TODO: implement it // TODO: implement it
let _ = pagination; let _ = pagination;
let output = pagination.output.unwrap_or_default();
let details = state.nym_contract_cache().nym_nodes().await; let details = state.nym_contract_cache().nym_nodes().await;
let total = details.len(); let total = details.len();
Json(PaginatedResponse { output.to_response(PaginatedResponse {
pagination: Pagination { pagination: Pagination {
total, total,
page: 0, page: 0,
@@ -213,21 +233,26 @@ async fn get_bonded_nodes(
path = "/described", path = "/described",
context_path = "/v1/nym-nodes", context_path = "/v1/nym-nodes",
responses( responses(
(status = 200, body = PaginatedResponse<NymNodeDescription>) (status = 200, content(
(PaginatedResponse<NymNodeDescription> = "application/json"),
(PaginatedResponse<NymNodeDescription> = "application/yaml"),
(PaginatedResponse<NymNodeDescription> = "application/bincode")
))
), ),
params(PaginationRequest) params(PaginationRequest)
)] )]
async fn get_described_nodes( async fn get_described_nodes(
State(state): State<AppState>, State(state): State<AppState>,
Query(pagination): Query<PaginationRequest>, Query(pagination): Query<PaginationRequest>,
) -> AxumResult<Json<PaginatedResponse<NymNodeDescription>>> { ) -> AxumResult<FormattedResponse<PaginatedResponse<NymNodeDescription>>> {
// TODO: implement it // TODO: implement it
let _ = pagination; let _ = pagination;
let output = pagination.output.unwrap_or_default();
let cache = state.described_nodes_cache.get().await?; let cache = state.described_nodes_cache.get().await?;
let descriptions = cache.all_nodes().cloned().collect::<Vec<_>>(); let descriptions = cache.all_nodes().cloned().collect::<Vec<_>>();
Ok(Json(PaginatedResponse { Ok(output.to_response(PaginatedResponse {
pagination: Pagination { pagination: Pagination {
total: descriptions.len(), total: descriptions.len(),
page: 0, page: 0,
@@ -243,21 +268,28 @@ async fn get_described_nodes(
path = "/annotation/{node_id}", path = "/annotation/{node_id}",
context_path = "/v1/nym-nodes", context_path = "/v1/nym-nodes",
responses( responses(
(status = 200, body = AnnotationResponse) (status = 200, content(
(AnnotationResponse = "application/json"),
(AnnotationResponse = "application/yaml"),
(AnnotationResponse = "application/bincode")
))
), ),
params(NodeIdParam), params(NodeIdParam, OutputParams),
)] )]
async fn get_node_annotation( async fn get_node_annotation(
Path(NodeIdParam { node_id }): Path<NodeIdParam>, Path(NodeIdParam { node_id }): Path<NodeIdParam>,
Query(output): Query<OutputParams>,
State(state): State<AppState>, State(state): State<AppState>,
) -> AxumResult<Json<AnnotationResponse>> { ) -> AxumResult<FormattedResponse<AnnotationResponse>> {
let output = output.output.unwrap_or_default();
let annotations = state let annotations = state
.node_status_cache .node_status_cache
.node_annotations() .node_annotations()
.await .await
.ok_or_else(AxumErrorResponse::internal)?; .ok_or_else(AxumErrorResponse::internal)?;
Ok(Json(AnnotationResponse { Ok(output.to_response(AnnotationResponse {
node_id, node_id,
annotation: annotations.get(&node_id).cloned(), annotation: annotations.get(&node_id).cloned(),
})) }))
@@ -269,21 +301,28 @@ async fn get_node_annotation(
path = "/performance/{node_id}", path = "/performance/{node_id}",
context_path = "/v1/nym-nodes", context_path = "/v1/nym-nodes",
responses( responses(
(status = 200, body = NodePerformanceResponse) (status = 200, content(
(NodePerformanceResponse = "application/json"),
(NodePerformanceResponse = "application/yaml"),
(NodePerformanceResponse = "application/bincode")
))
), ),
params(NodeIdParam), params(NodeIdParam, OutputParams),
)] )]
async fn get_current_node_performance( async fn get_current_node_performance(
Path(NodeIdParam { node_id }): Path<NodeIdParam>, Path(NodeIdParam { node_id }): Path<NodeIdParam>,
Query(output): Query<OutputParams>,
State(state): State<AppState>, State(state): State<AppState>,
) -> AxumResult<Json<NodePerformanceResponse>> { ) -> AxumResult<FormattedResponse<NodePerformanceResponse>> {
let output = output.output.unwrap_or_default();
let annotations = state let annotations = state
.node_status_cache .node_status_cache
.node_annotations() .node_annotations()
.await .await
.ok_or_else(AxumErrorResponse::internal)?; .ok_or_else(AxumErrorResponse::internal)?;
Ok(Json(NodePerformanceResponse { Ok(output.to_response(NodePerformanceResponse {
node_id, node_id,
performance: annotations performance: annotations
.get(&node_id) .get(&node_id)
@@ -292,12 +331,13 @@ async fn get_current_node_performance(
} }
// todo; probably extract it to requests crate // todo; probably extract it to requests crate
#[derive(Debug, Serialize, Deserialize, Copy, Clone, IntoParams, ToSchema, JsonSchema)] #[derive(Debug, Serialize, Deserialize, Copy, Clone, IntoParams, ToSchema)]
#[into_params(parameter_in = Query)] #[into_params(parameter_in = Query)]
pub(crate) struct DateQuery { pub(crate) struct DateQuery {
#[schema(value_type = String, example = "1970-01-01")] #[schema(value_type = String, example = "1970-01-01")]
#[schemars(with = "String")]
pub(crate) date: Date, pub(crate) date: Date,
pub(crate) output: Option<Output>,
} }
#[utoipa::path( #[utoipa::path(
@@ -306,21 +346,27 @@ pub(crate) struct DateQuery {
path = "/historical-performance/{node_id}", path = "/historical-performance/{node_id}",
context_path = "/v1/nym-nodes", context_path = "/v1/nym-nodes",
responses( responses(
(status = 200, body = NodeDatePerformanceResponse) (status = 200, content(
(NodeDatePerformanceResponse = "application/json"),
(NodeDatePerformanceResponse = "application/yaml"),
(NodeDatePerformanceResponse = "application/bincode")
))
), ),
params(DateQuery, NodeIdParam) params(DateQuery, NodeIdParam)
)] )]
async fn get_historical_performance( async fn get_historical_performance(
Path(NodeIdParam { node_id }): Path<NodeIdParam>, Path(NodeIdParam { node_id }): Path<NodeIdParam>,
Query(DateQuery { date }): Query<DateQuery>, Query(DateQuery { date, output }): Query<DateQuery>,
State(state): State<AppState>, State(state): State<AppState>,
) -> AxumResult<Json<NodeDatePerformanceResponse>> { ) -> AxumResult<FormattedResponse<NodeDatePerformanceResponse>> {
let output = output.unwrap_or_default();
let uptime = state let uptime = state
.storage() .storage()
.get_historical_node_uptime_on(node_id, date) .get_historical_node_uptime_on(node_id, date)
.await?; .await?;
Ok(Json(NodeDatePerformanceResponse { Ok(output.to_response(NodeDatePerformanceResponse {
node_id, node_id,
date, date,
performance: uptime.and_then(|u| { performance: uptime.and_then(|u| {
@@ -337,7 +383,11 @@ async fn get_historical_performance(
path = "/performance-history/{node_id}", path = "/performance-history/{node_id}",
context_path = "/v1/nym-nodes", context_path = "/v1/nym-nodes",
responses( responses(
(status = 200, body = PerformanceHistoryResponse) (status = 200, content(
(PerformanceHistoryResponse = "application/json"),
(PerformanceHistoryResponse = "application/yaml"),
(PerformanceHistoryResponse = "application/bincode")
))
), ),
params(PaginationRequest, NodeIdParam) params(PaginationRequest, NodeIdParam)
)] )]
@@ -345,9 +395,10 @@ async fn get_node_performance_history(
Path(NodeIdParam { node_id }): Path<NodeIdParam>, Path(NodeIdParam { node_id }): Path<NodeIdParam>,
State(state): State<AppState>, State(state): State<AppState>,
Query(pagination): Query<PaginationRequest>, Query(pagination): Query<PaginationRequest>,
) -> AxumResult<Json<PerformanceHistoryResponse>> { ) -> AxumResult<FormattedResponse<PerformanceHistoryResponse>> {
// TODO: implement it // TODO: implement it
let _ = pagination; let _ = pagination;
let output = pagination.output.unwrap_or_default();
let history = state let history = state
.storage() .storage()
@@ -358,7 +409,7 @@ async fn get_node_performance_history(
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let total = history.len(); let total = history.len();
Ok(Json(PerformanceHistoryResponse { Ok(output.to_response(PerformanceHistoryResponse {
node_id, node_id,
history: PaginatedResponse { history: PaginatedResponse {
pagination: Pagination { pagination: Pagination {
@@ -377,7 +428,11 @@ async fn get_node_performance_history(
path = "/uptime-history/{node_id}", path = "/uptime-history/{node_id}",
context_path = "/v1/nym-nodes", context_path = "/v1/nym-nodes",
responses( responses(
(status = 200, body = PerformanceHistoryResponse) (status = 200, content(
(PerformanceHistoryResponse = "application/json"),
(PerformanceHistoryResponse = "application/yaml"),
(PerformanceHistoryResponse = "application/bincode")
))
), ),
params(PaginationRequest, NodeIdParam) params(PaginationRequest, NodeIdParam)
)] )]
@@ -385,9 +440,10 @@ async fn get_node_uptime_history(
Path(NodeIdParam { node_id }): Path<NodeIdParam>, Path(NodeIdParam { node_id }): Path<NodeIdParam>,
State(state): State<AppState>, State(state): State<AppState>,
Query(pagination): Query<PaginationRequest>, Query(pagination): Query<PaginationRequest>,
) -> AxumResult<Json<UptimeHistoryResponse>> { ) -> AxumResult<FormattedResponse<UptimeHistoryResponse>> {
// TODO: implement it // TODO: implement it
let _ = pagination; let _ = pagination;
let output = pagination.output.unwrap_or_default();
let history = state let history = state
.storage() .storage()
@@ -398,7 +454,7 @@ async fn get_node_uptime_history(
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let total = history.len(); let total = history.len();
Ok(Json(UptimeHistoryResponse { Ok(output.to_response(UptimeHistoryResponse {
node_id, node_id,
history: PaginatedResponse { history: PaginatedResponse {
pagination: Pagination { pagination: Pagination {
@@ -5,8 +5,8 @@ use crate::node_status_api::models::{AxumErrorResponse, AxumResult};
use crate::nym_nodes::handlers::unstable::NodesParamsWithRole; use crate::nym_nodes::handlers::unstable::NodesParamsWithRole;
use crate::support::http::state::AppState; use crate::support::http::state::AppState;
use axum::extract::{Query, State}; use axum::extract::{Query, State};
use axum::Json;
use nym_api_requests::nym_nodes::{CachedNodesResponse, FullFatNode}; use nym_api_requests::nym_nodes::{CachedNodesResponse, FullFatNode};
use nym_http_api_common::FormattedResponse;
#[utoipa::path( #[utoipa::path(
tag = "Unstable Nym Nodes", tag = "Unstable Nym Nodes",
@@ -22,6 +22,6 @@ use nym_api_requests::nym_nodes::{CachedNodesResponse, FullFatNode};
pub(super) async fn nodes_detailed( pub(super) async fn nodes_detailed(
_state: State<AppState>, _state: State<AppState>,
_query_params: Query<NodesParamsWithRole>, _query_params: Query<NodesParamsWithRole>,
) -> AxumResult<Json<CachedNodesResponse<FullFatNode>>> { ) -> AxumResult<FormattedResponse<CachedNodesResponse<FullFatNode>>> {
Err(AxumErrorResponse::not_implemented()) Err(AxumErrorResponse::not_implemented())
} }
+19 -5
View File
@@ -30,12 +30,13 @@ use crate::nym_nodes::handlers::unstable::skimmed::{
}; };
use crate::support::http::helpers::PaginationRequest; use crate::support::http::helpers::PaginationRequest;
use crate::support::http::state::AppState; use crate::support::http::state::AppState;
use axum::extract::State; use axum::extract::{Query, State};
use axum::routing::{get, post}; use axum::routing::{get, post};
use axum::{Json, Router}; use axum::{Json, Router};
use nym_api_requests::nym_nodes::{ use nym_api_requests::nym_nodes::{
NodeRoleQueryParam, NodesByAddressesRequestBody, NodesByAddressesResponse, NodeRoleQueryParam, NodesByAddressesRequestBody, NodesByAddressesResponse,
}; };
use nym_http_api_common::{FormattedResponse, Output, OutputParams};
use serde::Deserialize; use serde::Deserialize;
use std::collections::HashMap; use std::collections::HashMap;
use tower_http::compression::CompressionLayer; use tower_http::compression::CompressionLayer;
@@ -98,6 +99,8 @@ struct NodesParamsWithRole {
// the client already knows about the latest topology state, allowing a `no-updates` response // the client already knows about the latest topology state, allowing a `no-updates` response
// instead of wasting bandwidth serving an unchanged topology. // instead of wasting bandwidth serving an unchanged topology.
epoch_id: Option<u32>, epoch_id: Option<u32>,
output: Option<Output>,
} }
#[derive(Debug, Deserialize, utoipa::IntoParams)] #[derive(Debug, Deserialize, utoipa::IntoParams)]
@@ -113,6 +116,7 @@ struct NodesParams {
// the client already knows about the latest topology state, allowing a `no-updates` response // the client already knows about the latest topology state, allowing a `no-updates` response
// instead of wasting bandwidth serving an unchanged topology. // instead of wasting bandwidth serving an unchanged topology.
epoch_id: Option<u32>, epoch_id: Option<u32>,
output: Option<Output>,
} }
impl From<NodesParamsWithRole> for NodesParams { impl From<NodesParamsWithRole> for NodesParams {
@@ -123,6 +127,7 @@ impl From<NodesParamsWithRole> for NodesParams {
page: params.page, page: params.page,
per_page: params.per_page, per_page: params.per_page,
epoch_id: params.epoch_id, epoch_id: params.epoch_id,
output: params.output,
} }
} }
} }
@@ -130,6 +135,7 @@ impl From<NodesParamsWithRole> for NodesParams {
impl<'a> From<&'a NodesParams> for PaginationRequest { impl<'a> From<&'a NodesParams> for PaginationRequest {
fn from(params: &'a NodesParams) -> Self { fn from(params: &'a NodesParams) -> Self {
PaginationRequest { PaginationRequest {
output: params.output,
page: params.page, page: params.page,
per_page: params.per_page, per_page: params.per_page,
} }
@@ -143,13 +149,19 @@ impl<'a> From<&'a NodesParams> for PaginationRequest {
path = "/by-addresses", path = "/by-addresses",
context_path = "/v1/unstable/nym-nodes", context_path = "/v1/unstable/nym-nodes",
responses( responses(
(status = 200, body = NodesByAddressesResponse) (status = 200, content(
) (NodesByAddressesResponse = "application/json"),
(NodesByAddressesResponse = "application/yaml"),
(NodesByAddressesResponse = "application/bincode")
))
),
params(OutputParams)
)] )]
async fn nodes_by_addresses( async fn nodes_by_addresses(
Query(output): Query<OutputParams>,
state: State<AppState>, state: State<AppState>,
Json(body): Json<NodesByAddressesRequestBody>, Json(body): Json<NodesByAddressesRequestBody>,
) -> AxumResult<Json<NodesByAddressesResponse>> { ) -> AxumResult<FormattedResponse<NodesByAddressesResponse>> {
// if the request is too big, simply reject it // if the request is too big, simply reject it
if body.addresses.len() > 100 { if body.addresses.len() > 100 {
return Err(AxumErrorResponse::bad_request( return Err(AxumErrorResponse::bad_request(
@@ -157,6 +169,8 @@ async fn nodes_by_addresses(
)); ));
} }
let output = output.output.unwrap_or_default();
// TODO: perhaps introduce different cache because realistically nym-api will receive // TODO: perhaps introduce different cache because realistically nym-api will receive
// request for the same couple addresses from all nodes in quick succession // request for the same couple addresses from all nodes in quick succession
let describe_cache = state.describe_nodes_cache_data().await?; let describe_cache = state.describe_nodes_cache_data().await?;
@@ -166,5 +180,5 @@ async fn nodes_by_addresses(
existence.insert(address, describe_cache.node_with_address(address)); existence.insert(address, describe_cache.node_with_address(address));
} }
Ok(Json(NodesByAddressesResponse { existence })) Ok(output.to_response(NodesByAddressesResponse { existence }))
} }
@@ -5,8 +5,8 @@ use crate::node_status_api::models::{AxumErrorResponse, AxumResult};
use crate::nym_nodes::handlers::unstable::NodesParamsWithRole; use crate::nym_nodes::handlers::unstable::NodesParamsWithRole;
use crate::support::http::state::AppState; use crate::support::http::state::AppState;
use axum::extract::{Query, State}; use axum::extract::{Query, State};
use axum::Json;
use nym_api_requests::nym_nodes::{CachedNodesResponse, SemiSkimmedNode}; use nym_api_requests::nym_nodes::{CachedNodesResponse, SemiSkimmedNode};
use nym_http_api_common::FormattedResponse;
#[utoipa::path( #[utoipa::path(
tag = "Unstable Nym Nodes", tag = "Unstable Nym Nodes",
@@ -22,6 +22,6 @@ use nym_api_requests::nym_nodes::{CachedNodesResponse, SemiSkimmedNode};
pub(super) async fn nodes_expanded( pub(super) async fn nodes_expanded(
_state: State<AppState>, _state: State<AppState>,
_query_params: Query<NodesParamsWithRole>, _query_params: Query<NodesParamsWithRole>,
) -> AxumResult<Json<CachedNodesResponse<SemiSkimmedNode>>> { ) -> AxumResult<FormattedResponse<CachedNodesResponse<SemiSkimmedNode>>> {
Err(AxumErrorResponse::not_implemented()) Err(AxumErrorResponse::not_implemented())
} }
@@ -8,7 +8,6 @@ use crate::nym_nodes::handlers::unstable::{NodesParams, NodesParamsWithRole};
use crate::support::caching::Cache; use crate::support::caching::Cache;
use crate::support::http::state::AppState; use crate::support::http::state::AppState;
use axum::extract::{Query, State}; use axum::extract::{Query, State};
use axum::Json;
use nym_api_requests::models::{ use nym_api_requests::models::{
NodeAnnotation, NymNodeDescription, OffsetDateTimeJsonSchemaWrapper, NodeAnnotation, NymNodeDescription, OffsetDateTimeJsonSchemaWrapper,
}; };
@@ -16,15 +15,18 @@ use nym_api_requests::nym_nodes::{
CachedNodesResponse, NodeRole, NodeRoleQueryParam, PaginatedCachedNodesResponse, SkimmedNode, CachedNodesResponse, NodeRole, NodeRoleQueryParam, PaginatedCachedNodesResponse, SkimmedNode,
}; };
use nym_api_requests::pagination::PaginatedResponse; use nym_api_requests::pagination::PaginatedResponse;
use nym_http_api_common::{FormattedResponse, Output};
use nym_mixnet_contract_common::NodeId; use nym_mixnet_contract_common::NodeId;
use nym_topology::CachedEpochRewardedSet; use nym_topology::CachedEpochRewardedSet;
use std::collections::HashMap; use std::collections::HashMap;
use std::future::Future; use std::future::Future;
use std::time::Duration;
use tokio::sync::RwLockReadGuard; use tokio::sync::RwLockReadGuard;
use tracing::trace; use tracing::trace;
use utoipa::ToSchema; use utoipa::ToSchema;
pub type PaginatedSkimmedNodes = AxumResult<Json<PaginatedCachedNodesResponse<SkimmedNode>>>; pub type PaginatedSkimmedNodes =
AxumResult<FormattedResponse<PaginatedCachedNodesResponse<SkimmedNode>>>;
/// Given all relevant caches, build part of response for JUST Nym Nodes /// Given all relevant caches, build part of response for JUST Nym Nodes
fn build_nym_nodes_response<'a, NI>( fn build_nym_nodes_response<'a, NI>(
@@ -97,6 +99,7 @@ async fn build_skimmed_nodes_response<'a, NI, LG, Fut, LN>(
nym_nodes_subset: NI, nym_nodes_subset: NI,
annotated_legacy_nodes_getter: LG, annotated_legacy_nodes_getter: LG,
active_only: bool, active_only: bool,
output: Output,
) -> PaginatedSkimmedNodes ) -> PaginatedSkimmedNodes
where where
// iterator returning relevant subset of nym-nodes (like mixing nym-nodes, entries, etc.) // iterator returning relevant subset of nym-nodes (like mixing nym-nodes, entries, etc.)
@@ -124,18 +127,20 @@ where
// (ideally it'd be tied directly to the NI iterator, but I couldn't defeat the compiler) // (ideally it'd be tied directly to the NI iterator, but I couldn't defeat the compiler)
let describe_cache = state.describe_nodes_cache_data().await?; let describe_cache = state.describe_nodes_cache_data().await?;
let maybe_interval = state let Some(interval) = state
.nym_contract_cache() .nym_contract_cache()
.current_interval() .current_interval()
.await .await
.to_owned(); .to_owned()
else {
// if we can't obtain interval information, it means caches are not valid
return Err(AxumErrorResponse::service_unavailable());
};
// 4.0 If the client indicates that they already know about the current topology send empty response // 4.0 If the client indicates that they already know about the current topology send empty response
if let Some(client_known_epoch) = query_params.epoch_id { if let Some(client_known_epoch) = query_params.epoch_id {
if let Some(ref interval) = maybe_interval { if client_known_epoch == interval.current_epoch_id() {
if client_known_epoch == interval.current_epoch_id() { return Ok(output.to_response(PaginatedCachedNodesResponse::no_updates()));
return Ok(Json(PaginatedCachedNodesResponse::no_updates()));
}
} }
} }
@@ -152,8 +157,8 @@ where
describe_cache.timestamp(), describe_cache.timestamp(),
]); ]);
return Ok(Json( return Ok(output.to_response(
PaginatedCachedNodesResponse::new_full(refreshed_at, nodes).fresh(maybe_interval), PaginatedCachedNodesResponse::new_full(refreshed_at, nodes).fresh(Some(interval)),
)); ));
} }
@@ -176,9 +181,19 @@ where
annotated_legacy_nodes.timestamp(), annotated_legacy_nodes.timestamp(),
]); ]);
Ok(Json( let base_response = output.to_response(
PaginatedCachedNodesResponse::new_full(refreshed_at, nodes).fresh(maybe_interval), PaginatedCachedNodesResponse::new_full(refreshed_at, nodes).fresh(Some(interval)),
)) );
if !active_only {
return Ok(base_response);
}
// if caller requested only active nodes, the response is valid until the epoch changes
// (but add 2 minutes due to epoch transition not being instantaneous
let epoch_end = interval.current_epoch_end();
let expiration = epoch_end + Duration::from_secs(120);
Ok(base_response.with_expires_header(expiration))
} }
/// Deprecated query that gets ALL gateways /// Deprecated query that gets ALL gateways
@@ -189,22 +204,30 @@ where
path = "/gateways/skimmed", path = "/gateways/skimmed",
context_path = "/v1/unstable/nym-nodes", context_path = "/v1/unstable/nym-nodes",
responses( responses(
(status = 200, body = CachedNodesResponse<SkimmedNode>) (status = 200, content(
) (CachedNodesResponse<SkimmedNode> = "application/json"),
(CachedNodesResponse<SkimmedNode> = "application/yaml"),
(CachedNodesResponse<SkimmedNode> = "application/bincode")
))
),
)] )]
#[deprecated(note = "use '/v1/unstable/nym-nodes/entry-gateways/skimmed/all' instead")] #[deprecated(note = "use '/v1/unstable/nym-nodes/entry-gateways/skimmed/all' instead")]
pub(super) async fn deprecated_gateways_basic( pub(super) async fn deprecated_gateways_basic(
state: State<AppState>, state: State<AppState>,
query_params: Query<NodesParams>, query_params: Query<NodesParams>,
) -> AxumResult<Json<CachedNodesResponse<SkimmedNode>>> { ) -> AxumResult<FormattedResponse<CachedNodesResponse<SkimmedNode>>> {
let output = query_params.output.unwrap_or_default();
// 1. call '/v1/unstable/skimmed/entry-gateways/all' // 1. call '/v1/unstable/skimmed/entry-gateways/all'
let all_gateways = entry_gateways_basic_all(state, query_params).await?; let all_gateways = entry_gateways_basic_all(state, query_params)
.await?
.into_inner();
// 3. return result // 3. return result
Ok(Json(CachedNodesResponse { Ok(output.to_response(CachedNodesResponse {
refreshed_at: all_gateways.refreshed_at, refreshed_at: all_gateways.refreshed_at,
// 2. remove pagination // 2. remove pagination
nodes: all_gateways.0.nodes.data, nodes: all_gateways.nodes.data,
})) }))
} }
@@ -216,30 +239,40 @@ pub(super) async fn deprecated_gateways_basic(
path = "/mixnodes/skimmed", path = "/mixnodes/skimmed",
context_path = "/v1/unstable/nym-nodes", context_path = "/v1/unstable/nym-nodes",
responses( responses(
(status = 200, body = CachedNodesResponse<SkimmedNode>) (status = 200, content(
) (CachedNodesResponse<SkimmedNode> = "application/json"),
(CachedNodesResponse<SkimmedNode> = "application/yaml"),
(CachedNodesResponse<SkimmedNode> = "application/bincode")
))
),
)] )]
#[deprecated(note = "use '/v1/unstable/nym-nodes/skimmed/mixnodes/active' instead")] #[deprecated(note = "use '/v1/unstable/nym-nodes/skimmed/mixnodes/active' instead")]
pub(super) async fn deprecated_mixnodes_basic( pub(super) async fn deprecated_mixnodes_basic(
state: State<AppState>, state: State<AppState>,
query_params: Query<NodesParams>, query_params: Query<NodesParams>,
) -> AxumResult<Json<CachedNodesResponse<SkimmedNode>>> { ) -> AxumResult<FormattedResponse<CachedNodesResponse<SkimmedNode>>> {
let output = query_params.output.unwrap_or_default();
// 1. call '/v1/unstable/nym-nodes/skimmed/mixnodes/active' // 1. call '/v1/unstable/nym-nodes/skimmed/mixnodes/active'
let active_mixnodes = mixnodes_basic_active(state, query_params).await?; let active_mixnodes = mixnodes_basic_active(state, query_params)
.await?
.into_inner();
// 3. return result // 3. return result
Ok(Json(CachedNodesResponse { Ok(output.to_response(CachedNodesResponse {
refreshed_at: active_mixnodes.refreshed_at, refreshed_at: active_mixnodes.refreshed_at,
// 2. remove pagination // 2. remove pagination
nodes: active_mixnodes.0.nodes.data, nodes: active_mixnodes.nodes.data,
})) }))
} }
async fn nodes_basic( async fn nodes_basic(
state: State<AppState>, state: State<AppState>,
Query(_query_params): Query<NodesParams>, Query(query_params): Query<NodesParams>,
active_only: bool, active_only: bool,
) -> PaginatedSkimmedNodes { ) -> PaginatedSkimmedNodes {
let output = query_params.output.unwrap_or_default();
// unfortunately we have to build the response semi-manually here as we need to add two sources of legacy nodes // unfortunately we have to build the response semi-manually here as we need to add two sources of legacy nodes
// 1. grab all relevant described nym-nodes // 1. grab all relevant described nym-nodes
@@ -281,10 +314,7 @@ async fn nodes_basic(
legacy_gateways.timestamp(), legacy_gateways.timestamp(),
]); ]);
Ok(Json(PaginatedCachedNodesResponse::new_full( Ok(output.to_response(PaginatedCachedNodesResponse::new_full(refreshed_at, nodes)))
refreshed_at,
nodes,
)))
} }
#[allow(dead_code)] // not dead, used in OpenAPI docs #[allow(dead_code)] // not dead, used in OpenAPI docs
@@ -305,8 +335,12 @@ pub struct PaginatedCachedNodesResponseSchema {
path = "", path = "",
context_path = "/v1/unstable/nym-nodes/skimmed", context_path = "/v1/unstable/nym-nodes/skimmed",
responses( responses(
(status = 200, body = PaginatedCachedNodesResponseSchema) (status = 200, content(
) (PaginatedCachedNodesResponseSchema = "application/json"),
(PaginatedCachedNodesResponseSchema = "application/yaml"),
(PaginatedCachedNodesResponseSchema = "application/bincode")
))
),
)] )]
pub(super) async fn nodes_basic_all( pub(super) async fn nodes_basic_all(
state: State<AppState>, state: State<AppState>,
@@ -338,8 +372,12 @@ pub(super) async fn nodes_basic_all(
path = "/active", path = "/active",
context_path = "/v1/unstable/nym-nodes/skimmed", context_path = "/v1/unstable/nym-nodes/skimmed",
responses( responses(
(status = 200, body = PaginatedCachedNodesResponseSchema) (status = 200, content(
) (PaginatedCachedNodesResponseSchema = "application/json"),
(PaginatedCachedNodesResponseSchema = "application/yaml"),
(PaginatedCachedNodesResponseSchema = "application/bincode")
))
),
)] )]
pub(super) async fn nodes_basic_active( pub(super) async fn nodes_basic_active(
state: State<AppState>, state: State<AppState>,
@@ -367,6 +405,8 @@ async fn mixnodes_basic(
query_params: Query<NodesParams>, query_params: Query<NodesParams>,
active_only: bool, active_only: bool,
) -> PaginatedSkimmedNodes { ) -> PaginatedSkimmedNodes {
let output = query_params.output.unwrap_or_default();
// 1. grab all relevant described nym-nodes // 1. grab all relevant described nym-nodes
let describe_cache = state.describe_nodes_cache_data().await?; let describe_cache = state.describe_nodes_cache_data().await?;
let mixing_nym_nodes = describe_cache.mixing_nym_nodes(); let mixing_nym_nodes = describe_cache.mixing_nym_nodes();
@@ -377,6 +417,7 @@ async fn mixnodes_basic(
mixing_nym_nodes, mixing_nym_nodes,
|state| state.legacy_mixnode_annotations(), |state| state.legacy_mixnode_annotations(),
active_only, active_only,
output,
) )
.await .await
} }
@@ -390,8 +431,12 @@ async fn mixnodes_basic(
path = "/mixnodes/all", path = "/mixnodes/all",
context_path = "/v1/unstable/nym-nodes/skimmed", context_path = "/v1/unstable/nym-nodes/skimmed",
responses( responses(
(status = 200, body = PaginatedCachedNodesResponseSchema) (status = 200, content(
) (PaginatedCachedNodesResponseSchema = "application/json"),
(PaginatedCachedNodesResponseSchema = "application/yaml"),
(PaginatedCachedNodesResponseSchema = "application/bincode")
))
),
)] )]
pub(super) async fn mixnodes_basic_all( pub(super) async fn mixnodes_basic_all(
state: State<AppState>, state: State<AppState>,
@@ -409,8 +454,12 @@ pub(super) async fn mixnodes_basic_all(
path = "/mixnodes/active", path = "/mixnodes/active",
context_path = "/v1/unstable/nym-nodes/skimmed", context_path = "/v1/unstable/nym-nodes/skimmed",
responses( responses(
(status = 200, body = PaginatedCachedNodesResponseSchema) (status = 200, content(
) (PaginatedCachedNodesResponseSchema = "application/json"),
(PaginatedCachedNodesResponseSchema = "application/yaml"),
(PaginatedCachedNodesResponseSchema = "application/bincode")
))
),
)] )]
pub(super) async fn mixnodes_basic_active( pub(super) async fn mixnodes_basic_active(
state: State<AppState>, state: State<AppState>,
@@ -424,6 +473,8 @@ async fn entry_gateways_basic(
query_params: Query<NodesParams>, query_params: Query<NodesParams>,
active_only: bool, active_only: bool,
) -> PaginatedSkimmedNodes { ) -> PaginatedSkimmedNodes {
let output = query_params.output.unwrap_or_default();
// 1. grab all relevant described nym-nodes // 1. grab all relevant described nym-nodes
let describe_cache = state.describe_nodes_cache_data().await?; let describe_cache = state.describe_nodes_cache_data().await?;
let mixing_nym_nodes = describe_cache.entry_capable_nym_nodes(); let mixing_nym_nodes = describe_cache.entry_capable_nym_nodes();
@@ -434,6 +485,7 @@ async fn entry_gateways_basic(
mixing_nym_nodes, mixing_nym_nodes,
|state| state.legacy_gateways_annotations(), |state| state.legacy_gateways_annotations(),
active_only, active_only,
output,
) )
.await .await
} }
@@ -447,8 +499,12 @@ async fn entry_gateways_basic(
path = "/entry-gateways/active", path = "/entry-gateways/active",
context_path = "/v1/unstable/nym-nodes/skimmed", context_path = "/v1/unstable/nym-nodes/skimmed",
responses( responses(
(status = 200, body = PaginatedCachedNodesResponseSchema) (status = 200, content(
) (PaginatedCachedNodesResponseSchema = "application/json"),
(PaginatedCachedNodesResponseSchema = "application/yaml"),
(PaginatedCachedNodesResponseSchema = "application/bincode")
))
),
)] )]
pub(super) async fn entry_gateways_basic_active( pub(super) async fn entry_gateways_basic_active(
state: State<AppState>, state: State<AppState>,
@@ -466,8 +522,12 @@ pub(super) async fn entry_gateways_basic_active(
path = "/entry-gateways/all", path = "/entry-gateways/all",
context_path = "/v1/unstable/nym-nodes/skimmed", context_path = "/v1/unstable/nym-nodes/skimmed",
responses( responses(
(status = 200, body = PaginatedCachedNodesResponseSchema) (status = 200, content(
) (PaginatedCachedNodesResponseSchema = "application/json"),
(PaginatedCachedNodesResponseSchema = "application/yaml"),
(PaginatedCachedNodesResponseSchema = "application/bincode")
))
),
)] )]
pub(super) async fn entry_gateways_basic_all( pub(super) async fn entry_gateways_basic_all(
state: State<AppState>, state: State<AppState>,
@@ -481,6 +541,8 @@ async fn exit_gateways_basic(
query_params: Query<NodesParams>, query_params: Query<NodesParams>,
active_only: bool, active_only: bool,
) -> PaginatedSkimmedNodes { ) -> PaginatedSkimmedNodes {
let output = query_params.output.unwrap_or_default();
// 1. grab all relevant described nym-nodes // 1. grab all relevant described nym-nodes
let describe_cache = state.describe_nodes_cache_data().await?; let describe_cache = state.describe_nodes_cache_data().await?;
let mixing_nym_nodes = describe_cache.exit_capable_nym_nodes(); let mixing_nym_nodes = describe_cache.exit_capable_nym_nodes();
@@ -491,6 +553,7 @@ async fn exit_gateways_basic(
mixing_nym_nodes, mixing_nym_nodes,
|state| state.legacy_gateways_annotations(), |state| state.legacy_gateways_annotations(),
active_only, active_only,
output,
) )
.await .await
} }
@@ -504,8 +567,12 @@ async fn exit_gateways_basic(
path = "/exit-gateways/active", path = "/exit-gateways/active",
context_path = "/v1/unstable/nym-nodes/skimmed", context_path = "/v1/unstable/nym-nodes/skimmed",
responses( responses(
(status = 200, body = PaginatedCachedNodesResponseSchema) (status = 200, content(
) (PaginatedCachedNodesResponseSchema = "application/json"),
(PaginatedCachedNodesResponseSchema = "application/yaml"),
(PaginatedCachedNodesResponseSchema = "application/bincode")
))
),
)] )]
pub(super) async fn exit_gateways_basic_active( pub(super) async fn exit_gateways_basic_active(
state: State<AppState>, state: State<AppState>,
@@ -523,8 +590,12 @@ pub(super) async fn exit_gateways_basic_active(
path = "/exit-gateways/all", path = "/exit-gateways/all",
context_path = "/v1/unstable/nym-nodes/skimmed", context_path = "/v1/unstable/nym-nodes/skimmed",
responses( responses(
(status = 200, body = PaginatedCachedNodesResponseSchema) (status = 200, content(
) (PaginatedCachedNodesResponseSchema = "application/json"),
(PaginatedCachedNodesResponseSchema = "application/yaml"),
(PaginatedCachedNodesResponseSchema = "application/bincode")
))
),
)] )]
pub(super) async fn exit_gateways_basic_all( pub(super) async fn exit_gateways_basic_all(
state: State<AppState>, state: State<AppState>,
+40 -14
View File
@@ -4,14 +4,14 @@
use crate::node_status_api::models::{AxumErrorResponse, AxumResult}; use crate::node_status_api::models::{AxumErrorResponse, AxumResult};
use crate::status::ApiStatusState; use crate::status::ApiStatusState;
use crate::support::http::state::AppState; use crate::support::http::state::AppState;
use axum::extract::State; use axum::extract::{Query, State};
use axum::Json;
use axum::Router; use axum::Router;
use nym_api_requests::models::{ use nym_api_requests::models::{
ApiHealthResponse, ApiStatus, ChainStatus, SignerInformationResponse, ApiHealthResponse, ApiStatus, ChainStatus, SignerInformationResponse,
}; };
use nym_bin_common::build_information::BinaryBuildInformationOwned; use nym_bin_common::build_information::BinaryBuildInformationOwned;
use nym_compact_ecash::Base58; use nym_compact_ecash::Base58;
use nym_http_api_common::{FormattedResponse, OutputParams};
use std::time::Duration; use std::time::Duration;
use time::OffsetDateTime; use time::OffsetDateTime;
@@ -30,12 +30,22 @@ pub(crate) fn api_status_routes() -> Router<AppState> {
get, get,
path = "/v1/api-status/health", path = "/v1/api-status/health",
responses( responses(
(status = 200, body = ApiHealthResponse) (status = 200, content(
) (ApiHealthResponse = "application/json"),
(ApiHealthResponse = "application/yaml"),
(ApiHealthResponse = "application/bincode")
))
),
params(OutputParams)
)] )]
async fn health(State(state): State<AppState>) -> Json<ApiHealthResponse> { async fn health(
Query(output): Query<OutputParams>,
State(state): State<AppState>,
) -> FormattedResponse<ApiHealthResponse> {
const CHAIN_STALL_THRESHOLD: Duration = Duration::from_secs(5 * 60); const CHAIN_STALL_THRESHOLD: Duration = Duration::from_secs(5 * 60);
let output = output.output.unwrap_or_default();
let uptime = state.api_status.startup_time.elapsed(); let uptime = state.api_status.startup_time.elapsed();
let chain_status = match state let chain_status = match state
.chain_status_cache .chain_status_cache
@@ -61,7 +71,7 @@ async fn health(State(state): State<AppState>) -> Json<ApiHealthResponse> {
chain_status, chain_status,
uptime: uptime.as_secs(), uptime: uptime.as_secs(),
}; };
Json(health) output.to_response(health)
} }
#[utoipa::path( #[utoipa::path(
@@ -69,13 +79,21 @@ async fn health(State(state): State<AppState>) -> Json<ApiHealthResponse> {
get, get,
path = "/v1/api-status/build-information", path = "/v1/api-status/build-information",
responses( responses(
(status = 200, body = BinaryBuildInformationOwned) (status = 200, content(
) (BinaryBuildInformationOwned = "application/json"),
(BinaryBuildInformationOwned = "application/yaml"),
(BinaryBuildInformationOwned = "application/bincode")
))
),
params(OutputParams)
)] )]
async fn build_information( async fn build_information(
Query(output): Query<OutputParams>,
State(state): State<ApiStatusState>, State(state): State<ApiStatusState>,
) -> Json<BinaryBuildInformationOwned> { ) -> FormattedResponse<BinaryBuildInformationOwned> {
Json(state.build_information.to_owned()) let output = output.output.unwrap_or_default();
output.to_response(state.build_information.to_owned())
} }
#[utoipa::path( #[utoipa::path(
@@ -83,17 +101,25 @@ async fn build_information(
get, get,
path = "/v1/api-status/signer-information", path = "/v1/api-status/signer-information",
responses( responses(
(status = 200, body = SignerInformationResponse) (status = 200, content(
) (SignerInformationResponse = "application/json"),
(SignerInformationResponse = "application/yaml"),
(SignerInformationResponse = "application/bincode")
))
),
params(OutputParams)
)] )]
async fn signer_information( async fn signer_information(
Query(output): Query<OutputParams>,
State(state): State<ApiStatusState>, State(state): State<ApiStatusState>,
) -> AxumResult<Json<SignerInformationResponse>> { ) -> AxumResult<FormattedResponse<SignerInformationResponse>> {
let signer_state = state.signer_information.as_ref().ok_or_else(|| { let signer_state = state.signer_information.as_ref().ok_or_else(|| {
AxumErrorResponse::internal_msg("this api does not expose zk-nym signing functionalities") AxumErrorResponse::internal_msg("this api does not expose zk-nym signing functionalities")
})?; })?;
Ok(Json(SignerInformationResponse { let output = output.output.unwrap_or_default();
Ok(output.to_response(SignerInformationResponse {
cosmos_address: signer_state.cosmos_address.clone(), cosmos_address: signer_state.cosmos_address.clone(),
identity: signer_state.identity.clone(), identity: signer_state.identity.clone(),
announce_address: signer_state.announce_address.clone(), announce_address: signer_state.announce_address.clone(),
+3 -2
View File
@@ -1,14 +1,15 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net> // Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
use nym_http_api_common::Output;
use nym_mixnet_contract_common::NodeId; use nym_mixnet_contract_common::NodeId;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use utoipa::{IntoParams, ToSchema}; use utoipa::{IntoParams, ToSchema};
#[derive(Serialize, Deserialize, Debug, JsonSchema, ToSchema, IntoParams)] #[derive(Serialize, Deserialize, Debug, ToSchema, IntoParams)]
#[into_params(parameter_in = Query)] #[into_params(parameter_in = Query)]
pub struct PaginationRequest { pub struct PaginationRequest {
pub output: Option<Output>,
pub page: Option<u32>, pub page: Option<u32>,
pub per_page: Option<u32>, pub per_page: Option<u32>,
} }
@@ -36,6 +36,6 @@ features = ["tokio"]
[features] [features]
default = ["query-types"] default = ["query-types"]
query-types = ["nym-http-api-common"] query-types = ["nym-http-api-common", "nym-http-api-common/output"]
openapi = ["utoipa"] openapi = ["utoipa"]
tsify = ["dep:tsify", "wasm-bindgen"] tsify = ["dep:tsify", "wasm-bindgen"]
@@ -102,7 +102,7 @@ impl Monitor {
.with_timeout(self.nym_api_client_timeout) .with_timeout(self.nym_api_client_timeout)
.build::<&str>()?; .build::<&str>()?;
let api_client = NymApiClient { nym_api }; let api_client = NymApiClient::from(nym_api);
let described_nodes = api_client let described_nodes = api_client
.get_all_described_nodes() .get_all_described_nodes()
@@ -187,7 +187,7 @@ impl Monitor {
tracing::info!("🟣 mixnodes_described: {}", mixnodes_described.len()); tracing::info!("🟣 mixnodes_described: {}", mixnodes_described.len());
let mixing_assigned_nodes = api_client let mixing_assigned_nodes = api_client
.nym_api .nym_api
.get_basic_active_mixing_assigned_nodes(false, None, None) .get_basic_active_mixing_assigned_nodes(false, None, None, false)
.await .await
.log_error("get_basic_active_mixing_assigned_nodes")? .log_error("get_basic_active_mixing_assigned_nodes")?
.nodes .nodes
@@ -62,7 +62,7 @@ async fn run(
.with_timeout(nym_api_client_timeout) .with_timeout(nym_api_client_timeout)
.build::<&str>()?; .build::<&str>()?;
let api_client = NymApiClient { nym_api }; let api_client = NymApiClient::from(nym_api);
//SW TBC what nodes exactly need to be scraped, the skimmed node endpoint seems to return more nodes //SW TBC what nodes exactly need to be scraped, the skimmed node endpoint seems to return more nodes
let bonded_nodes = api_client.get_all_bonded_nym_nodes().await?; let bonded_nodes = api_client.get_all_bonded_nym_nodes().await?;
+1 -1
View File
@@ -842,7 +842,7 @@ impl NymNode {
} }
}; };
let client = NymApiClient { nym_api }; let client = NymApiClient::from(nym_api);
// make new request every time in case previous one takes longer and invalidates the signature // make new request every time in case previous one takes longer and invalidates the signature
let request = NodeRefreshBody::new(self.ed25519_identity_keys.private_key()); let request = NodeRefreshBody::new(self.ed25519_identity_keys.private_key());
+1 -1
View File
@@ -177,7 +177,7 @@ impl NetworkRefresher {
let mut this = NetworkRefresher { let mut this = NetworkRefresher {
querier: NodesQuerier { querier: NodesQuerier {
client: NymApiClient { nym_api }, client: NymApiClient::from(nym_api),
nym_api_urls, nym_api_urls,
currently_used_api: 0, currently_used_api: 0,
}, },
+70 -53
View File
@@ -659,7 +659,7 @@ dependencies = [
"rand_core 0.6.4", "rand_core 0.6.4",
"ripemd", "ripemd",
"secp256k1", "secp256k1",
"sha2 0.10.8", "sha2 0.10.9",
"subtle", "subtle",
"zeroize", "zeroize",
] ]
@@ -816,7 +816,7 @@ version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4"
dependencies = [ dependencies = [
"sha2 0.10.8", "sha2 0.10.9",
"tinyvec", "tinyvec",
] ]
@@ -931,7 +931,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02260d489095346e5cafd04dea8e8cb54d1d74fcd759022a9b72986ebe9a1257" checksum = "02260d489095346e5cafd04dea8e8cb54d1d74fcd759022a9b72986ebe9a1257"
dependencies = [ dependencies = [
"serde", "serde",
"toml 0.8.20", "toml 0.8.22",
] ]
[[package]] [[package]]
@@ -1245,7 +1245,7 @@ dependencies = [
"p256", "p256",
"rand_core 0.6.4", "rand_core 0.6.4",
"rayon", "rayon",
"sha2 0.10.8", "sha2 0.10.9",
"thiserror 1.0.69", "thiserror 1.0.69",
] ]
@@ -1303,7 +1303,7 @@ dependencies = [
"schemars", "schemars",
"serde", "serde",
"serde-json-wasm", "serde-json-wasm",
"sha2 0.10.8", "sha2 0.10.9",
"static_assertions", "static_assertions",
"thiserror 1.0.69", "thiserror 1.0.69",
] ]
@@ -1902,7 +1902,7 @@ dependencies = [
"ed25519", "ed25519",
"rand_core 0.6.4", "rand_core 0.6.4",
"serde", "serde",
"sha2 0.10.8", "sha2 0.10.9",
"subtle", "subtle",
"zeroize", "zeroize",
] ]
@@ -1918,7 +1918,7 @@ dependencies = [
"hashbrown 0.14.5", "hashbrown 0.14.5",
"hex", "hex",
"rand_core 0.6.4", "rand_core 0.6.4",
"sha2 0.10.8", "sha2 0.10.9",
"zeroize", "zeroize",
] ]
@@ -1957,7 +1957,7 @@ dependencies = [
"cc", "cc",
"memchr", "memchr",
"rustc_version", "rustc_version",
"toml 0.8.20", "toml 0.8.22",
"vswhom", "vswhom",
"winreg 0.52.0", "winreg 0.52.0",
] ]
@@ -3556,7 +3556,7 @@ dependencies = [
"ecdsa", "ecdsa",
"elliptic-curve", "elliptic-curve",
"once_cell", "once_cell",
"sha2 0.10.8", "sha2 0.10.9",
"signature", "signature",
] ]
@@ -3965,7 +3965,7 @@ dependencies = [
"schemars", "schemars",
"serde", "serde",
"serde_json", "serde_json",
"sha2 0.10.8", "sha2 0.10.9",
"tendermint", "tendermint",
"tendermint-rpc", "tendermint-rpc",
"thiserror 2.0.12", "thiserror 2.0.12",
@@ -3979,7 +3979,6 @@ version = "0.6.0"
dependencies = [ dependencies = [
"const-str", "const-str",
"log", "log",
"pretty_env_logger",
"schemars", "schemars",
"serde", "serde",
"utoipa", "utoipa",
@@ -4015,7 +4014,7 @@ dependencies = [
"nym-pemstore", "nym-pemstore",
"rand 0.8.5", "rand 0.8.5",
"serde", "serde",
"sha2 0.10.8", "sha2 0.10.9",
"subtle", "subtle",
"thiserror 2.0.12", "thiserror 2.0.12",
"zeroize", "zeroize",
@@ -4031,7 +4030,7 @@ dependencies = [
"nym-network-defaults", "nym-network-defaults",
"serde", "serde",
"thiserror 2.0.12", "thiserror 2.0.12",
"toml 0.8.20", "toml 0.8.22",
"url", "url",
] ]
@@ -4129,12 +4128,14 @@ name = "nym-http-api-client"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"bincode",
"bytes", "bytes",
"encoding_rs", "encoding_rs",
"hickory-resolver", "hickory-resolver",
"http 1.3.1", "http 1.3.1",
"mime", "mime",
"nym-bin-common", "nym-bin-common",
"nym-http-api-common",
"once_cell", "once_cell",
"reqwest 0.12.15", "reqwest 0.12.15",
"serde", "serde",
@@ -4145,6 +4146,15 @@ dependencies = [
"wasmtimer", "wasmtimer",
] ]
[[package]]
name = "nym-http-api-common"
version = "0.1.0"
dependencies = [
"bincode",
"serde",
"tracing",
]
[[package]] [[package]]
name = "nym-mixnet-contract-common" name = "nym-mixnet-contract-common"
version = "0.6.0" version = "0.6.0"
@@ -4268,7 +4278,7 @@ dependencies = [
"rs_merkle", "rs_merkle",
"schemars", "schemars",
"serde", "serde",
"sha2 0.10.8", "sha2 0.10.9",
"time", "time",
"utoipa", "utoipa",
] ]
@@ -4293,7 +4303,7 @@ dependencies = [
"schemars", "schemars",
"serde", "serde",
"serde_json", "serde_json",
"sha2 0.10.8", "sha2 0.10.9",
"strum 0.26.3", "strum 0.26.3",
"thiserror 2.0.12", "thiserror 2.0.12",
"ts-rs", "ts-rs",
@@ -4339,7 +4349,7 @@ dependencies = [
"reqwest 0.12.15", "reqwest 0.12.15",
"serde", "serde",
"serde_json", "serde_json",
"sha2 0.10.8", "sha2 0.10.9",
"tendermint-rpc", "tendermint-rpc",
"thiserror 2.0.12", "thiserror 2.0.12",
"time", "time",
@@ -4759,7 +4769,7 @@ dependencies = [
"ecdsa", "ecdsa",
"elliptic-curve", "elliptic-curve",
"primeorder", "primeorder",
"sha2 0.10.8", "sha2 0.10.9",
] ]
[[package]] [[package]]
@@ -4934,7 +4944,7 @@ checksum = "7f9f832470494906d1fca5329f8ab5791cc60beb230c74815dff541cbd2b5ca0"
dependencies = [ dependencies = [
"once_cell", "once_cell",
"pest", "pest",
"sha2 0.10.8", "sha2 0.10.9",
] ]
[[package]] [[package]]
@@ -5258,7 +5268,7 @@ version = "3.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35"
dependencies = [ dependencies = [
"toml_edit 0.22.24", "toml_edit 0.22.26",
] ]
[[package]] [[package]]
@@ -5804,7 +5814,7 @@ version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb09b49230ba22e8c676e7b75dfe2887dea8121f18b530ae0ba519ce442d2b21" checksum = "bb09b49230ba22e8c676e7b75dfe2887dea8121f18b530ae0ba519ce442d2b21"
dependencies = [ dependencies = [
"sha2 0.10.8", "sha2 0.10.9",
] ]
[[package]] [[package]]
@@ -6299,9 +6309,9 @@ dependencies = [
[[package]] [[package]]
name = "sha2" name = "sha2"
version = "0.10.8" version = "0.10.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"cpufeatures", "cpufeatures",
@@ -6662,7 +6672,7 @@ dependencies = [
"cfg-expr", "cfg-expr",
"heck 0.5.0", "heck 0.5.0",
"pkg-config", "pkg-config",
"toml 0.8.20", "toml 0.8.22",
"version-compare", "version-compare",
] ]
@@ -6807,7 +6817,7 @@ dependencies = [
"serde_json", "serde_json",
"tauri-utils", "tauri-utils",
"tauri-winres", "tauri-winres",
"toml 0.8.20", "toml 0.8.22",
"walkdir", "walkdir",
] ]
@@ -6828,7 +6838,7 @@ dependencies = [
"semver", "semver",
"serde", "serde",
"serde_json", "serde_json",
"sha2 0.10.8", "sha2 0.10.9",
"syn 2.0.100", "syn 2.0.100",
"tauri-utils", "tauri-utils",
"thiserror 2.0.12", "thiserror 2.0.12",
@@ -6865,7 +6875,7 @@ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
"tauri-utils", "tauri-utils",
"toml 0.8.20", "toml 0.8.22",
"walkdir", "walkdir",
] ]
@@ -7047,7 +7057,7 @@ dependencies = [
"serde_with", "serde_with",
"swift-rs", "swift-rs",
"thiserror 2.0.12", "thiserror 2.0.12",
"toml 0.8.20", "toml 0.8.22",
"url", "url",
"urlpattern", "urlpattern",
"uuid", "uuid",
@@ -7061,7 +7071,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56eaa45f707bedf34d19312c26d350bc0f3c59a47e58e8adbeecdc850d2c13a0" checksum = "56eaa45f707bedf34d19312c26d350bc0f3c59a47e58e8adbeecdc850d2c13a0"
dependencies = [ dependencies = [
"embed-resource", "embed-resource",
"toml 0.8.20", "toml 0.8.22",
] ]
[[package]] [[package]]
@@ -7079,9 +7089,9 @@ dependencies = [
[[package]] [[package]]
name = "tendermint" name = "tendermint"
version = "0.40.3" version = "0.40.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab2972a56891bc9173eaebdd51290bc848e448aa281881f3a4712bd8fe1899b3" checksum = "fc997743ecfd4864bbca8170d68d9b2bee24653b034210752c2d883ef4b838b1"
dependencies = [ dependencies = [
"bytes", "bytes",
"digest 0.10.7", "digest 0.10.7",
@@ -7098,7 +7108,7 @@ dependencies = [
"serde_bytes", "serde_bytes",
"serde_json", "serde_json",
"serde_repr", "serde_repr",
"sha2 0.10.8", "sha2 0.10.9",
"signature", "signature",
"subtle", "subtle",
"subtle-encoding", "subtle-encoding",
@@ -7109,23 +7119,23 @@ dependencies = [
[[package]] [[package]]
name = "tendermint-config" name = "tendermint-config"
version = "0.40.3" version = "0.40.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "651cb680d41e586bb688cc17ab0b6dfe2e62a959c5742d1351fe0b084cd75d59" checksum = "069d1791f9b02a596abcd26eb72003b2e9906c6169a60fa82ffc080dd3a43fda"
dependencies = [ dependencies = [
"flex-error", "flex-error",
"serde", "serde",
"serde_json", "serde_json",
"tendermint", "tendermint",
"toml 0.8.20", "toml 0.8.22",
"url", "url",
] ]
[[package]] [[package]]
name = "tendermint-proto" name = "tendermint-proto"
version = "0.40.3" version = "0.40.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca44b9eaaa98e8440fbafe5593b8a32a1c395c094ac566b3eb50497f8bd2bee0" checksum = "d2c40e13d39ca19082d8a7ed22de7595979350319833698f8b1080f29620a094"
dependencies = [ dependencies = [
"bytes", "bytes",
"flex-error", "flex-error",
@@ -7138,9 +7148,9 @@ dependencies = [
[[package]] [[package]]
name = "tendermint-rpc" name = "tendermint-rpc"
version = "0.40.3" version = "0.40.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9367678af1a9fc6c064fab8b5d05f03c0fc243fe411581c00c7eed83f8ced380" checksum = "35e0569a4b4cc42ff00df5a665be2858a39ff79df4790b176f1cd0e169bc0fc2"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"bytes", "bytes",
@@ -7387,21 +7397,21 @@ dependencies = [
[[package]] [[package]]
name = "toml" name = "toml"
version = "0.8.20" version = "0.8.22"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148" checksum = "05ae329d1f08c4d17a59bed7ff5b5a769d062e64a62d34a3261b219e62cd5aae"
dependencies = [ dependencies = [
"serde", "serde",
"serde_spanned", "serde_spanned",
"toml_datetime", "toml_datetime",
"toml_edit 0.22.24", "toml_edit 0.22.26",
] ]
[[package]] [[package]]
name = "toml_datetime" name = "toml_datetime"
version = "0.6.8" version = "0.6.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" checksum = "3da5db5a963e24bc68be8b17b6fa82814bb22ee8660f192bb182771d498f09a3"
dependencies = [ dependencies = [
"serde", "serde",
] ]
@@ -7430,17 +7440,24 @@ dependencies = [
[[package]] [[package]]
name = "toml_edit" name = "toml_edit"
version = "0.22.24" version = "0.22.26"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e"
dependencies = [ dependencies = [
"indexmap 2.8.0", "indexmap 2.8.0",
"serde", "serde",
"serde_spanned", "serde_spanned",
"toml_datetime", "toml_datetime",
"winnow 0.7.4", "toml_write",
"winnow 0.7.10",
] ]
[[package]]
name = "toml_write"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfb942dfe1d8e29a7ee7fcbde5bd2b9a25fb89aa70caea2eba3bee836ff41076"
[[package]] [[package]]
name = "tower" name = "tower"
version = "0.5.2" version = "0.5.2"
@@ -8653,9 +8670,9 @@ dependencies = [
[[package]] [[package]]
name = "winnow" name = "winnow"
version = "0.7.4" version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e97b544156e9bebe1a0ffbc03484fc1ffe3100cbce3ffb17eac35f7cdd7ab36" checksum = "c06928c8748d81b05c9be96aad92e1b6ff01833332f281e8cfca3be4b35fc9ec"
dependencies = [ dependencies = [
"memchr", "memchr",
] ]
@@ -8750,7 +8767,7 @@ dependencies = [
"once_cell", "once_cell",
"percent-encoding", "percent-encoding",
"raw-window-handle", "raw-window-handle",
"sha2 0.10.8", "sha2 0.10.9",
"soup3", "soup3",
"tao-macros", "tao-macros",
"thiserror 2.0.12", "thiserror 2.0.12",
@@ -8887,7 +8904,7 @@ dependencies = [
"tracing", "tracing",
"uds_windows", "uds_windows",
"windows-sys 0.59.0", "windows-sys 0.59.0",
"winnow 0.7.4", "winnow 0.7.10",
"xdg-home", "xdg-home",
"zbus_macros", "zbus_macros",
"zbus_names", "zbus_names",
@@ -8917,7 +8934,7 @@ checksum = "7be68e64bf6ce8db94f63e72f0c7eb9a60d733f7e0499e628dfab0f84d6bcb97"
dependencies = [ dependencies = [
"serde", "serde",
"static_assertions", "static_assertions",
"winnow 0.7.4", "winnow 0.7.10",
"zvariant", "zvariant",
] ]
@@ -9048,7 +9065,7 @@ dependencies = [
"enumflags2", "enumflags2",
"serde", "serde",
"static_assertions", "static_assertions",
"winnow 0.7.4", "winnow 0.7.10",
"zvariant_derive", "zvariant_derive",
"zvariant_utils", "zvariant_utils",
] ]
@@ -9077,5 +9094,5 @@ dependencies = [
"serde", "serde",
"static_assertions", "static_assertions",
"syn 2.0.100", "syn 2.0.100",
"winnow 0.7.4", "winnow 0.7.10",
] ]