avalanche_types/jsonrpc/
info.rs

1//! Avalanche JSON-RPC API info request and responses.
2use std::{
3    collections::HashMap,
4    io::{self, Error, ErrorKind},
5    net::{IpAddr, Ipv4Addr, SocketAddr},
6};
7
8use crate::{
9    ids::{self, node},
10    jsonrpc,
11    key::bls,
12};
13use chrono::{DateTime, Utc};
14use serde::{Deserialize, Serialize};
15use serde_with::{serde_as, DisplayFromStr};
16
17/// ref. <https://docs.avax.network/build/avalanchego-apis/info/#infogetnetworkname>
18#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
19pub struct GetNetworkNameResponse {
20    pub jsonrpc: String,
21    pub id: u32,
22
23    #[serde(skip_serializing_if = "Option::is_none")]
24    pub result: Option<GetNetworkNameResult>,
25
26    #[serde(skip_serializing_if = "Option::is_none")]
27    pub error: Option<jsonrpc::ResponseError>,
28}
29
30/// ref. <https://docs.avax.network/build/avalanchego-apis/info/#infogetnetworkname>
31#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
32#[serde(rename_all = "camelCase")]
33#[derive(Default)]
34pub struct GetNetworkNameResult {
35    pub network_name: String,
36}
37
38/// ref. <https://docs.avax.network/build/avalanchego-apis/info/#infogetnetworkid>
39#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
40pub struct GetNetworkIdResponse {
41    pub jsonrpc: String,
42    pub id: u32,
43
44    #[serde(skip_serializing_if = "Option::is_none")]
45    pub result: Option<GetNetworkIdResult>,
46
47    #[serde(skip_serializing_if = "Option::is_none")]
48    pub error: Option<jsonrpc::ResponseError>,
49}
50
51/// ref. <https://docs.avax.network/build/avalanchego-apis/info/#infogetnetworkid>
52#[serde_as]
53#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
54pub struct GetNetworkIdResult {
55    #[serde(rename = "networkID")]
56    #[serde_as(as = "DisplayFromStr")]
57    pub network_id: u32,
58}
59
60impl Default for GetNetworkIdResult {
61    fn default() -> Self {
62        Self { network_id: 1 }
63    }
64}
65
66/// RUST_LOG=debug cargo test --package avalanche-types --lib -- jsonrpc::info::test_get_network_id --exact --show-output
67#[test]
68fn test_get_network_id() {
69    // ref. https://docs.avax.network/build/avalanchego-apis/info/#infogetnetworkid
70    let resp: GetNetworkIdResponse = serde_json::from_str(
71        "
72
73{
74    \"jsonrpc\": \"2.0\",
75    \"result\": {
76        \"networkID\": \"9999999\"
77    },
78    \"id\": 1
79}
80
81",
82    )
83    .unwrap();
84
85    let expected = GetNetworkIdResponse {
86        jsonrpc: "2.0".to_string(),
87        id: 1,
88        result: Some(GetNetworkIdResult {
89            network_id: 9999999_u32,
90        }),
91        error: None,
92    };
93    assert_eq!(resp, expected);
94}
95
96/// ref. <https://docs.avax.network/build/avalanchego-apis/info/#infogetblockchainid>
97#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
98pub struct GetBlockchainIdResponse {
99    pub jsonrpc: String,
100    pub id: u32,
101
102    #[serde(skip_serializing_if = "Option::is_none")]
103    pub result: Option<GetBlockchainIdResult>,
104
105    #[serde(skip_serializing_if = "Option::is_none")]
106    pub error: Option<jsonrpc::ResponseError>,
107}
108
109/// ref. <https://docs.avax.network/build/avalanchego-apis/info/#infogetblockchainid>
110#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone, Default)]
111pub struct GetBlockchainIdResult {
112    #[serde(rename = "blockchainID")]
113    pub blockchain_id: ids::Id,
114}
115
116/// RUST_LOG=debug cargo test --package avalanche-types --lib -- jsonrpc::info::test_get_blockchain_id --exact --show-output
117#[test]
118fn test_get_blockchain_id() {
119    use std::str::FromStr;
120
121    // ref. https://docs.avax.network/build/avalanchego-apis/info/#infogetblockchainid
122    let resp: GetBlockchainIdResponse = serde_json::from_str(
123        "
124
125{
126    \"jsonrpc\": \"2.0\",
127    \"result\": {
128        \"blockchainID\": \"sV6o671RtkGBcno1FiaDbVcFv2sG5aVXMZYzKdP4VQAWmJQnM\"
129    },
130    \"id\": 1
131}
132
133",
134    )
135    .unwrap();
136
137    let expected = GetBlockchainIdResponse {
138        jsonrpc: "2.0".to_string(),
139        id: 1,
140        result: Some(GetBlockchainIdResult {
141            blockchain_id: ids::Id::from_str("sV6o671RtkGBcno1FiaDbVcFv2sG5aVXMZYzKdP4VQAWmJQnM")
142                .unwrap(),
143        }),
144        error: None,
145    };
146    assert_eq!(resp, expected);
147}
148
149/// ref. <https://docs.avax.network/build/avalanchego-apis/info/#infogetnodeid>
150#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
151pub struct GetNodeIdResponse {
152    pub jsonrpc: String,
153    pub id: u32,
154
155    #[serde(skip_serializing_if = "Option::is_none")]
156    pub result: Option<GetNodeIdResult>,
157
158    #[serde(skip_serializing_if = "Option::is_none")]
159    pub error: Option<jsonrpc::ResponseError>,
160}
161
162/// ref. <https://docs.avax.network/build/avalanchego-apis/info/#infogetnodeid>
163#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone, Default)]
164pub struct GetNodeIdResult {
165    #[serde(rename = "nodeID")]
166    pub node_id: node::Id,
167    #[serde(rename = "nodePOP")]
168    pub node_pop: Option<bls::ProofOfPossession>,
169}
170
171/// RUST_LOG=debug cargo test --package avalanche-types --lib -- jsonrpc::info::test_get_node_id --exact --show-output
172#[test]
173fn test_get_node_id() {
174    use std::str::FromStr;
175
176    // ref. <https://docs.avax.network/build/avalanchego-apis/info/#infogetnodeid>
177    let resp: GetNodeIdResponse = serde_json::from_str(
178        "
179
180{
181    \"jsonrpc\": \"2.0\",
182    \"result\": {
183        \"nodeID\": \"NodeID-5mb46qkSBj81k9g9e4VFjGGSbaaSLFRzD\"
184    },
185    \"id\": 1
186}
187
188",
189    )
190    .unwrap();
191    let expected = GetNodeIdResponse {
192        jsonrpc: "2.0".to_string(),
193        id: 1,
194        result: Some(GetNodeIdResult {
195            node_id: node::Id::from_str("NodeID-5mb46qkSBj81k9g9e4VFjGGSbaaSLFRzD").unwrap(),
196            ..Default::default()
197        }),
198        error: None,
199    };
200    assert_eq!(resp, expected);
201
202    let resp: GetNodeIdResponse = serde_json::from_str(
203        "
204
205{
206    \"jsonrpc\": \"2.0\",
207    \"result\": {
208        \"nodeID\": \"NodeID-5mb46qkSBj81k9g9e4VFjGGSbaaSLFRzD\",
209        \"nodePOP\": {
210            \"publicKey\": \"0x8f95423f7142d00a48e1014a3de8d28907d420dc33b3052a6dee03a3f2941a393c2351e354704ca66a3fc29870282e15\",
211            \"proofOfPossession\": \"0x86a3ab4c45cfe31cae34c1d06f212434ac71b1be6cfe046c80c162e057614a94a5bc9f1ded1a7029deb0ba4ca7c9b71411e293438691be79c2dbf19d1ca7c3eadb9c756246fc5de5b7b89511c7d7302ae051d9e03d7991138299b5ed6a570a98\"
212        }
213    },
214    \"id\": 1
215}
216
217",
218    )
219    .unwrap();
220    let expected = GetNodeIdResponse {
221        jsonrpc: "2.0".to_string(),
222        id: 1,
223        result: Some(GetNodeIdResult {
224            node_id: node::Id::from_str("NodeID-5mb46qkSBj81k9g9e4VFjGGSbaaSLFRzD").unwrap(),
225            node_pop: Some(bls::ProofOfPossession {
226                public_key: hex::decode("0x8f95423f7142d00a48e1014a3de8d28907d420dc33b3052a6dee03a3f2941a393c2351e354704ca66a3fc29870282e15".trim_start_matches("0x")).unwrap(),
227                proof_of_possession: hex::decode("0x86a3ab4c45cfe31cae34c1d06f212434ac71b1be6cfe046c80c162e057614a94a5bc9f1ded1a7029deb0ba4ca7c9b71411e293438691be79c2dbf19d1ca7c3eadb9c756246fc5de5b7b89511c7d7302ae051d9e03d7991138299b5ed6a570a98".trim_start_matches("0x")).unwrap(),
228                ..Default::default()
229            }),
230        }),
231        error: None,
232    };
233    assert_eq!(resp, expected);
234}
235
236/// ref. <https://docs.avax.network/build/avalanchego-apis/info/#infogetnodeip>
237#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
238pub struct GetNodeIpResponse {
239    pub jsonrpc: String,
240    pub id: u32,
241
242    #[serde(skip_serializing_if = "Option::is_none")]
243    pub result: Option<GetNodeIpResult>,
244
245    #[serde(skip_serializing_if = "Option::is_none")]
246    pub error: Option<jsonrpc::ResponseError>,
247}
248
249/// ref. <https://docs.avax.network/build/avalanchego-apis/info/#infogetnodeip>
250#[serde_as]
251#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
252pub struct GetNodeIpResult {
253    #[serde_as(as = "crate::codec::serde::ip_port::IpPort")]
254    pub ip: SocketAddr,
255}
256
257impl Default for GetNodeIpResult {
258    fn default() -> Self {
259        Self {
260            ip: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 9651),
261        }
262    }
263}
264/// RUST_LOG=debug cargo test --package avalanche-types --lib -- jsonrpc::info::test_get_node_id --exact --show-output
265#[test]
266fn test_get_node_ip() {
267    // ref. <https://docs.avax.network/build/avalanchego-apis/info/#infogetnodeid>
268    let resp: GetNodeIpResponse = serde_json::from_str(
269        "
270
271{
272    \"jsonrpc\": \"2.0\",
273    \"result\": {
274        \"ip\": \"192.168.1.1:9651\"
275    },
276    \"id\": 1
277}
278
279",
280    )
281    .unwrap();
282    let expected = GetNodeIpResponse {
283        jsonrpc: "2.0".to_string(),
284        id: 1,
285        result: Some(GetNodeIpResult {
286            ip: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1)), 9651),
287        }),
288        error: None,
289    };
290    assert_eq!(resp, expected);
291}
292
293/// ref. <https://docs.avax.network/build/avalanchego-apis/info/#infogetnodeversion>
294#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
295pub struct GetNodeVersionResponse {
296    pub jsonrpc: String,
297    pub id: u32,
298
299    #[serde(skip_serializing_if = "Option::is_none")]
300    pub result: Option<GetNodeVersionResult>,
301
302    #[serde(skip_serializing_if = "Option::is_none")]
303    pub error: Option<jsonrpc::ResponseError>,
304}
305
306/// ref. <https://docs.avax.network/build/avalanchego-apis/info/#infogetnodeversion>
307#[derive(Default, Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
308#[serde(rename_all = "camelCase")]
309pub struct GetNodeVersionResult {
310    pub version: String,
311    pub database_version: String,
312    pub git_commit: String,
313    pub vm_versions: VmVersions,
314    pub rpc_protocol_version: String,
315}
316
317/// ref. <https://docs.avax.network/build/avalanchego-apis/info/#infogetnodeversion>
318#[derive(Default, Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
319#[serde(rename_all = "camelCase")]
320pub struct VmVersions {
321    pub avm: String,
322    pub evm: String,
323    pub platform: String,
324    #[serde(flatten)]
325    pub subnets: HashMap<String, String>,
326}
327
328/// RUST_LOG=debug cargo test --package avalanche-types --lib -- jsonrpc::info::test_get_node_version --exact --show-output
329#[test]
330fn test_get_node_version() {
331    let resp: GetNodeVersionResponse = serde_json::from_str(
332        r#"
333{
334    "jsonrpc": "2.0",
335    "result": {
336        "version": "avalanche/1.10.1",
337        "databaseVersion": "v1.4.5",
338        "rpcProtocolVersion": "26",
339        "gitCommit": "ef6a2a2f7facd8fbefd5fb2ac9c4908c2bcae3e2",
340        "vmVersions": {
341          "avm": "v1.10.1",
342          "evm": "v0.12.1",
343          "platform": "v1.10.1",
344          "subnet-evm": "v0.5.1"
345        }
346    },
347    "id": 1
348}
349"#,
350    )
351    .unwrap();
352    let expected = GetNodeVersionResponse {
353        jsonrpc: "2.0".to_string(),
354        id: 1,
355        result: Some(GetNodeVersionResult {
356            version: String::from("avalanche/1.10.1"),
357            database_version: String::from("v1.4.5"),
358            git_commit: String::from("ef6a2a2f7facd8fbefd5fb2ac9c4908c2bcae3e2"),
359            vm_versions: VmVersions {
360                avm: String::from("v1.10.1"),
361                evm: String::from("v0.12.1"),
362                platform: String::from("v1.10.1"),
363                subnets: HashMap::from([(String::from("subnet-evm"), String::from("v0.5.1"))]),
364            },
365            rpc_protocol_version: String::from("26"),
366        }),
367        error: None,
368    };
369    assert_eq!(resp, expected);
370}
371
372/// ref. <https://docs.avax.network/build/avalanchego-apis/info/#infogetvms>
373#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
374pub struct GetVmsResponse {
375    pub jsonrpc: String,
376    pub id: u32,
377
378    #[serde(skip_serializing_if = "Option::is_none")]
379    pub result: Option<GetVmsResult>,
380
381    #[serde(skip_serializing_if = "Option::is_none")]
382    pub error: Option<jsonrpc::ResponseError>,
383}
384
385/// ref. <https://docs.avax.network/build/avalanchego-apis/info/#infogetvms>
386#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
387#[serde(rename_all = "camelCase")]
388#[derive(Default)]
389pub struct GetVmsResult {
390    #[serde(skip_serializing_if = "Option::is_none")]
391    pub vms: Option<HashMap<String, Vec<String>>>,
392}
393
394/// ref. <https://docs.avax.network/build/avalanchego-apis/info/#infoisbootstrapped>
395#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
396pub struct IsBootstrappedResponse {
397    pub jsonrpc: String,
398    pub id: u32,
399
400    #[serde(skip_serializing_if = "Option::is_none")]
401    pub result: Option<IsBootstrappedResult>,
402
403    #[serde(skip_serializing_if = "Option::is_none")]
404    pub error: Option<jsonrpc::ResponseError>,
405}
406
407/// ref. <https://docs.avax.network/build/avalanchego-apis/info/#infoisbootstrapped>
408#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
409#[serde(rename_all = "camelCase")]
410#[derive(Default)]
411pub struct IsBootstrappedResult {
412    pub is_bootstrapped: bool,
413}
414
415/// ref. <https://docs.avax.network/build/avalanchego-apis/info/#infogettxfee>
416#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
417pub struct GetTxFeeResponse {
418    pub jsonrpc: String,
419    pub id: u32,
420
421    #[serde(skip_serializing_if = "Option::is_none")]
422    pub result: Option<GetTxFeeResult>,
423
424    #[serde(skip_serializing_if = "Option::is_none")]
425    pub error: Option<jsonrpc::ResponseError>,
426}
427
428/// ref. <https://docs.avax.network/build/avalanchego-apis/info/#infogettxfee>
429#[serde_as]
430#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
431#[serde(rename_all = "camelCase")]
432#[derive(Default)]
433pub struct GetTxFeeResult {
434    #[serde_as(as = "DisplayFromStr")]
435    pub tx_fee: u64,
436    #[serde_as(as = "DisplayFromStr")]
437    pub create_asset_tx_fee: u64,
438    #[serde_as(as = "DisplayFromStr")]
439    pub create_subnet_tx_fee: u64,
440    #[serde_as(as = "DisplayFromStr")]
441    pub transform_subnet_tx_fee: u64,
442    #[serde_as(as = "DisplayFromStr")]
443    pub create_blockchain_tx_fee: u64,
444    #[serde_as(as = "DisplayFromStr")]
445    pub add_primary_network_validator_fee: u64,
446    #[serde_as(as = "DisplayFromStr")]
447    pub add_primary_network_delegator_fee: u64,
448    #[serde_as(as = "DisplayFromStr")]
449    pub add_subnet_validator_fee: u64,
450    #[serde_as(as = "DisplayFromStr")]
451    pub add_subnet_delegator_fee: u64,
452}
453
454/// RUST_LOG=debug cargo test --package avalanche-types --lib -- jsonrpc::info::test_get_tx_fee --exact --show-output
455#[test]
456fn test_get_tx_fee() {
457    // ref. <https://docs.avax.network/build/avalanchego-apis/info/#infogettxfee>
458    // default local network fees
459    let resp: GetTxFeeResponse = serde_json::from_str(
460        "
461
462{
463    \"jsonrpc\": \"2.0\",
464    \"result\": {
465        \"txFee\": \"1000000\",
466        \"createAssetTxFee\": \"1000000\",
467        \"createSubnetTxFee\": \"100000000\",
468        \"transformSubnetTxFee\": \"100000000\",
469        \"createBlockchainTxFee\": \"100000000\",
470        \"addPrimaryNetworkValidatorFee\": \"0\",
471        \"addPrimaryNetworkDelegatorFee\": \"1000000\",
472        \"addSubnetValidatorFee\": \"1000000\",
473        \"addSubnetDelegatorFee\": \"1000000\"
474    },
475    \"id\": 1
476}
477
478",
479    )
480    .unwrap();
481
482    let expected = GetTxFeeResponse {
483        jsonrpc: "2.0".to_string(),
484        id: 1,
485        result: Some(GetTxFeeResult {
486            tx_fee: 1000000,
487            create_asset_tx_fee: 1000000,
488            create_subnet_tx_fee: 100000000,
489            transform_subnet_tx_fee: 100000000,
490            create_blockchain_tx_fee: 100000000,
491            add_primary_network_validator_fee: 0,
492            add_primary_network_delegator_fee: 1000000,
493            add_subnet_validator_fee: 1000000,
494            add_subnet_delegator_fee: 1000000,
495        }),
496        error: None,
497    };
498    assert_eq!(resp, expected);
499}
500
501/// ref. <https://docs.avax.network/apis/avalanchego/apis/info#infouptime>
502#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
503pub struct UptimeResponse {
504    pub jsonrpc: String,
505    pub id: u32,
506
507    #[serde(skip_serializing_if = "Option::is_none")]
508    pub result: Option<UptimeResult>,
509
510    #[serde(skip_serializing_if = "Option::is_none")]
511    pub error: Option<jsonrpc::ResponseError>,
512}
513
514/// ref. <https://docs.avax.network/apis/avalanchego/apis/info#infouptime>
515#[serde_as]
516#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
517#[serde(rename_all = "camelCase")]
518pub struct UptimeResult {
519    #[serde_as(as = "DisplayFromStr")]
520    pub rewarding_stake_percentage: f64,
521    #[serde_as(as = "DisplayFromStr")]
522    pub weighted_average_percentage: f64,
523}
524
525impl Default for UptimeResult {
526    fn default() -> Self {
527        Self {
528            rewarding_stake_percentage: 0_f64,
529            weighted_average_percentage: 0_f64,
530        }
531    }
532}
533
534/// RUST_LOG=debug cargo test --package avalanche-types --lib -- jsonrpc::info::test_uptime --exact --show-output
535#[test]
536fn test_uptime() {
537    // ref. https://docs.avax.network/apis/avalanchego/apis/info#infouptime
538    let resp: UptimeResponse = serde_json::from_str(
539        "
540
541{
542    \"jsonrpc\": \"2.0\",
543    \"result\": {
544        \"rewardingStakePercentage\": \"100.0000\",
545        \"weightedAveragePercentage\": \"99.0000\"
546    },
547    \"id\": 1
548}
549
550",
551    )
552    .unwrap();
553
554    let expected = UptimeResponse {
555        jsonrpc: "2.0".to_string(),
556        id: 1,
557        result: Some(UptimeResult {
558            rewarding_stake_percentage: 100.0000_f64,
559            weighted_average_percentage: 99.0000_f64,
560        }),
561        error: None,
562    };
563    assert_eq!(resp, expected);
564}
565
566/// ref. <https://docs.avax.network/apis/avalanchego/apis/info#infopeers>
567#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
568pub struct PeersRequest {
569    pub jsonrpc: String,
570    pub id: u32,
571
572    pub method: String,
573
574    #[serde(skip_serializing_if = "Option::is_none")]
575    pub params: Option<PeersParams>,
576}
577
578impl Default for PeersRequest {
579    fn default() -> Self {
580        Self {
581            jsonrpc: String::from(super::DEFAULT_VERSION),
582            id: super::DEFAULT_ID,
583            method: String::new(),
584            params: None,
585        }
586    }
587}
588
589impl PeersRequest {
590    pub fn encode_json(&self) -> io::Result<String> {
591        serde_json::to_string(&self)
592            .map_err(|e| Error::new(ErrorKind::Other, format!("failed to serialize JSON {}", e)))
593    }
594}
595
596#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
597pub struct PeersParams {
598    #[serde(rename = "nodeIDs")]
599    pub node_ids: Option<Vec<ids::node::Id>>,
600}
601
602/// ref. <https://docs.avax.network/apis/avalanchego/apis/info#infopeers>
603#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
604pub struct PeersResponse {
605    pub jsonrpc: String,
606    pub id: u32,
607
608    #[serde(skip_serializing_if = "Option::is_none")]
609    pub result: Option<PeersResult>,
610
611    #[serde(skip_serializing_if = "Option::is_none")]
612    pub error: Option<jsonrpc::ResponseError>,
613}
614
615/// ref. <https://docs.avax.network/apis/avalanchego/apis/info#infopeers>
616#[serde_as]
617#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
618#[serde(rename_all = "camelCase")]
619#[derive(Default)]
620pub struct PeersResult {
621    #[serde(rename = "numPeers")]
622    #[serde_as(as = "DisplayFromStr")]
623    pub num_peers: u64,
624    #[serde(skip_serializing_if = "Option::is_none")]
625    pub peers: Option<Vec<Peer>>,
626}
627
628/// TODO: add "benched"
629/// ref. <https://docs.avax.network/apis/avalanchego/apis/info#infopeers>
630#[serde_as]
631#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
632#[serde(rename_all = "camelCase")]
633pub struct Peer {
634    #[serde_as(as = "crate::codec::serde::ip_port::IpPort")]
635    pub ip: SocketAddr,
636    #[serde(rename = "publicIP")]
637    #[serde_as(as = "crate::codec::serde::ip_port::IpPort")]
638    pub public_ip: SocketAddr,
639    #[serde(rename = "nodeID")]
640    pub node_id: node::Id,
641    pub version: String,
642    #[serde_as(as = "crate::codec::serde::rfc_3339::DateTimeUtc")]
643    pub last_sent: DateTime<Utc>,
644    #[serde_as(as = "crate::codec::serde::rfc_3339::DateTimeUtc")]
645    pub last_received: DateTime<Utc>,
646    #[serde_as(as = "DisplayFromStr")]
647    pub observed_uptime: u32,
648    #[serde_as(as = "HashMap<_, DisplayFromStr>")]
649    pub observed_subnet_uptimes: HashMap<ids::Id, u32>,
650    pub tracked_subnets: Vec<ids::Id>,
651}
652
653impl Default for Peer {
654    fn default() -> Self {
655        Self {
656            ip: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080),
657            public_ip: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080),
658            node_id: node::Id::empty(),
659            version: String::new(),
660            last_sent: DateTime::<Utc>::MIN_UTC,
661            last_received: DateTime::<Utc>::MIN_UTC,
662            observed_uptime: 0,
663            observed_subnet_uptimes: HashMap::new(),
664            tracked_subnets: Vec::new(),
665        }
666    }
667}
668
669/// RUST_LOG=debug cargo test --package avalanche-types --lib -- jsonrpc::info::test_peers --exact --show-output
670#[test]
671fn test_peers() {
672    use std::str::FromStr;
673
674    use chrono::TimeZone;
675
676    // ref. <https://docs.avax.network/apis/avalanchego/apis/info#infopeers>
677    let resp: PeersResponse = serde_json::from_str(
678        "
679
680{
681    \"jsonrpc\": \"2.0\",
682    \"result\": {
683        \"numPeers\": \"3\",
684        \"peers\": [
685            {
686                \"ip\": \"206.189.137.87:9651\",
687                \"publicIP\": \"206.189.137.87:9651\",
688                \"nodeID\": \"NodeID-8PYXX47kqLDe2wD4oPbvRRchcnSzMA4J4\",
689                \"version\": \"avalanche/1.9.4\",
690                \"lastSent\": \"2020-06-01T15:23:02Z\",
691                \"lastReceived\": \"2020-06-01T15:22:57Z\",
692                \"benched\": [],
693                \"observedUptime\": \"99\",
694                \"observedSubnetUptimes\": {},
695                \"trackedSubnets\": [],
696                \"benched\": []
697            },
698            {
699                \"ip\": \"158.255.67.151:9651\",
700                \"publicIP\": \"158.255.67.151:9651\",
701                \"nodeID\": \"NodeID-C14fr1n8EYNKyDfYixJ3rxSAVqTY3a8BP\",
702                \"version\": \"avalanche/1.9.4\",
703                \"lastSent\": \"2020-06-01T15:23:02Z\",
704                \"lastReceived\": \"2020-06-01T15:22:34Z\",
705                \"benched\": [],
706                \"observedUptime\": \"75\",
707                \"observedSubnetUptimes\": {
708                    \"29uVeLPJB1eQJkzRemU8g8wZDw5uJRqpab5U2mX9euieVwiEbL\": \"100\"
709                },
710                \"trackedSubnets\": [
711                    \"29uVeLPJB1eQJkzRemU8g8wZDw5uJRqpab5U2mX9euieVwiEbL\"
712                ],
713                \"benched\": []
714            }
715        ]
716    },
717    \"id\": 1
718}
719
720",
721    )
722    .unwrap();
723
724    let uptimes: HashMap<ids::Id, u32> = [(
725        ids::Id::from_str("29uVeLPJB1eQJkzRemU8g8wZDw5uJRqpab5U2mX9euieVwiEbL").unwrap(),
726        100,
727    )]
728    .iter()
729    .cloned()
730    .collect();
731    let expected = PeersResponse {
732        jsonrpc: "2.0".to_string(),
733        id: 1,
734        result: Some(PeersResult {
735            num_peers: 3,
736            peers: Some(vec![
737                Peer {
738                    ip: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(206, 189, 137, 87)), 9651),
739                    public_ip: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(206, 189, 137, 87)), 9651),
740                    node_id: node::Id::from_str("NodeID-8PYXX47kqLDe2wD4oPbvRRchcnSzMA4J4")
741                        .unwrap(),
742                    version: String::from("avalanche/1.9.4"),
743                    last_sent: Utc.from_utc_datetime(
744                        &DateTime::parse_from_rfc3339("2020-06-01T15:23:02Z")
745                            .unwrap()
746                            .naive_utc(),
747                    ),
748                    last_received: Utc.from_utc_datetime(
749                        &DateTime::parse_from_rfc3339("2020-06-01T15:22:57Z")
750                            .unwrap()
751                            .naive_utc(),
752                    ),
753                    observed_uptime: 99,
754                    ..Peer::default()
755                },
756                Peer {
757                    ip: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(158, 255, 67, 151)), 9651),
758                    public_ip: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(158, 255, 67, 151)), 9651),
759                    node_id: node::Id::from_str("NodeID-C14fr1n8EYNKyDfYixJ3rxSAVqTY3a8BP")
760                        .unwrap(),
761                    version: String::from("avalanche/1.9.4"),
762                    last_sent: Utc.from_utc_datetime(
763                        &DateTime::parse_from_rfc3339("2020-06-01T15:23:02Z")
764                            .unwrap()
765                            .naive_utc(),
766                    ),
767                    last_received: Utc.from_utc_datetime(
768                        &DateTime::parse_from_rfc3339("2020-06-01T15:22:34Z")
769                            .unwrap()
770                            .naive_utc(),
771                    ),
772                    observed_uptime: 75,
773                    observed_subnet_uptimes: uptimes,
774                    tracked_subnets: vec![ids::Id::from_str(
775                        "29uVeLPJB1eQJkzRemU8g8wZDw5uJRqpab5U2mX9euieVwiEbL",
776                    )
777                    .unwrap()],
778                },
779            ]),
780        }),
781        error: None,
782    };
783    assert_eq!(resp, expected);
784}