Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
10 changes: 5 additions & 5 deletions crates/net/network/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use crate::{
transactions::TransactionsManagerConfig,
NetworkHandle, NetworkManager,
};
use alloy_primitives::B256;
use alloy_eips::BlockNumHash;
use reth_chainspec::{ChainSpecProvider, EthChainSpec, Hardforks};
use reth_discv4::{Discv4Config, Discv4ConfigBuilder, NatResolver, DEFAULT_DISCOVERY_ADDRESS};
use reth_discv5::NetworkStackId;
Expand Down Expand Up @@ -94,9 +94,9 @@ pub struct NetworkConfig<C, N: NetworkPrimitives = EthNetworkPrimitives> {
/// This can be overridden to support custom handshake logic via the
/// [`NetworkConfigBuilder`].
pub handshake: Arc<dyn EthRlpxHandshake>,
/// List of block hashes to check for required blocks.
/// List of block number-hash pairs to check for required blocks.
/// If non-empty, peers that don't have these blocks will be filtered out.
pub required_block_hashes: Vec<B256>,
pub required_block_hashes: Vec<BlockNumHash>,
}

// === impl NetworkConfig ===
Expand Down Expand Up @@ -225,7 +225,7 @@ pub struct NetworkConfigBuilder<N: NetworkPrimitives = EthNetworkPrimitives> {
/// <https://github.com/ethereum/devp2p/blob/master/rlpx.md#initial-handshake>.
handshake: Arc<dyn EthRlpxHandshake>,
/// List of block hashes to check for required blocks.
required_block_hashes: Vec<B256>,
required_block_hashes: Vec<BlockNumHash>,
}

impl NetworkConfigBuilder<EthNetworkPrimitives> {
Expand Down Expand Up @@ -552,7 +552,7 @@ impl<N: NetworkPrimitives> NetworkConfigBuilder<N> {
}

/// Sets the required block hashes for peer filtering.
pub fn required_block_hashes(mut self, hashes: Vec<B256>) -> Self {
pub fn required_block_hashes(mut self, hashes: Vec<BlockNumHash>) -> Self {
self.required_block_hashes = hashes;
self
}
Expand Down
68 changes: 46 additions & 22 deletions crates/net/network/src/required_block_filter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
//! This module provides functionality to filter out peers that don't have
//! specific required blocks (primarily used for shadowfork testing).

use alloy_primitives::B256;
use alloy_eips::BlockNumHash;
use futures::StreamExt;
use reth_eth_wire_types::{GetBlockHeaders, HeadersDirection};
use reth_network_api::{
Expand All @@ -19,30 +19,30 @@ use tracing::{debug, info, trace};
pub struct RequiredBlockFilter<N> {
/// Network handle for listening to events and managing peer reputation.
network: N,
/// List of block hashes that peers must have to be considered valid.
block_hashes: Vec<B256>,
/// List of block number-hash pairs that peers must have to be considered valid.
block_num_hashes: Vec<BlockNumHash>,
}

impl<N> RequiredBlockFilter<N>
where
N: NetworkEventListenerProvider + Peers + Clone + Send + Sync + 'static,
{
/// Creates a new required block peer filter.
pub const fn new(network: N, block_hashes: Vec<B256>) -> Self {
Self { network, block_hashes }
pub const fn new(network: N, block_num_hashes: Vec<BlockNumHash>) -> Self {
Self { network, block_num_hashes }
}

/// Spawns the required block peer filter task.
///
/// This task will run indefinitely, monitoring new peer sessions and filtering
/// out peers that don't have the required blocks.
pub fn spawn(self) {
if self.block_hashes.is_empty() {
if self.block_num_hashes.is_empty() {
debug!(target: "net::filter", "No required block hashes configured, skipping peer filtering");
return;
}

info!(target: "net::filter", "Starting required block peer filter with {} block hashes", self.block_hashes.len());
info!(target: "net::filter", "Starting required block peer filter with {} block hashes", self.block_num_hashes.len());

tokio::spawn(async move {
self.run().await;
Expand All @@ -60,10 +60,18 @@ where

// Spawn a task to check this peer's blocks
let network = self.network.clone();
let block_hashes = self.block_hashes.clone();
let block_num_hashes = self.block_num_hashes.clone();
let peer_block_number = info.status.latest_block.unwrap_or(0);

tokio::spawn(async move {
Self::check_peer_blocks(network, peer_id, messages, block_hashes).await;
Self::check_peer_blocks(
network,
peer_id,
messages,
block_num_hashes,
peer_block_number,
)
.await;
});
}
}
Expand All @@ -74,9 +82,18 @@ where
network: N,
peer_id: reth_network_api::PeerId,
messages: reth_network_api::PeerRequestSender<PeerRequest<N::Primitives>>,
block_hashes: Vec<B256>,
block_num_hashes: Vec<BlockNumHash>,
peer_block_number: u64,
) {
for block_hash in block_hashes {
for block_num_hash in block_num_hashes {
// Skip if peer's block number is lower than required (optimization)
if block_num_hash.number > 0 && peer_block_number < block_num_hash.number {
debug!(target: "net::filter", "Skipping check for block {} - peer {} only at block {}",
block_num_hash.number, peer_id, peer_block_number);
continue;
}

let block_hash = block_num_hash.hash;
trace!(target: "net::filter", "Checking if peer {} has block {}", peer_id, block_hash);

// Create a request for block headers
Expand Down Expand Up @@ -139,28 +156,35 @@ where
#[cfg(test)]
mod tests {
use super::*;
use alloy_eips::BlockNumHash;
use alloy_primitives::{b256, B256};
use reth_network_api::noop::NoopNetwork;

#[test]
fn test_required_block_filter_creation() {
let network = NoopNetwork::default();
let block_hashes = vec![
b256!("0x1111111111111111111111111111111111111111111111111111111111111111"),
b256!("0x2222222222222222222222222222222222222222222222222222222222222222"),
let block_num_hashes = vec![
BlockNumHash::new(
0,
b256!("0x1111111111111111111111111111111111111111111111111111111111111111"),
),
BlockNumHash::new(
23115201,
b256!("0x2222222222222222222222222222222222222222222222222222222222222222"),
),
];

let filter = RequiredBlockFilter::new(network, block_hashes.clone());
assert_eq!(filter.block_hashes.len(), 2);
assert_eq!(filter.block_hashes, block_hashes);
let filter = RequiredBlockFilter::new(network, block_num_hashes.clone());
assert_eq!(filter.block_num_hashes.len(), 2);
assert_eq!(filter.block_num_hashes, block_num_hashes);
}

#[test]
fn test_required_block_filter_empty_hashes_does_not_spawn() {
let network = NoopNetwork::default();
let block_hashes = vec![];
let block_num_hashes = vec![];

let filter = RequiredBlockFilter::new(network, block_hashes);
let filter = RequiredBlockFilter::new(network, block_num_hashes);
// This should not panic and should exit early when spawn is called
filter.spawn();
}
Expand All @@ -170,10 +194,10 @@ mod tests {
// This test would require a more complex setup with mock network components
// For now, we ensure the basic structure is correct
let network = NoopNetwork::default();
let block_hashes = vec![B256::default()];
let block_num_hashes = vec![BlockNumHash::new(0, B256::default())];

let filter = RequiredBlockFilter::new(network, block_hashes);
let filter = RequiredBlockFilter::new(network, block_num_hashes);
// Verify the filter can be created and basic properties are set
assert_eq!(filter.block_hashes.len(), 1);
assert_eq!(filter.block_num_hashes.len(), 1);
}
}
56 changes: 50 additions & 6 deletions crates/node/core/src/args/network.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//! clap [Args](clap::Args) for network related arguments.

use alloy_eips::BlockNumHash;
use alloy_primitives::B256;
use std::{
net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6},
Expand Down Expand Up @@ -37,6 +38,7 @@ use reth_network::{
};
use reth_network_peers::{mainnet_nodes, TrustedPeer};
use secp256k1::SecretKey;
use std::str::FromStr;
use tracing::error;

/// Parameters for configuring the network more granularity via CLI
Expand Down Expand Up @@ -180,10 +182,11 @@ pub struct NetworkArgs {
)]
pub propagation_mode: TransactionPropagationMode,

/// Comma separated list of required block hashes.
/// Comma separated list of required block hashes or block number=hash pairs.
/// Peers that don't have these blocks will be filtered out.
#[arg(long = "required-block-hashes", value_delimiter = ',')]
pub required_block_hashes: Vec<B256>,
/// Format: hash or `block_number=hash` (e.g., 23115201=0x1234...)
#[arg(long = "required-block-hashes", value_delimiter = ',', value_parser = parse_block_num_hash)]
pub required_block_hashes: Vec<BlockNumHash>,
}

impl NetworkArgs {
Expand Down Expand Up @@ -570,6 +573,19 @@ impl Default for DiscoveryArgs {
}
}

/// Parse a block number=hash pair or just a hash into `BlockNumHash`
fn parse_block_num_hash(s: &str) -> Result<BlockNumHash, String> {
if let Some((num_str, hash_str)) = s.split_once('=') {
let number = num_str.parse().map_err(|_| format!("Invalid block number: {}", num_str))?;
let hash = B256::from_str(hash_str).map_err(|_| format!("Invalid hash: {}", hash_str))?;
Ok(BlockNumHash::new(number, hash))
} else {
// For backward compatibility, treat as hash-only with number 0
let hash = B256::from_str(s).map_err(|_| format!("Invalid hash: {}", s))?;
Ok(BlockNumHash::new(0, hash))
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down Expand Up @@ -664,17 +680,21 @@ mod tests {
let args = CommandParser::<NetworkArgs>::parse_from([
"reth",
"--required-block-hashes",
"0x1111111111111111111111111111111111111111111111111111111111111111,0x2222222222222222222222222222222222222222222222222222222222222222",
"0x1111111111111111111111111111111111111111111111111111111111111111,23115201=0x2222222222222222222222222222222222222222222222222222222222222222",
])
.args;

assert_eq!(args.required_block_hashes.len(), 2);
// First hash without block number (should default to 0)
assert_eq!(args.required_block_hashes[0].number, 0);
assert_eq!(
args.required_block_hashes[0].to_string(),
args.required_block_hashes[0].hash.to_string(),
"0x1111111111111111111111111111111111111111111111111111111111111111"
);
// Second with block number=hash format
assert_eq!(args.required_block_hashes[1].number, 23115201);
assert_eq!(
args.required_block_hashes[1].to_string(),
args.required_block_hashes[1].hash.to_string(),
"0x2222222222222222222222222222222222222222222222222222222222222222"
);
}
Expand All @@ -684,4 +704,28 @@ mod tests {
let args = CommandParser::<NetworkArgs>::parse_from(["reth"]).args;
assert!(args.required_block_hashes.is_empty());
}

#[test]
fn test_parse_block_num_hash() {
// Test hash only format
let result = parse_block_num_hash(
"0x1111111111111111111111111111111111111111111111111111111111111111",
);
assert!(result.is_ok());
assert_eq!(result.unwrap().number, 0);

// Test block_number=hash format
let result = parse_block_num_hash(
"23115201=0x2222222222222222222222222222222222222222222222222222222222222222",
);
assert!(result.is_ok());
assert_eq!(result.unwrap().number, 23115201);

// Test invalid formats
assert!(parse_block_num_hash("invalid").is_err());
assert!(parse_block_num_hash(
"abc=0x1111111111111111111111111111111111111111111111111111111111111111"
)
.is_err());
}
}
2 changes: 1 addition & 1 deletion docs/vocs/docs/pages/cli/reth/node.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ Networking:
[default: sqrt]

--required-block-hashes <REQUIRED_BLOCK_HASHES>
Comma separated list of required block hashes. Peers that don't have these blocks will be filtered out
Comma separated list of required block hashes or block number=hash pairs. Peers that don't have these blocks will be filtered out. Format: hash or `block_number=hash` (e.g., 23115201=0x1234...)

RPC:
--http
Expand Down
2 changes: 1 addition & 1 deletion docs/vocs/docs/pages/cli/reth/p2p/body.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ Networking:
[default: sqrt]

--required-block-hashes <REQUIRED_BLOCK_HASHES>
Comma separated list of required block hashes. Peers that don't have these blocks will be filtered out
Comma separated list of required block hashes or block number=hash pairs. Peers that don't have these blocks will be filtered out. Format: hash or `block_number=hash` (e.g., 23115201=0x1234...)

Datadir:
--datadir <DATA_DIR>
Expand Down
2 changes: 1 addition & 1 deletion docs/vocs/docs/pages/cli/reth/p2p/header.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ Networking:
[default: sqrt]

--required-block-hashes <REQUIRED_BLOCK_HASHES>
Comma separated list of required block hashes. Peers that don't have these blocks will be filtered out
Comma separated list of required block hashes or block number=hash pairs. Peers that don't have these blocks will be filtered out. Format: hash or `block_number=hash` (e.g., 23115201=0x1234...)

Datadir:
--datadir <DATA_DIR>
Expand Down
2 changes: 1 addition & 1 deletion docs/vocs/docs/pages/cli/reth/stage/run.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,7 @@ Networking:
[default: sqrt]

--required-block-hashes <REQUIRED_BLOCK_HASHES>
Comma separated list of required block hashes. Peers that don't have these blocks will be filtered out
Comma separated list of required block hashes or block number=hash pairs. Peers that don't have these blocks will be filtered out. Format: hash or `block_number=hash` (e.g., 23115201=0x1234...)

Logging:
--log.stdout.format <FORMAT>
Expand Down