reth_downloaders/test_utils/
bodies_client.rs1use alloy_primitives::{map::B256Map, B256};
2use reth_ethereum_primitives::BlockBody;
3use reth_network_p2p::{
4 bodies::client::{BodiesClient, BodiesFut},
5 download::DownloadClient,
6 priority::Priority,
7};
8use reth_network_peers::PeerId;
9use std::{
10 fmt::Debug,
11 ops::RangeInclusive,
12 sync::{
13 atomic::{AtomicU64, Ordering},
14 Arc,
15 },
16 time::Duration,
17};
18use tokio::sync::Mutex;
19
20#[derive(Debug, Default)]
22pub struct TestBodiesClient {
23 bodies: Arc<Mutex<B256Map<BlockBody>>>,
24 should_delay: bool,
25 max_batch_size: Option<usize>,
26 times_requested: AtomicU64,
27 empty_response_mod: Option<u64>,
28}
29
30impl TestBodiesClient {
31 pub(crate) fn with_bodies(mut self, bodies: B256Map<BlockBody>) -> Self {
32 self.bodies = Arc::new(Mutex::new(bodies));
33 self
34 }
35
36 pub(crate) const fn with_should_delay(mut self, should_delay: bool) -> Self {
37 self.should_delay = should_delay;
38 self
39 }
40
41 pub(crate) const fn with_empty_responses(mut self, empty_mod: u64) -> Self {
44 self.empty_response_mod = Some(empty_mod);
45 self
46 }
47
48 pub(crate) const fn with_max_batch_size(mut self, max_batch_size: usize) -> Self {
49 self.max_batch_size = Some(max_batch_size);
50 self
51 }
52
53 pub(crate) fn times_requested(&self) -> u64 {
54 self.times_requested.load(Ordering::Relaxed)
55 }
56
57 pub(crate) fn should_respond_empty(&self) -> bool {
62 self.empty_response_mod.is_some_and(|empty_response_mod| {
63 self.times_requested.load(Ordering::Relaxed).is_multiple_of(empty_response_mod)
64 })
65 }
66}
67
68impl DownloadClient for TestBodiesClient {
69 fn report_bad_message(&self, _peer_id: PeerId) {
70 }
72
73 fn num_connected_peers(&self) -> usize {
74 0
75 }
76}
77
78impl BodiesClient for TestBodiesClient {
79 type Body = BlockBody;
80 type Output = BodiesFut;
81
82 fn get_block_bodies_with_priority_and_range_hint(
83 &self,
84 hashes: Vec<B256>,
85 _priority: Priority,
86 _range_hint: Option<RangeInclusive<u64>>,
87 ) -> Self::Output {
88 let should_delay = self.should_delay;
89 let bodies = self.bodies.clone();
90 let max_batch_size = self.max_batch_size;
91
92 self.times_requested.fetch_add(1, Ordering::Relaxed);
93 let should_respond_empty = self.should_respond_empty();
94
95 Box::pin(async move {
96 if should_respond_empty {
97 return Ok((PeerId::default(), vec![]).into())
98 }
99
100 if should_delay {
101 tokio::time::sleep(Duration::from_millis((hashes[0][0] % 100) as u64)).await;
102 }
103
104 let bodies = &mut *bodies.lock().await;
105 Ok((
106 PeerId::default(),
107 hashes
108 .into_iter()
109 .take(max_batch_size.unwrap_or(usize::MAX))
110 .map(|hash| {
111 bodies
112 .remove(&hash)
113 .expect("Downloader asked for a block it should not ask for")
114 })
115 .collect(),
116 )
117 .into())
118 })
119 }
120}