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