Skip to main content

reth_network_p2p/
full_block.rs

1use super::headers::client::HeadersRequest;
2use crate::{
3    block_access_lists::client::{BalRequirement, BlockAccessListsClient},
4    bodies::client::{BodiesClient, SingleBodyRequest},
5    download::DownloadClient,
6    error::PeerRequestResult,
7    headers::client::{HeadersClient, SingleHeaderRequest},
8    priority::Priority,
9    BlockClient,
10};
11use alloy_consensus::BlockHeader;
12use alloy_eip7928::bal::RawBal;
13use alloy_primitives::{Bytes, Sealable, B256};
14use core::marker::PhantomData;
15use futures::FutureExt;
16use reth_consensus::Consensus;
17use reth_eth_wire_types::{BlockAccessLists, EthNetworkPrimitives, NetworkPrimitives};
18use reth_network_peers::{PeerId, WithPeerId};
19use reth_primitives_traits::{Block, SealedBlock, SealedBlockWith, SealedHeader};
20use std::{
21    cmp::Reverse,
22    collections::{HashMap, VecDeque},
23    fmt::Debug,
24    hash::Hash,
25    ops::RangeInclusive,
26    pin::Pin,
27    sync::Arc,
28    task::{ready, Context, Poll},
29};
30use tracing::{debug, trace};
31
32/// A sealed block with optional validated raw block access-list data.
33pub type SealedBlockWithAccessList<B> = SealedBlockWith<B, Option<RawBal>>;
34
35/// A Client that can fetch full blocks from the network.
36#[derive(Debug, Clone)]
37pub struct FullBlockClient<Client>
38where
39    Client: BlockClient,
40{
41    client: Client,
42    consensus: Arc<dyn Consensus<Client::Block>>,
43}
44
45impl<Client> FullBlockClient<Client>
46where
47    Client: BlockClient,
48{
49    /// Creates a new instance of `FullBlockClient`.
50    pub fn new(client: Client, consensus: Arc<dyn Consensus<Client::Block>>) -> Self {
51        Self { client, consensus }
52    }
53
54    /// Returns a client with Test consensus
55    #[cfg(any(test, feature = "test-utils"))]
56    pub fn test_client(client: Client) -> Self {
57        Self::new(client, Arc::new(reth_consensus::test_utils::TestConsensus::default()))
58    }
59}
60
61impl<Client> FullBlockClient<Client>
62where
63    Client: BlockClient,
64{
65    /// Returns a future that fetches the [`SealedBlock`] for the given hash.
66    ///
67    /// Note: this future is cancel safe
68    ///
69    /// Caution: This does no validation of body (transactions) response but guarantees that the
70    /// [`SealedHeader`] matches the requested hash.
71    pub fn get_full_block(&self, hash: B256) -> FetchFullBlockFuture<Client> {
72        FetchFullBlockFuture::new(self.client.clone(), self.consensus.clone(), hash)
73    }
74
75    /// Returns a future that fetches [`SealedBlock`]s for the given hash and count.
76    ///
77    /// Note: this future is cancel safe.
78    ///
79    /// Caution: This does no validation of body (transactions) responses but guarantees that
80    /// the headers form a valid range starting at the requested hash, and that the number of
81    /// headers and bodies received matches the requested limit.
82    ///
83    /// The returned future yields bodies in falling order, i.e. with descending block numbers.
84    pub fn get_full_block_range(
85        &self,
86        hash: B256,
87        count: u64,
88    ) -> FetchFullBlockRangeFuture<Client> {
89        let client = self.client.clone();
90        FetchFullBlockRangeFuture {
91            start_hash: hash,
92            count,
93            request: FullBlockRangeRequest {
94                headers: Some(client.get_headers(HeadersRequest::falling(hash.into(), count))),
95                bodies: None,
96            },
97            client,
98            headers: None,
99            pending_headers: VecDeque::new(),
100            bodies: HashMap::default(),
101            consensus: Arc::clone(&self.consensus),
102        }
103    }
104}
105
106impl<Client> FullBlockClient<Client>
107where
108    Client: BlockClient + BlockAccessListsClient,
109{
110    /// Returns a future that fetches the [`SealedBlock`] and optionally its block access list
111    /// for the given hash.
112    ///
113    /// Note: this future is cancel safe
114    ///
115    /// Caution: This does no validation of body (transactions) response but guarantees that the
116    /// [`SealedHeader`] matches the requested hash.
117    pub fn get_full_block_with_access_lists(
118        &self,
119        hash: B256,
120    ) -> FetchFullBlockWithBalFuture<Client> {
121        self.get_full_block_with_access_lists_with_requirement(hash, BalRequirement::default())
122    }
123
124    /// Returns a future that fetches the [`SealedBlock`] and optionally its block access list
125    /// for the given hash using the requested BAL availability policy.
126    ///
127    /// Note: this future is cancel safe
128    ///
129    /// Caution: This does no validation of body (transactions) response but guarantees that the
130    /// [`SealedHeader`] matches the requested hash.
131    pub fn get_full_block_with_access_lists_with_requirement(
132        &self,
133        hash: B256,
134        requirement: BalRequirement,
135    ) -> FetchFullBlockWithBalFuture<Client> {
136        let client = self.client.clone();
137        FetchFullBlockWithBalFuture {
138            block: FetchFullBlockFuture::new(client.clone(), self.consensus.clone(), hash),
139            block_result: None,
140            bal_request_state: BalRequestState::Pending(
141                client.get_block_access_lists_with_requirement(vec![hash], requirement),
142            ),
143        }
144    }
145
146    /// Returns a future that fetches [`SealedBlock`]s and optionally their block access lists for
147    /// the given hash and count.
148    ///
149    /// The block range is always the primary result. Access lists are requested after the block
150    /// range is downloaded. Each block contains access-list data when the optional BAL response
151    /// included the corresponding entry.
152    pub fn get_full_block_range_with_optional_access_lists(
153        &self,
154        hash: B256,
155        count: u64,
156    ) -> FetchFullBlockRangeWithBalFuture<Client> {
157        self.get_full_block_range_with_optional_access_lists_with_requirement(
158            hash,
159            count,
160            BalRequirement::default(),
161        )
162    }
163
164    /// Returns a future that fetches [`SealedBlock`]s and optionally their block access lists for
165    /// the given hash and count using the requested BAL availability policy.
166    ///
167    /// The block range is always the primary result. Access lists are requested after the block
168    /// range is downloaded. Each block contains access-list data when the optional BAL response
169    /// included the corresponding entry.
170    pub fn get_full_block_range_with_optional_access_lists_with_requirement(
171        &self,
172        hash: B256,
173        count: u64,
174        requirement: BalRequirement,
175    ) -> FetchFullBlockRangeWithBalFuture<Client> {
176        let client = self.client.clone();
177        FetchFullBlockRangeWithBalFuture {
178            blocks: self.get_full_block_range(hash, count),
179            client,
180            block_result: None,
181            access_lists: OptionalBlockAccessListsState::WaitingForBlocks { requirement },
182        }
183    }
184}
185
186/// A future that downloads a full block from the network.
187///
188/// This will attempt to fetch both the header and body for the given block hash at the same time.
189/// When both requests succeed, the future will yield the full block.
190#[must_use = "futures do nothing unless polled"]
191pub struct FetchFullBlockFuture<Client>
192where
193    Client: BlockClient,
194{
195    client: Client,
196    consensus: Arc<dyn Consensus<Client::Block>>,
197    hash: B256,
198    request: FullBlockRequest<Client>,
199    header: Option<SealedHeader<Client::Header>>,
200    body: Option<BodyResponse<Client::Body>>,
201}
202
203impl<Client> FetchFullBlockFuture<Client>
204where
205    Client: BlockClient,
206{
207    fn new(client: Client, consensus: Arc<dyn Consensus<Client::Block>>, hash: B256) -> Self {
208        Self {
209            hash,
210            consensus,
211            request: FullBlockRequest {
212                header: Some(client.get_header(hash.into())),
213                body: Some(client.get_block_body(hash)),
214            },
215            client,
216            header: None,
217            body: None,
218        }
219    }
220
221    /// Returns the hash of the block being requested.
222    pub const fn hash(&self) -> &B256 {
223        &self.hash
224    }
225
226    /// If the header request is already complete, this returns the block number
227    pub fn block_number(&self) -> Option<u64> {
228        self.header.as_ref().map(|h| h.number())
229    }
230
231    /// Returns the [`SealedBlock`] if the request is complete and valid.
232    fn take_block(&mut self) -> Option<SealedBlock<Client::Block>> {
233        if self.header.is_none() || self.body.is_none() {
234            return None
235        }
236
237        let header = self.header.take().unwrap();
238        let resp = self.body.take().unwrap();
239        match resp {
240            BodyResponse::Validated(body) => Some(SealedBlock::from_sealed_parts(header, body)),
241            BodyResponse::PendingValidation(resp) => {
242                // ensure the block is valid, else retry
243                if let Err(err) = self.consensus.validate_body_against_header(resp.data(), &header)
244                {
245                    debug!(target: "downloaders", %err, hash=?header.hash(), "Received wrong body");
246                    self.client.report_bad_message(resp.peer_id());
247                    self.header = Some(header);
248                    self.request.body = Some(self.client.get_block_body(self.hash));
249                    return None
250                }
251                Some(SealedBlock::from_sealed_parts(header, resp.into_data()))
252            }
253        }
254    }
255
256    fn on_block_response(&mut self, resp: WithPeerId<Client::Body>) {
257        if let Some(ref header) = self.header {
258            if let Err(err) = self.consensus.validate_body_against_header(resp.data(), header) {
259                debug!(target: "downloaders", %err, hash=?header.hash(), "Received wrong body");
260                self.client.report_bad_message(resp.peer_id());
261                return
262            }
263            self.body = Some(BodyResponse::Validated(resp.into_data()));
264            return
265        }
266        self.body = Some(BodyResponse::PendingValidation(resp));
267    }
268}
269
270impl<Client> Future for FetchFullBlockFuture<Client>
271where
272    Client: BlockClient<Header: BlockHeader + Sealable> + 'static,
273{
274    type Output = SealedBlock<Client::Block>;
275
276    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
277        let this = self.get_mut();
278
279        // preemptive yield point
280        let mut budget = 4;
281
282        loop {
283            match ready!(this.request.poll(cx)) {
284                ResponseResult::Header(res) => {
285                    match res {
286                        Ok(maybe_header) => {
287                            let (peer, maybe_header) =
288                                maybe_header.map(|h| h.map(SealedHeader::seal_slow)).split();
289                            if let Some(header) = maybe_header {
290                                if header.hash() == this.hash {
291                                    this.header = Some(header);
292                                } else {
293                                    debug!(target: "downloaders", expected=?this.hash, received=?header.hash(), "Received wrong header");
294                                    // received a different header than requested
295                                    this.client.report_bad_message(peer)
296                                }
297                            }
298                        }
299                        Err(err) => {
300                            debug!(target: "downloaders", %err, ?this.hash, "Header download failed");
301                        }
302                    }
303
304                    if this.header.is_none() {
305                        // received bad response
306                        this.request.header = Some(this.client.get_header(this.hash.into()));
307                    }
308                }
309                ResponseResult::Body(res) => {
310                    match res {
311                        Ok(maybe_body) => {
312                            if let Some(body) = maybe_body.transpose() {
313                                this.on_block_response(body);
314                            }
315                        }
316                        Err(err) => {
317                            debug!(target: "downloaders", %err, ?this.hash, "Body download failed");
318                        }
319                    }
320                    if this.body.is_none() {
321                        // received bad response
322                        this.request.body = Some(this.client.get_block_body(this.hash));
323                    }
324                }
325            }
326
327            if let Some(res) = this.take_block() {
328                return Poll::Ready(res)
329            }
330
331            // ensure we still have enough budget for another iteration
332            budget -= 1;
333            if budget == 0 {
334                // make sure we're woken up again
335                cx.waker().wake_by_ref();
336                return Poll::Pending
337            }
338        }
339    }
340}
341
342/// A future that downloads a full block and optionally its block access lists from the network.
343///
344/// This composes the existing full block downloader with a block access list request so the
345/// header/body logic stays centralized. Missing access lists do not block returning the full block.
346#[must_use = "futures do nothing unless polled"]
347pub struct FetchFullBlockWithBalFuture<Client>
348where
349    Client: BlockClient + BlockAccessListsClient,
350{
351    block: FetchFullBlockFuture<Client>,
352    block_result: Option<SealedBlock<Client::Block>>,
353    bal_request_state: BalRequestState<<Client as BlockAccessListsClient>::Output>,
354}
355
356impl<Client> FetchFullBlockWithBalFuture<Client>
357where
358    Client: BlockClient<Header: BlockHeader> + BlockAccessListsClient,
359{
360    /// Returns the hash of the block being requested.
361    pub const fn hash(&self) -> &B256 {
362        self.block.hash()
363    }
364}
365
366impl<Client> FetchFullBlockWithBalFuture<Client>
367where
368    Client: BlockClient<Header: BlockHeader + Sealable> + BlockAccessListsClient + 'static,
369{
370    /// If the header request is already complete, this returns the block number.
371    pub fn block_number(&self) -> Option<u64> {
372        self.block_result.as_ref().map(|block| block.number()).or_else(|| self.block.block_number())
373    }
374
375    /// Polls the optional BAL request once and records its final best-effort result.
376    ///
377    /// Exactly one returned BAL entry is preserved. Empty responses, malformed responses, and
378    /// request errors resolve to `None` instead of being retried, so a BAL failure cannot block
379    /// returning the downloaded block.
380    fn poll_bal_request(&mut self, cx: &mut Context<'_>) -> Poll<()> {
381        let res = match &mut self.bal_request_state {
382            BalRequestState::Pending(fut) => ready!(fut.poll_unpin(cx)),
383            BalRequestState::Ready(_) => return Poll::Ready(()),
384        };
385
386        match res {
387            Ok(bal) => {
388                let (peer, access_lists) = bal.split();
389                match access_lists.0.len() {
390                    0 => self.bal_request_state = BalRequestState::Ready(None),
391                    1 => {
392                        let bal = access_lists.0.into_iter().next().expect("len checked");
393                        self.bal_request_state =
394                            BalRequestState::Ready(Some(WithPeerId::new(peer, bal)));
395                    }
396                    received => {
397                        debug!(
398                            target: "downloaders",
399                            hash = ?self.block.hash(),
400                            expected = 1,
401                            received,
402                            "Received wrong access list response",
403                        );
404                        self.block.client.report_bad_message(peer);
405                        self.bal_request_state = BalRequestState::Ready(None);
406                    }
407                }
408            }
409            Err(err) => {
410                debug!(
411                    target: "downloaders",
412                    %err,
413                    hash = ?self.block.hash(),
414                    "Access list download failed",
415                );
416                self.bal_request_state = BalRequestState::Ready(None);
417            }
418        }
419
420        Poll::Ready(())
421    }
422
423    /// Returns the block once the block download and optional BAL lookup have both completed.
424    ///
425    /// The BAL lookup must be ready even when it resolved to `None`, which prevents a fast block
426    /// response from racing a still-pending BAL response and incorrectly dropping available BAL
427    /// data.
428    fn take_block_and_access_lists(&mut self) -> Option<SealedBlockWithAccessList<Client::Block>> {
429        let BalRequestState::Ready(bal) = &mut self.bal_request_state else { return None };
430        let block = self.block_result.take()?;
431        let raw_bal =
432            bal.take().and_then(|bal| match seal_block_access_list_for_block(&block, bal) {
433                Ok(raw_bal) => raw_bal,
434                Err(peer) => {
435                    self.block.client.report_bad_message(peer);
436                    None
437                }
438            });
439        Some(SealedBlockWith::new(block, raw_bal))
440    }
441}
442
443impl<Client> Future for FetchFullBlockWithBalFuture<Client>
444where
445    Client: BlockClient<Header: BlockHeader + Sealable> + BlockAccessListsClient + 'static,
446{
447    type Output = SealedBlockWithAccessList<Client::Block>;
448
449    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
450        let this = self.get_mut();
451
452        if this.block_result.is_none() &&
453            let Poll::Ready(block) = this.block.poll_unpin(cx)
454        {
455            this.block_result = Some(block);
456        }
457
458        ready!(this.poll_bal_request(cx));
459
460        if let Some(res) = this.take_block_and_access_lists() {
461            return Poll::Ready(res)
462        }
463
464        Poll::Pending
465    }
466}
467
468impl<Client> Debug for FetchFullBlockWithBalFuture<Client>
469where
470    Client: BlockClient<Header: BlockHeader> + BlockAccessListsClient,
471{
472    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
473        f.debug_struct("FetchFullBlockWithBalFuture")
474            .field("hash", &self.block.hash())
475            .field("block_ready", &self.block_result.is_some())
476            .field("bal_request_ready", &self.bal_request_state.is_ready())
477            .finish()
478    }
479}
480
481/// Tracks the BAL request and its completed result.
482enum BalRequestState<Req> {
483    Pending(Req),
484    Ready(Option<WithPeerId<Option<Bytes>>>),
485}
486
487impl<Req> BalRequestState<Req> {
488    const fn is_ready(&self) -> bool {
489        matches!(self, Self::Ready(_))
490    }
491}
492
493/// A future that downloads a range of full blocks and optionally their block access lists from the
494/// network.
495///
496/// This composes the existing full block range downloader with a follow-up optional block
497/// access-list request, so callers that only need blocks can keep using
498/// [`FetchFullBlockRangeFuture`].
499#[must_use = "futures do nothing unless polled"]
500#[expect(missing_debug_implementations)]
501pub struct FetchFullBlockRangeWithBalFuture<Client>
502where
503    Client: BlockClient + BlockAccessListsClient,
504{
505    blocks: FetchFullBlockRangeFuture<Client>,
506    client: Client,
507    block_result: Option<Vec<SealedBlock<Client::Block>>>,
508    access_lists: OptionalBlockAccessListsState<<Client as BlockAccessListsClient>::Output>,
509}
510
511impl<Client> FetchFullBlockRangeWithBalFuture<Client>
512where
513    Client: BlockClient<Header: Debug + BlockHeader + Sealable + Clone + Hash + Eq>
514        + BlockAccessListsClient,
515{
516    /// Returns the block hash the requested range starts at (inclusive).
517    pub const fn start_hash(&self) -> B256 {
518        self.blocks.start_hash()
519    }
520
521    /// Returns the number of requested blocks.
522    pub const fn count(&self) -> u64 {
523        self.blocks.count()
524    }
525
526    fn start_access_lists_request_if_possible(&mut self) {
527        let requirement = match &self.access_lists {
528            OptionalBlockAccessListsState::WaitingForBlocks { requirement } => *requirement,
529            OptionalBlockAccessListsState::Pending(_) | OptionalBlockAccessListsState::Ready(_) => {
530                return
531            }
532        };
533
534        // BALs are requested by block hash, so wait until the block range is fully assembled.
535        let Some(blocks) = self.block_result.as_ref() else { return };
536        let hashes = blocks.iter().map(|block| block.hash()).collect::<Vec<_>>();
537        self.access_lists = OptionalBlockAccessListsState::Pending(
538            self.client.get_block_access_lists_with_requirement(hashes, requirement),
539        );
540    }
541
542    /// Starts and polls the optional BAL request once, if it is ready to make progress.
543    fn poll_access_lists(&mut self, cx: &mut Context<'_>) {
544        self.start_access_lists_request_if_possible();
545
546        let poll = match &mut self.access_lists {
547            OptionalBlockAccessListsState::Pending(fut) => fut.poll_unpin(cx),
548            OptionalBlockAccessListsState::WaitingForBlocks { .. } |
549            OptionalBlockAccessListsState::Ready(_) => return,
550        };
551
552        match poll {
553            Poll::Pending => {}
554            Poll::Ready(Ok(access_lists)) => {
555                self.access_lists = OptionalBlockAccessListsState::Ready(Some(access_lists));
556            }
557            Poll::Ready(Err(err)) => {
558                debug!(
559                    target: "downloaders",
560                    %err,
561                    start_hash = ?self.blocks.start_hash(),
562                    "Access list range download failed",
563                );
564
565                // Optional BAL lookup is best-effort: missing eth/71 support or request failures
566                // should not block returning the downloaded block range.
567                self.access_lists = OptionalBlockAccessListsState::Ready(None);
568            }
569        }
570    }
571
572    /// Returns the block range once blocks and the optional BAL lookup are both complete.
573    fn take_response(&mut self) -> Option<Vec<SealedBlockWithAccessList<Client::Block>>> {
574        let OptionalBlockAccessListsState::Ready(access_lists) = &mut self.access_lists else {
575            return None
576        };
577
578        let blocks = self.block_result.take()?;
579
580        Some(seal_blocks_with_access_lists(&self.client, blocks, access_lists.take()))
581    }
582}
583
584impl<Client> Future for FetchFullBlockRangeWithBalFuture<Client>
585where
586    Client: BlockClient<Header: Debug + BlockHeader + Sealable + Clone + Hash + Eq>
587        + BlockAccessListsClient
588        + 'static,
589{
590    type Output = Vec<SealedBlockWithAccessList<Client::Block>>;
591
592    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
593        let this = self.get_mut();
594
595        // Complete the normal block range first, then issue a separate BAL hash-list request.
596        if this.block_result.is_none() &&
597            let Poll::Ready(blocks) = this.blocks.poll_unpin(cx)
598        {
599            this.block_result = Some(blocks);
600        }
601
602        this.poll_access_lists(cx);
603
604        if let Some(response) = this.take_response() {
605            return Poll::Ready(response)
606        }
607
608        Poll::Pending
609    }
610}
611
612/// Tracks an optional BAL range request and its completed result.
613enum OptionalBlockAccessListsState<Req> {
614    /// The block hashes needed for `GetBlockAccessLists` are not known yet.
615    WaitingForBlocks {
616        /// The BAL availability policy for the eventual request.
617        requirement: BalRequirement,
618    },
619    /// A `GetBlockAccessLists` request is in flight.
620    Pending(Req),
621    /// `None` means the block range is available but optional BAL data is not.
622    Ready(Option<WithPeerId<BlockAccessLists>>),
623}
624
625impl<Client> Debug for FetchFullBlockFuture<Client>
626where
627    Client: BlockClient<Header: Debug, Body: Debug>,
628{
629    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
630        f.debug_struct("FetchFullBlockFuture")
631            .field("hash", &self.hash)
632            .field("header", &self.header)
633            .field("body", &self.body)
634            .finish()
635    }
636}
637
638struct FullBlockRequest<Client>
639where
640    Client: BlockClient,
641{
642    header: Option<SingleHeaderRequest<<Client as HeadersClient>::Output>>,
643    body: Option<SingleBodyRequest<<Client as BodiesClient>::Output>>,
644}
645
646impl<Client> FullBlockRequest<Client>
647where
648    Client: BlockClient,
649{
650    fn poll(&mut self, cx: &mut Context<'_>) -> Poll<ResponseResult<Client::Header, Client::Body>> {
651        if let Some(fut) = Pin::new(&mut self.header).as_pin_mut() &&
652            let Poll::Ready(res) = fut.poll(cx)
653        {
654            self.header = None;
655            return Poll::Ready(ResponseResult::Header(res))
656        }
657
658        if let Some(fut) = Pin::new(&mut self.body).as_pin_mut() &&
659            let Poll::Ready(res) = fut.poll(cx)
660        {
661            self.body = None;
662            return Poll::Ready(ResponseResult::Body(res))
663        }
664
665        Poll::Pending
666    }
667}
668
669/// The result of a request for a single header or body. This is yielded by the `FullBlockRequest`
670/// future.
671enum ResponseResult<H, B> {
672    Header(PeerRequestResult<Option<H>>),
673    Body(PeerRequestResult<Option<B>>),
674}
675
676/// The response of a body request.
677#[derive(Debug)]
678enum BodyResponse<B> {
679    /// Already validated against transaction root of header
680    Validated(B),
681    /// Still needs to be validated against header
682    PendingValidation(WithPeerId<B>),
683}
684/// A future that downloads a range of full blocks from the network.
685///
686/// This first fetches the headers for the given range using the inner `Client`. Once the request
687/// is complete, it will fetch the bodies for the headers it received.
688///
689/// Once the bodies request completes, the [`SealedBlock`]s will be assembled and the future will
690/// yield the full block range.
691///
692/// The full block range will be returned with falling block numbers, i.e. in descending order.
693///
694/// NOTE: this assumes that bodies responses are returned by the client in the same order as the
695/// hash array used to request them.
696#[must_use = "futures do nothing unless polled"]
697#[expect(missing_debug_implementations)]
698pub struct FetchFullBlockRangeFuture<Client>
699where
700    Client: BlockClient,
701{
702    /// The client used to fetch headers and bodies.
703    client: Client,
704    /// The consensus instance used to validate the blocks.
705    consensus: Arc<dyn Consensus<Client::Block>>,
706    /// The block hash to start fetching from (inclusive).
707    start_hash: B256,
708    /// How many blocks to fetch: `len([start_hash, ..]) == count`
709    count: u64,
710    /// Requests for headers and bodies that are in progress.
711    request: FullBlockRangeRequest<Client>,
712    /// Fetched headers.
713    headers: Option<Vec<SealedHeader<Client::Header>>>,
714    /// The next headers to request bodies for. This is drained as responses are received.
715    pending_headers: VecDeque<SealedHeader<Client::Header>>,
716    /// The bodies that have been received so far.
717    bodies: HashMap<SealedHeader<Client::Header>, BodyResponse<Client::Body>>,
718}
719
720impl<Client> FetchFullBlockRangeFuture<Client>
721where
722    Client: BlockClient<Header: Debug + BlockHeader + Sealable + Clone + Hash + Eq>,
723{
724    /// Returns whether or not the bodies map is fully populated with requested headers and bodies.
725    fn is_bodies_complete(&self) -> bool {
726        self.bodies.len() == self.count as usize
727    }
728
729    /// Inserts a block body, matching it with the `next_header`.
730    ///
731    /// Note: this assumes the response matches the next header in the queue.
732    fn insert_body(&mut self, body_response: BodyResponse<Client::Body>) {
733        if let Some(header) = self.pending_headers.pop_front() {
734            self.bodies.insert(header, body_response);
735        }
736    }
737
738    /// Inserts multiple block bodies.
739    fn insert_bodies(&mut self, bodies: impl IntoIterator<Item = BodyResponse<Client::Body>>) {
740        for body in bodies {
741            self.insert_body(body);
742        }
743    }
744
745    /// Returns the remaining hashes for the bodies request, based on the headers that still exist
746    /// in the `root_map`.
747    fn remaining_bodies_hashes(&self) -> Vec<B256> {
748        self.pending_headers.iter().map(|h| h.hash()).collect()
749    }
750
751    /// Returns the [`SealedBlock`]s if the request is complete and valid.
752    ///
753    /// The request is complete if the number of blocks requested is equal to the number of blocks
754    /// received. The request is valid if the returned bodies match the roots in the headers.
755    ///
756    /// These are returned in falling order starting with the requested `hash`, i.e. with
757    /// descending block numbers.
758    fn take_blocks(&mut self) -> Option<Vec<SealedBlock<Client::Block>>> {
759        if !self.is_bodies_complete() {
760            // not done with bodies yet
761            return None
762        }
763
764        let headers = self.headers.take()?;
765        let mut needs_retry = false;
766        let mut valid_responses = Vec::new();
767
768        for header in &headers {
769            if let Some(body_resp) = self.bodies.remove(header) {
770                // validate body w.r.t. the hashes in the header, only inserting into the response
771                let body = match body_resp {
772                    BodyResponse::Validated(body) => body,
773                    BodyResponse::PendingValidation(resp) => {
774                        // ensure the block is valid, else retry
775                        if let Err(err) =
776                            self.consensus.validate_body_against_header(resp.data(), header)
777                        {
778                            debug!(target: "downloaders", %err, hash=?header.hash(), "Received wrong body in range response");
779                            self.client.report_bad_message(resp.peer_id());
780
781                            // get body that doesn't match, put back into vecdeque, and retry it
782                            self.pending_headers.push_back(header.clone());
783                            needs_retry = true;
784                            continue
785                        }
786
787                        resp.into_data()
788                    }
789                };
790
791                valid_responses
792                    .push(SealedBlock::<Client::Block>::from_sealed_parts(header.clone(), body));
793            }
794        }
795
796        if needs_retry {
797            // put response hashes back into bodies map since we aren't returning them as a
798            // response
799            for block in valid_responses {
800                let (header, body) = block.split_sealed_header_body();
801                self.bodies.insert(header, BodyResponse::Validated(body));
802            }
803
804            // put headers back since they were `take`n before
805            self.headers = Some(headers);
806
807            // create response for failing bodies
808            let hashes = self.remaining_bodies_hashes();
809            self.request.bodies = Some(self.client.get_block_bodies(hashes));
810            return None
811        }
812
813        Some(valid_responses)
814    }
815
816    fn on_headers_response(&mut self, headers: WithPeerId<Vec<Client::Header>>) {
817        let (peer, mut headers_falling) =
818            headers.map(|h| h.into_iter().map(SealedHeader::seal_slow).collect::<Vec<_>>()).split();
819
820        // fill in the response if it's the correct length
821        if headers_falling.len() == self.count as usize {
822            // sort headers from highest to lowest block number
823            headers_falling.sort_unstable_by_key(|h| Reverse(h.number()));
824
825            // check the starting hash
826            if headers_falling[0].hash() == self.start_hash {
827                let headers_rising = headers_falling.iter().rev().cloned().collect::<Vec<_>>();
828                // check if the downloaded headers are valid
829                if let Err(err) = self.consensus.validate_header_range(&headers_rising) {
830                    debug!(target: "downloaders", %err, ?self.start_hash, "Received bad header response");
831                    self.client.report_bad_message(peer);
832                    return
833                }
834
835                // get the bodies request so it can be polled later
836                let hashes = headers_falling.iter().map(|h| h.hash()).collect::<Vec<_>>();
837
838                // populate the pending headers
839                self.pending_headers = headers_falling.clone().into();
840
841                // set the actual request if it hasn't been started yet
842                if !self.has_bodies_request_started() {
843                    // request the bodies for the downloaded headers
844                    self.request.bodies = Some(self.client.get_block_bodies(hashes));
845                }
846
847                // set the headers response
848                self.headers = Some(headers_falling);
849            } else {
850                // received a different header than requested
851                self.client.report_bad_message(peer);
852            }
853        }
854    }
855
856    /// Returns whether or not a bodies request has been started, returning false if there is no
857    /// pending request.
858    const fn has_bodies_request_started(&self) -> bool {
859        self.request.bodies.is_some()
860    }
861
862    /// Returns the start hash for the request
863    pub const fn start_hash(&self) -> B256 {
864        self.start_hash
865    }
866
867    /// Returns the block count for the request
868    pub const fn count(&self) -> u64 {
869        self.count
870    }
871}
872
873impl<Client> Future for FetchFullBlockRangeFuture<Client>
874where
875    Client: BlockClient<Header: Debug + BlockHeader + Sealable + Clone + Hash + Eq> + 'static,
876{
877    type Output = Vec<SealedBlock<Client::Block>>;
878
879    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
880        let this = self.get_mut();
881
882        loop {
883            match ready!(this.request.poll(cx)) {
884                // This branch handles headers responses from peers - it first ensures that the
885                // starting hash and number of headers matches what we requested.
886                //
887                // If these don't match, we penalize the peer and retry the request.
888                // If they do match, we sort the headers by block number and start the request for
889                // the corresponding block bodies.
890                //
891                // The next result that should be yielded by `poll` is the bodies response.
892                RangeResponseResult::Header(res) => {
893                    match res {
894                        Ok(headers) => {
895                            this.on_headers_response(headers);
896                        }
897                        Err(err) => {
898                            debug!(target: "downloaders", %err, ?this.start_hash, "Header range download failed");
899                        }
900                    }
901
902                    if this.headers.is_none() {
903                        // did not receive a correct response yet, retry
904                        this.request.headers = Some(this.client.get_headers(
905                            HeadersRequest::falling(this.start_hash.into(), this.count),
906                        ));
907                    }
908                }
909                // This branch handles block body responses from peers - it first inserts the
910                // bodies into the `bodies` map, and then checks if the request is complete.
911                //
912                // If the request is not complete, and we need to request more bodies, we send
913                // a bodies request for the headers we don't yet have bodies for.
914                RangeResponseResult::Body(res) => {
915                    match res {
916                        Ok(bodies_resp) => {
917                            let (peer, new_bodies) = bodies_resp.split();
918
919                            // first insert the received bodies
920                            this.insert_bodies(
921                                new_bodies
922                                    .into_iter()
923                                    .map(|resp| WithPeerId::new(peer, resp))
924                                    .map(BodyResponse::PendingValidation),
925                            );
926
927                            if !this.is_bodies_complete() {
928                                // get remaining hashes so we can send the next request
929                                let req_hashes = this.remaining_bodies_hashes();
930
931                                // set a new request
932                                this.request.bodies = Some(this.client.get_block_bodies(req_hashes))
933                            }
934                        }
935                        Err(err) => {
936                            debug!(target: "downloaders", %err, ?this.start_hash, "Body range download failed");
937                        }
938                    }
939                    if this.request.bodies.is_none() && !this.is_bodies_complete() {
940                        // no pending bodies request (e.g., request error), retry remaining bodies
941                        // TODO: convert this into two futures, one which is a headers range
942                        // future, and one which is a bodies range future.
943                        //
944                        // The headers range future should yield the bodies range future.
945                        // The bodies range future should not have an Option<Vec<B256>>, it should
946                        // have a populated Vec<B256> from the successful headers range future.
947                        //
948                        // This is optimal because we can not send a bodies request without
949                        // first completing the headers request. This way we can get rid of the
950                        // following `if let Some`. A bodies request should never be sent before
951                        // the headers request completes, so this should always be `Some` anyways.
952                        let hashes = this.remaining_bodies_hashes();
953                        if !hashes.is_empty() {
954                            this.request.bodies = Some(this.client.get_block_bodies(hashes));
955                        }
956                    }
957                }
958            }
959
960            if let Some(res) = this.take_blocks() {
961                return Poll::Ready(res)
962            }
963        }
964    }
965}
966
967/// A request for a range of full blocks. Polling this will poll the inner headers and bodies
968/// futures until they return responses. It will return either the header or body result, depending
969/// on which future successfully returned.
970struct FullBlockRangeRequest<Client>
971where
972    Client: BlockClient,
973{
974    headers: Option<<Client as HeadersClient>::Output>,
975    bodies: Option<<Client as BodiesClient>::Output>,
976}
977
978impl<Client> FullBlockRangeRequest<Client>
979where
980    Client: BlockClient,
981{
982    fn poll(
983        &mut self,
984        cx: &mut Context<'_>,
985    ) -> Poll<RangeResponseResult<Client::Header, Client::Body>> {
986        if let Some(fut) = Pin::new(&mut self.headers).as_pin_mut() &&
987            let Poll::Ready(res) = fut.poll(cx)
988        {
989            self.headers = None;
990            return Poll::Ready(RangeResponseResult::Header(res))
991        }
992
993        if let Some(fut) = Pin::new(&mut self.bodies).as_pin_mut() &&
994            let Poll::Ready(res) = fut.poll(cx)
995        {
996            self.bodies = None;
997            return Poll::Ready(RangeResponseResult::Body(res))
998        }
999
1000        Poll::Pending
1001    }
1002}
1003
1004// The result of a request for headers or block bodies. This is yielded by the
1005// `FullBlockRangeRequest` future.
1006enum RangeResponseResult<H, B> {
1007    Header(PeerRequestResult<Vec<H>>),
1008    Body(PeerRequestResult<Vec<B>>),
1009}
1010
1011/// A headers+bodies client implementation that does nothing.
1012#[derive(Debug, Clone)]
1013#[non_exhaustive]
1014pub struct NoopFullBlockClient<Net = EthNetworkPrimitives>(PhantomData<Net>);
1015
1016/// Implements the `DownloadClient` trait for the `NoopFullBlockClient` struct.
1017impl<Net> DownloadClient for NoopFullBlockClient<Net>
1018where
1019    Net: Debug + Send + Sync,
1020{
1021    /// Reports a bad message received from a peer.
1022    ///
1023    /// # Arguments
1024    ///
1025    /// * `_peer_id` - Identifier for the peer sending the bad message (unused in this
1026    ///   implementation).
1027    fn report_bad_message(&self, _peer_id: PeerId) {}
1028
1029    /// Retrieves the number of connected peers.
1030    ///
1031    /// # Returns
1032    ///
1033    /// The number of connected peers, which is always zero in this implementation.
1034    fn num_connected_peers(&self) -> usize {
1035        0
1036    }
1037}
1038
1039/// Implements the `BodiesClient` trait for the `NoopFullBlockClient` struct.
1040impl<Net> BodiesClient for NoopFullBlockClient<Net>
1041where
1042    Net: NetworkPrimitives,
1043{
1044    type Body = Net::BlockBody;
1045    /// Defines the output type of the function.
1046    type Output = futures::future::Ready<PeerRequestResult<Vec<Self::Body>>>;
1047
1048    /// Retrieves block bodies based on provided hashes and priority.
1049    ///
1050    /// # Arguments
1051    ///
1052    /// * `_hashes` - A vector of block hashes (unused in this implementation).
1053    /// * `_priority` - Priority level for block body retrieval (unused in this implementation).
1054    ///
1055    /// # Returns
1056    ///
1057    /// A future containing an empty vector of block bodies and a randomly generated `PeerId`.
1058    fn get_block_bodies_with_priority_and_range_hint(
1059        &self,
1060        _hashes: Vec<B256>,
1061        _priority: Priority,
1062        _range_hint: Option<RangeInclusive<u64>>,
1063    ) -> Self::Output {
1064        // Create a future that immediately returns an empty vector of block bodies and a random
1065        // PeerId.
1066        futures::future::ready(Ok(WithPeerId::new(PeerId::random(), vec![])))
1067    }
1068}
1069
1070impl<Net> HeadersClient for NoopFullBlockClient<Net>
1071where
1072    Net: NetworkPrimitives,
1073{
1074    type Header = Net::BlockHeader;
1075    /// The output type representing a future containing a peer request result with a vector of
1076    /// headers.
1077    type Output = futures::future::Ready<PeerRequestResult<Vec<Self::Header>>>;
1078
1079    /// Retrieves headers with a specified priority level.
1080    ///
1081    /// This implementation does nothing and returns an empty vector of headers.
1082    ///
1083    /// # Arguments
1084    ///
1085    /// * `_request` - A request for headers (unused in this implementation).
1086    /// * `_priority` - The priority level for the headers request (unused in this implementation).
1087    ///
1088    /// # Returns
1089    ///
1090    /// Always returns a ready future with an empty vector of headers wrapped in a
1091    /// `PeerRequestResult`.
1092    fn get_headers_with_priority(
1093        &self,
1094        _request: HeadersRequest,
1095        _priority: Priority,
1096    ) -> Self::Output {
1097        futures::future::ready(Ok(WithPeerId::new(PeerId::random(), vec![])))
1098    }
1099}
1100
1101impl<Net> BlockClient for NoopFullBlockClient<Net>
1102where
1103    Net: NetworkPrimitives,
1104{
1105    type Block = Net::Block;
1106}
1107
1108impl<Net> BlockAccessListsClient for NoopFullBlockClient<Net>
1109where
1110    Net: NetworkPrimitives,
1111{
1112    type Output = futures::future::Ready<PeerRequestResult<BlockAccessLists>>;
1113
1114    fn get_block_access_lists_with_priority_and_requirement(
1115        &self,
1116        _hashes: Vec<B256>,
1117        _priority: Priority,
1118        _requirement: BalRequirement,
1119    ) -> Self::Output {
1120        futures::future::ready(Ok(WithPeerId::new(PeerId::random(), BlockAccessLists::default())))
1121    }
1122}
1123
1124impl<Net> Default for NoopFullBlockClient<Net> {
1125    fn default() -> Self {
1126        Self(PhantomData::<Net>)
1127    }
1128}
1129
1130/// Validates one raw block access-list entry against the block's access-list hash.
1131///
1132/// Returns `Ok(Some(_))` for a matching entry, `Ok(None)` when the block has no access-list hash
1133/// or the peer returned an unavailable entry, and `Err(peer)` for a hash mismatch that should be
1134/// reported as a bad message.
1135fn seal_block_access_list_for_block<B: Block>(
1136    block: &SealedBlock<B>,
1137    bal: WithPeerId<Option<Bytes>>,
1138) -> Result<Option<RawBal>, PeerId> {
1139    let Some(expected) = block.header().block_access_list_hash() else { return Ok(None) };
1140
1141    let (peer, bal) = bal.split();
1142    let Some(bal) = bal else { return Ok(None) };
1143    let raw_bal = RawBal::new(bal);
1144    raw_bal.ensure_hash(expected).map_err(|error| {
1145        debug!(
1146            target: "downloaders",
1147            block_hash = ?block.hash(),
1148            computed = ?error.computed,
1149            expected = ?error.expected,
1150            "Received block access list with wrong hash",
1151        );
1152        peer
1153    })?;
1154    Ok(Some(raw_bal))
1155}
1156
1157/// Wraps a block range with validated block access-list entries.
1158///
1159/// Short responses are treated as a valid prefix and the remaining blocks receive `None`.
1160/// Responses longer than the requested block range return the full block range without access-list
1161/// data. Non-empty hash mismatches stop accepting access-list data after reporting the peer, so any
1162/// already validated prefix is preserved.
1163fn seal_blocks_with_access_lists<Client>(
1164    client: &Client,
1165    blocks: Vec<SealedBlock<Client::Block>>,
1166    access_lists: Option<WithPeerId<BlockAccessLists>>,
1167) -> Vec<SealedBlockWithAccessList<Client::Block>>
1168where
1169    Client: BlockClient,
1170{
1171    let Some(access_lists) = access_lists else {
1172        return blocks.into_iter().map(SealedBlockWith::from_block).collect()
1173    };
1174
1175    let (peer, access_lists) = access_lists.split();
1176    let expected = blocks.len();
1177    let received = access_lists.0.len();
1178
1179    if received > expected {
1180        trace!(
1181            target: "downloaders",
1182            expected,
1183            received,
1184            "Ignoring overlong access list range response",
1185        );
1186        return blocks.into_iter().map(SealedBlockWith::from_block).collect()
1187    }
1188
1189    let mut access_lists = access_lists.0.into_iter();
1190    let mut blocks = blocks.into_iter();
1191    let mut response = Vec::with_capacity(expected);
1192
1193    for block in blocks.by_ref() {
1194        let Some(bal) = access_lists.next() else {
1195            // Short BAL responses are valid; the current block and all remaining blocks are
1196            // returned without access-list data below.
1197            response.push(SealedBlockWith::from_block(block));
1198            break
1199        };
1200
1201        match seal_block_access_list_for_block(&block, WithPeerId::new(peer, bal)) {
1202            Ok(raw_bal) => response.push(SealedBlockWith::new(block, raw_bal)),
1203            Err(peer) => {
1204                // A hash mismatch means this BAL entry is not for the current block. Stop matching
1205                // later positional entries and return the rest of the range without BAL data.
1206                client.report_bad_message(peer);
1207                response.push(SealedBlockWith::from_block(block));
1208                break
1209            }
1210        }
1211    }
1212
1213    response.extend(blocks.map(SealedBlockWith::from_block));
1214    response
1215}
1216
1217#[cfg(test)]
1218mod tests {
1219    use reth_ethereum_primitives::BlockBody;
1220
1221    use super::*;
1222    use crate::{error::RequestError, test_utils::TestFullBlockClient};
1223    use alloy_consensus::Header;
1224    use alloy_primitives::{keccak256, map::B256Map, Bytes};
1225    use parking_lot::Mutex;
1226    use std::{
1227        ops::Range,
1228        sync::{
1229            atomic::{AtomicBool, AtomicUsize, Ordering},
1230            Arc,
1231        },
1232    };
1233
1234    const EMPTY_LIST_CODE: u8 = 0xc0;
1235    use tokio::time::{timeout, Duration};
1236
1237    fn sealed_header_with_access_list_hash(bal: &Bytes) -> SealedHeader {
1238        let header =
1239            Header { block_access_list_hash: Some(keccak256(bal.as_ref())), ..Default::default() };
1240        SealedHeader::seal_slow(header)
1241    }
1242
1243    fn range_access_lists<B: Block>(
1244        blocks: &[SealedBlockWithAccessList<B>],
1245    ) -> Vec<Option<RawBal>> {
1246        blocks.iter().map(|block| block.data().clone()).collect()
1247    }
1248
1249    #[tokio::test]
1250    async fn download_single_full_block() {
1251        let client = TestFullBlockClient::default();
1252        let header: SealedHeader = SealedHeader::default();
1253        let body = BlockBody::default();
1254        client.insert(header.clone(), body.clone());
1255        let client = FullBlockClient::test_client(client);
1256
1257        let received = client.get_full_block(header.hash()).await;
1258        assert_eq!(received, SealedBlock::from_sealed_parts(header, body));
1259    }
1260
1261    #[tokio::test]
1262    async fn download_single_full_block_range() {
1263        let client = TestFullBlockClient::default();
1264        let header: SealedHeader = SealedHeader::default();
1265        let body = BlockBody::default();
1266        client.insert(header.clone(), body.clone());
1267        let client = FullBlockClient::test_client(client);
1268
1269        let received = client.get_full_block_range(header.hash(), 1).await;
1270        let received = received.first().expect("response should include a block");
1271        assert_eq!(*received, SealedBlock::from_sealed_parts(header, body));
1272    }
1273
1274    #[tokio::test]
1275    async fn download_single_full_block_with_access_lists() {
1276        let client = FullBlockWithAccessListsClient::default();
1277        let body = BlockBody::default();
1278        let bal = Bytes::from_static(&[EMPTY_LIST_CODE]);
1279        let header = sealed_header_with_access_list_hash(&bal);
1280        client.insert(header.clone(), body.clone(), bal.clone());
1281
1282        let request_count = Arc::clone(&client.access_list_requests);
1283        let client = FullBlockClient::test_client(client);
1284
1285        let received = client.get_full_block_with_access_lists(header.hash()).await;
1286        let expected_raw_bal = RawBal::from(bal);
1287
1288        assert_eq!(received.block(), &SealedBlock::from_sealed_parts(header, body));
1289        assert_eq!(received.data().as_ref(), Some(&expected_raw_bal));
1290        assert_eq!(request_count.load(Ordering::SeqCst), 1);
1291    }
1292
1293    #[tokio::test]
1294    async fn download_single_full_block_with_access_lists_uses_requested_requirement() {
1295        let client = FullBlockWithAccessListsClient::default();
1296        let body = BlockBody::default();
1297        let bal = Bytes::from_static(&[EMPTY_LIST_CODE]);
1298        let header = sealed_header_with_access_list_hash(&bal);
1299        client.insert(header.clone(), body.clone(), bal.clone());
1300
1301        let requirement = Arc::clone(&client.last_access_list_requirement);
1302        let client = FullBlockClient::test_client(client);
1303
1304        let received = client
1305            .get_full_block_with_access_lists_with_requirement(
1306                header.hash(),
1307                BalRequirement::Mandatory,
1308            )
1309            .await;
1310
1311        let expected_raw_bal = RawBal::from(bal);
1312        assert_eq!(received.block(), &SealedBlock::from_sealed_parts(header, body));
1313        assert_eq!(received.data().as_ref(), Some(&expected_raw_bal));
1314        assert_eq!(*requirement.lock(), Some(BalRequirement::Mandatory));
1315    }
1316
1317    #[tokio::test]
1318    async fn download_single_full_block_with_access_lists_waits_for_pending_access_lists() {
1319        let client = FullBlockWithAccessListsClient::default();
1320        client.set_access_list_pending_polls(1);
1321
1322        let body = BlockBody::default();
1323        let bal = Bytes::from_static(&[EMPTY_LIST_CODE]);
1324        let header = sealed_header_with_access_list_hash(&bal);
1325        client.insert(header.clone(), body.clone(), bal.clone());
1326
1327        let request_count = Arc::clone(&client.access_list_requests);
1328        let client = FullBlockClient::test_client(client);
1329
1330        let received =
1331            timeout(Duration::from_secs(1), client.get_full_block_with_access_lists(header.hash()))
1332                .await
1333                .expect("access list request should complete");
1334
1335        let expected_raw_bal = RawBal::from(bal);
1336        assert_eq!(received.block(), &SealedBlock::from_sealed_parts(header, body));
1337        assert_eq!(received.data().as_ref(), Some(&expected_raw_bal));
1338        assert_eq!(request_count.load(Ordering::SeqCst), 1);
1339    }
1340
1341    #[tokio::test]
1342    async fn download_single_full_block_with_access_lists_rejects_wrong_hash() {
1343        let client = FullBlockWithAccessListsClient::default();
1344        let body = BlockBody::default();
1345        let expected_bal = Bytes::from_static(&[EMPTY_LIST_CODE]);
1346        let wrong_bal = Bytes::from_static(&[0xc1, 0x01]);
1347        let header = sealed_header_with_access_list_hash(&expected_bal);
1348        client.insert(header.clone(), body.clone(), wrong_bal);
1349
1350        let bad_messages = Arc::clone(&client.bad_messages);
1351        let client = FullBlockClient::test_client(client);
1352
1353        let received =
1354            timeout(Duration::from_secs(1), client.get_full_block_with_access_lists(header.hash()))
1355                .await
1356                .expect("block request should complete without access lists");
1357
1358        assert_eq!(received.block(), &SealedBlock::from_sealed_parts(header, body));
1359        assert!(received.data().is_none());
1360        assert_eq!(bad_messages.load(Ordering::SeqCst), 1);
1361    }
1362
1363    #[tokio::test]
1364    async fn download_single_full_block_with_access_lists_treats_none_as_unavailable() {
1365        let client = FullBlockWithAccessListsClient::default();
1366        let body = BlockBody::default();
1367        let expected_bal = Bytes::from_static(&[0xc1, 0x01]);
1368        let header = sealed_header_with_access_list_hash(&expected_bal);
1369        client.inner.insert(header.clone(), body.clone());
1370
1371        let bad_messages = Arc::clone(&client.bad_messages);
1372        let client = FullBlockClient::test_client(client);
1373
1374        let received =
1375            timeout(Duration::from_secs(1), client.get_full_block_with_access_lists(header.hash()))
1376                .await
1377                .expect("block request should complete without access lists");
1378
1379        assert_eq!(received.block(), &SealedBlock::from_sealed_parts(header, body));
1380        assert!(received.data().is_none());
1381        assert_eq!(bad_messages.load(Ordering::SeqCst), 0);
1382    }
1383
1384    #[tokio::test]
1385    async fn download_single_full_block_with_access_lists_rejects_wrong_empty_list() {
1386        let client = FullBlockWithAccessListsClient::default();
1387        let body = BlockBody::default();
1388        let expected_bal = Bytes::from_static(&[0xc1, 0x01]);
1389        let wrong_empty_bal = Bytes::from_static(&[EMPTY_LIST_CODE]);
1390        let header = sealed_header_with_access_list_hash(&expected_bal);
1391        client.insert(header.clone(), body.clone(), wrong_empty_bal);
1392
1393        let bad_messages = Arc::clone(&client.bad_messages);
1394        let client = FullBlockClient::test_client(client);
1395
1396        let received =
1397            timeout(Duration::from_secs(1), client.get_full_block_with_access_lists(header.hash()))
1398                .await
1399                .expect("block request should complete without access lists");
1400
1401        assert_eq!(received.block(), &SealedBlock::from_sealed_parts(header, body));
1402        assert!(received.data().is_none());
1403        assert_eq!(bad_messages.load(Ordering::SeqCst), 1);
1404    }
1405
1406    #[tokio::test]
1407    async fn download_single_full_block_with_access_lists_returns_none_after_empty_response() {
1408        let client = FullBlockWithAccessListsClient::default();
1409        client.empty_first_response.store(true, Ordering::SeqCst);
1410
1411        let body = BlockBody::default();
1412        let bal = Bytes::from_static(&[EMPTY_LIST_CODE]);
1413        let header = sealed_header_with_access_list_hash(&bal);
1414        client.insert(header.clone(), body.clone(), bal.clone());
1415
1416        let request_count = Arc::clone(&client.access_list_requests);
1417        let bad_messages = Arc::clone(&client.bad_messages);
1418        let client = FullBlockClient::test_client(client);
1419
1420        let received =
1421            timeout(Duration::from_secs(1), client.get_full_block_with_access_lists(header.hash()))
1422                .await
1423                .expect("block request should complete without access lists");
1424
1425        assert_eq!(received.block(), &SealedBlock::from_sealed_parts(header, body));
1426        assert!(received.data().is_none());
1427        assert_eq!(request_count.load(Ordering::SeqCst), 1);
1428        assert_eq!(bad_messages.load(Ordering::SeqCst), 0);
1429    }
1430
1431    #[tokio::test]
1432    async fn download_single_full_block_with_access_lists_returns_block_when_unavailable() {
1433        let client = FullBlockWithAccessListsClient::default();
1434        client.set_access_lists_unsupported(true);
1435
1436        let body = BlockBody::default();
1437        let bal = Bytes::from_static(&[EMPTY_LIST_CODE]);
1438        let header = sealed_header_with_access_list_hash(&bal);
1439        client.insert(header.clone(), body.clone(), bal);
1440
1441        let request_count = Arc::clone(&client.access_list_requests);
1442        let requirement = Arc::clone(&client.last_access_list_requirement);
1443        let client = FullBlockClient::test_client(client);
1444
1445        let received =
1446            timeout(Duration::from_secs(1), client.get_full_block_with_access_lists(header.hash()))
1447                .await
1448                .expect("block request should complete without access lists");
1449
1450        assert_eq!(received.block(), &SealedBlock::from_sealed_parts(header, body));
1451        assert!(received.data().is_none());
1452        assert_eq!(request_count.load(Ordering::SeqCst), 1);
1453        assert_eq!(
1454            *requirement.lock(),
1455            Some(BalRequirement::Optional),
1456            "single block BAL lookup should be best-effort"
1457        );
1458    }
1459
1460    /// Inserts headers and returns the last header and block body.
1461    fn insert_headers_into_client(
1462        client: &TestFullBlockClient,
1463        range: Range<usize>,
1464    ) -> (SealedHeader, BlockBody) {
1465        let mut sealed_header: SealedHeader = SealedHeader::default();
1466        let body = BlockBody::default();
1467        for _ in range {
1468            let (mut header, hash) = sealed_header.split();
1469            // update to the next header
1470            header.parent_hash = hash;
1471            header.number += 1;
1472
1473            sealed_header = SealedHeader::seal_slow(header);
1474
1475            client.insert(sealed_header.clone(), body.clone());
1476        }
1477
1478        (sealed_header, body)
1479    }
1480
1481    #[derive(Clone, Debug)]
1482    struct FullBlockWithAccessListsClient {
1483        inner: TestFullBlockClient,
1484        access_lists: Arc<Mutex<B256Map<Bytes>>>,
1485        access_list_requests: Arc<AtomicUsize>,
1486        access_list_soft_limit: Arc<AtomicUsize>,
1487        access_list_pending_polls: Arc<AtomicUsize>,
1488        extra_access_list_entries: Arc<AtomicUsize>,
1489        unsupported_access_lists: Arc<AtomicBool>,
1490        last_access_list_requirement: Arc<Mutex<Option<BalRequirement>>>,
1491        bad_messages: Arc<AtomicUsize>,
1492        empty_first_response: Arc<AtomicBool>,
1493    }
1494
1495    impl Default for FullBlockWithAccessListsClient {
1496        fn default() -> Self {
1497            Self {
1498                inner: TestFullBlockClient::default(),
1499                access_lists: Arc::new(Mutex::new(B256Map::default())),
1500                access_list_requests: Arc::new(AtomicUsize::new(0)),
1501                access_list_soft_limit: Arc::new(AtomicUsize::new(usize::MAX)),
1502                access_list_pending_polls: Arc::new(AtomicUsize::new(0)),
1503                extra_access_list_entries: Arc::new(AtomicUsize::new(0)),
1504                unsupported_access_lists: Arc::new(AtomicBool::new(false)),
1505                last_access_list_requirement: Arc::new(Mutex::new(None)),
1506                bad_messages: Arc::new(AtomicUsize::new(0)),
1507                empty_first_response: Arc::new(AtomicBool::new(false)),
1508            }
1509        }
1510    }
1511
1512    impl FullBlockWithAccessListsClient {
1513        fn insert(&self, header: SealedHeader, body: BlockBody, bal: Bytes) {
1514            self.inner.insert(header.clone(), body);
1515            self.access_lists.lock().insert(header.hash(), bal);
1516        }
1517
1518        fn set_access_list_soft_limit(&self, limit: usize) {
1519            self.access_list_soft_limit.store(limit, Ordering::SeqCst);
1520        }
1521
1522        fn set_access_list_pending_polls(&self, polls: usize) {
1523            self.access_list_pending_polls.store(polls, Ordering::SeqCst);
1524        }
1525
1526        fn set_extra_access_list_entries(&self, count: usize) {
1527            self.extra_access_list_entries.store(count, Ordering::SeqCst);
1528        }
1529
1530        fn set_access_lists_unsupported(&self, unsupported: bool) {
1531            self.unsupported_access_lists.store(unsupported, Ordering::SeqCst);
1532        }
1533    }
1534
1535    /// Inserts headers with block access lists and returns the last header and block body.
1536    fn insert_headers_with_access_lists_into_client(
1537        client: &FullBlockWithAccessListsClient,
1538        range: Range<usize>,
1539    ) -> (SealedHeader, BlockBody) {
1540        let mut sealed_header: SealedHeader = SealedHeader::default();
1541        let body = BlockBody::default();
1542        for block_idx in range {
1543            let (mut header, hash) = sealed_header.split();
1544            header.parent_hash = hash;
1545            header.number += 1;
1546            let bal = Bytes::from(vec![0xc1, block_idx as u8]);
1547            header.block_access_list_hash = Some(keccak256(bal.as_ref()));
1548
1549            sealed_header = SealedHeader::seal_slow(header);
1550
1551            client.insert(sealed_header.clone(), body.clone(), bal);
1552        }
1553
1554        (sealed_header, body)
1555    }
1556
1557    impl DownloadClient for FullBlockWithAccessListsClient {
1558        fn report_bad_message(&self, peer_id: PeerId) {
1559            self.bad_messages.fetch_add(1, Ordering::SeqCst);
1560            self.inner.report_bad_message(peer_id);
1561        }
1562
1563        fn num_connected_peers(&self) -> usize {
1564            self.inner.num_connected_peers()
1565        }
1566    }
1567
1568    impl HeadersClient for FullBlockWithAccessListsClient {
1569        type Header = <TestFullBlockClient as HeadersClient>::Header;
1570        type Output = <TestFullBlockClient as HeadersClient>::Output;
1571
1572        fn get_headers_with_priority(
1573            &self,
1574            request: HeadersRequest,
1575            priority: Priority,
1576        ) -> Self::Output {
1577            self.inner.get_headers_with_priority(request, priority)
1578        }
1579    }
1580
1581    impl BodiesClient for FullBlockWithAccessListsClient {
1582        type Body = <TestFullBlockClient as BodiesClient>::Body;
1583        type Output = <TestFullBlockClient as BodiesClient>::Output;
1584
1585        fn get_block_bodies_with_priority_and_range_hint(
1586            &self,
1587            hashes: Vec<B256>,
1588            priority: Priority,
1589            range_hint: Option<RangeInclusive<u64>>,
1590        ) -> Self::Output {
1591            self.inner.get_block_bodies_with_priority_and_range_hint(hashes, priority, range_hint)
1592        }
1593    }
1594
1595    struct MaybePendingAccessLists {
1596        response: Option<PeerRequestResult<BlockAccessLists>>,
1597        pending_polls: usize,
1598    }
1599
1600    impl MaybePendingAccessLists {
1601        const fn new(response: PeerRequestResult<BlockAccessLists>, pending_polls: usize) -> Self {
1602            Self { response: Some(response), pending_polls }
1603        }
1604    }
1605
1606    impl std::future::Future for MaybePendingAccessLists {
1607        type Output = PeerRequestResult<BlockAccessLists>;
1608
1609        fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
1610            if self.pending_polls > 0 {
1611                self.pending_polls -= 1;
1612                cx.waker().wake_by_ref();
1613                return Poll::Pending
1614            }
1615
1616            Poll::Ready(self.response.take().expect("future polled after completion"))
1617        }
1618    }
1619
1620    impl BlockAccessListsClient for FullBlockWithAccessListsClient {
1621        type Output = MaybePendingAccessLists;
1622
1623        fn get_block_access_lists_with_priority_and_requirement(
1624            &self,
1625            hashes: Vec<B256>,
1626            _priority: Priority,
1627            requirement: BalRequirement,
1628        ) -> Self::Output {
1629            self.access_list_requests.fetch_add(1, Ordering::SeqCst);
1630            *self.last_access_list_requirement.lock() = Some(requirement);
1631            let pending_polls = self.access_list_pending_polls.swap(0, Ordering::SeqCst);
1632
1633            if self.unsupported_access_lists.load(Ordering::SeqCst) {
1634                return MaybePendingAccessLists::new(
1635                    Err(RequestError::UnsupportedCapability),
1636                    pending_polls,
1637                )
1638            }
1639
1640            if self.empty_first_response.swap(false, Ordering::SeqCst) {
1641                return MaybePendingAccessLists::new(
1642                    Ok(WithPeerId::new(PeerId::random(), BlockAccessLists(Vec::new()))),
1643                    pending_polls,
1644                )
1645            }
1646
1647            let mut access_lists: Vec<_> = hashes
1648                .into_iter()
1649                .take(self.access_list_soft_limit.load(Ordering::SeqCst))
1650                .map(|hash| self.access_lists.lock().get(&hash).cloned())
1651                .collect();
1652            for _ in 0..self.extra_access_list_entries.load(Ordering::SeqCst) {
1653                access_lists.push(None);
1654            }
1655
1656            MaybePendingAccessLists::new(
1657                Ok(WithPeerId::new(PeerId::random(), BlockAccessLists(access_lists))),
1658                pending_polls,
1659            )
1660        }
1661    }
1662
1663    impl BlockClient for FullBlockWithAccessListsClient {
1664        type Block = reth_ethereum_primitives::Block;
1665    }
1666
1667    #[derive(Clone, Debug)]
1668    struct FailingBodiesClient {
1669        inner: TestFullBlockClient,
1670        fail_on: usize,
1671        body_requests: Arc<AtomicUsize>,
1672    }
1673
1674    impl FailingBodiesClient {
1675        fn new(inner: TestFullBlockClient, fail_on: usize) -> Self {
1676            Self { inner, fail_on, body_requests: Arc::new(AtomicUsize::new(0)) }
1677        }
1678    }
1679
1680    impl DownloadClient for FailingBodiesClient {
1681        fn report_bad_message(&self, peer_id: PeerId) {
1682            self.inner.report_bad_message(peer_id);
1683        }
1684
1685        fn num_connected_peers(&self) -> usize {
1686            self.inner.num_connected_peers()
1687        }
1688    }
1689
1690    impl HeadersClient for FailingBodiesClient {
1691        type Header = <TestFullBlockClient as HeadersClient>::Header;
1692        type Output = <TestFullBlockClient as HeadersClient>::Output;
1693
1694        fn get_headers_with_priority(
1695            &self,
1696            request: HeadersRequest,
1697            priority: Priority,
1698        ) -> Self::Output {
1699            self.inner.get_headers_with_priority(request, priority)
1700        }
1701    }
1702
1703    impl BodiesClient for FailingBodiesClient {
1704        type Body = <TestFullBlockClient as BodiesClient>::Body;
1705        type Output = <TestFullBlockClient as BodiesClient>::Output;
1706
1707        fn get_block_bodies_with_priority_and_range_hint(
1708            &self,
1709            hashes: Vec<B256>,
1710            priority: Priority,
1711            range_hint: Option<RangeInclusive<u64>>,
1712        ) -> Self::Output {
1713            let attempt = self.body_requests.fetch_add(1, Ordering::SeqCst);
1714            if attempt == self.fail_on {
1715                return futures::future::ready(Err(RequestError::Timeout))
1716            }
1717
1718            self.inner.get_block_bodies_with_priority_and_range_hint(hashes, priority, range_hint)
1719        }
1720    }
1721
1722    impl BlockClient for FailingBodiesClient {
1723        type Block = reth_ethereum_primitives::Block;
1724    }
1725
1726    #[tokio::test]
1727    async fn download_full_block_range() {
1728        let client = TestFullBlockClient::default();
1729        let (header, body) = insert_headers_into_client(&client, 0..50);
1730        let client = FullBlockClient::test_client(client);
1731
1732        let received = client.get_full_block_range(header.hash(), 1).await;
1733        let received = received.first().expect("response should include a block");
1734        assert_eq!(*received, SealedBlock::from_sealed_parts(header.clone(), body));
1735
1736        let received = client.get_full_block_range(header.hash(), 10).await;
1737        assert_eq!(received.len(), 10);
1738        for (i, block) in received.iter().enumerate() {
1739            let expected_number = header.number - i as u64;
1740            assert_eq!(block.number, expected_number);
1741        }
1742    }
1743
1744    #[tokio::test]
1745    async fn download_full_block_range_over_soft_limit() {
1746        // default soft limit is 20, so we will request 50 blocks
1747        let client = TestFullBlockClient::default();
1748        let (header, body) = insert_headers_into_client(&client, 0..50);
1749        let client = FullBlockClient::test_client(client);
1750
1751        let received = client.get_full_block_range(header.hash(), 1).await;
1752        let received = received.first().expect("response should include a block");
1753        assert_eq!(*received, SealedBlock::from_sealed_parts(header.clone(), body));
1754
1755        let received = client.get_full_block_range(header.hash(), 50).await;
1756        assert_eq!(received.len(), 50);
1757        for (i, block) in received.iter().enumerate() {
1758            let expected_number = header.number - i as u64;
1759            assert_eq!(block.number, expected_number);
1760        }
1761    }
1762
1763    #[tokio::test]
1764    async fn download_full_block_range_retries_after_body_error() {
1765        let mut client = TestFullBlockClient::default();
1766        client.set_soft_limit(2);
1767        let (header, _) = insert_headers_into_client(&client, 0..3);
1768
1769        let client = FailingBodiesClient::new(client, 1);
1770        let body_requests = Arc::clone(&client.body_requests);
1771        let client = FullBlockClient::test_client(client);
1772
1773        let received =
1774            timeout(Duration::from_secs(1), client.get_full_block_range(header.hash(), 3))
1775                .await
1776                .expect("body request retry should complete");
1777
1778        assert_eq!(received.len(), 3);
1779        assert_eq!(body_requests.load(Ordering::SeqCst), 3);
1780    }
1781
1782    #[tokio::test]
1783    async fn download_full_block_range_with_access_lists() {
1784        let client = FullBlockWithAccessListsClient::default();
1785        let (header, _) = insert_headers_with_access_lists_into_client(&client, 0..3);
1786
1787        let access_lists = Arc::clone(&client.access_lists);
1788        let request_count = Arc::clone(&client.access_list_requests);
1789        let requirement = Arc::clone(&client.last_access_list_requirement);
1790        let client = FullBlockClient::test_client(client);
1791
1792        let response = timeout(
1793            Duration::from_secs(1),
1794            client.get_full_block_range_with_optional_access_lists(header.hash(), 3),
1795        )
1796        .await
1797        .expect("range request should complete");
1798
1799        let blocks = response;
1800        assert_eq!(blocks.len(), 3);
1801        let expected = {
1802            let bals = access_lists.lock();
1803            blocks
1804                .iter()
1805                .map(|block| {
1806                    let bal = bals.get(&block.block().hash()).cloned().expect("access list exists");
1807                    Some(RawBal::from(bal))
1808                })
1809                .collect::<Vec<_>>()
1810        };
1811        assert_eq!(range_access_lists(&blocks), expected);
1812        assert_eq!(request_count.load(Ordering::SeqCst), 1);
1813        assert_eq!(*requirement.lock(), Some(BalRequirement::Optional));
1814    }
1815
1816    #[tokio::test]
1817    async fn download_full_block_range_with_access_lists_returns_none_for_empty_response() {
1818        let client = FullBlockWithAccessListsClient::default();
1819        client.empty_first_response.store(true, Ordering::SeqCst);
1820        let (header, _) = insert_headers_with_access_lists_into_client(&client, 0..3);
1821
1822        let request_count = Arc::clone(&client.access_list_requests);
1823        let client = FullBlockClient::test_client(client);
1824
1825        let response = timeout(
1826            Duration::from_secs(1),
1827            client.get_full_block_range_with_optional_access_lists(header.hash(), 3),
1828        )
1829        .await
1830        .expect("range request should complete without access lists");
1831
1832        let blocks = response;
1833        assert_eq!(blocks.len(), 3);
1834        assert_eq!(range_access_lists(&blocks), vec![None; blocks.len()]);
1835        assert_eq!(request_count.load(Ordering::SeqCst), 1);
1836    }
1837
1838    #[tokio::test]
1839    async fn download_full_block_range_with_access_lists_uses_requested_requirement() {
1840        let client = FullBlockWithAccessListsClient::default();
1841        let (header, _) = insert_headers_with_access_lists_into_client(&client, 0..3);
1842
1843        let requirement = Arc::clone(&client.last_access_list_requirement);
1844        let client = FullBlockClient::test_client(client);
1845
1846        let blocks = timeout(
1847            Duration::from_secs(1),
1848            client.get_full_block_range_with_optional_access_lists_with_requirement(
1849                header.hash(),
1850                3,
1851                BalRequirement::Mandatory,
1852            ),
1853        )
1854        .await
1855        .expect("range request should complete");
1856
1857        assert_eq!(blocks.len(), 3);
1858        assert_eq!(*requirement.lock(), Some(BalRequirement::Mandatory));
1859    }
1860
1861    #[tokio::test]
1862    async fn download_full_block_range_with_access_lists_preserves_short_response() {
1863        let client = FullBlockWithAccessListsClient::default();
1864        client.set_access_list_soft_limit(2);
1865        let (header, _) = insert_headers_with_access_lists_into_client(&client, 0..5);
1866
1867        let access_lists = Arc::clone(&client.access_lists);
1868        let request_count = Arc::clone(&client.access_list_requests);
1869        let client = FullBlockClient::test_client(client);
1870
1871        let blocks = timeout(
1872            Duration::from_secs(1),
1873            client.get_full_block_range_with_optional_access_lists(header.hash(), 5),
1874        )
1875        .await
1876        .expect("range request should complete without access lists");
1877
1878        assert_eq!(blocks.len(), 5);
1879        let expected = {
1880            let bals = access_lists.lock();
1881            blocks
1882                .iter()
1883                .enumerate()
1884                .map(|(idx, block)| {
1885                    if idx >= 2 {
1886                        return None
1887                    }
1888
1889                    let bal = bals.get(&block.block().hash()).cloned().expect("access list exists");
1890                    Some(RawBal::from(bal))
1891                })
1892                .collect::<Vec<_>>()
1893        };
1894        assert_eq!(range_access_lists(&blocks), expected);
1895        assert_eq!(request_count.load(Ordering::SeqCst), 1);
1896    }
1897
1898    #[tokio::test]
1899    async fn download_full_block_range_with_access_lists_preserves_unavailable_entries() {
1900        let client = FullBlockWithAccessListsClient::default();
1901        let (header, _) = insert_headers_with_access_lists_into_client(&client, 0..3);
1902        client.access_lists.lock().remove(&header.hash());
1903
1904        let access_lists = Arc::clone(&client.access_lists);
1905        let bad_messages = Arc::clone(&client.bad_messages);
1906        let client = FullBlockClient::test_client(client);
1907
1908        let blocks = timeout(
1909            Duration::from_secs(1),
1910            client.get_full_block_range_with_optional_access_lists(header.hash(), 3),
1911        )
1912        .await
1913        .expect("range request should complete");
1914
1915        assert_eq!(blocks.len(), 3);
1916        let expected = {
1917            let bals = access_lists.lock();
1918            blocks
1919                .iter()
1920                .map(|block| {
1921                    if block.block().hash() == header.hash() {
1922                        return None
1923                    }
1924
1925                    let bal = bals.get(&block.block().hash()).cloned().expect("access list exists");
1926                    Some(RawBal::from(bal))
1927                })
1928                .collect::<Vec<_>>()
1929        };
1930        assert_eq!(range_access_lists(&blocks), expected);
1931        assert_eq!(bad_messages.load(Ordering::SeqCst), 0);
1932    }
1933
1934    #[tokio::test]
1935    async fn download_full_block_range_with_access_lists_returns_none_when_unavailable() {
1936        let client = FullBlockWithAccessListsClient::default();
1937        client.set_access_lists_unsupported(true);
1938        let (header, _) = insert_headers_with_access_lists_into_client(&client, 0..3);
1939
1940        let request_count = Arc::clone(&client.access_list_requests);
1941        let client = FullBlockClient::test_client(client);
1942
1943        let blocks = timeout(
1944            Duration::from_secs(1),
1945            client.get_full_block_range_with_optional_access_lists(header.hash(), 3),
1946        )
1947        .await
1948        .expect("range request should complete without access lists");
1949
1950        assert_eq!(blocks.len(), 3);
1951        assert_eq!(range_access_lists(&blocks), vec![None; blocks.len()]);
1952        assert_eq!(request_count.load(Ordering::SeqCst), 1);
1953    }
1954
1955    #[tokio::test]
1956    async fn download_full_block_range_with_access_lists_ignores_long_response() {
1957        let client = FullBlockWithAccessListsClient::default();
1958        client.set_extra_access_list_entries(1);
1959        let (header, _) = insert_headers_with_access_lists_into_client(&client, 0..3);
1960
1961        let request_count = Arc::clone(&client.access_list_requests);
1962        let bad_messages = Arc::clone(&client.bad_messages);
1963        let client = FullBlockClient::test_client(client);
1964
1965        let blocks = timeout(
1966            Duration::from_secs(1),
1967            client.get_full_block_range_with_optional_access_lists(header.hash(), 3),
1968        )
1969        .await
1970        .expect("range request should complete without access lists");
1971
1972        assert_eq!(blocks.len(), 3);
1973        assert_eq!(range_access_lists(&blocks), vec![None; blocks.len()]);
1974        assert_eq!(request_count.load(Ordering::SeqCst), 1);
1975        assert_eq!(bad_messages.load(Ordering::SeqCst), 0);
1976    }
1977
1978    #[tokio::test]
1979    async fn download_full_block_range_with_access_lists_rejects_wrong_hash() {
1980        let client = FullBlockWithAccessListsClient::default();
1981        let (header, _) = insert_headers_with_access_lists_into_client(&client, 0..3);
1982        client.access_lists.lock().insert(header.hash(), Bytes::from_static(&[0xc1, 0x7f]));
1983
1984        let bad_messages = Arc::clone(&client.bad_messages);
1985        let client = FullBlockClient::test_client(client);
1986
1987        let blocks = timeout(
1988            Duration::from_secs(1),
1989            client.get_full_block_range_with_optional_access_lists(header.hash(), 3),
1990        )
1991        .await
1992        .expect("range request should complete without access lists");
1993
1994        assert_eq!(blocks.len(), 3);
1995        assert_eq!(range_access_lists(&blocks), vec![None; blocks.len()]);
1996        assert_eq!(bad_messages.load(Ordering::SeqCst), 1);
1997    }
1998
1999    #[tokio::test]
2000    async fn download_full_block_range_with_access_lists_preserves_valid_prefix_until_wrong_hash() {
2001        let client = FullBlockWithAccessListsClient::default();
2002        let (header, _) = insert_headers_with_access_lists_into_client(&client, 0..3);
2003        let first_bal =
2004            client.access_lists.lock().get(&header.hash()).cloned().expect("access list exists");
2005        let second_hash = header.parent_hash;
2006        client.access_lists.lock().insert(second_hash, Bytes::from_static(&[0xc1, 0x7f]));
2007
2008        let bad_messages = Arc::clone(&client.bad_messages);
2009        let client = FullBlockClient::test_client(client);
2010
2011        let blocks = timeout(
2012            Duration::from_secs(1),
2013            client.get_full_block_range_with_optional_access_lists(header.hash(), 3),
2014        )
2015        .await
2016        .expect("range request should complete without unvalidated access lists");
2017
2018        assert_eq!(blocks.len(), 3);
2019        assert_eq!(blocks[1].block().hash(), second_hash);
2020        assert_eq!(range_access_lists(&blocks), vec![Some(RawBal::from(first_bal)), None, None]);
2021        assert_eq!(bad_messages.load(Ordering::SeqCst), 1);
2022    }
2023
2024    #[tokio::test]
2025    async fn download_full_block_range_rejects_invalid_headers() {
2026        let client = TestFullBlockClient::default();
2027        let (header, _) = insert_headers_into_client(&client, 0..3);
2028        let consensus = reth_consensus::test_utils::TestConsensus::default();
2029        consensus.set_fail_validation(true);
2030        let client = FullBlockClient::new(client, Arc::new(consensus));
2031        let mut request = client.get_full_block_range(header.hash(), 3);
2032        let headers = client
2033            .client
2034            .get_headers(HeadersRequest::falling(header.hash().into(), 3))
2035            .await
2036            .unwrap();
2037
2038        request.on_headers_response(headers);
2039
2040        assert!(request.headers.is_none());
2041        assert!(request.pending_headers.is_empty());
2042        assert!(request.request.bodies.is_none());
2043    }
2044}