Skip to main content

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    /// The `eth` protocol version 70.
31    Eth70 = 70,
32    /// The `eth` protocol version 71.
33    Eth71 = 71,
34    /// The `eth` protocol version 72.
35    Eth72 = 72,
36}
37
38impl EthVersion {
39    /// The latest known eth version
40    pub const LATEST: Self = Self::Eth69;
41
42    /// All known eth versions
43    pub const ALL_VERSIONS: &'static [Self] = &[Self::Eth69, Self::Eth68, Self::Eth67, Self::Eth66];
44
45    /// Returns true if the version is eth/66
46    pub const fn is_eth66(&self) -> bool {
47        matches!(self, Self::Eth66)
48    }
49
50    /// Returns true if the version is eth/67
51    pub const fn is_eth67(&self) -> bool {
52        matches!(self, Self::Eth67)
53    }
54
55    /// Returns true if the version is eth/68
56    pub const fn is_eth68(&self) -> bool {
57        matches!(self, Self::Eth68)
58    }
59
60    /// Returns true if the version is eth/69
61    pub const fn is_eth69(&self) -> bool {
62        matches!(self, Self::Eth69)
63    }
64
65    /// Returns true if the version is eth/70
66    pub const fn is_eth70(&self) -> bool {
67        matches!(self, Self::Eth70)
68    }
69
70    /// Returns true if the version is eth/71
71    pub const fn is_eth71(&self) -> bool {
72        matches!(self, Self::Eth71)
73    }
74
75    /// Returns true if the version is eth/71
76    pub const fn is_eth72(&self) -> bool {
77        matches!(self, Self::Eth72)
78    }
79
80    /// Returns true if the version is eth/69 or newer.
81    pub const fn is_eth69_or_newer(&self) -> bool {
82        matches!(self, Self::Eth69 | Self::Eth70 | Self::Eth71 | Self::Eth72)
83    }
84}
85
86/// RLP encodes `EthVersion` as a single byte (66-71).
87impl Encodable for EthVersion {
88    fn encode(&self, out: &mut dyn BufMut) {
89        (*self as u8).encode(out)
90    }
91
92    fn length(&self) -> usize {
93        (*self as u8).length()
94    }
95}
96
97/// RLP decodes a single byte into `EthVersion`.
98/// Returns error if byte is not a valid version (66-71).
99impl Decodable for EthVersion {
100    fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
101        let version = u8::decode(buf)?;
102        Self::try_from(version).map_err(|_| RlpError::Custom("invalid eth version"))
103    }
104}
105
106/// Allow for converting from a `&str` to an `EthVersion`.
107///
108/// # Example
109/// ```
110/// use reth_eth_wire_types::EthVersion;
111///
112/// let version = EthVersion::try_from("67").unwrap();
113/// assert_eq!(version, EthVersion::Eth67);
114/// ```
115impl TryFrom<&str> for EthVersion {
116    type Error = ParseVersionError;
117
118    #[inline]
119    fn try_from(s: &str) -> Result<Self, Self::Error> {
120        match s {
121            "66" => Ok(Self::Eth66),
122            "67" => Ok(Self::Eth67),
123            "68" => Ok(Self::Eth68),
124            "69" => Ok(Self::Eth69),
125            "70" => Ok(Self::Eth70),
126            "71" => Ok(Self::Eth71),
127            "72" => Ok(Self::Eth72),
128            _ => Err(ParseVersionError(s.to_string())),
129        }
130    }
131}
132
133/// Allow for converting from a u8 to an `EthVersion`.
134///
135/// # Example
136/// ```
137/// use reth_eth_wire_types::EthVersion;
138///
139/// let version = EthVersion::try_from(67).unwrap();
140/// assert_eq!(version, EthVersion::Eth67);
141/// ```
142impl TryFrom<u8> for EthVersion {
143    type Error = ParseVersionError;
144
145    #[inline]
146    fn try_from(u: u8) -> Result<Self, Self::Error> {
147        match u {
148            66 => Ok(Self::Eth66),
149            67 => Ok(Self::Eth67),
150            68 => Ok(Self::Eth68),
151            69 => Ok(Self::Eth69),
152            70 => Ok(Self::Eth70),
153            71 => Ok(Self::Eth71),
154            72 => Ok(Self::Eth72),
155            _ => Err(ParseVersionError(u.to_string())),
156        }
157    }
158}
159
160impl FromStr for EthVersion {
161    type Err = ParseVersionError;
162
163    #[inline]
164    fn from_str(s: &str) -> Result<Self, Self::Err> {
165        Self::try_from(s)
166    }
167}
168
169impl From<EthVersion> for u8 {
170    #[inline]
171    fn from(v: EthVersion) -> Self {
172        v as Self
173    }
174}
175
176impl From<EthVersion> for &'static str {
177    #[inline]
178    fn from(v: EthVersion) -> &'static str {
179        match v {
180            EthVersion::Eth66 => "66",
181            EthVersion::Eth67 => "67",
182            EthVersion::Eth68 => "68",
183            EthVersion::Eth69 => "69",
184            EthVersion::Eth70 => "70",
185            EthVersion::Eth71 => "71",
186            EthVersion::Eth72 => "72",
187        }
188    }
189}
190
191/// `RLPx` `p2p` protocol version
192#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
193#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
194#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
195#[add_arbitrary_tests(rlp)]
196pub enum ProtocolVersion {
197    /// `p2p` version 4
198    V4 = 4,
199    /// `p2p` version 5
200    #[default]
201    V5 = 5,
202}
203
204impl fmt::Display for ProtocolVersion {
205    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
206        write!(f, "v{}", *self as u8)
207    }
208}
209
210impl Encodable for ProtocolVersion {
211    fn encode(&self, out: &mut dyn BufMut) {
212        (*self as u8).encode(out)
213    }
214    fn length(&self) -> usize {
215        // the version should be a single byte
216        (*self as u8).length()
217    }
218}
219
220impl Decodable for ProtocolVersion {
221    fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
222        let version = u8::decode(buf)?;
223        match version {
224            4 => Ok(Self::V4),
225            5 => Ok(Self::V5),
226            _ => Err(RlpError::Custom("unknown p2p protocol version")),
227        }
228    }
229}
230
231#[cfg(test)]
232mod tests {
233    use super::EthVersion;
234    use alloy_rlp::{Decodable, Encodable, Error as RlpError};
235    use bytes::BytesMut;
236
237    #[test]
238    fn test_eth_version_try_from_str() {
239        assert_eq!(EthVersion::Eth66, EthVersion::try_from("66").unwrap());
240        assert_eq!(EthVersion::Eth67, EthVersion::try_from("67").unwrap());
241        assert_eq!(EthVersion::Eth68, EthVersion::try_from("68").unwrap());
242        assert_eq!(EthVersion::Eth69, EthVersion::try_from("69").unwrap());
243        assert_eq!(EthVersion::Eth70, EthVersion::try_from("70").unwrap());
244        assert_eq!(EthVersion::Eth71, EthVersion::try_from("71").unwrap());
245        assert_eq!(EthVersion::Eth72, EthVersion::try_from("72").unwrap());
246    }
247
248    #[test]
249    fn test_eth_version_from_str() {
250        assert_eq!(EthVersion::Eth66, "66".parse().unwrap());
251        assert_eq!(EthVersion::Eth67, "67".parse().unwrap());
252        assert_eq!(EthVersion::Eth68, "68".parse().unwrap());
253        assert_eq!(EthVersion::Eth69, "69".parse().unwrap());
254        assert_eq!(EthVersion::Eth70, "70".parse().unwrap());
255        assert_eq!(EthVersion::Eth71, "71".parse().unwrap());
256        assert_eq!(EthVersion::Eth72, "72".parse().unwrap());
257    }
258
259    #[test]
260    fn test_eth_version_rlp_encode() {
261        let versions = [
262            EthVersion::Eth66,
263            EthVersion::Eth67,
264            EthVersion::Eth68,
265            EthVersion::Eth69,
266            EthVersion::Eth70,
267            EthVersion::Eth71,
268            EthVersion::Eth72,
269        ];
270
271        for version in versions {
272            let mut encoded = BytesMut::new();
273            version.encode(&mut encoded);
274
275            assert_eq!(encoded.len(), 1);
276            assert_eq!(encoded[0], version as u8);
277        }
278    }
279    #[test]
280    fn test_eth_version_rlp_decode() {
281        let test_cases = [
282            (66_u8, Ok(EthVersion::Eth66)),
283            (67_u8, Ok(EthVersion::Eth67)),
284            (68_u8, Ok(EthVersion::Eth68)),
285            (69_u8, Ok(EthVersion::Eth69)),
286            (70_u8, Ok(EthVersion::Eth70)),
287            (71_u8, Ok(EthVersion::Eth71)),
288            (72_u8, Ok(EthVersion::Eth72)),
289            (65_u8, Err(RlpError::Custom("invalid eth version"))),
290        ];
291
292        for (input, expected) in test_cases {
293            let mut encoded = BytesMut::new();
294            input.encode(&mut encoded);
295
296            let mut slice = encoded.as_ref();
297            let result = EthVersion::decode(&mut slice);
298            assert_eq!(result, expected);
299        }
300    }
301}