1use core::net::IpAddr;
4use derive_more::Display;
5use std::string::String;
6
7use alloy_primitives::{ChainId, map::HashMap};
8
9#[derive(Clone, Default, Debug, serde::Serialize, serde::Deserialize)]
13#[serde(rename_all = "camelCase")]
14pub struct PeerInfo {
15 #[serde(rename = "peerID")]
17 pub peer_id: String,
18 #[serde(rename = "nodeID")]
20 pub node_id: String,
21 pub user_agent: String,
23 pub protocol_version: String,
25 #[serde(rename = "ENR")]
28 #[serde(skip_serializing_if = "Option::is_none")]
29 pub enr: Option<String>,
30 pub addresses: Vec<String>,
32 pub protocols: Option<Vec<String>>,
34 pub connectedness: Connectedness,
39 pub direction: Direction,
43 pub protected: bool,
45 #[serde(rename = "chainID")]
47 pub chain_id: ChainId,
48 pub latency: u64,
50 pub gossip_blocks: bool,
52 #[serde(rename = "scores")]
54 pub peer_scores: PeerScores,
55}
56
57#[derive(Clone, Default, Debug, Copy, serde::Serialize, serde::Deserialize)]
65#[serde(rename_all = "camelCase")]
66pub struct TopicScores {
67 pub time_in_mesh: f64,
72
73 pub first_message_deliveries: f64,
78
79 pub mesh_message_deliveries: f64,
84
85 pub invalid_message_deliveries: f64,
90}
91
92#[derive(Debug, Default, Clone, Copy, serde::Serialize, serde::Deserialize)]
100#[serde(rename_all = "camelCase")]
101pub struct GossipScores {
102 pub total: f64,
107
108 pub blocks: TopicScores,
113
114 #[serde(rename = "IPColocationFactor")]
119 pub ip_colocation_factor: f64,
120
121 pub behavioral_penalty: f64,
126}
127
128#[derive(Debug, Default, Clone, Copy, serde::Serialize, serde::Deserialize)]
135#[serde(rename_all = "camelCase")]
136pub struct ReqRespScores {
137 pub valid_responses: f64,
142
143 pub error_responses: f64,
148 pub rejected_payloads: f64,
150}
151
152#[derive(Clone, Default, Debug, Copy, serde::Serialize, serde::Deserialize)]
156#[serde(rename_all = "camelCase")]
157pub struct PeerScores {
158 pub gossip: GossipScores,
160 pub req_resp: ReqRespScores,
162}
163
164#[derive(Clone, Default, Debug, serde::Serialize, serde::Deserialize)]
166#[serde(rename_all = "camelCase")]
167pub struct PeerCount {
168 pub connected_discovery: Option<usize>,
170 pub connected_gossip: usize,
172}
173
174#[derive(Clone, Default, Debug, serde::Serialize, serde::Deserialize)]
178#[serde(rename_all = "camelCase")]
179pub struct PeerDump {
180 pub total_connected: u32,
182 pub peers: HashMap<String, PeerInfo>,
184 pub banned_peers: Vec<String>,
186 #[serde(rename = "bannedIPS")]
188 pub banned_ips: Vec<IpAddr>,
189 pub banned_subnets: Vec<ipnet::IpNet>,
191}
192
193#[derive(Clone, Default, Debug, Copy, serde::Serialize, serde::Deserialize)]
197#[serde(rename_all = "camelCase")]
198pub struct PeerStats {
199 pub connected: u32,
201 pub table: u32,
203 #[serde(rename = "blocksTopic")]
205 pub blocks_topic: u32,
206 #[serde(rename = "blocksTopicV2")]
208 pub blocks_topic_v2: u32,
209 #[serde(rename = "blocksTopicV3")]
211 pub blocks_topic_v3: u32,
212 #[serde(rename = "blocksTopicV4")]
214 pub blocks_topic_v4: u32,
215 pub banned: u32,
217 pub known: u32,
219}
220
221#[derive(
224 Clone,
225 Debug,
226 Display,
227 PartialEq,
228 Copy,
229 Default,
230 serde_repr::Serialize_repr,
232 serde_repr::Deserialize_repr,
233)]
234#[repr(u8)]
235pub enum Connectedness {
236 #[default]
238 #[display("Not Connected")]
239 NotConnected = 0,
240
241 #[display("Connected")]
243 Connected = 1,
244
245 #[display("Can Connect")]
248 CanConnect = 2,
249
250 #[display("Cannot Connect")]
253 CannotConnect = 3,
254
255 #[display("Limited")]
257 Limited = 4,
258}
259
260impl From<u8> for Connectedness {
261 fn from(value: u8) -> Self {
262 match value {
263 0 => Self::NotConnected,
264 1 => Self::Connected,
265 2 => Self::CanConnect,
266 3 => Self::CannotConnect,
267 4 => Self::Limited,
268 _ => Self::NotConnected,
269 }
270 }
271}
272#[derive(Debug, Clone, Display, Copy, PartialEq, Eq, Default)]
274pub enum Direction {
275 #[default]
277 #[display("Unknown")]
278 Unknown = 0,
279 #[display("Inbound")]
281 Inbound = 1,
282 #[display("Outbound")]
284 Outbound = 2,
285}
286
287impl serde::Serialize for Direction {
288 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
289 where
290 S: serde::Serializer,
291 {
292 serializer.serialize_u8(*self as u8)
293 }
294}
295
296impl<'de> serde::Deserialize<'de> for Direction {
297 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
298 where
299 D: serde::Deserializer<'de>,
300 {
301 let value = u8::deserialize(deserializer)?;
302 match value {
303 0 => Ok(Self::Unknown),
304 1 => Ok(Self::Inbound),
305 2 => Ok(Self::Outbound),
306 _ => Err(serde::de::Error::invalid_value(
307 serde::de::Unexpected::Unsigned(value as u64),
308 &"a value between 0 and 2",
309 )),
310 }
311 }
312}
313
314#[cfg(test)]
315mod tests {
316 use super::*;
317
318 #[test]
319 fn test_connectedness_from_u8() {
320 assert_eq!(Connectedness::from(0), Connectedness::NotConnected);
321 assert_eq!(Connectedness::from(1), Connectedness::Connected);
322 assert_eq!(Connectedness::from(2), Connectedness::CanConnect);
323 assert_eq!(Connectedness::from(3), Connectedness::CannotConnect);
324 assert_eq!(Connectedness::from(4), Connectedness::Limited);
325 assert_eq!(Connectedness::from(5), Connectedness::NotConnected);
326 }
327
328 #[test]
329 fn test_direction_display() {
330 assert_eq!(Direction::Unknown.to_string(), "Unknown");
331 assert_eq!(Direction::Inbound.to_string(), "Inbound");
332 assert_eq!(Direction::Outbound.to_string(), "Outbound");
333 }
334
335 #[test]
336 fn test_direction_serialization() {
337 assert_eq!(
338 serde_json::to_string(&Direction::Unknown).unwrap(),
339 "0",
340 "Serialization failed for Direction::Unknown"
341 );
342 assert_eq!(
343 serde_json::to_string(&Direction::Inbound).unwrap(),
344 "1",
345 "Serialization failed for Direction::Inbound"
346 );
347 assert_eq!(
348 serde_json::to_string(&Direction::Outbound).unwrap(),
349 "2",
350 "Serialization failed for Direction::Outbound"
351 );
352 }
353
354 #[test]
355 fn test_direction_deserialization() {
356 let unknown: Direction = serde_json::from_str("0").unwrap();
357 let inbound: Direction = serde_json::from_str("1").unwrap();
358 let outbound: Direction = serde_json::from_str("2").unwrap();
359
360 assert_eq!(unknown, Direction::Unknown, "Deserialization mismatch for Direction::Unknown");
361 assert_eq!(inbound, Direction::Inbound, "Deserialization mismatch for Direction::Inbound");
362 assert_eq!(
363 outbound,
364 Direction::Outbound,
365 "Deserialization mismatch for Direction::Outbound"
366 );
367 }
368
369 #[test]
370 fn test_peer_info_connectedness_serialization() {
371 let peer_info = PeerInfo {
372 peer_id: String::from("peer123"),
373 node_id: String::from("node123"),
374 user_agent: String::from("MyUserAgent"),
375 protocol_version: String::from("v1"),
376 enr: Some(String::from("enr123")),
377 addresses: [String::from("127.0.0.1")].to_vec(),
378 protocols: Some([String::from("eth"), String::from("p2p")].to_vec()),
379 connectedness: Connectedness::Connected,
380 direction: Direction::Outbound,
381 protected: true,
382 chain_id: 1,
383 latency: 100,
384 gossip_blocks: true,
385 peer_scores: PeerScores {
386 gossip: GossipScores {
387 total: 1.0,
388 blocks: TopicScores {
389 time_in_mesh: 10.0,
390 first_message_deliveries: 5.0,
391 mesh_message_deliveries: 2.0,
392 invalid_message_deliveries: 0.0,
393 },
394 ip_colocation_factor: 0.5,
395 behavioral_penalty: 0.1,
396 },
397 req_resp: ReqRespScores {
398 valid_responses: 10.0,
399 error_responses: 1.0,
400 rejected_payloads: 0.0,
401 },
402 },
403 };
404
405 let serialized = serde_json::to_string(&peer_info).expect("Serialization failed");
406
407 let deserialized: PeerInfo =
408 serde_json::from_str(&serialized).expect("Deserialization failed");
409
410 assert_eq!(peer_info.peer_id, deserialized.peer_id);
411 assert_eq!(peer_info.node_id, deserialized.node_id);
412 assert_eq!(peer_info.user_agent, deserialized.user_agent);
413 assert_eq!(peer_info.protocol_version, deserialized.protocol_version);
414 assert_eq!(peer_info.enr, deserialized.enr);
415 assert_eq!(peer_info.addresses, deserialized.addresses);
416 assert_eq!(peer_info.protocols, deserialized.protocols);
417 assert_eq!(peer_info.connectedness, deserialized.connectedness);
418 assert_eq!(peer_info.direction, deserialized.direction);
419 assert_eq!(peer_info.protected, deserialized.protected);
420 assert_eq!(peer_info.chain_id, deserialized.chain_id);
421 assert_eq!(peer_info.latency, deserialized.latency);
422 assert_eq!(peer_info.gossip_blocks, deserialized.gossip_blocks);
423 assert_eq!(peer_info.peer_scores.gossip.total, deserialized.peer_scores.gossip.total);
424 assert_eq!(
425 peer_info.peer_scores.req_resp.valid_responses,
426 deserialized.peer_scores.req_resp.valid_responses
427 );
428 }
429}