chore(discv5): pub methods (#8057)

This commit is contained in:
Emilia Hane
2024-05-02 22:34:13 +02:00
committed by GitHub
parent 2ac2433a96
commit a590ed7ce5
3 changed files with 182 additions and 185 deletions

View File

@ -143,7 +143,7 @@ impl ConfigBuilder {
}
/// Sets the tcp port to advertise in the local [`Enr`](discv5::enr::Enr).
fn tcp_port(mut self, port: u16) -> Self {
pub fn tcp_port(mut self, port: u16) -> Self {
self.tcp_port = port;
self
}

View File

@ -35,14 +35,12 @@ impl MustIncludeKey {
/// Returns [`FilterOutcome::Ok`] if [`Enr`](discv5::Enr) contains the configured kv-pair key.
pub fn filter(&self, enr: &discv5::Enr) -> FilterOutcome {
if enr.get_raw_rlp(self.key).is_none() {
return FilterOutcome::Ignore { reason: self.ignore_reason() }
return FilterOutcome::Ignore {
reason: format!("{} fork required", String::from_utf8_lossy(self.key)),
}
}
FilterOutcome::Ok
}
fn ignore_reason(&self) -> String {
format!("{} fork required", String::from_utf8_lossy(self.key))
}
}
/// Filter requiring that peers not advertise kv-pairs using certain keys, e.g. b"eth2".
@ -69,20 +67,18 @@ impl MustNotIncludeKeys {
pub fn filter(&self, enr: &discv5::Enr) -> FilterOutcome {
for key in self.keys.iter() {
if matches!(key.filter(enr), FilterOutcome::Ok) {
return FilterOutcome::Ignore { reason: self.ignore_reason() }
return FilterOutcome::Ignore {
reason: format!(
"{} forks not allowed",
self.keys.iter().map(|key| String::from_utf8_lossy(key.key)).format(",")
),
}
}
}
FilterOutcome::Ok
}
fn ignore_reason(&self) -> String {
format!(
"{} forks not allowed",
self.keys.iter().map(|key| String::from_utf8_lossy(key.key)).format(",")
)
}
/// Adds a key that must not be present for any kv-pair in a node record.
pub fn add_disallowed_keys(&mut self, keys: &[&'static [u8]]) {
for key in keys {

View File

@ -161,7 +161,7 @@ impl Discv5 {
//
// 1. make local enr from listen config
//
let (enr, bc_enr, fork_key, ip_mode) = Self::build_local_enr(sk, &discv5_config);
let (enr, bc_enr, fork_key, ip_mode) = build_local_enr(sk, &discv5_config);
trace!(target: "net::discv5",
?enr,
@ -197,14 +197,14 @@ impl Discv5 {
//
// 3. add boot nodes
//
Self::bootstrap(bootstrap_nodes, &discv5).await?;
bootstrap(bootstrap_nodes, &discv5).await?;
let metrics = Discv5Metrics::default();
//
// 4. start bg kbuckets maintenance
//
Self::spawn_populate_kbuckets_bg(
spawn_populate_kbuckets_bg(
lookup_interval,
bootstrap_lookup_interval,
bootstrap_lookup_countdown,
@ -219,7 +219,177 @@ impl Discv5 {
))
}
fn build_local_enr(
/// Process an event from the underlying [`discv5::Discv5`] node.
pub fn on_discv5_update(&mut self, update: discv5::Event) -> Option<DiscoveredPeer> {
match update {
discv5::Event::SocketUpdated(_) | discv5::Event::TalkRequest(_) |
// `Discovered` not unique discovered peers
discv5::Event::Discovered(_) => None,
discv5::Event::NodeInserted { replaced: _, .. } => {
// node has been inserted into kbuckets
// `replaced` partly covers `reth_discv4::DiscoveryUpdate::Removed(_)`
self.metrics.discovered_peers.increment_kbucket_insertions(1);
None
}
discv5::Event::SessionEstablished(enr, remote_socket) => {
// covers `reth_discv4::DiscoveryUpdate` equivalents `DiscoveryUpdate::Added(_)`
// and `DiscoveryUpdate::DiscoveredAtCapacity(_)
// peer has been discovered as part of query, or, by incoming session (peer has
// discovered us)
self.metrics.discovered_peers_advertised_networks.increment_once_by_network_type(&enr);
self.metrics.discovered_peers.increment_established_sessions_raw(1);
self.on_discovered_peer(&enr, remote_socket)
}
_ => None,
}
}
/// Processes a discovered peer. Returns `true` if peer is added to
pub fn on_discovered_peer(
&mut self,
enr: &discv5::Enr,
socket: SocketAddr,
) -> Option<DiscoveredPeer> {
let node_record = match self.try_into_reachable(enr, socket) {
Ok(enr_bc) => enr_bc,
Err(err) => {
trace!(target: "net::discovery::discv5",
%err,
?enr,
"discovered peer is unreachable"
);
self.metrics.discovered_peers.increment_established_sessions_unreachable_enr(1);
return None
}
};
if let FilterOutcome::Ignore { reason } = self.filter_discovered_peer(enr) {
trace!(target: "net::discovery::discv5",
?enr,
reason,
"filtered out discovered peer"
);
self.metrics.discovered_peers.increment_established_sessions_filtered(1);
return None
}
// todo: extend for all network stacks in reth-network rlpx logic
let fork_id = (self.fork_key == Some(NetworkStackId::ETH))
.then(|| self.get_fork_id(enr).ok())
.flatten();
trace!(target: "net::discovery::discv5",
?fork_id,
?enr,
"discovered peer"
);
Some(DiscoveredPeer { node_record, fork_id })
}
/// Tries to convert an [`Enr`](discv5::Enr) into the backwards compatible type [`NodeRecord`],
/// w.r.t. local [`IpMode`]. Tries the socket from which the ENR was sent, if socket is missing
/// from ENR.
///
/// Note: [`discv5::Discv5`] won't initiate a session with any peer with a malformed node
/// record, that advertises a reserved IP address on a WAN network.
pub fn try_into_reachable(
&self,
enr: &discv5::Enr,
socket: SocketAddr,
) -> Result<NodeRecord, Error> {
let id = enr_to_discv4_id(enr).ok_or(Error::IncompatibleKeyType)?;
let udp_socket = self.ip_mode().get_contactable_addr(enr).unwrap_or(socket);
// since we, on bootstrap, set tcp4 in local ENR for `IpMode::Dual`, we prefer tcp4 here
// too
let Some(tcp_port) = (match self.ip_mode() {
IpMode::Ip4 | IpMode::DualStack => enr.tcp4(),
IpMode::Ip6 => enr.tcp6(),
}) else {
return Err(Error::IpVersionMismatchRlpx(self.ip_mode()))
};
Ok(NodeRecord { address: udp_socket.ip(), tcp_port, udp_port: udp_socket.port(), id })
}
/// Applies filtering rules on an ENR. Returns [`Ok`](FilterOutcome::Ok) if peer should be
/// passed up to app, and [`Ignore`](FilterOutcome::Ignore) if peer should instead be dropped.
pub fn filter_discovered_peer(&self, enr: &discv5::Enr) -> FilterOutcome {
self.discovered_peer_filter.filter(enr)
}
/// Returns the [`ForkId`] of the given [`Enr`](discv5::Enr) w.r.t. the local node's network
/// stack, if field is set.
pub fn get_fork_id<K: discv5::enr::EnrKey>(
&self,
enr: &discv5::enr::Enr<K>,
) -> Result<ForkId, Error> {
let Some(key) = self.fork_key else { return Err(Error::NetworkStackIdNotConfigured) };
let fork_id = enr
.get_decodable::<EnrForkIdEntry>(key)
.ok_or(Error::ForkMissing(key))?
.map(Into::into)?;
Ok(fork_id)
}
////////////////////////////////////////////////////////////////////////////////////////////////
// Interface with sigp/discv5
////////////////////////////////////////////////////////////////////////////////////////////////
/// Exposes API of [`discv5::Discv5`].
pub fn with_discv5<F, R>(&self, f: F) -> R
where
F: FnOnce(&discv5::Discv5) -> R,
{
f(&self.discv5)
}
////////////////////////////////////////////////////////////////////////////////////////////////
// Complementary
////////////////////////////////////////////////////////////////////////////////////////////////
/// Returns the [`IpMode`] of the local node.
pub fn ip_mode(&self) -> IpMode {
self.ip_mode
}
/// Returns the key to use to identify the [`ForkId`] kv-pair on the [`Enr`](discv5::Enr).
pub fn fork_key(&self) -> Option<&[u8]> {
self.fork_key
}
}
impl fmt::Debug for Discv5 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
"{ .. }".fmt(f)
}
}
/// Result of successfully processing a peer discovered by [`discv5::Discv5`].
#[derive(Debug)]
pub struct DiscoveredPeer {
/// A discovery v4 backwards compatible ENR.
pub node_record: NodeRecord,
/// [`ForkId`] extracted from ENR w.r.t. configured
pub fork_id: Option<ForkId>,
}
/// Builds the local ENR with the supplied key.
pub fn build_local_enr(
sk: &SecretKey,
config: &Config,
) -> (Enr<SecretKey>, NodeRecord, Option<&'static [u8]>, IpMode) {
@ -284,7 +454,7 @@ impl Discv5 {
}
/// Bootstraps underlying [`discv5::Discv5`] node with configured peers.
async fn bootstrap(
pub async fn bootstrap(
bootstrap_nodes: HashSet<BootNode>,
discv5: &Arc<discv5::Discv5>,
) -> Result<(), Error> {
@ -321,7 +491,7 @@ impl Discv5 {
}
/// Backgrounds regular look up queries, in order to keep kbuckets populated.
fn spawn_populate_kbuckets_bg(
pub fn spawn_populate_kbuckets_bg(
lookup_interval: u64,
bootstrap_lookup_interval: u64,
bootstrap_lookup_countdown: u64,
@ -382,175 +552,6 @@ impl Discv5 {
});
}
/// Process an event from the underlying [`discv5::Discv5`] node.
pub fn on_discv5_update(&mut self, update: discv5::Event) -> Option<DiscoveredPeer> {
match update {
discv5::Event::SocketUpdated(_) | discv5::Event::TalkRequest(_) |
// `Discovered` not unique discovered peers
discv5::Event::Discovered(_) => None,
discv5::Event::NodeInserted { replaced: _, .. } => {
// node has been inserted into kbuckets
// `replaced` partly covers `reth_discv4::DiscoveryUpdate::Removed(_)`
self.metrics.discovered_peers.increment_kbucket_insertions(1);
None
}
discv5::Event::SessionEstablished(enr, remote_socket) => {
// covers `reth_discv4::DiscoveryUpdate` equivalents `DiscoveryUpdate::Added(_)`
// and `DiscoveryUpdate::DiscoveredAtCapacity(_)
// peer has been discovered as part of query, or, by incoming session (peer has
// discovered us)
self.metrics.discovered_peers_advertised_networks.increment_once_by_network_type(&enr);
self.metrics.discovered_peers.increment_established_sessions_raw(1);
self.on_discovered_peer(&enr, remote_socket)
}
_ => None,
}
}
/// Processes a discovered peer. Returns `true` if peer is added to
fn on_discovered_peer(
&mut self,
enr: &discv5::Enr,
socket: SocketAddr,
) -> Option<DiscoveredPeer> {
let node_record = match self.try_into_reachable(enr, socket) {
Ok(enr_bc) => enr_bc,
Err(err) => {
trace!(target: "net::discovery::discv5",
%err,
?enr,
"discovered peer is unreachable"
);
self.metrics.discovered_peers.increment_established_sessions_unreachable_enr(1);
return None
}
};
if let FilterOutcome::Ignore { reason } = self.filter_discovered_peer(enr) {
trace!(target: "net::discovery::discv5",
?enr,
reason,
"filtered out discovered peer"
);
self.metrics.discovered_peers.increment_established_sessions_filtered(1);
return None
}
// todo: extend for all network stacks in reth-network rlpx logic
let fork_id = (self.fork_key == Some(NetworkStackId::ETH))
.then(|| self.get_fork_id(enr).ok())
.flatten();
trace!(target: "net::discovery::discv5",
?fork_id,
?enr,
"discovered peer"
);
Some(DiscoveredPeer { node_record, fork_id })
}
/// Tries to convert an [`Enr`](discv5::Enr) into the backwards compatible type [`NodeRecord`],
/// w.r.t. local [`IpMode`]. Tries the socket from which the ENR was sent, if socket is missing
/// from ENR.
///
/// Note: [`discv5::Discv5`] won't initiate a session with any peer with a malformed node
/// record, that advertises a reserved IP address on a WAN network.
fn try_into_reachable(
&self,
enr: &discv5::Enr,
socket: SocketAddr,
) -> Result<NodeRecord, Error> {
let id = enr_to_discv4_id(enr).ok_or(Error::IncompatibleKeyType)?;
let udp_socket = self.ip_mode().get_contactable_addr(enr).unwrap_or(socket);
// since we, on bootstrap, set tcp4 in local ENR for `IpMode::Dual`, we prefer tcp4 here
// too
let Some(tcp_port) = (match self.ip_mode() {
IpMode::Ip4 | IpMode::DualStack => enr.tcp4(),
IpMode::Ip6 => enr.tcp6(),
}) else {
return Err(Error::IpVersionMismatchRlpx(self.ip_mode()))
};
Ok(NodeRecord { address: udp_socket.ip(), tcp_port, udp_port: udp_socket.port(), id })
}
/// Applies filtering rules on an ENR. Returns [`Ok`](FilterOutcome::Ok) if peer should be
/// passed up to app, and [`Ignore`](FilterOutcome::Ignore) if peer should instead be dropped.
fn filter_discovered_peer(&self, enr: &discv5::Enr) -> FilterOutcome {
self.discovered_peer_filter.filter(enr)
}
/// Returns the [`ForkId`] of the given [`Enr`](discv5::Enr) w.r.t. the local node's network
/// stack, if field is set.
fn get_fork_id<K: discv5::enr::EnrKey>(
&self,
enr: &discv5::enr::Enr<K>,
) -> Result<ForkId, Error> {
let Some(key) = self.fork_key else { return Err(Error::NetworkStackIdNotConfigured) };
let fork_id = enr
.get_decodable::<EnrForkIdEntry>(key)
.ok_or(Error::ForkMissing(key))?
.map(Into::into)?;
Ok(fork_id)
}
////////////////////////////////////////////////////////////////////////////////////////////////
// Interface with sigp/discv5
////////////////////////////////////////////////////////////////////////////////////////////////
/// Exposes API of [`discv5::Discv5`].
pub fn with_discv5<F, R>(&self, f: F) -> R
where
F: FnOnce(&discv5::Discv5) -> R,
{
f(&self.discv5)
}
////////////////////////////////////////////////////////////////////////////////////////////////
// Complementary
////////////////////////////////////////////////////////////////////////////////////////////////
/// Returns the [`IpMode`] of the local node.
pub fn ip_mode(&self) -> IpMode {
self.ip_mode
}
/// Returns the key to use to identify the [`ForkId`] kv-pair on the [`Enr`](discv5::Enr).
pub fn fork_key(&self) -> Option<&[u8]> {
self.fork_key
}
}
impl fmt::Debug for Discv5 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
"{ .. }".fmt(f)
}
}
/// Result of successfully processing a peer discovered by [`discv5::Discv5`].
#[derive(Debug)]
pub struct DiscoveredPeer {
/// A discovery v4 backwards compatible ENR.
pub node_record: NodeRecord,
/// [`ForkId`] extracted from ENR w.r.t. configured
pub fork_id: Option<ForkId>,
}
/// Gets the next lookup target, based on which bucket is currently being targeted.
pub fn get_lookup_target(
kbucket_index: usize,
@ -846,7 +847,7 @@ mod tests {
let config = Config::builder(TCP_PORT).fork(NetworkStackId::ETH, fork_id).build();
let sk = SecretKey::new(&mut thread_rng());
let (enr, _, _, _) = Discv5::build_local_enr(&sk, &config);
let (enr, _, _, _) = build_local_enr(&sk, &config);
let decoded_fork_id = enr
.get_decodable::<EnrForkIdEntry>(NetworkStackId::ETH)