use std::{
collections::HashMap,
io::{self, Error, ErrorKind},
net::{IpAddr, Ipv4Addr, SocketAddr},
};
use crate::{
ids::{self, node},
jsonrpc,
key::bls,
};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use serde_with::{serde_as, DisplayFromStr};
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
pub struct GetNetworkNameResponse {
pub jsonrpc: String,
pub id: u32,
#[serde(skip_serializing_if = "Option::is_none")]
pub result: Option<GetNetworkNameResult>,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<jsonrpc::ResponseError>,
}
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
#[serde(rename_all = "camelCase")]
pub struct GetNetworkNameResult {
pub network_name: String,
}
impl Default for GetNetworkNameResult {
fn default() -> Self {
Self::default()
}
}
impl GetNetworkNameResult {
pub fn default() -> Self {
Self {
network_name: String::new(),
}
}
}
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
pub struct GetNetworkIdResponse {
pub jsonrpc: String,
pub id: u32,
#[serde(skip_serializing_if = "Option::is_none")]
pub result: Option<GetNetworkIdResult>,
}
#[serde_as]
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
pub struct GetNetworkIdResult {
#[serde(rename = "networkID")]
#[serde_as(as = "DisplayFromStr")]
pub network_id: u32,
}
impl Default for GetNetworkIdResult {
fn default() -> Self {
Self::default()
}
}
impl GetNetworkIdResult {
pub fn default() -> Self {
Self { network_id: 1 }
}
}
#[test]
fn test_get_network_id() {
let resp: GetNetworkIdResponse = serde_json::from_str(
"
{
\"jsonrpc\": \"2.0\",
\"result\": {
\"networkID\": \"9999999\"
},
\"id\": 1
}
",
)
.unwrap();
let expected = GetNetworkIdResponse {
jsonrpc: "2.0".to_string(),
id: 1,
result: Some(GetNetworkIdResult {
network_id: 9999999_u32,
}),
};
assert_eq!(resp, expected);
}
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
pub struct GetBlockchainIdResponse {
pub jsonrpc: String,
pub id: u32,
#[serde(skip_serializing_if = "Option::is_none")]
pub result: Option<GetBlockchainIdResult>,
}
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
pub struct GetBlockchainIdResult {
#[serde(rename = "blockchainID")]
pub blockchain_id: ids::Id,
}
impl Default for GetBlockchainIdResult {
fn default() -> Self {
Self::default()
}
}
impl GetBlockchainIdResult {
pub fn default() -> Self {
Self {
blockchain_id: ids::Id::default(),
}
}
}
#[test]
fn test_get_blockchain_id() {
use std::str::FromStr;
let resp: GetBlockchainIdResponse = serde_json::from_str(
"
{
\"jsonrpc\": \"2.0\",
\"result\": {
\"blockchainID\": \"sV6o671RtkGBcno1FiaDbVcFv2sG5aVXMZYzKdP4VQAWmJQnM\"
},
\"id\": 1
}
",
)
.unwrap();
let expected = GetBlockchainIdResponse {
jsonrpc: "2.0".to_string(),
id: 1,
result: Some(GetBlockchainIdResult {
blockchain_id: ids::Id::from_str("sV6o671RtkGBcno1FiaDbVcFv2sG5aVXMZYzKdP4VQAWmJQnM")
.unwrap(),
}),
};
assert_eq!(resp, expected);
}
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
pub struct GetNodeIdResponse {
pub jsonrpc: String,
pub id: u32,
#[serde(skip_serializing_if = "Option::is_none")]
pub result: Option<GetNodeIdResult>,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<jsonrpc::ResponseError>,
}
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
pub struct GetNodeIdResult {
#[serde(rename = "nodeID")]
pub node_id: node::Id,
#[serde(rename = "nodePOP")]
pub node_pop: Option<bls::ProofOfPossession>,
}
impl Default for GetNodeIdResult {
fn default() -> Self {
Self::default()
}
}
impl GetNodeIdResult {
pub fn default() -> Self {
Self {
node_id: node::Id::default(),
node_pop: None,
}
}
}
#[test]
fn test_get_node_id() {
use std::str::FromStr;
let resp: GetNodeIdResponse = serde_json::from_str(
"
{
\"jsonrpc\": \"2.0\",
\"result\": {
\"nodeID\": \"NodeID-5mb46qkSBj81k9g9e4VFjGGSbaaSLFRzD\"
},
\"id\": 1
}
",
)
.unwrap();
let expected = GetNodeIdResponse {
jsonrpc: "2.0".to_string(),
id: 1,
result: Some(GetNodeIdResult {
node_id: node::Id::from_str("NodeID-5mb46qkSBj81k9g9e4VFjGGSbaaSLFRzD").unwrap(),
..Default::default()
}),
error: None,
};
assert_eq!(resp, expected);
let resp: GetNodeIdResponse = serde_json::from_str(
"
{
\"jsonrpc\": \"2.0\",
\"result\": {
\"nodeID\": \"NodeID-5mb46qkSBj81k9g9e4VFjGGSbaaSLFRzD\",
\"nodePOP\": {
\"publicKey\": \"0x8f95423f7142d00a48e1014a3de8d28907d420dc33b3052a6dee03a3f2941a393c2351e354704ca66a3fc29870282e15\",
\"proofOfPossession\": \"0x86a3ab4c45cfe31cae34c1d06f212434ac71b1be6cfe046c80c162e057614a94a5bc9f1ded1a7029deb0ba4ca7c9b71411e293438691be79c2dbf19d1ca7c3eadb9c756246fc5de5b7b89511c7d7302ae051d9e03d7991138299b5ed6a570a98\"
}
},
\"id\": 1
}
",
)
.unwrap();
let expected = GetNodeIdResponse {
jsonrpc: "2.0".to_string(),
id: 1,
result: Some(GetNodeIdResult {
node_id: node::Id::from_str("NodeID-5mb46qkSBj81k9g9e4VFjGGSbaaSLFRzD").unwrap(),
node_pop: Some(bls::ProofOfPossession {
public_key: hex::decode("0x8f95423f7142d00a48e1014a3de8d28907d420dc33b3052a6dee03a3f2941a393c2351e354704ca66a3fc29870282e15".trim_start_matches("0x")).unwrap(),
proof_of_possession: hex::decode("0x86a3ab4c45cfe31cae34c1d06f212434ac71b1be6cfe046c80c162e057614a94a5bc9f1ded1a7029deb0ba4ca7c9b71411e293438691be79c2dbf19d1ca7c3eadb9c756246fc5de5b7b89511c7d7302ae051d9e03d7991138299b5ed6a570a98".trim_start_matches("0x")).unwrap(),
..Default::default()
}),
}),
error: None,
};
assert_eq!(resp, expected);
}
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
pub struct GetNodeIpResponse {
pub jsonrpc: String,
pub id: u32,
#[serde(skip_serializing_if = "Option::is_none")]
pub result: Option<GetNodeIpResult>,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<jsonrpc::ResponseError>,
}
#[serde_as]
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
pub struct GetNodeIpResult {
#[serde_as(as = "crate::codec::serde::ip_port::IpPort")]
pub ip: SocketAddr,
}
impl Default for GetNodeIpResult {
fn default() -> Self {
Self::default()
}
}
impl GetNodeIpResult {
pub fn default() -> Self {
Self {
ip: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 9651),
}
}
}
#[test]
fn test_get_node_ip() {
let resp: GetNodeIpResponse = serde_json::from_str(
"
{
\"jsonrpc\": \"2.0\",
\"result\": {
\"ip\": \"192.168.1.1:9651\"
},
\"id\": 1
}
",
)
.unwrap();
let expected = GetNodeIpResponse {
jsonrpc: "2.0".to_string(),
id: 1,
result: Some(GetNodeIpResult {
ip: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1)), 9651),
}),
error: None,
};
assert_eq!(resp, expected);
}
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
pub struct GetNodeVersionResponse {
pub jsonrpc: String,
pub id: u32,
#[serde(skip_serializing_if = "Option::is_none")]
pub result: Option<GetNodeVersionResult>,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<jsonrpc::ResponseError>,
}
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
#[serde(rename_all = "camelCase")]
pub struct GetNodeVersionResult {
pub version: String,
pub database_version: String,
pub git_commit: String,
pub vm_versions: VmVersions,
}
impl Default for GetNodeVersionResult {
fn default() -> Self {
Self::default()
}
}
impl GetNodeVersionResult {
pub fn default() -> Self {
Self {
version: String::new(),
database_version: String::new(),
git_commit: String::new(),
vm_versions: VmVersions::default(),
}
}
}
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
#[serde(rename_all = "camelCase")]
pub struct VmVersions {
pub avm: String,
pub evm: String,
pub platform: String,
}
impl Default for VmVersions {
fn default() -> Self {
Self::default()
}
}
impl VmVersions {
pub fn default() -> Self {
Self {
avm: String::new(),
evm: String::new(),
platform: String::new(),
}
}
}
#[test]
fn test_get_node_version() {
let resp: GetNodeVersionResponse = serde_json::from_str(
"
{
\"jsonrpc\": \"2.0\",
\"result\": {
\"version\": \"avalanche/1.4.10\",
\"databaseVersion\": \"v1.4.5\",
\"gitCommit\": \"a3930fe3fa115c018e71eb1e97ca8cec34db67f1\",
\"vmVersions\": {
\"avm\": \"v1.4.10\",
\"evm\": \"v0.5.5-rc.1\",
\"platform\": \"v1.4.10\"
}
},
\"id\": 1
}
",
)
.unwrap();
let expected = GetNodeVersionResponse {
jsonrpc: "2.0".to_string(),
id: 1,
result: Some(GetNodeVersionResult {
version: String::from("avalanche/1.4.10"),
database_version: String::from("v1.4.5"),
git_commit: String::from("a3930fe3fa115c018e71eb1e97ca8cec34db67f1"),
vm_versions: VmVersions {
avm: String::from("v1.4.10"),
evm: String::from("v0.5.5-rc.1"),
platform: String::from("v1.4.10"),
},
}),
error: None,
};
assert_eq!(resp, expected);
}
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
pub struct GetVmsResponse {
pub jsonrpc: String,
pub id: u32,
#[serde(skip_serializing_if = "Option::is_none")]
pub result: Option<GetVmsResult>,
}
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
#[serde(rename_all = "camelCase")]
pub struct GetVmsResult {
#[serde(skip_serializing_if = "Option::is_none")]
pub vms: Option<HashMap<String, Vec<String>>>,
}
impl Default for GetVmsResult {
fn default() -> Self {
Self::default()
}
}
impl GetVmsResult {
pub fn default() -> Self {
Self { vms: None }
}
}
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
pub struct IsBootstrappedResponse {
pub jsonrpc: String,
pub id: u32,
#[serde(skip_serializing_if = "Option::is_none")]
pub result: Option<IsBootstrappedResult>,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<jsonrpc::ResponseError>,
}
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
#[serde(rename_all = "camelCase")]
pub struct IsBootstrappedResult {
pub is_bootstrapped: bool,
}
impl Default for IsBootstrappedResult {
fn default() -> Self {
Self::default()
}
}
impl IsBootstrappedResult {
pub fn default() -> Self {
Self {
is_bootstrapped: false,
}
}
}
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
pub struct GetTxFeeResponse {
pub jsonrpc: String,
pub id: u32,
#[serde(skip_serializing_if = "Option::is_none")]
pub result: Option<GetTxFeeResult>,
}
#[serde_as]
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
#[serde(rename_all = "camelCase")]
pub struct GetTxFeeResult {
#[serde_as(as = "DisplayFromStr")]
pub tx_fee: u64,
#[serde_as(as = "DisplayFromStr")]
pub create_asset_tx_fee: u64,
#[serde_as(as = "DisplayFromStr")]
pub create_subnet_tx_fee: u64,
#[serde_as(as = "DisplayFromStr")]
pub transform_subnet_tx_fee: u64,
#[serde_as(as = "DisplayFromStr")]
pub create_blockchain_tx_fee: u64,
#[serde_as(as = "DisplayFromStr")]
pub add_primary_network_validator_fee: u64,
#[serde_as(as = "DisplayFromStr")]
pub add_primary_network_delegator_fee: u64,
#[serde_as(as = "DisplayFromStr")]
pub add_subnet_validator_fee: u64,
#[serde_as(as = "DisplayFromStr")]
pub add_subnet_delegator_fee: u64,
}
impl Default for GetTxFeeResult {
fn default() -> Self {
Self::default()
}
}
impl GetTxFeeResult {
pub fn default() -> Self {
Self {
tx_fee: 0,
create_asset_tx_fee: 0,
create_subnet_tx_fee: 0,
transform_subnet_tx_fee: 0,
create_blockchain_tx_fee: 0,
add_primary_network_validator_fee: 0,
add_primary_network_delegator_fee: 0,
add_subnet_validator_fee: 0,
add_subnet_delegator_fee: 0,
}
}
}
#[test]
fn test_get_tx_fee() {
let resp: GetTxFeeResponse = serde_json::from_str(
"
{
\"jsonrpc\": \"2.0\",
\"result\": {
\"txFee\": \"1000000\",
\"createAssetTxFee\": \"1000000\",
\"createSubnetTxFee\": \"100000000\",
\"transformSubnetTxFee\": \"100000000\",
\"createBlockchainTxFee\": \"100000000\",
\"addPrimaryNetworkValidatorFee\": \"0\",
\"addPrimaryNetworkDelegatorFee\": \"1000000\",
\"addSubnetValidatorFee\": \"1000000\",
\"addSubnetDelegatorFee\": \"1000000\"
},
\"id\": 1
}
",
)
.unwrap();
let expected = GetTxFeeResponse {
jsonrpc: "2.0".to_string(),
id: 1,
result: Some(GetTxFeeResult {
tx_fee: 1000000,
create_asset_tx_fee: 1000000,
create_subnet_tx_fee: 100000000,
transform_subnet_tx_fee: 100000000,
create_blockchain_tx_fee: 100000000,
add_primary_network_validator_fee: 0,
add_primary_network_delegator_fee: 1000000,
add_subnet_validator_fee: 1000000,
add_subnet_delegator_fee: 1000000,
}),
};
assert_eq!(resp, expected);
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
pub struct UptimeResponse {
pub jsonrpc: String,
pub id: u32,
#[serde(skip_serializing_if = "Option::is_none")]
pub result: Option<UptimeResult>,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<jsonrpc::ResponseError>,
}
#[serde_as]
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
#[serde(rename_all = "camelCase")]
pub struct UptimeResult {
#[serde_as(as = "DisplayFromStr")]
pub rewarding_stake_percentage: f64,
#[serde_as(as = "DisplayFromStr")]
pub weighted_average_percentage: f64,
}
impl Default for UptimeResult {
fn default() -> Self {
Self::default()
}
}
impl UptimeResult {
pub fn default() -> Self {
Self {
rewarding_stake_percentage: 0_f64,
weighted_average_percentage: 0_f64,
}
}
}
#[test]
fn test_uptime() {
let resp: UptimeResponse = serde_json::from_str(
"
{
\"jsonrpc\": \"2.0\",
\"result\": {
\"rewardingStakePercentage\": \"100.0000\",
\"weightedAveragePercentage\": \"99.0000\"
},
\"id\": 1
}
",
)
.unwrap();
let expected = UptimeResponse {
jsonrpc: "2.0".to_string(),
id: 1,
result: Some(UptimeResult {
rewarding_stake_percentage: 100.0000_f64,
weighted_average_percentage: 99.0000_f64,
}),
error: None,
};
assert_eq!(resp, expected);
}
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
pub struct PeersRequest {
pub jsonrpc: String,
pub id: u32,
pub method: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub params: Option<PeersParams>,
}
impl Default for PeersRequest {
fn default() -> Self {
Self::default()
}
}
impl PeersRequest {
pub fn default() -> Self {
Self {
jsonrpc: String::from(super::DEFAULT_VERSION),
id: super::DEFAULT_ID,
method: String::new(),
params: None,
}
}
pub fn encode_json(&self) -> io::Result<String> {
serde_json::to_string(&self)
.map_err(|e| Error::new(ErrorKind::Other, format!("failed to serialize JSON {}", e)))
}
}
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
pub struct PeersParams {
#[serde(rename = "nodeIDs")]
pub node_ids: Option<Vec<ids::node::Id>>,
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
pub struct PeersResponse {
pub jsonrpc: String,
pub id: u32,
#[serde(skip_serializing_if = "Option::is_none")]
pub result: Option<PeersResult>,
}
#[serde_as]
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
#[serde(rename_all = "camelCase")]
pub struct PeersResult {
#[serde(rename = "numPeers")]
#[serde_as(as = "DisplayFromStr")]
pub num_peers: u64,
#[serde(skip_serializing_if = "Option::is_none")]
pub peers: Option<Vec<Peer>>,
}
impl Default for PeersResult {
fn default() -> Self {
Self::default()
}
}
impl PeersResult {
pub fn default() -> Self {
Self {
num_peers: 0,
peers: None,
}
}
}
#[serde_as]
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
#[serde(rename_all = "camelCase")]
pub struct Peer {
#[serde_as(as = "crate::codec::serde::ip_port::IpPort")]
pub ip: SocketAddr,
#[serde(rename = "publicIP")]
#[serde_as(as = "crate::codec::serde::ip_port::IpPort")]
pub public_ip: SocketAddr,
#[serde(rename = "nodeID")]
pub node_id: node::Id,
pub version: String,
#[serde_as(as = "crate::codec::serde::rfc_3339::DateTimeUtc")]
pub last_sent: DateTime<Utc>,
#[serde_as(as = "crate::codec::serde::rfc_3339::DateTimeUtc")]
pub last_received: DateTime<Utc>,
#[serde_as(as = "DisplayFromStr")]
pub observed_uptime: u32,
#[serde_as(as = "HashMap<_, DisplayFromStr>")]
pub observed_subnet_uptimes: HashMap<ids::Id, u32>,
pub tracked_subnets: Vec<ids::Id>,
}
impl Default for Peer {
fn default() -> Self {
Self::default()
}
}
impl Peer {
pub fn default() -> Self {
Self {
ip: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080),
public_ip: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080),
node_id: node::Id::empty(),
version: String::new(),
last_sent: DateTime::<Utc>::MIN_UTC,
last_received: DateTime::<Utc>::MIN_UTC,
observed_uptime: 0,
observed_subnet_uptimes: HashMap::new(),
tracked_subnets: Vec::new(),
}
}
}
#[test]
fn test_peers() {
use std::str::FromStr;
use chrono::TimeZone;
let resp: PeersResponse = serde_json::from_str(
"
{
\"jsonrpc\": \"2.0\",
\"result\": {
\"numPeers\": \"3\",
\"peers\": [
{
\"ip\": \"206.189.137.87:9651\",
\"publicIP\": \"206.189.137.87:9651\",
\"nodeID\": \"NodeID-8PYXX47kqLDe2wD4oPbvRRchcnSzMA4J4\",
\"version\": \"avalanche/1.9.4\",
\"lastSent\": \"2020-06-01T15:23:02Z\",
\"lastReceived\": \"2020-06-01T15:22:57Z\",
\"benched\": [],
\"observedUptime\": \"99\",
\"observedSubnetUptimes\": {},
\"trackedSubnets\": [],
\"benched\": []
},
{
\"ip\": \"158.255.67.151:9651\",
\"publicIP\": \"158.255.67.151:9651\",
\"nodeID\": \"NodeID-C14fr1n8EYNKyDfYixJ3rxSAVqTY3a8BP\",
\"version\": \"avalanche/1.9.4\",
\"lastSent\": \"2020-06-01T15:23:02Z\",
\"lastReceived\": \"2020-06-01T15:22:34Z\",
\"benched\": [],
\"observedUptime\": \"75\",
\"observedSubnetUptimes\": {
\"29uVeLPJB1eQJkzRemU8g8wZDw5uJRqpab5U2mX9euieVwiEbL\": \"100\"
},
\"trackedSubnets\": [
\"29uVeLPJB1eQJkzRemU8g8wZDw5uJRqpab5U2mX9euieVwiEbL\"
],
\"benched\": []
}
]
},
\"id\": 1
}
",
)
.unwrap();
let uptimes: HashMap<ids::Id, u32> = [(
ids::Id::from_str("29uVeLPJB1eQJkzRemU8g8wZDw5uJRqpab5U2mX9euieVwiEbL").unwrap(),
100,
)]
.iter()
.cloned()
.collect();
let expected = PeersResponse {
jsonrpc: "2.0".to_string(),
id: 1,
result: Some(PeersResult {
num_peers: 3,
peers: Some(vec![
Peer {
ip: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(206, 189, 137, 87)), 9651),
public_ip: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(206, 189, 137, 87)), 9651),
node_id: node::Id::from_str("NodeID-8PYXX47kqLDe2wD4oPbvRRchcnSzMA4J4")
.unwrap(),
version: String::from("avalanche/1.9.4"),
last_sent: Utc.from_utc_datetime(
&DateTime::parse_from_rfc3339("2020-06-01T15:23:02Z")
.unwrap()
.naive_utc(),
),
last_received: Utc.from_utc_datetime(
&DateTime::parse_from_rfc3339("2020-06-01T15:22:57Z")
.unwrap()
.naive_utc(),
),
observed_uptime: 99,
..Peer::default()
},
Peer {
ip: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(158, 255, 67, 151)), 9651),
public_ip: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(158, 255, 67, 151)), 9651),
node_id: node::Id::from_str("NodeID-C14fr1n8EYNKyDfYixJ3rxSAVqTY3a8BP")
.unwrap(),
version: String::from("avalanche/1.9.4"),
last_sent: Utc.from_utc_datetime(
&DateTime::parse_from_rfc3339("2020-06-01T15:23:02Z")
.unwrap()
.naive_utc(),
),
last_received: Utc.from_utc_datetime(
&DateTime::parse_from_rfc3339("2020-06-01T15:22:34Z")
.unwrap()
.naive_utc(),
),
observed_uptime: 75,
observed_subnet_uptimes: uptimes,
tracked_subnets: vec![ids::Id::from_str(
"29uVeLPJB1eQJkzRemU8g8wZDw5uJRqpab5U2mX9euieVwiEbL",
)
.unwrap()],
..Peer::default()
},
]),
..PeersResult::default()
}),
};
assert_eq!(resp, expected);
}