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