nym_api_requests/
nym_nodes.rs

1// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::models::{DeclaredRoles, NymNodeData, OffsetDateTimeJsonSchemaWrapper};
5use crate::pagination::{PaginatedResponse, Pagination};
6use nym_crypto::asymmetric::ed25519::serde_helpers::bs58_ed25519_pubkey;
7use nym_crypto::asymmetric::x25519::serde_helpers::bs58_x25519_pubkey;
8use nym_crypto::asymmetric::{ed25519, x25519};
9use nym_mixnet_contract_common::nym_node::Role;
10use nym_mixnet_contract_common::reward_params::Performance;
11use nym_mixnet_contract_common::{EpochId, Interval, NodeId};
12use nym_noise_keys::VersionedNoiseKey;
13use serde::{Deserialize, Serialize};
14use std::collections::HashMap;
15use std::net::IpAddr;
16use time::OffsetDateTime;
17use utoipa::ToSchema;
18
19#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, utoipa::ToSchema)]
20pub struct SkimmedNodesWithMetadata {
21    pub nodes: Vec<SkimmedNode>,
22    pub metadata: NodesResponseMetadata,
23}
24
25impl SkimmedNodesWithMetadata {
26    pub fn new(nodes: Vec<SkimmedNode>, metadata: NodesResponseMetadata) -> Self {
27        SkimmedNodesWithMetadata { nodes, metadata }
28    }
29}
30
31#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, utoipa::ToSchema)]
32pub struct SemiSkimmedNodesWithMetadata {
33    pub nodes: Vec<SemiSkimmedNode>,
34    pub metadata: NodesResponseMetadata,
35}
36
37impl SemiSkimmedNodesWithMetadata {
38    pub fn new(nodes: Vec<SemiSkimmedNode>, metadata: NodesResponseMetadata) -> Self {
39        SemiSkimmedNodesWithMetadata { nodes, metadata }
40    }
41}
42
43#[derive(
44    Clone, Copy, Debug, Serialize, Deserialize, schemars::JsonSchema, utoipa::ToSchema, PartialEq,
45)]
46#[serde(rename_all = "kebab-case")]
47pub enum TopologyRequestStatus {
48    NoUpdates,
49    Fresh(Interval),
50}
51
52#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)]
53pub struct CachedNodesResponse<T: ToSchema> {
54    pub refreshed_at: OffsetDateTimeJsonSchemaWrapper,
55    pub nodes: Vec<T>,
56}
57
58impl<T: ToSchema> From<Vec<T>> for CachedNodesResponse<T> {
59    fn from(nodes: Vec<T>) -> Self {
60        CachedNodesResponse::new(nodes)
61    }
62}
63
64impl<T: ToSchema> CachedNodesResponse<T> {
65    pub fn new(nodes: Vec<T>) -> Self {
66        CachedNodesResponse {
67            refreshed_at: OffsetDateTime::now_utc().into(),
68            nodes,
69        }
70    }
71}
72
73#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, utoipa::ToSchema)]
74pub struct NodesResponseMetadata {
75    pub status: Option<TopologyRequestStatus>,
76    #[schema(value_type = u32)]
77    pub absolute_epoch_id: EpochId,
78    pub rotation_id: u32,
79    pub refreshed_at: OffsetDateTimeJsonSchemaWrapper,
80}
81
82impl NodesResponseMetadata {
83    pub fn consistency_check(&self, other: &NodesResponseMetadata) -> bool {
84        self.status == other.status
85            && self.absolute_epoch_id == other.absolute_epoch_id
86            && self.rotation_id == other.rotation_id
87    }
88
89    pub fn refreshed_at(&self) -> OffsetDateTime {
90        self.refreshed_at.into()
91    }
92}
93
94#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema)]
95// can't add any new fields here, even with #[serde(default)] and whatnot,
96// because it will break all clients using bincode : (
97pub struct PaginatedCachedNodesResponseV1<T> {
98    pub status: Option<TopologyRequestStatus>,
99    pub refreshed_at: OffsetDateTimeJsonSchemaWrapper,
100    pub nodes: PaginatedResponse<T>,
101}
102
103#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema)]
104pub struct PaginatedCachedNodesResponseV2<T> {
105    pub metadata: NodesResponseMetadata,
106    pub nodes: PaginatedResponse<T>,
107}
108
109impl<T> From<PaginatedCachedNodesResponseV2<T>> for PaginatedCachedNodesResponseV1<T> {
110    fn from(res: PaginatedCachedNodesResponseV2<T>) -> Self {
111        PaginatedCachedNodesResponseV1 {
112            status: res.metadata.status,
113            refreshed_at: res.metadata.refreshed_at,
114            nodes: res.nodes,
115        }
116    }
117}
118
119impl<T> PaginatedCachedNodesResponseV2<T> {
120    pub fn new_full(
121        absolute_epoch_id: EpochId,
122        rotation_id: u32,
123        refreshed_at: impl Into<OffsetDateTimeJsonSchemaWrapper>,
124        nodes: Vec<T>,
125    ) -> Self {
126        PaginatedCachedNodesResponseV2 {
127            nodes: PaginatedResponse {
128                pagination: Pagination {
129                    total: nodes.len(),
130                    page: 0,
131                    size: nodes.len(),
132                },
133                data: nodes,
134            },
135            metadata: NodesResponseMetadata {
136                refreshed_at: refreshed_at.into(),
137                status: None,
138                absolute_epoch_id,
139                rotation_id,
140            },
141        }
142    }
143
144    pub fn fresh(mut self, interval: Interval) -> Self {
145        self.metadata.status = Some(TopologyRequestStatus::Fresh(interval));
146        self
147    }
148
149    pub fn no_updates(absolute_epoch_id: EpochId, rotation_id: u32) -> Self {
150        PaginatedCachedNodesResponseV2 {
151            nodes: PaginatedResponse {
152                pagination: Pagination {
153                    total: 0,
154                    page: 0,
155                    size: 0,
156                },
157                data: Vec::new(),
158            },
159            metadata: NodesResponseMetadata {
160                refreshed_at: OffsetDateTime::now_utc().into(),
161                status: Some(TopologyRequestStatus::NoUpdates),
162                absolute_epoch_id,
163                rotation_id,
164            },
165        }
166    }
167}
168
169#[derive(Clone, Copy, Debug, Serialize, Deserialize, schemars::JsonSchema, utoipa::ToSchema)]
170#[serde(rename_all = "kebab-case")]
171pub enum NodeRoleQueryParam {
172    ActiveMixnode,
173
174    #[serde(alias = "entry", alias = "gateway")]
175    EntryGateway,
176
177    #[serde(alias = "exit")]
178    ExitGateway,
179}
180
181#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema, Default)]
182pub enum NodeRole {
183    // a properly active mixnode
184    Mixnode {
185        layer: u8,
186    },
187
188    #[serde(alias = "entry", alias = "gateway")]
189    EntryGateway,
190
191    #[serde(alias = "exit")]
192    ExitGateway,
193
194    // equivalent of node that's in rewarded set but not in the inactive set
195    Standby,
196
197    #[default]
198    Inactive,
199}
200
201impl NodeRole {
202    pub fn is_inactive(&self) -> bool {
203        matches!(self, NodeRole::Inactive)
204    }
205}
206
207impl From<Option<Role>> for NodeRole {
208    fn from(role: Option<Role>) -> Self {
209        match role {
210            Some(Role::EntryGateway) => NodeRole::EntryGateway,
211            Some(Role::Layer1) => NodeRole::Mixnode { layer: 1 },
212            Some(Role::Layer2) => NodeRole::Mixnode { layer: 2 },
213            Some(Role::Layer3) => NodeRole::Mixnode { layer: 3 },
214            Some(Role::ExitGateway) => NodeRole::ExitGateway,
215            Some(Role::Standby) => NodeRole::Standby,
216            None => NodeRole::Inactive,
217        }
218    }
219}
220
221#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)]
222pub struct BasicEntryInformation {
223    pub hostname: Option<String>,
224
225    pub ws_port: u16,
226    pub wss_port: Option<u16>,
227}
228
229// the bare minimum information needed to construct sphinx packets
230#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)]
231pub struct SkimmedNode {
232    // in directory v3 all nodes (mixnodes AND gateways) will have a unique id
233    // but to keep structure consistent, introduce this field now
234    #[schema(value_type = u32)]
235    pub node_id: NodeId,
236
237    #[serde(with = "bs58_ed25519_pubkey")]
238    #[schemars(with = "String")]
239    #[schema(value_type = String)]
240    pub ed25519_identity_pubkey: ed25519::PublicKey,
241
242    #[schema(value_type = Vec<String>)]
243    pub ip_addresses: Vec<IpAddr>,
244
245    pub mix_port: u16,
246
247    #[serde(with = "bs58_x25519_pubkey")]
248    #[schemars(with = "String")]
249    #[schema(value_type = String)]
250    pub x25519_sphinx_pubkey: x25519::PublicKey,
251
252    #[serde(alias = "epoch_role")]
253    pub role: NodeRole,
254
255    // needed for the purposes of sending appropriate test packets
256    #[serde(default)]
257    pub supported_roles: DeclaredRoles,
258
259    pub entry: Option<BasicEntryInformation>,
260
261    /// Average node performance in last 24h period
262    #[schema(value_type = String)]
263    pub performance: Performance,
264}
265
266impl SkimmedNode {
267    pub fn get_mix_layer(&self) -> Option<u8> {
268        match self.role {
269            NodeRole::Mixnode { layer } => Some(layer),
270            _ => None,
271        }
272    }
273}
274
275// an intermediate variant that exposes additional data such as noise keys but without
276// the full fat of the self-described data
277#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)]
278pub struct SemiSkimmedNode {
279    pub basic: SkimmedNode,
280
281    pub x25519_noise_versioned_key: Option<VersionedNoiseKey>,
282    // pub location:
283}
284
285#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)]
286pub struct FullFatNode {
287    pub expanded: SemiSkimmedNode,
288
289    // kinda temporary for now to make as few changes as possible for now
290    pub self_described: Option<NymNodeData>,
291}
292
293#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, ToSchema)]
294pub struct NodesByAddressesRequestBody {
295    #[schema(value_type = Vec<String>)]
296    pub addresses: Vec<IpAddr>,
297}
298
299#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, ToSchema)]
300pub struct NodesByAddressesResponse {
301    #[schema(value_type = HashMap<String, Option<u32>>)]
302    pub existence: HashMap<IpAddr, Option<NodeId>>,
303}