reth_eth_wire_types/
version.rs

1//! Support for representing the version of the `eth`
2
3use crate::alloc::string::ToString;
4use alloc::string::String;
5use alloy_rlp::{Decodable, Encodable, Error as RlpError};
6use bytes::BufMut;
7use core::{fmt, str::FromStr};
8use derive_more::Display;
9use reth_codecs_derive::add_arbitrary_tests;
10
11/// Error thrown when failed to parse a valid [`EthVersion`].
12#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
13#[error("Unknown eth protocol version: {0}")]
14pub struct ParseVersionError(String);
15
16/// The `eth` protocol version.
17#[repr(u8)]
18#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Display)]
19#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
20#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
21pub enum EthVersion {
22    /// The `eth` protocol version 66.
23    Eth66 = 66,
24    /// The `eth` protocol version 67.
25    Eth67 = 67,
26    /// The `eth` protocol version 68.
27    Eth68 = 68,
28    /// The `eth` protocol version 69.
29    Eth69 = 69,
30}
31
32impl EthVersion {
33    /// The latest known eth version
34    pub const LATEST: Self = Self::Eth68;
35
36    /// Returns the total number of messages the protocol version supports.
37    pub const fn total_messages(&self) -> u8 {
38        match self {
39            Self::Eth66 => 15,
40            Self::Eth67 | Self::Eth68 => {
41                // eth/67,68 are eth/66 minus GetNodeData and NodeData messages
42                13
43            }
44            // eth69 is both eth67 and eth68 minus NewBlockHashes and NewBlock
45            Self::Eth69 => 11,
46        }
47    }
48
49    /// Returns true if the version is eth/66
50    pub const fn is_eth66(&self) -> bool {
51        matches!(self, Self::Eth66)
52    }
53
54    /// Returns true if the version is eth/67
55    pub const fn is_eth67(&self) -> bool {
56        matches!(self, Self::Eth67)
57    }
58
59    /// Returns true if the version is eth/68
60    pub const fn is_eth68(&self) -> bool {
61        matches!(self, Self::Eth68)
62    }
63
64    /// Returns true if the version is eth/69
65    pub const fn is_eth69(&self) -> bool {
66        matches!(self, Self::Eth69)
67    }
68}
69
70/// RLP encodes `EthVersion` as a single byte (66-69).
71impl Encodable for EthVersion {
72    fn encode(&self, out: &mut dyn BufMut) {
73        (*self as u8).encode(out)
74    }
75
76    fn length(&self) -> usize {
77        (*self as u8).length()
78    }
79}
80
81/// RLP decodes a single byte into `EthVersion`.
82/// Returns error if byte is not a valid version (66-69).
83impl Decodable for EthVersion {
84    fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
85        let version = u8::decode(buf)?;
86        Self::try_from(version).map_err(|_| RlpError::Custom("invalid eth version"))
87    }
88}
89
90/// Allow for converting from a `&str` to an `EthVersion`.
91///
92/// # Example
93/// ```
94/// use reth_eth_wire_types::EthVersion;
95///
96/// let version = EthVersion::try_from("67").unwrap();
97/// assert_eq!(version, EthVersion::Eth67);
98/// ```
99impl TryFrom<&str> for EthVersion {
100    type Error = ParseVersionError;
101
102    #[inline]
103    fn try_from(s: &str) -> Result<Self, Self::Error> {
104        match s {
105            "66" => Ok(Self::Eth66),
106            "67" => Ok(Self::Eth67),
107            "68" => Ok(Self::Eth68),
108            "69" => Ok(Self::Eth69),
109            _ => Err(ParseVersionError(s.to_string())),
110        }
111    }
112}
113
114/// Allow for converting from a u8 to an `EthVersion`.
115///
116/// # Example
117/// ```
118/// use reth_eth_wire_types::EthVersion;
119///
120/// let version = EthVersion::try_from(67).unwrap();
121/// assert_eq!(version, EthVersion::Eth67);
122/// ```
123impl TryFrom<u8> for EthVersion {
124    type Error = ParseVersionError;
125
126    #[inline]
127    fn try_from(u: u8) -> Result<Self, Self::Error> {
128        match u {
129            66 => Ok(Self::Eth66),
130            67 => Ok(Self::Eth67),
131            68 => Ok(Self::Eth68),
132            69 => Ok(Self::Eth69),
133            _ => Err(ParseVersionError(u.to_string())),
134        }
135    }
136}
137
138impl FromStr for EthVersion {
139    type Err = ParseVersionError;
140
141    #[inline]
142    fn from_str(s: &str) -> Result<Self, Self::Err> {
143        Self::try_from(s)
144    }
145}
146
147impl From<EthVersion> for u8 {
148    #[inline]
149    fn from(v: EthVersion) -> Self {
150        v as Self
151    }
152}
153
154impl From<EthVersion> for &'static str {
155    #[inline]
156    fn from(v: EthVersion) -> &'static str {
157        match v {
158            EthVersion::Eth66 => "66",
159            EthVersion::Eth67 => "67",
160            EthVersion::Eth68 => "68",
161            EthVersion::Eth69 => "69",
162        }
163    }
164}
165
166/// RLPx `p2p` protocol version
167#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
168#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
169#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
170#[add_arbitrary_tests(rlp)]
171pub enum ProtocolVersion {
172    /// `p2p` version 4
173    V4 = 4,
174    /// `p2p` version 5
175    #[default]
176    V5 = 5,
177}
178
179impl fmt::Display for ProtocolVersion {
180    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
181        write!(f, "v{}", *self as u8)
182    }
183}
184
185impl Encodable for ProtocolVersion {
186    fn encode(&self, out: &mut dyn BufMut) {
187        (*self as u8).encode(out)
188    }
189    fn length(&self) -> usize {
190        // the version should be a single byte
191        (*self as u8).length()
192    }
193}
194
195impl Decodable for ProtocolVersion {
196    fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
197        let version = u8::decode(buf)?;
198        match version {
199            4 => Ok(Self::V4),
200            5 => Ok(Self::V5),
201            _ => Err(RlpError::Custom("unknown p2p protocol version")),
202        }
203    }
204}
205
206#[cfg(test)]
207mod tests {
208    use super::{EthVersion, ParseVersionError};
209    use alloy_rlp::{Decodable, Encodable, Error as RlpError};
210    use bytes::BytesMut;
211
212    #[test]
213    fn test_eth_version_try_from_str() {
214        assert_eq!(EthVersion::Eth66, EthVersion::try_from("66").unwrap());
215        assert_eq!(EthVersion::Eth67, EthVersion::try_from("67").unwrap());
216        assert_eq!(EthVersion::Eth68, EthVersion::try_from("68").unwrap());
217        assert_eq!(EthVersion::Eth69, EthVersion::try_from("69").unwrap());
218        assert_eq!(Err(ParseVersionError("70".to_string())), EthVersion::try_from("70"));
219    }
220
221    #[test]
222    fn test_eth_version_from_str() {
223        assert_eq!(EthVersion::Eth66, "66".parse().unwrap());
224        assert_eq!(EthVersion::Eth67, "67".parse().unwrap());
225        assert_eq!(EthVersion::Eth68, "68".parse().unwrap());
226        assert_eq!(EthVersion::Eth69, "69".parse().unwrap());
227        assert_eq!(Err(ParseVersionError("70".to_string())), "70".parse::<EthVersion>());
228    }
229
230    #[test]
231    fn test_eth_version_rlp_encode() {
232        let versions = [EthVersion::Eth66, EthVersion::Eth67, EthVersion::Eth68, EthVersion::Eth69];
233
234        for version in versions {
235            let mut encoded = BytesMut::new();
236            version.encode(&mut encoded);
237
238            assert_eq!(encoded.len(), 1);
239            assert_eq!(encoded[0], version as u8);
240        }
241    }
242    #[test]
243    fn test_eth_version_rlp_decode() {
244        let test_cases = [
245            (66_u8, Ok(EthVersion::Eth66)),
246            (67_u8, Ok(EthVersion::Eth67)),
247            (68_u8, Ok(EthVersion::Eth68)),
248            (69_u8, Ok(EthVersion::Eth69)),
249            (70_u8, Err(RlpError::Custom("invalid eth version"))),
250            (65_u8, Err(RlpError::Custom("invalid eth version"))),
251        ];
252
253        for (input, expected) in test_cases {
254            let mut encoded = BytesMut::new();
255            input.encode(&mut encoded);
256
257            let mut slice = encoded.as_ref();
258            let result = EthVersion::decode(&mut slice);
259            assert_eq!(result, expected);
260        }
261    }
262
263    #[test]
264    fn test_eth_version_total_messages() {
265        assert_eq!(EthVersion::Eth66.total_messages(), 15);
266        assert_eq!(EthVersion::Eth67.total_messages(), 13);
267        assert_eq!(EthVersion::Eth68.total_messages(), 13);
268        assert_eq!(EthVersion::Eth69.total_messages(), 11);
269    }
270}