Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cdd9ec3622 | |||
| fbcf44eeb9 | |||
| f4785099c2 | |||
| b04d3ba376 | |||
| b2dfdda210 | |||
| 41ef3a26f5 | |||
| bae1b488de | |||
| 40cf2c441a | |||
| 34871b14b3 | |||
| c14b010f9e |
@@ -59,3 +59,6 @@ nym-api/redocly/formatted-openapi.json
|
||||
|
||||
*.sqlite
|
||||
.build
|
||||
|
||||
**/settings.sql
|
||||
**/enter_db.sh
|
||||
|
||||
Generated
+9
-6
@@ -1211,9 +1211,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.36"
|
||||
version = "4.5.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2df961d8c8a0d08aa9945718ccf584145eee3f3aa06cddbeac12933781102e04"
|
||||
checksum = "eccb054f56cbd38340b380d4a8e69ef1f02f1af43db2f0cc817a4774d80ae071"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
@@ -1221,9 +1221,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.36"
|
||||
version = "4.5.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "132dbda40fb6753878316a489d5a1242a8ef2f0d9e47ba01c951ea8aa7d013a5"
|
||||
checksum = "efd9466fac8543255d3b1fcad4762c5e116ffe808c8a3043d4263cd4fd4862a2"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
@@ -4733,7 +4733,7 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
|
||||
|
||||
[[package]]
|
||||
name = "nym-api"
|
||||
version = "1.1.56"
|
||||
version = "1.1.55"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -4760,6 +4760,7 @@ dependencies = [
|
||||
"humantime-serde",
|
||||
"itertools 0.14.0",
|
||||
"k256",
|
||||
"moka",
|
||||
"nym-api-requests",
|
||||
"nym-bandwidth-controller",
|
||||
"nym-bin-common",
|
||||
@@ -6252,7 +6253,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-node-status-api"
|
||||
version = "2.1.0"
|
||||
version = "2.3.0"
|
||||
dependencies = [
|
||||
"ammonia",
|
||||
"anyhow",
|
||||
@@ -6269,6 +6270,8 @@ dependencies = [
|
||||
"nym-contracts-common",
|
||||
"nym-crypto",
|
||||
"nym-http-api-client",
|
||||
"nym-http-api-common",
|
||||
"nym-mixnet-contract-common",
|
||||
"nym-network-defaults",
|
||||
"nym-node-metrics",
|
||||
"nym-node-requests",
|
||||
|
||||
+1
-1
@@ -211,7 +211,7 @@ chacha20 = "0.9.0"
|
||||
chacha20poly1305 = "0.10.1"
|
||||
chrono = "0.4.40"
|
||||
cipher = "0.4.3"
|
||||
clap = "4.5.36"
|
||||
clap = "4.5.37"
|
||||
clap_complete = "4.5"
|
||||
clap_complete_fig = "4.5"
|
||||
colored = "2.2"
|
||||
|
||||
@@ -9,13 +9,35 @@ use axum::response::IntoResponse;
|
||||
use axum_client_ip::InsecureClientIp;
|
||||
use colored::Colorize;
|
||||
use std::time::Instant;
|
||||
use tracing::info;
|
||||
use tracing::{debug, info};
|
||||
|
||||
enum LogLevel {
|
||||
Debug,
|
||||
Info,
|
||||
}
|
||||
|
||||
pub async fn log_request_info(
|
||||
insecure_client_ip: InsecureClientIp,
|
||||
request: Request,
|
||||
next: Next,
|
||||
) -> impl IntoResponse {
|
||||
log_request(insecure_client_ip, request, next, LogLevel::Info).await
|
||||
}
|
||||
|
||||
pub async fn log_request_debug(
|
||||
insecure_client_ip: InsecureClientIp,
|
||||
request: Request,
|
||||
next: Next,
|
||||
) -> impl IntoResponse {
|
||||
log_request(insecure_client_ip, request, next, LogLevel::Debug).await
|
||||
}
|
||||
|
||||
/// Simple logger for requests
|
||||
pub async fn logger(
|
||||
async fn log_request(
|
||||
InsecureClientIp(addr): InsecureClientIp,
|
||||
request: Request,
|
||||
next: Next,
|
||||
level: LogLevel,
|
||||
) -> impl IntoResponse {
|
||||
// TODO dz use `OriginalUri` extractor to get full URI even for nested
|
||||
// routers if routes aren't logged correctly in handlers
|
||||
@@ -58,7 +80,14 @@ pub async fn logger(
|
||||
|
||||
let agent_str = "agent".bold();
|
||||
|
||||
info!("[{addr} -> {host}] {method} '{uri}': {print_status} {time_taken} {agent_str}: {agent}");
|
||||
match level {
|
||||
LogLevel::Debug => debug!(
|
||||
"[{addr} -> {host}] {method} '{uri}': {print_status} {time_taken} {agent_str}: {agent}"
|
||||
),
|
||||
LogLevel::Info => info!(
|
||||
"[{addr} -> {host}] {method} '{uri}': {print_status} {time_taken} {agent_str}: {agent}"
|
||||
),
|
||||
}
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ use nym_sphinx_addressing::nodes::{
|
||||
};
|
||||
use nym_sphinx_params::packet_sizes::PacketSize;
|
||||
use nym_sphinx_params::{PacketType, ReplySurbKeyDigestAlgorithm};
|
||||
use nym_sphinx_types::constants::PAYLOAD_KEY_SEED_SIZE;
|
||||
use nym_sphinx_types::{
|
||||
NymPacket, SURBMaterial, SphinxError, HEADER_SIZE, NODE_ADDRESS_LENGTH, SURB,
|
||||
X25519_WITH_EXPLICIT_PAYLOAD_KEYS_VERSION,
|
||||
@@ -127,12 +126,6 @@ impl ReplySurb {
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the expected number of bytes the [`ReplySURB`] will take after serialization using the new encoding format.
|
||||
/// Useful for deserialization from a bytes stream.
|
||||
pub fn v2_serialised_len(num_hops: u8) -> usize {
|
||||
Self::BASE_OVERHEAD + num_hops as usize * PAYLOAD_KEY_SEED_SIZE
|
||||
}
|
||||
|
||||
pub fn encryption_key(&self) -> &SurbEncryptionKey {
|
||||
&self.encryption_key
|
||||
}
|
||||
|
||||
@@ -32,24 +32,28 @@ fn v2_reply_surbs_serialised_len(surbs: &[ReplySurb]) -> usize {
|
||||
}
|
||||
}
|
||||
|
||||
// when serialising surbs are always prepended with u16-encoded count an u8-encoded number of hops
|
||||
3 + num_surbs * v2_reply_surb_serialised_len(num_hops)
|
||||
// when serialising surbs are always prepended with:
|
||||
// - u16-encoded count,
|
||||
// - u8-encoded number of hops
|
||||
// - u8 reserved value
|
||||
4 + num_surbs * v2_reply_surb_serialised_len(num_hops)
|
||||
}
|
||||
|
||||
// NUM_SURBS (u16) || HOPS (u8) || SURB_DATA
|
||||
// NUM_SURBS (u16) || HOPS (u8) || RESERVED (u8) || SURB_DATA
|
||||
fn recover_reply_surbs_v2(
|
||||
bytes: &[u8],
|
||||
) -> Result<(Vec<ReplySurb>, usize), InvalidReplyRequestError> {
|
||||
if bytes.len() < 2 {
|
||||
if bytes.len() < 4 {
|
||||
return Err(InvalidReplyRequestError::RequestTooShortToDeserialize);
|
||||
}
|
||||
|
||||
// we're not attaching more than 65k surbs...
|
||||
let num_surbs = u16::from_be_bytes([bytes[0], bytes[1]]);
|
||||
let num_hops = bytes[2];
|
||||
let mut consumed = 3;
|
||||
let _reserved = bytes[3];
|
||||
let mut consumed = 4;
|
||||
|
||||
let surb_size = ReplySurb::v2_serialised_len(num_hops);
|
||||
let surb_size = v2_reply_surb_serialised_len(num_hops);
|
||||
if bytes[consumed..].len() < num_surbs as usize * surb_size {
|
||||
return Err(InvalidReplyRequestError::RequestTooShortToDeserialize);
|
||||
}
|
||||
@@ -69,11 +73,13 @@ fn recover_reply_surbs_v2(
|
||||
fn reply_surbs_bytes_v2(reply_surbs: &[ReplySurb]) -> impl Iterator<Item = u8> + use<'_> {
|
||||
let num_surbs = reply_surbs.len() as u16;
|
||||
let num_hops = reply_surbs_hops(reply_surbs);
|
||||
let reserved = 0;
|
||||
|
||||
num_surbs
|
||||
.to_be_bytes()
|
||||
.into_iter()
|
||||
.chain(once(num_hops))
|
||||
.chain(once(reserved))
|
||||
.chain(reply_surbs.iter().flat_map(|surb| surb.to_bytes()))
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ const config: DocsThemeConfig = {
|
||||
bookDescriptions[topLevel] ||
|
||||
defaultDescription;
|
||||
|
||||
const title = config.title + (route === "/" ? "" : " - Nym docs");
|
||||
const title = (route === "/" ? "Nym docs" : config.title + " - Nym docs");
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -27,6 +27,7 @@ humantime-serde = { workspace = true }
|
||||
k256 = { workspace = true, features = [
|
||||
"ecdsa-core",
|
||||
] } # needed for the Verifier trait; pull whatever version is used by other dependencies
|
||||
moka = { workspace = true }
|
||||
pin-project = { workspace = true }
|
||||
rand = { workspace = true }
|
||||
rand_chacha = { workspace = true }
|
||||
@@ -128,6 +129,7 @@ v2-performance = []
|
||||
generate-ts = ["ts-rs"]
|
||||
|
||||
[build-dependencies]
|
||||
anyhow = { workspace = true }
|
||||
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
|
||||
sqlx = { workspace = true, features = [
|
||||
"runtime-tokio-rustls",
|
||||
|
||||
+37
-1
@@ -1,13 +1,20 @@
|
||||
use sqlx::{Connection, FromRow, SqliteConnection};
|
||||
use std::env;
|
||||
|
||||
const SQLITE_DB_FILENAME: &str = "nym-api-example.sqlite";
|
||||
|
||||
// it's fine if compilation fails
|
||||
#[allow(clippy::unwrap_used)]
|
||||
#[allow(clippy::expect_used)]
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let out_dir = env::var("OUT_DIR").unwrap();
|
||||
let database_path = format!("{}/nym-api-example.sqlite", out_dir);
|
||||
let database_path = format!("{}/{}", out_dir, SQLITE_DB_FILENAME);
|
||||
|
||||
#[cfg(target_family = "unix")]
|
||||
write_db_path_to_file(&out_dir, SQLITE_DB_FILENAME)
|
||||
.await
|
||||
.ok();
|
||||
|
||||
let mut conn = SqliteConnection::connect(&format!("sqlite://{}?mode=rwc", database_path))
|
||||
.await
|
||||
@@ -62,3 +69,32 @@ async fn main() {
|
||||
// not a valid windows path... but hey, it works...
|
||||
println!("cargo:rustc-env=DATABASE_URL=sqlite:///{}", &database_path);
|
||||
}
|
||||
|
||||
/// use `./enter_db.sh` to inspect DB
|
||||
#[cfg(target_family = "unix")]
|
||||
async fn write_db_path_to_file(out_dir: &str, db_filename: &str) -> anyhow::Result<()> {
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
use tokio::{fs::File, io::AsyncWriteExt};
|
||||
|
||||
if env::var("CI").is_ok() {
|
||||
return Ok(());
|
||||
}
|
||||
let mut file = File::create("settings.sql").await?;
|
||||
let settings = ".mode columns
|
||||
.headers on";
|
||||
file.write_all(settings.as_bytes()).await?;
|
||||
|
||||
let mut file = File::create("enter_db.sh").await?;
|
||||
let contents = format!(
|
||||
"#!/bin/sh\n\
|
||||
sqlite3 -init settings.sql {}/{}",
|
||||
out_dir, db_filename,
|
||||
);
|
||||
file.write_all(contents.as_bytes()).await?;
|
||||
|
||||
file.set_permissions(std::fs::Permissions::from_mode(0o755))
|
||||
.await
|
||||
.map_err(anyhow::Error::from)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ use crate::support::config;
|
||||
use crate::support::http::state::{AppState, ChainStatusCache, ForcedRefresh};
|
||||
use crate::support::nyxd::Client;
|
||||
use crate::support::storage::NymApiStorage;
|
||||
use crate::unstable_routes::account::cache::AddressInfoCache;
|
||||
use async_trait::async_trait;
|
||||
use axum::Router;
|
||||
use axum_test::http::StatusCode;
|
||||
@@ -1274,6 +1275,7 @@ impl TestFixture {
|
||||
AppState {
|
||||
nyxd_client,
|
||||
chain_status_cache: ChainStatusCache::new(Duration::from_secs(42)),
|
||||
address_info_cache: AddressInfoCache::new(),
|
||||
forced_refresh: ForcedRefresh::new(true),
|
||||
nym_contract_cache: NymContractCache::new(),
|
||||
node_status_cache: NodeStatusCache::new(),
|
||||
|
||||
@@ -6,7 +6,9 @@ use crate::epoch_operations::helpers::stake_to_f64;
|
||||
use crate::EpochAdvancer;
|
||||
use cosmwasm_std::Decimal;
|
||||
use nym_mixnet_contract_common::reward_params::{Performance, RewardedSetParams};
|
||||
use nym_mixnet_contract_common::{EpochState, NodeId, NymNodeDetails, RewardedSet};
|
||||
use nym_mixnet_contract_common::{
|
||||
EpochState, NodeId, NymNodeDetails, RewardedSet, RewardingParams,
|
||||
};
|
||||
use rand::prelude::SliceRandom;
|
||||
use rand::rngs::OsRng;
|
||||
use std::collections::HashSet;
|
||||
@@ -25,14 +27,14 @@ enum AvailableRole {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct NodeWithStakeAndPerformance {
|
||||
struct NodeWithSaturationAndPerformance {
|
||||
node_id: NodeId,
|
||||
available_roles: Vec<AvailableRole>,
|
||||
total_stake: Decimal,
|
||||
saturation: Decimal,
|
||||
performance: Performance,
|
||||
}
|
||||
|
||||
impl NodeWithStakeAndPerformance {
|
||||
impl NodeWithSaturationAndPerformance {
|
||||
fn to_selection_weight(&self) -> f64 {
|
||||
let scaled_performance = match self.performance.checked_pow(20) {
|
||||
Ok(perf) => perf,
|
||||
@@ -42,7 +44,7 @@ impl NodeWithStakeAndPerformance {
|
||||
}
|
||||
};
|
||||
|
||||
let scaled_stake = self.total_stake * scaled_performance;
|
||||
let scaled_stake = self.saturation * scaled_performance;
|
||||
stake_to_f64(scaled_stake)
|
||||
}
|
||||
|
||||
@@ -62,7 +64,7 @@ impl NodeWithStakeAndPerformance {
|
||||
impl EpochAdvancer {
|
||||
fn determine_rewarded_set(
|
||||
&self,
|
||||
nodes: Vec<NodeWithStakeAndPerformance>,
|
||||
nodes: Vec<NodeWithSaturationAndPerformance>,
|
||||
spec: RewardedSetParams,
|
||||
) -> Result<RewardedSet, RewardingError> {
|
||||
if nodes.is_empty() {
|
||||
@@ -204,7 +206,8 @@ impl EpochAdvancer {
|
||||
async fn attach_performance_to_eligible_nodes(
|
||||
&self,
|
||||
nym_nodes: &[NymNodeDetails],
|
||||
) -> Vec<NodeWithStakeAndPerformance> {
|
||||
reward_params: &RewardingParams,
|
||||
) -> Vec<NodeWithSaturationAndPerformance> {
|
||||
let mut with_performance = Vec::new();
|
||||
|
||||
// SAFETY: the cache MUST HAVE been initialised before now
|
||||
@@ -218,7 +221,7 @@ impl EpochAdvancer {
|
||||
|
||||
for nym_node in nym_nodes {
|
||||
let node_id = nym_node.node_id();
|
||||
let total_stake = nym_node.total_stake();
|
||||
let saturation = nym_node.rewarding_details.bond_saturation(reward_params);
|
||||
|
||||
let Some(self_described) = described_cache.get_description(&node_id) else {
|
||||
continue;
|
||||
@@ -230,7 +233,7 @@ impl EpochAdvancer {
|
||||
};
|
||||
|
||||
let performance = annotation.detailed_performance.to_rewarding_performance();
|
||||
debug!("nym-node {node_id}: stake: {total_stake}, performance: {performance}");
|
||||
debug!("nym-node {node_id}: saturation: {saturation}, performance: {performance}");
|
||||
|
||||
let mut available_roles = Vec::new();
|
||||
if self_described.declared_role.mixnode {
|
||||
@@ -248,10 +251,10 @@ impl EpochAdvancer {
|
||||
continue;
|
||||
}
|
||||
|
||||
with_performance.push(NodeWithStakeAndPerformance {
|
||||
with_performance.push(NodeWithSaturationAndPerformance {
|
||||
node_id: nym_node.node_id(),
|
||||
available_roles,
|
||||
total_stake,
|
||||
saturation,
|
||||
performance,
|
||||
})
|
||||
}
|
||||
@@ -273,13 +276,8 @@ impl EpochAdvancer {
|
||||
}
|
||||
|
||||
info!("attempting to assign the rewarded set for the upcoming epoch...");
|
||||
let nodes_with_performance =
|
||||
self.attach_performance_to_eligible_nodes(nym_nodes).await;
|
||||
|
||||
if let Err(err) = self
|
||||
._update_rewarded_set_and_advance_epoch(nodes_with_performance)
|
||||
.await
|
||||
{
|
||||
if let Err(err) = self._update_rewarded_set_and_advance_epoch(nym_nodes).await {
|
||||
error!("FAILED to assign the rewarded set... - {err}");
|
||||
Err(err)
|
||||
} else {
|
||||
@@ -300,15 +298,19 @@ impl EpochAdvancer {
|
||||
|
||||
async fn _update_rewarded_set_and_advance_epoch(
|
||||
&self,
|
||||
all_nodes: Vec<NodeWithStakeAndPerformance>,
|
||||
nym_nodes: &[NymNodeDetails],
|
||||
) -> Result<(), RewardingError> {
|
||||
// we grab rewarding parameters here as they might have gotten updated when performing epoch actions
|
||||
let rewarding_parameters = self.nyxd_client.get_current_rewarding_parameters().await?;
|
||||
|
||||
debug!("Rewarding parameters: {rewarding_parameters:?}");
|
||||
|
||||
let nodes_with_performance = self
|
||||
.attach_performance_to_eligible_nodes(nym_nodes, &rewarding_parameters)
|
||||
.await;
|
||||
|
||||
let new_rewarded_set =
|
||||
self.determine_rewarded_set(all_nodes, rewarding_parameters.rewarded_set)?;
|
||||
self.determine_rewarded_set(nodes_with_performance, rewarding_parameters.rewarded_set)?;
|
||||
|
||||
debug!("New rewarded set: {:?}", new_rewarded_set);
|
||||
|
||||
|
||||
+1
-1
@@ -26,8 +26,8 @@ pub(crate) mod nym_contract_cache;
|
||||
pub(crate) mod nym_nodes;
|
||||
mod status;
|
||||
pub(crate) mod support;
|
||||
mod unstable_routes;
|
||||
|
||||
// TODO rocket: remove all such Todos once rocket is phased out completely
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), anyhow::Error> {
|
||||
cfg_if::cfg_if! {if #[cfg(feature = "console-subscriber")] {
|
||||
|
||||
@@ -327,7 +327,6 @@ pub(crate) type AxumResult<T> = Result<T, AxumErrorResponse>;
|
||||
// #[schema(title = "ErrorResponse")]
|
||||
pub(crate) struct AxumErrorResponse {
|
||||
message: RequestError,
|
||||
// #[schema(value_type = u16)]
|
||||
status: StatusCode,
|
||||
}
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ pub(crate) mod semi_skimmed;
|
||||
pub(crate) mod skimmed;
|
||||
|
||||
#[allow(deprecated)]
|
||||
pub(crate) fn nym_node_routes_unstable() -> Router<AppState> {
|
||||
pub(crate) fn routes() -> Router<AppState> {
|
||||
Router::new()
|
||||
.nest(
|
||||
"/skimmed",
|
||||
|
||||
@@ -27,6 +27,7 @@ use crate::support::http::RouterBuilder;
|
||||
use crate::support::nyxd;
|
||||
use crate::support::storage::runtime_migrations::m001_directory_services_v2_1::migrate_to_directory_services_v2_1;
|
||||
use crate::support::storage::NymApiStorage;
|
||||
use crate::unstable_routes::account::cache::AddressInfoCache;
|
||||
use crate::{
|
||||
circulating_supply_api, ecash, epoch_operations, network_monitor, node_describe_cache,
|
||||
node_status_api, nym_contract_cache,
|
||||
@@ -193,6 +194,7 @@ async fn start_nym_api_tasks_axum(config: &Config) -> anyhow::Result<ShutdownHan
|
||||
let router = router.with_state(AppState {
|
||||
nyxd_client: nyxd_client.clone(),
|
||||
chain_status_cache: ChainStatusCache::new(DEFAULT_CHAIN_STATUS_CACHE_TTL),
|
||||
address_info_cache: AddressInfoCache::new(),
|
||||
forced_refresh: ForcedRefresh::new(
|
||||
config.topology_cacher.debug.node_describe_allow_illegal_ips,
|
||||
),
|
||||
|
||||
@@ -5,116 +5,6 @@ pub(crate) mod helpers;
|
||||
pub(crate) mod openapi;
|
||||
pub(crate) mod router;
|
||||
pub(crate) mod state;
|
||||
mod unstable_routes;
|
||||
|
||||
pub(crate) use router::RouterBuilder;
|
||||
|
||||
// pub(crate) async fn setup_rocket(
|
||||
// config: &Config,
|
||||
// network_details: NetworkDetails,
|
||||
// nyxd_client: nyxd::Client,
|
||||
// identity_keypair: identity::KeyPair,
|
||||
// coconut_keypair: ecash::keys::KeyPair,
|
||||
// storage: NymApiStorage,
|
||||
// ) -> anyhow::Result<Rocket<Ignite>> {
|
||||
// let openapi_settings = rocket_okapi::settings::OpenApiSettings::default();
|
||||
// let mut rocket = rocket::build();
|
||||
//
|
||||
// let mix_denom = network_details.network.chain_details.mix_denom.base.clone();
|
||||
//
|
||||
// mount_endpoints_and_merged_docs! {
|
||||
// rocket,
|
||||
// "/v1".to_owned(),
|
||||
// openapi_settings,
|
||||
// "/" => (vec![], openapi::custom_openapi_spec()),
|
||||
// "" => circulating_supply_api::circulating_supply_routes(&openapi_settings),
|
||||
// "" => nym_contract_cache::nym_contract_cache_routes(&openapi_settings),
|
||||
// "/status" => node_status_api::node_status_routes(&openapi_settings, config.network_monitor.enabled),
|
||||
// "/network" => network_routes(&openapi_settings),
|
||||
// "/api-status" => api_status_routes(&openapi_settings),
|
||||
// "/ecash" => ecash::routes_open_api(&openapi_settings, config.ecash_signer.enabled),
|
||||
// "" => nym_node_routes_deprecated(&openapi_settings),
|
||||
//
|
||||
// // => when we move those routes, we'll need to add a redirection for backwards compatibility
|
||||
// "/unstable/nym-nodes" => nym_node_routes_next(&openapi_settings)
|
||||
// }
|
||||
//
|
||||
// let rocket = rocket
|
||||
// .manage(network_details)
|
||||
// .manage(SharedCache::<DescribedNodes>::new())
|
||||
// .mount("/swagger", make_swagger_ui(&openapi::get_docs()))
|
||||
// .attach(setup_rocket_cors()?)
|
||||
// .attach(NymContractCache::stage())
|
||||
// .attach(NodeStatusCache::stage())
|
||||
// .attach(CirculatingSupplyCache::stage(mix_denom.clone()))
|
||||
// .manage(unstable::NodeInfoCache::default())
|
||||
// .manage(storage.clone());
|
||||
//
|
||||
// let mut status_state = ApiStatusState::new();
|
||||
//
|
||||
// let rocket = if config.ecash_signer.enabled {
|
||||
// // make sure we have some tokens to cover multisig fees
|
||||
// let balance = nyxd_client.balance(&mix_denom).await?;
|
||||
// if balance.amount < ecash::MINIMUM_BALANCE {
|
||||
// let address = nyxd_client.address().await;
|
||||
// let min = Coin::new(ecash::MINIMUM_BALANCE, mix_denom);
|
||||
// bail!("the account ({address}) doesn't have enough funds to cover verification fees. it has {balance} while it needs at least {min}")
|
||||
// }
|
||||
//
|
||||
// let cosmos_address = nyxd_client.address().await.to_string();
|
||||
// let announce_address = config
|
||||
// .ecash_signer
|
||||
// .announce_address
|
||||
// .clone()
|
||||
// .map(|u| u.to_string())
|
||||
// .unwrap_or_default();
|
||||
// status_state.add_zk_nym_signer(SignerState {
|
||||
// cosmos_address,
|
||||
// identity: identity_keypair.public_key().to_base58_string(),
|
||||
// announce_address,
|
||||
// ecash_keypair: coconut_keypair.clone(),
|
||||
// });
|
||||
//
|
||||
// let ecash_contract = nyxd_client
|
||||
// .get_ecash_contract_address()
|
||||
// .await
|
||||
// .context("e-cash contract address is required to setup the zk-nym signer")?;
|
||||
//
|
||||
// let comm_channel = QueryCommunicationChannel::new(nyxd_client.clone());
|
||||
//
|
||||
// let ecash_state = EcashState::new(
|
||||
// ecash_contract,
|
||||
// nyxd_client.clone(),
|
||||
// identity_keypair,
|
||||
// coconut_keypair,
|
||||
// comm_channel,
|
||||
// storage.clone(),
|
||||
// )
|
||||
// .await?;
|
||||
//
|
||||
// rocket.manage(ecash_state)
|
||||
// } else {
|
||||
// rocket
|
||||
// };
|
||||
//
|
||||
// Ok(rocket.manage(status_state).ignite().await?)
|
||||
// }
|
||||
//
|
||||
// fn setup_rocket_cors() -> Result<Cors> {
|
||||
// let allowed_origins = AllowedOrigins::all();
|
||||
//
|
||||
// // You can also deserialize this
|
||||
// let cors = rocket_cors::CorsOptions {
|
||||
// allowed_origins,
|
||||
// allowed_methods: vec![rocket::http::Method::Post, rocket::http::Method::Get]
|
||||
// .into_iter()
|
||||
// .map(From::from)
|
||||
// .collect(),
|
||||
// allowed_headers: AllowedHeaders::all(),
|
||||
// allow_credentials: true,
|
||||
// ..Default::default()
|
||||
// }
|
||||
// .to_cors()?;
|
||||
//
|
||||
// Ok(cors)
|
||||
// }
|
||||
use crate::unstable_routes;
|
||||
|
||||
@@ -17,7 +17,7 @@ use axum::response::Redirect;
|
||||
use axum::routing::get;
|
||||
use axum::Router;
|
||||
use core::net::SocketAddr;
|
||||
use nym_http_api_common::middleware::logging::logger;
|
||||
use nym_http_api_common::middleware::logging::log_request_info;
|
||||
use tokio::net::TcpListener;
|
||||
use tokio_util::sync::WaitForCancellationFutureOwned;
|
||||
use tower_http::cors::CorsLayer;
|
||||
@@ -91,7 +91,7 @@ impl RouterBuilder {
|
||||
fn finalize_routes(self) -> Router<AppState> {
|
||||
self.unfinished_router
|
||||
.layer(setup_cors())
|
||||
.layer(axum::middleware::from_fn(logger))
|
||||
.layer(axum::middleware::from_fn(log_request_info))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,8 @@ use crate::support::caching::cache::SharedCache;
|
||||
use crate::support::caching::Cache;
|
||||
use crate::support::nyxd::Client;
|
||||
use crate::support::storage;
|
||||
use crate::unstable_routes::account::cache::AddressInfoCache;
|
||||
use crate::unstable_routes::models::NyxAccountDetails;
|
||||
use axum::extract::FromRef;
|
||||
use nym_api_requests::models::{
|
||||
DetailedChainStatus, GatewayBondAnnotated, MixNodeBondAnnotated, NodeAnnotation,
|
||||
@@ -85,6 +87,7 @@ pub(crate) struct AppState {
|
||||
pub(crate) nyxd_client: Client,
|
||||
pub(crate) chain_status_cache: ChainStatusCache,
|
||||
|
||||
pub(crate) address_info_cache: AddressInfoCache,
|
||||
pub(crate) forced_refresh: ForcedRefresh,
|
||||
pub(crate) nym_contract_cache: NymContractCache,
|
||||
pub(crate) node_status_cache: NodeStatusCache,
|
||||
@@ -292,4 +295,43 @@ impl AppState {
|
||||
.await
|
||||
.ok_or_else(AxumErrorResponse::internal)
|
||||
}
|
||||
|
||||
pub(crate) async fn get_address_info(
|
||||
self,
|
||||
account_id: nym_validator_client::nyxd::AccountId,
|
||||
) -> Result<NyxAccountDetails, AxumErrorResponse> {
|
||||
let address = account_id.to_string();
|
||||
match self.address_info_cache.get(&address).await {
|
||||
Some(guard) => {
|
||||
tracing::trace!("Fetching from cache...");
|
||||
let read_lock = guard.read().await;
|
||||
Ok(read_lock.clone())
|
||||
}
|
||||
None => {
|
||||
tracing::trace!("No cache for {}, refreshing data...", &address);
|
||||
|
||||
let address_info = self
|
||||
.address_info_cache
|
||||
.collect_balances(
|
||||
self.nyxd_client.clone(),
|
||||
self.nym_contract_cache.clone(),
|
||||
self.network_details()
|
||||
.network
|
||||
.chain_details
|
||||
.mix_denom
|
||||
.base
|
||||
.to_owned(),
|
||||
&address,
|
||||
account_id,
|
||||
)
|
||||
.await?;
|
||||
|
||||
self.address_info_cache
|
||||
.upsert_address_info(&address, address_info.clone())
|
||||
.await;
|
||||
|
||||
Ok(address_info)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::nym_nodes::handlers::unstable::nym_node_routes_unstable;
|
||||
use crate::support::http::state::AppState;
|
||||
use axum::Router;
|
||||
|
||||
// as those get stabilised, they should get deprecated and use a redirection instead
|
||||
pub(crate) fn unstable_routes() -> Router<AppState> {
|
||||
Router::new().nest("/nym-nodes", nym_node_routes_unstable())
|
||||
}
|
||||
@@ -29,14 +29,15 @@ use nym_mixnet_contract_common::mixnode::MixNodeDetails;
|
||||
use nym_mixnet_contract_common::nym_node::Role;
|
||||
use nym_mixnet_contract_common::reward_params::RewardingParams;
|
||||
use nym_mixnet_contract_common::{
|
||||
ConfigScoreParams, CurrentIntervalResponse, EpochRewardedSet, EpochStatus, ExecuteMsg,
|
||||
GatewayBond, HistoricalNymNodeVersionEntry, IdentityKey, NymNodeDetails, RewardedSet,
|
||||
RoleAssignment,
|
||||
ConfigScoreParams, CurrentIntervalResponse, Delegation, EpochRewardedSet, EpochStatus,
|
||||
ExecuteMsg, GatewayBond, HistoricalNymNodeVersionEntry, IdentityKey, NymNodeDetails,
|
||||
RewardedSet, RoleAssignment,
|
||||
};
|
||||
use nym_validator_client::coconut::EcashApiError;
|
||||
use nym_validator_client::nyxd::contract_traits::mixnet_query_client::MixnetQueryClientExt;
|
||||
use nym_validator_client::nyxd::contract_traits::PagedDkgQueryClient;
|
||||
use nym_validator_client::nyxd::error::NyxdError;
|
||||
use nym_validator_client::nyxd::Coin;
|
||||
use nym_validator_client::nyxd::{
|
||||
contract_traits::{
|
||||
DkgQueryClient, DkgSigningClient, EcashQueryClient, GroupQueryClient, MixnetQueryClient,
|
||||
@@ -48,7 +49,7 @@ use nym_validator_client::nyxd::{
|
||||
};
|
||||
use nym_validator_client::nyxd::{
|
||||
hash::{Hash, SHA256_HASH_SIZE},
|
||||
AccountId, Coin, TendermintTime,
|
||||
AccountId, TendermintTime,
|
||||
};
|
||||
use nym_validator_client::{
|
||||
nyxd, DirectSigningHttpRpcNyxdClient, EcashApiClient, QueryHttpRpcNyxdClient,
|
||||
@@ -403,6 +404,21 @@ impl Client {
|
||||
nyxd_signing!(self, reconcile_epoch_events(limit, None).await?);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn get_all_delegator_delegations(
|
||||
&self,
|
||||
delegation_owner: &AccountId,
|
||||
) -> Result<Vec<Delegation>, NyxdError> {
|
||||
nyxd_query!(self, get_all_delegator_delegations(delegation_owner).await)
|
||||
}
|
||||
|
||||
pub(crate) async fn get_address_balance(
|
||||
&self,
|
||||
address: &AccountId,
|
||||
denom: impl Into<String>,
|
||||
) -> Result<Option<Coin>, NyxdError> {
|
||||
nyxd_query!(self, get_balance(&address, denom.into()).await)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
use crate::{
|
||||
node_status_api::models::AxumResult,
|
||||
nym_contract_cache::cache::NymContractCache,
|
||||
unstable_routes::{
|
||||
account::data_collector::AddressDataCollector,
|
||||
models::{NyxAccountDelegationDetails, NyxAccountDetails},
|
||||
},
|
||||
};
|
||||
use moka::{future::Cache, Entry};
|
||||
use nym_validator_client::nyxd::AccountId;
|
||||
use std::{sync::Arc, time::Duration};
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct AddressInfoCache {
|
||||
inner: Cache<String, Arc<RwLock<NyxAccountDetails>>>,
|
||||
}
|
||||
|
||||
impl AddressInfoCache {
|
||||
pub(crate) fn new() -> Self {
|
||||
// epoch duration = 1 hour
|
||||
// cache TTL is slightly lower than that to avoid too stale data in case
|
||||
// cache was refreshed JUST BEFORE epoch transition
|
||||
let cache_ttl = Duration::from_secs(60 * 30);
|
||||
let max_capacity = 1000;
|
||||
|
||||
AddressInfoCache {
|
||||
inner: Cache::builder()
|
||||
.time_to_live(cache_ttl)
|
||||
.max_capacity(max_capacity)
|
||||
.build(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn get(&self, key: &str) -> Option<Arc<RwLock<NyxAccountDetails>>> {
|
||||
self.inner.get(key).await
|
||||
}
|
||||
|
||||
pub(crate) async fn upsert_address_info(
|
||||
&self,
|
||||
address: &str,
|
||||
address_info: NyxAccountDetails,
|
||||
) -> Entry<String, Arc<RwLock<NyxAccountDetails>>> {
|
||||
self.inner
|
||||
.entry_by_ref(address)
|
||||
.and_upsert_with(|maybe_entry| async {
|
||||
if let Some(entry) = maybe_entry {
|
||||
let v = entry.into_value();
|
||||
let mut guard = v.write().await;
|
||||
*guard = address_info;
|
||||
v.clone()
|
||||
} else {
|
||||
Arc::new(RwLock::new(address_info))
|
||||
}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) async fn collect_balances(
|
||||
&self,
|
||||
nyxd_client: crate::nyxd::Client,
|
||||
nym_contract_cache: NymContractCache,
|
||||
base_denom: String,
|
||||
address: &str,
|
||||
account_id: AccountId,
|
||||
) -> AxumResult<NyxAccountDetails> {
|
||||
let mut collector = AddressDataCollector::new(
|
||||
nyxd_client,
|
||||
nym_contract_cache,
|
||||
base_denom,
|
||||
account_id.clone(),
|
||||
);
|
||||
|
||||
// ==> get balances of chain tokens <==
|
||||
let balance = collector.get_address_balance().await?;
|
||||
|
||||
// it's very difficult to lower existing balance to exactly 0
|
||||
// so assume this is an unused address and return early
|
||||
if balance.amount == 0 {
|
||||
let address_info = NyxAccountDetails {
|
||||
address: address.to_string(),
|
||||
balance: balance.clone().into(),
|
||||
total_value: balance.clone().into(),
|
||||
delegations: Vec::new(),
|
||||
accumulated_rewards: Vec::new(),
|
||||
total_delegations: balance.clone().into(),
|
||||
claimable_rewards: balance.clone().into(),
|
||||
operator_rewards: None,
|
||||
};
|
||||
|
||||
return Ok(address_info);
|
||||
}
|
||||
|
||||
// ==> get list of delegations (history) <==
|
||||
let delegation_data = collector.get_delegations().await?;
|
||||
|
||||
// ==> get the current reward for each active delegation <==
|
||||
// calculate rewards from nodes this delegator delegated to
|
||||
let accumulated_rewards = collector.calculate_rewards(&delegation_data).await?;
|
||||
|
||||
// ==> convert totals <==
|
||||
let claimable_rewards = collector.claimable_rewards();
|
||||
let total_value = collector.total_value();
|
||||
let total_delegations = collector.total_delegations();
|
||||
let operator_rewards = collector.operator_rewards();
|
||||
|
||||
let address_info = NyxAccountDetails {
|
||||
address: account_id.to_string(),
|
||||
balance: balance.into(),
|
||||
delegations: delegation_data
|
||||
.delegations()
|
||||
.into_iter()
|
||||
.map(|d| NyxAccountDelegationDetails {
|
||||
delegated: d.amount,
|
||||
height: d.height,
|
||||
node_id: d.node_id,
|
||||
proxy: d.proxy,
|
||||
})
|
||||
.collect(),
|
||||
accumulated_rewards,
|
||||
total_delegations,
|
||||
claimable_rewards,
|
||||
total_value,
|
||||
operator_rewards,
|
||||
};
|
||||
|
||||
Ok(address_info)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::{
|
||||
node_status_api::models::{AxumErrorResponse, AxumResult},
|
||||
nym_contract_cache::cache::NymContractCache,
|
||||
unstable_routes::models::NyxAccountDelegationRewardDetails,
|
||||
};
|
||||
use cosmwasm_std::{Coin, Decimal};
|
||||
use nym_mixnet_contract_common::NodeRewarding;
|
||||
use nym_topology::NodeId;
|
||||
use nym_validator_client::nyxd::AccountId;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use tracing::warn;
|
||||
|
||||
pub(crate) struct AddressDataCollector {
|
||||
nyxd_client: crate::nyxd::Client,
|
||||
nym_contract_cache: NymContractCache,
|
||||
account_id: AccountId,
|
||||
total_value: u128,
|
||||
operator_rewards: u128,
|
||||
claimable_rewards: u128,
|
||||
total_delegations: u128,
|
||||
base_denom: String,
|
||||
}
|
||||
|
||||
impl AddressDataCollector {
|
||||
pub(crate) fn new(
|
||||
nyxd_client: crate::nyxd::Client,
|
||||
nym_contract_cache: NymContractCache,
|
||||
base_denom: String,
|
||||
account_id: AccountId,
|
||||
) -> Self {
|
||||
Self {
|
||||
nyxd_client,
|
||||
nym_contract_cache,
|
||||
base_denom,
|
||||
account_id,
|
||||
total_value: 0,
|
||||
operator_rewards: 0,
|
||||
claimable_rewards: 0,
|
||||
total_delegations: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn get_address_balance(
|
||||
&mut self,
|
||||
) -> AxumResult<nym_validator_client::nyxd::Coin> {
|
||||
let balance = self
|
||||
.nyxd_client
|
||||
.get_address_balance(&self.account_id, &self.base_denom)
|
||||
.await?
|
||||
.unwrap_or_else(|| nym_validator_client::nyxd::Coin::new(0u128, &self.base_denom));
|
||||
self.total_value += balance.amount;
|
||||
|
||||
Ok(balance)
|
||||
}
|
||||
|
||||
pub(crate) async fn get_delegations(&mut self) -> AxumResult<AddressDelegationInfo> {
|
||||
let og_delegations = self
|
||||
.nyxd_client
|
||||
.get_all_delegator_delegations(&self.account_id)
|
||||
.await?;
|
||||
|
||||
let delegated_to_nodes = og_delegations
|
||||
.iter()
|
||||
.map(|d| d.node_id)
|
||||
.collect::<HashSet<_>>();
|
||||
|
||||
let nym_nodes = self
|
||||
.nym_contract_cache
|
||||
.all_cached_nym_nodes()
|
||||
.await
|
||||
.ok_or_else(AxumErrorResponse::service_unavailable)?
|
||||
.iter()
|
||||
.filter_map(|node_details| {
|
||||
// is this an operator of this node?
|
||||
if self.account_id.to_string() == node_details.bond_information.owner.as_str() {
|
||||
let pending_operator_reward =
|
||||
node_details.pending_operator_reward().amount.u128();
|
||||
|
||||
// add operator rewards
|
||||
self.operator_rewards += pending_operator_reward;
|
||||
|
||||
// add to totals
|
||||
self.total_value += pending_operator_reward;
|
||||
}
|
||||
if delegated_to_nodes.contains(&node_details.node_id()) {
|
||||
Some((
|
||||
node_details.node_id(),
|
||||
// avoid cloning node data which we don't need
|
||||
(
|
||||
node_details.rewarding_details.clone(),
|
||||
node_details.is_unbonding(),
|
||||
),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<HashMap<_, _>>();
|
||||
|
||||
Ok(AddressDelegationInfo {
|
||||
delegations: og_delegations,
|
||||
delegated_to_nodes: nym_nodes,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) async fn calculate_rewards(
|
||||
&mut self,
|
||||
delegation_data: &AddressDelegationInfo,
|
||||
) -> AxumResult<Vec<NyxAccountDelegationRewardDetails>> {
|
||||
let mut accumulated_rewards = Vec::new();
|
||||
for delegation in delegation_data.delegations.iter() {
|
||||
let node_id = &delegation.node_id;
|
||||
|
||||
if let Some((rewarding_details, is_unbonding)) =
|
||||
delegation_data.delegated_to_nodes.get(node_id)
|
||||
{
|
||||
match rewarding_details.determine_delegation_reward(delegation) {
|
||||
Ok(delegation_reward) => {
|
||||
let reward = NyxAccountDelegationRewardDetails {
|
||||
node_id: delegation.node_id,
|
||||
rewards: decimal_to_coin(delegation_reward, &self.base_denom),
|
||||
amount_staked: delegation.amount.clone(),
|
||||
node_still_fully_bonded: !is_unbonding,
|
||||
};
|
||||
// 4. sum the rewards and delegations
|
||||
self.total_delegations += delegation.amount.amount.u128();
|
||||
self.total_value += delegation.amount.amount.u128();
|
||||
self.total_value += reward.rewards.amount.u128();
|
||||
self.claimable_rewards += reward.rewards.amount.u128();
|
||||
|
||||
accumulated_rewards.push(reward);
|
||||
}
|
||||
Err(err) => {
|
||||
warn!(
|
||||
"Couldn't determine delegations for {} on node {}: {}",
|
||||
&self.account_id, node_id, err
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(accumulated_rewards)
|
||||
}
|
||||
|
||||
pub(crate) fn claimable_rewards(&self) -> Coin {
|
||||
Coin::new(self.claimable_rewards, self.base_denom.to_string())
|
||||
}
|
||||
|
||||
pub(crate) fn total_value(&self) -> Coin {
|
||||
Coin::new(self.total_value, self.base_denom.to_string())
|
||||
}
|
||||
|
||||
pub(crate) fn total_delegations(&self) -> Coin {
|
||||
Coin::new(self.total_delegations, self.base_denom.to_string())
|
||||
}
|
||||
|
||||
pub(crate) fn operator_rewards(&self) -> Option<Coin> {
|
||||
if self.operator_rewards > 0 {
|
||||
Some(Coin::new(
|
||||
self.operator_rewards,
|
||||
self.base_denom.to_string(),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct AddressDelegationInfo {
|
||||
delegations: Vec<nym_mixnet_contract_common::Delegation>,
|
||||
delegated_to_nodes: HashMap<NodeId, RewardAndBondInfo>,
|
||||
}
|
||||
|
||||
impl AddressDelegationInfo {
|
||||
pub(crate) fn delegations(self) -> Vec<nym_mixnet_contract_common::Delegation> {
|
||||
self.delegations
|
||||
}
|
||||
}
|
||||
|
||||
type RewardAndBondInfo = (NodeRewarding, bool);
|
||||
|
||||
fn decimal_to_coin(decimal: Decimal, denom: impl Into<String>) -> Coin {
|
||||
Coin::new(decimal.to_uint_floor(), denom)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn decimal_to_coin_test() {
|
||||
let test_values = [
|
||||
(1234, 0, 1234),
|
||||
(1234, 2, 12),
|
||||
(1_234_000_000_000_000u128, 6, 1_234_000_000u128),
|
||||
];
|
||||
|
||||
for (amount, decimal_places, coin_amount) in test_values {
|
||||
let decimal =
|
||||
Decimal::from_atomics(cosmwasm_std::Uint128::new(amount), decimal_places).unwrap();
|
||||
let coin_from_decimal = decimal_to_coin(decimal, "unym");
|
||||
assert_eq!(coin_from_decimal, Coin::new(coin_amount, "unym"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::{
|
||||
node_status_api::models::{AxumErrorResponse, AxumResult},
|
||||
support::http::state::AppState,
|
||||
unstable_routes::models::NyxAccountDetails,
|
||||
};
|
||||
use axum::{
|
||||
extract::{Path, State},
|
||||
routing::get,
|
||||
Json, Router,
|
||||
};
|
||||
use nym_validator_client::nyxd::AccountId;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::str::FromStr;
|
||||
use tracing::{error, instrument};
|
||||
use utoipa::ToSchema;
|
||||
|
||||
pub(crate) mod cache;
|
||||
pub(crate) mod data_collector;
|
||||
|
||||
pub(crate) fn routes() -> Router<AppState> {
|
||||
Router::new().route("/:address", get(address))
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema, utoipa::IntoParams)]
|
||||
pub struct AddressQueryParam {
|
||||
#[serde(default)]
|
||||
pub address: String,
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
tag = "Unstable",
|
||||
get,
|
||||
path = "/{address}",
|
||||
context_path = "/v1/unstable/account",
|
||||
responses(
|
||||
(status = 200, body = NyxAccountDetails)
|
||||
),
|
||||
params(AddressQueryParam)
|
||||
)]
|
||||
#[instrument(level = "info", skip_all, fields(address=address))]
|
||||
async fn address(
|
||||
Path(AddressQueryParam { address }): Path<AddressQueryParam>,
|
||||
State(state): State<AppState>,
|
||||
) -> AxumResult<Json<NyxAccountDetails>> {
|
||||
let account_id = AccountId::from_str(&address).map_err(|err| {
|
||||
error!("{err}");
|
||||
AxumErrorResponse::not_found(&address)
|
||||
})?;
|
||||
|
||||
state.get_address_info(account_id).await.map(Json)
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
pub(crate) mod account;
|
||||
pub(crate) mod models;
|
||||
|
||||
use crate::support::http::state::AppState;
|
||||
use axum::Router;
|
||||
|
||||
// as those get stabilised, they should get deprecated and use a redirection instead
|
||||
pub(crate) fn unstable_routes() -> Router<AppState> {
|
||||
Router::new()
|
||||
.nest("/nym-nodes", crate::nym_nodes::handlers::unstable::routes())
|
||||
.nest("/account", account::routes())
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use cosmwasm_std::{Addr, Coin};
|
||||
use nym_topology::NodeId;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use utoipa::schema;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, utoipa::ToSchema, utoipa::ToResponse)]
|
||||
#[schema(title = "Coin")]
|
||||
pub struct CoinSchema {
|
||||
pub denom: String,
|
||||
pub amount: u128,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, utoipa::ToSchema, utoipa::ToResponse)]
|
||||
pub struct NyxAccountDelegationDetails {
|
||||
pub node_id: NodeId,
|
||||
#[schema(value_type = CoinSchema)]
|
||||
pub delegated: Coin,
|
||||
pub height: u64,
|
||||
#[schema(value_type = Option<String>)]
|
||||
pub proxy: Option<Addr>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, utoipa::ToSchema, utoipa::ToResponse)]
|
||||
pub struct NyxAccountDelegationRewardDetails {
|
||||
pub node_id: NodeId,
|
||||
#[schema(value_type = CoinSchema)]
|
||||
pub rewards: Coin,
|
||||
#[schema(value_type = String)]
|
||||
pub amount_staked: Coin,
|
||||
pub node_still_fully_bonded: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, utoipa::ToSchema, utoipa::ToResponse)]
|
||||
pub struct NyxAccountDetails {
|
||||
pub address: String,
|
||||
#[schema(value_type = CoinSchema)]
|
||||
pub balance: Coin,
|
||||
#[schema(value_type = CoinSchema)]
|
||||
pub total_value: Coin,
|
||||
pub delegations: Vec<NyxAccountDelegationDetails>,
|
||||
pub accumulated_rewards: Vec<NyxAccountDelegationRewardDetails>,
|
||||
#[schema(value_type = String)]
|
||||
pub total_delegations: Coin,
|
||||
#[schema(value_type = CoinSchema)]
|
||||
pub claimable_rewards: Coin,
|
||||
#[schema(value_type = Option<CoinSchema>)]
|
||||
pub operator_rewards: Option<Coin>,
|
||||
}
|
||||
@@ -31,7 +31,7 @@ pub fn build_router(state: ApiState, auth_token: String) -> Router {
|
||||
.nest(routes::API, api::routes(auth_middleware))
|
||||
// we don't have to be using middleware, but we already had that code
|
||||
// we might want something like: https://github.com/tokio-rs/axum/blob/main/examples/tracing-aka-logging/src/main.rs#L44 instead
|
||||
.layer(axum::middleware::from_fn(logging::logger))
|
||||
.layer(axum::middleware::from_fn(logging::log_request_info))
|
||||
.with_state(state);
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
|
||||
@@ -20,7 +20,7 @@ nym-bin-common = { path = "../../common/bin-common", features = ["models"]}
|
||||
nym-node-status-client = { path = "../nym-node-status-client" }
|
||||
nym-crypto = { path = "../../common/crypto", features = ["asymmetric", "rand"] }
|
||||
rand = { workspace = true }
|
||||
tokio = { workspace = true, features = ["macros", "rt-multi-thread", "process"] }
|
||||
tokio = { workspace = true, features = ["macros", "rt-multi-thread", "process", "fs"] }
|
||||
tracing = { workspace = true }
|
||||
tracing-subscriber = { workspace = true, features = ["env-filter"] }
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
[package]
|
||||
name = "nym-node-status-api"
|
||||
version = "2.1.0"
|
||||
version = "2.3.0"
|
||||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
homepage.workspace = true
|
||||
@@ -25,10 +25,12 @@ futures-util = { workspace = true }
|
||||
itertools = { workspace = true }
|
||||
moka = { workspace = true, features = ["future"] }
|
||||
nym-contracts-common = { path = "../../common/cosmwasm-smart-contracts/contracts-common" }
|
||||
nym-mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-contract", features = ["utoipa"] }
|
||||
nym-bin-common = { path = "../../common/bin-common", features = ["models"] }
|
||||
nym-node-status-client = { path = "../nym-node-status-client" }
|
||||
nym-crypto = { path = "../../common/crypto", features = ["asymmetric", "serde"] }
|
||||
nym-http-api-client = { path = "../../common/http-api-client" }
|
||||
nym-http-api-common = { path = "../../common/http-api-common" }
|
||||
nym-network-defaults = { path = "../../common/network-defaults" }
|
||||
nym-serde-helpers = { path = "../../common/serde-helpers" }
|
||||
nym-statistics-common = { path = "../../common/statistics" }
|
||||
|
||||
@@ -6,6 +6,7 @@ use crate::{
|
||||
DbPool,
|
||||
},
|
||||
http::models::Gateway,
|
||||
mixnet_scraper::helpers::NodeDescriptionResponse,
|
||||
};
|
||||
use futures_util::TryStreamExt;
|
||||
use sqlx::{pool::PoolConnection, Sqlite};
|
||||
@@ -131,3 +132,39 @@ pub(crate) async fn get_bonded_gateway_id_keys(pool: &DbPool) -> anyhow::Result<
|
||||
|
||||
Ok(items)
|
||||
}
|
||||
|
||||
pub(crate) async fn insert_gateway_description(
|
||||
conn: &mut PoolConnection<Sqlite>,
|
||||
identity_key: &str,
|
||||
description: &NodeDescriptionResponse,
|
||||
timestamp: i64,
|
||||
) -> anyhow::Result<()> {
|
||||
sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO gateway_description (
|
||||
gateway_identity_key,
|
||||
moniker,
|
||||
website,
|
||||
security_contact,
|
||||
details,
|
||||
last_updated_utc
|
||||
) VALUES (?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT (gateway_identity_key) DO UPDATE SET
|
||||
moniker = excluded.moniker,
|
||||
website = excluded.website,
|
||||
security_contact = excluded.security_contact,
|
||||
details = excluded.details,
|
||||
last_updated_utc = excluded.last_updated_utc
|
||||
"#,
|
||||
identity_key,
|
||||
description.moniker,
|
||||
description.website,
|
||||
description.security_contact,
|
||||
description.details,
|
||||
timestamp,
|
||||
)
|
||||
.execute(conn.as_mut())
|
||||
.await
|
||||
.map(drop)
|
||||
.map_err(From::from)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use futures_util::TryStreamExt;
|
||||
use sqlx::{pool::PoolConnection, Sqlite};
|
||||
use tracing::error;
|
||||
|
||||
use crate::{
|
||||
@@ -9,6 +10,7 @@ use crate::{
|
||||
DbPool,
|
||||
},
|
||||
http::models::{DailyStats, Mixnode},
|
||||
mixnet_scraper::helpers::NodeDescriptionResponse,
|
||||
};
|
||||
|
||||
pub(crate) async fn update_mixnodes(
|
||||
@@ -157,3 +159,34 @@ pub(crate) async fn get_bonded_mix_ids(pool: &DbPool) -> anyhow::Result<HashSet<
|
||||
|
||||
Ok(items)
|
||||
}
|
||||
|
||||
pub(crate) async fn insert_mixnode_description(
|
||||
conn: &mut PoolConnection<Sqlite>,
|
||||
mix_id: &i64,
|
||||
description: &NodeDescriptionResponse,
|
||||
timestamp: i64,
|
||||
) -> anyhow::Result<()> {
|
||||
sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO mixnode_description (
|
||||
mix_id, moniker, website, security_contact, details, last_updated_utc
|
||||
) VALUES (?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT (mix_id) DO UPDATE SET
|
||||
moniker = excluded.moniker,
|
||||
website = excluded.website,
|
||||
security_contact = excluded.security_contact,
|
||||
details = excluded.details,
|
||||
last_updated_utc = excluded.last_updated_utc
|
||||
"#,
|
||||
mix_id,
|
||||
description.moniker,
|
||||
description.website,
|
||||
description.security_contact,
|
||||
description.details,
|
||||
timestamp,
|
||||
)
|
||||
.execute(conn.as_mut())
|
||||
.await
|
||||
.map(drop)
|
||||
.map_err(From::from)
|
||||
}
|
||||
|
||||
@@ -4,12 +4,16 @@ use nym_validator_client::{
|
||||
client::{NodeId, NymNodeDetails},
|
||||
models::NymNodeDescription,
|
||||
};
|
||||
use sqlx::{pool::PoolConnection, Sqlite};
|
||||
use std::collections::HashMap;
|
||||
use tracing::instrument;
|
||||
|
||||
use crate::db::{
|
||||
models::{NymNodeDto, NymNodeInsertRecord},
|
||||
DbPool,
|
||||
use crate::{
|
||||
db::{
|
||||
models::{NymNodeDto, NymNodeInsertRecord},
|
||||
DbPool,
|
||||
},
|
||||
mixnet_scraper::helpers::NodeDescriptionResponse,
|
||||
};
|
||||
|
||||
pub(crate) async fn get_all_nym_nodes(pool: &DbPool) -> anyhow::Result<Vec<NymNodeDto>> {
|
||||
@@ -194,6 +198,8 @@ pub(crate) async fn get_node_self_description(
|
||||
nym_nodes
|
||||
WHERE
|
||||
self_described IS NOT NULL
|
||||
ORDER BY
|
||||
node_id
|
||||
"#,
|
||||
)
|
||||
.fetch_all(&mut *conn)
|
||||
@@ -256,3 +262,34 @@ pub(crate) async fn get_bonded_node_description(
|
||||
})
|
||||
.map_err(From::from)
|
||||
}
|
||||
|
||||
pub(crate) async fn insert_nym_node_description(
|
||||
conn: &mut PoolConnection<Sqlite>,
|
||||
node_id: &i64,
|
||||
description: &NodeDescriptionResponse,
|
||||
timestamp: i64,
|
||||
) -> anyhow::Result<()> {
|
||||
sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO nym_node_descriptions (
|
||||
node_id, moniker, website, security_contact, details, last_updated_utc
|
||||
) VALUES (?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT (node_id) DO UPDATE SET
|
||||
moniker = excluded.moniker,
|
||||
website = excluded.website,
|
||||
security_contact = excluded.security_contact,
|
||||
details = excluded.details,
|
||||
last_updated_utc = excluded.last_updated_utc
|
||||
"#,
|
||||
node_id,
|
||||
description.moniker,
|
||||
description.website,
|
||||
description.security_contact,
|
||||
description.details,
|
||||
timestamp,
|
||||
)
|
||||
.execute(conn.as_mut())
|
||||
.await
|
||||
.map(drop)
|
||||
.map_err(From::from)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
use crate::{
|
||||
db::{
|
||||
models::{ScrapeNodeKind, ScraperNodeInfo},
|
||||
queries, DbPool,
|
||||
queries::{
|
||||
self, gateways::insert_gateway_description, mixnodes::insert_mixnode_description,
|
||||
nym_nodes::insert_nym_node_description,
|
||||
},
|
||||
DbPool,
|
||||
},
|
||||
mixnet_scraper::helpers::NodeDescriptionResponse,
|
||||
};
|
||||
@@ -125,78 +129,18 @@ pub(crate) async fn insert_scraped_node_description(
|
||||
|
||||
match node_kind {
|
||||
ScrapeNodeKind::LegacyMixnode { mix_id } => {
|
||||
sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO mixnode_description (
|
||||
mix_id, moniker, website, security_contact, details, last_updated_utc
|
||||
) VALUES (?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT (mix_id) DO UPDATE SET
|
||||
moniker = excluded.moniker,
|
||||
website = excluded.website,
|
||||
security_contact = excluded.security_contact,
|
||||
details = excluded.details,
|
||||
last_updated_utc = excluded.last_updated_utc
|
||||
"#,
|
||||
mix_id,
|
||||
description.moniker,
|
||||
description.website,
|
||||
description.security_contact,
|
||||
description.details,
|
||||
timestamp,
|
||||
)
|
||||
.execute(&mut *conn)
|
||||
.await?;
|
||||
insert_mixnode_description(&mut conn, mix_id, description, timestamp).await?;
|
||||
}
|
||||
ScrapeNodeKind::MixingNymNode { node_id } => {
|
||||
sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO nym_node_descriptions (
|
||||
node_id, moniker, website, security_contact, details, last_updated_utc
|
||||
) VALUES (?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT (node_id) DO UPDATE SET
|
||||
moniker = excluded.moniker,
|
||||
website = excluded.website,
|
||||
security_contact = excluded.security_contact,
|
||||
details = excluded.details,
|
||||
last_updated_utc = excluded.last_updated_utc
|
||||
"#,
|
||||
node_id,
|
||||
description.moniker,
|
||||
description.website,
|
||||
description.security_contact,
|
||||
description.details,
|
||||
timestamp,
|
||||
)
|
||||
.execute(&mut *conn)
|
||||
.await?;
|
||||
insert_nym_node_description(&mut conn, node_id, description, timestamp).await?;
|
||||
}
|
||||
ScrapeNodeKind::EntryExitNymNode { identity_key, .. } => {
|
||||
sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO gateway_description (
|
||||
gateway_identity_key,
|
||||
moniker,
|
||||
website,
|
||||
security_contact,
|
||||
details,
|
||||
last_updated_utc
|
||||
) VALUES (?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT (gateway_identity_key) DO UPDATE SET
|
||||
moniker = excluded.moniker,
|
||||
website = excluded.website,
|
||||
security_contact = excluded.security_contact,
|
||||
details = excluded.details,
|
||||
last_updated_utc = excluded.last_updated_utc
|
||||
"#,
|
||||
identity_key,
|
||||
description.moniker,
|
||||
description.website,
|
||||
description.security_contact,
|
||||
description.details,
|
||||
timestamp,
|
||||
)
|
||||
.execute(&mut *conn)
|
||||
.await?;
|
||||
ScrapeNodeKind::EntryExitNymNode {
|
||||
node_id,
|
||||
identity_key,
|
||||
} => {
|
||||
insert_nym_node_description(&mut conn, node_id, description, timestamp).await?;
|
||||
// for historic reasons (/gateways API), store this info into gateways table as well
|
||||
insert_gateway_description(&mut conn, identity_key, description, timestamp).await?;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -77,7 +77,7 @@ async fn get_all_sessions(
|
||||
};
|
||||
|
||||
Ok(Json(PagedResult::paginate(
|
||||
Pagination { size, page },
|
||||
Pagination::new(size, page),
|
||||
day_and_node_filtered,
|
||||
)))
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use anyhow::anyhow;
|
||||
use axum::{response::Redirect, Router};
|
||||
use nym_http_api_common::middleware::logging::log_request_debug;
|
||||
use tokio::net::ToSocketAddrs;
|
||||
use tower_http::{cors::CorsLayer, trace::TraceLayer};
|
||||
use tower_http::cors::CorsLayer;
|
||||
use utoipa::OpenApi;
|
||||
use utoipa_swagger_ui::SwaggerUi;
|
||||
|
||||
@@ -39,7 +40,10 @@ impl RouterBuilder {
|
||||
.nest("/summary", summary::routes())
|
||||
.nest("/metrics", metrics::routes()),
|
||||
)
|
||||
.nest("/v3", Router::new().nest("/nym-nodes", nym_nodes::routes()))
|
||||
.nest(
|
||||
"/explorer/v3",
|
||||
Router::new().nest("/nym-nodes", nym_nodes::routes()),
|
||||
)
|
||||
.nest(
|
||||
"/internal",
|
||||
Router::new().nest("/testruns", testruns::routes()),
|
||||
@@ -62,7 +66,7 @@ impl RouterBuilder {
|
||||
// CORS layer needs to wrap other API layers
|
||||
.layer(setup_cors())
|
||||
// logger should be outermost layer
|
||||
.layer(TraceLayer::new_for_http())
|
||||
.layer(axum::middleware::from_fn(log_request_debug))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,32 +1,41 @@
|
||||
use axum::{
|
||||
extract::{Query, State},
|
||||
extract::{Path, Query, State},
|
||||
Json, Router,
|
||||
};
|
||||
use nym_validator_client::client::NodeId;
|
||||
use serde::Deserialize;
|
||||
use tracing::instrument;
|
||||
use utoipa::IntoParams;
|
||||
|
||||
use crate::http::{
|
||||
error::{HttpError, HttpResult},
|
||||
models::ExtendedNymNode,
|
||||
models::{ExtendedNymNode, NodeDelegation},
|
||||
state::AppState,
|
||||
PagedResult, Pagination,
|
||||
};
|
||||
|
||||
pub(crate) fn routes() -> Router<AppState> {
|
||||
Router::new().route("/", axum::routing::get(nym_nodes))
|
||||
Router::new()
|
||||
.route("/", axum::routing::get(nym_nodes))
|
||||
.route(
|
||||
"/:node_id/delegations",
|
||||
axum::routing::get(node_delegations),
|
||||
)
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
tag = "Nym Nodes",
|
||||
tag = "Nym Explorer",
|
||||
get,
|
||||
params(
|
||||
Pagination
|
||||
),
|
||||
path = "/v3/nym-nodes",
|
||||
path = "/nym-nodes",
|
||||
context_path = "/explorer/v3",
|
||||
responses(
|
||||
(status = 200, body = PagedResult<ExtendedNymNode>)
|
||||
)
|
||||
)]
|
||||
#[instrument(level = tracing::Level::DEBUG, skip_all, fields(page=pagination.page, size=pagination.size))]
|
||||
#[instrument(level = tracing::Level::INFO, skip_all, fields(page=pagination.page, size=pagination.size))]
|
||||
async fn nym_nodes(
|
||||
Query(pagination): Query<Pagination>,
|
||||
State(state): State<AppState>,
|
||||
@@ -45,3 +54,35 @@ async fn nym_nodes(
|
||||
|
||||
Ok(Json(PagedResult::paginate(pagination, nodes)))
|
||||
}
|
||||
|
||||
#[allow(dead_code)] // clippy doesn't detect usage in utoipa macros
|
||||
#[derive(Deserialize, IntoParams)]
|
||||
#[into_params(parameter_in = Path)]
|
||||
struct NodeIdParam {
|
||||
#[param(minimum = 0)]
|
||||
node_id: NodeId,
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
tag = "Nym Explorer",
|
||||
get,
|
||||
params(
|
||||
NodeIdParam
|
||||
),
|
||||
path = "/{node_id}/delegations",
|
||||
context_path = "/explorer/v3/nym-nodes",
|
||||
responses(
|
||||
(status = 200, body = NodeDelegation)
|
||||
)
|
||||
)]
|
||||
#[instrument(level = tracing::Level::INFO, skip(state))]
|
||||
async fn node_delegations(
|
||||
Path(node_id): Path<NodeId>,
|
||||
State(state): State<AppState>,
|
||||
) -> HttpResult<Json<Vec<NodeDelegation>>> {
|
||||
state
|
||||
.node_delegations(node_id)
|
||||
.await
|
||||
.ok_or_else(|| HttpError::no_delegations_for_node(node_id))
|
||||
.map(Json)
|
||||
}
|
||||
|
||||
@@ -104,7 +104,10 @@ async fn mixnodes(
|
||||
.map(|(s, _)| s)
|
||||
.collect();
|
||||
|
||||
Ok(Json(PagedResult::paginate(Pagination { size, page }, res)))
|
||||
Ok(Json(PagedResult::paginate(
|
||||
Pagination::new(size, page),
|
||||
res,
|
||||
)))
|
||||
}
|
||||
|
||||
struct ServiceFilter {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use nym_mixnet_contract_common::NodeId;
|
||||
use std::fmt::Display;
|
||||
|
||||
pub(crate) type HttpResult<T> = Result<T, HttpError>;
|
||||
@@ -40,6 +41,14 @@ impl HttpError {
|
||||
status: axum::http::StatusCode::SERVICE_UNAVAILABLE,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn no_delegations_for_node(node_id: NodeId) -> Self {
|
||||
Self {
|
||||
message: serde_json::json!({"message": format!("No delegation data for node_id={}", node_id)})
|
||||
.to_string(),
|
||||
status: axum::http::StatusCode::NOT_FOUND,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl axum::response::IntoResponse for HttpError {
|
||||
|
||||
@@ -18,7 +18,7 @@ pub struct PagedResult<T: ToSchema> {
|
||||
impl<T: Clone + ToSchema> PagedResult<T> {
|
||||
pub fn paginate(pagination: Pagination, res: Vec<T>) -> Self {
|
||||
let total = res.len();
|
||||
let (size, mut page) = pagination.intoto_inner_values();
|
||||
let (size, mut page) = pagination.into_inner_values();
|
||||
|
||||
if page * size > total {
|
||||
page = total / size;
|
||||
@@ -42,14 +42,25 @@ pub(crate) struct Pagination {
|
||||
page: Option<usize>,
|
||||
}
|
||||
|
||||
const SIZE_DEFAULT: usize = 10;
|
||||
const SIZE_MAX: usize = 200;
|
||||
const PAGE_DEFAULT: usize = 0;
|
||||
|
||||
impl Default for Pagination {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
size: Some(SIZE_DEFAULT),
|
||||
page: Some(PAGE_DEFAULT),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Pagination {
|
||||
// unwrap stored values or use predefined defaults
|
||||
pub(crate) fn intoto_inner_values(self) -> (usize, usize) {
|
||||
const SIZE_DEFAULT: usize = 10;
|
||||
const SIZE_MAX: usize = 200;
|
||||
|
||||
const PAGE_DEFAULT: usize = 0;
|
||||
pub(crate) fn new(size: Option<usize>, page: Option<usize>) -> Self {
|
||||
Self { size, page }
|
||||
}
|
||||
|
||||
pub(crate) fn into_inner_values(self) -> (usize, usize) {
|
||||
(
|
||||
self.size.unwrap_or(SIZE_DEFAULT).min(SIZE_MAX),
|
||||
self.page.unwrap_or(PAGE_DEFAULT),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use cosmwasm_std::Decimal;
|
||||
use cosmwasm_std::{Addr, Coin, Decimal};
|
||||
use nym_mixnet_contract_common::CoinSchema;
|
||||
use nym_node_requests::api::v1::node::models::NodeDescription;
|
||||
use nym_validator_client::client::NodeId;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -55,13 +56,13 @@ pub(crate) struct ExtendedNymNode {
|
||||
#[schema(value_type = String)]
|
||||
pub(crate) total_stake: Decimal,
|
||||
pub(crate) original_pledge: u128,
|
||||
pub(crate) bonding_address: String,
|
||||
pub(crate) bonding_address: Option<String>,
|
||||
pub(crate) bonded: bool,
|
||||
pub(crate) node_type: String,
|
||||
pub(crate) node_type: nym_validator_client::models::DescribedNodeType,
|
||||
pub(crate) ip_address: String,
|
||||
pub(crate) accepted_tnc: bool,
|
||||
pub(crate) self_description: serde_json::Value,
|
||||
pub(crate) rewarding_details: serde_json::Value,
|
||||
pub(crate) self_description: nym_validator_client::models::NymNodeData,
|
||||
pub(crate) rewarding_details: Option<nym_mixnet_contract_common::NodeRewarding>,
|
||||
pub(crate) description: NodeDescription,
|
||||
pub(crate) geoip: Option<NodeGeoData>,
|
||||
}
|
||||
@@ -120,3 +121,27 @@ pub struct SessionStats {
|
||||
pub mixnet_sessions: Option<serde_json::Value>,
|
||||
pub unknown_sessions: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)]
|
||||
pub struct NodeDelegation {
|
||||
#[schema(value_type = CoinSchema)]
|
||||
pub amount: Coin,
|
||||
pub cumulative_reward_ratio: String,
|
||||
pub block_height: u64,
|
||||
#[schema(value_type = String)]
|
||||
pub owner: Addr,
|
||||
#[schema(value_type = Option<String>)]
|
||||
pub proxy: Option<Addr>,
|
||||
}
|
||||
|
||||
impl From<nym_mixnet_contract_common::Delegation> for NodeDelegation {
|
||||
fn from(value: nym_mixnet_contract_common::Delegation) -> Self {
|
||||
Self {
|
||||
amount: value.amount,
|
||||
cumulative_reward_ratio: value.cumulative_reward_ratio.to_string(),
|
||||
block_height: value.height,
|
||||
owner: value.owner,
|
||||
proxy: value.proxy,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
use axum::Router;
|
||||
use core::net::SocketAddr;
|
||||
use nym_crypto::asymmetric::ed25519::PublicKey;
|
||||
use tokio::{net::TcpListener, task::JoinHandle};
|
||||
use std::sync::Arc;
|
||||
use tokio::{net::TcpListener, sync::RwLock, task::JoinHandle};
|
||||
use tokio_util::sync::{CancellationToken, WaitForCancellationFutureOwned};
|
||||
|
||||
use crate::{
|
||||
db::DbPool,
|
||||
http::{api::RouterBuilder, state::AppState},
|
||||
monitor::NodeGeoCache,
|
||||
monitor::{DelegationsCache, NodeGeoCache},
|
||||
};
|
||||
|
||||
/// Return handles that allow for graceful shutdown of server + awaiting its
|
||||
@@ -19,6 +20,7 @@ pub(crate) async fn start_http_api(
|
||||
agent_key_list: Vec<PublicKey>,
|
||||
agent_max_count: i64,
|
||||
node_geocache: NodeGeoCache,
|
||||
node_delegations: Arc<RwLock<DelegationsCache>>,
|
||||
) -> anyhow::Result<ShutdownHandles> {
|
||||
let router_builder = RouterBuilder::with_default_routes();
|
||||
|
||||
@@ -28,6 +30,7 @@ pub(crate) async fn start_http_api(
|
||||
agent_key_list,
|
||||
agent_max_count,
|
||||
node_geocache,
|
||||
node_delegations,
|
||||
)
|
||||
.await;
|
||||
let router = router_builder.with_state(state);
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
use std::{collections::HashMap, sync::Arc, time::Duration};
|
||||
|
||||
use cosmwasm_std::Decimal;
|
||||
use moka::{future::Cache, Entry};
|
||||
use nym_contracts_common::NaiveFloat;
|
||||
use nym_crypto::asymmetric::ed25519::PublicKey;
|
||||
use nym_validator_client::{models::DescribedNodeType, nym_api::SkimmedNode};
|
||||
use nym_mixnet_contract_common::NodeId;
|
||||
use nym_validator_client::nym_api::SkimmedNode;
|
||||
use std::{collections::HashMap, sync::Arc, time::Duration};
|
||||
use tokio::sync::RwLock;
|
||||
use tracing::instrument;
|
||||
|
||||
use crate::{
|
||||
db::{queries, DbPool},
|
||||
http::models::{DailyStats, ExtendedNymNode, Gateway, Mixnode, NodeGeoData, SummaryHistory},
|
||||
monitor::NodeGeoCache,
|
||||
monitor::{DelegationsCache, NodeGeoCache},
|
||||
};
|
||||
|
||||
use super::models::SessionStats;
|
||||
@@ -23,6 +23,7 @@ pub(crate) struct AppState {
|
||||
agent_key_list: Vec<PublicKey>,
|
||||
agent_max_count: i64,
|
||||
node_geocache: NodeGeoCache,
|
||||
node_delegations: Arc<RwLock<DelegationsCache>>,
|
||||
}
|
||||
|
||||
impl AppState {
|
||||
@@ -32,6 +33,7 @@ impl AppState {
|
||||
agent_key_list: Vec<PublicKey>,
|
||||
agent_max_count: i64,
|
||||
node_geocache: NodeGeoCache,
|
||||
node_delegations: Arc<RwLock<DelegationsCache>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
db_pool,
|
||||
@@ -39,6 +41,7 @@ impl AppState {
|
||||
agent_key_list,
|
||||
agent_max_count,
|
||||
node_geocache,
|
||||
node_delegations,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,6 +64,16 @@ impl AppState {
|
||||
pub(crate) fn node_geocache(&self) -> NodeGeoCache {
|
||||
self.node_geocache.clone()
|
||||
}
|
||||
|
||||
pub(crate) async fn node_delegations(
|
||||
&self,
|
||||
node_id: NodeId,
|
||||
) -> Option<Vec<super::models::NodeDelegation>> {
|
||||
self.node_delegations
|
||||
.read()
|
||||
.await
|
||||
.delegations_owned(node_id)
|
||||
}
|
||||
}
|
||||
|
||||
static GATEWAYS_LIST_KEY: &str = "gateways";
|
||||
@@ -401,11 +414,7 @@ async fn aggregate_node_info_from_db(
|
||||
.get(&node_id)
|
||||
.map(|node| node.performance.naive_to_f64())
|
||||
.unwrap_or(0.0);
|
||||
let node_type = match described_node.contract_node_type {
|
||||
DescribedNodeType::NymNode => "nym_node".to_string(),
|
||||
DescribedNodeType::LegacyMixnode => "legacy_mixnode".to_string(),
|
||||
DescribedNodeType::LegacyGateway => "legacy_gateway".to_string(),
|
||||
};
|
||||
let node_type = described_node.contract_node_type;
|
||||
let ip_address = described_node
|
||||
.description
|
||||
.host_information
|
||||
@@ -417,11 +426,10 @@ async fn aggregate_node_info_from_db(
|
||||
.description
|
||||
.auxiliary_details
|
||||
.accepted_operator_terms_and_conditions;
|
||||
let description = described_node.description;
|
||||
let self_described = described_node.description;
|
||||
|
||||
let bonding_address = bond_details
|
||||
.map(|details| details.bond_information.owner.to_string())
|
||||
.unwrap_or_default();
|
||||
let bonding_address =
|
||||
bond_details.map(|details| details.bond_information.owner.to_string());
|
||||
|
||||
let node_description = node_descriptions.get(&node_id).cloned().unwrap_or_default();
|
||||
let geoip = {
|
||||
@@ -449,8 +457,8 @@ async fn aggregate_node_info_from_db(
|
||||
bonded,
|
||||
node_type,
|
||||
accepted_tnc,
|
||||
self_description: serde_json::to_value(description).unwrap_or_default(),
|
||||
rewarding_details: serde_json::to_value(rewarding_details).unwrap_or_default(),
|
||||
self_description: self_described,
|
||||
rewarding_details: rewarding_details.to_owned(),
|
||||
description: node_description,
|
||||
geoip,
|
||||
});
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
use crate::monitor::DelegationsCache;
|
||||
use clap::Parser;
|
||||
use nym_crypto::asymmetric::ed25519::PublicKey;
|
||||
use nym_task::signal::wait_for_signal;
|
||||
use nym_validator_client::nyxd::NyxdClient;
|
||||
use std::sync::Arc;
|
||||
|
||||
mod cli;
|
||||
mod db;
|
||||
@@ -41,19 +44,27 @@ async fn main() -> anyhow::Result<()> {
|
||||
let geocache = moka::future::Cache::builder()
|
||||
.time_to_live(args.geodata_ttl)
|
||||
.build();
|
||||
let delegations_cache = DelegationsCache::new();
|
||||
|
||||
// Start the monitor
|
||||
let args_clone = args.clone();
|
||||
let geocache_clone = geocache.clone();
|
||||
let delegations_cache_clone = Arc::clone(&delegations_cache);
|
||||
let config = nym_validator_client::nyxd::Config::try_from_nym_network_details(
|
||||
&nym_network_defaults::NymNetworkDetails::new_from_env(),
|
||||
)?;
|
||||
let nyxd_client = NyxdClient::connect(config, args.nyxd_addr.as_str())
|
||||
.map_err(|err| anyhow::anyhow!("Couldn't connect: {}", err))?;
|
||||
|
||||
tokio::spawn(async move {
|
||||
monitor::spawn_in_background(
|
||||
db_pool,
|
||||
args_clone.nym_api_client_timeout,
|
||||
args_clone.nyxd_addr,
|
||||
nyxd_client,
|
||||
args_clone.monitor_refresh_interval,
|
||||
args_clone.ipinfo_api_token,
|
||||
geocache_clone,
|
||||
delegations_cache_clone,
|
||||
)
|
||||
.await;
|
||||
tracing::info!("Started monitor task");
|
||||
@@ -74,6 +85,7 @@ async fn main() -> anyhow::Result<()> {
|
||||
agent_key_list.to_owned(),
|
||||
args.max_agent_count,
|
||||
geocache,
|
||||
delegations_cache,
|
||||
)
|
||||
.await
|
||||
.expect("Failed to start server");
|
||||
|
||||
@@ -12,25 +12,30 @@ use crate::utils::{decimal_to_i64, LogError, NumericalCheckedCast};
|
||||
use anyhow::anyhow;
|
||||
use moka::future::Cache;
|
||||
use nym_network_defaults::NymNetworkDetails;
|
||||
use nym_validator_client::client::{NodeId, NymApiClientExt, NymNodeDetails};
|
||||
use nym_validator_client::models::{
|
||||
LegacyDescribedMixNode, MixNodeBondAnnotated, NymNodeDescription,
|
||||
use nym_validator_client::{
|
||||
client::{NodeId, NymApiClientExt, NymNodeDetails},
|
||||
models::{LegacyDescribedMixNode, MixNodeBondAnnotated, NymNodeDescription},
|
||||
};
|
||||
use nym_validator_client::nym_nodes::{NodeRole, SkimmedNode};
|
||||
use nym_validator_client::nyxd::contract_traits::PagedMixnetQueryClient;
|
||||
use nym_validator_client::nyxd::{AccountId, NyxdClient};
|
||||
use nym_validator_client::NymApiClient;
|
||||
use reqwest::Url;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::str::FromStr;
|
||||
use tokio::time::Duration;
|
||||
use nym_validator_client::{
|
||||
nym_nodes::{NodeRole, SkimmedNode},
|
||||
nyxd::{contract_traits::PagedMixnetQueryClient, AccountId},
|
||||
NymApiClient, QueryHttpRpcNyxdClient,
|
||||
};
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
str::FromStr,
|
||||
sync::Arc,
|
||||
};
|
||||
use tokio::{sync::RwLock, time::Duration};
|
||||
use tracing::instrument;
|
||||
|
||||
pub(crate) use geodata::IpInfoClient;
|
||||
pub(crate) use node_delegations::DelegationsCache;
|
||||
|
||||
mod geodata;
|
||||
mod node_delegations;
|
||||
|
||||
const FAILURE_RETRY_DELAY: Duration = Duration::from_secs(60);
|
||||
const MONITOR_FAILURE_RETRY_DELAY: Duration = Duration::from_secs(60);
|
||||
static DELEGATION_PROGRAM_WALLET: &str = "n1rnxpdpx3kldygsklfft0gech7fhfcux4zst5lw";
|
||||
pub(crate) type NodeGeoCache = Cache<NodeId, Location>;
|
||||
|
||||
@@ -38,9 +43,10 @@ struct Monitor {
|
||||
db_pool: DbPool,
|
||||
network_details: NymNetworkDetails,
|
||||
nym_api_client_timeout: Duration,
|
||||
nyxd_addr: Url,
|
||||
nyxd_client: QueryHttpRpcNyxdClient,
|
||||
ipinfo: IpInfoClient,
|
||||
geocache: NodeGeoCache,
|
||||
node_delegations: Arc<RwLock<DelegationsCache>>,
|
||||
}
|
||||
|
||||
// TODO dz: query many NYM APIs:
|
||||
@@ -49,19 +55,22 @@ struct Monitor {
|
||||
pub(crate) async fn spawn_in_background(
|
||||
db_pool: DbPool,
|
||||
nym_api_client_timeout: Duration,
|
||||
nyxd_addr: Url,
|
||||
nyxd_client: nym_validator_client::QueryHttpRpcNyxdClient,
|
||||
refresh_interval: Duration,
|
||||
ipinfo_api_token: String,
|
||||
geocache: NodeGeoCache,
|
||||
node_delegations: Arc<RwLock<DelegationsCache>>,
|
||||
) {
|
||||
let ipinfo = IpInfoClient::new(ipinfo_api_token.clone());
|
||||
|
||||
let mut monitor = Monitor {
|
||||
db_pool,
|
||||
network_details: nym_network_defaults::NymNetworkDetails::new_from_env(),
|
||||
nym_api_client_timeout,
|
||||
nyxd_addr,
|
||||
nyxd_client,
|
||||
ipinfo,
|
||||
geocache,
|
||||
node_delegations,
|
||||
};
|
||||
|
||||
loop {
|
||||
@@ -70,10 +79,9 @@ pub(crate) async fn spawn_in_background(
|
||||
if let Err(e) = monitor.run().await {
|
||||
tracing::error!(
|
||||
"Monitor run failed: {e}, retrying in {}s...",
|
||||
FAILURE_RETRY_DELAY.as_secs()
|
||||
MONITOR_FAILURE_RETRY_DELAY.as_secs()
|
||||
);
|
||||
// TODO dz implement some sort of backoff
|
||||
tokio::time::sleep(FAILURE_RETRY_DELAY).await;
|
||||
tokio::time::sleep(MONITOR_FAILURE_RETRY_DELAY).await;
|
||||
} else {
|
||||
tracing::info!(
|
||||
"Info successfully collected, sleeping for {}s...",
|
||||
@@ -151,7 +159,7 @@ impl Monitor {
|
||||
})?;
|
||||
|
||||
// refresh geodata for all nodes
|
||||
for (_, node_description) in described_nodes.iter() {
|
||||
for node_description in described_nodes.values() {
|
||||
self.location_cached(node_description).await;
|
||||
}
|
||||
|
||||
@@ -193,8 +201,7 @@ impl Monitor {
|
||||
.nodes
|
||||
.data;
|
||||
|
||||
let delegation_program_members =
|
||||
get_delegation_program_details(&self.network_details, &self.nyxd_addr).await?;
|
||||
let delegation_program_members = self.get_delegation_program_details().await?;
|
||||
|
||||
// keep stats for later
|
||||
let assigned_entry_count = nym_nodes
|
||||
@@ -233,7 +240,9 @@ impl Monitor {
|
||||
tracing::debug!("{} mixnode info written to DB!", mixnodes_count);
|
||||
})?;
|
||||
|
||||
let (all_historical_gateways, all_historical_mixnodes) = calculate_stats(&pool).await?;
|
||||
self.refresh_node_delegations(&bonded_nym_nodes).await;
|
||||
|
||||
let (all_historical_gateways, all_historical_mixnodes) = historical_count(&pool).await?;
|
||||
|
||||
//
|
||||
// write summary keys and values to table
|
||||
@@ -295,7 +304,7 @@ impl Monitor {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all)]
|
||||
#[instrument(level = "info", skip_all)]
|
||||
async fn location_cached(&mut self, node: &NymNodeDescription) -> Location {
|
||||
let node_id = node.node_id;
|
||||
|
||||
@@ -457,9 +466,34 @@ impl Monitor {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[instrument(level = "info", skip_all)]
|
||||
async fn refresh_node_delegations(&mut self, bonded_nodes: &HashMap<NodeId, NymNodeDetails>) {
|
||||
let delegations_per_node = node_delegations::refresh(&self.nyxd_client, bonded_nodes).await;
|
||||
|
||||
// update after refreshing all to avoid holding write lock for too long
|
||||
*self.node_delegations.write().await = delegations_per_node;
|
||||
}
|
||||
|
||||
async fn get_delegation_program_details(&self) -> anyhow::Result<Vec<NodeId>> {
|
||||
let account_id = AccountId::from_str(DELEGATION_PROGRAM_WALLET)
|
||||
.map_err(|e| anyhow!("Invalid bech32 address: {}", e))?;
|
||||
|
||||
let delegations = self
|
||||
.nyxd_client
|
||||
.get_all_delegator_delegations(&account_id)
|
||||
.await?;
|
||||
|
||||
let mix_ids: Vec<NodeId> = delegations
|
||||
.iter()
|
||||
.map(|delegation| delegation.node_id)
|
||||
.collect();
|
||||
|
||||
Ok(mix_ids)
|
||||
}
|
||||
}
|
||||
|
||||
async fn calculate_stats(pool: &DbPool) -> anyhow::Result<(usize, usize)> {
|
||||
async fn historical_count(pool: &DbPool) -> anyhow::Result<(usize, usize)> {
|
||||
let mut conn = pool.acquire().await?;
|
||||
|
||||
let all_historical_gateways = sqlx::query_scalar!(r#"SELECT count(id) FROM gateways"#)
|
||||
@@ -474,25 +508,3 @@ async fn calculate_stats(pool: &DbPool) -> anyhow::Result<(usize, usize)> {
|
||||
|
||||
Ok((all_historical_gateways, all_historical_mixnodes))
|
||||
}
|
||||
|
||||
async fn get_delegation_program_details(
|
||||
network_details: &NymNetworkDetails,
|
||||
nyxd_addr: &Url,
|
||||
) -> anyhow::Result<Vec<u32>> {
|
||||
let config = nym_validator_client::nyxd::Config::try_from_nym_network_details(network_details)?;
|
||||
|
||||
let client = NyxdClient::connect(config, nyxd_addr.as_str())
|
||||
.map_err(|err| anyhow::anyhow!("Couldn't connect: {}", err))?;
|
||||
|
||||
let account_id = AccountId::from_str(DELEGATION_PROGRAM_WALLET)
|
||||
.map_err(|e| anyhow!("Invalid bech32 address: {}", e))?;
|
||||
|
||||
let delegations = client.get_all_delegator_delegations(&account_id).await?;
|
||||
|
||||
let mix_ids: Vec<u32> = delegations
|
||||
.iter()
|
||||
.map(|delegation| delegation.node_id)
|
||||
.collect();
|
||||
|
||||
Ok(mix_ids)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
use nym_mixnet_contract_common::{NodeId, NymNodeDetails};
|
||||
use nym_validator_client::{nyxd::contract_traits::PagedMixnetQueryClient, QueryHttpRpcNyxdClient};
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use tokio::{sync::RwLock, time::Instant};
|
||||
use tracing::{info, warn};
|
||||
|
||||
// abstracts away data structure that holds delegations
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct DelegationsCache {
|
||||
pub inner: HashMap<NodeId, Vec<crate::http::models::NodeDelegation>>,
|
||||
}
|
||||
|
||||
impl DelegationsCache {
|
||||
pub(crate) fn new() -> Arc<RwLock<Self>> {
|
||||
let a = Self {
|
||||
inner: HashMap::new(),
|
||||
};
|
||||
Arc::new(RwLock::new(a))
|
||||
}
|
||||
|
||||
pub(crate) fn delegations_owned(
|
||||
&self,
|
||||
node_id: NodeId,
|
||||
) -> Option<Vec<crate::http::models::NodeDelegation>> {
|
||||
self.inner.get(&node_id).cloned()
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) async fn refresh(
|
||||
client: &QueryHttpRpcNyxdClient,
|
||||
bonded_nodes: &HashMap<NodeId, NymNodeDetails>,
|
||||
) -> DelegationsCache {
|
||||
info!("👥 Refreshing {} node delegations...", bonded_nodes.len());
|
||||
let now = Instant::now();
|
||||
|
||||
let mut delegations_per_node = HashMap::new();
|
||||
for node_id in bonded_nodes.keys() {
|
||||
if let Ok(delegations) = client
|
||||
.get_all_single_mixnode_delegations(*node_id)
|
||||
.await
|
||||
.inspect_err(|err| warn!("Failed to get delegations for {}: {}", node_id, err))
|
||||
{
|
||||
delegations_per_node
|
||||
.insert(*node_id, delegations.into_iter().map(From::from).collect());
|
||||
}
|
||||
}
|
||||
let time_taken = Instant::now() - now;
|
||||
info!("👥 Node delegations refreshed in {}s", time_taken.as_secs(),);
|
||||
|
||||
DelegationsCache {
|
||||
inner: delegations_per_node,
|
||||
}
|
||||
}
|
||||
@@ -159,7 +159,7 @@ impl NymNodeRouter {
|
||||
)
|
||||
.nest(routes::LANDING_PAGE, landing_page::routes(config.landing))
|
||||
.nest(routes::API, api::routes(config.api))
|
||||
.layer(axum::middleware::from_fn(logging::logger))
|
||||
.layer(axum::middleware::from_fn(logging::log_request_info))
|
||||
.with_state(state),
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+259
-45
@@ -1326,6 +1326,12 @@ dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "critical-section"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b"
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-channel"
|
||||
version = "0.5.15"
|
||||
@@ -2458,6 +2464,19 @@ dependencies = [
|
||||
"x11",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generator"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cc6bd114ceda131d3b1d665eba35788690ad37f5916457286b32ab6fd3c438dd"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"log",
|
||||
"rustversion",
|
||||
"windows 0.58.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.7"
|
||||
@@ -2849,57 +2868,59 @@ checksum = "7ebdb29d2ea9ed0083cd8cece49bbd968021bd99b0849edb4a9a7ee0fdf6a4e0"
|
||||
|
||||
[[package]]
|
||||
name = "hickory-proto"
|
||||
version = "0.24.4"
|
||||
version = "0.25.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92652067c9ce6f66ce53cc38d1169daa36e6e7eb7dd3b63b5103bd9d97117248"
|
||||
checksum = "6d844af74f7b799e41c78221be863bade11c430d46042c3b49ca8ae0c6d27287"
|
||||
dependencies = [
|
||||
"async-recursion",
|
||||
"async-trait",
|
||||
"bytes",
|
||||
"cfg-if",
|
||||
"critical-section",
|
||||
"data-encoding",
|
||||
"enum-as-inner",
|
||||
"futures-channel",
|
||||
"futures-io",
|
||||
"futures-util",
|
||||
"h2 0.3.26",
|
||||
"http 0.2.12",
|
||||
"h2 0.4.8",
|
||||
"http 1.3.1",
|
||||
"idna",
|
||||
"ipnet",
|
||||
"once_cell",
|
||||
"rand 0.8.5",
|
||||
"rustls 0.21.12",
|
||||
"rustls-pemfile 1.0.4",
|
||||
"thiserror 1.0.69",
|
||||
"rand 0.9.0",
|
||||
"ring",
|
||||
"rustls 0.23.25",
|
||||
"thiserror 2.0.12",
|
||||
"tinyvec",
|
||||
"tokio",
|
||||
"tokio-rustls 0.24.1",
|
||||
"tokio-rustls 0.26.2",
|
||||
"tracing",
|
||||
"url",
|
||||
"webpki-roots 0.25.4",
|
||||
"webpki-roots",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hickory-resolver"
|
||||
version = "0.24.4"
|
||||
version = "0.25.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cbb117a1ca520e111743ab2f6688eddee69db4e0ea242545a604dce8a66fd22e"
|
||||
checksum = "a128410b38d6f931fcc6ca5c107a3b02cabd6c05967841269a4ad65d23c44331"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"futures-util",
|
||||
"hickory-proto",
|
||||
"ipconfig",
|
||||
"lru-cache",
|
||||
"moka",
|
||||
"once_cell",
|
||||
"parking_lot",
|
||||
"rand 0.8.5",
|
||||
"rand 0.9.0",
|
||||
"resolv-conf",
|
||||
"rustls 0.21.12",
|
||||
"rustls 0.23.25",
|
||||
"smallvec",
|
||||
"thiserror 1.0.69",
|
||||
"thiserror 2.0.12",
|
||||
"tokio",
|
||||
"tokio-rustls 0.24.1",
|
||||
"tokio-rustls 0.26.2",
|
||||
"tracing",
|
||||
"webpki-roots 0.25.4",
|
||||
"webpki-roots",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3102,7 +3123,7 @@ dependencies = [
|
||||
"tokio",
|
||||
"tokio-rustls 0.26.2",
|
||||
"tower-service",
|
||||
"webpki-roots 0.26.8",
|
||||
"webpki-roots",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3641,12 +3662,6 @@ dependencies = [
|
||||
"redox_syscall",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "linked-hash-map"
|
||||
version = "0.5.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.4.15"
|
||||
@@ -3685,12 +3700,16 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lru-cache"
|
||||
version = "0.1.2"
|
||||
name = "loom"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c"
|
||||
checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca"
|
||||
dependencies = [
|
||||
"linked-hash-map",
|
||||
"cfg-if",
|
||||
"generator",
|
||||
"scoped-tls",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3713,6 +3732,15 @@ dependencies = [
|
||||
"tendril",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "matchers"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558"
|
||||
dependencies = [
|
||||
"regex-automata 0.1.10",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "matches"
|
||||
version = "0.1.10"
|
||||
@@ -3773,6 +3801,25 @@ dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "moka"
|
||||
version = "0.12.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a9321642ca94a4282428e6ea4af8cc2ca4eac48ac7a6a4ea8f33f76d0ce70926"
|
||||
dependencies = [
|
||||
"crossbeam-channel",
|
||||
"crossbeam-epoch",
|
||||
"crossbeam-utils",
|
||||
"loom",
|
||||
"parking_lot",
|
||||
"portable-atomic",
|
||||
"rustc_version",
|
||||
"smallvec",
|
||||
"tagptr",
|
||||
"thiserror 1.0.69",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "muda"
|
||||
version = "0.16.1"
|
||||
@@ -3876,6 +3923,16 @@ dependencies = [
|
||||
"minimal-lexical",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nu-ansi-term"
|
||||
version = "0.46.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
|
||||
dependencies = [
|
||||
"overload",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-bigint"
|
||||
version = "0.4.6"
|
||||
@@ -4647,6 +4704,10 @@ name = "once_cell"
|
||||
version = "1.21.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||
dependencies = [
|
||||
"critical-section",
|
||||
"portable-atomic",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "opaque-debug"
|
||||
@@ -4750,6 +4811,12 @@ dependencies = [
|
||||
"thiserror 2.0.12",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "overload"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
|
||||
|
||||
[[package]]
|
||||
name = "p256"
|
||||
version = "0.13.2"
|
||||
@@ -5193,6 +5260,12 @@ dependencies = [
|
||||
"universal-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "portable-atomic"
|
||||
version = "1.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e"
|
||||
|
||||
[[package]]
|
||||
name = "powerfmt"
|
||||
version = "0.2.0"
|
||||
@@ -5620,8 +5693,17 @@ checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
"regex-automata 0.4.9",
|
||||
"regex-syntax 0.8.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
|
||||
dependencies = [
|
||||
"regex-syntax 0.6.29",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5632,9 +5714,15 @@ checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
"regex-syntax 0.8.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.6.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.5"
|
||||
@@ -5730,7 +5818,7 @@ dependencies = [
|
||||
"wasm-bindgen-futures",
|
||||
"wasm-streams",
|
||||
"web-sys",
|
||||
"webpki-roots 0.26.8",
|
||||
"webpki-roots",
|
||||
"windows-registry",
|
||||
]
|
||||
|
||||
@@ -5872,6 +5960,7 @@ version = "0.23.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "822ee9188ac4ec04a2f0531e55d035fb2de73f18b41a63c70c2712503b6fb13c"
|
||||
dependencies = [
|
||||
"log",
|
||||
"once_cell",
|
||||
"ring",
|
||||
"rustls-pki-types",
|
||||
@@ -5997,6 +6086,12 @@ dependencies = [
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scoped-tls"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.2.0"
|
||||
@@ -6308,6 +6403,15 @@ dependencies = [
|
||||
"digest 0.10.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sharded-slab"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shared_child"
|
||||
version = "1.0.1"
|
||||
@@ -6666,6 +6770,12 @@ dependencies = [
|
||||
"version-compare",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tagptr"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417"
|
||||
|
||||
[[package]]
|
||||
name = "tao"
|
||||
version = "0.32.8"
|
||||
@@ -7235,6 +7345,16 @@ dependencies = [
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thread_local"
|
||||
version = "1.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tiff"
|
||||
version = "0.9.1"
|
||||
@@ -7497,6 +7617,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"valuable",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-log"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
|
||||
dependencies = [
|
||||
"log",
|
||||
"once_cell",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-subscriber"
|
||||
version = "0.3.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008"
|
||||
dependencies = [
|
||||
"matchers",
|
||||
"nu-ansi-term",
|
||||
"once_cell",
|
||||
"regex",
|
||||
"sharded-slab",
|
||||
"smallvec",
|
||||
"thread_local",
|
||||
"tracing",
|
||||
"tracing-core",
|
||||
"tracing-log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7757,6 +7907,12 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "valuable"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
|
||||
|
||||
[[package]]
|
||||
name = "vcpkg"
|
||||
version = "0.2.15"
|
||||
@@ -8082,12 +8238,6 @@ dependencies = [
|
||||
"system-deps",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webpki-roots"
|
||||
version = "0.25.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1"
|
||||
|
||||
[[package]]
|
||||
name = "webpki-roots"
|
||||
version = "0.26.8"
|
||||
@@ -8108,7 +8258,7 @@ dependencies = [
|
||||
"windows 0.60.0",
|
||||
"windows-core 0.60.1",
|
||||
"windows-implement 0.59.0",
|
||||
"windows-interface",
|
||||
"windows-interface 0.59.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -8201,6 +8351,16 @@ dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows"
|
||||
version = "0.58.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6"
|
||||
dependencies = [
|
||||
"windows-core 0.58.0",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows"
|
||||
version = "0.60.0"
|
||||
@@ -8232,6 +8392,19 @@ dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.58.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99"
|
||||
dependencies = [
|
||||
"windows-implement 0.58.0",
|
||||
"windows-interface 0.58.0",
|
||||
"windows-result 0.2.0",
|
||||
"windows-strings 0.1.0",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.60.1"
|
||||
@@ -8239,9 +8412,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ca21a92a9cae9bf4ccae5cf8368dce0837100ddf6e6d57936749e85f152f6247"
|
||||
dependencies = [
|
||||
"windows-implement 0.59.0",
|
||||
"windows-interface",
|
||||
"windows-interface 0.59.1",
|
||||
"windows-link",
|
||||
"windows-result",
|
||||
"windows-result 0.3.2",
|
||||
"windows-strings 0.3.1",
|
||||
]
|
||||
|
||||
@@ -8252,9 +8425,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980"
|
||||
dependencies = [
|
||||
"windows-implement 0.60.0",
|
||||
"windows-interface",
|
||||
"windows-interface 0.59.1",
|
||||
"windows-link",
|
||||
"windows-result",
|
||||
"windows-result 0.3.2",
|
||||
"windows-strings 0.4.0",
|
||||
]
|
||||
|
||||
@@ -8268,6 +8441,17 @@ dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-implement"
|
||||
version = "0.58.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-implement"
|
||||
version = "0.59.0"
|
||||
@@ -8290,6 +8474,17 @@ dependencies = [
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-interface"
|
||||
version = "0.58.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-interface"
|
||||
version = "0.59.1"
|
||||
@@ -8323,11 +8518,20 @@ version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3"
|
||||
dependencies = [
|
||||
"windows-result",
|
||||
"windows-result 0.3.2",
|
||||
"windows-strings 0.3.1",
|
||||
"windows-targets 0.53.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-result"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e"
|
||||
dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-result"
|
||||
version = "0.3.2"
|
||||
@@ -8337,6 +8541,16 @@ dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-strings"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10"
|
||||
dependencies = [
|
||||
"windows-result 0.2.0",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-strings"
|
||||
version = "0.3.1"
|
||||
|
||||
@@ -4,8 +4,21 @@ use tauri_plugin_opener::OpenerExt;
|
||||
pub async fn open_url(url: String, app_handle: tauri::AppHandle) -> Result<(), String> {
|
||||
println!("Opening URL: {}", url);
|
||||
|
||||
match app_handle.opener().open_url(&url, None::<&str>) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) => Err(format!("Failed to open URL: {}", err)),
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
// Windows needs shell capability
|
||||
match app_handle.shell().open("", &url) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) => Err(format!("Failed to open URL: {}", err)),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
{
|
||||
// macOS and Linux work well with opener
|
||||
match app_handle.opener().open_url(&url, None::<&str>) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) => Err(format!("Failed to open URL: {}", err)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -1943,9 +1943,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/http-proxy-middleware": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz",
|
||||
"integrity": "sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==",
|
||||
"version": "2.0.9",
|
||||
"resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.9.tgz",
|
||||
"integrity": "sha512-c1IyJYLYppU574+YI7R4QyX2ystMtVXZwIdzazUIPIJsHuWNd+mho2j+bKoHftndicGj9yh+xjd+l0yj7VeT1Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
||||
@@ -1162,9 +1162,9 @@ http-parser-js@>=0.5.1:
|
||||
integrity sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==
|
||||
|
||||
http-proxy-middleware@^2.0.3:
|
||||
version "2.0.6"
|
||||
resolved "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz"
|
||||
integrity sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==
|
||||
version "2.0.9"
|
||||
resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-2.0.9.tgz#e9e63d68afaa4eee3d147f39149ab84c0c2815ef"
|
||||
integrity sha512-c1IyJYLYppU574+YI7R4QyX2ystMtVXZwIdzazUIPIJsHuWNd+mho2j+bKoHftndicGj9yh+xjd+l0yj7VeT1Q==
|
||||
dependencies:
|
||||
"@types/http-proxy" "^1.17.8"
|
||||
http-proxy "^1.18.1"
|
||||
|
||||
@@ -4,6 +4,6 @@ go 1.23.0
|
||||
|
||||
toolchain go1.23.3
|
||||
|
||||
require golang.org/x/net v0.36.0
|
||||
require golang.org/x/net v0.38.0
|
||||
|
||||
require golang.org/x/text v0.22.0 // indirect
|
||||
require golang.org/x/text v0.23.0 // indirect
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
golang.org/x/net v0.36.0 h1:vWF2fRbw4qslQsQzgFqZff+BItCvGFQqKzKIzx1rmoA=
|
||||
golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I=
|
||||
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
|
||||
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
||||
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
|
||||
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
||||
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
|
||||
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
|
||||
|
||||
Reference in New Issue
Block a user