Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(discv5): open dns for discv5 #7328

Merged
merged 18 commits into from
Mar 27, 2024
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

49 changes: 32 additions & 17 deletions crates/net/dns/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256",
issue_tracker_base_url = "https://github.com/paradigmxyz/reth/issues/"
)]
#![cfg_attr(not(test), warn(unused_crate_dependencies))]
#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]

pub use crate::resolver::{DnsResolver, MapResolver, Resolver};
Expand All @@ -22,12 +21,11 @@ use crate::{
pub use config::DnsDiscoveryConfig;
use enr::Enr;
use error::ParseDnsEntryError;
use reth_primitives::{ForkId, NodeRecord, PeerId};
use reth_primitives::{get_fork_id, ForkId, NodeRecord, NodeRecordParseError};
use schnellru::{ByLength, LruMap};
use secp256k1::SecretKey;
use std::{
collections::{hash_map::Entry, HashMap, HashSet, VecDeque},
net::IpAddr,
pin::Pin,
sync::Arc,
task::{ready, Context, Poll},
Expand Down Expand Up @@ -241,7 +239,7 @@ impl<R: Resolver> DnsDiscoveryService<R> {
}

fn on_resolved_enr(&mut self, enr: Enr<SecretKey>) {
if let Some(record) = convert_enr_node_record(&enr) {
if let Some(record) = convert_enr_node_record(enr.clone()) {
self.notify(record);
}
self.queued_events.push_back(DnsDiscoveryEvent::Enr(enr))
Expand Down Expand Up @@ -372,6 +370,8 @@ pub struct DnsNodeRecordUpdate {
pub node_record: NodeRecord,
/// The forkid of the node, if present in the ENR
pub fork_id: Option<ForkId>,
/// Original [`Enr`].
pub enr: Enr<SecretKey>,
}

/// Commands sent from [DnsDiscoveryHandle] to [DnsDiscoveryService]
Expand All @@ -389,21 +389,36 @@ pub enum DnsDiscoveryEvent {
}

/// Converts an [Enr] into a [NodeRecord]
fn convert_enr_node_record(enr: &Enr<SecretKey>) -> Option<DnsNodeRecordUpdate> {
use alloy_rlp::Decodable;

let node_record = NodeRecord {
address: enr.ip4().map(IpAddr::from).or_else(|| enr.ip6().map(IpAddr::from))?,
tcp_port: enr.tcp4().or_else(|| enr.tcp6())?,
udp_port: enr.udp4().or_else(|| enr.udp6())?,
id: PeerId::from_slice(&enr.public_key().serialize_uncompressed()[1..]),
}
.into_ipv4_mapped();
fn convert_enr_node_record(enr: Enr<SecretKey>) -> Option<DnsNodeRecordUpdate> {
let node_record = match NodeRecord::try_from(&enr) {
Ok(node_record) => node_record,
Err(err) => {
trace!(target: "disc::dns",
%err,
"can't convert enr to dns update"
);

return None
}
};

let fork_id = match get_fork_id(&enr) {
Ok(fork_id) => Some(fork_id),
Err(err) => {
if matches!(err, NodeRecordParseError::EthForkIdMissing) {
trace!(target: "disc::dns",
%err,
"can't convert enr to dns update"
);

return None
}

let mut maybe_fork_id = enr.get(b"eth")?;
let fork_id = ForkId::decode(&mut maybe_fork_id).ok();
None // enr fork could be badly configured in node record, peer could still be useful
}
};

Some(DnsNodeRecordUpdate { node_record, fork_id })
Some(DnsNodeRecordUpdate { node_record, fork_id, enr })
}

#[cfg(test)]
Expand Down
5 changes: 3 additions & 2 deletions crates/primitives/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,9 @@ pub use header::{Header, HeaderValidationError, HeadersDirection, SealedHeader};
pub use integer_list::IntegerList;
pub use log::{logs_bloom, Log};
pub use net::{
goerli_nodes, holesky_nodes, mainnet_nodes, parse_nodes, sepolia_nodes, NodeRecord,
GOERLI_BOOTNODES, HOLESKY_BOOTNODES, MAINNET_BOOTNODES, SEPOLIA_BOOTNODES,
get_fork_id, goerli_nodes, holesky_nodes, mainnet_nodes, parse_nodes, pk_to_id, sepolia_nodes,
NodeRecord, NodeRecordParseError, GOERLI_BOOTNODES, HOLESKY_BOOTNODES, MAINNET_BOOTNODES,
SEPOLIA_BOOTNODES,
};
pub use peer::{AnyNode, PeerId, WithPeerId};
pub use prune::{
Expand Down
60 changes: 58 additions & 2 deletions crates/primitives/src/net.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
pub use reth_rpc_types::NodeRecord;
use alloy_rlp::Decodable;
pub use reth_rpc_types::{pk_to_id, NodeRecord, NodeRecordParseError};

use enr::Enr;
emhane marked this conversation as resolved.
Show resolved Hide resolved

use crate::ForkId;
emhane marked this conversation as resolved.
Show resolved Hide resolved

// <https://github.com/ledgerwatch/erigon/blob/610e648dc43ec8cd6563313e28f06f534a9091b3/params/bootnodes.go>

Expand Down Expand Up @@ -66,17 +71,35 @@ pub fn parse_nodes(nodes: impl IntoIterator<Item = impl AsRef<str>>) -> Vec<Node
nodes.into_iter().map(|s| s.as_ref().parse().unwrap()).collect()
}

/// Tries to read the [`ForkId`] from given [`Enr`].
pub fn get_fork_id(enr: &Enr<secp256k1::SecretKey>) -> Result<ForkId, NodeRecordParseError> {
let Some(mut maybe_fork_id) = enr.get(b"eth") else {
return Err(NodeRecordParseError::EthForkIdMissing)
};

let Ok(fork_id) = ForkId::decode(&mut maybe_fork_id) else {
return Err(NodeRecordParseError::ForkIdDecodeError(maybe_fork_id.to_vec()))
};

Ok(fork_id)
}

#[cfg(test)]
mod tests {
use std::{
net::{IpAddr, Ipv4Addr},
str::FromStr,
};

use crate::MAINNET;

use super::*;
use alloy_rlp::Decodable;
use alloy_rlp::{Decodable, Encodable};
use enr::Enr;
use rand::{thread_rng, Rng, RngCore};
use reth_ethereum_forks::Hardfork;
use reth_rpc_types::PeerId;
use secp256k1::SecretKey;

#[test]
fn test_mapped_ipv6() {
Expand Down Expand Up @@ -197,4 +220,37 @@ mod tests {
id: PeerId::from_str("6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0").unwrap(),
})
}

#[test]
fn conversion_to_node_record_from_enr() {
const IP: &str = "::";
const TCP_PORT: u16 = 30303;
const UDP_PORT: u16 = 9000;

let mut rng = thread_rng();
let key = SecretKey::new(&mut rng);

let mut buf = Vec::new();
let fork_id = MAINNET.hardfork_fork_id(Hardfork::Frontier);
fork_id.unwrap().encode(&mut buf);

let enr = Enr::builder()
.ip6(IP.parse().unwrap())
.udp6(UDP_PORT)
.tcp6(TCP_PORT)
.build(&key)
.unwrap();

let node_record = NodeRecord::try_from(&enr).unwrap();

assert_eq!(
NodeRecord {
address: IP.parse().unwrap(),
tcp_port: TCP_PORT,
udp_port: UDP_PORT,
id: pk_to_id(&enr.public_key())
},
node_record
)
}
}
1 change: 1 addition & 0 deletions crates/rpc/rpc-types/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ alloy-rpc-engine-types = { workspace = true, features = ["jsonrpsee-types"] }
ethereum_ssz_derive = { version = "0.5", optional = true }
ethereum_ssz = { version = "0.5", optional = true }
alloy-genesis.workspace = true
enr = { workspace = true, features = ["serde", "rust-secp256k1"] }

# misc
thiserror.workspace = true
Expand Down
40 changes: 37 additions & 3 deletions crates/rpc/rpc-types/src/net.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::PeerId;
use crate::{pk_to_id, PeerId};
use alloy_rlp::{RlpDecodable, RlpEncodable};
use enr::Enr;
use secp256k1::{SecretKey, SECP256K1};
use serde_with::{DeserializeFromStr, SerializeDisplay};
use std::{
Expand All @@ -9,6 +10,7 @@ use std::{
num::ParseIntError,
str::FromStr,
};
use thiserror::Error;
use url::{Host, Url};

/// Represents a ENR in discovery.
Expand Down Expand Up @@ -114,8 +116,8 @@ impl fmt::Display for NodeRecord {
}
}

/// Possible error types when parsing a `NodeRecord`
#[derive(Debug, thiserror::Error)]
/// Possible error types when parsing a [`NodeRecord`]
#[derive(Debug, Error)]
pub enum NodeRecordParseError {
/// Invalid url
#[error("Failed to parse url: {0}")]
Expand All @@ -126,6 +128,15 @@ pub enum NodeRecordParseError {
/// Invalid discport
#[error("Failed to discport query: {0}")]
Discport(ParseIntError),
/// Conversion from type [`Enr<SecretKey>`] failed.
#[error("failed to convert enr into node record")]
ConversionFromEnrFailed,
/// Missing key used to identify an execution layer enr on Ethereum network.
emhane marked this conversation as resolved.
Show resolved Hide resolved
#[error("fork id missing on enr, 'eth' key missing")]
EthForkIdMissing,
/// Failed to decode fork ID rlp value.
#[error("failed to decode fork id, 'eth': {0:?}")]
ForkIdDecodeError(Vec<u8>),
}
emhane marked this conversation as resolved.
Show resolved Hide resolved

impl FromStr for NodeRecord {
Expand Down Expand Up @@ -165,6 +176,29 @@ impl FromStr for NodeRecord {
}
}

impl TryFrom<&Enr<SecretKey>> for NodeRecord {
type Error = NodeRecordParseError;

fn try_from(enr: &Enr<SecretKey>) -> Result<Self, Self::Error> {
let Some(address) = enr.ip4().map(IpAddr::from).or_else(|| enr.ip6().map(IpAddr::from))
else {
return Err(NodeRecordParseError::ConversionFromEnrFailed)
};

let Some(tcp_port) = enr.tcp4().or_else(|| enr.tcp6()) else {
return Err(NodeRecordParseError::ConversionFromEnrFailed)
};

let Some(udp_port) = enr.udp4().or_else(|| enr.udp6()) else {
return Err(NodeRecordParseError::ConversionFromEnrFailed)
};

let id = pk_to_id(&enr.public_key());

Ok(NodeRecord { address, tcp_port, udp_port, id }.into_ipv4_mapped())
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
5 changes: 5 additions & 0 deletions crates/rpc/rpc-types/src/peer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,8 @@ use alloy_primitives::B512;

/// Alias for a peer identifier
pub type PeerId = B512;

/// Converts a [`secp256k1::PublicKey`] to a [`PeerId`].
pub fn pk_to_id(pk: &secp256k1::PublicKey) -> PeerId {
PeerId::from_slice(&pk.serialize_uncompressed()[1..])
}
Loading