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