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