feat(net): enforce EIP-868 fork ID for discovered peers (#22013)

Co-authored-by: Emma <emma@tempo.xyz>
Co-authored-by: Matthias Seitz <mattsse@users.noreply.github.com>
Co-authored-by: Amp <amp@ampcode.com>
Co-authored-by: Emma Jamieson-Hoare <ejamieson19@gmail.com>
Co-authored-by: Emma Jamieson-Hoare <emmajam@users.noreply.github.com>
This commit is contained in:
Georgios Konstantopoulos
2026-02-12 10:29:37 -05:00
committed by GitHub
parent 4a62d38af2
commit 0b8e6c6ed3
12 changed files with 102 additions and 24 deletions

View File

@@ -0,0 +1,9 @@
---
reth-network-api: minor
reth-network-types: minor
reth-network: minor
reth-node-core: minor
reth: minor
---
Added optional ENR fork ID enforcement to filter out peers from incompatible networks during peer discovery, controlled by the `--enforce-enr-fork-id` CLI flag.

View File

@@ -8,7 +8,7 @@ use reth_eth_wire_types::{
};
use reth_ethereum_forks::ForkId;
use reth_network_p2p::error::{RequestError, RequestResult};
use reth_network_peers::PeerId;
use reth_network_peers::{NodeRecord, PeerId};
use reth_network_types::{PeerAddr, PeerKind};
use reth_tokio_util::EventStream;
use std::{
@@ -152,8 +152,13 @@ pub trait NetworkEventListenerProvider: NetworkPeersEvents {
pub enum DiscoveryEvent {
/// Discovered a node
NewNode(DiscoveredEvent),
/// Retrieved a [`ForkId`] from the peer via ENR request, See <https://eips.ethereum.org/EIPS/eip-868>
EnrForkId(PeerId, ForkId),
/// Retrieved a [`ForkId`] from the peer via ENR request.
///
/// Contains the full [`NodeRecord`] (peer ID + address) and the reported [`ForkId`].
/// Used to verify fork compatibility before admitting the peer.
///
/// See also <https://eips.ethereum.org/EIPS/eip-868>
EnrForkId(NodeRecord, ForkId),
}
/// Represents events related to peer discovery in the network.

View File

@@ -172,6 +172,11 @@ pub struct PeersConfig {
/// IPs within the specified CIDR ranges will be allowed.
#[cfg_attr(feature = "serde", serde(skip))]
pub ip_filter: IpFilter,
/// If true, discovered peers without a confirmed ENR [`ForkId`](alloy_eip2124::ForkId)
/// (EIP-868) will not be added to the peer set until their fork ID is verified.
///
/// This filters out peers from other networks that pollute the discovery table.
pub enforce_enr_fork_id: bool,
}
impl Default for PeersConfig {
@@ -191,6 +196,7 @@ impl Default for PeersConfig {
max_backoff_count: 5,
incoming_ip_throttle_duration: INBOUND_IP_THROTTLE_DURATION,
ip_filter: IpFilter::default(),
enforce_enr_fork_id: false,
}
}
}
@@ -314,6 +320,13 @@ impl PeersConfig {
self
}
/// If set, discovered peers without a confirmed ENR [`ForkId`](alloy_eip2124::ForkId) will not
/// be added to the peer set until their fork ID is verified via EIP-868.
pub const fn with_enforce_enr_fork_id(mut self, enforce: bool) -> Self {
self.enforce_enr_fork_id = enforce;
self
}
/// Returns settings for testing
#[cfg(any(test, feature = "test-utils"))]
pub fn test() -> Self {

View File

@@ -240,7 +240,7 @@ impl Discovery {
self.on_node_record_update(record, None);
}
DiscoveryUpdate::EnrForkId(node, fork_id) => {
self.queued_events.push_back(DiscoveryEvent::EnrForkId(node.id, fork_id))
self.queued_events.push_back(DiscoveryEvent::EnrForkId(node, fork_id))
}
DiscoveryUpdate::Removed(peer_id) => {
self.discovered_nodes.remove(&peer_id);

View File

@@ -92,6 +92,9 @@ pub struct PeersManager {
incoming_ip_throttle_duration: Duration,
/// IP address filter for restricting network connections to specific IP ranges.
ip_filter: reth_net_banlist::IpFilter,
/// If true, discovered peers without a confirmed ENR fork ID will not be added until their
/// fork ID is verified via EIP-868.
enforce_enr_fork_id: bool,
}
impl PeersManager {
@@ -111,6 +114,7 @@ impl PeersManager {
max_backoff_count,
incoming_ip_throttle_duration,
ip_filter,
enforce_enr_fork_id,
} = config;
let (manager_tx, handle_rx) = mpsc::unbounded_channel();
let now = Instant::now();
@@ -167,6 +171,7 @@ impl PeersManager {
net_connection_state: NetworkConnectionState::default(),
incoming_ip_throttle_duration,
ip_filter,
enforce_enr_fork_id,
}
}
@@ -175,6 +180,11 @@ impl PeersManager {
PeersHandle::new(self.manager_tx.clone())
}
/// Returns `true` if discovered peers must have a confirmed ENR fork ID before being added.
pub(crate) const fn enforce_enr_fork_id(&self) -> bool {
self.enforce_enr_fork_id
}
/// Returns the number of peers in the peer set
#[inline]
pub(crate) fn num_known_peers(&self) -> usize {
@@ -738,17 +748,6 @@ impl PeersManager {
}
}
/// Called as follow-up for a discovered peer.
///
/// The [`ForkId`] is retrieved from an ENR record that the peer announces over the discovery
/// protocol
pub(crate) fn set_discovered_fork_id(&mut self, peer_id: PeerId, fork_id: ForkId) {
if let Some(peer) = self.peers.get_mut(&peer_id) {
trace!(target: "net::peers", ?peer_id, ?fork_id, "set discovered fork id");
peer.fork_id = Some(Box::new(fork_id));
}
}
/// Called for a newly discovered peer.
///
/// If the peer already exists, then the address, kind and `fork_id` will be updated.

View File

@@ -332,9 +332,19 @@ impl<N: NetworkPrimitives> NetworkState<N> {
fork_id,
});
}
DiscoveryEvent::EnrForkId(peer_id, fork_id) => {
self.queued_messages
.push_back(StateAction::DiscoveredEnrForkId { peer_id, fork_id });
DiscoveryEvent::EnrForkId(record, fork_id) => {
let peer_id = record.id;
let tcp_addr = record.tcp_addr();
if tcp_addr.port() == 0 {
return
}
let udp_addr = record.udp_addr();
let addr = PeerAddr::new(tcp_addr, Some(udp_addr));
self.queued_messages.push_back(StateAction::DiscoveredEnrForkId {
peer_id,
addr,
fork_id,
});
}
}
}
@@ -552,6 +562,8 @@ pub(crate) enum StateAction<N: NetworkPrimitives> {
/// Retrieved a [`ForkId`] from the peer via ENR request, See <https://eips.ethereum.org/EIPS/eip-868>
DiscoveredEnrForkId {
peer_id: PeerId,
/// The address of the peer.
addr: PeerAddr,
/// The reported [`ForkId`] by this peer.
fork_id: ForkId,
},

View File

@@ -246,18 +246,28 @@ impl<N: NetworkPrimitives> Swarm<N> {
StateAction::PeerAdded(peer_id) => return Some(SwarmEvent::PeerAdded(peer_id)),
StateAction::PeerRemoved(peer_id) => return Some(SwarmEvent::PeerRemoved(peer_id)),
StateAction::DiscoveredNode { peer_id, addr, fork_id } => {
// Don't try to connect to peer if node is shutting down
if self.is_shutting_down() {
return None
}
// Insert peer only if no fork id or a valid fork id
if fork_id.map_or_else(|| true, |f| self.sessions.is_valid_fork_id(f)) {
// When `enforce_enr_fork_id` is enabled, peers discovered without a confirmed
// fork ID (via EIP-868 ENR) are deferred — they'll only be added once a
// `DiscoveredEnrForkId` event arrives with a validated fork ID.
//
// When disabled (default), peers without a fork ID are admitted immediately.
// Peers that *do* carry a fork ID are always validated against ours.
let enforce = self.state().peers().enforce_enr_fork_id();
let allow = match fork_id {
Some(f) => self.sessions.is_valid_fork_id(f),
None => !enforce,
};
if allow {
self.state_mut().peers_mut().add_peer(peer_id, addr, fork_id);
}
}
StateAction::DiscoveredEnrForkId { peer_id, fork_id } => {
StateAction::DiscoveredEnrForkId { peer_id, addr, fork_id } => {
if self.sessions.is_valid_fork_id(fork_id) {
self.state_mut().peers_mut().set_discovered_fork_id(peer_id, fork_id);
self.state_mut().peers_mut().add_peer(peer_id, addr, Some(fork_id));
} else {
trace!(target: "net", ?peer_id, remote_fork_id=?fork_id, our_fork_id=?self.sessions.fork_id(), "fork id mismatch, removing peer");
self.state_mut().peers_mut().remove_peer(peer_id);

View File

@@ -227,6 +227,14 @@ pub struct NetworkArgs {
/// Example: --netrestrict "192.168.0.0/16,10.0.0.0/8"
#[arg(long, value_name = "NETRESTRICT")]
pub netrestrict: Option<String>,
/// Enforce EIP-868 ENR fork ID validation for discovered peers.
///
/// When enabled, peers discovered without a confirmed fork ID are not added to the peer set
/// until their fork ID is verified via EIP-868 ENR request. This filters out peers from other
/// networks that pollute the discovery table.
#[arg(long)]
pub enforce_enr_fork_id: bool,
}
impl NetworkArgs {
@@ -333,7 +341,8 @@ impl NetworkArgs {
)
.with_max_inbound_opt(self.resolved_max_inbound_peers())
.with_max_outbound_opt(self.resolved_max_outbound_peers())
.with_ip_filter(ip_filter);
.with_ip_filter(ip_filter)
.with_enforce_enr_fork_id(self.enforce_enr_fork_id);
// Configure basic network stack
NetworkConfigBuilder::<N>::new(secret_key)
@@ -491,6 +500,7 @@ impl Default for NetworkArgs {
required_block_hashes: vec![],
network_id: None,
netrestrict: None,
enforce_enr_fork_id: false,
}
}
}

View File

@@ -293,6 +293,11 @@ Networking:
Example: --netrestrict "192.168.0.0/16,10.0.0.0/8"
--enforce-enr-fork-id
Enforce EIP-868 ENR fork ID validation for discovered peers.
When enabled, peers discovered without a confirmed fork ID are not added to the peer set until their fork ID is verified via EIP-868 ENR request. This filters out peers from other networks that pollute the discovery table.
RPC:
--http
Enable the HTTP-RPC server

View File

@@ -233,6 +233,11 @@ Networking:
Example: --netrestrict "192.168.0.0/16,10.0.0.0/8"
--enforce-enr-fork-id
Enforce EIP-868 ENR fork ID validation for discovered peers.
When enabled, peers discovered without a confirmed fork ID are not added to the peer set until their fork ID is verified via EIP-868 ENR request. This filters out peers from other networks that pollute the discovery table.
Datadir:
--datadir <DATA_DIR>
The path to the data dir for all reth files and subdirectories.

View File

@@ -233,6 +233,11 @@ Networking:
Example: --netrestrict "192.168.0.0/16,10.0.0.0/8"
--enforce-enr-fork-id
Enforce EIP-868 ENR fork ID validation for discovered peers.
When enabled, peers discovered without a confirmed fork ID are not added to the peer set until their fork ID is verified via EIP-868 ENR request. This filters out peers from other networks that pollute the discovery table.
Datadir:
--datadir <DATA_DIR>
The path to the data dir for all reth files and subdirectories.

View File

@@ -460,6 +460,11 @@ Networking:
Example: --netrestrict "192.168.0.0/16,10.0.0.0/8"
--enforce-enr-fork-id
Enforce EIP-868 ENR fork ID validation for discovered peers.
When enabled, peers discovered without a confirmed fork ID are not added to the peer set until their fork ID is verified via EIP-868 ENR request. This filters out peers from other networks that pollute the discovery table.
Logging:
--log.stdout.format <FORMAT>
The format to use for logs written to stdout