Skip to content

Commit

Permalink
QOL improvements
Browse files Browse the repository at this point in the history
- remove implicit panics via `as`
- safer DTO conversions
- add logging
- new config
  • Loading branch information
dynco-nym committed Sep 27, 2024
1 parent 64240ff commit d9b6a98
Show file tree
Hide file tree
Showing 13 changed files with 245 additions and 152 deletions.
11 changes: 4 additions & 7 deletions nym-node-status-api/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ axum = { workspace = true, features = ["tokio"] }
chrono = { workspace = true }
clap = { workspace = true, features = ["cargo", "derive"] }
cosmwasm-std = { workspace = true }
envy = { workspace = true }
futures-util = { workspace = true }
moka = { workspace = true, features = ["future"] }
nym-bin-common = { path = "../common/bin-common" }
Expand All @@ -26,21 +27,17 @@ nym-network-defaults = { path = "../common/network-defaults" }
nym-validator-client = { path = "../common/client-libs/validator-client" }
nym-task = { path = "../common/task" }
nym-node-requests = { path = "../nym-node/nym-node-requests", features = ["openapi"] }
reqwest = { workspace = true }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
serde_json_path = { workspace = true }
sqlx = { workspace = true, features = ["runtime-tokio-rustls", "sqlite"] }
thiserror = { workspace = true }
# TODO dz recosider features before merge
tokio = { workspace = true, features = [
"rt-multi-thread",
"macros",
"signal",
"time",
] }
tokio = { workspace = true, features = ["rt-multi-thread"] }
tokio-util = { workspace = true }
tracing = { workspace = true }
tracing-subscriber = { workspace = true, features = ["env-filter"] }
tracing-log = { workspace = true }
tower-http = { workspace = true, features = ["cors", "trace"] }
utoipa = { workspace = true, features = ["axum_extras", "time"] }
utoipa-swagger-ui = { workspace = true, features = ["axum"] }
Expand Down
60 changes: 3 additions & 57 deletions nym-node-status-api/launch_node_status_api.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,63 +2,9 @@

set -e

function usage() {
echo "Usage: $0 [-ci]"
echo " -c Clear DB and re-initialize it before launching the binary."
echo " -i Only initialize and prepare database, env vars then exit without"
echo " launching"
exit 0
}

function init_db() {
rm -rf data/*
# https://github.com/launchbadge/sqlx/blob/main/sqlx-cli/README.md
cargo sqlx database drop -y

cargo sqlx database create
cargo sqlx migrate run
cargo sqlx prepare

echo "Fresh database ready!"
}

# export DATABASE_URL as absolute path due to this
# https://github.com/launchbadge/sqlx/issues/3099
db_filename="nym-node-status-api.sqlite"
script_abs_path=$(realpath "$0")
package_dir=$(dirname "$script_abs_path")
db_abs_path="$package_dir/data/$db_filename"
dotenv_file="$package_dir/.env"
# echo "DATABASE_URL=sqlite://$db_abs_path" > "$dotenv_file"

export RUST_LOG=${RUST_LOG:-debug}

# export DATABASE_URL from .env file
set -a && source "$dotenv_file" && set +a

clear_db=false
init_only=false

while getopts "ci" opt; do
case ${opt} in
c)
clear_db=true
;;
i)
init_only=true
;;
\?)
usage
;;
esac
done

if [ "$clear_db" = true ] || [ "$init_only" = true ]; then
init_db
fi

if [ "$init_only" = true ]; then
exit 0
fi
export NYM_API_CLIENT_TIMEOUT=60;
export EXPLORER_CLIENT_TIMEOUT=60;

cargo run --package nym-node-status-api
cargo run --package nym-node-status-api --release -- --config-env-file ../envs/mainnet.env
68 changes: 53 additions & 15 deletions nym-node-status-api/src/config.rs
Original file line number Diff line number Diff line change
@@ -1,26 +1,42 @@
use anyhow::anyhow;
use reqwest::Url;
use serde::Deserialize;
use std::time::Duration;

#[derive(Debug)]
#[derive(Debug, Clone, Deserialize)]
pub(crate) struct Config {
#[serde(default = "Config::default_http_cache_seconds")]
nym_http_cache_ttl: u64,
#[serde(default = "Config::default_http_port")]
http_port: u16,
#[serde(rename = "nyxd")]
nyxd_addr: Url,
#[serde(default = "Config::default_client_timeout")]
#[serde(deserialize_with = "parse_duration")]
nym_api_client_timeout: Duration,
#[serde(default = "Config::default_client_timeout")]
#[serde(deserialize_with = "parse_duration")]
explorer_client_timeout: Duration,
}

const NYM_HTTP_CACHE_SECONDS_DEFAULT: u64 = 30;
const HTTP_PORT_DEFAULT: u16 = 8000;

impl Config {
pub(crate) fn from_env() -> Self {
Self {
nym_http_cache_ttl: read_env_var("NYM_HTTP_CACHE_SECONDS")
.unwrap_or(NYM_HTTP_CACHE_SECONDS_DEFAULT.to_string())
.parse()
.unwrap_or(NYM_HTTP_CACHE_SECONDS_DEFAULT),
http_port: read_env_var("HTTP_PORT")
.unwrap_or(HTTP_PORT_DEFAULT.to_string())
.parse()
.unwrap_or(HTTP_PORT_DEFAULT),
}
pub(crate) fn from_env() -> anyhow::Result<Self> {
envy::from_env::<Self>().map_err(|e| {
tracing::error!("Failed to load config from env: {e}");
anyhow::Error::from(e)
})
}

fn default_client_timeout() -> Duration {
Duration::from_secs(15)
}

fn default_http_port() -> u16 {
8000
}

fn default_http_cache_seconds() -> u64 {
30
}

pub(crate) fn nym_http_cache_ttl(&self) -> u64 {
Expand All @@ -30,6 +46,28 @@ impl Config {
pub(crate) fn http_port(&self) -> u16 {
self.http_port
}

pub(crate) fn nyxd_addr(&self) -> &Url {
&self.nyxd_addr
}

pub(crate) fn nym_api_client_timeout(&self) -> Duration {
self.nym_api_client_timeout.to_owned()
}

pub(crate) fn nym_explorer_client_timeout(&self) -> Duration {
self.explorer_client_timeout.to_owned()
}
}


fn parse_duration<'de, D>(deserializer: D) -> Result<Duration, D::Error>
where
D: serde::Deserializer<'de>,
{
let s: String = Deserialize::deserialize(deserializer)?;
let secs: u64 = s.parse().map_err(serde::de::Error::custom)?;
Ok(Duration::from_secs(secs))
}

pub(super) fn read_env_var(env_var: &str) -> anyhow::Result<String> {
Expand Down
50 changes: 30 additions & 20 deletions nym-node-status-api/src/db/models.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use crate::http::{self, models::SummaryHistory};
use crate::{
http::{self, models::SummaryHistory},
monitor::NumericalCheckedCast,
};
use nym_node_requests::api::v1::node::models::NodeDescription;
use serde::{Deserialize, Serialize};
use utoipa::ToSchema;
Expand Down Expand Up @@ -31,16 +34,20 @@ pub(crate) struct GatewayDto {
pub(crate) website: String,
}

impl From<GatewayDto> for http::models::Gateway {
fn from(value: GatewayDto) -> Self {
impl TryFrom<GatewayDto> for http::models::Gateway {
type Error = anyhow::Error;

fn try_from(value: GatewayDto) -> Result<Self, Self::Error> {
// Instead of using routing_score_successes / routing_score_samples, we use the
// number of successful testruns in the last 24h.
let routing_score = 0f32;
let config_score = 0u32;
let last_updated_utc = timestamp_as_utc(value.last_updated_utc as u64).to_rfc3339();
let last_updated_utc =
timestamp_as_utc(value.last_updated_utc.cast_checked()?).to_rfc3339();
let last_testrun_utc = value
.last_testrun_utc
.map(|t| timestamp_as_utc(t as u64).to_rfc3339());
.and_then(|i| i.cast_checked().ok())
.map(|t| timestamp_as_utc(t).to_rfc3339());

let self_described = value.self_described.clone().unwrap_or("null".to_string());
let explorer_pretty_bond = value
Expand Down Expand Up @@ -68,7 +75,7 @@ impl From<GatewayDto> for http::models::Gateway {
details: value.details.clone(),
};

http::models::Gateway {
Ok(http::models::Gateway {
gateway_identity_key: value.gateway_identity_key.clone(),
bonded,
blacklisted,
Expand All @@ -82,7 +89,7 @@ impl From<GatewayDto> for http::models::Gateway {
config_score,
last_testrun_utc,
last_updated_utc,
}
})
}
}

Expand Down Expand Up @@ -121,10 +128,11 @@ pub(crate) struct MixnodeDto {
pub(crate) details: String,
}

impl From<MixnodeDto> for http::models::Mixnode {
fn from(value: MixnodeDto) -> Self {
// TODO dz avoid as?
let mix_id = value.mix_id as u32;
impl TryFrom<MixnodeDto> for http::models::Mixnode {
type Error = anyhow::Error;

fn try_from(value: MixnodeDto) -> Result<Self, Self::Error> {
let mix_id = value.mix_id.cast_checked()?;
let full_details = value.full_details.clone();
let full_details = serde_json::from_str(&full_details).unwrap_or(None);

Expand All @@ -133,15 +141,16 @@ impl From<MixnodeDto> for http::models::Mixnode {
.clone()
.map(|v| serde_json::from_str(&v).unwrap_or(serde_json::Value::Null));

let last_updated_utc = timestamp_as_utc(value.last_updated_utc as u64).to_rfc3339();
let last_updated_utc =
timestamp_as_utc(value.last_updated_utc.cast_checked()?).to_rfc3339();
let blacklisted = value.blacklisted;
let is_dp_delegatee = value.is_dp_delegatee;
let moniker = value.moniker.clone();
let website = value.website.clone();
let security_contact = value.security_contact.clone();
let details = value.details.clone();

http::models::Mixnode {
Ok(http::models::Mixnode {
mix_id,
bonded: value.bonded,
blacklisted,
Expand All @@ -156,7 +165,7 @@ impl From<MixnodeDto> for http::models::Mixnode {
},
self_described,
last_updated_utc,
}
})
}
}

Expand Down Expand Up @@ -185,15 +194,16 @@ pub(crate) struct SummaryHistoryDto {
pub timestamp_utc: i64,
}

impl From<SummaryHistoryDto> for SummaryHistory {
fn from(value: SummaryHistoryDto) -> Self {
impl TryFrom<SummaryHistoryDto> for SummaryHistory {
type Error = anyhow::Error;

fn try_from(value: SummaryHistoryDto) -> Result<Self, Self::Error> {
let value_json = serde_json::from_str(&value.value_json).unwrap_or_default();
SummaryHistory {
Ok(SummaryHistory {
value_json,
date: value.date.clone(),
// TODO dz avoid as?
timestamp_utc: timestamp_as_utc(value.timestamp_utc as u64).to_rfc3339(),
}
timestamp_utc: timestamp_as_utc(value.timestamp_utc.cast_checked()?).to_rfc3339(),
})
}
}

Expand Down
10 changes: 9 additions & 1 deletion nym-node-status-api/src/db/queries/gateways.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use crate::{
};
use futures_util::TryStreamExt;
use nym_validator_client::models::DescribedGateway;
use tracing::error;

pub(crate) async fn insert_gateways(
pool: &DbPool,
Expand Down Expand Up @@ -146,7 +147,14 @@ pub(crate) async fn get_all_gateways(pool: &DbPool) -> anyhow::Result<Vec<Gatewa
.try_collect::<Vec<_>>()
.await?;

let items: Vec<Gateway> = items.into_iter().map(|item| item.into()).collect();
let items: Vec<Gateway> = items
.into_iter()
.map(|item| item.try_into())
.collect::<anyhow::Result<Vec<_>>>()
.map_err(|e| {
error!("Conversion from DTO failed: {e}. Invalidly stored data?");
e
})?;
tracing::trace!("Fetched {} gateways from DB", items.len());
Ok(items)
}
10 changes: 9 additions & 1 deletion nym-node-status-api/src/db/queries/mixnodes.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use futures_util::TryStreamExt;
use nym_validator_client::models::MixNodeBondAnnotated;
use tracing::error;

use crate::{
db::{
Expand Down Expand Up @@ -74,7 +75,14 @@ pub(crate) async fn get_all_mixnodes(pool: &DbPool) -> anyhow::Result<Vec<Mixnod
.try_collect::<Vec<_>>()
.await?;

let items = items.into_iter().map(|item| item.into()).collect();
let items = items
.into_iter()
.map(|item| item.try_into())
.collect::<anyhow::Result<Vec<_>>>()
.map_err(|e| {
error!("Conversion from DTO failed: {e}. Invalidly stored data?");
e
})?;
Ok(items)
}

Expand Down
10 changes: 9 additions & 1 deletion nym-node-status-api/src/db/queries/summary.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use chrono::{DateTime, Utc};
use futures_util::TryStreamExt;
use std::collections::HashMap;
use tracing::error;

use crate::{
db::{
Expand Down Expand Up @@ -40,7 +41,14 @@ pub(crate) async fn get_summary_history(pool: &DbPool) -> anyhow::Result<Vec<Sum
.try_collect::<Vec<_>>()
.await?;

let items = items.into_iter().map(|item| item.into()).collect();
let items = items
.into_iter()
.map(|item| item.try_into())
.collect::<anyhow::Result<Vec<_>>>()
.map_err(|e| {
error!("Conversion from DTO failed: {e}. Invalidly stored data?");
e
})?;
Ok(items)
}

Expand Down
Loading

0 comments on commit d9b6a98

Please sign in to comment.