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