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,
32 Eth71 = 71,
34 Eth72 = 72,
36}
37
38impl EthVersion {
39 pub const LATEST: Self = Self::Eth69;
41
42 pub const ALL_VERSIONS: &'static [Self] = &[Self::Eth69, Self::Eth68, Self::Eth67, Self::Eth66];
44
45 pub const fn is_eth66(&self) -> bool {
47 matches!(self, Self::Eth66)
48 }
49
50 pub const fn is_eth67(&self) -> bool {
52 matches!(self, Self::Eth67)
53 }
54
55 pub const fn is_eth68(&self) -> bool {
57 matches!(self, Self::Eth68)
58 }
59
60 pub const fn has_eth68_metadata(&self) -> bool {
62 matches!(self, Self::Eth68 | Self::Eth69 | Self::Eth70 | Self::Eth71 | Self::Eth72)
63 }
64
65 pub const fn is_eth69(&self) -> bool {
67 matches!(self, Self::Eth69)
68 }
69
70 pub const fn is_eth70(&self) -> bool {
72 matches!(self, Self::Eth70)
73 }
74
75 pub const fn is_eth71(&self) -> bool {
77 matches!(self, Self::Eth71)
78 }
79
80 pub const fn is_eth72(&self) -> bool {
82 matches!(self, Self::Eth72)
83 }
84
85 pub const fn is_eth69_or_newer(&self) -> bool {
87 matches!(self, Self::Eth69 | Self::Eth70 | Self::Eth71 | Self::Eth72)
88 }
89}
90
91impl 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
102impl 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
111impl 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
138impl 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#[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 V4 = 4,
204 #[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 (*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}