reth_network/
required_block_filter.rs1use 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
15pub struct RequiredBlockFilter<N> {
22 network: N,
24 block_num_hashes: Vec<BlockNumHash>,
26}
27
28impl<N> RequiredBlockFilter<N>
29where
30 N: NetworkEventListenerProvider + Peers + Clone + 'static,
31{
32 pub const fn new(network: N, block_num_hashes: Vec<BlockNumHash>) -> Self {
34 Self { network, block_num_hashes }
35 }
36
37 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 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 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 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 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 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 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 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 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; }
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 filter.spawn();
186 }
187
188 #[tokio::test]
189 async fn test_required_block_filter_with_mock_peer() {
190 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 assert_eq!(filter.block_num_hashes.len(), 1);
198 }
199}