1use super::request::{SnapVerifier, VerifyingRequest};
8use alloy_primitives::{keccak256, Bytes, B256};
9use futures::Future;
10use reth_eth_wire_types::snap::GetByteCodesMessage;
11use reth_network_p2p::{
12 error::RequestError,
13 snap::client::{SnapClient, SnapResponse},
14};
15use reth_network_peers::PeerId;
16use reth_tasks::Runtime;
17use std::{
18 pin::Pin,
19 task::{Context, Poll},
20};
21use tracing::debug;
22
23#[derive(Debug)]
27pub struct BytecodeDownloader<C: SnapClient>(VerifyingRequest<C, BytecodeVerifier>);
28
29impl<C: SnapClient> BytecodeDownloader<C> {
30 pub fn new(
32 client: C,
33 request: GetByteCodesMessage,
34 runtime: Runtime,
35 ) -> Result<Self, InvalidBytecodeRequest> {
36 if request.hashes.is_empty() {
37 return Err(InvalidBytecodeRequest::NoHashes)
38 }
39
40 let verifier =
41 BytecodeVerifier { request_id: request.request_id, hashes: request.hashes.clone() };
42 Ok(Self(VerifyingRequest::new(client, request, verifier, runtime)))
43 }
44}
45
46impl<C> Future for BytecodeDownloader<C>
47where
48 C: SnapClient + Unpin,
49{
50 type Output = Result<BytecodeOutcome, RequestError>;
51
52 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
53 self.get_mut().0.poll_verified(cx)
54 }
55}
56
57#[derive(Clone, Debug, PartialEq, Eq)]
59pub enum BytecodeOutcome {
60 Unavailable {
62 peer_id: PeerId,
64 },
65 Verified(VerifiedBytecode),
67}
68
69#[derive(Clone, Debug, PartialEq, Eq)]
71pub struct VerifiedBytecode {
72 peer_id: PeerId,
74 codes: Vec<(B256, Bytes)>,
77}
78
79impl VerifiedBytecode {
80 pub const fn peer_id(&self) -> PeerId {
82 self.peer_id
83 }
84
85 pub fn codes(&self) -> &[(B256, Bytes)] {
87 &self.codes
88 }
89
90 pub fn into_codes(self) -> Vec<(B256, Bytes)> {
92 self.codes
93 }
94
95 pub fn missing<'a>(&'a self, requested: &'a [B256]) -> impl Iterator<Item = B256> + 'a {
100 requested.iter().copied().filter(|hash| !self.codes.iter().any(|(got, _)| got == hash))
101 }
102}
103
104#[derive(Clone, Copy, Debug, PartialEq, Eq, thiserror::Error)]
106pub enum InvalidBytecodeRequest {
107 #[error("bytecode request has no code hashes")]
109 NoHashes,
110}
111
112#[derive(Clone, Debug)]
116struct BytecodeVerifier {
117 request_id: u64,
119 hashes: Vec<B256>,
121}
122
123impl SnapVerifier for BytecodeVerifier {
124 type Request = GetByteCodesMessage;
125 type Output = BytecodeOutcome;
126
127 fn verify(self, peer_id: PeerId, response: SnapResponse) -> Result<Self::Output, RequestError> {
128 let SnapResponse::ByteCodes(response) = response else {
129 debug!(target: "downloaders::snap", "Expected byte codes response");
130 return Err(RequestError::BadResponse)
131 };
132 if response.request_id != self.request_id {
133 debug!(
134 target: "downloaders::snap",
135 expected = self.request_id,
136 got = response.request_id,
137 "Byte codes response id mismatch"
138 );
139 return Err(RequestError::BadResponse)
140 }
141 if response.codes.len() > self.hashes.len() {
142 debug!(
143 target: "downloaders::snap",
144 requested = self.hashes.len(),
145 got = response.codes.len(),
146 "Byte codes response is longer than the request"
147 );
148 return Err(RequestError::BadResponse)
149 }
150 if response.codes.is_empty() {
152 return Ok(BytecodeOutcome::Unavailable { peer_id })
153 }
154
155 let mut remaining = self.hashes.as_slice();
158 let mut codes = Vec::with_capacity(response.codes.len());
159 for code in response.codes {
160 let hash = keccak256(&code);
161 let Some(offset) = remaining.iter().position(|requested| *requested == hash) else {
162 debug!(target: "downloaders::snap", %hash, "Unrequested or out-of-order bytecode");
163 return Err(RequestError::BadResponse)
164 };
165 remaining = &remaining[offset + 1..];
166 codes.push((hash, code));
167 }
168
169 Ok(BytecodeOutcome::Verified(VerifiedBytecode { peer_id, codes }))
170 }
171}
172
173#[cfg(test)]
174mod tests {
175 use super::{
176 super::{request::MAX_RETRIES, test_utils::TestSnapClient},
177 *,
178 };
179 use reth_eth_wire_types::snap::{AccountRangeMessage, ByteCodesMessage};
180 use reth_network_p2p::{error::PeerRequestResult, priority::Priority};
181 use reth_network_peers::WithPeerId;
182 use std::sync::Arc;
183
184 fn code(byte: u8) -> Bytes {
185 Bytes::from(vec![byte; 4])
186 }
187
188 fn request(codes: &[Bytes]) -> GetByteCodesMessage {
189 GetByteCodesMessage {
190 request_id: 1,
191 hashes: codes.iter().map(keccak256).collect(),
192 response_bytes: 512 * 1024,
193 }
194 }
195
196 fn response(
197 peer: PeerId,
198 request_id: u64,
199 codes: Vec<Bytes>,
200 ) -> PeerRequestResult<SnapResponse> {
201 Ok(WithPeerId::new(peer, SnapResponse::ByteCodes(ByteCodesMessage { request_id, codes })))
202 }
203
204 fn always(
206 peer: PeerId,
207 request_id: u64,
208 codes: Vec<Bytes>,
209 ) -> impl Iterator<Item = PeerRequestResult<SnapResponse>> {
210 std::iter::repeat_with(move || response(peer, request_id, codes.clone()))
211 .take(usize::from(MAX_RETRIES) + 1)
212 }
213
214 fn downloader(
215 client: Arc<TestSnapClient>,
216 request: GetByteCodesMessage,
217 ) -> Result<BytecodeDownloader<Arc<TestSnapClient>>, InvalidBytecodeRequest> {
218 BytecodeDownloader::new(client, request, Runtime::test())
219 }
220
221 fn verified(outcome: BytecodeOutcome) -> VerifiedBytecode {
222 match outcome {
223 BytecodeOutcome::Verified(verified) => verified,
224 BytecodeOutcome::Unavailable { .. } => panic!("expected verified code"),
225 }
226 }
227
228 #[tokio::test]
229 async fn every_requested_code_is_authenticated_by_its_hash() {
230 let codes = vec![code(1), code(2), code(3)];
231 let client = Arc::new(TestSnapClient::new([response(PeerId::random(), 1, codes.clone())]));
232
233 let outcome = downloader(Arc::clone(&client), request(&codes)).unwrap().await.unwrap();
234
235 let verified = verified(outcome);
236 assert_eq!(
237 verified.codes(),
238 codes.iter().map(|code| (keccak256(code), code.clone())).collect::<Vec<_>>()
239 );
240 assert!(verified.missing(&request(&codes).hashes).next().is_none());
241 assert!(client.reported().is_empty());
242 }
243
244 #[tokio::test]
245 async fn an_ordered_subsequence_reports_the_omitted_hashes() {
246 let codes = vec![code(1), code(2), code(3)];
247 let request = request(&codes);
248 let served = vec![codes[0].clone(), codes[2].clone()];
250 let peer = PeerId::random();
251 let client = Arc::new(TestSnapClient::new([response(peer, 1, served)]));
252
253 let outcome = downloader(Arc::clone(&client), request.clone()).unwrap().await.unwrap();
254
255 let verified = verified(outcome);
256 assert_eq!(verified.peer_id(), peer);
257 assert_eq!(
258 verified.codes().iter().map(|(hash, _)| *hash).collect::<Vec<_>>(),
259 [keccak256(&codes[0]), keccak256(&codes[2])]
260 );
261 assert_eq!(verified.missing(&request.hashes).collect::<Vec<_>>(), [keccak256(&codes[1])]);
262 assert!(client.reported().is_empty());
263 }
264
265 #[tokio::test]
266 async fn an_empty_response_is_unavailable_and_not_a_peer_fault() {
267 let codes = vec![code(1)];
268 let peer = PeerId::random();
269 let client = Arc::new(TestSnapClient::new([response(peer, 1, Vec::new())]));
270
271 let outcome = downloader(Arc::clone(&client), request(&codes)).unwrap().await.unwrap();
272
273 assert_eq!(outcome, BytecodeOutcome::Unavailable { peer_id: peer });
274 assert!(client.reported().is_empty());
275 }
276
277 #[tokio::test]
278 async fn unrequested_code_is_reported_and_retried_against_another_peer() {
279 let codes = vec![code(1)];
280 let bad_peer = PeerId::random();
281 let client = Arc::new(TestSnapClient::new([
282 response(bad_peer, 1, vec![code(9)]),
283 response(PeerId::random(), 1, codes.clone()),
284 ]));
285
286 let outcome = downloader(Arc::clone(&client), request(&codes)).unwrap().await.unwrap();
287
288 assert_eq!(verified(outcome).codes().len(), 1);
289 assert_eq!(*client.reported(), [bad_peer]);
290 assert_eq!(*client.priorities(), [Priority::Normal, Priority::High]);
291 }
292
293 #[tokio::test]
294 async fn out_of_order_repeated_and_oversized_responses_are_rejected() {
295 let codes = vec![code(1), code(2)];
296 let peer = PeerId::random();
297 for served in [
298 vec![codes[1].clone(), codes[0].clone()],
300 vec![codes[0].clone(), codes[0].clone()],
302 vec![codes[0].clone(), codes[1].clone(), code(3)],
304 ] {
305 let client = Arc::new(TestSnapClient::new(always(peer, 1, served)));
306
307 let error =
308 downloader(Arc::clone(&client), request(&codes)).unwrap().await.unwrap_err();
309
310 assert_eq!(error, RequestError::BadResponse);
311 assert_eq!(client.reported().len(), usize::from(MAX_RETRIES) + 1);
312 }
313 }
314
315 #[tokio::test]
316 async fn a_response_for_another_request_id_is_rejected() {
317 let codes = vec![code(1)];
318 let peer = PeerId::random();
319 let client = Arc::new(TestSnapClient::new(always(peer, 7, codes.clone())));
320
321 let error = downloader(Arc::clone(&client), request(&codes)).unwrap().await.unwrap_err();
322
323 assert_eq!(error, RequestError::BadResponse);
324 assert_eq!(client.reported().len(), usize::from(MAX_RETRIES) + 1);
325 }
326
327 #[tokio::test]
328 async fn a_wrong_response_type_exhausts_the_retry_budget() {
329 let codes = vec![code(1)];
330 let peers = [PeerId::random(), PeerId::random(), PeerId::random()];
331 let responses = peers.map(|peer| {
332 Ok(WithPeerId::new(
333 peer,
334 SnapResponse::AccountRange(AccountRangeMessage {
335 request_id: 1,
336 accounts: Vec::new(),
337 proof: Vec::new(),
338 }),
339 ))
340 });
341 let client = Arc::new(TestSnapClient::new(responses));
342
343 let error = downloader(Arc::clone(&client), request(&codes)).unwrap().await.unwrap_err();
344
345 assert_eq!(error, RequestError::BadResponse);
346 assert_eq!(*client.reported(), peers);
347 assert_eq!(*client.priorities(), [Priority::Normal, Priority::High, Priority::High]);
348 }
349
350 #[test]
351 fn a_request_without_hashes_is_rejected_before_submission() {
352 let client = Arc::new(TestSnapClient::new(std::iter::empty()));
353 let mut empty = request(&[code(1)]);
354 empty.hashes.clear();
355
356 assert_eq!(
357 downloader(Arc::clone(&client), empty).unwrap_err(),
358 InvalidBytecodeRequest::NoHashes
359 );
360 assert!(client.priorities().is_empty());
361 }
362}