1use crate::error::Result;
10use ant_protocol::get_port_from_multiaddr;
11use libp2p::PeerId;
12use serde::{Deserialize, Deserializer, Serializer, de::Error as DeError};
13use std::str::FromStr;
14
15pub type NodeServiceData = super::node_service_data_v2::NodeServiceDataV2;
17pub const NODE_SERVICE_DATA_SCHEMA_LATEST: u32 =
19 super::node_service_data_v2::NODE_SERVICE_DATA_SCHEMA_V2;
20
21impl<'de> Deserialize<'de> for NodeServiceData {
24 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
25 where
26 D: Deserializer<'de>,
27 {
28 let json_value = serde_json::Value::deserialize(deserializer)?;
29
30 let schema_version = match &json_value {
31 serde_json::Value::Object(obj) => obj.get("schema_version").and_then(|v| v.as_u64()),
32 _ => None,
33 };
34
35 match schema_version {
36 Some(2) => {
37 match super::node_service_data_v2::NodeServiceDataV2::deserialize_v2(
38 &mut serde_json::de::Deserializer::from_str(&json_value.to_string()),
39 ) {
40 Ok(v2) => Ok(v2),
41 Err(e) => Err(D::Error::custom(format!(
42 "Failed to deserialize as V2: {e}"
43 ))),
44 }
45 }
46 Some(1) => {
47 match serde_json::from_value::<super::node_service_data_v1::NodeServiceDataV1>(
48 json_value,
49 ) {
50 Ok(v1) => {
51 let v2: super::node_service_data_v2::NodeServiceDataV2 = v1.into();
52 Ok(v2)
53 }
54 Err(e) => Err(D::Error::custom(format!(
55 "Failed to deserialize as V1: {e}"
56 ))),
57 }
58 }
59 _ => {
60 match serde_json::from_value::<super::node_service_data_v0::NodeServiceDataV0>(
61 json_value,
62 ) {
63 Ok(v0) => {
64 let v1: super::node_service_data_v1::NodeServiceDataV1 = v0.into();
65 let v2: super::node_service_data_v2::NodeServiceDataV2 = v1.into();
66 Ok(v2)
67 }
68 Err(e) => Err(D::Error::custom(format!(
69 "Failed to deserialize as V0: {e}"
70 ))),
71 }
72 }
73 }
74 }
75}
76
77impl NodeServiceData {
78 pub fn get_antnode_port(&self) -> Option<u16> {
80 if let Some(multi_addrs) = &self.listen_addr {
82 println!("Listening addresses are defined");
83 for addr in multi_addrs {
84 if let Some(port) = get_port_from_multiaddr(addr) {
85 println!("Found port: {port}");
86 return Some(port);
87 }
88 }
89 }
90 None
91 }
92
93 pub fn get_critical_failure(&self) -> Option<(chrono::DateTime<chrono::Utc>, String)> {
95 const CRITICAL_FAILURE_LOG_FILE: &str = "critical_failure.log";
96
97 let log_path = self.log_dir_path.join(CRITICAL_FAILURE_LOG_FILE);
98
99 if let Ok(content) = std::fs::read_to_string(log_path)
100 && let Some((timestamp, message)) = content.split_once(']')
101 {
102 let timestamp_trimmed = timestamp.trim_start_matches('[').trim();
103 if let Ok(datetime) = timestamp_trimmed.parse::<chrono::DateTime<chrono::Utc>>() {
104 let message_trimmed = message
105 .trim()
106 .trim_start_matches("Node terminated due to: ");
107 return Some((datetime, message_trimmed.to_string()));
108 }
109 }
110
111 None
112 }
113
114 pub fn serialize_peer_id<S>(value: &Option<PeerId>, serializer: S) -> Result<S::Ok, S::Error>
115 where
116 S: Serializer,
117 {
118 if let Some(peer_id) = value {
119 return serializer.serialize_str(&peer_id.to_string());
120 }
121 serializer.serialize_none()
122 }
123
124 pub fn deserialize_peer_id<'de, D>(deserializer: D) -> Result<Option<PeerId>, D::Error>
125 where
126 D: Deserializer<'de>,
127 {
128 let s: Option<String> = Option::deserialize(deserializer)?;
129 if let Some(peer_id_str) = s {
130 PeerId::from_str(&peer_id_str)
131 .map(Some)
132 .map_err(DeError::custom)
133 } else {
134 Ok(None)
135 }
136 }
137
138 pub fn serialize_connected_peers<S>(
139 connected_peers: &Option<Vec<PeerId>>,
140 serializer: S,
141 ) -> Result<S::Ok, S::Error>
142 where
143 S: Serializer,
144 {
145 match connected_peers {
146 Some(peers) => {
147 let peer_strs: Vec<String> = peers.iter().map(|p| p.to_string()).collect();
148 serializer.serialize_some(&peer_strs)
149 }
150 None => serializer.serialize_none(),
151 }
152 }
153
154 pub fn deserialize_connected_peers<'de, D>(
155 deserializer: D,
156 ) -> Result<Option<Vec<PeerId>>, D::Error>
157 where
158 D: Deserializer<'de>,
159 {
160 let vec: Option<Vec<String>> = Option::deserialize(deserializer)?;
161 match vec {
162 Some(peer_strs) => {
163 let peers: Result<Vec<PeerId>, _> = peer_strs
164 .into_iter()
165 .map(|s| PeerId::from_str(&s).map_err(DeError::custom))
166 .collect();
167 peers.map(Some)
168 }
169 None => Ok(None),
170 }
171 }
172}
173
174#[cfg(test)]
175mod tests {
176 use std::{
177 net::{Ipv4Addr, SocketAddr},
178 path::PathBuf,
179 };
180
181 use ant_bootstrap::InitialPeersConfig;
182 use ant_evm::{AttoTokens, EvmNetwork, RewardsAddress};
183 use ant_logging::LogFormat;
184 use libp2p::Multiaddr;
185 use serde::Serialize;
186
187 use super::*;
188 use crate::{ServiceStatus, node::node_service_data_v1::NODE_SERVICE_DATA_SCHEMA_V1};
189
190 #[test]
197 fn fields_can_be_removed_without_breaking() {
198 let json_with_deprecated_field = serde_json::json!({
199 "dummy_field": "This field is not used in the v1 schema",
200 "schema_version": NODE_SERVICE_DATA_SCHEMA_V1,
201 "antnode_path": "/usr/bin/antnode",
202 "auto_restart": true,
203 "connected_peers": [
204 "12D3KooWDpJ7As7BWAwRMfu1VU2WCqNjvq387JEYKDBj4kx6nXTN"
205 ],
206 "data_dir_path": "/home/user/.local/share/safe/node/1",
207 "evm_network": "ArbitrumSepoliaTest",
208 "initial_peers_config": {
209 "first": false,
210 "local": false,
211 "addrs": [],
212 "network_contacts_url": [],
213 "disable_mainnet_contacts": false,
214 "ignore_cache": false,
215 "bootstrap_cache_dir": null
216 },
217 "listen_addr": [
218 "/ip4/127.0.0.1/udp/56215/quic-v1/p2p/12D3KooWDpJ7As7BWAwRMfu1VU2WCqNjvq387JEYKDBj4kx6nXTN"
219 ],
220 "log_dir_path": "/home/user/.local/share/safe/node/1/logs",
221 "log_format": "Default",
222 "max_archived_log_files": 5,
223 "max_log_files": 10,
224 "metrics_port": 8080,
225 "network_id": 1,
226 "node_ip": "127.0.0.1",
227 "node_port": 56215,
228 "no_upnp": false,
229 "number": 1,
230 "peer_id": "12D3KooWDpJ7As7BWAwRMfu1VU2WCqNjvq387JEYKDBj4kx6nXTN",
231 "pid": 12345,
232 "relay": true,
233 "rewards_address": "0x1234567890123456789012345678901234567890",
234 "reward_balance": "1000000000000000000",
235 "rpc_socket_addr": "127.0.0.1:8000",
236 "service_name": "safenode-1",
237 "status": "Running",
238 "user": "safe",
239 "user_mode": true,
240 "version": "0.1.0"
241 });
242
243 let service_data: Result<NodeServiceData, _> =
244 serde_json::from_value(json_with_deprecated_field);
245
246 assert!(
247 service_data.is_ok(),
248 "Failed to deserialize data with deprecated field 'disable_mainnet_contacts': {:?}",
249 service_data.err()
250 );
251
252 let data = service_data.unwrap();
253
254 assert_eq!(data.schema_version, NODE_SERVICE_DATA_SCHEMA_LATEST);
255 assert_eq!(data.service_name, "safenode-1");
256 assert_eq!(data.node_port, Some(56215));
257
258 assert!(!data.initial_peers_config.first);
259 assert!(!data.initial_peers_config.local);
260 assert!(data.initial_peers_config.addrs.is_empty());
261 assert!(data.initial_peers_config.network_contacts_url.is_empty());
262 assert!(!data.initial_peers_config.ignore_cache);
263 assert!(data.initial_peers_config.bootstrap_cache_dir.is_none());
264 }
265
266 #[test]
271 fn fields_can_be_added_without_breaking_with_serde_default() {
272 #[derive(Clone, Debug, Serialize, Deserialize)]
273 pub struct NodeServiceDataTest {
274 #[serde(default)]
275 pub dummy_addition: String, pub schema_version: u32,
277 pub antnode_path: PathBuf,
278 #[serde(default)]
279 pub auto_restart: bool,
280 #[serde(serialize_with = "NodeServiceData::serialize_connected_peers")]
281 pub connected_peers: Option<Vec<PeerId>>,
282 pub data_dir_path: PathBuf,
283 #[serde(default)]
284 pub evm_network: EvmNetwork,
285 pub initial_peers_config: InitialPeersConfig,
286 pub listen_addr: Option<Vec<Multiaddr>>,
287 pub log_dir_path: PathBuf,
288 pub log_format: Option<LogFormat>,
289 pub max_archived_log_files: Option<usize>,
290 pub max_log_files: Option<usize>,
291 #[serde(default)]
292 pub metrics_port: Option<u16>,
293 pub network_id: Option<u8>,
294 #[serde(default)]
295 pub node_ip: Option<Ipv4Addr>,
296 #[serde(default)]
297 pub node_port: Option<u16>,
298 pub no_upnp: bool,
299 pub number: u16,
300 #[serde(serialize_with = "NodeServiceData::serialize_peer_id")]
301 pub peer_id: Option<PeerId>,
302 pub pid: Option<u32>,
303 pub relay: bool,
304 #[serde(default)]
305 pub rewards_address: RewardsAddress,
306 pub reward_balance: Option<AttoTokens>,
307 pub rpc_socket_addr: SocketAddr,
308 pub service_name: String,
309 pub status: ServiceStatus,
310 pub user: Option<String>,
311 pub user_mode: bool,
312 pub version: String,
313 }
314
315 let json_with_deprecated_field = serde_json::json!({
316 "schema_version": NODE_SERVICE_DATA_SCHEMA_V1,
317 "antnode_path": "/usr/bin/antnode",
318 "auto_restart": true,
319 "connected_peers": [
320 "12D3KooWDpJ7As7BWAwRMfu1VU2WCqNjvq387JEYKDBj4kx6nXTN"
321 ],
322 "data_dir_path": "/home/user/.local/share/safe/node/1",
323 "evm_network": "ArbitrumSepoliaTest",
324 "initial_peers_config": {
325 "first": false,
326 "local": false,
327 "addrs": [],
328 "network_contacts_url": [],
329 "ignore_cache": false,
330 "bootstrap_cache_dir": null
331 },
332 "listen_addr": [
333 "/ip4/127.0.0.1/udp/56215/quic-v1/p2p/12D3KooWDpJ7As7BWAwRMfu1VU2WCqNjvq387JEYKDBj4kx6nXTN"
334 ],
335 "log_dir_path": "/home/user/.local/share/safe/node/1/logs",
336 "log_format": "Default",
337 "max_archived_log_files": 5,
338 "max_log_files": 10,
339 "metrics_port": 8080,
340 "network_id": 1,
341 "node_ip": "127.0.0.1",
342 "node_port": 56215,
343 "no_upnp": false,
344 "number": 1,
345 "peer_id": "12D3KooWDpJ7As7BWAwRMfu1VU2WCqNjvq387JEYKDBj4kx6nXTN",
346 "pid": 12345,
347 "relay": true,
348 "rewards_address": "0x1234567890123456789012345678901234567890",
349 "reward_balance": "1000000000000000000",
350 "rpc_socket_addr": "127.0.0.1:8000",
351 "service_name": "safenode-1",
352 "status": "Running",
353 "user": "safe",
354 "user_mode": true,
355 "version": "0.1.0"
356 });
357
358 let service_data: Result<NodeServiceDataTest, _> =
359 serde_json::from_value(json_with_deprecated_field);
360 assert!(
361 service_data.is_ok(),
362 "Failed to deserialize data with new field 'dummy_addition': {:?}",
363 service_data.err()
364 );
365 }
366
367 #[test]
372 fn fields_cannot_be_added_without_serde_default() {
373 #[derive(Clone, Debug, Serialize, Deserialize)]
374 pub struct NodeServiceDataTest {
375 pub dummy_addition: String, pub schema_version: u32,
377 pub antnode_path: PathBuf,
378 #[serde(default)]
379 pub auto_restart: bool,
380 #[serde(serialize_with = "NodeServiceData::serialize_connected_peers")]
381 pub connected_peers: Option<Vec<PeerId>>,
382 pub data_dir_path: PathBuf,
383 #[serde(default)]
384 pub evm_network: EvmNetwork,
385 pub initial_peers_config: InitialPeersConfig,
386 pub listen_addr: Option<Vec<Multiaddr>>,
387 pub log_dir_path: PathBuf,
388 pub log_format: Option<LogFormat>,
389 pub max_archived_log_files: Option<usize>,
390 pub max_log_files: Option<usize>,
391 #[serde(default)]
392 pub metrics_port: Option<u16>,
393 pub network_id: Option<u8>,
394 #[serde(default)]
395 pub node_ip: Option<Ipv4Addr>,
396 #[serde(default)]
397 pub node_port: Option<u16>,
398 pub no_upnp: bool,
399 pub number: u16,
400 #[serde(serialize_with = "NodeServiceData::serialize_peer_id")]
401 pub peer_id: Option<PeerId>,
402 pub pid: Option<u32>,
403 pub relay: bool,
404 #[serde(default)]
405 pub rewards_address: RewardsAddress,
406 pub reward_balance: Option<AttoTokens>,
407 pub rpc_socket_addr: SocketAddr,
408 pub service_name: String,
409 pub status: ServiceStatus,
410 pub user: Option<String>,
411 pub user_mode: bool,
412 pub version: String,
413 }
414
415 let json_with_deprecated_field = serde_json::json!({
416 "schema_version": NODE_SERVICE_DATA_SCHEMA_V1,
417 "antnode_path": "/usr/bin/antnode",
418 "auto_restart": true,
419 "connected_peers": [
420 "12D3KooWDpJ7As7BWAwRMfu1VU2WCqNjvq387JEYKDBj4kx6nXTN"
421 ],
422 "data_dir_path": "/home/user/.local/share/safe/node/1",
423 "evm_network": "ArbitrumSepoliaTest",
424 "initial_peers_config": {
425 "first": false,
426 "local": false,
427 "addrs": [],
428 "network_contacts_url": [],
429 "ignore_cache": false,
430 "bootstrap_cache_dir": null
431 },
432 "listen_addr": [
433 "/ip4/127.0.0.1/udp/56215/quic-v1/p2p/12D3KooWDpJ7As7BWAwRMfu1VU2WCqNjvq387JEYKDBj4kx6nXTN"
434 ],
435 "log_dir_path": "/home/user/.local/share/safe/node/1/logs",
436 "log_format": "Default",
437 "max_archived_log_files": 5,
438 "max_log_files": 10,
439 "metrics_port": 8080,
440 "network_id": 1,
441 "node_ip": "127.0.0.1",
442 "node_port": 56215,
443 "no_upnp": false,
444 "number": 1,
445 "peer_id": "12D3KooWDpJ7As7BWAwRMfu1VU2WCqNjvq387JEYKDBj4kx6nXTN",
446 "pid": 12345,
447 "relay": true,
448 "rewards_address": "0x1234567890123456789012345678901234567890",
449 "reward_balance": "1000000000000000000",
450 "rpc_socket_addr": "127.0.0.1:8000",
451 "service_name": "safenode-1",
452 "status": "Running",
453 "user": "safe",
454 "user_mode": true,
455 "version": "0.1.0"
456 });
457
458 let service_data: Result<NodeServiceDataTest, _> =
459 serde_json::from_value(json_with_deprecated_field);
460 assert!(
461 service_data.is_err(),
462 "We should not get an Ok().Deserialization should fail without serde default",
463 );
464 }
465
466 #[test]
467 fn enum_variants_can_be_added_without_breaking() {
468 #[derive(Clone, Debug, Serialize, Deserialize)]
469 pub enum TestEnum1 {
470 Variant1,
471 Variant2,
472 }
473
474 #[derive(Clone, Debug, Serialize, Deserialize)]
475 pub enum TestEnum2 {
476 Variant1,
477 Variant2,
478 Variant3,
479 }
480
481 let enum1 = TestEnum1::Variant1;
482 let enum1_json = serde_json::to_value(&enum1).unwrap();
483
484 let enum2: TestEnum2 = serde_json::from_value(enum1_json).unwrap();
485 assert!(matches!(enum2, TestEnum2::Variant1));
486 }
487
488 #[test]
489 fn enum_variants_cannot_be_removed_without_breaking() {
490 #[derive(Clone, Debug, Serialize, Deserialize)]
491 pub enum TestEnum1 {
492 Variant1,
493 Variant2,
494 }
495
496 #[derive(Clone, Debug, Serialize, Deserialize)]
497 pub enum TestEnum2 {
498 Variant1,
499 Variant2,
500 Variant3,
501 }
502
503 let enum2 = TestEnum2::Variant3;
504 let enum2_json = serde_json::to_value(&enum2).unwrap();
505
506 let result: Result<TestEnum1, _> = serde_json::from_value(enum2_json);
507 assert!(
508 result.is_err(),
509 "Deserialization should fail when removing variants"
510 );
511 }
512}