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