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