1use 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#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
13#[error("Unknown eth protocol version: {0}")]
14pub struct ParseVersionError(String);
15
16#[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 Eth66 = 66,
24 Eth67 = 67,
26 Eth68 = 68,
28 Eth69 = 69,
30 Eth70 = 70,
35 Eth71 = 71,
40 Eth72 = 72,
46}
47
48impl EthVersion {
49 pub const LATEST: Self = Self::Eth71;
51
52 pub const ALL_VERSIONS: &'static [Self] =
54 &[Self::Eth71, Self::Eth70, Self::Eth69, Self::Eth68, Self::Eth67, Self::Eth66];
55
56 pub const fn is_eth66(&self) -> bool {
58 matches!(self, Self::Eth66)
59 }
60
61 pub const fn is_eth67(&self) -> bool {
63 matches!(self, Self::Eth67)
64 }
65
66 pub const fn is_eth68(&self) -> bool {
68 matches!(self, Self::Eth68)
69 }
70
71 pub const fn has_eth68_metadata(&self) -> bool {
73 matches!(self, Self::Eth68 | Self::Eth69 | Self::Eth70 | Self::Eth71 | Self::Eth72)
74 }
75
76 pub const fn is_eth69(&self) -> bool {
78 matches!(self, Self::Eth69)
79 }
80
81 pub const fn is_eth70(&self) -> bool {
83 matches!(self, Self::Eth70)
84 }
85
86 pub const fn is_eth71(&self) -> bool {
88 matches!(self, Self::Eth71)
89 }
90
91 pub const fn is_eth72(&self) -> bool {
93 matches!(self, Self::Eth72)
94 }
95
96 pub const fn is_eth69_or_newer(&self) -> bool {
98 matches!(self, Self::Eth69 | Self::Eth70 | Self::Eth71 | Self::Eth72)
99 }
100}
101
102impl 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
113impl 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
122impl 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
149impl 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#[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 V4 = 4,
215 #[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 (*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}