Compare commits

...

4 Commits

Author SHA1 Message Date
durch 70fb7d75d6 Split daily stats queries 2025-07-25 12:07:42 +02:00
durch f11b2caeb1 No error swallowing 2025-07-25 11:43:05 +02:00
durch 1a07dbb09c Bump cutoff 2025-07-25 10:18:35 +02:00
durch 9587247536 Time and id header middleware 2025-07-24 14:35:57 +02:00
10 changed files with 157 additions and 59 deletions
Generated
+2 -1
View File
@@ -6768,7 +6768,7 @@ dependencies = [
[[package]]
name = "nym-node-status-api"
version = "3.2.2"
version = "3.2.6"
dependencies = [
"ammonia",
"anyhow",
@@ -6780,6 +6780,7 @@ dependencies = [
"cosmwasm-std",
"envy",
"futures-util",
"hex",
"itertools 0.14.0",
"moka",
"nym-bin-common 0.6.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)",
@@ -1,44 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT\n date_utc as \"date_utc!\",\n SUM(total_stake) as \"total_stake!: i64\",\n SUM(packets_received) as \"total_packets_received!: i64\",\n SUM(packets_sent) as \"total_packets_sent!: i64\",\n SUM(packets_dropped) as \"total_packets_dropped!: i64\"\n FROM (\n SELECT\n date_utc,\n n.total_stake,\n n.packets_received,\n n.packets_sent,\n n.packets_dropped\n FROM nym_node_daily_mixing_stats n\n UNION ALL\n SELECT\n m.date_utc,\n m.total_stake,\n m.packets_received,\n m.packets_sent,\n m.packets_dropped\n FROM mixnode_daily_stats m\n LEFT JOIN nym_node_daily_mixing_stats ON m.mix_id = nym_node_daily_mixing_stats.node_id\n WHERE nym_node_daily_mixing_stats.node_id IS NULL\n )\n GROUP BY date_utc\n ORDER BY date_utc ASC\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "date_utc!",
"type_info": "Varchar"
},
{
"ordinal": 1,
"name": "total_stake!: i64",
"type_info": "Numeric"
},
{
"ordinal": 2,
"name": "total_packets_received!: i64",
"type_info": "Int8"
},
{
"ordinal": 3,
"name": "total_packets_sent!: i64",
"type_info": "Int8"
},
{
"ordinal": 4,
"name": "total_packets_dropped!: i64",
"type_info": "Int8"
}
],
"parameters": {
"Left": []
},
"nullable": [
null,
null,
null,
null,
null
]
},
"hash": "124d45b9604439584650f401607c46bdbd162c7c689f74fe9ddfdfd48f5ddc07"
}
@@ -0,0 +1,44 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT\n date_utc as \"date_utc!\",\n SUM(total_stake)::BIGINT as \"total_stake!: i64\",\n SUM(packets_received)::BIGINT as \"total_packets_received!: i64\",\n SUM(packets_sent)::BIGINT as \"total_packets_sent!: i64\",\n SUM(packets_dropped)::BIGINT as \"total_packets_dropped!: i64\"\n FROM (\n SELECT\n date_utc,\n n.total_stake,\n n.packets_received,\n n.packets_sent,\n n.packets_dropped\n FROM nym_node_daily_mixing_stats n\n UNION ALL\n SELECT\n m.date_utc,\n m.total_stake,\n m.packets_received,\n m.packets_sent,\n m.packets_dropped\n FROM mixnode_daily_stats m\n LEFT JOIN nym_node_daily_mixing_stats ON m.mix_id = nym_node_daily_mixing_stats.node_id\n WHERE nym_node_daily_mixing_stats.node_id IS NULL\n )\n GROUP BY date_utc\n ORDER BY date_utc ASC\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "date_utc!",
"type_info": "Varchar"
},
{
"ordinal": 1,
"name": "total_stake!: i64",
"type_info": "Int8"
},
{
"ordinal": 2,
"name": "total_packets_received!: i64",
"type_info": "Int8"
},
{
"ordinal": 3,
"name": "total_packets_sent!: i64",
"type_info": "Int8"
},
{
"ordinal": 4,
"name": "total_packets_dropped!: i64",
"type_info": "Int8"
}
],
"parameters": {
"Left": []
},
"nullable": [
null,
null,
null,
null,
null
]
},
"hash": "a19da2073bc691fe5cd2119a9527c16d6a8806ba50dcb759ac705b1a19288ca9"
}
@@ -3,7 +3,7 @@
[package]
name = "nym-node-status-api"
version = "3.2.2"
version = "3.2.6"
authors.workspace = true
repository.workspace = true
homepage.workspace = true
@@ -22,6 +22,7 @@ clap = { workspace = true, features = ["cargo", "derive", "env", "string"] }
cosmwasm-std = { workspace = true }
envy = { workspace = true }
futures-util = { workspace = true }
hex = { workspace = true }
itertools = { workspace = true }
moka = { workspace = true, features = ["future"] }
# TODO dz had to switch to cheddar versions because develop is ahead of current
@@ -101,6 +101,7 @@ pub(crate) async fn get_all_mixnodes(pool: &DbPool) -> anyhow::Result<Vec<Mixnod
Ok(items)
}
#[cfg(feature = "sqlite")]
pub(crate) async fn get_daily_stats(pool: &DbPool) -> anyhow::Result<Vec<DailyStats>> {
let mut conn = pool.acquire().await?;
let items = sqlx::query_as!(
@@ -142,6 +143,48 @@ pub(crate) async fn get_daily_stats(pool: &DbPool) -> anyhow::Result<Vec<DailySt
Ok(items)
}
#[cfg(feature = "pg")]
pub(crate) async fn get_daily_stats(pool: &DbPool) -> anyhow::Result<Vec<DailyStats>> {
let mut conn = pool.acquire().await?;
let items = sqlx::query_as!(
DailyStats,
r#"
SELECT
date_utc as "date_utc!",
SUM(total_stake)::BIGINT as "total_stake!: i64",
SUM(packets_received)::BIGINT as "total_packets_received!: i64",
SUM(packets_sent)::BIGINT as "total_packets_sent!: i64",
SUM(packets_dropped)::BIGINT as "total_packets_dropped!: i64"
FROM (
SELECT
date_utc,
n.total_stake,
n.packets_received,
n.packets_sent,
n.packets_dropped
FROM nym_node_daily_mixing_stats n
UNION ALL
SELECT
m.date_utc,
m.total_stake,
m.packets_received,
m.packets_sent,
m.packets_dropped
FROM mixnode_daily_stats m
LEFT JOIN nym_node_daily_mixing_stats ON m.mix_id = nym_node_daily_mixing_stats.node_id
WHERE nym_node_daily_mixing_stats.node_id IS NULL
)
GROUP BY date_utc
ORDER BY date_utc ASC
"#,
)
.fetch(&mut *conn)
.try_collect::<Vec<DailyStats>>()
.await?;
Ok(items)
}
pub(crate) async fn get_bonded_mix_ids(pool: &DbPool) -> anyhow::Result<HashSet<i64>> {
let mut conn = pool.acquire().await?;
let items = crate::db::query(
@@ -6,7 +6,7 @@ use tower_http::cors::CorsLayer;
use utoipa::OpenApi;
use utoipa_swagger_ui::SwaggerUi;
use crate::http::{server::HttpServer, state::AppState};
use crate::http::{middleware, server::HttpServer, state::AppState};
pub(crate) mod dvpn;
pub(crate) mod gateways;
@@ -63,17 +63,23 @@ impl RouterBuilder {
pub(crate) fn with_state(self, state: AppState) -> RouterWithState {
RouterWithState {
router: self.finalize_routes().with_state(state),
router: self.finalize_routes(state),
}
}
fn finalize_routes(self) -> Router<AppState> {
fn finalize_routes(self, state: AppState) -> Router {
// layers added later wrap earlier layers
self.unfinished_router
// Add response headers middleware
.layer(axum::middleware::from_fn_with_state(
state.clone(),
middleware::add_response_headers,
))
// CORS layer needs to wrap other API layers
.layer(setup_cors())
// logger should be outermost layer
.layer(axum::middleware::from_fn(log_request_debug))
.with_state(state)
}
}
@@ -127,12 +133,4 @@ mod tests {
let _test_router = unfinished_router;
}
#[test]
fn test_router_builder_finalize() {
let router_builder = RouterBuilder::with_default_routes();
let finalized = router_builder.finalize_routes();
// This tests that finalize_routes produces a valid Router
let _router = finalized;
}
}
@@ -265,7 +265,7 @@ fn authenticate(request: &impl VerifiableRequest, state: &AppState) -> HttpResul
Ok(())
}
static FRESHNESS_CUTOFF: time::Duration = time::Duration::minutes(2);
static FRESHNESS_CUTOFF: time::Duration = time::Duration::minutes(5);
fn is_fresh(request_time: &i64) -> HttpResult<()> {
// if a request took longer than N minutes to reach NS API, something is very wrong
@@ -0,0 +1,34 @@
use axum::{
extract::{Request, State},
http::{HeaderName, HeaderValue},
middleware::Next,
response::Response,
};
use time::OffsetDateTime;
use crate::http::state::AppState;
pub async fn add_response_headers(
State(state): State<AppState>,
request: Request,
next: Next,
) -> Response {
let mut response = next.run(request).await;
let headers = response.headers_mut();
// Add timestamp header (RFC3339 format)
let timestamp = OffsetDateTime::now_utc()
.format(&time::format_description::well_known::Rfc3339)
.unwrap_or_else(|_| "unknown".to_string());
if let Ok(value) = HeaderValue::from_str(&timestamp) {
headers.insert(HeaderName::from_static("x-response-timestamp"), value);
}
// Add instance ID header
if let Ok(value) = HeaderValue::from_str(state.instance_id()) {
headers.insert(HeaderName::from_static("x-instance-id"), value);
}
response
}
@@ -3,6 +3,7 @@ use utoipa::ToSchema;
pub(crate) mod api;
pub(crate) mod api_docs;
pub(crate) mod error;
pub(crate) mod middleware;
pub(crate) mod models;
pub(crate) mod server;
pub(crate) mod state;
@@ -34,6 +34,7 @@ pub(crate) struct AppState {
node_geocache: NodeGeoCache,
node_delegations: Arc<RwLock<DelegationsCache>>,
bin_info: BinaryInfo,
instance_id: String,
}
impl AppState {
@@ -53,6 +54,7 @@ impl AppState {
node_geocache,
node_delegations,
bin_info: BinaryInfo::new(),
instance_id: generate_instance_id(),
}
}
@@ -94,6 +96,10 @@ impl AppState {
pub(crate) fn build_information(&self) -> &BinaryBuildInformationOwned {
&self.bin_info.build_info
}
pub(crate) fn instance_id(&self) -> &str {
&self.instance_id
}
}
static GATEWAYS_LIST_KEY: &str = "gateways";
@@ -499,7 +505,10 @@ impl HttpCache {
None => {
let new_node_stats = crate::db::queries::get_daily_stats(db)
.await
.unwrap_or_default()
.unwrap_or_else(|e| {
tracing::error!("Failed to get daily stats from database: {}", e);
vec![]
})
.into_iter()
.rev()
.collect::<Vec<_>>();
@@ -698,3 +707,14 @@ impl BinaryInfo {
pub(crate) struct HealthInfo {
pub(crate) uptime: i64,
}
fn generate_instance_id() -> String {
use rand::Rng;
let mut rng = rand::thread_rng();
let random_bytes: [u8; 16] = rng.gen();
format!(
"ns-api-{}-{}",
hex::encode(&random_bytes[..8]),
std::process::id()
)
}