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 is_eth69(&self) -> bool {
62 matches!(self, Self::Eth69)
63 }
64
65 pub const fn is_eth70(&self) -> bool {
67 matches!(self, Self::Eth70)
68 }
69
70 pub const fn is_eth71(&self) -> bool {
72 matches!(self, Self::Eth71)
73 }
74
75 pub const fn is_eth72(&self) -> bool {
77 matches!(self, Self::Eth72)
78 }
79
80 pub const fn is_eth69_or_newer(&self) -> bool {
82 matches!(self, Self::Eth69 | Self::Eth70 | Self::Eth71 | Self::Eth72)
83 }
84}
85
86impl Encodable for EthVersion {
88 fn encode(&self, out: &mut dyn BufMut) {
89 (*self as u8).encode(out)
90 }
91
92 fn length(&self) -> usize {
93 (*self as u8).length()
94 }
95}
96
97impl Decodable for EthVersion {
100 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
101 let version = u8::decode(buf)?;
102 Self::try_from(version).map_err(|_| RlpError::Custom("invalid eth version"))
103 }
104}
105
106impl TryFrom<&str> for EthVersion {
116 type Error = ParseVersionError;
117
118 #[inline]
119 fn try_from(s: &str) -> Result<Self, Self::Error> {
120 match s {
121 "66" => Ok(Self::Eth66),
122 "67" => Ok(Self::Eth67),
123 "68" => Ok(Self::Eth68),
124 "69" => Ok(Self::Eth69),
125 "70" => Ok(Self::Eth70),
126 "71" => Ok(Self::Eth71),
127 "72" => Ok(Self::Eth72),
128 _ => Err(ParseVersionError(s.to_string())),
129 }
130 }
131}
132
133impl TryFrom<u8> for EthVersion {
143 type Error = ParseVersionError;
144
145 #[inline]
146 fn try_from(u: u8) -> Result<Self, Self::Error> {
147 match u {
148 66 => Ok(Self::Eth66),
149 67 => Ok(Self::Eth67),
150 68 => Ok(Self::Eth68),
151 69 => Ok(Self::Eth69),
152 70 => Ok(Self::Eth70),
153 71 => Ok(Self::Eth71),
154 72 => Ok(Self::Eth72),
155 _ => Err(ParseVersionError(u.to_string())),
156 }
157 }
158}
159
160impl FromStr for EthVersion {
161 type Err = ParseVersionError;
162
163 #[inline]
164 fn from_str(s: &str) -> Result<Self, Self::Err> {
165 Self::try_from(s)
166 }
167}
168
169impl From<EthVersion> for u8 {
170 #[inline]
171 fn from(v: EthVersion) -> Self {
172 v as Self
173 }
174}
175
176impl From<EthVersion> for &'static str {
177 #[inline]
178 fn from(v: EthVersion) -> &'static str {
179 match v {
180 EthVersion::Eth66 => "66",
181 EthVersion::Eth67 => "67",
182 EthVersion::Eth68 => "68",
183 EthVersion::Eth69 => "69",
184 EthVersion::Eth70 => "70",
185 EthVersion::Eth71 => "71",
186 EthVersion::Eth72 => "72",
187 }
188 }
189}
190
191#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
193#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
194#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
195#[add_arbitrary_tests(rlp)]
196pub enum ProtocolVersion {
197 V4 = 4,
199 #[default]
201 V5 = 5,
202}
203
204impl fmt::Display for ProtocolVersion {
205 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
206 write!(f, "v{}", *self as u8)
207 }
208}
209
210impl Encodable for ProtocolVersion {
211 fn encode(&self, out: &mut dyn BufMut) {
212 (*self as u8).encode(out)
213 }
214 fn length(&self) -> usize {
215 (*self as u8).length()
217 }
218}
219
220impl Decodable for ProtocolVersion {
221 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
222 let version = u8::decode(buf)?;
223 match version {
224 4 => Ok(Self::V4),
225 5 => Ok(Self::V5),
226 _ => Err(RlpError::Custom("unknown p2p protocol version")),
227 }
228 }
229}
230
231#[cfg(test)]
232mod tests {
233 use super::EthVersion;
234 use alloy_rlp::{Decodable, Encodable, Error as RlpError};
235 use bytes::BytesMut;
236
237 #[test]
238 fn test_eth_version_try_from_str() {
239 assert_eq!(EthVersion::Eth66, EthVersion::try_from("66").unwrap());
240 assert_eq!(EthVersion::Eth67, EthVersion::try_from("67").unwrap());
241 assert_eq!(EthVersion::Eth68, EthVersion::try_from("68").unwrap());
242 assert_eq!(EthVersion::Eth69, EthVersion::try_from("69").unwrap());
243 assert_eq!(EthVersion::Eth70, EthVersion::try_from("70").unwrap());
244 assert_eq!(EthVersion::Eth71, EthVersion::try_from("71").unwrap());
245 assert_eq!(EthVersion::Eth72, EthVersion::try_from("72").unwrap());
246 }
247
248 #[test]
249 fn test_eth_version_from_str() {
250 assert_eq!(EthVersion::Eth66, "66".parse().unwrap());
251 assert_eq!(EthVersion::Eth67, "67".parse().unwrap());
252 assert_eq!(EthVersion::Eth68, "68".parse().unwrap());
253 assert_eq!(EthVersion::Eth69, "69".parse().unwrap());
254 assert_eq!(EthVersion::Eth70, "70".parse().unwrap());
255 assert_eq!(EthVersion::Eth71, "71".parse().unwrap());
256 assert_eq!(EthVersion::Eth72, "72".parse().unwrap());
257 }
258
259 #[test]
260 fn test_eth_version_rlp_encode() {
261 let versions = [
262 EthVersion::Eth66,
263 EthVersion::Eth67,
264 EthVersion::Eth68,
265 EthVersion::Eth69,
266 EthVersion::Eth70,
267 EthVersion::Eth71,
268 EthVersion::Eth72,
269 ];
270
271 for version in versions {
272 let mut encoded = BytesMut::new();
273 version.encode(&mut encoded);
274
275 assert_eq!(encoded.len(), 1);
276 assert_eq!(encoded[0], version as u8);
277 }
278 }
279 #[test]
280 fn test_eth_version_rlp_decode() {
281 let test_cases = [
282 (66_u8, Ok(EthVersion::Eth66)),
283 (67_u8, Ok(EthVersion::Eth67)),
284 (68_u8, Ok(EthVersion::Eth68)),
285 (69_u8, Ok(EthVersion::Eth69)),
286 (70_u8, Ok(EthVersion::Eth70)),
287 (71_u8, Ok(EthVersion::Eth71)),
288 (72_u8, Ok(EthVersion::Eth72)),
289 (65_u8, Err(RlpError::Custom("invalid eth version"))),
290 ];
291
292 for (input, expected) in test_cases {
293 let mut encoded = BytesMut::new();
294 input.encode(&mut encoded);
295
296 let mut slice = encoded.as_ref();
297 let result = EthVersion::decode(&mut slice);
298 assert_eq!(result, expected);
299 }
300 }
301}