Skip to main content

reth_network/
required_block_filter.rs

1//! Required block peer filtering implementation.
2//!
3//! This module provides functionality to filter out peers that don't have
4//! specific required blocks (primarily used for shadowfork testing).
5
6use alloy_eips::BlockNumHash;
7use futures::{FutureExt, StreamExt};
8use reth_eth_wire_types::{GetBlockHeaders, HeadersDirection};
9use reth_network_api::{
10    NetworkEvent, NetworkEventListenerProvider, PeerRequest, Peers, ReputationChangeKind,
11};
12use tokio::sync::oneshot;
13use tracing::{debug, info, trace};
14
15/// Task that filters peers based on required block hashes.
16///
17/// This task listens for new peer sessions and checks if they have the required
18/// block hashes. Peers that don't have these blocks are banned.
19///
20/// This type is mainly used to connect peers on shadow forks (e.g. mainnet shadowfork)
21pub struct RequiredBlockFilter<N> {
22    /// Network handle for listening to events and managing peer reputation.
23    network: N,
24    /// List of block number-hash pairs that peers must have to be considered valid.
25    block_num_hashes: Vec<BlockNumHash>,
26}
27
28impl<N> RequiredBlockFilter<N>
29where
30    N: NetworkEventListenerProvider + Peers + Clone + 'static,
31{
32    /// Creates a new required block peer filter.
33    pub const fn new(network: N, block_num_hashes: Vec<BlockNumHash>) -> Self {
34        Self { network, block_num_hashes }
35    }
36
37    /// Spawns the required block peer filter task.
38    ///
39    /// This task will run indefinitely, monitoring new peer sessions and filtering
40    /// out peers that don't have the required blocks.
41    pub fn spawn(self) {
42        if self.block_num_hashes.is_empty() {
43            debug!(target: "net::filter", "No required block hashes configured, skipping peer filtering");
44            return;
45        }
46
47        info!(target: "net::filter", "Starting required block peer filter with {} block hashes", self.block_num_hashes.len());
48
49        tokio::spawn(self.run().boxed());
50    }
51
52    /// Main loop for the required block peer filter.
53    async fn run(self) {
54        let mut event_stream = self.network.event_listener();
55
56        while let Some(event) = event_stream.next().await {
57            if let NetworkEvent::ActivePeerSession { info, messages } = event {
58                let peer_id = info.peer_id;
59                debug!(target: "net::filter", "New peer session established: {}", peer_id);
60
61                // Spawn a task to check this peer's blocks
62                let network = self.network.clone();
63                let block_num_hashes = self.block_num_hashes.clone();
64                let peer_block_number = info.status.latest_block.unwrap_or(0);
65
66                tokio::spawn(
67                    Self::check_peer_blocks(
68                        network,
69                        peer_id,
70                        messages,
71                        block_num_hashes,
72                        peer_block_number,
73                    )
74                    .boxed(),
75                );
76            }
77        }
78    }
79
80    /// Checks if a peer has the required blocks and bans them if not.
81    async fn check_peer_blocks(
82        network: N,
83        peer_id: reth_network_api::PeerId,
84        messages: reth_network_api::PeerRequestSender<PeerRequest<N::Primitives>>,
85        block_num_hashes: Vec<BlockNumHash>,
86        latest_peer_block: u64,
87    ) {
88        for block_num_hash in block_num_hashes {
89            // Skip if peer's block number is lower than required, peer might also be syncing and
90            // still on the same chain.
91            if block_num_hash.number > 0 && latest_peer_block < block_num_hash.number {
92                debug!(target: "net::filter", "Skipping check for block {} - peer {} only at block {}", 
93                       block_num_hash.number, peer_id, latest_peer_block);
94                continue;
95            }
96
97            let block_hash = block_num_hash.hash;
98            trace!(target: "net::filter", "Checking if peer {} has block {}", peer_id, block_hash);
99
100            // Create a request for block headers
101            let request = GetBlockHeaders {
102                start_block: block_hash.into(),
103                limit: 1,
104                skip: 0,
105                direction: HeadersDirection::Rising,
106            };
107
108            let (tx, rx) = oneshot::channel();
109            let peer_request = PeerRequest::GetBlockHeaders { request, response: tx };
110
111            // Send the request to the peer
112            if let Err(e) = messages.try_send(peer_request) {
113                debug!(target: "net::filter", "Failed to send block header request to peer {}: {:?}", peer_id, e);
114                continue;
115            }
116
117            // Wait for the response
118            let response = match rx.await {
119                Ok(response) => response,
120                Err(e) => {
121                    debug!(
122                        target: "net::filter",
123                        "Channel error getting block {} from peer {}: {:?}",
124                        block_hash, peer_id, e
125                    );
126                    continue;
127                }
128            };
129
130            let headers = match response {
131                Ok(headers) => headers,
132                Err(e) => {
133                    debug!(target: "net::filter", "Error getting block {} from peer {}: {:?}", block_hash, peer_id, e);
134                    // Ban the peer if they fail to respond properly
135                    network.reputation_change(peer_id, ReputationChangeKind::BadProtocol);
136                    return;
137                }
138            };
139
140            if headers.is_empty() {
141                info!(
142                    target: "net::filter",
143                    "Peer {} does not have required block {}, banning",
144                    peer_id, block_hash
145                );
146                network.reputation_change(peer_id, ReputationChangeKind::BadProtocol);
147                return; // No need to check more blocks if one is missing
148            }
149
150            trace!(target: "net::filter", "Peer {} has required block {}", peer_id, block_hash);
151        }
152
153        debug!(target: "net::filter", "Peer {} has all required blocks", peer_id);
154    }
155}
156
157#[cfg(test)]
158mod tests {
159
160    use super::*;
161    use alloy_eips::BlockNumHash;
162    use alloy_primitives::B256;
163    use reth_network_api::noop::NoopNetwork;
164
165    #[test]
166    fn test_required_block_filter_creation() {
167        let network = NoopNetwork::default();
168        let block_num_hashes = vec![
169            BlockNumHash::new(0, B256::repeat_byte(0x11)),
170            BlockNumHash::new(23115201, B256::repeat_byte(0x22)),
171        ];
172
173        let filter = RequiredBlockFilter::new(network, block_num_hashes.clone());
174        assert_eq!(filter.block_num_hashes.len(), 2);
175        assert_eq!(filter.block_num_hashes, block_num_hashes);
176    }
177
178    #[test]
179    fn test_required_block_filter_empty_hashes_does_not_spawn() {
180        let network = NoopNetwork::default();
181        let block_num_hashes = vec![];
182
183        let filter = RequiredBlockFilter::new(network, block_num_hashes);
184        // This should not panic and should exit early when spawn is called
185        filter.spawn();
186    }
187
188    #[tokio::test]
189    async fn test_required_block_filter_with_mock_peer() {
190        // This test would require a more complex setup with mock network components
191        // For now, we ensure the basic structure is correct
192        let network = NoopNetwork::default();
193        let block_num_hashes = vec![BlockNumHash::new(0, B256::default())];
194
195        let filter = RequiredBlockFilter::new(network, block_num_hashes);
196        // Verify the filter can be created and basic properties are set
197        assert_eq!(filter.block_num_hashes.len(), 1);
198    }
199}