Skip to main content

reth_network_p2p/test_utils/
full_block.rs

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