alloy_rpc_types_admin/
admin.rs

1//! Types for the `admin` API.
2
3use alloy_genesis::ChainConfig;
4use alloy_primitives::{B256, U256};
5use serde::{Deserialize, Serialize};
6use std::{
7    collections::BTreeMap,
8    net::{IpAddr, SocketAddr},
9};
10
11/// This includes general information about a running node, spanning networking and protocol
12/// details.
13///
14/// See [geth's `NodeInfo` struct](https://github.com/ethereum/go-ethereum/blob/v1.14.0/p2p/server.go#L1078)
15/// for the source of each field.
16#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
17pub struct NodeInfo {
18    /// Unique node identifier (also the encryption key).
19    pub id: String,
20    /// The node's user agent, containing a client name, version, OS, and other metadata.
21    pub name: String,
22    /// The enode URL of the connected node.
23    pub enode: String,
24    /// The [ENR](https://eips.ethereum.org/EIPS/eip-778) of the running client.
25    pub enr: String,
26    /// The IP address of the connected node.
27    pub ip: IpAddr,
28    /// The node's listening ports.
29    pub ports: Ports,
30    /// The node's listening address.
31    #[serde(rename = "listenAddr")]
32    pub listen_addr: SocketAddr,
33    /// The protocols that the node supports, with protocol metadata.
34    pub protocols: ProtocolInfo,
35}
36
37/// Represents a node's discovery and listener ports.
38#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
39pub struct Ports {
40    /// The node's discovery port.
41    pub discovery: u16,
42    /// The node's listener port.
43    pub listener: u16,
44}
45
46/// Represents protocols that the connected RPC node supports.
47///
48/// This contains protocol information reported by the connected RPC node.
49#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
50pub struct ProtocolInfo {
51    /// Details about the node's supported eth protocol. `None` if unsupported
52    #[serde(default, skip_serializing_if = "Option::is_none")]
53    pub eth: Option<EthProtocolInfo>,
54    /// Details about the node's supported snap protocol. `None` if unsupported
55    #[serde(default, skip_serializing_if = "Option::is_none")]
56    pub snap: Option<SnapProtocolInfo>,
57}
58
59/// Represents a short summary of the `eth` sub-protocol metadata known about the host peer.
60///
61/// See [geth's `NodeInfo`
62/// struct](https://github.com/ethereum/go-ethereum/blob/c2e0abce2eedc1ba2a1b32c46fd07ef18a25354a/eth/protocols/eth/handler.go#L129)
63/// for how these fields are determined.
64#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
65pub struct EthProtocolInfo {
66    /// The eth network version.
67    pub network: u64,
68    /// The total difficulty of the host's blockchain.
69    ///
70    /// NOTE: This is deprecated as total difficulty related fields are being removed from RPC,
71    /// since the merge has long passed.
72    ///
73    /// See changes to geth's `NodeInfo` structs:
74    /// * <https://github.com/ethereum/go-ethereum/pull/30744>
75    /// * <https://github.com/ethereum/go-ethereum/blob/314e18193eeca3e47b627408da47e33132d72aa8/eth/protocols/eth/handler.go#L119-L126>
76    #[deprecated(
77        since = "0.8.2",
78        note = "`difficulty` is being removed from `admin_nodeInfo`, see https://github.com/ethereum/go-ethereum/pull/30744"
79    )]
80    pub difficulty: Option<U256>,
81    /// The Keccak hash of the host's genesis block.
82    pub genesis: B256,
83    /// The chain configuration for the host's fork rules.
84    pub config: ChainConfig,
85    /// The hash of the host's best known block.
86    pub head: B256,
87}
88
89/// Represents a short summary of the host's `snap` sub-protocol metadata.
90///
91/// This is just an empty struct, because [geth's internal representation is
92/// empty](https://github.com/ethereum/go-ethereum/blob/c2e0abce2eedc1ba2a1b32c46fd07ef18a25354a/eth/protocols/snap/handler.go#L571-L576).
93#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
94pub struct SnapProtocolInfo {}
95
96/// Represents the protocols that a peer supports.
97///
98/// This differs from [`ProtocolInfo`] in that [`PeerProtocolInfo`] contains protocol information
99/// gathered from the protocol handshake, and [`ProtocolInfo`] contains information reported by the
100/// connected RPC node.
101#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
102pub struct PeerProtocolInfo {
103    /// Details about the peer's supported eth protocol. `None` if unsupported
104    #[serde(default, skip_serializing_if = "Option::is_none")]
105    pub eth: Option<EthPeerInfo>,
106    /// Details about the peer's supported snap protocol. `None` if unsupported
107    #[serde(default, skip_serializing_if = "Option::is_none")]
108    pub snap: Option<SnapPeerInfo>,
109    /// Placeholder for any other protocols
110    #[serde(flatten, default)]
111    pub other: BTreeMap<String, serde_json::Value>,
112}
113
114/// Can contain either eth protocol info or a string "handshake", which geth uses if the peer is
115/// still completing the handshake for the protocol.
116#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
117#[serde(untagged)]
118pub enum EthPeerInfo {
119    /// The `eth` sub-protocol metadata known about the host peer.
120    Info(EthInfo),
121    /// The string "handshake" if the peer is still completing the handshake for the protocol.
122    #[serde(with = "handshake")]
123    Handshake,
124}
125
126/// Represents a short summary of the `eth` sub-protocol metadata known about a connected peer
127///
128/// See [geth's `ethPeerInfo`
129/// struct](https://github.com/ethereum/go-ethereum/blob/94579932b18931115f28aa7f87f02450bda084c9/eth/peer.go#L26)
130/// for how these fields are determined.
131#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
132#[serde(default)]
133pub struct EthInfo {
134    /// The negotiated eth version.
135    pub version: u64,
136}
137
138/// Can contain either snap protocol info or a string "handshake", which geth uses if the peer is
139/// still completing the handshake for the protocol.
140#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
141#[serde(untagged)]
142pub enum SnapPeerInfo {
143    /// The `snap` sub-protocol metadata known about the host peer.
144    Info(SnapInfo),
145    /// The string "handshake" if the peer is still completing the handshake for the protocol.
146    #[serde(with = "handshake")]
147    Handshake,
148}
149
150/// Represents a short summary of the `snap` sub-protocol metadata known about a connected peer.
151///
152/// See [geth's `snapPeerInfo`
153/// struct](https://github.com/ethereum/go-ethereum/blob/94579932b18931115f28aa7f87f02450bda084c9/eth/peer.go#L45)
154/// for how these fields are determined.
155#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
156#[serde(default)]
157pub struct SnapInfo {
158    /// The negotiated snap version.
159    pub version: u64,
160}
161
162/// Represents a short summary of information known about a connected peer.
163///
164/// See [geth's `PeerInfo` struct](https://github.com/ethereum/go-ethereum/blob/94579932b18931115f28aa7f87f02450bda084c9/p2p/peer.go#L495) for the source of each field.
165#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
166pub struct PeerInfo {
167    /// The peer's ENR.
168    #[serde(default, skip_serializing_if = "Option::is_none")]
169    pub enr: Option<String>,
170    /// The peer's enode URL.
171    pub enode: String,
172    /// The peer's enode ID.
173    pub id: String,
174    /// The peer's name.
175    pub name: String,
176    /// The peer's capabilities.
177    pub caps: Vec<String>,
178    /// Networking information about the peer.
179    pub network: PeerNetworkInfo,
180    /// The protocols that the peer supports, with protocol metadata.
181    pub protocols: PeerProtocolInfo,
182}
183
184/// Represents networking related information about the peer, including details about whether or
185/// not it is inbound, trusted, or static.
186#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
187#[serde(rename_all = "camelCase")]
188pub struct PeerNetworkInfo {
189    /// The local endpoint of the TCP connection.
190    pub local_address: SocketAddr,
191    /// The remote endpoint of the TCP connection.
192    pub remote_address: SocketAddr,
193    /// Whether or not the peer is inbound.
194    pub inbound: bool,
195    /// Whether or not the peer is trusted.
196    pub trusted: bool,
197    /// Whether or not the peer is a static peer.
198    #[serde(rename = "static")]
199    pub static_node: bool,
200}
201
202/// The type of a peer event.
203#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
204#[serde(rename_all = "lowercase")]
205pub enum PeerEventType {
206    /// A peer was added to the server.
207    Add,
208    /// A peer was dropped from the server.
209    Drop,
210    /// A message was successfully sent to the peer.
211    MsgSend,
212    /// A message was successfully received by the peer.
213    MsgRecv,
214}
215
216/// An event emitted when peers are either added or dropped from a p2p server or when a message is
217/// sent or received on a peer connection.
218///
219/// See [geth's `PeerEvent` struct](https://github.com/ethereum/go-ethereum/blob/94579932b18931115f28aa7f87f02450bda084c9/p2p/peer.go#L94-L103) for the source of each field.
220#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
221pub struct PeerEvent {
222    /// The type of the event.
223    #[serde(rename = "type")]
224    pub kind: PeerEventType,
225    /// The peer's enode ID.
226    pub peer: String,
227    /// An error occurred on the peer.
228    #[serde(default, skip_serializing_if = "Option::is_none")]
229    pub error: Option<String>,
230    /// The protocol of the peer.
231    #[serde(default, skip_serializing_if = "Option::is_none")]
232    pub protocol: Option<String>,
233    /// The message code.
234    #[serde(default, skip_serializing_if = "Option::is_none")]
235    pub msg_code: Option<u64>,
236    /// The message size.
237    #[serde(default, skip_serializing_if = "Option::is_none")]
238    pub msg_size: Option<u32>,
239    /// The local endpoint of the TCP connection.
240    #[serde(default, skip_serializing_if = "Option::is_none")]
241    pub local_address: Option<SocketAddr>,
242    /// The remote endpoint of the TCP connection.
243    #[serde(default, skip_serializing_if = "Option::is_none")]
244    pub remote_address: Option<SocketAddr>,
245}
246
247mod handshake {
248    use super::*;
249
250    pub(crate) fn deserialize<'de, D: serde::Deserializer<'de>>(
251        deserializer: D,
252    ) -> Result<(), D::Error> {
253        let s = String::deserialize(deserializer)?;
254        if s == "handshake" {
255            Ok(())
256        } else {
257            Err(serde::de::Error::custom(
258                "expected \"handshake\" if protocol info did not appear in the response",
259            ))
260        }
261    }
262
263    pub(crate) fn serialize<S: serde::Serializer>(serializer: S) -> Result<S::Ok, S::Error> {
264        serializer.serialize_str("handshake")
265    }
266}
267
268#[cfg(test)]
269mod tests {
270    use super::*;
271    use similar_asserts::assert_eq;
272    use std::str::FromStr;
273
274    #[test]
275    fn deserialize_peer_info() {
276        let response = r#"{
277            "enode":"enode://bb37b7302f79e47c1226d6e3ccf0ef6d51146019efdcc1f6e861fd1c1a78d5e84e486225a6a8a503b93d5c50125ee980835c92bde7f7d12f074c16f4e439a578@127.0.0.1:60872",
278            "id":"ca23c04b7e796da5d6a5f04a62b81c88d41b1341537db85a2b6443e838d8339b",
279            "name":"Geth/v1.10.19-stable/darwin-arm64/go1.18.3",
280            "caps":["eth/66","eth/67","snap/1"],
281            "network":{
282                "localAddress":"127.0.0.1:30304",
283                "remoteAddress":"127.0.0.1:60872",
284                "inbound":true,
285                "trusted":false,
286                "static":false
287            },
288            "protocols":{
289                "eth":{
290                    "version":67,
291                    "difficulty":0,
292                    "head":"0xb04009ddf4b0763f42778e7d5937e49bebf1e11b2d26c9dac6cefb5f84b6f8ea"
293                },
294                "snap":{"version":1}
295            }
296        }"#;
297        let peer_info: PeerInfo = serde_json::from_str(response).unwrap();
298
299        assert_eq!(peer_info.enode, "enode://bb37b7302f79e47c1226d6e3ccf0ef6d51146019efdcc1f6e861fd1c1a78d5e84e486225a6a8a503b93d5c50125ee980835c92bde7f7d12f074c16f4e439a578@127.0.0.1:60872");
300    }
301
302    #[test]
303    fn deserialize_peer_info_handshake() {
304        let response = r#"{
305            "enode": "enode://a997fde0023537ad01e536ebf2eeeb4b4b3d5286707586727b704f32e8e2b4959e08b6db5b27eb6b7e9f6efcbb53657f4e2bd16900aa77a89426dc3382c29ce0@[::1]:60948",
306            "id": "df6f8bc331005962c2ef1f5236486a753bc6b2ddb5ef04370757999d1ca832d4",
307            "name": "Geth/v1.10.26-stable-e5eb32ac/linux-amd64/go1.18.5",
308            "caps": ["eth/66","eth/67","snap/1"],
309            "network":{
310                "localAddress":"[::1]:30304",
311                "remoteAddress":"[::1]:60948",
312                "inbound":true,
313                "trusted":false,
314                "static":false
315            },
316            "protocols":{
317                "eth":"handshake",
318                "snap":"handshake"
319            }
320        }"#;
321
322        let info: PeerInfo = serde_json::from_str(response).unwrap();
323        assert_eq!(info.protocols.eth, Some(EthPeerInfo::Handshake));
324        assert_eq!(info.protocols.snap, Some(SnapPeerInfo::Handshake));
325    }
326
327    #[test]
328    fn deserialize_peer_info_newer() {
329        let response = r#"{
330            "enode": "enode://f769f8cf850dd9f88a13c81ff3e70c3400cf93511c676c6d50f0e359beb43c28388931f64f56ab4110ccced37fb08163b6966fe42b6e15ec647fa8087914463d@127.0.0.1:45591?discport=0",
331            "id": "daa738efebf7e349b9f5b1a91d782e7355060bb15af8570e23463729d0632deb",
332            "name": "Geth/v1.13.14-stable-2bd6bd01/linux-amd64/go1.21.6",
333            "caps": ["eth/68", "snap/1"],
334            "network": {
335                "localAddress": "127.0.0.1:33236",
336                "remoteAddress": "127.0.0.1:45591",
337                "inbound": false,
338                "trusted": false,
339                "static": true
340            },
341            "protocols": { "eth": { "version": 68 }, "snap": { "version": 1 } }
342        }"#;
343
344        let info: PeerInfo = serde_json::from_str(response).unwrap();
345        assert_eq!(info.protocols.eth, Some(EthPeerInfo::Info(EthInfo { version: 68 })));
346        assert_eq!(info.protocols.snap, Some(SnapPeerInfo::Info(SnapInfo { version: 1 })));
347    }
348
349    #[test]
350    fn deserialize_node_info() {
351        // this response also has an enr
352        let response = r#"{
353            "id":"6e2fe698f3064cd99410926ce16734e35e3cc947d4354461d2594f2d2dd9f7b6",
354            "name":"Geth/v1.10.19-stable/darwin-arm64/go1.18.3",
355            "enode":"enode://d7dfaea49c7ef37701e668652bcf1bc63d3abb2ae97593374a949e175e4ff128730a2f35199f3462a56298b981dfc395a5abebd2d6f0284ffe5bdc3d8e258b86@127.0.0.1:30304?discport=0",
356            "enr":"enr:-Jy4QIvS0dKBLjTTV_RojS8hjriwWsJNHRVyOh4Pk4aUXc5SZjKRVIOeYc7BqzEmbCjLdIY4Ln7x5ZPf-2SsBAc2_zqGAYSwY1zog2V0aMfGhNegsXuAgmlkgnY0gmlwhBiT_DiJc2VjcDI1NmsxoQLX366knH7zdwHmaGUrzxvGPTq7Kul1kzdKlJ4XXk_xKIRzbmFwwIN0Y3CCdmA",
357            "ip":"127.0.0.1",
358            "ports":{
359                "discovery":0,
360                "listener":30304
361            },
362            "listenAddr":"[::]:30304",
363            "protocols":{
364                "eth":{
365                    "network":1337,
366                    "difficulty":0,
367                    "genesis":"0xb04009ddf4b0763f42778e7d5937e49bebf1e11b2d26c9dac6cefb5f84b6f8ea",
368                    "config":{
369                        "chainId":0,
370                        "eip150Hash":"0x0000000000000000000000000000000000000000000000000000000000000000"
371                    },
372                    "head":"0xb04009ddf4b0763f42778e7d5937e49bebf1e11b2d26c9dac6cefb5f84b6f8ea"
373                },
374                "snap":{}
375            }
376        }"#;
377
378        let _: NodeInfo = serde_json::from_str(response).unwrap();
379    }
380
381    #[test]
382    fn deserialize_node_info_post_merge() {
383        // this response also has an enr
384        let response = r#"{
385            "id":"6e2fe698f3064cd99410926ce16734e35e3cc947d4354461d2594f2d2dd9f7b6",
386            "name":"Geth/v1.10.19-stable/darwin-arm64/go1.18.3",
387            "enode":"enode://d7dfaea49c7ef37701e668652bcf1bc63d3abb2ae97593374a949e175e4ff128730a2f35199f3462a56298b981dfc395a5abebd2d6f0284ffe5bdc3d8e258b86@127.0.0.1:30304?discport=0",
388            "enr":"enr:-Jy4QIvS0dKBLjTTV_RojS8hjriwWsJNHRVyOh4Pk4aUXc5SZjKRVIOeYc7BqzEmbCjLdIY4Ln7x5ZPf-2SsBAc2_zqGAYSwY1zog2V0aMfGhNegsXuAgmlkgnY0gmlwhBiT_DiJc2VjcDI1NmsxoQLX366knH7zdwHmaGUrzxvGPTq7Kul1kzdKlJ4XXk_xKIRzbmFwwIN0Y3CCdmA",
389            "ip":"127.0.0.1",
390            "ports":{
391                "discovery":0,
392                "listener":30304
393            },
394            "listenAddr":"[::]:30304",
395            "protocols":{
396                "eth":{
397                    "network":1337,
398                    "difficulty":0,
399                    "genesis":"0xb04009ddf4b0763f42778e7d5937e49bebf1e11b2d26c9dac6cefb5f84b6f8ea",
400                    "config":{
401                        "chainId":0,
402                        "eip150Hash":"0x0000000000000000000000000000000000000000000000000000000000000000",
403                        "terminalTotalDifficulty": "0xC70D808A128D7380000",
404                        "terminalTotalDifficultyPassed":true,
405                        "ethash":{}
406                    },
407                    "head":"0xb04009ddf4b0763f42778e7d5937e49bebf1e11b2d26c9dac6cefb5f84b6f8ea"
408                },
409                "snap":{}
410            }
411        }"#;
412
413        let _: NodeInfo = serde_json::from_str(response).unwrap();
414    }
415
416    #[test]
417    fn deserialize_node_info_mainnet_full() {
418        let actual_response = r#"{
419            "id": "74477ca052fcf55ee9eafb369fafdb3e91ad7b64fbd7ae15a4985bfdc43696f2",
420            "name": "Geth/v1.10.26-stable/darwin-arm64/go1.19.3",
421            "enode": "enode://962184c6f2a19e064e2ddf0d5c5a788c8c5ed3a4909b7f75fb4dad967392ff542772bcc498cd7f15e13eecbde830265f379779c6da1f71fb8fe1a4734dfc0a1e@127.0.0.1:13337?discport=0",
422            "enr": "enr:-J-4QFttJyL3f2-B2TQmBZNFxex99TSBv1YtB_8jqUbXWkf6LOREKQAPW2bIn8kJ8QvHbWxCQNFzTX6sehjbrz1ZkSuGAYSyQ0_rg2V0aMrJhPxk7ASDEYwwgmlkgnY0gmlwhH8AAAGJc2VjcDI1NmsxoQKWIYTG8qGeBk4t3w1cWniMjF7TpJCbf3X7Ta2Wc5L_VIRzbmFwwIN0Y3CCNBk",
423            "ip": "127.0.0.1",
424            "ports": {
425                "discovery": 0,
426                "listener": 13337
427            },
428            "listenAddr": "[::]:13337",
429            "protocols": {
430                "eth": {
431                    "network": 1337,
432                    "difficulty": 17179869184,
433                    "genesis": "0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3",
434                    "config": {
435                        "chainId": 1,
436                        "homesteadBlock": 1150000,
437                        "daoForkBlock": 1920000,
438                        "daoForkSupport": true,
439                        "eip150Block": 2463000,
440                        "eip150Hash": "0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0",
441                        "eip155Block": 2675000,
442                        "eip158Block": 2675000,
443                        "byzantiumBlock": 4370000,
444                        "constantinopleBlock": 7280000,
445                        "petersburgBlock": 7280000,
446                        "istanbulBlock": 9069000,
447                        "muirGlacierBlock": 9200000,
448                        "berlinBlock": 12244000,
449                        "londonBlock": 12965000,
450                        "arrowGlacierBlock": 13773000,
451                        "grayGlacierBlock": 15050000,
452                        "terminalTotalDifficulty": "0xC70D808A128D7380000",
453                        "terminalTotalDifficultyPassed": true,
454                        "ethash": {}
455                    },
456                    "head": "0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3"
457                },
458                "snap": {}
459            }
460        }"#;
461
462        let _: NodeInfo = serde_json::from_str(actual_response).unwrap();
463    }
464
465    #[test]
466    fn serialize_deserialize_node_info_roundtrip() {
467        let node_info = NodeInfo {
468            id: "6e2fe698f3064cd99410926ce16734e35e3cc947d4354461d2594f2d2dd9f7b6".to_string(),
469            name: "Geth/v1.10.19-stable/darwin-arm64/go1.18.3".to_string(),
470            enode: "enode://d7dfaea49c7ef37701e668652bcf1bc63d3abb2ae97593374a949e175e4ff128730a2f35199f3462a56298b981dfc395a5abebd2d6f0284ffe5bdc3d8e258b86@127.0.0.1:30304?discport=0".to_string(),
471            enr: "enr:-Jy4QIvS0dKBLjTTV_RojS8hjriwWsJNHRVyOh4Pk4aUXc5SZjKRVIOeYc7BqzEmbCjLdIY4Ln7x5ZPf-2SsBAc2_zqGAYSwY1zog2V0aMfGhNegsXuAgmlkgnY0gmlwhBiT_DiJc2VjcDI1NmsxoQLX366knH7zdwHmaGUrzxvGPTq7Kul1kzdKlJ4XXk_xKIRzbmFwwIN0Y3CCdmA".to_string(),
472            ip: "127.0.0.1".parse().unwrap(),
473            ports: Ports {
474                discovery: 0,
475                listener: 30304,
476            },
477            listen_addr: "[::]:30304".parse().unwrap(),
478            protocols: ProtocolInfo {
479                eth: Some(EthProtocolInfo {
480                    network: 1337,
481                    #[allow(deprecated)]
482                    difficulty: Some(U256::from(0)),
483                    genesis: B256::from_str("0xb04009ddf4b0763f42778e7d5937e49bebf1e11b2d26c9dac6cefb5f84b6f8ea").unwrap(),
484                    config: ChainConfig::default(),
485                    head: B256::from_str("0xb04009ddf4b0763f42778e7d5937e49bebf1e11b2d26c9dac6cefb5f84b6f8ea").unwrap(),
486                }),
487                snap: Some(SnapProtocolInfo {}),
488            },
489        };
490
491        let serialized = serde_json::to_string(&node_info).expect("Serialization failed");
492        let deserialized: NodeInfo =
493            serde_json::from_str(&serialized).expect("Deserialization failed");
494
495        assert_eq!(node_info, deserialized);
496    }
497
498    #[test]
499    fn serialize_deserialize_peer_info_roundtrip() {
500        let peer_info = PeerInfo {
501            enr: Some("enr:-Jy4QIvS0dKBLjTTV_RojS8hjriwWsJNHRVyOh4Pk4aUXc5SZjKRVIOeYc7BqzEmbCjLdIY4Ln7x5ZPf-2SsBAc2_zqGAYSwY1zog2V0aMfGhNegsXuAgmlkgnY0gmlwhBiT_DiJc2VjcDI1NmsxoQLX366knH7zdwHmaGUrzxvGPTq7Kul1kzdKlJ4XXk_xKIRzbmFwwIN0Y3CCdmA".to_string()),
502            enode: "enode://bb37b7302f79e47c1226d6e3ccf0ef6d51146019efdcc1f6e861fd1c1a78d5e84e486225a6a8a503b93d5c50125ee980835c92bde7f7d12f074c16f4e439a578@127.0.0.1:60872".to_string(),
503            id: "ca23c04b7e796da5d6a5f04a62b81c88d41b1341537db85a2b6443e838d8339b".to_string(),
504            name: "Geth/v1.10.19-stable/darwin-arm64/go1.18.3".to_string(),
505            caps: vec!["eth/66".to_string(), "eth/67".to_string(), "snap/1".to_string()],
506            network: PeerNetworkInfo {
507                local_address: "127.0.0.1:30304".parse().unwrap(),
508                remote_address: "127.0.0.1:60872".parse().unwrap(),
509                inbound: true,
510                trusted: false,
511                static_node: false,
512            },
513            protocols: PeerProtocolInfo {
514                eth: Some(EthPeerInfo::Info(EthInfo { version: 67 })),
515                snap: Some(SnapPeerInfo::Info(SnapInfo { version: 1 })),
516                other: BTreeMap::new(),
517            },
518        };
519
520        let serialized = serde_json::to_string(&peer_info).expect("Serialization failed");
521        let deserialized: PeerInfo =
522            serde_json::from_str(&serialized).expect("Deserialization failed");
523
524        assert_eq!(peer_info, deserialized);
525    }
526
527    #[test]
528    fn serialize_deserialize_peer_info_handshake_roundtrip() {
529        let peer_info = PeerInfo {
530            enr: None,
531            enode: "enode://a997fde0023537ad01e536ebf2eeeb4b4b3d5286707586727b704f32e8e2b4959e08b6db5b27eb6b7e9f6efcbb53657f4e2bd16900aa77a89426dc3382c29ce0@[::1]:60948".to_string(),
532            id: "df6f8bc331005962c2ef1f5236486a753bc6b2ddb5ef04370757999d1ca832d4".to_string(),
533            name: "Geth/v1.10.26-stable-e5eb32ac/linux-amd64/go1.18.5".to_string(),
534            caps: vec!["eth/66".to_string(), "eth/67".to_string(), "snap/1".to_string()],
535            network: PeerNetworkInfo {
536                local_address: "[::1]:30304".parse().unwrap(),
537                remote_address: "[::1]:60948".parse().unwrap(),
538                inbound: true,
539                trusted: false,
540                static_node: false,
541            },
542            protocols: PeerProtocolInfo {
543                eth: Some(EthPeerInfo::Handshake),
544                snap: Some(SnapPeerInfo::Handshake),
545                other: BTreeMap::new(),
546            },
547        };
548
549        let serialized = serde_json::to_string(&peer_info).expect("Serialization failed");
550        let deserialized: PeerInfo =
551            serde_json::from_str(&serialized).expect("Deserialization failed");
552
553        assert_eq!(peer_info, deserialized);
554    }
555
556    #[test]
557    fn serialize_deserialize_peer_info_with_optional_fields_roundtrip() {
558        let peer_info = PeerInfo {
559            enr: None,
560            enode: "enode://f769f8cf850dd9f88a13c81ff3e70c3400cf93511c676c6d50f0e359beb43c28388931f64f56ab4110ccced37fb08163b6966fe42b6e15ec647fa8087914463d@127.0.0.1:45591?discport=0".to_string(),
561            id: "daa738efebf7e349b9f5b1a91d782e7355060bb15af8570e23463729d0632deb".to_string(),
562            name: "Geth/v1.13.14-stable-2bd6bd01/linux-amd64/go1.21.6".to_string(),
563            caps: vec!["eth/68".to_string(), "snap/1".to_string()],
564            network: PeerNetworkInfo {
565                local_address: "127.0.0.1:33236".parse().unwrap(),
566                remote_address: "127.0.0.1:45591".parse().unwrap(),
567                inbound: false,
568                trusted: false,
569                static_node: true,
570            },
571            protocols: PeerProtocolInfo {
572                eth: Some(EthPeerInfo::Info(EthInfo { version: 68 })),
573                snap: Some(SnapPeerInfo::Info(SnapInfo { version: 1 })),
574                other: BTreeMap::new(),
575            },
576        };
577
578        let serialized = serde_json::to_string(&peer_info).expect("Serialization failed");
579        let deserialized: PeerInfo =
580            serde_json::from_str(&serialized).expect("Deserialization failed");
581
582        assert_eq!(peer_info, deserialized);
583    }
584
585    #[test]
586    fn serialize_deserialize_peer_event_roundtrip() {
587        let peer_event = PeerEvent {
588            kind: PeerEventType::Add,
589            peer: "ca23c04b7e796da5d6a5f04a62b81c88d41b1341537db85a2b6443e838d8339b".to_string(),
590            error: None,
591            protocol: None,
592            msg_code: None,
593            msg_size: None,
594            local_address: Some("127.0.0.1:30304".parse().unwrap()),
595            remote_address: Some("127.0.0.1:60872".parse().unwrap()),
596        };
597
598        let serialized = serde_json::to_string(&peer_event).expect("Serialization failed");
599        let deserialized: PeerEvent =
600            serde_json::from_str(&serialized).expect("Deserialization failed");
601
602        assert_eq!(peer_event, deserialized);
603    }
604
605    #[test]
606    fn serialize_deserialize_peer_event_with_all_fields_roundtrip() {
607        let peer_event = PeerEvent {
608            kind: PeerEventType::MsgRecv,
609            peer: "df6f8bc331005962c2ef1f5236486a753bc6b2ddb5ef04370757999d1ca832d4".to_string(),
610            error: Some("connection error".to_string()),
611            protocol: Some("eth".to_string()),
612            msg_code: Some(0x10),
613            msg_size: Some(1024),
614            local_address: Some("[::1]:30304".parse().unwrap()),
615            remote_address: Some("[::1]:60948".parse().unwrap()),
616        };
617
618        let serialized = serde_json::to_string(&peer_event).expect("Serialization failed");
619        let deserialized: PeerEvent =
620            serde_json::from_str(&serialized).expect("Deserialization failed");
621
622        assert_eq!(peer_event, deserialized);
623    }
624
625    #[test]
626    fn serialize_deserialize_peer_event_all_types_roundtrip() {
627        let event_types = vec![
628            PeerEventType::Add,
629            PeerEventType::Drop,
630            PeerEventType::MsgSend,
631            PeerEventType::MsgRecv,
632        ];
633
634        for event_type in event_types {
635            let peer_event = PeerEvent {
636                kind: event_type,
637                peer: "test_peer_id".to_string(),
638                error: None,
639                protocol: None,
640                msg_code: None,
641                msg_size: None,
642                local_address: None,
643                remote_address: None,
644            };
645
646            let serialized = serde_json::to_string(&peer_event).expect("Serialization failed");
647            let deserialized: PeerEvent =
648                serde_json::from_str(&serialized).expect("Deserialization failed");
649
650            assert_eq!(peer_event, deserialized);
651        }
652    }
653}