Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7a9c41ad73 | |||
| ea0d2f8c66 | |||
| e849e608b2 |
Generated
+4
-1
@@ -5786,12 +5786,14 @@ name = "nym-http-api-client"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"bincode",
|
||||
"bytes",
|
||||
"encoding_rs",
|
||||
"hickory-resolver",
|
||||
"http 1.3.1",
|
||||
"mime",
|
||||
"nym-bin-common",
|
||||
"nym-http-api-common",
|
||||
"once_cell",
|
||||
"reqwest 0.12.15",
|
||||
"serde",
|
||||
@@ -5809,14 +5811,15 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"axum 0.7.9",
|
||||
"axum-client-ip",
|
||||
"bincode",
|
||||
"bytes",
|
||||
"colored",
|
||||
"futures",
|
||||
"mime",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_yaml",
|
||||
"subtle 2.6.1",
|
||||
"time",
|
||||
"tower 0.5.2",
|
||||
"tracing",
|
||||
"utoipa",
|
||||
|
||||
@@ -22,7 +22,7 @@ nym-group-contract-common = { path = "../../cosmwasm-smart-contracts/group-contr
|
||||
nym-serde-helpers = { path = "../../serde-helpers", features = ["hex", "base64"] }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
nym-http-api-client = { path = "../../../common/http-api-client" }
|
||||
nym-http-api-client = { path = "../../../common/http-api-client", features = ["bincode"] }
|
||||
thiserror = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
url = { workspace = true, features = ["serde"] }
|
||||
|
||||
@@ -345,25 +345,47 @@ impl<C, S> Client<C, S> {
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct NymApiClient {
|
||||
pub use_bincode: bool,
|
||||
pub nym_api: nym_api::Client,
|
||||
// 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
|
||||
}
|
||||
|
||||
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
|
||||
#[allow(deprecated)]
|
||||
impl NymApiClient {
|
||||
pub fn new(api_url: Url) -> Self {
|
||||
let nym_api = nym_api::Client::new(api_url, None);
|
||||
|
||||
NymApiClient { nym_api }
|
||||
NymApiClient {
|
||||
use_bincode: false,
|
||||
nym_api,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn new_with_timeout(api_url: Url, timeout: std::time::Duration) -> Self {
|
||||
let nym_api = nym_api::Client::new(api_url, Some(timeout));
|
||||
|
||||
NymApiClient { nym_api }
|
||||
NymApiClient {
|
||||
use_bincode: false,
|
||||
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 {
|
||||
@@ -373,7 +395,10 @@ impl NymApiClient {
|
||||
.build::<ValidatorClientError>()
|
||||
.expect("failed to build nym api client");
|
||||
|
||||
NymApiClient { nym_api }
|
||||
NymApiClient {
|
||||
use_bincode: false,
|
||||
nym_api,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn api_url(&self) -> &Url {
|
||||
@@ -410,7 +435,7 @@ impl NymApiClient {
|
||||
loop {
|
||||
let mut res = self
|
||||
.nym_api
|
||||
.get_basic_entry_assigned_nodes(false, Some(page), None)
|
||||
.get_basic_entry_assigned_nodes(false, Some(page), None, self.use_bincode)
|
||||
.await?;
|
||||
|
||||
nodes.append(&mut res.nodes.data);
|
||||
@@ -436,7 +461,7 @@ impl NymApiClient {
|
||||
loop {
|
||||
let mut res = self
|
||||
.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?;
|
||||
|
||||
nodes.append(&mut res.nodes.data);
|
||||
@@ -462,7 +487,7 @@ impl NymApiClient {
|
||||
loop {
|
||||
let mut res = self
|
||||
.nym_api
|
||||
.get_basic_mixing_capable_nodes(false, Some(page), None)
|
||||
.get_basic_mixing_capable_nodes(false, Some(page), None, self.use_bincode)
|
||||
.await?;
|
||||
|
||||
nodes.append(&mut res.nodes.data);
|
||||
@@ -485,7 +510,7 @@ impl NymApiClient {
|
||||
loop {
|
||||
let mut res = self
|
||||
.nym_api
|
||||
.get_basic_nodes(false, Some(page), None)
|
||||
.get_basic_nodes(false, Some(page), None, self.use_bincode)
|
||||
.await?;
|
||||
|
||||
nodes.append(&mut res.nodes.data);
|
||||
|
||||
@@ -318,6 +318,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
no_legacy: bool,
|
||||
page: Option<u32>,
|
||||
per_page: Option<u32>,
|
||||
use_bincode: bool,
|
||||
) -> Result<PaginatedCachedNodesResponse<SkimmedNode>, NymAPIError> {
|
||||
let mut params = Vec::new();
|
||||
|
||||
@@ -333,7 +334,11 @@ pub trait NymApiClientExt: ApiClient {
|
||||
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,
|
||||
"unstable",
|
||||
@@ -355,6 +360,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
no_legacy: bool,
|
||||
page: Option<u32>,
|
||||
per_page: Option<u32>,
|
||||
use_bincode: bool,
|
||||
) -> Result<PaginatedCachedNodesResponse<SkimmedNode>, NymAPIError> {
|
||||
let mut params = Vec::new();
|
||||
|
||||
@@ -370,7 +376,11 @@ pub trait NymApiClientExt: ApiClient {
|
||||
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,
|
||||
"unstable",
|
||||
@@ -392,6 +402,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
no_legacy: bool,
|
||||
page: Option<u32>,
|
||||
per_page: Option<u32>,
|
||||
use_bincode: bool,
|
||||
) -> Result<PaginatedCachedNodesResponse<SkimmedNode>, NymAPIError> {
|
||||
let mut params = Vec::new();
|
||||
|
||||
@@ -407,7 +418,11 @@ pub trait NymApiClientExt: ApiClient {
|
||||
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,
|
||||
"unstable",
|
||||
@@ -427,6 +442,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
no_legacy: bool,
|
||||
page: Option<u32>,
|
||||
per_page: Option<u32>,
|
||||
use_bincode: bool,
|
||||
) -> Result<PaginatedCachedNodesResponse<SkimmedNode>, NymAPIError> {
|
||||
let mut params = Vec::new();
|
||||
|
||||
@@ -442,7 +458,11 @@ pub trait NymApiClientExt: ApiClient {
|
||||
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,
|
||||
"unstable",
|
||||
|
||||
@@ -12,6 +12,7 @@ license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
async-trait = { workspace = true }
|
||||
bincode = { workspace = true, optional = true }
|
||||
reqwest = { workspace = true, features = ["json", "gzip"] }
|
||||
http.workspace = true
|
||||
url = { workspace = true }
|
||||
@@ -26,7 +27,7 @@ bytes = { workspace = true }
|
||||
encoding_rs = { workspace = true }
|
||||
mime = { workspace = true }
|
||||
|
||||
|
||||
nym-http-api-common = { path = "../http-api-common", default-features = false, optional = true }
|
||||
nym-bin-common = { path = "../bin-common" }
|
||||
|
||||
[target."cfg(not(target_arch = \"wasm32\"))".dependencies]
|
||||
@@ -39,3 +40,6 @@ features = ["tokio"]
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { workspace = true, features = ["rt", "macros"] }
|
||||
|
||||
[features]
|
||||
bincode = ["dep:bincode", "nym-http-api-common", "nym-http-api-common/bincode"]
|
||||
|
||||
@@ -144,10 +144,13 @@ use serde::{Deserialize, Serialize};
|
||||
use std::fmt::Display;
|
||||
use std::time::Duration;
|
||||
use thiserror::Error;
|
||||
use tracing::{instrument, warn};
|
||||
use tracing::{debug, instrument, warn};
|
||||
use url::Url;
|
||||
|
||||
use bytes::Bytes;
|
||||
use http::header::CONTENT_TYPE;
|
||||
use http::HeaderMap;
|
||||
use mime::Mime;
|
||||
pub use reqwest::IntoUrl;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
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}")]
|
||||
EndpointFailure { status: StatusCode, error: E },
|
||||
|
||||
#[error("failed to decode response body: {source} from {content}")]
|
||||
ResponseDecodeFailure {
|
||||
source: serde_json::Error,
|
||||
content: String,
|
||||
},
|
||||
#[error("failed to decode response body: {message} from {content}")]
|
||||
ResponseDecodeFailure { message: String, content: String },
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[error("the request has timed out")]
|
||||
@@ -698,11 +698,29 @@ pub trait ApiClient: ApiClientCore {
|
||||
/// defined key-value parameters, e.g. `[("since", "12345")]`. Attempt to parse the response
|
||||
/// into the provided type `T`.
|
||||
#[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>(
|
||||
&self,
|
||||
path: PathSegments<'_>,
|
||||
params: Params<'_, K, V>,
|
||||
) -> 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
|
||||
for<'a> T: Deserialize<'a>,
|
||||
K: AsRef<str> + Sync,
|
||||
@@ -877,14 +895,10 @@ fn sanitize_url<K: AsRef<str>, V: AsRef<str>>(
|
||||
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 mime::Mime;
|
||||
|
||||
let content_type = headers
|
||||
.get(http::header::CONTENT_TYPE)
|
||||
.and_then(|value| value.to_str().ok())
|
||||
.and_then(|value| value.parse::<Mime>().ok());
|
||||
let content_type = try_get_mime_type(headers);
|
||||
|
||||
let encoding_name = content_type
|
||||
.as_ref()
|
||||
@@ -897,7 +911,7 @@ fn decode_as_text(bytes: &bytes::Bytes, headers: HeaderMap) -> String {
|
||||
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)]
|
||||
pub async fn parse_response<T, E>(res: Response, allow_empty: bool) -> Result<T, HttpClientError<E>>
|
||||
where
|
||||
@@ -919,16 +933,7 @@ where
|
||||
// internally reqwest is first retrieving bytes and then performing parsing via serde_json
|
||||
// (and similarly does the same thing for text())
|
||||
let full = res.bytes().await?;
|
||||
match serde_json::from_slice(&full) {
|
||||
Ok(data) => Ok(data),
|
||||
Err(err) => {
|
||||
let content = decode_as_text(&full, headers);
|
||||
Err(HttpClientError::ResponseDecodeFailure {
|
||||
source: err,
|
||||
content,
|
||||
})
|
||||
}
|
||||
}
|
||||
decode_raw_response(&headers, full)
|
||||
} else if res.status() == StatusCode::NOT_FOUND {
|
||||
Err(HttpClientError::NotFound)
|
||||
} else {
|
||||
@@ -947,6 +952,73 @@ 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,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "bincode")]
|
||||
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),
|
||||
#[cfg(feature = "bincode")]
|
||||
(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)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -11,20 +11,45 @@ license.workspace = true
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
axum-client-ip.workspace = true
|
||||
axum.workspace = true
|
||||
bytes = { workspace = true }
|
||||
colored.workspace = true
|
||||
futures = { workspace = true }
|
||||
mime = { workspace = true }
|
||||
axum = { workspace = true, optional = true }
|
||||
axum-client-ip = { workspace = true, optional = true }
|
||||
bincode = { workspace = true, optional = true }
|
||||
bytes = { workspace = true, optional = true }
|
||||
colored = { workspace = true, optional = true }
|
||||
futures = { workspace = true, optional = true }
|
||||
mime = { workspace = true, optional = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json.workspace = true
|
||||
serde_yaml = { workspace = true }
|
||||
subtle.workspace = true
|
||||
tower = { workspace = true }
|
||||
serde_yaml = { workspace = true, optional = true }
|
||||
subtle = { workspace = true, optional = true }
|
||||
time = { workspace = true, optional = true, features = ["macros"] }
|
||||
tower = { workspace = true, optional = true }
|
||||
tracing.workspace = true
|
||||
utoipa = { workspace = true, optional = true }
|
||||
zeroize = { workspace = true }
|
||||
zeroize = { workspace = true, optional = true }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
output = [
|
||||
"axum",
|
||||
"bytes",
|
||||
"mime",
|
||||
"serde_yaml",
|
||||
"time",
|
||||
"time/formatting"
|
||||
]
|
||||
|
||||
middleware = [
|
||||
"axum",
|
||||
"axum-client-ip",
|
||||
"colored",
|
||||
"futures",
|
||||
"subtle",
|
||||
"tower",
|
||||
"zeroize"
|
||||
]
|
||||
|
||||
utoipa = ["dep:utoipa"]
|
||||
bincode = ["dep:bincode"]
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -1,92 +1,21 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2023-2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use axum::http::{header, HeaderValue, StatusCode};
|
||||
use axum::response::{IntoResponse, Response};
|
||||
use axum::Json;
|
||||
use bytes::{BufMut, BytesMut};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[cfg(feature = "middleware")]
|
||||
pub mod middleware;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum FormattedResponse<T> {
|
||||
Json(Json<T>),
|
||||
Yaml(Yaml<T>),
|
||||
}
|
||||
#[cfg(feature = "output")]
|
||||
pub mod response;
|
||||
|
||||
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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
// don't break existing imports
|
||||
#[cfg(feature = "output")]
|
||||
pub use 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,
|
||||
}
|
||||
|
||||
#[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(),
|
||||
}
|
||||
}
|
||||
// be explicit about those values because bincode uses different defaults in different places
|
||||
#[cfg(feature = "bincode")]
|
||||
pub fn make_bincode_serializer() -> impl ::bincode::Options {
|
||||
use ::bincode::Options;
|
||||
::bincode::DefaultOptions::new()
|
||||
.with_little_endian()
|
||||
.with_varint_encoding()
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
// 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;
|
||||
|
||||
#[cfg(feature = "bincode")]
|
||||
pub mod bincode;
|
||||
pub mod json;
|
||||
pub mod yaml;
|
||||
|
||||
pub use json::Json;
|
||||
pub use yaml::Yaml;
|
||||
|
||||
#[cfg(feature = "bincode")]
|
||||
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>),
|
||||
#[cfg(feature = "bincode")]
|
||||
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,
|
||||
#[cfg(feature = "bincode")]
|
||||
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))
|
||||
}
|
||||
#[cfg(feature = "bincode")]
|
||||
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(),
|
||||
#[cfg(feature = "bincode")]
|
||||
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,
|
||||
#[cfg(feature = "bincode")]
|
||||
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)),
|
||||
#[cfg(feature = "bincode")]
|
||||
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),
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -4,7 +4,7 @@
|
||||
[package]
|
||||
name = "nym-api"
|
||||
license = "GPL-3.0"
|
||||
version = "1.1.57"
|
||||
version = "1.1.58"
|
||||
authors.workspace = true
|
||||
edition = "2021"
|
||||
rust-version.workspace = true
|
||||
@@ -117,7 +117,7 @@ nym-bin-common = { path = "../common/bin-common", features = ["output_format", "
|
||||
nym-node-tester-utils = { path = "../common/node-tester-utils" }
|
||||
nym-node-requests = { path = "../nym-node/nym-node-requests" }
|
||||
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", "bincode", "output", "middleware"] }
|
||||
nym-serde-helpers = { path = "../common/serde-helpers", features = ["date"] }
|
||||
nym-ticketbooks-merkle = { path = "../common/ticketbooks-merkle" }
|
||||
nym-statistics-common = { path = "../common/statistics" }
|
||||
|
||||
@@ -3,8 +3,10 @@
|
||||
|
||||
use crate::node_status_api::models::{AxumErrorResponse, AxumResult};
|
||||
use crate::support::http::state::AppState;
|
||||
use axum::extract::Query;
|
||||
use axum::{extract, Router};
|
||||
use nym_api_requests::models::CirculatingSupplyResponse;
|
||||
use nym_http_api_common::{FormattedResponse, OutputParams};
|
||||
use nym_validator_client::nyxd::Coin;
|
||||
|
||||
pub(crate) fn circulating_supply_routes() -> Router<AppState> {
|
||||
@@ -25,18 +27,26 @@ pub(crate) fn circulating_supply_routes() -> Router<AppState> {
|
||||
get,
|
||||
path = "/v1/circulating-supply",
|
||||
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(
|
||||
Query(output): Query<OutputParams>,
|
||||
extract::State(state): extract::State<AppState>,
|
||||
) -> AxumResult<axum::Json<CirculatingSupplyResponse>> {
|
||||
) -> AxumResult<FormattedResponse<CirculatingSupplyResponse>> {
|
||||
let output = output.output.unwrap_or_default();
|
||||
|
||||
match state
|
||||
.circulating_supply_cache()
|
||||
.get_circulating_supply()
|
||||
.await
|
||||
{
|
||||
Some(value) => Ok(value.into()),
|
||||
Some(value) => Ok(output.to_response(value)),
|
||||
None => Err(AxumErrorResponse::internal_msg("unavailable")),
|
||||
}
|
||||
}
|
||||
@@ -46,12 +56,19 @@ async fn get_full_circulating_supply(
|
||||
get,
|
||||
path = "/v1/circulating-supply/total-supply-value",
|
||||
responses(
|
||||
(status = 200, body = [f64])
|
||||
)
|
||||
(status = 200, content(
|
||||
(f64 = "application/json"),
|
||||
(f64 = "application/yaml"),
|
||||
(f64 = "application/bincode")
|
||||
))
|
||||
),
|
||||
params(OutputParams)
|
||||
)]
|
||||
async fn get_total_supply(
|
||||
Query(output): Query<OutputParams>,
|
||||
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
|
||||
.circulating_supply_cache()
|
||||
.get_circulating_supply()
|
||||
@@ -61,7 +78,9 @@ async fn get_total_supply(
|
||||
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(
|
||||
@@ -69,12 +88,20 @@ async fn get_total_supply(
|
||||
get,
|
||||
path = "/v1/circulating-supply/circulating-supply-value",
|
||||
responses(
|
||||
(status = 200, body = [f64])
|
||||
)
|
||||
(status = 200, content(
|
||||
(f64 = "application/json"),
|
||||
(f64 = "application/yaml"),
|
||||
(f64 = "application/bincode")
|
||||
))
|
||||
),
|
||||
params(OutputParams)
|
||||
)]
|
||||
async fn get_circulating_supply(
|
||||
Query(output): Query<OutputParams>,
|
||||
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
|
||||
.circulating_supply_cache()
|
||||
.get_circulating_supply()
|
||||
@@ -84,7 +111,10 @@ async fn get_circulating_supply(
|
||||
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,
|
||||
|
||||
@@ -7,12 +7,13 @@ use crate::ecash::state::EcashState;
|
||||
use crate::node_status_api::models::AxumResult;
|
||||
use crate::support::http::state::AppState;
|
||||
use axum::extract::{Query, State};
|
||||
use axum::{Json, Router};
|
||||
use axum::Router;
|
||||
use nym_api_requests::ecash::models::{
|
||||
AggregatedCoinIndicesSignatureResponse, AggregatedExpirationDateSignatureResponse,
|
||||
};
|
||||
use nym_api_requests::ecash::VerificationKeyResponse;
|
||||
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 serde::Deserialize;
|
||||
use std::sync::Arc;
|
||||
@@ -45,26 +46,32 @@ pub(crate) fn aggregation_routes() -> Router<AppState> {
|
||||
),
|
||||
path = "/v1/ecash/master-verification-key",
|
||||
responses(
|
||||
(status = 200, body = VerificationKeyResponse)
|
||||
)
|
||||
(status = 200, content(
|
||||
(VerificationKeyResponse = "application/json"),
|
||||
(VerificationKeyResponse = "application/yaml"),
|
||||
(VerificationKeyResponse = "application/bincode")
|
||||
))
|
||||
),
|
||||
)]
|
||||
async fn master_verification_key(
|
||||
State(state): State<Arc<EcashState>>,
|
||||
Query(EpochIdParam { epoch_id }): Query<EpochIdParam>,
|
||||
) -> AxumResult<Json<VerificationKeyResponse>> {
|
||||
Query(EpochIdParam { epoch_id, output }): Query<EpochIdParam>,
|
||||
) -> AxumResult<FormattedResponse<VerificationKeyResponse>> {
|
||||
trace!("aggregated_verification_key request");
|
||||
let output = output.unwrap_or_default();
|
||||
|
||||
// see if we're not in the middle of new dkg
|
||||
state.ensure_dkg_not_in_progress().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)]
|
||||
struct ExpirationDateParam {
|
||||
expiration_date: Option<String>,
|
||||
output: Option<Output>,
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
@@ -75,14 +82,22 @@ struct ExpirationDateParam {
|
||||
),
|
||||
path = "/v1/ecash/aggregated-expiration-date-signatures",
|
||||
responses(
|
||||
(status = 200, body = AggregatedExpirationDateSignatureResponse)
|
||||
)
|
||||
(status = 200, content(
|
||||
(AggregatedExpirationDateSignatureResponse = "application/json"),
|
||||
(AggregatedExpirationDateSignatureResponse = "application/yaml"),
|
||||
(AggregatedExpirationDateSignatureResponse = "application/bincode")
|
||||
))
|
||||
),
|
||||
)]
|
||||
async fn expiration_date_signatures(
|
||||
State(state): State<Arc<EcashState>>,
|
||||
Query(ExpirationDateParam { expiration_date }): Query<ExpirationDateParam>,
|
||||
) -> AxumResult<Json<AggregatedExpirationDateSignatureResponse>> {
|
||||
Query(ExpirationDateParam {
|
||||
expiration_date,
|
||||
output,
|
||||
}): Query<ExpirationDateParam>,
|
||||
) -> AxumResult<FormattedResponse<AggregatedExpirationDateSignatureResponse>> {
|
||||
trace!("aggregated_expiration_date_signatures request");
|
||||
let output = output.unwrap_or_default();
|
||||
|
||||
let expiration_date = match expiration_date {
|
||||
None => cred_exp_date().ecash_date(),
|
||||
@@ -97,11 +112,13 @@ async fn expiration_date_signatures(
|
||||
.master_expiration_date_signatures(expiration_date)
|
||||
.await?;
|
||||
|
||||
Ok(Json(AggregatedExpirationDateSignatureResponse {
|
||||
epoch_id: expiration_date_signatures.epoch_id,
|
||||
expiration_date,
|
||||
signatures: expiration_date_signatures.signatures.clone(),
|
||||
}))
|
||||
Ok(
|
||||
output.to_response(AggregatedExpirationDateSignatureResponse {
|
||||
epoch_id: expiration_date_signatures.epoch_id,
|
||||
expiration_date,
|
||||
signatures: expiration_date_signatures.signatures.clone(),
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
@@ -112,21 +129,26 @@ async fn expiration_date_signatures(
|
||||
),
|
||||
path = "/v1/ecash/aggregated-coin-indices-signatures",
|
||||
responses(
|
||||
(status = 200, body = AggregatedCoinIndicesSignatureResponse)
|
||||
)
|
||||
(status = 200, content(
|
||||
(AggregatedCoinIndicesSignatureResponse = "application/json"),
|
||||
(AggregatedCoinIndicesSignatureResponse = "application/yaml"),
|
||||
(AggregatedCoinIndicesSignatureResponse = "application/bincode")
|
||||
))
|
||||
),
|
||||
)]
|
||||
async fn coin_indices_signatures(
|
||||
Query(EpochIdParam { epoch_id }): Query<EpochIdParam>,
|
||||
Query(EpochIdParam { epoch_id, output }): Query<EpochIdParam>,
|
||||
State(state): State<Arc<EcashState>>,
|
||||
) -> AxumResult<Json<AggregatedCoinIndicesSignatureResponse>> {
|
||||
) -> AxumResult<FormattedResponse<AggregatedCoinIndicesSignatureResponse>> {
|
||||
trace!("aggregated_coin_indices_signatures request");
|
||||
|
||||
let output = output.unwrap_or_default();
|
||||
// see if we're not in the middle of new dkg
|
||||
state.ensure_dkg_not_in_progress().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,
|
||||
signatures: coin_indices_signatures.signatures.clone(),
|
||||
}))
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use nym_http_api_common::Output;
|
||||
|
||||
#[derive(serde::Deserialize, utoipa::IntoParams)]
|
||||
pub(super) struct EpochIdParam {
|
||||
pub(super) epoch_id: Option<u64>,
|
||||
pub(super) output: Option<Output>,
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ use nym_api_requests::ecash::models::{
|
||||
IssuedTicketbooksForCountResponse, IssuedTicketbooksForResponse,
|
||||
IssuedTicketbooksOnCountResponse, SignableMessageBody,
|
||||
};
|
||||
use nym_http_api_common::{FormattedResponse, OutputParams};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::cmp::min;
|
||||
@@ -80,15 +81,20 @@ pub(crate) struct IssuanceDatePathParam {
|
||||
path = "/issued-ticketbooks-count",
|
||||
context_path = "/v1/ecash",
|
||||
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"),
|
||||
)
|
||||
)]
|
||||
async fn issued_ticketbooks_count(
|
||||
Query(pagination): Query<PaginationRequest>,
|
||||
State(state): State<Arc<EcashState>>,
|
||||
) -> AxumResult<Json<IssuedTicketbooksCountResponse>> {
|
||||
) -> AxumResult<FormattedResponse<IssuedTicketbooksCountResponse>> {
|
||||
state.ensure_signer().await?;
|
||||
let output = pagination.output.unwrap_or_default();
|
||||
|
||||
let page = pagination.page.unwrap_or_default();
|
||||
let per_page = min(
|
||||
@@ -98,9 +104,7 @@ async fn issued_ticketbooks_count(
|
||||
MAX_ISSUANCE_COUNT_PAGE_SIZE,
|
||||
);
|
||||
|
||||
Ok(Json(
|
||||
state.get_issued_ticketbooks_count(page, per_page).await?,
|
||||
))
|
||||
Ok(output.to_response(state.get_issued_ticketbooks_count(page, per_page).await?))
|
||||
}
|
||||
|
||||
/// Returns number of issued ticketbooks for particular expiration date.
|
||||
@@ -109,22 +113,28 @@ async fn issued_ticketbooks_count(
|
||||
tag = "Ecash",
|
||||
get,
|
||||
params(
|
||||
ExpirationDatePathParam
|
||||
ExpirationDatePathParam, OutputParams
|
||||
),
|
||||
path = "/issued-ticketbooks-for-count/{expiration_date}",
|
||||
context_path = "/v1/ecash",
|
||||
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"),
|
||||
)
|
||||
)]
|
||||
async fn issued_ticketbooks_for_count(
|
||||
Query(output): Query<OutputParams>,
|
||||
Path(ExpirationDatePathParam { expiration_date }): Path<ExpirationDatePathParam>,
|
||||
State(state): State<Arc<EcashState>>,
|
||||
) -> AxumResult<Json<IssuedTicketbooksForCountResponse>> {
|
||||
) -> AxumResult<FormattedResponse<IssuedTicketbooksForCountResponse>> {
|
||||
state.ensure_signer().await?;
|
||||
let output = output.output.unwrap_or_default();
|
||||
|
||||
Ok(Json(
|
||||
Ok(output.to_response(
|
||||
state
|
||||
.get_issued_ticketbooks_for_count(expiration_date)
|
||||
.await?,
|
||||
@@ -137,46 +147,56 @@ async fn issued_ticketbooks_for_count(
|
||||
tag = "Ecash",
|
||||
get,
|
||||
params(
|
||||
IssuanceDatePathParam
|
||||
IssuanceDatePathParam, OutputParams
|
||||
),
|
||||
path = "/issued-ticketbooks-on-count/{issuance_date}",
|
||||
context_path = "/v1/ecash",
|
||||
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"),
|
||||
)
|
||||
)]
|
||||
async fn issued_ticketbooks_on_count(
|
||||
Query(output): Query<OutputParams>,
|
||||
Path(IssuanceDatePathParam { issuance_date }): Path<IssuanceDatePathParam>,
|
||||
State(state): State<Arc<EcashState>>,
|
||||
) -> AxumResult<Json<IssuedTicketbooksOnCountResponse>> {
|
||||
) -> AxumResult<FormattedResponse<IssuedTicketbooksOnCountResponse>> {
|
||||
state.ensure_signer().await?;
|
||||
let output = output.output.unwrap_or_default();
|
||||
|
||||
Ok(Json(
|
||||
state.get_issued_ticketbooks_on_count(issuance_date).await?,
|
||||
))
|
||||
Ok(output.to_response(state.get_issued_ticketbooks_on_count(issuance_date).await?))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
tag = "Ecash",
|
||||
get,
|
||||
params(
|
||||
ExpirationDatePathParam
|
||||
ExpirationDatePathParam, OutputParams
|
||||
),
|
||||
path = "/issued-ticketbooks-for/{expiration_date}",
|
||||
context_path = "/v1/ecash",
|
||||
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"),
|
||||
)
|
||||
)]
|
||||
async fn issued_ticketbooks_for(
|
||||
Query(output): Query<OutputParams>,
|
||||
State(state): State<Arc<EcashState>>,
|
||||
Path(ExpirationDatePathParam { expiration_date }): Path<ExpirationDatePathParam>,
|
||||
) -> AxumResult<Json<IssuedTicketbooksForResponse>> {
|
||||
) -> AxumResult<FormattedResponse<IssuedTicketbooksForResponse>> {
|
||||
state.ensure_signer().await?;
|
||||
let output = output.output.unwrap_or_default();
|
||||
|
||||
Ok(Json(
|
||||
Ok(output.to_response(
|
||||
state
|
||||
.get_issued_ticketbooks_deposits_on(expiration_date)
|
||||
.await?
|
||||
@@ -191,17 +211,24 @@ async fn issued_ticketbooks_for(
|
||||
path = "/issued-ticketbooks-challenge-commitment",
|
||||
context_path = "/v1/ecash",
|
||||
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"),
|
||||
)
|
||||
),
|
||||
params(OutputParams)
|
||||
)]
|
||||
async fn issued_ticketbooks_challenge_commitment(
|
||||
Query(output): Query<OutputParams>,
|
||||
State(state): State<Arc<EcashState>>,
|
||||
Json(request): Json<IssuedTicketbooksChallengeCommitmentRequest>,
|
||||
) -> AxumResult<Json<IssuedTicketbooksChallengeCommitmentResponse>> {
|
||||
) -> AxumResult<FormattedResponse<IssuedTicketbooksChallengeCommitmentResponse>> {
|
||||
state.ensure_signer().await?;
|
||||
let output = output.output.unwrap_or_default();
|
||||
|
||||
Ok(Json(
|
||||
Ok(output.to_response(
|
||||
state
|
||||
.get_issued_ticketbooks_challenge_commitment(request)
|
||||
.await?
|
||||
@@ -216,17 +243,24 @@ async fn issued_ticketbooks_challenge_commitment(
|
||||
path = "/issued-ticketbooks-data",
|
||||
context_path = "/v1/ecash",
|
||||
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"),
|
||||
)
|
||||
),
|
||||
params(OutputParams)
|
||||
)]
|
||||
async fn issued_ticketbooks_data(
|
||||
Query(output): Query<OutputParams>,
|
||||
State(state): State<Arc<EcashState>>,
|
||||
Json(request): Json<IssuedTicketbooksDataRequest>,
|
||||
) -> AxumResult<Json<IssuedTicketbooksDataResponse>> {
|
||||
) -> AxumResult<FormattedResponse<IssuedTicketbooksDataResponse>> {
|
||||
state.ensure_signer().await?;
|
||||
let output = output.output.unwrap_or_default();
|
||||
|
||||
Ok(Json(
|
||||
Ok(output.to_response(
|
||||
state
|
||||
.get_issued_ticketbooks_data(request)
|
||||
.await?
|
||||
|
||||
@@ -14,6 +14,7 @@ use nym_api_requests::ecash::{
|
||||
PartialExpirationDateSignatureResponse,
|
||||
};
|
||||
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 serde::Deserialize;
|
||||
use std::ops::Deref;
|
||||
@@ -41,16 +42,22 @@ pub(crate) fn partial_signing_routes() -> Router<AppState> {
|
||||
request_body = BlindSignRequestBody,
|
||||
path = "/v1/ecash/blind-sign",
|
||||
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"),
|
||||
|
||||
)
|
||||
),
|
||||
params(OutputParams)
|
||||
)]
|
||||
async fn post_blind_sign(
|
||||
Query(output): Query<OutputParams>,
|
||||
State(state): State<Arc<EcashState>>,
|
||||
Json(blind_sign_request_body): Json<BlindSignRequestBody>,
|
||||
) -> AxumResult<Json<BlindedSignatureResponse>> {
|
||||
) -> AxumResult<FormattedResponse<BlindedSignatureResponse>> {
|
||||
state.ensure_signer().await?;
|
||||
let output = output.output.unwrap_or_default();
|
||||
|
||||
debug!("Received blind sign request");
|
||||
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})",
|
||||
);
|
||||
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
|
||||
@@ -101,12 +108,13 @@ async fn post_blind_sign(
|
||||
.await?;
|
||||
|
||||
// finally return the credential to the client
|
||||
Ok(Json(BlindedSignatureResponse { blinded_signature }))
|
||||
Ok(output.to_response(BlindedSignatureResponse { blinded_signature }))
|
||||
}
|
||||
|
||||
#[derive(Deserialize, IntoParams)]
|
||||
struct ExpirationDateParam {
|
||||
expiration_date: Option<String>,
|
||||
output: Option<Output>,
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
@@ -117,15 +125,23 @@ struct ExpirationDateParam {
|
||||
),
|
||||
path = "/v1/ecash/partial-expiration-date-signatures",
|
||||
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"),
|
||||
)
|
||||
)]
|
||||
async fn partial_expiration_date_signatures(
|
||||
State(state): State<Arc<EcashState>>,
|
||||
Query(ExpirationDateParam { expiration_date }): Query<ExpirationDateParam>,
|
||||
) -> AxumResult<Json<PartialExpirationDateSignatureResponse>> {
|
||||
Query(ExpirationDateParam {
|
||||
expiration_date,
|
||||
output,
|
||||
}): Query<ExpirationDateParam>,
|
||||
) -> AxumResult<FormattedResponse<PartialExpirationDateSignatureResponse>> {
|
||||
state.ensure_signer().await?;
|
||||
let output = output.unwrap_or_default();
|
||||
|
||||
let expiration_date = match expiration_date {
|
||||
None => cred_exp_date().ecash_date(),
|
||||
@@ -140,7 +156,7 @@ async fn partial_expiration_date_signatures(
|
||||
.partial_expiration_date_signatures(expiration_date)
|
||||
.await?;
|
||||
|
||||
Ok(Json(PartialExpirationDateSignatureResponse {
|
||||
Ok(output.to_response(PartialExpirationDateSignatureResponse {
|
||||
epoch_id: expiration_date_signatures.epoch_id,
|
||||
expiration_date,
|
||||
signatures: expiration_date_signatures.signatures.clone(),
|
||||
@@ -155,14 +171,18 @@ async fn partial_expiration_date_signatures(
|
||||
),
|
||||
path = "/v1/ecash/partial-coin-indices-signatures",
|
||||
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"),
|
||||
)
|
||||
)]
|
||||
async fn partial_coin_indices_signatures(
|
||||
State(state): State<Arc<EcashState>>,
|
||||
Query(EpochIdParam { epoch_id }): Query<EpochIdParam>,
|
||||
) -> AxumResult<Json<PartialCoinIndicesSignatureResponse>> {
|
||||
Query(EpochIdParam { epoch_id, output }): Query<EpochIdParam>,
|
||||
) -> AxumResult<FormattedResponse<PartialCoinIndicesSignatureResponse>> {
|
||||
state.ensure_signer().await?;
|
||||
|
||||
// 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?;
|
||||
|
||||
Ok(Json(PartialCoinIndicesSignatureResponse {
|
||||
epoch_id: coin_indices_signatures.epoch_id,
|
||||
signatures: coin_indices_signatures.signatures.clone(),
|
||||
}))
|
||||
Ok(output
|
||||
.unwrap_or_default()
|
||||
.to_response(PartialCoinIndicesSignatureResponse {
|
||||
epoch_id: coin_indices_signatures.epoch_id,
|
||||
signatures: coin_indices_signatures.signatures.clone(),
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ use crate::ecash::error::EcashError;
|
||||
use crate::ecash::state::EcashState;
|
||||
use crate::node_status_api::models::{AxumErrorResponse, AxumResult};
|
||||
use crate::support::http::state::AppState;
|
||||
use axum::extract::State;
|
||||
use axum::extract::{Query, State};
|
||||
use axum::http::StatusCode;
|
||||
use axum::{Json, Router};
|
||||
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_ecash_time::EcashTime;
|
||||
use nym_http_api_common::{FormattedResponse, Output, OutputParams};
|
||||
use std::collections::HashSet;
|
||||
use std::ops::Deref;
|
||||
use std::sync::Arc;
|
||||
@@ -39,9 +40,10 @@ pub(crate) fn spending_routes() -> Router<AppState> {
|
||||
const ONE_AM: Time = time!(1:00);
|
||||
|
||||
fn reject_ticket(
|
||||
output: Output,
|
||||
reason: EcashTicketVerificationRejection,
|
||||
) -> AxumResult<Json<EcashTicketVerificationResponse>> {
|
||||
Ok(Json(EcashTicketVerificationResponse::reject(reason)))
|
||||
) -> AxumResult<FormattedResponse<EcashTicketVerificationResponse>> {
|
||||
Ok(output.to_response(EcashTicketVerificationResponse::reject(reason)))
|
||||
}
|
||||
|
||||
// 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,
|
||||
path = "/v1/ecash/verify-ecash-ticket",
|
||||
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"),
|
||||
)
|
||||
)]
|
||||
async fn verify_ticket(
|
||||
Query(output): Query<OutputParams>,
|
||||
State(state): State<Arc<EcashState>>,
|
||||
// TODO in the future: make it send binary data rather than json
|
||||
Json(verify_ticket_body): Json<VerifyEcashTicketBody>,
|
||||
) -> AxumResult<Json<EcashTicketVerificationResponse>> {
|
||||
) -> AxumResult<FormattedResponse<EcashTicketVerificationResponse>> {
|
||||
state.ensure_signer().await?;
|
||||
let output = output.output.unwrap_or_default();
|
||||
|
||||
let credential_data = &verify_ticket_body.credential;
|
||||
let gateway_cosmos_addr = &verify_ticket_body.gateway_cosmos_addr;
|
||||
|
||||
// easy check: is there only a single payment attached?
|
||||
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();
|
||||
@@ -83,11 +91,14 @@ async fn verify_ticket(
|
||||
|
||||
// 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 {
|
||||
return reject_ticket(EcashTicketVerificationRejection::InvalidSpentDate {
|
||||
today: today_ecash,
|
||||
yesterday: yesterday_ecash,
|
||||
received: spend_date,
|
||||
});
|
||||
return reject_ticket(
|
||||
output,
|
||||
EcashTicketVerificationRejection::InvalidSpentDate {
|
||||
today: today_ecash,
|
||||
yesterday: yesterday_ecash,
|
||||
received: spend_date,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// 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::DuplicatePayInfo(_) => {
|
||||
warn!("Identical payInfo");
|
||||
return reject_ticket(EcashTicketVerificationRejection::ReplayedTicket);
|
||||
return reject_ticket(output, EcashTicketVerificationRejection::ReplayedTicket);
|
||||
}
|
||||
IdentifyResult::DoubleSpendingPublicKeys(pub_key) => {
|
||||
//Actual double spending
|
||||
@@ -110,7 +121,7 @@ async fn verify_ticket(
|
||||
pub_key.to_base58_string()
|
||||
);
|
||||
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
|
||||
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)
|
||||
@@ -127,10 +138,10 @@ async fn verify_ticket(
|
||||
.store_verified_ticket(credential_data, gateway_cosmos_addr)
|
||||
.await?;
|
||||
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(
|
||||
@@ -139,16 +150,23 @@ async fn verify_ticket(
|
||||
request_body = BatchRedeemTicketsBody,
|
||||
path = "/v1/ecash/batch-redeem-ecash-tickets",
|
||||
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"),
|
||||
)
|
||||
),
|
||||
params(OutputParams)
|
||||
)]
|
||||
async fn batch_redeem_tickets(
|
||||
Query(output): Query<OutputParams>,
|
||||
State(state): State<Arc<EcashState>>,
|
||||
// TODO in the future: make it send binary data rather than json
|
||||
Json(batch_redeem_credentials_body): Json<BatchRedeemTicketsBody>,
|
||||
) -> AxumResult<Json<EcashBatchTicketRedemptionResponse>> {
|
||||
) -> AxumResult<FormattedResponse<EcashBatchTicketRedemptionResponse>> {
|
||||
state.ensure_signer().await?;
|
||||
let output = output.output.unwrap_or_default();
|
||||
|
||||
// 1. see if that gateway has even submitted any tickets
|
||||
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
|
||||
state.update_last_batch_verification(&provider_info).await?;
|
||||
|
||||
Ok(Json(EcashBatchTicketRedemptionResponse {
|
||||
Ok(output.to_response(EcashBatchTicketRedemptionResponse {
|
||||
proposal_accepted: true,
|
||||
}))
|
||||
}
|
||||
@@ -224,7 +242,7 @@ async fn batch_redeem_tickets(
|
||||
)
|
||||
)]
|
||||
#[deprecated]
|
||||
async fn double_spending_filter_v1() -> AxumResult<Json<SpentCredentialsResponse>> {
|
||||
async fn double_spending_filter_v1() -> AxumResult<FormattedResponse<SpentCredentialsResponse>> {
|
||||
AxumResult::Err(AxumErrorResponse::new(
|
||||
"permanently restricted",
|
||||
StatusCode::GONE,
|
||||
|
||||
@@ -4,10 +4,11 @@
|
||||
use crate::network::models::{ContractInformation, NetworkDetails};
|
||||
use crate::node_status_api::models::AxumResult;
|
||||
use crate::support::http::state::AppState;
|
||||
use axum::extract::State;
|
||||
use axum::{extract, Json, Router};
|
||||
use axum::extract::{Query, State};
|
||||
use axum::{extract, Router};
|
||||
use nym_api_requests::models::ChainStatusResponse;
|
||||
use nym_contracts_common::ContractBuildInformation;
|
||||
use nym_http_api_common::{FormattedResponse, OutputParams};
|
||||
use std::collections::HashMap;
|
||||
use tower_http::compression::CompressionLayer;
|
||||
use utoipa::ToSchema;
|
||||
@@ -29,13 +30,21 @@ pub(crate) fn nym_network_routes() -> Router<AppState> {
|
||||
get,
|
||||
path = "/v1/network/details",
|
||||
responses(
|
||||
(status = 200, body = NetworkDetails)
|
||||
)
|
||||
(status = 200, content(
|
||||
(NetworkDetails = "application/json"),
|
||||
(NetworkDetails = "application/yaml"),
|
||||
(NetworkDetails = "application/bincode")
|
||||
))
|
||||
),
|
||||
params(OutputParams)
|
||||
)]
|
||||
async fn network_details(
|
||||
Query(output): Query<OutputParams>,
|
||||
extract::State(state): extract::State<AppState>,
|
||||
) -> axum::Json<NetworkDetails> {
|
||||
state.network_details().to_owned().into()
|
||||
) -> FormattedResponse<NetworkDetails> {
|
||||
let output = output.output.unwrap_or_default();
|
||||
|
||||
output.to_response(state.network_details().to_owned())
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
@@ -43,10 +52,20 @@ async fn network_details(
|
||||
get,
|
||||
path = "/v1/network/chain-status",
|
||||
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
|
||||
.chain_status_cache
|
||||
.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;
|
||||
|
||||
Ok(Json(ChainStatusResponse {
|
||||
Ok(output.to_response(ChainStatusResponse {
|
||||
connected_nyxd,
|
||||
status: chain_status,
|
||||
}))
|
||||
@@ -87,25 +106,34 @@ pub struct ContractInformationContractVersion {
|
||||
get,
|
||||
path = "/v1/network/nym-contracts",
|
||||
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(
|
||||
Query(output): Query<OutputParams>,
|
||||
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;
|
||||
info.iter()
|
||||
.map(|(contract, info)| {
|
||||
(
|
||||
contract.to_owned(),
|
||||
ContractInformation {
|
||||
address: info.address.as_ref().map(|a| a.to_string()),
|
||||
details: info.base.clone(),
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect::<HashMap<_, _>>()
|
||||
.into()
|
||||
output.to_response(
|
||||
info.iter()
|
||||
.map(|(contract, info)| {
|
||||
(
|
||||
contract.to_owned(),
|
||||
ContractInformation {
|
||||
address: info.address.as_ref().map(|a| a.to_string()),
|
||||
details: info.base.clone(),
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect::<HashMap<_, _>>(),
|
||||
)
|
||||
}
|
||||
|
||||
#[allow(dead_code)] // not dead, used in OpenAPI docs
|
||||
@@ -120,23 +148,32 @@ pub struct ContractInformationBuildInformation {
|
||||
get,
|
||||
path = "/v1/network/nym-contracts-detailed",
|
||||
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(
|
||||
Query(output): Query<OutputParams>,
|
||||
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;
|
||||
info.iter()
|
||||
.map(|(contract, info)| {
|
||||
(
|
||||
contract.to_owned(),
|
||||
ContractInformation {
|
||||
address: info.address.as_ref().map(|a| a.to_string()),
|
||||
details: info.detailed.clone(),
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect::<HashMap<_, _>>()
|
||||
.into()
|
||||
output.to_response(
|
||||
info.iter()
|
||||
.map(|(contract, info)| {
|
||||
(
|
||||
contract.to_owned(),
|
||||
ContractInformation {
|
||||
address: info.address.as_ref().map(|a| a.to_string()),
|
||||
details: info.detailed.clone(),
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect::<HashMap<_, _>>(),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -4,10 +4,11 @@
|
||||
use crate::node_status_api::models::AxumResult;
|
||||
use crate::support::caching::cache::UninitialisedCache;
|
||||
use crate::support::http::state::AppState;
|
||||
use axum::extract::State;
|
||||
use axum::extract::{Query, State};
|
||||
use axum::routing::get;
|
||||
use axum::{Json, Router};
|
||||
use axum::Router;
|
||||
use nym_api_requests::models::ConfigScoreDataResponse;
|
||||
use nym_http_api_common::{FormattedResponse, OutputParams};
|
||||
use nym_mixnet_contract_common::NodeId;
|
||||
use serde::Deserialize;
|
||||
use utoipa::IntoParams;
|
||||
@@ -43,17 +44,25 @@ struct MixIdParam {
|
||||
path = "/config-score-details",
|
||||
context_path = "/v1/status",
|
||||
responses(
|
||||
(status = 200, body = ConfigScoreDataResponse)
|
||||
(status = 200, content(
|
||||
(ConfigScoreDataResponse = "application/json"),
|
||||
(ConfigScoreDataResponse = "application/yaml"),
|
||||
(ConfigScoreDataResponse = "application/bincode")
|
||||
))
|
||||
),
|
||||
params(OutputParams)
|
||||
)]
|
||||
async fn config_score_details(
|
||||
Query(output): Query<OutputParams>,
|
||||
State(state): State<AppState>,
|
||||
) -> AxumResult<Json<ConfigScoreDataResponse>> {
|
||||
) -> AxumResult<FormattedResponse<ConfigScoreDataResponse>> {
|
||||
let output = output.output.unwrap_or_default();
|
||||
|
||||
let data = state
|
||||
.nym_contract_cache()
|
||||
.maybe_config_score_data_owned()
|
||||
.await
|
||||
.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,
|
||||
MixnodeUptimeHistoryResponse, RewardEstimationResponse, UptimeResponse,
|
||||
};
|
||||
use nym_http_api_common::{FormattedResponse, Output, OutputParams};
|
||||
use serde::Deserialize;
|
||||
use utoipa::IntoParams;
|
||||
|
||||
@@ -103,17 +104,23 @@ pub(super) fn network_monitor_routes() -> Router<AppState> {
|
||||
get,
|
||||
path = "/v1/status/gateway/{identity}/report",
|
||||
responses(
|
||||
(status = 200, body = GatewayStatusReportResponse)
|
||||
)
|
||||
(status = 200, content(
|
||||
(GatewayStatusReportResponse = "application/json"),
|
||||
(GatewayStatusReportResponse = "application/yaml"),
|
||||
(GatewayStatusReportResponse = "application/bincode")
|
||||
))
|
||||
),
|
||||
params(OutputParams)
|
||||
)]
|
||||
#[deprecated]
|
||||
async fn gateway_report(
|
||||
Path(identity): Path<String>,
|
||||
Query(output): Query<OutputParams>,
|
||||
State(state): State<AppState>,
|
||||
) -> AxumResult<Json<GatewayStatusReportResponse>> {
|
||||
Ok(Json(
|
||||
_gateway_report(state.node_status_cache(), &identity).await?,
|
||||
))
|
||||
) -> AxumResult<FormattedResponse<GatewayStatusReportResponse>> {
|
||||
let output = output.output.unwrap_or_default();
|
||||
|
||||
Ok(output.to_response(_gateway_report(state.node_status_cache(), &identity).await?))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
@@ -121,15 +128,23 @@ async fn gateway_report(
|
||||
get,
|
||||
path = "/v1/status/gateway/{identity}/history",
|
||||
responses(
|
||||
(status = 200, body = GatewayUptimeHistoryResponse)
|
||||
)
|
||||
(status = 200, content(
|
||||
(GatewayUptimeHistoryResponse = "application/json"),
|
||||
(GatewayUptimeHistoryResponse = "application/yaml"),
|
||||
(GatewayUptimeHistoryResponse = "application/bincode")
|
||||
))
|
||||
),
|
||||
params(OutputParams)
|
||||
)]
|
||||
#[deprecated]
|
||||
async fn gateway_uptime_history(
|
||||
Path(identity): Path<String>,
|
||||
Query(output): Query<OutputParams>,
|
||||
State(state): State<AppState>,
|
||||
) -> AxumResult<Json<GatewayUptimeHistoryResponse>> {
|
||||
Ok(Json(
|
||||
) -> AxumResult<FormattedResponse<GatewayUptimeHistoryResponse>> {
|
||||
let output = output.output.unwrap_or_default();
|
||||
|
||||
Ok(output.to_response(
|
||||
_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)]
|
||||
struct SinceQueryParams {
|
||||
since: Option<i64>,
|
||||
output: Option<Output>,
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
@@ -148,18 +164,22 @@ struct SinceQueryParams {
|
||||
),
|
||||
path = "/v1/status/gateway/{identity}/core-status-count",
|
||||
responses(
|
||||
(status = 200, body = GatewayCoreStatusResponse)
|
||||
)
|
||||
(status = 200, content(
|
||||
(GatewayCoreStatusResponse = "application/json"),
|
||||
(GatewayCoreStatusResponse = "application/yaml"),
|
||||
(GatewayCoreStatusResponse = "application/bincode")
|
||||
))
|
||||
),
|
||||
)]
|
||||
#[deprecated]
|
||||
async fn gateway_core_status_count(
|
||||
Path(identity): Path<String>,
|
||||
Query(SinceQueryParams { since }): Query<SinceQueryParams>,
|
||||
Query(SinceQueryParams { since, output }): Query<SinceQueryParams>,
|
||||
State(state): State<AppState>,
|
||||
) -> AxumResult<Json<GatewayCoreStatusResponse>> {
|
||||
Ok(Json(
|
||||
_gateway_core_status_count(state.storage(), &identity, since).await?,
|
||||
))
|
||||
) -> AxumResult<FormattedResponse<GatewayCoreStatusResponse>> {
|
||||
Ok(output
|
||||
.unwrap_or_default()
|
||||
.to_response(_gateway_core_status_count(state.storage(), &identity, since).await?))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
@@ -167,57 +187,71 @@ async fn gateway_core_status_count(
|
||||
get,
|
||||
path = "/v1/status/gateway/{identity}/avg_uptime",
|
||||
responses(
|
||||
(status = 200, body = GatewayUptimeResponse)
|
||||
)
|
||||
(status = 200, content(
|
||||
(GatewayUptimeResponse = "application/json"),
|
||||
(GatewayUptimeResponse = "application/yaml"),
|
||||
(GatewayUptimeResponse = "application/bincode")
|
||||
))
|
||||
),
|
||||
params(OutputParams)
|
||||
)]
|
||||
#[deprecated]
|
||||
async fn get_gateway_avg_uptime(
|
||||
Path(identity): Path<String>,
|
||||
Query(output): Query<OutputParams>,
|
||||
State(state): State<AppState>,
|
||||
) -> AxumResult<Json<GatewayUptimeResponse>> {
|
||||
Ok(Json(
|
||||
_get_gateway_avg_uptime(state.node_status_cache(), &identity).await?,
|
||||
))
|
||||
) -> AxumResult<FormattedResponse<GatewayUptimeResponse>> {
|
||||
let output = output.output.unwrap_or_default();
|
||||
|
||||
Ok(output.to_response(_get_gateway_avg_uptime(state.node_status_cache(), &identity).await?))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
tag = "network-monitor-status",
|
||||
get,
|
||||
params(
|
||||
MixIdParam
|
||||
),
|
||||
path = "/v1/status/mixnode/{mix_id}/report",
|
||||
responses(
|
||||
(status = 200, body = MixnodeStatusReportResponse)
|
||||
)
|
||||
(status = 200, content(
|
||||
(MixnodeStatusReportResponse = "application/json"),
|
||||
(MixnodeStatusReportResponse = "application/yaml"),
|
||||
(MixnodeStatusReportResponse = "application/bincode")
|
||||
))
|
||||
),
|
||||
params(OutputParams, MixIdParam)
|
||||
)]
|
||||
#[deprecated]
|
||||
async fn mixnode_report(
|
||||
Path(MixIdParam { mix_id }): Path<MixIdParam>,
|
||||
Query(output): Query<OutputParams>,
|
||||
State(state): State<AppState>,
|
||||
) -> AxumResult<Json<MixnodeStatusReportResponse>> {
|
||||
Ok(Json(
|
||||
_mixnode_report(state.node_status_cache(), mix_id).await?,
|
||||
))
|
||||
) -> AxumResult<FormattedResponse<MixnodeStatusReportResponse>> {
|
||||
let output = output.output.unwrap_or_default();
|
||||
|
||||
Ok(output.to_response(_mixnode_report(state.node_status_cache(), mix_id).await?))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
tag = "network-monitor-status",
|
||||
get,
|
||||
params(
|
||||
MixIdParam
|
||||
),
|
||||
path = "/v1/status/mixnode/{mix_id}/history",
|
||||
responses(
|
||||
(status = 200, body = MixnodeUptimeHistoryResponse)
|
||||
)
|
||||
(status = 200, content(
|
||||
(MixnodeUptimeHistoryResponse = "application/json"),
|
||||
(MixnodeUptimeHistoryResponse = "application/yaml"),
|
||||
(MixnodeUptimeHistoryResponse = "application/bincode")
|
||||
))
|
||||
),
|
||||
params(MixIdParam, OutputParams)
|
||||
)]
|
||||
#[deprecated]
|
||||
async fn mixnode_uptime_history(
|
||||
Path(MixIdParam { mix_id }): Path<MixIdParam>,
|
||||
Query(output): Query<OutputParams>,
|
||||
State(state): State<AppState>,
|
||||
) -> AxumResult<Json<MixnodeUptimeHistoryResponse>> {
|
||||
Ok(Json(
|
||||
) -> AxumResult<FormattedResponse<MixnodeUptimeHistoryResponse>> {
|
||||
let output = output.output.unwrap_or_default();
|
||||
|
||||
Ok(output.to_response(
|
||||
_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",
|
||||
responses(
|
||||
(status = 200, body = MixnodeCoreStatusResponse)
|
||||
)
|
||||
(status = 200, content(
|
||||
(MixnodeCoreStatusResponse = "application/json"),
|
||||
(MixnodeCoreStatusResponse = "application/yaml"),
|
||||
(MixnodeCoreStatusResponse = "application/bincode")
|
||||
))
|
||||
),
|
||||
)]
|
||||
#[deprecated]
|
||||
async fn mixnode_core_status_count(
|
||||
Path(MixIdParam { mix_id }): Path<MixIdParam>,
|
||||
Query(SinceQueryParams { since }): Query<SinceQueryParams>,
|
||||
Query(SinceQueryParams { since, output }): Query<SinceQueryParams>,
|
||||
State(state): State<AppState>,
|
||||
) -> AxumResult<Json<MixnodeCoreStatusResponse>> {
|
||||
Ok(Json(
|
||||
_mixnode_core_status_count(state.storage(), mix_id, since).await?,
|
||||
))
|
||||
) -> AxumResult<FormattedResponse<MixnodeCoreStatusResponse>> {
|
||||
let output = output.unwrap_or_default();
|
||||
|
||||
Ok(output.to_response(_mixnode_core_status_count(state.storage(), mix_id, since).await?))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
tag = "network-monitor-status",
|
||||
get,
|
||||
params(
|
||||
MixIdParam
|
||||
MixIdParam, OutputParams
|
||||
),
|
||||
path = "/v1/status/mixnode/{mix_id}/reward-estimation",
|
||||
responses(
|
||||
(status = 200, body = RewardEstimationResponse)
|
||||
)
|
||||
(status = 200, content(
|
||||
(RewardEstimationResponse = "application/json"),
|
||||
(RewardEstimationResponse = "application/yaml"),
|
||||
(RewardEstimationResponse = "application/bincode")
|
||||
))
|
||||
),
|
||||
)]
|
||||
#[deprecated]
|
||||
async fn get_mixnode_reward_estimation(
|
||||
Path(MixIdParam { mix_id }): Path<MixIdParam>,
|
||||
Query(output): Query<OutputParams>,
|
||||
State(state): State<AppState>,
|
||||
) -> AxumResult<Json<RewardEstimationResponse>> {
|
||||
Ok(Json(
|
||||
) -> AxumResult<FormattedResponse<RewardEstimationResponse>> {
|
||||
let output = output.output.unwrap_or_default();
|
||||
|
||||
Ok(output.to_response(
|
||||
_get_mixnode_reward_estimation(
|
||||
state.node_status_cache(),
|
||||
state.nym_contract_cache(),
|
||||
@@ -274,21 +319,28 @@ async fn get_mixnode_reward_estimation(
|
||||
tag = "network-monitor-status",
|
||||
post,
|
||||
params(
|
||||
ComputeRewardEstParam, MixIdParam
|
||||
OutputParams, MixIdParam
|
||||
),
|
||||
path = "/v1/status/mixnode/{mix_id}/compute-reward-estimation",
|
||||
request_body = ComputeRewardEstParam,
|
||||
responses(
|
||||
(status = 200, body = RewardEstimationResponse)
|
||||
)
|
||||
(status = 200, content(
|
||||
(RewardEstimationResponse = "application/json"),
|
||||
(RewardEstimationResponse = "application/yaml"),
|
||||
(RewardEstimationResponse = "application/bincode")
|
||||
))
|
||||
),
|
||||
)]
|
||||
#[deprecated]
|
||||
async fn compute_mixnode_reward_estimation(
|
||||
Path(MixIdParam { mix_id }): Path<MixIdParam>,
|
||||
Query(output): Query<OutputParams>,
|
||||
State(state): State<AppState>,
|
||||
Json(user_reward_param): Json<ComputeRewardEstParam>,
|
||||
) -> AxumResult<Json<RewardEstimationResponse>> {
|
||||
Ok(Json(
|
||||
) -> AxumResult<FormattedResponse<RewardEstimationResponse>> {
|
||||
let output = output.output.unwrap_or_default();
|
||||
|
||||
Ok(output.to_response(
|
||||
_compute_mixnode_reward_estimation(
|
||||
&user_reward_param,
|
||||
state.node_status_cache(),
|
||||
@@ -303,21 +355,26 @@ async fn compute_mixnode_reward_estimation(
|
||||
tag = "network-monitor-status",
|
||||
get,
|
||||
params(
|
||||
MixIdParam
|
||||
MixIdParam, OutputParams
|
||||
),
|
||||
path = "/v1/status/mixnode/{mix_id}/avg_uptime",
|
||||
responses(
|
||||
(status = 200, body = UptimeResponse)
|
||||
)
|
||||
(status = 200, content(
|
||||
(UptimeResponse = "application/json"),
|
||||
(UptimeResponse = "application/yaml"),
|
||||
(UptimeResponse = "application/bincode")
|
||||
))
|
||||
),
|
||||
)]
|
||||
#[deprecated]
|
||||
async fn get_mixnode_avg_uptime(
|
||||
Path(MixIdParam { mix_id }): Path<MixIdParam>,
|
||||
Query(output): Query<OutputParams>,
|
||||
State(state): State<AppState>,
|
||||
) -> AxumResult<Json<UptimeResponse>> {
|
||||
Ok(Json(
|
||||
_get_mixnode_avg_uptime(state.node_status_cache(), mix_id).await?,
|
||||
))
|
||||
) -> AxumResult<FormattedResponse<UptimeResponse>> {
|
||||
let output = output.output.unwrap_or_default();
|
||||
|
||||
Ok(output.to_response(_get_mixnode_avg_uptime(state.node_status_cache(), mix_id).await?))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
@@ -325,14 +382,22 @@ async fn get_mixnode_avg_uptime(
|
||||
get,
|
||||
path = "/v1/status/mixnodes/detailed-unfiltered",
|
||||
responses(
|
||||
(status = 200, body = MixNodeBondAnnotated)
|
||||
)
|
||||
(status = 200, content(
|
||||
(MixNodeBondAnnotated = "application/json"),
|
||||
(MixNodeBondAnnotated = "application/yaml"),
|
||||
(MixNodeBondAnnotated = "application/bincode")
|
||||
))
|
||||
),
|
||||
params(OutputParams)
|
||||
)]
|
||||
#[deprecated]
|
||||
pub async fn get_mixnodes_detailed_unfiltered(
|
||||
Query(output): Query<OutputParams>,
|
||||
State(state): State<AppState>,
|
||||
) -> Json<Vec<MixNodeBondAnnotated>> {
|
||||
Json(_get_mixnodes_detailed_unfiltered(state.node_status_cache()).await)
|
||||
) -> FormattedResponse<Vec<MixNodeBondAnnotated>> {
|
||||
let output = output.output.unwrap_or_default();
|
||||
|
||||
output.to_response(_get_mixnodes_detailed_unfiltered(state.node_status_cache()).await)
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
@@ -340,14 +405,22 @@ pub async fn get_mixnodes_detailed_unfiltered(
|
||||
get,
|
||||
path = "/v1/status/gateways/detailed",
|
||||
responses(
|
||||
(status = 200, body = GatewayBondAnnotated)
|
||||
)
|
||||
(status = 200, content(
|
||||
(GatewayBondAnnotated = "application/json"),
|
||||
(GatewayBondAnnotated = "application/yaml"),
|
||||
(GatewayBondAnnotated = "application/bincode")
|
||||
))
|
||||
),
|
||||
params(OutputParams)
|
||||
)]
|
||||
#[deprecated]
|
||||
pub async fn get_gateways_detailed(
|
||||
Query(output): Query<OutputParams>,
|
||||
State(state): State<AppState>,
|
||||
) -> Json<Vec<GatewayBondAnnotated>> {
|
||||
Json(_get_legacy_gateways_detailed(state.node_status_cache()).await)
|
||||
) -> FormattedResponse<Vec<GatewayBondAnnotated>> {
|
||||
let output = output.output.unwrap_or_default();
|
||||
|
||||
output.to_response(_get_legacy_gateways_detailed(state.node_status_cache()).await)
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
@@ -355,12 +428,20 @@ pub async fn get_gateways_detailed(
|
||||
get,
|
||||
path = "/v1/status/gateways/detailed-unfiltered",
|
||||
responses(
|
||||
(status = 200, body = GatewayBondAnnotated)
|
||||
)
|
||||
(status = 200, content(
|
||||
(GatewayBondAnnotated = "application/json"),
|
||||
(GatewayBondAnnotated = "application/yaml"),
|
||||
(GatewayBondAnnotated = "application/bincode")
|
||||
))
|
||||
),
|
||||
params(OutputParams)
|
||||
)]
|
||||
#[deprecated]
|
||||
pub async fn get_gateways_detailed_unfiltered(
|
||||
Query(output): Query<OutputParams>,
|
||||
State(state): State<AppState>,
|
||||
) -> Json<Vec<GatewayBondAnnotated>> {
|
||||
Json(_get_legacy_gateways_detailed_unfiltered(state.node_status_cache()).await)
|
||||
) -> FormattedResponse<Vec<GatewayBondAnnotated>> {
|
||||
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 anyhow::bail;
|
||||
use axum::extract::{Path, Query, State};
|
||||
use axum::Json;
|
||||
use nym_api_requests::models::{
|
||||
GatewayTestResultResponse, MixnodeTestResultResponse, NetworkMonitorRunDetailsResponse,
|
||||
PartialTestResult, TestNode, TestRoute,
|
||||
};
|
||||
use nym_api_requests::pagination::Pagination;
|
||||
use nym_http_api_common::{FormattedResponse, OutputParams};
|
||||
use nym_mixnet_contract_common::NodeId;
|
||||
use std::cmp::min;
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
@@ -173,14 +173,18 @@ async fn _mixnode_test_results(
|
||||
),
|
||||
path = "/v1/status/mixnodes/unstable/{mix_id}/test-results",
|
||||
responses(
|
||||
(status = 200, body = MixnodeTestResultResponse)
|
||||
)
|
||||
(status = 200, content(
|
||||
(MixnodeTestResultResponse = "application/json"),
|
||||
(MixnodeTestResultResponse = "application/yaml"),
|
||||
(MixnodeTestResultResponse = "application/bincode")
|
||||
))
|
||||
),
|
||||
)]
|
||||
pub async fn mixnode_test_results(
|
||||
Path(mix_id): Path<NodeId>,
|
||||
Query(pagination): Query<PaginationRequest>,
|
||||
State(state): State<AppState>,
|
||||
) -> AxumResult<Json<MixnodeTestResultResponse>> {
|
||||
) -> AxumResult<FormattedResponse<MixnodeTestResultResponse>> {
|
||||
let page = pagination.page.unwrap_or_default();
|
||||
let per_page = min(
|
||||
pagination
|
||||
@@ -188,6 +192,7 @@ pub async fn mixnode_test_results(
|
||||
.unwrap_or(DEFAULT_TEST_RESULTS_PAGE_SIZE),
|
||||
MAX_TEST_RESULTS_PAGE_SIZE,
|
||||
);
|
||||
let output = pagination.output.unwrap_or_default();
|
||||
|
||||
match _mixnode_test_results(
|
||||
mix_id,
|
||||
@@ -198,7 +203,7 @@ pub async fn mixnode_test_results(
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(res) => Ok(Json(res)),
|
||||
Ok(res) => Ok(output.to_response(res)),
|
||||
Err(err) => Err(AxumErrorResponse::internal_msg(format!(
|
||||
"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",
|
||||
responses(
|
||||
(status = 200, body = GatewayTestResultResponse)
|
||||
)
|
||||
(status = 200, content(
|
||||
(GatewayTestResultResponse = "application/json"),
|
||||
(GatewayTestResultResponse = "application/yaml"),
|
||||
(GatewayTestResultResponse = "application/bincode")
|
||||
))
|
||||
),
|
||||
)]
|
||||
pub async fn gateway_test_results(
|
||||
Path(gateway_identity): Path<String>,
|
||||
Query(pagination): Query<PaginationRequest>,
|
||||
State(state): State<AppState>,
|
||||
) -> AxumResult<Json<GatewayTestResultResponse>> {
|
||||
) -> AxumResult<FormattedResponse<GatewayTestResultResponse>> {
|
||||
let page = pagination.page.unwrap_or_default();
|
||||
let per_page = min(
|
||||
pagination
|
||||
@@ -287,6 +296,7 @@ pub async fn gateway_test_results(
|
||||
.unwrap_or(DEFAULT_TEST_RESULTS_PAGE_SIZE),
|
||||
MAX_TEST_RESULTS_PAGE_SIZE,
|
||||
);
|
||||
let output = pagination.output.unwrap_or_default();
|
||||
|
||||
match _gateway_test_results(
|
||||
&gateway_identity,
|
||||
@@ -297,7 +307,7 @@ pub async fn gateway_test_results(
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(res) => Ok(Json(res)),
|
||||
Ok(res) => Ok(output.to_response(res)),
|
||||
Err(err) => Err(AxumErrorResponse::internal_msg(format!(
|
||||
"failed to retrieve mixnode test results for gateway {gateway_identity}: {err}"
|
||||
))),
|
||||
@@ -349,15 +359,23 @@ async fn _latest_monitor_run_report(
|
||||
get,
|
||||
path = "/v1/status/network-monitor/unstable/run/{monitor_run_id}/details",
|
||||
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(
|
||||
Path(monitor_run_id): Path<i64>,
|
||||
Query(output): Query<OutputParams>,
|
||||
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 {
|
||||
Ok(res) => Ok(Json(res)),
|
||||
Ok(res) => Ok(output.to_response(res)),
|
||||
Err(err) => Err(AxumErrorResponse::internal_msg(format!(
|
||||
"failed to retrieve monitor run report for run {monitor_run_id}: {err}"
|
||||
))),
|
||||
@@ -369,14 +387,22 @@ pub async fn monitor_run_report(
|
||||
get,
|
||||
path = "/v1/status/network-monitor/unstable/run/latest/details",
|
||||
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(
|
||||
Query(output): Query<OutputParams>,
|
||||
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 {
|
||||
Ok(res) => Ok(Json(res)),
|
||||
Ok(res) => Ok(output.to_response(res)),
|
||||
Err(err) => Err(AxumErrorResponse::internal_msg(format!(
|
||||
"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::support::http::state::AppState;
|
||||
use axum::extract::{Path, State};
|
||||
use axum::extract::{Path, Query, State};
|
||||
use axum::routing::{get, post};
|
||||
use axum::Json;
|
||||
use axum::Router;
|
||||
use nym_api_requests::models::{
|
||||
MixNodeBondAnnotated, MixnodeStatusResponse, StakeSaturationResponse,
|
||||
};
|
||||
use nym_http_api_common::{FormattedResponse, OutputParams};
|
||||
use nym_mixnet_contract_common::NodeId;
|
||||
use nym_types::monitoring::MonitorMessage;
|
||||
use tracing::error;
|
||||
@@ -153,15 +154,22 @@ pub(crate) async fn submit_node_monitoring_results(
|
||||
),
|
||||
path = "/v1/status/mixnode/{mix_id}/status",
|
||||
responses(
|
||||
(status = 200, body = MixnodeStatusResponse)
|
||||
)
|
||||
(status = 200, content(
|
||||
(MixnodeStatusResponse = "application/json"),
|
||||
(MixnodeStatusResponse = "application/yaml"),
|
||||
(MixnodeStatusResponse = "application/bincode")
|
||||
))
|
||||
),
|
||||
params(OutputParams)
|
||||
)]
|
||||
#[deprecated]
|
||||
async fn get_mixnode_status(
|
||||
Path(MixIdParam { mix_id }): Path<MixIdParam>,
|
||||
Query(output): Query<OutputParams>,
|
||||
State(state): State<AppState>,
|
||||
) -> Json<MixnodeStatusResponse> {
|
||||
Json(_get_mixnode_status(state.nym_contract_cache(), mix_id).await)
|
||||
) -> FormattedResponse<MixnodeStatusResponse> {
|
||||
let output = output.output.unwrap_or_default();
|
||||
output.to_response(_get_mixnode_status(state.nym_contract_cache(), mix_id).await)
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
@@ -172,15 +180,23 @@ async fn get_mixnode_status(
|
||||
),
|
||||
path = "/v1/status/mixnode/{mix_id}/stake-saturation",
|
||||
responses(
|
||||
(status = 200, body = StakeSaturationResponse)
|
||||
)
|
||||
(status = 200, content(
|
||||
(StakeSaturationResponse = "application/json"),
|
||||
(StakeSaturationResponse = "application/yaml"),
|
||||
(StakeSaturationResponse = "application/bincode")
|
||||
))
|
||||
),
|
||||
params(OutputParams)
|
||||
)]
|
||||
#[deprecated]
|
||||
async fn get_mixnode_stake_saturation(
|
||||
Path(mix_id): Path<NodeId>,
|
||||
Query(output): Query<OutputParams>,
|
||||
State(state): State<AppState>,
|
||||
) -> AxumResult<Json<StakeSaturationResponse>> {
|
||||
Ok(Json(
|
||||
) -> AxumResult<FormattedResponse<StakeSaturationResponse>> {
|
||||
let output = output.output.unwrap_or_default();
|
||||
|
||||
Ok(output.to_response(
|
||||
_get_mixnode_stake_saturation(
|
||||
state.node_status_cache(),
|
||||
state.nym_contract_cache(),
|
||||
@@ -194,22 +210,28 @@ async fn get_mixnode_stake_saturation(
|
||||
tag = "status",
|
||||
get,
|
||||
params(
|
||||
MixIdParam
|
||||
MixIdParam, OutputParams
|
||||
),
|
||||
path = "/v1/status/mixnode/{mix_id}/inclusion-probability",
|
||||
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]
|
||||
#[allow(deprecated)]
|
||||
async fn get_mixnode_inclusion_probability(
|
||||
Path(mix_id): Path<NodeId>,
|
||||
Query(output): Query<OutputParams>,
|
||||
State(state): State<AppState>,
|
||||
) -> AxumResult<Json<nym_api_requests::models::InclusionProbabilityResponse>> {
|
||||
Ok(Json(
|
||||
_get_mixnode_inclusion_probability(state.node_status_cache(), mix_id).await?,
|
||||
))
|
||||
) -> AxumResult<FormattedResponse<nym_api_requests::models::InclusionProbabilityResponse>> {
|
||||
let output = output.output.unwrap_or_default();
|
||||
|
||||
Ok(output
|
||||
.to_response(_get_mixnode_inclusion_probability(state.node_status_cache(), mix_id).await?))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
@@ -217,17 +239,23 @@ async fn get_mixnode_inclusion_probability(
|
||||
get,
|
||||
path = "/v1/status/mixnodes/inclusion-probability",
|
||||
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]
|
||||
#[allow(deprecated)]
|
||||
async fn get_mixnode_inclusion_probabilities(
|
||||
Query(output): Query<OutputParams>,
|
||||
State(state): State<AppState>,
|
||||
) -> AxumResult<Json<nym_api_requests::models::AllInclusionProbabilitiesResponse>> {
|
||||
Ok(Json(
|
||||
_get_mixnode_inclusion_probabilities(state.node_status_cache()).await?,
|
||||
))
|
||||
) -> AxumResult<FormattedResponse<nym_api_requests::models::AllInclusionProbabilitiesResponse>> {
|
||||
let output = output.output.unwrap_or_default();
|
||||
|
||||
Ok(output.to_response(_get_mixnode_inclusion_probabilities(state.node_status_cache()).await?))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
@@ -235,14 +263,22 @@ async fn get_mixnode_inclusion_probabilities(
|
||||
get,
|
||||
path = "/v1/status/mixnodes/detailed",
|
||||
responses(
|
||||
(status = 200, body = MixNodeBondAnnotated)
|
||||
)
|
||||
(status = 200, content(
|
||||
(MixNodeBondAnnotated = "application/json"),
|
||||
(MixNodeBondAnnotated = "application/yaml"),
|
||||
(MixNodeBondAnnotated = "application/bincode")
|
||||
))
|
||||
),
|
||||
params(OutputParams)
|
||||
)]
|
||||
#[deprecated]
|
||||
pub async fn get_mixnodes_detailed(
|
||||
Query(output): Query<OutputParams>,
|
||||
State(state): State<AppState>,
|
||||
) -> Json<Vec<MixNodeBondAnnotated>> {
|
||||
Json(_get_legacy_mixnodes_detailed(state.node_status_cache()).await)
|
||||
) -> FormattedResponse<Vec<MixNodeBondAnnotated>> {
|
||||
let output = output.output.unwrap_or_default();
|
||||
|
||||
output.to_response(_get_legacy_mixnodes_detailed(state.node_status_cache()).await)
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
@@ -250,14 +286,22 @@ pub async fn get_mixnodes_detailed(
|
||||
get,
|
||||
path = "/v1/status/mixnodes/rewarded/detailed",
|
||||
responses(
|
||||
(status = 200, body = MixNodeBondAnnotated)
|
||||
)
|
||||
(status = 200, content(
|
||||
(MixNodeBondAnnotated = "application/json"),
|
||||
(MixNodeBondAnnotated = "application/yaml"),
|
||||
(MixNodeBondAnnotated = "application/bincode")
|
||||
))
|
||||
),
|
||||
params(OutputParams)
|
||||
)]
|
||||
#[deprecated]
|
||||
pub async fn get_rewarded_set_detailed(
|
||||
Query(output): Query<OutputParams>,
|
||||
State(state): State<AppState>,
|
||||
) -> Json<Vec<MixNodeBondAnnotated>> {
|
||||
Json(
|
||||
) -> FormattedResponse<Vec<MixNodeBondAnnotated>> {
|
||||
let output = output.output.unwrap_or_default();
|
||||
|
||||
output.to_response(
|
||||
_get_rewarded_set_legacy_mixnodes_detailed(
|
||||
state.node_status_cache(),
|
||||
state.nym_contract_cache(),
|
||||
@@ -271,14 +315,22 @@ pub async fn get_rewarded_set_detailed(
|
||||
get,
|
||||
path = "/v1/status/mixnodes/active/detailed",
|
||||
responses(
|
||||
(status = 200, body = MixNodeBondAnnotated)
|
||||
)
|
||||
(status = 200, content(
|
||||
(MixNodeBondAnnotated = "application/json"),
|
||||
(MixNodeBondAnnotated = "application/yaml"),
|
||||
(MixNodeBondAnnotated = "application/bincode")
|
||||
))
|
||||
),
|
||||
params(OutputParams)
|
||||
)]
|
||||
#[deprecated]
|
||||
pub async fn get_active_set_detailed(
|
||||
Query(output): Query<OutputParams>,
|
||||
State(state): State<AppState>,
|
||||
) -> Json<Vec<MixNodeBondAnnotated>> {
|
||||
Json(
|
||||
) -> 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(),
|
||||
|
||||
@@ -7,10 +7,11 @@ use crate::node_status_api::helpers::{
|
||||
};
|
||||
use crate::support::http::state::AppState;
|
||||
use crate::support::legacy_helpers::{to_legacy_gateway, to_legacy_mixnode};
|
||||
use axum::extract::State;
|
||||
use axum::{Json, Router};
|
||||
use axum::extract::{Query, State};
|
||||
use axum::Router;
|
||||
use nym_api_requests::legacy::LegacyMixNodeDetailsWithLayer;
|
||||
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::RewardingParams, GatewayBond, Interval, NodeId};
|
||||
use std::collections::HashSet;
|
||||
@@ -55,23 +56,32 @@ pub(crate) fn nym_contract_cache_routes() -> Router<AppState> {
|
||||
get,
|
||||
path = "/v1/mixnodes",
|
||||
responses(
|
||||
(status = 200, body = Vec<LegacyMixNodeDetailsWithLayer>)
|
||||
)
|
||||
(status = 200, content(
|
||||
(Vec<LegacyMixNodeDetailsWithLayer> = "application/json"),
|
||||
(Vec<LegacyMixNodeDetailsWithLayer> = "application/yaml"),
|
||||
(Vec<LegacyMixNodeDetailsWithLayer> = "application/bincode")
|
||||
))
|
||||
),
|
||||
params(OutputParams)
|
||||
)]
|
||||
#[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 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 {
|
||||
return Json(out);
|
||||
return output.to_response(out);
|
||||
};
|
||||
|
||||
let Ok(annotations) = state.node_annotations().await else {
|
||||
return Json(out);
|
||||
return output.to_response(out);
|
||||
};
|
||||
|
||||
// safety: valid percentage value
|
||||
@@ -100,7 +110,7 @@ async fn get_mixnodes(State(state): State<AppState>) -> Json<Vec<LegacyMixNodeDe
|
||||
out.push(node);
|
||||
}
|
||||
|
||||
Json(out)
|
||||
output.to_response(out)
|
||||
}
|
||||
|
||||
// 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,
|
||||
path = "/v1/mixnodes/detailed",
|
||||
responses(
|
||||
(status = 200, body = Vec<MixNodeBondAnnotated>)
|
||||
)
|
||||
(status = 200, content(
|
||||
(Vec<MixNodeBondAnnotated> = "application/json"),
|
||||
(Vec<MixNodeBondAnnotated> = "application/yaml"),
|
||||
(Vec<MixNodeBondAnnotated> = "application/bincode")
|
||||
))
|
||||
),
|
||||
params(OutputParams)
|
||||
)]
|
||||
#[deprecated]
|
||||
async fn get_mixnodes_detailed(State(state): State<AppState>) -> Json<Vec<MixNodeBondAnnotated>> {
|
||||
_get_legacy_mixnodes_detailed(state.node_status_cache())
|
||||
.await
|
||||
.into()
|
||||
async fn get_mixnodes_detailed(
|
||||
Query(output): Query<OutputParams>,
|
||||
State(state): State<AppState>,
|
||||
) -> FormattedResponse<Vec<MixNodeBondAnnotated>> {
|
||||
let output = output.output.unwrap_or_default();
|
||||
|
||||
output.to_response(_get_legacy_mixnodes_detailed(state.node_status_cache()).await)
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
@@ -130,11 +148,21 @@ async fn get_mixnodes_detailed(State(state): State<AppState>) -> Json<Vec<MixNod
|
||||
get,
|
||||
path = "/v1/gateways",
|
||||
responses(
|
||||
(status = 200, body = Vec<GatewayBond>)
|
||||
)
|
||||
(status = 200, content(
|
||||
(Vec<GatewayBond> = "application/json"),
|
||||
(Vec<GatewayBond> = "application/yaml"),
|
||||
(Vec<GatewayBond> = "application/bincode")
|
||||
))
|
||||
),
|
||||
params(OutputParams)
|
||||
)]
|
||||
#[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
|
||||
let mut out: Vec<GatewayBond> = state
|
||||
.nym_contract_cache()
|
||||
@@ -145,15 +173,15 @@ async fn get_gateways(State(state): State<AppState>) -> Json<Vec<GatewayBond>> {
|
||||
.collect();
|
||||
|
||||
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 {
|
||||
return Json(out);
|
||||
return output.to_response(out);
|
||||
};
|
||||
|
||||
let Ok(annotations) = state.node_annotations().await else {
|
||||
return Json(out);
|
||||
return output.to_response(out);
|
||||
};
|
||||
|
||||
// safety: valid percentage value
|
||||
@@ -182,7 +210,7 @@ async fn get_gateways(State(state): State<AppState>) -> Json<Vec<GatewayBond>> {
|
||||
out.push(node);
|
||||
}
|
||||
|
||||
Json(out)
|
||||
output.to_response(out)
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
@@ -190,14 +218,22 @@ async fn get_gateways(State(state): State<AppState>) -> Json<Vec<GatewayBond>> {
|
||||
get,
|
||||
path = "/v1/mixnodes/rewarded",
|
||||
responses(
|
||||
(status = 200, body = Vec<LegacyMixNodeDetailsWithLayer>)
|
||||
)
|
||||
(status = 200, content(
|
||||
(Vec<LegacyMixNodeDetailsWithLayer> = "application/json"),
|
||||
(Vec<LegacyMixNodeDetailsWithLayer> = "application/yaml"),
|
||||
(Vec<LegacyMixNodeDetailsWithLayer> = "application/bincode")
|
||||
))
|
||||
),
|
||||
params(OutputParams)
|
||||
)]
|
||||
#[deprecated]
|
||||
async fn get_rewarded_set(
|
||||
Query(output): Query<OutputParams>,
|
||||
State(state): State<AppState>,
|
||||
) -> Json<Vec<LegacyMixNodeDetailsWithLayer>> {
|
||||
Json(
|
||||
) -> FormattedResponse<Vec<LegacyMixNodeDetailsWithLayer>> {
|
||||
let output = output.output.unwrap_or_default();
|
||||
|
||||
output.to_response(
|
||||
state
|
||||
.nym_contract_cache()
|
||||
.legacy_v1_rewarded_set_mixnodes()
|
||||
@@ -218,19 +254,28 @@ async fn get_rewarded_set(
|
||||
get,
|
||||
path = "/v1/mixnodes/rewarded/detailed",
|
||||
responses(
|
||||
(status = 200, body = Vec<MixNodeBondAnnotated>)
|
||||
)
|
||||
(status = 200, content(
|
||||
(Vec<MixNodeBondAnnotated> = "application/json"),
|
||||
(Vec<MixNodeBondAnnotated> = "application/yaml"),
|
||||
(Vec<MixNodeBondAnnotated> = "application/bincode")
|
||||
))
|
||||
),
|
||||
params(OutputParams)
|
||||
)]
|
||||
#[deprecated]
|
||||
async fn get_rewarded_set_detailed(
|
||||
Query(output): Query<OutputParams>,
|
||||
State(state): State<AppState>,
|
||||
) -> Json<Vec<MixNodeBondAnnotated>> {
|
||||
_get_rewarded_set_legacy_mixnodes_detailed(
|
||||
state.node_status_cache(),
|
||||
state.nym_contract_cache(),
|
||||
) -> FormattedResponse<Vec<MixNodeBondAnnotated>> {
|
||||
let output = output.output.unwrap_or_default();
|
||||
|
||||
output.to_response(
|
||||
_get_rewarded_set_legacy_mixnodes_detailed(
|
||||
state.node_status_cache(),
|
||||
state.nym_contract_cache(),
|
||||
)
|
||||
.await,
|
||||
)
|
||||
.await
|
||||
.into()
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
@@ -238,11 +283,21 @@ async fn get_rewarded_set_detailed(
|
||||
get,
|
||||
path = "/v1/mixnodes/active",
|
||||
responses(
|
||||
(status = 200, body = Vec<LegacyMixNodeDetailsWithLayer>)
|
||||
)
|
||||
(status = 200, content(
|
||||
(Vec<LegacyMixNodeDetailsWithLayer> = "application/json"),
|
||||
(Vec<LegacyMixNodeDetailsWithLayer> = "application/yaml"),
|
||||
(Vec<LegacyMixNodeDetailsWithLayer> = "application/bincode")
|
||||
))
|
||||
),
|
||||
params(OutputParams)
|
||||
)]
|
||||
#[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
|
||||
.nym_contract_cache()
|
||||
.legacy_v1_active_set_mixnodes()
|
||||
@@ -250,19 +305,19 @@ async fn get_active_set(State(state): State<AppState>) -> Json<Vec<LegacyMixNode
|
||||
.clone();
|
||||
|
||||
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 {
|
||||
return Json(out);
|
||||
return output.to_response(out);
|
||||
};
|
||||
|
||||
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 {
|
||||
return Json(out);
|
||||
return output.to_response(out);
|
||||
};
|
||||
|
||||
// safety: valid percentage value
|
||||
@@ -295,7 +350,7 @@ async fn get_active_set(State(state): State<AppState>) -> Json<Vec<LegacyMixNode
|
||||
out.push(node);
|
||||
}
|
||||
|
||||
Json(out)
|
||||
output.to_response(out)
|
||||
}
|
||||
|
||||
// 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,
|
||||
path = "/v1/mixnodes/active/detailed",
|
||||
responses(
|
||||
(status = 200, body = Vec<MixNodeBondAnnotated>)
|
||||
)
|
||||
(status = 200, content(
|
||||
(Vec<MixNodeBondAnnotated> = "application/json"),
|
||||
(Vec<MixNodeBondAnnotated> = "application/yaml"),
|
||||
(Vec<MixNodeBondAnnotated> = "application/bincode")
|
||||
))
|
||||
),
|
||||
params(OutputParams)
|
||||
)]
|
||||
#[deprecated]
|
||||
async fn get_active_set_detailed(State(state): State<AppState>) -> Json<Vec<MixNodeBondAnnotated>> {
|
||||
_get_active_set_legacy_mixnodes_detailed(state.node_status_cache(), state.nym_contract_cache())
|
||||
.await
|
||||
.into()
|
||||
async fn get_active_set_detailed(
|
||||
Query(output): Query<OutputParams>,
|
||||
State(state): State<AppState>,
|
||||
) -> 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(
|
||||
@@ -326,22 +395,31 @@ async fn get_active_set_detailed(State(state): State<AppState>) -> Json<Vec<MixN
|
||||
get,
|
||||
path = "/v1/mixnodes/blacklisted",
|
||||
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]
|
||||
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
|
||||
.nym_contract_cache()
|
||||
.mixnodes_blacklist()
|
||||
.await
|
||||
.to_owned();
|
||||
if blacklist.is_empty() {
|
||||
None
|
||||
output.to_response(None)
|
||||
} else {
|
||||
Some(blacklist)
|
||||
output.to_response(Some(blacklist))
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
@@ -349,18 +427,28 @@ async fn get_blacklisted_mixnodes(State(state): State<AppState>) -> Json<Option<
|
||||
get,
|
||||
path = "/v1/gateways/blacklisted",
|
||||
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]
|
||||
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 blacklist = cache.gateways_blacklist().await.clone();
|
||||
if blacklist.is_empty() {
|
||||
Json(None)
|
||||
output.to_response(None)
|
||||
} else {
|
||||
let gateways = cache.legacy_gateways_all().await;
|
||||
Json(Some(
|
||||
output.to_response(Some(
|
||||
gateways
|
||||
.into_iter()
|
||||
.filter(|g| blacklist.contains(&g.node_id))
|
||||
@@ -375,18 +463,27 @@ async fn get_blacklisted_gateways(State(state): State<AppState>) -> Json<Option<
|
||||
get,
|
||||
path = "/v1/epoch/reward_params",
|
||||
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(
|
||||
Query(output): Query<OutputParams>,
|
||||
State(state): State<AppState>,
|
||||
) -> Json<Option<RewardingParams>> {
|
||||
state
|
||||
.nym_contract_cache()
|
||||
.interval_reward_params()
|
||||
.await
|
||||
.to_owned()
|
||||
.into()
|
||||
) -> FormattedResponse<Option<RewardingParams>> {
|
||||
let output = output.output.unwrap_or_default();
|
||||
|
||||
output.to_response(
|
||||
state
|
||||
.nym_contract_cache()
|
||||
.interval_reward_params()
|
||||
.await
|
||||
.to_owned(),
|
||||
)
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
@@ -394,14 +491,25 @@ async fn get_interval_reward_params(
|
||||
get,
|
||||
path = "/v1/epoch/current",
|
||||
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>> {
|
||||
state
|
||||
.nym_contract_cache()
|
||||
.current_interval()
|
||||
.await
|
||||
.to_owned()
|
||||
.into()
|
||||
async fn get_current_epoch(
|
||||
Query(output): Query<OutputParams>,
|
||||
State(state): State<AppState>,
|
||||
) -> FormattedResponse<Option<Interval>> {
|
||||
let output = output.output.unwrap_or_default();
|
||||
|
||||
output.to_response(
|
||||
state
|
||||
.nym_contract_cache()
|
||||
.current_interval()
|
||||
.await
|
||||
.to_owned(),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3,10 +3,11 @@
|
||||
|
||||
use crate::support::http::state::AppState;
|
||||
use crate::support::legacy_helpers::{to_legacy_gateway, to_legacy_mixnode};
|
||||
use axum::extract::State;
|
||||
use axum::{Json, Router};
|
||||
use axum::extract::{Query, State};
|
||||
use axum::Router;
|
||||
use nym_api_requests::legacy::LegacyMixNodeBondWithLayer;
|
||||
use nym_api_requests::models::{LegacyDescribedGateway, LegacyDescribedMixNode};
|
||||
use nym_http_api_common::{FormattedResponse, OutputParams};
|
||||
use tower_http::compression::CompressionLayer;
|
||||
|
||||
// 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,
|
||||
path = "/v1/gateways/described",
|
||||
responses(
|
||||
(status = 200, body = Vec<LegacyDescribedGateway>)
|
||||
)
|
||||
(status = 200, content(
|
||||
(Vec<LegacyDescribedGateway> = "application/json"),
|
||||
(Vec<LegacyDescribedGateway> = "application/yaml"),
|
||||
(Vec<LegacyDescribedGateway> = "application/bincode")
|
||||
))
|
||||
),
|
||||
params(OutputParams)
|
||||
)]
|
||||
#[deprecated]
|
||||
async fn get_gateways_described(
|
||||
Query(output): Query<OutputParams>,
|
||||
State(state): State<AppState>,
|
||||
) -> Json<Vec<LegacyDescribedGateway>> {
|
||||
) -> FormattedResponse<Vec<LegacyDescribedGateway>> {
|
||||
let contract_cache = state.nym_contract_cache();
|
||||
let describe_cache = state.described_nodes_cache();
|
||||
let output = output.output.unwrap_or_default();
|
||||
|
||||
// legacy
|
||||
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
|
||||
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;
|
||||
@@ -75,7 +83,7 @@ async fn get_gateways_described(
|
||||
})
|
||||
}
|
||||
|
||||
Json(out)
|
||||
output.to_response(out)
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
@@ -83,15 +91,22 @@ async fn get_gateways_described(
|
||||
get,
|
||||
path = "/v1/mixnodes/described",
|
||||
responses(
|
||||
(status = 200, body = Vec<LegacyDescribedMixNode>)
|
||||
)
|
||||
(status = 200, content(
|
||||
(Vec<LegacyDescribedMixNode> = "application/json"),
|
||||
(Vec<LegacyDescribedMixNode> = "application/yaml"),
|
||||
(Vec<LegacyDescribedMixNode> = "application/bincode")
|
||||
))
|
||||
),
|
||||
params(OutputParams)
|
||||
)]
|
||||
#[deprecated]
|
||||
async fn get_mixnodes_described(
|
||||
Query(output): Query<OutputParams>,
|
||||
State(state): State<AppState>,
|
||||
) -> Json<Vec<LegacyDescribedMixNode>> {
|
||||
) -> FormattedResponse<Vec<LegacyDescribedMixNode>> {
|
||||
let contract_cache = state.nym_contract_cache();
|
||||
let describe_cache = state.described_nodes_cache();
|
||||
let output = output.output.unwrap_or_default();
|
||||
|
||||
let legacy: Vec<LegacyMixNodeBondWithLayer> = contract_cache
|
||||
.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
|
||||
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;
|
||||
@@ -131,5 +146,5 @@ async fn get_mixnodes_described(
|
||||
})
|
||||
}
|
||||
|
||||
Json(out)
|
||||
output.to_response(out)
|
||||
}
|
||||
|
||||
@@ -15,9 +15,9 @@ use nym_api_requests::models::{
|
||||
};
|
||||
use nym_api_requests::pagination::{PaginatedResponse, Pagination};
|
||||
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::NymNodeDetails;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::time::Duration;
|
||||
use time::{Date, OffsetDateTime};
|
||||
@@ -55,10 +55,20 @@ pub(crate) fn nym_node_routes() -> Router<AppState> {
|
||||
path = "/rewarded-set",
|
||||
context_path = "/v1/nym-nodes",
|
||||
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
|
||||
.nym_contract_cache()
|
||||
.rewarded_set()
|
||||
@@ -67,7 +77,7 @@ async fn rewarded_set(State(state): State<AppState>) -> AxumResult<Json<Rewarded
|
||||
.ok_or(UninitialisedCache)?
|
||||
.into_inner();
|
||||
|
||||
Ok(Json(
|
||||
Ok(output.to_response(
|
||||
nym_mixnet_contract_common::EpochRewardedSet::from(cached_rewarded_set).into(),
|
||||
))
|
||||
}
|
||||
@@ -136,16 +146,21 @@ async fn refresh_described(
|
||||
path = "/noise",
|
||||
context_path = "/v1/nym-nodes",
|
||||
responses(
|
||||
(status = 200, body = PaginatedResponse<NoiseDetails>)
|
||||
(status = 200, content(
|
||||
(PaginatedResponse<NoiseDetails> = "application/json"),
|
||||
(PaginatedResponse<NoiseDetails> = "application/yaml"),
|
||||
(PaginatedResponse<NoiseDetails> = "application/bincode")
|
||||
))
|
||||
),
|
||||
params(PaginationRequest)
|
||||
)]
|
||||
async fn nodes_noise(
|
||||
State(state): State<AppState>,
|
||||
Query(pagination): Query<PaginationRequest>,
|
||||
) -> AxumResult<Json<PaginatedResponse<NoiseDetails>>> {
|
||||
) -> AxumResult<FormattedResponse<PaginatedResponse<NoiseDetails>>> {
|
||||
// TODO: implement it
|
||||
let _ = pagination;
|
||||
let output = pagination.output.unwrap_or_default();
|
||||
|
||||
let describe_cache = state.describe_nodes_cache_data().await?;
|
||||
|
||||
@@ -167,7 +182,7 @@ async fn nodes_noise(
|
||||
|
||||
let total = nodes.len();
|
||||
|
||||
Ok(Json(PaginatedResponse {
|
||||
Ok(output.to_response(PaginatedResponse {
|
||||
pagination: Pagination {
|
||||
total,
|
||||
page: 0,
|
||||
@@ -183,21 +198,26 @@ async fn nodes_noise(
|
||||
path = "/bonded",
|
||||
context_path = "/v1/nym-nodes",
|
||||
responses(
|
||||
(status = 200, body = PaginatedResponse<NymNodeDetails>)
|
||||
(status = 200, content(
|
||||
(PaginatedResponse<NymNodeDetails> = "application/json"),
|
||||
(PaginatedResponse<NymNodeDetails> = "application/yaml"),
|
||||
(PaginatedResponse<NymNodeDetails> = "application/bincode")
|
||||
))
|
||||
),
|
||||
params(PaginationRequest)
|
||||
)]
|
||||
async fn get_bonded_nodes(
|
||||
State(state): State<AppState>,
|
||||
Query(pagination): Query<PaginationRequest>,
|
||||
) -> Json<PaginatedResponse<NymNodeDetails>> {
|
||||
) -> FormattedResponse<PaginatedResponse<NymNodeDetails>> {
|
||||
// TODO: implement it
|
||||
let _ = pagination;
|
||||
let output = pagination.output.unwrap_or_default();
|
||||
|
||||
let details = state.nym_contract_cache().nym_nodes().await;
|
||||
let total = details.len();
|
||||
|
||||
Json(PaginatedResponse {
|
||||
output.to_response(PaginatedResponse {
|
||||
pagination: Pagination {
|
||||
total,
|
||||
page: 0,
|
||||
@@ -213,21 +233,26 @@ async fn get_bonded_nodes(
|
||||
path = "/described",
|
||||
context_path = "/v1/nym-nodes",
|
||||
responses(
|
||||
(status = 200, body = PaginatedResponse<NymNodeDescription>)
|
||||
(status = 200, content(
|
||||
(PaginatedResponse<NymNodeDescription> = "application/json"),
|
||||
(PaginatedResponse<NymNodeDescription> = "application/yaml"),
|
||||
(PaginatedResponse<NymNodeDescription> = "application/bincode")
|
||||
))
|
||||
),
|
||||
params(PaginationRequest)
|
||||
)]
|
||||
async fn get_described_nodes(
|
||||
State(state): State<AppState>,
|
||||
Query(pagination): Query<PaginationRequest>,
|
||||
) -> AxumResult<Json<PaginatedResponse<NymNodeDescription>>> {
|
||||
) -> AxumResult<FormattedResponse<PaginatedResponse<NymNodeDescription>>> {
|
||||
// TODO: implement it
|
||||
let _ = pagination;
|
||||
let output = pagination.output.unwrap_or_default();
|
||||
|
||||
let cache = state.described_nodes_cache.get().await?;
|
||||
let descriptions = cache.all_nodes().cloned().collect::<Vec<_>>();
|
||||
|
||||
Ok(Json(PaginatedResponse {
|
||||
Ok(output.to_response(PaginatedResponse {
|
||||
pagination: Pagination {
|
||||
total: descriptions.len(),
|
||||
page: 0,
|
||||
@@ -243,21 +268,28 @@ async fn get_described_nodes(
|
||||
path = "/annotation/{node_id}",
|
||||
context_path = "/v1/nym-nodes",
|
||||
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(
|
||||
Path(NodeIdParam { node_id }): Path<NodeIdParam>,
|
||||
Query(output): Query<OutputParams>,
|
||||
State(state): State<AppState>,
|
||||
) -> AxumResult<Json<AnnotationResponse>> {
|
||||
) -> AxumResult<FormattedResponse<AnnotationResponse>> {
|
||||
let output = output.output.unwrap_or_default();
|
||||
|
||||
let annotations = state
|
||||
.node_status_cache
|
||||
.node_annotations()
|
||||
.await
|
||||
.ok_or_else(AxumErrorResponse::internal)?;
|
||||
|
||||
Ok(Json(AnnotationResponse {
|
||||
Ok(output.to_response(AnnotationResponse {
|
||||
node_id,
|
||||
annotation: annotations.get(&node_id).cloned(),
|
||||
}))
|
||||
@@ -269,21 +301,28 @@ async fn get_node_annotation(
|
||||
path = "/performance/{node_id}",
|
||||
context_path = "/v1/nym-nodes",
|
||||
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(
|
||||
Path(NodeIdParam { node_id }): Path<NodeIdParam>,
|
||||
Query(output): Query<OutputParams>,
|
||||
State(state): State<AppState>,
|
||||
) -> AxumResult<Json<NodePerformanceResponse>> {
|
||||
) -> AxumResult<FormattedResponse<NodePerformanceResponse>> {
|
||||
let output = output.output.unwrap_or_default();
|
||||
|
||||
let annotations = state
|
||||
.node_status_cache
|
||||
.node_annotations()
|
||||
.await
|
||||
.ok_or_else(AxumErrorResponse::internal)?;
|
||||
|
||||
Ok(Json(NodePerformanceResponse {
|
||||
Ok(output.to_response(NodePerformanceResponse {
|
||||
node_id,
|
||||
performance: annotations
|
||||
.get(&node_id)
|
||||
@@ -292,12 +331,13 @@ async fn get_current_node_performance(
|
||||
}
|
||||
|
||||
// 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)]
|
||||
pub(crate) struct DateQuery {
|
||||
#[schema(value_type = String, example = "1970-01-01")]
|
||||
#[schemars(with = "String")]
|
||||
pub(crate) date: Date,
|
||||
|
||||
pub(crate) output: Option<Output>,
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
@@ -306,21 +346,27 @@ pub(crate) struct DateQuery {
|
||||
path = "/historical-performance/{node_id}",
|
||||
context_path = "/v1/nym-nodes",
|
||||
responses(
|
||||
(status = 200, body = NodeDatePerformanceResponse)
|
||||
(status = 200, content(
|
||||
(NodeDatePerformanceResponse = "application/json"),
|
||||
(NodeDatePerformanceResponse = "application/yaml"),
|
||||
(NodeDatePerformanceResponse = "application/bincode")
|
||||
))
|
||||
),
|
||||
params(DateQuery, NodeIdParam)
|
||||
)]
|
||||
async fn get_historical_performance(
|
||||
Path(NodeIdParam { node_id }): Path<NodeIdParam>,
|
||||
Query(DateQuery { date }): Query<DateQuery>,
|
||||
Query(DateQuery { date, output }): Query<DateQuery>,
|
||||
State(state): State<AppState>,
|
||||
) -> AxumResult<Json<NodeDatePerformanceResponse>> {
|
||||
) -> AxumResult<FormattedResponse<NodeDatePerformanceResponse>> {
|
||||
let output = output.unwrap_or_default();
|
||||
|
||||
let uptime = state
|
||||
.storage()
|
||||
.get_historical_node_uptime_on(node_id, date)
|
||||
.await?;
|
||||
|
||||
Ok(Json(NodeDatePerformanceResponse {
|
||||
Ok(output.to_response(NodeDatePerformanceResponse {
|
||||
node_id,
|
||||
date,
|
||||
performance: uptime.and_then(|u| {
|
||||
@@ -337,7 +383,11 @@ async fn get_historical_performance(
|
||||
path = "/performance-history/{node_id}",
|
||||
context_path = "/v1/nym-nodes",
|
||||
responses(
|
||||
(status = 200, body = PerformanceHistoryResponse)
|
||||
(status = 200, content(
|
||||
(PerformanceHistoryResponse = "application/json"),
|
||||
(PerformanceHistoryResponse = "application/yaml"),
|
||||
(PerformanceHistoryResponse = "application/bincode")
|
||||
))
|
||||
),
|
||||
params(PaginationRequest, NodeIdParam)
|
||||
)]
|
||||
@@ -345,9 +395,10 @@ async fn get_node_performance_history(
|
||||
Path(NodeIdParam { node_id }): Path<NodeIdParam>,
|
||||
State(state): State<AppState>,
|
||||
Query(pagination): Query<PaginationRequest>,
|
||||
) -> AxumResult<Json<PerformanceHistoryResponse>> {
|
||||
) -> AxumResult<FormattedResponse<PerformanceHistoryResponse>> {
|
||||
// TODO: implement it
|
||||
let _ = pagination;
|
||||
let output = pagination.output.unwrap_or_default();
|
||||
|
||||
let history = state
|
||||
.storage()
|
||||
@@ -358,7 +409,7 @@ async fn get_node_performance_history(
|
||||
.collect::<Vec<_>>();
|
||||
let total = history.len();
|
||||
|
||||
Ok(Json(PerformanceHistoryResponse {
|
||||
Ok(output.to_response(PerformanceHistoryResponse {
|
||||
node_id,
|
||||
history: PaginatedResponse {
|
||||
pagination: Pagination {
|
||||
@@ -377,7 +428,11 @@ async fn get_node_performance_history(
|
||||
path = "/uptime-history/{node_id}",
|
||||
context_path = "/v1/nym-nodes",
|
||||
responses(
|
||||
(status = 200, body = PerformanceHistoryResponse)
|
||||
(status = 200, content(
|
||||
(PerformanceHistoryResponse = "application/json"),
|
||||
(PerformanceHistoryResponse = "application/yaml"),
|
||||
(PerformanceHistoryResponse = "application/bincode")
|
||||
))
|
||||
),
|
||||
params(PaginationRequest, NodeIdParam)
|
||||
)]
|
||||
@@ -385,9 +440,10 @@ async fn get_node_uptime_history(
|
||||
Path(NodeIdParam { node_id }): Path<NodeIdParam>,
|
||||
State(state): State<AppState>,
|
||||
Query(pagination): Query<PaginationRequest>,
|
||||
) -> AxumResult<Json<UptimeHistoryResponse>> {
|
||||
) -> AxumResult<FormattedResponse<UptimeHistoryResponse>> {
|
||||
// TODO: implement it
|
||||
let _ = pagination;
|
||||
let output = pagination.output.unwrap_or_default();
|
||||
|
||||
let history = state
|
||||
.storage()
|
||||
@@ -398,7 +454,7 @@ async fn get_node_uptime_history(
|
||||
.collect::<Vec<_>>();
|
||||
let total = history.len();
|
||||
|
||||
Ok(Json(UptimeHistoryResponse {
|
||||
Ok(output.to_response(UptimeHistoryResponse {
|
||||
node_id,
|
||||
history: PaginatedResponse {
|
||||
pagination: Pagination {
|
||||
|
||||
@@ -5,8 +5,8 @@ use crate::node_status_api::models::{AxumErrorResponse, AxumResult};
|
||||
use crate::nym_nodes::handlers::unstable::NodesParamsWithRole;
|
||||
use crate::support::http::state::AppState;
|
||||
use axum::extract::{Query, State};
|
||||
use axum::Json;
|
||||
use nym_api_requests::nym_nodes::{CachedNodesResponse, FullFatNode};
|
||||
use nym_http_api_common::FormattedResponse;
|
||||
|
||||
#[utoipa::path(
|
||||
tag = "Unstable Nym Nodes",
|
||||
@@ -22,6 +22,6 @@ use nym_api_requests::nym_nodes::{CachedNodesResponse, FullFatNode};
|
||||
pub(super) async fn nodes_detailed(
|
||||
_state: State<AppState>,
|
||||
_query_params: Query<NodesParamsWithRole>,
|
||||
) -> AxumResult<Json<CachedNodesResponse<FullFatNode>>> {
|
||||
) -> AxumResult<FormattedResponse<CachedNodesResponse<FullFatNode>>> {
|
||||
Err(AxumErrorResponse::not_implemented())
|
||||
}
|
||||
|
||||
@@ -30,12 +30,13 @@ use crate::nym_nodes::handlers::unstable::skimmed::{
|
||||
};
|
||||
use crate::support::http::helpers::PaginationRequest;
|
||||
use crate::support::http::state::AppState;
|
||||
use axum::extract::State;
|
||||
use axum::extract::{Query, State};
|
||||
use axum::routing::{get, post};
|
||||
use axum::{Json, Router};
|
||||
use nym_api_requests::nym_nodes::{
|
||||
NodeRoleQueryParam, NodesByAddressesRequestBody, NodesByAddressesResponse,
|
||||
};
|
||||
use nym_http_api_common::{FormattedResponse, Output, OutputParams};
|
||||
use serde::Deserialize;
|
||||
use std::collections::HashMap;
|
||||
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
|
||||
// instead of wasting bandwidth serving an unchanged topology.
|
||||
epoch_id: Option<u32>,
|
||||
|
||||
output: Option<Output>,
|
||||
}
|
||||
|
||||
#[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
|
||||
// instead of wasting bandwidth serving an unchanged topology.
|
||||
epoch_id: Option<u32>,
|
||||
output: Option<Output>,
|
||||
}
|
||||
|
||||
impl From<NodesParamsWithRole> for NodesParams {
|
||||
@@ -123,6 +127,7 @@ impl From<NodesParamsWithRole> for NodesParams {
|
||||
page: params.page,
|
||||
per_page: params.per_page,
|
||||
epoch_id: params.epoch_id,
|
||||
output: params.output,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -130,6 +135,7 @@ impl From<NodesParamsWithRole> for NodesParams {
|
||||
impl<'a> From<&'a NodesParams> for PaginationRequest {
|
||||
fn from(params: &'a NodesParams) -> Self {
|
||||
PaginationRequest {
|
||||
output: params.output,
|
||||
page: params.page,
|
||||
per_page: params.per_page,
|
||||
}
|
||||
@@ -143,13 +149,19 @@ impl<'a> From<&'a NodesParams> for PaginationRequest {
|
||||
path = "/by-addresses",
|
||||
context_path = "/v1/unstable/nym-nodes",
|
||||
responses(
|
||||
(status = 200, body = NodesByAddressesResponse)
|
||||
)
|
||||
(status = 200, content(
|
||||
(NodesByAddressesResponse = "application/json"),
|
||||
(NodesByAddressesResponse = "application/yaml"),
|
||||
(NodesByAddressesResponse = "application/bincode")
|
||||
))
|
||||
),
|
||||
params(OutputParams)
|
||||
)]
|
||||
async fn nodes_by_addresses(
|
||||
Query(output): Query<OutputParams>,
|
||||
state: State<AppState>,
|
||||
Json(body): Json<NodesByAddressesRequestBody>,
|
||||
) -> AxumResult<Json<NodesByAddressesResponse>> {
|
||||
) -> AxumResult<FormattedResponse<NodesByAddressesResponse>> {
|
||||
// if the request is too big, simply reject it
|
||||
if body.addresses.len() > 100 {
|
||||
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
|
||||
// request for the same couple addresses from all nodes in quick succession
|
||||
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));
|
||||
}
|
||||
|
||||
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::support::http::state::AppState;
|
||||
use axum::extract::{Query, State};
|
||||
use axum::Json;
|
||||
use nym_api_requests::nym_nodes::{CachedNodesResponse, SemiSkimmedNode};
|
||||
use nym_http_api_common::FormattedResponse;
|
||||
|
||||
#[utoipa::path(
|
||||
tag = "Unstable Nym Nodes",
|
||||
@@ -22,6 +22,6 @@ use nym_api_requests::nym_nodes::{CachedNodesResponse, SemiSkimmedNode};
|
||||
pub(super) async fn nodes_expanded(
|
||||
_state: State<AppState>,
|
||||
_query_params: Query<NodesParamsWithRole>,
|
||||
) -> AxumResult<Json<CachedNodesResponse<SemiSkimmedNode>>> {
|
||||
) -> AxumResult<FormattedResponse<CachedNodesResponse<SemiSkimmedNode>>> {
|
||||
Err(AxumErrorResponse::not_implemented())
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ use crate::nym_nodes::handlers::unstable::{NodesParams, NodesParamsWithRole};
|
||||
use crate::support::caching::Cache;
|
||||
use crate::support::http::state::AppState;
|
||||
use axum::extract::{Query, State};
|
||||
use axum::Json;
|
||||
use nym_api_requests::models::{
|
||||
NodeAnnotation, NymNodeDescription, OffsetDateTimeJsonSchemaWrapper,
|
||||
};
|
||||
@@ -16,15 +15,18 @@ use nym_api_requests::nym_nodes::{
|
||||
CachedNodesResponse, NodeRole, NodeRoleQueryParam, PaginatedCachedNodesResponse, SkimmedNode,
|
||||
};
|
||||
use nym_api_requests::pagination::PaginatedResponse;
|
||||
use nym_http_api_common::{FormattedResponse, Output};
|
||||
use nym_mixnet_contract_common::NodeId;
|
||||
use nym_topology::CachedEpochRewardedSet;
|
||||
use std::collections::HashMap;
|
||||
use std::future::Future;
|
||||
use std::time::Duration;
|
||||
use tokio::sync::RwLockReadGuard;
|
||||
use tracing::trace;
|
||||
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
|
||||
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,
|
||||
annotated_legacy_nodes_getter: LG,
|
||||
active_only: bool,
|
||||
output: Output,
|
||||
) -> PaginatedSkimmedNodes
|
||||
where
|
||||
// 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)
|
||||
let describe_cache = state.describe_nodes_cache_data().await?;
|
||||
|
||||
let maybe_interval = state
|
||||
let Some(interval) = state
|
||||
.nym_contract_cache()
|
||||
.current_interval()
|
||||
.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
|
||||
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() {
|
||||
return Ok(Json(PaginatedCachedNodesResponse::no_updates()));
|
||||
}
|
||||
if client_known_epoch == interval.current_epoch_id() {
|
||||
return Ok(output.to_response(PaginatedCachedNodesResponse::no_updates()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,8 +157,8 @@ where
|
||||
describe_cache.timestamp(),
|
||||
]);
|
||||
|
||||
return Ok(Json(
|
||||
PaginatedCachedNodesResponse::new_full(refreshed_at, nodes).fresh(maybe_interval),
|
||||
return Ok(output.to_response(
|
||||
PaginatedCachedNodesResponse::new_full(refreshed_at, nodes).fresh(Some(interval)),
|
||||
));
|
||||
}
|
||||
|
||||
@@ -176,9 +181,19 @@ where
|
||||
annotated_legacy_nodes.timestamp(),
|
||||
]);
|
||||
|
||||
Ok(Json(
|
||||
PaginatedCachedNodesResponse::new_full(refreshed_at, nodes).fresh(maybe_interval),
|
||||
))
|
||||
let base_response = output.to_response(
|
||||
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
|
||||
@@ -189,22 +204,30 @@ where
|
||||
path = "/gateways/skimmed",
|
||||
context_path = "/v1/unstable/nym-nodes",
|
||||
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")]
|
||||
pub(super) async fn deprecated_gateways_basic(
|
||||
state: State<AppState>,
|
||||
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'
|
||||
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
|
||||
Ok(Json(CachedNodesResponse {
|
||||
Ok(output.to_response(CachedNodesResponse {
|
||||
refreshed_at: all_gateways.refreshed_at,
|
||||
// 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",
|
||||
context_path = "/v1/unstable/nym-nodes",
|
||||
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")]
|
||||
pub(super) async fn deprecated_mixnodes_basic(
|
||||
state: State<AppState>,
|
||||
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'
|
||||
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
|
||||
Ok(Json(CachedNodesResponse {
|
||||
Ok(output.to_response(CachedNodesResponse {
|
||||
refreshed_at: active_mixnodes.refreshed_at,
|
||||
// 2. remove pagination
|
||||
nodes: active_mixnodes.0.nodes.data,
|
||||
nodes: active_mixnodes.nodes.data,
|
||||
}))
|
||||
}
|
||||
|
||||
async fn nodes_basic(
|
||||
state: State<AppState>,
|
||||
Query(_query_params): Query<NodesParams>,
|
||||
Query(query_params): Query<NodesParams>,
|
||||
active_only: bool,
|
||||
) -> 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
|
||||
|
||||
// 1. grab all relevant described nym-nodes
|
||||
@@ -281,10 +314,7 @@ async fn nodes_basic(
|
||||
legacy_gateways.timestamp(),
|
||||
]);
|
||||
|
||||
Ok(Json(PaginatedCachedNodesResponse::new_full(
|
||||
refreshed_at,
|
||||
nodes,
|
||||
)))
|
||||
Ok(output.to_response(PaginatedCachedNodesResponse::new_full(refreshed_at, nodes)))
|
||||
}
|
||||
|
||||
#[allow(dead_code)] // not dead, used in OpenAPI docs
|
||||
@@ -305,8 +335,12 @@ pub struct PaginatedCachedNodesResponseSchema {
|
||||
path = "",
|
||||
context_path = "/v1/unstable/nym-nodes/skimmed",
|
||||
responses(
|
||||
(status = 200, body = PaginatedCachedNodesResponseSchema)
|
||||
)
|
||||
(status = 200, content(
|
||||
(PaginatedCachedNodesResponseSchema = "application/json"),
|
||||
(PaginatedCachedNodesResponseSchema = "application/yaml"),
|
||||
(PaginatedCachedNodesResponseSchema = "application/bincode")
|
||||
))
|
||||
),
|
||||
)]
|
||||
pub(super) async fn nodes_basic_all(
|
||||
state: State<AppState>,
|
||||
@@ -338,8 +372,12 @@ pub(super) async fn nodes_basic_all(
|
||||
path = "/active",
|
||||
context_path = "/v1/unstable/nym-nodes/skimmed",
|
||||
responses(
|
||||
(status = 200, body = PaginatedCachedNodesResponseSchema)
|
||||
)
|
||||
(status = 200, content(
|
||||
(PaginatedCachedNodesResponseSchema = "application/json"),
|
||||
(PaginatedCachedNodesResponseSchema = "application/yaml"),
|
||||
(PaginatedCachedNodesResponseSchema = "application/bincode")
|
||||
))
|
||||
),
|
||||
)]
|
||||
pub(super) async fn nodes_basic_active(
|
||||
state: State<AppState>,
|
||||
@@ -367,6 +405,8 @@ async fn mixnodes_basic(
|
||||
query_params: Query<NodesParams>,
|
||||
active_only: bool,
|
||||
) -> PaginatedSkimmedNodes {
|
||||
let output = query_params.output.unwrap_or_default();
|
||||
|
||||
// 1. grab all relevant described nym-nodes
|
||||
let describe_cache = state.describe_nodes_cache_data().await?;
|
||||
let mixing_nym_nodes = describe_cache.mixing_nym_nodes();
|
||||
@@ -377,6 +417,7 @@ async fn mixnodes_basic(
|
||||
mixing_nym_nodes,
|
||||
|state| state.legacy_mixnode_annotations(),
|
||||
active_only,
|
||||
output,
|
||||
)
|
||||
.await
|
||||
}
|
||||
@@ -390,8 +431,12 @@ async fn mixnodes_basic(
|
||||
path = "/mixnodes/all",
|
||||
context_path = "/v1/unstable/nym-nodes/skimmed",
|
||||
responses(
|
||||
(status = 200, body = PaginatedCachedNodesResponseSchema)
|
||||
)
|
||||
(status = 200, content(
|
||||
(PaginatedCachedNodesResponseSchema = "application/json"),
|
||||
(PaginatedCachedNodesResponseSchema = "application/yaml"),
|
||||
(PaginatedCachedNodesResponseSchema = "application/bincode")
|
||||
))
|
||||
),
|
||||
)]
|
||||
pub(super) async fn mixnodes_basic_all(
|
||||
state: State<AppState>,
|
||||
@@ -409,8 +454,12 @@ pub(super) async fn mixnodes_basic_all(
|
||||
path = "/mixnodes/active",
|
||||
context_path = "/v1/unstable/nym-nodes/skimmed",
|
||||
responses(
|
||||
(status = 200, body = PaginatedCachedNodesResponseSchema)
|
||||
)
|
||||
(status = 200, content(
|
||||
(PaginatedCachedNodesResponseSchema = "application/json"),
|
||||
(PaginatedCachedNodesResponseSchema = "application/yaml"),
|
||||
(PaginatedCachedNodesResponseSchema = "application/bincode")
|
||||
))
|
||||
),
|
||||
)]
|
||||
pub(super) async fn mixnodes_basic_active(
|
||||
state: State<AppState>,
|
||||
@@ -424,6 +473,8 @@ async fn entry_gateways_basic(
|
||||
query_params: Query<NodesParams>,
|
||||
active_only: bool,
|
||||
) -> PaginatedSkimmedNodes {
|
||||
let output = query_params.output.unwrap_or_default();
|
||||
|
||||
// 1. grab all relevant described nym-nodes
|
||||
let describe_cache = state.describe_nodes_cache_data().await?;
|
||||
let mixing_nym_nodes = describe_cache.entry_capable_nym_nodes();
|
||||
@@ -434,6 +485,7 @@ async fn entry_gateways_basic(
|
||||
mixing_nym_nodes,
|
||||
|state| state.legacy_gateways_annotations(),
|
||||
active_only,
|
||||
output,
|
||||
)
|
||||
.await
|
||||
}
|
||||
@@ -447,8 +499,12 @@ async fn entry_gateways_basic(
|
||||
path = "/entry-gateways/active",
|
||||
context_path = "/v1/unstable/nym-nodes/skimmed",
|
||||
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(
|
||||
state: State<AppState>,
|
||||
@@ -466,8 +522,12 @@ pub(super) async fn entry_gateways_basic_active(
|
||||
path = "/entry-gateways/all",
|
||||
context_path = "/v1/unstable/nym-nodes/skimmed",
|
||||
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(
|
||||
state: State<AppState>,
|
||||
@@ -481,6 +541,8 @@ async fn exit_gateways_basic(
|
||||
query_params: Query<NodesParams>,
|
||||
active_only: bool,
|
||||
) -> PaginatedSkimmedNodes {
|
||||
let output = query_params.output.unwrap_or_default();
|
||||
|
||||
// 1. grab all relevant described nym-nodes
|
||||
let describe_cache = state.describe_nodes_cache_data().await?;
|
||||
let mixing_nym_nodes = describe_cache.exit_capable_nym_nodes();
|
||||
@@ -491,6 +553,7 @@ async fn exit_gateways_basic(
|
||||
mixing_nym_nodes,
|
||||
|state| state.legacy_gateways_annotations(),
|
||||
active_only,
|
||||
output,
|
||||
)
|
||||
.await
|
||||
}
|
||||
@@ -504,8 +567,12 @@ async fn exit_gateways_basic(
|
||||
path = "/exit-gateways/active",
|
||||
context_path = "/v1/unstable/nym-nodes/skimmed",
|
||||
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(
|
||||
state: State<AppState>,
|
||||
@@ -523,8 +590,12 @@ pub(super) async fn exit_gateways_basic_active(
|
||||
path = "/exit-gateways/all",
|
||||
context_path = "/v1/unstable/nym-nodes/skimmed",
|
||||
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(
|
||||
state: State<AppState>,
|
||||
|
||||
@@ -4,14 +4,14 @@
|
||||
use crate::node_status_api::models::{AxumErrorResponse, AxumResult};
|
||||
use crate::status::ApiStatusState;
|
||||
use crate::support::http::state::AppState;
|
||||
use axum::extract::State;
|
||||
use axum::Json;
|
||||
use axum::extract::{Query, State};
|
||||
use axum::Router;
|
||||
use nym_api_requests::models::{
|
||||
ApiHealthResponse, ApiStatus, ChainStatus, SignerInformationResponse,
|
||||
};
|
||||
use nym_bin_common::build_information::BinaryBuildInformationOwned;
|
||||
use nym_compact_ecash::Base58;
|
||||
use nym_http_api_common::{FormattedResponse, OutputParams};
|
||||
use std::time::Duration;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
@@ -30,12 +30,22 @@ pub(crate) fn api_status_routes() -> Router<AppState> {
|
||||
get,
|
||||
path = "/v1/api-status/health",
|
||||
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);
|
||||
|
||||
let output = output.output.unwrap_or_default();
|
||||
|
||||
let uptime = state.api_status.startup_time.elapsed();
|
||||
let chain_status = match state
|
||||
.chain_status_cache
|
||||
@@ -61,7 +71,7 @@ async fn health(State(state): State<AppState>) -> Json<ApiHealthResponse> {
|
||||
chain_status,
|
||||
uptime: uptime.as_secs(),
|
||||
};
|
||||
Json(health)
|
||||
output.to_response(health)
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
@@ -69,13 +79,21 @@ async fn health(State(state): State<AppState>) -> Json<ApiHealthResponse> {
|
||||
get,
|
||||
path = "/v1/api-status/build-information",
|
||||
responses(
|
||||
(status = 200, body = BinaryBuildInformationOwned)
|
||||
)
|
||||
(status = 200, content(
|
||||
(BinaryBuildInformationOwned = "application/json"),
|
||||
(BinaryBuildInformationOwned = "application/yaml"),
|
||||
(BinaryBuildInformationOwned = "application/bincode")
|
||||
))
|
||||
),
|
||||
params(OutputParams)
|
||||
)]
|
||||
async fn build_information(
|
||||
Query(output): Query<OutputParams>,
|
||||
State(state): State<ApiStatusState>,
|
||||
) -> Json<BinaryBuildInformationOwned> {
|
||||
Json(state.build_information.to_owned())
|
||||
) -> FormattedResponse<BinaryBuildInformationOwned> {
|
||||
let output = output.output.unwrap_or_default();
|
||||
|
||||
output.to_response(state.build_information.to_owned())
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
@@ -83,17 +101,25 @@ async fn build_information(
|
||||
get,
|
||||
path = "/v1/api-status/signer-information",
|
||||
responses(
|
||||
(status = 200, body = SignerInformationResponse)
|
||||
)
|
||||
(status = 200, content(
|
||||
(SignerInformationResponse = "application/json"),
|
||||
(SignerInformationResponse = "application/yaml"),
|
||||
(SignerInformationResponse = "application/bincode")
|
||||
))
|
||||
),
|
||||
params(OutputParams)
|
||||
)]
|
||||
async fn signer_information(
|
||||
Query(output): Query<OutputParams>,
|
||||
State(state): State<ApiStatusState>,
|
||||
) -> AxumResult<Json<SignerInformationResponse>> {
|
||||
) -> AxumResult<FormattedResponse<SignerInformationResponse>> {
|
||||
let signer_state = state.signer_information.as_ref().ok_or_else(|| {
|
||||
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(),
|
||||
identity: signer_state.identity.clone(),
|
||||
announce_address: signer_state.announce_address.clone(),
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use nym_http_api_common::Output;
|
||||
use nym_mixnet_contract_common::NodeId;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use utoipa::{IntoParams, ToSchema};
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, JsonSchema, ToSchema, IntoParams)]
|
||||
#[derive(Serialize, Deserialize, Debug, ToSchema, IntoParams)]
|
||||
#[into_params(parameter_in = Query)]
|
||||
pub struct PaginationRequest {
|
||||
pub output: Option<Output>,
|
||||
pub page: Option<u32>,
|
||||
pub per_page: Option<u32>,
|
||||
}
|
||||
|
||||
@@ -36,6 +36,6 @@ features = ["tokio"]
|
||||
|
||||
[features]
|
||||
default = ["query-types"]
|
||||
query-types = ["nym-http-api-common"]
|
||||
query-types = ["nym-http-api-common", "nym-http-api-common/output"]
|
||||
openapi = ["utoipa"]
|
||||
tsify = ["dep:tsify", "wasm-bindgen"]
|
||||
|
||||
@@ -110,7 +110,7 @@ impl Monitor {
|
||||
.with_timeout(self.nym_api_client_timeout)
|
||||
.build::<&str>()?;
|
||||
|
||||
let api_client = NymApiClient { nym_api };
|
||||
let api_client = NymApiClient::from(nym_api);
|
||||
|
||||
let described_nodes = api_client
|
||||
.get_all_described_nodes()
|
||||
@@ -195,7 +195,7 @@ impl Monitor {
|
||||
tracing::info!("🟣 mixnodes_described: {}", mixnodes_described.len());
|
||||
let mixing_assigned_nodes = api_client
|
||||
.nym_api
|
||||
.get_basic_active_mixing_assigned_nodes(false, None, None)
|
||||
.get_basic_active_mixing_assigned_nodes(false, None, None, false)
|
||||
.await
|
||||
.log_error("get_basic_active_mixing_assigned_nodes")?
|
||||
.nodes
|
||||
|
||||
@@ -62,7 +62,7 @@ async fn run(
|
||||
.with_timeout(nym_api_client_timeout)
|
||||
.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
|
||||
let bonded_nodes = api_client.get_all_bonded_nym_nodes().await?;
|
||||
|
||||
@@ -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
|
||||
let request = NodeRefreshBody::new(self.ed25519_identity_keys.private_key());
|
||||
|
||||
@@ -177,7 +177,7 @@ impl NetworkRefresher {
|
||||
|
||||
let mut this = NetworkRefresher {
|
||||
querier: NodesQuerier {
|
||||
client: NymApiClient { nym_api },
|
||||
client: NymApiClient::from(nym_api),
|
||||
nym_api_urls,
|
||||
currently_used_api: 0,
|
||||
},
|
||||
|
||||
Generated
+11
-1
@@ -4036,7 +4036,6 @@ version = "0.6.0"
|
||||
dependencies = [
|
||||
"const-str",
|
||||
"log",
|
||||
"pretty_env_logger",
|
||||
"schemars",
|
||||
"serde",
|
||||
"utoipa",
|
||||
@@ -4186,12 +4185,14 @@ name = "nym-http-api-client"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"bincode",
|
||||
"bytes",
|
||||
"encoding_rs",
|
||||
"hickory-resolver",
|
||||
"http 1.3.1",
|
||||
"mime",
|
||||
"nym-bin-common",
|
||||
"nym-http-api-common",
|
||||
"once_cell",
|
||||
"reqwest 0.12.15",
|
||||
"serde",
|
||||
@@ -4202,6 +4203,15 @@ dependencies = [
|
||||
"wasmtimer",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-http-api-common"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
"serde",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-mixnet-contract-common"
|
||||
version = "0.6.0"
|
||||
|
||||
Reference in New Issue
Block a user