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}