reth_eth_wire/
hello.rs

1use crate::{Capability, EthVersion, ProtocolVersion};
2use alloy_rlp::{RlpDecodable, RlpEncodable};
3use reth_codecs::add_arbitrary_tests;
4use reth_network_peers::PeerId;
5use reth_primitives_traits::constants::RETH_CLIENT_VERSION;
6
7/// The default tcp port for p2p.
8///
9/// Note: this is the same as discovery port: `DEFAULT_DISCOVERY_PORT`
10pub(crate) const DEFAULT_TCP_PORT: u16 = 30303;
11
12use crate::protocol::Protocol;
13#[cfg(feature = "serde")]
14use serde::{Deserialize, Serialize};
15
16/// This is a superset of [`HelloMessage`] that provides additional protocol [Protocol] information
17/// about the number of messages used by each capability in order to do proper message ID
18/// multiplexing.
19///
20/// This type is required for the `p2p` handshake because the [`HelloMessage`] does not share the
21/// number of messages used by each capability.
22///
23/// To get the encodable [`HelloMessage`] without the additional protocol information, use the
24/// [`HelloMessageWithProtocols::message`].
25#[derive(Debug, Clone, PartialEq, Eq)]
26#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
27pub struct HelloMessageWithProtocols {
28    /// The version of the `p2p` protocol.
29    pub protocol_version: ProtocolVersion,
30    /// Specifies the client software identity, as a human-readable string (e.g.
31    /// "Ethereum(++)/1.0.0").
32    pub client_version: String,
33    /// The list of supported capabilities and their versions.
34    pub protocols: Vec<Protocol>,
35    /// The port that the client is listening on, zero indicates the client is not listening.
36    ///
37    /// By default this is `30303` which is the same as the default discovery port.
38    pub port: u16,
39    /// The secp256k1 public key corresponding to the node's private key.
40    pub id: PeerId,
41}
42
43impl HelloMessageWithProtocols {
44    /// Starts a new `HelloMessageProtocolsBuilder`
45    ///
46    /// ```
47    /// use reth_eth_wire::HelloMessageWithProtocols;
48    /// use reth_network_peers::pk2id;
49    /// use secp256k1::{SecretKey, SECP256K1};
50    /// let secret_key = SecretKey::new(&mut rand::thread_rng());
51    /// let id = pk2id(&secret_key.public_key(SECP256K1));
52    /// let status = HelloMessageWithProtocols::builder(id).build();
53    /// ```
54    pub const fn builder(id: PeerId) -> HelloMessageBuilder {
55        HelloMessageBuilder::new(id)
56    }
57
58    /// Returns the raw [`HelloMessage`] without the additional protocol information.
59    #[inline]
60    pub fn message(&self) -> HelloMessage {
61        HelloMessage {
62            protocol_version: self.protocol_version,
63            client_version: self.client_version.clone(),
64            capabilities: self.protocols.iter().map(|p| p.cap.clone()).collect(),
65            port: self.port,
66            id: self.id,
67        }
68    }
69
70    /// Converts the type into a [`HelloMessage`] without the additional protocol information.
71    pub fn into_message(self) -> HelloMessage {
72        HelloMessage {
73            protocol_version: self.protocol_version,
74            client_version: self.client_version,
75            capabilities: self.protocols.into_iter().map(|p| p.cap).collect(),
76            port: self.port,
77            id: self.id,
78        }
79    }
80
81    /// Returns true if the set of protocols contains the given protocol.
82    #[inline]
83    pub fn contains_protocol(&self, protocol: &Protocol) -> bool {
84        self.protocols.iter().any(|p| p.cap == protocol.cap)
85    }
86
87    /// Adds a new protocol to the set.
88    ///
89    /// Returns an error if the protocol already exists.
90    #[inline]
91    pub fn try_add_protocol(&mut self, protocol: Protocol) -> Result<(), Protocol> {
92        if self.contains_protocol(&protocol) {
93            Err(protocol)
94        } else {
95            self.protocols.push(protocol);
96            Ok(())
97        }
98    }
99}
100
101// TODO: determine if we should allow for the extra fields at the end like EIP-706 suggests
102/// Raw rlpx protocol message used in the `p2p` handshake, containing information about the
103/// supported RLPx protocol version and capabilities.
104///
105/// See also <https://github.com/ethereum/devp2p/blob/master/rlpx.md#hello-0x00>
106#[derive(Clone, Debug, PartialEq, Eq, RlpEncodable, RlpDecodable)]
107#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
108#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
109#[add_arbitrary_tests(rlp)]
110pub struct HelloMessage {
111    /// The version of the `p2p` protocol.
112    pub protocol_version: ProtocolVersion,
113    /// Specifies the client software identity, as a human-readable string (e.g.
114    /// "Ethereum(++)/1.0.0").
115    pub client_version: String,
116    /// The list of supported capabilities and their versions.
117    pub capabilities: Vec<Capability>,
118    /// The port that the client is listening on, zero indicates the client is not listening.
119    pub port: u16,
120    /// The secp256k1 public key corresponding to the node's private key.
121    pub id: PeerId,
122}
123
124// === impl HelloMessage ===
125
126impl HelloMessage {
127    /// Starts a new `HelloMessageBuilder`
128    ///
129    /// ```
130    /// use reth_eth_wire::HelloMessage;
131    /// use reth_network_peers::pk2id;
132    /// use secp256k1::{SecretKey, SECP256K1};
133    /// let secret_key = SecretKey::new(&mut rand::thread_rng());
134    /// let id = pk2id(&secret_key.public_key(SECP256K1));
135    /// let status = HelloMessage::builder(id).build();
136    /// ```
137    pub const fn builder(id: PeerId) -> HelloMessageBuilder {
138        HelloMessageBuilder::new(id)
139    }
140}
141
142/// Builder for [`HelloMessageWithProtocols`]
143#[derive(Debug)]
144pub struct HelloMessageBuilder {
145    /// The version of the `p2p` protocol.
146    pub protocol_version: Option<ProtocolVersion>,
147    /// Specifies the client software identity, as a human-readable string (e.g.
148    /// "Ethereum(++)/1.0.0").
149    pub client_version: Option<String>,
150    /// The list of supported protocols.
151    pub protocols: Option<Vec<Protocol>>,
152    /// The port that the client is listening on, zero indicates the client is not listening.
153    pub port: Option<u16>,
154    /// The secp256k1 public key corresponding to the node's private key.
155    pub id: PeerId,
156}
157
158// === impl HelloMessageBuilder ===
159
160impl HelloMessageBuilder {
161    /// Create a new builder to configure a [`HelloMessage`]
162    pub const fn new(id: PeerId) -> Self {
163        Self { protocol_version: None, client_version: None, protocols: None, port: None, id }
164    }
165
166    /// Sets the port the client is listening on
167    pub const fn port(mut self, port: u16) -> Self {
168        self.port = Some(port);
169        self
170    }
171
172    /// Adds a new protocol to use.
173    pub fn protocol(mut self, protocols: impl Into<Protocol>) -> Self {
174        self.protocols.get_or_insert_with(Vec::new).push(protocols.into());
175        self
176    }
177
178    /// Sets protocols to use.
179    pub fn protocols(mut self, protocols: impl IntoIterator<Item = Protocol>) -> Self {
180        self.protocols.get_or_insert_with(Vec::new).extend(protocols);
181        self
182    }
183
184    /// Sets client version.
185    pub fn client_version(mut self, client_version: impl Into<String>) -> Self {
186        self.client_version = Some(client_version.into());
187        self
188    }
189
190    /// Sets protocol version.
191    pub const fn protocol_version(mut self, protocol_version: ProtocolVersion) -> Self {
192        self.protocol_version = Some(protocol_version);
193        self
194    }
195
196    /// Consumes the type and returns the configured [`HelloMessage`]
197    ///
198    /// Unset fields will be set to their default values:
199    /// - `protocol_version`: [`ProtocolVersion::V5`]
200    /// - `client_version`: [`RETH_CLIENT_VERSION`]
201    /// - `capabilities`: All [`EthVersion`]
202    pub fn build(self) -> HelloMessageWithProtocols {
203        let Self { protocol_version, client_version, protocols, port, id } = self;
204        HelloMessageWithProtocols {
205            protocol_version: protocol_version.unwrap_or_default(),
206            client_version: client_version.unwrap_or_else(|| RETH_CLIENT_VERSION.to_string()),
207            protocols: protocols.unwrap_or_else(|| {
208                vec![EthVersion::Eth68.into(), EthVersion::Eth67.into(), EthVersion::Eth66.into()]
209            }),
210            port: port.unwrap_or(DEFAULT_TCP_PORT),
211            id,
212        }
213    }
214}
215
216#[cfg(test)]
217mod tests {
218    use crate::{p2pstream::P2PMessage, Capability, EthVersion, HelloMessage, ProtocolVersion};
219    use alloy_rlp::{Decodable, Encodable, EMPTY_STRING_CODE};
220    use reth_network_peers::pk2id;
221    use secp256k1::{SecretKey, SECP256K1};
222
223    #[test]
224    fn test_hello_encoding_round_trip() {
225        let secret_key = SecretKey::new(&mut rand::thread_rng());
226        let id = pk2id(&secret_key.public_key(SECP256K1));
227        let hello = P2PMessage::Hello(HelloMessage {
228            protocol_version: ProtocolVersion::V5,
229            client_version: "reth/0.1.0".to_string(),
230            capabilities: vec![Capability::new_static("eth", EthVersion::Eth67 as usize)],
231            port: 30303,
232            id,
233        });
234
235        let mut hello_encoded = Vec::new();
236        hello.encode(&mut hello_encoded);
237
238        let hello_decoded = P2PMessage::decode(&mut &hello_encoded[..]).unwrap();
239
240        assert_eq!(hello, hello_decoded);
241    }
242
243    #[test]
244    fn hello_encoding_length() {
245        let secret_key = SecretKey::new(&mut rand::thread_rng());
246        let id = pk2id(&secret_key.public_key(SECP256K1));
247        let hello = P2PMessage::Hello(HelloMessage {
248            protocol_version: ProtocolVersion::V5,
249            client_version: "reth/0.1.0".to_string(),
250            capabilities: vec![Capability::new_static("eth", EthVersion::Eth67 as usize)],
251            port: 30303,
252            id,
253        });
254
255        let mut hello_encoded = Vec::new();
256        hello.encode(&mut hello_encoded);
257
258        assert_eq!(hello_encoded.len(), hello.length());
259    }
260
261    #[test]
262    fn hello_message_id_prefix() {
263        // ensure that the hello message id is prefixed
264        let secret_key = SecretKey::new(&mut rand::thread_rng());
265        let id = pk2id(&secret_key.public_key(SECP256K1));
266        let hello = P2PMessage::Hello(HelloMessage {
267            protocol_version: ProtocolVersion::V5,
268            client_version: "reth/0.1.0".to_string(),
269            capabilities: vec![Capability::new_static("eth", EthVersion::Eth67 as usize)],
270            port: 30303,
271            id,
272        });
273
274        let mut hello_encoded = Vec::new();
275        hello.encode(&mut hello_encoded);
276
277        // zero is encoded as 0x80, the empty string code in RLP
278        assert_eq!(hello_encoded[0], EMPTY_STRING_CODE);
279    }
280}