reth_eth_wire_types/
version.rs

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