reth_network_p2p/test_utils/full_block.rs
1use crate::{
2 bodies::client::BodiesClient,
3 download::DownloadClient,
4 error::PeerRequestResult,
5 headers::client::{HeadersClient, HeadersRequest},
6 priority::Priority,
7 BlockClient,
8};
9use alloy_consensus::Header;
10use alloy_eips::{BlockHashOrNumber, BlockNumHash};
11use alloy_primitives::B256;
12use parking_lot::Mutex;
13use reth_eth_wire_types::HeadersDirection;
14use reth_ethereum_primitives::{Block, BlockBody};
15use reth_network_peers::{PeerId, WithPeerId};
16use reth_primitives_traits::{SealedBlock, SealedHeader};
17use std::{collections::HashMap, sync::Arc};
18
19/// A headers+bodies client implementation that does nothing.
20#[derive(Debug, Default, Clone)]
21#[non_exhaustive]
22pub struct NoopFullBlockClient;
23
24/// Implements the `DownloadClient` trait for the `NoopFullBlockClient` struct.
25impl DownloadClient for NoopFullBlockClient {
26 /// Reports a bad message received from a peer.
27 ///
28 /// # Arguments
29 ///
30 /// * `_peer_id` - Identifier for the peer sending the bad message (unused in this
31 /// implementation).
32 fn report_bad_message(&self, _peer_id: PeerId) {}
33
34 /// Retrieves the number of connected peers.
35 ///
36 /// # Returns
37 ///
38 /// The number of connected peers, which is always zero in this implementation.
39 fn num_connected_peers(&self) -> usize {
40 0
41 }
42}
43
44/// Implements the `BodiesClient` trait for the `NoopFullBlockClient` struct.
45impl BodiesClient for NoopFullBlockClient {
46 type Body = BlockBody;
47 /// Defines the output type of the function.
48 type Output = futures::future::Ready<PeerRequestResult<Vec<BlockBody>>>;
49
50 /// Retrieves block bodies based on provided hashes and priority.
51 ///
52 /// # Arguments
53 ///
54 /// * `_hashes` - A vector of block hashes (unused in this implementation).
55 /// * `_priority` - Priority level for block body retrieval (unused in this implementation).
56 ///
57 /// # Returns
58 ///
59 /// A future containing an empty vector of block bodies and a randomly generated `PeerId`.
60 fn get_block_bodies_with_priority(
61 &self,
62 _hashes: Vec<B256>,
63 _priority: Priority,
64 ) -> Self::Output {
65 // Create a future that immediately returns an empty vector of block bodies and a random
66 // PeerId.
67 futures::future::ready(Ok(WithPeerId::new(PeerId::random(), vec![])))
68 }
69}
70
71impl HeadersClient for NoopFullBlockClient {
72 type Header = Header;
73 /// The output type representing a future containing a peer request result with a vector of
74 /// headers.
75 type Output = futures::future::Ready<PeerRequestResult<Vec<Header>>>;
76
77 /// Retrieves headers with a specified priority level.
78 ///
79 /// This implementation does nothing and returns an empty vector of headers.
80 ///
81 /// # Arguments
82 ///
83 /// * `_request` - A request for headers (unused in this implementation).
84 /// * `_priority` - The priority level for the headers request (unused in this implementation).
85 ///
86 /// # Returns
87 ///
88 /// Always returns a ready future with an empty vector of headers wrapped in a
89 /// `PeerRequestResult`.
90 fn get_headers_with_priority(
91 &self,
92 _request: HeadersRequest,
93 _priority: Priority,
94 ) -> Self::Output {
95 futures::future::ready(Ok(WithPeerId::new(PeerId::random(), vec![])))
96 }
97}
98
99/// A headers+bodies client that stores the headers and bodies in memory, with an artificial soft
100/// bodies response limit that is set to 20 by default.
101///
102/// This full block client can be [Clone]d and shared between multiple tasks.
103#[derive(Clone, Debug)]
104pub struct TestFullBlockClient {
105 headers: Arc<Mutex<HashMap<B256, Header>>>,
106 bodies: Arc<Mutex<HashMap<B256, BlockBody>>>,
107 // soft response limit, max number of bodies to respond with
108 soft_limit: usize,
109}
110
111impl Default for TestFullBlockClient {
112 fn default() -> Self {
113 Self {
114 headers: Arc::new(Mutex::new(HashMap::default())),
115 bodies: Arc::new(Mutex::new(HashMap::default())),
116 soft_limit: 20,
117 }
118 }
119}
120
121impl TestFullBlockClient {
122 /// Insert a header and body into the client maps.
123 pub fn insert(&self, header: SealedHeader, body: BlockBody) {
124 let hash = header.hash();
125 self.headers.lock().insert(hash, header.unseal());
126 self.bodies.lock().insert(hash, body);
127 }
128
129 /// Set the soft response limit.
130 pub const fn set_soft_limit(&mut self, limit: usize) {
131 self.soft_limit = limit;
132 }
133
134 /// Get the block with the highest block number.
135 pub fn highest_block(&self) -> Option<SealedBlock<Block>> {
136 self.headers.lock().iter().max_by_key(|(_, header)| header.number).and_then(
137 |(hash, header)| {
138 self.bodies.lock().get(hash).map(|body| {
139 SealedBlock::from_parts_unchecked(header.clone(), body.clone(), *hash)
140 })
141 },
142 )
143 }
144}
145
146impl DownloadClient for TestFullBlockClient {
147 /// Reports a bad message from a specific peer.
148 fn report_bad_message(&self, _peer_id: PeerId) {}
149
150 /// Retrieves the number of connected peers.
151 ///
152 /// Returns the number of connected peers in the test scenario (1).
153 fn num_connected_peers(&self) -> usize {
154 1
155 }
156}
157
158/// Implements the `HeadersClient` trait for the `TestFullBlockClient` struct.
159impl HeadersClient for TestFullBlockClient {
160 type Header = Header;
161 /// Specifies the associated output type.
162 type Output = futures::future::Ready<PeerRequestResult<Vec<Header>>>;
163
164 /// Retrieves headers with a given priority level.
165 ///
166 /// # Arguments
167 ///
168 /// * `request` - A `HeadersRequest` indicating the headers to retrieve.
169 /// * `_priority` - A `Priority` level for the request.
170 ///
171 /// # Returns
172 ///
173 /// A `Ready` future containing a `PeerRequestResult` with a vector of retrieved headers.
174 fn get_headers_with_priority(
175 &self,
176 request: HeadersRequest,
177 _priority: Priority,
178 ) -> Self::Output {
179 let headers = self.headers.lock();
180
181 // Initializes the block hash or number.
182 let mut block: BlockHashOrNumber = match request.start {
183 BlockHashOrNumber::Hash(hash) => headers.get(&hash).cloned(),
184 BlockHashOrNumber::Number(num) => headers.values().find(|h| h.number == num).cloned(),
185 }
186 .map(|h| h.number.into())
187 .unwrap();
188
189 // Retrieves headers based on the provided limit and request direction.
190 let resp = (0..request.limit)
191 .filter_map(|_| {
192 headers.iter().find_map(|(hash, header)| {
193 // Checks if the header matches the specified block or number.
194 BlockNumHash::new(header.number, *hash).matches_block_or_num(&block).then(
195 || {
196 match request.direction {
197 HeadersDirection::Falling => block = header.parent_hash.into(),
198 HeadersDirection::Rising => block = (header.number + 1).into(),
199 }
200 header.clone()
201 },
202 )
203 })
204 })
205 .collect::<Vec<_>>();
206
207 // Returns a future containing the retrieved headers with a random peer ID.
208 futures::future::ready(Ok(WithPeerId::new(PeerId::random(), resp)))
209 }
210}
211
212/// Implements the `BodiesClient` trait for the `TestFullBlockClient` struct.
213impl BodiesClient for TestFullBlockClient {
214 type Body = BlockBody;
215 /// Defines the output type of the function.
216 type Output = futures::future::Ready<PeerRequestResult<Vec<BlockBody>>>;
217
218 /// Retrieves block bodies corresponding to provided hashes with a given priority.
219 ///
220 /// # Arguments
221 ///
222 /// * `hashes` - A vector of block hashes to retrieve bodies for.
223 /// * `_priority` - Priority level for block body retrieval (unused in this implementation).
224 ///
225 /// # Returns
226 ///
227 /// A future containing the result of the block body retrieval operation.
228 fn get_block_bodies_with_priority(
229 &self,
230 hashes: Vec<B256>,
231 _priority: Priority,
232 ) -> Self::Output {
233 // Acquire a lock on the bodies.
234 let bodies = self.bodies.lock();
235
236 // Create a future that immediately returns the result of the block body retrieval
237 // operation.
238 futures::future::ready(Ok(WithPeerId::new(
239 PeerId::random(),
240 hashes
241 .iter()
242 .filter_map(|hash| bodies.get(hash).cloned())
243 .take(self.soft_limit)
244 .collect(),
245 )))
246 }
247}
248
249impl BlockClient for TestFullBlockClient {
250 type Block = reth_ethereum_primitives::Block;
251}