nym_api_requests/models/
node_status.rs

1// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::helpers::PlaceholderJsonSchemaImpl;
5use crate::pagination::PaginatedResponse;
6use cosmwasm_std::Decimal;
7use nym_contracts_common::{IdentityKey, NaiveFloat};
8use nym_crypto::asymmetric::ed25519;
9use nym_crypto::asymmetric::ed25519::serde_helpers::bs58_ed25519_pubkey;
10use nym_mixnet_contract_common::reward_params::Performance;
11use nym_mixnet_contract_common::NodeId;
12use schemars::JsonSchema;
13use serde::{Deserialize, Serialize};
14use std::time::Duration;
15use time::{Date, OffsetDateTime};
16use utoipa::ToSchema;
17
18use crate::models::DisplayRole;
19pub use config_score::*;
20
21pub type StakeSaturation = Decimal;
22
23#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, ToSchema)]
24#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
25#[cfg_attr(
26    feature = "generate-ts",
27    ts(
28        export,
29        export_to = "ts-packages/types/src/types/rust/StakeSaturationResponse.ts"
30    )
31)]
32pub struct StakeSaturationResponse {
33    #[cfg_attr(feature = "generate-ts", ts(type = "string"))]
34    #[schema(value_type = String)]
35    pub saturation: StakeSaturation,
36
37    #[cfg_attr(feature = "generate-ts", ts(type = "string"))]
38    #[schema(value_type = String)]
39    pub uncapped_saturation: StakeSaturation,
40    pub as_at: i64,
41}
42
43pub mod config_score {
44    use nym_contracts_common::NaiveFloat;
45    use serde::{Deserialize, Serialize};
46    use std::cmp::Ordering;
47    use utoipa::ToSchema;
48
49    #[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)]
50    pub struct ConfigScoreDataResponse {
51        pub parameters: ConfigScoreParams,
52        pub version_history: Vec<HistoricalNymNodeVersionEntry>,
53    }
54
55    #[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema, PartialEq)]
56    pub struct HistoricalNymNodeVersionEntry {
57        /// The unique, ordered, id of this particular entry
58        pub id: u32,
59
60        /// Data associated with this particular version
61        pub version_information: HistoricalNymNodeVersion,
62    }
63
64    impl PartialOrd for HistoricalNymNodeVersionEntry {
65        fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
66            // we only care about id for the purposes of ordering as they should have unique data
67            self.id.partial_cmp(&other.id)
68        }
69    }
70
71    impl From<nym_mixnet_contract_common::HistoricalNymNodeVersionEntry>
72        for HistoricalNymNodeVersionEntry
73    {
74        fn from(value: nym_mixnet_contract_common::HistoricalNymNodeVersionEntry) -> Self {
75            HistoricalNymNodeVersionEntry {
76                id: value.id,
77                version_information: value.version_information.into(),
78            }
79        }
80    }
81
82    #[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema, PartialEq)]
83    pub struct HistoricalNymNodeVersion {
84        /// Version of the nym node that is going to be used for determining the version score of a node.
85        /// note: value stored here is pre-validated `semver::Version`
86        pub semver: String,
87
88        /// Block height of when this version has been added to the contract
89        pub introduced_at_height: u64,
90        // for now ignore that field. it will give nothing useful to the users
91        //     pub difference_since_genesis: TotalVersionDifference,
92    }
93
94    impl From<nym_mixnet_contract_common::HistoricalNymNodeVersion> for HistoricalNymNodeVersion {
95        fn from(value: nym_mixnet_contract_common::HistoricalNymNodeVersion) -> Self {
96            HistoricalNymNodeVersion {
97                semver: value.semver,
98                introduced_at_height: value.introduced_at_height,
99            }
100        }
101    }
102
103    #[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)]
104    pub struct ConfigScoreParams {
105        /// Defines weights for calculating numbers of versions behind the current release.
106        pub version_weights: OutdatedVersionWeights,
107
108        /// Defines the parameters of the formula for calculating the version score
109        pub version_score_formula_params: VersionScoreFormulaParams,
110    }
111
112    /// Defines weights for calculating numbers of versions behind the current release.
113    #[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)]
114    pub struct OutdatedVersionWeights {
115        pub major: u32,
116        pub minor: u32,
117        pub patch: u32,
118        pub prerelease: u32,
119    }
120
121    /// Given the formula of version_score = penalty ^ (versions_behind_factor ^ penalty_scaling)
122    /// define the relevant parameters
123    #[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)]
124    pub struct VersionScoreFormulaParams {
125        pub penalty: f64,
126        pub penalty_scaling: f64,
127    }
128
129    impl From<nym_mixnet_contract_common::ConfigScoreParams> for ConfigScoreParams {
130        fn from(value: nym_mixnet_contract_common::ConfigScoreParams) -> Self {
131            ConfigScoreParams {
132                version_weights: value.version_weights.into(),
133                version_score_formula_params: value.version_score_formula_params.into(),
134            }
135        }
136    }
137
138    impl From<nym_mixnet_contract_common::OutdatedVersionWeights> for OutdatedVersionWeights {
139        fn from(value: nym_mixnet_contract_common::OutdatedVersionWeights) -> Self {
140            OutdatedVersionWeights {
141                major: value.major,
142                minor: value.minor,
143                patch: value.patch,
144                prerelease: value.prerelease,
145            }
146        }
147    }
148
149    impl From<nym_mixnet_contract_common::VersionScoreFormulaParams> for VersionScoreFormulaParams {
150        fn from(value: nym_mixnet_contract_common::VersionScoreFormulaParams) -> Self {
151            VersionScoreFormulaParams {
152                penalty: value.penalty.naive_to_f64(),
153                penalty_scaling: value.penalty_scaling.naive_to_f64(),
154            }
155        }
156    }
157}
158
159#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)]
160pub struct NodeRefreshBody {
161    #[serde(with = "bs58_ed25519_pubkey")]
162    #[schemars(with = "String")]
163    #[schema(value_type = String)]
164    pub node_identity: ed25519::PublicKey,
165
166    // a poor man's nonce
167    pub request_timestamp: i64,
168
169    #[schemars(with = "PlaceholderJsonSchemaImpl")]
170    #[schema(value_type = String)]
171    pub signature: ed25519::Signature,
172}
173
174impl NodeRefreshBody {
175    pub fn plaintext(node_identity: ed25519::PublicKey, request_timestamp: i64) -> Vec<u8> {
176        node_identity
177            .to_bytes()
178            .into_iter()
179            .chain(request_timestamp.to_be_bytes())
180            .chain(b"describe-cache-refresh-request".iter().copied())
181            .collect()
182    }
183
184    pub fn new(private_key: &ed25519::PrivateKey) -> Self {
185        let node_identity = private_key.public_key();
186        let request_timestamp = OffsetDateTime::now_utc().unix_timestamp();
187        let signature = private_key.sign(Self::plaintext(node_identity, request_timestamp));
188        NodeRefreshBody {
189            node_identity,
190            request_timestamp,
191            signature,
192        }
193    }
194
195    pub fn verify_signature(&self) -> bool {
196        self.node_identity
197            .verify(
198                Self::plaintext(self.node_identity, self.request_timestamp),
199                &self.signature,
200            )
201            .is_ok()
202    }
203
204    pub fn is_stale(&self) -> bool {
205        let Ok(encoded) = OffsetDateTime::from_unix_timestamp(self.request_timestamp) else {
206            return true;
207        };
208        let now = OffsetDateTime::now_utc();
209
210        if encoded > now {
211            return true;
212        }
213
214        if (encoded + Duration::from_secs(30)) < now {
215            return true;
216        }
217
218        false
219    }
220}
221
222#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, ToSchema)]
223pub struct UptimeResponse {
224    #[schema(value_type = u32)]
225    pub mix_id: NodeId,
226    // The same as node_performance.last_24h. Legacy
227    pub avg_uptime: u8,
228    pub node_performance: NodePerformance,
229}
230
231#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, ToSchema)]
232pub struct GatewayUptimeResponse {
233    pub identity: String,
234    // The same as node_performance.last_24h. Legacy
235    pub avg_uptime: u8,
236    pub node_performance: NodePerformance,
237}
238
239type Uptime = u8;
240
241#[derive(Clone, Serialize, Deserialize, schemars::JsonSchema, ToSchema)]
242pub struct MixnodeStatusReportResponse {
243    pub mix_id: NodeId,
244    pub identity: IdentityKey,
245    pub owner: String,
246    #[schema(value_type = u8)]
247    pub most_recent: Uptime,
248    #[schema(value_type = u8)]
249    pub last_hour: Uptime,
250    #[schema(value_type = u8)]
251    pub last_day: Uptime,
252}
253
254#[derive(Clone, Serialize, Deserialize, schemars::JsonSchema, ToSchema)]
255pub struct GatewayStatusReportResponse {
256    pub identity: String,
257    pub owner: String,
258    #[schema(value_type = u8)]
259    pub most_recent: Uptime,
260    #[schema(value_type = u8)]
261    pub last_hour: Uptime,
262    #[schema(value_type = u8)]
263    pub last_day: Uptime,
264}
265
266#[derive(Serialize, Deserialize, schemars::JsonSchema, ToSchema)]
267#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
268#[cfg_attr(
269    feature = "generate-ts",
270    ts(
271        export,
272        export_to = "ts-packages/types/src/types/rust/PerformanceHistoryResponse.ts"
273    )
274)]
275pub struct PerformanceHistoryResponse {
276    #[schema(value_type = u32)]
277    pub node_id: NodeId,
278    pub history: PaginatedResponse<HistoricalPerformanceResponse>,
279}
280
281#[derive(Serialize, Deserialize, schemars::JsonSchema, ToSchema)]
282#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
283#[cfg_attr(
284    feature = "generate-ts",
285    ts(
286        export,
287        export_to = "ts-packages/types/src/types/rust/UptimeHistoryResponse.ts"
288    )
289)]
290pub struct UptimeHistoryResponse {
291    #[schema(value_type = u32)]
292    pub node_id: NodeId,
293    pub history: PaginatedResponse<HistoricalUptimeResponse>,
294}
295
296#[derive(Clone, Serialize, Deserialize, schemars::JsonSchema, ToSchema)]
297#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
298#[cfg_attr(
299    feature = "generate-ts",
300    ts(
301        export,
302        export_to = "ts-packages/types/src/types/rust/HistoricalUptimeResponse.ts"
303    )
304)]
305pub struct HistoricalUptimeResponse {
306    #[schema(value_type = String, example = "1970-01-01")]
307    #[schemars(with = "String")]
308    #[cfg_attr(feature = "generate-ts", ts(type = "string"))]
309    pub date: Date,
310
311    pub uptime: Uptime,
312}
313
314#[derive(Clone, Serialize, Deserialize, schemars::JsonSchema, ToSchema)]
315#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
316#[cfg_attr(
317    feature = "generate-ts",
318    ts(
319        export,
320        export_to = "ts-packages/types/src/types/rust/HistoricalPerformanceResponse.ts"
321    )
322)]
323pub struct HistoricalPerformanceResponse {
324    #[schema(value_type = String, example = "1970-01-01")]
325    #[schemars(with = "String")]
326    #[cfg_attr(feature = "generate-ts", ts(type = "string"))]
327    pub date: Date,
328
329    pub performance: f64,
330}
331
332#[derive(Clone, Serialize, Deserialize, schemars::JsonSchema, ToSchema)]
333pub struct OldHistoricalUptimeResponse {
334    pub date: String,
335    #[schema(value_type = u8)]
336    pub uptime: Uptime,
337}
338
339#[derive(Clone, Serialize, Deserialize, schemars::JsonSchema, ToSchema)]
340pub struct MixnodeUptimeHistoryResponse {
341    pub mix_id: NodeId,
342    pub identity: String,
343    pub history: Vec<OldHistoricalUptimeResponse>,
344}
345
346#[derive(Clone, Serialize, Deserialize, schemars::JsonSchema, ToSchema)]
347pub struct GatewayUptimeHistoryResponse {
348    pub identity: String,
349    pub history: Vec<OldHistoricalUptimeResponse>,
350}
351
352#[derive(
353    Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, ToSchema, Default,
354)]
355#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
356#[cfg_attr(
357    feature = "generate-ts",
358    ts(
359        export,
360        export_to = "ts-packages/types/src/types/rust/MixnodeStatus.ts"
361    )
362)]
363#[serde(rename_all = "snake_case")]
364pub enum MixnodeStatus {
365    Active,   // in both the active set and the rewarded set
366    Standby,  // only in the rewarded set
367    Inactive, // in neither the rewarded set nor the active set, but is bonded
368    #[default]
369    NotFound, // doesn't even exist in the bonded set
370}
371impl MixnodeStatus {
372    pub fn is_active(&self) -> bool {
373        *self == MixnodeStatus::Active
374    }
375}
376
377#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, ToSchema)]
378#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
379#[cfg_attr(
380    feature = "generate-ts",
381    ts(
382        export,
383        export_to = "ts-packages/types/src/types/rust/MixnodeStatusResponse.ts"
384    )
385)]
386pub struct MixnodeStatusResponse {
387    pub status: MixnodeStatus,
388}
389
390#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, ToSchema)]
391pub struct NodePerformance {
392    #[schema(value_type = String)]
393    pub most_recent: Performance,
394    #[schema(value_type = String)]
395    pub last_hour: Performance,
396    #[schema(value_type = String)]
397    pub last_24h: Performance,
398}
399
400// imo for now there's no point in exposing more than that,
401// nym-api shouldn't be calculating apy or stake saturation for you.
402// it should just return its own metrics (performance) and then you can do with it as you wish
403#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, JsonSchema, ToSchema)]
404#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
405#[cfg_attr(
406    feature = "generate-ts",
407    ts(
408        export,
409        export_to = "ts-packages/types/src/types/rust/NodeAnnotation.ts"
410    )
411)]
412pub struct NodeAnnotation {
413    #[cfg_attr(feature = "generate-ts", ts(type = "string"))]
414    // legacy
415    #[schema(value_type = String)]
416    pub last_24h_performance: Performance,
417    pub current_role: Option<DisplayRole>,
418
419    pub detailed_performance: DetailedNodePerformance,
420}
421
422#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, JsonSchema, ToSchema)]
423#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
424#[cfg_attr(
425    feature = "generate-ts",
426    ts(
427        export,
428        export_to = "ts-packages/types/src/types/rust/DetailedNodePerformance.ts"
429    )
430)]
431#[non_exhaustive]
432pub struct DetailedNodePerformance {
433    /// routing_score * config_score
434    pub performance_score: f64,
435
436    pub routing_score: RoutingScore,
437    pub config_score: ConfigScore,
438}
439
440impl DetailedNodePerformance {
441    pub fn new(
442        performance_score: f64,
443        routing_score: RoutingScore,
444        config_score: ConfigScore,
445    ) -> DetailedNodePerformance {
446        Self {
447            performance_score,
448            routing_score,
449            config_score,
450        }
451    }
452
453    pub fn to_rewarding_performance(&self) -> Performance {
454        Performance::naive_try_from_f64(self.performance_score).unwrap_or_default()
455    }
456}
457
458#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, JsonSchema, ToSchema)]
459#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
460#[cfg_attr(
461    feature = "generate-ts",
462    ts(export, export_to = "ts-packages/types/src/types/rust/RoutingScore.ts")
463)]
464#[non_exhaustive]
465pub struct RoutingScore {
466    /// Total score after taking all the criteria into consideration
467    pub score: f64,
468}
469
470impl RoutingScore {
471    pub fn new(score: f64) -> RoutingScore {
472        Self { score }
473    }
474
475    pub const fn zero() -> RoutingScore {
476        RoutingScore { score: 0.0 }
477    }
478
479    pub fn legacy_performance(&self) -> Performance {
480        Performance::naive_try_from_f64(self.score).unwrap_or_default()
481    }
482}
483
484#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, JsonSchema, ToSchema)]
485#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
486#[cfg_attr(
487    feature = "generate-ts",
488    ts(export, export_to = "ts-packages/types/src/types/rust/ConfigScore.ts")
489)]
490#[non_exhaustive]
491pub struct ConfigScore {
492    /// Total score after taking all the criteria into consideration
493    pub score: f64,
494
495    pub versions_behind: Option<u32>,
496    pub self_described_api_available: bool,
497    pub accepted_terms_and_conditions: bool,
498    pub runs_nym_node_binary: bool,
499}
500
501impl ConfigScore {
502    pub fn new(
503        score: f64,
504        versions_behind: u32,
505        accepted_terms_and_conditions: bool,
506        runs_nym_node_binary: bool,
507    ) -> ConfigScore {
508        Self {
509            score,
510            versions_behind: Some(versions_behind),
511            self_described_api_available: true,
512            accepted_terms_and_conditions,
513            runs_nym_node_binary,
514        }
515    }
516
517    pub fn bad_semver() -> ConfigScore {
518        ConfigScore {
519            score: 0.0,
520            versions_behind: None,
521            self_described_api_available: true,
522            accepted_terms_and_conditions: false,
523            runs_nym_node_binary: false,
524        }
525    }
526
527    pub fn unavailable() -> ConfigScore {
528        ConfigScore {
529            score: 0.0,
530            versions_behind: None,
531            self_described_api_available: false,
532            accepted_terms_and_conditions: false,
533            runs_nym_node_binary: false,
534        }
535    }
536}
537
538#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, ToSchema)]
539#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
540#[cfg_attr(
541    feature = "generate-ts",
542    ts(
543        export,
544        export_to = "ts-packages/types/src/types/rust/AnnotationResponse.ts"
545    )
546)]
547pub struct AnnotationResponse {
548    #[schema(value_type = u32)]
549    pub node_id: NodeId,
550    pub annotation: Option<NodeAnnotation>,
551}
552
553#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, ToSchema)]
554#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
555#[cfg_attr(
556    feature = "generate-ts",
557    ts(
558        export,
559        export_to = "ts-packages/types/src/types/rust/NodePerformanceResponse.ts"
560    )
561)]
562pub struct NodePerformanceResponse {
563    #[schema(value_type = u32)]
564    pub node_id: NodeId,
565    pub performance: Option<f64>,
566}
567
568#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, ToSchema)]
569#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
570#[cfg_attr(
571    feature = "generate-ts",
572    ts(
573        export,
574        export_to = "ts-packages/types/src/types/rust/NodeDatePerformanceResponse.ts"
575    )
576)]
577pub struct NodeDatePerformanceResponse {
578    #[schema(value_type = u32)]
579    pub node_id: NodeId,
580    #[schema(value_type = String, example = "1970-01-01")]
581    #[schemars(with = "String")]
582    #[cfg_attr(feature = "generate-ts", ts(type = "string"))]
583    pub date: Date,
584    pub performance: Option<f64>,
585}