1use super::request::{SnapVerifier, VerifyingRequest};
8use alloy_consensus::BlockHeader;
9use alloy_eips::eip7928::bal::{DecodedBal, RawBal};
10use alloy_primitives::{Bytes, Sealable, B256};
11use futures::Future;
12use reth_eth_wire_types::snap::{BlockAccessListsMessage, GetBlockAccessListsMessage};
13use reth_network_p2p::{
14 error::RequestError,
15 snap::client::{SnapClient, SnapResponse},
16};
17use reth_network_peers::PeerId;
18use reth_primitives_traits::SealedHeader;
19use reth_tasks::Runtime;
20use std::{
21 pin::Pin,
22 task::{Context, Poll},
23};
24use tracing::debug;
25
26#[derive(Debug)]
31pub struct BlockAccessListDownloader<C: SnapClient>(VerifyingRequest<C, BlockAccessListVerifier>);
32
33impl<C: SnapClient> BlockAccessListDownloader<C> {
34 pub fn new<H: BlockHeader + Sealable>(
39 client: C,
40 request: GetBlockAccessListsMessage,
41 headers: &[SealedHeader<H>],
42 runtime: Runtime,
43 ) -> Result<Self, InvalidBlockAccessListRequest> {
44 for (index, (requested, header)) in request.block_hashes.iter().zip(headers).enumerate() {
45 if *requested != header.hash() {
46 return Err(InvalidBlockAccessListRequest::HashMismatch {
47 index,
48 requested: *requested,
49 supplied: header.hash(),
50 })
51 }
52 }
53 let access_list_hashes =
54 headers.iter().map(|header| header.block_access_list_hash()).collect();
55 Self::with_access_list_hashes(client, request, access_list_hashes, runtime)
56 }
57
58 pub fn with_access_list_hashes(
61 client: C,
62 request: GetBlockAccessListsMessage,
63 access_list_hashes: Vec<Option<B256>>,
64 runtime: Runtime,
65 ) -> Result<Self, InvalidBlockAccessListRequest> {
66 if request.block_hashes.is_empty() {
67 return Err(InvalidBlockAccessListRequest::NoBlocks)
68 }
69 if request.block_hashes.len() != access_list_hashes.len() {
70 return Err(InvalidBlockAccessListRequest::AccessListHashCount {
71 requested: request.block_hashes.len(),
72 supplied: access_list_hashes.len(),
73 })
74 }
75 let mut blocks = Vec::with_capacity(access_list_hashes.len());
78 for (index, (requested, access_list_hash)) in
79 request.block_hashes.iter().zip(access_list_hashes).enumerate()
80 {
81 let Some(access_list_hash) = access_list_hash else {
82 return Err(InvalidBlockAccessListRequest::MissingAccessListHash {
83 index,
84 block_hash: *requested,
85 })
86 };
87 blocks.push((*requested, access_list_hash));
88 }
89 let verifier = BlockAccessListVerifier {
90 request_id: request.request_id,
91 response_bytes: request.response_bytes,
92 blocks,
93 };
94 Ok(Self(VerifyingRequest::new(client, request, verifier, runtime)))
95 }
96}
97
98impl<C> Future for BlockAccessListDownloader<C>
99where
100 C: SnapClient + Unpin,
101{
102 type Output = Result<BlockAccessListOutcome, RequestError>;
103
104 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
105 self.get_mut().0.poll_verified(cx)
106 }
107}
108
109#[derive(Clone, Debug, PartialEq, Eq)]
111pub enum BlockAccessListOutcome {
112 Unavailable {
114 peer_id: PeerId,
116 },
117 Verified(VerifiedBlockAccessLists),
119}
120
121#[derive(Clone, Debug, PartialEq, Eq)]
123pub struct VerifiedBlockAccessLists {
124 peer_id: PeerId,
126 block_access_lists: Vec<(B256, Option<DecodedBal>)>,
128 missing: Vec<B256>,
130 response_bytes: u64,
132}
133
134impl VerifiedBlockAccessLists {
135 pub const fn peer_id(&self) -> PeerId {
137 self.peer_id
138 }
139
140 pub fn block_access_lists(&self) -> &[(B256, Option<DecodedBal>)] {
142 &self.block_access_lists
143 }
144
145 pub fn into_block_access_lists(self) -> Vec<(B256, Option<DecodedBal>)> {
147 self.block_access_lists
148 }
149
150 pub fn follow_up(&self, request_id: u64) -> Option<GetBlockAccessListsMessage> {
153 (!self.missing.is_empty()).then(|| GetBlockAccessListsMessage {
154 request_id,
155 block_hashes: self.missing.clone(),
156 response_bytes: self.response_bytes,
157 })
158 }
159
160 pub fn missing(&self) -> &[B256] {
163 &self.missing
164 }
165}
166
167#[derive(Clone, Copy, Debug, PartialEq, Eq, thiserror::Error)]
169pub enum InvalidBlockAccessListRequest {
170 #[error("block access list request has no block hashes")]
172 NoBlocks,
173 #[error("requested {requested} block access lists but supplied {supplied} hashes")]
175 AccessListHashCount {
176 requested: usize,
178 supplied: usize,
180 },
181 #[error("requested block {requested} at index {index} but header is for {supplied}")]
183 HashMismatch {
184 index: usize,
186 requested: B256,
188 supplied: B256,
190 },
191 #[error("header for block {block_hash} at index {index} has no block access list hash")]
194 MissingAccessListHash {
195 index: usize,
197 block_hash: B256,
199 },
200}
201
202#[derive(Clone, Debug)]
207struct BlockAccessListVerifier {
208 request_id: u64,
210 response_bytes: u64,
212 blocks: Vec<(B256, B256)>,
214}
215
216impl BlockAccessListVerifier {
217 fn validate_response(
219 &self,
220 response: SnapResponse,
221 ) -> Result<BlockAccessListsMessage, RequestError> {
222 let SnapResponse::BlockAccessLists(response) = response else {
223 debug!(target: "downloaders::snap", "Expected block access lists response");
224 return Err(RequestError::BadResponse)
225 };
226 if response.request_id != self.request_id {
227 debug!(
228 target: "downloaders::snap",
229 expected = self.request_id,
230 got = response.request_id,
231 "Block access lists response id mismatch"
232 );
233 return Err(RequestError::BadResponse)
234 }
235 if response.block_access_lists.0.len() > self.blocks.len() {
236 debug!(
237 target: "downloaders::snap",
238 requested = self.blocks.len(),
239 got = response.block_access_lists.0.len(),
240 "Block access lists response is longer than the request"
241 );
242 return Err(RequestError::BadResponse)
243 }
244
245 Ok(response)
246 }
247
248 fn authenticate_entries(
250 &self,
251 entries: Vec<Option<Bytes>>,
252 ) -> Result<Vec<(B256, Option<DecodedBal>)>, RequestError> {
253 let mut block_access_lists = Vec::with_capacity(entries.len());
254 for (index, entry) in entries.into_iter().enumerate() {
255 let (block_hash, access_list_hash) = self.blocks[index];
256 let Some(raw) = entry else {
258 block_access_lists.push((block_hash, None));
259 continue
260 };
261 let raw = RawBal::new(raw);
264 raw.ensure_hash(access_list_hash).map_err(|error| {
265 debug!(
266 target: "downloaders::snap",
267 %block_hash,
268 expected = %error.expected,
269 got = %error.computed,
270 "Block access list does not match its header hash"
271 );
272 RequestError::BadResponse
273 })?;
274 let decoded = DecodedBal::from_raw_bal(raw).map_err(|error| {
275 debug!(target: "downloaders::snap", %block_hash, %error, "Invalid block access list");
276 RequestError::BadResponse
277 })?;
278 block_access_lists.push((block_hash, Some(decoded)));
279 }
280
281 Ok(block_access_lists)
282 }
283}
284
285impl SnapVerifier for BlockAccessListVerifier {
286 type Request = GetBlockAccessListsMessage;
287 type Output = BlockAccessListOutcome;
288
289 fn verify(self, peer_id: PeerId, response: SnapResponse) -> Result<Self::Output, RequestError> {
291 let entries = self.validate_response(response)?.block_access_lists.0;
292 if entries.is_empty() {
294 return Ok(BlockAccessListOutcome::Unavailable { peer_id })
295 }
296
297 let block_access_lists = self.authenticate_entries(entries)?;
298 let missing = block_access_lists
301 .iter()
302 .filter_map(|(block_hash, list)| list.is_none().then_some(*block_hash))
303 .chain(
304 self.blocks[block_access_lists.len()..].iter().map(|(block_hash, _)| *block_hash),
305 )
306 .collect::<Vec<_>>();
307 if block_access_lists.len() == self.blocks.len() && missing.len() == self.blocks.len() {
310 return Ok(BlockAccessListOutcome::Unavailable { peer_id })
311 }
312
313 Ok(BlockAccessListOutcome::Verified(VerifiedBlockAccessLists {
314 peer_id,
315 block_access_lists,
316 missing,
317 response_bytes: self.response_bytes,
318 }))
319 }
320}
321
322#[cfg(test)]
323mod tests {
324 use super::{
325 super::{request::MAX_RETRIES, test_utils::TestSnapClient},
326 *,
327 };
328 use alloy_consensus::Header;
329 use alloy_eips::eip7928::bal::Bal;
330 use alloy_primitives::Bytes;
331 use reth_eth_wire_types::{
332 snap::{BlockAccessListsMessage, ByteCodesMessage},
333 BlockAccessLists,
334 };
335 use reth_network_p2p::{error::PeerRequestResult, priority::Priority};
336 use reth_network_peers::WithPeerId;
337 use std::sync::Arc;
338
339 fn bal() -> Bytes {
340 Bytes::from(alloy_rlp::encode(Bal::default()))
341 }
342
343 fn access_list_hash(raw: Bytes) -> B256 {
344 DecodedBal::from_rlp_bytes(raw).expect("test bal decodes").hash()
345 }
346
347 fn headers(entries: &[Option<Bytes>]) -> Vec<SealedHeader<Header>> {
350 entries
351 .iter()
352 .enumerate()
353 .map(|(index, raw)| {
354 let header = Header {
355 block_access_list_hash: Some(
356 raw.clone().map_or(B256::repeat_byte(0xee), access_list_hash),
357 ),
358 ..Default::default()
359 };
360 SealedHeader::new(header, B256::repeat_byte(index as u8 + 1))
361 })
362 .collect()
363 }
364
365 fn request(headers: &[SealedHeader<Header>]) -> GetBlockAccessListsMessage {
366 GetBlockAccessListsMessage {
367 request_id: 1,
368 block_hashes: headers.iter().map(SealedHeader::hash).collect(),
369 response_bytes: 512 * 1024,
370 }
371 }
372
373 fn response(
374 peer: PeerId,
375 request_id: u64,
376 entries: Vec<Option<Bytes>>,
377 ) -> PeerRequestResult<SnapResponse> {
378 Ok(WithPeerId::new(
379 peer,
380 SnapResponse::BlockAccessLists(BlockAccessListsMessage {
381 request_id,
382 block_access_lists: BlockAccessLists(entries),
383 }),
384 ))
385 }
386
387 fn unverifiable(peer: PeerId) -> PeerRequestResult<SnapResponse> {
389 Ok(WithPeerId::new(
390 peer,
391 SnapResponse::ByteCodes(ByteCodesMessage { request_id: 1, codes: Vec::new() }),
392 ))
393 }
394
395 fn always(
397 peer: PeerId,
398 request_id: u64,
399 entries: Vec<Option<Bytes>>,
400 ) -> impl Iterator<Item = PeerRequestResult<SnapResponse>> {
401 std::iter::repeat_with(move || response(peer, request_id, entries.clone()))
402 .take(usize::from(MAX_RETRIES) + 1)
403 }
404
405 fn downloader(
406 client: Arc<TestSnapClient>,
407 request: GetBlockAccessListsMessage,
408 headers: &[SealedHeader<Header>],
409 ) -> Result<BlockAccessListDownloader<Arc<TestSnapClient>>, InvalidBlockAccessListRequest> {
410 BlockAccessListDownloader::new(client, request, headers, Runtime::test())
411 }
412
413 fn verified(outcome: BlockAccessListOutcome) -> VerifiedBlockAccessLists {
414 match outcome {
415 BlockAccessListOutcome::Verified(verified) => verified,
416 BlockAccessListOutcome::Unavailable { .. } => panic!("expected verified lists"),
417 }
418 }
419
420 #[tokio::test]
421 async fn present_and_omitted_entries_keep_their_positions() {
422 let entries = vec![Some(bal()), None, Some(bal())];
423 let headers = headers(&entries);
424 let peer = PeerId::random();
425 let client = Arc::new(TestSnapClient::new([response(peer, 1, entries)]));
426
427 let outcome =
428 downloader(Arc::clone(&client), request(&headers), &headers).unwrap().await.unwrap();
429
430 let verified = verified(outcome);
431 assert_eq!(verified.peer_id(), peer);
432 assert_eq!(
433 verified
434 .block_access_lists()
435 .iter()
436 .map(|(block_hash, entry)| (*block_hash, entry.as_ref().map(DecodedBal::hash)))
437 .collect::<Vec<_>>(),
438 [
439 (headers[0].hash(), Some(access_list_hash(bal()))),
440 (headers[1].hash(), None),
441 (headers[2].hash(), Some(access_list_hash(bal()))),
442 ]
443 );
444 assert_eq!(verified.missing(), [headers[1].hash()]);
446 assert_eq!(
447 verified.follow_up(2),
448 Some(GetBlockAccessListsMessage {
449 request_id: 2,
450 block_hashes: vec![headers[1].hash()],
451 response_bytes: request(&headers).response_bytes,
452 })
453 );
454 assert!(client.reported().is_empty());
455 }
456
457 #[tokio::test]
458 async fn a_complete_response_needs_no_follow_up() {
459 let entries = vec![Some(bal()), Some(bal())];
460 let headers = headers(&entries);
461 let client = Arc::new(TestSnapClient::new([response(PeerId::random(), 1, entries)]));
462
463 let outcome =
464 downloader(Arc::clone(&client), request(&headers), &headers).unwrap().await.unwrap();
465
466 let verified = verified(outcome);
467 assert!(verified.missing().is_empty());
468 assert_eq!(verified.follow_up(2), None);
469 }
470
471 #[tokio::test]
472 async fn a_truncated_response_leaves_the_rest_of_the_request_unanswered() {
473 let entries = vec![Some(bal()), Some(bal()), Some(bal())];
474 let headers = headers(&entries);
475 let client =
476 Arc::new(TestSnapClient::new([response(PeerId::random(), 1, entries[..2].to_vec())]));
477
478 let outcome =
479 downloader(Arc::clone(&client), request(&headers), &headers).unwrap().await.unwrap();
480
481 let verified = verified(outcome);
482 assert_eq!(verified.missing(), [headers[2].hash()]);
483 assert_eq!(verified.block_access_lists().len(), 2);
484 assert_eq!(verified.block_access_lists()[0].0, headers[0].hash());
485 assert_eq!(verified.block_access_lists()[1].0, headers[1].hash());
486 assert!(client.reported().is_empty());
487 }
488
489 #[tokio::test]
490 async fn an_empty_or_complete_response_holding_no_lists_is_unavailable() {
491 let headers = headers(&[Some(bal()), Some(bal())]);
492 let peer = PeerId::random();
493 for entries in [Vec::new(), vec![None, None]] {
494 let client = Arc::new(TestSnapClient::new([response(peer, 1, entries)]));
495
496 let outcome = downloader(Arc::clone(&client), request(&headers), &headers)
497 .unwrap()
498 .await
499 .unwrap();
500
501 assert_eq!(outcome, BlockAccessListOutcome::Unavailable { peer_id: peer });
502 assert!(client.reported().is_empty());
503 }
504 }
505
506 #[tokio::test]
507 async fn a_truncated_response_after_an_omission_stays_resumable() {
508 let headers = headers(&[Some(bal()), Some(bal())]);
509 let peer = PeerId::random();
510 let client = Arc::new(TestSnapClient::new([response(peer, 1, vec![None])]));
511
512 let outcome =
513 downloader(Arc::clone(&client), request(&headers), &headers).unwrap().await.unwrap();
514
515 let verified = verified(outcome);
516 assert_eq!(verified.missing(), [headers[0].hash(), headers[1].hash()]);
517 assert_eq!(
518 verified.follow_up(2),
519 Some(GetBlockAccessListsMessage {
520 request_id: 2,
521 block_hashes: headers.iter().map(SealedHeader::hash).collect(),
522 response_bytes: request(&headers).response_bytes,
523 })
524 );
525 assert!(client.reported().is_empty());
526 }
527
528 #[tokio::test]
529 async fn an_invalid_list_is_reported_and_retried() {
530 let entries = vec![Some(bal())];
531 let headers = headers(&entries);
532 let bad_peer = PeerId::random();
533 let client = Arc::new(TestSnapClient::new([
534 response(bad_peer, 1, vec![Some(Bytes::from_static(&[0xff, 0xff]))]),
535 response(PeerId::random(), 1, entries),
536 ]));
537
538 let outcome =
539 downloader(Arc::clone(&client), request(&headers), &headers).unwrap().await.unwrap();
540
541 assert_eq!(verified(outcome).block_access_lists().len(), 1);
542 assert_eq!(*client.reported(), [bad_peer]);
543 assert_eq!(*client.priorities(), [Priority::Normal, Priority::High]);
544 }
545
546 #[tokio::test]
547 async fn a_list_that_misses_its_header_hash_is_rejected() {
548 let headers = vec![SealedHeader::new(
550 Header { block_access_list_hash: Some(B256::repeat_byte(0xab)), ..Default::default() },
551 B256::repeat_byte(1),
552 )];
553 let peer = PeerId::random();
554 let client = Arc::new(TestSnapClient::new(always(peer, 1, vec![Some(bal())])));
555
556 let error = downloader(Arc::clone(&client), request(&headers), &headers)
557 .unwrap()
558 .await
559 .unwrap_err();
560
561 assert_eq!(error, RequestError::BadResponse);
562 assert_eq!(client.reported().len(), usize::from(MAX_RETRIES) + 1);
563 }
564
565 #[tokio::test]
566 async fn dropping_an_entry_instead_of_omitting_it_in_place_is_rejected() {
567 let headers = headers(&[None, Some(bal())]);
570 let peer = PeerId::random();
571 let client = Arc::new(TestSnapClient::new(always(peer, 1, vec![Some(bal())])));
572
573 let error = downloader(Arc::clone(&client), request(&headers), &headers)
574 .unwrap()
575 .await
576 .unwrap_err();
577
578 assert_eq!(error, RequestError::BadResponse);
579 assert_eq!(client.reported().len(), usize::from(MAX_RETRIES) + 1);
580 }
581
582 #[tokio::test]
583 async fn malformed_wrong_id_and_oversized_responses_are_rejected() {
584 let headers = headers(&[Some(bal())]);
585 let peer = PeerId::random();
586 for (request_id, entries) in [
587 (1, vec![Some(Bytes::from_static(&[0xff, 0xff]))]),
589 (7, vec![Some(bal())]),
591 (1, vec![Some(bal()), Some(bal())]),
593 ] {
594 let client = Arc::new(TestSnapClient::new(always(peer, request_id, entries)));
595
596 let error = downloader(Arc::clone(&client), request(&headers), &headers)
597 .unwrap()
598 .await
599 .unwrap_err();
600
601 assert_eq!(error, RequestError::BadResponse);
602 assert_eq!(client.reported().len(), usize::from(MAX_RETRIES) + 1);
603 }
604 }
605
606 #[tokio::test]
607 async fn a_wrong_response_type_exhausts_the_retry_budget() {
608 let headers = headers(&[Some(bal())]);
609 let peers = [PeerId::random(), PeerId::random(), PeerId::random()];
610 let client = Arc::new(TestSnapClient::new(peers.map(unverifiable)));
611
612 let error = downloader(Arc::clone(&client), request(&headers), &headers)
613 .unwrap()
614 .await
615 .unwrap_err();
616
617 assert_eq!(error, RequestError::BadResponse);
618 assert_eq!(*client.reported(), peers);
619 assert_eq!(*client.priorities(), [Priority::Normal, Priority::High, Priority::High]);
620 }
621
622 #[test]
623 fn requests_that_cannot_be_authenticated_are_rejected_before_submission() {
624 let headers = headers(&[Some(bal())]);
625 let client = Arc::new(TestSnapClient::new(std::iter::empty()));
626
627 let mut empty = request(&headers);
628 empty.block_hashes.clear();
629 assert_eq!(
630 downloader(Arc::clone(&client), empty, &[]).unwrap_err(),
631 InvalidBlockAccessListRequest::NoBlocks
632 );
633
634 assert!(matches!(
635 downloader(Arc::clone(&client), request(&headers), &[]).unwrap_err(),
636 InvalidBlockAccessListRequest::AccessListHashCount { requested: 1, supplied: 0 }
637 ));
638
639 let mut wrong_block = request(&headers);
640 wrong_block.block_hashes[0] = B256::repeat_byte(0x99);
641 assert!(matches!(
642 downloader(Arc::clone(&client), wrong_block, &headers).unwrap_err(),
643 InvalidBlockAccessListRequest::HashMismatch { index: 0, .. }
644 ));
645
646 let unhashed = vec![SealedHeader::new(Header::default(), headers[0].hash())];
647 assert!(matches!(
648 downloader(Arc::clone(&client), request(&headers), &unhashed).unwrap_err(),
649 InvalidBlockAccessListRequest::MissingAccessListHash { index: 0, .. }
650 ));
651
652 assert!(client.priorities().is_empty());
654 }
655}