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}
31
32impl EthVersion {
33 pub const LATEST: Self = Self::Eth68;
35
36 pub const fn total_messages(&self) -> u8 {
38 match self {
39 Self::Eth66 => 15,
40 Self::Eth67 | Self::Eth68 => {
41 13
43 }
44 Self::Eth69 => 11,
46 }
47 }
48
49 pub const fn is_eth66(&self) -> bool {
51 matches!(self, Self::Eth66)
52 }
53
54 pub const fn is_eth67(&self) -> bool {
56 matches!(self, Self::Eth67)
57 }
58
59 pub const fn is_eth68(&self) -> bool {
61 matches!(self, Self::Eth68)
62 }
63
64 pub const fn is_eth69(&self) -> bool {
66 matches!(self, Self::Eth69)
67 }
68}
69
70impl Encodable for EthVersion {
72 fn encode(&self, out: &mut dyn BufMut) {
73 (*self as u8).encode(out)
74 }
75
76 fn length(&self) -> usize {
77 (*self as u8).length()
78 }
79}
80
81impl Decodable for EthVersion {
84 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
85 let version = u8::decode(buf)?;
86 Self::try_from(version).map_err(|_| RlpError::Custom("invalid eth version"))
87 }
88}
89
90impl TryFrom<&str> for EthVersion {
100 type Error = ParseVersionError;
101
102 #[inline]
103 fn try_from(s: &str) -> Result<Self, Self::Error> {
104 match s {
105 "66" => Ok(Self::Eth66),
106 "67" => Ok(Self::Eth67),
107 "68" => Ok(Self::Eth68),
108 "69" => Ok(Self::Eth69),
109 _ => Err(ParseVersionError(s.to_string())),
110 }
111 }
112}
113
114impl TryFrom<u8> for EthVersion {
124 type Error = ParseVersionError;
125
126 #[inline]
127 fn try_from(u: u8) -> Result<Self, Self::Error> {
128 match u {
129 66 => Ok(Self::Eth66),
130 67 => Ok(Self::Eth67),
131 68 => Ok(Self::Eth68),
132 69 => Ok(Self::Eth69),
133 _ => Err(ParseVersionError(u.to_string())),
134 }
135 }
136}
137
138impl FromStr for EthVersion {
139 type Err = ParseVersionError;
140
141 #[inline]
142 fn from_str(s: &str) -> Result<Self, Self::Err> {
143 Self::try_from(s)
144 }
145}
146
147impl From<EthVersion> for u8 {
148 #[inline]
149 fn from(v: EthVersion) -> Self {
150 v as Self
151 }
152}
153
154impl From<EthVersion> for &'static str {
155 #[inline]
156 fn from(v: EthVersion) -> &'static str {
157 match v {
158 EthVersion::Eth66 => "66",
159 EthVersion::Eth67 => "67",
160 EthVersion::Eth68 => "68",
161 EthVersion::Eth69 => "69",
162 }
163 }
164}
165
166#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
168#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
169#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
170#[add_arbitrary_tests(rlp)]
171pub enum ProtocolVersion {
172 V4 = 4,
174 #[default]
176 V5 = 5,
177}
178
179impl fmt::Display for ProtocolVersion {
180 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
181 write!(f, "v{}", *self as u8)
182 }
183}
184
185impl Encodable for ProtocolVersion {
186 fn encode(&self, out: &mut dyn BufMut) {
187 (*self as u8).encode(out)
188 }
189 fn length(&self) -> usize {
190 (*self as u8).length()
192 }
193}
194
195impl Decodable for ProtocolVersion {
196 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
197 let version = u8::decode(buf)?;
198 match version {
199 4 => Ok(Self::V4),
200 5 => Ok(Self::V5),
201 _ => Err(RlpError::Custom("unknown p2p protocol version")),
202 }
203 }
204}
205
206#[cfg(test)]
207mod tests {
208 use super::{EthVersion, ParseVersionError};
209 use alloy_rlp::{Decodable, Encodable, Error as RlpError};
210 use bytes::BytesMut;
211
212 #[test]
213 fn test_eth_version_try_from_str() {
214 assert_eq!(EthVersion::Eth66, EthVersion::try_from("66").unwrap());
215 assert_eq!(EthVersion::Eth67, EthVersion::try_from("67").unwrap());
216 assert_eq!(EthVersion::Eth68, EthVersion::try_from("68").unwrap());
217 assert_eq!(EthVersion::Eth69, EthVersion::try_from("69").unwrap());
218 assert_eq!(Err(ParseVersionError("70".to_string())), EthVersion::try_from("70"));
219 }
220
221 #[test]
222 fn test_eth_version_from_str() {
223 assert_eq!(EthVersion::Eth66, "66".parse().unwrap());
224 assert_eq!(EthVersion::Eth67, "67".parse().unwrap());
225 assert_eq!(EthVersion::Eth68, "68".parse().unwrap());
226 assert_eq!(EthVersion::Eth69, "69".parse().unwrap());
227 assert_eq!(Err(ParseVersionError("70".to_string())), "70".parse::<EthVersion>());
228 }
229
230 #[test]
231 fn test_eth_version_rlp_encode() {
232 let versions = [EthVersion::Eth66, EthVersion::Eth67, EthVersion::Eth68, EthVersion::Eth69];
233
234 for version in versions {
235 let mut encoded = BytesMut::new();
236 version.encode(&mut encoded);
237
238 assert_eq!(encoded.len(), 1);
239 assert_eq!(encoded[0], version as u8);
240 }
241 }
242 #[test]
243 fn test_eth_version_rlp_decode() {
244 let test_cases = [
245 (66_u8, Ok(EthVersion::Eth66)),
246 (67_u8, Ok(EthVersion::Eth67)),
247 (68_u8, Ok(EthVersion::Eth68)),
248 (69_u8, Ok(EthVersion::Eth69)),
249 (70_u8, Err(RlpError::Custom("invalid eth version"))),
250 (65_u8, Err(RlpError::Custom("invalid eth version"))),
251 ];
252
253 for (input, expected) in test_cases {
254 let mut encoded = BytesMut::new();
255 input.encode(&mut encoded);
256
257 let mut slice = encoded.as_ref();
258 let result = EthVersion::decode(&mut slice);
259 assert_eq!(result, expected);
260 }
261 }
262
263 #[test]
264 fn test_eth_version_total_messages() {
265 assert_eq!(EthVersion::Eth66.total_messages(), 15);
266 assert_eq!(EthVersion::Eth67.total_messages(), 13);
267 assert_eq!(EthVersion::Eth68.total_messages(), 13);
268 assert_eq!(EthVersion::Eth69.total_messages(), 11);
269 }
270}