1use crate::{NodeRecord, PeerId};
4use alloc::string::{String, ToString};
5use core::{
6 fmt::{self, Write},
7 net::IpAddr,
8 num::ParseIntError,
9 str::FromStr,
10};
11use serde_with::{DeserializeFromStr, SerializeDisplay};
12use url::Host;
13
14#[derive(Clone, Debug, Eq, PartialEq, Hash, SerializeDisplay, DeserializeFromStr)]
23pub struct TrustedPeer {
24 pub host: Host,
26 pub tcp_port: u16,
28 pub udp_port: u16,
30 pub id: PeerId,
32}
33
34impl TrustedPeer {
35 #[cfg(feature = "secp256k1")]
37 pub fn from_secret_key(host: Host, port: u16, sk: &secp256k1::SecretKey) -> Self {
38 let pk = secp256k1::PublicKey::from_secret_key(secp256k1::SECP256K1, sk);
39 let id = PeerId::from_slice(&pk.serialize_uncompressed()[1..]);
40 Self::new(host, port, id)
41 }
42
43 pub const fn new(host: Host, port: u16, id: PeerId) -> Self {
45 Self { host, tcp_port: port, udp_port: port, id }
46 }
47
48 #[cfg(any(test, feature = "std"))]
49 const fn to_node_record(&self, ip: IpAddr) -> NodeRecord {
50 NodeRecord { address: ip, id: self.id, tcp_port: self.tcp_port, udp_port: self.udp_port }
51 }
52
53 #[cfg(any(test, feature = "std"))]
55 fn try_node_record(&self) -> Result<NodeRecord, &str> {
56 match &self.host {
57 Host::Ipv4(ip) => Ok(self.to_node_record((*ip).into())),
58 Host::Ipv6(ip) => Ok(self.to_node_record((*ip).into())),
59 Host::Domain(domain) => Err(domain),
60 }
61 }
62
63 #[cfg(any(test, feature = "std"))]
67 pub fn resolve_blocking(&self) -> Result<NodeRecord, std::io::Error> {
68 let domain = match self.try_node_record() {
69 Ok(record) => return Ok(record),
70 Err(domain) => domain,
71 };
72 let mut ips = std::net::ToSocketAddrs::to_socket_addrs(&(domain, 0))?;
74 let ip = ips.next().ok_or_else(|| {
75 std::io::Error::new(std::io::ErrorKind::AddrNotAvailable, "No IP found")
76 })?;
77
78 Ok(self.to_node_record(ip.ip()))
79 }
80
81 #[cfg(any(test, feature = "net"))]
83 pub async fn resolve(&self) -> Result<NodeRecord, std::io::Error> {
84 let domain = match self.try_node_record() {
85 Ok(record) => return Ok(record),
86 Err(domain) => domain,
87 };
88
89 let mut ips = tokio::net::lookup_host(format!("{domain}:0")).await?;
91 let ip = ips.next().ok_or_else(|| {
92 std::io::Error::new(std::io::ErrorKind::AddrNotAvailable, "No IP found")
93 })?;
94
95 Ok(self.to_node_record(ip.ip()))
96 }
97}
98
99impl fmt::Display for TrustedPeer {
100 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
101 f.write_str("enode://")?;
102 alloy_primitives::hex::encode(self.id.as_slice()).fmt(f)?;
103 f.write_char('@')?;
104 self.host.fmt(f)?;
105 f.write_char(':')?;
106 self.tcp_port.fmt(f)?;
107 if self.tcp_port != self.udp_port {
108 f.write_str("?discport=")?;
109 self.udp_port.fmt(f)?;
110 }
111
112 Ok(())
113 }
114}
115
116#[derive(Debug, thiserror::Error)]
118pub enum NodeRecordParseError {
119 #[error("Failed to parse url: {0}")]
121 InvalidUrl(String),
122 #[error("Failed to parse id")]
124 InvalidId(String),
125 #[error("Failed to discport query: {0}")]
127 Discport(ParseIntError),
128}
129
130impl FromStr for TrustedPeer {
131 type Err = NodeRecordParseError;
132
133 fn from_str(s: &str) -> Result<Self, Self::Err> {
134 use url::Url;
135
136 let url = Url::parse(s.replace("enode://", "http://").as_str())
140 .map_err(|e| NodeRecordParseError::InvalidUrl(e.to_string()))?;
141
142 let host = url
143 .host()
144 .ok_or_else(|| NodeRecordParseError::InvalidUrl("no host specified".to_string()))?
145 .to_owned();
146
147 let port = url
148 .port()
149 .ok_or_else(|| NodeRecordParseError::InvalidUrl("no port specified".to_string()))?;
150
151 let udp_port = if let Some(discovery_port) = url
152 .query_pairs()
153 .find_map(|(maybe_disc, port)| (maybe_disc.as_ref() == "discport").then_some(port))
154 {
155 discovery_port.parse::<u16>().map_err(NodeRecordParseError::Discport)?
156 } else {
157 port
158 };
159
160 let id = url
161 .username()
162 .parse::<PeerId>()
163 .map_err(|e| NodeRecordParseError::InvalidId(e.to_string()))?;
164
165 Ok(Self { host, id, tcp_port: port, udp_port })
166 }
167}
168
169impl From<NodeRecord> for TrustedPeer {
170 fn from(record: NodeRecord) -> Self {
171 let host = match record.address {
172 IpAddr::V4(ip) => Host::Ipv4(ip),
173 IpAddr::V6(ip) => Host::Ipv6(ip),
174 };
175
176 Self { host, tcp_port: record.tcp_port, udp_port: record.udp_port, id: record.id }
177 }
178}
179
180#[cfg(test)]
181mod tests {
182 use super::*;
183 use std::net::Ipv6Addr;
184
185 #[test]
186 fn test_url_parse() {
187 let url = "enode://6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@10.3.58.6:30303?discport=30301";
188 let node: TrustedPeer = url.parse().unwrap();
189 assert_eq!(node, TrustedPeer {
190 host: Host::Ipv4([10,3,58,6].into()),
191 tcp_port: 30303,
192 udp_port: 30301,
193 id: "6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0".parse().unwrap(),
194 })
195 }
196
197 #[test]
198 fn test_node_display() {
199 let url = "enode://6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@10.3.58.6:30303";
200 let node: TrustedPeer = url.parse().unwrap();
201 assert_eq!(url, &format!("{node}"));
202 }
203
204 #[test]
205 fn test_node_display_discport() {
206 let url = "enode://6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@10.3.58.6:30303?discport=30301";
207 let node: TrustedPeer = url.parse().unwrap();
208 assert_eq!(url, &format!("{node}"));
209 }
210
211 #[test]
212 fn test_node_serialize() {
213 let cases = vec![
214 (
216 TrustedPeer {
217 host: Host::Ipv4([10, 3, 58, 6].into()),
218 tcp_port: 30303u16,
219 udp_port: 30301u16,
220 id: PeerId::from_str("6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0").unwrap(),
221 },
222 "\"enode://6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@10.3.58.6:30303?discport=30301\""
223 ),
224 (
226 TrustedPeer {
227 host: Host::Ipv6(Ipv6Addr::new(0x2001, 0xdb8, 0x3c4d, 0x15, 0x0, 0x0, 0xabcd, 0xef12)),
228 tcp_port: 52150u16,
229 udp_port: 52151u16,
230 id: PeerId::from_str("1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439").unwrap(),
231 },
232 "\"enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@[2001:db8:3c4d:15::abcd:ef12]:52150?discport=52151\""
233 ),
234 (
236 TrustedPeer {
237 host: Host::Domain("my-domain".to_string()),
238 tcp_port: 52150u16,
239 udp_port: 52151u16,
240 id: PeerId::from_str("1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439").unwrap(),
241 },
242 "\"enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@my-domain:52150?discport=52151\""
243 ),
244 ];
245
246 for (node, expected) in cases {
247 let ser = serde_json::to_string::<TrustedPeer>(&node).expect("couldn't serialize");
248 assert_eq!(ser, expected);
249 }
250 }
251
252 #[test]
253 fn test_node_deserialize() {
254 let cases = vec![
255 (
257 "\"enode://6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@10.3.58.6:30303?discport=30301\"",
258 TrustedPeer {
259 host: Host::Ipv4([10, 3, 58, 6].into()),
260 tcp_port: 30303u16,
261 udp_port: 30301u16,
262 id: PeerId::from_str("6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0").unwrap(),
263 }
264 ),
265 (
267 "\"enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@[2001:db8:3c4d:15::abcd:ef12]:52150?discport=52151\"",
268 TrustedPeer {
269 host: Host::Ipv6(Ipv6Addr::new(0x2001, 0xdb8, 0x3c4d, 0x15, 0x0, 0x0, 0xabcd, 0xef12)),
270 tcp_port: 52150u16,
271 udp_port: 52151u16,
272 id: PeerId::from_str("1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439").unwrap(),
273 }
274 ),
275 (
277 "\"enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@my-domain:52150?discport=52151\"",
278 TrustedPeer {
279 host: Host::Domain("my-domain".to_string()),
280 tcp_port: 52150u16,
281 udp_port: 52151u16,
282 id: PeerId::from_str("1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439").unwrap(),
283 }
284 ),
285 ];
286
287 for (url, expected) in cases {
288 let node: TrustedPeer = serde_json::from_str(url).expect("couldn't deserialize");
289 assert_eq!(node, expected);
290 }
291 }
292
293 #[tokio::test]
294 async fn test_resolve_dns_node_record() {
295 let tests = vec![("localhost")];
297
298 for domain in tests {
300 let rec =
302 TrustedPeer::new(url::Host::Domain(domain.to_owned()), 30300, PeerId::random());
303
304 let ensure = |rec: NodeRecord| match rec.address {
306 IpAddr::V4(addr) => {
307 assert_eq!(addr, std::net::Ipv4Addr::new(127, 0, 0, 1))
308 }
309 IpAddr::V6(addr) => {
310 assert_eq!(addr, Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1))
311 }
312 };
313 ensure(rec.resolve().await.unwrap());
314 ensure(rec.resolve_blocking().unwrap());
315 }
316 }
317}