Skip to main content

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::{
5    DeclaredRolesV1, LewesProtocolDetailsV1, NymNodeDataV1, OffsetDateTimeJsonSchemaWrapper,
6};
7use crate::pagination::{PaginatedResponse, Pagination};
8use nym_crypto::asymmetric::ed25519::serde_helpers::bs58_ed25519_pubkey;
9use nym_crypto::asymmetric::x25519::serde_helpers::bs58_x25519_pubkey;
10use nym_crypto::asymmetric::{ed25519, x25519};
11use nym_mixnet_contract_common::nym_node::Role;
12use nym_mixnet_contract_common::reward_params::Performance;
13use nym_mixnet_contract_common::{EpochId, Interval, NodeId};
14use nym_noise_keys::VersionedNoiseKeyV1;
15use serde::{Deserialize, Serialize};
16use std::collections::HashMap;
17use std::net::IpAddr;
18use time::OffsetDateTime;
19use utoipa::ToSchema;
20
21#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, utoipa::ToSchema)]
22pub struct SkimmedNodesWithMetadata {
23    pub nodes: Vec<SkimmedNodeV1>,
24    pub metadata: NodesResponseMetadata,
25}
26
27impl SkimmedNodesWithMetadata {
28    pub fn new(nodes: Vec<SkimmedNodeV1>, metadata: NodesResponseMetadata) -> Self {
29        SkimmedNodesWithMetadata { nodes, metadata }
30    }
31}
32
33#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, utoipa::ToSchema)]
34pub struct SemiSkimmedNodesWithMetadata {
35    pub nodes: Vec<SemiSkimmedNodeV1>,
36    pub metadata: NodesResponseMetadata,
37}
38
39impl SemiSkimmedNodesWithMetadata {
40    pub fn new(nodes: Vec<SemiSkimmedNodeV1>, metadata: NodesResponseMetadata) -> Self {
41        SemiSkimmedNodesWithMetadata { nodes, metadata }
42    }
43}
44
45#[derive(
46    Clone, Copy, Debug, Serialize, Deserialize, schemars::JsonSchema, utoipa::ToSchema, PartialEq,
47)]
48#[serde(rename_all = "kebab-case")]
49pub enum TopologyRequestStatus {
50    NoUpdates,
51    Fresh(Interval),
52}
53
54#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)]
55pub struct CachedNodesResponse<T: ToSchema> {
56    pub refreshed_at: OffsetDateTimeJsonSchemaWrapper,
57    pub nodes: Vec<T>,
58}
59
60impl<T: ToSchema> From<Vec<T>> for CachedNodesResponse<T> {
61    fn from(nodes: Vec<T>) -> Self {
62        CachedNodesResponse::new(nodes)
63    }
64}
65
66impl<T: ToSchema> CachedNodesResponse<T> {
67    pub fn new(nodes: Vec<T>) -> Self {
68        CachedNodesResponse {
69            refreshed_at: OffsetDateTime::now_utc().into(),
70            nodes,
71        }
72    }
73}
74
75#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, utoipa::ToSchema)]
76pub struct NodesResponseMetadata {
77    pub status: Option<TopologyRequestStatus>,
78    #[schema(value_type = u32)]
79    pub absolute_epoch_id: EpochId,
80    pub rotation_id: u32,
81    pub refreshed_at: OffsetDateTimeJsonSchemaWrapper,
82}
83
84impl NodesResponseMetadata {
85    pub fn consistency_check(&self, other: &NodesResponseMetadata) -> bool {
86        self.status == other.status
87            && self.absolute_epoch_id == other.absolute_epoch_id
88            && self.rotation_id == other.rotation_id
89    }
90
91    pub fn refreshed_at(&self) -> OffsetDateTime {
92        self.refreshed_at.into()
93    }
94}
95
96#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema)]
97// can't add any new fields here, even with #[serde(default)] and whatnot,
98// because it will break all clients using bincode : (
99pub struct PaginatedCachedNodesResponseV1<T> {
100    pub status: Option<TopologyRequestStatus>,
101    pub refreshed_at: OffsetDateTimeJsonSchemaWrapper,
102    pub nodes: PaginatedResponse<T>,
103}
104
105#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema)]
106pub struct PaginatedCachedNodesResponseV2<T> {
107    pub metadata: NodesResponseMetadata,
108    pub nodes: PaginatedResponse<T>,
109}
110
111impl<T> From<PaginatedCachedNodesResponseV2<T>> for PaginatedCachedNodesResponseV1<T> {
112    fn from(res: PaginatedCachedNodesResponseV2<T>) -> Self {
113        PaginatedCachedNodesResponseV1 {
114            status: res.metadata.status,
115            refreshed_at: res.metadata.refreshed_at,
116            nodes: res.nodes,
117        }
118    }
119}
120
121impl<T> PaginatedCachedNodesResponseV2<T> {
122    pub fn new_full(
123        absolute_epoch_id: EpochId,
124        rotation_id: u32,
125        refreshed_at: impl Into<OffsetDateTimeJsonSchemaWrapper>,
126        nodes: Vec<T>,
127    ) -> Self {
128        PaginatedCachedNodesResponseV2 {
129            nodes: PaginatedResponse {
130                pagination: Pagination {
131                    total: nodes.len(),
132                    page: 0,
133                    size: nodes.len(),
134                },
135                data: nodes,
136            },
137            metadata: NodesResponseMetadata {
138                refreshed_at: refreshed_at.into(),
139                status: None,
140                absolute_epoch_id,
141                rotation_id,
142            },
143        }
144    }
145
146    pub fn fresh(mut self, interval: Interval) -> Self {
147        self.metadata.status = Some(TopologyRequestStatus::Fresh(interval));
148        self
149    }
150
151    pub fn no_updates(absolute_epoch_id: EpochId, rotation_id: u32) -> Self {
152        PaginatedCachedNodesResponseV2 {
153            nodes: PaginatedResponse {
154                pagination: Pagination {
155                    total: 0,
156                    page: 0,
157                    size: 0,
158                },
159                data: Vec::new(),
160            },
161            metadata: NodesResponseMetadata {
162                refreshed_at: OffsetDateTime::now_utc().into(),
163                status: Some(TopologyRequestStatus::NoUpdates),
164                absolute_epoch_id,
165                rotation_id,
166            },
167        }
168    }
169}
170
171#[derive(Clone, Copy, Debug, Serialize, Deserialize, schemars::JsonSchema, utoipa::ToSchema)]
172#[serde(rename_all = "kebab-case")]
173pub enum NodeRoleQueryParam {
174    ActiveMixnode,
175
176    #[serde(alias = "entry", alias = "gateway")]
177    EntryGateway,
178
179    #[serde(alias = "exit")]
180    ExitGateway,
181}
182
183#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema, Default)]
184pub enum NodeRole {
185    // a properly active mixnode
186    Mixnode {
187        layer: u8,
188    },
189
190    #[serde(alias = "entry", alias = "gateway")]
191    EntryGateway,
192
193    #[serde(alias = "exit")]
194    ExitGateway,
195
196    // equivalent of node that's in rewarded set but not in the inactive set
197    Standby,
198
199    #[default]
200    Inactive,
201}
202
203impl NodeRole {
204    pub fn is_inactive(&self) -> bool {
205        matches!(self, NodeRole::Inactive)
206    }
207}
208
209impl From<Option<Role>> for NodeRole {
210    fn from(role: Option<Role>) -> Self {
211        match role {
212            Some(Role::EntryGateway) => NodeRole::EntryGateway,
213            Some(Role::Layer1) => NodeRole::Mixnode { layer: 1 },
214            Some(Role::Layer2) => NodeRole::Mixnode { layer: 2 },
215            Some(Role::Layer3) => NodeRole::Mixnode { layer: 3 },
216            Some(Role::ExitGateway) => NodeRole::ExitGateway,
217            Some(Role::Standby) => NodeRole::Standby,
218            None => NodeRole::Inactive,
219        }
220    }
221}
222
223#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)]
224pub struct BasicEntryInformation {
225    pub hostname: Option<String>,
226
227    pub ws_port: u16,
228    pub wss_port: Option<u16>,
229}
230
231// the bare minimum information needed to construct sphinx packets
232#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)]
233pub struct SkimmedNodeV1 {
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: DeclaredRolesV1,
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 SkimmedNodeV1 {
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 SemiSkimmedNodeV1 {
279    pub basic: SkimmedNodeV1,
280
281    pub x25519_noise_versioned_key: Option<VersionedNoiseKeyV1>,
282    // pub location:
283}
284
285#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)]
286pub struct FullFatNode {
287    pub expanded: SemiSkimmedNodeV1,
288
289    // kinda temporary for now to make as few changes as possible for now
290    pub self_described: Option<NymNodeDataV1>,
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}
304
305/// All the information required for sending packets between nodes (sphinx, noise, LP)
306#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)]
307pub struct SemiSkimmedNodeV3 {
308    /// Basic node information required for mixnet routing
309    pub basic: SkimmedNodeV1,
310
311    /// Noise key of the node
312    pub noise_key: Option<VersionedNoiseKeyV1>,
313
314    /// Build version of this node used as a hint in inferring the Ciphersuite compatibility
315    pub build_version: String,
316
317    /// Information required for establishing an LP connection
318    pub lp: Option<LewesProtocolDetailsV1>,
319}